|
6 | 6 | * found in the LICENSE file at https://angular.io/license
|
7 | 7 | */
|
8 | 8 | 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'; |
11 | 15 | import { createArchitect, host } from '../test-utils';
|
12 | 16 |
|
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', () => { |
14 | 23 | const target = { project: 'app', target: 'serve' };
|
| 24 | + const overrides = { hmr: true, watch: true, port: 0 }; |
15 | 25 | 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[] = []; |
17 | 29 | let runs: BuilderRun[];
|
18 | 30 |
|
| 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 | + |
19 | 46 | beforeEach(async () => {
|
20 | 47 | await host.initialize().toPromise();
|
21 | 48 | architect = (await createArchitect(host.root())).architect;
|
| 49 | + |
| 50 | + logs = []; |
22 | 51 | 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 | + }); |
23 | 67 | });
|
| 68 | + |
24 | 69 | afterEach(async () => {
|
25 | 70 | await host.restore().toPromise();
|
| 71 | + await page.close(); |
26 | 72 | await Promise.all(runs.map(r => r.stop()));
|
27 | 73 | });
|
28 | 74 |
|
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); |
31 | 77 | 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/'); |
35 | 78 |
|
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(); |
38 | 150 | }, 30000);
|
39 | 151 |
|
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); |
42 | 154 | 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/'); |
46 | 155 |
|
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(); |
49 | 194 | }, 30000);
|
50 | 195 | });
|
0 commit comments