From a9237f22dd6586e1c7b012fae74ce5a2e03c5abc Mon Sep 17 00:00:00 2001 From: Tim Deschryver <28659384+timdeschryver@users.noreply.github.com> Date: Thu, 9 Dec 2021 15:53:14 +0100 Subject: [PATCH 1/9] test: add test case for #269 (#271) Closes #269 --- .../src/app/examples/18-html-as-input.spec.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 apps/example-app/src/app/examples/18-html-as-input.spec.ts diff --git a/apps/example-app/src/app/examples/18-html-as-input.spec.ts b/apps/example-app/src/app/examples/18-html-as-input.spec.ts new file mode 100644 index 00000000..5a56b412 --- /dev/null +++ b/apps/example-app/src/app/examples/18-html-as-input.spec.ts @@ -0,0 +1,36 @@ +import { render, screen } from '@testing-library/angular'; +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'stripHTML', +}) +class StripHTMLPipe implements PipeTransform { + transform(stringValueWithHTML: string): string { + return stringValueWithHTML.replace(/<[^>]*>?/gm, ''); + } +} + +const STRING_WITH_HTML = + 'Some database field
with stripped HTML
'; + +// https://github.com/testing-library/angular-testing-library/pull/271 +test('passes HTML as component properties', async () => { + await render(`

{{ stringWithHtml | stripHTML }}

`, { + componentProperties: { + stringWithHtml: STRING_WITH_HTML, + }, + declarations: [StripHTMLPipe], + }); + + expect(screen.getByText('Some database field with stripped HTML')).toBeInTheDocument(); +}); + + +test('throws when passed HTML is passed in directly', async () => { + await expect(() => + render(`

{{ '${STRING_WITH_HTML}' | stripHTML }}

`, { + declarations: [StripHTMLPipe], + }), + ).rejects.toThrow(); +}); + From c9af6efaab15fa9571c6902f56c7ba81bb281386 Mon Sep 17 00:00:00 2001 From: Tim Deschryver <28659384+timdeschryver@users.noreply.github.com> Date: Thu, 9 Dec 2021 15:54:03 +0100 Subject: [PATCH 2/9] refactor: enable strict mode (#274) Closes #261 --- apps/example-app-karma/src/test.ts | 6 +- .../src/app/examples/01-nested-component.ts | 4 +- apps/example-app/src/app/examples/03-forms.ts | 6 +- .../examples/04-forms-with-material.spec.ts | 3 +- .../app/examples/04-forms-with-material.ts | 6 +- .../src/app/examples/06-with-ngrx-store.ts | 6 +- .../src/app/examples/08-directive.spec.ts | 8 +- .../src/app/examples/12-service-component.ts | 4 +- .../app/examples/15-dialog.component.spec.ts | 4 +- .../app/examples/16-input-getter-setter.ts | 4 +- .../src/app/issues/issue-254.spec.ts | 45 ++++++------ projects/testing-library/.eslintrc.json | 7 ++ projects/testing-library/src/lib/models.ts | 4 +- .../src/lib/testing-library.ts | 73 ++++++++++--------- .../tests/auto-cleanup.spec.ts | 2 +- projects/testing-library/tests/change.spec.ts | 6 +- projects/testing-library/tests/config.spec.ts | 6 +- .../testing-library/tests/integration.spec.ts | 6 +- .../tests/issues/issue-188.spec.ts | 4 +- .../testing-library/tests/rerender.spec.ts | 2 +- tsconfig.base.json | 7 +- 21 files changed, 108 insertions(+), 105 deletions(-) diff --git a/apps/example-app-karma/src/test.ts b/apps/example-app-karma/src/test.ts index 0a45adb5..827caf48 100644 --- a/apps/example-app-karma/src/test.ts +++ b/apps/example-app-karma/src/test.ts @@ -2,11 +2,7 @@ import 'zone.js/dist/zone-testing'; import { getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; -import JasmineDOM from '@testing-library/jasmine-dom/dist'; - -beforeAll(() => { - (jasmine.getEnv() as any).addMatchers(JasmineDOM); -}); +import '@testing-library/jasmine-dom'; declare const require: any; diff --git a/apps/example-app/src/app/examples/01-nested-component.ts b/apps/example-app/src/app/examples/01-nested-component.ts index 5b0faeb2..51087b44 100644 --- a/apps/example-app/src/app/examples/01-nested-component.ts +++ b/apps/example-app/src/app/examples/01-nested-component.ts @@ -5,7 +5,7 @@ import { Component, Input, Output, EventEmitter } from '@angular/core'; template: ' ', }) export class NestedButtonComponent { - @Input() name: string; + @Input() name = ''; @Output() raise = new EventEmitter(); } @@ -14,7 +14,7 @@ export class NestedButtonComponent { template: ' {{ value }} ', }) export class NestedValueComponent { - @Input() value: number; + @Input() value?: number; } @Component({ diff --git a/apps/example-app/src/app/examples/03-forms.ts b/apps/example-app/src/app/examples/03-forms.ts index bc035a14..f68766e4 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 { FormBuilder, Validators, ValidationErrors } from '@angular/forms'; +import { FormBuilder, Validators } from '@angular/forms'; @Component({ selector: 'app-fixture', @@ -46,8 +46,8 @@ export class FormsComponent { get formErrors() { return Object.keys(this.form.controls) .map((formKey) => { - const controlErrors: ValidationErrors = this.form.get(formKey).errors; - if (controlErrors != null) { + const controlErrors = this.form.get(formKey)?.errors; + if (controlErrors) { return Object.keys(controlErrors).map((keyError) => { const error = controlErrors[keyError]; switch (keyError) { 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 c215d9c4..ee4209b9 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 @@ -44,6 +44,5 @@ test('is possible to fill in a form and verify error messages (with the help of score: 7, }); - // not added to the form? - expect((fixture.componentInstance as MaterialFormsComponent).form.get('color').value).toBe('G'); + expect((fixture.componentInstance as MaterialFormsComponent).form?.get('color')?.value).toBe('G'); }); 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 2718e389..a672380c 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 { FormBuilder, Validators, ValidationErrors } from '@angular/forms'; +import { FormBuilder, Validators } from '@angular/forms'; @Component({ selector: 'app-fixture', @@ -68,8 +68,8 @@ export class MaterialFormsComponent { get formErrors() { return Object.keys(this.form.controls) .map((formKey) => { - const controlErrors: ValidationErrors = this.form.get(formKey).errors; - if (controlErrors != null) { + const controlErrors = this.form.get(formKey)?.errors; + if (controlErrors) { return Object.keys(controlErrors).map((keyError) => { const error = controlErrors[keyError]; switch (keyError) { diff --git a/apps/example-app/src/app/examples/06-with-ngrx-store.ts b/apps/example-app/src/app/examples/06-with-ngrx-store.ts index f260b978..470f52d9 100644 --- a/apps/example-app/src/app/examples/06-with-ngrx-store.ts +++ b/apps/example-app/src/app/examples/06-with-ngrx-store.ts @@ -3,16 +3,12 @@ import { createSelector, Store, createAction, createReducer, on, select } from ' const increment = createAction('increment'); const decrement = createAction('decrement'); -const counterReducer = createReducer( +export const reducer = createReducer( 0, on(increment, (state) => state + 1), on(decrement, (state) => state - 1), ); -export function reducer(state, action) { - return counterReducer(state, action); -} - const selectValue = createSelector( (state: any) => state.value, (value) => value * 10, diff --git a/apps/example-app/src/app/examples/08-directive.spec.ts b/apps/example-app/src/app/examples/08-directive.spec.ts index 5df9413a..ea3332b4 100644 --- a/apps/example-app/src/app/examples/08-directive.spec.ts +++ b/apps/example-app/src/app/examples/08-directive.spec.ts @@ -36,11 +36,11 @@ test('it is possible to test directives with props', async () => { expect(screen.queryByText(visible)).not.toBeInTheDocument(); expect(screen.queryByText(hidden)).toBeInTheDocument(); - fireEvent.mouseOver(screen.queryByText(hidden)); + fireEvent.mouseOver(screen.getByText(hidden)); expect(screen.queryByText(hidden)).not.toBeInTheDocument(); expect(screen.queryByText(visible)).toBeInTheDocument(); - fireEvent.mouseLeave(screen.queryByText(visible)); + fireEvent.mouseLeave(screen.getByText(visible)); expect(screen.queryByText(hidden)).toBeInTheDocument(); expect(screen.queryByText(visible)).not.toBeInTheDocument(); }); @@ -56,11 +56,11 @@ test('it is possible to test directives with props in template', async () => { expect(screen.queryByText(visible)).not.toBeInTheDocument(); expect(screen.queryByText(hidden)).toBeInTheDocument(); - fireEvent.mouseOver(screen.queryByText(hidden)); + fireEvent.mouseOver(screen.getByText(hidden)); expect(screen.queryByText(hidden)).not.toBeInTheDocument(); expect(screen.queryByText(visible)).toBeInTheDocument(); - fireEvent.mouseLeave(screen.queryByText(visible)); + fireEvent.mouseLeave(screen.getByText(visible)); expect(screen.queryByText(hidden)).toBeInTheDocument(); expect(screen.queryByText(visible)).not.toBeInTheDocument(); }); diff --git a/apps/example-app/src/app/examples/12-service-component.ts b/apps/example-app/src/app/examples/12-service-component.ts index af35f10a..d272e270 100644 --- a/apps/example-app/src/app/examples/12-service-component.ts +++ b/apps/example-app/src/app/examples/12-service-component.ts @@ -2,8 +2,8 @@ import { Component, Injectable } from '@angular/core'; import { Observable, of } from 'rxjs'; export class Customer { - id: string; - name: string; + id!: string; + name!: string; } @Injectable({ diff --git a/apps/example-app/src/app/examples/15-dialog.component.spec.ts b/apps/example-app/src/app/examples/15-dialog.component.spec.ts index f0fbd465..8a0ccd88 100644 --- a/apps/example-app/src/app/examples/15-dialog.component.spec.ts +++ b/apps/example-app/src/app/examples/15-dialog.component.spec.ts @@ -37,8 +37,8 @@ test('closes the dialog via the backdrop', async () => { // using fireEvent because of: // unable to click element as it has or inherits pointer-events set to "none" - // eslint-disable-next-line testing-library/no-node-access - fireEvent.click(document.querySelector('.cdk-overlay-backdrop')); + // eslint-disable-next-line testing-library/no-node-access, @typescript-eslint/no-non-null-assertion + fireEvent.click(document.querySelector('.cdk-overlay-backdrop')!); await waitForElementToBeRemoved(() => screen.getByRole('dialog')); diff --git a/apps/example-app/src/app/examples/16-input-getter-setter.ts b/apps/example-app/src/app/examples/16-input-getter-setter.ts index 11f8c949..22d9641a 100644 --- a/apps/example-app/src/app/examples/16-input-getter-setter.ts +++ b/apps/example-app/src/app/examples/16-input-getter-setter.ts @@ -18,6 +18,6 @@ export class InputGetterSetter { return 'I am value from getter ' + this.originalValue; } - private originalValue: string; - derivedValue: string; + private originalValue?: string; + derivedValue?: string; } diff --git a/apps/example-app/src/app/issues/issue-254.spec.ts b/apps/example-app/src/app/issues/issue-254.spec.ts index 01960b4f..74e90fbb 100644 --- a/apps/example-app/src/app/issues/issue-254.spec.ts +++ b/apps/example-app/src/app/issues/issue-254.spec.ts @@ -1,12 +1,11 @@ -/* eslint-disable @typescript-eslint/naming-convention */ import { Component, Inject, OnInit } from '@angular/core'; import { render, screen } from '@testing-library/angular'; import { createMock } from '@testing-library/angular/jest-utils'; interface Division { - JobType: string; - JobBullets: string[]; - Description: string; + jobType?: string; + jobBullets?: string[]; + description?: string; } @Inject({ @@ -21,25 +20,25 @@ class JobsService { @Component({ selector: 'app-home-career-oportunities', template: ` `, }) class CareerOportunitiesComponent implements OnInit { - dedicated = {} as Division; - intermodal = {} as Division; - noCdl = {} as Division; - otr = {} as Division; + dedicated?: Division; + intermodal?: Division; + noCdl?: Division; + otr?: Division; constructor(private jobsService: JobsService) {} ngOnInit(): void { this.jobsService.divisions().then((apiDivisions) => { - this.dedicated = apiDivisions.find((c) => c.JobType === 'DEDICATED'); - this.intermodal = apiDivisions.find((c) => c.JobType === 'INTERMODAL'); - this.noCdl = apiDivisions.find((c) => c.JobType === 'NO_CDL'); - this.otr = apiDivisions.find((c) => c.JobType === 'OVER_THE_ROAD'); + this.dedicated = apiDivisions.find((c) => c.jobType === 'DEDICATED'); + this.intermodal = apiDivisions.find((c) => c.jobType === 'INTERMODAL'); + this.noCdl = apiDivisions.find((c) => c.jobType === 'NO_CDL'); + this.otr = apiDivisions.find((c) => c.jobType === 'OVER_THE_ROAD'); }); } } @@ -47,20 +46,20 @@ class CareerOportunitiesComponent implements OnInit { test('Render Component', async () => { const divisions2: Division[] = [ { - JobType: 'INTERMODAL', - JobBullets: ['Local Routes', 'Flexible Schedules', 'Competitive Pay'], - Description: '', + jobType: 'INTERMODAL', + jobBullets: ['Local Routes', 'Flexible Schedules', 'Competitive Pay'], + description: '', }, - { JobType: 'NO_CDL', JobBullets: ['We Train', 'We Hire', 'We Pay'], Description: '' }, + { jobType: 'NO_CDL', jobBullets: ['We Train', 'We Hire', 'We Pay'], description: '' }, { - JobType: 'OVER_THE_ROAD', - JobBullets: ['Great Miles', 'Competitive Pay', 'Explore the Country'], - Description: '', + jobType: 'OVER_THE_ROAD', + jobBullets: ['Great Miles', 'Competitive Pay', 'Explore the Country'], + description: '', }, { - JobType: 'DEDICATED', - JobBullets: ['Regular Routes', 'Consistent Miles', 'Great Pay'], - Description: '', + jobType: 'DEDICATED', + jobBullets: ['Regular Routes', 'Consistent Miles', 'Great Pay'], + description: '', }, ]; const jobService = createMock(JobsService); diff --git a/projects/testing-library/.eslintrc.json b/projects/testing-library/.eslintrc.json index 4089aa79..918e7859 100644 --- a/projects/testing-library/.eslintrc.json +++ b/projects/testing-library/.eslintrc.json @@ -2,6 +2,12 @@ "extends": "../../.eslintrc.json", "ignorePatterns": ["!**/*"], "overrides": [ + { + "files": ["*.ts"], + "rules": { + "@typescript-eslint/ban-ts-comment": "off" + } + }, { "files": ["*.ts"], "extends": ["plugin:@nrwl/nx/angular", "plugin:@angular-eslint/template/process-inline-templates"], @@ -9,6 +15,7 @@ "project": ["projects/testing-library/tsconfig.*?.json"] }, "rules": { + "@typescript-eslint/ban-ts-comment": "off", "@angular-eslint/directive-selector": [ "error", { diff --git a/projects/testing-library/src/lib/models.ts b/projects/testing-library/src/lib/models.ts index 7f5a02a7..9320fb6a 100644 --- a/projects/testing-library/src/lib/models.ts +++ b/projects/testing-library/src/lib/models.ts @@ -268,8 +268,8 @@ export interface RenderTemplateOptions` + * const component = await render(`
`, { + * declarations: [SpoilerDirective] * wrapper: CustomWrapperComponent * }) */ diff --git a/projects/testing-library/src/lib/testing-library.ts b/projects/testing-library/src/lib/testing-library.ts index 1a517aa2..1197cf8e 100644 --- a/projects/testing-library/src/lib/testing-library.ts +++ b/projects/testing-library/src/lib/testing-library.ts @@ -53,8 +53,7 @@ export async function render( providers = [], schemas = [], queries, - template = undefined, - wrapper = WrapperComponent, + wrapper = WrapperComponent as Type, componentProperties = {}, componentProviders = [], excludeComponentDeclaration = false, @@ -89,13 +88,13 @@ export async function render( await TestBed.compileComponents(); componentProviders - .reduce((acc, provider) => acc.concat(provider), []) - .forEach((p) => { + .reduce((acc, provider) => acc.concat(provider), [] as any[]) + .forEach((p: any) => { const { provide, ...provider } = p; TestBed.overrideProvider(provide, provider); }); - const componentContainer = createComponentFixture(sut, { wrapper }); + const componentContainer = createComponentFixture(sut, wrapper); let fixture: ComponentFixture; let detectChanges: () => void; @@ -120,7 +119,7 @@ export async function render( let router = routes ? inject(Router) : null; const zone = inject(NgZone); - const navigate = async (elementOrPath: Element | string, basePath = '') => { + const navigate = async (elementOrPath: Element | string, basePath = ''): Promise => { if (!router) { router = inject(Router); } @@ -139,16 +138,18 @@ export async function render( qp[key] = [currentValue, value]; } return qp; - }, {}) + }, {} as Record) : undefined; - const navigateOptions: NavigationExtras = queryParams + const navigateOptions: NavigationExtras | undefined = queryParams ? { queryParams, } : undefined; - const doNavigate = () => (navigateOptions ? router.navigate([path], navigateOptions) : router.navigate([path])); + const doNavigate = () => { + return navigateOptions ? router?.navigate([path], navigateOptions) : router?.navigate([path]); + }; let result; @@ -159,21 +160,25 @@ export async function render( } detectChanges(); - return result; + return result ?? false; }; return { + // @ts-ignore: fixture assigned fixture, detectChanges: () => detectChanges(), navigate, rerender, change, + // @ts-ignore: fixture assigned debugElement: typeof sut === 'string' ? fixture.debugElement : fixture.debugElement.query(By.directive(sut)), + // @ts-ignore: fixture assigned container: fixture.nativeElement, debug: (element = fixture.nativeElement, maxLength, options) => Array.isArray(element) ? element.forEach((e) => console.log(dtlPrettyDOM(e, maxLength, options))) : console.log(dtlPrettyDOM(element, maxLength, options)), + // @ts-ignore: fixture assigned ...replaceFindWithFindAndDetectChanges(dtlGetQueriesForElement(fixture.nativeElement, queries)), }; @@ -220,9 +225,9 @@ async function createComponent(component: Type): Promise( +function createComponentFixture( sut: Type | string, - { wrapper }: Pick, 'wrapper'>, + wrapper: Type, ): Type { if (typeof sut === 'string') { TestBed.overrideTemplate(wrapper, sut); @@ -236,13 +241,10 @@ function setComponentProperties( { componentProperties = {} }: Pick, 'componentProperties'>, ) { for (const key of Object.keys(componentProperties)) { - const descriptor: PropertyDescriptor = Object.getOwnPropertyDescriptor( - fixture.componentInstance.constructor.prototype, - key, - ); + const descriptor = Object.getOwnPropertyDescriptor((fixture.componentInstance as any).constructor.prototype, key); let _value = componentProperties[key]; const defaultGetter = () => _value; - const extendedSetter = (value) => { + const extendedSetter = (value: any) => { _value = value; descriptor?.set?.call(fixture.componentInstance, _value); fixture.detectChanges(); @@ -268,21 +270,24 @@ function hasOnChangesHook(componentInstance: SutType): componentInstanc ); } -function getChangesObj(oldProps: Partial | null, newProps: Partial) { +function getChangesObj>( + oldProps: Partial | null, + newProps: Partial, +) { const isFirstChange = oldProps === null; return Object.keys(newProps).reduce( (changes, key) => ({ ...changes, [key]: new SimpleChange(isFirstChange ? null : oldProps[key], newProps[key], isFirstChange), }), - {}, + {} as SutType, ); } function addAutoDeclarations( sut: Type | string, { - declarations, + declarations = [], excludeComponentDeclaration, wrapper, }: Pick, 'declarations' | 'excludeComponentDeclaration' | 'wrapper'>, @@ -295,7 +300,7 @@ function addAutoDeclarations( return [...declarations, ...components()]; } -function addAutoImports({ imports, routes }: Pick, 'imports' | 'routes'>) { +function addAutoImports({ imports = [], routes }: Pick, 'imports' | 'routes'>) { const animations = () => { const animationIsDefined = imports.indexOf(NoopAnimationsModule) > -1 || imports.indexOf(BrowserAnimationsModule) > -1; @@ -341,19 +346,19 @@ async function waitForElementToBeRemovedWrapper( callback: (() => T) | T, options?: dtlWaitForOptions, ): Promise { - let cb; + let cb: () => T; if (typeof callback !== 'function') { const elements = (Array.isArray(callback) ? callback : [callback]) as Element[]; const getRemainingElements = elements.map((element) => { - let parent = element.parentElement; + let parent = element.parentElement as Element; while (parent.parentElement) { parent = parent.parentElement; } return () => (parent.contains(element) ? element : null); }); - cb = () => getRemainingElements.map((c) => c()).filter(Boolean); + cb = () => getRemainingElements.map((c) => c()).find(Boolean) as unknown as T; } else { - cb = callback; + cb = callback as () => T; } return await dtlWaitForElementToBeRemoved(() => { @@ -367,7 +372,7 @@ function cleanup() { mountedFixtures.forEach(cleanupAtFixture); } -function cleanupAtFixture(fixture) { +function cleanupAtFixture(fixture: ComponentFixture) { fixture.destroy(); if (!fixture.nativeElement.getAttribute('ng-version') && fixture.nativeElement.parentNode === document.body) { @@ -394,25 +399,21 @@ class WrapperComponent {} /** * Wrap findBy queries to poke the Angular change detection cycle */ -function replaceFindWithFindAndDetectChanges(originalQueriesForContainer: T): T { +function replaceFindWithFindAndDetectChanges>(originalQueriesForContainer: T): T { return Object.keys(originalQueriesForContainer).reduce((newQueries, key) => { const getByQuery = originalQueriesForContainer[key.replace('find', 'get')]; if (key.startsWith('find') && getByQuery) { - newQueries[key] = async (text, options, waitOptions) => { + newQueries[key] = async (...queryOptions: any[]) => { + const waitOptions = queryOptions.length === 3 ? queryOptions.pop() : undefined; // original implementation at https://github.com/testing-library/dom-testing-library/blob/main/src/query-helpers.js - const result = await waitForWrapper( - detectChangesForMountedFixtures, - () => getByQuery(text, options), - waitOptions, - ); - return result; + return await waitForWrapper(detectChangesForMountedFixtures, () => getByQuery(...queryOptions), waitOptions); }; } else { newQueries[key] = originalQueriesForContainer[key]; } return newQueries; - }, {} as T); + }, {} as Record) as T; } /** @@ -422,7 +423,7 @@ function detectChangesForMountedFixtures() { mountedFixtures.forEach((fixture) => { try { fixture.detectChanges(); - } catch (err) { + } catch (err: any) { if (!err.message.startsWith('ViewDestroyedError')) { throw err; } diff --git a/projects/testing-library/tests/auto-cleanup.spec.ts b/projects/testing-library/tests/auto-cleanup.spec.ts index c69eda62..1e37f242 100644 --- a/projects/testing-library/tests/auto-cleanup.spec.ts +++ b/projects/testing-library/tests/auto-cleanup.spec.ts @@ -6,7 +6,7 @@ import { render } from '../src/public_api'; template: `Hello {{ name }}!`, }) class FixtureComponent { - @Input() name: string; + @Input() name = ''; } describe('Angular auto clean up - previous components only get cleanup up on init (based on root-id)', () => { diff --git a/projects/testing-library/tests/change.spec.ts b/projects/testing-library/tests/change.spec.ts index 85cc3667..1ba67513 100644 --- a/projects/testing-library/tests/change.spec.ts +++ b/projects/testing-library/tests/change.spec.ts @@ -7,7 +7,7 @@ import { render, screen } from '../src/public_api'; }) class FixtureComponent { @Input() firstName = 'Sarah'; - @Input() lastName; + @Input() lastName?: string; } test('changes the component with updated props', async () => { @@ -44,7 +44,7 @@ test('changes the component with updated props while keeping other props untouch }) class FixtureWithNgOnChangesComponent implements OnChanges { @Input() name = 'Sarah'; - @Input() nameChanged: (name: string, isFirstChange: boolean) => void; + @Input() nameChanged?: (name: string, isFirstChange: boolean) => void; ngOnChanges(changes: SimpleChanges) { if (changes.name && this.nameChanged) { @@ -72,7 +72,7 @@ test('will call ngOnChanges on change', async () => { template: `
Number
`, }) class FixtureWithOnPushComponent { - @Input() activeField: string; + @Input() activeField = ''; } test('update properties on change', async () => { diff --git a/projects/testing-library/tests/config.spec.ts b/projects/testing-library/tests/config.spec.ts index ccd004f5..bb8c61fc 100644 --- a/projects/testing-library/tests/config.spec.ts +++ b/projects/testing-library/tests/config.spec.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; import { TestBed } from '@angular/core/testing'; -import { render, configure } from '../src/public_api'; +import { render, configure, Config } from '../src/public_api'; import { ReactiveFormsModule, FormBuilder } from '@angular/forms'; @Component({ @@ -22,12 +22,12 @@ class FormsComponent { constructor(private formBuilder: FormBuilder) {} } -let originalConfig; +let originalConfig: Config; beforeEach(() => { // Grab the existing configuration so we can restore // it at the end of the test configure((existingConfig) => { - originalConfig = existingConfig; + originalConfig = existingConfig as Config; // Don't change the existing config return {}; }); diff --git a/projects/testing-library/tests/integration.spec.ts b/projects/testing-library/tests/integration.spec.ts index afd56698..112177e1 100644 --- a/projects/testing-library/tests/integration.spec.ts +++ b/projects/testing-library/tests/integration.spec.ts @@ -36,7 +36,9 @@ class EntitiesComponent { query = new BehaviorSubject(''); readonly entities = this.query.pipe( debounceTime(DEBOUNCE_TIME), - switchMap((q) => this.entitiesService.fetchAll().pipe(map((ent) => ent.filter((e) => e.name.includes(q))))), + switchMap((q) => + this.entitiesService.fetchAll().pipe(map((ent: any) => ent.filter((e: any) => e.name.includes(q)))), + ), startWith(entities), ); @@ -65,7 +67,7 @@ class EntitiesComponent { `, }) class TableComponent { - @Input() entities: any[]; + @Input() entities: any[] = []; @Output() edit = new EventEmitter(); } diff --git a/projects/testing-library/tests/issues/issue-188.spec.ts b/projects/testing-library/tests/issues/issue-188.spec.ts index 8077358a..b150dacc 100644 --- a/projects/testing-library/tests/issues/issue-188.spec.ts +++ b/projects/testing-library/tests/issues/issue-188.spec.ts @@ -6,9 +6,9 @@ import { render, screen } from '../../src/public_api'; template: `

Hello {{ formattedName }}

`, }) class BugOnChangeComponent implements OnChanges { - @Input() name: string; + @Input() name?: string; - formattedName: string; + formattedName?: string; ngOnChanges(changes: SimpleChanges) { if (changes.name) { diff --git a/projects/testing-library/tests/rerender.spec.ts b/projects/testing-library/tests/rerender.spec.ts index 443560a2..0edf69ea 100644 --- a/projects/testing-library/tests/rerender.spec.ts +++ b/projects/testing-library/tests/rerender.spec.ts @@ -7,7 +7,7 @@ import { render, screen } from '../src/public_api'; }) class FixtureComponent { @Input() firstName = 'Sarah'; - @Input() lastName; + @Input() lastName?: string; } test('rerenders the component with updated props', async () => { diff --git a/tsconfig.base.json b/tsconfig.base.json index 0ad01d03..59f62014 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -13,9 +13,12 @@ "sourceMap": true, "target": "es2015", "typeRoots": ["node_modules/@types"], - "forceConsistentCasingInFileNames": true, - "noImplicitReturns": true, + "strict": true, "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "forceConsistentCasingInFileNames": true, "paths": { "@testing-library/angular": ["projects/testing-library"], "@testing-library/angular/jest-utils": ["projects/jest-utils"] From 20ae40dd58ddf5ffa614311789352e1e3ed03bd3 Mon Sep 17 00:00:00 2001 From: mleimer Date: Wed, 5 Jan 2022 20:46:11 +0100 Subject: [PATCH 3/9] docs: add example that presets form values (#276) --- .../examples/04-forms-with-material.spec.ts | 31 ++++++++++++++++++- .../app/examples/04-forms-with-material.ts | 10 +++++- 2 files changed, 39 insertions(+), 2 deletions(-) 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 ee4209b9..06f14ea8 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 @@ -4,6 +4,7 @@ import userEvent from '@testing-library/user-event'; import { MaterialModule } from '../material.module'; import { MaterialFormsComponent } from './04-forms-with-material'; + test('is possible to fill in a form and verify error messages (with the help of jest-dom https://testing-library.com/docs/ecosystem-jest-dom)', async () => { const { fixture } = await render(MaterialFormsComponent, { imports: [MaterialModule], @@ -37,12 +38,40 @@ 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'); const form = screen.getByRole('form'); expect(form).toHaveFormValues({ name: 'Tim', score: 7, }); - expect((fixture.componentInstance as MaterialFormsComponent).form?.get('color')?.value).toBe('G'); }); + +test('set and show pre-set form values', async () => { + const { fixture, detectChanges } = await render(MaterialFormsComponent, { + imports: [MaterialModule], + }); + + fixture.componentInstance.form.setValue({ + name: 'Max', + score: 4, + color: 'B' + }) + detectChanges(); + + const nameControl = screen.getByLabelText(/name/i); + const scoreControl = screen.getByRole('spinbutton', { name: /score/i }); + const colorControl = screen.getByRole('combobox', { name: /color/i }); + + expect(nameControl).toHaveValue('Max'); + expect(scoreControl).toHaveValue(4); + expect(colorControl).toHaveTextContent('Blue'); + + const form = screen.getByRole('form'); + expect(form).toHaveFormValues({ + name: 'Max', + score: 4, + }); + expect((fixture.componentInstance as MaterialFormsComponent).form?.get('color')?.value).toBe('B'); +}); 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 a672380c..f27f4f1e 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 @@ -24,6 +24,9 @@ import { FormBuilder, Validators } from '@angular/forms'; + + {{ colorControlDisplayValue }} + --- {{ color.value }} @@ -60,11 +63,16 @@ export class MaterialFormsComponent { form = this.formBuilder.group({ name: ['', Validators.required], score: [0, [Validators.min(1), Validators.max(10)]], - color: ['', Validators.required], + color: [null, Validators.required], }); constructor(private formBuilder: FormBuilder) {} + get colorControlDisplayValue(): string | undefined { + const selectedId = this.form.get('color')?.value; + return this.colors.filter(color => color.id === selectedId)[0]?.value; + } + get formErrors() { return Object.keys(this.form.controls) .map((formKey) => { From 11105d0ee8bcb2b7bcb186143158734ed8b7cff0 Mon Sep 17 00:00:00 2001 From: Arjen Date: Fri, 7 Jan 2022 22:17:39 +0100 Subject: [PATCH 4/9] chore: improve dependencies and lint (#277) --- .husky/_/husky.sh | 7 +- angular.json | 297 +----------------- .../src/app/examples/03-forms.spec.ts | 2 +- .../examples/04-forms-with-material.spec.ts | 7 +- .../src/app/examples/08-directive.spec.ts | 18 +- .../src/app/examples/09-router.spec.ts | 16 +- .../app/examples/15-dialog.component.spec.ts | 16 +- .../src/app/issues/issue-106.spec.ts | 6 +- .../src/app/issues/issue-254.spec.ts | 11 +- apps/example-app/tsconfig.app.json | 3 +- package.json | 90 +++--- projects/jest-utils/tsconfig.json | 4 +- projects/jest-utils/tsconfig.lib.json | 5 +- projects/jest-utils/tsconfig.lib.prod.json | 9 + projects/testing-library/package.json | 6 +- projects/testing-library/project.json | 6 +- .../src/lib/testing-library.ts | 2 +- projects/testing-library/tsconfig.json | 4 +- projects/testing-library/tsconfig.lib.json | 5 +- .../testing-library/tsconfig.lib.prod.json | 9 + tsconfig.base.json | 4 +- workspace.json | 9 - 22 files changed, 136 insertions(+), 400 deletions(-) create mode 100644 projects/jest-utils/tsconfig.lib.prod.json create mode 100644 projects/testing-library/tsconfig.lib.prod.json delete mode 100644 workspace.json diff --git a/.husky/_/husky.sh b/.husky/_/husky.sh index ca2720e0..6809ccca 100644 --- a/.husky/_/husky.sh +++ b/.husky/_/husky.sh @@ -1,7 +1,9 @@ #!/bin/sh if [ -z "$husky_skip_init" ]; then debug () { - [ "$HUSKY_DEBUG" = "1" ] && echo "husky (debug) - $1" + if [ "$HUSKY_DEBUG" = "1" ]; then + echo "husky (debug) - $1" + fi } readonly hook_name="$(basename "$0")" @@ -23,8 +25,7 @@ if [ -z "$husky_skip_init" ]; then if [ $exitCode != 0 ]; then echo "husky - $hook_name hook exited with code $exitCode (error)" - exit $exitCode fi - exit 0 + exit $exitCode fi diff --git a/angular.json b/angular.json index 50afdec9..dfd6bcf1 100644 --- a/angular.json +++ b/angular.json @@ -1,296 +1,9 @@ { - "version": 1, - "cli": { - "analytics": false - }, - "defaultProject": "example-app", + "version": 2, "projects": { - "example-app": { - "projectType": "application", - "root": "apps/example-app", - "sourceRoot": "apps/example-app/src", - "prefix": "app", - "schematics": {}, - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/apps/example-app", - "index": "apps/example-app/src/index.html", - "main": "apps/example-app/src/main.ts", - "polyfills": "apps/example-app/src/polyfills.ts", - "tsConfig": "apps/example-app/tsconfig.app.json", - "assets": ["apps/example-app/src/favicon.ico", "apps/example-app/src/assets"], - "styles": ["apps/example-app/src/styles.css"], - "scripts": [], - "vendorChunk": true, - "extractLicenses": false, - "buildOptimizer": false, - "sourceMap": true, - "optimization": false, - "namedChunks": true - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "anyComponentStyle", - "maximumWarning": "6kb" - } - ], - "fileReplacements": [ - { - "replace": "apps/example-app/src/environments/environment.ts", - "with": "apps/example-app/src/environments/environment.prod.ts" - } - ], - "optimization": true, - "outputHashing": "all", - "sourceMap": false, - "namedChunks": false, - "extractLicenses": true, - "vendorChunk": false, - "buildOptimizer": true - } - }, - "outputs": ["{options.outputPath}"] - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "options": { - "browserTarget": "example-app:build" - }, - "configurations": { - "production": { - "browserTarget": "example-app:build:production" - } - } - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "example-app:build" - } - }, - "lint": { - "builder": "@nrwl/linter:eslint", - "options": { - "lintFilePatterns": [ - "apps/example-app/**/*.ts", - "apps/example-app/**/*.html", - "apps/example-app/src/**/*.html", - "apps/example-app/src/**/*.html", - "apps/example-app/src/**/*.html" - ] - }, - "outputs": ["{options.outputFile}"] - }, - "test": { - "builder": "@nrwl/jest:jest", - "options": { - "jestConfig": "apps/example-app/jest.config.js" - }, - "outputs": ["coverage/"] - } - } - }, - "example-app-karma": { - "projectType": "application", - "root": "apps/example-app-karma", - "sourceRoot": "apps/example-app-karma/src", - "prefix": "app", - "schematics": {}, - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/apps/example-app-karma", - "index": "apps/example-app-karma/src/index.html", - "main": "apps/example-app-karma/src/main.ts", - "polyfills": "apps/example-app-karma/src/polyfills.ts", - "tsConfig": "apps/example-app-karma/tsconfig.app.json", - "assets": ["apps/example-app-karma/src/favicon.ico", "apps/example-app-karma/src/assets"], - "styles": [], - "scripts": [], - "vendorChunk": true, - "extractLicenses": false, - "buildOptimizer": false, - "sourceMap": true, - "optimization": false, - "namedChunks": true - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "anyComponentStyle", - "maximumWarning": "6kb" - } - ], - "fileReplacements": [ - { - "replace": "apps/example-app-karma/src/environments/environment.ts", - "with": "apps/example-app-karma/src/environments/environment.prod.ts" - } - ], - "optimization": true, - "outputHashing": "all", - "sourceMap": false, - "namedChunks": false, - "extractLicenses": true, - "vendorChunk": false, - "buildOptimizer": true - } - }, - "outputs": ["{options.outputPath}"] - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "options": { - "browserTarget": "example-app-karma:build" - }, - "configurations": { - "production": { - "browserTarget": "example-app-karma:build:production" - } - } - }, - "lint": { - "builder": "@nrwl/linter:eslint", - "options": { - "lintFilePatterns": [ - "apps/example-app-karma/**/*.ts", - "apps/example-app-karma/**/*.html", - "apps/example-app-karma/src/**/*.html", - "apps/example-app-karma/src/**/*.html", - "apps/example-app-karma/src/**/*.html" - ] - }, - "outputs": ["{options.outputFile}"] - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "apps/example-app-karma/src/test.ts", - "tsConfig": "apps/example-app-karma/tsconfig.spec.json", - "polyfills": "apps/example-app-karma/src/polyfills.ts", - "karmaConfig": "apps/example-app-karma/karma.conf.js", - "styles": [], - "scripts": [], - "assets": [] - } - } - } - }, - "jest-utils": { - "root": "projects/jest-utils", - "sourceRoot": "projects/jest-utils/src", - "projectType": "library", - "prefix": "lib", - "architect": { - "build-package": { - "builder": "@angular-devkit/build-angular:ng-packagr", - "options": { - "tsConfig": "projects/jest-utils/tsconfig.lib.json", - "project": "projects/jest-utils/ng-package.json" - }, - "configurations": { - "production": { - "project": "projects/jest-utils/ng-package.json", - "tsConfig": "projects/jest-utils/tsconfig.lib.json" - } - } - }, - "lint": { - "builder": "@nrwl/linter:eslint", - "options": { - "lintFilePatterns": [ - "projects/jest-utils/**/*.ts", - "projects/jest-utils/**/*.html", - "projects/jest-utils/src/**/*.html", - "projects/jest-utils/src/**/*.html", - "projects/jest-utils/src/**/*.html" - ] - }, - "outputs": ["{options.outputFile}"] - }, - "build": { - "builder": "@nrwl/workspace:run-commands", - "options": { - "parallel": false, - "commands": [ - { - "command": "ng run jest-utils:build-package" - } - ] - } - }, - "test": { - "builder": "@nrwl/jest:jest", - "options": { - "jestConfig": "projects/jest-utils/jest.config.js" - }, - "outputs": ["coverage/projects/jest-utils"] - } - } - }, - "testing-library": { - "root": "projects/testing-library", - "sourceRoot": "projects/testing-library/src", - "projectType": "library", - "prefix": "lib", - "architect": { - "build-package": { - "builder": "@angular-devkit/build-angular:ng-packagr", - "options": { - "tsConfig": "projects/testing-library/tsconfig.lib.json", - "project": "projects/testing-library/ng-package.json" - }, - "configurations": { - "production": { - "project": "projects/testing-library/ng-package.json", - "tsConfig": "projects/testing-library/tsconfig.lib.json" - } - } - }, - "lint": { - "builder": "@nrwl/linter:eslint", - "options": { - "lintFilePatterns": [ - "projects/testing-library/**/*.ts", - "projects/testing-library/**/*.html", - "projects/testing-library/src/**/*.html", - "projects/testing-library/src/**/*.html", - "projects/testing-library/src/**/*.html" - ] - }, - "outputs": ["{options.outputFile}"] - }, - "build": { - "builder": "@nrwl/workspace:run-commands", - "options": { - "parallel": false, - "commands": [ - { - "command": "ng run testing-library:build-package" - }, - { - "command": "npm run build:schematics" - }, - { - "command": "cpy ./README.md ./dist/@testing-library/angular" - } - ] - } - }, - "test": { - "builder": "@nrwl/jest:jest", - "options": { - "jestConfig": "projects/testing-library/jest.config.js" - }, - "outputs": ["coverage/projects/testing-library"] - } - } - } + "example-app": "apps/example-app", + "example-app-karma": "apps/example-app-karma", + "jest-utils": "projects/jest-utils", + "testing-library": "projects/testing-library" } } diff --git a/apps/example-app/src/app/examples/03-forms.spec.ts b/apps/example-app/src/app/examples/03-forms.spec.ts index 6be38a45..2a53a5f0 100644 --- a/apps/example-app/src/app/examples/03-forms.spec.ts +++ b/apps/example-app/src/app/examples/03-forms.spec.ts @@ -23,7 +23,7 @@ test('is possible to fill in a form and verify error messages (with the help of userEvent.selectOptions(colorControl, 'G'); expect(screen.queryByText('name is required')).not.toBeInTheDocument(); - expect(screen.queryByText('score must be lesser than 10')).toBeInTheDocument(); + expect(screen.getByText('score must be lesser than 10')).toBeInTheDocument(); expect(screen.queryByText('color is required')).not.toBeInTheDocument(); expect(scoreControl).toBeInvalid(); 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 06f14ea8..6d7e1e18 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 @@ -4,7 +4,6 @@ import userEvent from '@testing-library/user-event'; import { MaterialModule } from '../material.module'; import { MaterialFormsComponent } from './04-forms-with-material'; - test('is possible to fill in a form and verify error messages (with the help of jest-dom https://testing-library.com/docs/ecosystem-jest-dom)', async () => { const { fixture } = await render(MaterialFormsComponent, { imports: [MaterialModule], @@ -26,7 +25,7 @@ test('is possible to fill in a form and verify error messages (with the help of userEvent.click(screen.getByText(/green/i)); expect(screen.queryByText('name is required')).not.toBeInTheDocument(); - expect(screen.queryByText('score must be lesser than 10')).toBeInTheDocument(); + expect(screen.getByText('score must be lesser than 10')).toBeInTheDocument(); expect(screen.queryByText('color is required')).not.toBeInTheDocument(); expect(scoreControl).toBeInvalid(); @@ -56,8 +55,8 @@ test('set and show pre-set form values', async () => { fixture.componentInstance.form.setValue({ name: 'Max', score: 4, - color: 'B' - }) + color: 'B', + }); detectChanges(); const nameControl = screen.getByLabelText(/name/i); diff --git a/apps/example-app/src/app/examples/08-directive.spec.ts b/apps/example-app/src/app/examples/08-directive.spec.ts index ea3332b4..efd2a5b9 100644 --- a/apps/example-app/src/app/examples/08-directive.spec.ts +++ b/apps/example-app/src/app/examples/08-directive.spec.ts @@ -10,14 +10,14 @@ test('it is possible to test directives', async () => { const directive = screen.getByTestId('dir'); expect(screen.queryByText('I am visible now...')).not.toBeInTheDocument(); - expect(screen.queryByText('SPOILER')).toBeInTheDocument(); + expect(screen.getByText('SPOILER')).toBeInTheDocument(); fireEvent.mouseOver(directive); expect(screen.queryByText('SPOILER')).not.toBeInTheDocument(); - expect(screen.queryByText('I am visible now...')).toBeInTheDocument(); + expect(screen.getByText('I am visible now...')).toBeInTheDocument(); fireEvent.mouseLeave(directive); - expect(screen.queryByText('SPOILER')).toBeInTheDocument(); + expect(screen.getByText('SPOILER')).toBeInTheDocument(); expect(screen.queryByText('I am visible now...')).not.toBeInTheDocument(); }); @@ -34,14 +34,14 @@ test('it is possible to test directives with props', async () => { }); expect(screen.queryByText(visible)).not.toBeInTheDocument(); - expect(screen.queryByText(hidden)).toBeInTheDocument(); + expect(screen.getByText(hidden)).toBeInTheDocument(); fireEvent.mouseOver(screen.getByText(hidden)); expect(screen.queryByText(hidden)).not.toBeInTheDocument(); - expect(screen.queryByText(visible)).toBeInTheDocument(); + expect(screen.getByText(visible)).toBeInTheDocument(); fireEvent.mouseLeave(screen.getByText(visible)); - expect(screen.queryByText(hidden)).toBeInTheDocument(); + expect(screen.getByText(hidden)).toBeInTheDocument(); expect(screen.queryByText(visible)).not.toBeInTheDocument(); }); @@ -54,13 +54,13 @@ test('it is possible to test directives with props in template', async () => { }); expect(screen.queryByText(visible)).not.toBeInTheDocument(); - expect(screen.queryByText(hidden)).toBeInTheDocument(); + expect(screen.getByText(hidden)).toBeInTheDocument(); fireEvent.mouseOver(screen.getByText(hidden)); expect(screen.queryByText(hidden)).not.toBeInTheDocument(); - expect(screen.queryByText(visible)).toBeInTheDocument(); + expect(screen.getByText(visible)).toBeInTheDocument(); fireEvent.mouseLeave(screen.getByText(visible)); - expect(screen.queryByText(hidden)).toBeInTheDocument(); + expect(screen.getByText(hidden)).toBeInTheDocument(); expect(screen.queryByText(visible)).not.toBeInTheDocument(); }); diff --git a/apps/example-app/src/app/examples/09-router.spec.ts b/apps/example-app/src/app/examples/09-router.spec.ts index cb6743fc..16f037fc 100644 --- a/apps/example-app/src/app/examples/09-router.spec.ts +++ b/apps/example-app/src/app/examples/09-router.spec.ts @@ -25,19 +25,19 @@ test('it can navigate to routes', async () => { expect(screen.queryByText(/Detail one/i)).not.toBeInTheDocument(); await navigate(screen.getByRole('link', { name: /load one/i })); - expect(screen.queryByRole('heading', { name: /Detail one/i })).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: /Detail one/i })).toBeInTheDocument(); await navigate(screen.getByRole('link', { name: /load three/i })); expect(screen.queryByRole('heading', { name: /Detail one/i })).not.toBeInTheDocument(); - expect(screen.queryByRole('heading', { name: /Detail three/i })).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: /Detail three/i })).toBeInTheDocument(); await navigate(screen.getByRole('link', { name: /back to parent/i })); expect(screen.queryByRole('heading', { name: /Detail three/i })).not.toBeInTheDocument(); await navigate(screen.getByRole('link', { name: /load two/i })); - expect(screen.queryByRole('heading', { name: /Detail two/i })).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: /Detail two/i })).toBeInTheDocument(); await navigate(screen.getByRole('link', { name: /hidden x/i })); - expect(screen.queryByText(/You found the treasure!/i)).toBeInTheDocument(); + expect(screen.getByText(/You found the treasure!/i)).toBeInTheDocument(); }); test('it can navigate to routes with a base path', async () => { @@ -64,20 +64,20 @@ test('it can navigate to routes with a base path', async () => { expect(screen.queryByRole('heading', { name: /Detail one/i })).not.toBeInTheDocument(); await navigate(screen.getByRole('link', { name: /load one/i }), basePath); - expect(screen.queryByRole('heading', { name: /Detail one/i })).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: /Detail one/i })).toBeInTheDocument(); await navigate(screen.getByRole('link', { name: /load three/i }), basePath); expect(screen.queryByRole('heading', { name: /Detail one/i })).not.toBeInTheDocument(); - expect(screen.queryByRole('heading', { name: /Detail three/i })).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: /Detail three/i })).toBeInTheDocument(); await navigate(screen.getByRole('link', { name: /back to parent/i })); expect(screen.queryByRole('heading', { name: /Detail three/i })).not.toBeInTheDocument(); // It's possible to just use strings await navigate('base/detail/two?text=Hello&subtext=World'); - expect(screen.queryByRole('heading', { name: /Detail two/i })).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: /Detail two/i })).toBeInTheDocument(); expect(screen.getByText(/Hello World/i)).toBeInTheDocument(); await navigate('/hidden-detail', basePath); - expect(screen.queryByText(/You found the treasure!/i)).toBeInTheDocument(); + expect(screen.getByText(/You found the treasure!/i)).toBeInTheDocument(); }); diff --git a/apps/example-app/src/app/examples/15-dialog.component.spec.ts b/apps/example-app/src/app/examples/15-dialog.component.spec.ts index 8a0ccd88..31cf2fba 100644 --- a/apps/example-app/src/app/examples/15-dialog.component.spec.ts +++ b/apps/example-app/src/app/examples/15-dialog.component.spec.ts @@ -32,15 +32,17 @@ test('closes the dialog via the backdrop', async () => { const openDialogButton = await screen.findByRole('button', { name: /open dialog/i }); userEvent.click(openDialogButton); - await screen.findByRole('dialog'); - await screen.findByRole('heading', { name: /dialog title/i }); + const dialogControl = await screen.findByRole('dialog'); + expect(dialogControl).toBeInTheDocument(); + const dialogTitleControl = await screen.findByRole('heading', { name: /dialog title/i }); + expect(dialogTitleControl).toBeInTheDocument(); // using fireEvent because of: // unable to click element as it has or inherits pointer-events set to "none" // eslint-disable-next-line testing-library/no-node-access, @typescript-eslint/no-non-null-assertion fireEvent.click(document.querySelector('.cdk-overlay-backdrop')!); - await waitForElementToBeRemoved(() => screen.getByRole('dialog')); + await waitForElementToBeRemoved(() => screen.queryByRole('dialog')); const dialogTitle = screen.queryByRole('heading', { name: /dialog title/i }); expect(dialogTitle).not.toBeInTheDocument(); @@ -54,13 +56,15 @@ test('opens and closes the dialog with buttons', async () => { const openDialogButton = await screen.findByRole('button', { name: /open dialog/i }); userEvent.click(openDialogButton); - await screen.findByRole('dialog'); - await screen.findByRole('heading', { name: /dialog title/i }); + const dialogControl = await screen.findByRole('dialog'); + expect(dialogControl).toBeInTheDocument(); + const dialogTitleControl = await screen.findByRole('heading', { name: /dialog title/i }); + expect(dialogTitleControl).toBeInTheDocument(); const cancelButton = await screen.findByRole('button', { name: /cancel/i }); userEvent.click(cancelButton); - await waitForElementToBeRemoved(() => screen.getByRole('dialog')); + await waitForElementToBeRemoved(() => screen.queryByRole('dialog')); const dialogTitle = screen.queryByRole('heading', { name: /dialog title/i }); expect(dialogTitle).not.toBeInTheDocument(); diff --git a/apps/example-app/src/app/issues/issue-106.spec.ts b/apps/example-app/src/app/issues/issue-106.spec.ts index b1f6cc2c..56097a83 100644 --- a/apps/example-app/src/app/issues/issue-106.spec.ts +++ b/apps/example-app/src/app/issues/issue-106.spec.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; -import { render, screen, fireEvent, waitFor } from '@testing-library/angular'; +import { render, screen, fireEvent } from '@testing-library/angular'; +import { waitFor } from '@testing-library/dom'; @Component({ template: ` @@ -27,6 +28,9 @@ test('https://github.com/testing-library/angular-testing-library/issues/106', as // await waitFor(() => expect(hiddenText).not.toBeNull()); // succeeds + /// Next line is disabled, because we wish to test the behavior of the library and test the bug/issue #106 + /// @see https://github.com/testing-library/angular-testing-library/pull/277/files#r779743116 + // eslint-disable-next-line testing-library/prefer-presence-queries, testing-library/prefer-find-by await waitFor(() => expect(screen.queryByTestId('getme')).toBeInTheDocument()); }); diff --git a/apps/example-app/src/app/issues/issue-254.spec.ts b/apps/example-app/src/app/issues/issue-254.spec.ts index 74e90fbb..917f35de 100644 --- a/apps/example-app/src/app/issues/issue-254.spec.ts +++ b/apps/example-app/src/app/issues/issue-254.spec.ts @@ -26,10 +26,10 @@ class JobsService { `, }) class CareerOportunitiesComponent implements OnInit { - dedicated?: Division; - intermodal?: Division; - noCdl?: Division; - otr?: Division; + dedicated: Division | undefined; + intermodal: Division | undefined; + noCdl: Division | undefined; + otr: Division | undefined; constructor(private jobsService: JobsService) {} @@ -73,5 +73,6 @@ test('Render Component', async () => { }, ], }); - await screen.findAllByRole('listitem'); + const listItemControl = await screen.findAllByRole('listitem'); + expect(listItemControl).toHaveLength(3); }); diff --git a/apps/example-app/tsconfig.app.json b/apps/example-app/tsconfig.app.json index 4de7101b..a009fad9 100644 --- a/apps/example-app/tsconfig.app.json +++ b/apps/example-app/tsconfig.app.json @@ -3,7 +3,8 @@ "compilerOptions": { "outDir": "../../dist/out-tsc", "types": [], - "allowJs": true + "allowJs": true, + "target": "ES2017" }, "files": ["src/main.ts", "src/polyfills.ts"], "include": ["src/**/*.d.ts"], diff --git a/package.json b/package.json index 3c7c74ec..7b514db6 100644 --- a/package.json +++ b/package.json @@ -28,75 +28,75 @@ "prepare": "husky install" }, "dependencies": { - "@angular/animations": "13.0.2", - "@angular/cdk": "13.0.2", - "@angular/common": "13.0.2", - "@angular/compiler": "13.0.2", - "@angular/core": "13.0.2", - "@angular/forms": "13.0.2", - "@angular/material": "13.0.2", - "@angular/platform-browser": "13.0.2", - "@angular/platform-browser-dynamic": "13.0.2", - "@angular/router": "13.0.2", - "@ngrx/store": "13.0.1", - "@nrwl/angular": "13.2.2", - "@nrwl/nx-cloud": "12.5.4", + "@angular/animations": "13.1.1", + "@angular/cdk": "13.1.1", + "@angular/common": "13.1.1", + "@angular/compiler": "13.1.1", + "@angular/core": "13.1.1", + "@angular/material": "13.1.1", + "@angular/platform-browser": "13.1.1", + "@angular/platform-browser-dynamic": "13.1.1", + "@angular/router": "13.1.1", + "@ngrx/store": "13.0.2", + "@nrwl/angular": "13.4.3", + "@nrwl/nx-cloud": "13.0.2", "@testing-library/dom": "^8.11.1", - "@testing-library/user-event": "^13.5.0", - "rxjs": "^7.4.0", + "rxjs": "^7.5.1", "tslib": "~2.3.1", "zone.js": "~0.11.4" }, "devDependencies": { - "@angular-devkit/build-angular": "13.0.3", - "@angular-eslint/eslint-plugin": "12.6.1", - "@angular-eslint/eslint-plugin-template": "12.6.1", - "@angular-eslint/template-parser": "12.6.1", - "@angular/cli": "13.0.3", - "@angular/compiler-cli": "13.0.2", - "@angular/language-service": "13.0.2", - "@nrwl/cli": "13.2.2", - "@nrwl/eslint-plugin-nx": "13.2.2", - "@nrwl/jest": "13.2.2", - "@nrwl/linter": "13.2.2", - "@nrwl/node": "13.2.2", - "@nrwl/nx-plugin": "13.2.2", - "@nrwl/workspace": "13.2.2", + "@angular-devkit/build-angular": "13.1.2", + "@angular-eslint/eslint-plugin": "13.0.1", + "@angular-eslint/eslint-plugin-template": "13.0.1", + "@angular-eslint/template-parser": "13.0.1", + "@angular/cli": "13.1.2", + "@angular/compiler-cli": "13.1.1", + "@angular/forms": "13.1.1", + "@angular/language-service": "13.1.1", + "@nrwl/cli": "13.4.3", + "@nrwl/eslint-plugin-nx": "13.4.3", + "@nrwl/jest": "13.4.3", + "@nrwl/linter": "13.4.3", + "@nrwl/node": "13.4.3", + "@nrwl/nx-plugin": "13.4.3", + "@nrwl/workspace": "13.4.3", "@testing-library/jasmine-dom": "^1.2.0", "@testing-library/jest-dom": "^5.15.1", + "@testing-library/user-event": "^13.5.0", "@types/jasmine": "^3.10.2", - "@types/jest": "27.0.3", + "@types/jest": "27.4.0", "@types/node": "14.14.37", - "@typescript-eslint/eslint-plugin": "4.33.0", - "@typescript-eslint/parser": "4.33.0", + "@typescript-eslint/eslint-plugin": "~5.3.0", + "@typescript-eslint/parser": "~5.3.0", "cpy-cli": "^3.1.1", - "eslint": "7.32.0", + "eslint": "~8.6.0", "eslint-config-prettier": "8.3.0", - "eslint-plugin-import": "2.23.4", - "eslint-plugin-jasmine": "^4.1.2", - "eslint-plugin-jest": "24.3.6", - "eslint-plugin-jest-dom": "3.9.0", - "eslint-plugin-testing-library": "4.9.0", + "eslint-plugin-import": "~2.25.4", + "eslint-plugin-jasmine": "~4.1.3", + "eslint-plugin-jest": "~25.3.4", + "eslint-plugin-jest-dom": "~4.0.1", + "eslint-plugin-testing-library": "~5.0.1", "husky": "^7.0.0", "jasmine-core": "^3.10.1", "jasmine-spec-reporter": "^7.0.0", - "jest": "27.3.1", + "jest": "27.4.7", "jest-preset-angular": "11.0.1", "karma": "^6.3.9", "karma-chrome-launcher": "^3.1.0", "karma-jasmine": "^4.0.1", "karma-jasmine-html-reporter": "^1.7.0", - "lint-staged": "^12.1.2", - "ng-packagr": "~13.0.7", - "postcss": "^8.3.11", + "lint-staged": "^12.1.6", + "ng-packagr": "13.1.2", + "postcss": "^8.4.5", "postcss-import": "^14.0.2", - "postcss-preset-env": "^7.0.1", + "postcss-preset-env": "^7.2.0", "postcss-url": "^10.1.3", "prettier": "^2.4.1", "rimraf": "^3.0.2", "semantic-release": "^18.0.0", - "ts-jest": "27.0.7", + "ts-jest": "27.1.2", "ts-node": "~10.4.0", - "typescript": "4.4.4" + "typescript": "4.5.4" } } diff --git a/projects/jest-utils/tsconfig.json b/projects/jest-utils/tsconfig.json index fd77caf1..24663f6f 100644 --- a/projects/jest-utils/tsconfig.json +++ b/projects/jest-utils/tsconfig.json @@ -6,13 +6,15 @@ { "path": "./tsconfig.lib.json" }, + { + "path": "./tsconfig.lib.prod.json" + }, { "path": "./tsconfig.spec.json" } ], "compilerOptions": {}, "angularCompilerOptions": { - "compilationMode": "partial", "strictInjectionParameters": true, "strictInputAccessModifiers": true, "strictTemplates": true, diff --git a/projects/jest-utils/tsconfig.lib.json b/projects/jest-utils/tsconfig.lib.json index 22fef254..0566ff84 100644 --- a/projects/jest-utils/tsconfig.lib.json +++ b/projects/jest-utils/tsconfig.lib.json @@ -1,11 +1,12 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "outDir": "../../out-tsc/lib", + "outDir": "../../dist/out-tsc", "declaration": true, "declarationMap": true, "inlineSources": true, "types": ["jest"] }, - "exclude": ["src/test.ts", "**/*.spec.ts", "**/*.test.ts"] + "exclude": ["src/test.ts", "**/*.spec.ts", "**/*.test.ts"], + "include": ["**/*.ts"] } diff --git a/projects/jest-utils/tsconfig.lib.prod.json b/projects/jest-utils/tsconfig.lib.prod.json new file mode 100644 index 00000000..2a2faa88 --- /dev/null +++ b/projects/jest-utils/tsconfig.lib.prod.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.lib.json", + "compilerOptions": { + "declarationMap": false + }, + "angularCompilerOptions": { + "compilationMode": "partial" + } +} diff --git a/projects/testing-library/package.json b/projects/testing-library/package.json index e41389cf..7d14c654 100644 --- a/projects/testing-library/package.json +++ b/projects/testing-library/package.json @@ -31,13 +31,13 @@ "peerDependencies": { "@angular/common": ">= 13.0.0", "@angular/platform-browser": ">= 13.0.0", - "@angular/animations": ">= 13.0.0", "@angular/router": ">= 13.0.0", - "@angular/core": ">= 13.0.0" + "@angular/core": ">= 13.0.0", + "rxjs": ">= 7.4.0" }, "dependencies": { "@testing-library/dom": "^8.0.0", - "tslib": "^2.0.0" + "tslib": "^2.3.1" }, "publishConfig": { "access": "public" diff --git a/projects/testing-library/project.json b/projects/testing-library/project.json index fe6d0834..1caa86ee 100644 --- a/projects/testing-library/project.json +++ b/projects/testing-library/project.json @@ -23,11 +23,7 @@ "lint": { "executor": "@nrwl/linter:eslint", "options": { - "lintFilePatterns": [ - "projects/testing-library/**/*.ts", - "projects/testing-library/**/*.html", - "projects/testing-library/src/**/*.html" - ] + "lintFilePatterns": ["projects/testing-library/**/*.ts", "projects/testing-library/**/*.html"] }, "outputs": ["{options.outputFile}"] }, diff --git a/projects/testing-library/src/lib/testing-library.ts b/projects/testing-library/src/lib/testing-library.ts index 1197cf8e..65877ef3 100644 --- a/projects/testing-library/src/lib/testing-library.ts +++ b/projects/testing-library/src/lib/testing-library.ts @@ -57,7 +57,7 @@ export async function render( componentProperties = {}, componentProviders = [], excludeComponentDeclaration = false, - routes, + routes = [], removeAngularAttributes = false, defaultImports = [], } = { ...globalConfig, ...renderOptions }; diff --git a/projects/testing-library/tsconfig.json b/projects/testing-library/tsconfig.json index fd77caf1..24663f6f 100644 --- a/projects/testing-library/tsconfig.json +++ b/projects/testing-library/tsconfig.json @@ -6,13 +6,15 @@ { "path": "./tsconfig.lib.json" }, + { + "path": "./tsconfig.lib.prod.json" + }, { "path": "./tsconfig.spec.json" } ], "compilerOptions": {}, "angularCompilerOptions": { - "compilationMode": "partial", "strictInjectionParameters": true, "strictInputAccessModifiers": true, "strictTemplates": true, diff --git a/projects/testing-library/tsconfig.lib.json b/projects/testing-library/tsconfig.lib.json index f7b1b9ca..7d77d4c9 100644 --- a/projects/testing-library/tsconfig.lib.json +++ b/projects/testing-library/tsconfig.lib.json @@ -1,11 +1,12 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "outDir": "../../out-tsc/lib", + "outDir": "../../dist/out-tsc", "declaration": true, "declarationMap": true, "inlineSources": true, "types": ["node", "jest"] }, - "exclude": ["src/test.ts", "**/*.spec.ts", "**/*.test.ts"] + "exclude": ["src/test-setup.ts", "**/*.spec.ts", "**/*.test.ts"], + "include": ["**/*.ts"] } diff --git a/projects/testing-library/tsconfig.lib.prod.json b/projects/testing-library/tsconfig.lib.prod.json new file mode 100644 index 00000000..2a2faa88 --- /dev/null +++ b/projects/testing-library/tsconfig.lib.prod.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.lib.json", + "compilerOptions": { + "declarationMap": false + }, + "angularCompilerOptions": { + "compilationMode": "partial" + } +} diff --git a/tsconfig.base.json b/tsconfig.base.json index 59f62014..1e0de6e1 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -14,11 +14,13 @@ "target": "es2015", "typeRoots": ["node_modules/@types"], "strict": true, + "exactOptionalPropertyTypes": true, + "forceConsistentCasingInFileNames": true, + "noImplicitOverride": true, "noFallthroughCasesInSwitch": true, "noImplicitReturns": true, "noUnusedLocals": true, "noUnusedParameters": true, - "forceConsistentCasingInFileNames": true, "paths": { "@testing-library/angular": ["projects/testing-library"], "@testing-library/angular/jest-utils": ["projects/jest-utils"] diff --git a/workspace.json b/workspace.json deleted file mode 100644 index dfd6bcf1..00000000 --- a/workspace.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "version": 2, - "projects": { - "example-app": "apps/example-app", - "example-app-karma": "apps/example-app-karma", - "jest-utils": "projects/jest-utils", - "testing-library": "projects/testing-library" - } -} From c6b864c18606732fecea9d1d7fdc2eba2df52eff Mon Sep 17 00:00:00 2001 From: Tim Deschryver <28659384+timdeschryver@users.noreply.github.com> Date: Thu, 10 Feb 2022 08:28:22 +0100 Subject: [PATCH 5/9] chore: update TL types (#282) --- projects/testing-library/src/lib/models.ts | 2 +- projects/testing-library/src/lib/testing-library.ts | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/projects/testing-library/src/lib/models.ts b/projects/testing-library/src/lib/models.ts index 9320fb6a..7799c3ce 100644 --- a/projects/testing-library/src/lib/models.ts +++ b/projects/testing-library/src/lib/models.ts @@ -20,7 +20,7 @@ export interface RenderResult extend * element: The to be printed HTML element, if not provided it will log the whole component's DOM */ debug: ( - element?: Element | HTMLDocument | (Element | HTMLDocument)[], + element?: Element | Document | (Element | Document)[], maxLength?: number, options?: PrettyDOMOptions, ) => void; diff --git a/projects/testing-library/src/lib/testing-library.ts b/projects/testing-library/src/lib/testing-library.ts index 65877ef3..b1c8c16d 100644 --- a/projects/testing-library/src/lib/testing-library.ts +++ b/projects/testing-library/src/lib/testing-library.ts @@ -22,10 +22,9 @@ import { within as dtlWithin, waitForOptions as dtlWaitForOptions, configure as dtlConfigure, - Queries, - getQueriesForElement, queries as dtlQueries, } from '@testing-library/dom'; +import type { Queries, BoundFunctions } from '@testing-library/dom'; import { RenderComponentOptions, RenderTemplateOptions, RenderResult } from './models'; import { getConfig } from './config'; @@ -439,12 +438,11 @@ const screen = replaceFindWithFindAndDetectChanges(dtlScreen); /** * Re-export within with patched queries */ - -const within: typeof getQueriesForElement = ( +const within = ( element: HTMLElement, queriesToBind?: T, -) => { - const container = dtlWithin(element, queriesToBind); +): BoundFunctions => { + const container = dtlWithin(element, queriesToBind); return replaceFindWithFindAndDetectChanges(container); }; From 21b119ef01e26847bb2ca6475c1ad672b43e71ac Mon Sep 17 00:00:00 2001 From: Tim Deschryver <28659384+timdeschryver@users.noreply.github.com> Date: Thu, 10 Feb 2022 08:43:33 +0100 Subject: [PATCH 6/9] chore: add .node-version and remove husky (#281) --- .githooks/pre-commit | 3 +++ .github/workflows/ci.yml | 5 ++++- .husky/_/husky.sh | 31 ------------------------------- .husky/pre-commit | 4 ---- .node-version | 1 + lint-staged.config.js | 4 ++-- package.json | 3 +-- 7 files changed, 11 insertions(+), 40 deletions(-) create mode 100644 .githooks/pre-commit delete mode 100644 .husky/_/husky.sh delete mode 100644 .husky/pre-commit create mode 100644 .node-version diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100644 index 00000000..f0f7d343 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,3 @@ +#!/bin/sh + +npm run pre-commit diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 998b0652..80741a41 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,10 @@ on: - 'beta' pull_request: {} +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: build_test_release: strategy: @@ -34,4 +38,3 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} CI: true - HUSKY: 0 diff --git a/.husky/_/husky.sh b/.husky/_/husky.sh deleted file mode 100644 index 6809ccca..00000000 --- a/.husky/_/husky.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/sh -if [ -z "$husky_skip_init" ]; then - debug () { - if [ "$HUSKY_DEBUG" = "1" ]; then - echo "husky (debug) - $1" - fi - } - - readonly hook_name="$(basename "$0")" - debug "starting $hook_name..." - - if [ "$HUSKY" = "0" ]; then - debug "HUSKY env variable is set to 0, skipping hook" - exit 0 - fi - - if [ -f ~/.huskyrc ]; then - debug "sourcing ~/.huskyrc" - . ~/.huskyrc - fi - - export readonly husky_skip_init=1 - sh -e "$0" "$@" - exitCode="$?" - - if [ $exitCode != 0 ]; then - echo "husky - $hook_name hook exited with code $exitCode (error)" - fi - - exit $exitCode -fi diff --git a/.husky/pre-commit b/.husky/pre-commit deleted file mode 100644 index d0612ad3..00000000 --- a/.husky/pre-commit +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -npm run pre-commit diff --git a/.node-version b/.node-version new file mode 100644 index 00000000..b6a7d89c --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +16 diff --git a/lint-staged.config.js b/lint-staged.config.js index a10d3ccf..ebb35d3b 100644 --- a/lint-staged.config.js +++ b/lint-staged.config.js @@ -1,4 +1,4 @@ module.exports = { - '*.{ts,js}': ['eslint --fix', 'git add'], - '*.{json,md}': ['prettier --write', 'git add'], + '*.{ts,js}': ['eslint --fix'], + '*.{json,md}': ['prettier --write'], }; diff --git a/package.json b/package.json index 7b514db6..977d7a88 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "format:check": "nx format:check", "pre-commit": "lint-staged", "semantic-release": "semantic-release", - "prepare": "husky install" + "prepare": "git config core.hookspath .githooks" }, "dependencies": { "@angular/animations": "13.1.1", @@ -77,7 +77,6 @@ "eslint-plugin-jest": "~25.3.4", "eslint-plugin-jest-dom": "~4.0.1", "eslint-plugin-testing-library": "~5.0.1", - "husky": "^7.0.0", "jasmine-core": "^3.10.1", "jasmine-spec-reporter": "^7.0.0", "jest": "27.4.7", From 8a56824befee4b35a3e8fcd3f556c8ba8eb5dc50 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Mon, 14 Feb 2022 20:24:16 +0100 Subject: [PATCH 7/9] docs: add meirka as a contributor for bug, test (#285) --- .all-contributorsrc | 10 ++++++++++ README.md | 3 +++ 2 files changed, 13 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 96c7a291..dbc27208 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -274,6 +274,16 @@ "doc", "test" ] + }, + { + "login": "meirka", + "name": "MeIr", + "avatar_url": "https://avatars.githubusercontent.com/u/750901?v=4", + "profile": "https://github.com/meirka", + "contributions": [ + "bug", + "test" + ] } ], "contributorsPerLine": 7, diff --git a/README.md b/README.md index d750dae4..a66015c9 100644 --- a/README.md +++ b/README.md @@ -219,6 +219,9 @@ Thanks goes to these people ([emoji key][emojis]):
Matan Borenkraout

🚧
mleimer

📖 ⚠️ + +
MeIr

🐛 ⚠️ + From b34993969a697cbeb4d4f002b6805cc07bb195ae Mon Sep 17 00:00:00 2001 From: MeIr Date: Wed, 16 Feb 2022 13:34:38 -0500 Subject: [PATCH 8/9] test: add test case for #280 (#284) --- .../tests/issues/issue-280.spec.ts | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 projects/testing-library/tests/issues/issue-280.spec.ts diff --git a/projects/testing-library/tests/issues/issue-280.spec.ts b/projects/testing-library/tests/issues/issue-280.spec.ts new file mode 100644 index 00000000..4c490741 --- /dev/null +++ b/projects/testing-library/tests/issues/issue-280.spec.ts @@ -0,0 +1,76 @@ +import {Component, NgModule} from '@angular/core'; +import {render, screen} from '@testing-library/angular'; +import {Router, RouterModule, Routes} from "@angular/router"; +import {RouterTestingModule} from "@angular/router/testing"; +import userEvent from "@testing-library/user-event"; +import {Location} from "@angular/common"; +import {TestBed} from "@angular/core/testing"; + +@Component({ + template: `
Navigate
`, +}) +class MainComponent {} + +@Component({ + template: `
first page
go to second` +}) +class FirstComponent {} + +@Component({ + template: `
second page
` +}) +class SecondComponent { + constructor(private location: Location) { } + goBack() {this.location.back();} +} + +const routes: Routes = [ + {path: '', redirectTo: '/first', pathMatch: 'full'}, + {path: 'first', component: FirstComponent}, + {path: 'second', component: SecondComponent} +]; + +@NgModule({ + declarations: [FirstComponent, SecondComponent], + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule] +}) +class AppRoutingModule {} + + +// test('navigate to second page and back', async () => { +// const subject = await render(MainComponent, {imports: [AppRoutingModule, RouterTestingModule]}); +// await subject.navigate('/'); +// +// expect(await screen.findByText('Navigate')).toBeInTheDocument(); +// expect(await screen.findByText('first page')).toBeInTheDocument(); +// +// userEvent.click(await screen.findByText('go to second')); +// +// expect(await screen.findByText('second page')).toBeInTheDocument(); +// expect(await screen.findByText('navigate back')).toBeInTheDocument(); +// +// userEvent.click(await screen.findByText('navigate back')); +// +// expect(await screen.findByText('first page')).toBeInTheDocument(); +// }); + +test('navigate to second page and back', async () => { + const subject = await render(MainComponent, {imports: [AppRoutingModule, RouterTestingModule]}); + + TestBed.inject(Router).initialNavigation(); + + await subject.navigate('/'); + + expect(await screen.findByText('Navigate')).toBeInTheDocument(); + expect(await screen.findByText('first page')).toBeInTheDocument(); + + userEvent.click(await screen.findByText('go to second')); + + expect(await screen.findByText('second page')).toBeInTheDocument(); + expect(await screen.findByText('navigate back')).toBeInTheDocument(); + + userEvent.click(await screen.findByText('navigate back')); + + expect(await screen.findByText('first page')).toBeInTheDocument(); +}); From d758b945dd2516675b65fde955291fea43cad282 Mon Sep 17 00:00:00 2001 From: Tim Deschryver <28659384+timdeschryver@users.noreply.github.com> Date: Fri, 25 Feb 2022 19:28:15 +0100 Subject: [PATCH 9/9] fix: invoke initialNavigation when a router is imported (#286) --- lint-staged.config.js | 2 +- .../src/lib/testing-library.ts | 9 ++- .../tests/issues/issue-280.spec.ts | 60 +++++++------------ 3 files changed, 26 insertions(+), 45 deletions(-) diff --git a/lint-staged.config.js b/lint-staged.config.js index ebb35d3b..00ee30fd 100644 --- a/lint-staged.config.js +++ b/lint-staged.config.js @@ -1,4 +1,4 @@ module.exports = { '*.{ts,js}': ['eslint --fix'], - '*.{json,md}': ['prettier --write'], + '*.{ts,js,json,md}': ['prettier --write'], }; diff --git a/projects/testing-library/src/lib/testing-library.ts b/projects/testing-library/src/lib/testing-library.ts index b1c8c16d..8a6ad6a7 100644 --- a/projects/testing-library/src/lib/testing-library.ts +++ b/projects/testing-library/src/lib/testing-library.ts @@ -116,13 +116,12 @@ export async function render( fixture.componentRef.injector.get(ChangeDetectorRef).detectChanges(); }; - let router = routes ? inject(Router) : null; const zone = inject(NgZone); - const navigate = async (elementOrPath: Element | string, basePath = ''): Promise => { - if (!router) { - router = inject(Router); - } + const router = inject(Router); + router?.initialNavigation(); + + const navigate = async (elementOrPath: Element | string, basePath = ''): Promise => { const href = typeof elementOrPath === 'string' ? elementOrPath : elementOrPath.getAttribute('href'); const [path, params] = (basePath + href).split('?'); const queryParams = params diff --git a/projects/testing-library/tests/issues/issue-280.spec.ts b/projects/testing-library/tests/issues/issue-280.spec.ts index 4c490741..19f644ef 100644 --- a/projects/testing-library/tests/issues/issue-280.spec.ts +++ b/projects/testing-library/tests/issues/issue-280.spec.ts @@ -1,66 +1,48 @@ -import {Component, NgModule} from '@angular/core'; -import {render, screen} from '@testing-library/angular'; -import {Router, RouterModule, Routes} from "@angular/router"; -import {RouterTestingModule} from "@angular/router/testing"; -import userEvent from "@testing-library/user-event"; -import {Location} from "@angular/common"; -import {TestBed} from "@angular/core/testing"; +import { Location } from '@angular/common'; +import { Component, NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; +import userEvent from '@testing-library/user-event'; +import { render, screen } from '../../src/public_api'; @Component({ - template: `
Navigate
`, + template: `
Navigate
+ `, }) class MainComponent {} @Component({ - template: `
first page
go to second` + template: `
first page
+ go to second`, }) class FirstComponent {} @Component({ - template: `
second page
` + template: `
second page
+ `, }) class SecondComponent { - constructor(private location: Location) { } - goBack() {this.location.back();} + constructor(private location: Location) {} + goBack() { + this.location.back(); + } } const routes: Routes = [ - {path: '', redirectTo: '/first', pathMatch: 'full'}, - {path: 'first', component: FirstComponent}, - {path: 'second', component: SecondComponent} + { path: '', redirectTo: '/first', pathMatch: 'full' }, + { path: 'first', component: FirstComponent }, + { path: 'second', component: SecondComponent }, ]; @NgModule({ declarations: [FirstComponent, SecondComponent], imports: [RouterModule.forRoot(routes)], - exports: [RouterModule] + exports: [RouterModule], }) class AppRoutingModule {} - -// test('navigate to second page and back', async () => { -// const subject = await render(MainComponent, {imports: [AppRoutingModule, RouterTestingModule]}); -// await subject.navigate('/'); -// -// expect(await screen.findByText('Navigate')).toBeInTheDocument(); -// expect(await screen.findByText('first page')).toBeInTheDocument(); -// -// userEvent.click(await screen.findByText('go to second')); -// -// expect(await screen.findByText('second page')).toBeInTheDocument(); -// expect(await screen.findByText('navigate back')).toBeInTheDocument(); -// -// userEvent.click(await screen.findByText('navigate back')); -// -// expect(await screen.findByText('first page')).toBeInTheDocument(); -// }); - test('navigate to second page and back', async () => { - const subject = await render(MainComponent, {imports: [AppRoutingModule, RouterTestingModule]}); - - TestBed.inject(Router).initialNavigation(); - - await subject.navigate('/'); + await render(MainComponent, { imports: [AppRoutingModule, RouterTestingModule] }); expect(await screen.findByText('Navigate')).toBeInTheDocument(); expect(await screen.findByText('first page')).toBeInTheDocument();