gogcli-mcp-slides 2.7.1 → 2.18.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.
@@ -1,7 +1,8 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { registerExtraSlidesTools } from '../../src/tools/slides-extra.js';
3
3
  import * as lib from '../../../gogcli-mcp/src/lib.js';
4
- import { setupHandlers, toText, type ToolHandler } from '../../../gogcli-mcp/tests/helpers/test-harness.js';
4
+ import { createTestHarness, type TestHarness } from '@chrischall/mcp-utils/test';
5
+ import { rawTextResult } from '@chrischall/mcp-utils';
5
6
 
6
7
  vi.mock('../../../gogcli-mcp/src/lib.js', async (importOriginal) => {
7
8
  const actual = await importOriginal<typeof lib>();
@@ -11,17 +12,17 @@ vi.mock('../../../gogcli-mcp/src/lib.js', async (importOriginal) => {
11
12
  };
12
13
  });
13
14
 
14
- let handlers: Map<string, ToolHandler>;
15
+ let harness: TestHarness;
15
16
 
16
- beforeEach(() => {
17
+ beforeEach(async () => {
17
18
  vi.clearAllMocks();
18
- vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
19
- handlers = setupHandlers(registerExtraSlidesTools);
19
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(rawTextResult('{}'));
20
+ harness = await createTestHarness(registerExtraSlidesTools);
20
21
  });
21
22
 
22
23
  describe('gog_slides_create_from_markdown', () => {
23
24
  it('calls runOrDiagnose with title only', async () => {
24
- await handlers.get('gog_slides_create_from_markdown')!({ title: 'Deck' });
25
+ await harness.callTool('gog_slides_create_from_markdown', { title: 'Deck' });
25
26
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(
26
27
  ['slides', 'create-from-markdown', 'Deck'],
27
28
  { account: undefined },
@@ -29,7 +30,7 @@ describe('gog_slides_create_from_markdown', () => {
29
30
  });
30
31
 
31
32
  it('passes all optional flags', async () => {
32
- await handlers.get('gog_slides_create_from_markdown')!({
33
+ await harness.callTool('gog_slides_create_from_markdown', {
33
34
  title: 'Deck',
34
35
  content: '# Slide 1',
35
36
  contentFile: '/tmp/deck.md',
@@ -49,7 +50,7 @@ describe('gog_slides_create_from_markdown', () => {
49
50
  });
50
51
 
51
52
  it('omits --debug when false', async () => {
52
- await handlers.get('gog_slides_create_from_markdown')!({ title: 'Deck', debug: false });
53
+ await harness.callTool('gog_slides_create_from_markdown', { title: 'Deck', debug: false });
53
54
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(
54
55
  ['slides', 'create-from-markdown', 'Deck'],
55
56
  { account: undefined },
@@ -59,7 +60,7 @@ describe('gog_slides_create_from_markdown', () => {
59
60
 
60
61
  describe('gog_slides_create_from_template', () => {
61
62
  it('calls runOrDiagnose with templateId and title only', async () => {
62
- await handlers.get('gog_slides_create_from_template')!({ templateId: 'tpl1', title: 'Deck' });
63
+ await harness.callTool('gog_slides_create_from_template', { templateId: 'tpl1', title: 'Deck' });
63
64
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(
64
65
  ['slides', 'create-from-template', 'tpl1', 'Deck'],
65
66
  { account: undefined },
@@ -67,7 +68,7 @@ describe('gog_slides_create_from_template', () => {
67
68
  });
68
69
 
69
70
  it('passes --replace for a single replacement entry', async () => {
70
- await handlers.get('gog_slides_create_from_template')!({
71
+ await harness.callTool('gog_slides_create_from_template', {
71
72
  templateId: 'tpl1',
72
73
  title: 'Deck',
73
74
  replacements: { name: 'Alice' },
@@ -79,7 +80,7 @@ describe('gog_slides_create_from_template', () => {
79
80
  });
80
81
 
81
82
  it('passes --replace for each entry in replacements', async () => {
82
- await handlers.get('gog_slides_create_from_template')!({
83
+ await harness.callTool('gog_slides_create_from_template', {
83
84
  templateId: 'tpl1',
84
85
  title: 'Deck',
85
86
  replacements: { name: 'Alice', company: 'Acme' },
@@ -90,7 +91,7 @@ describe('gog_slides_create_from_template', () => {
90
91
  });
91
92
 
92
93
  it('passes --replacements, --parent, and --exact', async () => {
93
- await handlers.get('gog_slides_create_from_template')!({
94
+ await harness.callTool('gog_slides_create_from_template', {
94
95
  templateId: 'tpl1',
95
96
  title: 'Deck',
96
97
  replacementsFile: '/tmp/r.json',
@@ -109,7 +110,7 @@ describe('gog_slides_create_from_template', () => {
109
110
  });
110
111
 
111
112
  it('omits --exact when false', async () => {
112
- await handlers.get('gog_slides_create_from_template')!({
113
+ await harness.callTool('gog_slides_create_from_template', {
113
114
  templateId: 'tpl1',
114
115
  title: 'Deck',
115
116
  exact: false,
@@ -121,9 +122,77 @@ describe('gog_slides_create_from_template', () => {
121
122
  });
122
123
  });
123
124
 
125
+ // Slides is the asymmetric case: gog accepts both the inline and the *-file
126
+ // flag and lets the FILE win, so these tools keep passing both through rather
127
+ // than rejecting like gog_docs_cell_update / the gmail body pairs do.
128
+ describe('slides payload routing', () => {
129
+ const big = 'x'.repeat(lib.PAYLOAD_INLINE_MAX + 1);
130
+
131
+ it('routes a large create-from-markdown content to --content-file', async () => {
132
+ await harness.callTool('gog_slides_create_from_markdown', { title: 'Deck', content: big });
133
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
134
+ ['slides', 'create-from-markdown', 'Deck',
135
+ { kind: 'file', flag: 'content-file', contents: big, ext: 'md' }],
136
+ { account: undefined },
137
+ );
138
+ });
139
+
140
+ it('routes large add-slide notes to --notes-file', async () => {
141
+ await harness.callTool('gog_slides_add_slide', { presentationId: 'p1', image: '/tmp/i.png', notes: big });
142
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
143
+ ['slides', 'add-slide', 'p1', '/tmp/i.png',
144
+ { kind: 'file', flag: 'notes-file', contents: big, ext: undefined }],
145
+ { account: undefined },
146
+ );
147
+ });
148
+
149
+ it('routes large update-notes notes to --notes-file', async () => {
150
+ await harness.callTool('gog_slides_update_notes', { presentationId: 'p1', slideId: 's1', notes: big });
151
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
152
+ ['slides', 'update-notes', 'p1', 's1',
153
+ { kind: 'file', flag: 'notes-file', contents: big, ext: undefined }],
154
+ { account: undefined },
155
+ );
156
+ });
157
+
158
+ it('routes large replace-slide notes to --notes-file', async () => {
159
+ await harness.callTool('gog_slides_replace_slide', {
160
+ presentationId: 'p1', slideId: 's1', image: '/tmp/i.png', notes: big,
161
+ });
162
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
163
+ ['slides', 'replace-slide', 'p1', 's1', '/tmp/i.png',
164
+ { kind: 'file', flag: 'notes-file', contents: big, ext: undefined }],
165
+ { account: undefined },
166
+ );
167
+ });
168
+
169
+ // A caller-supplied notesFile is pushed last, so it still wins over a
170
+ // payloadArg-generated file — matching gog's own "last flag wins" behavior.
171
+ it('keeps a caller-supplied notesFile last so it still wins over a spilled payload', async () => {
172
+ await harness.callTool('gog_slides_update_notes', {
173
+ presentationId: 'p1', slideId: 's1', notes: big, notesFile: '/tmp/mine.txt',
174
+ });
175
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
176
+ ['slides', 'update-notes', 'p1', 's1',
177
+ { kind: 'file', flag: 'notes-file', contents: big, ext: undefined },
178
+ '--notes-file=/tmp/mine.txt'],
179
+ { account: undefined },
180
+ );
181
+ });
182
+
183
+ // Trailing newlines survive the file round-trip for slides notes (gog 0.34.1),
184
+ // unlike gmail bodies where they are stripped. Documented as a property.
185
+ it('preserves trailing newlines in a spilled notes payload', async () => {
186
+ const padded = `${big}\n\n`;
187
+ await harness.callTool('gog_slides_update_notes', { presentationId: 'p1', slideId: 's1', notes: padded });
188
+ const [args] = vi.mocked(lib.runOrDiagnose).mock.calls[0];
189
+ expect(args[4]).toEqual({ kind: 'file', flag: 'notes-file', contents: padded, ext: undefined });
190
+ });
191
+ });
192
+
124
193
  describe('gog_slides_add_slide', () => {
125
194
  it('calls runOrDiagnose with presentationId and image', async () => {
126
- await handlers.get('gog_slides_add_slide')!({ presentationId: 'p1', image: '/tmp/img.png' });
195
+ await harness.callTool('gog_slides_add_slide', { presentationId: 'p1', image: '/tmp/img.png' });
127
196
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(
128
197
  ['slides', 'add-slide', 'p1', '/tmp/img.png'],
129
198
  { account: undefined },
@@ -131,7 +200,7 @@ describe('gog_slides_add_slide', () => {
131
200
  });
132
201
 
133
202
  it('passes --notes, --notes-file, and --before', async () => {
134
- await handlers.get('gog_slides_add_slide')!({
203
+ await harness.callTool('gog_slides_add_slide', {
135
204
  presentationId: 'p1',
136
205
  image: '/tmp/img.png',
137
206
  notes: 'Speaker note',
@@ -152,9 +221,9 @@ describe('gog_slides_add_slide', () => {
152
221
 
153
222
  describe('gog_slides_delete_slide', () => {
154
223
  it('calls runOrDiagnose with presentationId and slideId', async () => {
155
- await handlers.get('gog_slides_delete_slide')!({ presentationId: 'p1', slideId: 's1' });
224
+ await harness.callTool('gog_slides_delete_slide', { presentationId: 'p1', slideId: 's1' });
156
225
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(
157
- ['slides', 'delete-slide', 'p1', 's1'],
226
+ ['slides', 'delete-slide', 'p1', 's1', '--force'],
158
227
  { account: undefined },
159
228
  );
160
229
  });
@@ -162,7 +231,7 @@ describe('gog_slides_delete_slide', () => {
162
231
 
163
232
  describe('gog_slides_update_notes', () => {
164
233
  it('calls runOrDiagnose with presentationId and slideId', async () => {
165
- await handlers.get('gog_slides_update_notes')!({ presentationId: 'p1', slideId: 's1' });
234
+ await harness.callTool('gog_slides_update_notes', { presentationId: 'p1', slideId: 's1' });
166
235
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(
167
236
  ['slides', 'update-notes', 'p1', 's1'],
168
237
  { account: undefined },
@@ -170,7 +239,7 @@ describe('gog_slides_update_notes', () => {
170
239
  });
171
240
 
172
241
  it('passes --notes and --notes-file when provided', async () => {
173
- await handlers.get('gog_slides_update_notes')!({
242
+ await harness.callTool('gog_slides_update_notes', {
174
243
  presentationId: 'p1',
175
244
  slideId: 's1',
176
245
  notes: 'speak clearly',
@@ -189,7 +258,7 @@ describe('gog_slides_update_notes', () => {
189
258
 
190
259
  describe('gog_slides_replace_slide', () => {
191
260
  it('calls runOrDiagnose with presentationId, slideId, and image', async () => {
192
- await handlers.get('gog_slides_replace_slide')!({
261
+ await harness.callTool('gog_slides_replace_slide', {
193
262
  presentationId: 'p1',
194
263
  slideId: 's1',
195
264
  image: '/tmp/img.png',
@@ -201,7 +270,7 @@ describe('gog_slides_replace_slide', () => {
201
270
  });
202
271
 
203
272
  it('passes --notes and --notes-file when provided', async () => {
204
- await handlers.get('gog_slides_replace_slide')!({
273
+ await harness.callTool('gog_slides_replace_slide', {
205
274
  presentationId: 'p1',
206
275
  slideId: 's1',
207
276
  image: '/tmp/img.png',
@@ -222,7 +291,7 @@ describe('gog_slides_replace_slide', () => {
222
291
  // gog 0.23.0
223
292
  describe('gog_slides_insert_image', () => {
224
293
  it('inserts an image with the required width', async () => {
225
- await handlers.get('gog_slides_insert_image')!({
294
+ await harness.callTool('gog_slides_insert_image', {
226
295
  presentationId: 'p1', slideId: 's1', image: '/tmp/i.png', width: 200,
227
296
  });
228
297
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(
@@ -232,7 +301,7 @@ describe('gog_slides_insert_image', () => {
232
301
  });
233
302
 
234
303
  it('passes height, position and unit', async () => {
235
- await handlers.get('gog_slides_insert_image')!({
304
+ await harness.callTool('gog_slides_insert_image', {
236
305
  presentationId: 'p1', slideId: 's1', image: '/tmp/i.png', width: 200, height: 100, x: 50, y: 60, unit: 'PT', account: 'a@b.com',
237
306
  });
238
307
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(
@@ -241,3 +310,452 @@ describe('gog_slides_insert_image', () => {
241
310
  );
242
311
  });
243
312
  });
313
+
314
+ // ===========================================================================
315
+ // gog 0.29 native authoring (PR3a): reads, slide mgmt, text, shapes/lines
316
+ // ===========================================================================
317
+
318
+ describe('gog_slides_insert_image url mode', () => {
319
+ it('inserts from a public URL with width and height', async () => {
320
+ await harness.callTool('gog_slides_insert_image', {
321
+ presentationId: 'p1', slideId: 's1', url: 'https://x.test/i.png', width: 200, height: 100,
322
+ });
323
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
324
+ ['slides', 'insert-image', 'p1', 's1', '--width=200', '--url=https://x.test/i.png', '--height=100'],
325
+ { account: undefined },
326
+ );
327
+ });
328
+ });
329
+
330
+ describe('gog_slides_replace_slide url mode', () => {
331
+ it('replaces from a public URL', async () => {
332
+ await harness.callTool('gog_slides_replace_slide', {
333
+ presentationId: 'p1', slideId: 's1', url: 'https://x.test/i.png', notes: 'n',
334
+ });
335
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
336
+ ['slides', 'replace-slide', 'p1', 's1', '--url=https://x.test/i.png', '--notes=n'],
337
+ { account: undefined },
338
+ );
339
+ });
340
+ });
341
+
342
+ describe('gog_slides_raw', () => {
343
+ it('passes --pretty', async () => {
344
+ await harness.callTool('gog_slides_raw', { presentationId: 'p1', pretty: true });
345
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['slides', 'raw', 'p1', '--pretty'], { account: undefined });
346
+ });
347
+ it('bare', async () => {
348
+ await harness.callTool('gog_slides_raw', { presentationId: 'p1' });
349
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['slides', 'raw', 'p1'], { account: undefined });
350
+ });
351
+ });
352
+
353
+ describe('gog_slides_locate', () => {
354
+ it('passes all filters', async () => {
355
+ await harness.callTool('gog_slides_locate', {
356
+ presentationId: 'p1', text: 'hi', page: 'sl1', occurrence: 2, all: true, matchCase: true, failEmpty: true,
357
+ });
358
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
359
+ ['slides', 'locate', 'p1', 'hi', '--page=sl1', '--occurrence=2', '--all', '--match-case', '--fail-empty'],
360
+ { account: undefined },
361
+ );
362
+ });
363
+ it('bare', async () => {
364
+ await harness.callTool('gog_slides_locate', { presentationId: 'p1', text: 'hi' });
365
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['slides', 'locate', 'p1', 'hi'], { account: undefined });
366
+ });
367
+ });
368
+
369
+ describe('gog_slides_thumbnail', () => {
370
+ it('passes format, size, out and overwrite', async () => {
371
+ await harness.callTool('gog_slides_thumbnail', {
372
+ presentationId: 'p1', slideId: 's1', format: 'png', size: 'large', out: '/tmp/t.png', overwrite: true,
373
+ });
374
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
375
+ ['slides', 'thumbnail', 'p1', 's1', '--format=png', '--size=large', '--out=/tmp/t.png', '--overwrite'],
376
+ { account: undefined },
377
+ );
378
+ });
379
+ it('bare', async () => {
380
+ await harness.callTool('gog_slides_thumbnail', { presentationId: 'p1', slideId: 's1' });
381
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['slides', 'thumbnail', 'p1', 's1'], { account: undefined });
382
+ });
383
+ });
384
+
385
+ describe('gog_slides_new_slide', () => {
386
+ it('passes layout and index', async () => {
387
+ await harness.callTool('gog_slides_new_slide', { presentationId: 'p1', layout: 'TITLE_AND_BODY', index: 2 });
388
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
389
+ ['slides', 'new-slide', 'p1', '--layout=TITLE_AND_BODY', '--index=2'],
390
+ { account: undefined },
391
+ );
392
+ });
393
+ it('passes layoutId', async () => {
394
+ await harness.callTool('gog_slides_new_slide', { presentationId: 'p1', layoutId: 'LAY1' });
395
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['slides', 'new-slide', 'p1', '--layout-id=LAY1'], { account: undefined });
396
+ });
397
+ it('bare', async () => {
398
+ await harness.callTool('gog_slides_new_slide', { presentationId: 'p1' });
399
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['slides', 'new-slide', 'p1'], { account: undefined });
400
+ });
401
+ });
402
+
403
+ describe('gog_slides_duplicate_slide', () => {
404
+ it('passes --to-index', async () => {
405
+ await harness.callTool('gog_slides_duplicate_slide', { presentationId: 'p1', slideId: 's1', toIndex: 3 });
406
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
407
+ ['slides', 'duplicate-slide', 'p1', 's1', '--to-index=3'], { account: undefined });
408
+ });
409
+ it('bare', async () => {
410
+ await harness.callTool('gog_slides_duplicate_slide', { presentationId: 'p1', slideId: 's1' });
411
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['slides', 'duplicate-slide', 'p1', 's1'], { account: undefined });
412
+ });
413
+ });
414
+
415
+ describe('gog_slides_move_slide', () => {
416
+ it('passes required --to-index', async () => {
417
+ await harness.callTool('gog_slides_move_slide', { presentationId: 'p1', slideId: 's1', toIndex: 0 });
418
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
419
+ ['slides', 'move-slide', 'p1', 's1', '--to-index=0'], { account: undefined });
420
+ });
421
+ });
422
+
423
+ describe('gog_slides_insert_text', () => {
424
+ it('passes cell targeting, insertion index and replace', async () => {
425
+ await harness.callTool('gog_slides_insert_text', {
426
+ presentationId: 'p1', objectId: 'o1', text: 'hi', row: 0, col: 1, insertionIndex: 2, replace: true,
427
+ });
428
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
429
+ ['slides', 'insert-text', 'p1', 'o1', 'hi', '--row=0', '--col=1', '--insertion-index=2', '--replace'],
430
+ { account: undefined },
431
+ );
432
+ });
433
+ it('bare', async () => {
434
+ await harness.callTool('gog_slides_insert_text', { presentationId: 'p1', objectId: 'o1', text: 'hi' });
435
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['slides', 'insert-text', 'p1', 'o1', 'hi'], { account: undefined });
436
+ });
437
+ });
438
+
439
+ describe('gog_slides_style_text', () => {
440
+ it('passes every styling flag', async () => {
441
+ await harness.callTool('gog_slides_style_text', {
442
+ presentationId: 'p1', objectId: 'o1', range: '0:5', font: 'Arial', size: 18, textColor: '#ff0000',
443
+ bold: true, noBold: true, italic: true, noItalic: true, underline: true, noUnderline: true,
444
+ });
445
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
446
+ ['slides', 'style-text', 'p1', 'o1', '--range=0:5', '--font=Arial', '--size=18', '--text-color=#ff0000',
447
+ '--bold', '--no-bold', '--italic', '--no-italic', '--underline', '--no-underline'],
448
+ { account: undefined },
449
+ );
450
+ });
451
+ it('minimal (range only)', async () => {
452
+ await harness.callTool('gog_slides_style_text', { presentationId: 'p1', objectId: 'o1', range: '0:5' });
453
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['slides', 'style-text', 'p1', 'o1', '--range=0:5'], { account: undefined });
454
+ });
455
+ });
456
+
457
+ describe('gog_slides_link', () => {
458
+ it('applies a url', async () => {
459
+ await harness.callTool('gog_slides_link', { presentationId: 'p1', objectId: 'o1', range: '0:5', url: 'https://x.test' });
460
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
461
+ ['slides', 'link', 'p1', 'o1', '--range=0:5', '--url=https://x.test'], { account: undefined });
462
+ });
463
+ it('clears the link', async () => {
464
+ await harness.callTool('gog_slides_link', { presentationId: 'p1', objectId: 'o1', range: '0:5', clear: true });
465
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
466
+ ['slides', 'link', 'p1', 'o1', '--range=0:5', '--clear'], { account: undefined });
467
+ });
468
+ });
469
+
470
+ describe('gog_slides_bullets', () => {
471
+ it('turns bullets on with a preset', async () => {
472
+ await harness.callTool('gog_slides_bullets', { presentationId: 'p1', objectId: 'o1', range: '0:5', on: true, preset: 'BULLET_DISC_CIRCLE_SQUARE' });
473
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
474
+ ['slides', 'bullets', 'p1', 'o1', '--range=0:5', '--on', '--preset=BULLET_DISC_CIRCLE_SQUARE'], { account: undefined });
475
+ });
476
+ it('turns bullets off', async () => {
477
+ await harness.callTool('gog_slides_bullets', { presentationId: 'p1', objectId: 'o1', range: '0:5', off: true });
478
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
479
+ ['slides', 'bullets', 'p1', 'o1', '--range=0:5', '--off'], { account: undefined });
480
+ });
481
+ });
482
+
483
+ describe('gog_slides_replace_text', () => {
484
+ it('scopes to a single object', async () => {
485
+ await harness.callTool('gog_slides_replace_text', { presentationId: 'p1', find: 'a', replacement: 'b', object: 'o1', matchCase: true });
486
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
487
+ ['slides', 'replace-text', 'p1', 'a', 'b', '--object=o1', '--match-case'], { account: undefined });
488
+ });
489
+ it('scopes to specific pages (repeatable)', async () => {
490
+ await harness.callTool('gog_slides_replace_text', { presentationId: 'p1', find: 'a', replacement: 'b', pages: ['s1', 's2'] });
491
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
492
+ ['slides', 'replace-text', 'p1', 'a', 'b', '--page=s1', '--page=s2'], { account: undefined });
493
+ });
494
+ it('scopes to the whole presentation', async () => {
495
+ await harness.callTool('gog_slides_replace_text', { presentationId: 'p1', find: 'a', replacement: 'b', all: true });
496
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
497
+ ['slides', 'replace-text', 'p1', 'a', 'b', '--all'], { account: undefined });
498
+ });
499
+ });
500
+
501
+ describe('gog_slides_element_create_shape', () => {
502
+ it('passes type, geometry and object id', async () => {
503
+ await harness.callTool('gog_slides_element_create_shape', {
504
+ presentationId: 'p1', slideId: 's1', type: 'RECTANGLE', x: 10, y: 20, width: 100, height: 50, unit: 'PT', objectId: 'shape1',
505
+ });
506
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
507
+ ['slides', 'element', 'create-shape', 'p1', 's1', '--type=RECTANGLE', '--x=10', '--y=20', '--width=100', '--height=50', '--unit=PT', '--object-id=shape1'],
508
+ { account: undefined },
509
+ );
510
+ });
511
+ it('bare', async () => {
512
+ await harness.callTool('gog_slides_element_create_shape', { presentationId: 'p1', slideId: 's1' });
513
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['slides', 'element', 'create-shape', 'p1', 's1'], { account: undefined });
514
+ });
515
+ });
516
+
517
+ describe('gog_slides_element_create_line', () => {
518
+ it('passes category, geometry and object id', async () => {
519
+ await harness.callTool('gog_slides_element_create_line', {
520
+ presentationId: 'p1', slideId: 's1', category: 'BENT', x: 10, y: 20, width: 100, height: 50, unit: 'EMU', objectId: 'line1',
521
+ });
522
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
523
+ ['slides', 'element', 'create-line', 'p1', 's1', '--category=BENT', '--x=10', '--y=20', '--width=100', '--height=50', '--unit=EMU', '--object-id=line1'],
524
+ { account: undefined },
525
+ );
526
+ });
527
+ it('bare', async () => {
528
+ await harness.callTool('gog_slides_element_create_line', { presentationId: 'p1', slideId: 's1' });
529
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['slides', 'element', 'create-line', 'p1', 's1'], { account: undefined });
530
+ });
531
+ });
532
+
533
+ describe('gog_slides_element_style', () => {
534
+ it('passes fill, outline and dash flags', async () => {
535
+ await harness.callTool('gog_slides_element_style', {
536
+ presentationId: 'p1', objectId: 'o1', kind: 'shape', fillColor: '#fff', fillTransparent: true,
537
+ outlineColor: '#000', outlineWeight: 2, outlineDash: 'DASH', outlineTransparent: true,
538
+ });
539
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
540
+ ['slides', 'element', 'style', 'p1', 'o1', '--kind=shape', '--fill-color=#fff', '--fill-transparent',
541
+ '--outline-color=#000', '--outline-weight=2', '--outline-dash=DASH', '--outline-transparent'],
542
+ { account: undefined },
543
+ );
544
+ });
545
+ it('bare', async () => {
546
+ await harness.callTool('gog_slides_element_style', { presentationId: 'p1', objectId: 'o1' });
547
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['slides', 'element', 'style', 'p1', 'o1'], { account: undefined });
548
+ });
549
+ });
550
+
551
+ describe('gog_slides_element_transform', () => {
552
+ it('passes translate, scale, shear, rotate, apply-mode and unit', async () => {
553
+ await harness.callTool('gog_slides_element_transform', {
554
+ presentationId: 'p1', objectId: 'o1', translateX: 10, translateY: 20, scaleX: 1.5, scaleY: 2,
555
+ shearX: 0.1, shearY: 0.2, rotate: 45, applyMode: 'ABSOLUTE', unit: 'PT',
556
+ });
557
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
558
+ ['slides', 'element', 'transform', 'p1', 'o1', '--translate-x=10', '--translate-y=20', '--scale-x=1.5', '--scale-y=2',
559
+ '--shear-x=0.1', '--shear-y=0.2', '--rotate=45', '--apply-mode=ABSOLUTE', '--unit=PT'],
560
+ { account: undefined },
561
+ );
562
+ });
563
+ it('bare', async () => {
564
+ await harness.callTool('gog_slides_element_transform', { presentationId: 'p1', objectId: 'o1' });
565
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['slides', 'element', 'transform', 'p1', 'o1'], { account: undefined });
566
+ });
567
+ });
568
+
569
+ describe('gog_slides_element_z_order', () => {
570
+ it('passes the operation', async () => {
571
+ await harness.callTool('gog_slides_element_z_order', { presentationId: 'p1', objectId: 'o1', operation: 'BRING_TO_FRONT' });
572
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
573
+ ['slides', 'element', 'z-order', 'p1', 'o1', '--operation=BRING_TO_FRONT'], { account: undefined });
574
+ });
575
+ });
576
+
577
+ describe('gog_slides_element_group', () => {
578
+ it('groups elements with a group id', async () => {
579
+ await harness.callTool('gog_slides_element_group', { presentationId: 'p1', objectIds: ['o1', 'o2', 'o3'], groupId: 'g1' });
580
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
581
+ ['slides', 'element', 'group', 'p1', 'o1', 'o2', 'o3', '--group-id=g1'], { account: undefined });
582
+ });
583
+ it('groups elements without a group id', async () => {
584
+ await harness.callTool('gog_slides_element_group', { presentationId: 'p1', objectIds: ['o1', 'o2'] });
585
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
586
+ ['slides', 'element', 'group', 'p1', 'o1', 'o2'], { account: undefined });
587
+ });
588
+ });
589
+
590
+ describe('gog_slides_element_ungroup', () => {
591
+ it('ungroups groups', async () => {
592
+ await harness.callTool('gog_slides_element_ungroup', { presentationId: 'p1', groupIds: ['g1', 'g2'] });
593
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
594
+ ['slides', 'element', 'ungroup', 'p1', 'g1', 'g2'], { account: undefined });
595
+ });
596
+ });
597
+
598
+ describe('gog_slides_element_alt_text', () => {
599
+ it('sets title and description', async () => {
600
+ await harness.callTool('gog_slides_element_alt_text', { presentationId: 'p1', objectId: 'o1', title: 'T', description: 'D' });
601
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
602
+ ['slides', 'element', 'alt-text', 'p1', 'o1', '--title=T', '--description=D'], { account: undefined });
603
+ });
604
+ it('clears both fields with empty strings', async () => {
605
+ await harness.callTool('gog_slides_element_alt_text', { presentationId: 'p1', objectId: 'o1', title: '', description: '' });
606
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
607
+ ['slides', 'element', 'alt-text', 'p1', 'o1', '--title=', '--description='], { account: undefined });
608
+ });
609
+ it('bare leaves both untouched', async () => {
610
+ await harness.callTool('gog_slides_element_alt_text', { presentationId: 'p1', objectId: 'o1' });
611
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['slides', 'element', 'alt-text', 'p1', 'o1'], { account: undefined });
612
+ });
613
+ });
614
+
615
+ describe('gog_slides_element_delete', () => {
616
+ it('deletes an element', async () => {
617
+ await harness.callTool('gog_slides_element_delete', { presentationId: 'p1', objectId: 'o1' });
618
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['slides', 'element', 'delete', 'p1', 'o1', '--force'], { account: undefined });
619
+ });
620
+ });
621
+
622
+ // ===========================================================================
623
+ // gog 0.29 native tables (PR3b)
624
+ // ===========================================================================
625
+
626
+ describe('gog_slides_table_create', () => {
627
+ it('passes rows, cols and object id', async () => {
628
+ await harness.callTool('gog_slides_table_create', { presentationId: 'p1', slideId: 's1', rows: 3, cols: 4, objectId: 'tbl1' });
629
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
630
+ ['slides', 'table', 'create', 'p1', 's1', '--rows=3', '--cols=4', '--object-id=tbl1'], { account: undefined });
631
+ });
632
+ it('bare', async () => {
633
+ await harness.callTool('gog_slides_table_create', { presentationId: 'p1', slideId: 's1', rows: 2, cols: 2 });
634
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
635
+ ['slides', 'table', 'create', 'p1', 's1', '--rows=2', '--cols=2'], { account: undefined });
636
+ });
637
+ });
638
+
639
+ describe('gog_slides_table_cell_style', () => {
640
+ it('passes fill, alignment, range and text styling', async () => {
641
+ await harness.callTool('gog_slides_table_cell_style', {
642
+ presentationId: 'p1', tableObjectId: 't1', row: 0, col: 1, range: '0:3', fillColor: '#eee', fillTransparent: true,
643
+ contentAlign: 'MIDDLE', font: 'Arial', size: 12, textColor: '#111',
644
+ bold: true, noBold: true, italic: true, noItalic: true, underline: true, noUnderline: true,
645
+ });
646
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
647
+ ['slides', 'table', 'cell', 'style', 'p1', 't1', '--row=0', '--col=1', '--range=0:3', '--fill-color=#eee', '--fill-transparent',
648
+ '--content-align=MIDDLE', '--font=Arial', '--size=12', '--text-color=#111',
649
+ '--bold', '--no-bold', '--italic', '--no-italic', '--underline', '--no-underline'],
650
+ { account: undefined },
651
+ );
652
+ });
653
+ it('minimal (row/col only)', async () => {
654
+ await harness.callTool('gog_slides_table_cell_style', { presentationId: 'p1', tableObjectId: 't1', row: 0, col: 0 });
655
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
656
+ ['slides', 'table', 'cell', 'style', 'p1', 't1', '--row=0', '--col=0'], { account: undefined });
657
+ });
658
+ });
659
+
660
+ describe('gog_slides_table_border_style', () => {
661
+ it('passes span, position, color, weight, dash and transparent', async () => {
662
+ await harness.callTool('gog_slides_table_border_style', {
663
+ presentationId: 'p1', tableObjectId: 't1', row: 0, col: 0, rowSpan: 2, colSpan: 2,
664
+ position: 'OUTER', borderColor: '#000', weight: 2, dash: 'DASH', transparent: true,
665
+ });
666
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
667
+ ['slides', 'table', 'border', 'style', 'p1', 't1', '--row=0', '--col=0', '--row-span=2', '--col-span=2',
668
+ '--position=OUTER', '--border-color=#000', '--weight=2', '--dash=DASH', '--transparent'],
669
+ { account: undefined },
670
+ );
671
+ });
672
+ it('minimal (row/col only)', async () => {
673
+ await harness.callTool('gog_slides_table_border_style', { presentationId: 'p1', tableObjectId: 't1', row: 1, col: 1 });
674
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
675
+ ['slides', 'table', 'border', 'style', 'p1', 't1', '--row=1', '--col=1'], { account: undefined });
676
+ });
677
+ });
678
+
679
+ describe('gog_slides_table_column_insert', () => {
680
+ it('passes count and --right', async () => {
681
+ await harness.callTool('gog_slides_table_column_insert', { presentationId: 'p1', tableObjectId: 't1', col: 1, count: 2, right: true });
682
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
683
+ ['slides', 'table', 'column', 'insert', 'p1', 't1', '--col=1', '--count=2', '--right'], { account: undefined });
684
+ });
685
+ it('bare', async () => {
686
+ await harness.callTool('gog_slides_table_column_insert', { presentationId: 'p1', tableObjectId: 't1', col: 0 });
687
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
688
+ ['slides', 'table', 'column', 'insert', 'p1', 't1', '--col=0'], { account: undefined });
689
+ });
690
+ });
691
+
692
+ describe('gog_slides_table_column_delete', () => {
693
+ it('deletes by column index', async () => {
694
+ await harness.callTool('gog_slides_table_column_delete', { presentationId: 'p1', tableObjectId: 't1', col: 2 });
695
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
696
+ ['slides', 'table', 'column', 'delete', 'p1', 't1', '--col=2', '--force'], { account: undefined });
697
+ });
698
+ });
699
+
700
+ describe('gog_slides_table_column_size', () => {
701
+ it('sets column width', async () => {
702
+ await harness.callTool('gog_slides_table_column_size', { presentationId: 'p1', tableObjectId: 't1', col: 1, width: 120 });
703
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
704
+ ['slides', 'table', 'column', 'size', 'p1', 't1', '--col=1', '--width=120'], { account: undefined });
705
+ });
706
+ });
707
+
708
+ describe('gog_slides_table_row_insert', () => {
709
+ it('passes count and --below', async () => {
710
+ await harness.callTool('gog_slides_table_row_insert', { presentationId: 'p1', tableObjectId: 't1', row: 0, count: 1, below: true });
711
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
712
+ ['slides', 'table', 'row', 'insert', 'p1', 't1', '--row=0', '--count=1', '--below'], { account: undefined });
713
+ });
714
+ it('bare', async () => {
715
+ await harness.callTool('gog_slides_table_row_insert', { presentationId: 'p1', tableObjectId: 't1', row: 1 });
716
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
717
+ ['slides', 'table', 'row', 'insert', 'p1', 't1', '--row=1'], { account: undefined });
718
+ });
719
+ });
720
+
721
+ describe('gog_slides_table_row_delete', () => {
722
+ it('deletes by row index', async () => {
723
+ await harness.callTool('gog_slides_table_row_delete', { presentationId: 'p1', tableObjectId: 't1', row: 2 });
724
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
725
+ ['slides', 'table', 'row', 'delete', 'p1', 't1', '--row=2', '--force'], { account: undefined });
726
+ });
727
+ });
728
+
729
+ describe('gog_slides_table_row_size', () => {
730
+ it('sets row min height', async () => {
731
+ await harness.callTool('gog_slides_table_row_size', { presentationId: 'p1', tableObjectId: 't1', row: 0, height: 40 });
732
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
733
+ ['slides', 'table', 'row', 'size', 'p1', 't1', '--row=0', '--height=40'], { account: undefined });
734
+ });
735
+ });
736
+
737
+ describe('gog_slides_table_merge', () => {
738
+ it('passes span', async () => {
739
+ await harness.callTool('gog_slides_table_merge', { presentationId: 'p1', tableObjectId: 't1', row: 0, col: 0, rowSpan: 2, colSpan: 3 });
740
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
741
+ ['slides', 'table', 'merge', 'p1', 't1', '--row=0', '--col=0', '--row-span=2', '--col-span=3'], { account: undefined });
742
+ });
743
+ it('minimal', async () => {
744
+ await harness.callTool('gog_slides_table_merge', { presentationId: 'p1', tableObjectId: 't1', row: 0, col: 0 });
745
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
746
+ ['slides', 'table', 'merge', 'p1', 't1', '--row=0', '--col=0'], { account: undefined });
747
+ });
748
+ });
749
+
750
+ describe('gog_slides_table_unmerge', () => {
751
+ it('passes span', async () => {
752
+ await harness.callTool('gog_slides_table_unmerge', { presentationId: 'p1', tableObjectId: 't1', row: 0, col: 0, rowSpan: 2, colSpan: 2 });
753
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
754
+ ['slides', 'table', 'unmerge', 'p1', 't1', '--row=0', '--col=0', '--row-span=2', '--col-span=2'], { account: undefined });
755
+ });
756
+ it('minimal', async () => {
757
+ await harness.callTool('gog_slides_table_unmerge', { presentationId: 'p1', tableObjectId: 't1', row: 1, col: 1 });
758
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
759
+ ['slides', 'table', 'unmerge', 'p1', 't1', '--row=1', '--col=1'], { account: undefined });
760
+ });
761
+ });