diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 78880eb..28aac0d 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]' || '[16, 18, 20]') }}
+ node-version: ${{ fromJSON((github.ref == 'refs/heads/main' || github.ref == 'refs/heads/beta') && '[20]' || '[18, 20]') }}
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/README.md b/README.md
index 569dff2..eed18ab 100644
--- a/README.md
+++ b/README.md
@@ -98,7 +98,7 @@ counter.component.ts
```ts
@Component({
- selector: 'counter',
+ selector: 'app-counter',
template: `
Current Count: {{ counter }}
@@ -122,7 +122,7 @@ counter.component.spec.ts
```typescript
import { render, screen, fireEvent } from '@testing-library/angular';
-import { CounterComponent } from './counter.component.ts';
+import { CounterComponent } from './counter.component';
describe('Counter', () => {
test('should render counter', async () => {
@@ -134,7 +134,7 @@ describe('Counter', () => {
test('should increment the counter on click', async () => {
await render(CounterComponent, { componentProperties: { counter: 5 } });
- const incrementButton = screen.getByRole('button', { name: /increment/i });
+ const incrementButton = screen.getByRole('button', { name: '+' });
fireEvent.click(incrementButton);
expect(screen.getByText('Current Count: 6'));
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 07a1e1d..9c51065 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
@@ -51,16 +51,7 @@ class LoginComponent {
});
constructor(private fb: FormBuilder) {}
-
- blurred() {
- console.log('aaaaaaaaac');
- console.log('aaaaaaaaac');
- console.log('aaaaaaaaac');
- console.log('aaaaaaaaac');
- console.log('aaaaaaaaac');
- console.log('aaaaaaaaac');
- console.log('aaaaaaaaac');
- }
+
get email(): FormControl {
return this.form.get('email') as FormControl;
}
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
new file mode 100644
index 0000000..6a55e61
--- /dev/null
+++ b/apps/example-app/src/app/examples/22-signal-inputs.component.spec.ts
@@ -0,0 +1,91 @@
+import { render, screen } from '@testing-library/angular';
+import { SignalInputComponent } from './22-signal-inputs.component';
+import userEvent from '@testing-library/user-event';
+
+test('works with signal inputs', async () => {
+ await render(SignalInputComponent, {
+ componentInputs: {
+ greeting: 'Hello',
+ name: 'world',
+ },
+ });
+
+ expect(screen.getByText(/hello world/i)).toBeInTheDocument();
+});
+
+test('can update signal inputs', async () => {
+ const { fixture } = await render(SignalInputComponent, {
+ componentInputs: {
+ greeting: 'Hello',
+ name: 'world',
+ },
+ });
+
+ expect(screen.getByText(/hello world/i)).toBeInTheDocument();
+
+ fixture.componentInstance.name.set('updated');
+ // set doesn't trigger change detection within the test, findBy is needed to update the template
+ expect(await screen.findByText(/hello updated/i)).toBeInTheDocument();
+ // it's not recommended to access the model directly, but it's possible
+ expect(fixture.componentInstance.name()).toBe('updated');
+});
+
+test('output emits a value', async () => {
+ const submitFn = jest.fn();
+ await render(SignalInputComponent, {
+ componentInputs: {
+ greeting: 'Hello',
+ name: 'world',
+ },
+ componentOutputs: {
+ submit: { emit: submitFn } as any,
+ },
+ });
+
+ await userEvent.click(screen.getByRole('button'));
+
+ expect(submitFn).toHaveBeenCalledWith('world');
+});
+
+test('model update also updates the template', async () => {
+ const { fixture } = await render(SignalInputComponent, {
+ componentInputs: {
+ greeting: 'Hello',
+ name: 'initial',
+ },
+ });
+
+ expect(screen.getByText(/hello initial/i)).toBeInTheDocument();
+
+ await userEvent.clear(screen.getByRole('textbox'));
+ await userEvent.type(screen.getByRole('textbox'), 'updated');
+
+ expect(screen.getByText(/hello updated/i)).toBeInTheDocument();
+ expect(fixture.componentInstance.name()).toBe('updated');
+
+ fixture.componentInstance.name.set('new value');
+ // set doesn't trigger change detection within the test, findBy is needed to update the template
+ expect(await screen.findByText(/hello new value/i)).toBeInTheDocument();
+ // it's not recommended to access the model directly, but it's possible
+ expect(fixture.componentInstance.name()).toBe('new value');
+});
+
+test('works with signal inputs and rerenders', async () => {
+ const view = await render(SignalInputComponent, {
+ componentInputs: {
+ greeting: 'Hello',
+ name: 'world',
+ },
+ });
+
+ expect(screen.getByText(/hello world/i)).toBeInTheDocument();
+
+ await view.rerender({
+ componentInputs: {
+ greeting: 'bye',
+ name: 'test',
+ },
+ });
+
+ expect(screen.getByText(/bye test/i)).toBeInTheDocument();
+});
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
new file mode 100644
index 0000000..ae1e779
--- /dev/null
+++ b/apps/example-app/src/app/examples/22-signal-inputs.component.ts
@@ -0,0 +1,24 @@
+import { Component, input, model, output } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+
+@Component({
+ selector: 'app-signal-input',
+ template: `
+
{{ greetings() }} {{ name() }}
+
+
+ `,
+ standalone: true,
+ imports: [FormsModule],
+})
+export class SignalInputComponent {
+ greetings = input('', {
+ alias: 'greeting',
+ });
+ name = model.required();
+ submit = output();
+
+ submitName() {
+ this.submit.emit(this.name());
+ }
+}
diff --git a/package.json b/package.json
index 4950026..6c96abe 100644
--- a/package.json
+++ b/package.json
@@ -28,41 +28,42 @@
"prepare": "git config core.hookspath .githooks"
},
"dependencies": {
- "@angular/animations": "17.1.0",
- "@angular/cdk": "17.1.0",
- "@angular/common": "17.1.0",
- "@angular/compiler": "17.1.0",
- "@angular/core": "17.1.0",
- "@angular/material": "17.1.0",
- "@angular/platform-browser": "17.1.0",
- "@angular/platform-browser-dynamic": "17.1.0",
- "@angular/router": "17.1.0",
+ "@angular/animations": "17.3.2",
+ "@angular/cdk": "17.3.2",
+ "@angular/common": "17.3.2",
+ "@angular/compiler": "17.3.2",
+ "@angular/core": "17.3.2",
+ "@angular/material": "17.3.2",
+ "@angular/platform-browser": "17.3.2",
+ "@angular/platform-browser-dynamic": "17.3.2",
+ "@angular/router": "17.3.2",
"@ngrx/store": "17.1.0",
"@nx/angular": "17.2.8",
- "@testing-library/dom": "^9.0.0",
+ "@testing-library/dom": "^10.0.0",
"rxjs": "7.8.0",
"tslib": "~2.3.1",
"zone.js": "0.14.2"
},
"devDependencies": {
- "@angular-devkit/build-angular": "17.1.0",
- "@angular-devkit/core": "17.1.0",
- "@angular-devkit/schematics": "17.1.0",
+ "@angular-devkit/build-angular": "17.3.2",
+ "@angular-devkit/core": "17.3.2",
+ "@angular-devkit/schematics": "17.3.2",
"@angular-eslint/builder": "17.0.1",
"@angular-eslint/eslint-plugin": "17.0.1",
"@angular-eslint/eslint-plugin-template": "17.0.1",
"@angular-eslint/schematics": "17.0.1",
"@angular-eslint/template-parser": "17.0.1",
- "@angular/cli": "~17.1.0",
- "@angular/compiler-cli": "17.1.0",
- "@angular/forms": "17.1.0",
- "@angular/language-service": "17.1.0",
+ "@angular/cli": "~17.3.2",
+ "@angular/compiler-cli": "17.3.2",
+ "@angular/forms": "17.3.2",
+ "@angular/language-service": "17.3.2",
+ "@nx/eslint": "17.2.8",
"@nx/eslint-plugin": "17.2.8",
"@nx/jest": "17.2.8",
"@nx/node": "17.2.8",
"@nx/plugin": "17.2.8",
"@nx/workspace": "17.2.8",
- "@schematics/angular": "17.1.0",
+ "@schematics/angular": "17.3.2",
"@testing-library/jasmine-dom": "^1.2.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/user-event": "^14.4.3",
@@ -77,13 +78,14 @@
"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",
"jasmine-core": "4.2.0",
"jasmine-spec-reporter": "7.0.0",
- "jest": "29.5.0",
+ "jest": "29.7.0",
"jest-environment-jsdom": "29.5.0",
- "jest-preset-angular": "13.1.6",
+ "jest-preset-angular": "14.0.3",
"karma": "6.4.0",
"karma-chrome-launcher": "^3.1.0",
"karma-coverage": "^2.2.1",
@@ -91,7 +93,7 @@
"karma-jasmine-html-reporter": "2.0.0",
"lint-staged": "^12.1.6",
"ng-mocks": "^14.11.0",
- "ng-packagr": "17.1.0",
+ "ng-packagr": "17.3.0",
"nx": "17.2.8",
"postcss": "^8.4.5",
"postcss-import": "14.1.0",
@@ -102,7 +104,6 @@
"semantic-release": "^18.0.0",
"ts-jest": "29.1.0",
"ts-node": "10.9.1",
- "typescript": "5.2.2",
- "@nx/eslint": "17.2.8"
+ "typescript": "5.2.2"
}
}
diff --git a/projects/testing-library/package.json b/projects/testing-library/package.json
index 7ebb6e7..fd3cfac 100644
--- a/projects/testing-library/package.json
+++ b/projects/testing-library/package.json
@@ -35,7 +35,7 @@
"@angular/core": ">= 17.0.0"
},
"dependencies": {
- "@testing-library/dom": "^9.0.0",
+ "@testing-library/dom": "^10.0.0",
"tslib": "^2.3.1"
},
"publishConfig": {
diff --git a/projects/testing-library/src/lib/testing-library.ts b/projects/testing-library/src/lib/testing-library.ts
index 2b3ac75..9b57b50 100644
--- a/projects/testing-library/src/lib/testing-library.ts
+++ b/projects/testing-library/src/lib/testing-library.ts
@@ -9,11 +9,11 @@ import {
SimpleChanges,
Type,
} from '@angular/core';
-import {ComponentFixture, DeferBlockState, TestBed, tick} from '@angular/core/testing';
-import {BrowserAnimationsModule, NoopAnimationsModule} from '@angular/platform-browser/animations';
-import {NavigationExtras, Router} from '@angular/router';
-import {RouterTestingModule} from '@angular/router/testing';
-import type {BoundFunctions, Queries} from '@testing-library/dom';
+import { ComponentFixture, DeferBlockBehavior, DeferBlockState, TestBed, tick } from '@angular/core/testing';
+import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { NavigationExtras, Router } from '@angular/router';
+import { RouterTestingModule } from '@angular/router/testing';
+import type { BoundFunctions, Queries } from '@testing-library/dom';
import {
configure as dtlConfigure,
getQueriesForElement as dtlGetQueriesForElement,
@@ -25,8 +25,8 @@ import {
waitForOptions as dtlWaitForOptions,
within as dtlWithin,
} from '@testing-library/dom';
-import {ComponentOverride, RenderComponentOptions, RenderResult, RenderTemplateOptions} from './models';
-import {getConfig} from './config';
+import { ComponentOverride, RenderComponentOptions, RenderResult, RenderTemplateOptions } from './models';
+import { getConfig } from './config';
const mountedFixtures = new Set>();
const safeInject = TestBed.inject || TestBed.get;
@@ -95,7 +95,7 @@ export async function render(
}),
providers: [...providers],
schemas: [...schemas],
- deferBlockBehavior: deferBlockBehavior as any
+ deferBlockBehavior: deferBlockBehavior ?? DeferBlockBehavior.Manual,
});
overrideComponentImports(sut, componentImports);
overrideChildComponentProviders(childComponentOverrides);
diff --git a/projects/testing-library/tests/defer-blocks.spec.ts b/projects/testing-library/tests/defer-blocks.spec.ts
index 147c5c7..7405a4d 100644
--- a/projects/testing-library/tests/defer-blocks.spec.ts
+++ b/projects/testing-library/tests/defer-blocks.spec.ts
@@ -1,7 +1,6 @@
-import {Component} from '@angular/core';
-import {DeferBlockBehavior, DeferBlockState} from '@angular/core/testing';
-import {render, screen} from '../src/public_api';
-import {fireEvent} from "@testing-library/dom";
+import { Component } from '@angular/core';
+import { DeferBlockBehavior, DeferBlockState } from '@angular/core/testing';
+import { render, screen, fireEvent } from '../src/public_api';
test('renders a defer block in different states using the official API', async () => {
const { fixture } = await render(FixtureComponent);
@@ -30,8 +29,8 @@ test('renders a defer block in different states using ATL', async () => {
});
test('renders a defer block in different states using DeferBlockBehavior.Playthrough', async () => {
- await render(FixtureComponent, {
- deferBlockBehavior: DeferBlockBehavior.Playthrough
+ await render(FixtureComponent, {
+ deferBlockBehavior: DeferBlockBehavior.Playthrough,
});
expect(await screen.findByText(/loading/i)).toBeInTheDocument();
@@ -39,12 +38,12 @@ test('renders a defer block in different states using DeferBlockBehavior.Playthr
});
test('renders a defer block in different states using DeferBlockBehavior.Playthrough event', async () => {
- await render(FixtureComponentWithEvents, {
- deferBlockBehavior: DeferBlockBehavior.Playthrough
+ await render(FixtureComponentWithEventsComponent, {
+ deferBlockBehavior: DeferBlockBehavior.Playthrough,
});
- const button = screen.getByRole('button', {name: /click/i});
- fireEvent.click(button)
+ const button = screen.getByRole('button', { name: /click/i });
+ fireEvent.click(button);
expect(screen.getByText(/empty defer block/i)).toBeInTheDocument();
});
@@ -91,9 +90,8 @@ class FixtureComponent {}
template: `
@defer(on interaction(trigger)) {
- empty defer block
+ empty defer block
}
`,
})
-class FixtureComponentWithEvents {}
-
+class FixtureComponentWithEventsComponent {}
diff --git a/projects/testing-library/tests/issues/issue-435.spec.ts b/projects/testing-library/tests/issues/issue-435.spec.ts
new file mode 100644
index 0000000..e1e420f
--- /dev/null
+++ b/projects/testing-library/tests/issues/issue-435.spec.ts
@@ -0,0 +1,37 @@
+import { CommonModule } from '@angular/common';
+import { BehaviorSubject } from 'rxjs';
+import { Component, Inject, Injectable } from '@angular/core';
+import { screen, render } from '../../src/public_api';
+
+// Service
+@Injectable()
+class DemoService {
+ buttonTitle = new BehaviorSubject('Click me');
+}
+
+// Component
+@Component({
+ selector: 'atl-issue-435',
+ standalone: true,
+ imports: [CommonModule],
+ providers: [DemoService],
+ template: `
+
+ `,
+})
+class DemoComponent {
+ constructor(@Inject(DemoService) public demoService: DemoService) {}
+}
+
+test('issue #435', async () => {
+ await render(DemoComponent);
+
+ const button = screen.getByRole('button', {
+ name: /Click me/,
+ });
+
+ expect(button).toBeVisible();
+});
diff --git a/projects/testing-library/tests/issues/issue-437.spec.ts b/projects/testing-library/tests/issues/issue-437.spec.ts
new file mode 100644
index 0000000..2d0e7c5
--- /dev/null
+++ b/projects/testing-library/tests/issues/issue-437.spec.ts
@@ -0,0 +1,58 @@
+import userEvent from '@testing-library/user-event';
+import { screen, render } from '../../src/public_api';
+import { MatSidenavModule } from '@angular/material/sidenav';
+
+afterEach(() => {
+ jest.useRealTimers();
+});
+
+test('issue #437', async () => {
+ const user = userEvent.setup();
+ await render(
+ `
+
+
+
+
+
+
+
+
+
+
+ `,
+ { imports: [MatSidenavModule] },
+ );
+
+ // eslint-disable-next-line testing-library/prefer-explicit-assert
+ await screen.findByTestId('test-button');
+
+ await user.click(screen.getByTestId('test-button'));
+});
+
+test('issue #437 with fakeTimers', async () => {
+ jest.useFakeTimers();
+ const user = userEvent.setup({
+ advanceTimers: jest.advanceTimersByTime,
+ });
+ await render(
+ `
+
+
+
+
+
+
+
+
+
+
+ `,
+ { imports: [MatSidenavModule] },
+ );
+
+ // eslint-disable-next-line testing-library/prefer-explicit-assert
+ await screen.findByTestId('test-button');
+
+ await user.click(screen.getByTestId('test-button'));
+});