gogcli-mcp-sheets 2.0.12 → 2.3.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.
@@ -119,11 +119,21 @@ describe('gog_sheets_insert', () => {
119
119
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'insert', 'sid', 'Sheet1', 'ROWS', '5'], { account: undefined });
120
120
  });
121
121
 
122
- it('includes --count and --after when provided', async () => {
122
+ it('shifts start by +1 when after:true so the new dimension lands AFTER start', async () => {
123
+ // Issue #42: with after:true, start=0 means "insert after index 0" — i.e. land at index 1.
124
+ // The MCP must compensate for the CLI's 1-based interpretation by passing start+1.
123
125
  vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
124
126
  const handlers = setupHandlers();
125
127
  await handlers.get('gog_sheets_insert')!({ spreadsheetId: 'sid', sheet: 'S1', dimension: 'COLUMNS', start: 0, count: 3, after: true });
126
- expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'insert', 'sid', 'S1', 'COLUMNS', '0', '--count=3', '--after'], { account: undefined });
128
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'insert', 'sid', 'S1', 'COLUMNS', '1', '--count=3', '--after'], { account: undefined });
129
+ });
130
+
131
+ it('issue #42 acceptance: start=28, after:true leaves column 28 untouched (insertion lands at 29)', async () => {
132
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
133
+ const handlers = setupHandlers();
134
+ await handlers.get('gog_sheets_insert')!({ spreadsheetId: 'sid', sheet: 'S1', dimension: 'COLUMNS', start: 28, after: true, count: 1 });
135
+ // CLI is 1-based; with --after CLI uses startIndex = c.Start, so passing 29 → API startIndex=29.
136
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'insert', 'sid', 'S1', 'COLUMNS', '29', '--count=1', '--after'], { account: undefined });
127
137
  });
128
138
 
129
139
  it('includes --count=0 when count is 0', async () => {
@@ -133,7 +143,7 @@ describe('gog_sheets_insert', () => {
133
143
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'insert', 'sid', 'S1', 'ROWS', '0', '--count=0'], { account: undefined });
134
144
  });
135
145
 
136
- it('omits --after when false', async () => {
146
+ it('omits --after and does not shift start when after:false', async () => {
137
147
  vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
138
148
  const handlers = setupHandlers();
139
149
  await handlers.get('gog_sheets_insert')!({ spreadsheetId: 'sid', sheet: 'S1', dimension: 'ROWS', start: 0, after: false });
@@ -314,6 +324,218 @@ describe('gog_sheets_number_format', () => {
314
324
  await handlers.get('gog_sheets_number_format')!({ spreadsheetId: 'sid', range: 'A1', type: 'CURRENCY', pattern: '#,##0.00' });
315
325
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'number-format', 'sid', 'A1', '--type=CURRENCY', '--pattern=#,##0.00'], { account: undefined });
316
326
  });
327
+
328
+ // Issue #43: warn when DATE/DATE_TIME format applied to small integers (silent 1899/1900 render).
329
+ describe('DATE/DATE_TIME small-integer warning', () => {
330
+ it('emits a warning when DATE applied to a range of small integers', async () => {
331
+ const handlers = setupHandlers();
332
+ vi.mocked(lib.runOrDiagnose).mockImplementation(async (args: string[]) => {
333
+ if (args[1] === 'get') {
334
+ return toText(JSON.stringify({ range: 'Sheet1!A1:A3', values: [[1], [2], [3]] }));
335
+ }
336
+ return toText('{"ok":true}');
337
+ });
338
+ const result = await handlers.get('gog_sheets_number_format')!({ spreadsheetId: 'sid', range: 'Sheet1!A1:A3', type: 'DATE' });
339
+ // Peek call goes out first.
340
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
341
+ ['sheets', 'get', 'sid', 'Sheet1!A1:A3', '--render=UNFORMATTED_VALUE'],
342
+ { account: undefined },
343
+ );
344
+ // The format call still happens (warning is non-blocking).
345
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
346
+ ['sheets', 'number-format', 'sid', 'Sheet1!A1:A3', '--type=DATE'],
347
+ { account: undefined },
348
+ );
349
+ const text = result.content[0].text;
350
+ expect(text).toMatch(/warning/i);
351
+ expect(text).toMatch(/1899|1900|day-serial|small integer/i);
352
+ expect(text).toMatch(/force/);
353
+ });
354
+
355
+ it('also warns for DATE_TIME', async () => {
356
+ const handlers = setupHandlers();
357
+ vi.mocked(lib.runOrDiagnose).mockImplementation(async (args: string[]) => {
358
+ if (args[1] === 'get') {
359
+ return toText(JSON.stringify({ range: 'Sheet1!A1:A2', values: [[5], [7]] }));
360
+ }
361
+ return toText('{"ok":true}');
362
+ });
363
+ const result = await handlers.get('gog_sheets_number_format')!({ spreadsheetId: 'sid', range: 'Sheet1!A1:A2', type: 'DATE_TIME' });
364
+ expect(result.content[0].text).toMatch(/warning/i);
365
+ });
366
+
367
+ it('does not peek or warn when force:true', async () => {
368
+ const handlers = setupHandlers();
369
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{"ok":true}'));
370
+ const result = await handlers.get('gog_sheets_number_format')!({ spreadsheetId: 'sid', range: 'Sheet1!A1:A3', type: 'DATE', force: true });
371
+ // Only the format call should fire — no peek.
372
+ expect(lib.runOrDiagnose).toHaveBeenCalledTimes(1);
373
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
374
+ ['sheets', 'number-format', 'sid', 'Sheet1!A1:A3', '--type=DATE'],
375
+ { account: undefined },
376
+ );
377
+ expect(result.content[0].text).not.toMatch(/warning/i);
378
+ });
379
+
380
+ it('peeks and warns when force:false is passed explicitly (round-trips the no-force path)', async () => {
381
+ const handlers = setupHandlers();
382
+ vi.mocked(lib.runOrDiagnose).mockImplementation(async (args: string[]) => {
383
+ if (args[1] === 'get') {
384
+ return toText(JSON.stringify({ range: 'A1:A3', values: [[1], [2], [3]] }));
385
+ }
386
+ return toText('{"ok":true}');
387
+ });
388
+ const result = await handlers.get('gog_sheets_number_format')!({ spreadsheetId: 'sid', range: 'A1:A3', type: 'DATE', force: false });
389
+ expect(lib.runOrDiagnose).toHaveBeenCalledTimes(2);
390
+ expect(result.content[0].text).toMatch(/warning/i);
391
+ });
392
+
393
+ it('does not peek when type is not DATE/DATE_TIME', async () => {
394
+ const handlers = setupHandlers();
395
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{"ok":true}'));
396
+ await handlers.get('gog_sheets_number_format')!({ spreadsheetId: 'sid', range: 'A1:A3', type: 'CURRENCY' });
397
+ expect(lib.runOrDiagnose).toHaveBeenCalledTimes(1);
398
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
399
+ ['sheets', 'number-format', 'sid', 'A1:A3', '--type=CURRENCY'],
400
+ { account: undefined },
401
+ );
402
+ });
403
+
404
+ it('does not warn when values include a large integer (>=10000)', async () => {
405
+ const handlers = setupHandlers();
406
+ vi.mocked(lib.runOrDiagnose).mockImplementation(async (args: string[]) => {
407
+ if (args[1] === 'get') {
408
+ // 45000 is a real day-serial (year ~2023) — legitimate date value.
409
+ return toText(JSON.stringify({ range: 'A1:A2', values: [[1], [45000]] }));
410
+ }
411
+ return toText('{"ok":true}');
412
+ });
413
+ const result = await handlers.get('gog_sheets_number_format')!({ spreadsheetId: 'sid', range: 'A1:A2', type: 'DATE' });
414
+ expect(result.content[0].text).not.toMatch(/warning/i);
415
+ });
416
+
417
+ it('does not warn when values include a negative integer (deltas/error codes, not day-serials)', async () => {
418
+ const handlers = setupHandlers();
419
+ vi.mocked(lib.runOrDiagnose).mockImplementation(async (args: string[]) => {
420
+ if (args[1] === 'get') {
421
+ return toText(JSON.stringify({ range: 'A1:A3', values: [[1], [-5], [3]] }));
422
+ }
423
+ return toText('{"ok":true}');
424
+ });
425
+ const result = await handlers.get('gog_sheets_number_format')!({ spreadsheetId: 'sid', range: 'A1:A3', type: 'DATE' });
426
+ expect(result.content[0].text).not.toMatch(/warning/i);
427
+ });
428
+
429
+ it('does not warn when values include non-integer numbers', async () => {
430
+ const handlers = setupHandlers();
431
+ vi.mocked(lib.runOrDiagnose).mockImplementation(async (args: string[]) => {
432
+ if (args[1] === 'get') {
433
+ return toText(JSON.stringify({ range: 'A1:A2', values: [[1], [2.5]] }));
434
+ }
435
+ return toText('{"ok":true}');
436
+ });
437
+ const result = await handlers.get('gog_sheets_number_format')!({ spreadsheetId: 'sid', range: 'A1:A2', type: 'DATE' });
438
+ expect(result.content[0].text).not.toMatch(/warning/i);
439
+ });
440
+
441
+ it('does not warn when range has no values (missing values key)', async () => {
442
+ const handlers = setupHandlers();
443
+ vi.mocked(lib.runOrDiagnose).mockImplementation(async (args: string[]) => {
444
+ if (args[1] === 'get') {
445
+ return toText(JSON.stringify({ range: 'A1:A3' }));
446
+ }
447
+ return toText('{"ok":true}');
448
+ });
449
+ const result = await handlers.get('gog_sheets_number_format')!({ spreadsheetId: 'sid', range: 'A1:A3', type: 'DATE' });
450
+ expect(result.content[0].text).not.toMatch(/warning/i);
451
+ });
452
+
453
+ it('does not warn when range has empty values array', async () => {
454
+ const handlers = setupHandlers();
455
+ vi.mocked(lib.runOrDiagnose).mockImplementation(async (args: string[]) => {
456
+ if (args[1] === 'get') {
457
+ return toText(JSON.stringify({ range: 'A1:A3', values: [] }));
458
+ }
459
+ return toText('{"ok":true}');
460
+ });
461
+ const result = await handlers.get('gog_sheets_number_format')!({ spreadsheetId: 'sid', range: 'A1:A3', type: 'DATE' });
462
+ expect(result.content[0].text).not.toMatch(/warning/i);
463
+ });
464
+
465
+ it('does not warn when values are strings', async () => {
466
+ const handlers = setupHandlers();
467
+ vi.mocked(lib.runOrDiagnose).mockImplementation(async (args: string[]) => {
468
+ if (args[1] === 'get') {
469
+ return toText(JSON.stringify({ range: 'A1:A2', values: [['hello'], ['world']] }));
470
+ }
471
+ return toText('{"ok":true}');
472
+ });
473
+ const result = await handlers.get('gog_sheets_number_format')!({ spreadsheetId: 'sid', range: 'A1:A2', type: 'DATE' });
474
+ expect(result.content[0].text).not.toMatch(/warning/i);
475
+ });
476
+
477
+ it('ignores null and empty cells when deciding to warn', async () => {
478
+ const handlers = setupHandlers();
479
+ vi.mocked(lib.runOrDiagnose).mockImplementation(async (args: string[]) => {
480
+ if (args[1] === 'get') {
481
+ // Real-world: nulls/empty strings interspersed with the ordinal ints.
482
+ return toText(JSON.stringify({ range: 'A1:A4', values: [[1], [null], [''], [3]] }));
483
+ }
484
+ return toText('{"ok":true}');
485
+ });
486
+ const result = await handlers.get('gog_sheets_number_format')!({ spreadsheetId: 'sid', range: 'A1:A4', type: 'DATE' });
487
+ expect(result.content[0].text).toMatch(/warning/i);
488
+ });
489
+
490
+ it('does not warn when every cell is null or empty (no small integers seen)', async () => {
491
+ const handlers = setupHandlers();
492
+ vi.mocked(lib.runOrDiagnose).mockImplementation(async (args: string[]) => {
493
+ if (args[1] === 'get') {
494
+ return toText(JSON.stringify({ range: 'A1:A3', values: [[null], [''], [null]] }));
495
+ }
496
+ return toText('{"ok":true}');
497
+ });
498
+ const result = await handlers.get('gog_sheets_number_format')!({ spreadsheetId: 'sid', range: 'A1:A3', type: 'DATE' });
499
+ expect(result.content[0].text).not.toMatch(/warning/i);
500
+ });
501
+
502
+ it('proceeds with format when peek fails (does not block)', async () => {
503
+ const handlers = setupHandlers();
504
+ vi.mocked(lib.runOrDiagnose).mockImplementation(async (args: string[]) => {
505
+ if (args[1] === 'get') {
506
+ return toText('Error: something went wrong');
507
+ }
508
+ return toText('{"ok":true}');
509
+ });
510
+ const result = await handlers.get('gog_sheets_number_format')!({ spreadsheetId: 'sid', range: 'A1:A3', type: 'DATE' });
511
+ // Format call should still happen.
512
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
513
+ ['sheets', 'number-format', 'sid', 'A1:A3', '--type=DATE'],
514
+ { account: undefined },
515
+ );
516
+ // No warning emitted when peek didn't yield clear evidence.
517
+ expect(result.content[0].text).not.toMatch(/warning/i);
518
+ });
519
+
520
+ it('forwards account on both peek and format calls', async () => {
521
+ const handlers = setupHandlers();
522
+ vi.mocked(lib.runOrDiagnose).mockImplementation(async (args: string[]) => {
523
+ if (args[1] === 'get') {
524
+ return toText(JSON.stringify({ range: 'A1', values: [[1]] }));
525
+ }
526
+ return toText('{}');
527
+ });
528
+ await handlers.get('gog_sheets_number_format')!({ spreadsheetId: 'sid', range: 'A1', type: 'DATE', account: 'a@b.com' });
529
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
530
+ ['sheets', 'get', 'sid', 'A1', '--render=UNFORMATTED_VALUE'],
531
+ { account: 'a@b.com' },
532
+ );
533
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
534
+ ['sheets', 'number-format', 'sid', 'A1', '--type=DATE'],
535
+ { account: 'a@b.com' },
536
+ );
537
+ });
538
+ });
317
539
  });
318
540
 
319
541
  // 12. read-format
@@ -565,3 +787,325 @@ describe('gog_sheets_reorder_tab', () => {
565
787
  );
566
788
  });
567
789
  });
790
+
791
+ // 25. chart list (gog 0.19.0)
792
+ describe('gog_sheets_chart_list', () => {
793
+ it('calls runOrDiagnose with correct args', async () => {
794
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
795
+ const handlers = setupHandlers();
796
+ await handlers.get('gog_sheets_chart_list')!({ spreadsheetId: 'sid' });
797
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'chart', 'list', 'sid'], { account: undefined });
798
+ });
799
+
800
+ it('forwards account', async () => {
801
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
802
+ const handlers = setupHandlers();
803
+ await handlers.get('gog_sheets_chart_list')!({ spreadsheetId: 'sid', account: 'a@b.com' });
804
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'chart', 'list', 'sid'], { account: 'a@b.com' });
805
+ });
806
+ });
807
+
808
+ // 26. chart get
809
+ describe('gog_sheets_chart_get', () => {
810
+ it('calls runOrDiagnose with correct args', async () => {
811
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
812
+ const handlers = setupHandlers();
813
+ await handlers.get('gog_sheets_chart_get')!({ spreadsheetId: 'sid', chartId: '12345' });
814
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'chart', 'get', 'sid', '12345'], { account: undefined });
815
+ });
816
+ });
817
+
818
+ // 27. chart create
819
+ describe('gog_sheets_chart_create', () => {
820
+ it('calls runOrDiagnose with only --spec-json', async () => {
821
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
822
+ const handlers = setupHandlers();
823
+ const spec = '{"basicChart":{"chartType":"COLUMN"}}';
824
+ await handlers.get('gog_sheets_chart_create')!({ spreadsheetId: 'sid', specJson: spec });
825
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
826
+ ['sheets', 'chart', 'create', 'sid', `--spec-json=${spec}`],
827
+ { account: undefined },
828
+ );
829
+ });
830
+
831
+ it('includes --sheet, --anchor, --width, --height when provided', async () => {
832
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
833
+ const handlers = setupHandlers();
834
+ await handlers.get('gog_sheets_chart_create')!({
835
+ spreadsheetId: 'sid', specJson: '{}', sheet: 'Data', anchor: 'E10', width: 800, height: 400,
836
+ });
837
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
838
+ ['sheets', 'chart', 'create', 'sid', '--spec-json={}', '--sheet=Data', '--anchor=E10', '--width=800', '--height=400'],
839
+ { account: undefined },
840
+ );
841
+ });
842
+
843
+ it('includes --width=0 and --height=0 when zero', async () => {
844
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
845
+ const handlers = setupHandlers();
846
+ await handlers.get('gog_sheets_chart_create')!({ spreadsheetId: 'sid', specJson: '{}', width: 0, height: 0 });
847
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
848
+ ['sheets', 'chart', 'create', 'sid', '--spec-json={}', '--width=0', '--height=0'],
849
+ { account: undefined },
850
+ );
851
+ });
852
+ });
853
+
854
+ // 28. chart update
855
+ describe('gog_sheets_chart_update', () => {
856
+ it('calls runOrDiagnose with chartId and --spec-json', async () => {
857
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
858
+ const handlers = setupHandlers();
859
+ await handlers.get('gog_sheets_chart_update')!({ spreadsheetId: 'sid', chartId: '99', specJson: '{}' });
860
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
861
+ ['sheets', 'chart', 'update', 'sid', '99', '--spec-json={}'],
862
+ { account: undefined },
863
+ );
864
+ });
865
+ });
866
+
867
+ // 29. chart delete
868
+ describe('gog_sheets_chart_delete', () => {
869
+ it('calls runOrDiagnose with correct args', async () => {
870
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
871
+ const handlers = setupHandlers();
872
+ await handlers.get('gog_sheets_chart_delete')!({ spreadsheetId: 'sid', chartId: '7' });
873
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'chart', 'delete', 'sid', '7'], { account: undefined });
874
+ });
875
+ });
876
+
877
+ // 30. table list
878
+ describe('gog_sheets_table_list', () => {
879
+ it('calls runOrDiagnose with correct args', async () => {
880
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
881
+ const handlers = setupHandlers();
882
+ await handlers.get('gog_sheets_table_list')!({ spreadsheetId: 'sid' });
883
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'table', 'list', 'sid'], { account: undefined });
884
+ });
885
+ });
886
+
887
+ // 31. table get
888
+ describe('gog_sheets_table_get', () => {
889
+ it('calls runOrDiagnose with correct args', async () => {
890
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
891
+ const handlers = setupHandlers();
892
+ await handlers.get('gog_sheets_table_get')!({ spreadsheetId: 'sid', tableId: 't1' });
893
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'table', 'get', 'sid', 't1'], { account: undefined });
894
+ });
895
+ });
896
+
897
+ // 32. table create
898
+ describe('gog_sheets_table_create', () => {
899
+ it('calls runOrDiagnose with range, --name, --columns-json', async () => {
900
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
901
+ const handlers = setupHandlers();
902
+ const cols = '[{"columnName":"Name","columnType":"TEXT"}]';
903
+ await handlers.get('gog_sheets_table_create')!({ spreadsheetId: 'sid', range: 'Sheet1!A1:B10', name: 'People', columnsJson: cols });
904
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
905
+ ['sheets', 'table', 'create', 'sid', 'Sheet1!A1:B10', '--name=People', `--columns-json=${cols}`],
906
+ { account: undefined },
907
+ );
908
+ });
909
+ });
910
+
911
+ // 33. table append
912
+ describe('gog_sheets_table_append', () => {
913
+ it('calls runOrDiagnose with --values-json (no --input)', async () => {
914
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
915
+ const handlers = setupHandlers();
916
+ await handlers.get('gog_sheets_table_append')!({ spreadsheetId: 'sid', tableId: 't1', valuesJson: '[["a",1]]' });
917
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
918
+ ['sheets', 'table', 'append', 'sid', 't1', '--values-json=[["a",1]]'],
919
+ { account: undefined },
920
+ );
921
+ });
922
+
923
+ it('includes --input when provided', async () => {
924
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
925
+ const handlers = setupHandlers();
926
+ await handlers.get('gog_sheets_table_append')!({ spreadsheetId: 'sid', tableId: 't1', valuesJson: '[]', input: 'RAW' });
927
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
928
+ ['sheets', 'table', 'append', 'sid', 't1', '--values-json=[]', '--input=RAW'],
929
+ { account: undefined },
930
+ );
931
+ });
932
+ });
933
+
934
+ // 34. table clear
935
+ describe('gog_sheets_table_clear', () => {
936
+ it('calls runOrDiagnose with correct args', async () => {
937
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
938
+ const handlers = setupHandlers();
939
+ await handlers.get('gog_sheets_table_clear')!({ spreadsheetId: 'sid', tableId: 't1' });
940
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'table', 'clear', 'sid', 't1'], { account: undefined });
941
+ });
942
+ });
943
+
944
+ // 35. table delete
945
+ describe('gog_sheets_table_delete', () => {
946
+ it('calls runOrDiagnose with correct args', async () => {
947
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
948
+ const handlers = setupHandlers();
949
+ await handlers.get('gog_sheets_table_delete')!({ spreadsheetId: 'sid', tableId: 't1' });
950
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'table', 'delete', 'sid', 't1'], { account: undefined });
951
+ });
952
+ });
953
+
954
+ // 36. banding list
955
+ describe('gog_sheets_banding_list', () => {
956
+ it('calls runOrDiagnose with no --sheet', async () => {
957
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
958
+ const handlers = setupHandlers();
959
+ await handlers.get('gog_sheets_banding_list')!({ spreadsheetId: 'sid' });
960
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'banding', 'list', 'sid'], { account: undefined });
961
+ });
962
+
963
+ it('includes --sheet when provided', async () => {
964
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
965
+ const handlers = setupHandlers();
966
+ await handlers.get('gog_sheets_banding_list')!({ spreadsheetId: 'sid', sheet: 'Data' });
967
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'banding', 'list', 'sid', '--sheet=Data'], { account: undefined });
968
+ });
969
+ });
970
+
971
+ // 37. banding set
972
+ describe('gog_sheets_banding_set', () => {
973
+ it('calls runOrDiagnose with range only', async () => {
974
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
975
+ const handlers = setupHandlers();
976
+ await handlers.get('gog_sheets_banding_set')!({ spreadsheetId: 'sid', range: 'Sheet1!A1:D20' });
977
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'banding', 'set', 'sid', 'Sheet1!A1:D20'], { account: undefined });
978
+ });
979
+
980
+ it('includes --row-properties-json and --column-properties-json when provided', async () => {
981
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
982
+ const handlers = setupHandlers();
983
+ await handlers.get('gog_sheets_banding_set')!({
984
+ spreadsheetId: 'sid', range: 'A1:D20',
985
+ rowPropertiesJson: '{"firstBandColor":{}}',
986
+ columnPropertiesJson: '{"secondBandColor":{}}',
987
+ });
988
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
989
+ ['sheets', 'banding', 'set', 'sid', 'A1:D20', '--row-properties-json={"firstBandColor":{}}', '--column-properties-json={"secondBandColor":{}}'],
990
+ { account: undefined },
991
+ );
992
+ });
993
+ });
994
+
995
+ // 38. banding clear
996
+ describe('gog_sheets_banding_clear', () => {
997
+ it('calls runOrDiagnose with no optional flags', async () => {
998
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
999
+ const handlers = setupHandlers();
1000
+ await handlers.get('gog_sheets_banding_clear')!({ spreadsheetId: 'sid' });
1001
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'banding', 'clear', 'sid'], { account: undefined });
1002
+ });
1003
+
1004
+ it('includes --id when provided (including 0)', async () => {
1005
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
1006
+ const handlers = setupHandlers();
1007
+ await handlers.get('gog_sheets_banding_clear')!({ spreadsheetId: 'sid', id: 0 });
1008
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'banding', 'clear', 'sid', '--id=0'], { account: undefined });
1009
+ });
1010
+
1011
+ it('includes --all and --sheet when clearing a whole sheet', async () => {
1012
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
1013
+ const handlers = setupHandlers();
1014
+ await handlers.get('gog_sheets_banding_clear')!({ spreadsheetId: 'sid', all: true, sheet: 'Data' });
1015
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'banding', 'clear', 'sid', '--all', '--sheet=Data'], { account: undefined });
1016
+ });
1017
+
1018
+ it('omits --all when false', async () => {
1019
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
1020
+ const handlers = setupHandlers();
1021
+ await handlers.get('gog_sheets_banding_clear')!({ spreadsheetId: 'sid', all: false, id: 5 });
1022
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'banding', 'clear', 'sid', '--id=5'], { account: undefined });
1023
+ });
1024
+ });
1025
+
1026
+ // 39. conditional-format list
1027
+ describe('gog_sheets_conditional_format_list', () => {
1028
+ it('calls runOrDiagnose with no --sheet', async () => {
1029
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
1030
+ const handlers = setupHandlers();
1031
+ await handlers.get('gog_sheets_conditional_format_list')!({ spreadsheetId: 'sid' });
1032
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'conditional-format', 'list', 'sid'], { account: undefined });
1033
+ });
1034
+
1035
+ it('includes --sheet when provided', async () => {
1036
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
1037
+ const handlers = setupHandlers();
1038
+ await handlers.get('gog_sheets_conditional_format_list')!({ spreadsheetId: 'sid', sheet: 'Data' });
1039
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'conditional-format', 'list', 'sid', '--sheet=Data'], { account: undefined });
1040
+ });
1041
+ });
1042
+
1043
+ // 40. conditional-format add
1044
+ describe('gog_sheets_conditional_format_add', () => {
1045
+ it('calls runOrDiagnose with type and --format-json (no expr/fields)', async () => {
1046
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
1047
+ const handlers = setupHandlers();
1048
+ await handlers.get('gog_sheets_conditional_format_add')!({
1049
+ spreadsheetId: 'sid', range: 'A1:A100', type: 'not-blank', formatJson: '{"backgroundColor":{}}',
1050
+ });
1051
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
1052
+ ['sheets', 'conditional-format', 'add', 'sid', 'A1:A100', '--type=not-blank', '--format-json={"backgroundColor":{}}'],
1053
+ { account: undefined },
1054
+ );
1055
+ });
1056
+
1057
+ it('includes --expr and --format-fields when provided', async () => {
1058
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
1059
+ const handlers = setupHandlers();
1060
+ await handlers.get('gog_sheets_conditional_format_add')!({
1061
+ spreadsheetId: 'sid', range: 'B1:B50', type: 'number-gt', expr: '100', formatJson: '{}', formatFields: 'backgroundColor,textFormat.bold',
1062
+ });
1063
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
1064
+ ['sheets', 'conditional-format', 'add', 'sid', 'B1:B50', '--type=number-gt', '--format-json={}', '--expr=100', '--format-fields=backgroundColor,textFormat.bold'],
1065
+ { account: undefined },
1066
+ );
1067
+ });
1068
+
1069
+ it('includes --expr when it is an empty string', async () => {
1070
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
1071
+ const handlers = setupHandlers();
1072
+ await handlers.get('gog_sheets_conditional_format_add')!({
1073
+ spreadsheetId: 'sid', range: 'A1', type: 'text-eq', expr: '', formatJson: '{}',
1074
+ });
1075
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
1076
+ ['sheets', 'conditional-format', 'add', 'sid', 'A1', '--type=text-eq', '--format-json={}', '--expr='],
1077
+ { account: undefined },
1078
+ );
1079
+ });
1080
+ });
1081
+
1082
+ // 41. conditional-format clear
1083
+ describe('gog_sheets_conditional_format_clear', () => {
1084
+ it('calls runOrDiagnose with --sheet only', async () => {
1085
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
1086
+ const handlers = setupHandlers();
1087
+ await handlers.get('gog_sheets_conditional_format_clear')!({ spreadsheetId: 'sid', sheet: 'Data' });
1088
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'conditional-format', 'clear', 'sid', '--sheet=Data'], { account: undefined });
1089
+ });
1090
+
1091
+ it('includes --index when provided (including 0)', async () => {
1092
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
1093
+ const handlers = setupHandlers();
1094
+ await handlers.get('gog_sheets_conditional_format_clear')!({ spreadsheetId: 'sid', sheet: 'Data', index: 0 });
1095
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'conditional-format', 'clear', 'sid', '--sheet=Data', '--index=0'], { account: undefined });
1096
+ });
1097
+
1098
+ it('includes --all when true', async () => {
1099
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
1100
+ const handlers = setupHandlers();
1101
+ await handlers.get('gog_sheets_conditional_format_clear')!({ spreadsheetId: 'sid', sheet: 'Data', all: true });
1102
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'conditional-format', 'clear', 'sid', '--sheet=Data', '--all'], { account: undefined });
1103
+ });
1104
+
1105
+ it('omits --all when false', async () => {
1106
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
1107
+ const handlers = setupHandlers();
1108
+ await handlers.get('gog_sheets_conditional_format_clear')!({ spreadsheetId: 'sid', sheet: 'Data', all: false, index: 2 });
1109
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'conditional-format', 'clear', 'sid', '--sheet=Data', '--index=2'], { account: undefined });
1110
+ });
1111
+ });