Compare commits

..

213 Commits
4.3.0 ... 4.2.6

Author SHA1 Message Date
b69f0faee6 docs: add changelog for 4.2.6 2017-07-07 21:48:49 -07:00
9c4fcd370a release: cut the 4.2.6 release 2017-07-07 21:46:08 -07:00
8b8b4cb794 refactor(upgrade): move shareable functionality to UpgradeHelper class (#17971)
This functionality can be potentionally re-used by the dynamic version.
2017-07-07 21:11:55 -07:00
53378749ef fix(upgrade): fix transclusion on upgraded components (#17971)
Previously, only simple, single-slot transclusion worked on upgraded components.
This commit fixes/adds support for the following:

- Multi-slot transclusion.
- Using fallback content when no transclusion content is provided.
- Destroy unused scope (when using fallback content).

Fixes #13271
2017-07-07 21:11:46 -07:00
3e61bf76c4 Revert "fix(upgrade): fix transclusion on upgraded components (#17971)"
This reverts commit 30beb52673.
2017-07-07 21:07:18 -07:00
b4fab438ea Revert "refactor(upgrade): move shareable functionality to UpgradeHelper class (#17971)"
This reverts commit 620c2161f6.
2017-07-07 21:07:01 -07:00
426191bc4e build: update the public API golden files 2017-07-07 20:23:48 -07:00
6307581f54 fix(compiler): fix merge error 2017-07-07 19:10:17 -07:00
591d894329 feat(compiler): adds support for quoted object keys in the parser 2017-07-07 19:06:23 -07:00
48772f189f refactor: fix typos (#18000) 2017-07-07 17:23:15 -07:00
70b1ff11d5 ci(aio): only deploy latest commits to staging/production (#17988)
Fixes #17941
2017-07-07 17:23:15 -07:00
f1c9197e07 feat(aio): use new ngo (#17977) 2017-07-07 17:23:14 -07:00
991f8ad687 fix(compiler-cli): fix relative source paths on windows for extracted msg (#17915)
Fixes #16639
2017-07-07 17:23:13 -07:00
efa45b9299 docs(aio): Update NgStyle and NgClass to use quoted keys 2017-07-07 17:23:13 -07:00
45ae14c461 fix(compiler): emits quoted keys only iff they are quoted in the original template
fixes #14292
2017-07-07 17:23:12 -07:00
5ea9b62d58 fix(compiler): fix types 2017-07-07 17:23:11 -07:00
276357780c fix(compiler): remove i18n markup even if no translations (#17999)
Fixes #11042
2017-07-07 17:23:11 -07:00
2108c23b42 revert: "refactor(compiler-cli): remove the dependency on fs in codegen.ts (#17738)"
This reverts commit b116901400.
2017-07-07 17:23:10 -07:00
d48b7d3a8e fix(animations): ensure :animating queries collect previous animation elements properly 2017-07-07 17:23:10 -07:00
00c97417e3 fix(animations): properly detect state transition changes for object literals 2017-07-07 17:23:09 -07:00
16e6c6e7f9 refactor: move DOCUMENT from platform/browser to common 2017-07-07 17:23:08 -07:00
f9c4c3710e ci: disable sauce-connect logging for realz (#17995)
follow up on #17947
2017-07-07 17:23:08 -07:00
eb90ba1321 fix(aio): activate ServiceWorker updates asap (#17699)
Previouly, whenever a new ServiceWorker update was detected the user was
prompted to update (with a notification). This turned out to be more distracting
than helpful. Also, one would get notifications on all open browser tabs/windows
and had to manually reload each one in order for the whole content (including
the app) to be updated.

This commit changes the update strategy as follows:
- Whenever a new update is detected, it is immediately activated (and all
  tabs/windows will be notified).
- Once an update is activated (regardless of whether the activation was
  initiated by the current tab/window or not), a flag will be set to do a
  "full page navigation" the next time the user navigates to a document.

Benefits:
- All tabs/windows are updated asap.
- The updates are applied authomatically, without the user's needing to do
  anything.
- The updates are applied in a way that:
  a. Ensures that the app and content versions are always compatible.
  b. Does not distract the user from their usual workflow.

NOTE:
The "full page navigation" may cause a flash (while the page is loading from
scratch), but this is expected to be minimal, since at that point almost all
necessary resources are cached by and served from the ServiceWorker.

Fixes #17539
2017-07-07 17:23:07 -07:00
9e626415eb fix(aio): home & marketing styles cleanup (#17926) 2017-07-07 17:23:07 -07:00
f632ca4284 refactor(common): replace Object.assign with the spread operator (#17982)
`Object.assign` is not available in all supported browsers and one had to
provide a polyfill. This commit replaces `Object.assign` with the spread
operator (`...`), which TypeScript will transpile to ES5-compatible code.
2017-07-07 17:23:06 -07:00
620c2161f6 refactor(upgrade): move shareable functionality to UpgradeHelper class (#17971)
This functionality can be potentionally re-used by the dynamic version.
2017-07-07 17:21:38 -07:00
30beb52673 fix(upgrade): fix transclusion on upgraded components (#17971)
Previously, only simple, single-slot transclusion worked on upgraded components.
This commit fixes/adds support for the following:

- Multi-slot transclusion.
- Using fallback content when no transclusion content is provided.
- Destroy unused scope (when using fallback content).

Fixes #13271
2017-07-07 17:21:35 -07:00
fe09e10d02 fix(language-service): ignore hover of symbols not in the TypeScript program (#17969)
Fixes: #17965
2017-07-07 17:21:35 -07:00
ee7d134d8e fix(tsc-wrapped): emit exports metadata in flat modules (#17893)
Fixes: #17888
2017-07-07 17:21:34 -07:00
2ab90578f3 fix(language-service): do not crash when hovering over a label definitions (#17974)
Fixes: #17972
2017-07-07 17:21:33 -07:00
8f7cce38f8 fix(router): encode URLs the same way AngularJS did (closer to spec) (#17890)
fixes #16067
2017-07-07 17:21:33 -07:00
a3180937c1 fix(core): fix re-insertions in the iterable differ (#17891)
fixes #17852
2017-07-07 17:21:32 -07:00
3d52675c1b refactor(compiler-cli): remove the dependency on fs in codegen.ts (#17738) 2017-07-07 17:21:32 -07:00
14afb9d314 feat(aio): serve-and-sync command (#17850) 2017-07-07 17:21:31 -07:00
62b973773f ci: allow chuck to approve animations (#17940) 2017-07-07 17:21:30 -07:00
8bdc921502 docs(aio): add instructions for example e2e tests to README (#17819) 2017-07-07 17:21:30 -07:00
cd99c51a40 docs(aio): add DevExtreme to the UI Components section of resources.json (#17939) 2017-07-07 17:21:29 -07:00
3fb93fdb5e build: disable sauce connect logging (#17947)
this change is expected to mitigate the flakes on CI that occur when
we cat the log in the print-logs.sh
2017-07-07 17:21:29 -07:00
cf5752738a fix(animations): properly handle cancelled animation style application 2017-07-07 17:21:28 -07:00
00de9ff531 fix(animations): properly cleanup query artificats when animation construction fails 2017-07-07 17:21:28 -07:00
12a2099265 fix(router): export missing UrlMatcher and UrlMatchResult types
Fixes #15140
2017-07-07 17:21:27 -07:00
cdc0c8ff58 ci: add angular.io content related groups to .pullapprove.yaml 2017-07-07 17:21:26 -07:00
14fbc2e7c0 build(aio): upgrade to angular@4.2.4 and zone.js 2017-07-07 17:21:26 -07:00
ea8fe9316e build: upgrade yarn to 0.24.6 2017-07-07 17:21:25 -07:00
b658afc3de ci: add Chuck to the owners of core (#17892) 2017-07-07 17:21:25 -07:00
706a48e9c1 docs: add animations package to CONTRIBUTING.md (#15413) 2017-07-07 17:21:24 -07:00
Joe
d472aa201a docs(changelog): Correct typos (#16966) 2017-07-07 17:21:23 -07:00
6df57adab3 docs: README - use correctly encoded link for npm badge (#17707)
closes #14990, an old PR that was approved but to old/difficult to merge
2017-07-07 17:21:23 -07:00
54df1e287d docs: shefali edits to Authors Style Guide (#17853) 2017-07-07 17:21:22 -07:00
6d55a807cd perf(core): refactor NgZone, decrease size by 1.2Kb (#17773)
- Remove getters
- Hide private methods for better property renaming

```
497893 May 31 11:26 core.umd.js
718073 May 31 11:26 core.umd.js.map
217108 May 31 11:26 core.umd.min.js
575092 May 31 11:26 core.umd.min.js.map
```

```
495594 May 31 11:28 core.umd.js
716943 May 31 11:28 core.umd.js.map
215826 May 31 11:28 core.umd.min.js
574401 May 31 11:28 core.umd.min.js.map
```
diff: 1,282
2017-07-07 17:21:22 -07:00
670f2e5599 ci(aio): raise polyfill payload limits 2017-07-06 14:48:32 +01:00
408bb89e8f ci(aio): Fix the payload script only check for changes in aio/scripts 2017-07-03 10:41:01 +01:00
ed45830044 docs(aio): update quickstart to latest cli 2017-07-03 08:25:50 +01:00
37b200010b ci(aio): use valid database path for Firebase payload size upload
Firebase does not allow `.` in the path, so when trying to upload payload size
data for branches like `4.2.x`, the following error is thrown:

```
HTTP Error: 400, Invalid path: Invalid token in path
```

This commit fixes it by replacing `.` with `_` in branch names.
2017-07-03 08:20:31 +01:00
571adbe40e docs(aio): fix visual-studio-2015 typo
This fixes #17283
2017-07-03 08:13:10 +01:00
10bfa8a11e ci(aio): avoid printing too large PWA report (and restore print-logs.sh)
There have been some issues lately with Travis jobs failing due to
`print-log.sh`. This is likely due to trying to print the Lighthouse PWA report,
which is too large.
This commit stops printing that report (since it was rarely used and is pretty
easy to acquire when needed) and restores the `print-logs.sh` script (that was
temporarily removed with dfcca66fd).
2017-07-01 14:41:05 +01:00
c460757d2e docs(aio): Add resources contributor info 2017-07-01 14:39:04 +01:00
1a476b81ab ci(aio): use correct URL for preview in PWA score test
In 4268c8289, the preview URLs were changed to not use the whole SHA, but just
the 7 first characters.
2017-06-30 13:03:08 +01:00
27b87e893c docs(aio): fix quickstart-cli bad HTML escape chars 2017-06-30 12:23:38 +01:00
7080fccb38 fix(aio): restore visible sidenav keyboard focus; shift TOC up
closes #17665
Restores keyboard focus that was removed by commit b8b91d3.
Raises the right-TOC by 20px (96px->76px) because was too far down.

To prevent keyboard focus on hidden child nodes,
also collapses inner expanded nodes when parent node is collapsed.
The implicit parent node of top nodes is always expanded.
2017-06-30 11:59:49 +01:00
54f96bfe27 fix(aio): do not include hidden content in window title
The window title is derived based on the current document's `<h1>` heading. Such
headings may contain hidden/non-visible content (e.g. textual name of font
ligatures: `<i class="material-icons">link</i>`) that should not be included in
the title.

This commit fixes this by using `innerText` (instead of `textContent`) to
extract the visible text from the `<h1>` heading. It will still fall back to
`textContent` on browsers that do not support `innerText` (e.g. Firefox 44).

Fixes #17732
2017-06-30 11:55:00 +01:00
61e6618429 docs: add changelog for 4.2.5 2017-06-29 17:17:00 -07:00
e1fea47400 release: cut the 4.2.5 release 2017-06-29 17:15:50 -07:00
f31b0d664d fix(core): add needed closure compiler warning suppression 2017-06-29 17:08:12 -07:00
79b634692a fix(animations): properly collect :enter nodes that exist within multi-level DOM trees
Closes #17632
2017-06-29 17:08:12 -07:00
6909171c0a fix(animations): do not validate style overlap errors in different transitions 2017-06-29 17:08:12 -07:00
ec4ae60fb8 fix(animations): do not remove container nodes when children are queried by a parent animation
Closes #17746
2017-06-29 17:08:12 -07:00
7559b7898e fix(animations): do not delay style() values before a stagger() runs
Closes #17412
2017-06-29 17:08:12 -07:00
7728d7efc6 ci: remove print-logs.sh to try to fix failing builds on Travis 2017-06-29 15:33:21 -07:00
2c1ef08466 docs: Updated router guide content and examples for paramMap
and queryParamMap, tracing, and incidental improvements.
closes #16991 and #16259 which it also fixes.
2017-06-29 15:29:51 -07:00
5f1dcfc228 feat(aio): use shorter URLs for previews
Use the 7 first characters of the 40-chars long SHAs for shorter/cleaner URLs.
The collision probability is extremely low (since all SHAs are further
"namespaced" under the corresponding PR). In case of a collision, the second PR
will not be deployed, in order to avoid overwriting the original build.

(This is a design decision to keep the implementation simple. It can be changed
later if necessary.)
2017-06-29 15:29:51 -07:00
f9f2b23afc fix(aio): clean up non-public previews
The previous clean-up code for PR directories on the preview server assumed that
all directories were named after the PR number. With the changes introduced
in #17640 it is possible to have PR directories that do not follow that naming
convention (e.g. "non-public" directories).

This PR ensures that both public and non-public directories are removed when
cleaning up.
2017-06-29 15:29:51 -07:00
ad3661cff7 feat(aio): cross reference readme and docs styleguide 2017-06-29 15:29:51 -07:00
804173c8a6 docs(aio): update punctuation mark added in TOOLS guide 2017-06-29 15:29:51 -07:00
a0c790e7bd docs: correct grammar in CONTRIBUTING.md 2017-06-29 15:29:51 -07:00
7205d7a568 fix(aio): build scripts-js before creating a new docker image for the preview server
When creating a new docker image for the preview server, the TypeScript source
code in `scripts-js/` is not copied over. Instead only the generated JavaScript
core in `scripts-js/dist/` are. Because of that, it is necessary to have run
`yarn build` before running `docker build`, so that the new docker image
contains the latest changes in `scripts-js/`.

This was previously part of the `create-image.sh` script, but was accidentally
removed in 21d213dfc.
2017-06-27 15:57:50 -04:00
e57eec9b13 ci: add npm postinstall back to the lint step so node_modules doesn't get out of date 2017-06-27 15:57:44 -04:00
5e1b339f46 docs: fix spelling of case variants in naming.md
unify variant forms of spelling letter cases (upper-case, uppercase, lower case)
2017-06-27 15:57:38 -04:00
6b23e2f427 build(aio): boilerplate wont be removed by default now 2017-06-27 15:57:32 -04:00
08dba3f93b fix(aio): Typo in Setup Anatomy documentation page
Solves #17076
2017-06-27 15:57:27 -04:00
2624fa5ae2 docs(aio): fix typo 2017-06-27 15:57:22 -04:00
a1458d9cb9 docs(aio): animations typos fixed 2017-06-27 15:57:17 -04:00
ffbdf74e92 docs(aio): cleanup rollup-config script 2017-06-23 12:11:57 -07:00
589826f5e5 ci(aio): Change the firebase token 2017-06-23 12:11:56 -07:00
6f5e6374f8 ci(aio): address comments 2017-06-23 12:11:56 -07:00
a309d62806 ci(aio): fix test 2017-06-23 12:11:56 -07:00
9699c02642 ci(aio): remove umd 2017-06-23 12:11:56 -07:00
6040c66e95 ci(aio): debug 2017-06-23 12:11:55 -07:00
347fec5e8c ci(aio): add back deploy-preview 2017-06-23 12:11:55 -07:00
c7bbf0573a ci(aio): updated limits 2017-06-23 12:11:55 -07:00
c2fda10a73 ci(aio): rename limits file and address comments 2017-06-23 12:11:55 -07:00
b38edeb055 ci(aio): Also track umd.min.js file size 2017-06-23 12:11:54 -07:00
2407beb17e ci(aio): Add payload size limit file 2017-06-23 12:11:54 -07:00
b6418039e0 ci(aio): upload aio payload size to firebase
ci(aio): Add timestamp and change data
2017-06-23 12:11:54 -07:00
7dd7722220 build: No longer need to bazel build twice 2017-06-23 12:11:54 -07:00
9b14f62387 build: circleci workflows to run lint in parallel 2017-06-23 12:11:54 -07:00
107655e43f refactor(aio): provide fallback values for secrets (useful during dev) 2017-06-23 12:11:53 -07:00
162bddf0b8 refactor(aio): enable -u flag on preview server scripts 2017-06-23 12:11:53 -07:00
81c18b7241 docs(aio): document preview server HTTP status codes 2017-06-23 12:11:53 -07:00
0c4b561999 ci(aio): deploy previews for all PRs
PRs that could not be automatically verified will not be publicly accessible,
until manually verified.
2017-06-23 12:11:53 -07:00
563577eeab test(aio): add e2e tests for non-public previews 2017-06-23 12:11:53 -07:00
17e000a678 feat(aio): enable previews for any PR
This commit introduces the ability to show previews for PRs by any author. It works as follows:

- The build artifacts of all PRs are uploaded to the preview server.
- Automatically verified PRs (i.e. from trusted authors or having a specific label) are deployed and
  publicly accessible as usual.
- PRs that could not be automatically verified are stored for later use (after re-verification).
- A PR can be marked as "trusted" and make its preview publicly accessible by adding the GitHub
  label specified in the `AIO_TRUSTED_PR_LABEL` env var of the preview server.

At the moment, there is no automatic mechanism for notifying the preview server about changes to the
PR's verification status. The PR's "visibility" will be checked and updated every time a new build
is uploaded.
2017-06-23 12:11:52 -07:00
b8806a656f refactor(aio): simplify preview server build events 2017-06-23 12:11:52 -07:00
ee5cd52d5f test(aio): add missing unit test for preview server 2017-06-23 12:11:52 -07:00
84a72b774e test(aio): fix preview server tests on Windows 2017-06-23 12:11:52 -07:00
9e28f58ec8 build(aio): upgrade preview server dependencies 2017-06-23 12:11:51 -07:00
10e2db000c fix(aio): prefix location.assign with window.
No practical effect but clears the TS compiler warning.
2017-06-23 12:11:51 -07:00
098d35ef64 fix(aio): fix links on /about in Firefox
Fixes #17661
2017-06-23 12:11:51 -07:00
d5a8f6ebfe refactor(aio): clean up aio-contributor template and styles 2017-06-23 12:11:51 -07:00
9415e6603f fix(aio): preserve newlines when copying code
Before 4f37f8643, we were using `innerText` to retrieved the code content for
copying. This preserved the text layout (including newlines), but suffered from
other issues (browser support, performance). With 4f37f8643 we switched to
`textContent`, which works well except in the following case:
When `prettify` formats the code to have line numbers, it removes the newlines
and uses `<li>` elements instead. This affects `textContent`.

This commit fixes this by keeping a reference of the code as text and using that
for copying.

Fixes #17659
2017-06-23 12:11:50 -07:00
927ea2462e ci: test merge commits on circle
We expect this behavior because it's what Travis does. Also it's better because we want
to test what happens if we merge the PR, not the status of the PR branch.
2017-06-23 12:11:50 -07:00
1d6465b596 docs(aio): Update resources.json - fixed language error 2017-06-23 12:11:50 -07:00
58012df46f docs(aio): update resources - Add Compodoc - documentation tool for Angular app 2017-06-23 12:11:50 -07:00
21c5b177ef fix(aio): fix topbar nav-item focus style
Fixing it requires upgrading `@angular/material` to v2.0.0-beta.7.

Fixes #17216
2017-06-23 12:11:50 -07:00
bc10291d42 docs: remove unnecessary newline 2017-06-23 12:11:49 -07:00
9344c86242 docs: fix “under to hood” typo in changelog.md
closes #14856
2017-06-23 13:18:45 -04:00
6b5b3a7269 docs(aio): resources.json - replace “Angular 2” in titles/descriptions
closes #16965
2017-06-23 13:18:41 -04:00
58c5e86209 docs(aio): update Bash for Windows info
As of the Creator's Update, Bash on Ubuntu on Windows supports all npm commands. 
https://blogs.msdn.microsoft.com/commandline/2017/04/11/windows-10-creators-update-whats-new-in-bashwsl-windows-console/
Update setup documentation accordingly.
2017-06-23 13:18:37 -04:00
1657fa756f docs(aio): minor fixes for the upgrade guide
Fixes #17093
2017-06-23 13:18:33 -04:00
2ff85c6790 fix(aio): add missing WeakMap polyfill 2017-06-23 13:18:26 -04:00
353de5191c docs(aio): replace “Angular 1” in upgrade phonecat script 2017-06-22 23:26:08 -04:00
ea5ac89b50 docs(aio): fix promises link in toh-pt4
closes #16050
2017-06-22 23:26:03 -04:00
f5ce60e6bc docs(aio): remove "_" from private property name
follow best practices from the documentation: https://angular.io/guide/styleguide#properties-and-methods
2017-06-22 23:25:58 -04:00
7a2903993a docs(aio): correct typos in Tour of Heroes HeroSearchService 2017-06-22 23:25:53 -04:00
a29a0df183 docs(aio): Added resources to UI Components (#17635)
docs(aio): Added resources to UI Components
2017-06-22 23:25:48 -04:00
746fed453a docs(aio): remove min, max & move built-in validators paragraph 2017-06-22 23:25:43 -04:00
64e1b9cc9a docs(aio): added link to Russian Angular Courses 2017-06-22 23:25:39 -04:00
0947f4a358 docs: add documentation for LTS versions 2017-06-22 23:25:35 -04:00
f10d5a5572 docs: add missing colon in the Constants section of NAMING.md 2017-06-22 23:25:30 -04:00
93f57d7d70 docs: Fix Stack Overflow being repeatedly misspelt as a single word
(To see that it is two words, see e.g. the page title at https://stackoverflow.com/ or the
spelling at http://stackoverflow.com/tour)
2017-06-22 23:24:31 -04:00
af1c4db0e4 docs: Fix some non-code being formatted as code in the router docs 2017-06-22 23:24:18 -04:00
4f43029570 docs: add changelog for 4.3.0-beta.0 2017-06-22 23:24:06 -04:00
8a09015211 docs: add changelog for 4.2.4 2017-06-21 17:01:14 -07:00
f4b7bf7e38 release: cut the 4.2.4 release 2017-06-21 16:57:31 -07:00
d20313698d revert: feat(core): update zone.js to 0.8.12
This reverts commit 25234227ec.

Mistakenly cherry picked a feature commit which does not belong on a
patch branch.
2017-06-21 16:52:14 -07:00
74954f1a8d docs(aio): ward's changes 2017-06-21 16:28:19 -07:00
368aed087c docs(aio): change to overall prose 2017-06-21 16:28:19 -07:00
975341a77e docs(aio): create author style guide 2017-06-21 16:28:19 -07:00
c112232fa3 fix(compiler): avoid emitting self importing factories
Fixes: #17389
2017-06-21 16:20:05 -07:00
36348325de feat(aio): display “Searching ..." while building search index
closes #15923
2017-06-21 14:31:59 -07:00
fdbe62f112 fix(aio): fix patch script on windows
Running the patch script on Windows (with `patch` available) yields an invalid syntax warning, and does not apply patches.

```
kamik@T460p MINGW64 /d/work/angular/aio (master)
$ yarn postinstall
yarn postinstall v0.24.6
$ node tools/cli-patches/patch.js && uglifyjs node_modules/lunr/lunr.js -c -m -o src/assets/js/lunr.min.js --source-map
The syntax of the command is incorrect.
Done in 1.52s.
```
2017-06-21 13:51:47 -07:00
af9ba91e5c build(aio): ensure all doc tests are run
It is not possible to run all the docs tests directly via the  jasmine CLI.
Instead we now have a small script that will run jasmine via its library.
2017-06-21 13:51:27 -07:00
b840ec3684 feat(aio): select contributor group with URL “about?group=gde”
closes #17656
also adds test for ContributorListComponent.
2017-06-21 13:51:09 -07:00
b38552f36e fix(aio): restore component-styles/exclude hidden children 2017-06-21 11:33:33 -07:00
572885dcc8 docs(aio): fix numbered list in testing.md
This list renders OK in the github UI but not at https://angular.io/guide/testing#setup
2017-06-20 16:43:46 -07:00
33906489ad fix(tsc-wrapped): skip collecting metadata for default functions
Fixes: #17518
2017-06-20 14:23:04 -07:00
59299de374 fix(compiler-cli): find lazy routes in nested module import arrays
Fixes: #17531
2017-06-20 14:21:35 -07:00
f99793700f docs(aio): update resource listing 2017-06-20 12:58:20 -07:00
77860a0023 fix: argument destructuring sometimes breaks strictNullChecks
Destructuring of the form:

function foo({a, b}: {a?, b?} = {})

breaks strictNullChecks, due to the TypeScript bug https://github.com/microsoft/typescript/issues/10078.
This change eliminates usage of destructuring in function argument lists in cases where it would leak
into the public API .d.ts.
2017-06-20 12:56:21 -07:00
93d834a0b2 refactor(core): remove toString() method from DefaultKeyValueDiffer
toString() from DefaultKeyValueDiffer is only used in tests and should not
be part of the production code. toString() methods from differs add
~ 0.3KB (min+gzip) to the production bundle size.
2017-06-20 12:55:35 -07:00
63a5f33e99 fix(language-service): infer any ngForOf of type any
Fixes: #17611
2017-06-20 12:05:10 -07:00
20eb5cfd59 fix(language-service): rollup tslib into the language service package
Fixes: #17614
2017-06-20 11:50:42 -07:00
4ab7353a9f fix(forms): roll back breaking change with min/max directives
With 4.2, we introduced the min and max validator directives. This was actually a breaking change because their selectors could include custom value accessors using the min/max properties for their own purposes.

For now, we are rolling back the change by removing the exports. At the least, we should wait to add them until a major version. In the meantime, we will have further discussion about what the best solution is going forward for all validator directives.

Closes #17491.

----

PR #17551 tried to roll this back, but did not remove the dead code. This failed internal tests that were checking that all declared directives were used.
This PR rolls back the original PR and commit the same as #17551 while also removing the dead code.
2017-06-20 09:08:25 -07:00
eb23460e09 revert: fix(forms): temp roll back breaking change with min/max directives
This reverts commit 232bd9395d.
2017-06-20 09:08:25 -07:00
2e8efdaffa fix(aio): leave results panel open when opening search result on new page
Fixes #17580
2017-06-19 15:12:57 -07:00
3cfb13f746 build(aio): upgrade jasmine to v2.6.4
This version fixes the DISCONNECTED errors (described in #17543) and removes the
need to the workaround (8af203c).
The relevant jasmine commit is jasmine/jasmine@c60d66994.
2017-06-19 15:12:38 -07:00
7de1ae2be8 fix(router): update the version placeholder so that it gets replaced during the build
Fixes #17403
2017-06-19 15:11:22 -07:00
341b812a10 docs(TRIAGE_AND_LABELS): update labels to reflect the current state 2017-06-19 14:54:15 -07:00
09512c7770 docs(RELEASE_SCHEDULE): fix version numbers for August/September releases 2017-06-19 14:46:49 -07:00
1ec0a53fec docs(RELEASE_SCHEDULE): update the release schedule w/ recent regression patch releases 2017-06-19 13:20:53 -07:00
3607a48a08 docs: remove aio from changelog 2017-06-19 11:28:17 -07:00
62957fa515 fix(aio): switch from innerText to textContent to support older browsers
`innerText` is not supported in Firefox prior to v45. In most cases (at least
the ones we are interested in), `innerText` and `textContent` work equally well,
but `textContent` is more performant (as it doesn't require a reflow).

From [MDN][1] on the differences of `innerText` vs `textContent`:

> - [...]
> - `innerText` is aware of style and will not return the text of hidden
>   elements, whereas `textContent` will.
> - As `innerText` is aware of CSS styling, it will trigger a reflow, whereas
>   `textContent` will not.
> - [...]

[1]: https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent#Differences_from_innerText

Fixes #17585
2017-06-19 10:32:58 -07:00
ab81c1c068 build(aio): update @angular/service-worker to 1.0.0-beta.16
This version includes a fix for potential cache corruption and invalid redirect behavior in rare cases.
2017-06-16 13:34:00 -07:00
25234227ec feat(core): update zone.js to 0.8.12 2017-06-16 12:13:31 -07:00
71a8ef5a15 fix(aio): improve no-javascript screen 2017-06-16 12:05:03 -07:00
4dd6deca16 fix(aio): remove unused icon reference and file
These icons are not there (and never were afaict).

Fixes #17561
2017-06-16 12:04:15 -07:00
f8db05ef7d fix(aio): correctly redirect /docs/ts/latest and /styleguide
Previously, we had redirect rules for Firebase for `/docs/ts/latest` and
`/styleguide`, but once the ServiceWorker was activated, it would take over
routing and rewrite these requests to `/index.html`.
This commit fixes it by excluding them from ServiceWorker routing.

Fixes #17542
2017-06-16 12:03:24 -07:00
623b9c6e58 test(compiler): fix typo "mamespace" 2017-06-16 11:17:49 -07:00
8a547eeee0 docs: add changelog for 4.2.3 2017-06-16 09:41:51 -07:00
4211432fc8 release: cut the 4.2.3 release 2017-06-16 09:38:29 -07:00
b8c39cdf71 fix(forms): temp roll back breaking change with min/max directives
With 4.2, we introduced the min and max validator directives. This was actually a breaking change because
their selectors could include custom value accessors using the min/max properties for their own purposes.

For now, we are rolling back the change by removing the exports.

Closes #17491.
2017-06-16 09:32:19 -07:00
9c7a84de51 docs(aio): re-add biography entry for devversion
With SHA 2c3e948e61 the biography of Paul Gschwendtner has been accidentally removed.

This re-adds the biography entry (picture still present) as requested on Slack.
2017-06-16 07:56:30 +01:00
dbc6a4cb12 build(aio): do not fail if check-env for the main angular project fails
Fixes #17434
2017-06-16 07:53:26 +01:00
784410e3c8 build: fix link to DEVELOPER.md in check-environment.js 2017-06-16 07:53:25 +01:00
90a5a1ef43 build(aio): remove dependency on build artifacts from parent folder 2017-06-16 07:53:25 +01:00
301f99cd6c build: remove redundant line
The same value is set a few lines below.
2017-06-16 07:53:25 +01:00
64e63b9422 fix(aio): add missing redirect rule for /styleguide
Fixes #17542
2017-06-16 07:52:29 +01:00
b192dd5761 fix(animations): remove duplicate license header 2017-06-15 14:51:40 -07:00
96aa3bb135 docs(aio): update about page 2017-06-15 22:15:59 +01:00
8abc1df2c1 feat(aio): add iphone pwa features 2017-06-15 22:15:59 +01:00
f5eb528a5c docs(aio): incorporate Ward's comments 2017-06-15 22:15:58 +01:00
5cf06a9f3f docs(aio): add short section on built-in validators, copy edits 2017-06-15 22:15:58 +01:00
ab90f63575 fix(aio): do not log messages in production
In dev mode, all messages passed to `Logger` will be logged.
In production mode, only warnings and errors will be logged.

Fixes #17453
2017-06-15 22:15:57 +01:00
150d271f79 refactor(aio): remove unused Logger dependencies 2017-06-15 22:15:57 +01:00
a686eb2c9e build(aio): extra redirect rule 2017-06-15 22:15:57 +01:00
bfa788935a docs(aio): http guide shows how to import toPromise operator from rxjs
Solves #17454
2017-06-15 22:15:56 +01:00
64fa100a71 fix(aio): specify large image for PWA splash-screen 2017-06-14 09:45:37 -07:00
5fae987bfa build(aio): upgrade lighthouse to v2.1 2017-06-14 09:45:37 -07:00
9c4cda1c7d ci(aio): fail the build if the PWA score is too low
Previously, there was an issue with testing the PWA score on staging and failing
the build was temporarily disabled. It works now, so we need to enable failing
the build is the score drops below some threshold.
2017-06-14 09:45:37 -07:00
d9cbe56b63 test(aio): add async beforeEach to prevent Chrome disconnects
Related to 3d5f520ff0 from #17405
2017-06-14 09:45:37 -07:00
86df7108b0 fix(aio): always cover the whole footer with its background
Fixes #17465
2017-06-14 09:45:37 -07:00
e7a4f92be7 fix(aio): fix trackBy demo in template-syntax article 2017-06-14 09:45:36 -07:00
76af452d29 test(platform-server): fix and re-enable integration tests 2017-06-14 09:45:36 -07:00
11dfb685f4 ci: update github templates (#17466) 2017-06-13 15:27:35 -07:00
d363aa0aa4 fix(aio): make the footer links clickable on all browsers
The footer background (implemented via `footer:after`) had a higher `z-index`
than other footer elements and was obscuring the footer links on certain
browsers (Firefox, Edge, IE), which made them unclickable.
This commit lowers the index of `footer:after`, so that links are clickable on
these browsers.

Fixes #17460
2017-06-13 15:27:35 -07:00
209d74c342 ci: disable platform-server integration test as it is currently broken 2017-06-13 15:27:35 -07:00
fec8f6febe refactor(compiler): remove duplicate code 2017-06-13 15:27:35 -07:00
ee9daaf4c8 ci: use npm_install for bazel
using yarn_install polluted our node_modules cache because it disregards the npm_shrinkwrap.json
2017-06-13 15:27:35 -07:00
4164369db4 docs(aio): update typescript for examples/webpack to same as cli 2017-06-13 11:35:17 -07:00
747b6a61b8 build(aio): add staging environment
You can now specify what environment you are building
by add it to the `yarn build` command. For example:

```
yarn build -- --env=stage
```

Moreover the `deploy-to-firebase.sh` script will automatically apply the
appropriate environment.
2017-06-13 11:35:17 -07:00
9ed836c939 docs(aio): i18n guide - updates for v4 2017-06-13 11:35:17 -07:00
e4c82443f9 build(aio): increase docs integration test timeouts
The API docs tests have very variable run times, depending
upon the build environment.
This change doubles their test timeout values to prevent
false-negative failures.
2017-06-13 11:35:17 -07:00
a2f232166b fix(aio): fix scrolling to elements near the bottom of the page
Previously, we always assumed that elements would be scrolled to the top of the
page, when calling `element.scrollIntoView()`. This is not true for elements
that cannot be scrolled to the top, e.g. when the viewport height is larger than
the height of the content after the element (common for small sections near the
end of the page).
In such cases, we would unnecessarily scroll up to account for the static
toolbar, which was unnecessary (since the element was not behind the toolbar
anyway) and caused ScrollSpy to fail to identify the scrolled-to section as
active.

This commit fixes it by ensuring that we do not scroll more than necessary in
order to align the top of the element with the bottom of the toolbar.

Fixes #17452
2017-06-13 11:35:17 -07:00
668f9ede65 fix(aio): show search results when search box gets focus
Due to a previous commit, the search was only triggered
if the query changed, and not when the search box regained
focus.
2017-06-13 11:35:17 -07:00
b784829512 fix(aio): use locally hosted lunr library
The library is downloaded from npm but then
copied into the assets folder (and ignored by git)
as part of the postinstall step.
2017-06-13 11:35:17 -07:00
ad4fee7053 fix(aio): make search results better
* update to latest version of lunr search
* add trailing wildcard to search terms to increase matches
* fix unwanted error when escape was pressed

Closes #17417
2017-06-13 11:35:17 -07:00
2d31e17251 fix(aio): fix buttons in "Home" and "Features"
Using `<a>` inside a `<button>` is not syntactically valid HTML and breaks on
some browsers (e.g. Firefox). Furthermore, clicking the button doesn't do
anything unless you click on the link (e.g. clicking on the padding around the
link does nothing), which is inconvenient and confusing.

Fixes #17448
2017-06-13 11:35:17 -07:00
39cff565ee docs: clarify when non-null-assertion-operator is needed in template-syntax 2017-06-13 11:35:17 -07:00
203c5ba1b3 fix(aio): ensure that API filter page can display 3 columns in wide view
Fixes #17251
2017-06-13 11:35:17 -07:00
eda7bb5c3e fix(aio): tidy up layout of api filter page
* Remove the "info-banner" styling from the filters.
* Fix alignment of the search box on a narrow screen (closes #17395)
* Remove unnecessary whitespace before section headers
2017-06-13 11:35:17 -07:00
1480a30050 docs(aio): rename Upgrade docs to cheatsheet 2017-06-13 11:35:16 -07:00
d6087f75e2 fix(aio): remove outline from search input on focus
Closes #17396
2017-06-13 11:35:16 -07:00
dc084a5bdf build(aio): make deploy-to-firebase.sh executable 2017-06-12 16:13:46 -07:00
253 changed files with 6778 additions and 15100 deletions

View File

@ -1,31 +1,18 @@
# Configuration file for https://circleci.com/gh/angular/angular
# Note: YAML anchors allow an object to be re-used, reducing duplication.
# The ampersand declares an alias for an object, then later the `<<: *name`
# syntax dereferences it.
# See http://blog.daemonl.com/2016/02/yaml.html
# To validate changes, use an online parser, eg.
# http://yaml-online-parser.appspot.com/
# Settings common to each job
anchor_1: &job_defaults
defaults: &defaults
working_directory: ~/ng
docker:
- image: angular/ngcontainer
# After checkout, rebase on top of master.
# Similar to travis behavior, but not quite the same.
# See https://discuss.circleci.com/t/1662
anchor_2: &post_checkout
post: git pull --ff-only origin "refs/pull/${CI_PULL_REQUEST//*pull\//}/merge"
version: 2
jobs:
lint:
<<: *job_defaults
<<: *defaults
steps:
- checkout:
<<: *post_checkout
# After checkout, rebase on top of master.
# Similar to travis behavior, but not quite the same.
# See https://discuss.circleci.com/t/1662
post: git pull --ff-only origin "refs/pull/${CI_PULL_REQUEST//*pull\//}/merge"
- restore_cache:
key: angular-{{ .Branch }}-{{ checksum "npm-shrinkwrap.json" }}
@ -34,10 +21,9 @@ jobs:
- run: ./node_modules/.bin/gulp lint
build:
<<: *job_defaults
<<: *defaults
steps:
- checkout:
<<: *post_checkout
- checkout
- restore_cache:
key: angular-{{ .Branch }}-{{ checksum "npm-shrinkwrap.json" }}

View File

@ -1,14 +1,14 @@
<!--
PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATION.
ISSUES MISSING IMPORTANT INFORMATION MAY BE CLOSED WITHOUT INVESTIGATION.
ISSUES MISSING IMPORTANT INFORMATION MIGHT BE CLOSED WITHOUT INVESTIGATION.
-->
## I'm submitting a...
## I'm submitting a ...
<!-- Check one of the following options with "x" -->
<pre><code>
[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report <!-- Please search GitHub for a similar issue or PR before submitting -->
[ ] Regression (behavior that used to work and stopped working in a new release)
[ ] Bug report <!-- Please search github for a similar issue or PR before submitting -->
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
@ -32,7 +32,7 @@ https://plnkr.co or similar (you can use this template as a starting point: http
<!-- Describe the motivation or the concrete use case. -->
## Environment
## Please tell us about your environment
<pre><code>
Angular version: X.Y.Z
@ -49,8 +49,8 @@ Browser:
- [ ] Edge version XX
For Tooling issues:
- Node version: XX <!-- run `node --version` -->
- Platform: <!-- Mac, Linux, Windows -->
- Node version: XX <!-- use `node --version` -->
- Platform: <!-- Mac, Linux, Windows -->
Others:
<!-- Anything else relevant? Operating system version, IDE, package manager, HTTP server, ... -->

View File

@ -1,5 +1,5 @@
## PR Checklist
Please check if your PR fulfills the following requirements:
Does please check if your PR fulfills the following requirements:
- [ ] The commit message follows our guidelines: https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit
- [ ] Tests for the changes have been added (for bug fixes / features)

1
.gitignore vendored
View File

@ -2,7 +2,6 @@
/dist/
bazel-*
e2e_test.*
node_modules
bower_components

View File

@ -256,6 +256,8 @@ groups:
files:
include:
- "aio/*"
exclude:
- "aio/content/*"
users:
- petebacondarwin #primary
- IgorMinar
@ -266,11 +268,11 @@ groups:
conditions:
files:
include:
- "aio/content/*"
exclude:
- "aio/content/marketing/*"
- "aio/content/navigation.json"
- "aio/content/license.md"
- "aio/content/examples/*"
- "aio/content/guide/*"
- "aio/content/images/*"
- "aio/content/tutorial/*"
- "aio/content/file-not-found.md"
users:
- juleskremer #primary
- Foxandxss
@ -283,9 +285,13 @@ groups:
conditions:
files:
include:
- "aio/content/marketing/*"
- "aio/content/navigation.json"
- "aio/content/license.md"
- "aio/content/*"
exclude:
- "aio/content/examples/*"
- "aio/content/guide/*"
- "aio/content/images/*"
- "aio/content/tutorial/*"
- "aio/content/file-not-found.md"
users:
- juleskremer #primary
- stephenfluin

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,7 @@ Help us keep Angular open and inclusive. Please read and follow our [Code of Con
## <a name="question"></a> Got a Question or Problem?
Do not open issues for general support questions as we want to keep GitHub issues for bug reports and feature requests. You've got much better chances of getting your question answered on [Stack Overflow](https://stackoverflow.com/questions/tagged/angular) where the questions should be tagged with tag `angular`.
Please, do not open issues for the general support questions as we want to keep GitHub issues for bug reports and feature requests. You've got much better chances of getting your question answered on [Stack Overflow](https://stackoverflow.com/questions/tagged/angular) where the questions should be tagged with tag `angular`.
Stack Overflow is a much better place to ask questions since:
@ -25,7 +25,7 @@ Stack Overflow is a much better place to ask questions since:
- questions and answers stay available for public viewing so your question / answer might help someone else
- Stack Overflow's voting system assures that the best answers are prominently visible.
To save your and our time, we will systematically close all issues that are requests for general support and redirect people to Stack Overflow.
To save your and our time we will be systematically closing all the issues that are requests for general support and redirecting people to Stack Overflow.
If you would like to chat about the question in real-time, you can reach out via [our gitter channel][gitter].

View File

@ -4,20 +4,22 @@
[![Issue Stats](http://issuestats.com/github/angular/angular/badge/pr?style=flat)](http://issuestats.com/github/angular/angular)
[![Issue Stats](http://issuestats.com/github/angular/angular/badge/issue?style=flat)](http://issuestats.com/github/angular/angular)
[![npm version](https://badge.fury.io/js/%40angular%2Fcore.svg)](https://www.npmjs.com/@angular/core)
)
[![Sauce Test Status](https://saucelabs.com/browser-matrix/angular2-ci.svg)](https://saucelabs.com/u/angular2-ci)
*Safari (7+), iOS (7+), Edge (14) and IE mobile (11) are tested on [BrowserStack][browserstack].*
# Angular
Angular
=========
Angular is a development platform for building mobile and desktop web applications using Typescript/JavaScript (JS) and other languages.
Angular is a development platform for building mobile and desktop web applications using Typescript/JavaScript and other languages.
## Quickstart
[Get started in 5 minutes][quickstart].
## Want to help?
Want to file a bug, contribute some code, or improve documentation? Excellent! Read up on our

View File

@ -88,21 +88,6 @@ server {
resolver 127.0.0.1;
}
# Notify about PR changes
location "~^/pr-updated/?$" {
if ($request_method != "POST") {
add_header Allow "POST";
return 405;
}
proxy_pass_request_headers on;
proxy_redirect off;
proxy_method POST;
proxy_pass http://{{$AIO_UPLOAD_HOSTNAME}}:{{$AIO_UPLOAD_PORT}}$request_uri;
resolver 127.0.0.1;
}
# Everything else
location / {
return 404;

View File

@ -18,17 +18,45 @@ export class BuildCreator extends EventEmitter {
}
// Methods - Public
public changePrVisibility(pr: string, makePublic: boolean): Promise<void> {
const {oldPrDir, newPrDir} = this.getCandidatePrDirs(pr, makePublic);
return Promise.
all([this.exists(oldPrDir), this.exists(newPrDir)]).
then(([oldPrDirExisted, newPrDirExisted]) => {
if (!oldPrDirExisted) {
throw new UploadError(404, `Request to move non-existing directory '${oldPrDir}' to '${newPrDir}'.`);
} else if (newPrDirExisted) {
throw new UploadError(409, `Request to move '${oldPrDir}' to existing directory '${newPrDir}'.`);
}
return Promise.resolve().
then(() => shell.mv(oldPrDir, newPrDir)).
then(() => this.listShasByDate(newPrDir)).
then(shas => this.emit(ChangedPrVisibilityEvent.type, new ChangedPrVisibilityEvent(+pr, shas, makePublic))).
then(() => undefined);
}).
catch(err => {
if (!(err instanceof UploadError)) {
err = new UploadError(500, `Error while making PR ${pr} ${makePublic ? 'public' : 'hidden'}.\n${err}`);
}
throw err;
});
}
public create(pr: string, sha: string, archivePath: string, isPublic: boolean): Promise<void> {
// Use only part of the SHA for more readable URLs.
sha = sha.substr(0, SHORT_SHA_LEN);
const {newPrDir: prDir} = this.getCandidatePrDirs(pr, isPublic);
const {oldPrDir: otherVisPrDir, newPrDir: prDir} = this.getCandidatePrDirs(pr, isPublic);
const shaDir = path.join(prDir, sha);
let dirToRemoveOnError: string;
return Promise.resolve().
then(() => this.exists(otherVisPrDir)).
// If the same PR exists with different visibility, update the visibility first.
then(() => this.updatePrVisibility(pr, isPublic)).
then(otherVisPrDirExisted => (otherVisPrDirExisted && this.changePrVisibility(pr, isPublic)) as any).
then(() => Promise.all([this.exists(prDir), this.exists(shaDir)])).
then(([prDirExisted, shaDirExisted]) => {
if (shaDirExisted) {
@ -56,36 +84,6 @@ export class BuildCreator extends EventEmitter {
});
}
public updatePrVisibility(pr: string, makePublic: boolean): Promise<boolean> {
const {oldPrDir: otherVisPrDir, newPrDir: targetVisPrDir} = this.getCandidatePrDirs(pr, makePublic);
return Promise.
all([this.exists(otherVisPrDir), this.exists(targetVisPrDir)]).
then(([otherVisPrDirExisted, targetVisPrDirExisted]) => {
if (!otherVisPrDirExisted) {
// No visibility change: Either the visibility is up-to-date or the PR does not exist.
return false;
} else if (targetVisPrDirExisted) {
// Error: Directories for both visibilities exist.
throw new UploadError(409, `Request to move '${otherVisPrDir}' to existing directory '${targetVisPrDir}'.`);
}
// Visibility change: Moving `otherVisPrDir` to `targetVisPrDir`.
return Promise.resolve().
then(() => shell.mv(otherVisPrDir, targetVisPrDir)).
then(() => this.listShasByDate(targetVisPrDir)).
then(shas => this.emit(ChangedPrVisibilityEvent.type, new ChangedPrVisibilityEvent(+pr, shas, makePublic))).
then(() => true);
}).
catch(err => {
if (!(err instanceof UploadError)) {
err = new UploadError(500, `Error while making PR ${pr} ${makePublic ? 'public' : 'hidden'}.\n${err}`);
}
throw err;
});
}
// Methods - Protected
protected exists(fileOrDir: string): Promise<boolean> {
return new Promise(resolve => fs.access(fileOrDir, err => resolve(!err)));

View File

@ -1,31 +1,17 @@
// Imports
import {GithubPullRequests} from '../common/github-pull-requests';
import {BUILD_VERIFICATION_STATUS, BuildVerifier} from '../upload-server/build-verifier';
import {UploadError} from '../upload-server/upload-error';
import * as c from './constants';
import {BUILD_VERIFICATION_STATUS, BuildVerifier} from './build-verifier';
import {UploadError} from './upload-error';
// Run
// TODO(gkalpak): Add e2e tests to cover these interactions as well.
GithubPullRequests.prototype.addComment = () => Promise.resolve();
BuildVerifier.prototype.getPrIsTrusted = (pr: number) => {
switch (pr) {
case c.BV_getPrIsTrusted_error:
// For e2e tests, fake an error.
return Promise.reject('Test');
case c.BV_getPrIsTrusted_notTrusted:
// For e2e tests, fake an untrusted PR (`false`).
return Promise.resolve(false);
default:
// For e2e tests, default to trusted PRs (`true`).
return Promise.resolve(true);
}
};
BuildVerifier.prototype.verify = (expectedPr: number, authHeader: string) => {
switch (authHeader) {
case c.BV_verify_error:
case 'FAKE_VERIFICATION_ERROR':
// For e2e tests, fake a verification error.
return Promise.reject(new UploadError(403, `Error while verifying upload for PR ${expectedPr}: Test`));
case c.BV_verify_verifiedNotTrusted:
case 'FAKE_VERIFIED_NOT_TRUSTED':
// For e2e tests, fake a `verifiedNotTrusted` verification status.
return Promise.resolve(BUILD_VERIFICATION_STATUS.verifiedNotTrusted);
default:
@ -35,4 +21,4 @@ BuildVerifier.prototype.verify = (expectedPr: number, authHeader: string) => {
};
// tslint:disable-next-line: no-var-requires
require('../upload-server/index');
require('./index');

View File

@ -1,5 +1,4 @@
// Imports
import * as bodyParser from 'body-parser';
import * as express from 'express';
import * as http from 'http';
import {GithubPullRequests} from '../common/github-pull-requests';
@ -85,7 +84,6 @@ class UploadServerFactory {
protected createMiddleware(buildVerifier: BuildVerifier, buildCreator: BuildCreator): express.Express {
const middleware = express();
const jsonParser = bodyParser.json();
middleware.get(/^\/create-build\/([1-9][0-9]*)\/([0-9a-f]{40})\/?$/, (req, res) => {
const pr = req.params[0];
@ -98,8 +96,8 @@ class UploadServerFactory {
} else if (!archive) {
this.throwRequestError(400, `Missing or empty '${X_FILE_HEADER}' header`, req);
} else {
Promise.resolve().
then(() => buildVerifier.verify(+pr, authHeader)).
buildVerifier.
verify(+pr, authHeader).
then(verStatus => verStatus === BUILD_VERIFICATION_STATUS.verifiedAndTrusted).
then(isPublic => buildCreator.create(pr, sha, archive, isPublic).
then(() => res.sendStatus(isPublic ? 201 : 202))).
@ -107,23 +105,8 @@ class UploadServerFactory {
}
});
middleware.get(/^\/health-check\/?$/, (_req, res) => res.sendStatus(200));
middleware.post(/^\/pr-updated\/?$/, jsonParser, (req, res) => {
const {action, number: prNo}: {action?: string, number?: number} = req.body;
const visMayHaveChanged = !action || (action === 'labeled') || (action === 'unlabeled');
if (!visMayHaveChanged) {
res.sendStatus(200);
} else if (!prNo) {
this.throwRequestError(400, `Missing or empty 'number' field`, req);
} else {
Promise.resolve().
then(() => buildVerifier.getPrIsTrusted(prNo)).
then(isPublic => buildCreator.updatePrVisibility(String(prNo), isPublic)).
then(() => res.sendStatus(200)).
catch(err => this.respondWithError(res, err));
}
});
middleware.all('*', req => this.throwRequestError(404, 'Unknown resource', req));
middleware.get('*', req => this.throwRequestError(404, 'Unknown resource', req));
middleware.all('*', req => this.throwRequestError(405, 'Unsupported method', req));
middleware.use((err: any, _req: any, res: express.Response, _next: any) => this.respondWithError(res, err));
return middleware;
@ -142,10 +125,7 @@ class UploadServerFactory {
}
protected throwRequestError(status: number, error: string, req: express.Request) {
const message = `${error} in request: ${req.method} ${req.originalUrl}` +
(!req.body ? '' : ` ${JSON.stringify(req.body)}`);
throw new UploadError(status, message);
throw new UploadError(status, `${error} in request: ${req.method} ${req.originalUrl}`);
}
}

View File

@ -1,16 +0,0 @@
// Using the values below, we can fake the response of the corresponding methods in tests. This is
// necessary, because the test upload-server will be running as a separate node process, so we will
// not have direct access to the code (e.g. for mocking).
// (See also 'lib/verify-setup/start-test-upload-server.ts'.)
/* tslint:disable: variable-name */
// Special values to be used as `authHeader` in `BuildVerifier#verify()`.
export const BV_verify_error = 'FAKE_VERIFICATION_ERROR';
export const BV_verify_verifiedNotTrusted = 'FAKE_VERIFIED_NOT_TRUSTED';
// Special values to be used as `pr` in `BuildVerifier#getPrIsTrusted()`.
export const BV_getPrIsTrusted_error = 32203;
export const BV_getPrIsTrusted_notTrusted = 72457;
/* tslint:enable: variable-name */

View File

@ -317,51 +317,6 @@ describe(`nginx`, () => {
});
describe(`${host}/pr-updated`, () => {
const url = `${scheme}://${host}/pr-updated`;
it('should disallow non-POST requests', done => {
Promise.all([
h.runCmd(`curl -iLX GET ${url}`).then(h.verifyResponse([405, 'Not Allowed'])),
h.runCmd(`curl -iLX PUT ${url}`).then(h.verifyResponse([405, 'Not Allowed'])),
h.runCmd(`curl -iLX PATCH ${url}`).then(h.verifyResponse([405, 'Not Allowed'])),
h.runCmd(`curl -iLX DELETE ${url}`).then(h.verifyResponse([405, 'Not Allowed'])),
]).then(done);
});
it('should pass requests through to the upload server', done => {
const cmdPrefix = `curl -iLX POST --header "Content-Type: application/json"`;
const cmd1 = `${cmdPrefix} ${url}`;
const cmd2 = `${cmdPrefix} --data '{"number":${pr}}' ${url}`;
const cmd3 = `${cmdPrefix} --data '{"number":${pr},"action":"foo"}' ${url}`;
Promise.all([
h.runCmd(cmd1).then(h.verifyResponse(400, /Missing or empty 'number' field/)),
h.runCmd(cmd2).then(h.verifyResponse(200)),
h.runCmd(cmd3).then(h.verifyResponse(200)),
]).then(done);
});
it('should respond with 404 for unknown paths', done => {
const cmdPrefix = `curl -iLX POST ${scheme}://${host}`;
Promise.all([
h.runCmd(`${cmdPrefix}/foo/pr-updated`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/foo-pr-updated`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/foonpr-updated`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/pr-updated/foo`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/pr-updated-foo`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/pr-updatednfoo`).then(h.verifyResponse(404)),
]).then(done);
});
});
describe(`${host}/*`, () => {
it('should respond with 404 for unknown URLs (even if the resource exists)', done => {

View File

@ -1,6 +1,5 @@
// Imports
import * as path from 'path';
import * as c from './constants';
import {helper as h} from './helper';
// Tests
@ -15,14 +14,12 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
const getFile = (pr: string, sha: string, file: string) =>
h.runCmd(`curl -iL ${scheme}://pr${pr}-${h.getShordSha(sha)}.${host}/${file}`);
const uploadBuild = (pr: string, sha: string, archive: string, authHeader = 'Token FOO') => {
// Using `FAKE_VERIFICATION_ERROR` or `FAKE_VERIFIED_NOT_TRUSTED` as `authHeader`,
// we can fake the response of the overwritten `BuildVerifier.verify()` method.
// (See 'lib/upload-server/index-test.ts'.)
const curlPost = `curl -iLX POST --header "Authorization: ${authHeader}"`;
return h.runCmd(`${curlPost} --data-binary "@${archive}" ${scheme}://${host}/create-build/${pr}/${sha}`);
};
const prUpdated = (pr: number, action?: string) => {
const url = `${scheme}://${host}/pr-updated`;
const payloadStr = JSON.stringify({number: pr, action});
return h.runCmd(`curl -iLX POST --header "Content-Type: application/json" --data '${payloadStr}' ${url}`);
};
beforeEach(() => jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000);
afterEach(() => {
@ -32,7 +29,7 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
});
describe('for a new/non-existing PR', () => {
describe('for a new PR', () => {
it('should be able to upload and serve a public build', done => {
const regexPrefix9 = `^PR: uploaded\\/${pr9} \\| SHA: ${sha9} \\| File:`;
@ -57,7 +54,7 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
h.createDummyArchive(pr9, sha9, archivePath);
uploadBuild(pr9, sha9, archivePath, c.BV_verify_verifiedNotTrusted).
uploadBuild(pr9, sha9, archivePath, 'FAKE_VERIFIED_NOT_TRUSTED').
then(() => Promise.all([
getFile(pr9, sha9, 'index.html').then(h.verifyResponse(404)),
getFile(pr9, sha9, 'foo/bar.js').then(h.verifyResponse(404)),
@ -77,7 +74,7 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
h.createDummyArchive(pr9, sha9, archivePath);
uploadBuild(pr9, sha9, archivePath, c.BV_verify_error).
uploadBuild(pr9, sha9, archivePath, 'FAKE_VERIFICATION_ERROR').
then(h.verifyResponse(403, errorRegex9)).
then(() => {
expect(h.buildExists(pr9)).toBe(false);
@ -86,18 +83,6 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
then(done);
});
it('should be able to notify that a PR has been updated (and do nothing)', done => {
prUpdated(+pr9).
then(h.verifyResponse(200)).
then(() => {
// The PR should still not exist.
expect(h.buildExists(pr9, '', false)).toBe(false);
expect(h.buildExists(pr9, '', true)).toBe(false);
}).
then(done);
});
});
@ -138,7 +123,7 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
h.createDummyBuild(pr9, sha0, false);
h.createDummyArchive(pr9, sha9, archivePath);
uploadBuild(pr9, sha9, archivePath, c.BV_verify_verifiedNotTrusted).
uploadBuild(pr9, sha9, archivePath, 'FAKE_VERIFIED_NOT_TRUSTED').
then(() => Promise.all([
getFile(pr9, sha0, 'index.html').then(h.verifyResponse(404)),
getFile(pr9, sha0, 'foo/bar.js').then(h.verifyResponse(404)),
@ -163,7 +148,7 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
h.createDummyBuild(pr9, sha0);
h.createDummyArchive(pr9, sha9, archivePath);
uploadBuild(pr9, sha9, archivePath, c.BV_verify_error).
uploadBuild(pr9, sha9, archivePath, 'FAKE_VERIFICATION_ERROR').
then(h.verifyResponse(403, errorRegex9)).
then(() => {
expect(h.buildExists(pr9)).toBe(true);
@ -201,7 +186,7 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
h.createDummyBuild(pr9, sha9, false);
h.createDummyArchive(pr9, sha9, archivePath);
uploadBuild(pr9, sha9, archivePath, c.BV_verify_verifiedNotTrusted).
uploadBuild(pr9, sha9, archivePath, 'FAKE_VERIFIED_NOT_TRUSTED').
then(h.verifyResponse(409)).
then(() => {
expect(h.readBuildFile(pr9, sha9, 'index.html', false)).toMatch(idxContentRegex9);
@ -210,110 +195,6 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
then(done);
});
it('should be able to request re-checking visibility (if outdated)', done => {
const publicPr = pr9;
const hiddenPr = String(c.BV_getPrIsTrusted_notTrusted);
h.createDummyBuild(publicPr, sha9, false);
h.createDummyBuild(hiddenPr, sha9, true);
// PR visibilities are outdated (i.e. the opposte of what the should).
expect(h.buildExists(publicPr, '', false)).toBe(true);
expect(h.buildExists(publicPr, '', true)).toBe(false);
expect(h.buildExists(hiddenPr, '', false)).toBe(false);
expect(h.buildExists(hiddenPr, '', true)).toBe(true);
Promise.
all([
prUpdated(+publicPr).then(h.verifyResponse(200)),
prUpdated(+hiddenPr).then(h.verifyResponse(200)),
]).
then(() => {
// PR visibilities should have been updated.
expect(h.buildExists(publicPr, '', false)).toBe(false);
expect(h.buildExists(publicPr, '', true)).toBe(true);
expect(h.buildExists(hiddenPr, '', false)).toBe(true);
expect(h.buildExists(hiddenPr, '', true)).toBe(false);
}).
then(() => {
h.deletePrDir(publicPr, true);
h.deletePrDir(hiddenPr, false);
}).
then(done);
});
it('should be able to request re-checking visibility (if up-to-date)', done => {
const publicPr = pr9;
const hiddenPr = String(c.BV_getPrIsTrusted_notTrusted);
h.createDummyBuild(publicPr, sha9, true);
h.createDummyBuild(hiddenPr, sha9, false);
// PR visibilities are already up-to-date.
expect(h.buildExists(publicPr, '', false)).toBe(false);
expect(h.buildExists(publicPr, '', true)).toBe(true);
expect(h.buildExists(hiddenPr, '', false)).toBe(true);
expect(h.buildExists(hiddenPr, '', true)).toBe(false);
Promise.
all([
prUpdated(+publicPr).then(h.verifyResponse(200)),
prUpdated(+hiddenPr).then(h.verifyResponse(200)),
]).
then(() => {
// PR visibilities are still up-to-date.
expect(h.buildExists(publicPr, '', false)).toBe(false);
expect(h.buildExists(publicPr, '', true)).toBe(true);
expect(h.buildExists(hiddenPr, '', false)).toBe(true);
expect(h.buildExists(hiddenPr, '', true)).toBe(false);
}).
then(done);
});
it('should reject a request if re-checking visibility fails', done => {
const errorPr = String(c.BV_getPrIsTrusted_error);
h.createDummyBuild(errorPr, sha9, true);
expect(h.buildExists(errorPr, '', false)).toBe(false);
expect(h.buildExists(errorPr, '', true)).toBe(true);
prUpdated(+errorPr).
then(h.verifyResponse(500, /Test/)).
then(() => {
// PR visibility should not have been updated.
expect(h.buildExists(errorPr, '', false)).toBe(false);
expect(h.buildExists(errorPr, '', true)).toBe(true);
}).
then(done);
});
it('should reject a request if updating visibility fails', done => {
// One way to cause an error is to have both a public and a hidden directory for the same PR.
h.createDummyBuild(pr9, sha9, false);
h.createDummyBuild(pr9, sha9, true);
const hiddenPrDir = h.getPrDir(pr9, false);
const publicPrDir = h.getPrDir(pr9, true);
const bodyRegex = new RegExp(`Request to move '${hiddenPrDir}' to existing directory '${publicPrDir}'`);
expect(h.buildExists(pr9, '', false)).toBe(true);
expect(h.buildExists(pr9, '', true)).toBe(true);
prUpdated(+pr9).
then(h.verifyResponse(409, bodyRegex)).
then(() => {
// PR visibility should not have been updated.
expect(h.buildExists(pr9, '', false)).toBe(true);
expect(h.buildExists(pr9, '', true)).toBe(true);
}).
then(done);
});
});
}));

View File

@ -1,7 +1,6 @@
// Imports
import * as fs from 'fs';
import * as path from 'path';
import * as c from './constants';
import {CmdResult, helper as h} from './helper';
// Tests
@ -26,13 +25,13 @@ describe('upload-server (on HTTP)', () => {
it('should disallow non-GET requests', done => {
const url = `http://${host}/create-build/${pr}/${sha9}`;
const bodyRegex = /^Unknown resource/;
const bodyRegex = /^Unsupported method/;
Promise.all([
h.runCmd(`curl -iLX PUT ${url}`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iLX POST ${url}`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iLX PATCH ${url}`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iLX DELETE ${url}`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iLX PUT ${url}`).then(h.verifyResponse(405, bodyRegex)),
h.runCmd(`curl -iLX POST ${url}`).then(h.verifyResponse(405, bodyRegex)),
h.runCmd(`curl -iLX PATCH ${url}`).then(h.verifyResponse(405, bodyRegex)),
h.runCmd(`curl -iLX DELETE ${url}`).then(h.verifyResponse(405, bodyRegex)),
]).then(done);
});
@ -64,7 +63,7 @@ describe('upload-server (on HTTP)', () => {
it('should reject requests for which the PR verification fails', done => {
const headers = `--header "Authorization: ${c.BV_verify_error}" ${xFileHeader}`;
const headers = `--header "Authorization: FAKE_VERIFICATION_ERROR" ${xFileHeader}`;
const url = `http://${host}/create-build/${pr}/${sha9}`;
const bodyRegex = new RegExp(`Error while verifying upload for PR ${pr}: Test`);
@ -108,7 +107,7 @@ describe('upload-server (on HTTP)', () => {
[true, false].forEach(isPublic => describe(`(for ${isPublic ? 'public' : 'hidden'} builds)`, () => {
const authorizationHeader2 = isPublic ?
authorizationHeader : `--header "Authorization: ${c.BV_verify_verifiedNotTrusted}"`;
authorizationHeader : '--header "Authorization: FAKE_VERIFIED_NOT_TRUSTED"';
const cmdPrefix = curl('', `${authorizationHeader2} ${xFileHeader}`);
@ -374,194 +373,27 @@ describe('upload-server (on HTTP)', () => {
});
describe(`${host}/pr-updated`, () => {
const url = `http://${host}/pr-updated`;
// Helpers
const curl = (payload?: {number: number, action?: string}) => {
const payloadStr = payload && JSON.stringify(payload) || '';
return `curl -iLX POST --header "Content-Type: application/json" --data '${payloadStr}' ${url}`;
};
it('should disallow non-POST requests', done => {
const bodyRegex = /^Unknown resource in request/;
Promise.all([
h.runCmd(`curl -iLX GET ${url}`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iLX PUT ${url}`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iLX PATCH ${url}`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iLX DELETE ${url}`).then(h.verifyResponse(404, bodyRegex)),
]).then(done);
});
it('should respond with 400 for requests without a payload', done => {
const bodyRegex = /^Missing or empty 'number' field in request/;
h.runCmd(curl()).
then(h.verifyResponse(400, bodyRegex)).
then(done);
});
it('should respond with 400 for requests without a \'number\' field', done => {
const bodyRegex = /^Missing or empty 'number' field in request/;
Promise.all([
h.runCmd(curl({} as any)).then(h.verifyResponse(400, bodyRegex)),
h.runCmd(curl({number: null} as any)).then(h.verifyResponse(400, bodyRegex)),
]).then(done);
});
it('should reject requests for which checking the PR visibility fails', done => {
h.runCmd(curl({number: c.BV_getPrIsTrusted_error})).
then(h.verifyResponse(500, /Test/)).
then(done);
});
it('should respond with 404 for unknown paths', done => {
const mockPayload = JSON.stringify({number: +pr});
const cmdPrefix = `curl -iLX POST --data "${mockPayload}" http://${host}`;
Promise.all([
h.runCmd(`${cmdPrefix}/foo/pr-updated`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/foo-pr-updated`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/foonpr-updated`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/pr-updated/foo`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/pr-updated-foo`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/pr-updatednfoo`).then(h.verifyResponse(404)),
]).then(done);
});
it('should do nothing if PR\'s visibility is already up-to-date', done => {
const publicPr = pr;
const hiddenPr = String(c.BV_getPrIsTrusted_notTrusted);
const checkVisibilities = () => {
// Public build is already public.
expect(h.buildExists(publicPr, '', false)).toBe(false);
expect(h.buildExists(publicPr, '', true)).toBe(true);
// Hidden build is already hidden.
expect(h.buildExists(hiddenPr, '', false)).toBe(true);
expect(h.buildExists(hiddenPr, '', true)).toBe(false);
};
h.createDummyBuild(publicPr, sha9, true);
h.createDummyBuild(hiddenPr, sha9, false);
checkVisibilities();
Promise.
all([
h.runCmd(curl({number: +publicPr, action: 'foo'})).then(h.verifyResponse(200)),
h.runCmd(curl({number: +hiddenPr, action: 'foo'})).then(h.verifyResponse(200)),
]).
// Visibilities should not have changed, because the specified action could not have triggered a change.
then(checkVisibilities).
then(done);
});
it('should do nothing if \'action\' implies no visibility change', done => {
const publicPr = pr;
const hiddenPr = String(c.BV_getPrIsTrusted_notTrusted);
const checkVisibilities = () => {
// Public build is hidden atm.
expect(h.buildExists(publicPr, '', false)).toBe(true);
expect(h.buildExists(publicPr, '', true)).toBe(false);
// Hidden build is public atm.
expect(h.buildExists(hiddenPr, '', false)).toBe(false);
expect(h.buildExists(hiddenPr, '', true)).toBe(true);
};
h.createDummyBuild(publicPr, sha9, false);
h.createDummyBuild(hiddenPr, sha9, true);
checkVisibilities();
Promise.
all([
h.runCmd(curl({number: +publicPr, action: 'foo'})).then(h.verifyResponse(200)),
h.runCmd(curl({number: +hiddenPr, action: 'foo'})).then(h.verifyResponse(200)),
]).
// Visibilities should not have changed, because the specified action could not have triggered a change.
then(checkVisibilities).
then(done);
});
describe('when the visiblity has changed', () => {
const publicPr = pr;
const hiddenPr = String(c.BV_getPrIsTrusted_notTrusted);
beforeEach(() => {
// Create initial PR builds with opposite visibilities as the ones that will be reported:
// - The now public PR was previously hidden.
// - The now hidden PR was previously public.
h.createDummyBuild(publicPr, sha9, false);
h.createDummyBuild(hiddenPr, sha9, true);
expect(h.buildExists(publicPr, '', false)).toBe(true);
expect(h.buildExists(publicPr, '', true)).toBe(false);
expect(h.buildExists(hiddenPr, '', false)).toBe(false);
expect(h.buildExists(hiddenPr, '', true)).toBe(true);
});
afterEach(() => {
// Expect PRs' visibility to have been updated:
// - The public PR should be actually public (previously it was hidden).
// - The hidden PR should be actually hidden (previously it was public).
expect(h.buildExists(publicPr, '', false)).toBe(false);
expect(h.buildExists(publicPr, '', true)).toBe(true);
expect(h.buildExists(hiddenPr, '', false)).toBe(true);
expect(h.buildExists(hiddenPr, '', true)).toBe(false);
h.deletePrDir(publicPr, true);
h.deletePrDir(hiddenPr, false);
});
it('should update the PR\'s visibility (action: undefined)', done => {
Promise.all([
h.runCmd(curl({number: +publicPr})).then(h.verifyResponse(200)),
h.runCmd(curl({number: +hiddenPr})).then(h.verifyResponse(200)),
]).then(done);
});
it('should update the PR\'s visibility (action: labeled)', done => {
Promise.all([
h.runCmd(curl({number: +publicPr, action: 'labeled'})).then(h.verifyResponse(200)),
h.runCmd(curl({number: +hiddenPr, action: 'labeled'})).then(h.verifyResponse(200)),
]).then(done);
});
it('should update the PR\'s visibility (action: unlabeled)', done => {
Promise.all([
h.runCmd(curl({number: +publicPr, action: 'unlabeled'})).then(h.verifyResponse(200)),
h.runCmd(curl({number: +hiddenPr, action: 'unlabeled'})).then(h.verifyResponse(200)),
]).then(done);
});
});
});
describe(`${host}/*`, () => {
it('should respond with 404 for requests to unknown URLs', done => {
it('should respond with 404 for GET requests to unknown URLs', done => {
const bodyRegex = /^Unknown resource/;
Promise.all([
h.runCmd(`curl -iL http://${host}/index.html`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iL http://${host}/`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iL http://${host}`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iLX PUT http://${host}`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iLX POST http://${host}`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iLX PATCH http://${host}`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iLX DELETE http://${host}`).then(h.verifyResponse(404, bodyRegex)),
]).then(done);
});
it('should respond with 405 for non-GET requests to any URL', done => {
const bodyRegex = /^Unsupported method/;
Promise.all([
h.runCmd(`curl -iLX PUT http://${host}`).then(h.verifyResponse(405, bodyRegex)),
h.runCmd(`curl -iLX POST http://${host}`).then(h.verifyResponse(405, bodyRegex)),
h.runCmd(`curl -iLX PATCH http://${host}`).then(h.verifyResponse(405, bodyRegex)),
h.runCmd(`curl -iLX DELETE http://${host}`).then(h.verifyResponse(405, bodyRegex)),
]).then(done);
});

View File

@ -20,14 +20,12 @@
"test-watch": "nodemon --exec \"yarn ~~test-only\" --watch dist"
},
"dependencies": {
"body-parser": "^1.17.2",
"express": "^4.14.1",
"jasmine": "^2.5.3",
"jsonwebtoken": "^7.3.0",
"shelljs": "^0.7.6"
},
"devDependencies": {
"@types/body-parser": "^1.16.4",
"@types/express": "^4.0.35",
"@types/jasmine": "^2.5.43",
"@types/jsonwebtoken": "^7.2.0",

View File

@ -43,25 +43,178 @@ describe('BuildCreator', () => {
});
describe('create()', () => {
describe('changePrVisibility()', () => {
let bcEmitSpy: jasmine.Spy;
let bcExistsSpy: jasmine.Spy;
let bcExtractArchiveSpy: jasmine.Spy;
let bcUpdatePrVisibilitySpy: jasmine.Spy;
let shellMkdirSpy: jasmine.Spy;
let shellRmSpy: jasmine.Spy;
let bcListShasByDate: jasmine.Spy;
let shellMvSpy: jasmine.Spy;
beforeEach(() => {
bcEmitSpy = spyOn(bc, 'emit');
bcExistsSpy = spyOn(bc as any, 'exists');
bcListShasByDate = spyOn(bc as any, 'listShasByDate');
shellMvSpy = spyOn(shell, 'mv');
bcExistsSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
bcListShasByDate.and.returnValue([]);
});
it('should return a promise', done => {
const promise = bc.changePrVisibility(pr, true);
promise.then(done); // Do not complete the test (and release the spies) synchronously
// to avoid running the actual `extractArchive()`.
expect(promise).toEqual(jasmine.any(Promise));
});
[true, false].forEach(makePublic => {
const oldPrDir = makePublic ? hiddenPrDir : publicPrDir;
const newPrDir = makePublic ? publicPrDir : hiddenPrDir;
it('should rename the directory', done => {
bc.changePrVisibility(pr, makePublic).
then(() => expect(shellMvSpy).toHaveBeenCalledWith(oldPrDir, newPrDir)).
then(done);
});
it('should emit a ChangedPrVisibilityEvent on success', done => {
let emitted = false;
bcEmitSpy.and.callFake((type: string, evt: ChangedPrVisibilityEvent) => {
expect(type).toBe(ChangedPrVisibilityEvent.type);
expect(evt).toEqual(jasmine.any(ChangedPrVisibilityEvent));
expect(evt.pr).toBe(+pr);
expect(evt.shas).toEqual(jasmine.any(Array));
expect(evt.isPublic).toBe(makePublic);
emitted = true;
});
bc.changePrVisibility(pr, makePublic).
then(() => expect(emitted).toBe(true)).
then(done);
});
it('should include all shas in the emitted event', done => {
const shas = ['foo', 'bar', 'baz'];
let emitted = false;
bcListShasByDate.and.returnValue(Promise.resolve(shas));
bcEmitSpy.and.callFake((type: string, evt: ChangedPrVisibilityEvent) => {
expect(bcListShasByDate).toHaveBeenCalledWith(newPrDir);
expect(type).toBe(ChangedPrVisibilityEvent.type);
expect(evt).toEqual(jasmine.any(ChangedPrVisibilityEvent));
expect(evt.pr).toBe(+pr);
expect(evt.shas).toBe(shas);
expect(evt.isPublic).toBe(makePublic);
emitted = true;
});
bc.changePrVisibility(pr, makePublic).
then(() => expect(emitted).toBe(true)).
then(done);
});
describe('on error', () => {
it('should abort and skip further operations if the old directory does not exist', done => {
bcExistsSpy.and.callFake((dir: string) => dir !== oldPrDir);
bc.changePrVisibility(pr, makePublic).catch(err => {
expectToBeUploadError(err, 404, `Request to move non-existing directory '${oldPrDir}' to '${newPrDir}'.`);
expect(shellMvSpy).not.toHaveBeenCalled();
expect(bcListShasByDate).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
});
it('should abort and skip further operations if the new directory does already exist', done => {
bcExistsSpy.and.returnValue(true);
bc.changePrVisibility(pr, makePublic).catch(err => {
expectToBeUploadError(err, 409, `Request to move '${oldPrDir}' to existing directory '${newPrDir}'.`);
expect(shellMvSpy).not.toHaveBeenCalled();
expect(bcListShasByDate).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
});
it('should abort and skip further operations if it fails to rename the directory', done => {
shellMvSpy.and.throwError('');
bc.changePrVisibility(pr, makePublic).catch(() => {
expect(shellMvSpy).toHaveBeenCalled();
expect(bcListShasByDate).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
});
it('should abort and skip further operations if it fails to list the SHAs', done => {
bcListShasByDate.and.throwError('');
bc.changePrVisibility(pr, makePublic).catch(() => {
expect(shellMvSpy).toHaveBeenCalled();
expect(bcListShasByDate).toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
});
it('should reject with an UploadError', done => {
shellMvSpy.and.callFake(() => { throw 'Test'; });
bc.changePrVisibility(pr, makePublic).catch(err => {
expectToBeUploadError(err, 500, `Error while making PR ${pr} ${makePublic ? 'public' : 'hidden'}.\nTest`);
done();
});
});
it('should pass UploadError instances unmodified', done => {
shellMvSpy.and.callFake(() => { throw new UploadError(543, 'Test'); });
bc.changePrVisibility(pr, makePublic).catch(err => {
expectToBeUploadError(err, 543, 'Test');
done();
});
});
});
});
});
describe('create()', () => {
let bcChangePrVisibilitySpy: jasmine.Spy;
let bcEmitSpy: jasmine.Spy;
let bcExistsSpy: jasmine.Spy;
let bcExtractArchiveSpy: jasmine.Spy;
let shellMkdirSpy: jasmine.Spy;
let shellRmSpy: jasmine.Spy;
beforeEach(() => {
bcChangePrVisibilitySpy = spyOn(bc, 'changePrVisibility');
bcEmitSpy = spyOn(bc, 'emit');
bcExistsSpy = spyOn(bc as any, 'exists');
bcExtractArchiveSpy = spyOn(bc as any, 'extractArchive');
bcUpdatePrVisibilitySpy = spyOn(bc, 'updatePrVisibility');
shellMkdirSpy = spyOn(shell, 'mkdir');
shellRmSpy = spyOn(shell, 'rm');
});
[true, false].forEach(isPublic => {
const otherVisPrDir = isPublic ? hiddenPrDir : publicPrDir;
const prDir = isPublic ? publicPrDir : hiddenPrDir;
const shaDir = isPublic ? publicShaDir : hiddenShaDir;
@ -75,12 +228,20 @@ describe('BuildCreator', () => {
});
it('should not update the PR\'s visibility first if not necessary', done => {
bc.create(pr, sha, archive, isPublic).
then(() => expect(bcChangePrVisibilitySpy).not.toHaveBeenCalled()).
then(done);
});
it('should update the PR\'s visibility first if necessary', done => {
bcUpdatePrVisibilitySpy.and.callFake(() => expect(shellMkdirSpy).not.toHaveBeenCalled());
bcChangePrVisibilitySpy.and.callFake(() => expect(shellMkdirSpy).not.toHaveBeenCalled());
bcExistsSpy.and.callFake((dir: string) => dir === otherVisPrDir);
bc.create(pr, sha, archive, isPublic).
then(() => {
expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith(pr, isPublic);
expect(bcChangePrVisibilitySpy).toHaveBeenCalledWith(pr, isPublic);
expect(shellMkdirSpy).toHaveBeenCalled();
}).
then(done);
@ -125,6 +286,7 @@ describe('BuildCreator', () => {
beforeEach(() => {
existsValues = {
[otherVisPrDir]: false,
[prDir]: false,
[shaDir]: false,
};
@ -135,12 +297,14 @@ describe('BuildCreator', () => {
it('should abort and skip further operations if changing the PR\'s visibility fails', done => {
const mockError = new UploadError(543, 'Test');
bcUpdatePrVisibilitySpy.and.returnValue(Promise.reject(mockError));
existsValues[otherVisPrDir] = true;
bcChangePrVisibilitySpy.and.returnValue(Promise.reject(mockError));
bc.create(pr, sha, archive, isPublic).catch(err => {
expect(err).toBe(mockError);
expect(bcExistsSpy).not.toHaveBeenCalled();
expect(bcExistsSpy).toHaveBeenCalledTimes(1);
expect(shellMkdirSpy).not.toHaveBeenCalled();
expect(bcExtractArchiveSpy).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
@ -163,10 +327,8 @@ describe('BuildCreator', () => {
it('should detect existing build directory after visibility change', done => {
bcUpdatePrVisibilitySpy.and.callFake(() => existsValues[prDir] = existsValues[shaDir] = true);
expect(bcExistsSpy(prDir)).toBe(false);
expect(bcExistsSpy(shaDir)).toBe(false);
existsValues[otherVisPrDir] = true;
bcChangePrVisibilitySpy.and.callFake(() => existsValues[prDir] = existsValues[shaDir] = true);
bc.create(pr, sha, archive, isPublic).catch(err => {
expectToBeUploadError(err, 409, `Request to overwrite existing directory: ${shaDir}`);
@ -244,190 +406,6 @@ describe('BuildCreator', () => {
});
describe('updatePrVisibility()', () => {
let bcEmitSpy: jasmine.Spy;
let bcExistsSpy: jasmine.Spy;
let bcListShasByDate: jasmine.Spy;
let shellMvSpy: jasmine.Spy;
beforeEach(() => {
bcEmitSpy = spyOn(bc, 'emit');
bcExistsSpy = spyOn(bc as any, 'exists');
bcListShasByDate = spyOn(bc as any, 'listShasByDate');
shellMvSpy = spyOn(shell, 'mv');
bcExistsSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
bcListShasByDate.and.returnValue([]);
});
it('should return a promise', done => {
const promise = bc.updatePrVisibility(pr, true);
promise.then(done); // Do not complete the test (and release the spies) synchronously
// to avoid running the actual `extractArchive()`.
expect(promise).toEqual(jasmine.any(Promise));
});
[true, false].forEach(makePublic => {
const oldPrDir = makePublic ? hiddenPrDir : publicPrDir;
const newPrDir = makePublic ? publicPrDir : hiddenPrDir;
it('should rename the directory', done => {
bc.updatePrVisibility(pr, makePublic).
then(() => expect(shellMvSpy).toHaveBeenCalledWith(oldPrDir, newPrDir)).
then(done);
});
describe('when the visibility is updated', () => {
it('should resolve to true', done => {
bc.updatePrVisibility(pr, makePublic).
then(result => expect(result).toBe(true)).
then(done);
});
it('should rename the directory', done => {
bc.updatePrVisibility(pr, makePublic).
then(() => expect(shellMvSpy).toHaveBeenCalledWith(oldPrDir, newPrDir)).
then(done);
});
it('should emit a ChangedPrVisibilityEvent on success', done => {
let emitted = false;
bcEmitSpy.and.callFake((type: string, evt: ChangedPrVisibilityEvent) => {
expect(type).toBe(ChangedPrVisibilityEvent.type);
expect(evt).toEqual(jasmine.any(ChangedPrVisibilityEvent));
expect(evt.pr).toBe(+pr);
expect(evt.shas).toEqual(jasmine.any(Array));
expect(evt.isPublic).toBe(makePublic);
emitted = true;
});
bc.updatePrVisibility(pr, makePublic).
then(() => expect(emitted).toBe(true)).
then(done);
});
it('should include all shas in the emitted event', done => {
const shas = ['foo', 'bar', 'baz'];
let emitted = false;
bcListShasByDate.and.returnValue(Promise.resolve(shas));
bcEmitSpy.and.callFake((type: string, evt: ChangedPrVisibilityEvent) => {
expect(bcListShasByDate).toHaveBeenCalledWith(newPrDir);
expect(type).toBe(ChangedPrVisibilityEvent.type);
expect(evt).toEqual(jasmine.any(ChangedPrVisibilityEvent));
expect(evt.pr).toBe(+pr);
expect(evt.shas).toBe(shas);
expect(evt.isPublic).toBe(makePublic);
emitted = true;
});
bc.updatePrVisibility(pr, makePublic).
then(() => expect(emitted).toBe(true)).
then(done);
});
});
it('should do nothing if the visibility is already up-to-date', done => {
bcExistsSpy.and.callFake((dir: string) => dir === newPrDir);
bc.updatePrVisibility(pr, makePublic).
then(result => {
expect(result).toBe(false);
expect(shellMvSpy).not.toHaveBeenCalled();
expect(bcListShasByDate).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
}).
then(done);
});
it('should do nothing if the PR directory does not exist', done => {
bcExistsSpy.and.returnValue(false);
bc.updatePrVisibility(pr, makePublic).
then(result => {
expect(result).toBe(false);
expect(shellMvSpy).not.toHaveBeenCalled();
expect(bcListShasByDate).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
}).
then(done);
});
describe('on error', () => {
it('should abort and skip further operations if both directories exist', done => {
bcExistsSpy.and.returnValue(true);
bc.updatePrVisibility(pr, makePublic).catch(err => {
expectToBeUploadError(err, 409, `Request to move '${oldPrDir}' to existing directory '${newPrDir}'.`);
expect(shellMvSpy).not.toHaveBeenCalled();
expect(bcListShasByDate).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
});
it('should abort and skip further operations if it fails to rename the directory', done => {
shellMvSpy.and.throwError('');
bc.updatePrVisibility(pr, makePublic).catch(() => {
expect(shellMvSpy).toHaveBeenCalled();
expect(bcListShasByDate).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
});
it('should abort and skip further operations if it fails to list the SHAs', done => {
bcListShasByDate.and.throwError('');
bc.updatePrVisibility(pr, makePublic).catch(() => {
expect(shellMvSpy).toHaveBeenCalled();
expect(bcListShasByDate).toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
});
it('should reject with an UploadError', done => {
shellMvSpy.and.callFake(() => { throw 'Test'; });
bc.updatePrVisibility(pr, makePublic).catch(err => {
expectToBeUploadError(err, 500, `Error while making PR ${pr} ${makePublic ? 'public' : 'hidden'}.\nTest`);
done();
});
});
it('should pass UploadError instances unmodified', done => {
shellMvSpy.and.callFake(() => { throw new UploadError(543, 'Test'); });
bc.updatePrVisibility(pr, makePublic).catch(err => {
expectToBeUploadError(err, 543, 'Test');
done();
});
});
});
});
});
// Protected methods
describe('exists()', () => {

View File

@ -258,12 +258,12 @@ describe('uploadServerFactory', () => {
});
it('should respond with 404 for non-GET requests', done => {
it('should respond with 405 for non-GET requests', done => {
verifyRequests([
agent.put(`/create-build/${pr}/${sha}`).expect(404),
agent.post(`/create-build/${pr}/${sha}`).expect(404),
agent.patch(`/create-build/${pr}/${sha}`).expect(404),
agent.delete(`/create-build/${pr}/${sha}`).expect(404),
agent.put(`/create-build/${pr}/${sha}`).expect(405),
agent.post(`/create-build/${pr}/${sha}`).expect(405),
agent.patch(`/create-build/${pr}/${sha}`).expect(405),
agent.delete(`/create-build/${pr}/${sha}`).expect(405),
], done);
});
@ -418,12 +418,12 @@ describe('uploadServerFactory', () => {
});
it('should respond with 404 for non-GET requests', done => {
it('should respond with 405 for non-GET requests', done => {
verifyRequests([
agent.put('/health-check').expect(404),
agent.post('/health-check').expect(404),
agent.patch('/health-check').expect(404),
agent.delete('/health-check').expect(404),
agent.put('/health-check').expect(405),
agent.post('/health-check').expect(405),
agent.patch('/health-check').expect(405),
agent.delete('/health-check').expect(405),
], done);
});
@ -442,141 +442,11 @@ describe('uploadServerFactory', () => {
});
describe('POST /pr-updated', () => {
const pr = '9';
const url = '/pr-updated';
let bvGetPrIsTrustedSpy: jasmine.Spy;
let bcUpdatePrVisibilitySpy: jasmine.Spy;
// Helpers
const createRequest = (num: number, action?: string) =>
agent.post(url).send({number: num, action});
beforeEach(() => {
bvGetPrIsTrustedSpy = spyOn(buildVerifier, 'getPrIsTrusted');
bcUpdatePrVisibilitySpy = spyOn(buildCreator, 'updatePrVisibility');
});
it('should respond with 404 for non-POST requests', done => {
verifyRequests([
agent.get(url).expect(404),
agent.put(url).expect(404),
agent.patch(url).expect(404),
agent.delete(url).expect(404),
], done);
});
it('should respond with 400 for requests without a payload', done => {
const responseBody = `Missing or empty 'number' field in request: POST ${url} {}`;
const request1 = agent.post(url);
const request2 = agent.post(url).send();
verifyRequests([
request1.expect(400, responseBody),
request2.expect(400, responseBody),
], done);
});
it('should respond with 400 for requests without a \'number\' field', done => {
const responseBodyPrefix = `Missing or empty 'number' field in request: POST ${url}`;
const request1 = agent.post(url).send({});
const request2 = agent.post(url).send({number: null});
verifyRequests([
request1.expect(400, `${responseBodyPrefix} {}`),
request2.expect(400, `${responseBodyPrefix} {"number":null}`),
], done);
});
it('should call \'BuildVerifier#gtPrIsTrusted()\' with the correct arguments', done => {
const req = createRequest(+pr);
promisifyRequest(req).
then(() => expect(bvGetPrIsTrustedSpy).toHaveBeenCalledWith(9)).
then(done, done.fail);
});
it('should propagate errors from BuildVerifier', done => {
bvGetPrIsTrustedSpy.and.callFake(() => Promise.reject('Test'));
const req = createRequest(+pr).expect(500, 'Test');
promisifyRequest(req).
then(() => {
expect(bvGetPrIsTrustedSpy).toHaveBeenCalledWith(9);
expect(bcUpdatePrVisibilitySpy).not.toHaveBeenCalled();
}).
then(done, done.fail);
});
it('should call \'BuildCreator#updatePrVisibility()\' with the correct arguments', done => {
bvGetPrIsTrustedSpy.and.callFake((pr2: number) => Promise.resolve(pr2 === 42));
const req1 = createRequest(24);
const req2 = createRequest(42);
Promise.all([
promisifyRequest(req1).then(() => expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith('24', false)),
promisifyRequest(req2).then(() => expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith('42', true)),
]).then(done, done.fail);
});
it('should propagate errors from BuildCreator', done => {
bcUpdatePrVisibilitySpy.and.callFake(() => Promise.reject('Test'));
const req = createRequest(+pr).expect(500, 'Test');
verifyRequests([req], done);
});
describe('on success', () => {
it('should respond with 200 (action: undefined)', done => {
bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
const reqs = [4, 2].map(num => createRequest(num).expect(200, http.STATUS_CODES[200]));
verifyRequests(reqs, done);
});
it('should respond with 200 (action: labeled)', done => {
bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
const reqs = [4, 2].map(num => createRequest(num, 'labeled').expect(200, http.STATUS_CODES[200]));
verifyRequests(reqs, done);
});
it('should respond with 200 (action: unlabeled)', done => {
bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
const reqs = [4, 2].map(num => createRequest(num, 'unlabeled').expect(200, http.STATUS_CODES[200]));
verifyRequests(reqs, done);
});
it('should respond with 200 (and do nothing) if \'action\' implies no visibility change', done => {
const promises = ['foo', 'notlabeled'].
map(action => createRequest(+pr, action).expect(200, http.STATUS_CODES[200])).
map(promisifyRequest);
Promise.all(promises).
then(() => {
expect(bvGetPrIsTrustedSpy).not.toHaveBeenCalled();
expect(bcUpdatePrVisibilitySpy).not.toHaveBeenCalled();
}).
then(done, done.fail);
});
describe('GET *', () => {
it('should respond with 404', done => {
const responseBody = 'Unknown resource in request: GET /some/url';
verifyRequests([agent.get('/some/url').expect(404, responseBody)], done);
});
});
@ -584,15 +454,14 @@ describe('uploadServerFactory', () => {
describe('ALL *', () => {
it('should respond with 404', done => {
const responseFor = (method: string) => `Unknown resource in request: ${method.toUpperCase()} /some/url`;
it('should respond with 405', done => {
const responseFor = (method: string) => `Unsupported method in request: ${method.toUpperCase()} /some/url`;
verifyRequests([
agent.get('/some/url').expect(404, responseFor('get')),
agent.put('/some/url').expect(404, responseFor('put')),
agent.post('/some/url').expect(404, responseFor('post')),
agent.patch('/some/url').expect(404, responseFor('patch')),
agent.delete('/some/url').expect(404, responseFor('delete')),
agent.put('/some/url').expect(405, responseFor('put')),
agent.post('/some/url').expect(405, responseFor('post')),
agent.patch('/some/url').expect(405, responseFor('patch')),
agent.delete('/some/url').expect(405, responseFor('delete')),
], done);
});

View File

@ -2,20 +2,13 @@
# yarn lockfile v1
"@types/body-parser@^1.16.4":
version "1.16.4"
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.16.4.tgz#96f3660e6f88a677fee7250f5a5e6d6bda3c76bb"
dependencies:
"@types/express" "*"
"@types/node" "*"
"@types/express-serve-static-core@*":
version "4.0.48"
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.0.48.tgz#b4fa06b0fce282e582b4535ff7fac85cc90173e9"
dependencies:
"@types/node" "*"
"@types/express@*", "@types/express@^4.0.35":
"@types/express@^4.0.35":
version "4.0.36"
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.0.36.tgz#14eb47de7ecb10319f0a2fb1cf971aa8680758c2"
dependencies:
@ -243,21 +236,6 @@ block-stream@*:
dependencies:
inherits "~2.0.0"
body-parser@^1.17.2:
version "1.17.2"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.17.2.tgz#f8892abc8f9e627d42aedafbca66bf5ab99104ee"
dependencies:
bytes "2.4.0"
content-type "~1.0.2"
debug "2.6.7"
depd "~1.1.0"
http-errors "~1.6.1"
iconv-lite "0.4.15"
on-finished "~2.3.0"
qs "6.4.0"
raw-body "~2.2.0"
type-is "~1.6.15"
boom@2.x.x:
version "2.10.1"
resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f"
@ -295,10 +273,6 @@ buffer-equal-constant-time@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
bytes@2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.4.0.tgz#7d97196f9d5baf7f6935e25985549edd2a6c2339"
caller-path@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f"
@ -1184,10 +1158,6 @@ http-signature@~1.1.0:
jsprim "^1.2.2"
sshpk "^1.7.0"
iconv-lite@0.4.15:
version "0.4.15"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb"
ignore-by-default@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09"
@ -1988,14 +1958,6 @@ range-parser@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
raw-body@~2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.2.0.tgz#994976cf6a5096a41162840492f0bdc5d6e7fb96"
dependencies:
bytes "2.4.0"
iconv-lite "0.4.15"
unpipe "1.0.0"
rc@^1.0.1, rc@^1.1.6, rc@^1.1.7:
version "1.2.1"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.1.tgz#2e03e8e42ee450b8cb3dce65be1bf8974e1dfd95"
@ -2515,7 +2477,7 @@ unique-string@^1.0.0:
dependencies:
crypto-random-string "^1.0.0"
unpipe@1.0.0, unpipe@~1.0.0:
unpipe@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"

View File

@ -21,7 +21,7 @@ appName=aio-upload-server-test
if [[ "$1" == "stop" ]]; then
pm2 delete $appName
else
pm2 start $AIO_SCRIPTS_JS_DIR/dist/lib/verify-setup/start-test-upload-server.js \
pm2 start $AIO_SCRIPTS_JS_DIR/dist/lib/upload-server/index-test.js \
--log /var/log/aio/upload-server-test.log \
--name $appName \
--no-autorestart \

View File

@ -3,9 +3,10 @@
TODO (gkalpak): Add docs. Mention:
- Travis' JWT addon (+ limitations).
Relevant files: `.travis.yml`, `scripts/ci/env.sh`
Relevant files: `.travis.yml`
- Testing on CI.
Relevant files: `scripts/ci/test-aio.sh`, `aio/aio-builds-setup/scripts/test.sh`
Relevant files: `ci/test-aio.sh`, `aio/aio-builds-setup/scripts/test.sh`
- Preverifying on CI.
Relevant files: `ci/deploy.sh`, `aio/aio-builds-setup/scripts/travis-preverify-pr.sh`
- Deploying from CI.
Relevant files: `scripts/ci/deploy.sh`, `aio/scripts/deploy-preview.sh`,
`aio/scripts/deploy-to-firebase.sh`
Relevant files: `ci/deploy.sh`, `aio/scripts/deploy-preview.sh`

View File

@ -80,31 +80,13 @@ More info on the possible HTTP status codes and their meaning can be found
[here](overview--http-status-codes.md).
### Updating PR visibility
- nginx receives a natification that a PR has been updated and passes it through to the
upload-server. This could, for example, be sent by a GitHub webhook every time a PR's labels
change.
E.g.: `ngbuilds.io/pr-updated` (payload: `{"number":<PR>,"action":"labeled"}`)
- The request contains the PR number (as `number`) and optionally the action that triggered the
request (as `action`) in the payload.
- The upload-server verifies the payload and determines whether the `action` (if specified) could
have led to PR visibility changes. Only requests that omit the `action` field altogether or
specify an action that can affect visibility are further processed.
(Currently, the only actions that are considered capable of affecting visibility are `labeled` and
`unlabeled`.)
- The upload-server re-checks and if necessary updates the PR's visibility.
More info on the possible HTTP status codes and their meaning can be found
[here](overview--http-status-codes.md).
### Serving build artifacts
- nginx receives a request for an uploaded resource on a subdomain corresponding to the PR and SHA.
E.g.: `pr<PR>-<SHA>.ngbuilds.io/path/to/resource`
- nginx maps the subdomain to the correct sub-directory and serves the resource.
E.g.: `/<PR>/<SHA>/path/to/resource`
More info on the possible HTTP status codes and their meaning can be found
Again, more info on the possible HTTP status codes and their meaning can be found
[here](overview--http-status-codes.md).

View File

@ -42,6 +42,10 @@ with a bried explanation of what they mean:
- **403 (Forbidden)**:
Unable to verify build (e.g. invalid JWT token, or unable to talk to 3rd-party APIs, etc).
- **404 (Not Found)**:
Tried to change PR visibility but the source directory did not exist.
(Currently, this can only happen as a rare race condition during build deployment.)
- **405 (Method Not Allowed)**:
Request method other than POST.
@ -53,28 +57,6 @@ with a bried explanation of what they mean:
Payload larger than size specified in `AIO_UPLOAD_MAX_SIZE`.
## `https://ngbuilds.io/health-check`
- **200 (OK)**:
The server is healthy (i.e. up and running and processing requests).
## `https://ngbuilds.io/pr-updated`
- **200 (OK)**:
Request processed successfully. Processing may or may not have resulted in further actions.
- **400 (Bad Request)**:
No payload or no `number` field in payload.
- **405 (Method Not Allowed)**:
Request method other than POST.
- **409 (Conflict)**:
Request to overwrite existing directory (i.e. directories for both visibilities exist).
(Normally, this should not happen.)
## `https://*.ngbuilds.io/*`
- **404 (Not Found)**:

View File

@ -16,6 +16,13 @@ available:
Can be used for running the tests for `<aio-builds-setup-dir>/dockerbuild/scripts-js/`. This is
useful for CI integration. See [here](misc--integrate-with-ci.md) for more info.
- `travis-preverify-pr.sh`:
Can be used for "pre-verifying" a PR before uploading the artifacts to the server. It checks
whether the author of the PR is a member of one of the specified GitHub teams (therefore allowed
to upload build artifacts) or the PR has the specified "trusted PR" label (meaning it has been
manually verified by a trusted member). This is useful for CI integration.
See [here](misc--integrate-with-ci.md) for more info.
- `update-preview-server.sh`:
Can be used for updating the docker container (and image) based on the latest changes checked out
from a git repository. See [here](vm-setup--update-docker-container.md) for more info.

View File

@ -0,0 +1,26 @@
#!/bin/bash
set -eux -o pipefail
# Set up env
source "`dirname $0`/_env.sh"
# Build `scripts-js/`
(
cd "$SCRIPTS_JS_DIR"
yarn install
yarn build
)
# Preverify PR
AIO_GITHUB_ORGANIZATION="angular" \
AIO_GITHUB_TEAM_SLUGS="angular-core,aio-contributors" \
AIO_GITHUB_TOKEN=$(echo ${GITHUB_TEAM_MEMBERSHIP_CHECK_KEY} | rev) \
AIO_REPO_SLUG=$TRAVIS_REPO_SLUG \
AIO_TRUSTED_PR_LABEL="aio: preview" \
AIO_PREVERIFY_PR=$TRAVIS_PULL_REQUEST \
node "$SCRIPTS_JS_DIR/dist/lib/upload-server/index-preverify-pr"
# Exit codes:
# - 0: The PR can be automatically trusted (i.e. author belongs to trusted team or PR has the "trusted PR" label).
# - 1: An error occurred.
# - 2: The PR cannot be automatically trusted.

View File

@ -12,7 +12,7 @@ import { UserService } from './user.service';
})
export class TitleComponent {
@Input() subtitle = '';
title = 'NgModules';
title = 'Angular Modules';
// #enddocregion v1
user = '';

View File

@ -6,7 +6,7 @@ import { Pipe, PipeTransform } from '@angular/core';
* Usage:
* value | exponentialStrength:exponent
* Example:
* {{ 2 | exponentialStrength:10 }}
* {{ 2 | exponentialStrength:10}}
* formats to: 1024
*/
@Pipe({name: 'exponentialStrength'})

View File

@ -16,7 +16,7 @@ import { HeroService } from './hero.service'; // <-- #1 import service
@NgModule({
imports: [
BrowserModule,
ReactiveFormsModule // <-- #2 add to @NgModule imports
ReactiveFormsModule // <-- #2 add to Angular module imports
],
declarations: [
AppComponent,

View File

@ -4,7 +4,7 @@ import { Directive } from '@angular/core';
@Directive({
selector: '[tohValidator2]',
host: {
'[attr.role]': 'role',
'attr.role': 'button',
'(mouseenter)': 'onMouseEnter()'
}
})

View File

@ -1000,7 +1000,7 @@ For more information on pipes, see [Pipes](guide/pipes).
## Modules/controllers/components
In both AngularJS and Angular, modules help you organize your application into cohesive blocks of functionality.
In both AngularJS and Angular, Angular modules help you organize your application into cohesive blocks of functionality.
In AngularJS, you write the code that provides the model and the methods for the view in a **controller**.
In Angular, you build a **component**.
@ -1080,18 +1080,18 @@ The Angular code is shown using TypeScript.
<td>
### NgModules
### Angular modules
<code-example hideCopy path="ajs-quick-reference/src/app/app.module.1.ts" linenums="false">
</code-example>
NgModules, defined with the `NgModule` decorator, serve the same purpose:
Angular modules, defined with the `NgModule` decorator, serve the same purpose:
* `imports`: specifies the list of other modules that this module depends upon
* `declaration`: keeps track of your components, pipes, and directives.
For more information on modules, see [NgModules](guide/ngmodule).
For more information on modules, see [Angular Modules (NgModule)](guide/ngmodule).
</td>
</tr>

View File

@ -475,7 +475,7 @@ You'll need separate TypeScript configuration files such as these:
<div class="callout is-helpful">
<header>
`@types` and node modules
@Types and node modules
</header>
In the file structure of _this particular sample project_,
@ -568,7 +568,7 @@ Run the following command to generate the map.
</code-example>
The `source-map-explorer` analyzes the source map generated with the bundle and draws a map of all dependencies,
showing exactly which application and NgModules and classes are included in the bundle.
showing exactly which application and Angular modules and classes are included in the bundle.
Here's the map for _Tour of Heroes_.

View File

@ -31,21 +31,21 @@ You'll learn the details in the pages that follow. For now, focus on the big pic
<img src="generated/images/guide/architecture/module.png" alt="Component" class="left">
Angular apps are modular and Angular has its own modularity system called _NgModules_.
Angular apps are modular and Angular has its own modularity system called _Angular modules_ or _NgModules_.
NgModules are a big deal.
This page introduces modules; the [NgModules](guide/ngmodule) page covers them in depth.
_Angular modules_ are a big deal.
This page introduces modules; the [Angular modules](guide/ngmodule) page covers them in depth.
<br class="clear">
Every Angular app has at least one NgModule class, [the _root module_](guide/bootstrapping "Bootstrapping"),
Every Angular app has at least one Angular module class, [the _root module_](guide/bootstrapping "AppModule: the root module"),
conventionally named `AppModule`.
While the _root module_ may be the only module in a small application, most apps have many more
_feature modules_, each a cohesive block of code dedicated to an application domain,
a workflow, or a closely related set of capabilities.
An NgModule, whether a _root_ or _feature_, is a class with an `@NgModule` decorator.
An Angular module, whether a _root_ or _feature_, is a class with an `@NgModule` decorator.
<div class="l-sub-section">
@ -87,12 +87,12 @@ During development you're likely to bootstrap the `AppModule` in a `main.ts` fil
<code-example path="architecture/src/main.ts" title="src/main.ts" linenums="false"></code-example>
### NgModules vs. JavaScript modules
### Angular modules vs. JavaScript modules
The NgModule &mdash; a class decorated with `@NgModule` &mdash; is a fundamental feature of Angular.
The Angular module &mdash; a class decorated with `@NgModule` &mdash; is a fundamental feature of Angular.
JavaScript also has its own module system for managing collections of JavaScript objects.
It's completely different and unrelated to the NgModule system.
It's completely different and unrelated to the Angular module system.
In JavaScript each _file_ is a module and all objects defined in the file belong to that module.
The module declares some objects to be public by marking them with the `export` key word.
@ -124,7 +124,7 @@ For example, import Angular's `Component` decorator from the `@angular/core` lib
<code-example path="architecture/src/app/app.component.ts" region="import" linenums="false"></code-example>
You also import NgModules_ from Angular _libraries_ using JavaScript import statements:
You also import Angular _modules_ from Angular _libraries_ using JavaScript import statements:
<code-example path="architecture/src/app/mini-app.ts" region="import-browser-module" linenums="false"></code-example>
@ -139,7 +139,7 @@ Hang in there. The confusion yields to clarity with time and experience.
<div class="l-sub-section">
Learn more from the [NgModules](guide/ngmodule) page.
Learn more from the [Angular modules](guide/ngmodule) page.
</div>

View File

@ -1,8 +1,8 @@
# Bootstrapping
An NgModule class describes how the application parts fit together.
Every application has at least one NgModule, the _root_ module
that you [bootstrap](guide/appmodule#main) to launch the application.
An Angular module class describes how the application parts fit together.
Every application has at least one Angular module, the _root_ module
that you [bootstrap](guide/bootstrapping#main) to launch the application.
You can call it anything you want. The conventional name is `AppModule`.
The [setup](guide/setup) instructions produce a new project with the following minimal `AppModule`.
@ -17,15 +17,15 @@ You'll evolve this module as your application grows.
After the `import` statements, you come to a class adorned with the
**`@NgModule`** [_decorator_](guide/glossary#decorator '"Decorator" explained').
The `@NgModule` decorator identifies `AppModule` as an `NgModule` class.
The `@NgModule` decorator identifies `AppModule` as an Angular module class (also called an `NgModule` class).
`@NgModule` takes a _metadata_ object that tells Angular how to compile and launch the application.
* **_imports_** &mdash; the `BrowserModule` that this and every application needs to run in a browser.
* **_declarations_** &mdash; the application's lone component, which is also ...
* **_bootstrap_** &mdash; the _root_ component that Angular creates and inserts into the `index.html` host web page.
The [NgModules](guide/ngmodule) guide dives deeply into the details of NgModules.
All you need to know at the moment is a few basics about these three properties.
The [Angular Modules (NgModule)](guide/ngmodule) guide dives deeply into the details of Angular modules.
All you need to know at the moment is a few basics about these three properties.
{@a imports}
@ -33,8 +33,8 @@ All you need to know at the moment is a few basics about these three properties.
### The _imports_ array
NgModules are a way to consolidate features that belong together into discrete units.
Many features of Angular itself are organized as NgModules.
Angular modules are a way to consolidate features that belong together into discrete units.
Many features of Angular itself are organized as Angular modules.
HTTP services are in the `HttpModule`. The router is in the `RouterModule`.
Eventually you may create a feature module.
@ -61,7 +61,7 @@ Other guide and cookbook pages will tell you when you need to add additional mod
The `import` statements at the top of the file and the NgModule's `imports` array
The `import` statements at the top of the file and the Angular module's `imports` array
are unrelated and have completely different jobs.
The _JavaScript_ `import` statements give you access to symbols _exported_ by other files
@ -70,8 +70,8 @@ You add `import` statements to almost every application file.
They have nothing to do with Angular and Angular knows nothing about them.
The _module's_ `imports` array appears _exclusively_ in the `@NgModule` metadata object.
It tells Angular about specific _other_ NgModules&mdash;all of them classes decorated
with `@NgModule`&mdash;that the application needs to function properly.
It tells Angular about specific _other_ Angular modules &mdash; all of them classes decorated with `@NgModule` &mdash;
that the application needs to function properly.
</div>
@ -178,11 +178,11 @@ This file is very stable. Once you've set it up, you may never change it again.
## More about NgModules
## More about Angular Modules
Your initial app has only a single module, the _root_ module.
As your app grows, you'll consider subdividing it into multiple "feature" modules,
some of which can be loaded later ("lazy loaded") if and when the user chooses
to visit those features.
When you're ready to explore these possibilities, visit the [NgModules](guide/ngmodule) guide.
When you're ready to explore these possibilities, visit the [Angular Modules (NgModule)](guide/ngmodule) guide.

View File

@ -199,7 +199,7 @@ The new "angular-in-memory-web-api" has new features.
## "Style Guide" with _NgModules_ (2016-09-27)
[StyleGuide](guide/styleguide) explains recommended conventions for NgModules.
[StyleGuide](guide/styleguide) explains recommended conventions for Angular modules (NgModule).
Barrels now are far less useful and have been removed from the style guide;
they remain valuable but are not a matter of Angular style.
Also relaxed the rule that discouraged use of the `@Component.host` property.

View File

@ -119,13 +119,12 @@ if some ancestor element has the CSS class `theme-light`.
### (deprecated) `/deep/`, `>>>`, and `::ng-deep`
### /deep/
Component styles normally apply only to the HTML in the component's own template.
Use the `/deep/` shadow-piercing descendant combinator to force a style down through the child
component tree into all the child component views.
The `/deep/` combinator works to any depth of nested components, and it applies to both the view
Use the `/deep/` selector to force a style down through the child component tree into all the child component views.
The `/deep/` selector works to any depth of nested components, and it applies to both the view
children and content children of the component.
The following example targets all `<h3>` elements, from the host element down
@ -135,24 +134,17 @@ through this component to all of its child elements in the DOM.
</code-example>
The `/deep/` combinator also has the aliases `>>>`, and `::ng-deep`.
The `/deep/` selector also has the alias `>>>`. You can use either interchangeably.
<div class="alert is-important">
Use `/deep/`, `>>>` and `::ng-deep` only with *emulated* view encapsulation.
Use the `/deep/` and `>>>` selectors only with *emulated* view encapsulation.
Emulated is the default and most commonly used view encapsulation. For more information, see the
[Controlling view encapsulation](guide/component-styles#view-encapsulation) section.
</div>
<div class="alert is-important">
The shadow-piercing descendant combinator is deprecated and [support is being removed from major browsers](https://www.chromestatus.com/features/6750456638341120) and tools.
As such we plan to drop support in Angular (for all 3 of `/deep/`, `>>>` and `::ng-deep`).
Until then `::ng-deep` should be preferred for a broader compatibility with the tools.
</div>
{@a loading-styles}
## Loading component styles

View File

@ -64,7 +64,7 @@ This gives you a reference to the Angular `NgModel` directive
associated with this control that you can use _in the template_
to check for control states such as `valid` and `dirty`.
* The `*ngIf` on the `<div>` element reveals a set of nested message `divs`
* The `*ngIf` on the `<div>` element reveals a set of nested message `divs`
but only if there are `name` errors and
the control is either `dirty` or `touched`.
@ -321,7 +321,7 @@ This allows you to do the following:
* Add, change, and remove validation functions on the fly.
* Manipulate the control model dynamically from within the component.
* [Test](guide/form-validation#testing-considerations) validation and control logic with isolated unit tests.
* [Test](guide/form-validation#testing) validation and control logic with isolated unit tests.
The following sample re-writes the hero form in Reactive Forms style.
@ -386,7 +386,7 @@ but rather for css styling and accessibility.
<div class="l-sub-section">
Currently, Reactive Forms doesn't add the `required` or `aria-required`
Currently, Reactive Forms doesn't add the `required` or `aria-required`
HTML validation attribute to the DOM element
when the control has the `required` validator function.
@ -455,12 +455,12 @@ to set error messages for the new control model.
## Built-in validators
Angular forms include a number of built-in validator functions, which are functions
that help you check common user input in forms. In addition to the built-in
validators covered here of `minlength`, `maxlength`,
and `required`, there are others such as `email` and `pattern`
for Reactive Forms.
For a full list of built-in validators,
Angular forms include a number of built-in validator functions, which are functions
that help you check common user input in forms. In addition to the built-in
validators covered here of `minlength`, `maxlength`,
and `required`, there are others such as `email` and `pattern`
for Reactive Forms.
For a full list of built-in validators,
see the [Validators](api/forms/Validators) API reference.
@ -486,7 +486,7 @@ Learn more about `FormBuilder` in the [Introduction to FormBuilder](guide/reacti
#### Committing hero value changes
In two-way data binding, the user's changes flow automatically from the controls back to the data model properties.
A Reactive Forms component should not use data binding to
A Reactive Forms component should not use data binding to
automatically update data model properties.
The developer decides _when and how_ to update the data model from control values.

View File

@ -214,10 +214,10 @@ There are three changes:
1. You import `FormsModule` and the new `HeroFormComponent`.
1. You add the `FormsModule` to the list of `imports` defined in the `@NgModule` decorator. This gives the application
1. You add the `FormsModule` to the list of `imports` defined in the `ngModule` decorator. This gives the application
access to all of the template-driven forms features, including `ngModel`.
1. You add the `HeroFormComponent` to the list of `declarations` defined in the `@NgModule` decorator. This makes
1. You add the `HeroFormComponent` to the list of `declarations` defined in the `ngModule` decorator. This makes
the `HeroFormComponent` component visible throughout this module.

View File

@ -25,8 +25,15 @@ to a module factory, meaning you don't need to include the Angular compiler in y
Ahead-of-time compiled applications also benefit from decreased load time and increased performance.
## Angular module
</div>
Helps you organize an application into cohesive blocks of functionality.
An Angular module identifies the components, directives, and pipes that the application uses along with the list of external Angular modules that the application needs, such as `FormsModule`.
Every Angular application has an application root-module class. By convention, the class is
called `AppModule` and resides in a file named `app.module.ts`.
For details and examples, see the [Angular Modules (NgModule)](guide/ngmodule) page.
## Annotation
@ -108,7 +115,7 @@ The Angular [scoped packages](guide/glossary#scoped-package) each have a barrel
You can often achieve the same result using [NgModules](guide/glossary#ngmodule) instead.
You can often achieve the same result using [Angular modules](guide/glossary#angular-module) instead.
</div>
@ -125,11 +132,7 @@ between a "token"&mdash;also referred to as a "key"&mdash;and a dependency [prov
## Bootstrap
<div class="l-sub-section">
You launch an Angular application by "bootstrapping" it using the application root NgModule (`AppModule`).
You launch an Angular application by "bootstrapping" it using the application root Angular module (`AppModule`).
Bootstrapping identifies an application's top level "root" [component](guide/glossary#component),
which is the first component that is loaded for the application.
For more information, see the [Setup](guide/setup) page.
@ -343,13 +346,13 @@ elements and their children.
The [official JavaScript language specification](https://en.wikipedia.org/wiki/ECMAScript).
The latest approved version of JavaScript is
[ECMAScript 2017](http://www.ecma-international.org/ecma-262/8.0/)
(also known as "ES2017" or "ES8"). Many Angular developers write their applications
in ES8 or a dialect that strives to be
[ECMAScript 2016](http://www.ecma-international.org/ecma-262/7.0/)
(also known as "ES2016" or "ES7"). Many Angular developers write their applications
in ES7 or a dialect that strives to be
compatible with it, such as [TypeScript](guide/glossary#typescript).
Most modern browsers only support the much older "ECMAScript 5" (also known as "ES5") standard.
Applications written in ES2017, ES2016, ES2015, or one of their dialects must be [transpiled](guide/glossary#transpile)
Applications written in ES2016, ES2015, or one of their dialects must be [transpiled](guide/glossary#transpile)
to ES5 JavaScript.
Angular developers can write in ES5 directly.
@ -472,8 +475,8 @@ Read more in the [Lifecycle Hooks](guide/lifecycle-hooks) page.
Angular has the following types of modules:
* [NgModules](guide/glossary#ngmodule).
For details and examples, see the [NgModules](guide/ngmodule) page.
* [Angular modules](guide/glossary#angular-module).
For details and examples, see the [Angular Modules](guide/ngmodule) page.
* ES2015 modules, as described in this section.
@ -490,7 +493,7 @@ In general, you assemble an application from many modules, both the ones you wri
A module *exports* something of value in that code, typically one thing such as a class;
a module that needs that class *imports* it.
The structure of NgModules and the import/export syntax
The structure of Angular modules and the import/export syntax
is based on the [ES2015 module standard](http://www.2ality.com/2014/09/es6-modules-final.html).
An application that adheres to this standard requires a module loader to
@ -508,24 +511,6 @@ You rarely access Angular feature modules directly. You usually import them from
{@a N}
## NgModule
<div class="l-sub-section">
Helps you organize an application into cohesive blocks of functionality.
An NgModule identifies the components, directives, and pipes that the application uses along with the list of external NgModules that the application needs, such as `FormsModule`.
Every Angular application has an application root-module class. By convention, the class is
called `AppModule` and resides in a file named `app.module.ts`.
For details and examples, see [NgModules](guide/ngmodule).
</div>
{@a O}
## Observable
@ -629,9 +614,7 @@ For more information, see the [Routing & Navigation](guide/router) page.
## Router module
<div class="l-sub-section">
A separate [NgModule](guide/glossary#ngmodule) that provides the necessary service providers and directives for navigating through application views.
A separate [Angular module](guide/glossary#angular-module) that provides the necessary service providers and directives for navigating through application views.
For more information, see the [Routing & Navigation](guide/router) page.
@ -650,7 +633,7 @@ For more information, see the [Routing & Navigation](guide/router) page.
A way to group related *npm* packages.
Read more at the [npm-scope](https://docs.npmjs.com/misc/scope) page.
NgModules are delivered within *scoped packages* such as `@angular/core`,
Angular modules are delivered within *scoped packages* such as `@angular/core`,
`@angular/common`, `@angular/platform-browser-dynamic`, `@angular/http`, and `@angular/router`.
Import a scoped package the same way that you import a normal package.
@ -812,4 +795,4 @@ asynchronous events by checking for data changes and updating
the information it displays via [data bindings](guide/glossary#data-binding).
Learn more about zones in this
[Brian Ford video](https://www.youtube.com/watch?v=3IqtmUscE_U).
[Brian Ford video](https://www.youtube.com/watch?v=3IqtmUscE_U).

File diff suppressed because it is too large Load Diff

View File

@ -1327,7 +1327,7 @@ Here's an _NgModule_ class with imports, exports, and declarations.
Of course you use _JavaScript_ modules to write NgModules as seen in the complete `contact.module.ts` file:
Of course you use _JavaScript_ modules to write _Angular_ modules as seen in the complete `contact.module.ts` file:
<code-example path="ngmodule/src/app/contact/contact.module.2.ts" title="src/app/contact/contact.module.ts" linenums="false">

View File

@ -464,7 +464,7 @@ These files go in the root folder next to `src/`.
</td>
<td>
Inside `e2e/` live the end-to-end tests.
Inside `e2e/` live the End-to-End tests.
They shouldn't be inside `src/` because e2e tests are really a separate app that
just so happens to test your main app.
That's also why they have their own `tsconfig.e2e.json`.
@ -493,7 +493,7 @@ These files go in the root folder next to `src/`.
Configuration for Angular CLI.
In this file you can set several defaults and also configure what files are included
when your project is built.
when your project is build.
Check out the official documentation if you want to know more.
</td>

View File

@ -347,7 +347,7 @@ Here are the key `Router` terms and their meanings:
</td>
<td>
A separate NgModule that provides the necessary service providers
A separate Angular module that provides the necessary service providers
and directives for navigating through application views.
</td>
@ -3794,7 +3794,7 @@ Take the final step and detach the admin feature set from the main application.
The root `AppModule` must neither load nor reference the `AdminModule` or its files.
In `app.module.ts`, remove the `AdminModule` import statement from the top of the file
and remove the `AdminModule` from the NgModule's `imports` array.
and remove the `AdminModule` from the Angular module's `imports` array.
{@a can-load-guard}

View File

@ -282,7 +282,36 @@ This technique is effective because all browsers implement the _same origin poli
on which cookies are set can read the cookies from that site and set custom headers on requests to that site.
That means only your application can read this cookie token and set the custom header. The malicious code on `evil.com` can't.
Angular's `HttpClient` has built-in support for the client-side half of this technique. Read about it more in the [HttpClient guide](/guide/http).
Angular's `http` has built-in support for the client-side half of this technique in its `XSRFStrategy`.
The default `CookieXSRFStrategy` is turned on automatically.
Before sending an HTTP request, the `CookieXSRFStrategy` looks for a cookie called `XSRF-TOKEN` and
sets a header named `X-XSRF-TOKEN` with the value of that cookie.
The server must do its part by setting the
initial `XSRF-TOKEN` cookie and confirming that each subsequent state-modifying request
includes a matching `XSRF-TOKEN` cookie and `X-XSRF-TOKEN` header.
XSRF/CSRF tokens should be unique per user and session, have a large random value generated by a
cryptographically secure random number generator, and expire in a day or two.
Your server may use a different cookie or header name for this purpose.
An Angular application can customize cookie and header names by providing its own `CookieXSRFStrategy` values.
<code-example language="typescript">
{ provide: XSRFStrategy, useValue: new CookieXSRFStrategy('myCookieName', 'My-Header-Name') }
</code-example>
Or you can implement and provide an entirely custom `XSRFStrategy`:
<code-example language="typescript">
{ provide: XSRFStrategy, useClass: MyXSRFStrategy }
</code-example>
For information about CSRF at the Open Web Application Security Project (OWASP), see
<a href="https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29">Cross-Site Request Forgery (CSRF)</a> and
@ -308,7 +337,7 @@ This attack is only successful if the returned JSON is executable as JavaScript.
prevent an attack by prefixing all JSON responses to make them non-executable, by convention, using the
well-known string `")]}',\n"`.
Angular's `HttpClient` library recognizes this convention and automatically strips the string
Angular's `Http` library recognizes this convention and automatically strips the string
`")]}',\n"` from all responses before further parsing.
For more information, see the XSSI section of this [Google web security blog

View File

@ -2129,12 +2129,12 @@ discourage the `I` prefix.
<a href="#toc">Back to top</a>
## Application structure and NgModules
## Application structure and Angular modules
Have a near-term view of implementation and a long-term vision. Start small but keep in mind where the app is heading down the road.
All of the app's code goes in a folder named `src`.
All feature areas are in their own folder, with their own NgModule.
All feature areas are in their own folder, with their own Angular module.
All content is one asset per file. Each component, service, and pipe is in its own file.
All third party vendor scripts are stored in another folder and not in the `src` folder.
@ -2779,7 +2779,7 @@ and more difficult in a flat structure.
**Do** create an NgModule for each feature area.
**Do** create an Angular module for each feature area.
</div>
@ -2790,7 +2790,7 @@ and more difficult in a flat structure.
**Why?** NgModules make it easy to lazy load routable features.
**Why?** Angular modules make it easy to lazy load routable features.
</div>
@ -2801,7 +2801,7 @@ and more difficult in a flat structure.
**Why?** NgModules make it easier to isolate, test, and re-use features.
**Why?** Angular modules make it easier to isolate, test, and re-use features.
</div>
@ -2827,7 +2827,7 @@ and more difficult in a flat structure.
**Do** create an NgModule in the app's root folder,
**Do** create an Angular module in the app's root folder,
for example, in `/src/app`.
@ -2839,7 +2839,7 @@ for example, in `/src/app`.
**Why?** Every app requires at least one root NgModule.
**Why?** Every app requires at least one root Angular module.
</div>
@ -2888,7 +2888,7 @@ for example, in `/src/app`.
**Do** create an NgModule for all distinct features in an application;
**Do** create an Angular module for all distinct features in an application;
for example, a `Heroes` feature.

View File

@ -12,8 +12,8 @@ component class instance (the *component*) and its user-facing template.
You may be familiar with the component/template duality from your experience with model-view-controller (MVC) or model-view-viewmodel (MVVM).
In Angular, the component plays the part of the controller/viewmodel, and the template represents the view.
This page is a comprehensive technical reference to the Angular template language.
It explains basic principles of the template language and describes most of the syntax that you'll encounter elsewhere in the documentation.
This page is a comprehensive technical reference to the Angular template language.
It explains basic principles of the template language and describes most of the syntax that you'll encounter elsewhere in the documentation.
Many code snippets illustrate the points and concepts, all of them available
in the <live-example title="Template Syntax Live Code"></live-example>.
@ -1155,7 +1155,7 @@ other HTML elements, attributes, properties, and components.
They are usually applied to elements as if they were HTML attributes, hence the name.
Many details are covered in the [_Attribute Directives_](guide/attribute-directives) guide.
Many NgMdules such as the [`RouterModule`](guide/router "Routing and Navigation")
Many Angular modules such as the [`RouterModule`](guide/router "Routing and Navigation")
and the [`FormsModule`](guide/forms "Forms") define their own attribute directives.
This section is an introduction to the most commonly used attribute directives:
@ -1260,7 +1260,7 @@ Two-way data binding with the `NgModel` directive makes that easy. Here's an exa
#### _FormsModule_ is required to use _ngModel_
Before using the `ngModel` directive in a two-way data binding,
you must import the `FormsModule` and add it to the NgModule's `imports` list.
you must import the `FormsModule` and add it to the Angular module's `imports` list.
Learn more about the `FormsModule` and `ngModel` in the
[Forms](guide/forms#ngModel) guide.
@ -1947,12 +1947,12 @@ As of Typescript 2.0, you can enforce [strict null checking](http://www.typescri
In this mode, typed variables disallow null and undefined by default. The type checker throws an error if you leave a variable unassigned or try to assign null or undefined to a variable whose type disallows null and undefined.
The type checker also throws an error if it can't determine whether a variable will be null or undefined at runtime.
You may know that can't happen but the type checker doesn't know.
The type checker also throws an error if it can't determine whether a variable will be null or undefined at runtime.
You may know that can't happen but the type checker doesn't know.
You tell the type checker that it can't happen by applying the post-fix
[_non-null assertion operator (!)_](http://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator "Non-null assertion operator").
[_non-null assertion operator (!)_]((http://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator "Non-null assertion operator").
The _Angular_ **non-null assertion operator (`!`)** serves the same purpose in an Angular template.
The _Angular_ **non-null assertion operator (`!`)** serves the same purpose in an Angular template.
For example, after you use [*ngIf](guide/template-syntax#ngIf) to check that `hero` is defined, you can assert that
`hero` properties are also defined.

View File

@ -427,7 +427,7 @@ and re-attach it to a dynamically-constructed Angular test module
tailored specifically for this battery of tests.
The `configureTestingModule` method takes an `@NgModule`-like metadata object.
The metadata object can have most of the properties of a normal [NgModule](guide/ngmodule).
The metadata object can have most of the properties of a normal [Angular module](guide/ngmodule).
_This metadata object_ simply declares the component to test, `BannerComponent`.
The metadata lack `imports` because (a) the default testing module configuration already has what `BannerComponent` needs

View File

@ -62,7 +62,7 @@ There are a few rules in particular that will make it much easier to do
* The [Folders-by-Feature Structure](https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md#folders-by-feature-structure)
and [Modularity](https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md#modularity)
rules define similar principles on a higher level of abstraction: Different parts of the
application should reside in different directories and NgModules.
application should reside in different directories and Angular modules.
When an application is laid out feature per feature in this way, it can also be
migrated one feature at a time. For applications that don't already look like
@ -382,12 +382,12 @@ that describes Angular assets in metadata. The differences blossom from there.
In a hybrid application you run both versions of Angular at the same time.
That means that you need at least one module each from both AngularJS and Angular.
You will import `UpgradeModule` inside the NgModule, and then use it for
You will import `UpgradeModule` inside the Angular module, and then use it for
bootstrapping the AngularJS module.
<div class="l-sub-section">
Read more about [NgModules](guide/ngmodule).
Learn more about Angular modules at the [NgModule guide](guide/ngmodule).
</div>
@ -485,7 +485,7 @@ Because `HeroDetailComponent` is an Angular component, you must also add it to t
And because this component is being used from the AngularJS module, and is an entry point into
the Angular application, you must add it to the `entryComponents` for the
NgModule.
Angular module.
<code-example path="upgrade-module/src/app/downgrade-static/app.module.ts" region="ngmodule" title="app.module.ts">
</code-example>

View File

@ -316,14 +316,7 @@
"desc": "Angular UI Components including Grids, Charts, Scheduling and more.",
"rev": true,
"title": "jQWidgets",
"url": "http://www.jqwidgets.com/angular/"
},
"amexio": {
"desc": "Amexio (Angular MetaMagic EXtensions for Inputs and Outputs) is a rich set of Angular components powered by Bootstrap for Responsive Design. UI Components include Standard Form Components, Data Grids, Tree Grids, Tabs etc. Open Source (Apache 2 License) & Free and backed by MetaMagic Global Inc",
"rev": true,
"title": "Amexio - Angular Extensions",
"url": "http://www.amexio.tech/",
"logo": "http://www.amexio.org/amexio-logo.png"
"url": "https://www.jqwidgets.com/angular/"
}
}
}
@ -515,6 +508,12 @@
"title": "Learn Angular (francais)",
"url": "http://www.learn-angular.fr/"
},
"sad200": {
"desc": "Free Angular training delivered by SFEIR in France",
"rev": true,
"title": "SFEIR School (French)",
"url": "https://school.sfeir.com/project/sad-200/"
},
"toddmotto-ultimateangular": {
"desc": "Online courses providing in-depth coverage of the Angular ecosystem, AngularJS, Angular and TypeScript, with functional code samples and a full-featured seed environment. Get a deep understanding of Angular and TypeScript from foundation to functional application, then move on to advanced topics with Todd Motto and collaborators.",
"rev": true,
@ -601,12 +600,6 @@
"rev": true,
"title": "Learn Javascript (Russian)",
"url": "https://learn.javascript.ru/courses/angular"
},
"sa200": {
"desc": "Free Angular training delivered by SFEIR in France",
"rev": true,
"title": "SFEIR School (French)",
"url": "https://school.sfeir.com/project/sa200/"
}
}
}

View File

@ -252,7 +252,7 @@
{
"url": "guide/http",
"title": "HttpClient",
"title": "Server Communication",
"tooltip": "Use HTTP to talk to a remote server."
},
{
@ -470,8 +470,6 @@
],
"docVersions": [
{ "title": "v2", "url": "https://v2.angular.io" },
{ "title": "AngularDart", "url": "https://webdev.dartlang.org/angular" }
{ "title": "v2", "url": "https://v2.angular.io" }
]
}

View File

@ -249,7 +249,7 @@ Here's the complete `HeroDetailComponent`.
## Declare _HeroDetailComponent_ in the _AppModule_
Every component must be declared in one&mdash;and only one&mdash;NgModule.
Every component must be declared in one&mdash;and only one&mdash;Angular module.
Open `app.module.ts` in your editor and import the `HeroDetailComponent` so you can refer to it.
@ -276,7 +276,7 @@ This module declares only the two application components, `AppComponent` and `He
Read more about NgModules in the [NgModules](guide/ngmodule "NgModules") guide.
Read more about Angular modules in the [NgModules](guide/ngmodule "Angular Modules") guide.
</div>
@ -449,8 +449,8 @@ Here's what you achieved in this page:
* You created a reusable component.
* You learned how to make a component accept input.
* You learned to declare the required application directives in an NgModule. You
listed the directives in the `@NgModule` decorator's `declarations` array.
* You learned to declare the required application directives in an Angular module. You
listed the directives in the `NgModule` decorator's `declarations` array.
* You learned to bind a parent component to a child component.
Your app should look like this <live-example></live-example>.

View File

@ -30,7 +30,7 @@ You can keep building the Tour of Heroes without pausing to recompile or refresh
## Providing HTTP Services
The `HttpModule` is not a core NgModule.
The `HttpModule` is not a core Angular module.
`HttpModule` is Angular's optional approach to web access. It exists as a separate add-on module called `@angular/http`
and is shipped in a separate script file as part of the Angular npm package.

View File

@ -7,8 +7,7 @@
"cleanUrls": true,
"redirects": [
// cli-quickstart.html glossary.html, quickstart.html, http.html, style-guide.html, styleguide
{"type": 301, "source": "/docs/ts/latest/cli-quickstart.html", "destination": "/guide/quickstart"},
{"type": 301, "source": "/guide/cli-quickstart", "destination": "/guide/quickstart"},
{"type": 301, "source": "/docs/ts/latest/cli-quickstart.html", "destination": "/guide/cli-quickstart"},
{"type": 301, "source": "/docs/ts/latest/glossary.html", "destination": "/guide/glossary"},
{"type": 301, "source": "/docs/ts/latest/quickstart.html", "destination": "/guide/quickstart"},
{"type": 301, "source": "/docs/ts/latest/guide/server-communication.html", "destination": "/guide/http"},

View File

@ -19,7 +19,7 @@
"routing": {
"index": "/index.html",
"routes": {
"^(?!/docs/ts/latest|/guide/cli-quickstart|/styleguide).*/(?!e?plnkr)[^/.]*$": {
"^(?!/docs/ts/latest|/styleguide).*/(?!e?plnkr)[^/.]*$": {
"match": "regex"
}
}

View File

@ -81,7 +81,7 @@
"concurrently": "^3.4.0",
"cross-spawn": "^5.1.0",
"dgeni": "^0.4.7",
"dgeni-packages": "^0.20.0-rc.5",
"dgeni-packages": "^0.19.1",
"entities": "^1.1.1",
"eslint": "^3.19.0",
"eslint-plugin-jasmine": "^2.2.0",

View File

@ -1,15 +1,13 @@
#!/bin/bash
set -eu -o pipefail
# WARNING: FIREBASE_TOKEN should NOT be printed.
set +x -eu -o pipefail
readonly thisDir=$(cd $(dirname $0); pwd)
readonly parentDir=$(dirname $thisDir)
readonly TOKEN=${ANGULAR_PAYLOAD_FIREBASE_TOKEN:-}
readonly PROJECT_NAME="angular-payload-size"
# temporarily turn on debugging - we disable it later in the script to prevent token leak
set -x
source ${thisDir}/_payload-limits.sh
failed=false
@ -45,15 +43,20 @@ timestamp=$(date +%s)
payloadData="$payloadData\"timestamp\": $timestamp, "
# Add change source: application, dependencies, or 'application+dependencies'
applicationChanges=$(git diff --name-only $TRAVIS_COMMIT_RANGE $parentDir | grep -v ${parentDir}/content | grep -v ${parentDir}/yarn.lock | wc -l)
dependencyChanges=$(git diff --name-only $TRAVIS_COMMIT_RANGE ${parentDir}/yarn.lock | wc -l)
yarnChanged=false
allChangedFiles=$(git diff --name-only $TRAVIS_COMMIT_RANGE $parentDir | wc -l)
allChangedFileNames=$(git diff --name-only $TRAVIS_COMMIT_RANGE $parentDir)
if [[ $dependencyChanges -eq 1 ]] && [[ $applicationChanges -eq 0 ]]; then
if [[ $allChangedFileNames == *"yarn.lock"* ]]; then
yarnChanged=true
fi
if [[ $allChangedFiles -eq 1 ]] && [[ "$yarnChanged" = true ]]; then
# only yarn.lock changed
change='dependencies'
elif [[ $dependencyChanges -eq 1 ]] && [[ $applicationChanges -gt 0 ]]; then
elif [[ $allChangedFiles -gt 1 ]] && [[ "$yarnChanged" = true ]]; then
change='application+dependencies'
elif [[ $applicationChanges -gt 0 ]]; then
elif [[ $allChangedFiles -gt 0 ]]; then
change='application'
else
# Nothing changed in aio/
@ -68,9 +71,6 @@ echo $payloadData
if [[ "$TRAVIS_PULL_REQUEST" == "false" ]]; then
readonly safeBranchName=$(echo $TRAVIS_BRANCH | sed -e 's/\./_/g')
readonly dbPath=/payload/aio/$safeBranchName/$TRAVIS_COMMIT
# WARNING: FIREBASE_TOKEN should NOT be printed.
set +x
firebase database:update --data "$payloadData" --project $PROJECT_NAME --confirm --token "$TOKEN" $dbPath
fi

View File

@ -1,6 +1,7 @@
import { Component, DebugElement, Input } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { Component, DebugElement, ElementRef, Input, OnInit, ViewChild } from '@angular/core';
import { CodeExampleComponent } from './code-example.component';
@ -74,12 +75,13 @@ describe('CodeExampleComponent', () => {
});
//// Test helpers ////
// tslint:disable:member-ordering
@Component({
selector: 'aio-code',
template: `
<div>lang: {{language}}</div>
<div>linenums: {{linenums}}</div>
code: <pre>{{someCode}}</pre>
<div>lang: {{language}}</div>
<div>linenums: {{linenums}}</div>
code: <pre>{{someCode}}</pre>
`
})
class TestCodeComponent {

View File

@ -1,245 +0,0 @@
import { CommonModule } from '@angular/common';
import { Component, DebugElement, Input, NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MdTabGroup, MdTabsModule } from '@angular/material';
import { By } from '@angular/platform-browser';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { CodeTabsComponent } from './code-tabs.component';
describe('CodeTabsComponent', () => {
let fixture: ComponentFixture<HostComponent>;
let hostComponent: HostComponent;
let codeTabsDe: DebugElement;
let codeTabsComponent: CodeTabsComponent;
const createComponentBasic = (codeTabsContent = '') => {
fixture = TestBed.createComponent(HostComponent);
hostComponent = fixture.componentInstance;
codeTabsDe = fixture.debugElement.children[0];
codeTabsComponent = codeTabsDe.componentInstance;
// Copy the CodeTab's innerHTML (content)
// into the `codeTabsContent` property as the DocViewer does.
codeTabsDe.nativeElement.codeTabsContent = codeTabsContent;
fixture.detectChanges();
};
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ CodeTabsComponent, HostComponent, TestCodeComponent ],
imports: [ CommonModule ],
schemas: [ NO_ERRORS_SCHEMA ],
});
});
it('should create CodeTabsComponent', () => {
createComponentBasic();
expect(codeTabsComponent).toBeTruthy('CodeTabsComponent');
});
describe('(tab labels)', () => {
let labelElems: HTMLSpanElement[];
const createComponent = (codeTabsContent?: string) => {
createComponentBasic(codeTabsContent);
const labelDes = codeTabsDe.queryAll(By.css('.mat-tab-label'));
labelElems = labelDes.map(de => de.nativeElement.querySelector('span'));
};
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ MdTabsModule, NoopAnimationsModule ]
});
});
it('should create a label for each tab', () => {
createComponent(`
<code-pane>foo</code-pane>
<code-pane>bar</code-pane>
<code-pane>baz</code-pane>
`);
expect(labelElems.length).toBe(3);
});
it('should use the `title` as label', () => {
createComponent(`
<code-pane title="foo-title">foo</code-pane>
<code-pane title="bar-title">bar</code-pane>
`);
const texts = labelElems.map(s => s.textContent);
expect(texts).toEqual(['foo-title', 'bar-title']);
});
it('should add the `class` to the label element', () => {
createComponent(`
<code-pane class="foo-class">foo</code-pane>
<code-pane class="bar-class">bar</code-pane>
`);
const classes = labelElems.map(s => s.className);
expect(classes).toEqual(['foo-class', 'bar-class']);
});
it('should disable ripple effect on tab labels', () => {
createComponent();
const tabsGroupComponent = codeTabsDe.query(By.directive(MdTabGroup)).componentInstance;
expect(tabsGroupComponent.disableRipple).toBe(true);
});
});
describe('(tab content)', () => {
let codeDes: DebugElement[];
let codeComponents: TestCodeComponent[];
const createComponent = (codeTabsContent?: string) => {
createComponentBasic(codeTabsContent);
codeDes = codeTabsDe.queryAll(By.directive(TestCodeComponent));
codeComponents = codeDes.map(de => de.componentInstance);
};
it('should pass `class` to CodeComponent (<aio-code>)', () => {
createComponent(`
<code-pane class="foo-class">foo</code-pane>
<code-pane class="bar-class">bar</code-pane>
`);
const classes = codeDes.map(de => de.nativeElement.className);
expect(classes).toEqual(['foo-class', 'bar-class']);
});
it('should pass content to CodeComponent (<aio-code>)', () => {
createComponent(`
<code-pane>foo</code-pane>
<code-pane>bar</code-pane>
`);
const codes = codeComponents.map(c => c.code);
expect(codes).toEqual(['foo', 'bar']);
});
it('should pass `language` to CodeComponent (<aio-code>)', () => {
createComponent(`
<code-pane language="foo-lang">foo</code-pane>
<code-pane language="bar-lang">bar</code-pane>
`);
const langs = codeComponents.map(c => c.language);
expect(langs).toEqual(['foo-lang', 'bar-lang']);
});
it('should pass `linenums` to CodeComponent (<aio-code>)', () => {
createComponent(`
<code-pane linenums="foo-lnums">foo</code-pane>
<code-pane linenums="bar-lnums">bar</code-pane>
<code-pane linenums="">baz</code-pane>
<code-pane linenums>qux</code-pane>
`);
const lnums = codeComponents.map(c => c.linenums);
expect(lnums).toEqual(['foo-lnums', 'bar-lnums', '', '']);
});
it('should use the default value (if present on <code-tabs>) if `linenums` is not specified', () => {
TestBed.overrideComponent(HostComponent, {
set: { template: '<code-tabs linenums="default-lnums"></code-tabs>' }
});
createComponent(`
<code-pane linenums="foo-lnums">foo</code-pane>
<code-pane linenums>bar</code-pane>
<code-pane>baz</code-pane>
`);
const lnums = codeComponents.map(c => c.linenums);
expect(lnums).toEqual(['foo-lnums', '', 'default-lnums']);
});
it('should pass `path` to CodeComponent (<aio-code>)', () => {
createComponent(`
<code-pane path="foo-path">foo</code-pane>
<code-pane path="bar-path">bar</code-pane>
`);
const paths = codeComponents.map(c => c.path);
expect(paths).toEqual(['foo-path', 'bar-path']);
});
it('should default to an empty string if `path` is not spcified', () => {
createComponent(`
<code-pane>foo</code-pane>
<code-pane>bar</code-pane>
`);
const paths = codeComponents.map(c => c.path);
expect(paths).toEqual(['', '']);
});
it('should pass `region` to CodeComponent (<aio-code>)', () => {
createComponent(`
<code-pane region="foo-region">foo</code-pane>
<code-pane region="bar-region">bar</code-pane>
`);
const regions = codeComponents.map(c => c.region);
expect(regions).toEqual(['foo-region', 'bar-region']);
});
it('should default to an empty string if `region` is not spcified', () => {
createComponent(`
<code-pane>foo</code-pane>
<code-pane>bar</code-pane>
`);
const regions = codeComponents.map(c => c.region);
expect(regions).toEqual(['', '']);
});
it('should pass `title` to CodeComponent (<aio-code>)', () => {
createComponent(`
<code-pane title="foo-title">foo</code-pane>
<code-pane title="bar-title">bar</code-pane>
`);
const titles = codeComponents.map(c => c.title);
expect(titles).toEqual(['foo-title', 'bar-title']);
});
});
});
//// Test helpers ////
@Component({
selector: 'aio-code',
template: `
<div>lang: {{ language }}</div>
<div>linenums: {{ linenums }}</div>
code: <pre>{{ someCode }}</pre>
`
})
class TestCodeComponent {
@Input() code = '';
@Input() hideCopy: boolean;
@Input() language: string;
@Input() linenums: string;
@Input() path: string;
@Input() region: string;
@Input() title: string;
get someCode() {
if (this.code && this.code.length > 30) {
return `${this.code.substring(0, 30)}...`;
}
return this.code;
}
}
@Component({
selector: 'aio-host-comp',
template: `<code-tabs></code-tabs>`
})
class HostComponent {}

View File

@ -21,21 +21,16 @@ export interface TabInfo {
@Component({
selector: 'code-tabs',
template: `
<md-tab-group class="code-tab-group" disableRipple>
<md-tab style="overflow-y: hidden;" *ngFor="let tab of tabs">
<ng-template md-tab-label>
<span class="{{ tab.class }}">{{ tab.title }}</span>
</ng-template>
<aio-code class="{{ tab.class }}"
[code]="tab.code"
[language]="tab.language"
[linenums]="tab.linenums"
[path]="tab.path"
[region]="tab.region"
[title]="tab.title">
</aio-code>
</md-tab>
</md-tab-group>
<md-tab-group class="code-tab-group">
<md-tab style="overflow-y: hidden;" *ngFor="let tab of tabs">
<ng-template md-tab-label>
<span class="{{tab.class}}">{{ tab.title }}</span>
</ng-template>
<aio-code [code]="tab.code" [language]="tab.language" [linenums]="tab.linenums"
[path]="tab.path" [region]="tab.region" [title]="tab.title"
class="{{ tab.class }}"></aio-code>
</md-tab>
</md-tab-group>
`
})
export class CodeTabsComponent implements OnInit {
@ -57,7 +52,7 @@ export class CodeTabsComponent implements OnInit {
processContent(content: string) {
// We add it to an element so that we can easily parse the HTML
const element = document.createElement('div');
// **Security:** `codeTabsContent` is provided by docs authors and as such is considered to
// **Security:** `codeTabsContent` is provided by docs authors and as such its considered to
// be safe for innerHTML purposes.
element.innerHTML = content;
@ -66,8 +61,8 @@ export class CodeTabsComponent implements OnInit {
for (let i = 0; i < codeExamples.length; i++) {
const codeExample = codeExamples.item(i);
const tab = {
class: codeExample.getAttribute('class'),
code: codeExample.innerHTML,
class: codeExample.getAttribute('class'),
language: codeExample.getAttribute('language'),
linenums: this.getLinenums(codeExample),
path: codeExample.getAttribute('path') || '',

View File

@ -1,7 +1,8 @@
import { Component, DebugElement } from '@angular/core';
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MdSnackBarModule, MdSnackBar } from '@angular/material';
import { By } from '@angular/platform-browser';
import { Component, DebugElement } from '@angular/core';
import { MdSnackBarModule, MdSnackBar } from '@angular/material';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { CodeComponent } from './code.component';
@ -267,9 +268,9 @@ describe('CodeComponent', () => {
@Component({
selector: 'aio-host-comp',
template: `
<aio-code md-no-ink [code]="code" [language]="language"
[linenums]="linenums" [path]="path" [region]="region"
[hideCopy]="hideCopy" [title]="title"></aio-code>
<aio-code md-no-ink [code]="code" [language]="language"
[linenums]="linenums" [path]="path" [region]="region"
[hideCopy]="hideCopy" [title]="title"></aio-code>
`
})
class HostComponent {

View File

@ -18,8 +18,7 @@ self.onmessage = handleMessage;
function createIndex(addFn) {
return lunr(/** @this */function() {
this.ref('path');
this.field('titleWords', {boost: 100});
this.field('headingWords', {boost: 50});
this.field('titleWords', {boost: 50});
this.field('members', {boost: 40});
this.field('keywords', {boost: 20});
addFn(this);
@ -81,7 +80,17 @@ function loadIndex(searchInfo) {
// Query the index and return the processed results
function queryIndex(query) {
var results = index.search(query);
// The index requires the query to be lowercase
var terms = query.toLowerCase().split(/\s+/);
var results = index.query(function(qb) {
terms.forEach(function(term) {
// Only include terms that are longer than 2 characters, if there is more than one term
// Add trailing wildcard to each term so that it will match more results
if (terms.length === 1 || term.trim().length > 2) {
qb.term(term, { wildcard: lunr.Query.wildcard.TRAILING });
}
});
});
// Only return the array of paths to pages
return results.map(function(hit) { return pages[hit.ref]; });
}

View File

@ -0,0 +1,13 @@
.api-info-bar {
max-width: 800px;
text-align: left;
span {
margin: 0 16px 0 0;
@media screen and (max-width: 600px) {
display: block;
}
}
}

View File

@ -1,23 +0,0 @@
.api-info-bar {
max-width: 800px;
text-align: left;
span {
margin: 0 16px 0 0;
@media screen and (max-width: 600px) {
display: block;
}
}
}
.api-heading {
margin-top: 24px;
margin-bottom: 18px;
font-size: 16px;
}
.overloads .detail-contents {
padding-top: 0;
}

View File

@ -112,6 +112,11 @@ aio-code pre {
}
// REMOVE RIPPLE EFFECT FROM MATERIAL TABS
code-tabs md-tab-group *.mat-ripple-element, code-tabs md-tab-group *.mat-tab-body-active, code-tabs md-tab-group *.mat-tab-body-content, code-tabs md-tab-group *.mat-tab-body-content {
transform: none !important;
}
[role="tabpanel"] {
transition: none;
}

View File

@ -1,51 +0,0 @@
/*
* General styling to make detail/summary tags look a bit more material
* To get the best out of it you should structure your usage like this:
*
* ```
* <details>
* <summary>Some title</summary>
* <div class="details-content">
* Some content
* </div>
* </details>
*
*/
summary {
cursor: pointer;
font-size: 16px;
position: relative;
padding: 16px 24px;
color: $black;
height: 16px;
display: block; // Remove the built in details marker in FF
&::-webkit-details-marker {
display: none; // Remove the built in details marker in webkit
}
&::after {
content: '\E5CE'; // See https://material.io/icons/#ic_expand_less
font-family: 'Material Icons';
font-size: 24px;
-webkit-font-smoothing: antialiased;
@include rotate(0deg); // We will rotate 180 degrees when details is open
position: absolute;
top: 12px;
right: 22px;
}
}
details {
box-shadow: 0 1px 4px 0 rgba($black, 0.37);
.detail-contents {
padding: 16px 24px;
}
&[open] > summary::after {
@include rotate(180deg); // Rotate the icon
}
}

View File

@ -3,7 +3,7 @@
============================== */
@import 'alert';
@import 'api-pages';
@import 'api-info-bar';
@import 'api-list';
@import 'banner';
@import 'buttons';
@ -12,7 +12,6 @@
@import 'code';
@import 'contribute';
@import 'contributor';
@import 'details';
@import 'edit-page-cta';
@import 'features';
@import 'filetree';

View File

@ -208,7 +208,7 @@ aio-resource-list {
border-color: #2B85E7;
border-radius: 4px;
box-shadow: 0 8px 8px rgba(1, 67, 163, .24), 0 0 8px rgba(1, 67, 163, .12), 0 6px 18px rgba(43, 133, 231, .12);
transform: translateY(-2px);
transform: translate3d(0, -2px, 0);
}
@media(max-width: 900px) {

View File

@ -20,11 +20,10 @@ module.exports = new Package('angular-api', [basePackage, typeScriptPackage])
.processor(require('./processors/mergeDecoratorDocs'))
.processor(require('./processors/extractDecoratedClasses'))
.processor(require('./processors/matchUpDirectiveDecorators'))
.processor(require('./processors/filterContainedDocs'))
.processor(require('./processors/filterMemberDocs'))
.processor(require('./processors/markBarredODocsAsPrivate'))
.processor(require('./processors/filterPrivateDocs'))
.processor(require('./processors/computeSearchTitle'))
.processor(require('./processors/simplifyMemberAnchors'))
// Where do we get the source files?
.config(function(readTypeScriptModules, readFilesProcessor, collectExamples) {
@ -37,8 +36,6 @@ module.exports = new Package('angular-api', [basePackage, typeScriptPackage])
'animations/index.ts',
'animations/browser/index.ts',
'animations/browser/testing/index.ts',
'common/http/index.ts',
'common/http/testing/index.ts',
'common/index.ts',
'common/testing/index.ts',
'core/index.ts',

View File

@ -1,17 +0,0 @@
/**
* Remove docs that are contained in (owned by) another doc
* so that they don't get rendered as files in themselves.
*/
module.exports = function filterContainedDocs() {
return {
docTypes: ['member', 'function-overload'],
$runAfter: ['extra-docs-added'],
$runBefore: ['computing-paths'],
$process: function(docs) {
var docTypes = this.docTypes;
return docs.filter(function(doc) {
return docTypes.indexOf(doc.docType) === -1;
});
}
};
};

View File

@ -0,0 +1,7 @@
module.exports = function filterMemberDocs() {
return {
$runAfter: ['extra-docs-added'], $runBefore: ['computing-paths'], $process: function(docs) {
return docs.filter(function(doc) { return doc.docType !== 'member'; });
}
};
};

View File

@ -2,7 +2,7 @@ const testPackage = require('../../helpers/test-package');
const processorFactory = require('./markBarredODocsAsPrivate');
const Dgeni = require('dgeni');
describe('markBarredODocsAsPrivate processor', () => {
describe('generateApiListDoc processor', () => {
it('should be available on the injector', () => {
const dgeni = new Dgeni([testPackage('angular-api-package')]);

View File

@ -1,85 +1,61 @@
var _ = require('lodash');
/**
* @dgProcessor
* @description
* Directives in Angular are specified by various decorators. In particular the `@Directive()`
* decorator on the class and various other property decorators, such as `@Input`.
*
* This processor will extract this decorator information and attach it as properties to the
* directive document.
*
* Notably, the `input` and `output` binding information can be specified
* either via property decorators (`@Input()`/`@Output()`) or by properties on the metadata
* passed to the `@Directive` decorator. This processor will collect up info from both and
* merge them.
*/
module.exports = function matchUpDirectiveDecorators() {
module.exports = function matchUpDirectiveDecoratorsProcessor() {
return {
$runAfter: ['ids-computed', 'paths-computed'],
$runBefore: ['rendering-docs'],
decoratorMappings: {'Inputs': 'inputs', 'Outputs': 'outputs'},
$process: function(docs) {
docs.forEach(function(doc) {
var decoratorMappings = this.decoratorMappings;
_.forEach(docs, function(doc) {
if (doc.docType === 'directive') {
doc.selector = doc.directiveOptions.selector;
doc.selector = stripQuotes(doc.directiveOptions.selector);
doc.exportAs = stripQuotes(doc.directiveOptions.exportAs);
doc.inputs = getBindingInfo(doc.directiveOptions.inputs, doc.members, 'Input');
doc.outputs = getBindingInfo(doc.directiveOptions.outputs, doc.members, 'Output');
for (let decoratorName in decoratorMappings) {
var propertyName = decoratorMappings[decoratorName];
doc[propertyName] =
getDecoratorValues(doc.directiveOptions[propertyName], decoratorName, doc.members);
}
}
});
}
};
};
function getBindingInfo(directiveBindings, members, bindingType) {
const bindings = {};
function getDecoratorValues(classDecoratorValues, memberDecoratorName, members) {
var decoratorValues = {};
// Parse the bindings from the directive decorator
if (directiveBindings) {
directiveBindings.forEach(function(binding) {
const bindingInfo = parseBinding(binding);
bindings[bindingInfo.propertyName] = bindingInfo;
});
}
// Parse the class decorator
_.forEach(classDecoratorValues, function(option) {
// Options are of the form: "propName : bindingName" (bindingName is optional)
var optionPair = option.split(':');
var propertyName = optionPair.shift().trim();
var bindingName = (optionPair.shift() || '').trim() || propertyName;
if (members) {
members.forEach(function(member) {
if (member.decorators) {
// Search for members with binding decorators
member.decorators.forEach(function(decorator) {
if (decorator.name === bindingType) {
bindings[member.name] = createBindingInfo(member.name, decorator.arguments[0] || member.name);
}
});
}
decoratorValues[propertyName] = {propertyName: propertyName, bindingName: bindingName};
});
// Now ensure that any bindings have the associated member attached
// Note that this binding could have come from the directive decorator
if (bindings[member.name]) {
bindings[member.name].memberDoc = member;
_.forEach(members, function(member) {
_.forEach(member.decorators, function(decorator) {
if (decorator.name === memberDecoratorName) {
decoratorValues[member.name] = {
propertyName: member.name,
bindingName: decorator.arguments[0] || member.name
};
}
});
if (decoratorValues[member.name]) {
decoratorValues[member.name].memberDoc = member;
}
});
if (Object.keys(decoratorValues).length) {
return decoratorValues;
}
// Convert the map back to an array
return Object.keys(bindings).map(function(key) { return bindings[key]; });
}
function stripQuotes(value) {
return (typeof(value) === 'string') ? value.trim().replace(/^(['"])(.*)\1$/, '$2') : value;
}
function parseBinding(option) {
// Directive decorator bindings are of the form: "propName : bindingName" (bindingName is optional)
const optionPair = option.split(':');
const propertyName = optionPair[0].trim();
const bindingName = (optionPair[1] || '').trim() || propertyName;
return createBindingInfo(propertyName, bindingName);
}
function createBindingInfo(propertyName, bindingName) {
return {
propertyName: stripQuotes(propertyName),
bindingName: stripQuotes(bindingName)
};
}

View File

@ -1,141 +0,0 @@
const testPackage = require('../../helpers/test-package');
const processorFactory = require('./matchUpDirectiveDecorators');
const Dgeni = require('dgeni');
describe('matchUpDirectiveDecorators processor', () => {
it('should be available on the injector', () => {
const dgeni = new Dgeni([testPackage('angular-api-package')]);
const injector = dgeni.configureInjector();
const processor = injector.get('matchUpDirectiveDecorators');
expect(processor.$process).toBeDefined();
expect(processor.$runAfter).toContain('ids-computed');
expect(processor.$runAfter).toContain('paths-computed');
expect(processor.$runBefore).toContain('rendering-docs');
});
it('should extract selector and exportAs from the directive decorator on directive docs', () => {
const docs = [{
docType: 'directive',
directiveOptions: { selector: 'a,b,c', exportAs: 'someExport' }
}];
processorFactory().$process(docs);
expect(docs[0].selector).toEqual('a,b,c');
expect(docs[0].exportAs).toEqual('someExport');
});
it('should ignore properties from the directive decorator on non-directive docs', () => {
const docs = [{
docType: 'class',
directiveOptions: { selector: 'a,b,c', exportAs: 'someExport' }
}];
processorFactory().$process(docs);
expect(docs[0].selector).toBeUndefined();
expect(docs[0].exportAs).toBeUndefined();
});
it('should strip whitespace and quotes off directive properties', () => {
const docs = [
{
docType: 'directive',
directiveOptions: { selector: '"a,b,c"', exportAs: '\'someExport\'' }
},
{
docType: 'directive',
directiveOptions: { selector: ' a,b,c ', exportAs: ' someExport ' }
},
{
docType: 'directive',
directiveOptions: { selector: ' "a,b,c" ', exportAs: ' \'someExport\' ' }
}
];
processorFactory().$process(docs);
expect(docs[0].selector).toEqual('a,b,c');
expect(docs[0].exportAs).toEqual('someExport');
expect(docs[1].selector).toEqual('a,b,c');
expect(docs[1].exportAs).toEqual('someExport');
expect(docs[2].selector).toEqual('a,b,c');
expect(docs[2].exportAs).toEqual('someExport');
});
it('should extract inputs and outputs from the directive decorator', () => {
const docs = [{
docType: 'directive',
directiveOptions: {
inputs: ['in1:in2', 'in3', ' in4:in5 ', ' in6 '],
outputs: ['out1:out1', ' out2:out3 ', ' out4 ']
},
members: [
{ name: 'in1' },
{ name: 'in3' },
{ name: 'in4' },
{ name: 'in6' },
{ name: 'out1' },
{ name: 'out2' },
{ name: 'out4' }
]
}];
processorFactory().$process(docs);
expect(docs[0].inputs).toEqual([
{ propertyName: 'in1', bindingName: 'in2', memberDoc: docs[0].members[0] },
{ propertyName: 'in3', bindingName: 'in3', memberDoc: docs[0].members[1] },
{ propertyName: 'in4', bindingName: 'in5', memberDoc: docs[0].members[2] },
{ propertyName: 'in6', bindingName: 'in6', memberDoc: docs[0].members[3] }
]);
expect(docs[0].outputs).toEqual([
{ propertyName: 'out1', bindingName: 'out1', memberDoc: docs[0].members[4] },
{ propertyName: 'out2', bindingName: 'out3', memberDoc: docs[0].members[5] },
{ propertyName: 'out4', bindingName: 'out4', memberDoc: docs[0].members[6] }
]);
});
it('should extract inputs and outputs from decorated properties', () => {
const docs = [{
docType: 'directive',
directiveOptions: {},
members: [
{ name: 'a1', decorators: [{ name: 'Input', arguments: ['a2'] }] },
{ name: 'b1', decorators: [{ name: 'Output', arguments: ['b2'] }] },
{ name: 'c1', decorators: [{ name: 'Input', arguments: [] }] },
{ name: 'd1', decorators: [{ name: 'Output', arguments: [] }] },
]
}];
processorFactory().$process(docs);
expect(docs[0].inputs).toEqual([
{ propertyName: 'a1', bindingName: 'a2', memberDoc: docs[0].members[0] },
{ propertyName: 'c1', bindingName: 'c1', memberDoc: docs[0].members[2] }
]);
expect(docs[0].outputs).toEqual([
{ propertyName: 'b1', bindingName: 'b2', memberDoc: docs[0].members[1] },
{ propertyName: 'd1', bindingName: 'd1', memberDoc: docs[0].members[3] }
]);
});
it('should merge directive inputs/outputs with decorator property inputs/outputs', () => {
const docs = [{
docType: 'directive',
directiveOptions: {
inputs: ['a1:a2'],
outputs: ['b1:b2']
},
members: [
{ name: 'a1' },
{ name: 'a3', decorators: [{ name: 'Input', arguments: ['a4'] }] },
{ name: 'b1' },
{ name: 'b3', decorators: [{ name: 'Output', arguments: ['b4'] }] },
]
}];
processorFactory().$process(docs);
expect(docs[0].inputs).toEqual([
{ propertyName: 'a1', bindingName: 'a2', memberDoc: docs[0].members[0] },
{ propertyName: 'a3', bindingName: 'a4', memberDoc: docs[0].members[1] }
]);
expect(docs[0].outputs).toEqual([
{ propertyName: 'b1', bindingName: 'b2', memberDoc: docs[0].members[2] },
{ propertyName: 'b3', bindingName: 'b4', memberDoc: docs[0].members[3] }
]);
});
});

View File

@ -83,14 +83,13 @@ module.exports = function mergeDecoratorDocs(log) {
if (docsToMerge[doc.name]) {
// We have found an `XxxDecorator` document that will hold the call signature of the decorator
var decoratorDoc = docsToMerge[doc.name];
var callMember = doc.members.filter(function(member) { return member.isCallMember; })[0];
log.debug(
'mergeDecoratorDocs: merging', doc.name, 'into', decoratorDoc.name,
callMember.description.substring(0, 50));
doc.callMember.description.substring(0, 50));
// Merge the documentation found in this call signature into the original decorator
decoratorDoc.description = callMember.description;
decoratorDoc.howToUse = callMember.howToUse;
decoratorDoc.whatItDoes = callMember.whatItDoes;
decoratorDoc.description = doc.callMember.description;
decoratorDoc.howToUse = doc.callMember.howToUse;
decoratorDoc.whatItDoes = doc.callMember.whatItDoes;
// remove doc from its module doc's exports
doc.moduleDoc.exports =
@ -109,8 +108,8 @@ module.exports = function mergeDecoratorDocs(log) {
function getMakeDecoratorCall(doc, type) {
var makeDecoratorFnName = 'make' + (type || '') + 'Decorator';
var initializer = doc.declaration &&
doc.declaration.initializer;
var initializer = doc.exportSymbol && doc.exportSymbol.valueDeclaration &&
doc.exportSymbol.valueDeclaration.initializer;
if (initializer) {
// There appear to be two forms of initializer:

View File

@ -15,7 +15,10 @@ describe('mergeDecoratorDocs processor', () => {
name: 'Component',
docType: 'const',
description: 'A description of the metadata for the Component decorator',
declaration: {initializer: {expression: {text: 'makeDecorator'}, arguments: [{text: 'X'}]}},
exportSymbol: {
valueDeclaration:
{initializer: {expression: {text: 'makeDecorator'}, arguments: [{text: 'X'}]}}
},
members: [
{ name: 'templateUrl', description: 'A description of the templateUrl property' }
],
@ -26,30 +29,34 @@ describe('mergeDecoratorDocs processor', () => {
name: 'ComponentDecorator',
docType: 'interface',
description: 'A description of the interface for the call signature for the Component decorator',
members: [
{
isCallMember: true,
description: 'The actual description of the call signature',
whatItDoes: 'Does something cool...',
howToUse: 'Use it like this...'
},
{
description: 'Some other member'
}
],
callMember: {
description: 'The actual description of the call signature',
whatItDoes: 'Does something cool...',
howToUse: 'Use it like this...'
},
moduleDoc
};
decoratorDocWithTypeAssertion = {
name: 'Y',
docType: 'const',
declaration: { initializer: { expression: {type: {}, expression: {text: 'makeDecorator'}, arguments: [{text: 'Y'}]} } },
docType: 'var',
exportSymbol: {
valueDeclaration: {
initializer: {
expression:
{type: {}, expression: {text: 'makeDecorator'}, arguments: [{text: 'Y'}]}
}
}
},
moduleDoc
};
otherDoc = {
name: 'Y',
docType: 'const',
declaration: {initializer: {expression: {text: 'otherCall'}, arguments: [{text: 'param1'}]}},
docType: 'var',
exportSymbol: {
valueDeclaration:
{initializer: {expression: {text: 'otherCall'}, arguments: [{text: 'param1'}]}}
},
moduleDoc
};
@ -61,7 +68,7 @@ describe('mergeDecoratorDocs processor', () => {
processor.$process([decoratorDoc, metadataDoc, decoratorDocWithTypeAssertion, otherDoc]);
expect(decoratorDoc.docType).toEqual('decorator');
expect(decoratorDocWithTypeAssertion.docType).toEqual('decorator');
expect(otherDoc.docType).toEqual('const');
expect(otherDoc.docType).toEqual('var');
});
it('should extract the "type" of the decorator meta data', () => {

View File

@ -1,26 +0,0 @@
/**
* Members that have overloads get long unwieldy anchors because they must be distinguished
* by their parameter lists.
* But the primary overload doesn't not need this distinction, so can just be the name of the member.
*/
module.exports = function simplifyMemberAnchors() {
return {
$runAfter: ['extra-docs-added'],
$runBefore: ['computing-paths'],
$process: function(docs) {
return docs.forEach(doc => {
if (doc.members) {
doc.members.forEach(member => member.anchor = computeAnchor(member));
}
if (doc.statics) {
doc.statics.forEach(member => member.anchor = computeAnchor(member));
}
});
}
};
};
function computeAnchor(member) {
// if the member is a "call" type then it has no name
return encodeURI(member.name.trim() || 'call');
}

View File

@ -1,28 +1,22 @@
const visit = require('unist-util-visit');
const is = require('hast-util-is-element');
const source = require('unist-util-source');
const toString = require('hast-util-to-string');
const filter = require('unist-util-filter');
module.exports = function h1CheckerPostProcessor() {
return (ast, file) => {
file.headings = {
h1: [],
h2: [],
h3: [],
h4: [],
h5: [],
h6: [],
hgroup: []
};
let h1s = [];
visit(ast, node => {
if (is(node, ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hgroup'])) {
file.headings[node.tagName].push(getText(node));
if (is(node, 'h1')) {
h1s.push(node);
file.title = getText(node);
}
});
file.title = file.headings.h1[0];
if (file.headings.h1.length > 1) {
file.fail(`More than one h1 found in ${file}`);
if (h1s.length > 1) {
const h1Src = h1s.map(node => source(node, file)).join(', ');
file.fail(`More than one h1 found [${h1Src}]`);
}
};
};

View File

@ -23,7 +23,7 @@ describe('h1Checker postprocessor', () => {
<h1>Heading 1a</h1>
`
};
expect(() => processor.$process([doc])).toThrowError(createDocMessage('More than one h1 found in ' + doc.renderedContent, doc));
expect(() => processor.$process([doc])).toThrowError(createDocMessage('More than one h1 found [<h1>Heading 1, <h1>Heading 1a</h1>]', doc));
});
it('should not complain if there is exactly one h1 in a document', () => {

View File

@ -50,14 +50,19 @@ module.exports = function generateKeywordsProcessor(log, readFilesProcessor) {
var ignoreWordsMap = convertToMap(wordsToIgnore);
// If the heading contains a name starting with ng, e.g. "ngController", then add the
// name without the ng to the text, e.g. "controller".
function preprocessText(text) {
return text.replace(/(^|\s)([nN]g([A-Z]\w*))/g, '$1$2 $3');
// If the title contains a name starting with ng, e.g. "ngController", then add the module
// name
// without the ng to the title text, e.g. "controller".
function extractTitleWords(title) {
var match = /ng([A-Z]\w*)/.exec(title);
if (match) {
title = title + ' ' + match[1].toLowerCase();
}
return title;
}
function extractWords(text, words, keywordMap) {
var tokens = preprocessText(text).toLowerCase().split(/[.\s,`'"#]+/mg);
var tokens = text.toLowerCase().split(/[.\s,`'"#]+/mg);
tokens.forEach(function(token) {
var match = token.match(KEYWORD_REGEX);
if (match) {
@ -77,15 +82,13 @@ module.exports = function generateKeywordsProcessor(log, readFilesProcessor) {
// Ignore internals and private exports (indicated by the ɵ prefix)
.filter(function(doc) { return !doc.internal && !doc.privateExport; });
filteredDocs.forEach(function(doc) {
var words = [];
var keywordMap = Object.assign({}, ignoreWordsMap);
var members = [];
var membersMap = Object.assign({}, ignoreWordsMap);
const headingWords = [];
const headingWordMap = Object.assign({}, ignoreWordsMap);
var membersMap = {};
// Search each top level property of the document for search terms
Object.keys(doc).forEach(function(key) {
@ -95,44 +98,26 @@ module.exports = function generateKeywordsProcessor(log, readFilesProcessor) {
extractWords(value, words, keywordMap);
}
// Special case properties that contain content relating to "members"
// of a doc that represents, say, a class or interface
if (key === 'methods' || key === 'properties' || key === 'events') {
value.forEach(function(member) { extractWords(member.name, members, membersMap); });
}
});
// Extract all the keywords from the headings
if (doc.vFile && doc.vFile.headings) {
Object.keys(doc.vFile.headings).forEach(function(headingTag) {
doc.vFile.headings[headingTag].forEach(function(headingText) {
extractWords(headingText, headingWords, headingWordMap);
});
});
}
doc.searchTitle = doc.searchTitle || doc.title || doc.vFile && doc.vFile.title || doc.name;
// Extract the title to use in searches
doc.searchTitle = doc.searchTitle || doc.title || doc.vFile && doc.vFile.title || doc.name || '';
// Attach all this search data to the document
doc.searchTerms = {
titleWords: preprocessText(doc.searchTitle),
headingWords: headingWords.sort().join(' '),
titleWords: extractTitleWords(doc.searchTitle),
keywords: words.sort().join(' '),
members: members.sort().join(' ')
};
});
// Now process all the search data and collect it up to be used in creating a new document
var searchData = filteredDocs.map(function(page) {
// Copy the properties from the searchTerms object onto the search data object
return Object.assign({
path: page.path,
title: page.searchTitle,
type: page.docType
}, page.searchTerms);
});
var searchData =
filteredDocs.filter(function(page) { return page.searchTerms; }).map(function(page) {
return Object.assign(
{path: page.path, title: page.searchTitle, type: page.docType}, page.searchTerms);
});
docs.push({
docType: 'json-doc',

View File

@ -42,9 +42,9 @@ describe('generateKeywords processor', () => {
it('should compute `doc.searchTitle` from the doc properties if not already provided', () => {
const processor = processorFactory(mockLogger, mockReadFilesProcessor);
const docs = [
{ docType: 'class', name: 'A', searchTitle: 'searchTitle A', title: 'title A', vFile: { headings: { h1: ['vFile A'] } } },
{ docType: 'class', name: 'B', title: 'title B', vFile: { headings: { h1: ['vFile B'] } } },
{ docType: 'class', name: 'C', vFile: { title: 'vFile C', headings: { h1: ['vFile C'] } } },
{ docType: 'class', name: 'A', searchTitle: 'searchTitle A', title: 'title A', vFile: { title: 'vFile A'} },
{ docType: 'class', name: 'B', title: 'title B', vFile: { title: 'vFile B'} },
{ docType: 'class', name: 'C', vFile: { title: 'vFile C'} },
{ docType: 'class', name: 'D' },
];
processor.$process(docs);
@ -62,82 +62,19 @@ describe('generateKeywords processor', () => {
{ docType: 'class', name: 'PublicExport', searchTitle: 'class PublicExport' },
];
processor.$process(docs);
const keywordsDoc = docs[docs.length - 1];
expect(keywordsDoc.data).toEqual([
expect(docs[docs.length - 1].data).toEqual([
jasmine.objectContaining({ title: 'class PublicExport', type: 'class'})
]);
});
it('should add title words to the search terms', () => {
const processor = processorFactory(mockLogger, mockReadFilesProcessor);
const docs = [
{
docType: 'class',
name: 'PublicExport',
searchTitle: 'class PublicExport',
vFile: { headings: { h2: ['heading A', 'heading B'] } }
},
];
processor.$process(docs);
const keywordsDoc = docs[docs.length - 1];
expect(keywordsDoc.data[0].titleWords).toEqual('class PublicExport');
});
it('should add heading words to the search terms', () => {
const processor = processorFactory(mockLogger, mockReadFilesProcessor);
const docs = [
{
docType: 'class',
name: 'PublicExport',
searchTitle: 'class PublicExport',
vFile: { headings: { h2: ['Important heading', 'Secondary heading'] } }
},
];
processor.$process(docs);
const keywordsDoc = docs[docs.length - 1];
expect(keywordsDoc.data[0].headingWords).toEqual('heading important secondary');
});
it('should process terms prefixed with "ng" to include the term stripped of "ng"', () => {
const processor = processorFactory(mockLogger, mockReadFilesProcessor);
const docs = [
{
docType: 'class',
name: 'PublicExport',
searchTitle: 'ngController',
vFile: { headings: { h2: ['ngModel'] } },
content: 'Some content with ngClass in it.'
},
];
processor.$process(docs);
const keywordsDoc = docs[docs.length - 1];
expect(keywordsDoc.data[0].titleWords).toEqual('ngController Controller');
expect(keywordsDoc.data[0].headingWords).toEqual('model ngmodel');
expect(keywordsDoc.data[0].keywords).toContain('class');
expect(keywordsDoc.data[0].keywords).toContain('ngclass');
});
it('should generate renderedContent property', () => {
const processor = processorFactory(mockLogger, mockReadFilesProcessor);
const docs = [
{
docType: 'class',
name: 'SomeClass',
description: 'The is the documentation for the SomeClass API.',
vFile: { headings: { h1: ['SomeClass'], h2: ['Some heading'] } }
},
{ docType: 'class', name: 'SomeClass', description: 'The is the documentation for the SomeClass API.' },
];
processor.$process(docs);
const keywordsDoc = docs[docs.length - 1];
expect(JSON.parse(keywordsDoc.renderedContent)).toEqual(
[{
'title':'SomeClass',
'type':'class',
'titleWords':'SomeClass',
'headingWords':'heading some someclass',
'keywords':'api class documentation for is someclass the',
'members':''
}]
expect(docs[docs.length - 1].renderedContent).toEqual(
'[{"title":"SomeClass","type":"class","titleWords":"SomeClass","keywords":"api class documentation for is someclass the","members":""}]'
);
});
});

View File

@ -1,49 +0,0 @@
module.exports = function() {
return {
name: 'truncateCode',
process: function(str, lines) {
if (lines === undefined) return str;
const parts = str && str.split && str.split(/\r?\n/);
if (parts && parts.length > lines) {
return balance(parts[0] + '...', ['{', '(', '['], ['}', ')', ']']);
} else {
return str;
}
}
};
};
/**
* Try to balance the brackets by adding closers on to the end of a string
* for every bracket that is left open.
* The chars at each index in the openers and closers should match (i.e openers = ['{', '('], closers = ['}', ')'])
*
* @param {string} str The string to balance
* @param {string[]} openers an array of chars that open a bracket
* @param {string[]} closers an array of chars that close a brack
* @returns the balanced string
*/
function balance(str, openers, closers) {
const stack = [];
// Add each open bracket to the stack, removing them when there is a matching closer
str.split('').forEach(function(char) {
const closerIndex = closers.indexOf(char);
if (closerIndex !== -1 && stack[stack.length-1] === closerIndex) {
stack.pop();
} else {
const openerIndex = openers.indexOf(char);
if (openerIndex !== -1) {
stack.push(openerIndex);
}
}
});
// Now the stack should contain all the unclosed brackets
while(stack.length) {
str += closers[stack.pop()];
}
return str;
}

View File

@ -1,35 +0,0 @@
var factory = require('./truncateCode');
describe('truncateCode filter', function() {
var filter;
beforeEach(function() { filter = factory(); });
it('should be called "truncateCode"',
function() { expect(filter.name).toEqual('truncateCode'); });
it('should return the whole string given lines is undefined', function() {
expect(filter.process('some text\n \nmore text\n \n'))
.toEqual('some text\n \nmore text\n \n');
});
it('should return the whole string if less than the given number of lines', function() {
expect(filter.process('this is a pretty long string that only exists on one line', 1))
.toEqual('this is a pretty long string that only exists on one line');
expect(filter.process('this is a pretty long string\nthat exists on two lines', 2))
.toEqual('this is a pretty long string\nthat exists on two lines');
});
it('should return the specified number of lines and an ellipsis if there are more lines', function() {
expect(filter.process('some text\n \nmore text\n \n', 1)).toEqual('some text...');
});
it('should add closing brackets for all the unclosed opening brackets after truncating', function() {
expect(filter.process('()[]{}\nsecond line', 1)).toEqual('()[]{}...');
expect(filter.process('([]{}\nsecond line', 1)).toEqual('([]{}...)');
expect(filter.process('()[{}\nsecond line', 1)).toEqual('()[{}...]');
expect(filter.process('()[]{\nsecond line', 1)).toEqual('()[]{...}');
expect(filter.process('([{\nsecond line', 1)).toEqual('([{...}])');
});
});

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
const Package = require('dgeni').Package;
const glob = require('glob');
const globby = require('globby');
const ignore = require('ignore');
const fs = require('fs');
const path = require('canonical-path');
@ -23,7 +23,7 @@ module.exports = new Package('angular-content', [basePackage, contentPackage])
const gitignoreFile = fs.readFileSync(path.resolve(GUIDE_EXAMPLES_PATH, '.gitignore'), 'utf8');
const gitignore = ignore().add(gitignoreFile);
const examplePaths = glob.sync('**/*', { cwd: GUIDE_EXAMPLES_PATH, dot: true, ignore: '**/node_modules/**', mark: true })
const examplePaths = globby.sync('**/*', { cwd: GUIDE_EXAMPLES_PATH, mark: true, dot: true })
.filter(filePath => filePath !== '.gitignore') // we are not interested in the .gitignore file itself
.filter(filePath => !/\/$/.test(filePath)); // this filter removes the folders, leaving only files
const filteredExamplePaths = gitignore.filter(examplePaths) // filter out files that match the .gitignore rules

View File

@ -1,14 +1,14 @@
{% import "lib/memberHelpers.html" as memberHelpers -%}
{% import "lib/paramList.html" as params -%}
{% extends 'export-base.template.html' -%}
{% block overview %}{% include "includes/class-overview.html" %}{% endblock %}
{% block details %}
{% include "includes/class-overview.html" %}
{% block additional %}{% endblock %}
{% include "includes/description.html" %}
{$ memberHelpers.renderMemberDetails(doc.statics, 'static-members', 'static-member', 'Static Members') $}
{% include "includes/constructor.html" %}
{$ memberHelpers.renderMemberDetails(doc.members, 'instance-members', 'instance-member', 'Members') $}
{% include "includes/annotations.html" %}
{% include "includes/statics.html" %}
{% include "includes/constructor.html" %}
{% include "includes/members.html" %}
{% endblock %}

View File

@ -1,9 +1,7 @@
{% import "lib/memberHelpers.html" as memberHelper -%}
{% import "lib/paramList.html" as params -%}
{% extends 'export-base.template.html' %}
{% block overview %}{% include "includes/decorator-overview.html" %}{% endblock %}
{% block details %}
{% include "includes/description.html" %}
{$ memberHelper.renderMemberDetails(doc.members, 'metadata-members', 'metadata-member', 'Metadata Properties') $}
{% include "includes/metadata.html" %}
{% endblock %}

View File

@ -1,11 +1,9 @@
{% import "lib/directiveHelpers.html" as directiveHelper -%}
{% import "lib/paramList.html" as params -%}
{% extends 'class.template.html' -%}
{% block overview %}{% include "includes/directive-overview.html" %}{% endblock %}
{% block additional -%}
{% include "includes/selectors.html" %}
{$ directiveHelper.renderBindings(doc.inputs, 'inputs', 'input', 'Inputs') $}
{$ directiveHelper.renderBindings(doc.outputs, 'outputs', 'output', 'Outputs') $}
{% include "includes/outputs.html" %}
{% include "includes/inputs.html" %}
{% include "includes/export-as.html" %}
{% endblock %}

View File

@ -5,7 +5,6 @@
{% include "includes/what-it-does.html" %}
{% include "includes/security-notes.html" %}
{% include "includes/deprecation.html" %}
{% block overview %}{% endblock %}
{% include "includes/how-to-use.html" %}
{% block details %}{% endblock %}
{% endblock %}

View File

@ -1,24 +1,9 @@
{% import "lib/paramList.html" as params -%}
{% extends 'export-base.template.html' -%}
{% block overview %}
<code-example language="ts" hideCopy="true" class="no-box api-heading">
function {$ doc.name $}{$ params.paramList(doc.parameters) $}
{%- if doc.type %}: {$ doc.type | escape $}{% endif %};
</code-example>
{% endblock %}
{% block details %}
{% include "includes/description.html" %}
{% if doc.overloads.length %}
<h2>Overloads</h2>{% for overload in doc.overloads %}
<code-example language="ts" hideCopy="true" class="no-box api-heading">
function {$ overload.name $}{$ params.paramList(overload.parameters) $}
{%- if overload.type %}: {$ overload.type | escape $}{% endif %};
<code-example language="ts" hideCopy="true">
function {$ doc.name $}{$ params.paramList(doc.parameters) $}: {$ doc.returnType or 'any' $};
</code-example>
<section class="description">
{$ overload.description | trimBlankLines | marked $}
</section>
{% endfor %}
{% endif %}
{% include "includes/description.html" %}
{% endblock %}

View File

@ -2,7 +2,7 @@
<section class="annotations">
<h2>Annotations</h2>
{%- for decorator in doc.decorators %}
<code-example hideCopy="true" class="no-box api-heading">@{$ decorator.name $}{$ params.paramList(decorator.arguments) $}</code-example>
<code-example hideCopy="true">@{$ decorator.name $}{$ params.paramList(decorator.arguments) $}</code-example>
{% if not decorator.notYetDocumented %}{$ decorator.description | marked $}{% endif %}
{% endfor %}
</section>

View File

@ -1,13 +1,17 @@
{% import "lib/memberHelpers.html" as memberHelper -%}
{% macro renderMember(member) %}{% if not member.internal -%}
<a class="code-anchor" href="#{$ member.name $}">{$ member.name $}</a>{$ params.paramList(member.parameters) | indent(4, false) | trim() $}{$ params.returnType(member.returnType) $}
{%- endif %}{% endmacro -%}
<section class="{$ doc.docType $}-overview">
<h2>Overview</h2>
<code-example language="ts" hideCopy="true">
{$ doc.docType $} {$ doc.name $}{$ doc.typeParams | escape $}{$ memberHelper.renderHeritage(doc) $} {
{%- if doc.statics.length %}{% for member in doc.statics %}{% if not member.internal %}
<a class="code-anchor" href="#{$ member.anchor $}">{$ memberHelper.renderMember(member, 1) $}</a>{% endif %}{% endfor %}{% endif %}
{%- if doc.members.length %}{% for member in doc.members %}{% if not member.internal %}
<a class="code-anchor" href="#{$ member.anchor $}">{$ memberHelper.renderMember(member, 1) $}</a>{% endif %}{% endfor %}{% endif %}
}
</code-example>
</section>
<section class="class-overview">
<h2>Overview</h2>
<code-example language="ts" hideCopy="true">
{$ doc.docType $} {$ doc.name $}{$ doc.heritage $} {
{%- if doc.statics.length %}{% for member in doc.statics %}
static {$ renderMember(member) $}{% endfor %}{% endif %}
{%- if doc.constructorDoc %}
{$ renderMember(doc.constructorDoc) $}{% endif %}
{%- if doc.members.length %}{% for member in doc.members %}
{$ renderMember(member) $}{% endfor %}{% endif %}
}
</code-example>
</section>

View File

@ -2,7 +2,7 @@
<section class="constructor">
<a id="{$ doc.constructorDoc.name $}"></a>
<h2>Constructor</h2>
<code-example hideCopy="true" class="no-box api-heading">{$ doc.constructorDoc.name $}{$ params.paramList(doc.constructorDoc.parameters) $}</code-example>
<code-example hideCopy="true">{$ doc.constructorDoc.name $}{$ params.paramList(doc.constructorDoc.parameters) $}</code-example>
{% if not doc.constructorDoc.notYetDocumented %}{$ doc.constructorDoc.description | marked $}{% endif %}
</section>
{% endif %}

View File

@ -1,10 +0,0 @@
{% import "lib/memberHelpers.html" as memberHelper -%}
<section class="decorator-overview">
<h2>Metadata Overview</h2>
<code-example language="ts" hideCopy="true">
@{$ doc.name $}{$ doc.typeParams | escape $}({ {% if doc.members.length %}{% for member in doc.members %}{% if not member.internal %}
<a class="code-anchor" href="#{$ member.anchor $}">{$ memberHelper.renderMember(member, 1) $}</a>{% endif %}{% endfor %}{% endif %}
})
</code-example>
</section>

View File

@ -1,14 +0,0 @@
{% import "lib/memberHelpers.html" as memberHelper -%}
<section class="{$ doc.docType $}-overview">
<h2>Overview</h2>
<code-example language="ts" hideCopy="true">{% for decorator in doc.decorators %}
<a href="#annotations">@{$ decorator.name $}{$ params.paramList(decorator.arguments) $}</a>{% endfor %}
class {$ doc.name $}{$ doc.typeParams | escape $}{$ memberHelper.renderHeritage(doc) $} {
{%- if doc.statics.length %}{% for member in doc.statics %}{% if not member.internal %}
<a class="code-anchor" href="#{$ member.anchor $}">{$ memberHelper.renderMember(member, 1) $}</a>{% endif %}{% endfor %}{% endif %}
{%- if doc.members.length %}{% for member in doc.members %}{% if not member.internal %}
<a class="code-anchor" href="#{$ member.anchor $}">{$ memberHelper.renderMember(member, 1) $}</a>{% endif %}{% endfor %}{% endif %}
}
</code-example>
</section>

View File

@ -1,8 +1,8 @@
{%- if doc.exportAs %}
{%- if doc.directiveOptions.exportAs %}
<section class="export-as">
<h2>Exported as</h2>
<div>
<code>{$ doc.exportAs $}</code>
<code>{$ doc.directiveOptions.exportAs $}</code>
</div>
</section>
{% endif %}

View File

@ -1,27 +1,14 @@
{% import "lib/githubLinks.html" as github -%}
<!-- INFO BAR -->
<section class="info-bar">
<div class="info-banner api-info-bar">
<span class="info-bar-item">
npm package: <code>@angular/{$ doc.moduleDoc.id $}</code>
</span>
<table class="is-full-width">
<tbody>
<tr>
<th>npm Package</th>
<td><a href="https://www.npmjs.com/package/@angular/{$ doc.moduleDoc.id.split('/')[0] $}">@angular/{$ doc.moduleDoc.id.split('/')[0] $}</a></td>
</tr>
<tr>
<th>Module</th>
<td><code>import { {$ doc.name $} } from <a href="{$ doc.moduleDoc.path $}">@angular/{$ doc.moduleDoc.id $}</a>;</code></td>
</tr>
<tr>
<th>Source</th>
<td>{$ github.githubViewLink(doc, versionInfo) $}</td>
</tr>
{% if doc.ngModule %}
<tr>
<th>NgModule</th>
<td>{@link {$ doc.ngModule $}}</td>
</tr>
{% endif %}
</tbody>
</table>
{% if doc.ngModule %}
<span class="info-bar-item">
NgModule: {@link {$ doc.ngModule $}}
</span>
{% endif %}
</div>

View File

@ -0,0 +1,11 @@
{% if doc.inputs %}
<section class="inputs">
<h2>Inputs</h2>
{% for binding, property in doc.inputs %}
<div class="input">
<code>{$ property.bindingName $}</code>&nbsp;bound to&nbsp;<code>{$ property.memberDoc.classDoc.name $}.{$ property.propertyName $}</code>
{$ property.memberDoc.description | trimBlankLines | marked $}
</div>
{% endfor %}
</section>
{% endif %}

View File

@ -1,10 +1,10 @@
{% import "lib/memberHelpers.html" as memberHelper -%}
<section class="interface-overview">
<h2>Interface Overview</h2>
<code-example language="ts" hideCopy="true">
interface {$ doc.name $}{$ doc.typeParams | escape $}{$ memberHelper.renderHeritage(doc) $} { {% if doc.members.length %}{% for member in doc.members %}{% if not member.internal %}
<a class="code-anchor" href="#{$ member.anchor $}">{$ memberHelper.renderMember(member, 1) $}</a>{% endif %}{% endfor %}{% endif %}
}
</code-example>
<h2>Interface Overview</h2>
<code-example language="ts" hideCopy="true">
interface {$ doc.name $}{$ doc.heritage $} { {% if doc.newMember %}
<a class="code-anchor" href="#{$ doc.newMember.name $}">{$ doc.newMember.name | indent(6, false) | trim $}</a>{$ params.paramList(doc.newMember.parameters) | indent(8, false) | trim $}{$ params.returnType(doc.newMember.returnType) $}{% endif %}{% if doc.callMember %}
<a class="code-anchor" href="#{$ doc.callMember.name $}">{$ doc.callMember.name | indent(6, false) | trim $}</a>{$ params.paramList(doc.callMember.parameters) | indent(8, false) | trim $}{$ params.returnType(doc.callMember.returnType) $}{% endif %}{% if doc.members.length %}{% for member in doc.members %}{% if not member.internal %}
<a class="code-anchor" href="#{$ member.name $}">{$ member.name | indent(6, false) | trim $}</a>{$ params.paramList(member.parameters) | indent(8, false) | trim $}{$ params.returnType(member.returnType) $}{% endif %}{% endfor %}{% endif %}
}
</code-example>
</section>

View File

@ -0,0 +1,29 @@
{% if doc.members.length or doc.newMember or doc.callMember %}
<section class="member-members">
<h2>Members</h2>
{% if doc.newMember %}
<div class="new-member">
<a id="{$ doc.newMember.name $}"></a>
<code-example hideCopy="true">{$ doc.newMember.name $}{$ params.paramList(doc.newMember.parameters) | trim $}{$ params.returnType(doc.newMember.returnType) $}</code-example>
{% if not doc.newMember.notYetDocumented %}{$ doc.newMember.description | marked $}{% endif %}
</div>
{% if doc.members.length or doc.callMember %}<hr>{% endif %}
{% endif %}
{% if doc.callMember %}
<div class="call-member">
<a id="{$ doc.callMember.name $}"></a>
<code-example hideCopy="true">{$ doc.callMember.name $}{$ params.paramList(doc.callMember.parameters) | trim $}{$ params.returnType(doc.callMember.returnType) $}</code-example>
{% if not doc.callMember.notYetDocumented %}{$ doc.callMember.description | marked $}{% endif %}
</div>
{% if doc.members.length %}<hr>{% endif %}
{% endif %}
{% for member in doc.members %}{% if not member.internal %}
<div class="instance-member">
<a id="{$ member.name $}"></a>
<code-example hideCopy="true">{$ member.name $}{$ params.paramList(member.parameters) | trim $}{$ params.returnType(member.returnType) $}</code-example>
{% if not member.notYetDocumented %}{$ member.description | marked $}{% endif %}
</div>
{% if not loop.last %}<hr>{% endif %}
{% endif %}{% endfor %}
</section>
{% endif %}

View File

@ -4,7 +4,7 @@
{% for metadata in doc.members %}{% if not metadata.internal %}
<div class="metadata-member">
<a name="{$ metadata.name $}" class="anchor-offset"></a>
<code-example hideCopy="true">{$ metadata.name $}{$ params.paramList(metadata.parameters) | trim $}{$ params.returnType(metadata.type) $}</code-example>
<code-example hideCopy="true">{$ metadata.name $}{$ params.paramList(metadata.parameters) | trim $}{$ params.returnType(metadata.returnType) $}</code-example>
{%- if not metadata.notYetDocumented %}{$ metadata.description | marked $}{% endif -%}
</div>
{% if not loop.last %}<hr class="hr-margin">{% endif %}

View File

@ -0,0 +1,11 @@
{% if doc.outputs %}
<section class="outputs">
<h2>Outputs</h2>
{% for binding, property in doc.outputs %}
<div class="output">
<code>{$ property.bindingName $}</code>&nbsp;bound to&nbsp;<code>{$ property.memberDoc.classDoc.name $}.{$ property.propertyName $}</code>
{$ property.memberDoc.description | trimBlankLines | marked $}
</div>
{% endfor %}
</section>
{% endif %}

Some files were not shown because too many files have changed in this diff Show More