From 0c8adbc4ec3d26858412ca94239de0b12897c5c1 Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Thu, 9 Jan 2020 17:50:29 -0800 Subject: [PATCH] refactor(core): remove unused embedded view instructions (#34715) This commit performs a cleanup and removes unused embedded view instructions and corresponding tests. PR Close #34715 --- goldens/circular-deps/packages.json | 48 +- goldens/public-api/core/core.d.ts | 10 - .../src/tree/render3_function/BUILD.bazel | 46 - .../src/tree/render3_function/index.html | 42 - .../src/tree/render3_function/index.ts | 90 -- .../compiler/src/render3/r3_identifiers.ts | 2 - .../core/src/core_render3_private_export.ts | 5 - packages/core/src/render3/assert.ts | 5 - packages/core/src/render3/index.ts | 10 +- packages/core/src/render3/instructions/all.ts | 1 - .../src/render3/instructions/container.ts | 133 +-- .../src/render3/instructions/embedded_view.ts | 145 --- .../core/src/render3/interfaces/container.ts | 16 +- packages/core/src/render3/jit/environment.ts | 5 - .../core/test/acceptance/listener_spec.ts | 107 ++ .../core/test/acceptance/providers_spec.ts | 107 ++ packages/core/test/acceptance/query_spec.ts | 109 +- .../test/acceptance/renderer_factory_spec.ts | 77 ++ packages/core/test/render3/basic_perf.ts | 79 -- packages/core/test/render3/component_spec.ts | 355 +------ .../core/test/render3/control_flow_spec.ts | 961 ------------------ .../core/test/render3/integration_spec.ts | 152 +-- packages/core/test/render3/lifecycle_spec.ts | 120 --- packages/core/test/render3/listeners_spec.ts | 527 +--------- packages/core/test/render3/outputs_spec.ts | 117 --- packages/core/test/render3/providers_spec.ts | 301 +----- .../core/test/render3/pure_function_spec.ts | 89 -- packages/core/test/render3/query_spec.ts | 114 +-- .../test/render3/renderer_factory_spec.ts | 102 +- .../test/render3/view_container_ref_spec.ts | 114 +-- 30 files changed, 447 insertions(+), 3542 deletions(-) delete mode 100644 modules/benchmarks/src/tree/render3_function/BUILD.bazel delete mode 100644 modules/benchmarks/src/tree/render3_function/index.html delete mode 100644 modules/benchmarks/src/tree/render3_function/index.ts delete mode 100644 packages/core/src/render3/instructions/embedded_view.ts delete mode 100644 packages/core/test/render3/basic_perf.ts delete mode 100644 packages/core/test/render3/control_flow_spec.ts delete mode 100644 packages/core/test/render3/lifecycle_spec.ts delete mode 100644 packages/core/test/render3/outputs_spec.ts delete mode 100644 packages/core/test/render3/pure_function_spec.ts diff --git a/goldens/circular-deps/packages.json b/goldens/circular-deps/packages.json index 97146ccabc..c34ed8bc11 100644 --- a/goldens/circular-deps/packages.json +++ b/goldens/circular-deps/packages.json @@ -1601,16 +1601,6 @@ "packages/core/src/render3/component.ts", "packages/core/src/render3/styling/static_styling.ts" ], - [ - "packages/core/src/render3/assert.ts", - "packages/core/src/render3/interfaces/type_checks.ts", - "packages/core/src/render3/index.ts", - "packages/core/src/render3/component.ts", - "packages/core/src/render3/util/global_utils.ts", - "packages/core/src/render3/util/change_detection_utils.ts", - "packages/core/src/render3/instructions/all.ts", - "packages/core/src/render3/instructions/container.ts" - ], [ "packages/core/src/render3/assert.ts", "packages/core/src/render3/interfaces/type_checks.ts", @@ -1631,16 +1621,6 @@ "packages/core/src/render3/instructions/all.ts", "packages/core/src/render3/instructions/element.ts" ], - [ - "packages/core/src/render3/assert.ts", - "packages/core/src/render3/interfaces/type_checks.ts", - "packages/core/src/render3/index.ts", - "packages/core/src/render3/component.ts", - "packages/core/src/render3/util/global_utils.ts", - "packages/core/src/render3/util/change_detection_utils.ts", - "packages/core/src/render3/instructions/all.ts", - "packages/core/src/render3/instructions/embedded_view.ts" - ], [ "packages/core/src/render3/assert.ts", "packages/core/src/render3/interfaces/type_checks.ts", @@ -1662,6 +1642,16 @@ "packages/core/src/render3/instructions/styling.ts", "packages/core/src/render3/styling/style_binding_list.ts" ], + [ + "packages/core/src/render3/assert.ts", + "packages/core/src/render3/interfaces/type_checks.ts", + "packages/core/src/render3/index.ts", + "packages/core/src/render3/component.ts", + "packages/core/src/render3/util/global_utils.ts", + "packages/core/src/render3/util/change_detection_utils.ts", + "packages/core/src/render3/instructions/all.ts", + "packages/core/src/render3/instructions/template.ts" + ], [ "packages/core/src/render3/assert.ts", "packages/core/src/render3/interfaces/type_checks.ts", @@ -1715,15 +1705,6 @@ "packages/core/src/render3/interfaces/type_checks.ts", "packages/core/src/render3/index.ts" ], - [ - "packages/core/src/render3/component.ts", - "packages/core/src/render3/util/global_utils.ts", - "packages/core/src/render3/util/change_detection_utils.ts", - "packages/core/src/render3/instructions/all.ts", - "packages/core/src/render3/instructions/container.ts", - "packages/core/src/render3/interfaces/type_checks.ts", - "packages/core/src/render3/index.ts" - ], [ "packages/core/src/render3/component.ts", "packages/core/src/render3/util/global_utils.ts", @@ -1751,6 +1732,15 @@ "packages/core/src/render3/interfaces/type_checks.ts", "packages/core/src/render3/index.ts" ], + [ + "packages/core/src/render3/component.ts", + "packages/core/src/render3/util/global_utils.ts", + "packages/core/src/render3/util/change_detection_utils.ts", + "packages/core/src/render3/instructions/all.ts", + "packages/core/src/render3/instructions/template.ts", + "packages/core/src/render3/interfaces/type_checks.ts", + "packages/core/src/render3/index.ts" + ], [ "packages/core/src/render3/di_setup.ts", "packages/core/src/render3/interfaces/type_checks.ts", diff --git a/goldens/public-api/core/core.d.ts b/goldens/public-api/core/core.d.ts index 915a1f657e..ad94e2c480 100644 --- a/goldens/public-api/core/core.d.ts +++ b/goldens/public-api/core/core.d.ts @@ -719,12 +719,6 @@ export declare type ɵɵComponentDefWithMeta any, useCapture?: boolean, eventTargetResolver?: GlobalTargetResolver): typeof ɵɵcomponentHostSyntheticListener; -export declare function ɵɵcontainer(index: number): void; - -export declare function ɵɵcontainerRefreshEnd(): void; - -export declare function ɵɵcontainerRefreshStart(index: number): void; - export declare function ɵɵcontentQuery(directiveIndex: number, predicate: Type | string[], descend: boolean, read?: any): void; export declare function ɵɵCopyDefinitionFeature(definition: ɵDirectiveDef | ɵComponentDef): void; @@ -828,10 +822,6 @@ export declare function ɵɵelementEnd(): void; export declare function ɵɵelementStart(index: number, name: string, attrsIndex?: number | null, localRefsIndex?: number): void; -export declare function ɵɵembeddedViewEnd(): void; - -export declare function ɵɵembeddedViewStart(viewBlockId: number, decls: number, vars: number): ɵRenderFlags; - export declare function ɵɵenableBindings(): void; export declare type ɵɵFactoryDef = () => T; diff --git a/modules/benchmarks/src/tree/render3_function/BUILD.bazel b/modules/benchmarks/src/tree/render3_function/BUILD.bazel deleted file mode 100644 index da9ec7ecb2..0000000000 --- a/modules/benchmarks/src/tree/render3_function/BUILD.bazel +++ /dev/null @@ -1,46 +0,0 @@ -load("//tools:defaults.bzl", "ts_devserver", "ts_library") -load("//modules/benchmarks:benchmark_test.bzl", "benchmark_test") -load("//modules/benchmarks:e2e_test.bzl", "e2e_test") - -package(default_visibility = ["//modules/benchmarks:__subpackages__"]) - -ts_library( - name = "render3_function_lib", - srcs = glob(["**/*.ts"]), - deps = [ - "//modules/benchmarks/src:util_lib", - "//modules/benchmarks/src/tree:util_lib", - "//modules/benchmarks/src/tree/render3:tree_lib", - "//packages/core", - ], -) - -ts_devserver( - name = "devserver", - entry_module = "angular/modules/benchmarks/src/tree/render3_function/index", - port = 4200, - scripts = [ - "@npm//:node_modules/tslib/tslib.js", - "//tools/rxjs:rxjs_umd_modules", - ], - static_files = ["index.html"], - deps = [":render3_function_lib"], -) - -benchmark_test( - name = "perf", - server = ":devserver", - deps = [ - "//modules/benchmarks/src/tree:detect_changes_perf_tests_lib", - "//modules/benchmarks/src/tree:perf_tests_lib", - ], -) - -e2e_test( - name = "e2e", - server = ":devserver", - deps = [ - "//modules/benchmarks/src/tree:detect_changes_e2e_tests_lib", - "//modules/benchmarks/src/tree:e2e_tests_lib", - ], -) diff --git a/modules/benchmarks/src/tree/render3_function/index.html b/modules/benchmarks/src/tree/render3_function/index.html deleted file mode 100644 index a78a937281..0000000000 --- a/modules/benchmarks/src/tree/render3_function/index.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - -

Params

-
- Depth: - -
- -
- -

Render3 Tree Benchmark with functions

-

- - - - - - -

- -
- Change detection runs: -
-
- -
- - - - - - - diff --git a/modules/benchmarks/src/tree/render3_function/index.ts b/modules/benchmarks/src/tree/render3_function/index.ts deleted file mode 100644 index 8afd56308d..0000000000 --- a/modules/benchmarks/src/tree/render3_function/index.ts +++ /dev/null @@ -1,90 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {ɵrenderComponent as renderComponent, ɵRenderFlags, ɵɵadvance, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵdefineComponent, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵstyleProp, ɵɵtext, ɵɵtextInterpolate1} from '@angular/core'; - -import {bindAction, profile} from '../../util'; -import {createDom, destroyDom, detectChanges} from '../render3/tree'; -import {emptyTree, TreeNode} from '../util'; - -function noop() {} - -export class TreeFunction { - data: TreeNode = emptyTree; - - /** @nocollapse */ - static ɵfac = () => new TreeFunction; - - /** @nocollapse */ - static ɵcmp = ɵɵdefineComponent({ - type: TreeFunction, - selectors: [['tree']], - decls: 5, - vars: 2, - template: - function(rf: ɵRenderFlags, ctx: TreeFunction) { - // bit of a hack - TreeTpl(rf, ctx.data); - }, - inputs: {data: 'data'} - }); -} - -const TreeFunctionCmpDef = TreeFunction.ɵcmp as {decls: number, vars: number}; -export function TreeTpl(rf: ɵRenderFlags, ctx: TreeNode) { - if (rf & ɵRenderFlags.Create) { - ɵɵelementStart(0, 'tree'); - { - ɵɵelementStart(1, 'span'); - { ɵɵtext(2); } - ɵɵelementEnd(); - ɵɵcontainer(3); - ɵɵcontainer(4); - } - ɵɵelementEnd(); - } - if (rf & ɵRenderFlags.Update) { - ɵɵadvance(1); - ɵɵstyleProp('background-color', ctx.depth % 2 ? '' : 'grey'); - ɵɵadvance(1); - ɵɵtextInterpolate1(' ', ctx.value, ' '); - ɵɵcontainerRefreshStart(3); - { - if (ctx.left != null) { - let rf0 = ɵɵembeddedViewStart(0, 5, 2); - { TreeTpl(rf0, ctx.left); } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - ɵɵcontainerRefreshStart(4); - { - if (ctx.right != null) { - let rf0 = ɵɵembeddedViewStart(0, TreeFunctionCmpDef.decls, TreeFunctionCmpDef.vars); - { TreeTpl(rf0, ctx.right); } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } -} - -let component: TreeFunction; -if (typeof window !== 'undefined') { - component = renderComponent(TreeFunction); - bindAction('#createDom', () => createDom(component as any)); - bindAction('#destroyDom', () => destroyDom(component as any)); - bindAction('#detectChanges', () => detectChanges(component as any)); - bindAction( - '#detectChangesProfile', - profile(() => detectChanges(component as any), noop, 'detectChanges')); - bindAction('#updateDomProfile', profile(() => createDom(component as any), noop, 'update')); - bindAction( - '#createDomProfile', - profile(() => createDom(component as any), () => destroyDom(component as any), 'create')); -} diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index 4d27564bd2..8e072e6488 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -132,8 +132,6 @@ export class Identifiers { static stylePropInterpolateV: o.ExternalReference = {name: 'ɵɵstylePropInterpolateV', moduleName: CORE}; - static containerCreate: o.ExternalReference = {name: 'ɵɵcontainer', moduleName: CORE}; - static nextContext: o.ExternalReference = {name: 'ɵɵnextContext', moduleName: CORE}; static templateCreate: o.ExternalReference = {name: 'ɵɵtemplate', moduleName: CORE}; diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index 0f1c8e77d5..5854e66094 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -125,9 +125,6 @@ export { ɵɵclassProp, ɵɵComponentDefWithMeta, ɵɵcomponentHostSyntheticListener, - ɵɵcontainer, - ɵɵcontainerRefreshEnd, - ɵɵcontainerRefreshStart, ɵɵcontentQuery, ɵɵCopyDefinitionFeature, ɵɵdefineComponent, @@ -143,8 +140,6 @@ export { ɵɵelementContainerStart, ɵɵelementEnd, ɵɵelementStart, - ɵɵembeddedViewEnd, - ɵɵembeddedViewStart, ɵɵenableBindings, ɵɵFactoryDef, ɵɵgetCurrentView, diff --git a/packages/core/src/render3/assert.ts b/packages/core/src/render3/assert.ts index 56c74663d5..d643d31414 100644 --- a/packages/core/src/render3/assert.ts +++ b/packages/core/src/render3/assert.ts @@ -57,11 +57,6 @@ export function assertDataNext(lView: LView, index: number, arr?: any[]) { arr.length, index, `index ${index} expected to be at the end of arr (length ${arr.length})`); } -export function assertLContainerOrUndefined(value: any): asserts value is LContainer|undefined| - null { - value && assertEqual(isLContainer(value), true, 'Expecting LContainer or undefined or null'); -} - export function assertLContainer(value: any): asserts value is LContainer { assertDefined(value, 'LContainer must be defined'); assertEqual(isLContainer(value), true, 'Expecting LContainer'); diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index cea4adee29..34854caa2f 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -50,23 +50,15 @@ export { ɵɵclassProp, ɵɵcomponentHostSyntheticListener, - ɵɵcontainer, - ɵɵcontainerRefreshEnd, - ɵɵcontainerRefreshStart, - ɵɵdirectiveInject, ɵɵelement, + ɵɵelementContainer, ɵɵelementContainerEnd, - ɵɵelementContainerStart, ɵɵelementEnd, - ɵɵelementStart, - ɵɵembeddedViewEnd, - - ɵɵembeddedViewStart, ɵɵgetCurrentView, ɵɵhostProperty, diff --git a/packages/core/src/render3/instructions/all.ts b/packages/core/src/render3/instructions/all.ts index 6e37202846..ec72f45dc2 100644 --- a/packages/core/src/render3/instructions/all.ts +++ b/packages/core/src/render3/instructions/all.ts @@ -33,7 +33,6 @@ export * from './storage'; export * from './di'; export * from './element'; export * from './element_container'; -export * from './embedded_view'; export * from './get_current_view'; export * from './listener'; export * from './namespace'; diff --git a/packages/core/src/render3/instructions/container.ts b/packages/core/src/render3/instructions/container.ts index 4685b665c2..6e9273e2f7 100644 --- a/packages/core/src/render3/instructions/container.ts +++ b/packages/core/src/render3/instructions/container.ts @@ -5,46 +5,20 @@ * 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 {assertDataInRange, assertEqual} from '../../util/assert'; -import {assertFirstCreatePass, assertHasParent} from '../assert'; +import {assertFirstCreatePass} from '../assert'; import {attachPatchData} from '../context_discovery'; -import {executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags, registerPostOrderHooks} from '../hooks'; -import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/container'; +import {registerPostOrderHooks} from '../hooks'; import {ComponentTemplate} from '../interfaces/definition'; -import {LocalRefExtractor, TAttributes, TContainerNode, TNode, TNodeType, TViewNode} from '../interfaces/node'; +import {LocalRefExtractor, TAttributes, TContainerNode, TNodeType, TViewNode} from '../interfaces/node'; import {isDirectiveHost} from '../interfaces/type_checks'; -import {FLAGS, HEADER_OFFSET, InitPhaseState, LView, LViewFlags, RENDERER, T_HOST, TView, TViewType} from '../interfaces/view'; -import {assertNodeType} from '../node_assert'; -import {appendChild, removeView} from '../node_manipulation'; -import {getBindingIndex, getCheckNoChangesMode, getIsParent, getLView, getPreviousOrParentTNode, getTView, setIsNotParent, setPreviousOrParentTNode} from '../state'; -import {getConstant, getLContainerActiveIndex, load} from '../util/view_utils'; +import {HEADER_OFFSET, LView, RENDERER, T_HOST, TView, TViewType} from '../interfaces/view'; +import {appendChild} from '../node_manipulation'; +import {getLView, getTView, setPreviousOrParentTNode} from '../state'; +import {getConstant} from '../util/view_utils'; import {addToViewTree, createDirectivesInstances, createLContainer, createTNode, createTView, getOrCreateTNode, resolveDirectives, saveResolvedLocalsInData} from './shared'; - -/** - * Creates an LContainer for inline views, e.g. - * - * % if (showing) { - *
- * % } - * - * @param index The index of the container in the data array - * - * @codeGenApi - */ -export function ɵɵcontainer(index: number): void { - const lView = getLView(); - const tView = getTView(); - const tNode = containerInternal(tView, lView, index, null, null); - - if (tView.firstCreatePass) { - tNode.tViews = []; - } - setIsNotParent(); -} - function templateFirstCreatePass( index: number, tView: TView, lView: LView, templateFn: ComponentTemplate|null, decls: number, vars: number, tagName?: string|null, attrsIndex?: number|null, @@ -121,97 +95,4 @@ export function ɵɵtemplate( if (localRefsIndex != null) { saveResolvedLocalsInData(lView, tNode, localRefExtractor); } -} - -/** - * Sets a container up to receive views. - * - * @param index The index of the container in the data array - * - * @codeGenApi - */ -export function ɵɵcontainerRefreshStart(index: number): void { - const lView = getLView(); - const tView = getTView(); - let previousOrParentTNode = load(tView.data, index) as TNode; - ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.Container); - setPreviousOrParentTNode(previousOrParentTNode, true); - - lView[index + HEADER_OFFSET][ACTIVE_INDEX] = 0; - - // We need to execute init hooks here so ngOnInit hooks are called in top level views - // before they are called in embedded views (for backwards compatibility). - if (!getCheckNoChangesMode()) { - const hooksInitPhaseCompleted = - (lView[FLAGS] & LViewFlags.InitPhaseStateMask) === InitPhaseState.InitPhaseCompleted; - if (hooksInitPhaseCompleted) { - const preOrderCheckHooks = tView.preOrderCheckHooks; - if (preOrderCheckHooks !== null) { - executeCheckHooks(lView, preOrderCheckHooks, null); - } - } else { - const preOrderHooks = tView.preOrderHooks; - if (preOrderHooks !== null) { - executeInitAndCheckHooks(lView, preOrderHooks, InitPhaseState.OnInitHooksToBeRun, null); - } - incrementInitPhaseFlags(lView, InitPhaseState.OnInitHooksToBeRun); - } - } -} - -/** - * Marks the end of the LContainer. - * - * Marking the end of LContainer is the time when to child views get inserted or removed. - * - * @codeGenApi - */ -export function ɵɵcontainerRefreshEnd(): void { - let previousOrParentTNode = getPreviousOrParentTNode(); - if (getIsParent()) { - setIsNotParent(); - } else { - ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.View); - ngDevMode && assertHasParent(previousOrParentTNode); - previousOrParentTNode = previousOrParentTNode.parent!; - setPreviousOrParentTNode(previousOrParentTNode, false); - } - - ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.Container); - - const lContainer: LContainer = getLView()[previousOrParentTNode.index]; - const nextIndex = getLContainerActiveIndex(lContainer); - - // remove extra views at the end of the container - while (nextIndex < lContainer.length - CONTAINER_HEADER_OFFSET) { - removeView(lContainer, nextIndex); - } -} - -function containerInternal( - tView: TView, lView: LView, nodeIndex: number, tagName: string|null, - attrs: TAttributes|null): TContainerNode { - ngDevMode && - assertEqual( - getBindingIndex(), tView.bindingStartIndex, - 'container nodes should be created before any bindings'); - - const adjustedIndex = nodeIndex + HEADER_OFFSET; - ngDevMode && assertDataInRange(lView, nodeIndex + HEADER_OFFSET); - ngDevMode && ngDevMode.rendererCreateComment++; - const comment = lView[adjustedIndex] = - lView[RENDERER].createComment(ngDevMode ? 'container' : ''); - const tNode = - getOrCreateTNode(tView, lView[T_HOST], nodeIndex, TNodeType.Container, tagName, attrs); - const lContainer = lView[adjustedIndex] = createLContainer(comment, lView, comment, tNode); - - appendChild(tView, lView, comment, tNode); - attachPatchData(comment, lView); - - // Containers are added to the current view tree instead of their embedded views - // because views can be removed and re-inserted. - addToViewTree(lView, lContainer); - - ngDevMode && assertNodeType(getPreviousOrParentTNode(), TNodeType.Container); - return tNode; } \ No newline at end of file diff --git a/packages/core/src/render3/instructions/embedded_view.ts b/packages/core/src/render3/instructions/embedded_view.ts deleted file mode 100644 index 971d04214f..0000000000 --- a/packages/core/src/render3/instructions/embedded_view.ts +++ /dev/null @@ -1,145 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {assertDefined, assertEqual} from '../../util/assert'; -import {assertLContainerOrUndefined} from '../assert'; -import {ACTIVE_INDEX, ActiveIndexFlag, CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/container'; -import {RenderFlags} from '../interfaces/definition'; -import {TContainerNode, TNodeType} from '../interfaces/node'; -import {CONTEXT, LView, LViewFlags, PARENT, T_HOST, TVIEW, TView, TViewType} from '../interfaces/view'; -import {assertNodeType} from '../node_assert'; -import {insertView, removeView} from '../node_manipulation'; -import {enterView, getIsParent, getLView, getPreviousOrParentTNode, getTView, leaveView, setIsParent, setPreviousOrParentTNode} from '../state'; -import {getLContainerActiveIndex, isCreationMode} from '../util/view_utils'; - -import {assignTViewNodeToLView, createLView, createTView, refreshView, renderView} from './shared'; - - - -/** - * Marks the start of an embedded view. - * - * @param viewBlockId The ID of this view - * @return boolean Whether or not this view is in creation mode - * - * @codeGenApi - */ -export function ɵɵembeddedViewStart(viewBlockId: number, decls: number, vars: number): RenderFlags { - const lView = getLView(); - const previousOrParentTNode = getPreviousOrParentTNode(); - // The previous node can be a view node if we are processing an inline for loop - const containerTNode = previousOrParentTNode.type === TNodeType.View ? - previousOrParentTNode.parent! : - previousOrParentTNode; - const lContainer = lView[containerTNode.index] as LContainer; - - ngDevMode && assertNodeType(containerTNode, TNodeType.Container); - let viewToRender = scanForView(lContainer, getLContainerActiveIndex(lContainer), viewBlockId); - - if (viewToRender) { - setIsParent(); - enterView(viewToRender, viewToRender[TVIEW].node); - } else { - // When we create a new LView, we always reset the state of the instructions. - viewToRender = createLView( - lView, getOrCreateEmbeddedTView(viewBlockId, decls, vars, containerTNode as TContainerNode), - null, LViewFlags.CheckAlways, null, null); - - const tParentNode = getIsParent() ? previousOrParentTNode : - previousOrParentTNode && previousOrParentTNode.parent; - assignTViewNodeToLView(viewToRender[TVIEW], tParentNode, viewBlockId, viewToRender); - enterView(viewToRender, viewToRender[TVIEW].node); - } - if (lContainer) { - if (isCreationMode(viewToRender)) { - // it is a new view, insert it into collection of views for a given container - insertView( - viewToRender[TVIEW], viewToRender, lContainer, getLContainerActiveIndex(lContainer)); - } - lContainer[ACTIVE_INDEX] += ActiveIndexFlag.INCREMENT; - } - return isCreationMode(viewToRender) ? RenderFlags.Create | RenderFlags.Update : - RenderFlags.Update; -} - -/** - * Initialize the TView (e.g. static data) for the active embedded view. - * - * Each embedded view block must create or retrieve its own TView. Otherwise, the embedded view's - * static data for a particular node would overwrite the static data for a node in the view above - * it with the same index (since it's in the same template). - * - * @param viewIndex The index of the TView in TNode.tViews - * @param decls The number of nodes, local refs, and pipes in this template - * @param vars The number of bindings and pure function bindings in this template - * @param container The parent container in which to look for the view's static data - * @returns TView - */ -function getOrCreateEmbeddedTView( - viewIndex: number, decls: number, vars: number, parent: TContainerNode): TView { - const tView = getLView()[TVIEW]; - ngDevMode && assertNodeType(parent, TNodeType.Container); - const containerTViews = parent.tViews as TView[]; - ngDevMode && assertDefined(containerTViews, 'TView expected'); - ngDevMode && assertEqual(Array.isArray(containerTViews), true, 'TViews should be in an array'); - if (viewIndex >= containerTViews.length || containerTViews[viewIndex] == null) { - containerTViews[viewIndex] = createTView( - TViewType.Embedded, viewIndex, null, decls, vars, tView.directiveRegistry, - tView.pipeRegistry, null, null, tView.consts); - } - return containerTViews[viewIndex]; -} - - -/** - * Looks for a view with a given view block id inside a provided LContainer. - * Removes views that need to be deleted in the process. - * - * @param lContainer to search for views - * @param startIdx starting index in the views array to search from - * @param viewBlockId exact view block id to look for - */ -function scanForView(lContainer: LContainer, startIdx: number, viewBlockId: number): LView|null { - for (let i = startIdx + CONTAINER_HEADER_OFFSET; i < lContainer.length; i++) { - const viewAtPositionId = lContainer[i][TVIEW].id; - if (viewAtPositionId === viewBlockId) { - return lContainer[i]; - } else if (viewAtPositionId < viewBlockId) { - // found a view that should not be at this position - remove - removeView(lContainer, i - CONTAINER_HEADER_OFFSET); - } else { - // found a view with id greater than the one we are searching for - // which means that required view doesn't exist and can't be found at - // later positions in the views array - stop the searchdef.cont here - break; - } - } - return null; -} - -/** - * Marks the end of an embedded view. - * - * @codeGenApi - */ -export function ɵɵembeddedViewEnd(): void { - const lView = getLView(); - const tView = getTView(); - const viewHost = lView[T_HOST]; - const context = lView[CONTEXT]; - - if (isCreationMode(lView)) { - renderView(tView, lView, context); // creation mode pass - } - refreshView(tView, lView, tView.template, context); // update mode pass - - const lContainer = lView[PARENT] as LContainer; - ngDevMode && assertLContainerOrUndefined(lContainer); - leaveView(); - setPreviousOrParentTNode(viewHost!, false); -} diff --git a/packages/core/src/render3/interfaces/container.ts b/packages/core/src/render3/interfaces/container.ts index b4d1e07369..532657940e 100644 --- a/packages/core/src/render3/interfaces/container.ts +++ b/packages/core/src/render3/interfaces/container.ts @@ -20,6 +20,7 @@ import {HOST, LView, NEXT, PARENT, T_HOST, TRANSPLANTED_VIEWS_TO_REFRESH} from ' * `LContainer`. */ export const TYPE = 1; + /** * Below are constants for LContainer indices to help us look up LContainer members * without having to remember the specific indices. @@ -48,9 +49,7 @@ export const CONTAINER_HEADER_OFFSET = 10; /** - * Used to track: - * - Inline embedded views (see: `ɵɵembeddedViewStart`) - * - Transplanted `LView`s (see: `LView[DECLARATION_COMPONENT_VIEW])` + * Used to track Transplanted `LView`s (see: `LView[DECLARATION_COMPONENT_VIEW])` */ export const enum ActiveIndexFlag { /** @@ -72,13 +71,6 @@ export const enum ActiveIndexFlag { * Number of bits to shift inline embedded views counter to make space for other flags. */ SHIFT = 1, - - - /** - * When incrementing the active index for inline embedded views, the amount to increment to leave - * space for other flags. - */ - INCREMENT = 1 << SHIFT, } /** @@ -111,10 +103,6 @@ export interface LContainer extends Array { * it is set to null to identify this scenario, as indices are "absolute" in that case, * i.e. provided directly by the user of the ViewContainerRef API. * - * This is used by `ɵɵembeddedViewStart` to track which `LView` is currently active. - * Because `ɵɵembeddedViewStart` is not generated by the compiler this feature is essentially - * unused. - * * The lowest bit signals that this `LContainer` has transplanted views which need to be change * detected as part of the declaration CD. (See `LView[DECLARATION_COMPONENT_VIEW]`) */ diff --git a/packages/core/src/render3/jit/environment.ts b/packages/core/src/render3/jit/environment.ts index bcc4172397..11ae78c872 100644 --- a/packages/core/src/render3/jit/environment.ts +++ b/packages/core/src/render3/jit/environment.ts @@ -49,10 +49,7 @@ export const angularCoreEnv: {[name: string]: Function} = 'ɵɵProvidersFeature': r3.ɵɵProvidersFeature, 'ɵɵCopyDefinitionFeature': r3.ɵɵCopyDefinitionFeature, 'ɵɵInheritDefinitionFeature': r3.ɵɵInheritDefinitionFeature, - 'ɵɵcontainer': r3.ɵɵcontainer, 'ɵɵnextContext': r3.ɵɵnextContext, - 'ɵɵcontainerRefreshStart': r3.ɵɵcontainerRefreshStart, - 'ɵɵcontainerRefreshEnd': r3.ɵɵcontainerRefreshEnd, 'ɵɵnamespaceHTML': r3.ɵɵnamespaceHTML, 'ɵɵnamespaceMathML': r3.ɵɵnamespaceMathML, 'ɵɵnamespaceSVG': r3.ɵɵnamespaceSVG, @@ -152,8 +149,6 @@ export const angularCoreEnv: {[name: string]: Function} = 'ɵɵtextInterpolate7': r3.ɵɵtextInterpolate7, 'ɵɵtextInterpolate8': r3.ɵɵtextInterpolate8, 'ɵɵtextInterpolateV': r3.ɵɵtextInterpolateV, - 'ɵɵembeddedViewStart': r3.ɵɵembeddedViewStart, - 'ɵɵembeddedViewEnd': r3.ɵɵembeddedViewEnd, 'ɵɵi18n': r3.ɵɵi18n, 'ɵɵi18nAttributes': r3.ɵɵi18nAttributes, 'ɵɵi18nExp': r3.ɵɵi18nExp, diff --git a/packages/core/test/acceptance/listener_spec.ts b/packages/core/test/acceptance/listener_spec.ts index 7bb045800c..0667a16b58 100644 --- a/packages/core/test/acceptance/listener_spec.ts +++ b/packages/core/test/acceptance/listener_spec.ts @@ -289,4 +289,111 @@ describe('event listeners', () => { expect(log).toEqual(['dirA.click', 'dirB.click', 'component.click']); }); }); + + it('should destroy listeners when view is removed', () => { + @Component({ + selector: 'my-comp', + template: ` + + `, + }) + class MyComp { + visible = true; + counter = 0; + count() { + this.counter++; + } + } + + TestBed.configureTestingModule({declarations: [MyComp]}); + const fixture = TestBed.createComponent(MyComp); + fixture.detectChanges(); + + const comp = fixture.componentInstance; + + const button = fixture.nativeElement.querySelector('button'); + button.click(); + expect(comp.counter).toBe(1); + + comp.visible = false; + fixture.detectChanges(); + + button.click(); + expect(comp.counter).toBe(1); + }); + + it('should destroy listeners when views generated using *ngFor are removed', () => { + let counter = 0; + @Component({ + selector: 'my-comp', + template: ` + + `, + }) + class MyComp { + buttons = [1, 2]; + count() { + counter++; + } + } + + TestBed.configureTestingModule({declarations: [MyComp]}); + const fixture = TestBed.createComponent(MyComp); + fixture.detectChanges(); + + const comp = fixture.componentInstance; + + const buttons = fixture.nativeElement.querySelectorAll('button'); + buttons[0].click(); + buttons[1].click(); + expect(counter).toBe(2); + + comp.buttons = []; + fixture.detectChanges(); + + buttons[0].click(); + buttons[1].click(); + expect(counter).toBe(2); + }); + + it('should destroy listeners when nested view is removed', () => { + @Component({ + selector: 'my-comp', + template: ` + + Click to submit a form: + + + `, + }) + class MyComp { + isSectionVisible = true; + isButtonVisible = true; + counter = 0; + count() { + this.counter++; + } + } + + TestBed.configureTestingModule({declarations: [MyComp]}); + const fixture = TestBed.createComponent(MyComp); + fixture.detectChanges(); + + const comp = fixture.componentInstance; + const button = fixture.nativeElement.querySelector('button'); + button.click(); + expect(comp.counter).toBe(1); + + comp.isButtonVisible = false; + fixture.detectChanges(); + + button.click(); + expect(comp.counter).toBe(1); + + comp.isSectionVisible = false; + fixture.detectChanges(); + + button.click(); + expect(comp.counter).toBe(1); + }); }); diff --git a/packages/core/test/acceptance/providers_spec.ts b/packages/core/test/acceptance/providers_spec.ts index 90a40decf6..649c5351b4 100644 --- a/packages/core/test/acceptance/providers_spec.ts +++ b/packages/core/test/acceptance/providers_spec.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ +import {CommonModule} from '@angular/common'; import {Component, Directive, forwardRef, Inject, Injectable, InjectionToken, Injector, NgModule, Optional} from '@angular/core'; import {async, inject, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; @@ -506,6 +507,47 @@ describe('providers', () => { expect(destroyCalls).toBe(0); }); + + it('should call ngOnDestroy if host component is destroyed', () => { + const logs: string[] = []; + + @Injectable() + class InjectableWithDestroyHookToken { + ngOnDestroy() { + logs.push('OnDestroy Token'); + } + } + + @Component({ + selector: 'comp-with-provider', + template: '', + providers: [InjectableWithDestroyHookToken], + }) + class CompWithProvider { + constructor(token: InjectableWithDestroyHookToken) {} + } + + @Component({ + selector: 'app', + template: '', + }) + class App { + condition = true; + } + + TestBed.configureTestingModule({ + declarations: [App, CompWithProvider], + imports: [CommonModule], + }); + + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + fixture.componentInstance.condition = false; + fixture.detectChanges(); + + expect(logs).toEqual(['OnDestroy Token']); + }); }); describe('components and directives', () => { @@ -669,4 +711,69 @@ describe('providers', () => { expect(injector.get(MyService).value).toBe(null); }); }); + + describe('view providers', () => { + it('should have access to viewProviders within the same component', () => { + @Component({ + selector: 'comp', + template: '{{s}}-{{n}}', + providers: [ + {provide: Number, useValue: 1, multi: true}, + ], + viewProviders: [ + {provide: String, useValue: 'bar'}, + {provide: Number, useValue: 2, multi: true}, + ] + }) + class Comp { + constructor(private s: String, private n: Number) {} + } + + TestBed.configureTestingModule({declarations: [Comp]}); + + const fixture = TestBed.createComponent(Comp); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent).toBe('bar-1,2'); + }); + + it('should have access to viewProviders of the host component', () => { + @Component({ + selector: 'repeated', + template: '[{{s}}-{{n}}]', + }) + class Repeated { + constructor(private s: String, private n: Number) {} + } + + @Component({ + template: ` +
+ + + +
+ `, + providers: [ + {provide: Number, useValue: 1, multi: true}, + ], + viewProviders: [ + {provide: String, useValue: 'foo'}, + {provide: Number, useValue: 2, multi: true}, + ], + }) + class ComponentWithProviders { + items = [1, 2, 3]; + } + + TestBed.configureTestingModule({ + declarations: [ComponentWithProviders, Repeated], + imports: [CommonModule], + }); + + const fixture = TestBed.createComponent(ComponentWithProviders); + fixture.detectChanges(); + expect(fixture.nativeElement.textContent).toBe('[foo-1,2][foo-1,2][foo-1,2]'); + }); + }); }); diff --git a/packages/core/test/acceptance/query_spec.ts b/packages/core/test/acceptance/query_spec.ts index c36f6027fb..fc01ed9883 100644 --- a/packages/core/test/acceptance/query_spec.ts +++ b/packages/core/test/acceptance/query_spec.ts @@ -7,7 +7,7 @@ */ import {CommonModule} from '@angular/common'; -import {AfterViewInit, Component, ContentChild, ContentChildren, Directive, ElementRef, forwardRef, Input, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef, ViewRef} from '@angular/core'; +import {AfterViewInit, Component, ContentChild, ContentChildren, Directive, ElementRef, EventEmitter, forwardRef, Input, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef, ViewRef} from '@angular/core'; import {TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {expect} from '@angular/platform-browser/testing/src/matchers'; @@ -300,6 +300,51 @@ describe('query logic', () => { fixture.detectChanges(); expect(fixture.componentInstance.viewChildAvailableInAfterViewInit).toBe(true); }); + + it('should destroy QueryList when the containing view is destroyed', () => { + let queryInstance: QueryList; + + @Component({ + selector: 'comp-with-view-query', + template: '
Content
', + }) + class ComponentWithViewQuery { + @ViewChildren('foo') + set foo(value: any) { + queryInstance = value; + } + get foo() { + return queryInstance; + } + } + + @Component({ + selector: 'root', + template: ` + + + + ` + }) + class Root { + condition = true; + } + + TestBed.configureTestingModule({ + declarations: [Root, ComponentWithViewQuery], + imports: [CommonModule], + }); + + const fixture = TestBed.createComponent(Root); + fixture.detectChanges(); + + expect((queryInstance!.changes as EventEmitter).closed).toBeFalsy(); + + fixture.componentInstance.condition = false; + fixture.detectChanges(); + + expect((queryInstance!.changes as EventEmitter).closed).toBeTruthy(); + }); }); describe('content queries', () => { @@ -676,6 +721,68 @@ describe('query logic', () => { fixture.detectChanges(); expect(queryList.length).toBe(0); }); + + it('should support content queries for directives within repeated embedded views', () => { + const withContentInstances: DirWithContentQuery[] = []; + + @Directive({ + selector: '[with-content]', + }) + class DirWithContentQuery { + constructor() { + withContentInstances.push(this); + } + + @ContentChildren('foo', {descendants: false}) foos!: QueryList; + + contentInitQuerySnapshot = 0; + contentCheckedQuerySnapshot = 0; + + ngAfterContentInit() { + this.contentInitQuerySnapshot = this.foos ? this.foos.length : 0; + } + + ngAfterContentChecked() { + this.contentCheckedQuerySnapshot = this.foos ? this.foos.length : 0; + } + } + + @Component({ + selector: 'comp', + template: ` + +
+ +
+
+ `, + }) + class Root { + items = [1, 2, 3]; + } + + TestBed.configureTestingModule({ + declarations: [Root, DirWithContentQuery], + imports: [CommonModule], + }); + const fixture = TestBed.createComponent(Root); + fixture.detectChanges(); + + for (let i = 0; i < 3; i++) { + expect(withContentInstances[i].foos.length) + .toBe(1, `Expected content query to match .`); + + expect(withContentInstances[i].contentInitQuerySnapshot) + .toBe( + 1, + `Expected content query results to be available when ngAfterContentInit was called.`); + + expect(withContentInstances[i].contentCheckedQuerySnapshot) + .toBe( + 1, + `Expected content query results to be available when ngAfterContentChecked was called.`); + } + }); }); // Some root components may have ContentChildren queries if they are also diff --git a/packages/core/test/acceptance/renderer_factory_spec.ts b/packages/core/test/acceptance/renderer_factory_spec.ts index 856997f47e..9c75b00175 100644 --- a/packages/core/test/acceptance/renderer_factory_spec.ts +++ b/packages/core/test/acceptance/renderer_factory_spec.ts @@ -11,6 +11,7 @@ import {ɵAnimationEngine, ɵNoopAnimationStyleNormalizer} from '@angular/animat import {MockAnimationDriver, MockAnimationPlayer} from '@angular/animations/browser/testing'; import {DOCUMENT} from '@angular/common'; import {Component, DoCheck, NgZone, RendererFactory2, RendererType2} from '@angular/core'; +import {ngDevModeResetPerfCounters} from '@angular/core/src/util/ng_dev_mode'; import {NoopNgZone} from '@angular/core/src/zone/ng_zone'; import {TestBed} from '@angular/core/testing'; import {EventManager, ɵDomSharedStylesHost} from '@angular/platform-browser'; @@ -252,4 +253,80 @@ describe('custom renderer', () => { fixture.detectChanges(); }).not.toThrow(); }); +}); + +onlyInIvy('access global ngDevMode').describe('Renderer2 destruction hooks', () => { + @Component({ + selector: 'some-component', + template: ` + A + B + C + `, + }) + class SimpleApp { + isContentVisible = true; + } + + @Component({ + selector: 'basic-comp', + template: 'comp()', + }) + class BasicComponent { + } + + @Component({ + selector: 'some-component', + template: ` + A + B + C + `, + }) + class AppWithComponents { + isContentVisible = true; + } + + beforeEach(() => { + // Tests below depend on perf counters when running with Ivy. In order to have + // clean perf counters at the beginning of a test, we reset those here. + ngDevModeResetPerfCounters(); + + TestBed.configureTestingModule({ + declarations: [SimpleApp, AppWithComponents, BasicComponent], + providers: [{ + provide: RendererFactory2, + useFactory: (document: any) => getRendererFactory2(document), + deps: [DOCUMENT] + }] + }); + }); + + it('should call renderer.destroyNode for each node destroyed', () => { + const fixture = TestBed.createComponent(SimpleApp); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent).toBe('ABC'); + + fixture.componentInstance.isContentVisible = false; + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent).toBe(''); + expect(ngDevMode!.rendererDestroy).toBe(0); + expect(ngDevMode!.rendererDestroyNode).toBe(3); + }); + + it('should call renderer.destroy for each component destroyed', () => { + const fixture = TestBed.createComponent(AppWithComponents); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent).toBe('comp(A)comp(B)comp(C)'); + + fixture.componentInstance.isContentVisible = false; + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent).toBe(''); + expect(ngDevMode!.rendererDestroy).toBe(3); + expect(ngDevMode!.rendererDestroyNode).toBe(3); + }); }); \ No newline at end of file diff --git a/packages/core/test/render3/basic_perf.ts b/packages/core/test/render3/basic_perf.ts deleted file mode 100644 index 27e562f64e..0000000000 --- a/packages/core/test/render3/basic_perf.ts +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {ɵɵdefineComponent} from '../../src/render3/index'; -import {ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵtext} from '../../src/render3/instructions/all'; -import {RenderFlags} from '../../src/render3/interfaces/definition'; - -import {document, renderComponent} from './render_util'; - -describe('iv perf test', () => { - const count = 100000; - const noOfIterations = 10; - - describe('render', () => { - for (let iteration = 0; iteration < noOfIterations; iteration++) { - it(`${iteration}. create ${count} divs in DOM`, () => { - const start = new Date().getTime(); - const container = document.createElement('div'); - for (let i = 0; i < count; i++) { - const div = document.createElement('div'); - div.appendChild(document.createTextNode('-')); - container.appendChild(div); - } - const end = new Date().getTime(); - log(`${count} DIVs in DOM`, (end - start) / count); - }); - - it(`${iteration}. create ${count} divs in Render3`, () => { - class Component { - static ɵfac = () => new Component; - static ɵcmp = ɵɵdefineComponent({ - type: Component, - selectors: [['div']], - decls: 1, - vars: 0, - template: - function Template(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵcontainer(0); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(0); - { - for (let i = 0; i < count; i++) { - let rf0 = ɵɵembeddedViewStart(0, 2, 0); - { - if (rf0 & RenderFlags.Create) { - ɵɵelementStart(0, 'div'); - ɵɵtext(1, '-'); - ɵɵelementEnd(); - } - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - } - }); - } - - const start = new Date().getTime(); - renderComponent(Component); - const end = new Date().getTime(); - log(`${count} DIVs in Render3`, (end - start) / count); - }); - } - }); -}); - -function log(text: string, duration: number) { - // tslint:disable-next-line:no-console - console.log(text, duration * 1000, 'ns'); -} diff --git a/packages/core/test/render3/component_spec.ts b/packages/core/test/render3/component_spec.ts index b15319cc0b..d8c19412b4 100644 --- a/packages/core/test/render3/component_spec.ts +++ b/packages/core/test/render3/component_spec.ts @@ -8,12 +8,12 @@ import {ViewEncapsulation, ɵɵdefineInjectable, ɵɵdefineInjector} from '../../src/core'; import {createInjector} from '../../src/di/r3_injector'; -import {AttributeMarker, ComponentFactory, getRenderedText, LifecycleHooksFeature, markDirty, ɵɵadvance, ɵɵdefineComponent, ɵɵdirectiveInject, ɵɵproperty, ɵɵselect, ɵɵtemplate} from '../../src/render3/index'; -import {tick, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵnextContext, ɵɵtext, ɵɵtextInterpolate} from '../../src/render3/instructions/all'; -import {ComponentDef, RenderFlags} from '../../src/render3/interfaces/definition'; +import {AttributeMarker, markDirty, ɵɵadvance, ɵɵdefineComponent, ɵɵdirectiveInject, ɵɵproperty, ɵɵtemplate} from '../../src/render3/index'; +import {ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵtext, ɵɵtextInterpolate} from '../../src/render3/instructions/all'; +import {RenderFlags} from '../../src/render3/interfaces/definition'; import {NgIf} from './common_with_def'; -import {ComponentFixture, containerEl, createComponent, MockRendererFactory, renderComponent, renderToHtml, requestAnimationFrame, toHtml} from './render_util'; +import {ComponentFixture, containerEl, createComponent, MockRendererFactory, renderComponent, requestAnimationFrame, toHtml} from './render_util'; describe('component', () => { class CounterComponent { @@ -219,349 +219,4 @@ it('should not invoke renderer destroy method for embedded views', () => { // we should never see `destroy` method being called // in case child views are created/removed expect(destroySpy.calls.count()).toBe(0); -}); - -describe('component with a container', () => { - function showItems(rf: RenderFlags, ctx: {items: string[]}) { - if (rf & RenderFlags.Create) { - ɵɵcontainer(0); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(0); - { - for (const item of ctx.items) { - const rf0 = ɵɵembeddedViewStart(0, 1, 1); - { - if (rf0 & RenderFlags.Create) { - ɵɵtext(0); - } - if (rf0 & RenderFlags.Update) { - ɵɵselect(0); - ɵɵtextInterpolate(item); - } - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - } - - class WrapperComponent { - // TODO(issue/24571): remove '!'. - items!: string[]; - static ɵfac = () => new WrapperComponent; - static ɵcmp = ɵɵdefineComponent({ - type: WrapperComponent, - encapsulation: ViewEncapsulation.None, - selectors: [['wrapper']], - decls: 1, - vars: 0, - template: - function ChildComponentTemplate(rf: RenderFlags, ctx: {items: string[]}) { - if (rf & RenderFlags.Create) { - ɵɵcontainer(0); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(0); - { - const rf0 = ɵɵembeddedViewStart(0, 1, 0); - { showItems(rf0, {items: ctx.items}); } - ɵɵembeddedViewEnd(); - } - ɵɵcontainerRefreshEnd(); - } - }, - inputs: {items: 'items'} - }); - } - - function template(rf: RenderFlags, ctx: {items: string[]}) { - if (rf & RenderFlags.Create) { - ɵɵelement(0, 'wrapper'); - } - if (rf & RenderFlags.Update) { - ɵɵproperty('items', ctx.items); - } - } - - const defs = [WrapperComponent]; - - it('should re-render on input change', () => { - const ctx: {items: string[]} = {items: ['a']}; - expect(renderToHtml(template, ctx, 1, 1, defs)).toEqual('a'); - - ctx.items = [...ctx.items, 'b']; - expect(renderToHtml(template, ctx, 1, 1, defs)).toEqual('ab'); - }); -}); - -describe('recursive components', () => { - let events: string[]; - let count: number; - - beforeEach(() => { - events = []; - count = 0; - }); - - class TreeNode { - constructor( - public value: number, public depth: number, public left: TreeNode|null, - public right: TreeNode|null) {} - } - - /** - * {{ data.value }} - * - * % if (data.left != null) { - * - * % } - * % if (data.right != null) { - * - * % } - */ - class TreeComponent { - data: TreeNode = _buildTree(0); - - ngDoCheck() { - events.push('check' + this.data.value); - } - - ngOnDestroy() { - events.push('destroy' + this.data.value); - } - - static ɵfac = () => new TreeComponent(); - static ɵcmp = ɵɵdefineComponent({ - type: TreeComponent, - encapsulation: ViewEncapsulation.None, - selectors: [['tree-comp']], - decls: 3, - vars: 1, - template: - (rf: RenderFlags, ctx: TreeComponent) => { - if (rf & RenderFlags.Create) { - ɵɵtext(0); - ɵɵcontainer(1); - ɵɵcontainer(2); - } - if (rf & RenderFlags.Update) { - ɵɵtextInterpolate(ctx.data.value); - ɵɵcontainerRefreshStart(1); - { - if (ctx.data.left != null) { - let rf0 = ɵɵembeddedViewStart(0, 1, 1); - if (rf0 & RenderFlags.Create) { - ɵɵelement(0, 'tree-comp'); - } - if (rf0 & RenderFlags.Update) { - ɵɵselect(0); - ɵɵproperty('data', ctx.data.left); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - ɵɵcontainerRefreshStart(2); - { - if (ctx.data.right != null) { - let rf0 = ɵɵembeddedViewStart(0, 1, 1); - if (rf0 & RenderFlags.Create) { - ɵɵelement(0, 'tree-comp'); - } - if (rf0 & RenderFlags.Update) { - ɵɵselect(0); - ɵɵproperty('data', ctx.data.right); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - }, - inputs: {data: 'data'} - }); - } - - (TreeComponent.ɵcmp as ComponentDef).directiveDefs = () => [TreeComponent.ɵcmp]; - - /** - * {{ data.value }} - * - * - */ - class NgIfTree { - data: TreeNode = _buildTree(0); - - ngDoCheck() { - events.push('check' + this.data.value); - } - - ngOnDestroy() { - events.push('destroy' + this.data.value); - } - - static ɵfac = () => new NgIfTree(); - static ɵcmp = ɵɵdefineComponent({ - type: NgIfTree, - encapsulation: ViewEncapsulation.None, - selectors: [['ng-if-tree']], - decls: 3, - vars: 3, - consts: [[AttributeMarker.Bindings, 'data', AttributeMarker.Template, 'ngIf']], - template: - (rf: RenderFlags, ctx: NgIfTree) => { - if (rf & RenderFlags.Create) { - ɵɵtext(0); - ɵɵtemplate(1, IfTemplate, 1, 1, 'ng-if-tree', 0); - ɵɵtemplate(2, IfTemplate2, 1, 1, 'ng-if-tree', 0); - } - if (rf & RenderFlags.Update) { - ɵɵtextInterpolate(ctx.data.value); - ɵɵadvance(1); - ɵɵproperty('ngIf', ctx.data.left); - ɵɵadvance(1); - ɵɵproperty('ngIf', ctx.data.right); - } - }, - inputs: {data: 'data'}, - }); - } - - function IfTemplate(rf: RenderFlags, left: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'ng-if-tree'); - ɵɵelementEnd(); - } - if (rf & RenderFlags.Update) { - const parent = ɵɵnextContext(); - ɵɵproperty('data', parent.data.left); - } - } - - function IfTemplate2(rf: RenderFlags, right: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'ng-if-tree'); - ɵɵelementEnd(); - } - if (rf & RenderFlags.Update) { - const parent = ɵɵnextContext(); - ɵɵproperty('data', parent.data.right); - } - } - - (NgIfTree.ɵcmp as ComponentDef).directiveDefs = () => [NgIfTree.ɵcmp, NgIf.ɵdir]; - - function _buildTree(currDepth: number): TreeNode { - const children = currDepth < 2 ? _buildTree(currDepth + 1) : null; - const children2 = currDepth < 2 ? _buildTree(currDepth + 1) : null; - return new TreeNode(count++, currDepth, children, children2); - } - - it('should check each component just once', () => { - const comp = renderComponent(TreeComponent, {hostFeatures: [LifecycleHooksFeature]}); - expect(getRenderedText(comp)).toEqual('6201534'); - expect(events).toEqual(['check6', 'check2', 'check0', 'check1', 'check5', 'check3', 'check4']); - - events = []; - tick(comp); - expect(events).toEqual(['check6', 'check2', 'check0', 'check1', 'check5', 'check3', 'check4']); - }); - - // This tests that the view tree is set up properly for recursive components - it('should call onDestroys properly', () => { - /** - * % if (!skipContent) { - * - * % } - */ - const App = createComponent('app', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵcontainer(0); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(0); - if (!ctx.skipContent) { - const rf0 = ɵɵembeddedViewStart(0, 1, 0); - if (rf0 & RenderFlags.Create) { - ɵɵelementStart(0, 'tree-comp'); - ɵɵelementEnd(); - } - ɵɵembeddedViewEnd(); - } - ɵɵcontainerRefreshEnd(); - } - }, 1, 0, [TreeComponent]); - - const fixture = new ComponentFixture(App); - expect(getRenderedText(fixture.component)).toEqual('6201534'); - - events = []; - fixture.component.skipContent = true; - fixture.update(); - expect(events).toEqual( - ['destroy0', 'destroy1', 'destroy2', 'destroy3', 'destroy4', 'destroy5', 'destroy6']); - }); - - it('should call onDestroys properly with ngIf', () => { - /** - * % if (!skipContent) { - * - * % } - */ - const App = createComponent('app', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵcontainer(0); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(0); - if (!ctx.skipContent) { - const rf0 = ɵɵembeddedViewStart(0, 1, 0); - if (rf0 & RenderFlags.Create) { - ɵɵelementStart(0, 'ng-if-tree'); - ɵɵelementEnd(); - } - ɵɵembeddedViewEnd(); - } - ɵɵcontainerRefreshEnd(); - } - }, 1, 0, [NgIfTree]); - - const fixture = new ComponentFixture(App); - expect(getRenderedText(fixture.component)).toEqual('6201534'); - expect(events).toEqual(['check6', 'check2', 'check0', 'check1', 'check5', 'check3', 'check4']); - - events = []; - fixture.component.skipContent = true; - fixture.update(); - expect(events).toEqual( - ['destroy0', 'destroy1', 'destroy2', 'destroy3', 'destroy4', 'destroy5', 'destroy6']); - }); - - it('should map inputs minified & unminified names', async () => { - class TestInputsComponent { - // TODO(issue/24571): remove '!'. - minifiedName!: string; - static ɵfac = () => new TestInputsComponent(); - static ɵcmp = ɵɵdefineComponent({ - type: TestInputsComponent, - encapsulation: ViewEncapsulation.None, - selectors: [['test-inputs']], - inputs: {minifiedName: 'unminifiedName'}, - decls: 0, - vars: 0, - template: function(rf: RenderFlags, ctx: TestInputsComponent): - void { - // Template not needed for this test - } - }); - } - - const testInputsComponentFactory = new ComponentFactory(TestInputsComponent.ɵcmp); - - expect([ - {propName: 'minifiedName', templateName: 'unminifiedName'} - ]).toEqual(testInputsComponentFactory.inputs); - }); -}); +}); \ No newline at end of file diff --git a/packages/core/test/render3/control_flow_spec.ts b/packages/core/test/render3/control_flow_spec.ts deleted file mode 100644 index 524d6a184c..0000000000 --- a/packages/core/test/render3/control_flow_spec.ts +++ /dev/null @@ -1,961 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {ɵɵdefineComponent} from '../../src/render3/definition'; -import {ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵselect, ɵɵtext, ɵɵtextInterpolate} from '../../src/render3/instructions/all'; -import {RenderFlags} from '../../src/render3/interfaces/definition'; - -import {ComponentFixture, createComponent, TemplateFixture} from './render_util'; - -describe('JS control flow', () => { - it('should work with if block', () => { - const App = createComponent('app', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'div'); - { ɵɵcontainer(1); } - ɵɵelementEnd(); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(1); - { - if (ctx.condition) { - let rf1 = ɵɵembeddedViewStart(1, 2, 1); - { - if (rf1 & RenderFlags.Create) { - ɵɵelementStart(0, 'span'); - { ɵɵtext(1); } - ɵɵelementEnd(); - } - if (rf1 & RenderFlags.Update) { - ɵɵselect(1); - ɵɵtextInterpolate(ctx.message); - } - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - }, 2); - - const fixture = new ComponentFixture(App); - fixture.component.condition = true; - fixture.component.message = 'Hello'; - fixture.update(); - expect(fixture.html).toEqual('
Hello
'); - - fixture.component.condition = false; - fixture.component.message = 'Hi!'; - fixture.update(); - expect(fixture.html).toEqual('
'); - - fixture.component.condition = true; - fixture.update(); - expect(fixture.html).toEqual('
Hi!
'); - }); - - it('should work with nested if blocks', () => { - /** - *
- * % if(ctx.condition) { - * - * % if(ctx.condition2) { - * Hello - * % } - * - * % } - *
- */ - const App = createComponent('app', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'div'); - { ɵɵcontainer(1); } - ɵɵelementEnd(); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(1); - { - if (ctx.condition) { - let rf1 = ɵɵembeddedViewStart(1, 2, 0); - { - if (rf1 & RenderFlags.Create) { - ɵɵelementStart(0, 'span'); - { ɵɵcontainer(1); } - ɵɵelementEnd(); - } - if (rf1 & RenderFlags.Update) { - ɵɵcontainerRefreshStart(1); - { - if (ctx.condition2) { - let rf2 = ɵɵembeddedViewStart(2, 1, 0); - { - if (rf2 & RenderFlags.Create) { - ɵɵtext(0, 'Hello'); - } - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - }, 2); - - const fixture = new ComponentFixture(App); - fixture.component.condition = true; - fixture.component.condition2 = true; - fixture.update(); - expect(fixture.html).toEqual('
Hello
'); - - fixture.component.condition = false; - fixture.update(); - expect(fixture.html).toEqual('
'); - - fixture.component.condition = true; - fixture.update(); - expect(fixture.html).toEqual('
Hello
'); - - fixture.component.condition2 = false; - fixture.update(); - expect(fixture.html).toEqual('
'); - - fixture.component.condition2 = true; - fixture.update(); - expect(fixture.html).toEqual('
Hello
'); - - fixture.component.condition2 = false; - fixture.update(); - expect(fixture.html).toEqual('
'); - - fixture.component.condition = false; - fixture.update(); - expect(fixture.html).toEqual('
'); - - fixture.component.condition = true; - fixture.update(); - expect(fixture.html).toEqual('
'); - - fixture.component.condition2 = true; - fixture.update(); - expect(fixture.html).toEqual('
Hello
'); - }); - - it('should work with nested adjacent if blocks', () => { - const ctx: - {condition: boolean, - condition2: boolean, - condition3: boolean} = {condition: true, condition2: false, condition3: true}; - - /** - * % if(ctx.condition) { - * % if(ctx.condition2) { - * Hello - * % } - * % if(ctx.condition3) { - * World - * % } - * % } - */ - function createTemplate() { - ɵɵcontainer(0); - } - - function updateTemplate() { - ɵɵcontainerRefreshStart(0); - { - if (ctx.condition) { - let rf1 = ɵɵembeddedViewStart(1, 2, 0); - { - if (rf1 & RenderFlags.Create) { - { - ɵɵcontainer(0); - } - { ɵɵcontainer(1); } - } - if (rf1 & RenderFlags.Update) { - ɵɵcontainerRefreshStart(0); - { - if (ctx.condition2) { - let rf2 = ɵɵembeddedViewStart(2, 1, 0); - { - if (rf2 & RenderFlags.Create) { - ɵɵtext(0, 'Hello'); - } - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - ɵɵcontainerRefreshStart(1); - { - if (ctx.condition3) { - let rf2 = ɵɵembeddedViewStart(2, 1, 0); - { - if (rf2 & RenderFlags.Create) { - ɵɵtext(0, 'World'); - } - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - - const fixture = new TemplateFixture(createTemplate, updateTemplate, 1); - expect(fixture.html).toEqual('World'); - - ctx.condition2 = true; - fixture.update(); - expect(fixture.html).toEqual('HelloWorld'); - }); - - it('should work with adjacent if blocks managing views in the same container', () => { - /** - * % if(ctx.condition1) { - * 1 - * % }; if(ctx.condition2) { - * 2 - * % }; if(ctx.condition3) { - * 3 - * % } - */ - const App = createComponent('app', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵcontainer(0); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(0); - if (ctx.condition1) { - const rf1 = ɵɵembeddedViewStart(1, 1, 0); - if (rf1 & RenderFlags.Create) { - ɵɵtext(0, '1'); - } - ɵɵembeddedViewEnd(); - } // can't have ; here due linting rules - if (ctx.condition2) { - const rf2 = ɵɵembeddedViewStart(2, 1, 0); - if (rf2 & RenderFlags.Create) { - ɵɵtext(0, '2'); - } - ɵɵembeddedViewEnd(); - } // can't have ; here due linting rules - if (ctx.condition3) { - const rf3 = ɵɵembeddedViewStart(3, 1, 0); - if (rf3 & RenderFlags.Create) { - ɵɵtext(0, '3'); - } - ɵɵembeddedViewEnd(); - } - ɵɵcontainerRefreshEnd(); - } - }, 1); - - const fixture = new ComponentFixture(App); - fixture.component.condition1 = true; - fixture.component.condition2 = true; - fixture.component.condition3 = true; - fixture.update(); - expect(fixture.html).toEqual('123'); - - fixture.component.condition2 = false; - fixture.update(); - expect(fixture.html).toEqual('13'); - }); - - it('should work with containers with views as parents', () => { - const App = createComponent('app', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'div'); - { ɵɵtext(1, 'hello'); } - ɵɵelementEnd(); - ɵɵcontainer(2); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(2); - { - if (ctx.condition1) { - let rf0 = ɵɵembeddedViewStart(0, 1, 0); - { - if (rf0 & RenderFlags.Create) { - ɵɵcontainer(0); - } - if (rf0 & RenderFlags.Update) { - ɵɵcontainerRefreshStart(0); - { - if (ctx.condition2) { - let rf0 = ɵɵembeddedViewStart(0, 1, 0); - { - if (rf0 & RenderFlags.Create) { - ɵɵtext(0, 'world'); - } - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - }, 3); - - const fixture = new ComponentFixture(App); - fixture.component.condition1 = true; - fixture.component.condition2 = true; - fixture.update(); - expect(fixture.html).toEqual('
hello
world'); - - fixture.component.condition1 = false; - fixture.component.condition2 = false; - fixture.update(); - expect(fixture.html).toEqual('
hello
'); - }); - - it('should work with loop block', () => { - let data: string[] = ['a', 'b', 'c']; - const App = createComponent('app', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'ul'); - { ɵɵcontainer(1); } - ɵɵelementEnd(); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(1); - { - for (let i = 0; i < data.length; i++) { - let rf1 = ɵɵembeddedViewStart(1, 2, 1); - { - if (rf1 & RenderFlags.Create) { - ɵɵelementStart(0, 'li'); - { ɵɵtext(1); } - ɵɵelementEnd(); - } - if (rf1 & RenderFlags.Update) { - ɵɵselect(1); - ɵɵtextInterpolate(data[i]); - } - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - }, 2); - - const fixture = new ComponentFixture(App); - fixture.update(); - expect(fixture.html).toEqual('
  • a
  • b
  • c
'); - - data = ['e', 'f']; - fixture.update(); - expect(fixture.html).toEqual('
  • e
  • f
'); - - data = []; - fixture.update(); - expect(fixture.html).toEqual('
    '); - - data = ['a', 'b', 'c']; - fixture.update(); - expect(fixture.html).toEqual('
    • a
    • b
    • c
    '); - - data.push('d'); - fixture.update(); - expect(fixture.html).toEqual('
    • a
    • b
    • c
    • d
    '); - - data = ['e']; - fixture.update(); - expect(fixture.html).toEqual('
    • e
    '); - }); - - it('should work with nested loop blocks', () => { - let data: string[][] = [['a', 'b', 'c'], ['m', 'n']]; - - const App = createComponent('app', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'ul'); - { ɵɵcontainer(1); } - ɵɵelementEnd(); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(1); - { - for (let i = 0; i < data[0].length; i++) { - let rf1 = ɵɵembeddedViewStart(1, 2, 0); - { - if (rf1 & RenderFlags.Create) { - ɵɵelementStart(0, 'li'); - { ɵɵcontainer(1); } - ɵɵelementEnd(); - } - if (rf1 & RenderFlags.Update) { - ɵɵcontainerRefreshStart(1); - { - data[1].forEach((value: string, ind: number) => { - let rf2 = ɵɵembeddedViewStart(2, 1, 1); - if (rf2 & RenderFlags.Create) { - ɵɵtext(0); - } - if (rf2 & RenderFlags.Update) { - ɵɵselect(0); - ɵɵtextInterpolate(data[0][i] + value); - } - ɵɵembeddedViewEnd(); - }); - } - ɵɵcontainerRefreshEnd(); - } - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - }, 2); - - const fixture = new ComponentFixture(App); - fixture.update(); - expect(fixture.html).toEqual('
    • aman
    • bmbn
    • cmcn
    '); - - data = [[], []]; - fixture.update(); - expect(fixture.html).toEqual('
      '); - }); - - it('should work with nested loop blocks where nested container is a root node', () => { - let cafes = [ - {name: '1', entrees: ['a', 'b', 'c']}, {name: '2', entrees: ['d', 'e', 'f']}, - {name: '3', entrees: ['g', 'h', 'i']} - ]; - - /** - *
      - * Before - * % for (let i = 0; i < cafes.length; i++) { - *

      {{ cafes[i].name }}

      - * % for (let j = 0; j < cafes[i].entrees; j++) { - * {{ cafes[i].entrees[j] }} - * % } - * - - * % } - * After - *
      - */ - const App = createComponent('app', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'div'); - { - ɵɵtext(1, 'Before'); - ɵɵcontainer(2); - ɵɵtext(3, 'After'); - } - ɵɵelementEnd(); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(2); - { - for (let i = 0; i < cafes.length; i++) { - let rf1 = ɵɵembeddedViewStart(1, 4, 1); - { - if (rf1 & RenderFlags.Create) { - ɵɵelementStart(0, 'h2'); - { ɵɵtext(1); } - ɵɵelementEnd(); - ɵɵcontainer(2); - ɵɵtext(3, '-'); - } - if (rf1 & RenderFlags.Update) { - ɵɵselect(1); - ɵɵtextInterpolate(cafes[i].name); - ɵɵcontainerRefreshStart(2); - { - for (let j = 0; j < cafes[i].entrees.length; j++) { - let rf2 = ɵɵembeddedViewStart(2, 1, 1); - if (rf2 & RenderFlags.Create) { - ɵɵtext(0); - } - if (rf2 & RenderFlags.Update) { - ɵɵselect(0); - ɵɵtextInterpolate(cafes[i].entrees[j]); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - }, 4); - - const fixture = new ComponentFixture(App); - fixture.update(); - expect(fixture.html) - .toEqual('
      Before

      1

      abc-

      2

      def-

      3

      ghi-After
      '); - - cafes = []; - fixture.update(); - expect(fixture.html).toEqual('
      BeforeAfter
      '); - - cafes = [ - {name: '1', entrees: ['a', 'c']}, - {name: '2', entrees: ['d', 'e']}, - ]; - fixture.update(); - expect(fixture.html).toEqual('
      Before

      1

      ac-

      2

      de-After
      '); - }); - - it('should work with loop blocks nested three deep', () => { - let cafes = [ - { - name: '1', - entrees: - [{name: 'a', foods: [1, 2]}, {name: 'b', foods: [3, 4]}, {name: 'c', foods: [5, 6]}] - }, - { - name: '2', - entrees: - [{name: 'd', foods: [1, 2]}, {name: 'e', foods: [3, 4]}, {name: 'f', foods: [5, 6]}] - } - ]; - - /** - *
      - * Before - * % for (let i = 0; i < cafes.length; i++) { - *

      {{ cafes[i].name }}

      - * % for (let j = 0; j < cafes[i].entrees.length; j++) { - *

      {{ cafes[i].entrees[j].name }}

      - * % for (let k = 0; k < cafes[i].entrees[j].foods.length; k++) { - * {{ cafes[i].entrees[j].foods[k] }} - * % } - * % } - * - - * % } - * After - *
      - */ - const App = createComponent('app', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'div'); - { - ɵɵtext(1, 'Before'); - ɵɵcontainer(2); - ɵɵtext(3, 'After'); - } - ɵɵelementEnd(); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(2); - { - for (let i = 0; i < cafes.length; i++) { - let rf1 = ɵɵembeddedViewStart(1, 4, 1); - { - if (rf1 & RenderFlags.Create) { - ɵɵelementStart(0, 'h2'); - { ɵɵtext(1); } - ɵɵelementEnd(); - ɵɵcontainer(2); - ɵɵtext(3, '-'); - } - if (rf1 & RenderFlags.Update) { - ɵɵselect(1); - ɵɵtextInterpolate(cafes[i].name); - ɵɵcontainerRefreshStart(2); - { - for (let j = 0; j < cafes[i].entrees.length; j++) { - let rf1 = ɵɵembeddedViewStart(1, 3, 1); - { - if (rf1 & RenderFlags.Create) { - ɵɵelementStart(0, 'h3'); - { ɵɵtext(1); } - ɵɵelementEnd(); - ɵɵcontainer(2); - } - if (rf1 & RenderFlags.Update) { - ɵɵselect(1); - ɵɵtextInterpolate(cafes[i].entrees[j].name); - ɵɵcontainerRefreshStart(2); - { - for (let k = 0; k < cafes[i].entrees[j].foods.length; k++) { - let rf2 = ɵɵembeddedViewStart(1, 1, 1); - if (rf2 & RenderFlags.Create) { - ɵɵtext(0); - } - if (rf2 & RenderFlags.Update) { - ɵɵselect(0); - ɵɵtextInterpolate(cafes[i].entrees[j].foods[k]); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - }, 4); - - const fixture = new ComponentFixture(App); - fixture.update(); - expect(fixture.html) - .toEqual( - '
      ' + - 'Before' + - '

      1

      a

      12

      b

      34

      c

      56-' + - '

      2

      d

      12

      e

      34

      f

      56-' + - 'After' + - '
      '); - - cafes = []; - fixture.update(); - expect(fixture.html).toEqual('
      BeforeAfter
      '); - }); - - it('should work with if/else blocks', () => { - const App = createComponent('app', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'div'); - { ɵɵcontainer(1); } - ɵɵelementEnd(); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(1); - { - if (ctx.condition) { - let rf1 = ɵɵembeddedViewStart(1, 2, 0); - { - if (rf1 & RenderFlags.Create) { - ɵɵelementStart(0, 'span'); - { ɵɵtext(1, 'Hello'); } - ɵɵelementEnd(); - } - } - ɵɵembeddedViewEnd(); - } else { - let rf2 = ɵɵembeddedViewStart(2, 2, 0); - { - if (rf2) { - ɵɵelementStart(0, 'div'); - { ɵɵtext(1, 'Goodbye'); } - ɵɵelementEnd(); - } - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - }, 2); - - const fixture = new ComponentFixture(App); - fixture.component.condition = true; - fixture.update(); - expect(fixture.html).toEqual('
      Hello
      '); - - fixture.component.condition = false; - fixture.update(); - expect(fixture.html).toEqual('
      Goodbye
      '); - - fixture.component.condition = true; - fixture.update(); - expect(fixture.html).toEqual('
      Hello
      '); - }); - - it('should work with sibling if blocks with children', () => { - let log: string[] = []; - - // Intentionally duplicating the templates in test below so we are - // testing the behavior on firstCreatePass for each of these tests - class Comp { - static ɵfac = - () => { - log.push('comp!'); - return new Comp(); - } - - static ɵcmp = ɵɵdefineComponent({ - type: Comp, - selectors: [['comp']], - decls: 0, - vars: 0, - template: function(rf: RenderFlags, ctx: Comp) {} - }); - } - - class App { - condition = true; - condition2 = true; - - static ɵfac = () => new App(); - static ɵcmp = ɵɵdefineComponent({ - type: App, - selectors: [['app']], - decls: 3, - vars: 0, - template: - function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelement(0, 'div'); - ɵɵcontainer(1); - ɵɵcontainer(2); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(1); - { - if (ctx.condition) { - let rf1 = ɵɵembeddedViewStart(0, 1, 0); - if (rf1 & RenderFlags.Create) { - ɵɵelement(0, 'comp'); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - ɵɵcontainerRefreshStart(2); - { - if (ctx.condition2) { - let rf1 = ɵɵembeddedViewStart(0, 1, 0); - if (rf1 & RenderFlags.Create) { - ɵɵelement(0, 'comp'); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - }, - directives: () => [Comp] - }); - } - - const fixture = new ComponentFixture(App); - expect(log).toEqual(['comp!', 'comp!']); - }); - - it('should work with a sibling if block that starts closed', () => { - let log: string[] = []; - - // Intentionally duplicating the templates from above so we are - // testing the behavior on firstCreatePass for each of these tests - class Comp { - static ɵfac = - () => { - log.push('comp!'); - return new Comp(); - } - - static ɵcmp = ɵɵdefineComponent({ - type: Comp, - selectors: [['comp']], - decls: 0, - vars: 0, - template: function(rf: RenderFlags, ctx: Comp) {} - }); - } - - class App { - condition = false; - condition2 = true; - - static ɵfac = () => new App(); - static ɵcmp = ɵɵdefineComponent({ - type: App, - selectors: [['app']], - decls: 3, - vars: 0, - template: - function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelement(0, 'div'); - ɵɵcontainer(1); - ɵɵcontainer(2); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(1); - { - if (ctx.condition) { - let rf1 = ɵɵembeddedViewStart(0, 1, 0); - if (rf1 & RenderFlags.Create) { - ɵɵelement(0, 'comp'); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - ɵɵcontainerRefreshStart(2); - { - if (ctx.condition2) { - let rf1 = ɵɵembeddedViewStart(0, 1, 0); - if (rf1 & RenderFlags.Create) { - ɵɵelement(0, 'comp'); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - }, - directives: () => [Comp] - }); - } - - const fixture = new ComponentFixture(App); - expect(log).toEqual(['comp!']); - - fixture.component.condition = true; - fixture.update(); - expect(log).toEqual(['comp!', 'comp!']); - }); -}); - -describe('JS for loop', () => { - it('should work with sibling for blocks', () => { - const config: {data1: string[], data2: number[]} = {data1: ['a', 'b', 'c'], data2: [1, 2]}; - - /** - *
      - * % for (let i = 0; i < ctx.data1.length; i++) { - * {{data1[i]}} - * % } for (let j = 0; j < ctx.data2.length; j++) { - * {{data1[j]}} - * % } - *
      - */ - const App = createComponent('app', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'div'); - { ɵɵcontainer(1); } - ɵɵelementEnd(); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(1); - { - for (let i = 0; i < config.data1.length; i++) { - let rf2 = ɵɵembeddedViewStart(1, 1, 1); - if (rf2 & RenderFlags.Create) { - ɵɵtext(0); - } - if (rf2 & RenderFlags.Update) { - ɵɵselect(0); - ɵɵtextInterpolate(config.data1[i]); - } - ɵɵembeddedViewEnd(); - } - for (let j = 0; j < config.data2.length; j++) { - let rf2 = ɵɵembeddedViewStart(1, 1, 1); - if (rf2 & RenderFlags.Create) { - ɵɵtext(0); - } - if (rf2 & RenderFlags.Update) { - ɵɵselect(0); - ɵɵtextInterpolate(config.data2[j]); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - }, 2); - - const fixture = new ComponentFixture(App); - expect(fixture.html).toEqual('
      abc12
      '); - - config.data1 = ['e', 'f']; - fixture.update(); - expect(fixture.html).toEqual('
      ef12
      '); - - config.data2 = [8]; - fixture.update(); - expect(fixture.html).toEqual('
      ef8
      '); - - config.data1 = ['x', 'y']; - fixture.update(); - expect(fixture.html).toEqual('
      xy8
      '); - }); -}); - -describe('function calls', () => { - it('should work', () => { - let data: string[] = ['foo', 'bar']; - - function spanify(rf: RenderFlags, ctx: {message: string|null}) { - const message = ctx.message; - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'span'); - { ɵɵtext(1); } - ɵɵelementEnd(); - } - if (rf & RenderFlags.Update) { - ɵɵselect(1); - ɵɵtextInterpolate(message); - } - } - - const App = createComponent('app', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'div'); - { - ɵɵtext(1, 'Before'); - ɵɵcontainer(2); - ɵɵcontainer(3); - ɵɵtext(4, 'After'); - } - ɵɵelementEnd(); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(2); - { - let rf0 = ɵɵembeddedViewStart(0, 2, 1); - { spanify(rf0, {message: data[0]}); } - ɵɵembeddedViewEnd(); - } - ɵɵcontainerRefreshEnd(); - ɵɵcontainerRefreshStart(3); - { - let rf0 = ɵɵembeddedViewStart(0, 2, 1); - { spanify(rf0, {message: data[1]}); } - ɵɵembeddedViewEnd(); - } - ɵɵcontainerRefreshEnd(); - } - }, 5); - - const fixture = new ComponentFixture(App); - expect(fixture.html).toEqual('
      BeforefoobarAfter
      '); - - data = []; - fixture.update(); - expect(fixture.html).toEqual('
      BeforeAfter
      '); - }); -}); diff --git a/packages/core/test/render3/integration_spec.ts b/packages/core/test/render3/integration_spec.ts index 055c91a249..c38dfa97d0 100644 --- a/packages/core/test/render3/integration_spec.ts +++ b/packages/core/test/render3/integration_spec.ts @@ -9,7 +9,7 @@ import {RendererType2} from '../../src/render/api'; import {getLContext} from '../../src/render3/context_discovery'; import {AttributeMarker, ɵɵadvance, ɵɵattribute, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵhostProperty, ɵɵproperty} from '../../src/render3/index'; -import {ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵprojection, ɵɵprojectionDef, ɵɵtemplate, ɵɵtext, ɵɵtextInterpolate} from '../../src/render3/instructions/all'; +import {ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵprojection, ɵɵprojectionDef, ɵɵtemplate, ɵɵtext} from '../../src/render3/instructions/all'; import {MONKEY_PATCH_KEY_NAME} from '../../src/render3/interfaces/context'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {domRendererFactory3, RElement, Renderer3, RendererFactory3} from '../../src/render3/interfaces/renderer'; @@ -41,156 +41,6 @@ describe('render3 integration test', () => { }); }); }); - - describe('tree', () => { - interface Tree { - beforeLabel?: string; - subTrees?: Tree[]; - afterLabel?: string; - } - - interface ParentCtx { - beforeTree: Tree; - projectedTree: Tree; - afterTree: Tree; - } - - function showLabel(rf: RenderFlags, ctx: {label: string|undefined}) { - if (rf & RenderFlags.Create) { - ɵɵcontainer(0); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(0); - { - if (ctx.label != null) { - let rf1 = ɵɵembeddedViewStart(0, 1, 1); - if (rf1 & RenderFlags.Create) { - ɵɵtext(0); - } - if (rf1 & RenderFlags.Update) { - ɵɵtextInterpolate(ctx.label); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - } - - function showTree(rf: RenderFlags, ctx: {tree: Tree}) { - if (rf & RenderFlags.Create) { - ɵɵcontainer(0); - ɵɵcontainer(1); - ɵɵcontainer(2); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(0); - { - const rf0 = ɵɵembeddedViewStart(0, 1, 0); - { showLabel(rf0, {label: ctx.tree.beforeLabel}); } - ɵɵembeddedViewEnd(); - } - ɵɵcontainerRefreshEnd(); - ɵɵcontainerRefreshStart(1); - { - for (let subTree of ctx.tree.subTrees || []) { - const rf0 = ɵɵembeddedViewStart(0, 3, 0); - { showTree(rf0, {tree: subTree}); } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - ɵɵcontainerRefreshStart(2); - { - const rf0 = ɵɵembeddedViewStart(0, 1, 0); - { showLabel(rf0, {label: ctx.tree.afterLabel}); } - ɵɵembeddedViewEnd(); - } - ɵɵcontainerRefreshEnd(); - } - } - - class ChildComponent { - // TODO(issue/24571): remove '!'. - beforeTree!: Tree; - // TODO(issue/24571): remove '!'. - afterTree!: Tree; - - static ɵfac = () => new ChildComponent; - static ɵcmp = ɵɵdefineComponent({ - selectors: [['child']], - type: ChildComponent, - decls: 3, - vars: 0, - template: - function ChildComponentTemplate( - rf: RenderFlags, ctx: {beforeTree: Tree, afterTree: Tree}) { - if (rf & RenderFlags.Create) { - ɵɵprojectionDef(); - ɵɵcontainer(0); - ɵɵprojection(1); - ɵɵcontainer(2); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(0); - { - const rf0 = ɵɵembeddedViewStart(0, 3, 0); - { showTree(rf0, {tree: ctx.beforeTree}); } - ɵɵembeddedViewEnd(); - } - ɵɵcontainerRefreshEnd(); - ɵɵcontainerRefreshStart(2); - { - const rf0 = ɵɵembeddedViewStart(0, 3, 0); - { showTree(rf0, {tree: ctx.afterTree}); } - ɵɵembeddedViewEnd(); - } - ɵɵcontainerRefreshEnd(); - } - }, - inputs: {beforeTree: 'beforeTree', afterTree: 'afterTree'} - }); - } - - function parentTemplate(rf: RenderFlags, ctx: ParentCtx) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'child'); - { ɵɵcontainer(1); } - ɵɵelementEnd(); - } - if (rf & RenderFlags.Update) { - ɵɵproperty('beforeTree', ctx.beforeTree); - ɵɵproperty('afterTree', ctx.afterTree); - ɵɵcontainerRefreshStart(1); - { - const rf0 = ɵɵembeddedViewStart(0, 3, 0); - { showTree(rf0, {tree: ctx.projectedTree}); } - ɵɵembeddedViewEnd(); - } - ɵɵcontainerRefreshEnd(); - } - } - - it('should work with a tree', () => { - const ctx: ParentCtx = { - beforeTree: {subTrees: [{beforeLabel: 'a'}]}, - projectedTree: {beforeLabel: 'p'}, - afterTree: {afterLabel: 'z'} - }; - const defs = [ChildComponent]; - expect(renderToHtml(parentTemplate, ctx, 2, 2, defs)).toEqual('apz'); - ctx.projectedTree = {subTrees: [{}, {}, {subTrees: [{}, {}]}, {}]}; - ctx.beforeTree.subTrees!.push({afterLabel: 'b'}); - expect(renderToHtml(parentTemplate, ctx, 2, 2, defs)).toEqual('abz'); - ctx.projectedTree.subTrees![1].afterLabel = 'h'; - expect(renderToHtml(parentTemplate, ctx, 2, 2, defs)).toEqual('abhz'); - ctx.beforeTree.subTrees!.push({beforeLabel: 'c'}); - expect(renderToHtml(parentTemplate, ctx, 2, 2, defs)).toEqual('abchz'); - - // To check the context easily: - // console.log(JSON.stringify(ctx)); - }); - }); }); describe('component styles', () => { diff --git a/packages/core/test/render3/lifecycle_spec.ts b/packages/core/test/render3/lifecycle_spec.ts deleted file mode 100644 index b8abf18072..0000000000 --- a/packages/core/test/render3/lifecycle_spec.ts +++ /dev/null @@ -1,120 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {ComponentTemplate, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵproperty} from '../../src/render3/index'; -import {ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵprojection, ɵɵprojectionDef, ɵɵtext} from '../../src/render3/instructions/all'; -import {RenderFlags} from '../../src/render3/interfaces/definition'; -import {NgIf} from './common_with_def'; -import {ComponentFixture, createComponent} from './render_util'; - -describe('lifecycles', () => { - function getParentTemplate(name: string) { - return (rf: RenderFlags, ctx: any) => { - if (rf & RenderFlags.Create) { - ɵɵelement(0, name); - } - if (rf & RenderFlags.Update) { - ɵɵproperty('val', ctx.val); - } - }; - } - - describe('onInit', () => { - let events: string[]; - - beforeEach(() => { - events = []; - }); - - let Comp = createOnInitComponent('comp', (rf: RenderFlags) => { - if (rf & RenderFlags.Create) { - ɵɵprojectionDef(); - ɵɵelementStart(0, 'div'); - { ɵɵprojection(1); } - ɵɵelementEnd(); - } - }, 2); - let Parent = createOnInitComponent('parent', getParentTemplate('comp'), 1, 1, [Comp]); - let ProjectedComp = createOnInitComponent('projected', (rf: RenderFlags) => { - if (rf & RenderFlags.Create) { - ɵɵtext(0, 'content'); - } - }, 1); - - function createOnInitComponent( - name: string, template: ComponentTemplate, decls: number, vars: number = 0, - directives: any[] = []) { - return class Component { - val: string = ''; - ngOnInit() { - if (!this.val) this.val = ''; - events.push(`${name}${this.val}`); - } - - static ɵfac = () => new Component(); - static ɵcmp = ɵɵdefineComponent({ - type: Component, - selectors: [[name]], - decls: decls, - vars: vars, - inputs: {val: 'val'}, - template, - directives: directives - }); - }; - } - - class Directive { - ngOnInit() { - events.push('dir'); - } - - static ɵfac = () => new Directive(); - static ɵdir = ɵɵdefineDirective({type: Directive, selectors: [['', 'dir', '']]}); - } - - const directives = [Comp, Parent, ProjectedComp, Directive, NgIf]; - - it('should call onInit every time a new view is created (if block)', () => { - /** - * % if (!skip) { - * - * % } - */ - const App = createComponent('app', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵcontainer(0); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(0); - { - if (!ctx.skip) { - let rf1 = ɵɵembeddedViewStart(0, 1, 0); - if (rf1 & RenderFlags.Create) { - ɵɵelement(0, 'comp'); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - }, 1, 0, directives); - - const fixture = new ComponentFixture(App); - expect(events).toEqual(['comp']); - - fixture.component.skip = true; - fixture.update(); - expect(events).toEqual(['comp']); - - fixture.component.skip = false; - fixture.update(); - expect(events).toEqual(['comp', 'comp']); - }); - }); -}); diff --git a/packages/core/test/render3/listeners_spec.ts b/packages/core/test/render3/listeners_spec.ts index 67fe696feb..8bb81f6d53 100644 --- a/packages/core/test/render3/listeners_spec.ts +++ b/packages/core/test/render3/listeners_spec.ts @@ -8,14 +8,14 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'; -import {markDirty, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵreference, ɵɵresolveBody, ɵɵresolveDocument, ɵɵselect, ɵɵtextInterpolate} from '../../src/render3/index'; -import {ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵgetCurrentView, ɵɵlistener, ɵɵtext} from '../../src/render3/instructions/all'; +import {ɵɵdefineComponent, ɵɵdefineDirective, ɵɵreference, ɵɵresolveBody, ɵɵresolveDocument} from '../../src/render3/index'; +import {ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵgetCurrentView, ɵɵlistener, ɵɵtext} from '../../src/render3/instructions/all'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {GlobalTargetResolver} from '../../src/render3/interfaces/renderer'; import {ɵɵrestoreView} from '../../src/render3/state'; import {getRendererFactory2} from './imported_renderer2'; -import {ComponentFixture, containerEl, createComponent, getDirectiveOnNode, renderToHtml, requestAnimationFrame, TemplateFixture} from './render_util'; +import {ComponentFixture, containerEl, createComponent, getDirectiveOnNode, renderToHtml, TemplateFixture} from './render_util'; describe('event listeners', () => { @@ -287,277 +287,6 @@ describe('event listeners', () => { expect(ctx.showing).toBe(false); }); - it('should support listeners in views', () => { - /** - * % if (ctx.showing) { - * - * % } - */ - function Template(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵcontainer(0); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(0); - { - if (ctx.showing) { - if (ɵɵembeddedViewStart(1, 2, 0)) { - ɵɵelementStart(0, 'button'); - { - ɵɵlistener('click', function() { - return ctx.onClick(); - }); - ɵɵtext(1, 'Click me'); - } - ɵɵelementEnd(); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - } - - let comp = new MyComp(); - renderToHtml(Template, comp, 1); - const button = containerEl.querySelector('button')!; - - button.click(); - expect(comp.counter).toEqual(1); - - button.click(); - expect(comp.counter).toEqual(2); - - // the listener should be removed when the view is removed - comp.showing = false; - renderToHtml(Template, comp, 1); - button.click(); - expect(comp.counter).toEqual(2); - }); - - it('should destroy listeners in views with renderer2', () => { - /** - * % if (ctx.showing) { - * - * % } - */ - class AppComp { - counter = 0; - showing = true; - - onClick() { - this.counter++; - } - - static ɵfac = () => new AppComp(); - static ɵcmp = ɵɵdefineComponent({ - type: AppComp, - selectors: [['app-comp']], - decls: 1, - vars: 0, - template: - function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵcontainer(0); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(0); - { - if (ctx.showing) { - if (ɵɵembeddedViewStart(0, 2, 0)) { - ɵɵelementStart(0, 'button'); - { - ɵɵlistener('click', function() { - return ctx.onClick(); - }); - ɵɵtext(1, 'Click me'); - } - ɵɵelementEnd(); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - } - }); - } - - const fixture = new ComponentFixture(AppComp, {rendererFactory: getRendererFactory2(document)}); - const comp = fixture.component; - const button = fixture.hostElement.querySelector('button')!; - - button.click(); - expect(comp.counter).toEqual(1); - - button.click(); - expect(comp.counter).toEqual(2); - - // the listener should be removed when the view is removed - comp.showing = false; - fixture.update(); - button.click(); - expect(comp.counter).toEqual(2); - }); - - it('should destroy listeners in for loops', () => { - /** - * % for (let i = 0; i < ctx.buttons; i++) { - * - * % } - */ - class AppComp { - buttons = 2; - counters = [0, 0]; - - onClick(index: number) { - this.counters[index]++; - } - - static ɵfac = () => new AppComp(); - static ɵcmp = ɵɵdefineComponent({ - type: AppComp, - selectors: [['app-comp']], - decls: 1, - vars: 0, - template: - function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵcontainer(0); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(0); - { - for (let i = 0; i < ctx.buttons; i++) { - if (ɵɵembeddedViewStart(0, 2, 0)) { - ɵɵelementStart(0, 'button'); - { - ɵɵlistener('click', function() { - return ctx.onClick(i); - }); - ɵɵtext(1, 'Click me'); - } - ɵɵelementEnd(); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - } - }); - } - - const fixture = new ComponentFixture(AppComp); - const comp = fixture.component; - const buttons = fixture.hostElement.querySelectorAll('button')!; - - buttons[0].click(); - expect(comp.counters).toEqual([1, 0]); - - buttons[1].click(); - expect(comp.counters).toEqual([1, 1]); - - // the listener should be removed when the view is removed - comp.buttons = 0; - fixture.update(); - - buttons[0].click(); - buttons[1].click(); - expect(comp.counters).toEqual([1, 1]); - }); - - it('should destroy listeners in for loops with renderer2', () => { - /** - * % for (let i = 0; i < ctx.buttons; i++) { - * - * {{ counters[i] }} - * % } - */ - class AppComp { - buttons = 2; - counters = [0, 0]; - - onClick(index: number) { - this.counters[index]++; - } - - static ɵfac = () => new AppComp(); - static ɵcmp = ɵɵdefineComponent({ - type: AppComp, - selectors: [['app-comp']], - decls: 1, - vars: 0, - template: - function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵcontainer(0); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(0); - { - for (let i = 0; i < ctx.buttons; i++) { - const rf1 = ɵɵembeddedViewStart(1, 4, 1); - if (rf1 & RenderFlags.Create) { - ɵɵelementStart(0, 'button'); - { - ɵɵlistener('click', function() { - return ctx.onClick(i); - }); - ɵɵtext(1, 'Click me'); - } - ɵɵelementEnd(); - ɵɵelementStart(2, 'div'); - { ɵɵtext(3); } - ɵɵelementEnd(); - } - if (rf1 & RenderFlags.Update) { - ɵɵselect(3); - ɵɵtextInterpolate(ctx.counters[i]); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - } - }); - } - - const fixture = new ComponentFixture(AppComp, {rendererFactory: getRendererFactory2(document)}); - const comp = fixture.component; - const buttons = fixture.hostElement.querySelectorAll('button')!; - const divs = fixture.hostElement.querySelectorAll('div'); - - buttons[0].click(); - expect(comp.counters).toEqual([1, 0]); - expect(divs[0].textContent).toEqual('0'); - expect(divs[1].textContent).toEqual('0'); - - markDirty(comp); - requestAnimationFrame.flush(); - expect(divs[0].textContent).toEqual('1'); - expect(divs[1].textContent).toEqual('0'); - - buttons[1].click(); - expect(comp.counters).toEqual([1, 1]); - expect(divs[0].textContent).toEqual('1'); - expect(divs[1].textContent).toEqual('0'); - - markDirty(comp); - requestAnimationFrame.flush(); - expect(divs[0].textContent).toEqual('1'); - expect(divs[1].textContent).toEqual('1'); - - // the listener should be removed when the view is removed - comp.buttons = 0; - fixture.update(); - - buttons[0].click(); - buttons[1].click(); - expect(comp.counters).toEqual([1, 1]); - }); - it('should support host listeners on components', () => { let events: string[] = []; class MyComp { @@ -718,254 +447,6 @@ describe('event listeners', () => { expect(comp.counter).toEqual(6); }); - it('should destroy listeners in nested views', () => { - /** - * % if (showing) { - * Hello - * % if (button) { - * - * % } - * % } - */ - function Template(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵcontainer(0); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(0); - { - if (ctx.showing) { - let rf1 = ɵɵembeddedViewStart(0, 2, 0); - if (rf1 & RenderFlags.Create) { - ɵɵtext(0, 'Hello'); - ɵɵcontainer(1); - } - if (rf1 & RenderFlags.Update) { - ɵɵcontainerRefreshStart(1); - { - if (ctx.button) { - let rf1 = ɵɵembeddedViewStart(0, 2, 0); - if (rf1 & RenderFlags.Create) { - ɵɵelementStart(0, 'button'); - { - ɵɵlistener('click', function() { - return ctx.onClick(); - }); - ɵɵtext(1, 'Click'); - } - ɵɵelementEnd(); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - } - - const comp = { - showing: true, - counter: 0, - button: true, - onClick: function() { - this.counter++; - } - }; - renderToHtml(Template, comp, 1); - const button = containerEl.querySelector('button')!; - - button.click(); - expect(comp.counter).toEqual(1); - - // the child view listener should be removed when the parent view is removed - comp.showing = false; - renderToHtml(Template, comp, 1); - button.click(); - expect(comp.counter).toEqual(1); - }); - - it('should destroy listeners in component views', () => { - /** - * % if (showing) { - * Hello - * - * - * % } - * - * comp: - * - */ - function Template(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵcontainer(0); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(0); - { - if (ctx.showing) { - let rf1 = ɵɵembeddedViewStart(0, 3, 0); - if (rf1 & RenderFlags.Create) { - ɵɵtext(0, 'Hello'); - ɵɵelement(1, 'comp'); - ɵɵelement(2, 'comp'); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - } - - const ctx = {showing: true}; - renderToHtml(Template, ctx, 1, 0, [MyComp]); - const buttons = containerEl.querySelectorAll('button')!; - - buttons[0].click(); - expect(comps[0]!.counter).toEqual(1); - - buttons[1].click(); - expect(comps[1]!.counter).toEqual(1); - - // the child view listener should be removed when the parent view is removed - ctx.showing = false; - renderToHtml(Template, ctx, 1, 0, [MyComp]); - buttons[0].click(); - buttons[1].click(); - expect(comps[0]!.counter).toEqual(1); - expect(comps[1]!.counter).toEqual(1); - }); - - it('should destroy global listeners in component views', () => { - const ctx = {showing: true}; - - const fixture = new TemplateFixture( - () => { - ɵɵcontainer(0); - }, - () => { - ɵɵcontainerRefreshStart(0); - { - if (ctx.showing) { - let rf1 = ɵɵembeddedViewStart(0, 1, 0); - if (rf1 & RenderFlags.Create) { - ɵɵelement(0, 'comp'); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - }, - 1, 0, [MyCompWithGlobalListeners]); - - const body = fixture.hostElement.ownerDocument!.body; - - body.click(); - expect(events).toEqual(['component - body:click']); - - // the child view listener should be removed when the parent view is removed - ctx.showing = false; - fixture.update(); - - body.click(); - // expecting no changes in events array - expect(events).toEqual(['component - body:click']); - }); - - it('should support listeners with sibling nested containers', () => { - /** - * % if (condition) { - * Hello - * % if (sub1) { - * - * % } - * - * % if (sub2) { - * - * % } - * % } - */ - function Template(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵcontainer(0); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(0); - { - if (ctx.condition) { - let rf1 = ɵɵembeddedViewStart(0, 3, 0); - if (rf1 & RenderFlags.Create) { - ɵɵtext(0, 'Hello'); - ɵɵcontainer(1); - ɵɵcontainer(2); - } - if (rf1 & RenderFlags.Update) { - ɵɵcontainerRefreshStart(1); - { - if (ctx.sub1) { - let rf1 = ɵɵembeddedViewStart(0, 2, 0); - if (rf1 & RenderFlags.Create) { - ɵɵelementStart(0, 'button'); - { - ɵɵlistener('click', function() { - return ctx.counter1++; - }); - ɵɵtext(1, 'Click'); - } - ɵɵelementEnd(); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - ɵɵcontainerRefreshStart(2); - { - if (ctx.sub2) { - let rf1 = ɵɵembeddedViewStart(0, 2, 0); - if (rf1 & RenderFlags.Create) { - ɵɵelementStart(0, 'button'); - { - ɵɵlistener('click', function() { - return ctx.counter2++; - }); - ɵɵtext(1, 'Click'); - } - ɵɵelementEnd(); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - } - - const ctx = {condition: true, counter1: 0, counter2: 0, sub1: true, sub2: true}; - renderToHtml(Template, ctx, 1); - const buttons = containerEl.querySelectorAll('button')!; - - buttons[0].click(); - expect(ctx.counter1).toEqual(1); - - buttons[1].click(); - expect(ctx.counter2).toEqual(1); - - // the child view listeners should be removed when the parent view is removed - ctx.condition = false; - renderToHtml(Template, ctx, 1); - buttons[0].click(); - buttons[1].click(); - expect(ctx.counter1).toEqual(1); - expect(ctx.counter2).toEqual(1); - }); - it('should support local refs in listeners', () => { let compInstance: any; @@ -1019,4 +500,4 @@ describe('event listeners', () => { button.click(); expect(fixture.component.comp).toEqual(compInstance); }); -}); +}); \ No newline at end of file diff --git a/packages/core/test/render3/outputs_spec.ts b/packages/core/test/render3/outputs_spec.ts deleted file mode 100644 index fd009950bd..0000000000 --- a/packages/core/test/render3/outputs_spec.ts +++ /dev/null @@ -1,117 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {EventEmitter} from '@angular/core'; - -import {ɵɵdefineComponent, ɵɵdefineDirective} from '../../src/render3/index'; -import {ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵlistener, ɵɵtext} from '../../src/render3/instructions/all'; -import {RenderFlags} from '../../src/render3/interfaces/definition'; - -import {renderToHtml} from './render_util'; - -describe('outputs', () => { - let buttonToggle: ButtonToggle; - - class ButtonToggle { - change = new EventEmitter(); - resetStream = new EventEmitter(); - - static ɵfac = () => buttonToggle = new ButtonToggle(); - static ɵcmp = ɵɵdefineComponent({ - type: ButtonToggle, - selectors: [['button-toggle']], - template: function(rf: RenderFlags, ctx: any) {}, - decls: 0, - vars: 0, - outputs: {change: 'change', resetStream: 'reset'} - }); - } - - let otherDir: OtherDir; - - class OtherDir { - changeStream = new EventEmitter(); - - static ɵfac = () => otherDir = new OtherDir; - static ɵdir = ɵɵdefineDirective( - {type: OtherDir, selectors: [['', 'otherDir', '']], outputs: {changeStream: 'change'}}); - } - - - const deps = [ButtonToggle, OtherDir]; - - it('should work with outputs at same index in if block', () => { - /** - * // outputs: null - * % if (condition) { - * // outputs: {change: [0, 'change']} - * % } else { - *
      // outputs: {change: [0, - * 'changeStream']} - * % } - */ - function Template(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'button'); - { - ɵɵlistener('click', function() { - return ctx.onClick(); - }); - ɵɵtext(1, 'Click me'); - } - ɵɵelementEnd(); - ɵɵcontainer(2); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(2); - { - if (ctx.condition) { - let rf1 = ɵɵembeddedViewStart(0, 1, 0); - if (rf1 & RenderFlags.Create) { - ɵɵelementStart(0, 'button-toggle'); - { - ɵɵlistener('change', function() { - return ctx.onChange(); - }); - } - ɵɵelementEnd(); - } - ɵɵembeddedViewEnd(); - } else { - if (ɵɵembeddedViewStart(1, 1, 0)) { - ɵɵelementStart(0, 'div', 0); - { - ɵɵlistener('change', function() { - return ctx.onChange(); - }); - } - ɵɵelementEnd(); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - } - - let counter = 0; - const ctx = {condition: true, onChange: () => counter++, onClick: () => {}}; - const attrs = [['otherDir', '']]; - renderToHtml(Template, ctx, 3, 0, deps, null, null, false, attrs); - - buttonToggle!.change.next(); - expect(counter).toEqual(1); - - ctx.condition = false; - renderToHtml(Template, ctx, 3, 0, deps, null, null, false, attrs); - expect(counter).toEqual(1); - - otherDir!.changeStream.next(); - expect(counter).toEqual(2); - }); -}); diff --git a/packages/core/test/render3/providers_spec.ts b/packages/core/test/render3/providers_spec.ts index 6f3e25d003..4149679dfd 100644 --- a/packages/core/test/render3/providers_spec.ts +++ b/packages/core/test/render3/providers_spec.ts @@ -9,8 +9,7 @@ import {Component as _Component, ComponentFactoryResolver, ElementRef, Injectable as _Injectable, InjectFlags, InjectionToken, InjectorType, Provider, RendererFactory2, ViewContainerRef, ɵNgModuleDef as NgModuleDef, ɵɵdefineInjectable, ɵɵdefineInjector, ɵɵinject} from '../../src/core'; import {forwardRef} from '../../src/di/forward_ref'; import {createInjector} from '../../src/di/r3_injector'; -import {injectComponentFactoryResolver, ɵɵadvance, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵdirectiveInject, ɵɵProvidersFeature, ɵɵtextInterpolate1} from '../../src/render3/index'; -import {ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵtext, ɵɵtextInterpolate} from '../../src/render3/instructions/all'; +import {injectComponentFactoryResolver, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵdirectiveInject, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵProvidersFeature, ɵɵtext, ɵɵtextInterpolate1} from '../../src/render3/index'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {NgModuleFactory} from '../../src/render3/ng_module_ref'; import {getInjector} from '../../src/render3/util/discovery_utils'; @@ -983,192 +982,6 @@ describe('providers', () => { }); }); - describe('- embedded views', () => { - it('should have access to viewProviders of the host component', () => { - @Component({ - template: '{{s}}{{n}}', - }) - class Repeated { - constructor(private s: String, private n: Number) {} - - static ɵfac = - () => { - return new Repeated(ɵɵdirectiveInject(String), ɵɵdirectiveInject(Number)); - } - - static ɵcmp = ɵɵdefineComponent({ - type: Repeated, - selectors: [['repeated']], - decls: 2, - vars: 2, - template: - function(fs: RenderFlags, ctx: Repeated) { - if (fs & RenderFlags.Create) { - ɵɵtext(0); - ɵɵtext(1); - } - if (fs & RenderFlags.Update) { - ɵɵtextInterpolate(ctx.s); - ɵɵadvance(1); - ɵɵtextInterpolate(ctx.n); - } - } - }); - } - - @Component({ - template: `
      - % for (let i = 0; i < 3; i++) { - - % } -
      `, - providers: [{provide: Number, useValue: 1, multi: true}], - viewProviders: - [{provide: String, useValue: 'foo'}, {provide: Number, useValue: 2, multi: true}], - }) - class ComponentWithProviders { - static ɵfac = () => new ComponentWithProviders(); - static ɵcmp = ɵɵdefineComponent({ - type: ComponentWithProviders, - selectors: [['component-with-providers']], - decls: 2, - vars: 0, - template: - function(fs: RenderFlags, ctx: ComponentWithProviders) { - if (fs & RenderFlags.Create) { - ɵɵelementStart(0, 'div'); - { ɵɵcontainer(1); } - ɵɵelementEnd(); - } - if (fs & RenderFlags.Update) { - ɵɵcontainerRefreshStart(1); - { - for (let i = 0; i < 3; i++) { - let rf1 = ɵɵembeddedViewStart(1, 1, 0); - { - if (rf1 & RenderFlags.Create) { - ɵɵelement(0, 'repeated'); - } - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - }, - features: - [ - ɵɵProvidersFeature( - [{provide: Number, useValue: 1, multi: true}], - [ - {provide: String, useValue: 'foo'}, - {provide: Number, useValue: 2, multi: true} - ]), - ], - directives: [Repeated] - }); - } - - const fixture = new ComponentFixture(ComponentWithProviders); - expect(fixture.html) - .toEqual( - '
      foo1,2foo1,2foo1,2
      '); - }); - - it('should have access to viewProviders of the repeated component', () => { - @Component({ - template: '{{s}}{{n}}', - providers: [{provide: Number, useValue: 1, multi: true}], - viewProviders: - [{provide: String, useValue: 'bar'}, {provide: Number, useValue: 2, multi: true}] - }) - class Repeated { - constructor(private s: String, private n: Number) {} - - static ɵfac = - () => { - return new Repeated(ɵɵdirectiveInject(String), ɵɵdirectiveInject(Number)); - } - - static ɵcmp = ɵɵdefineComponent({ - type: Repeated, - selectors: [['repeated']], - decls: 2, - vars: 2, - template: - function(fs: RenderFlags, ctx: Repeated) { - if (fs & RenderFlags.Create) { - ɵɵtext(0); - ɵɵtext(1); - } - if (fs & RenderFlags.Update) { - ɵɵtextInterpolate(ctx.s); - ɵɵadvance(1); - ɵɵtextInterpolate(ctx.n); - } - }, - features: - [ - ɵɵProvidersFeature( - [{provide: Number, useValue: 1, multi: true}], - [ - {provide: String, useValue: 'bar'}, - {provide: Number, useValue: 2, multi: true} - ]), - ], - }); - } - - @Component({ - template: `
      - % for (let i = 0; i < 3; i++) { - - % } -
      `, - viewProviders: [{provide: toString, useValue: 'foo'}], - }) - class ComponentWithProviders { - static ɵfac = () => new ComponentWithProviders(); - static ɵcmp = ɵɵdefineComponent({ - type: ComponentWithProviders, - selectors: [['component-with-providers']], - decls: 2, - vars: 0, - template: - function(fs: RenderFlags, ctx: ComponentWithProviders) { - if (fs & RenderFlags.Create) { - ɵɵelementStart(0, 'div'); - { ɵɵcontainer(1); } - ɵɵelementEnd(); - } - if (fs & RenderFlags.Update) { - ɵɵcontainerRefreshStart(1); - { - for (let i = 0; i < 3; i++) { - let rf1 = ɵɵembeddedViewStart(1, 1, 0); - { - if (rf1 & RenderFlags.Create) { - ɵɵelement(0, 'repeated'); - } - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - }, - features: [ɵɵProvidersFeature([], [{provide: String, useValue: 'foo'}])], - directives: [Repeated] - }); - } - - const fixture = new ComponentFixture(ComponentWithProviders); - expect(fixture.html) - .toEqual( - '
      bar1,2bar1,2bar1,2
      '); - }); - }); - describe('- dynamic components dependency resolution', () => { let hostComponent: HostComponent|null = null; @@ -1469,118 +1282,6 @@ describe('providers', () => { expect(injector.get(Some).location).toEqual('From app component'); }); }); - - describe('lifecycles', () => { - it('should execute ngOnDestroy hooks on providers (and only this one)', () => { - const logs: string[] = []; - - @Injectable() - class InjectableWithLifeCycleHooks { - ngOnChanges() { - logs.push('Injectable OnChanges'); - } - ngOnInit() { - logs.push('Injectable OnInit'); - } - ngDoCheck() { - logs.push('Injectable DoCheck'); - } - ngAfterContentInit() { - logs.push('Injectable AfterContentInit'); - } - ngAfterContentChecked() { - logs.push('Injectable AfterContentChecked'); - } - ngAfterViewInit() { - logs.push('Injectable AfterViewInit'); - } - ngAfterViewChecked() { - logs.push('Injectable gAfterViewChecked'); - } - ngOnDestroy() { - logs.push('Injectable OnDestroy'); - } - } - - @Component({template: ``, providers: [InjectableWithLifeCycleHooks]}) - class MyComponent { - constructor(foo: InjectableWithLifeCycleHooks) {} - - static ɵfac = - () => { - return new MyComponent(ɵɵdirectiveInject(InjectableWithLifeCycleHooks)); - } - - static ɵcmp = ɵɵdefineComponent({ - type: MyComponent, - selectors: [['my-comp']], - decls: 1, - vars: 0, - template: - (rf: RenderFlags, ctx: MyComponent) => { - if (rf & RenderFlags.Create) { - ɵɵelement(0, 'span'); - } - }, - features: [ɵɵProvidersFeature([InjectableWithLifeCycleHooks])] - }); - } - - @Component({ - template: ` -
      - % if (ctx.condition) { - - % } -
      - `, - }) - class App { - public condition = true; - - static ɵfac = () => new App(); - static ɵcmp = ɵɵdefineComponent({ - type: App, - selectors: [['app-cmp']], - decls: 2, - vars: 0, - template: - (rf: RenderFlags, ctx: App) => { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'div'); - { ɵɵcontainer(1); } - ɵɵelementEnd(); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(1); - { - if (ctx.condition) { - let rf1 = ɵɵembeddedViewStart(1, 2, 1); - { - if (rf1 & RenderFlags.Create) { - ɵɵelement(0, 'my-comp'); - } - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - }, - directives: [MyComponent] - }); - } - - const fixture = new ComponentFixture(App); - fixture.update(); - expect(fixture.html).toEqual('
      '); - - fixture.component.condition = false; - fixture.update(); - expect(fixture.html).toEqual('
      '); - expect(logs).toEqual(['Injectable OnDestroy']); - }); - }); }); interface ComponentTest { providers?: Provider[]; diff --git a/packages/core/test/render3/pure_function_spec.ts b/packages/core/test/render3/pure_function_spec.ts deleted file mode 100644 index 940b8f2a2d..0000000000 --- a/packages/core/test/render3/pure_function_spec.ts +++ /dev/null @@ -1,89 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {ɵɵdefineComponent, ɵɵproperty, ɵɵselect} from '../../src/render3/index'; -import {ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart} from '../../src/render3/instructions/all'; -import {RenderFlags} from '../../src/render3/interfaces/definition'; -import {ɵɵpureFunction2} from '../../src/render3/pure_function'; -import {getDirectiveOnNode, renderToHtml} from '../../test/render3/render_util'; - - -describe('object literals', () => { - let objectComp: ObjectComp; - - class ObjectComp { - // TODO(issue/24571): remove '!'. - config!: {[key: string]: any}; - - static ɵfac = function ObjectComp_Factory() { - return objectComp = new ObjectComp(); - }; - static ɵcmp = ɵɵdefineComponent({ - type: ObjectComp, - selectors: [['object-comp']], - decls: 0, - vars: 1, - template: function ObjectComp_Template() {}, - inputs: {config: 'config'} - }); - } - - const defs = [ObjectComp]; - - // NOTE: This test cannot be ported to acceptance tests with TestBed because - // the syntax is still unsupported. - it('should support multiple view instances with multiple bindings', () => { - let objectComps: ObjectComp[] = []; - - /** - * % for(let i = 0; i < 2; i++) { - * - * - * % } - */ - function Template(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵcontainer(0); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(0); - { - for (let i = 0; i < 2; i++) { - let rf1 = ɵɵembeddedViewStart(0, 1, 4); - if (rf1 & RenderFlags.Create) { - ɵɵelementStart(0, 'object-comp'); - objectComps.push(getDirectiveOnNode(0)); - ɵɵelementEnd(); - } - if (rf1 & RenderFlags.Update) { - ɵɵselect(0); - ɵɵproperty( - 'config', - ɵɵpureFunction2(1, e0_ff, ctx.configs[i].opacity, ctx.configs[i].duration)); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - } - - const e0_ff = (v1: any, v2: any) => { - return {opacity: v1, duration: v2}; - }; - - const configs = [{opacity: 0, duration: 500}, {opacity: 1, duration: 600}]; - renderToHtml(Template, {configs}, 1, 0, defs); - expect(objectComps[0].config).toEqual({opacity: 0, duration: 500}); - expect(objectComps[1].config).toEqual({opacity: 1, duration: 600}); - - configs[0].duration = 1000; - renderToHtml(Template, {configs}, 1, 0, defs); - expect(objectComps[0].config).toEqual({opacity: 0, duration: 1000}); - expect(objectComps[1].config).toEqual({opacity: 1, duration: 600}); - }); -}); diff --git a/packages/core/test/render3/query_spec.ts b/packages/core/test/render3/query_spec.ts index 9a688ecec0..67e318f404 100644 --- a/packages/core/test/render3/query_spec.ts +++ b/packages/core/test/render3/query_spec.ts @@ -8,9 +8,8 @@ import {ElementRef, QueryList, TemplateRef, ViewContainerRef} from '@angular/core'; -import {EventEmitter} from '../..'; import {AttributeMarker, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵProvidersFeature} from '../../src/render3/index'; -import {ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵdirectiveInject, ɵɵelement, ɵɵelementContainerEnd, ɵɵelementContainerStart, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵtemplate, ɵɵtext} from '../../src/render3/instructions/all'; +import {ɵɵdirectiveInject, ɵɵelement, ɵɵelementContainerEnd, ɵɵelementContainerStart, ɵɵelementEnd, ɵɵelementStart, ɵɵtemplate, ɵɵtext} from '../../src/render3/instructions/all'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {ɵɵcontentQuery, ɵɵloadQuery, ɵɵqueryRefresh, ɵɵviewQuery} from '../../src/render3/query'; import {getLView} from '../../src/render3/state'; @@ -1298,68 +1297,6 @@ describe('query', () => { }); }); - describe('queryList', () => { - it('should be destroyed when the containing view is destroyed', () => { - let queryInstance: QueryList; - - const SimpleComponentWithQuery = createComponent( - 'some-component-with-query', - function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelement(0, 'div', null, 0); - } - }, - 2, 0, [], [], - function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵviewQuery(['foo'], false); - } - if (rf & RenderFlags.Update) { - let tmp: any; - ɵɵqueryRefresh(tmp = ɵɵloadQuery>()) && - (ctx.query = queryInstance = tmp as QueryList); - } - }, - [], [], undefined, [['foo', '']]); - - function createTemplate() { - ɵɵcontainer(0); - } - - function updateTemplate() { - ɵɵcontainerRefreshStart(0); - { - if (condition) { - let rf1 = ɵɵembeddedViewStart(1, 1, 0); - { - if (rf1 & RenderFlags.Create) { - ɵɵelement(0, 'some-component-with-query'); - } - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - - /** - * % if (condition) { - * - * %} - */ - let condition = true; - const t = - new TemplateFixture(createTemplate, updateTemplate, 1, 0, [SimpleComponentWithQuery]); - expect(t.html).toEqual('
      '); - expect((queryInstance!.changes as EventEmitter).closed).toBeFalsy(); - - condition = false; - t.update(); - expect(t.html).toEqual(''); - expect((queryInstance!.changes as EventEmitter).closed).toBeTruthy(); - }); - }); - it('should restore queries if view changes', () => { class SomeDir { constructor(public vcr: ViewContainerRef, public temp: TemplateRef) { @@ -1481,55 +1418,6 @@ describe('query', () => { `Expected content query results to be available when ngAfterContentChecked was called.`); }); - it('should support content queries for directives within repeated embedded views', () => { - /** - * % for (let i = 0; i < 3; i++) { - *
      - * - *
      - * % } - */ - const AppComponent = createComponent( - 'app-component', - function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵcontainer(0); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(0); - { - for (let i = 0; i < 3; i++) { - let rf = ɵɵembeddedViewStart(1, 3, 0); - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'div', 0); - { ɵɵelement(1, 'span', null, 1); } - ɵɵelementEnd(); - } - ɵɵembeddedViewEnd(); - } - } - - ɵɵcontainerRefreshEnd(); - } - }, - 1, 0, [WithContentDirective], [], null, [], [], undefined, - [[AttributeMarker.Bindings, 'with-content'], ['foo', '']]); - - const fixture = new ComponentFixture(AppComponent); - expect(withContentInstance!.foos.length) - .toBe(1, `Expected content query to match .`); - - expect(withContentInstance!.contentInitQuerySnapshot) - .toBe( - 1, - `Expected content query results to be available when ngAfterContentInit was called.`); - - expect(withContentInstance!.contentCheckedQuerySnapshot) - .toBe( - 1, - `Expected content query results to be available when ngAfterContentChecked was called.`); - }); - it('should not match directive host with content queries', () => { /** *
      diff --git a/packages/core/test/render3/renderer_factory_spec.ts b/packages/core/test/render3/renderer_factory_spec.ts index 829fa1c15f..6b873b75ce 100644 --- a/packages/core/test/render3/renderer_factory_spec.ts +++ b/packages/core/test/render3/renderer_factory_spec.ts @@ -8,11 +8,11 @@ import {RendererType2, ViewEncapsulation} from '../../src/core'; import {ɵɵdefineComponent} from '../../src/render3/index'; -import {ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵtext} from '../../src/render3/instructions/all'; +import {ɵɵelement, ɵɵtext} from '../../src/render3/instructions/all'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {getRendererFactory2} from './imported_renderer2'; -import {document, renderToHtml, TemplateFixture} from './render_util'; +import {document, renderToHtml} from './render_util'; describe('renderer factory lifecycle', () => { let logs: string[] = []; @@ -109,101 +109,3 @@ describe('renderer factory lifecycle', () => { expect(logs).toEqual(['begin', 'function_with_component update', 'component update', 'end']); }); }); - -describe('Renderer2 destruction hooks', () => { - const rendererFactory = getRendererFactory2(document); - - it('should call renderer.destroyNode for each node destroyed', () => { - let condition = true; - - function createTemplate() { - ɵɵelementStart(0, 'div'); - { ɵɵcontainer(1); } - ɵɵelementEnd(); - } - - function updateTemplate() { - ɵɵcontainerRefreshStart(1); - { - if (condition) { - let rf1 = ɵɵembeddedViewStart(1, 3, 0); - { - if (rf1 & RenderFlags.Create) { - ɵɵelement(0, 'span'); - ɵɵelement(1, 'span'); - ɵɵelement(2, 'span'); - } - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - - const t = new TemplateFixture( - createTemplate, updateTemplate, 2, 0, null, null, null, rendererFactory); - - expect(t.html).toEqual('
      '); - - condition = false; - t.update(); - expect(t.html).toEqual('
      '); - expect(ngDevMode).toHaveProperties({rendererDestroy: 0, rendererDestroyNode: 3}); - }); - - it('should call renderer.destroy for each component destroyed', () => { - class SimpleComponent { - static ɵfac = () => new SimpleComponent; - static ɵcmp = ɵɵdefineComponent({ - type: SimpleComponent, - encapsulation: ViewEncapsulation.None, - selectors: [['simple']], - decls: 1, - vars: 0, - template: - function(rf: RenderFlags, ctx: SimpleComponent) { - if (rf & RenderFlags.Create) { - ɵɵelement(0, 'span'); - } - }, - }); - } - - let condition = true; - - function createTemplate() { - ɵɵelementStart(0, 'div'); - { ɵɵcontainer(1); } - ɵɵelementEnd(); - } - - function updateTemplate() { - ɵɵcontainerRefreshStart(1); - { - if (condition) { - let rf1 = ɵɵembeddedViewStart(1, 3, 0); - { - if (rf1 & RenderFlags.Create) { - ɵɵelement(0, 'simple'); - ɵɵelement(1, 'span'); - ɵɵelement(2, 'simple'); - } - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - - const t = new TemplateFixture( - createTemplate, updateTemplate, 2, 0, [SimpleComponent], null, null, rendererFactory); - - expect(t.html).toEqual( - '
      '); - - condition = false; - t.update(); - expect(t.html).toEqual('
      '); - expect(ngDevMode).toHaveProperties({rendererDestroy: 2, rendererDestroyNode: 3}); - }); -}); diff --git a/packages/core/test/render3/view_container_ref_spec.ts b/packages/core/test/render3/view_container_ref_spec.ts index 5195c67e5e..7194fb31f9 100644 --- a/packages/core/test/render3/view_container_ref_spec.ts +++ b/packages/core/test/render3/view_container_ref_spec.ts @@ -6,16 +6,16 @@ * found in the LICENSE file at https://angular.io/license */ -import {ChangeDetectorRef, Component as _Component, ComponentFactoryResolver, ComponentRef, ElementRef, QueryList, TemplateRef, ViewContainerRef, ViewRef,} from '../../src/core'; +import {ChangeDetectorRef, Component as _Component, ComponentFactoryResolver, ElementRef, QueryList, TemplateRef, ViewContainerRef, ViewRef} from '../../src/core'; import {ViewEncapsulation} from '../../src/metadata'; -import {injectComponentFactoryResolver, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵlistener, ɵɵloadQuery, ɵɵqueryRefresh, ɵɵviewQuery,} from '../../src/render3/index'; - -import {ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵdirectiveInject, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵtemplate, ɵɵtext,} from '../../src/render3/instructions/all'; +import {injectComponentFactoryResolver, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵlistener, ɵɵloadQuery, ɵɵqueryRefresh, ɵɵviewQuery} from '../../src/render3/index'; +import {ɵɵdirectiveInject, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵtemplate, ɵɵtext} from '../../src/render3/instructions/all'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {RElement} from '../../src/render3/interfaces/renderer'; import {getLView} from '../../src/render3/state'; import {getNativeByIndex} from '../../src/render3/util/view_utils'; -import {ComponentFixture, createComponent, TemplateFixture,} from './render_util'; + +import {ComponentFixture, createComponent, TemplateFixture} from './render_util'; const Component: typeof _Component = function(...args: any[]): any { // In test we use @Component for documentation only so it's safe to mock out the implementation. @@ -133,110 +133,6 @@ describe('ViewContainerRef', () => { directiveInstances![0].insertTpl({}); expect(fixture.html).toEqual('before|AB|after'); }); - - - it('should add embedded views at the right position in the DOM tree (ng-template next to a JS block)', - () => { - let directiveInstance: TestDirective; - - class TestDirective { - static ɵfac = () => directiveInstance = new TestDirective( - ɵɵdirectiveInject(ViewContainerRef as any), ɵɵdirectiveInject(TemplateRef as any)) - - static ɵdir = - ɵɵdefineDirective({type: TestDirective, selectors: [['', 'testdir', '']]}); - - constructor(private _vcRef: ViewContainerRef, private _tplRef: TemplateRef<{}>) {} - - insertTpl(ctx: {}) { - this._vcRef.createEmbeddedView(this._tplRef, ctx); - } - - insertTpl2(ctx: {}) { - const viewRef = this._tplRef.createEmbeddedView(ctx); - this._vcRef.insert(viewRef); - } - - remove(index?: number) { - this._vcRef.remove(index); - } - } - - function EmbeddedTemplateA(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵtext(0, 'A'); - } - } - - /** - * before| - * A - * % if (condition) { - * B - * % } - * |after - */ - class TestComponent { - condition = false; - // TODO(issue/24571): remove '!'. - testDir!: TestDirective; - static ɵfac = () => new TestComponent(); - static ɵcmp = ɵɵdefineComponent({ - type: TestComponent, - encapsulation: ViewEncapsulation.None, - selectors: [['test-cmp']], - decls: 4, - vars: 0, - consts: [['testdir', '']], - template: - (rf: RenderFlags, cmp: TestComponent) => { - if (rf & RenderFlags.Create) { - ɵɵtext(0, 'before|'); - ɵɵtemplate(1, EmbeddedTemplateA, 1, 0, 'ng-template', 0); - ɵɵcontainer(2); - ɵɵtext(3, '|after'); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(2); - { - if (cmp.condition) { - let rf1 = ɵɵembeddedViewStart(0, 1, 0); - { - if (rf1 & RenderFlags.Create) { - ɵɵtext(0, 'B'); - } - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - }, - directives: [TestDirective] - }); - } - - const fixture = new ComponentFixture(TestComponent); - expect(fixture.html).toEqual('before||after'); - - fixture.component.condition = true; - fixture.update(); - expect(fixture.html).toEqual('before|B|after'); - - directiveInstance!.insertTpl({}); - expect(fixture.html).toEqual('before|AB|after'); - - fixture.component.condition = false; - fixture.update(); - expect(fixture.html).toEqual('before|A|after'); - - directiveInstance!.insertTpl2({}); - expect(fixture.html).toEqual('before|AA|after'); - - fixture.component.condition = true; - fixture.update(); - expect(fixture.html).toEqual('before|AAB|after'); - }); }); describe('createComponent', () => {