diff --git a/.eslintignore b/.eslintignore index ae177d5627..7cc5fe61e4 100644 --- a/.eslintignore +++ b/.eslintignore @@ -6,3 +6,4 @@ src/config/setup-jest.ts coverage src/transformers/jit_transform.js index.html +*.mjs diff --git a/.eslintrc.js b/.eslintrc.js index b5e8311f6b..bc0571e7e2 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -9,6 +9,7 @@ const baseJsTsConfig = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.eslint.json', + sourceType: 'module', }, rules: { '@typescript-eslint/no-empty-function': 'error', @@ -71,7 +72,7 @@ module.exports = { overrides: [ { ...baseJsTsConfig, - files: ['*.ts', '*.js'], + files: ['*.ts', '*.js', '*.mts'], extends: [ 'plugin:@angular-eslint/recommended', 'plugin:@angular-eslint/template/process-inline-templates', diff --git a/examples/example-app-monorepo/apps/app1/setup-jest-esm.ts b/examples/example-app-monorepo/apps/app1/setup-jest-esm.ts index f35c45e008..66c0fdead5 100644 --- a/examples/example-app-monorepo/apps/app1/setup-jest-esm.ts +++ b/examples/example-app-monorepo/apps/app1/setup-jest-esm.ts @@ -1,2 +1,4 @@ -import 'jest-preset-angular/setup-jest.mjs'; +import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone/index.mjs'; import './jest-global-mocks'; + +setupZoneTestEnv(); diff --git a/examples/example-app-monorepo/apps/app1/setup-jest.ts b/examples/example-app-monorepo/apps/app1/setup-jest.ts index 9a3535cfee..07158519f6 100644 --- a/examples/example-app-monorepo/apps/app1/setup-jest.ts +++ b/examples/example-app-monorepo/apps/app1/setup-jest.ts @@ -1,2 +1,4 @@ -import 'jest-preset-angular/setup-jest'; +import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone'; import './jest-global-mocks'; + +setupZoneTestEnv(); diff --git a/examples/example-app-monorepo/libs/user/setup-jest-esm.ts b/examples/example-app-monorepo/libs/user/setup-jest-esm.ts index f35c45e008..66c0fdead5 100644 --- a/examples/example-app-monorepo/libs/user/setup-jest-esm.ts +++ b/examples/example-app-monorepo/libs/user/setup-jest-esm.ts @@ -1,2 +1,4 @@ -import 'jest-preset-angular/setup-jest.mjs'; +import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone/index.mjs'; import './jest-global-mocks'; + +setupZoneTestEnv(); diff --git a/examples/example-app-monorepo/libs/user/setup-jest.ts b/examples/example-app-monorepo/libs/user/setup-jest.ts index 9a3535cfee..07158519f6 100644 --- a/examples/example-app-monorepo/libs/user/setup-jest.ts +++ b/examples/example-app-monorepo/libs/user/setup-jest.ts @@ -1,2 +1,4 @@ -import 'jest-preset-angular/setup-jest'; +import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone'; import './jest-global-mocks'; + +setupZoneTestEnv(); diff --git a/examples/example-app-v16/setup-jest-esm.ts b/examples/example-app-v16/setup-jest-esm.ts index f35c45e008..66c0fdead5 100644 --- a/examples/example-app-v16/setup-jest-esm.ts +++ b/examples/example-app-v16/setup-jest-esm.ts @@ -1,2 +1,4 @@ -import 'jest-preset-angular/setup-jest.mjs'; +import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone/index.mjs'; import './jest-global-mocks'; + +setupZoneTestEnv(); diff --git a/examples/example-app-v16/setup-jest.ts b/examples/example-app-v16/setup-jest.ts index 9a3535cfee..07158519f6 100644 --- a/examples/example-app-v16/setup-jest.ts +++ b/examples/example-app-v16/setup-jest.ts @@ -1,2 +1,4 @@ -import 'jest-preset-angular/setup-jest'; +import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone'; import './jest-global-mocks'; + +setupZoneTestEnv(); diff --git a/examples/example-app-v17/setup-jest-esm.ts b/examples/example-app-v17/setup-jest-esm.ts index f35c45e008..66c0fdead5 100644 --- a/examples/example-app-v17/setup-jest-esm.ts +++ b/examples/example-app-v17/setup-jest-esm.ts @@ -1,2 +1,4 @@ -import 'jest-preset-angular/setup-jest.mjs'; +import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone/index.mjs'; import './jest-global-mocks'; + +setupZoneTestEnv(); diff --git a/examples/example-app-v17/setup-jest.ts b/examples/example-app-v17/setup-jest.ts index 9a3535cfee..07158519f6 100644 --- a/examples/example-app-v17/setup-jest.ts +++ b/examples/example-app-v17/setup-jest.ts @@ -1,2 +1,4 @@ -import 'jest-preset-angular/setup-jest'; +import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone'; import './jest-global-mocks'; + +setupZoneTestEnv(); diff --git a/examples/example-app-v18/setup-jest-esm.ts b/examples/example-app-v18/setup-jest-esm.ts index f35c45e008..66c0fdead5 100644 --- a/examples/example-app-v18/setup-jest-esm.ts +++ b/examples/example-app-v18/setup-jest-esm.ts @@ -1,2 +1,4 @@ -import 'jest-preset-angular/setup-jest.mjs'; +import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone/index.mjs'; import './jest-global-mocks'; + +setupZoneTestEnv(); diff --git a/examples/example-app-v18/setup-jest.ts b/examples/example-app-v18/setup-jest.ts index 9a3535cfee..07158519f6 100644 --- a/examples/example-app-v18/setup-jest.ts +++ b/examples/example-app-v18/setup-jest.ts @@ -1,2 +1,4 @@ -import 'jest-preset-angular/setup-jest'; +import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone'; import './jest-global-mocks'; + +setupZoneTestEnv(); diff --git a/examples/example-app-yarn-workspace/packages/angular-app/setup-jest-esm.ts b/examples/example-app-yarn-workspace/packages/angular-app/setup-jest-esm.ts index f35c45e008..66c0fdead5 100644 --- a/examples/example-app-yarn-workspace/packages/angular-app/setup-jest-esm.ts +++ b/examples/example-app-yarn-workspace/packages/angular-app/setup-jest-esm.ts @@ -1,2 +1,4 @@ -import 'jest-preset-angular/setup-jest.mjs'; +import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone/index.mjs'; import './jest-global-mocks'; + +setupZoneTestEnv(); diff --git a/examples/example-app-yarn-workspace/packages/angular-app/setup-jest.ts b/examples/example-app-yarn-workspace/packages/angular-app/setup-jest.ts index 9a3535cfee..07158519f6 100644 --- a/examples/example-app-yarn-workspace/packages/angular-app/setup-jest.ts +++ b/examples/example-app-yarn-workspace/packages/angular-app/setup-jest.ts @@ -1,2 +1,4 @@ -import 'jest-preset-angular/setup-jest'; +import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone'; import './jest-global-mocks'; + +setupZoneTestEnv(); diff --git a/examples/example-app-yarn-workspace/packages/user/setup-jest-esm.ts b/examples/example-app-yarn-workspace/packages/user/setup-jest-esm.ts index f35c45e008..66c0fdead5 100644 --- a/examples/example-app-yarn-workspace/packages/user/setup-jest-esm.ts +++ b/examples/example-app-yarn-workspace/packages/user/setup-jest-esm.ts @@ -1,2 +1,4 @@ -import 'jest-preset-angular/setup-jest.mjs'; +import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone/index.mjs'; import './jest-global-mocks'; + +setupZoneTestEnv(); diff --git a/examples/example-app-yarn-workspace/packages/user/setup-jest.ts b/examples/example-app-yarn-workspace/packages/user/setup-jest.ts index 9a3535cfee..07158519f6 100644 --- a/examples/example-app-yarn-workspace/packages/user/setup-jest.ts +++ b/examples/example-app-yarn-workspace/packages/user/setup-jest.ts @@ -1,2 +1,4 @@ -import 'jest-preset-angular/setup-jest'; +import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone'; import './jest-global-mocks'; + +setupZoneTestEnv(); diff --git a/scripts/test-examples.js b/scripts/test-examples.js index bd34c10b05..ba369a5acb 100755 --- a/scripts/test-examples.js +++ b/scripts/test-examples.js @@ -43,6 +43,7 @@ const executeTest = (projectPath) => { 'package.json', 'setup-jest.js', 'setup-jest.mjs', + 'setup-env', ].forEach((asset) => { const assetToReplace = join(projectPath, 'node_modules', 'jest-preset-angular', asset); const assetToCopy = join(rootDir, asset); diff --git a/setup-env/utils.mjs b/setup-env/utils.mjs new file mode 100644 index 0000000000..eceb631a4e --- /dev/null +++ b/setup-env/utils.mjs @@ -0,0 +1,19 @@ +import { TextDecoder, TextEncoder } from 'util'; + +export const polyfillEncoder = () => { + if (typeof globalThis.TextEncoder === 'undefined') { + globalThis.TextEncoder = TextEncoder; + globalThis.TextDecoder = TextDecoder; + } +}; + +export const resolveTestEnvOptions = (options) => { + const globalTestEnvOptions = globalThis.ngJest?.testEnvironmentOptions; + if (globalTestEnvOptions) { + console.warn( + 'Setting testEnvironmentOptions via globalThis.ngJest is deprecated. Please provide testEnvironmentOptions via function argument', + ); + } + + return globalTestEnvOptions ?? options; +}; diff --git a/setup-env/zone/index.d.mts b/setup-env/zone/index.d.mts new file mode 100644 index 0000000000..276b145cb4 --- /dev/null +++ b/setup-env/zone/index.d.mts @@ -0,0 +1,3 @@ +import type { TestEnvironmentOptions } from '@angular/core/testing'; + +export declare const setupZoneTestEnv: (options?: TestEnvironmentOptions) => void; diff --git a/setup-env/zone/index.d.ts b/setup-env/zone/index.d.ts new file mode 100644 index 0000000000..f7bc10b95a --- /dev/null +++ b/setup-env/zone/index.d.ts @@ -0,0 +1,6 @@ +import type { TestEnvironmentOptions } from '@angular/core/testing'; + +declare const _default: { + setupZoneTestEnv: (options?: TestEnvironmentOptions) => void; +}; +export = _default; diff --git a/setup-env/zone/index.js b/setup-env/zone/index.js new file mode 100644 index 0000000000..b2b2d2a17a --- /dev/null +++ b/setup-env/zone/index.js @@ -0,0 +1,43 @@ +require('zone.js'); +require('zone.js/testing'); + +const { TextEncoder, TextDecoder } = require('util'); + +const { getTestBed } = require('@angular/core/testing'); +const { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting, +} = require('@angular/platform-browser-dynamic/testing'); + +const polyfillEncoder = () => { + if (typeof globalThis.TextEncoder === 'undefined') { + globalThis.TextEncoder = TextEncoder; + globalThis.TextDecoder = TextDecoder; + } +}; + +const resolveTestEnvOptions = (options) => { + const globalTestEnvOptions = globalThis.ngJest?.testEnvironmentOptions; + if (globalTestEnvOptions) { + console.warn( + 'Setting testEnvironmentOptions via globalThis.ngJest is deprecated. Please provide testEnvironmentOptions via function argument', + ); + } + + return globalTestEnvOptions ?? options; +}; + +const setupZoneTestEnv = (options) => { + polyfillEncoder(); + const testEnvironmentOptions = resolveTestEnvOptions(options); + + getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting(), + testEnvironmentOptions, + ); +}; + +module.exports = { + setupZoneTestEnv, +}; diff --git a/setup-env/zone/index.mjs b/setup-env/zone/index.mjs new file mode 100644 index 0000000000..fe0d2a993c --- /dev/null +++ b/setup-env/zone/index.mjs @@ -0,0 +1,18 @@ +import 'zone.js'; +import 'zone.js/testing'; + +import { getTestBed } from '@angular/core/testing'; +import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; + +import { polyfillEncoder, resolveTestEnvOptions } from '../utils.mjs'; + +export const setupZoneTestEnv = (options) => { + polyfillEncoder(); + const testEnvironmentOptions = resolveTestEnvOptions(options); + + getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting(), + testEnvironmentOptions, + ); +}; diff --git a/setup-env/zoneless/index.d.mts b/setup-env/zoneless/index.d.mts new file mode 100644 index 0000000000..f01ad512c0 --- /dev/null +++ b/setup-env/zoneless/index.d.mts @@ -0,0 +1,3 @@ +import type { TestEnvironmentOptions } from '@angular/core/testing'; + +export declare const setupZonelessTestEnv: (options?: TestEnvironmentOptions) => void; diff --git a/setup-env/zoneless/index.d.ts b/setup-env/zoneless/index.d.ts new file mode 100644 index 0000000000..147c1685da --- /dev/null +++ b/setup-env/zoneless/index.d.ts @@ -0,0 +1,6 @@ +import type { TestEnvironmentOptions } from '@angular/core/testing'; + +declare const _default: { + setupZonelessTestEnv: (options?: TestEnvironmentOptions) => void; +}; +export = _default; diff --git a/setup-env/zoneless/index.js b/setup-env/zoneless/index.js new file mode 100644 index 0000000000..b725a157ad --- /dev/null +++ b/setup-env/zoneless/index.js @@ -0,0 +1,11 @@ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const setupZonelessTestEnv = (_options) => { + throw Error( + 'Zoneless testing environment only works when running Jest in ESM mode with Jest 29. ' + + 'Jest 30+ will support to work with CommonJS mode.', + ); +}; + +module.exports = { + setupZonelessTestEnv, +}; diff --git a/setup-env/zoneless/index.mjs b/setup-env/zoneless/index.mjs new file mode 100644 index 0000000000..b63cd62e6e --- /dev/null +++ b/setup-env/zoneless/index.mjs @@ -0,0 +1,39 @@ +import { ErrorHandler, NgModule, provideExperimentalZonelessChangeDetection } from '@angular/core'; +import { getTestBed } from '@angular/core/testing'; +import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; + +import { polyfillEncoder, resolveTestEnvOptions } from '../utils.mjs'; + +export const setupZonelessTestEnv = (options) => { + if (typeof provideExperimentalZonelessChangeDetection !== 'undefined') { + polyfillEncoder(); + const testEnvironmentOptions = resolveTestEnvOptions(options); + @NgModule({ + providers: [ + provideExperimentalZonelessChangeDetection(), + { + provide: ErrorHandler, + useValue: { + handleError: (e) => { + throw e; + }, + }, + }, + ], + }) + export class TestModule {} + + getTestBed().initTestEnvironment( + [BrowserDynamicTestingModule, TestModule], + platformBrowserDynamicTesting(), + testEnvironmentOptions, + ); + + return; + } + + throw Error( + 'Cannot find provideExperimentalZonelessChangeDetection() to setup zoneless testing environment. ' + + 'Please use setupZoneTestEnv() from jest-preset-angular/setup-env/setup-zone-env.mjs instead.', + ); +}; diff --git a/src/config/setup-jest.spec.ts b/src/config/setup-jest.spec.ts index 18f1811dee..1de0e5bd21 100644 --- a/src/config/setup-jest.spec.ts +++ b/src/config/setup-jest.spec.ts @@ -85,6 +85,39 @@ describe('setup-jest', () => { expect(globalThis.TextEncoder).toBeDefined(); }); + + it('should call getTestBed() and initTestEnvironment() with the testEnvironmentOptions passed as argument with setupZoneTestEnv()', async () => { + const { setupZoneTestEnv } = await import('../../setup-env/zone/index.js'); + + setupZoneTestEnv({ + teardown: { + destroyAfterEach: false, + rethrowErrors: true, + }, + errorOnUnknownElements: true, + errorOnUnknownProperties: true, + }); + + expect(mockZoneJs).toHaveBeenCalled(); + expect(mockZoneJsTesting).toHaveBeenCalled(); + assertOnInitTestEnv(); + expect(mockInitTestEnvironment.mock.calls[0][2]).toEqual({ + teardown: { + destroyAfterEach: false, + rethrowErrors: true, + }, + errorOnUnknownElements: true, + errorOnUnknownProperties: true, + }); + }); + + it('should always have TextEncoder in globalThis with setupZoneTestEnv()', async () => { + const { setupZoneTestEnv } = await import('../../setup-env/zone/index.js'); + + setupZoneTestEnv(); + + expect(globalThis.TextEncoder).toBeDefined(); + }); }); describe('for ESM setup-jest, test environment initialization', () => { @@ -120,5 +153,38 @@ describe('setup-jest', () => { expect(globalThis.TextEncoder).toBeDefined(); }); + + it('should call getTestBed() and initTestEnvironment() with the testEnvironmentOptions passed as argument with setupZoneTestEnv()', async () => { + const { setupZoneTestEnv } = await import('../../setup-env/zone/index.mjs'); + + setupZoneTestEnv({ + teardown: { + destroyAfterEach: false, + rethrowErrors: true, + }, + errorOnUnknownElements: true, + errorOnUnknownProperties: true, + }); + + expect(mockZoneJs).toHaveBeenCalled(); + expect(mockZoneJsTesting).toHaveBeenCalled(); + assertOnInitTestEnv(); + expect(mockInitTestEnvironment.mock.calls[0][2]).toEqual({ + teardown: { + destroyAfterEach: false, + rethrowErrors: true, + }, + errorOnUnknownElements: true, + errorOnUnknownProperties: true, + }); + }); + + it('should always have TextEncoder in globalThis with setupZoneTestEnv()', async () => { + const { setupZoneTestEnv } = await import('../../setup-env/zone/index.mjs'); + + setupZoneTestEnv(); + + expect(globalThis.TextEncoder).toBeDefined(); + }); }); }); diff --git a/website/docs/getting-started/installation.md b/website/docs/getting-started/installation.md index 53681eaafe..99f03531ad 100644 --- a/website/docs/getting-started/installation.md +++ b/website/docs/getting-started/installation.md @@ -3,6 +3,9 @@ id: installation title: Installation --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + ### Dependencies You can install `jest-preset-angular` and dependencies all at once with one of the following commands. @@ -19,27 +22,38 @@ Angular doesn't support native `async/await` in testing with `target` higher tha ::: -In your project root, create `setup-jest.ts` file with following contents: +In your project root, create a setup file with following contents: -```ts +```ts tab={"label":"TypeScript CJS"} // setup-jest.ts -import 'jest-preset-angular/setup-jest'; +import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone'; + +setupZoneTestEnv(); ``` -Add the following section: +```ts tab={"label":"TypeScript ESM"} +// setup-jest.ts +import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone.mjs'; + +setupZoneTestEnv(); +``` -- to your root Jest config +Add the following section to your root Jest config -```js tab -// jest.config.js -module.exports = { +```ts tab={"label":"TypeScript CJS"} +// jest.config.ts +import type { Config } from 'jest'; + +const jestConfig: Config = { preset: 'jest-preset-angular', setupFilesAfterEnv: ['/setup-jest.ts'], }; + +export default jestConfig; ``` -```ts tab -// jest.config.ts +```ts tab={"label":"TypeScript ESM"} +// jest.config.mts import type { Config } from 'jest'; const jestConfig: Config = { @@ -52,26 +66,47 @@ export default jestConfig; Adjust your `tsconfig.spec.json` to be: -```json +```json5 tab={"label": "Tsconfig CJS"} +// tsconfig.spec.json { + //... + extends: './tsconfig.json', + compilerOptions: { + //... + module: 'CommonJS', + types: ['jest'], + }, + include: ['src/**/*.spec.ts', 'src/**/*.d.ts'], + //... +} +``` + +```json tab={"label": "Tsconfig ESM"} +// tsconfig.spec.json +{ + //... "extends": "./tsconfig.json", "compilerOptions": { - "outDir": "./out-tsc/spec", - "module": "CommonJs", + //... + "module": "ES2022", "types": ["jest"] }, "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] + //... } ``` Adjust `scripts` part your `package.json` to use `jest` instead of `ng`, e.g. ```json +// package.json { + //... "scripts": { "test": "jest", "test:watch": "jest --watch" } + //... } ``` @@ -85,17 +120,28 @@ simulate the behaviors of real browsers in `JSDOM`. To add global mocks, you can - Create a file `jest-global-mocks.ts` to your root project. - Import it in your global setup file: -```ts -// Assuming that your global setup file is setup-jest.ts -import 'jest-preset-angular/setup-jest'; +```ts tab={"label":"TypeScript CJS"} +// setup-jest.ts +import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone'; +import './jest-global-mocks'; + +setupZoneTestEnv(); +``` + +```ts tab={"label":"TypeScript ESM"} +// setup-jest.ts +import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone.mjs'; import './jest-global-mocks'; + +setupZoneTestEnv(); ``` :::tip An example for `jest-global-mocks.ts` -``` +```ts +// jest-global-mocks.ts Object.defineProperty(document, 'doctype', { value: '', }); diff --git a/website/docs/getting-started/test-environment.md b/website/docs/getting-started/test-environment.md index 610c147f9d..93f89960cb 100644 --- a/website/docs/getting-started/test-environment.md +++ b/website/docs/getting-started/test-environment.md @@ -3,32 +3,120 @@ id: test-environment title: Test environment --- -If you look at [`setup-jest.js`](https://github.com/thymikee/jest-preset-angular/blob/main/setup-jest.js), -what we're doing here is we're adding globals required by Angular. With the included [Angular zone patch](https://github.com/angular/angular/tree/main/packages/zone.js) -we also make sure Jest test methods run in Zone context. Then we initialize the Angular testing environment like normal. - -While `setup-jest.js` above is for running Jest with **CommonJS** mode, we also provide [`setup-jest.mjs`](https://github.com/thymikee/jest-preset-angular/blob/main/setup-jest.mjs) -to run with **ESM** mode. - -### Configure test environment - -When creating Angular test environment with `TestBed`, it is possible to specify the `testEnvironmentOptions` via `globalThis` in the Jest setup file. -For example: - -```ts -// setup-test.ts -globalThis.ngJest = { - testEnvironmentOptions: { - teardown: { - destroyAfterEach: false, - rethrowErrors: true, - }, - errorOnUnknownElements: true, - errorOnUnknownProperties: true, - }, +In Jest, a test environment defines the sandbox context in which your tests run. +For Angular projects, setting up the correct test environment is essential to ensure compatibility with the +framework-specific features, such as dependency injection and change detection. + +`jest-preset-angular` provides utility functions to simplify setting up a Jest test environment tailored for Angular projects. +These functions support both **zone-based** and **zoneless** environments, catering to different testing needs. + +## Methods + +import TOCInline from '@theme/TOCInline'; + + + +--- + +### `setupZoneTestEnv(options)` + +Configures a test environment that uses `zone.js`, which is the mechanism for tracking asynchronous operations. +It is suitable for most Angular applications that rely on `zone.js` for change detection and other framework features. + +You can customize the environment by providing options as function arguments. + +#### Parameters + +- `options`**(optional)**: An object follows [TestEnvironmentOptions interface](https://github.com/angular/angular/blob/a55341b1ab8d2bc4285a4cce59df7fc0b23c0125/packages/core/testing/src/test_bed_common.ts#L95), which allows fine-tuning the environment. + +#### Example: + +- Create a Jest setup file: + +```ts tab={"label": "TypeScript CJS"} +// setup-jest.ts +import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone'; + +setupZoneTestEnv({ + //...options +}); +``` + +```ts tab={"label": "TypeScript ESM"} +// setup-jest.ts +import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone.mjs'; + +setupZoneTestEnv({ + //...options +}); +``` + +- Update your Jest configuration: + +```ts tab={"label": "TypeScript CJS"} +// jest.config.ts +import type { Config } from 'jest'; + +const jestConfig: Config = { + preset: 'jest-preset-angular', + setupFilesAfterEnv: ['/setup-jest.ts'], }; -import 'jest-preset-angular/setup-jest'; +export default jestConfig; ``` -`jest-preset-angular` will look at `globalThis.ngJest` and pass the correct [`TestEnvironmentOptions`](https://angular.io/api/core/testing/TestEnvironmentOptions) object to `TestBed`. +```ts tab={"label": "TypeScript ESM"} +// jest.config.mts +import type { Config } from 'jest'; + +const jestConfig: Config = { + preset: 'jest-preset-angular', + setupFilesAfterEnv: ['/setup-jest.ts'], +}; + +export default jestConfig; +``` + +### `setupZonelessTestEnv(options)` + +Configures a test environment that **DOESN'T** use `zone.js`, as described in [Angular experimental zoneless guide](https://angular.dev/guide/experimental/zoneless). +It is designed for projects that have disabled `zone.js`, which can lead to improved performance and simplified testing. + +:::important + +This function is only supported in Jest `ESM` mode in [Jest 29](https://github.com/jestjs/jest/issues/10962). Jest 30+ will support to use for `CommonJS` mode. + +::: + +You can customize the environment by providing options as function arguments. + +#### Parameters + +- `options`**(optional)**: An object follows [TestEnvironmentOptions interface](https://github.com/angular/angular/blob/a55341b1ab8d2bc4285a4cce59df7fc0b23c0125/packages/core/testing/src/test_bed_common.ts#L95), which allows fine-tuning the environment. + +#### Example: + +- Create a Jest setup file: + +```ts tab={"label": "TypeScript ESM"} +// setup-jest.ts +import { setupZonelessTestEnv } from 'jest-preset-angular/setup-env/zone.mjs'; + +setupZonelessTestEnv({ + //...options +}); +``` + +- Update your Jest configuration: + +```ts tab={"label": "TypeScript ESM"} +// jest.config.mts +import type { Config } from 'jest'; + +const jestConfig: Config = { + preset: 'jest-preset-angular', + setupFilesAfterEnv: ['/setup-jest.ts'], +}; + +export default jestConfig; +``` diff --git a/website/docs/guides/troubleshooting.md b/website/docs/guides/troubleshooting.md index 67cf67c5f3..f03e5c6fc2 100644 --- a/website/docs/guides/troubleshooting.md +++ b/website/docs/guides/troubleshooting.md @@ -18,7 +18,7 @@ With Angular 8 and higher, a [change to the way the Angular CLI works](https://g "emitDecoratorMetadata": true ``` -In general, this is related to Angular's reflection and also depends on a reflection library, as e. g. included in `core-js`. We use our own minimal reflection that satisfy Angular's current requirements, but in case these change, you can install `core-js` and import the reflection library in your `setup-jest.ts`: +In general, this is related to `Angular`'s reflection and also depends on a reflection library, as e. g. included in `core-js`. We use our own minimal reflection that satisfy `Angular`'s current requirements, but in case these change, you can install `core-js` and import the reflection library in your `setup-jest.ts`: ```ts // setup-jest.ts