mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-16 18:43:42 +08:00
test(@angular-devkit/build-angular): move output-hashing test to new test harness
This commit is contained in:
parent
a86ea3f154
commit
54f44bc49a
@ -1,195 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google Inc. 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 { Architect } from '@angular-devkit/architect';
|
|
||||||
import { normalize } from '@angular-devkit/core';
|
|
||||||
import {
|
|
||||||
browserBuild,
|
|
||||||
createArchitect,
|
|
||||||
host,
|
|
||||||
lazyModuleFiles,
|
|
||||||
lazyModuleFnImport,
|
|
||||||
} from '../../test-utils';
|
|
||||||
|
|
||||||
describe('Browser Builder output hashing', () => {
|
|
||||||
const target = { project: 'app', target: 'build' };
|
|
||||||
let architect: Architect;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await host.initialize().toPromise();
|
|
||||||
architect = (await createArchitect(host.root())).architect;
|
|
||||||
});
|
|
||||||
afterEach(async () => host.restore().toPromise());
|
|
||||||
|
|
||||||
it('updates hash as content changes', async () => {
|
|
||||||
const OUTPUT_RE = /(main|styles|lazy\.module)\.([a-z0-9]+)\.(chunk|bundle)\.(js|css)$/;
|
|
||||||
|
|
||||||
function generateFileHashMap(): Map<string, string> {
|
|
||||||
const hashes = new Map<string, string>();
|
|
||||||
|
|
||||||
host
|
|
||||||
.scopedSync()
|
|
||||||
.list(normalize('./dist'))
|
|
||||||
.forEach(name => {
|
|
||||||
const matches = name.match(OUTPUT_RE);
|
|
||||||
if (matches) {
|
|
||||||
const [, module, hash] = matches;
|
|
||||||
hashes.set(module, hash);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return hashes;
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateHashes(
|
|
||||||
oldHashes: Map<string, string>,
|
|
||||||
newHashes: Map<string, string>,
|
|
||||||
shouldChange: Array<string>,
|
|
||||||
): void {
|
|
||||||
newHashes.forEach((hash, module) => {
|
|
||||||
if (hash == oldHashes.get(module)) {
|
|
||||||
if (shouldChange.includes(module)) {
|
|
||||||
throw new Error(
|
|
||||||
`Module "${module}" did not change hash (${hash}), but was expected to.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (!shouldChange.includes(module)) {
|
|
||||||
throw new Error(`Module "${module}" changed hash (${hash}), but was not expected to.`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let oldHashes: Map<string, string>;
|
|
||||||
let newHashes: Map<string, string>;
|
|
||||||
|
|
||||||
host.writeMultipleFiles(lazyModuleFiles);
|
|
||||||
host.writeMultipleFiles(lazyModuleFnImport);
|
|
||||||
|
|
||||||
const overrides = { outputHashing: 'all', extractCss: true };
|
|
||||||
|
|
||||||
// We must do several builds instead of a single one in watch mode, so that the output
|
|
||||||
// path is deleted on each run and only contains the most recent files.
|
|
||||||
await browserBuild(architect, host, target, overrides);
|
|
||||||
|
|
||||||
// Save the current hashes.
|
|
||||||
oldHashes = generateFileHashMap();
|
|
||||||
host.writeMultipleFiles(lazyModuleFiles);
|
|
||||||
host.writeMultipleFiles(lazyModuleFnImport);
|
|
||||||
|
|
||||||
await browserBuild(architect, host, target, overrides);
|
|
||||||
newHashes = generateFileHashMap();
|
|
||||||
validateHashes(oldHashes, newHashes, []);
|
|
||||||
oldHashes = newHashes;
|
|
||||||
host.writeMultipleFiles({ 'src/styles.css': 'body { background: blue; }' });
|
|
||||||
|
|
||||||
// Style hash should change.
|
|
||||||
await browserBuild(architect, host, target, overrides);
|
|
||||||
newHashes = generateFileHashMap();
|
|
||||||
validateHashes(oldHashes, newHashes, ['styles']);
|
|
||||||
oldHashes = newHashes;
|
|
||||||
host.writeMultipleFiles({ 'src/app/app.component.css': 'h1 { margin: 10px; }' });
|
|
||||||
|
|
||||||
// Main hash should change, since inline styles go in the main bundle.
|
|
||||||
await browserBuild(architect, host, target, overrides);
|
|
||||||
newHashes = generateFileHashMap();
|
|
||||||
validateHashes(oldHashes, newHashes, ['main']);
|
|
||||||
oldHashes = newHashes;
|
|
||||||
host.appendToFile('src/app/lazy/lazy.module.ts', `console.log(1);`);
|
|
||||||
|
|
||||||
// Lazy loaded bundle should change, and so should inline.
|
|
||||||
await browserBuild(architect, host, target, overrides);
|
|
||||||
newHashes = generateFileHashMap();
|
|
||||||
validateHashes(oldHashes, newHashes, ['lazy.module']);
|
|
||||||
oldHashes = newHashes;
|
|
||||||
host.appendToFile('src/main.ts', '');
|
|
||||||
|
|
||||||
// Nothing should have changed.
|
|
||||||
await browserBuild(architect, host, target, overrides);
|
|
||||||
newHashes = generateFileHashMap();
|
|
||||||
validateHashes(oldHashes, newHashes, []);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('supports options', async () => {
|
|
||||||
host.writeMultipleFiles({ 'src/styles.css': `h1 { background: url('./spectrum.png')}` });
|
|
||||||
host.writeMultipleFiles(lazyModuleFiles);
|
|
||||||
host.writeMultipleFiles(lazyModuleFnImport);
|
|
||||||
|
|
||||||
// We must do several builds instead of a single one in watch mode, so that the output
|
|
||||||
// path is deleted on each run and only contains the most recent files.
|
|
||||||
// 'all' should hash everything.
|
|
||||||
await browserBuild(architect, host, target, { outputHashing: 'all', extractCss: true });
|
|
||||||
|
|
||||||
expect(host.fileMatchExists('dist', /runtime\.[0-9a-f]{20}\.js/)).toBeTruthy();
|
|
||||||
expect(host.fileMatchExists('dist', /main\.[0-9a-f]{20}\.js/)).toBeTruthy();
|
|
||||||
expect(host.fileMatchExists('dist', /polyfills\.[0-9a-f]{20}\.js/)).toBeTruthy();
|
|
||||||
expect(host.fileMatchExists('dist', /vendor\.[0-9a-f]{20}\.js/)).toBeTruthy();
|
|
||||||
expect(host.fileMatchExists('dist', /styles\.[0-9a-f]{20}\.css/)).toBeTruthy();
|
|
||||||
expect(host.fileMatchExists('dist', /spectrum\.[0-9a-f]{20}\.png/)).toBeTruthy();
|
|
||||||
|
|
||||||
// 'none' should hash nothing.
|
|
||||||
await browserBuild(architect, host, target, { outputHashing: 'none', extractCss: true });
|
|
||||||
|
|
||||||
expect(host.fileMatchExists('dist', /runtime\.[0-9a-f]{20}\.js/)).toBeFalsy();
|
|
||||||
expect(host.fileMatchExists('dist', /main\.[0-9a-f]{20}\.js/)).toBeFalsy();
|
|
||||||
expect(host.fileMatchExists('dist', /polyfills\.[0-9a-f]{20}\.js/)).toBeFalsy();
|
|
||||||
expect(host.fileMatchExists('dist', /vendor\.[0-9a-f]{20}\.js/)).toBeFalsy();
|
|
||||||
expect(host.fileMatchExists('dist', /styles\.[0-9a-f]{20}\.css/)).toBeFalsy();
|
|
||||||
expect(host.fileMatchExists('dist', /spectrum\.[0-9a-f]{20}\.png/)).toBeFalsy();
|
|
||||||
|
|
||||||
// 'media' should hash css resources only.
|
|
||||||
await browserBuild(architect, host, target, { outputHashing: 'media', extractCss: true });
|
|
||||||
|
|
||||||
expect(host.fileMatchExists('dist', /runtime\.[0-9a-f]{20}\.js/)).toBeFalsy();
|
|
||||||
expect(host.fileMatchExists('dist', /main\.[0-9a-f]{20}\.js/)).toBeFalsy();
|
|
||||||
expect(host.fileMatchExists('dist', /polyfills\.[0-9a-f]{20}\.js/)).toBeFalsy();
|
|
||||||
expect(host.fileMatchExists('dist', /vendor\.[0-9a-f]{20}\.js/)).toBeFalsy();
|
|
||||||
expect(host.fileMatchExists('dist', /styles\.[0-9a-f]{20}\.css/)).toBeFalsy();
|
|
||||||
expect(host.fileMatchExists('dist', /spectrum\.[0-9a-f]{20}\.png/)).toBeTruthy();
|
|
||||||
|
|
||||||
// 'bundles' should hash bundles only.
|
|
||||||
await browserBuild(architect, host, target, { outputHashing: 'bundles', extractCss: true });
|
|
||||||
expect(host.fileMatchExists('dist', /runtime\.[0-9a-f]{20}\.js/)).toBeTruthy();
|
|
||||||
expect(host.fileMatchExists('dist', /main\.[0-9a-f]{20}\.js/)).toBeTruthy();
|
|
||||||
expect(host.fileMatchExists('dist', /polyfills\.[0-9a-f]{20}\.js/)).toBeTruthy();
|
|
||||||
expect(host.fileMatchExists('dist', /vendor\.[0-9a-f]{20}\.js/)).toBeTruthy();
|
|
||||||
expect(host.fileMatchExists('dist', /styles\.[0-9a-f]{20}\.css/)).toBeTruthy();
|
|
||||||
expect(host.fileMatchExists('dist', /spectrum\.[0-9a-f]{20}\.png/)).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not hash non injected styles', async () => {
|
|
||||||
const overrides = {
|
|
||||||
outputHashing: 'all',
|
|
||||||
extractCss: true,
|
|
||||||
styles: [{ input: 'src/styles.css', inject: false }],
|
|
||||||
};
|
|
||||||
|
|
||||||
await browserBuild(architect, host, target, overrides);
|
|
||||||
|
|
||||||
expect(host.fileMatchExists('dist', /styles\.[0-9a-f]{20}\.js/)).toBeFalsy();
|
|
||||||
expect(host.fileMatchExists('dist', /styles\.[0-9a-f]{20}\.js.map/)).toBeFalsy();
|
|
||||||
expect(host.scopedSync().exists(normalize('dist/styles.css'))).toBe(true);
|
|
||||||
expect(host.scopedSync().exists(normalize('dist/styles.css.map'))).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not hash non injected styles when optimization is enabled', async () => {
|
|
||||||
host.writeMultipleFiles({ 'src/styles.css': 'body { background: blue; }' });
|
|
||||||
|
|
||||||
const overrides = {
|
|
||||||
outputHashing: 'all',
|
|
||||||
extractCss: true,
|
|
||||||
optimization: true,
|
|
||||||
styles: [{ input: 'src/styles.css', inject: false }],
|
|
||||||
};
|
|
||||||
|
|
||||||
await browserBuild(architect, host, target, overrides);
|
|
||||||
expect(host.fileMatchExists('dist', /styles\.[0-9a-f]{20}\.js/)).toBeFalsy();
|
|
||||||
expect(host.fileMatchExists('dist', /styles\.[0-9a-f]{20}\.js.map/)).toBeFalsy();
|
|
||||||
expect(host.scopedSync().exists(normalize('dist/styles.css'))).toBe(true);
|
|
||||||
expect(host.scopedSync().exists(normalize('dist/styles.css.map'))).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
@ -0,0 +1,176 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. 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 { buildWebpackBrowser } from '../../index';
|
||||||
|
import { OutputHashing } from '../../schema';
|
||||||
|
import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup';
|
||||||
|
|
||||||
|
describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => {
|
||||||
|
describe('Option: "outputHashing"', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
// Application code is not needed for asset tests
|
||||||
|
await harness.writeFile('src/main.ts', '');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('hashes all filenames when set to "all"', async () => {
|
||||||
|
await harness.writeFile(
|
||||||
|
'src/styles.css',
|
||||||
|
`h1 { background: url('./spectrum.png')}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
harness.useTarget('build', {
|
||||||
|
...BASE_OPTIONS,
|
||||||
|
styles: ['src/styles.css'],
|
||||||
|
outputHashing: OutputHashing.All,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { result } = await harness.executeOnce();
|
||||||
|
expect(result?.success).toBe(true);
|
||||||
|
|
||||||
|
expect(harness.hasFileMatch('dist', /runtime\.[0-9a-f]{20}\.js$/)).toBeTrue();
|
||||||
|
expect(harness.hasFileMatch('dist', /main\.[0-9a-f]{20}\.js$/)).toBeTrue();
|
||||||
|
expect(harness.hasFileMatch('dist', /polyfills\.[0-9a-f]{20}\.js$/)).toBeTrue();
|
||||||
|
expect(harness.hasFileMatch('dist', /styles\.[0-9a-f]{20}\.css$/)).toBeTrue();
|
||||||
|
expect(harness.hasFileMatch('dist', /spectrum\.[0-9a-f]{20}\.png$/)).toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`doesn't hash any filenames when not set`, async () => {
|
||||||
|
await harness.writeFile(
|
||||||
|
'src/styles.css',
|
||||||
|
`h1 { background: url('./spectrum.png')}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
harness.useTarget('build', {
|
||||||
|
...BASE_OPTIONS,
|
||||||
|
styles: ['src/styles.css'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { result } = await harness.executeOnce();
|
||||||
|
expect(result?.success).toBe(true);
|
||||||
|
|
||||||
|
expect(harness.hasFileMatch('dist', /runtime\.[0-9a-f]{20}\.js$/)).toBeFalse();
|
||||||
|
expect(harness.hasFileMatch('dist', /main\.[0-9a-f]{20}\.js$/)).toBeFalse();
|
||||||
|
expect(harness.hasFileMatch('dist', /polyfills\.[0-9a-f]{20}\.js$/)).toBeFalse();
|
||||||
|
expect(harness.hasFileMatch('dist', /styles\.[0-9a-f]{20}\.css$/)).toBeFalse();
|
||||||
|
expect(harness.hasFileMatch('dist', /spectrum\.[0-9a-f]{20}\.png$/)).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`doesn't hash any filenames when set to "none"`, async () => {
|
||||||
|
await harness.writeFile(
|
||||||
|
'src/styles.css',
|
||||||
|
`h1 { background: url('./spectrum.png')}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
harness.useTarget('build', {
|
||||||
|
...BASE_OPTIONS,
|
||||||
|
styles: ['src/styles.css'],
|
||||||
|
outputHashing: OutputHashing.None,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { result } = await harness.executeOnce();
|
||||||
|
expect(result?.success).toBe(true);
|
||||||
|
|
||||||
|
expect(harness.hasFileMatch('dist', /runtime\.[0-9a-f]{20}\.js$/)).toBeFalse();
|
||||||
|
expect(harness.hasFileMatch('dist', /main\.[0-9a-f]{20}\.js$/)).toBeFalse();
|
||||||
|
expect(harness.hasFileMatch('dist', /polyfills\.[0-9a-f]{20}\.js$/)).toBeFalse();
|
||||||
|
expect(harness.hasFileMatch('dist', /styles\.[0-9a-f]{20}\.css$/)).toBeFalse();
|
||||||
|
expect(harness.hasFileMatch('dist', /spectrum\.[0-9a-f]{20}\.png$/)).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`hashes CSS resources filenames only when set to "media"`, async () => {
|
||||||
|
await harness.writeFile(
|
||||||
|
'src/styles.css',
|
||||||
|
`h1 { background: url('./spectrum.png')}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
harness.useTarget('build', {
|
||||||
|
...BASE_OPTIONS,
|
||||||
|
styles: ['src/styles.css'],
|
||||||
|
outputHashing: OutputHashing.Media,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { result } = await harness.executeOnce();
|
||||||
|
expect(result?.success).toBe(true);
|
||||||
|
|
||||||
|
expect(harness.hasFileMatch('dist', /runtime\.[0-9a-f]{20}\.js$/)).toBeFalse();
|
||||||
|
expect(harness.hasFileMatch('dist', /main\.[0-9a-f]{20}\.js$/)).toBeFalse();
|
||||||
|
expect(harness.hasFileMatch('dist', /polyfills\.[0-9a-f]{20}\.js$/)).toBeFalse();
|
||||||
|
expect(harness.hasFileMatch('dist', /styles\.[0-9a-f]{20}\.css$/)).toBeFalse();
|
||||||
|
expect(harness.hasFileMatch('dist', /spectrum\.[0-9a-f]{20}\.png$/)).toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`hashes bundles filenames only when set to "bundles"`, async () => {
|
||||||
|
await harness.writeFile(
|
||||||
|
'src/styles.css',
|
||||||
|
`h1 { background: url('./spectrum.png')}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
harness.useTarget('build', {
|
||||||
|
...BASE_OPTIONS,
|
||||||
|
styles: ['src/styles.css'],
|
||||||
|
outputHashing: OutputHashing.Bundles,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { result } = await harness.executeOnce();
|
||||||
|
expect(result?.success).toBe(true);
|
||||||
|
|
||||||
|
expect(harness.hasFileMatch('dist', /runtime\.[0-9a-f]{20}\.js$/)).toBeTrue();
|
||||||
|
expect(harness.hasFileMatch('dist', /main\.[0-9a-f]{20}\.js$/)).toBeTrue();
|
||||||
|
expect(harness.hasFileMatch('dist', /polyfills\.[0-9a-f]{20}\.js$/)).toBeTrue();
|
||||||
|
expect(harness.hasFileMatch('dist', /styles\.[0-9a-f]{20}\.css$/)).toBeTrue();
|
||||||
|
expect(harness.hasFileMatch('dist', /spectrum\.[0-9a-f]{20}\.png$/)).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not hash non injected styles', async () => {
|
||||||
|
harness.useTarget('build', {
|
||||||
|
...BASE_OPTIONS,
|
||||||
|
outputHashing: OutputHashing.All,
|
||||||
|
styles: [{
|
||||||
|
input: 'src/styles.css',
|
||||||
|
inject: false,
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { result } = await harness.executeOnce();
|
||||||
|
expect(result?.success).toBe(true);
|
||||||
|
|
||||||
|
expect(harness.hasFileMatch('dist', /styles\.[0-9a-f]{20}\.js$/)).toBeFalse();
|
||||||
|
expect(harness.hasFileMatch('dist', /styles\.[0-9a-f]{20}\.js.map$/)).toBeFalse();
|
||||||
|
harness.expectFile('dist/styles.css').toExist();
|
||||||
|
harness.expectFile('dist/styles.css.map').toExist();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not override different files which has the same filenames when hashing is "none"', async () => {
|
||||||
|
await harness.writeFiles({
|
||||||
|
'src/styles.css': `
|
||||||
|
h1 { background: url('./test.svg')}
|
||||||
|
h2 { background: url('./small/test.svg')}
|
||||||
|
`,
|
||||||
|
'./src/test.svg': `<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<text x="20" y="20" font-size="20" fill="red">Hello World</text>
|
||||||
|
</svg>`,
|
||||||
|
'./src/small/test.svg': `<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<text x="10" y="10" font-size="10" fill="red">Hello World</text>
|
||||||
|
</svg>`,
|
||||||
|
});
|
||||||
|
|
||||||
|
harness.useTarget('build', {
|
||||||
|
...BASE_OPTIONS,
|
||||||
|
styles: ['src/styles.css'],
|
||||||
|
outputHashing: OutputHashing.None,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { result } = await harness.executeOnce();
|
||||||
|
expect(result?.success).toBe(true);
|
||||||
|
|
||||||
|
harness.expectFile('dist/test.svg').toExist();
|
||||||
|
harness.expectFile('dist/small-test.svg').toExist();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -312,6 +312,12 @@ export class BuilderHarness<T> {
|
|||||||
return this.host.scopedSync().exists(normalize(path));
|
return this.host.scopedSync().exists(normalize(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasFileMatch(directory: string, pattern: RegExp): boolean {
|
||||||
|
return this.host.scopedSync()
|
||||||
|
.list(normalize(directory))
|
||||||
|
.some(name => pattern.test(name));
|
||||||
|
}
|
||||||
|
|
||||||
readFile(path: string): string {
|
readFile(path: string): string {
|
||||||
const content = this.host.scopedSync().read(normalize(path));
|
const content = this.host.scopedSync().read(normalize(path));
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user