mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
Change direct deps in bazel targets and import specifiers within files to maintain strict deps requirements ahead of enabling strict deps tests in the repo PR Close #63323
902 lines
31 KiB
TypeScript
902 lines
31 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google LLC All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.dev/license
|
|
*/
|
|
|
|
/// <reference types="node" />
|
|
import {inspect} from 'node:util';
|
|
import ts from 'typescript';
|
|
|
|
import {runInEachFileSystem} from '../../src/ngtsc/file_system/testing';
|
|
import {loadStandardTestFiles} from '../../src/ngtsc/testing';
|
|
|
|
import {NgtscTestEnvironment} from './env';
|
|
import {getMappedSegments, SegmentMapping} from './sourcemap_utils';
|
|
|
|
const testFiles = loadStandardTestFiles();
|
|
|
|
runInEachFileSystem((os) => {
|
|
describe('template source-mapping', () => {
|
|
let env!: NgtscTestEnvironment;
|
|
|
|
beforeEach(() => {
|
|
env = NgtscTestEnvironment.setup(testFiles);
|
|
env.tsconfig({
|
|
sourceMap: true,
|
|
target: ts.ScriptTarget.ES2015,
|
|
enableI18nLegacyMessageIdFormat: false,
|
|
});
|
|
});
|
|
|
|
describe('Inline templates', () => {
|
|
describe('(element creation)', () => {
|
|
it('should map simple element with content', async () => {
|
|
const mappings = await compileAndMap('<h1>Heading 1</h1>');
|
|
expectMapping(mappings, {
|
|
source: '<h1>',
|
|
generated: 'i0.ɵɵelementStart(0, "h1")',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: 'Heading 1',
|
|
generated: 'i0.ɵɵtext(1, "Heading 1")',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: '</h1>',
|
|
generated: 'i0.ɵɵelementEnd()',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
});
|
|
|
|
it('should map void element', async () => {
|
|
const mappings = await compileAndMap('<hr>');
|
|
expectMapping(mappings, {
|
|
source: '<hr>',
|
|
generated: 'i0.ɵɵelement(0, "hr")',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('(interpolations)', () => {
|
|
it('should map a mix of interpolated and static content', async () => {
|
|
const mappings = await compileAndMap('<h3>Hello {{ name }}</h3>');
|
|
expectMapping(mappings, {
|
|
source: '<h3>',
|
|
generated: 'i0.ɵɵelementStart(0, "h3")',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: 'Hello {{ name }}',
|
|
generated: 'i0.ɵɵtextInterpolate1("Hello ", ctx.name)',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: '</h3>',
|
|
generated: 'i0.ɵɵelementEnd()',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
});
|
|
|
|
it('should map a complex interpolated expression', async () => {
|
|
const mappings = await compileAndMap('<h2>{{ greeting + " " + name }}</h2>');
|
|
expectMapping(mappings, {
|
|
source: '<h2>',
|
|
generated: 'i0.ɵɵelementStart(0, "h2")',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: '{{ greeting + " " + name }}',
|
|
generated: 'i0.ɵɵtextInterpolate(ctx.greeting + " " + ctx.name)',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: '</h2>',
|
|
generated: 'i0.ɵɵelementEnd()',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
});
|
|
|
|
it('should map interpolated properties', async () => {
|
|
const mappings = await compileAndMap('<div id="{{name}}"></div>');
|
|
expectMapping(mappings, {
|
|
source: '<div id="{{name}}"></div>',
|
|
generated: 'i0.ɵɵelement(0, "div", 0)',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: 'id="{{name}}"',
|
|
generated: 'i0.ɵɵinterpolate(ctx.name)',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
});
|
|
|
|
it('should map interpolation with pipe', async () => {
|
|
const mappings = await compileAndMap('<div>{{200.3 | percent : 2 }}</div>');
|
|
expectMapping(mappings, {
|
|
source: '<div>',
|
|
generated: 'i0.ɵɵelementStart(0, "div")',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: '{{200.3 | percent : 2 }}',
|
|
generated: 'i0.ɵɵtextInterpolate(i0.ɵɵpipeBind2(2, 1, 200.3, 2))',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: '</div>',
|
|
generated: 'i0.ɵɵelementEnd()',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('(property bindings)', () => {
|
|
it('should map a simple input binding expression', async () => {
|
|
const mappings = await compileAndMap('<div [attr]="name"></div>');
|
|
expectMapping(mappings, {
|
|
source: '<div [attr]="name"></div>',
|
|
generated: 'i0.ɵɵelement(0, "div", 0)',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: '[attr]="name"',
|
|
generated: 'i0.ɵɵproperty("attr", ctx.name)',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
});
|
|
|
|
it('should map a complex input binding expression', async () => {
|
|
const mappings = await compileAndMap('<div [attr]="greeting + name"></div>');
|
|
|
|
expectMapping(mappings, {
|
|
source: '<div [attr]="greeting + name"></div>',
|
|
generated: 'i0.ɵɵelement(0, "div", 0)',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: '[attr]="greeting + name"',
|
|
generated: 'i0.ɵɵproperty("attr", ctx.greeting + ctx.name)',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
});
|
|
|
|
it('should map a longhand input binding expression', async () => {
|
|
const mappings = await compileAndMap('<div bind-attr="name"></div>');
|
|
expectMapping(mappings, {
|
|
source: '<div bind-attr="name"></div>',
|
|
generated: 'i0.ɵɵelement(0, "div", 0)',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: 'bind-attr="name"',
|
|
generated: 'i0.ɵɵproperty("attr", ctx.name)',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
});
|
|
|
|
it('should map a simple output binding expression', async () => {
|
|
const mappings = await compileAndMap('<button (click)="doSomething()">Do it</button>');
|
|
expectMapping(mappings, {
|
|
source: '<button (click)="doSomething()">',
|
|
generated: 'i0.ɵɵelementStart(0, "button", 0)',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: 'Do it',
|
|
generated: 'i0.ɵɵtext(1, "Do it")',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: 'doSomething()',
|
|
generated: 'ctx.doSomething()',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: '</button>',
|
|
generated: 'i0.ɵɵelementEnd()',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
});
|
|
|
|
it('should map a complex output binding expression', async () => {
|
|
const mappings = await compileAndMap(
|
|
`<button (click)="items.push('item' + items.length)">Add Item</button>`,
|
|
);
|
|
expectMapping(mappings, {
|
|
source: `<button (click)="items.push('item' + items.length)">`,
|
|
generated: 'i0.ɵɵelementStart(0, "button", 0)',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: 'Add Item',
|
|
generated: 'i0.ɵɵtext(1, "Add Item")',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: 'items.push(',
|
|
generated: 'ctx.items.push(',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {source: `'item'`, generated: `"item"`, sourceUrl: '../test.ts'});
|
|
expectMapping(mappings, {
|
|
source: ' + items.length)',
|
|
generated: ' + ctx.items.length)',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: '</button>',
|
|
generated: 'i0.ɵɵelementEnd()',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
});
|
|
|
|
it('should map a longhand output binding expression', async () => {
|
|
const mappings = await compileAndMap('<button on-click="doSomething()">Do it</button>');
|
|
expectMapping(mappings, {
|
|
source: '<button on-click="doSomething()">',
|
|
generated: 'i0.ɵɵelementStart(0, "button", 0)',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: 'Do it',
|
|
generated: 'i0.ɵɵtext(1, "Do it")',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: 'doSomething()',
|
|
generated: 'ctx.doSomething()',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: '</button>',
|
|
generated: 'i0.ɵɵelementEnd()',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
});
|
|
|
|
it('should map a two-way binding expression', async () => {
|
|
const mappings = await compileAndMap('Name: <input [(ngModel)]="name">');
|
|
expectMapping(mappings, {
|
|
source: '<input [(ngModel)]="name">',
|
|
generated: 'i0.ɵɵelementStart(1, "input", 0)',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
// TODO: improve mappings here
|
|
expectMapping(mappings, {
|
|
source: '[(ngModel)]="name"',
|
|
generated:
|
|
'i0.ɵɵtwoWayListener("ngModelChange", function TestCmp_Template_input_ngModelChange_1_listener($event) { i0.ɵɵtwoWayBindingSet(ctx.name, $event) || (ctx.name = $event); return $event; })',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: '<input [(ngModel)]="name">',
|
|
generated: 'i0.ɵɵelementEnd()',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
});
|
|
|
|
it('should map a longhand two-way binding expression', async () => {
|
|
const mappings = await compileAndMap('Name: <input bindon-ngModel="name">');
|
|
expectMapping(mappings, {
|
|
source: '<input bindon-ngModel="name">',
|
|
generated: 'i0.ɵɵelementStart(1, "input", 0)',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
// TODO: improve mappings here
|
|
expectMapping(mappings, {
|
|
source: 'bindon-ngModel="name"',
|
|
generated:
|
|
'i0.ɵɵtwoWayListener("ngModelChange", function TestCmp_Template_input_ngModelChange_1_listener($event) { i0.ɵɵtwoWayBindingSet(ctx.name, $event) || (ctx.name = $event); return $event; })',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: '<input bindon-ngModel="name">',
|
|
generated: 'i0.ɵɵelementEnd()',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
});
|
|
|
|
it('should map a class input binding', async () => {
|
|
const mappings = await compileAndMap('<div [class.initial]="isInitial">Message</div>');
|
|
expectMapping(mappings, {
|
|
source: '<div [class.initial]="isInitial">',
|
|
generated: 'i0.ɵɵelementStart(0, "div")',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
|
|
// TODO: Add better mappings for binding
|
|
|
|
expectMapping(mappings, {
|
|
source: 'Message',
|
|
generated: 'i0.ɵɵtext(1, "Message")',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
|
|
expectMapping(mappings, {
|
|
source: '</div>',
|
|
generated: 'i0.ɵɵelementEnd()',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('(structural directives)', () => {
|
|
it('should map *ngIf scenario', async () => {
|
|
const mappings = await compileAndMap('<div *ngIf="showMessage()">{{ name }}</div>');
|
|
|
|
expectMapping(mappings, {
|
|
source: '<div *ngIf="showMessage()">',
|
|
generated: 'i0.ɵɵelementStart(0, "div")',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
|
|
// TODO - map the bindings better
|
|
|
|
expectMapping(mappings, {
|
|
source: '</div>',
|
|
generated: 'i0.ɵɵelementEnd()',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
|
|
// TODO: the `ctx_r...` appears to be dependent upon previous tests!!!
|
|
|
|
// expectMapping(mappings, {
|
|
// source: '{{ name }}',
|
|
// generated: 'i0.ɵɵtextInterpolate(ctx_r0.name)',
|
|
// sourceUrl: '../test.ts'
|
|
// });
|
|
});
|
|
|
|
it('should map ng-template [ngIf] scenario', async () => {
|
|
const mappings = await compileAndMap(
|
|
`<ng-template [ngIf]="showMessage()">\n` +
|
|
` <div>{{ name }}</div>\n` +
|
|
` <hr>\n` +
|
|
`</ng-template>`,
|
|
);
|
|
|
|
expectMapping(mappings, {
|
|
source: '<div>',
|
|
generated: 'i0.ɵɵelementStart(0, "div")',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
|
|
// TODO - map the bindings better
|
|
|
|
expectMapping(mappings, {
|
|
source: '</div>',
|
|
generated: 'i0.ɵɵelementEnd()',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
|
|
// TODO: the `ctx_r...` appears to be dependent upon previous tests!!!
|
|
|
|
// expectMapping(mappings, {
|
|
// source: '{{ name }}',
|
|
// generated: 'i0.ɵɵtextInterpolate(ctx_r0.name)',
|
|
// sourceUrl: '../test.ts'
|
|
// });
|
|
});
|
|
|
|
it('should map *ngFor scenario', async () => {
|
|
const mappings = await compileAndMap(
|
|
'<div *ngFor="let item of items; index as i; trackBy: trackByFn">{{ item }}</div>',
|
|
);
|
|
|
|
expectMapping(mappings, {
|
|
source: '<div *ngFor="let item of items; index as i; trackBy: trackByFn">',
|
|
generated: 'i0.ɵɵelementStart(0, "div")',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
|
|
// TODO - map the bindings better
|
|
|
|
expectMapping(mappings, {
|
|
source: '</div>',
|
|
generated: 'i0.ɵɵelementEnd()',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
});
|
|
|
|
it('should map ng-template [ngFor] scenario', () => {
|
|
const mappings = compileAndMap(
|
|
`<ng-template ngFor [ngForOf]="items" let-item>{{ item }}</ng-template>`,
|
|
);
|
|
|
|
// TODO - map the bindings better
|
|
});
|
|
});
|
|
|
|
describe('(content projection)', () => {
|
|
it('should map default and selected projection', async () => {
|
|
const mappings = await compileAndMap(
|
|
`<h3><ng-content select="title"></ng-content></h3>\n` +
|
|
`<div><ng-content></ng-content></div>`,
|
|
);
|
|
|
|
expectMapping(mappings, {
|
|
source: '<h3>',
|
|
generated: 'i0.ɵɵelementStart(0, "h3")',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: '<ng-content select="title"></ng-content>',
|
|
generated: 'i0.ɵɵprojection(1)',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: '</h3>',
|
|
generated: 'i0.ɵɵelementEnd()',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: '<div>',
|
|
generated: 'i0.ɵɵelementStart(2, "div")',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: '<ng-content></ng-content>',
|
|
generated: 'i0.ɵɵprojection(3, 1)',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: '</div>',
|
|
generated: 'i0.ɵɵelementEnd()',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('$localize', () => {
|
|
it('should create simple i18n message source-mapping', async () => {
|
|
const mappings = await compileAndMap(`<div i18n>Hello, World!</div>`);
|
|
expectMapping(mappings, {
|
|
source: '<div i18n>',
|
|
generated: 'i0.ɵɵelementStart(0, "div")',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: 'Hello, World!',
|
|
generated: '`Hello, World!`',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
});
|
|
|
|
it('should create placeholder source-mappings', async () => {
|
|
const mappings = await compileAndMap(`<div i18n>Hello, {{name}}!</div>`);
|
|
expectMapping(mappings, {
|
|
source: '<div i18n>',
|
|
generated: 'i0.ɵɵelementStart(0, "div")',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: '</div>',
|
|
generated: 'i0.ɵɵelementEnd()',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: 'Hello, ',
|
|
generated: '`Hello, ${',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: '{{name}}',
|
|
generated: '"\\uFFFD0\\uFFFD"',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: '!',
|
|
generated: '}:INTERPOLATION:!`',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
});
|
|
|
|
// TODO: Temporarily disabled because Template Pipeline produces different const indices
|
|
// than TemplateDefinitionBuilder. Re-enable after Template Pipeline is the default, and
|
|
// update the test with the new const index.
|
|
xit('should correctly handle collapsed whitespace in interpolation placeholder source-mappings', async () => {
|
|
const mappings = await compileAndMap(
|
|
`<div i18n title=" pre-title {{name}} post-title" i18n-title> pre-body {{greeting}} post-body</div>`,
|
|
);
|
|
expectMapping(mappings, {
|
|
source: '<div i18n title=" pre-title {{name}} post-title" i18n-title>',
|
|
generated: 'i0.ɵɵelementStart(0, "div", 0)',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: '</div>',
|
|
generated: 'i0.ɵɵelementEnd()',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: ' pre-body ',
|
|
generated: '` pre-body ${',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: '{{greeting}}',
|
|
generated: '"\\uFFFD0\\uFFFD"',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: ' post-body',
|
|
generated: '}:INTERPOLATION: post-body`',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
});
|
|
|
|
it('should correctly handle collapsed whitespace in element placeholder source-mappings', async () => {
|
|
const mappings = await compileAndMap(
|
|
`<div i18n>\n pre-p\n <p>\n in-p\n </p>\n post-p\n</div>`,
|
|
);
|
|
// $localize expressions
|
|
expectMapping(mappings, {
|
|
sourceUrl: '../test.ts',
|
|
source: '\n pre-p\n ',
|
|
generated: '` pre-p ${',
|
|
});
|
|
expectMapping(mappings, {
|
|
sourceUrl: '../test.ts',
|
|
source: '<p>',
|
|
generated: '"\\uFFFD#2\\uFFFD"',
|
|
});
|
|
expectMapping(mappings, {
|
|
sourceUrl: '../test.ts',
|
|
source: '\n in-p\n ',
|
|
generated: '}:START_PARAGRAPH: in-p ${',
|
|
});
|
|
expectMapping(mappings, {
|
|
sourceUrl: '../test.ts',
|
|
source: '</p>',
|
|
generated: '"\\uFFFD/#2\\uFFFD"',
|
|
});
|
|
expectMapping(mappings, {
|
|
sourceUrl: '../test.ts',
|
|
source: '\n post-p\n',
|
|
generated: '}:CLOSE_PARAGRAPH: post-p\n`',
|
|
});
|
|
// ivy instructions
|
|
expectMapping(mappings, {
|
|
sourceUrl: '../test.ts',
|
|
source: '<div i18n>',
|
|
generated: 'i0.ɵɵelementStart(0, "div")',
|
|
});
|
|
expectMapping(mappings, {
|
|
sourceUrl: '../test.ts',
|
|
source: '<div i18n>',
|
|
generated: 'i0.ɵɵi18nStart(1, 0)',
|
|
});
|
|
expectMapping(mappings, {
|
|
sourceUrl: '../test.ts',
|
|
source: '<p>\n in-p\n </p>',
|
|
generated: 'i0.ɵɵelement(2, "p")',
|
|
});
|
|
expectMapping(mappings, {
|
|
sourceUrl: '../test.ts',
|
|
source: '</div>',
|
|
generated: 'i0.ɵɵi18nEnd()',
|
|
});
|
|
expectMapping(mappings, {
|
|
sourceUrl: '../test.ts',
|
|
source: '</div>',
|
|
generated: 'i0.ɵɵelementEnd()',
|
|
});
|
|
});
|
|
|
|
it('should create tag (container) placeholder source-mappings', async () => {
|
|
const mappings = await compileAndMap(`<div i18n>Hello, <b>World</b>!</div>`);
|
|
expectMapping(mappings, {
|
|
source: '<div i18n>',
|
|
generated: 'i0.ɵɵelementStart(0, "div")',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: '</div>',
|
|
generated: 'i0.ɵɵelementEnd()',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: 'Hello, ',
|
|
generated: '`Hello, ${',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: '<b>',
|
|
generated: '"\\uFFFD#2\\uFFFD"',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: 'World',
|
|
generated: '}:START_BOLD_TEXT:World${',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: '</b>',
|
|
generated: '"\\uFFFD/#2\\uFFFD"',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
source: '!',
|
|
generated: '}:CLOSE_BOLD_TEXT:!`',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should create (simple string) inline template source-mapping', async () => {
|
|
const mappings = await compileAndMap('<div>this is a test</div><div>{{ 1 + 2 }}</div>');
|
|
|
|
// Creation mode
|
|
expectMapping(mappings, {
|
|
generated: 'i0.ɵɵelementStart(0, "div")',
|
|
source: '<div>',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
generated: 'i0.ɵɵtext(1, "this is a test")',
|
|
source: 'this is a test',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
generated: 'i0.ɵɵelementEnd()',
|
|
source: '</div>',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
generated: 'i0.ɵɵelementStart(2, "div")',
|
|
source: '<div>',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
generated: 'i0.ɵɵtext(3)',
|
|
source: '{{ 1 + 2 }}',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
expectMapping(mappings, {
|
|
generated: 'i0.ɵɵelementEnd()',
|
|
source: '</div>',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
|
|
// Update mode
|
|
expectMapping(mappings, {
|
|
generated: 'i0.ɵɵtextInterpolate(1 + 2)',
|
|
source: '{{ 1 + 2 }}',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
});
|
|
|
|
it('should create correct inline template source-mapping when the source contains escape sequences', async () => {
|
|
// Note that the escaped double quotes, which need un-escaping to be parsed correctly.
|
|
const mappings = await compileAndMap('<div class=\\"some-class\\">this is a test</div>');
|
|
|
|
expectMapping(mappings, {
|
|
generated: 'i0.ɵɵelementStart(0, "div", 0)',
|
|
source: '<div class=\\"some-class\\">',
|
|
sourceUrl: '../test.ts',
|
|
});
|
|
|
|
const attrsMapping = mappings.find((mapping) =>
|
|
/consts: \[\[1, "some-class"\]\]/.test(mapping.generated),
|
|
);
|
|
expect(attrsMapping).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('External templates (where TS supports source-mapping)', () => {
|
|
it('should create external template source-mapping', async () => {
|
|
const mappings = await compileAndMap(
|
|
'<div>this is a test</div><div>{{ 1 + 2 }}</div>',
|
|
'./dir/test.html',
|
|
);
|
|
|
|
// Creation mode
|
|
expectMapping(mappings, {
|
|
generated: 'i0.ɵɵelementStart(0, "div")',
|
|
source: '<div>',
|
|
sourceUrl: '../dir/test.html',
|
|
});
|
|
expectMapping(mappings, {
|
|
generated: 'i0.ɵɵtext(1, "this is a test")',
|
|
source: 'this is a test',
|
|
sourceUrl: '../dir/test.html',
|
|
});
|
|
expectMapping(mappings, {
|
|
generated: 'i0.ɵɵelementEnd()',
|
|
source: '</div>',
|
|
sourceUrl: '../dir/test.html',
|
|
});
|
|
expectMapping(mappings, {
|
|
generated: 'i0.ɵɵelementStart(2, "div")',
|
|
source: '<div>',
|
|
sourceUrl: '../dir/test.html',
|
|
});
|
|
expectMapping(mappings, {
|
|
generated: 'i0.ɵɵtext(3)',
|
|
source: '{{ 1 + 2 }}',
|
|
sourceUrl: '../dir/test.html',
|
|
});
|
|
expectMapping(mappings, {
|
|
generated: 'i0.ɵɵelementEnd()',
|
|
source: '</div>',
|
|
sourceUrl: '../dir/test.html',
|
|
});
|
|
|
|
// Update mode
|
|
expectMapping(mappings, {
|
|
generated: 'i0.ɵɵtextInterpolate(1 + 2)',
|
|
source: '{{ 1 + 2 }}',
|
|
sourceUrl: '../dir/test.html',
|
|
});
|
|
});
|
|
|
|
it('should create correct mappings when templateUrl is in a different rootDir', async () => {
|
|
const mappings = await compileAndMap(
|
|
'<div>this is a test</div><div>{{ 1 + 2 }}</div>',
|
|
'extraRootDir/test.html',
|
|
);
|
|
|
|
// Creation mode
|
|
expectMapping(mappings, {
|
|
generated: 'i0.ɵɵelementStart(0, "div")',
|
|
source: '<div>',
|
|
sourceUrl: '../extraRootDir/test.html',
|
|
});
|
|
expectMapping(mappings, {
|
|
generated: 'i0.ɵɵtext(1, "this is a test")',
|
|
source: 'this is a test',
|
|
sourceUrl: '../extraRootDir/test.html',
|
|
});
|
|
expectMapping(mappings, {
|
|
generated: 'i0.ɵɵelementEnd()',
|
|
source: '</div>',
|
|
sourceUrl: '../extraRootDir/test.html',
|
|
});
|
|
expectMapping(mappings, {
|
|
generated: 'i0.ɵɵelementStart(2, "div")',
|
|
source: '<div>',
|
|
sourceUrl: '../extraRootDir/test.html',
|
|
});
|
|
expectMapping(mappings, {
|
|
generated: 'i0.ɵɵtext(3)',
|
|
source: '{{ 1 + 2 }}',
|
|
sourceUrl: '../extraRootDir/test.html',
|
|
});
|
|
expectMapping(mappings, {
|
|
generated: 'i0.ɵɵelementEnd()',
|
|
source: '</div>',
|
|
sourceUrl: '../extraRootDir/test.html',
|
|
});
|
|
|
|
// Update mode
|
|
expectMapping(mappings, {
|
|
generated: 'i0.ɵɵtextInterpolate(1 + 2)',
|
|
source: '{{ 1 + 2 }}',
|
|
sourceUrl: '../extraRootDir/test.html',
|
|
});
|
|
});
|
|
});
|
|
|
|
async function compileAndMap(template: string, templateUrl: string | null = null) {
|
|
const templateConfig = templateUrl
|
|
? `templateUrl: '${templateUrl}'`
|
|
: 'template: `' + template.replace(/\`/g, '\\`') + '`';
|
|
env.write(
|
|
'test.ts',
|
|
`
|
|
import {Component, Directive, Input, Output, EventEmitter, Pipe, NgModule} from '@angular/core';
|
|
|
|
@Directive({
|
|
selector: '[ngModel],[attr],[ngModelChange]',
|
|
standalone: false,
|
|
})
|
|
export class AllDirective {
|
|
@Input() ngModel!: any;
|
|
@Output() ngModelChange = new EventEmitter<any>();
|
|
@Input() attr!: any;
|
|
}
|
|
|
|
@Pipe({
|
|
name: 'percent',
|
|
standalone: false,
|
|
})
|
|
export class PercentPipe {
|
|
transform(v: any) {}
|
|
}
|
|
|
|
@Component({
|
|
selector: 'test-cmp',
|
|
${templateConfig},
|
|
standalone: false,
|
|
})
|
|
export class TestCmp {
|
|
name = '';
|
|
isInitial = false;
|
|
doSomething() {}
|
|
items: any[] = [];
|
|
greeting = '';
|
|
}
|
|
|
|
@NgModule({
|
|
declarations: [TestCmp, AllDirective, PercentPipe],
|
|
})
|
|
export class Module {}
|
|
`,
|
|
);
|
|
if (templateUrl) {
|
|
env.write(templateUrl, template);
|
|
}
|
|
env.driveMain();
|
|
return await getMappedSegments(env, 'test.js');
|
|
}
|
|
|
|
/**
|
|
* Helper function for debugging failed mappings.
|
|
* This lays out the segment mappings in the console to make it easier to compare.
|
|
*/
|
|
function dumpMappings(mappings: SegmentMapping[]) {
|
|
mappings.forEach((map) => {
|
|
// tslint:disable-next-line:no-console
|
|
console.log(
|
|
padValue(map.sourceUrl, 20, 0) +
|
|
' : ' +
|
|
padValue(inspect(map.source), 100, 23) +
|
|
' : ' +
|
|
inspect(map.generated),
|
|
);
|
|
});
|
|
function padValue(value: string, max: number, start: number) {
|
|
const padding =
|
|
value.length > max ? '\n' + ' '.repeat(max + start) : ' '.repeat(max - value.length);
|
|
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');
|
|
}
|
|
});
|
|
});
|