feat(ivy): implement TestBed (#25369)

PR Close #25369
This commit is contained in:
Victor Berchet
2018-08-06 14:09:38 -07:00
committed by Ben Lesh
parent 85106375ac
commit 14ac7ad6b4
37 changed files with 1543 additions and 363 deletions

View File

@ -8,8 +8,8 @@ source ${currentDir}/scripts/ci/_travis-fold.sh
# TODO(i): wrap into subshell, so that we don't pollute CWD, but not yet to minimize diff collision with Jason # TODO(i): wrap into subshell, so that we don't pollute CWD, but not yet to minimize diff collision with Jason
cd ${currentDir} cd ${currentDir}
PACKAGES=(core PACKAGES=(compiler
compiler core
common common
animations animations
platform-browser platform-browser
@ -27,7 +27,8 @@ PACKAGES=(core
service-worker service-worker
elements) elements)
TSC_PACKAGES=(compiler-cli TSC_PACKAGES=(compiler
compiler-cli
language-service language-service
benchpress) benchpress)
@ -239,7 +240,13 @@ compilePackage() {
# For TSC_PACKAGES items # For TSC_PACKAGES items
if containsElement "${3}" "${TSC_PACKAGES[@]}"; then if containsElement "${3}" "${TSC_PACKAGES[@]}"; then
echo "====== [${3}]: COMPILING: ${TSC} -p ${1}/tsconfig-build.json" echo "====== [${3}]: COMPILING: ${TSC} -p ${1}/tsconfig-build.json"
local package_name=$(basename "${2}")
$TSC -p ${1}/tsconfig-build.json $TSC -p ${1}/tsconfig-build.json
if [[ "${3}" = "compiler" ]]; then
if [[ "${package_name}" = "testing" ]]; then
echo "$(cat ${LICENSE_BANNER}) ${N} export * from './${package_name}/${package_name}'" > ${2}/../${package_name}.d.ts
fi
fi
else else
echo "====== [${3}]: COMPILING: ${NGC} -p ${1}/tsconfig-build.json" echo "====== [${3}]: COMPILING: ${NGC} -p ${1}/tsconfig-build.json"
local package_name=$(basename "${2}") local package_name=$(basename "${2}")

View File

@ -24,9 +24,8 @@ filegroup(
ANGULAR_TESTING = [ ANGULAR_TESTING = [
"node_modules/@angular/*/bundles/*-testing.umd.js", "node_modules/@angular/*/bundles/*-testing.umd.js",
# We use AOT, so the compiler and the dynamic platform-browser should be # We use AOT, so the dynamic platform-browser should be visible only in tests
# visible only in tests # NOTE that we still need to include the compiler because the core depends on it
"node_modules/@angular/compiler/bundles/*.umd.js",
"node_modules/@angular/platform-browser-dynamic/bundles/*.umd.js", "node_modules/@angular/platform-browser-dynamic/bundles/*.umd.js",
] ]

View File

@ -19,6 +19,9 @@ node_modules/zone.js/dist/zone_externs.js
--js node_modules/rxjs/operators/package.json --js node_modules/rxjs/operators/package.json
--js node_modules/rxjs/_esm2015/operators/index.js --js node_modules/rxjs/_esm2015/operators/index.js
--js node_modules/@angular/compiler/package.json
--js node_modules/@angular/compiler/fesm2015/compiler.js
--js node_modules/@angular/core/package.json --js node_modules/@angular/core/package.json
--js node_modules/@angular/core/fesm2015/core.js --js node_modules/@angular/core/fesm2015/core.js
--js node_modules/@angular/core/src/testability/testability.externs.js --js node_modules/@angular/core/src/testability/testability.externs.js

View File

@ -17,6 +17,9 @@ node_modules/zone.js/dist/zone_externs.js
--module_resolution=node --module_resolution=node
--package_json_entry_names es2015 --package_json_entry_names es2015
--js node_modules/@angular/compiler/package.json
--js node_modules/@angular/compiler/fesm2015/compiler.js
--js node_modules/@angular/core/package.json --js node_modules/@angular/core/package.json
--js node_modules/@angular/core/fesm2015/core.js --js node_modules/@angular/core/fesm2015/core.js
--js node_modules/@angular/core/src/testability/testability.externs.js --js node_modules/@angular/core/src/testability/testability.externs.js

View File

@ -9,6 +9,7 @@
"@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli", "@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli",
"@angular/core": "file:../../dist/packages-dist/core", "@angular/core": "file:../../dist/packages-dist/core",
"@angular/platform-browser": "file:../../dist/packages-dist/platform-browser", "@angular/platform-browser": "file:../../dist/packages-dist/platform-browser",
"@angular/platform-browser-dynamic": "file:../../dist/packages-dist/platform-browser-dynamic",
"@angular/platform-server": "file:../../dist/packages-dist/platform-server", "@angular/platform-server": "file:../../dist/packages-dist/platform-server",
"google-closure-compiler": "git+https://github.com/alexeagle/closure-compiler.git#packagejson.dist", "google-closure-compiler": "git+https://github.com/alexeagle/closure-compiler.git#packagejson.dist",
"rxjs": "file:../../node_modules/rxjs", "rxjs": "file:../../node_modules/rxjs",
@ -30,4 +31,4 @@
"preprotractor": "tsc -p e2e", "preprotractor": "tsc -p e2e",
"protractor": "protractor e2e/protractor.config.js" "protractor": "protractor e2e/protractor.config.js"
} }
} }

View File

@ -3,40 +3,48 @@
"@angular/animations@file:../../dist/packages-dist/animations": "@angular/animations@file:../../dist/packages-dist/animations":
version "6.0.0-beta.7-8203e0365a" version "7.0.0-beta.1-d2d510089c"
dependencies: dependencies:
tslib "^1.9.0" tslib "^1.9.0"
"@angular/common@file:../../dist/packages-dist/common": "@angular/common@file:../../dist/packages-dist/common":
version "6.0.0-beta.7-8203e0365a" version "7.0.0-beta.1-d2d510089c"
dependencies: dependencies:
tslib "^1.9.0" tslib "^1.9.0"
"@angular/compiler-cli@file:../../dist/packages-dist/compiler-cli": "@angular/compiler-cli@file:../../dist/packages-dist/compiler-cli":
version "6.0.0-beta.7-8203e0365a" version "7.0.0-beta.1-d2d510089c"
dependencies: dependencies:
chokidar "^1.4.2" chokidar "^1.4.2"
convert-source-map "^1.5.1"
magic-string "^0.25.0"
minimist "^1.2.0" minimist "^1.2.0"
reflect-metadata "^0.1.2" reflect-metadata "^0.1.2"
tsickle "^0.27.2" source-map "^0.6.1"
tsickle "^0.32.1"
"@angular/compiler@file:../../dist/packages-dist/compiler": "@angular/compiler@file:../../dist/packages-dist/compiler":
version "6.0.0-beta.7-8203e0365a" version "7.0.0-beta.1-d2d510089c"
dependencies: dependencies:
tslib "^1.9.0" tslib "^1.9.0"
"@angular/core@file:../../dist/packages-dist/core": "@angular/core@file:../../dist/packages-dist/core":
version "6.0.0-beta.7-8203e0365a" version "7.0.0-beta.1-d2d510089c"
dependencies:
tslib "^1.9.0"
"@angular/platform-browser-dynamic@file:../../dist/packages-dist/platform-browser-dynamic":
version "7.0.0-beta.1-d2d510089c"
dependencies: dependencies:
tslib "^1.9.0" tslib "^1.9.0"
"@angular/platform-browser@file:../../dist/packages-dist/platform-browser": "@angular/platform-browser@file:../../dist/packages-dist/platform-browser":
version "6.0.0-beta.7-8203e0365a" version "7.0.0-beta.1-d2d510089c"
dependencies: dependencies:
tslib "^1.9.0" tslib "^1.9.0"
"@angular/platform-server@file:../../dist/packages-dist/platform-server": "@angular/platform-server@file:../../dist/packages-dist/platform-server":
version "6.0.0-beta.7-8203e0365a" version "7.0.0-beta.1-d2d510089c"
dependencies: dependencies:
domino "^2.0.1" domino "^2.0.1"
tslib "^1.9.0" tslib "^1.9.0"
@ -510,6 +518,10 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
convert-source-map@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5"
cookie@0.3.1: cookie@0.3.1:
version "0.3.1" version "0.3.1"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
@ -612,6 +624,10 @@ dev-ip@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/dev-ip/-/dev-ip-1.0.1.tgz#a76a3ed1855be7a012bb8ac16cb80f3c00dc28f0" resolved "https://registry.yarnpkg.com/dev-ip/-/dev-ip-1.0.1.tgz#a76a3ed1855be7a012bb8ac16cb80f3c00dc28f0"
diff@^3.2.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
domino@^2.0.1: domino@^2.0.1:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/domino/-/domino-2.0.1.tgz#9e1d63215d0fe8dcb8202bff07effa1a216db504" resolved "https://registry.yarnpkg.com/domino/-/domino-2.0.1.tgz#9e1d63215d0fe8dcb8202bff07effa1a216db504"
@ -1203,6 +1219,12 @@ jasmine-core@~2.8.0:
version "2.8.0" version "2.8.0"
resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.8.0.tgz#bcc979ae1f9fd05701e45e52e65d3a5d63f1a24e" resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.8.0.tgz#bcc979ae1f9fd05701e45e52e65d3a5d63f1a24e"
jasmine-diff@^0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/jasmine-diff/-/jasmine-diff-0.1.3.tgz#93ccc2dcc41028c5ddd4606558074839f2deeaa8"
dependencies:
diff "^3.2.0"
jasmine@^2.5.3: jasmine@^2.5.3:
version "2.8.0" version "2.8.0"
resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-2.8.0.tgz#6b089c0a11576b1f16df11b80146d91d4e8b8a3e" resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-2.8.0.tgz#6b089c0a11576b1f16df11b80146d91d4e8b8a3e"
@ -1319,6 +1341,12 @@ lodash@^4.11.1, lodash@^4.5.1:
version "4.17.4" version "4.17.4"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
magic-string@^0.25.0:
version "0.25.0"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.0.tgz#1f3696f9931ff0a1ed4c132250529e19cad6759b"
dependencies:
sourcemap-codec "^1.4.1"
micromatch@2.3.11, micromatch@^2.1.5: micromatch@2.3.11, micromatch@^2.1.5:
version "2.3.11" version "2.3.11"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
@ -1835,7 +1863,7 @@ rx@4.1.0:
resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782" resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782"
"rxjs@file:../../node_modules/rxjs": "rxjs@file:../../node_modules/rxjs":
version "6.0.0-alpha.4" version "6.0.0"
dependencies: dependencies:
tslib "^1.9.0" tslib "^1.9.0"
@ -2016,10 +2044,14 @@ source-map@^0.5.1, source-map@^0.5.6:
version "0.5.7" version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
source-map@^0.6.0: source-map@^0.6.0, source-map@^0.6.1:
version "0.6.1" version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
sourcemap-codec@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.1.tgz#c8fd92d91889e902a07aee392bdd2c5863958ba2"
spawn-command@^0.0.2-1: spawn-command@^0.0.2-1:
version "0.0.2" version "0.0.2"
resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2.tgz#9544e1a43ca045f8531aac1a48cb29bdae62338e" resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2.tgz#9544e1a43ca045f8531aac1a48cb29bdae62338e"
@ -2180,10 +2212,11 @@ tree-kill@^1.1.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.0.tgz#5846786237b4239014f05db156b643212d4c6f36" resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.0.tgz#5846786237b4239014f05db156b643212d4c6f36"
tsickle@^0.27.2: tsickle@^0.32.1:
version "0.27.2" version "0.32.1"
resolved "https://registry.yarnpkg.com/tsickle/-/tsickle-0.27.2.tgz#f33d46d046f73dd5c155a37922e422816e878736" resolved "https://registry.yarnpkg.com/tsickle/-/tsickle-0.32.1.tgz#f16e94ba80b32fc9ebe320dc94fbc2ca7f3521a5"
dependencies: dependencies:
jasmine-diff "^0.1.3"
minimist "^1.2.0" minimist "^1.2.0"
mkdirp "^0.5.1" mkdirp "^0.5.1"
source-map "^0.6.0" source-map "^0.6.0"
@ -2204,7 +2237,7 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0:
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
"typescript@file:../../node_modules/typescript": "typescript@file:../../node_modules/typescript":
version "2.7.2" version "2.9.2"
ua-parser-js@0.7.12: ua-parser-js@0.7.12:
version "0.7.12" version "0.7.12"
@ -2424,4 +2457,4 @@ yeast@0.1.2:
resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419"
"zone.js@file:../../node_modules/zone.js": "zone.js@file:../../node_modules/zone.js":
version "0.8.20" version "0.8.26"

View File

@ -52,5 +52,12 @@ function getHookName(hook: LifecycleHooks): string {
return 'ngAfterViewInit'; return 'ngAfterViewInit';
case LifecycleHooks.AfterViewChecked: case LifecycleHooks.AfterViewChecked:
return 'ngAfterViewChecked'; return 'ngAfterViewChecked';
default:
// This default case is not needed by TypeScript compiler, as the switch is exhaustive.
// However Closure Compiler does not understand that and reports an error in typed mode.
// The `throw new Error` below works around the problem, and the unexpected: never variable
// makes sure tsc still checks this code is unreachable.
const unexpected: never = hook;
throw new Error(`unexpected ${unexpected}`);
} }
} }

View File

@ -115,16 +115,13 @@ export class CompileMetadataResolver {
return this.getGeneratedClass(dirType, cpl.hostViewClassName(dirType)); return this.getGeneratedClass(dirType, cpl.hostViewClassName(dirType));
} }
getHostComponentType(dirType: any): StaticSymbol|Type { getHostComponentType(dirType: any): StaticSymbol|cpl.ProxyClass {
const name = `${cpl.identifierName({reference: dirType})}_Host`; const name = `${cpl.identifierName({reference: dirType})}_Host`;
if (dirType instanceof StaticSymbol) { if (dirType instanceof StaticSymbol) {
return this._staticSymbolCache.get(dirType.filePath, name); return this._staticSymbolCache.get(dirType.filePath, name);
} else {
const HostClass = <any>function HostClass() {};
HostClass.overriddenName = name;
return HostClass;
} }
return this._createProxyClass(dirType, name);
} }
private getRendererType(dirType: any): StaticSymbol|object { private getRendererType(dirType: any): StaticSymbol|object {

View File

@ -940,7 +940,8 @@ function needsAdditionalRootNode(astNodes: TemplateAst[]): boolean {
function elementBindingDef(inputAst: BoundElementPropertyAst, dirAst: DirectiveAst): o.Expression { function elementBindingDef(inputAst: BoundElementPropertyAst, dirAst: DirectiveAst): o.Expression {
switch (inputAst.type) { const inputType = inputAst.type;
switch (inputType) {
case PropertyBindingType.Attribute: case PropertyBindingType.Attribute:
return o.literalArr([ return o.literalArr([
o.literal(BindingFlags.TypeElementAttribute), o.literal(inputAst.name), o.literal(BindingFlags.TypeElementAttribute), o.literal(inputAst.name),
@ -965,6 +966,13 @@ function elementBindingDef(inputAst: BoundElementPropertyAst, dirAst: DirectiveA
return o.literalArr([ return o.literalArr([
o.literal(BindingFlags.TypeElementStyle), o.literal(inputAst.name), o.literal(inputAst.unit) o.literal(BindingFlags.TypeElementStyle), o.literal(inputAst.name), o.literal(inputAst.unit)
]); ]);
default:
// This default case is not needed by TypeScript compiler, as the switch is exhaustive.
// However Closure Compiler does not understand that and reports an error in typed mode.
// The `throw new Error` below works around the problem, and the unexpected: never variable
// makes sure tsc still checks this code is unreachable.
const unexpected: never = inputType;
throw new Error(`unexpected ${unexpected}`);
} }
} }

View File

@ -15,6 +15,8 @@ export {
detectChanges as ɵdetectChanges, detectChanges as ɵdetectChanges,
renderComponent as ɵrenderComponent, renderComponent as ɵrenderComponent,
ComponentType as ɵComponentType, ComponentType as ɵComponentType,
ComponentFactory as ɵRender3ComponentFactory,
ComponentRef as ɵRender3ComponentRef,
DirectiveType as ɵDirectiveType, DirectiveType as ɵDirectiveType,
RenderFlags as ɵRenderFlags, RenderFlags as ɵRenderFlags,
directiveInject as ɵdirectiveInject, directiveInject as ɵdirectiveInject,
@ -29,6 +31,7 @@ export {
InheritDefinitionFeature as ɵInheritDefinitionFeature, InheritDefinitionFeature as ɵInheritDefinitionFeature,
NgOnChangesFeature as ɵNgOnChangesFeature, NgOnChangesFeature as ɵNgOnChangesFeature,
NgModuleType as ɵNgModuleType, NgModuleType as ɵNgModuleType,
NgModuleRef as ɵRender3NgModuleRef,
CssSelectorList as ɵCssSelectorList, CssSelectorList as ɵCssSelectorList,
markDirty as ɵmarkDirty, markDirty as ɵmarkDirty,
NgModuleFactory as ɵNgModuleFactory, NgModuleFactory as ɵNgModuleFactory,
@ -95,6 +98,7 @@ export {
ld as ɵld, ld as ɵld,
Pp as ɵPp, Pp as ɵPp,
ComponentDef as ɵComponentDef, ComponentDef as ɵComponentDef,
ComponentDefInternal as ɵComponentDefInternal,
DirectiveDef as ɵDirectiveDef, DirectiveDef as ɵDirectiveDef,
PipeDef as ɵPipeDef, PipeDef as ɵPipeDef,
whenRendered as ɵwhenRendered, whenRendered as ɵwhenRendered,
@ -112,14 +116,38 @@ export {
iM as ɵiM, iM as ɵiM,
I18nInstruction as ɵI18nInstruction, I18nInstruction as ɵI18nInstruction,
I18nExpInstruction as ɵI18nExpInstruction, I18nExpInstruction as ɵI18nExpInstruction,
WRAP_RENDERER_FACTORY2 as ɵWRAP_RENDERER_FACTORY2,
Render3DebugRendererFactory2 as ɵRender3DebugRendererFactory2,
} from './render3/index'; } from './render3/index';
export {NgModuleDef as ɵNgModuleDef} from './metadata/ng_module';
export {
compileNgModuleDefs as ɵcompileNgModuleDefs,
patchComponentDefWithScope as ɵpatchComponentDefWithScope,
} from './render3/jit/module';
export {
compileComponent as ɵcompileComponent,
compileDirective as ɵcompileDirective,
} from './render3/jit/directive';
export {
compilePipe as ɵcompilePipe,
} from './render3/jit/pipe';
export {
NgModuleDef as ɵNgModuleDef,
NgModuleDefInternal as ɵNgModuleDefInternal,
NgModuleTransitiveScopes as ɵNgModuleTransitiveScopes,
} from './metadata/ng_module';
export { export {
sanitizeHtml as ɵsanitizeHtml, sanitizeHtml as ɵsanitizeHtml,
sanitizeStyle as ɵsanitizeStyle, sanitizeStyle as ɵsanitizeStyle,
sanitizeUrl as ɵsanitizeUrl, sanitizeUrl as ɵsanitizeUrl,
sanitizeResourceUrl as ɵsanitizeResourceUrl, sanitizeResourceUrl as ɵsanitizeResourceUrl,
} from './sanitization/sanitization'; } from './sanitization/sanitization';
export { export {
bypassSanitizationTrustHtml as ɵbypassSanitizationTrustHtml, bypassSanitizationTrustHtml as ɵbypassSanitizationTrustHtml,
bypassSanitizationTrustStyle as ɵbypassSanitizationTrustStyle, bypassSanitizationTrustStyle as ɵbypassSanitizationTrustStyle,
@ -127,4 +155,4 @@ export {
bypassSanitizationTrustUrl as ɵbypassSanitizationTrustUrl, bypassSanitizationTrustUrl as ɵbypassSanitizationTrustUrl,
bypassSanitizationTrustResourceUrl as ɵbypassSanitizationTrustResourceUrl, bypassSanitizationTrustResourceUrl as ɵbypassSanitizationTrustResourceUrl,
} from './sanitization/bypass'; } from './sanitization/bypass';
// clang-format on // clang-format on

View File

@ -6,12 +6,12 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ChangeDetectorRef} from '../change_detection/change_detector_ref'; import {ChangeDetectorRef as ViewEngine_ChangeDetectorRef} from '../change_detection/change_detector_ref';
import {InjectionToken} from '../di/injection_token'; import {InjectionToken} from '../di/injection_token';
import {Injector, inject} from '../di/injector'; import {Injector, inject} from '../di/injector';
import {ComponentFactory as viewEngine_ComponentFactory, ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory'; import {ComponentFactory as viewEngine_ComponentFactory, ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory';
import {ComponentFactoryResolver as viewEngine_ComponentFactoryResolver} from '../linker/component_factory_resolver'; import {ComponentFactoryResolver as viewEngine_ComponentFactoryResolver} from '../linker/component_factory_resolver';
import {ElementRef} from '../linker/element_ref'; import {ElementRef as viewEngine_ElementRef} from '../linker/element_ref';
import {NgModuleRef as viewEngine_NgModuleRef} from '../linker/ng_module_factory'; import {NgModuleRef as viewEngine_NgModuleRef} from '../linker/ng_module_factory';
import {RendererFactory2} from '../render/api'; import {RendererFactory2} from '../render/api';
import {Type} from '../type'; import {Type} from '../type';
@ -21,7 +21,7 @@ import {LifecycleHooksFeature, createRootContext} from './component';
import {baseDirectiveCreate, createLNode, createLViewData, createTView, elementCreate, enterView, hostElement, initChangeDetectorIfExisting, locateHostElement, renderEmbeddedTemplate} from './instructions'; import {baseDirectiveCreate, createLNode, createLViewData, createTView, elementCreate, enterView, hostElement, initChangeDetectorIfExisting, locateHostElement, renderEmbeddedTemplate} from './instructions';
import {ComponentDefInternal, ComponentType, RenderFlags} from './interfaces/definition'; import {ComponentDefInternal, ComponentType, RenderFlags} from './interfaces/definition';
import {LElementNode, TNode, TNodeType} from './interfaces/node'; import {LElementNode, TNode, TNodeType} from './interfaces/node';
import {RElement, domRendererFactory3} from './interfaces/renderer'; import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
import {CONTEXT, FLAGS, INJECTOR, LViewData, LViewFlags, RootContext, TVIEW} from './interfaces/view'; import {CONTEXT, FLAGS, INJECTOR, LViewData, LViewFlags, RootContext, TVIEW} from './interfaces/view';
import {RootViewRef, ViewRef} from './view_ref'; import {RootViewRef, ViewRef} from './view_ref';
@ -55,8 +55,20 @@ export const ROOT_CONTEXT = new InjectionToken<RootContext>(
* A change detection scheduler token for {@link RootContext}. This token is the default value used * A change detection scheduler token for {@link RootContext}. This token is the default value used
* for the default `RootContext` found in the {@link ROOT_CONTEXT} token. * for the default `RootContext` found in the {@link ROOT_CONTEXT} token.
*/ */
export const SCHEDULER = new InjectionToken<((fn: () => void) => void)>( export const SCHEDULER = new InjectionToken<((fn: () => void) => void)>('SCHEDULER_TOKEN', {
'SCHEDULER_TOKEN', {providedIn: 'root', factory: () => requestAnimationFrame.bind(window)}); providedIn: 'root',
factory: () => {
const useRaf = typeof requestAnimationFrame !== 'undefined' && typeof window !== 'undefined';
return useRaf ? requestAnimationFrame.bind(window) : setTimeout;
},
});
/**
* A function used to wrap the `RendererFactory2`.
* Used in tests to change the `RendererFactory2` into a `DebugRendererFactory2`.
*/
export const WRAP_RENDERER_FACTORY2 =
new InjectionToken<(rf: RendererFactory2) => RendererFactory2>('WRAP_RENDERER_FACTORY2');
/** /**
* Render3 implementation of {@link viewEngine_ComponentFactory}. * Render3 implementation of {@link viewEngine_ComponentFactory}.
@ -65,9 +77,11 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
selector: string; selector: string;
componentType: Type<any>; componentType: Type<any>;
ngContentSelectors: string[]; ngContentSelectors: string[];
get inputs(): {propName: string; templateName: string;}[] { get inputs(): {propName: string; templateName: string;}[] {
return toRefArray(this.componentDef.inputs); return toRefArray(this.componentDef.inputs);
} }
get outputs(): {propName: string; templateName: string;}[] { get outputs(): {propName: string; templateName: string;}[] {
return toRefArray(this.componentDef.outputs); return toRefArray(this.componentDef.outputs);
} }
@ -84,8 +98,15 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
ngModule?: viewEngine_NgModuleRef<any>|undefined): viewEngine_ComponentRef<T> { ngModule?: viewEngine_NgModuleRef<any>|undefined): viewEngine_ComponentRef<T> {
const isInternalRootView = rootSelectorOrNode === undefined; const isInternalRootView = rootSelectorOrNode === undefined;
const rendererFactory = let rendererFactory: RendererFactory3;
ngModule ? ngModule.injector.get(RendererFactory2) : domRendererFactory3;
if (ngModule) {
const wrapper = ngModule.injector.get(WRAP_RENDERER_FACTORY2, (v: RendererFactory2) => v);
rendererFactory = wrapper(ngModule.injector.get(RendererFactory2)) as RendererFactory3;
} else {
rendererFactory = domRendererFactory3;
}
const hostNode = isInternalRootView ? const hostNode = isInternalRootView ?
elementCreate(this.selector, rendererFactory.createRenderer(null, this.componentDef)) : elementCreate(this.selector, rendererFactory.createRenderer(null, this.componentDef)) :
locateHostElement(rendererFactory, rootSelectorOrNode); locateHostElement(rendererFactory, rootSelectorOrNode);
@ -116,11 +137,10 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
elementNode = hostElement(componentTag, hostNode, this.componentDef); elementNode = hostElement(componentTag, hostNode, this.componentDef);
// Create directive instance with factory() and store at index 0 in directives array // Create directive instance with factory() and store at index 0 in directives array
rootContext.components.push( component = baseDirectiveCreate(0, this.componentDef.factory(), this.componentDef);
component = baseDirectiveCreate(0, this.componentDef.factory(), this.componentDef) as T); rootContext.components.push(component);
initChangeDetectorIfExisting(elementNode.nodeInjector, component, elementNode.data !); initChangeDetectorIfExisting(elementNode.nodeInjector, component, elementNode.data !);
(elementNode.data as LViewData)[CONTEXT] = component; (elementNode.data as LViewData)[CONTEXT] = component;
// TODO: should LifecycleHooksFeature and other host features be generated by the compiler and // TODO: should LifecycleHooksFeature and other host features be generated by the compiler and
// executed here? // executed here?
// Angular 5 reference: https://stackblitz.com/edit/lifecycle-hooks-vcref // Angular 5 reference: https://stackblitz.com/edit/lifecycle-hooks-vcref
@ -177,11 +197,11 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
*/ */
export class ComponentRef<T> extends viewEngine_ComponentRef<T> { export class ComponentRef<T> extends viewEngine_ComponentRef<T> {
destroyCbs: (() => void)[]|null = []; destroyCbs: (() => void)[]|null = [];
location: ElementRef<any>; location: viewEngine_ElementRef<any>;
injector: Injector; injector: Injector;
instance: T; instance: T;
hostView: ViewRef<T>; hostView: ViewRef<T>;
changeDetectorRef: ChangeDetectorRef; changeDetectorRef: ViewEngine_ChangeDetectorRef;
componentType: Type<T>; componentType: Type<T>;
constructor( constructor(
@ -192,7 +212,7 @@ export class ComponentRef<T> extends viewEngine_ComponentRef<T> {
this.hostView = this.changeDetectorRef = new RootViewRef<T>(rootView); this.hostView = this.changeDetectorRef = new RootViewRef<T>(rootView);
this.hostView._lViewNode = createLNode(-1, TNodeType.View, null, null, null, rootView); this.hostView._lViewNode = createLNode(-1, TNodeType.View, null, null, null, rootView);
this.injector = injector; this.injector = injector;
this.location = new ElementRef(hostNode); this.location = new viewEngine_ElementRef(hostNode);
this.componentType = componentType; this.componentType = componentType;
} }
@ -205,4 +225,4 @@ export class ComponentRef<T> extends viewEngine_ComponentRef<T> {
ngDevMode && assertDefined(this.destroyCbs, 'NgModule already destroyed'); ngDevMode && assertDefined(this.destroyCbs, 'NgModule already destroyed');
this.destroyCbs !.push(callback); this.destroyCbs !.push(callback);
} }
} }

View File

@ -0,0 +1,115 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Injector} from '../di/injector';
import {Renderer2, RendererType2} from '../render/api';
import {DebugContext} from '../view';
import {DebugRenderer2, DebugRendererFactory2} from '../view/services';
import * as di from './di';
import {NG_HOST_SYMBOL, _getViewData} from './instructions';
import {LElementNode} from './interfaces/node';
import {CONTEXT, DIRECTIVES, LViewData, TVIEW} from './interfaces/view';
/**
* Adapts the DebugRendererFactory2 to create a DebugRenderer2 specific for IVY.
*
* The created DebugRenderer know how to create a Debug Context specific to IVY.
*/
export class Render3DebugRendererFactory2 extends DebugRendererFactory2 {
createRenderer(element: any, renderData: RendererType2|null): Renderer2 {
const renderer = super.createRenderer(element, renderData) as DebugRenderer2;
renderer.debugContextFactory = () => new Render3DebugContext(_getViewData());
return renderer;
}
}
/**
* Stores context information about view nodes.
*
* Used in tests to retrieve information those nodes.
*/
class Render3DebugContext implements DebugContext {
readonly nodeIndex: number|null;
constructor(private viewData: LViewData) {
// The LNode will be created next and appended to viewData
this.nodeIndex = viewData ? viewData.length : null;
}
get view(): any { return this.viewData; }
get injector(): Injector {
if (this.nodeIndex !== null) {
const lElementNode: LElementNode = this.view[this.nodeIndex];
const nodeInjector = lElementNode.nodeInjector;
if (nodeInjector) {
return new di.NodeInjector(nodeInjector);
}
}
return Injector.NULL;
}
get component(): any {
// TODO(vicb): why/when
if (this.nodeIndex === null) {
return null;
}
const tView = this.view[TVIEW];
const components: number[]|null = tView.components;
return (components && components.indexOf(this.nodeIndex) == -1) ?
null :
this.view[this.nodeIndex].data[CONTEXT];
}
// TODO(vicb): add view providers when supported
get providerTokens(): any[] {
const matchedDirectives: any[] = [];
// TODO(vicb): why/when
if (this.nodeIndex === null) {
return matchedDirectives;
}
const directives = this.view[DIRECTIVES];
if (directives) {
const currentNode = this.view[this.nodeIndex];
for (let dirIndex = 0; dirIndex < directives.length; dirIndex++) {
const directive = directives[dirIndex];
if (directive[NG_HOST_SYMBOL] === currentNode) {
matchedDirectives.push(directive.constructor);
}
}
}
return matchedDirectives;
}
get references(): {[key: string]: any} {
// TODO(vicb): implement retrieving references
throw new Error('Not implemented yet in ivy');
}
get context(): any {
if (this.nodeIndex === null) {
return null;
}
const lNode = this.view[this.nodeIndex];
return lNode.view[CONTEXT];
}
get componentRenderElement(): any { throw new Error('Not implemented in ivy'); }
get renderNode(): any { throw new Error('Not implemented in ivy'); }
// TODO(vicb): check previous implementation
logError(console: Console, ...values: any[]): void { console.error(...values); }
}

View File

@ -605,7 +605,7 @@ export function getOrCreateContainerRef(di: LInjector): viewEngine.ViewContainer
return di.viewContainerRef; return di.viewContainerRef;
} }
class NodeInjector implements Injector { export class NodeInjector implements Injector {
constructor(private _lInjector: LInjector) {} constructor(private _lInjector: LInjector) {}
get(token: any): any { get(token: any): any {

View File

@ -89,7 +89,9 @@ export function NgOnChangesFeature<T>(definition: DirectiveDefInternal<T>): void
} }
if (setter) setter.call(this, value); if (setter) setter.call(this, value);
} },
// Make the property configurable in dev mode to allow overriding in tests
configurable: !!ngDevMode
}); });
} }
} }

View File

@ -13,13 +13,12 @@ import {NgOnChangesFeature} from './features/ng_onchanges_feature';
import {PublicFeature} from './features/public_feature'; import {PublicFeature} from './features/public_feature';
import {ComponentDef, ComponentDefInternal, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveDefInternal, DirectiveType, PipeDef} from './interfaces/definition'; import {ComponentDef, ComponentDefInternal, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveDefInternal, DirectiveType, PipeDef} from './interfaces/definition';
export {ComponentFactory, ComponentFactoryResolver, ComponentRef} from './component_ref'; export {ComponentFactory, ComponentFactoryResolver, ComponentRef, WRAP_RENDERER_FACTORY2} from './component_ref';
export {Render3DebugRendererFactory2} from './debug';
export {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, directiveInject, getFactoryOf, getInheritedFactory, injectAttribute, injectChangeDetectorRef, injectComponentFactoryResolver, injectElementRef, injectTemplateRef, injectViewContainerRef} from './di'; export {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, directiveInject, getFactoryOf, getInheritedFactory, injectAttribute, injectChangeDetectorRef, injectComponentFactoryResolver, injectElementRef, injectTemplateRef, injectViewContainerRef} from './di';
export {RenderFlags} from './interfaces/definition'; export {RenderFlags} from './interfaces/definition';
export {CssSelectorList} from './interfaces/projection'; export {CssSelectorList} from './interfaces/projection';
// Naming scheme: // Naming scheme:
// - Capital letters are for creating things: T(Text), E(Element), D(Directive), V(View), // - Capital letters are for creating things: T(Text), E(Element), D(Directive), V(View),
// C(Container), L(Listener) // C(Container), L(Listener)

View File

@ -30,8 +30,6 @@ import {StylingContext, allocStylingContext, createStylingContextTemplate, rende
import {assertDataInRangeInternal, isDifferent, loadElementInternal, loadInternal, stringify} from './util'; import {assertDataInRangeInternal, isDifferent, loadElementInternal, loadInternal, stringify} from './util';
import {ViewRef} from './view_ref'; import {ViewRef} from './view_ref';
/** /**
* Directive (D) sets a property on all component instances using this constant as a key and the * Directive (D) sets a property on all component instances using this constant as a key and the
* component's host node (LElement) as the value. This is used in methods like detectChanges to * component's host node (LElement) as the value. This is used in methods like detectChanges to
@ -126,16 +124,6 @@ export function getCurrentView(): OpaqueViewState {
return viewData as any as OpaqueViewState; return viewData as any as OpaqueViewState;
} }
/**
* Internal function that returns the current LViewData instance.
*
* The getCurrentView() instruction should be used for anything public.
*/
export function _getViewData(): LViewData {
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
return viewData;
}
/** /**
* Restores `contextViewData` to the given OpaqueViewState instance. * Restores `contextViewData` to the given OpaqueViewState instance.
* *
@ -207,6 +195,16 @@ export function getCreationMode(): boolean {
*/ */
let viewData: LViewData; let viewData: LViewData;
/**
* Internal function that returns the current LViewData instance.
*
* The getCurrentView() instruction should be used for anything public.
*/
export function _getViewData(): LViewData {
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
return viewData;
}
/** /**
* The last viewData retrieved by nextContext(). * The last viewData retrieved by nextContext().
* Allows building nextContext() and reference() calls. * Allows building nextContext() and reference() calls.

View File

@ -10,13 +10,12 @@ import {ConstantPool, R3DirectiveMetadata, WrappedNodeExpr, compileComponentFrom
import {Component, Directive, HostBinding, HostListener, Input, Output} from '../../metadata/directives'; import {Component, Directive, HostBinding, HostListener, Input, Output} from '../../metadata/directives';
import {componentNeedsResolution, maybeQueueResolutionOfComponentResources} from '../../metadata/resource_loading'; import {componentNeedsResolution, maybeQueueResolutionOfComponentResources} from '../../metadata/resource_loading';
import {ReflectionCapabilities} from '../../reflection/reflection_capabilities';
import {Type} from '../../type'; import {Type} from '../../type';
import {stringify} from '../../util'; import {stringify} from '../../util';
import {angularCoreEnv} from './environment'; import {angularCoreEnv} from './environment';
import {NG_COMPONENT_DEF, NG_DIRECTIVE_DEF} from './fields'; import {NG_COMPONENT_DEF, NG_DIRECTIVE_DEF} from './fields';
import {patchComponentDefWithScope} from './module'; import {patchComponentDefWithScope, transitiveScopesFor} from './module';
import {getReflect, reflectDependencies} from './util'; import {getReflect, reflectDependencies} from './util';
type StringMap = { type StringMap = {
@ -33,12 +32,12 @@ type StringMap = {
* until the global queue has been resolved with a call to `resolveComponentResources`. * until the global queue has been resolved with a call to `resolveComponentResources`.
*/ */
export function compileComponent(type: Type<any>, metadata: Component): void { export function compileComponent(type: Type<any>, metadata: Component): void {
let def: any = null; let ngComponentDef: any = null;
// Metadata may have resources which need to be resolved. // Metadata may have resources which need to be resolved.
maybeQueueResolutionOfComponentResources(metadata); maybeQueueResolutionOfComponentResources(metadata);
Object.defineProperty(type, NG_COMPONENT_DEF, { Object.defineProperty(type, NG_COMPONENT_DEF, {
get: () => { get: () => {
if (def === null) { if (ngComponentDef === null) {
if (componentNeedsResolution(metadata)) { if (componentNeedsResolution(metadata)) {
const error = [`Component '${stringify(type)}' is not resolved:`]; const error = [`Component '${stringify(type)}' is not resolved:`];
if (metadata.templateUrl) { if (metadata.templateUrl) {
@ -78,7 +77,7 @@ export function compileComponent(type: Type<any>, metadata: Component): void {
constantPool, makeBindingParser()); constantPool, makeBindingParser());
const preStatements = [...constantPool.statements, ...res.statements]; const preStatements = [...constantPool.statements, ...res.statements];
def = jitExpression( ngComponentDef = jitExpression(
res.expression, angularCoreEnv, `ng://${type.name}/ngComponentDef.js`, preStatements); res.expression, angularCoreEnv, `ng://${type.name}/ngComponentDef.js`, preStatements);
// If component compilation is async, then the @NgModule annotation which declares the // If component compilation is async, then the @NgModule annotation which declares the
@ -86,11 +85,14 @@ export function compileComponent(type: Type<any>, metadata: Component): void {
// allows the component to patch itself with directiveDefs from the module after it finishes // allows the component to patch itself with directiveDefs from the module after it finishes
// compiling. // compiling.
if (hasSelectorScope(type)) { if (hasSelectorScope(type)) {
patchComponentDefWithScope(def, type.ngSelectorScope); const scopes = transitiveScopesFor(type.ngSelectorScope);
patchComponentDefWithScope(ngComponentDef, scopes);
} }
} }
return def; return ngComponentDef;
}, },
// Make the property configurable in dev mode to allow overriding in tests
configurable: !!ngDevMode,
}); });
} }
@ -107,23 +109,24 @@ function hasSelectorScope<T>(component: Type<T>): component is Type<T>&
* will resolve when compilation completes and the directive becomes usable. * will resolve when compilation completes and the directive becomes usable.
*/ */
export function compileDirective(type: Type<any>, directive: Directive): void { export function compileDirective(type: Type<any>, directive: Directive): void {
let def: any = null; let ngDirectiveDef: any = null;
Object.defineProperty(type, NG_DIRECTIVE_DEF, { Object.defineProperty(type, NG_DIRECTIVE_DEF, {
get: () => { get: () => {
if (def === null) { if (ngDirectiveDef === null) {
const constantPool = new ConstantPool(); const constantPool = new ConstantPool();
const sourceMapUrl = `ng://${type && type.name}/ngDirectiveDef.js`; const sourceMapUrl = `ng://${type && type.name}/ngDirectiveDef.js`;
const res = compileR3Directive( const res = compileR3Directive(
directiveMetadata(type, directive), constantPool, makeBindingParser()); directiveMetadata(type, directive), constantPool, makeBindingParser());
const preStatements = [...constantPool.statements, ...res.statements]; const preStatements = [...constantPool.statements, ...res.statements];
def = jitExpression(res.expression, angularCoreEnv, sourceMapUrl, preStatements); ngDirectiveDef = jitExpression(res.expression, angularCoreEnv, sourceMapUrl, preStatements);
} }
return def; return ngDirectiveDef;
}, },
// Make the property configurable in dev mode to allow overriding in tests
configurable: !!ngDevMode,
}); });
} }
export function extendsDirectlyFromObject(type: Type<any>): boolean { export function extendsDirectlyFromObject(type: Type<any>): boolean {
return Object.getPrototypeOf(type.prototype) === Object.prototype; return Object.getPrototypeOf(type.prototype) === Object.prototype;
} }

View File

@ -18,15 +18,28 @@ import {reflectDependencies} from './util';
const EMPTY_ARRAY: Type<any>[] = []; const EMPTY_ARRAY: Type<any>[] = [];
export function compileNgModule(type: Type<any>, ngModule: NgModule): void { /**
* Compiles a module in JIT mode.
*
* This function automatically gets called when a class has a `@NgModule` decorator.
*/
export function compileNgModule(moduleType: Type<any>, ngModule: NgModule): void {
compileNgModuleDefs(moduleType, ngModule);
setScopeOnDeclaredComponents(moduleType, ngModule);
}
/**
* Compiles and adds the `ngModuleDef` and `ngInjectorDef` properties to the module class.
*/
export function compileNgModuleDefs(moduleType: Type<any>, ngModule: NgModule): void {
const declarations: Type<any>[] = flatten(ngModule.declarations || EMPTY_ARRAY); const declarations: Type<any>[] = flatten(ngModule.declarations || EMPTY_ARRAY);
let ngModuleDef: any = null; let ngModuleDef: any = null;
Object.defineProperty(type, NG_MODULE_DEF, { Object.defineProperty(moduleType, NG_MODULE_DEF, {
get: () => { get: () => {
if (ngModuleDef === null) { if (ngModuleDef === null) {
const meta: R3NgModuleMetadata = { const meta: R3NgModuleMetadata = {
type: wrap(type), type: wrap(moduleType),
bootstrap: flatten(ngModule.bootstrap || EMPTY_ARRAY).map(wrap), bootstrap: flatten(ngModule.bootstrap || EMPTY_ARRAY).map(wrap),
declarations: declarations.map(wrapReference), declarations: declarations.map(wrapReference),
imports: flatten(ngModule.imports || EMPTY_ARRAY) imports: flatten(ngModule.imports || EMPTY_ARRAY)
@ -38,21 +51,23 @@ export function compileNgModule(type: Type<any>, ngModule: NgModule): void {
emitInline: true, emitInline: true,
}; };
const res = compileR3NgModule(meta); const res = compileR3NgModule(meta);
ngModuleDef = ngModuleDef = jitExpression(
jitExpression(res.expression, angularCoreEnv, `ng://${type.name}/ngModuleDef.js`, []); res.expression, angularCoreEnv, `ng://${moduleType.name}/ngModuleDef.js`, []);
} }
return ngModuleDef; return ngModuleDef;
}, },
// Make the property configurable in dev mode to allow overriding in tests
configurable: !!ngDevMode,
}); });
let ngInjectorDef: any = null; let ngInjectorDef: any = null;
Object.defineProperty(type, NG_INJECTOR_DEF, { Object.defineProperty(moduleType, NG_INJECTOR_DEF, {
get: () => { get: () => {
if (ngInjectorDef === null) { if (ngInjectorDef === null) {
const meta: R3InjectorMetadata = { const meta: R3InjectorMetadata = {
name: type.name, name: moduleType.name,
type: wrap(type), type: wrap(moduleType),
deps: reflectDependencies(type), deps: reflectDependencies(moduleType),
providers: new WrappedNodeExpr(ngModule.providers || EMPTY_ARRAY), providers: new WrappedNodeExpr(ngModule.providers || EMPTY_ARRAY),
imports: new WrappedNodeExpr([ imports: new WrappedNodeExpr([
ngModule.imports || EMPTY_ARRAY, ngModule.imports || EMPTY_ARRAY,
@ -61,25 +76,36 @@ export function compileNgModule(type: Type<any>, ngModule: NgModule): void {
}; };
const res = compileInjector(meta); const res = compileInjector(meta);
ngInjectorDef = jitExpression( ngInjectorDef = jitExpression(
res.expression, angularCoreEnv, `ng://${type.name}/ngInjectorDef.js`, res.statements); res.expression, angularCoreEnv, `ng://${moduleType.name}/ngInjectorDef.js`,
res.statements);
} }
return ngInjectorDef; return ngInjectorDef;
}, },
// Make the property configurable in dev mode to allow overriding in tests
configurable: !!ngDevMode,
}); });
}
/**
* Some declared components may be compiled asynchronously, and thus may not have their
* ngComponentDef set yet. If this is the case, then a reference to the module is written into
* the `ngSelectorScope` property of the declared type.
*/
function setScopeOnDeclaredComponents(moduleType: Type<any>, ngModule: NgModule) {
const declarations: Type<any>[] = flatten(ngModule.declarations || EMPTY_ARRAY);
const transitiveScopes = transitiveScopesFor(moduleType);
declarations.forEach(declaration => { declarations.forEach(declaration => {
// Some declared components may be compiled asynchronously, and thus may not have their
// ngComponentDef set yet. If this is the case, then a reference to the module is written into
// the `ngSelectorScope` property of the declared type.
if (declaration.hasOwnProperty(NG_COMPONENT_DEF)) { if (declaration.hasOwnProperty(NG_COMPONENT_DEF)) {
// An `ngComponentDef` field exists - go ahead and patch the component directly. // An `ngComponentDef` field exists - go ahead and patch the component directly.
patchComponentDefWithScope( const component = declaration as Type<any>& {ngComponentDef: ComponentDefInternal<any>};
(declaration as Type<any>& {ngComponentDef: ComponentDefInternal<any>}).ngComponentDef, const componentDef = component.ngComponentDef;
type); patchComponentDefWithScope(componentDef, transitiveScopes);
} else if ( } else if (
!declaration.hasOwnProperty(NG_DIRECTIVE_DEF) && !declaration.hasOwnProperty(NG_PIPE_DEF)) { !declaration.hasOwnProperty(NG_DIRECTIVE_DEF) && !declaration.hasOwnProperty(NG_PIPE_DEF)) {
// Set `ngSelectorScope` for future reference when the component compilation finishes. // Set `ngSelectorScope` for future reference when the component compilation finishes.
(declaration as Type<any>& {ngSelectorScope?: any}).ngSelectorScope = type; (declaration as Type<any>& {ngSelectorScope?: any}).ngSelectorScope = moduleType;
} }
}); });
} }
@ -88,13 +114,13 @@ export function compileNgModule(type: Type<any>, ngModule: NgModule): void {
* Patch the definition of a component with directives and pipes from the compilation scope of * Patch the definition of a component with directives and pipes from the compilation scope of
* a given module. * a given module.
*/ */
export function patchComponentDefWithScope<C, M>( export function patchComponentDefWithScope<C>(
componentDef: ComponentDefInternal<C>, module: Type<M>) { componentDef: ComponentDefInternal<C>, transitiveScopes: NgModuleTransitiveScopes) {
componentDef.directiveDefs = () => Array.from(transitiveScopesFor(module).compilation.directives) componentDef.directiveDefs = () => Array.from(transitiveScopes.compilation.directives)
.map(dir => dir.ngDirectiveDef || dir.ngComponentDef) .map(dir => dir.ngDirectiveDef || dir.ngComponentDef)
.filter(def => !!def); .filter(def => !!def);
componentDef.pipeDefs = () => componentDef.pipeDefs = () =>
Array.from(transitiveScopesFor(module).compilation.pipes).map(pipe => pipe.ngPipeDef); Array.from(transitiveScopes.compilation.pipes).map(pipe => pipe.ngPipeDef);
} }
/** /**
@ -104,7 +130,7 @@ export function patchComponentDefWithScope<C, M>(
* on modules with components that have not fully compiled yet, but the result should not be used * on modules with components that have not fully compiled yet, but the result should not be used
* until they have. * until they have.
*/ */
function transitiveScopesFor<T>(moduleType: Type<T>): NgModuleTransitiveScopes { export function transitiveScopesFor<T>(moduleType: Type<T>): NgModuleTransitiveScopes {
if (!isNgModule(moduleType)) { if (!isNgModule(moduleType)) {
throw new Error(`${moduleType.name} does not have an ngModuleDef`); throw new Error(`${moduleType.name} does not have an ngModuleDef`);
} }

View File

@ -17,10 +17,10 @@ import {NG_PIPE_DEF} from './fields';
import {reflectDependencies} from './util'; import {reflectDependencies} from './util';
export function compilePipe(type: Type<any>, meta: Pipe): void { export function compilePipe(type: Type<any>, meta: Pipe): void {
let def: any = null; let ngPipeDef: any = null;
Object.defineProperty(type, NG_PIPE_DEF, { Object.defineProperty(type, NG_PIPE_DEF, {
get: () => { get: () => {
if (def === null) { if (ngPipeDef === null) {
const sourceMapUrl = `ng://${stringify(type)}/ngPipeDef.js`; const sourceMapUrl = `ng://${stringify(type)}/ngPipeDef.js`;
const name = type.name; const name = type.name;
@ -32,9 +32,11 @@ export function compilePipe(type: Type<any>, meta: Pipe): void {
pure: meta.pure !== undefined ? meta.pure : true, pure: meta.pure !== undefined ? meta.pure : true,
}); });
def = jitExpression(res.expression, angularCoreEnv, sourceMapUrl, res.statements); ngPipeDef = jitExpression(res.expression, angularCoreEnv, sourceMapUrl, res.statements);
} }
return def; return ngPipeDef;
} },
// Make the property configurable in dev mode to allow overriding in tests
configurable: !!ngDevMode,
}); });
} }

View File

@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
declare global { declare global {
const ngDevMode: null|NgDevModePerfCounters; const ngDevMode: null|NgDevModePerfCounters;
interface NgDevModePerfCounters { interface NgDevModePerfCounters {
@ -33,8 +32,6 @@ declare global {
} }
} }
declare let global: any; declare let global: any;
// NOTE: The order here matters: Checking window, then global, then self is important. // NOTE: The order here matters: Checking window, then global, then self is important.

View File

@ -663,8 +663,7 @@ export function getCurrentDebugContext(): DebugContext|null {
return _currentView ? new DebugContext_(_currentView, _currentNodeIndex) : null; return _currentView ? new DebugContext_(_currentView, _currentNodeIndex) : null;
} }
export class DebugRendererFactory2 implements RendererFactory2 {
class DebugRendererFactory2 implements RendererFactory2 {
constructor(private delegate: RendererFactory2) {} constructor(private delegate: RendererFactory2) {}
createRenderer(element: any, renderData: RendererType2|null): Renderer2 { createRenderer(element: any, renderData: RendererType2|null): Renderer2 {
@ -690,9 +689,21 @@ class DebugRendererFactory2 implements RendererFactory2 {
} }
} }
export class DebugRenderer2 implements Renderer2 {
class DebugRenderer2 implements Renderer2 {
readonly data: {[key: string]: any}; readonly data: {[key: string]: any};
/**
* Factory function used to create a `DebugContext` when a node is created.
*
* The `DebugContext` allows to retrieve information about the nodes that are useful in tests.
*
* The factory is configurable so that the `DebugRenderer2` could instantiate either a View Engine
* or a Render context.
*/
debugContextFactory: () => DebugContext | null = getCurrentDebugContext;
private get debugContext() { return this.debugContextFactory(); }
constructor(private delegate: Renderer2) { this.data = this.delegate.data; } constructor(private delegate: Renderer2) { this.data = this.delegate.data; }
destroyNode(node: any) { destroyNode(node: any) {
@ -706,7 +717,7 @@ class DebugRenderer2 implements Renderer2 {
createElement(name: string, namespace?: string): any { createElement(name: string, namespace?: string): any {
const el = this.delegate.createElement(name, namespace); const el = this.delegate.createElement(name, namespace);
const debugCtx = getCurrentDebugContext(); const debugCtx = this.debugContext;
if (debugCtx) { if (debugCtx) {
const debugEl = new DebugElement(el, null, debugCtx); const debugEl = new DebugElement(el, null, debugCtx);
debugEl.name = name; debugEl.name = name;
@ -717,7 +728,7 @@ class DebugRenderer2 implements Renderer2 {
createComment(value: string): any { createComment(value: string): any {
const comment = this.delegate.createComment(value); const comment = this.delegate.createComment(value);
const debugCtx = getCurrentDebugContext(); const debugCtx = this.debugContext;
if (debugCtx) { if (debugCtx) {
indexDebugNode(new DebugNode(comment, null, debugCtx)); indexDebugNode(new DebugNode(comment, null, debugCtx));
} }
@ -726,7 +737,7 @@ class DebugRenderer2 implements Renderer2 {
createText(value: string): any { createText(value: string): any {
const text = this.delegate.createText(value); const text = this.delegate.createText(value);
const debugCtx = getCurrentDebugContext(); const debugCtx = this.debugContext;
if (debugCtx) { if (debugCtx) {
indexDebugNode(new DebugNode(text, null, debugCtx)); indexDebugNode(new DebugNode(text, null, debugCtx));
} }
@ -764,7 +775,7 @@ class DebugRenderer2 implements Renderer2 {
selectRootElement(selectorOrNode: string|any): any { selectRootElement(selectorOrNode: string|any): any {
const el = this.delegate.selectRootElement(selectorOrNode); const el = this.delegate.selectRootElement(selectorOrNode);
const debugCtx = getCurrentDebugContext(); const debugCtx = this.debugContext;
if (debugCtx) { if (debugCtx) {
indexDebugNode(new DebugElement(el, null, debugCtx)); indexDebugNode(new DebugElement(el, null, debugCtx));
} }

View File

@ -6,44 +6,33 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ApplicationModule, ApplicationRef, DoCheck, InjectFlags, InjectorType, Input, OnInit, PlatformRef, TestabilityRegistry, Type, defineInjector, inject, ɵE as elementStart, ɵNgModuleDef as NgModuleDef, ɵRenderFlags as RenderFlags, ɵT as text, ɵdefineComponent as defineComponent, ɵe as elementEnd, ɵi1 as interpolation1, ɵt as textBinding} from '@angular/core'; import {ApplicationRef, Component, DoCheck, NgModule, OnInit, TestabilityRegistry, ɵivyEnabled as ivyEnabled} from '@angular/core';
import {getTestBed} from '@angular/core/testing'; import {getTestBed} from '@angular/core/testing';
import {BrowserModule, EVENT_MANAGER_PLUGINS, platformBrowser} from '@angular/platform-browser'; import {BrowserModule} from '@angular/platform-browser';
import {withBody} from '@angular/private/testing'; import {withBody} from '@angular/private/testing';
import {BROWSER_MODULE_PROVIDERS} from '../../platform-browser/src/browser';
import {APPLICATION_MODULE_PROVIDERS} from '../src/application_module';
import {NgModuleFactory} from '../src/render3/ng_module_ref'; import {NgModuleFactory} from '../src/render3/ng_module_ref';
describe('ApplicationRef bootstrap', () => { ivyEnabled && describe('ApplicationRef bootstrap', () => {
class HelloWorldComponent implements OnInit, DoCheck { @Component({
selector: 'hello-world',
template: '<div>Hello {{ name }}</div>',
})
class HelloWorldComponent implements OnInit,
DoCheck {
log: string[] = []; log: string[] = [];
name = 'World'; name = 'World';
static ngComponentDef = defineComponent({
type: HelloWorldComponent,
selectors: [['hello-world']],
factory: () => new HelloWorldComponent(),
template: function(rf: RenderFlags, ctx: HelloWorldComponent): void {
if (rf & RenderFlags.Create) {
elementStart(0, 'div');
text(1);
elementEnd();
}
if (rf & RenderFlags.Update) {
textBinding(1, interpolation1('Hello ', ctx.name, ''));
}
}
});
ngOnInit(): void { this.log.push('OnInit'); } ngOnInit(): void { this.log.push('OnInit'); }
ngDoCheck(): void { this.log.push('DoCheck'); } ngDoCheck(): void { this.log.push('DoCheck'); }
} }
@NgModule({
declarations: [HelloWorldComponent],
bootstrap: [HelloWorldComponent],
imports: [BrowserModule],
})
class MyAppModule { class MyAppModule {
static ngInjectorDef =
defineInjector({factory: () => new MyAppModule(), imports: [BrowserModule]});
static ngModuleDef = defineNgModule({bootstrap: [HelloWorldComponent]});
} }
it('should bootstrap hello world', withBody('<hello-world></hello-world>', async() => { it('should bootstrap hello world', withBody('<hello-world></hello-world>', async() => {
@ -66,29 +55,3 @@ describe('ApplicationRef bootstrap', () => {
})); }));
}); });
/////////////////////////////////////////////////////////
// These go away when Compiler is ready
(BrowserModule as any as InjectorType<BrowserModule>).ngInjectorDef = defineInjector({
factory: function BrowserModule_Factory() {
return new BrowserModule(inject(BrowserModule, InjectFlags.Optional | InjectFlags.SkipSelf));
},
imports: [ApplicationModule],
providers: BROWSER_MODULE_PROVIDERS
});
(ApplicationModule as any as InjectorType<ApplicationModule>).ngInjectorDef = defineInjector({
factory: function ApplicationModule_Factory() {
return new ApplicationModule(inject(ApplicationRef));
},
providers: APPLICATION_MODULE_PROVIDERS
});
export function defineNgModule({bootstrap}: {bootstrap?: Type<any>[]}):
NgModuleDef<any, any, any, any> {
return ({ bootstrap: bootstrap || [], } as any);
}
/////////////////////////////////////////////////////////

View File

@ -0,0 +1,149 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Component, Inject, InjectionToken, NgModule, Optional} from '@angular/core';
import {TestBed, getTestBed} from '@angular/core/testing/src/test_bed';
import {By} from '@angular/platform-browser';
import {expect} from '@angular/platform-browser/testing/src/matchers';
const NAME = new InjectionToken<string>('name');
// -- module: HWModule
@Component({
selector: 'hello-world',
template: '<greeting-cmp></greeting-cmp>',
})
export class HelloWorld {
}
// -- module: Greeting
@Component({
selector: 'greeting-cmp',
template: 'Hello {{ name }}',
})
export class GreetingCmp {
name: string;
constructor(@Inject(NAME) @Optional() name: string) { this.name = name || 'nobody!'; }
}
@NgModule({
declarations: [GreetingCmp],
exports: [GreetingCmp],
})
export class GreetingModule {
}
@Component({selector: 'simple-cmp', template: '<b>simple</b>'})
export class SimpleCmp {
}
@NgModule({
declarations: [HelloWorld, SimpleCmp],
imports: [GreetingModule],
providers: [
{provide: NAME, useValue: 'World!'},
]
})
export class HelloWorldModule {
}
describe('TestBed', () => {
beforeEach(() => {
getTestBed().resetTestingModule();
TestBed.configureTestingModule({imports: [HelloWorldModule]});
});
it('should compile and render a component', () => {
const hello = TestBed.createComponent(HelloWorld);
hello.detectChanges();
expect(hello.nativeElement).toHaveText('Hello World!');
});
it('should give access to the component instance', () => {
const hello = TestBed.createComponent(HelloWorld);
hello.detectChanges();
expect(hello.componentInstance).toBeAnInstanceOf(HelloWorld);
});
it('should give the ability to query by css', () => {
const hello = TestBed.createComponent(HelloWorld);
hello.detectChanges();
const greetingByCss = hello.debugElement.query(By.css('greeting-cmp'));
expect(greetingByCss.nativeElement).toHaveText('Hello World!');
expect(greetingByCss.componentInstance).toBeAnInstanceOf(GreetingCmp);
});
it('should give the ability to trigger the change detection', () => {
const hello = TestBed.createComponent(HelloWorld);
hello.detectChanges();
const greetingByCss = hello.debugElement.query(By.css('greeting-cmp'));
expect(greetingByCss.nativeElement).toHaveText('Hello World!');
greetingByCss.componentInstance.name = 'TestBed!';
hello.detectChanges();
expect(greetingByCss.nativeElement).toHaveText('Hello TestBed!');
});
it('should give access to the node injector', () => {
const hello = TestBed.createComponent(HelloWorld);
hello.detectChanges();
const injector = hello.debugElement.query(By.css('greeting-cmp')).injector;
// from the node injector
const helloInjected = injector.get(HelloWorld);
expect(helloInjected).toBe(hello.componentInstance);
// from the module injector
const nameInjected = injector.get(NAME);
expect(nameInjected).toEqual('World!');
});
it('should give the ability to query by directive', () => {
const hello = TestBed.createComponent(HelloWorld);
hello.detectChanges();
const greetingByDirective = hello.debugElement.query(By.directive(GreetingCmp));
expect(greetingByDirective.componentInstance).toBeAnInstanceOf(GreetingCmp);
});
it('allow to override a template', () => {
// use original template when there is no override
let hello = TestBed.createComponent(HelloWorld);
hello.detectChanges();
expect(hello.nativeElement).toHaveText('Hello World!');
// override the template
getTestBed().resetTestingModule();
TestBed.configureTestingModule({imports: [HelloWorldModule]});
TestBed.overrideComponent(GreetingCmp, {set: {template: `Bonjour {{ name }}`}});
hello = TestBed.createComponent(HelloWorld);
hello.detectChanges();
expect(hello.nativeElement).toHaveText('Bonjour World!');
// restore the original template by calling `.resetTestingModule()`
getTestBed().resetTestingModule();
TestBed.configureTestingModule({imports: [HelloWorldModule]});
hello = TestBed.createComponent(HelloWorld);
hello.detectChanges();
expect(hello.nativeElement).toHaveText('Hello World!');
});
it('allow to override a provider', () => {
TestBed.overrideProvider(NAME, {useValue: 'injected World !'});
const hello = TestBed.createComponent(HelloWorld);
hello.detectChanges();
expect(hello.nativeElement).toHaveText('Hello injected World !');
});
});

View File

@ -11,6 +11,7 @@ const sourcemaps = require('rollup-plugin-sourcemaps');
const globals = { const globals = {
'@angular/core': 'ng.core', '@angular/core': 'ng.core',
'@angular/compiler': 'ng.compiler',
'rxjs': 'rxjs', 'rxjs': 'rxjs',
}; };

View File

@ -0,0 +1,131 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {ɵstringify as stringify} from '@angular/core';
import {MetadataOverride} from './metadata_override';
type StringMap = {
[key: string]: any
};
let _nextReferenceId = 0;
export class MetadataOverrider {
private _references = new Map<any, string>();
/**
* Creates a new instance for the given metadata class
* based on an old instance and overrides.
*/
overrideMetadata<C extends T, T>(
metadataClass: {new (options: T): C;}, oldMetadata: C, override: MetadataOverride<T>): C {
const props: StringMap = {};
if (oldMetadata) {
_valueProps(oldMetadata).forEach((prop) => props[prop] = (<any>oldMetadata)[prop]);
}
if (override.set) {
if (override.remove || override.add) {
throw new Error(`Cannot set and add/remove ${stringify(metadataClass)} at the same time!`);
}
setMetadata(props, override.set);
}
if (override.remove) {
removeMetadata(props, override.remove, this._references);
}
if (override.add) {
addMetadata(props, override.add);
}
return new metadataClass(<any>props);
}
}
function removeMetadata(metadata: StringMap, remove: any, references: Map<any, string>) {
const removeObjects = new Set<string>();
for (const prop in remove) {
const removeValue = remove[prop];
if (removeValue instanceof Array) {
removeValue.forEach(
(value: any) => { removeObjects.add(_propHashKey(prop, value, references)); });
} else {
removeObjects.add(_propHashKey(prop, removeValue, references));
}
}
for (const prop in metadata) {
const propValue = metadata[prop];
if (propValue instanceof Array) {
metadata[prop] = propValue.filter(
(value: any) => !removeObjects.has(_propHashKey(prop, value, references)));
} else {
if (removeObjects.has(_propHashKey(prop, propValue, references))) {
metadata[prop] = undefined;
}
}
}
}
function addMetadata(metadata: StringMap, add: any) {
for (const prop in add) {
const addValue = add[prop];
const propValue = metadata[prop];
if (propValue != null && propValue instanceof Array) {
metadata[prop] = propValue.concat(addValue);
} else {
metadata[prop] = addValue;
}
}
}
function setMetadata(metadata: StringMap, set: any) {
for (const prop in set) {
metadata[prop] = set[prop];
}
}
function _propHashKey(propName: any, propValue: any, references: Map<any, string>): string {
const replacer = (key: any, value: any) => {
if (typeof value === 'function') {
value = _serializeReference(value, references);
}
return value;
};
return `${propName}:${JSON.stringify(propValue, replacer)}`;
}
function _serializeReference(ref: any, references: Map<any, string>): string {
let id = references.get(ref);
if (!id) {
id = `${stringify(ref)}${_nextReferenceId++}`;
references.set(ref, id);
}
return id;
}
function _valueProps(obj: any): string[] {
const props: string[] = [];
// regular public props
Object.keys(obj).forEach((prop) => {
if (!prop.startsWith('_')) {
props.push(prop);
}
});
// getters
let proto = obj;
while (proto = Object.getPrototypeOf(proto)) {
Object.keys(proto).forEach((protoProp) => {
const desc = Object.getOwnPropertyDescriptor(proto, protoProp);
if (!protoProp.startsWith('_') && desc && 'get' in desc) {
props.push(protoProp);
}
});
}
return props;
}

View File

@ -0,0 +1,604 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Component, Directive, Injector, NgModule, Pipe, PlatformRef, Provider, RendererFactory2, SchemaMetadata, Type, ɵNgModuleDefInternal as NgModuleDefInternal, ɵNgModuleTransitiveScopes as NgModuleTransitiveScopes, ɵRender3ComponentFactory as ComponentFactory, ɵRender3DebugRendererFactory2 as Render3DebugRendererFactory2, ɵRender3NgModuleRef as NgModuleRef, ɵWRAP_RENDERER_FACTORY2 as WRAP_RENDERER_FACTORY2, ɵcompileComponent as compileComponent, ɵcompileDirective as compileDirective, ɵcompileNgModuleDefs as compileNgModuleDefs, ɵcompilePipe as compilePipe, ɵpatchComponentDefWithScope as patchComponentDefWithScope, ɵstringify as stringify} from '@angular/core';
import {ComponentFixture} from './component_fixture';
import {MetadataOverride} from './metadata_override';
import {ComponentResolver, DirectiveResolver, NgModuleResolver, PipeResolver, Resolver} from './resolvers';
import {ComponentFixtureAutoDetect, TestComponentRenderer, TestModuleMetadata} from './test_bed_common';
let _nextRootElementId = 0;
/**
* @description
* Configures and initializes environment for unit testing and provides methods for
* creating components and services in unit tests.
*
* TestBed is the primary api for writing unit tests for Angular applications and libraries.
*
* Note: Use `TestBed` in tests. It will be set to either `TestBedViewEngine` or `TestBedRender3`
* according to the compiler used.
*/
export class TestBedRender3 {
/**
* Initialize the environment for testing with a compiler factory, a PlatformRef, and an
* angular module. These are common to every test in the suite.
*
* This may only be called once, to set up the common providers for the current test
* suite on the current platform. If you absolutely need to change the providers,
* first use `resetTestEnvironment`.
*
* Test modules and platforms for individual platforms are available from
* '@angular/<platform_name>/testing'.
*
* @experimental
*/
static initTestEnvironment(
ngModule: Type<any>|Type<any>[], platform: PlatformRef,
aotSummaries?: () => any[]): TestBedRender3 {
const testBed = _getTestBedRender3();
testBed.initTestEnvironment(ngModule, platform, aotSummaries);
return testBed;
}
/**
* Reset the providers for the test injector.
*
* @experimental
*/
static resetTestEnvironment(): void { _getTestBedRender3().resetTestEnvironment(); }
static configureCompiler(config: {providers?: any[]; useJit?: boolean;}): typeof TestBedRender3 {
_getTestBedRender3().configureCompiler(config);
return TestBedRender3;
}
/**
* Allows overriding default providers, directives, pipes, modules of the test injector,
* which are defined in test_injector.js
*/
static configureTestingModule(moduleDef: TestModuleMetadata): typeof TestBedRender3 {
_getTestBedRender3().configureTestingModule(moduleDef);
return TestBedRender3;
}
/**
* Compile components with a `templateUrl` for the test's NgModule.
* It is necessary to call this function
* as fetching urls is asynchronous.
*/
static compileComponents(): Promise<any> { return _getTestBedRender3().compileComponents(); }
static overrideModule(ngModule: Type<any>, override: MetadataOverride<NgModule>):
typeof TestBedRender3 {
_getTestBedRender3().overrideModule(ngModule, override);
return TestBedRender3;
}
static overrideComponent(component: Type<any>, override: MetadataOverride<Component>):
typeof TestBedRender3 {
_getTestBedRender3().overrideComponent(component, override);
return TestBedRender3;
}
static overrideDirective(directive: Type<any>, override: MetadataOverride<Directive>):
typeof TestBedRender3 {
_getTestBedRender3().overrideDirective(directive, override);
return TestBedRender3;
}
static overridePipe(pipe: Type<any>, override: MetadataOverride<Pipe>): typeof TestBedRender3 {
_getTestBedRender3().overridePipe(pipe, override);
return TestBedRender3;
}
static overrideTemplate(component: Type<any>, template: string): typeof TestBedRender3 {
_getTestBedRender3().overrideComponent(component, {set: {template, templateUrl: null !}});
return TestBedRender3;
}
/**
* Overrides the template of the given component, compiling the template
* in the context of the TestingModule.
*
* Note: This works for JIT and AOTed components as well.
*/
static overrideTemplateUsingTestingModule(component: Type<any>, template: string):
typeof TestBedRender3 {
_getTestBedRender3().overrideTemplateUsingTestingModule(component, template);
return TestBedRender3;
}
overrideTemplateUsingTestingModule(component: Type<any>, template: string): void {
throw new Error('Render3TestBed.overrideTemplateUsingTestingModule is not implemented yet');
}
static overrideProvider(token: any, provider: {
useFactory: Function,
deps: any[],
}): typeof TestBedRender3;
static overrideProvider(token: any, provider: {useValue: any;}): typeof TestBedRender3;
static overrideProvider(token: any, provider: {
useFactory?: Function,
useValue?: any,
deps?: any[],
}): typeof TestBedRender3 {
_getTestBedRender3().overrideProvider(token, provider);
return TestBedRender3;
}
/**
* Overwrites all providers for the given token with the given provider definition.
*
* @deprecated as it makes all NgModules lazy. Introduced only for migrating off of it.
*/
static deprecatedOverrideProvider(token: any, provider: {
useFactory: Function,
deps: any[],
}): void;
static deprecatedOverrideProvider(token: any, provider: {useValue: any;}): void;
static deprecatedOverrideProvider(token: any, provider: {
useFactory?: Function,
useValue?: any,
deps?: any[],
}): typeof TestBedRender3 {
throw new Error('Render3TestBed.deprecatedOverrideProvider is not implemented');
}
static get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any {
return _getTestBedRender3().get(token, notFoundValue);
}
static createComponent<T>(component: Type<T>): ComponentFixture<T> {
return _getTestBedRender3().createComponent(component);
}
static resetTestingModule(): typeof TestBedRender3 {
_getTestBedRender3().resetTestingModule();
return TestBedRender3;
}
// Properties
platform: PlatformRef = null !;
ngModule: Type<any>|Type<any>[] = null !;
// metadata overrides
private _moduleOverrides: [Type<any>, MetadataOverride<NgModule>][] = [];
private _componentOverrides: [Type<any>, MetadataOverride<Component>][] = [];
private _directiveOverrides: [Type<any>, MetadataOverride<Directive>][] = [];
private _pipeOverrides: [Type<any>, MetadataOverride<Pipe>][] = [];
private _providerOverrides: Provider[] = [];
private _rootProviderOverrides: Provider[] = [];
// test module configuration
private _providers: Provider[] = [];
private _declarations: Array<Type<any>|any[]|any> = [];
private _imports: Array<Type<any>|any[]|any> = [];
private _schemas: Array<SchemaMetadata|any[]> = [];
private _activeFixtures: ComponentFixture<any>[] = [];
private _moduleRef: NgModuleRef<any> = null !;
private _instantiated: boolean = false;
/**
* Initialize the environment for testing with a compiler factory, a PlatformRef, and an
* angular module. These are common to every test in the suite.
*
* This may only be called once, to set up the common providers for the current test
* suite on the current platform. If you absolutely need to change the providers,
* first use `resetTestEnvironment`.
*
* Test modules and platforms for individual platforms are available from
* '@angular/<platform_name>/testing'.
*
* @experimental
*/
initTestEnvironment(
ngModule: Type<any>|Type<any>[], platform: PlatformRef, aotSummaries?: () => any[]): void {
if (this.platform || this.ngModule) {
throw new Error('Cannot set base providers because it has already been called');
}
this.platform = platform;
this.ngModule = ngModule;
}
/**
* Reset the providers for the test injector.
*
* @experimental
*/
resetTestEnvironment(): void {
this.resetTestingModule();
this.platform = null !;
this.ngModule = null !;
}
resetTestingModule(): void {
// reset metadata overrides
this._moduleOverrides = [];
this._componentOverrides = [];
this._directiveOverrides = [];
this._pipeOverrides = [];
this._providerOverrides = [];
this._rootProviderOverrides = [];
// reset test module config
this._providers = [];
this._declarations = [];
this._imports = [];
this._schemas = [];
this._moduleRef = null !;
this._instantiated = false;
this._activeFixtures.forEach((fixture) => {
try {
fixture.destroy();
} catch (e) {
console.error('Error during cleanup of component', {
component: fixture.componentInstance,
stacktrace: e,
});
}
});
this._activeFixtures = [];
}
configureCompiler(config: {providers?: any[]; useJit?: boolean;}): void {
throw new Error('the Render3 compiler is not configurable !');
}
configureTestingModule(moduleDef: TestModuleMetadata): void {
this._assertNotInstantiated('R3TestBed.configureTestingModule', 'configure the test module');
if (moduleDef.providers) {
this._providers.push(...moduleDef.providers);
}
if (moduleDef.declarations) {
this._declarations.push(...moduleDef.declarations);
}
if (moduleDef.imports) {
this._imports.push(...moduleDef.imports);
}
if (moduleDef.schemas) {
this._schemas.push(...moduleDef.schemas);
}
}
// TODO(vicb): implement
compileComponents(): Promise<any> {
throw new Error('Render3TestBed.compileComponents is not implemented yet');
}
get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any {
this._initIfNeeded();
if (token === TestBedRender3) {
return this;
}
return this._moduleRef.injector.get(token, notFoundValue);
}
execute(tokens: any[], fn: Function, context?: any): any {
this._initIfNeeded();
const params = tokens.map(t => this.get(t));
return fn.apply(context, params);
}
overrideModule(ngModule: Type<any>, override: MetadataOverride<NgModule>): void {
this._assertNotInstantiated('overrideModule', 'override module metadata');
this._moduleOverrides.push([ngModule, override]);
}
overrideComponent(component: Type<any>, override: MetadataOverride<Component>): void {
this._assertNotInstantiated('overrideComponent', 'override component metadata');
this._componentOverrides.push([component, override]);
}
overrideDirective(directive: Type<any>, override: MetadataOverride<Directive>): void {
this._assertNotInstantiated('overrideDirective', 'override directive metadata');
this._directiveOverrides.push([directive, override]);
}
overridePipe(pipe: Type<any>, override: MetadataOverride<Pipe>): void {
this._assertNotInstantiated('overridePipe', 'override pipe metadata');
this._pipeOverrides.push([pipe, override]);
}
/**
* Overwrites all providers for the given token with the given provider definition.
*/
overrideProvider(token: any, provider: {useFactory?: Function, useValue?: any, deps?: any[]}):
void {
const isRoot =
(typeof token !== 'string' && token.ngInjectableDef &&
token.ngInjectableDef.providedIn === 'root');
const overrides = isRoot ? this._rootProviderOverrides : this._providerOverrides;
if (provider.useFactory) {
overrides.push({provide: token, useFactory: provider.useFactory, deps: provider.deps || []});
} else {
overrides.push({provide: token, useValue: provider.useValue});
}
}
/**
* Overwrites all providers for the given token with the given provider definition.
*
* @deprecated as it makes all NgModules lazy. Introduced only for migrating off of it.
*/
deprecatedOverrideProvider(token: any, provider: {
useFactory: Function,
deps: any[],
}): void;
deprecatedOverrideProvider(token: any, provider: {useValue: any;}): void;
deprecatedOverrideProvider(
token: any, provider: {useFactory?: Function, useValue?: any, deps?: any[]}): void {
throw new Error('No implemented in IVY');
}
createComponent<T>(type: Type<T>): ComponentFixture<T> {
this._initIfNeeded();
const testComponentRenderer: TestComponentRenderer = this.get(TestComponentRenderer);
const rootElId = `root${_nextRootElementId++}`;
testComponentRenderer.insertRootElement(rootElId);
const componentDef = (type as any).ngComponentDef;
if (!componentDef) {
throw new Error(
`It looks like '${stringify(type)}' has not been IVY compiled - it has no 'ngComponentDef' field`);
}
const componentFactory = new ComponentFactory(componentDef);
const componentRef =
componentFactory.create(Injector.NULL, [], `#${rootElId}`, this._moduleRef);
const autoDetect: boolean = this.get(ComponentFixtureAutoDetect, false);
const fixture = new ComponentFixture<any>(componentRef, null, autoDetect);
this._activeFixtures.push(fixture);
return fixture;
}
// internal methods
private _initIfNeeded(): void {
if (this._instantiated) {
return;
}
const resolvers = this._getResolvers();
const testModuleType = this._createTestModule();
compileNgModule(testModuleType, resolvers);
const parentInjector = this.platform.injector;
this._moduleRef = new NgModuleRef(testModuleType, parentInjector);
this._instantiated = true;
}
// creates resolvers taking overrides into account
private _getResolvers() {
const module = new NgModuleResolver();
module.setOverrides(this._moduleOverrides);
const component = new ComponentResolver();
component.setOverrides(this._componentOverrides);
const directive = new DirectiveResolver();
directive.setOverrides(this._directiveOverrides);
const pipe = new PipeResolver();
pipe.setOverrides(this._pipeOverrides);
return {module, component, directive, pipe};
}
private _assertNotInstantiated(methodName: string, methodDescription: string) {
if (this._instantiated) {
throw new Error(
`Cannot ${methodDescription} when the test module has already been instantiated. ` +
`Make sure you are not using \`inject\` before \`${methodName}\`.`);
}
}
private _createTestModule(): Type<any> {
const rootProviderOverrides = this._rootProviderOverrides;
const rendererFactoryWrapper = {
provide: WRAP_RENDERER_FACTORY2,
useFactory: () => (rf: RendererFactory2) => new Render3DebugRendererFactory2(rf),
};
@NgModule({
providers: [...rootProviderOverrides, rendererFactoryWrapper],
jit: true,
})
class RootScopeModule {
}
const providers = [...this._providers, ...this._providerOverrides];
const declarations = this._declarations;
const imports = [RootScopeModule, this.ngModule, this._imports];
const schemas = this._schemas;
@NgModule({providers, declarations, imports, schemas, jit: true})
class DynamicTestModule {
}
return DynamicTestModule;
}
}
let testBed: TestBedRender3;
export function _getTestBedRender3(): TestBedRender3 {
return testBed = testBed || new TestBedRender3();
}
// Module compiler
const EMPTY_ARRAY: Type<any>[] = [];
// Resolvers for Angular decorators
type Resolvers = {
module: Resolver<NgModule>,
component: Resolver<Directive>,
directive: Resolver<Component>,
pipe: Resolver<Pipe>,
};
function compileNgModule(moduleType: Type<any>, resolvers: Resolvers): void {
const ngModule = resolvers.module.resolve(moduleType);
if (ngModule === null) {
throw new Error(`${stringify(moduleType)} has not @NgModule annotation`);
}
compileNgModuleDefs(moduleType, ngModule);
const declarations: Type<any>[] = flatten(ngModule.declarations || EMPTY_ARRAY);
const compiledComponents: Type<any>[] = [];
// Compile the components, directives and pipes declared by this module
declarations.forEach(declaration => {
const component = resolvers.component.resolve(declaration);
if (component) {
compileComponent(declaration, component);
compiledComponents.push(declaration);
return;
}
const directive = resolvers.directive.resolve(declaration);
if (directive) {
compileDirective(declaration, directive);
return;
}
const pipe = resolvers.pipe.resolve(declaration);
if (pipe) {
compilePipe(declaration, pipe);
return;
}
});
// Compile transitive modules, components, directives and pipes
const transitiveScope = transitiveScopesFor(moduleType, resolvers);
compiledComponents.forEach(
cmp => patchComponentDefWithScope((cmp as any).ngComponentDef, transitiveScope));
}
/**
* Compute the pair of transitive scopes (compilation scope and exported scope) for a given module.
*
* This operation is memoized and the result is cached on the module's definition. It can be called
* on modules with components that have not fully compiled yet, but the result should not be used
* until they have.
*/
function transitiveScopesFor<T>(
moduleType: Type<T>, resolvers: Resolvers): NgModuleTransitiveScopes {
if (!isNgModule(moduleType)) {
throw new Error(`${moduleType.name} does not have an ngModuleDef`);
}
const def = moduleType.ngModuleDef;
if (def.transitiveCompileScopes !== null) {
return def.transitiveCompileScopes;
}
const scopes: NgModuleTransitiveScopes = {
compilation: {
directives: new Set<any>(),
pipes: new Set<any>(),
},
exported: {
directives: new Set<any>(),
pipes: new Set<any>(),
},
};
def.declarations.forEach(declared => {
const declaredWithDefs = declared as Type<any>& { ngPipeDef?: any; };
if (declaredWithDefs.ngPipeDef !== undefined) {
scopes.compilation.pipes.add(declared);
} else {
scopes.compilation.directives.add(declared);
}
});
def.imports.forEach(<I>(imported: Type<I>) => {
const ngModule = resolvers.module.resolve(imported);
if (ngModule === null) {
throw new Error(`Importing ${imported.name} which does not have an @ngModule`);
} else {
compileNgModule(imported, resolvers);
}
// When this module imports another, the imported module's exported directives and pipes are
// added to the compilation scope of this module.
const importedScope = transitiveScopesFor(imported, resolvers);
importedScope.exported.directives.forEach(entry => scopes.compilation.directives.add(entry));
importedScope.exported.pipes.forEach(entry => scopes.compilation.pipes.add(entry));
});
def.exports.forEach(<E>(exported: Type<E>) => {
const exportedTyped = exported as Type<E>& {
// Components, Directives, NgModules, and Pipes can all be exported.
ngComponentDef?: any;
ngDirectiveDef?: any;
ngModuleDef?: NgModuleDefInternal<E>;
ngPipeDef?: any;
};
// Either the type is a module, a pipe, or a component/directive (which may not have an
// ngComponentDef as it might be compiled asynchronously).
if (isNgModule(exportedTyped)) {
// When this module exports another, the exported module's exported directives and pipes are
// added to both the compilation and exported scopes of this module.
const exportedScope = transitiveScopesFor(exportedTyped, resolvers);
exportedScope.exported.directives.forEach(entry => {
scopes.compilation.directives.add(entry);
scopes.exported.directives.add(entry);
});
exportedScope.exported.pipes.forEach(entry => {
scopes.compilation.pipes.add(entry);
scopes.exported.pipes.add(entry);
});
} else if (exportedTyped.ngPipeDef !== undefined) {
scopes.exported.pipes.add(exportedTyped);
} else {
scopes.exported.directives.add(exportedTyped);
}
});
def.transitiveCompileScopes = scopes;
return scopes;
}
function flatten<T>(values: any[]): T[] {
const out: T[] = [];
values.forEach(value => {
if (Array.isArray(value)) {
out.push(...flatten<T>(value));
} else {
out.push(value);
}
});
return out;
}
function isNgModule<T>(value: Type<T>): value is Type<T>&{ngModuleDef: NgModuleDefInternal<T>} {
return (value as{ngModuleDef?: NgModuleDefInternal<T>}).ngModuleDef !== undefined;
}

View File

@ -0,0 +1,73 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Component, Directive, NgModule, Pipe, Type, ɵReflectionCapabilities as ReflectionCapabilities} from '@angular/core';
import {MetadataOverride} from './metadata_override';
import {MetadataOverrider} from './metadata_overrider';
const reflection = new ReflectionCapabilities();
/**
* Base interface to resolve `@Component`, `@Directive`, `@Pipe` and `@NgModule`.
*/
export interface Resolver<T> { resolve(type: Type<any>): T|null; }
/**
* Allows to override ivy metadata for tests (via the `TestBed`).
*/
abstract class OverrideResolver<T> implements Resolver<T> {
private overrides = new Map<Type<any>, MetadataOverride<T>>();
private resolved = new Map<Type<any>, T|null>();
abstract get type(): any;
setOverrides(overrides: Array<[Type<any>, MetadataOverride<T>]>) {
this.overrides.clear();
overrides.forEach(([type, override]) => this.overrides.set(type, override));
}
getAnnotation(type: Type<any>): T|null {
return reflection.annotations(type).find(a => a instanceof this.type) || null;
}
resolve(type: Type<any>): T|null {
let resolved = this.resolved.get(type) || null;
if (!resolved) {
resolved = this.getAnnotation(type);
if (resolved) {
const override = this.overrides.get(type);
if (override) {
const overrider = new MetadataOverrider();
resolved = overrider.overrideMetadata(this.type, resolved, override);
}
}
this.resolved.set(type, resolved);
}
return resolved;
}
}
export class DirectiveResolver extends OverrideResolver<Directive> {
get type() { return Directive; }
}
export class ComponentResolver extends OverrideResolver<Component> {
get type() { return Component; }
}
export class PipeResolver extends OverrideResolver<Pipe> {
get type() { return Pipe; }
}
export class NgModuleResolver extends OverrideResolver<NgModule> {
get type() { return NgModule; }
}

View File

@ -6,56 +6,31 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ApplicationInitStatus, CompilerOptions, Component, Directive, InjectionToken, Injector, NgModule, NgModuleFactory, NgModuleRef, NgZone, Optional, Pipe, PlatformRef, Provider, SchemaMetadata, SkipSelf, StaticProvider, Type, ɵAPP_ROOT as APP_ROOT, ɵDepFlags as DepFlags, ɵNodeFlags as NodeFlags, ɵclearOverrides as clearOverrides, ɵgetComponentViewDefinitionFactory as getComponentViewDefinitionFactory, ɵoverrideComponentView as overrideComponentView, ɵoverrideProvider as overrideProvider, ɵstringify as stringify} from '@angular/core'; import {ApplicationInitStatus, CompilerOptions, Component, Directive, Injector, NgModule, NgModuleFactory, NgModuleRef, NgZone, Optional, Pipe, PlatformRef, Provider, SchemaMetadata, SkipSelf, StaticProvider, Type, ɵAPP_ROOT as APP_ROOT, ɵDepFlags as DepFlags, ɵNodeFlags as NodeFlags, ɵclearOverrides as clearOverrides, ɵgetComponentViewDefinitionFactory as getComponentViewDefinitionFactory, ɵivyEnabled as ivyEnabled, ɵoverrideComponentView as overrideComponentView, ɵoverrideProvider as overrideProvider, ɵstringify as stringify} from '@angular/core';
import {AsyncTestCompleter} from './async_test_completer'; import {AsyncTestCompleter} from './async_test_completer';
import {ComponentFixture} from './component_fixture'; import {ComponentFixture} from './component_fixture';
import {MetadataOverride} from './metadata_override'; import {MetadataOverride} from './metadata_override';
import {TestBedRender3, _getTestBedRender3} from './r3_test_bed';
import {ComponentFixtureAutoDetect, ComponentFixtureNoNgZone, TestComponentRenderer, TestModuleMetadata} from './test_bed_common';
import {TestingCompiler, TestingCompilerFactory} from './test_compiler'; import {TestingCompiler, TestingCompilerFactory} from './test_compiler';
const UNDEFINED = new Object(); const UNDEFINED = new Object();
/**
* An abstract class for inserting the root test component element in a platform independent way.
*
* @experimental
*/
export class TestComponentRenderer {
insertRootElement(rootElementId: string) {}
}
let _nextRootElementId = 0; let _nextRootElementId = 0;
/**
* @experimental
*/
export const ComponentFixtureAutoDetect =
new InjectionToken<boolean[]>('ComponentFixtureAutoDetect');
/**
* @experimental
*/
export const ComponentFixtureNoNgZone = new InjectionToken<boolean[]>('ComponentFixtureNoNgZone');
/**
* @experimental
*/
export type TestModuleMetadata = {
providers?: any[],
declarations?: any[],
imports?: any[],
schemas?: Array<SchemaMetadata|any[]>,
aotSummaries?: () => any[],
};
/** /**
* @description * @description
* Configures and initializes environment for unit testing and provides methods for * Configures and initializes environment for unit testing and provides methods for
* creating components and services in unit tests. * creating components and services in unit tests.
* *
* TestBed is the primary api for writing unit tests for Angular applications and libraries. * `TestBed` is the primary api for writing unit tests for Angular applications and libraries.
*
* Note: Use `TestBed` in tests. It will be set to either `TestBedViewEngine` or `TestBedRender3`
* according to the compiler used.
*/ */
export class TestBed implements Injector { export class TestBedViewEngine implements Injector {
/** /**
* Initialize the environment for testing with a compiler factory, a PlatformRef, and an * Initialize the environment for testing with a compiler factory, a PlatformRef, and an
* angular module. These are common to every test in the suite. * angular module. These are common to every test in the suite.
@ -70,8 +45,9 @@ export class TestBed implements Injector {
* @experimental * @experimental
*/ */
static initTestEnvironment( static initTestEnvironment(
ngModule: Type<any>|Type<any>[], platform: PlatformRef, aotSummaries?: () => any[]): TestBed { ngModule: Type<any>|Type<any>[], platform: PlatformRef,
const testBed = getTestBed(); aotSummaries?: () => any[]): TestBedViewEngine {
const testBed = _getTestBedViewEngine();
testBed.initTestEnvironment(ngModule, platform, aotSummaries); testBed.initTestEnvironment(ngModule, platform, aotSummaries);
return testBed; return testBed;
} }
@ -81,10 +57,10 @@ export class TestBed implements Injector {
* *
* @experimental * @experimental
*/ */
static resetTestEnvironment() { getTestBed().resetTestEnvironment(); } static resetTestEnvironment(): void { _getTestBedViewEngine().resetTestEnvironment(); }
static resetTestingModule(): typeof TestBed { static resetTestingModule(): typeof TestBed {
getTestBed().resetTestingModule(); _getTestBedViewEngine().resetTestingModule();
return TestBed; return TestBed;
} }
@ -93,7 +69,7 @@ export class TestBed implements Injector {
* which are defined in test_injector.js * which are defined in test_injector.js
*/ */
static configureCompiler(config: {providers?: any[]; useJit?: boolean;}): typeof TestBed { static configureCompiler(config: {providers?: any[]; useJit?: boolean;}): typeof TestBed {
getTestBed().configureCompiler(config); _getTestBedViewEngine().configureCompiler(config);
return TestBed; return TestBed;
} }
@ -102,7 +78,7 @@ export class TestBed implements Injector {
* which are defined in test_injector.js * which are defined in test_injector.js
*/ */
static configureTestingModule(moduleDef: TestModuleMetadata): typeof TestBed { static configureTestingModule(moduleDef: TestModuleMetadata): typeof TestBed {
getTestBed().configureTestingModule(moduleDef); _getTestBedViewEngine().configureTestingModule(moduleDef);
return TestBed; return TestBed;
} }
@ -114,29 +90,29 @@ export class TestBed implements Injector {
static compileComponents(): Promise<any> { return getTestBed().compileComponents(); } static compileComponents(): Promise<any> { return getTestBed().compileComponents(); }
static overrideModule(ngModule: Type<any>, override: MetadataOverride<NgModule>): typeof TestBed { static overrideModule(ngModule: Type<any>, override: MetadataOverride<NgModule>): typeof TestBed {
getTestBed().overrideModule(ngModule, override); _getTestBedViewEngine().overrideModule(ngModule, override);
return TestBed; return TestBed;
} }
static overrideComponent(component: Type<any>, override: MetadataOverride<Component>): static overrideComponent(component: Type<any>, override: MetadataOverride<Component>):
typeof TestBed { typeof TestBed {
getTestBed().overrideComponent(component, override); _getTestBedViewEngine().overrideComponent(component, override);
return TestBed; return TestBed;
} }
static overrideDirective(directive: Type<any>, override: MetadataOverride<Directive>): static overrideDirective(directive: Type<any>, override: MetadataOverride<Directive>):
typeof TestBed { typeof TestBed {
getTestBed().overrideDirective(directive, override); _getTestBedViewEngine().overrideDirective(directive, override);
return TestBed; return TestBed;
} }
static overridePipe(pipe: Type<any>, override: MetadataOverride<Pipe>): typeof TestBed { static overridePipe(pipe: Type<any>, override: MetadataOverride<Pipe>): typeof TestBed {
getTestBed().overridePipe(pipe, override); _getTestBedViewEngine().overridePipe(pipe, override);
return TestBed; return TestBed;
} }
static overrideTemplate(component: Type<any>, template: string): typeof TestBed { static overrideTemplate(component: Type<any>, template: string): typeof TestBed {
getTestBed().overrideComponent(component, {set: {template, templateUrl: null !}}); _getTestBedViewEngine().overrideComponent(component, {set: {template, templateUrl: null !}});
return TestBed; return TestBed;
} }
@ -148,11 +124,10 @@ export class TestBed implements Injector {
*/ */
static overrideTemplateUsingTestingModule(component: Type<any>, template: string): static overrideTemplateUsingTestingModule(component: Type<any>, template: string):
typeof TestBed { typeof TestBed {
getTestBed().overrideTemplateUsingTestingModule(component, template); _getTestBedViewEngine().overrideTemplateUsingTestingModule(component, template);
return TestBed; return TestBed;
} }
/** /**
* Overwrites all providers for the given token with the given provider definition. * Overwrites all providers for the given token with the given provider definition.
* *
@ -168,7 +143,7 @@ export class TestBed implements Injector {
useValue?: any, useValue?: any,
deps?: any[], deps?: any[],
}): typeof TestBed { }): typeof TestBed {
getTestBed().overrideProvider(token, provider as any); _getTestBedViewEngine().overrideProvider(token, provider as any);
return TestBed; return TestBed;
} }
@ -187,16 +162,16 @@ export class TestBed implements Injector {
useValue?: any, useValue?: any,
deps?: any[], deps?: any[],
}): typeof TestBed { }): typeof TestBed {
getTestBed().deprecatedOverrideProvider(token, provider as any); _getTestBedViewEngine().deprecatedOverrideProvider(token, provider as any);
return TestBed; return TestBed;
} }
static get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND) { static get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND) {
return getTestBed().get(token, notFoundValue); return _getTestBedViewEngine().get(token, notFoundValue);
} }
static createComponent<T>(component: Type<T>): ComponentFixture<T> { static createComponent<T>(component: Type<T>): ComponentFixture<T> {
return getTestBed().createComponent(component); return _getTestBedViewEngine().createComponent(component);
} }
private _instantiated: boolean = false; private _instantiated: boolean = false;
@ -243,7 +218,7 @@ export class TestBed implements Injector {
* @experimental * @experimental
*/ */
initTestEnvironment( initTestEnvironment(
ngModule: Type<any>|Type<any>[], platform: PlatformRef, aotSummaries?: () => any[]) { ngModule: Type<any>|Type<any>[], platform: PlatformRef, aotSummaries?: () => any[]): void {
if (this.platform || this.ngModule) { if (this.platform || this.ngModule) {
throw new Error('Cannot set base providers because it has already been called'); throw new Error('Cannot set base providers because it has already been called');
} }
@ -259,14 +234,14 @@ export class TestBed implements Injector {
* *
* @experimental * @experimental
*/ */
resetTestEnvironment() { resetTestEnvironment(): void {
this.resetTestingModule(); this.resetTestingModule();
this.platform = null !; this.platform = null !;
this.ngModule = null !; this.ngModule = null !;
this._testEnvAotSummaries = () => []; this._testEnvAotSummaries = () => [];
} }
resetTestingModule() { resetTestingModule(): void {
clearOverrides(); clearOverrides();
this._aotSummaries = []; this._aotSummaries = [];
this._templateOverrides = []; this._templateOverrides = [];
@ -300,12 +275,12 @@ export class TestBed implements Injector {
this._activeFixtures = []; this._activeFixtures = [];
} }
configureCompiler(config: {providers?: any[], useJit?: boolean}) { configureCompiler(config: {providers?: any[], useJit?: boolean}): void {
this._assertNotInstantiated('TestBed.configureCompiler', 'configure the compiler'); this._assertNotInstantiated('TestBed.configureCompiler', 'configure the compiler');
this._compilerOptions.push(config); this._compilerOptions.push(config);
} }
configureTestingModule(moduleDef: TestModuleMetadata) { configureTestingModule(moduleDef: TestModuleMetadata): void {
this._assertNotInstantiated('TestBed.configureTestingModule', 'configure the test module'); this._assertNotInstantiated('TestBed.configureTestingModule', 'configure the test module');
if (moduleDef.providers) { if (moduleDef.providers) {
this._providers.push(...moduleDef.providers); this._providers.push(...moduleDef.providers);
@ -336,7 +311,7 @@ export class TestBed implements Injector {
}); });
} }
private _initIfNeeded() { private _initIfNeeded(): void {
if (this._instantiated) { if (this._instantiated) {
return; return;
} }
@ -425,7 +400,7 @@ export class TestBed implements Injector {
} }
} }
get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND) { get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any {
this._initIfNeeded(); this._initIfNeeded();
if (token === TestBed) { if (token === TestBed) {
return this; return this;
@ -574,13 +549,32 @@ export class TestBed implements Injector {
} }
} }
let _testBed: TestBed = null !; /**
* @description
* Configures and initializes environment for unit testing and provides methods for
* creating components and services in unit tests.
*
* `TestBed` is the primary api for writing unit tests for Angular applications and libraries.
*
* Note: Use `TestBed` in tests. It will be set to either `TestBedViewEngine` or `TestBedRender3`
* according to the compiler used.
*/
export const TestBed = ivyEnabled ? TestBedRender3 : TestBedViewEngine;
/** /**
* Returns a singleton of the applicable `TestBed`.
*
* It will be either an instance of `TestBedViewEngine` or `TestBedRender3`.
*
* @experimental * @experimental
*/ */
export function getTestBed(): TestBed { export const getTestBed: () => TestBedRender3 | TestBedViewEngine =
return _testBed = _testBed || new TestBed(); ivyEnabled ? _getTestBedRender3 : _getTestBedViewEngine;
let testBed: TestBedViewEngine;
function _getTestBedViewEngine(): TestBedViewEngine {
return testBed = testBed || new TestBedViewEngine();
} }
/** /**

View File

@ -0,0 +1,40 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {InjectionToken, SchemaMetadata} from '@angular/core';
/**
* An abstract class for inserting the root test component element in a platform independent way.
*
* @experimental
*/
export class TestComponentRenderer {
insertRootElement(rootElementId: string) {}
}
/**
* @experimental
*/
export const ComponentFixtureAutoDetect =
new InjectionToken<boolean[]>('ComponentFixtureAutoDetect');
/**
* @experimental
*/
export const ComponentFixtureNoNgZone = new InjectionToken<boolean[]>('ComponentFixtureNoNgZone');
/**
* @experimental
*/
export type TestModuleMetadata = {
providers?: any[],
declarations?: any[],
imports?: any[],
schemas?: Array<SchemaMetadata|any[]>,
aotSummaries?: () => any[],
};

View File

@ -16,6 +16,9 @@ export * from './async';
export * from './component_fixture'; export * from './component_fixture';
export * from './fake_async'; export * from './fake_async';
export * from './test_bed'; export * from './test_bed';
export * from './test_bed_common';
export * from './r3_test_bed';
export * from './before_each'; export * from './before_each';
export * from './metadata_override'; export * from './metadata_override';
export * from './metadata_overrider';
export * from './private_export_testing'; export * from './private_export_testing';

View File

@ -1,27 +1,31 @@
{ {
"extends": "../tsconfig-build.json", "extends": "../tsconfig-build.json",
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "baseUrl": ".",
"rootDir": "../", "rootDir": "../",
"paths": { "paths": {
"rxjs/*": ["../../../node_modules/rxjs/*"], "rxjs/*": [
"@angular/core": ["../../../dist/packages/core"] "../../../node_modules/rxjs/*"
],
"@angular/core": [
"../../../dist/packages/core"
],
"@angular/compiler": [
"../../../dist/packages/compiler"
],
}, },
"outDir": "../../../dist/packages/core" "outDir": "../../../dist/packages/core"
}, },
"files": [ "files": [
"public_api.ts", "public_api.ts",
"../../../node_modules/zone.js/dist/zone.js.d.ts", "../../../node_modules/zone.js/dist/zone.js.d.ts",
"../../system.d.ts", "../../system.d.ts",
"../../types.d.ts" "../../types.d.ts"
], ],
"angularCompilerOptions": { "angularCompilerOptions": {
"strictMetadataEmit": false, "strictMetadataEmit": false,
"skipTemplateCodegen": true, "skipTemplateCodegen": true,
"flatModuleOutFile": "testing.js", "flatModuleOutFile": "testing.js",
"flatModuleId": "@angular/core/testing" "flatModuleId": "@angular/core/testing"
} }
} }

View File

@ -1,22 +1,26 @@
{ {
"extends": "../tsconfig-build.json", "extends": "../tsconfig-build.json",
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "baseUrl": ".",
"rootDir": ".", "rootDir": ".",
"paths": { "paths": {
"rxjs/*": ["../../node_modules/rxjs/*"], "rxjs/*": [
"@angular/core": ["."] "../../node_modules/rxjs/*"
],
"@angular/core": [
"."
],
"@angular/compiler": [
"../../dist/packages/compiler"
]
}, },
"outDir": "../../dist/packages/core" "outDir": "../../dist/packages/core"
}, },
"files": [ "files": [
"public_api.ts", "public_api.ts",
"../../node_modules/zone.js/dist/zone.js.d.ts", "../../node_modules/zone.js/dist/zone.js.d.ts",
"../system.d.ts" "../system.d.ts"
], ],
"angularCompilerOptions": { "angularCompilerOptions": {
"annotateForClosureCompiler": true, "annotateForClosureCompiler": true,
"strictMetadataEmit": false, "strictMetadataEmit": false,
@ -24,4 +28,4 @@
"flatModuleOutFile": "core.js", "flatModuleOutFile": "core.js",
"flatModuleId": "@angular/core" "flatModuleId": "@angular/core"
} }
} }

View File

@ -18,6 +18,7 @@ jasmine_node_test(
bootstrap = ["angular/tools/testing/init_node_spec.js"], bootstrap = ["angular/tools/testing/init_node_spec.js"],
data = [ data = [
"//packages/common:npm_package", "//packages/common:npm_package",
"//packages/compiler:npm_package",
"//packages/core:npm_package", "//packages/core:npm_package",
"//packages/forms:npm_package", "//packages/forms:npm_package",
], ],

View File

@ -6,126 +6,5 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ɵstringify as stringify} from '@angular/core'; // `MetadataOverrider` has been moved to core to allow using it from the render3 TestBed
import {MetadataOverride} from '@angular/core/testing'; export {MetadataOverrider} from '@angular/core/testing';
type StringMap = {
[key: string]: any
};
let _nextReferenceId = 0;
export class MetadataOverrider {
private _references = new Map<any, string>();
/**
* Creates a new instance for the given metadata class
* based on an old instance and overrides.
*/
overrideMetadata<C extends T, T>(
metadataClass: {new (options: T): C;}, oldMetadata: C, override: MetadataOverride<T>): C {
const props: StringMap = {};
if (oldMetadata) {
_valueProps(oldMetadata).forEach((prop) => props[prop] = (<any>oldMetadata)[prop]);
}
if (override.set) {
if (override.remove || override.add) {
throw new Error(`Cannot set and add/remove ${stringify(metadataClass)} at the same time!`);
}
setMetadata(props, override.set);
}
if (override.remove) {
removeMetadata(props, override.remove, this._references);
}
if (override.add) {
addMetadata(props, override.add);
}
return new metadataClass(<any>props);
}
}
function removeMetadata(metadata: StringMap, remove: any, references: Map<any, string>) {
const removeObjects = new Set<string>();
for (const prop in remove) {
const removeValue = remove[prop];
if (removeValue instanceof Array) {
removeValue.forEach(
(value: any) => { removeObjects.add(_propHashKey(prop, value, references)); });
} else {
removeObjects.add(_propHashKey(prop, removeValue, references));
}
}
for (const prop in metadata) {
const propValue = metadata[prop];
if (propValue instanceof Array) {
metadata[prop] = propValue.filter(
(value: any) => !removeObjects.has(_propHashKey(prop, value, references)));
} else {
if (removeObjects.has(_propHashKey(prop, propValue, references))) {
metadata[prop] = undefined;
}
}
}
}
function addMetadata(metadata: StringMap, add: any) {
for (const prop in add) {
const addValue = add[prop];
const propValue = metadata[prop];
if (propValue != null && propValue instanceof Array) {
metadata[prop] = propValue.concat(addValue);
} else {
metadata[prop] = addValue;
}
}
}
function setMetadata(metadata: StringMap, set: any) {
for (const prop in set) {
metadata[prop] = set[prop];
}
}
function _propHashKey(propName: any, propValue: any, references: Map<any, string>): string {
const replacer = (key: any, value: any) => {
if (typeof value === 'function') {
value = _serializeReference(value, references);
}
return value;
};
return `${propName}:${JSON.stringify(propValue, replacer)}`;
}
function _serializeReference(ref: any, references: Map<any, string>): string {
let id = references.get(ref);
if (!id) {
id = `${stringify(ref)}${_nextReferenceId++}`;
references.set(ref, id);
}
return id;
}
function _valueProps(obj: any): string[] {
const props: string[] = [];
// regular public props
Object.keys(obj).forEach((prop) => {
if (!prop.startsWith('_')) {
props.push(prop);
}
});
// getters
let proto = obj;
while (proto = Object.getPrototypeOf(proto)) {
Object.keys(proto).forEach((protoProp) => {
const desc = Object.getOwnPropertyDescriptor(proto, protoProp);
if (!protoProp.startsWith('_') && desc && 'get' in desc) {
props.push(protoProp);
}
});
}
return props;
}

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {NgModule, createPlatformFactory} from '@angular/core'; import {NgModule, PlatformRef, StaticProvider, createPlatformFactory} from '@angular/core';
import {TestComponentRenderer} from '@angular/core/testing'; import {TestComponentRenderer} from '@angular/core/testing';
import {ɵINTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS as INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS} from '@angular/platform-browser-dynamic'; import {ɵINTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS as INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS} from '@angular/platform-browser-dynamic';
import {BrowserTestingModule} from '@angular/platform-browser/testing'; import {BrowserTestingModule} from '@angular/platform-browser/testing';

View File

@ -56,7 +56,7 @@ if [[ ${TRAVIS} &&
travisFoldStart "yarn-install.aio" travisFoldStart "yarn-install.aio"
( (
# HACK (don't submit with this): Build Angular # HACK (don't submit with this): Build Angular
./build.sh --packages=core,elements --examples=false ./build.sh --packages=compiler,core,elements --examples=false
cd ${PROJECT_ROOT}/aio cd ${PROJECT_ROOT}/aio
yarn install --frozen-lockfile --non-interactive yarn install --frozen-lockfile --non-interactive

View File

@ -1,3 +1,5 @@
export declare function _getTestBedRender3(): TestBedRender3;
export declare function async(fn: Function): (done: any) => any; export declare function async(fn: Function): (done: any) => any;
export declare class ComponentFixture<T> { export declare class ComponentFixture<T> {
@ -37,7 +39,7 @@ export declare function flush(maxTurns?: number): number;
export declare function flushMicrotasks(): void; export declare function flushMicrotasks(): void;
/** @experimental */ /** @experimental */
export declare function getTestBed(): TestBed; export declare const getTestBed: () => TestBedRender3 | TestBedViewEngine;
export declare function inject(tokens: any[], fn: Function): () => any; export declare function inject(tokens: any[], fn: Function): () => any;
@ -54,10 +56,83 @@ export declare type MetadataOverride<T> = {
set?: Partial<T>; set?: Partial<T>;
}; };
export declare class MetadataOverrider {
overrideMetadata<C extends T, T>(metadataClass: {
new (options: T): C;
}, oldMetadata: C, override: MetadataOverride<T>): C;
}
/** @experimental */ /** @experimental */
export declare function resetFakeAsyncZone(): void; export declare function resetFakeAsyncZone(): void;
export declare class TestBed implements Injector { export declare const TestBed: typeof TestBedRender3 | typeof TestBedViewEngine;
export declare class TestBedRender3 {
ngModule: Type<any> | Type<any>[];
platform: PlatformRef;
compileComponents(): Promise<any>;
configureCompiler(config: {
providers?: any[];
useJit?: boolean;
}): void;
configureTestingModule(moduleDef: TestModuleMetadata): void;
createComponent<T>(type: Type<T>): ComponentFixture<T>;
deprecatedOverrideProvider(token: any, provider: {
useValue: any;
}): void;
/** @deprecated */ deprecatedOverrideProvider(token: any, provider: {
useFactory: Function;
deps: any[];
}): void;
execute(tokens: any[], fn: Function, context?: any): any;
get(token: any, notFoundValue?: any): any;
/** @experimental */ initTestEnvironment(ngModule: Type<any> | Type<any>[], platform: PlatformRef, aotSummaries?: () => any[]): void;
overrideComponent(component: Type<any>, override: MetadataOverride<Component>): void;
overrideDirective(directive: Type<any>, override: MetadataOverride<Directive>): void;
overrideModule(ngModule: Type<any>, override: MetadataOverride<NgModule>): void;
overridePipe(pipe: Type<any>, override: MetadataOverride<Pipe>): void;
overrideProvider(token: any, provider: {
useFactory?: Function;
useValue?: any;
deps?: any[];
}): void;
overrideTemplateUsingTestingModule(component: Type<any>, template: string): void;
/** @experimental */ resetTestEnvironment(): void;
resetTestingModule(): void;
static compileComponents(): Promise<any>;
static configureCompiler(config: {
providers?: any[];
useJit?: boolean;
}): typeof TestBedRender3;
static configureTestingModule(moduleDef: TestModuleMetadata): typeof TestBedRender3;
static createComponent<T>(component: Type<T>): ComponentFixture<T>;
static deprecatedOverrideProvider(token: any, provider: {
useValue: any;
}): void;
/** @deprecated */ static deprecatedOverrideProvider(token: any, provider: {
useFactory: Function;
deps: any[];
}): void;
static get(token: any, notFoundValue?: any): any;
/** @experimental */ static initTestEnvironment(ngModule: Type<any> | Type<any>[], platform: PlatformRef, aotSummaries?: () => any[]): TestBedRender3;
static overrideComponent(component: Type<any>, override: MetadataOverride<Component>): typeof TestBedRender3;
static overrideDirective(directive: Type<any>, override: MetadataOverride<Directive>): typeof TestBedRender3;
static overrideModule(ngModule: Type<any>, override: MetadataOverride<NgModule>): typeof TestBedRender3;
static overridePipe(pipe: Type<any>, override: MetadataOverride<Pipe>): typeof TestBedRender3;
static overrideProvider(token: any, provider: {
useFactory: Function;
deps: any[];
}): typeof TestBedRender3;
static overrideProvider(token: any, provider: {
useValue: any;
}): typeof TestBedRender3;
static overrideTemplate(component: Type<any>, template: string): typeof TestBedRender3;
static overrideTemplateUsingTestingModule(component: Type<any>, template: string): typeof TestBedRender3;
/** @experimental */ static resetTestEnvironment(): void;
static resetTestingModule(): typeof TestBedRender3;
}
export declare class TestBedViewEngine implements Injector {
ngModule: Type<any> | Type<any>[]; ngModule: Type<any> | Type<any>[];
platform: PlatformRef; platform: PlatformRef;
compileComponents(): Promise<any>; compileComponents(): Promise<any>;
@ -106,7 +181,7 @@ export declare class TestBed implements Injector {
deps: any[]; deps: any[];
}): void; }): void;
static get(token: any, notFoundValue?: any): any; static get(token: any, notFoundValue?: any): any;
/** @experimental */ static initTestEnvironment(ngModule: Type<any> | Type<any>[], platform: PlatformRef, aotSummaries?: () => any[]): TestBed; /** @experimental */ static initTestEnvironment(ngModule: Type<any> | Type<any>[], platform: PlatformRef, aotSummaries?: () => any[]): TestBedViewEngine;
static overrideComponent(component: Type<any>, override: MetadataOverride<Component>): typeof TestBed; static overrideComponent(component: Type<any>, override: MetadataOverride<Component>): typeof TestBed;
static overrideDirective(directive: Type<any>, override: MetadataOverride<Directive>): typeof TestBed; static overrideDirective(directive: Type<any>, override: MetadataOverride<Directive>): typeof TestBed;
static overrideModule(ngModule: Type<any>, override: MetadataOverride<NgModule>): typeof TestBed; static overrideModule(ngModule: Type<any>, override: MetadataOverride<NgModule>): typeof TestBed;