Skip to content

Commit

Permalink
feat: support configuring zoneless test env for CJS
Browse files Browse the repository at this point in the history
  • Loading branch information
ahnpnl committed Nov 22, 2024
1 parent 76b98e3 commit fe2fbe9
Show file tree
Hide file tree
Showing 23 changed files with 365 additions and 113 deletions.
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ tab_width = 4
[*.{yml, yaml}]
indent_size = 2

[*.{js,jsx,ts,tsx}]
[*.{js,jsx,ts,tsx,mjs,mjsx,tsx,mtsx}]
indent_size = 4

[*.html]
Expand Down
18 changes: 18 additions & 0 deletions e2e/zoneless-env/__tests__/zoneless-env.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { TestBed } from '@angular/core/testing';

import { FooComponent } from '../foo.component';

describe('FooComponent', () => {
it('should trigger change detection without fixture.detectChanges', () => {
TestBed.configureTestingModule({
imports: [FooComponent],
});
const fixture = TestBed.createComponent(FooComponent);

expect(fixture.componentInstance.value1()).toBe('val1');

fixture.componentRef.setInput('value1', 'hello');

expect(fixture.componentInstance.value1()).toBe('hello');
});
});
10 changes: 10 additions & 0 deletions e2e/zoneless-env/foo.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!-- SOMETHING -->
<p>Line 1</p>
<div>
<div *ngIf="condition1">
{{ value1() }}
</div>
<span *ngIf="condition2">
{{ value2() }}
</span>
</div>
3 changes: 3 additions & 0 deletions e2e/zoneless-env/foo.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
p {
font-size: 1.6rem;
}
25 changes: 25 additions & 0 deletions e2e/zoneless-env/foo.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { NgIf } from '@angular/common';
import { Component, input } from '@angular/core';

@Component({
selector: 'foo',
standalone: true,
templateUrl: './foo.component.html',
styleUrls: ['./foo.component.scss'],
// we have to setup styles this way, since simple styles/styleUrs properties will be removed (jest does not unit test styles)
styles: [
`
p {
color: red;
}
`,
],
imports: [NgIf],
})
export class FooComponent {
readonly value1 = input('val1');
readonly value2 = input('val2');

protected readonly condition1 = true;
protected readonly condition2 = false;
}
24 changes: 24 additions & 0 deletions e2e/zoneless-env/jest-cjs.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { JestConfigWithTsJest } from 'ts-jest';

const config: JestConfigWithTsJest = {
displayName: 'e2e-zoneless-env',
testEnvironment: 'jsdom',
snapshotSerializers: [
'<rootDir>/../../build/serializers/html-comment',
'<rootDir>/../../build/serializers/ng-snapshot',
'<rootDir>/../../build/serializers/no-ng-attributes',
],
setupFilesAfterEnv: ['./setup-zoneless-env.ts'],
transform: {
'^.+\\.(ts|js|mjs|html)$': [
'<rootDir>/../../build/index.js',
{
tsconfig: '<rootDir>/tsconfig-cjs.spec.json',
stringifyContentPathRegex: '\\.(html|svg)$',
},
],
},
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
};

export default config;
28 changes: 28 additions & 0 deletions e2e/zoneless-env/jest-esm.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { JestConfigWithTsJest } from 'ts-jest';

const config: JestConfigWithTsJest = {
displayName: 'e2e-zoneless-env',
testEnvironment: 'jsdom',
snapshotSerializers: [
'<rootDir>/../../build/serializers/html-comment',
'<rootDir>/../../build/serializers/ng-snapshot',
'<rootDir>/../../build/serializers/no-ng-attributes',
],
setupFilesAfterEnv: ['./setup-zoneless-env.mts'],
moduleNameMapper: {
rxjs: '<rootDir>/../../node_modules/rxjs/dist/bundles/rxjs.umd.js',
},
extensionsToTreatAsEsm: ['.ts', '.mts'],
transform: {
'^.+\\.(ts|mts|js|mjs|html)$': [
'<rootDir>/../../build/index.js',
{
useESM: true,
tsconfig: '<rootDir>/tsconfig-esm.spec.json',
stringifyContentPathRegex: '\\.(html|svg)$',
},
],
},
};

export default config;
25 changes: 25 additions & 0 deletions e2e/zoneless-env/jest-transpile-cjs.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { JestConfigWithTsJest } from 'ts-jest';

const config: JestConfigWithTsJest = {
displayName: 'e2e-snapshot-serializers',
testEnvironment: 'jsdom',
snapshotSerializers: [
'<rootDir>/../../build/serializers/html-comment',
'<rootDir>/../../build/serializers/ng-snapshot',
'<rootDir>/../../build/serializers/no-ng-attributes',
],
setupFilesAfterEnv: ['./setup-zoneless-env.ts'],
transform: {
'^.+\\.(ts|js|mjs|html)$': [
'<rootDir>/../../build/index.js',
{
tsconfig: '<rootDir>/tsconfig-cjs.spec.json',
stringifyContentPathRegex: '\\.(html|svg)$',
isolatedModules: true,
},
],
},
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
};

export default config;
29 changes: 29 additions & 0 deletions e2e/zoneless-env/jest-transpile-esm.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { JestConfigWithTsJest } from 'ts-jest';

const config: JestConfigWithTsJest = {
displayName: 'e2e-snapshot-serializers',
testEnvironment: 'jsdom',
snapshotSerializers: [
'<rootDir>/../../build/serializers/html-comment',
'<rootDir>/../../build/serializers/ng-snapshot',
'<rootDir>/../../build/serializers/no-ng-attributes',
],
setupFilesAfterEnv: ['./setup-zoneless-env.mts'],
moduleNameMapper: {
rxjs: '<rootDir>/../../node_modules/rxjs/dist/bundles/rxjs.umd.js',
},
extensionsToTreatAsEsm: ['.ts', '.mts'],
transform: {
'^.+\\.(ts|mts|js|mjs|html)$': [
'<rootDir>/../../build/index.js',
{
useESM: true,
tsconfig: '<rootDir>/tsconfig-esm.spec.json',
stringifyContentPathRegex: '\\.(html|svg)$',
isolatedModules: true,
},
],
},
};

export default config;
3 changes: 3 additions & 0 deletions e2e/zoneless-env/setup-zoneless-env.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { setupZonelessTestEnv } from '../../setup-env/zoneless/index.mjs';

setupZonelessTestEnv();
3 changes: 3 additions & 0 deletions e2e/zoneless-env/setup-zoneless-env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { setupZonelessTestEnv } from '../../setup-env/zoneless';

setupZonelessTestEnv();
3 changes: 3 additions & 0 deletions e2e/zoneless-env/tsconfig-cjs.spec.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "../../tsconfig-base.spec.json"
}
7 changes: 7 additions & 0 deletions e2e/zoneless-env/tsconfig-esm.spec.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig-base.spec.json",
"compilerOptions": {
"module": "ES2022",
"esModuleInterop": true
}
}
11 changes: 8 additions & 3 deletions setup-env/utils.mjs → setup-env/utils.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { TextDecoder, TextEncoder } from 'util';
const { TextDecoder, TextEncoder } = require('util');

export const polyfillEncoder = () => {
const polyfillEncoder = () => {
if (typeof globalThis.TextEncoder === 'undefined') {
globalThis.TextEncoder = TextEncoder;
globalThis.TextDecoder = TextDecoder;
}
};

export const resolveTestEnvOptions = (options) => {
const resolveTestEnvOptions = (options) => {
const globalTestEnvOptions = globalThis.ngJest?.testEnvironmentOptions;
if (globalTestEnvOptions) {
console.warn(
Expand All @@ -17,3 +17,8 @@ export const resolveTestEnvOptions = (options) => {

return globalTestEnvOptions ?? options;
};

module.exports = {
polyfillEncoder,
resolveTestEnvOptions,
};
22 changes: 2 additions & 20 deletions setup-env/zone/index.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,20 @@
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 { polyfillEncoder, resolveTestEnvOptions } = require('../utils');

const setupZoneTestEnv = (options) => {
polyfillEncoder();
const testEnvironmentOptions = resolveTestEnvOptions(options);

getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
[BrowserDynamicTestingModule],
platformBrowserDynamicTesting(),
testEnvironmentOptions,
);
Expand Down
10 changes: 7 additions & 3 deletions setup-env/zone/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@ 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';
import { polyfillEncoder, resolveTestEnvOptions } from '../utils';

export const setupZoneTestEnv = (options) => {
const setupZoneTestEnv = (options) => {
polyfillEncoder();
const testEnvironmentOptions = resolveTestEnvOptions(options);

getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
[BrowserDynamicTestingModule],
platformBrowserDynamicTesting(),
testEnvironmentOptions,
);
};

export {
setupZoneTestEnv,
}
48 changes: 44 additions & 4 deletions setup-env/zoneless/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,48 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const setupZonelessTestEnv = (_options) => {
const { provideExperimentalZonelessChangeDetection, NgModule, ErrorHandler } = require('@angular/core');
const { getTestBed } = require('@angular/core/testing');
const {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting,
} = require('@angular/platform-browser-dynamic/testing');

const { polyfillEncoder, resolveTestEnvOptions } = require('../utils');

const provideZonelessConfig = () => {
class TestModule {}
NgModule({
providers: [
provideExperimentalZonelessChangeDetection(),
{
provide: ErrorHandler,
useValue: {
handleError: (e) => {
throw e;
},
},
},
],
})(TestModule);

return TestModule;
};

const setupZonelessTestEnv = (options) => {
polyfillEncoder();
if (typeof provideExperimentalZonelessChangeDetection !== 'undefined') {
const testEnvironmentOptions = resolveTestEnvOptions(options);

getTestBed().initTestEnvironment(
[BrowserDynamicTestingModule, provideZonelessConfig()],
platformBrowserDynamicTesting(),
testEnvironmentOptions,
);

return;
}

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.',
'Cannot find provideExperimentalZonelessChangeDetection() to setup zoneless testing environment. ' +
'Please use setupZoneTestEnv() from jest-preset-angular/setup-env/setup-zone-env.mjs instead.',
);
};

Expand Down
Loading

0 comments on commit fe2fbe9

Please sign in to comment.