@hubspot/ui-extensions-dev-server 1.1.0 → 1.1.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.
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/lib/DevModeInterface.d.ts +9 -0
- package/dist/lib/DevModeInterface.js +36 -0
- package/dist/lib/DevModeParentInterface.d.ts +19 -0
- package/dist/lib/DevModeParentInterface.js +181 -0
- package/dist/lib/DevModeUnifiedInterface.d.ts +9 -0
- package/dist/lib/DevModeUnifiedInterface.js +118 -0
- package/dist/lib/DevServerState.d.ts +44 -0
- package/dist/lib/DevServerState.js +95 -0
- package/dist/lib/ExtensionsWebSocket.d.ts +25 -0
- package/dist/lib/ExtensionsWebSocket.js +110 -0
- package/dist/lib/__mocks__/config.d.ts +2 -0
- package/dist/lib/__mocks__/config.js +5 -0
- package/dist/lib/__mocks__/isExtensionFile.d.ts +5 -0
- package/dist/lib/__mocks__/isExtensionFile.js +11 -0
- package/dist/lib/__tests__/DevModeInterface.spec.d.ts +1 -0
- package/dist/lib/__tests__/DevModeInterface.spec.js +155 -0
- package/dist/lib/__tests__/DevModeParentInterface.spec.d.ts +1 -0
- package/dist/lib/__tests__/DevModeParentInterface.spec.js +179 -0
- package/dist/lib/__tests__/DevModeUnifiedInterface.spec.d.ts +1 -0
- package/dist/lib/__tests__/DevModeUnifiedInterface.spec.js +236 -0
- package/dist/lib/__tests__/ExtensionsWebSocket.spec.d.ts +1 -0
- package/dist/lib/__tests__/ExtensionsWebSocket.spec.js +304 -0
- package/dist/lib/__tests__/ast.spec.d.ts +1 -0
- package/dist/lib/__tests__/ast.spec.js +737 -0
- package/dist/lib/__tests__/build.spec.d.ts +1 -0
- package/dist/lib/__tests__/build.spec.js +159 -0
- package/dist/lib/__tests__/config.spec.d.ts +1 -0
- package/dist/lib/__tests__/config.spec.js +291 -0
- package/dist/lib/__tests__/dev.spec.d.ts +1 -0
- package/dist/lib/__tests__/dev.spec.js +80 -0
- package/dist/lib/__tests__/extensionsService.spec.d.ts +1 -0
- package/dist/lib/__tests__/extensionsService.spec.js +150 -0
- package/dist/lib/__tests__/factories.d.ts +48 -0
- package/dist/lib/__tests__/factories.js +32 -0
- package/dist/lib/__tests__/fixtures/extensionConfig.d.ts +182 -0
- package/dist/lib/__tests__/fixtures/extensionConfig.js +304 -0
- package/dist/lib/__tests__/fixtures/urls.d.ts +4 -0
- package/dist/lib/__tests__/fixtures/urls.js +4 -0
- package/dist/lib/__tests__/parsing-utils.spec.d.ts +1 -0
- package/dist/lib/__tests__/parsing-utils.spec.js +467 -0
- package/dist/lib/__tests__/plugins/codeBlockingPlugin.spec.d.ts +1 -0
- package/dist/lib/__tests__/plugins/codeBlockingPlugin.spec.js +112 -0
- package/dist/lib/__tests__/plugins/codeCheckingPlugin.spec.d.ts +1 -0
- package/dist/lib/__tests__/plugins/codeCheckingPlugin.spec.js +111 -0
- package/dist/lib/__tests__/plugins/devBuildPlugin.spec.d.ts +1 -0
- package/dist/lib/__tests__/plugins/devBuildPlugin.spec.js +345 -0
- package/dist/lib/__tests__/plugins/friendlyLoggingPlugin.spec.d.ts +1 -0
- package/dist/lib/__tests__/plugins/friendlyLoggingPlugin.spec.js +65 -0
- package/dist/lib/__tests__/plugins/manifestPlugin.spec.d.ts +1 -0
- package/dist/lib/__tests__/plugins/manifestPlugin.spec.js +455 -0
- package/dist/lib/__tests__/plugins/relevantModulesPlugin.spec.d.ts +1 -0
- package/dist/lib/__tests__/plugins/relevantModulesPlugin.spec.js +81 -0
- package/dist/lib/__tests__/server.spec.d.ts +1 -0
- package/dist/lib/__tests__/server.spec.js +152 -0
- package/dist/lib/__tests__/test-utils/ast.d.ts +1 -0
- package/dist/lib/__tests__/test-utils/ast.js +4 -0
- package/dist/lib/__tests__/utils.spec.d.ts +1 -0
- package/dist/lib/__tests__/utils.spec.js +176 -0
- package/dist/lib/ast.d.ts +16 -0
- package/dist/lib/ast.js +281 -0
- package/dist/lib/bin/cli.d.ts +2 -0
- package/dist/lib/bin/cli.js +143 -0
- package/dist/lib/build.d.ts +24 -0
- package/dist/lib/build.js +73 -0
- package/dist/lib/config.d.ts +7 -0
- package/dist/lib/config.js +124 -0
- package/dist/lib/constants.d.ts +32 -0
- package/dist/lib/constants.js +43 -0
- package/dist/lib/dev.d.ts +2 -0
- package/dist/lib/dev.js +58 -0
- package/dist/lib/extensionsService.d.ts +10 -0
- package/dist/lib/extensionsService.js +45 -0
- package/dist/lib/parsing-utils.d.ts +31 -0
- package/dist/lib/parsing-utils.js +289 -0
- package/dist/lib/plugins/codeBlockingPlugin.d.ts +8 -0
- package/dist/lib/plugins/codeBlockingPlugin.js +45 -0
- package/dist/lib/plugins/codeCheckingPlugin.d.ts +8 -0
- package/dist/lib/plugins/codeCheckingPlugin.js +89 -0
- package/dist/lib/plugins/devBuildPlugin.d.ts +8 -0
- package/dist/lib/plugins/devBuildPlugin.js +201 -0
- package/dist/lib/plugins/friendlyLoggingPlugin.d.ts +14 -0
- package/dist/lib/plugins/friendlyLoggingPlugin.js +36 -0
- package/dist/lib/plugins/manifestPlugin.d.ts +12 -0
- package/dist/lib/plugins/manifestPlugin.js +158 -0
- package/dist/lib/plugins/relevantModulesPlugin.d.ts +13 -0
- package/dist/lib/plugins/relevantModulesPlugin.js +25 -0
- package/dist/lib/server.d.ts +13 -0
- package/dist/lib/server.js +99 -0
- package/dist/lib/types.d.ts +290 -0
- package/dist/lib/types.js +12 -0
- package/dist/lib/utils.d.ts +25 -0
- package/dist/lib/utils.js +113 -0
- package/package.json +1 -1
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { tmpdir } from 'os';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { build } from 'vite';
|
|
5
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
6
|
+
import codeCheckingPlugin from "../../plugins/codeCheckingPlugin.js";
|
|
7
|
+
import { ROLLUP_OPTIONS } from "../../constants.js";
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
const fixturesDir = path.join(__dirname, 'fixtures/codeCheckingPlugin');
|
|
11
|
+
async function runViteBuild(fixtureFile, plugin) {
|
|
12
|
+
const fixturePath = path.join(fixturesDir, fixtureFile);
|
|
13
|
+
const outputDir = path.join(tmpdir(), `vite-test-${Date.now()}-${Math.random().toString(36).substring(7)}`);
|
|
14
|
+
await build({
|
|
15
|
+
logLevel: 'silent',
|
|
16
|
+
root: fixturesDir,
|
|
17
|
+
build: {
|
|
18
|
+
lib: {
|
|
19
|
+
entry: fixturePath,
|
|
20
|
+
name: 'test',
|
|
21
|
+
formats: ['es'],
|
|
22
|
+
fileName: () => 'test.js',
|
|
23
|
+
},
|
|
24
|
+
rollupOptions: {
|
|
25
|
+
...ROLLUP_OPTIONS,
|
|
26
|
+
external: [
|
|
27
|
+
...(ROLLUP_OPTIONS.external || []),
|
|
28
|
+
'@hubspot/ui-extensions',
|
|
29
|
+
'other-package',
|
|
30
|
+
],
|
|
31
|
+
plugins: [plugin],
|
|
32
|
+
},
|
|
33
|
+
outDir: outputDir,
|
|
34
|
+
emptyOutDir: true,
|
|
35
|
+
write: false,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
const createPlugin = (options) => {
|
|
40
|
+
const logger = options?.logger || {
|
|
41
|
+
info: vi.fn(),
|
|
42
|
+
error: vi.fn(),
|
|
43
|
+
debug: vi.fn(),
|
|
44
|
+
warn: vi.fn(),
|
|
45
|
+
};
|
|
46
|
+
return {
|
|
47
|
+
logger,
|
|
48
|
+
plugin: codeCheckingPlugin({ logger, ...options }),
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
describe('codeCheckingPlugin', () => {
|
|
52
|
+
describe('with warning', () => {
|
|
53
|
+
it('should log a warning when entry point has no imports from @hubspot/ui-extensions', async () => {
|
|
54
|
+
const { logger, plugin } = createPlugin();
|
|
55
|
+
await runViteBuild('emptyFile.js', plugin);
|
|
56
|
+
expect(logger.warn).toHaveBeenCalledTimes(1);
|
|
57
|
+
const warnCall = logger.warn.mock
|
|
58
|
+
.calls[0][0];
|
|
59
|
+
expect(warnCall).toContain('WARNING:');
|
|
60
|
+
expect(warnCall).toContain('hubspot');
|
|
61
|
+
expect(warnCall).toContain('@hubspot/ui-extensions');
|
|
62
|
+
});
|
|
63
|
+
it('should log a warning when code does not contain @hubspot/ui-extensions package at all', async () => {
|
|
64
|
+
const { logger, plugin } = createPlugin();
|
|
65
|
+
await runViteBuild('withoutHubspotPackage.js', plugin);
|
|
66
|
+
expect(logger.warn).toHaveBeenCalledTimes(1);
|
|
67
|
+
});
|
|
68
|
+
it('should log a warning when code imports from @hubspot/ui-extensions but does not import hubspot', async () => {
|
|
69
|
+
const { logger, plugin } = createPlugin();
|
|
70
|
+
await runViteBuild('withoutHubspotImport.js', plugin);
|
|
71
|
+
expect(logger.warn).toHaveBeenCalledTimes(1);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
describe('without warning', () => {
|
|
75
|
+
it('should not log a warning when hubspot is imported from a separate entry point', async () => {
|
|
76
|
+
const { logger, plugin } = createPlugin();
|
|
77
|
+
await runViteBuild('withHubspotImportInSeparateEntry.js', plugin);
|
|
78
|
+
expect(logger.warn).not.toHaveBeenCalled();
|
|
79
|
+
});
|
|
80
|
+
it('should not log a warning when hubspot is imported from @hubspot/ui-extensions', async () => {
|
|
81
|
+
const { logger, plugin } = createPlugin();
|
|
82
|
+
await runViteBuild('withHubspotImport.js', plugin);
|
|
83
|
+
expect(logger.warn).not.toHaveBeenCalled();
|
|
84
|
+
});
|
|
85
|
+
it('should not log a warning when hubspot is imported alongside other exports from @hubspot/ui-extensions', async () => {
|
|
86
|
+
const { logger, plugin } = createPlugin();
|
|
87
|
+
await runViteBuild('withHubspotAndOtherExports.js', plugin);
|
|
88
|
+
expect(logger.warn).not.toHaveBeenCalled();
|
|
89
|
+
});
|
|
90
|
+
it('should not log a warning when hubspot is imported with other exports in any order', async () => {
|
|
91
|
+
const { logger, plugin } = createPlugin();
|
|
92
|
+
await runViteBuild('withHubspotInMixedExports.js', plugin);
|
|
93
|
+
expect(logger.warn).not.toHaveBeenCalled();
|
|
94
|
+
});
|
|
95
|
+
it('should not log a warning when hubspot is imported using single quotes', async () => {
|
|
96
|
+
const { logger, plugin } = createPlugin();
|
|
97
|
+
await runViteBuild('withHubspotImportSingleQuotes.js', plugin);
|
|
98
|
+
expect(logger.warn).not.toHaveBeenCalled();
|
|
99
|
+
});
|
|
100
|
+
it('should not log a warning when using namespace import from @hubspot/ui-extensions', async () => {
|
|
101
|
+
const { logger, plugin } = createPlugin();
|
|
102
|
+
await runViteBuild('withNamespaceImport.js', plugin);
|
|
103
|
+
expect(logger.warn).not.toHaveBeenCalled();
|
|
104
|
+
});
|
|
105
|
+
it('should not log a warning when hubspot is imported in TypeScript files', async () => {
|
|
106
|
+
const { logger, plugin } = createPlugin();
|
|
107
|
+
await runViteBuild('ts-withHubspotImport.ts', plugin);
|
|
108
|
+
expect(logger.warn).not.toHaveBeenCalled();
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
vi.mock('../../plugins/relevantModulesPlugin', () => {
|
|
3
|
+
return {
|
|
4
|
+
__esModule: true,
|
|
5
|
+
getRelevantModules: () => {
|
|
6
|
+
return ['extension.js', 'helper-file.js'];
|
|
7
|
+
},
|
|
8
|
+
default: () => {
|
|
9
|
+
return {
|
|
10
|
+
name: 'ui-extensions-relevant-modules-plugin',
|
|
11
|
+
};
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
});
|
|
15
|
+
vi.mock('vite');
|
|
16
|
+
import devBuildPlugin from "../../plugins/devBuildPlugin.js";
|
|
17
|
+
import { build } from 'vite';
|
|
18
|
+
import { PLATFORM_VERSION, WEBSOCKET_MESSAGE_VERSION, } from "../../constants.js";
|
|
19
|
+
import { DevServerState } from "../../DevServerState.js";
|
|
20
|
+
import { transformedUnifiedCardOneConfig } from "../fixtures/extensionConfig.js";
|
|
21
|
+
import { urls } from "../fixtures/urls.js";
|
|
22
|
+
describe('devBuildPlugin', () => {
|
|
23
|
+
let options;
|
|
24
|
+
let plugin;
|
|
25
|
+
let server;
|
|
26
|
+
let logger;
|
|
27
|
+
let mockWebSocket;
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
logger = {
|
|
30
|
+
info: vi.fn(),
|
|
31
|
+
error: vi.fn(),
|
|
32
|
+
debug: vi.fn(),
|
|
33
|
+
warn: vi.fn(),
|
|
34
|
+
};
|
|
35
|
+
// Create mock ExtensionsWebSocket
|
|
36
|
+
mockWebSocket = {
|
|
37
|
+
broadcast: vi.fn(),
|
|
38
|
+
onConnection: vi.fn(),
|
|
39
|
+
clientCount: 1,
|
|
40
|
+
};
|
|
41
|
+
options = {
|
|
42
|
+
devServerState: new DevServerState({
|
|
43
|
+
extensionConfigs: [transformedUnifiedCardOneConfig],
|
|
44
|
+
accountId: 88888,
|
|
45
|
+
platformVersion: PLATFORM_VERSION.V20232,
|
|
46
|
+
expressPort: 1234,
|
|
47
|
+
logger,
|
|
48
|
+
urls,
|
|
49
|
+
}),
|
|
50
|
+
};
|
|
51
|
+
// Set the mock websocket on the devServerState
|
|
52
|
+
// @ts-expect-error Setting private property for testing
|
|
53
|
+
options.devServerState._mutableState.extensionsWebSocket =
|
|
54
|
+
mockWebSocket;
|
|
55
|
+
plugin = devBuildPlugin(options);
|
|
56
|
+
server = {};
|
|
57
|
+
});
|
|
58
|
+
afterEach(() => {
|
|
59
|
+
vi.clearAllMocks();
|
|
60
|
+
});
|
|
61
|
+
describe('metadata', () => {
|
|
62
|
+
it('should create the correct plugin metadata', () => {
|
|
63
|
+
expect(plugin).toStrictEqual(expect.objectContaining({
|
|
64
|
+
name: 'ui-extensions-dev-build-plugin',
|
|
65
|
+
enforce: 'pre',
|
|
66
|
+
configureServer: expect.any(Function),
|
|
67
|
+
handleHotUpdate: expect.any(Function),
|
|
68
|
+
buildEnd: expect.any(Function),
|
|
69
|
+
}));
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
describe('configureServer', () => {
|
|
73
|
+
it('should setup event handlers and perform initial build', async () => {
|
|
74
|
+
// @ts-expect-error TS thinks these aren't functions
|
|
75
|
+
await plugin.configureServer(server);
|
|
76
|
+
options.devServerState.triggerWebSocketSetup();
|
|
77
|
+
expect(mockWebSocket.onConnection).toHaveBeenCalledTimes(1);
|
|
78
|
+
expect(mockWebSocket.onConnection).toHaveBeenCalledWith(expect.any(Function));
|
|
79
|
+
// Should also perform initial build for all extensions
|
|
80
|
+
expect(build).toHaveBeenCalledTimes(1);
|
|
81
|
+
});
|
|
82
|
+
it('should broadcast start messages when a client connects', async () => {
|
|
83
|
+
// @ts-expect-error TS thinks these aren't functions
|
|
84
|
+
await plugin.configureServer(server);
|
|
85
|
+
options.devServerState.triggerWebSocketSetup();
|
|
86
|
+
// Get the connection handler that was registered
|
|
87
|
+
const connectionHandler = mockWebSocket.onConnection.mock.calls[0][0];
|
|
88
|
+
// Simulate a connection
|
|
89
|
+
connectionHandler();
|
|
90
|
+
const baseMessage = options.devServerState.extensionsMetadata[0].baseMessage;
|
|
91
|
+
expect(mockWebSocket.broadcast).toHaveBeenCalledWith({
|
|
92
|
+
event: 'start',
|
|
93
|
+
...baseMessage,
|
|
94
|
+
version: WEBSOCKET_MESSAGE_VERSION,
|
|
95
|
+
});
|
|
96
|
+
expect(logger.info).toHaveBeenCalledWith('Browser connected and listening for bundle updates');
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
describe('handleHotUpdate', () => {
|
|
100
|
+
it('should trigger a vite build', async () => {
|
|
101
|
+
// @ts-expect-error TS thinks these aren't functions
|
|
102
|
+
await plugin.handleHotUpdate({ file: 'extension.js', server });
|
|
103
|
+
expect(build).toHaveBeenCalledTimes(1);
|
|
104
|
+
const extensionConfig = options.devServerState.extensionsMetadata[0].config;
|
|
105
|
+
expect(build).toHaveBeenCalledWith(expect.objectContaining({
|
|
106
|
+
mode: 'development',
|
|
107
|
+
logLevel: 'warn',
|
|
108
|
+
clearScreen: false,
|
|
109
|
+
define: expect.objectContaining({
|
|
110
|
+
'process.env.NODE_ENV': expect.any(String),
|
|
111
|
+
}),
|
|
112
|
+
esbuild: expect.objectContaining({
|
|
113
|
+
tsconfigRaw: expect.any(Object),
|
|
114
|
+
}),
|
|
115
|
+
build: expect.objectContaining({
|
|
116
|
+
lib: {
|
|
117
|
+
entry: extensionConfig.data.module.file,
|
|
118
|
+
name: extensionConfig.output,
|
|
119
|
+
formats: ['iife'],
|
|
120
|
+
fileName: expect.any(Function),
|
|
121
|
+
},
|
|
122
|
+
rollupOptions: expect.objectContaining({
|
|
123
|
+
plugins: expect.arrayContaining([
|
|
124
|
+
expect.objectContaining({
|
|
125
|
+
name: 'ui-extensions-manifest-generation-plugin',
|
|
126
|
+
}),
|
|
127
|
+
expect.objectContaining({
|
|
128
|
+
name: 'ui-extensions-code-checking-plugin',
|
|
129
|
+
}),
|
|
130
|
+
expect.objectContaining({
|
|
131
|
+
name: 'ui-extensions-friendly-logging-plugin',
|
|
132
|
+
}),
|
|
133
|
+
expect.objectContaining({
|
|
134
|
+
name: 'ui-extensions-relevant-modules-plugin',
|
|
135
|
+
}),
|
|
136
|
+
expect.objectContaining({
|
|
137
|
+
name: 'ui-extensions-code-blocking-plugin',
|
|
138
|
+
}),
|
|
139
|
+
]),
|
|
140
|
+
}),
|
|
141
|
+
outDir: options.devServerState.outputDir,
|
|
142
|
+
emptyOutDir: false,
|
|
143
|
+
minify: false,
|
|
144
|
+
sourcemap: 'inline',
|
|
145
|
+
}),
|
|
146
|
+
}));
|
|
147
|
+
});
|
|
148
|
+
it('should broadcast error message on build failure', async () => {
|
|
149
|
+
const error = {
|
|
150
|
+
plugin: 'vite:esbuild',
|
|
151
|
+
errors: ['you did something wrong'],
|
|
152
|
+
frame: 'It broke on this line',
|
|
153
|
+
loc: {
|
|
154
|
+
column: 1,
|
|
155
|
+
line: 5,
|
|
156
|
+
},
|
|
157
|
+
id: 'this is the file where things broke',
|
|
158
|
+
};
|
|
159
|
+
vi.mocked(build).mockImplementationOnce(() => {
|
|
160
|
+
// eslint-disable-next-line no-throw-literal
|
|
161
|
+
throw error;
|
|
162
|
+
});
|
|
163
|
+
// @ts-expect-error TS thinks these aren't functions
|
|
164
|
+
await plugin.handleHotUpdate({ file: 'extension.js', server });
|
|
165
|
+
expect(mockWebSocket.broadcast).toHaveBeenCalled();
|
|
166
|
+
const baseMessage = options.devServerState.extensionsMetadata[0].baseMessage;
|
|
167
|
+
expect(mockWebSocket.broadcast).toHaveBeenCalledWith({
|
|
168
|
+
event: 'error',
|
|
169
|
+
...baseMessage,
|
|
170
|
+
error: {
|
|
171
|
+
details: {
|
|
172
|
+
errors: error.errors,
|
|
173
|
+
file: error.id,
|
|
174
|
+
formattedError: error.frame,
|
|
175
|
+
location: error.loc,
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
version: WEBSOCKET_MESSAGE_VERSION,
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
it('should not broadcast when error is from ui-extensions plugin', async () => {
|
|
182
|
+
const error = {
|
|
183
|
+
plugin: 'ui-extensions-some-plugin-that-threw-an-error',
|
|
184
|
+
};
|
|
185
|
+
vi.mocked(build).mockImplementationOnce(() => {
|
|
186
|
+
throw error;
|
|
187
|
+
});
|
|
188
|
+
// @ts-expect-error TS thinks these aren't functions
|
|
189
|
+
await plugin.handleHotUpdate({ file: 'extension.js', server });
|
|
190
|
+
expect(mockWebSocket.broadcast).not.toHaveBeenCalled();
|
|
191
|
+
});
|
|
192
|
+
it('should not trigger a build if it is not a relevant file', async () => {
|
|
193
|
+
const file = 'card config file';
|
|
194
|
+
plugin = devBuildPlugin({
|
|
195
|
+
...options,
|
|
196
|
+
});
|
|
197
|
+
// @ts-expect-error TS thinks these aren't functions
|
|
198
|
+
await plugin.handleHotUpdate({ server, file });
|
|
199
|
+
expect(build).not.toHaveBeenCalled();
|
|
200
|
+
});
|
|
201
|
+
it('should not broadcast if there are no connected clients', async () => {
|
|
202
|
+
// Set clientCount to 0
|
|
203
|
+
mockWebSocket.clientCount = 0;
|
|
204
|
+
// @ts-expect-error TS thinks these aren't functions
|
|
205
|
+
await plugin.handleHotUpdate({
|
|
206
|
+
server,
|
|
207
|
+
file: 'extension.js',
|
|
208
|
+
});
|
|
209
|
+
expect(mockWebSocket.broadcast).not.toHaveBeenCalled();
|
|
210
|
+
expect(logger.debug).toHaveBeenCalledWith('Bundle updated, no browsers connected to notify');
|
|
211
|
+
});
|
|
212
|
+
it('should broadcast update message to connected clients on build success', async () => {
|
|
213
|
+
// @ts-expect-error TS thinks these aren't functions
|
|
214
|
+
await plugin.handleHotUpdate({
|
|
215
|
+
server,
|
|
216
|
+
file: 'extension.js',
|
|
217
|
+
});
|
|
218
|
+
expect(mockWebSocket.broadcast).toHaveBeenCalledTimes(1);
|
|
219
|
+
const baseMessage = options.devServerState.extensionsMetadata[0].baseMessage;
|
|
220
|
+
expect(mockWebSocket.broadcast).toHaveBeenCalledWith({
|
|
221
|
+
event: 'update',
|
|
222
|
+
...baseMessage,
|
|
223
|
+
version: WEBSOCKET_MESSAGE_VERSION,
|
|
224
|
+
});
|
|
225
|
+
expect(logger.debug).toHaveBeenCalledWith('Bundle updated, notifying connected browsers');
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
describe('buildEnd', () => {
|
|
229
|
+
it('should log an error if one is provided', () => {
|
|
230
|
+
const error = 'Error message';
|
|
231
|
+
// @ts-expect-error TS thinks these aren't functions
|
|
232
|
+
plugin.buildEnd(error);
|
|
233
|
+
expect(logger.error).toHaveBeenCalledWith(error);
|
|
234
|
+
});
|
|
235
|
+
it('should broadcast shutdown message when websocket is available', () => {
|
|
236
|
+
// @ts-expect-error TS thinks these aren't functions
|
|
237
|
+
plugin.buildEnd(null);
|
|
238
|
+
const baseMessage = options.devServerState.extensionsMetadata[0].baseMessage;
|
|
239
|
+
expect(mockWebSocket.broadcast).toHaveBeenCalledWith({
|
|
240
|
+
event: 'shutdown',
|
|
241
|
+
...baseMessage,
|
|
242
|
+
version: WEBSOCKET_MESSAGE_VERSION,
|
|
243
|
+
});
|
|
244
|
+
expect(logger.debug).toHaveBeenCalledWith('Sending shutdown message to connected browsers');
|
|
245
|
+
});
|
|
246
|
+
it('should not crash if websocket is not initialized', () => {
|
|
247
|
+
// Remove the websocket
|
|
248
|
+
// @ts-expect-error Setting private property for testing
|
|
249
|
+
options.devServerState._mutableState.extensionsWebSocket = null;
|
|
250
|
+
expect(() => {
|
|
251
|
+
// @ts-expect-error TS thinks these aren't functions
|
|
252
|
+
plugin.buildEnd(null);
|
|
253
|
+
}).not.toThrow();
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
describe('handleBuildError', () => {
|
|
257
|
+
it('should not crash if websocket is not initialized during configureServer', async () => {
|
|
258
|
+
// Remove the websocket to simulate it not being initialized yet
|
|
259
|
+
// @ts-expect-error Setting private property for testing
|
|
260
|
+
options.devServerState._mutableState.extensionsWebSocket = null;
|
|
261
|
+
const error = {
|
|
262
|
+
plugin: 'vite:esbuild',
|
|
263
|
+
errors: ['build error'],
|
|
264
|
+
frame: 'Error on this line',
|
|
265
|
+
loc: { column: 1, line: 5 },
|
|
266
|
+
id: 'src/file.ts',
|
|
267
|
+
};
|
|
268
|
+
vi.mocked(build).mockImplementationOnce(() => {
|
|
269
|
+
throw error;
|
|
270
|
+
});
|
|
271
|
+
// Should not throw even though websocket doesn't exist
|
|
272
|
+
await expect(
|
|
273
|
+
// @ts-expect-error TS thinks this isn't a function
|
|
274
|
+
plugin.configureServer(server)).resolves.not.toThrow();
|
|
275
|
+
// Verify broadcast was NOT called (since websocket doesn't exist)
|
|
276
|
+
expect(mockWebSocket.broadcast).not.toHaveBeenCalled();
|
|
277
|
+
});
|
|
278
|
+
it('should broadcast error when websocket exists and browser connects', async () => {
|
|
279
|
+
// Websocket is already set up in beforeEach
|
|
280
|
+
const error = {
|
|
281
|
+
plugin: 'vite:esbuild',
|
|
282
|
+
errors: ['build error'],
|
|
283
|
+
frame: 'Error on this line',
|
|
284
|
+
loc: { column: 1, line: 5 },
|
|
285
|
+
id: 'src/file.ts',
|
|
286
|
+
};
|
|
287
|
+
vi.mocked(build).mockImplementationOnce(() => {
|
|
288
|
+
throw error;
|
|
289
|
+
});
|
|
290
|
+
// @ts-expect-error TS thinks this isn't a function
|
|
291
|
+
await plugin.configureServer(server);
|
|
292
|
+
options.devServerState.triggerWebSocketSetup();
|
|
293
|
+
// Get the connection handler
|
|
294
|
+
const connectionHandler = mockWebSocket.onConnection.mock.calls[0][0];
|
|
295
|
+
// Simulate browser connection - should broadcast the stored error
|
|
296
|
+
connectionHandler();
|
|
297
|
+
expect(mockWebSocket.broadcast).toHaveBeenCalledWith(expect.objectContaining({
|
|
298
|
+
event: 'error',
|
|
299
|
+
error: expect.objectContaining({
|
|
300
|
+
details: expect.objectContaining({
|
|
301
|
+
file: error.id,
|
|
302
|
+
}),
|
|
303
|
+
}),
|
|
304
|
+
}));
|
|
305
|
+
});
|
|
306
|
+
it('should broadcast error after websocket is initialized and browser connects', async () => {
|
|
307
|
+
// Remove the websocket to simulate it not being initialized yet
|
|
308
|
+
// @ts-expect-error Setting private property for testing
|
|
309
|
+
options.devServerState._mutableState.extensionsWebSocket = null;
|
|
310
|
+
const error = {
|
|
311
|
+
plugin: 'vite:esbuild',
|
|
312
|
+
errors: ['build error'],
|
|
313
|
+
frame: 'Error on this line',
|
|
314
|
+
loc: { column: 1, line: 5 },
|
|
315
|
+
id: 'src/file.ts',
|
|
316
|
+
};
|
|
317
|
+
vi.mocked(build).mockImplementationOnce(() => {
|
|
318
|
+
throw error;
|
|
319
|
+
});
|
|
320
|
+
// configureServer runs with error, but websocket doesn't exist yet
|
|
321
|
+
// @ts-expect-error TS thinks this isn't a function
|
|
322
|
+
await plugin.configureServer(server);
|
|
323
|
+
// At this point, broadcast should not have been called
|
|
324
|
+
expect(mockWebSocket.broadcast).not.toHaveBeenCalled();
|
|
325
|
+
// Now initialize the websocket (simulating what happens in server.ts)
|
|
326
|
+
// @ts-expect-error Setting private property for testing
|
|
327
|
+
options.devServerState._mutableState.extensionsWebSocket =
|
|
328
|
+
mockWebSocket;
|
|
329
|
+
// Trigger the websocket setup callback
|
|
330
|
+
options.devServerState.triggerWebSocketSetup();
|
|
331
|
+
// Get the connection handler
|
|
332
|
+
const connectionHandler = mockWebSocket.onConnection.mock.calls[0][0];
|
|
333
|
+
// Simulate browser connection - should NOW broadcast the stored error
|
|
334
|
+
connectionHandler();
|
|
335
|
+
expect(mockWebSocket.broadcast).toHaveBeenCalledWith(expect.objectContaining({
|
|
336
|
+
event: 'error',
|
|
337
|
+
error: expect.objectContaining({
|
|
338
|
+
details: expect.objectContaining({
|
|
339
|
+
file: error.id,
|
|
340
|
+
}),
|
|
341
|
+
}),
|
|
342
|
+
}));
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import friendlyLoggingPlugin from "../../plugins/friendlyLoggingPlugin.js";
|
|
3
|
+
describe('friendlyLoggingPlugin', () => {
|
|
4
|
+
let plugin;
|
|
5
|
+
let logger;
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
logger = {
|
|
8
|
+
info: vi.fn(),
|
|
9
|
+
error: vi.fn(),
|
|
10
|
+
debug: vi.fn(),
|
|
11
|
+
warn: vi.fn(),
|
|
12
|
+
};
|
|
13
|
+
plugin = friendlyLoggingPlugin({ logger });
|
|
14
|
+
});
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
vi.clearAllMocks();
|
|
17
|
+
});
|
|
18
|
+
describe('metadata', () => {
|
|
19
|
+
it('should create the correct plugin metadata', () => {
|
|
20
|
+
expect(plugin).toStrictEqual(expect.objectContaining({
|
|
21
|
+
name: 'ui-extensions-friendly-logging-plugin',
|
|
22
|
+
enforce: 'post',
|
|
23
|
+
onLog: expect.any(Function),
|
|
24
|
+
}));
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
describe('onLog', () => {
|
|
28
|
+
it('should return true if it is a log message we do not want altered', () => {
|
|
29
|
+
// @ts-expect-error TS doesn't think lifecycle hooks are functions
|
|
30
|
+
const result = plugin.onLog('error', {
|
|
31
|
+
code: "it's an older code but it checks out",
|
|
32
|
+
});
|
|
33
|
+
expect(result).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
it('should return false if it is a log message we want to transform', () => {
|
|
36
|
+
// @ts-expect-error TS doesn't think lifecycle hooks are functions
|
|
37
|
+
const result = plugin.onLog('error', {
|
|
38
|
+
code: 'MISSING_GLOBAL_NAME',
|
|
39
|
+
});
|
|
40
|
+
expect(result).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
it('should not log MISSING_GLOBAL_NAME errors', () => {
|
|
43
|
+
// @ts-expect-error TS doesn't think lifecycle hooks are functions
|
|
44
|
+
const result = plugin.onLog('error', {
|
|
45
|
+
code: 'MISSING_GLOBAL_NAME',
|
|
46
|
+
});
|
|
47
|
+
expect(result).toBe(false);
|
|
48
|
+
expect(logger.error).toHaveBeenCalledTimes(0);
|
|
49
|
+
expect(logger.info).toHaveBeenCalledTimes(0);
|
|
50
|
+
expect(logger.debug).toHaveBeenCalledTimes(0);
|
|
51
|
+
expect(logger.warn).toHaveBeenCalledTimes(0);
|
|
52
|
+
});
|
|
53
|
+
it('should call the logger correctly for UNRESOLVED_IMPORT errors', () => {
|
|
54
|
+
// @ts-expect-error TS doesn't think lifecycle hooks are functions
|
|
55
|
+
const result = plugin.onLog('we ignore this for now', {
|
|
56
|
+
code: 'UNRESOLVED_IMPORT',
|
|
57
|
+
id: 'the/file/that/imported/the/bad/import.jsx',
|
|
58
|
+
exporter: '@hubspot/extensions',
|
|
59
|
+
});
|
|
60
|
+
expect(result).toBe(false);
|
|
61
|
+
expect(logger.error).toHaveBeenCalledTimes(1);
|
|
62
|
+
expect(logger.error).toHaveBeenCalledWith('@hubspot/extensions is imported by import.jsx, but @hubspot/extensions cannot be resolved. Make sure @hubspot/extensions is installed by running `hs project install-deps`.');
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|