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));
|
||||
}
|
||||
|
||||
hasFileMatch(directory: string, pattern: RegExp): boolean {
|
||||
return this.host.scopedSync()
|
||||
.list(normalize(directory))
|
||||
.some(name => pattern.test(name));
|
||||
}
|
||||
|
||||
readFile(path: string): string {
|
||||
const content = this.host.scopedSync().read(normalize(path));
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user