@squiz/dxp-cli-next 5.24.0 → 5.25.0-develop.1

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.
@@ -0,0 +1,275 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
+ return new (P || (P = Promise))(function (resolve, reject) {
28
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
32
+ });
33
+ };
34
+ var __importDefault = (this && this.__importDefault) || function (mod) {
35
+ return (mod && mod.__esModule) ? mod : { "default": mod };
36
+ };
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ const server_1 = require("./server");
39
+ const render_1 = require("./render");
40
+ const fs = __importStar(require("fs/promises"));
41
+ const http = __importStar(require("http"));
42
+ const opener_1 = __importDefault(require("opener"));
43
+ const definitions_1 = require("./definitions");
44
+ jest.mock('./render');
45
+ jest.mock('./definitions');
46
+ jest.mock('fs/promises');
47
+ jest.mock('fs', () => ({
48
+ existsSync: jest.fn().mockImplementation(path => {
49
+ return true;
50
+ }),
51
+ createReadStream: jest.fn().mockReturnValue({
52
+ pipe: jest.fn(),
53
+ on: jest.fn(),
54
+ }),
55
+ }));
56
+ jest.mock('http');
57
+ jest.mock('opener');
58
+ jest.mock('@squiz/dx-logger-lib', () => ({
59
+ getLogger: () => ({
60
+ info: jest.fn(),
61
+ error: jest.fn(),
62
+ warn: jest.fn(),
63
+ }),
64
+ }));
65
+ describe('startDevServer', () => {
66
+ const mockRenderLayout = render_1.renderLayout;
67
+ const mockLoadLayoutDefinition = definitions_1.loadLayoutDefinition;
68
+ const mockLoadLayoutFromFile = definitions_1.loadLayoutFromFile;
69
+ const mockReadFile = fs.readFile;
70
+ const mockStat = fs.stat;
71
+ const mockCreateServer = http.createServer;
72
+ const mockOpener = opener_1.default;
73
+ let mockServer;
74
+ const mockLayoutDefinition = {
75
+ name: 'test-layout',
76
+ displayName: 'Test Layout',
77
+ description: 'A test layout',
78
+ entry: 'path/to/template.html',
79
+ template: '<div class="{{options.size}}">{{{zones.main}}}</div>',
80
+ zones: {
81
+ main: {
82
+ displayName: 'Main Content',
83
+ description: 'Main content area',
84
+ minNodes: 1,
85
+ },
86
+ sidebar: {
87
+ displayName: 'Sidebar',
88
+ description: 'Sidebar content',
89
+ minNodes: 0,
90
+ maxNodes: 3,
91
+ },
92
+ },
93
+ options: {
94
+ size: {
95
+ displayName: 'Size',
96
+ description: 'Container size',
97
+ values: ['small', 'medium', 'large'],
98
+ },
99
+ },
100
+ };
101
+ const mockServerOptions = {
102
+ configPath: '/path/to/config.yaml',
103
+ layoutDefinition: mockLayoutDefinition,
104
+ zoneContent: {
105
+ main: ['/path/to/main.html'],
106
+ sidebar: ['/path/to/sidebar1.html', '/path/to/sidebar2.html'],
107
+ },
108
+ layoutOptions: {
109
+ size: 'large',
110
+ },
111
+ stylesheet: '/path/to/styles.css',
112
+ port: 4040,
113
+ openBrowser: true,
114
+ };
115
+ const originalProcessOn = process.on;
116
+ beforeAll(() => {
117
+ // @ts-ignore - we need to mock process.on
118
+ process.on = jest.fn();
119
+ });
120
+ beforeEach(() => {
121
+ jest.resetAllMocks();
122
+ mockServer = {
123
+ listen: jest.fn().mockImplementation((port, callback) => {
124
+ if (callback)
125
+ callback();
126
+ return mockServer;
127
+ }),
128
+ close: jest.fn().mockReturnValue(mockServer),
129
+ on: jest.fn().mockReturnValue(mockServer),
130
+ requestListener: undefined,
131
+ };
132
+ mockCreateServer.mockImplementation(handler => {
133
+ if (typeof handler === 'function') {
134
+ mockServer.requestListener = handler;
135
+ }
136
+ return mockServer;
137
+ });
138
+ mockReadFile.mockImplementation(path => {
139
+ if (typeof path === 'string') {
140
+ if (path.includes('main.html')) {
141
+ return Promise.resolve('<p>Main content</p>');
142
+ }
143
+ else if (path.includes('sidebar')) {
144
+ return Promise.resolve('<div>Sidebar item</div>');
145
+ }
146
+ else if (path.includes('styles.css')) {
147
+ return Promise.resolve('body { color: #333; }');
148
+ }
149
+ }
150
+ return Promise.resolve('');
151
+ });
152
+ mockStat.mockImplementation(() => {
153
+ return Promise.resolve({
154
+ mtime: new Date(),
155
+ isFile: () => true,
156
+ isDirectory: () => false,
157
+ size: 100,
158
+ });
159
+ });
160
+ mockLoadLayoutDefinition.mockResolvedValue(mockLayoutDefinition);
161
+ mockLoadLayoutFromFile.mockResolvedValue(mockLayoutDefinition.template);
162
+ mockRenderLayout.mockResolvedValue('<div class="rendered-layout">Content</div>');
163
+ });
164
+ afterAll(() => {
165
+ process.on = originalProcessOn;
166
+ });
167
+ it('should initialize server with provided options', () => __awaiter(void 0, void 0, void 0, function* () {
168
+ yield (0, server_1.startDevServer)(mockServerOptions);
169
+ expect(mockCreateServer).toHaveBeenCalled();
170
+ expect(mockServer.listen).toHaveBeenCalled();
171
+ expect(mockCreateServer).toHaveBeenCalledWith(expect.any(Function));
172
+ }));
173
+ it('should load content from files for each zone', () => __awaiter(void 0, void 0, void 0, function* () {
174
+ yield (0, server_1.startDevServer)(mockServerOptions);
175
+ const mockRequest = {
176
+ url: '/',
177
+ on: jest.fn(),
178
+ };
179
+ const mockResponse = {
180
+ writeHead: jest.fn(),
181
+ end: jest.fn(),
182
+ write: jest.fn(),
183
+ };
184
+ const requestListener = mockServer.requestListener;
185
+ if (!requestListener) {
186
+ fail('Request listener was not set');
187
+ return;
188
+ }
189
+ yield requestListener(mockRequest, mockResponse);
190
+ expect(mockReadFile).toHaveBeenCalledWith('/path/to/main.html', 'utf-8');
191
+ expect(mockReadFile).toHaveBeenCalledWith('/path/to/sidebar1.html', 'utf-8');
192
+ expect(mockReadFile).toHaveBeenCalledWith('/path/to/sidebar2.html', 'utf-8');
193
+ }));
194
+ it('should open browser if openBrowser is true', () => __awaiter(void 0, void 0, void 0, function* () {
195
+ yield (0, server_1.startDevServer)(mockServerOptions);
196
+ expect(mockOpener).toHaveBeenCalledWith('http://localhost:4040/');
197
+ }));
198
+ it('should not open browser if openBrowser is false', () => __awaiter(void 0, void 0, void 0, function* () {
199
+ const options = Object.assign(Object.assign({}, mockServerOptions), { openBrowser: false });
200
+ yield (0, server_1.startDevServer)(options);
201
+ expect(mockOpener).not.toHaveBeenCalled();
202
+ }));
203
+ it('should pass concatenated zone content to renderLayout', () => __awaiter(void 0, void 0, void 0, function* () {
204
+ const mockRequest = {
205
+ url: '/',
206
+ on: jest.fn(),
207
+ };
208
+ const mockResponse = {
209
+ writeHead: jest.fn(),
210
+ end: jest.fn(),
211
+ write: jest.fn(),
212
+ };
213
+ yield (0, server_1.startDevServer)(mockServerOptions);
214
+ const requestListener = mockServer.requestListener;
215
+ if (!requestListener) {
216
+ fail('Request listener was not set');
217
+ return;
218
+ }
219
+ yield requestListener(mockRequest, mockResponse);
220
+ // Check that renderLayout was called with concatenated content
221
+ expect(mockRenderLayout).toHaveBeenCalledWith(mockLayoutDefinition.template, expect.objectContaining({
222
+ main: '<p>Main content</p>',
223
+ sidebar: '<div>Sidebar item</div><div>Sidebar item</div>',
224
+ }), mockServerOptions.layoutOptions, mockLayoutDefinition);
225
+ }));
226
+ it('should set up check-changes endpoint for client polling', () => __awaiter(void 0, void 0, void 0, function* () {
227
+ const mockRequest = {
228
+ url: '/check-changes?lastModified=2023-01-01T00:00:00.000Z',
229
+ on: jest.fn(),
230
+ searchParams: {
231
+ get: jest.fn().mockReturnValue('2023-01-01T00:00:00.000Z'),
232
+ },
233
+ };
234
+ const mockResponse = {
235
+ writeHead: jest.fn(),
236
+ end: jest.fn(),
237
+ write: jest.fn(),
238
+ };
239
+ yield (0, server_1.startDevServer)(mockServerOptions);
240
+ const requestListener = mockServer.requestListener;
241
+ if (!requestListener) {
242
+ fail('Request listener was not set');
243
+ return;
244
+ }
245
+ yield requestListener(mockRequest, mockResponse);
246
+ expect(mockResponse.writeHead).toHaveBeenCalledWith(200, {
247
+ 'Content-Type': 'application/json',
248
+ });
249
+ }));
250
+ it('should redirect to root for unknown paths', () => __awaiter(void 0, void 0, void 0, function* () {
251
+ const mockRequest = {
252
+ url: '/unknown-path',
253
+ on: jest.fn(),
254
+ };
255
+ const mockResponse = {
256
+ writeHead: jest.fn(),
257
+ end: jest.fn(),
258
+ write: jest.fn(),
259
+ };
260
+ yield (0, server_1.startDevServer)(mockServerOptions);
261
+ const requestListener = mockServer.requestListener;
262
+ if (!requestListener) {
263
+ fail('Request listener was not set');
264
+ return;
265
+ }
266
+ yield requestListener(mockRequest, mockResponse);
267
+ expect(mockResponse.writeHead).toHaveBeenCalledWith(302, {
268
+ Location: '/',
269
+ });
270
+ }));
271
+ it('should set up process exit handler', () => __awaiter(void 0, void 0, void 0, function* () {
272
+ yield (0, server_1.startDevServer)(mockServerOptions);
273
+ expect(process.on).toHaveBeenCalledWith('SIGINT', expect.any(Function));
274
+ }));
275
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squiz/dxp-cli-next",
3
- "version": "5.24.0",
3
+ "version": "5.25.0-develop.1",
4
4
  "repository": {
5
5
  "url": "https://gitlab.squiz.net/dxp/dxp-cli-next"
6
6
  },
@@ -51,6 +51,7 @@
51
51
  "dotenv": "^16.3.1",
52
52
  "env-paths": "2.2.1",
53
53
  "esbuild": "^0.21.4",
54
+ "handlebars": "^4.7.8",
54
55
  "inquirer": "8.2.5",
55
56
  "opener": "1.5.2",
56
57
  "ora": "^5.4.1",