Skip to content

Commit c345ec3

Browse files
alan-agius4mgechev
authored andcommitted
test(@angular-devkit/build-angular): test HMR using puppeteer
1 parent 16f7767 commit c345ec3

File tree

2 files changed

+164
-18
lines changed

2 files changed

+164
-18
lines changed

packages/angular_devkit/build_angular/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ LARGE_SPECS = {
281281
"@npm//@types/node-fetch",
282282
"@npm//express",
283283
"@npm//node-fetch",
284+
"@npm//puppeteer",
284285
],
285286
},
286287
"extract-i18n": {},

packages/angular_devkit/build_angular/src/dev-server/hmr_spec.ts

Lines changed: 163 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,45 +6,190 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88
import { Architect, BuilderRun } from '@angular-devkit/architect';
9-
import { DevServerBuilderOutput } from '@angular-devkit/build-angular';
10-
import fetch from 'node-fetch'; // tslint:disable-line:no-implicit-dependencies
9+
// tslint:disable: no-implicit-dependencies
10+
import puppeteer from 'puppeteer/lib/cjs/puppeteer';
11+
import { Browser } from 'puppeteer/lib/cjs/puppeteer/common/Browser';
12+
import { Page } from 'puppeteer/lib/cjs/puppeteer/common/Page';
13+
// tslint:enable: no-implicit-dependencies
14+
import { debounceTime, switchMap, take } from 'rxjs/operators';
1115
import { createArchitect, host } from '../test-utils';
1216

13-
describe('Dev Server Builder hmr', () => {
17+
// tslint:disable: no-any
18+
declare const document: any;
19+
declare const getComputedStyle: any;
20+
// tslint:enable: no-any
21+
22+
describe('Dev Server Builder HMR', () => {
1423
const target = { project: 'app', target: 'serve' };
24+
const overrides = { hmr: true, watch: true, port: 0 };
1525
let architect: Architect;
16-
// We use runs like this to ensure it WILL stop the servers at the end of each tests.
26+
let browser: Browser;
27+
let page: Page;
28+
let logs: string[] = [];
1729
let runs: BuilderRun[];
1830

31+
beforeAll(async () => {
32+
browser = await puppeteer.launch({
33+
// MacOSX users need to set the local binary manually because Chrome has lib files with
34+
// spaces in them which Bazel does not support in runfiles
35+
// See: https://github.com/angular/angular-cli/pull/17624
36+
// tslint:disable-next-line: max-line-length
37+
// executablePath: '/Users/<USERNAME>/git/angular-cli/node_modules/puppeteer/.local-chromium/mac-800071/chrome-mac/Chromium.app/Contents/MacOS/Chromium',
38+
args: ['--no-sandbox', '--disable-gpu'],
39+
});
40+
});
41+
42+
afterAll(async () => {
43+
await browser.close();
44+
});
45+
1946
beforeEach(async () => {
2047
await host.initialize().toPromise();
2148
architect = (await createArchitect(host.root())).architect;
49+
50+
logs = [];
2251
runs = [];
52+
page = await browser.newPage();
53+
page.on('console', msg => logs.push(msg.text()));
54+
55+
host.writeMultipleFiles({
56+
'src/app/app.component.html': `
57+
<p>{{title}}</p>
58+
59+
<input type="text">
60+
61+
<select>
62+
<option>one</option>
63+
<option>two</option>
64+
</select>
65+
`,
66+
});
2367
});
68+
2469
afterEach(async () => {
2570
await host.restore().toPromise();
71+
await page.close();
2672
await Promise.all(runs.map(r => r.stop()));
2773
});
2874

29-
it('adds HMR accept code in main JS bundle', async () => {
30-
const run = await architect.scheduleTarget(target, { hmr: true });
75+
it('works for CSS changes', async () => {
76+
const run = await architect.scheduleTarget(target, overrides);
3177
runs.push(run);
32-
const output = await run.result as DevServerBuilderOutput;
33-
expect(output.success).toBe(true);
34-
expect(output.baseUrl).toBe('http://localhost:4200/');
3578

36-
const response = await fetch('http://localhost:4200/main.js');
37-
expect(await response.text()).toContain('// HMR Accept Code');
79+
let buildCount = 0;
80+
await run.output
81+
.pipe(
82+
debounceTime(1000),
83+
switchMap(async buildEvent => {
84+
expect(buildEvent.success).toBe(true);
85+
const url = buildEvent.baseUrl as string;
86+
switch (buildCount) {
87+
case 0:
88+
await page.goto(url);
89+
expect(logs).toContain('[HMR] Waiting for update signal from WDS...');
90+
host.writeMultipleFiles({
91+
'src/styles.css': 'p { color: rgb(255, 255, 0) }',
92+
});
93+
break;
94+
case 1:
95+
expect(logs).toContain('[HMR] Updated modules:');
96+
expect(logs).toContain(`[HMR] css reload %s ${url}styles.css`);
97+
expect(logs).toContain('[HMR] App is up to date.');
98+
99+
const pTag = await page.evaluate(() => {
100+
const el = document.querySelector('p');
101+
102+
return JSON.parse(JSON.stringify(getComputedStyle(el)));
103+
});
104+
105+
expect(pTag.color).toBe('rgb(255, 255, 0)');
106+
break;
107+
}
108+
109+
logs = [];
110+
buildCount++;
111+
}),
112+
take(2),
113+
)
114+
.toPromise();
115+
}, 30000);
116+
117+
it('works for TS changes', async () => {
118+
const run = await architect.scheduleTarget(target, overrides);
119+
runs.push(run);
120+
121+
let buildCount = 0;
122+
await run.output
123+
.pipe(
124+
debounceTime(1000),
125+
switchMap(async buildEvent => {
126+
expect(buildEvent.success).toBe(true);
127+
const url = buildEvent.baseUrl as string;
128+
129+
switch (buildCount) {
130+
case 0:
131+
await page.goto(url);
132+
expect(logs).toContain('[HMR] Waiting for update signal from WDS...');
133+
host.replaceInFile('src/app/app.component.ts', `'app'`, `'app-hmr'`);
134+
break;
135+
case 1:
136+
expect(logs).toContain('[HMR] Updated modules:');
137+
expect(logs).toContain('[HMR] App is up to date.');
138+
139+
const innerText = await page.evaluate(() => document.querySelector('p').innerText);
140+
expect(innerText).toBe('app-hmr');
141+
break;
142+
}
143+
144+
logs = [];
145+
buildCount++;
146+
}),
147+
take(2),
148+
)
149+
.toPromise();
38150
}, 30000);
39151

40-
it('adds HMR accept code for CSS in JS bundles', async () => {
41-
const run = await architect.scheduleTarget(target, { hmr: true });
152+
it('restores input and select values', async () => {
153+
const run = await architect.scheduleTarget(target, overrides);
42154
runs.push(run);
43-
const output = await run.result as DevServerBuilderOutput;
44-
expect(output.success).toBe(true);
45-
expect(output.baseUrl).toBe('http://localhost:4200/');
46155

47-
const response = await fetch('http://localhost:4200/styles.js');
48-
expect(await response.text()).toContain('module.hot.accept(undefined, cssReload);');
156+
let buildCount = 0;
157+
await run.output
158+
.pipe(
159+
debounceTime(1000),
160+
switchMap(async buildEvent => {
161+
expect(buildEvent.success).toBe(true);
162+
const url = buildEvent.baseUrl as string;
163+
switch (buildCount) {
164+
case 0:
165+
await page.goto(url);
166+
expect(logs).toContain('[HMR] Waiting for update signal from WDS...');
167+
await page.evaluate(() => {
168+
document.querySelector('input').value = 'input value';
169+
document.querySelector('select').value = 'two';
170+
});
171+
172+
host.replaceInFile('src/app/app.component.ts', `'app'`, `'app-hmr'`);
173+
break;
174+
case 1:
175+
expect(logs).toContain('[HMR] Updated modules:');
176+
expect(logs).toContain('[HMR] App is up to date.');
177+
expect(logs).toContain('[NG HMR] Restoring input/textarea values.');
178+
expect(logs).toContain('[NG HMR] Restoring selected options.');
179+
180+
const inputValue = await page.evaluate(() => document.querySelector('input').value);
181+
expect(inputValue).toBe('input value');
182+
183+
const selectValue = await page.evaluate(() => document.querySelector('select').value);
184+
expect(selectValue).toBe('two');
185+
break;
186+
}
187+
188+
logs = [];
189+
buildCount++;
190+
}),
191+
take(2),
192+
)
193+
.toPromise();
49194
}, 30000);
50195
});

0 commit comments

Comments
 (0)