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 {
|
||||
R3ResolvedDependencyType = R3ResolvedDependencyType as any;
|
||||
private elementSchemaRegistry = new DomElementSchemaRegistry();
|
||||
private jitEvaluator = new JitEvaluator();
|
||||
|
||||
constructor(private jitEvaluator = new JitEvaluator()) {}
|
||||
|
||||
compilePipe(angularCoreEnv: CoreEnvironment, sourceMapUrl: string, facade: R3PipeMetadataFacade):
|
||||
any {
|
||||
|
|
|
@ -46,6 +46,7 @@ ts_library(
|
|||
"//packages/compiler",
|
||||
"//packages/compiler/testing",
|
||||
"//packages/core",
|
||||
"//packages/core/src/compiler",
|
||||
"//packages/core/testing",
|
||||
"//packages/platform-server",
|
||||
"//packages/platform-server/testing",
|
||||
|
|
|
@ -6,93 +6,238 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ResourceLoader} from '@angular/compiler';
|
||||
import {SourceMap} from '@angular/compiler/src/output/source_map';
|
||||
import {ResourceLoader, SourceMap} from '@angular/compiler';
|
||||
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 {MockResourceLoader} from '@angular/compiler/testing/src/resource_loader_mock';
|
||||
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 {ivyEnabled} from '@angular/core/src/ivy_switch';
|
||||
import {resolveComponentResources} from '@angular/core/src/metadata/resource_loading';
|
||||
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', () => {
|
||||
let jitSpy: jasmine.Spy;
|
||||
let resourceLoader: MockResourceLoader;
|
||||
describe('jit source mapping', () => {
|
||||
let resourceLoader: MockResourceLoader;
|
||||
let jitEvaluator: MockJitEvaluator;
|
||||
|
||||
beforeEach(() => {
|
||||
// Jasmine relies on methods on `Function.prototype`, so restore the prototype on the spy.
|
||||
// Work around for: https://github.com/jasmine/jasmine/issues/1573
|
||||
// TODO: Figure out a better way to retrieve the JIT sources, without spying on `Function`.
|
||||
const originalProto = ɵglobal.Function.prototype;
|
||||
jitSpy = spyOn(ɵglobal, 'Function').and.callThrough();
|
||||
ɵglobal.Function.prototype = originalProto;
|
||||
|
||||
resourceLoader = new MockResourceLoader();
|
||||
TestBed.configureCompiler({providers: [{provide: ResourceLoader, useValue: resourceLoader}]});
|
||||
beforeEach(() => {
|
||||
resourceLoader = new MockResourceLoader();
|
||||
jitEvaluator = new MockJitEvaluator();
|
||||
TestBed.configureCompiler({
|
||||
providers: [
|
||||
{
|
||||
provide: ResourceLoader,
|
||||
useValue: resourceLoader,
|
||||
},
|
||||
{
|
||||
provide: JitEvaluator,
|
||||
useValue: jitEvaluator,
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
function getErrorLoggerStack(e: Error): string {
|
||||
let logStack: string = undefined !;
|
||||
getErrorLogger(e)(<any>{error: () => logStack = new Error().stack !}, e.message);
|
||||
return logStack;
|
||||
}
|
||||
modifiedInIvy('Generated filenames and stack traces have changed in ivy')
|
||||
.describe('(View Engine)', () => {
|
||||
describe('inline templates', () => {
|
||||
const ngUrl = 'ng:///DynamicTestModule/MyComp.html';
|
||||
function templateDecorator(template: string) { return {template}; }
|
||||
declareTests({ngUrl, templateDecorator});
|
||||
});
|
||||
|
||||
function getSourceMap(genFile: string): SourceMap {
|
||||
const jitSources = jitSpy.calls.all().map((call) => call.args[call.args.length - 1]);
|
||||
return jitSources.map(source => extractSourceMap(source))
|
||||
.find(map => !!(map && map.file === genFile)) !;
|
||||
}
|
||||
describe('external templates', () => {
|
||||
const ngUrl = 'ng:///some/url.html';
|
||||
const templateUrl = 'http://localhost:1234/some/url.html';
|
||||
function templateDecorator(template: string) {
|
||||
resourceLoader.expect(templateUrl, template);
|
||||
return {templateUrl};
|
||||
}
|
||||
declareTests({ngUrl, templateDecorator});
|
||||
});
|
||||
|
||||
function getSourcePositionForStack(stack: string):
|
||||
{source: string, line: number, column: number} {
|
||||
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];
|
||||
function declareTests({ngUrl, templateDecorator}: TestConfig) {
|
||||
const ngFactoryUrl = 'ng:///DynamicTestModule/MyComp.ngfactory.js';
|
||||
|
||||
const sourceMap = getSourceMap(ngFactoryLocation.file);
|
||||
return originalPositionFor(
|
||||
sourceMap, {line: ngFactoryLocation.line, column: ngFactoryLocation.column});
|
||||
}
|
||||
it('should use the right source url in html parse errors', fakeAsync(() => {
|
||||
@Component({...templateDecorator('<div>\n </error>')})
|
||||
class MyComp {
|
||||
}
|
||||
|
||||
function compileAndCreateComponent(comType: any) {
|
||||
TestBed.configureTestingModule({declarations: [comType]});
|
||||
expect(() => { compileAndCreateComponent(MyComp); })
|
||||
.toThrowError(
|
||||
new RegExp(`Template parse errors[\\s\\S]*${escapeRegExp(ngUrl)}@1:2`));
|
||||
}));
|
||||
|
||||
let error: any;
|
||||
TestBed.compileComponents().catch((e) => error = e);
|
||||
if (resourceLoader.hasPendingRequests()) {
|
||||
resourceLoader.flush();
|
||||
}
|
||||
tick();
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
return TestBed.createComponent(comType);
|
||||
}
|
||||
it('should use the right source url in template parse errors', fakeAsync(() => {
|
||||
@Component({...templateDecorator('<div>\n <div unknown="{{ctxProp}}"></div>')})
|
||||
class MyComp {
|
||||
}
|
||||
|
||||
expect(() => { compileAndCreateComponent(MyComp); })
|
||||
.toThrowError(
|
||||
new RegExp(`Template parse errors[\\s\\S]*${escapeRegExp(ngUrl)}@1:7`));
|
||||
}));
|
||||
|
||||
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', () => {
|
||||
const ngUrl = 'ng:///DynamicTestModule/MyComp.html';
|
||||
|
||||
const ngUrl = 'ng:///MyComp/template.html';
|
||||
function templateDecorator(template: string) { return {template}; }
|
||||
|
||||
declareTests({ngUrl, templateDecorator});
|
||||
});
|
||||
|
||||
describe('external templates', () => {
|
||||
const ngUrl = 'ng:///some/url.html';
|
||||
const templateUrl = 'http://localhost:1234/some/url.html';
|
||||
|
||||
const ngUrl = templateUrl;
|
||||
function templateDecorator(template: string) {
|
||||
resourceLoader.expect(templateUrl, template);
|
||||
return {templateUrl};
|
||||
|
@ -101,176 +246,251 @@ import {fixmeIvy} from '@angular/private/testing';
|
|||
declareTests({ngUrl, templateDecorator});
|
||||
});
|
||||
|
||||
function declareTests(
|
||||
{ngUrl, templateDecorator}:
|
||||
{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 {
|
||||
}
|
||||
function declareTests({ngUrl, templateDecorator}: TestConfig) {
|
||||
const generatedUrl = 'ng:///MyComp.js';
|
||||
|
||||
expect(() => {
|
||||
ivyEnabled && resolveComponentResources(null !);
|
||||
compileAndCreateComponent(MyComp);
|
||||
})
|
||||
.toThrowError(new RegExp(
|
||||
`Template parse errors[\\s\\S]*${ngUrl.replace('$', '\\$')}@1:2`));
|
||||
}));
|
||||
it('should use the right source url in html parse errors', fakeAsync(() => {
|
||||
const template = '<div>\n </error>';
|
||||
@Component({...templateDecorator(template)})
|
||||
class MyComp {
|
||||
}
|
||||
|
||||
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(() => {
|
||||
@Component({...templateDecorator('<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!`;
|
||||
|
||||
const template = '<div>\n <div unknown="{{ctxProp}}"></div>';
|
||||
@Component({...templateDecorator(template)})
|
||||
class MyComp {
|
||||
}
|
||||
|
||||
compileAndCreateComponent(MyComp);
|
||||
|
||||
const sourceMap = getSourceMap('ng:///DynamicTestModule/MyComp.ngfactory.js');
|
||||
expect(sourceMap.sources).toEqual([
|
||||
'ng:///DynamicTestModule/MyComp.ngfactory.js', ngUrl
|
||||
]);
|
||||
expect(sourceMap.sourcesContent).toEqual([' ', template]);
|
||||
expect(() => { resolveCompileAndCreateComponent(MyComp, template); })
|
||||
.toThrowError(
|
||||
new RegExp(`Template parse errors[\\s\\S]*${escapeRegExp(ngUrl)}@1:7`));
|
||||
}));
|
||||
|
||||
it('should create a sourceMap for templates', fakeAsync(() => {
|
||||
const template = `Hello World!`;
|
||||
|
||||
fixmeIvy('FW-223: Generate source maps during template compilation')
|
||||
.it('should report source location for di errors', fakeAsync(() => {
|
||||
const template = `<div>\n <div someDir></div></div>`;
|
||||
@Component({...templateDecorator(template)})
|
||||
class MyComp {
|
||||
}
|
||||
|
||||
@Component({...templateDecorator(template)})
|
||||
class MyComp {
|
||||
}
|
||||
resolveCompileAndCreateComponent(MyComp, template);
|
||||
|
||||
@Directive({selector: '[someDir]'})
|
||||
class SomeDir {
|
||||
constructor() { throw new Error('Test'); }
|
||||
}
|
||||
const sourceMap = jitEvaluator.getSourceMap(generatedUrl);
|
||||
expect(sourceMap.sources).toEqual([generatedUrl, ngUrl]);
|
||||
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 di errors with multiple elements and directives', fakeAsync(() => {
|
||||
const template = `<div someDir></div><div someDir="throw"></div>`;
|
||||
it('should report source location for di errors', fakeAsync(() => {
|
||||
const template = `<div>\n <div someDir></div></div>`;
|
||||
|
||||
@Component({...templateDecorator(template)})
|
||||
class MyComp {
|
||||
}
|
||||
@Component({...templateDecorator(template)})
|
||||
class MyComp {
|
||||
}
|
||||
|
||||
@Directive({selector: '[someDir]'})
|
||||
class SomeDir {
|
||||
constructor(@Attribute('someDir') someDir: string) {
|
||||
if (someDir === 'throw') {
|
||||
throw new Error('Test');
|
||||
}
|
||||
}
|
||||
}
|
||||
@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 2nd-element
|
||||
expect(getSourcePositionForStack(getErrorLoggerStack(error))).toEqual({
|
||||
line: 1,
|
||||
column: 19,
|
||||
source: ngUrl,
|
||||
});
|
||||
}));
|
||||
TestBed.configureTestingModule({declarations: [SomeDir]});
|
||||
let error: any;
|
||||
try {
|
||||
resolveCompileAndCreateComponent(MyComp, template);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
// The error should be logged from the element
|
||||
expect(jitEvaluator.getSourcePositionForStack(error.stack, generatedUrl)).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(() => {
|
||||
const template = `<div>\n <span [title]="createError()"></span></div>`;
|
||||
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 {
|
||||
createError() { throw new Error('Test'); }
|
||||
}
|
||||
@Component({...templateDecorator(template)})
|
||||
class MyComp {
|
||||
}
|
||||
|
||||
const comp = compileAndCreateComponent(MyComp);
|
||||
@Directive({selector: '[someDir]'})
|
||||
class SomeDir {
|
||||
constructor(@Attribute('someDir') someDir: string) {
|
||||
if (someDir === 'throw') {
|
||||
throw new Error('Test');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let error: any;
|
||||
try {
|
||||
comp.detectChanges();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
// the stack should point to the binding
|
||||
expect(getSourcePositionForStack(error.stack)).toEqual({
|
||||
line: 2,
|
||||
column: 12,
|
||||
source: ngUrl,
|
||||
});
|
||||
// The error should be logged from the element
|
||||
expect(getSourcePositionForStack(getErrorLoggerStack(error))).toEqual({
|
||||
line: 2,
|
||||
column: 4,
|
||||
source: ngUrl,
|
||||
});
|
||||
}));
|
||||
TestBed.configureTestingModule({declarations: [SomeDir]});
|
||||
let error: any;
|
||||
try {
|
||||
resolveCompileAndCreateComponent(MyComp, template);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
// The error should be logged from the 2nd-element
|
||||
expect(jitEvaluator.getSourcePositionForStack(error.stack, generatedUrl)).toEqual({
|
||||
line: 1,
|
||||
column: 19,
|
||||
source: ngUrl,
|
||||
});
|
||||
}));
|
||||
|
||||
fixmeIvy('FW-223: Generate source maps during template compilation')
|
||||
.it('should report source location for event errors', fakeAsync(() => {
|
||||
const template = `<div>\n <span (click)="createError()"></span></div>`;
|
||||
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'); }
|
||||
}
|
||||
@Component({...templateDecorator(template)})
|
||||
class MyComp {
|
||||
createError() { throw new Error('Test'); }
|
||||
}
|
||||
|
||||
const comp = compileAndCreateComponent(MyComp);
|
||||
const comp = resolveCompileAndCreateComponent(MyComp, template);
|
||||
|
||||
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(getSourcePositionForStack(error.stack)).toEqual({
|
||||
line: 2,
|
||||
column: 12,
|
||||
source: ngUrl,
|
||||
});
|
||||
// The error should be logged from the element
|
||||
expect(getSourcePositionForStack(getErrorLoggerStack(error))).toEqual({
|
||||
line: 2,
|
||||
column: 4,
|
||||
source: ngUrl,
|
||||
});
|
||||
let error: any;
|
||||
try {
|
||||
comp.detectChanges();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
// the stack should point to the binding
|
||||
expect(jitEvaluator.getSourcePositionForStack(error.stack, generatedUrl)).toEqual({
|
||||
line: 2,
|
||||
column: 12,
|
||||
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