test(compiler-cli): compliance tests not always reporting test failure (#30597)

Currently the `@angular/compiler-cli` compliance tests sometimes do
not throw an exception if the expected output does not match the
generated JavaScript output. This can happen for the following cases:

1. Expected code includes character that is not part of known alphabet
    (e.g. `Δ` is still used in a new compliance test after rebasing a PR)
2. Expected code asserts that a string literal matches a string with
    escaped quotes. e.g. expects `const $var$ = "\"quoted\"";`)

PR Close #30597
This commit is contained in:
Paul Gschwendtner 2019-05-21 21:59:53 +02:00 committed by Jason Aden
parent 3125376ec1
commit 70fd4300f4
4 changed files with 57 additions and 13 deletions

View File

@ -17,12 +17,12 @@ import {NgtscProgram} from '../../src/ngtsc/program';
const IDENTIFIER = /[A-Za-z_$ɵ][A-Za-z0-9_$]*/;
const OPERATOR =
/!|\?|%|\*|\/|\^|&&?|\|\|?|\(|\)|\{|\}|\[|\]|:|;|<=?|>=?|={1,3}|!==?|=>|\+\+?|--?|@|,|\.|\.\.\./;
const STRING = /'[^']*'|"[^"]*"|`[\s\S]*?`/;
const STRING = /'(\\'|[^'])*'|"(\\"|[^"])*"|`(\\`[\s\S])*?`/;
const NUMBER = /\d+/;
const ELLIPSIS = '…';
const TOKEN = new RegExp(
`\\s*((${IDENTIFIER.source})|(${OPERATOR.source})|(${STRING.source})|${NUMBER.source}|${ELLIPSIS})`,
`\\s*((${IDENTIFIER.source})|(${OPERATOR.source})|(${STRING.source})|${NUMBER.source}|${ELLIPSIS})\\s*`,
'y');
type Piece = string | RegExp;
@ -35,10 +35,11 @@ function tokenize(text: string): Piece[] {
TOKEN.lastIndex = 0;
let match: RegExpMatchArray|null;
let tokenizedTextEnd = 0;
const pieces: Piece[] = [];
while ((match = TOKEN.exec(text)) !== null) {
const token = match[1];
const [fullMatch, token] = match;
if (token === 'IDENT') {
pieces.push(IDENTIFIER);
} else if (token === ELLIPSIS) {
@ -46,12 +47,17 @@ function tokenize(text: string): Piece[] {
} else {
pieces.push(token);
}
tokenizedTextEnd += fullMatch.length;
}
if (pieces.length === 0 || TOKEN.lastIndex !== 0) {
const from = TOKEN.lastIndex;
if (pieces.length === 0 || tokenizedTextEnd < text.length) {
// The new token that could not be found is located after the
// last tokenized character.
const from = tokenizedTextEnd;
const to = from + ERROR_CONTEXT_WIDTH;
throw Error(`Invalid test, no token found for '${text.substr(from, to)}...'`);
throw Error(
`Invalid test, no token found for "${text[tokenizedTextEnd]}" ` +
`(context = '${text.substr(from, to)}...'`);
}
return pieces;

View File

@ -82,6 +82,34 @@ describe('mock_compiler', () => {
result.source, 'name \n\n . \n length',
'name length expression not found (whitespace)');
});
it('should throw if the expected output contains unknown characters', () => {
const files = {
app: {
'test.ts': `ɵsayHello();`,
}
};
const result = compile(files, angularFiles);
expect(() => {
expectEmit(result.source, `ΔsayHello();`, 'Output does not match.');
}).toThrowError(/Invalid test, no token found for "Δ"/);
});
it('should be able to properly handle string literals with escaped quote', () => {
const files = {
app: {
'test.ts': String.raw `const identifier = "\"quoted\"";`,
}
};
const result = compile(files, angularFiles);
expect(() => {
expectEmit(result.source, String.raw `const $a$ = "\"quoted\"";`, 'Output does not match.');
}).not.toThrow();
});
});
it('should be able to skip untested regions (… and // ...)', () => {

View File

@ -2692,7 +2692,7 @@ describe('i18n support in the view compiler', () => {
"startItalicText": "\uFFFD#4\uFFFD",
"closeItalicText": "\uFFFD/#4\uFFFD",
"closeTagDiv": "\uFFFD/#3\uFFFD",
"icu": I18N_APP_SPEC_TS_1
"icu": $I18N_1$
});
$I18N_0$ = $MSG_EXTERNAL_5791551881115084301$$APP_SPEC_TS_0$;
}
@ -2704,7 +2704,7 @@ describe('i18n support in the view compiler', () => {
"startItalicText": "\uFFFD#4\uFFFD",
"closeItalicText": "\uFFFD/#4\uFFFD",
"closeTagDiv": "\uFFFD/#3\uFFFD",
"icu": I18N_APP_SPEC_TS_1
"icu": $I18N_1$
});
}
@ -2715,14 +2715,14 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵɵelementStart(0, "div");
$r3$.ɵɵi18nStart(1, $I18N_0$);
$r3$.ɵɵelement(2, "b");
$r3$.ɵɵelementStart(3, "div");
$r3$.ɵɵstyling($_c2$);
$r3$.ɵɵelementStart(3, "div", $_c2$);
$r3$.ɵɵelement(4, "i");
$r3$.ɵɵelementEnd();
$r3$.ɵɵi18nEnd();
$r3$.ɵɵelementEnd();
}
if (rf & 2) {
$r3$.ɵɵselect(1);
$r3$.ɵɵi18nExp($r3$.ɵɵbind(ctx.gender));
$r3$.ɵɵi18nApply(1);
}

View File

@ -144,9 +144,19 @@ describe('compiler compliance: providers', () => {
result.source, `
export class MyComponent {
}
MyComponent.ngComponentDef = i0.ɵdefineComponent({ type: MyComponent, selectors: [["my-component"]], factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }, consts: 1, vars: 0, template: function MyComponent_Template(rf, ctx) { if (rf & 1) {
i0.ɵelement(0, "div");
} } });`,
MyComponent.ngComponentDef = i0.ɵɵdefineComponent({
type: MyComponent,
selectors: [["my-component"]],
factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); },
consts: 1,
vars: 0,
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
i0.ɵɵelement(0, "div");
}
},
encapsulation: 2
});`,
'Incorrect features');
});
});