diff --git a/packages/compiler-cli/test/ngtsc/template_mapping_spec.ts b/packages/compiler-cli/test/ngtsc/template_mapping_spec.ts index c830f30f8e482..a64dbf73b17f0 100644 --- a/packages/compiler-cli/test/ngtsc/template_mapping_spec.ts +++ b/packages/compiler-cli/test/ngtsc/template_mapping_spec.ts @@ -31,20 +31,22 @@ runInEachFileSystem((os) => { describe('(element creation)', () => { it('should map simple element with content', () => { const mappings = compileAndMap('

Heading 1

'); - expect(mappings).toContain( + expectMapping( + mappings, {source: '

', generated: 'i0.ɵɵelementStart(0, "h1")', sourceUrl: '../test.ts'}); - expect(mappings).toContain({ + expectMapping(mappings, { source: 'Heading 1', generated: 'i0.ɵɵtext(1, "Heading 1")', sourceUrl: '../test.ts' }); - expect(mappings).toContain( - {source: '

', generated: 'i0.ɵɵelementEnd()', sourceUrl: '../test.ts'}); + expectMapping( + mappings, {source: '', generated: 'i0.ɵɵelementEnd()', sourceUrl: '../test.ts'}); }); it('should map void element', () => { const mappings = compileAndMap('
'); - expect(mappings).toContain( + expectMapping( + mappings, {source: '
', generated: 'i0.ɵɵelement(0, "hr")', sourceUrl: '../test.ts'}); }); }); @@ -52,38 +54,40 @@ runInEachFileSystem((os) => { describe('(interpolations)', () => { it('should map a mix of interpolated and static content', () => { const mappings = compileAndMap('

Hello {{ name }}

'); - expect(mappings).toContain( + expectMapping( + mappings, {source: '

', generated: 'i0.ɵɵelementStart(0, "h3")', sourceUrl: '../test.ts'}); - expect(mappings).toContain({ + expectMapping(mappings, { source: 'Hello {{ name }}', generated: 'i0.ɵɵtextInterpolate1("Hello ", ctx.name, "")', sourceUrl: '../test.ts' }); - expect(mappings).toContain( - {source: '

', generated: 'i0.ɵɵelementEnd()', sourceUrl: '../test.ts'}); + expectMapping( + mappings, {source: '', generated: 'i0.ɵɵelementEnd()', sourceUrl: '../test.ts'}); }); it('should map a complex interpolated expression', () => { const mappings = compileAndMap('

{{ greeting + " " + name }}

'); - expect(mappings).toContain( + expectMapping( + mappings, {source: '

', generated: 'i0.ɵɵelementStart(0, "h2")', sourceUrl: '../test.ts'}); - expect(mappings).toContain({ + expectMapping(mappings, { source: '{{ greeting + " " + name }}', generated: 'i0.ɵɵtextInterpolate(ctx.greeting + " " + ctx.name)', sourceUrl: '../test.ts' }); - expect(mappings).toContain( - {source: '

', generated: 'i0.ɵɵelementEnd()', sourceUrl: '../test.ts'}); + expectMapping( + mappings, {source: '', generated: 'i0.ɵɵelementEnd()', sourceUrl: '../test.ts'}); }); it('should map interpolated properties', () => { const mappings = compileAndMap('
'); - expect(mappings).toContain({ + expectMapping(mappings, { source: '
', generated: 'i0.ɵɵelement(0, "div", 0)', sourceUrl: '../test.ts' }); - expect(mappings).toContain({ + expectMapping(mappings, { source: 'id="{{name}}"', generated: 'i0.ɵɵpropertyInterpolate("id", ctx.name)', sourceUrl: '../test.ts' @@ -92,14 +96,16 @@ runInEachFileSystem((os) => { it('should map interpolation with pipe', () => { const mappings = compileAndMap('
{{200.3 | percent : 2 }}
'); - expect(mappings).toContain( + expectMapping( + mappings, {source: '
', generated: 'i0.ɵɵelementStart(0, "div")', sourceUrl: '../test.ts'}); - expect(mappings).toContain({ + expectMapping(mappings, { source: '{{200.3 | percent : 2 }}', generated: 'i0.ɵɵtextInterpolate(i0.ɵɵpipeBind2(2, 1, 200.3, 2))', sourceUrl: '../test.ts' }); - expect(mappings).toContain( + expectMapping( + mappings, {source: '
', generated: 'i0.ɵɵelementEnd()', sourceUrl: '../test.ts'}); }); }); @@ -107,12 +113,12 @@ runInEachFileSystem((os) => { describe('(property bindings)', () => { it('should map a simple input binding expression', () => { const mappings = compileAndMap('
'); - expect(mappings).toContain({ + expectMapping(mappings, { source: '
', generated: 'i0.ɵɵelement(0, "div", 0)', sourceUrl: '../test.ts' }); - expect(mappings).toContain({ + expectMapping(mappings, { source: '[attr]="name"', generated: 'i0.ɵɵproperty("attr", ctx.name)', sourceUrl: '../test.ts' @@ -122,12 +128,12 @@ runInEachFileSystem((os) => { it('should map a complex input binding expression', () => { const mappings = compileAndMap('
'); - expect(mappings).toContain({ + expectMapping(mappings, { source: '
', generated: 'i0.ɵɵelement(0, "div", 0)', sourceUrl: '../test.ts' }); - expect(mappings).toContain({ + expectMapping(mappings, { source: '[attr]="greeting + name"', generated: 'i0.ɵɵproperty("attr", ctx.greeting + ctx.name)', sourceUrl: '../test.ts' @@ -136,12 +142,12 @@ runInEachFileSystem((os) => { it('should map a longhand input binding expression', () => { const mappings = compileAndMap('
'); - expect(mappings).toContain({ + expectMapping(mappings, { source: '
', generated: 'i0.ɵɵelement(0, "div", 0)', sourceUrl: '../test.ts' }); - expect(mappings).toContain({ + expectMapping(mappings, { source: 'bind-attr="name"', generated: 'i0.ɵɵproperty("attr", ctx.name)', sourceUrl: '../test.ts' @@ -150,72 +156,80 @@ runInEachFileSystem((os) => { it('should map a simple output binding expression', () => { const mappings = compileAndMap(''); - expect(mappings).toContain({ + expectMapping(mappings, { source: '', generated: 'i0.ɵɵelementEnd()', sourceUrl: '../test.ts'}); }); it('should map a complex output binding expression', () => { const mappings = compileAndMap( ``); - expect(mappings).toContain({ + expectMapping(mappings, { source: `', generated: 'i0.ɵɵelementEnd()', sourceUrl: '../test.ts'}); }); it('should map a longhand output binding expression', () => { const mappings = compileAndMap(''); - expect(mappings).toContain({ + expectMapping(mappings, { source: '', generated: 'i0.ɵɵelementEnd()', sourceUrl: '../test.ts'}); }); it('should map a two-way binding expression', () => { const mappings = compileAndMap('Name: '); - expect(mappings).toContain({ + expectMapping(mappings, { source: '', generated: 'i0.ɵɵelementStart(1, "input", 0)', sourceUrl: '../test.ts' }); // TODO: improve mappings here - expect(mappings).toContain({ + expectMapping(mappings, { source: '[(ngModel)]="name"', generated: 'i0.ɵɵlistener("ngModelChange", function TestCmp_Template_input_ngModelChange_1_listener($event) { return ctx.name = $event; })', sourceUrl: '../test.ts' }); - expect(mappings).toContain({ + expectMapping(mappings, { source: '', generated: 'i0.ɵɵelementEnd()', sourceUrl: '../test.ts' @@ -224,19 +238,19 @@ runInEachFileSystem((os) => { it('should map a longhand two-way binding expression', () => { const mappings = compileAndMap('Name: '); - expect(mappings).toContain({ + expectMapping(mappings, { source: '', generated: 'i0.ɵɵelementStart(1, "input", 0)', sourceUrl: '../test.ts' }); // TODO: improve mappings here - expect(mappings).toContain({ + expectMapping(mappings, { source: 'bindon-ngModel="name"', generated: 'i0.ɵɵlistener("ngModelChange", function TestCmp_Template_input_ngModelChange_1_listener($event) { return ctx.name = $event; })', sourceUrl: '../test.ts' }); - expect(mappings).toContain({ + expectMapping(mappings, { source: '', generated: 'i0.ɵɵelementEnd()', sourceUrl: '../test.ts' @@ -245,7 +259,7 @@ runInEachFileSystem((os) => { it('should map a class input binding', () => { const mappings = compileAndMap('
Message
'); - expect(mappings).toContain({ + expectMapping(mappings, { source: '
', generated: 'i0.ɵɵelementStart(0, "div")', sourceUrl: '../test.ts' @@ -253,10 +267,12 @@ runInEachFileSystem((os) => { // TODO: Add better mappings for binding - expect(mappings).toContain( + expectMapping( + mappings, {source: 'Message', generated: 'i0.ɵɵtext(1, "Message")', sourceUrl: '../test.ts'}); - expect(mappings).toContain( + expectMapping( + mappings, {source: '
', generated: 'i0.ɵɵelementEnd()', sourceUrl: '../test.ts'}); }); }); @@ -265,7 +281,7 @@ runInEachFileSystem((os) => { it('should map *ngIf scenario', () => { const mappings = compileAndMap('
{{ name }}
'); - expect(mappings).toContain({ + expectMapping(mappings, { source: '
', generated: 'i0.ɵɵelementStart(0, "div")', sourceUrl: '../test.ts' @@ -273,12 +289,13 @@ runInEachFileSystem((os) => { // TODO - map the bindings better - expect(mappings).toContain( + expectMapping( + mappings, {source: '
', generated: 'i0.ɵɵelementEnd()', sourceUrl: '../test.ts'}); // TODO: the `ctx_r...` appears to be dependent upon previous tests!!! - // expect(mappings).toContain({ + // expectMapping(mappings, { // source: '{{ name }}', // generated: 'i0.ɵɵtextInterpolate(ctx_r0.name)', // sourceUrl: '../test.ts' @@ -292,17 +309,19 @@ runInEachFileSystem((os) => { `
\n` + ``); - expect(mappings).toContain( + expectMapping( + mappings, {source: '
', generated: 'i0.ɵɵelementStart(0, "div")', sourceUrl: '../test.ts'}); // TODO - map the bindings better - expect(mappings).toContain( + expectMapping( + mappings, {source: '
', generated: 'i0.ɵɵelementEnd()', sourceUrl: '../test.ts'}); // TODO: the `ctx_r...` appears to be dependent upon previous tests!!! - // expect(mappings).toContain({ + // expectMapping(mappings, { // source: '{{ name }}', // generated: 'i0.ɵɵtextInterpolate(ctx_r0.name)', // sourceUrl: '../test.ts' @@ -313,7 +332,7 @@ runInEachFileSystem((os) => { const mappings = compileAndMap( '
{{ item }}
'); - expect(mappings).toContain({ + expectMapping(mappings, { source: '
', generated: 'i0.ɵɵelementStart(0, "div")', sourceUrl: '../test.ts' @@ -321,7 +340,8 @@ runInEachFileSystem((os) => { // TODO - map the bindings better - expect(mappings).toContain( + expectMapping( + mappings, {source: '
', generated: 'i0.ɵɵelementEnd()', sourceUrl: '../test.ts'}); }); @@ -339,23 +359,26 @@ runInEachFileSystem((os) => { `

\n` + `
`); - expect(mappings).toContain( + expectMapping( + mappings, {source: '

', generated: 'i0.ɵɵelementStart(0, "h3")', sourceUrl: '../test.ts'}); - expect(mappings).toContain({ + expectMapping(mappings, { source: '', generated: 'i0.ɵɵprojection(1)', sourceUrl: '../test.ts' }); - expect(mappings).toContain( - {source: '

', generated: 'i0.ɵɵelementEnd()', sourceUrl: '../test.ts'}); - expect(mappings).toContain( + expectMapping( + mappings, {source: '', generated: 'i0.ɵɵelementEnd()', sourceUrl: '../test.ts'}); + expectMapping( + mappings, {source: '
', generated: 'i0.ɵɵelementStart(2, "div")', sourceUrl: '../test.ts'}); - expect(mappings).toContain({ + expectMapping(mappings, { source: '', generated: 'i0.ɵɵprojection(3, 1)', sourceUrl: '../test.ts' }); - expect(mappings).toContain( + expectMapping( + mappings, {source: '
', generated: 'i0.ɵɵelementEnd()', sourceUrl: '../test.ts'}); }); }); @@ -363,12 +386,12 @@ runInEachFileSystem((os) => { describe('$localize', () => { it('should create simple i18n message source-mapping', () => { const mappings = compileAndMap(`
Hello, World!
`); - expect(mappings).toContain({ + expectMapping(mappings, { source: '
', generated: 'i0.ɵɵelementStart(0, "div")', sourceUrl: '../test.ts', }); - expect(mappings).toContain({ + expectMapping(mappings, { source: 'Hello, World!', generated: '`Hello, World!`', sourceUrl: '../test.ts', @@ -377,27 +400,27 @@ runInEachFileSystem((os) => { it('should create placeholder source-mappings', () => { const mappings = compileAndMap(`
Hello, {{name}}!
`); - expect(mappings).toContain({ + expectMapping(mappings, { source: '
', generated: 'i0.ɵɵelementStart(0, "div")', sourceUrl: '../test.ts', }); - expect(mappings).toContain({ + expectMapping(mappings, { source: '
', generated: 'i0.ɵɵelementEnd()', sourceUrl: '../test.ts', }); - expect(mappings).toContain({ + expectMapping(mappings, { source: 'Hello, ', generated: '`Hello, ${', sourceUrl: '../test.ts', }); - expect(mappings).toContain({ + expectMapping(mappings, { source: '{{name}}', generated: '"\\uFFFD0\\uFFFD"', sourceUrl: '../test.ts', }); - expect(mappings).toContain({ + expectMapping(mappings, { source: '!', generated: '}:INTERPOLATION:!`', sourceUrl: '../test.ts', @@ -406,37 +429,37 @@ runInEachFileSystem((os) => { it('should create tag (container) placeholder source-mappings', () => { const mappings = compileAndMap(`
Hello, World!
`); - expect(mappings).toContain({ + expectMapping(mappings, { source: '
', generated: 'i0.ɵɵelementStart(0, "div")', sourceUrl: '../test.ts', }); - expect(mappings).toContain({ + expectMapping(mappings, { source: '
', generated: 'i0.ɵɵelementEnd()', sourceUrl: '../test.ts', }); - expect(mappings).toContain({ + expectMapping(mappings, { source: 'Hello, ', generated: '`Hello, ${', sourceUrl: '../test.ts', }); - expect(mappings).toContain({ + expectMapping(mappings, { source: '', generated: '"\\uFFFD#2\\uFFFD"', sourceUrl: '../test.ts', }); - expect(mappings).toContain({ + expectMapping(mappings, { source: 'World', generated: '}:START_BOLD_TEXT:World${', sourceUrl: '../test.ts', }); - expect(mappings).toContain({ + expectMapping(mappings, { source: '', generated: '"\\uFFFD/#2\\uFFFD"', sourceUrl: '../test.ts', }); - expect(mappings).toContain({ + expectMapping(mappings, { source: '!', generated: '}:CLOSE_BOLD_TEXT:!`', sourceUrl: '../test.ts', @@ -448,24 +471,26 @@ runInEachFileSystem((os) => { const mappings = compileAndMap('
this is a test
{{ 1 + 2 }}
'); // Creation mode - expect(mappings).toContain( + expectMapping( + mappings, {generated: 'i0.ɵɵelementStart(0, "div")', source: '
', sourceUrl: '../test.ts'}); - expect(mappings).toContain({ + expectMapping(mappings, { generated: 'i0.ɵɵtext(1, "this is a test")', source: 'this is a test', sourceUrl: '../test.ts' }); - expect(mappings).toContain( - {generated: 'i0.ɵɵelementEnd()', source: '
', sourceUrl: '../test.ts'}); - expect(mappings).toContain( + expectMapping( + mappings, {generated: 'i0.ɵɵelementEnd()', source: '
', sourceUrl: '../test.ts'}); + expectMapping( + mappings, {generated: 'i0.ɵɵelementStart(2, "div")', source: '
', sourceUrl: '../test.ts'}); - expect(mappings).toContain( - {generated: 'i0.ɵɵtext(3)', source: '{{ 1 + 2 }}', sourceUrl: '../test.ts'}); - expect(mappings).toContain( - {generated: 'i0.ɵɵelementEnd()', source: '
', sourceUrl: '../test.ts'}); + expectMapping( + mappings, {generated: 'i0.ɵɵtext(3)', source: '{{ 1 + 2 }}', sourceUrl: '../test.ts'}); + expectMapping( + mappings, {generated: 'i0.ɵɵelementEnd()', source: '', sourceUrl: '../test.ts'}); // Update mode - expect(mappings).toContain({ + expectMapping(mappings, { generated: 'i0.ɵɵtextInterpolate(1 + 2)', source: '{{ 1 + 2 }}', sourceUrl: '../test.ts' @@ -476,25 +501,27 @@ runInEachFileSystem((os) => { const mappings = compileAndMap('
this is a test
{{ 1 + 2 }}
'); // Creation mode - expect(mappings).toContain( + expectMapping( + mappings, {generated: 'i0.ɵɵelementStart(0, "div")', source: '
', sourceUrl: '../test.ts'}); - expect(mappings).toContain({ + expectMapping(mappings, { generated: 'i0.ɵɵtext(1, "this is a test")', source: 'this is a test', sourceUrl: '../test.ts' }); - expect(mappings).toContain( - {generated: 'i0.ɵɵelementEnd()', source: '
', sourceUrl: '../test.ts'}); - expect(mappings).toContain( + expectMapping( + mappings, {generated: 'i0.ɵɵelementEnd()', source: '', sourceUrl: '../test.ts'}); + expectMapping( + mappings, {generated: 'i0.ɵɵelementStart(2, "div")', source: '
', sourceUrl: '../test.ts'}); - expect(mappings).toContain( - {generated: 'i0.ɵɵtext(3)', source: '{{ 1 + 2 }}', sourceUrl: '../test.ts'}); - expect(mappings).toContain( - {generated: 'i0.ɵɵelementEnd()', source: '
', sourceUrl: '../test.ts'}); + expectMapping( + mappings, {generated: 'i0.ɵɵtext(3)', source: '{{ 1 + 2 }}', sourceUrl: '../test.ts'}); + expectMapping( + mappings, {generated: 'i0.ɵɵelementEnd()', source: '', sourceUrl: '../test.ts'}); // TODO(benlesh): We need to circle back and prevent the extra parens from being generated. // Update mode - expect(mappings).toContain({ + expectMapping(mappings, { generated: 'i0.ɵɵtextInterpolate(1 + 2)', source: '{{ 1 + 2 }}', sourceUrl: '../test.ts' @@ -506,7 +533,7 @@ runInEachFileSystem((os) => { // Note that the escaped double quotes, which need un-escaping to be parsed correctly. const mappings = compileAndMap('
this is a test
'); - expect(mappings).toContain({ + expectMapping(mappings, { generated: 'i0.ɵɵelementStart(0, "div", 0)', source: '
', sourceUrl: '../test.ts' @@ -525,30 +552,33 @@ runInEachFileSystem((os) => { compileAndMap('
this is a test
{{ 1 + 2 }}
', './dir/test.html'); // Creation mode - expect(mappings).toContain({ + expectMapping(mappings, { generated: 'i0.ɵɵelementStart(0, "div")', source: '
', sourceUrl: '../dir/test.html' }); - expect(mappings).toContain({ + expectMapping(mappings, { generated: 'i0.ɵɵtext(1, "this is a test")', source: 'this is a test', sourceUrl: '../dir/test.html' }); - expect(mappings).toContain( + expectMapping( + mappings, {generated: 'i0.ɵɵelementEnd()', source: '
', sourceUrl: '../dir/test.html'}); - expect(mappings).toContain({ + expectMapping(mappings, { generated: 'i0.ɵɵelementStart(2, "div")', source: '
', sourceUrl: '../dir/test.html' }); - expect(mappings).toContain( + expectMapping( + mappings, {generated: 'i0.ɵɵtext(3)', source: '{{ 1 + 2 }}', sourceUrl: '../dir/test.html'}); - expect(mappings).toContain( + expectMapping( + mappings, {generated: 'i0.ɵɵelementEnd()', source: '
', sourceUrl: '../dir/test.html'}); // Update mode - expect(mappings).toContain({ + expectMapping(mappings, { generated: 'i0.ɵɵtextInterpolate(1 + 2)', source: '{{ 1 + 2 }}', sourceUrl: '../dir/test.html' @@ -560,39 +590,39 @@ runInEachFileSystem((os) => { '
this is a test
{{ 1 + 2 }}
', 'extraRootDir/test.html'); // Creation mode - expect(mappings).toContain({ + expectMapping(mappings, { generated: 'i0.ɵɵelementStart(0, "div")', source: '
', sourceUrl: '../extraRootDir/test.html' }); - expect(mappings).toContain({ + expectMapping(mappings, { generated: 'i0.ɵɵtext(1, "this is a test")', source: 'this is a test', sourceUrl: '../extraRootDir/test.html' }); - expect(mappings).toContain({ + expectMapping(mappings, { generated: 'i0.ɵɵelementEnd()', source: '
', sourceUrl: '../extraRootDir/test.html' }); - expect(mappings).toContain({ + expectMapping(mappings, { generated: 'i0.ɵɵelementStart(2, "div")', source: '
', sourceUrl: '../extraRootDir/test.html' }); - expect(mappings).toContain({ + expectMapping(mappings, { generated: 'i0.ɵɵtext(3)', source: '{{ 1 + 2 }}', sourceUrl: '../extraRootDir/test.html' }); - expect(mappings).toContain({ + expectMapping(mappings, { generated: 'i0.ɵɵelementEnd()', source: '
', sourceUrl: '../extraRootDir/test.html' }); // Update mode - expect(mappings).toContain({ + expectMapping(mappings, { generated: 'i0.ɵɵtextInterpolate(1 + 2)', source: '{{ 1 + 2 }}', sourceUrl: '../extraRootDir/test.html' @@ -638,5 +668,42 @@ runInEachFileSystem((os) => { return value + padding; } } + + function expectMapping(mappings: SegmentMapping[], expected: SegmentMapping): void { + if (mappings.some( + m => m.generated === expected.generated && m.source === expected.source && + m.sourceUrl === expected.sourceUrl)) { + return; + } + const matchingGenerated = mappings.filter(m => m.generated === expected.generated); + const matchingSource = mappings.filter(m => m.source === expected.source); + + const message = [ + 'Expected mappings to contain the following mapping', + prettyPrintMapping(expected), + ]; + if (matchingGenerated.length > 0) { + message.push(''); + message.push('There are the following mappings that match the generated text:'); + matchingGenerated.forEach(m => message.push(prettyPrintMapping(m))); + } + if (matchingSource.length > 0) { + message.push(''); + message.push('There are the following mappings that match the source text:'); + matchingSource.forEach(m => message.push(prettyPrintMapping(m))); + } + + fail(message.join('\n')); + } + + function prettyPrintMapping(mapping: SegmentMapping): string { + return [ + '{', + ` generated: ${JSON.stringify(mapping.generated)}`, + ` source: ${JSON.stringify(mapping.source)}`, + ` sourceUrl: ${JSON.stringify(mapping.sourceUrl)}`, + '}', + ].join('\n'); + } }); });