build(forms): create sample forms app (#38044)
This commit creates a sample forms test application to introduce the symbol tests. It serves as a guard to ensure that any future work on the forms package does not unintentionally increase the payload size. PR Close #38044
This commit is contained in:
parent
b518b30dae
commit
7b2e2f5d91
|
@ -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",
|
||||||
|
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",
|
||||||
|
"//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",
|
||||||
|
],
|
||||||
|
)
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,67 @@
|
||||||
|
/**
|
||||||
|
* @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';
|
||||||
|
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('<app-root></app-root>', 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);
|
||||||
|
|
||||||
|
const reactiveForm = document.querySelector('app-reactive-forms')!;
|
||||||
|
|
||||||
|
// Check for inputs
|
||||||
|
const inputs = reactiveForm.querySelectorAll('input');
|
||||||
|
expect(inputs.length).toBe(5);
|
||||||
|
|
||||||
|
// Check for button
|
||||||
|
const reactiveButtons = reactiveForm.querySelectorAll('button');
|
||||||
|
expect(reactiveButtons.length).toBe(1);
|
||||||
|
expect(reactiveButtons[0]).toBeDefined();
|
||||||
|
|
||||||
|
// Make sure button click works
|
||||||
|
const reactiveFormSpy = spyOn(reactiveFormsComponent, 'addCity').and.callThrough();
|
||||||
|
reactiveButtons[0].click();
|
||||||
|
expect(reactiveFormSpy).toHaveBeenCalled();
|
||||||
|
expect(reactiveFormsComponent.addresses.length).toBe(2);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,32 @@
|
||||||
|
<!doctype html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Angular Forms Example</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- The Angular application will be bootstrapped into this element. -->
|
||||||
|
|
||||||
|
<app-root></app-root>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Script tag which bootstraps the application. Use `?debug` in URL to select
|
||||||
|
the debug version of the script.
|
||||||
|
|
||||||
|
There are two scripts sources: `bundle.min.js` and `bundle.min_debug.js` You can
|
||||||
|
switch between which bundle the browser loads to experiment with the application.
|
||||||
|
|
||||||
|
- `bundle.min.js`: Is what the site would serve to their users. It has gone
|
||||||
|
through rollup, build-optimizer, and uglify with tree shaking.
|
||||||
|
- `bundle.min_debug.js`: Is what the developer would like to see when debugging
|
||||||
|
the application. It has also done through full pipeline of rollup, build-optimizer,
|
||||||
|
and uglify, however special flags were passed to uglify to prevent inlining and
|
||||||
|
property renaming.
|
||||||
|
-->
|
||||||
|
<script>
|
||||||
|
document.write('<script src="' +
|
||||||
|
(document.location.search.endsWith('debug') ? '/bundle.min_debug.js' : '/bundle.min.js') +
|
||||||
|
'"></' + 'script>');
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,138 @@
|
||||||
|
/**
|
||||||
|
* @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 {FormArray, FormBuilder, FormControl, FormGroup, FormsModule, NgForm, ReactiveFormsModule, Validators} from '@angular/forms';
|
||||||
|
import {BrowserModule, platformBrowser} from '@angular/platform-browser';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-template-forms',
|
||||||
|
template: `
|
||||||
|
<form novalidate>
|
||||||
|
<div ngModelGroup="profileForm">
|
||||||
|
<div>
|
||||||
|
First Name:
|
||||||
|
<input name="first" ngModel required />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Last Name:
|
||||||
|
<input name="last" ngModel />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Subscribe:
|
||||||
|
<input name="subscribed" type="checkbox" ngModel />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>Disabled: <input name="foo" ngModel disabled /></div>
|
||||||
|
|
||||||
|
<div *ngFor="let city of addresses; let i = index">
|
||||||
|
City <input [(ngModel)]="addresses[i].city" name="name" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button (click)="addCity()">Add City</button>
|
||||||
|
</div>
|
||||||
|
</form>`,
|
||||||
|
})
|
||||||
|
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: `
|
||||||
|
<form [formGroup]="profileForm">
|
||||||
|
<div>
|
||||||
|
First Name:
|
||||||
|
<input type="text" formControlName="firstName" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Last Name:
|
||||||
|
<input type="text" formControlName="lastName" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
Subscribe:
|
||||||
|
<input type="checkbox" formControlName="subscribed" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>Disabled: <input formControlName="disabledInput" /></div>
|
||||||
|
<div formArrayName="addresses">
|
||||||
|
<div *ngFor="let item of itemControls; let i = index" [formGroupName]="i">
|
||||||
|
<div>City: <input formControlName="city" /></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button (click)="addCity()">Add City</button>
|
||||||
|
</form>`,
|
||||||
|
})
|
||||||
|
class ReactiveFormsComponent {
|
||||||
|
profileForm!: FormGroup;
|
||||||
|
addresses!: FormArray;
|
||||||
|
|
||||||
|
get itemControls() {
|
||||||
|
return (this.profileForm.get('addresses') as FormArray).controls;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(private formBuilder: FormBuilder) {
|
||||||
|
// We use this reference in our test
|
||||||
|
(window as any).reactiveFormsComponent = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.profileForm = new FormGroup({
|
||||||
|
firstName: new FormControl('', Validators.required),
|
||||||
|
lastName: new FormControl(''),
|
||||||
|
addresses: new FormArray([]),
|
||||||
|
subscribed: new FormControl(),
|
||||||
|
disabledInput: new FormControl({value: '', disabled: true}),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addCity();
|
||||||
|
}
|
||||||
|
|
||||||
|
createItem(): FormGroup {
|
||||||
|
return this.formBuilder.group({
|
||||||
|
city: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addCity(): void {
|
||||||
|
this.addresses = this.profileForm.get('addresses') as FormArray;
|
||||||
|
this.addresses.push(this.createItem());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-root',
|
||||||
|
template: `
|
||||||
|
<app-template-forms></app-template-forms>
|
||||||
|
<app-reactive-forms></app-reactive-forms>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
class RootComponent {
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [RootComponent, TemplateFormsComponent, ReactiveFormsComponent],
|
||||||
|
imports: [BrowserModule, FormsModule, ReactiveFormsModule]
|
||||||
|
})
|
||||||
|
class FormsExampleModule {
|
||||||
|
ngDoBootstrap(app: any) {
|
||||||
|
app.bootstrap(RootComponent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(window as any).waitForApp = platformBrowser().bootstrapModuleFactory(
|
||||||
|
new NgModuleFactory(FormsExampleModule), {ngZone: 'noop'});
|
|
@ -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';
|
||||||
|
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue