/
testHelper.ts
232 lines (220 loc) · 7.74 KB
/
testHelper.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
import * as tslint from 'tslint';
import * as Lint from 'tslint';
import chai = require('chai');
interface ISourcePosition {
line: number;
character: number;
}
export interface IExpectedFailure {
message: string;
startPosition: ISourcePosition;
endPosition: ISourcePosition;
}
/**
* A helper function for specs. Lints the given `source` string against the `ruleName` with
* `options`.
*
* You're unlikely to use these in actual specs. Usually you'd use some of the following:
* - `assertAnnotated` or
* - `assertSuccess`.
*
* @param ruleName the name of the rule which is being tested
* @param source the source code, as a string
* @param options additional options for the lint rule
* @returns {LintResult} the result of linting
*/
function lint(ruleName: string, source: string, options: any): tslint.LintResult {
let configuration = {
extends: [],
rules: new Map<string, Partial<tslint.IOptions>>(),
jsRules: new Map<string, Partial<tslint.IOptions>>(),
rulesDirectory: []
};
if (!options) {
options = [];
}
const ops: Partial<tslint.IOptions> = { ruleName, ruleArguments: options, disabledIntervals: [] };
configuration.rules.set(ruleName, ops);
var linterOptions: tslint.ILinterOptions = {
formatter: 'json',
rulesDirectory: './dist/src',
formattersDirectory: null,
fix: false
};
let linter = new tslint.Linter(linterOptions, undefined);
linter.lint('file.ts', source, configuration);
return linter.getResult();
}
export interface AssertConfig {
ruleName: string;
source: string;
options?: any;
message?: string;
}
export interface AssertMultipleConfigs {
ruleName: string;
source: string;
options?: any;
failures: {char: string; msg: string}[];
}
/**
* When testing a failure, we also test to see if the linter will report the correct place where
* the source code doesn't match the rule.
*
* For example, if you use a private property in your template, the linter should report _where_
* did it happen. Because it's tedious to supply actual line/column number in the spec, we use
* some custom syntax with "underlining" the problematic part with tildes:
*
* ```
* template: '{{ foo }}'
* ~~~
* ```
*
* When giving a spec which we expect to fail, we give it "source code" such as above, with tildes.
* We call this kind of source code "annotated". This source code cannot be compiled (and thus
* cannot be linted/tested), so we use this function to get rid of tildes, but maintain the
* information about where the linter is supposed to catch error.
*
* The result of the function contains "cleaned" source (`.source`) and a `.failure` object which
* contains the `.startPosition` and `.endPosition` of the tildes.
*
* @param source The annotated source code with tildes.
* @param message Passed to the result's `.failure.message` property.
* @param specialChar The character to look for; in the above example that's ~.
* @param otherChars All other characters which should be ignored. Used when asserting multiple
* failures where there are multiple invalid characters.
* @returns {{source: string, failure: {message: string, startPosition: null, endPosition: any}}}
*/
const parseInvalidSource = (source: string, message: string, specialChar: string = '~', otherChars: string[] = []) => {
otherChars.forEach(char => source.replace(new RegExp(char, 'g'), ' '));
let start = null;
let end;
let line = 0;
let col = 0;
let lastCol = 0;
let lastLine = 0;
for (let i = 0; i < source.length; i += 1) {
if (source[i] === specialChar && source[i - 1] !== '/' && start === null) {
start = {
line: line - 1,
character: col
};
}
if (source[i] === '\n') {
col = 0;
line += 1;
} else {
col += 1;
}
if (source[i] === specialChar && source[i - 1] !== '/') {
lastCol = col;
lastLine = line - 1;
}
}
end = {
line: lastLine,
character: lastCol
};
source = source.replace(new RegExp(specialChar, 'g'), '');
return {
source: source,
failure: {
message: message,
startPosition: start,
endPosition: end
}
};
};
/**
* Helper function used in specs for asserting an annotated failure.
* See explanation given in `parseInvalidSource` about annotated source code. *
*
* @param config
*/
export function assertAnnotated(config: AssertConfig) {
if (config.message) {
const parsed = parseInvalidSource(config.source, config.message);
return assertFailure(config.ruleName, parsed.source, parsed.failure, config.options);
} else {
return assertSuccess(config.ruleName, config.source, config.options);
}
}
/**
* Helper function which asserts multiple annotated failures.
* @param configs
*/
export function assertMultipleAnnotated(configs: AssertMultipleConfigs): void {
configs.failures.forEach((failure, index) => {
const otherCharacters = configs.failures.map(message => message.char).filter(x => x !== failure.char);
if (failure.msg) {
const parsed = parseInvalidSource(configs.source, failure.msg, failure.char, otherCharacters);
assertFailure(configs.ruleName, parsed.source, parsed.failure, configs.options, index);
} else {
assertSuccess(configs.ruleName, configs.source, configs.options);
}
});
}
/**
* A helper function used in specs to assert a failure (meaning that the code contains a lint error).
* Consider using `assertAnnotated` instead.
*
* @param ruleName
* @param source
* @param fail
* @param options
* @param onlyNthFailure When there are multiple failures in code, we might want to test only some.
* This is 0-based index of the error that will be tested for. 0 by default.
* @returns {any}
*/
export function assertFailure(ruleName: string, source: string, fail: IExpectedFailure,
options = null, onlyNthFailure: number = 0): Lint.RuleFailure[] {
let result: Lint.LintResult;
try {
result = lint(ruleName, source, options);
} catch (e) {
console.log(e.stack);
}
chai.assert(result.failures && result.failures.length > 0, 'no failures');
const ruleFail = result.failures[onlyNthFailure];
chai.assert.equal(fail.message, ruleFail.getFailure(), `error messages don't match`);
chai.assert.deepEqual(fail.startPosition, ruleFail.getStartPosition().getLineAndCharacter(), `start char doesn't match`);
chai.assert.deepEqual(fail.endPosition, ruleFail.getEndPosition().getLineAndCharacter(), `end char doesn't match`);
if (result) {
return result.failures;
}
return undefined;
}
/**
* A helper function used in specs to assert more than one failure.
* Consider using `assertAnnotated` instead.
*
* @param ruleName
* @param source
* @param fails
* @param options
*/
export function assertFailures(ruleName: string, source: string, fails: IExpectedFailure[], options = null) {
let result;
try {
result = lint(ruleName, source, options);
} catch (e) {
console.log(e.stack);
}
chai.assert(result.failures && result.failures.length > 0, 'no failures');
result.failures.forEach((ruleFail, index) => {
chai.assert.equal(fails[index].message, ruleFail.getFailure(), `error messages don't match`);
chai.assert.deepEqual(fails[index].startPosition, ruleFail.getStartPosition().getLineAndCharacter(), `start char doesn't match`);
chai.assert.deepEqual(fails[index].endPosition, ruleFail.getEndPosition().getLineAndCharacter(), `end char doesn't match`);
});
}
/**
* A helper function used in specs to assert a success (meaning that there are no lint errors).
*
* @param ruleName
* @param source
* @param options
*/
export function assertSuccess(ruleName: string, source: string, options = null) {
const result = lint(ruleName, source, options);
chai.assert.isTrue(result && result.failures.length === 0);
}