diff --git a/.all-contributorsrc b/.all-contributorsrc index 8f50d57..7780dd0 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -341,6 +341,15 @@ "bug", "ideas" ] + }, + { + "login": "TrustNoOneElse", + "name": "Florian Pabst", + "avatar_url": "https://avatars.githubusercontent.com/u/25935352?v=4", + "profile": "https://github.com/TrustNoOneElse", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/README.md b/README.md index e0b7702..46f5c6a 100644 --- a/README.md +++ b/README.md @@ -185,49 +185,52 @@ Thanks goes to these people ([emoji key][emojis]): - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + + + +
Tim Deschryver
Tim Deschryver

๐Ÿ’ป ๐Ÿ“– ๐Ÿš‡ โš ๏ธ
Michaรซl De Boey
Michaรซl De Boey

๐Ÿ“–
Ignacio Le Fluk
Ignacio Le Fluk

๐Ÿ’ป โš ๏ธ
Tamรกs Szabรณ
Tamรกs Szabรณ

๐Ÿ’ป
Gregor Woiwode
Gregor Woiwode

๐Ÿ’ป
Toni Villena
Toni Villena

๐Ÿ› ๐Ÿ’ป ๐Ÿ“– โš ๏ธ
ShPelles
ShPelles

๐Ÿ“–
Tim Deschryver
Tim Deschryver

๐Ÿ’ป ๐Ÿ“– ๐Ÿš‡ โš ๏ธ
Michaรซl De Boey
Michaรซl De Boey

๐Ÿ“–
Ignacio Le Fluk
Ignacio Le Fluk

๐Ÿ’ป โš ๏ธ
Tamรกs Szabรณ
Tamรกs Szabรณ

๐Ÿ’ป
Gregor Woiwode
Gregor Woiwode

๐Ÿ’ป
Toni Villena
Toni Villena

๐Ÿ› ๐Ÿ’ป ๐Ÿ“– โš ๏ธ
ShPelles
ShPelles

๐Ÿ“–
Miluoshi
Miluoshi

๐Ÿ’ป โš ๏ธ
Nick McCurdy
Nick McCurdy

๐Ÿ“–
Srinivasan Sekar
Srinivasan Sekar

๐Ÿ“–
Bitcollage
Bitcollage

๐Ÿ“–
Emil Sundin
Emil Sundin

๐Ÿ’ป
Ombrax
Ombrax

๐Ÿ’ป
Rafael Santana
Rafael Santana

๐Ÿ’ป โš ๏ธ ๐Ÿ›
Miluoshi
Miluoshi

๐Ÿ’ป โš ๏ธ
Nick McCurdy
Nick McCurdy

๐Ÿ“–
Srinivasan Sekar
Srinivasan Sekar

๐Ÿ“–
Bitcollage
Bitcollage

๐Ÿ“–
Emil Sundin
Emil Sundin

๐Ÿ’ป
Ombrax
Ombrax

๐Ÿ’ป
Rafael Santana
Rafael Santana

๐Ÿ’ป โš ๏ธ ๐Ÿ›
Benjamin Blackwood
Benjamin Blackwood

๐Ÿ“– โš ๏ธ
Gustavo Porto
Gustavo Porto

๐Ÿ“–
Bo Vandersteene
Bo Vandersteene

๐Ÿ’ป
Janek
Janek

๐Ÿ’ป โš ๏ธ
Gleb Irovich
Gleb Irovich

๐Ÿ’ป โš ๏ธ
Arjen
Arjen

๐Ÿ’ป ๐Ÿšง
Suguru Inatomi
Suguru Inatomi

๐Ÿ’ป ๐Ÿค”
Benjamin Blackwood
Benjamin Blackwood

๐Ÿ“– โš ๏ธ
Gustavo Porto
Gustavo Porto

๐Ÿ“–
Bo Vandersteene
Bo Vandersteene

๐Ÿ’ป
Janek
Janek

๐Ÿ’ป โš ๏ธ
Gleb Irovich
Gleb Irovich

๐Ÿ’ป โš ๏ธ
Arjen
Arjen

๐Ÿ’ป ๐Ÿšง
Suguru Inatomi
Suguru Inatomi

๐Ÿ’ป ๐Ÿค”
Amit Miran
Amit Miran

๐Ÿš‡
Jan-Willem Willebrands
Jan-Willem Willebrands

๐Ÿ’ป
Sandro
Sandro

๐Ÿ’ป ๐Ÿ›
Michael Westphal
Michael Westphal

๐Ÿ’ป โš ๏ธ
Lukas
Lukas

๐Ÿ’ป
Matan Borenkraout
Matan Borenkraout

๐Ÿšง
mleimer
mleimer

๐Ÿ“– โš ๏ธ
Amit Miran
Amit Miran

๐Ÿš‡
Jan-Willem Willebrands
Jan-Willem Willebrands

๐Ÿ’ป
Sandro
Sandro

๐Ÿ’ป ๐Ÿ›
Michael Westphal
Michael Westphal

๐Ÿ’ป โš ๏ธ
Lukas
Lukas

๐Ÿ’ป
Matan Borenkraout
Matan Borenkraout

๐Ÿšง
mleimer
mleimer

๐Ÿ“– โš ๏ธ
MeIr
MeIr

๐Ÿ› โš ๏ธ
John Dengis
John Dengis

๐Ÿ’ป โš ๏ธ
Rokas Brazdลพionis
Rokas Brazdลพionis

๐Ÿ’ป
Mateus Duraes
Mateus Duraes

๐Ÿ’ป
Josh Joseph
Josh Joseph

๐Ÿ’ป โš ๏ธ
Torsten Knauf
Torsten Knauf

๐Ÿšง
antischematic
antischematic

๐Ÿ› ๐Ÿค”
MeIr
MeIr

๐Ÿ› โš ๏ธ
John Dengis
John Dengis

๐Ÿ’ป โš ๏ธ
Rokas Brazdลพionis
Rokas Brazdลพionis

๐Ÿ’ป
Mateus Duraes
Mateus Duraes

๐Ÿ’ป
Josh Joseph
Josh Joseph

๐Ÿ’ป โš ๏ธ
Torsten Knauf
Torsten Knauf

๐Ÿšง
antischematic
antischematic

๐Ÿ› ๐Ÿค”
Florian Pabst
Florian Pabst

๐Ÿ’ป
diff --git a/apps/example-app/src/app/examples/03-forms.ts b/apps/example-app/src/app/examples/03-forms.ts index df861a3..cbf0a31 100644 --- a/apps/example-app/src/app/examples/03-forms.ts +++ b/apps/example-app/src/app/examples/03-forms.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { UntypedFormBuilder, Validators } from '@angular/forms'; +import { FormBuilder, Validators } from '@angular/forms'; @Component({ selector: 'app-fixture', @@ -35,13 +35,14 @@ export class FormsComponent { { id: 'B', value: 'Blue' }, { id: 'G', value: 'Green' }, ]; + form = this.formBuilder.group({ - name: ['', Validators.required], + name: ['', [Validators.required]], score: [0, { validators: [Validators.min(1), Validators.max(10)], updateOn: 'blur' }], - color: ['', Validators.required], + color: [null as string | null, Validators.required], }); - constructor(private formBuilder: UntypedFormBuilder) {} + constructor(private formBuilder: FormBuilder) {} get formErrors() { return Object.keys(this.form.controls) diff --git a/apps/example-app/src/app/examples/04-forms-with-material.spec.ts b/apps/example-app/src/app/examples/04-forms-with-material.spec.ts index 64ace4a..e1e16e3 100644 --- a/apps/example-app/src/app/examples/04-forms-with-material.spec.ts +++ b/apps/example-app/src/app/examples/04-forms-with-material.spec.ts @@ -13,12 +13,14 @@ test('is possible to fill in a form and verify error messages (with the help of const scoreControl = screen.getByRole('spinbutton', { name: /score/i }); const colorControl = screen.getByPlaceholderText(/color/i); const dateControl = screen.getByRole('textbox', { name: /Choose a date/i }); + const checkboxControl = screen.getByRole('checkbox', { name: /agree/i }); const errors = screen.getByRole('alert'); expect(errors).toContainElement(screen.queryByText('name is required')); expect(errors).toContainElement(screen.queryByText('score must be greater than 1')); expect(errors).toContainElement(screen.queryByText('color is required')); + expect(errors).toContainElement(screen.queryByText('agree is required')); userEvent.type(nameControl, 'Tim'); userEvent.clear(scoreControl); @@ -26,9 +28,15 @@ test('is possible to fill in a form and verify error messages (with the help of userEvent.click(colorControl); userEvent.click(screen.getByText(/green/i)); + expect(checkboxControl).not.toBeChecked(); + userEvent.click(checkboxControl); + expect(checkboxControl).toBeChecked(); + expect(checkboxControl).toBeValid(); + expect(screen.queryByText('name is required')).not.toBeInTheDocument(); expect(screen.getByText('score must be lesser than 10')).toBeInTheDocument(); expect(screen.queryByText('color is required')).not.toBeInTheDocument(); + expect(screen.queryByText('agree is required')).not.toBeInTheDocument(); expect(scoreControl).toBeInvalid(); userEvent.clear(scoreControl); @@ -42,6 +50,7 @@ test('is possible to fill in a form and verify error messages (with the help of expect(nameControl).toHaveValue('Tim'); expect(scoreControl).toHaveValue(7); expect(colorControl).toHaveTextContent('Green'); + expect(checkboxControl).toBeChecked(); const form = screen.getByRole('form'); expect(form).toHaveFormValues({ @@ -50,6 +59,7 @@ test('is possible to fill in a form and verify error messages (with the help of }); // material doesn't add these to the form + expect((fixture.componentInstance as MaterialFormsComponent).form?.get('agree')?.value).toBe(true); expect((fixture.componentInstance as MaterialFormsComponent).form?.get('color')?.value).toBe('G'); expect((fixture.componentInstance as MaterialFormsComponent).form?.get('date')?.value).toEqual(new Date(2022, 7, 11)); }); @@ -64,22 +74,29 @@ test('set and show pre-set form values', async () => { score: 4, color: 'B', date: new Date(2022, 7, 11), + agree: true, }); detectChanges(); const nameControl = screen.getByLabelText(/name/i); const scoreControl = screen.getByRole('spinbutton', { name: /score/i }); const colorControl = screen.getByPlaceholderText(/color/i); + const checkboxControl = screen.getByRole('checkbox', { name: /agree/i }); expect(nameControl).toHaveValue('Max'); expect(scoreControl).toHaveValue(4); expect(colorControl).toHaveTextContent('Blue'); + expect(checkboxControl).toBeChecked(); + userEvent.click(checkboxControl); const form = screen.getByRole('form'); expect(form).toHaveFormValues({ name: 'Max', score: 4, }); + + // material doesn't add these to the form + expect((fixture.componentInstance as MaterialFormsComponent).form?.get('agree')?.value).toBe(false); expect((fixture.componentInstance as MaterialFormsComponent).form?.get('color')?.value).toBe('B'); expect((fixture.componentInstance as MaterialFormsComponent).form?.get('date')?.value).toEqual(new Date(2022, 7, 11)); }); diff --git a/apps/example-app/src/app/examples/04-forms-with-material.ts b/apps/example-app/src/app/examples/04-forms-with-material.ts index ed510d1..852a345 100644 --- a/apps/example-app/src/app/examples/04-forms-with-material.ts +++ b/apps/example-app/src/app/examples/04-forms-with-material.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { UntypedFormBuilder, Validators } from '@angular/forms'; +import { FormBuilder, Validators } from '@angular/forms'; @Component({ selector: 'app-fixture', @@ -10,6 +10,8 @@ import { UntypedFormBuilder, Validators } from '@angular/forms'; + I Agree + Score - + Current Count: {{ counter }} + `, +}) +class AppComponent { + @Input() counter = 0; + + increment() { + this.counter += 1; + } + + decrement() { + this.counter -= 1; + } +} + +describe('Counter', () => { + it('should render counter', async () => { + await render(AppComponent, { + componentProperties: { counter: 5 }, + }); + + expect(screen.getByText('Current Count: 5')).toBeInTheDocument(); + }); + + it('should increment the counter on click', async () => { + await render(AppComponent, { + componentProperties: { counter: 5 }, + }); + + fireEvent.click(screen.getByText('+')); + + expect(screen.getByText('Current Count: 6')).toBeInTheDocument(); + }); +}); diff --git a/apps/example-app/src/app/material.module.ts b/apps/example-app/src/app/material.module.ts index 297c9d7..51a9189 100644 --- a/apps/example-app/src/app/material.module.ts +++ b/apps/example-app/src/app/material.module.ts @@ -1,11 +1,12 @@ import { NgModule } from '@angular/core'; +import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; import { MatDatepickerModule } from '@angular/material/datepicker'; import { MatNativeDateModule } from '@angular/material/core'; @NgModule({ - exports: [MatInputModule, MatSelectModule, MatDatepickerModule, MatNativeDateModule], + exports: [MatInputModule, MatSelectModule, MatDatepickerModule, MatNativeDateModule, MatCheckboxModule], }) export class MaterialModule {} diff --git a/projects/testing-library/src/lib/models.ts b/projects/testing-library/src/lib/models.ts index 51fe5f7..0e4b404 100644 --- a/projects/testing-library/src/lib/models.ts +++ b/projects/testing-library/src/lib/models.ts @@ -328,6 +328,21 @@ export interface RenderComponentOptions( routes = [], removeAngularAttributes = false, defaultImports = [], + initialRoute = '', } = { ...globalConfig, ...renderOptions }; dtlConfigure({ @@ -107,6 +108,8 @@ export async function render( const zone = safeInject(NgZone); const router = safeInject(Router); + if (initialRoute) await router.navigate([initialRoute]); + if (typeof router?.initialNavigation === 'function') { if (zone) { zone.run(() => router.initialNavigation()); diff --git a/projects/testing-library/tests/render.spec.ts b/projects/testing-library/tests/render.spec.ts index 12a1f62..28e5996 100644 --- a/projects/testing-library/tests/render.spec.ts +++ b/projects/testing-library/tests/render.spec.ts @@ -12,6 +12,7 @@ import { import { NoopAnimationsModule, BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { TestBed } from '@angular/core/testing'; import { render, fireEvent, screen } from '../src/public_api'; +import { Resolve, RouterModule } from '@angular/router'; @Component({ selector: 'atl-fixture', @@ -296,3 +297,48 @@ describe('DebugElement', () => { expect(view.debugElement.componentInstance).toBeInstanceOf(FixtureComponent); }); }); + +describe('initialRoute', () => { + @Component({ + standalone: true, + selector: 'atl-fixture2', + template: ``, + }) + class SecondaryFixtureComponent {} + + @Component({ + standalone: true, + selector: 'atl-router-fixture', + template: ``, + imports: [RouterModule], + }) + class RouterFixtureComponent {} + + @Injectable() + class FixtureResolver implements Resolve { + public isResolved = false; + + public resolve() { + this.isResolved = true; + } + } + + it('allows initially rendering a specific route to avoid triggering a resolver for the default route', async () => { + const initialRoute = 'initial-route'; + const routes = [ + { path: initialRoute, component: FixtureComponent }, + { path: '**', resolve: { data: FixtureResolver }, component: SecondaryFixtureComponent }, + ]; + + await render(RouterFixtureComponent, { + initialRoute, + routes, + providers: [FixtureResolver], + }); + const resolver = TestBed.inject(FixtureResolver); + + expect(resolver.isResolved).toBe(false); + expect(screen.queryByText('Secondary Component')).not.toBeInTheDocument(); + expect(screen.getByText('button')).toBeInTheDocument(); + }); +});