Skip to content

Commit 239c781

Browse files
devversionjelbourn
authored andcommitted
refactor(schematics): add tests for component walker (#13106)
* The `ComponentWalker` is a major part of the update schematics and we should verify that external resource files are resolved properly. All test-cases currently just use inline stylesheets or templates. * Removes the `color` helper function. * Removes the superfluous utility function for retrieving a `StringLiteral`'s text content.
1 parent 18d0fa8 commit 239c781

File tree

8 files changed

+161
-48
lines changed

8 files changed

+161
-48
lines changed

package-lock.json

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
"@types/jasmine": "^2.8.8",
6666
"@types/merge2": "^0.3.30",
6767
"@types/minimist": "^1.2.0",
68+
"@types/mock-fs": "^3.6.30",
6869
"@types/node": "^7.0.21",
6970
"@types/parse5": "^5.0.0",
7071
"@types/run-sequence": "^0.0.29",
@@ -112,6 +113,7 @@
112113
"magic-string": "^0.22.4",
113114
"minimatch": "^3.0.4",
114115
"minimist": "^1.2.0",
116+
"mock-fs": "^4.7.0",
115117
"moment": "^2.18.1",
116118
"node-sass": "^4.9.3",
117119
"parse5": "^5.0.0",

src/lib/schematics/update/material/color.ts

Lines changed: 0 additions & 23 deletions
This file was deleted.

src/lib/schematics/update/material/data/method-call-checks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export const methodCallChecks: VersionChanges<MaterialMethodCallData> = {
2929
invalidArgCounts: [
3030
{
3131
count: 3,
32-
message: 'The "r{{renderer}}" argument has been removed'
32+
message: 'The "renderer" argument has been removed'
3333
}
3434
]
3535
}

src/lib/schematics/update/rules/signature-check/methodCallsCheckRule.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import {bold} from 'chalk';
1010
import {ProgramAwareRuleWalker, RuleFailure, Rules} from 'tslint';
1111
import * as ts from 'typescript';
12-
import {color} from '../../material/color';
1312
import {methodCallChecks} from '../../material/data/method-call-checks';
1413
import {getChangesForTarget} from '../../material/transform-change-data';
1514

@@ -60,6 +59,6 @@ export class Walker extends ProgramAwareRuleWalker {
6059
}
6160

6261
this.addFailureAtNode(node, `Found call to "${bold(hostTypeName + '.' + methodName)}" ` +
63-
`with ${bold(`${failure.count}`)} arguments. Message: ${color(failure.message)}`);
62+
`with ${bold(`${failure.count}`)} arguments. Message: ${failure.message}`);
6463
}
6564
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import * as mockFs from 'mock-fs';
2+
import {dirname, join} from 'path';
3+
import {IOptions} from 'tslint';
4+
import * as ts from 'typescript';
5+
import {ComponentWalker} from './component-walker';
6+
7+
describe('ComponentWalker', () => {
8+
9+
const defaultRuleOptions: IOptions = {
10+
ruleArguments: [],
11+
ruleSeverity: 'error',
12+
disabledIntervals: [],
13+
ruleName: 'component-walker-test'
14+
};
15+
16+
afterEach(() => mockFs.restore());
17+
18+
function createSourceFile(content: string) {
19+
return ts.createSourceFile(join(__dirname, 'test-source-file.ts'), content,
20+
ts.ScriptTarget.Latest, true);
21+
}
22+
23+
it('should report inline stylesheets', () => {
24+
const sourceFile = createSourceFile(inlineStylesheetSource);
25+
const walker = new ComponentWalker(sourceFile, defaultRuleOptions);
26+
27+
spyOn(walker, 'visitInlineStylesheet');
28+
29+
walker.walk(sourceFile);
30+
31+
expect(walker.visitInlineStylesheet).toHaveBeenCalledTimes(1);
32+
});
33+
34+
it('should report external stylesheets', () => {
35+
const sourceFile = createSourceFile(externalStylesheetSource);
36+
const walker = new ComponentWalker(sourceFile, defaultRuleOptions);
37+
const stylePath = join(dirname(sourceFile.fileName), 'my-component.css');
38+
const styleContent = ':host { color: red; }';
39+
40+
spyOn(walker, 'visitExternalStylesheet').and.callFake(node => {
41+
expect(node.getFullText()).toBe(styleContent);
42+
});
43+
44+
mockFs({[stylePath]: styleContent});
45+
46+
walker.walk(sourceFile);
47+
48+
expect(walker.visitExternalStylesheet).toHaveBeenCalledTimes(1);
49+
});
50+
51+
it('should not throw if an external stylesheet could not be resolved', () => {
52+
const sourceFile = createSourceFile(externalStylesheetSource);
53+
const walker = new ComponentWalker(sourceFile, defaultRuleOptions);
54+
55+
spyOn(console, 'error');
56+
57+
expect(() => walker.walk(sourceFile)).not.toThrow();
58+
expect(console.error).toHaveBeenCalledTimes(1);
59+
});
60+
61+
it('should report inline templates', () => {
62+
const sourceFile = createSourceFile(inlineTemplateSource);
63+
const walker = new ComponentWalker(sourceFile, defaultRuleOptions);
64+
65+
spyOn(walker, 'visitInlineTemplate');
66+
67+
walker.walk(sourceFile);
68+
69+
expect(walker.visitInlineTemplate).toHaveBeenCalledTimes(1);
70+
});
71+
72+
it('should report external templates', () => {
73+
const sourceFile = createSourceFile(externalTemplateSource);
74+
const walker = new ComponentWalker(sourceFile, defaultRuleOptions);
75+
const templatePath = join(dirname(sourceFile.fileName), 'my-component.html');
76+
const templateContent = '<span>External template</span>';
77+
78+
spyOn(walker, 'visitExternalTemplate').and.callFake(node => {
79+
expect(node.getFullText()).toBe(templateContent);
80+
});
81+
82+
mockFs({[templatePath]: templateContent});
83+
84+
walker.walk(sourceFile);
85+
86+
expect(walker.visitExternalTemplate).toHaveBeenCalledTimes(1);
87+
});
88+
89+
it('should not throw if an external template could not be resolved', () => {
90+
const sourceFile = createSourceFile(externalTemplateSource);
91+
const walker = new ComponentWalker(sourceFile, defaultRuleOptions);
92+
93+
spyOn(console, 'error');
94+
95+
expect(() => walker.walk(sourceFile)).not.toThrow();
96+
expect(console.error).toHaveBeenCalledTimes(1);
97+
});
98+
});
99+
100+
/** TypeScript source file content that includes a component with inline styles. */
101+
const inlineStylesheetSource = `
102+
@Component({
103+
styles: [':host { color: red; }']
104+
}
105+
export class MyComponent {}
106+
`;
107+
108+
/** TypeScript source file content that includes an inline component template. */
109+
const inlineTemplateSource = `
110+
@Component({
111+
template: '<span>My component</span>'
112+
}
113+
export class MyComponent {}
114+
`;
115+
116+
/** TypeScript source file that includes a component with external styles. */
117+
const externalStylesheetSource = `
118+
@Component({
119+
styleUrls: ['./my-component.css']
120+
})
121+
export class MyComponent {}
122+
`;
123+
124+
/** TypeScript source file that includes a component with an external template. */
125+
const externalTemplateSource = `
126+
@Component({
127+
templateUrl: './my-component.html',
128+
})
129+
export class MyComponent {}
130+
`;

src/lib/schematics/update/tslint/component-walker.ts

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {existsSync, readFileSync} from 'fs';
1313
import {dirname, join, resolve} from 'path';
1414
import {IOptions, RuleWalker} from 'tslint';
1515
import * as ts from 'typescript';
16-
import {getLiteralTextWithoutQuotes} from '../typescript/literal';
1716
import {createComponentFile, ExternalResource} from './component-file';
1817

1918
/**
@@ -22,11 +21,11 @@ import {createComponentFile, ExternalResource} from './component-file';
2221
*/
2322
export class ComponentWalker extends RuleWalker {
2423

25-
protected visitInlineTemplate(_template: ts.StringLiteral) {}
26-
protected visitInlineStylesheet(_stylesheet: ts.StringLiteral) {}
24+
visitInlineTemplate(_template: ts.StringLiteral) {}
25+
visitInlineStylesheet(_stylesheet: ts.StringLiteral) {}
2726

28-
protected visitExternalTemplate(_template: ExternalResource) {}
29-
protected visitExternalStylesheet(_stylesheet: ExternalResource) {}
27+
visitExternalTemplate(_template: ExternalResource) {}
28+
visitExternalStylesheet(_stylesheet: ExternalResource) {}
3029

3130
private extraFiles: Set<string>;
3231

@@ -92,9 +91,8 @@ export class ComponentWalker extends RuleWalker {
9291
}
9392

9493
private _visitExternalStylesArrayLiteral(styleUrls: ts.ArrayLiteralExpression) {
95-
styleUrls.elements.forEach(styleUrlLiteral => {
96-
const styleUrl = getLiteralTextWithoutQuotes(styleUrlLiteral as ts.StringLiteral);
97-
const stylePath = resolve(join(dirname(this.getSourceFile().fileName), styleUrl));
94+
styleUrls.elements.forEach((node: ts.StringLiteral) => {
95+
const stylePath = resolve(join(dirname(this.getSourceFile().fileName), node.text));
9896

9997
// Do not report the specified additional files multiple times.
10098
if (!this.extraFiles.has(stylePath)) {
@@ -103,9 +101,8 @@ export class ComponentWalker extends RuleWalker {
103101
});
104102
}
105103

106-
private _reportExternalTemplate(templateUrlLiteral: ts.StringLiteral) {
107-
const templateUrl = getLiteralTextWithoutQuotes(templateUrlLiteral);
108-
const templatePath = resolve(join(dirname(this.getSourceFile().fileName), templateUrl));
104+
private _reportExternalTemplate(node: ts.StringLiteral) {
105+
const templatePath = resolve(join(dirname(this.getSourceFile().fileName), node.text));
109106

110107
// Do not report the specified additional files multiple times.
111108
if (this.extraFiles.has(templatePath)) {
@@ -116,7 +113,7 @@ export class ComponentWalker extends RuleWalker {
116113
if (!existsSync(templatePath)) {
117114
console.error(`PARSE ERROR: ${this.getSourceFile().fileName}:` +
118115
` Could not find template: "${templatePath}".`);
119-
process.exit(1);
116+
return;
120117
}
121118

122119
// Create a fake TypeScript source file that includes the template content.
@@ -128,9 +125,9 @@ export class ComponentWalker extends RuleWalker {
128125
_reportExternalStyle(stylePath: string) {
129126
// Check if the external stylesheet file exists before proceeding.
130127
if (!existsSync(stylePath)) {
131-
console.error(`PARSE ERROR: ${this.getSourceFile().fileName}:` +
132-
` Could not find stylesheet: "${stylePath}".`);
133-
process.exit(1);
128+
console.error(`PARSE ERROR: ${this.getSourceFile().fileName}: ` +
129+
`Could not find stylesheet: "${stylePath}".`);
130+
return;
134131
}
135132

136133
// Create a fake TypeScript source file that includes the stylesheet content.

src/lib/schematics/update/typescript/literal.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,6 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import * as ts from 'typescript';
10-
11-
/** Returns the text of a string literal without the quotes. */
12-
export function getLiteralTextWithoutQuotes(literal: ts.StringLiteral) {
13-
return literal.getText().substring(1, literal.getText().length - 1);
14-
}
15-
169
/** Finds all start indices of the given search string in the input string. */
1710
export function findAllSubstringIndices(input: string, search: string): number[] {
1811
const result: number[] = [];

0 commit comments

Comments
 (0)