Skip to content

Commit

Permalink
test(@angular-devkit/build-angular): additional unit tests for esbuil…
Browse files Browse the repository at this point in the history
…d builder

The following unit tests have been ported over to test the experimental esbuild-based
browser application builder:
* `baseHref` option
* `crossOrigin` option
* TypeScript path mapping behavior
  • Loading branch information
clydin authored and angular-robot[bot] committed Feb 21, 2023
1 parent 54cc8d4 commit 4c53c8f
Show file tree
Hide file tree
Showing 3 changed files with 325 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import { buildEsbuildBrowser } from '../../index';
import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup';

describeBuilder(buildEsbuildBrowser, BROWSER_BUILDER_INFO, (harness) => {
describe('Behavior: "TypeScript Path Mapping"', () => {
it('should resolve TS files when imported with a path mapping', async () => {
// Change main module import to use path mapping
await harness.modifyFile('src/main.ts', (content) =>
content.replace(`'./app/app.module'`, `'@root/app.module'`),
);

// Add a path mapping for `@root`
await harness.modifyFile('tsconfig.json', (content) => {
const tsconfig = JSON.parse(content);
tsconfig.compilerOptions.paths = {
'@root/*': ['./src/app/*'],
};

return JSON.stringify(tsconfig);
});

harness.useTarget('build', {
...BASE_OPTIONS,
});

const { result } = await harness.executeOnce();

expect(result?.success).toBe(true);
});

it('should fail to resolve if no path mapping for an import is present', async () => {
// Change main module import to use path mapping
await harness.modifyFile('src/main.ts', (content) =>
content.replace(`'./app/app.module'`, `'@root/app.module'`),
);

// Add a path mapping for `@not-root`
await harness.modifyFile('tsconfig.json', (content) => {
const tsconfig = JSON.parse(content);
tsconfig.compilerOptions.paths = {
'@not-root/*': ['./src/app/*'],
};

return JSON.stringify(tsconfig);
});

harness.useTarget('build', {
...BASE_OPTIONS,
});

const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false });

expect(result?.success).toBe(false);
expect(logs).toContain(
jasmine.objectContaining({
message: jasmine.stringMatching('Could not resolve "@root/app.module"'),
}),
);
});

it('should resolve JS files when imported with a path mapping', async () => {
// Change main module import to use path mapping
await harness.modifyFile('src/main.ts', (content) =>
content.replace(`'./app/app.module'`, `'app-module'`),
);

await harness.writeFiles({
'a.js': `export * from './src/app/app.module';\n\nconsole.log('A');`,
'a.d.ts': `export * from './src/app/app.module';`,
});

// Add a path mapping for `@root`
await harness.modifyFile('tsconfig.json', (content) => {
const tsconfig = JSON.parse(content);
tsconfig.compilerOptions.paths = {
'app-module': ['a.js'],
};

return JSON.stringify(tsconfig);
});

// app.module needs to be manually included since it is not referenced via a TS file
// with the test path mapping in place.
await harness.modifyFile('src/tsconfig.app.json', (content) => {
const tsconfig = JSON.parse(content);
tsconfig.files.push('app/app.module.ts');

return JSON.stringify(tsconfig);
});

harness.useTarget('build', {
...BASE_OPTIONS,
});

const { result } = await harness.executeOnce();

expect(result?.success).toBe(true);
harness.expectFile('dist/main.js').content.toContain(`console.log("A")`);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import { buildEsbuildBrowser } from '../../index';
import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup';

describeBuilder(buildEsbuildBrowser, BROWSER_BUILDER_INFO, (harness) => {
describe('Option: "baseHref"', () => {
beforeEach(async () => {
// Application code is not needed for asset tests
await harness.writeFile('src/main.ts', 'console.log("TEST");');
});

it('should update the base element href attribute when option is set', async () => {
harness.useTarget('build', {
...BASE_OPTIONS,
baseHref: '/abc',
});

const { result } = await harness.executeOnce();
expect(result?.success).toBe(true);
harness.expectFile('dist/index.html').content.toContain('<base href="/abc">');
});

it('should update the base element with no href attribute when option is set', async () => {
await harness.writeFile(
'src/index.html',
`
<html>
<head><base></head>
<body></body>
</html>
`,
);

harness.useTarget('build', {
...BASE_OPTIONS,
baseHref: '/abc',
});

const { result } = await harness.executeOnce();
expect(result?.success).toBe(true);
harness.expectFile('dist/index.html').content.toContain('<base href="/abc">');
});

it('should add the base element href attribute when option is set', async () => {
await harness.writeFile(
'src/index.html',
`
<html>
<head></head>
<body></body>
</html>
`,
);

harness.useTarget('build', {
...BASE_OPTIONS,
baseHref: '/abc',
});

const { result } = await harness.executeOnce();
expect(result?.success).toBe(true);
harness.expectFile('dist/index.html').content.toContain('<base href="/abc">');
});

it('should update the base element href attribute when option is set to an empty string', async () => {
harness.useTarget('build', {
...BASE_OPTIONS,
baseHref: '',
});

const { result } = await harness.executeOnce();
expect(result?.success).toBe(true);
harness.expectFile('dist/index.html').content.toContain('<base href="">');
});

it('should not update the base element href attribute when option is not present', async () => {
harness.useTarget('build', {
...BASE_OPTIONS,
});

const { result } = await harness.executeOnce();
expect(result?.success).toBe(true);
harness.expectFile('dist/index.html').content.toContain('<base href="/">');
});

it('should not change the base element href attribute when option is not present', async () => {
await harness.writeFile(
'src/index.html',
`
<html>
<head><base href="."></head>
<body></body>
</html>
`,
);

harness.useTarget('build', {
...BASE_OPTIONS,
});

const { result } = await harness.executeOnce();
expect(result?.success).toBe(true);
harness.expectFile('dist/index.html').content.toContain('<base href=".">');
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import { buildEsbuildBrowser } from '../../index';
import { CrossOrigin } from '../../schema';
import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup';

describeBuilder(buildEsbuildBrowser, BROWSER_BUILDER_INFO, (harness) => {
describe('Option: "crossOrigin"', () => {
beforeEach(async () => {
// Application code is not needed for asset tests
await harness.writeFile('src/main.ts', 'console.log("TEST");');

// Add a global stylesheet to test link elements
await harness.writeFile('src/styles.css', '// Global styles');

// Reduce the input index HTML to a single line to simplify comparing
await harness.writeFile(
'src/index.html',
'<html><head><base href="/"></head><body><app-root></app-root></body></html>',
);
});

it('should add the use-credentials crossorigin attribute when option is set to use-credentials', async () => {
harness.useTarget('build', {
...BASE_OPTIONS,
styles: ['src/styles.css'],
crossOrigin: CrossOrigin.UseCredentials,
});

const { result } = await harness.executeOnce();
expect(result?.success).toBe(true);
harness
.expectFile('dist/index.html')
.content.toEqual(
`<html><head><base href="/"><link rel="stylesheet" href="styles.css" crossorigin="use-credentials"></head>` +
`<body><app-root></app-root>` +
`<script src="main.js" type="module" crossorigin="use-credentials"></script></body></html>`,
);
});

it('should add the anonymous crossorigin attribute when option is set to anonymous', async () => {
harness.useTarget('build', {
...BASE_OPTIONS,
styles: ['src/styles.css'],
crossOrigin: CrossOrigin.Anonymous,
});

const { result } = await harness.executeOnce();
expect(result?.success).toBe(true);
harness
.expectFile('dist/index.html')
.content.toEqual(
`<html><head><base href="/">` +
`<link rel="stylesheet" href="styles.css" crossorigin="anonymous"></head>` +
`<body><app-root></app-root>` +
`<script src="main.js" type="module" crossorigin="anonymous"></script></body></html>`,
);
});

it('should not add a crossorigin attribute when option is set to none', async () => {
harness.useTarget('build', {
...BASE_OPTIONS,
styles: ['src/styles.css'],
crossOrigin: CrossOrigin.None,
});

const { result } = await harness.executeOnce();
expect(result?.success).toBe(true);
harness
.expectFile('dist/index.html')
.content.toEqual(
`<html><head><base href="/">` +
`<link rel="stylesheet" href="styles.css"></head>` +
`<body><app-root></app-root>` +
`<script src="main.js" type="module"></script></body></html>`,
);
});

it('should not add a crossorigin attribute when option is not present', async () => {
harness.useTarget('build', {
...BASE_OPTIONS,
styles: ['src/styles.css'],
});

const { result } = await harness.executeOnce();
expect(result?.success).toBe(true);
harness
.expectFile('dist/index.html')
.content.toEqual(
`<html><head><base href="/">` +
`<link rel="stylesheet" href="styles.css"></head>` +
`<body><app-root></app-root>` +
`<script src="main.js" type="module"></script></body></html>`,
);
});
});
});

0 comments on commit 4c53c8f

Please sign in to comment.