Compare commits
30 Commits
Author | SHA1 | Date | |
---|---|---|---|
d489ad595d | |||
0a3753bcce | |||
7fc2dceaf5 | |||
c3b39bac52 | |||
2cd88bfb0f | |||
0cefb0b79b | |||
55a7443974 | |||
f9ebaf1b90 | |||
bc81fbdd27 | |||
c7aa8a132d | |||
5c99b01512 | |||
992ba33a28 | |||
17e7c58981 | |||
b8f15d2b77 | |||
5f9a10aab9 | |||
04bc5257a6 | |||
5bca58e748 | |||
526a67f8f4 | |||
53a27e07b1 | |||
ba0fb1e055 | |||
ed2c8aa6f8 | |||
9226760120 | |||
b9ee8b46a0 | |||
e7eb0b8b7c | |||
ae52851458 | |||
e63cf3b89e | |||
9624fda082 | |||
e19b6a8f38 | |||
2aa6b54201 | |||
fe8550d278 |
@ -11,7 +11,7 @@
|
||||
anchor_1: &job_defaults
|
||||
working_directory: ~/ng
|
||||
docker:
|
||||
- image: angular/ngcontainer
|
||||
- image: angular/ngcontainer:0.0.2
|
||||
|
||||
# After checkout, rebase on top of master.
|
||||
# Similar to travis behavior, but not quite the same.
|
||||
|
13
CHANGELOG.md
13
CHANGELOG.md
@ -1,3 +1,16 @@
|
||||
<a name="4.4.4"></a>
|
||||
## [4.4.4](https://github.com/angular/angular/compare/4.4.3...4.4.4) (2017-09-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** support negative query limit value ([#19419](https://github.com/angular/angular/issues/19419)) ([bc81fbd](https://github.com/angular/angular/commit/bc81fbd)), closes [#19232](https://github.com/angular/angular/issues/19232)
|
||||
* **compiler:** correctly map error message locations ([#19424](https://github.com/angular/angular/issues/19424)) ([c3b39ba](https://github.com/angular/angular/commit/c3b39ba))
|
||||
* **compiler:** do not consider a reference with members as a reference ([#19466](https://github.com/angular/angular/issues/19466)) ([7fc2dce](https://github.com/angular/angular/commit/7fc2dce))
|
||||
* **compiler:** skip when trimming / removing whitespaces ([#19310](https://github.com/angular/angular/issues/19310)) ([c7aa8a1](https://github.com/angular/angular/commit/c7aa8a1)), closes [#19304](https://github.com/angular/angular/issues/19304)
|
||||
* **tsc-wrapped:** add metadata for `type` declarations ([#19040](https://github.com/angular/angular/issues/19040)) ([ae52851](https://github.com/angular/angular/commit/ae52851))
|
||||
|
||||
|
||||
<a name="4.4.3"></a>
|
||||
## [4.4.3](https://github.com/angular/angular/compare/4.4.2...4.4.3) (2017-09-19)
|
||||
|
||||
|
@ -52,7 +52,7 @@ For more details see #16745.
|
||||
|
||||
## Guide to authoring
|
||||
|
||||
There are two types of content in the documentatation:
|
||||
There are two types of content in the documentation:
|
||||
|
||||
* **API docs**: descriptions of the modules, classes, interfaces, decorators, etc that make up the Angular platform.
|
||||
API docs are generated directly from the source code.
|
||||
@ -107,8 +107,16 @@ yarn start
|
||||
yarn docs-watch
|
||||
```
|
||||
|
||||
>Alternatively, try the consolidated `serve-and-sync` command that builds, watches and serves in the same terminal window
|
||||
```bash
|
||||
yarn serve-and-sync
|
||||
```
|
||||
|
||||
* Open a browser at https://localhost:4200/ and navigate to the document on which you want to work.
|
||||
You can automatically open the browser by using `yarn start -- -o` in the first terminal.
|
||||
|
||||
* Make changes to the page's associated doc or example files. Every time a file is saved, the doc will
|
||||
be regenerated, the app will rebuild and the page will reload.
|
||||
|
||||
* If you get a build error complaining about examples or any other odd behavior, be sure to consult
|
||||
the [Authors Style Guide](https://angular.io/guide/docs-style-guide).
|
@ -13,10 +13,8 @@ const AIO_REPO_SLUG = getEnvVar('AIO_REPO_SLUG');
|
||||
const AIO_TRUSTED_PR_LABEL = getEnvVar('AIO_TRUSTED_PR_LABEL');
|
||||
const AIO_UPLOAD_HOSTNAME = getEnvVar('AIO_UPLOAD_HOSTNAME');
|
||||
const AIO_UPLOAD_PORT = +getEnvVar('AIO_UPLOAD_PORT');
|
||||
const AIO_WWW_USER = getEnvVar('AIO_WWW_USER');
|
||||
|
||||
// Run
|
||||
process.setuid(AIO_WWW_USER); // TODO(gkalpak): Find more suitable way to run as `www-data`.
|
||||
_main();
|
||||
|
||||
// Functions
|
||||
|
@ -18,7 +18,7 @@ const TEST_AIO_UPLOAD_PORT = +getEnvVar('TEST_AIO_UPLOAD_PORT');
|
||||
const WWW_USER = getEnvVar('AIO_WWW_USER');
|
||||
|
||||
// Interfaces - Types
|
||||
export interface CmdResult { success: boolean; err: Error; stdout: string; stderr: string; }
|
||||
export interface CmdResult { success: boolean; err: Error | null; stdout: string; stderr: string; }
|
||||
export interface FileSpecs { content?: string; size?: number; }
|
||||
|
||||
export type CleanUpFn = () => void;
|
||||
@ -143,7 +143,7 @@ class Helper {
|
||||
statusText = status[1];
|
||||
} else {
|
||||
statusCode = status;
|
||||
statusText = http.STATUS_CODES[statusCode];
|
||||
statusText = http.STATUS_CODES[statusCode] || 'UNKNOWN_STATUS_CODE';
|
||||
}
|
||||
|
||||
return (result: CmdResult) => {
|
||||
@ -196,7 +196,7 @@ class Helper {
|
||||
}
|
||||
|
||||
// Methods - Protected
|
||||
protected createCleanUpFn(fn: Function): CleanUpFn {
|
||||
protected createCleanUpFn(fn: () => void): CleanUpFn {
|
||||
const cleanUpFn = () => {
|
||||
const idx = this.cleanUpFns.indexOf(cleanUpFn);
|
||||
if (idx !== -1) {
|
||||
|
@ -8,7 +8,7 @@
|
||||
"scripts": {
|
||||
"prebuild": "yarn clean-dist",
|
||||
"build": "tsc",
|
||||
"build-watch": "yarn tsc -- --watch",
|
||||
"build-watch": "yarn tsc --watch",
|
||||
"clean-dist": "node --eval \"require('shelljs').rm('-rf', 'dist')\"",
|
||||
"dev": "concurrently --kill-others --raw --success first \"yarn build-watch\" \"yarn test-watch\"",
|
||||
"lint": "tslint --project tsconfig.json",
|
||||
@ -20,26 +20,26 @@
|
||||
"test-watch": "nodemon --exec \"yarn ~~test-only\" --watch dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"body-parser": "^1.17.2",
|
||||
"express": "^4.14.1",
|
||||
"jasmine": "^2.5.3",
|
||||
"jsonwebtoken": "^7.3.0",
|
||||
"shelljs": "^0.7.6"
|
||||
"body-parser": "^1.18.2",
|
||||
"express": "^4.15.4",
|
||||
"jasmine": "^2.8.0",
|
||||
"jsonwebtoken": "^8.0.1",
|
||||
"shelljs": "^0.7.8",
|
||||
"tslib": "^1.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/body-parser": "^1.16.4",
|
||||
"@types/express": "^4.0.35",
|
||||
"@types/jasmine": "^2.5.43",
|
||||
"@types/jsonwebtoken": "^7.2.0",
|
||||
"@types/node": "^7.0.5",
|
||||
"@types/shelljs": "^0.7.0",
|
||||
"@types/supertest": "^2.0.0",
|
||||
"concurrently": "^3.3.0",
|
||||
"eslint": "^3.15.0",
|
||||
"eslint-plugin-jasmine": "^2.2.0",
|
||||
"nodemon": "^1.11.0",
|
||||
"@types/body-parser": "^1.16.5",
|
||||
"@types/express": "^4.0.37",
|
||||
"@types/jasmine": "^2.6.0",
|
||||
"@types/jsonwebtoken": "^7.2.3",
|
||||
"@types/node": "^8.0.30",
|
||||
"@types/shelljs": "^0.7.4",
|
||||
"@types/supertest": "^2.0.3",
|
||||
"concurrently": "^3.5.0",
|
||||
"nodemon": "^1.12.1",
|
||||
"supertest": "^3.0.0",
|
||||
"tslint": "^4.4.2",
|
||||
"typescript": "^2.1.6"
|
||||
"tslint": "^5.7.0",
|
||||
"tslint-jasmine-noSkipOrFocus": "^1.0.8",
|
||||
"typescript": "^2.5.2"
|
||||
}
|
||||
}
|
||||
|
@ -39,8 +39,8 @@ describe('BuildCleaner', () => {
|
||||
let cleanerGetExistingBuildNumbersSpy: jasmine.Spy;
|
||||
let cleanerGetOpenPrNumbersSpy: jasmine.Spy;
|
||||
let cleanerRemoveUnnecessaryBuildsSpy: jasmine.Spy;
|
||||
let existingBuildsDeferred: {resolve: Function, reject: Function};
|
||||
let openPrsDeferred: {resolve: Function, reject: Function};
|
||||
let existingBuildsDeferred: {resolve: (v?: any) => void, reject: (e?: any) => void};
|
||||
let openPrsDeferred: {resolve: (v?: any) => void, reject: (e?: any) => void};
|
||||
let promise: Promise<void>;
|
||||
|
||||
beforeEach(() => {
|
||||
@ -195,7 +195,7 @@ describe('BuildCleaner', () => {
|
||||
|
||||
|
||||
describe('getOpenPrNumbers()', () => {
|
||||
let prDeferred: {resolve: Function, reject: Function};
|
||||
let prDeferred: {resolve: (v: any) => void, reject: (v: any) => void};
|
||||
let promise: Promise<number[]>;
|
||||
|
||||
beforeEach(() => {
|
||||
@ -277,7 +277,10 @@ describe('BuildCleaner', () => {
|
||||
|
||||
it('should catch errors and log them', () => {
|
||||
const consoleErrorSpy = spyOn(console, 'error');
|
||||
shellRmSpy.and.callFake(() => { throw 'Test'; });
|
||||
shellRmSpy.and.callFake(() => {
|
||||
// tslint:disable-next-line: no-string-throw
|
||||
throw 'Test';
|
||||
});
|
||||
|
||||
(cleaner as any).removeDir('/foo/bar');
|
||||
|
||||
|
@ -56,7 +56,7 @@ describe('GithubApi', () => {
|
||||
|
||||
|
||||
it('should not pass data to \'request()\'', () => {
|
||||
(api.get as Function)('foo', {}, {});
|
||||
(api.get as any)('foo', {}, {});
|
||||
|
||||
expect(apiRequestSpy).toHaveBeenCalled();
|
||||
expect(apiRequestSpy.calls.argsFor(0)[2]).toBeUndefined();
|
||||
@ -144,7 +144,7 @@ describe('GithubApi', () => {
|
||||
|
||||
|
||||
describe('getPaginated()', () => {
|
||||
let deferreds: {resolve: Function, reject: Function}[];
|
||||
let deferreds: {resolve: (v: any) => void, reject: (v: any) => void}[];
|
||||
|
||||
beforeEach(() => {
|
||||
deferreds = [];
|
||||
@ -292,7 +292,7 @@ describe('GithubApi', () => {
|
||||
|
||||
|
||||
describe('onResponse', () => {
|
||||
let promise: Promise<Object>;
|
||||
let promise: Promise<object>;
|
||||
let respond: (statusCode: number) => IncomingMessage;
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -22,7 +22,7 @@ describe('GithubPullRequests', () => {
|
||||
|
||||
describe('addComment()', () => {
|
||||
let prs: GithubPullRequests;
|
||||
let deferred: {resolve: Function, reject: Function};
|
||||
let deferred: {resolve: (v: any) => void, reject: (v: any) => void};
|
||||
|
||||
beforeEach(() => {
|
||||
prs = new GithubPullRequests('12345', 'foo/bar');
|
||||
|
@ -223,6 +223,7 @@ describe('BuildCreator', () => {
|
||||
|
||||
|
||||
it('should reject with an UploadError', done => {
|
||||
// tslint:disable-next-line: no-string-throw
|
||||
shellMkdirSpy.and.callFake(() => { throw 'Test'; });
|
||||
bc.create(pr, sha, archive, isPublic).catch(err => {
|
||||
expectToBeUploadError(err, 500, `Error while uploading to directory: ${shaDir}\nTest`);
|
||||
@ -407,6 +408,7 @@ describe('BuildCreator', () => {
|
||||
|
||||
|
||||
it('should reject with an UploadError', done => {
|
||||
// tslint:disable-next-line: no-string-throw
|
||||
shellMvSpy.and.callFake(() => { throw 'Test'; });
|
||||
bc.updatePrVisibility(pr, makePublic).catch(err => {
|
||||
expectToBeUploadError(err, 500, `Error while making PR ${pr} ${makePublic ? 'public' : 'hidden'}.\nTest`);
|
||||
@ -434,11 +436,11 @@ describe('BuildCreator', () => {
|
||||
|
||||
describe('exists()', () => {
|
||||
let fsAccessSpy: jasmine.Spy;
|
||||
let fsAccessCbs: Function[];
|
||||
let fsAccessCbs: ((v?: any) => void)[];
|
||||
|
||||
beforeEach(() => {
|
||||
fsAccessCbs = [];
|
||||
fsAccessSpy = spyOn(fs, 'access').and.callFake((_: string, cb: Function) => fsAccessCbs.push(cb));
|
||||
fsAccessSpy = spyOn(fs, 'access').and.callFake((_: string, cb: (v?: any) => void) => fsAccessCbs.push(cb));
|
||||
});
|
||||
|
||||
|
||||
@ -482,7 +484,7 @@ describe('BuildCreator', () => {
|
||||
let shellChmodSpy: jasmine.Spy;
|
||||
let shellRmSpy: jasmine.Spy;
|
||||
let cpExecSpy: jasmine.Spy;
|
||||
let cpExecCbs: Function[];
|
||||
let cpExecCbs: ((...args: any[]) => void)[];
|
||||
|
||||
beforeEach(() => {
|
||||
cpExecCbs = [];
|
||||
@ -490,7 +492,7 @@ describe('BuildCreator', () => {
|
||||
consoleWarnSpy = spyOn(console, 'warn');
|
||||
shellChmodSpy = spyOn(shell, 'chmod');
|
||||
shellRmSpy = spyOn(shell, 'rm');
|
||||
cpExecSpy = spyOn(cp, 'exec').and.callFake((_: string, cb: Function) => cpExecCbs.push(cb));
|
||||
cpExecSpy = spyOn(cp, 'exec').and.callFake((_: string, cb: (...args: any[]) => void) => cpExecCbs.push(cb));
|
||||
});
|
||||
|
||||
|
||||
@ -556,7 +558,11 @@ describe('BuildCreator', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
shellChmodSpy.and.callFake(() => { throw 'Test'; });
|
||||
shellChmodSpy.and.callFake(() => {
|
||||
// tslint:disable-next-line: no-string-throw
|
||||
throw 'Test';
|
||||
});
|
||||
|
||||
cpExecCbs[0]();
|
||||
});
|
||||
|
||||
@ -569,7 +575,11 @@ describe('BuildCreator', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
shellRmSpy.and.callFake(() => { throw 'Test'; });
|
||||
shellRmSpy.and.callFake(() => {
|
||||
// tslint:disable-next-line: no-string-throw
|
||||
throw 'Test';
|
||||
});
|
||||
|
||||
cpExecCbs[0]();
|
||||
});
|
||||
|
||||
|
@ -116,7 +116,7 @@ describe('uploadServerFactory', () => {
|
||||
|
||||
it('should log the server address info on \'listening\'', () => {
|
||||
const consoleInfoSpy = spyOn(console, 'info');
|
||||
const server = createUploadServer('builds/dir');
|
||||
const server = createUploadServer();
|
||||
server.address = () => ({address: 'foo', family: '', port: 1337});
|
||||
|
||||
expect(consoleInfoSpy).not.toHaveBeenCalled();
|
||||
|
@ -1,25 +1,66 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"alwaysStrict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"inlineSourceMap": true,
|
||||
/* Basic Options */
|
||||
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||
"lib": [
|
||||
"es2016"
|
||||
],
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"outDir": "dist",
|
||||
"pretty": true,
|
||||
"rootDir": ".",
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": true,
|
||||
"target": "es5",
|
||||
"es2015",
|
||||
"es2016.array.include"
|
||||
], /* Specify library files to be included in the compilation: */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
"outDir": "dist", /* Redirect output structure to the directory. */
|
||||
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
"importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
|
||||
/* Additional Checks */
|
||||
"noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
"noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
|
||||
/* Module Resolution Options */
|
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
]
|
||||
], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
"inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
|
||||
/* Other */
|
||||
"forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
|
||||
"newLine": "LF", /* Use the specified end of line sequence to be used when emitting files: "crlf" (windows) or "lf" (unix). */
|
||||
"pretty": true, /* Stylize errors and messages using color and context. */
|
||||
"skipLibCheck": true /* Skip type checking of all declaration files (*.d.ts). */
|
||||
},
|
||||
"include": [
|
||||
"lib/**/*",
|
||||
|
@ -1,5 +1,9 @@
|
||||
{
|
||||
"extends": "tslint:recommended",
|
||||
"defaultSeverity": "error",
|
||||
"extends": [
|
||||
"tslint:recommended"
|
||||
],
|
||||
"jsRules": {},
|
||||
"rules": {
|
||||
"array-type": [true, "array"],
|
||||
"arrow-parens": [true, "ban-single-arg-parens"],
|
||||
@ -7,9 +11,14 @@
|
||||
"max-classes-per-file": [true, 4],
|
||||
"no-consecutive-blank-lines": [true, 2],
|
||||
"no-console": [false],
|
||||
"no-focused-test": true,
|
||||
"no-namespace": [true, "allow-declarations"],
|
||||
"no-skipped-test": true,
|
||||
"no-string-literal": false,
|
||||
"quotemark": [true, "single"],
|
||||
"variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore"]
|
||||
}
|
||||
},
|
||||
"rulesDirectory": [
|
||||
"node_modules/tslint-jasmine-noSkipOrFocus"
|
||||
]
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -6,10 +6,9 @@ export AIO_GITHUB_TOKEN=$(head -c -1 /aio-secrets/GITHUB_TOKEN 2>/dev/null || ec
|
||||
export AIO_PREVIEW_DEPLOYMENT_TOKEN=$(head -c -1 /aio-secrets/PREVIEW_DEPLOYMENT_TOKEN 2>/dev/null || echo "MISSING_PREVIEW_DEPLOYMENT_TOKEN")
|
||||
|
||||
# Start the upload-server instance
|
||||
# TODO(gkalpak): Ideally, the upload server should be run as a non-privileged user.
|
||||
# (Currently, there doesn't seem to be a straight forward way.)
|
||||
action=$([ "$1" == "stop" ] && echo "stop" || echo "start")
|
||||
pm2 $action $AIO_SCRIPTS_JS_DIR/dist/lib/upload-server \
|
||||
--uid $AIO_WWW_USER \
|
||||
--log /var/log/aio/upload-server-prod.log \
|
||||
--name aio-upload-server-prod \
|
||||
${@:2}
|
||||
|
@ -15,13 +15,12 @@ export AIO_GITHUB_TOKEN=$(head -c -1 /aio-secrets/TEST_GITHUB_TOKEN 2>/dev/null
|
||||
export AIO_PREVIEW_DEPLOYMENT_TOKEN=$(head -c -1 /aio-secrets/TEST_PREVIEW_DEPLOYMENT_TOKEN 2>/dev/null || echo "TEST_PREVIEW_DEPLOYMENT_TOKEN")
|
||||
|
||||
# Start the upload-server instance
|
||||
# TODO(gkalpak): Ideally, the upload server should be run as a non-privileged user.
|
||||
# (Currently, there doesn't seem to be a straight forward way.)
|
||||
appName=aio-upload-server-test
|
||||
if [[ "$1" == "stop" ]]; then
|
||||
pm2 delete $appName
|
||||
else
|
||||
pm2 start $AIO_SCRIPTS_JS_DIR/dist/lib/verify-setup/start-test-upload-server.js \
|
||||
--uid $AIO_WWW_USER \
|
||||
--log /var/log/aio/upload-server-test.log \
|
||||
--name $appName \
|
||||
--no-autorestart \
|
||||
|
@ -1,5 +1,6 @@
|
||||
// #docregion animations-module
|
||||
// #docplaster
|
||||
import { NgModule } from '@angular/core';
|
||||
// #docregion animations-module
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
// #enddocregion animations-module
|
||||
@ -15,11 +16,12 @@ import { HeroListAutoComponent } from './hero-list-auto.component';
|
||||
import { HeroListGroupsComponent } from './hero-list-groups.component';
|
||||
import { HeroListMultistepComponent } from './hero-list-multistep.component';
|
||||
import { HeroListTimingsComponent } from './hero-list-timings.component';
|
||||
// #docregion animations-module
|
||||
|
||||
// #docregion animation-module
|
||||
@NgModule({
|
||||
imports: [ BrowserModule, BrowserAnimationsModule ],
|
||||
// #enddocregion animation-module
|
||||
// ... more stuff ...
|
||||
// #enddocregion animations-module
|
||||
declarations: [
|
||||
HeroTeamBuilderComponent,
|
||||
HeroListBasicComponent,
|
||||
@ -34,5 +36,8 @@ import { HeroListTimingsComponent } from './hero-list-timings.component';
|
||||
HeroListGroupsComponent
|
||||
],
|
||||
bootstrap: [ HeroTeamBuilderComponent ]
|
||||
// #docregion animations-module
|
||||
})
|
||||
export class AppModule { }
|
||||
// #enddocregion animations-module
|
||||
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
transition
|
||||
} from '@angular/animations';
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list-auto',
|
||||
@ -43,5 +43,5 @@ import { Heroes } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListAutoComponent {
|
||||
@Input() heroes: Heroes;
|
||||
@Input() heroes: Hero[];
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import {
|
||||
} from '@angular/animations';
|
||||
// #enddocregion imports
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list-basic',
|
||||
@ -66,5 +66,5 @@ import { Heroes } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListBasicComponent {
|
||||
@Input() heroes: Heroes;
|
||||
@Input() heroes: Hero[];
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import {
|
||||
} from '@angular/animations';
|
||||
// #enddocregion imports
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list-combined-transitions',
|
||||
@ -55,5 +55,5 @@ import { Heroes } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListCombinedTransitionsComponent {
|
||||
@Input() heroes: Heroes;
|
||||
@Input() heroes: Hero[];
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
transition
|
||||
} from '@angular/animations';
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list-enter-leave-states',
|
||||
@ -59,5 +59,5 @@ import { Heroes } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListEnterLeaveStatesComponent {
|
||||
@Input() heroes: Heroes;
|
||||
@Input() heroes: Hero[];
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
transition
|
||||
} from '@angular/animations';
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list-enter-leave',
|
||||
@ -47,5 +47,5 @@ import { Heroes } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListEnterLeaveComponent {
|
||||
@Input() heroes: Heroes;
|
||||
@Input() heroes: Hero[];
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
group
|
||||
} from '@angular/animations';
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list-groups',
|
||||
@ -76,5 +76,5 @@ import { Heroes } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListGroupsComponent {
|
||||
@Input() heroes: Heroes;
|
||||
@Input() heroes: Hero[];
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import {
|
||||
} from '@angular/animations';
|
||||
// #enddocregion imports
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list-inline-styles',
|
||||
@ -56,5 +56,5 @@ import { Heroes } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListInlineStylesComponent {
|
||||
@Input() heroes: Heroes;
|
||||
@Input() heroes: Hero[];
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import {
|
||||
AnimationEvent
|
||||
} from '@angular/animations';
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list-multistep',
|
||||
@ -59,7 +59,7 @@ import { Heroes } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListMultistepComponent {
|
||||
@Input() heroes: Heroes;
|
||||
@Input() heroes: Hero[];
|
||||
|
||||
animationStarted(event: AnimationEvent) {
|
||||
console.warn('Animation started: ', event);
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
transition
|
||||
} from '@angular/animations';
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list-timings',
|
||||
@ -54,5 +54,5 @@ import { Heroes } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListTimingsComponent {
|
||||
@Input() heroes: Heroes;
|
||||
@Input() heroes: Hero[];
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import {
|
||||
} from '@angular/animations';
|
||||
// #enddocregion imports
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list-twoway',
|
||||
@ -54,5 +54,5 @@ import { Heroes } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListTwowayComponent {
|
||||
@Input() heroes: Heroes;
|
||||
@Input() heroes: Hero[];
|
||||
}
|
||||
|
@ -1,40 +1,41 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero, HeroService } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-team-builder',
|
||||
template: `
|
||||
<div class="buttons">
|
||||
<button [disabled]="!heroes.canAdd()" (click)="heroes.addInactive()">Add inactive hero</button>
|
||||
<button [disabled]="!heroes.canAdd()" (click)="heroes.addActive()">Add active hero</button>
|
||||
<button [disabled]="!heroes.canRemove()" (click)="heroes.remove()">Remove hero</button>
|
||||
<button [disabled]="!heroService.canAdd()" (click)="heroService.addInactive()">Add inactive hero</button>
|
||||
<button [disabled]="!heroService.canAdd()" (click)="heroService.addActive()">Add active hero</button>
|
||||
<button [disabled]="!heroService.canRemove()" (click)="heroService.remove()">Remove hero</button>
|
||||
</div>
|
||||
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<h4>Basic State</h4>
|
||||
<p>Switch between active/inactive on click.</p>
|
||||
<hero-list-basic [heroes]=heroes></hero-list-basic>
|
||||
<hero-list-basic [heroes]="heroes"></hero-list-basic>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Styles inline in transitions</h4>
|
||||
<p>Animated effect on click, no persistend end styles.</p>
|
||||
<hero-list-inline-styles [heroes]=heroes></hero-list-inline-styles>
|
||||
<p>Animated effect on click, no persistent end styles.</p>
|
||||
<hero-list-inline-styles [heroes]="heroes"></hero-list-inline-styles>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Combined transition syntax</h4>
|
||||
<p>Switch between active/inactive on click. Define just one transition used in both directions.</p>
|
||||
<hero-list-combined-transitions [heroes]=heroes></hero-list-combined-transitions>
|
||||
<hero-list-combined-transitions [heroes]="heroes"></hero-list-combined-transitions>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Two-way transition syntax</h4>
|
||||
<p>Switch between active/inactive on click. Define just one transition used in both directions using the <=> syntax.</p>
|
||||
<hero-list-twoway [heroes]=heroes></hero-list-twoway>
|
||||
<hero-list-twoway [heroes]="heroes"></hero-list-twoway>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Enter & Leave</h4>
|
||||
<p>Enter and leave animations using the void state.</p>
|
||||
<hero-list-enter-leave [heroes]=heroes></hero-list-enter-leave>
|
||||
<hero-list-enter-leave [heroes]="heroes"></hero-list-enter-leave>
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns">
|
||||
@ -44,27 +45,27 @@ import { Heroes } from './hero.service';
|
||||
Enter and leave animations combined with active/inactive state animations.
|
||||
Different enter and leave transitions depending on state.
|
||||
</p>
|
||||
<hero-list-enter-leave-states [heroes]=heroes></hero-list-enter-leave-states>
|
||||
<hero-list-enter-leave-states [heroes]="heroes"></hero-list-enter-leave-states>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Auto Style Calc</h4>
|
||||
<p>Leave animation from the current computed height using the auto-style value *.</p>
|
||||
<hero-list-auto [heroes]=heroes></hero-list-auto>
|
||||
<hero-list-auto [heroes]="heroes"></hero-list-auto>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Different Timings</h4>
|
||||
<p>Enter and leave animations with different easings, ease-in for enter, ease-out for leave.</p>
|
||||
<hero-list-timings [heroes]=heroes></hero-list-timings>
|
||||
<hero-list-timings [heroes]="heroes"></hero-list-timings>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Multiple Keyframes</h4>
|
||||
<p>Enter and leave animations with three keyframes in each, to give the transition some bounce.</p>
|
||||
<hero-list-multistep [heroes]=heroes></hero-list-multistep>
|
||||
<hero-list-multistep [heroes]="heroes"></hero-list-multistep>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Parallel Groups</h4>
|
||||
<p>Enter and leave animations with multiple properties animated in parallel with different timings.</p>
|
||||
<hero-list-groups [heroes]=heroes></hero-list-groups>
|
||||
<hero-list-groups [heroes]="heroes"></hero-list-groups>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
@ -87,8 +88,12 @@ import { Heroes } from './hero.service';
|
||||
min-height: 6em;
|
||||
}
|
||||
`],
|
||||
providers: [Heroes]
|
||||
providers: [HeroService]
|
||||
})
|
||||
export class HeroTeamBuilderComponent {
|
||||
constructor(private heroes: Heroes) { }
|
||||
heroes: Hero[];
|
||||
|
||||
constructor(private heroService: HeroService) {
|
||||
this.heroes = heroService.heroes;
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
class Hero {
|
||||
constructor(public name: string,
|
||||
public state = 'inactive') {
|
||||
}
|
||||
// #docregion hero
|
||||
export class Hero {
|
||||
constructor(public name: string, public state = 'inactive') { }
|
||||
|
||||
toggleState() {
|
||||
this.state = (this.state === 'active' ? 'inactive' : 'active');
|
||||
this.state = this.state === 'active' ? 'inactive' : 'active';
|
||||
}
|
||||
}
|
||||
// #enddocregion hero
|
||||
|
||||
let ALL_HEROES = [
|
||||
const ALL_HEROES = [
|
||||
'Windstorm',
|
||||
'RubberMan',
|
||||
'Bombasto',
|
||||
@ -25,36 +25,30 @@ let ALL_HEROES = [
|
||||
].map(name => new Hero(name));
|
||||
|
||||
@Injectable()
|
||||
export class Heroes implements Iterable<Hero> {
|
||||
export class HeroService {
|
||||
|
||||
currentHeroes: Hero[] = [];
|
||||
|
||||
[Symbol.iterator]() {
|
||||
return this.currentHeroes.values();
|
||||
}
|
||||
heroes: Hero[] = [];
|
||||
|
||||
canAdd() {
|
||||
return this.currentHeroes.length < ALL_HEROES.length;
|
||||
return this.heroes.length < ALL_HEROES.length;
|
||||
}
|
||||
|
||||
canRemove() {
|
||||
return this.currentHeroes.length > 0;
|
||||
return this.heroes.length > 0;
|
||||
}
|
||||
|
||||
addActive() {
|
||||
let hero = ALL_HEROES[this.currentHeroes.length];
|
||||
hero.state = 'active';
|
||||
this.currentHeroes.push(hero);
|
||||
addActive(active = true) {
|
||||
let hero = ALL_HEROES[this.heroes.length];
|
||||
hero.state = active ? 'active' : 'inactive';
|
||||
this.heroes.push(hero);
|
||||
}
|
||||
|
||||
addInactive() {
|
||||
let hero = ALL_HEROES[this.currentHeroes.length];
|
||||
hero.state = 'inactive';
|
||||
this.currentHeroes.push(hero);
|
||||
this.addActive(false);
|
||||
}
|
||||
|
||||
remove() {
|
||||
this.currentHeroes.splice(this.currentHeroes.length - 1, 1);
|
||||
this.heroes.length -= 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -150,11 +150,6 @@ The following table lists some of the key AngularJS template features with their
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
[Back to top](guide/ajs-quick-reference#top)
|
||||
|
||||
|
||||
|
||||
## Template directives
|
||||
AngularJS provides more than seventy built-in directives for templates.
|
||||
Many of them aren't needed in Angular because of its more capable and expressive binding system.
|
||||
@ -721,10 +716,6 @@ The following are some of the key AngularJS built-in directives and their equiva
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
[Back to top](guide/ajs-quick-reference#top)
|
||||
|
||||
|
||||
{@a filters-pipes}
|
||||
|
||||
|
||||
@ -992,9 +983,6 @@ For more information on pipes, see [Pipes](guide/pipes).
|
||||
|
||||
|
||||
|
||||
[Back to top](guide/ajs-quick-reference#top)
|
||||
|
||||
|
||||
{@a controllers-components}
|
||||
|
||||
|
||||
@ -1215,11 +1203,6 @@ The Angular code is shown using TypeScript.
|
||||
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
[Back to top](guide/ajs-quick-reference#top)
|
||||
|
||||
|
||||
{@a style-sheets}
|
||||
|
||||
|
||||
@ -1297,6 +1280,3 @@ also encapsulate a style sheet within a specific component.
|
||||
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
[Back to top](guide/ajs-quick-reference#top)
|
||||
|
@ -28,19 +28,37 @@ The examples in this page are available as a <live-example></live-example>.
|
||||
|
||||
</div>
|
||||
|
||||
## Setup
|
||||
|
||||
Before you can add animations to your application, you need
|
||||
to import a few animation-specific modules and functions to the root application module.
|
||||
|
||||
<code-example path="animations/src/app/app.module.ts" region="animations-module" title="app.module.ts (animation module import excerpt)" linenums="false"></code-example>
|
||||
|
||||
#### Example basics
|
||||
|
||||
The animations examples in this guide animate a list of heroes.
|
||||
|
||||
A `Hero` class has a `name` property, a `state` property that indicates if the hero is active or not,
|
||||
and a `toggleState()` method to switch between the states.
|
||||
|
||||
<code-example path="animations/src/app/hero.service.ts" region="hero" title="hero.service.ts (Hero class)" linenums="false"></code-example>
|
||||
|
||||
Across the top of the screen (`app.hero-team-builder.component.ts`)
|
||||
are a series of buttons that add and remove heroes from the list (via the `HeroService`).
|
||||
The buttons trigger changes to the list that all of the example components see at the same time.
|
||||
|
||||
{@a example-transitioning-between-states}
|
||||
|
||||
## Quickstart example: Transitioning between two states
|
||||
## Transitioning between two states
|
||||
|
||||
<img src="generated/images/guide/animations/animation_basic_click.gif" alt="A simple transition animation" class="right">
|
||||
|
||||
You can build a simple animation that transitions an element between two states
|
||||
driven by a model attribute.
|
||||
|
||||
Animations are defined inside `@Component` metadata. Before you can add animations, you need
|
||||
to import a few animation-specific imports and functions:
|
||||
|
||||
<code-example path="animations/src/app/app.module.ts" region="animations-module" title="app.module.ts (@NgModule imports excerpt)" linenums="false"></code-example>
|
||||
Animations can be defined inside `@Component` metadata.
|
||||
|
||||
<code-example path="animations/src/app/hero-list-basic.component.ts" region="imports" title="hero-list-basic.component.ts" linenums="false"></code-example>
|
||||
|
||||
|
@ -348,7 +348,7 @@ Here's the brief markup that produced that lengthy snippet:
|
||||
```
|
||||
|
||||
You identified the snippet's source file by setting the `path` attribute to sample folder's location _within_ `content/examples`.
|
||||
In this example, that path is `docs-style-guide/src/app/app.module.ts`.
|
||||
In this example, that path is `docs-style-guide/src/app/app.module.ts`.
|
||||
|
||||
You added a header to tell the reader where to find the file by setting the `title` attribute.
|
||||
Following convention, you set the `title` attribute to the file's location within the sample's root folder.
|
||||
@ -360,6 +360,23 @@ located in the `content/examples/docs-style-guide` directory.
|
||||
|
||||
</div>
|
||||
|
||||
<div class="alert is-important">
|
||||
|
||||
The doc tooling reports an error if the file identified in the path does not exist **or is _git_-ignored**.
|
||||
|
||||
Most `.js` files are _git_-ignored.
|
||||
If you want to include an ignored code file in your project and display it in a guide you must _un-ignore_ it.
|
||||
|
||||
The preferred way to un-ignore a file is to update the `content/examples/.gitignore` like this:
|
||||
|
||||
<code-example title="content/examples/.gitignore">
|
||||
# my-guide
|
||||
!my-guide/src/something.js
|
||||
!my-guide/more-javascript*.js
|
||||
</code-example>
|
||||
|
||||
</div>
|
||||
|
||||
#### Code-example attributes
|
||||
|
||||
You control the _code-example_ output by setting one or more of its attributes:
|
||||
|
@ -83,4 +83,3 @@ you'll have to provide a different `Title` service that understands
|
||||
the concept of a "document title" for that specific platform.
|
||||
Ideally, the application itself neither knows nor cares about the runtime environment.
|
||||
|
||||
[Back to top](guide/set-document-title#top)
|
||||
|
@ -316,10 +316,13 @@ which `NgFor` has initialized with the hero for the current iteration.
|
||||
* The [API guide](api/common/NgFor "API: NgFor")
|
||||
describes additional `NgFor` directive properties and context properties.
|
||||
|
||||
* `NgFor` is implemented by the `NgForOf` directive. Read more about additional `NgForOf` directive properties and context properties [NgForOf API reference](api/common/NgForOf).
|
||||
|
||||
|
||||
These microsyntax mechanisms are available to you when you write your own structural directives.
|
||||
Studying the
|
||||
[source code for `NgIf`](https://github.com/angular/angular/blob/master/packages/common/src/directives/ng_if.ts "Source: NgIf")
|
||||
and [`NgFor`](https://github.com/angular/angular/blob/master/packages/common/src/directives/ng_for_of.ts "Source: NgFor")
|
||||
and [`NgForOf`](https://github.com/angular/angular/blob/master/packages/common/src/directives/ng_for_of.ts "Source: NgForOf")
|
||||
is a great way to learn more.
|
||||
|
||||
|
||||
|
@ -37,7 +37,6 @@ You can extend the HTML vocabulary of your templates with components and directi
|
||||
In the following sections, you'll learn how to get and set DOM (Document Object Model) values dynamically through data binding.
|
||||
|
||||
Begin with the first form of data binding—interpolation—to see how much richer template HTML can be.
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -81,7 +80,7 @@ Though this is not exactly true. Interpolation is a special syntax that Angular
|
||||
|
||||
But first, let's take a closer look at template expressions and statements.
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -147,7 +146,6 @@ the global namespace. They can't refer to `window` or `document`. They
|
||||
can't call `console.log` or `Math.max`. They are restricted to referencing
|
||||
members of the expression context.
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
{@a no-side-effects}
|
||||
|
||||
@ -204,7 +202,7 @@ Dependent values should not change during a single turn of the event loop.
|
||||
If an idempotent expression returns a string or a number, it returns the same string or number
|
||||
when called twice in a row. If the expression returns an object (including an `array`),
|
||||
it returns the same object *reference* when called twice in a row.
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -276,7 +274,6 @@ A method call or simple property assignment should be the norm.
|
||||
Now that you have a feel for template expressions and statements,
|
||||
you're ready to learn about the varieties of data binding syntax beyond interpolation.
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -585,7 +582,6 @@ The following table summarizes:
|
||||
</table>
|
||||
|
||||
With this broad view in mind, you're ready to look at binding types in detail.
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -773,7 +769,6 @@ content harmlessly.
|
||||
<img src='generated/images/guide/template-syntax/evil-title.png' alt="evil title made safe">
|
||||
</figure>
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -847,7 +842,6 @@ is to set ARIA attributes, as in this example:
|
||||
<code-example path="template-syntax/src/app/app.component.html" region="attrib-binding-aria" title="src/app/app.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -885,7 +879,6 @@ the [NgClass directive](guide/template-syntax#ngClass) is usually preferred when
|
||||
|
||||
</div>
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -921,8 +914,6 @@ Note that a _style property_ name can be written in either
|
||||
|
||||
</div>
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
{@a event-binding}
|
||||
@ -1053,7 +1044,6 @@ Deleting the hero updates the model, perhaps triggering other changes
|
||||
including queries and saves to a remote server.
|
||||
These changes percolate through the system and are ultimately displayed in this and other views.
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -1118,7 +1108,6 @@ However, no native HTML element follows the `x` value and `xChange` event patter
|
||||
|
||||
Fortunately, the Angular [_NgModel_](guide/template-syntax#ngModel) directive is a bridge that enables two-way binding to form elements.
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -1162,7 +1151,7 @@ This section is an introduction to the most commonly used attribute directives:
|
||||
* [`NgClass`](guide/template-syntax#ngClass) - add and remove a set of CSS classes
|
||||
* [`NgStyle`](guide/template-syntax#ngStyle) - add and remove a set of HTML styles
|
||||
* [`NgModel`](guide/template-syntax#ngModel) - two-way data binding to an HTML form element
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -1203,8 +1192,6 @@ It's up to you to call `setCurrentClassess()`, both initially and when the depen
|
||||
|
||||
</div>
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
{@a ngStyle}
|
||||
@ -1241,7 +1228,6 @@ It's up to you to call `setCurrentStyles()`, both initially and when the depende
|
||||
|
||||
</div>
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -1335,8 +1321,6 @@ Here are all variations in action, including the uppercase version:
|
||||
<img src='generated/images/guide/template-syntax/ng-model-anim.gif' alt="NgModel variations">
|
||||
</figure>
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
{@a structural-directives}
|
||||
@ -1431,7 +1415,6 @@ described below.
|
||||
|
||||
</div>
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -1511,8 +1494,8 @@ The next example captures the `index` in a variable named `i` and displays it wi
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
Learn about the other `NgFor` context values such as `last`, `even`,
|
||||
and `odd` in the [NgFor API reference](api/common/NgFor).
|
||||
`NgFor` is implemented by the `NgForOf` directive. Read more about the other `NgForOf` context values such as `last`, `even`,
|
||||
and `odd` in the [NgForOf API reference](api/common/NgForOf).
|
||||
|
||||
</div>
|
||||
|
||||
@ -1553,7 +1536,6 @@ Here is an illustration of the _trackBy_ effect.
|
||||
<img src="generated/images/guide/template-syntax/ng-for-track-by-anim.gif" alt="trackBy">
|
||||
</figure>
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -1601,8 +1583,6 @@ For example, you could replace the `<confused-hero>` switch case with the follow
|
||||
<code-example path="template-syntax/src/app/app.component.html" region="NgSwitch-div" title="src/app/app.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
{@a template-reference-variable}
|
||||
@ -1673,7 +1653,6 @@ This example declares the `fax` variable as `ref-fax` instead of `#fax`.
|
||||
<code-example path="template-syntax/src/app/app.component.html" region="ref-fax" title="src/app/app.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -1810,7 +1789,6 @@ the directive property name on the *left* and the public alias on the *right*:
|
||||
|
||||
</div>
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -1861,7 +1839,6 @@ The generated output would look something like this
|
||||
"rate": 325 }
|
||||
</code-example>
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -1935,7 +1912,6 @@ The display is blank, but the app keeps rolling without errors.
|
||||
|
||||
It works perfectly with long property paths such as `a?.b?.c?.d`.
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
|
@ -18,7 +18,7 @@ The sample application and all tests in this guide are available as live example
|
||||
* <live-example name="setup" plnkr="quickstart-specs" embedded-style>The QuickStart seed's AppComponent spec</live-example>.
|
||||
* <live-example embedded-style>The sample application to be tested</live-example>.
|
||||
* <live-example plnkr="app-specs" embedded-style>All specs that test the sample application</live-example>.
|
||||
* <live-example plnkr="bag-specs" embedded-style>A grab bag of additional specs</live-example>.<a href="#top" class='to-top'>Back to top</a>
|
||||
* <live-example plnkr="bag-specs" embedded-style>A grab bag of additional specs</live-example>.
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -199,7 +199,6 @@ A comprehensive review of the Angular testing utilities appears [later in this g
|
||||
|
||||
But first you should write a dummy test to verify that your test environment is set up properly
|
||||
and to lock in a few basic testing skills.
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -365,7 +364,6 @@ Debug specs in the browser in the same way that you debug an application.
|
||||
|
||||
You can also try this test as a <live-example plnkr="1st-specs" title="First spec" embedded-style></live-example> in plunker.
|
||||
All of the tests in this guide are available as [live examples](guide/testing#live-examples "Live examples of these tests").
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -631,7 +629,7 @@ There is no harm in calling `detectChanges()` more often than is strictly necess
|
||||
|
||||
</div>
|
||||
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -800,7 +798,7 @@ The tests in this guide only call `compileComponents` when necessary.
|
||||
|
||||
</div>
|
||||
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -966,7 +964,6 @@ In a spec with multiple expectations, it can help clarify what went wrong and wh
|
||||
The remaining tests confirm the logic of the component when the service returns different values.
|
||||
The second test validates the effect of changing the user name.
|
||||
The third test checks that the component displays the proper message when there is no logged-in user.
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -1202,7 +1199,7 @@ and `fakeAsync`, is a viable and occasionally necessary technique.
|
||||
For example, you can't call `async` or `fakeAsync` when testing
|
||||
code that involves the `intervalTimer`, as is common when
|
||||
testing async `Observable` methods.
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -1482,7 +1479,7 @@ The tests themselves are almost identical to the stand-alone version:
|
||||
|
||||
Only the selected event test differs. It confirms that the selected `DashboardHeroComponent` hero
|
||||
really does find its way up through the event binding to the host component.
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -1782,7 +1779,6 @@ Inspect and download _all_ of the guide's application test code with this <live-
|
||||
|
||||
</div>
|
||||
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -1848,7 +1844,7 @@ Here are a few more `HeroDetailComponent` tests to drive the point home.
|
||||
|
||||
</code-example>
|
||||
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -1945,8 +1941,6 @@ especially when the feature module is small and mostly self-contained, as featur
|
||||
|
||||
|
||||
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
|
||||
@ -2104,7 +2098,6 @@ The `TestBed` offers similar `overrideDirective`, `overrideModule`, and `overrid
|
||||
for digging into and replacing parts of these other classes.
|
||||
|
||||
Explore the options and combinations on your own.
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -2272,8 +2265,6 @@ tests with the `RouterTestingModule`.
|
||||
|
||||
</div>
|
||||
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
|
||||
@ -2327,7 +2318,6 @@ such as misspelled or misused components and directives.
|
||||
|
||||
</div>
|
||||
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -2427,7 +2417,7 @@ The test for the default color uses the injector of the second `<h2>` to get its
|
||||
and its `defaultColor`.
|
||||
|
||||
* `DebugElement.properties` affords access to the artificial custom property that is set by the directive.
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -2655,7 +2645,6 @@ may require meticulous preparation with the Angular testing utilities.
|
||||
On the other hand, isolated unit tests can't confirm that the `ButtonComp` is
|
||||
properly bound to its template or even data bound at all.
|
||||
Use Angular tests for that.
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -3642,8 +3631,6 @@ The Angular `By` class has three static methods for common predicates:
|
||||
|
||||
</code-example>
|
||||
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
<div class='l' class='hr'>
|
||||
|
||||
</div>
|
||||
@ -3792,7 +3779,6 @@ The sample tests are written to run in Jasmine and karma.
|
||||
The two "fast path" setups added the appropriate Jasmine and karma npm packages to the
|
||||
`devDependencies` section of the `package.json`.
|
||||
They're installed when you run `npm install`.
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
<div class='l' class='hr'>
|
||||
|
||||
|
@ -87,7 +87,7 @@ Here's the app in action:
|
||||
|
||||
|
||||
|
||||
## Up next
|
||||
## Next step
|
||||
|
||||
You'll build the Tour of Heroes app, step by step.
|
||||
Each step is motivated with a requirement that you've likely
|
||||
|
@ -263,7 +263,7 @@ You can edit the hero's name and see the changes reflected immediately in the `<
|
||||
|
||||
|
||||
|
||||
## The road you've travelled
|
||||
## Summary
|
||||
|
||||
Take stock of what you've built.
|
||||
|
||||
@ -286,7 +286,7 @@ Here's the complete `app.component.ts` as it stands now:
|
||||
|
||||
|
||||
|
||||
## The road ahead
|
||||
## Next step
|
||||
In the [next tutorial page](tutorial/toh-pt2 "Master/Detail"), you'll build on the Tour of Heroes app to display a list of heroes.
|
||||
You'll also allow the user to select heroes and display their details.
|
||||
You'll learn more about how to retrieve lists and bind them to the template.
|
||||
|
@ -450,7 +450,7 @@ Here's the complete `app.component.ts` as of now:
|
||||
|
||||
|
||||
|
||||
## The road you've travelled
|
||||
## Summary
|
||||
|
||||
Here's what you achieved in this page:
|
||||
|
||||
@ -460,7 +460,7 @@ Here's what you achieved in this page:
|
||||
|
||||
Your app should look like this <live-example></live-example>.
|
||||
|
||||
## The road ahead
|
||||
## Next step
|
||||
You've expanded the Tour of Heroes app, but it's far from complete.
|
||||
An app shouldn't be one monolithic component.
|
||||
In the [next page](tutorial/toh-pt3 "Multiple Components"), you'll split the app into subcomponents and make them work together.
|
||||
|
@ -443,7 +443,7 @@ Here are the code files discussed in this page.
|
||||
|
||||
|
||||
|
||||
## The road you’ve travelled
|
||||
## Summary
|
||||
|
||||
Here's what you achieved in this page:
|
||||
|
||||
@ -457,7 +457,7 @@ Your app should look like this <live-example></live-example>.
|
||||
|
||||
|
||||
|
||||
## The road ahead
|
||||
## Next step
|
||||
The Tour of Heroes app is more reusable with shared components,
|
||||
but its (mock) data is still hard coded within the `AppComponent`.
|
||||
That's not sustainable.
|
||||
|
@ -593,7 +593,7 @@ Here are the code files discussed in this page.
|
||||
|
||||
|
||||
|
||||
## The road you've travelled
|
||||
## Summary
|
||||
Here's what you achieved in this page:
|
||||
|
||||
* You created a service class that can be shared by many components.
|
||||
@ -604,7 +604,7 @@ Here's what you achieved in this page:
|
||||
|
||||
Your app should look like this <live-example></live-example>.
|
||||
|
||||
## The road ahead
|
||||
## Next step
|
||||
The Tour of Heroes has become more reusable using shared components and services.
|
||||
The next goal is to create a dashboard, add menu links that route between the views, and format data in a template.
|
||||
As the app evolves, you'll discover how to design it to make it easier to grow and maintain.
|
||||
|
@ -1356,7 +1356,7 @@ Verify that you have the following structure:
|
||||
|
||||
|
||||
|
||||
## The road you’ve travelled
|
||||
## Summary
|
||||
Here's what you achieved in this page:
|
||||
|
||||
* You added the Angular router to navigate among different components.
|
||||
@ -1368,7 +1368,7 @@ Here's what you achieved in this page:
|
||||
|
||||
Your app should look like this <live-example></live-example>.
|
||||
|
||||
### The road ahead
|
||||
### Next step
|
||||
|
||||
You have much of the foundation you need to build an app.
|
||||
You're still missing a key piece: remote data access.
|
||||
|
@ -518,7 +518,7 @@ Verify that you have the following structure:
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## Home Stretch
|
||||
## Summary
|
||||
|
||||
You're at the end of your journey, and you've accomplished a lot.
|
||||
* You added the necessary dependencies to use HTTP in the app.
|
||||
|
24
aio/e2e/api.e2e-spec.ts
Normal file
24
aio/e2e/api.e2e-spec.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { ApiPage } from './api.po';
|
||||
|
||||
describe('Api pages', function() {
|
||||
it('should show direct subclasses of a class', () => {
|
||||
const page = new ApiPage('api/forms/AbstractControlDirective');
|
||||
// We must use `as any` (here and below) because of broken typings for jasmine
|
||||
expect(page.getDescendants('class', true)).toEqual(['ControlContainer', 'NgControl'] as any);
|
||||
});
|
||||
|
||||
it('should show direct and indirect subclasses of a class', () => {
|
||||
const page = new ApiPage('api/forms/AbstractControlDirective');
|
||||
expect(page.getDescendants('class')).toEqual(['ControlContainer', 'AbstractFormGroupDirective', 'NgControl'] as any);
|
||||
});
|
||||
|
||||
it('should show child interfaces that extend an interface', () => {
|
||||
const page = new ApiPage('api/forms/Validator');
|
||||
expect(page.getDescendants('interface')).toEqual(['AsyncValidator'] as any);
|
||||
});
|
||||
|
||||
it('should show classes that implement an interface', () => {
|
||||
const page = new ApiPage('api/animations/AnimationPlayer');
|
||||
expect(page.getDescendants('class')).toEqual(['NoopAnimationPlayer', 'MockAnimationPlayer'] as any);
|
||||
});
|
||||
});
|
30
aio/e2e/api.po.ts
Normal file
30
aio/e2e/api.po.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { element, by } from 'protractor';
|
||||
import { SitePage } from './app.po';
|
||||
|
||||
export class ApiPage extends SitePage {
|
||||
constructor(url: string) {
|
||||
super();
|
||||
this.navigateTo(url);
|
||||
}
|
||||
|
||||
getDescendants(docType: string, onlyDirect = false) {
|
||||
// This selector is horrible because we have potentially recursive HTML lists
|
||||
//
|
||||
// ul
|
||||
// li
|
||||
// code
|
||||
// ul
|
||||
// li
|
||||
// code
|
||||
// ul
|
||||
// li
|
||||
// code
|
||||
// li
|
||||
// code
|
||||
//
|
||||
// and we want to be able to pull out the code elements from only the first level
|
||||
// if `onlyDirect` is set to `true`.
|
||||
const selector = `.descendants.${docType} ${onlyDirect ? '>' : ''} li > :not(ul) code`;
|
||||
return element.all(by.css(selector)).map<string>(item => item.getText());
|
||||
}
|
||||
}
|
@ -82,7 +82,7 @@
|
||||
"concurrently": "^3.4.0",
|
||||
"cross-spawn": "^5.1.0",
|
||||
"dgeni": "^0.4.7",
|
||||
"dgeni-packages": "^0.21.2",
|
||||
"dgeni-packages": "^0.21.3",
|
||||
"entities": "^1.1.1",
|
||||
"eslint": "^3.19.0",
|
||||
"eslint-plugin-jasmine": "^2.2.0",
|
||||
|
@ -61,7 +61,7 @@ else
|
||||
# Nothing changed in aio/
|
||||
exit 0
|
||||
fi
|
||||
message=$(echo $TRAVIS_COMMIT_MESSAGE | sed 's/"/\\"/g' | sed 's/\\/\\\\/g')
|
||||
message=$(echo $TRAVIS_COMMIT_MESSAGE | sed 's/\\/\\\\/g' | sed 's/"/\\"/g')
|
||||
payloadData="$payloadData\"change\": \"$change\", \"message\": \"$message\""
|
||||
|
||||
payloadData="{${payloadData}}"
|
||||
|
70
aio/tools/README.md
Normal file
70
aio/tools/README.md
Normal file
@ -0,0 +1,70 @@
|
||||
# AIO project tooling
|
||||
|
||||
This document gives an overview of the tools that we use to generate the content for the angular.io website.
|
||||
|
||||
The application that actually renders this content can be found in the `/aio/src` folder.
|
||||
The handwritten content can be found in the `/aio/content` folder.
|
||||
|
||||
Each subfolder in this `/aio/tools/` folder contains a self-contained tool and its configuration. There is
|
||||
a `README.md` file in each folder that describes the tool in more detail.
|
||||
|
||||
## cli-patches
|
||||
|
||||
The AIO application is built using the Angular CLI tool. We are often trialling new features for the CLI, which
|
||||
we apply to the library after it is installed. This folder contains git patch files that contain these new features
|
||||
and a utility to apply those patches to the CLI library.
|
||||
|
||||
See the [README.md](cli-patches/README.md) for more details.
|
||||
|
||||
## examples
|
||||
|
||||
Many of the documentation pages contain snippets of code examples. We extract these snippets from real
|
||||
working example applications, which are stored in subfolders of the `/aio/content/examples` folder. Each
|
||||
example can be built and run independently. Each example also provides e2e specs, which are run as part
|
||||
of our Travis build tasks, to verify that the examples continue to work as expected, as changes are made
|
||||
to the core Angular libraries.
|
||||
|
||||
In order to build, run and test these examples independently we need to install dependencies into their
|
||||
sub-folder. Also there are a number of common boilerplate files that are needed to configure each
|
||||
example's project. We maintain these common boilerplate files centrally to reduce the amount of effort
|
||||
if one of them needs to change.
|
||||
|
||||
This `examples` tool folder contains two utilities:
|
||||
|
||||
* example-boilerplate.js - install/remove the npm dependencies and boilerplate files into/from each of the
|
||||
examples' subfolders.
|
||||
* run-example-e2e.js - run the e2e tests for one or more examples
|
||||
|
||||
See the [README.md](examples/README.md) for more details.
|
||||
|
||||
## example-zipper
|
||||
|
||||
In the AIO application, we offer the reader the option to download each example as a full self-contained
|
||||
runnable project packaged as a zip file. These zip files are generated by the utility in this folder.
|
||||
|
||||
See the [README.md](example-zipper/README.md) for more details.
|
||||
|
||||
## plunker-builder
|
||||
|
||||
In the AIO application, we can embed a running version of the example as a [Plunker](http://plnkr.co/).
|
||||
We can also provide a link to create a runnable version of the example in the [Plunker](http://plnkr.co/edit)
|
||||
editor.
|
||||
|
||||
This folder contains three utilities: `regularPlunker.js`, `embeddedPlunker.js`, `generatePlunkers.js`.
|
||||
See the [README.md](plunker-builder/README.md) for more details.
|
||||
|
||||
## transforms
|
||||
|
||||
All the content that is rendered by the AIO application, and some of its configuration files, are
|
||||
generated from source files by [Dgeni](https://github.com/angular/dgeni). Dgeni is a general purpose
|
||||
documentation generation tool.
|
||||
|
||||
Markdown files in `/aio/content`, code comments in the core Angular source files and example files are
|
||||
processed and transformed into files that are consumed by the AIO application.
|
||||
|
||||
Dgeni is configured by "packages", which contain services and processors. Some of these packages are
|
||||
installed as `node_modules` from the [dgeni-packages](https://github.com/angular/dgeni-packages) and
|
||||
some are specific to the AIO project.
|
||||
|
||||
The project specific packages are stored in the `aio/tools/transforms` folder. See the
|
||||
[README.md](transforms/README.md) for more details.
|
7
aio/tools/cli-patches/README.md
Normal file
7
aio/tools/cli-patches/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
# Overview
|
||||
|
||||
The AIO application is built using the Angular CLI tool. We are often trialling new features for the CLI, which
|
||||
we apply to the library after it is installed. This folder contains git patch files that contain these new features
|
||||
and a utility to apply those patches to the CLI library.
|
||||
|
||||
**Currently, we have no patches to run.**
|
84
aio/tools/example-zipper/README.md
Normal file
84
aio/tools/example-zipper/README.md
Normal file
@ -0,0 +1,84 @@
|
||||
# Overview
|
||||
|
||||
In the AIO application, we offer the reader the option to download each example as a full self-contained
|
||||
runnable project packaged as a zip file. These zip files are generated by the utility in this folder.
|
||||
|
||||
## Example zipper
|
||||
|
||||
The `exampleZipper.js` tool is very similar to the Plunker's `builder.js`. The latter creates an HTML file
|
||||
with all the examples' files and the former creates a zip file instead. They both use the `plnkr.json` file
|
||||
to flag an example as something to plunker or zip. For example:
|
||||
|
||||
```json
|
||||
{
|
||||
"description": "Tour of Heroes: Part 6",
|
||||
"basePath": "src/",
|
||||
"files":[
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js",
|
||||
"!**/*.[1,2].*"
|
||||
],
|
||||
"tags": ["tutorial", "tour", "heroes", "http"]
|
||||
}
|
||||
```
|
||||
|
||||
The zipper will use this information for creating new zips.
|
||||
|
||||
## Three kinds of examples
|
||||
|
||||
The majority of examples in AIO use `System.js` but there are also `CLI` and `Webpack` projects. This
|
||||
tool is able to differentiate between them.
|
||||
|
||||
The boilerplate uses a `package.json` that contains packages and scripts to run any kind of example.
|
||||
Using that `package.json` in the zips would confuse the users.
|
||||
|
||||
Thanks to the `package.json` customizer, we can create a new `package.json` on the fly that would
|
||||
only contain the packages and scripts needed to run that example.
|
||||
|
||||
The `exampleZipper.js` won't include any `System.js` related files for `CLI` or `Webpack` projects.
|
||||
|
||||
### The package.json customizer
|
||||
|
||||
Given a `type`, this tool will populate a `package.json` file customized for that type.
|
||||
|
||||
Here you find a:
|
||||
|
||||
* **base.json** - All the common scripts and packages
|
||||
* **cli.json** - Extra scripts and packages for the CLI
|
||||
* **webpack.json** - Extra scripts and packages for Webpack
|
||||
* **systemjs.json** - All the System.js related packages but it also contains the remainder scripts
|
||||
that are not in the other files.
|
||||
|
||||
The tool will also give some standard names to the scripts.
|
||||
|
||||
## The zipper.json
|
||||
|
||||
As mentioned, the tool uses the `plnkr.json` as a flag and also a configuration file for the zipper.
|
||||
The problem is that not all examples have a plunker but they could offer a zip instead.
|
||||
|
||||
In those cases, you can create a `zipper.json` file with the same syntax. It will be ignored by the
|
||||
plunker tool.
|
||||
|
||||
## Choosing the zip "type"
|
||||
|
||||
In both `plnkr.json` and `zipper.json` you can use two extra properties for the zipper configuration:
|
||||
|
||||
```
|
||||
{
|
||||
...
|
||||
"removeSystemJsConfig": true,
|
||||
"type": "webpack"
|
||||
}
|
||||
```
|
||||
|
||||
This would generate a zip for a webpack application and it will also remove everything related with
|
||||
SystemJS.
|
||||
|
||||
## Executing the zip generation
|
||||
|
||||
`generateZips.js` will create a zip for each `plnkr.json` or `zipper.json` it finds.
|
||||
|
||||
Where? At `src/generated/zips/`.
|
||||
|
||||
Then the `<live-example>` embedded component will look at this folder to get the zip it needs for
|
||||
the example.
|
108
aio/tools/examples/README.md
Normal file
108
aio/tools/examples/README.md
Normal file
@ -0,0 +1,108 @@
|
||||
# Overview
|
||||
|
||||
Many of the documentation pages contain snippets of code examples. We extract these snippets from
|
||||
real working example applications, which are stored in subfolders of the `/aio/content/examples`
|
||||
folder. Each example can be built and run independently. Each example also provides e2e specs, which
|
||||
are run as part of our Travis build tasks, to verify that the examples continue to work as expected,
|
||||
as changes are made to the core Angular libraries.
|
||||
|
||||
In order to build, run and test these examples independently we need to install dependencies into
|
||||
their sub-folder. Also there are a number of common boilerplate files that are needed to configure
|
||||
each example's project. We maintain these common boilerplate files centrally to reduce the amount
|
||||
of effort if one of them needs to change.
|
||||
|
||||
## Boilerplate overview
|
||||
|
||||
As mentioned, many of the documentation pages contain snippets extracted from real example applications.
|
||||
To achieve that, all those applications needs to contain a basic boilerplate. E.g. a `node_modules`
|
||||
folder, `package.json` with scripts, `system.js` configuration, etc.
|
||||
|
||||
No one wants to maintain the boilerplate on each example, so the goal of this tool is to provide a
|
||||
generic boilerplate that works in all the examples.
|
||||
|
||||
### Boilerplate files
|
||||
|
||||
Inside `/aio/tools/examples/shared/boilerplate` you will see all the common boilerplate you can find
|
||||
in any Angular application using System.js. This is the boilerplate that will be carried to each example.
|
||||
|
||||
Among these files, there are a few special ones:
|
||||
|
||||
* **src/systemjs.config.js** - This is the configuration of System.js used to run the example locally.
|
||||
* **src/systemjs.config.web.js** - This configuration replaces the previous one on Plunkers.
|
||||
* **src/systemjs.config.web.build.js** - Same as the previous one but for using angular's `-builds`
|
||||
versions.
|
||||
* **src/systemjs-angular-loader.js** - It is a System.js plugin that removes the need of `moduleId`.
|
||||
* **package.json** - This package.json only contains scripts, no dependencies. It contains the
|
||||
different tasks needed to run any example. Doesn't matter if CLI, System.js or Webpack.
|
||||
* **plnkr.json** - This file is used by the Plunker tool to generate a plunker for an example. This
|
||||
concrete file is just a placeholder. Authors needs to tweak it for each guide. More at the
|
||||
[plunker docs](../plunker-builder/README.md).
|
||||
* **example-config.json** - This file serves as a flag to indicate that the current folder is an
|
||||
example. This concrete file is just a placeholder. More on this later in this readme.
|
||||
|
||||
### The example-config.json
|
||||
|
||||
So what is this **example-config.json** again? If an author wants to create a new example, say
|
||||
`/aio/content/examples/awesome-example`. The author needs to create an empty `example-config.json`
|
||||
in that folder. That serves as a flag so this tool will say "Hey, that is an example, let's copy
|
||||
all the boilerplate there".
|
||||
|
||||
So when the tool runs, it finds **all** the folders with an `example-config.json` file, and puts
|
||||
a copy of the boilerplate in those folders.
|
||||
|
||||
Normally the file is empty, but we can add information in it, for example:
|
||||
|
||||
```json
|
||||
{
|
||||
"build": "build:cli",
|
||||
"run": "serve:cli"
|
||||
}
|
||||
```
|
||||
|
||||
In this case, this would indicate that this is a CLI example. Won't make any difference on the
|
||||
boilerplate, but will be useful for e2e tests (more on this later). Also works as a hint for
|
||||
the example to know how is executed.
|
||||
|
||||
|
||||
### A node_modules to share
|
||||
|
||||
With all the boilerplate files in place, the only missing piece are the installed packages. For
|
||||
that we have a `/aio/tools/examples/shared/package.json` which contains **all** the packages
|
||||
needed to run all the examples.
|
||||
|
||||
After installing these dependencies, a `node_modules` will be created at
|
||||
`/aio/tools/examples/shared/node_modules`. This folder will be **symlinked** into each example.
|
||||
So it is not a copy like the other boilerplate files. This solution works in all OSes. Windows
|
||||
may require admin rights.
|
||||
|
||||
### End to end tests
|
||||
|
||||
Each example contains an `e2e-spec.ts` file. We can find all the related configuration files for
|
||||
e2e in the `/aio/tools/examples/shared` folder.
|
||||
|
||||
This tool expects all the examples to be build with `npm run build`. If an example is not built
|
||||
with that script, the author would need to specify the new build command in the `example-config.json`
|
||||
as shown earlier.
|
||||
|
||||
### add-example-boilerplate.js
|
||||
|
||||
This script installs all the dependencies that are shared among all the examples, creates the
|
||||
`node_modules` symlinks and copy all the boilerplate files where needed. It won't do anything
|
||||
about plunkers nor e2e tests.
|
||||
|
||||
It also contains a function to remove all the boilerplate. It uses a `git clean -xdf` to do
|
||||
the job. It will remove all files that don't exist in the git repository, **including any
|
||||
new file that you are working on that hasn't been stage yet.** So be sure to save your work
|
||||
before removing the boilerplate.
|
||||
|
||||
### run-example-e2e.js
|
||||
|
||||
This script will find all the `e2e-spec.ts` files and run them.
|
||||
|
||||
To not run all tests, you can use the `--filter=name` flag to run the example's e2e that contains
|
||||
that name.
|
||||
|
||||
It also has an optional `--setup` flag to run the `add-example-boilerplate.js` script and install
|
||||
the latest `webdriver`.
|
||||
|
||||
It will create a `/aio/protractor-results-txt` file when it finishes running tests.
|
78
aio/tools/plunker-builder/README.md
Normal file
78
aio/tools/plunker-builder/README.md
Normal file
@ -0,0 +1,78 @@
|
||||
# Overview
|
||||
|
||||
[Plunker](http://plnkr.co) is an online tool for creating, collaborating and sharing ideas. In AIO
|
||||
we use it to share one or more runnable versions of our examples.
|
||||
|
||||
Plunker comes in two flavours. The [classic UI](http://plnkr.co/edit) and an [embedded UI](http://embed.plnkr.co).
|
||||
The latter can be used both in a new tab or embedded within a guide. The AIO project uses the
|
||||
embedded version in both ways.
|
||||
|
||||
* `regularPlunker.js` - generates an HTML file for each example that will post to Plunker to create
|
||||
a new editable project, when rendered.
|
||||
* `embeddedPlunker.js` - generates an HTML file for each example that can be used in an iframe to
|
||||
render an embedded Plunker project.
|
||||
* `generatePlunkers.js` - executes each of the `regularPlunker.js` and `embeddedPlunker.js` utilities
|
||||
to generate all the example plunker files.
|
||||
|
||||
## Plunker generation
|
||||
|
||||
Both flavours are created within `builder.js`. How is a plunker created? What is the process from a
|
||||
directory with files to a link with a plunker.
|
||||
|
||||
An "executable" plunker is an HTML file with a `<form>` that makes a post to plunker on submit. It
|
||||
contains an `<input>` element for each file we need in the plunker.
|
||||
|
||||
The form will be submitted on load, so you can either double click the HTML file or open it with an
|
||||
anchor tag to open the plunker.
|
||||
|
||||
So the `builder.js` job is to get all the needed files from an example and build this HTML file for you.
|
||||
|
||||
For plunkers, we use a special `systemjs.config` that exists in
|
||||
`/aio/tools/examples/shared/boilerplate/src/systemjs.config.web.js` and we also add the Google's copyright
|
||||
to each file.
|
||||
|
||||
## Customizing the generation per example basis
|
||||
|
||||
How does this tool know what is an example and what is not? It will look for all folders containing a
|
||||
`plnkr.json` file. If found, all files within the folder and subfolders will be used in the plunker, with
|
||||
a few generic exceptions that you can find at `builder.js`.
|
||||
|
||||
You can use the `plnkr.json` to customize the plunker generation. For example:
|
||||
|
||||
```json
|
||||
{
|
||||
"description": "Tour of Heroes: Part 6",
|
||||
"basePath": "src/",
|
||||
"files":[
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js",
|
||||
"!**/*.[1,2].*"
|
||||
],
|
||||
"tags": ["tutorial", "tour", "heroes", "http"]
|
||||
}
|
||||
```
|
||||
|
||||
Here you can specify a description for the plunker, some tags, a basePath and also a files array where you
|
||||
can specify extra files to add or to ignore.
|
||||
|
||||
## Classic plunkers and embedded ones
|
||||
|
||||
Luckily, both kind of plunkers are very similar, they are created in the same way with minor exceptions.
|
||||
|
||||
To handle those exceptions, we have the `embeddedPlunker.js` and the `regularPlunker.js`. Thanks to them,
|
||||
the `builder.js` is as generic as possible.
|
||||
|
||||
## Executing the plunker generation
|
||||
|
||||
`generatePlunkers.js` will create a classic plunker and an embedded plunker for each `plnkr.json` it finds.
|
||||
|
||||
Where? At `src/generated/live-examples/`.
|
||||
|
||||
Then the `<live-example>` embedded component will look at this folder to get the plunker it needs for the
|
||||
example.
|
||||
|
||||
## Appendix: Why not generating plunkers at runtime?
|
||||
|
||||
At AngularJS, all the plunkers were generated a runtime. The downside is that all the example codes would
|
||||
need to be deployed as well and they won't be no longer useful after the plunker is generated. This tool
|
||||
takes a few seconds to run, and the end result is only 3mb~.
|
@ -1,27 +1,50 @@
|
||||
# Documentation Generation
|
||||
# Overview
|
||||
|
||||
The dgeni tool is used to generate the documentation from the source files held in this repository.
|
||||
The documentation generation is configured by a dgeni package defined in `tools/transforms/angular.io-package/index.js`.
|
||||
This package, in turn requires a number of other packages, some are defined locally in the `tools/transforms` folder,
|
||||
such as `tools/transforms/cheatsheet-package` and `tools/transforms/content-package`, etc. And some are brought in from the
|
||||
`dgeni-packages` node modules, such as `jsdoc` and `nunjucks`.
|
||||
All the content that is rendered by the AIO application, and some of its configuration files, are
|
||||
generated from source files by [Dgeni](https://github.com/angular/dgeni). Dgeni is a general purpose
|
||||
documentation generation tool.
|
||||
|
||||
## Generating the docs
|
||||
Markdown files in `/aio/content`, code comments in the core Angular source files and example files
|
||||
are processed and transformed into files that are consumed by the AIO application.
|
||||
|
||||
To generate the documentation simply run `yarn docs` from the command line.
|
||||
Dgeni is configured by "packages", which contain services and processors. Some of these packages are
|
||||
installed as `node_modules` from the [dgeni-packages](https://github.com/angular/dgeni-packages) and
|
||||
some are specific to the AIO project.
|
||||
|
||||
## Testing the dgeni packages
|
||||
The project specific packages are stored in this folder (`aio/tools/transforms`).
|
||||
|
||||
The local packages have unit tests that you can execute by running `yarn docs-test` from the command line.
|
||||
If you are an author and want to know how to generate the documentation, the steps are outlined in
|
||||
the top level [README.md](../../README.md#guide-to-authoring).
|
||||
|
||||
## What does it generate?
|
||||
## Root packages
|
||||
|
||||
The output from dgeni is written to files in the `src/generated` folder.
|
||||
To run Dgeni, you must specify a root package, which acts as the entry point to the documentation
|
||||
generation.
|
||||
This root package, in turn requires a number of other packages, some are defined locally in the
|
||||
`tools/transforms` folder, such as `tools/transforms/cheatsheet-package` and
|
||||
`tools/transforms/content-package`, etc. And some are brought in from the `dgeni-packages` node
|
||||
modules, such as `jsdoc` and `nunjucks`.
|
||||
|
||||
Notably this includes a JSON file containing the partial HTML for each "page" of the documentation, such as API pages and guides.
|
||||
It also includes JSON files that contain metadata about the documentation such as navigation data and
|
||||
keywords for building a search index.
|
||||
* The primary root package is defined in `tools/transforms/angular.io-package/index.js`. This package
|
||||
is used to run a full generation of all the documentation.
|
||||
* There are also root packages defined in `tools/transforms/authors-package/*-package.js`. These
|
||||
packages are used by the documentation authors when writing docs, since it allows them to run partial
|
||||
doc generation, which is not complete but is faster for quickly seeing changes to the document that
|
||||
you are working on.
|
||||
|
||||
## Viewing the docs
|
||||
## Other packages
|
||||
|
||||
You can view the pages by running `yarn start` and navigating to https://localhost:4200.
|
||||
* angular-base-package
|
||||
* angular-api-package
|
||||
* angular-content-package
|
||||
* content-package
|
||||
* examples-package
|
||||
* links-package
|
||||
* post-process-package
|
||||
* remark-package
|
||||
* target-package
|
||||
|
||||
## Templates
|
||||
|
||||
All the templates for the angular.io dgeni transformations are stoted in the `tools/transforms/templates`
|
||||
folder. See the [README](./templates/README.md).
|
@ -20,6 +20,7 @@ module.exports = new Package('angular-api', [basePackage, typeScriptPackage])
|
||||
.processor(require('./processors/mergeDecoratorDocs'))
|
||||
.processor(require('./processors/extractDecoratedClasses'))
|
||||
.processor(require('./processors/matchUpDirectiveDecorators'))
|
||||
.processor(require('./processors/addMetadataAliases'))
|
||||
.processor(require('./processors/filterContainedDocs'))
|
||||
.processor(require('./processors/markBarredODocsAsPrivate'))
|
||||
.processor(require('./processors/filterPrivateDocs'))
|
||||
|
40
aio/tools/transforms/angular-api-package/processors/addMetadataAliases.js
vendored
Normal file
40
aio/tools/transforms/angular-api-package/processors/addMetadataAliases.js
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* @dgProcessor addMetadataAliases
|
||||
*
|
||||
* Directives and components can also be referenced by their selectors,
|
||||
* and Pipes can be referenced by their name.
|
||||
* So let's add each selector as an alias to this doc.
|
||||
*/
|
||||
module.exports = function addMetadataAliasesProcessor() {
|
||||
return {
|
||||
$runAfter: ['extractDecoratedClassesProcessor'],
|
||||
$runBefore: ['computing-ids'],
|
||||
$process: function(docs) {
|
||||
docs.forEach(doc => {
|
||||
switch(doc.docType) {
|
||||
case 'directive':
|
||||
case 'component':
|
||||
doc.aliases = doc.aliases.concat(extractSelectors(doc[doc.docType + 'Options'].selector));
|
||||
break;
|
||||
case 'pipe':
|
||||
if (doc.pipeOptions.name) {
|
||||
doc.aliases = doc.aliases.concat(stripQuotes(doc.pipeOptions.name));
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function extractSelectors(selectors) {
|
||||
if (selectors) {
|
||||
return stripQuotes(selectors).split(',').map(selector => selector.replace(/^\W*([\w-]+)\W*$/, '$1'));
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function stripQuotes(value) {
|
||||
return (typeof(value) === 'string') ? value.trim().replace(/^(['"])(.*)\1$/, '$2') : value;
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
const testPackage = require('../../helpers/test-package');
|
||||
const processorFactory = require('./addMetadataAliases');
|
||||
const Dgeni = require('dgeni');
|
||||
|
||||
describe('addSelectorsAsAliases processor', () => {
|
||||
|
||||
it('should be available on the injector', () => {
|
||||
const dgeni = new Dgeni([testPackage('angular-api-package')]);
|
||||
const injector = dgeni.configureInjector();
|
||||
const processor = injector.get('addMetadataAliasesProcessor');
|
||||
expect(processor.$process).toBeDefined();
|
||||
});
|
||||
|
||||
it('should run after the correct processor', () => {
|
||||
const processor = processorFactory();
|
||||
expect(processor.$runAfter).toEqual(['extractDecoratedClassesProcessor']);
|
||||
});
|
||||
|
||||
it('should run before the correct processor', () => {
|
||||
const processor = processorFactory();
|
||||
expect(processor.$runBefore).toEqual(['computing-ids']);
|
||||
});
|
||||
|
||||
it('should add new aliases for directives, components and pipes', () => {
|
||||
const processor = processorFactory();
|
||||
const docs = [
|
||||
{ docType: 'class', name: 'MyClass', aliases: ['MyClass'] },
|
||||
{ docType: 'interface', name: 'MyInterface', aliases: ['MyInterface'] },
|
||||
{ docType: 'enum', name: 'MyEnum', aliases: ['MyEnum'] },
|
||||
{ docType: 'function', name: 'myFunction', aliases: ['myFunction'] },
|
||||
{ docType: 'pipe', name: 'MyPipe', aliases: ['MyPipe'], pipeOptions: { name: '\'myPipe\'' } },
|
||||
{ docType: 'directive', name: 'MyDirective', aliases: ['MyDirective'], directiveOptions: { selector: '\'my-directive,[myDirective],[my-directive]\'' } },
|
||||
{ docType: 'directive', name: 'NgModel', aliases: ['NgModel'], directiveOptions: { selector: '\'[ngModel]:not([formControlName]):not([formControl])\'' } },
|
||||
{ docType: 'component', name: 'MyComponent', aliases: ['MyComponent'], componentOptions: { selector: '\'my-component\'' } },
|
||||
{ docType: 'decorator', name: 'MyDecorator', aliases: ['MyDecorator'] },
|
||||
{ docType: 'module', name: 'myModule', aliases: ['myModule'], id: 'some/myModule' },
|
||||
{ docType: 'var', name: 'myVar', aliases: ['myVar'] },
|
||||
{ docType: 'let', name: 'myLet', aliases: ['myLet'] },
|
||||
{ docType: 'const', name: 'myConst', aliases: ['myConst'] },
|
||||
{ docType: 'type-alias', name: 'myType', aliases: ['myType'] },
|
||||
];
|
||||
processor.$process(docs);
|
||||
expect(docs[0].aliases).toEqual([docs[0].name]);
|
||||
expect(docs[1].aliases).toEqual([docs[1].name]);
|
||||
expect(docs[2].aliases).toEqual([docs[2].name]);
|
||||
expect(docs[3].aliases).toEqual([docs[3].name]);
|
||||
expect(docs[4].aliases).toEqual([docs[4].name, 'myPipe']);
|
||||
expect(docs[5].aliases).toEqual([docs[5].name, 'my-directive', 'myDirective', 'my-directive']);
|
||||
expect(docs[6].aliases).toEqual([docs[6].name, '[ngModel]:not([formControlName]):not([formControl])']);
|
||||
expect(docs[7].aliases).toEqual([docs[7].name, 'my-component']);
|
||||
expect(docs[8].aliases).toEqual([docs[8].name]);
|
||||
expect(docs[9].aliases).toEqual([docs[9].name]);
|
||||
expect(docs[10].aliases).toEqual([docs[10].name]);
|
||||
expect(docs[11].aliases).toEqual([docs[11].name]);
|
||||
expect(docs[12].aliases).toEqual([docs[12].name]);
|
||||
});
|
||||
});
|
@ -48,9 +48,9 @@ module.exports = function mergeDecoratorDocs(log) {
|
||||
$runAfter: ['processing-docs'],
|
||||
$runBefore: ['docs-processed'],
|
||||
makeDecoratorCalls: [
|
||||
{type: '', description: 'toplevel'},
|
||||
{type: 'Prop', description: 'property'},
|
||||
{type: 'Param', description: 'parameter'},
|
||||
{type: '', description: 'toplevel', functionName: 'makeDecorator'},
|
||||
{type: 'Prop', description: 'property', functionName: 'makePropDecorator'},
|
||||
{type: 'Param', description: 'parameter', functionName: 'makeParamDecorator'},
|
||||
],
|
||||
$process: function(docs) {
|
||||
|
||||
@ -58,24 +58,25 @@ module.exports = function mergeDecoratorDocs(log) {
|
||||
var docsToMerge = Object.create(null);
|
||||
|
||||
docs.forEach(function(doc) {
|
||||
const initializer = getInitializer(doc);
|
||||
if (initializer) {
|
||||
makeDecoratorCalls.forEach(function(call) {
|
||||
// find all the decorators, signified by a call to `make...Decorator<Decorator>(metadata)`
|
||||
if (initializer.expression && initializer.expression.text === call.functionName) {
|
||||
log.debug('mergeDecoratorDocs: found decorator', doc.docType, doc.name);
|
||||
doc.docType = 'decorator';
|
||||
doc.decoratorLocation = call.description;
|
||||
// Get the type of the decorator metadata from the first "type" argument of the call.
|
||||
// For example the `X` of `createDecorator<X>(...)`.
|
||||
doc.decoratorType = initializer.arguments[0].text;
|
||||
// clear the symbol type named since it is not needed
|
||||
doc.symbolTypeName = undefined;
|
||||
|
||||
makeDecoratorCalls.forEach(function(call) {
|
||||
// find all the decorators, signified by a call to `make...Decorator<Decorator>(metadata)`
|
||||
var makeDecorator = getMakeDecoratorCall(doc, call.type);
|
||||
if (makeDecorator) {
|
||||
log.debug('mergeDecoratorDocs: found decorator', doc.docType, doc.name);
|
||||
doc.docType = 'decorator';
|
||||
doc.decoratorLocation = call.description;
|
||||
// Get the type of the decorator metadata from the first "type" argument of the call.
|
||||
// For example the `X` of `createDecorator<X>(...)`.
|
||||
doc.decoratorType = makeDecorator.arguments[0].text;
|
||||
// clear the symbol type named since it is not needed
|
||||
doc.symbolTypeName = undefined;
|
||||
|
||||
// keep track of the names of the metadata interface that will need to be merged into this decorator doc
|
||||
docsToMerge[doc.name + 'Decorator'] = doc;
|
||||
}
|
||||
});
|
||||
// keep track of the names of the metadata interface that will need to be merged into this decorator doc
|
||||
docsToMerge[doc.name + 'Decorator'] = doc;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// merge the metadata docs into the decorator docs
|
||||
@ -106,27 +107,19 @@ module.exports = function mergeDecoratorDocs(log) {
|
||||
};
|
||||
};
|
||||
|
||||
function getMakeDecoratorCall(doc, type) {
|
||||
var makeDecoratorFnName = 'make' + (type || '') + 'Decorator';
|
||||
|
||||
var initializer = doc.declaration &&
|
||||
doc.declaration.initializer;
|
||||
|
||||
if (initializer) {
|
||||
// There appear to be two forms of initializer:
|
||||
// export var Injectable: InjectableFactory =
|
||||
// <InjectableFactory>makeDecorator(InjectableMetadata);
|
||||
// and
|
||||
// export var RouteConfig: (configs: RouteDefinition[]) => ClassDecorator =
|
||||
// makeDecorator(RouteConfigAnnotation);
|
||||
// In the first case, the type assertion `<InjectableFactory>` causes the AST to contain an
|
||||
// extra level of expression
|
||||
// to hold the new type of the expression.
|
||||
if (initializer.expression && initializer.expression.expression) {
|
||||
initializer = initializer.expression;
|
||||
}
|
||||
if (initializer.expression && initializer.expression.text === makeDecoratorFnName) {
|
||||
return initializer;
|
||||
}
|
||||
function getInitializer(doc) {
|
||||
var initializer = doc.symbol && doc.symbol.valueDeclaration && doc.symbol.valueDeclaration.initializer;
|
||||
// There appear to be two forms of initializer:
|
||||
// export var Injectable: InjectableFactory =
|
||||
// <InjectableFactory>makeDecorator(InjectableMetadata);
|
||||
// and
|
||||
// export var RouteConfig: (configs: RouteDefinition[]) => ClassDecorator =
|
||||
// makeDecorator(RouteConfigAnnotation);
|
||||
// In the first case, the type assertion `<InjectableFactory>` causes the AST to contain an
|
||||
// extra level of expression
|
||||
// to hold the new type of the expression.
|
||||
if (initializer && initializer.expression && initializer.expression.expression) {
|
||||
initializer = initializer.expression;
|
||||
}
|
||||
return initializer;
|
||||
}
|
||||
|
@ -2,11 +2,11 @@ var testPackage = require('../../helpers/test-package');
|
||||
var Dgeni = require('dgeni');
|
||||
|
||||
describe('mergeDecoratorDocs processor', () => {
|
||||
var dgeni, injector, processor, moduleDoc, decoratorDoc, metadataDoc, decoratorDocWithTypeAssertion, otherDoc;
|
||||
let processor, moduleDoc, decoratorDoc, metadataDoc, otherDoc;
|
||||
|
||||
beforeEach(() => {
|
||||
dgeni = new Dgeni([testPackage('angular-api-package')]);
|
||||
injector = dgeni.configureInjector();
|
||||
const dgeni = new Dgeni([testPackage('angular-api-package')]);
|
||||
const injector = dgeni.configureInjector();
|
||||
processor = injector.get('mergeDecoratorDocs');
|
||||
|
||||
moduleDoc = {};
|
||||
@ -15,7 +15,9 @@ describe('mergeDecoratorDocs processor', () => {
|
||||
name: 'Component',
|
||||
docType: 'const',
|
||||
description: 'A description of the metadata for the Component decorator',
|
||||
declaration: {initializer: {expression: {text: 'makeDecorator'}, arguments: [{text: 'X'}]}},
|
||||
symbol: {
|
||||
valueDeclaration: { initializer: { expression: { text: 'makeDecorator' }, arguments: [{ text: 'X' }] } }
|
||||
},
|
||||
members: [
|
||||
{ name: 'templateUrl', description: 'A description of the templateUrl property' }
|
||||
],
|
||||
@ -40,45 +42,45 @@ describe('mergeDecoratorDocs processor', () => {
|
||||
moduleDoc
|
||||
};
|
||||
|
||||
decoratorDocWithTypeAssertion = {
|
||||
name: 'Y',
|
||||
docType: 'const',
|
||||
declaration: { initializer: { expression: {type: {}, expression: {text: 'makeDecorator'}, arguments: [{text: 'Y'}]} } },
|
||||
moduleDoc
|
||||
};
|
||||
otherDoc = {
|
||||
name: 'Y',
|
||||
docType: 'const',
|
||||
declaration: {initializer: {expression: {text: 'otherCall'}, arguments: [{text: 'param1'}]}},
|
||||
symbol: {
|
||||
valueDeclaration: { initializer: { expression: { text: 'otherCall' }, arguments: [{ text: 'param1' }] } }
|
||||
},
|
||||
moduleDoc
|
||||
};
|
||||
|
||||
moduleDoc.exports = [decoratorDoc, metadataDoc, decoratorDocWithTypeAssertion, otherDoc];
|
||||
moduleDoc.exports = [decoratorDoc, metadataDoc, otherDoc];
|
||||
});
|
||||
|
||||
|
||||
it('should change the docType of only the docs that are initialied by a call to makeDecorator', () => {
|
||||
processor.$process([decoratorDoc, metadataDoc, decoratorDocWithTypeAssertion, otherDoc]);
|
||||
it('should change the docType of only the docs that are initialized by a call to makeDecorator', () => {
|
||||
processor.$process([decoratorDoc, metadataDoc, otherDoc]);
|
||||
expect(decoratorDoc.docType).toEqual('decorator');
|
||||
expect(decoratorDocWithTypeAssertion.docType).toEqual('decorator');
|
||||
expect(otherDoc.docType).toEqual('const');
|
||||
});
|
||||
|
||||
it('should extract the "type" of the decorator meta data', () => {
|
||||
processor.$process([decoratorDoc, metadataDoc, decoratorDocWithTypeAssertion, otherDoc]);
|
||||
processor.$process([decoratorDoc, metadataDoc, otherDoc]);
|
||||
expect(decoratorDoc.decoratorType).toEqual('X');
|
||||
expect(decoratorDocWithTypeAssertion.decoratorType).toEqual('Y');
|
||||
});
|
||||
|
||||
it('should copy across properties from the call signature doc', () => {
|
||||
processor.$process([decoratorDoc, metadataDoc, decoratorDocWithTypeAssertion, otherDoc]);
|
||||
processor.$process([decoratorDoc, metadataDoc, otherDoc]);
|
||||
expect(decoratorDoc.description).toEqual('The actual description of the call signature');
|
||||
expect(decoratorDoc.whatItDoes).toEqual('Does something cool...');
|
||||
expect(decoratorDoc.howToUse).toEqual('Use it like this...');
|
||||
});
|
||||
|
||||
it('should remove the metadataDoc from the module exports', () => {
|
||||
processor.$process([decoratorDoc, metadataDoc, decoratorDocWithTypeAssertion, otherDoc]);
|
||||
processor.$process([decoratorDoc, metadataDoc, otherDoc]);
|
||||
expect(moduleDoc.exports).not.toContain(metadataDoc);
|
||||
});
|
||||
|
||||
it('should cope with decorators that have type params', () => {
|
||||
decoratorDoc.symbol.valueDeclaration.initializer.expression.type = {};
|
||||
processor.$process([decoratorDoc, metadataDoc, otherDoc]);
|
||||
expect(decoratorDoc.docType).toEqual('decorator');
|
||||
});
|
||||
});
|
||||
|
9
aio/tools/transforms/angular-base-package/rendering/filterByPropertyValue.js
vendored
Normal file
9
aio/tools/transforms/angular-base-package/rendering/filterByPropertyValue.js
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
module.exports = function filterBy() {
|
||||
return {
|
||||
name: 'filterByPropertyValue',
|
||||
process: function(list, property, value) {
|
||||
if (!list) return list;
|
||||
return list.filter(item => item[property] === value);
|
||||
}
|
||||
};
|
||||
};
|
@ -0,0 +1,14 @@
|
||||
const factory = require('./filterByPropertyValue');
|
||||
|
||||
describe('filterByPropertyValue filter', () => {
|
||||
let filter;
|
||||
|
||||
beforeEach(function() { filter = factory(); });
|
||||
|
||||
it('should be called "filterByPropertyValue"', function() { expect(filter.name).toEqual('filterByPropertyValue'); });
|
||||
|
||||
it('should filter out items that do not match the given property value', function() {
|
||||
expect(filter.process([{ a: 1 }, { a: 2 }, { b: 1 }, { a: 1, b: 2 }, { a: null }, { a: undefined }], 'a', 1))
|
||||
.toEqual([{ a: 1 }, { a: 1, b: 2 }]);
|
||||
});
|
||||
});
|
@ -20,14 +20,25 @@ module.exports = new Package('angular-content', [basePackage, contentPackage])
|
||||
// Where do we get the source files?
|
||||
.config(function(readFilesProcessor, collectExamples) {
|
||||
|
||||
const gitignoreFile = fs.readFileSync(path.resolve(GUIDE_EXAMPLES_PATH, '.gitignore'), 'utf8');
|
||||
const gitignoreFilePath = path.resolve(GUIDE_EXAMPLES_PATH, '.gitignore');
|
||||
const gitignoreFile = fs.readFileSync(gitignoreFilePath, 'utf8');
|
||||
const gitignore = ignore().add(gitignoreFile);
|
||||
|
||||
const examplePaths = glob.sync('**/*', { cwd: GUIDE_EXAMPLES_PATH, dot: true, ignore: '**/node_modules/**', mark: true })
|
||||
.filter(filePath => filePath !== '.gitignore') // we are not interested in the .gitignore file itself
|
||||
.filter(filePath => !/\/$/.test(filePath)); // this filter removes the folders, leaving only files
|
||||
const filteredExamplePaths = gitignore.filter(examplePaths) // filter out files that match the .gitignore rules
|
||||
.map(filePath => path.resolve(GUIDE_EXAMPLES_PATH, filePath)); // we need the full paths for the filereader
|
||||
const ignoredExamplePaths = [];
|
||||
const resolvedExamplePaths = [];
|
||||
|
||||
examplePaths.forEach(filePath => {
|
||||
// filter out files that match the .gitignore rules
|
||||
if (gitignore.ignores(filePath)) {
|
||||
ignoredExamplePaths.push(filePath);
|
||||
} else {
|
||||
// we need the full paths for the filereader
|
||||
resolvedExamplePaths.push(path.resolve(GUIDE_EXAMPLES_PATH, filePath));
|
||||
}
|
||||
});
|
||||
|
||||
readFilesProcessor.sourceFiles = readFilesProcessor.sourceFiles.concat([
|
||||
{
|
||||
@ -48,7 +59,7 @@ module.exports = new Package('angular-content', [basePackage, contentPackage])
|
||||
},
|
||||
{
|
||||
basePath: CONTENTS_PATH,
|
||||
include: filteredExamplePaths,
|
||||
include: resolvedExamplePaths,
|
||||
fileReader: 'exampleFileReader'
|
||||
},
|
||||
{
|
||||
@ -69,6 +80,7 @@ module.exports = new Package('angular-content', [basePackage, contentPackage])
|
||||
]);
|
||||
|
||||
collectExamples.exampleFolders.push('examples');
|
||||
collectExamples.registerIgnoredExamples(ignoredExamplePaths, gitignoreFilePath);
|
||||
})
|
||||
|
||||
|
||||
|
@ -5,7 +5,6 @@ module.exports =
|
||||
new Package('examples', [jsdocPackage])
|
||||
|
||||
.factory(require('./inline-tag-defs/example'))
|
||||
// .factory(require('./inline-tag-defs/exampleTabs'))
|
||||
.factory(require('./services/parseArgString'))
|
||||
.factory(require('./services/example-map'))
|
||||
.factory(require('./file-readers/example-reader'))
|
||||
@ -21,7 +20,6 @@ module.exports =
|
||||
|
||||
.config(function(inlineTagProcessor, exampleInlineTagDef) {
|
||||
inlineTagProcessor.inlineTagDefinitions.push(exampleInlineTagDef);
|
||||
// inlineTagProcessor.inlineTagDefinitions.push(exampleTabsInlineTagDef);
|
||||
})
|
||||
|
||||
.config(function(computePathsProcessor) {
|
||||
|
@ -6,7 +6,26 @@ module.exports = function collectExamples(exampleMap, regionParser, log, createD
|
||||
$runAfter: ['files-read'],
|
||||
$runBefore: ['parsing-tags'],
|
||||
$validate: {exampleFolders: {presence: true}},
|
||||
$process: function(docs) {
|
||||
exampleFolders: [],
|
||||
ignoredExamples: {},
|
||||
/**
|
||||
* Call this method to indicate to the processor that some files, that actually exist,
|
||||
* have been filtered out from being processed.
|
||||
* @param paths an array of relative paths to the examples that have been ignored.
|
||||
* @param gitIgnorePath the path to the gitignore file that caused this example to be ignored.
|
||||
*/
|
||||
registerIgnoredExamples(paths, gitIgnorePath) {
|
||||
paths.forEach(path => { this.ignoredExamples[path] = gitIgnorePath; });
|
||||
},
|
||||
/**
|
||||
* Call this method to find out if an example was ignored.
|
||||
* @param path a relative path to the example file to test for being ignored.
|
||||
* @returns the path to the .gitignore file.
|
||||
*/
|
||||
isExampleIgnored(path) {
|
||||
return this.ignoredExamples[path];
|
||||
},
|
||||
$process(docs) {
|
||||
const exampleFolders = this.exampleFolders;
|
||||
const regionDocs = [];
|
||||
docs = docs.filter((doc) => {
|
||||
|
@ -23,161 +23,175 @@ describe('collectExampleRegions processor', () => {
|
||||
processor.exampleFolders = ['examples-1', 'examples-2'];
|
||||
});
|
||||
|
||||
it('should identify example files that are in the exampleFolders', () => {
|
||||
const docs = [
|
||||
createDoc('A', 'examples-1/x/app.js'), createDoc('B', 'examples-1/y/index.html'),
|
||||
createDoc('C', 'examples-2/s/app.js'), createDoc('D', 'examples-2/t/style.css'),
|
||||
createDoc('E', 'other/b/c.js')
|
||||
];
|
||||
describe('$process', () => {
|
||||
|
||||
processor.$process(docs);
|
||||
it('should identify example files that are in the exampleFolders', () => {
|
||||
const docs = [
|
||||
createDoc('A', 'examples-1/x/app.js'), createDoc('B', 'examples-1/y/index.html'),
|
||||
createDoc('C', 'examples-2/s/app.js'), createDoc('D', 'examples-2/t/style.css'),
|
||||
createDoc('E', 'other/b/c.js')
|
||||
];
|
||||
|
||||
expect(exampleMap['examples-1']['x/app.js']).toBeDefined();
|
||||
expect(exampleMap['examples-1']['y/index.html']).toBeDefined();
|
||||
expect(exampleMap['examples-2']['s/app.js']).toBeDefined();
|
||||
expect(exampleMap['examples-2']['t/style.css']).toBeDefined();
|
||||
processor.$process(docs);
|
||||
|
||||
expect(exampleMap['other']).toBeUndefined();
|
||||
});
|
||||
expect(exampleMap['examples-1']['x/app.js']).toBeDefined();
|
||||
expect(exampleMap['examples-1']['y/index.html']).toBeDefined();
|
||||
expect(exampleMap['examples-2']['s/app.js']).toBeDefined();
|
||||
expect(exampleMap['examples-2']['t/style.css']).toBeDefined();
|
||||
|
||||
it('should remove example files from the docs collection', () => {
|
||||
const docs = [
|
||||
createDoc('Example A', 'examples-1/x/app.js'),
|
||||
createDoc('Example B', 'examples-1/y/index.html'),
|
||||
createDoc('Other doc 1', 'examples-2/t/style.css', 'content'),
|
||||
createDoc('Example C', 'examples-2/s/app.js'),
|
||||
createDoc('Other doc 2', 'other/b/c.js', 'content')
|
||||
];
|
||||
expect(exampleMap['other']).toBeUndefined();
|
||||
});
|
||||
|
||||
const processedDocs = processor.$process(docs);
|
||||
it('should remove example files from the docs collection', () => {
|
||||
const docs = [
|
||||
createDoc('Example A', 'examples-1/x/app.js'),
|
||||
createDoc('Example B', 'examples-1/y/index.html'),
|
||||
createDoc('Other doc 1', 'examples-2/t/style.css', 'content'),
|
||||
createDoc('Example C', 'examples-2/s/app.js'),
|
||||
createDoc('Other doc 2', 'other/b/c.js', 'content')
|
||||
];
|
||||
|
||||
expect(processedDocs.filter(doc => doc.docType === 'example-file')).toEqual([]);
|
||||
});
|
||||
const processedDocs = processor.$process(docs);
|
||||
|
||||
it('should not remove docs from the docs collection that are not example files', () => {
|
||||
const docs = [
|
||||
createDoc('Example A', 'examples-1/x/app.js'),
|
||||
createDoc('Example B', 'examples-1/y/index.html'),
|
||||
createDoc('Other doc 1', 'examples-2/t/style.css', 'content'),
|
||||
createDoc('Example C', 'examples-2/s/app.js'),
|
||||
createDoc('Other doc 2', 'other/b/c.js', 'content')
|
||||
];
|
||||
expect(processedDocs.filter(doc => doc.docType === 'example-file')).toEqual([]);
|
||||
});
|
||||
|
||||
const processedDocs = processor.$process(docs);
|
||||
it('should not remove docs from the docs collection that are not example files', () => {
|
||||
const docs = [
|
||||
createDoc('Example A', 'examples-1/x/app.js'),
|
||||
createDoc('Example B', 'examples-1/y/index.html'),
|
||||
createDoc('Other doc 1', 'examples-2/t/style.css', 'content'),
|
||||
createDoc('Example C', 'examples-2/s/app.js'),
|
||||
createDoc('Other doc 2', 'other/b/c.js', 'content')
|
||||
];
|
||||
|
||||
expect(processedDocs.filter(doc => doc.docType !== 'example-file'))
|
||||
.toEqual(jasmine.objectContaining([
|
||||
const processedDocs = processor.$process(docs);
|
||||
|
||||
expect(processedDocs.filter(doc => doc.docType !== 'example-file'))
|
||||
.toEqual(jasmine.objectContaining([
|
||||
createDoc('Other doc 1', 'examples-2/t/style.css', 'content'),
|
||||
createDoc('Other doc 2', 'other/b/c.js', 'content')
|
||||
]));
|
||||
});
|
||||
|
||||
it('should call `regionParser` from with the content and file extension of each example doc',
|
||||
() => {
|
||||
const docs = [
|
||||
createDoc('Example A', 'examples-1/x/app.js'),
|
||||
createDoc('Example B', 'examples-1/y/index.html'),
|
||||
createDoc('Other doc 1', 'examples-2/t/style.css', 'content'),
|
||||
createDoc('Example C', 'examples-2/s/app.js'),
|
||||
createDoc('Other doc 2', 'other/b/c.js', 'content')
|
||||
]));
|
||||
});
|
||||
];
|
||||
|
||||
it('should call `regionParser` from with the content and file extension of each example doc',
|
||||
() => {
|
||||
const docs = [
|
||||
createDoc('Example A', 'examples-1/x/app.js'),
|
||||
createDoc('Example B', 'examples-1/y/index.html'),
|
||||
createDoc('Other doc 1', 'examples-2/t/style.css', 'content'),
|
||||
createDoc('Example C', 'examples-2/s/app.js'),
|
||||
createDoc('Other doc 2', 'other/b/c.js', 'content')
|
||||
];
|
||||
processor.$process(docs);
|
||||
|
||||
processor.$process(docs);
|
||||
|
||||
expect(regionParser).toHaveBeenCalledTimes(3);
|
||||
expect(regionParser).toHaveBeenCalledWith('Example A', 'js');
|
||||
expect(regionParser).toHaveBeenCalledWith('Example B', 'html');
|
||||
expect(regionParser).toHaveBeenCalledWith('Example C', 'js');
|
||||
});
|
||||
expect(regionParser).toHaveBeenCalledTimes(3);
|
||||
expect(regionParser).toHaveBeenCalledWith('Example A', 'js');
|
||||
expect(regionParser).toHaveBeenCalledWith('Example B', 'html');
|
||||
expect(regionParser).toHaveBeenCalledWith('Example C', 'js');
|
||||
});
|
||||
|
||||
|
||||
it('should attach parsed content as renderedContent to the example file docs', () => {
|
||||
const docs = [
|
||||
createDoc('A', 'examples-1/x/app.js'),
|
||||
createDoc('B', 'examples-1/y/index.html'),
|
||||
createDoc('C', 'examples-2/s/app.js'),
|
||||
createDoc('D', 'examples-2/t/style.css'),
|
||||
];
|
||||
it('should attach parsed content as renderedContent to the example file docs', () => {
|
||||
const docs = [
|
||||
createDoc('A', 'examples-1/x/app.js'),
|
||||
createDoc('B', 'examples-1/y/index.html'),
|
||||
createDoc('C', 'examples-2/s/app.js'),
|
||||
createDoc('D', 'examples-2/t/style.css'),
|
||||
];
|
||||
|
||||
processor.$process(docs);
|
||||
processor.$process(docs);
|
||||
|
||||
expect(exampleMap['examples-1']['x/app.js'].renderedContent).toEqual('PARSED:A');
|
||||
expect(exampleMap['examples-1']['y/index.html'].renderedContent).toEqual('PARSED:B');
|
||||
expect(exampleMap['examples-2']['s/app.js'].renderedContent).toEqual('PARSED:C');
|
||||
expect(exampleMap['examples-2']['t/style.css'].renderedContent).toEqual('PARSED:D');
|
||||
expect(exampleMap['examples-1']['x/app.js'].renderedContent).toEqual('PARSED:A');
|
||||
expect(exampleMap['examples-1']['y/index.html'].renderedContent).toEqual('PARSED:B');
|
||||
expect(exampleMap['examples-2']['s/app.js'].renderedContent).toEqual('PARSED:C');
|
||||
expect(exampleMap['examples-2']['t/style.css'].renderedContent).toEqual('PARSED:D');
|
||||
|
||||
});
|
||||
|
||||
it('should create region docs for each region in the example file docs', () => {
|
||||
const docs = [
|
||||
createDoc('/* #docregion X */\nA', 'examples-1/x/app.js'),
|
||||
createDoc('<!-- #docregion Y -->\nB', 'examples-1/y/index.html'),
|
||||
createDoc('/* #docregion Z */\nC', 'examples-2/t/style.css'),
|
||||
];
|
||||
|
||||
const newDocs = processor.$process(docs);
|
||||
|
||||
expect(newDocs.length).toEqual(3);
|
||||
expect(newDocs).toEqual([
|
||||
jasmine.objectContaining({
|
||||
docType: 'example-region',
|
||||
name: 'dummy',
|
||||
id: 'examples-1/x/app.js#dummy',
|
||||
contents: 'js'
|
||||
}),
|
||||
jasmine.objectContaining({
|
||||
docType: 'example-region',
|
||||
name: 'dummy',
|
||||
id: 'examples-1/y/index.html#dummy',
|
||||
contents: 'html'
|
||||
}),
|
||||
jasmine.objectContaining({
|
||||
docType: 'example-region',
|
||||
name: 'dummy',
|
||||
id: 'examples-2/t/style.css#dummy',
|
||||
contents: 'css'
|
||||
})
|
||||
]);
|
||||
});
|
||||
|
||||
it('should attach region docs to the example file docs', () => {
|
||||
const docs = [
|
||||
createDoc('/* #docregion X */\nA', 'examples-1/x/app.js'),
|
||||
createDoc('<!-- #docregion Y -->\nB', 'examples-1/y/index.html'),
|
||||
createDoc('/* #docregion Z */\nC', 'examples-2/t/style.css'),
|
||||
];
|
||||
|
||||
processor.$process(docs);
|
||||
|
||||
expect(exampleMap['examples-1']['x/app.js'].regions).toEqual({
|
||||
dummy: {
|
||||
docType: 'example-region',
|
||||
path: 'examples-1/x/app.js',
|
||||
name: 'dummy',
|
||||
id: 'examples-1/x/app.js#dummy',
|
||||
aliases: ['examples-1/x/app.js#dummy'],
|
||||
contents: 'js'
|
||||
}
|
||||
});
|
||||
expect(exampleMap['examples-1']['y/index.html'].regions).toEqual({
|
||||
dummy: {
|
||||
docType: 'example-region',
|
||||
path: 'examples-1/y/index.html',
|
||||
name: 'dummy',
|
||||
id: 'examples-1/y/index.html#dummy',
|
||||
aliases: ['examples-1/y/index.html#dummy'],
|
||||
contents: 'html'
|
||||
}
|
||||
|
||||
it('should create region docs for each region in the example file docs', () => {
|
||||
const docs = [
|
||||
createDoc('/* #docregion X */\nA', 'examples-1/x/app.js'),
|
||||
createDoc('<!-- #docregion Y -->\nB', 'examples-1/y/index.html'),
|
||||
createDoc('/* #docregion Z */\nC', 'examples-2/t/style.css'),
|
||||
];
|
||||
|
||||
const newDocs = processor.$process(docs);
|
||||
|
||||
expect(newDocs.length).toEqual(3);
|
||||
expect(newDocs).toEqual([
|
||||
jasmine.objectContaining({
|
||||
docType: 'example-region',
|
||||
name: 'dummy',
|
||||
id: 'examples-1/x/app.js#dummy',
|
||||
contents: 'js'
|
||||
}),
|
||||
jasmine.objectContaining({
|
||||
docType: 'example-region',
|
||||
name: 'dummy',
|
||||
id: 'examples-1/y/index.html#dummy',
|
||||
contents: 'html'
|
||||
}),
|
||||
jasmine.objectContaining({
|
||||
docType: 'example-region',
|
||||
name: 'dummy',
|
||||
id: 'examples-2/t/style.css#dummy',
|
||||
contents: 'css'
|
||||
})
|
||||
]);
|
||||
});
|
||||
expect(exampleMap['examples-2']['t/style.css'].regions).toEqual({
|
||||
dummy: {
|
||||
docType: 'example-region',
|
||||
path: 'examples-2/t/style.css',
|
||||
name: 'dummy',
|
||||
id: 'examples-2/t/style.css#dummy',
|
||||
aliases: ['examples-2/t/style.css#dummy'],
|
||||
contents: 'css'
|
||||
}
|
||||
|
||||
it('should attach region docs to the example file docs', () => {
|
||||
const docs = [
|
||||
createDoc('/* #docregion X */\nA', 'examples-1/x/app.js'),
|
||||
createDoc('<!-- #docregion Y -->\nB', 'examples-1/y/index.html'),
|
||||
createDoc('/* #docregion Z */\nC', 'examples-2/t/style.css'),
|
||||
];
|
||||
|
||||
processor.$process(docs);
|
||||
|
||||
expect(exampleMap['examples-1']['x/app.js'].regions).toEqual({
|
||||
dummy: {
|
||||
docType: 'example-region',
|
||||
path: 'examples-1/x/app.js',
|
||||
name: 'dummy',
|
||||
id: 'examples-1/x/app.js#dummy',
|
||||
aliases: ['examples-1/x/app.js#dummy'],
|
||||
contents: 'js'
|
||||
}
|
||||
});
|
||||
expect(exampleMap['examples-1']['y/index.html'].regions).toEqual({
|
||||
dummy: {
|
||||
docType: 'example-region',
|
||||
path: 'examples-1/y/index.html',
|
||||
name: 'dummy',
|
||||
id: 'examples-1/y/index.html#dummy',
|
||||
aliases: ['examples-1/y/index.html#dummy'],
|
||||
contents: 'html'
|
||||
}
|
||||
});
|
||||
expect(exampleMap['examples-2']['t/style.css'].regions).toEqual({
|
||||
dummy: {
|
||||
docType: 'example-region',
|
||||
path: 'examples-2/t/style.css',
|
||||
name: 'dummy',
|
||||
id: 'examples-2/t/style.css#dummy',
|
||||
aliases: ['examples-2/t/style.css#dummy'],
|
||||
contents: 'css'
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('filtered examples', () => {
|
||||
it('should indicate if an example was filtered', () => {
|
||||
processor.registerIgnoredExamples(['c/d/e', 'e/f/g'], 'path/to/gitignore');
|
||||
processor.registerIgnoredExamples(['x/y/z'], 'path/to/other/gitignore');
|
||||
expect(processor.isExampleIgnored('a/b/c')).toBeFalsy();
|
||||
expect(processor.isExampleIgnored('c/d/e')).toEqual('path/to/gitignore');
|
||||
expect(processor.isExampleIgnored('e/f/g')).toEqual('path/to/gitignore');
|
||||
expect(processor.isExampleIgnored('x/y/z')).toEqual('path/to/other/gitignore');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -14,15 +14,22 @@ module.exports = function getExampleRegion(exampleMap, createDocMessage, collect
|
||||
|
||||
// If still no file then we error
|
||||
if (!exampleFile) {
|
||||
const message = createDocMessage('Missing example file... relativePath: "' + relativePath + '".', doc) + '\n' +
|
||||
'Example files can be found in: ' + EXAMPLES_FOLDERS.join(', ');
|
||||
throw new Error(message);
|
||||
const gitIgnoreFile = collectExamples.isExampleIgnored(relativePath);
|
||||
if( gitIgnoreFile) {
|
||||
const message = createDocMessage('Ignored example file... relativePath: "' + relativePath + '"', doc) + '\n' +
|
||||
'This example file exists but has been ignored by a rule, in "' + gitIgnoreFile + '".';
|
||||
throw new Error(message);
|
||||
} else {
|
||||
const message = createDocMessage('Missing example file... relativePath: "' + relativePath + '".', doc) + '\n' +
|
||||
'Example files can be found in the following relative paths: ' + EXAMPLES_FOLDERS.map(function(folder) { return '"' + folder + '"'; }).join(', ');
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
var sourceCodeDoc = exampleFile.regions[regionName || ''];
|
||||
if (!sourceCodeDoc) {
|
||||
const message = createDocMessage('Missing example region... relativePath: "' + relativePath + '", region: "' + regionName + '".', doc) + '\n' +
|
||||
'Regions available are:' + Object.keys[exampleFile.regions];
|
||||
'Regions available are: ' + Object.keys(exampleFile.regions).map(function(region) { return '"' + region + '"'; }).join(', ');
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@ describe('getExampleRegion', () => {
|
||||
collectExamples = injector.get('collectExamples');
|
||||
exampleMap = injector.get('exampleMap');
|
||||
collectExamples.exampleFolders = ['examples'];
|
||||
collectExamples.registerIgnoredExamples(['filtered/path'], 'some/gitignore');
|
||||
exampleMap['examples'] = {
|
||||
'test/url': { regions: {
|
||||
'': { renderedContent: 'whole file' },
|
||||
@ -27,12 +28,19 @@ describe('getExampleRegion', () => {
|
||||
expect(getExampleRegion({}, 'test/url', 'region-1')).toEqual('region 1 contents');
|
||||
});
|
||||
|
||||
it('should throw an error if an example doesn\'t exist', function() {
|
||||
expect(function() {
|
||||
it('should throw an error if an example doesn\'t exist', () => {
|
||||
expect(() => {
|
||||
getExampleRegion({}, 'missing/file', 'region-1');
|
||||
}).toThrowError();
|
||||
expect(function() {
|
||||
}).toThrowError('Missing example file... relativePath: "missing/file". - doc\nExample files can be found in the following relative paths: "examples"');
|
||||
expect(() => {
|
||||
getExampleRegion({}, 'test/url', 'missing-region');
|
||||
}).toThrowError();
|
||||
}).toThrowError('Missing example region... relativePath: "test/url", region: "missing-region". - doc\nRegions available are: "", "region-1"');
|
||||
});
|
||||
|
||||
it('should throw an error if an example has been filtered out', () => {
|
||||
expect(() => {
|
||||
getExampleRegion({}, 'filtered/path', 'any-region');
|
||||
}).toThrowError('Ignored example file... relativePath: "filtered/path" - doc\n' +
|
||||
'This example file exists but has been ignored by a rule, in "some/gitignore".');
|
||||
});
|
||||
});
|
||||
|
@ -10,7 +10,8 @@ child. The template extension hierarchy looks like this (with declared blocks in
|
||||
|
||||
- layout/base.template.html (base)
|
||||
- module.template.html
|
||||
- layout/api-base.template.html (jumpNav, jumpNavLinks, whatItDoes, infoBar, securityConsiderations, deprecationNotes, howToUse, details)
|
||||
- layout/api-base.template.html (jumpNav, jumpNavLinks, whatItDoes, infoBar, securityConsiderations,
|
||||
deprecationNotes, howToUse, details)
|
||||
- class.template.html
|
||||
- directive.template.html
|
||||
- enum.template.html
|
||||
@ -26,119 +27,6 @@ child. The template extension hierarchy looks like this (with declared blocks in
|
||||
# Doc Properties
|
||||
|
||||
It is useful to know what properties are available on each doc type when working with the templates.
|
||||
Here is an overview:
|
||||
|
||||
## class
|
||||
|
||||
- docType
|
||||
- name
|
||||
- id
|
||||
- moduleDoc
|
||||
- path
|
||||
- description
|
||||
- notYetDocumented
|
||||
- members
|
||||
|
||||
## directive
|
||||
|
||||
- docType
|
||||
- name
|
||||
- id
|
||||
- moduleDoc
|
||||
- path
|
||||
- description
|
||||
- notYetDocumented
|
||||
- members
|
||||
|
||||
## enum
|
||||
|
||||
- docType
|
||||
- name
|
||||
- id
|
||||
- moduleDoc
|
||||
- path
|
||||
- description
|
||||
- notYetDocumented
|
||||
|
||||
## var
|
||||
|
||||
- docType
|
||||
- name
|
||||
- id
|
||||
- moduleDoc
|
||||
- path
|
||||
- description
|
||||
- notYetDocumented
|
||||
|
||||
## const
|
||||
|
||||
- docType
|
||||
- name
|
||||
- id
|
||||
- moduleDoc
|
||||
- path
|
||||
- description
|
||||
- notYetDocumented
|
||||
|
||||
## let
|
||||
|
||||
- docType
|
||||
- name
|
||||
- id
|
||||
- moduleDoc
|
||||
- path
|
||||
- description
|
||||
- notYetDocumented
|
||||
|
||||
## decorator
|
||||
|
||||
- docType
|
||||
- name
|
||||
- id
|
||||
- moduleDoc
|
||||
- path
|
||||
- description
|
||||
- notYetDocumented
|
||||
- members
|
||||
|
||||
## function
|
||||
|
||||
- docType
|
||||
- name
|
||||
- id
|
||||
- moduleDoc
|
||||
- path
|
||||
- description
|
||||
- notYetDocumented
|
||||
|
||||
## interface
|
||||
|
||||
- docType
|
||||
- name
|
||||
- id
|
||||
- moduleDoc
|
||||
- path
|
||||
- description
|
||||
- notYetDocumented
|
||||
- members
|
||||
|
||||
## type-alias
|
||||
|
||||
- docType
|
||||
- name
|
||||
- id
|
||||
- moduleDoc
|
||||
- path
|
||||
- description
|
||||
- notYetDocumented
|
||||
|
||||
## pipe
|
||||
|
||||
- docType
|
||||
- name
|
||||
- id
|
||||
- moduleDoc
|
||||
- path
|
||||
- description
|
||||
- notYetDocumented
|
||||
|
||||
The `typescript` Dgeni package is now written in TypeScript and there is a class for each of the types of
|
||||
API document. See https://github.com/angular/dgeni-packages/tree/master/typescript/src/api-doc-types.
|
||||
This is a good place to go to see what properties you can use in the templates.
|
@ -1,4 +1,5 @@
|
||||
{% import "lib/memberHelpers.html" as memberHelpers -%}
|
||||
{% import "lib/descendants.html" as descendants -%}
|
||||
{% import "lib/paramList.html" as params -%}
|
||||
{% extends 'export-base.template.html' -%}
|
||||
|
||||
@ -6,6 +7,7 @@
|
||||
{% block details %}
|
||||
{% block additional %}{% endblock %}
|
||||
{% include "includes/description.html" %}
|
||||
{$ descendants.renderDescendants(doc, 'class', 'Subclasses') $}
|
||||
{$ memberHelpers.renderMemberDetails(doc.statics, 'static-members', 'static-member', 'Static Members') $}
|
||||
{% if doc.constructorDoc %}{$ memberHelpers.renderMemberDetails([doc.constructorDoc], 'constructors', 'constructor', 'Constructor') $}{% endif %}
|
||||
{$ memberHelpers.renderMemberDetails(doc.members, 'instance-members', 'instance-member', 'Members') $}
|
||||
|
@ -1,9 +1,11 @@
|
||||
{% import "lib/memberHelpers.html" as memberHelper -%}
|
||||
|
||||
{% if doc.members.length %}
|
||||
<section class="decorator-overview">
|
||||
<h2>Metadata Overview</h2>
|
||||
<h2>Metadata Overview</h2>
|
||||
<code-example language="ts" hideCopy="true">
|
||||
@{$ doc.name $}{$ doc.typeParams | escape $}({ {$ memberHelper.renderMembers(doc) $}
|
||||
})
|
||||
</code-example>
|
||||
</section>
|
||||
</section>
|
||||
{% endif %}
|
@ -1,9 +1,12 @@
|
||||
{% import "lib/paramList.html" as params -%}
|
||||
{% import "lib/memberHelpers.html" as memberHelper -%}
|
||||
{% import "lib/descendants.html" as descendants -%}
|
||||
{% extends 'export-base.template.html' -%}
|
||||
|
||||
{% block overview %}{% include "includes/interface-overview.html" %}{% endblock %}
|
||||
{% block details %}
|
||||
{% include "includes/description.html" %}
|
||||
{$ descendants.renderDescendants(doc, 'interface', 'Child Interfaces') $}
|
||||
{$ descendants.renderDescendants(doc, 'class', 'Class Implementations') $}
|
||||
{$ memberHelper.renderMemberDetails(doc.members, 'instance-members', 'instance-member', 'Members') $}
|
||||
{% endblock %}
|
||||
|
14
aio/tools/transforms/templates/api/lib/descendants.html
Normal file
14
aio/tools/transforms/templates/api/lib/descendants.html
Normal file
@ -0,0 +1,14 @@
|
||||
{% macro renderDescendants(doc, docType, title='', recursed=false) %}
|
||||
{% set descendants = doc.descendants | filterByPropertyValue('docType', docType) %}
|
||||
{% if descendants.length %}
|
||||
{% if title %}<h2>{$ title $}</h2>{% endif %}
|
||||
<ul {% if not recursed %}class="descendants {$ docType $}"{% endif %}>
|
||||
{% for descendant in descendants %}
|
||||
<li>
|
||||
<pre class="prettyprint lang-ts"><code>{$ descendant.name $}</code></pre>
|
||||
{$ renderDescendants(descendant, docType, '', true) $}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
@ -2002,9 +2002,9 @@ devtools-timeline-model@1.1.6:
|
||||
chrome-devtools-frontend "1.0.401423"
|
||||
resolve "1.1.7"
|
||||
|
||||
dgeni-packages@^0.21.2:
|
||||
version "0.21.2"
|
||||
resolved "https://registry.yarnpkg.com/dgeni-packages/-/dgeni-packages-0.21.2.tgz#b031194176507b7c7d1c9735ea14664970763866"
|
||||
dgeni-packages@^0.21.3:
|
||||
version "0.21.3"
|
||||
resolved "https://registry.yarnpkg.com/dgeni-packages/-/dgeni-packages-0.21.3.tgz#49d5264400cdd8c8a2f66040267e38c099d540f4"
|
||||
dependencies:
|
||||
canonical-path "0.0.2"
|
||||
catharsis "^0.8.1"
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-srcs",
|
||||
"version": "4.4.3",
|
||||
"version": "4.4.4",
|
||||
"private": true,
|
||||
"branchPattern": "2.0.*",
|
||||
"description": "Angular - a web framework for modern web apps",
|
||||
|
@ -556,7 +556,11 @@ export class AnimationTimelineContext {
|
||||
}
|
||||
if (selector.length > 0) { // if :self is only used then the selector is empty
|
||||
const multi = limit != 1;
|
||||
results.push(...this._driver.query(this.element, selector, multi));
|
||||
let elements = this._driver.query(this.element, selector, multi);
|
||||
if (limit !== 0) {
|
||||
elements = elements.slice(0, limit);
|
||||
}
|
||||
results.push(...elements);
|
||||
}
|
||||
|
||||
if (!optional && results.length == 0) {
|
||||
|
@ -220,6 +220,13 @@ export interface AnimationGroupMetadata extends AnimationMetadata {
|
||||
*/
|
||||
export declare interface AnimationQueryOptions extends AnimationOptions {
|
||||
optional?: boolean;
|
||||
/**
|
||||
* Used to limit the total amount of results from the start of the query list.
|
||||
*
|
||||
* If a negative value is provided then the queried results will be limited from the
|
||||
* end of the query list towards the beginning (e.g. if `limit: -3` is used then the
|
||||
* final 3 (or less) queried results will be used for the animation).
|
||||
*/
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
|
@ -425,7 +425,9 @@ export class StaticReflector implements CompileReflector {
|
||||
for (const item of (<any>expression)) {
|
||||
// Check for a spread expression
|
||||
if (item && item.__symbolic === 'spread') {
|
||||
const spreadArray = simplify(item.expression);
|
||||
// We call with references as 0 because we require the actual value and cannot
|
||||
// tolerate a reference here.
|
||||
const spreadArray = simplifyInContext(context, item.expression, depth, 0);
|
||||
if (Array.isArray(spreadArray)) {
|
||||
for (const spreadItem of spreadArray) {
|
||||
result.push(spreadItem);
|
||||
@ -444,7 +446,7 @@ export class StaticReflector implements CompileReflector {
|
||||
if (expression instanceof StaticSymbol) {
|
||||
// Stop simplification at builtin symbols or if we are in a reference context
|
||||
if (expression === self.injectionToken || expression === self.opaqueToken ||
|
||||
self.conversionMap.has(expression) || references > 0) {
|
||||
self.conversionMap.has(expression) || (references > 0 && !expression.members.length)) {
|
||||
return expression;
|
||||
} else {
|
||||
const staticSymbol = expression;
|
||||
|
@ -14,6 +14,12 @@ export const PRESERVE_WS_ATTR_NAME = 'ngPreserveWhitespaces';
|
||||
|
||||
const SKIP_WS_TRIM_TAGS = new Set(['pre', 'template', 'textarea', 'script', 'style']);
|
||||
|
||||
// Equivalent to \s with \u00a0 (non-breaking space) excluded.
|
||||
// Based on https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp
|
||||
const WS_CHARS = ' \f\n\r\t\v\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff';
|
||||
const NO_WS_REGEXP = new RegExp(`[^${WS_CHARS}]`);
|
||||
const WS_REPLACE_REGEXP = new RegExp(`[${WS_CHARS}]{2,}`, 'g');
|
||||
|
||||
function hasPreserveWhitespacesAttr(attrs: html.Attribute[]): boolean {
|
||||
return attrs.some((attr: html.Attribute) => attr.name === PRESERVE_WS_ATTR_NAME);
|
||||
}
|
||||
@ -63,10 +69,11 @@ class WhitespaceVisitor implements html.Visitor {
|
||||
}
|
||||
|
||||
visitText(text: html.Text, context: any): any {
|
||||
const isBlank = text.value.trim().length === 0;
|
||||
const isNotBlank = text.value.match(NO_WS_REGEXP);
|
||||
|
||||
if (!isBlank) {
|
||||
return new html.Text(replaceNgsp(text.value).replace(/\s\s+/g, ' '), text.sourceSpan);
|
||||
if (isNotBlank) {
|
||||
return new html.Text(
|
||||
replaceNgsp(text.value).replace(WS_REPLACE_REGEXP, ' '), text.sourceSpan);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -161,7 +161,7 @@ export class EmitterVisitorContext {
|
||||
spanOf(line: number, column: number): ParseSourceSpan|null {
|
||||
const emittedLine = this._lines[line - this._preambleLineCount];
|
||||
if (emittedLine) {
|
||||
let columnsLeft = column - emittedLine.indent;
|
||||
let columnsLeft = column - _createIndent(emittedLine.indent).length;
|
||||
for (let partIndex = 0; partIndex < emittedLine.parts.length; partIndex++) {
|
||||
const part = emittedLine.parts[partIndex];
|
||||
if (part.length > columnsLeft) {
|
||||
|
@ -872,6 +872,42 @@ describe('StaticReflector', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// Regression #18170
|
||||
it('should continue to aggresively evaluate enum member accessors', () => {
|
||||
const data = Object.create(DEFAULT_TEST_DATA);
|
||||
const file = '/tmp/src/my_component.ts';
|
||||
data[file] = `
|
||||
import {Component} from '@angular/core';
|
||||
import {intermediate} from './index';
|
||||
|
||||
@Component({
|
||||
template: '<div></div>',
|
||||
providers: [{provide: 'foo', useValue: [...intermediate]}]
|
||||
})
|
||||
export class MyComponent { }
|
||||
`;
|
||||
data['/tmp/src/intermediate.ts'] = `
|
||||
import {MyEnum} from './indirect';
|
||||
export const intermediate = [{
|
||||
data: {
|
||||
c: [MyEnum.Value]
|
||||
}
|
||||
}];`;
|
||||
data['/tmp/src/index.ts'] = `export * from './intermediate';`;
|
||||
data['/tmp/src/indirect.ts'] = `export * from './consts';`;
|
||||
data['/tmp/src/consts.ts'] = `
|
||||
export enum MyEnum {
|
||||
Value = 3
|
||||
}
|
||||
`;
|
||||
init(data);
|
||||
|
||||
expect(reflector.annotations(reflector.getStaticSymbol(file, 'MyComponent'))[0]
|
||||
.providers[0]
|
||||
.useValue)
|
||||
.toEqual([{data: {c: [3]}}]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
const DEFAULT_TEST_DATA: {[key: string]: any} = {
|
||||
|
@ -64,6 +64,16 @@ export function main() {
|
||||
expect(parseAndRemoveWS(' \n foo \t ')).toEqual([[html.Text, ' foo ', 0]]);
|
||||
});
|
||||
|
||||
it('should not replace ', () => {
|
||||
expect(parseAndRemoveWS(' ')).toEqual([[html.Text, '\u00a0', 0]]);
|
||||
});
|
||||
|
||||
it('should not replace sequences of ', () => {
|
||||
expect(parseAndRemoveWS(' foo ')).toEqual([
|
||||
[html.Text, '\u00a0\u00a0foo\u00a0\u00a0', 0]
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not replace single tab and newline with spaces', () => {
|
||||
expect(parseAndRemoveWS('\nfoo')).toEqual([[html.Text, '\nfoo', 0]]);
|
||||
expect(parseAndRemoveWS('\tfoo')).toEqual([[html.Text, '\tfoo', 0]]);
|
||||
|
@ -9,7 +9,7 @@
|
||||
import {StaticSymbol} from '@angular/compiler/src/aot/static_symbol';
|
||||
import * as o from '@angular/compiler/src/output/output_ast';
|
||||
import {TypeScriptEmitter} from '@angular/compiler/src/output/ts_emitter';
|
||||
|
||||
import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '@angular/compiler/src/parse_util';
|
||||
import {stripSourceMapAndNewLine} from './abstract_emitter_spec';
|
||||
|
||||
const someGenFilePath = 'somePackage/someGenFile';
|
||||
@ -420,5 +420,46 @@ export function main() {
|
||||
'/* SomePreamble */', 'a;'
|
||||
].join('\n'));
|
||||
});
|
||||
|
||||
describe('emitter context', () => {
|
||||
it('should be able to back to the generating span', () => {
|
||||
const file = new ParseSourceFile('some content', 'a.ts');
|
||||
const returnSpan = new ParseSourceSpan(
|
||||
new ParseLocation(file, 100, 10, 10), new ParseLocation(file, 200, 20, 10));
|
||||
const referenceSpan = new ParseSourceSpan(
|
||||
new ParseLocation(file, 150, 15, 10), new ParseLocation(file, 175, 17, 10));
|
||||
const statements = [new o.ClassStmt(
|
||||
'SomeClass', null, [], [], new o.ClassMethod(null, [], []),
|
||||
[new o.ClassMethod('someMethod', [new o.FnParam('a', o.INT_TYPE)], [
|
||||
o.variable('someVar', o.INT_TYPE).set(o.literal(0)).toDeclStmt(),
|
||||
new o.ReturnStatement(o.variable('someVar', null, referenceSpan), returnSpan)
|
||||
])])];
|
||||
const {sourceText, context} =
|
||||
emitter.emitStatementsAndContext('a.ts', 'a.ts', statements, '/* some preamble /*\n\n');
|
||||
const spanOf = (text: string, after: number = 0) => {
|
||||
const location = sourceText.indexOf(text, after);
|
||||
const {line, col} = calculateLineCol(sourceText, location);
|
||||
return context.spanOf(line, col);
|
||||
};
|
||||
const returnLoc = sourceText.indexOf('return');
|
||||
expect(spanOf('return someVar')).toEqual(returnSpan, 'return span calculated incorrectly');
|
||||
expect(spanOf(';', returnLoc)).toEqual(returnSpan, 'reference span calculated incorrectly');
|
||||
expect(spanOf('someVar', returnLoc))
|
||||
.toEqual(referenceSpan, 'return span calculated incorrectly');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function calculateLineCol(text: string, offset: number): {line: number, col: number} {
|
||||
const lines = text.split('\n');
|
||||
let line = 0;
|
||||
for (let cur = 0; cur < text.length; line++) {
|
||||
const next = cur + lines[line].length + 1;
|
||||
if (next > offset) {
|
||||
return {line, col: offset - cur};
|
||||
}
|
||||
cur = next;
|
||||
}
|
||||
return {line, col: 0};
|
||||
}
|
@ -55,7 +55,7 @@ export function anchorDef(
|
||||
|
||||
export function elementDef(
|
||||
flags: NodeFlags, matchedQueriesDsl: [string | number, QueryValueType][],
|
||||
ngContentIndex: number, childCount: number, namespaceAndName: string,
|
||||
ngContentIndex: number, childCount: number, namespaceAndName: string | null,
|
||||
fixedAttrs: [string, string][] = [],
|
||||
bindings?: [BindingFlags, string, string | SecurityContext][], outputs?: ([string, string])[],
|
||||
handleEvent?: ElementHandleEventFn, componentView?: ViewDefinitionFactory,
|
||||
|
@ -233,6 +233,7 @@ export const enum QueryValueType {
|
||||
}
|
||||
|
||||
export interface ElementDef {
|
||||
// set to null for `<ng-container>`
|
||||
name: string|null;
|
||||
ns: string|null;
|
||||
/** ns, name, value */
|
||||
|
@ -30,34 +30,21 @@ export function viewDef(
|
||||
let viewRootNodeFlags = 0;
|
||||
let viewMatchedQueries = 0;
|
||||
let currentParent: NodeDef|null = null;
|
||||
let currentRenderParent: NodeDef|null = null;
|
||||
let currentElementHasPublicProviders = false;
|
||||
let currentElementHasPrivateProviders = false;
|
||||
let lastRenderRootNode: NodeDef|null = null;
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
while (currentParent && i > currentParent.index + currentParent.childCount) {
|
||||
const newParent: NodeDef|null = currentParent.parent;
|
||||
if (newParent) {
|
||||
newParent.childFlags |= currentParent.childFlags !;
|
||||
newParent.childMatchedQueries |= currentParent.childMatchedQueries;
|
||||
}
|
||||
currentParent = newParent;
|
||||
}
|
||||
const node = nodes[i];
|
||||
node.index = i;
|
||||
node.parent = currentParent;
|
||||
node.bindingIndex = viewBindingCount;
|
||||
node.outputIndex = viewDisposableCount;
|
||||
|
||||
// renderParent needs to account for ng-container!
|
||||
let currentRenderParent: NodeDef|null;
|
||||
if (currentParent && currentParent.flags & NodeFlags.TypeElement &&
|
||||
!currentParent.element !.name) {
|
||||
currentRenderParent = currentParent.renderParent;
|
||||
} else {
|
||||
currentRenderParent = currentParent;
|
||||
}
|
||||
node.renderParent = currentRenderParent;
|
||||
|
||||
viewNodeFlags |= node.flags;
|
||||
viewMatchedQueries |= node.matchedQueryIds;
|
||||
|
||||
if (node.element) {
|
||||
const elDef = node.element;
|
||||
elDef.publicProviders =
|
||||
@ -66,24 +53,13 @@ export function viewDef(
|
||||
// Note: We assume that all providers of an element are before any child element!
|
||||
currentElementHasPublicProviders = false;
|
||||
currentElementHasPrivateProviders = false;
|
||||
|
||||
if (node.element.template) {
|
||||
viewMatchedQueries |= node.element.template.nodeMatchedQueries;
|
||||
}
|
||||
}
|
||||
validateNode(currentParent, node, nodes.length);
|
||||
|
||||
viewNodeFlags |= node.flags;
|
||||
viewMatchedQueries |= node.matchedQueryIds;
|
||||
if (node.element && node.element.template) {
|
||||
viewMatchedQueries |= node.element.template.nodeMatchedQueries;
|
||||
}
|
||||
if (currentParent) {
|
||||
currentParent.childFlags |= node.flags;
|
||||
currentParent.directChildFlags |= node.flags;
|
||||
currentParent.childMatchedQueries |= node.matchedQueryIds;
|
||||
if (node.element && node.element.template) {
|
||||
currentParent.childMatchedQueries |= node.element.template.nodeMatchedQueries;
|
||||
}
|
||||
} else {
|
||||
viewRootNodeFlags |= node.flags;
|
||||
}
|
||||
|
||||
viewBindingCount += node.bindings.length;
|
||||
viewDisposableCount += node.outputs.length;
|
||||
@ -91,6 +67,7 @@ export function viewDef(
|
||||
if (!currentRenderParent && (node.flags & NodeFlags.CatRenderNode)) {
|
||||
lastRenderRootNode = node;
|
||||
}
|
||||
|
||||
if (node.flags & NodeFlags.CatProvider) {
|
||||
if (!currentElementHasPublicProviders) {
|
||||
currentElementHasPublicProviders = true;
|
||||
@ -106,7 +83,7 @@ export function viewDef(
|
||||
} else {
|
||||
if (!currentElementHasPrivateProviders) {
|
||||
currentElementHasPrivateProviders = true;
|
||||
// Use protoyypical inheritance to not get O(n^2) complexity...
|
||||
// Use prototypical inheritance to not get O(n^2) complexity...
|
||||
currentParent !.element !.allProviders =
|
||||
Object.create(currentParent !.element !.publicProviders);
|
||||
}
|
||||
@ -116,20 +93,50 @@ export function viewDef(
|
||||
currentParent !.element !.componentProvider = node;
|
||||
}
|
||||
}
|
||||
if (node.childCount) {
|
||||
|
||||
if (currentParent) {
|
||||
currentParent.childFlags |= node.flags;
|
||||
currentParent.directChildFlags |= node.flags;
|
||||
currentParent.childMatchedQueries |= node.matchedQueryIds;
|
||||
if (node.element && node.element.template) {
|
||||
currentParent.childMatchedQueries |= node.element.template.nodeMatchedQueries;
|
||||
}
|
||||
} else {
|
||||
viewRootNodeFlags |= node.flags;
|
||||
}
|
||||
|
||||
if (node.childCount > 0) {
|
||||
currentParent = node;
|
||||
|
||||
if (!isNgContainer(node)) {
|
||||
currentRenderParent = node;
|
||||
}
|
||||
} else {
|
||||
// When the current node has no children, check if it is the last children of its parent.
|
||||
// When it is, propagate the flags up.
|
||||
// The loop is required because an element could be the last transitive children of several
|
||||
// elements. We loop to either the root or the highest opened element (= with remaining
|
||||
// children)
|
||||
while (currentParent && i === currentParent.index + currentParent.childCount) {
|
||||
const newParent: NodeDef|null = currentParent.parent;
|
||||
if (newParent) {
|
||||
newParent.childFlags |= currentParent.childFlags;
|
||||
newParent.childMatchedQueries |= currentParent.childMatchedQueries;
|
||||
}
|
||||
currentParent = newParent;
|
||||
// We also need to update the render parent & account for ng-container
|
||||
if (currentParent && isNgContainer(currentParent)) {
|
||||
currentRenderParent = currentParent.renderParent;
|
||||
} else {
|
||||
currentRenderParent = currentParent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
while (currentParent) {
|
||||
const newParent = currentParent.parent;
|
||||
if (newParent) {
|
||||
newParent.childFlags |= currentParent.childFlags;
|
||||
newParent.childMatchedQueries |= currentParent.childMatchedQueries;
|
||||
}
|
||||
currentParent = newParent;
|
||||
}
|
||||
|
||||
const handleEvent: ViewHandleEventFn = (view, nodeIndex, eventName, event) =>
|
||||
nodes[nodeIndex].element !.handleEvent !(view, eventName, event);
|
||||
|
||||
return {
|
||||
// Will be filled later...
|
||||
factory: null,
|
||||
@ -138,13 +145,16 @@ export function viewDef(
|
||||
nodeMatchedQueries: viewMatchedQueries, flags,
|
||||
nodes: nodes,
|
||||
updateDirectives: updateDirectives || NOOP,
|
||||
updateRenderer: updateRenderer || NOOP,
|
||||
handleEvent: handleEvent || NOOP,
|
||||
updateRenderer: updateRenderer || NOOP, handleEvent,
|
||||
bindingCount: viewBindingCount,
|
||||
outputCount: viewDisposableCount, lastRenderRootNode
|
||||
};
|
||||
}
|
||||
|
||||
function isNgContainer(node: NodeDef): boolean {
|
||||
return (node.flags & NodeFlags.TypeElement) !== 0 && node.element !.name === null;
|
||||
}
|
||||
|
||||
function validateNode(parent: NodeDef | null, node: NodeDef, nodeCount: number) {
|
||||
const template = node.element && node.element.template;
|
||||
if (template) {
|
||||
|
@ -461,8 +461,8 @@ export function main() {
|
||||
selector: 'ani-cmp',
|
||||
template: `
|
||||
<div [@myAnimation]="exp">
|
||||
<header></header>
|
||||
<footer></footer>
|
||||
<header></header>
|
||||
<footer></footer>
|
||||
</div>
|
||||
`,
|
||||
animations: [
|
||||
@ -587,8 +587,8 @@ export function main() {
|
||||
selector: 'ani-cmp',
|
||||
template: `
|
||||
<div [@myAnimation]="exp">
|
||||
<header></header>
|
||||
<footer></footer>
|
||||
<header></header>
|
||||
<footer></footer>
|
||||
</div>
|
||||
`,
|
||||
animations: [
|
||||
@ -640,9 +640,9 @@ export function main() {
|
||||
template: `
|
||||
<div [@myAnimation]="exp">
|
||||
<div *ngFor="let item of items" class="item">
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
animations: [
|
||||
trigger(
|
||||
@ -706,9 +706,9 @@ export function main() {
|
||||
template: `
|
||||
<div [@myAnimation]="exp">
|
||||
<div *ngFor="let item of items" class="item">
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
animations: [trigger(
|
||||
'myAnimation',
|
||||
@ -809,8 +809,8 @@ export function main() {
|
||||
template: `
|
||||
<div @myAnimation>
|
||||
<div *ngFor="let item of items" class="child">
|
||||
{{ item }}
|
||||
</div>
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
animations: [trigger(
|
||||
@ -923,8 +923,8 @@ export function main() {
|
||||
template: `
|
||||
<div [@myAnimation]="exp" class="parent">
|
||||
<div *ngFor="let item of items" class="child">
|
||||
{{ item }}
|
||||
</div>
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
animations: [trigger(
|
||||
@ -1111,8 +1111,8 @@ export function main() {
|
||||
template: `
|
||||
<div [@myAnimation]="exp" class="parent">
|
||||
<div *ngFor="let item of items" class="child">
|
||||
{{ item }}
|
||||
</div>
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
animations: [trigger(
|
||||
@ -1333,8 +1333,8 @@ export function main() {
|
||||
template: `
|
||||
<div [@myAnimation]="exp" class="parent">
|
||||
<div *ngFor="let item of items" class="child">
|
||||
{{ item }}
|
||||
</div>
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
animations: [trigger(
|
||||
@ -1385,8 +1385,8 @@ export function main() {
|
||||
template: `
|
||||
<div [@myAnimation]="exp" class="parent">
|
||||
<div *ngFor="let item of items" class="child">
|
||||
{{ item }}
|
||||
</div>
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
animations: [trigger(
|
||||
@ -1436,8 +1436,8 @@ export function main() {
|
||||
template: `
|
||||
<div [@one]="exp1" [@two]="exp2" class="parent">
|
||||
<div *ngFor="let item of items" class="child">
|
||||
{{ item }}
|
||||
</div>
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
animations: [
|
||||
@ -1519,8 +1519,8 @@ export function main() {
|
||||
template: `
|
||||
<div [@myAnimation]="exp" class="parent">
|
||||
<div *ngFor="let item of items" class="child">
|
||||
{{ item }}
|
||||
</div>
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
animations: [trigger(
|
||||
@ -1569,8 +1569,8 @@ export function main() {
|
||||
template: `
|
||||
<div [@myAnimation]="exp" class="parent">
|
||||
<div *ngFor="let item of items" class="child">
|
||||
{{ item }}
|
||||
</div>
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
animations: [
|
||||
@ -1713,6 +1713,56 @@ export function main() {
|
||||
expect(players[1].element.innerText.trim()).toEqual('5');
|
||||
expect(players[2].element.innerText.trim()).toEqual('6');
|
||||
});
|
||||
|
||||
describe('options.limit', () => {
|
||||
it('should limit results when a limit value is passed into the query options', () => {
|
||||
@Component({
|
||||
selector: 'cmp',
|
||||
template: `
|
||||
<div [@myAnimation]="exp">
|
||||
<div *ngFor="let item of items" class="item">
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
animations: [
|
||||
trigger(
|
||||
'myAnimation',
|
||||
[
|
||||
transition(
|
||||
'* => go',
|
||||
[
|
||||
query(
|
||||
'.item',
|
||||
[
|
||||
style({opacity: 0}),
|
||||
animate('1s', style({opacity: 1})),
|
||||
],
|
||||
{limit: 2}),
|
||||
]),
|
||||
]),
|
||||
]
|
||||
})
|
||||
class Cmp {
|
||||
public exp: any;
|
||||
public items: any[] = [];
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
cmp.items = ['a', 'b', 'c', 'd', 'e'];
|
||||
fixture.detectChanges();
|
||||
|
||||
cmp.exp = 'go';
|
||||
fixture.detectChanges();
|
||||
|
||||
const players = getLog() as any[];
|
||||
expect(players.length).toEqual(2);
|
||||
expect(players[0].element.innerText.trim()).toEqual('a');
|
||||
expect(players[1].element.innerText.trim()).toEqual('b');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('sub triggers', () => {
|
||||
@ -2314,7 +2364,7 @@ export function main() {
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
class Cmp {
|
||||
|
@ -265,12 +265,12 @@ export class MetadataCollector {
|
||||
|
||||
const isExportedIdentifier = (identifier: ts.Identifier) => exportMap.has(identifier.text);
|
||||
const isExported =
|
||||
(node: ts.FunctionDeclaration | ts.ClassDeclaration | ts.InterfaceDeclaration |
|
||||
(node: ts.FunctionDeclaration | ts.ClassDeclaration | ts.InterfaceDeclaration | ts.TypeAliasDeclaration |
|
||||
ts.EnumDeclaration) => isExport(node) || isExportedIdentifier(node.name);
|
||||
const exportedIdentifierName = (identifier: ts.Identifier) =>
|
||||
exportMap.get(identifier.text) || identifier.text;
|
||||
const exportedName =
|
||||
(node: ts.FunctionDeclaration | ts.ClassDeclaration | ts.InterfaceDeclaration |
|
||||
(node: ts.FunctionDeclaration | ts.ClassDeclaration | ts.InterfaceDeclaration | ts.TypeAliasDeclaration |
|
||||
ts.EnumDeclaration) => exportedIdentifierName(node.name);
|
||||
|
||||
|
||||
@ -364,7 +364,16 @@ export class MetadataCollector {
|
||||
}
|
||||
// Otherwise don't record metadata for the class.
|
||||
break;
|
||||
|
||||
case ts.SyntaxKind.TypeAliasDeclaration:
|
||||
const typeDeclaration = <ts.TypeAliasDeclaration>node;
|
||||
if (typeDeclaration.name && isExported(typeDeclaration)) {
|
||||
const name = exportedName(typeDeclaration);
|
||||
if (name) {
|
||||
if (!metadata) metadata = {};
|
||||
metadata[name] = {__symbolic: 'interface'};
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ts.SyntaxKind.InterfaceDeclaration:
|
||||
const interfaceDeclaration = <ts.InterfaceDeclaration>node;
|
||||
if (interfaceDeclaration.name && isExported(interfaceDeclaration)) {
|
||||
@ -372,7 +381,6 @@ export class MetadataCollector {
|
||||
metadata[exportedName(interfaceDeclaration)] = {__symbolic: 'interface'};
|
||||
}
|
||||
break;
|
||||
|
||||
case ts.SyntaxKind.FunctionDeclaration:
|
||||
// Record functions that return a single value. Record the parameter
|
||||
// names substitution will be performed by the StaticReflector.
|
||||
|
@ -35,6 +35,7 @@ describe('Collector', () => {
|
||||
'exported-functions.ts',
|
||||
'exported-enum.ts',
|
||||
'exported-consts.ts',
|
||||
'exported-type.ts',
|
||||
'local-symbol-ref.ts',
|
||||
'local-function-ref.ts',
|
||||
'local-symbol-ref-func.ts',
|
||||
@ -66,6 +67,13 @@ describe('Collector', () => {
|
||||
expect(metadata).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return an interface reference for types', () => {
|
||||
const sourceFile = program.getSourceFile('/exported-type.ts');
|
||||
const metadata = collector.getMetadata(sourceFile);
|
||||
expect(metadata).toEqual(
|
||||
{__symbolic: 'module', version: 3, metadata: {SomeType: {__symbolic: 'interface'}}});
|
||||
});
|
||||
|
||||
it('should return an interface reference for interfaces', () => {
|
||||
const sourceFile = program.getSourceFile('app/hero.ts');
|
||||
const metadata = collector.getMetadata(sourceFile);
|
||||
@ -1234,6 +1242,9 @@ const FILES: Directory = {
|
||||
}
|
||||
export declare function declaredFn();
|
||||
`,
|
||||
'exported-type.ts': `
|
||||
export type SomeType = 'a' | 'b';
|
||||
`,
|
||||
'exported-enum.ts': `
|
||||
import {constValue} from './exported-consts';
|
||||
|
||||
|
Reference in New Issue
Block a user