@apollo/client-ai-apps 0.3.2 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/.github/workflows/pr.yaml +52 -3
  2. package/.github/workflows/prep-release.yml +38 -0
  3. package/.github/workflows/release.yaml +8 -4
  4. package/.github/workflows/verify-changeset.yml +58 -0
  5. package/CHANGELOG.md +25 -0
  6. package/dist/core/ApolloClient.d.ts +3 -2
  7. package/dist/core/ApolloClient.d.ts.map +1 -0
  8. package/dist/core/ApolloClient.js +65 -0
  9. package/dist/core/ApolloClient.js.map +1 -0
  10. package/dist/index.d.ts +18 -17
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +16 -278
  13. package/dist/index.js.map +1 -0
  14. package/dist/link/ToolCallLink.d.ts +1 -0
  15. package/dist/link/ToolCallLink.d.ts.map +1 -0
  16. package/dist/link/ToolCallLink.js +39 -0
  17. package/dist/link/ToolCallLink.js.map +1 -0
  18. package/dist/react/ApolloProvider.d.ts +4 -3
  19. package/dist/react/ApolloProvider.d.ts.map +1 -0
  20. package/dist/react/ApolloProvider.js +30 -0
  21. package/dist/react/ApolloProvider.js.map +1 -0
  22. package/dist/react/context/ToolUseContext.d.ts +4 -3
  23. package/dist/react/context/ToolUseContext.d.ts.map +1 -0
  24. package/dist/react/context/ToolUseContext.js +11 -0
  25. package/dist/react/context/ToolUseContext.js.map +1 -0
  26. package/dist/react/hooks/useCallTool.d.ts +4 -0
  27. package/dist/react/hooks/useCallTool.d.ts.map +1 -0
  28. package/dist/react/hooks/useCallTool.js +5 -0
  29. package/dist/react/hooks/useCallTool.js.map +1 -0
  30. package/dist/react/hooks/useOpenAiGlobal.d.ts +2 -1
  31. package/dist/react/hooks/useOpenAiGlobal.d.ts.map +1 -0
  32. package/dist/react/hooks/useOpenAiGlobal.js +20 -0
  33. package/dist/react/hooks/useOpenAiGlobal.js.map +1 -0
  34. package/dist/react/hooks/useOpenExternal.d.ts +1 -0
  35. package/dist/react/hooks/useOpenExternal.d.ts.map +1 -0
  36. package/dist/react/hooks/useOpenExternal.js +5 -0
  37. package/dist/react/hooks/useOpenExternal.js.map +1 -0
  38. package/dist/react/hooks/useRequestDisplayMode.d.ts +2 -1
  39. package/dist/react/hooks/useRequestDisplayMode.d.ts.map +1 -0
  40. package/dist/react/hooks/useRequestDisplayMode.js +6 -0
  41. package/dist/react/hooks/useRequestDisplayMode.js.map +1 -0
  42. package/dist/react/hooks/useSendFollowUpMessage.d.ts +1 -0
  43. package/dist/react/hooks/useSendFollowUpMessage.d.ts.map +1 -0
  44. package/dist/react/hooks/useSendFollowUpMessage.js +8 -0
  45. package/dist/react/hooks/useSendFollowUpMessage.js.map +1 -0
  46. package/dist/react/hooks/useToolEffect.d.ts +1 -0
  47. package/dist/react/hooks/useToolEffect.d.ts.map +1 -0
  48. package/dist/react/hooks/useToolEffect.js +28 -0
  49. package/dist/react/hooks/useToolEffect.js.map +1 -0
  50. package/dist/react/hooks/useToolInput.d.ts +1 -0
  51. package/dist/react/hooks/useToolInput.d.ts.map +1 -0
  52. package/dist/react/hooks/useToolInput.js +6 -0
  53. package/dist/react/hooks/useToolInput.js.map +1 -0
  54. package/dist/react/hooks/useToolName.d.ts +1 -0
  55. package/dist/react/hooks/useToolName.d.ts.map +1 -0
  56. package/dist/react/hooks/useToolName.js +6 -0
  57. package/dist/react/hooks/useToolName.js.map +1 -0
  58. package/dist/react/hooks/useToolOutput.d.ts +2 -1
  59. package/dist/react/hooks/useToolOutput.d.ts.map +1 -0
  60. package/dist/react/hooks/useToolOutput.js +5 -0
  61. package/dist/react/hooks/useToolOutput.js.map +1 -0
  62. package/dist/react/hooks/useToolResponseMetadata.d.ts +2 -1
  63. package/dist/react/hooks/useToolResponseMetadata.d.ts.map +1 -0
  64. package/dist/react/hooks/useToolResponseMetadata.js +5 -0
  65. package/dist/react/hooks/useToolResponseMetadata.js.map +1 -0
  66. package/dist/react/hooks/useWidgetState.d.ts +3 -2
  67. package/dist/react/hooks/useWidgetState.d.ts.map +1 -0
  68. package/dist/react/hooks/useWidgetState.js +27 -0
  69. package/dist/react/hooks/useWidgetState.js.map +1 -0
  70. package/dist/types/application-manifest.d.ts +7 -0
  71. package/dist/types/application-manifest.d.ts.map +1 -0
  72. package/dist/types/application-manifest.js +2 -0
  73. package/dist/types/application-manifest.js.map +1 -0
  74. package/dist/types/openai.d.ts +1 -0
  75. package/dist/types/openai.d.ts.map +1 -0
  76. package/dist/types/openai.js +6 -0
  77. package/dist/types/openai.js.map +1 -0
  78. package/dist/vite/absolute_asset_imports_plugin.d.ts +1 -0
  79. package/dist/vite/absolute_asset_imports_plugin.d.ts.map +1 -0
  80. package/dist/vite/absolute_asset_imports_plugin.js +17 -0
  81. package/dist/vite/absolute_asset_imports_plugin.js.map +1 -0
  82. package/dist/vite/application_manifest_plugin.d.ts +2 -1
  83. package/dist/vite/application_manifest_plugin.d.ts.map +1 -0
  84. package/dist/vite/application_manifest_plugin.js +274 -0
  85. package/dist/vite/application_manifest_plugin.js.map +1 -0
  86. package/dist/vite/index.d.ts +3 -2
  87. package/dist/vite/index.d.ts.map +1 -0
  88. package/dist/vite/index.js +3 -307
  89. package/dist/vite/index.js.map +1 -0
  90. package/knope.toml +63 -0
  91. package/package.json +15 -8
  92. package/src/core/ApolloClient.ts +10 -5
  93. package/src/core/__tests__/ApolloClient.test.ts +12 -9
  94. package/src/index.ts +17 -17
  95. package/src/react/ApolloProvider.tsx +4 -3
  96. package/src/react/__tests__/ApolloProvider.test.tsx +3 -3
  97. package/src/react/context/ToolUseContext.tsx +2 -1
  98. package/src/react/hooks/__tests__/useCallTool.test.ts +1 -1
  99. package/src/react/hooks/__tests__/useOpenAiGlobal.test.ts +6 -6
  100. package/src/react/hooks/__tests__/useOpenExternal.test.tsx +2 -2
  101. package/src/react/hooks/__tests__/useRequestDisplayMode.test.ts +2 -2
  102. package/src/react/hooks/__tests__/useSendFollowUpMessage.test.ts +1 -1
  103. package/src/react/hooks/__tests__/useToolEffect.test.tsx +2 -2
  104. package/src/react/hooks/__tests__/useToolInput.test.ts +1 -1
  105. package/src/react/hooks/__tests__/useToolName.test.ts +1 -1
  106. package/src/react/hooks/__tests__/useToolOutput.test.tsx +2 -2
  107. package/src/react/hooks/__tests__/useToolResponseMetadata.test.tsx +2 -2
  108. package/src/react/hooks/__tests__/useWidgetState.test.tsx +2 -2
  109. package/src/react/hooks/useOpenAiGlobal.ts +2 -5
  110. package/src/react/hooks/useOpenExternal.ts +1 -1
  111. package/src/react/hooks/useRequestDisplayMode.ts +1 -1
  112. package/src/react/hooks/useToolEffect.tsx +3 -3
  113. package/src/react/hooks/useToolInput.ts +1 -1
  114. package/src/react/hooks/useToolName.ts +1 -1
  115. package/src/react/hooks/useToolOutput.ts +1 -1
  116. package/src/react/hooks/useToolResponseMetadata.ts +1 -1
  117. package/src/react/hooks/useWidgetState.ts +4 -3
  118. package/src/testing/internal/index.ts +2 -2
  119. package/src/testing/internal/matchers/index.ts +1 -1
  120. package/src/testing/internal/matchers/toRerender.ts +2 -2
  121. package/src/testing/internal/matchers/{index.d.ts → types.ts} +1 -1
  122. package/src/testing/internal/openai/dispatchStateChange.ts +1 -1
  123. package/src/testing/internal/openai/stubOpenAiGlobals.ts +6 -2
  124. package/src/types/application-manifest.ts +7 -0
  125. package/src/vite/__tests__/absolute_asset_imports_plugin.test.ts +2 -2
  126. package/src/vite/__tests__/application_manifest_plugin.test.ts +299 -240
  127. package/src/vite/application_manifest_plugin.ts +160 -96
  128. package/src/vite/index.ts +2 -2
  129. package/tsconfig.base.build.json +13 -0
  130. package/tsconfig.base.json +21 -0
  131. package/tsconfig.config.json +9 -0
  132. package/tsconfig.json +10 -0
  133. package/tsconfig.src.build.json +14 -0
  134. package/tsconfig.src.json +17 -0
  135. package/tsconfig.test.json +20 -0
  136. package/tsconfig.vite.build.json +6 -0
  137. package/tsconfig.vite.json +16 -0
  138. package/scripts/build-vite.mjs +0 -18
  139. package/scripts/build.mjs +0 -7
  140. package/scripts/shared.mjs +0 -9
@@ -1,8 +1,10 @@
1
- import { expect, test, vi, describe, beforeEach, Mock } from "vitest";
2
- import { ApplicationManifestPlugin } from "../application_manifest_plugin";
1
+ import type { Mock } from "vitest";
2
+ import { expect, test, vi, describe, beforeEach } from "vitest";
3
+ import { ApplicationManifestPlugin } from "../application_manifest_plugin.js";
3
4
  import fs from "fs";
4
5
  import * as glob from "glob";
5
6
  import path from "path";
7
+ import type { ManifestWidgetSettings } from "../../types/application-manifest.js";
6
8
 
7
9
  const root = process.cwd();
8
10
 
@@ -31,12 +33,7 @@ vi.mock(import("path"), async (importOriginal) => {
31
33
  };
32
34
  });
33
35
 
34
- vi.mock(import("glob"), async (importOriginal) => {
35
- const actual = await importOriginal();
36
- return {
37
- glob: vi.fn(),
38
- };
39
- });
36
+ vi.mock(import("glob"));
40
37
 
41
38
  beforeEach(() => {
42
39
  vi.clearAllMocks();
@@ -44,19 +41,23 @@ beforeEach(() => {
44
41
 
45
42
  describe("buildStart", () => {
46
43
  test("Should write to dev application manifest file when using a serve command", async () => {
47
- vi.spyOn(fs, "readFileSync").mockImplementation((path) => {
48
- if (path === "package.json") {
49
- return JSON.stringify({});
50
- } else if (path === root + "/my-component.tsx") {
51
- return `
52
- const MY_QUERY = gql\`query HelloWorldQuery($name: string!) @tool(name: "hello-world", description: "This is an awesome tool!", extraInputs: [{
53
- name: "doStuff",
54
- type: "boolean",
55
- description: "Should we do stuff?"
56
- }]) { helloWorld(name: $name) }\`;
57
- `;
58
- }
44
+ mockReadFile({
45
+ "package.json": JSON.stringify({
46
+ widgetSettings: {
47
+ description: "Test",
48
+ domain: "https://example.com",
49
+ prefersBorder: true,
50
+ } satisfies ManifestWidgetSettings,
51
+ }),
52
+ [`${root}/my-component.tsx`]: `
53
+ const MY_QUERY = gql\`query HelloWorldQuery($name: string!) @tool(name: "hello-world", description: "This is an awesome tool!", extraInputs: [{
54
+ name: "doStuff",
55
+ type: "boolean",
56
+ description: "Should we do stuff?"
57
+ }]) { helloWorld(name: $name) }\`;
58
+ `,
59
59
  });
60
+
60
61
  vi.spyOn(glob, "glob").mockImplementation(() =>
61
62
  Promise.resolve(["my-component.tsx"])
62
63
  );
@@ -114,19 +115,21 @@ describe("buildStart", () => {
114
115
  ],
115
116
  "resource": "http://localhost:undefined",
116
117
  "version": "1",
118
+ "widgetSettings": {
119
+ "description": "Test",
120
+ "domain": "https://example.com",
121
+ "prefersBorder": true,
122
+ },
117
123
  }
118
124
  `);
119
125
  });
120
126
 
121
127
  test("Should NOT write to dev application manifest file when using a build command", async () => {
122
- vi.spyOn(fs, "readFileSync").mockImplementation((path) => {
123
- if (path === "package.json") {
124
- return JSON.stringify({});
125
- } else if (path === "my-component.tsx") {
126
- return `
127
- const MY_QUERY = gql\`query HelloWorldQuery @tool(name: "hello-world", description: "This is an awesome tool!") { helloWorld }\`;
128
- `;
129
- }
128
+ mockReadFile({
129
+ "package.json": JSON.stringify({}),
130
+ "my-component.tsx": `
131
+ const MY_QUERY = gql\`query HelloWorldQuery @tool(name: "hello-world", description: "This is an awesome tool!") { helloWorld }\`;
132
+ `,
130
133
  });
131
134
  vi.spyOn(glob, "glob").mockImplementation(() =>
132
135
  Promise.resolve(["my-component.tsx"])
@@ -142,14 +145,11 @@ describe("buildStart", () => {
142
145
  });
143
146
 
144
147
  test("Should not process files that do not contain gql tags", async () => {
145
- vi.spyOn(fs, "readFileSync").mockImplementation((path) => {
146
- if (path === "package.json") {
147
- return JSON.stringify({});
148
- } else if (path === root + "/my-component.tsx") {
149
- return `
150
- const MY_QUERY = \`query HelloWorldQuery @tool(name: "hello-world", description: "This is an awesome tool!") { helloWorld }\`;
151
- `;
152
- }
148
+ mockReadFile({
149
+ "package.json": JSON.stringify({}),
150
+ [`${root}/my-component.tsx`]: `
151
+ const MY_QUERY = \`query HelloWorldQuery @tool(name: "hello-world", description: "This is an awesome tool!") { helloWorld }\`;
152
+ `,
153
153
  });
154
154
  vi.spyOn(glob, "glob").mockImplementation(() =>
155
155
  Promise.resolve(["my-component.tsx"])
@@ -187,14 +187,11 @@ describe("buildStart", () => {
187
187
  });
188
188
 
189
189
  test("Should capture queries when writing to manifest file", async () => {
190
- vi.spyOn(fs, "readFileSync").mockImplementation((path) => {
191
- if (path === "package.json") {
192
- return JSON.stringify({});
193
- } else if (path === root + "/my-component.tsx") {
194
- return `
195
- const MY_QUERY = gql\`query HelloWorldQuery { helloWorld }\`;
196
- `;
197
- }
190
+ mockReadFile({
191
+ "package.json": JSON.stringify({}),
192
+ [`${root}/my-component.tsx`]: `
193
+ const MY_QUERY = gql\`query HelloWorldQuery { helloWorld }\`;
194
+ `,
198
195
  });
199
196
  vi.spyOn(glob, "glob").mockImplementation(() =>
200
197
  Promise.resolve(["my-component.tsx"])
@@ -244,14 +241,11 @@ describe("buildStart", () => {
244
241
  });
245
242
 
246
243
  test("Should capture queries as prefetch when query is marked with @prefetch directive", async () => {
247
- vi.spyOn(fs, "readFileSync").mockImplementation((path) => {
248
- if (path === "package.json") {
249
- return JSON.stringify({});
250
- } else if (path === root + "/my-component.tsx") {
251
- return `
252
- const MY_QUERY = gql\`query HelloWorldQuery @prefetch { helloWorld }\`;
253
- `;
254
- }
244
+ mockReadFile({
245
+ "package.json": JSON.stringify({}),
246
+ [`${root}/my-component.tsx`]: `
247
+ const MY_QUERY = gql\`query HelloWorldQuery @prefetch { helloWorld }\`;
248
+ `,
255
249
  });
256
250
  vi.spyOn(glob, "glob").mockImplementation(() =>
257
251
  Promise.resolve(["my-component.tsx"])
@@ -302,15 +296,12 @@ describe("buildStart", () => {
302
296
  });
303
297
 
304
298
  test("Should error when multiple operations are marked with @prefetch", async () => {
305
- vi.spyOn(fs, "readFileSync").mockImplementation((path) => {
306
- if (path === "package.json") {
307
- return JSON.stringify({});
308
- } else if (path === "my-component.tsx") {
309
- return `
310
- const MY_QUERY = gql\`query HelloWorldQuery @prefetch { helloWorld }\`;
311
- const MY_QUERY2 = gql\`query HelloWorldQuery2 @prefetch { helloWorld }\`;
312
- `;
313
- }
299
+ mockReadFile({
300
+ "package.json": JSON.stringify({}),
301
+ "my-component.tsx": `
302
+ const MY_QUERY = gql\`query HelloWorldQuery @prefetch { helloWorld }\`;
303
+ const MY_QUERY2 = gql\`query HelloWorldQuery2 @prefetch { helloWorld }\`;
304
+ `,
314
305
  });
315
306
  vi.spyOn(glob, "glob").mockImplementation(() =>
316
307
  Promise.resolve(["my-component.tsx"])
@@ -328,14 +319,11 @@ describe("buildStart", () => {
328
319
  });
329
320
 
330
321
  test("Should capture mutations when writing to manifest file", async () => {
331
- vi.spyOn(fs, "readFileSync").mockImplementation((path) => {
332
- if (path === "package.json") {
333
- return JSON.stringify({});
334
- } else if (path === root + "/my-component.tsx") {
335
- return `
336
- const MY_QUERY = gql\`mutation HelloWorldQuery @tool(name: "hello-world", description: "This is an awesome tool!") { helloWorld }\`;
337
- `;
338
- }
322
+ mockReadFile({
323
+ "package.json": JSON.stringify({}),
324
+ [`${root}/my-component.tsx`]: `
325
+ const MY_QUERY = gql\`mutation HelloWorldQuery @tool(name: "hello-world", description: "This is an awesome tool!") { helloWorld }\`;
326
+ `,
339
327
  });
340
328
  vi.spyOn(glob, "glob").mockImplementation(() =>
341
329
  Promise.resolve(["my-component.tsx"])
@@ -390,14 +378,11 @@ describe("buildStart", () => {
390
378
  });
391
379
 
392
380
  test("Should throw error when a subscription operation type is discovered", async () => {
393
- vi.spyOn(fs, "readFileSync").mockImplementation((path) => {
394
- if (path === "package.json") {
395
- return JSON.stringify({});
396
- } else if (path === "my-component.tsx") {
397
- return `
398
- const MY_QUERY = gql\`subscription HelloWorldQuery @tool(name: "hello-world", description: "This is an awesome tool!") { helloWorld }\`;
399
- `;
400
- }
381
+ mockReadFile({
382
+ "package.json": JSON.stringify({}),
383
+ "my-component.tsx": `
384
+ const MY_QUERY = gql\`subscription HelloWorldQuery @tool(name: "hello-world", description: "This is an awesome tool!") { helloWorld }\`;
385
+ `,
401
386
  });
402
387
  vi.spyOn(glob, "glob").mockImplementation(() =>
403
388
  Promise.resolve(["my-component.tsx"])
@@ -416,18 +401,15 @@ describe("buildStart", () => {
416
401
  });
417
402
 
418
403
  test("Should use custom entry point when in serve mode and provided in package.json", async () => {
419
- vi.spyOn(fs, "readFileSync").mockImplementation((path) => {
420
- if (path === "package.json") {
421
- return JSON.stringify({
422
- entry: {
423
- staging: "http://staging.awesome.com",
424
- },
425
- });
426
- } else if (path === "my-component.tsx") {
427
- return `
428
- const MY_QUERY = gql\`query HelloWorldQuery @tool(name: "hello-world", description: "This is an awesome tool!") { helloWorld }\`;
429
- `;
430
- }
404
+ mockReadFile({
405
+ "package.json": JSON.stringify({
406
+ entry: {
407
+ staging: "http://staging.awesome.com",
408
+ },
409
+ }),
410
+ "my-component.tsx": `
411
+ const MY_QUERY = gql\`query HelloWorldQuery @tool(name: "hello-world", description: "This is an awesome tool!") { helloWorld }\`;
412
+ `,
431
413
  });
432
414
  vi.spyOn(glob, "glob").mockImplementation(() =>
433
415
  Promise.resolve(["my-component.tsx"])
@@ -444,21 +426,18 @@ describe("buildStart", () => {
444
426
  });
445
427
  await plugin.buildStart();
446
428
 
447
- let [file, content] = (fs.writeFileSync as unknown as Mock).mock.calls[0];
429
+ let [, content] = (fs.writeFileSync as unknown as Mock).mock.calls[0];
448
430
  let contentObj = JSON.parse(content);
449
431
 
450
432
  expect(contentObj.resource).toBe("http://staging.awesome.com");
451
433
  });
452
434
 
453
435
  test("Should use https when enabled in server config", async () => {
454
- vi.spyOn(fs, "readFileSync").mockImplementation((path) => {
455
- if (path === "package.json") {
456
- return JSON.stringify({});
457
- } else if (path === "my-component.tsx") {
458
- return `
459
- const MY_QUERY = gql\`query HelloWorldQuery @tool(name: "hello-world", description: "This is an awesome tool!") { helloWorld }\`;
460
- `;
461
- }
436
+ mockReadFile({
437
+ "package.json": JSON.stringify({}),
438
+ "my-component.tsx": `
439
+ const MY_QUERY = gql\`query HelloWorldQuery @tool(name: "hello-world", description: "This is an awesome tool!") { helloWorld }\`;
440
+ `,
462
441
  });
463
442
  vi.spyOn(glob, "glob").mockImplementation(() =>
464
443
  Promise.resolve(["my-component.tsx"])
@@ -474,21 +453,18 @@ describe("buildStart", () => {
474
453
  });
475
454
  await plugin.buildStart();
476
455
 
477
- let [file, content] = (fs.writeFileSync as unknown as Mock).mock.calls[0];
456
+ let [, content] = (fs.writeFileSync as unknown as Mock).mock.calls[0];
478
457
  let contentObj = JSON.parse(content);
479
458
 
480
459
  expect(contentObj.resource).toBe("https://localhost:5678");
481
460
  });
482
461
 
483
462
  test("Should use custom host when specified in server config", async () => {
484
- vi.spyOn(fs, "readFileSync").mockImplementation((path) => {
485
- if (path === "package.json") {
486
- return JSON.stringify({});
487
- } else if (path === "my-component.tsx") {
488
- return `
489
- const MY_QUERY = gql\`query HelloWorldQuery @tool(name: "hello-world", description: "This is an awesome tool!") { helloWorld }\`;
490
- `;
491
- }
463
+ mockReadFile({
464
+ "package.json": JSON.stringify({}),
465
+ "my-component.tsx": `
466
+ const MY_QUERY = gql\`query HelloWorldQuery @tool(name: "hello-world", description: "This is an awesome tool!") { helloWorld }\`;
467
+ `,
492
468
  });
493
469
  vi.spyOn(glob, "glob").mockImplementation(() =>
494
470
  Promise.resolve(["my-component.tsx"])
@@ -504,21 +480,18 @@ describe("buildStart", () => {
504
480
  });
505
481
  await plugin.buildStart();
506
482
 
507
- let [file, content] = (fs.writeFileSync as unknown as Mock).mock.calls[0];
483
+ let [, content] = (fs.writeFileSync as unknown as Mock).mock.calls[0];
508
484
  let contentObj = JSON.parse(content);
509
485
 
510
486
  expect(contentObj.resource).toBe("http://awesome.com:5678");
511
487
  });
512
488
 
513
489
  test("Should error when tool name is not provided", async () => {
514
- vi.spyOn(fs, "readFileSync").mockImplementation((path) => {
515
- if (path === "package.json") {
516
- return JSON.stringify({});
517
- } else if (path === "my-component.tsx") {
518
- return `
519
- const MY_QUERY = gql\`query HelloWorldQuery @tool { helloWorld }\`;
520
- `;
521
- }
490
+ mockReadFile({
491
+ "package.json": JSON.stringify({}),
492
+ "my-component.tsx": `
493
+ const MY_QUERY = gql\`query HelloWorldQuery @tool { helloWorld }\`;
494
+ `,
522
495
  });
523
496
  vi.spyOn(glob, "glob").mockImplementation(() =>
524
497
  Promise.resolve(["my-component.tsx"])
@@ -537,14 +510,11 @@ describe("buildStart", () => {
537
510
  });
538
511
 
539
512
  test("Should error when tool description is not provided", async () => {
540
- vi.spyOn(fs, "readFileSync").mockImplementation((path) => {
541
- if (path === "package.json") {
542
- return JSON.stringify({});
543
- } else if (path === "my-component.tsx") {
544
- return `
545
- const MY_QUERY = gql\`query HelloWorldQuery @tool(name: "hello-world") { helloWorld }\`;
546
- `;
547
- }
513
+ mockReadFile({
514
+ "package.json": JSON.stringify({}),
515
+ "my-component.tsx": `
516
+ const MY_QUERY = gql\`query HelloWorldQuery @tool(name: "hello-world") { helloWorld }\`;
517
+ `,
548
518
  });
549
519
  vi.spyOn(glob, "glob").mockImplementation(() =>
550
520
  Promise.resolve(["my-component.tsx"])
@@ -563,14 +533,11 @@ describe("buildStart", () => {
563
533
  });
564
534
 
565
535
  test("Should error when tool name contains spaces", async () => {
566
- vi.spyOn(fs, "readFileSync").mockImplementation((path) => {
567
- if (path === "package.json") {
568
- return JSON.stringify({});
569
- } else if (path === "my-component.tsx") {
570
- return `
571
- const MY_QUERY = gql\`query HelloWorldQuery @tool(name: "hello world", description: "A tool") { helloWorld }\`;
572
- `;
573
- }
536
+ mockReadFile({
537
+ "package.json": JSON.stringify({}),
538
+ "my-component.tsx": `
539
+ const MY_QUERY = gql\`query HelloWorldQuery @tool(name: "hello world", description: "A tool") { helloWorld }\`;
540
+ `,
574
541
  });
575
542
  vi.spyOn(glob, "glob").mockImplementation(() =>
576
543
  Promise.resolve(["my-component.tsx"])
@@ -589,14 +556,11 @@ describe("buildStart", () => {
589
556
  });
590
557
 
591
558
  test("Should error when tool name is not a string", async () => {
592
- vi.spyOn(fs, "readFileSync").mockImplementation((path) => {
593
- if (path === "package.json") {
594
- return JSON.stringify({});
595
- } else if (path === "my-component.tsx") {
596
- return `
597
- const MY_QUERY = gql\`query HelloWorldQuery @tool(name: true) { helloWorld }\`;
598
- `;
599
- }
559
+ mockReadFile({
560
+ "package.json": JSON.stringify({}),
561
+ "my-component.tsx": `
562
+ const MY_QUERY = gql\`query HelloWorldQuery @tool(name: true) { helloWorld }\`;
563
+ `,
600
564
  });
601
565
  vi.spyOn(glob, "glob").mockImplementation(() =>
602
566
  Promise.resolve(["my-component.tsx"])
@@ -615,14 +579,11 @@ describe("buildStart", () => {
615
579
  });
616
580
 
617
581
  test("Should error when tool description is not a string", async () => {
618
- vi.spyOn(fs, "readFileSync").mockImplementation((path) => {
619
- if (path === "package.json") {
620
- return JSON.stringify({});
621
- } else if (path === "my-component.tsx") {
622
- return `
623
- const MY_QUERY = gql\`query HelloWorldQuery @tool(name: "hello-world", description: false) { helloWorld }\`;
624
- `;
625
- }
582
+ mockReadFile({
583
+ "package.json": JSON.stringify({}),
584
+ "my-component.tsx": `
585
+ const MY_QUERY = gql\`query HelloWorldQuery @tool(name: "hello-world", description: false) { helloWorld }\`;
586
+ `,
626
587
  });
627
588
  vi.spyOn(glob, "glob").mockImplementation(() =>
628
589
  Promise.resolve(["my-component.tsx"])
@@ -641,14 +602,11 @@ describe("buildStart", () => {
641
602
  });
642
603
 
643
604
  test("Should error when extraInputs is not an array", async () => {
644
- vi.spyOn(fs, "readFileSync").mockImplementation((path) => {
645
- if (path === "package.json") {
646
- return JSON.stringify({});
647
- } else if (path === "my-component.tsx") {
648
- return `
649
- const MY_QUERY = gql\`query HelloWorldQuery @tool(name: "hello-world", description: "hello", extraInputs: false ) { helloWorld }\`;
650
- `;
651
- }
605
+ mockReadFile({
606
+ "package.json": JSON.stringify({}),
607
+ "my-component.tsx": `
608
+ const MY_QUERY = gql\`query HelloWorldQuery @tool(name: "hello-world", description: "hello", extraInputs: false ) { helloWorld }\`;
609
+ `,
652
610
  });
653
611
  vi.spyOn(glob, "glob").mockImplementation(() =>
654
612
  Promise.resolve(["my-component.tsx"])
@@ -666,17 +624,116 @@ describe("buildStart", () => {
666
624
  );
667
625
  });
668
626
 
627
+ test("Should error when widgetSettings.prefersBorder is not a boolean", async () => {
628
+ mockReadFile({
629
+ "package.json": JSON.stringify({
630
+ widgetSettings: {
631
+ prefersBorder: "test",
632
+ },
633
+ }),
634
+ "my-component.tsx": `
635
+ const MY_QUERY = gql\`query HelloWorldQuery @tool(name: "test", description: "Test") { helloWorld }\`;
636
+ `,
637
+ });
638
+ vi.spyOn(glob, "glob").mockImplementation(() =>
639
+ Promise.resolve(["my-component.tsx"])
640
+ );
641
+ vi.spyOn(path, "resolve").mockImplementation((_, file) => file);
642
+ vi.spyOn(fs, "writeFileSync");
643
+
644
+ const plugin = ApplicationManifestPlugin();
645
+ plugin.configResolved({ command: "serve", server: {} });
646
+
647
+ await expect(
648
+ async () => await plugin.buildStart()
649
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
650
+ `[Error: Expected 'widgetSettings.prefersBorder' to be of type 'boolean' but found 'string' instead.]`
651
+ );
652
+ });
653
+
654
+ test("Should error when widgetSettings.description is not a string", async () => {
655
+ mockReadFile({
656
+ "package.json": JSON.stringify({
657
+ widgetSettings: {
658
+ description: true,
659
+ },
660
+ }),
661
+ "my-component.tsx": `
662
+ const MY_QUERY = gql\`query HelloWorldQuery @tool(name: "test", description: "Test") { helloWorld }\`;
663
+ `,
664
+ });
665
+ vi.spyOn(glob, "glob").mockImplementation(() =>
666
+ Promise.resolve(["my-component.tsx"])
667
+ );
668
+ vi.spyOn(path, "resolve").mockImplementation((_, file) => file);
669
+ vi.spyOn(fs, "writeFileSync");
670
+
671
+ const plugin = ApplicationManifestPlugin();
672
+ plugin.configResolved({ command: "serve", server: {} });
673
+
674
+ await expect(
675
+ async () => await plugin.buildStart()
676
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
677
+ `[Error: Expected 'widgetSettings.description' to be of type 'string' but found 'boolean' instead.]`
678
+ );
679
+ });
680
+
681
+ test("Should error when widgetSettings.domain is not a string", async () => {
682
+ mockReadFile({
683
+ "package.json": JSON.stringify({
684
+ widgetSettings: {
685
+ domain: true,
686
+ },
687
+ }),
688
+ "my-component.tsx": `
689
+ const MY_QUERY = gql\`query HelloWorldQuery @tool(name: "test", description: "Test") { helloWorld }\`;
690
+ `,
691
+ });
692
+ vi.spyOn(glob, "glob").mockImplementation(() =>
693
+ Promise.resolve(["my-component.tsx"])
694
+ );
695
+ vi.spyOn(path, "resolve").mockImplementation((_, file) => file);
696
+ vi.spyOn(fs, "writeFileSync");
697
+
698
+ const plugin = ApplicationManifestPlugin();
699
+ plugin.configResolved({ command: "serve", server: {} });
700
+
701
+ await expect(
702
+ async () => await plugin.buildStart()
703
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
704
+ `[Error: Expected 'widgetSettings.domain' to be of type 'string' but found 'boolean' instead.]`
705
+ );
706
+ });
707
+
708
+ test("Should allow empty widgetSettings value", async () => {
709
+ mockReadFile({
710
+ "package.json": JSON.stringify({
711
+ widgetSettings: {},
712
+ }),
713
+ "my-component.tsx": `
714
+ const MY_QUERY = gql\`query HelloWorldQuery @tool(name: "test", description: "Test") { helloWorld }\`;
715
+ `,
716
+ });
717
+ vi.spyOn(glob, "glob").mockImplementation(() =>
718
+ Promise.resolve(["my-component.tsx"])
719
+ );
720
+ vi.spyOn(path, "resolve").mockImplementation((_, file) => file);
721
+ vi.spyOn(fs, "writeFileSync");
722
+
723
+ const plugin = ApplicationManifestPlugin();
724
+ plugin.configResolved({ command: "serve", server: {}, build: {} });
725
+
726
+ await expect(plugin.buildStart()).resolves.toBeUndefined();
727
+ });
728
+
669
729
  test("Should error when an unknown type is discovered", async () => {
670
- vi.spyOn(fs, "readFileSync").mockImplementation((path) => {
671
- if (path === "package.json") {
672
- return JSON.stringify({});
673
- } else if (path === "my-component.tsx") {
674
- return `
675
- const MY_QUERY = gql\`query HelloWorldQuery @tool(name: "hello-world", description: "hello", extraInputs: [{
676
- name: 3.1
677
- }] ) { helloWorld }\`;
678
- `;
679
- }
730
+ mockReadFile({
731
+ "package.json": JSON.stringify({}),
732
+ "my-component.tsx": `
733
+ const MY_QUERY = gql\`query HelloWorldQuery @tool(name: "hello-world", description: "hello", extraInputs: [{
734
+ name: 3.1
735
+ }] ) { helloWorld }\`;
736
+ `,
680
737
  });
681
738
  vi.spyOn(glob, "glob").mockImplementation(() =>
682
739
  Promise.resolve(["my-component.tsx"])
@@ -695,25 +752,22 @@ describe("buildStart", () => {
695
752
  });
696
753
 
697
754
  test("Should order operations and fragments when generating normalized operation", async () => {
698
- vi.spyOn(fs, "readFileSync").mockImplementation((path) => {
699
- if (path === "package.json") {
700
- return JSON.stringify({});
701
- } else if (path === root + "/my-component.tsx") {
702
- return `
703
- const MY_QUERY = gql\`
704
- fragment A on User { firstName }
705
- fragment B on User { lastName }
706
- query HelloWorldQuery @tool(name: "hello-world", description: "This is an awesome tool!") {
707
- helloWorld {
708
- ...B
709
- ...A
710
- ...C
711
- }
712
-
713
- fragment C on User { middleName }
714
- }\`;
715
- `;
716
- }
755
+ mockReadFile({
756
+ "package.json": JSON.stringify({}),
757
+ [`${root}/my-component.tsx`]: `
758
+ const MY_QUERY = gql\`
759
+ fragment A on User { firstName }
760
+ fragment B on User { lastName }
761
+ query HelloWorldQuery @tool(name: "hello-world", description: "This is an awesome tool!") {
762
+ helloWorld {
763
+ ...B
764
+ ...A
765
+ ...C
766
+ }
767
+
768
+ fragment C on User { middleName }
769
+ }\`;
770
+ `,
717
771
  });
718
772
  vi.spyOn(glob, "glob").mockImplementation(() =>
719
773
  Promise.resolve(["my-component.tsx"])
@@ -792,18 +846,15 @@ describe("buildStart", () => {
792
846
 
793
847
  describe("writeBundle", () => {
794
848
  test("Should use custom entry point when in build mode and provided in package.json", async () => {
795
- vi.spyOn(fs, "readFileSync").mockImplementation((path) => {
796
- if (path === "package.json") {
797
- return JSON.stringify({
798
- entry: {
799
- staging: "http://staging.awesome.com",
800
- },
801
- });
802
- } else if (path === "my-component.tsx") {
803
- return `
804
- const MY_QUERY = gql\`query HelloWorldQuery @tool(name: "hello-world", description: "This is an awesome tool!") { helloWorld }\`;
805
- `;
806
- }
849
+ mockReadFile({
850
+ "package.json": JSON.stringify({
851
+ entry: {
852
+ staging: "http://staging.awesome.com",
853
+ },
854
+ }),
855
+ "my-component.tsx": `
856
+ const MY_QUERY = gql\`query HelloWorldQuery @tool(name: "hello-world", description: "This is an awesome tool!") { helloWorld }\`;
857
+ `,
807
858
  });
808
859
  vi.spyOn(glob, "glob").mockImplementation(() =>
809
860
  Promise.resolve(["my-component.tsx"])
@@ -822,21 +873,18 @@ describe("writeBundle", () => {
822
873
  await plugin.buildStart();
823
874
  await plugin.writeBundle();
824
875
 
825
- let [file, content] = (fs.writeFileSync as unknown as Mock).mock.calls[0];
876
+ let [, content] = (fs.writeFileSync as unknown as Mock).mock.calls[0];
826
877
  let contentObj = JSON.parse(content);
827
878
 
828
879
  expect(contentObj.resource).toBe("http://staging.awesome.com");
829
880
  });
830
881
 
831
882
  test("Should use index.html when in build production and not provided in package.json", async () => {
832
- vi.spyOn(fs, "readFileSync").mockImplementation((path) => {
833
- if (path === "package.json") {
834
- return JSON.stringify({});
835
- } else if (path === "my-component.tsx") {
836
- return `
837
- const MY_QUERY = gql\`query HelloWorldQuery @tool(name: "hello-world", description: "This is an awesome tool!") { helloWorld }\`;
838
- `;
839
- }
883
+ mockReadFile({
884
+ "package.json": JSON.stringify({}),
885
+ "my-component.tsx": `
886
+ const MY_QUERY = gql\`query HelloWorldQuery @tool(name: "hello-world", description: "This is an awesome tool!") { helloWorld }\`;
887
+ `,
840
888
  });
841
889
  vi.spyOn(glob, "glob").mockImplementation(() =>
842
890
  Promise.resolve(["my-component.tsx"])
@@ -855,21 +903,18 @@ describe("writeBundle", () => {
855
903
  await plugin.buildStart();
856
904
  await plugin.writeBundle();
857
905
 
858
- let [file, content] = (fs.writeFileSync as unknown as Mock).mock.calls[0];
906
+ let [, content] = (fs.writeFileSync as unknown as Mock).mock.calls[0];
859
907
  let contentObj = JSON.parse(content);
860
908
 
861
909
  expect(contentObj.resource).toBe("index.html");
862
910
  });
863
911
 
864
912
  test("Should throw an error when in build mode and using a mode that is not production and not provided in package.json", async () => {
865
- vi.spyOn(fs, "readFileSync").mockImplementation((path) => {
866
- if (path === "package.json") {
867
- return JSON.stringify({});
868
- } else if (path === "my-component.tsx") {
869
- return `
870
- const MY_QUERY = gql\`query HelloWorldQuery @tool(name: "hello-world", description: "This is an awesome tool!") { helloWorld }\`;
871
- `;
872
- }
913
+ mockReadFile({
914
+ "package.json": JSON.stringify({}),
915
+ "my-component.tsx": `
916
+ const MY_QUERY = gql\`query HelloWorldQuery @tool(name: "hello-world", description: "This is an awesome tool!") { helloWorld }\`;
917
+ `,
873
918
  });
874
919
  vi.spyOn(glob, "glob").mockImplementation(() =>
875
920
  Promise.resolve(["my-component.tsx"])
@@ -895,14 +940,11 @@ describe("writeBundle", () => {
895
940
  });
896
941
 
897
942
  test("Should always write to both locations when running in build mode", async () => {
898
- vi.spyOn(fs, "readFileSync").mockImplementation((path) => {
899
- if (path === "package.json") {
900
- return JSON.stringify({});
901
- } else if (path === "my-component.tsx") {
902
- return `
903
- const MY_QUERY = gql\`query HelloWorldQuery @tool(name: "hello-world", description: "This is an awesome tool!") { helloWorld }\`;
904
- `;
905
- }
943
+ mockReadFile({
944
+ "package.json": JSON.stringify({}),
945
+ "my-component.tsx": `
946
+ const MY_QUERY = gql\`query HelloWorldQuery @tool(name: "hello-world", description: "This is an awesome tool!") { helloWorld }\`;
947
+ `,
906
948
  });
907
949
  vi.spyOn(glob, "glob").mockImplementation(() =>
908
950
  Promise.resolve(["my-component.tsx"])
@@ -927,18 +969,15 @@ describe("writeBundle", () => {
927
969
 
928
970
  describe("configureServer", () => {
929
971
  test("Should write to manifest file when package.json or file is updated", async () => {
930
- vi.spyOn(fs, "readFileSync").mockImplementation((path) => {
931
- if (path === "package.json") {
932
- return JSON.stringify({});
933
- } else if (path === "my-component.tsx") {
934
- return `
935
- const MY_QUERY = gql\`query HelloWorldQuery($name: string!) @tool(name: "hello-world", description: "This is an awesome tool!", extraInputs: [{
936
- name: "doStuff",
937
- type: "boolean",
938
- description: "Should we do stuff?"
939
- }]) { helloWorld(name: $name) }\`;
940
- `;
941
- }
972
+ mockReadFile({
973
+ "package.json": JSON.stringify({}),
974
+ "my-component.tsx": `
975
+ const MY_QUERY = gql\`query HelloWorldQuery($name: string!) @tool(name: "hello-world", description: "This is an awesome tool!", extraInputs: [{
976
+ name: "doStuff",
977
+ type: "boolean",
978
+ description: "Should we do stuff?"
979
+ }]) { helloWorld(name: $name) }\`;
980
+ `,
942
981
  });
943
982
  vi.spyOn(glob, "glob").mockImplementation(() =>
944
983
  Promise.resolve(["my-component.tsx"])
@@ -946,16 +985,18 @@ describe("configureServer", () => {
946
985
  vi.spyOn(path, "resolve").mockImplementation((_, file) => file);
947
986
  vi.spyOn(fs, "writeFileSync");
948
987
 
988
+ let _callbacks: Function[] = [];
989
+
949
990
  const server = {
950
991
  watcher: {
951
992
  init: () => {
952
- this._callbacks = [];
993
+ _callbacks = [];
953
994
  },
954
995
  on: (_event: string, callback: Function) => {
955
- this._callbacks.push(callback);
996
+ _callbacks.push(callback);
956
997
  },
957
- trigger: async (file) => {
958
- for (const callback of this._callbacks) {
998
+ trigger: async (file: string) => {
999
+ for (const callback of _callbacks) {
959
1000
  await callback(file);
960
1001
  }
961
1002
  },
@@ -970,10 +1011,28 @@ describe("configureServer", () => {
970
1011
  build: { outDir: "/dist" },
971
1012
  });
972
1013
  await plugin.buildStart();
973
- await plugin.configureServer(server);
1014
+ plugin.configureServer(server);
974
1015
  await server.watcher.trigger("package.json");
975
1016
  await server.watcher.trigger("my-component.tsx");
976
1017
 
977
1018
  expect(fs.writeFileSync).toBeCalledTimes(6);
978
1019
  });
979
1020
  });
1021
+
1022
+ type FilePath = string;
1023
+
1024
+ function mockReadFile(mocks: Record<FilePath, string | (() => string)>) {
1025
+ vi.spyOn(fs, "readFileSync").mockImplementation((path) => {
1026
+ const mock = mocks[path.toString()];
1027
+
1028
+ if (!mock) {
1029
+ throw new Error(`No matched mock for path '${path}'`);
1030
+ }
1031
+
1032
+ if (typeof mock === "function") {
1033
+ return mock();
1034
+ }
1035
+
1036
+ return mock;
1037
+ });
1038
+ }