Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ function createTestBedInitVirtualFile(
providersFile: string | undefined,
projectSourceRoot: string,
teardown: boolean,
zoneTestingStrategy: 'none' | 'static' | 'dynamic',
zoneTestingStrategy: 'none' | 'static' | 'dynamic' | 'dynamic-zone',
hasLocalize: boolean,
): string {
let providersImport = 'const providers = [];';
Expand All @@ -53,6 +53,13 @@ function createTestBedInitVirtualFile(
// It must be imported dynamically to avoid a static dependency on 'zone.js'.
await import('zone.js/testing');
}`;
} else if (zoneTestingStrategy === 'dynamic-zone') {
zoneTestingSnippet = `try {
await import('zone.js');
await import('zone.js/testing');
} catch (e) {
console.error('DYNAMIC IMPORT ERROR:', e);
}`;
}
Comment on lines +56 to 63

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Swallowing the dynamic import error here and only logging it to console.error can make debugging difficult if zone.js or zone.js/testing fails to load at runtime. Since dynamic-zone is only selected when zone.js is resolved at build time, any runtime import failure is an exceptional state.

Letting the error propagate (similar to how the 'dynamic' strategy does) ensures the test runner fails fast with a clear stack trace pointing directly to the failed import, rather than continuing and failing later with cryptic errors (e.g., Zone is undefined).

  } else if (zoneTestingStrategy === 'dynamic-zone') {
    zoneTestingSnippet = 'await import(\'zone.js\');\n    await import(\'zone.js/testing\');';
  }


// The DynamicDOMTestComponentRenderer is used to avoid stale document references
Expand Down Expand Up @@ -150,12 +157,12 @@ function adjustOutputHashing(hashing?: OutputHashing): OutputHashing {
*
* @param buildOptions The partial application builder options.
* @param projectSourceRoot The root directory of the project source.
* @returns The resolved zone testing strategy ('none', 'static', 'dynamic').
* @returns The resolved zone testing strategy ('none', 'static', 'dynamic', 'dynamic-zone').
*/
function getZoneTestingStrategy(
buildOptions: Partial<ApplicationBuilderInternalOptions>,
projectSourceRoot: string,
): 'none' | 'static' | 'dynamic' {
): 'none' | 'static' | 'dynamic' | 'dynamic-zone' {
if (buildOptions.polyfills?.includes('zone.js/testing')) {
return 'none';
}
Expand All @@ -168,6 +175,12 @@ function getZoneTestingStrategy(
const projectRequire = createRequire(path.join(projectSourceRoot, 'package.json'));
projectRequire.resolve('zone.js');

// If polyfills is undefined (e.g. library build target), load zone.js dynamically.
// If polyfills is defined but doesn't include zone.js (e.g. zoneless application), do NOT load zone.js.
if (buildOptions.polyfills === undefined) {
return 'dynamic-zone';
}

return 'dynamic';
} catch {
return 'none';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,48 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => {
const { result } = await harness.executeOnce();
expect(result?.success).toBe(true);
});

it('should load Zone and Zone testing support when testing a library and zone.js is installed', async () => {
harness.withBuilderTarget(
'build',
async () => ({ success: true }),
{
project: 'ng-package.json',
},
{
builderName: '@angular/build:ng-packagr',
},
);

await harness.writeFile(
'ng-package.json',
JSON.stringify({
lib: {
entryFile: 'src/public-api.ts',
},
}),
);

harness.useTarget('test', {
...BASE_OPTIONS,
include: ['src/app.component.spec.ts'],
});

await harness.writeFile(
'src/app.component.spec.ts',
`
import { describe, it, expect } from 'vitest';

describe('Library Zone Test', () => {
it('should have Zone defined', () => {
expect((globalThis as any).Zone).toBeDefined();
});
});
`,
);

const { result } = await harness.executeOnce();
expect(result?.success).toBeTrue();
});
});
});
Loading