@hubspot/ui-extensions-dev-server 1.0.1 → 1.1.0

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 (95) hide show
  1. package/package.json +2 -11
  2. package/dist/index.d.ts +0 -4
  3. package/dist/index.js +0 -4
  4. package/dist/lib/DevModeInterface.d.ts +0 -9
  5. package/dist/lib/DevModeInterface.js +0 -36
  6. package/dist/lib/DevModeParentInterface.d.ts +0 -19
  7. package/dist/lib/DevModeParentInterface.js +0 -181
  8. package/dist/lib/DevModeUnifiedInterface.d.ts +0 -9
  9. package/dist/lib/DevModeUnifiedInterface.js +0 -118
  10. package/dist/lib/DevServerState.d.ts +0 -44
  11. package/dist/lib/DevServerState.js +0 -95
  12. package/dist/lib/ExtensionsWebSocket.d.ts +0 -25
  13. package/dist/lib/ExtensionsWebSocket.js +0 -110
  14. package/dist/lib/__mocks__/config.d.ts +0 -2
  15. package/dist/lib/__mocks__/config.js +0 -5
  16. package/dist/lib/__mocks__/isExtensionFile.d.ts +0 -5
  17. package/dist/lib/__mocks__/isExtensionFile.js +0 -11
  18. package/dist/lib/__tests__/DevModeInterface.spec.d.ts +0 -1
  19. package/dist/lib/__tests__/DevModeInterface.spec.js +0 -155
  20. package/dist/lib/__tests__/DevModeParentInterface.spec.d.ts +0 -1
  21. package/dist/lib/__tests__/DevModeParentInterface.spec.js +0 -179
  22. package/dist/lib/__tests__/DevModeUnifiedInterface.spec.d.ts +0 -1
  23. package/dist/lib/__tests__/DevModeUnifiedInterface.spec.js +0 -236
  24. package/dist/lib/__tests__/ExtensionsWebSocket.spec.d.ts +0 -1
  25. package/dist/lib/__tests__/ExtensionsWebSocket.spec.js +0 -304
  26. package/dist/lib/__tests__/ast.spec.d.ts +0 -1
  27. package/dist/lib/__tests__/ast.spec.js +0 -737
  28. package/dist/lib/__tests__/build.spec.d.ts +0 -1
  29. package/dist/lib/__tests__/build.spec.js +0 -159
  30. package/dist/lib/__tests__/config.spec.d.ts +0 -1
  31. package/dist/lib/__tests__/config.spec.js +0 -291
  32. package/dist/lib/__tests__/dev.spec.d.ts +0 -1
  33. package/dist/lib/__tests__/dev.spec.js +0 -80
  34. package/dist/lib/__tests__/extensionsService.spec.d.ts +0 -1
  35. package/dist/lib/__tests__/extensionsService.spec.js +0 -150
  36. package/dist/lib/__tests__/factories.d.ts +0 -48
  37. package/dist/lib/__tests__/factories.js +0 -32
  38. package/dist/lib/__tests__/fixtures/extensionConfig.d.ts +0 -182
  39. package/dist/lib/__tests__/fixtures/extensionConfig.js +0 -304
  40. package/dist/lib/__tests__/fixtures/urls.d.ts +0 -4
  41. package/dist/lib/__tests__/fixtures/urls.js +0 -4
  42. package/dist/lib/__tests__/parsing-utils.spec.d.ts +0 -1
  43. package/dist/lib/__tests__/parsing-utils.spec.js +0 -467
  44. package/dist/lib/__tests__/plugins/codeBlockingPlugin.spec.d.ts +0 -1
  45. package/dist/lib/__tests__/plugins/codeBlockingPlugin.spec.js +0 -112
  46. package/dist/lib/__tests__/plugins/codeCheckingPlugin.spec.d.ts +0 -1
  47. package/dist/lib/__tests__/plugins/codeCheckingPlugin.spec.js +0 -82
  48. package/dist/lib/__tests__/plugins/devBuildPlugin.spec.d.ts +0 -1
  49. package/dist/lib/__tests__/plugins/devBuildPlugin.spec.js +0 -256
  50. package/dist/lib/__tests__/plugins/friendlyLoggingPlugin.spec.d.ts +0 -1
  51. package/dist/lib/__tests__/plugins/friendlyLoggingPlugin.spec.js +0 -65
  52. package/dist/lib/__tests__/plugins/manifestPlugin.spec.d.ts +0 -1
  53. package/dist/lib/__tests__/plugins/manifestPlugin.spec.js +0 -455
  54. package/dist/lib/__tests__/plugins/relevantModulesPlugin.spec.d.ts +0 -1
  55. package/dist/lib/__tests__/plugins/relevantModulesPlugin.spec.js +0 -81
  56. package/dist/lib/__tests__/server.spec.d.ts +0 -1
  57. package/dist/lib/__tests__/server.spec.js +0 -152
  58. package/dist/lib/__tests__/test-utils/ast.d.ts +0 -1
  59. package/dist/lib/__tests__/test-utils/ast.js +0 -4
  60. package/dist/lib/__tests__/utils.spec.d.ts +0 -1
  61. package/dist/lib/__tests__/utils.spec.js +0 -176
  62. package/dist/lib/ast.d.ts +0 -16
  63. package/dist/lib/ast.js +0 -281
  64. package/dist/lib/bin/cli.d.ts +0 -2
  65. package/dist/lib/bin/cli.js +0 -118
  66. package/dist/lib/build.d.ts +0 -23
  67. package/dist/lib/build.js +0 -67
  68. package/dist/lib/config.d.ts +0 -7
  69. package/dist/lib/config.js +0 -124
  70. package/dist/lib/constants.d.ts +0 -32
  71. package/dist/lib/constants.js +0 -43
  72. package/dist/lib/dev.d.ts +0 -2
  73. package/dist/lib/dev.js +0 -58
  74. package/dist/lib/extensionsService.d.ts +0 -10
  75. package/dist/lib/extensionsService.js +0 -45
  76. package/dist/lib/parsing-utils.d.ts +0 -31
  77. package/dist/lib/parsing-utils.js +0 -289
  78. package/dist/lib/plugins/codeBlockingPlugin.d.ts +0 -8
  79. package/dist/lib/plugins/codeBlockingPlugin.js +0 -45
  80. package/dist/lib/plugins/codeCheckingPlugin.d.ts +0 -9
  81. package/dist/lib/plugins/codeCheckingPlugin.js +0 -25
  82. package/dist/lib/plugins/devBuildPlugin.d.ts +0 -8
  83. package/dist/lib/plugins/devBuildPlugin.js +0 -194
  84. package/dist/lib/plugins/friendlyLoggingPlugin.d.ts +0 -14
  85. package/dist/lib/plugins/friendlyLoggingPlugin.js +0 -36
  86. package/dist/lib/plugins/manifestPlugin.d.ts +0 -12
  87. package/dist/lib/plugins/manifestPlugin.js +0 -158
  88. package/dist/lib/plugins/relevantModulesPlugin.d.ts +0 -13
  89. package/dist/lib/plugins/relevantModulesPlugin.js +0 -25
  90. package/dist/lib/server.d.ts +0 -13
  91. package/dist/lib/server.js +0 -99
  92. package/dist/lib/types.d.ts +0 -290
  93. package/dist/lib/types.js +0 -12
  94. package/dist/lib/utils.d.ts +0 -25
  95. package/dist/lib/utils.js +0 -113
@@ -1,236 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach, } from 'vitest';
2
- import DevModeUnifiedInterface from "../DevModeUnifiedInterface.js";
3
- import { DevModeParentInterface } from "../DevModeParentInterface.js";
4
- import * as config from "../config.js";
5
- import { cardConfig, cardConfigTwo, transformedUnifiedAppConfig, transformedUnifiedAppConfigTwoCards, transformedUnifiedCardOneConfig, transformedUnifiedCardTwoConfig, unifiedAppConfig, unifiedCardOneConfig, unifiedCardTwoConfig, settingsExtensionConfig, settingsExtensionTransformedData, appHomesConfig, appHomesTransformedData, } from "./fixtures/extensionConfig.js";
6
- import inquirer from 'inquirer';
7
- import { urls } from "./fixtures/urls.js";
8
- vi.mock('../config.ts', async () => {
9
- const actual = await vi.importActual('../config.ts');
10
- return {
11
- ...actual,
12
- loadExtensionConfig: vi.fn(),
13
- };
14
- });
15
- vi.mock('inquirer', () => ({
16
- default: {
17
- createPromptModule: vi.fn(),
18
- },
19
- }));
20
- describe('DevModeUnifiedInterface', () => {
21
- describe('setup', () => {
22
- let parentSetupSpy;
23
- let mockPromptModule;
24
- let setActiveApp;
25
- let logger;
26
- const cardKey = `${unifiedAppConfig.uid}::${cardConfig.data.uid}`;
27
- const components = {
28
- application_name: unifiedAppConfig,
29
- card_one: unifiedCardOneConfig,
30
- };
31
- const expectedConfig = {
32
- ...transformedUnifiedCardOneConfig,
33
- appConfig: transformedUnifiedAppConfig,
34
- };
35
- const cardOneChoiceName = `${unifiedAppConfig.config.name}/${unifiedCardOneConfig.config.name}`;
36
- beforeEach(() => {
37
- DevModeUnifiedInterface._reset();
38
- logger = {
39
- info: vi.fn(),
40
- debug: vi.fn(),
41
- warn: vi.fn(),
42
- error: vi.fn(),
43
- };
44
- DevModeUnifiedInterface.logger = logger;
45
- mockPromptModule = vi.fn().mockResolvedValue({
46
- extensions: [expectedConfig],
47
- });
48
- vi.mocked(inquirer.createPromptModule).mockReturnValue(mockPromptModule);
49
- setActiveApp = vi.fn(() => {
50
- return Promise.resolve();
51
- });
52
- parentSetupSpy = vi.spyOn(DevModeParentInterface.prototype, 'parentSetup');
53
- vi.mocked(config.loadExtensionConfig).mockReturnValue({
54
- [cardKey]: cardConfig,
55
- });
56
- });
57
- afterEach(() => {
58
- vi.clearAllMocks();
59
- });
60
- it('should throw an error if no extensions are parsed from the provided arguments', async () => {
61
- const badComponents = {
62
- 'not a valid application': {
63
- config: {
64
- name: 'I do not have an extensions object',
65
- },
66
- path: '/path/to/my/application',
67
- },
68
- };
69
- await expect(DevModeUnifiedInterface.setup({
70
- // @ts-expect-error - We're trying to trigger an error with bad data
71
- components: badComponents,
72
- logger,
73
- setActiveApp,
74
- urls,
75
- })).rejects.toThrowError('No extensions to run');
76
- });
77
- it('should directly set configs when there is only one choice', async () => {
78
- await DevModeUnifiedInterface.setup({
79
- components,
80
- logger,
81
- setActiveApp,
82
- urls,
83
- });
84
- expect(parentSetupSpy).toHaveBeenCalledWith({
85
- choices: [
86
- {
87
- name: cardOneChoiceName,
88
- value: expectedConfig,
89
- },
90
- ],
91
- components,
92
- logger,
93
- setActiveApp,
94
- urls,
95
- });
96
- expect(DevModeUnifiedInterface.configs).toStrictEqual([expectedConfig]);
97
- });
98
- it('should prompt the user when there is more than one choice', async () => {
99
- const twoCardComponents = {
100
- ...components,
101
- card_two: unifiedCardTwoConfig,
102
- };
103
- const cardOneKey = `${unifiedAppConfig.uid}::${unifiedCardOneConfig.uid}`;
104
- const cardTwoKey = `${unifiedAppConfig.uid}::${unifiedCardTwoConfig.uid}`;
105
- vi.mocked(config.loadExtensionConfig).mockReturnValue({
106
- [cardOneKey]: cardConfig,
107
- [cardTwoKey]: cardConfigTwo,
108
- });
109
- await DevModeUnifiedInterface.setup({
110
- components: twoCardComponents,
111
- logger,
112
- setActiveApp,
113
- urls,
114
- });
115
- expect(parentSetupSpy).toHaveBeenCalledWith({
116
- choices: [
117
- {
118
- name: cardOneChoiceName,
119
- value: {
120
- ...transformedUnifiedCardOneConfig,
121
- appConfig: transformedUnifiedAppConfigTwoCards,
122
- },
123
- },
124
- {
125
- name: `${unifiedAppConfig.config.name}/${unifiedCardTwoConfig.config.name}`,
126
- value: {
127
- ...transformedUnifiedCardTwoConfig,
128
- appConfig: transformedUnifiedAppConfigTwoCards,
129
- },
130
- },
131
- ],
132
- components: twoCardComponents,
133
- logger,
134
- setActiveApp,
135
- urls,
136
- });
137
- expect(mockPromptModule).toHaveBeenCalledTimes(1);
138
- expect(DevModeUnifiedInterface.configs).toStrictEqual([expectedConfig]);
139
- });
140
- it('should call setActiveApp with the correct arg', async () => {
141
- await DevModeUnifiedInterface.setup({
142
- components,
143
- logger,
144
- setActiveApp,
145
- urls,
146
- });
147
- expect(setActiveApp).toHaveBeenCalledTimes(1);
148
- expect(setActiveApp).toHaveBeenCalledWith(unifiedAppConfig.uid);
149
- });
150
- it('should not run setup if it has already been configured', async () => {
151
- DevModeUnifiedInterface.isConfigured = true;
152
- await DevModeUnifiedInterface.setup({
153
- components,
154
- logger,
155
- setActiveApp,
156
- urls,
157
- });
158
- expect(logger.debug).toHaveBeenCalledWith('Dev server has already been configured, skipping');
159
- expect(DevModeUnifiedInterface.configs).toBeUndefined();
160
- });
161
- it('should generate the correct configs for a settings card', async () => {
162
- const settingsComponents = {
163
- application_name: unifiedAppConfig,
164
- card_one: unifiedCardOneConfig,
165
- card_settings: settingsExtensionConfig,
166
- };
167
- mockPromptModule = vi.fn().mockResolvedValue({
168
- extensions: [expectedConfig, settingsExtensionTransformedData],
169
- });
170
- vi.mocked(inquirer.createPromptModule).mockReturnValue(mockPromptModule);
171
- await DevModeUnifiedInterface.setup({
172
- components: settingsComponents,
173
- logger,
174
- setActiveApp,
175
- urls,
176
- });
177
- expect(mockPromptModule).toHaveBeenCalledTimes(1);
178
- expect(DevModeUnifiedInterface.configs).toStrictEqual([
179
- expectedConfig,
180
- settingsExtensionTransformedData,
181
- ]);
182
- expect(DevModeUnifiedInterface.configs?.[1]).toHaveProperty('data.title', 'Settings');
183
- });
184
- it('should generate the correct configs for a pages extension', async () => {
185
- const pagesComponents = {
186
- application_name: unifiedAppConfig,
187
- card_one: unifiedCardOneConfig,
188
- app_homes: appHomesConfig,
189
- };
190
- mockPromptModule = vi.fn().mockResolvedValue({
191
- extensions: [expectedConfig, appHomesTransformedData],
192
- });
193
- vi.mocked(inquirer.createPromptModule).mockReturnValue(mockPromptModule);
194
- await DevModeUnifiedInterface.setup({
195
- components: pagesComponents,
196
- logger,
197
- setActiveApp,
198
- urls,
199
- });
200
- expect(mockPromptModule).toHaveBeenCalledTimes(1);
201
- expect(DevModeUnifiedInterface.configs).toStrictEqual([
202
- expectedConfig,
203
- appHomesTransformedData,
204
- ]);
205
- expect(DevModeUnifiedInterface.configs?.[1]).toHaveProperty('data.title', 'App Home');
206
- });
207
- it('should use profileData variables when provided', async () => {
208
- const profileData = {
209
- API_KEY: 'test-key-123',
210
- DEBUG_MODE: true,
211
- MAX_RETRIES: 5,
212
- };
213
- await DevModeUnifiedInterface.setup({
214
- components,
215
- profileData,
216
- logger,
217
- setActiveApp,
218
- urls,
219
- });
220
- const appConfig = DevModeUnifiedInterface.configs?.[0]
221
- ?.appConfig;
222
- expect(appConfig?.variables).toStrictEqual(profileData);
223
- });
224
- it('should use empty object for variables when no profileData provided', async () => {
225
- await DevModeUnifiedInterface.setup({
226
- components,
227
- logger,
228
- setActiveApp,
229
- urls,
230
- });
231
- const appConfig = DevModeUnifiedInterface.configs?.[0]
232
- ?.appConfig;
233
- expect(appConfig?.variables).toStrictEqual({});
234
- });
235
- });
236
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,304 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- import { ExtensionsWebSocket, isAllowedOrigin, } from "../ExtensionsWebSocket.js";
3
- import { DevServerState } from "../DevServerState.js";
4
- import { createMockLogger, createDevServerConfig } from "./factories.js";
5
- const MOCK_INTERVAL_ID = 123;
6
- let mockClients;
7
- let mockWss;
8
- let upgradeHandler = null;
9
- vi.mock('ws', () => ({
10
- WebSocketServer: vi.fn(() => {
11
- mockClients = new Set();
12
- const listeners = {};
13
- let isClosed = false;
14
- mockWss = {
15
- clients: mockClients,
16
- on: vi.fn((event, handler) => {
17
- if (!listeners[event])
18
- listeners[event] = [];
19
- listeners[event].push(handler);
20
- }),
21
- emit: vi.fn((event, ...args) => {
22
- listeners[event]?.forEach((handler) => handler(...args));
23
- }),
24
- handleUpgrade: vi.fn((request, socket, head, callback) => {
25
- const mockClient = createMockWebSocket();
26
- mockClients.add(mockClient);
27
- callback(mockClient);
28
- }),
29
- close: vi.fn((callback) => {
30
- if (isClosed) {
31
- callback?.(new Error('The server is not running'));
32
- }
33
- else {
34
- isClosed = true;
35
- callback?.();
36
- }
37
- }),
38
- };
39
- return mockWss;
40
- }),
41
- WebSocket: { OPEN: 1, CLOSED: 3 },
42
- }));
43
- function createMockWebSocket() {
44
- const listeners = {};
45
- const mockWs = {
46
- readyState: 1,
47
- on: vi.fn((event, handler) => {
48
- if (!listeners[event])
49
- listeners[event] = [];
50
- listeners[event].push(handler);
51
- }),
52
- once: vi.fn(),
53
- emit: vi.fn((event, ...args) => {
54
- listeners[event]?.forEach((handler) => handler(...args));
55
- }),
56
- send: vi.fn(),
57
- close: vi.fn(() => {
58
- mockWs.readyState = 3;
59
- mockClients.delete(mockWs);
60
- listeners['close']?.forEach((handler) => handler());
61
- }),
62
- terminate: vi.fn(() => {
63
- mockWs.readyState = 3;
64
- mockClients.delete(mockWs);
65
- }),
66
- ping: vi.fn(),
67
- };
68
- return mockWs;
69
- }
70
- let mockHttpServer;
71
- let devServerState;
72
- let extensionsWebSocket;
73
- function createMockHttpServer() {
74
- const listeners = {};
75
- return {
76
- on: vi.fn((event, handler) => {
77
- if (!listeners[event])
78
- listeners[event] = [];
79
- listeners[event].push(handler);
80
- if (event === 'upgrade') {
81
- upgradeHandler = handler;
82
- }
83
- }),
84
- emit: vi.fn((event, ...args) => {
85
- listeners[event]?.forEach((handler) => handler(...args));
86
- }),
87
- listen: vi.fn(),
88
- close: vi.fn(),
89
- };
90
- }
91
- function simulateClientConnection(origin = 'http://localhost') {
92
- if (!upgradeHandler)
93
- throw new Error('upgradeHandler not initialized');
94
- const mockRequest = { headers: { origin } };
95
- const mockSocket = { destroy: vi.fn() };
96
- upgradeHandler(mockRequest, mockSocket, Buffer.from([]));
97
- return {
98
- mockRequest,
99
- mockSocket,
100
- client: Array.from(mockClients)[mockClients.size - 1],
101
- };
102
- }
103
- describe('ExtensionsWebSocket', () => {
104
- describe('isAllowedOrigin', () => {
105
- it('should return false for undefined origin', () => {
106
- expect(isAllowedOrigin(undefined)).toBe(false);
107
- });
108
- it('should return true for localhost origins', () => {
109
- expect(isAllowedOrigin('http://localhost')).toBe(true);
110
- expect(isAllowedOrigin('https://localhost')).toBe(true);
111
- expect(isAllowedOrigin('https://localhost:8080')).toBe(true);
112
- expect(isAllowedOrigin('https://localhost:5173')).toBe(true);
113
- });
114
- it('should return true for hubspot.com origins', () => {
115
- expect(isAllowedOrigin('https://app.hubspot.com')).toBe(true);
116
- expect(isAllowedOrigin('http://hubspot.com')).toBe(true);
117
- expect(isAllowedOrigin('https://api.hubspot.com:443')).toBe(true);
118
- });
119
- it('should return true for hubspotqa.com origins', () => {
120
- expect(isAllowedOrigin('https://app.hubspotqa.com')).toBe(true);
121
- expect(isAllowedOrigin('http://hubspotqa.com')).toBe(true);
122
- expect(isAllowedOrigin('https://api.hubspotqa.com:443')).toBe(true);
123
- });
124
- it('should return false for non-allowed origins', () => {
125
- expect(isAllowedOrigin('https://evil.com')).toBe(false);
126
- expect(isAllowedOrigin('http://example.com')).toBe(false);
127
- expect(isAllowedOrigin('https://hubspot.com.evil.com')).toBe(false);
128
- });
129
- });
130
- describe('ExtensionsWebSocket class', () => {
131
- beforeEach(() => {
132
- const logger = createMockLogger();
133
- const config = createDevServerConfig(logger);
134
- devServerState = new DevServerState(config);
135
- mockHttpServer = createMockHttpServer();
136
- mockClients = new Set();
137
- });
138
- afterEach(() => {
139
- vi.useRealTimers();
140
- vi.clearAllMocks();
141
- mockClients.clear();
142
- });
143
- describe('constructor', () => {
144
- it('should create an ExtensionsWebSocket instance', () => {
145
- extensionsWebSocket = new ExtensionsWebSocket(mockHttpServer, devServerState);
146
- expect(extensionsWebSocket).toBeDefined();
147
- });
148
- });
149
- describe('setupUpgradeHandler', () => {
150
- it('should reject connections from non-allowed origins', () => {
151
- extensionsWebSocket = new ExtensionsWebSocket(mockHttpServer, devServerState);
152
- const mockSocket = {
153
- destroy: vi.fn(),
154
- };
155
- const mockRequest = {
156
- headers: {
157
- origin: 'https://evil.com',
158
- },
159
- };
160
- mockHttpServer.emit('upgrade', mockRequest, mockSocket, Buffer.from([]));
161
- expect(mockSocket.destroy).toHaveBeenCalled();
162
- expect(devServerState.logger.debug).toHaveBeenCalledWith('Rejected WebSocket: https://evil.com');
163
- });
164
- it('should accept connections from allowed origins', () => {
165
- extensionsWebSocket = new ExtensionsWebSocket(mockHttpServer, devServerState);
166
- simulateClientConnection('http://localhost');
167
- expect(extensionsWebSocket.clientCount).toBe(1);
168
- });
169
- });
170
- describe('broadcast', () => {
171
- it('should log when no clients are connected', () => {
172
- extensionsWebSocket = new ExtensionsWebSocket(mockHttpServer, devServerState);
173
- extensionsWebSocket.broadcast({ event: 'test' });
174
- expect(devServerState.logger.debug).toHaveBeenCalledWith('No clients connected, message not sent');
175
- });
176
- it('should send messages to all connected clients', () => {
177
- extensionsWebSocket = new ExtensionsWebSocket(mockHttpServer, devServerState);
178
- const { client: client1 } = simulateClientConnection();
179
- const { client: client2 } = simulateClientConnection();
180
- extensionsWebSocket.broadcast({ event: 'test', data: 'hello' });
181
- expect(client1.send).toHaveBeenCalledWith(JSON.stringify({ event: 'test', data: 'hello' }));
182
- expect(client2.send).toHaveBeenCalledWith(JSON.stringify({ event: 'test', data: 'hello' }));
183
- });
184
- it('should handle send failures gracefully', () => {
185
- extensionsWebSocket = new ExtensionsWebSocket(mockHttpServer, devServerState);
186
- const { client } = simulateClientConnection();
187
- vi.mocked(client.send).mockImplementation(() => {
188
- throw new Error('Send failed');
189
- });
190
- extensionsWebSocket.broadcast({ event: 'test' });
191
- expect(devServerState.logger.warn).toHaveBeenCalledWith('Sent to 0 clients, 1 failed');
192
- });
193
- });
194
- describe('onConnection', () => {
195
- it('should register connection handler', () => {
196
- extensionsWebSocket = new ExtensionsWebSocket(mockHttpServer, devServerState);
197
- let connectionHandlerCalled = false;
198
- extensionsWebSocket.onConnection(() => {
199
- connectionHandlerCalled = true;
200
- });
201
- simulateClientConnection();
202
- expect(connectionHandlerCalled).toBe(true);
203
- });
204
- });
205
- describe('clientCount', () => {
206
- it('should return 0 when no clients are connected', () => {
207
- extensionsWebSocket = new ExtensionsWebSocket(mockHttpServer, devServerState);
208
- expect(extensionsWebSocket.clientCount).toBe(0);
209
- });
210
- it('should return correct count of connected clients', () => {
211
- extensionsWebSocket = new ExtensionsWebSocket(mockHttpServer, devServerState);
212
- simulateClientConnection();
213
- simulateClientConnection();
214
- expect(extensionsWebSocket.clientCount).toBe(2);
215
- });
216
- });
217
- describe('keepAlive', () => {
218
- it('should terminate clients that do not respond to ping', () => {
219
- let keepAliveCallback;
220
- const setIntervalSpy = vi
221
- .spyOn(global, 'setInterval')
222
- .mockImplementation((fn) => {
223
- keepAliveCallback = fn;
224
- return MOCK_INTERVAL_ID;
225
- });
226
- extensionsWebSocket = new ExtensionsWebSocket(mockHttpServer, devServerState);
227
- const { client } = simulateClientConnection();
228
- // First keepAlive call: marks client as not alive and sends ping
229
- keepAliveCallback();
230
- // Second keepAlive call: should terminate since client didn't respond
231
- keepAliveCallback();
232
- expect(client.terminate).toHaveBeenCalled();
233
- setIntervalSpy.mockRestore();
234
- });
235
- });
236
- describe('client handlers', () => {
237
- it('should log client errors', () => {
238
- extensionsWebSocket = new ExtensionsWebSocket(mockHttpServer, devServerState);
239
- const { client } = simulateClientConnection();
240
- client.emit('error', new Error('Test error'));
241
- expect(devServerState.logger.debug).toHaveBeenCalledWith('Client error: Test error');
242
- });
243
- it('should log client disconnections', () => {
244
- extensionsWebSocket = new ExtensionsWebSocket(mockHttpServer, devServerState);
245
- const { client } = simulateClientConnection();
246
- client.close();
247
- const debugCalls = vi.mocked(devServerState.logger.debug).mock.calls;
248
- const hasDisconnectLog = debugCalls.some((call) => call[0].includes('Client disconnected'));
249
- expect(hasDisconnectLog).toBe(true);
250
- });
251
- });
252
- describe('close', () => {
253
- it('should terminate all clients before closing', async () => {
254
- extensionsWebSocket = new ExtensionsWebSocket(mockHttpServer, devServerState);
255
- const { client: client1 } = simulateClientConnection();
256
- const { client: client2 } = simulateClientConnection();
257
- expect(extensionsWebSocket.clientCount).toBe(2);
258
- await extensionsWebSocket.close();
259
- expect(client1.terminate).toHaveBeenCalled();
260
- expect(client2.terminate).toHaveBeenCalled();
261
- expect(extensionsWebSocket.clientCount).toBe(0);
262
- });
263
- it('should close the WebSocket server after clients disconnect', async () => {
264
- extensionsWebSocket = new ExtensionsWebSocket(mockHttpServer, devServerState);
265
- const { client } = simulateClientConnection();
266
- expect(extensionsWebSocket.clientCount).toBe(1);
267
- client.close();
268
- await extensionsWebSocket.close();
269
- expect(extensionsWebSocket.clientCount).toBe(0);
270
- });
271
- it('should stop keep-alive mechanism after close', async () => {
272
- extensionsWebSocket = new ExtensionsWebSocket(mockHttpServer, devServerState);
273
- const { client } = simulateClientConnection();
274
- const clearIntervalSpy = vi.spyOn(global, 'clearInterval');
275
- client.close();
276
- await extensionsWebSocket.close();
277
- expect(clearIntervalSpy).toHaveBeenCalled();
278
- clearIntervalSpy.mockRestore();
279
- });
280
- it('should reject when close is called multiple times', async () => {
281
- extensionsWebSocket = new ExtensionsWebSocket(mockHttpServer, devServerState);
282
- await extensionsWebSocket.close();
283
- await expect(extensionsWebSocket.close()).rejects.toThrow('The server is not running');
284
- });
285
- it('should not broadcast after close', async () => {
286
- extensionsWebSocket = new ExtensionsWebSocket(mockHttpServer, devServerState);
287
- const { client } = simulateClientConnection();
288
- client.close();
289
- await extensionsWebSocket.close();
290
- extensionsWebSocket.broadcast({ event: 'test' });
291
- expect(devServerState.logger.debug).toHaveBeenCalledWith('No clients connected, message not sent');
292
- });
293
- });
294
- describe('error handlers', () => {
295
- it('should log WebSocket server errors', () => {
296
- extensionsWebSocket = new ExtensionsWebSocket(mockHttpServer, devServerState);
297
- const testError = new Error('Server error');
298
- const errorHandler = vi.mocked(mockWss.on).mock.calls.find((call) => call[0] === 'error')?.[1];
299
- errorHandler?.(testError);
300
- expect(devServerState.logger.error).toHaveBeenCalledWith('WebSocket server error: Error: Server error');
301
- });
302
- });
303
- });
304
- });
@@ -1 +0,0 @@
1
- export {};