style(compiler): reformat of codebase with new clang-format version (#36520)
This commit reformats the packages/compiler tree using the new version of clang-format. PR Close #36520
This commit is contained in:
@ -13,7 +13,7 @@ import {extractSourceMap, originalPositionFor} from '@angular/compiler/testing/s
|
||||
import {NodeFlags} from '@angular/core/src/view/index';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {EmittingCompilerHost, MockAotCompilerHost, MockCompilerHost, MockDirectory, MockMetadataBundlerHost, arrayToMockDir, compile, expectNoDiagnostics, isInBazel, settings, setup, toMockFileArray} from './test_util';
|
||||
import {arrayToMockDir, compile, EmittingCompilerHost, expectNoDiagnostics, isInBazel, MockAotCompilerHost, MockCompilerHost, MockDirectory, MockMetadataBundlerHost, settings, setup, toMockFileArray} from './test_util';
|
||||
|
||||
describe('compiler (unbundled Angular)', () => {
|
||||
let angularFiles = setup();
|
||||
@ -53,11 +53,11 @@ describe('compiler (unbundled Angular)', () => {
|
||||
function compileApp(): GeneratedFile {
|
||||
const {genFiles} = compile([rootDir, angularFiles]);
|
||||
return genFiles.find(
|
||||
genFile => genFile.srcFileUrl === componentPath && genFile.genFileUrl.endsWith('.ts')) !;
|
||||
genFile => genFile.srcFileUrl === componentPath && genFile.genFileUrl.endsWith('.ts'))!;
|
||||
}
|
||||
|
||||
function findLineAndColumn(
|
||||
file: string, token: string): {line: number | null, column: number | null} {
|
||||
file: string, token: string): {line: number|null, column: number|null} {
|
||||
const index = file.indexOf(token);
|
||||
if (index === -1) {
|
||||
return {line: null, column: null};
|
||||
@ -84,7 +84,9 @@ describe('compiler (unbundled Angular)', () => {
|
||||
describe('inline templates', () => {
|
||||
const ngUrl = `${componentPath}.AppComponent.html`;
|
||||
|
||||
function templateDecorator(template: string) { return `template: \`${template}\`,`; }
|
||||
function templateDecorator(template: string) {
|
||||
return `template: \`${template}\`,`;
|
||||
}
|
||||
|
||||
declareTests({ngUrl, templateDecorator});
|
||||
});
|
||||
@ -125,7 +127,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||
|
||||
const genFile = compileApp();
|
||||
const genSource = toTypeScript(genFile);
|
||||
const sourceMap = extractSourceMap(genSource) !;
|
||||
const sourceMap = extractSourceMap(genSource)!;
|
||||
expect(sourceMap.file).toEqual(genFile.genFileUrl);
|
||||
|
||||
// Note: the generated file also contains code that is not mapped to
|
||||
@ -146,7 +148,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||
|
||||
const genFile = compileApp();
|
||||
const genSource = toTypeScript(genFile);
|
||||
const sourceMap = extractSourceMap(genSource) !;
|
||||
const sourceMap = extractSourceMap(genSource)!;
|
||||
expect(originalPositionFor(sourceMap, findLineAndColumn(genSource, `'span'`)))
|
||||
.toEqual({line: 2, column: 3, source: ngUrl});
|
||||
});
|
||||
@ -158,7 +160,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||
|
||||
const genFile = compileApp();
|
||||
const genSource = toTypeScript(genFile);
|
||||
const sourceMap = extractSourceMap(genSource) !;
|
||||
const sourceMap = extractSourceMap(genSource)!;
|
||||
expect(originalPositionFor(sourceMap, findLineAndColumn(genSource, `someMethod()`)))
|
||||
.toEqual({line: 2, column: 9, source: ngUrl});
|
||||
});
|
||||
@ -170,7 +172,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||
|
||||
const genFile = compileApp();
|
||||
const genSource = toTypeScript(genFile);
|
||||
const sourceMap = extractSourceMap(genSource) !;
|
||||
const sourceMap = extractSourceMap(genSource)!;
|
||||
expect(originalPositionFor(sourceMap, findLineAndColumn(genSource, `someMethod()`)))
|
||||
.toEqual({line: 2, column: 9, source: ngUrl});
|
||||
});
|
||||
@ -180,7 +182,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||
|
||||
const genFile = compileApp();
|
||||
const genSource = toTypeScript(genFile);
|
||||
const sourceMap = extractSourceMap(genSource) !;
|
||||
const sourceMap = extractSourceMap(genSource)!;
|
||||
expect(originalPositionFor(sourceMap, {line: 1, column: 0}))
|
||||
.toEqual({line: 1, column: 0, source: ngFactoryPath});
|
||||
});
|
||||
@ -205,7 +207,6 @@ describe('compiler (unbundled Angular)', () => {
|
||||
compile([FILES, angularFiles]);
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
`Warning: Can't resolve all parameters for MyService in /app/app.ts: (?). This will become an error in Angular v6.x`);
|
||||
|
||||
});
|
||||
|
||||
it('should error if not all arguments of an @Injectable class can be resolved if strictInjectionParameters is true',
|
||||
@ -279,7 +280,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||
};
|
||||
compile([FILES, angularFiles], {
|
||||
postCompile: program => {
|
||||
const factorySource = program.getSourceFile('/app/app.ngfactory.ts') !;
|
||||
const factorySource = program.getSourceFile('/app/app.ngfactory.ts')!;
|
||||
expect(factorySource.text).not.toContain('\'/app/app.ngfactory\'');
|
||||
}
|
||||
});
|
||||
@ -321,7 +322,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||
const genFilePreamble = '/* Hello world! */';
|
||||
const {genFiles} = compile([FILES, angularFiles]);
|
||||
const genFile =
|
||||
genFiles.find(gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts')) !;
|
||||
genFiles.find(gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts'))!;
|
||||
const genSource = toTypeScript(genFile, genFilePreamble);
|
||||
expect(genSource.startsWith(genFilePreamble)).toBe(true);
|
||||
});
|
||||
@ -445,9 +446,9 @@ describe('compiler (unbundled Angular)', () => {
|
||||
}
|
||||
};
|
||||
const {genFiles} = compile([FILES, angularFiles]);
|
||||
const genFile = genFiles.find(genFile => genFile.srcFileUrl === '/app/app.ts') !;
|
||||
const genFile = genFiles.find(genFile => genFile.srcFileUrl === '/app/app.ts')!;
|
||||
const genSource = toTypeScript(genFile);
|
||||
const createComponentFactoryCall = /ɵccf\([^)]*\)/m.exec(genSource) ![0].replace(/\s*/g, '');
|
||||
const createComponentFactoryCall = /ɵccf\([^)]*\)/m.exec(genSource)![0].replace(/\s*/g, '');
|
||||
// selector
|
||||
expect(createComponentFactoryCall).toContain('my-comp');
|
||||
// inputs
|
||||
@ -476,10 +477,9 @@ describe('compiler (unbundled Angular)', () => {
|
||||
};
|
||||
const {genFiles} = compile([FILES, angularFiles]);
|
||||
const genFile =
|
||||
genFiles.find(gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts')) !;
|
||||
genFiles.find(gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts'))!;
|
||||
const genSource = toTypeScript(genFile);
|
||||
expect(genSource).not.toContain('check(');
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@ -492,7 +492,6 @@ describe('compiler (unbundled Angular)', () => {
|
||||
inheritanceWithSummariesSpecs(() => angularSummaryFiles);
|
||||
|
||||
describe('external symbol re-exports enabled', () => {
|
||||
|
||||
it('should not reexport type symbols mentioned in constructors', () => {
|
||||
const libInput: MockDirectory = {
|
||||
'lib': {
|
||||
@ -520,7 +519,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||
const {genFiles: appGenFiles} = compile(
|
||||
[appInput, libOutDir, angularSummaryFiles],
|
||||
{useSummaries: true, createExternalSymbolFactoryReexports: true});
|
||||
const appNgFactory = appGenFiles.find((f) => f.genFileUrl === '/app/main.ngfactory.ts') !;
|
||||
const appNgFactory = appGenFiles.find((f) => f.genFileUrl === '/app/main.ngfactory.ts')!;
|
||||
const appNgFactoryTs = toTypeScript(appNgFactory);
|
||||
expect(appNgFactoryTs).not.toContain('AType');
|
||||
expect(appNgFactoryTs).toContain('AValue');
|
||||
@ -570,7 +569,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||
const {genFiles: appGenFiles} = compile(
|
||||
[appInput, libOutDir, angularSummaryFiles],
|
||||
{useSummaries: true, createExternalSymbolFactoryReexports: true});
|
||||
const appNgFactory = appGenFiles.find((f) => f.genFileUrl === '/app/main.ngfactory.ts') !;
|
||||
const appNgFactory = appGenFiles.find((f) => f.genFileUrl === '/app/main.ngfactory.ts')!;
|
||||
const appNgFactoryTs = toTypeScript(appNgFactory);
|
||||
|
||||
// metadata of ctor calls is preserved, so we reexport the argument
|
||||
@ -614,7 +613,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||
const {genFiles: appGenFiles} = compile(
|
||||
[appInput, libOutDir, angularSummaryFiles],
|
||||
{useSummaries: true, createExternalSymbolFactoryReexports: true});
|
||||
const appNgFactory = appGenFiles.find((f) => f.genFileUrl === '/app/main.ngfactory.ts') !;
|
||||
const appNgFactory = appGenFiles.find((f) => f.genFileUrl === '/app/main.ngfactory.ts')!;
|
||||
const appNgFactoryTs = toTypeScript(appNgFactory);
|
||||
|
||||
// we don't need to reexport exported symbols via the .ngfactory
|
||||
@ -707,7 +706,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||
compile([libInput, getAngularSummaryFiles()], {useSummaries: true});
|
||||
const {genFiles} =
|
||||
compile([libOutDir, appInput, getAngularSummaryFiles()], {useSummaries: true});
|
||||
const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts') !;
|
||||
const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts')!;
|
||||
const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy;
|
||||
expect(toTypeScript(mainNgFactory))
|
||||
.toContain(`${flags},(null as any),0,i1.Extends,[i2.AParam]`);
|
||||
@ -761,7 +760,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||
const {genFiles} = compile(
|
||||
[lib1OutDir, lib2OutDir, appInput, getAngularSummaryFiles()], {useSummaries: true});
|
||||
|
||||
const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts') !;
|
||||
const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts')!;
|
||||
const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy;
|
||||
const mainNgFactorySource = toTypeScript(mainNgFactory);
|
||||
expect(mainNgFactorySource).toContain(`import * as i2 from '/lib1/base';`);
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import {AotCompiler, AotCompilerHost, AotCompilerOptions, CompileSummaryKind, GeneratedFile, toTypeScript} from '@angular/compiler';
|
||||
|
||||
import {MockDirectory, compile, setup} from './test_util';
|
||||
import {compile, MockDirectory, setup} from './test_util';
|
||||
|
||||
describe('aot summaries for jit', () => {
|
||||
let angularFiles = setup();
|
||||
@ -19,7 +19,7 @@ describe('aot summaries for jit', () => {
|
||||
});
|
||||
|
||||
function compileApp(
|
||||
rootDir: MockDirectory, options: {useSummaries?: boolean}& AotCompilerOptions = {}):
|
||||
rootDir: MockDirectory, options: {useSummaries?: boolean}&AotCompilerOptions = {}):
|
||||
{genFiles: GeneratedFile[], outDir: MockDirectory} {
|
||||
return compile(
|
||||
[rootDir, options.useSummaries ? angularSummaryFiles : angularFiles],
|
||||
@ -42,7 +42,7 @@ describe('aot summaries for jit', () => {
|
||||
const rootDir = {'app': appDir};
|
||||
|
||||
const genFile =
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts') !;
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts')!;
|
||||
const genSource = toTypeScript(genFile);
|
||||
|
||||
expect(genSource).toContain(`import * as i0 from '/app/app.module'`);
|
||||
@ -71,7 +71,7 @@ describe('aot summaries for jit', () => {
|
||||
const rootDir = {'app': appDir};
|
||||
|
||||
const genFile =
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts') !;
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts')!;
|
||||
const genSource = toTypeScript(genFile);
|
||||
|
||||
expect(genSource).toContain(`import * as i0 from '/app/app.module'`);
|
||||
@ -100,7 +100,7 @@ describe('aot summaries for jit', () => {
|
||||
const rootDir = {'app': appDir};
|
||||
|
||||
const genFile =
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts') !;
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts')!;
|
||||
const genSource = toTypeScript(genFile);
|
||||
|
||||
expect(genSource).toContain(`import * as i0 from '/app/app.module'`);
|
||||
@ -126,7 +126,7 @@ describe('aot summaries for jit', () => {
|
||||
const rootDir = {'app': appDir};
|
||||
|
||||
const genFile =
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts') !;
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts')!;
|
||||
const genSource = toTypeScript(genFile);
|
||||
|
||||
expect(genSource).toContain(`import * as i0 from '/app/app.module'`);
|
||||
@ -165,7 +165,7 @@ describe('aot summaries for jit', () => {
|
||||
const rootDir = {'app': appDir};
|
||||
|
||||
const genFile =
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts') !;
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts')!;
|
||||
const genSource = toTypeScript(genFile);
|
||||
|
||||
expect(genSource).toMatch(/useClass:\{\s*reference:i1.MyService/);
|
||||
@ -199,7 +199,7 @@ describe('aot summaries for jit', () => {
|
||||
const rootDir = {'app': appDir};
|
||||
|
||||
const genFile =
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts') !;
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts')!;
|
||||
const genSource = toTypeScript(genFile);
|
||||
|
||||
expect(genSource).toMatch(/useClass:\{\s*reference:i1.MyService/);
|
||||
@ -226,7 +226,7 @@ describe('aot summaries for jit', () => {
|
||||
const rootDir = {'app': appDir};
|
||||
|
||||
const genFile =
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts') !;
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts')!;
|
||||
const genSource = toTypeScript(genFile);
|
||||
|
||||
expect(genSource).toMatch(
|
||||
@ -248,7 +248,7 @@ describe('aot summaries for jit', () => {
|
||||
const rootDir = {'app': appDir};
|
||||
|
||||
const genFile =
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts') !;
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts')!;
|
||||
const genSource = toTypeScript(genFile);
|
||||
|
||||
expect(genSource).toMatch(
|
||||
@ -301,10 +301,9 @@ describe('aot summaries for jit', () => {
|
||||
createExternalSymbolFactoryReexports: true,
|
||||
});
|
||||
|
||||
const lib2ModuleNgSummary =
|
||||
lib2Gen.find(f => f.genFileUrl === '/lib2/module.ngsummary.ts') !;
|
||||
const lib2ModuleNgSummary = lib2Gen.find(f => f.genFileUrl === '/lib2/module.ngsummary.ts')!;
|
||||
const lib2ReexportNgSummary =
|
||||
lib2Gen.find(f => f.genFileUrl === '/lib2/reexport.ngsummary.ts') !;
|
||||
lib2Gen.find(f => f.genFileUrl === '/lib2/reexport.ngsummary.ts')!;
|
||||
|
||||
// ngsummaries should add reexports for imported NgModules from a direct dependency
|
||||
expect(toTypeScript(lib2ModuleNgSummary))
|
||||
@ -336,10 +335,9 @@ describe('aot summaries for jit', () => {
|
||||
useSummaries: true,
|
||||
createExternalSymbolFactoryReexports: true
|
||||
}).genFiles;
|
||||
const lib3ModuleNgSummary =
|
||||
lib3Gen.find(f => f.genFileUrl === '/lib3/module.ngsummary.ts') !;
|
||||
const lib3ModuleNgSummary = lib3Gen.find(f => f.genFileUrl === '/lib3/module.ngsummary.ts')!;
|
||||
const lib3ReexportNgSummary =
|
||||
lib3Gen.find(f => f.genFileUrl === '/lib3/reexport.ngsummary.ts') !;
|
||||
lib3Gen.find(f => f.genFileUrl === '/lib3/reexport.ngsummary.ts')!;
|
||||
|
||||
// ngsummary.ts files should use the reexported values from direct and deep deps
|
||||
const lib3ModuleNgSummarySource = toTypeScript(lib3ModuleNgSummary);
|
||||
@ -398,9 +396,9 @@ describe('aot summaries for jit', () => {
|
||||
|
||||
const {outDir: lib2Out, genFiles: lib2Gen} = compileApp(lib2In, {useSummaries: true});
|
||||
|
||||
const lib2ModuleNgSummary = lib2Gen.find(f => f.genFileUrl === '/lib2/module.ngsummary.ts') !;
|
||||
const lib2ModuleNgSummary = lib2Gen.find(f => f.genFileUrl === '/lib2/module.ngsummary.ts')!;
|
||||
const lib2ReexportNgSummary =
|
||||
lib2Gen.find(f => f.genFileUrl === '/lib2/reexport.ngsummary.ts') !;
|
||||
lib2Gen.find(f => f.genFileUrl === '/lib2/reexport.ngsummary.ts')!;
|
||||
|
||||
// ngsummaries should not add reexports by default for imported NgModules from a direct
|
||||
// dependency
|
||||
@ -435,9 +433,9 @@ describe('aot summaries for jit', () => {
|
||||
};
|
||||
|
||||
const lib3Gen = compileApp(lib3In, {useSummaries: true}).genFiles;
|
||||
const lib3ModuleNgSummary = lib3Gen.find(f => f.genFileUrl === '/lib3/module.ngsummary.ts') !;
|
||||
const lib3ModuleNgSummary = lib3Gen.find(f => f.genFileUrl === '/lib3/module.ngsummary.ts')!;
|
||||
const lib3ReexportNgSummary =
|
||||
lib3Gen.find(f => f.genFileUrl === '/lib3/reexport.ngsummary.ts') !;
|
||||
lib3Gen.find(f => f.genFileUrl === '/lib3/reexport.ngsummary.ts')!;
|
||||
|
||||
// ngsummary.ts files should use the external symbols which are manually re-exported from
|
||||
// "lib2" from their original symbol location. With re-exported external symbols this would
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {MockDirectory, compile, expectNoDiagnostics, setup} from './test_util';
|
||||
import {compile, expectNoDiagnostics, MockDirectory, setup} from './test_util';
|
||||
|
||||
describe('regressions', () => {
|
||||
let angularFiles = setup();
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, StaticSymbolResolverHost, core as compilerCore} from '@angular/compiler';
|
||||
import {core as compilerCore, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, StaticSymbolResolverHost} from '@angular/compiler';
|
||||
import {CollectorOptions, METADATA_VERSION} from '@angular/compiler-cli';
|
||||
|
||||
import {MockStaticSymbolResolverHost, MockSummaryResolver} from './static_symbol_resolver_spec';
|
||||
@ -358,7 +358,7 @@ describe('StaticReflector', () => {
|
||||
it('should record data about the error in the exception', () => {
|
||||
let threw = false;
|
||||
try {
|
||||
const metadata = host.getMetadataFor('/tmp/src/invalid-metadata.ts') !;
|
||||
const metadata = host.getMetadataFor('/tmp/src/invalid-metadata.ts')!;
|
||||
expect(metadata).toBeDefined();
|
||||
const moduleMetadata: any = metadata[0]['metadata'];
|
||||
expect(moduleMetadata).toBeDefined();
|
||||
@ -1334,10 +1334,9 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
|
||||
'decorators': [{
|
||||
'__symbolic': 'call',
|
||||
'expression': {'__symbolic': 'reference', 'name': 'Directive', 'module': '@angular/core'},
|
||||
'arguments': [{
|
||||
'selector': '[ngFor][ngForOf]',
|
||||
'inputs': ['ngForTrackBy', 'ngForOf', 'ngForTemplate']
|
||||
}]
|
||||
'arguments': [
|
||||
{'selector': '[ngFor][ngForOf]', 'inputs': ['ngForTrackBy', 'ngForOf', 'ngForTemplate']}
|
||||
]
|
||||
}],
|
||||
'members': {
|
||||
'__ctor__': [{
|
||||
@ -1345,11 +1344,8 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
|
||||
'parameters': [
|
||||
{'__symbolic': 'reference', 'module': '@angular/core', 'name': 'ViewContainerRef'},
|
||||
{'__symbolic': 'reference', 'module': '@angular/core', 'name': 'TemplateRef'},
|
||||
{'__symbolic': 'reference', 'module': '@angular/core', 'name': 'IterableDiffers'}, {
|
||||
'__symbolic': 'reference',
|
||||
'module': '@angular/core',
|
||||
'name': 'ChangeDetectorRef'
|
||||
}
|
||||
{'__symbolic': 'reference', 'module': '@angular/core', 'name': 'IterableDiffers'},
|
||||
{'__symbolic': 'reference', 'module': '@angular/core', 'name': 'ChangeDetectorRef'}
|
||||
]
|
||||
}]
|
||||
}
|
||||
@ -1387,8 +1383,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
|
||||
'__symbolic': 'property',
|
||||
'decorators': [{
|
||||
'__symbolic': 'call',
|
||||
'expression':
|
||||
{'__symbolic': 'reference', 'name': 'Input', 'module': '@angular/core'}
|
||||
'expression': {'__symbolic': 'reference', 'name': 'Input', 'module': '@angular/core'}
|
||||
}]
|
||||
}],
|
||||
'onMouseOver': [{
|
||||
|
@ -19,7 +19,9 @@ describe('StaticSymbolResolver', () => {
|
||||
let symbolResolver: StaticSymbolResolver;
|
||||
let symbolCache: StaticSymbolCache;
|
||||
|
||||
beforeEach(() => { symbolCache = new StaticSymbolCache(); });
|
||||
beforeEach(() => {
|
||||
symbolCache = new StaticSymbolCache();
|
||||
});
|
||||
|
||||
function init(
|
||||
testData: {[key: string]: any} = DEFAULT_TEST_DATA, summaries: Summary<StaticSymbol>[] = [],
|
||||
@ -36,7 +38,8 @@ describe('StaticSymbolResolver', () => {
|
||||
() => symbolResolver.resolveSymbol(
|
||||
symbolResolver.getSymbolByModule('src/version-error', 'e')))
|
||||
.toThrow(new Error(
|
||||
`Metadata version mismatch for module /tmp/src/version-error.d.ts, found version 100, expected ${METADATA_VERSION}`));
|
||||
`Metadata version mismatch for module /tmp/src/version-error.d.ts, found version 100, expected ${
|
||||
METADATA_VERSION}`));
|
||||
});
|
||||
|
||||
it('should throw an exception for version 2 metadata', () => {
|
||||
@ -159,7 +162,6 @@ describe('StaticSymbolResolver', () => {
|
||||
});
|
||||
|
||||
describe('importAs', () => {
|
||||
|
||||
it('should calculate importAs relationship for non source files without summaries', () => {
|
||||
init(
|
||||
{
|
||||
@ -241,7 +243,6 @@ describe('StaticSymbolResolver', () => {
|
||||
expect(symbolResolver.getImportAs(symbolCache.get('/test2.d.ts', 'a', ['someMember'])))
|
||||
.toBe(symbolCache.get('/test3.d.ts', 'b', ['someMember']));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should replace references by StaticSymbols', () => {
|
||||
@ -345,10 +346,9 @@ describe('StaticSymbolResolver', () => {
|
||||
__symbolic: 'class',
|
||||
arity: 1,
|
||||
members: {
|
||||
__ctor__: [{
|
||||
__symbolic: 'constructor',
|
||||
parameters: [symbolCache.get('/test.d.ts', 'AParam')]
|
||||
}]
|
||||
__ctor__: [
|
||||
{__symbolic: 'constructor', parameters: [symbolCache.get('/test.d.ts', 'AParam')]}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -423,7 +423,6 @@ describe('StaticSymbolResolver', () => {
|
||||
expect(symbol.name).toEqual('One');
|
||||
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
export class MockSummaryResolver implements SummaryResolver<StaticSymbol> {
|
||||
@ -431,9 +430,11 @@ export class MockSummaryResolver implements SummaryResolver<StaticSymbol> {
|
||||
symbol: StaticSymbol,
|
||||
importAs: StaticSymbol
|
||||
}[] = []) {}
|
||||
addSummary(summary: Summary<StaticSymbol>) { this.summaries.push(summary); }
|
||||
addSummary(summary: Summary<StaticSymbol>) {
|
||||
this.summaries.push(summary);
|
||||
}
|
||||
resolveSummary(reference: StaticSymbol): Summary<StaticSymbol> {
|
||||
return this.summaries.find(summary => summary.symbol === reference) !;
|
||||
return this.summaries.find(summary => summary.symbol === reference)!;
|
||||
}
|
||||
getSymbolsOf(filePath: string): StaticSymbol[]|null {
|
||||
const symbols = this.summaries.filter(summary => summary.symbol.filePath === filePath)
|
||||
@ -442,12 +443,20 @@ export class MockSummaryResolver implements SummaryResolver<StaticSymbol> {
|
||||
}
|
||||
getImportAs(symbol: StaticSymbol): StaticSymbol {
|
||||
const entry = this.importAs.find(entry => entry.symbol === symbol);
|
||||
return entry ? entry.importAs : undefined !;
|
||||
return entry ? entry.importAs : undefined!;
|
||||
}
|
||||
getKnownModuleName(fileName: string): string|null {
|
||||
return null;
|
||||
}
|
||||
isLibraryFile(filePath: string): boolean {
|
||||
return filePath.endsWith('.d.ts');
|
||||
}
|
||||
toSummaryFileName(filePath: string): string {
|
||||
return filePath.replace(/(\.d)?\.ts$/, '.d.ts');
|
||||
}
|
||||
fromSummaryFileName(filePath: string): string {
|
||||
return filePath;
|
||||
}
|
||||
getKnownModuleName(fileName: string): string|null { return null; }
|
||||
isLibraryFile(filePath: string): boolean { return filePath.endsWith('.d.ts'); }
|
||||
toSummaryFileName(filePath: string): string { return filePath.replace(/(\.d)?\.ts$/, '.d.ts'); }
|
||||
fromSummaryFileName(filePath: string): string { return filePath; }
|
||||
}
|
||||
|
||||
export class MockStaticSymbolResolverHost implements StaticSymbolResolverHost {
|
||||
@ -459,7 +468,9 @@ export class MockStaticSymbolResolverHost implements StaticSymbolResolverHost {
|
||||
|
||||
// In tests, assume that symbols are not re-exported
|
||||
moduleNameToFileName(modulePath: string, containingFile?: string): string {
|
||||
function splitPath(path: string): string[] { return path.split(/\/|\\/g); }
|
||||
function splitPath(path: string): string[] {
|
||||
return path.split(/\/|\\/g);
|
||||
}
|
||||
|
||||
function resolvePath(pathParts: string[]): string {
|
||||
const result: string[] = [];
|
||||
@ -490,7 +501,7 @@ export class MockStaticSymbolResolverHost implements StaticSymbolResolverHost {
|
||||
}
|
||||
|
||||
if (modulePath.indexOf('.') === 0) {
|
||||
const baseName = pathTo(containingFile !, modulePath);
|
||||
const baseName = pathTo(containingFile!, modulePath);
|
||||
const tsName = baseName + '.ts';
|
||||
if (this._getMetadataFor(tsName)) {
|
||||
return tsName;
|
||||
@ -498,14 +509,18 @@ export class MockStaticSymbolResolverHost implements StaticSymbolResolverHost {
|
||||
return baseName + '.d.ts';
|
||||
}
|
||||
if (modulePath == 'unresolved') {
|
||||
return undefined !;
|
||||
return undefined!;
|
||||
}
|
||||
return '/tmp/' + modulePath + '.d.ts';
|
||||
}
|
||||
|
||||
getMetadataFor(moduleId: string): any { return this._getMetadataFor(moduleId); }
|
||||
getMetadataFor(moduleId: string): any {
|
||||
return this._getMetadataFor(moduleId);
|
||||
}
|
||||
|
||||
getOutputName(filePath: string): string { return filePath; }
|
||||
getOutputName(filePath: string): string {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
private _getMetadataFor(filePath: string): any {
|
||||
if (this.data[filePath] && filePath.match(TS_EXT)) {
|
||||
@ -515,13 +530,13 @@ export class MockStaticSymbolResolverHost implements StaticSymbolResolverHost {
|
||||
filePath, this.data[filePath], ts.ScriptTarget.ES5, /* setParentNodes */ true);
|
||||
const diagnostics: ts.Diagnostic[] = (<any>sf).parseDiagnostics;
|
||||
if (diagnostics && diagnostics.length) {
|
||||
const errors =
|
||||
diagnostics
|
||||
.map(d => {
|
||||
const {line, character} = ts.getLineAndCharacterOfPosition(d.file !, d.start !);
|
||||
return `(${line}:${character}): ${d.messageText}`;
|
||||
})
|
||||
.join('\n');
|
||||
const errors = diagnostics
|
||||
.map(d => {
|
||||
const {line, character} =
|
||||
ts.getLineAndCharacterOfPosition(d.file!, d.start!);
|
||||
return `(${line}:${character}): ${d.messageText}`;
|
||||
})
|
||||
.join('\n');
|
||||
throw Error(`Error encountered during parse of file ${filePath}\n${errors}`);
|
||||
}
|
||||
return [this.collector.getMetadata(sf)];
|
||||
|
@ -23,7 +23,9 @@ const EXT = /(\.d)?\.ts$/;
|
||||
let symbolCache: StaticSymbolCache;
|
||||
let host: MockAotSummaryResolverHost;
|
||||
|
||||
beforeEach(() => { symbolCache = new StaticSymbolCache(); });
|
||||
beforeEach(() => {
|
||||
symbolCache = new StaticSymbolCache();
|
||||
});
|
||||
|
||||
function init(summaries: {[filePath: string]: string} = {}) {
|
||||
host = new MockAotSummaryResolverHost(summaries);
|
||||
@ -121,11 +123,17 @@ export class MockAotSummaryResolverHost implements AotSummaryResolverHost {
|
||||
return sourceFileName.replace(EXT, '') + '.d.ts';
|
||||
}
|
||||
|
||||
fromSummaryFileName(filePath: string): string { return filePath; }
|
||||
fromSummaryFileName(filePath: string): string {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
isSourceFile(filePath: string) { return !filePath.endsWith('.d.ts'); }
|
||||
isSourceFile(filePath: string) {
|
||||
return !filePath.endsWith('.d.ts');
|
||||
}
|
||||
|
||||
loadSummary(filePath: string): string { return this.summaries[filePath]; }
|
||||
loadSummary(filePath: string): string {
|
||||
return this.summaries[filePath];
|
||||
}
|
||||
}
|
||||
|
||||
export function createMockOutputContext(): OutputContext {
|
||||
|
@ -12,7 +12,7 @@ import {deserializeSummaries, serializeSummaries} from '@angular/compiler/src/ao
|
||||
import {summaryFileName} from '@angular/compiler/src/aot/util';
|
||||
|
||||
import {MockStaticSymbolResolverHost} from './static_symbol_resolver_spec';
|
||||
import {MockAotSummaryResolverHost, createMockOutputContext} from './summary_resolver_spec';
|
||||
import {createMockOutputContext, MockAotSummaryResolverHost} from './summary_resolver_spec';
|
||||
|
||||
|
||||
{
|
||||
@ -22,7 +22,9 @@ import {MockAotSummaryResolverHost, createMockOutputContext} from './summary_res
|
||||
let symbolCache: StaticSymbolCache;
|
||||
let host: MockAotSummaryResolverHost;
|
||||
|
||||
beforeEach(() => { symbolCache = new StaticSymbolCache(); });
|
||||
beforeEach(() => {
|
||||
symbolCache = new StaticSymbolCache();
|
||||
});
|
||||
|
||||
function init(
|
||||
summaries: {[filePath: string]: string} = {}, metadata: {[key: string]: any} = {}) {
|
||||
@ -101,7 +103,7 @@ import {MockAotSummaryResolverHost, createMockOutputContext} from './summary_res
|
||||
members: {aMethod: {__symbolic: 'function'}},
|
||||
statics: {aStatic: true}
|
||||
});
|
||||
expect(summaries[1].type !.type.reference)
|
||||
expect(summaries[1].type!.type.reference)
|
||||
.toBe(symbolCache.get('/tmp/some_service.d.ts', 'SomeService'));
|
||||
});
|
||||
|
||||
@ -274,7 +276,7 @@ import {MockAotSummaryResolverHost, createMockOutputContext} from './summary_res
|
||||
'/tmp/external_svc.d.ts', 'SomeService')]);
|
||||
// SomService is a transitive dep, but should have been serialized as well.
|
||||
expect(summaries[2].symbol).toBe(symbolCache.get('/tmp/external_svc.d.ts', 'SomeService'));
|
||||
expect(summaries[2].type !.type.reference)
|
||||
expect(summaries[2].type!.type.reference)
|
||||
.toBe(symbolCache.get('/tmp/external_svc.d.ts', 'SomeService'));
|
||||
// there was no summary for non_summary, but it should have
|
||||
// been serialized as well.
|
||||
@ -387,7 +389,6 @@ import {MockAotSummaryResolverHost, createMockOutputContext} from './summary_res
|
||||
|
||||
|
||||
describe('symbol re-exports enabled', () => {
|
||||
|
||||
it('should not create "importAs" names for ctor arguments which are types of reexported classes in libraries',
|
||||
() => {
|
||||
init();
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AotCompilerHost, AotCompilerOptions, GeneratedFile, createAotCompiler, toTypeScript} from '@angular/compiler';
|
||||
import {AotCompilerHost, AotCompilerOptions, createAotCompiler, GeneratedFile, toTypeScript} from '@angular/compiler';
|
||||
import {MetadataBundlerHost} from '@angular/compiler-cli/src/metadata/bundler';
|
||||
import {MetadataCollector} from '@angular/compiler-cli/src/metadata/collector';
|
||||
import {ModuleMetadata} from '@angular/compiler-cli/src/metadata/index';
|
||||
@ -15,7 +15,9 @@ import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
export interface MetadataProvider { getMetadata(source: ts.SourceFile): ModuleMetadata|undefined; }
|
||||
export interface MetadataProvider {
|
||||
getMetadata(source: ts.SourceFile): ModuleMetadata|undefined;
|
||||
}
|
||||
|
||||
let nodeModulesPath: string;
|
||||
let angularSourcePath: string;
|
||||
@ -23,13 +25,13 @@ let rootPath: string;
|
||||
|
||||
calcPathsOnDisc();
|
||||
|
||||
export type MockFileOrDirectory = string | MockDirectory;
|
||||
export type MockFileOrDirectory = string|MockDirectory;
|
||||
|
||||
export type MockDirectory = {
|
||||
[name: string]: MockFileOrDirectory | undefined;
|
||||
[name: string]: MockFileOrDirectory|undefined;
|
||||
};
|
||||
|
||||
export function isDirectory(data: MockFileOrDirectory | undefined): data is MockDirectory {
|
||||
export function isDirectory(data: MockFileOrDirectory|undefined): data is MockDirectory {
|
||||
return typeof data !== 'string';
|
||||
}
|
||||
|
||||
@ -119,9 +121,13 @@ export class EmittingCompilerHost implements ts.CompilerHost {
|
||||
return Array.from(this.writtenFiles).map(f => ({name: f[0], content: f[1]}));
|
||||
}
|
||||
|
||||
public get scripts(): string[] { return this.scriptNames; }
|
||||
public get scripts(): string[] {
|
||||
return this.scriptNames;
|
||||
}
|
||||
|
||||
public get written(): Map<string, string> { return this.writtenFiles; }
|
||||
public get written(): Map<string, string> {
|
||||
return this.writtenFiles;
|
||||
}
|
||||
|
||||
public effectiveName(fileName: string): string {
|
||||
const prefix = '@angular/';
|
||||
@ -154,7 +160,9 @@ export class EmittingCompilerHost implements ts.CompilerHost {
|
||||
(fs.existsSync(directoryName) && fs.statSync(directoryName).isDirectory());
|
||||
}
|
||||
|
||||
getCurrentDirectory(): string { return this.root; }
|
||||
getCurrentDirectory(): string {
|
||||
return this.root;
|
||||
}
|
||||
|
||||
getDirectories(dir: string): string[] {
|
||||
const result = open(dir, this.options.mockData);
|
||||
@ -179,7 +187,9 @@ export class EmittingCompilerHost implements ts.CompilerHost {
|
||||
throw new Error(`File not found '${fileName}'.`);
|
||||
}
|
||||
|
||||
getDefaultLibFileName(options: ts.CompilerOptions): string { return 'lib.d.ts'; }
|
||||
getDefaultLibFileName(options: ts.CompilerOptions): string {
|
||||
return 'lib.d.ts';
|
||||
}
|
||||
|
||||
writeFile: ts.WriteFileCallback =
|
||||
(fileName: string, data: string, writeByteOrderMark: boolean,
|
||||
@ -197,8 +207,12 @@ export class EmittingCompilerHost implements ts.CompilerHost {
|
||||
getCanonicalFileName(fileName: string): string {
|
||||
return fileName;
|
||||
}
|
||||
useCaseSensitiveFileNames(): boolean { return false; }
|
||||
getNewLine(): string { return '\n'; }
|
||||
useCaseSensitiveFileNames(): boolean {
|
||||
return false;
|
||||
}
|
||||
getNewLine(): string {
|
||||
return '\n';
|
||||
}
|
||||
|
||||
private getAddedDirectories(): Set<string> {
|
||||
let result = this.cachedAddedDirectories;
|
||||
@ -247,7 +261,9 @@ export class MockCompilerHost implements ts.CompilerHost {
|
||||
this.sourceFiles.delete(fileName);
|
||||
}
|
||||
|
||||
assumeFileExists(fileName: string) { this.assumeExists.add(fileName); }
|
||||
assumeFileExists(fileName: string) {
|
||||
this.assumeExists.add(fileName);
|
||||
}
|
||||
|
||||
remove(files: string[]) {
|
||||
// Remove the files from the list of scripts.
|
||||
@ -274,11 +290,17 @@ export class MockCompilerHost implements ts.CompilerHost {
|
||||
return false;
|
||||
}
|
||||
|
||||
readFile(fileName: string): string { return this.getFileContent(fileName) !; }
|
||||
readFile(fileName: string): string {
|
||||
return this.getFileContent(fileName)!;
|
||||
}
|
||||
|
||||
trace(s: string): void { this.traces.push(s); }
|
||||
trace(s: string): void {
|
||||
this.traces.push(s);
|
||||
}
|
||||
|
||||
getCurrentDirectory(): string { return '/'; }
|
||||
getCurrentDirectory(): string {
|
||||
return '/';
|
||||
}
|
||||
|
||||
getDirectories(dir: string): string[] {
|
||||
const effectiveName = this.getEffectiveName(dir);
|
||||
@ -303,10 +325,12 @@ export class MockCompilerHost implements ts.CompilerHost {
|
||||
this.sourceFiles.set(fileName, result);
|
||||
}
|
||||
}
|
||||
return result !;
|
||||
return result!;
|
||||
}
|
||||
|
||||
getDefaultLibFileName(options: ts.CompilerOptions): string { return 'lib.d.ts'; }
|
||||
getDefaultLibFileName(options: ts.CompilerOptions): string {
|
||||
return 'lib.d.ts';
|
||||
}
|
||||
|
||||
writeFile: ts.WriteFileCallback =
|
||||
(fileName: string, data: string, writeByteOrderMark: boolean) => {
|
||||
@ -317,8 +341,12 @@ export class MockCompilerHost implements ts.CompilerHost {
|
||||
getCanonicalFileName(fileName: string): string {
|
||||
return fileName;
|
||||
}
|
||||
useCaseSensitiveFileNames(): boolean { return false; }
|
||||
getNewLine(): string { return '\n'; }
|
||||
useCaseSensitiveFileNames(): boolean {
|
||||
return false;
|
||||
}
|
||||
getNewLine(): string {
|
||||
return '\n';
|
||||
}
|
||||
|
||||
// Private methods
|
||||
private getFileContent(fileName: string): string|undefined {
|
||||
@ -373,9 +401,13 @@ export class MockAotCompilerHost implements AotCompilerHost {
|
||||
};
|
||||
}
|
||||
|
||||
hideMetadata() { this.metadataVisible = false; }
|
||||
hideMetadata() {
|
||||
this.metadataVisible = false;
|
||||
}
|
||||
|
||||
tsFilesOnly() { this.dtsAreSource = false; }
|
||||
tsFilesOnly() {
|
||||
this.dtsAreSource = false;
|
||||
}
|
||||
|
||||
// StaticSymbolResolverHost
|
||||
getMetadataFor(modulePath: string): {[key: string]: any}[]|undefined {
|
||||
@ -414,7 +446,9 @@ export class MockAotCompilerHost implements AotCompilerHost {
|
||||
return resolved ? resolved.resolvedFileName : null;
|
||||
}
|
||||
|
||||
getOutputName(filePath: string) { return filePath; }
|
||||
getOutputName(filePath: string) {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
resourceNameToFileName(resourceName: string, containingFile: string) {
|
||||
// Note: we convert package paths into relative paths to be compatible with the the
|
||||
@ -428,16 +462,22 @@ export class MockAotCompilerHost implements AotCompilerHost {
|
||||
}
|
||||
|
||||
// AotSummaryResolverHost
|
||||
loadSummary(filePath: string): string|null { return this.tsHost.readFile(filePath); }
|
||||
loadSummary(filePath: string): string|null {
|
||||
return this.tsHost.readFile(filePath);
|
||||
}
|
||||
|
||||
isSourceFile(sourceFilePath: string): boolean {
|
||||
return !GENERATED_FILES.test(sourceFilePath) &&
|
||||
(this.dtsAreSource || !DTS.test(sourceFilePath));
|
||||
}
|
||||
|
||||
toSummaryFileName(filePath: string): string { return filePath.replace(EXT, '') + '.d.ts'; }
|
||||
toSummaryFileName(filePath: string): string {
|
||||
return filePath.replace(EXT, '') + '.d.ts';
|
||||
}
|
||||
|
||||
fromSummaryFileName(filePath: string): string { return filePath; }
|
||||
fromSummaryFileName(filePath: string): string {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
// AotCompilerHost
|
||||
fileNameToModuleName(importedFile: string, containingFile: string): string {
|
||||
@ -464,7 +504,7 @@ export class MockMetadataBundlerHost implements MetadataBundlerHost {
|
||||
}
|
||||
}
|
||||
|
||||
function find(fileName: string, data: MockFileOrDirectory | undefined): MockFileOrDirectory|
|
||||
function find(fileName: string, data: MockFileOrDirectory|undefined): MockFileOrDirectory|
|
||||
undefined {
|
||||
if (!data) return undefined;
|
||||
const names = fileName.split('/');
|
||||
@ -479,7 +519,7 @@ function find(fileName: string, data: MockFileOrDirectory | undefined): MockFile
|
||||
return current;
|
||||
}
|
||||
|
||||
function open(fileName: string, data: MockFileOrDirectory | undefined): string|undefined {
|
||||
function open(fileName: string, data: MockFileOrDirectory|undefined): string|undefined {
|
||||
let result = find(fileName, data);
|
||||
if (typeof result === 'string') {
|
||||
return result;
|
||||
@ -487,7 +527,7 @@ function open(fileName: string, data: MockFileOrDirectory | undefined): string|u
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function directoryExists(dirname: string, data: MockFileOrDirectory | undefined): boolean {
|
||||
function directoryExists(dirname: string, data: MockFileOrDirectory|undefined): boolean {
|
||||
let result = find(dirname, data);
|
||||
return !!result && typeof result !== 'string';
|
||||
}
|
||||
@ -497,7 +537,7 @@ export type MockFileArray = {
|
||||
content: string
|
||||
}[];
|
||||
|
||||
export type MockData = MockDirectory | Map<string, string>| (MockDirectory | Map<string, string>)[];
|
||||
export type MockData = MockDirectory|Map<string, string>|(MockDirectory|Map<string, string>)[];
|
||||
|
||||
export function toMockFileArray(data: MockData, target: MockFileArray = []): MockFileArray {
|
||||
if (data instanceof Map) {
|
||||
@ -512,7 +552,7 @@ export function toMockFileArray(data: MockData, target: MockFileArray = []): Moc
|
||||
|
||||
function mockDirToFileArray(dir: MockDirectory, path: string, target: MockFileArray) {
|
||||
Object.keys(dir).forEach((localFileName) => {
|
||||
const value = dir[localFileName] !;
|
||||
const value = dir[localFileName]!;
|
||||
const fileName = `${path}/${localFileName}`;
|
||||
if (typeof value === 'string') {
|
||||
target.push({fileName, content: value});
|
||||
@ -523,12 +563,16 @@ function mockDirToFileArray(dir: MockDirectory, path: string, target: MockFileAr
|
||||
}
|
||||
|
||||
function mapToMockFileArray(files: Map<string, string>, target: MockFileArray) {
|
||||
files.forEach((content, fileName) => { target.push({fileName, content}); });
|
||||
files.forEach((content, fileName) => {
|
||||
target.push({fileName, content});
|
||||
});
|
||||
}
|
||||
|
||||
export function arrayToMockMap(arr: MockFileArray): Map<string, string> {
|
||||
const map = new Map<string, string>();
|
||||
arr.forEach(({fileName, content}) => { map.set(fileName, content); });
|
||||
arr.forEach(({fileName, content}) => {
|
||||
map.set(fileName, content);
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
@ -594,8 +638,8 @@ function readBazelWrittenFilesFrom(
|
||||
map.set(path.posix.join('/node_modules/@angular', packageName, 'index.d.ts'), content);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`Consider adding //packages/${packageName} as a data dependency in the BUILD.bazel rule for the failing test`);
|
||||
console.error(`Consider adding //packages/${
|
||||
packageName} as a data dependency in the BUILD.bazel rule for the failing test`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@ -606,8 +650,8 @@ export function isInBazel(): boolean {
|
||||
|
||||
export function setup(options: {
|
||||
compileAngular: boolean,
|
||||
compileFakeCore?: boolean,
|
||||
compileAnimations: boolean, compileCommon?: boolean
|
||||
compileFakeCore?: boolean, compileAnimations: boolean,
|
||||
compileCommon?: boolean
|
||||
} = {
|
||||
compileAngular: true,
|
||||
compileAnimations: true,
|
||||
@ -687,7 +731,9 @@ export function expectNoDiagnostics(program: ts.Program) {
|
||||
return '';
|
||||
}
|
||||
|
||||
function chars(len: number, ch: string): string { return newArray(len, ch).join(''); }
|
||||
function chars(len: number, ch: string): string {
|
||||
return newArray(len, ch).join('');
|
||||
}
|
||||
|
||||
function lineNoOf(offset: number, text: string): number {
|
||||
let result = 1;
|
||||
@ -699,8 +745,8 @@ export function expectNoDiagnostics(program: ts.Program) {
|
||||
|
||||
function lineInfo(diagnostic: ts.Diagnostic): string {
|
||||
if (diagnostic.file) {
|
||||
const start = diagnostic.start !;
|
||||
let end = diagnostic.start ! + diagnostic.length !;
|
||||
const start = diagnostic.start!;
|
||||
let end = diagnostic.start! + diagnostic.length!;
|
||||
const source = diagnostic.file.text;
|
||||
let lineStart = start;
|
||||
let lineEnd = end;
|
||||
@ -726,8 +772,8 @@ export function expectNoDiagnostics(program: ts.Program) {
|
||||
'Errors from TypeScript:\n' +
|
||||
diagnostics
|
||||
.map(
|
||||
d =>
|
||||
`${fileInfo(d)}${ts.flattenDiagnosticMessageText(d.messageText, '\n')}${lineInfo(d)}`)
|
||||
d => `${fileInfo(d)}${ts.flattenDiagnosticMessageText(d.messageText, '\n')}${
|
||||
lineInfo(d)}`)
|
||||
.join(' \n'));
|
||||
}
|
||||
}
|
||||
@ -758,7 +804,7 @@ export function compile(
|
||||
useSummaries?: boolean,
|
||||
preCompile?: (program: ts.Program) => void,
|
||||
postCompile?: (program: ts.Program) => void,
|
||||
}& AotCompilerOptions = {},
|
||||
}&AotCompilerOptions = {},
|
||||
tsOptions: ts.CompilerOptions = {}): {genFiles: GeneratedFile[], outDir: MockDirectory} {
|
||||
// when using summaries, always emit so the next step can use the results.
|
||||
const emit = options.emit || options.useSummaries;
|
||||
@ -777,7 +823,9 @@ export function compile(
|
||||
const tsSettings = {...settings, ...tsOptions};
|
||||
const program = ts.createProgram([...host.scriptNames], tsSettings, host);
|
||||
preCompile(program);
|
||||
const {compiler, reflector} = createAotCompiler(aotHost, options, (err) => { throw err; });
|
||||
const {compiler, reflector} = createAotCompiler(aotHost, options, (err) => {
|
||||
throw err;
|
||||
});
|
||||
const analyzedModules =
|
||||
compiler.analyzeModulesSync(program.getSourceFiles().map(sf => sf.fileName));
|
||||
const genFiles = compiler.emitAllImpls(analyzedModules);
|
||||
|
@ -26,87 +26,87 @@ import * as compiler from '../src/compiler_facade_interface';
|
||||
*/
|
||||
|
||||
const coreExportedCompilerFacade1: core.ExportedCompilerFacade =
|
||||
null !as compiler.ExportedCompilerFacade;
|
||||
null! as compiler.ExportedCompilerFacade;
|
||||
const compilerExportedCompilerFacade2: compiler.ExportedCompilerFacade =
|
||||
null !as core.ExportedCompilerFacade;
|
||||
null! as core.ExportedCompilerFacade;
|
||||
|
||||
const coreCompilerFacade: core.CompilerFacade = null !as compiler.CompilerFacade;
|
||||
const compilerCompilerFacade: compiler.CompilerFacade = null !as core.CompilerFacade;
|
||||
const coreCompilerFacade: core.CompilerFacade = null! as compiler.CompilerFacade;
|
||||
const compilerCompilerFacade: compiler.CompilerFacade = null! as core.CompilerFacade;
|
||||
|
||||
const coreCoreEnvironment: core.CoreEnvironment = null !as compiler.CoreEnvironment;
|
||||
const compilerCoreEnvironment: compiler.CoreEnvironment = null !as core.CoreEnvironment;
|
||||
const coreCoreEnvironment: core.CoreEnvironment = null! as compiler.CoreEnvironment;
|
||||
const compilerCoreEnvironment: compiler.CoreEnvironment = null! as core.CoreEnvironment;
|
||||
|
||||
const coreResourceLoader: core.ResourceLoader = null !as compiler.ResourceLoader;
|
||||
const compilerResourceLoader: compiler.ResourceLoader = null !as core.ResourceLoader;
|
||||
const coreResourceLoader: core.ResourceLoader = null! as compiler.ResourceLoader;
|
||||
const compilerResourceLoader: compiler.ResourceLoader = null! as core.ResourceLoader;
|
||||
|
||||
const coreStringMap: core.StringMap = null !as compiler.StringMap;
|
||||
const compilerStringMap: compiler.StringMap = null !as core.StringMap;
|
||||
const coreStringMap: core.StringMap = null! as compiler.StringMap;
|
||||
const compilerStringMap: compiler.StringMap = null! as core.StringMap;
|
||||
|
||||
const coreProvider: core.Provider = null !as compiler.Provider;
|
||||
const compilerProvider: compiler.Provider = null !as core.Provider;
|
||||
const coreProvider: core.Provider = null! as compiler.Provider;
|
||||
const compilerProvider: compiler.Provider = null! as core.Provider;
|
||||
|
||||
const coreR3ResolvedDependencyType: core.R3ResolvedDependencyType =
|
||||
null !as compiler.R3ResolvedDependencyType;
|
||||
null! as compiler.R3ResolvedDependencyType;
|
||||
const compilerR3ResolvedDependencyType: compiler.R3ResolvedDependencyType =
|
||||
null !as core.R3ResolvedDependencyType;
|
||||
null! as core.R3ResolvedDependencyType;
|
||||
|
||||
const coreR3ResolvedDependencyType2: R3ResolvedDependencyType =
|
||||
null !as core.R3ResolvedDependencyType;
|
||||
null! as core.R3ResolvedDependencyType;
|
||||
const compilerR3ResolvedDependencyType2: R3ResolvedDependencyType =
|
||||
null !as core.R3ResolvedDependencyType;
|
||||
null! as core.R3ResolvedDependencyType;
|
||||
|
||||
const coreR3ResolvedDependencyType3: core.R3ResolvedDependencyType =
|
||||
null !as R3ResolvedDependencyType;
|
||||
null! as R3ResolvedDependencyType;
|
||||
const compilerR3ResolvedDependencyType3: compiler.R3ResolvedDependencyType =
|
||||
null !as R3ResolvedDependencyType;
|
||||
null! as R3ResolvedDependencyType;
|
||||
|
||||
const coreR3FactoryTarget: core.R3FactoryTarget = null !as compiler.R3FactoryTarget;
|
||||
const compilerR3FactoryTarget: compiler.R3FactoryTarget = null !as core.R3FactoryTarget;
|
||||
const coreR3FactoryTarget: core.R3FactoryTarget = null! as compiler.R3FactoryTarget;
|
||||
const compilerR3FactoryTarget: compiler.R3FactoryTarget = null! as core.R3FactoryTarget;
|
||||
|
||||
const coreR3FactoryTarget2: R3FactoryTarget = null !as core.R3FactoryTarget;
|
||||
const compilerR3FactoryTarget2: R3FactoryTarget = null !as core.R3FactoryTarget;
|
||||
const coreR3FactoryTarget2: R3FactoryTarget = null! as core.R3FactoryTarget;
|
||||
const compilerR3FactoryTarget2: R3FactoryTarget = null! as core.R3FactoryTarget;
|
||||
|
||||
const coreR3FactoryTarget3: core.R3FactoryTarget = null !as R3FactoryTarget;
|
||||
const compilerR3FactoryTarget3: compiler.R3FactoryTarget = null !as R3FactoryTarget;
|
||||
const coreR3FactoryTarget3: core.R3FactoryTarget = null! as R3FactoryTarget;
|
||||
const compilerR3FactoryTarget3: compiler.R3FactoryTarget = null! as R3FactoryTarget;
|
||||
|
||||
const coreR3DependencyMetadataFacade: core.R3DependencyMetadataFacade =
|
||||
null !as compiler.R3DependencyMetadataFacade;
|
||||
null! as compiler.R3DependencyMetadataFacade;
|
||||
const compilerR3DependencyMetadataFacade: compiler.R3DependencyMetadataFacade =
|
||||
null !as core.R3DependencyMetadataFacade;
|
||||
null! as core.R3DependencyMetadataFacade;
|
||||
|
||||
const coreR3PipeMetadataFacade: core.R3PipeMetadataFacade = null !as compiler.R3PipeMetadataFacade;
|
||||
const coreR3PipeMetadataFacade: core.R3PipeMetadataFacade = null! as compiler.R3PipeMetadataFacade;
|
||||
const compilerR3PipeMetadataFacade: compiler.R3PipeMetadataFacade =
|
||||
null !as core.R3PipeMetadataFacade;
|
||||
null! as core.R3PipeMetadataFacade;
|
||||
|
||||
const coreR3InjectableMetadataFacade: core.R3InjectableMetadataFacade =
|
||||
null !as compiler.R3InjectableMetadataFacade;
|
||||
null! as compiler.R3InjectableMetadataFacade;
|
||||
const compilerR3InjectableMetadataFacade: compiler.R3InjectableMetadataFacade =
|
||||
null !as core.R3InjectableMetadataFacade;
|
||||
null! as core.R3InjectableMetadataFacade;
|
||||
|
||||
const coreR3NgModuleMetadataFacade: core.R3NgModuleMetadataFacade =
|
||||
null !as compiler.R3NgModuleMetadataFacade;
|
||||
null! as compiler.R3NgModuleMetadataFacade;
|
||||
const compilerR3NgModuleMetadataFacade: compiler.R3NgModuleMetadataFacade =
|
||||
null !as core.R3NgModuleMetadataFacade;
|
||||
null! as core.R3NgModuleMetadataFacade;
|
||||
|
||||
const coreR3InjectorMetadataFacade: core.R3InjectorMetadataFacade =
|
||||
null !as compiler.R3InjectorMetadataFacade;
|
||||
null! as compiler.R3InjectorMetadataFacade;
|
||||
const compilerR3InjectorMetadataFacade: compiler.R3InjectorMetadataFacade =
|
||||
null !as core.R3InjectorMetadataFacade;
|
||||
null! as core.R3InjectorMetadataFacade;
|
||||
|
||||
const coreR3DirectiveMetadataFacade: core.R3DirectiveMetadataFacade =
|
||||
null !as compiler.R3DirectiveMetadataFacade;
|
||||
null! as compiler.R3DirectiveMetadataFacade;
|
||||
const compilerR3DirectiveMetadataFacade: compiler.R3DirectiveMetadataFacade =
|
||||
null !as core.R3DirectiveMetadataFacade;
|
||||
null! as core.R3DirectiveMetadataFacade;
|
||||
|
||||
const coreR3ComponentMetadataFacade: core.R3ComponentMetadataFacade =
|
||||
null !as compiler.R3ComponentMetadataFacade;
|
||||
null! as compiler.R3ComponentMetadataFacade;
|
||||
const compilerR3ComponentMetadataFacade: compiler.R3ComponentMetadataFacade =
|
||||
null !as core.R3ComponentMetadataFacade;
|
||||
null! as core.R3ComponentMetadataFacade;
|
||||
|
||||
const coreViewEncapsulation: core.ViewEncapsulation = null !as compiler.ViewEncapsulation;
|
||||
const compilerViewEncapsulation: compiler.ViewEncapsulation = null !as core.ViewEncapsulation;
|
||||
const coreViewEncapsulation: core.ViewEncapsulation = null! as compiler.ViewEncapsulation;
|
||||
const compilerViewEncapsulation: compiler.ViewEncapsulation = null! as core.ViewEncapsulation;
|
||||
|
||||
const coreR3QueryMetadataFacade: core.R3QueryMetadataFacade =
|
||||
null !as compiler.R3QueryMetadataFacade;
|
||||
null! as compiler.R3QueryMetadataFacade;
|
||||
const compilerR3QueryMetadataFacade: compiler.R3QueryMetadataFacade =
|
||||
null !as core.R3QueryMetadataFacade;
|
||||
null! as core.R3QueryMetadataFacade;
|
||||
|
@ -19,7 +19,9 @@ import {CompilerConfig, preserveWhitespacesDefault} from '../src/config';
|
||||
|
||||
describe('preserveWhitespacesDefault', () => {
|
||||
it('should return the default `false` setting when no preserveWhitespacesOption are provided',
|
||||
() => { expect(preserveWhitespacesDefault(null)).toEqual(false); });
|
||||
() => {
|
||||
expect(preserveWhitespacesDefault(null)).toEqual(false);
|
||||
});
|
||||
it('should return the preserveWhitespacesOption when provided as a parameter', () => {
|
||||
expect(preserveWhitespacesDefault(true)).toEqual(true);
|
||||
expect(preserveWhitespacesDefault(false)).toEqual(false);
|
||||
|
@ -190,7 +190,9 @@ import * as core from '@angular/core';
|
||||
function compareRuntimeShape(a: any, b: any) {
|
||||
const keys = metadataKeys(a);
|
||||
expect(keys).toEqual(metadataKeys(b));
|
||||
keys.forEach(key => { expect(a[key]).toBe(b[key]); });
|
||||
keys.forEach(key => {
|
||||
expect(a[key]).toBe(b[key]);
|
||||
});
|
||||
// Need to check 'ngMetadataName' separately, as this is
|
||||
// on the prototype in @angular/core, but a regular property in @angular/compiler.
|
||||
expect(a.ngMetadataName).toBe(b.ngMetadataName);
|
||||
|
@ -7,370 +7,381 @@
|
||||
*/
|
||||
|
||||
import {describe, expect, it} from '../../../core/testing/src/testing_internal';
|
||||
import {CssLexer, CssLexerMode, CssToken, CssTokenType, cssScannerError, getRawMessage, getToken} from '../../src/css_parser/css_lexer';
|
||||
import {CssLexer, CssLexerMode, cssScannerError, CssToken, CssTokenType, getRawMessage, getToken} from '../../src/css_parser/css_lexer';
|
||||
|
||||
(function() {
|
||||
function tokenize(
|
||||
code: string, trackComments: boolean = false,
|
||||
mode: CssLexerMode = CssLexerMode.ALL): CssToken[] {
|
||||
const scanner = new CssLexer().scan(code, trackComments);
|
||||
scanner.setMode(mode);
|
||||
function tokenize(
|
||||
code: string, trackComments: boolean = false,
|
||||
mode: CssLexerMode = CssLexerMode.ALL): CssToken[] {
|
||||
const scanner = new CssLexer().scan(code, trackComments);
|
||||
scanner.setMode(mode);
|
||||
|
||||
const tokens: CssToken[] = [];
|
||||
let output = scanner.scan();
|
||||
while (output != null) {
|
||||
const error = output.error;
|
||||
if (error != null) {
|
||||
throw cssScannerError(getToken(error), getRawMessage(error));
|
||||
}
|
||||
tokens.push(output.token);
|
||||
output = scanner.scan();
|
||||
const tokens: CssToken[] = [];
|
||||
let output = scanner.scan();
|
||||
while (output != null) {
|
||||
const error = output.error;
|
||||
if (error != null) {
|
||||
throw cssScannerError(getToken(error), getRawMessage(error));
|
||||
}
|
||||
|
||||
return tokens;
|
||||
tokens.push(output.token);
|
||||
output = scanner.scan();
|
||||
}
|
||||
|
||||
describe('CssLexer', () => {
|
||||
it('should lex newline characters as whitespace when whitespace mode is on', () => {
|
||||
const newlines = ['\n', '\r\n', '\r', '\f'];
|
||||
newlines.forEach((line) => {
|
||||
const token = tokenize(line, false, CssLexerMode.ALL_TRACK_WS)[0];
|
||||
expect(token.type).toEqual(CssTokenType.Whitespace);
|
||||
});
|
||||
});
|
||||
return tokens;
|
||||
}
|
||||
|
||||
it('should combined newline characters as one newline token when whitespace mode is on', () => {
|
||||
const newlines = ['\n', '\r\n', '\r', '\f'].join('');
|
||||
const tokens = tokenize(newlines, false, CssLexerMode.ALL_TRACK_WS);
|
||||
expect(tokens.length).toEqual(1);
|
||||
expect(tokens[0].type).toEqual(CssTokenType.Whitespace);
|
||||
describe('CssLexer', () => {
|
||||
it('should lex newline characters as whitespace when whitespace mode is on', () => {
|
||||
const newlines = ['\n', '\r\n', '\r', '\f'];
|
||||
newlines.forEach((line) => {
|
||||
const token = tokenize(line, false, CssLexerMode.ALL_TRACK_WS)[0];
|
||||
expect(token.type).toEqual(CssTokenType.Whitespace);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not consider whitespace or newline values at all when whitespace mode is off',
|
||||
it('should combined newline characters as one newline token when whitespace mode is on', () => {
|
||||
const newlines = ['\n', '\r\n', '\r', '\f'].join('');
|
||||
const tokens = tokenize(newlines, false, CssLexerMode.ALL_TRACK_WS);
|
||||
expect(tokens.length).toEqual(1);
|
||||
expect(tokens[0].type).toEqual(CssTokenType.Whitespace);
|
||||
});
|
||||
|
||||
it('should not consider whitespace or newline values at all when whitespace mode is off', () => {
|
||||
const newlines = ['\n', '\r\n', '\r', '\f'].join('');
|
||||
const tokens = tokenize(newlines);
|
||||
expect(tokens.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should lex simple selectors and their inner properties', () => {
|
||||
const cssCode = '\n' +
|
||||
' .selector { my-prop: my-value; }\n';
|
||||
const tokens = tokenize(cssCode);
|
||||
|
||||
expect(tokens[0].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[0].strValue).toEqual('.');
|
||||
|
||||
expect(tokens[1].type).toEqual(CssTokenType.Identifier);
|
||||
expect(tokens[1].strValue).toEqual('selector');
|
||||
|
||||
expect(tokens[2].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[2].strValue).toEqual('{');
|
||||
|
||||
expect(tokens[3].type).toEqual(CssTokenType.Identifier);
|
||||
expect(tokens[3].strValue).toEqual('my-prop');
|
||||
|
||||
expect(tokens[4].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[4].strValue).toEqual(':');
|
||||
|
||||
expect(tokens[5].type).toEqual(CssTokenType.Identifier);
|
||||
expect(tokens[5].strValue).toEqual('my-value');
|
||||
|
||||
expect(tokens[6].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[6].strValue).toEqual(';');
|
||||
|
||||
expect(tokens[7].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[7].strValue).toEqual('}');
|
||||
});
|
||||
|
||||
it('should capture the column and line values for each token', () => {
|
||||
const cssCode = '#id {\n' +
|
||||
' prop:value;\n' +
|
||||
'}';
|
||||
|
||||
const tokens = tokenize(cssCode);
|
||||
|
||||
// #
|
||||
expect(tokens[0].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[0].column).toEqual(0);
|
||||
expect(tokens[0].line).toEqual(0);
|
||||
|
||||
// id
|
||||
expect(tokens[1].type).toEqual(CssTokenType.Identifier);
|
||||
expect(tokens[1].column).toEqual(1);
|
||||
expect(tokens[1].line).toEqual(0);
|
||||
|
||||
// {
|
||||
expect(tokens[2].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[2].column).toEqual(4);
|
||||
expect(tokens[2].line).toEqual(0);
|
||||
|
||||
// prop
|
||||
expect(tokens[3].type).toEqual(CssTokenType.Identifier);
|
||||
expect(tokens[3].column).toEqual(2);
|
||||
expect(tokens[3].line).toEqual(1);
|
||||
|
||||
// :
|
||||
expect(tokens[4].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[4].column).toEqual(6);
|
||||
expect(tokens[4].line).toEqual(1);
|
||||
|
||||
// value
|
||||
expect(tokens[5].type).toEqual(CssTokenType.Identifier);
|
||||
expect(tokens[5].column).toEqual(7);
|
||||
expect(tokens[5].line).toEqual(1);
|
||||
|
||||
// ;
|
||||
expect(tokens[6].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[6].column).toEqual(12);
|
||||
expect(tokens[6].line).toEqual(1);
|
||||
|
||||
// }
|
||||
expect(tokens[7].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[7].column).toEqual(0);
|
||||
expect(tokens[7].line).toEqual(2);
|
||||
});
|
||||
|
||||
it('should lex quoted strings and escape accordingly', () => {
|
||||
const cssCode = 'prop: \'some { value } \\\' that is quoted\'';
|
||||
const tokens = tokenize(cssCode);
|
||||
|
||||
expect(tokens[0].type).toEqual(CssTokenType.Identifier);
|
||||
expect(tokens[1].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[2].type).toEqual(CssTokenType.String);
|
||||
expect(tokens[2].strValue).toEqual('\'some { value } \\\' that is quoted\'');
|
||||
});
|
||||
|
||||
it('should treat attribute operators as regular characters', () => {
|
||||
tokenize('^|~+*').forEach((token) => {
|
||||
expect(token.type).toEqual(CssTokenType.Character);
|
||||
});
|
||||
});
|
||||
|
||||
it('should lex numbers properly and set them as numbers', () => {
|
||||
const cssCode = '0 1 -2 3.0 -4.001';
|
||||
const tokens = tokenize(cssCode);
|
||||
|
||||
expect(tokens[0].type).toEqual(CssTokenType.Number);
|
||||
expect(tokens[0].strValue).toEqual('0');
|
||||
|
||||
expect(tokens[1].type).toEqual(CssTokenType.Number);
|
||||
expect(tokens[1].strValue).toEqual('1');
|
||||
|
||||
expect(tokens[2].type).toEqual(CssTokenType.Number);
|
||||
expect(tokens[2].strValue).toEqual('-2');
|
||||
|
||||
expect(tokens[3].type).toEqual(CssTokenType.Number);
|
||||
expect(tokens[3].strValue).toEqual('3.0');
|
||||
|
||||
expect(tokens[4].type).toEqual(CssTokenType.Number);
|
||||
expect(tokens[4].strValue).toEqual('-4.001');
|
||||
});
|
||||
|
||||
it('should lex @keywords', () => {
|
||||
const cssCode = '@import()@something';
|
||||
const tokens = tokenize(cssCode);
|
||||
|
||||
expect(tokens[0].type).toEqual(CssTokenType.AtKeyword);
|
||||
expect(tokens[0].strValue).toEqual('@import');
|
||||
|
||||
expect(tokens[1].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[1].strValue).toEqual('(');
|
||||
|
||||
expect(tokens[2].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[2].strValue).toEqual(')');
|
||||
|
||||
expect(tokens[3].type).toEqual(CssTokenType.AtKeyword);
|
||||
expect(tokens[3].strValue).toEqual('@something');
|
||||
});
|
||||
|
||||
it('should still lex a number even if it has a dimension suffix', () => {
|
||||
const cssCode = '40% is 40 percent';
|
||||
const tokens = tokenize(cssCode);
|
||||
|
||||
expect(tokens[0].type).toEqual(CssTokenType.Number);
|
||||
expect(tokens[0].strValue).toEqual('40');
|
||||
|
||||
expect(tokens[1].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[1].strValue).toEqual('%');
|
||||
|
||||
expect(tokens[2].type).toEqual(CssTokenType.Identifier);
|
||||
expect(tokens[2].strValue).toEqual('is');
|
||||
|
||||
expect(tokens[3].type).toEqual(CssTokenType.Number);
|
||||
expect(tokens[3].strValue).toEqual('40');
|
||||
});
|
||||
|
||||
it('should allow escaped character and unicode character-strings in CSS selectors', () => {
|
||||
const cssCode = '\\123456 .some\\thing \{\}';
|
||||
const tokens = tokenize(cssCode);
|
||||
|
||||
expect(tokens[0].type).toEqual(CssTokenType.Identifier);
|
||||
expect(tokens[0].strValue).toEqual('\\123456');
|
||||
|
||||
expect(tokens[1].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[2].type).toEqual(CssTokenType.Identifier);
|
||||
expect(tokens[2].strValue).toEqual('some\\thing');
|
||||
});
|
||||
|
||||
it('should distinguish identifiers and numbers from special characters', () => {
|
||||
const cssCode = 'one*two=-4+three-4-equals_value$';
|
||||
const tokens = tokenize(cssCode);
|
||||
|
||||
expect(tokens[0].type).toEqual(CssTokenType.Identifier);
|
||||
expect(tokens[0].strValue).toEqual('one');
|
||||
|
||||
expect(tokens[1].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[1].strValue).toEqual('*');
|
||||
|
||||
expect(tokens[2].type).toEqual(CssTokenType.Identifier);
|
||||
expect(tokens[2].strValue).toEqual('two');
|
||||
|
||||
expect(tokens[3].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[3].strValue).toEqual('=');
|
||||
|
||||
expect(tokens[4].type).toEqual(CssTokenType.Number);
|
||||
expect(tokens[4].strValue).toEqual('-4');
|
||||
|
||||
expect(tokens[5].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[5].strValue).toEqual('+');
|
||||
|
||||
expect(tokens[6].type).toEqual(CssTokenType.Identifier);
|
||||
expect(tokens[6].strValue).toEqual('three-4-equals_value');
|
||||
|
||||
expect(tokens[7].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[7].strValue).toEqual('$');
|
||||
});
|
||||
|
||||
it('should filter out comments and whitespace by default', () => {
|
||||
const cssCode = '.selector /* comment */ { /* value */ }';
|
||||
const tokens = tokenize(cssCode);
|
||||
|
||||
expect(tokens[0].strValue).toEqual('.');
|
||||
expect(tokens[1].strValue).toEqual('selector');
|
||||
expect(tokens[2].strValue).toEqual('{');
|
||||
expect(tokens[3].strValue).toEqual('}');
|
||||
});
|
||||
|
||||
it('should track comments when the flag is set to true', () => {
|
||||
const cssCode = '.selector /* comment */ { /* value */ }';
|
||||
const trackComments = true;
|
||||
const tokens = tokenize(cssCode, trackComments, CssLexerMode.ALL_TRACK_WS);
|
||||
|
||||
expect(tokens[0].strValue).toEqual('.');
|
||||
expect(tokens[1].strValue).toEqual('selector');
|
||||
expect(tokens[2].strValue).toEqual(' ');
|
||||
|
||||
expect(tokens[3].type).toEqual(CssTokenType.Comment);
|
||||
expect(tokens[3].strValue).toEqual('/* comment */');
|
||||
|
||||
expect(tokens[4].strValue).toEqual(' ');
|
||||
expect(tokens[5].strValue).toEqual('{');
|
||||
expect(tokens[6].strValue).toEqual(' ');
|
||||
|
||||
expect(tokens[7].type).toEqual(CssTokenType.Comment);
|
||||
expect(tokens[7].strValue).toEqual('/* value */');
|
||||
});
|
||||
|
||||
describe('Selector Mode', () => {
|
||||
it('should throw an error if a selector is being parsed while in the wrong mode', () => {
|
||||
const cssCode = '.class > tag';
|
||||
|
||||
let capturedMessage: string|null = null;
|
||||
try {
|
||||
tokenize(cssCode, false, CssLexerMode.STYLE_BLOCK);
|
||||
} catch (e) {
|
||||
capturedMessage = getRawMessage(e);
|
||||
}
|
||||
|
||||
expect(capturedMessage).toMatch(/Unexpected character \[\>\] at column 0:7 in expression/g);
|
||||
|
||||
capturedMessage = null;
|
||||
try {
|
||||
tokenize(cssCode, false, CssLexerMode.SELECTOR);
|
||||
} catch (e) {
|
||||
capturedMessage = getRawMessage(e);
|
||||
}
|
||||
|
||||
expect(capturedMessage).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Attribute Mode', () => {
|
||||
it('should consider attribute selectors as valid input and throw when an invalid modifier is used',
|
||||
() => {
|
||||
const newlines = ['\n', '\r\n', '\r', '\f'].join('');
|
||||
const tokens = tokenize(newlines);
|
||||
expect(tokens.length).toEqual(0);
|
||||
function tokenizeAttr(modifier: string) {
|
||||
const cssCode = 'value' + modifier + '=\'something\'';
|
||||
return tokenize(cssCode, false, CssLexerMode.ATTRIBUTE_SELECTOR);
|
||||
}
|
||||
|
||||
expect(tokenizeAttr('*').length).toEqual(4);
|
||||
expect(tokenizeAttr('|').length).toEqual(4);
|
||||
expect(tokenizeAttr('^').length).toEqual(4);
|
||||
expect(tokenizeAttr('$').length).toEqual(4);
|
||||
expect(tokenizeAttr('~').length).toEqual(4);
|
||||
expect(tokenizeAttr('').length).toEqual(3);
|
||||
|
||||
expect(() => {
|
||||
tokenizeAttr('+');
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
it('should lex simple selectors and their inner properties', () => {
|
||||
const cssCode = '\n' +
|
||||
' .selector { my-prop: my-value; }\n';
|
||||
const tokens = tokenize(cssCode);
|
||||
describe('Media Query Mode', () => {
|
||||
it('should validate media queries with a reduced subset of valid characters', () => {
|
||||
function tokenizeQuery(code: string) {
|
||||
return tokenize(code, false, CssLexerMode.MEDIA_QUERY);
|
||||
}
|
||||
|
||||
expect(tokens[0].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[0].strValue).toEqual('.');
|
||||
// the reason why the numbers are so high is because MediaQueries keep
|
||||
// track of the whitespace values
|
||||
expect(tokenizeQuery('(prop: value)').length).toEqual(5);
|
||||
expect(tokenizeQuery('(prop: value) and (prop2: value2)').length).toEqual(11);
|
||||
expect(tokenizeQuery('tv and (prop: value)').length).toEqual(7);
|
||||
expect(tokenizeQuery('print and ((prop: value) or (prop2: value2))').length).toEqual(15);
|
||||
expect(tokenizeQuery('(content: \'something $ crazy inside &\')').length).toEqual(5);
|
||||
|
||||
expect(tokens[1].type).toEqual(CssTokenType.Identifier);
|
||||
expect(tokens[1].strValue).toEqual('selector');
|
||||
expect(() => {
|
||||
tokenizeQuery('(max-height: 10 + 20)');
|
||||
}).toThrow();
|
||||
|
||||
expect(tokens[2].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[2].strValue).toEqual('{');
|
||||
|
||||
expect(tokens[3].type).toEqual(CssTokenType.Identifier);
|
||||
expect(tokens[3].strValue).toEqual('my-prop');
|
||||
|
||||
expect(tokens[4].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[4].strValue).toEqual(':');
|
||||
|
||||
expect(tokens[5].type).toEqual(CssTokenType.Identifier);
|
||||
expect(tokens[5].strValue).toEqual('my-value');
|
||||
|
||||
expect(tokens[6].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[6].strValue).toEqual(';');
|
||||
|
||||
expect(tokens[7].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[7].strValue).toEqual('}');
|
||||
expect(() => {
|
||||
tokenizeQuery('(max-height: fifty < 100)');
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should capture the column and line values for each token', () => {
|
||||
const cssCode = '#id {\n' +
|
||||
' prop:value;\n' +
|
||||
'}';
|
||||
|
||||
const tokens = tokenize(cssCode);
|
||||
|
||||
// #
|
||||
expect(tokens[0].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[0].column).toEqual(0);
|
||||
expect(tokens[0].line).toEqual(0);
|
||||
|
||||
// id
|
||||
expect(tokens[1].type).toEqual(CssTokenType.Identifier);
|
||||
expect(tokens[1].column).toEqual(1);
|
||||
expect(tokens[1].line).toEqual(0);
|
||||
|
||||
// {
|
||||
expect(tokens[2].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[2].column).toEqual(4);
|
||||
expect(tokens[2].line).toEqual(0);
|
||||
|
||||
// prop
|
||||
expect(tokens[3].type).toEqual(CssTokenType.Identifier);
|
||||
expect(tokens[3].column).toEqual(2);
|
||||
expect(tokens[3].line).toEqual(1);
|
||||
|
||||
// :
|
||||
expect(tokens[4].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[4].column).toEqual(6);
|
||||
expect(tokens[4].line).toEqual(1);
|
||||
|
||||
// value
|
||||
expect(tokens[5].type).toEqual(CssTokenType.Identifier);
|
||||
expect(tokens[5].column).toEqual(7);
|
||||
expect(tokens[5].line).toEqual(1);
|
||||
|
||||
// ;
|
||||
expect(tokens[6].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[6].column).toEqual(12);
|
||||
expect(tokens[6].line).toEqual(1);
|
||||
|
||||
// }
|
||||
expect(tokens[7].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[7].column).toEqual(0);
|
||||
expect(tokens[7].line).toEqual(2);
|
||||
});
|
||||
|
||||
it('should lex quoted strings and escape accordingly', () => {
|
||||
const cssCode = 'prop: \'some { value } \\\' that is quoted\'';
|
||||
const tokens = tokenize(cssCode);
|
||||
|
||||
expect(tokens[0].type).toEqual(CssTokenType.Identifier);
|
||||
expect(tokens[1].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[2].type).toEqual(CssTokenType.String);
|
||||
expect(tokens[2].strValue).toEqual('\'some { value } \\\' that is quoted\'');
|
||||
});
|
||||
|
||||
it('should treat attribute operators as regular characters', () => {
|
||||
tokenize('^|~+*').forEach((token) => { expect(token.type).toEqual(CssTokenType.Character); });
|
||||
});
|
||||
|
||||
it('should lex numbers properly and set them as numbers', () => {
|
||||
const cssCode = '0 1 -2 3.0 -4.001';
|
||||
const tokens = tokenize(cssCode);
|
||||
|
||||
expect(tokens[0].type).toEqual(CssTokenType.Number);
|
||||
expect(tokens[0].strValue).toEqual('0');
|
||||
|
||||
expect(tokens[1].type).toEqual(CssTokenType.Number);
|
||||
expect(tokens[1].strValue).toEqual('1');
|
||||
|
||||
expect(tokens[2].type).toEqual(CssTokenType.Number);
|
||||
expect(tokens[2].strValue).toEqual('-2');
|
||||
|
||||
expect(tokens[3].type).toEqual(CssTokenType.Number);
|
||||
expect(tokens[3].strValue).toEqual('3.0');
|
||||
|
||||
expect(tokens[4].type).toEqual(CssTokenType.Number);
|
||||
expect(tokens[4].strValue).toEqual('-4.001');
|
||||
});
|
||||
|
||||
it('should lex @keywords', () => {
|
||||
const cssCode = '@import()@something';
|
||||
const tokens = tokenize(cssCode);
|
||||
|
||||
expect(tokens[0].type).toEqual(CssTokenType.AtKeyword);
|
||||
expect(tokens[0].strValue).toEqual('@import');
|
||||
|
||||
expect(tokens[1].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[1].strValue).toEqual('(');
|
||||
|
||||
expect(tokens[2].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[2].strValue).toEqual(')');
|
||||
|
||||
expect(tokens[3].type).toEqual(CssTokenType.AtKeyword);
|
||||
expect(tokens[3].strValue).toEqual('@something');
|
||||
});
|
||||
|
||||
it('should still lex a number even if it has a dimension suffix', () => {
|
||||
const cssCode = '40% is 40 percent';
|
||||
const tokens = tokenize(cssCode);
|
||||
|
||||
expect(tokens[0].type).toEqual(CssTokenType.Number);
|
||||
expect(tokens[0].strValue).toEqual('40');
|
||||
|
||||
expect(tokens[1].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[1].strValue).toEqual('%');
|
||||
|
||||
expect(tokens[2].type).toEqual(CssTokenType.Identifier);
|
||||
expect(tokens[2].strValue).toEqual('is');
|
||||
|
||||
expect(tokens[3].type).toEqual(CssTokenType.Number);
|
||||
expect(tokens[3].strValue).toEqual('40');
|
||||
});
|
||||
|
||||
it('should allow escaped character and unicode character-strings in CSS selectors', () => {
|
||||
const cssCode = '\\123456 .some\\thing \{\}';
|
||||
const tokens = tokenize(cssCode);
|
||||
|
||||
expect(tokens[0].type).toEqual(CssTokenType.Identifier);
|
||||
expect(tokens[0].strValue).toEqual('\\123456');
|
||||
|
||||
expect(tokens[1].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[2].type).toEqual(CssTokenType.Identifier);
|
||||
expect(tokens[2].strValue).toEqual('some\\thing');
|
||||
});
|
||||
|
||||
it('should distinguish identifiers and numbers from special characters', () => {
|
||||
const cssCode = 'one*two=-4+three-4-equals_value$';
|
||||
const tokens = tokenize(cssCode);
|
||||
|
||||
expect(tokens[0].type).toEqual(CssTokenType.Identifier);
|
||||
expect(tokens[0].strValue).toEqual('one');
|
||||
|
||||
expect(tokens[1].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[1].strValue).toEqual('*');
|
||||
|
||||
expect(tokens[2].type).toEqual(CssTokenType.Identifier);
|
||||
expect(tokens[2].strValue).toEqual('two');
|
||||
|
||||
expect(tokens[3].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[3].strValue).toEqual('=');
|
||||
|
||||
expect(tokens[4].type).toEqual(CssTokenType.Number);
|
||||
expect(tokens[4].strValue).toEqual('-4');
|
||||
|
||||
expect(tokens[5].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[5].strValue).toEqual('+');
|
||||
|
||||
expect(tokens[6].type).toEqual(CssTokenType.Identifier);
|
||||
expect(tokens[6].strValue).toEqual('three-4-equals_value');
|
||||
|
||||
expect(tokens[7].type).toEqual(CssTokenType.Character);
|
||||
expect(tokens[7].strValue).toEqual('$');
|
||||
});
|
||||
|
||||
it('should filter out comments and whitespace by default', () => {
|
||||
const cssCode = '.selector /* comment */ { /* value */ }';
|
||||
const tokens = tokenize(cssCode);
|
||||
|
||||
expect(tokens[0].strValue).toEqual('.');
|
||||
expect(tokens[1].strValue).toEqual('selector');
|
||||
expect(tokens[2].strValue).toEqual('{');
|
||||
expect(tokens[3].strValue).toEqual('}');
|
||||
});
|
||||
|
||||
it('should track comments when the flag is set to true', () => {
|
||||
const cssCode = '.selector /* comment */ { /* value */ }';
|
||||
const trackComments = true;
|
||||
const tokens = tokenize(cssCode, trackComments, CssLexerMode.ALL_TRACK_WS);
|
||||
|
||||
expect(tokens[0].strValue).toEqual('.');
|
||||
expect(tokens[1].strValue).toEqual('selector');
|
||||
expect(tokens[2].strValue).toEqual(' ');
|
||||
|
||||
expect(tokens[3].type).toEqual(CssTokenType.Comment);
|
||||
expect(tokens[3].strValue).toEqual('/* comment */');
|
||||
|
||||
expect(tokens[4].strValue).toEqual(' ');
|
||||
expect(tokens[5].strValue).toEqual('{');
|
||||
expect(tokens[6].strValue).toEqual(' ');
|
||||
|
||||
expect(tokens[7].type).toEqual(CssTokenType.Comment);
|
||||
expect(tokens[7].strValue).toEqual('/* value */');
|
||||
});
|
||||
|
||||
describe('Selector Mode', () => {
|
||||
it('should throw an error if a selector is being parsed while in the wrong mode', () => {
|
||||
const cssCode = '.class > tag';
|
||||
|
||||
let capturedMessage: string|null = null;
|
||||
try {
|
||||
tokenize(cssCode, false, CssLexerMode.STYLE_BLOCK);
|
||||
} catch (e) {
|
||||
capturedMessage = getRawMessage(e);
|
||||
}
|
||||
|
||||
expect(capturedMessage).toMatch(/Unexpected character \[\>\] at column 0:7 in expression/g);
|
||||
|
||||
capturedMessage = null;
|
||||
try {
|
||||
tokenize(cssCode, false, CssLexerMode.SELECTOR);
|
||||
} catch (e) {
|
||||
capturedMessage = getRawMessage(e);
|
||||
}
|
||||
|
||||
expect(capturedMessage).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Attribute Mode', () => {
|
||||
it('should consider attribute selectors as valid input and throw when an invalid modifier is used',
|
||||
() => {
|
||||
function tokenizeAttr(modifier: string) {
|
||||
const cssCode = 'value' + modifier + '=\'something\'';
|
||||
return tokenize(cssCode, false, CssLexerMode.ATTRIBUTE_SELECTOR);
|
||||
}
|
||||
|
||||
expect(tokenizeAttr('*').length).toEqual(4);
|
||||
expect(tokenizeAttr('|').length).toEqual(4);
|
||||
expect(tokenizeAttr('^').length).toEqual(4);
|
||||
expect(tokenizeAttr('$').length).toEqual(4);
|
||||
expect(tokenizeAttr('~').length).toEqual(4);
|
||||
expect(tokenizeAttr('').length).toEqual(3);
|
||||
|
||||
expect(() => { tokenizeAttr('+'); }).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Media Query Mode', () => {
|
||||
it('should validate media queries with a reduced subset of valid characters', () => {
|
||||
function tokenizeQuery(code: string) {
|
||||
return tokenize(code, false, CssLexerMode.MEDIA_QUERY);
|
||||
}
|
||||
|
||||
// the reason why the numbers are so high is because MediaQueries keep
|
||||
// track of the whitespace values
|
||||
expect(tokenizeQuery('(prop: value)').length).toEqual(5);
|
||||
expect(tokenizeQuery('(prop: value) and (prop2: value2)').length).toEqual(11);
|
||||
expect(tokenizeQuery('tv and (prop: value)').length).toEqual(7);
|
||||
expect(tokenizeQuery('print and ((prop: value) or (prop2: value2))').length).toEqual(15);
|
||||
expect(tokenizeQuery('(content: \'something $ crazy inside &\')').length).toEqual(5);
|
||||
|
||||
expect(() => { tokenizeQuery('(max-height: 10 + 20)'); }).toThrow();
|
||||
|
||||
expect(() => { tokenizeQuery('(max-height: fifty < 100)'); }).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Pseudo Selector Mode', () => {
|
||||
it('should validate pseudo selector identifiers with a reduced subset of valid characters',
|
||||
() => {
|
||||
function tokenizePseudo(code: string, withArgs = false): CssToken[] {
|
||||
const mode = withArgs ? CssLexerMode.PSEUDO_SELECTOR_WITH_ARGUMENTS :
|
||||
CssLexerMode.PSEUDO_SELECTOR;
|
||||
return tokenize(code, false, mode);
|
||||
}
|
||||
|
||||
expect(tokenizePseudo('hover').length).toEqual(1);
|
||||
expect(tokenizePseudo('focus').length).toEqual(1);
|
||||
expect(tokenizePseudo('lang(en-us)', true).length).toEqual(4);
|
||||
|
||||
expect(() => { tokenizePseudo('lang(something:broken)', true); }).toThrow();
|
||||
|
||||
expect(() => { tokenizePseudo('not(.selector)', true); }).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe(
|
||||
'Style Block Mode', () => {
|
||||
it('should style blocks with a reduced subset of valid characters',
|
||||
() => {
|
||||
function tokenizeStyles(code: string) {
|
||||
return tokenize(code, false, CssLexerMode.STYLE_BLOCK);
|
||||
}
|
||||
|
||||
expect(tokenizeStyles(`
|
||||
});
|
||||
|
||||
describe('Pseudo Selector Mode', () => {
|
||||
it('should validate pseudo selector identifiers with a reduced subset of valid characters',
|
||||
() => {
|
||||
function tokenizePseudo(code: string, withArgs = false): CssToken[] {
|
||||
const mode = withArgs ? CssLexerMode.PSEUDO_SELECTOR_WITH_ARGUMENTS :
|
||||
CssLexerMode.PSEUDO_SELECTOR;
|
||||
return tokenize(code, false, mode);
|
||||
}
|
||||
|
||||
expect(tokenizePseudo('hover').length).toEqual(1);
|
||||
expect(tokenizePseudo('focus').length).toEqual(1);
|
||||
expect(tokenizePseudo('lang(en-us)', true).length).toEqual(4);
|
||||
|
||||
expect(() => {
|
||||
tokenizePseudo('lang(something:broken)', true);
|
||||
}).toThrow();
|
||||
|
||||
expect(() => {
|
||||
tokenizePseudo('not(.selector)', true);
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe(
|
||||
'Style Block Mode', () => {
|
||||
it(
|
||||
'should style blocks with a reduced subset of valid characters', () => {
|
||||
function tokenizeStyles(code: string) {
|
||||
return tokenize(code, false, CssLexerMode.STYLE_BLOCK);
|
||||
}
|
||||
|
||||
expect(tokenizeStyles(`
|
||||
key: value;
|
||||
prop: 100;
|
||||
style: value3!important;
|
||||
`).length).toEqual(14);
|
||||
|
||||
expect(() => tokenizeStyles(` key$: value; `)).toThrow();
|
||||
expect(() => tokenizeStyles(` key: value$; `)).toThrow();
|
||||
expect(() => tokenizeStyles(` key: value + 10; `)).toThrow();
|
||||
expect(() => tokenizeStyles(` key: &value; `)).toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
expect(() => tokenizeStyles(` key$: value; `)).toThrow();
|
||||
expect(() => tokenizeStyles(` key: value$; `)).toThrow();
|
||||
expect(() => tokenizeStyles(` key: value + 10; `)).toThrow();
|
||||
expect(() => tokenizeStyles(` key: &value; `)).toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
@ -111,26 +111,26 @@ export function assertTokens(tokens: CssToken[], valuesArr: string[]) {
|
||||
expect(ast.rules.length).toEqual(1);
|
||||
|
||||
const rule = <CssKeyframeRuleAst>ast.rules[0];
|
||||
expect(rule.name !.strValue).toEqual('rotateMe');
|
||||
expect(rule.name!.strValue).toEqual('rotateMe');
|
||||
|
||||
const block = <CssBlockAst>rule.block;
|
||||
const fromRule = <CssKeyframeDefinitionAst>block.entries[0];
|
||||
|
||||
expect(fromRule.name !.strValue).toEqual('from');
|
||||
expect(fromRule.name!.strValue).toEqual('from');
|
||||
const fromStyle = <CssDefinitionAst>(<CssBlockAst>fromRule.block).entries[0];
|
||||
expect(fromStyle.property.strValue).toEqual('transform');
|
||||
assertTokens(fromStyle.value.tokens, ['rotate', '(', '-360', 'deg', ')']);
|
||||
|
||||
const midRule = <CssKeyframeDefinitionAst>block.entries[1];
|
||||
|
||||
expect(midRule.name !.strValue).toEqual('50%');
|
||||
expect(midRule.name!.strValue).toEqual('50%');
|
||||
const midStyle = <CssDefinitionAst>(<CssBlockAst>midRule.block).entries[0];
|
||||
expect(midStyle.property.strValue).toEqual('transform');
|
||||
assertTokens(midStyle.value.tokens, ['rotate', '(', '0', 'deg', ')']);
|
||||
|
||||
const toRule = <CssKeyframeDefinitionAst>block.entries[2];
|
||||
|
||||
expect(toRule.name !.strValue).toEqual('to');
|
||||
expect(toRule.name!.strValue).toEqual('to');
|
||||
const toStyle = <CssDefinitionAst>(<CssBlockAst>toRule.block).entries[0];
|
||||
expect(toStyle.property.strValue).toEqual('transform');
|
||||
assertTokens(toStyle.value.tokens, ['rotate', '(', '360', 'deg', ')']);
|
||||
@ -695,7 +695,7 @@ export function assertTokens(tokens: CssToken[], valuesArr: string[]) {
|
||||
const ast = output.ast;
|
||||
|
||||
assertMatchesOffsetAndChar(ast.location.start, 0, '#');
|
||||
assertMatchesOffsetAndChar(ast.location.end, 22, undefined !);
|
||||
assertMatchesOffsetAndChar(ast.location.end, 22, undefined!);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
|
||||
import {beforeEach, describe, expect, it} from '../../../core/testing/src/testing_internal';
|
||||
import {CssAst, CssAstVisitor, CssAtRulePredicateAst, CssBlockAst, CssDefinitionAst, CssInlineRuleAst, CssKeyframeDefinitionAst, CssKeyframeRuleAst, CssMediaQueryRuleAst, CssPseudoSelectorAst, CssRuleAst, CssSelectorAst, CssSelectorRuleAst, CssSimpleSelectorAst, CssStyleSheetAst, CssStyleValueAst, CssStylesBlockAst, CssUnknownRuleAst, CssUnknownTokenListAst} from '../../src/css_parser/css_ast';
|
||||
import {CssAst, CssAstVisitor, CssAtRulePredicateAst, CssBlockAst, CssDefinitionAst, CssInlineRuleAst, CssKeyframeDefinitionAst, CssKeyframeRuleAst, CssMediaQueryRuleAst, CssPseudoSelectorAst, CssRuleAst, CssSelectorAst, CssSelectorRuleAst, CssSimpleSelectorAst, CssStylesBlockAst, CssStyleSheetAst, CssStyleValueAst, CssUnknownRuleAst, CssUnknownTokenListAst} from '../../src/css_parser/css_ast';
|
||||
import {BlockType, CssParseError, CssParser, CssToken} from '../../src/css_parser/css_parser';
|
||||
|
||||
function _assertTokens(tokens: CssToken[], valuesArr: string[]): void {
|
||||
@ -29,7 +29,9 @@ class MyVisitor implements CssAstVisitor {
|
||||
this.captures[method].push([ast, context]);
|
||||
}
|
||||
|
||||
constructor(ast: CssStyleSheetAst, context: any) { ast.visit(this, context); }
|
||||
constructor(ast: CssStyleSheetAst, context: any) {
|
||||
ast.visit(this, context);
|
||||
}
|
||||
|
||||
visitCssValue(ast: CssStyleValueAst, context: any): void {
|
||||
this._capture('visitCssValue', ast, context);
|
||||
@ -61,20 +63,24 @@ class MyVisitor implements CssAstVisitor {
|
||||
|
||||
visitCssSelectorRule(ast: CssSelectorRuleAst, context: any): void {
|
||||
this._capture('visitCssSelectorRule', ast, context);
|
||||
ast.selectors.forEach((selAst: CssSelectorAst) => { selAst.visit(this, context); });
|
||||
ast.selectors.forEach((selAst: CssSelectorAst) => {
|
||||
selAst.visit(this, context);
|
||||
});
|
||||
ast.block.visit(this, context);
|
||||
}
|
||||
|
||||
visitCssSelector(ast: CssSelectorAst, context: any): void {
|
||||
this._capture('visitCssSelector', ast, context);
|
||||
ast.selectorParts.forEach(
|
||||
(simpleAst: CssSimpleSelectorAst) => { simpleAst.visit(this, context); });
|
||||
ast.selectorParts.forEach((simpleAst: CssSimpleSelectorAst) => {
|
||||
simpleAst.visit(this, context);
|
||||
});
|
||||
}
|
||||
|
||||
visitCssSimpleSelector(ast: CssSimpleSelectorAst, context: any): void {
|
||||
this._capture('visitCssSimpleSelector', ast, context);
|
||||
ast.pseudoSelectors.forEach(
|
||||
(pseudoAst: CssPseudoSelectorAst) => { pseudoAst.visit(this, context); });
|
||||
ast.pseudoSelectors.forEach((pseudoAst: CssPseudoSelectorAst) => {
|
||||
pseudoAst.visit(this, context);
|
||||
});
|
||||
}
|
||||
|
||||
visitCssDefinition(ast: CssDefinitionAst, context: any): void {
|
||||
@ -84,18 +90,23 @@ class MyVisitor implements CssAstVisitor {
|
||||
|
||||
visitCssBlock(ast: CssBlockAst, context: any): void {
|
||||
this._capture('visitCssBlock', ast, context);
|
||||
ast.entries.forEach((entryAst: CssAst) => { entryAst.visit(this, context); });
|
||||
ast.entries.forEach((entryAst: CssAst) => {
|
||||
entryAst.visit(this, context);
|
||||
});
|
||||
}
|
||||
|
||||
visitCssStylesBlock(ast: CssStylesBlockAst, context: any): void {
|
||||
this._capture('visitCssStylesBlock', ast, context);
|
||||
ast.definitions.forEach(
|
||||
(definitionAst: CssDefinitionAst) => { definitionAst.visit(this, context); });
|
||||
ast.definitions.forEach((definitionAst: CssDefinitionAst) => {
|
||||
definitionAst.visit(this, context);
|
||||
});
|
||||
}
|
||||
|
||||
visitCssStyleSheet(ast: CssStyleSheetAst, context: any): void {
|
||||
this._capture('visitCssStyleSheet', ast, context);
|
||||
ast.rules.forEach((ruleAst: CssRuleAst) => { ruleAst.visit(this, context); });
|
||||
ast.rules.forEach((ruleAst: CssRuleAst) => {
|
||||
ruleAst.visit(this, context);
|
||||
});
|
||||
}
|
||||
|
||||
visitCssUnknownRule(ast: CssUnknownRuleAst, context: any): void {
|
||||
@ -116,21 +127,21 @@ function _getCaptureAst(capture: any[], index = 0): CssAst {
|
||||
}
|
||||
|
||||
(function() {
|
||||
function parse(cssCode: string, ignoreErrors: boolean = false) {
|
||||
const output = new CssParser().parse(cssCode, 'some-fake-css-file.css');
|
||||
const errors = output.errors;
|
||||
if (errors.length > 0 && !ignoreErrors) {
|
||||
throw new Error(errors.map((error: CssParseError) => error.msg).join(', '));
|
||||
}
|
||||
return output.ast;
|
||||
function parse(cssCode: string, ignoreErrors: boolean = false) {
|
||||
const output = new CssParser().parse(cssCode, 'some-fake-css-file.css');
|
||||
const errors = output.errors;
|
||||
if (errors.length > 0 && !ignoreErrors) {
|
||||
throw new Error(errors.map((error: CssParseError) => error.msg).join(', '));
|
||||
}
|
||||
return output.ast;
|
||||
}
|
||||
|
||||
describe('CSS parsing and visiting', () => {
|
||||
let ast: CssStyleSheetAst;
|
||||
const context = {};
|
||||
describe('CSS parsing and visiting', () => {
|
||||
let ast: CssStyleSheetAst;
|
||||
const context = {};
|
||||
|
||||
beforeEach(() => {
|
||||
const cssCode = `
|
||||
beforeEach(() => {
|
||||
const cssCode = `
|
||||
.rule1 { prop1: value1 }
|
||||
.rule2 { prop2: value2 }
|
||||
|
||||
@ -149,174 +160,174 @@ function _getCaptureAst(capture: any[], index = 0): CssAst {
|
||||
}
|
||||
}
|
||||
`;
|
||||
ast = parse(cssCode);
|
||||
});
|
||||
ast = parse(cssCode);
|
||||
});
|
||||
|
||||
it('should parse and visit a stylesheet', () => {
|
||||
const visitor = new MyVisitor(ast, context);
|
||||
const captures = visitor.captures['visitCssStyleSheet'];
|
||||
it('should parse and visit a stylesheet', () => {
|
||||
const visitor = new MyVisitor(ast, context);
|
||||
const captures = visitor.captures['visitCssStyleSheet'];
|
||||
|
||||
expect(captures.length).toEqual(1);
|
||||
expect(captures.length).toEqual(1);
|
||||
|
||||
const capture = captures[0];
|
||||
expect(capture[0]).toEqual(ast);
|
||||
expect(capture[1]).toEqual(context);
|
||||
});
|
||||
const capture = captures[0];
|
||||
expect(capture[0]).toEqual(ast);
|
||||
expect(capture[1]).toEqual(context);
|
||||
});
|
||||
|
||||
it('should parse and visit each of the stylesheet selectors', () => {
|
||||
const visitor = new MyVisitor(ast, context);
|
||||
const captures = visitor.captures['visitCssSelectorRule'];
|
||||
it('should parse and visit each of the stylesheet selectors', () => {
|
||||
const visitor = new MyVisitor(ast, context);
|
||||
const captures = visitor.captures['visitCssSelectorRule'];
|
||||
|
||||
expect(captures.length).toEqual(3);
|
||||
expect(captures.length).toEqual(3);
|
||||
|
||||
const rule1 = <CssSelectorRuleAst>_getCaptureAst(captures, 0);
|
||||
expect(rule1).toEqual(ast.rules[0] as CssSelectorRuleAst);
|
||||
const rule1 = <CssSelectorRuleAst>_getCaptureAst(captures, 0);
|
||||
expect(rule1).toEqual(ast.rules[0] as CssSelectorRuleAst);
|
||||
|
||||
const firstSelector = rule1.selectors[0];
|
||||
const firstSimpleSelector = firstSelector.selectorParts[0];
|
||||
_assertTokens(firstSimpleSelector.tokens, ['.', 'rule1']);
|
||||
const firstSelector = rule1.selectors[0];
|
||||
const firstSimpleSelector = firstSelector.selectorParts[0];
|
||||
_assertTokens(firstSimpleSelector.tokens, ['.', 'rule1']);
|
||||
|
||||
const rule2 = <CssSelectorRuleAst>_getCaptureAst(captures, 1);
|
||||
expect(rule2).toEqual(ast.rules[1] as CssSelectorRuleAst);
|
||||
const rule2 = <CssSelectorRuleAst>_getCaptureAst(captures, 1);
|
||||
expect(rule2).toEqual(ast.rules[1] as CssSelectorRuleAst);
|
||||
|
||||
const secondSelector = rule2.selectors[0];
|
||||
const secondSimpleSelector = secondSelector.selectorParts[0];
|
||||
_assertTokens(secondSimpleSelector.tokens, ['.', 'rule2']);
|
||||
const secondSelector = rule2.selectors[0];
|
||||
const secondSimpleSelector = secondSelector.selectorParts[0];
|
||||
_assertTokens(secondSimpleSelector.tokens, ['.', 'rule2']);
|
||||
|
||||
const rule3 = <CssSelectorRuleAst>_getCaptureAst(captures, 2);
|
||||
expect(rule3).toEqual(
|
||||
(ast.rules[2] as CssSelectorRuleAst).block.entries[0] as CssSelectorRuleAst);
|
||||
const rule3 = <CssSelectorRuleAst>_getCaptureAst(captures, 2);
|
||||
expect(rule3).toEqual(
|
||||
(ast.rules[2] as CssSelectorRuleAst).block.entries[0] as CssSelectorRuleAst);
|
||||
|
||||
const thirdSelector = rule3.selectors[0];
|
||||
const thirdSimpleSelector = thirdSelector.selectorParts[0];
|
||||
_assertTokens(thirdSimpleSelector.tokens, ['#', 'rule3']);
|
||||
});
|
||||
const thirdSelector = rule3.selectors[0];
|
||||
const thirdSimpleSelector = thirdSelector.selectorParts[0];
|
||||
_assertTokens(thirdSimpleSelector.tokens, ['#', 'rule3']);
|
||||
});
|
||||
|
||||
it('should parse and visit each of the stylesheet style key/value definitions', () => {
|
||||
const visitor = new MyVisitor(ast, context);
|
||||
const captures = visitor.captures['visitCssDefinition'];
|
||||
it('should parse and visit each of the stylesheet style key/value definitions', () => {
|
||||
const visitor = new MyVisitor(ast, context);
|
||||
const captures = visitor.captures['visitCssDefinition'];
|
||||
|
||||
expect(captures.length).toEqual(5);
|
||||
expect(captures.length).toEqual(5);
|
||||
|
||||
const def1 = <CssDefinitionAst>_getCaptureAst(captures, 0);
|
||||
expect(def1.property.strValue).toEqual('prop1');
|
||||
expect(def1.value.tokens[0].strValue).toEqual('value1');
|
||||
const def1 = <CssDefinitionAst>_getCaptureAst(captures, 0);
|
||||
expect(def1.property.strValue).toEqual('prop1');
|
||||
expect(def1.value.tokens[0].strValue).toEqual('value1');
|
||||
|
||||
const def2 = <CssDefinitionAst>_getCaptureAst(captures, 1);
|
||||
expect(def2.property.strValue).toEqual('prop2');
|
||||
expect(def2.value.tokens[0].strValue).toEqual('value2');
|
||||
const def2 = <CssDefinitionAst>_getCaptureAst(captures, 1);
|
||||
expect(def2.property.strValue).toEqual('prop2');
|
||||
expect(def2.value.tokens[0].strValue).toEqual('value2');
|
||||
|
||||
const def3 = <CssDefinitionAst>_getCaptureAst(captures, 2);
|
||||
expect(def3.property.strValue).toEqual('prop3');
|
||||
expect(def3.value.tokens[0].strValue).toEqual('value3');
|
||||
const def3 = <CssDefinitionAst>_getCaptureAst(captures, 2);
|
||||
expect(def3.property.strValue).toEqual('prop3');
|
||||
expect(def3.value.tokens[0].strValue).toEqual('value3');
|
||||
|
||||
const def4 = <CssDefinitionAst>_getCaptureAst(captures, 3);
|
||||
expect(def4.property.strValue).toEqual('prop4');
|
||||
expect(def4.value.tokens[0].strValue).toEqual('value4');
|
||||
const def4 = <CssDefinitionAst>_getCaptureAst(captures, 3);
|
||||
expect(def4.property.strValue).toEqual('prop4');
|
||||
expect(def4.value.tokens[0].strValue).toEqual('value4');
|
||||
|
||||
const def5 = <CssDefinitionAst>_getCaptureAst(captures, 4);
|
||||
expect(def5.property.strValue).toEqual('prop5');
|
||||
expect(def5.value.tokens[0].strValue).toEqual('value5');
|
||||
});
|
||||
const def5 = <CssDefinitionAst>_getCaptureAst(captures, 4);
|
||||
expect(def5.property.strValue).toEqual('prop5');
|
||||
expect(def5.value.tokens[0].strValue).toEqual('value5');
|
||||
});
|
||||
|
||||
it('should parse and visit the associated media query values', () => {
|
||||
const visitor = new MyVisitor(ast, context);
|
||||
const captures = visitor.captures['visitCssMediaQueryRule'];
|
||||
it('should parse and visit the associated media query values', () => {
|
||||
const visitor = new MyVisitor(ast, context);
|
||||
const captures = visitor.captures['visitCssMediaQueryRule'];
|
||||
|
||||
expect(captures.length).toEqual(1);
|
||||
expect(captures.length).toEqual(1);
|
||||
|
||||
const query1 = <CssMediaQueryRuleAst>_getCaptureAst(captures, 0);
|
||||
_assertTokens(query1.query.tokens, ['all', 'and', '(', 'max-width', '100', 'px', ')']);
|
||||
expect(query1.block.entries.length).toEqual(1);
|
||||
});
|
||||
const query1 = <CssMediaQueryRuleAst>_getCaptureAst(captures, 0);
|
||||
_assertTokens(query1.query.tokens, ['all', 'and', '(', 'max-width', '100', 'px', ')']);
|
||||
expect(query1.block.entries.length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should capture the media query predicate', () => {
|
||||
const visitor = new MyVisitor(ast, context);
|
||||
const captures = visitor.captures['visitCssAtRulePredicate'];
|
||||
it('should capture the media query predicate', () => {
|
||||
const visitor = new MyVisitor(ast, context);
|
||||
const captures = visitor.captures['visitCssAtRulePredicate'];
|
||||
|
||||
expect(captures.length).toEqual(1);
|
||||
expect(captures.length).toEqual(1);
|
||||
|
||||
const predicate = <CssAtRulePredicateAst>_getCaptureAst(captures, 0);
|
||||
expect(predicate.strValue).toEqual('@media all (max-width: 100px)');
|
||||
});
|
||||
const predicate = <CssAtRulePredicateAst>_getCaptureAst(captures, 0);
|
||||
expect(predicate.strValue).toEqual('@media all (max-width: 100px)');
|
||||
});
|
||||
|
||||
it('should parse and visit the associated "@inline" rule values', () => {
|
||||
const visitor = new MyVisitor(ast, context);
|
||||
const captures = visitor.captures['visitCssInlineRule'];
|
||||
it('should parse and visit the associated "@inline" rule values', () => {
|
||||
const visitor = new MyVisitor(ast, context);
|
||||
const captures = visitor.captures['visitCssInlineRule'];
|
||||
|
||||
expect(captures.length).toEqual(1);
|
||||
expect(captures.length).toEqual(1);
|
||||
|
||||
const inline1 = <CssInlineRuleAst>_getCaptureAst(captures, 0);
|
||||
expect(inline1.type).toEqual(BlockType.Import);
|
||||
_assertTokens(inline1.value.tokens, ['url', '(', 'file.css', ')']);
|
||||
});
|
||||
const inline1 = <CssInlineRuleAst>_getCaptureAst(captures, 0);
|
||||
expect(inline1.type).toEqual(BlockType.Import);
|
||||
_assertTokens(inline1.value.tokens, ['url', '(', 'file.css', ')']);
|
||||
});
|
||||
|
||||
it('should parse and visit the keyframe blocks', () => {
|
||||
const visitor = new MyVisitor(ast, context);
|
||||
const captures = visitor.captures['visitCssKeyframeRule'];
|
||||
it('should parse and visit the keyframe blocks', () => {
|
||||
const visitor = new MyVisitor(ast, context);
|
||||
const captures = visitor.captures['visitCssKeyframeRule'];
|
||||
|
||||
expect(captures.length).toEqual(1);
|
||||
expect(captures.length).toEqual(1);
|
||||
|
||||
const keyframe1 = <CssKeyframeRuleAst>_getCaptureAst(captures, 0);
|
||||
expect(keyframe1.name !.strValue).toEqual('rotate');
|
||||
expect(keyframe1.block.entries.length).toEqual(2);
|
||||
});
|
||||
const keyframe1 = <CssKeyframeRuleAst>_getCaptureAst(captures, 0);
|
||||
expect(keyframe1.name!.strValue).toEqual('rotate');
|
||||
expect(keyframe1.block.entries.length).toEqual(2);
|
||||
});
|
||||
|
||||
it('should parse and visit the associated keyframe rules', () => {
|
||||
const visitor = new MyVisitor(ast, context);
|
||||
const captures = visitor.captures['visitCssKeyframeDefinition'];
|
||||
it('should parse and visit the associated keyframe rules', () => {
|
||||
const visitor = new MyVisitor(ast, context);
|
||||
const captures = visitor.captures['visitCssKeyframeDefinition'];
|
||||
|
||||
expect(captures.length).toEqual(2);
|
||||
expect(captures.length).toEqual(2);
|
||||
|
||||
const def1 = <CssKeyframeDefinitionAst>_getCaptureAst(captures, 0);
|
||||
_assertTokens(def1.steps, ['from']);
|
||||
expect(def1.block.entries.length).toEqual(1);
|
||||
const def1 = <CssKeyframeDefinitionAst>_getCaptureAst(captures, 0);
|
||||
_assertTokens(def1.steps, ['from']);
|
||||
expect(def1.block.entries.length).toEqual(1);
|
||||
|
||||
const def2 = <CssKeyframeDefinitionAst>_getCaptureAst(captures, 1);
|
||||
_assertTokens(def2.steps, ['50%', '100%']);
|
||||
expect(def2.block.entries.length).toEqual(1);
|
||||
});
|
||||
const def2 = <CssKeyframeDefinitionAst>_getCaptureAst(captures, 1);
|
||||
_assertTokens(def2.steps, ['50%', '100%']);
|
||||
expect(def2.block.entries.length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should visit an unknown `@` rule', () => {
|
||||
const cssCode = `
|
||||
it('should visit an unknown `@` rule', () => {
|
||||
const cssCode = `
|
||||
@someUnknownRule param {
|
||||
one two three
|
||||
}
|
||||
`;
|
||||
ast = parse(cssCode, true);
|
||||
const visitor = new MyVisitor(ast, context);
|
||||
const captures = visitor.captures['visitCssUnknownRule'];
|
||||
ast = parse(cssCode, true);
|
||||
const visitor = new MyVisitor(ast, context);
|
||||
const captures = visitor.captures['visitCssUnknownRule'];
|
||||
|
||||
expect(captures.length).toEqual(1);
|
||||
expect(captures.length).toEqual(1);
|
||||
|
||||
const rule = <CssUnknownRuleAst>_getCaptureAst(captures, 0);
|
||||
expect(rule.ruleName).toEqual('@someUnknownRule');
|
||||
const rule = <CssUnknownRuleAst>_getCaptureAst(captures, 0);
|
||||
expect(rule.ruleName).toEqual('@someUnknownRule');
|
||||
|
||||
_assertTokens(rule.tokens, ['param', '{', 'one', 'two', 'three', '}']);
|
||||
});
|
||||
|
||||
it('should collect an invalid list of tokens before a valid selector', () => {
|
||||
const cssCode = 'one two three four five; selector { }';
|
||||
ast = parse(cssCode, true);
|
||||
const visitor = new MyVisitor(ast, context);
|
||||
const captures = visitor.captures['visitCssUnknownTokenList'];
|
||||
|
||||
expect(captures.length).toEqual(1);
|
||||
|
||||
const rule = <CssUnknownTokenListAst>_getCaptureAst(captures, 0);
|
||||
_assertTokens(rule.tokens, ['one', 'two', 'three', 'four', 'five']);
|
||||
});
|
||||
|
||||
it('should collect an invalid list of tokens after a valid selector', () => {
|
||||
const cssCode = 'selector { } six seven eight';
|
||||
ast = parse(cssCode, true);
|
||||
const visitor = new MyVisitor(ast, context);
|
||||
const captures = visitor.captures['visitCssUnknownTokenList'];
|
||||
|
||||
expect(captures.length).toEqual(1);
|
||||
|
||||
const rule = <CssUnknownTokenListAst>_getCaptureAst(captures, 0);
|
||||
_assertTokens(rule.tokens, ['six', 'seven', 'eight']);
|
||||
});
|
||||
_assertTokens(rule.tokens, ['param', '{', 'one', 'two', 'three', '}']);
|
||||
});
|
||||
|
||||
it('should collect an invalid list of tokens before a valid selector', () => {
|
||||
const cssCode = 'one two three four five; selector { }';
|
||||
ast = parse(cssCode, true);
|
||||
const visitor = new MyVisitor(ast, context);
|
||||
const captures = visitor.captures['visitCssUnknownTokenList'];
|
||||
|
||||
expect(captures.length).toEqual(1);
|
||||
|
||||
const rule = <CssUnknownTokenListAst>_getCaptureAst(captures, 0);
|
||||
_assertTokens(rule.tokens, ['one', 'two', 'three', 'four', 'five']);
|
||||
});
|
||||
|
||||
it('should collect an invalid list of tokens after a valid selector', () => {
|
||||
const cssCode = 'selector { } six seven eight';
|
||||
ast = parse(cssCode, true);
|
||||
const visitor = new MyVisitor(ast, context);
|
||||
const captures = visitor.captures['visitCssUnknownTokenList'];
|
||||
|
||||
expect(captures.length).toEqual(1);
|
||||
|
||||
const rule = <CssUnknownTokenListAst>_getCaptureAst(captures, 0);
|
||||
_assertTokens(rule.tokens, ['six', 'seven', 'eight']);
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {LifecycleHooks as Hooks, hasLifecycleHook as hasLifecycleHookImpl} from '@angular/compiler/src/lifecycle_reflector';
|
||||
import {hasLifecycleHook as hasLifecycleHookImpl, LifecycleHooks as Hooks} from '@angular/compiler/src/lifecycle_reflector';
|
||||
import {SimpleChanges} from '@angular/core';
|
||||
import {JitReflector} from '@angular/platform-browser-dynamic/src/compiler_reflector';
|
||||
|
||||
@ -17,14 +17,14 @@ function hasLifecycleHook(hook: Hooks, directive: any): boolean {
|
||||
{
|
||||
describe('Create Directive', () => {
|
||||
describe('lifecycle', () => {
|
||||
|
||||
describe('ngOnChanges', () => {
|
||||
it('should be true when the directive has the ngOnChanges method', () => {
|
||||
expect(hasLifecycleHook(Hooks.OnChanges, DirectiveWithOnChangesMethod)).toBe(true);
|
||||
});
|
||||
|
||||
it('should be false otherwise',
|
||||
() => { expect(hasLifecycleHook(Hooks.OnChanges, DirectiveNoHooks)).toBe(false); });
|
||||
it('should be false otherwise', () => {
|
||||
expect(hasLifecycleHook(Hooks.OnChanges, DirectiveNoHooks)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ngOnDestroy', () => {
|
||||
@ -32,16 +32,19 @@ function hasLifecycleHook(hook: Hooks, directive: any): boolean {
|
||||
expect(hasLifecycleHook(Hooks.OnDestroy, DirectiveWithOnDestroyMethod)).toBe(true);
|
||||
});
|
||||
|
||||
it('should be false otherwise',
|
||||
() => { expect(hasLifecycleHook(Hooks.OnDestroy, DirectiveNoHooks)).toBe(false); });
|
||||
it('should be false otherwise', () => {
|
||||
expect(hasLifecycleHook(Hooks.OnDestroy, DirectiveNoHooks)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ngOnInit', () => {
|
||||
it('should be true when the directive has the ngOnInit method',
|
||||
() => { expect(hasLifecycleHook(Hooks.OnInit, DirectiveWithOnInitMethod)).toBe(true); });
|
||||
it('should be true when the directive has the ngOnInit method', () => {
|
||||
expect(hasLifecycleHook(Hooks.OnInit, DirectiveWithOnInitMethod)).toBe(true);
|
||||
});
|
||||
|
||||
it('should be false otherwise',
|
||||
() => { expect(hasLifecycleHook(Hooks.OnInit, DirectiveNoHooks)).toBe(false); });
|
||||
it('should be false otherwise', () => {
|
||||
expect(hasLifecycleHook(Hooks.OnInit, DirectiveNoHooks)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ngDoCheck', () => {
|
||||
@ -49,8 +52,9 @@ function hasLifecycleHook(hook: Hooks, directive: any): boolean {
|
||||
expect(hasLifecycleHook(Hooks.DoCheck, DirectiveWithOnCheckMethod)).toBe(true);
|
||||
});
|
||||
|
||||
it('should be false otherwise',
|
||||
() => { expect(hasLifecycleHook(Hooks.DoCheck, DirectiveNoHooks)).toBe(false); });
|
||||
it('should be false otherwise', () => {
|
||||
expect(hasLifecycleHook(Hooks.DoCheck, DirectiveNoHooks)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ngAfterContentInit', () => {
|
||||
@ -83,8 +87,9 @@ function hasLifecycleHook(hook: Hooks, directive: any): boolean {
|
||||
.toBe(true);
|
||||
});
|
||||
|
||||
it('should be false otherwise',
|
||||
() => { expect(hasLifecycleHook(Hooks.AfterViewInit, DirectiveNoHooks)).toBe(false); });
|
||||
it('should be false otherwise', () => {
|
||||
expect(hasLifecycleHook(Hooks.AfterViewInit, DirectiveNoHooks)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ngAfterViewChecked', () => {
|
||||
|
@ -10,7 +10,7 @@ import {CompilerConfig, preserveWhitespacesDefault} from '@angular/compiler/src/
|
||||
import {DirectiveNormalizer} from '@angular/compiler/src/directive_normalizer';
|
||||
import {ResourceLoader} from '@angular/compiler/src/resource_loader';
|
||||
import {ViewEncapsulation} from '@angular/core/src/metadata/view';
|
||||
import {TestBed, inject} from '@angular/core/testing';
|
||||
import {inject, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {noUndefined} from '../src/util';
|
||||
|
||||
@ -20,7 +20,10 @@ const SOME_MODULE_URL = 'package:some/module/a.js';
|
||||
const SOME_HTTP_MODULE_URL = 'http://some/module/a.js';
|
||||
|
||||
function normalizeTemplate(normalizer: DirectiveNormalizer, o: {
|
||||
moduleUrl?: string; template?: string | null; templateUrl?: string | null; styles?: string[];
|
||||
moduleUrl?: string;
|
||||
template?: string | null;
|
||||
templateUrl?: string | null;
|
||||
styles?: string[];
|
||||
styleUrls?: string[];
|
||||
interpolation?: [string, string] | null;
|
||||
encapsulation?: ViewEncapsulation | null;
|
||||
@ -51,8 +54,7 @@ function normalizeTemplate(normalizer: DirectiveNormalizer, o: {
|
||||
jasmine.createSpy('get').and.callFake((url: string) => `resource(${url})`);
|
||||
const resourceLoader = {get: resourceLoaderSpy};
|
||||
TestBed.configureCompiler({
|
||||
providers:
|
||||
[...TEST_COMPILER_PROVIDERS, {provide: ResourceLoader, useValue: resourceLoader}]
|
||||
providers: [...TEST_COMPILER_PROVIDERS, {provide: ResourceLoader, useValue: resourceLoader}]
|
||||
});
|
||||
});
|
||||
|
||||
@ -64,12 +66,12 @@ function normalizeTemplate(normalizer: DirectiveNormalizer, o: {
|
||||
}));
|
||||
it('should throw if template is not a string',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
expect(() => normalizeTemplate(normalizer, {template: <any>{}}))
|
||||
expect(() => normalizeTemplate(normalizer, {template: <any> {}}))
|
||||
.toThrowError('The template specified for component SomeComp is not a string');
|
||||
}));
|
||||
it('should throw if templateUrl is not a string',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
expect(() => normalizeTemplate(normalizer, {templateUrl: <any>{}}))
|
||||
expect(() => normalizeTemplate(normalizer, {templateUrl: <any> {}}))
|
||||
.toThrowError('The templateUrl specified for component SomeComp is not a string');
|
||||
}));
|
||||
it('should throw if both template and templateUrl are defined',
|
||||
@ -89,11 +91,9 @@ function normalizeTemplate(normalizer: DirectiveNormalizer, o: {
|
||||
.toThrowError(
|
||||
'The preserveWhitespaces option for component SomeComp must be a boolean');
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
describe('inline template', () => {
|
||||
|
||||
it('should store the template',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
const template = <CompileTemplateMetadata>normalizeTemplate(normalizer, {
|
||||
@ -174,7 +174,6 @@ function normalizeTemplate(normalizer: DirectiveNormalizer, o: {
|
||||
}));
|
||||
|
||||
describe('externalStylesheets', () => {
|
||||
|
||||
it('should load an external stylesheet',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
const template = <CompileTemplateMetadata>normalizeTemplate(
|
||||
@ -222,7 +221,6 @@ function normalizeTemplate(normalizer: DirectiveNormalizer, o: {
|
||||
|
||||
expect(resourceLoaderSpy).toHaveBeenCalledTimes(1);
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
describe('normalizeLoadedTemplate', () => {
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {Component, Directive, Injector} from '@angular/core';
|
||||
import {TestBed, inject} from '@angular/core/testing';
|
||||
import {inject, TestBed} from '@angular/core/testing';
|
||||
import {JitReflector} from '@angular/platform-browser-dynamic/src/compiler_reflector';
|
||||
|
||||
import {MockDirectiveResolver} from '../testing';
|
||||
|
@ -32,13 +32,16 @@ class SomeDirectiveWithOutputs {
|
||||
@Directive({selector: 'someDirective'})
|
||||
class SomeDirectiveWithSetterProps {
|
||||
@Input('renamed')
|
||||
set a(value: any) {}
|
||||
set a(value: any) {
|
||||
}
|
||||
}
|
||||
|
||||
@Directive({selector: 'someDirective'})
|
||||
class SomeDirectiveWithGetterOutputs {
|
||||
@Output('renamed')
|
||||
get a(): any { return null; }
|
||||
get a(): any {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Directive({selector: 'someDirective', host: {'[c]': 'c'}})
|
||||
@ -51,9 +54,11 @@ class SomeDirectiveWithHostBindings {
|
||||
@Directive({selector: 'someDirective', host: {'(c)': 'onC()'}})
|
||||
class SomeDirectiveWithHostListeners {
|
||||
@HostListener('a')
|
||||
onA() {}
|
||||
onA() {
|
||||
}
|
||||
@HostListener('b', ['$event.value'])
|
||||
onB(value: any) {}
|
||||
onB(value: any) {
|
||||
}
|
||||
}
|
||||
|
||||
@Directive({selector: 'someDirective', queries: {'cs': new ContentChildren('c')}})
|
||||
@ -101,13 +106,15 @@ class SomeDirectiveWithSameHostBindingAndInput {
|
||||
@Directive({selector: 'someDirective'})
|
||||
class SomeDirectiveWithMalformedHostBinding1 {
|
||||
@HostBinding('(a)')
|
||||
onA() {}
|
||||
onA() {
|
||||
}
|
||||
}
|
||||
|
||||
@Directive({selector: 'someDirective'})
|
||||
class SomeDirectiveWithMalformedHostBinding2 {
|
||||
@HostBinding('[a]')
|
||||
onA() {}
|
||||
onA() {
|
||||
}
|
||||
}
|
||||
|
||||
class SomeDirectiveWithoutMetadata {}
|
||||
@ -116,7 +123,9 @@ class SomeDirectiveWithoutMetadata {}
|
||||
describe('DirectiveResolver', () => {
|
||||
let resolver: DirectiveResolver;
|
||||
|
||||
beforeEach(() => { resolver = new DirectiveResolver(new JitReflector()); });
|
||||
beforeEach(() => {
|
||||
resolver = new DirectiveResolver(new JitReflector());
|
||||
});
|
||||
|
||||
it('should read out the Directive metadata', () => {
|
||||
const directiveMetadata = resolver.resolve(SomeDirective);
|
||||
@ -204,8 +213,7 @@ class SomeDirectiveWithoutMetadata {}
|
||||
it('should prefer @Input over @Directive.inputs', () => {
|
||||
@Directive({selector: 'someDirective', inputs: ['a']})
|
||||
class SomeDirectiveWithDuplicateInputs {
|
||||
@Input('a')
|
||||
propA: any;
|
||||
@Input('a') propA: any;
|
||||
}
|
||||
const directiveMetadata = resolver.resolve(SomeDirectiveWithDuplicateInputs);
|
||||
expect(directiveMetadata.inputs).toEqual(['propA: a']);
|
||||
@ -214,17 +222,13 @@ class SomeDirectiveWithoutMetadata {}
|
||||
it('should support inheriting inputs', () => {
|
||||
@Directive({selector: 'p'})
|
||||
class Parent {
|
||||
@Input()
|
||||
p1: any;
|
||||
@Input('p21')
|
||||
p2: any;
|
||||
@Input() p1: any;
|
||||
@Input('p21') p2: any;
|
||||
}
|
||||
|
||||
class Child extends Parent {
|
||||
@Input('p22')
|
||||
p2: any;
|
||||
@Input()
|
||||
p3: any;
|
||||
@Input('p22') p2: any;
|
||||
@Input() p3: any;
|
||||
}
|
||||
|
||||
const directiveMetadata = resolver.resolve(Child);
|
||||
@ -264,8 +268,7 @@ class SomeDirectiveWithoutMetadata {}
|
||||
it('should prefer @Output over @Directive.outputs', () => {
|
||||
@Directive({selector: 'someDirective', outputs: ['a']})
|
||||
class SomeDirectiveWithDuplicateOutputs {
|
||||
@Output('a')
|
||||
propA: any;
|
||||
@Output('a') propA: any;
|
||||
}
|
||||
const directiveMetadata = resolver.resolve(SomeDirectiveWithDuplicateOutputs);
|
||||
expect(directiveMetadata.outputs).toEqual(['propA: a']);
|
||||
@ -274,17 +277,13 @@ class SomeDirectiveWithoutMetadata {}
|
||||
it('should support inheriting outputs', () => {
|
||||
@Directive({selector: 'p'})
|
||||
class Parent {
|
||||
@Output()
|
||||
p1: any;
|
||||
@Output('p21')
|
||||
p2: any;
|
||||
@Output() p1: any;
|
||||
@Output('p21') p2: any;
|
||||
}
|
||||
|
||||
class Child extends Parent {
|
||||
@Output('p22')
|
||||
p2: any;
|
||||
@Output()
|
||||
p3: any;
|
||||
@Output('p22') p2: any;
|
||||
@Output() p3: any;
|
||||
}
|
||||
|
||||
const directiveMetadata = resolver.resolve(Child);
|
||||
@ -324,17 +323,13 @@ class SomeDirectiveWithoutMetadata {}
|
||||
it('should support inheriting host bindings', () => {
|
||||
@Directive({selector: 'p'})
|
||||
class Parent {
|
||||
@HostBinding()
|
||||
p1: any;
|
||||
@HostBinding('p21')
|
||||
p2: any;
|
||||
@HostBinding() p1: any;
|
||||
@HostBinding('p21') p2: any;
|
||||
}
|
||||
|
||||
class Child extends Parent {
|
||||
@HostBinding('p22')
|
||||
p2: any;
|
||||
@HostBinding()
|
||||
p3: any;
|
||||
@HostBinding('p22') p2: any;
|
||||
@HostBinding() p3: any;
|
||||
}
|
||||
|
||||
const directiveMetadata = resolver.resolve(Child);
|
||||
@ -346,16 +341,20 @@ class SomeDirectiveWithoutMetadata {}
|
||||
@Directive({selector: 'p'})
|
||||
class Parent {
|
||||
@HostListener('p1')
|
||||
p1() {}
|
||||
p1() {
|
||||
}
|
||||
@HostListener('p21')
|
||||
p2() {}
|
||||
p2() {
|
||||
}
|
||||
}
|
||||
|
||||
class Child extends Parent {
|
||||
@HostListener('p22')
|
||||
p2() {}
|
||||
p2() {
|
||||
}
|
||||
@HostListener('p3')
|
||||
p3() {}
|
||||
p3() {
|
||||
}
|
||||
}
|
||||
|
||||
const directiveMetadata = resolver.resolve(Child);
|
||||
@ -366,19 +365,20 @@ class SomeDirectiveWithoutMetadata {}
|
||||
it('should combine host bindings and listeners during inheritance', () => {
|
||||
@Directive({selector: 'p'})
|
||||
class Parent {
|
||||
@HostListener('p11') @HostListener('p12')
|
||||
p1() {}
|
||||
@HostListener('p11')
|
||||
@HostListener('p12')
|
||||
p1() {
|
||||
}
|
||||
|
||||
@HostBinding('p21') @HostBinding('p22')
|
||||
p2: any;
|
||||
@HostBinding('p21') @HostBinding('p22') p2: any;
|
||||
}
|
||||
|
||||
class Child extends Parent {
|
||||
@HostListener('c1')
|
||||
p1() {}
|
||||
p1() {
|
||||
}
|
||||
|
||||
@HostBinding('c2')
|
||||
p2: any;
|
||||
@HostBinding('c2') p2: any;
|
||||
}
|
||||
|
||||
const directiveMetadata = resolver.resolve(Child);
|
||||
@ -421,17 +421,13 @@ class SomeDirectiveWithoutMetadata {}
|
||||
it('should support inheriting queries', () => {
|
||||
@Directive({selector: 'p'})
|
||||
class Parent {
|
||||
@ContentChild('p1')
|
||||
p1: any;
|
||||
@ContentChild('p21')
|
||||
p2: any;
|
||||
@ContentChild('p1') p1: any;
|
||||
@ContentChild('p21') p2: any;
|
||||
}
|
||||
|
||||
class Child extends Parent {
|
||||
@ContentChild('p22')
|
||||
p2: any;
|
||||
@ContentChild('p3')
|
||||
p3: any;
|
||||
@ContentChild('p22') p2: any;
|
||||
@ContentChild('p3') p3: any;
|
||||
}
|
||||
|
||||
const directiveMetadata = resolver.resolve(Child);
|
||||
|
@ -101,14 +101,17 @@ function expectErrorToken(token: Token, index: any, end: number, message: string
|
||||
expectNumberToken(tokens[0], 0, 2, 88);
|
||||
});
|
||||
|
||||
it('should tokenize numbers within index ops',
|
||||
() => { expectNumberToken(lex('a[22]')[2], 2, 4, 22); });
|
||||
it('should tokenize numbers within index ops', () => {
|
||||
expectNumberToken(lex('a[22]')[2], 2, 4, 22);
|
||||
});
|
||||
|
||||
it('should tokenize simple quoted strings',
|
||||
() => { expectStringToken(lex('"a"')[0], 0, 3, 'a'); });
|
||||
it('should tokenize simple quoted strings', () => {
|
||||
expectStringToken(lex('"a"')[0], 0, 3, 'a');
|
||||
});
|
||||
|
||||
it('should tokenize quoted strings with escaped quotes',
|
||||
() => { expectStringToken(lex('"a\\""')[0], 0, 5, 'a"'); });
|
||||
it('should tokenize quoted strings with escaped quotes', () => {
|
||||
expectStringToken(lex('"a\\""')[0], 0, 5, 'a"');
|
||||
});
|
||||
|
||||
it('should tokenize a string', () => {
|
||||
const tokens: Token[] = lex('j-a.bc[22]+1.3|f:\'a\\\'c\':"d\\"e"');
|
||||
@ -213,7 +216,9 @@ function expectErrorToken(token: Token, index: any, end: number, message: string
|
||||
expectCharacterToken(tokens[13], 16, 17, ')');
|
||||
});
|
||||
|
||||
it('should tokenize number', () => { expectNumberToken(lex('0.5')[0], 0, 3, 0.5); });
|
||||
it('should tokenize number', () => {
|
||||
expectNumberToken(lex('0.5')[0], 0, 3, 0.5);
|
||||
});
|
||||
|
||||
it('should tokenize number with exponent', () => {
|
||||
let tokens: Token[] = lex('0.5E-10');
|
||||
@ -233,8 +238,9 @@ function expectErrorToken(token: Token, index: any, end: number, message: string
|
||||
'Lexer Error: Invalid exponent at column 4 in expression [0.5E-A]');
|
||||
});
|
||||
|
||||
it('should tokenize number starting with a dot',
|
||||
() => { expectNumberToken(lex('.5')[0], 0, 2, 0.5); });
|
||||
it('should tokenize number starting with a dot', () => {
|
||||
expectNumberToken(lex('.5')[0], 0, 2, 0.5);
|
||||
});
|
||||
|
||||
it('should throw error on invalid unicode', () => {
|
||||
expectErrorToken(
|
||||
@ -242,11 +248,13 @@ function expectErrorToken(token: Token, index: any, end: number, message: string
|
||||
'Lexer Error: Invalid unicode escape [\\u1\'\'b] at column 2 in expression [\'\\u1\'\'bla\']');
|
||||
});
|
||||
|
||||
it('should tokenize hash as operator',
|
||||
() => { expectOperatorToken(lex('#')[0], 0, 1, '#'); });
|
||||
it('should tokenize hash as operator', () => {
|
||||
expectOperatorToken(lex('#')[0], 0, 1, '#');
|
||||
});
|
||||
|
||||
it('should tokenize ?. as operator',
|
||||
() => { expectOperatorToken(lex('?.')[0], 0, 2, '?.'); });
|
||||
it('should tokenize ?. as operator', () => {
|
||||
expectOperatorToken(lex('?.')[0], 0, 2, '?.');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -17,16 +17,22 @@ import {validate} from './utils/validator';
|
||||
|
||||
describe('parser', () => {
|
||||
describe('parseAction', () => {
|
||||
it('should parse numbers', () => { checkAction('1'); });
|
||||
it('should parse numbers', () => {
|
||||
checkAction('1');
|
||||
});
|
||||
|
||||
it('should parse strings', () => {
|
||||
checkAction('\'1\'', '"1"');
|
||||
checkAction('"1"');
|
||||
});
|
||||
|
||||
it('should parse null', () => { checkAction('null'); });
|
||||
it('should parse null', () => {
|
||||
checkAction('null');
|
||||
});
|
||||
|
||||
it('should parse undefined', () => { checkAction('undefined'); });
|
||||
it('should parse undefined', () => {
|
||||
checkAction('undefined');
|
||||
});
|
||||
|
||||
it('should parse unary - expressions', () => {
|
||||
checkAction('-1', '0 - 1');
|
||||
@ -47,10 +53,13 @@ describe('parser', () => {
|
||||
checkAction('a!!!!.b');
|
||||
});
|
||||
|
||||
it('should parse multiplicative expressions',
|
||||
() => { checkAction('3*4/2%5', '3 * 4 / 2 % 5'); });
|
||||
it('should parse multiplicative expressions', () => {
|
||||
checkAction('3*4/2%5', '3 * 4 / 2 % 5');
|
||||
});
|
||||
|
||||
it('should parse additive expressions', () => { checkAction('3 + 6 - 2'); });
|
||||
it('should parse additive expressions', () => {
|
||||
checkAction('3 + 6 - 2');
|
||||
});
|
||||
|
||||
it('should parse relational expressions', () => {
|
||||
checkAction('2 < 3');
|
||||
@ -74,14 +83,21 @@ describe('parser', () => {
|
||||
checkAction('true || false');
|
||||
});
|
||||
|
||||
it('should parse grouped expressions', () => { checkAction('(1 + 2) * 3', '1 + 2 * 3'); });
|
||||
it('should parse grouped expressions', () => {
|
||||
checkAction('(1 + 2) * 3', '1 + 2 * 3');
|
||||
});
|
||||
|
||||
it('should ignore comments in expressions', () => { checkAction('a //comment', 'a'); });
|
||||
it('should ignore comments in expressions', () => {
|
||||
checkAction('a //comment', 'a');
|
||||
});
|
||||
|
||||
it('should retain // in string literals',
|
||||
() => { checkAction(`"http://www.google.com"`, `"http://www.google.com"`); });
|
||||
it('should retain // in string literals', () => {
|
||||
checkAction(`"http://www.google.com"`, `"http://www.google.com"`);
|
||||
});
|
||||
|
||||
it('should parse an empty string', () => { checkAction(''); });
|
||||
it('should parse an empty string', () => {
|
||||
checkAction('');
|
||||
});
|
||||
|
||||
describe('literals', () => {
|
||||
it('should parse array', () => {
|
||||
@ -133,7 +149,9 @@ describe('parser', () => {
|
||||
});
|
||||
|
||||
describe('functional calls', () => {
|
||||
it('should parse function calls', () => { checkAction('fn()(1, 2)'); });
|
||||
it('should parse function calls', () => {
|
||||
checkAction('fn()(1, 2)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('conditional', () => {
|
||||
@ -154,20 +172,26 @@ describe('parser', () => {
|
||||
checkAction('a = 123; b = 234;');
|
||||
});
|
||||
|
||||
it('should report on safe field assignments',
|
||||
() => { expectActionError('a?.a = 123', 'cannot be used in the assignment'); });
|
||||
it('should report on safe field assignments', () => {
|
||||
expectActionError('a?.a = 123', 'cannot be used in the assignment');
|
||||
});
|
||||
|
||||
it('should support array updates', () => { checkAction('a[0] = 200'); });
|
||||
it('should support array updates', () => {
|
||||
checkAction('a[0] = 200');
|
||||
});
|
||||
});
|
||||
|
||||
it('should error when using pipes',
|
||||
() => { expectActionError('x|blah', 'Cannot have a pipe'); });
|
||||
it('should error when using pipes', () => {
|
||||
expectActionError('x|blah', 'Cannot have a pipe');
|
||||
});
|
||||
|
||||
it('should store the source in the result',
|
||||
() => { expect(parseAction('someExpr', 'someExpr')); });
|
||||
it('should store the source in the result', () => {
|
||||
expect(parseAction('someExpr', 'someExpr'));
|
||||
});
|
||||
|
||||
it('should store the passed-in location',
|
||||
() => { expect(parseAction('someExpr', 'location').location).toBe('location'); });
|
||||
it('should store the passed-in location', () => {
|
||||
expect(parseAction('someExpr', 'location').location).toBe('location');
|
||||
});
|
||||
|
||||
it('should report when encountering interpolation', () => {
|
||||
expectActionError('{{a()}}', 'Got interpolation ({{}}) where expression was expected');
|
||||
@ -175,11 +199,13 @@ describe('parser', () => {
|
||||
});
|
||||
|
||||
describe('general error handling', () => {
|
||||
it('should report an unexpected token',
|
||||
() => { expectActionError('[1,2] trac', 'Unexpected token \'trac\''); });
|
||||
it('should report an unexpected token', () => {
|
||||
expectActionError('[1,2] trac', 'Unexpected token \'trac\'');
|
||||
});
|
||||
|
||||
it('should report reasonable error for unconsumed tokens',
|
||||
() => { expectActionError(')', 'Unexpected token ) at column 1 in [)]'); });
|
||||
it('should report reasonable error for unconsumed tokens', () => {
|
||||
expectActionError(')', 'Unexpected token ) at column 1 in [)]');
|
||||
});
|
||||
|
||||
it('should report a missing expected token', () => {
|
||||
expectActionError('a(b', 'Missing expected ) at the end of the expression [a(b]');
|
||||
@ -206,12 +232,17 @@ describe('parser', () => {
|
||||
expectBindingError('"Foo"|"uppercase"', 'identifier or keyword');
|
||||
});
|
||||
|
||||
it('should parse quoted expressions', () => { checkBinding('a:b', 'a:b'); });
|
||||
it('should parse quoted expressions', () => {
|
||||
checkBinding('a:b', 'a:b');
|
||||
});
|
||||
|
||||
it('should not crash when prefix part is not tokenizable',
|
||||
() => { checkBinding('"a:b"', '"a:b"'); });
|
||||
it('should not crash when prefix part is not tokenizable', () => {
|
||||
checkBinding('"a:b"', '"a:b"');
|
||||
});
|
||||
|
||||
it('should ignore whitespace around quote prefix', () => { checkBinding(' a :b', 'a:b'); });
|
||||
it('should ignore whitespace around quote prefix', () => {
|
||||
checkBinding(' a :b', 'a:b');
|
||||
});
|
||||
|
||||
it('should refuse prefixes that are not single identifiers', () => {
|
||||
expectBindingError('a + b:c', '');
|
||||
@ -219,31 +250,41 @@ describe('parser', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should store the source in the result',
|
||||
() => { expect(parseBinding('someExpr').source).toBe('someExpr'); });
|
||||
it('should store the source in the result', () => {
|
||||
expect(parseBinding('someExpr').source).toBe('someExpr');
|
||||
});
|
||||
|
||||
it('should store the passed-in location',
|
||||
() => { expect(parseBinding('someExpr', 'location').location).toBe('location'); });
|
||||
it('should store the passed-in location', () => {
|
||||
expect(parseBinding('someExpr', 'location').location).toBe('location');
|
||||
});
|
||||
|
||||
it('should report chain expressions',
|
||||
() => { expectError(parseBinding('1;2'), 'contain chained expression'); });
|
||||
it('should report chain expressions', () => {
|
||||
expectError(parseBinding('1;2'), 'contain chained expression');
|
||||
});
|
||||
|
||||
it('should report assignment',
|
||||
() => { expectError(parseBinding('a=2'), 'contain assignments'); });
|
||||
it('should report assignment', () => {
|
||||
expectError(parseBinding('a=2'), 'contain assignments');
|
||||
});
|
||||
|
||||
it('should report when encountering interpolation', () => {
|
||||
expectBindingError('{{a.b}}', 'Got interpolation ({{}}) where expression was expected');
|
||||
});
|
||||
|
||||
it('should parse conditional expression', () => { checkBinding('a < b ? a : b'); });
|
||||
it('should parse conditional expression', () => {
|
||||
checkBinding('a < b ? a : b');
|
||||
});
|
||||
|
||||
it('should ignore comments in bindings', () => { checkBinding('a //comment', 'a'); });
|
||||
it('should ignore comments in bindings', () => {
|
||||
checkBinding('a //comment', 'a');
|
||||
});
|
||||
|
||||
it('should retain // in string literals',
|
||||
() => { checkBinding(`"http://www.google.com"`, `"http://www.google.com"`); });
|
||||
|
||||
it('should retain // in : microsyntax', () => { checkBinding('one:a//b', 'one:a//b'); });
|
||||
it('should retain // in string literals', () => {
|
||||
checkBinding(`"http://www.google.com"`, `"http://www.google.com"`);
|
||||
});
|
||||
|
||||
it('should retain // in : microsyntax', () => {
|
||||
checkBinding('one:a//b', 'one:a//b');
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseTemplateBindings', () => {
|
||||
@ -555,11 +596,12 @@ describe('parser', () => {
|
||||
});
|
||||
|
||||
describe('parseInterpolation', () => {
|
||||
it('should return null if no interpolation',
|
||||
() => { expect(parseInterpolation('nothing')).toBe(null); });
|
||||
it('should return null if no interpolation', () => {
|
||||
expect(parseInterpolation('nothing')).toBe(null);
|
||||
});
|
||||
|
||||
it('should parse no prefix/suffix interpolation', () => {
|
||||
const ast = parseInterpolation('{{a}}') !.ast as Interpolation;
|
||||
const ast = parseInterpolation('{{a}}')!.ast as Interpolation;
|
||||
expect(ast.strings).toEqual(['', '']);
|
||||
expect(ast.expressions.length).toEqual(1);
|
||||
expect(ast.expressions[0].name).toEqual('a');
|
||||
@ -567,22 +609,23 @@ describe('parser', () => {
|
||||
|
||||
it('should parse prefix/suffix with multiple interpolation', () => {
|
||||
const originalExp = 'before {{ a }} middle {{ b }} after';
|
||||
const ast = parseInterpolation(originalExp) !.ast;
|
||||
const ast = parseInterpolation(originalExp)!.ast;
|
||||
expect(unparse(ast)).toEqual(originalExp);
|
||||
validate(ast);
|
||||
});
|
||||
|
||||
it('should report empty interpolation expressions', () => {
|
||||
expectError(
|
||||
parseInterpolation('{{}}') !,
|
||||
'Blank expressions are not allowed in interpolated strings');
|
||||
parseInterpolation('{{}}')!, 'Blank expressions are not allowed in interpolated strings');
|
||||
|
||||
expectError(
|
||||
parseInterpolation('foo {{ }}') !,
|
||||
parseInterpolation('foo {{ }}')!,
|
||||
'Parser Error: Blank expressions are not allowed in interpolated strings');
|
||||
});
|
||||
|
||||
it('should parse conditional expression', () => { checkInterpolation('{{ a < b ? a : b }}'); });
|
||||
it('should parse conditional expression', () => {
|
||||
checkInterpolation('{{ a < b ? a : b }}');
|
||||
});
|
||||
|
||||
it('should parse expression with newline characters', () => {
|
||||
checkInterpolation(`{{ 'foo' +\n 'bar' +\r 'baz' }}`, `{{ "foo" + "bar" + "baz" }}`);
|
||||
@ -591,15 +634,16 @@ describe('parser', () => {
|
||||
it('should support custom interpolation', () => {
|
||||
const parser = new Parser(new Lexer());
|
||||
const ast =
|
||||
parser.parseInterpolation('{% a %}', null, 0, {start: '{%', end: '%}'}) !.ast as any;
|
||||
parser.parseInterpolation('{% a %}', null, 0, {start: '{%', end: '%}'})!.ast as any;
|
||||
expect(ast.strings).toEqual(['', '']);
|
||||
expect(ast.expressions.length).toEqual(1);
|
||||
expect(ast.expressions[0].name).toEqual('a');
|
||||
});
|
||||
|
||||
describe('comments', () => {
|
||||
it('should ignore comments in interpolation expressions',
|
||||
() => { checkInterpolation('{{a //comment}}', '{{ a }}'); });
|
||||
it('should ignore comments in interpolation expressions', () => {
|
||||
checkInterpolation('{{a //comment}}', '{{ a }}');
|
||||
});
|
||||
|
||||
it('should retain // in single quote strings', () => {
|
||||
checkInterpolation(`{{ 'http://www.google.com' }}`, `{{ "http://www.google.com" }}`);
|
||||
@ -609,18 +653,19 @@ describe('parser', () => {
|
||||
checkInterpolation(`{{ "http://www.google.com" }}`, `{{ "http://www.google.com" }}`);
|
||||
});
|
||||
|
||||
it('should ignore comments after string literals',
|
||||
() => { checkInterpolation(`{{ "a//b" //comment }}`, `{{ "a//b" }}`); });
|
||||
it('should ignore comments after string literals', () => {
|
||||
checkInterpolation(`{{ "a//b" //comment }}`, `{{ "a//b" }}`);
|
||||
});
|
||||
|
||||
it('should retain // in complex strings', () => {
|
||||
checkInterpolation(
|
||||
`{{"//a\'//b\`//c\`//d\'//e" //comment}}`, `{{ "//a\'//b\`//c\`//d\'//e" }}`);
|
||||
});
|
||||
|
||||
it('should retain // in nested, unterminated strings',
|
||||
() => { checkInterpolation(`{{ "a\'b\`" //comment}}`, `{{ "a\'b\`" }}`); });
|
||||
it('should retain // in nested, unterminated strings', () => {
|
||||
checkInterpolation(`{{ "a\'b\`" //comment}}`, `{{ "a\'b\`" }}`);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('parseSimpleBinding', () => {
|
||||
@ -670,12 +715,12 @@ describe('parser', () => {
|
||||
|
||||
describe('offsets', () => {
|
||||
it('should retain the offsets of an interpolation', () => {
|
||||
const interpolations = splitInterpolation('{{a}} {{b}} {{c}}') !;
|
||||
const interpolations = splitInterpolation('{{a}} {{b}} {{c}}')!;
|
||||
expect(interpolations.offsets).toEqual([2, 9, 16]);
|
||||
});
|
||||
|
||||
it('should retain the offsets into the expression AST of interpolations', () => {
|
||||
const source = parseInterpolation('{{a}} {{b}} {{c}}') !;
|
||||
const source = parseInterpolation('{{a}} {{b}} {{c}}')!;
|
||||
const interpolation = source.ast as Interpolation;
|
||||
expect(interpolation.expressions.map(e => e.span.start)).toEqual([2, 9, 16]);
|
||||
});
|
||||
@ -722,7 +767,7 @@ function parseSimpleBinding(text: string, location: any = null, offset: number =
|
||||
}
|
||||
|
||||
function checkInterpolation(exp: string, expected?: string) {
|
||||
const ast = parseInterpolation(exp) !;
|
||||
const ast = parseInterpolation(exp)!;
|
||||
if (expected == null) expected = exp;
|
||||
expect(unparse(ast)).toEqual(expected);
|
||||
validate(ast);
|
||||
|
@ -12,9 +12,9 @@ import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../../src/ml
|
||||
class Unparser implements AstVisitor {
|
||||
private static _quoteRegExp = /"/g;
|
||||
// TODO(issue/24571): remove '!'.
|
||||
private _expression !: string;
|
||||
private _expression!: string;
|
||||
// TODO(issue/24571): remove '!'.
|
||||
private _interpolationConfig !: InterpolationConfig;
|
||||
private _interpolationConfig!: InterpolationConfig;
|
||||
|
||||
unparse(ast: AST, interpolationConfig: InterpolationConfig) {
|
||||
this._expression = '';
|
||||
@ -69,7 +69,7 @@ class Unparser implements AstVisitor {
|
||||
}
|
||||
|
||||
visitFunctionCall(ast: FunctionCall, context: any) {
|
||||
this._visit(ast.target !);
|
||||
this._visit(ast.target!);
|
||||
this._expression += '(';
|
||||
let isFirst = true;
|
||||
ast.args.forEach(arg => {
|
||||
@ -137,7 +137,7 @@ class Unparser implements AstVisitor {
|
||||
|
||||
visitLiteralPrimitive(ast: LiteralPrimitive, context: any) {
|
||||
if (typeof ast.value === 'string') {
|
||||
this._expression += `"${ast.value.replace( Unparser._quoteRegExp, '\"')}"`;
|
||||
this._expression += `"${ast.value.replace(Unparser._quoteRegExp, '\"')}"`;
|
||||
} else {
|
||||
this._expression += `${ast.value}`;
|
||||
}
|
||||
@ -186,7 +186,9 @@ class Unparser implements AstVisitor {
|
||||
this._expression += `${ast.prefix}:${ast.uninterpretedExpression}`;
|
||||
}
|
||||
|
||||
private _visit(ast: AST) { ast.visit(this); }
|
||||
private _visit(ast: AST) {
|
||||
ast.visit(this);
|
||||
}
|
||||
}
|
||||
|
||||
const sharedUnparser = new Unparser();
|
||||
|
@ -22,8 +22,8 @@ class ASTValidator extends RecursiveAstVisitor {
|
||||
if (!inSpan(ast.span, this.parentSpan)) {
|
||||
if (this.parentSpan) {
|
||||
const parentSpan = this.parentSpan as ParseSpan;
|
||||
throw Error(
|
||||
`Invalid AST span [expected (${ast.span.start}, ${ast.span.end}) to be in (${parentSpan.start}, ${parentSpan.end}) for ${unparse(ast)}`);
|
||||
throw Error(`Invalid AST span [expected (${ast.span.start}, ${ast.span.end}) to be in (${
|
||||
parentSpan.start}, ${parentSpan.end}) for ${unparse(ast)}`);
|
||||
} else {
|
||||
throw Error(`Invalid root AST span for ${unparse(ast)}`);
|
||||
}
|
||||
@ -111,7 +111,7 @@ class ASTValidator extends RecursiveAstVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
function inSpan(span: ParseSpan, parentSpan: ParseSpan | undefined): parentSpan is ParseSpan {
|
||||
function inSpan(span: ParseSpan, parentSpan: ParseSpan|undefined): parentSpan is ParseSpan {
|
||||
return !parentSpan || (span.start >= parentSpan.start && span.end <= parentSpan.end);
|
||||
}
|
||||
|
||||
|
@ -27,14 +27,17 @@ import {computeMsgId, digest, sha1} from '../../src/i18n/digest';
|
||||
});
|
||||
|
||||
describe('sha1', () => {
|
||||
it('should work on empty strings',
|
||||
() => { expect(sha1('')).toEqual('da39a3ee5e6b4b0d3255bfef95601890afd80709'); });
|
||||
it('should work on empty strings', () => {
|
||||
expect(sha1('')).toEqual('da39a3ee5e6b4b0d3255bfef95601890afd80709');
|
||||
});
|
||||
|
||||
it('should returns the sha1 of "hello world"',
|
||||
() => { expect(sha1('abc')).toEqual('a9993e364706816aba3e25717850c26c9cd0d89d'); });
|
||||
it('should returns the sha1 of "hello world"', () => {
|
||||
expect(sha1('abc')).toEqual('a9993e364706816aba3e25717850c26c9cd0d89d');
|
||||
});
|
||||
|
||||
it('should returns the sha1 of unicode strings',
|
||||
() => { expect(sha1('你好,世界')).toEqual('3becb03b015ed48050611c8d7afe4b88f70d5a20'); });
|
||||
it('should returns the sha1 of unicode strings', () => {
|
||||
expect(sha1('你好,世界')).toEqual('3becb03b015ed48050611c8d7afe4b88f70d5a20');
|
||||
});
|
||||
|
||||
it('should support arbitrary string size', () => {
|
||||
// node.js reference code:
|
||||
@ -89,8 +92,9 @@ import {computeMsgId, digest, sha1} from '../../src/i18n/digest';
|
||||
'': '4416290763660062288',
|
||||
};
|
||||
|
||||
Object.keys(fixtures).forEach(
|
||||
msg => { expect(computeMsgId(msg, '')).toEqual(fixtures[msg]); });
|
||||
Object.keys(fixtures).forEach(msg => {
|
||||
expect(computeMsgId(msg, '')).toEqual(fixtures[msg]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should work on well known inputs with meaning', () => {
|
||||
@ -100,8 +104,9 @@ import {computeMsgId, digest, sha1} from '../../src/i18n/digest';
|
||||
'3993998469942805487': ['View', 'Gmail UI'],
|
||||
};
|
||||
|
||||
Object.keys(fixtures).forEach(
|
||||
id => { expect(computeMsgId(fixtures[id][0], fixtures[id][1])).toEqual(id); });
|
||||
Object.keys(fixtures).forEach(id => {
|
||||
expect(computeMsgId(fixtures[id][0], fixtures[id][1])).toEqual(id);
|
||||
});
|
||||
});
|
||||
|
||||
it('should support arbitrary string size', () => {
|
||||
@ -116,7 +121,6 @@ import {computeMsgId, digest, sha1} from '../../src/i18n/digest';
|
||||
}
|
||||
expect(computeMsgId(result, '')).toEqual('2122606631351252558');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -94,8 +94,9 @@ import {serializeNodes as serializeHtmlNodes} from '../ml_parser/util/util';
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not create a message for empty elements',
|
||||
() => { expect(extract('<div i18n="m|d"></div>')).toEqual([]); });
|
||||
it('should not create a message for empty elements', () => {
|
||||
expect(extract('<div i18n="m|d"></div>')).toEqual([]);
|
||||
});
|
||||
|
||||
it('should ignore implicit elements in translatable elements', () => {
|
||||
expect(extract('<div i18n="m|d"><p></p></div>', ['p'])).toEqual([
|
||||
@ -138,7 +139,8 @@ import {serializeNodes as serializeHtmlNodes} from '../ml_parser/util/util';
|
||||
],
|
||||
[
|
||||
[
|
||||
'text', '<ph tag name="START_PARAGRAPH">html, <ph tag' +
|
||||
'text',
|
||||
'<ph tag name="START_PARAGRAPH">html, <ph tag' +
|
||||
' name="START_BOLD_TEXT">nested</ph name="CLOSE_BOLD_TEXT"></ph name="CLOSE_PARAGRAPH">',
|
||||
'<ph icu name="ICU">{count, plural, =0 {[<ph tag' +
|
||||
' name="START_TAG_SPAN">html</ph name="CLOSE_TAG_SPAN">]}}</ph>',
|
||||
@ -156,8 +158,9 @@ import {serializeNodes as serializeHtmlNodes} from '../ml_parser/util/util';
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not create a message for empty blocks',
|
||||
() => { expect(extract(`<!-- i18n: meaning1|desc1 --><!-- /i18n -->`)).toEqual([]); });
|
||||
it('should not create a message for empty blocks', () => {
|
||||
expect(extract(`<!-- i18n: meaning1|desc1 --><!-- /i18n -->`)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ICU messages', () => {
|
||||
@ -199,8 +202,9 @@ import {serializeNodes as serializeHtmlNodes} from '../ml_parser/util/util';
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not extract ICU messages outside of i18n sections',
|
||||
() => { expect(extract('{count, plural, =0 {text}}')).toEqual([]); });
|
||||
it('should not extract ICU messages outside of i18n sections', () => {
|
||||
expect(extract('{count, plural, =0 {text}}')).toEqual([]);
|
||||
});
|
||||
|
||||
it('should ignore nested ICU messages', () => {
|
||||
expect(extract('<div i18n="m|d">{count, plural, =0 { {sex, select, male {m}} }}</div>'))
|
||||
@ -280,8 +284,9 @@ import {serializeNodes as serializeHtmlNodes} from '../ml_parser/util/util';
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not create a message for empty attributes',
|
||||
() => { expect(extract('<div i18n-title="m|d" title></div>')).toEqual([]); });
|
||||
it('should not create a message for empty attributes', () => {
|
||||
expect(extract('<div i18n-title="m|d" title></div>')).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('implicit elements', () => {
|
||||
@ -292,7 +297,7 @@ import {serializeNodes as serializeHtmlNodes} from '../ml_parser/util/util';
|
||||
});
|
||||
|
||||
it('should allow nested implicit elements', () => {
|
||||
let result: any[] = undefined !;
|
||||
let result: any[] = undefined!;
|
||||
|
||||
expect(() => {
|
||||
result = extract('<div>outer<div>inner</div></div>', ['div']);
|
||||
@ -302,7 +307,6 @@ import {serializeNodes as serializeHtmlNodes} from '../ml_parser/util/util';
|
||||
[['outer', '<ph tag name="START_TAG_DIV">inner</ph name="CLOSE_TAG_DIV">'], '', '', ''],
|
||||
]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('implicit attributes', () => {
|
||||
@ -341,7 +345,6 @@ import {serializeNodes as serializeHtmlNodes} from '../ml_parser/util/util';
|
||||
['Could not start a block inside a translatable section', '<!--'],
|
||||
['Trying to close an unopened block', '<!--'],
|
||||
]);
|
||||
|
||||
});
|
||||
|
||||
it('should report unclosed blocks', () => {
|
||||
@ -527,7 +530,7 @@ function fakeTranslate(
|
||||
messages.forEach(message => {
|
||||
const id = digest(message);
|
||||
const text = serializeI18nNodes(message.nodes).join('').replace(/</g, '[');
|
||||
i18nMsgMap[id] = [new i18n.Text(`**${text}**`, null !)];
|
||||
i18nMsgMap[id] = [new i18n.Text(`**${text}**`, null!)];
|
||||
});
|
||||
|
||||
const translationBundle = new TranslationBundle(i18nMsgMap, null, digest);
|
||||
|
@ -26,6 +26,5 @@ import {ParseTreeResult} from '@angular/compiler/src/ml_parser/parser';
|
||||
i18nHtmlParser.parse('source', 'url');
|
||||
expect(TranslationBundle.load).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ import {DEFAULT_INTERPOLATION_CONFIG} from '@angular/compiler/src/ml_parser/inte
|
||||
|
||||
{
|
||||
describe('I18nParser', () => {
|
||||
|
||||
describe('elements', () => {
|
||||
it('should extract from elements', () => {
|
||||
expect(_humanizeMessages('<div i18n="m|d">text</div>')).toEqual([
|
||||
@ -34,11 +33,13 @@ import {DEFAULT_INTERPOLATION_CONFIG} from '@angular/compiler/src/ml_parser/inte
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not create a message for empty elements',
|
||||
() => { expect(_humanizeMessages('<div i18n="m|d"></div>')).toEqual([]); });
|
||||
it('should not create a message for empty elements', () => {
|
||||
expect(_humanizeMessages('<div i18n="m|d"></div>')).toEqual([]);
|
||||
});
|
||||
|
||||
it('should not create a message for plain elements',
|
||||
() => { expect(_humanizeMessages('<div></div>')).toEqual([]); });
|
||||
it('should not create a message for plain elements', () => {
|
||||
expect(_humanizeMessages('<div></div>')).toEqual([]);
|
||||
});
|
||||
|
||||
it('should support void elements', () => {
|
||||
expect(_humanizeMessages('<div i18n="m|d"><p><br></p></div>')).toEqual([
|
||||
@ -115,8 +116,9 @@ import {DEFAULT_INTERPOLATION_CONFIG} from '@angular/compiler/src/ml_parser/inte
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not create a message for empty attributes',
|
||||
() => { expect(_humanizeMessages('<div i18n-title="m|d" title></div>')).toEqual([]); });
|
||||
it('should not create a message for empty attributes', () => {
|
||||
expect(_humanizeMessages('<div i18n-title="m|d" title></div>')).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('interpolation', () => {
|
||||
@ -252,7 +254,6 @@ import {DEFAULT_INTERPOLATION_CONFIG} from '@angular/compiler/src/ml_parser/inte
|
||||
expect(_humanizePlaceholders(html)).toEqual([
|
||||
'START_PARAGRAPH=<p>, CLOSE_PARAGRAPH=</p>, START_PARAGRAPH_1=<p other>',
|
||||
]);
|
||||
|
||||
});
|
||||
|
||||
it('should reuse the same placeholder name for interpolations', () => {
|
||||
@ -302,7 +303,6 @@ import {DEFAULT_INTERPOLATION_CONFIG} from '@angular/compiler/src/ml_parser/inte
|
||||
'',
|
||||
'',
|
||||
]);
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -20,11 +20,11 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
})
|
||||
export class I18nComponent {
|
||||
// TODO(issue/24571): remove '!'.
|
||||
count !: number;
|
||||
count!: number;
|
||||
// TODO(issue/24571): remove '!'.
|
||||
sex !: string;
|
||||
sex!: string;
|
||||
// TODO(issue/24571): remove '!'.
|
||||
sexB !: string;
|
||||
sexB!: string;
|
||||
response: any = {getItemsList: (): any[] => []};
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@ import {Xliff2} from '@angular/compiler/src/i18n/serializers/xliff2';
|
||||
import {HtmlParser} from '@angular/compiler/src/ml_parser/html_parser';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG} from '@angular/compiler/src/ml_parser/interpolation_config';
|
||||
import {DebugElement, TRANSLATIONS, TRANSLATIONS_FORMAT} from '@angular/core';
|
||||
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
|
||||
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
|
||||
import {SpyResourceLoader} from '../spies';
|
||||
@ -22,7 +22,6 @@ import {FrLocalization, HTML, I18nComponent, validateHtml} from './integration_c
|
||||
|
||||
{
|
||||
describe('i18n XLIFF 2.0 integration spec', () => {
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureCompiler({
|
||||
providers: [
|
||||
|
@ -13,7 +13,7 @@ import {Xliff} from '@angular/compiler/src/i18n/serializers/xliff';
|
||||
import {HtmlParser} from '@angular/compiler/src/ml_parser/html_parser';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG} from '@angular/compiler/src/ml_parser/interpolation_config';
|
||||
import {DebugElement, TRANSLATIONS, TRANSLATIONS_FORMAT} from '@angular/core';
|
||||
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
|
||||
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
|
||||
import {SpyResourceLoader} from '../spies';
|
||||
@ -22,7 +22,6 @@ import {FrLocalization, HTML, I18nComponent, validateHtml} from './integration_c
|
||||
|
||||
{
|
||||
describe('i18n XLIFF integration spec', () => {
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureCompiler({
|
||||
providers: [
|
||||
|
@ -13,7 +13,7 @@ import {Xmb} from '@angular/compiler/src/i18n/serializers/xmb';
|
||||
import {HtmlParser} from '@angular/compiler/src/ml_parser/html_parser';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG} from '@angular/compiler/src/ml_parser/interpolation_config';
|
||||
import {DebugElement, TRANSLATIONS, TRANSLATIONS_FORMAT} from '@angular/core';
|
||||
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
|
||||
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
|
||||
import {SpyResourceLoader} from '../spies';
|
||||
@ -22,7 +22,6 @@ import {FrLocalization, HTML, I18nComponent, validateHtml} from './integration_c
|
||||
|
||||
{
|
||||
describe('i18n XMB/XTB integration spec', () => {
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureCompiler({
|
||||
providers: [
|
||||
|
@ -18,7 +18,9 @@ import {DEFAULT_INTERPOLATION_CONFIG} from '../../src/ml_parser/interpolation_co
|
||||
describe('Messages', () => {
|
||||
let messages: MessageBundle;
|
||||
|
||||
beforeEach(() => { messages = new MessageBundle(new HtmlParser, [], {}); });
|
||||
beforeEach(() => {
|
||||
messages = new MessageBundle(new HtmlParser, [], {});
|
||||
});
|
||||
|
||||
it('should extract the message to the catalog', () => {
|
||||
messages.updateFromTemplate(
|
||||
@ -48,11 +50,13 @@ class _TestSerializer extends Serializer {
|
||||
}
|
||||
|
||||
load(content: string, url: string):
|
||||
{locale: string | null, i18nNodesByMsgId: {[id: string]: i18n.Node[]}} {
|
||||
{locale: string|null, i18nNodesByMsgId: {[id: string]: i18n.Node[]}} {
|
||||
return {locale: null, i18nNodesByMsgId: {}};
|
||||
}
|
||||
|
||||
digest(msg: i18n.Message): string { return msg.id || `default`; }
|
||||
digest(msg: i18n.Message): string {
|
||||
return msg.id || `default`;
|
||||
}
|
||||
}
|
||||
|
||||
function humanizeMessages(catalog: MessageBundle): string[] {
|
||||
|
@ -36,13 +36,13 @@ import {_extractMessages} from '../i18n_parser_spec';
|
||||
const visitor = new RecurseVisitor();
|
||||
const container = new i18n.Container(
|
||||
[
|
||||
new i18n.Text('', null !),
|
||||
new i18n.Placeholder('', '', null !),
|
||||
new i18n.IcuPlaceholder(null !, '', null !),
|
||||
new i18n.Text('', null!),
|
||||
new i18n.Placeholder('', '', null!),
|
||||
new i18n.IcuPlaceholder(null!, '', null!),
|
||||
],
|
||||
null !);
|
||||
const tag = new i18n.TagPlaceholder('', {}, '', '', [container], false, null !);
|
||||
const icu = new i18n.Icu('', '', {tag}, null !);
|
||||
null!);
|
||||
const tag = new i18n.TagPlaceholder('', {}, '', '', [container], false, null!);
|
||||
const icu = new i18n.Icu('', '', {tag}, null!);
|
||||
|
||||
icu.visit(visitor);
|
||||
expect(visitor.textCount).toEqual(1);
|
||||
@ -58,9 +58,15 @@ class RecurseVisitor extends i18n.RecurseVisitor {
|
||||
phCount = 0;
|
||||
icuPhCount = 0;
|
||||
|
||||
visitText(text: i18n.Text, context?: any): any { this.textCount++; }
|
||||
visitText(text: i18n.Text, context?: any): any {
|
||||
this.textCount++;
|
||||
}
|
||||
|
||||
visitPlaceholder(ph: i18n.Placeholder, context?: any): any { this.phCount++; }
|
||||
visitPlaceholder(ph: i18n.Placeholder, context?: any): any {
|
||||
this.phCount++;
|
||||
}
|
||||
|
||||
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): any { this.icuPhCount++; }
|
||||
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): any {
|
||||
this.icuPhCount++;
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,9 @@ import {PlaceholderRegistry} from '../../../src/i18n/serializers/placeholder';
|
||||
describe('PlaceholderRegistry', () => {
|
||||
let reg: PlaceholderRegistry;
|
||||
|
||||
beforeEach(() => { reg = new PlaceholderRegistry(); });
|
||||
beforeEach(() => {
|
||||
reg = new PlaceholderRegistry();
|
||||
});
|
||||
|
||||
describe('tag placeholder', () => {
|
||||
it('should generate names for well known tags', () => {
|
||||
@ -84,7 +86,6 @@ import {PlaceholderRegistry} from '../../../src/i18n/serializers/placeholder';
|
||||
expect(reg.getPlaceholderName('name1', 'content')).toEqual('NAME1');
|
||||
expect(reg.getPlaceholderName('name2', 'content')).toEqual('NAME2');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
}
|
@ -261,65 +261,67 @@ lignes</target>
|
||||
`;
|
||||
|
||||
(function() {
|
||||
const serializer = new Xliff2();
|
||||
const serializer = new Xliff2();
|
||||
|
||||
function toXliff(html: string, locale: string | null = null): string {
|
||||
const catalog = new MessageBundle(new HtmlParser, [], {}, locale);
|
||||
catalog.updateFromTemplate(html, 'file.ts', DEFAULT_INTERPOLATION_CONFIG);
|
||||
return catalog.write(serializer);
|
||||
}
|
||||
function toXliff(html: string, locale: string|null = null): string {
|
||||
const catalog = new MessageBundle(new HtmlParser, [], {}, locale);
|
||||
catalog.updateFromTemplate(html, 'file.ts', DEFAULT_INTERPOLATION_CONFIG);
|
||||
return catalog.write(serializer);
|
||||
}
|
||||
|
||||
function loadAsMap(xliff: string): {[id: string]: string} {
|
||||
const {i18nNodesByMsgId} = serializer.load(xliff, 'url');
|
||||
function loadAsMap(xliff: string): {[id: string]: string} {
|
||||
const {i18nNodesByMsgId} = serializer.load(xliff, 'url');
|
||||
|
||||
const msgMap: {[id: string]: string} = {};
|
||||
Object.keys(i18nNodesByMsgId)
|
||||
.forEach(id => msgMap[id] = serializeNodes(i18nNodesByMsgId[id]).join(''));
|
||||
const msgMap: {[id: string]: string} = {};
|
||||
Object.keys(i18nNodesByMsgId)
|
||||
.forEach(id => msgMap[id] = serializeNodes(i18nNodesByMsgId[id]).join(''));
|
||||
|
||||
return msgMap;
|
||||
}
|
||||
return msgMap;
|
||||
}
|
||||
|
||||
describe('XLIFF 2.0 serializer', () => {
|
||||
describe('write', () => {
|
||||
it('should write a valid xliff 2.0 file',
|
||||
() => { expect(toXliff(HTML)).toEqual(WRITE_XLIFF); });
|
||||
it('should write a valid xliff 2.0 file with a source language',
|
||||
() => { expect(toXliff(HTML, 'fr')).toContain('srcLang="fr"'); });
|
||||
describe('XLIFF 2.0 serializer', () => {
|
||||
describe('write', () => {
|
||||
it('should write a valid xliff 2.0 file', () => {
|
||||
expect(toXliff(HTML)).toEqual(WRITE_XLIFF);
|
||||
});
|
||||
it('should write a valid xliff 2.0 file with a source language', () => {
|
||||
expect(toXliff(HTML, 'fr')).toContain('srcLang="fr"');
|
||||
});
|
||||
});
|
||||
|
||||
describe('load', () => {
|
||||
it('should load XLIFF files', () => {
|
||||
expect(loadAsMap(LOAD_XLIFF)).toEqual({
|
||||
'1933478729560469763': 'etubirtta elbatalsnart',
|
||||
'7056919470098446707':
|
||||
'<ph name="INTERPOLATION"/> <ph name="START_BOLD_TEXT"/>sredlohecalp htiw<ph name="CLOSE_BOLD_TEXT"/> tnemele elbatalsnart',
|
||||
'2981514368455622387':
|
||||
'{VAR_PLURAL, plural, =0 {[<ph name="START_PARAGRAPH"/>, TEST, <ph name="CLOSE_PARAGRAPH"/>]}}',
|
||||
'i': 'oof',
|
||||
'6440235004920703622':
|
||||
'<ph name="START_BOLD_TEXT"/><ph name="START_UNDERLINED_TEXT"/>txeT <ph name="INTERPOLATION"/><ph name="CLOSE_UNDERLINED_TEXT"/><ph name="CLOSE_BOLD_TEXT"/>',
|
||||
'8779402634269838862':
|
||||
'<ph name="TAG_IMG_1"/><ph name="TAG_IMG"/><ph name="LINE_BREAK"/>',
|
||||
'6536355551500405293': '<ph name="START_TAG_SPAN"/><ph name="CLOSE_TAG_SPAN"/> olleh',
|
||||
'baz':
|
||||
'{VAR_PLURAL, plural, =0 {[{VAR_SELECT, select, other {[<ph name="START_PARAGRAPH"/>, profondément imbriqué, <ph name="CLOSE_PARAGRAPH"/>]}}, ]}}',
|
||||
'6997386649824869937': 'Test: <ph name="ICU"/>',
|
||||
'5229984852258993423': '{VAR_PLURAL, plural, =0 {[{VAR_SELECT, select, other {[<ph' +
|
||||
' name="START_PARAGRAPH"/>, profondément imbriqué, <ph name="CLOSE_PARAGRAPH"/>]}}, ]}, =other {[beaucoup]}}',
|
||||
'2340165783990709777': `multi
|
||||
describe('load', () => {
|
||||
it('should load XLIFF files', () => {
|
||||
expect(loadAsMap(LOAD_XLIFF)).toEqual({
|
||||
'1933478729560469763': 'etubirtta elbatalsnart',
|
||||
'7056919470098446707':
|
||||
'<ph name="INTERPOLATION"/> <ph name="START_BOLD_TEXT"/>sredlohecalp htiw<ph name="CLOSE_BOLD_TEXT"/> tnemele elbatalsnart',
|
||||
'2981514368455622387':
|
||||
'{VAR_PLURAL, plural, =0 {[<ph name="START_PARAGRAPH"/>, TEST, <ph name="CLOSE_PARAGRAPH"/>]}}',
|
||||
'i': 'oof',
|
||||
'6440235004920703622':
|
||||
'<ph name="START_BOLD_TEXT"/><ph name="START_UNDERLINED_TEXT"/>txeT <ph name="INTERPOLATION"/><ph name="CLOSE_UNDERLINED_TEXT"/><ph name="CLOSE_BOLD_TEXT"/>',
|
||||
'8779402634269838862': '<ph name="TAG_IMG_1"/><ph name="TAG_IMG"/><ph name="LINE_BREAK"/>',
|
||||
'6536355551500405293': '<ph name="START_TAG_SPAN"/><ph name="CLOSE_TAG_SPAN"/> olleh',
|
||||
'baz':
|
||||
'{VAR_PLURAL, plural, =0 {[{VAR_SELECT, select, other {[<ph name="START_PARAGRAPH"/>, profondément imbriqué, <ph name="CLOSE_PARAGRAPH"/>]}}, ]}}',
|
||||
'6997386649824869937': 'Test: <ph name="ICU"/>',
|
||||
'5229984852258993423': '{VAR_PLURAL, plural, =0 {[{VAR_SELECT, select, other {[<ph' +
|
||||
' name="START_PARAGRAPH"/>, profondément imbriqué, <ph name="CLOSE_PARAGRAPH"/>]}}, ]}, =other {[beaucoup]}}',
|
||||
'2340165783990709777': `multi
|
||||
lignes`,
|
||||
'mrk-test': 'Vous pouvez utiliser votre propre namespace.',
|
||||
'mrk-test2': 'Vous pouvez utiliser votre propre namespace.'
|
||||
});
|
||||
'mrk-test': 'Vous pouvez utiliser votre propre namespace.',
|
||||
'mrk-test2': 'Vous pouvez utiliser votre propre namespace.'
|
||||
});
|
||||
|
||||
it('should return the target locale',
|
||||
() => { expect(serializer.load(LOAD_XLIFF, 'url').locale).toEqual('fr'); });
|
||||
});
|
||||
|
||||
describe('structure errors', () => {
|
||||
it('should throw when a wrong xliff version is used', () => {
|
||||
const XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
it('should return the target locale', () => {
|
||||
expect(serializer.load(LOAD_XLIFF, 'url').locale).toEqual('fr');
|
||||
});
|
||||
});
|
||||
|
||||
describe('structure errors', () => {
|
||||
it('should throw when a wrong xliff version is used', () => {
|
||||
const XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<file source-language="en" datatype="plaintext" original="ng2.template">
|
||||
<body>
|
||||
@ -331,13 +333,13 @@ lignes`,
|
||||
</file>
|
||||
</xliff>`;
|
||||
|
||||
expect(() => {
|
||||
loadAsMap(XLIFF);
|
||||
}).toThrowError(/The XLIFF file version 1.2 is not compatible with XLIFF 2.0 serializer/);
|
||||
});
|
||||
expect(() => {
|
||||
loadAsMap(XLIFF);
|
||||
}).toThrowError(/The XLIFF file version 1.2 is not compatible with XLIFF 2.0 serializer/);
|
||||
});
|
||||
|
||||
it('should throw when an unit has no translation', () => {
|
||||
const XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
it('should throw when an unit has no translation', () => {
|
||||
const XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<xliff version="2.0" xmlns="urn:oasis:names:tc:xliff:document:2.0" srcLang="en">
|
||||
<file original="ng.template" id="ngi18n">
|
||||
<unit id="missingtarget">
|
||||
@ -348,14 +350,14 @@ lignes`,
|
||||
</file>
|
||||
</xliff>`;
|
||||
|
||||
expect(() => {
|
||||
loadAsMap(XLIFF);
|
||||
}).toThrowError(/Message missingtarget misses a translation/);
|
||||
});
|
||||
expect(() => {
|
||||
loadAsMap(XLIFF);
|
||||
}).toThrowError(/Message missingtarget misses a translation/);
|
||||
});
|
||||
|
||||
|
||||
it('should throw when an unit has no id attribute', () => {
|
||||
const XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
it('should throw when an unit has no id attribute', () => {
|
||||
const XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<xliff version="2.0" xmlns="urn:oasis:names:tc:xliff:document:2.0" srcLang="en">
|
||||
<file original="ng.template" id="ngi18n">
|
||||
<unit>
|
||||
@ -367,11 +369,13 @@ lignes`,
|
||||
</file>
|
||||
</xliff>`;
|
||||
|
||||
expect(() => { loadAsMap(XLIFF); }).toThrowError(/<unit> misses the "id" attribute/);
|
||||
});
|
||||
expect(() => {
|
||||
loadAsMap(XLIFF);
|
||||
}).toThrowError(/<unit> misses the "id" attribute/);
|
||||
});
|
||||
|
||||
it('should throw on duplicate unit id', () => {
|
||||
const XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
it('should throw on duplicate unit id', () => {
|
||||
const XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<xliff version="2.0" xmlns="urn:oasis:names:tc:xliff:document:2.0" srcLang="en">
|
||||
<file original="ng.template" id="ngi18n">
|
||||
<unit id="deadbeef">
|
||||
@ -389,15 +393,15 @@ lignes`,
|
||||
</file>
|
||||
</xliff>`;
|
||||
|
||||
expect(() => {
|
||||
loadAsMap(XLIFF);
|
||||
}).toThrowError(/Duplicated translations for msg deadbeef/);
|
||||
});
|
||||
expect(() => {
|
||||
loadAsMap(XLIFF);
|
||||
}).toThrowError(/Duplicated translations for msg deadbeef/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('message errors', () => {
|
||||
it('should throw on unknown message tags', () => {
|
||||
const XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
describe('message errors', () => {
|
||||
it('should throw on unknown message tags', () => {
|
||||
const XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<xliff version="2.0" xmlns="urn:oasis:names:tc:xliff:document:2.0" srcLang="en">
|
||||
<file original="ng.template" id="ngi18n">
|
||||
<unit id="deadbeef">
|
||||
@ -409,13 +413,15 @@ lignes`,
|
||||
</file>
|
||||
</xliff>`;
|
||||
|
||||
expect(() => { loadAsMap(XLIFF); })
|
||||
.toThrowError(new RegExp(
|
||||
escapeRegExp(`[ERROR ->]<b>msg should contain only ph and pc tags</b>`)));
|
||||
});
|
||||
expect(() => {
|
||||
loadAsMap(XLIFF);
|
||||
})
|
||||
.toThrowError(
|
||||
new RegExp(escapeRegExp(`[ERROR ->]<b>msg should contain only ph and pc tags</b>`)));
|
||||
});
|
||||
|
||||
it('should throw when a placeholder misses an id attribute', () => {
|
||||
const XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
it('should throw when a placeholder misses an id attribute', () => {
|
||||
const XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<xliff version="2.0" xmlns="urn:oasis:names:tc:xliff:document:2.0" srcLang="en">
|
||||
<file original="ng.template" id="ngi18n">
|
||||
<unit id="deadbeef">
|
||||
@ -427,10 +433,10 @@ lignes`,
|
||||
</file>
|
||||
</xliff>`;
|
||||
|
||||
expect(() => {
|
||||
loadAsMap(XLIFF);
|
||||
}).toThrowError(new RegExp(escapeRegExp(`<ph> misses the "equiv" attribute`)));
|
||||
});
|
||||
expect(() => {
|
||||
loadAsMap(XLIFF);
|
||||
}).toThrowError(new RegExp(escapeRegExp(`<ph> misses the "equiv" attribute`)));
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
@ -241,63 +241,67 @@ lignes</target>
|
||||
`;
|
||||
|
||||
(function() {
|
||||
const serializer = new Xliff();
|
||||
const serializer = new Xliff();
|
||||
|
||||
function toXliff(html: string, locale: string | null = null): string {
|
||||
const catalog = new MessageBundle(new HtmlParser, [], {}, locale);
|
||||
catalog.updateFromTemplate(html, 'file.ts', DEFAULT_INTERPOLATION_CONFIG);
|
||||
return catalog.write(serializer);
|
||||
}
|
||||
function toXliff(html: string, locale: string|null = null): string {
|
||||
const catalog = new MessageBundle(new HtmlParser, [], {}, locale);
|
||||
catalog.updateFromTemplate(html, 'file.ts', DEFAULT_INTERPOLATION_CONFIG);
|
||||
return catalog.write(serializer);
|
||||
}
|
||||
|
||||
function loadAsMap(xliff: string): {[id: string]: string} {
|
||||
const {i18nNodesByMsgId} = serializer.load(xliff, 'url');
|
||||
function loadAsMap(xliff: string): {[id: string]: string} {
|
||||
const {i18nNodesByMsgId} = serializer.load(xliff, 'url');
|
||||
|
||||
const msgMap: {[id: string]: string} = {};
|
||||
Object.keys(i18nNodesByMsgId)
|
||||
.forEach(id => msgMap[id] = serializeNodes(i18nNodesByMsgId[id]).join(''));
|
||||
const msgMap: {[id: string]: string} = {};
|
||||
Object.keys(i18nNodesByMsgId)
|
||||
.forEach(id => msgMap[id] = serializeNodes(i18nNodesByMsgId[id]).join(''));
|
||||
|
||||
return msgMap;
|
||||
}
|
||||
return msgMap;
|
||||
}
|
||||
|
||||
describe('XLIFF serializer', () => {
|
||||
describe('write', () => {
|
||||
it('should write a valid xliff file', () => { expect(toXliff(HTML)).toEqual(WRITE_XLIFF); });
|
||||
it('should write a valid xliff file with a source language',
|
||||
() => { expect(toXliff(HTML, 'fr')).toContain('file source-language="fr"'); });
|
||||
describe('XLIFF serializer', () => {
|
||||
describe('write', () => {
|
||||
it('should write a valid xliff file', () => {
|
||||
expect(toXliff(HTML)).toEqual(WRITE_XLIFF);
|
||||
});
|
||||
it('should write a valid xliff file with a source language', () => {
|
||||
expect(toXliff(HTML, 'fr')).toContain('file source-language="fr"');
|
||||
});
|
||||
});
|
||||
|
||||
describe('load', () => {
|
||||
it('should load XLIFF files', () => {
|
||||
expect(loadAsMap(LOAD_XLIFF)).toEqual({
|
||||
'983775b9a51ce14b036be72d4cfd65d68d64e231': 'etubirtta elbatalsnart',
|
||||
'ec1d033f2436133c14ab038286c4f5df4697484a':
|
||||
'<ph name="INTERPOLATION"/> footnemele elbatalsnart <ph name="START_BOLD_TEXT"/>sredlohecalp htiw<ph name="CLOSE_BOLD_TEXT"/>',
|
||||
'e2ccf3d131b15f54aa1fcf1314b1ca77c14bfcc2':
|
||||
'{VAR_PLURAL, plural, =0 {[<ph name="START_PARAGRAPH"/>, TEST, <ph name="CLOSE_PARAGRAPH"/>]}}',
|
||||
'db3e0a6a5a96481f60aec61d98c3eecddef5ac23': 'oof',
|
||||
'i': 'toto',
|
||||
'bar': 'tata',
|
||||
'd7fa2d59aaedcaa5309f13028c59af8c85b8c49d':
|
||||
'<ph name="START_TAG_DIV"/><ph name="CLOSE_TAG_DIV"/><ph name="TAG_IMG"/><ph name="LINE_BREAK"/>',
|
||||
'empty target': '',
|
||||
'baz':
|
||||
'{VAR_PLURAL, plural, =0 {[{VAR_SELECT, select, other {[<ph name="START_PARAGRAPH"/>, profondément imbriqué, <ph name="CLOSE_PARAGRAPH"/>]}}, ]}}',
|
||||
'52ffa620dcd76247a56d5331f34e73f340a43cdb': 'Test: <ph name="ICU"/>',
|
||||
'1503afd0ccc20ff01d5e2266a9157b7b342ba494':
|
||||
'{VAR_PLURAL, plural, =0 {[{VAR_SELECT, select, other {[<ph' +
|
||||
' name="START_PARAGRAPH"/>, profondément imbriqué, <ph name="CLOSE_PARAGRAPH"/>]}}, ]}, =other {[beaucoup]}}',
|
||||
'fcfa109b0e152d4c217dbc02530be0bcb8123ad1': `multi
|
||||
lignes`,
|
||||
'mrk-test': 'Translated first sentence.',
|
||||
'mrk-test2': 'Translated first sentence.'
|
||||
});
|
||||
});
|
||||
|
||||
describe('load', () => {
|
||||
it('should load XLIFF files', () => {
|
||||
expect(loadAsMap(LOAD_XLIFF)).toEqual({
|
||||
'983775b9a51ce14b036be72d4cfd65d68d64e231': 'etubirtta elbatalsnart',
|
||||
'ec1d033f2436133c14ab038286c4f5df4697484a':
|
||||
'<ph name="INTERPOLATION"/> footnemele elbatalsnart <ph name="START_BOLD_TEXT"/>sredlohecalp htiw<ph name="CLOSE_BOLD_TEXT"/>',
|
||||
'e2ccf3d131b15f54aa1fcf1314b1ca77c14bfcc2':
|
||||
'{VAR_PLURAL, plural, =0 {[<ph name="START_PARAGRAPH"/>, TEST, <ph name="CLOSE_PARAGRAPH"/>]}}',
|
||||
'db3e0a6a5a96481f60aec61d98c3eecddef5ac23': 'oof',
|
||||
'i': 'toto',
|
||||
'bar': 'tata',
|
||||
'd7fa2d59aaedcaa5309f13028c59af8c85b8c49d':
|
||||
'<ph name="START_TAG_DIV"/><ph name="CLOSE_TAG_DIV"/><ph name="TAG_IMG"/><ph name="LINE_BREAK"/>',
|
||||
'empty target': '',
|
||||
'baz':
|
||||
'{VAR_PLURAL, plural, =0 {[{VAR_SELECT, select, other {[<ph name="START_PARAGRAPH"/>, profondément imbriqué, <ph name="CLOSE_PARAGRAPH"/>]}}, ]}}',
|
||||
'52ffa620dcd76247a56d5331f34e73f340a43cdb': 'Test: <ph name="ICU"/>',
|
||||
'1503afd0ccc20ff01d5e2266a9157b7b342ba494':
|
||||
'{VAR_PLURAL, plural, =0 {[{VAR_SELECT, select, other {[<ph' +
|
||||
' name="START_PARAGRAPH"/>, profondément imbriqué, <ph name="CLOSE_PARAGRAPH"/>]}}, ]}, =other {[beaucoup]}}',
|
||||
'fcfa109b0e152d4c217dbc02530be0bcb8123ad1': `multi
|
||||
lignes`,
|
||||
'mrk-test': 'Translated first sentence.',
|
||||
'mrk-test2': 'Translated first sentence.'
|
||||
});
|
||||
});
|
||||
it('should return the target locale', () => {
|
||||
expect(serializer.load(LOAD_XLIFF, 'url').locale).toEqual('fr');
|
||||
});
|
||||
|
||||
it('should return the target locale',
|
||||
() => { expect(serializer.load(LOAD_XLIFF, 'url').locale).toEqual('fr'); });
|
||||
|
||||
it('should ignore alt-trans targets', () => {
|
||||
const XLIFF = `
|
||||
it('should ignore alt-trans targets', () => {
|
||||
const XLIFF = `
|
||||
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<file source-language="en" target-language="fr" datatype="plaintext" original="ng2.template">
|
||||
<body>
|
||||
@ -318,12 +322,12 @@ lignes`,
|
||||
</file>
|
||||
</xliff>`;
|
||||
|
||||
expect(loadAsMap(XLIFF)).toEqual({'registration.submit': 'Weiter'});
|
||||
});
|
||||
expect(loadAsMap(XLIFF)).toEqual({'registration.submit': 'Weiter'});
|
||||
});
|
||||
|
||||
describe('structure errors', () => {
|
||||
it('should throw when a trans-unit has no translation', () => {
|
||||
const XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
describe('structure errors', () => {
|
||||
it('should throw when a trans-unit has no translation', () => {
|
||||
const XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<file source-language="en" datatype="plaintext" original="ng2.template">
|
||||
<body>
|
||||
@ -334,14 +338,14 @@ lignes`,
|
||||
</file>
|
||||
</xliff>`;
|
||||
|
||||
expect(() => {
|
||||
loadAsMap(XLIFF);
|
||||
}).toThrowError(/Message missingtarget misses a translation/);
|
||||
});
|
||||
expect(() => {
|
||||
loadAsMap(XLIFF);
|
||||
}).toThrowError(/Message missingtarget misses a translation/);
|
||||
});
|
||||
|
||||
|
||||
it('should throw when a trans-unit has no id attribute', () => {
|
||||
const XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
it('should throw when a trans-unit has no id attribute', () => {
|
||||
const XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<file source-language="en" datatype="plaintext" original="ng2.template">
|
||||
<body>
|
||||
@ -353,13 +357,13 @@ lignes`,
|
||||
</file>
|
||||
</xliff>`;
|
||||
|
||||
expect(() => {
|
||||
loadAsMap(XLIFF);
|
||||
}).toThrowError(/<trans-unit> misses the "id" attribute/);
|
||||
});
|
||||
expect(() => {
|
||||
loadAsMap(XLIFF);
|
||||
}).toThrowError(/<trans-unit> misses the "id" attribute/);
|
||||
});
|
||||
|
||||
it('should throw on duplicate trans-unit id', () => {
|
||||
const XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
it('should throw on duplicate trans-unit id', () => {
|
||||
const XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<file source-language="en" datatype="plaintext" original="ng2.template">
|
||||
<body>
|
||||
@ -375,15 +379,15 @@ lignes`,
|
||||
</file>
|
||||
</xliff>`;
|
||||
|
||||
expect(() => {
|
||||
loadAsMap(XLIFF);
|
||||
}).toThrowError(/Duplicated translations for msg deadbeef/);
|
||||
});
|
||||
expect(() => {
|
||||
loadAsMap(XLIFF);
|
||||
}).toThrowError(/Duplicated translations for msg deadbeef/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('message errors', () => {
|
||||
it('should throw on unknown message tags', () => {
|
||||
const XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
describe('message errors', () => {
|
||||
it('should throw on unknown message tags', () => {
|
||||
const XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<file source-language="en" datatype="plaintext" original="ng2.template">
|
||||
<body>
|
||||
@ -395,13 +399,15 @@ lignes`,
|
||||
</file>
|
||||
</xliff>`;
|
||||
|
||||
expect(() => { loadAsMap(XLIFF); })
|
||||
.toThrowError(
|
||||
new RegExp(escapeRegExp(`[ERROR ->]<b>msg should contain only ph tags</b>`)));
|
||||
});
|
||||
expect(() => {
|
||||
loadAsMap(XLIFF);
|
||||
})
|
||||
.toThrowError(
|
||||
new RegExp(escapeRegExp(`[ERROR ->]<b>msg should contain only ph tags</b>`)));
|
||||
});
|
||||
|
||||
it('should throw when a placeholder misses an id attribute', () => {
|
||||
const XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
it('should throw when a placeholder misses an id attribute', () => {
|
||||
const XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<file source-language="en" datatype="plaintext" original="ng2.template">
|
||||
<body>
|
||||
@ -413,12 +419,11 @@ lignes`,
|
||||
</file>
|
||||
</xliff>`;
|
||||
|
||||
expect(() => {
|
||||
loadAsMap(XLIFF);
|
||||
}).toThrowError(new RegExp(escapeRegExp(`<x> misses the "id" attribute`)));
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
loadAsMap(XLIFF);
|
||||
}).toThrowError(new RegExp(escapeRegExp(`<x> misses the "id" attribute`)));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
@ -76,7 +76,7 @@ lines</msg>
|
||||
});
|
||||
}
|
||||
|
||||
function toXmb(html: string, url: string, locale: string | null = null): string {
|
||||
function toXmb(html: string, url: string, locale: string|null = null): string {
|
||||
const catalog = new MessageBundle(new HtmlParser, [], {}, locale);
|
||||
const serializer = new Xmb();
|
||||
|
||||
|
@ -15,11 +15,13 @@ import * as xml from '../../../src/i18n/serializers/xml_helper';
|
||||
.toEqual('<?xml version="1.0" ?>');
|
||||
});
|
||||
|
||||
it('should serialize text node',
|
||||
() => { expect(xml.serialize([new xml.Text('foo bar')])).toEqual('foo bar'); });
|
||||
it('should serialize text node', () => {
|
||||
expect(xml.serialize([new xml.Text('foo bar')])).toEqual('foo bar');
|
||||
});
|
||||
|
||||
it('should escape text nodes',
|
||||
() => { expect(xml.serialize([new xml.Text('<>')])).toEqual('<>'); });
|
||||
it('should escape text nodes', () => {
|
||||
expect(xml.serialize([new xml.Text('<>')])).toEqual('<>');
|
||||
});
|
||||
|
||||
it('should serialize xml nodes without children', () => {
|
||||
expect(xml.serialize([new xml.Tag('el', {foo: 'bar'}, [])])).toEqual('<el foo="bar"/>');
|
||||
@ -42,6 +44,5 @@ import * as xml from '../../../src/i18n/serializers/xml_helper';
|
||||
expect(xml.serialize([new xml.Tag('el', {foo: '<">'}, [])]))
|
||||
.toEqual('<el foo="<">"/>');
|
||||
});
|
||||
|
||||
});
|
||||
}
|
@ -117,7 +117,7 @@ import {Xtb} from '../../../src/i18n/serializers/xtb';
|
||||
</translationbundle>`;
|
||||
|
||||
// Invalid messages should not cause the parser to throw
|
||||
let i18nNodesByMsgId: {[id: string]: i18n.Node[]} = undefined !;
|
||||
let i18nNodesByMsgId: {[id: string]: i18n.Node[]} = undefined!;
|
||||
expect(() => {
|
||||
i18nNodesByMsgId = serializer.load(XTB, 'url').i18nNodesByMsgId;
|
||||
}).not.toThrow();
|
||||
@ -144,7 +144,9 @@ import {Xtb} from '../../../src/i18n/serializers/xtb';
|
||||
<translation></translation>
|
||||
</translationbundle>`;
|
||||
|
||||
expect(() => { loadAsMap(XTB); }).toThrowError(/<translation> misses the "id" attribute/);
|
||||
expect(() => {
|
||||
loadAsMap(XTB);
|
||||
}).toThrowError(/<translation> misses the "id" attribute/);
|
||||
});
|
||||
|
||||
it('should throw when a placeholder has no name attribute', () => {
|
||||
@ -152,7 +154,9 @@ import {Xtb} from '../../../src/i18n/serializers/xtb';
|
||||
<translation id="1186013544048295927"><ph /></translation>
|
||||
</translationbundle>`;
|
||||
|
||||
expect(() => { loadAsMap(XTB); }).toThrowError(/<ph> misses the "name" attribute/);
|
||||
expect(() => {
|
||||
loadAsMap(XTB);
|
||||
}).toThrowError(/<ph> misses the "name" attribute/);
|
||||
});
|
||||
|
||||
it('should throw on unknown xtb tags', () => {
|
||||
@ -168,7 +172,9 @@ import {Xtb} from '../../../src/i18n/serializers/xtb';
|
||||
<translation id="1186013544048295927"><b>msg should contain only ph tags</b></translation>
|
||||
</translationbundle>`;
|
||||
|
||||
expect(() => { loadAsMap(XTB); })
|
||||
expect(() => {
|
||||
loadAsMap(XTB);
|
||||
})
|
||||
.toThrowError(
|
||||
new RegExp(escapeRegExp(`[ERROR ->]<b>msg should contain only ph tags</b>`)));
|
||||
});
|
||||
@ -184,9 +190,11 @@ import {Xtb} from '../../../src/i18n/serializers/xtb';
|
||||
}).toThrowError(/Duplicated translations for msg 1186013544048295927/);
|
||||
});
|
||||
|
||||
it('should throw when trying to save an xtb file',
|
||||
() => { expect(() => { serializer.write([], null); }).toThrowError(/Unsupported/); });
|
||||
|
||||
it('should throw when trying to save an xtb file', () => {
|
||||
expect(() => {
|
||||
serializer.write([], null);
|
||||
}).toThrowError(/Unsupported/);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -25,14 +25,14 @@ import {_extractMessages} from './i18n_parser_spec';
|
||||
const srcNode = new i18n.Text('src', span);
|
||||
|
||||
it('should translate a plain text', () => {
|
||||
const msgMap = {foo: [new i18n.Text('bar', null !)]};
|
||||
const msgMap = {foo: [new i18n.Text('bar', null!)]};
|
||||
const tb = new TranslationBundle(msgMap, null, (_) => 'foo');
|
||||
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i');
|
||||
expect(serializeNodes(tb.get(msg))).toEqual(['bar']);
|
||||
});
|
||||
|
||||
it('should translate html-like plain text', () => {
|
||||
const msgMap = {foo: [new i18n.Text('<p>bar</p>', null !)]};
|
||||
const msgMap = {foo: [new i18n.Text('<p>bar</p>', null!)]};
|
||||
const tb = new TranslationBundle(msgMap, null, (_) => 'foo');
|
||||
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i');
|
||||
const nodes = tb.get(msg);
|
||||
@ -45,8 +45,8 @@ import {_extractMessages} from './i18n_parser_spec';
|
||||
it('should translate a message with placeholder', () => {
|
||||
const msgMap = {
|
||||
foo: [
|
||||
new i18n.Text('bar', null !),
|
||||
new i18n.Placeholder('', 'ph1', null !),
|
||||
new i18n.Text('bar', null!),
|
||||
new i18n.Placeholder('', 'ph1', null!),
|
||||
]
|
||||
};
|
||||
const phMap = {
|
||||
@ -60,12 +60,12 @@ import {_extractMessages} from './i18n_parser_spec';
|
||||
it('should translate a message with placeholder referencing messages', () => {
|
||||
const msgMap = {
|
||||
foo: [
|
||||
new i18n.Text('--', null !),
|
||||
new i18n.Placeholder('', 'ph1', null !),
|
||||
new i18n.Text('++', null !),
|
||||
new i18n.Text('--', null!),
|
||||
new i18n.Placeholder('', 'ph1', null!),
|
||||
new i18n.Text('++', null!),
|
||||
],
|
||||
ref: [
|
||||
new i18n.Text('*refMsg*', null !),
|
||||
new i18n.Text('*refMsg*', null!),
|
||||
],
|
||||
};
|
||||
const refMsg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i');
|
||||
@ -84,13 +84,13 @@ import {_extractMessages} from './i18n_parser_spec';
|
||||
|
||||
const digest = (_: any) => `no matching id`;
|
||||
// Empty message map -> use source messages in Ignore mode
|
||||
let tb = new TranslationBundle({}, null, digest, null !, MissingTranslationStrategy.Ignore);
|
||||
let tb = new TranslationBundle({}, null, digest, null!, MissingTranslationStrategy.Ignore);
|
||||
expect(serializeNodes(tb.get(messages[0])).join('')).toEqual(src);
|
||||
// Empty message map -> use source messages in Warning mode
|
||||
tb = new TranslationBundle({}, null, digest, null !, MissingTranslationStrategy.Warning);
|
||||
tb = new TranslationBundle({}, null, digest, null!, MissingTranslationStrategy.Warning);
|
||||
expect(serializeNodes(tb.get(messages[0])).join('')).toEqual(src);
|
||||
// Empty message map -> throw in Error mode
|
||||
tb = new TranslationBundle({}, null, digest, null !, MissingTranslationStrategy.Error);
|
||||
tb = new TranslationBundle({}, null, digest, null!, MissingTranslationStrategy.Error);
|
||||
expect(() => serializeNodes(tb.get(messages[0])).join('')).toThrow();
|
||||
});
|
||||
|
||||
@ -98,7 +98,7 @@ import {_extractMessages} from './i18n_parser_spec';
|
||||
it('should report unknown placeholders', () => {
|
||||
const msgMap = {
|
||||
foo: [
|
||||
new i18n.Text('bar', null !),
|
||||
new i18n.Text('bar', null!),
|
||||
new i18n.Placeholder('', 'ph1', span),
|
||||
]
|
||||
};
|
||||
@ -109,7 +109,7 @@ import {_extractMessages} from './i18n_parser_spec';
|
||||
|
||||
it('should report missing translation', () => {
|
||||
const tb =
|
||||
new TranslationBundle({}, null, (_) => 'foo', null !, MissingTranslationStrategy.Error);
|
||||
new TranslationBundle({}, null, (_) => 'foo', null!, MissingTranslationStrategy.Error);
|
||||
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i');
|
||||
expect(() => tb.get(msg)).toThrowError(/Missing translation for message "foo"/);
|
||||
});
|
||||
@ -117,12 +117,14 @@ import {_extractMessages} from './i18n_parser_spec';
|
||||
it('should report missing translation with MissingTranslationStrategy.Warning', () => {
|
||||
const log: string[] = [];
|
||||
const console = {
|
||||
log: (msg: string) => { throw `unexpected`; },
|
||||
log: (msg: string) => {
|
||||
throw `unexpected`;
|
||||
},
|
||||
warn: (msg: string) => log.push(msg),
|
||||
};
|
||||
|
||||
const tb = new TranslationBundle(
|
||||
{}, 'en', (_) => 'foo', null !, MissingTranslationStrategy.Warning, console);
|
||||
{}, 'en', (_) => 'foo', null!, MissingTranslationStrategy.Warning, console);
|
||||
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i');
|
||||
|
||||
expect(() => tb.get(msg)).not.toThrowError();
|
||||
@ -131,8 +133,8 @@ import {_extractMessages} from './i18n_parser_spec';
|
||||
});
|
||||
|
||||
it('should not report missing translation with MissingTranslationStrategy.Ignore', () => {
|
||||
const tb = new TranslationBundle(
|
||||
{}, null, (_) => 'foo', null !, MissingTranslationStrategy.Ignore);
|
||||
const tb =
|
||||
new TranslationBundle({}, null, (_) => 'foo', null!, MissingTranslationStrategy.Ignore);
|
||||
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i');
|
||||
expect(() => tb.get(msg)).not.toThrowError();
|
||||
});
|
||||
@ -146,15 +148,15 @@ import {_extractMessages} from './i18n_parser_spec';
|
||||
let count = 0;
|
||||
const digest = (_: any) => count++ ? 'ref' : 'foo';
|
||||
const tb =
|
||||
new TranslationBundle(msgMap, null, digest, null !, MissingTranslationStrategy.Error);
|
||||
new TranslationBundle(msgMap, null, digest, null!, MissingTranslationStrategy.Error);
|
||||
expect(() => tb.get(msg)).toThrowError(/Missing translation for message "ref"/);
|
||||
});
|
||||
|
||||
it('should report invalid translated html', () => {
|
||||
const msgMap = {
|
||||
foo: [
|
||||
new i18n.Text('text', null !),
|
||||
new i18n.Placeholder('', 'ph1', null !),
|
||||
new i18n.Text('text', null!),
|
||||
new i18n.Placeholder('', 'ph1', null!),
|
||||
]
|
||||
};
|
||||
const phMap = {
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {Component, Directive, Input} from '@angular/core';
|
||||
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
|
||||
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
import {browserDetection} from '@angular/platform-browser/testing/src/browser_util';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
@ -21,7 +21,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
@Directive({selector: '[dot.name]'})
|
||||
class MyDir {
|
||||
// TODO(issue/24571): remove '!'.
|
||||
@Input('dot.name') value !: string;
|
||||
@Input('dot.name') value!: string;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import {LIFECYCLE_HOOKS_VALUES, LifecycleHooks} from '@angular/compiler/src/lifecycle_reflector';
|
||||
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectionStrategy, Component, Directive, DoCheck, Injectable, NgModule, OnChanges, OnDestroy, OnInit, Pipe, SimpleChanges, ViewEncapsulation, ɵstringify as stringify} from '@angular/core';
|
||||
import {TestBed, async, inject} from '@angular/core/testing';
|
||||
import {async, inject, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {CompileDiDependencyMetadata, identifierName} from '../src/compile_metadata';
|
||||
import {CompileMetadataResolver} from '../src/metadata_resolver';
|
||||
@ -20,7 +20,9 @@ import {TEST_COMPILER_PROVIDERS} from './test_bindings';
|
||||
|
||||
{
|
||||
describe('CompileMetadataResolver', () => {
|
||||
beforeEach(() => { TestBed.configureCompiler({providers: TEST_COMPILER_PROVIDERS}); });
|
||||
beforeEach(() => {
|
||||
TestBed.configureCompiler({providers: TEST_COMPILER_PROVIDERS});
|
||||
});
|
||||
|
||||
it('should throw on the getDirectiveMetadata/getPipeMetadata methods if the module has not been loaded yet',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
@ -70,8 +72,8 @@ import {TEST_COMPILER_PROVIDERS} from './test_bindings';
|
||||
}
|
||||
|
||||
expect(() => resolver.loadNgModuleDirectiveAndPipeMetadata(SomeModule, true))
|
||||
.toThrowError(
|
||||
`Can't compile synchronously as ${stringify(ComponentWithExternalResources)} is still being loaded!`);
|
||||
.toThrowError(`Can't compile synchronously as ${
|
||||
stringify(ComponentWithExternalResources)} is still being loaded!`);
|
||||
}));
|
||||
|
||||
it('should read external metadata when sync=false',
|
||||
@ -106,7 +108,7 @@ import {TEST_COMPILER_PROVIDERS} from './test_bindings';
|
||||
|
||||
resolver.loadNgModuleDirectiveAndPipeMetadata(SomeModule, false).then(() => {
|
||||
const value: string =
|
||||
resolver.getDirectiveMetadata(ComponentWithoutModuleId).template !.templateUrl !;
|
||||
resolver.getDirectiveMetadata(ComponentWithoutModuleId).template !.templateUrl!;
|
||||
const expectedEndValue = './someUrl';
|
||||
expect(value.endsWith(expectedEndValue)).toBe(true);
|
||||
});
|
||||
@ -217,7 +219,7 @@ import {TEST_COMPILER_PROVIDERS} from './test_bindings';
|
||||
|
||||
it('should throw with descriptive error message when null is passed to declarations',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
@NgModule({declarations: [null !]})
|
||||
@NgModule({declarations: [null!]})
|
||||
class ModuleWithNullDeclared {
|
||||
}
|
||||
expect(() => resolver.loadNgModuleDirectiveAndPipeMetadata(ModuleWithNullDeclared, true))
|
||||
@ -227,7 +229,7 @@ import {TEST_COMPILER_PROVIDERS} from './test_bindings';
|
||||
|
||||
it('should throw with descriptive error message when null is passed to imports',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
@NgModule({imports: [null !]})
|
||||
@NgModule({imports: [null!]})
|
||||
class ModuleWithNullImported {
|
||||
}
|
||||
expect(() => resolver.loadNgModuleDirectiveAndPipeMetadata(ModuleWithNullImported, true))
|
||||
@ -248,7 +250,7 @@ import {TEST_COMPILER_PROVIDERS} from './test_bindings';
|
||||
|
||||
it('should throw with descriptive error message when encounter invalid provider',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
@NgModule({providers: [{provide: SimpleService, useClass: undefined !}]})
|
||||
@NgModule({providers: [{provide: SimpleService, useClass: undefined!}]})
|
||||
class SomeModule {
|
||||
}
|
||||
|
||||
@ -258,7 +260,7 @@ import {TEST_COMPILER_PROVIDERS} from './test_bindings';
|
||||
|
||||
it('should throw with descriptive error message when provider is undefined',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
@NgModule({providers: [undefined !]})
|
||||
@NgModule({providers: [undefined!]})
|
||||
class SomeModule {
|
||||
}
|
||||
|
||||
@ -290,10 +292,10 @@ import {TEST_COMPILER_PROVIDERS} from './test_bindings';
|
||||
|
||||
it('should throw with descriptive error message when null or undefined is passed to module bootstrap',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
@NgModule({bootstrap: [null !]})
|
||||
@NgModule({bootstrap: [null!]})
|
||||
class ModuleWithNullBootstrap {
|
||||
}
|
||||
@NgModule({bootstrap: [undefined !]})
|
||||
@NgModule({bootstrap: [undefined!]})
|
||||
class ModuleWithUndefinedBootstrap {
|
||||
}
|
||||
|
||||
@ -347,7 +349,6 @@ import {TEST_COMPILER_PROVIDERS} from './test_bindings';
|
||||
|
||||
it(`should throw an error when a Pipe is added to module's bootstrap list`,
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
|
||||
@Pipe({name: 'pipe'})
|
||||
class MyPipe {
|
||||
}
|
||||
@ -362,7 +363,6 @@ import {TEST_COMPILER_PROVIDERS} from './test_bindings';
|
||||
|
||||
it(`should throw an error when a Service is added to module's bootstrap list`,
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
|
||||
@NgModule({declarations: [], bootstrap: [SimpleService]})
|
||||
class ModuleWithServiceInBootstrap {
|
||||
}
|
||||
@ -373,7 +373,6 @@ import {TEST_COMPILER_PROVIDERS} from './test_bindings';
|
||||
|
||||
it('should generate an error when a dependency could not be resolved',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
|
||||
// Override the errorCollector so that error gets collected instead of
|
||||
// being thrown.
|
||||
(resolver as any)._errorCollector = (error: Error, type?: any) => {
|
||||
@ -390,12 +389,11 @@ import {TEST_COMPILER_PROVIDERS} from './test_bindings';
|
||||
class AppModule {
|
||||
}
|
||||
|
||||
const moduleMetadata = resolver.getNgModuleMetadata(AppModule) !;
|
||||
const moduleMetadata = resolver.getNgModuleMetadata(AppModule)!;
|
||||
expect(moduleMetadata).toBeTruthy();
|
||||
expect(moduleMetadata.declaredDirectives.length).toBe(1);
|
||||
const directive = moduleMetadata.declaredDirectives[0];
|
||||
const directiveMetadata =
|
||||
resolver.getNonNormalizedDirectiveMetadata(directive.reference) !;
|
||||
const directiveMetadata = resolver.getNonNormalizedDirectiveMetadata(directive.reference)!;
|
||||
expect(directiveMetadata).toBeTruthy();
|
||||
const {metadata} = directiveMetadata;
|
||||
const diDeps: CompileDiDependencyMetadata[] = metadata.type.diDeps;
|
||||
@ -405,7 +403,6 @@ import {TEST_COMPILER_PROVIDERS} from './test_bindings';
|
||||
|
||||
it(`should throw an error when a Directive is added to module's bootstrap list`,
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
|
||||
@Directive({selector: 'directive'})
|
||||
class MyDirective {
|
||||
}
|
||||
@ -420,7 +417,6 @@ import {TEST_COMPILER_PROVIDERS} from './test_bindings';
|
||||
|
||||
it(`should not throw an error when a Component is added to module's bootstrap list`,
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
|
||||
@Component({template: ''})
|
||||
class MyComp {
|
||||
}
|
||||
@ -439,7 +435,9 @@ import {TEST_COMPILER_PROVIDERS} from './test_bindings';
|
||||
class InvalidModule {
|
||||
}
|
||||
|
||||
expect(() => { resolver.getNgModuleMetadata(InvalidModule); })
|
||||
expect(() => {
|
||||
resolver.getNgModuleMetadata(InvalidModule);
|
||||
})
|
||||
.toThrowError(
|
||||
`Unexpected value '[object Object]' imported by the module 'InvalidModule'. Please add a @NgModule annotation.`);
|
||||
}));
|
||||
@ -447,7 +445,6 @@ import {TEST_COMPILER_PROVIDERS} from './test_bindings';
|
||||
|
||||
it('should dedupe declarations in NgModule',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
|
||||
@Component({template: ''})
|
||||
class MyComp {
|
||||
}
|
||||
@ -456,7 +453,7 @@ import {TEST_COMPILER_PROVIDERS} from './test_bindings';
|
||||
class MyModule {
|
||||
}
|
||||
|
||||
const modMeta = resolver.getNgModuleMetadata(MyModule) !;
|
||||
const modMeta = resolver.getNgModuleMetadata(MyModule)!;
|
||||
expect(modMeta.declaredDirectives.length).toBe(1);
|
||||
expect(modMeta.declaredDirectives[0].reference).toBe(MyComp);
|
||||
}));
|
||||
@ -495,9 +492,9 @@ class ComponentWithExternalResources {
|
||||
styles: ['someStyle'],
|
||||
interpolation: ['{{', '}}']
|
||||
})
|
||||
class ComponentWithEverythingInline implements OnChanges,
|
||||
OnInit, DoCheck, OnDestroy, AfterContentInit, AfterContentChecked, AfterViewInit,
|
||||
AfterViewChecked {
|
||||
class ComponentWithEverythingInline implements OnChanges, OnInit, DoCheck, OnDestroy,
|
||||
AfterContentInit, AfterContentChecked, AfterViewInit,
|
||||
AfterViewChecked {
|
||||
ngOnChanges(changes: SimpleChanges): void {}
|
||||
ngOnInit(): void {}
|
||||
ngDoCheck(): void {}
|
||||
@ -526,12 +523,12 @@ class MyBrokenComp2 {
|
||||
class SimpleService {
|
||||
}
|
||||
|
||||
@Component({selector: 'my-broken-comp', template: '', providers: [SimpleService, null !, [null]]})
|
||||
@Component({selector: 'my-broken-comp', template: '', providers: [SimpleService, null!, [null]]})
|
||||
class MyBrokenComp3 {
|
||||
}
|
||||
|
||||
@Component(
|
||||
{selector: 'my-broken-comp', template: '', viewProviders: [null !, SimpleService, [null]]})
|
||||
{selector: 'my-broken-comp', template: '', viewProviders: [null!, SimpleService, [null]]})
|
||||
class MyBrokenComp4 {
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,9 @@ import {serializeNodes} from './util/util';
|
||||
describe('Node serializer', () => {
|
||||
let parser: HtmlParser;
|
||||
|
||||
beforeEach(() => { parser = new HtmlParser(); });
|
||||
beforeEach(() => {
|
||||
parser = new HtmlParser();
|
||||
});
|
||||
|
||||
it('should support element', () => {
|
||||
const html = '<p></p>';
|
||||
|
@ -78,7 +78,7 @@ class _Humanizer implements html.Visitor {
|
||||
|
||||
private _appendContext(ast: html.Node, input: any[]): any[] {
|
||||
if (!this.includeSourceSpan) return input;
|
||||
input.push(ast.sourceSpan !.toString());
|
||||
input.push(ast.sourceSpan!.toString());
|
||||
return input;
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,9 @@ import {humanizeDom, humanizeDomSourceSpans, humanizeLineColumn} from './ast_spe
|
||||
describe('HtmlParser', () => {
|
||||
let parser: HtmlParser;
|
||||
|
||||
beforeEach(() => { parser = new HtmlParser(); });
|
||||
beforeEach(() => {
|
||||
parser = new HtmlParser();
|
||||
});
|
||||
|
||||
describe('parse', () => {
|
||||
describe('text nodes', () => {
|
||||
@ -77,11 +79,20 @@ import {humanizeDom, humanizeDomSourceSpans, humanizeLineColumn} from './ast_spe
|
||||
// <meta> - it can be present in head only
|
||||
// <command> - obsolete
|
||||
// <keygen> - obsolete
|
||||
['<map><area></map>', '<div><br></div>', '<colgroup><col></colgroup>',
|
||||
'<div><embed></div>', '<div><hr></div>', '<div><img></div>', '<div><input></div>',
|
||||
'<object><param>/<object>', '<audio><source></audio>', '<audio><track></audio>',
|
||||
['<map><area></map>',
|
||||
'<div><br></div>',
|
||||
'<colgroup><col></colgroup>',
|
||||
'<div><embed></div>',
|
||||
'<div><hr></div>',
|
||||
'<div><img></div>',
|
||||
'<div><input></div>',
|
||||
'<object><param>/<object>',
|
||||
'<audio><source></audio>',
|
||||
'<audio><track></audio>',
|
||||
'<p><wbr></p>',
|
||||
].forEach((html) => { expect(parser.parse(html, 'TestComp').errors).toEqual([]); });
|
||||
].forEach((html) => {
|
||||
expect(parser.parse(html, 'TestComp').errors).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should close void elements on text nodes', () => {
|
||||
@ -189,7 +200,6 @@ import {humanizeDom, humanizeDomSourceSpans, humanizeLineColumn} from './ast_spe
|
||||
[html.Text, '\n', 1],
|
||||
]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('attributes', () => {
|
||||
@ -263,8 +273,9 @@ import {humanizeDom, humanizeDomSourceSpans, humanizeLineColumn} from './ast_spe
|
||||
[html.Text, ' messages', 0],
|
||||
]);
|
||||
|
||||
expect(humanizeDom(new ParseTreeResult(cases[1].expression, [
|
||||
]))).toEqual([[html.Text, 'One {{message}}', 0]]);
|
||||
expect(humanizeDom(new ParseTreeResult(cases[1].expression, []))).toEqual([
|
||||
[html.Text, 'One {{message}}', 0]
|
||||
]);
|
||||
});
|
||||
|
||||
it('should parse out expansion forms', () => {
|
||||
@ -367,11 +378,11 @@ import {humanizeDom, humanizeDomSourceSpans, humanizeLineColumn} from './ast_spe
|
||||
it('should set the start and end source spans', () => {
|
||||
const node = <html.Element>parser.parse('<div>a</div>', 'TestComp').rootNodes[0];
|
||||
|
||||
expect(node.startSourceSpan !.start.offset).toEqual(0);
|
||||
expect(node.startSourceSpan !.end.offset).toEqual(5);
|
||||
expect(node.startSourceSpan!.start.offset).toEqual(0);
|
||||
expect(node.startSourceSpan!.end.offset).toEqual(5);
|
||||
|
||||
expect(node.endSourceSpan !.start.offset).toEqual(6);
|
||||
expect(node.endSourceSpan !.end.offset).toEqual(12);
|
||||
expect(node.endSourceSpan!.start.offset).toEqual(6);
|
||||
expect(node.endSourceSpan!.end.offset).toEqual(12);
|
||||
});
|
||||
|
||||
it('should support expansion form', () => {
|
||||
@ -393,15 +404,15 @@ import {humanizeDom, humanizeDomSourceSpans, humanizeLineColumn} from './ast_spe
|
||||
it('should report a value span for an attribute with a value', () => {
|
||||
const ast = parser.parse('<div bar="12"></div>', 'TestComp');
|
||||
const attr = (ast.rootNodes[0] as html.Element).attrs[0];
|
||||
expect(attr.valueSpan !.start.offset).toEqual(10);
|
||||
expect(attr.valueSpan !.end.offset).toEqual(12);
|
||||
expect(attr.valueSpan!.start.offset).toEqual(10);
|
||||
expect(attr.valueSpan!.end.offset).toEqual(12);
|
||||
});
|
||||
|
||||
it('should report a value span for an unquoted attribute value', () => {
|
||||
const ast = parser.parse('<div bar=12></div>', 'TestComp');
|
||||
const attr = (ast.rootNodes[0] as html.Element).attrs[0];
|
||||
expect(attr.valueSpan !.start.offset).toEqual(9);
|
||||
expect(attr.valueSpan !.end.offset).toEqual(11);
|
||||
expect(attr.valueSpan!.start.offset).toEqual(9);
|
||||
expect(attr.valueSpan!.end.offset).toEqual(11);
|
||||
});
|
||||
});
|
||||
|
||||
@ -426,7 +437,9 @@ import {humanizeDom, humanizeDomSourceSpans, humanizeLineColumn} from './ast_spe
|
||||
parser.parse('<div id="foo"><span id="bar">a</span><span>b</span></div>', 'TestComp');
|
||||
const accumulator: html.Node[] = [];
|
||||
const visitor = new class {
|
||||
visit(node: html.Node, context: any) { accumulator.push(node); }
|
||||
visit(node: html.Node, context: any) {
|
||||
accumulator.push(node);
|
||||
}
|
||||
visitElement(element: html.Element, context: any): any {
|
||||
html.visitAll(this, element.attrs);
|
||||
html.visitAll(this, element.children);
|
||||
@ -449,13 +462,21 @@ import {humanizeDom, humanizeDomSourceSpans, humanizeLineColumn} from './ast_spe
|
||||
|
||||
it('should skip typed visit if visit() returns a truthy value', () => {
|
||||
const visitor = new class {
|
||||
visit(node: html.Node, context: any) { return true; }
|
||||
visitElement(element: html.Element, context: any): any { throw Error('Unexpected'); }
|
||||
visit(node: html.Node, context: any) {
|
||||
return true;
|
||||
}
|
||||
visitElement(element: html.Element, context: any): any {
|
||||
throw Error('Unexpected');
|
||||
}
|
||||
visitAttribute(attribute: html.Attribute, context: any): any {
|
||||
throw Error('Unexpected');
|
||||
}
|
||||
visitText(text: html.Text, context: any): any { throw Error('Unexpected'); }
|
||||
visitComment(comment: html.Comment, context: any): any { throw Error('Unexpected'); }
|
||||
visitText(text: html.Text, context: any): any {
|
||||
throw Error('Unexpected');
|
||||
}
|
||||
visitComment(comment: html.Comment, context: any): any {
|
||||
throw Error('Unexpected');
|
||||
}
|
||||
visitExpansion(expansion: html.Expansion, context: any): any {
|
||||
throw Error('Unexpected');
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ import {humanizeDom} from './ast_spec_utils';
|
||||
|
||||
{
|
||||
describe('removeWhitespaces', () => {
|
||||
|
||||
function parseAndRemoveWS(template: string, options?: TokenizeOptions): any[] {
|
||||
return humanizeDom(removeWhitespaces(new HtmlParser().parse(template, 'TestComp', options)));
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import * as html from '../../src/ml_parser/ast';
|
||||
import {HtmlParser} from '../../src/ml_parser/html_parser';
|
||||
import {ExpansionResult, expandNodes} from '../../src/ml_parser/icu_ast_expander';
|
||||
import {expandNodes, ExpansionResult} from '../../src/ml_parser/icu_ast_expander';
|
||||
import {ParseError} from '../../src/parse_util';
|
||||
|
||||
import {humanizeNodes} from './ast_spec_utils';
|
||||
@ -56,28 +56,28 @@ import {humanizeNodes} from './ast_spec_utils';
|
||||
const nodes = expand(`{messages.length, plural,=0 {<b>bold</b>}}`).nodes;
|
||||
|
||||
const container: html.Element = <html.Element>nodes[0];
|
||||
expect(container.sourceSpan !.start.col).toEqual(0);
|
||||
expect(container.sourceSpan !.end.col).toEqual(42);
|
||||
expect(container.startSourceSpan !.start.col).toEqual(0);
|
||||
expect(container.startSourceSpan !.end.col).toEqual(42);
|
||||
expect(container.endSourceSpan !.start.col).toEqual(0);
|
||||
expect(container.endSourceSpan !.end.col).toEqual(42);
|
||||
expect(container.sourceSpan!.start.col).toEqual(0);
|
||||
expect(container.sourceSpan!.end.col).toEqual(42);
|
||||
expect(container.startSourceSpan!.start.col).toEqual(0);
|
||||
expect(container.startSourceSpan!.end.col).toEqual(42);
|
||||
expect(container.endSourceSpan!.start.col).toEqual(0);
|
||||
expect(container.endSourceSpan!.end.col).toEqual(42);
|
||||
|
||||
const switchExp = container.attrs[0];
|
||||
expect(switchExp.sourceSpan.start.col).toEqual(1);
|
||||
expect(switchExp.sourceSpan.end.col).toEqual(16);
|
||||
|
||||
const template: html.Element = <html.Element>container.children[0];
|
||||
expect(template.sourceSpan !.start.col).toEqual(25);
|
||||
expect(template.sourceSpan !.end.col).toEqual(41);
|
||||
expect(template.sourceSpan!.start.col).toEqual(25);
|
||||
expect(template.sourceSpan!.end.col).toEqual(41);
|
||||
|
||||
const switchCheck = template.attrs[0];
|
||||
expect(switchCheck.sourceSpan.start.col).toEqual(25);
|
||||
expect(switchCheck.sourceSpan.end.col).toEqual(28);
|
||||
|
||||
const b: html.Element = <html.Element>template.children[0];
|
||||
expect(b.sourceSpan !.start.col).toEqual(29);
|
||||
expect(b.endSourceSpan !.end.col).toEqual(40);
|
||||
expect(b.sourceSpan!.start.col).toEqual(29);
|
||||
expect(b.endSourceSpan!.end.col).toEqual(40);
|
||||
});
|
||||
|
||||
it('should handle other special forms', () => {
|
||||
|
@ -232,7 +232,6 @@ import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '../../src/parse_u
|
||||
[lex.TokenType.EOF, ''],
|
||||
]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('attributes', () => {
|
||||
@ -400,7 +399,6 @@ import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '../../src/parse_u
|
||||
[lex.TokenType.ATTR_VALUE, 'Unexpected character "EOF"', '0:8'],
|
||||
]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('closing tags', () => {
|
||||
@ -729,7 +727,6 @@ import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '../../src/parse_u
|
||||
[lex.TokenType.EOF, ''],
|
||||
]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('expansion forms', () => {
|
||||
@ -898,7 +895,7 @@ import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '../../src/parse_u
|
||||
const file = new ParseSourceFile(src, 'file://');
|
||||
const location = new ParseLocation(file, 12, 123, 456);
|
||||
const span = new ParseSourceSpan(location, location);
|
||||
const error = new lex.TokenError('**ERROR**', null !, span);
|
||||
const error = new lex.TokenError('**ERROR**', null!, span);
|
||||
expect(error.toString())
|
||||
.toEqual(`**ERROR** ("\n222\n333\n[ERROR ->]E\n444\n555\n"): file://@123:456`);
|
||||
});
|
||||
|
@ -15,16 +15,21 @@ class _SerializerVisitor implements html.Visitor {
|
||||
return `<${element.name}${this._visitAll(element.attrs, ' ')}/>`;
|
||||
}
|
||||
|
||||
return `<${element.name}${this._visitAll(element.attrs, ' ')}>${this._visitAll(element.children)}</${element.name}>`;
|
||||
return `<${element.name}${this._visitAll(element.attrs, ' ')}>${
|
||||
this._visitAll(element.children)}</${element.name}>`;
|
||||
}
|
||||
|
||||
visitAttribute(attribute: html.Attribute, context: any): any {
|
||||
return `${attribute.name}="${attribute.value}"`;
|
||||
}
|
||||
|
||||
visitText(text: html.Text, context: any): any { return text.value; }
|
||||
visitText(text: html.Text, context: any): any {
|
||||
return text.value;
|
||||
}
|
||||
|
||||
visitComment(comment: html.Comment, context: any): any { return `<!--${comment.value}-->`; }
|
||||
visitComment(comment: html.Comment, context: any): any {
|
||||
return `<!--${comment.value}-->`;
|
||||
}
|
||||
|
||||
visitExpansion(expansion: html.Expansion, context: any): any {
|
||||
return `{${expansion.switchValue}, ${expansion.type},${this._visitAll(expansion.cases)}}`;
|
||||
|
@ -33,7 +33,9 @@ class SimpleClass {}
|
||||
describe('NgModuleResolver', () => {
|
||||
let resolver: NgModuleResolver;
|
||||
|
||||
beforeEach(() => { resolver = new NgModuleResolver(new JitReflector()); });
|
||||
beforeEach(() => {
|
||||
resolver = new NgModuleResolver(new JitReflector());
|
||||
});
|
||||
|
||||
it('should read out the metadata from the class', () => {
|
||||
const moduleMetadata = resolver.resolve(SomeModule);
|
||||
@ -66,6 +68,5 @@ class SimpleClass {}
|
||||
|
||||
expect(resolver.resolve(ChildWithDecorator)).toEqual(new NgModule({id: 'c'}));
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
@ -18,14 +18,16 @@ import {extractSourceMap, originalPositionFor} from '@angular/compiler/testing/s
|
||||
const fileB = new ParseSourceFile('b0b1b2b3b4b5b6b7b8b9', 'b.js');
|
||||
let ctx: EmitterVisitorContext;
|
||||
|
||||
beforeEach(() => { ctx = EmitterVisitorContext.createRoot(); });
|
||||
beforeEach(() => {
|
||||
ctx = EmitterVisitorContext.createRoot();
|
||||
});
|
||||
|
||||
it('should add source files to the source map', () => {
|
||||
ctx.print(createSourceSpan(fileA, 0), 'o0');
|
||||
ctx.print(createSourceSpan(fileA, 1), 'o1');
|
||||
ctx.print(createSourceSpan(fileB, 0), 'o2');
|
||||
ctx.print(createSourceSpan(fileB, 1), 'o3');
|
||||
const sm = ctx.toSourceMapGenerator('o.ts').toJSON() !;
|
||||
const sm = ctx.toSourceMapGenerator('o.ts').toJSON()!;
|
||||
expect(sm.sources).toEqual([fileA.url, fileB.url]);
|
||||
expect(sm.sourcesContent).toEqual([fileA.content, fileB.content]);
|
||||
});
|
||||
@ -43,7 +45,7 @@ import {extractSourceMap, originalPositionFor} from '@angular/compiler/testing/s
|
||||
it('should be able to shift the content', () => {
|
||||
ctx.print(createSourceSpan(fileA, 0), 'fileA-0');
|
||||
|
||||
const sm = ctx.toSourceMapGenerator('o.ts', 10).toJSON() !;
|
||||
const sm = ctx.toSourceMapGenerator('o.ts', 10).toJSON()!;
|
||||
expect(originalPositionFor(sm, {line: 11, column: 0})).toEqual({
|
||||
line: 1,
|
||||
column: 0,
|
||||
@ -111,9 +113,9 @@ import {extractSourceMap, originalPositionFor} from '@angular/compiler/testing/s
|
||||
// All lines / columns indexes are 0-based
|
||||
// Note: source-map line indexes are 1-based, column 0-based
|
||||
function expectMap(
|
||||
ctx: EmitterVisitorContext, genLine: number, genCol: number, source: string | null = null,
|
||||
srcLine: number | null = null, srcCol: number | null = null) {
|
||||
const sm = ctx.toSourceMapGenerator('o.ts').toJSON() !;
|
||||
ctx: EmitterVisitorContext, genLine: number, genCol: number, source: string|null = null,
|
||||
srcLine: number|null = null, srcCol: number|null = null) {
|
||||
const sm = ctx.toSourceMapGenerator('o.ts').toJSON()!;
|
||||
const genPosition = {line: genLine + 1, column: genCol};
|
||||
const origPosition = originalPositionFor(sm, genPosition);
|
||||
// TODO: Review use of `any` here (#19904)
|
||||
@ -124,7 +126,7 @@ function expectMap(
|
||||
|
||||
// returns the number of segments per line
|
||||
function nbSegmentsPerLine(ctx: EmitterVisitorContext) {
|
||||
const sm = ctx.toSourceMapGenerator('o.ts').toJSON() !;
|
||||
const sm = ctx.toSourceMapGenerator('o.ts').toJSON()!;
|
||||
const lines = sm.mappings.split(';');
|
||||
return lines.map(l => {
|
||||
const m = l.match(/,/g);
|
||||
|
@ -11,24 +11,34 @@ import {escapeIdentifier} from '@angular/compiler/src/output/abstract_emitter';
|
||||
{
|
||||
describe('AbstractEmitter', () => {
|
||||
describe('escapeIdentifier', () => {
|
||||
it('should escape single quotes',
|
||||
() => { expect(escapeIdentifier(`'`, false)).toEqual(`'\\''`); });
|
||||
it('should escape single quotes', () => {
|
||||
expect(escapeIdentifier(`'`, false)).toEqual(`'\\''`);
|
||||
});
|
||||
|
||||
it('should escape backslash',
|
||||
() => { expect(escapeIdentifier('\\', false)).toEqual(`'\\\\'`); });
|
||||
it('should escape backslash', () => {
|
||||
expect(escapeIdentifier('\\', false)).toEqual(`'\\\\'`);
|
||||
});
|
||||
|
||||
it('should escape newlines',
|
||||
() => { expect(escapeIdentifier('\n', false)).toEqual(`'\\n'`); });
|
||||
it('should escape newlines', () => {
|
||||
expect(escapeIdentifier('\n', false)).toEqual(`'\\n'`);
|
||||
});
|
||||
|
||||
it('should escape carriage returns',
|
||||
() => { expect(escapeIdentifier('\r', false)).toEqual(`'\\r'`); });
|
||||
it('should escape carriage returns', () => {
|
||||
expect(escapeIdentifier('\r', false)).toEqual(`'\\r'`);
|
||||
});
|
||||
|
||||
it('should escape $', () => { expect(escapeIdentifier('$', true)).toEqual(`'\\$'`); });
|
||||
it('should not escape $', () => { expect(escapeIdentifier('$', false)).toEqual(`'$'`); });
|
||||
it('should add quotes for non-identifiers',
|
||||
() => { expect(escapeIdentifier('==', false, false)).toEqual(`'=='`); });
|
||||
it('does not escape class (but it probably should)',
|
||||
() => { expect(escapeIdentifier('class', false, false)).toEqual('class'); });
|
||||
it('should escape $', () => {
|
||||
expect(escapeIdentifier('$', true)).toEqual(`'\\$'`);
|
||||
});
|
||||
it('should not escape $', () => {
|
||||
expect(escapeIdentifier('$', false)).toEqual(`'$'`);
|
||||
});
|
||||
it('should add quotes for non-identifiers', () => {
|
||||
expect(escapeIdentifier('==', false, false)).toEqual(`'=='`);
|
||||
});
|
||||
it('does not escape class (but it probably should)', () => {
|
||||
expect(escapeIdentifier('class', false, false)).toEqual('class');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -22,12 +22,14 @@ const someGenFilePath = 'somePackage/someGenFile';
|
||||
let emitter: JavaScriptEmitter;
|
||||
let someVar: o.ReadVarExpr;
|
||||
|
||||
beforeEach(() => { emitter = new JavaScriptEmitter(); });
|
||||
beforeEach(() => {
|
||||
emitter = new JavaScriptEmitter();
|
||||
});
|
||||
|
||||
function emitSourceMap(stmt: o.Statement | o.Statement[], preamble?: string): SourceMap {
|
||||
function emitSourceMap(stmt: o.Statement|o.Statement[], preamble?: string): SourceMap {
|
||||
const stmts = Array.isArray(stmt) ? stmt : [stmt];
|
||||
const source = emitter.emitStatements(someGenFilePath, stmts, preamble);
|
||||
return extractSourceMap(source) !;
|
||||
return extractSourceMap(source)!;
|
||||
}
|
||||
|
||||
describe('source maps', () => {
|
||||
|
@ -171,8 +171,9 @@ const externalModuleIdentifier = new o.ExternalReference(anotherModuleUrl, 'some
|
||||
});
|
||||
|
||||
it('should support function statements', () => {
|
||||
expect(emitStmt(new o.DeclareFunctionStmt('someFn', [], [
|
||||
]))).toEqual(['function someFn() {', '}'].join('\n'));
|
||||
expect(emitStmt(new o.DeclareFunctionStmt('someFn', [], []))).toEqual([
|
||||
'function someFn() {', '}'
|
||||
].join('\n'));
|
||||
expect(emitStmt(new o.DeclareFunctionStmt('someFn', [], [], null, [o.StmtModifier.Exported])))
|
||||
.toEqual([
|
||||
'function someFn() {', '}',
|
||||
@ -181,8 +182,9 @@ const externalModuleIdentifier = new o.ExternalReference(anotherModuleUrl, 'some
|
||||
expect(emitStmt(new o.DeclareFunctionStmt('someFn', [], [
|
||||
new o.ReturnStatement(o.literal(1))
|
||||
]))).toEqual(['function someFn() {', ' return 1;', '}'].join('\n'));
|
||||
expect(emitStmt(new o.DeclareFunctionStmt('someFn', [new o.FnParam('param1')], [
|
||||
]))).toEqual(['function someFn(param1) {', '}'].join('\n'));
|
||||
expect(emitStmt(new o.DeclareFunctionStmt('someFn', [new o.FnParam('param1')], []))).toEqual([
|
||||
'function someFn(param1) {', '}'
|
||||
].join('\n'));
|
||||
});
|
||||
|
||||
it('should support comments', () => {
|
||||
@ -219,25 +221,29 @@ const externalModuleIdentifier = new o.ExternalReference(anotherModuleUrl, 'some
|
||||
].join('\n'));
|
||||
});
|
||||
|
||||
it('should support support throwing',
|
||||
() => { expect(emitStmt(new o.ThrowStmt(someVar))).toEqual('throw someVar;'); });
|
||||
it('should support support throwing', () => {
|
||||
expect(emitStmt(new o.ThrowStmt(someVar))).toEqual('throw someVar;');
|
||||
});
|
||||
|
||||
describe('classes', () => {
|
||||
let callSomeMethod: o.Statement;
|
||||
|
||||
beforeEach(() => { callSomeMethod = o.THIS_EXPR.callMethod('someMethod', []).toStmt(); });
|
||||
beforeEach(() => {
|
||||
callSomeMethod = o.THIS_EXPR.callMethod('someMethod', []).toStmt();
|
||||
});
|
||||
|
||||
it('should support declaring classes', () => {
|
||||
expect(emitStmt(new o.ClassStmt('SomeClass', null !, [], [], null !, [
|
||||
]))).toEqual(['function SomeClass() {', '}'].join('\n'));
|
||||
expect(emitStmt(new o.ClassStmt('SomeClass', null!, [], [], null!, []))).toEqual([
|
||||
'function SomeClass() {', '}'
|
||||
].join('\n'));
|
||||
expect(emitStmt(new o.ClassStmt(
|
||||
'SomeClass', null !, [], [], null !, [], [o.StmtModifier.Exported])))
|
||||
'SomeClass', null!, [], [], null!, [], [o.StmtModifier.Exported])))
|
||||
.toEqual([
|
||||
'function SomeClass() {', '}',
|
||||
`Object.defineProperty(exports, 'SomeClass', { get: function() { return SomeClass; }});`
|
||||
].join('\n'));
|
||||
expect(emitStmt(
|
||||
new o.ClassStmt('SomeClass', o.variable('SomeSuperClass'), [], [], null !, [])))
|
||||
expect(
|
||||
emitStmt(new o.ClassStmt('SomeClass', o.variable('SomeSuperClass'), [], [], null!, [])))
|
||||
.toEqual([
|
||||
'function SomeClass() {', '}',
|
||||
'SomeClass.prototype = Object.create(SomeSuperClass.prototype);'
|
||||
@ -247,23 +253,22 @@ const externalModuleIdentifier = new o.ExternalReference(anotherModuleUrl, 'some
|
||||
it('should support declaring constructors', () => {
|
||||
const superCall = o.SUPER_EXPR.callFn([o.variable('someParam')]).toStmt();
|
||||
expect(emitStmt(new o.ClassStmt(
|
||||
'SomeClass', null !, [], [], new o.ClassMethod(null !, [], []), [])))
|
||||
'SomeClass', null!, [], [], new o.ClassMethod(null!, [], []), [])))
|
||||
.toEqual(['function SomeClass() {', '}'].join('\n'));
|
||||
expect(emitStmt(new o.ClassStmt(
|
||||
'SomeClass', null !, [], [],
|
||||
new o.ClassMethod(null !, [new o.FnParam('someParam')], []), [])))
|
||||
'SomeClass', null!, [], [],
|
||||
new o.ClassMethod(null!, [new o.FnParam('someParam')], []), [])))
|
||||
.toEqual(['function SomeClass(someParam) {', '}'].join('\n'));
|
||||
expect(emitStmt(new o.ClassStmt(
|
||||
'SomeClass', o.variable('SomeSuperClass'), [], [],
|
||||
new o.ClassMethod(null !, [], [superCall]), [])))
|
||||
new o.ClassMethod(null!, [], [superCall]), [])))
|
||||
.toEqual([
|
||||
'function SomeClass() {', ' var self = this;',
|
||||
' SomeSuperClass.call(this, someParam);', '}',
|
||||
'SomeClass.prototype = Object.create(SomeSuperClass.prototype);'
|
||||
].join('\n'));
|
||||
expect(
|
||||
emitStmt(new o.ClassStmt(
|
||||
'SomeClass', null !, [], [], new o.ClassMethod(null !, [], [callSomeMethod]), [])))
|
||||
expect(emitStmt(new o.ClassStmt(
|
||||
'SomeClass', null!, [], [], new o.ClassMethod(null!, [], [callSomeMethod]), [])))
|
||||
.toEqual([
|
||||
'function SomeClass() {', ' var self = this;', ' self.someMethod();', '}'
|
||||
].join('\n'));
|
||||
@ -271,14 +276,14 @@ const externalModuleIdentifier = new o.ExternalReference(anotherModuleUrl, 'some
|
||||
|
||||
it('should support declaring getters', () => {
|
||||
expect(emitStmt(new o.ClassStmt(
|
||||
'SomeClass', null !, [], [new o.ClassGetter('someGetter', [])], null !, [])))
|
||||
'SomeClass', null!, [], [new o.ClassGetter('someGetter', [])], null!, [])))
|
||||
.toEqual([
|
||||
'function SomeClass() {', '}',
|
||||
`Object.defineProperty(SomeClass.prototype, 'someGetter', { get: function() {`, `}});`
|
||||
].join('\n'));
|
||||
expect(emitStmt(new o.ClassStmt(
|
||||
'SomeClass', null !, [], [new o.ClassGetter('someGetter', [callSomeMethod])],
|
||||
null !, [])))
|
||||
'SomeClass', null!, [], [new o.ClassGetter('someGetter', [callSomeMethod])],
|
||||
null!, [])))
|
||||
.toEqual([
|
||||
'function SomeClass() {', '}',
|
||||
`Object.defineProperty(SomeClass.prototype, 'someGetter', { get: function() {`,
|
||||
@ -288,19 +293,19 @@ const externalModuleIdentifier = new o.ExternalReference(anotherModuleUrl, 'some
|
||||
|
||||
it('should support methods', () => {
|
||||
expect(emitStmt(new o.ClassStmt(
|
||||
'SomeClass', null !, [], [], null !, [new o.ClassMethod('someMethod', [], [])])))
|
||||
'SomeClass', null!, [], [], null!, [new o.ClassMethod('someMethod', [], [])])))
|
||||
.toEqual([
|
||||
'function SomeClass() {', '}', 'SomeClass.prototype.someMethod = function() {', '};'
|
||||
].join('\n'));
|
||||
expect(emitStmt(new o.ClassStmt(
|
||||
'SomeClass', null !, [], [], null !,
|
||||
'SomeClass', null!, [], [], null!,
|
||||
[new o.ClassMethod('someMethod', [new o.FnParam('someParam')], [])])))
|
||||
.toEqual([
|
||||
'function SomeClass() {', '}',
|
||||
'SomeClass.prototype.someMethod = function(someParam) {', '};'
|
||||
].join('\n'));
|
||||
expect(emitStmt(new o.ClassStmt(
|
||||
'SomeClass', null !, [], [], null !,
|
||||
'SomeClass', null!, [], [], null!,
|
||||
[new o.ClassMethod('someMethod', [], [callSomeMethod])])))
|
||||
.toEqual([
|
||||
'function SomeClass() {', '}', 'SomeClass.prototype.someMethod = function() {',
|
||||
|
@ -15,8 +15,8 @@ import * as o from '../../src/output/output_ast';
|
||||
const ref1 = new o.ExternalReference('aModule', 'name1');
|
||||
const ref2 = new o.ExternalReference('aModule', 'name2');
|
||||
const stmt =
|
||||
o.variable('test').set(o.NULL_EXPR).toDeclStmt(o.importType(ref1, [o.importType(
|
||||
ref2) !]));
|
||||
o.variable('test').set(o.NULL_EXPR).toDeclStmt(o.importType(ref1, [o.importType(ref2)!
|
||||
]));
|
||||
|
||||
expect(o.collectExternalReferences([stmt])).toEqual([ref1, ref2]);
|
||||
});
|
||||
|
@ -57,7 +57,7 @@ const anotherModuleUrl = 'somePackage/someOtherPath';
|
||||
o.literal('use strict').toStmt(),
|
||||
],
|
||||
ctx);
|
||||
const matches = ctx.toSource().match(/'use strict';/g) !;
|
||||
const matches = ctx.toSource().match(/'use strict';/g)!;
|
||||
expect(matches.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
@ -52,7 +52,7 @@ import {SourceMapGenerator, toBase64String} from '@angular/compiler/src/output/s
|
||||
.addMapping(3, 'a.js', 5, 2);
|
||||
|
||||
// Generated with https://sokra.github.io/source-map-visualization using a TS source map
|
||||
expect(map.toJSON() !.mappings)
|
||||
expect(map.toJSON()!.mappings)
|
||||
.toEqual(
|
||||
'AAAA,IAAM,CAAC,GAAe,CAAC,CAAC;AACxB,IAAM,CAAC,GAAG,CAAC,CAAC;AAEZ,EAAE,CAAC,OAAO,CAAC,UAAA,CAAC;IACR,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC,CAAC,CAAA');
|
||||
});
|
||||
@ -64,7 +64,7 @@ import {SourceMapGenerator, toBase64String} from '@angular/compiler/src/output/s
|
||||
.addSource('url.ts', null)
|
||||
.addLine()
|
||||
.addMapping(0, 'inline.ts', 0, 0)
|
||||
.toJSON() !;
|
||||
.toJSON()!;
|
||||
|
||||
expect(map.file).toEqual('out.js');
|
||||
expect(map.sources).toEqual(['inline.ts', 'url.ts']);
|
||||
@ -81,7 +81,11 @@ import {SourceMapGenerator, toBase64String} from '@angular/compiler/src/output/s
|
||||
|
||||
describe('encodeB64String', () => {
|
||||
it('should return the b64 encoded value', () => {
|
||||
[['', ''], ['a', 'YQ=='], ['Foo', 'Rm9v'], ['Foo1', 'Rm9vMQ=='], ['Foo12', 'Rm9vMTI='],
|
||||
[['', ''],
|
||||
['a', 'YQ=='],
|
||||
['Foo', 'Rm9v'],
|
||||
['Foo1', 'Rm9vMQ=='],
|
||||
['Foo12', 'Rm9vMTI='],
|
||||
['Foo123', 'Rm9vMTIz'],
|
||||
].forEach(([src, b64]) => expect(toBase64String(src)).toEqual(b64));
|
||||
});
|
||||
@ -113,7 +117,7 @@ import {SourceMapGenerator, toBase64String} from '@angular/compiler/src/output/s
|
||||
|
||||
it('should throw when adding segments without column', () => {
|
||||
expect(() => {
|
||||
new SourceMapGenerator('out.js').addSource('in.js').addLine().addMapping(null !);
|
||||
new SourceMapGenerator('out.js').addSource('in.js').addLine().addMapping(null!);
|
||||
}).toThrowError('The column in the generated code must be provided');
|
||||
});
|
||||
|
||||
|
@ -31,10 +31,10 @@ const someGenFilePath = 'somePackage/someGenFile';
|
||||
someVar = o.variable('someVar');
|
||||
});
|
||||
|
||||
function emitSourceMap(stmt: o.Statement | o.Statement[], preamble?: string): SourceMap {
|
||||
function emitSourceMap(stmt: o.Statement|o.Statement[], preamble?: string): SourceMap {
|
||||
const stmts = Array.isArray(stmt) ? stmt : [stmt];
|
||||
const source = emitter.emitStatements(someGenFilePath, stmts, preamble);
|
||||
return extractSourceMap(source) !;
|
||||
return extractSourceMap(source)!;
|
||||
}
|
||||
|
||||
describe('source maps', () => {
|
||||
|
@ -35,7 +35,7 @@ const externalModuleIdentifier = new o.ExternalReference(anotherModuleUrl, 'some
|
||||
someVar = o.variable('someVar', null, null);
|
||||
});
|
||||
|
||||
function emitStmt(stmt: o.Statement | o.Statement[], preamble?: string): string {
|
||||
function emitStmt(stmt: o.Statement|o.Statement[], preamble?: string): string {
|
||||
const stmts = Array.isArray(stmt) ? stmt : [stmt];
|
||||
const source = emitter.emitStatements(someGenFilePath, stmts, preamble);
|
||||
return stripSourceMapAndNewLine(source);
|
||||
@ -223,15 +223,17 @@ const externalModuleIdentifier = new o.ExternalReference(anotherModuleUrl, 'some
|
||||
});
|
||||
|
||||
it('should support function statements', () => {
|
||||
expect(emitStmt(new o.DeclareFunctionStmt('someFn', [], [
|
||||
]))).toEqual(['function someFn():void {', '}'].join('\n'));
|
||||
expect(emitStmt(new o.DeclareFunctionStmt('someFn', [], []))).toEqual([
|
||||
'function someFn():void {', '}'
|
||||
].join('\n'));
|
||||
expect(emitStmt(new o.DeclareFunctionStmt('someFn', [], [], null, [o.StmtModifier.Exported])))
|
||||
.toEqual(['export function someFn():void {', '}'].join('\n'));
|
||||
expect(emitStmt(new o.DeclareFunctionStmt(
|
||||
'someFn', [], [new o.ReturnStatement(o.literal(1))], o.INT_TYPE)))
|
||||
.toEqual(['function someFn():number {', ' return 1;', '}'].join('\n'));
|
||||
expect(emitStmt(new o.DeclareFunctionStmt('someFn', [new o.FnParam('param1', o.INT_TYPE)], [
|
||||
]))).toEqual(['function someFn(param1:number):void {', '}'].join('\n'));
|
||||
expect(
|
||||
emitStmt(new o.DeclareFunctionStmt('someFn', [new o.FnParam('param1', o.INT_TYPE)], [])))
|
||||
.toEqual(['function someFn(param1:number):void {', '}'].join('\n'));
|
||||
});
|
||||
|
||||
it('should support comments', () => {
|
||||
@ -266,43 +268,47 @@ const externalModuleIdentifier = new o.ExternalReference(anotherModuleUrl, 'some
|
||||
].join('\n'));
|
||||
});
|
||||
|
||||
it('should support support throwing',
|
||||
() => { expect(emitStmt(new o.ThrowStmt(someVar))).toEqual('throw someVar;'); });
|
||||
it('should support support throwing', () => {
|
||||
expect(emitStmt(new o.ThrowStmt(someVar))).toEqual('throw someVar;');
|
||||
});
|
||||
|
||||
describe('classes', () => {
|
||||
let callSomeMethod: o.Statement;
|
||||
|
||||
beforeEach(() => { callSomeMethod = o.THIS_EXPR.callMethod('someMethod', []).toStmt(); });
|
||||
beforeEach(() => {
|
||||
callSomeMethod = o.THIS_EXPR.callMethod('someMethod', []).toStmt();
|
||||
});
|
||||
|
||||
|
||||
it('should support declaring classes', () => {
|
||||
expect(emitStmt(new o.ClassStmt('SomeClass', null !, [], [], null !, [
|
||||
]))).toEqual(['class SomeClass {', '}'].join('\n'));
|
||||
expect(emitStmt(new o.ClassStmt('SomeClass', null !, [], [], null !, [], [
|
||||
expect(emitStmt(new o.ClassStmt('SomeClass', null!, [], [], null!, []))).toEqual([
|
||||
'class SomeClass {', '}'
|
||||
].join('\n'));
|
||||
expect(emitStmt(new o.ClassStmt('SomeClass', null!, [], [], null!, [], [
|
||||
o.StmtModifier.Exported
|
||||
]))).toEqual(['export class SomeClass {', '}'].join('\n'));
|
||||
expect(emitStmt(new o.ClassStmt('SomeClass', o.variable('SomeSuperClass'), [], [], null !, [
|
||||
]))).toEqual(['class SomeClass extends SomeSuperClass {', '}'].join('\n'));
|
||||
expect(
|
||||
emitStmt(new o.ClassStmt('SomeClass', o.variable('SomeSuperClass'), [], [], null!, [])))
|
||||
.toEqual(['class SomeClass extends SomeSuperClass {', '}'].join('\n'));
|
||||
});
|
||||
|
||||
it('should support declaring constructors', () => {
|
||||
const superCall = o.SUPER_EXPR.callFn([o.variable('someParam')]).toStmt();
|
||||
expect(emitStmt(new o.ClassStmt(
|
||||
'SomeClass', null !, [], [], new o.ClassMethod(null !, [], []), [])))
|
||||
'SomeClass', null!, [], [], new o.ClassMethod(null!, [], []), [])))
|
||||
.toEqual(['class SomeClass {', ' constructor() {', ' }', '}'].join('\n'));
|
||||
expect(emitStmt(new o.ClassStmt(
|
||||
'SomeClass', null !, [], [],
|
||||
new o.ClassMethod(null !, [new o.FnParam('someParam', o.INT_TYPE)], []), [])))
|
||||
'SomeClass', null!, [], [],
|
||||
new o.ClassMethod(null!, [new o.FnParam('someParam', o.INT_TYPE)], []), [])))
|
||||
.toEqual(
|
||||
['class SomeClass {', ' constructor(someParam:number) {', ' }', '}'].join('\n'));
|
||||
expect(emitStmt(new o.ClassStmt(
|
||||
'SomeClass', null !, [], [], new o.ClassMethod(null !, [], [superCall]), [])))
|
||||
'SomeClass', null!, [], [], new o.ClassMethod(null!, [], [superCall]), [])))
|
||||
.toEqual([
|
||||
'class SomeClass {', ' constructor() {', ' super(someParam);', ' }', '}'
|
||||
].join('\n'));
|
||||
expect(
|
||||
emitStmt(new o.ClassStmt(
|
||||
'SomeClass', null !, [], [], new o.ClassMethod(null !, [], [callSomeMethod]), [])))
|
||||
expect(emitStmt(new o.ClassStmt(
|
||||
'SomeClass', null!, [], [], new o.ClassMethod(null!, [], [callSomeMethod]), [])))
|
||||
.toEqual([
|
||||
'class SomeClass {', ' constructor() {', ' this.someMethod();', ' }', '}'
|
||||
].join('\n'));
|
||||
@ -310,57 +316,56 @@ const externalModuleIdentifier = new o.ExternalReference(anotherModuleUrl, 'some
|
||||
|
||||
it('should support declaring fields', () => {
|
||||
expect(emitStmt(new o.ClassStmt(
|
||||
'SomeClass', null !, [new o.ClassField('someField')], [], null !, [])))
|
||||
'SomeClass', null!, [new o.ClassField('someField')], [], null!, [])))
|
||||
.toEqual(['class SomeClass {', ' someField:any;', '}'].join('\n'));
|
||||
expect(
|
||||
emitStmt(new o.ClassStmt(
|
||||
'SomeClass', null !, [new o.ClassField('someField', o.INT_TYPE)], [], null !, [])))
|
||||
expect(emitStmt(new o.ClassStmt(
|
||||
'SomeClass', null!, [new o.ClassField('someField', o.INT_TYPE)], [], null!, [])))
|
||||
.toEqual(['class SomeClass {', ' someField:number;', '}'].join('\n'));
|
||||
expect(emitStmt(new o.ClassStmt(
|
||||
'SomeClass', null !,
|
||||
[new o.ClassField('someField', o.INT_TYPE, [o.StmtModifier.Private])], [],
|
||||
null !, [])))
|
||||
'SomeClass', null!,
|
||||
[new o.ClassField('someField', o.INT_TYPE, [o.StmtModifier.Private])], [], null!,
|
||||
[])))
|
||||
.toEqual(['class SomeClass {', ' /*private*/ someField:number;', '}'].join('\n'));
|
||||
});
|
||||
|
||||
it('should support declaring getters', () => {
|
||||
expect(emitStmt(new o.ClassStmt(
|
||||
'SomeClass', null !, [], [new o.ClassGetter('someGetter', [])], null !, [])))
|
||||
'SomeClass', null!, [], [new o.ClassGetter('someGetter', [])], null!, [])))
|
||||
.toEqual(['class SomeClass {', ' get someGetter():any {', ' }', '}'].join('\n'));
|
||||
expect(emitStmt(new o.ClassStmt(
|
||||
'SomeClass', null !, [], [new o.ClassGetter('someGetter', [], o.INT_TYPE)],
|
||||
null !, [])))
|
||||
'SomeClass', null!, [], [new o.ClassGetter('someGetter', [], o.INT_TYPE)], null!,
|
||||
[])))
|
||||
.toEqual(['class SomeClass {', ' get someGetter():number {', ' }', '}'].join('\n'));
|
||||
expect(emitStmt(new o.ClassStmt(
|
||||
'SomeClass', null !, [], [new o.ClassGetter('someGetter', [callSomeMethod])],
|
||||
null !, [])))
|
||||
'SomeClass', null!, [], [new o.ClassGetter('someGetter', [callSomeMethod])],
|
||||
null!, [])))
|
||||
.toEqual([
|
||||
'class SomeClass {', ' get someGetter():any {', ' this.someMethod();', ' }', '}'
|
||||
].join('\n'));
|
||||
expect(emitStmt(new o.ClassStmt(
|
||||
'SomeClass', null !, [],
|
||||
[new o.ClassGetter('someGetter', [], null !, [o.StmtModifier.Private])], null !,
|
||||
[])))
|
||||
expect(
|
||||
emitStmt(new o.ClassStmt(
|
||||
'SomeClass', null!, [],
|
||||
[new o.ClassGetter('someGetter', [], null!, [o.StmtModifier.Private])], null!, [])))
|
||||
.toEqual(
|
||||
['class SomeClass {', ' private get someGetter():any {', ' }', '}'].join('\n'));
|
||||
});
|
||||
|
||||
it('should support methods', () => {
|
||||
expect(emitStmt(new o.ClassStmt('SomeClass', null !, [], [], null !, [
|
||||
expect(emitStmt(new o.ClassStmt('SomeClass', null!, [], [], null!, [
|
||||
new o.ClassMethod('someMethod', [], [])
|
||||
]))).toEqual(['class SomeClass {', ' someMethod():void {', ' }', '}'].join('\n'));
|
||||
expect(emitStmt(new o.ClassStmt('SomeClass', null !, [], [], null !, [
|
||||
expect(emitStmt(new o.ClassStmt('SomeClass', null!, [], [], null!, [
|
||||
new o.ClassMethod('someMethod', [], [], o.INT_TYPE)
|
||||
]))).toEqual(['class SomeClass {', ' someMethod():number {', ' }', '}'].join('\n'));
|
||||
expect(
|
||||
emitStmt(new o.ClassStmt(
|
||||
'SomeClass', null !, [], [], null !,
|
||||
'SomeClass', null!, [], [], null!,
|
||||
[new o.ClassMethod('someMethod', [new o.FnParam('someParam', o.INT_TYPE)], [])])))
|
||||
.toEqual([
|
||||
'class SomeClass {', ' someMethod(someParam:number):void {', ' }', '}'
|
||||
].join('\n'));
|
||||
expect(emitStmt(new o.ClassStmt(
|
||||
'SomeClass', null !, [], [], null !,
|
||||
'SomeClass', null!, [], [], null!,
|
||||
[new o.ClassMethod('someMethod', [], [callSomeMethod])])))
|
||||
.toEqual([
|
||||
'class SomeClass {', ' someMethod():void {', ' this.someMethod();', ' }', '}'
|
||||
@ -412,7 +417,7 @@ const externalModuleIdentifier = new o.ExternalReference(anotherModuleUrl, 'some
|
||||
|
||||
it('should support combined types', () => {
|
||||
const writeVarExpr = o.variable('a').set(o.NULL_EXPR);
|
||||
expect(emitStmt(writeVarExpr.toDeclStmt(new o.ArrayType(null !))))
|
||||
expect(emitStmt(writeVarExpr.toDeclStmt(new o.ArrayType(null!))))
|
||||
.toEqual('var a:any[] = (null as any);');
|
||||
expect(emitStmt(writeVarExpr.toDeclStmt(new o.ArrayType(o.INT_TYPE))))
|
||||
.toEqual('var a:number[] = (null as any);');
|
||||
|
@ -21,7 +21,9 @@ class SimpleClass {}
|
||||
describe('PipeResolver', () => {
|
||||
let resolver: PipeResolver;
|
||||
|
||||
beforeEach(() => { resolver = new PipeResolver(new JitReflector()); });
|
||||
beforeEach(() => {
|
||||
resolver = new PipeResolver(new JitReflector());
|
||||
});
|
||||
|
||||
it('should read out the metadata from the class', () => {
|
||||
const moduleMetadata = resolver.resolve(SomePipe);
|
||||
@ -48,6 +50,5 @@ class SimpleClass {}
|
||||
|
||||
expect(resolver.resolve(ChildWithDecorator)).toEqual(new Pipe({name: 'c'}));
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
@ -74,18 +74,24 @@ class R3AstSourceSpans implements t.Visitor<void> {
|
||||
['BoundEvent', humanizeSpan(event.sourceSpan), humanizeSpan(event.handlerSpan)]);
|
||||
}
|
||||
|
||||
visitText(text: t.Text) { this.result.push(['Text', humanizeSpan(text.sourceSpan)]); }
|
||||
visitText(text: t.Text) {
|
||||
this.result.push(['Text', humanizeSpan(text.sourceSpan)]);
|
||||
}
|
||||
|
||||
visitBoundText(text: t.BoundText) {
|
||||
this.result.push(['BoundText', humanizeSpan(text.sourceSpan)]);
|
||||
}
|
||||
|
||||
visitIcu(icu: t.Icu) { return null; }
|
||||
visitIcu(icu: t.Icu) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private visitAll(nodes: t.Node[][]) { nodes.forEach(node => t.visitAll(this, node)); }
|
||||
private visitAll(nodes: t.Node[][]) {
|
||||
nodes.forEach(node => t.visitAll(this, node));
|
||||
}
|
||||
}
|
||||
|
||||
function humanizeSpan(span: ParseSourceSpan | null | undefined): string {
|
||||
function humanizeSpan(span: ParseSourceSpan|null|undefined): string {
|
||||
if (span === null || span === undefined) {
|
||||
return `<empty>`;
|
||||
}
|
||||
|
@ -75,13 +75,21 @@ class R3AstHumanizer implements t.Visitor<void> {
|
||||
]);
|
||||
}
|
||||
|
||||
visitText(text: t.Text) { this.result.push(['Text', text.value]); }
|
||||
visitText(text: t.Text) {
|
||||
this.result.push(['Text', text.value]);
|
||||
}
|
||||
|
||||
visitBoundText(text: t.BoundText) { this.result.push(['BoundText', unparse(text.value)]); }
|
||||
visitBoundText(text: t.BoundText) {
|
||||
this.result.push(['BoundText', unparse(text.value)]);
|
||||
}
|
||||
|
||||
visitIcu(icu: t.Icu) { return null; }
|
||||
visitIcu(icu: t.Icu) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private visitAll(nodes: t.Node[][]) { nodes.forEach(node => t.visitAll(this, node)); }
|
||||
private visitAll(nodes: t.Node[][]) {
|
||||
nodes.forEach(node => t.visitAll(this, node));
|
||||
}
|
||||
}
|
||||
|
||||
function expectFromHtml(html: string) {
|
||||
@ -97,13 +105,14 @@ function expectFromR3Nodes(nodes: t.Node[]) {
|
||||
|
||||
function expectSpanFromHtml(html: string) {
|
||||
const {nodes} = parse(html);
|
||||
return expect(nodes[0] !.sourceSpan.toString());
|
||||
return expect(nodes[0]!.sourceSpan.toString());
|
||||
}
|
||||
|
||||
describe('R3 template transform', () => {
|
||||
describe('ParseSpan on nodes toString', () => {
|
||||
it('should create valid text span on Element with adjacent start and end tags',
|
||||
() => { expectSpanFromHtml('<div></div>').toBe('<div></div>'); });
|
||||
it('should create valid text span on Element with adjacent start and end tags', () => {
|
||||
expectSpanFromHtml('<div></div>').toBe('<div></div>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Nodes without binding', () => {
|
||||
|
@ -90,7 +90,8 @@ describe('style parsing', () => {
|
||||
expect(hyphenate('-fooBar-man')).toEqual('-foo-bar-man');
|
||||
});
|
||||
|
||||
it('should make everything lowercase',
|
||||
() => { expect(hyphenate('-WebkitAnimation')).toEqual('-webkit-animation'); });
|
||||
it('should make everything lowercase', () => {
|
||||
expect(hyphenate('-WebkitAnimation')).toEqual('-webkit-animation');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -15,7 +15,9 @@ type HumanizedExpressionSource = [string, AbsoluteSourceSpan];
|
||||
class ExpressionSourceHumanizer extends e.RecursiveAstVisitor implements t.Visitor {
|
||||
result: HumanizedExpressionSource[] = [];
|
||||
|
||||
private recordAst(ast: e.AST) { this.result.push([unparse(ast), ast.sourceSpan]); }
|
||||
private recordAst(ast: e.AST) {
|
||||
this.result.push([unparse(ast), ast.sourceSpan]);
|
||||
}
|
||||
|
||||
// This method is defined to reconcile the type of ExpressionSourceHumanizer
|
||||
// since both RecursiveAstVisitor and Visitor define the visit() method in
|
||||
@ -124,11 +126,19 @@ class ExpressionSourceHumanizer extends e.RecursiveAstVisitor implements t.Visit
|
||||
}
|
||||
visitReference(ast: t.Reference) {}
|
||||
visitVariable(ast: t.Variable) {}
|
||||
visitEvent(ast: t.BoundEvent) { ast.handler.visit(this); }
|
||||
visitEvent(ast: t.BoundEvent) {
|
||||
ast.handler.visit(this);
|
||||
}
|
||||
visitTextAttribute(ast: t.TextAttribute) {}
|
||||
visitBoundAttribute(ast: t.BoundAttribute) { ast.value.visit(this); }
|
||||
visitBoundEvent(ast: t.BoundEvent) { ast.handler.visit(this); }
|
||||
visitBoundText(ast: t.BoundText) { ast.value.visit(this); }
|
||||
visitBoundAttribute(ast: t.BoundAttribute) {
|
||||
ast.value.visit(this);
|
||||
}
|
||||
visitBoundEvent(ast: t.BoundEvent) {
|
||||
ast.handler.visit(this);
|
||||
}
|
||||
visitBoundText(ast: t.BoundText) {
|
||||
ast.value.visit(this);
|
||||
}
|
||||
visitContent(ast: t.Content) {}
|
||||
visitText(ast: t.Text) {}
|
||||
visitIcu(ast: t.Icu) {}
|
||||
|
@ -54,8 +54,9 @@ describe('t2 binding', () => {
|
||||
const binder = new R3TargetBinder(new SelectorMatcher<DirectiveMeta>());
|
||||
const res = binder.bind({template: template.nodes});
|
||||
|
||||
const itemBinding = (findExpression(template.nodes, '{{item.name}}') !as e.Interpolation)
|
||||
.expressions[0] as e.PropertyRead;
|
||||
const itemBinding =
|
||||
(findExpression(template.nodes, '{{item.name}}')! as e.Interpolation).expressions[0] as
|
||||
e.PropertyRead;
|
||||
const item = itemBinding.receiver;
|
||||
const itemTarget = res.getExpressionTarget(item);
|
||||
if (!(itemTarget instanceof a.Variable)) {
|
||||
@ -64,7 +65,7 @@ describe('t2 binding', () => {
|
||||
expect(itemTarget.value).toBe('$implicit');
|
||||
const itemTemplate = res.getTemplateOfSymbol(itemTarget);
|
||||
expect(itemTemplate).not.toBeNull();
|
||||
expect(res.getNestingLevel(itemTemplate !)).toBe(1);
|
||||
expect(res.getNestingLevel(itemTemplate!)).toBe(1);
|
||||
});
|
||||
|
||||
it('should match directives when binding a simple template', () => {
|
||||
@ -72,7 +73,7 @@ describe('t2 binding', () => {
|
||||
const binder = new R3TargetBinder(makeSelectorMatcher());
|
||||
const res = binder.bind({template: template.nodes});
|
||||
const tmpl = template.nodes[0] as a.Template;
|
||||
const directives = res.getDirectivesOfNode(tmpl) !;
|
||||
const directives = res.getDirectivesOfNode(tmpl)!;
|
||||
expect(directives).not.toBeNull();
|
||||
expect(directives.length).toBe(1);
|
||||
expect(directives[0].name).toBe('NgFor');
|
||||
@ -92,7 +93,7 @@ describe('t2 binding', () => {
|
||||
const res = binder.bind({template: template.nodes});
|
||||
const svgNode = template.nodes[0] as a.Element;
|
||||
const textNode = svgNode.children[0] as a.Element;
|
||||
const directives = res.getDirectivesOfNode(textNode) !;
|
||||
const directives = res.getDirectivesOfNode(textNode)!;
|
||||
expect(directives).not.toBeNull();
|
||||
expect(directives.length).toBe(1);
|
||||
expect(directives[0].name).toBe('Dir');
|
||||
@ -103,11 +104,11 @@ describe('t2 binding', () => {
|
||||
const binder = new R3TargetBinder(makeSelectorMatcher());
|
||||
const res = binder.bind({template: template.nodes});
|
||||
const tmpl = template.nodes[0] as a.Template;
|
||||
const tmplDirectives = res.getDirectivesOfNode(tmpl) !;
|
||||
const tmplDirectives = res.getDirectivesOfNode(tmpl)!;
|
||||
expect(tmplDirectives).not.toBeNull();
|
||||
expect(tmplDirectives.length).toBe(1);
|
||||
expect(tmplDirectives[0].name).toBe('NgFor');
|
||||
const elDirectives = res.getDirectivesOfNode(tmpl.children[0] as a.Element) !;
|
||||
const elDirectives = res.getDirectivesOfNode(tmpl.children[0] as a.Element)!;
|
||||
expect(elDirectives).not.toBeNull();
|
||||
expect(elDirectives.length).toBe(1);
|
||||
expect(elDirectives[0].name).toBe('Dir');
|
||||
|
@ -21,7 +21,7 @@ import {formatI18nPlaceholderName} from '../../../src/render3/view/i18n/util';
|
||||
import {parseR3 as parse} from './util';
|
||||
|
||||
const expressionParser = new Parser(new Lexer());
|
||||
const i18nOf = (element: t.Node & {i18n?: i18n.I18nMeta}) => element.i18n !;
|
||||
const i18nOf = (element: t.Node&{i18n?: i18n.I18nMeta}) => element.i18n!;
|
||||
|
||||
describe('I18nContext', () => {
|
||||
it('should support i18n content collection', () => {
|
||||
@ -74,7 +74,7 @@ describe('I18nContext', () => {
|
||||
const [boundTextB, elementC, boundTextC] = (elementB as t.Element).children;
|
||||
|
||||
// simulate I18nContext for a given template
|
||||
const ctx = new I18nContext(1, o.variable('ctx'), 0, null, root.i18n !);
|
||||
const ctx = new I18nContext(1, o.variable('ctx'), 0, null, root.i18n!);
|
||||
|
||||
// set data for root ctx
|
||||
ctx.appendBoundText(i18nOf(boundTextA));
|
||||
@ -87,7 +87,7 @@ describe('I18nContext', () => {
|
||||
expect(ctx.isResolved).toBe(false);
|
||||
|
||||
// create child context
|
||||
const childCtx = ctx.forkChildContext(2, 1, (templateA as t.Template).i18n !);
|
||||
const childCtx = ctx.forkChildContext(2, 1, (templateA as t.Template).i18n!);
|
||||
expect(childCtx.bindings.size).toBe(0);
|
||||
expect(childCtx.isRoot).toBe(false);
|
||||
|
||||
@ -116,11 +116,12 @@ describe('I18nContext', () => {
|
||||
const expected = new Map([
|
||||
['INTERPOLATION', '<27>0<EFBFBD>'], ['START_TAG_DIV', '<27>#0<>|<7C>#1:1<>'],
|
||||
['START_BOLD_TEXT', '<27>*1:1<><31>#0:1<>'], ['CLOSE_BOLD_TEXT', '<27>/#0:1<><31>/*1:1<>'],
|
||||
['CLOSE_TAG_DIV', '<27>/#0<>|<7C>/#1:1<>'], ['INTERPOLATION_1', '<27>0:1<>'],
|
||||
['INTERPOLATION_2', '<27>1:1<>']
|
||||
['CLOSE_TAG_DIV', '<27>/#0<>|<7C>/#1:1<>'], ['INTERPOLATION_1', '<27>0:1<>'], ['INTERPOLATION_2', '<27>1:1<>']
|
||||
]);
|
||||
const phs = ctx.getSerializedPlaceholders();
|
||||
expected.forEach((value, key) => { expect(phs.get(key) !.join('|')).toEqual(value); });
|
||||
expected.forEach((value, key) => {
|
||||
expect(phs.get(key)!.join('|')).toEqual(value);
|
||||
});
|
||||
|
||||
// placeholders are added into the root ctx
|
||||
expect(phs.size).toBe(expected.size);
|
||||
@ -152,7 +153,7 @@ describe('I18nContext', () => {
|
||||
const [textC] = (templateB as t.Template).children;
|
||||
|
||||
// simulate I18nContext for a given template
|
||||
const ctxLevelA = new I18nContext(0, o.variable('ctx'), 0, null, root.i18n !);
|
||||
const ctxLevelA = new I18nContext(0, o.variable('ctx'), 0, null, root.i18n!);
|
||||
|
||||
// create Level A context
|
||||
ctxLevelA.appendTemplate(i18nOf(templateA), 1);
|
||||
@ -160,12 +161,12 @@ describe('I18nContext', () => {
|
||||
expect(ctxLevelA.isResolved).toBe(false);
|
||||
|
||||
// create Level B context
|
||||
const ctxLevelB = ctxLevelA.forkChildContext(0, 1, (templateA as t.Template).i18n !);
|
||||
const ctxLevelB = ctxLevelA.forkChildContext(0, 1, (templateA as t.Template).i18n!);
|
||||
ctxLevelB.appendTemplate(i18nOf(templateB), 1);
|
||||
expect(ctxLevelB.isRoot).toBe(false);
|
||||
|
||||
// create Level 2 context
|
||||
const ctxLevelC = ctxLevelB.forkChildContext(0, 1, (templateB as t.Template).i18n !);
|
||||
const ctxLevelC = ctxLevelB.forkChildContext(0, 1, (templateB as t.Template).i18n!);
|
||||
expect(ctxLevelC.isRoot).toBe(false);
|
||||
|
||||
// reconcile
|
||||
@ -176,7 +177,9 @@ describe('I18nContext', () => {
|
||||
const expected = new Map(
|
||||
[['START_TAG_NG-TEMPLATE', '<27>*1:1<>|<7C>*1:2<>'], ['CLOSE_TAG_NG-TEMPLATE', '<27>/*1:2<>|<7C>/*1:1<>']]);
|
||||
const phs = ctxLevelA.getSerializedPlaceholders();
|
||||
expected.forEach((value, key) => { expect(phs.get(key) !.join('|')).toEqual(value); });
|
||||
expected.forEach((value, key) => {
|
||||
expect(phs.get(key)!.join('|')).toEqual(value);
|
||||
});
|
||||
|
||||
// placeholders are added into the root ctx
|
||||
expect(phs.size).toBe(expected.size);
|
||||
@ -195,8 +198,9 @@ describe('Utils', () => {
|
||||
['START_TAG_NG-CONTAINER_1', 'startTagNgContainer_1'], ['CLOSE_TAG_ITALIC', 'closeTagItalic'],
|
||||
['CLOSE_TAG_BOLD_1', 'closeTagBold_1']
|
||||
];
|
||||
cases.forEach(
|
||||
([input, output]) => { expect(formatI18nPlaceholderName(input)).toEqual(output); });
|
||||
cases.forEach(([input, output]) => {
|
||||
expect(formatI18nPlaceholderName(input)).toEqual(output);
|
||||
});
|
||||
});
|
||||
|
||||
describe('metadata serialization', () => {
|
||||
@ -292,8 +296,9 @@ describe('serializeI18nMessageForGetMsg', () => {
|
||||
return serializeI18nMessageForGetMsg(root.i18n as i18n.Message);
|
||||
};
|
||||
|
||||
it('should serialize plain text for `GetMsg()`',
|
||||
() => { expect(serialize('Some text')).toEqual('Some text'); });
|
||||
it('should serialize plain text for `GetMsg()`', () => {
|
||||
expect(serialize('Some text')).toEqual('Some text');
|
||||
});
|
||||
|
||||
it('should serialize text with interpolation for `GetMsg()`', () => {
|
||||
expect(serialize('Some text {{ valueA }} and {{ valueB + valueC }}'))
|
||||
|
@ -14,7 +14,7 @@ import {HtmlParser, ParseTreeResult} from '../../../src/ml_parser/html_parser';
|
||||
import {WhitespaceVisitor} from '../../../src/ml_parser/html_whitespaces';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../../src/ml_parser/interpolation_config';
|
||||
import * as a from '../../../src/render3/r3_ast';
|
||||
import {Render3ParseResult, htmlAstToRender3Ast} from '../../../src/render3/r3_template_transform';
|
||||
import {htmlAstToRender3Ast, Render3ParseResult} from '../../../src/render3/r3_template_transform';
|
||||
import {I18nMetaVisitor} from '../../../src/render3/view/i18n/meta';
|
||||
import {BindingParser} from '../../../src/template_parser/binding_parser';
|
||||
import {MockSchemaRegistry} from '../../../testing';
|
||||
|
@ -13,10 +13,12 @@ import {AsyncTestCompleter, beforeEach, describe, expect, inject, it} from '@ang
|
||||
describe('MockResourceLoader', () => {
|
||||
let resourceLoader: MockResourceLoader;
|
||||
|
||||
beforeEach(() => { resourceLoader = new MockResourceLoader(); });
|
||||
beforeEach(() => {
|
||||
resourceLoader = new MockResourceLoader();
|
||||
});
|
||||
|
||||
function expectResponse(
|
||||
request: Promise<string>, url: string, response: string, done: () => void = null !) {
|
||||
request: Promise<string>, url: string, response: string, done: () => void = null!) {
|
||||
function onResponse(text: string): string {
|
||||
if (response === null) {
|
||||
throw `Unexpected response ${url} -> ${text}`;
|
||||
@ -52,7 +54,7 @@ import {AsyncTestCompleter, beforeEach, describe, expect, inject, it} from '@ang
|
||||
it('should return an error from the definitions',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
const url = '/foo';
|
||||
const response: string = null !;
|
||||
const response: string = null!;
|
||||
resourceLoader.when(url, response);
|
||||
expectResponse(resourceLoader.get(url), url, response, () => async.done());
|
||||
resourceLoader.flush();
|
||||
@ -70,7 +72,7 @@ import {AsyncTestCompleter, beforeEach, describe, expect, inject, it} from '@ang
|
||||
it('should return an error from the expectations',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
const url = '/foo';
|
||||
const response: string = null !;
|
||||
const response: string = null!;
|
||||
resourceLoader.expect(url, response);
|
||||
expectResponse(resourceLoader.get(url), url, response, () => async.done());
|
||||
resourceLoader.flush();
|
||||
@ -82,7 +84,9 @@ import {AsyncTestCompleter, beforeEach, describe, expect, inject, it} from '@ang
|
||||
resourceLoader.expect(url, response);
|
||||
resourceLoader.get(url);
|
||||
resourceLoader.get(url);
|
||||
expect(() => { resourceLoader.flush(); }).toThrowError('Unexpected request /foo');
|
||||
expect(() => {
|
||||
resourceLoader.flush();
|
||||
}).toThrowError('Unexpected request /foo');
|
||||
});
|
||||
|
||||
it('should return expectations before definitions',
|
||||
@ -97,18 +101,24 @@ import {AsyncTestCompleter, beforeEach, describe, expect, inject, it} from '@ang
|
||||
|
||||
it('should throw when there is no definitions or expectations', () => {
|
||||
resourceLoader.get('/foo');
|
||||
expect(() => { resourceLoader.flush(); }).toThrowError('Unexpected request /foo');
|
||||
expect(() => {
|
||||
resourceLoader.flush();
|
||||
}).toThrowError('Unexpected request /foo');
|
||||
});
|
||||
|
||||
it('should throw when flush is called without any pending requests', () => {
|
||||
expect(() => { resourceLoader.flush(); }).toThrowError('No pending requests to flush');
|
||||
expect(() => {
|
||||
resourceLoader.flush();
|
||||
}).toThrowError('No pending requests to flush');
|
||||
});
|
||||
|
||||
it('should throw on unsatisfied expectations', () => {
|
||||
resourceLoader.expect('/foo', 'bar');
|
||||
resourceLoader.when('/bar', 'foo');
|
||||
resourceLoader.get('/bar');
|
||||
expect(() => { resourceLoader.flush(); }).toThrowError('Unsatisfied requests: /foo');
|
||||
expect(() => {
|
||||
resourceLoader.flush();
|
||||
}).toThrowError('Unsatisfied requests: /foo');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -8,9 +8,11 @@
|
||||
|
||||
import {DirectiveResolver, ResourceLoader} from '@angular/compiler';
|
||||
import {Compiler, Component, Injector, NgModule, NgModuleFactory, ɵstringify as stringify} from '@angular/core';
|
||||
import {TestBed, async, fakeAsync, inject, tick} from '@angular/core/testing';
|
||||
import {async, fakeAsync, inject, TestBed, tick} from '@angular/core/testing';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
|
||||
import {MockDirectiveResolver} from '../testing';
|
||||
|
||||
import {SpyResourceLoader} from './spies';
|
||||
|
||||
@Component({selector: 'child-cmp'})
|
||||
@ -27,11 +29,12 @@ class SomeCompWithUrlTemplate {
|
||||
|
||||
{
|
||||
describe('RuntimeCompiler', () => {
|
||||
|
||||
describe('compilerComponentSync', () => {
|
||||
describe('never resolving loader', () => {
|
||||
class StubResourceLoader {
|
||||
get(url: string) { return new Promise(() => {}); }
|
||||
get(url: string) {
|
||||
return new Promise(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
@ -43,8 +46,8 @@ class SomeCompWithUrlTemplate {
|
||||
TestBed.configureTestingModule({declarations: [SomeCompWithUrlTemplate]});
|
||||
TestBed.compileComponents().then(() => {
|
||||
expect(() => TestBed.createComponent(SomeCompWithUrlTemplate))
|
||||
.toThrowError(
|
||||
`Can't compile synchronously as ${stringify(SomeCompWithUrlTemplate)} is still being loaded!`);
|
||||
.toThrowError(`Can't compile synchronously as ${
|
||||
stringify(SomeCompWithUrlTemplate)} is still being loaded!`);
|
||||
});
|
||||
}));
|
||||
|
||||
@ -55,15 +58,17 @@ class SomeCompWithUrlTemplate {
|
||||
TestBed.overrideComponent(SomeComp, {set: {template: '<child-cmp></child-cmp>'}});
|
||||
TestBed.compileComponents().then(() => {
|
||||
expect(() => TestBed.createComponent(SomeComp))
|
||||
.toThrowError(
|
||||
`Can't compile synchronously as ${stringify(ChildComp)} is still being loaded!`);
|
||||
.toThrowError(`Can't compile synchronously as ${
|
||||
stringify(ChildComp)} is still being loaded!`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolving loader', () => {
|
||||
class StubResourceLoader {
|
||||
get(url: string) { return Promise.resolve('hello'); }
|
||||
get(url: string) {
|
||||
return Promise.resolve('hello');
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
@ -88,7 +93,9 @@ class SomeCompWithUrlTemplate {
|
||||
let dirResolver: MockDirectiveResolver;
|
||||
let injector: Injector;
|
||||
|
||||
beforeEach(() => { TestBed.configureCompiler({providers: [SpyResourceLoader.PROVIDE]}); });
|
||||
beforeEach(() => {
|
||||
TestBed.configureCompiler({providers: [SpyResourceLoader.PROVIDE]});
|
||||
});
|
||||
|
||||
beforeEach(fakeAsync(inject(
|
||||
[Compiler, ResourceLoader, DirectiveResolver, Injector],
|
||||
@ -110,7 +117,7 @@ class SomeCompWithUrlTemplate {
|
||||
}
|
||||
|
||||
resourceLoader.spy('get').and.callFake(() => Promise.resolve('hello'));
|
||||
let ngModuleFactory: NgModuleFactory<any> = undefined !;
|
||||
let ngModuleFactory: NgModuleFactory<any> = undefined!;
|
||||
compiler.compileModuleAsync(SomeModule).then((f) => ngModuleFactory = f);
|
||||
tick();
|
||||
expect(ngModuleFactory.moduleType).toBe(SomeModule);
|
||||
@ -126,8 +133,8 @@ class SomeCompWithUrlTemplate {
|
||||
|
||||
resourceLoader.spy('get').and.callFake(() => Promise.resolve(''));
|
||||
expect(() => compiler.compileModuleSync(SomeModule))
|
||||
.toThrowError(
|
||||
`Can't compile synchronously as ${stringify(SomeCompWithUrlTemplate)} is still being loaded!`);
|
||||
.toThrowError(`Can't compile synchronously as ${
|
||||
stringify(SomeCompWithUrlTemplate)} is still being loaded!`);
|
||||
});
|
||||
|
||||
it('should throw when using a templateUrl in a nested component that has not been compiled before',
|
||||
|
@ -19,7 +19,9 @@ import {extractSchema} from './schema_extractor';
|
||||
{
|
||||
describe('DOMElementSchema', () => {
|
||||
let registry: DomElementSchemaRegistry;
|
||||
beforeEach(() => { registry = new DomElementSchemaRegistry(); });
|
||||
beforeEach(() => {
|
||||
registry = new DomElementSchemaRegistry();
|
||||
});
|
||||
|
||||
it('should detect elements', () => {
|
||||
expect(registry.hasElement('div', [])).toBeTruthy();
|
||||
@ -97,8 +99,9 @@ import {extractSchema} from './schema_extractor';
|
||||
expect(registry.hasElement('unknown', [NO_ERRORS_SCHEMA])).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should re-map property names that are specified in DOM facade',
|
||||
() => { expect(registry.getMappedPropName('readonly')).toEqual('readOnly'); });
|
||||
it('should re-map property names that are specified in DOM facade', () => {
|
||||
expect(registry.getMappedPropName('readonly')).toEqual('readOnly');
|
||||
});
|
||||
|
||||
it('should not re-map property names that are not specified in DOM facade', () => {
|
||||
expect(registry.getMappedPropName('title')).toEqual('title');
|
||||
@ -173,8 +176,9 @@ If 'onAnything' is a directive input, make sure the directive is imported by the
|
||||
});
|
||||
|
||||
describe('Angular custom elements', () => {
|
||||
it('should support <ng-container>',
|
||||
() => { expect(registry.hasProperty('ng-container', 'id', [])).toBeFalsy(); });
|
||||
it('should support <ng-container>', () => {
|
||||
expect(registry.hasProperty('ng-container', 'id', [])).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should support <ng-content>', () => {
|
||||
expect(registry.hasProperty('ng-content', 'id', [])).toBeFalsy();
|
||||
@ -185,8 +189,9 @@ If 'onAnything' is a directive input, make sure the directive is imported by the
|
||||
if (browserDetection.isChromeDesktop) {
|
||||
it('generate a new schema', () => {
|
||||
let schema = '\n';
|
||||
extractSchema() !.forEach(
|
||||
(props, name) => { schema += `'${name}|${props.join(',')}',\n`; });
|
||||
extractSchema()!.forEach((props, name) => {
|
||||
schema += `'${name}|${props.join(',')}',\n`;
|
||||
});
|
||||
// Uncomment this line to see:
|
||||
// the generated schema which can then be pasted to the DomElementSchemaRegistry
|
||||
// console.log(schema);
|
||||
|
@ -97,7 +97,9 @@ export function extractSchema(): Map<string, string[]>|null {
|
||||
|
||||
types.sort();
|
||||
|
||||
types.forEach(type => { extractRecursiveProperties(visited, descMap, (window as any)[type]); });
|
||||
types.forEach(type => {
|
||||
extractRecursiveProperties(visited, descMap, (window as any)[type]);
|
||||
});
|
||||
|
||||
// Add elements missed by Chrome auto-detection
|
||||
Object.keys(MISSING_FROM_CHROME).forEach(elHierarchy => {
|
||||
@ -125,7 +127,7 @@ function assertNoMissingTags(descMap: Map<string, string[]>): void {
|
||||
|
||||
function extractRecursiveProperties(
|
||||
visited: {[name: string]: boolean}, descMap: Map<string, string[]>, type: Function): string {
|
||||
const name = extractName(type) !;
|
||||
const name = extractName(type)!;
|
||||
|
||||
if (visited[name]) {
|
||||
return name;
|
||||
@ -181,7 +183,7 @@ function extractProperties(
|
||||
|
||||
const fullName = name + (superName ? '^' + superName : '');
|
||||
|
||||
const props: string[] = descMap.has(fullName) ? descMap.get(fullName) ! : [];
|
||||
const props: string[] = descMap.has(fullName) ? descMap.get(fullName)! : [];
|
||||
|
||||
const prototype = type.prototype;
|
||||
const keys = Object.getOwnPropertyNames(prototype);
|
||||
|
@ -17,13 +17,16 @@ import {el} from '@angular/platform-browser/testing/src/browser_util';
|
||||
let s1: any[], s2: any[], s3: any[], s4: any[];
|
||||
let matched: any[];
|
||||
|
||||
function reset() { matched = []; }
|
||||
function reset() {
|
||||
matched = [];
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
reset();
|
||||
s1 = s2 = s3 = s4 = null !;
|
||||
selectableCollector =
|
||||
(selector: CssSelector, context: any) => { matched.push(selector, context); };
|
||||
s1 = s2 = s3 = s4 = null!;
|
||||
selectableCollector = (selector: CssSelector, context: any) => {
|
||||
matched.push(selector, context);
|
||||
};
|
||||
matcher = new SelectorMatcher();
|
||||
});
|
||||
|
||||
@ -128,7 +131,7 @@ import {el} from '@angular/platform-browser/testing/src/browser_util';
|
||||
|
||||
const elementSelector = new CssSelector();
|
||||
const element = el('<div attr></div>');
|
||||
const empty = element.getAttribute('attr') !;
|
||||
const empty = element.getAttribute('attr')!;
|
||||
elementSelector.addAttribute('some-decor', empty);
|
||||
matcher.match(elementSelector, selectableCollector);
|
||||
expect(matched).toEqual([s1[0], 1]);
|
||||
@ -458,9 +461,13 @@ function getSelectorFor(
|
||||
const selector = new CssSelector();
|
||||
selector.setElement(tag);
|
||||
|
||||
attrs.forEach(nameValue => { selector.addAttribute(nameValue[0], nameValue[1]); });
|
||||
attrs.forEach(nameValue => {
|
||||
selector.addAttribute(nameValue[0], nameValue[1]);
|
||||
});
|
||||
|
||||
classes.trim().split(/\s+/g).forEach(cName => { selector.addClassName(cName); });
|
||||
classes.trim().split(/\s+/g).forEach(cName => {
|
||||
selector.addClassName(cName);
|
||||
});
|
||||
|
||||
return selector;
|
||||
}
|
||||
|
@ -6,12 +6,11 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CssRule, ShadowCss, processRules} from '@angular/compiler/src/shadow_css';
|
||||
import {CssRule, processRules, ShadowCss} from '@angular/compiler/src/shadow_css';
|
||||
import {normalizeCSS} from '@angular/platform-browser/testing/src/browser_util';
|
||||
|
||||
{
|
||||
describe('ShadowCss', function() {
|
||||
|
||||
function s(css: string, contentAttr: string, hostAttr: string = '') {
|
||||
const shadowCss = new ShadowCss();
|
||||
const shim = shadowCss.shimCssText(css, contentAttr, hostAttr);
|
||||
@ -19,7 +18,9 @@ import {normalizeCSS} from '@angular/platform-browser/testing/src/browser_util';
|
||||
return normalizeCSS(shim.replace(nlRegexp, ''));
|
||||
}
|
||||
|
||||
it('should handle empty string', () => { expect(s('', 'contenta')).toEqual(''); });
|
||||
it('should handle empty string', () => {
|
||||
expect(s('', 'contenta')).toEqual('');
|
||||
});
|
||||
|
||||
it('should add an attribute to every rule', () => {
|
||||
const css = 'one {color: red;}two {color: red;}';
|
||||
@ -112,14 +113,17 @@ import {normalizeCSS} from '@angular/platform-browser/testing/src/browser_util';
|
||||
});
|
||||
|
||||
describe((':host'), () => {
|
||||
it('should handle no context',
|
||||
() => { expect(s(':host {}', 'contenta', 'a-host')).toEqual('[a-host] {}'); });
|
||||
it('should handle no context', () => {
|
||||
expect(s(':host {}', 'contenta', 'a-host')).toEqual('[a-host] {}');
|
||||
});
|
||||
|
||||
it('should handle tag selector',
|
||||
() => { expect(s(':host(ul) {}', 'contenta', 'a-host')).toEqual('ul[a-host] {}'); });
|
||||
it('should handle tag selector', () => {
|
||||
expect(s(':host(ul) {}', 'contenta', 'a-host')).toEqual('ul[a-host] {}');
|
||||
});
|
||||
|
||||
it('should handle class selector',
|
||||
() => { expect(s(':host(.x) {}', 'contenta', 'a-host')).toEqual('.x[a-host] {}'); });
|
||||
it('should handle class selector', () => {
|
||||
expect(s(':host(.x) {}', 'contenta', 'a-host')).toEqual('.x[a-host] {}');
|
||||
});
|
||||
|
||||
it('should handle attribute selector', () => {
|
||||
expect(s(':host([a="b"]) {}', 'contenta', 'a-host')).toEqual('[a="b"][a-host] {}');
|
||||
@ -285,14 +289,17 @@ import {normalizeCSS} from '@angular/platform-browser/testing/src/browser_util';
|
||||
expect(css).toEqual('div[contenta] {height:calc(100% - 55px);}');
|
||||
});
|
||||
|
||||
it('should strip comments',
|
||||
() => { expect(s('/* x */b {c}', 'contenta')).toEqual('b[contenta] {c}'); });
|
||||
it('should strip comments', () => {
|
||||
expect(s('/* x */b {c}', 'contenta')).toEqual('b[contenta] {c}');
|
||||
});
|
||||
|
||||
it('should ignore special characters in comments',
|
||||
() => { expect(s('/* {;, */b {c}', 'contenta')).toEqual('b[contenta] {c}'); });
|
||||
it('should ignore special characters in comments', () => {
|
||||
expect(s('/* {;, */b {c}', 'contenta')).toEqual('b[contenta] {c}');
|
||||
});
|
||||
|
||||
it('should support multiline comments',
|
||||
() => { expect(s('/* \n */b {c}', 'contenta')).toEqual('b[contenta] {c}'); });
|
||||
it('should support multiline comments', () => {
|
||||
expect(s('/* \n */b {c}', 'contenta')).toEqual('b[contenta] {c}');
|
||||
});
|
||||
|
||||
it('should keep sourceMappingURL comments', () => {
|
||||
expect(s('b {c}/*# sourceMappingURL=data:x */', 'contenta'))
|
||||
@ -318,13 +325,17 @@ import {normalizeCSS} from '@angular/platform-browser/testing/src/browser_util';
|
||||
return result;
|
||||
}
|
||||
|
||||
it('should work with empty css', () => { expect(captureRules('')).toEqual([]); });
|
||||
it('should work with empty css', () => {
|
||||
expect(captureRules('')).toEqual([]);
|
||||
});
|
||||
|
||||
it('should capture a rule without body',
|
||||
() => { expect(captureRules('a;')).toEqual([new CssRule('a', '')]); });
|
||||
it('should capture a rule without body', () => {
|
||||
expect(captureRules('a;')).toEqual([new CssRule('a', '')]);
|
||||
});
|
||||
|
||||
it('should capture css rules with body',
|
||||
() => { expect(captureRules('a {b}')).toEqual([new CssRule('a', 'b')]); });
|
||||
it('should capture css rules with body', () => {
|
||||
expect(captureRules('a {b}')).toEqual([new CssRule('a', 'b')]);
|
||||
});
|
||||
|
||||
it('should capture css rules with nested rules', () => {
|
||||
expect(captureRules('a {b {c}} d {e}')).toEqual([
|
||||
|
@ -12,5 +12,7 @@ import {SpyObject} from '@angular/core/testing/src/testing_internal';
|
||||
|
||||
export class SpyResourceLoader extends SpyObject {
|
||||
public static PROVIDE = {provide: ResourceLoader, useClass: SpyResourceLoader, deps: []};
|
||||
constructor() { super(ResourceLoader); }
|
||||
constructor() {
|
||||
super(ResourceLoader);
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,9 @@ import {UrlResolver} from '@angular/compiler/src/url_resolver';
|
||||
describe('extractStyleUrls', () => {
|
||||
let urlResolver: UrlResolver;
|
||||
|
||||
beforeEach(() => { urlResolver = new UrlResolver(); });
|
||||
beforeEach(() => {
|
||||
urlResolver = new UrlResolver();
|
||||
});
|
||||
|
||||
it('should not resolve "url()" urls', () => {
|
||||
const css = `
|
||||
@ -104,23 +106,25 @@ import {UrlResolver} from '@angular/compiler/src/url_resolver';
|
||||
expect(styleWithImports.style.trim()).toEqual(``);
|
||||
expect(styleWithImports.styleUrls).toEqual(['fake_resolved_url']);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('isStyleUrlResolvable', () => {
|
||||
it('should resolve relative urls',
|
||||
() => { expect(isStyleUrlResolvable('someUrl.css')).toBe(true); });
|
||||
it('should resolve relative urls', () => {
|
||||
expect(isStyleUrlResolvable('someUrl.css')).toBe(true);
|
||||
});
|
||||
|
||||
it('should resolve package: urls',
|
||||
() => { expect(isStyleUrlResolvable('package:someUrl.css')).toBe(true); });
|
||||
it('should resolve package: urls', () => {
|
||||
expect(isStyleUrlResolvable('package:someUrl.css')).toBe(true);
|
||||
});
|
||||
|
||||
it('should not resolve empty urls', () => {
|
||||
expect(isStyleUrlResolvable(null !)).toBe(false);
|
||||
expect(isStyleUrlResolvable(null!)).toBe(false);
|
||||
expect(isStyleUrlResolvable('')).toBe(false);
|
||||
});
|
||||
|
||||
it('should not resolve urls with other schema',
|
||||
() => { expect(isStyleUrlResolvable('http://otherurl')).toBe(false); });
|
||||
it('should not resolve urls with other schema', () => {
|
||||
expect(isStyleUrlResolvable('http://otherurl')).toBe(false);
|
||||
});
|
||||
|
||||
it('should not resolve urls with absolute paths', () => {
|
||||
expect(isStyleUrlResolvable('/otherurl')).toBe(false);
|
||||
@ -130,7 +134,11 @@ import {UrlResolver} from '@angular/compiler/src/url_resolver';
|
||||
}
|
||||
|
||||
class FakeUrlResolver extends UrlResolver {
|
||||
constructor() { super(); }
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
resolve(baseUrl: string, url: string): string { return 'fake_resolved_url'; }
|
||||
resolve(baseUrl: string, url: string): string {
|
||||
return 'fake_resolved_url';
|
||||
}
|
||||
}
|
||||
|
@ -16,16 +16,18 @@ import {calcPossibleSecurityContexts} from '../../src/template_parser/binding_pa
|
||||
describe('BindingParser', () => {
|
||||
let registry: ElementSchemaRegistry;
|
||||
|
||||
beforeEach(inject(
|
||||
[ElementSchemaRegistry], (_registry: ElementSchemaRegistry) => { registry = _registry; }));
|
||||
beforeEach(inject([ElementSchemaRegistry], (_registry: ElementSchemaRegistry) => {
|
||||
registry = _registry;
|
||||
}));
|
||||
|
||||
describe('possibleSecurityContexts', () => {
|
||||
function hrefSecurityContexts(selector: string) {
|
||||
return calcPossibleSecurityContexts(registry, selector, 'href', false);
|
||||
}
|
||||
|
||||
it('should return a single security context if the selector as an element name',
|
||||
() => { expect(hrefSecurityContexts('a')).toEqual([SecurityContext.URL]); });
|
||||
it('should return a single security context if the selector as an element name', () => {
|
||||
expect(hrefSecurityContexts('a')).toEqual([SecurityContext.URL]);
|
||||
});
|
||||
|
||||
it('should return the possible security contexts if the selector has no element name', () => {
|
||||
expect(hrefSecurityContexts('[myDir]')).toEqual([
|
||||
@ -45,8 +47,9 @@ import {calcPossibleSecurityContexts} from '../../src/template_parser/binding_pa
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return SecurityContext.NONE if there are no possible elements',
|
||||
() => { expect(hrefSecurityContexts('img:not(img)')).toEqual([SecurityContext.NONE]); });
|
||||
it('should return SecurityContext.NONE if there are no possible elements', () => {
|
||||
expect(hrefSecurityContexts('img:not(img)')).toEqual([SecurityContext.NONE]);
|
||||
});
|
||||
|
||||
it('should return the union of the possible security contexts if multiple selectors are specified',
|
||||
() => {
|
||||
|
@ -36,7 +36,7 @@ describe('expression AST absolute source spans', () => {
|
||||
beforeEach(inject([TemplateParser], (parser: TemplateParser) => {
|
||||
parse =
|
||||
(template: string, directives: CompileDirectiveSummary[] = [],
|
||||
pipes: CompilePipeSummary[] | null = null, schemas: SchemaMetadata[] = [],
|
||||
pipes: CompilePipeSummary[]|null = null, schemas: SchemaMetadata[] = [],
|
||||
preserveWhitespaces = true): TemplateAst[] => {
|
||||
if (pipes === null) {
|
||||
pipes = [];
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -14,7 +14,9 @@ import {PreparsedElement, PreparsedElementType, preparseElement} from '../../src
|
||||
{
|
||||
describe('preparseElement', () => {
|
||||
let htmlParser: HtmlParser;
|
||||
beforeEach(inject([HtmlParser], (_htmlParser: HtmlParser) => { htmlParser = _htmlParser; }));
|
||||
beforeEach(inject([HtmlParser], (_htmlParser: HtmlParser) => {
|
||||
htmlParser = _htmlParser;
|
||||
}));
|
||||
|
||||
function preparse(html: string): PreparsedElement {
|
||||
return preparseElement(htmlParser.parse(html, 'TestComp').rootNodes[0] as Element);
|
||||
|
@ -15,12 +15,16 @@ type HumanizedExpressionSource = [string, AbsoluteSourceSpan];
|
||||
class ExpressionSourceHumanizer extends e.RecursiveAstVisitor implements t.TemplateAstVisitor {
|
||||
result: HumanizedExpressionSource[] = [];
|
||||
|
||||
private recordAst(ast: e.AST) { this.result.push([unparse(ast), ast.sourceSpan]); }
|
||||
private recordAst(ast: e.AST) {
|
||||
this.result.push([unparse(ast), ast.sourceSpan]);
|
||||
}
|
||||
|
||||
// This method is defined to reconcile the type of ExpressionSourceHumanizer
|
||||
// since both RecursiveAstVisitor and TemplateAstVisitor define the visit()
|
||||
// method in their interfaces.
|
||||
visit(node: e.AST|t.TemplateAst, context?: any) { node.visit(this, context); }
|
||||
visit(node: e.AST|t.TemplateAst, context?: any) {
|
||||
node.visit(this, context);
|
||||
}
|
||||
|
||||
visitASTWithSource(ast: e.ASTWithSource) {
|
||||
this.recordAst(ast);
|
||||
@ -128,17 +132,25 @@ class ExpressionSourceHumanizer extends e.RecursiveAstVisitor implements t.Templ
|
||||
}
|
||||
visitReference(ast: t.ReferenceAst) {}
|
||||
visitVariable(ast: t.VariableAst) {}
|
||||
visitEvent(ast: t.BoundEventAst) { ast.handler.visit(this); }
|
||||
visitElementProperty(ast: t.BoundElementPropertyAst) { ast.value.visit(this); }
|
||||
visitEvent(ast: t.BoundEventAst) {
|
||||
ast.handler.visit(this);
|
||||
}
|
||||
visitElementProperty(ast: t.BoundElementPropertyAst) {
|
||||
ast.value.visit(this);
|
||||
}
|
||||
visitAttr(ast: t.AttrAst) {}
|
||||
visitBoundText(ast: t.BoundTextAst) { ast.value.visit(this); }
|
||||
visitBoundText(ast: t.BoundTextAst) {
|
||||
ast.value.visit(this);
|
||||
}
|
||||
visitText(ast: t.TextAst) {}
|
||||
visitDirective(ast: t.DirectiveAst) {
|
||||
t.templateVisitAll(this, ast.hostEvents);
|
||||
t.templateVisitAll(this, ast.hostProperties);
|
||||
t.templateVisitAll(this, ast.inputs);
|
||||
}
|
||||
visitDirectiveProperty(ast: t.BoundDirectivePropertyAst) { ast.value.visit(this); }
|
||||
visitDirectiveProperty(ast: t.BoundDirectivePropertyAst) {
|
||||
ast.value.visit(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -5,8 +5,9 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {CompileDirectiveMetadata, CompileEntryComponentMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, CompileTypeMetadata, ProxyClass, StaticSymbol, preserveWhitespacesDefault} from '@angular/compiler';
|
||||
import {CompileDirectiveMetadata, CompileEntryComponentMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, CompileTypeMetadata, preserveWhitespacesDefault, ProxyClass, StaticSymbol} from '@angular/compiler';
|
||||
import {ChangeDetectionStrategy, RendererType2, ViewEncapsulation} from '@angular/core';
|
||||
|
||||
import {noUndefined} from '../../../src/util';
|
||||
|
||||
export function createTypeMeta({reference, diDeps}: {reference: any, diDeps?: any[]}):
|
||||
@ -14,13 +15,28 @@ export function createTypeMeta({reference, diDeps}: {reference: any, diDeps?: an
|
||||
return {reference: reference, diDeps: diDeps || [], lifecycleHooks: []};
|
||||
}
|
||||
|
||||
export function compileDirectiveMetadataCreate(
|
||||
{isHost, type, isComponent, selector, exportAs, inputs, outputs, host, providers, viewProviders,
|
||||
queries, guards, viewQueries, entryComponents, template, componentViewType,
|
||||
rendererType}: Partial<Parameters<typeof CompileDirectiveMetadata.create>[0]>) {
|
||||
export function compileDirectiveMetadataCreate({
|
||||
isHost,
|
||||
type,
|
||||
isComponent,
|
||||
selector,
|
||||
exportAs,
|
||||
inputs,
|
||||
outputs,
|
||||
host,
|
||||
providers,
|
||||
viewProviders,
|
||||
queries,
|
||||
guards,
|
||||
viewQueries,
|
||||
entryComponents,
|
||||
template,
|
||||
componentViewType,
|
||||
rendererType
|
||||
}: Partial<Parameters<typeof CompileDirectiveMetadata.create>[0]>) {
|
||||
return CompileDirectiveMetadata.create({
|
||||
isHost: !!isHost,
|
||||
type: noUndefined(type) !,
|
||||
type: noUndefined(type)!,
|
||||
isComponent: !!isComponent,
|
||||
selector: noUndefined(selector),
|
||||
exportAs: noUndefined(exportAs),
|
||||
@ -34,17 +50,26 @@ export function compileDirectiveMetadataCreate(
|
||||
guards: guards || {},
|
||||
viewQueries: viewQueries || [],
|
||||
entryComponents: entryComponents || [],
|
||||
template: noUndefined(template) !,
|
||||
template: noUndefined(template)!,
|
||||
componentViewType: noUndefined(componentViewType),
|
||||
rendererType: noUndefined(rendererType),
|
||||
componentFactory: null,
|
||||
});
|
||||
}
|
||||
|
||||
export function compileTemplateMetadata(
|
||||
{encapsulation, template, templateUrl, styles, styleUrls, externalStylesheets, animations,
|
||||
ngContentSelectors, interpolation, isInline,
|
||||
preserveWhitespaces}: Partial<CompileTemplateMetadata>): CompileTemplateMetadata {
|
||||
export function compileTemplateMetadata({
|
||||
encapsulation,
|
||||
template,
|
||||
templateUrl,
|
||||
styles,
|
||||
styleUrls,
|
||||
externalStylesheets,
|
||||
animations,
|
||||
ngContentSelectors,
|
||||
interpolation,
|
||||
isInline,
|
||||
preserveWhitespaces
|
||||
}: Partial<CompileTemplateMetadata>): CompileTemplateMetadata {
|
||||
return new CompileTemplateMetadata({
|
||||
encapsulation: noUndefined(encapsulation),
|
||||
template: noUndefined(template),
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {UrlResolver, createOfflineCompileUrlResolver} from '@angular/compiler/src/url_resolver';
|
||||
import {createOfflineCompileUrlResolver, UrlResolver} from '@angular/compiler/src/url_resolver';
|
||||
import {beforeEach, describe, expect, inject, it} from '@angular/core/testing/src/testing_internal';
|
||||
|
||||
{
|
||||
@ -65,7 +65,6 @@ import {beforeEach, describe, expect, inject, it} from '@angular/core/testing/sr
|
||||
expect(resolver.resolve('foo/', './bar')).toEqual('foo/bar');
|
||||
expect(resolver.resolve('foo/baz', './bar')).toEqual('foo/bar');
|
||||
expect(resolver.resolve('foo/baz', 'bar')).toEqual('foo/bar');
|
||||
|
||||
});
|
||||
|
||||
it('should support ".." in the path', () => {
|
||||
@ -89,14 +88,14 @@ import {beforeEach, describe, expect, inject, it} from '@angular/core/testing/sr
|
||||
describe('packages', () => {
|
||||
it('should resolve a url based on the application package', () => {
|
||||
resolver = new UrlResolver('my_packages_dir');
|
||||
expect(resolver.resolve(null !, 'package:some/dir/file.txt'))
|
||||
expect(resolver.resolve(null!, 'package:some/dir/file.txt'))
|
||||
.toEqual('my_packages_dir/some/dir/file.txt');
|
||||
expect(resolver.resolve(null !, 'some/dir/file.txt')).toEqual('some/dir/file.txt');
|
||||
expect(resolver.resolve(null!, 'some/dir/file.txt')).toEqual('some/dir/file.txt');
|
||||
});
|
||||
|
||||
it('should contain a default value of "/" when nothing is provided',
|
||||
inject([UrlResolver], (resolver: UrlResolver) => {
|
||||
expect(resolver.resolve(null !, 'package:file')).toEqual('/file');
|
||||
expect(resolver.resolve(null!, 'package:file')).toEqual('/file');
|
||||
}));
|
||||
|
||||
it('should resolve a package value when present within the baseurl', () => {
|
||||
|
@ -15,7 +15,9 @@ import {escapeRegExp, splitAtColon, stringify, utf8Encode} from '../src/util';
|
||||
expect(splitAtColon('a:b', [])).toEqual(['a', 'b']);
|
||||
});
|
||||
|
||||
it('should trim parts', () => { expect(splitAtColon(' a : b ', [])).toEqual(['a', 'b']); });
|
||||
it('should trim parts', () => {
|
||||
expect(splitAtColon(' a : b ', [])).toEqual(['a', 'b']);
|
||||
});
|
||||
|
||||
it('should support multiple ":"', () => {
|
||||
expect(splitAtColon('a:b:c', [])).toEqual(['a', 'b:c']);
|
||||
@ -70,13 +72,16 @@ import {escapeRegExp, splitAtColon, stringify, utf8Encode} from '../src/util';
|
||||
['\uDEEE', '\xED\xBB\xAE'],
|
||||
['\uDFFF', '\xED\xBF\xBF'],
|
||||
];
|
||||
tests.forEach(([input, output]) => { expect(utf8Encode(input)).toEqual(output); });
|
||||
tests.forEach(([input, output]) => {
|
||||
expect(utf8Encode(input)).toEqual(output);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('stringify()', () => {
|
||||
it('should handle objects with no prototype.',
|
||||
() => { expect(stringify(Object.create(null))).toEqual('object'); });
|
||||
it('should handle objects with no prototype.', () => {
|
||||
expect(stringify(Object.create(null))).toEqual('object');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
Reference in New Issue
Block a user