@salesforce/pwa-kit-dev 3.8.0-preview.0-basepath → 3.8.0-preview.2-basepath

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/configs/babel/babel-config.js +33 -0
  2. package/configs/eslint/README.md +21 -0
  3. package/configs/eslint/eslint-config.js +11 -0
  4. package/configs/eslint/index.js +11 -0
  5. package/configs/eslint/no-react.js +18 -0
  6. package/configs/eslint/partials/base.js +38 -0
  7. package/configs/eslint/partials/jest.js +24 -0
  8. package/configs/eslint/partials/react.js +29 -0
  9. package/configs/eslint/partials/typescript-permit-any.js +31 -0
  10. package/configs/eslint/partials/typescript.js +17 -0
  11. package/configs/eslint/recommended.js +20 -0
  12. package/configs/eslint/safe-types.js +20 -0
  13. package/configs/jest/jest-babel-transform.js +19 -0
  14. package/configs/jest/jest.config.js +40 -0
  15. package/configs/jest/mocks/fileMock.js +9 -0
  16. package/configs/jest/mocks/styleMock.js +9 -0
  17. package/configs/jest/mocks/svgMock.js +11 -0
  18. package/configs/webpack/config-names.js +19 -0
  19. package/configs/webpack/config.js +406 -0
  20. package/configs/webpack/overrides-plugin.js +168 -0
  21. package/configs/webpack/overrides-plugin.test.js +388 -0
  22. package/configs/webpack/plugins.js +86 -0
  23. package/configs/webpack/test/overrides/exists.js +7 -0
  24. package/configs/webpack/test/overrides/newExtension.js +7 -0
  25. package/configs/webpack/test/overrides/path/data.js +7 -0
  26. package/configs/webpack/test/overrides/path/index.js +7 -0
  27. package/configs/webpack/test/overrides/path/index.mock.js +7 -0
  28. package/configs/webpack/test/overrides/path/nested/icon.svg +0 -0
  29. package/configs/webpack/test/package.json +13 -0
  30. package/configs/webpack/utils.js +23 -0
  31. package/package.json +5 -4
  32. package/ssr/server/build-dev-server.js +468 -0
  33. package/ssr/server/build-dev-server.test.js +661 -0
  34. package/ssr/server/loading-screen/css/main.css +272 -0
  35. package/ssr/server/loading-screen/css/normalize.css +349 -0
  36. package/ssr/server/loading-screen/img/cloud-1.svg +1 -0
  37. package/ssr/server/loading-screen/img/cloud-2.svg +1 -0
  38. package/ssr/server/loading-screen/img/cloud-3.svg +1 -0
  39. package/ssr/server/loading-screen/img/cloud.svg +1 -0
  40. package/ssr/server/loading-screen/img/codey-arm.svg +1 -0
  41. package/ssr/server/loading-screen/img/codey-bear.svg +1 -0
  42. package/ssr/server/loading-screen/img/codey-bg.svg +1 -0
  43. package/ssr/server/loading-screen/img/codey-cloud.svg +1 -0
  44. package/ssr/server/loading-screen/img/codey-search.svg +1 -0
  45. package/ssr/server/loading-screen/img/codey.svg +1 -0
  46. package/ssr/server/loading-screen/img/codeyCarry.svg +1 -0
  47. package/ssr/server/loading-screen/img/devDocumentation.svg +1 -0
  48. package/ssr/server/loading-screen/img/devGithub.svg +1 -0
  49. package/ssr/server/loading-screen/img/devTrailhead.svg +1 -0
  50. package/ssr/server/loading-screen/img/logo.svg +1 -0
  51. package/ssr/server/loading-screen/img/slds_spinner_brand_9EA9F1.gif +0 -0
  52. package/ssr/server/loading-screen/index.html +129 -0
  53. package/ssr/server/test_fixtures/app/main.js +6 -0
  54. package/ssr/server/test_fixtures/app/static/favicon.ico +0 -0
  55. package/ssr/server/test_fixtures/localhost.pem +45 -0
  56. package/utils/mocks/dependency-tree-mock-data.js +127 -0
  57. package/utils/script-utils.js +497 -0
  58. package/utils/script-utils.test.js +496 -0
  59. package/utils/test-fixtures/minimal-built-app/ssr.js +9 -0
  60. package/utils/test-fixtures/minimal-built-app/static/favicon.ico +0 -0
@@ -0,0 +1,661 @@
1
+ "use strict";
2
+
3
+ var _constants = require("@salesforce/pwa-kit-runtime/ssr/server/constants");
4
+ var _ssrProxying = require("@salesforce/pwa-kit-runtime/utils/ssr-proxying");
5
+ var _express = require("@salesforce/pwa-kit-runtime/ssr/server/express");
6
+ var _nodeFetch = _interopRequireDefault(require("node-fetch"));
7
+ var _supertest = _interopRequireDefault(require("supertest"));
8
+ var _buildDevServer = require("./build-dev-server");
9
+ var _os = _interopRequireDefault(require("os"));
10
+ var _path = _interopRequireDefault(require("path"));
11
+ var _http = _interopRequireDefault(require("http"));
12
+ var _https = _interopRequireDefault(require("https"));
13
+ var _nock = _interopRequireDefault(require("nock"));
14
+ var _zlib = _interopRequireDefault(require("zlib"));
15
+ var _fsExtra = _interopRequireDefault(require("fs-extra"));
16
+ var _rimraf = _interopRequireDefault(require("rimraf"));
17
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
18
+ function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
19
+ function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
20
+ function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
21
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
22
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
23
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
24
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
25
+ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } /*
26
+ * Copyright (c) 2022, Salesforce, Inc.
27
+ * All rights reserved.
28
+ * SPDX-License-Identifier: BSD-3-Clause
29
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
30
+ */
31
+ const TEST_PORT = 3444;
32
+ const testFixtures = _path.default.resolve(__dirname, 'test_fixtures');
33
+
34
+ // Mocks methods on the DevServerFactory to skip setting
35
+ // up Webpack's dev middleware – a massive simplification
36
+ // for testing.
37
+ const NoWebpackDevServerFactory = _objectSpread(_objectSpread({}, _buildDevServer.DevServerFactory), {}, {
38
+ _addSDKInternalHandlers() {
39
+ // Override default implementation with no-op
40
+ },
41
+ _getRequestProcessor() {
42
+ // Override default implementation with no-op
43
+ }
44
+ });
45
+ const httpAgent = new _http.default.Agent({});
46
+
47
+ /**
48
+ * An HTTPS.Agent that allows self-signed certificates
49
+ * @type {module:https.Agent}
50
+ */
51
+ const httpsAgent = new _https.default.Agent({
52
+ rejectUnauthorized: false
53
+ });
54
+
55
+ /**
56
+ * Fetch and ignore self-signed certificate errors.
57
+ */
58
+ const insecureFetch = (url, opts) => {
59
+ return (0, _nodeFetch.default)(url, _objectSpread(_objectSpread({}, opts), {}, {
60
+ agent: _parsedURL => _parsedURL.protocol === 'https:' ? httpsAgent : httpAgent
61
+ }));
62
+ };
63
+ const mobifyConfig = {
64
+ ssrEnabled: true,
65
+ ssrOnly: ['main.js.map', 'ssr.js', 'ssr.js.map'],
66
+ ssrShared: ['main.js', 'ssr-loader.js', 'worker.js'],
67
+ ssrParameters: {
68
+ proxyConfigs: [{
69
+ protocol: 'https',
70
+ host: 'test.proxy.com',
71
+ path: 'base'
72
+ }, {
73
+ protocol: 'https',
74
+ // This is intentionally an unreachable host
75
+ host: '0.0.0.0',
76
+ path: 'base2'
77
+ }, {
78
+ protocol: 'https',
79
+ host: 'test.proxy.com',
80
+ path: 'base3',
81
+ caching: true
82
+ }]
83
+ }
84
+ };
85
+ const opts = (overrides = {}) => {
86
+ const defaults = {
87
+ buildDir: _path.default.join(testFixtures, 'build'),
88
+ mobify: mobifyConfig,
89
+ quiet: true,
90
+ port: TEST_PORT,
91
+ protocol: 'http',
92
+ sslFilePath: _path.default.join(testFixtures, 'localhost.pem')
93
+ };
94
+ return _objectSpread(_objectSpread({}, defaults), overrides);
95
+ };
96
+ jest.mock('@salesforce/pwa-kit-runtime/utils/ssr-config', () => {
97
+ return {
98
+ getConfig: () => mobifyConfig
99
+ };
100
+ });
101
+ describe('DevServer error handlers', () => {
102
+ const expectServerErrorHandled = (error, times) => {
103
+ const proc = {
104
+ exit: jest.fn()
105
+ };
106
+ const devserver = {
107
+ close: jest.fn()
108
+ };
109
+ const handler = (0, _buildDevServer.makeErrorHandler)(proc, devserver, jest.fn());
110
+ handler({
111
+ code: error
112
+ });
113
+ expect(devserver.close).toHaveBeenCalledTimes(times);
114
+ };
115
+ test('should exit the current process if the requested port is in use', () => {
116
+ expectServerErrorHandled('EADDRINUSE', 1);
117
+ });
118
+ test('should ignore errors other than EADDRINUSE', () => {
119
+ expectServerErrorHandled('EACCES', 0);
120
+ });
121
+ });
122
+ describe('DevServer startup', () => {
123
+ test('_createApp creates an express app', () => {
124
+ const app = NoWebpackDevServerFactory._createApp(opts());
125
+ expect(app.options.defaultCacheControl).toEqual(_constants.NO_CACHE);
126
+ });
127
+ test(`_createApp validates missing or invalid field "protocol"`, () => {
128
+ expect(() => NoWebpackDevServerFactory._createApp(opts({
129
+ protocol: 'ssl'
130
+ }))).toThrow();
131
+ });
132
+ });
133
+ describe('DevServer loading page', () => {
134
+ test('should redirect to the loading screen with an HTTP 302', /*#__PURE__*/_asyncToGenerator(function* () {
135
+ const options = opts();
136
+ const app = NoWebpackDevServerFactory._createApp(options);
137
+ app.use('/', _buildDevServer.DevServerFactory._redirectToLoadingScreen);
138
+ const expectedPath = '/__mrt/loading-screen/index.html?loading=1&path=%2F';
139
+ return (0, _supertest.default)(app).get('/').expect(302) // Expecting the 302 temporary redirect (not 301)
140
+ .then(response => {
141
+ expect(response.headers.location).toBe(expectedPath);
142
+ });
143
+ }));
144
+ test('should contain path query parameter with encoded original url', /*#__PURE__*/_asyncToGenerator(function* () {
145
+ const options = opts();
146
+ const app = NoWebpackDevServerFactory._createApp(options);
147
+ app.use('/', _buildDevServer.DevServerFactory._redirectToLoadingScreen);
148
+ const originalUrl = '/global/en-GB/product/25519318M?color=JJ8UTXX&size=9MD&pid=701642923497M';
149
+ const expectedPath = `/__mrt/loading-screen/index.html?loading=1&path=${encodeURIComponent(originalUrl)}`;
150
+ return (0, _supertest.default)(app).get(originalUrl).then(response => {
151
+ expect(response.headers.location).toBe(expectedPath);
152
+ });
153
+ }));
154
+ });
155
+ describe('DevServer request processor support', () => {
156
+ const helloWorld = '<div>hello world</div>';
157
+ let route;
158
+ beforeEach(() => {
159
+ route = jest.fn().mockImplementation((req, res) => {
160
+ res.send(helloWorld);
161
+ });
162
+ });
163
+ afterEach(() => {
164
+ route = undefined;
165
+ });
166
+ test('SSRServer supports the request-processor and request class', () => {
167
+ const ServerFactory = _objectSpread(_objectSpread({}, NoWebpackDevServerFactory), {
168
+ _getRequestProcessor() {
169
+ return {
170
+ processRequest: ({
171
+ getRequestClass,
172
+ setRequestClass
173
+ }) => {
174
+ console.log(`getRequestClass returns ${getRequestClass()}`);
175
+ setRequestClass('bot');
176
+ return {
177
+ path: '/altered',
178
+ querystring: 'foo=bar'
179
+ };
180
+ }
181
+ };
182
+ }
183
+ });
184
+ const app = ServerFactory._createApp(opts());
185
+ app.get('/*', route);
186
+ return (0, _supertest.default)(app).get('/').expect(200).then(response => {
187
+ const requestClass = response.headers[_ssrProxying.X_MOBIFY_REQUEST_CLASS];
188
+ expect(requestClass).toBe('bot');
189
+ expect(route).toHaveBeenCalled();
190
+ });
191
+ });
192
+ test('SSRServer handles no request processor', () => {
193
+ const ServerFactory = _objectSpread(_objectSpread({}, NoWebpackDevServerFactory), {
194
+ _getRequestProcessor() {
195
+ return null;
196
+ }
197
+ });
198
+ const options = opts();
199
+ const app = ServerFactory._createApp(options);
200
+ app.get('/*', route);
201
+ return (0, _supertest.default)(app).get('/').expect(200).then(response => {
202
+ expect(response.headers[_ssrProxying.X_MOBIFY_REQUEST_CLASS]).toBeUndefined();
203
+ expect(route).toHaveBeenCalled();
204
+ expect(response.text).toEqual(helloWorld);
205
+ });
206
+ });
207
+ test('SSRServer handles a broken request processor', () => {
208
+ // This is a broken because processRequest is required to return
209
+ // {path, querystring}, but returns undefined
210
+
211
+ const ServerFactory = _objectSpread(_objectSpread({}, NoWebpackDevServerFactory), {
212
+ _getRequestProcessor() {
213
+ return {
214
+ processRequest: () => {
215
+ return;
216
+ }
217
+ };
218
+ }
219
+ });
220
+ const app = ServerFactory._createApp(opts());
221
+ app.get('/*', route);
222
+ return (0, _supertest.default)(app).get('/').expect(500).then(() => {
223
+ expect(route).not.toHaveBeenCalled();
224
+ });
225
+ });
226
+ });
227
+ describe('DevServer listening on http/https protocol', () => {
228
+ let server;
229
+ let originalEnv = process.env;
230
+ beforeEach(() => {
231
+ process.env = _objectSpread({}, originalEnv);
232
+ });
233
+ afterEach(() => {
234
+ var _server2;
235
+ (_server2 = server) === null || _server2 === void 0 ? void 0 : _server2.close();
236
+ process.env = originalEnv;
237
+ });
238
+ const cases = [{
239
+ options: {
240
+ protocol: 'http'
241
+ },
242
+ env: {},
243
+ name: 'listens on http (set in options)'
244
+ }, {
245
+ options: {
246
+ protocol: 'https'
247
+ },
248
+ env: {},
249
+ name: 'listens on https (set in options)'
250
+ }, {
251
+ options: {},
252
+ env: {
253
+ DEV_SERVER_PROTOCOL: 'http'
254
+ },
255
+ name: 'listens on http (set in env var)'
256
+ }, {
257
+ options: {},
258
+ env: {
259
+ DEV_SERVER_PROTOCOL: 'https'
260
+ },
261
+ name: 'listens on https (set in env var)'
262
+ }];
263
+ cases.forEach(({
264
+ options,
265
+ env,
266
+ name
267
+ }) => {
268
+ const protocol = options.protocol || env.DEV_SERVER_PROTOCOL;
269
+ test(`${name}`, /*#__PURE__*/_asyncToGenerator(function* () {
270
+ process.env = _objectSpread(_objectSpread({}, process.env), env);
271
+ const {
272
+ server: _server
273
+ } = NoWebpackDevServerFactory.createHandler(opts(options), app => {
274
+ app.get('/*', (req, res) => {
275
+ res.send('<div>hello world</div>');
276
+ });
277
+ });
278
+ server = _server;
279
+ const response = yield insecureFetch(`${protocol}://localhost:${TEST_PORT}`);
280
+ expect(response.ok).toBe(true);
281
+ }));
282
+ });
283
+ });
284
+ test('SSRServer proxying handles empty path', () => {
285
+ // This tests a specific fault that occurred when making a proxy request to mobify/proxy/base/.
286
+ const location = '/another/path';
287
+
288
+ // Create a mock response from the proxied backend
289
+ const nockRedirect = (0, _nock.default)('https://test.proxy.com').get('/').reply(301, '', {
290
+ Location: location
291
+ });
292
+ const options = opts();
293
+
294
+ // We expect the Express app to rewrite redirect responses
295
+ const rewritten = `${options.protocol}://localhost:${options.port}/mobify/proxy/base${location}`;
296
+ const app = NoWebpackDevServerFactory._createApp(options);
297
+ return (0, _supertest.default)(app).get('/mobify/proxy/base/').expect(301).expect('Location', rewritten)
298
+ // Expected to hit the backend
299
+ .then(() => expect(nockRedirect.isDone()).toBe(true));
300
+ });
301
+ describe('DevServer proxying', () => {
302
+ afterEach(() => {
303
+ _nock.default.cleanAll();
304
+ });
305
+ test('rewrites redirects', () => {
306
+ // Example:
307
+ // - You're running a proxy server for example.com at localhost/mobify/base
308
+ // - You request localhost/mobify/base which hits example.com and returns a redirect to example.com/found
309
+ // - The proxy must rewrite that redirect to localhost/mobify/base/found
310
+
311
+ // Create a mock response from the proxied backend
312
+ const location = '/another/path';
313
+ const nockRedirect = (0, _nock.default)('https://test.proxy.com').get('/test/path').reply(301, '', {
314
+ Location: location
315
+ });
316
+ const options = opts();
317
+
318
+ // We expect the Express app to rewrite redirect responses
319
+ const rewritten = `${options.protocol}://localhost:${options.port}/mobify/proxy/base${location}`;
320
+ const app = NoWebpackDevServerFactory._createApp(options);
321
+ return (0, _supertest.default)(app).get('/mobify/proxy/base/test/path').expect(301).expect('Location', rewritten)
322
+ // Expected to hit the backend
323
+ .then(() => expect(nockRedirect.isDone()).toBe(true));
324
+ });
325
+ test('rewrites headers', () => {
326
+ // Use nock to mock out a host to which we proxy
327
+ const requestHeaders = [];
328
+ const responseHeaders = {
329
+ 'Set-Cookie': 'xyz=456'
330
+ };
331
+ const targetPath = '/test/path3?abc=123';
332
+ const nockResponse = (0, _nock.default)('https://test.proxy.com').get(targetPath).reply(200, function () {
333
+ requestHeaders.push(this.req.headers);
334
+ }, responseHeaders);
335
+ const app = NoWebpackDevServerFactory._createApp(opts());
336
+ const path = `/mobify/proxy/base${targetPath}`;
337
+ const outgoingHeaders = {
338
+ Host: 'localhost:4567',
339
+ Origin: 'https://localhost:4567',
340
+ Cookie: 'abc=123',
341
+ 'x-multi-value': 'abc, def'
342
+ };
343
+ return (0, _supertest.default)(app).get(path).set(outgoingHeaders).then(response => {
344
+ // Expected that proxy request would be fetched
345
+ expect(nockResponse.isDone()).toBe(true);
346
+
347
+ // We expect a 200 (that nock returned)
348
+ expect(response.status).toBe(200);
349
+
350
+ // We expect that we got a copy of the request headers
351
+ expect(requestHeaders).toHaveLength(1);
352
+
353
+ // Verify that the request headers were rewritten
354
+ const headers = requestHeaders[0];
355
+ expect(headers.host).toBe('test.proxy.com');
356
+ expect(headers.origin).toBe('https://test.proxy.com');
357
+
358
+ // Verify that the cookie and multi-value headers are
359
+ // correctly preserved.
360
+ expect(headers.cookie).toBe('abc=123');
361
+ const multi = headers['x-multi-value'];
362
+ expect(multi).toBe('abc, def');
363
+
364
+ // Verify that the response contains a Set-Cookie
365
+ const setCookie = response.headers['set-cookie'];
366
+ expect(setCookie).toHaveLength(1);
367
+ expect(setCookie[0]).toBe('xyz=456');
368
+
369
+ // Verify that the x-proxy-request-url header is present in
370
+ // the response
371
+ const requestUrl = response.headers[_ssrProxying.X_PROXY_REQUEST_URL];
372
+ expect(requestUrl).toBe(`https://test.proxy.com${targetPath}`);
373
+ });
374
+ });
375
+ test('restricts methods', () => {
376
+ // Use nock to mock out a host to which we proxy, though we
377
+ // do not expect the request to be made.
378
+ const nockResponse = (0, _nock.default)('https://test.proxy.com').get('/test/path3').reply(200, 'OK');
379
+ const app = NoWebpackDevServerFactory._createApp(opts());
380
+ const path = '/mobify/caching/base3/test/path3';
381
+ return (0, _supertest.default)(app).put(path).then(response => {
382
+ // Expected that proxy request would not be fetched
383
+ expect(nockResponse.isDone()).toBe(false);
384
+ expect(response.status).toBe(405);
385
+ });
386
+ });
387
+ test('filters headers', () => {
388
+ // Use nock to mock out a host to which we proxy
389
+ const nockResponse = (0, _nock.default)('https://test.proxy.com').get('/test/path3').reply(200, function () {
390
+ const headers = this.req.headers;
391
+ expect('x-mobify-access-key' in headers).toBe(false);
392
+ expect('cache-control' in headers).toBe(false);
393
+ expect('cookie' in headers).toBe(false);
394
+ expect(headers['accept-language']).toBe('en');
395
+ expect(headers['accept-encoding']).toBe('gzip');
396
+
397
+ // This value is fixed
398
+ expect(headers['user-agent']).toBe('Amazon CloudFront');
399
+ return 'Success';
400
+ });
401
+ const app = NoWebpackDevServerFactory._createApp(opts());
402
+ const path = '/mobify/caching/base3/test/path3';
403
+ return (0, _supertest.default)(app).get(path).set({
404
+ // These headers are disallowed and should be removed
405
+ 'x-mobify-access-key': '12345',
406
+ 'cache-control': 'no-cache',
407
+ cookie: 'abc=123',
408
+ // These headers are allowed
409
+ 'accept-encoding': 'gzip',
410
+ 'accept-language': 'en'
411
+ }).then(response => {
412
+ // Expected that proxy request would be fetched
413
+ expect(nockResponse.isDone()).toBe(true);
414
+ expect(response.status).toBe(200);
415
+ });
416
+ });
417
+ test('handles error', () => {
418
+ const app = NoWebpackDevServerFactory._createApp(opts());
419
+ return (0, _supertest.default)(app).get('/mobify/proxy/base2/test/path').expect(500);
420
+ });
421
+ });
422
+ describe('DevServer persistent caching support', () => {
423
+ const namespace = 'test';
424
+ const keyFromURL = url => encodeURIComponent(url);
425
+
426
+ /**
427
+ * A cache decorator for a route function that uses the percent-encoded req.url
428
+ * as keys for all cache entries (this makes testing easier).
429
+ */
430
+ const cachedRoute = route => (req, res) => {
431
+ const shouldCache = !req.query.noCache;
432
+ const cacheArgs = {
433
+ req,
434
+ res,
435
+ namespace,
436
+ key: keyFromURL(req.url)
437
+ };
438
+ const shouldCacheResponse = (req, res) => res.statusCode >= 200 && res.statusCode < 300;
439
+ return Promise.resolve().then(() => (0, _express.getResponseFromCache)(cacheArgs)).then(entry => {
440
+ if (entry.found) {
441
+ (0, _express.sendCachedResponse)(entry);
442
+ } else {
443
+ if (shouldCache) {
444
+ (0, _express.cacheResponseWhenDone)(_objectSpread({
445
+ shouldCacheResponse
446
+ }, cacheArgs));
447
+ }
448
+ return route(req, res);
449
+ }
450
+ });
451
+ };
452
+
453
+ /**
454
+ * A test route that returns different content types based on query params.
455
+ */
456
+ const routeImplementation = (req, res) => {
457
+ const status = parseInt(req.query.status || 200);
458
+ switch (req.query.type) {
459
+ case 'precompressed':
460
+ res.status(status);
461
+ res.setHeader('content-type', 'application/javascript');
462
+ res.setHeader('content-encoding', 'gzip');
463
+ res.send(_zlib.default.gzipSync(_fsExtra.default.readFileSync(_path.default.join(testFixtures, 'app', 'main.js'))));
464
+ break;
465
+ case 'compressed-responses-test':
466
+ // The "compression" middleware only compresses responses that are
467
+ // "compressable". So we must set the `content-type` to a known
468
+ // "compressible" type.
469
+ res.setHeader('content-type', 'text/html');
470
+ res.write('<div>Hello Compression</div>');
471
+ res.end();
472
+ break;
473
+ default:
474
+ throw new Error('Unhandled case');
475
+ }
476
+ };
477
+ let app, route;
478
+ beforeEach(() => {
479
+ route = jest.fn().mockImplementation(routeImplementation);
480
+ const withCaching = cachedRoute(route);
481
+ app = NoWebpackDevServerFactory._createApp(opts());
482
+ app.get('/*', withCaching);
483
+ });
484
+ afterEach(() => {
485
+ app.applicationCache.close();
486
+ app = null;
487
+ route = null;
488
+ });
489
+ test('No caching of compressed responses', () => {
490
+ // ADN-118 reported that a cached response was correctly sent
491
+ // the first time, but was corrupted the second time. This
492
+ // test is specific to that issue.
493
+ const url = '/?type=compressed-responses-test';
494
+ const expected = '<div>Hello Compression</div>';
495
+ return Promise.resolve().then(() => (0, _supertest.default)(app).get(url)).then(res => app._requestMonitor._waitForResponses().then(() => res)).then(res => {
496
+ expect(res.status).toBe(200);
497
+ expect(res.headers['x-mobify-from-cache']).toBe('false');
498
+ expect(res.headers['content-encoding']).toBe('gzip');
499
+ expect(res.text).toEqual(expected);
500
+ }).then(() => app.applicationCache.get({
501
+ key: keyFromURL(url),
502
+ namespace
503
+ })).then(entry => expect(entry.found).toBe(false)).then(() => (0, _supertest.default)(app).get(url)).then(res => app._requestMonitor._waitForResponses().then(() => res)).then(res => {
504
+ expect(res.status).toBe(200);
505
+ expect(res.headers['x-mobify-from-cache']).toBe('false');
506
+ expect(res.headers['content-encoding']).toBe('gzip');
507
+ expect(res.text).toEqual(expected);
508
+ });
509
+ });
510
+ test('Compressed responses are not re-compressed', () => {
511
+ const url = '/?type=precompressed';
512
+ return (0, _supertest.default)(app).get(url).then(res => app._requestMonitor._waitForResponses().then(() => res)).then(res => {
513
+ expect(res.status).toBe(200);
514
+ expect(res.headers['x-mobify-from-cache']).toBe('false');
515
+ expect(res.headers['content-encoding']).toBe('gzip');
516
+ }).then(() => app.applicationCache.get({
517
+ key: keyFromURL(url),
518
+ namespace
519
+ })).then(entry => {
520
+ expect(entry.found).toBe(false);
521
+ });
522
+ });
523
+ });
524
+ describe('DevServer helpers', () => {
525
+ test('Local asset headers', /*#__PURE__*/_asyncToGenerator(function* () {
526
+ const tmpDir = yield _fsExtra.default.mkdtemp(_path.default.join(_os.default.tmpdir(), 'pwa-kit-'));
527
+ const tmpFile = _path.default.join(tmpDir, 'local-asset-headers-test.svg');
528
+ yield _fsExtra.default.ensureFile(tmpFile);
529
+ const now = new Date();
530
+ yield _fsExtra.default.utimes(tmpFile, now, now);
531
+ const res = new Map(); // Don't need a full Response, just `.set` functionality
532
+ (0, _buildDevServer.setLocalAssetHeaders)(res, tmpFile);
533
+ expect([...res]).toEqual([['content-type', 'image/svg+xml'], ['date', now.toUTCString()], ['last-modified', now.toUTCString()], ['etag', now.getTime()], ['cache-control', 'max-age=0, nocache, nostore, must-revalidate']]);
534
+ }));
535
+ });
536
+ describe('DevServer rendering', () => {
537
+ test('uses hot server middleware when ready', () => {
538
+ const req = {
539
+ app: {
540
+ __webpackReady: jest.fn().mockReturnValue(true),
541
+ __hotServerMiddleware: jest.fn(),
542
+ __devMiddleware: {
543
+ waitUntilValid: callback => callback()
544
+ }
545
+ }
546
+ };
547
+ const res = {};
548
+ const next = jest.fn();
549
+ NoWebpackDevServerFactory.render(req, res, next);
550
+ expect(req.app.__hotServerMiddleware).toHaveBeenCalledWith(req, res, next);
551
+ });
552
+ test('redirects to loading screen during the inital build', () => {
553
+ const TestFactory = _objectSpread(_objectSpread({}, NoWebpackDevServerFactory), {}, {
554
+ _redirectToLoadingScreen: jest.fn()
555
+ });
556
+ const req = {
557
+ app: {
558
+ __isInitialBuild: true
559
+ }
560
+ };
561
+ const res = {};
562
+ const next = jest.fn();
563
+ TestFactory.render(req, res, next);
564
+ expect(TestFactory._redirectToLoadingScreen).toHaveBeenCalledWith(req, res, next);
565
+ });
566
+ });
567
+ describe('DevServer service worker', () => {
568
+ let tmpDir;
569
+ beforeEach( /*#__PURE__*/_asyncToGenerator(function* () {
570
+ tmpDir = yield _fsExtra.default.mkdtemp(_path.default.join(_os.default.tmpdir(), 'pwa-kit-test-'));
571
+ }));
572
+ afterEach(() => {
573
+ _rimraf.default.sync(tmpDir);
574
+ });
575
+ const createApp = () => {
576
+ const app = NoWebpackDevServerFactory._createApp(opts());
577
+ // This isn't ideal! We need a way to test the dev middleware
578
+ // including the on demand webpack compiler. However, the webpack config and
579
+ // the Dev server assumes the code runs at the root of a project.
580
+ // When we run the tests, we are not in a project.
581
+ // We have a /test_fixtures project, but Jest does not support process.chdir(),
582
+ // nor mocking process.cwd(), so we mock the dev middleware for now.
583
+ // TODO: create a proper testing fixture project and run the tests in the isolated
584
+ // project environment.
585
+ return _extends(app, {
586
+ __devMiddleware: {
587
+ waitUntilValid: cb => cb(),
588
+ context: {
589
+ outputFileSystem: _fsExtra.default,
590
+ stats: {
591
+ toJson: () => ({
592
+ children: {
593
+ find: () => ({
594
+ outputPath: tmpDir
595
+ })
596
+ }
597
+ })
598
+ }
599
+ }
600
+ },
601
+ __webpackReady: () => true
602
+ });
603
+ };
604
+ const cases = [{
605
+ file: 'worker.js',
606
+ content: '// a service worker',
607
+ name: 'Should serve the service worker',
608
+ requestPath: '/worker.js'
609
+ }, {
610
+ file: 'worker.js.map',
611
+ content: '{}',
612
+ name: 'Should serve the service worker source map',
613
+ requestPath: '/worker.js.map'
614
+ }];
615
+ cases.forEach(({
616
+ file,
617
+ content,
618
+ name,
619
+ requestPath
620
+ }) => {
621
+ test(`${name}`, /*#__PURE__*/_asyncToGenerator(function* () {
622
+ const updatedFile = _path.default.resolve(tmpDir, file);
623
+ yield _fsExtra.default.writeFile(updatedFile, content);
624
+ const app = createApp();
625
+ app.get('/worker.js(.map)?', NoWebpackDevServerFactory.serveServiceWorker);
626
+ yield (0, _supertest.default)(app).get(requestPath).expect(200).then(res => expect(res.text).toEqual(content));
627
+ }));
628
+ test(`${name} (and handle 404s correctly)`, () => {
629
+ const app = createApp();
630
+ app.get('/worker.js(.map)?', NoWebpackDevServerFactory.serveServiceWorker);
631
+ return (0, _supertest.default)(app).get(requestPath).expect(404);
632
+ });
633
+ });
634
+ });
635
+ describe('DevServer serveStaticFile', () => {
636
+ test('should serve static files', /*#__PURE__*/_asyncToGenerator(function* () {
637
+ const options = opts({
638
+ projectDir: testFixtures
639
+ });
640
+ const app = NoWebpackDevServerFactory._createApp(options);
641
+ app.use('/test', NoWebpackDevServerFactory.serveStaticFile('static/favicon.ico'));
642
+ return (0, _supertest.default)(app).get('/test').expect(200);
643
+ }));
644
+ test('should return 404 if static file does not exist', /*#__PURE__*/_asyncToGenerator(function* () {
645
+ const options = opts({
646
+ projectDir: testFixtures
647
+ });
648
+ const app = NoWebpackDevServerFactory._createApp(options);
649
+ app.use('/test', NoWebpackDevServerFactory.serveStaticFile('static/IDoNotExist.ico'));
650
+ return (0, _supertest.default)(app).get('/test').expect(404);
651
+ }));
652
+ });
653
+ describe('SLAS private client proxy', () => {
654
+ test('should throw error if PWA_KIT_SLAS_CLIENT_SECRET env var not set', () => {
655
+ expect(() => {
656
+ NoWebpackDevServerFactory._createApp(opts({
657
+ useSLASPrivateClient: true
658
+ }));
659
+ }).toThrow();
660
+ });
661
+ });