From fa3689f43281a42a4e557d5ee7835149619144d9 Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Sun, 7 Mar 2021 14:33:10 -0800 Subject: [PATCH] test(forms): split Forms example app into Reactive and Template-driven ones (#41108) One of the main goals of the bundling tests is to verify that unused symbols are tree-shaken away in prod bundles. Currently both Reactive and Template-driven test apps are merged into one. In order to make these tree-shaking tests even more useful, this commit splits exiting test app into two, so that we can further optimize sets of symbols that should be retained in both scenarios. PR Close #41108 --- .../{forms => forms_reactive}/BUILD.bazel | 4 +- .../bundle.golden_symbols.json | 72 - .../forms_e2e_spec.ts | 24 +- .../{forms => forms_reactive}/index.html | 2 +- .../{forms => forms_reactive}/index.ts | 93 +- .../treeshaking_spec.ts | 2 +- .../forms_template_driven/BUILD.bazel | 85 + .../bundle.golden_symbols.json | 1676 +++++++++++++++++ .../forms_template_driven/forms_e2e_spec.ts | 46 + .../bundling/forms_template_driven/index.html | 32 + .../bundling/forms_template_driven/index.ts | 74 + .../forms_template_driven/treeshaking_spec.ts | 35 + 12 files changed, 1980 insertions(+), 165 deletions(-) rename packages/core/test/bundling/{forms => forms_reactive}/BUILD.bazel (96%) rename packages/core/test/bundling/{forms => forms_reactive}/bundle.golden_symbols.json (95%) rename packages/core/test/bundling/{forms => forms_reactive}/forms_e2e_spec.ts (64%) rename packages/core/test/bundling/{forms => forms_reactive}/index.html (95%) rename packages/core/test/bundling/{forms => forms_reactive}/index.ts (51%) rename packages/core/test/bundling/{forms => forms_reactive}/treeshaking_spec.ts (93%) create mode 100644 packages/core/test/bundling/forms_template_driven/BUILD.bazel create mode 100644 packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json create mode 100644 packages/core/test/bundling/forms_template_driven/forms_e2e_spec.ts create mode 100644 packages/core/test/bundling/forms_template_driven/index.html create mode 100644 packages/core/test/bundling/forms_template_driven/index.ts create mode 100644 packages/core/test/bundling/forms_template_driven/treeshaking_spec.ts diff --git a/packages/core/test/bundling/forms/BUILD.bazel b/packages/core/test/bundling/forms_reactive/BUILD.bazel similarity index 96% rename from packages/core/test/bundling/forms/BUILD.bazel rename to packages/core/test/bundling/forms_reactive/BUILD.bazel index 5c0930c0d9..cd49c7e669 100644 --- a/packages/core/test/bundling/forms/BUILD.bazel +++ b/packages/core/test/bundling/forms_reactive/BUILD.bazel @@ -5,7 +5,7 @@ load("//tools/symbol-extractor:index.bzl", "js_expected_symbol_test") load("@npm//http-server:index.bzl", "http_server") ng_module( - name = "forms", + name = "forms_reactive", srcs = ["index.ts"], tags = [ "ivy-only", @@ -24,7 +24,7 @@ ng_rollup_bundle( "ivy-only", ], deps = [ - ":forms", + ":forms_reactive", "//packages/core", "//packages/forms", "//packages/platform-browser", diff --git a/packages/core/test/bundling/forms/bundle.golden_symbols.json b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json similarity index 95% rename from packages/core/test/bundling/forms/bundle.golden_symbols.json rename to packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json index b6ddd81b75..bad7d9063a 100644 --- a/packages/core/test/bundling/forms/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json @@ -227,9 +227,6 @@ { "name": "FormsExampleModule" }, - { - "name": "FormsModule" - }, { "name": "INJECTOR" }, @@ -389,21 +386,12 @@ { "name": "NgForOfContext" }, - { - "name": "NgForm" - }, { "name": "NgLocaleLocalization" }, { "name": "NgLocalization" }, - { - "name": "NgModel" - }, - { - "name": "NgModelGroup" - }, { "name": "NgModuleFactory" }, @@ -473,9 +461,6 @@ { "name": "RANGE_VALUE_ACCESSOR" }, - { - "name": "REQUIRED_VALIDATOR" - }, { "name": "RadioControlRegistry" }, @@ -512,9 +497,6 @@ { "name": "RendererStyleFlags2" }, - { - "name": "RequiredValidator" - }, { "name": "RootComponent" }, @@ -593,12 +575,6 @@ { "name": "TRANSITION_ID" }, - { - "name": "TemplateFormsComponent" - }, - { - "name": "TemplateFormsComponent_div_14_Template" - }, { "name": "TemplateRef" }, @@ -941,12 +917,6 @@ { "name": "formArrayNameProvider" }, - { - "name": "formControlBinding" - }, - { - "name": "formDirectiveProvider" - }, { "name": "formDirectiveProvider" }, @@ -1094,9 +1064,6 @@ { "name": "getSelectedIndex" }, - { - "name": "getSelectedTNode" - }, { "name": "getSimpleChangesStore" }, @@ -1271,9 +1238,6 @@ { "name": "isPromise" }, - { - "name": "isPropertyUpdated" - }, { "name": "isScheduler" }, @@ -1361,9 +1325,6 @@ { "name": "mergeValidators" }, - { - "name": "modelGroupProvider" - }, { "name": "modules" }, @@ -1391,9 +1352,6 @@ { "name": "nativeParentNode" }, - { - "name": "nextBindingIndex" - }, { "name": "nextNgElementId" }, @@ -1493,9 +1451,6 @@ { "name": "renderComponentOrTemplate" }, - { - "name": "renderStringify" - }, { "name": "renderView" }, @@ -1511,12 +1466,6 @@ { "name": "resolveProvider" }, - { - "name": "resolvedPromise" - }, - { - "name": "resolvedPromise" - }, { "name": "rxSubscriber" }, @@ -1538,9 +1487,6 @@ { "name": "selectIndexInternal" }, - { - "name": "selectValueAccessor" - }, { "name": "setBindingRootForHostBindings" }, @@ -1595,9 +1541,6 @@ { "name": "setUpControl" }, - { - "name": "setUpFormContainer" - }, { "name": "setUpValidators" }, @@ -1622,9 +1565,6 @@ { "name": "subscribeToArray" }, - { - "name": "syncPendingControls" - }, { "name": "throwProviderNotFoundError" }, @@ -1685,12 +1625,6 @@ { "name": "ɵɵProvidersFeature" }, - { - "name": "ɵɵadvance" - }, - { - "name": "ɵɵattribute" - }, { "name": "ɵɵclassProp" }, @@ -1730,15 +1664,9 @@ { "name": "ɵɵlistener" }, - { - "name": "ɵɵnextContext" - }, { "name": "ɵɵproperty" }, - { - "name": "ɵɵtemplate" - }, { "name": "ɵɵtext" } diff --git a/packages/core/test/bundling/forms/forms_e2e_spec.ts b/packages/core/test/bundling/forms_reactive/forms_e2e_spec.ts similarity index 64% rename from packages/core/test/bundling/forms/forms_e2e_spec.ts rename to packages/core/test/bundling/forms_reactive/forms_e2e_spec.ts index eacb295420..65ee1ee746 100644 --- a/packages/core/test/bundling/forms/forms_e2e_spec.ts +++ b/packages/core/test/bundling/forms_reactive/forms_e2e_spec.ts @@ -11,36 +11,16 @@ import {ɵwhenRendered as whenRendered} from '@angular/core'; import {withBody} from '@angular/private/testing'; import * as path from 'path'; -const PACKAGE = 'angular/packages/core/test/bundling/forms'; +const PACKAGE = 'angular/packages/core/test/bundling/forms_reactive'; const BUNDLES = ['bundle.js', 'bundle.min_debug.js', 'bundle.min.js']; -describe('functional test for forms', () => { +describe('functional test for reactive forms', () => { BUNDLES.forEach((bundle) => { describe(`using ${bundle} bundle`, () => { it('should render template form', withBody('', async () => { require(path.join(PACKAGE, bundle)); await (window as any).waitForApp; - // Template forms - const templateFormsComponent = (window as any).templateFormsComponent; - await whenRendered(templateFormsComponent); - - const templateForm = document.querySelector('app-template-forms')!; - - // Check for inputs - const iputs = templateForm.querySelectorAll('input'); - expect(iputs.length).toBe(5); - - // Check for button - const templateButtons = templateForm.querySelectorAll('button'); - expect(templateButtons.length).toBe(1); - expect(templateButtons[0]).toBeDefined(); - - // Make sure button click works - const templateFormSpy = spyOn(templateFormsComponent, 'addCity'); - templateButtons[0].click(); - expect(templateFormSpy).toHaveBeenCalled(); - // Reactive forms const reactiveFormsComponent = (window as any).reactiveFormsComponent; await whenRendered(reactiveFormsComponent); diff --git a/packages/core/test/bundling/forms/index.html b/packages/core/test/bundling/forms_reactive/index.html similarity index 95% rename from packages/core/test/bundling/forms/index.html rename to packages/core/test/bundling/forms_reactive/index.html index 0703f0dbb5..389b4959af 100644 --- a/packages/core/test/bundling/forms/index.html +++ b/packages/core/test/bundling/forms_reactive/index.html @@ -2,7 +2,7 @@ - Angular Forms Example + Angular Reactive Forms Example diff --git a/packages/core/test/bundling/forms/index.ts b/packages/core/test/bundling/forms_reactive/index.ts similarity index 51% rename from packages/core/test/bundling/forms/index.ts rename to packages/core/test/bundling/forms_reactive/index.ts index 4a2e8d1294..b44c4f410b 100644 --- a/packages/core/test/bundling/forms/index.ts +++ b/packages/core/test/bundling/forms_reactive/index.ts @@ -6,76 +6,36 @@ * found in the LICENSE file at https://angular.io/license */ import {Component, NgModule, ɵNgModuleFactory as NgModuleFactory} from '@angular/core'; -import {FormArray, FormBuilder, FormControl, FormGroup, FormsModule, NgForm, ReactiveFormsModule, Validators} from '@angular/forms'; +import {FormArray, FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms'; import {BrowserModule, platformBrowser} from '@angular/platform-browser'; -@Component({ - selector: 'app-template-forms', - template: ` -
-
-
- First Name: - -
-
- Last Name: - -
-
- Subscribe: - -
- -
Disabled:
- -
- City -
- - -
-
`, -}) -class TemplateFormsComponent { - name = {first: 'Nancy', last: 'Drew', subscribed: true}; - addresses = [{city: 'Toronto'}]; - constructor() { - // We use this reference in our test - (window as any).templateFormsComponent = this; - } - - addCity() { - this.addresses.push(({city: ''})); - } -} - @Component({ selector: 'app-reactive-forms', template: ` -
-
- First Name: - -
-
- Last Name: - -
- -
- Subscribe: - -
- -
Disabled:
-
-
-
City:
+ +
+ First Name: +
-
- - `, +
+ Last Name: + +
+ +
+ Subscribe: + +
+ +
Disabled:
+
+
+
City:
+
+
+ + + ` }) class ReactiveFormsComponent { profileForm!: FormGroup; @@ -117,7 +77,6 @@ class ReactiveFormsComponent { @Component({ selector: 'app-root', template: ` - ` }) @@ -125,8 +84,8 @@ class RootComponent { } @NgModule({ - declarations: [RootComponent, TemplateFormsComponent, ReactiveFormsComponent], - imports: [BrowserModule, FormsModule, ReactiveFormsModule] + declarations: [RootComponent, ReactiveFormsComponent], + imports: [BrowserModule, ReactiveFormsModule] }) class FormsExampleModule { ngDoBootstrap(app: any) { diff --git a/packages/core/test/bundling/forms/treeshaking_spec.ts b/packages/core/test/bundling/forms_reactive/treeshaking_spec.ts similarity index 93% rename from packages/core/test/bundling/forms/treeshaking_spec.ts rename to packages/core/test/bundling/forms_reactive/treeshaking_spec.ts index 546a88ee0a..319a0fcb86 100644 --- a/packages/core/test/bundling/forms/treeshaking_spec.ts +++ b/packages/core/test/bundling/forms_reactive/treeshaking_spec.ts @@ -13,7 +13,7 @@ import * as path from 'path'; const UTF8 = { encoding: 'utf-8' }; -const PACKAGE = 'angular/packages/core/test/bundling/forms'; +const PACKAGE = 'angular/packages/core/test/bundling/forms_reactive'; describe('treeshaking with uglify', () => { let content: string; diff --git a/packages/core/test/bundling/forms_template_driven/BUILD.bazel b/packages/core/test/bundling/forms_template_driven/BUILD.bazel new file mode 100644 index 0000000000..caebf192dd --- /dev/null +++ b/packages/core/test/bundling/forms_template_driven/BUILD.bazel @@ -0,0 +1,85 @@ +package(default_visibility = ["//visibility:public"]) + +load("//tools:defaults.bzl", "jasmine_node_test", "ng_module", "ng_rollup_bundle", "ts_library") +load("//tools/symbol-extractor:index.bzl", "js_expected_symbol_test") +load("@npm//http-server:index.bzl", "http_server") + +ng_module( + name = "forms_template_driven", + srcs = ["index.ts"], + tags = [ + "ivy-only", + ], + deps = [ + "//packages/core", + "//packages/forms", + "//packages/platform-browser", + ], +) + +ng_rollup_bundle( + name = "bundle", + entry_point = ":index.ts", + tags = [ + "ivy-only", + ], + deps = [ + ":forms_template_driven", + "//packages/core", + "//packages/forms", + "//packages/platform-browser", + "@npm//rxjs", + ], +) + +ts_library( + name = "test_lib", + testonly = True, + srcs = glob(["*_spec.ts"]), + tags = [ + "ivy-only", + ], + deps = [ + "//packages:types", + "//packages/compiler", + "//packages/core", + "//packages/core/testing", + "//packages/private/testing", + ], +) + +jasmine_node_test( + name = "test", + data = [ + ":bundle.js", + ":bundle.min.js", + ":bundle.min.js.br", + ":bundle.min_debug.js", + ], + tags = [ + "ivy-only", + ], + deps = [":test_lib"], +) + +js_expected_symbol_test( + name = "symbol_test", + src = ":bundle.min_debug.js", + golden = ":bundle.golden_symbols.json", + tags = [ + "ivy-aot", + "ivy-only", + ], +) + +http_server( + name = "prodserver", + data = [ + "index.html", + ":bundle.min.js", + ":bundle.min_debug.js", + ], + tags = [ + "ivy-only", + ], +) diff --git a/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json new file mode 100644 index 0000000000..39c77b5574 --- /dev/null +++ b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json @@ -0,0 +1,1676 @@ +[ + { + "name": "ALLOW_MULTIPLE_PLATFORMS" + }, + { + "name": "APPLICATION_MODULE_PROVIDERS" + }, + { + "name": "APP_BOOTSTRAP_LISTENER" + }, + { + "name": "APP_ID" + }, + { + "name": "APP_ID_RANDOM_PROVIDER" + }, + { + "name": "APP_INITIALIZER" + }, + { + "name": "AbstractControl" + }, + { + "name": "AbstractControlDirective" + }, + { + "name": "AbstractControlStatus" + }, + { + "name": "AbstractFormGroupDirective" + }, + { + "name": "AnonymousSubject" + }, + { + "name": "ApplicationInitStatus" + }, + { + "name": "ApplicationModule" + }, + { + "name": "ApplicationRef" + }, + { + "name": "BROWSER_MODULE_PROVIDERS" + }, + { + "name": "BUILTIN_ACCESSORS" + }, + { + "name": "BrowserDomAdapter" + }, + { + "name": "BrowserGetTestability" + }, + { + "name": "BrowserModule" + }, + { + "name": "CHECKBOX_VALUE_ACCESSOR" + }, + { + "name": "CIRCULAR" + }, + { + "name": "CLEAN_PROMISE" + }, + { + "name": "COMPONENT_REGEX" + }, + { + "name": "COMPOSITION_BUFFER_MODE" + }, + { + "name": "ChangeDetectionStrategy" + }, + { + "name": "CheckboxControlValueAccessor" + }, + { + "name": "CommonModule" + }, + { + "name": "Compiler" + }, + { + "name": "Compiler_compileModuleAndAllComponentsAsync" + }, + { + "name": "Compiler_compileModuleAndAllComponentsSync" + }, + { + "name": "Compiler_compileModuleAndAllComponentsSync__POST_R3__" + }, + { + "name": "Compiler_compileModuleAsync" + }, + { + "name": "Compiler_compileModuleSync" + }, + { + "name": "Compiler_compileModuleSync__POST_R3__" + }, + { + "name": "ComponentFactory" + }, + { + "name": "ComponentFactory" + }, + { + "name": "ComponentFactoryResolver" + }, + { + "name": "ComponentFactoryResolver" + }, + { + "name": "ComponentRef" + }, + { + "name": "ConnectableObservable" + }, + { + "name": "ConnectableSubscriber" + }, + { + "name": "Console" + }, + { + "name": "ControlContainer" + }, + { + "name": "DEFAULT_CURRENCY_CODE" + }, + { + "name": "DEFAULT_VALUE_ACCESSOR" + }, + { + "name": "DOCUMENT" + }, + { + "name": "DOCUMENT" + }, + { + "name": "DefaultDomRenderer2" + }, + { + "name": "DefaultIterableDiffer" + }, + { + "name": "DefaultIterableDifferFactory" + }, + { + "name": "DefaultKeyValueDiffer" + }, + { + "name": "DefaultKeyValueDifferFactory" + }, + { + "name": "DefaultValueAccessor" + }, + { + "name": "DomEventsPlugin" + }, + { + "name": "DomRendererFactory2" + }, + { + "name": "DomSharedStylesHost" + }, + { + "name": "EMAIL_REGEXP" + }, + { + "name": "EMPTY_ARRAY" + }, + { + "name": "EMPTY_OBJ" + }, + { + "name": "EMPTY_PAYLOAD" + }, + { + "name": "EVENT_MANAGER_PLUGINS" + }, + { + "name": "ElementRef" + }, + { + "name": "EmulatedEncapsulationDomRenderer2" + }, + { + "name": "ErrorHandler" + }, + { + "name": "EventEmitter" + }, + { + "name": "EventManager" + }, + { + "name": "EventManagerPlugin" + }, + { + "name": "FormArray" + }, + { + "name": "FormControl" + }, + { + "name": "FormGroup" + }, + { + "name": "FormsExampleModule" + }, + { + "name": "FormsModule" + }, + { + "name": "INJECTOR" + }, + { + "name": "INJECTOR_IMPL" + }, + { + "name": "INJECTOR_SCOPE" + }, + { + "name": "Inject" + }, + { + "name": "InjectFlags" + }, + { + "name": "InjectionToken" + }, + { + "name": "Injector" + }, + { + "name": "InnerSubscriber" + }, + { + "name": "IterableChangeRecord_" + }, + { + "name": "IterableDiffers" + }, + { + "name": "KeyEventsPlugin" + }, + { + "name": "KeyValueChangeRecord_" + }, + { + "name": "KeyValueDiffers" + }, + { + "name": "LOCALE_DATA" + }, + { + "name": "LOCALE_ID" + }, + { + "name": "LOCALE_ID" + }, + { + "name": "LifecycleHooksFeature" + }, + { + "name": "LocaleDataIndex" + }, + { + "name": "MODIFIER_KEYS" + }, + { + "name": "MODIFIER_KEY_GETTERS" + }, + { + "name": "MapOperator" + }, + { + "name": "MapSubscriber" + }, + { + "name": "MergeMapOperator" + }, + { + "name": "MergeMapSubscriber" + }, + { + "name": "ModuleWithComponentFactories" + }, + { + "name": "NAMESPACE_URIS" + }, + { + "name": "NEW_LINE" + }, + { + "name": "NG_ASYNC_VALIDATORS" + }, + { + "name": "NG_COMP_DEF" + }, + { + "name": "NG_DIR_DEF" + }, + { + "name": "NG_ELEMENT_ID" + }, + { + "name": "NG_FACTORY_DEF" + }, + { + "name": "NG_INJECTABLE_DEF" + }, + { + "name": "NG_INJECTOR_DEF" + }, + { + "name": "NG_INJ_DEF" + }, + { + "name": "NG_LOC_ID_DEF" + }, + { + "name": "NG_MOD_DEF" + }, + { + "name": "NG_PIPE_DEF" + }, + { + "name": "NG_PROV_DEF" + }, + { + "name": "NG_VALIDATORS" + }, + { + "name": "NG_VALUE_ACCESSOR" + }, + { + "name": "NOT_FOUND" + }, + { + "name": "NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR" + }, + { + "name": "NOT_YET" + }, + { + "name": "NO_CHANGE" + }, + { + "name": "NULL_INJECTOR" + }, + { + "name": "NUMBER_VALUE_ACCESSOR" + }, + { + "name": "NgControl" + }, + { + "name": "NgControlStatus" + }, + { + "name": "NgControlStatusGroup" + }, + { + "name": "NgForOf" + }, + { + "name": "NgForOfContext" + }, + { + "name": "NgForm" + }, + { + "name": "NgLocaleLocalization" + }, + { + "name": "NgLocalization" + }, + { + "name": "NgModel" + }, + { + "name": "NgModelGroup" + }, + { + "name": "NgModuleFactory" + }, + { + "name": "NgModuleRef" + }, + { + "name": "NgModuleRef" + }, + { + "name": "NgOnChangesFeatureImpl" + }, + { + "name": "NgZone" + }, + { + "name": "NodeInjector" + }, + { + "name": "NodeInjectorFactory" + }, + { + "name": "NoopNgZone" + }, + { + "name": "NullInjector" + }, + { + "name": "NumberValueAccessor" + }, + { + "name": "ObjectUnsubscribedError" + }, + { + "name": "Observable" + }, + { + "name": "Optional" + }, + { + "name": "OuterSubscriber" + }, + { + "name": "PLATFORM_ID" + }, + { + "name": "PLATFORM_INITIALIZER" + }, + { + "name": "PlatformRef" + }, + { + "name": "Plural" + }, + { + "name": "R3Injector" + }, + { + "name": "R3TemplateRef" + }, + { + "name": "R3ViewContainerRef" + }, + { + "name": "RADIO_VALUE_ACCESSOR" + }, + { + "name": "RANGE_VALUE_ACCESSOR" + }, + { + "name": "REQUIRED_VALIDATOR" + }, + { + "name": "RadioControlRegistry" + }, + { + "name": "RadioControlValueAccessor" + }, + { + "name": "RangeValueAccessor" + }, + { + "name": "RecordViewTuple" + }, + { + "name": "RefCountOperator" + }, + { + "name": "RefCountSubscriber" + }, + { + "name": "Renderer2" + }, + { + "name": "RendererFactory2" + }, + { + "name": "RendererStyleFlags2" + }, + { + "name": "RequiredValidator" + }, + { + "name": "RootComponent" + }, + { + "name": "RootViewRef" + }, + { + "name": "RuntimeError" + }, + { + "name": "SCHEDULER" + }, + { + "name": "SELECT_MULTIPLE_VALUE_ACCESSOR" + }, + { + "name": "SELECT_VALUE_ACCESSOR" + }, + { + "name": "SERVER_TRANSITION_PROVIDERS" + }, + { + "name": "SWITCH_ELEMENT_REF_FACTORY" + }, + { + "name": "SWITCH_RENDERER2_FACTORY" + }, + { + "name": "SWITCH_TEMPLATE_REF_FACTORY" + }, + { + "name": "SWITCH_VIEW_CONTAINER_REF_FACTORY" + }, + { + "name": "SafeSubscriber" + }, + { + "name": "Sanitizer" + }, + { + "name": "SelectControlValueAccessor" + }, + { + "name": "SelectMultipleControlValueAccessor" + }, + { + "name": "ShadowDomRenderer" + }, + { + "name": "SharedStylesHost" + }, + { + "name": "SimpleChange" + }, + { + "name": "SkipSelf" + }, + { + "name": "Subject" + }, + { + "name": "SubjectSubscriber" + }, + { + "name": "SubjectSubscription" + }, + { + "name": "Subscriber" + }, + { + "name": "Subscription" + }, + { + "name": "THROW_IF_NOT_FOUND" + }, + { + "name": "TRANSITION_ID" + }, + { + "name": "TemplateFormsComponent" + }, + { + "name": "TemplateFormsComponent_div_14_Template" + }, + { + "name": "TemplateRef" + }, + { + "name": "Testability" + }, + { + "name": "TestabilityRegistry" + }, + { + "name": "USE_VALUE" + }, + { + "name": "UnsubscriptionError" + }, + { + "name": "VERSION" + }, + { + "name": "VE_ViewContainerRef" + }, + { + "name": "Validators" + }, + { + "name": "Version" + }, + { + "name": "ViewContainerRef" + }, + { + "name": "ViewEncapsulation" + }, + { + "name": "ViewEngineTemplateRef" + }, + { + "name": "ViewRef" + }, + { + "name": "_DOM" + }, + { + "name": "_DuplicateItemRecordList" + }, + { + "name": "_DuplicateMap" + }, + { + "name": "_NoopGetTestability" + }, + { + "name": "_NullComponentFactoryResolver" + }, + { + "name": "__extends" + }, + { + "name": "__forward_ref__" + }, + { + "name": "__global" + }, + { + "name": "__globalThis" + }, + { + "name": "__self" + }, + { + "name": "__window" + }, + { + "name": "_chromeNumKeyPadMap" + }, + { + "name": "_currentInjector" + }, + { + "name": "_enable_super_gross_mode_that_will_cause_bad_things" + }, + { + "name": "_global" + }, + { + "name": "_keyMap" + }, + { + "name": "_randomChar" + }, + { + "name": "_renderCompCount" + }, + { + "name": "_symbolIterator" + }, + { + "name": "_testabilityGetter" + }, + { + "name": "addComponentLogic" + }, + { + "name": "addToArray" + }, + { + "name": "addToViewTree" + }, + { + "name": "allocExpando" + }, + { + "name": "allocLFrame" + }, + { + "name": "appendChild" + }, + { + "name": "applyNodes" + }, + { + "name": "applyProjectionRecursive" + }, + { + "name": "applyToElementOrContainer" + }, + { + "name": "applyView" + }, + { + "name": "attachInjectFlag" + }, + { + "name": "attachPatchData" + }, + { + "name": "autoRegisterModuleById" + }, + { + "name": "baseElement" + }, + { + "name": "bindingUpdated" + }, + { + "name": "bloomHasToken" + }, + { + "name": "callHook" + }, + { + "name": "callHooks" + }, + { + "name": "checkStable" + }, + { + "name": "classIndexOf" + }, + { + "name": "cleanUpView" + }, + { + "name": "coerceToAsyncValidator" + }, + { + "name": "coerceToValidator" + }, + { + "name": "collectStylingFromDirectives" + }, + { + "name": "collectStylingFromTAttrs" + }, + { + "name": "composeAsyncValidators" + }, + { + "name": "composeValidators" + }, + { + "name": "computeStaticStyling" + }, + { + "name": "concatStringsWithSpace" + }, + { + "name": "config" + }, + { + "name": "configureViewWithDirective" + }, + { + "name": "connectableObservableDescriptor" + }, + { + "name": "controlPath" + }, + { + "name": "createDirectivesInstances" + }, + { + "name": "createElementNode" + }, + { + "name": "createElementRef" + }, + { + "name": "createInjectorWithoutInjectorInstances" + }, + { + "name": "createLContainer" + }, + { + "name": "createLFrame" + }, + { + "name": "createLView" + }, + { + "name": "createNodeInjector" + }, + { + "name": "createPlatformFactory" + }, + { + "name": "createTView" + }, + { + "name": "decoratePreventDefault" + }, + { + "name": "deepForEach" + }, + { + "name": "defaultErrorLogger" + }, + { + "name": "defaultIterableDiffers" + }, + { + "name": "defaultIterableDiffersFactory" + }, + { + "name": "defaultKeyValueDiffers" + }, + { + "name": "defaultKeyValueDiffersFactory" + }, + { + "name": "defaultScheduler" + }, + { + "name": "destroyLView" + }, + { + "name": "detachMovedView" + }, + { + "name": "detachView" + }, + { + "name": "detectChangesInRootView" + }, + { + "name": "detectChangesInternal" + }, + { + "name": "diPublicInInjector" + }, + { + "name": "domRendererFactory3" + }, + { + "name": "empty" + }, + { + "name": "enterDI" + }, + { + "name": "enterView" + }, + { + "name": "executeCheckHooks" + }, + { + "name": "executeInitAndCheckHooks" + }, + { + "name": "executeListenerWithErrorHandling" + }, + { + "name": "executeTemplate" + }, + { + "name": "executeValidators" + }, + { + "name": "executeViewQueryFn" + }, + { + "name": "extendStatics" + }, + { + "name": "extractDirectiveDef" + }, + { + "name": "extractPipeDef" + }, + { + "name": "fillProperties" + }, + { + "name": "findAttrIndexInNode" + }, + { + "name": "findStylingValue" + }, + { + "name": "flattenStyles" + }, + { + "name": "flattenUnsubscriptionErrors" + }, + { + "name": "forkJoinInternal" + }, + { + "name": "formControlBinding" + }, + { + "name": "formDirectiveProvider" + }, + { + "name": "forwardRef" + }, + { + "name": "from" + }, + { + "name": "fromArray" + }, + { + "name": "generateInitialInputs" + }, + { + "name": "generatePropertyAliases" + }, + { + "name": "getClosureSafeProperty" + }, + { + "name": "getComponentDef" + }, + { + "name": "getComponentLViewByIndex" + }, + { + "name": "getConstant" + }, + { + "name": "getCurrentTNode" + }, + { + "name": "getCurrentTNodePlaceholderOk" + }, + { + "name": "getDOM" + }, + { + "name": "getDebugContext" + }, + { + "name": "getDeclarationTNode" + }, + { + "name": "getFactoryDef" + }, + { + "name": "getFactoryOf" + }, + { + "name": "getFirstLContainer" + }, + { + "name": "getInjectableDef" + }, + { + "name": "getInjectorDef" + }, + { + "name": "getInjectorIndex" + }, + { + "name": "getLView" + }, + { + "name": "getLViewParent" + }, + { + "name": "getLocaleData" + }, + { + "name": "getNativeByTNode" + }, + { + "name": "getNearestLContainer" + }, + { + "name": "getNextLContainer" + }, + { + "name": "getNgModuleDef" + }, + { + "name": "getNodeInjectable" + }, + { + "name": "getNullInjector" + }, + { + "name": "getOrCreateInjectable" + }, + { + "name": "getOrCreateLViewCleanup" + }, + { + "name": "getOrCreateNodeInjectorForNode" + }, + { + "name": "getOrCreateTComponentView" + }, + { + "name": "getOrCreateTNode" + }, + { + "name": "getOrCreateViewRefs" + }, + { + "name": "getOriginalError" + }, + { + "name": "getOwnDefinition" + }, + { + "name": "getParentInjectorIndex" + }, + { + "name": "getParentInjectorLocation" + }, + { + "name": "getParentInjectorView" + }, + { + "name": "getPlatform" + }, + { + "name": "getPreviousIndex" + }, + { + "name": "getProjectionNodes" + }, + { + "name": "getPromiseCtor" + }, + { + "name": "getSelectedIndex" + }, + { + "name": "getSelectedTNode" + }, + { + "name": "getSimpleChangesStore" + }, + { + "name": "getSymbolIterator" + }, + { + "name": "getSymbolIterator" + }, + { + "name": "getTNode" + }, + { + "name": "getTStylingRangeNext" + }, + { + "name": "getTStylingRangePrev" + }, + { + "name": "getTView" + }, + { + "name": "getViewRefs" + }, + { + "name": "handleError" + }, + { + "name": "hasParentInjector" + }, + { + "name": "hasTagAndTypeMatch" + }, + { + "name": "hasValidLength" + }, + { + "name": "hostReportError" + }, + { + "name": "icuContainerIterate" + }, + { + "name": "identity" + }, + { + "name": "includeViewProviders" + }, + { + "name": "incrementInitPhaseFlags" + }, + { + "name": "indexOf" + }, + { + "name": "inheritContentQueries" + }, + { + "name": "inheritHostBindings" + }, + { + "name": "inheritViewQuery" + }, + { + "name": "initTNodeFlags" + }, + { + "name": "injectArgs" + }, + { + "name": "injectInjectorOnly" + }, + { + "name": "injectRootLimpMode" + }, + { + "name": "injectableDefOrInjectorDefFactory" + }, + { + "name": "insertBloom" + }, + { + "name": "instructionState" + }, + { + "name": "invertObject" + }, + { + "name": "invokeHostBindingsInCreationMode" + }, + { + "name": "isAnimationProp" + }, + { + "name": "isArray" + }, + { + "name": "isArrayLike" + }, + { + "name": "isComponentDef" + }, + { + "name": "isComponentHost" + }, + { + "name": "isContentQueryHost" + }, + { + "name": "isCssClassMatching" + }, + { + "name": "isCurrentTNodeParent" + }, + { + "name": "isDirectiveHost" + }, + { + "name": "isEmptyInputValue" + }, + { + "name": "isForwardRef" + }, + { + "name": "isFunction" + }, + { + "name": "isInCheckNoChangesMode" + }, + { + "name": "isInlineTemplate" + }, + { + "name": "isJsObject" + }, + { + "name": "isLContainer" + }, + { + "name": "isLView" + }, + { + "name": "isListLikeIterable" + }, + { + "name": "isNodeMatchingSelector" + }, + { + "name": "isNodeMatchingSelectorList" + }, + { + "name": "isObject" + }, + { + "name": "isObservable" + }, + { + "name": "isOptionsObj" + }, + { + "name": "isPositive" + }, + { + "name": "isPresent" + }, + { + "name": "isProceduralRenderer" + }, + { + "name": "isPromise" + }, + { + "name": "isPromise" + }, + { + "name": "isScheduler" + }, + { + "name": "isStylingMatch" + }, + { + "name": "isStylingValuePresent" + }, + { + "name": "isTypeProvider" + }, + { + "name": "isValueProvider" + }, + { + "name": "iterator" + }, + { + "name": "keyValDiff" + }, + { + "name": "keyValueArrayGet" + }, + { + "name": "keyValueArrayIndexOf" + }, + { + "name": "keyValueArraySet" + }, + { + "name": "leaveDI" + }, + { + "name": "leaveView" + }, + { + "name": "leaveViewLight" + }, + { + "name": "localeEn" + }, + { + "name": "lookupTokenUsingModuleInjector" + }, + { + "name": "makeParamDecorator" + }, + { + "name": "makeRecord" + }, + { + "name": "map" + }, + { + "name": "markAsComponentHost" + }, + { + "name": "markDuplicates" + }, + { + "name": "markViewDirty" + }, + { + "name": "maybeUnwrapEmpty" + }, + { + "name": "maybeUnwrapFn" + }, + { + "name": "maybeWrapInNotSelector" + }, + { + "name": "mergeAll" + }, + { + "name": "mergeErrors" + }, + { + "name": "mergeHostAttribute" + }, + { + "name": "mergeHostAttrs" + }, + { + "name": "mergeValidators" + }, + { + "name": "modelGroupProvider" + }, + { + "name": "modules" + }, + { + "name": "multiFactoryAdd" + }, + { + "name": "multiProvidersFactoryResolver" + }, + { + "name": "multiResolve" + }, + { + "name": "multiViewProvidersFactoryResolver" + }, + { + "name": "nativeAppendChild" + }, + { + "name": "nativeAppendOrInsertBefore" + }, + { + "name": "nativeInsertBefore" + }, + { + "name": "nativeParentNode" + }, + { + "name": "nextBindingIndex" + }, + { + "name": "nextNgElementId" + }, + { + "name": "ngOnChangesSetInput" + }, + { + "name": "noSideEffects" + }, + { + "name": "noop" + }, + { + "name": "noop" + }, + { + "name": "normalizeValidators" + }, + { + "name": "notFoundValueOrThrow" + }, + { + "name": "observable" + }, + { + "name": "onEnter" + }, + { + "name": "onLeave" + }, + { + "name": "optionsReducer" + }, + { + "name": "pickAsyncValidators" + }, + { + "name": "pickValidators" + }, + { + "name": "pipeFromArray" + }, + { + "name": "platformBrowser" + }, + { + "name": "platformCore" + }, + { + "name": "promise" + }, + { + "name": "providerToFactory" + }, + { + "name": "readPatchedLView" + }, + { + "name": "refCount" + }, + { + "name": "refreshComponent" + }, + { + "name": "refreshContentQueries" + }, + { + "name": "refreshView" + }, + { + "name": "registerDestroyHooksIfSupported" + }, + { + "name": "registerHostBindingOpCodes" + }, + { + "name": "registerOnValidatorChange" + }, + { + "name": "registerPostOrderHooks" + }, + { + "name": "rememberChangeHistoryAndInvokeOnChangesHook" + }, + { + "name": "remove" + }, + { + "name": "removeFromArray" + }, + { + "name": "removeListItem" + }, + { + "name": "renderComponent" + }, + { + "name": "renderComponentOrTemplate" + }, + { + "name": "renderStringify" + }, + { + "name": "renderView" + }, + { + "name": "resetPreOrderHookFlags" + }, + { + "name": "resolveDirectives" + }, + { + "name": "resolveForwardRef" + }, + { + "name": "resolveProvider" + }, + { + "name": "resolvedPromise" + }, + { + "name": "resolvedPromise" + }, + { + "name": "rxSubscriber" + }, + { + "name": "saveNameToExportMap" + }, + { + "name": "saveResolvedLocalsInData" + }, + { + "name": "scheduleArray" + }, + { + "name": "scheduleMicroTask" + }, + { + "name": "searchTokensOnInjector" + }, + { + "name": "selectIndexInternal" + }, + { + "name": "setBindingRootForHostBindings" + }, + { + "name": "setCurrentDirectiveIndex" + }, + { + "name": "setCurrentInjector" + }, + { + "name": "setCurrentQueryIndex" + }, + { + "name": "setCurrentTNode" + }, + { + "name": "setDirectiveInputsWhichShadowsStyling" + }, + { + "name": "setIncludeViewProviders" + }, + { + "name": "setInjectImplementation" + }, + { + "name": "setInputsForProperty" + }, + { + "name": "setInputsFromAttrs" + }, + { + "name": "setIsInCheckNoChangesMode" + }, + { + "name": "setLocaleId" + }, + { + "name": "setSelectedIndex" + }, + { + "name": "setTStylingRangeNext" + }, + { + "name": "setTStylingRangeNextDuplicate" + }, + { + "name": "setTStylingRangePrevDuplicate" + }, + { + "name": "setUpAttributes" + }, + { + "name": "setUpControl" + }, + { + "name": "setUpValidators" + }, + { + "name": "shareSubjectFactory" + }, + { + "name": "shouldSearchParent" + }, + { + "name": "stringify" + }, + { + "name": "stringifyCSSSelector" + }, + { + "name": "stringifyForError" + }, + { + "name": "subscribeTo" + }, + { + "name": "subscribeToArray" + }, + { + "name": "throwProviderNotFoundError" + }, + { + "name": "toObservable" + }, + { + "name": "toRefArray" + }, + { + "name": "toTStylingRange" + }, + { + "name": "trackByIdentity" + }, + { + "name": "u" + }, + { + "name": "unwrapRNode" + }, + { + "name": "updateControl" + }, + { + "name": "updateMicroTaskStatus" + }, + { + "name": "updateTransplantedViewCount" + }, + { + "name": "viewAttachedToChangeDetector" + }, + { + "name": "wrapListener" + }, + { + "name": "writeDirectClass" + }, + { + "name": "writeDirectStyle" + }, + { + "name": "ɵAbstractFormGroupDirective_BaseFactory" + }, + { + "name": "ɵInternalFormsSharedModule" + }, + { + "name": "ɵNgNoValidate" + }, + { + "name": "ɵɵInheritDefinitionFeature" + }, + { + "name": "ɵɵNgOnChangesFeature" + }, + { + "name": "ɵɵProvidersFeature" + }, + { + "name": "ɵɵadvance" + }, + { + "name": "ɵɵattribute" + }, + { + "name": "ɵɵclassProp" + }, + { + "name": "ɵɵdefineComponent" + }, + { + "name": "ɵɵdefineDirective" + }, + { + "name": "ɵɵdefineInjectable" + }, + { + "name": "ɵɵdefineInjector" + }, + { + "name": "ɵɵdefineNgModule" + }, + { + "name": "ɵɵdirectiveInject" + }, + { + "name": "ɵɵelement" + }, + { + "name": "ɵɵelementEnd" + }, + { + "name": "ɵɵelementStart" + }, + { + "name": "ɵɵgetInheritedFactory" + }, + { + "name": "ɵɵinject" + }, + { + "name": "ɵɵlistener" + }, + { + "name": "ɵɵnextContext" + }, + { + "name": "ɵɵproperty" + }, + { + "name": "ɵɵtext" + } +] \ No newline at end of file diff --git a/packages/core/test/bundling/forms_template_driven/forms_e2e_spec.ts b/packages/core/test/bundling/forms_template_driven/forms_e2e_spec.ts new file mode 100644 index 0000000000..fb67ed4908 --- /dev/null +++ b/packages/core/test/bundling/forms_template_driven/forms_e2e_spec.ts @@ -0,0 +1,46 @@ +/** + * @license + * Copyright Google LLC 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 '@angular/compiler'; +import {ɵwhenRendered as whenRendered} from '@angular/core'; +import {withBody} from '@angular/private/testing'; +import * as path from 'path'; + +const PACKAGE = 'angular/packages/core/test/bundling/forms_template_driven'; +const BUNDLES = ['bundle.js', 'bundle.min_debug.js', 'bundle.min.js']; + +describe('functional test for forms', () => { + BUNDLES.forEach((bundle) => { + describe(`using ${bundle} bundle`, () => { + it('should render template form', withBody('', async () => { + require(path.join(PACKAGE, bundle)); + await (window as any).waitForApp; + + // Template forms + const templateFormsComponent = (window as any).templateFormsComponent; + await whenRendered(templateFormsComponent); + + const templateForm = document.querySelector('app-template-forms')!; + + // Check for inputs + const iputs = templateForm.querySelectorAll('input'); + expect(iputs.length).toBe(5); + + // Check for button + const templateButtons = templateForm.querySelectorAll('button'); + expect(templateButtons.length).toBe(1); + expect(templateButtons[0]).toBeDefined(); + + // Make sure button click works + const templateFormSpy = spyOn(templateFormsComponent, 'addCity'); + templateButtons[0].click(); + expect(templateFormSpy).toHaveBeenCalled(); + })); + }); + }); +}); diff --git a/packages/core/test/bundling/forms_template_driven/index.html b/packages/core/test/bundling/forms_template_driven/index.html new file mode 100644 index 0000000000..a1c883fd25 --- /dev/null +++ b/packages/core/test/bundling/forms_template_driven/index.html @@ -0,0 +1,32 @@ + + + + + Angular Template-driven Forms Example + + + + + + + + + + diff --git a/packages/core/test/bundling/forms_template_driven/index.ts b/packages/core/test/bundling/forms_template_driven/index.ts new file mode 100644 index 0000000000..fb03c5c0c5 --- /dev/null +++ b/packages/core/test/bundling/forms_template_driven/index.ts @@ -0,0 +1,74 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import {Component, NgModule, ɵNgModuleFactory as NgModuleFactory} from '@angular/core'; +import {FormsModule} from '@angular/forms'; +import {BrowserModule, platformBrowser} from '@angular/platform-browser'; + +@Component({ + selector: 'app-template-forms', + template: ` +
+
+
+ First Name: + +
+
+ Last Name: + +
+
+ Subscribe: + +
+ +
Disabled:
+ +
+ City +
+ + +
+
+ ` +}) +class TemplateFormsComponent { + name = {first: 'Nancy', last: 'Drew', subscribed: true}; + addresses = [{city: 'Toronto'}]; + constructor() { + // We use this reference in our test + (window as any).templateFormsComponent = this; + } + + addCity() { + this.addresses.push({city: ''}); + } +} + +@Component({ + selector: 'app-root', + template: ` + + ` +}) +class RootComponent { +} + +@NgModule({ + declarations: [RootComponent, TemplateFormsComponent], + imports: [BrowserModule, FormsModule], +}) +class FormsExampleModule { + ngDoBootstrap(app: any) { + app.bootstrap(RootComponent); + } +} + +(window as any).waitForApp = platformBrowser().bootstrapModuleFactory( + new NgModuleFactory(FormsExampleModule), {ngZone: 'noop'}); diff --git a/packages/core/test/bundling/forms_template_driven/treeshaking_spec.ts b/packages/core/test/bundling/forms_template_driven/treeshaking_spec.ts new file mode 100644 index 0000000000..5bbd672263 --- /dev/null +++ b/packages/core/test/bundling/forms_template_driven/treeshaking_spec.ts @@ -0,0 +1,35 @@ +/** + * @license + * Copyright Google LLC 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 '@angular/compiler'; +import * as fs from 'fs'; +import * as path from 'path'; + +const UTF8 = { + encoding: 'utf-8' +}; +const PACKAGE = 'angular/packages/core/test/bundling/forms_template_driven'; + +describe('treeshaking with uglify', () => { + let content: string; + // We use the debug version as otherwise symbols/identifiers would be mangled (and the test would + // always pass) + const contentPath = require.resolve(path.join(PACKAGE, 'bundle.min_debug.js')); + beforeAll(() => { + content = fs.readFileSync(contentPath, UTF8); + }); + + it('should drop unused TypeScript helpers', () => { + expect(content).not.toContain('__asyncGenerator'); + }); + + it('should not contain rxjs from commonjs distro', () => { + expect(content).not.toContain('commonjsGlobal'); + expect(content).not.toContain('createCommonjsModule'); + }); +});