@hubspot/ui-extensions-dev-server 0.10.2 → 1.0.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/README.md +23 -4
  2. package/dist/index.d.ts +3 -3
  3. package/dist/index.js +4 -45
  4. package/dist/lib/DevModeInterface.d.ts +2 -2
  5. package/dist/lib/DevModeInterface.js +12 -28
  6. package/dist/lib/DevModeParentInterface.d.ts +2 -2
  7. package/dist/lib/DevModeParentInterface.js +138 -154
  8. package/dist/lib/DevModeUnifiedInterface.d.ts +2 -2
  9. package/dist/lib/DevModeUnifiedInterface.js +28 -49
  10. package/dist/lib/DevServerState.d.ts +9 -5
  11. package/dist/lib/DevServerState.js +37 -18
  12. package/dist/lib/ExtensionsWebSocket.d.ts +25 -0
  13. package/dist/lib/ExtensionsWebSocket.js +110 -0
  14. package/dist/lib/__mocks__/config.d.ts +2 -0
  15. package/dist/lib/__mocks__/config.js +5 -0
  16. package/dist/lib/__mocks__/isExtensionFile.d.ts +5 -0
  17. package/dist/lib/__mocks__/isExtensionFile.js +11 -0
  18. package/dist/lib/__tests__/DevModeInterface.spec.d.ts +1 -0
  19. package/dist/lib/__tests__/DevModeInterface.spec.js +155 -0
  20. package/dist/lib/__tests__/DevModeParentInterface.spec.d.ts +1 -0
  21. package/dist/lib/__tests__/DevModeParentInterface.spec.js +179 -0
  22. package/dist/lib/__tests__/DevModeUnifiedInterface.spec.d.ts +1 -0
  23. package/dist/lib/__tests__/DevModeUnifiedInterface.spec.js +236 -0
  24. package/dist/lib/__tests__/ExtensionsWebSocket.spec.d.ts +1 -0
  25. package/dist/lib/__tests__/ExtensionsWebSocket.spec.js +304 -0
  26. package/dist/lib/__tests__/ast.spec.d.ts +1 -0
  27. package/dist/lib/__tests__/ast.spec.js +737 -0
  28. package/dist/lib/__tests__/build.spec.d.ts +1 -0
  29. package/dist/lib/__tests__/build.spec.js +159 -0
  30. package/dist/lib/__tests__/config.spec.d.ts +1 -0
  31. package/dist/lib/__tests__/config.spec.js +291 -0
  32. package/dist/lib/__tests__/dev.spec.d.ts +1 -0
  33. package/dist/lib/__tests__/dev.spec.js +80 -0
  34. package/dist/lib/__tests__/extensionsService.spec.d.ts +1 -0
  35. package/dist/lib/__tests__/extensionsService.spec.js +150 -0
  36. package/dist/lib/__tests__/factories.d.ts +48 -0
  37. package/dist/lib/__tests__/factories.js +32 -0
  38. package/dist/lib/__tests__/fixtures/extensionConfig.d.ts +182 -0
  39. package/dist/lib/__tests__/fixtures/extensionConfig.js +304 -0
  40. package/dist/lib/__tests__/fixtures/urls.d.ts +4 -0
  41. package/dist/lib/__tests__/fixtures/urls.js +4 -0
  42. package/dist/lib/__tests__/parsing-utils.spec.d.ts +1 -0
  43. package/dist/lib/__tests__/parsing-utils.spec.js +467 -0
  44. package/dist/lib/__tests__/plugins/codeBlockingPlugin.spec.d.ts +1 -0
  45. package/dist/lib/__tests__/plugins/codeBlockingPlugin.spec.js +112 -0
  46. package/dist/lib/__tests__/plugins/codeCheckingPlugin.spec.d.ts +1 -0
  47. package/dist/lib/__tests__/plugins/codeCheckingPlugin.spec.js +73 -0
  48. package/dist/lib/__tests__/plugins/devBuildPlugin.spec.d.ts +1 -0
  49. package/dist/lib/__tests__/plugins/devBuildPlugin.spec.js +256 -0
  50. package/dist/lib/__tests__/plugins/friendlyLoggingPlugin.spec.d.ts +1 -0
  51. package/dist/lib/__tests__/plugins/friendlyLoggingPlugin.spec.js +65 -0
  52. package/dist/lib/__tests__/plugins/manifestPlugin.spec.d.ts +1 -0
  53. package/dist/lib/__tests__/plugins/manifestPlugin.spec.js +455 -0
  54. package/dist/lib/__tests__/plugins/relevantModulesPlugin.spec.d.ts +1 -0
  55. package/dist/lib/__tests__/plugins/relevantModulesPlugin.spec.js +81 -0
  56. package/dist/lib/__tests__/server.spec.d.ts +1 -0
  57. package/dist/lib/__tests__/server.spec.js +152 -0
  58. package/dist/lib/__tests__/test-utils/ast.d.ts +1 -0
  59. package/dist/lib/__tests__/test-utils/ast.js +4 -0
  60. package/dist/lib/__tests__/utils.spec.d.ts +1 -0
  61. package/dist/lib/__tests__/utils.spec.js +176 -0
  62. package/dist/lib/ast.d.ts +1 -1
  63. package/dist/lib/ast.js +22 -29
  64. package/dist/lib/bin/cli.js +52 -72
  65. package/dist/lib/build.d.ts +1 -1
  66. package/dist/lib/build.js +60 -78
  67. package/dist/lib/config.d.ts +1 -1
  68. package/dist/lib/config.js +31 -34
  69. package/dist/lib/constants.d.ts +0 -2
  70. package/dist/lib/constants.js +20 -27
  71. package/dist/lib/dev.d.ts +1 -1
  72. package/dist/lib/dev.js +52 -69
  73. package/dist/lib/extensionsService.d.ts +1 -1
  74. package/dist/lib/extensionsService.js +21 -15
  75. package/dist/lib/parsing-utils.d.ts +1 -1
  76. package/dist/lib/parsing-utils.js +7 -11
  77. package/dist/lib/plugins/codeBlockingPlugin.d.ts +1 -1
  78. package/dist/lib/plugins/codeBlockingPlugin.js +5 -8
  79. package/dist/lib/plugins/codeCheckingPlugin.d.ts +1 -1
  80. package/dist/lib/plugins/codeCheckingPlugin.js +4 -9
  81. package/dist/lib/plugins/devBuildPlugin.d.ts +2 -2
  82. package/dist/lib/plugins/devBuildPlugin.js +74 -99
  83. package/dist/lib/plugins/friendlyLoggingPlugin.d.ts +2 -2
  84. package/dist/lib/plugins/friendlyLoggingPlugin.js +4 -12
  85. package/dist/lib/plugins/manifestPlugin.d.ts +1 -1
  86. package/dist/lib/plugins/manifestPlugin.js +46 -26
  87. package/dist/lib/plugins/relevantModulesPlugin.d.ts +2 -2
  88. package/dist/lib/plugins/relevantModulesPlugin.js +4 -7
  89. package/dist/lib/server.d.ts +7 -2
  90. package/dist/lib/server.js +85 -84
  91. package/dist/lib/types.d.ts +1 -1
  92. package/dist/lib/types.js +4 -7
  93. package/dist/lib/utils.d.ts +1 -1
  94. package/dist/lib/utils.js +23 -40
  95. package/package.json +44 -31
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,159 @@
1
+ import { describe, it, expect, vi, afterEach } from 'vitest';
2
+ vi.mock('vite');
3
+ import { buildSingleExtension, remoteBuild, extensionErrorBaseMessage, } from "../build.js";
4
+ import { build } from 'vite';
5
+ import { ROLLUP_OPTIONS } from "../constants.js";
6
+ describe('build', () => {
7
+ afterEach(() => {
8
+ vi.clearAllMocks();
9
+ });
10
+ describe('buildSingleExtension', () => {
11
+ it('should build without the need to load config', async () => {
12
+ await buildSingleExtension({
13
+ file: 'SomeFile.jsx',
14
+ outputDir: 'builds',
15
+ emptyOutDir: false,
16
+ minify: true,
17
+ root: '/some/fake/dir',
18
+ });
19
+ expect(build).toHaveBeenCalledTimes(1);
20
+ });
21
+ it("should call vite's build function with the correct arguments", async () => {
22
+ const expected = {
23
+ file: 'SomeFile.jsx',
24
+ outputDir: 'builds',
25
+ emptyOutDir: false,
26
+ minify: true,
27
+ root: '/some/fake/dir',
28
+ };
29
+ const output = 'SomeFile.js';
30
+ await buildSingleExtension(expected);
31
+ expect(build).toHaveBeenCalledTimes(1);
32
+ expect(build).toHaveBeenCalledWith(expect.objectContaining({
33
+ root: expected.root,
34
+ build: expect.objectContaining({
35
+ emptyOutDir: expected.emptyOutDir,
36
+ lib: expect.objectContaining({
37
+ entry: expected.file,
38
+ name: output,
39
+ formats: ['iife'],
40
+ }),
41
+ outDir: expected.outputDir,
42
+ minify: expected.minify,
43
+ rollupOptions: {
44
+ ...ROLLUP_OPTIONS,
45
+ plugins: expect.arrayContaining([
46
+ expect.objectContaining({
47
+ name: 'ui-extensions-manifest-generation-plugin',
48
+ }),
49
+ expect.objectContaining({
50
+ name: 'ui-extensions-friendly-logging-plugin',
51
+ }),
52
+ expect.objectContaining({
53
+ name: 'ui-extensions-code-blocking-plugin',
54
+ }),
55
+ ]),
56
+ },
57
+ }),
58
+ }));
59
+ });
60
+ it('should pass appConfig to manifestPlugin when provided', async () => {
61
+ const mockAppConfig = {
62
+ variables: {
63
+ name: 'Test Extension',
64
+ version: '1.0.0',
65
+ },
66
+ };
67
+ await buildSingleExtension({
68
+ file: 'SomeFile.jsx',
69
+ outputDir: 'builds',
70
+ emptyOutDir: false,
71
+ minify: true,
72
+ root: '/some/fake/dir',
73
+ appConfig: mockAppConfig,
74
+ });
75
+ expect(build).toHaveBeenCalledTimes(1);
76
+ expect(build).toHaveBeenCalledWith(expect.objectContaining({
77
+ build: expect.objectContaining({
78
+ rollupOptions: expect.objectContaining({
79
+ plugins: expect.arrayContaining([
80
+ expect.objectContaining({
81
+ name: 'ui-extensions-manifest-generation-plugin',
82
+ }),
83
+ ]),
84
+ }),
85
+ }),
86
+ }));
87
+ });
88
+ it('should work without appConfig parameter', async () => {
89
+ await buildSingleExtension({
90
+ file: 'SomeFile.jsx',
91
+ outputDir: 'builds',
92
+ emptyOutDir: false,
93
+ minify: true,
94
+ root: '/some/fake/dir',
95
+ });
96
+ expect(build).toHaveBeenCalledTimes(1);
97
+ });
98
+ });
99
+ describe('remoteBuild', () => {
100
+ it('should throw an error for invalid file extensions', async () => {
101
+ await expect(remoteBuild({ root: '/fake/file/path', entryPoint: 'file.xml' })).rejects.toThrow(`${extensionErrorBaseMessage} .xml`);
102
+ });
103
+ it('should call build with the correct arguments', async () => {
104
+ const root = '/fake/file/path';
105
+ const entry = 'file.jsx';
106
+ const outDir = 'someOutDir';
107
+ await remoteBuild({ root, entryPoint: entry, outputDir: outDir });
108
+ expect(build).toHaveBeenCalledTimes(1);
109
+ expect(build).toHaveBeenCalledWith(expect.objectContaining({
110
+ build: expect.objectContaining({
111
+ emptyOutDir: true,
112
+ lib: expect.objectContaining({
113
+ entry,
114
+ name: 'file.js',
115
+ }),
116
+ outDir,
117
+ minify: true,
118
+ }),
119
+ root,
120
+ }));
121
+ });
122
+ it('should pass appConfig parameter when provided', async () => {
123
+ const mockAppConfig = {
124
+ variables: {
125
+ name: 'Test Remote Extension',
126
+ version: '2.0.0',
127
+ },
128
+ };
129
+ const root = '/fake/file/path';
130
+ const entry = 'file.jsx';
131
+ const outDir = 'someOutDir';
132
+ await remoteBuild({
133
+ root,
134
+ entryPoint: entry,
135
+ outputDir: outDir,
136
+ appConfig: mockAppConfig,
137
+ });
138
+ expect(build).toHaveBeenCalledTimes(1);
139
+ expect(build).toHaveBeenCalledWith(expect.objectContaining({
140
+ build: expect.objectContaining({
141
+ emptyOutDir: true,
142
+ lib: expect.objectContaining({
143
+ entry,
144
+ name: 'file.js',
145
+ }),
146
+ outDir,
147
+ minify: true,
148
+ }),
149
+ root,
150
+ }));
151
+ });
152
+ it('should work without appConfig parameter', async () => {
153
+ const root = '/fake/file/path';
154
+ const entry = 'file.jsx';
155
+ await remoteBuild({ root, entryPoint: entry });
156
+ expect(build).toHaveBeenCalledTimes(1);
157
+ });
158
+ });
159
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,291 @@
1
+ import { describe, vi, beforeEach, afterEach, it, expect } from 'vitest';
2
+ import { loadLocalConfig, loadExtensionConfig, validateCardConfig, } from "../config.js";
3
+ import fs from 'fs';
4
+ vi.mock('fs', () => {
5
+ const existsSyncMock = vi.fn(() => true);
6
+ const readFileSyncMock = vi.fn(() => JSON.stringify({
7
+ proxy: { 'https://inbound.com': 'http://localhost' },
8
+ }));
9
+ return {
10
+ default: {
11
+ existsSync: existsSyncMock,
12
+ readFileSync: readFileSyncMock,
13
+ },
14
+ existsSync: existsSyncMock,
15
+ readFileSync: readFileSyncMock,
16
+ };
17
+ });
18
+ const mockProxyConfig = (key, value) => {
19
+ vi.mocked(fs.readFileSync).mockImplementationOnce(() => JSON.stringify({ proxy: { [key]: value } }));
20
+ };
21
+ const createAppConfig = (overrides = {}) => ({
22
+ name: 'Yooooooooooo',
23
+ description: 'Yooooooooooo',
24
+ scopes: [],
25
+ public: false,
26
+ uid: 'no-one-will-know-this-is-not-a-uuid',
27
+ ...overrides,
28
+ });
29
+ describe('config', () => {
30
+ let logger;
31
+ const appPath = 'path/to/the/app';
32
+ beforeEach(() => {
33
+ logger = {
34
+ info: vi.fn(),
35
+ debug: vi.fn(),
36
+ warn: vi.fn(),
37
+ error: vi.fn(),
38
+ };
39
+ });
40
+ afterEach(() => {
41
+ vi.clearAllMocks();
42
+ });
43
+ describe('loadExtensionConfig', () => {
44
+ it('should throw an error if the appConfig is missing the extensions list', () => {
45
+ const appConfig = createAppConfig();
46
+ expect(() => loadExtensionConfig(appConfig, '')).toThrow("Unable to find extensions files, make sure the 'extensions.crm.cards' array is defined in the application configuration file");
47
+ });
48
+ it('should log an error if the appConfig has invalid extension configs', () => {
49
+ const consoleErrorSpy = vi
50
+ .spyOn(console, 'error')
51
+ .mockImplementation(() => { });
52
+ vi.mocked(fs.readFileSync).mockImplementationOnce(() => JSON.stringify({
53
+ type: 'crm-card',
54
+ data: {
55
+ location: 'crm.record.tab',
56
+ module: { file: 'src/index.js' },
57
+ objectTypes: [{ name: 'contact' }],
58
+ },
59
+ }));
60
+ const appConfig = createAppConfig({
61
+ extensions: {
62
+ crm: {
63
+ cards: [{ file: './invalid.json' }],
64
+ },
65
+ },
66
+ });
67
+ expect(() => loadExtensionConfig(appConfig, '')).not.toThrow();
68
+ expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
69
+ expect(consoleErrorSpy).toHaveBeenCalledWith('[DevServer] Extension config invalid.json is invalid: `data` must have a `title` string');
70
+ consoleErrorSpy.mockRestore();
71
+ });
72
+ it('should successfully return a map of extensions if the appConfig has valid extension configs', () => {
73
+ const cardConfig = {
74
+ type: 'crm-card',
75
+ data: {
76
+ title: 'Yes Card',
77
+ location: 'crm.record.tab',
78
+ module: { file: 'src/yes-card.js' },
79
+ objectTypes: [{ name: 'contact' }],
80
+ uid: 'yes-this-is-card',
81
+ },
82
+ };
83
+ vi.mocked(fs.readFileSync).mockImplementation(() => JSON.stringify(cardConfig));
84
+ const appConfig = createAppConfig({
85
+ uid: 'app-uid-here',
86
+ extensions: {
87
+ crm: {
88
+ cards: [{ file: 'yes-card.json' }],
89
+ },
90
+ },
91
+ });
92
+ expect(() => loadExtensionConfig(appConfig, '')).not.toThrow();
93
+ expect(loadExtensionConfig(appConfig, '')).toEqual({
94
+ 'app-uid-here::yes-this-is-card': {
95
+ ...cardConfig,
96
+ appConfig,
97
+ extensionConfigPath: 'yes-card.json',
98
+ extensionPath: 'src',
99
+ output: 'yes-card.js',
100
+ path: '',
101
+ data: {
102
+ ...cardConfig.data,
103
+ appName: 'Yooooooooooo',
104
+ sourceId: 'app-uid-here::yes-this-is-card',
105
+ },
106
+ },
107
+ });
108
+ });
109
+ });
110
+ describe('loadLocalConfig', () => {
111
+ it('should log a warning if the key has a path', () => {
112
+ const key = 'https://inbound.com/this/is/a/path';
113
+ mockProxyConfig(key, 'http://localhost');
114
+ loadLocalConfig(appPath, logger);
115
+ expect(logger.warn).toHaveBeenCalledTimes(1);
116
+ expect(logger.warn).toHaveBeenCalledWith(`The key "${key}" in "local.json" is invalid, paths are not supported for keys`);
117
+ });
118
+ it('should log a warning if the key is an invalid url', () => {
119
+ const key = 'invalid-url';
120
+ mockProxyConfig(key, 'http://localhost');
121
+ loadLocalConfig(appPath, logger);
122
+ expect(logger.warn).toHaveBeenCalledTimes(1);
123
+ expect(logger.warn).toHaveBeenCalledWith(`The key "${key}" in "local.json" is an invalid url`);
124
+ });
125
+ it('should not log a warning if the key is a valid url', () => {
126
+ mockProxyConfig('http://inbound.com', 'http://localhost');
127
+ loadLocalConfig(appPath, logger);
128
+ expect(logger.warn).not.toHaveBeenCalled();
129
+ });
130
+ it('should log a warning if the value is an invalid url', () => {
131
+ const key = 'https://inbound.com';
132
+ const value = 'invalid-url';
133
+ mockProxyConfig(key, value);
134
+ loadLocalConfig(appPath, logger);
135
+ expect(logger.warn).toHaveBeenCalledTimes(1);
136
+ expect(logger.warn).toHaveBeenCalledWith(`The value "${value}" for key "${key}" in "local.json" is an invalid url`);
137
+ });
138
+ it('should not log a warning if both key and value are valid urls', () => {
139
+ mockProxyConfig('https://inbound.com', 'https://localhost');
140
+ loadLocalConfig(appPath, logger);
141
+ expect(logger.warn).not.toHaveBeenCalled();
142
+ });
143
+ it('should return undefined if the local config file does not exist', () => {
144
+ vi.mocked(fs.existsSync).mockImplementationOnce(() => false);
145
+ expect(loadLocalConfig('foo', logger)).toBeUndefined();
146
+ });
147
+ it('should log an error if we are unable to parse the file', () => {
148
+ vi.mocked(fs.readFileSync).mockImplementationOnce(() => 'this-will-not-be-parsed-to-json');
149
+ loadLocalConfig(appPath, logger);
150
+ expect(logger.error).toHaveBeenCalledTimes(1);
151
+ expect(logger.error).toHaveBeenCalledWith(expect.stringMatching(`Error loading and parsing ${appPath}/local.json, SyntaxError: Unexpected token`));
152
+ });
153
+ it('should return the config if it is valid', () => {
154
+ const expected = {
155
+ proxy: {
156
+ 'https://inbound.com': 'http://localhost',
157
+ },
158
+ };
159
+ vi.mocked(fs.readFileSync).mockImplementationOnce(() => JSON.stringify(expected));
160
+ expect(loadLocalConfig(appPath, logger)).toEqual(expected);
161
+ });
162
+ });
163
+ describe('validateCardConfig', () => {
164
+ const validCardConfig = {
165
+ type: 'crm-card',
166
+ data: {
167
+ title: 'Example Card',
168
+ location: 'crm.record.tab',
169
+ module: { file: 'src/index.js' },
170
+ objectTypes: [{ name: 'contact' }],
171
+ },
172
+ };
173
+ const expectValidationError = (config, errorMessage) => {
174
+ expect(validateCardConfig(config)).toEqual(new Error(errorMessage));
175
+ };
176
+ it('should return an Error if the card config is missing the `type` property', () => {
177
+ expectValidationError({ data: validCardConfig.data }, '`type` must be "crm-card"');
178
+ });
179
+ it('should return an Error if the card config has a `type` value other than `crm-card`', () => {
180
+ expectValidationError({ ...validCardConfig, type: 'something-incorrect' }, '`type` must be "crm-card"');
181
+ });
182
+ it('should return an Error if the card config is missing a `data` property', () => {
183
+ expectValidationError({ type: 'crm-card' }, '`data` must be an object');
184
+ });
185
+ it('should return an Error if the card config data is missing a `title` property', () => {
186
+ expectValidationError({
187
+ type: 'crm-card',
188
+ data: {
189
+ location: 'crm.record.tab',
190
+ module: { file: 'src/index.js' },
191
+ objectTypes: [{ name: 'contact' }],
192
+ },
193
+ }, '`data` must have a `title` string');
194
+ });
195
+ it('should return an Error if the card config `title` property is not a string', () => {
196
+ expectValidationError({
197
+ type: 'crm-card',
198
+ data: { ...validCardConfig.data, title: null },
199
+ }, '`data` must have a `title` string');
200
+ });
201
+ it('should return an Error if the card config data is missing a `location` property', () => {
202
+ expectValidationError({
203
+ type: 'crm-card',
204
+ data: {
205
+ title: 'Example Card',
206
+ module: { file: 'src/index.js' },
207
+ objectTypes: [{ name: 'contact' }],
208
+ },
209
+ }, '`data` must have a `location` string');
210
+ });
211
+ it('should return an Error if the card config `location` property is not a string', () => {
212
+ expectValidationError({
213
+ type: 'crm-card',
214
+ data: { ...validCardConfig.data, location: null },
215
+ }, '`data` must have a `location` string');
216
+ });
217
+ it('should return an Error if the card config data is missing a `module` property', () => {
218
+ expectValidationError({
219
+ type: 'crm-card',
220
+ data: {
221
+ title: 'Example Card',
222
+ location: 'crm.record.tab',
223
+ objectTypes: [{ name: 'contact' }],
224
+ },
225
+ }, '`data` must have a `module` object');
226
+ });
227
+ it('should return an Error if the card config `module` property is not an object', () => {
228
+ expectValidationError({
229
+ type: 'crm-card',
230
+ data: { ...validCardConfig.data, module: 'src/index.js' },
231
+ }, '`data` must have a `module` object');
232
+ });
233
+ it('should return an Error if the card config `module` property has no `file` property', () => {
234
+ expectValidationError({
235
+ type: 'crm-card',
236
+ data: { ...validCardConfig.data, module: {} },
237
+ }, '`data.module` must have a `file` string');
238
+ });
239
+ it('should return an Error if the card config `module` property has an incorrect `file` property', () => {
240
+ expectValidationError({
241
+ type: 'crm-card',
242
+ data: { ...validCardConfig.data, module: { file: null } },
243
+ }, '`data.module` must have a `file` string');
244
+ });
245
+ it('should return an Error if the card config is missing an `objectTypes` property', () => {
246
+ expectValidationError({
247
+ type: 'crm-card',
248
+ data: {
249
+ title: 'Example Card',
250
+ location: 'crm.record.tab',
251
+ module: { file: 'src/index.js' },
252
+ },
253
+ }, '`data.module` must have an `objectTypes` array');
254
+ });
255
+ it('should return an Error if the card config `objectTypes` property is not an array', () => {
256
+ expectValidationError({
257
+ type: 'crm-card',
258
+ data: { ...validCardConfig.data, objectTypes: null },
259
+ }, '`data.module` must have an `objectTypes` array');
260
+ });
261
+ it('should return an Error if the card config `objectTypes` property is an empty array', () => {
262
+ expectValidationError({
263
+ type: 'crm-card',
264
+ data: { ...validCardConfig.data, objectTypes: [] },
265
+ }, 'all `data.module.objectTypes` objects must have `name` strings');
266
+ });
267
+ it('should return an Error if the card config `objectType` object `name` is not a string', () => {
268
+ expectValidationError({
269
+ type: 'crm-card',
270
+ data: {
271
+ ...validCardConfig.data,
272
+ objectTypes: [{ name: null }],
273
+ },
274
+ }, 'all `data.module.objectTypes` objects must have `name` strings');
275
+ });
276
+ it('should return true if the config is valid', () => {
277
+ expect(validateCardConfig(validCardConfig)).toBe(true);
278
+ });
279
+ it('should return true if the config is valid and has extra fields', () => {
280
+ const cardConfig = {
281
+ ...validCardConfig,
282
+ data: {
283
+ ...validCardConfig.data,
284
+ uid: 'cool-card',
285
+ },
286
+ thisDoesNot: 'matter here',
287
+ };
288
+ expect(validateCardConfig(cardConfig)).toBe(true);
289
+ });
290
+ });
291
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,80 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { startDevMode } from "../dev.js";
3
+ import { createServer } from 'vite';
4
+ import startDevServer from "../server.js";
5
+ import { DevServerState } from "../DevServerState.js";
6
+ import { createMockLogger, createDevServerConfig } from "./factories.js";
7
+ vi.mock('vite', () => ({
8
+ createServer: vi.fn(() => ({
9
+ name: 'vite-dev-server',
10
+ })),
11
+ }));
12
+ vi.mock('../server', () => ({
13
+ default: vi.fn(() => ({
14
+ httpServer: {},
15
+ shutdown: vi.fn(),
16
+ })),
17
+ }));
18
+ vi.mock('detect-port', () => ({
19
+ default: vi.fn((port) => {
20
+ if (port === 7890) {
21
+ return 1235; // Simulate port unavailable
22
+ }
23
+ return port;
24
+ }),
25
+ }));
26
+ describe('dev', () => {
27
+ let devServerState;
28
+ let logger;
29
+ beforeEach(() => {
30
+ vi.clearAllMocks();
31
+ logger = createMockLogger();
32
+ devServerState = new DevServerState(createDevServerConfig(logger));
33
+ });
34
+ describe('startDevMode', () => {
35
+ it('should throw an error if extensionConfig is undefined', async () => {
36
+ // @ts-expect-error Causing an error on purpose
37
+ await expect(startDevMode(undefined)).rejects.toThrow(/^Unable to determine which extension to run/);
38
+ });
39
+ it('should throw an error if the express port is in use', async () => {
40
+ const badPort = new DevServerState(createDevServerConfig(logger, { expressPort: 7890 }));
41
+ await expect(startDevMode(badPort)).rejects.toThrow(/^Unable to start because port 7890 is already in use/);
42
+ });
43
+ it('should call vite createServer', async () => {
44
+ await startDevMode(devServerState);
45
+ expect(createServer).toHaveBeenCalledTimes(1);
46
+ expect(createServer).toHaveBeenCalledWith(expect.objectContaining({
47
+ appType: 'custom',
48
+ mode: 'development',
49
+ server: expect.objectContaining({
50
+ middlewareMode: true,
51
+ hmr: expect.objectContaining({
52
+ server: null,
53
+ }),
54
+ cors: expect.objectContaining({
55
+ credentials: true,
56
+ }),
57
+ watch: {
58
+ ignored: [
59
+ `${devServerState.outputDir}/**/*`,
60
+ '**/src/app/app.functions/**/*',
61
+ '**/app.json',
62
+ '**/package.json',
63
+ '**/package-lock.json',
64
+ ],
65
+ },
66
+ }),
67
+ clearScreen: false,
68
+ logLevel: 'silent',
69
+ }));
70
+ });
71
+ it('should call startDevServer', async () => {
72
+ await startDevMode(devServerState);
73
+ expect(startDevServer).toHaveBeenCalledTimes(1);
74
+ expect(startDevServer).toHaveBeenCalledWith({
75
+ devServerState,
76
+ viteDevServer: { name: 'vite-dev-server' },
77
+ });
78
+ });
79
+ });
80
+ });
@@ -0,0 +1 @@
1
+ export {};