cdp-skill 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.
@@ -0,0 +1,274 @@
1
+ import { describe, it, mock, beforeEach, afterEach } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { createTargetManager } from '../cdp.js';
4
+
5
+ /**
6
+ * Mock CDPConnection for testing
7
+ */
8
+ function createMockConnection() {
9
+ const eventListeners = new Map();
10
+
11
+ return {
12
+ eventListeners,
13
+ sentCommands: [],
14
+
15
+ on(event, callback) {
16
+ if (!eventListeners.has(event)) {
17
+ eventListeners.set(event, new Set());
18
+ }
19
+ eventListeners.get(event).add(callback);
20
+ },
21
+
22
+ off(event, callback) {
23
+ const listeners = eventListeners.get(event);
24
+ if (listeners) {
25
+ listeners.delete(callback);
26
+ }
27
+ },
28
+
29
+ async send(method, params = {}) {
30
+ this.sentCommands.push({ method, params });
31
+
32
+ // Return mock responses based on method
33
+ switch (method) {
34
+ case 'Target.setDiscoverTargets':
35
+ return {};
36
+ case 'Target.getTargets':
37
+ return {
38
+ targetInfos: [
39
+ { targetId: 'target1', type: 'page', title: 'Test Page', url: 'https://example.com', attached: false },
40
+ { targetId: 'target2', type: 'service_worker', title: 'SW', url: 'https://example.com/sw.js', attached: false }
41
+ ]
42
+ };
43
+ case 'Target.createTarget':
44
+ return { targetId: 'new-target-id' };
45
+ case 'Target.closeTarget':
46
+ return { success: true };
47
+ case 'Target.activateTarget':
48
+ return {};
49
+ case 'Target.getTargetInfo':
50
+ return {
51
+ targetInfo: { targetId: params.targetId, type: 'page', title: 'Test', url: 'https://test.com', attached: true }
52
+ };
53
+ default:
54
+ throw new Error(`Unknown method: ${method}`);
55
+ }
56
+ },
57
+
58
+ // Helper to emit events for testing
59
+ emit(event, params) {
60
+ const listeners = eventListeners.get(event);
61
+ if (listeners) {
62
+ for (const callback of listeners) {
63
+ callback(params);
64
+ }
65
+ }
66
+ }
67
+ };
68
+ }
69
+
70
+ describe('TargetManager', () => {
71
+ let mockConnection;
72
+ let targetManager;
73
+
74
+ beforeEach(() => {
75
+ mockConnection = createMockConnection();
76
+ targetManager = createTargetManager(mockConnection);
77
+ });
78
+
79
+ describe('constructor', () => {
80
+ it('should create instance with connection', () => {
81
+ assert.ok(targetManager);
82
+ });
83
+ });
84
+
85
+ describe('enableDiscovery', () => {
86
+ it('should enable target discovery', async () => {
87
+ await targetManager.enableDiscovery();
88
+
89
+ const cmd = mockConnection.sentCommands.find(c => c.method === 'Target.setDiscoverTargets');
90
+ assert.ok(cmd);
91
+ assert.strictEqual(cmd.params.discover, true);
92
+ });
93
+
94
+ it('should register event listeners', async () => {
95
+ await targetManager.enableDiscovery();
96
+
97
+ assert.ok(mockConnection.eventListeners.has('Target.targetCreated'));
98
+ assert.ok(mockConnection.eventListeners.has('Target.targetDestroyed'));
99
+ assert.ok(mockConnection.eventListeners.has('Target.targetInfoChanged'));
100
+ });
101
+
102
+ it('should not enable twice', async () => {
103
+ await targetManager.enableDiscovery();
104
+ await targetManager.enableDiscovery();
105
+
106
+ const cmds = mockConnection.sentCommands.filter(c => c.method === 'Target.setDiscoverTargets');
107
+ assert.strictEqual(cmds.length, 1);
108
+ });
109
+ });
110
+
111
+ describe('disableDiscovery', () => {
112
+ it('should disable target discovery', async () => {
113
+ await targetManager.enableDiscovery();
114
+ await targetManager.disableDiscovery();
115
+
116
+ const cmds = mockConnection.sentCommands.filter(c => c.method === 'Target.setDiscoverTargets');
117
+ assert.strictEqual(cmds.length, 2);
118
+ assert.strictEqual(cmds[1].params.discover, false);
119
+ });
120
+
121
+ it('should remove event listeners', async () => {
122
+ await targetManager.enableDiscovery();
123
+ await targetManager.disableDiscovery();
124
+
125
+ // Listeners should be removed (sets should be empty)
126
+ const createdListeners = mockConnection.eventListeners.get('Target.targetCreated');
127
+ assert.strictEqual(createdListeners?.size || 0, 0);
128
+ });
129
+
130
+ it('should do nothing if not enabled', async () => {
131
+ await targetManager.disableDiscovery();
132
+
133
+ const cmds = mockConnection.sentCommands.filter(c => c.method === 'Target.setDiscoverTargets');
134
+ assert.strictEqual(cmds.length, 0);
135
+ });
136
+ });
137
+
138
+ describe('getTargets', () => {
139
+ it('should return all targets', async () => {
140
+ const targets = await targetManager.getTargets();
141
+
142
+ assert.strictEqual(targets.length, 2);
143
+ assert.strictEqual(targets[0].targetId, 'target1');
144
+ });
145
+
146
+ it('should update internal cache', async () => {
147
+ await targetManager.getTargets();
148
+
149
+ const cached = targetManager.getCachedTarget('target1');
150
+ assert.ok(cached);
151
+ assert.strictEqual(cached.type, 'page');
152
+ });
153
+ });
154
+
155
+ describe('getPages', () => {
156
+ it('should return only page targets', async () => {
157
+ const pages = await targetManager.getPages();
158
+
159
+ assert.strictEqual(pages.length, 1);
160
+ assert.strictEqual(pages[0].type, 'page');
161
+ });
162
+ });
163
+
164
+ describe('createTarget', () => {
165
+ it('should create new target with default URL', async () => {
166
+ const targetId = await targetManager.createTarget();
167
+
168
+ assert.strictEqual(targetId, 'new-target-id');
169
+
170
+ const cmd = mockConnection.sentCommands.find(c => c.method === 'Target.createTarget');
171
+ assert.strictEqual(cmd.params.url, 'about:blank');
172
+ });
173
+
174
+ it('should create target with custom URL and options', async () => {
175
+ await targetManager.createTarget('https://example.com', {
176
+ width: 1920,
177
+ height: 1080,
178
+ background: true,
179
+ newWindow: true
180
+ });
181
+
182
+ const cmd = mockConnection.sentCommands.find(c => c.method === 'Target.createTarget');
183
+ assert.strictEqual(cmd.params.url, 'https://example.com');
184
+ assert.strictEqual(cmd.params.width, 1920);
185
+ assert.strictEqual(cmd.params.height, 1080);
186
+ assert.strictEqual(cmd.params.background, true);
187
+ assert.strictEqual(cmd.params.newWindow, true);
188
+ });
189
+ });
190
+
191
+ describe('closeTarget', () => {
192
+ it('should close target and remove from cache', async () => {
193
+ await targetManager.getTargets(); // Populate cache
194
+ const result = await targetManager.closeTarget('target1');
195
+
196
+ assert.strictEqual(result, true);
197
+ assert.strictEqual(targetManager.getCachedTarget('target1'), undefined);
198
+ });
199
+ });
200
+
201
+ describe('activateTarget', () => {
202
+ it('should send activate command', async () => {
203
+ await targetManager.activateTarget('target1');
204
+
205
+ const cmd = mockConnection.sentCommands.find(c => c.method === 'Target.activateTarget');
206
+ assert.strictEqual(cmd.params.targetId, 'target1');
207
+ });
208
+ });
209
+
210
+ describe('getTargetInfo', () => {
211
+ it('should get target info and update cache', async () => {
212
+ const info = await targetManager.getTargetInfo('target1');
213
+
214
+ assert.strictEqual(info.targetId, 'target1');
215
+ assert.strictEqual(targetManager.getCachedTarget('target1').type, 'page');
216
+ });
217
+ });
218
+
219
+ describe('getCachedTargets', () => {
220
+ it('should return copy of cached targets', async () => {
221
+ await targetManager.getTargets();
222
+
223
+ const cached = targetManager.getCachedTargets();
224
+ assert.ok(cached instanceof Map);
225
+ assert.strictEqual(cached.size, 2);
226
+ });
227
+ });
228
+
229
+ describe('event handling', () => {
230
+ it('should handle targetCreated event', async () => {
231
+ await targetManager.enableDiscovery();
232
+
233
+ mockConnection.emit('Target.targetCreated', {
234
+ targetInfo: { targetId: 'new-target', type: 'page', title: 'New', url: 'https://new.com' }
235
+ });
236
+
237
+ const cached = targetManager.getCachedTarget('new-target');
238
+ assert.ok(cached);
239
+ assert.strictEqual(cached.title, 'New');
240
+ });
241
+
242
+ it('should handle targetDestroyed event', async () => {
243
+ await targetManager.enableDiscovery();
244
+ await targetManager.getTargets(); // Populate cache
245
+
246
+ mockConnection.emit('Target.targetDestroyed', { targetId: 'target1' });
247
+
248
+ assert.strictEqual(targetManager.getCachedTarget('target1'), undefined);
249
+ });
250
+
251
+ it('should handle targetInfoChanged event', async () => {
252
+ await targetManager.enableDiscovery();
253
+ await targetManager.getTargets(); // Populate cache
254
+
255
+ mockConnection.emit('Target.targetInfoChanged', {
256
+ targetInfo: { targetId: 'target1', type: 'page', title: 'Updated Title', url: 'https://updated.com' }
257
+ });
258
+
259
+ const cached = targetManager.getCachedTarget('target1');
260
+ assert.strictEqual(cached.title, 'Updated Title');
261
+ });
262
+ });
263
+
264
+ describe('cleanup', () => {
265
+ it('should disable discovery and clear cache', async () => {
266
+ await targetManager.enableDiscovery();
267
+ await targetManager.getTargets();
268
+
269
+ await targetManager.cleanup();
270
+
271
+ assert.strictEqual(targetManager.getCachedTargets().size, 0);
272
+ });
273
+ });
274
+ });