test(core): update JIT source mapping tests for ivy (#28055)
There are some differences in how ivy maps template source compared to View Engine. In this commit we recreate the View Engine tests for ivy. PR Close #28055
This commit is contained in:
parent
4f46bfb779
commit
e6a00be014
|
@ -28,7 +28,8 @@ import {DomElementSchemaRegistry} from './schema/dom_element_schema_registry';
|
||||||
export class CompilerFacadeImpl implements CompilerFacade {
|
export class CompilerFacadeImpl implements CompilerFacade {
|
||||||
R3ResolvedDependencyType = R3ResolvedDependencyType as any;
|
R3ResolvedDependencyType = R3ResolvedDependencyType as any;
|
||||||
private elementSchemaRegistry = new DomElementSchemaRegistry();
|
private elementSchemaRegistry = new DomElementSchemaRegistry();
|
||||||
private jitEvaluator = new JitEvaluator();
|
|
||||||
|
constructor(private jitEvaluator = new JitEvaluator()) {}
|
||||||
|
|
||||||
compilePipe(angularCoreEnv: CoreEnvironment, sourceMapUrl: string, facade: R3PipeMetadataFacade):
|
compilePipe(angularCoreEnv: CoreEnvironment, sourceMapUrl: string, facade: R3PipeMetadataFacade):
|
||||||
any {
|
any {
|
||||||
|
|
|
@ -46,6 +46,7 @@ ts_library(
|
||||||
"//packages/compiler",
|
"//packages/compiler",
|
||||||
"//packages/compiler/testing",
|
"//packages/compiler/testing",
|
||||||
"//packages/core",
|
"//packages/core",
|
||||||
|
"//packages/core/src/compiler",
|
||||||
"//packages/core/testing",
|
"//packages/core/testing",
|
||||||
"//packages/platform-server",
|
"//packages/platform-server",
|
||||||
"//packages/platform-server/testing",
|
"//packages/platform-server/testing",
|
||||||
|
|
|
@ -6,93 +6,238 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ResourceLoader} from '@angular/compiler';
|
import {ResourceLoader, SourceMap} from '@angular/compiler';
|
||||||
import {SourceMap} from '@angular/compiler/src/output/source_map';
|
import {CompilerFacadeImpl} from '@angular/compiler/src/jit_compiler_facade';
|
||||||
|
import {JitEvaluator} from '@angular/compiler/src/output/output_jit';
|
||||||
|
import {escapeRegExp} from '@angular/compiler/src/util';
|
||||||
import {extractSourceMap, originalPositionFor} from '@angular/compiler/testing/src/output/source_map_util';
|
import {extractSourceMap, originalPositionFor} from '@angular/compiler/testing/src/output/source_map_util';
|
||||||
import {MockResourceLoader} from '@angular/compiler/testing/src/resource_loader_mock';
|
import {MockResourceLoader} from '@angular/compiler/testing/src/resource_loader_mock';
|
||||||
import {Attribute, Component, Directive, ErrorHandler, ɵglobal} from '@angular/core';
|
import {Attribute, Component, Directive, ErrorHandler, ɵglobal} from '@angular/core';
|
||||||
|
import {CompilerFacade, ExportedCompilerFacade} from '@angular/core/src/compiler/compiler_facade';
|
||||||
import {getErrorLogger} from '@angular/core/src/errors';
|
import {getErrorLogger} from '@angular/core/src/errors';
|
||||||
import {ivyEnabled} from '@angular/core/src/ivy_switch';
|
|
||||||
import {resolveComponentResources} from '@angular/core/src/metadata/resource_loading';
|
import {resolveComponentResources} from '@angular/core/src/metadata/resource_loading';
|
||||||
import {TestBed, fakeAsync, tick} from '@angular/core/testing';
|
import {TestBed, fakeAsync, tick} from '@angular/core/testing';
|
||||||
import {fixmeIvy} from '@angular/private/testing';
|
import {fixmeIvy, modifiedInIvy, onlyInIvy} from '@angular/private/testing';
|
||||||
|
|
||||||
{
|
describe('jit source mapping', () => {
|
||||||
describe('jit source mapping', () => {
|
let resourceLoader: MockResourceLoader;
|
||||||
let jitSpy: jasmine.Spy;
|
let jitEvaluator: MockJitEvaluator;
|
||||||
let resourceLoader: MockResourceLoader;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Jasmine relies on methods on `Function.prototype`, so restore the prototype on the spy.
|
resourceLoader = new MockResourceLoader();
|
||||||
// Work around for: https://github.com/jasmine/jasmine/issues/1573
|
jitEvaluator = new MockJitEvaluator();
|
||||||
// TODO: Figure out a better way to retrieve the JIT sources, without spying on `Function`.
|
TestBed.configureCompiler({
|
||||||
const originalProto = ɵglobal.Function.prototype;
|
providers: [
|
||||||
jitSpy = spyOn(ɵglobal, 'Function').and.callThrough();
|
{
|
||||||
ɵglobal.Function.prototype = originalProto;
|
provide: ResourceLoader,
|
||||||
|
useValue: resourceLoader,
|
||||||
resourceLoader = new MockResourceLoader();
|
},
|
||||||
TestBed.configureCompiler({providers: [{provide: ResourceLoader, useValue: resourceLoader}]});
|
{
|
||||||
|
provide: JitEvaluator,
|
||||||
|
useValue: jitEvaluator,
|
||||||
|
}
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function getErrorLoggerStack(e: Error): string {
|
modifiedInIvy('Generated filenames and stack traces have changed in ivy')
|
||||||
let logStack: string = undefined !;
|
.describe('(View Engine)', () => {
|
||||||
getErrorLogger(e)(<any>{error: () => logStack = new Error().stack !}, e.message);
|
describe('inline templates', () => {
|
||||||
return logStack;
|
const ngUrl = 'ng:///DynamicTestModule/MyComp.html';
|
||||||
}
|
function templateDecorator(template: string) { return {template}; }
|
||||||
|
declareTests({ngUrl, templateDecorator});
|
||||||
|
});
|
||||||
|
|
||||||
function getSourceMap(genFile: string): SourceMap {
|
describe('external templates', () => {
|
||||||
const jitSources = jitSpy.calls.all().map((call) => call.args[call.args.length - 1]);
|
const ngUrl = 'ng:///some/url.html';
|
||||||
return jitSources.map(source => extractSourceMap(source))
|
const templateUrl = 'http://localhost:1234/some/url.html';
|
||||||
.find(map => !!(map && map.file === genFile)) !;
|
function templateDecorator(template: string) {
|
||||||
}
|
resourceLoader.expect(templateUrl, template);
|
||||||
|
return {templateUrl};
|
||||||
|
}
|
||||||
|
declareTests({ngUrl, templateDecorator});
|
||||||
|
});
|
||||||
|
|
||||||
function getSourcePositionForStack(stack: string):
|
function declareTests({ngUrl, templateDecorator}: TestConfig) {
|
||||||
{source: string, line: number, column: number} {
|
const ngFactoryUrl = 'ng:///DynamicTestModule/MyComp.ngfactory.js';
|
||||||
const ngFactoryLocations =
|
|
||||||
stack
|
|
||||||
.split('\n')
|
|
||||||
// e.g. at View_MyComp_0 (ng:///DynamicTestModule/MyComp.ngfactory.js:153:40)
|
|
||||||
.map(line => /\((.*\.ngfactory\.js):(\d+):(\d+)/.exec(line))
|
|
||||||
.filter(match => !!match)
|
|
||||||
.map(match => ({
|
|
||||||
file: match ![1],
|
|
||||||
line: parseInt(match ![2], 10),
|
|
||||||
column: parseInt(match ![3], 10)
|
|
||||||
}));
|
|
||||||
const ngFactoryLocation = ngFactoryLocations[0];
|
|
||||||
|
|
||||||
const sourceMap = getSourceMap(ngFactoryLocation.file);
|
it('should use the right source url in html parse errors', fakeAsync(() => {
|
||||||
return originalPositionFor(
|
@Component({...templateDecorator('<div>\n </error>')})
|
||||||
sourceMap, {line: ngFactoryLocation.line, column: ngFactoryLocation.column});
|
class MyComp {
|
||||||
}
|
}
|
||||||
|
|
||||||
function compileAndCreateComponent(comType: any) {
|
expect(() => { compileAndCreateComponent(MyComp); })
|
||||||
TestBed.configureTestingModule({declarations: [comType]});
|
.toThrowError(
|
||||||
|
new RegExp(`Template parse errors[\\s\\S]*${escapeRegExp(ngUrl)}@1:2`));
|
||||||
|
}));
|
||||||
|
|
||||||
let error: any;
|
it('should use the right source url in template parse errors', fakeAsync(() => {
|
||||||
TestBed.compileComponents().catch((e) => error = e);
|
@Component({...templateDecorator('<div>\n <div unknown="{{ctxProp}}"></div>')})
|
||||||
if (resourceLoader.hasPendingRequests()) {
|
class MyComp {
|
||||||
resourceLoader.flush();
|
}
|
||||||
}
|
|
||||||
tick();
|
expect(() => { compileAndCreateComponent(MyComp); })
|
||||||
if (error) {
|
.toThrowError(
|
||||||
throw error;
|
new RegExp(`Template parse errors[\\s\\S]*${escapeRegExp(ngUrl)}@1:7`));
|
||||||
}
|
}));
|
||||||
return TestBed.createComponent(comType);
|
|
||||||
}
|
it('should create a sourceMap for templates', fakeAsync(() => {
|
||||||
|
const template = `Hello World!`;
|
||||||
|
|
||||||
|
@Component({...templateDecorator(template)})
|
||||||
|
class MyComp {
|
||||||
|
}
|
||||||
|
|
||||||
|
compileAndCreateComponent(MyComp);
|
||||||
|
|
||||||
|
const sourceMap = jitEvaluator.getSourceMap(ngFactoryUrl);
|
||||||
|
expect(sourceMap.sources).toEqual([ngFactoryUrl, ngUrl]);
|
||||||
|
expect(sourceMap.sourcesContent).toEqual([' ', template]);
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should report source location for di errors', fakeAsync(() => {
|
||||||
|
const template = `<div>\n <div someDir></div></div>`;
|
||||||
|
|
||||||
|
@Component({...templateDecorator(template)})
|
||||||
|
class MyComp {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Directive({selector: '[someDir]'})
|
||||||
|
class SomeDir {
|
||||||
|
constructor() { throw new Error('Test'); }
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [SomeDir]});
|
||||||
|
let error: any;
|
||||||
|
try {
|
||||||
|
compileAndCreateComponent(MyComp);
|
||||||
|
} catch (e) {
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
// The error should be logged from the element
|
||||||
|
expect(
|
||||||
|
jitEvaluator.getSourcePositionForStack(getErrorLoggerStack(error), ngFactoryUrl))
|
||||||
|
.toEqual({
|
||||||
|
line: 2,
|
||||||
|
column: 4,
|
||||||
|
source: ngUrl,
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should report di errors with multiple elements and directives', fakeAsync(() => {
|
||||||
|
const template = `<div someDir></div><div someDir="throw"></div>`;
|
||||||
|
|
||||||
|
@Component({...templateDecorator(template)})
|
||||||
|
class MyComp {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Directive({selector: '[someDir]'})
|
||||||
|
class SomeDir {
|
||||||
|
constructor(@Attribute('someDir') someDir: string) {
|
||||||
|
if (someDir === 'throw') {
|
||||||
|
throw new Error('Test');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [SomeDir]});
|
||||||
|
let error: any;
|
||||||
|
try {
|
||||||
|
compileAndCreateComponent(MyComp);
|
||||||
|
} catch (e) {
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
// The error should be logged from the 2nd-element
|
||||||
|
expect(
|
||||||
|
jitEvaluator.getSourcePositionForStack(getErrorLoggerStack(error), ngFactoryUrl))
|
||||||
|
.toEqual({
|
||||||
|
line: 1,
|
||||||
|
column: 19,
|
||||||
|
source: ngUrl,
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should report source location for binding errors', fakeAsync(() => {
|
||||||
|
const template = `<div>\n <span [title]="createError()"></span></div>`;
|
||||||
|
|
||||||
|
@Component({...templateDecorator(template)})
|
||||||
|
class MyComp {
|
||||||
|
createError() { throw new Error('Test'); }
|
||||||
|
}
|
||||||
|
|
||||||
|
const comp = compileAndCreateComponent(MyComp);
|
||||||
|
|
||||||
|
let error: any;
|
||||||
|
try {
|
||||||
|
comp.detectChanges();
|
||||||
|
} catch (e) {
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
// the stack should point to the binding
|
||||||
|
expect(jitEvaluator.getSourcePositionForStack(error.stack, ngFactoryUrl)).toEqual({
|
||||||
|
line: 2,
|
||||||
|
column: 12,
|
||||||
|
source: ngUrl,
|
||||||
|
});
|
||||||
|
// The error should be logged from the element
|
||||||
|
expect(
|
||||||
|
jitEvaluator.getSourcePositionForStack(getErrorLoggerStack(error), ngFactoryUrl))
|
||||||
|
.toEqual({
|
||||||
|
line: 2,
|
||||||
|
column: 4,
|
||||||
|
source: ngUrl,
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should report source location for event errors', fakeAsync(() => {
|
||||||
|
const template = `<div>\n <span (click)="createError()"></span></div>`;
|
||||||
|
|
||||||
|
@Component({...templateDecorator(template)})
|
||||||
|
class MyComp {
|
||||||
|
createError() { throw new Error('Test'); }
|
||||||
|
}
|
||||||
|
|
||||||
|
const comp = compileAndCreateComponent(MyComp);
|
||||||
|
|
||||||
|
let error: any;
|
||||||
|
const errorHandler = TestBed.get(ErrorHandler);
|
||||||
|
spyOn(errorHandler, 'handleError').and.callFake((e: any) => error = e);
|
||||||
|
comp.debugElement.children[0].children[0].triggerEventHandler('click', 'EVENT');
|
||||||
|
expect(error).toBeTruthy();
|
||||||
|
// the stack should point to the binding
|
||||||
|
expect(jitEvaluator.getSourcePositionForStack(error.stack, ngFactoryUrl)).toEqual({
|
||||||
|
line: 2,
|
||||||
|
column: 12,
|
||||||
|
source: ngUrl,
|
||||||
|
});
|
||||||
|
// The error should be logged from the element
|
||||||
|
expect(
|
||||||
|
jitEvaluator.getSourcePositionForStack(getErrorLoggerStack(error), ngFactoryUrl))
|
||||||
|
.toEqual({
|
||||||
|
line: 2,
|
||||||
|
column: 4,
|
||||||
|
source: ngUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onlyInIvy('Generated filenames and stack traces have changed in ivy').describe('(Ivy)', () => {
|
||||||
|
|
||||||
|
beforeEach(() => overrideCompilerFacade());
|
||||||
|
afterEach(() => restoreCompilerFacade());
|
||||||
|
|
||||||
describe('inline templates', () => {
|
describe('inline templates', () => {
|
||||||
const ngUrl = 'ng:///DynamicTestModule/MyComp.html';
|
const ngUrl = 'ng:///MyComp/template.html';
|
||||||
|
|
||||||
function templateDecorator(template: string) { return {template}; }
|
function templateDecorator(template: string) { return {template}; }
|
||||||
|
|
||||||
declareTests({ngUrl, templateDecorator});
|
declareTests({ngUrl, templateDecorator});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('external templates', () => {
|
describe('external templates', () => {
|
||||||
const ngUrl = 'ng:///some/url.html';
|
|
||||||
const templateUrl = 'http://localhost:1234/some/url.html';
|
const templateUrl = 'http://localhost:1234/some/url.html';
|
||||||
|
const ngUrl = templateUrl;
|
||||||
function templateDecorator(template: string) {
|
function templateDecorator(template: string) {
|
||||||
resourceLoader.expect(templateUrl, template);
|
resourceLoader.expect(templateUrl, template);
|
||||||
return {templateUrl};
|
return {templateUrl};
|
||||||
|
@ -101,176 +246,251 @@ import {fixmeIvy} from '@angular/private/testing';
|
||||||
declareTests({ngUrl, templateDecorator});
|
declareTests({ngUrl, templateDecorator});
|
||||||
});
|
});
|
||||||
|
|
||||||
function declareTests(
|
function declareTests({ngUrl, templateDecorator}: TestConfig) {
|
||||||
{ngUrl, templateDecorator}:
|
const generatedUrl = 'ng:///MyComp.js';
|
||||||
{ngUrl: string, templateDecorator: (template: string) => { [key: string]: any }}) {
|
|
||||||
fixmeIvy('FW-223: Generate source maps during template compilation')
|
|
||||||
.it('should use the right source url in html parse errors', fakeAsync(() => {
|
|
||||||
@Component({...templateDecorator('<div>\n </error>')})
|
|
||||||
class MyComp {
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(() => {
|
it('should use the right source url in html parse errors', fakeAsync(() => {
|
||||||
ivyEnabled && resolveComponentResources(null !);
|
const template = '<div>\n </error>';
|
||||||
compileAndCreateComponent(MyComp);
|
@Component({...templateDecorator(template)})
|
||||||
})
|
class MyComp {
|
||||||
.toThrowError(new RegExp(
|
}
|
||||||
`Template parse errors[\\s\\S]*${ngUrl.replace('$', '\\$')}@1:2`));
|
|
||||||
}));
|
|
||||||
|
|
||||||
fixmeIvy('FW-223: Generate source maps during template compilation')
|
expect(() => {
|
||||||
|
resolveCompileAndCreateComponent(MyComp, template);
|
||||||
|
}).toThrowError(new RegExp(`${escapeRegExp(ngUrl)}@1:2`));
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
fixmeIvy('FW-511: Report template typing errors')
|
||||||
.it('should use the right source url in template parse errors', fakeAsync(() => {
|
.it('should use the right source url in template parse errors', fakeAsync(() => {
|
||||||
@Component({...templateDecorator('<div>\n <div unknown="{{ctxProp}}"></div>')})
|
const template = '<div>\n <div unknown="{{ctxProp}}"></div>';
|
||||||
class MyComp {
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(() => {
|
|
||||||
ivyEnabled && resolveComponentResources(null !);
|
|
||||||
compileAndCreateComponent(MyComp);
|
|
||||||
})
|
|
||||||
.toThrowError(new RegExp(
|
|
||||||
`Template parse errors[\\s\\S]*${ngUrl.replace('$', '\\$')}@1:7`));
|
|
||||||
}));
|
|
||||||
|
|
||||||
fixmeIvy('FW-223: Generate source maps during template compilation')
|
|
||||||
.it('should create a sourceMap for templates', fakeAsync(() => {
|
|
||||||
const template = `Hello World!`;
|
|
||||||
|
|
||||||
@Component({...templateDecorator(template)})
|
@Component({...templateDecorator(template)})
|
||||||
class MyComp {
|
class MyComp {
|
||||||
}
|
}
|
||||||
|
|
||||||
compileAndCreateComponent(MyComp);
|
expect(() => { resolveCompileAndCreateComponent(MyComp, template); })
|
||||||
|
.toThrowError(
|
||||||
const sourceMap = getSourceMap('ng:///DynamicTestModule/MyComp.ngfactory.js');
|
new RegExp(`Template parse errors[\\s\\S]*${escapeRegExp(ngUrl)}@1:7`));
|
||||||
expect(sourceMap.sources).toEqual([
|
|
||||||
'ng:///DynamicTestModule/MyComp.ngfactory.js', ngUrl
|
|
||||||
]);
|
|
||||||
expect(sourceMap.sourcesContent).toEqual([' ', template]);
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should create a sourceMap for templates', fakeAsync(() => {
|
||||||
|
const template = `Hello World!`;
|
||||||
|
|
||||||
fixmeIvy('FW-223: Generate source maps during template compilation')
|
@Component({...templateDecorator(template)})
|
||||||
.it('should report source location for di errors', fakeAsync(() => {
|
class MyComp {
|
||||||
const template = `<div>\n <div someDir></div></div>`;
|
}
|
||||||
|
|
||||||
@Component({...templateDecorator(template)})
|
resolveCompileAndCreateComponent(MyComp, template);
|
||||||
class MyComp {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Directive({selector: '[someDir]'})
|
const sourceMap = jitEvaluator.getSourceMap(generatedUrl);
|
||||||
class SomeDir {
|
expect(sourceMap.sources).toEqual([generatedUrl, ngUrl]);
|
||||||
constructor() { throw new Error('Test'); }
|
expect(sourceMap.sourcesContent).toEqual([' ', template]);
|
||||||
}
|
}));
|
||||||
|
|
||||||
TestBed.configureTestingModule({declarations: [SomeDir]});
|
|
||||||
let error: any;
|
|
||||||
try {
|
|
||||||
compileAndCreateComponent(MyComp);
|
|
||||||
} catch (e) {
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
// The error should be logged from the element
|
|
||||||
expect(getSourcePositionForStack(getErrorLoggerStack(error))).toEqual({
|
|
||||||
line: 2,
|
|
||||||
column: 4,
|
|
||||||
source: ngUrl,
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
fixmeIvy('FW-223: Generate source maps during template compilation')
|
it('should report source location for di errors', fakeAsync(() => {
|
||||||
.it('should report di errors with multiple elements and directives', fakeAsync(() => {
|
const template = `<div>\n <div someDir></div></div>`;
|
||||||
const template = `<div someDir></div><div someDir="throw"></div>`;
|
|
||||||
|
|
||||||
@Component({...templateDecorator(template)})
|
@Component({...templateDecorator(template)})
|
||||||
class MyComp {
|
class MyComp {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Directive({selector: '[someDir]'})
|
@Directive({selector: '[someDir]'})
|
||||||
class SomeDir {
|
class SomeDir {
|
||||||
constructor(@Attribute('someDir') someDir: string) {
|
constructor() { throw new Error('Test'); }
|
||||||
if (someDir === 'throw') {
|
}
|
||||||
throw new Error('Test');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TestBed.configureTestingModule({declarations: [SomeDir]});
|
TestBed.configureTestingModule({declarations: [SomeDir]});
|
||||||
let error: any;
|
let error: any;
|
||||||
try {
|
try {
|
||||||
compileAndCreateComponent(MyComp);
|
resolveCompileAndCreateComponent(MyComp, template);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
// The error should be logged from the 2nd-element
|
// The error should be logged from the element
|
||||||
expect(getSourcePositionForStack(getErrorLoggerStack(error))).toEqual({
|
expect(jitEvaluator.getSourcePositionForStack(error.stack, generatedUrl)).toEqual({
|
||||||
line: 1,
|
line: 2,
|
||||||
column: 19,
|
column: 4,
|
||||||
source: ngUrl,
|
source: ngUrl,
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
fixmeIvy('FW-223: Generate source maps during template compilation')
|
it('should report di errors with multiple elements and directives', fakeAsync(() => {
|
||||||
.it('should report source location for binding errors', fakeAsync(() => {
|
const template = `<div someDir></div><div someDir="throw"></div>`;
|
||||||
const template = `<div>\n <span [title]="createError()"></span></div>`;
|
|
||||||
|
|
||||||
@Component({...templateDecorator(template)})
|
@Component({...templateDecorator(template)})
|
||||||
class MyComp {
|
class MyComp {
|
||||||
createError() { throw new Error('Test'); }
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const comp = compileAndCreateComponent(MyComp);
|
@Directive({selector: '[someDir]'})
|
||||||
|
class SomeDir {
|
||||||
|
constructor(@Attribute('someDir') someDir: string) {
|
||||||
|
if (someDir === 'throw') {
|
||||||
|
throw new Error('Test');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let error: any;
|
TestBed.configureTestingModule({declarations: [SomeDir]});
|
||||||
try {
|
let error: any;
|
||||||
comp.detectChanges();
|
try {
|
||||||
} catch (e) {
|
resolveCompileAndCreateComponent(MyComp, template);
|
||||||
error = e;
|
} catch (e) {
|
||||||
}
|
error = e;
|
||||||
// the stack should point to the binding
|
}
|
||||||
expect(getSourcePositionForStack(error.stack)).toEqual({
|
// The error should be logged from the 2nd-element
|
||||||
line: 2,
|
expect(jitEvaluator.getSourcePositionForStack(error.stack, generatedUrl)).toEqual({
|
||||||
column: 12,
|
line: 1,
|
||||||
source: ngUrl,
|
column: 19,
|
||||||
});
|
source: ngUrl,
|
||||||
// The error should be logged from the element
|
});
|
||||||
expect(getSourcePositionForStack(getErrorLoggerStack(error))).toEqual({
|
}));
|
||||||
line: 2,
|
|
||||||
column: 4,
|
|
||||||
source: ngUrl,
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
fixmeIvy('FW-223: Generate source maps during template compilation')
|
it('should report source location for binding errors', fakeAsync(() => {
|
||||||
.it('should report source location for event errors', fakeAsync(() => {
|
const template = `<div>\n <span [title]="createError()"></span></div>`;
|
||||||
const template = `<div>\n <span (click)="createError()"></span></div>`;
|
|
||||||
|
|
||||||
@Component({...templateDecorator(template)})
|
@Component({...templateDecorator(template)})
|
||||||
class MyComp {
|
class MyComp {
|
||||||
createError() { throw new Error('Test'); }
|
createError() { throw new Error('Test'); }
|
||||||
}
|
}
|
||||||
|
|
||||||
const comp = compileAndCreateComponent(MyComp);
|
const comp = resolveCompileAndCreateComponent(MyComp, template);
|
||||||
|
|
||||||
let error: any;
|
let error: any;
|
||||||
const errorHandler = TestBed.get(ErrorHandler);
|
try {
|
||||||
spyOn(errorHandler, 'handleError').and.callFake((e: any) => error = e);
|
comp.detectChanges();
|
||||||
comp.debugElement.children[0].children[0].triggerEventHandler('click', 'EVENT');
|
} catch (e) {
|
||||||
expect(error).toBeTruthy();
|
error = e;
|
||||||
// the stack should point to the binding
|
}
|
||||||
expect(getSourcePositionForStack(error.stack)).toEqual({
|
// the stack should point to the binding
|
||||||
line: 2,
|
expect(jitEvaluator.getSourcePositionForStack(error.stack, generatedUrl)).toEqual({
|
||||||
column: 12,
|
line: 2,
|
||||||
source: ngUrl,
|
column: 12,
|
||||||
});
|
source: ngUrl,
|
||||||
// The error should be logged from the element
|
});
|
||||||
expect(getSourcePositionForStack(getErrorLoggerStack(error))).toEqual({
|
}));
|
||||||
line: 2,
|
|
||||||
column: 4,
|
|
||||||
source: ngUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
}));
|
it('should report source location for event errors', fakeAsync(() => {
|
||||||
|
const template = `<div>\n <span (click)="createError()"></span></div>`;
|
||||||
|
|
||||||
|
@Component({...templateDecorator(template)})
|
||||||
|
class MyComp {
|
||||||
|
createError() { throw new Error('Test'); }
|
||||||
|
}
|
||||||
|
|
||||||
|
const comp = resolveCompileAndCreateComponent(MyComp, template);
|
||||||
|
|
||||||
|
let error: any;
|
||||||
|
const errorHandler = TestBed.get(ErrorHandler);
|
||||||
|
spyOn(errorHandler, 'handleError').and.callFake((e: any) => error = e);
|
||||||
|
try {
|
||||||
|
comp.debugElement.children[0].children[0].triggerEventHandler('click', 'EVENT');
|
||||||
|
} catch (e) {
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
expect(error).toBeTruthy();
|
||||||
|
// the stack should point to the binding
|
||||||
|
expect(jitEvaluator.getSourcePositionForStack(error.stack, generatedUrl)).toEqual({
|
||||||
|
line: 2,
|
||||||
|
column: 21,
|
||||||
|
source: ngUrl,
|
||||||
|
});
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
function compileAndCreateComponent(comType: any) {
|
||||||
|
TestBed.configureTestingModule({declarations: [comType]});
|
||||||
|
|
||||||
|
let error: any;
|
||||||
|
TestBed.compileComponents().catch((e) => error = e);
|
||||||
|
if (resourceLoader.hasPendingRequests()) {
|
||||||
|
resourceLoader.flush();
|
||||||
|
}
|
||||||
|
tick();
|
||||||
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
return TestBed.createComponent(comType);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createResolver(contents: string) { return (_url: string) => Promise.resolve(contents); }
|
||||||
|
|
||||||
|
function resolveCompileAndCreateComponent(comType: any, template: string) {
|
||||||
|
resolveComponentResources(createResolver(template));
|
||||||
|
return compileAndCreateComponent(comType);
|
||||||
|
}
|
||||||
|
|
||||||
|
let ɵcompilerFacade: CompilerFacade;
|
||||||
|
function overrideCompilerFacade() {
|
||||||
|
const ng: ExportedCompilerFacade = (global as any).ng;
|
||||||
|
if (ng) {
|
||||||
|
ɵcompilerFacade = ng.ɵcompilerFacade;
|
||||||
|
ng.ɵcompilerFacade = new CompilerFacadeImpl(jitEvaluator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function restoreCompilerFacade() {
|
||||||
|
if (ɵcompilerFacade) {
|
||||||
|
const ng: ExportedCompilerFacade = (global as any).ng;
|
||||||
|
ng.ɵcompilerFacade = ɵcompilerFacade;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TestConfig {
|
||||||
|
ngUrl: string;
|
||||||
|
templateDecorator: (template: string) => { [key: string]: any };
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SourcePos {
|
||||||
|
source: string;
|
||||||
|
line: number;
|
||||||
|
column: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper class that captures the sources that have been JIT compiled.
|
||||||
|
*/
|
||||||
|
class MockJitEvaluator extends JitEvaluator {
|
||||||
|
sources: string[] = [];
|
||||||
|
|
||||||
|
executeFunction(fn: Function, args: any[]) {
|
||||||
|
// Capture the source that has been generated.
|
||||||
|
this.sources.push(fn.toString());
|
||||||
|
// Then execute it anyway.
|
||||||
|
return super.executeFunction(fn, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the source-map for a specified JIT compiled file.
|
||||||
|
* @param genFile the URL of the file whose source-map we want.
|
||||||
|
*/
|
||||||
|
getSourceMap(genFile: string): SourceMap {
|
||||||
|
return this.sources.map(source => extractSourceMap(source))
|
||||||
|
.find(map => !!(map && map.file === genFile)) !;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSourcePositionForStack(stack: string, genFile: string): SourcePos {
|
||||||
|
const urlRegexp = new RegExp(`(${escapeRegExp(genFile)}):(\\d+):(\\d+)`);
|
||||||
|
const pos = stack.split('\n')
|
||||||
|
.map(line => urlRegexp.exec(line))
|
||||||
|
.filter(match => !!match)
|
||||||
|
.map(match => ({
|
||||||
|
file: match ![1],
|
||||||
|
line: parseInt(match ![2], 10),
|
||||||
|
column: parseInt(match ![3], 10)
|
||||||
|
}))
|
||||||
|
.shift();
|
||||||
|
if (!pos) {
|
||||||
|
throw new Error(`${genFile} was not mentioned in this stack:\n${stack}`);
|
||||||
|
}
|
||||||
|
const sourceMap = this.getSourceMap(pos.file);
|
||||||
|
return originalPositionFor(sourceMap, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getErrorLoggerStack(e: Error): string {
|
||||||
|
let logStack: string = undefined !;
|
||||||
|
getErrorLogger(e)(<any>{error: () => logStack = new Error().stack !}, e.message);
|
||||||
|
return logStack;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in New Issue