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:
parent
3125376ec1
commit
70fd4300f4
|
@ -17,12 +17,12 @@ import {NgtscProgram} from '../../src/ngtsc/program';
|
||||||
const IDENTIFIER = /[A-Za-z_$ɵ][A-Za-z0-9_$]*/;
|
const IDENTIFIER = /[A-Za-z_$ɵ][A-Za-z0-9_$]*/;
|
||||||
const OPERATOR =
|
const OPERATOR =
|
||||||
/!|\?|%|\*|\/|\^|&&?|\|\|?|\(|\)|\{|\}|\[|\]|:|;|<=?|>=?|={1,3}|!==?|=>|\+\+?|--?|@|,|\.|\.\.\./;
|
/!|\?|%|\*|\/|\^|&&?|\|\|?|\(|\)|\{|\}|\[|\]|:|;|<=?|>=?|={1,3}|!==?|=>|\+\+?|--?|@|,|\.|\.\.\./;
|
||||||
const STRING = /'[^']*'|"[^"]*"|`[\s\S]*?`/;
|
const STRING = /'(\\'|[^'])*'|"(\\"|[^"])*"|`(\\`[\s\S])*?`/;
|
||||||
const NUMBER = /\d+/;
|
const NUMBER = /\d+/;
|
||||||
|
|
||||||
const ELLIPSIS = '…';
|
const ELLIPSIS = '…';
|
||||||
const TOKEN = new RegExp(
|
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');
|
'y');
|
||||||
|
|
||||||
type Piece = string | RegExp;
|
type Piece = string | RegExp;
|
||||||
|
@ -35,10 +35,11 @@ function tokenize(text: string): Piece[] {
|
||||||
TOKEN.lastIndex = 0;
|
TOKEN.lastIndex = 0;
|
||||||
|
|
||||||
let match: RegExpMatchArray|null;
|
let match: RegExpMatchArray|null;
|
||||||
|
let tokenizedTextEnd = 0;
|
||||||
const pieces: Piece[] = [];
|
const pieces: Piece[] = [];
|
||||||
|
|
||||||
while ((match = TOKEN.exec(text)) !== null) {
|
while ((match = TOKEN.exec(text)) !== null) {
|
||||||
const token = match[1];
|
const [fullMatch, token] = match;
|
||||||
if (token === 'IDENT') {
|
if (token === 'IDENT') {
|
||||||
pieces.push(IDENTIFIER);
|
pieces.push(IDENTIFIER);
|
||||||
} else if (token === ELLIPSIS) {
|
} else if (token === ELLIPSIS) {
|
||||||
|
@ -46,12 +47,17 @@ function tokenize(text: string): Piece[] {
|
||||||
} else {
|
} else {
|
||||||
pieces.push(token);
|
pieces.push(token);
|
||||||
}
|
}
|
||||||
|
tokenizedTextEnd += fullMatch.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pieces.length === 0 || TOKEN.lastIndex !== 0) {
|
if (pieces.length === 0 || tokenizedTextEnd < text.length) {
|
||||||
const from = TOKEN.lastIndex;
|
// The new token that could not be found is located after the
|
||||||
|
// last tokenized character.
|
||||||
|
const from = tokenizedTextEnd;
|
||||||
const to = from + ERROR_CONTEXT_WIDTH;
|
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;
|
return pieces;
|
||||||
|
|
|
@ -82,6 +82,34 @@ describe('mock_compiler', () => {
|
||||||
result.source, 'name \n\n . \n length',
|
result.source, 'name \n\n . \n length',
|
||||||
'name length expression not found (whitespace)');
|
'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 // ...)', () => {
|
it('should be able to skip untested regions (… and // ...)', () => {
|
||||||
|
|
|
@ -2692,7 +2692,7 @@ describe('i18n support in the view compiler', () => {
|
||||||
"startItalicText": "\uFFFD#4\uFFFD",
|
"startItalicText": "\uFFFD#4\uFFFD",
|
||||||
"closeItalicText": "\uFFFD/#4\uFFFD",
|
"closeItalicText": "\uFFFD/#4\uFFFD",
|
||||||
"closeTagDiv": "\uFFFD/#3\uFFFD",
|
"closeTagDiv": "\uFFFD/#3\uFFFD",
|
||||||
"icu": I18N_APP_SPEC_TS_1
|
"icu": $I18N_1$
|
||||||
});
|
});
|
||||||
$I18N_0$ = $MSG_EXTERNAL_5791551881115084301$$APP_SPEC_TS_0$;
|
$I18N_0$ = $MSG_EXTERNAL_5791551881115084301$$APP_SPEC_TS_0$;
|
||||||
}
|
}
|
||||||
|
@ -2704,7 +2704,7 @@ describe('i18n support in the view compiler', () => {
|
||||||
"startItalicText": "\uFFFD#4\uFFFD",
|
"startItalicText": "\uFFFD#4\uFFFD",
|
||||||
"closeItalicText": "\uFFFD/#4\uFFFD",
|
"closeItalicText": "\uFFFD/#4\uFFFD",
|
||||||
"closeTagDiv": "\uFFFD/#3\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$.ɵɵelementStart(0, "div");
|
||||||
$r3$.ɵɵi18nStart(1, $I18N_0$);
|
$r3$.ɵɵi18nStart(1, $I18N_0$);
|
||||||
$r3$.ɵɵelement(2, "b");
|
$r3$.ɵɵelement(2, "b");
|
||||||
$r3$.ɵɵelementStart(3, "div");
|
$r3$.ɵɵelementStart(3, "div", $_c2$);
|
||||||
$r3$.ɵɵstyling($_c2$);
|
|
||||||
$r3$.ɵɵelement(4, "i");
|
$r3$.ɵɵelement(4, "i");
|
||||||
$r3$.ɵɵelementEnd();
|
$r3$.ɵɵelementEnd();
|
||||||
$r3$.ɵɵi18nEnd();
|
$r3$.ɵɵi18nEnd();
|
||||||
$r3$.ɵɵelementEnd();
|
$r3$.ɵɵelementEnd();
|
||||||
}
|
}
|
||||||
if (rf & 2) {
|
if (rf & 2) {
|
||||||
|
$r3$.ɵɵselect(1);
|
||||||
$r3$.ɵɵi18nExp($r3$.ɵɵbind(ctx.gender));
|
$r3$.ɵɵi18nExp($r3$.ɵɵbind(ctx.gender));
|
||||||
$r3$.ɵɵi18nApply(1);
|
$r3$.ɵɵi18nApply(1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,9 +144,19 @@ describe('compiler compliance: providers', () => {
|
||||||
result.source, `
|
result.source, `
|
||||||
export class MyComponent {
|
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) {
|
MyComponent.ngComponentDef = i0.ɵɵdefineComponent({
|
||||||
i0.ɵelement(0, "div");
|
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');
|
'Incorrect features');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue