Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 766d4a0

Browse filesBrowse files
committed
feat(@schematics/angular): add migration to remove require calls from karma builder main file
With the recent changes in build-angular the `require.context` calls have become unneeded.
1 parent 2624d89 commit 766d4a0
Copy full SHA for 766d4a0

File tree

3 files changed

+257
-0
lines changed
Filter options

3 files changed

+257
-0
lines changed

‎packages/schematics/angular/migrations/migration-collection.json

Copy file name to clipboardExpand all lines: packages/schematics/angular/migrations/migration-collection.json
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@
1919
"version": "15.0.0",
2020
"factory": "./update-15/update-workspace-config",
2121
"description": "Remove options from 'angular.json' that are no longer supported by the official builders."
22+
},
23+
"update-karma-main-file": {
24+
"version": "15.0.0",
25+
"factory": "./update-15/update-karma-main-file",
26+
"description": "Remove no longer needed require calls in Karma builder main file."
2227
}
2328
}
2429
}
+101Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import { Rule, Tree } from '@angular-devkit/schematics';
10+
import * as ts from '../../third_party/github.com/Microsoft/TypeScript/lib/typescript';
11+
import { readWorkspace } from '../../utility';
12+
import { allTargetOptions } from '../../utility/workspace';
13+
import { Builders } from '../../utility/workspace-models';
14+
15+
export default function (): Rule {
16+
return async (host) => {
17+
for (const file of await findTestMainFiles(host)) {
18+
updateTestFile(host, file);
19+
}
20+
};
21+
}
22+
23+
async function findTestMainFiles(host: Tree): Promise<Set<string>> {
24+
const testFiles = new Set<string>();
25+
const workspace = await readWorkspace(host);
26+
27+
// find all test.ts files.
28+
for (const project of workspace.projects.values()) {
29+
for (const target of project.targets.values()) {
30+
if (target.builder !== Builders.Karma) {
31+
continue;
32+
}
33+
34+
for (const [, options] of allTargetOptions(target)) {
35+
if (typeof options.main === 'string') {
36+
testFiles.add(options.main);
37+
}
38+
}
39+
}
40+
}
41+
42+
return testFiles;
43+
}
44+
45+
function updateTestFile(host: Tree, file: string): void {
46+
const content = host.readText(file);
47+
if (!content.includes('require.context')) {
48+
return;
49+
}
50+
51+
const sourceFile = ts.createSourceFile(
52+
file,
53+
content.replace(/^\uFEFF/, ''),
54+
ts.ScriptTarget.Latest,
55+
true,
56+
);
57+
58+
const usedVariableNames = new Set<string>();
59+
const recorder = host.beginUpdate(sourceFile.fileName);
60+
61+
ts.forEachChild(sourceFile, (node) => {
62+
if (ts.isVariableStatement(node)) {
63+
const variableDeclaration = node.declarationList.declarations[0];
64+
65+
if (ts.getModifiers(node)?.some((m) => m.kind === ts.SyntaxKind.DeclareKeyword)) {
66+
// `declare const require`
67+
if (variableDeclaration.name.getText() !== 'require') {
68+
return;
69+
}
70+
} else {
71+
// `const context = require.context('./', true, /\.spec\.ts$/);`
72+
if (!variableDeclaration.initializer?.getText().startsWith('require.context')) {
73+
return;
74+
}
75+
76+
// add variable name as used.
77+
usedVariableNames.add(variableDeclaration.name.getText());
78+
}
79+
80+
// Delete node.
81+
recorder.remove(node.getFullStart(), node.getFullWidth());
82+
}
83+
84+
if (
85+
usedVariableNames.size &&
86+
ts.isExpressionStatement(node) && // context.keys().map(context);
87+
ts.isCallExpression(node.expression) && // context.keys().map(context);
88+
ts.isPropertyAccessExpression(node.expression.expression) && // context.keys().map
89+
ts.isCallExpression(node.expression.expression.expression) && // context.keys()
90+
ts.isPropertyAccessExpression(node.expression.expression.expression.expression) && // context.keys
91+
ts.isIdentifier(node.expression.expression.expression.expression.expression) && // context
92+
usedVariableNames.has(node.expression.expression.expression.expression.expression.getText())
93+
) {
94+
// `context.keys().map(context);`
95+
// `context.keys().forEach(context);`
96+
recorder.remove(node.getFullStart(), node.getFullWidth());
97+
}
98+
});
99+
100+
host.commitUpdate(recorder);
101+
}
+151Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import { tags } from '@angular-devkit/core';
10+
import { EmptyTree } from '@angular-devkit/schematics';
11+
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
12+
import { Builders, ProjectType, WorkspaceSchema } from '../../utility/workspace-models';
13+
14+
function createWorkspace(tree: UnitTestTree): void {
15+
const angularConfig: WorkspaceSchema = {
16+
version: 1,
17+
projects: {
18+
app: {
19+
root: '',
20+
sourceRoot: 'src',
21+
projectType: ProjectType.Application,
22+
prefix: 'app',
23+
architect: {
24+
test: {
25+
builder: Builders.Karma,
26+
options: {
27+
main: 'test.ts',
28+
karmaConfig: './karma.config.js',
29+
tsConfig: 'test-spec.json',
30+
},
31+
configurations: {
32+
production: {
33+
main: 'test-multiple-context.ts',
34+
},
35+
},
36+
},
37+
},
38+
},
39+
},
40+
};
41+
42+
tree.create('/angular.json', JSON.stringify(angularConfig, undefined, 2));
43+
tree.create(
44+
'test.ts',
45+
tags.stripIndents`
46+
import { getTestBed } from '@angular/core/testing';
47+
import {
48+
BrowserDynamicTestingModule,
49+
platformBrowserDynamicTesting
50+
} from '@angular/platform-browser-dynamic/testing';
51+
52+
declare const require: {
53+
context(path: string, deep?: boolean, filter?: RegExp): {
54+
<T>(id: string): T;
55+
keys(): string[];
56+
};
57+
};
58+
59+
// First, initialize the Angular testing environment.
60+
getTestBed().initTestEnvironment(
61+
BrowserDynamicTestingModule,
62+
platformBrowserDynamicTesting(),
63+
);
64+
65+
// Then we find all the tests.
66+
const context = require.context('./', true, /\.spec\.ts$/);
67+
// And load the modules.
68+
context.keys().map(context);
69+
`,
70+
);
71+
72+
tree.create(
73+
'test-multiple-context.ts',
74+
tags.stripIndents`
75+
import { getTestBed } from '@angular/core/testing';
76+
import {
77+
BrowserDynamicTestingModule,
78+
platformBrowserDynamicTesting
79+
} from '@angular/platform-browser-dynamic/testing';
80+
81+
declare const require: {
82+
context(path: string, deep?: boolean, filter?: RegExp): {
83+
<T>(id: string): T;
84+
keys(): string[];
85+
};
86+
};
87+
88+
// First, initialize the Angular testing environment.
89+
getTestBed().initTestEnvironment(
90+
BrowserDynamicTestingModule,
91+
platformBrowserDynamicTesting(),
92+
);
93+
94+
// Then we find all the tests.
95+
const context1 = require.context('./', true, /\.spec\.ts$/);
96+
const context2 = require.context('./', true, /\.spec\.ts$/);
97+
// And load the modules.
98+
context2.keys().forEach(context2);
99+
context1.keys().map(context1);
100+
`,
101+
);
102+
}
103+
104+
describe(`Migration to karma builder main file (test.ts)`, () => {
105+
const schematicName = 'update-karma-main-file';
106+
107+
const schematicRunner = new SchematicTestRunner(
108+
'migrations',
109+
require.resolve('../migration-collection.json'),
110+
);
111+
112+
let tree: UnitTestTree;
113+
beforeEach(() => {
114+
tree = new UnitTestTree(new EmptyTree());
115+
createWorkspace(tree);
116+
});
117+
118+
it(`should remove 'declare const require' and 'require.context' usages`, async () => {
119+
const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
120+
expect(newTree.readText('test.ts')).toBe(tags.stripIndents`
121+
import { getTestBed } from '@angular/core/testing';
122+
import {
123+
BrowserDynamicTestingModule,
124+
platformBrowserDynamicTesting
125+
} from '@angular/platform-browser-dynamic/testing';
126+
127+
// First, initialize the Angular testing environment.
128+
getTestBed().initTestEnvironment(
129+
BrowserDynamicTestingModule,
130+
platformBrowserDynamicTesting(),
131+
);
132+
`);
133+
});
134+
135+
it(`should remove multiple 'require.context' usages`, async () => {
136+
const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
137+
expect(newTree.readText('test-multiple-context.ts')).toBe(tags.stripIndents`
138+
import { getTestBed } from '@angular/core/testing';
139+
import {
140+
BrowserDynamicTestingModule,
141+
platformBrowserDynamicTesting
142+
} from '@angular/platform-browser-dynamic/testing';
143+
144+
// First, initialize the Angular testing environment.
145+
getTestBed().initTestEnvironment(
146+
BrowserDynamicTestingModule,
147+
platformBrowserDynamicTesting(),
148+
);
149+
`);
150+
});
151+
});

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.