Compare commits
89 Commits
Author | SHA1 | Date | |
---|---|---|---|
018750154d | |||
b19216d58b | |||
84fc1a3663 | |||
1c40be26c6 | |||
2c5cf19c6d | |||
0dacf6d5f1 | |||
e9f1d44015 | |||
1d9024ee9a | |||
6a6164ab4f | |||
7231f5e26a | |||
86415223cb | |||
269f5acc54 | |||
6e6c866de9 | |||
732ed92cb7 | |||
53a807ae09 | |||
3342a8253b | |||
630c19f52d | |||
af8c2fa4be | |||
0789601dd6 | |||
ce0ac46e42 | |||
b531d87580 | |||
23a2154817 | |||
76d2496f24 | |||
b85cb410f1 | |||
1be22df0df | |||
a805839d38 | |||
3ac61a7550 | |||
57ea33bc5c | |||
4891649d68 | |||
93aba1bb1c | |||
f983a6c615 | |||
18f1b016e5 | |||
591dcc26af | |||
4acd322128 | |||
32a814bdfa | |||
912068e71c | |||
df8e57dc5d | |||
f27f6e498f | |||
01bfbcb84a | |||
a15abbb324 | |||
28c29d560e | |||
65ca7fd4aa | |||
3e3f918bb3 | |||
fc1dcffbdd | |||
ee3c681f98 | |||
6225fedcb8 | |||
2905069559 | |||
47202dd747 | |||
fb130c4eae | |||
734378c90b | |||
3959b7ef28 | |||
e292548523 | |||
c2506a78a1 | |||
34f70c6de2 | |||
3d9c2a6352 | |||
3232125650 | |||
c9f8718d2a | |||
f89ac92e5e | |||
f82efcf942 | |||
dc22f4dc69 | |||
173ccf03ab | |||
1d5c3c1c9b | |||
e4dac421db | |||
c639cdd3b3 | |||
5bcfa7cdfe | |||
d8fd892e71 | |||
074a997302 | |||
62616f541a | |||
156442f80d | |||
37112f549a | |||
cf4b4d53ba | |||
d45e3aa433 | |||
ccc25ee901 | |||
3052063c05 | |||
e107322f5c | |||
f0774254de | |||
6c66031c4a | |||
8f668807cf | |||
f98eb35179 | |||
cc8ae32503 | |||
95f3b1dbe6 | |||
6b96b069bf | |||
ecfe85b06c | |||
5439d4cd49 | |||
df91fd032d | |||
2d5ef15e08 | |||
544a7ad0a5 | |||
ec2c1bec4a | |||
e064d2607d |
@ -27,10 +27,9 @@ jobs:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- restore_cache:
|
||||
key: angular-{{ .Branch }}-{{ checksum "npm-shrinkwrap.json" }}
|
||||
key: angular-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||
|
||||
- run: npm install
|
||||
- run: npm run postinstall
|
||||
- run: yarn install --freeze-lockfile --non-interactive
|
||||
- run: ./node_modules/.bin/gulp lint
|
||||
|
||||
build:
|
||||
@ -39,12 +38,12 @@ jobs:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- restore_cache:
|
||||
key: angular-{{ .Branch }}-{{ checksum "npm-shrinkwrap.json" }}
|
||||
key: angular-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||
|
||||
- run: bazel run @build_bazel_rules_typescript_node//:bin/npm install
|
||||
- run: bazel run @yarn//:yarn
|
||||
- run: bazel build ...
|
||||
- save_cache:
|
||||
key: angular-{{ .Branch }}-{{ checksum "npm-shrinkwrap.json" }}
|
||||
key: angular-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- "node_modules"
|
||||
|
||||
|
@ -104,7 +104,7 @@ groups:
|
||||
animations:
|
||||
conditions:
|
||||
files:
|
||||
- "packages/animation/*"
|
||||
- "packages/animations/*"
|
||||
- "packages/platform-browser/animations/*"
|
||||
users:
|
||||
- matsko #primary
|
||||
|
@ -1,12 +1,10 @@
|
||||
language: node_js
|
||||
sudo: false
|
||||
# force trusty as Google Chrome addon is not supported on Precise
|
||||
dist: trusty
|
||||
node_js:
|
||||
- '6.9.5'
|
||||
|
||||
addons:
|
||||
chrome: stable
|
||||
# firefox: "38.0"
|
||||
apt:
|
||||
sources:
|
||||
@ -50,7 +48,8 @@ env:
|
||||
- CI_MODE=e2e_2
|
||||
- CI_MODE=js
|
||||
- CI_MODE=saucelabs_required
|
||||
- CI_MODE=browserstack_required
|
||||
# deactivated, see #19768
|
||||
# - CI_MODE=browserstack_required
|
||||
- CI_MODE=saucelabs_optional
|
||||
- CI_MODE=browserstack_optional
|
||||
- CI_MODE=aio_tools_test
|
||||
|
42
CHANGELOG.md
42
CHANGELOG.md
@ -1,3 +1,45 @@
|
||||
<a name="4.4.7"></a>
|
||||
## [4.4.7](https://github.com/angular/angular/compare/4.4.6...4.4.7) (2018-04-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** use appropriate inert document strategy for Firefox & Safari ([#22077](https://github.com/angular/angular/issues/22077)) ([2c5cf19](https://github.com/angular/angular/commit/2c5cf19))
|
||||
|
||||
|
||||
|
||||
<a name="4.4.6"></a>
|
||||
## [4.4.6](https://github.com/angular/angular/compare/4.4.5...4.4.6) (2017-10-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** properly support boolean-based transitions and state changes ([#19672](https://github.com/angular/angular/issues/19672)) ([f983a6c](https://github.com/angular/angular/commit/f983a6c)), closes [#9396](https://github.com/angular/angular/issues/9396) [#12337](https://github.com/angular/angular/issues/12337)
|
||||
* **common:** attempt to JSON.parse errors for JSON responses ([#19773](https://github.com/angular/angular/issues/19773)) ([269f5ac](https://github.com/angular/angular/commit/269f5ac))
|
||||
* **router:** RouterLinkActive should update its state right after checking the children ([53a807a](https://github.com/angular/angular/commit/53a807a)), closes [#18983](https://github.com/angular/angular/issues/18983)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **animations:** reduce size of bundle by removing AST classes ([#19673](https://github.com/angular/angular/issues/19673)) ([76d2496](https://github.com/angular/angular/commit/76d2496))
|
||||
|
||||
|
||||
|
||||
<a name="4.4.5"></a>
|
||||
## [4.4.5](https://github.com/angular/angular/compare/4.4.4...4.4.5) (2017-10-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler:** `TestBed.overrideProvider` should keep imported `NgModule`s eager ([#19624](https://github.com/angular/angular/issues/19624)) ([734378c](https://github.com/angular/angular/commit/734378c))
|
||||
* **compiler:** correctly instantiate eager providers that are used via `Injector.get` ([#19558](https://github.com/angular/angular/issues/19558)) ([e292548](https://github.com/angular/angular/commit/e292548)), closes [#15501](https://github.com/angular/angular/issues/15501)
|
||||
* **compiler:** disallow references for select and index evaluation ([95f3b1d](https://github.com/angular/angular/commit/95f3b1d))
|
||||
* **core:** make dynamic & inline code checking behave the same ([#19189](https://github.com/angular/angular/issues/19189)) ([6c66031](https://github.com/angular/angular/commit/6c66031))
|
||||
* **platform-browser:** support customEqualityTesters when overriding Jasmine toEqual ([cc8ae32](https://github.com/angular/angular/commit/cc8ae32))
|
||||
* **tsc-wrapped:** don't rewrite imports when annotating for closure ([#19579](https://github.com/angular/angular/issues/19579)) ([c9f8718](https://github.com/angular/angular/commit/c9f8718))
|
||||
|
||||
|
||||
|
||||
<a name="4.4.4"></a>
|
||||
## [4.4.4](https://github.com/angular/angular/compare/4.4.3...4.4.4) (2017-09-28)
|
||||
|
||||
|
@ -13,7 +13,11 @@ You should run all these tasks from the `angular/aio` folder.
|
||||
Here are the most important tasks you might need to use:
|
||||
|
||||
* `yarn` - install all the dependencies.
|
||||
* `yarn setup` - Install all the dependencies, boilerplate, plunkers, zips and runs dgeni on the docs.
|
||||
* `yarn setup` - install all the dependencies, boilerplate, plunkers, zips and run dgeni on the docs.
|
||||
* `yarn setup-local` - same as `setup`, but use the locally built Angular packages for aio and docs examples boilerplate.
|
||||
|
||||
* `yarn build` - create a production build of the application (after installing dependencies, boilerplate, etc).
|
||||
* `yarn build-local` - same as `build`, but use `setup-local` instead of `setup`.
|
||||
|
||||
* `yarn start` - run a development web server that watches the files; then builds the doc-viewer and reloads the page, as necessary.
|
||||
* `yarn serve-and-sync` - run both the `docs-watch` and `start` in the same console.
|
||||
@ -26,26 +30,26 @@ Here are the most important tasks you might need to use:
|
||||
* `yarn docs-lint` - check that the doc gen code follows our style rules.
|
||||
* `yarn docs-test` - run the unit tests for the doc generation code.
|
||||
|
||||
* `yarn boilerplate:add` - generate all the boilerplate code for the examples, so that they can be run locally. Add the option `-- --local` to use your local version of Angular contained in the "dist" folder.
|
||||
* `yarn boilerplate:add` - generate all the boilerplate code for the examples, so that they can be run locally. Add the option `--local` to use your local version of Angular contained in the "dist" folder.
|
||||
* `yarn boilerplate:remove` - remove all the boilerplate code that was added via `yarn boilerplate:add`.
|
||||
* `yarn generate-plunkers` - generate the plunker files that are used by the `live-example` tags in the docs.
|
||||
* `yarn generate-zips` - generate the zip files from the examples. Zip available via the `live-example` tags in the docs.
|
||||
|
||||
* `yarn example-e2e` - run all e2e tests for examples
|
||||
- `yarn example-e2e -- --setup` - force webdriver update & other setup, then run tests
|
||||
- `yarn example-e2e -- --filter=foo` - limit e2e tests to those containing the word "foo"
|
||||
- `yarn example-e2e -- --setup --local` - run e2e tests with the local version of Angular contained in the "dist" folder
|
||||
- `yarn example-e2e --setup` - force webdriver update & other setup, then run tests
|
||||
- `yarn example-e2e --filter=foo` - limit e2e tests to those containing the word "foo"
|
||||
- `yarn example-e2e --setup --local` - run e2e tests with the local version of Angular contained in the "dist" folder
|
||||
|
||||
* `yarn build-ie-polyfills` - generates a js file of polyfills that can be loaded in Internet Explorer.
|
||||
|
||||
## Using ServiceWorker locally
|
||||
|
||||
Since abb36e3cb, running `yarn start -- --prod` will no longer set up the ServiceWorker, which
|
||||
Since abb36e3cb, running `yarn start --prod` will no longer set up the ServiceWorker, which
|
||||
would require manually running `yarn sw-manifest` and `yarn sw-copy` (something that is not possible
|
||||
with webpack serving the files from memory).
|
||||
|
||||
If you want to test ServiceWorker locally, you can use `yarn build` and serve the files in `dist/`
|
||||
with `yarn http-server -- dist -p 4200`.
|
||||
with `yarn http-server dist -p 4200`.
|
||||
|
||||
For more details see #16745.
|
||||
|
||||
@ -113,10 +117,10 @@ 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.
|
||||
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).
|
||||
the [Authors Style Guide](https://angular.io/guide/docs-style-guide).
|
||||
|
@ -156,7 +156,7 @@ RUN find $AIO_SCRIPTS_SH_DIR -maxdepth 1 -type f -printf "%P\n" \
|
||||
# Set up the Node.js scripts
|
||||
COPY scripts-js/ $AIO_SCRIPTS_JS_DIR/
|
||||
WORKDIR $AIO_SCRIPTS_JS_DIR/
|
||||
RUN yarn install --production
|
||||
RUN yarn install --production --freeze-lockfile
|
||||
|
||||
|
||||
# Set up health check
|
||||
|
@ -6,7 +6,7 @@ server=8.8.4.4
|
||||
# Listen for DHCP and DNS requests only on this address.
|
||||
listen-address=127.0.0.1
|
||||
|
||||
# Force an IP addres for these domains.
|
||||
# Force an IP address for these domains.
|
||||
address=/{{$AIO_NGINX_HOSTNAME}}/127.0.0.1
|
||||
address=/{{$AIO_UPLOAD_HOSTNAME}}/127.0.0.1
|
||||
address=/{{$TEST_AIO_NGINX_HOSTNAME}}/127.0.0.1
|
||||
|
@ -9,7 +9,7 @@ VM host to update the preview server based on changes in the source code.
|
||||
The script will pull the latest changes from the origin's master branch and examine if there have
|
||||
been any changes in files inside the preview server source code directory (see below). If there are,
|
||||
it will create a new image and verify that is works as expected. Finally, it will stop and remove
|
||||
the old docker container and image, create and new container based on the new image and start it.
|
||||
the old docker container and image, create a new container based on the new image and start it.
|
||||
|
||||
The script assumes that the preview server source code is in the repository's
|
||||
`aio/aio-builds-setup/` directory and expects the following inputs:
|
||||
|
@ -9,7 +9,7 @@ readonly defaultImageNameAndTag="aio-builds:latest"
|
||||
# (Necessary, because only `scripts-js/dist/` is copied to the docker image.)
|
||||
(
|
||||
cd "$SCRIPTS_JS_DIR"
|
||||
yarn install
|
||||
yarn install --freeze-lockfile --non-interactive
|
||||
yarn build
|
||||
)
|
||||
|
||||
|
@ -7,6 +7,6 @@ source "`dirname $0`/_env.sh"
|
||||
# Test `scripts-js/`
|
||||
(
|
||||
cd "$SCRIPTS_JS_DIR"
|
||||
yarn install
|
||||
yarn install --freeze-lockfile --non-interactive
|
||||
yarn test
|
||||
)
|
||||
|
@ -335,17 +335,17 @@ describe('Animation Tests', () => {
|
||||
});
|
||||
}
|
||||
|
||||
function getBoundingClientWidth(el: ElementFinder): promise.Promise<number> {
|
||||
function getBoundingClientWidth(el: ElementFinder) {
|
||||
return browser.executeScript(
|
||||
'return arguments[0].getBoundingClientRect().width',
|
||||
el.getWebElement()
|
||||
);
|
||||
) as PromiseLike<number>;
|
||||
}
|
||||
|
||||
function getOffsetWidth(el: ElementFinder): promise.Promise<number> {
|
||||
function getOffsetWidth(el: ElementFinder) {
|
||||
return browser.executeScript(
|
||||
'return arguments[0].offsetWidth',
|
||||
el.getWebElement()
|
||||
);
|
||||
) as PromiseLike<number>;
|
||||
}
|
||||
});
|
||||
|
@ -12,8 +12,10 @@ describe('Component Style Tests', function () {
|
||||
let componentH1 = element(by.css('hero-app > h1'));
|
||||
let externalH1 = element(by.css('body > h1'));
|
||||
|
||||
expect(componentH1.getCssValue('fontWeight')).toEqual('normal');
|
||||
expect(externalH1.getCssValue('fontWeight')).not.toEqual('normal');
|
||||
// Note: sometimes webdriver returns the fontWeight as "normal",
|
||||
// othertimes as "400", both of which are equal in CSS terms.
|
||||
expect(componentH1.getCssValue('fontWeight')).toMatch(/normal|400/);
|
||||
expect(externalH1.getCssValue('fontWeight')).not.toMatch(/normal|400/);
|
||||
});
|
||||
|
||||
|
||||
|
@ -7,3 +7,4 @@
|
||||
<p>We're sorry. The page you are looking for cannot be found.</p>
|
||||
</div>
|
||||
</div>
|
||||
<aio-file-not-found-search></aio-file-not-found-search>
|
||||
|
@ -2,13 +2,13 @@
|
||||
|
||||
This page presents design and layout guidelines for Angular documentation pages. These guidelines should be followed by all guide page authors. Deviations must be approved by the documentation editor.
|
||||
|
||||
Most guide pages should have [accompanying sample code](#from-code-samples) with
|
||||
[special markup](#source-code-markup) for the code snippets on the page.
|
||||
Code samples should adhere to the
|
||||
[style guide for Angular applications](guide/styleguide "Application Code Style Guide")
|
||||
Most guide pages should have [accompanying sample code](#from-code-samples) with
|
||||
[special markup](#source-code-markup) for the code snippets on the page.
|
||||
Code samples should adhere to the
|
||||
[style guide for Angular applications](guide/styleguide "Application Code Style Guide")
|
||||
because readers expect consistency.
|
||||
|
||||
For clarity and precision, every guideline on _this_ page is illustrated with a working example,
|
||||
For clarity and precision, every guideline on _this_ page is illustrated with a working example,
|
||||
followed by the page markup for that example ... as shown here.
|
||||
|
||||
```html
|
||||
@ -19,13 +19,13 @@ followed by the page markup for that example ... as shown here.
|
||||
|
||||
To make changes to the documentation pages and sample code, clone the [Angular github repository](https://github.com/angular/angular "Angular repo") and go to the `aio/` folder.
|
||||
|
||||
The [aio/README.md](https://github.com/angular/angular/blob/master/aio/README.md "AIO ReadMe") explains how to install and use the tools to edit and test your changes.
|
||||
The [aio/README.md](https://github.com/angular/angular/blob/master/aio/README.md "AIO ReadMe") explains how to install and use the tools to edit and test your changes.
|
||||
|
||||
Here are a few essential commands for guide page authors.
|
||||
|
||||
1. `yarn setup` — installs packages; builds docs, plunkers, and zips.
|
||||
|
||||
1. `yarn docs-watch -- --watch-only` — watches for saved content changes and refreshes the browser. The (optional) `--watch-only` flag skips the initial docs rebuild.
|
||||
1. `yarn docs-watch --watch-only` — watches for saved content changes and refreshes the browser. The (optional) `--watch-only` flag skips the initial docs rebuild.
|
||||
|
||||
1. `yarn start` — starts the doc viewer application so you can see your local changes in the browser.
|
||||
|
||||
@ -33,9 +33,9 @@ Here are a few essential commands for guide page authors.
|
||||
|
||||
## Guide pages
|
||||
|
||||
All but a few guide pages are [markdown](https://daringfireball.net/projects/markdown/syntax "markdown") files with an `.md` extension.
|
||||
All but a few guide pages are [markdown](https://daringfireball.net/projects/markdown/syntax "markdown") files with an `.md` extension.
|
||||
|
||||
Every guide page file is stored in the `content/guide` directory. Although the [side navigation](#navigation) panel displays as a hierarchy, the directory is flat with no sub-folders.
|
||||
Every guide page file is stored in the `content/guide` directory. Although the [side navigation](#navigation) panel displays as a hierarchy, the directory is flat with no sub-folders.
|
||||
The flat folder approach allows us to shuffle the apparent navigation structure without moving page files or redirecting old page URLs.
|
||||
|
||||
The doc generation process consumes the markdown files in the `content/guide` directory and produces JSON files in the `src/generated/docs/guide` directory, which is also flat. Those JSON files contain a combination of document metadata and HTML content.
|
||||
@ -52,7 +52,7 @@ _Tutorial_ pages are exactly like guide pages. The only difference is that they
|
||||
_API_ pages are generated from Angular source code into the `src/generated/docs/api` directory.
|
||||
The doc viewer translates URLs that begin `api/` into requests for document JSON files in that directory. This style guide does not discuss creation or maintenance of API pages.
|
||||
|
||||
_Marketing_ pages are similar to guide pages. They're located in the `content/marketing` directory. While they can be markdown files, they may be static HTML pages or dynamic HTML pages that render with JSON data.
|
||||
_Marketing_ pages are similar to guide pages. They're located in the `content/marketing` directory. While they can be markdown files, they may be static HTML pages or dynamic HTML pages that render with JSON data.
|
||||
|
||||
Only a few people are authorized to write marketing pages. This style guide does not discuss creation or maintenance of marketing pages.
|
||||
|
||||
@ -70,14 +70,14 @@ Standard markdown processors don't allow you to put markdown _within_ HTML tags.
|
||||
|
||||
<div class="alert is-critical">
|
||||
|
||||
**Always** follow every opening and closing HTML tag with _a blank line_.
|
||||
**Always** follow every opening and closing HTML tag with _a blank line_.
|
||||
|
||||
</div>
|
||||
|
||||
```html
|
||||
<div class="alert is-critical">
|
||||
|
||||
**Always** follow every opening and closing HTML tag with _a blank line_.
|
||||
**Always** follow every opening and closing HTML tag with _a blank line_.
|
||||
|
||||
</div>
|
||||
```
|
||||
@ -111,7 +111,7 @@ Title text should be in "Title Case", which means that you use capital letters t
|
||||
|
||||
## Sections
|
||||
|
||||
A typical document is divided into sections.
|
||||
A typical document is divided into sections.
|
||||
|
||||
All section heading text should be in "Sentence case", which means the first word is capitalized and all other words are lower case.
|
||||
|
||||
@ -122,7 +122,7 @@ Main section heading
|
||||
</h2>
|
||||
There are usually one or more main sections that may be further divided into secondary sections.
|
||||
|
||||
Begin a main section heading with the markdown `##` characters. Alternatively, you can write the equivalent `<h2>` HTML tag.
|
||||
Begin a main section heading with the markdown `##` characters. Alternatively, you can write the equivalent `<h2>` HTML tag.
|
||||
|
||||
The main section heading should be followed by a blank line and then the content for that heading.
|
||||
|
||||
@ -164,7 +164,7 @@ Try to minimize the heading depth, preferably only two. But more headings, such
|
||||
|
||||
Subsections typically present extra detail and references to other pages.
|
||||
|
||||
Use subsections for commentary that _enriches_ the reader's understanding of the text that precedes it.
|
||||
Use subsections for commentary that _enriches_ the reader's understanding of the text that precedes it.
|
||||
|
||||
A subsection _must not_ contain anything _essential_ to that understanding. Don't put a critical instruction or a tutorial step in a subsection.
|
||||
|
||||
@ -192,7 +192,7 @@ Note that at least one blank line must follow the opening `<div>`. A blank line
|
||||
|
||||
Most pages display a table of contents (TOC). The TOC appears in the right panel when the viewport is wide. When narrow, the TOC appears in an expandable/collapsible region near the top of the page.
|
||||
|
||||
You should not create your own TOC by hand. The TOC is generated automatically from the page's main and secondary section headers.
|
||||
You should not create your own TOC by hand. The TOC is generated automatically from the page's main and secondary section headers.
|
||||
|
||||
To exclude a heading from the TOC, create the heading as an `<h2>` or `<h3>` element with a class called 'no-toc'. You can't do this with markdown.
|
||||
|
||||
@ -214,7 +214,7 @@ A guide without a TOC
|
||||
|
||||
The navigation links at the top, left, and bottom of the screen are generated from the JSON configuration file, `content/navigation.json`.
|
||||
|
||||
The authority to change the `navigation.json` file is limited to a few core team members.
|
||||
The authority to change the `navigation.json` file is limited to a few core team members.
|
||||
But for a new guide page, you should suggest a navigation title and position in the left-side navigation panel called the "side nav".
|
||||
|
||||
Look for the `SideNav` node in `navigation.json`. The `SideNav` node is an array of navigation nodes. Each node is either an _item_ node for a single document or a _header_ node with child nodes.
|
||||
@ -302,7 +302,7 @@ For terminal input and output, put the content between `<code-example>` tags, se
|
||||
|
||||
Inline, hand-coded snippets like this one are _not_ testable and, therefore, are intrinsically unreliable.
|
||||
This example belongs to the small set of pre-approved, inline snippets that includes
|
||||
user input in a command shell or the _output_ of some process.
|
||||
user input in a command shell or the _output_ of some process.
|
||||
|
||||
**Do not write inline code snippets** unless you have a good reason and the editor's permission to do so.
|
||||
In all other cases, code snippets should be generated automatically from tested code samples.
|
||||
@ -341,8 +341,8 @@ The following _code-example_ displays the sample's `app.module.ts`.
|
||||
Here's the brief markup that produced that lengthy snippet:
|
||||
|
||||
```html
|
||||
<code-example
|
||||
path="docs-style-guide/src/app/app.module.ts"
|
||||
<code-example
|
||||
path="docs-style-guide/src/app/app.module.ts"
|
||||
title="src/app/app.module.ts">
|
||||
</code-example>
|
||||
```
|
||||
@ -355,7 +355,7 @@ Following convention, you set the `title` attribute to the file's location withi
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
Unless otherwise noted, all code snippets in this page are derived from sample source code
|
||||
Unless otherwise noted, all code snippets in this page are derived from sample source code
|
||||
located in the `content/examples/docs-style-guide` directory.
|
||||
|
||||
</div>
|
||||
@ -377,7 +377,7 @@ The preferred way to un-ignore a file is to update the `content/examples/.gitign
|
||||
|
||||
</div>
|
||||
|
||||
#### Code-example attributes
|
||||
#### Code-example attributes
|
||||
|
||||
You control the _code-example_ output by setting one or more of its attributes:
|
||||
|
||||
@ -401,8 +401,8 @@ You control the _code-example_ output by setting one or more of its attributes:
|
||||
|
||||
Often you want to focus on a fragment of code within a sample code file. In this example, you focus on the `AppModule` class and its `NgModule` metadata.
|
||||
|
||||
<code-example
|
||||
path="docs-style-guide/src/app/app.module.ts"
|
||||
<code-example
|
||||
path="docs-style-guide/src/app/app.module.ts"
|
||||
region="class">
|
||||
</code-example>
|
||||
|
||||
@ -411,8 +411,8 @@ Then you reference that _docregion_ in the `region` attribute of the `<code-exam
|
||||
|
||||
|
||||
```html
|
||||
<code-example
|
||||
path="docs-style-guide/src/app/app.module.ts"
|
||||
<code-example
|
||||
path="docs-style-guide/src/app/app.module.ts"
|
||||
region="class">
|
||||
</code-example>
|
||||
```
|
||||
@ -430,20 +430,20 @@ There's no need to repeat the header.
|
||||
|
||||
Sometimes you want to display an example of bad code or bad design.
|
||||
|
||||
You should be careful. Readers don't always read carefully and are likely to copy and paste your example of bad code in their own applications. So don't display bad code often.
|
||||
You should be careful. Readers don't always read carefully and are likely to copy and paste your example of bad code in their own applications. So don't display bad code often.
|
||||
|
||||
When you do, set the `class` to `avoid`. The code snippet will be framed in bright red to grab the reader's attention.
|
||||
|
||||
Here's the markup for an "avoid" example in the
|
||||
Here's the markup for an "avoid" example in the
|
||||
[_Angular Style Guide_](guide/styleguide#style-05-03 "Style 05-03: components as elements").
|
||||
|
||||
```html
|
||||
<code-example
|
||||
path="styleguide/src/05-03/app/heroes/shared/hero-button/hero-button.component.avoid.ts"
|
||||
region="example"
|
||||
<code-example
|
||||
path="styleguide/src/05-03/app/heroes/shared/hero-button/hero-button.component.avoid.ts"
|
||||
region="example"
|
||||
title="app/heroes/hero-button/hero-button.component.ts">
|
||||
</code-example>
|
||||
```
|
||||
```
|
||||
|
||||
<code-example path="styleguide/src/05-03/app/heroes/shared/hero-button/hero-button.component.avoid.ts" region="example" title="app/heroes/hero-button/hero-button.component.ts">
|
||||
</code-example>
|
||||
@ -467,22 +467,22 @@ The next example displays multiple code tabs, each with its own title.
|
||||
It demonstrates control over display of line numbers at both the `<code-tabs>` and `<code-pane>` levels.
|
||||
|
||||
<code-tabs linenums="false">
|
||||
<code-pane
|
||||
title="app.component.html"
|
||||
<code-pane
|
||||
title="app.component.html"
|
||||
path="docs-style-guide/src/app/app.component.html">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
title="app.component.ts"
|
||||
<code-pane
|
||||
title="app.component.ts"
|
||||
path="docs-style-guide/src/app/app.component.ts"
|
||||
linenums="true">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
title="app.component.css (heroes)"
|
||||
path="docs-style-guide/src/app/app.component.css"
|
||||
<code-pane
|
||||
title="app.component.css (heroes)"
|
||||
path="docs-style-guide/src/app/app.component.css"
|
||||
region="heroes">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
title="package.json (scripts)"
|
||||
<code-pane
|
||||
title="package.json (scripts)"
|
||||
path="docs-style-guide/package.1.json">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
@ -494,22 +494,22 @@ The `linenums` attribute in the second pane restores line numbering for _itself
|
||||
|
||||
```html
|
||||
<code-tabs linenums="false">
|
||||
<code-pane
|
||||
title="app.component.html"
|
||||
<code-pane
|
||||
title="app.component.html"
|
||||
path="docs-style-guide/src/app/app.component.html">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
title="app.component.ts"
|
||||
<code-pane
|
||||
title="app.component.ts"
|
||||
path="docs-style-guide/src/app/app.component.ts"
|
||||
linenums="true">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
title="app.component.css (heroes)"
|
||||
path="docs-style-guide/src/app/app.component.css"
|
||||
<code-pane
|
||||
title="app.component.css (heroes)"
|
||||
path="docs-style-guide/src/app/app.component.css"
|
||||
region="heroes">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
title="package.json (scripts)"
|
||||
<code-pane
|
||||
title="package.json (scripts)"
|
||||
path="docs-style-guide/package.1.json">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
@ -572,8 +572,8 @@ Every line of code _after_ that comment belongs in the region _until_ the code f
|
||||
|
||||
The `src/main.ts` is a simple example of a file with a single _#docregion_ at the top of the file.
|
||||
|
||||
<code-example
|
||||
path="docs-style-guide/src/main.ts"
|
||||
<code-example
|
||||
path="docs-style-guide/src/main.ts"
|
||||
title="src/main.ts"></code-example>
|
||||
|
||||
</div>
|
||||
@ -592,8 +592,8 @@ You distinguish among them by giving each fragment its own _#docregion name_ as
|
||||
Remember to refer to this region by name in the `region` attribute of the `<code-example>` or `<code-pane>` as you did in an example above like this:
|
||||
|
||||
```html
|
||||
<code-example
|
||||
path="docs-style-guide/src/app/app.module.ts"
|
||||
<code-example
|
||||
path="docs-style-guide/src/app/app.module.ts"
|
||||
region="class"></code-example>
|
||||
```
|
||||
|
||||
@ -603,7 +603,7 @@ The _#docregion_ with no name is the _default region_. Do _not_ set the `region`
|
||||
|
||||
You can nest _#docregions_ within _#docregions_
|
||||
```
|
||||
// #docregion
|
||||
// #docregion
|
||||
... some code ...
|
||||
// #docregion inner-region
|
||||
... more code ...
|
||||
@ -647,12 +647,12 @@ export class AppComponent {
|
||||
Here's are the two corresponding code snippets displayed side-by-side.
|
||||
|
||||
<code-tabs>
|
||||
<code-pane
|
||||
title="app.component.ts (class)"
|
||||
<code-pane
|
||||
title="app.component.ts (class)"
|
||||
path="docs-style-guide/src/app/app.component.ts"
|
||||
region="class">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
<code-pane
|
||||
title="app.component.ts (class-skeleton)"
|
||||
path="docs-style-guide/src/app/app.component.ts"
|
||||
region="class-skeleton">
|
||||
@ -661,7 +661,7 @@ Here's are the two corresponding code snippets displayed side-by-side.
|
||||
|
||||
Some observations:
|
||||
|
||||
* The `#docplaster` at the top is another bit of code snippet markup. It tells the processor how to join the fragments into a single snippet.
|
||||
* The `#docplaster` at the top is another bit of code snippet markup. It tells the processor how to join the fragments into a single snippet.
|
||||
|
||||
In this example, we tell the processor to put the fragments together without anything in between - without any "plaster". Most sample files define this _empty plaster_.
|
||||
|
||||
@ -673,7 +673,7 @@ Some observations:
|
||||
|
||||
Code snippet markup is not supported for JSON files because comments are forbidden in JSON files.
|
||||
|
||||
You can display an entire JSON file by referring to it in the `src` attribute.
|
||||
You can display an entire JSON file by referring to it in the `src` attribute.
|
||||
But you can't display JSON fragments because you can't add `#docregion` tags to the file.
|
||||
|
||||
If the JSON file is too big, you could copy the nodes-of-interest into markdown backticks.
|
||||
@ -684,12 +684,12 @@ You can't test this partial file and you'll never use it in the application. But
|
||||
|
||||
Here's an example that excerpts certain scripts from `package.json` into a partial file named `package.1.json`.
|
||||
|
||||
<code-example
|
||||
<code-example
|
||||
path="docs-style-guide/package.1.json"
|
||||
title="package.json (selected scripts)"></code-example>
|
||||
|
||||
```html
|
||||
<code-example
|
||||
<code-example
|
||||
path="docs-style-guide/package.1.json"
|
||||
title="package.json (selected scripts)"></code-example>
|
||||
```
|
||||
@ -713,7 +713,7 @@ You'll find many such files among the samples in the Angular documentation.
|
||||
|
||||
Remember to exclude these files from plunkers by listing them in the `plnkr.json` as illustrated here.
|
||||
|
||||
<code-example
|
||||
<code-example
|
||||
path="docs-style-guide/plnkr.json"
|
||||
title="plnkr.json"></code-example>
|
||||
|
||||
@ -722,7 +722,7 @@ Remember to exclude these files from plunkers by listing them in the `plnkr.json
|
||||
|
||||
By adding `<live-example>` to the page you generate links that run sample code in the Plunker live coding environment and download that code to the reader's file system.
|
||||
|
||||
Live examples (AKA "plunkers") are defined by one or more `plnkr.json` files in the root of a code sample folder. Each sample folder usually has a single unnamed definition file, the default `plnkr.json`.
|
||||
Live examples (AKA "plunkers") are defined by one or more `plnkr.json` files in the root of a code sample folder. Each sample folder usually has a single unnamed definition file, the default `plnkr.json`.
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
@ -745,7 +745,7 @@ Clicking the first link opens the code sample in a new browser tab in the "embed
|
||||
You can change the appearance and behavior of the live example with attributes and classes.
|
||||
|
||||
|
||||
<h3 class="no-toc">Custom label and tooltip</h3>
|
||||
<h3 class="no-toc">Custom label and tooltip</h3>
|
||||
|
||||
Give the live example anchor a custom label and tooltip by setting the `title` attribute.
|
||||
|
||||
@ -763,7 +763,7 @@ You can achieve the same effect by putting the label between the `<live-example>
|
||||
<live-example>Live example with content label</live-example>
|
||||
```
|
||||
|
||||
<h3 class="no-toc">Live example from another guide</h3>
|
||||
<h3 class="no-toc">Live example from another guide</h3>
|
||||
|
||||
To link to a plunker in a folder whose name is not the same as the current guide page, set the `name` attribute to the name of that folder.
|
||||
|
||||
@ -773,7 +773,7 @@ To link to a plunker in a folder whose name is not the same as the current guide
|
||||
<live-example name="router">Live Example from the Router guide</live-example>
|
||||
```
|
||||
|
||||
<h3 class="no-toc">Live Example for named plunker</h3>
|
||||
<h3 class="no-toc">Live Example for named plunker</h3>
|
||||
|
||||
To link to a plunker defined by a named `plnkr.json` file, set the `plnkr` attribute. The following example links to the plunker defined by `second.plnkr.json` in the current guide's directory.
|
||||
|
||||
@ -783,7 +783,7 @@ To link to a plunker defined by a named `plnkr.json` file, set the `plnkr` attri
|
||||
<live-example plnkr="second"></live-example>
|
||||
```
|
||||
|
||||
<h3 class="no-toc">Live Example without download</h3>
|
||||
<h3 class="no-toc">Live Example without download</h3>
|
||||
|
||||
To skip the download link, add the `noDownload` attribute.
|
||||
|
||||
@ -793,7 +793,7 @@ To skip the download link, add the `noDownload` attribute.
|
||||
<live-example noDownload>Just the plunker</live-example>
|
||||
```
|
||||
|
||||
<h3 class="no-toc">Live Example with download-only</h3>
|
||||
<h3 class="no-toc">Live Example with download-only</h3>
|
||||
|
||||
To skip the live plunker link and only link to the download, add the `downloadOnly` attribute.
|
||||
|
||||
@ -803,9 +803,9 @@ To skip the live plunker link and only link to the download, add the `downloadOn
|
||||
<live-example downloadOnly>Download only</live-example>
|
||||
```
|
||||
|
||||
<h3 class="no-toc">Embedded live example</h3>
|
||||
<h3 class="no-toc">Embedded live example</h3>
|
||||
|
||||
By default, a live example link opens a plunker in a separate browser tab.
|
||||
By default, a live example link opens a plunker in a separate browser tab.
|
||||
You can embed the plunker within the guide page itself by adding the `embedded` attribute.
|
||||
|
||||
For performance reasons, the plunker does not start right away. The reader sees an image instead. Clicking the image starts the sometimes-slow process of launching the embedded plunker within an iframe on the page.
|
||||
@ -853,10 +853,10 @@ When navigating within the page, you can omit the page URL when specifying the l
|
||||
|
||||
It is often a good idea to *lock-in* a good anchor name.
|
||||
|
||||
Sometimes the section header text makes for an unattractive anchor. [This one](#ugly-long-section-header-anchors) is pretty bad.
|
||||
Sometimes the section header text makes for an unattractive anchor. [This one](#ugly-long-section-header-anchors) is pretty bad.
|
||||
|
||||
```html
|
||||
[This one](#ugly-long-section-header-anchors) is pretty bad.
|
||||
[This one](#ugly-long-section-header-anchors) is pretty bad.
|
||||
```
|
||||
|
||||
The greater danger is that **a future rewording of the header text would break** a link to this section.
|
||||
@ -922,7 +922,7 @@ A helpful, informational alert.
|
||||
```
|
||||
|
||||
Alerts are meant to grab the user's attention and should be used sparingly.
|
||||
They are not for casual asides or commentary. Use [subsections](#subsections "subsections") for commentary.
|
||||
They are not for casual asides or commentary. Use [subsections](#subsections "subsections") for commentary.
|
||||
|
||||
## Callouts
|
||||
|
||||
@ -1163,9 +1163,9 @@ src="generated/images/guide/docs-style-guide/flying-hero.png"
|
||||
|
||||
**Do not use the markdown image syntax, \!\[\.\.\.\]\(\.\.\.\).**
|
||||
|
||||
Images should be specified in an `<img>` tag.
|
||||
Images should be specified in an `<img>` tag.
|
||||
|
||||
For accessibility, always set the `alt` attribute with a meaningful description of the image.
|
||||
For accessibility, always set the `alt` attribute with a meaningful description of the image.
|
||||
|
||||
You should nest the `<img>` tag within a `<figure>` tag, which styles the image within a drop-shadow frame. You'll need the editor's permission to skip the `<figure>` tag.
|
||||
|
||||
@ -1177,7 +1177,7 @@ Here's a conforming example
|
||||
|
||||
```html
|
||||
<figure>
|
||||
<img src="generated/images/guide/docs-style-guide/flying-hero.png"
|
||||
<img src="generated/images/guide/docs-style-guide/flying-hero.png"
|
||||
alt="flying hero">
|
||||
</figure>
|
||||
```
|
||||
@ -1197,13 +1197,13 @@ Here's the "flying hero" at a more reasonable scale.
|
||||
```html
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/docs-style-guide/flying-hero.png"
|
||||
alt="flying Angular hero"
|
||||
<img src="generated/images/guide/docs-style-guide/flying-hero.png"
|
||||
alt="flying Angular hero"
|
||||
width="200">
|
||||
</figure>
|
||||
```
|
||||
|
||||
Wide images can be a problem. Most browsers try to rescale the image but wide images may overflow the document in certain viewports.
|
||||
Wide images can be a problem. Most browsers try to rescale the image but wide images may overflow the document in certain viewports.
|
||||
|
||||
**Do not set a width greater than 700px**. If you wish to display a larger image, provide a link to the actual image that the user can click on to see the full size image separately as in this example of `source-map-explorer` output from the "Ahead-of-time Compilation" guide:
|
||||
|
||||
@ -1222,11 +1222,11 @@ Consider using an image compression web site such as [tinypng](https://tinypng.c
|
||||
|
||||
You can float the image to the left or right of text by applying the class="left" or class="right" attributes respectively.
|
||||
|
||||
<img src="generated/images/guide/docs-style-guide/flying-hero.png"
|
||||
alt="flying Angular hero"
|
||||
<img src="generated/images/guide/docs-style-guide/flying-hero.png"
|
||||
alt="flying Angular hero"
|
||||
width="200"
|
||||
class="left">
|
||||
|
||||
|
||||
This text wraps around to the right of the floating "flying hero" image.
|
||||
|
||||
Headings and code-examples automatically clear a floating image. If you need to force a piece of text to clear a floating image, add `<br class="clear">` where the text should break.
|
||||
@ -1236,11 +1236,11 @@ Headings and code-examples automatically clear a floating image. If you need to
|
||||
The markup for the above example is:
|
||||
|
||||
```html
|
||||
<img src="generated/images/guide/docs-style-guide/flying-hero.png"
|
||||
alt="flying Angular hero"
|
||||
<img src="generated/images/guide/docs-style-guide/flying-hero.png"
|
||||
alt="flying Angular hero"
|
||||
width="200"
|
||||
class="left">
|
||||
|
||||
|
||||
This text wraps around to the right of the floating "flying hero" image.
|
||||
|
||||
Headings and code-examples automatically clear a floating image. If you need to force a piece of text to clear a floating image, add `<br class="clear">` where the text should break.
|
||||
@ -1256,8 +1256,8 @@ If you have a floating image inside an alert, callout, or a subsection, it is a
|
||||
|
||||
<div class="l-sub-section clear-fix">
|
||||
|
||||
<img src="generated/images/guide/docs-style-guide/flying-hero.png"
|
||||
alt="flying Angular hero"
|
||||
<img src="generated/images/guide/docs-style-guide/flying-hero.png"
|
||||
alt="flying Angular hero"
|
||||
width="100"
|
||||
class="right">
|
||||
|
||||
@ -1268,12 +1268,12 @@ If you have a floating image inside an alert, callout, or a subsection, it is a
|
||||
```html
|
||||
<div class="l-sub-section clear-fix">
|
||||
|
||||
<img src="generated/images/guide/docs-style-guide/flying-hero.png"
|
||||
alt="flying Angular hero"
|
||||
<img src="generated/images/guide/docs-style-guide/flying-hero.png"
|
||||
alt="flying Angular hero"
|
||||
width="100"
|
||||
class="right">
|
||||
|
||||
|
||||
A subsection with **markdown** formatted text.
|
||||
|
||||
</div>
|
||||
```
|
||||
```
|
||||
|
@ -139,6 +139,10 @@ null if the control value is valid _or_ a validation error object.
|
||||
The validation error object typically has a property whose name is the validation key, `'forbiddenName'`,
|
||||
and whose value is an arbitrary dictionary of values that you could insert into an error message, `{name}`.
|
||||
|
||||
Custom async validators are similar to sync validators, but they must instead return a Promise or Observable
|
||||
that later emits null or a validation error object. In the case of an Observable, the Observable must complete,
|
||||
at which point the form uses the last value emitted for validation.
|
||||
|
||||
### Adding to reactive forms
|
||||
|
||||
In reactive forms, custom validators are fairly simple to add. All you have to do is pass the function directly
|
||||
|
@ -117,7 +117,7 @@ Now the extractor tool and compiler will generate a translation unit with _your
|
||||
<code-example path="i18n/src/locale/messages.es.xlf.html" region="custom-id" linenums="false">
|
||||
</code-example>
|
||||
|
||||
Here is the `i18n` attribute with a _definition_, followed by the custom `id`:
|
||||
Here is the `i18n` attribute with a _description_, followed by the custom `id`:
|
||||
|
||||
<code-example path='i18n/src/app/app.component.1.html' region='i18n-attribute-id' title='app/app.component.html' linenums="false">
|
||||
</code-example>
|
||||
|
@ -4502,8 +4502,8 @@ helps instantly identify which members of the component serve which purpose.
|
||||
|
||||
**Why?** The property associated with `@HostBinding` or the method associated with `@HostListener`
|
||||
can be modified only in a single place—in the directive's class.
|
||||
If you use the `host` metadata property, you must modify both the property declaration inside the controller,
|
||||
and the metadata associated with the directive.
|
||||
If you use the `host` metadata property, you must modify both the property/method declaration in the
|
||||
directive's class and the metadata in the decorator associated with the directive.
|
||||
|
||||
|
||||
</div>
|
||||
|
@ -142,7 +142,7 @@ The `hero` in `{{hero.name}}`
|
||||
refers to the template input variable, not the component's property.
|
||||
|
||||
Template expressions cannot refer to anything in
|
||||
the global namespace. They can't refer to `window` or `document`. They
|
||||
the global namespace (except `undefined`). 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.
|
||||
|
||||
|
BIN
aio/content/images/marketing/home/angular-connect.png
Normal file
BIN
aio/content/images/marketing/home/angular-connect.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.8 KiB |
@ -13,18 +13,6 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- ngJapan -->
|
||||
<tr>
|
||||
<th><a href="http://ngjapan.org/" title="ng-Japan">ng-Japan</a></th>
|
||||
<td>Tokyo, Japan</td>
|
||||
<td>June 17, 2017</td>
|
||||
</tr>
|
||||
<!-- AngularMix -->
|
||||
<tr>
|
||||
<th><a href="https://angularmix.com/" title="AngularMix">AngularMix</a></th>
|
||||
<td>Universal Studios, Orlando, Florida</td>
|
||||
<td>October 8, 2017</td>
|
||||
</tr>
|
||||
<!-- ReactiveConf -->
|
||||
<tr>
|
||||
<th><a href="https://reactiveconf.com/" title="ReactiveConf">ReactiveConf</a></th>
|
||||
@ -35,7 +23,7 @@
|
||||
<tr>
|
||||
<th><a href="http://angularconnect.com" title="AngularConnect">AngularConnect</a></th>
|
||||
<td>London, United Kingdom</td>
|
||||
<td>November 07, 2017</td>
|
||||
<td>November 7-8, 2017</td>
|
||||
</tr>
|
||||
<!-- ngAtlanta-->
|
||||
<tr>
|
||||
@ -43,6 +31,30 @@
|
||||
<td>Atlanta, Georgia</td>
|
||||
<td>January 30, 2018</td>
|
||||
</tr>
|
||||
<!-- ngVikings-->
|
||||
<tr>
|
||||
<th><a href="https://ngvikings.org/" title="ngVikings">ngVikings</a></th>
|
||||
<td>Helsinki, Finland</td>
|
||||
<td>March 1-2, 2018</td>
|
||||
</tr>
|
||||
<!-- ngconf 2018-->
|
||||
<tr>
|
||||
<th><a href="https://www.ng-conf.org/" title="ng-conf">ng-conf</a></th>
|
||||
<td>Salt Lake City, UT</td>
|
||||
<td>April 18-20, 2018</td>
|
||||
</tr>
|
||||
<!-- WeRDevs-->
|
||||
<tr>
|
||||
<th><a href="https://www.wearedevelopers.com/" title="WeAreDevs">WeAreDevelopers</a></th>
|
||||
<td>Vienna</td>
|
||||
<td>May 16-18, 2018</td>
|
||||
</tr>
|
||||
<!-- AngularConnect-->
|
||||
<tr>
|
||||
<th><a href="http://angularconnect.com" title="AngularConnect">AngularConnect</a></th>
|
||||
<td>London, United Kingdom</td>
|
||||
<td>November 5-7, 2018</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
|
@ -30,10 +30,10 @@
|
||||
<!--Announcement Bar-->
|
||||
<div class="homepage-container">
|
||||
<div class="announcement-bar">
|
||||
<img src="generated/images/marketing/home/angular-mix.png" height="40" width="151">
|
||||
<p>Join us at our newest event, October 2017</p>
|
||||
<a class="button" href="https://angularmix.com/">Learn More</a>
|
||||
</div>
|
||||
<img src="generated/images/marketing/home/angular-connect.png">
|
||||
<p>Join us in London for AngularConnect<br>November 7-8, 2017</p>
|
||||
<a class="button" href="https://angularconnect.com/">Learn More</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Group 1-->
|
||||
|
@ -317,7 +317,7 @@
|
||||
"desc": "Material Design components for Angular",
|
||||
"logo": "",
|
||||
"rev": true,
|
||||
"title": "Angular Material 2",
|
||||
"title": "Angular Material",
|
||||
"url": "https://github.com/angular/material2"
|
||||
},
|
||||
"aggrid": {
|
||||
|
@ -1207,7 +1207,7 @@ Here's an excerpt:
|
||||
|
||||
|
||||
Create the file <code>styles.css</code>.
|
||||
Ensure that the file contains the [master styles provided here](https://raw.githubusercontent.com/angular/angular/master/aio/tools/examples/shared/boilerplate/src/styles.css).
|
||||
Ensure that the file contains the [master styles provided here](https://raw.githubusercontent.com/angular/angular/master/aio/tools/examples/shared/boilerplate/common/src/styles.css).
|
||||
Also edit <code>index.html</code> to refer to this stylesheet.
|
||||
|
||||
|
||||
|
@ -21,4 +21,24 @@ describe('Api pages', function() {
|
||||
const page = new ApiPage('api/animations/AnimationPlayer');
|
||||
expect(page.getDescendants('class')).toEqual(['NoopAnimationPlayer', 'MockAnimationPlayer'] as any);
|
||||
});
|
||||
|
||||
it('should show type params of type-aliases', () => {
|
||||
const page = new ApiPage('api/common/http/HttpEvent');
|
||||
expect(page.getOverview('type-alias').getText()).toContain('type HttpEvent<T>');
|
||||
});
|
||||
|
||||
it('should show readonly properties as getters', () => {
|
||||
const page = new ApiPage('api/common/http/HttpRequest');
|
||||
expect(page.getOverview('class').getText()).toContain('get body: T|null');
|
||||
});
|
||||
|
||||
it('should not show parenthesis for getters', () => {
|
||||
const page = new ApiPage('api/core/NgModuleRef');
|
||||
expect(page.getOverview('class').getText()).toContain('get injector: Injector');
|
||||
});
|
||||
|
||||
it('should show both type and initializer if set', () => {
|
||||
const page = new ApiPage('api/common/HashLocationStrategy');
|
||||
expect(page.getOverview('class').getText()).toContain('path(includeHash: boolean = false): string');
|
||||
});
|
||||
});
|
||||
|
@ -27,4 +27,8 @@ export class ApiPage extends SitePage {
|
||||
const selector = `.descendants.${docType} ${onlyDirect ? '>' : ''} li > :not(ul) code`;
|
||||
return element.all(by.css(selector)).map<string>(item => item.getText());
|
||||
}
|
||||
|
||||
getOverview(docType) {
|
||||
return element(by.css(`.${docType}-overview`));
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { browser, element, by, promise } from 'protractor';
|
||||
import { element, by } from 'protractor';
|
||||
import { SitePage } from './app.po';
|
||||
|
||||
describe('site App', function() {
|
||||
@ -41,20 +41,20 @@ describe('site App', function() {
|
||||
|
||||
describe('scrolling to the top', () => {
|
||||
it('should scroll to the top when navigating to another page', () => {
|
||||
page.navigateTo('guide/docs');
|
||||
page.navigateTo('guide/security');
|
||||
page.scrollToBottom();
|
||||
page.getScrollTop().then(scrollTop => expect(scrollTop).toBeGreaterThan(0));
|
||||
|
||||
page.navigateTo('guide/api');
|
||||
page.navigateTo('api');
|
||||
page.getScrollTop().then(scrollTop => expect(scrollTop).toBe(0));
|
||||
});
|
||||
|
||||
it('should scroll to the top when navigating to the same page', () => {
|
||||
page.navigateTo('guide/docs');
|
||||
page.navigateTo('guide/security');
|
||||
page.scrollToBottom();
|
||||
page.getScrollTop().then(scrollTop => expect(scrollTop).toBeGreaterThan(0));
|
||||
|
||||
page.navigateTo('guide/docs');
|
||||
page.navigateTo('guide/security');
|
||||
page.getScrollTop().then(scrollTop => expect(scrollTop).toBe(0));
|
||||
});
|
||||
});
|
||||
@ -66,7 +66,9 @@ describe('site App', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('google analytics', () => {
|
||||
// TODO(https://github.com/angular/angular/issues/19785): Activate this again
|
||||
// once it is no more flaky.
|
||||
xdescribe('google analytics', () => {
|
||||
beforeEach(done => page.gaReady.then(done));
|
||||
|
||||
it('should call ga', done => {
|
||||
@ -100,4 +102,12 @@ describe('site App', function() {
|
||||
expect(page.getSearchResults().map(link => link.getText())).toContain('ControlValueAccessor');
|
||||
});
|
||||
});
|
||||
|
||||
describe('404 page', () => {
|
||||
it('should search the index for words found in the url', () => {
|
||||
page.navigateTo('http/router');
|
||||
expect(page.getSearchResults().map(link => link.getText())).toContain('Http');
|
||||
expect(page.getSearchResults().map(link => link.getText())).toContain('Router');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -30,8 +30,14 @@ module.exports = function (config) {
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
browsers: ['CustomChrome'],
|
||||
browserNoActivityTimeout: 60000,
|
||||
singleRun: false
|
||||
singleRun: false,
|
||||
customLaunchers: {
|
||||
CustomChrome: {
|
||||
base: 'Chrome',
|
||||
flags: process.env.TRAVIS && ['--no-sandbox']
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -6,33 +6,45 @@
|
||||
"author": "Angular",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"aio-use-local": "node tools/ng-packages-installer overwrite . --debug --ignore-packages @angular/service-worker",
|
||||
"aio-use-npm": "node tools/ng-packages-installer restore .",
|
||||
"aio-check-local": "node tools/ng-packages-installer check .",
|
||||
"ng": "yarn check-env && ng",
|
||||
"start": "yarn check-env && ng serve",
|
||||
"prebuild": "yarn check-env && yarn setup",
|
||||
"build": "ng build --target=production --environment=stable -sm --build-optimizer",
|
||||
"postbuild": "yarn sw-manifest && yarn sw-copy",
|
||||
"prebuild": "yarn setup",
|
||||
"build": "yarn ~~build",
|
||||
"prebuild-local": "yarn setup-local",
|
||||
"build-local": "yarn ~~build",
|
||||
"lint": "yarn check-env && yarn docs-lint && ng lint && yarn example-lint",
|
||||
"test": "yarn check-env && ng test",
|
||||
"pree2e": "yarn check-env && yarn ~~update-webdriver",
|
||||
"e2e": "ng e2e --no-webdriver-update",
|
||||
"setup": "yarn && yarn build-ie-polyfills && yarn boilerplate:remove && yarn boilerplate:add && yarn generate-plunkers && yarn generate-zips && yarn docs",
|
||||
"pretest-pwa-score-local": "yarn build",
|
||||
"test-pwa-score-local": "concurrently --kill-others --success first \"http-server dist -p 4200 --silent\" \"yarn test-pwa-score -- http://localhost:4200 90\"",
|
||||
"presetup": "yarn install --freeze-lockfile && yarn ~~check-env && yarn boilerplate:remove",
|
||||
"setup": "yarn aio-use-npm && yarn example-use-npm",
|
||||
"postsetup": "yarn boilerplate:add && yarn build-ie-polyfills && yarn generate-plunkers && yarn generate-zips && yarn docs",
|
||||
"presetup-local": "yarn presetup",
|
||||
"setup-local": "yarn aio-use-local && yarn example-use-local",
|
||||
"postsetup-local": "yarn postsetup",
|
||||
"pretest-pwa-score-localhost": "yarn build",
|
||||
"test-pwa-score-localhost": "concurrently --kill-others --success first \"http-server dist -p 4200 --silent\" \"yarn test-pwa-score http://localhost:4200 90\"",
|
||||
"test-pwa-score": "node scripts/test-pwa-score",
|
||||
"example-e2e": "node ./tools/examples/run-example-e2e",
|
||||
"example-e2e": "yarn example-check-local && node ./tools/examples/run-example-e2e",
|
||||
"example-lint": "tslint -c \"content/examples/tslint.json\" \"content/examples/**/*.ts\" -e \"content/examples/styleguide/**/*.avoid.ts\"",
|
||||
"example-use-local": "node tools/ng-packages-installer overwrite ./tools/examples/shared",
|
||||
"example-use-npm": "node tools/ng-packages-installer restore ./tools/examples/shared",
|
||||
"example-check-local": "node tools/ng-packages-installer check ./tools/examples/shared",
|
||||
"deploy-preview": "scripts/deploy-preview.sh",
|
||||
"deploy-production": "scripts/deploy-to-firebase.sh",
|
||||
"check-env": "node scripts/check-environment",
|
||||
"check-env": "yarn ~~check-env",
|
||||
"postcheck-env": "yarn aio-check-local",
|
||||
"payload-size": "scripts/payload.sh",
|
||||
"predocs": "rimraf src/generated/{docs,*.json}",
|
||||
"docs": "dgeni ./tools/transforms/angular.io-package",
|
||||
"docs-watch": "node tools/transforms/authors-package/watchr.js",
|
||||
"docs-lint": "eslint --ignore-path=\"tools/transforms/.eslintignore\" tools/transforms",
|
||||
"docs-test": "node tools/transforms/test.js",
|
||||
"tools-test": "./scripts/deploy-to-firebase.test.sh && yarn docs-test",
|
||||
"tools-test": "./scripts/deploy-to-firebase.test.sh && yarn docs-test && yarn boilerplate:test && jasmine tools/ng-packages-installer/index.spec.js",
|
||||
"serve-and-sync": "concurrently --kill-others \"yarn docs-watch\" \"yarn start\"",
|
||||
"~~update-webdriver": "webdriver-manager update --standalone false --gecko false",
|
||||
"boilerplate:add": "node ./tools/examples/example-boilerplate add",
|
||||
"boilerplate:remove": "node ./tools/examples/example-boilerplate remove",
|
||||
"boilerplate:test": "node tools/examples/test.js",
|
||||
@ -41,7 +53,11 @@
|
||||
"sw-manifest": "ngu-sw-manifest --dist dist --in ngsw-manifest.json --out dist/ngsw-manifest.json",
|
||||
"sw-copy": "cp node_modules/@angular/service-worker/bundles/worker-basic.min.js dist/",
|
||||
"postinstall": "uglifyjs node_modules/lunr/lunr.js -c -m -o src/assets/js/lunr.min.js --source-map",
|
||||
"build-ie-polyfills": "node node_modules/webpack/bin/webpack.js -p src/ie-polyfills.js src/generated/ie-polyfills.min.js"
|
||||
"build-ie-polyfills": "node node_modules/webpack/bin/webpack.js -p src/ie-polyfills.js src/generated/ie-polyfills.min.js",
|
||||
"~~check-env": "node scripts/check-environment",
|
||||
"~~build": "ng build --target=production --environment=stable -sm --build-optimizer",
|
||||
"post~~build": "yarn sw-manifest && yarn sw-copy",
|
||||
"~~update-webdriver": "webdriver-manager update --standalone false --gecko false $CHROMEDRIVER_VERSION_ARG"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.5 <7.0.0",
|
||||
@ -66,7 +82,7 @@
|
||||
"core-js": "^2.4.1",
|
||||
"jasmine": "^2.6.0",
|
||||
"ng-pwa-tools": "^0.0.10",
|
||||
"rxjs": "^5.2.0",
|
||||
"rxjs": "^5.4.3",
|
||||
"tslib": "^1.7.1",
|
||||
"web-animations-js": "^2.2.5",
|
||||
"zone.js": "^0.8.16"
|
||||
@ -78,11 +94,12 @@
|
||||
"@types/node": "~6.0.60",
|
||||
"archiver": "^1.3.0",
|
||||
"canonical-path": "^0.0.2",
|
||||
"chalk": "^2.1.0",
|
||||
"codelyzer": "~2.0.0",
|
||||
"concurrently": "^3.4.0",
|
||||
"cross-spawn": "^5.1.0",
|
||||
"dgeni": "^0.4.7",
|
||||
"dgeni-packages": "^0.21.3",
|
||||
"dgeni-packages": "0.22.0",
|
||||
"entities": "^1.1.1",
|
||||
"eslint": "^3.19.0",
|
||||
"eslint-plugin-jasmine": "^2.2.0",
|
||||
@ -117,7 +134,7 @@
|
||||
"semver": "^5.3.0",
|
||||
"shelljs": "^0.7.7",
|
||||
"tree-kill": "^1.1.0",
|
||||
"ts-node": "~2.0.0",
|
||||
"ts-node": "^3.3.0",
|
||||
"tslint": "~4.5.0",
|
||||
"typescript": "2.3.2",
|
||||
"uglify-js": "^3.0.15",
|
||||
|
@ -12,7 +12,8 @@ exports.config = {
|
||||
browserName: 'chrome',
|
||||
// For Travis
|
||||
chromeOptions: {
|
||||
binary: process.env.CHROME_BIN
|
||||
binary: process.env.CHROME_BIN,
|
||||
args: ['--no-sandbox']
|
||||
}
|
||||
},
|
||||
directConnect: true,
|
||||
|
@ -3,7 +3,7 @@
|
||||
set -u -e -o pipefail
|
||||
|
||||
declare -A limitUncompressed
|
||||
limitUncompressed=(["inline"]=1600 ["main"]=525000 ["polyfills"]=38000)
|
||||
limitUncompressed=(["inline"]=1600 ["main"]=525500 ["polyfills"]=38000)
|
||||
declare -A limitGzip7
|
||||
limitGzip7=(["inline"]=1000 ["main"]=127000 ["polyfills"]=12500)
|
||||
declare -A limitGzip9
|
||||
|
@ -51,6 +51,6 @@ readonly relevantChangedFilesCount=$(git diff --name-only $TRAVIS_COMMIT_RANGE |
|
||||
# Run PWA-score tests (unless the deployment is not public yet;
|
||||
# i.e. it could not be automatically verified).
|
||||
if [[ $httpCode -ne 202 ]] && [[ "$isHidden" != "true" ]]; then
|
||||
yarn test-pwa-score -- "$DEPLOYED_URL" "$MIN_PWA_SCORE"
|
||||
yarn test-pwa-score "$DEPLOYED_URL" "$MIN_PWA_SCORE"
|
||||
fi
|
||||
)
|
||||
|
@ -67,7 +67,7 @@ case $deployEnv in
|
||||
readonly firebaseToken=$FIREBASE_TOKEN
|
||||
;;
|
||||
archive)
|
||||
readonly projectId=angular-io-${majorVersion}
|
||||
readonly projectId=v${majorVersion}-angular-io
|
||||
readonly deployedUrl=https://v${majorVersion}.angular.io/
|
||||
readonly firebaseToken=$FIREBASE_TOKEN
|
||||
;;
|
||||
@ -87,7 +87,7 @@ fi
|
||||
cd "`dirname $0`/.."
|
||||
|
||||
# Build the app
|
||||
yarn build -- --env=$deployEnv
|
||||
yarn build --env=$deployEnv
|
||||
|
||||
# Include any mode-specific files
|
||||
cp -rf src/extra-files/$deployEnv/. dist/
|
||||
@ -100,5 +100,5 @@ fi
|
||||
firebase deploy --message "Commit: $TRAVIS_COMMIT" --non-interactive --token "$firebaseToken"
|
||||
|
||||
# Run PWA-score tests
|
||||
yarn test-pwa-score -- "$deployedUrl" "$MIN_PWA_SCORE"
|
||||
yarn test-pwa-score "$deployedUrl" "$MIN_PWA_SCORE"
|
||||
)
|
||||
|
@ -94,7 +94,7 @@ Deployment URL : https://angular.io/"
|
||||
)
|
||||
expected="Git branch : 2.4.x
|
||||
Build/deploy mode : archive
|
||||
Firebase project : angular-io-2
|
||||
Firebase project : v2-angular-io
|
||||
Deployment URL : https://v2.angular.io/"
|
||||
check "$actual" "$expected"
|
||||
)
|
||||
|
@ -17,8 +17,16 @@ const printer = require('lighthouse/lighthouse-cli/printer');
|
||||
const config = require('lighthouse/lighthouse-core/config/default.js');
|
||||
|
||||
// Constants
|
||||
const CHROME_LAUNCH_OPTS = {};
|
||||
const VIEWER_URL = 'https://googlechrome.github.io/lighthouse/viewer/';
|
||||
|
||||
|
||||
// Specify the path and flags for Chrome on Travis
|
||||
if (process.env.TRAVIS) {
|
||||
process.env.LIGHTHOUSE_CHROMIUM_PATH = process.env.CHROME_BIN;
|
||||
CHROME_LAUNCH_OPTS.chromeFlags = ['--no-sandbox'];
|
||||
}
|
||||
|
||||
// Run
|
||||
_main(process.argv.slice(2));
|
||||
|
||||
@ -66,7 +74,7 @@ function ignoreHttpsAudits(config) {
|
||||
}
|
||||
|
||||
function launchChromeAndRunLighthouse(url, flags, config) {
|
||||
return chromeLauncher.launch().then(chrome => {
|
||||
return chromeLauncher.launch(CHROME_LAUNCH_OPTS).then(chrome => {
|
||||
flags.port = chrome.port;
|
||||
return lighthouse(url, flags, config).
|
||||
then(results => chrome.kill().then(() => results)).
|
||||
|
@ -13,7 +13,7 @@
|
||||
<aio-top-menu *ngIf="isSideBySide" [nodes]="topMenuNodes"></aio-top-menu>
|
||||
<aio-search-box class="search-container" #searchBox (onSearch)="doSearch($event)" (onFocus)="doSearch($event)"></aio-search-box>
|
||||
</md-toolbar>
|
||||
<aio-search-results #searchResults *ngIf="showSearchResults" (resultSelected)="hideSearchResults()"></aio-search-results>
|
||||
<aio-search-results #searchResultsView *ngIf="showSearchResults" [searchResults]="searchResults | async" (resultSelected)="hideSearchResults()"></aio-search-results>
|
||||
|
||||
<md-sidenav-container class="sidenav-container" [class.starting]="isStarting" [class.has-floating-toc]="hasFloatingToc" role="main">
|
||||
|
||||
|
@ -1,12 +1,11 @@
|
||||
import { NO_ERRORS_SCHEMA, DebugElement } from '@angular/core';
|
||||
import { async, inject, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
|
||||
import { inject, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { APP_BASE_HREF } from '@angular/common';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { MdProgressBar, MdSidenav } from '@angular/material';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
import { of } from 'rxjs/observable/of';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
@ -22,9 +21,9 @@ import { MockSearchService } from 'testing/search.service';
|
||||
import { NavigationNode } from 'app/navigation/navigation.service';
|
||||
import { ScrollService } from 'app/shared/scroll.service';
|
||||
import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
|
||||
import { SearchResultsComponent } from 'app/search/search-results/search-results.component';
|
||||
import { SearchResultsComponent } from 'app/shared/search-results/search-results.component';
|
||||
import { SearchService } from 'app/search/search.service';
|
||||
import { SelectComponent, Option } from 'app/shared/select/select.component';
|
||||
import { SelectComponent } from 'app/shared/select/select.component';
|
||||
import { TocComponent } from 'app/embedded/toc/toc.component';
|
||||
import { TocItem, TocService } from 'app/shared/toc.service';
|
||||
|
||||
@ -1054,11 +1053,6 @@ class TestGaService {
|
||||
locationChanged = jasmine.createSpy('locationChanged');
|
||||
}
|
||||
|
||||
class TestSearchService {
|
||||
initWorker = jasmine.createSpy('initWorker');
|
||||
loadIndex = jasmine.createSpy('loadIndex');
|
||||
}
|
||||
|
||||
class TestHttpClient {
|
||||
|
||||
static versionInfo = {
|
||||
|
@ -2,18 +2,18 @@ import { Component, ElementRef, HostBinding, HostListener, OnInit,
|
||||
QueryList, ViewChild, ViewChildren } from '@angular/core';
|
||||
import { MdSidenav } from '@angular/material';
|
||||
|
||||
import { CurrentNodes, NavigationService, NavigationViews, NavigationNode, VersionInfo } from 'app/navigation/navigation.service';
|
||||
import { CurrentNodes, NavigationService, NavigationNode, VersionInfo } from 'app/navigation/navigation.service';
|
||||
import { DocumentService, DocumentContents } from 'app/documents/document.service';
|
||||
import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component';
|
||||
import { Deployment } from 'app/shared/deployment.service';
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
import { NavMenuComponent } from 'app/layout/nav-menu/nav-menu.component';
|
||||
import { ScrollService } from 'app/shared/scroll.service';
|
||||
import { SearchResultsComponent } from 'app/search/search-results/search-results.component';
|
||||
import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
|
||||
import { SearchResults } from 'app/search/interfaces';
|
||||
import { SearchService } from 'app/search/search.service';
|
||||
import { TocService } from 'app/shared/toc.service';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
import { combineLatest } from 'rxjs/observable/combineLatest';
|
||||
|
||||
@ -89,10 +89,9 @@ export class AppComponent implements OnInit {
|
||||
|
||||
// Search related properties
|
||||
showSearchResults = false;
|
||||
@ViewChildren('searchBox, searchResults', { read: ElementRef })
|
||||
searchResults: Observable<SearchResults>;
|
||||
@ViewChildren('searchBox, searchResultsView', { read: ElementRef })
|
||||
searchElements: QueryList<ElementRef>;
|
||||
@ViewChild(SearchResultsComponent)
|
||||
searchResults: SearchResultsComponent;
|
||||
@ViewChild(SearchBoxComponent)
|
||||
searchBox: SearchBoxComponent;
|
||||
|
||||
@ -332,7 +331,7 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
|
||||
doSearch(query) {
|
||||
this.searchService.search(query);
|
||||
this.searchResults = this.searchService.search(query);
|
||||
this.showSearchResults = !!query;
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,6 @@ import { NavMenuComponent } from 'app/layout/nav-menu/nav-menu.component';
|
||||
import { NavItemComponent } from 'app/layout/nav-item/nav-item.component';
|
||||
import { ScrollService } from 'app/shared/scroll.service';
|
||||
import { ScrollSpyService } from 'app/shared/scroll-spy.service';
|
||||
import { SearchResultsComponent } from './search/search-results/search-results.component';
|
||||
import { SearchBoxComponent } from './search/search-box/search-box.component';
|
||||
import { TocService } from 'app/shared/toc.service';
|
||||
|
||||
@ -95,7 +94,6 @@ export const svgIconProviders = [
|
||||
ModeBannerComponent,
|
||||
NavMenuComponent,
|
||||
NavItemComponent,
|
||||
SearchResultsComponent,
|
||||
SearchBoxComponent,
|
||||
TopMenuComponent,
|
||||
],
|
||||
|
@ -1,10 +1,7 @@
|
||||
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
|
||||
import { Injector } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
import { MockLocationService } from 'testing/location.service';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
|
||||
import { ApiListComponent } from './api-list.component';
|
||||
|
@ -13,7 +13,7 @@ import { ReplaySubject } from 'rxjs/ReplaySubject';
|
||||
import { combineLatest } from 'rxjs/observable/combineLatest';
|
||||
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
import { ApiItem, ApiSection, ApiService } from './api.service';
|
||||
import { ApiSection, ApiService } from './api.service';
|
||||
|
||||
import { Option } from 'app/shared/select/select.component';
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
|
||||
import { Injector } from '@angular/core';
|
||||
import { TestBed, inject } from '@angular/core/testing';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
|
||||
|
@ -1,11 +1,9 @@
|
||||
import { Component, ElementRef, ViewChild, OnChanges, OnDestroy, Input } from '@angular/core';
|
||||
import { Component, ElementRef, ViewChild, OnChanges, Input } from '@angular/core';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
import { PrettyPrinter } from './pretty-printer.service';
|
||||
import { CopierService } from 'app/shared/copier.service';
|
||||
import { MdSnackBar } from '@angular/material';
|
||||
|
||||
const originalLabel = 'Copy Code';
|
||||
const copiedLabel = 'Copied!';
|
||||
const defaultLineNumsCount = 10; // by default, show linenums over this number
|
||||
|
||||
/**
|
||||
|
@ -3,7 +3,7 @@ import { Injector } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ContributorService } from './contributor.service';
|
||||
import { Contributor, ContributorGroup } from './contributors.model';
|
||||
import { ContributorGroup } from './contributors.model';
|
||||
|
||||
describe('ContributorService', () => {
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
import { MockLocationService } from 'testing/location.service';
|
||||
import { CurrentLocationComponent } from './current-location.component';
|
||||
|
@ -20,6 +20,7 @@ import { CodeTabsComponent } from './code/code-tabs.component';
|
||||
import { ContributorListComponent } from './contributor/contributor-list.component';
|
||||
import { ContributorComponent } from './contributor/contributor.component';
|
||||
import { CurrentLocationComponent } from './current-location.component';
|
||||
import { FileNotFoundSearchComponent } from './search/file-not-found-search.component';
|
||||
import { LiveExampleComponent, EmbeddedPlunkerComponent } from './live-example/live-example.component';
|
||||
import { ResourceListComponent } from './resource/resource-list.component';
|
||||
import { ResourceService } from './resource/resource.service';
|
||||
@ -30,7 +31,8 @@ import { TocComponent } from './toc/toc.component';
|
||||
*/
|
||||
export const embeddedComponents: any[] = [
|
||||
ApiListComponent, CodeExampleComponent, CodeTabsComponent, ContributorListComponent,
|
||||
CurrentLocationComponent, LiveExampleComponent, ResourceListComponent, TocComponent
|
||||
CurrentLocationComponent, FileNotFoundSearchComponent, LiveExampleComponent, ResourceListComponent,
|
||||
TocComponent
|
||||
];
|
||||
|
||||
/** Injectable class w/ property returning components that can be embedded in docs */
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Component, DebugElement, ElementRef } from '@angular/core';
|
||||
import { Component, DebugElement } from '@angular/core';
|
||||
import { Location } from '@angular/common';
|
||||
|
||||
import { LiveExampleComponent, EmbeddedPlunkerComponent } from './live-example.component';
|
||||
@ -71,7 +71,6 @@ describe('LiveExampleComponent', () => {
|
||||
|
||||
describe('when not embedded', () => {
|
||||
function getLiveExampleAnchor() { return getAnchors()[0]; }
|
||||
function getDownloadAnchor() { return getAnchors()[1]; }
|
||||
|
||||
it('should create LiveExampleComponent', () => {
|
||||
testComponent(() => {
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { ReflectiveInjector } from '@angular/core';
|
||||
import { PlatformLocation } from '@angular/common';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { of } from 'rxjs/observable/of';
|
||||
|
||||
import { ResourceListComponent } from './resource-list.component';
|
||||
|
@ -3,7 +3,7 @@ import { Injector } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ResourceService } from './resource.service';
|
||||
import { Category, SubCategory, Resource } from './resource.model';
|
||||
import { Category } from './resource.model';
|
||||
|
||||
describe('ResourceService', () => {
|
||||
|
||||
|
@ -0,0 +1,49 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
import { MockLocationService } from 'testing/location.service';
|
||||
import { SearchResults } from 'app/search/interfaces';
|
||||
import { SearchResultsComponent } from 'app/shared/search-results/search-results.component';
|
||||
import { SearchService } from 'app/search/search.service';
|
||||
import { FileNotFoundSearchComponent } from './file-not-found-search.component';
|
||||
|
||||
|
||||
describe('FileNotFoundSearchComponent', () => {
|
||||
let element: HTMLElement;
|
||||
let fixture: ComponentFixture<FileNotFoundSearchComponent>;
|
||||
let searchService: SearchService;
|
||||
let searchResultSubject: Subject<SearchResults>;
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ FileNotFoundSearchComponent, SearchResultsComponent ],
|
||||
providers: [
|
||||
{ provide: LocationService, useValue: new MockLocationService('base/initial-url?some-query') },
|
||||
SearchService
|
||||
]
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(FileNotFoundSearchComponent);
|
||||
searchService = TestBed.get(SearchService);
|
||||
searchResultSubject = new Subject<SearchResults>();
|
||||
spyOn(searchService, 'search').and.callFake(() => searchResultSubject.asObservable());
|
||||
fixture.detectChanges();
|
||||
element = fixture.nativeElement;
|
||||
});
|
||||
|
||||
it('should run a search with a query built from the current url', () => {
|
||||
expect(searchService.search).toHaveBeenCalledWith('base initial url');
|
||||
});
|
||||
|
||||
it('should pass through any results to the `aio-search-results` component', () => {
|
||||
const searchResultsComponent = fixture.debugElement.query(By.directive(SearchResultsComponent)).componentInstance;
|
||||
expect(searchResultsComponent.searchResults).toBe(null);
|
||||
|
||||
const results = { query: 'base initial url', results: []};
|
||||
searchResultSubject.next(results);
|
||||
fixture.detectChanges();
|
||||
expect(searchResultsComponent.searchResults).toEqual(results);
|
||||
});
|
||||
});
|
@ -0,0 +1,23 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
import { SearchResults } from 'app/search/interfaces';
|
||||
import { SearchService } from 'app/search/search.service';
|
||||
|
||||
@Component({
|
||||
selector: 'aio-file-not-found-search',
|
||||
template:
|
||||
`<p>Let's see if any of these search results help...</p>
|
||||
<aio-search-results class="embedded" [searchResults]="searchResults | async"></aio-search-results>`
|
||||
})
|
||||
export class FileNotFoundSearchComponent implements OnInit {
|
||||
searchResults: Observable<SearchResults>;
|
||||
constructor(private location: LocationService, private search: SearchService) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.searchResults = this.location.currentPath.switchMap(path => {
|
||||
const query = path.split(/\W+/).join(' ');
|
||||
return this.search.search(query);
|
||||
});
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { Component, CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By, DOCUMENT } from '@angular/platform-browser';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
import { asap } from 'rxjs/scheduler/asap';
|
||||
|
||||
|
@ -1,11 +1,8 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import {
|
||||
Component, ComponentFactoryResolver, DebugElement,
|
||||
ElementRef, Injector, NgModule, OnInit, ViewChild } from '@angular/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { Component, DebugElement, ElementRef, NgModule, OnInit, ViewChild } from '@angular/core';
|
||||
import { DocViewerComponent } from './doc-viewer.component';
|
||||
import { DocumentContents } from 'app/documents/document.service';
|
||||
import { EmbeddedModule, embeddedComponents, EmbeddedComponents } from 'app/embedded/embedded.module';
|
||||
import { EmbeddedModule, EmbeddedComponents } from 'app/embedded/embedded.module';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { TocService } from 'app/shared/toc.service';
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {
|
||||
Component, ComponentFactory, ComponentFactoryResolver, ComponentRef,
|
||||
DoCheck, ElementRef, EventEmitter, Injector, Input, OnDestroy,
|
||||
Output, ViewEncapsulation
|
||||
Output
|
||||
} from '@angular/core';
|
||||
|
||||
import { EmbeddedComponents } from 'app/embedded/embedded.module';
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { SimpleChange, SimpleChanges, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
|
||||
import { NavItemComponent } from './nav-item.component';
|
||||
import { NavigationNode } from 'app/navigation/navigation.model';
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
|
||||
import { TopMenuComponent } from './top-menu.component';
|
||||
import { NavigationService, NavigationViews, NavigationNode } from 'app/navigation/navigation.service';
|
||||
import { NavigationService, NavigationViews } from 'app/navigation/navigation.service';
|
||||
|
||||
describe('TopMenuComponent', () => {
|
||||
let component: TopMenuComponent;
|
||||
|
@ -2,7 +2,6 @@ import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { AsyncSubject } from 'rxjs/AsyncSubject';
|
||||
import { combineLatest } from 'rxjs/observable/combineLatest';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/operator/publishLast';
|
||||
|
19
aio/src/app/search/interfaces.ts
Normal file
19
aio/src/app/search/interfaces.ts
Normal file
@ -0,0 +1,19 @@
|
||||
export interface SearchResults {
|
||||
query: string;
|
||||
results: SearchResult[];
|
||||
}
|
||||
|
||||
export interface SearchResult {
|
||||
path: string;
|
||||
title: string;
|
||||
type: string;
|
||||
titleWords: string;
|
||||
keywords: string;
|
||||
}
|
||||
|
||||
export interface SearchArea {
|
||||
name: string;
|
||||
pages: SearchResult[];
|
||||
priorityPages: SearchResult[];
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ import { Component } from '@angular/core';
|
||||
import { ComponentFixture, fakeAsync, inject, TestBed, tick } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { SearchBoxComponent } from './search-box.component';
|
||||
import { MockSearchService } from 'testing/search.service';
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
import { MockLocationService } from 'testing/location.service';
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { ReflectiveInjector, NgZone } from '@angular/core';
|
||||
import { fakeAsync, tick } from '@angular/core/testing';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import 'rxjs/add/observable/of';
|
||||
import { SearchService } from './search.service';
|
||||
import { WebWorkerClient } from 'app/shared/web-worker';
|
||||
|
||||
@ -36,27 +37,29 @@ describe('SearchService', () => {
|
||||
|
||||
describe('search', () => {
|
||||
beforeEach(() => {
|
||||
// We must initialize the service before calling search
|
||||
service.initWorker('some/url', 100);
|
||||
// We must initialize the service before calling connectSearches
|
||||
service.initWorker('some/url', 1000);
|
||||
// Simulate the index being ready so that searches get sent to the worker
|
||||
(service as any).ready = Observable.of(true);
|
||||
});
|
||||
|
||||
it('should trigger a `loadIndex` synchronously', () => {
|
||||
service.search('some query');
|
||||
it('should trigger a `loadIndex` synchronously (not waiting for the delay)', () => {
|
||||
expect(mockWorker.sendMessage).not.toHaveBeenCalled();
|
||||
service.search('some query').subscribe();
|
||||
expect(mockWorker.sendMessage).toHaveBeenCalledWith('load-index');
|
||||
});
|
||||
|
||||
it('should send a "query-index" message to the worker', () => {
|
||||
service.search('some query');
|
||||
service.search('some query').subscribe();
|
||||
expect(mockWorker.sendMessage).toHaveBeenCalledWith('query-index', 'some query');
|
||||
});
|
||||
|
||||
it('should push the response to the `searchResults` observable', () => {
|
||||
it('should push the response to the returned observable', () => {
|
||||
const mockSearchResults = { results: ['a', 'b'] };
|
||||
let actualSearchResults;
|
||||
(mockWorker.sendMessage as jasmine.Spy).and.returnValue(Observable.of(mockSearchResults));
|
||||
let searchResults: any;
|
||||
service.searchResults.subscribe(results => searchResults = results);
|
||||
service.search('some query');
|
||||
expect(searchResults).toEqual(mockSearchResults);
|
||||
service.search('some query').subscribe(results => actualSearchResults = results);
|
||||
expect(actualSearchResults).toEqual(mockSearchResults);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -4,34 +4,21 @@ Use of this source code is governed by an MIT-style license that
|
||||
can be found in the LICENSE file at http://angular.io/license
|
||||
*/
|
||||
|
||||
import { NgZone, Injectable, Type } from '@angular/core';
|
||||
import { NgZone, Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { ReplaySubject } from 'rxjs/ReplaySubject';
|
||||
import 'rxjs/add/observable/race';
|
||||
import 'rxjs/add/observable/timer';
|
||||
import 'rxjs/add/operator/concatMap';
|
||||
import 'rxjs/add/operator/publish';
|
||||
import 'rxjs/add/operator/publishReplay';
|
||||
import { WebWorkerClient } from 'app/shared/web-worker';
|
||||
|
||||
export interface SearchResults {
|
||||
query: string;
|
||||
results: SearchResult[];
|
||||
}
|
||||
|
||||
export interface SearchResult {
|
||||
path: string;
|
||||
title: string;
|
||||
type: string;
|
||||
titleWords: string;
|
||||
keywords: string;
|
||||
}
|
||||
|
||||
import { SearchResults } from 'app/search/interfaces';
|
||||
|
||||
@Injectable()
|
||||
export class SearchService {
|
||||
private ready: Observable<boolean>;
|
||||
private searchesSubject = new ReplaySubject<string>(1);
|
||||
searchResults: Observable<SearchResults>;
|
||||
|
||||
private worker: WebWorkerClient;
|
||||
constructor(private zone: NgZone) {}
|
||||
|
||||
/**
|
||||
@ -43,36 +30,32 @@ export class SearchService {
|
||||
* @param initDelay the number of milliseconds to wait before we load the WebWorker and generate the search index
|
||||
*/
|
||||
initWorker(workerUrl: string, initDelay: number) {
|
||||
const searchResults = Observable
|
||||
const ready = this.ready = Observable
|
||||
// Wait for the initDelay or the first search
|
||||
.race(
|
||||
.race<any>(
|
||||
Observable.timer(initDelay),
|
||||
this.searchesSubject.first()
|
||||
(this.searchesSubject as Observable<string>).first()
|
||||
)
|
||||
.concatMap(() => {
|
||||
// Create the worker and load the index
|
||||
const worker = WebWorkerClient.create(workerUrl, this.zone);
|
||||
return worker.sendMessage('load-index').concatMap(() =>
|
||||
// Once the index has loaded, switch to listening to the searches coming in
|
||||
this.searchesSubject.switchMap((query) =>
|
||||
// Each search gets switched to a web worker message, whose results are returned via an observable
|
||||
worker.sendMessage<SearchResults>('query-index', query)
|
||||
)
|
||||
);
|
||||
}).publish();
|
||||
this.worker = WebWorkerClient.create(workerUrl, this.zone);
|
||||
return this.worker.sendMessage<boolean>('load-index');
|
||||
}).publishReplay(1);
|
||||
|
||||
// Connect to the observable to kick off the timer
|
||||
searchResults.connect();
|
||||
|
||||
// Expose the connected observable to the rest of the world
|
||||
this.searchResults = searchResults;
|
||||
// Connect to the observable to kick off the timer
|
||||
ready.connect();
|
||||
return ready;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a search query to the index.
|
||||
* The results will appear on the `searchResults` observable.
|
||||
* Search the index using the given query and emit results on the observable that is returned.
|
||||
* @param query The query to run against the index.
|
||||
* @returns an observable collection of search results
|
||||
*/
|
||||
search(query: string) {
|
||||
search(query: string): Observable<SearchResults> {
|
||||
// Trigger the searches subject to override the init delay timer
|
||||
this.searchesSubject.next(query);
|
||||
// Once the index has loaded, switch to listening to the searches coming in.
|
||||
return this.ready.concatMap(() => this.worker.sendMessage<SearchResults>('query-index', query));
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { MdIconRegistry } from '@angular/material';
|
||||
import { CustomMdIconRegistry, SVG_ICONS, SvgIconInfo } from './custom-md-icon-registry';
|
||||
import { CustomMdIconRegistry, SvgIconInfo } from './custom-md-icon-registry';
|
||||
|
||||
describe('CustomMdIconRegistry', () => {
|
||||
it('should get the SVG element for a preloaded icon from the cache', () => {
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Location, PlatformLocation } from '@angular/common';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { ReplaySubject } from 'rxjs/ReplaySubject';
|
||||
import 'rxjs/add/operator/do';
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { fakeAsync, tick } from '@angular/core/testing';
|
||||
import { DOCUMENT } from '@angular/platform-browser';
|
||||
|
||||
import { ScrollService } from 'app/shared/scroll.service';
|
||||
import { ScrollItem, ScrollSpiedElement, ScrollSpiedElementGroup, ScrollSpyInfo, ScrollSpyService } from 'app/shared/scroll-spy.service';
|
||||
import { ScrollItem, ScrollSpiedElement, ScrollSpiedElementGroup, ScrollSpyService } from 'app/shared/scroll-spy.service';
|
||||
|
||||
|
||||
describe('ScrollSpiedElement', () => {
|
||||
@ -197,6 +197,7 @@ describe('ScrollSpyService', () => {
|
||||
.and.callFake(() => actions.push('calibrate'));
|
||||
|
||||
expect(onResizeSpy).not.toHaveBeenCalled();
|
||||
expect(calibrateSpy).not.toHaveBeenCalled();
|
||||
|
||||
scrollSpyService.spyOn([]);
|
||||
expect(actions).toEqual(['onResize', 'calibrate']);
|
||||
|
@ -1,16 +1,12 @@
|
||||
import { DebugElement } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import { SearchService, SearchResult, SearchResults } from '../search.service';
|
||||
import { SearchResultsComponent, SearchArea } from './search-results.component';
|
||||
import { MockSearchService } from 'testing/search.service';
|
||||
import { SearchResult } from 'app/search/interfaces';
|
||||
import { SearchResultsComponent } from './search-results.component';
|
||||
|
||||
describe('SearchResultsComponent', () => {
|
||||
let component: SearchResultsComponent;
|
||||
let fixture: ComponentFixture<SearchResultsComponent>;
|
||||
let searchResults: Subject<SearchResults>;
|
||||
|
||||
/** Get all text from component element */
|
||||
function getText() { return fixture.debugElement.nativeElement.textContent; }
|
||||
@ -38,27 +34,26 @@ describe('SearchResultsComponent', () => {
|
||||
return l.title.toUpperCase() > r.title.toUpperCase() ? 1 : -1;
|
||||
}
|
||||
|
||||
function setSearchResults(query: string, results: SearchResult[]) {
|
||||
component.searchResults = {query, results};
|
||||
component.ngOnChanges({});
|
||||
fixture.detectChanges();
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ SearchResultsComponent ],
|
||||
providers: [
|
||||
{ provide: SearchService, useFactory: () => new MockSearchService() }
|
||||
]
|
||||
declarations: [ SearchResultsComponent ]
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SearchResultsComponent);
|
||||
component = fixture.componentInstance;
|
||||
searchResults = TestBed.get(SearchService).searchResults;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should map the search results into groups based on their containing folder', () => {
|
||||
const results = getTestResults(3);
|
||||
|
||||
searchResults.next({ query: '', results: results});
|
||||
setSearchResults('', getTestResults(3));
|
||||
expect(component.searchAreas).toEqual([
|
||||
{ name: 'api', priorityPages: [
|
||||
{ path: 'api/d', title: 'API D', type: '', keywords: '', titleWords: '' }
|
||||
@ -71,10 +66,10 @@ describe('SearchResultsComponent', () => {
|
||||
});
|
||||
|
||||
it('should special case results that are top level folders', () => {
|
||||
searchResults.next({ query: '', results: [
|
||||
setSearchResults('', [
|
||||
{ path: 'tutorial', title: 'Tutorial index', type: '', keywords: '', titleWords: '' },
|
||||
{ path: 'tutorial/toh-pt1', title: 'Tutorial - part 1', type: '', keywords: '', titleWords: '' },
|
||||
]});
|
||||
]);
|
||||
expect(component.searchAreas).toEqual([
|
||||
{ name: 'tutorial', priorityPages: [
|
||||
{ path: 'tutorial', title: 'Tutorial index', type: '', keywords: '', titleWords: '' },
|
||||
@ -85,21 +80,21 @@ describe('SearchResultsComponent', () => {
|
||||
|
||||
it('should put first 5 results for each area into priorityPages', () => {
|
||||
const results = getTestResults();
|
||||
searchResults.next({ query: '', results: results });
|
||||
setSearchResults('', results);
|
||||
expect(component.searchAreas[0].priorityPages).toEqual(results.filter(p => p.path.startsWith('api')).slice(0, 5));
|
||||
expect(component.searchAreas[1].priorityPages).toEqual(results.filter(p => p.path.startsWith('guide')).slice(0, 5));
|
||||
});
|
||||
|
||||
it('should put the nonPriorityPages into the pages array, sorted by title', () => {
|
||||
const results = getTestResults();
|
||||
searchResults.next({ query: '', results: results });
|
||||
setSearchResults('', results);
|
||||
expect(component.searchAreas[0].pages).toEqual([]);
|
||||
expect(component.searchAreas[1].pages).toEqual(results.filter(p => p.path.startsWith('guide')).slice(5).sort(compareTitle));
|
||||
});
|
||||
|
||||
it('should put a total count in the header of each area of search results', () => {
|
||||
const results = getTestResults();
|
||||
searchResults.next({ query: '', results: results });
|
||||
setSearchResults('', results);
|
||||
fixture.detectChanges();
|
||||
const headers = fixture.debugElement.queryAll(By.css('h3'));
|
||||
expect(headers.length).toEqual(2);
|
||||
@ -112,7 +107,7 @@ describe('SearchResultsComponent', () => {
|
||||
{ path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '' }
|
||||
];
|
||||
|
||||
searchResults.next({ query: '', results: results });
|
||||
setSearchResults('', results);
|
||||
expect(component.searchAreas).toEqual([
|
||||
{ name: 'other', priorityPages: [
|
||||
{ path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '' }
|
||||
@ -125,7 +120,7 @@ describe('SearchResultsComponent', () => {
|
||||
{ path: 'news', title: undefined, type: 'marketing', keywords: '', titleWords: '' }
|
||||
];
|
||||
|
||||
searchResults.next({ query: 'something', results: results });
|
||||
setSearchResults('something', results);
|
||||
expect(component.searchAreas).toEqual([]);
|
||||
});
|
||||
|
||||
@ -144,7 +139,7 @@ describe('SearchResultsComponent', () => {
|
||||
|
||||
selected = null;
|
||||
searchResult = { path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '' };
|
||||
searchResults.next({ query: 'something', results: [searchResult] });
|
||||
setSearchResults('something', [searchResult]);
|
||||
|
||||
fixture.detectChanges();
|
||||
anchor = fixture.debugElement.query(By.css('a'));
|
||||
@ -179,10 +174,8 @@ describe('SearchResultsComponent', () => {
|
||||
|
||||
describe('when no query results', () => {
|
||||
it('should display "not found" message', () => {
|
||||
searchResults.next({ query: 'something', results: [] });
|
||||
fixture.detectChanges();
|
||||
setSearchResults('something', []);
|
||||
expect(getText()).toContain('No results');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
@ -1,28 +1,20 @@
|
||||
import { Component, EventEmitter, HostListener, OnDestroy, OnInit, Output } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
import { SearchResult, SearchResults, SearchService } from '../search.service';
|
||||
|
||||
export interface SearchArea {
|
||||
name: string;
|
||||
pages: SearchResult[];
|
||||
priorityPages: SearchResult[];
|
||||
}
|
||||
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
|
||||
import { SearchResult, SearchResults, SearchArea } from 'app/search/interfaces';
|
||||
|
||||
/**
|
||||
* A component to display the search results
|
||||
* A component to display search results in groups
|
||||
*/
|
||||
@Component({
|
||||
selector: 'aio-search-results',
|
||||
templateUrl: './search-results.component.html',
|
||||
})
|
||||
export class SearchResultsComponent implements OnInit, OnDestroy {
|
||||
export class SearchResultsComponent implements OnChanges {
|
||||
|
||||
private resultsSubscription: Subscription;
|
||||
readonly defaultArea = 'other';
|
||||
notFoundMessage = 'Searching ...';
|
||||
readonly topLevelFolders = ['guide', 'tutorial'];
|
||||
/**
|
||||
* The results to display
|
||||
*/
|
||||
@Input()
|
||||
searchResults: SearchResults;
|
||||
|
||||
/**
|
||||
* Emitted when the user selects a search result
|
||||
@ -30,20 +22,13 @@ export class SearchResultsComponent implements OnInit, OnDestroy {
|
||||
@Output()
|
||||
resultSelected = new EventEmitter<SearchResult>();
|
||||
|
||||
/**
|
||||
* A mapping of the search results grouped into areas
|
||||
*/
|
||||
readonly defaultArea = 'other';
|
||||
notFoundMessage = 'Searching ...';
|
||||
readonly topLevelFolders = ['guide', 'tutorial'];
|
||||
searchAreas: SearchArea[] = [];
|
||||
|
||||
constructor(private searchService: SearchService) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.resultsSubscription = this.searchService.searchResults
|
||||
.subscribe(search => this.searchAreas = this.processSearchResults(search));
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.resultsSubscription.unsubscribe();
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
this.searchAreas = this.processSearchResults(this.searchResults);
|
||||
}
|
||||
|
||||
onResultSelected(page: SearchResult, event: MouseEvent) {
|
||||
@ -55,6 +40,9 @@ export class SearchResultsComponent implements OnInit, OnDestroy {
|
||||
|
||||
// Map the search results into groups by area
|
||||
private processSearchResults(search: SearchResults) {
|
||||
if (!search) {
|
||||
return [];
|
||||
}
|
||||
this.notFoundMessage = 'No results found.';
|
||||
const searchAreaMap = {};
|
||||
search.results.forEach(result => {
|
||||
@ -84,6 +72,6 @@ export class SearchResultsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
function compareResults(l: {title: string}, r: {title: string}) {
|
||||
function compareResults(l: SearchResult, r: SearchResult) {
|
||||
return l.title.toUpperCase() > r.title.toUpperCase() ? 1 : -1;
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import { Component, DebugElement } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed, inject } from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { SelectComponent, Option } from './select.component';
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { SearchResultsComponent } from './search-results/search-results.component';
|
||||
import { SelectComponent } from './select/select.component';
|
||||
|
||||
@NgModule({
|
||||
@ -7,9 +8,11 @@ import { SelectComponent } from './select/select.component';
|
||||
CommonModule
|
||||
],
|
||||
exports: [
|
||||
SearchResultsComponent,
|
||||
SelectComponent
|
||||
],
|
||||
declarations: [
|
||||
SearchResultsComponent,
|
||||
SelectComponent
|
||||
]
|
||||
})
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ReflectiveInjector, SecurityContext } from '@angular/core';
|
||||
import { ReflectiveInjector } from '@angular/core';
|
||||
import { DOCUMENT, DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
|
||||
@ -268,7 +268,6 @@ describe('TocService', () => {
|
||||
});
|
||||
|
||||
it('should calculate and set id of heading without an id', () => {
|
||||
const tocItem = lastTocList.find(item => item.title === 'H2 Two');
|
||||
const id = headings[2].getAttribute('id');
|
||||
expect(id).toEqual('h2-two');
|
||||
});
|
||||
|
@ -4,7 +4,7 @@ Use of this source code is governed by an MIT-style license that
|
||||
can be found in the LICENSE file at http://angular.io/license
|
||||
*/
|
||||
|
||||
import {NgZone, Injectable} from '@angular/core';
|
||||
import {NgZone} from '@angular/core';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
|
||||
export interface WebWorkerMessage {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable, OnDestroy } from '@angular/core';
|
||||
import { Injectable, OnDestroy } from '@angular/core';
|
||||
import { NgServiceWorker } from '@angular/service-worker';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
|
@ -29,6 +29,24 @@ aio-search-results {
|
||||
}
|
||||
}
|
||||
|
||||
aio-search-results.embedded .search-results {
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
width: auto;
|
||||
max-height: 100%;
|
||||
position: relative;
|
||||
background-color: inherit;
|
||||
box-shadow: none;
|
||||
box-sizing: border-box;
|
||||
|
||||
.search-area a {
|
||||
color: lighten($darkgray, 10);
|
||||
&:hover {
|
||||
color: $accentblue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import { SearchResults } from 'app/search/search.service';
|
||||
import { SearchResults } from 'app/search/interfaces';
|
||||
|
||||
export class MockSearchService {
|
||||
searchResults = new Subject<SearchResults>();
|
||||
|
@ -27,49 +27,22 @@ const BOILERPLATE_TEST_PATHS = [
|
||||
'karma.conf.js'
|
||||
];
|
||||
|
||||
const ANGULAR_DIST_PATH = path.resolve(__dirname, '../../../dist');
|
||||
const ANGULAR_PACKAGES_PATH = path.resolve(ANGULAR_DIST_PATH, 'packages-dist');
|
||||
const ANGULAR_PACKAGES = [
|
||||
'animations',
|
||||
'common',
|
||||
'compiler',
|
||||
'compiler-cli',
|
||||
'core',
|
||||
'forms',
|
||||
'http',
|
||||
'platform-browser',
|
||||
'platform-browser-dynamic',
|
||||
'platform-server',
|
||||
'router',
|
||||
'upgrade',
|
||||
];
|
||||
const ANGULAR_TOOLS_PACKAGES_PATH = path.resolve(ANGULAR_DIST_PATH, 'tools', '@angular');
|
||||
const ANGULAR_TOOLS_PACKAGES = [
|
||||
'tsc-wrapped'
|
||||
];
|
||||
|
||||
const EXAMPLE_CONFIG_FILENAME = 'example-config.json';
|
||||
|
||||
class ExampleBoilerPlate {
|
||||
/**
|
||||
* Add boilerplate files to all the examples
|
||||
*
|
||||
* @param useLocal if true then overwrite the Angular library files with locally built ones
|
||||
*/
|
||||
add(useLocal) {
|
||||
// first install the shared node_modules
|
||||
this.installNodeModules(SHARED_PATH);
|
||||
|
||||
// Replace the Angular packages with those from the dist folder, if necessary
|
||||
if (useLocal) {
|
||||
ANGULAR_PACKAGES.forEach(packageName => this.overridePackage(ANGULAR_PACKAGES_PATH, packageName));
|
||||
ANGULAR_TOOLS_PACKAGES.forEach(packageName => this.overridePackage(ANGULAR_TOOLS_PACKAGES_PATH, packageName));
|
||||
}
|
||||
|
||||
add() {
|
||||
// Get all the examples folders, indicated by those that contain a `example-config.json` file
|
||||
const exampleFolders = this.getFoldersContaining(EXAMPLES_BASE_PATH, EXAMPLE_CONFIG_FILENAME, 'node_modules');
|
||||
exampleFolders.forEach(exampleFolder => {
|
||||
|
||||
if (!fs.existsSync(SHARED_NODE_MODULES_PATH)) {
|
||||
throw new Error(`The shared node_modules folder for the examples (${SHARED_NODE_MODULES_PATH}) is missing.\n` +
|
||||
`Perhaps you need to run "yarn example-use-npm" or "yarn example-use-local" to install the dependencies?`);
|
||||
}
|
||||
|
||||
// Link the node modules - requires admin access (on Windows) because it adds symlinks
|
||||
const destinationNodeModules = path.resolve(exampleFolder, 'node_modules');
|
||||
fs.ensureSymlinkSync(SHARED_NODE_MODULES_PATH, destinationNodeModules);
|
||||
@ -95,25 +68,12 @@ class ExampleBoilerPlate {
|
||||
main() {
|
||||
yargs
|
||||
.usage('$0 <cmd> [args]')
|
||||
.command('add [--local]', 'add the boilerplate to each example',
|
||||
{ local: { describe: 'Use the locally built Angular libraries, rather than ones from npm.' } },
|
||||
argv => this.add(argv.local))
|
||||
.command('add', 'add the boilerplate to each example', () => this.add())
|
||||
.command('remove', 'remove the boilerplate from each example', () => this.remove())
|
||||
.demandCommand(1, 'Please supply a command from the list above')
|
||||
.argv;
|
||||
}
|
||||
|
||||
installNodeModules(basePath) {
|
||||
shelljs.exec('yarn', {cwd: basePath});
|
||||
}
|
||||
|
||||
overridePackage(basePath, packageName) {
|
||||
const sourceFolder = path.resolve(basePath, packageName);
|
||||
const destinationFolder = path.resolve(SHARED_NODE_MODULES_PATH, '@angular', packageName);
|
||||
shelljs.rm('-rf', destinationFolder);
|
||||
fs.copySync(sourceFolder, destinationFolder);
|
||||
}
|
||||
|
||||
getFoldersContaining(basePath, filename, ignore) {
|
||||
const pattern = path.resolve(basePath, '**', filename);
|
||||
const ignorePattern = path.resolve(basePath, '**', ignore, '**');
|
||||
|
@ -1,50 +1,46 @@
|
||||
const exampleBoilerPlate = require('./example-boilerplate');
|
||||
const shelljs = require('shelljs');
|
||||
const path = require('canonical-path');
|
||||
const fs = require('fs-extra');
|
||||
const glob = require('glob');
|
||||
const path = require('canonical-path');
|
||||
const shelljs = require('shelljs');
|
||||
|
||||
const exampleBoilerPlate = require('./example-boilerplate');
|
||||
|
||||
describe('example-boilerplate tool', () => {
|
||||
describe('add', () => {
|
||||
const sharedDir = path.resolve(__dirname, 'shared');
|
||||
const sharedNodeModulesDir = path.resolve(sharedDir, 'node_modules');
|
||||
const numberOfBoilerPlateFiles = 8;
|
||||
const numberOfBoilerPlateTestFiles = 3;
|
||||
const exampleFolders = ['a/b', 'c/d'];
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(exampleBoilerPlate, 'installNodeModules');
|
||||
spyOn(exampleBoilerPlate, 'overridePackage');
|
||||
spyOn(exampleBoilerPlate, 'getFoldersContaining').and.returnValue(exampleFolders);
|
||||
spyOn(fs, 'ensureSymlinkSync');
|
||||
spyOn(fs, 'existsSync').and.returnValue(true);
|
||||
spyOn(exampleBoilerPlate, 'copyFile');
|
||||
spyOn(exampleBoilerPlate, 'getFoldersContaining').and.returnValue(exampleFolders);
|
||||
spyOn(exampleBoilerPlate, 'loadJsonFile').and.returnValue({});
|
||||
});
|
||||
|
||||
it('should install the node modules', () => {
|
||||
exampleBoilerPlate.add();
|
||||
expect(exampleBoilerPlate.installNodeModules).toHaveBeenCalledWith(path.resolve(__dirname, 'shared'));
|
||||
});
|
||||
|
||||
it('should override the Angular node_modules with the locally built Angular packages if `useLocal` is true', () => {
|
||||
const numberOfAngularPackages = 12;
|
||||
const numberOfAngularToolsPackages = 1;
|
||||
exampleBoilerPlate.add(true);
|
||||
expect(exampleBoilerPlate.overridePackage).toHaveBeenCalledTimes(numberOfAngularPackages + numberOfAngularToolsPackages);
|
||||
// for example
|
||||
expect(exampleBoilerPlate.overridePackage).toHaveBeenCalledWith(path.resolve(__dirname, '../../../dist/packages-dist'), 'common');
|
||||
expect(exampleBoilerPlate.overridePackage).toHaveBeenCalledWith(path.resolve(__dirname, '../../../dist/packages-dist'), 'core');
|
||||
expect(exampleBoilerPlate.overridePackage).toHaveBeenCalledWith(path.resolve(__dirname, '../../../dist/tools/@angular'), 'tsc-wrapped');
|
||||
});
|
||||
|
||||
it('should process all the example folders', () => {
|
||||
const examplesDir = path.resolve(__dirname, '../../content/examples');
|
||||
exampleBoilerPlate.add();
|
||||
expect(exampleBoilerPlate.getFoldersContaining).toHaveBeenCalledWith(path.resolve(__dirname, '../../content/examples'), 'example-config.json', 'node_modules');
|
||||
expect(exampleBoilerPlate.getFoldersContaining)
|
||||
.toHaveBeenCalledWith(examplesDir, 'example-config.json', 'node_modules');
|
||||
});
|
||||
|
||||
it('should symlink the node_modules', () => {
|
||||
exampleBoilerPlate.add();
|
||||
expect(fs.ensureSymlinkSync).toHaveBeenCalledTimes(exampleFolders.length);
|
||||
expect(fs.ensureSymlinkSync).toHaveBeenCalledWith(path.resolve(__dirname, 'shared/node_modules'), path.resolve('a/b/node_modules'));
|
||||
expect(fs.ensureSymlinkSync).toHaveBeenCalledWith(path.resolve(__dirname, 'shared/node_modules'), path.resolve('c/d/node_modules'));
|
||||
expect(fs.ensureSymlinkSync).toHaveBeenCalledWith(sharedNodeModulesDir, path.resolve('a/b/node_modules'));
|
||||
expect(fs.ensureSymlinkSync).toHaveBeenCalledWith(sharedNodeModulesDir, path.resolve('c/d/node_modules'));
|
||||
});
|
||||
|
||||
it('should error if the node_modules folder is missing', () => {
|
||||
fs.existsSync.and.returnValue(false);
|
||||
expect(() => exampleBoilerPlate.add()).toThrowError(
|
||||
`The shared node_modules folder for the examples (${sharedNodeModulesDir}) is missing.\n` +
|
||||
`Perhaps you need to run "yarn example-use-npm" or "yarn example-use-local" to install the dependencies?`);
|
||||
expect(fs.ensureSymlinkSync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should copy all the source boilerplate files', () => {
|
||||
@ -80,31 +76,6 @@ describe('example-boilerplate tool', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('installNodeModules', () => {
|
||||
it('should run `yarn` in the base path', () => {
|
||||
spyOn(shelljs, 'exec');
|
||||
exampleBoilerPlate.installNodeModules('some/base/path');
|
||||
expect(shelljs.exec).toHaveBeenCalledWith('yarn', { cwd: 'some/base/path' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('overridePackage', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(shelljs, 'rm');
|
||||
spyOn(fs, 'copySync');
|
||||
});
|
||||
|
||||
it('should remove the original package from the shared node_modules folder', () => {
|
||||
exampleBoilerPlate.overridePackage('base/path', 'somePackage');
|
||||
expect(shelljs.rm).toHaveBeenCalledWith('-rf', path.resolve(__dirname, 'shared/node_modules/@angular/somePackage'));
|
||||
});
|
||||
|
||||
it('should copy the source folder to the shared node_modules folder', () => {
|
||||
exampleBoilerPlate.overridePackage('base/path', 'somePackage');
|
||||
expect(fs.copySync).toHaveBeenCalledWith(path.resolve('base/path/somePackage'), path.resolve(__dirname, 'shared/node_modules/@angular/somePackage'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFoldersContaining', () => {
|
||||
it('should use glob.sync', () => {
|
||||
spyOn(glob, 'sync').and.returnValue(['a/b/config.json', 'c/d/config.json']);
|
||||
|
@ -4,6 +4,9 @@ const argv = require('yargs').argv;
|
||||
const globby = require('globby');
|
||||
const xSpawn = require('cross-spawn');
|
||||
const treeKill = require('tree-kill');
|
||||
const shelljs = require('shelljs');
|
||||
|
||||
shelljs.set('-e');
|
||||
|
||||
const AIO_PATH = path.join(__dirname, '../../');
|
||||
const SHARED_PATH = path.join(__dirname, '/shared');
|
||||
@ -12,6 +15,7 @@ const PROTRACTOR_CONFIG_FILENAME = path.join(__dirname, './shared/protractor.con
|
||||
const SPEC_FILENAME = 'e2e-spec.ts';
|
||||
const EXAMPLE_CONFIG_FILENAME = 'example-config.json';
|
||||
const IGNORED_EXAMPLES = [
|
||||
'upgrade-p', // Temporarily disabled to unblock 4.4.x while fixing.
|
||||
'ts-to-js/'
|
||||
];
|
||||
|
||||
@ -35,21 +39,18 @@ const IGNORED_EXAMPLES = [
|
||||
* e.g. --shard=1/3 // the second of every three specs: 1, 4, 7, etc
|
||||
*/
|
||||
function runE2e() {
|
||||
let promise = Promise.resolve();
|
||||
if (argv.setup) {
|
||||
// Run setup.
|
||||
console.log('runE2e: copy boilerplate');
|
||||
const spawnInfo = spawnExt('yarn', ['boilerplate:add', '--', argv.local ? '--local' : ''], { cwd: AIO_PATH });
|
||||
promise = spawnInfo.promise
|
||||
.then(() => {
|
||||
console.log('runE2e: update webdriver');
|
||||
return spawnExt('yarn', ['webdriver:update'], { cwd: SHARED_PATH }).promise;
|
||||
});
|
||||
};
|
||||
console.log('runE2e: setup boilerplate');
|
||||
const installPackagesCommand = `example-use-${argv.local ? 'local' : 'npm'}`;
|
||||
const addBoilerplateCommand = 'boilerplate:add';
|
||||
shelljs.exec(`yarn ${installPackagesCommand}`, { cwd: AIO_PATH });
|
||||
shelljs.exec(`yarn ${addBoilerplateCommand}`, { cwd: AIO_PATH });
|
||||
}
|
||||
|
||||
const outputFile = path.join(AIO_PATH, './protractor-results.txt');
|
||||
|
||||
return promise
|
||||
return Promise.resolve()
|
||||
.then(() => findAndRunE2eTests(argv.filter, outputFile, argv.shard))
|
||||
.then((status) => {
|
||||
reportStatus(status, outputFile);
|
||||
@ -105,7 +106,7 @@ function runE2eTests(appDir, outputFile) {
|
||||
const config = loadExampleConfig(appDir);
|
||||
|
||||
const appBuildSpawnInfo = spawnExt('yarn', [config.build], { cwd: appDir });
|
||||
const appRunSpawnInfo = spawnExt('yarn', [config.run, '--', '-s'], { cwd: appDir }, true);
|
||||
const appRunSpawnInfo = spawnExt('yarn', [config.run, '-s'], { cwd: appDir }, true);
|
||||
|
||||
let run = runProtractor(appBuildSpawnInfo.promise, appDir, appRunSpawnInfo, outputFile);
|
||||
|
||||
@ -124,12 +125,12 @@ function runProtractor(prepPromise, appDir, appRunSpawnInfo, outputFile) {
|
||||
fs.appendFileSync(outputFile, emsg);
|
||||
return Promise.reject(emsg);
|
||||
})
|
||||
.then(function (data) {
|
||||
.then(function () {
|
||||
let transpileError = false;
|
||||
|
||||
// Start protractor.
|
||||
|
||||
const spawnInfo = spawnExt('yarn', ['protractor', '--',
|
||||
const spawnInfo = spawnExt('yarn', ['protractor',
|
||||
PROTRACTOR_CONFIG_FILENAME,
|
||||
`--specs=${specFilename}`,
|
||||
'--params.appDir=' + appDir,
|
||||
@ -139,19 +140,19 @@ function runProtractor(prepPromise, appDir, appRunSpawnInfo, outputFile) {
|
||||
spawnInfo.proc.stderr.on('data', function (data) {
|
||||
transpileError = transpileError || /npm ERR! Exit status 100/.test(data.toString());
|
||||
});
|
||||
return spawnInfo.promise.catch(function (err) {
|
||||
return spawnInfo.promise.catch(function () {
|
||||
if (transpileError) {
|
||||
const emsg = `${specFilename} failed to transpile.\n\n`;
|
||||
console.log(emsg);
|
||||
fs.appendFileSync(outputFile, emsg);
|
||||
}
|
||||
return Promise.reject(emsg);
|
||||
return Promise.reject();
|
||||
});
|
||||
})
|
||||
.then(
|
||||
function () { return finish(true); },
|
||||
function () { return finish(false); }
|
||||
)
|
||||
);
|
||||
|
||||
function finish(ok) {
|
||||
// Ugh... proc.kill does not work properly on windows with child processes.
|
||||
@ -201,7 +202,7 @@ function reportStatus(status, outputFile) {
|
||||
function spawnExt(command, args, options, ignoreClose = false) {
|
||||
let proc;
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
let descr = command + " " + args.join(' ');
|
||||
let descr = command + ' ' + args.join(' ');
|
||||
console.log('running: ' + descr);
|
||||
try {
|
||||
proc = xSpawn.spawn(command, args, options);
|
||||
@ -237,7 +238,7 @@ function getE2eSpecPaths(basePath, filter) {
|
||||
const e2eSpecGlob = `${filter ? `*${filter}*` : '*'}/${SPEC_FILENAME}`;
|
||||
return globby(e2eSpecGlob, { cwd: basePath, nodir: true })
|
||||
.then(paths => paths
|
||||
.filter(file => IGNORED_EXAMPLES.some(ignored => !file.startsWith(ignored)))
|
||||
.filter(file => !IGNORED_EXAMPLES.some(ignored => file.startsWith(ignored)))
|
||||
.map(file => path.join(basePath, file))
|
||||
);
|
||||
}
|
||||
|
@ -68,7 +68,7 @@
|
||||
// other libraries
|
||||
'rxjs': 'npm:rxjs@5.0.1',
|
||||
'tslib': 'npm:tslib/tslib.js',
|
||||
'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js',
|
||||
'angular-in-memory-web-api': 'npm:angular-in-memory-web-api@0.4/bundles/in-memory-web-api.umd.js',
|
||||
'ts': 'npm:plugin-typescript@5.2.7/lib/plugin.js',
|
||||
'typescript': 'npm:typescript@2.3.2/lib/typescript.js',
|
||||
|
||||
|
@ -54,7 +54,7 @@
|
||||
// other libraries
|
||||
'rxjs': 'npm:rxjs@5.0.1',
|
||||
'tslib': 'npm:tslib/tslib.js',
|
||||
'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js',
|
||||
'angular-in-memory-web-api': 'npm:angular-in-memory-web-api@0.4/bundles/in-memory-web-api.umd.js',
|
||||
'ts': 'npm:plugin-typescript@5.2.7/lib/plugin.js',
|
||||
'typescript': 'npm:typescript@2.3.2/lib/typescript.js',
|
||||
|
||||
|
@ -6,7 +6,8 @@
|
||||
"scripts": {
|
||||
"http-server": "http-server",
|
||||
"protractor": "protractor",
|
||||
"webdriver:update": "webdriver-manager update --standalone false --gecko false"
|
||||
"webdriver:update": "webdriver-manager update --standalone false --gecko false $CHROMEDRIVER_VERSION_ARG",
|
||||
"postinstall": "yarn webdriver:update"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
@ -27,7 +28,7 @@
|
||||
"@angular/upgrade": "~4.3.1",
|
||||
"angular-in-memory-web-api": "~0.4.0",
|
||||
"core-js": "^2.4.1",
|
||||
"rxjs": "^5.1.0",
|
||||
"rxjs": "^5.4.3",
|
||||
"systemjs": "0.19.39",
|
||||
"zone.js": "^0.8.4"
|
||||
},
|
||||
@ -78,7 +79,7 @@
|
||||
"rollup-plugin-uglify": "^1.0.1",
|
||||
"source-map-explorer": "^1.3.2",
|
||||
"style-loader": "^0.13.1",
|
||||
"ts-node": "~3.0.4",
|
||||
"ts-node": "^3.3.0",
|
||||
"tslint": "^3.15.1",
|
||||
"typescript": "~2.3.2",
|
||||
"webpack": "2.2.1",
|
||||
|
@ -20,7 +20,12 @@ exports.config = {
|
||||
|
||||
// Capabilities to be passed to the webdriver instance.
|
||||
capabilities: {
|
||||
'browserName': 'chrome'
|
||||
'browserName': 'chrome',
|
||||
// For Travis
|
||||
chromeOptions: {
|
||||
binary: process.env.CHROME_BIN,
|
||||
args: ['--no-sandbox']
|
||||
}
|
||||
},
|
||||
|
||||
// Framework to use. Jasmine is recommended.
|
||||
|
@ -1390,6 +1390,14 @@ chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
|
||||
strip-ansi "^3.0.0"
|
||||
supports-color "^2.0.0"
|
||||
|
||||
chalk@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.1.0.tgz#ac5becf14fa21b99c6c92ca7a7d7cfd5b17e743e"
|
||||
dependencies:
|
||||
ansi-styles "^3.1.0"
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^4.0.0"
|
||||
|
||||
chalk@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.0.1.tgz#dbec49436d2ae15f536114e76d14656cdbc0f44d"
|
||||
@ -2950,6 +2958,12 @@ home-or-tmp@^2.0.0:
|
||||
os-homedir "^1.0.0"
|
||||
os-tmpdir "^1.0.1"
|
||||
|
||||
homedir-polyfill@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc"
|
||||
dependencies:
|
||||
parse-passwd "^1.0.0"
|
||||
|
||||
hosted-git-info@^2.1.4:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.4.2.tgz#0076b9f46a270506ddbaaea56496897460612a67"
|
||||
@ -4383,6 +4397,10 @@ parse-json@^2.2.0:
|
||||
dependencies:
|
||||
error-ex "^1.2.0"
|
||||
|
||||
parse-passwd@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
|
||||
|
||||
parse5@^3.0.1:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.2.tgz#05eff57f0ef4577fb144a79f8b9a967a6cc44510"
|
||||
@ -5372,12 +5390,18 @@ rx@4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782"
|
||||
|
||||
rxjs@^5.0.1, rxjs@^5.1.0:
|
||||
rxjs@^5.0.1:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.1.0.tgz#0aa9018b7f440b505fa42bd742b6738be550e720"
|
||||
dependencies:
|
||||
symbol-observable "^1.0.1"
|
||||
|
||||
rxjs@^5.4.3:
|
||||
version "5.4.3"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.4.3.tgz#0758cddee6033d68e0fd53676f0f3596ce3d483f"
|
||||
dependencies:
|
||||
symbol-observable "^1.0.1"
|
||||
|
||||
safe-buffer@^5.0.1, safe-buffer@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.0.tgz#fe4c8460397f9eaaaa58e73be46273408a45e223"
|
||||
@ -6101,19 +6125,19 @@ trim-right@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
|
||||
|
||||
ts-node@~3.0.4:
|
||||
version "3.0.6"
|
||||
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-3.0.6.tgz#55127ff790c7eebf6ba68c1e6dde94b09aaa21e0"
|
||||
ts-node@^3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-3.3.0.tgz#c13c6a3024e30be1180dd53038fc209289d4bf69"
|
||||
dependencies:
|
||||
arrify "^1.0.0"
|
||||
chalk "^1.1.1"
|
||||
chalk "^2.0.0"
|
||||
diff "^3.1.0"
|
||||
make-error "^1.1.1"
|
||||
minimist "^1.2.0"
|
||||
mkdirp "^0.5.1"
|
||||
source-map-support "^0.4.0"
|
||||
tsconfig "^6.0.0"
|
||||
v8flags "^2.0.11"
|
||||
v8flags "^3.0.0"
|
||||
yn "^2.0.0"
|
||||
|
||||
tsconfig@^6.0.0:
|
||||
@ -6341,12 +6365,18 @@ uuid@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1"
|
||||
|
||||
v8flags@^2.0.10, v8flags@^2.0.11:
|
||||
v8flags@^2.0.10:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4"
|
||||
dependencies:
|
||||
user-home "^1.1.1"
|
||||
|
||||
v8flags@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.0.1.tgz#dce8fc379c17d9f2c9e9ed78d89ce00052b1b76b"
|
||||
dependencies:
|
||||
homedir-polyfill "^1.0.1"
|
||||
|
||||
validate-npm-package-license@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc"
|
||||
|
291
aio/tools/ng-packages-installer/index.js
Normal file
291
aio/tools/ng-packages-installer/index.js
Normal file
@ -0,0 +1,291 @@
|
||||
'use strict';
|
||||
|
||||
const chalk = require('chalk');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('canonical-path');
|
||||
const shelljs = require('shelljs');
|
||||
const yargs = require('yargs');
|
||||
|
||||
const PACKAGE_JSON = 'package.json';
|
||||
const LOCAL_MARKER_PATH = 'node_modules/_local_.json';
|
||||
const PACKAGE_JSON_REGEX = /^[^/]+\/package\.json$/;
|
||||
|
||||
const ANGULAR_ROOT_DIR = path.resolve(__dirname, '../../..');
|
||||
const ANGULAR_DIST_PACKAGES = path.resolve(ANGULAR_ROOT_DIR, 'dist/packages-dist');
|
||||
const ANGULAR_DIST_TOOLS = path.resolve(ANGULAR_ROOT_DIR, 'dist/tools/@angular');
|
||||
|
||||
/**
|
||||
* A tool that can install Angular dependencies for a project from NPM or from the
|
||||
* locally built distributables.
|
||||
*
|
||||
* This tool is used to change dependencies of the `aio` application and the example
|
||||
* applications.
|
||||
*/
|
||||
class NgPackagesInstaller {
|
||||
|
||||
/**
|
||||
* Create a new installer for a project in the specified directory.
|
||||
*
|
||||
* @param {string} projectDir - the path to the directory containing the project.
|
||||
* @param {object} options - a hash of options for the install
|
||||
* * `debug` (`boolean`) - whether to display debug messages.
|
||||
* * `force` (`boolean`) - whether to force a local installation
|
||||
* even if there is a local marker file.
|
||||
* * `ignorePackages` (`string[]`) - a collection of names of packages
|
||||
* that should not be copied over.
|
||||
*/
|
||||
constructor(projectDir, options = {}) {
|
||||
this.debug = options.debug;
|
||||
this.force = options.force;
|
||||
this.ignorePackages = options.ignorePackages || [];
|
||||
this.projectDir = path.resolve(projectDir);
|
||||
this.localMarkerPath = path.resolve(this.projectDir, LOCAL_MARKER_PATH);
|
||||
|
||||
this._log('Project directory:', this.projectDir);
|
||||
}
|
||||
|
||||
// Public methods
|
||||
|
||||
/**
|
||||
* Check whether the dependencies have been overridden with locally built
|
||||
* Angular packages. This is done by checking for the `_local_.json` marker file.
|
||||
* This will emit a warning to the console if the dependencies have been overridden.
|
||||
*/
|
||||
checkDependencies() {
|
||||
if (this._checkLocalMarker()) {
|
||||
this._printWarning();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Install locally built Angular dependencies, overriding the dependencies in the package.json
|
||||
* This will also write a "marker" file (`_local_.json`), which contains the overridden package.json
|
||||
* contents and acts as an indicator that dependencies have been overridden.
|
||||
*/
|
||||
installLocalDependencies() {
|
||||
if (this._checkLocalMarker() !== true || this.force) {
|
||||
const pathToPackageConfig = path.resolve(this.projectDir, PACKAGE_JSON);
|
||||
const packages = this._getDistPackages();
|
||||
|
||||
try {
|
||||
// Overwrite local Angular packages dependencies to other Angular packages with local files.
|
||||
Object.keys(packages).forEach(key => {
|
||||
const pkg = packages[key];
|
||||
const tmpConfig = JSON.parse(JSON.stringify(pkg.config));
|
||||
|
||||
// Prevent accidental publishing of the package, if something goes wrong.
|
||||
tmpConfig.private = true;
|
||||
|
||||
// Overwrite project dependencies/devDependencies to Angular packages with local files.
|
||||
['dependencies', 'devDependencies'].forEach(prop => {
|
||||
const deps = tmpConfig[prop] || {};
|
||||
Object.keys(deps).forEach(key2 => {
|
||||
const pkg2 = packages[key2];
|
||||
if (pkg2) {
|
||||
// point the core Angular packages at the distributable folder
|
||||
deps[key2] = `file:${pkg2.parentDir}/${key2.replace('@angular/', '')}`;
|
||||
this._log(`Overriding dependency of local ${key} with local package: ${key2}: ${deps[key2]}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
fs.writeFileSync(pkg.packageJsonPath, JSON.stringify(tmpConfig));
|
||||
});
|
||||
|
||||
const packageConfigFile = fs.readFileSync(pathToPackageConfig);
|
||||
const packageConfig = JSON.parse(packageConfigFile);
|
||||
|
||||
const [dependencies, peers] = this._collectDependencies(packageConfig.dependencies || {}, packages);
|
||||
const [devDependencies, devPeers] = this._collectDependencies(packageConfig.devDependencies || {}, packages);
|
||||
|
||||
this._assignPeerDependencies(peers, dependencies, devDependencies);
|
||||
this._assignPeerDependencies(devPeers, dependencies, devDependencies);
|
||||
|
||||
const localPackageConfig = Object.assign(Object.create(null), packageConfig, { dependencies, devDependencies });
|
||||
localPackageConfig.__angular = { local: true };
|
||||
const localPackageConfigJson = JSON.stringify(localPackageConfig, null, 2);
|
||||
|
||||
try {
|
||||
this._log(`Writing temporary local ${PACKAGE_JSON} to ${pathToPackageConfig}`);
|
||||
fs.writeFileSync(pathToPackageConfig, localPackageConfigJson);
|
||||
this._installDeps('--no-lockfile', '--check-files');
|
||||
this._setLocalMarker(localPackageConfigJson);
|
||||
} finally {
|
||||
this._log(`Restoring original ${PACKAGE_JSON} to ${pathToPackageConfig}`);
|
||||
fs.writeFileSync(pathToPackageConfig, packageConfigFile);
|
||||
}
|
||||
} finally {
|
||||
// Restore local Angular packages dependencies to other Angular packages.
|
||||
this._log(`Restoring original ${PACKAGE_JSON} for local Angular packages.`);
|
||||
Object.keys(packages).forEach(key => {
|
||||
const pkg = packages[key];
|
||||
fs.writeFileSync(pkg.packageJsonPath, JSON.stringify(pkg.config));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reinstall the original package.json depdendencies
|
||||
* Yarn will also delete the local marker file for us.
|
||||
*/
|
||||
restoreNpmDependencies() {
|
||||
this._installDeps('--freeze-lockfile', '--check-files');
|
||||
}
|
||||
|
||||
// Protected helpers
|
||||
|
||||
_assignPeerDependencies(peerDependencies, dependencies, devDependencies) {
|
||||
Object.keys(peerDependencies).forEach(key => {
|
||||
// If there is already an equivalent dependency then override it - otherwise assign/override the devDependency
|
||||
if (dependencies[key]) {
|
||||
this._log(`Overriding dependency with peerDependency: ${key}: ${peerDependencies[key]}`);
|
||||
dependencies[key] = peerDependencies[key];
|
||||
} else {
|
||||
this._log(`${devDependencies[key] ? 'Overriding' : 'Assigning'} devDependency with peerDependency: ${key}: ${peerDependencies[key]}`);
|
||||
devDependencies[key] = peerDependencies[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_collectDependencies(dependencies, packages) {
|
||||
const peerDependencies = Object.create(null);
|
||||
const mergedDependencies = Object.assign(Object.create(null), dependencies);
|
||||
|
||||
Object.keys(dependencies).forEach(key => {
|
||||
const sourcePackage = packages[key];
|
||||
if (sourcePackage) {
|
||||
// point the core Angular packages at the distributable folder
|
||||
mergedDependencies[key] = `file:${sourcePackage.parentDir}/${key.replace('@angular/', '')}`;
|
||||
this._log(`Overriding dependency with local package: ${key}: ${mergedDependencies[key]}`);
|
||||
// grab peer dependencies
|
||||
const sourcePackagePeerDeps = sourcePackage.config.peerDependencies || {};
|
||||
Object.keys(sourcePackagePeerDeps)
|
||||
// ignore peerDependencies which are already core Angular packages
|
||||
.filter(key => !packages[key])
|
||||
.forEach(key => peerDependencies[key] = sourcePackagePeerDeps[key]);
|
||||
}
|
||||
});
|
||||
return [mergedDependencies, peerDependencies];
|
||||
}
|
||||
|
||||
/**
|
||||
* A hash of Angular package configs.
|
||||
* (Detected as directories in '/packages/' that contain a top-level 'package.json' file.)
|
||||
*/
|
||||
_getDistPackages() {
|
||||
const packageConfigs = Object.create(null);
|
||||
|
||||
[ANGULAR_DIST_PACKAGES, ANGULAR_DIST_TOOLS].forEach(distDir => {
|
||||
this._log(`Angular distributable directory: ${distDir}.`);
|
||||
shelljs
|
||||
.find(distDir)
|
||||
.map(filePath => filePath.slice(distDir.length + 1))
|
||||
.filter(filePath => PACKAGE_JSON_REGEX.test(filePath))
|
||||
.forEach(packagePath => {
|
||||
const packageName = `@angular/${packagePath.slice(0, -PACKAGE_JSON.length -1)}`;
|
||||
if (this.ignorePackages.indexOf(packageName) === -1) {
|
||||
const packageConfig = require(path.resolve(distDir, packagePath));
|
||||
packageConfigs[packageName] = {
|
||||
parentDir: distDir,
|
||||
packageJsonPath: path.resolve(distDir, packagePath),
|
||||
config: packageConfig
|
||||
};
|
||||
} else {
|
||||
this._log('Ignoring package', packageName);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
this._log('Found the following Angular distributables:', Object.keys(packageConfigs).map(key => `\n - ${key}`));
|
||||
return packageConfigs;
|
||||
}
|
||||
|
||||
_installDeps(...options) {
|
||||
const command = 'yarn install ' + options.join(' ');
|
||||
this._log('Installing dependencies with:', command);
|
||||
shelljs.exec(command, {cwd: this.projectDir});
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a message if the `debug` property is set to true.
|
||||
* @param {...string[]} messages - The messages to be logged.
|
||||
*/
|
||||
_log(...messages) {
|
||||
if (this.debug) {
|
||||
const header = ` [${NgPackagesInstaller.name}]: `;
|
||||
const indent = ' '.repeat(header.length);
|
||||
const message = messages.join(' ');
|
||||
console.info(`${header}${message.split('\n').join(`\n${indent}`)}`);
|
||||
}
|
||||
}
|
||||
|
||||
_printWarning() {
|
||||
const relativeScriptPath = path.relative('.', __filename.replace(/\.js$/, ''));
|
||||
const absoluteProjectDir = path.resolve(this.projectDir);
|
||||
const restoreCmd = `node ${relativeScriptPath} restore ${absoluteProjectDir}`;
|
||||
|
||||
// Log a warning.
|
||||
console.warn(chalk.yellow([
|
||||
'',
|
||||
'!'.repeat(110),
|
||||
'!!!',
|
||||
'!!! WARNING',
|
||||
'!!!',
|
||||
`!!! The project at "${absoluteProjectDir}" is running against the local Angular build.`,
|
||||
'!!!',
|
||||
'!!! To restore the npm packages run:',
|
||||
'!!!',
|
||||
`!!! "${restoreCmd}"`,
|
||||
'!!!',
|
||||
'!'.repeat(110),
|
||||
'',
|
||||
].join('\n')));
|
||||
}
|
||||
|
||||
// Local marker helpers
|
||||
|
||||
_checkLocalMarker() {
|
||||
this._log('Checking for local marker at', this.localMarkerPath);
|
||||
return fs.existsSync(this.localMarkerPath);
|
||||
}
|
||||
|
||||
_setLocalMarker(contents) {
|
||||
this._log('Writing local marker file to', this.localMarkerPath);
|
||||
fs.writeFileSync(this.localMarkerPath, contents);
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
shelljs.set('-e');
|
||||
|
||||
yargs
|
||||
.usage('$0 <cmd> [args]')
|
||||
|
||||
.option('debug', { describe: 'Print additional debug information.', default: false })
|
||||
.option('force', { describe: 'Force the command to execute even if not needed.', default: false })
|
||||
.option('ignore-packages', { describe: 'List of Angular packages that should not be used in local mode.', default: [], array: true })
|
||||
|
||||
.command('overwrite <projectDir> [--force] [--debug] [--ignore-packages package1 package2]', 'Install dependencies from the locally built Angular distributables.', () => {}, argv => {
|
||||
const installer = new NgPackagesInstaller(argv.projectDir, argv);
|
||||
installer.installLocalDependencies();
|
||||
})
|
||||
.command('restore <projectDir> [--debug]', 'Install dependencies from the npm registry.', () => {}, argv => {
|
||||
const installer = new NgPackagesInstaller(argv.projectDir, argv);
|
||||
installer.restoreNpmDependencies();
|
||||
})
|
||||
.command('check <projectDir> [--debug]', 'Check that dependencies came from npm. Otherwise display a warning message.', () => {}, argv => {
|
||||
const installer = new NgPackagesInstaller(argv.projectDir, argv);
|
||||
installer.checkDependencies();
|
||||
})
|
||||
.demandCommand(1, 'Please supply a command from the list above.')
|
||||
.strict()
|
||||
.wrap(yargs.terminalWidth())
|
||||
.argv;
|
||||
}
|
||||
|
||||
module.exports = NgPackagesInstaller;
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
324
aio/tools/ng-packages-installer/index.spec.js
Normal file
324
aio/tools/ng-packages-installer/index.spec.js
Normal file
@ -0,0 +1,324 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs-extra');
|
||||
const path = require('canonical-path');
|
||||
const shelljs = require('shelljs');
|
||||
|
||||
const NgPackagesInstaller = require('./index');
|
||||
|
||||
describe('NgPackagesInstaller', () => {
|
||||
const rootDir = 'root/dir';
|
||||
const absoluteRootDir = path.resolve(rootDir);
|
||||
const nodeModulesDir = path.resolve(absoluteRootDir, 'node_modules');
|
||||
const packageJsonPath = path.resolve(absoluteRootDir, 'package.json');
|
||||
const packagesDir = path.resolve(path.resolve(__dirname, '../../../dist/packages-dist'));
|
||||
const toolsDir = path.resolve(path.resolve(__dirname, '../../../dist/tools/@angular'));
|
||||
let installer;
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(fs, 'existsSync');
|
||||
spyOn(fs, 'readFileSync');
|
||||
spyOn(fs, 'writeFileSync');
|
||||
spyOn(shelljs, 'exec');
|
||||
spyOn(shelljs, 'rm');
|
||||
spyOn(console, 'log');
|
||||
spyOn(console, 'warn');
|
||||
installer = new NgPackagesInstaller(rootDir);
|
||||
});
|
||||
|
||||
describe('checkDependencies()', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(installer, '_printWarning');
|
||||
});
|
||||
|
||||
it('should not print a warning if there is no _local_.json file', () => {
|
||||
fs.existsSync.and.returnValue(false);
|
||||
installer.checkDependencies();
|
||||
expect(fs.existsSync).toHaveBeenCalledWith(path.resolve(rootDir, 'node_modules/_local_.json'));
|
||||
expect(installer._printWarning).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should print a warning if there is a _local_.json file', () => {
|
||||
fs.existsSync.and.returnValue(true);
|
||||
installer.checkDependencies();
|
||||
expect(fs.existsSync).toHaveBeenCalledWith(path.resolve(rootDir, 'node_modules/_local_.json'));
|
||||
expect(installer._printWarning).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('installLocalDependencies()', () => {
|
||||
const copyJsonObj = obj => JSON.parse(JSON.stringify(obj));
|
||||
let dummyNgPackages, dummyPackage, dummyPackageJson, expectedModifiedPackage, expectedModifiedPackageJson;
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(installer, '_checkLocalMarker');
|
||||
|
||||
// These are the packages that are "found" in the dist directory
|
||||
dummyNgPackages = {
|
||||
'@angular/core': {
|
||||
parentDir: packagesDir,
|
||||
packageJsonPath: `${packagesDir}/core/package.json`,
|
||||
config: { peerDependencies: { rxjs: '5.0.1' } }
|
||||
},
|
||||
'@angular/common': {
|
||||
parentDir: packagesDir,
|
||||
packageJsonPath: `${packagesDir}/common/package.json`,
|
||||
config: { peerDependencies: { '@angular/core': '4.4.4-1ab23cd4' } }
|
||||
},
|
||||
'@angular/compiler': {
|
||||
parentDir: packagesDir,
|
||||
packageJsonPath: `${packagesDir}/compiler/package.json`,
|
||||
config: { peerDependencies: { '@angular/common': '4.4.4-1ab23cd4' } }
|
||||
},
|
||||
'@angular/compiler-cli': {
|
||||
parentDir: toolsDir,
|
||||
packageJsonPath: `${toolsDir}/compiler-cli/package.json`,
|
||||
config: {
|
||||
dependencies: { '@angular/tsc-wrapped': '4.4.4-1ab23cd4' },
|
||||
peerDependencies: { typescript: '^2.4.2', '@angular/compiler': '4.4.4-1ab23cd4' }
|
||||
}
|
||||
},
|
||||
'@angular/tsc-wrapped': {
|
||||
parentDir: toolsDir,
|
||||
packageJsonPath: `${toolsDir}/tsc-wrapped/package.json`,
|
||||
config: {
|
||||
devDependencies: { '@angular/common': '4.4.4-1ab23cd4' },
|
||||
peerDependencies: { tsickle: '^1.4.0' }
|
||||
}
|
||||
}
|
||||
};
|
||||
spyOn(installer, '_getDistPackages').and.callFake(() => copyJsonObj(dummyNgPackages));
|
||||
|
||||
// This is the package.json in the "test" folder
|
||||
dummyPackage = {
|
||||
dependencies: {
|
||||
'@angular/core': '4.4.1',
|
||||
'@angular/common': '4.4.1'
|
||||
},
|
||||
devDependencies: {
|
||||
'@angular/compiler-cli': '4.4.1'
|
||||
}
|
||||
};
|
||||
dummyPackageJson = JSON.stringify(dummyPackage);
|
||||
fs.readFileSync.and.returnValue(dummyPackageJson);
|
||||
|
||||
// This is the package.json that is temporarily written to the "test" folder
|
||||
// Note that the Angular (dev)dependencies have been modified to use a "file:" path
|
||||
// And that the peerDependencies from `dummyNgPackages` have been added as (dev)dependencies.
|
||||
expectedModifiedPackage = {
|
||||
dependencies: {
|
||||
'@angular/core': `file:${packagesDir}/core`,
|
||||
'@angular/common': `file:${packagesDir}/common`
|
||||
},
|
||||
devDependencies: {
|
||||
'@angular/compiler-cli': `file:${toolsDir}/compiler-cli`,
|
||||
rxjs: '5.0.1',
|
||||
typescript: '^2.4.2'
|
||||
},
|
||||
__angular: { local: true }
|
||||
};
|
||||
expectedModifiedPackageJson = JSON.stringify(expectedModifiedPackage, null, 2);
|
||||
});
|
||||
|
||||
describe('when there is a local package marker', () => {
|
||||
it('should not continue processing', () => {
|
||||
installer._checkLocalMarker.and.returnValue(true);
|
||||
installer.installLocalDependencies();
|
||||
expect(installer._checkLocalMarker).toHaveBeenCalled();
|
||||
expect(installer._getDistPackages).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when there is no local package marker', () => {
|
||||
let log;
|
||||
|
||||
beforeEach(() => {
|
||||
log = [];
|
||||
fs.writeFileSync.and.callFake((filePath, contents) => filePath === packageJsonPath && log.push(`writeFile: ${contents}`));
|
||||
spyOn(installer, '_installDeps').and.callFake(() => log.push('installDeps:'));
|
||||
spyOn(installer, '_setLocalMarker');
|
||||
installer._checkLocalMarker.and.returnValue(false);
|
||||
installer.installLocalDependencies();
|
||||
});
|
||||
|
||||
it('should get the dist packages', () => {
|
||||
expect(installer._checkLocalMarker).toHaveBeenCalled();
|
||||
expect(installer._getDistPackages).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should temporarily overwrite the package.json files of local Angular packages', () => {
|
||||
const pkgJsonFor = pkgName => dummyNgPackages[`@angular/${pkgName}`].packageJsonPath;
|
||||
const pkgConfigFor = pkgName => copyJsonObj(dummyNgPackages[`@angular/${pkgName}`].config);
|
||||
const overwriteConfigFor = (pkgName, newProps) => Object.assign(pkgConfigFor(pkgName), newProps);
|
||||
|
||||
const allArgs = fs.writeFileSync.calls.allArgs();
|
||||
const firstFiveArgs = allArgs.slice(0, 5);
|
||||
const lastFiveArgs = allArgs.slice(-5);
|
||||
|
||||
expect(firstFiveArgs).toEqual([
|
||||
[pkgJsonFor('core'), JSON.stringify(overwriteConfigFor('core', {private: true}))],
|
||||
[pkgJsonFor('common'), JSON.stringify(overwriteConfigFor('common', {private: true}))],
|
||||
[pkgJsonFor('compiler'), JSON.stringify(overwriteConfigFor('compiler', {private: true}))],
|
||||
[pkgJsonFor('compiler-cli'), JSON.stringify(overwriteConfigFor('compiler-cli', {
|
||||
private: true,
|
||||
dependencies: { '@angular/tsc-wrapped': `file:${toolsDir}/tsc-wrapped` }
|
||||
}))],
|
||||
[pkgJsonFor('tsc-wrapped'), JSON.stringify(overwriteConfigFor('tsc-wrapped', {
|
||||
private: true,
|
||||
devDependencies: { '@angular/common': `file:${packagesDir}/common` }
|
||||
}))],
|
||||
]);
|
||||
|
||||
expect(lastFiveArgs).toEqual(['core', 'common', 'compiler', 'compiler-cli', 'tsc-wrapped']
|
||||
.map(pkgName => [pkgJsonFor(pkgName), JSON.stringify(pkgConfigFor(pkgName))]));
|
||||
});
|
||||
|
||||
it('should load the package.json', () => {
|
||||
expect(fs.readFileSync).toHaveBeenCalledWith(packageJsonPath);
|
||||
});
|
||||
|
||||
it('should overwrite package.json with modified config', () => {
|
||||
expect(fs.writeFileSync).toHaveBeenCalledWith(packageJsonPath, expectedModifiedPackageJson);
|
||||
});
|
||||
|
||||
it('should restore original package.json', () => {
|
||||
expect(fs.writeFileSync).toHaveBeenCalledWith(packageJsonPath, dummyPackageJson);
|
||||
});
|
||||
|
||||
it('should overwrite package.json, then install deps, then restore original package.json', () => {
|
||||
expect(log).toEqual([
|
||||
`writeFile: ${expectedModifiedPackageJson}`,
|
||||
`installDeps:`,
|
||||
`writeFile: ${dummyPackageJson}`
|
||||
]);
|
||||
});
|
||||
|
||||
it('should set the local marker file with the contents of the modified package.json', () => {
|
||||
expect(installer._setLocalMarker).toHaveBeenCalledWith(expectedModifiedPackageJson);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('restoreNpmDependencies()', () => {
|
||||
it('should run `yarn install` in the specified directory, with the correct options', () => {
|
||||
spyOn(installer, '_installDeps');
|
||||
installer.restoreNpmDependencies();
|
||||
expect(installer._installDeps).toHaveBeenCalledWith('--freeze-lockfile', '--check-files');
|
||||
});
|
||||
});
|
||||
|
||||
describe('_getDistPackages()', () => {
|
||||
it('should include top level Angular packages', () => {
|
||||
const ngPackages = installer._getDistPackages();
|
||||
const expectedValue = jasmine.objectContaining({
|
||||
parentDir: jasmine.any(String),
|
||||
packageJsonPath: jasmine.any(String),
|
||||
config: jasmine.any(Object),
|
||||
});
|
||||
|
||||
// For example...
|
||||
expect(ngPackages['@angular/common']).toEqual(expectedValue);
|
||||
expect(ngPackages['@angular/core']).toEqual(expectedValue);
|
||||
expect(ngPackages['@angular/router']).toEqual(expectedValue);
|
||||
expect(ngPackages['@angular/upgrade']).toEqual(expectedValue);
|
||||
expect(ngPackages['@angular/tsc-wrapped']).toEqual(expectedValue);
|
||||
|
||||
expect(ngPackages['@angular/upgrade/static']).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should store each package\'s parent directory', () => {
|
||||
const ngPackages = installer._getDistPackages();
|
||||
|
||||
// For example...
|
||||
expect(ngPackages['@angular/core'].parentDir).toBe(packagesDir);
|
||||
expect(ngPackages['@angular/tsc-wrapped'].parentDir).toBeDefined(toolsDir);
|
||||
});
|
||||
|
||||
it('should not include packages that have been ignored', () => {
|
||||
installer = new NgPackagesInstaller(rootDir, { ignorePackages: ['@angular/router'] });
|
||||
const ngPackages = installer._getDistPackages();
|
||||
|
||||
expect(ngPackages['@angular/common']).toBeDefined();
|
||||
expect(ngPackages['@angular/router']).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('_log()', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(console, 'info');
|
||||
});
|
||||
|
||||
it('should assign the debug property from the options', () => {
|
||||
installer = new NgPackagesInstaller(rootDir, { debug: true });
|
||||
expect(installer.debug).toBe(true);
|
||||
installer = new NgPackagesInstaller(rootDir, { });
|
||||
expect(installer.debug).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should log a message to the console if the `debug` property is true', () => {
|
||||
installer._log('foo');
|
||||
expect(console.info).not.toHaveBeenCalled();
|
||||
|
||||
installer.debug = true;
|
||||
installer._log('bar');
|
||||
expect(console.info).toHaveBeenCalledWith(' [NgPackagesInstaller]: bar');
|
||||
});
|
||||
});
|
||||
|
||||
describe('_printWarning()', () => {
|
||||
it('should mention the message passed in the warning', () => {
|
||||
installer._printWarning();
|
||||
expect(console.warn.calls.argsFor(0)[0]).toContain('is running against the local Angular build');
|
||||
});
|
||||
|
||||
it('should mention the command to restore the Angular packages in any warning', () => {
|
||||
// When run for the current working directory...
|
||||
const dir1 = '.';
|
||||
const restoreCmdRe1 = RegExp('\\bnode .*?ng-packages-installer/index restore ' + path.resolve(dir1));
|
||||
installer = new NgPackagesInstaller(dir1);
|
||||
installer._printWarning('');
|
||||
expect(console.warn.calls.argsFor(0)[0]).toMatch(restoreCmdRe1);
|
||||
|
||||
// When run for a different directory...
|
||||
const dir2 = rootDir;
|
||||
const restoreCmdRe2 = RegExp(`\\bnode .*?ng-packages-installer/index restore .*?${path.resolve(dir1)}\\b`);
|
||||
installer = new NgPackagesInstaller(dir2);
|
||||
installer._printWarning('');
|
||||
expect(console.warn.calls.argsFor(1)[0]).toMatch(restoreCmdRe2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('_installDeps()', () => {
|
||||
it('should run yarn install with the given options', () => {
|
||||
installer._installDeps('option-1', 'option-2');
|
||||
expect(shelljs.exec).toHaveBeenCalledWith('yarn install option-1 option-2', { cwd: absoluteRootDir });
|
||||
});
|
||||
});
|
||||
|
||||
describe('local marker helpers', () => {
|
||||
let installer;
|
||||
beforeEach(() => {
|
||||
installer = new NgPackagesInstaller(rootDir);
|
||||
});
|
||||
|
||||
describe('_checkLocalMarker', () => {
|
||||
it ('should return true if the local marker file exists', () => {
|
||||
fs.existsSync.and.returnValue(true);
|
||||
expect(installer._checkLocalMarker()).toEqual(true);
|
||||
expect(fs.existsSync).toHaveBeenCalledWith(path.resolve(nodeModulesDir, '_local_.json'));
|
||||
fs.existsSync.calls.reset();
|
||||
|
||||
fs.existsSync.and.returnValue(false);
|
||||
expect(installer._checkLocalMarker()).toEqual(false);
|
||||
expect(fs.existsSync).toHaveBeenCalledWith(path.resolve(nodeModulesDir, '_local_.json'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('_setLocalMarker', () => {
|
||||
it('should create a local marker file', () => {
|
||||
installer._setLocalMarker('test contents');
|
||||
expect(fs.writeFileSync).toHaveBeenCalledWith(path.resolve(nodeModulesDir, '_local_.json'), 'test contents');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -4,7 +4,7 @@
|
||||
*/
|
||||
module.exports = function filterContainedDocs() {
|
||||
return {
|
||||
docTypes: ['member', 'function-overload'],
|
||||
docTypes: ['member', 'function-overload', 'get-accessor-info', 'set-accessor-info'],
|
||||
$runAfter: ['extra-docs-added'],
|
||||
$runBefore: ['computing-paths'],
|
||||
$process: function(docs) {
|
||||
|
@ -26,7 +26,9 @@ function getModuleInfo(moduleDoc) {
|
||||
title: moduleName,
|
||||
items: moduleDoc.exports
|
||||
// Ignore internals and private exports (indicated by the ɵ prefix)
|
||||
.filter(doc => !doc.internal && !doc.privateExport).map(getExportInfo)
|
||||
.filter(doc => !doc.internal && !doc.privateExport)
|
||||
.map(getExportInfo)
|
||||
.sort((a, b) => a.name === b.name ? 0 : a.name > b.name ? 1 : -1)
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -113,7 +113,6 @@ describe('generateApiListDoc processor', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
it('should convert security to a boolean securityRisk', () => {
|
||||
const processor = processorFactory();
|
||||
const docs = [
|
||||
@ -129,7 +128,6 @@ describe('generateApiListDoc processor', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
it('should convert stability tags to the stable string property', () => {
|
||||
const processor = processorFactory();
|
||||
const docs = [
|
||||
@ -148,5 +146,24 @@ describe('generateApiListDoc processor', () => {
|
||||
{ docType: 'class', title: 'DddDdd', name: 'dddddd', path: 'ddd', stability: '', securityRisk: false },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should sort items in each group alphabetically', () => {
|
||||
const processor = processorFactory();
|
||||
const docs = [
|
||||
{ docType: 'module', id: '@angular/common/index', exports: [
|
||||
{ docType: 'class', name: 'DddDdd', path: 'uuu' },
|
||||
{ docType: 'class', name: 'BbbBbb', path: 'vvv' },
|
||||
{ docType: 'class', name: 'AaaAaa', path: 'xxx' },
|
||||
{ docType: 'class', name: 'CccCcc', path: 'yyy' },
|
||||
]}
|
||||
];
|
||||
processor.$process(docs);
|
||||
expect(docs[1].data[0].items).toEqual([
|
||||
{ docType: 'class', title: 'AaaAaa', name: 'aaaaaa', path: 'xxx', stability: '', securityRisk: false },
|
||||
{ docType: 'class', title: 'BbbBbb', name: 'bbbbbb', path: 'vvv', stability: '', securityRisk: false },
|
||||
{ docType: 'class', title: 'CccCcc', name: 'cccccc', path: 'yyy', stability: '', securityRisk: false },
|
||||
{ docType: 'class', title: 'DddDdd', name: 'dddddd', path: 'uuu', stability: '', securityRisk: false },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -29,6 +29,7 @@ module.exports = new Package('angular-base', [
|
||||
.processor(require('./processors/convertToJson'))
|
||||
.processor(require('./processors/fixInternalDocumentLinks'))
|
||||
.processor(require('./processors/copyContentAssets'))
|
||||
.processor(require('./processors/renderLinkInfo'))
|
||||
|
||||
// overrides base packageInfo and returns the one for the 'angular/angular' repo.
|
||||
.factory('packageInfo', function() { return require(path.resolve(PROJECT_ROOT, 'package.json')); })
|
||||
|
52
aio/tools/transforms/angular-base-package/processors/renderLinkInfo.js
vendored
Normal file
52
aio/tools/transforms/angular-base-package/processors/renderLinkInfo.js
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* @dgProcessor renderLinkInfo
|
||||
* @description For each doc that has one of the specified docTypes,
|
||||
* add HTML comments that describe the links to and from the doc.
|
||||
*/
|
||||
module.exports = function renderLinkInfo(extractLinks) {
|
||||
return {
|
||||
docTypes: [],
|
||||
$runBefore: ['convertToJsonProcessor'],
|
||||
$runAfter: ['fixInternalDocumentLinks'],
|
||||
$process(docs) {
|
||||
const toLinks = {};
|
||||
const fromLinks = {};
|
||||
const docsToCheck = docs.filter(doc => this.docTypes.indexOf(doc.docType) !== -1);
|
||||
|
||||
// Extract and store all links found in each doc in hashes
|
||||
docsToCheck.forEach(doc => {
|
||||
const linksFromDoc = extractLinks(doc.renderedContent).hrefs;
|
||||
// Update the hashes
|
||||
fromLinks[doc.path] = linksFromDoc;
|
||||
linksFromDoc.forEach(linkPath => {
|
||||
linkPath = linkPath.match(/^[^#?]+/)[0]; // remove the query and hash from the link
|
||||
(toLinks[linkPath] = toLinks[linkPath] || []).push(doc.path);
|
||||
});
|
||||
});
|
||||
|
||||
// Add HTML comments to the end of the rendered content that list the links found above
|
||||
docsToCheck.forEach(doc => {
|
||||
const linksFromDoc = getLinks(fromLinks, doc.path);
|
||||
const linksToDoc = getLinks(toLinks, doc.path);
|
||||
doc.renderedContent +=
|
||||
`\n<!-- links to this doc:${linksToDoc.map(link => `\n - ${link}`).join('')}\n-->\n` +
|
||||
`<!-- links from this doc:${linksFromDoc.map(link => `\n - ${link}`).join('')}\n-->`;
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function getLinks(hash, docPath) {
|
||||
const links = (hash[docPath] || []).filter(link => link !== docPath);
|
||||
const internal = {};
|
||||
const external = {};
|
||||
links.forEach(link => {
|
||||
if (/^[^:/#?]+:/.test(link)) {
|
||||
external[link] = true;
|
||||
} else {
|
||||
internal[link] = true;
|
||||
}
|
||||
});
|
||||
return Object.keys(internal).sort()
|
||||
.concat(Object.keys(external).sort());
|
||||
}
|
@ -0,0 +1,222 @@
|
||||
const testPackage = require('../../helpers/test-package');
|
||||
const processorFactory = require('./renderLinkInfo');
|
||||
const extractLinks = require('dgeni-packages/base/services/extractLinks')();
|
||||
const Dgeni = require('dgeni');
|
||||
|
||||
describe('renderLinkInfo processor', () => {
|
||||
|
||||
it('should be available on the injector', () => {
|
||||
const dgeni = new Dgeni([testPackage('angular-base-package')]);
|
||||
const injector = dgeni.configureInjector();
|
||||
const processor = injector.get('renderLinkInfo');
|
||||
expect(processor.$process).toBeDefined();
|
||||
});
|
||||
|
||||
it('should run before the correct processor', () => {
|
||||
const processor = processorFactory(extractLinks);
|
||||
expect(processor.$runBefore).toEqual(['convertToJsonProcessor']);
|
||||
});
|
||||
|
||||
it('should run after the correct processor', () => {
|
||||
const processor = processorFactory(extractLinks);
|
||||
expect(processor.$runAfter).toEqual(['fixInternalDocumentLinks']);
|
||||
});
|
||||
|
||||
it('should add HTML comments for links out of docs', () => {
|
||||
const processor = processorFactory(extractLinks);
|
||||
processor.docTypes = ['test'];
|
||||
const docs = [
|
||||
{ path: 'test-1', docType: 'test', renderedContent: '<a href="a/b/c"></a><a href="x/y/z"></a>' },
|
||||
{ path: 'test-2', docType: 'test', renderedContent: '<a href="foo"></a><a href="bar"></a>' },
|
||||
];
|
||||
processor.$process(docs);
|
||||
expect(docs).toEqual([
|
||||
{
|
||||
path: 'test-1',
|
||||
docType: 'test',
|
||||
renderedContent: '<a href="a/b/c"></a><a href="x/y/z"></a>\n' +
|
||||
'<!-- links to this doc:\n-->\n' +
|
||||
'<!-- links from this doc:\n - a/b/c\n - x/y/z\n-->'
|
||||
},
|
||||
{
|
||||
path: 'test-2',
|
||||
docType: 'test',
|
||||
renderedContent: '<a href="foo"></a><a href="bar"></a>\n' +
|
||||
'<!-- links to this doc:\n-->\n' +
|
||||
'<!-- links from this doc:\n - bar\n - foo\n-->'
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should order links alphabetically', () => {
|
||||
const processor = processorFactory(extractLinks);
|
||||
processor.docTypes = ['test'];
|
||||
const docs = [
|
||||
{ path: 'test-1', docType: 'test', renderedContent: '<a href="orange"></a><a href="apple"></a><a href="banana"></a>' },
|
||||
];
|
||||
processor.$process(docs);
|
||||
expect(docs).toEqual([
|
||||
{
|
||||
path: 'test-1',
|
||||
docType: 'test',
|
||||
renderedContent: '<a href="orange"></a><a href="apple"></a><a href="banana"></a>\n' +
|
||||
'<!-- links to this doc:\n-->\n' +
|
||||
'<!-- links from this doc:\n - apple\n - banana\n - orange\n-->'
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should list repeated links only once', () => {
|
||||
const processor = processorFactory(extractLinks);
|
||||
processor.docTypes = ['test'];
|
||||
const docs = [
|
||||
{ path: 'test-1', docType: 'test', renderedContent: '<a href="banana"></a><a href="apple"></a><a href="banana"></a>' },
|
||||
];
|
||||
processor.$process(docs);
|
||||
expect(docs).toEqual([
|
||||
{
|
||||
path: 'test-1',
|
||||
docType: 'test',
|
||||
renderedContent: '<a href="banana"></a><a href="apple"></a><a href="banana"></a>\n' +
|
||||
'<!-- links to this doc:\n-->\n' +
|
||||
'<!-- links from this doc:\n - apple\n - banana\n-->'
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should list internal links before external', () => {
|
||||
const processor = processorFactory(extractLinks);
|
||||
processor.docTypes = ['test'];
|
||||
const docs = [
|
||||
{ path: 'test-1', docType: 'test', renderedContent: '<a href="https://www.google.com"></a><a href="apple"></a><a href="ftp://myfile.org"></a>' },
|
||||
];
|
||||
processor.$process(docs);
|
||||
expect(docs).toEqual([
|
||||
{
|
||||
path: 'test-1',
|
||||
docType: 'test',
|
||||
renderedContent: '<a href="https://www.google.com"></a><a href="apple"></a><a href="ftp://myfile.org"></a>\n' +
|
||||
'<!-- links to this doc:\n-->\n' +
|
||||
'<!-- links from this doc:\n - apple\n - ftp://myfile.org\n - https://www.google.com\n-->'
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should ignore docs that do not have the specified docType', () => {
|
||||
const processor = processorFactory(extractLinks);
|
||||
processor.docTypes = ['test'];
|
||||
const docs = [
|
||||
{ path: 'test-1', docType: 'test', renderedContent: '<a href="a/b/c"></a><a href="x/y/z"></a>' },
|
||||
{ path: 'test-2', docType: 'test2', renderedContent: '<a href="foo"></a><a href="bar"></a>' },
|
||||
];
|
||||
processor.$process(docs);
|
||||
expect(docs).toEqual([
|
||||
{
|
||||
path: 'test-1',
|
||||
docType: 'test',
|
||||
renderedContent: '<a href="a/b/c"></a><a href="x/y/z"></a>\n' +
|
||||
'<!-- links to this doc:\n-->\n' +
|
||||
'<!-- links from this doc:\n - a/b/c\n - x/y/z\n-->'
|
||||
},
|
||||
{
|
||||
path: 'test-2',
|
||||
docType: 'test2',
|
||||
renderedContent: '<a href="foo"></a><a href="bar"></a>'
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should add HTML comments for links into docs', () => {
|
||||
const processor = processorFactory(extractLinks);
|
||||
processor.docTypes = ['test'];
|
||||
const docs = [
|
||||
{ path: 'test-1', docType: 'test', renderedContent: '<a href="test-2"></a>' },
|
||||
{ path: 'test-2', docType: 'test', renderedContent: '<a href="test-1"></a><a href="test-3"></a>' },
|
||||
{ path: 'test-3', docType: 'test', renderedContent: '<a href="test-1"></a><a href="test-2"></a>' },
|
||||
];
|
||||
processor.$process(docs);
|
||||
expect(docs).toEqual([
|
||||
{
|
||||
path: 'test-1',
|
||||
docType: 'test',
|
||||
renderedContent: '<a href="test-2"></a>\n' +
|
||||
'<!-- links to this doc:\n - test-2\n - test-3\n-->\n' +
|
||||
'<!-- links from this doc:\n - test-2\n-->'
|
||||
},
|
||||
{
|
||||
path: 'test-2',
|
||||
docType: 'test',
|
||||
renderedContent: '<a href="test-1"></a><a href="test-3"></a>\n' +
|
||||
'<!-- links to this doc:\n - test-1\n - test-3\n-->\n' +
|
||||
'<!-- links from this doc:\n - test-1\n - test-3\n-->'
|
||||
},
|
||||
{
|
||||
path: 'test-3',
|
||||
docType: 'test',
|
||||
renderedContent: '<a href="test-1"></a><a href="test-2"></a>\n' +
|
||||
'<!-- links to this doc:\n - test-2\n-->\n' +
|
||||
'<!-- links from this doc:\n - test-1\n - test-2\n-->'
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not include links to themselves', () => {
|
||||
const processor = processorFactory(extractLinks);
|
||||
processor.docTypes = ['test'];
|
||||
const docs = [
|
||||
{ path: 'test-1', docType: 'test', renderedContent: '<a href="test-2"></a>' },
|
||||
{ path: 'test-2', docType: 'test', renderedContent: '<a href="test-1"></a><a href="test-2"></a>' },
|
||||
];
|
||||
processor.$process(docs);
|
||||
expect(docs).toEqual([
|
||||
{
|
||||
path: 'test-1',
|
||||
docType: 'test',
|
||||
renderedContent: '<a href="test-2"></a>\n' +
|
||||
'<!-- links to this doc:\n - test-2\n-->\n' +
|
||||
'<!-- links from this doc:\n - test-2\n-->'
|
||||
},
|
||||
{
|
||||
path: 'test-2',
|
||||
docType: 'test',
|
||||
renderedContent: '<a href="test-1"></a><a href="test-2"></a>\n' +
|
||||
'<!-- links to this doc:\n - test-1\n-->\n' +
|
||||
'<!-- links from this doc:\n - test-1\n-->'
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should match links that contain fragments or queries', () => {
|
||||
const processor = processorFactory(extractLinks);
|
||||
processor.docTypes = ['test'];
|
||||
const docs = [
|
||||
{ path: 'test-1', docType: 'test', renderedContent: '<a href="test-2#foo"></a>' },
|
||||
{ path: 'test-2', docType: 'test', renderedContent: '<a href="test-1?some-query"></a>' },
|
||||
{ path: 'test-3', docType: 'test', renderedContent: '<a href="test-1?some-query#foo"></a>' },
|
||||
];
|
||||
processor.$process(docs);
|
||||
expect(docs).toEqual([
|
||||
{
|
||||
path: 'test-1',
|
||||
docType: 'test',
|
||||
renderedContent: '<a href="test-2#foo"></a>\n' +
|
||||
'<!-- links to this doc:\n - test-2\n - test-3\n-->\n' +
|
||||
'<!-- links from this doc:\n - test-2#foo\n-->'
|
||||
},
|
||||
{
|
||||
path: 'test-2',
|
||||
docType: 'test',
|
||||
renderedContent: '<a href="test-1?some-query"></a>\n' +
|
||||
'<!-- links to this doc:\n - test-1\n-->\n' +
|
||||
'<!-- links from this doc:\n - test-1?some-query\n-->'
|
||||
},
|
||||
{
|
||||
path: 'test-3',
|
||||
docType: 'test',
|
||||
renderedContent: '<a href="test-1?some-query#foo"></a>\n' +
|
||||
'<!-- links to this doc:\n-->\n' +
|
||||
'<!-- links from this doc:\n - test-1?some-query#foo\n-->'
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
@ -48,4 +48,8 @@ module.exports = new Package('angular.io', [gitPackage, apiPackage, contentPacka
|
||||
});
|
||||
checkAnchorLinksProcessor.pathVariants = ['', '/', '.html', '/index.html', '#top-of-page'];
|
||||
checkAnchorLinksProcessor.errorOnUnmatchedLinks = true;
|
||||
})
|
||||
|
||||
.config(function(renderLinkInfo, postProcessHtml) {
|
||||
renderLinkInfo.docTypes = postProcessHtml.docTypes;
|
||||
});
|
||||
|
@ -23,11 +23,11 @@ function next(error) {
|
||||
let p = Promise.resolve();
|
||||
|
||||
if (process.argv.indexOf('--watch-only') === -1) {
|
||||
console.log('===================================================================');
|
||||
console.log('================================================================');
|
||||
console.log('Running initial doc generation');
|
||||
console.log('-------------------------------------------------------------------');
|
||||
console.log('Skip the full doc-gen by running: `yarn docs-watch -- --watch-only`');
|
||||
console.log('===================================================================');
|
||||
console.log('----------------------------------------------------------------');
|
||||
console.log('Skip the full doc-gen by running: `yarn docs-watch --watch-only`');
|
||||
console.log('================================================================');
|
||||
const {Dgeni} = require('dgeni');
|
||||
var dgeni = new Dgeni([require('../angular.io-package')]);
|
||||
p = dgeni.generate();
|
||||
@ -44,4 +44,4 @@ p.then(() => {
|
||||
watchr.open(CONTENTS_PATH, listener, next);
|
||||
watchr.open(API_SOURCE_PATH, listener, next);
|
||||
|
||||
});
|
||||
});
|
||||
|
@ -18,10 +18,10 @@
|
||||
|
||||
{%- macro renderMember(member, truncateLines) -%}
|
||||
{%- if member.accessibility !== 'public' %}{$ member.accessibility $} {% endif -%}
|
||||
{%- if member.isGetAccessor %}get {% endif -%}
|
||||
{%- if member.isSetAccessor %}set {% endif -%}
|
||||
{%- if (member.isGetAccessor or member.isReadonly) and not member.isSetAccessor %}get {% endif -%}
|
||||
{%- if member.isSetAccessor and not member.isGetAccessor %}set {% endif -%}
|
||||
{%- if member.isStatic %}static {% endif -%}
|
||||
{$ member.name $}{$ member.typeParameters | escape $}{$ params.paramList(member.parameters, truncateLines) | trim $}
|
||||
{$ member.name $}{$ member.typeParameters | escape $}{% if not member.isGetAccessor %}{$ params.paramList(member.parameters, truncateLines) | trim $}{% endif %}
|
||||
{%- if member.isOptional %}?{% endif -%}
|
||||
{$ params.returnType(member.type) | trim | truncateCode(truncateLines) $}
|
||||
{%- endmacro -%}
|
||||
|
@ -1,9 +1,11 @@
|
||||
{% extends 'export-base.template.html' %}
|
||||
|
||||
{% block overview %}
|
||||
<code-example language="ts" hideCopy="true">
|
||||
type {$ doc.name $}{% if doc.typeDefinition %} = {$ doc.typeDefinition | escape $}{% endif %};
|
||||
</code-example>
|
||||
<section class="{$ doc.docType $}-overview">
|
||||
<code-example language="ts" hideCopy="true">
|
||||
type {$ doc.name $}{$ doc.typeParameters | escape $}{% if doc.typeDefinition %} = {$ doc.typeDefinition | escape $}{% endif %};
|
||||
</code-example>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
{% block details %}
|
||||
|
@ -8,6 +8,7 @@
|
||||
"moduleResolution": "node",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"noUnusedLocals": true,
|
||||
"target": "es5",
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
|
@ -380,10 +380,6 @@ ansi-styles@^3.1.0:
|
||||
dependencies:
|
||||
color-convert "^1.9.0"
|
||||
|
||||
any-promise@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
|
||||
|
||||
anymatch@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.0.tgz#a3e52fa39168c825ff57b0248126ce5a8ff95507"
|
||||
@ -1085,7 +1081,7 @@ chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
|
||||
strip-ansi "^3.0.0"
|
||||
supports-color "^2.0.0"
|
||||
|
||||
chalk@^2.0.0, chalk@^2.0.1:
|
||||
chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.1.0.tgz#ac5becf14fa21b99c6c92ca7a7d7cfd5b17e743e"
|
||||
dependencies:
|
||||
@ -2002,9 +1998,9 @@ devtools-timeline-model@1.1.6:
|
||||
chrome-devtools-frontend "1.0.401423"
|
||||
resolve "1.1.7"
|
||||
|
||||
dgeni-packages@^0.21.3:
|
||||
version "0.21.3"
|
||||
resolved "https://registry.yarnpkg.com/dgeni-packages/-/dgeni-packages-0.21.3.tgz#49d5264400cdd8c8a2f66040267e38c099d540f4"
|
||||
dgeni-packages@0.22.0:
|
||||
version "0.22.0"
|
||||
resolved "https://registry.yarnpkg.com/dgeni-packages/-/dgeni-packages-0.22.0.tgz#7ed07af9074f6547847256c1a65b488a5a17ad03"
|
||||
dependencies:
|
||||
canonical-path "0.0.2"
|
||||
catharsis "^0.8.1"
|
||||
@ -3442,6 +3438,12 @@ home-dir@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/home-dir/-/home-dir-1.0.0.tgz#2917eb44bdc9072ceda942579543847e3017fe4e"
|
||||
|
||||
homedir-polyfill@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc"
|
||||
dependencies:
|
||||
parse-passwd "^1.0.0"
|
||||
|
||||
hosted-git-info@^2.1.4:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.4.2.tgz#0076b9f46a270506ddbaaea56496897460612a67"
|
||||
@ -5464,6 +5466,10 @@ parse-json@^2.1.0, parse-json@^2.2.0:
|
||||
dependencies:
|
||||
error-ex "^1.2.0"
|
||||
|
||||
parse-passwd@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
|
||||
|
||||
parse5@^1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/parse5/-/parse5-1.5.1.tgz#9b7f3b0de32be78dc2401b17573ccaf0f6f59d94"
|
||||
@ -5599,7 +5605,7 @@ pinkie-promise@^2.0.0:
|
||||
dependencies:
|
||||
pinkie "^2.0.0"
|
||||
|
||||
pinkie@^2.0.0, pinkie@^2.0.4:
|
||||
pinkie@^2.0.0:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
|
||||
|
||||
@ -6558,13 +6564,7 @@ rx@2.3.24:
|
||||
version "2.3.24"
|
||||
resolved "https://registry.yarnpkg.com/rx/-/rx-2.3.24.tgz#14f950a4217d7e35daa71bbcbe58eff68ea4b2b7"
|
||||
|
||||
rxjs@^5.2.0:
|
||||
version "5.4.0"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.4.0.tgz#a7db14ab157f9d7aac6a56e655e7a3860d39bf26"
|
||||
dependencies:
|
||||
symbol-observable "^1.0.1"
|
||||
|
||||
rxjs@^5.4.2:
|
||||
rxjs@^5.4.2, rxjs@^5.4.3:
|
||||
version "5.4.3"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.4.3.tgz#0758cddee6033d68e0fd53676f0f3596ce3d483f"
|
||||
dependencies:
|
||||
@ -7514,31 +7514,20 @@ ts-node@^3.0.2:
|
||||
v8flags "^2.0.11"
|
||||
yn "^1.2.0"
|
||||
|
||||
ts-node@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-2.0.0.tgz#16e4fecc949088238b4cbf1c39c9582526b66f74"
|
||||
ts-node@^3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-3.3.0.tgz#c13c6a3024e30be1180dd53038fc209289d4bf69"
|
||||
dependencies:
|
||||
arrify "^1.0.0"
|
||||
chalk "^1.1.1"
|
||||
chalk "^2.0.0"
|
||||
diff "^3.1.0"
|
||||
make-error "^1.1.1"
|
||||
minimist "^1.2.0"
|
||||
mkdirp "^0.5.1"
|
||||
pinkie "^2.0.4"
|
||||
source-map-support "^0.4.0"
|
||||
tsconfig "^5.0.2"
|
||||
v8flags "^2.0.11"
|
||||
xtend "^4.0.0"
|
||||
yn "^1.2.0"
|
||||
|
||||
tsconfig@^5.0.2:
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tsconfig/-/tsconfig-5.0.3.tgz#5f4278e701800967a8fc383fd19648878f2a6e3a"
|
||||
dependencies:
|
||||
any-promise "^1.3.0"
|
||||
parse-json "^2.2.0"
|
||||
strip-bom "^2.0.0"
|
||||
strip-json-comments "^2.0.0"
|
||||
tsconfig "^6.0.0"
|
||||
v8flags "^3.0.0"
|
||||
yn "^2.0.0"
|
||||
|
||||
tsconfig@^6.0.0:
|
||||
version "6.0.0"
|
||||
@ -7965,6 +7954,12 @@ v8flags@^2.0.11:
|
||||
dependencies:
|
||||
user-home "^1.1.1"
|
||||
|
||||
v8flags@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.0.1.tgz#dce8fc379c17d9f2c9e9ed78d89ce00052b1b76b"
|
||||
dependencies:
|
||||
homedir-polyfill "^1.0.1"
|
||||
|
||||
valid-url@^1:
|
||||
version "1.0.9"
|
||||
resolved "https://registry.yarnpkg.com/valid-url/-/valid-url-1.0.9.tgz#1c14479b40f1397a75782f115e4086447433a200"
|
||||
@ -8507,6 +8502,10 @@ yn@^1.2.0:
|
||||
dependencies:
|
||||
object-assign "^4.1.1"
|
||||
|
||||
yn@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/yn/-/yn-2.0.0.tgz#e5adabc8acf408f6385fc76495684c88e6af689a"
|
||||
|
||||
zip-stream@^1.1.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-1.1.1.tgz#5216b48bbb4d2651f64d5c6e6f09eb4a7399d557"
|
||||
|
@ -30,7 +30,7 @@ var CIconfiguration = {
|
||||
'Safari8': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
|
||||
'Safari9': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
|
||||
'Safari10': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
|
||||
'iOS7': {unitTest: {target: 'BS', required: true}, e2e: {target: null, required: true}},
|
||||
'iOS7': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
|
||||
'iOS8': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
|
||||
'iOS9': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
|
||||
'iOS10': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
|
||||
|
@ -27,9 +27,9 @@ repository (google3).
|
||||
|
||||
That repository defines dependencies on specific versions of
|
||||
all the tools. You can run the tools Bazel installed, for
|
||||
example rather than `npm install` (which depends on whatever
|
||||
example rather than `yarn install` (which depends on whatever
|
||||
version you have installed on your machine), you can
|
||||
`bazel run @build_bazel_rules_typescript_node//:bin/npm install`.
|
||||
`bazel run @yarn//:yarn`.
|
||||
|
||||
Bazel accepts a lot of options. We check in some options in the
|
||||
`.bazelrc` file. See the [bazelrc doc]. For example, if you don't
|
||||
|
@ -26,6 +26,8 @@ following products on your development machine:
|
||||
(version `>=3.10.7 <4.0.0`), which comes with Node. Depending on your system, you can install Node either from
|
||||
source or as a pre-packaged bundle.
|
||||
|
||||
* [Yarn](https://yarnpkg.com) (version `>=1.0.2 <2.0.0`) which is used to install dependencies.
|
||||
|
||||
* [Java Development Kit](http://www.oracle.com/technetwork/es/java/javase/downloads/index.html) which is used
|
||||
to execute the selenium standalone server for e2e testing.
|
||||
|
||||
@ -56,29 +58,13 @@ Next, install the JavaScript modules needed to build and test Angular:
|
||||
|
||||
```shell
|
||||
# Install Angular project dependencies (package.json)
|
||||
npm install
|
||||
yarn install
|
||||
```
|
||||
|
||||
**Optional**: In this document, we make use of project local `npm` package scripts and binaries
|
||||
(stored under `./node_modules/.bin`) by prefixing these command invocations with `$(npm bin)`; in
|
||||
particular `gulp` and `protractor` commands. If you prefer, you can drop this path prefix by either:
|
||||
|
||||
*Option 1*: globally installing these two packages as follows:
|
||||
|
||||
* `npm install -g gulp` (you might need to prefix this command with `sudo`)
|
||||
* `npm install -g protractor` (you might need to prefix this command with `sudo`)
|
||||
|
||||
Since global installs can become stale, and required versions can vary by project, we avoid their
|
||||
use in these instructions.
|
||||
|
||||
*Option 2*: globally installing the package `npm-run` by running `npm install -g npm-run`
|
||||
(you might need to prefix this command with `sudo`). You will then be able to run locally installed
|
||||
package scripts by invoking: e.g., `npm-run gulp build`
|
||||
(see [npm-run project page](https://github.com/timoxley/npm-run) for more details).
|
||||
|
||||
|
||||
*Option 3*: defining a bash alias like `alias nbin='PATH=$(npm bin):$PATH'` as detailed in this
|
||||
[Stack Overflow answer](http://stackoverflow.com/questions/9679932/how-to-use-package-installed-locally-in-node-modules/15157360#15157360) and used like this: e.g., `nbin gulp build`.
|
||||
|
||||
## Installing Bower Modules
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user