Compare commits
26 Commits
Author | SHA1 | Date | |
---|---|---|---|
79fb9d449c | |||
73a93d3ab6 | |||
8eda5a152b | |||
7b82ce0c67 | |||
2eb5fe699f | |||
f99febcdf9 | |||
36cbfb1771 | |||
1f5315f6f7 | |||
eeebe621fe | |||
05f279df49 | |||
485d67bfed | |||
a1592f5a20 | |||
a251374ecd | |||
2b00c17091 | |||
81724f5790 | |||
1f06b6c99b | |||
6790709b93 | |||
9f7f67121c | |||
db49beae15 | |||
97609daea9 | |||
abcb03cb82 | |||
4f09f7db73 | |||
f1e14a3224 | |||
50de03a83a | |||
65555fe35d | |||
6da3867d63 |
@ -56,6 +56,7 @@ env:
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- env: "CI_MODE=aio_local"
|
||||
- env: "CI_MODE=saucelabs_optional"
|
||||
- env: "CI_MODE=browserstack_optional"
|
||||
|
||||
|
@ -71,6 +71,8 @@ Before you submit your Pull Request (PR) consider the following guidelines:
|
||||
|
||||
1. Search [GitHub](https://github.com/angular/angular/pulls) for an open or closed PR
|
||||
that relates to your submission. You don't want to duplicate effort.
|
||||
1. Be sure that an issue describes the problem you're fixing, or documents the design for the feature you'd like to add.
|
||||
Discussing the design up front helps to ensure that we're ready to accept your work.
|
||||
1. Please sign our [Contributor License Agreement (CLA)](#cla) before sending PRs.
|
||||
We cannot accept code without this. Make sure you sign with the primary email address of the Git identity that has been granted access to the Angular repository.
|
||||
1. Fork the angular/angular repo.
|
||||
|
@ -46,7 +46,7 @@
|
||||
"@types/shelljs": "^0.8.0",
|
||||
"@types/supertest": "^2.0.5",
|
||||
"nodemon": "^1.18.3",
|
||||
"npm-run-all": "^4.1.3",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"supertest": "^3.1.0",
|
||||
"tslint": "^5.11.0",
|
||||
"tslint-jasmine-noSkipOrFocus": "^1.0.9",
|
||||
|
@ -129,7 +129,7 @@ ansi-styles@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
|
||||
|
||||
ansi-styles@^3.2.0, ansi-styles@^3.2.1:
|
||||
ansi-styles@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
|
||||
dependencies:
|
||||
@ -384,7 +384,7 @@ chalk@^1.1.3:
|
||||
strip-ansi "^3.0.0"
|
||||
supports-color "^2.0.0"
|
||||
|
||||
chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0:
|
||||
chalk@^2.0.1, chalk@^2.3.0:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e"
|
||||
dependencies:
|
||||
@ -392,6 +392,15 @@ chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0:
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
chalk@^2.4.1:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
||||
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
|
||||
dependencies:
|
||||
ansi-styles "^3.2.1"
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
check-error@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
|
||||
@ -532,9 +541,10 @@ cross-spawn@^5.0.1:
|
||||
shebang-command "^1.2.0"
|
||||
which "^1.2.9"
|
||||
|
||||
cross-spawn@^6.0.4:
|
||||
cross-spawn@^6.0.5:
|
||||
version "6.0.5"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
|
||||
integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
|
||||
dependencies:
|
||||
nice-try "^1.0.4"
|
||||
path-key "^2.0.1"
|
||||
@ -1630,16 +1640,17 @@ npm-packlist@^1.1.6:
|
||||
ignore-walk "^3.0.1"
|
||||
npm-bundled "^1.0.1"
|
||||
|
||||
npm-run-all@^4.1.3:
|
||||
version "4.1.3"
|
||||
resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.3.tgz#49f15b55a66bb4101664ce270cb18e7103f8f185"
|
||||
npm-run-all@^4.1.5:
|
||||
version "4.1.5"
|
||||
resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba"
|
||||
integrity sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==
|
||||
dependencies:
|
||||
ansi-styles "^3.2.0"
|
||||
chalk "^2.1.0"
|
||||
cross-spawn "^6.0.4"
|
||||
ansi-styles "^3.2.1"
|
||||
chalk "^2.4.1"
|
||||
cross-spawn "^6.0.5"
|
||||
memorystream "^0.3.1"
|
||||
minimatch "^3.0.4"
|
||||
ps-tree "^1.1.0"
|
||||
pidtree "^0.3.0"
|
||||
read-pkg "^3.0.0"
|
||||
shell-quote "^1.6.1"
|
||||
string.prototype.padend "^3.0.0"
|
||||
@ -1786,6 +1797,11 @@ pause-stream@0.0.11:
|
||||
dependencies:
|
||||
through "~2.3"
|
||||
|
||||
pidtree@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.0.tgz#f6fada10fccc9f99bf50e90d0b23d72c9ebc2e6b"
|
||||
integrity sha512-9CT4NFlDcosssyg8KVFltgokyKZIFjoBxw8CTGy+5F38Y1eQWrt8tRayiUOXE+zVKQnYu5BR8JjCtvK3BcnBhg==
|
||||
|
||||
pify@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
||||
|
@ -17,7 +17,7 @@ Here are some key features.
|
||||
* [Server-side Rendering](guide/universal): Angular Universal generates static application pages on the server through server-side rendering (SSR). This allows you to run your Angular app on the server in order to improve performance and show the first page quickly on mobile and low-powered devices, and also facilitate web crawlers.
|
||||
|
||||
* [Service Workers](guide/service-worker-intro): Use a service worker to reduce dependency on the network
|
||||
significantly improving the use experience.
|
||||
significantly improving the user experience.
|
||||
|
||||
## Domain-specific libraries
|
||||
|
||||
|
@ -240,7 +240,7 @@ The `@Injectable()` decorator is the standard decorator for service classes.
|
||||
|
||||
<div class="alert-is-helpful">
|
||||
|
||||
The decorator requirement is imposed by TypeScript. TypeScript normally discards parameter type information when it [transpiles]((guide/glossary#transpile) the code to JavaScript. TypeScript preserves this information if the class has a decorator and the `emitDecoratorMetadata` compiler option is set `true` in TypeScript's `tsconfig.json` configuration file. The CLI configures `tsconfig.json` with `emitDecoratorMetadata: true`.
|
||||
The decorator requirement is imposed by TypeScript. TypeScript normally discards parameter type information when it [transpiles](guide/glossary#transpile) the code to JavaScript. TypeScript preserves this information if the class has a decorator and the `emitDecoratorMetadata` compiler option is set `true` in TypeScript's `tsconfig.json` configuration file. The CLI configures `tsconfig.json` with `emitDecoratorMetadata: true`.
|
||||
|
||||
This means you're responsible for putting `@Injectable()` on your service classes.
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -13,6 +13,12 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- AngularMix -->
|
||||
<tr>
|
||||
<th><a href="https://angularmix.com/" title="AngularMix">AngularMix</a></th>
|
||||
<td>Orlando, Florida</td>
|
||||
<td>October 10-12, 2018</td>
|
||||
</tr>
|
||||
<!-- ReactiveConf -->
|
||||
<tr>
|
||||
<th><a href="https://reactiveconf.com/" title="ReactiveConf">ReactiveConf</a></th>
|
||||
@ -59,13 +65,13 @@
|
||||
<!-- ngconf 2018-->
|
||||
<tr>
|
||||
<th><a href="https://www.ng-conf.org/" title="ng-conf">ng-conf</a></th>
|
||||
<td>Salt Lake City, UT</td>
|
||||
<td>Salt Lake City, Utah</td>
|
||||
<td>April 18-20, 2018</td>
|
||||
</tr>
|
||||
<!-- WeRDevs-->
|
||||
<tr>
|
||||
<th><a href="https://www.wearedevelopers.com/" title="WeAreDevs">WeAreDevelopers</a></th>
|
||||
<td>Vienna</td>
|
||||
<td>Vienna, Austria</td>
|
||||
<td>May 16-18, 2018</td>
|
||||
</tr>
|
||||
<!-- ngJapan-->
|
||||
|
@ -30,7 +30,7 @@
|
||||
"postsetup-local": "yarn postsetup",
|
||||
"set-opensearch-url": "node --eval \"const sh = require('shelljs'); sh.set('-e'); sh.sed('-i', /PLACEHOLDER_URL/g, process.argv[1], 'dist/assets/opensearch.xml');\"",
|
||||
"test-pwa-score": "node scripts/test-pwa-score",
|
||||
"test-pwa-score-localhost": "run-p --race \"~~http-server dist -p 4200 --silent\" \"test-pwa-score http://localhost:4200 {1}\" --",
|
||||
"test-pwa-score-localhost": "run-p --race \"~~http-server dist -p 4200 --silent\" \"test-pwa-score http://localhost:4200 {1} {2}\" --",
|
||||
"example-e2e": "yarn example-check-local && node ./tools/examples/run-example-e2e",
|
||||
"example-lint": "tslint -c \"content/examples/tslint.json\" \"content/examples/**/*.ts\" -e \"content/examples/styleguide/**/*.avoid.ts\"",
|
||||
"example-use-local": "node tools/ng-packages-installer overwrite ./tools/examples/shared --debug",
|
||||
@ -71,17 +71,17 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^7.0.0-rc.0",
|
||||
"@angular/cdk": "6.0.2",
|
||||
"@angular/common": "^7.0.0-rc.0",
|
||||
"@angular/core": "^7.0.0-rc.0",
|
||||
"@angular/elements": "^7.0.0-rc.0",
|
||||
"@angular/forms": "^7.0.0-rc.0",
|
||||
"@angular/material": "6.0.2",
|
||||
"@angular/platform-browser": "^7.0.0-rc.0",
|
||||
"@angular/platform-browser-dynamic": "^7.0.0-rc.0",
|
||||
"@angular/router": "^7.0.0-rc.0",
|
||||
"@angular/service-worker": "^7.0.0-rc.0",
|
||||
"@angular/animations": "^7.0.0-rc.1",
|
||||
"@angular/cdk": "7.0.0-rc.1",
|
||||
"@angular/common": "^7.0.0-rc.1",
|
||||
"@angular/core": "^7.0.0-rc.1",
|
||||
"@angular/elements": "^7.0.0-rc.1",
|
||||
"@angular/forms": "^7.0.0-rc.1",
|
||||
"@angular/material": "7.0.0-rc.1",
|
||||
"@angular/platform-browser": "^7.0.0-rc.1",
|
||||
"@angular/platform-browser-dynamic": "^7.0.0-rc.1",
|
||||
"@angular/router": "^7.0.0-rc.1",
|
||||
"@angular/service-worker": "^7.0.0-rc.1",
|
||||
"@webcomponents/custom-elements": "^1.2.0",
|
||||
"classlist.js": "^1.1.20150312",
|
||||
"core-js": "^2.4.1",
|
||||
@ -91,14 +91,15 @@
|
||||
"zone.js": "^0.8.26"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^0.8.3",
|
||||
"@angular/cli": "^6.2.3",
|
||||
"@angular/compiler": "^7.0.0-rc.0",
|
||||
"@angular/compiler-cli": "^7.0.0-rc.0",
|
||||
"@angular/language-service": "^7.0.0-rc.0",
|
||||
"@angular-devkit/build-angular": "~0.9.0-rc.1",
|
||||
"@angular/cli": "^7.0.0-rc.2",
|
||||
"@angular/compiler": "^7.0.0-rc.1",
|
||||
"@angular/compiler-cli": "^7.0.0-rc.1",
|
||||
"@angular/language-service": "^7.0.0-rc.1",
|
||||
"@types/jasmine": "^2.5.52",
|
||||
"@types/jasminewd2": "^2.0.3",
|
||||
"@types/jasminewd2": "^2.0.4",
|
||||
"@types/node": "~6.0.60",
|
||||
"@yarnpkg/lockfile": "^1.1.0",
|
||||
"archiver": "^1.3.0",
|
||||
"canonical-path": "^0.0.2",
|
||||
"chalk": "^2.1.0",
|
||||
@ -111,7 +112,7 @@
|
||||
"entities": "^1.1.1",
|
||||
"eslint": "^3.19.0",
|
||||
"eslint-plugin-jasmine": "^2.2.0",
|
||||
"firebase-tools": "^3.2.1",
|
||||
"firebase-tools": "^5.1.1",
|
||||
"fs-extra": "^2.1.2",
|
||||
"globby": "^6.1.0",
|
||||
"hast-util-is-element": "^1.0.0",
|
||||
@ -132,10 +133,10 @@
|
||||
"karma-coverage-istanbul-reporter": "^1.3.0",
|
||||
"karma-jasmine": "^1.1.0",
|
||||
"karma-jasmine-html-reporter": "^0.2.2",
|
||||
"lighthouse": "^2.5.0",
|
||||
"lighthouse": "^3.2.1",
|
||||
"lodash": "^4.17.4",
|
||||
"lunr": "^2.1.0",
|
||||
"npm-run-all": "^4.1.3",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"protractor": "^5.2.0",
|
||||
"rehype": "^4.0.0",
|
||||
"rehype-slug": "^2.0.0",
|
||||
@ -147,7 +148,7 @@
|
||||
"tree-kill": "^1.1.0",
|
||||
"ts-node": "^3.3.0",
|
||||
"tslint": "~5.9.1",
|
||||
"typescript": "^3.1.1",
|
||||
"typescript": "^3.1.2",
|
||||
"uglify-js": "^3.0.15",
|
||||
"unist-util-filter": "^0.2.1",
|
||||
"unist-util-source": "^1.0.1",
|
||||
|
@ -3,9 +3,9 @@
|
||||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime": 3173,
|
||||
"main": 483428,
|
||||
"polyfills": 53920,
|
||||
"prettify": 14913
|
||||
"main": 494475,
|
||||
"polyfills": 53926,
|
||||
"prettify": 14917
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -100,8 +100,8 @@ fi
|
||||
yarn payload-size
|
||||
|
||||
# Deploy to Firebase
|
||||
firebase use "$projectId" --token "$firebaseToken"
|
||||
firebase deploy --message "Commit: $TRAVIS_COMMIT" --non-interactive --token "$firebaseToken"
|
||||
yarn firebase use "$projectId" --token "$firebaseToken"
|
||||
yarn firebase deploy --message "Commit: $TRAVIS_COMMIT" --non-interactive --token "$firebaseToken"
|
||||
|
||||
# Run PWA-score tests
|
||||
yarn test-pwa-score "$deployedUrl" "$AIO_MIN_PWA_SCORE"
|
||||
|
@ -11,13 +11,15 @@
|
||||
*/
|
||||
|
||||
// Imports
|
||||
const chromeLauncher = require('chrome-launcher');
|
||||
const lighthouse = require('lighthouse');
|
||||
const chromeLauncher = require('lighthouse/chrome-launcher');
|
||||
const printer = require('lighthouse/lighthouse-cli/printer');
|
||||
const config = require('lighthouse/lighthouse-core/config/default.js');
|
||||
const config = require('lighthouse/lighthouse-core/config/default-config.js');
|
||||
const logger = require('lighthouse-logger');
|
||||
|
||||
// Constants
|
||||
const CHROME_LAUNCH_OPTS = {};
|
||||
const LIGHTHOUSE_FLAGS = {logLevel: 'info'};
|
||||
const SKIPPED_HTTPS_AUDITS = ['redirects-http'];
|
||||
const VIEWER_URL = 'https://googlechrome.github.io/lighthouse/viewer/';
|
||||
|
||||
@ -26,6 +28,7 @@ const VIEWER_URL = 'https://googlechrome.github.io/lighthouse/viewer/';
|
||||
if (process.env.TRAVIS) {
|
||||
process.env.LIGHTHOUSE_CHROMIUM_PATH = process.env.CHROME_BIN;
|
||||
CHROME_LAUNCH_OPTS.chromeFlags = ['--no-sandbox'];
|
||||
LIGHTHOUSE_FLAGS.logLevel = 'error';
|
||||
}
|
||||
|
||||
// Run
|
||||
@ -42,18 +45,20 @@ function _main(args) {
|
||||
skipHttpsAudits(config);
|
||||
}
|
||||
|
||||
launchChromeAndRunLighthouse(url, {}, config).
|
||||
logger.setLevel(LIGHTHOUSE_FLAGS.logLevel);
|
||||
|
||||
launchChromeAndRunLighthouse(url, LIGHTHOUSE_FLAGS, config).
|
||||
then(results => processResults(results, logFile)).
|
||||
then(score => evaluateScore(minScore, score)).
|
||||
catch(onError);
|
||||
}
|
||||
|
||||
function evaluateScore(expectedScore, actualScore) {
|
||||
console.log('Lighthouse PWA score:');
|
||||
console.log(` - Expected: ${expectedScore} / 100 (or higher)`);
|
||||
console.log(` - Actual: ${actualScore} / 100`);
|
||||
console.log('\nLighthouse PWA score:');
|
||||
console.log(` - Expected: ${expectedScore.toFixed(0).padStart(3)} / 100 (or higher)`);
|
||||
console.log(` - Actual: ${actualScore.toFixed(0).padStart(3)} / 100\n`);
|
||||
|
||||
if (actualScore < expectedScore) {
|
||||
if (isNaN(actualScore) || (actualScore < expectedScore)) {
|
||||
throw new Error(`PWA score is too low. (${actualScore} < ${expectedScore})`);
|
||||
}
|
||||
}
|
||||
@ -87,20 +92,30 @@ function parseInput(args) {
|
||||
}
|
||||
|
||||
function processResults(results, logFile) {
|
||||
let promise = Promise.resolve();
|
||||
const categories = results.lhr.categories;
|
||||
const report = results.report;
|
||||
|
||||
if (logFile) {
|
||||
console.log(`Saving results in '${logFile}'...`);
|
||||
console.log(`(LightHouse viewer: ${VIEWER_URL})`);
|
||||
return Promise.resolve().
|
||||
then(() => {
|
||||
if (logFile) {
|
||||
console.log(`Saving results in '${logFile}'...`);
|
||||
console.log(`(LightHouse viewer: ${VIEWER_URL})`);
|
||||
|
||||
// Remove the artifacts, which are not necessary for the report.
|
||||
// (Saves ~1,500,000 lines of formatted JSON output \o/)
|
||||
results.artifacts = undefined;
|
||||
return printer.write(report, printer.OutputMode.json, logFile);
|
||||
}
|
||||
}).
|
||||
then(() => {
|
||||
const categoryData = Object.keys(categories).map(name => categories[name]);
|
||||
const maxTitleLen = Math.max(...categoryData.map(({title}) => title.length));
|
||||
|
||||
promise = printer.write(results, 'json', logFile);
|
||||
}
|
||||
|
||||
return promise.then(() => Math.round(results.score));
|
||||
console.log('\nAudit scores:');
|
||||
categoryData.forEach(({title, score}) => {
|
||||
const paddedTitle = `${title}:`.padEnd(maxTitleLen + 1);
|
||||
const paddedScore = (score * 100).toFixed(0).padStart(3);
|
||||
console.log(` - ${paddedTitle} ${paddedScore} / 100`);
|
||||
});
|
||||
}).
|
||||
then(() => categories.pwa.score * 100);
|
||||
}
|
||||
|
||||
function skipHttpsAudits(config) {
|
||||
|
@ -164,10 +164,10 @@ describe('AppComponent', () => {
|
||||
|
||||
describe('onScroll', () => {
|
||||
it('should update `tocMaxHeight` accordingly', () => {
|
||||
expect(component.tocMaxHeight).toBeUndefined();
|
||||
|
||||
component.tocMaxHeight = '';
|
||||
component.onScroll();
|
||||
expect(component.tocMaxHeight).toBeGreaterThan(0);
|
||||
|
||||
expect(component.tocMaxHeight).toMatch(/^\d+\.\d{2}$/);
|
||||
});
|
||||
});
|
||||
|
||||
@ -654,12 +654,13 @@ describe('AppComponent', () => {
|
||||
it('should update the TOC container\'s `maxHeight` based on `tocMaxHeight`', () => {
|
||||
setHasFloatingToc(true);
|
||||
|
||||
expect(tocContainer!.style['max-height']).toBe('');
|
||||
|
||||
component.tocMaxHeight = '100';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(tocContainer!.style['max-height']).toBe('100px');
|
||||
|
||||
component.tocMaxHeight = '200';
|
||||
fixture.detectChanges();
|
||||
expect(tocContainer!.style['max-height']).toBe('200px');
|
||||
});
|
||||
|
||||
it('should restrain scrolling inside the ToC container', () => {
|
||||
|
@ -349,12 +349,17 @@ export class AppComponent implements OnInit {
|
||||
@HostListener('window:scroll')
|
||||
onScroll() {
|
||||
if (!this.tocMaxHeightOffset) {
|
||||
// Must wait until now for mat-toolbar to be measurable.
|
||||
// Must wait until `mat-toolbar` is measurable.
|
||||
const el = this.hostElement.nativeElement as Element;
|
||||
this.tocMaxHeightOffset =
|
||||
el.querySelector('footer')!.clientHeight +
|
||||
el.querySelector('.app-toolbar')!.clientHeight +
|
||||
24; // fudge margin
|
||||
const headerEl = el.querySelector('.app-toolbar');
|
||||
const footerEl = el.querySelector('footer');
|
||||
|
||||
if (headerEl && footerEl) {
|
||||
this.tocMaxHeightOffset =
|
||||
headerEl.clientHeight +
|
||||
footerEl.clientHeight +
|
||||
24; // fudge margin
|
||||
}
|
||||
}
|
||||
|
||||
this.tocMaxHeight = (document.body.scrollHeight - window.pageYOffset - this.tocMaxHeightOffset).toFixed(2);
|
||||
|
@ -115,6 +115,7 @@ button.vertical-menu-item {
|
||||
}
|
||||
|
||||
.heading-children.collapsed {
|
||||
overflow: hidden; // Needed to prevent unnecessary sidenav scrollbar.
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
max-height: 1px; // Must have measurement to transition height.
|
||||
|
@ -53,7 +53,7 @@
|
||||
"@types/angular-route": "^1.3.5",
|
||||
"@types/express": "^4.0.35",
|
||||
"@types/jasmine": "~2.8.0",
|
||||
"@types/jasminewd2": "^2.0.3",
|
||||
"@types/jasminewd2": "^2.0.4",
|
||||
"@types/jquery": "^3.3.4",
|
||||
"@types/node": "^6.0.45",
|
||||
"canonical-path": "0.0.2",
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -2,11 +2,14 @@
|
||||
|
||||
const chalk = require('chalk');
|
||||
const fs = require('fs-extra');
|
||||
const lockfile = require('@yarnpkg/lockfile');
|
||||
const path = require('canonical-path');
|
||||
const semver = require('semver');
|
||||
const shelljs = require('shelljs');
|
||||
const yargs = require('yargs');
|
||||
|
||||
const PACKAGE_JSON = 'package.json';
|
||||
const YARN_LOCK = 'yarn.lock';
|
||||
const LOCAL_MARKER_PATH = 'node_modules/_local_.json';
|
||||
const PACKAGE_JSON_REGEX = /^[^/]+\/package\.json$/;
|
||||
|
||||
@ -62,8 +65,10 @@ class NgPackagesInstaller {
|
||||
* contents and acts as an indicator that dependencies have been overridden.
|
||||
*/
|
||||
installLocalDependencies() {
|
||||
if (this._checkLocalMarker() !== true || this.force) {
|
||||
if (this.force || !this._checkLocalMarker()) {
|
||||
const pathToPackageConfig = path.resolve(this.projectDir, PACKAGE_JSON);
|
||||
const pathToLockfile = path.resolve(this.projectDir, YARN_LOCK);
|
||||
const parsedLockfile = this._parseLockfile(pathToLockfile);
|
||||
const packages = this._getDistPackages();
|
||||
|
||||
try {
|
||||
@ -88,17 +93,17 @@ class NgPackagesInstaller {
|
||||
});
|
||||
});
|
||||
|
||||
fs.writeFileSync(pkg.packageJsonPath, JSON.stringify(tmpConfig));
|
||||
fs.writeFileSync(pkg.packageJsonPath, JSON.stringify(tmpConfig, null, 2));
|
||||
});
|
||||
|
||||
const packageConfigFile = fs.readFileSync(pathToPackageConfig);
|
||||
const packageConfigFile = fs.readFileSync(pathToPackageConfig, 'utf8');
|
||||
const packageConfig = JSON.parse(packageConfigFile);
|
||||
|
||||
const [dependencies, peers] = this._collectDependencies(packageConfig.dependencies || {}, packages);
|
||||
const [devDependencies, devPeers] = this._collectDependencies(packageConfig.devDependencies || {}, packages);
|
||||
|
||||
this._assignPeerDependencies(peers, dependencies, devDependencies);
|
||||
this._assignPeerDependencies(devPeers, dependencies, devDependencies);
|
||||
this._assignPeerDependencies(peers, dependencies, devDependencies, parsedLockfile);
|
||||
this._assignPeerDependencies(devPeers, dependencies, devDependencies, parsedLockfile);
|
||||
|
||||
const localPackageConfig = Object.assign(Object.create(null), packageConfig, { dependencies, devDependencies });
|
||||
localPackageConfig.__angular = { local: true };
|
||||
@ -107,7 +112,7 @@ class NgPackagesInstaller {
|
||||
try {
|
||||
this._log(`Writing temporary local ${PACKAGE_JSON} to ${pathToPackageConfig}`);
|
||||
fs.writeFileSync(pathToPackageConfig, localPackageConfigJson);
|
||||
this._installDeps('--no-lockfile', '--check-files');
|
||||
this._installDeps('--pure-lockfile', '--check-files');
|
||||
this._setLocalMarker(localPackageConfigJson);
|
||||
} finally {
|
||||
this._log(`Restoring original ${PACKAGE_JSON} to ${pathToPackageConfig}`);
|
||||
@ -118,7 +123,7 @@ class NgPackagesInstaller {
|
||||
this._log(`Restoring original ${PACKAGE_JSON} for local Angular packages.`);
|
||||
Object.keys(packages).forEach(key => {
|
||||
const pkg = packages[key];
|
||||
fs.writeFileSync(pkg.packageJsonPath, JSON.stringify(pkg.config));
|
||||
fs.writeFileSync(pkg.packageJsonPath, JSON.stringify(pkg.config, null, 2));
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -134,15 +139,23 @@ class NgPackagesInstaller {
|
||||
|
||||
// Protected helpers
|
||||
|
||||
_assignPeerDependencies(peerDependencies, dependencies, devDependencies) {
|
||||
_assignPeerDependencies(peerDependencies, dependencies, devDependencies, parsedLockfile) {
|
||||
Object.keys(peerDependencies).forEach(key => {
|
||||
const peerDepRange = peerDependencies[key];
|
||||
|
||||
// Ignore peerDependencies whose range is already satisfied by current version in lockfile.
|
||||
const originalRange = dependencies[key] || devDependencies[key];
|
||||
const lockfileVersion = originalRange && parsedLockfile[`${key}@${originalRange}`].version;
|
||||
|
||||
if (lockfileVersion && semver.satisfies(lockfileVersion, peerDepRange)) return;
|
||||
|
||||
// If there is already an equivalent dependency then override it - otherwise assign/override the devDependency
|
||||
if (dependencies[key]) {
|
||||
this._log(`Overriding dependency with peerDependency: ${key}: ${peerDependencies[key]}`);
|
||||
dependencies[key] = peerDependencies[key];
|
||||
this._log(`Overriding dependency with peerDependency: ${key}: ${peerDepRange}`);
|
||||
dependencies[key] = peerDepRange;
|
||||
} else {
|
||||
this._log(`${devDependencies[key] ? 'Overriding' : 'Assigning'} devDependency with peerDependency: ${key}: ${peerDependencies[key]}`);
|
||||
devDependencies[key] = peerDependencies[key];
|
||||
this._log(`${devDependencies[key] ? 'Overriding' : 'Assigning'} devDependency with peerDependency: ${key}: ${peerDepRange}`);
|
||||
devDependencies[key] = peerDepRange;
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -166,11 +179,6 @@ class NgPackagesInstaller {
|
||||
}
|
||||
});
|
||||
|
||||
// FIXME: Temporarily use RxJS from root `node_modules/`.
|
||||
if (peerDependencies.rxjs) {
|
||||
peerDependencies.rxjs = `file:${ANGULAR_ROOT_DIR}/node_modules/rxjs`;
|
||||
}
|
||||
|
||||
return [mergedDependencies, peerDependencies];
|
||||
}
|
||||
|
||||
@ -226,6 +234,20 @@ class NgPackagesInstaller {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and return a `yarn.lock` file.
|
||||
*/
|
||||
_parseLockfile(lockfilePath) {
|
||||
const lockfileContent = fs.readFileSync(lockfilePath, 'utf8');
|
||||
const parsed = lockfile.parse(lockfileContent);
|
||||
|
||||
if (parsed.type !== 'success') {
|
||||
throw new Error(`[${NgPackagesInstaller.name}]: Error parsing lockfile '${lockfilePath}' (result type: ${parsed.type}).`);
|
||||
}
|
||||
|
||||
return parsed.object;
|
||||
}
|
||||
|
||||
_printWarning() {
|
||||
const relativeScriptPath = path.relative('.', __filename.replace(/\.js$/, ''));
|
||||
const absoluteProjectDir = path.resolve(this.projectDir);
|
||||
|
@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs-extra');
|
||||
const lockfile = require('@yarnpkg/lockfile');
|
||||
const path = require('canonical-path');
|
||||
const shelljs = require('shelljs');
|
||||
|
||||
@ -11,6 +12,7 @@ describe('NgPackagesInstaller', () => {
|
||||
const absoluteRootDir = path.resolve(rootDir);
|
||||
const nodeModulesDir = path.resolve(absoluteRootDir, 'node_modules');
|
||||
const packageJsonPath = path.resolve(absoluteRootDir, 'package.json');
|
||||
const yarnLockPath = path.resolve(absoluteRootDir, 'yarn.lock');
|
||||
const packagesDir = path.resolve(path.resolve(__dirname, '../../../dist/packages-dist'));
|
||||
const toolsDir = path.resolve(path.resolve(__dirname, '../../../dist/tools/@angular'));
|
||||
let installer;
|
||||
@ -52,13 +54,26 @@ describe('NgPackagesInstaller', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(installer, '_checkLocalMarker');
|
||||
spyOn(installer, '_installDeps');
|
||||
spyOn(installer, '_setLocalMarker');
|
||||
|
||||
spyOn(installer, '_parseLockfile').and.returnValue({
|
||||
'rxjs@^6.3.0': {version: '6.3.3'},
|
||||
'zone.js@^0.8.26': {version: '0.8.27'}
|
||||
});
|
||||
|
||||
// These are the packages that are "found" in the dist directory
|
||||
dummyNgPackages = {
|
||||
'@angular/core': {
|
||||
parentDir: packagesDir,
|
||||
packageJsonPath: `${packagesDir}/core/package.json`,
|
||||
config: { peerDependencies: { 'some-package': '5.0.1' } }
|
||||
config: {
|
||||
peerDependencies: {
|
||||
'rxjs': '^6.4.0',
|
||||
'some-package': '5.0.1',
|
||||
'zone.js': '~0.8.26'
|
||||
}
|
||||
}
|
||||
},
|
||||
'@angular/common': {
|
||||
parentDir: packagesDir,
|
||||
@ -93,10 +108,12 @@ describe('NgPackagesInstaller', () => {
|
||||
dummyPackage = {
|
||||
dependencies: {
|
||||
'@angular/core': '4.4.1',
|
||||
'@angular/common': '4.4.1'
|
||||
'@angular/common': '4.4.1',
|
||||
rxjs: '^6.3.0'
|
||||
},
|
||||
devDependencies: {
|
||||
'@angular/compiler-cli': '4.4.1'
|
||||
'@angular/compiler-cli': '4.4.1',
|
||||
'zone.js': '^0.8.26'
|
||||
}
|
||||
};
|
||||
dummyPackageJson = JSON.stringify(dummyPackage);
|
||||
@ -104,14 +121,23 @@ describe('NgPackagesInstaller', () => {
|
||||
|
||||
// This is the package.json that is temporarily written to the "test" folder
|
||||
// Note that the Angular (dev)dependencies have been modified to use a "file:" path
|
||||
// And that the peerDependencies from `dummyNgPackages` have been added as (dev)dependencies.
|
||||
// And that the peerDependencies from `dummyNgPackages` have been updated or added as
|
||||
// (dev)dependencies (unless the current version in lockfile satisfies semver).
|
||||
//
|
||||
// For example, `zone.js@0.8.27` (from lockfile) satisfies `zone.js@~0.8.26` (from
|
||||
// `@angular/core`), thus `zone.js: ^0.8.26` (from original `package.json`) is retained.
|
||||
// In contrast, `rxjs@6.3.3` (from lockfile) does not satisfy `rxjs@^6.4.0 (from
|
||||
// `@angular/core`), thus `rxjs: ^6.3.0` (from original `package.json`) is replaced with
|
||||
// `rxjs: ^6.4.0` (from `@angular/core`).
|
||||
expectedModifiedPackage = {
|
||||
dependencies: {
|
||||
'@angular/core': `file:${packagesDir}/core`,
|
||||
'@angular/common': `file:${packagesDir}/common`
|
||||
'@angular/common': `file:${packagesDir}/common`,
|
||||
'rxjs': '^6.4.0'
|
||||
},
|
||||
devDependencies: {
|
||||
'@angular/compiler-cli': `file:${toolsDir}/compiler-cli`,
|
||||
'zone.js': '^0.8.26',
|
||||
'some-package': '5.0.1',
|
||||
typescript: '^2.4.2'
|
||||
},
|
||||
@ -121,12 +147,20 @@ describe('NgPackagesInstaller', () => {
|
||||
});
|
||||
|
||||
describe('when there is a local package marker', () => {
|
||||
beforeEach(() => installer._checkLocalMarker.and.returnValue(true));
|
||||
|
||||
it('should not continue processing', () => {
|
||||
installer._checkLocalMarker.and.returnValue(true);
|
||||
installer.installLocalDependencies();
|
||||
expect(installer._checkLocalMarker).toHaveBeenCalled();
|
||||
expect(installer._getDistPackages).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should continue processing (without checking for local marker) if `force` is true', () => {
|
||||
installer.force = true;
|
||||
installer.installLocalDependencies();
|
||||
expect(installer._checkLocalMarker).not.toHaveBeenCalled();
|
||||
expect(installer._getDistPackages).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when there is no local package marker', () => {
|
||||
@ -135,14 +169,14 @@ describe('NgPackagesInstaller', () => {
|
||||
beforeEach(() => {
|
||||
log = [];
|
||||
fs.writeFileSync.and.callFake((filePath, contents) => filePath === packageJsonPath && log.push(`writeFile: ${contents}`));
|
||||
spyOn(installer, '_installDeps').and.callFake(() => log.push('installDeps:'));
|
||||
spyOn(installer, '_setLocalMarker');
|
||||
installer._installDeps.and.callFake((...args) => log.push(`installDeps: ${args.join(' ')}`));
|
||||
installer._checkLocalMarker.and.returnValue(false);
|
||||
installer.installLocalDependencies();
|
||||
});
|
||||
|
||||
it('should get the dist packages', () => {
|
||||
it('should parse the lockfile and get the dist packages', () => {
|
||||
expect(installer._checkLocalMarker).toHaveBeenCalled();
|
||||
expect(installer._parseLockfile).toHaveBeenCalledWith(yarnLockPath);
|
||||
expect(installer._getDistPackages).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@ -150,31 +184,32 @@ describe('NgPackagesInstaller', () => {
|
||||
const pkgJsonFor = pkgName => dummyNgPackages[`@angular/${pkgName}`].packageJsonPath;
|
||||
const pkgConfigFor = pkgName => copyJsonObj(dummyNgPackages[`@angular/${pkgName}`].config);
|
||||
const overwriteConfigFor = (pkgName, newProps) => Object.assign(pkgConfigFor(pkgName), newProps);
|
||||
const stringifyConfig = config => JSON.stringify(config, null, 2);
|
||||
|
||||
const allArgs = fs.writeFileSync.calls.allArgs();
|
||||
const firstFiveArgs = allArgs.slice(0, 5);
|
||||
const lastFiveArgs = allArgs.slice(-5);
|
||||
|
||||
expect(firstFiveArgs).toEqual([
|
||||
[pkgJsonFor('core'), JSON.stringify(overwriteConfigFor('core', {private: true}))],
|
||||
[pkgJsonFor('common'), JSON.stringify(overwriteConfigFor('common', {private: true}))],
|
||||
[pkgJsonFor('compiler'), JSON.stringify(overwriteConfigFor('compiler', {private: true}))],
|
||||
[pkgJsonFor('compiler-cli'), JSON.stringify(overwriteConfigFor('compiler-cli', {
|
||||
[pkgJsonFor('core'), stringifyConfig(overwriteConfigFor('core', {private: true}))],
|
||||
[pkgJsonFor('common'), stringifyConfig(overwriteConfigFor('common', {private: true}))],
|
||||
[pkgJsonFor('compiler'), stringifyConfig(overwriteConfigFor('compiler', {private: true}))],
|
||||
[pkgJsonFor('compiler-cli'), stringifyConfig(overwriteConfigFor('compiler-cli', {
|
||||
private: true,
|
||||
dependencies: { '@angular/tsc-wrapped': `file:${toolsDir}/tsc-wrapped` }
|
||||
}))],
|
||||
[pkgJsonFor('tsc-wrapped'), JSON.stringify(overwriteConfigFor('tsc-wrapped', {
|
||||
[pkgJsonFor('tsc-wrapped'), stringifyConfig(overwriteConfigFor('tsc-wrapped', {
|
||||
private: true,
|
||||
devDependencies: { '@angular/common': `file:${packagesDir}/common` }
|
||||
}))],
|
||||
]);
|
||||
|
||||
expect(lastFiveArgs).toEqual(['core', 'common', 'compiler', 'compiler-cli', 'tsc-wrapped']
|
||||
.map(pkgName => [pkgJsonFor(pkgName), JSON.stringify(pkgConfigFor(pkgName))]));
|
||||
.map(pkgName => [pkgJsonFor(pkgName), stringifyConfig(pkgConfigFor(pkgName))]));
|
||||
});
|
||||
|
||||
it('should load the package.json', () => {
|
||||
expect(fs.readFileSync).toHaveBeenCalledWith(packageJsonPath);
|
||||
expect(fs.readFileSync).toHaveBeenCalledWith(packageJsonPath, 'utf8');
|
||||
});
|
||||
|
||||
it('should overwrite package.json with modified config', () => {
|
||||
@ -188,7 +223,7 @@ describe('NgPackagesInstaller', () => {
|
||||
it('should overwrite package.json, then install deps, then restore original package.json', () => {
|
||||
expect(log).toEqual([
|
||||
`writeFile: ${expectedModifiedPackageJson}`,
|
||||
`installDeps:`,
|
||||
`installDeps: --pure-lockfile --check-files`,
|
||||
`writeFile: ${dummyPackageJson}`
|
||||
]);
|
||||
});
|
||||
@ -264,6 +299,42 @@ describe('NgPackagesInstaller', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('_parseLockfile()', () => {
|
||||
let originalLockfileParseDescriptor;
|
||||
|
||||
beforeEach(() => {
|
||||
// Workaround for `lockfile.parse()` being non-writable.
|
||||
let parse = lockfile.parse;
|
||||
originalLockfileParseDescriptor = Object.getOwnPropertyDescriptor(lockfile, 'parse');
|
||||
Object.defineProperty(lockfile, 'parse', {
|
||||
get() { return parse; },
|
||||
set(newParse) { parse = newParse; },
|
||||
});
|
||||
|
||||
fs.readFileSync.and.returnValue('mock content');
|
||||
spyOn(lockfile, 'parse').and.returnValue({type: 'success', object: {foo: {version: 'bar'}}});
|
||||
});
|
||||
|
||||
afterEach(() => Object.defineProperty(lockfile, 'parse', originalLockfileParseDescriptor));
|
||||
|
||||
it('should parse the specified lockfile', () => {
|
||||
installer._parseLockfile('/foo/bar/yarn.lock');
|
||||
expect(fs.readFileSync).toHaveBeenCalledWith('/foo/bar/yarn.lock', 'utf8');
|
||||
expect(lockfile.parse).toHaveBeenCalledWith('mock content');
|
||||
});
|
||||
|
||||
it('should throw if parsing the lockfile fails', () => {
|
||||
lockfile.parse.and.returnValue({type: 'not success'});
|
||||
expect(() => installer._parseLockfile('/foo/bar/yarn.lock')).toThrowError(
|
||||
'[NgPackagesInstaller]: Error parsing lockfile \'/foo/bar/yarn.lock\' (result type: not success).');
|
||||
});
|
||||
|
||||
it('should return the parsed lockfile content as an object', () => {
|
||||
const parsed = installer._parseLockfile('/foo/bar/yarn.lock');
|
||||
expect(parsed).toEqual({foo: {version: 'bar'}});
|
||||
});
|
||||
});
|
||||
|
||||
describe('_printWarning()', () => {
|
||||
it('should mention the message passed in the warning', () => {
|
||||
installer._printWarning();
|
||||
|
@ -208,7 +208,7 @@
|
||||
{% set nonInternalMembers = ancestor.doc[collectionName] | filterByPropertyValue('internal', undefined) %}
|
||||
{% if nonInternalMembers.length -%}
|
||||
<section class="inherited-members-list">
|
||||
<h3>Inherited from <code>{$ ancestor.doc.id $}</code></h3>
|
||||
<h3>Inherited from <code><a class="code-anchor" href="{$ ancestor.doc.path $}">{$ ancestor.doc.name $}</a></code></h3>
|
||||
<ul>
|
||||
{% for member in nonInternalMembers %}
|
||||
<li>
|
||||
|
3999
aio/yarn.lock
3999
aio/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -32,7 +32,7 @@
|
||||
"@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli",
|
||||
"@angular/language-service": "file:../../dist/packages-dist/language-service",
|
||||
"@types/jasmine": "~2.8.3",
|
||||
"@types/jasminewd2": "~2.0.2",
|
||||
"@types/jasminewd2": "~2.0.4",
|
||||
"@types/node": "~6.0.60",
|
||||
"codelyzer": "^4.3.0",
|
||||
"jasmine-core": "~2.8.0",
|
||||
|
@ -206,9 +206,9 @@
|
||||
version "2.8.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-2.8.6.tgz#14445b6a1613cf4e05dd61c3c3256d0e95c0421e"
|
||||
|
||||
"@types/jasminewd2@~2.0.2":
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/jasminewd2/-/jasminewd2-2.0.3.tgz#0d2886b0cbdae4c0eeba55e30792f584bf040a95"
|
||||
"@types/jasminewd2@~2.0.4":
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/jasminewd2/-/jasminewd2-2.0.4.tgz#12422ee719f372d30c3cc7d99cc72dadba6ace01"
|
||||
dependencies:
|
||||
"@types/jasmine" "*"
|
||||
|
||||
|
1
modules/types.d.ts
vendored
1
modules/types.d.ts
vendored
@ -13,6 +13,5 @@
|
||||
/// <reference types="jasminewd2" />
|
||||
/// <reference types="node" />
|
||||
/// <reference types="zone.js" />
|
||||
/// <reference path="../tools/types-ext/jasminewd2.d.ts" />
|
||||
/// <reference path="./es6-subset.d.ts" />
|
||||
/// <reference path="./system.d.ts" />
|
||||
|
@ -43,12 +43,12 @@
|
||||
"@types/angular": "^1.6.47",
|
||||
"@types/base64-js": "1.2.5",
|
||||
"@types/chai": "^4.1.2",
|
||||
"@types/chokidar": "1.7.3",
|
||||
"@types/chokidar": "^1.7.5",
|
||||
"@types/diff": "^3.2.2",
|
||||
"@types/fs-extra": "4.0.2",
|
||||
"@types/hammerjs": "2.0.35",
|
||||
"@types/jasmine": "^2.8.8",
|
||||
"@types/jasminewd2": "^2.0.3",
|
||||
"@types/jasminewd2": "^2.0.4",
|
||||
"@types/minimist": "^1.2.0",
|
||||
"@types/node": "6.0.88",
|
||||
"@types/selenium-webdriver": "3.0.7",
|
||||
@ -66,7 +66,7 @@
|
||||
"bower": "1.8.2",
|
||||
"browserstacktunnel-wrapper": "2.0.1",
|
||||
"canonical-path": "0.0.2",
|
||||
"chokidar": "1.7.0",
|
||||
"chokidar": "^2.1.1",
|
||||
"clang-format": "1.0.41",
|
||||
"cldr": "4.10.0",
|
||||
"cldr-data-downloader": "0.3.2",
|
||||
|
@ -12,7 +12,7 @@
|
||||
"reflect-metadata": "^0.1.2",
|
||||
"minimist": "^1.2.0",
|
||||
"tsickle": "^0.32.1",
|
||||
"chokidar": "^1.4.2"
|
||||
"chokidar": "^2.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=2.7.2 <2.10",
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
"include": [
|
||||
"../../node_modules/@types/jasminewd2/index.d.ts",
|
||||
"../../tools/types-ext/jasminewd2.d.ts",
|
||||
"../types.d.ts",
|
||||
"**/*.ts"
|
||||
]
|
||||
|
@ -10,7 +10,6 @@
|
||||
|
||||
"include": [
|
||||
"../../node_modules/@types/jasminewd2/index.d.ts",
|
||||
"../../tools/types-ext/jasminewd2.d.ts",
|
||||
"../types.d.ts",
|
||||
"**/e2e_test/*"
|
||||
],
|
||||
|
@ -214,24 +214,29 @@ export interface INgModelController {
|
||||
$name: string;
|
||||
}
|
||||
|
||||
function noNg() {
|
||||
function noNg(): never {
|
||||
throw new Error('AngularJS v1.x is not loaded!');
|
||||
}
|
||||
|
||||
const noNgElement: typeof angular.element = (() => noNg()) as any;
|
||||
noNgElement.cleanData = noNg;
|
||||
|
||||
let angular: {
|
||||
bootstrap: (e: Element, modules: (string | IInjectable)[], config?: IAngularBootstrapConfig) =>
|
||||
IInjectorService,
|
||||
module: (prefix: string, dependencies?: string[]) => IModule,
|
||||
element: (e: string | Element | Document | IAugmentedJQuery) => IAugmentedJQuery,
|
||||
element: {
|
||||
(e: string | Element | Document | IAugmentedJQuery): IAugmentedJQuery;
|
||||
cleanData: (nodes: Node[] | NodeList) => void;
|
||||
},
|
||||
version: {major: number},
|
||||
resumeBootstrap: () => void,
|
||||
getTestability: (e: Element) => ITestabilityService
|
||||
} = <any>{
|
||||
} = {
|
||||
bootstrap: noNg,
|
||||
module: noNg,
|
||||
element: noNg,
|
||||
version: undefined,
|
||||
element: noNgElement,
|
||||
version: undefined as any,
|
||||
resumeBootstrap: noNg,
|
||||
getTestability: noNg
|
||||
};
|
||||
@ -281,7 +286,8 @@ export const bootstrap: typeof angular.bootstrap = (e, modules, config?) =>
|
||||
export const module: typeof angular.module = (prefix, dependencies?) =>
|
||||
angular.module(prefix, dependencies);
|
||||
|
||||
export const element: typeof angular.element = e => angular.element(e);
|
||||
export const element: typeof angular.element = (e => angular.element(e)) as typeof angular.element;
|
||||
element.cleanData = nodes => angular.element.cleanData(nodes);
|
||||
|
||||
export const resumeBootstrap: typeof angular.resumeBootstrap = () => angular.resumeBootstrap();
|
||||
|
||||
|
@ -124,7 +124,15 @@ export class UpgradeHelper {
|
||||
controllerInstance.$onDestroy();
|
||||
}
|
||||
$scope.$destroy();
|
||||
this.$element.triggerHandler !('$destroy');
|
||||
|
||||
// Clean the jQuery/jqLite data on the component+child elements.
|
||||
// Equivelent to how jQuery/jqLite invoke `cleanData` on an Element (this.element)
|
||||
// https://github.com/jquery/jquery/blob/e743cbd28553267f955f71ea7248377915613fd9/src/manipulation.js#L223
|
||||
// https://github.com/angular/angular.js/blob/26ddc5f830f902a3d22f4b2aab70d86d4d688c82/src/jqLite.js#L306-L312
|
||||
// `cleanData` will invoke the AngularJS `$destroy` DOM event
|
||||
// https://github.com/angular/angular.js/blob/26ddc5f830f902a3d22f4b2aab70d86d4d688c82/src/Angular.js#L1911-L1924
|
||||
angular.element.cleanData([this.element]);
|
||||
angular.element.cleanData(this.element.querySelectorAll('*'));
|
||||
}
|
||||
|
||||
prepareTransclusion(): angular.ILinkFn|undefined {
|
||||
|
@ -12,6 +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 {UpgradeAdapter, UpgradeAdapterRef} from '@angular/upgrade/src/dynamic/upgrade_adapter';
|
||||
|
||||
import {$apply, $digest, html, multiTrim, withEachNg1Version} from './test_helpers';
|
||||
|
||||
declare global {
|
||||
@ -2177,9 +2178,10 @@ withEachNg1Version(() => {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should emit `$destroy` on `$element`', fakeAsync(() => {
|
||||
it('should emit `$destroy` on `$element` and descendants', fakeAsync(() => {
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||
const elementDestroyListener = jasmine.createSpy('elementDestroyListener');
|
||||
const descendantDestroyListener = jasmine.createSpy('descendantDestroyListener');
|
||||
let ng2ComponentInstance: Ng2Component;
|
||||
|
||||
@Component({selector: 'ng2', template: '<div *ngIf="!ng2Destroy"><ng1></ng1></div>'})
|
||||
@ -2194,9 +2196,13 @@ withEachNg1Version(() => {
|
||||
// Mocking animations (via `ngAnimateMock`) avoids the issue.
|
||||
angular.module('ng1', ['ngAnimateMock'])
|
||||
.component('ng1', {
|
||||
controller: function($element: angular.IAugmentedJQuery) {
|
||||
$element.on !('$destroy', elementDestroyListener);
|
||||
controller: class {
|
||||
constructor(private $element: angular.IAugmentedJQuery) {} $onInit() {
|
||||
this.$element.on !('$destroy', elementDestroyListener);
|
||||
this.$element.contents !().on !('$destroy', descendantDestroyListener);
|
||||
}
|
||||
},
|
||||
template: '<div></div>'
|
||||
})
|
||||
.directive('ng2', adapter.downgradeNg2Component(Ng2Component));
|
||||
|
||||
@ -2210,14 +2216,150 @@ withEachNg1Version(() => {
|
||||
const element = html('<ng2></ng2>');
|
||||
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
||||
const $rootScope = ref.ng1RootScope as any;
|
||||
tick();
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(elementDestroyListener).not.toHaveBeenCalled();
|
||||
expect(descendantDestroyListener).not.toHaveBeenCalled();
|
||||
|
||||
ng2ComponentInstance.ng2Destroy = true;
|
||||
tick();
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(elementDestroyListener).toHaveBeenCalledTimes(1);
|
||||
expect(descendantDestroyListener).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
}));
|
||||
|
||||
it('should clear data on `$element` and descendants', fakeAsync(() => {
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||
let ng1ComponentElement: angular.IAugmentedJQuery;
|
||||
let ng2ComponentAInstance: Ng2ComponentA;
|
||||
|
||||
// Define `ng1Component`
|
||||
const ng1Component: angular.IComponent = {
|
||||
controller: class {
|
||||
constructor(private $element: angular.IAugmentedJQuery) {} $onInit() {
|
||||
this.$element.data !('test', 1);
|
||||
this.$element.contents !().data !('test', 2);
|
||||
|
||||
ng1ComponentElement = this.$element;
|
||||
}
|
||||
},
|
||||
template: '<div></div>'
|
||||
};
|
||||
|
||||
// Define `Ng2Component`
|
||||
@Component({selector: 'ng2A', template: '<ng2B *ngIf="!destroyIt"></ng2B>'})
|
||||
class Ng2ComponentA {
|
||||
destroyIt = false;
|
||||
|
||||
constructor() { ng2ComponentAInstance = this; }
|
||||
}
|
||||
|
||||
@Component({selector: 'ng2B', template: '<ng1></ng1>'})
|
||||
class Ng2ComponentB {
|
||||
}
|
||||
|
||||
// Define `ng1Module`
|
||||
angular.module('ng1Module', [])
|
||||
.component('ng1', ng1Component)
|
||||
.directive('ng2A', adapter.downgradeNg2Component(Ng2ComponentA));
|
||||
|
||||
// Define `Ng2Module`
|
||||
@NgModule({
|
||||
declarations: [adapter.upgradeNg1Component('ng1'), Ng2ComponentA, Ng2ComponentB],
|
||||
entryComponents: [Ng2ComponentA],
|
||||
imports: [BrowserModule]
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
// Bootstrap
|
||||
const element = html(`<ng2-a></ng2-a>`);
|
||||
|
||||
adapter.bootstrap(element, ['ng1Module']).ready((ref) => {
|
||||
const $rootScope = ref.ng1RootScope as any;
|
||||
tick();
|
||||
$rootScope.$digest();
|
||||
expect(ng1ComponentElement.data !('test')).toBe(1);
|
||||
expect(ng1ComponentElement.contents !().data !('test')).toBe(2);
|
||||
|
||||
ng2ComponentAInstance.destroyIt = true;
|
||||
tick();
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(ng1ComponentElement.data !('test')).toBeUndefined();
|
||||
expect(ng1ComponentElement.contents !().data !('test')).toBeUndefined();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should clear dom listeners on `$element` and descendants`', fakeAsync(() => {
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||
const elementClickListener = jasmine.createSpy('elementClickListener');
|
||||
const descendantClickListener = jasmine.createSpy('descendantClickListener');
|
||||
let ng1DescendantElement: angular.IAugmentedJQuery;
|
||||
let ng2ComponentAInstance: Ng2ComponentA;
|
||||
|
||||
// Define `ng1Component`
|
||||
const ng1Component: angular.IComponent = {
|
||||
controller: class {
|
||||
constructor(private $element: angular.IAugmentedJQuery) {} $onInit() {
|
||||
ng1DescendantElement = this.$element.contents !();
|
||||
|
||||
this.$element.on !('click', elementClickListener);
|
||||
ng1DescendantElement.on !('click', descendantClickListener);
|
||||
}
|
||||
},
|
||||
template: '<div></div>'
|
||||
};
|
||||
|
||||
// Define `Ng2Component`
|
||||
@Component({selector: 'ng2A', template: '<ng2B *ngIf="!destroyIt"></ng2B>'})
|
||||
class Ng2ComponentA {
|
||||
destroyIt = false;
|
||||
|
||||
constructor() { ng2ComponentAInstance = this; }
|
||||
}
|
||||
|
||||
@Component({selector: 'ng2B', template: '<ng1></ng1>'})
|
||||
class Ng2ComponentB {
|
||||
}
|
||||
|
||||
// Define `ng1Module`
|
||||
angular.module('ng1Module', [])
|
||||
.component('ng1', ng1Component)
|
||||
.directive('ng2A', adapter.downgradeNg2Component(Ng2ComponentA));
|
||||
|
||||
// Define `Ng2Module`
|
||||
@NgModule({
|
||||
declarations: [adapter.upgradeNg1Component('ng1'), Ng2ComponentA, Ng2ComponentB],
|
||||
entryComponents: [Ng2ComponentA],
|
||||
imports: [BrowserModule]
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
// Bootstrap
|
||||
const element = html(`<ng2-a></ng2-a>`);
|
||||
|
||||
adapter.bootstrap(element, ['ng1Module']).ready((ref) => {
|
||||
const $rootScope = ref.ng1RootScope as any;
|
||||
tick();
|
||||
$rootScope.$digest();
|
||||
(ng1DescendantElement[0] as HTMLElement).click();
|
||||
expect(elementClickListener).toHaveBeenCalledTimes(1);
|
||||
expect(descendantClickListener).toHaveBeenCalledTimes(1);
|
||||
|
||||
ng2ComponentAInstance.destroyIt = true;
|
||||
tick();
|
||||
$rootScope.$digest();
|
||||
|
||||
(ng1DescendantElement[0] as HTMLElement).click();
|
||||
expect(elementClickListener).toHaveBeenCalledTimes(1);
|
||||
expect(descendantClickListener).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
@ -3633,8 +3633,9 @@ withEachNg1Version(() => {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should emit `$destroy` on `$element`', async(() => {
|
||||
it('should emit `$destroy` on `$element` and descendants', async(() => {
|
||||
const elementDestroyListener = jasmine.createSpy('elementDestroyListener');
|
||||
const descendantDestroyListener = jasmine.createSpy('descendantDestroyListener');
|
||||
let ng2ComponentAInstance: Ng2ComponentA;
|
||||
|
||||
// Define `ng1Component`
|
||||
@ -3642,8 +3643,10 @@ withEachNg1Version(() => {
|
||||
controller: class {
|
||||
constructor($element: angular.IAugmentedJQuery) {
|
||||
$element.on !('$destroy', elementDestroyListener);
|
||||
$element.contents !().on !('$destroy', descendantDestroyListener);
|
||||
}
|
||||
}
|
||||
},
|
||||
template: '<div></div>'
|
||||
};
|
||||
|
||||
// Define `Ng1ComponentFacade`
|
||||
@ -3686,11 +3689,151 @@ withEachNg1Version(() => {
|
||||
|
||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => {
|
||||
expect(elementDestroyListener).not.toHaveBeenCalled();
|
||||
expect(descendantDestroyListener).not.toHaveBeenCalled();
|
||||
|
||||
ng2ComponentAInstance.destroyIt = true;
|
||||
$digest(adapter);
|
||||
|
||||
expect(elementDestroyListener).toHaveBeenCalledTimes(1);
|
||||
expect(descendantDestroyListener).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
}));
|
||||
|
||||
it('should clear data on `$element` and descendants`', async(() => {
|
||||
let ng1ComponentElement: angular.IAugmentedJQuery;
|
||||
let ng2ComponentAInstance: Ng2ComponentA;
|
||||
|
||||
// Define `ng1Component`
|
||||
const ng1Component: angular.IComponent = {
|
||||
controller: class {
|
||||
constructor($element: angular.IAugmentedJQuery) {
|
||||
$element.data !('test', 1);
|
||||
$element.contents !().data !('test', 2);
|
||||
|
||||
ng1ComponentElement = $element;
|
||||
}
|
||||
},
|
||||
template: '<div></div>'
|
||||
};
|
||||
|
||||
// Define `Ng1ComponentFacade`
|
||||
@Directive({selector: 'ng1'})
|
||||
class Ng1ComponentFacade extends UpgradeComponent {
|
||||
constructor(elementRef: ElementRef, injector: Injector) {
|
||||
super('ng1', elementRef, injector);
|
||||
}
|
||||
}
|
||||
|
||||
// Define `Ng2Component`
|
||||
@Component({selector: 'ng2A', template: '<ng2B *ngIf="!destroyIt"></ng2B>'})
|
||||
class Ng2ComponentA {
|
||||
destroyIt = false;
|
||||
|
||||
constructor() { ng2ComponentAInstance = this; }
|
||||
}
|
||||
|
||||
@Component({selector: 'ng2B', template: '<ng1></ng1>'})
|
||||
class Ng2ComponentB {
|
||||
}
|
||||
|
||||
// Define `ng1Module`
|
||||
const ng1Module = angular.module('ng1Module', [])
|
||||
.component('ng1', ng1Component)
|
||||
.directive('ng2A', downgradeComponent({component: Ng2ComponentA}));
|
||||
|
||||
// Define `Ng2Module`
|
||||
@NgModule({
|
||||
declarations: [Ng1ComponentFacade, Ng2ComponentA, Ng2ComponentB],
|
||||
entryComponents: [Ng2ComponentA],
|
||||
imports: [BrowserModule, UpgradeModule]
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
// Bootstrap
|
||||
const element = html(`<ng2-a></ng2-a>`);
|
||||
|
||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => {
|
||||
expect(ng1ComponentElement.data !('test')).toBe(1);
|
||||
expect(ng1ComponentElement.contents !().data !('test')).toBe(2);
|
||||
|
||||
ng2ComponentAInstance.destroyIt = true;
|
||||
$digest(adapter);
|
||||
|
||||
expect(ng1ComponentElement.data !('test')).toBeUndefined();
|
||||
expect(ng1ComponentElement.contents !().data !('test')).toBeUndefined();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should clear dom listeners on `$element` and descendants`', async(() => {
|
||||
const elementClickListener = jasmine.createSpy('elementClickListener');
|
||||
const descendantClickListener = jasmine.createSpy('descendantClickListener');
|
||||
let ng1DescendantElement: angular.IAugmentedJQuery;
|
||||
let ng2ComponentAInstance: Ng2ComponentA;
|
||||
|
||||
// Define `ng1Component`
|
||||
const ng1Component: angular.IComponent = {
|
||||
controller: class {
|
||||
constructor($element: angular.IAugmentedJQuery) {
|
||||
ng1DescendantElement = $element.contents !();
|
||||
|
||||
$element.on !('click', elementClickListener);
|
||||
ng1DescendantElement.on !('click', descendantClickListener);
|
||||
}
|
||||
},
|
||||
template: '<div></div>'
|
||||
};
|
||||
|
||||
// Define `Ng1ComponentFacade`
|
||||
@Directive({selector: 'ng1'})
|
||||
class Ng1ComponentFacade extends UpgradeComponent {
|
||||
constructor(elementRef: ElementRef, injector: Injector) {
|
||||
super('ng1', elementRef, injector);
|
||||
}
|
||||
}
|
||||
|
||||
// Define `Ng2Component`
|
||||
@Component({selector: 'ng2A', template: '<ng2B *ngIf="!destroyIt"></ng2B>'})
|
||||
class Ng2ComponentA {
|
||||
destroyIt = false;
|
||||
|
||||
constructor() { ng2ComponentAInstance = this; }
|
||||
}
|
||||
|
||||
@Component({selector: 'ng2B', template: '<ng1></ng1>'})
|
||||
class Ng2ComponentB {
|
||||
}
|
||||
|
||||
// Define `ng1Module`
|
||||
const ng1Module = angular.module('ng1Module', [])
|
||||
.component('ng1', ng1Component)
|
||||
.directive('ng2A', downgradeComponent({component: Ng2ComponentA}));
|
||||
|
||||
// Define `Ng2Module`
|
||||
@NgModule({
|
||||
declarations: [Ng1ComponentFacade, Ng2ComponentA, Ng2ComponentB],
|
||||
entryComponents: [Ng2ComponentA],
|
||||
imports: [BrowserModule, UpgradeModule]
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
// Bootstrap
|
||||
const element = html(`<ng2-a></ng2-a>`);
|
||||
|
||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => {
|
||||
(ng1DescendantElement[0] as HTMLElement).click();
|
||||
expect(elementClickListener).toHaveBeenCalledTimes(1);
|
||||
expect(descendantClickListener).toHaveBeenCalledTimes(1);
|
||||
|
||||
ng2ComponentAInstance.destroyIt = true;
|
||||
$digest(adapter);
|
||||
|
||||
(ng1DescendantElement[0] as HTMLElement).click();
|
||||
expect(elementClickListener).toHaveBeenCalledTimes(1);
|
||||
expect(descendantClickListener).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
}));
|
||||
|
||||
|
30
tools/types-ext/jasminewd2.d.ts
vendored
30
tools/types-ext/jasminewd2.d.ts
vendored
@ -1,30 +0,0 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
/// <reference types="jasminewd2" />
|
||||
|
||||
/**
|
||||
* Extended typings for `jasminewd2`.
|
||||
*
|
||||
* The currently used `jasminewd2` version (v2.1.0), supports passing a `done` callback to a spec,
|
||||
* but the latest typings on [DefinitelyTyped][1] do not reflect that.
|
||||
* Overwrite the relevant function signatures to add a `done` callback.
|
||||
*
|
||||
* [1]:
|
||||
* https://github.com/DefinitelyTyped/DefinitelyTyped/blob/566e0394859fdc1dc893658ccec6b06372d56a91/types/jasminewd2/index.d.ts#L9-L15
|
||||
*/
|
||||
declare function it(
|
||||
expectation: string, assertion?: (done: DoneFn) => Promise<void>, timeout?: number): void;
|
||||
declare function fit(
|
||||
expectation: string, assertion?: (done: DoneFn) => Promise<void>, timeout?: number): void;
|
||||
declare function xit(
|
||||
expectation: string, assertion?: (done: DoneFn) => Promise<void>, timeout?: number): void;
|
||||
declare function beforeEach(action: (done: DoneFn) => Promise<void>, timeout?: number): void;
|
||||
declare function afterEach(action: (done: DoneFn) => Promise<void>, timeout?: number): void;
|
||||
declare function beforeAll(action: (done: DoneFn) => Promise<void>, timeout?: number): void;
|
||||
declare function afterAll(action: (done: DoneFn) => Promise<void>, timeout?: number): void;
|
Reference in New Issue
Block a user