Compare commits
29 Commits
Author | SHA1 | Date | |
---|---|---|---|
6ccb93728e | |||
b54a957ef4 | |||
21c14a79fa | |||
eed2d456d7 | |||
719c232321 | |||
bd033b69c1 | |||
5b92f7ee6d | |||
20ffb4196d | |||
11e6f45387 | |||
a30a73c2aa | |||
fb94c9937c | |||
b3259e26fe | |||
be406f8464 | |||
5cab3b146e | |||
eff6216319 | |||
88b2ab09ea | |||
5ad6d55a45 | |||
c81c7c0920 | |||
6f8e23b061 | |||
dc9711107d | |||
4c0c1e5fa0 | |||
06ef0908c7 | |||
a893d5e0a9 | |||
44afa5a03a | |||
0733f8d3e1 | |||
6604a4dc40 | |||
96c63a92e2 | |||
c444b1575f | |||
2b676192f6 |
60
CHANGELOG.md
60
CHANGELOG.md
@ -1,3 +1,63 @@
|
||||
<a name="4.0.3"></a>
|
||||
## [4.0.3](https://github.com/angular/angular/compare/4.0.2...4.0.3) (2017-04-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **benchpress:** chrome - prevent trace buffer overflow ([d216f94](https://github.com/angular/angular/commit/d216f94))
|
||||
* **compiler:** fix build error in xliff2 ([1870347](https://github.com/angular/angular/commit/1870347))
|
||||
* **compiler:** ignore calls to unresolved symbols in metadata ([d4038ab](https://github.com/angular/angular/commit/d4038ab)), closes [#15969](https://github.com/angular/angular/issues/15969)
|
||||
* **compiler:** ignore calls to unresolved symbols in metadata ([#15970](https://github.com/angular/angular/issues/15970)) ([db25f08](https://github.com/angular/angular/commit/db25f08)), closes [#15969](https://github.com/angular/angular/issues/15969)
|
||||
* **compiler:** Inform user where Quoted error was thrown ([3184cc5](https://github.com/angular/angular/commit/3184cc5))
|
||||
* **compiler:** suppress another closure warning ([#16137](https://github.com/angular/angular/issues/16137)) ([72e240a](https://github.com/angular/angular/commit/72e240a))
|
||||
* **core:** benchmarks - enable ng1 benchmark again ([ccac4c6](https://github.com/angular/angular/commit/ccac4c6))
|
||||
* **core:** distribute externs for testability API ([#16179](https://github.com/angular/angular/issues/16179)) ([e377d9d](https://github.com/angular/angular/commit/e377d9d))
|
||||
* **core:** key-value differ changes iteration ([#15968](https://github.com/angular/angular/issues/15968)) ([a8600dc](https://github.com/angular/angular/commit/a8600dc)), closes [#14997](https://github.com/angular/angular/issues/14997)
|
||||
* **language-service:** only use canonical symbols ([786093a](https://github.com/angular/angular/commit/786093a))
|
||||
* **packaging:** increased buffer size ([#15840](https://github.com/angular/angular/issues/15840)) ([88ad490](https://github.com/angular/angular/commit/88ad490))
|
||||
* **platform-server:** handle innerText ([#15818](https://github.com/angular/angular/issues/15818)) ([7de340d](https://github.com/angular/angular/commit/7de340d))
|
||||
* **router:** prevent `RouterLinkActive` from causing an infinite CD loop ([4479c42](https://github.com/angular/angular/commit/4479c42)), closes [#15825](https://github.com/angular/angular/issues/15825)
|
||||
* **tsc-wrapped:** collect new expressions with no arguments ([#15908](https://github.com/angular/angular/issues/15908)) ([41cac9e](https://github.com/angular/angular/commit/41cac9e)), closes [#15906](https://github.com/angular/angular/issues/15906)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compiler:** Implement i18n XLIFF 2.0 serializer ([#14185](https://github.com/angular/angular/issues/14185)) ([a7d8edd](https://github.com/angular/angular/commit/a7d8edd)), closes [#11735](https://github.com/angular/angular/issues/11735)
|
||||
* **upgrade:** allow setting the angularjs lib at runtime ([#15168](https://github.com/angular/angular/issues/15168)) ([a75d056](https://github.com/angular/angular/commit/a75d056))
|
||||
* **upgrade:** allow setting the angularjs lib at runtime ([#15168](https://github.com/angular/angular/issues/15168)) ([4f172b0](https://github.com/angular/angular/commit/4f172b0))
|
||||
* **upgrade:** fixes for allow setting the angularjs lib at runtime ([bb6932d](https://github.com/angular/angular/commit/bb6932d))
|
||||
* add support for TS 2.3 ([5cf101f](https://github.com/angular/angular/commit/5cf101f))
|
||||
|
||||
|
||||
|
||||
<a name="4.1.0-beta.1"></a>
|
||||
# [4.1.0-beta.1](https://github.com/angular/angular/compare/4.1.0-beta.0...4.1.0-beta.1) (2017-04-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler:** fix inheritance for AOT with summaries ([#15583](https://github.com/angular/angular/issues/15583)) ([8ef621a](https://github.com/angular/angular/commit/8ef621a))
|
||||
* **language-service:** avoid throwing exceptions when reporting metadata errors ([7764c5c](https://github.com/angular/angular/commit/7764c5c))
|
||||
* **language-service:** detect when there isn't a tsconfig.json ([258d539](https://github.com/angular/angular/commit/258d539)), closes [#15874](https://github.com/angular/angular/issues/15874)
|
||||
* **language-service:** improve resilience to incomplete information ([71a8627](https://github.com/angular/angular/commit/71a8627))
|
||||
* **language-service:** initialize static reflector correctly ([fe0d02f](https://github.com/angular/angular/commit/fe0d02f)), closes [#15768](https://github.com/angular/angular/issues/15768)
|
||||
* **language-service:** parse extended i18n forms ([bde9771](https://github.com/angular/angular/commit/bde9771))
|
||||
* **language-service:** resolve any parameter types to any result ([5fbb0d0](https://github.com/angular/angular/commit/5fbb0d0))
|
||||
* **router:** fix query param parsing ([a487563](https://github.com/angular/angular/commit/a487563))
|
||||
* **router:** the preloader use the module from the loaded config ([6d12aa9](https://github.com/angular/angular/commit/6d12aa9))
|
||||
* **tsc-wrapped:** ensure valid path separators in metadata ([96aa236](https://github.com/angular/angular/commit/96aa236))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **animations:** Update types for TypeScript nullability support ([38d75d4](https://github.com/angular/angular/commit/38d75d4)), closes [#15870](https://github.com/angular/angular/issues/15870)
|
||||
* **benchpress:** Update types for TypeScript nullability support ([14669f2](https://github.com/angular/angular/commit/14669f2))
|
||||
* **common:** Update types for TypeScript nullability support ([d8b73e4](https://github.com/angular/angular/commit/d8b73e4))
|
||||
* **compiler:** Update types for TypeScript nullability support ([09d9f5f](https://github.com/angular/angular/commit/09d9f5f))
|
||||
* **language-service:** Update types for TypeScript nullability support ([540581d](https://github.com/angular/angular/commit/540581d))
|
||||
|
||||
|
||||
|
||||
<a name="4.0.2"></a>
|
||||
## [4.0.2](https://github.com/angular/angular/compare/4.0.1...4.0.2) (2017-04-11)
|
||||
|
||||
|
3
build.sh
3
build.sh
@ -472,8 +472,9 @@ do
|
||||
rsync -a ${OUT_DIR}/ ${NPM_DIR}
|
||||
fi
|
||||
|
||||
echo "====== Copy ${PACKAGE} package.json files"
|
||||
echo "====== Copy ${PACKAGE} package.json and .externs.js files"
|
||||
rsync -am --include="package.json" --include="*/" --exclude=* ${SRC_DIR}/ ${NPM_DIR}/
|
||||
rsync -am --include="*.externs.js" --include="*/" --exclude=* ${SRC_DIR}/ ${NPM_DIR}/
|
||||
|
||||
cp ${ROOT_DIR}/README.md ${NPM_DIR}/
|
||||
fi
|
||||
|
@ -34,7 +34,7 @@ CLOSURE_ARGS=(
|
||||
# Uncomment for easier debugging
|
||||
# "--formatting=PRETTY_PRINT"
|
||||
|
||||
e2e/testability.externs.js
|
||||
node_modules/@angular/core/src/testability/testability.externs.js
|
||||
node_modules/zone.js/dist/zone.js
|
||||
$(find -L vendor/rxjs -name *.js)
|
||||
node_modules/@angular/core/@angular/core.js
|
||||
|
@ -1,47 +0,0 @@
|
||||
/** @externs */
|
||||
// Workaround for #11119
|
||||
// TODO(alexeagle): these externs ought to be distributed with Angular.
|
||||
/**
|
||||
* @externs
|
||||
* @suppress {duplicate}
|
||||
*/
|
||||
// NOTE: generated by tsickle, do not edit.
|
||||
|
||||
/** @record @struct */
|
||||
function BrowserNodeGlobal() {}
|
||||
/** @type {?} */
|
||||
BrowserNodeGlobal.prototype.getAngularTestability;
|
||||
/** @type {?} */
|
||||
BrowserNodeGlobal.prototype.getAllAngularTestabilities;
|
||||
/** @type {?} */
|
||||
BrowserNodeGlobal.prototype.getAllAngularRootElements;
|
||||
/** @type {?} */
|
||||
BrowserNodeGlobal.prototype.frameworkStabilizers;
|
||||
|
||||
/**
|
||||
* @param {?} condition
|
||||
* @return {?}
|
||||
*/
|
||||
BrowserNodeGlobal.prototype.assert = function(condition) {};
|
||||
|
||||
/** @record @struct */
|
||||
function PublicTestability() {}
|
||||
|
||||
/**
|
||||
* @return {?}
|
||||
*/
|
||||
PublicTestability.prototype.isStable = function() {};
|
||||
|
||||
/**
|
||||
* @param {?} callback
|
||||
* @return {?}
|
||||
*/
|
||||
PublicTestability.prototype.whenStable = function(callback) {};
|
||||
|
||||
/**
|
||||
* @param {?} using
|
||||
* @param {?} provider
|
||||
* @param {?} exactMatch
|
||||
* @return {?}
|
||||
*/
|
||||
PublicTestability.prototype.findProviders = function(using, provider, exactMatch) {};
|
81
modules/benchmarks/e2e_test/tree_data.ts
Normal file
81
modules/benchmarks/e2e_test/tree_data.ts
Normal file
@ -0,0 +1,81 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {$} from 'protractor';
|
||||
|
||||
export const CreateBtn = '#createDom';
|
||||
export const DestroyBtn = '#destroyDom';
|
||||
export const DetectChangesBtn = '#detectChanges';
|
||||
export const RootEl = '#root';
|
||||
export const NumberOfChecksEl = '#numberOfChecks';
|
||||
|
||||
export interface Benchmark {
|
||||
id: string;
|
||||
url: string;
|
||||
buttons: string[];
|
||||
ignoreBrowserSynchronization?: boolean;
|
||||
extraParams?: {name: string, value: any}[];
|
||||
}
|
||||
|
||||
const CreateDestroyButtons: string[] = [CreateBtn, DestroyBtn];
|
||||
const CreateDestroyDetectChangesButtons: string[] = [...CreateDestroyButtons, DetectChangesBtn];
|
||||
|
||||
export const Benchmarks: Benchmark[] = [
|
||||
{
|
||||
id: `deepTree.ng2`,
|
||||
url: 'all/benchmarks/src/tree/ng2/index.html',
|
||||
buttons: CreateDestroyDetectChangesButtons,
|
||||
},
|
||||
{
|
||||
id: `deepTree.ng2.next`,
|
||||
url: 'all/benchmarks/src/tree/ng2_next/index.html',
|
||||
buttons: CreateDestroyDetectChangesButtons,
|
||||
ignoreBrowserSynchronization: true,
|
||||
// Can't use bundles as we use non exported code
|
||||
extraParams: [{name: 'bundles', value: false}]
|
||||
},
|
||||
{
|
||||
id: `deepTree.ng2.static`,
|
||||
url: 'all/benchmarks/src/tree/ng2_static/index.html',
|
||||
buttons: CreateDestroyButtons,
|
||||
},
|
||||
{
|
||||
id: `deepTree.ng2_switch`,
|
||||
url: 'all/benchmarks/src/tree/ng2_switch/index.html',
|
||||
buttons: CreateDestroyButtons,
|
||||
},
|
||||
{
|
||||
id: `deepTree.baseline`,
|
||||
url: 'all/benchmarks/src/tree/baseline/index.html',
|
||||
buttons: CreateDestroyButtons,
|
||||
ignoreBrowserSynchronization: true,
|
||||
},
|
||||
{
|
||||
id: `deepTree.incremental_dom`,
|
||||
url: 'all/benchmarks/src/tree/incremental_dom/index.html',
|
||||
buttons: CreateDestroyButtons,
|
||||
ignoreBrowserSynchronization: true,
|
||||
},
|
||||
{
|
||||
id: `deepTree.polymer`,
|
||||
url: 'all/benchmarks/src/tree/polymer/index.html',
|
||||
buttons: CreateDestroyButtons,
|
||||
ignoreBrowserSynchronization: true,
|
||||
},
|
||||
{
|
||||
id: `deepTree.polymer_leaves`,
|
||||
url: 'all/benchmarks/src/tree/polymer_leaves/index.html',
|
||||
buttons: CreateDestroyButtons,
|
||||
ignoreBrowserSynchronization: true,
|
||||
},
|
||||
{
|
||||
id: `deepTree.ng1`,
|
||||
url: 'all/benchmarks/src/tree/ng1/index.html',
|
||||
buttons: CreateDestroyDetectChangesButtons,
|
||||
}
|
||||
];
|
@ -7,162 +7,77 @@
|
||||
*/
|
||||
|
||||
import {runBenchmark, verifyNoBrowserErrors} from 'e2e_util/perf_util';
|
||||
import {$} from 'protractor';
|
||||
import {$, browser} from 'protractor';
|
||||
|
||||
interface Worker {
|
||||
id: string;
|
||||
prepare?(): void;
|
||||
work(): void;
|
||||
}
|
||||
|
||||
const CreateOnlyWorker: Worker = {
|
||||
id: 'createOnly',
|
||||
prepare: () => $('#destroyDom').click(),
|
||||
work: () => $('#createDom').click()
|
||||
};
|
||||
|
||||
const CreateAndDestroyWorker: Worker = {
|
||||
id: 'createDestroy',
|
||||
work: () => {
|
||||
$('#createDom').click();
|
||||
$('#destroyDom').click();
|
||||
}
|
||||
};
|
||||
|
||||
const UpdateWorker: Worker = {
|
||||
id: 'update',
|
||||
work: () => $('#createDom').click()
|
||||
};
|
||||
import {Benchmark, Benchmarks, CreateBtn, DestroyBtn, DetectChangesBtn, RootEl} from './tree_data';
|
||||
|
||||
describe('tree benchmark perf', () => {
|
||||
|
||||
afterEach(verifyNoBrowserErrors);
|
||||
let _oldRootEl: any;
|
||||
beforeEach(() => _oldRootEl = browser.rootEl);
|
||||
|
||||
[CreateOnlyWorker, CreateAndDestroyWorker, UpdateWorker].forEach((worker) => {
|
||||
describe(worker.id, () => {
|
||||
afterEach(() => {
|
||||
browser.rootEl = _oldRootEl;
|
||||
verifyNoBrowserErrors();
|
||||
});
|
||||
|
||||
it('should run for ng2', (done) => {
|
||||
Benchmarks.forEach(benchmark => {
|
||||
describe(benchmark.id, () => {
|
||||
it('should work for createOnly', (done) => {
|
||||
runTreeBenchmark({
|
||||
id: `deepTree.ng2.${worker.id}`,
|
||||
url: 'all/benchmarks/src/tree/ng2/index.html',
|
||||
work: worker.work,
|
||||
prepare: worker.prepare,
|
||||
id: 'createOnly',
|
||||
benchmark,
|
||||
prepare: () => $(CreateBtn).click(),
|
||||
work: () => $(DestroyBtn).click()
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
|
||||
it('should run for ng2 next', (done) => {
|
||||
it('should work for createDestroy', (done) => {
|
||||
runTreeBenchmark({
|
||||
id: `deepTree.ng2.next.${worker.id}`,
|
||||
url: 'all/benchmarks/src/tree/ng2_next/index.html',
|
||||
ignoreBrowserSynchronization: true,
|
||||
work: worker.work,
|
||||
prepare: worker.prepare,
|
||||
// Can't use bundles as we use non exported code
|
||||
extraParams: [{name: 'bundles', value: false}]
|
||||
id: 'createDestroy',
|
||||
benchmark,
|
||||
work: () => {
|
||||
$(DestroyBtn).click();
|
||||
$(CreateBtn).click();
|
||||
}
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
|
||||
it('should run for ng2 static', (done) => {
|
||||
runTreeBenchmark({
|
||||
id: `deepTree.ng2.static.${worker.id}`,
|
||||
url: 'all/benchmarks/src/tree/ng2_static/index.html',
|
||||
work: worker.work,
|
||||
prepare: worker.prepare,
|
||||
}).then(done, done.fail);
|
||||
it('should work for update', (done) => {
|
||||
runTreeBenchmark({id: 'update', benchmark, work: () => $(CreateBtn).click()})
|
||||
.then(done, done.fail);
|
||||
});
|
||||
|
||||
it('should run for ng2 switch', (done) => {
|
||||
runTreeBenchmark({
|
||||
id: `deepTree.ng2_switch.${worker.id}`,
|
||||
url: 'all/benchmarks/src/tree/ng2_switch/index.html',
|
||||
work: worker.work,
|
||||
prepare: worker.prepare,
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
if (benchmark.buttons.indexOf(DetectChangesBtn) !== -1) {
|
||||
it('should work for detectChanges', (done) => {
|
||||
runTreeBenchmark({
|
||||
id: 'detectChanges',
|
||||
benchmark,
|
||||
work: () => $(DetectChangesBtn).click(),
|
||||
setup: () => $(DestroyBtn).click()
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
}
|
||||
|
||||
it('should run for the baseline', (done) => {
|
||||
runTreeBenchmark({
|
||||
id: `deepTree.baseline.${worker.id}`,
|
||||
url: 'all/benchmarks/src/tree/baseline/index.html',
|
||||
ignoreBrowserSynchronization: true,
|
||||
work: worker.work,
|
||||
prepare: worker.prepare,
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
|
||||
it('should run for incremental-dom', (done) => {
|
||||
runTreeBenchmark({
|
||||
id: `deepTree.incremental_dom.${worker.id}`,
|
||||
url: 'all/benchmarks/src/tree/incremental_dom/index.html',
|
||||
ignoreBrowserSynchronization: true,
|
||||
work: worker.work,
|
||||
prepare: worker.prepare,
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
|
||||
it('should run for polymer binary tree', (done) => {
|
||||
runTreeBenchmark({
|
||||
id: `deepTree.polymer.${worker.id}`,
|
||||
url: 'all/benchmarks/src/tree/polymer/index.html',
|
||||
ignoreBrowserSynchronization: true,
|
||||
work: worker.work,
|
||||
prepare: worker.prepare,
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
|
||||
it('should run for polymer leaves', (done) => {
|
||||
runTreeBenchmark({
|
||||
id: `deepTree.polymer_leaves.${worker.id}`,
|
||||
url: 'all/benchmarks/src/tree/polymer_leaves/index.html',
|
||||
ignoreBrowserSynchronization: true,
|
||||
work: worker.work,
|
||||
prepare: worker.prepare,
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should run ng2 changedetection', (done) => {
|
||||
runTreeBenchmark({
|
||||
id: `deepTree.ng2.changedetection`,
|
||||
url: 'all/benchmarks/src/tree/ng2/index.html',
|
||||
work: () => $('#detectChanges').click(),
|
||||
setup: () => $('#createDom').click(),
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
|
||||
it('should run ng2 next changedetection', (done) => {
|
||||
runTreeBenchmark({
|
||||
id: `deepTree.ng2.next.changedetection`,
|
||||
url: 'all/benchmarks/src/tree/ng2_next/index.html',
|
||||
work: () => $('#detectChanges').click(),
|
||||
setup: () => $('#createDom').click(),
|
||||
ignoreBrowserSynchronization: true,
|
||||
// Can't use bundles as we use non exported code
|
||||
extraParams: [{name: 'bundles', value: false}]
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
|
||||
function runTreeBenchmark(config: {
|
||||
id: string,
|
||||
url: string, ignoreBrowserSynchronization?: boolean,
|
||||
work: () => any,
|
||||
prepare?: () => any,
|
||||
extraParams?: {name: string, value: any}[],
|
||||
setup?: () => any
|
||||
}) {
|
||||
let params = [{name: 'depth', value: 11}];
|
||||
if (config.extraParams) {
|
||||
params = params.concat(config.extraParams);
|
||||
}
|
||||
return runBenchmark({
|
||||
id: config.id,
|
||||
url: config.url,
|
||||
ignoreBrowserSynchronization: config.ignoreBrowserSynchronization,
|
||||
params: params,
|
||||
work: config.work,
|
||||
prepare: config.prepare,
|
||||
setup: config.setup
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function runTreeBenchmark({id, benchmark, prepare, setup, work}: {
|
||||
id: string; benchmark: Benchmark, prepare ? () : void; setup ? () : void; work(): void;
|
||||
}) {
|
||||
let params = [{name: 'depth', value: 11}];
|
||||
if (benchmark.extraParams) {
|
||||
params = params.concat(benchmark.extraParams);
|
||||
}
|
||||
browser.rootEl = RootEl;
|
||||
return runBenchmark({
|
||||
id: `${benchmark.id}.${id}`,
|
||||
url: benchmark.url,
|
||||
ignoreBrowserSynchronization: benchmark.ignoreBrowserSynchronization,
|
||||
params: params,
|
||||
work: work,
|
||||
prepare: prepare,
|
||||
setup: setup
|
||||
});
|
||||
}
|
||||
|
@ -7,107 +7,57 @@
|
||||
*/
|
||||
|
||||
import {openBrowser, verifyNoBrowserErrors} from 'e2e_util/e2e_util';
|
||||
import {$} from 'protractor';
|
||||
import {$, browser} from 'protractor';
|
||||
|
||||
import {Benchmark, Benchmarks, CreateBtn, DestroyBtn, DetectChangesBtn, NumberOfChecksEl, RootEl} from './tree_data';
|
||||
|
||||
describe('tree benchmark spec', () => {
|
||||
|
||||
afterEach(verifyNoBrowserErrors);
|
||||
let _oldRootEl: any;
|
||||
beforeEach(() => _oldRootEl = browser.rootEl);
|
||||
|
||||
it('should work for ng2', () => {
|
||||
testTreeBenchmark({
|
||||
url: 'all/benchmarks/src/tree/ng2/index.html',
|
||||
afterEach(() => {
|
||||
browser.rootEl = _oldRootEl;
|
||||
verifyNoBrowserErrors();
|
||||
});
|
||||
|
||||
Benchmarks.forEach(benchmark => {
|
||||
describe(benchmark.id, () => {
|
||||
it('should work for createDestroy', () => {
|
||||
openTreeBenchmark(benchmark);
|
||||
$(CreateBtn).click();
|
||||
expect($(RootEl).getText()).toContain('0');
|
||||
$(DestroyBtn).click();
|
||||
expect($(RootEl).getText()).toEqual('');
|
||||
});
|
||||
|
||||
it('should work for update', () => {
|
||||
openTreeBenchmark(benchmark);
|
||||
$(CreateBtn).click();
|
||||
$(CreateBtn).click();
|
||||
expect($(RootEl).getText()).toContain('A');
|
||||
});
|
||||
|
||||
if (benchmark.buttons.indexOf(DetectChangesBtn) !== -1) {
|
||||
it('should work for detectChanges', () => {
|
||||
openTreeBenchmark(benchmark);
|
||||
$(DetectChangesBtn).click();
|
||||
expect($(NumberOfChecksEl).getText()).toContain('10');
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should work for ng2 detect changes', () => {
|
||||
function openTreeBenchmark(benchmark: Benchmark) {
|
||||
let params = [{name: 'depth', value: 4}];
|
||||
openBrowser({url: 'all/benchmarks/src/tree/ng2/index.html', params});
|
||||
$('#detectChanges').click();
|
||||
expect($('#numberOfChecks').getText()).toContain('10');
|
||||
});
|
||||
|
||||
it('should work for ng2 next', () => {
|
||||
testTreeBenchmark({
|
||||
url: 'all/benchmarks/src/tree/ng2_next/index.html',
|
||||
ignoreBrowserSynchronization: true,
|
||||
// Can't use bundles as we use non exported code
|
||||
extraParams: [{name: 'bundles', value: false}]
|
||||
});
|
||||
});
|
||||
|
||||
it('should work for ng2 next detect changes', () => {
|
||||
let params = [
|
||||
{name: 'depth', value: 4},
|
||||
// Can't use bundles as we use non exported code
|
||||
{name: 'bundles', value: false}
|
||||
];
|
||||
openBrowser({
|
||||
url: 'all/benchmarks/src/tree/ng2_next/index.html',
|
||||
ignoreBrowserSynchronization: true, params
|
||||
});
|
||||
$('#detectChanges').click();
|
||||
expect($('#numberOfChecks').getText()).toContain('10');
|
||||
});
|
||||
|
||||
it('should work for ng2 static', () => {
|
||||
testTreeBenchmark({
|
||||
url: 'all/benchmarks/src/tree/ng2_static/index.html',
|
||||
});
|
||||
});
|
||||
|
||||
it('should work for ng2 switch', () => {
|
||||
testTreeBenchmark({
|
||||
url: 'all/benchmarks/src/tree/ng2_switch/index.html',
|
||||
});
|
||||
});
|
||||
|
||||
it('should work for the baseline', () => {
|
||||
testTreeBenchmark({
|
||||
url: 'all/benchmarks/src/tree/baseline/index.html',
|
||||
ignoreBrowserSynchronization: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should work for incremental dom', () => {
|
||||
testTreeBenchmark({
|
||||
url: 'all/benchmarks/src/tree/incremental_dom/index.html',
|
||||
ignoreBrowserSynchronization: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should work for polymer binary tree', () => {
|
||||
testTreeBenchmark({
|
||||
url: 'all/benchmarks/src/tree/polymer/index.html',
|
||||
ignoreBrowserSynchronization: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should work for polymer leaves', () => {
|
||||
testTreeBenchmark({
|
||||
url: 'all/benchmarks/src/tree/polymer_leaves/index.html',
|
||||
ignoreBrowserSynchronization: true,
|
||||
});
|
||||
});
|
||||
|
||||
function testTreeBenchmark(openConfig: {
|
||||
url: string,
|
||||
ignoreBrowserSynchronization?: boolean,
|
||||
extraParams?: {name: string, value: any}[]
|
||||
}) {
|
||||
let params = [{name: 'depth', value: 4}];
|
||||
if (openConfig.extraParams) {
|
||||
params = params.concat(openConfig.extraParams);
|
||||
if (benchmark.extraParams) {
|
||||
params = params.concat(benchmark.extraParams);
|
||||
}
|
||||
browser.rootEl = RootEl;
|
||||
openBrowser({
|
||||
url: openConfig.url,
|
||||
ignoreBrowserSynchronization: openConfig.ignoreBrowserSynchronization,
|
||||
url: benchmark.url,
|
||||
ignoreBrowserSynchronization: benchmark.ignoreBrowserSynchronization,
|
||||
params: params,
|
||||
});
|
||||
$('#createDom').click();
|
||||
expect($('#root').getText()).toContain('0');
|
||||
$('#createDom').click();
|
||||
expect($('#root').getText()).toContain('A');
|
||||
$('#destroyDom').click();
|
||||
expect($('#root').getText()).toEqual('');
|
||||
}
|
||||
});
|
||||
|
44
modules/benchmarks/src/tree/ng1/index.html
Normal file
44
modules/benchmarks/src/tree/ng1/index.html
Normal file
@ -0,0 +1,44 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<h2>Params</h2>
|
||||
<form>
|
||||
Depth:
|
||||
<input type="number" name="depth" placeholder="depth" value="9">
|
||||
<br>
|
||||
<button>Apply</button>
|
||||
</form>
|
||||
|
||||
<h2>Ng1 Tree Benchmark</h2>
|
||||
<p>
|
||||
<button id="destroyDom">destroyDom</button>
|
||||
<button id="createDom">createDom</button>
|
||||
<button id="detectChanges">detectChanges</button>
|
||||
<button id="updateDomProfile">profile updateDom</button>
|
||||
<button id="createDomProfile">profile createDom</button>
|
||||
<button id="detectChangesProfile">profile detectChanges</button>
|
||||
</p>
|
||||
|
||||
<div>
|
||||
Change detection runs:<span id="numberOfChecks"></span>
|
||||
</div>
|
||||
<div>
|
||||
<tree id="root" data="initData">Loading...</tree>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var mainUrls = [
|
||||
'/all/benchmarks/vendor/angular.js',
|
||||
'../../bootstrap_plain.js'
|
||||
];
|
||||
var mainUrl = window.location.search.split(/[?&]main=([^&]+)/)[1];
|
||||
if (mainUrl) {
|
||||
mainUrls = [mainUrl];
|
||||
}
|
||||
mainUrls.forEach(function(mainUrl) {
|
||||
document.write('<script src="' + mainUrl + '">\u003c/script>');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
53
modules/benchmarks/src/tree/ng1/index.ts
Normal file
53
modules/benchmarks/src/tree/ng1/index.ts
Normal file
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {bindAction, profile} from '../../util';
|
||||
import {buildTree, emptyTree} from '../util';
|
||||
|
||||
import {addTreeToModule} from './tree';
|
||||
|
||||
declare var angular: any;
|
||||
|
||||
function init() {
|
||||
let detectChangesRuns = 0;
|
||||
const numberOfChecksEl = document.getElementById('numberOfChecks') !;
|
||||
|
||||
addTreeToModule(angular.module('app', [])).run([
|
||||
'$rootScope',
|
||||
($rootScope: any) => {
|
||||
function detectChanges() {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
$rootScope.$digest();
|
||||
}
|
||||
detectChangesRuns += 10;
|
||||
numberOfChecksEl.textContent = `${detectChangesRuns}`;
|
||||
}
|
||||
|
||||
function noop() {}
|
||||
|
||||
function destroyDom() {
|
||||
$rootScope.$apply(() => { $rootScope.initData = emptyTree; });
|
||||
}
|
||||
|
||||
function createDom() {
|
||||
$rootScope.$apply(() => { $rootScope.initData = buildTree(); });
|
||||
}
|
||||
|
||||
bindAction('#destroyDom', destroyDom);
|
||||
bindAction('#createDom', createDom);
|
||||
bindAction('#detectChanges', detectChanges);
|
||||
bindAction('#detectChangesProfile', profile(detectChanges, noop, 'detectChanges'));
|
||||
bindAction('#updateDomProfile', profile(createDom, noop, 'update'));
|
||||
bindAction('#createDomProfile', profile(createDom, destroyDom, 'create'));
|
||||
}
|
||||
]);
|
||||
|
||||
angular.bootstrap(document.querySelector('tree'), ['app']);
|
||||
}
|
||||
|
||||
init();
|
72
modules/benchmarks/src/tree/ng1/tree.ts
Normal file
72
modules/benchmarks/src/tree/ng1/tree.ts
Normal file
@ -0,0 +1,72 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {TreeNode} from '../util';
|
||||
|
||||
declare var angular: any;
|
||||
|
||||
export function addTreeToModule(mod: any): any {
|
||||
return mod
|
||||
.directive(
|
||||
'tree',
|
||||
function() {
|
||||
return {
|
||||
scope: {data: '='},
|
||||
template:
|
||||
`<span ng-style="{'background-color': data.depth % 2 ? '' : 'grey'}"> {{data.value}} </span><tree-if data='data.right'></tree-if><tree-if data='data.left'></tree-if>`
|
||||
};
|
||||
})
|
||||
// special directive for "if" as angular 1.3 does not support
|
||||
// recursive components.
|
||||
// Cloned from real ngIf directive, but using a lazily created transclude function.
|
||||
.directive(
|
||||
'treeIf',
|
||||
[
|
||||
'$compile', '$animate',
|
||||
function($compile: any, $animate: any) {
|
||||
let transcludeFn: any;
|
||||
return {
|
||||
transclude: 'element',
|
||||
priority: 600,
|
||||
terminal: true,
|
||||
$$tlb: true,
|
||||
link: function($scope: any, $element: any, $attr: any, ctrl: any) {
|
||||
if (!transcludeFn) {
|
||||
const template = '<tree data="' + $attr.data + '"></tree>';
|
||||
transcludeFn = $compile(template);
|
||||
}
|
||||
let childElement: any, childScope: any;
|
||||
$scope.$watch($attr.data, function ngIfWatchAction(value: any) {
|
||||
|
||||
if (value) {
|
||||
if (!childScope) {
|
||||
childScope = $scope.$new();
|
||||
transcludeFn(childScope, function(clone: any) {
|
||||
childElement = clone;
|
||||
$animate.enter(clone, $element.parent(), $element);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (childScope) {
|
||||
childScope.$destroy();
|
||||
childScope = null;
|
||||
}
|
||||
if (childElement) {
|
||||
$animate.leave(childElement);
|
||||
childElement = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
])
|
||||
.config([
|
||||
'$compileProvider',
|
||||
function($compileProvider: any) { $compileProvider.debugInfoEnabled(false); }
|
||||
]);
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-srcs",
|
||||
"version": "4.0.0-rc.5",
|
||||
"version": "4.1.0-beta.1",
|
||||
"dependencies": {
|
||||
"@types/angularjs": {
|
||||
"version": "1.5.13-alpha",
|
||||
@ -86,6 +86,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ajv": {
|
||||
"version": "4.11.7",
|
||||
"dev": true
|
||||
},
|
||||
"align-text": {
|
||||
"version": "0.1.3",
|
||||
"dev": true,
|
||||
@ -2025,6 +2029,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"co": {
|
||||
"version": "4.6.0",
|
||||
"dev": true
|
||||
},
|
||||
"code-point-at": {
|
||||
"version": "1.0.0",
|
||||
"dev": true
|
||||
@ -3793,6 +3801,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"har-schema": {
|
||||
"version": "1.0.5",
|
||||
"dev": true
|
||||
},
|
||||
"har-validator": {
|
||||
"version": "2.0.6",
|
||||
"dev": true,
|
||||
@ -4219,6 +4231,10 @@
|
||||
"version": "0.2.3",
|
||||
"dev": true
|
||||
},
|
||||
"json-stable-stringify": {
|
||||
"version": "1.0.1",
|
||||
"dev": true
|
||||
},
|
||||
"json-stringify-safe": {
|
||||
"version": "5.0.1",
|
||||
"dev": true
|
||||
@ -4235,6 +4251,10 @@
|
||||
"version": "2.2.3",
|
||||
"dev": true
|
||||
},
|
||||
"jsonify": {
|
||||
"version": "0.0.0",
|
||||
"dev": true
|
||||
},
|
||||
"jsonparse": {
|
||||
"version": "1.2.0",
|
||||
"dev": true
|
||||
@ -5170,6 +5190,10 @@
|
||||
"version": "0.9.0",
|
||||
"dev": true
|
||||
},
|
||||
"performance-now": {
|
||||
"version": "0.2.0",
|
||||
"dev": true
|
||||
},
|
||||
"pify": {
|
||||
"version": "2.3.0",
|
||||
"dev": true
|
||||
@ -5225,39 +5249,51 @@
|
||||
}
|
||||
},
|
||||
"protractor": {
|
||||
"version": "4.0.11",
|
||||
"version": "4.0.14",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/jasmine": {
|
||||
"version": "2.5.37",
|
||||
"version": "2.5.47",
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "6.0.46",
|
||||
"version": "6.0.68",
|
||||
"dev": true
|
||||
},
|
||||
"@types/selenium-webdriver": {
|
||||
"version": "2.53.37",
|
||||
"dev": true
|
||||
},
|
||||
"caseless": {
|
||||
"version": "0.12.0",
|
||||
"dev": true
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.1",
|
||||
"dev": true
|
||||
},
|
||||
"har-validator": {
|
||||
"version": "4.2.1",
|
||||
"dev": true
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.24.0",
|
||||
"version": "1.27.0",
|
||||
"dev": true
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.12",
|
||||
"version": "2.1.15",
|
||||
"dev": true
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.3.0",
|
||||
"version": "6.4.0",
|
||||
"dev": true
|
||||
},
|
||||
"request": {
|
||||
"version": "2.78.0",
|
||||
"version": "2.81.0",
|
||||
"dev": true
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.5.4",
|
||||
"version": "2.6.1",
|
||||
"dev": true
|
||||
},
|
||||
"saucelabs": {
|
||||
@ -5272,8 +5308,16 @@
|
||||
"version": "2.3.2",
|
||||
"dev": true
|
||||
},
|
||||
"tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"dev": true
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.0.1",
|
||||
"dev": true
|
||||
},
|
||||
"webdriver-manager": {
|
||||
"version": "10.2.8",
|
||||
"version": "10.3.0",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
@ -5604,6 +5648,10 @@
|
||||
"rxjs": {
|
||||
"version": "5.0.1"
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.0.1",
|
||||
"dev": true
|
||||
},
|
||||
"sander": {
|
||||
"version": "0.5.1",
|
||||
"dev": true,
|
||||
|
118
npm-shrinkwrap.json
generated
118
npm-shrinkwrap.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-srcs",
|
||||
"version": "4.0.0-rc.5",
|
||||
"version": "4.1.0-beta.1",
|
||||
"dependencies": {
|
||||
"@types/angularjs": {
|
||||
"version": "1.5.13-alpha",
|
||||
@ -126,6 +126,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ajv": {
|
||||
"version": "4.11.7",
|
||||
"from": "ajv@>=4.9.1 <5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.7.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"align-text": {
|
||||
"version": "0.1.3",
|
||||
"from": "align-text@>=0.1.0 <0.2.0",
|
||||
@ -2911,6 +2917,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"co": {
|
||||
"version": "4.6.0",
|
||||
"from": "co@>=4.6.0 <5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"code-point-at": {
|
||||
"version": "1.0.0",
|
||||
"from": "code-point-at@>=1.0.0 <2.0.0",
|
||||
@ -5529,6 +5541,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"har-schema": {
|
||||
"version": "1.0.5",
|
||||
"from": "har-schema@>=1.0.5 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"har-validator": {
|
||||
"version": "2.0.6",
|
||||
"from": "har-validator@>=2.0.6 <2.1.0",
|
||||
@ -6159,6 +6177,12 @@
|
||||
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"json-stable-stringify": {
|
||||
"version": "1.0.1",
|
||||
"from": "json-stable-stringify@>=1.0.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"json-stringify-safe": {
|
||||
"version": "5.0.1",
|
||||
"from": "json-stringify-safe@>=5.0.1 <5.1.0",
|
||||
@ -6183,6 +6207,12 @@
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.2.3.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"jsonify": {
|
||||
"version": "0.0.0",
|
||||
"from": "jsonify@>=0.0.0 <0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"jsonparse": {
|
||||
"version": "1.2.0",
|
||||
"from": "jsonparse@>=1.1.0 <2.0.0",
|
||||
@ -7554,6 +7584,12 @@
|
||||
"resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.9.0.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"performance-now": {
|
||||
"version": "0.2.0",
|
||||
"from": "performance-now@>=0.2.0 <0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"pify": {
|
||||
"version": "2.3.0",
|
||||
"from": "pify@>=2.0.0 <3.0.0",
|
||||
@ -7635,21 +7671,33 @@
|
||||
}
|
||||
},
|
||||
"protractor": {
|
||||
"version": "4.0.11",
|
||||
"from": "protractor@4.0.11",
|
||||
"resolved": "https://registry.npmjs.org/protractor/-/protractor-4.0.11.tgz",
|
||||
"version": "4.0.14",
|
||||
"from": "protractor@4.0.14",
|
||||
"resolved": "https://registry.npmjs.org/protractor/-/protractor-4.0.14.tgz",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/jasmine": {
|
||||
"version": "2.5.37",
|
||||
"version": "2.5.47",
|
||||
"from": "@types/jasmine@>=2.5.36 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.5.37.tgz",
|
||||
"resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.5.47.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "6.0.46",
|
||||
"version": "6.0.68",
|
||||
"from": "@types/node@>=6.0.46 <7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.46.tgz",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.68.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"@types/selenium-webdriver": {
|
||||
"version": "2.53.37",
|
||||
"from": "@types/selenium-webdriver@2.53.37",
|
||||
"resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-2.53.37.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"caseless": {
|
||||
"version": "0.12.0",
|
||||
"from": "caseless@>=0.12.0 <0.13.0",
|
||||
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"glob": {
|
||||
@ -7658,34 +7706,40 @@
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"har-validator": {
|
||||
"version": "4.2.1",
|
||||
"from": "har-validator@>=4.2.1 <4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.24.0",
|
||||
"from": "mime-db@~1.24.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.24.0.tgz",
|
||||
"version": "1.27.0",
|
||||
"from": "mime-db@>=1.27.0 <1.28.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.12",
|
||||
"version": "2.1.15",
|
||||
"from": "mime-types@>=2.1.7 <2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.12.tgz",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.3.0",
|
||||
"from": "qs@>=6.3.0 <6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.3.0.tgz",
|
||||
"version": "6.4.0",
|
||||
"from": "qs@>=6.4.0 <6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"request": {
|
||||
"version": "2.78.0",
|
||||
"version": "2.81.0",
|
||||
"from": "request@>=2.78.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.78.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.5.4",
|
||||
"version": "2.6.1",
|
||||
"from": "rimraf@>=2.5.2 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"saucelabs": {
|
||||
@ -7706,10 +7760,22 @@
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"from": "tunnel-agent@>=0.6.0 <0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.0.1",
|
||||
"from": "uuid@>=3.0.0 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"webdriver-manager": {
|
||||
"version": "10.2.8",
|
||||
"from": "webdriver-manager@>=10.2.8 <11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-10.2.8.tgz",
|
||||
"version": "10.3.0",
|
||||
"from": "webdriver-manager@>=10.3.0 <11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-10.3.0.tgz",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
@ -8196,6 +8262,12 @@
|
||||
"from": "rxjs@5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.0.1.tgz"
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.0.1",
|
||||
"from": "safe-buffer@>=5.0.1 <6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"sander": {
|
||||
"version": "0.5.1",
|
||||
"from": "sander@>=0.5.0 <0.6.0",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-srcs",
|
||||
"version": "4.0.2",
|
||||
"version": "4.0.3",
|
||||
"private": true,
|
||||
"branchPattern": "2.0.*",
|
||||
"description": "Angular - a web framework for modern web apps",
|
||||
@ -77,7 +77,7 @@
|
||||
"nan": "^2.4.0",
|
||||
"node-uuid": "1.4.x",
|
||||
"parse5": "^3.0.1",
|
||||
"protractor": "^4.0.11",
|
||||
"protractor": "^4.0.14",
|
||||
"react": "^0.14.0",
|
||||
"rewire": "^2.3.3",
|
||||
"rho": "^0.3.0",
|
||||
|
@ -24,6 +24,7 @@ export class ChromeDriverExtension extends WebDriverExtension {
|
||||
static PROVIDERS = [ChromeDriverExtension];
|
||||
|
||||
private _majorChromeVersion: number;
|
||||
private _firstRun = true;
|
||||
|
||||
constructor(private _driver: WebDriverAdapter, @Inject(Options.USER_AGENT) userAgent: string) {
|
||||
super();
|
||||
@ -48,6 +49,12 @@ export class ChromeDriverExtension extends WebDriverExtension {
|
||||
gc() { return this._driver.executeScript('window.gc()'); }
|
||||
|
||||
timeBegin(name: string): Promise<any> {
|
||||
if (this._firstRun) {
|
||||
this._firstRun = false;
|
||||
// Before the first run, read out the existing performance logs
|
||||
// so that the chrome buffer does not fill up.
|
||||
this._driver.logs('performance');
|
||||
}
|
||||
return this._driver.executeScript(`console.time('${name}');`);
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/src/testing_internal';
|
||||
import {AsyncTestCompleter, describe, expect, iit, inject, it} from '@angular/core/testing/src/testing_internal';
|
||||
|
||||
import {ChromeDriverExtension, Options, ReflectiveInjector, WebDriverAdapter, WebDriverExtension} from '../../index';
|
||||
import {TraceEventFactory} from '../trace_event_factory';
|
||||
@ -61,14 +61,29 @@ export function main() {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should mark the timeline via console.time()',
|
||||
it('should clear the perf logs and mark the timeline via console.time() on the first call',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createExtension().timeBegin('someName').then((_) => {
|
||||
expect(log).toEqual([['executeScript', `console.time('someName');`]]);
|
||||
createExtension().timeBegin('someName').then(() => {
|
||||
expect(log).toEqual(
|
||||
[['logs', 'performance'], ['executeScript', `console.time('someName');`]]);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should mark the timeline via console.time() on the second call',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
const ext = createExtension();
|
||||
ext.timeBegin('someName')
|
||||
.then((_) => {
|
||||
log.splice(0, log.length);
|
||||
ext.timeBegin('someName');
|
||||
})
|
||||
.then(() => {
|
||||
expect(log).toEqual([['executeScript', `console.time('someName');`]]);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should mark the timeline via console.timeEnd()',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createExtension().timeEnd('someName', null).then((_) => {
|
||||
|
@ -63,6 +63,33 @@ const EXPECTED_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
</xliff>
|
||||
`;
|
||||
|
||||
const EXPECTED_XLIFF2 = `<?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="8136548302122759730">
|
||||
<notes>
|
||||
<note category="description">desc</note>
|
||||
<note category="meaning">meaning</note>
|
||||
</notes>
|
||||
<segment>
|
||||
<source>translate me</source>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="3492007542396725315">
|
||||
<segment>
|
||||
<source>Welcome</source>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="126808141597411718">
|
||||
<segment>
|
||||
<source>other-3rdP-component
|
||||
multi-lines</source>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>
|
||||
`;
|
||||
|
||||
describe('template i18n extraction output', () => {
|
||||
const outDir = '';
|
||||
const genDir = 'out';
|
||||
@ -81,6 +108,13 @@ describe('template i18n extraction output', () => {
|
||||
expect(xlf).toEqual(EXPECTED_XLIFF);
|
||||
});
|
||||
|
||||
it('should extract i18n messages as xliff version 2.0', () => {
|
||||
const xlfOutput = path.join(outDir, 'messages.xliff2.xlf');
|
||||
expect(fs.existsSync(xlfOutput)).toBeTruthy();
|
||||
const xlf = fs.readFileSync(xlfOutput, {encoding: 'utf-8'});
|
||||
expect(xlf).toEqual(EXPECTED_XLIFF2);
|
||||
});
|
||||
|
||||
it('should not emit js', () => {
|
||||
const genOutput = path.join(genDir, '');
|
||||
expect(fs.existsSync(genOutput)).toBeFalsy();
|
||||
|
@ -9,7 +9,7 @@
|
||||
"ng-xi18n": "./src/extract_i18n.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/tsc-wrapped": "4.0.2",
|
||||
"@angular/tsc-wrapped": "4.0.3",
|
||||
"reflect-metadata": "^0.1.2",
|
||||
"minimist": "^1.2.0"
|
||||
},
|
||||
|
@ -24,7 +24,7 @@ const GENERATED_META_FILES = /\.json$/;
|
||||
const PREAMBLE = `/**
|
||||
* @fileoverview This file is generated by the Angular template compiler.
|
||||
* Do not edit.
|
||||
* @suppress {suspiciousCode,uselessCode,missingProperties}
|
||||
* @suppress {suspiciousCode,uselessCode,missingProperties,missingOverride}
|
||||
*/
|
||||
/* tslint:disable */
|
||||
|
||||
|
@ -34,7 +34,7 @@ export class Extractor {
|
||||
const promiseBundle = this.extractBundle();
|
||||
|
||||
return promiseBundle.then(bundle => {
|
||||
const content = this.serialize(bundle, ext);
|
||||
const content = this.serialize(bundle, formatName);
|
||||
const dstFile = outFile || `messages.${ext}`;
|
||||
const dstPath = path.join(this.options.genDir, dstFile);
|
||||
this.host.writeFile(dstPath, content, false);
|
||||
@ -48,14 +48,20 @@ export class Extractor {
|
||||
return this.ngExtractor.extract(files);
|
||||
}
|
||||
|
||||
serialize(bundle: compiler.MessageBundle, ext: string): string {
|
||||
serialize(bundle: compiler.MessageBundle, formatName: string): string {
|
||||
const format = formatName.toLowerCase();
|
||||
let serializer: compiler.Serializer;
|
||||
|
||||
switch (ext) {
|
||||
switch (format) {
|
||||
case 'xmb':
|
||||
serializer = new compiler.Xmb();
|
||||
break;
|
||||
case 'xliff2':
|
||||
case 'xlf2':
|
||||
serializer = new compiler.Xliff2();
|
||||
break;
|
||||
case 'xlf':
|
||||
case 'xliff':
|
||||
default:
|
||||
serializer = new compiler.Xliff();
|
||||
}
|
||||
@ -66,10 +72,18 @@ export class Extractor {
|
||||
getExtension(formatName: string): string {
|
||||
const format = (formatName || 'xlf').toLowerCase();
|
||||
|
||||
if (format === 'xmb') return 'xmb';
|
||||
if (format === 'xlf' || format === 'xlif' || format === 'xliff') return 'xlf';
|
||||
switch (format) {
|
||||
case 'xmb':
|
||||
return 'xmb';
|
||||
case 'xlf':
|
||||
case 'xlif':
|
||||
case 'xliff':
|
||||
case 'xlf2':
|
||||
case 'xliff2':
|
||||
return 'xlf';
|
||||
}
|
||||
|
||||
throw new Error('Unsupported format "${formatName}"');
|
||||
throw new Error(`Unsupported format "${formatName}"`);
|
||||
}
|
||||
|
||||
static create(
|
||||
|
@ -584,7 +584,7 @@ export class StaticReflector implements ɵReflectorReader {
|
||||
return simplifyCall(staticSymbol, targetFunction, argExpressions);
|
||||
}
|
||||
}
|
||||
break;
|
||||
return IGNORE;
|
||||
case 'error':
|
||||
let message = produceErrorMessage(expression);
|
||||
if (expression['line']) {
|
||||
|
@ -388,7 +388,8 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||
visitAll(asts: cdAst.AST[], mode: _Mode): any { return asts.map(ast => this.visit(ast, mode)); }
|
||||
|
||||
visitQuote(ast: cdAst.Quote, mode: _Mode): any {
|
||||
throw new Error('Quotes are not supported for evaluation!');
|
||||
throw new Error(`Quotes are not supported for evaluation!
|
||||
Statement: ${ast.uninterpretedExpression} located at ${ast.location}`);
|
||||
}
|
||||
|
||||
private visit(ast: cdAst.AST, mode: _Mode): any {
|
||||
|
@ -13,6 +13,7 @@ import {ParseTreeResult} from '../ml_parser/parser';
|
||||
import {mergeTranslations} from './extractor_merger';
|
||||
import {Serializer} from './serializers/serializer';
|
||||
import {Xliff} from './serializers/xliff';
|
||||
import {Xliff2} from './serializers/xliff2';
|
||||
import {Xmb} from './serializers/xmb';
|
||||
import {Xtb} from './serializers/xtb';
|
||||
import {TranslationBundle} from './translation_bundle';
|
||||
@ -62,6 +63,9 @@ function createSerializer(format?: string): Serializer {
|
||||
return new Xmb();
|
||||
case 'xtb':
|
||||
return new Xtb();
|
||||
case 'xliff2':
|
||||
case 'xlf2':
|
||||
return new Xliff2();
|
||||
case 'xliff':
|
||||
case 'xlf':
|
||||
default:
|
||||
|
@ -11,5 +11,6 @@ export {I18NHtmlParser} from './i18n_html_parser';
|
||||
export {MessageBundle} from './message_bundle';
|
||||
export {Serializer} from './serializers/serializer';
|
||||
export {Xliff} from './serializers/xliff';
|
||||
export {Xliff2} from './serializers/xliff2';
|
||||
export {Xmb} from './serializers/xmb';
|
||||
export {Xtb} from './serializers/xtb';
|
||||
|
370
packages/compiler/src/i18n/serializers/xliff2.ts
Normal file
370
packages/compiler/src/i18n/serializers/xliff2.ts
Normal file
@ -0,0 +1,370 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as ml from '../../ml_parser/ast';
|
||||
import {XmlParser} from '../../ml_parser/xml_parser';
|
||||
import {decimalDigest} from '../digest';
|
||||
import * as i18n from '../i18n_ast';
|
||||
import {I18nError} from '../parse_util';
|
||||
|
||||
import {Serializer} from './serializer';
|
||||
import * as xml from './xml_helper';
|
||||
|
||||
const _VERSION = '2.0';
|
||||
const _XMLNS = 'urn:oasis:names:tc:xliff:document:2.0';
|
||||
// TODO(vicb): make this a param (s/_/-/)
|
||||
const _DEFAULT_SOURCE_LANG = 'en';
|
||||
const _PLACEHOLDER_TAG = 'ph';
|
||||
const _PLACEHOLDER_SPANNING_TAG = 'pc';
|
||||
|
||||
const _XLIFF_TAG = 'xliff';
|
||||
const _SOURCE_TAG = 'source';
|
||||
const _TARGET_TAG = 'target';
|
||||
const _UNIT_TAG = 'unit';
|
||||
|
||||
// http://docs.oasis-open.org/xliff/xliff-core/v2.0/os/xliff-core-v2.0-os.html
|
||||
export class Xliff2 extends Serializer {
|
||||
write(messages: i18n.Message[], locale: string|null): string {
|
||||
const visitor = new _WriteVisitor();
|
||||
const units: xml.Node[] = [];
|
||||
|
||||
messages.forEach(message => {
|
||||
const unit = new xml.Tag(_UNIT_TAG, {id: message.id});
|
||||
|
||||
if (message.description || message.meaning) {
|
||||
const notes = new xml.Tag('notes');
|
||||
if (message.description) {
|
||||
notes.children.push(
|
||||
new xml.CR(8),
|
||||
new xml.Tag('note', {category: 'description'}, [new xml.Text(message.description)]));
|
||||
}
|
||||
|
||||
if (message.meaning) {
|
||||
notes.children.push(
|
||||
new xml.CR(8),
|
||||
new xml.Tag('note', {category: 'meaning'}, [new xml.Text(message.meaning)]));
|
||||
}
|
||||
|
||||
notes.children.push(new xml.CR(6));
|
||||
unit.children.push(new xml.CR(6), notes);
|
||||
}
|
||||
|
||||
const segment = new xml.Tag('segment');
|
||||
|
||||
segment.children.push(
|
||||
new xml.CR(8), new xml.Tag(_SOURCE_TAG, {}, visitor.serialize(message.nodes)),
|
||||
new xml.CR(6));
|
||||
|
||||
unit.children.push(new xml.CR(6), segment, new xml.CR(4));
|
||||
|
||||
units.push(new xml.CR(4), unit);
|
||||
});
|
||||
|
||||
const file =
|
||||
new xml.Tag('file', {'original': 'ng.template', id: 'ngi18n'}, [...units, new xml.CR(2)]);
|
||||
|
||||
const xliff = new xml.Tag(
|
||||
_XLIFF_TAG, {version: _VERSION, xmlns: _XMLNS, srcLang: locale || _DEFAULT_SOURCE_LANG},
|
||||
[new xml.CR(2), file, new xml.CR()]);
|
||||
|
||||
return xml.serialize([
|
||||
new xml.Declaration({version: '1.0', encoding: 'UTF-8'}), new xml.CR(), xliff, new xml.CR()
|
||||
]);
|
||||
}
|
||||
|
||||
load(content: string, url: string):
|
||||
{locale: string, i18nNodesByMsgId: {[msgId: string]: i18n.Node[]}} {
|
||||
// xliff to xml nodes
|
||||
const xliff2Parser = new Xliff2Parser();
|
||||
const {locale, msgIdToHtml, errors} = xliff2Parser.parse(content, url);
|
||||
|
||||
// xml nodes to i18n nodes
|
||||
const i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {};
|
||||
const converter = new XmlToI18n();
|
||||
|
||||
Object.keys(msgIdToHtml).forEach(msgId => {
|
||||
const {i18nNodes, errors: e} = converter.convert(msgIdToHtml[msgId], url);
|
||||
errors.push(...e);
|
||||
i18nNodesByMsgId[msgId] = i18nNodes;
|
||||
});
|
||||
|
||||
if (errors.length) {
|
||||
throw new Error(`xliff2 parse errors:\n${errors.join('\n')}`);
|
||||
}
|
||||
|
||||
return {locale: locale !, i18nNodesByMsgId};
|
||||
}
|
||||
|
||||
digest(message: i18n.Message): string { return decimalDigest(message); }
|
||||
}
|
||||
|
||||
class _WriteVisitor implements i18n.Visitor {
|
||||
private _nextPlaceholderId: number;
|
||||
|
||||
visitText(text: i18n.Text, context?: any): xml.Node[] { return [new xml.Text(text.value)]; }
|
||||
|
||||
visitContainer(container: i18n.Container, context?: any): xml.Node[] {
|
||||
const nodes: xml.Node[] = [];
|
||||
container.children.forEach((node: i18n.Node) => nodes.push(...node.visit(this)));
|
||||
return nodes;
|
||||
}
|
||||
|
||||
visitIcu(icu: i18n.Icu, context?: any): xml.Node[] {
|
||||
const nodes = [new xml.Text(`{${icu.expressionPlaceholder}, ${icu.type}, `)];
|
||||
|
||||
Object.keys(icu.cases).forEach((c: string) => {
|
||||
nodes.push(new xml.Text(`${c} {`), ...icu.cases[c].visit(this), new xml.Text(`} `));
|
||||
});
|
||||
|
||||
nodes.push(new xml.Text(`}`));
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
visitTagPlaceholder(ph: i18n.TagPlaceholder, context?: any): xml.Node[] {
|
||||
const type = getTypeForTag(ph.tag);
|
||||
|
||||
if (ph.isVoid) {
|
||||
const tagPh = new xml.Tag(_PLACEHOLDER_TAG, {
|
||||
id: (this._nextPlaceholderId++).toString(),
|
||||
equiv: ph.startName,
|
||||
type: type,
|
||||
disp: `<${ph.tag}/>`,
|
||||
});
|
||||
return [tagPh];
|
||||
}
|
||||
|
||||
const tagPc = new xml.Tag(_PLACEHOLDER_SPANNING_TAG, {
|
||||
id: (this._nextPlaceholderId++).toString(),
|
||||
equivStart: ph.startName,
|
||||
equivEnd: ph.closeName,
|
||||
type: type,
|
||||
dispStart: `<${ph.tag}>`,
|
||||
dispEnd: `</${ph.tag}>`,
|
||||
});
|
||||
const nodes: xml.Node[] = [].concat(...ph.children.map(node => node.visit(this)));
|
||||
if (nodes.length) {
|
||||
nodes.forEach((node: xml.Node) => tagPc.children.push(node));
|
||||
} else {
|
||||
tagPc.children.push(new xml.Text(''));
|
||||
}
|
||||
|
||||
return [tagPc];
|
||||
}
|
||||
|
||||
visitPlaceholder(ph: i18n.Placeholder, context?: any): xml.Node[] {
|
||||
return [new xml.Tag(_PLACEHOLDER_TAG, {
|
||||
id: (this._nextPlaceholderId++).toString(),
|
||||
equiv: ph.name,
|
||||
disp: `{{${ph.value}}}`,
|
||||
})];
|
||||
}
|
||||
|
||||
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): xml.Node[] {
|
||||
return [new xml.Tag(_PLACEHOLDER_TAG, {id: (this._nextPlaceholderId++).toString()})];
|
||||
}
|
||||
|
||||
serialize(nodes: i18n.Node[]): xml.Node[] {
|
||||
this._nextPlaceholderId = 0;
|
||||
return [].concat(...nodes.map(node => node.visit(this)));
|
||||
}
|
||||
}
|
||||
|
||||
// Extract messages as xml nodes from the xliff file
|
||||
class Xliff2Parser implements ml.Visitor {
|
||||
private _unitMlString: string|null;
|
||||
private _errors: I18nError[];
|
||||
private _msgIdToHtml: {[msgId: string]: string};
|
||||
private _locale: string|null = null;
|
||||
|
||||
parse(xliff: string, url: string) {
|
||||
this._unitMlString = null;
|
||||
this._msgIdToHtml = {};
|
||||
|
||||
const xml = new XmlParser().parse(xliff, url, false);
|
||||
|
||||
this._errors = xml.errors;
|
||||
ml.visitAll(this, xml.rootNodes, null);
|
||||
|
||||
return {
|
||||
msgIdToHtml: this._msgIdToHtml,
|
||||
errors: this._errors,
|
||||
locale: this._locale,
|
||||
};
|
||||
}
|
||||
|
||||
visitElement(element: ml.Element, context: any): any {
|
||||
switch (element.name) {
|
||||
case _UNIT_TAG:
|
||||
this._unitMlString = null;
|
||||
const idAttr = element.attrs.find((attr) => attr.name === 'id');
|
||||
if (!idAttr) {
|
||||
this._addError(element, `<${_UNIT_TAG}> misses the "id" attribute`);
|
||||
} else {
|
||||
const id = idAttr.value;
|
||||
if (this._msgIdToHtml.hasOwnProperty(id)) {
|
||||
this._addError(element, `Duplicated translations for msg ${id}`);
|
||||
} else {
|
||||
ml.visitAll(this, element.children, null);
|
||||
if (typeof this._unitMlString === 'string') {
|
||||
this._msgIdToHtml[id] = this._unitMlString;
|
||||
} else {
|
||||
this._addError(element, `Message ${id} misses a translation`);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case _SOURCE_TAG:
|
||||
// ignore source message
|
||||
break;
|
||||
|
||||
case _TARGET_TAG:
|
||||
const innerTextStart = element.startSourceSpan !.end.offset;
|
||||
const innerTextEnd = element.endSourceSpan !.start.offset;
|
||||
const content = element.startSourceSpan !.start.file.content;
|
||||
const innerText = content.slice(innerTextStart, innerTextEnd);
|
||||
this._unitMlString = innerText;
|
||||
break;
|
||||
|
||||
case _XLIFF_TAG:
|
||||
const localeAttr = element.attrs.find((attr) => attr.name === 'trgLang');
|
||||
if (localeAttr) {
|
||||
this._locale = localeAttr.value;
|
||||
}
|
||||
|
||||
const versionAttr = element.attrs.find((attr) => attr.name === 'version');
|
||||
if (versionAttr) {
|
||||
const version = versionAttr.value;
|
||||
if (version !== '2.0') {
|
||||
this._addError(
|
||||
element,
|
||||
`The XLIFF file version ${version} is not compatible with XLIFF 2.0 serializer`);
|
||||
} else {
|
||||
ml.visitAll(this, element.children, null);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ml.visitAll(this, element.children, null);
|
||||
}
|
||||
}
|
||||
|
||||
visitAttribute(attribute: ml.Attribute, context: any): any {}
|
||||
|
||||
visitText(text: ml.Text, context: any): any {}
|
||||
|
||||
visitComment(comment: ml.Comment, context: any): any {}
|
||||
|
||||
visitExpansion(expansion: ml.Expansion, context: any): any {}
|
||||
|
||||
visitExpansionCase(expansionCase: ml.ExpansionCase, context: any): any {}
|
||||
|
||||
private _addError(node: ml.Node, message: string): void {
|
||||
this._errors.push(new I18nError(node.sourceSpan, message));
|
||||
}
|
||||
}
|
||||
|
||||
// Convert ml nodes (xliff syntax) to i18n nodes
|
||||
class XmlToI18n implements ml.Visitor {
|
||||
private _errors: I18nError[];
|
||||
|
||||
convert(message: string, url: string) {
|
||||
const xmlIcu = new XmlParser().parse(message, url, true);
|
||||
this._errors = xmlIcu.errors;
|
||||
|
||||
const i18nNodes = this._errors.length > 0 || xmlIcu.rootNodes.length == 0 ?
|
||||
[] :
|
||||
[].concat(...ml.visitAll(this, xmlIcu.rootNodes));
|
||||
|
||||
return {
|
||||
i18nNodes,
|
||||
errors: this._errors,
|
||||
};
|
||||
}
|
||||
|
||||
visitText(text: ml.Text, context: any) { return new i18n.Text(text.value, text.sourceSpan); }
|
||||
|
||||
visitElement(el: ml.Element, context: any): i18n.Node[]|null {
|
||||
switch (el.name) {
|
||||
case _PLACEHOLDER_TAG:
|
||||
const nameAttr = el.attrs.find((attr) => attr.name === 'equiv');
|
||||
if (nameAttr) {
|
||||
return [new i18n.Placeholder('', nameAttr.value, el.sourceSpan)];
|
||||
}
|
||||
|
||||
this._addError(el, `<${_PLACEHOLDER_TAG}> misses the "equiv" attribute`);
|
||||
break;
|
||||
case _PLACEHOLDER_SPANNING_TAG:
|
||||
const startAttr = el.attrs.find((attr) => attr.name === 'equivStart');
|
||||
const endAttr = el.attrs.find((attr) => attr.name === 'equivEnd');
|
||||
|
||||
if (!startAttr) {
|
||||
this._addError(el, `<${_PLACEHOLDER_TAG}> misses the "equivStart" attribute`);
|
||||
} else if (!endAttr) {
|
||||
this._addError(el, `<${_PLACEHOLDER_TAG}> misses the "equivEnd" attribute`);
|
||||
} else {
|
||||
const startId = startAttr.value;
|
||||
const endId = endAttr.value;
|
||||
|
||||
const nodes: i18n.Node[] = [];
|
||||
|
||||
return nodes.concat(
|
||||
new i18n.Placeholder('', startId, el.sourceSpan),
|
||||
...el.children.map(node => node.visit(this, null)),
|
||||
new i18n.Placeholder('', endId, el.sourceSpan));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
this._addError(el, `Unexpected tag`);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
visitExpansion(icu: ml.Expansion, context: any) {
|
||||
const caseMap: {[value: string]: i18n.Node} = {};
|
||||
|
||||
ml.visitAll(this, icu.cases).forEach((c: any) => {
|
||||
caseMap[c.value] = new i18n.Container(c.nodes, icu.sourceSpan);
|
||||
});
|
||||
|
||||
return new i18n.Icu(icu.switchValue, icu.type, caseMap, icu.sourceSpan);
|
||||
}
|
||||
|
||||
visitExpansionCase(icuCase: ml.ExpansionCase, context: any): any {
|
||||
return {
|
||||
value: icuCase.value,
|
||||
nodes: [].concat(...ml.visitAll(this, icuCase.expression)),
|
||||
};
|
||||
}
|
||||
|
||||
visitComment(comment: ml.Comment, context: any) {}
|
||||
|
||||
visitAttribute(attribute: ml.Attribute, context: any) {}
|
||||
|
||||
private _addError(node: ml.Node, message: string): void {
|
||||
this._errors.push(new I18nError(node.sourceSpan, message));
|
||||
}
|
||||
}
|
||||
|
||||
function getTypeForTag(tag: string): string {
|
||||
switch (tag.toLowerCase()) {
|
||||
case 'br':
|
||||
case 'b':
|
||||
case 'i':
|
||||
case 'u':
|
||||
return 'fmt';
|
||||
case 'img':
|
||||
return 'image';
|
||||
case 'a':
|
||||
return 'link';
|
||||
default:
|
||||
return 'other';
|
||||
}
|
||||
}
|
@ -140,13 +140,13 @@ export class TemplateParser {
|
||||
return this.tryParseHtml(
|
||||
this.expandHtml(this._htmlParser.parse(
|
||||
template, templateUrl, true, this.getInterpolationConfig(component))),
|
||||
component, template, directives, pipes, schemas, templateUrl);
|
||||
component, directives, pipes, schemas);
|
||||
}
|
||||
|
||||
tryParseHtml(
|
||||
htmlAstWithErrors: ParseTreeResult, component: CompileDirectiveMetadata, template: string,
|
||||
directives: CompileDirectiveSummary[], pipes: CompilePipeSummary[], schemas: SchemaMetadata[],
|
||||
templateUrl: string): TemplateParseResult {
|
||||
htmlAstWithErrors: ParseTreeResult, component: CompileDirectiveMetadata,
|
||||
directives: CompileDirectiveSummary[], pipes: CompilePipeSummary[],
|
||||
schemas: SchemaMetadata[]): TemplateParseResult {
|
||||
let result: TemplateAst[];
|
||||
const errors = htmlAstWithErrors.errors;
|
||||
const usedPipes: CompilePipeSummary[] = [];
|
||||
|
@ -546,6 +546,30 @@ describe('StaticReflector', () => {
|
||||
expect(annotation.providers).toEqual([1, 2, 3, 4, 5, 6, 7]);
|
||||
});
|
||||
|
||||
it('should ignore unresolved calls', () => {
|
||||
const data = Object.create(DEFAULT_TEST_DATA);
|
||||
const file = '/tmp/src/invalid-component.ts';
|
||||
data[file] = `
|
||||
import {Component} from '@angular/core';
|
||||
import {unknown} from 'unresolved';
|
||||
|
||||
@Component({
|
||||
selector: 'tmp',
|
||||
template: () => {},
|
||||
providers: [triggers()]
|
||||
})
|
||||
export class BadComponent {
|
||||
|
||||
}
|
||||
`;
|
||||
init(data, [], () => {}, {verboseInvalidExpression: true});
|
||||
|
||||
const badComponent = reflector.getStaticSymbol(file, 'BadComponent');
|
||||
const annotations = reflector.annotations(badComponent);
|
||||
const annotation = annotations[0];
|
||||
expect(annotation.providers).toEqual([]);
|
||||
});
|
||||
|
||||
describe('inheritance', () => {
|
||||
class ClassDecorator {
|
||||
constructor(public value: any) {}
|
||||
|
@ -436,6 +436,9 @@ export class MockStaticSymbolResolverHost implements StaticSymbolResolverHost {
|
||||
}
|
||||
return baseName + '.d.ts';
|
||||
}
|
||||
if (modulePath == 'unresolved') {
|
||||
return undefined;
|
||||
}
|
||||
return '/tmp/' + modulePath + '.d.ts';
|
||||
}
|
||||
|
||||
|
336
packages/compiler/test/i18n/serializers/xliff2_spec.ts
Normal file
336
packages/compiler/test/i18n/serializers/xliff2_spec.ts
Normal file
@ -0,0 +1,336 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {escapeRegExp} from '@angular/compiler/src/util';
|
||||
|
||||
import {serializeNodes} from '../../../src/i18n/digest';
|
||||
import {MessageBundle} from '../../../src/i18n/message_bundle';
|
||||
import {Xliff2} from '../../../src/i18n/serializers/xliff2';
|
||||
import {HtmlParser} from '../../../src/ml_parser/html_parser';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG} from '../../../src/ml_parser/interpolation_config';
|
||||
|
||||
const HTML = `
|
||||
<p i18n-title title="translatable attribute">not translatable</p>
|
||||
<p i18n>translatable element <b>with placeholders</b> {{ interpolation}}</p>
|
||||
<!-- i18n -->{ count, plural, =0 {<p>test</p>}}<!-- /i18n -->
|
||||
<p i18n="m|d@@i">foo</p>
|
||||
<p i18n="nested"><b><u>{{interpolation}} Text</u></b></p>
|
||||
<p i18n="ph names"><br><img src="1.jpg"><img src="2.jpg"></p>
|
||||
<p i18n="empty element">hello <span></span></p>
|
||||
<p i18n="@@baz">{ count, plural, =0 { { sex, select, other {<p>deeply nested</p>}} }}</p>
|
||||
<p i18n>{ count, plural, =0 { { sex, select, other {<p>deeply nested</p>}} }}</p>
|
||||
`;
|
||||
|
||||
const WRITE_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="1933478729560469763">
|
||||
<segment>
|
||||
<source>translatable attribute</source>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="7056919470098446707">
|
||||
<segment>
|
||||
<source>translatable element <pc id="0" equivStart="START_BOLD_TEXT" equivEnd="CLOSE_BOLD_TEXT" type="fmt" dispStart="<b>" dispEnd="</b>">with placeholders</pc> <ph id="1" equiv="INTERPOLATION" disp="{{ interpolation}}"/></source>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="2981514368455622387">
|
||||
<segment>
|
||||
<source>{VAR_PLURAL, plural, =0 {<pc id="0" equivStart="START_PARAGRAPH" equivEnd="CLOSE_PARAGRAPH" type="other" dispStart="<p>" dispEnd="</p>">test</pc>} }</source>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="i">
|
||||
<notes>
|
||||
<note category="description">d</note>
|
||||
<note category="meaning">m</note>
|
||||
</notes>
|
||||
<segment>
|
||||
<source>foo</source>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="6440235004920703622">
|
||||
<notes>
|
||||
<note category="description">nested</note>
|
||||
</notes>
|
||||
<segment>
|
||||
<source><pc id="0" equivStart="START_BOLD_TEXT" equivEnd="CLOSE_BOLD_TEXT" type="fmt" dispStart="<b>" dispEnd="</b>"><pc id="1" equivStart="START_UNDERLINED_TEXT" equivEnd="CLOSE_UNDERLINED_TEXT" type="fmt" dispStart="<u>" dispEnd="</u>"><ph id="2" equiv="INTERPOLATION" disp="{{interpolation}}"/> Text</pc></pc></source>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="8779402634269838862">
|
||||
<notes>
|
||||
<note category="description">ph names</note>
|
||||
</notes>
|
||||
<segment>
|
||||
<source><ph id="0" equiv="LINE_BREAK" type="fmt" disp="<br/>"/><ph id="1" equiv="TAG_IMG" type="image" disp="<img/>"/><ph id="2" equiv="TAG_IMG_1" type="image" disp="<img/>"/></source>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="6536355551500405293">
|
||||
<notes>
|
||||
<note category="description">empty element</note>
|
||||
</notes>
|
||||
<segment>
|
||||
<source>hello <pc id="0" equivStart="START_TAG_SPAN" equivEnd="CLOSE_TAG_SPAN" type="other" dispStart="<span>" dispEnd="</span>"></pc></source>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="baz">
|
||||
<segment>
|
||||
<source>{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {<pc id="0" equivStart="START_PARAGRAPH" equivEnd="CLOSE_PARAGRAPH" type="other" dispStart="<p>" dispEnd="</p>">deeply nested</pc>} } } }</source>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="2015957479576096115">
|
||||
<segment>
|
||||
<source>{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {<pc id="0" equivStart="START_PARAGRAPH" equivEnd="CLOSE_PARAGRAPH" type="other" dispStart="<p>" dispEnd="</p>">deeply nested</pc>} } } }</source>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>
|
||||
`;
|
||||
|
||||
const LOAD_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<xliff version="2.0" xmlns="urn:oasis:names:tc:xliff:document:2.0" srcLang="en" trgLang="fr">
|
||||
<file original="ng.template" id="ngi18n">
|
||||
<unit id="1933478729560469763">
|
||||
<segment>
|
||||
<source>translatable attribute</source>
|
||||
<target>etubirtta elbatalsnart</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="7056919470098446707">
|
||||
<segment>
|
||||
<source>translatable element <pc id="0" equivStart="START_BOLD_TEXT" equivEnd="CLOSE_BOLD_TEXT" type="fmt" dispStart="<b>" dispEnd="</b>">with placeholders</pc> <ph id="1" equiv="INTERPOLATION" disp="{{ interpolation}}"/></source>
|
||||
<target><ph id="1" equiv="INTERPOLATION" disp="{{ interpolation}}"/> <pc id="0" equivStart="START_BOLD_TEXT" equivEnd="CLOSE_BOLD_TEXT" type="fmt" dispStart="<b>" dispEnd="</b>">sredlohecalp htiw</pc> tnemele elbatalsnart</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="2981514368455622387">
|
||||
<segment>
|
||||
<source>{VAR_PLURAL, plural, =0 {<pc id="0" equivStart="START_PARAGRAPH" equivEnd="CLOSE_PARAGRAPH" type="other" dispStart="<p>" dispEnd="</p>">test</pc>} }</source>
|
||||
<target>{VAR_PLURAL, plural, =0 {<pc id="0" equivStart="START_PARAGRAPH" equivEnd="CLOSE_PARAGRAPH" type="other" dispStart="<p>" dispEnd="</p>">TEST</pc>} }</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="i">
|
||||
<notes>
|
||||
<note category="description">d</note>
|
||||
<note category="meaning">m</note>
|
||||
</notes>
|
||||
<segment>
|
||||
<source>foo</source>
|
||||
<target>oof</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="6440235004920703622">
|
||||
<notes>
|
||||
<note category="description">nested</note>
|
||||
</notes>
|
||||
<segment>
|
||||
<source><pc id="0" equivStart="START_BOLD_TEXT" equivEnd="CLOSE_BOLD_TEXT" type="fmt" dispStart="<b>" dispEnd="</b>"><pc id="1" equivStart="START_UNDERLINED_TEXT" equivEnd="CLOSE_UNDERLINED_TEXT" type="fmt" dispStart="<u>" dispEnd="</u>"><ph id="2" equiv="INTERPOLATION" disp="{{interpolation}}"/> Text</pc></pc></source>
|
||||
<target><pc id="0" equivStart="START_BOLD_TEXT" equivEnd="CLOSE_BOLD_TEXT" type="fmt" dispStart="<b>" dispEnd="</b>"><pc id="1" equivStart="START_UNDERLINED_TEXT" equivEnd="CLOSE_UNDERLINED_TEXT" type="fmt" dispStart="<u>" dispEnd="</u>">txeT <ph id="2" equiv="INTERPOLATION" disp="{{interpolation}}"/></pc></pc></target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="8779402634269838862">
|
||||
<notes>
|
||||
<note category="description">ph names</note>
|
||||
</notes>
|
||||
<segment>
|
||||
<source><ph id="0" equiv="LINE_BREAK" type="fmt" disp="<br/>"/><ph id="1" equiv="TAG_IMG" type="image" disp="<img/>"/><ph id="2" equiv="TAG_IMG_1" type="image" disp="<img/>"/></source>
|
||||
<target><ph id="2" equiv="TAG_IMG_1" type="image" disp="<img/>"/><ph id="1" equiv="TAG_IMG" type="image" disp="<img/>"/><ph id="0" equiv="LINE_BREAK" type="fmt" disp="<br/>"/></target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="6536355551500405293">
|
||||
<notes>
|
||||
<note category="description">empty element</note>
|
||||
</notes>
|
||||
<segment>
|
||||
<source>hello <pc id="0" equivStart="START_TAG_SPAN" equivEnd="CLOSE_TAG_SPAN" type="other" dispStart="<span>" dispEnd="</span>"></pc></source>
|
||||
<target><pc id="0" equivStart="START_TAG_SPAN" equivEnd="CLOSE_TAG_SPAN" type="other" dispStart="<span>" dispEnd="</span>"></pc> olleh</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="baz">
|
||||
<segment>
|
||||
<source>{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {<pc id="0" equivStart="START_PARAGRAPH" equivEnd="CLOSE_PARAGRAPH" type="other" dispStart="<p>" dispEnd="</p>">deeply nested</pc>} } } }</source>
|
||||
<target>{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {<pc id="0" equivStart="START_PARAGRAPH" equivEnd="CLOSE_PARAGRAPH" type="other" dispStart="<p>" dispEnd="</p>">profondément imbriqué</pc>} } } }</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="2015957479576096115">
|
||||
<segment>
|
||||
<source>{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {<pc id="0" equivStart="START_PARAGRAPH" equivEnd="CLOSE_PARAGRAPH" type="other" dispStart="<p>" dispEnd="</p>">deeply nested</pc>} } } }</source>
|
||||
<target>{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {<pc id="0" equivStart="START_PARAGRAPH" equivEnd="CLOSE_PARAGRAPH" type="other" dispStart="<p>" dispEnd="</p>">profondément imbriqué</pc>} } } }</target>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>
|
||||
`;
|
||||
|
||||
export function main(): void {
|
||||
const serializer = new Xliff2();
|
||||
|
||||
function toXliff(html: string, locale: string | null = null): string {
|
||||
const catalog = new MessageBundle(new HtmlParser, [], {}, locale);
|
||||
catalog.updateFromTemplate(html, '', DEFAULT_INTERPOLATION_CONFIG);
|
||||
return catalog.write(serializer);
|
||||
}
|
||||
|
||||
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(''));
|
||||
|
||||
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('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"/>]}}, ]}}',
|
||||
'2015957479576096115':
|
||||
'{VAR_PLURAL, plural, =0 {[{VAR_SELECT, select, other {[<ph name="START_PARAGRAPH"/>, profondément imbriqué, <ph name="CLOSE_PARAGRAPH"/>]}}, ]}}'
|
||||
});
|
||||
});
|
||||
|
||||
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>
|
||||
<trans-unit id="deadbeef">
|
||||
<source/>
|
||||
<target/>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>`;
|
||||
|
||||
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" ?>
|
||||
<xliff version="2.0" xmlns="urn:oasis:names:tc:xliff:document:2.0" srcLang="en">
|
||||
<file original="ng.template" id="ngi18n">
|
||||
<unit id="missingtarget">
|
||||
<segment>
|
||||
<source/>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>`;
|
||||
|
||||
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" ?>
|
||||
<xliff version="2.0" xmlns="urn:oasis:names:tc:xliff:document:2.0" srcLang="en">
|
||||
<file original="ng.template" id="ngi18n">
|
||||
<unit>
|
||||
<segment>
|
||||
<source/>
|
||||
<target/>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>`;
|
||||
|
||||
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" ?>
|
||||
<xliff version="2.0" xmlns="urn:oasis:names:tc:xliff:document:2.0" srcLang="en">
|
||||
<file original="ng.template" id="ngi18n">
|
||||
<unit id="deadbeef">
|
||||
<segment>
|
||||
<source/>
|
||||
<target/>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="deadbeef">
|
||||
<segment>
|
||||
<source/>
|
||||
<target/>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>`;
|
||||
|
||||
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" ?>
|
||||
<xliff version="2.0" xmlns="urn:oasis:names:tc:xliff:document:2.0" srcLang="en">
|
||||
<file original="ng.template" id="ngi18n">
|
||||
<unit id="deadbeef">
|
||||
<segment>
|
||||
<source/>
|
||||
<target><b>msg should contain only ph and pc tags</b></target>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>`;
|
||||
|
||||
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" ?>
|
||||
<xliff version="2.0" xmlns="urn:oasis:names:tc:xliff:document:2.0" srcLang="en">
|
||||
<file original="ng.template" id="ngi18n">
|
||||
<unit id="deadbeef">
|
||||
<segment>
|
||||
<source/>
|
||||
<target><ph/></target>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>`;
|
||||
|
||||
expect(() => {
|
||||
loadAsMap(XLIFF);
|
||||
}).toThrowError(new RegExp(escapeRegExp(`<ph> misses the "equiv" attribute`)));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -120,7 +120,6 @@ export class DefaultKeyValueDiffer<K, V> implements KeyValueDiffer<K, V>, KeyVal
|
||||
}
|
||||
|
||||
this._removalsHead = insertBefore;
|
||||
this._removalsTail = insertBefore;
|
||||
|
||||
for (let record = insertBefore; record !== null; record = record._nextRemoved) {
|
||||
if (record === this._mapHead) {
|
||||
@ -135,6 +134,10 @@ export class DefaultKeyValueDiffer<K, V> implements KeyValueDiffer<K, V>, KeyVal
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure tails have no next records from previous runs
|
||||
if (this._changesTail) this._changesTail._nextChanged = null;
|
||||
if (this._additionsTail) this._additionsTail._nextAdded = null;
|
||||
|
||||
return this.isDirty;
|
||||
}
|
||||
|
||||
@ -222,7 +225,7 @@ export class DefaultKeyValueDiffer<K, V> implements KeyValueDiffer<K, V>, KeyVal
|
||||
|
||||
this._changesHead = this._changesTail = null;
|
||||
this._additionsHead = this._additionsTail = null;
|
||||
this._removalsHead = this._removalsTail = null;
|
||||
this._removalsHead = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -254,28 +257,17 @@ export class DefaultKeyValueDiffer<K, V> implements KeyValueDiffer<K, V>, KeyVal
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
const items: any[] = [];
|
||||
const previous: any[] = [];
|
||||
const changes: any[] = [];
|
||||
const additions: any[] = [];
|
||||
const removals: any[] = [];
|
||||
let record: KeyValueChangeRecord_<K, V>|null;
|
||||
const items: string[] = [];
|
||||
const previous: string[] = [];
|
||||
const changes: string[] = [];
|
||||
const additions: string[] = [];
|
||||
const removals: string[] = [];
|
||||
|
||||
for (record = this._mapHead; record !== null; record = record._next) {
|
||||
items.push(stringify(record));
|
||||
}
|
||||
for (record = this._previousMapHead; record !== null; record = record._nextPrevious) {
|
||||
previous.push(stringify(record));
|
||||
}
|
||||
for (record = this._changesHead; record !== null; record = record._nextChanged) {
|
||||
changes.push(stringify(record));
|
||||
}
|
||||
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
|
||||
additions.push(stringify(record));
|
||||
}
|
||||
for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
|
||||
removals.push(stringify(record));
|
||||
}
|
||||
this.forEachItem(r => items.push(stringify(r)));
|
||||
this.forEachPreviousItem(r => previous.push(stringify(r)));
|
||||
this.forEachChangedItem(r => changes.push(stringify(r)));
|
||||
this.forEachAddedItem(r => additions.push(stringify(r)));
|
||||
this.forEachRemovedItem(r => removals.push(stringify(r)));
|
||||
|
||||
return 'map: ' + items.join(', ') + '\n' +
|
||||
'previous: ' + previous.join(', ') + '\n' +
|
||||
|
23
packages/core/src/testability/testability.externs.js
Normal file
23
packages/core/src/testability/testability.externs.js
Normal file
@ -0,0 +1,23 @@
|
||||
/** @externs */
|
||||
|
||||
/** @record @struct */
|
||||
function PublicTestability() {}
|
||||
|
||||
/**
|
||||
* @return {?}
|
||||
*/
|
||||
PublicTestability.prototype.isStable = function() {};
|
||||
|
||||
/**
|
||||
* @param {?} callback
|
||||
* @return {?}
|
||||
*/
|
||||
PublicTestability.prototype.whenStable = function(callback) {};
|
||||
|
||||
/**
|
||||
* @param {?} using
|
||||
* @param {?} provider
|
||||
* @param {?} exactMatch
|
||||
* @return {?}
|
||||
*/
|
||||
PublicTestability.prototype.findProviders = function(using, provider, exactMatch) {};
|
@ -194,6 +194,19 @@ export function main() {
|
||||
}));
|
||||
});
|
||||
|
||||
// https://github.com/angular/angular/issues/14997
|
||||
it('should work regardless key order', () => {
|
||||
differ.check({a: 1, b: 2});
|
||||
differ.check({b: 3, a: 2});
|
||||
differ.check({a: 1, b: 2});
|
||||
|
||||
expect(differ.toString()).toEqual(kvChangesAsString({
|
||||
map: ['a[2->1]', 'b[3->2]'],
|
||||
previous: ['b[3->2]', 'a[2->1]'],
|
||||
changes: ['a[2->1]', 'b[3->2]']
|
||||
}));
|
||||
});
|
||||
|
||||
it('should when the first item is moved', () => {
|
||||
differ.check({a: 'a', b: 'b'});
|
||||
differ.check({c: 'c', a: 'a'});
|
||||
|
@ -8,12 +8,12 @@
|
||||
|
||||
import {CompileMetadataResolver, CompileNgModuleMetadata, CompilerConfig, DomElementSchemaRegistry, HtmlParser, I18NHtmlParser, Lexer, NgAnalyzedModules, Parser, TemplateParser} from '@angular/compiler';
|
||||
|
||||
import {AstResult, AttrInfo, TemplateInfo} from './common';
|
||||
import {AstResult, TemplateInfo} from './common';
|
||||
import {getTemplateCompletions} from './completions';
|
||||
import {getDefinition} from './definitions';
|
||||
import {getDeclarationDiagnostics, getTemplateDiagnostics} from './diagnostics';
|
||||
import {getHover} from './hover';
|
||||
import {Completion, CompletionKind, Completions, Declaration, Declarations, Definition, Diagnostic, DiagnosticKind, Diagnostics, Hover, LanguageService, LanguageServiceHost, Location, PipeInfo, Pipes, Signature, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable, TemplateSource, TemplateSources} from './types';
|
||||
import {Completions, Definition, Diagnostic, DiagnosticKind, Diagnostics, Hover, LanguageService, LanguageServiceHost, Pipes, Span, TemplateSource} from './types';
|
||||
|
||||
|
||||
/**
|
||||
@ -126,8 +126,7 @@ class LanguageServiceImpl implements LanguageService {
|
||||
const pipes = ngModule.transitiveModule.pipes.map(
|
||||
p => this.host.resolver.getOrLoadPipeMetadata(p.reference).toSummary());
|
||||
const schemas = ngModule.schemas;
|
||||
const parseResult = parser.tryParseHtml(
|
||||
htmlResult, metadata, template.source, directives, pipes, schemas, '');
|
||||
const parseResult = parser.tryParseHtml(htmlResult, metadata, directives, pipes, schemas);
|
||||
result = {
|
||||
htmlAst: htmlResult.rootNodes,
|
||||
templateAst: parseResult.templateAst,
|
||||
|
@ -191,7 +191,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
|
||||
|
||||
let sourceFile = this.getSourceFile(fileName);
|
||||
if (sourceFile) {
|
||||
this.context = sourceFile.path;
|
||||
this.context = (sourceFile as any).path || sourceFile.fileName;
|
||||
ts.forEachChild(sourceFile, visit);
|
||||
}
|
||||
return result.length ? result : undefined;
|
||||
@ -807,10 +807,15 @@ class TypeWrapper implements Symbol {
|
||||
}
|
||||
|
||||
class SymbolWrapper implements Symbol {
|
||||
private symbol: ts.Symbol;
|
||||
private _tsType: ts.Type;
|
||||
private _members: SymbolTable;
|
||||
|
||||
constructor(private symbol: ts.Symbol, private context: TypeContext) {}
|
||||
constructor(symbol: ts.Symbol, private context: TypeContext) {
|
||||
this.symbol = symbol && context && (symbol.flags & ts.SymbolFlags.Alias) ?
|
||||
context.checker.getAliasedSymbol(symbol) :
|
||||
symbol;
|
||||
}
|
||||
|
||||
get name(): string { return this.symbol.name; }
|
||||
|
||||
|
@ -80,6 +80,8 @@ export class Parse5DomAdapter extends DomAdapter {
|
||||
setProperty(el: any, name: string, value: any) {
|
||||
if (name === 'innerHTML') {
|
||||
this.setInnerHTML(el, value);
|
||||
} else if (name === 'innerText') {
|
||||
this.setText(el, value);
|
||||
} else if (name === 'className') {
|
||||
el.attribs['class'] = el.className = value;
|
||||
} else {
|
||||
|
@ -54,15 +54,19 @@ class TitleApp {
|
||||
class TitleAppModule {
|
||||
}
|
||||
|
||||
@Component({selector: 'app', template: '{{text}}'})
|
||||
@Component({selector: 'app', template: '{{text}}<h1 [innerText]="h1"></h1>'})
|
||||
class MyAsyncServerApp {
|
||||
text = '';
|
||||
h1 = '';
|
||||
|
||||
@HostListener('window:scroll')
|
||||
track() { console.error('scroll'); }
|
||||
|
||||
ngOnInit() {
|
||||
Promise.resolve(null).then(() => setTimeout(() => { this.text = 'Works!'; }, 10));
|
||||
Promise.resolve(null).then(() => setTimeout(() => {
|
||||
this.text = 'Works!';
|
||||
this.h1 = 'fine';
|
||||
}, 10));
|
||||
}
|
||||
}
|
||||
|
||||
@ -353,7 +357,7 @@ export function main() {
|
||||
let doc: string;
|
||||
let called: boolean;
|
||||
let expectedOutput =
|
||||
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">Works!</app></body></html>';
|
||||
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">Works!<h1 innerText="fine">fine</h1></app></body></html>';
|
||||
|
||||
beforeEach(() => {
|
||||
// PlatformConfig takes in a parsed document so that it can be cached across requests.
|
||||
|
@ -10,6 +10,18 @@ import {Injectable, RenderComponentType, RendererType2, Type, ɵstringify as str
|
||||
import {RenderStore} from './render_store';
|
||||
|
||||
|
||||
/**
|
||||
* @experimental WebWorker support in Angular is currently experimental.
|
||||
*/
|
||||
export const enum SerializerTypes {
|
||||
// RendererType2
|
||||
RENDERER_TYPE_2,
|
||||
// Primitive types
|
||||
PRIMITIVE,
|
||||
// An object stored in a RenderStore
|
||||
RENDER_STORE_OBJECT,
|
||||
}
|
||||
|
||||
/**
|
||||
* Any type that does not need to be serialized (string, number, boolean)
|
||||
*
|
||||
@ -25,18 +37,6 @@ export class LocationType {
|
||||
public origin: string) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* @experimental WebWorker support in Angular is currently experimental.
|
||||
*/
|
||||
export const enum SerializerTypes {
|
||||
// RendererType2
|
||||
RENDERER_TYPE_2,
|
||||
// Primitive types
|
||||
PRIMITIVE,
|
||||
// An object stored in a RenderStore
|
||||
RENDER_STORE_OBJECT,
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class Serializer {
|
||||
constructor(private _renderStore: RenderStore) {}
|
||||
|
@ -18,8 +18,8 @@ import {map} from 'rxjs/operator/map';
|
||||
import {mergeMap} from 'rxjs/operator/mergeMap';
|
||||
import {EmptyError} from 'rxjs/util/EmptyError';
|
||||
|
||||
import {InternalRoute, Route, Routes} from './config';
|
||||
import {LoadedRouterConfig, RouterConfigLoader} from './router_config_loader';
|
||||
import {LoadedRouterConfig, Route, Routes} from './config';
|
||||
import {RouterConfigLoader} from './router_config_loader';
|
||||
import {PRIMARY_OUTLET, Params, defaultUrlMatcher, navigationCancelingError} from './shared';
|
||||
import {UrlSegment, UrlSegmentGroup, UrlSerializer, UrlTree} from './url_tree';
|
||||
import {andObservables, forEach, waitForMap, wrapIntoObservable} from './utils/collection';
|
||||
@ -247,7 +247,7 @@ class ApplyRedirects {
|
||||
}
|
||||
|
||||
private matchSegmentAgainstRoute(
|
||||
ngModule: NgModuleRef<any>, rawSegmentGroup: UrlSegmentGroup, route: InternalRoute,
|
||||
ngModule: NgModuleRef<any>, rawSegmentGroup: UrlSegmentGroup, route: Route,
|
||||
segments: UrlSegment[]): Observable<UrlSegmentGroup> {
|
||||
if (route.path === '**') {
|
||||
if (route.loadChildren) {
|
||||
@ -292,8 +292,7 @@ class ApplyRedirects {
|
||||
});
|
||||
}
|
||||
|
||||
private getChildConfig(ngModule: NgModuleRef<any>, route: InternalRoute):
|
||||
Observable<LoadedRouterConfig> {
|
||||
private getChildConfig(ngModule: NgModuleRef<any>, route: Route): Observable<LoadedRouterConfig> {
|
||||
if (route.children) {
|
||||
// The children belong to the same module
|
||||
return of (new LoadedRouterConfig(route.children, ngModule));
|
||||
|
@ -6,13 +6,11 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {NgModuleFactory, Type} from '@angular/core';
|
||||
import {NgModuleFactory, NgModuleRef, Type} from '@angular/core';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
|
||||
import {PRIMARY_OUTLET} from './shared';
|
||||
import {UrlSegment, UrlSegmentGroup} from './url_tree';
|
||||
|
||||
|
||||
/**
|
||||
* @whatItDoes Represents router configuration.
|
||||
*
|
||||
@ -359,11 +357,15 @@ export interface Route {
|
||||
children?: Routes;
|
||||
loadChildren?: LoadChildren;
|
||||
runGuardsAndResolvers?: RunGuardsAndResolvers;
|
||||
/**
|
||||
* Filled for routes with `loadChildren` once the module has been loaded
|
||||
* @internal
|
||||
*/
|
||||
_loadedConfig?: LoadedRouterConfig;
|
||||
}
|
||||
|
||||
export interface InternalRoute extends Route {
|
||||
// `LoadedRouterConfig` loaded via a Route `loadChildren`
|
||||
_loadedConfig?: any;
|
||||
export class LoadedRouterConfig {
|
||||
constructor(public routes: Route[], public module: NgModuleRef<any>) {}
|
||||
}
|
||||
|
||||
export function validateConfig(config: Routes, parentPath: string = ''): void {
|
||||
|
@ -123,10 +123,9 @@ export class RouterLinkActive implements OnChanges,
|
||||
|
||||
// react only when status has changed to prevent unnecessary dom updates
|
||||
if (this.active !== hasActiveLinks) {
|
||||
this.active = hasActiveLinks;
|
||||
this.classes.forEach(
|
||||
c => this.renderer.setElementClass(this.element.nativeElement, c, hasActiveLinks));
|
||||
this.cdr.detectChanges();
|
||||
Promise.resolve(hasActiveLinks).then(active => this.active = active);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ import {Observable} from 'rxjs/Observable';
|
||||
import {Observer} from 'rxjs/Observer';
|
||||
import {of } from 'rxjs/observable/of';
|
||||
|
||||
import {Data, InternalRoute, ResolveData, Route, Routes} from './config';
|
||||
import {Data, ResolveData, Route, Routes} from './config';
|
||||
import {ActivatedRouteSnapshot, RouterStateSnapshot, inheritedParamsDataResolve} from './router_state';
|
||||
import {PRIMARY_OUTLET, defaultUrlMatcher} from './shared';
|
||||
import {UrlSegment, UrlSegmentGroup, UrlTree, mapChildrenIntoArray} from './url_tree';
|
||||
@ -154,7 +154,7 @@ function sortActivatedRouteSnapshots(nodes: TreeNode<ActivatedRouteSnapshot>[]):
|
||||
});
|
||||
}
|
||||
|
||||
function getChildConfig(route: InternalRoute): Route[] {
|
||||
function getChildConfig(route: Route): Route[] {
|
||||
if (route.children) {
|
||||
return route.children;
|
||||
}
|
||||
|
@ -22,14 +22,14 @@ import {mergeMap} from 'rxjs/operator/mergeMap';
|
||||
import {reduce} from 'rxjs/operator/reduce';
|
||||
|
||||
import {applyRedirects} from './apply_redirects';
|
||||
import {InternalRoute, QueryParamsHandling, ResolveData, Route, Routes, RunGuardsAndResolvers, validateConfig} from './config';
|
||||
import {LoadedRouterConfig, QueryParamsHandling, ResolveData, Route, Routes, RunGuardsAndResolvers, validateConfig} from './config';
|
||||
import {createRouterState} from './create_router_state';
|
||||
import {createUrlTree} from './create_url_tree';
|
||||
import {RouterOutlet} from './directives/router_outlet';
|
||||
import {Event, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, RouteConfigLoadEnd, RouteConfigLoadStart, RoutesRecognized} from './events';
|
||||
import {recognize} from './recognize';
|
||||
import {DetachedRouteHandle, DetachedRouteHandleInternal, RouteReuseStrategy} from './route_reuse_strategy';
|
||||
import {LoadedRouterConfig, RouterConfigLoader} from './router_config_loader';
|
||||
import {RouterConfigLoader} from './router_config_loader';
|
||||
import {RouterOutletMap} from './router_outlet_map';
|
||||
import {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState, equalParamsAndUrlSegments, inheritedParamsDataResolve} from './router_state';
|
||||
import {PRIMARY_OUTLET, Params, isNavigationCancelingError} from './shared';
|
||||
@ -1168,7 +1168,7 @@ function advanceActivatedRouteNodeAndItsChildren(node: TreeNode<ActivatedRoute>)
|
||||
|
||||
function parentLoadedConfig(snapshot: ActivatedRouteSnapshot): LoadedRouterConfig {
|
||||
for (let s = snapshot.parent; s; s = s.parent) {
|
||||
const route: InternalRoute = s._routeConfig;
|
||||
const route = s._routeConfig;
|
||||
if (route && route._loadedConfig) return route._loadedConfig;
|
||||
if (route && route.component) return null;
|
||||
}
|
||||
@ -1180,7 +1180,7 @@ function closestLoadedConfig(snapshot: ActivatedRouteSnapshot): LoadedRouterConf
|
||||
if (!snapshot) return null;
|
||||
|
||||
for (let s = snapshot.parent; s; s = s.parent) {
|
||||
const route: InternalRoute = s._routeConfig;
|
||||
const route = s._routeConfig;
|
||||
if (route && route._loadedConfig) return route._loadedConfig;
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ import {fromPromise} from 'rxjs/observable/fromPromise';
|
||||
import {of } from 'rxjs/observable/of';
|
||||
import {map} from 'rxjs/operator/map';
|
||||
import {mergeMap} from 'rxjs/operator/mergeMap';
|
||||
import {LoadChildren, Route} from './config';
|
||||
import {LoadChildren, LoadedRouterConfig, Route} from './config';
|
||||
import {flatten, wrapIntoObservable} from './utils/collection';
|
||||
|
||||
/**
|
||||
@ -21,10 +21,6 @@ import {flatten, wrapIntoObservable} from './utils/collection';
|
||||
*/
|
||||
export const ROUTES = new InjectionToken<Route[][]>('ROUTES');
|
||||
|
||||
export class LoadedRouterConfig {
|
||||
constructor(public routes: Route[], public module: NgModuleRef<any>) {}
|
||||
}
|
||||
|
||||
export class RouterConfigLoader {
|
||||
constructor(
|
||||
private loader: NgModuleFactoryLoader, private compiler: Compiler,
|
||||
|
@ -6,7 +6,7 @@
|
||||
*found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Compiler, Injectable, Injector, NgModuleFactoryLoader, NgModuleRef} from '@angular/core';
|
||||
import {Compiler, Injectable, Injector, NgModuleFactoryLoader, NgModuleRef, OnDestroy} from '@angular/core';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {Subscription} from 'rxjs/Subscription';
|
||||
import {from} from 'rxjs/observable/from';
|
||||
@ -16,8 +16,8 @@ import {concatMap} from 'rxjs/operator/concatMap';
|
||||
import {filter} from 'rxjs/operator/filter';
|
||||
import {mergeAll} from 'rxjs/operator/mergeAll';
|
||||
import {mergeMap} from 'rxjs/operator/mergeMap';
|
||||
import {InternalRoute, Route, Routes} from './config';
|
||||
import {NavigationEnd, RouteConfigLoadEnd, RouteConfigLoadStart} from './events';
|
||||
import {LoadedRouterConfig, Route, Routes} from './config';
|
||||
import {Event, NavigationEnd, RouteConfigLoadEnd, RouteConfigLoadStart} from './events';
|
||||
import {Router} from './router';
|
||||
import {RouterConfigLoader} from './router_config_loader';
|
||||
|
||||
@ -73,7 +73,7 @@ export class NoPreloading implements PreloadingStrategy {
|
||||
* @stable
|
||||
*/
|
||||
@Injectable()
|
||||
export class RouterPreloader {
|
||||
export class RouterPreloader implements OnDestroy {
|
||||
private loader: RouterConfigLoader;
|
||||
private subscription: Subscription;
|
||||
|
||||
@ -87,8 +87,8 @@ export class RouterPreloader {
|
||||
};
|
||||
|
||||
setUpPreloading(): void {
|
||||
const navigations = filter.call(this.router.events, (e: any) => e instanceof NavigationEnd);
|
||||
this.subscription = concatMap.call(navigations, () => this.preload()).subscribe(() => {});
|
||||
const navigations$ = filter.call(this.router.events, (e: Event) => e instanceof NavigationEnd);
|
||||
this.subscription = concatMap.call(navigations$, () => this.preload()).subscribe(() => {});
|
||||
}
|
||||
|
||||
preload(): Observable<any> {
|
||||
@ -100,8 +100,7 @@ export class RouterPreloader {
|
||||
|
||||
private processRoutes(ngModule: NgModuleRef<any>, routes: Routes): Observable<void> {
|
||||
const res: Observable<any>[] = [];
|
||||
for (const r of routes) {
|
||||
const route: InternalRoute = r;
|
||||
for (const route of routes) {
|
||||
// we already have the config loaded, just recurse
|
||||
if (route.loadChildren && !route.canLoad && route._loadedConfig) {
|
||||
const childConfig = route._loadedConfig;
|
||||
@ -119,10 +118,10 @@ export class RouterPreloader {
|
||||
return mergeAll.call(from(res));
|
||||
}
|
||||
|
||||
private preloadConfig(ngModule: NgModuleRef<any>, route: InternalRoute): Observable<void> {
|
||||
private preloadConfig(ngModule: NgModuleRef<any>, route: Route): Observable<void> {
|
||||
return this.preloadingStrategy.preload(route, () => {
|
||||
const loaded = this.loader.load(ngModule.injector, route);
|
||||
return mergeMap.call(loaded, (config: any): any => {
|
||||
const loaded$ = this.loader.load(ngModule.injector, route);
|
||||
return mergeMap.call(loaded$, (config: LoadedRouterConfig) => {
|
||||
route._loadedConfig = config;
|
||||
return this.processRoutes(config.module, config.routes);
|
||||
});
|
||||
|
@ -12,8 +12,7 @@ import {Observable} from 'rxjs/Observable';
|
||||
import {of } from 'rxjs/observable/of';
|
||||
|
||||
import {applyRedirects} from '../src/apply_redirects';
|
||||
import {Routes} from '../src/config';
|
||||
import {LoadedRouterConfig} from '../src/router_config_loader';
|
||||
import {LoadedRouterConfig, Routes} from '../src/config';
|
||||
import {DefaultUrlSerializer, UrlSegmentGroup, UrlTree, equalSegments} from '../src/url_tree';
|
||||
|
||||
describe('applyRedirects', () => {
|
||||
@ -163,12 +162,12 @@ describe('applyRedirects', () => {
|
||||
return of (loadedConfig);
|
||||
}
|
||||
};
|
||||
const config = [{path: 'a', component: ComponentA, loadChildren: 'children'}];
|
||||
const config: Routes = [{path: 'a', component: ComponentA, loadChildren: 'children'}];
|
||||
|
||||
applyRedirects(testModule.injector, <any>loader, serializer, tree('a/b'), config)
|
||||
.forEach(r => {
|
||||
compareTrees(r, tree('/a/b'));
|
||||
expect((<any>config[0])._loadedConfig).toBe(loadedConfig);
|
||||
expect(config[0]._loadedConfig).toBe(loadedConfig);
|
||||
});
|
||||
});
|
||||
|
||||
@ -290,12 +289,12 @@ describe('applyRedirects', () => {
|
||||
|
||||
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
||||
|
||||
const config =
|
||||
const config: Routes =
|
||||
[{path: '', pathMatch: 'full', redirectTo: '/a'}, {path: 'a', loadChildren: 'children'}];
|
||||
|
||||
applyRedirects(testModule.injector, <any>loader, serializer, tree(''), config).forEach(r => {
|
||||
compareTrees(r, tree('a'));
|
||||
expect((<any>config[1])._loadedConfig).toBe(loadedConfig);
|
||||
expect(config[1]._loadedConfig).toBe(loadedConfig);
|
||||
});
|
||||
});
|
||||
|
||||
@ -311,7 +310,7 @@ describe('applyRedirects', () => {
|
||||
}
|
||||
};
|
||||
|
||||
const config = [{path: 'a', loadChildren: 'children'}];
|
||||
const config: Routes = [{path: 'a', loadChildren: 'children'}];
|
||||
|
||||
applyRedirects(testModule.injector, <any>loader, serializer, tree('a?k1'), config)
|
||||
.subscribe(r => {});
|
||||
@ -320,7 +319,7 @@ describe('applyRedirects', () => {
|
||||
.subscribe(
|
||||
r => {
|
||||
compareTrees(r, tree('a?k2'));
|
||||
expect((<any>config[0])._loadedConfig).toBe(loadedConfig);
|
||||
expect(config[0]._loadedConfig).toBe(loadedConfig);
|
||||
},
|
||||
(e) => { throw 'Should not reach'; });
|
||||
});
|
||||
@ -330,10 +329,10 @@ describe('applyRedirects', () => {
|
||||
|
||||
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
||||
|
||||
const config = [{path: '**', loadChildren: 'children'}];
|
||||
const config: Routes = [{path: '**', loadChildren: 'children'}];
|
||||
|
||||
applyRedirects(testModule.injector, <any>loader, serializer, tree('xyz'), config)
|
||||
.forEach(r => { expect((<any>config[0])._loadedConfig).toBe(loadedConfig); });
|
||||
.forEach(r => { expect(config[0]._loadedConfig).toBe(loadedConfig); });
|
||||
});
|
||||
|
||||
it('should load the configuration after a local redirect from a wildcard route', () => {
|
||||
@ -341,11 +340,11 @@ describe('applyRedirects', () => {
|
||||
|
||||
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
||||
|
||||
const config =
|
||||
const config: Routes =
|
||||
[{path: 'not-found', loadChildren: 'children'}, {path: '**', redirectTo: 'not-found'}];
|
||||
|
||||
applyRedirects(testModule.injector, <any>loader, serializer, tree('xyz'), config)
|
||||
.forEach(r => { expect((<any>config[0])._loadedConfig).toBe(loadedConfig); });
|
||||
.forEach(r => { expect(config[0]._loadedConfig).toBe(loadedConfig); });
|
||||
});
|
||||
|
||||
it('should load the configuration after an absolute redirect from a wildcard route', () => {
|
||||
@ -353,11 +352,11 @@ describe('applyRedirects', () => {
|
||||
|
||||
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
||||
|
||||
const config =
|
||||
const config: Routes =
|
||||
[{path: 'not-found', loadChildren: 'children'}, {path: '**', redirectTo: '/not-found'}];
|
||||
|
||||
applyRedirects(testModule.injector, <any>loader, serializer, tree('xyz'), config)
|
||||
.forEach(r => { expect((<any>config[0])._loadedConfig).toBe(loadedConfig); });
|
||||
.forEach(r => { expect(config[0]._loadedConfig).toBe(loadedConfig); });
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -11,11 +11,10 @@ import {Component, Injectable, NgModule, NgModuleFactoryLoader, NgModuleRef} fro
|
||||
import {ComponentFixture, TestBed, fakeAsync, inject, tick} from '@angular/core/testing';
|
||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
import {ActivatedRoute, ActivatedRouteSnapshot, CanActivate, CanDeactivate, DetachedRouteHandle, Event, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, PRIMARY_OUTLET, ParamMap, Params, PreloadAllModules, PreloadingStrategy, Resolve, RouteConfigLoadEnd, RouteConfigLoadStart, RouteReuseStrategy, Router, RouterModule, RouterPreloader, RouterStateSnapshot, RoutesRecognized, RunGuardsAndResolvers, UrlHandlingStrategy, UrlSegmentGroup, UrlTree} from '@angular/router';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {map} from 'rxjs/operator/map';
|
||||
|
||||
import {ActivatedRoute, ActivatedRouteSnapshot, CanActivate, CanDeactivate, DetachedRouteHandle, Event, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, PRIMARY_OUTLET, ParamMap, Params, PreloadAllModules, PreloadingStrategy, Resolve, RouteConfigLoadEnd, RouteConfigLoadStart, RouteReuseStrategy, Router, RouterModule, RouterStateSnapshot, RoutesRecognized, RunGuardsAndResolvers, UrlHandlingStrategy, UrlSegmentGroup, UrlTree} from '../index';
|
||||
import {RouterPreloader} from '../src/router_preloader';
|
||||
import {forEach} from '../src/utils/collection';
|
||||
import {RouterTestingModule, SpyNgModuleFactoryLoader} from '../testing';
|
||||
|
||||
@ -2470,13 +2469,14 @@ describe('Integration', () => {
|
||||
const fixture = TestBed.createComponent(ComponentWithRouterLink);
|
||||
router.navigateByUrl('/team');
|
||||
expect(() => advance(fixture)).not.toThrow();
|
||||
advance(fixture);
|
||||
|
||||
const paragraph = fixture.nativeElement.querySelector('p');
|
||||
expect(paragraph.textContent).toEqual('true');
|
||||
|
||||
router.navigateByUrl('/otherteam');
|
||||
advance(fixture);
|
||||
|
||||
advance(fixture);
|
||||
expect(paragraph.textContent).toEqual('false');
|
||||
}));
|
||||
|
||||
@ -3049,7 +3049,7 @@ describe('Integration', () => {
|
||||
router.navigateByUrl('/blank');
|
||||
advance(fixture);
|
||||
|
||||
const config: any = router.config;
|
||||
const config = router.config;
|
||||
const firstConfig = config[1]._loadedConfig;
|
||||
|
||||
expect(firstConfig).toBeDefined();
|
||||
|
74
packages/router/test/regression_integration.spec.ts
Normal file
74
packages/router/test/regression_integration.spec.ts
Normal file
@ -0,0 +1,74 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Component, NgModule, Type} from '@angular/core';
|
||||
import {ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing';
|
||||
import {Router} from '@angular/router';
|
||||
import {RouterTestingModule} from '@angular/router/testing';
|
||||
|
||||
describe('Integration', () => {
|
||||
|
||||
describe('routerLinkActive', () => {
|
||||
it('should not cause infinite loops in the change detection - #15825', fakeAsync(() => {
|
||||
@Component({selector: 'simple', template: 'simple'})
|
||||
class SimpleCmp {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'some-root',
|
||||
template: `
|
||||
<div *ngIf="show">
|
||||
<ng-container *ngTemplateOutlet="tpl"></ng-container>
|
||||
</div>
|
||||
<router-outlet></router-outlet>
|
||||
<ng-template #tpl>
|
||||
<a routerLink="/simple" routerLinkActive="active"></a>
|
||||
</ng-template>`
|
||||
})
|
||||
class MyCmp {
|
||||
show: boolean = false;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, RouterTestingModule],
|
||||
declarations: [MyCmp, SimpleCmp],
|
||||
entryComponents: [SimpleCmp],
|
||||
})
|
||||
class MyModule {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({imports: [MyModule]});
|
||||
|
||||
const router: Router = TestBed.get(Router);
|
||||
const fixture = createRoot(router, MyCmp);
|
||||
router.resetConfig([{path: 'simple', component: SimpleCmp}]);
|
||||
|
||||
router.navigateByUrl('/simple');
|
||||
advance(fixture);
|
||||
|
||||
const instance = fixture.componentInstance;
|
||||
instance.show = true;
|
||||
expect(() => advance(fixture)).not.toThrow();
|
||||
}));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function advance<T>(fixture: ComponentFixture<T>): void {
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
}
|
||||
|
||||
function createRoot<T>(router: Router, type: Type<T>): ComponentFixture<T> {
|
||||
const f = TestBed.createComponent(type);
|
||||
advance(f);
|
||||
router.initialNavigation();
|
||||
advance(f);
|
||||
return f;
|
||||
}
|
@ -10,7 +10,7 @@ import {Compiler, Component, NgModule, NgModuleFactoryLoader, NgModuleRef} from
|
||||
import {TestBed, fakeAsync, inject, tick} from '@angular/core/testing';
|
||||
|
||||
import {Route, RouteConfigLoadEnd, RouteConfigLoadStart, Router, RouterModule} from '../index';
|
||||
import {LoadedRouterConfig} from '../src/router_config_loader';
|
||||
import {LoadedRouterConfig} from '../src/config';
|
||||
import {PreloadAllModules, PreloadingStrategy, RouterPreloader} from '../src/router_preloader';
|
||||
import {RouterTestingModule, SpyNgModuleFactoryLoader} from '../testing';
|
||||
|
||||
@ -46,7 +46,7 @@ describe('RouterPreloader', () => {
|
||||
tick();
|
||||
|
||||
const c = router.config;
|
||||
expect(!!((<any>c[0])._loadedConfig)).toBe(false);
|
||||
expect(c[0]._loadedConfig).not.toBeDefined();
|
||||
})));
|
||||
});
|
||||
|
||||
@ -97,12 +97,12 @@ describe('RouterPreloader', () => {
|
||||
const c = router.config;
|
||||
expect(c[0].loadChildren).toEqual('expected');
|
||||
|
||||
const loadedConfig: LoadedRouterConfig = (<any>c[0])._loadedConfig;
|
||||
const loadedConfig: LoadedRouterConfig = c[0]._loadedConfig;
|
||||
const module: any = loadedConfig.module;
|
||||
expect(loadedConfig.routes[0].path).toEqual('LoadedModule1');
|
||||
expect(module.parent).toBe(testModule);
|
||||
|
||||
const loadedConfig2: LoadedRouterConfig = (<any>loadedConfig.routes[0])._loadedConfig;
|
||||
const loadedConfig2: LoadedRouterConfig = loadedConfig.routes[0]._loadedConfig;
|
||||
const module2: any = loadedConfig2.module;
|
||||
expect(loadedConfig2.routes[0].path).toEqual('LoadedModule2');
|
||||
expect(module2.parent).toBe(module);
|
||||
@ -165,12 +165,12 @@ describe('RouterPreloader', () => {
|
||||
|
||||
const c = router.config;
|
||||
|
||||
const loadedConfig: LoadedRouterConfig = (<any>c[0])._loadedConfig;
|
||||
const loadedConfig: LoadedRouterConfig = c[0]._loadedConfig;
|
||||
const module: any = loadedConfig.module;
|
||||
expect(module.parent).toBe(testModule);
|
||||
|
||||
const loadedConfig2: LoadedRouterConfig = (<any>loadedConfig.routes[0])._loadedConfig;
|
||||
const loadedConfig3: LoadedRouterConfig = (<any>loadedConfig2.routes[0])._loadedConfig;
|
||||
const loadedConfig2: LoadedRouterConfig = loadedConfig.routes[0]._loadedConfig;
|
||||
const loadedConfig3: LoadedRouterConfig = loadedConfig2.routes[0]._loadedConfig;
|
||||
const module3: any = loadedConfig3.module;
|
||||
expect(module3.parent).toBe(module2);
|
||||
})));
|
||||
@ -204,8 +204,8 @@ describe('RouterPreloader', () => {
|
||||
tick();
|
||||
|
||||
const c = router.config;
|
||||
expect(!!((<any>c[0])._loadedConfig)).toBe(false);
|
||||
expect(!!((<any>c[1])._loadedConfig)).toBe(true);
|
||||
expect(c[0]._loadedConfig).not.toBeDefined();
|
||||
expect(c[1]._loadedConfig).toBeDefined();
|
||||
})));
|
||||
});
|
||||
});
|
||||
|
@ -201,6 +201,7 @@ function noNg() {
|
||||
throw new Error('AngularJS v1.x is not loaded!');
|
||||
}
|
||||
|
||||
|
||||
let angular: {
|
||||
bootstrap: (e: Element, modules: (string | IInjectable)[], config: IAngularBootstrapConfig) =>
|
||||
void,
|
||||
@ -217,7 +218,6 @@ let angular: {
|
||||
getTestability: noNg
|
||||
};
|
||||
|
||||
|
||||
try {
|
||||
if (window.hasOwnProperty('angular')) {
|
||||
angular = (<any>window).angular;
|
||||
@ -226,9 +226,37 @@ try {
|
||||
// ignore in CJS mode.
|
||||
}
|
||||
|
||||
export const bootstrap = angular.bootstrap;
|
||||
export const module = angular.module;
|
||||
export const element = angular.element;
|
||||
/**
|
||||
* Resets the AngularJS library.
|
||||
*
|
||||
* Used when angularjs is loaded lazily, and not available on `window`.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export function setAngularLib(ng: any): void {
|
||||
angular = ng;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current version of the AngularJS library.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export function getAngularLib(): any {
|
||||
return angular;
|
||||
}
|
||||
|
||||
export const bootstrap =
|
||||
(e: Element, modules: (string | IInjectable)[], config: IAngularBootstrapConfig): void =>
|
||||
angular.bootstrap(e, modules, config);
|
||||
|
||||
export const module = (prefix: string, dependencies?: string[]): IModule =>
|
||||
angular.module(prefix, dependencies);
|
||||
|
||||
export const element = (e: Element | string): IAugmentedJQuery => angular.element(e);
|
||||
|
||||
export const resumeBootstrap = (): void => angular.resumeBootstrap();
|
||||
|
||||
export const getTestability = (e: Element): ITestabilityService => angular.getTestability(e);
|
||||
|
||||
export const version = angular.version;
|
||||
export const resumeBootstrap = angular.resumeBootstrap;
|
||||
export const getTestability = angular.getTestability;
|
||||
|
@ -12,6 +12,7 @@
|
||||
* Entry point for all public APIs of the upgrade/static package, allowing
|
||||
* Angular 1 and Angular 2+ to run side by side in the same application.
|
||||
*/
|
||||
export {getAngularLib, setAngularLib} from './src/common/angular1';
|
||||
export {downgradeComponent} from './src/common/downgrade_component';
|
||||
export {downgradeInjectable} from './src/common/downgrade_injectable';
|
||||
export {VERSION} from './src/common/version';
|
||||
|
@ -12,7 +12,7 @@ import {BrowserModule} from '@angular/platform-browser';
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
import * as angular from '@angular/upgrade/src/common/angular1';
|
||||
import {$INJECTOR, INJECTOR_KEY} from '@angular/upgrade/src/common/constants';
|
||||
import {UpgradeModule, downgradeInjectable} from '@angular/upgrade/static';
|
||||
import {UpgradeModule, downgradeInjectable, getAngularLib, setAngularLib} from '@angular/upgrade/static';
|
||||
|
||||
import {bootstrap, html} from '../test_helpers';
|
||||
|
||||
@ -99,5 +99,33 @@ export function main() {
|
||||
expect(runBlockTriggered).toBeTruthy();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should allow resetting angular at runtime', async(() => {
|
||||
let wrappedBootstrapepedCalled = false;
|
||||
|
||||
const n: any = getAngularLib();
|
||||
|
||||
setAngularLib({
|
||||
bootstrap: (...args: any[]) => {
|
||||
wrappedBootstrapepedCalled = true;
|
||||
n.bootstrap(...args);
|
||||
},
|
||||
module: n.module,
|
||||
element: n.element,
|
||||
version: n.version,
|
||||
resumeBootstrap: n.resumeBootstrap,
|
||||
getTestability: n.getTestability
|
||||
});
|
||||
|
||||
@NgModule({imports: [BrowserModule, UpgradeModule]})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
const ng1Module = angular.module('ng1Module', []);
|
||||
|
||||
bootstrap(platformBrowserDynamic(), Ng2Module, html('<div>'), ng1Module)
|
||||
.then((upgrade) => { expect(wrappedBootstrapepedCalled).toEqual(true); });
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
@ -58,6 +58,7 @@ cp -v package.json $TMP
|
||||
./node_modules/.bin/ngc -p tsconfig-build.json --i18nFile=src/messages.fi.xlf --locale=fi --i18nFormat=xlf
|
||||
|
||||
./node_modules/.bin/ng-xi18n -p tsconfig-xi18n.json --i18nFormat=xlf --locale=fr
|
||||
./node_modules/.bin/ng-xi18n -p tsconfig-xi18n.json --i18nFormat=xlf2 --outFile=messages.xliff2.xlf
|
||||
./node_modules/.bin/ng-xi18n -p tsconfig-xi18n.json --i18nFormat=xmb --outFile=custom_file.xmb
|
||||
|
||||
# Removed until #15219 is fixed
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@angular/tsc-wrapped",
|
||||
"version": "4.0.2",
|
||||
"version": "4.0.3",
|
||||
"description": "Wraps the tsc CLI, allowing extensions.",
|
||||
"homepage": "https://github.com/angular/angular/tree/master/tools/tsc-wrapped",
|
||||
"bugs": "https://github.com/angular/angular/issues",
|
||||
|
@ -9,14 +9,11 @@ import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {MetadataCollector} from './collector';
|
||||
import {ClassMetadata, ConstructorMetadata, FunctionMetadata, MemberMetadata, MetadataArray, MetadataEntry, MetadataError, MetadataImportedSymbolReferenceExpression, MetadataMap, MetadataObject, MetadataSymbolicBinaryExpression, MetadataSymbolicCallExpression, MetadataSymbolicExpression, MetadataSymbolicIfExpression, MetadataSymbolicIndexExpression, MetadataSymbolicPrefixExpression, MetadataSymbolicReferenceExpression, MetadataSymbolicSelectExpression, MetadataSymbolicSpreadExpression, MetadataValue, MethodMetadata, ModuleMetadata, VERSION, isClassMetadata, isConstructorMetadata, isFunctionMetadata, isInterfaceMetadata, isMetadataError, isMetadataGlobalReferenceExpression, isMetadataImportedSymbolReferenceExpression, isMetadataModuleReferenceExpression, isMetadataSymbolicExpression, isMetadataSymbolicReferenceExpression, isMethodMetadata} from './schema';
|
||||
import {ClassMetadata, ConstructorMetadata, FunctionMetadata, MemberMetadata, MetadataEntry, MetadataError, MetadataImportedSymbolReferenceExpression, MetadataMap, MetadataObject, MetadataSymbolicExpression, MetadataSymbolicReferenceExpression, MetadataValue, MethodMetadata, ModuleMetadata, VERSION, isClassMetadata, isConstructorMetadata, isFunctionMetadata, isInterfaceMetadata, isMetadataError, isMetadataGlobalReferenceExpression, isMetadataImportedSymbolReferenceExpression, isMetadataModuleReferenceExpression, isMetadataSymbolicExpression, isMethodMetadata} from './schema';
|
||||
|
||||
|
||||
// The character set used to produce private names.
|
||||
const PRIVATE_NAME_CHARS = [
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
||||
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
|
||||
];
|
||||
const PRIVATE_NAME_CHARS = 'abcdefghijklmnopqrstuvwxyz';
|
||||
|
||||
interface Symbol {
|
||||
module: string;
|
||||
@ -199,7 +196,6 @@ export class MetadataBundler {
|
||||
private canonicalizeSymbols(exportedSymbols: Symbol[]) {
|
||||
const symbols = Array.from(this.symbolMap.values());
|
||||
this.exported = new Set(exportedSymbols);
|
||||
;
|
||||
symbols.forEach(this.canonicalizeSymbol, this);
|
||||
}
|
||||
|
||||
@ -433,18 +429,15 @@ export class MetadataBundler {
|
||||
|
||||
if (isMetadataImportedSymbolReferenceExpression(value)) {
|
||||
// References to imported symbols are separated into two, references to bundled modules and
|
||||
// references to modules
|
||||
// external to the bundle. If the module reference is relative it is assuemd to be in the
|
||||
// bundle. If it is Global
|
||||
// it is assumed to be outside the bundle. References to symbols outside the bundle are left
|
||||
// unmodified. Refernces
|
||||
// to symbol inside the bundle need to be converted to a bundle import reference reachable
|
||||
// from the bundle index.
|
||||
// references to modules external to the bundle. If the module reference is relative it is
|
||||
// assumed to be in the bundle. If it is Global it is assumed to be outside the bundle.
|
||||
// References to symbols outside the bundle are left unmodified. References to symbol inside
|
||||
// the bundle need to be converted to a bundle import reference reachable from the bundle
|
||||
// index.
|
||||
|
||||
if (value.module.startsWith('.')) {
|
||||
// Reference is to a symbol defined inside the module. Convert the reference to a reference
|
||||
// to the canonical
|
||||
// symbol.
|
||||
// to the canonical symbol.
|
||||
const referencedModule = resolveModule(value.module, moduleName);
|
||||
const referencedName = value.name;
|
||||
return createReference(this.canonicalSymbolOf(referencedModule, referencedName));
|
||||
@ -453,7 +446,7 @@ export class MetadataBundler {
|
||||
// Value is a reference to a symbol defined outside the module.
|
||||
if (value.arguments) {
|
||||
// If a reference has arguments the arguments need to be converted.
|
||||
const result: MetadataImportedSymbolReferenceExpression = {
|
||||
return {
|
||||
__symbolic: 'reference',
|
||||
name: value.name,
|
||||
module: value.module,
|
||||
@ -538,10 +531,6 @@ function isPrimitive(o: any): o is boolean|string|number {
|
||||
return o === null || (typeof o !== 'function' && typeof o !== 'object');
|
||||
}
|
||||
|
||||
function isMetadataArray(o: MetadataValue): o is MetadataArray {
|
||||
return Array.isArray(o);
|
||||
}
|
||||
|
||||
function getRootExport(symbol: Symbol): Symbol {
|
||||
return symbol.reexportedAs ? getRootExport(symbol.reexportedAs) : symbol;
|
||||
}
|
||||
|
@ -94,7 +94,6 @@ export class MetadataCollector {
|
||||
value: evaluator.evaluateNode(returnStatement.expression)
|
||||
};
|
||||
if (functionDeclaration.parameters.some(p => p.initializer != null)) {
|
||||
const defaults: MetadataValue[] = [];
|
||||
func.defaults = functionDeclaration.parameters.map(
|
||||
p => p.initializer && evaluator.evaluateNode(p.initializer));
|
||||
}
|
||||
@ -358,7 +357,6 @@ export class MetadataCollector {
|
||||
case ts.SyntaxKind.ClassDeclaration:
|
||||
const classDeclaration = <ts.ClassDeclaration>node;
|
||||
if (classDeclaration.name) {
|
||||
const className = classDeclaration.name.text;
|
||||
if (isExported(classDeclaration)) {
|
||||
if (!metadata) metadata = {};
|
||||
metadata[exportedName(classDeclaration)] = classMetadataOf(classDeclaration);
|
||||
|
@ -8,7 +8,6 @@
|
||||
|
||||
import {writeFileSync} from 'fs';
|
||||
import {normalize} from 'path';
|
||||
import * as tsickle from 'tsickle';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import NgOptions from './options';
|
||||
@ -98,8 +97,7 @@ export class MetadataWriterHost extends DelegatingHost {
|
||||
}
|
||||
if (isDts) {
|
||||
// TODO: remove this early return after https://github.com/Microsoft/TypeScript/pull/8412
|
||||
// is
|
||||
// released
|
||||
// is released
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {CollectorOptions} from './collector';
|
||||
import {MetadataEntry, MetadataError, MetadataGlobalReferenceExpression, MetadataImportedSymbolReferenceExpression, MetadataSymbolicCallExpression, MetadataSymbolicReferenceExpression, MetadataValue, isMetadataError, isMetadataGlobalReferenceExpression, isMetadataImportedSymbolReferenceExpression, isMetadataModuleReferenceExpression, isMetadataSymbolicReferenceExpression, isMetadataSymbolicSpreadExpression} from './schema';
|
||||
import {MetadataEntry, MetadataError, MetadataImportedSymbolReferenceExpression, MetadataSymbolicCallExpression, MetadataValue, isMetadataError, isMetadataModuleReferenceExpression, isMetadataSymbolicReferenceExpression, isMetadataSymbolicSpreadExpression} from './schema';
|
||||
import {Symbols} from './symbols';
|
||||
|
||||
// In TypeScript 2.1 the spread element kind was renamed.
|
||||
@ -86,7 +86,7 @@ export function errorSymbol(
|
||||
const {line, character} =
|
||||
ts.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile));
|
||||
result = {__symbolic: 'error', message, line, character};
|
||||
};
|
||||
}
|
||||
}
|
||||
if (!result) {
|
||||
result = {__symbolic: 'error', message};
|
||||
@ -154,7 +154,8 @@ export class Evaluator {
|
||||
case ts.SyntaxKind.CallExpression:
|
||||
const callExpression = <ts.CallExpression>node;
|
||||
// We can fold a <array>.concat(<v>).
|
||||
if (isMethodCallOf(callExpression, 'concat') && callExpression.arguments.length === 1) {
|
||||
if (isMethodCallOf(callExpression, 'concat') &&
|
||||
arrayOrEmpty(callExpression.arguments).length === 1) {
|
||||
const arrayNode = (<ts.PropertyAccessExpression>callExpression.expression).expression;
|
||||
if (this.isFoldableWorker(arrayNode, folding) &&
|
||||
this.isFoldableWorker(callExpression.arguments[0], folding)) {
|
||||
@ -167,7 +168,8 @@ export class Evaluator {
|
||||
}
|
||||
|
||||
// We can fold a call to CONST_EXPR
|
||||
if (isCallOf(callExpression, 'CONST_EXPR') && callExpression.arguments.length === 1)
|
||||
if (isCallOf(callExpression, 'CONST_EXPR') &&
|
||||
arrayOrEmpty(callExpression.arguments).length === 1)
|
||||
return this.isFoldableWorker(callExpression.arguments[0], folding);
|
||||
return false;
|
||||
case ts.SyntaxKind.NoSubstitutionTemplateLiteral:
|
||||
@ -295,14 +297,15 @@ export class Evaluator {
|
||||
return recordEntry({__symbolic: 'spread', expression: spreadExpression}, node);
|
||||
case ts.SyntaxKind.CallExpression:
|
||||
const callExpression = <ts.CallExpression>node;
|
||||
if (isCallOf(callExpression, 'forwardRef') && callExpression.arguments.length === 1) {
|
||||
if (isCallOf(callExpression, 'forwardRef') &&
|
||||
arrayOrEmpty(callExpression.arguments).length === 1) {
|
||||
const firstArgument = callExpression.arguments[0];
|
||||
if (firstArgument.kind == ts.SyntaxKind.ArrowFunction) {
|
||||
const arrowFunction = <ts.ArrowFunction>firstArgument;
|
||||
return recordEntry(this.evaluateNode(arrowFunction.body), node);
|
||||
}
|
||||
}
|
||||
const args = callExpression.arguments.map(arg => this.evaluateNode(arg));
|
||||
const args = arrayOrEmpty(callExpression.arguments).map(arg => this.evaluateNode(arg));
|
||||
if (!this.options.verboseInvalidExpression && args.some(isMetadataError)) {
|
||||
return args.find(isMetadataError);
|
||||
}
|
||||
@ -315,7 +318,8 @@ export class Evaluator {
|
||||
}
|
||||
}
|
||||
// Always fold a CONST_EXPR even if the argument is not foldable.
|
||||
if (isCallOf(callExpression, 'CONST_EXPR') && callExpression.arguments.length === 1) {
|
||||
if (isCallOf(callExpression, 'CONST_EXPR') &&
|
||||
arrayOrEmpty(callExpression.arguments).length === 1) {
|
||||
return recordEntry(args[0], node);
|
||||
}
|
||||
const expression = this.evaluateNode(callExpression.expression);
|
||||
@ -329,7 +333,7 @@ export class Evaluator {
|
||||
return recordEntry(result, node);
|
||||
case ts.SyntaxKind.NewExpression:
|
||||
const newExpression = <ts.NewExpression>node;
|
||||
const newArgs = newExpression.arguments.map(arg => this.evaluateNode(arg));
|
||||
const newArgs = arrayOrEmpty(newExpression.arguments).map(arg => this.evaluateNode(arg));
|
||||
if (!this.options.verboseInvalidExpression && newArgs.some(isMetadataError)) {
|
||||
return recordEntry(newArgs.find(isMetadataError), node);
|
||||
}
|
||||
@ -575,3 +579,9 @@ export class Evaluator {
|
||||
function isPropertyAssignment(node: ts.Node): node is ts.PropertyAssignment {
|
||||
return node.kind == ts.SyntaxKind.PropertyAssignment;
|
||||
}
|
||||
|
||||
const empty = [] as ts.NodeArray<any>;
|
||||
|
||||
function arrayOrEmpty<T extends ts.Node>(v: ts.NodeArray<T>): ts.NodeArray<T> {
|
||||
return v || empty;
|
||||
}
|
@ -55,7 +55,7 @@ export function main(
|
||||
|
||||
let host = ts.createCompilerHost(parsed.options, true);
|
||||
|
||||
// If the comilation is a flat module index then produce the flat module index
|
||||
// If the compilation is a flat module index then produce the flat module index
|
||||
// metadata and the synthetic flat module index.
|
||||
if (ngOptions.flatModuleOutFile && !ngOptions.skipMetadataEmit) {
|
||||
const files = parsed.fileNames.filter(f => !DTS.test(f));
|
||||
@ -170,7 +170,7 @@ export function main(
|
||||
// CLI entry point
|
||||
if (require.main === module) {
|
||||
const args = process.argv.slice(2);
|
||||
let {options, fileNames, errors} = (ts as any).parseCommandLine(args);
|
||||
let {options, errors} = (ts as any).parseCommandLine(args);
|
||||
check(errors);
|
||||
const project = options.project || '.';
|
||||
// TODO(alexeagle): command line should be TSC-compatible, remove "CliOptions" here
|
||||
|
@ -51,7 +51,7 @@ export class Symbols {
|
||||
// in the parent chain). This doesn't damage the node as the binder unconditionally
|
||||
// sets the parent.
|
||||
externalReference.expression.parent = externalReference;
|
||||
externalReference.parent = this.sourceFile;
|
||||
externalReference.parent = this.sourceFile as any;
|
||||
}
|
||||
const from = stripQuotes(externalReference.expression.getText());
|
||||
symbols.set(importEqualsDeclaration.name.text, {__symbolic: 'reference', module: from});
|
||||
|
@ -12,8 +12,7 @@ export interface VinylFile extends Object {
|
||||
// Content of the virtual file
|
||||
contents: Buffer;
|
||||
}
|
||||
;
|
||||
|
||||
export function isVinylFile(obj: any): obj is VinylFile {
|
||||
return (typeof obj === 'object') && ('path' in obj) && ('contents' in obj);
|
||||
};
|
||||
}
|
@ -215,8 +215,21 @@ describe('Evaluator', () => {
|
||||
0, {__symbolic: 'spread', expression: {__symbolic: 'reference', name: 'arrImport'}}, 5
|
||||
]);
|
||||
});
|
||||
|
||||
it('should be able to handle a new expression with no arguments', () => {
|
||||
const source = sourceFileOf(`
|
||||
export var a = new f;
|
||||
`);
|
||||
const expr = findVar(source, 'a');
|
||||
expect(evaluator.evaluateNode(expr.initializer))
|
||||
.toEqual({__symbolic: 'new', expression: {__symbolic: 'reference', name: 'f'}});
|
||||
});
|
||||
});
|
||||
|
||||
function sourceFileOf(text: string): ts.SourceFile {
|
||||
return ts.createSourceFile('test.ts', text, ts.ScriptTarget.Latest, true);
|
||||
}
|
||||
|
||||
const FILES: Directory = {
|
||||
'directives.ts': `
|
||||
export function Pipe(options: { name?: string, pure?: boolean}) {
|
||||
|
@ -84,6 +84,9 @@ export class MockNode implements ts.Node {
|
||||
getText(sourceFile?: ts.SourceFile): string { return ''; }
|
||||
getFirstToken(sourceFile?: ts.SourceFile): ts.Node { return null; }
|
||||
getLastToken(sourceFile?: ts.SourceFile): ts.Node { return null; }
|
||||
forEachChild<T>(cbNode: (node: ts.Node) => T, cbNodeArray?: (nodes: ts.Node[]) => T): T {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export class MockIdentifier extends MockNode implements ts.Identifier {
|
||||
|
@ -2,5 +2,6 @@
|
||||
module.exports = (gulp) => (done) => {
|
||||
const path = require('path');
|
||||
const childProcess = require('child_process');
|
||||
childProcess.exec(path.join(__dirname, '../../build.sh'), done);
|
||||
// increase maxbuffer to address out of memory exception when running certain tasks
|
||||
childProcess.exec(path.join(__dirname, '../../build.sh'), {maxBuffer: 300 * 1024}, done);
|
||||
};
|
||||
|
2
tools/public_api_guard/router/router.d.ts
vendored
2
tools/public_api_guard/router/router.d.ts
vendored
@ -354,7 +354,7 @@ export declare class RouterOutletMap {
|
||||
}
|
||||
|
||||
/** @stable */
|
||||
export declare class RouterPreloader {
|
||||
export declare class RouterPreloader implements OnDestroy {
|
||||
constructor(router: Router, moduleLoader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector, preloadingStrategy: PreloadingStrategy);
|
||||
ngOnDestroy(): void;
|
||||
preload(): Observable<any>;
|
||||
|
6
tools/public_api_guard/upgrade/static.d.ts
vendored
6
tools/public_api_guard/upgrade/static.d.ts
vendored
@ -9,6 +9,12 @@ export declare function downgradeComponent(info: {
|
||||
/** @experimental */
|
||||
export declare function downgradeInjectable(token: any): Function;
|
||||
|
||||
/** @stable */
|
||||
export declare function getAngularLib(): any;
|
||||
|
||||
/** @stable */
|
||||
export declare function setAngularLib(ng: any): void;
|
||||
|
||||
/** @experimental */
|
||||
export declare class UpgradeComponent implements OnInit, OnChanges, DoCheck, OnDestroy {
|
||||
constructor(name: string, elementRef: ElementRef, injector: Injector);
|
||||
|
Reference in New Issue
Block a user