From 81b457b8e5ec8e15d8af92cfdfdb666e83f90046 Mon Sep 17 00:00:00 2001 From: Mahdi Lazraq <94069699+mlz11@users.noreply.github.com> Date: Wed, 14 Aug 2024 08:52:03 +0200 Subject: [PATCH 01/22] feat: deprecate componentProperties (#486) Closes #471 --- projects/testing-library/src/lib/models.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/testing-library/src/lib/models.ts b/projects/testing-library/src/lib/models.ts index 1956728..62413fb 100644 --- a/projects/testing-library/src/lib/models.ts +++ b/projects/testing-library/src/lib/models.ts @@ -215,7 +215,7 @@ export interface RenderComponentOptions Date: Sat, 17 Aug 2024 10:51:10 +0200 Subject: [PATCH 02/22] chore: bump @angular-eslint/eslint-plugin v17.3.0 -> v18.3.0 (#488) --- .github/workflows/ci.yml | 2 +- package.json | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5820814..28aac0d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: with: node-version: ${{ matrix.node-version }} - name: install - run: npm install --force + run: npm install - name: build run: npm run build -- --skip-nx-cache - name: test diff --git a/package.json b/package.json index 235d0ec..a934fb2 100644 --- a/package.json +++ b/package.json @@ -47,11 +47,11 @@ "@angular-devkit/build-angular": "18.0.1", "@angular-devkit/core": "18.0.1", "@angular-devkit/schematics": "18.0.1", - "@angular-eslint/builder": "17.3.0", - "@angular-eslint/eslint-plugin": "17.3.0", - "@angular-eslint/eslint-plugin-template": "17.3.0", - "@angular-eslint/schematics": "17.5.1", - "@angular-eslint/template-parser": "17.3.0", + "@angular-eslint/builder": "18.3.0", + "@angular-eslint/eslint-plugin": "18.3.0", + "@angular-eslint/eslint-plugin-template": "18.3.0", + "@angular-eslint/schematics": "18.3.0", + "@angular-eslint/template-parser": "18.3.0", "@angular/cli": "~18.0.0", "@angular/compiler-cli": "18.0.0", "@angular/forms": "18.0.0", From eb4fc7445b58af9b4fb6e0f0f5b886d4c0504065 Mon Sep 17 00:00:00 2001 From: Tim Deschryver <28659384+timdeschryver@users.noreply.github.com> Date: Fri, 23 Aug 2024 18:47:53 +0200 Subject: [PATCH 03/22] fix: update description in JSDocs (#489) --- projects/testing-library/src/lib/models.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/projects/testing-library/src/lib/models.ts b/projects/testing-library/src/lib/models.ts index 62413fb..a52371d 100644 --- a/projects/testing-library/src/lib/models.ts +++ b/projects/testing-library/src/lib/models.ts @@ -254,10 +254,11 @@ export interface RenderComponentOptions; @@ -292,7 +293,7 @@ export interface RenderComponentOptions { ... } * await render(AppComponent, { * on: { - * send: (_v:any) => void + * send: (value) => sendValue(value) * } * }) */ From fbbed20eaabd71db5e480af54da738b151482c80 Mon Sep 17 00:00:00 2001 From: Tim Deschryver <28659384+timdeschryver@users.noreply.github.com> Date: Mon, 14 Oct 2024 09:26:57 +0200 Subject: [PATCH 04/22] docs: add test case for #492 (#495) --- .../tests/issues/issue-492.spec.ts | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 projects/testing-library/tests/issues/issue-492.spec.ts diff --git a/projects/testing-library/tests/issues/issue-492.spec.ts b/projects/testing-library/tests/issues/issue-492.spec.ts new file mode 100644 index 0000000..981f5de --- /dev/null +++ b/projects/testing-library/tests/issues/issue-492.spec.ts @@ -0,0 +1,48 @@ +import { AsyncPipe } from '@angular/common'; +import { Component, inject, Injectable } from '@angular/core'; +import { render, screen, waitFor } from '../../src/public_api'; +import { Observable, BehaviorSubject, map } from 'rxjs'; + +test('displays username', async () => { + // stubbed user service using a Subject + const user = new BehaviorSubject({ name: 'username 1' }); + const userServiceStub: Partial = { + getName: () => user.asObservable().pipe(map((u) => u.name)), + }; + + // render the component with injection of the stubbed service + await render(UserComponent, { + componentProviders: [ + { + provide: UserService, + useValue: userServiceStub, + }, + ], + }); + + // assert first username emitted is rendered + expect(await screen.findByRole('heading')).toHaveTextContent('username 1'); + + // emitting a second username + user.next({ name: 'username 2' }); + + // assert the second username is rendered + await waitFor(() => expect(screen.getByRole('heading')).toHaveTextContent('username 2')); +}); + +@Component({ + selector: 'atl-user', + standalone: true, + template: `

{{ username$ | async }}

`, + imports: [AsyncPipe], +}) +class UserComponent { + readonly username$: Observable = inject(UserService).getName(); +} + +@Injectable() +class UserService { + getName(): Observable { + throw new Error('Not implemented'); + } +} From be9c3d5cb990b98768f95bbc12e5ed33cbed72a6 Mon Sep 17 00:00:00 2001 From: Tim Deschryver <28659384+timdeschryver@users.noreply.github.com> Date: Mon, 14 Oct 2024 09:30:37 +0200 Subject: [PATCH 05/22] docs: add test case for #492 --- projects/testing-library/tests/issues/issue-492.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/projects/testing-library/tests/issues/issue-492.spec.ts b/projects/testing-library/tests/issues/issue-492.spec.ts index 981f5de..a1e44b0 100644 --- a/projects/testing-library/tests/issues/issue-492.spec.ts +++ b/projects/testing-library/tests/issues/issue-492.spec.ts @@ -1,6 +1,6 @@ import { AsyncPipe } from '@angular/common'; import { Component, inject, Injectable } from '@angular/core'; -import { render, screen, waitFor } from '../../src/public_api'; +import { render, screen } from '../../src/public_api'; import { Observable, BehaviorSubject, map } from 'rxjs'; test('displays username', async () => { @@ -21,13 +21,13 @@ test('displays username', async () => { }); // assert first username emitted is rendered - expect(await screen.findByRole('heading')).toHaveTextContent('username 1'); + expect(await screen.findByRole('heading', { name: 'username 1' })).toBeInTheDocument(); // emitting a second username user.next({ name: 'username 2' }); // assert the second username is rendered - await waitFor(() => expect(screen.getByRole('heading')).toHaveTextContent('username 2')); + expect(await screen.findByRole('heading', { name: 'username 2' })).toBeInTheDocument(); }); @Component({ From 2cfca82442cc2d51ba08e649f37b65fa285eadb6 Mon Sep 17 00:00:00 2001 From: Tim Deschryver <28659384+timdeschryver@users.noreply.github.com> Date: Mon, 14 Oct 2024 09:35:35 +0200 Subject: [PATCH 06/22] docs: add test case for #491 (#494) --- .../src/app/issues/issue-491.spec.ts | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 apps/example-app-karma/src/app/issues/issue-491.spec.ts diff --git a/apps/example-app-karma/src/app/issues/issue-491.spec.ts b/apps/example-app-karma/src/app/issues/issue-491.spec.ts new file mode 100644 index 0000000..7da4d6d --- /dev/null +++ b/apps/example-app-karma/src/app/issues/issue-491.spec.ts @@ -0,0 +1,57 @@ +import { Component } from '@angular/core'; +import { Router } from '@angular/router'; +import { render, screen, waitForElementToBeRemoved } from '@testing-library/angular'; +import userEvent from '@testing-library/user-event'; + +it('test click event with router.navigate', async () => { + const user = userEvent.setup(); + await render(``, { + routes: [ + { + path: '', + component: LoginComponent, + }, + { + path: 'logged-in', + component: LoggedInComponent, + }, + ], + }); + + expect(await screen.findByRole('heading', { name: 'Login' })).toBeVisible(); + expect(screen.getByRole('button', { name: 'submit' })).toBeInTheDocument(); + + const email = screen.getByRole('textbox', { name: 'email' }); + const password = screen.getByLabelText('password'); + + await user.type(email, 'user@example.com'); + await user.type(password, 'with_valid_password'); + + expect(screen.getByRole('button', { name: 'submit' })).toBeEnabled(); + + await user.click(screen.getByRole('button', { name: 'submit' })); + + await waitForElementToBeRemoved(() => screen.queryByRole('heading', { name: 'Login' })); + + expect(await screen.findByRole('heading', { name: 'Logged In' })).toBeVisible(); +}); + +@Component({ + template: ` +

Login

+ + + + `, +}) +class LoginComponent { + constructor(private router: Router) {} + onSubmit(): void { + this.router.navigate(['logged-in']); + } +} + +@Component({ + template: `

Logged In

`, +}) +class LoggedInComponent {} From e8ddcf469a5c6dbadd6a1497cfa113ae56920461 Mon Sep 17 00:00:00 2001 From: Tim Deschryver <28659384+timdeschryver@users.noreply.github.com> Date: Mon, 14 Oct 2024 19:06:58 +0200 Subject: [PATCH 07/22] docs: add test case for #493 (#496) --- .../tests/issues/issue-493.spec.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 projects/testing-library/tests/issues/issue-493.spec.ts diff --git a/projects/testing-library/tests/issues/issue-493.spec.ts b/projects/testing-library/tests/issues/issue-493.spec.ts new file mode 100644 index 0000000..a49bc80 --- /dev/null +++ b/projects/testing-library/tests/issues/issue-493.spec.ts @@ -0,0 +1,27 @@ +import { HttpClient, provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; +import { Component, input } from '@angular/core'; +import { render, screen } from '../../src/public_api'; + +test('succeeds', async () => { + await render(DummyComponent, { + inputs: { + value: 'test', + }, + providers: [provideHttpClientTesting(), provideHttpClient()], + }); + + expect(screen.getByText('test')).toBeVisible(); +}); + +@Component({ + selector: 'atl-dummy', + standalone: true, + imports: [], + template: '

{{ value() }}

', +}) +class DummyComponent { + value = input.required(); + // @ts-ignore + constructor(private http: HttpClient) {} +} From 5a0665fad22a79af5837d0c110148a09ff963d11 Mon Sep 17 00:00:00 2001 From: Suguru Inatomi Date: Sun, 10 Nov 2024 01:58:38 +0900 Subject: [PATCH 08/22] fix: make wrapper component `standalone: false` explicitly (#498) --- projects/testing-library/src/lib/models.ts | 1 + projects/testing-library/src/lib/testing-library.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/projects/testing-library/src/lib/models.ts b/projects/testing-library/src/lib/models.ts index a52371d..0c34aa8 100644 --- a/projects/testing-library/src/lib/models.ts +++ b/projects/testing-library/src/lib/models.ts @@ -473,6 +473,7 @@ export interface RenderTemplateOptions Date: Fri, 29 Nov 2024 19:52:25 +0100 Subject: [PATCH 09/22] chore: update to NX 20 (#502) --- .gitignore | 1 + .prettierignore | 3 +- jest.config.ts | 8 +++--- package.json | 73 +++++++++++++++++++++++++------------------------ 4 files changed, 44 insertions(+), 41 deletions(-) diff --git a/.gitignore b/.gitignore index 215c8cb..d16a75b 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ /.angular/cache .angular .nx +migrations.json .cache /.sass-cache /connect.lock diff --git a/.prettierignore b/.prettierignore index 2bdc4f9..03ff48d 100644 --- a/.prettierignore +++ b/.prettierignore @@ -54,4 +54,5 @@ deployment.yaml .DS_Store Thumbs.db -/.nx/cache \ No newline at end of file +/.nx/cache +/.nx/workspace-data \ No newline at end of file diff --git a/jest.config.ts b/jest.config.ts index 0830aab..f5c10f4 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -1,5 +1,5 @@ -const { getJestProjects } = require('@nx/jest'); +const { getJestProjectsAsync } = require('@nx/jest'); -export default { - projects: getJestProjects(), -}; +export default async () => ({ + projects: await getJestProjectsAsync(), +}); diff --git a/package.json b/package.json index a934fb2..e5030d0 100644 --- a/package.json +++ b/package.json @@ -27,51 +27,52 @@ "prepare": "git config core.hookspath .githooks" }, "dependencies": { - "@angular/animations": "18.0.0", - "@angular/cdk": "18.0.0", - "@angular/common": "18.0.0", - "@angular/compiler": "18.0.0", - "@angular/core": "18.0.0", - "@angular/material": "18.0.0", - "@angular/platform-browser": "18.0.0", - "@angular/platform-browser-dynamic": "18.0.0", - "@angular/router": "18.0.0", - "@ngrx/store": "18.0.0-beta.1", - "@nx/angular": "19.1.0", + "@angular/animations": "18.2.13", + "@angular/cdk": "18.2.14", + "@angular/common": "18.2.13", + "@angular/compiler": "18.2.13", + "@angular/core": "18.2.13", + "@angular/material": "18.2.14", + "@angular/platform-browser": "18.2.13", + "@angular/platform-browser-dynamic": "18.2.13", + "@angular/router": "18.2.13", + "@ngrx/store": "18.0.2", + "@nx/angular": "20.1.3", "@testing-library/dom": "^10.0.0", "rxjs": "7.8.0", "tslib": "~2.3.1", - "zone.js": "0.14.2" + "zone.js": "0.14.10" }, "devDependencies": { - "@angular-devkit/build-angular": "18.0.1", - "@angular-devkit/core": "18.0.1", - "@angular-devkit/schematics": "18.0.1", + "@angular-devkit/build-angular": "18.2.9", + "@angular-devkit/core": "18.2.9", + "@angular-devkit/schematics": "18.2.9", "@angular-eslint/builder": "18.3.0", - "@angular-eslint/eslint-plugin": "18.3.0", - "@angular-eslint/eslint-plugin-template": "18.3.0", + "@angular-eslint/eslint-plugin": "18.0.1", + "@angular-eslint/eslint-plugin-template": "18.0.1", "@angular-eslint/schematics": "18.3.0", - "@angular-eslint/template-parser": "18.3.0", - "@angular/cli": "~18.0.0", - "@angular/compiler-cli": "18.0.0", - "@angular/forms": "18.0.0", - "@angular/language-service": "18.0.0", - "@nx/eslint": "19.1.0", - "@nx/eslint-plugin": "19.1.0", - "@nx/jest": "19.1.0", - "@nx/node": "19.1.0", - "@nx/plugin": "19.1.0", - "@nx/workspace": "19.1.0", - "@schematics/angular": "18.0.1", + "@angular-eslint/template-parser": "18.0.1", + "@angular/cli": "~18.2.0", + "@angular/compiler-cli": "18.2.13", + "@angular/forms": "18.2.13", + "@angular/language-service": "18.2.13", + "@nx/eslint": "20.1.3", + "@nx/eslint-plugin": "20.1.3", + "@nx/jest": "20.1.3", + "@nx/node": "20.1.3", + "@nx/plugin": "20.1.3", + "@nx/workspace": "20.1.3", + "@schematics/angular": "18.2.9", "@testing-library/jasmine-dom": "^1.2.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/user-event": "^14.4.3", "@types/jasmine": "4.3.1", - "@types/jest": "29.5.1", + "@types/jest": "29.5.14", "@types/node": "18.16.9", "@types/testing-library__jasmine-dom": "^1.3.0", - "@typescript-eslint/eslint-plugin": "7.3.0", - "@typescript-eslint/parser": "7.3.0", + "@typescript-eslint/eslint-plugin": "7.16.0", + "@typescript-eslint/parser": "7.16.0", + "@typescript-eslint/utils": "^7.16.0", "autoprefixer": "^10.4.0", "cpy-cli": "^3.1.1", "eslint": "8.57.0", @@ -84,7 +85,7 @@ "jasmine-core": "4.2.0", "jasmine-spec-reporter": "7.0.0", "jest": "29.7.0", - "jest-environment-jsdom": "29.5.0", + "jest-environment-jsdom": "29.7.0", "jest-preset-angular": "14.1.0", "karma": "6.4.0", "karma-chrome-launcher": "^3.1.0", @@ -93,8 +94,8 @@ "karma-jasmine-html-reporter": "2.0.0", "lint-staged": "^12.1.6", "ng-mocks": "^14.11.0", - "ng-packagr": "18.0.0", - "nx": "19.1.0", + "ng-packagr": "18.2.1", + "nx": "20.1.3", "postcss": "^8.4.5", "postcss-import": "14.1.0", "postcss-preset-env": "7.5.0", @@ -104,6 +105,6 @@ "semantic-release": "^18.0.0", "ts-jest": "29.1.0", "ts-node": "10.9.1", - "typescript": "5.4.5" + "typescript": "5.5.4" } } From 061d5cc70aae3b2946707e1711ac833906f0aef5 Mon Sep 17 00:00:00 2001 From: Arthur Petrie Date: Sat, 30 Nov 2024 13:30:06 +0100 Subject: [PATCH 10/22] perf: optimize reduce and foreach loops (#501) --- .../src/lib/testing-library.ts | 73 ++++++++++--------- 1 file changed, 40 insertions(+), 33 deletions(-) diff --git a/projects/testing-library/src/lib/testing-library.ts b/projects/testing-library/src/lib/testing-library.ts index ffd3898..8dfa946 100644 --- a/projects/testing-library/src/lib/testing-library.ts +++ b/projects/testing-library/src/lib/testing-library.ts @@ -2,7 +2,6 @@ import { ApplicationInitStatus, ChangeDetectorRef, Component, - isStandalone, NgZone, OnChanges, OutputRef, @@ -10,6 +9,7 @@ import { SimpleChange, SimpleChanges, Type, + isStandalone, } from '@angular/core'; import { ComponentFixture, DeferBlockBehavior, DeferBlockState, TestBed, tick } from '@angular/core/testing'; import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations'; @@ -27,14 +27,14 @@ import { waitForOptions as dtlWaitForOptions, within as dtlWithin, } from '@testing-library/dom'; +import { getConfig } from './config'; import { ComponentOverride, + OutputRefKeysWithCallback, RenderComponentOptions, RenderResult, RenderTemplateOptions, - OutputRefKeysWithCallback, } from './models'; -import { getConfig } from './config'; type SubscribedOutput = readonly [key: keyof T, callback: (v: any) => void, subscription: OutputRefSubscription]; @@ -71,7 +71,7 @@ export async function render( on = {}, componentProviders = [], childComponentOverrides = [], - componentImports: componentImports, + componentImports, excludeComponentDeclaration = false, routes = [], removeAngularAttributes = false, @@ -116,12 +116,9 @@ export async function render( await TestBed.compileComponents(); - componentProviders - .reduce((acc, provider) => acc.concat(provider), [] as any[]) - .forEach((p: any) => { - const { provide, ...provider } = p; - TestBed.overrideProvider(provide, provider); - }); + for (const { provide, ...provider } of componentProviders) { + TestBed.overrideProvider(provide, provider); + } const componentContainer = createComponentFixture(sut, wrapper); @@ -158,7 +155,9 @@ export async function render( let result; if (zone) { - await zone.run(() => (result = doNavigate())); + await zone.run(() => { + result = doNavigate(); + }); } else { result = doNavigate(); } @@ -199,7 +198,7 @@ export async function render( if (removeAngularAttributes) { createdFixture.nativeElement.removeAttribute('ng-version'); const idAttribute = createdFixture.nativeElement.getAttribute('id'); - if (idAttribute && idAttribute.startsWith('root')) { + if (idAttribute?.startsWith('root')) { createdFixture.nativeElement.removeAttribute('id'); } } @@ -207,7 +206,9 @@ export async function render( mountedFixtures.add(createdFixture); let isAlive = true; - createdFixture.componentRef.onDestroy(() => (isAlive = false)); + createdFixture.componentRef.onDestroy(() => { + isAlive = false; + }); if (hasOnChangesHook(createdFixture.componentInstance) && Object.keys(properties).length > 0) { const changes = getChangesObj(null, componentProperties); @@ -318,10 +319,15 @@ export async function render( }, debugElement: fixture.debugElement, 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)), + debug: (element = fixture.nativeElement, maxLength, options) => { + if (Array.isArray(element)) { + for (const e of element) { + console.log(dtlPrettyDOM(e, maxLength, options)); + } + } else { + console.log(dtlPrettyDOM(element, maxLength, options)); + } + }, ...replaceFindWithFindAndDetectChanges(dtlGetQueriesForElement(fixture.nativeElement, queries)), }; } @@ -423,9 +429,11 @@ function overrideComponentImports(sut: Type | string, imports: } function overrideChildComponentProviders(componentOverrides: ComponentOverride[]) { - componentOverrides?.forEach(({ component, providers }) => { - TestBed.overrideComponent(component, { set: { providers } }); - }); + if (componentOverrides) { + for (const { component, providers } of componentOverrides) { + TestBed.overrideComponent(component, { set: { providers } }); + } + } } function hasOnChangesHook(componentInstance: SutType): componentInstance is SutType & OnChanges { @@ -439,13 +447,10 @@ function hasOnChangesHook(componentInstance: SutType): componentInstanc function getChangesObj(oldProps: Record | null, newProps: Record) { const isFirstChange = oldProps === null; - return Object.keys(newProps).reduce( - (changes, key) => ({ - ...changes, - [key]: new SimpleChange(isFirstChange ? null : oldProps[key], newProps[key], isFirstChange), - }), - {} as Record, - ); + return Object.keys(newProps).reduce((changes, key) => { + changes[key] = new SimpleChange(isFirstChange ? null : oldProps[key], newProps[key], isFirstChange); + return changes; + }, {} as Record); } function update( @@ -461,10 +466,12 @@ function update( const componentInstance = fixture.componentInstance as Record; const simpleChanges: SimpleChanges = {}; - for (const key of prevRenderedKeys) { - if (!partialUpdate && !Object.prototype.hasOwnProperty.call(newValues, key)) { - simpleChanges[key] = new SimpleChange(componentInstance[key], undefined, false); - delete componentInstance[key]; + if (!partialUpdate) { + for (const key of prevRenderedKeys) { + if (!Object.prototype.hasOwnProperty.call(newValues, key)) { + simpleChanges[key] = new SimpleChange(componentInstance[key], undefined, false); + delete componentInstance[key]; + } } } @@ -643,7 +650,7 @@ function replaceFindWithFindAndDetectChanges>(orig * Call detectChanges for all fixtures */ function detectChangesForMountedFixtures() { - mountedFixtures.forEach((fixture) => { + for (const fixture of mountedFixtures) { try { fixture.detectChanges(); } catch (err: any) { @@ -651,7 +658,7 @@ function detectChangesForMountedFixtures() { throw err; } } - }); + } } /** From 4bd4ab2541a77ebb8b9c265a046f4b17b9bfffe0 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sat, 30 Nov 2024 13:45:33 +0100 Subject: [PATCH 11/22] docs: add Arthie as a contributor for code (#504) * docs: update README.md [skip ci] * docs: update .all-contributorsrc [skip ci] --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ README.md | 3 +++ 2 files changed, 12 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 198cd09..3b3a470 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -410,6 +410,15 @@ "code", "test" ] + }, + { + "login": "Arthie", + "name": "Arthur Petrie", + "avatar_url": "https://avatars.githubusercontent.com/u/16376476?v=4", + "profile": "https://arthurpetrie.com", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/README.md b/README.md index 17bffce..c98936d 100644 --- a/README.md +++ b/README.md @@ -270,6 +270,9 @@ Thanks goes to these people ([emoji key][emojis]): Daniel Ramírez Barrientos
Daniel Ramírez Barrientos

💻 Mahdi Lazraq
Mahdi Lazraq

💻 ⚠️ + + Arthur Petrie
Arthur Petrie

💻 + From fdcf5fa5c2e30b535439be0766d0b023fce7c35d Mon Sep 17 00:00:00 2001 From: Tim Deschryver <28659384+timdeschryver@users.noreply.github.com> Date: Tue, 3 Dec 2024 10:12:09 +0100 Subject: [PATCH 12/22] fix: support Angular 19 (#503) --- .github/workflows/ci.yml | 2 +- package.json | 46 +++++++-------- .../src/lib/testing-library.ts | 15 +++-- projects/testing-library/test-setup.ts | 4 +- projects/testing-library/tests/config.spec.ts | 1 + .../testing-library/tests/find-by.spec.ts | 2 + .../testing-library/tests/integration.spec.ts | 38 +++++++------ .../tests/issues/issue-230.spec.ts | 4 +- .../tests/issues/issue-280.spec.ts | 9 +-- .../tests/render-template.spec.ts | 18 +++--- projects/testing-library/tests/render.spec.ts | 57 ++++++++++--------- .../wait-for-element-to-be-removed.spec.ts | 2 + 12 files changed, 111 insertions(+), 87 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 28aac0d..5820814 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: with: node-version: ${{ matrix.node-version }} - name: install - run: npm install + run: npm install --force - name: build run: npm run build -- --skip-nx-cache - name: test diff --git a/package.json b/package.json index e5030d0..0f8aad8 100644 --- a/package.json +++ b/package.json @@ -27,35 +27,35 @@ "prepare": "git config core.hookspath .githooks" }, "dependencies": { - "@angular/animations": "18.2.13", - "@angular/cdk": "18.2.14", - "@angular/common": "18.2.13", - "@angular/compiler": "18.2.13", - "@angular/core": "18.2.13", - "@angular/material": "18.2.14", - "@angular/platform-browser": "18.2.13", - "@angular/platform-browser-dynamic": "18.2.13", - "@angular/router": "18.2.13", - "@ngrx/store": "18.0.2", + "@angular/animations": "19.0.1", + "@angular/cdk": "19.0.1", + "@angular/common": "19.0.1", + "@angular/compiler": "19.0.1", + "@angular/core": "19.0.1", + "@angular/material": "19.0.1", + "@angular/platform-browser": "19.0.1", + "@angular/platform-browser-dynamic": "19.0.1", + "@angular/router": "19.0.1", + "@ngrx/store": "19.0.0-beta.0", "@nx/angular": "20.1.3", - "@testing-library/dom": "^10.0.0", + "@testing-library/dom": "^10.4.0", "rxjs": "7.8.0", "tslib": "~2.3.1", - "zone.js": "0.14.10" + "zone.js": "^0.15.0" }, "devDependencies": { - "@angular-devkit/build-angular": "18.2.9", - "@angular-devkit/core": "18.2.9", - "@angular-devkit/schematics": "18.2.9", + "@angular-devkit/build-angular": "19.0.1", + "@angular-devkit/core": "19.0.1", + "@angular-devkit/schematics": "19.0.1", "@angular-eslint/builder": "18.3.0", "@angular-eslint/eslint-plugin": "18.0.1", "@angular-eslint/eslint-plugin-template": "18.0.1", "@angular-eslint/schematics": "18.3.0", "@angular-eslint/template-parser": "18.0.1", - "@angular/cli": "~18.2.0", - "@angular/compiler-cli": "18.2.13", - "@angular/forms": "18.2.13", - "@angular/language-service": "18.2.13", + "@angular/cli": "19.0.1", + "@angular/compiler-cli": "19.0.1", + "@angular/forms": "19.0.1", + "@angular/language-service": "19.0.1", "@nx/eslint": "20.1.3", "@nx/eslint-plugin": "20.1.3", "@nx/jest": "20.1.3", @@ -68,7 +68,7 @@ "@testing-library/user-event": "^14.4.3", "@types/jasmine": "4.3.1", "@types/jest": "29.5.14", - "@types/node": "18.16.9", + "@types/node": "22.10.1", "@types/testing-library__jasmine-dom": "^1.3.0", "@typescript-eslint/eslint-plugin": "7.16.0", "@typescript-eslint/parser": "7.16.0", @@ -86,7 +86,7 @@ "jasmine-spec-reporter": "7.0.0", "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", - "jest-preset-angular": "14.1.0", + "jest-preset-angular": "14.4.1", "karma": "6.4.0", "karma-chrome-launcher": "^3.1.0", "karma-coverage": "^2.2.1", @@ -94,7 +94,7 @@ "karma-jasmine-html-reporter": "2.0.0", "lint-staged": "^12.1.6", "ng-mocks": "^14.11.0", - "ng-packagr": "18.2.1", + "ng-packagr": "19.0.1", "nx": "20.1.3", "postcss": "^8.4.5", "postcss-import": "14.1.0", @@ -105,6 +105,6 @@ "semantic-release": "^18.0.0", "ts-jest": "29.1.0", "ts-node": "10.9.1", - "typescript": "5.5.4" + "typescript": "5.6.2" } } diff --git a/projects/testing-library/src/lib/testing-library.ts b/projects/testing-library/src/lib/testing-library.ts index 8dfa946..7c7de89 100644 --- a/projects/testing-library/src/lib/testing-library.ts +++ b/projects/testing-library/src/lib/testing-library.ts @@ -34,6 +34,7 @@ import { RenderComponentOptions, RenderResult, RenderTemplateOptions, + Config, } from './models'; type SubscribedOutput = readonly [key: keyof T, callback: (v: any) => void, subscription: OutputRefSubscription]; @@ -82,7 +83,9 @@ export async function render( configureTestBed = () => { /* noop*/ }, - } = { ...globalConfig, ...renderOptions }; + } = { ...globalConfig, ...renderOptions } as RenderComponentOptions & + RenderTemplateOptions & + Config; dtlConfigure({ eventWrapper: (cb) => { @@ -228,7 +231,7 @@ export async function render( return createdFixture; }; - const fixture = await renderFixture(componentProperties, allInputs, componentOutputs, on); + const fixture = await renderFixture(componentProperties, allInputs as any, componentOutputs, on); if (deferBlockStates) { if (Array.isArray(deferBlockStates)) { @@ -494,12 +497,16 @@ function addAutoDeclarations( wrapper, }: Pick, 'declarations' | 'excludeComponentDeclaration' | 'wrapper'>, ) { + const nonStandaloneDeclarations = declarations?.filter((d) => !isStandalone(d)); if (typeof sut === 'string') { - return [...declarations, wrapper]; + if (wrapper && isStandalone(wrapper)) { + return nonStandaloneDeclarations; + } + return [...nonStandaloneDeclarations, wrapper]; } const components = () => (excludeComponentDeclaration || isStandalone(sut) ? [] : [sut]); - return [...declarations, ...components()]; + return [...nonStandaloneDeclarations, ...components()]; } function addAutoImports( diff --git a/projects/testing-library/test-setup.ts b/projects/testing-library/test-setup.ts index 600d085..8d79c74 100644 --- a/projects/testing-library/test-setup.ts +++ b/projects/testing-library/test-setup.ts @@ -1,6 +1,8 @@ -import 'jest-preset-angular/setup-jest'; +import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone'; import '@testing-library/jest-dom'; import { TextEncoder, TextDecoder } from 'util'; +setupZoneTestEnv(); + // eslint-disable-next-line @typescript-eslint/naming-convention Object.assign(global, { TextDecoder, TextEncoder }); diff --git a/projects/testing-library/tests/config.spec.ts b/projects/testing-library/tests/config.spec.ts index bb8c61f..041d991 100644 --- a/projects/testing-library/tests/config.spec.ts +++ b/projects/testing-library/tests/config.spec.ts @@ -13,6 +13,7 @@ import { ReactiveFormsModule, FormBuilder } from '@angular/forms'; `, + standalone: false, }) class FormsComponent { form = this.formBuilder.group({ diff --git a/projects/testing-library/tests/find-by.spec.ts b/projects/testing-library/tests/find-by.spec.ts index 9d499fd..30f11ee 100644 --- a/projects/testing-library/tests/find-by.spec.ts +++ b/projects/testing-library/tests/find-by.spec.ts @@ -2,10 +2,12 @@ import { Component } from '@angular/core'; import { timer } from 'rxjs'; import { render, screen } from '../src/public_api'; import { mapTo } from 'rxjs/operators'; +import { AsyncPipe } from '@angular/common'; @Component({ selector: 'atl-fixture', template: `
{{ result | async }}
`, + imports: [AsyncPipe], }) class FixtureComponent { result = timer(30).pipe(mapTo('I am visible')); diff --git a/projects/testing-library/tests/integration.spec.ts b/projects/testing-library/tests/integration.spec.ts index eedec0e..02ca290 100644 --- a/projects/testing-library/tests/integration.spec.ts +++ b/projects/testing-library/tests/integration.spec.ts @@ -4,6 +4,7 @@ import { of, BehaviorSubject } from 'rxjs'; import { debounceTime, switchMap, map, startWith } from 'rxjs/operators'; import { render, screen, waitFor, waitForElementToBeRemoved, within } from '../src/lib/testing-library'; import userEvent from '@testing-library/user-event'; +import { AsyncPipe, NgForOf } from '@angular/common'; const DEBOUNCE_TIME = 1_000; @@ -21,6 +22,25 @@ class ModalService { } } +@Component({ + selector: 'atl-table', + template: ` + + + + + +
{{ entity.name }} + +
+ `, + imports: [NgForOf], +}) +class TableComponent { + @Input() entities: any[] = []; + @Output() edit = new EventEmitter(); +} + @Component({ template: `

Entities Title

@@ -31,6 +51,7 @@ class ModalService { `, + imports: [TableComponent, AsyncPipe], }) class EntitiesComponent { query = new BehaviorSubject(''); @@ -55,22 +76,6 @@ class EntitiesComponent { } } -@Component({ - selector: 'atl-table', - template: ` - - - - - -
{{ entity.name }}
- `, -}) -class TableComponent { - @Input() entities: any[] = []; - @Output() edit = new EventEmitter(); -} - const entities = [ { id: 1, @@ -91,7 +96,6 @@ async function setup() { const user = userEvent.setup(); await render(EntitiesComponent, { - declarations: [TableComponent], providers: [ { provide: EntitiesService, diff --git a/projects/testing-library/tests/issues/issue-230.spec.ts b/projects/testing-library/tests/issues/issue-230.spec.ts index fe004b6..8df58f6 100644 --- a/projects/testing-library/tests/issues/issue-230.spec.ts +++ b/projects/testing-library/tests/issues/issue-230.spec.ts @@ -1,8 +1,10 @@ import { Component } from '@angular/core'; import { render, waitFor, screen } from '../../src/public_api'; +import { NgClass } from '@angular/common'; @Component({ template: ` `, + imports: [NgClass], }) class LoopComponent { get classes() { @@ -17,7 +19,7 @@ test('wait does not end up in a loop', async () => { await expect( waitFor(() => { - expect(true).toEqual(false); + expect(true).toBe(false); }), ).rejects.toThrow(); }); diff --git a/projects/testing-library/tests/issues/issue-280.spec.ts b/projects/testing-library/tests/issues/issue-280.spec.ts index 19f644e..5e59534 100644 --- a/projects/testing-library/tests/issues/issue-280.spec.ts +++ b/projects/testing-library/tests/issues/issue-280.spec.ts @@ -1,19 +1,21 @@ import { Location } from '@angular/common'; import { Component, NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; +import { RouterLink, RouterModule, RouterOutlet, 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
`, + imports: [RouterOutlet], }) class MainComponent {} @Component({ - template: `
first page
+ template: `
first page
go to second`, + imports: [RouterLink], }) class FirstComponent {} @@ -35,7 +37,6 @@ const routes: Routes = [ ]; @NgModule({ - declarations: [FirstComponent, SecondComponent], imports: [RouterModule.forRoot(routes)], exports: [RouterModule], }) diff --git a/projects/testing-library/tests/render-template.spec.ts b/projects/testing-library/tests/render-template.spec.ts index a6892db..e185f70 100644 --- a/projects/testing-library/tests/render-template.spec.ts +++ b/projects/testing-library/tests/render-template.spec.ts @@ -45,7 +45,7 @@ class GreetingComponent { test('the directive renders', async () => { const view = await render('
', { - declarations: [OnOffDirective], + imports: [OnOffDirective], }); // eslint-disable-next-line testing-library/no-container @@ -54,7 +54,7 @@ test('the directive renders', async () => { test('the component renders', async () => { const view = await render('', { - declarations: [GreetingComponent], + imports: [GreetingComponent], }); // eslint-disable-next-line testing-library/no-container @@ -64,7 +64,7 @@ test('the component renders', async () => { test('uses the default props', async () => { await render('
', { - declarations: [OnOffDirective], + imports: [OnOffDirective], }); fireEvent.click(screen.getByText('init')); @@ -74,7 +74,7 @@ test('uses the default props', async () => { test('overrides input properties', async () => { await render('
', { - declarations: [OnOffDirective], + imports: [OnOffDirective], }); fireEvent.click(screen.getByText('init')); @@ -85,7 +85,7 @@ test('overrides input properties', async () => { test('overrides input properties via a wrapper', async () => { // `bar` will be set as a property on the wrapper component, the property will be used to pass to the directive await render('
', { - declarations: [OnOffDirective], + imports: [OnOffDirective], componentProperties: { bar: 'hello', }, @@ -100,7 +100,7 @@ test('overrides output properties', async () => { const clicked = jest.fn(); await render('
', { - declarations: [OnOffDirective], + imports: [OnOffDirective], componentProperties: { clicked, }, @@ -116,7 +116,7 @@ test('overrides output properties', async () => { describe('removeAngularAttributes', () => { it('should remove angular attributes', async () => { await render('
', { - declarations: [OnOffDirective], + imports: [OnOffDirective], removeAngularAttributes: true, }); @@ -126,7 +126,7 @@ describe('removeAngularAttributes', () => { it('is disabled by default', async () => { await render('
', { - declarations: [OnOffDirective], + imports: [OnOffDirective], }); expect(document.querySelector('[ng-version]')).not.toBeNull(); @@ -136,7 +136,7 @@ describe('removeAngularAttributes', () => { test('updates properties and invokes change detection', async () => { const view = await render<{ value: string }>('
', { - declarations: [UpdateInputDirective], + imports: [UpdateInputDirective], componentProperties: { value: 'value1', }, diff --git a/projects/testing-library/tests/render.spec.ts b/projects/testing-library/tests/render.spec.ts index 59e0f75..52d318c 100644 --- a/projects/testing-library/tests/render.spec.ts +++ b/projects/testing-library/tests/render.spec.ts @@ -47,33 +47,31 @@ describe('DTL functionality', () => { }); }); -describe('standalone', () => { +describe('components', () => { @Component({ selector: 'atl-fixture', template: ` {{ name }} `, }) - class StandaloneFixtureComponent { + class FixtureWithInputComponent { @Input() name = ''; } - it('renders standalone component', async () => { - await render(StandaloneFixtureComponent, { componentProperties: { name: 'Bob' } }); + it('renders component', async () => { + await render(FixtureWithInputComponent, { componentProperties: { name: 'Bob' } }); expect(screen.getByText('Bob')).toBeInTheDocument(); }); }); -describe('standalone with child', () => { +describe('component with child', () => { @Component({ selector: 'atl-child-fixture', template: `A child fixture`, - standalone: true, }) class ChildFixtureComponent {} @Component({ selector: 'atl-child-fixture', template: `A mock child fixture`, - standalone: true, // eslint-disable-next-line @angular-eslint/no-host-metadata-property, @typescript-eslint/naming-convention host: { 'collision-id': MockChildFixtureComponent.name }, }) @@ -83,18 +81,17 @@ describe('standalone with child', () => { selector: 'atl-parent-fixture', template: `

Parent fixture

`, - standalone: true, imports: [ChildFixtureComponent], }) class ParentFixtureComponent {} - it('renders the standalone component with a mocked child', async () => { + it('renders the component with a mocked child', async () => { await render(ParentFixtureComponent, { componentImports: [MockChildFixtureComponent] }); expect(screen.getByText('Parent fixture')).toBeInTheDocument(); expect(screen.getByText('A mock child fixture')).toBeInTheDocument(); }); - it('renders the standalone component with child', async () => { + it('renders the component with child', async () => { await render(ParentFixtureComponent); expect(screen.getByText('Parent fixture')).toBeInTheDocument(); expect(screen.getByText('A child fixture')).toBeInTheDocument(); @@ -118,7 +115,6 @@ describe('childComponentOverrides', () => { @Component({ selector: 'atl-child-fixture', template: `{{ simpleService.value }}`, - standalone: true, providers: [MySimpleService], }) class NestedChildFixtureComponent { @@ -128,7 +124,6 @@ describe('childComponentOverrides', () => { @Component({ selector: 'atl-parent-fixture', template: ``, - standalone: true, imports: [NestedChildFixtureComponent], }) class ParentFixtureComponent {} @@ -190,22 +185,22 @@ describe('componentOutputs', () => { }); describe('on', () => { - @Component({ template: ``, standalone: true }) + @Component({ template: `` }) class TestFixtureWithEventEmitterComponent { @Output() readonly event = new EventEmitter(); } - @Component({ template: ``, standalone: true }) + @Component({ template: `` }) class TestFixtureWithDerivedEventComponent { @Output() readonly event = fromEvent(inject(ElementRef).nativeElement, 'click'); } - @Component({ template: ``, standalone: true }) + @Component({ template: `` }) class TestFixtureWithFunctionalOutputComponent { readonly event = output(); } - @Component({ template: ``, standalone: true }) + @Component({ template: `` }) class TestFixtureWithFunctionalDerivedEventComponent { readonly event = outputFromObservable(fromEvent(inject(ElementRef).nativeElement, 'click')); } @@ -313,20 +308,31 @@ describe('on', () => { }); }); -describe('animationModule', () => { +describe('excludeComponentDeclaration', () => { + @Component({ + selector: 'atl-fixture', + template: ` + + + `, + standalone: false, + }) + class NotStandaloneFixtureComponent {} + @NgModule({ - declarations: [FixtureComponent], + declarations: [NotStandaloneFixtureComponent], }) class FixtureModule {} - describe('excludeComponentDeclaration', () => { - it('does not throw if component is declared in an imported module', async () => { - await render(FixtureComponent, { - imports: [FixtureModule], - excludeComponentDeclaration: true, - }); + + it('does not throw if component is declared in an imported module', async () => { + await render(NotStandaloneFixtureComponent, { + imports: [FixtureModule], + excludeComponentDeclaration: true, }); }); +}); +describe('animationModule', () => { it('adds NoopAnimationsModule by default', async () => { await render(FixtureComponent); const noopAnimationsModule = TestBed.inject(NoopAnimationsModule); @@ -458,14 +464,12 @@ describe('DebugElement', () => { describe('initialRoute', () => { @Component({ - standalone: true, selector: 'atl-fixture2', template: ``, }) class SecondaryFixtureComponent {} @Component({ - standalone: true, selector: 'atl-router-fixture', template: ``, imports: [RouterModule], @@ -502,7 +506,6 @@ describe('initialRoute', () => { it('allows initially rendering a specific route with query parameters', async () => { @Component({ - standalone: true, selector: 'atl-query-param-fixture', template: `

paramPresent$: {{ paramPresent$ | async }}

`, imports: [NgIf, AsyncPipe], diff --git a/projects/testing-library/tests/wait-for-element-to-be-removed.spec.ts b/projects/testing-library/tests/wait-for-element-to-be-removed.spec.ts index 5c16a53..64d6c35 100644 --- a/projects/testing-library/tests/wait-for-element-to-be-removed.spec.ts +++ b/projects/testing-library/tests/wait-for-element-to-be-removed.spec.ts @@ -1,10 +1,12 @@ import { Component, OnInit } from '@angular/core'; import { render, screen, waitForElementToBeRemoved } from '../src/public_api'; import { timer } from 'rxjs'; +import { NgIf } from '@angular/common'; @Component({ selector: 'atl-fixture', template: `
👋
`, + imports: [NgIf], }) class FixtureComponent implements OnInit { visible = true; From d59e4f213a4441e63b3157f83e54e9e9d6070a6d Mon Sep 17 00:00:00 2001 From: Suguru Inatomi Date: Sun, 15 Dec 2024 01:35:56 +0900 Subject: [PATCH 13/22] fix: flatten component providers for consistency to the framework (#507) Closes #506 --- .../src/lib/testing-library.ts | 3 ++- .../providers/component-provider.spec.ts | 20 ++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/projects/testing-library/src/lib/testing-library.ts b/projects/testing-library/src/lib/testing-library.ts index 7c7de89..2ddf389 100644 --- a/projects/testing-library/src/lib/testing-library.ts +++ b/projects/testing-library/src/lib/testing-library.ts @@ -119,7 +119,8 @@ export async function render( await TestBed.compileComponents(); - for (const { provide, ...provider } of componentProviders) { + // Angular supports nested arrays of providers, so we need to flatten them to emulate the same behavior. + for (const { provide, ...provider } of componentProviders.flat(Infinity)) { TestBed.overrideProvider(provide, provider); } diff --git a/projects/testing-library/tests/providers/component-provider.spec.ts b/projects/testing-library/tests/providers/component-provider.spec.ts index 3c3ec0c..9290d5b 100644 --- a/projects/testing-library/tests/providers/component-provider.spec.ts +++ b/projects/testing-library/tests/providers/component-provider.spec.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { Injectable, Provider } from '@angular/core'; import { Component } from '@angular/core'; import { render, screen } from '../../src/public_api'; @@ -42,6 +42,24 @@ test('shows the provided service value with template syntax', async () => { expect(screen.getByText('bar')).toBeInTheDocument(); }); +test('flatten the nested array of component providers', async () => { + const provideService = (): Provider => [ + { + provide: Service, + useValue: { + foo() { + return 'bar'; + }, + }, + }, + ]; + await render(FixtureComponent, { + componentProviders: [provideService()], + }); + + expect(screen.getByText('bar')).toBeInTheDocument(); +}); + @Injectable() class Service { foo() { From 5ef0863a2e984d47e077ada300e87d8d3adbfabf Mon Sep 17 00:00:00 2001 From: Tim Deschryver <28659384+timdeschryver@users.noreply.github.com> Date: Sat, 21 Dec 2024 18:58:26 +0100 Subject: [PATCH 14/22] docs: add Angular 19 to Version compatibility table (#509) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c98936d..5ecd39e 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,7 @@ You may also be interested in installing `jest-dom` so you can use | Angular | Angular Testing Library | | ------- | ---------------------------- | +| 19.x | 17.x, 16.x, 15.x, 14.x, 13.x | | 18.x | 17.x, 16.x, 15.x, 14.x, 13.x | | 17.x | 17.x, 16.x, 15.x, 14.x, 13.x | | 16.x | 14.x, 13.x | From 33af2876d3dca98e52b6633b70cf088f7354071a Mon Sep 17 00:00:00 2001 From: Tim Deschryver <28659384+timdeschryver@users.noreply.github.com> Date: Fri, 3 Jan 2025 20:20:39 +0100 Subject: [PATCH 15/22] chore: update deps (#510) --- .devcontainer/devcontainer.json | 2 +- .github/workflows/ci.yml | 2 +- .node-version | 2 +- package.json | 34 ++++++++++++++++----------------- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index db976dc..72780fc 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,7 +1,7 @@ // For format details, see https://aka.ms/devcontainer.json. { "name": "angular-testing-library", - "image": "mcr.microsoft.com/devcontainers/typescript-node:0-20-bullseye", + "image": "mcr.microsoft.com/devcontainers/typescript-node:0-22-bullseye", // Features to add to the dev container. More info: https://containers.dev/features. "features": { diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5820814..9955c0d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: strategy: matrix: - node-version: ${{ fromJSON((github.ref == 'refs/heads/main' || github.ref == 'refs/heads/beta') && '[20]' || '[18, 20]') }} + node-version: ${{ fromJSON((github.ref == 'refs/heads/main' || github.ref == 'refs/heads/beta') && '[22]' || '[18, 20, 22]') }} os: ${{ fromJSON((github.ref == 'refs/heads/main' || github.ref == 'refs/heads/beta') && '["ubuntu-latest"]' || '["ubuntu-latest", "windows-latest"]') }} runs-on: ${{ matrix.os }} diff --git a/.node-version b/.node-version index 914d1a7..8fdd954 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -20.9 \ No newline at end of file +22 \ No newline at end of file diff --git a/package.json b/package.json index 0f8aad8..16ac142 100644 --- a/package.json +++ b/package.json @@ -36,8 +36,8 @@ "@angular/platform-browser": "19.0.1", "@angular/platform-browser-dynamic": "19.0.1", "@angular/router": "19.0.1", - "@ngrx/store": "19.0.0-beta.0", - "@nx/angular": "20.1.3", + "@ngrx/store": "19.0.0", + "@nx/angular": "20.3.0", "@testing-library/dom": "^10.4.0", "rxjs": "7.8.0", "tslib": "~2.3.1", @@ -47,21 +47,21 @@ "@angular-devkit/build-angular": "19.0.1", "@angular-devkit/core": "19.0.1", "@angular-devkit/schematics": "19.0.1", - "@angular-eslint/builder": "18.3.0", - "@angular-eslint/eslint-plugin": "18.0.1", - "@angular-eslint/eslint-plugin-template": "18.0.1", - "@angular-eslint/schematics": "18.3.0", - "@angular-eslint/template-parser": "18.0.1", - "@angular/cli": "19.0.1", + "@angular-eslint/builder": "19.0.2", + "@angular-eslint/eslint-plugin": "19.0.2", + "@angular-eslint/eslint-plugin-template": "19.0.2", + "@angular-eslint/schematics": "19.0.2", + "@angular-eslint/template-parser": "19.0.2", + "@angular/cli": "~19.0.0", "@angular/compiler-cli": "19.0.1", "@angular/forms": "19.0.1", "@angular/language-service": "19.0.1", - "@nx/eslint": "20.1.3", - "@nx/eslint-plugin": "20.1.3", - "@nx/jest": "20.1.3", - "@nx/node": "20.1.3", - "@nx/plugin": "20.1.3", - "@nx/workspace": "20.1.3", + "@nx/eslint": "20.3.0", + "@nx/eslint-plugin": "20.3.0", + "@nx/jest": "20.3.0", + "@nx/node": "20.3.0", + "@nx/plugin": "20.3.0", + "@nx/workspace": "20.3.0", "@schematics/angular": "18.2.9", "@testing-library/jasmine-dom": "^1.2.0", "@testing-library/jest-dom": "^5.16.5", @@ -86,16 +86,16 @@ "jasmine-spec-reporter": "7.0.0", "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", - "jest-preset-angular": "14.4.1", + "jest-preset-angular": "14.4.2", "karma": "6.4.0", "karma-chrome-launcher": "^3.1.0", "karma-coverage": "^2.2.1", "karma-jasmine": "5.1.0", "karma-jasmine-html-reporter": "2.0.0", "lint-staged": "^12.1.6", - "ng-mocks": "^14.11.0", + "ng-mocks": "^14.13.1", "ng-packagr": "19.0.1", - "nx": "20.1.3", + "nx": "20.3.0", "postcss": "^8.4.5", "postcss-import": "14.1.0", "postcss-preset-env": "7.5.0", From e0cd81e6a881dafe92cad10d19ecef26be977f88 Mon Sep 17 00:00:00 2001 From: Tim Deschryver <28659384+timdeschryver@users.noreply.github.com> Date: Sat, 4 Jan 2025 13:51:48 +0100 Subject: [PATCH 16/22] chore: migrate to eslint flat config --- .eslintignore | 1 - .eslintrc.json | 127 ------------------ .github/workflows/ci.yml | 2 + README.md | 2 +- apps/example-app-karma/.eslintrc.json | 44 ------ apps/example-app-karma/eslint.config.cjs | 7 + apps/example-app-karma/eslint.config.mjs | 8 ++ apps/example-app-karma/jasmine-dom.d.ts | 1 - .../src/app/examples/login-form.spec.ts | 4 +- .../src/app/issues/rerender.spec.ts | 4 +- apps/example-app/.eslintrc.json | 47 ------- apps/example-app/eslint.config.cjs | 7 + apps/example-app/eslint.config.mjs | 8 ++ apps/example-app/jest.config.ts | 1 - .../src/app/examples/00-single-component.ts | 2 +- .../src/app/examples/01-nested-component.ts | 12 +- .../src/app/examples/02-input-output.spec.ts | 4 +- .../src/app/examples/02-input-output.ts | 2 +- apps/example-app/src/app/examples/03-forms.ts | 2 +- .../app/examples/04-forms-with-material.ts | 2 +- .../src/app/examples/05-component-provider.ts | 2 +- .../src/app/examples/06-with-ngrx-store.ts | 2 +- .../app/examples/07-with-ngrx-mock-store.ts | 6 +- .../src/app/examples/08-directive.spec.ts | 8 +- .../src/app/examples/08-directive.ts | 2 +- .../example-app/src/app/examples/09-router.ts | 6 +- .../examples/10-inject-token-dependency.ts | 2 +- .../src/app/examples/11-ng-content.spec.ts | 2 +- .../src/app/examples/11-ng-content.ts | 2 +- .../src/app/examples/12-service-component.ts | 2 +- .../app/examples/13-scrolling.component.ts | 2 +- .../app/examples/14-async-component.spec.ts | 1 - .../src/app/examples/14-async-component.ts | 2 +- .../app/examples/15-dialog.component.spec.ts | 2 +- .../src/app/examples/15-dialog.component.ts | 4 +- .../app/examples/16-input-getter-setter.ts | 2 +- ...-component-with-attribute-selector.spec.ts | 2 +- .../17-component-with-attribute-selector.ts | 2 +- .../app/examples/19-standalone-component.ts | 6 +- .../src/app/examples/20-test-harness.spec.ts | 4 +- .../src/app/examples/20-test-harness.ts | 2 +- .../examples/21-deferable-view.component.ts | 4 +- .../22-signal-inputs.component.spec.ts | 2 +- .../examples/22-signal-inputs.component.ts | 6 +- apps/example-app/src/test-setup.ts | 4 +- eslint.config.cjs | 7 + eslint.config.mjs | 66 +++++++++ nx.json | 4 +- package.json | 48 ++++--- projects/testing-library/.eslintrc.json | 62 --------- projects/testing-library/eslint.config.cjs | 7 + projects/testing-library/eslint.config.mjs | 8 ++ projects/testing-library/jest.config.ts | 1 - .../dtl-as-dev-dependency/index.spec.ts | 2 - .../schematics/ng-add/schema.ts | 1 - projects/testing-library/src/lib/models.ts | 4 +- .../src/lib/testing-library.ts | 2 +- projects/testing-library/test-setup.ts | 1 - projects/testing-library/tests/debug.spec.ts | 8 +- .../tests/issues/issue-280.spec.ts | 4 +- .../tests/issues/issue-389.spec.ts | 1 - .../issue-396-standalone-stub-child.spec.ts | 1 - ...ssue-398-component-without-host-id.spec.ts | 2 - .../issue-422-view-already-destroyed.spec.ts | 1 + .../tests/issues/issue-437.spec.ts | 2 - .../tests/issues/issue-493.spec.ts | 2 +- projects/testing-library/tests/render.spec.ts | 19 ++- 67 files changed, 222 insertions(+), 397 deletions(-) delete mode 100644 .eslintignore delete mode 100644 .eslintrc.json delete mode 100644 apps/example-app-karma/.eslintrc.json create mode 100644 apps/example-app-karma/eslint.config.cjs create mode 100644 apps/example-app-karma/eslint.config.mjs delete mode 100644 apps/example-app/.eslintrc.json create mode 100644 apps/example-app/eslint.config.cjs create mode 100644 apps/example-app/eslint.config.mjs create mode 100644 eslint.config.cjs create mode 100644 eslint.config.mjs delete mode 100644 projects/testing-library/.eslintrc.json create mode 100644 projects/testing-library/eslint.config.cjs create mode 100644 projects/testing-library/eslint.config.mjs diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 3c3629e..0000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -node_modules diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 0a96094..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,127 +0,0 @@ -{ - "root": true, - "ignorePatterns": ["**/*"], - "plugins": ["@nx", "testing-library"], - "overrides": [ - { - "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], - "rules": { - "@nx/enforce-module-boundaries": [ - "error", - { - "enforceBuildableLibDependency": true, - "allow": [], - "depConstraints": [ - { - "sourceTag": "*", - "onlyDependOnLibsWithTags": ["*"] - } - ] - } - ] - } - }, - { - "files": ["*.ts", "*.tsx"], - "extends": ["plugin:@nx/typescript"], - "rules": { - "@typescript-eslint/no-extra-semi": "error", - "no-extra-semi": "off" - } - }, - { - "files": ["*.js", "*.jsx"], - "extends": ["plugin:@nx/javascript"], - "rules": { - "@typescript-eslint/no-extra-semi": "error", - "no-extra-semi": "off" - } - }, - { - "files": ["*.ts"], - "plugins": ["eslint-plugin-import", "@angular-eslint/eslint-plugin", "@typescript-eslint"], - "rules": { - "@typescript-eslint/consistent-type-definitions": "error", - "@typescript-eslint/dot-notation": "off", - "@typescript-eslint/naming-convention": "error", - "@typescript-eslint/no-shadow": [ - "error", - { - "hoist": "all" - } - ], - "@typescript-eslint/no-unused-expressions": "error", - "@typescript-eslint/prefer-function-type": "error", - "@typescript-eslint/quotes": "off", - "@typescript-eslint/type-annotation-spacing": "error", - "@typescript-eslint/no-explicit-any": "off", - "arrow-body-style": "off", - "brace-style": ["error", "1tbs"], - "curly": "error", - "eol-last": "error", - "eqeqeq": ["error", "smart"], - "guard-for-in": "error", - "id-blacklist": "off", - "id-match": "off", - "import/no-deprecated": "warn", - "no-bitwise": "error", - "no-caller": "error", - "no-console": [ - "error", - { - "allow": [ - "log", - "warn", - "dir", - "timeLog", - "assert", - "clear", - "count", - "countReset", - "group", - "groupEnd", - "table", - "dirxml", - "error", - "groupCollapsed", - "Console", - "profile", - "profileEnd", - "timeStamp", - "context" - ] - } - ], - "no-empty": "off", - "no-eval": "error", - "no-new-wrappers": "error", - "no-throw-literal": "error", - "no-undef-init": "error", - "no-underscore-dangle": "off", - "radix": "error", - "spaced-comment": [ - "error", - "always", - { - "markers": ["/"] - } - ] - } - }, - { - "files": ["*.html"], - "rules": {} - }, - { - "files": ["*.ts", "*.js"], - "extends": ["prettier"] - }, - { - "files": ["*.spec.ts"], - "extends": ["plugin:testing-library/angular"], - "rules": { - "testing-library/prefer-explicit-assert": "error" - } - } - ] -} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9955c0d..215ef7d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,6 +38,8 @@ jobs: run: npm run build -- --skip-nx-cache - name: test run: npm run test + - name: lint + run: npm run lint - name: Release if: github.repository == 'testing-library/angular-testing-library' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/beta') run: npx semantic-release diff --git a/README.md b/README.md index 5ecd39e..e03ce68 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ counter.component.ts ```ts @Component({ - selector: 'app-counter', + selector: 'atl-counter', template: ` {{ hello() }} diff --git a/apps/example-app-karma/.eslintrc.json b/apps/example-app-karma/.eslintrc.json deleted file mode 100644 index 404aa66..0000000 --- a/apps/example-app-karma/.eslintrc.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "extends": "../../.eslintrc.json", - "ignorePatterns": ["!**/*"], - "overrides": [ - { - "files": ["*.ts"], - "extends": ["plugin:@nx/angular", "plugin:@angular-eslint/template/process-inline-templates"], - "parserOptions": { - "project": ["apps/example-app-karma/tsconfig.*?.json"] - }, - "rules": { - "@angular-eslint/directive-selector": [ - "error", - { - "type": "attribute", - "prefix": "app", - "style": "camelCase" - } - ], - "@angular-eslint/component-selector": [ - "error", - { - "type": "element", - "prefix": "app", - "style": "kebab-case" - } - ] - } - }, - { - "files": ["*.spec.ts"], - "env": { - "jasmine": true - }, - "plugins": ["jasmine"], - "extends": ["plugin:jasmine/recommended"] - }, - { - "files": ["*.html"], - "extends": ["plugin:@nx/angular-template"], - "rules": {} - } - ] -} diff --git a/apps/example-app-karma/eslint.config.cjs b/apps/example-app-karma/eslint.config.cjs new file mode 100644 index 0000000..9e951e7 --- /dev/null +++ b/apps/example-app-karma/eslint.config.cjs @@ -0,0 +1,7 @@ +// @ts-check + +// TODO - https://github.com/nrwl/nx/issues/22576 + +/** @type {import('@typescript-eslint/utils/ts-eslint').FlatConfig.ConfigPromise} */ +const config = (async () => (await import('./eslint.config.mjs')).default)(); +module.exports = config; diff --git a/apps/example-app-karma/eslint.config.mjs b/apps/example-app-karma/eslint.config.mjs new file mode 100644 index 0000000..8f627db --- /dev/null +++ b/apps/example-app-karma/eslint.config.mjs @@ -0,0 +1,8 @@ +// @ts-check + +import tseslint from "typescript-eslint"; +import rootConfig from "../../eslint.config.mjs"; + +export default tseslint.config( + ...rootConfig, +); diff --git a/apps/example-app-karma/jasmine-dom.d.ts b/apps/example-app-karma/jasmine-dom.d.ts index f8fa4a7..54d7903 100644 --- a/apps/example-app-karma/jasmine-dom.d.ts +++ b/apps/example-app-karma/jasmine-dom.d.ts @@ -1,5 +1,4 @@ declare module '@testing-library/jasmine-dom' { - // eslint-disable-next-line @typescript-eslint/naming-convention const JasmineDOM: any; export default JasmineDOM; } diff --git a/apps/example-app-karma/src/app/examples/login-form.spec.ts b/apps/example-app-karma/src/app/examples/login-form.spec.ts index 9c51065..a028234 100644 --- a/apps/example-app-karma/src/app/examples/login-form.spec.ts +++ b/apps/example-app-karma/src/app/examples/login-form.spec.ts @@ -29,7 +29,7 @@ it('should display invalid message and submit button must be disabled', async () }); @Component({ - selector: 'app-login', + selector: 'atl-login', standalone: true, imports: [ReactiveFormsModule, NgIf], template: ` @@ -51,7 +51,7 @@ class LoginComponent { }); constructor(private fb: FormBuilder) {} - + get email(): FormControl { return this.form.get('email') as FormControl; } diff --git a/apps/example-app-karma/src/app/issues/rerender.spec.ts b/apps/example-app-karma/src/app/issues/rerender.spec.ts index 9b044d1..324e8a1 100644 --- a/apps/example-app-karma/src/app/issues/rerender.spec.ts +++ b/apps/example-app-karma/src/app/issues/rerender.spec.ts @@ -7,9 +7,9 @@ it('can rerender component', async () => { }, }); - expect(screen.getByText('Hello Sarah')).toBeTruthy(); + expect(screen.getByText('Hello Sarah')).toBeInTheDocument(); await rerender({ componentProperties: { name: 'Mark' } }); - expect(screen.getByText('Hello Mark')).toBeTruthy(); + expect(screen.getByText('Hello Mark')).toBeInTheDocument(); }); diff --git a/apps/example-app/.eslintrc.json b/apps/example-app/.eslintrc.json deleted file mode 100644 index ed5e4d1..0000000 --- a/apps/example-app/.eslintrc.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "extends": "../../.eslintrc.json", - "ignorePatterns": ["!**/*"], - "overrides": [ - { - "files": ["*.ts"], - "extends": ["plugin:@nx/angular", "plugin:@angular-eslint/template/process-inline-templates"], - "parserOptions": { - "project": ["apps/example-app/tsconfig.*?.json"] - }, - "rules": { - "@angular-eslint/directive-selector": [ - "error", - { - "type": "attribute", - "prefix": "app", - "style": "camelCase" - } - ], - "@angular-eslint/component-selector": [ - "error", - { - "type": "element", - "prefix": "app", - "style": "kebab-case" - } - ] - } - }, - { - "files": ["*.spec.ts"], - "env": { - "jest": true - }, - "extends": ["plugin:jest/recommended", "plugin:jest/style", "plugin:jest-dom/recommended"], - "rules": { - "jest/consistent-test-it": ["error"], - "jest/expect-expect": "off" - } - }, - { - "files": ["*.html"], - "extends": ["plugin:@nx/angular-template"], - "rules": {} - } - ] -} diff --git a/apps/example-app/eslint.config.cjs b/apps/example-app/eslint.config.cjs new file mode 100644 index 0000000..9e951e7 --- /dev/null +++ b/apps/example-app/eslint.config.cjs @@ -0,0 +1,7 @@ +// @ts-check + +// TODO - https://github.com/nrwl/nx/issues/22576 + +/** @type {import('@typescript-eslint/utils/ts-eslint').FlatConfig.ConfigPromise} */ +const config = (async () => (await import('./eslint.config.mjs')).default)(); +module.exports = config; diff --git a/apps/example-app/eslint.config.mjs b/apps/example-app/eslint.config.mjs new file mode 100644 index 0000000..0162584 --- /dev/null +++ b/apps/example-app/eslint.config.mjs @@ -0,0 +1,8 @@ +// @ts-check + +import tseslint from "typescript-eslint"; +import rootConfig from "../../eslint.config.mjs"; + +export default tseslint.config( + ...rootConfig, +); \ No newline at end of file diff --git a/apps/example-app/jest.config.ts b/apps/example-app/jest.config.ts index 4b0c248..e0ea9c2 100644 --- a/apps/example-app/jest.config.ts +++ b/apps/example-app/jest.config.ts @@ -1,4 +1,3 @@ -/* eslint-disable */ export default { displayName: { name: 'Example App', diff --git a/apps/example-app/src/app/examples/00-single-component.ts b/apps/example-app/src/app/examples/00-single-component.ts index 7c132c2..4a09239 100644 --- a/apps/example-app/src/app/examples/00-single-component.ts +++ b/apps/example-app/src/app/examples/00-single-component.ts @@ -1,7 +1,7 @@ import { Component } from '@angular/core'; @Component({ - selector: 'app-fixture', + selector: 'atl-fixture', standalone: true, template: ` 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 645ce96..fd0d0c0 100644 --- a/apps/example-app/src/app/examples/01-nested-component.ts +++ b/apps/example-app/src/app/examples/01-nested-component.ts @@ -2,7 +2,7 @@ import { Component, Input, Output, EventEmitter } from '@angular/core'; @Component({ standalone: true, - selector: 'app-button', + selector: 'atl-button', template: ' ', }) export class NestedButtonComponent { @@ -12,7 +12,7 @@ export class NestedButtonComponent { @Component({ standalone: true, - selector: 'app-value', + selector: 'atl-value', template: ' {{ value }} ', }) export class NestedValueComponent { @@ -21,11 +21,11 @@ export class NestedValueComponent { @Component({ standalone: true, - selector: 'app-fixture', + selector: 'atl-fixture', template: ` - - - + + + `, imports: [NestedButtonComponent, NestedValueComponent], }) diff --git a/apps/example-app/src/app/examples/02-input-output.spec.ts b/apps/example-app/src/app/examples/02-input-output.spec.ts index 847f6e1..5a55bd5 100644 --- a/apps/example-app/src/app/examples/02-input-output.spec.ts +++ b/apps/example-app/src/app/examples/02-input-output.spec.ts @@ -36,7 +36,7 @@ test.skip('is possible to set input and listen for output with the template synt const user = userEvent.setup(); const sendSpy = jest.fn(); - await render('', { + await render('', { imports: [InputOutputComponent], on: { sendValue: sendSpy, @@ -94,7 +94,7 @@ test('is possible to set input and listen for output with the template syntax (d const user = userEvent.setup(); const sendSpy = jest.fn(); - await render('', { + await render('', { imports: [InputOutputComponent], componentProperties: { sendValue: sendSpy, diff --git a/apps/example-app/src/app/examples/02-input-output.ts b/apps/example-app/src/app/examples/02-input-output.ts index 5bf70ab..3d7f979 100644 --- a/apps/example-app/src/app/examples/02-input-output.ts +++ b/apps/example-app/src/app/examples/02-input-output.ts @@ -2,7 +2,7 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; @Component({ standalone: true, - selector: 'app-fixture', + selector: 'atl-fixture', template: ` {{ value }} diff --git a/apps/example-app/src/app/examples/03-forms.ts b/apps/example-app/src/app/examples/03-forms.ts index 49756dc..a62d865 100644 --- a/apps/example-app/src/app/examples/03-forms.ts +++ b/apps/example-app/src/app/examples/03-forms.ts @@ -4,7 +4,7 @@ import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; @Component({ standalone: true, - selector: 'app-fixture', + selector: 'atl-fixture', imports: [ReactiveFormsModule, NgForOf, NgIf], template: `
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 ef80493..cf117a5 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 @@ -18,7 +18,7 @@ import { MatNativeDateModule } from '@angular/material/core'; NgForOf, NgIf, ], - selector: 'app-fixture', + selector: 'atl-fixture', template: ` diff --git a/apps/example-app/src/app/examples/05-component-provider.ts b/apps/example-app/src/app/examples/05-component-provider.ts index 1f345b9..2d66b07 100644 --- a/apps/example-app/src/app/examples/05-component-provider.ts +++ b/apps/example-app/src/app/examples/05-component-provider.ts @@ -21,7 +21,7 @@ export class CounterService { @Component({ standalone: true, - selector: 'app-fixture', + selector: 'atl-fixture', template: ` {{ counter.value() }} 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 b1db1d4..8702843 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 @@ -18,7 +18,7 @@ const selectValue = createSelector( @Component({ standalone: true, imports: [AsyncPipe], - selector: 'app-fixture', + selector: 'atl-fixture', template: ` {{ value | async }} diff --git a/apps/example-app/src/app/examples/07-with-ngrx-mock-store.ts b/apps/example-app/src/app/examples/07-with-ngrx-mock-store.ts index 7754bf1..915a88d 100644 --- a/apps/example-app/src/app/examples/07-with-ngrx-mock-store.ts +++ b/apps/example-app/src/app/examples/07-with-ngrx-mock-store.ts @@ -10,10 +10,12 @@ export const selectItems = createSelector( @Component({ standalone: true, imports: [AsyncPipe, NgForOf], - selector: 'app-fixture', + selector: 'atl-fixture', template: `
    -
  • {{ item }}
  • +
  • + +
`, }) 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 8b70b38..28a41e9 100644 --- a/apps/example-app/src/app/examples/08-directive.spec.ts +++ b/apps/example-app/src/app/examples/08-directive.spec.ts @@ -6,7 +6,7 @@ import { SpoilerDirective } from './08-directive'; test('it is possible to test directives with container component', async () => { @Component({ - template: `
`, + template: `
`, imports: [SpoilerDirective], standalone: true, }) @@ -32,7 +32,7 @@ test('it is possible to test directives with container component', async () => { test('it is possible to test directives', async () => { const user = userEvent.setup(); - await render('
', { + await render('
', { imports: [SpoilerDirective], }); @@ -55,7 +55,7 @@ test('it is possible to test directives with props', async () => { const hidden = 'SPOILER ALERT'; const visible = 'There is nothing to see here ...'; - await render('
', { + await render('
', { imports: [SpoilerDirective], componentProperties: { hidden, @@ -80,7 +80,7 @@ test('it is possible to test directives with props in template', async () => { const hidden = 'SPOILER ALERT'; const visible = 'There is nothing to see here ...'; - await render(``, { + await render(``, { imports: [SpoilerDirective], }); diff --git a/apps/example-app/src/app/examples/08-directive.ts b/apps/example-app/src/app/examples/08-directive.ts index 40548b1..63efe41 100644 --- a/apps/example-app/src/app/examples/08-directive.ts +++ b/apps/example-app/src/app/examples/08-directive.ts @@ -2,7 +2,7 @@ import { Directive, HostListener, ElementRef, Input, OnInit } from '@angular/cor @Directive({ standalone: true, - selector: '[appSpoiler]', + selector: '[atlSpoiler]', }) export class SpoilerDirective implements OnInit { @Input() hidden = 'SPOILER'; diff --git a/apps/example-app/src/app/examples/09-router.ts b/apps/example-app/src/app/examples/09-router.ts index 888d7fd..e46773b 100644 --- a/apps/example-app/src/app/examples/09-router.ts +++ b/apps/example-app/src/app/examples/09-router.ts @@ -6,7 +6,7 @@ import { map } from 'rxjs/operators'; @Component({ standalone: true, imports: [RouterLink, RouterOutlet], - selector: 'app-main', + selector: 'atl-main', template: ` Load one | Load two | Load three | @@ -21,7 +21,7 @@ export class RootComponent {} @Component({ standalone: true, imports: [RouterLink, AsyncPipe], - selector: 'app-detail', + selector: 'atl-detail', template: `

Detail {{ id | async }}

@@ -40,7 +40,7 @@ export class DetailComponent { @Component({ standalone: true, - selector: 'app-detail-hidden', + selector: 'atl-detail-hidden', template: ' You found the treasure! ', }) export class HiddenDetailComponent {} diff --git a/apps/example-app/src/app/examples/10-inject-token-dependency.ts b/apps/example-app/src/app/examples/10-inject-token-dependency.ts index 6a17d53..f7b2f66 100644 --- a/apps/example-app/src/app/examples/10-inject-token-dependency.ts +++ b/apps/example-app/src/app/examples/10-inject-token-dependency.ts @@ -4,7 +4,7 @@ export const DATA = new InjectionToken<{ text: string }>('Components Data'); @Component({ standalone: true, - selector: 'app-fixture', + selector: 'atl-fixture', template: ' {{ data.text }} ', }) export class DataInjectedComponent { diff --git a/apps/example-app/src/app/examples/11-ng-content.spec.ts b/apps/example-app/src/app/examples/11-ng-content.spec.ts index 2f91025..468a3f2 100644 --- a/apps/example-app/src/app/examples/11-ng-content.spec.ts +++ b/apps/example-app/src/app/examples/11-ng-content.spec.ts @@ -5,7 +5,7 @@ import { CellComponent } from './11-ng-content'; test('it is possible to test ng-content without selector', async () => { const projection = 'it should be showed into a p element!'; - await render(`${projection}`, { + await render(`${projection}`, { imports: [CellComponent], }); diff --git a/apps/example-app/src/app/examples/11-ng-content.ts b/apps/example-app/src/app/examples/11-ng-content.ts index d444683..0dd668b 100644 --- a/apps/example-app/src/app/examples/11-ng-content.ts +++ b/apps/example-app/src/app/examples/11-ng-content.ts @@ -2,7 +2,7 @@ import { Component, ChangeDetectionStrategy } from '@angular/core'; @Component({ standalone: true, - selector: 'app-fixture', + selector: 'atl-fixture', template: `

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 2aed165..1746eb2 100644 --- a/apps/example-app/src/app/examples/12-service-component.ts +++ b/apps/example-app/src/app/examples/12-service-component.ts @@ -19,7 +19,7 @@ export class CustomersService { @Component({ standalone: true, imports: [AsyncPipe, NgForOf], - selector: 'app-fixture', + selector: 'atl-fixture', template: `

  • diff --git a/apps/example-app/src/app/examples/13-scrolling.component.ts b/apps/example-app/src/app/examples/13-scrolling.component.ts index 7d7b2e7..6a36ed8 100644 --- a/apps/example-app/src/app/examples/13-scrolling.component.ts +++ b/apps/example-app/src/app/examples/13-scrolling.component.ts @@ -4,7 +4,7 @@ import { ScrollingModule } from '@angular/cdk/scrolling'; @Component({ standalone: true, imports: [ScrollingModule], - selector: 'app-cdk-virtual-scroll-overview-example', + selector: 'atl-cdk-virtual-scroll-overview-example', template: `
    {{ item }}
    diff --git a/apps/example-app/src/app/examples/14-async-component.spec.ts b/apps/example-app/src/app/examples/14-async-component.spec.ts index b54740a..5cfd3e0 100644 --- a/apps/example-app/src/app/examples/14-async-component.spec.ts +++ b/apps/example-app/src/app/examples/14-async-component.spec.ts @@ -3,7 +3,6 @@ import { render, screen, fireEvent } from '@testing-library/angular'; import { AsyncComponent } from './14-async-component'; -// eslint-disable-next-line jest/no-disabled-tests test.skip('can use fakeAsync utilities', fakeAsync(async () => { await render(AsyncComponent); diff --git a/apps/example-app/src/app/examples/14-async-component.ts b/apps/example-app/src/app/examples/14-async-component.ts index f87732a..64d7aaa 100644 --- a/apps/example-app/src/app/examples/14-async-component.ts +++ b/apps/example-app/src/app/examples/14-async-component.ts @@ -6,7 +6,7 @@ import { delay, filter, mapTo } from 'rxjs/operators'; @Component({ standalone: true, imports: [AsyncPipe, NgIf], - selector: 'app-fixture', + selector: 'atl-fixture', template: `
    {{ data }}
    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 97ad829..017afdc 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 @@ -38,7 +38,7 @@ test('closes the dialog via the backdrop', async () => { const dialogTitleControl = await screen.findByRole('heading', { name: /dialog title/i }); expect(dialogTitleControl).toBeInTheDocument(); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, testing-library/no-node-access + // eslint-disable-next-line testing-library/no-node-access await user.click(document.querySelector('.cdk-overlay-backdrop')!); expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); diff --git a/apps/example-app/src/app/examples/15-dialog.component.ts b/apps/example-app/src/app/examples/15-dialog.component.ts index f6c8970..029ee64 100644 --- a/apps/example-app/src/app/examples/15-dialog.component.ts +++ b/apps/example-app/src/app/examples/15-dialog.component.ts @@ -4,7 +4,7 @@ import { MatDialog, MatDialogModule, MatDialogRef } from '@angular/material/dial @Component({ standalone: true, imports: [MatDialogModule], - selector: 'app-dialog-overview-example', + selector: 'atl-dialog-overview-example', template: '', }) export class DialogComponent { @@ -18,7 +18,7 @@ export class DialogComponent { @Component({ standalone: true, imports: [MatDialogModule], - selector: 'app-dialog-overview-example-dialog', + selector: 'atl-dialog-overview-example-dialog', template: `

    Dialog Title

    Dialog content
    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 a9097a4..4c18900 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 @@ -2,7 +2,7 @@ import { Component, Input } from '@angular/core'; @Component({ standalone: true, - selector: 'app-fixture', + selector: 'atl-fixture', template: ` {{ derivedValue }} {{ value }} diff --git a/apps/example-app/src/app/examples/17-component-with-attribute-selector.spec.ts b/apps/example-app/src/app/examples/17-component-with-attribute-selector.spec.ts index ba69f70..f33dee3 100644 --- a/apps/example-app/src/app/examples/17-component-with-attribute-selector.spec.ts +++ b/apps/example-app/src/app/examples/17-component-with-attribute-selector.spec.ts @@ -5,7 +5,7 @@ import { ComponentWithAttributeSelectorComponent } from './17-component-with-att // for components with attribute selectors! test('is possible to set input of component with attribute selector through template', async () => { await render( - ``, + ``, { imports: [ComponentWithAttributeSelectorComponent], }, diff --git a/apps/example-app/src/app/examples/17-component-with-attribute-selector.ts b/apps/example-app/src/app/examples/17-component-with-attribute-selector.ts index ac2a25d..930032c 100644 --- a/apps/example-app/src/app/examples/17-component-with-attribute-selector.ts +++ b/apps/example-app/src/app/examples/17-component-with-attribute-selector.ts @@ -2,7 +2,7 @@ import { Component, Input } from '@angular/core'; @Component({ standalone: true, - selector: 'app-fixture-component-with-attribute-selector[value]', + selector: 'atl-fixture-component-with-attribute-selector[value]', template: ` {{ value }} `, }) export class ComponentWithAttributeSelectorComponent { diff --git a/apps/example-app/src/app/examples/19-standalone-component.ts b/apps/example-app/src/app/examples/19-standalone-component.ts index efcf088..95eae3d 100644 --- a/apps/example-app/src/app/examples/19-standalone-component.ts +++ b/apps/example-app/src/app/examples/19-standalone-component.ts @@ -1,17 +1,17 @@ import { Component, Input } from '@angular/core'; @Component({ - selector: 'app-standalone', + selector: 'atl-standalone', template: `
    Standalone Component
    `, standalone: true, }) export class StandaloneComponent {} @Component({ - selector: 'app-standalone-with-child', + selector: 'atl-standalone-with-child', template: `

    Hi {{ name }}

    This has a child

    - `, + `, standalone: true, imports: [StandaloneComponent], }) diff --git a/apps/example-app/src/app/examples/20-test-harness.spec.ts b/apps/example-app/src/app/examples/20-test-harness.spec.ts index 6e4d9e3..4a88a58 100644 --- a/apps/example-app/src/app/examples/20-test-harness.spec.ts +++ b/apps/example-app/src/app/examples/20-test-harness.spec.ts @@ -6,9 +6,8 @@ import userEvent from '@testing-library/user-event'; import { HarnessComponent } from './20-test-harness'; -// eslint-disable-next-line jest/no-disabled-tests test.skip('can be used with TestHarness', async () => { - const view = await render(``, { + const view = await render(``, { imports: [HarnessComponent], }); const loader = TestbedHarnessEnvironment.documentRootLoader(view.fixture); @@ -21,7 +20,6 @@ test.skip('can be used with TestHarness', async () => { expect(await snackbarHarness.getMessage()).toMatch(/Pizza Party!!!/i); }); -// eslint-disable-next-line jest/no-disabled-tests test.skip('can be used in combination with TestHarness', async () => { const user = userEvent.setup(); diff --git a/apps/example-app/src/app/examples/20-test-harness.ts b/apps/example-app/src/app/examples/20-test-harness.ts index 08d6afd..8e5e407 100644 --- a/apps/example-app/src/app/examples/20-test-harness.ts +++ b/apps/example-app/src/app/examples/20-test-harness.ts @@ -3,7 +3,7 @@ import { MatButtonModule } from '@angular/material/button'; import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; @Component({ - selector: 'app-harness', + selector: 'atl-harness', standalone: true, imports: [MatButtonModule, MatSnackBarModule], template: ` diff --git a/apps/example-app/src/app/examples/21-deferable-view.component.ts b/apps/example-app/src/app/examples/21-deferable-view.component.ts index ce47a58..7b66d85 100644 --- a/apps/example-app/src/app/examples/21-deferable-view.component.ts +++ b/apps/example-app/src/app/examples/21-deferable-view.component.ts @@ -1,7 +1,7 @@ import { Component } from '@angular/core'; @Component({ - selector: 'app-deferable-view-child', + selector: 'atl-deferable-view-child', template: `

    Hello from deferred child component

    `, standalone: true, }) @@ -10,7 +10,7 @@ export class DeferableViewChildComponent {} @Component({ template: ` @defer (on timer(2s)) { - + } @placeholder {

    Hello from placeholder

    } @loading { diff --git a/apps/example-app/src/app/examples/22-signal-inputs.component.spec.ts b/apps/example-app/src/app/examples/22-signal-inputs.component.spec.ts index 470e639..355e8ae 100644 --- a/apps/example-app/src/app/examples/22-signal-inputs.component.spec.ts +++ b/apps/example-app/src/app/examples/22-signal-inputs.component.spec.ts @@ -60,7 +60,7 @@ test('output emits a value', async () => { age: '45', }, on: { - submit: submitFn, + submitValue: submitFn, }, }); diff --git a/apps/example-app/src/app/examples/22-signal-inputs.component.ts b/apps/example-app/src/app/examples/22-signal-inputs.component.ts index dfe6bd0..27ed23b 100644 --- a/apps/example-app/src/app/examples/22-signal-inputs.component.ts +++ b/apps/example-app/src/app/examples/22-signal-inputs.component.ts @@ -2,7 +2,7 @@ import { Component, computed, input, model, numberAttribute, output } from '@ang import { FormsModule } from '@angular/forms'; @Component({ - selector: 'app-signal-input', + selector: 'atl-signal-input', template: `
    {{ greetings() }} {{ name() }} of {{ age() }} years old
    {{ greetingMessage() }}
    @@ -18,11 +18,11 @@ export class SignalInputComponent { }); age = input.required({ transform: numberAttribute }); name = model.required(); - submit = output(); + submitValue = output(); greetingMessage = computed(() => `${this.greetings()} ${this.name()} of ${this.age()} years old`); submitName() { - this.submit.emit(this.name()); + this.submitValue.emit(this.name()); } } diff --git a/apps/example-app/src/test-setup.ts b/apps/example-app/src/test-setup.ts index 0da94a0..96bfd34 100644 --- a/apps/example-app/src/test-setup.ts +++ b/apps/example-app/src/test-setup.ts @@ -1,2 +1,4 @@ -import 'jest-preset-angular/setup-jest'; +import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone'; import '@testing-library/jest-dom'; + +setupZoneTestEnv(); diff --git a/eslint.config.cjs b/eslint.config.cjs new file mode 100644 index 0000000..9e951e7 --- /dev/null +++ b/eslint.config.cjs @@ -0,0 +1,7 @@ +// @ts-check + +// TODO - https://github.com/nrwl/nx/issues/22576 + +/** @type {import('@typescript-eslint/utils/ts-eslint').FlatConfig.ConfigPromise} */ +const config = (async () => (await import('./eslint.config.mjs')).default)(); +module.exports = config; diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..95e031a --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,66 @@ +// @ts-check + +import eslint from "@eslint/js"; +import tseslint from "typescript-eslint"; +import angular from "angular-eslint"; +import jestDom from 'eslint-plugin-jest-dom'; +import testingLibrary from 'eslint-plugin-testing-library'; + +export default tseslint.config( + { + files: ["**/*.ts"], + extends: [ + eslint.configs.recommended, + ...tseslint.configs.recommended, + ...tseslint.configs.stylistic, + ...angular.configs.tsRecommended, + ], + processor: angular.processInlineTemplates, + rules: { + "@angular-eslint/directive-selector": [ + "error", + { + type: "attribute", + prefix: "atl", + style: "camelCase", + }, + ], + "@angular-eslint/component-selector": [ + "error", + { + type: "element", + prefix: "atl", + style: "kebab-case", + }, + ], + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_" + } + ], + // These are needed for test cases + "@angular-eslint/prefer-standalone": "off", + "@angular-eslint/no-input-rename": "off", + "@angular-eslint/no-input-rename": "off", + }, + }, + { + files: ["**/*.spec.ts"], + extends: [ + jestDom.configs["flat/recommended"], + testingLibrary.configs["flat/angular"], + ], + }, + { + files: ["**/*.html"], + extends: [ + ...angular.configs.templateRecommended, + ...angular.configs.templateAccessibility, + ], + rules: {}, + } +); diff --git a/nx.json b/nx.json index df534f7..a308e67 100644 --- a/nx.json +++ b/nx.json @@ -82,7 +82,7 @@ } }, "@nx/eslint:lint": { - "inputs": ["default", "{workspaceRoot}/.eslintrc.json"], + "inputs": ["default", "{workspaceRoot}/eslint.config.cjs"], "cache": true } }, @@ -96,7 +96,7 @@ "!{projectRoot}/karma.conf.js", "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)", "!{projectRoot}/jest.config.[jt]s", - "!{projectRoot}/.eslintrc.json", + "!{projectRoot}/eslint.config.cjs", "!{projectRoot}/src/test-setup.[jt]s" ] }, diff --git a/package.json b/package.json index 16ac142..bbf0e3c 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "build": "nx run-many --target=build --projects=testing-library", "build:schematics": "tsc -p ./projects/testing-library/tsconfig.schematics.json", "test": "nx run-many --target=test --all --parallel=1", - "lint": "nx workspace-lint && nx lint", + "lint": "nx run-many --all --target=lint", "e2e": "nx e2e", "affected:apps": "nx affected:apps", "affected:libs": "nx affected:libs", @@ -40,7 +40,7 @@ "@nx/angular": "20.3.0", "@testing-library/dom": "^10.4.0", "rxjs": "7.8.0", - "tslib": "~2.3.1", + "tslib": "~2.8.1", "zone.js": "^0.15.0" }, "devDependencies": { @@ -52,10 +52,11 @@ "@angular-eslint/eslint-plugin-template": "19.0.2", "@angular-eslint/schematics": "19.0.2", "@angular-eslint/template-parser": "19.0.2", - "@angular/cli": "~19.0.0", + "@angular/cli": "~19.0.6", "@angular/compiler-cli": "19.0.1", "@angular/forms": "19.0.1", "@angular/language-service": "19.0.1", + "@eslint/eslintrc": "^2.1.1", "@nx/eslint": "20.3.0", "@nx/eslint-plugin": "20.3.0", "@nx/jest": "20.3.0", @@ -63,48 +64,45 @@ "@nx/plugin": "20.3.0", "@nx/workspace": "20.3.0", "@schematics/angular": "18.2.9", - "@testing-library/jasmine-dom": "^1.2.0", - "@testing-library/jest-dom": "^5.16.5", - "@testing-library/user-event": "^14.4.3", + "@testing-library/jasmine-dom": "^1.3.3", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/user-event": "^14.5.2", "@types/jasmine": "4.3.1", "@types/jest": "29.5.14", "@types/node": "22.10.1", - "@types/testing-library__jasmine-dom": "^1.3.0", - "@typescript-eslint/eslint-plugin": "7.16.0", - "@typescript-eslint/parser": "7.16.0", - "@typescript-eslint/utils": "^7.16.0", - "autoprefixer": "^10.4.0", - "cpy-cli": "^3.1.1", - "eslint": "8.57.0", - "eslint-config-prettier": "9.0.0", - "eslint-plugin-import": "~2.25.4", - "eslint-plugin-jasmine": "~4.1.3", - "eslint-plugin-jest": "^27.6.3", - "eslint-plugin-jest-dom": "~4.0.1", - "eslint-plugin-testing-library": "~5.0.1", + "@types/testing-library__jasmine-dom": "^1.3.4", + "@typescript-eslint/types": "^8.19.0", + "@typescript-eslint/utils": "^8.19.0", + "angular-eslint": "^19.0.2", + "autoprefixer": "^10.4.20", + "cpy-cli": "^5.0.0", + "eslint": "^9.8.0", + "eslint-plugin-jest-dom": "~5.5.0", + "eslint-plugin-testing-library": "~7.1.1", "jasmine-core": "4.2.0", "jasmine-spec-reporter": "7.0.0", "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", "jest-preset-angular": "14.4.2", "karma": "6.4.0", - "karma-chrome-launcher": "^3.1.0", + "karma-chrome-launcher": "^3.2.0", "karma-coverage": "^2.2.1", "karma-jasmine": "5.1.0", "karma-jasmine-html-reporter": "2.0.0", - "lint-staged": "^12.1.6", + "lint-staged": "^15.3.0", "ng-mocks": "^14.13.1", "ng-packagr": "19.0.1", "nx": "20.3.0", - "postcss": "^8.4.5", + "postcss": "^8.4.49", "postcss-import": "14.1.0", "postcss-preset-env": "7.5.0", "postcss-url": "10.1.3", "prettier": "2.6.2", - "rimraf": "^3.0.2", - "semantic-release": "^18.0.0", + "rimraf": "^5.0.10", + "semantic-release": "^24.2.1", "ts-jest": "29.1.0", "ts-node": "10.9.1", - "typescript": "5.6.2" + "typescript": "5.6.2", + "typescript-eslint": "^8.19.0" } } diff --git a/projects/testing-library/.eslintrc.json b/projects/testing-library/.eslintrc.json deleted file mode 100644 index 5a9d690..0000000 --- a/projects/testing-library/.eslintrc.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "extends": "../../.eslintrc.json", - "ignorePatterns": ["!**/*"], - "overrides": [ - { - "files": ["*.ts"], - "rules": { - "@typescript-eslint/ban-ts-comment": "off", - "@typescript-eslint/no-unused-vars": [ - "error", - { - "argsIgnorePattern": "^_", - "varsIgnorePattern": "^_", - "caughtErrorsIgnorePattern": "^_" - } - ] - } - }, - { - "files": ["*.ts"], - "extends": ["plugin:@nx/angular", "plugin:@angular-eslint/template/process-inline-templates"], - "parserOptions": { - "project": ["projects/testing-library/tsconfig.*?.json"] - }, - "rules": { - "@typescript-eslint/ban-ts-comment": "off", - "@angular-eslint/directive-selector": [ - "error", - { - "type": "attribute", - "prefix": "atl", - "style": "camelCase" - } - ], - "@angular-eslint/component-selector": [ - "error", - { - "type": "element", - "prefix": "atl", - "style": "kebab-case" - } - ] - } - }, - { - "files": ["*.spec.ts"], - "env": { - "jest": true - }, - "extends": ["plugin:jest/recommended", "plugin:jest/style", "plugin:jest-dom/recommended"], - "rules": { - "jest/consistent-test-it": ["error"], - "jest/expect-expect": "off" - } - }, - { - "files": ["*.html"], - "extends": ["plugin:@nx/angular-template"], - "rules": {} - } - ] -} diff --git a/projects/testing-library/eslint.config.cjs b/projects/testing-library/eslint.config.cjs new file mode 100644 index 0000000..9e951e7 --- /dev/null +++ b/projects/testing-library/eslint.config.cjs @@ -0,0 +1,7 @@ +// @ts-check + +// TODO - https://github.com/nrwl/nx/issues/22576 + +/** @type {import('@typescript-eslint/utils/ts-eslint').FlatConfig.ConfigPromise} */ +const config = (async () => (await import('./eslint.config.mjs')).default)(); +module.exports = config; diff --git a/projects/testing-library/eslint.config.mjs b/projects/testing-library/eslint.config.mjs new file mode 100644 index 0000000..8f627db --- /dev/null +++ b/projects/testing-library/eslint.config.mjs @@ -0,0 +1,8 @@ +// @ts-check + +import tseslint from "typescript-eslint"; +import rootConfig from "../../eslint.config.mjs"; + +export default tseslint.config( + ...rootConfig, +); diff --git a/projects/testing-library/jest.config.ts b/projects/testing-library/jest.config.ts index 189e52f..bc5a665 100644 --- a/projects/testing-library/jest.config.ts +++ b/projects/testing-library/jest.config.ts @@ -1,4 +1,3 @@ -/* eslint-disable */ export default { displayName: { name: 'ATL', diff --git a/projects/testing-library/schematics/migrations/dtl-as-dev-dependency/index.spec.ts b/projects/testing-library/schematics/migrations/dtl-as-dev-dependency/index.spec.ts index a3c0fd1..ebc3922 100644 --- a/projects/testing-library/schematics/migrations/dtl-as-dev-dependency/index.spec.ts +++ b/projects/testing-library/schematics/migrations/dtl-as-dev-dependency/index.spec.ts @@ -16,7 +16,6 @@ test('adds DTL to devDependencies', async () => { }); test('ignores if DTL is already listed as a dev dependency', async () => { - // eslint-disable-next-line @typescript-eslint/naming-convention const tree = await setup({ devDependencies: { '@testing-library/dom': '^9.0.0' } }); const pkg = tree.readContent('package.json'); @@ -24,7 +23,6 @@ test('ignores if DTL is already listed as a dev dependency', async () => { }); test('ignores if DTL is already listed as a dependency', async () => { - // eslint-disable-next-line @typescript-eslint/naming-convention const tree = await setup({ dependencies: { '@testing-library/dom': '^11.0.0' } }); const pkg = tree.readContent('package.json'); diff --git a/projects/testing-library/schematics/ng-add/schema.ts b/projects/testing-library/schematics/ng-add/schema.ts index dc14633..b0dcd22 100644 --- a/projects/testing-library/schematics/ng-add/schema.ts +++ b/projects/testing-library/schematics/ng-add/schema.ts @@ -1,4 +1,3 @@ -// eslint-disable-next-line @typescript-eslint/no-empty-interface export interface Schema { installJestDom: boolean; installUserEvent: boolean; diff --git a/projects/testing-library/src/lib/models.ts b/projects/testing-library/src/lib/models.ts index 0c34aa8..159ac41 100644 --- a/projects/testing-library/src/lib/models.ts +++ b/projects/testing-library/src/lib/models.ts @@ -243,7 +243,7 @@ export interface RenderComponentOptions | { [alias: string]: unknown }; + componentInputs?: Partial | Record; /** * @description @@ -466,7 +466,7 @@ export interface ComponentOverride { providers: any[]; } -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface RenderTemplateOptions extends RenderComponentOptions { /** diff --git a/projects/testing-library/src/lib/testing-library.ts b/projects/testing-library/src/lib/testing-library.ts index 2ddf389..f498a89 100644 --- a/projects/testing-library/src/lib/testing-library.ts +++ b/projects/testing-library/src/lib/testing-library.ts @@ -560,7 +560,7 @@ async function waitForWrapper( let inFakeAsync = true; try { tick(0); - } catch (err) { + } catch { inFakeAsync = false; } diff --git a/projects/testing-library/test-setup.ts b/projects/testing-library/test-setup.ts index 8d79c74..be311bf 100644 --- a/projects/testing-library/test-setup.ts +++ b/projects/testing-library/test-setup.ts @@ -4,5 +4,4 @@ import { TextEncoder, TextDecoder } from 'util'; setupZoneTestEnv(); -// eslint-disable-next-line @typescript-eslint/naming-convention Object.assign(global, { TextDecoder, TextEncoder }); diff --git a/projects/testing-library/tests/debug.spec.ts b/projects/testing-library/tests/debug.spec.ts index e1ad1df..63ab7e6 100644 --- a/projects/testing-library/tests/debug.spec.ts +++ b/projects/testing-library/tests/debug.spec.ts @@ -14,11 +14,11 @@ test('debug', async () => { jest.spyOn(console, 'log').mockImplementation(); const { debug } = await render(FixtureComponent); - // eslint-disable-next-line testing-library/no-debug + // eslint-disable-next-line testing-library/no-debugging-utils debug(); expect(console.log).toHaveBeenCalledWith(expect.stringContaining('rawr')); - (console.log).mockRestore(); + (console.log as any).mockRestore(); }); test('debug allows to be called with an element', async () => { @@ -26,10 +26,10 @@ test('debug allows to be called with an element', async () => { const { debug } = await render(FixtureComponent); const btn = screen.getByTestId('btn'); - // eslint-disable-next-line testing-library/no-debug + // eslint-disable-next-line testing-library/no-debugging-utils debug(btn); expect(console.log).not.toHaveBeenCalledWith(expect.stringContaining('rawr')); expect(console.log).toHaveBeenCalledWith(expect.stringContaining(`I'm a button`)); - (console.log).mockRestore(); + (console.log as any).mockRestore(); }); diff --git a/projects/testing-library/tests/issues/issue-280.spec.ts b/projects/testing-library/tests/issues/issue-280.spec.ts index 5e59534..711cbec 100644 --- a/projects/testing-library/tests/issues/issue-280.spec.ts +++ b/projects/testing-library/tests/issues/issue-280.spec.ts @@ -48,12 +48,12 @@ test('navigate to second page and back', async () => { expect(await screen.findByText('Navigate')).toBeInTheDocument(); expect(await screen.findByText('first page')).toBeInTheDocument(); - userEvent.click(await screen.findByText('go to second')); + await 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')); + await userEvent.click(await screen.findByText('navigate back')); expect(await screen.findByText('first page')).toBeInTheDocument(); }); diff --git a/projects/testing-library/tests/issues/issue-389.spec.ts b/projects/testing-library/tests/issues/issue-389.spec.ts index 03f25f7..626d388 100644 --- a/projects/testing-library/tests/issues/issue-389.spec.ts +++ b/projects/testing-library/tests/issues/issue-389.spec.ts @@ -6,7 +6,6 @@ import { render, screen } from '../../src/public_api'; template: `Hello {{ name }}`, }) class TestComponent { - // eslint-disable-next-line @angular-eslint/no-input-rename @Input('aliasName') name = ''; } diff --git a/projects/testing-library/tests/issues/issue-396-standalone-stub-child.spec.ts b/projects/testing-library/tests/issues/issue-396-standalone-stub-child.spec.ts index 2da43b3..7be9913 100644 --- a/projects/testing-library/tests/issues/issue-396-standalone-stub-child.spec.ts +++ b/projects/testing-library/tests/issues/issue-396-standalone-stub-child.spec.ts @@ -42,7 +42,6 @@ class ChildComponent {} selector: 'atl-child', template: `Hello from stub`, standalone: true, - // eslint-disable-next-line @angular-eslint/no-host-metadata-property, @typescript-eslint/naming-convention host: { 'collision-id': StubComponent.name }, }) class StubComponent {} diff --git a/projects/testing-library/tests/issues/issue-398-component-without-host-id.spec.ts b/projects/testing-library/tests/issues/issue-398-component-without-host-id.spec.ts index 4508d64..c775a2a 100644 --- a/projects/testing-library/tests/issues/issue-398-component-without-host-id.spec.ts +++ b/projects/testing-library/tests/issues/issue-398-component-without-host-id.spec.ts @@ -15,9 +15,7 @@ test('should re-create the app', async () => { selector: 'atl-fixture', standalone: true, template: '

    My title

    ', - // eslint-disable-next-line @angular-eslint/no-host-metadata-property host: { - // eslint-disable-next-line @typescript-eslint/naming-convention '[attr.id]': 'null', // this breaks the cleaning up of tests }, }) diff --git a/projects/testing-library/tests/issues/issue-422-view-already-destroyed.spec.ts b/projects/testing-library/tests/issues/issue-422-view-already-destroyed.spec.ts index 05e6e11..c4fa7a3 100644 --- a/projects/testing-library/tests/issues/issue-422-view-already-destroyed.spec.ts +++ b/projects/testing-library/tests/issues/issue-422-view-already-destroyed.spec.ts @@ -9,6 +9,7 @@ test('declaration specific dependencies should be available for components', asy template: `
    Test
    `, }) class TestComponent { + // eslint-disable-next-line @typescript-eslint/no-empty-function constructor(_elementRef: ElementRef) {} } diff --git a/projects/testing-library/tests/issues/issue-437.spec.ts b/projects/testing-library/tests/issues/issue-437.spec.ts index 2d0e7c5..dbf2506 100644 --- a/projects/testing-library/tests/issues/issue-437.spec.ts +++ b/projects/testing-library/tests/issues/issue-437.spec.ts @@ -24,7 +24,6 @@ test('issue #437', async () => { { imports: [MatSidenavModule] }, ); - // eslint-disable-next-line testing-library/prefer-explicit-assert await screen.findByTestId('test-button'); await user.click(screen.getByTestId('test-button')); @@ -51,7 +50,6 @@ test('issue #437 with fakeTimers', async () => { { imports: [MatSidenavModule] }, ); - // eslint-disable-next-line testing-library/prefer-explicit-assert await screen.findByTestId('test-button'); await user.click(screen.getByTestId('test-button')); diff --git a/projects/testing-library/tests/issues/issue-493.spec.ts b/projects/testing-library/tests/issues/issue-493.spec.ts index a49bc80..5d0e123 100644 --- a/projects/testing-library/tests/issues/issue-493.spec.ts +++ b/projects/testing-library/tests/issues/issue-493.spec.ts @@ -22,6 +22,6 @@ test('succeeds', async () => { }) class DummyComponent { value = input.required(); - // @ts-ignore + // @ts-expect-error http is unused but needed for the test constructor(private http: HttpClient) {} } diff --git a/projects/testing-library/tests/render.spec.ts b/projects/testing-library/tests/render.spec.ts index 52d318c..dc54ac5 100644 --- a/projects/testing-library/tests/render.spec.ts +++ b/projects/testing-library/tests/render.spec.ts @@ -37,7 +37,7 @@ describe('DTL functionality', () => { it('creates queries and events', async () => { const view = await render(FixtureComponent); - /// We wish to test the utility function from `render` here. + // We wish to test the utility function from `render` here. // eslint-disable-next-line testing-library/prefer-screen-queries fireEvent.input(view.getByTestId('input'), { target: { value: 'a super awesome input' } }); // eslint-disable-next-line testing-library/prefer-screen-queries @@ -72,7 +72,6 @@ describe('component with child', () => { @Component({ selector: 'atl-child-fixture', template: `A mock child fixture`, - // eslint-disable-next-line @angular-eslint/no-host-metadata-property, @typescript-eslint/naming-convention host: { 'collision-id': MockChildFixtureComponent.name }, }) class MockChildFixtureComponent {} @@ -287,19 +286,19 @@ describe('on', () => { // eslint-disable-next-line @typescript-eslint/no-empty-function function _test(_on: OutputRefKeysWithCallback) {} - // @ts-expect-error + // @ts-expect-error wrong event type _test({ event: fnWithNumberArg }); _test({ event: fnWithVoidArg }); - // @ts-expect-error + // @ts-expect-error wrong event type _test({ event: fnWithNumberArg }); _test({ event: fnWithMouseEventArg }); - // @ts-expect-error + // @ts-expect-error wrong event type _test({ event: fnWithNumberArg }); _test({ event: fnWithStringArg }); - // @ts-expect-error + // @ts-expect-error wrong event type _test({ event: fnWithNumberArg }); _test({ event: fnWithMouseEventArg }); @@ -392,11 +391,11 @@ describe('Angular component life-cycle hooks', () => { const view = await render(FixtureWithNgOnChangesComponent, { componentProperties }); - /// We wish to test the utility function from `render` here. + // We wish to test the utility function from `render` here. // eslint-disable-next-line testing-library/prefer-screen-queries expect(view.getByText('Sarah')).toBeInTheDocument(); expect(nameChanged).toHaveBeenCalledWith('Sarah', true); - /// expect `nameChanged` to be called before `nameInitialized` + // expect `nameChanged` to be called before `nameInitialized` expect(nameChanged.mock.invocationCallOrder[0]).toBeLessThan(nameInitialized.mock.invocationCallOrder[0]); expect(nameChanged).toHaveBeenCalledTimes(1); }); @@ -408,11 +407,11 @@ describe('Angular component life-cycle hooks', () => { const view = await render(FixtureWithNgOnChangesComponent, { componentInputs: componentInput }); - /// We wish to test the utility function from `render` here. + // We wish to test the utility function from `render` here. // eslint-disable-next-line testing-library/prefer-screen-queries expect(view.getByText('Sarah')).toBeInTheDocument(); expect(nameChanged).toHaveBeenCalledWith('Sarah', true); - /// expect `nameChanged` to be called before `nameInitialized` + // expect `nameChanged` to be called before `nameInitialized` expect(nameChanged.mock.invocationCallOrder[0]).toBeLessThan(nameInitialized.mock.invocationCallOrder[0]); expect(nameChanged).toHaveBeenCalledTimes(1); }); From a18f647f310fa5fd09b09a40bb6a6001f66173f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabien=20Dehopr=C3=A9?= Date: Mon, 13 Jan 2025 18:23:09 +0100 Subject: [PATCH 17/22] fix: do not force npm to install dependencies when using `ng add` (#515) Closes #513 --- projects/testing-library/schematics/ng-add/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/testing-library/schematics/ng-add/index.ts b/projects/testing-library/schematics/ng-add/index.ts index d961e15..868d203 100644 --- a/projects/testing-library/schematics/ng-add/index.ts +++ b/projects/testing-library/schematics/ng-add/index.ts @@ -32,9 +32,9 @@ function addDependency(packageName: string, version: string, dependencyType: Nod }; } -export function installDependencies(packageManager = 'npm') { +export function installDependencies() { return (_tree: Tree, context: SchematicContext) => { - context.addTask(new NodePackageInstallTask({ packageManager })); + context.addTask(new NodePackageInstallTask()); context.logger.info( `Correctly installed @testing-library/angular. From 6efeb36bd8d6bfef53257d0396bd6b01b606b082 Mon Sep 17 00:00:00 2001 From: Tim Deschryver <28659384+timdeschryver@users.noreply.github.com> Date: Mon, 13 Jan 2025 18:24:11 +0100 Subject: [PATCH 18/22] docs: add host directive example (#514) Closes #512 --- .../app/examples/23-host-directive.spec.ts | 22 +++++++++++++++++++ .../src/app/examples/23-host-directive.ts | 21 ++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 apps/example-app/src/app/examples/23-host-directive.spec.ts create mode 100644 apps/example-app/src/app/examples/23-host-directive.ts diff --git a/apps/example-app/src/app/examples/23-host-directive.spec.ts b/apps/example-app/src/app/examples/23-host-directive.spec.ts new file mode 100644 index 0000000..3289299 --- /dev/null +++ b/apps/example-app/src/app/examples/23-host-directive.spec.ts @@ -0,0 +1,22 @@ +import { aliasedInput, render, screen } from '@testing-library/angular'; +import { HostDirectiveComponent } from './23-host-directive'; + +test('can set input properties of host directives using aliasedInput', async () => { + await render(HostDirectiveComponent, { + inputs: { + ...aliasedInput('atlText', 'Hello world'), + }, + }); + + expect(screen.getByText(/hello world/i)).toBeInTheDocument(); +}); + +test('can set input properties of host directives using componentInputs', async () => { + await render(HostDirectiveComponent, { + componentInputs: { + atlText: 'Hello world', + }, + }); + + expect(screen.getByText(/hello world/i)).toBeInTheDocument(); +}); diff --git a/apps/example-app/src/app/examples/23-host-directive.ts b/apps/example-app/src/app/examples/23-host-directive.ts new file mode 100644 index 0000000..3e201c7 --- /dev/null +++ b/apps/example-app/src/app/examples/23-host-directive.ts @@ -0,0 +1,21 @@ +import { Component, Directive, ElementRef, input, OnInit } from '@angular/core'; + +@Directive({ + selector: '[atlText]', +}) +export class TextDirective implements OnInit { + atlText = input(''); + + constructor(private el: ElementRef) {} + + ngOnInit() { + this.el.nativeElement.textContent = this.atlText(); + } +} + +@Component({ + selector: 'atl-host-directive', + template: ``, + hostDirectives: [{ directive: TextDirective, inputs: ['atlText'] }], +}) +export class HostDirectiveComponent {} From 90c43c72962e4353ed7eeb618ed661385b4ef616 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 18:24:40 +0100 Subject: [PATCH 19/22] docs: add FabienDehopre as a contributor for code (#516) * docs: update README.md [skip ci] * docs: update .all-contributorsrc [skip ci] --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ README.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 3b3a470..03c629e 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -419,6 +419,15 @@ "contributions": [ "code" ] + }, + { + "login": "FabienDehopre", + "name": "Fabien Dehopré", + "avatar_url": "https://avatars.githubusercontent.com/u/97023?v=4", + "profile": "https://github.com/FabienDehopre", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/README.md b/README.md index e03ce68..3ac1d10 100644 --- a/README.md +++ b/README.md @@ -273,6 +273,7 @@ Thanks goes to these people ([emoji key][emojis]): Arthur Petrie
    Arthur Petrie

    💻 + Fabien Dehopré
    Fabien Dehopré

    💻 From 31ad7ceba8e79d9c921d6f0800fa6cb7629924f0 Mon Sep 17 00:00:00 2001 From: Tim Deschryver <28659384+timdeschryver@users.noreply.github.com> Date: Fri, 14 Feb 2025 09:06:44 +0100 Subject: [PATCH 20/22] chore: fix devcontainer creation (#518) --- .devcontainer/devcontainer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 72780fc..ac9d248 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,7 +1,7 @@ // For format details, see https://aka.ms/devcontainer.json. { "name": "angular-testing-library", - "image": "mcr.microsoft.com/devcontainers/typescript-node:0-22-bullseye", + "image": "mcr.microsoft.com/devcontainers/typescript-node:22-bullseye", // Features to add to the dev container. More info: https://containers.dev/features. "features": { @@ -13,7 +13,7 @@ // "forwardPorts": [], // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "npm i", + "postCreateCommand": "npm install --force", "onCreateCommand": "sudo cp .devcontainer/welcome-message.txt /usr/local/etc/vscode-dev-containers/first-run-notice.txt", "waitFor": "postCreateCommand", From 3176b3312143a32c693bcce2fc624bdc0c154307 Mon Sep 17 00:00:00 2001 From: Jamie Vereecken <108937550+jvereecken@users.noreply.github.com> Date: Mon, 31 Mar 2025 13:53:43 +0200 Subject: [PATCH 21/22] fix: add @angular/animations as a peer dependency (#522) Closes #519 --- projects/testing-library/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/projects/testing-library/package.json b/projects/testing-library/package.json index 2852d02..0c3abd6 100644 --- a/projects/testing-library/package.json +++ b/projects/testing-library/package.json @@ -29,6 +29,7 @@ "migrations": "./schematics/migrations/migrations.json" }, "peerDependencies": { + "@angular/animations": ">= 17.0.0", "@angular/common": ">= 17.0.0", "@angular/platform-browser": ">= 17.0.0", "@angular/router": ">= 17.0.0", From e1e046c75c297fd8ab06237178d036553b1ff215 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Mon, 31 Mar 2025 14:02:24 +0200 Subject: [PATCH 22/22] docs: add jvereecken as a contributor for code (#523) --- .all-contributorsrc | 9 +++++++++ README.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 03c629e..07a9a93 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -428,6 +428,15 @@ "contributions": [ "code" ] + }, + { + "login": "jvereecken", + "name": "Jamie Vereecken", + "avatar_url": "https://avatars.githubusercontent.com/u/108937550?v=4", + "profile": "https://github.com/jvereecken", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/README.md b/README.md index 3ac1d10..028f721 100644 --- a/README.md +++ b/README.md @@ -274,6 +274,7 @@ Thanks goes to these people ([emoji key][emojis]): Arthur Petrie
    Arthur Petrie

    💻 Fabien Dehopré
    Fabien Dehopré

    💻 + Jamie Vereecken
    Jamie Vereecken

    💻