@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.
- package/.github/workflows/pr.yaml +52 -3
- package/.github/workflows/prep-release.yml +38 -0
- package/.github/workflows/release.yaml +8 -4
- package/.github/workflows/verify-changeset.yml +58 -0
- package/CHANGELOG.md +25 -0
- package/dist/core/ApolloClient.d.ts +3 -2
- package/dist/core/ApolloClient.d.ts.map +1 -0
- package/dist/core/ApolloClient.js +65 -0
- package/dist/core/ApolloClient.js.map +1 -0
- package/dist/index.d.ts +18 -17
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -278
- package/dist/index.js.map +1 -0
- package/dist/link/ToolCallLink.d.ts +1 -0
- package/dist/link/ToolCallLink.d.ts.map +1 -0
- package/dist/link/ToolCallLink.js +39 -0
- package/dist/link/ToolCallLink.js.map +1 -0
- package/dist/react/ApolloProvider.d.ts +4 -3
- package/dist/react/ApolloProvider.d.ts.map +1 -0
- package/dist/react/ApolloProvider.js +30 -0
- package/dist/react/ApolloProvider.js.map +1 -0
- package/dist/react/context/ToolUseContext.d.ts +4 -3
- package/dist/react/context/ToolUseContext.d.ts.map +1 -0
- package/dist/react/context/ToolUseContext.js +11 -0
- package/dist/react/context/ToolUseContext.js.map +1 -0
- package/dist/react/hooks/useCallTool.d.ts +4 -0
- package/dist/react/hooks/useCallTool.d.ts.map +1 -0
- package/dist/react/hooks/useCallTool.js +5 -0
- package/dist/react/hooks/useCallTool.js.map +1 -0
- package/dist/react/hooks/useOpenAiGlobal.d.ts +2 -1
- package/dist/react/hooks/useOpenAiGlobal.d.ts.map +1 -0
- package/dist/react/hooks/useOpenAiGlobal.js +20 -0
- package/dist/react/hooks/useOpenAiGlobal.js.map +1 -0
- package/dist/react/hooks/useOpenExternal.d.ts +1 -0
- package/dist/react/hooks/useOpenExternal.d.ts.map +1 -0
- package/dist/react/hooks/useOpenExternal.js +5 -0
- package/dist/react/hooks/useOpenExternal.js.map +1 -0
- package/dist/react/hooks/useRequestDisplayMode.d.ts +2 -1
- package/dist/react/hooks/useRequestDisplayMode.d.ts.map +1 -0
- package/dist/react/hooks/useRequestDisplayMode.js +6 -0
- package/dist/react/hooks/useRequestDisplayMode.js.map +1 -0
- package/dist/react/hooks/useSendFollowUpMessage.d.ts +1 -0
- package/dist/react/hooks/useSendFollowUpMessage.d.ts.map +1 -0
- package/dist/react/hooks/useSendFollowUpMessage.js +8 -0
- package/dist/react/hooks/useSendFollowUpMessage.js.map +1 -0
- package/dist/react/hooks/useToolEffect.d.ts +1 -0
- package/dist/react/hooks/useToolEffect.d.ts.map +1 -0
- package/dist/react/hooks/useToolEffect.js +28 -0
- package/dist/react/hooks/useToolEffect.js.map +1 -0
- package/dist/react/hooks/useToolInput.d.ts +1 -0
- package/dist/react/hooks/useToolInput.d.ts.map +1 -0
- package/dist/react/hooks/useToolInput.js +6 -0
- package/dist/react/hooks/useToolInput.js.map +1 -0
- package/dist/react/hooks/useToolName.d.ts +1 -0
- package/dist/react/hooks/useToolName.d.ts.map +1 -0
- package/dist/react/hooks/useToolName.js +6 -0
- package/dist/react/hooks/useToolName.js.map +1 -0
- package/dist/react/hooks/useToolOutput.d.ts +2 -1
- package/dist/react/hooks/useToolOutput.d.ts.map +1 -0
- package/dist/react/hooks/useToolOutput.js +5 -0
- package/dist/react/hooks/useToolOutput.js.map +1 -0
- package/dist/react/hooks/useToolResponseMetadata.d.ts +2 -1
- package/dist/react/hooks/useToolResponseMetadata.d.ts.map +1 -0
- package/dist/react/hooks/useToolResponseMetadata.js +5 -0
- package/dist/react/hooks/useToolResponseMetadata.js.map +1 -0
- package/dist/react/hooks/useWidgetState.d.ts +3 -2
- package/dist/react/hooks/useWidgetState.d.ts.map +1 -0
- package/dist/react/hooks/useWidgetState.js +27 -0
- package/dist/react/hooks/useWidgetState.js.map +1 -0
- package/dist/types/application-manifest.d.ts +7 -0
- package/dist/types/application-manifest.d.ts.map +1 -0
- package/dist/types/application-manifest.js +2 -0
- package/dist/types/application-manifest.js.map +1 -0
- package/dist/types/openai.d.ts +1 -0
- package/dist/types/openai.d.ts.map +1 -0
- package/dist/types/openai.js +6 -0
- package/dist/types/openai.js.map +1 -0
- package/dist/vite/absolute_asset_imports_plugin.d.ts +1 -0
- package/dist/vite/absolute_asset_imports_plugin.d.ts.map +1 -0
- package/dist/vite/absolute_asset_imports_plugin.js +17 -0
- package/dist/vite/absolute_asset_imports_plugin.js.map +1 -0
- package/dist/vite/application_manifest_plugin.d.ts +2 -1
- package/dist/vite/application_manifest_plugin.d.ts.map +1 -0
- package/dist/vite/application_manifest_plugin.js +274 -0
- package/dist/vite/application_manifest_plugin.js.map +1 -0
- package/dist/vite/index.d.ts +3 -2
- package/dist/vite/index.d.ts.map +1 -0
- package/dist/vite/index.js +3 -307
- package/dist/vite/index.js.map +1 -0
- package/knope.toml +63 -0
- package/package.json +15 -8
- package/src/core/ApolloClient.ts +10 -5
- package/src/core/__tests__/ApolloClient.test.ts +12 -9
- package/src/index.ts +17 -17
- package/src/react/ApolloProvider.tsx +4 -3
- package/src/react/__tests__/ApolloProvider.test.tsx +3 -3
- package/src/react/context/ToolUseContext.tsx +2 -1
- package/src/react/hooks/__tests__/useCallTool.test.ts +1 -1
- package/src/react/hooks/__tests__/useOpenAiGlobal.test.ts +6 -6
- package/src/react/hooks/__tests__/useOpenExternal.test.tsx +2 -2
- package/src/react/hooks/__tests__/useRequestDisplayMode.test.ts +2 -2
- package/src/react/hooks/__tests__/useSendFollowUpMessage.test.ts +1 -1
- package/src/react/hooks/__tests__/useToolEffect.test.tsx +2 -2
- package/src/react/hooks/__tests__/useToolInput.test.ts +1 -1
- package/src/react/hooks/__tests__/useToolName.test.ts +1 -1
- package/src/react/hooks/__tests__/useToolOutput.test.tsx +2 -2
- package/src/react/hooks/__tests__/useToolResponseMetadata.test.tsx +2 -2
- package/src/react/hooks/__tests__/useWidgetState.test.tsx +2 -2
- package/src/react/hooks/useOpenAiGlobal.ts +2 -5
- package/src/react/hooks/useOpenExternal.ts +1 -1
- package/src/react/hooks/useRequestDisplayMode.ts +1 -1
- package/src/react/hooks/useToolEffect.tsx +3 -3
- package/src/react/hooks/useToolInput.ts +1 -1
- package/src/react/hooks/useToolName.ts +1 -1
- package/src/react/hooks/useToolOutput.ts +1 -1
- package/src/react/hooks/useToolResponseMetadata.ts +1 -1
- package/src/react/hooks/useWidgetState.ts +4 -3
- package/src/testing/internal/index.ts +2 -2
- package/src/testing/internal/matchers/index.ts +1 -1
- package/src/testing/internal/matchers/toRerender.ts +2 -2
- package/src/testing/internal/matchers/{index.d.ts → types.ts} +1 -1
- package/src/testing/internal/openai/dispatchStateChange.ts +1 -1
- package/src/testing/internal/openai/stubOpenAiGlobals.ts +6 -2
- package/src/types/application-manifest.ts +7 -0
- package/src/vite/__tests__/absolute_asset_imports_plugin.test.ts +2 -2
- package/src/vite/__tests__/application_manifest_plugin.test.ts +299 -240
- package/src/vite/application_manifest_plugin.ts +160 -96
- package/src/vite/index.ts +2 -2
- package/tsconfig.base.build.json +13 -0
- package/tsconfig.base.json +21 -0
- package/tsconfig.config.json +9 -0
- package/tsconfig.json +10 -0
- package/tsconfig.src.build.json +14 -0
- package/tsconfig.src.json +17 -0
- package/tsconfig.test.json +20 -0
- package/tsconfig.vite.build.json +6 -0
- package/tsconfig.vite.json +16 -0
- package/scripts/build-vite.mjs +0 -18
- package/scripts/build.mjs +0 -7
- package/scripts/shared.mjs +0 -9
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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")
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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 [
|
|
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
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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 [
|
|
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
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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 [
|
|
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
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
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
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
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
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
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
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
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
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
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
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
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 [
|
|
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
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
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 [
|
|
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
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
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
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
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
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
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
|
-
|
|
993
|
+
_callbacks = [];
|
|
953
994
|
},
|
|
954
995
|
on: (_event: string, callback: Function) => {
|
|
955
|
-
|
|
996
|
+
_callbacks.push(callback);
|
|
956
997
|
},
|
|
957
|
-
trigger: async (file) => {
|
|
958
|
-
for (const callback of
|
|
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
|
-
|
|
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
|
+
}
|