Skip to content

Commit ec7867a

Browse files
authored
Add writer schema request copy and response import tools (#208)
* Add writer schema request copy and response import * Add copy buttons for Chrome flag URLs
1 parent 2f2d272 commit ec7867a

4 files changed

Lines changed: 475 additions & 56 deletions

File tree

apps/web/src/tests/jest/writer-schema-page.test.js

Lines changed: 149 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,19 @@ import {
1212

1313
describe('writer schema prototype page', () => {
1414
let dom;
15+
let navigatorObj;
1516

1617
beforeEach(() => {
1718
dom = new JSDOM(
1819
`<!doctype html><html><body>
1920
<div class="header" data-role="theme-toggle-host"><div class="pageheading">AnyWayData</div></div>
2021
<main id="writer-schema-page-root">
2122
<p id="writer-schema-support-status">Checking Writer API availability...</p>
23+
<button
24+
id="writer-schema-copy-flag"
25+
type="button"
26+
data-copy-text="chrome://flags/#writer-api-for-gemini-nano"
27+
></button>
2228
<textarea id="writer-schema-prompt"></textarea>
2329
<button id="writer-schema-example-prompt" type="button">Load example prompt</button>
2430
<button id="writer-schema-generate" type="button">Generate schema from prompt</button>
@@ -28,11 +34,20 @@ describe('writer schema prototype page', () => {
2834
<pre id="writer-schema-raw-output">No raw Writer response yet.</pre>
2935
<pre id="writer-schema-error-output">No errors yet.</pre>
3036
<ol id="writer-schema-progress-output"><li>No generation activity yet.</li></ol>
37+
<button id="writer-schema-copy-request" type="button"></button>
38+
<button id="writer-schema-copy-prompt" type="button"></button>
39+
<textarea id="writer-schema-process-response"></textarea>
40+
<button id="writer-schema-create-schema" type="button">Create Schema</button>
3141
<div id="writer-schema-editor-root"></div>
3242
</main>
3343
</body></html>`,
3444
{ url: 'https://example.test/writer-schema.html' }
3545
);
46+
navigatorObj = {
47+
clipboard: {
48+
writeText: jest.fn(async () => {}),
49+
},
50+
};
3651
});
3752

3853
afterEach(() => {
@@ -170,6 +185,57 @@ describe('writer schema prototype page', () => {
170185
expect(writer.destroy).toHaveBeenCalledTimes(1);
171186
});
172187

188+
test('runWriterSchemaGeneration accumulates streaming Writer chunks into structured output', async () => {
189+
const writer = {
190+
destroy: jest.fn(),
191+
writeStreaming: jest.fn(async function* () {
192+
yield '{"schemaFields":[';
193+
yield '{"name":"Book Title","sourceType":"domain","command":"commerce.productName"},';
194+
yield '{"name":"Genre","sourceType":"enum","values":["Fiction","Non-fiction"]}';
195+
yield ']}';
196+
}),
197+
};
198+
const WriterCtor = {
199+
create: jest.fn(async () => writer),
200+
};
201+
202+
const result = await runWriterSchemaGeneration({
203+
WriterCtor,
204+
promptText: DEFAULT_PROMPT,
205+
domainCommands: ['commerce.productName', 'person.fullName'],
206+
sampleSchemaText: 'Name\nperson.fullName',
207+
onStatus: jest.fn(),
208+
});
209+
210+
expect(WriterCtor.create).toHaveBeenCalledTimes(1);
211+
expect(writer.writeStreaming).toHaveBeenCalledWith(
212+
DEFAULT_PROMPT,
213+
expect.objectContaining({
214+
context: expect.any(String),
215+
expectedInputLanguages: ['en'],
216+
expectedContextLanguages: ['en'],
217+
outputLanguage: 'en',
218+
})
219+
);
220+
expect(result.parsedPayload.schemaFields).toHaveLength(2);
221+
expect(result.requestDetails).toMatchObject({
222+
promptText: DEFAULT_PROMPT,
223+
taskContext: expect.any(String),
224+
writeOptions: expect.objectContaining({
225+
outputLanguage: 'en',
226+
}),
227+
createOptions: expect.objectContaining({
228+
sharedContext: expect.any(String),
229+
}),
230+
});
231+
expect(result.schemaRows).toMatchObject([
232+
{ name: 'Book Title', sourceType: 'domain', command: 'commerce.productName' },
233+
{ name: 'Genre', sourceType: 'enum', value: '"Fiction","Non-fiction"' },
234+
]);
235+
expect(result.normalizationErrors).toEqual([]);
236+
expect(writer.destroy).toHaveBeenCalledTimes(1);
237+
});
238+
173239
test('bootstrap warns when Writer API support is unavailable', async () => {
174240
const schemaComponent = {
175241
destroy: jest.fn(),
@@ -184,6 +250,7 @@ describe('writer schema prototype page', () => {
184250
await bootstrapWriterSchemaPage({
185251
documentObj: dom.window.document,
186252
WriterCtor: null,
253+
navigatorObj,
187254
createThemeToggleComponentFn: () => ({ destroy: jest.fn() }),
188255
createSharedSchemaDefinitionComponentFn: () => schemaComponent,
189256
});
@@ -194,6 +261,34 @@ describe('writer schema prototype page', () => {
194261
expect(dom.window.document.getElementById('writer-schema-prompt').value).toBe(DEFAULT_PROMPT);
195262
});
196263

264+
test('bootstrap copies setup flag URLs to the clipboard', async () => {
265+
const schemaComponent = {
266+
destroy: jest.fn(),
267+
replaceRows: jest.fn(),
268+
setTextMode: jest.fn(),
269+
render: jest.fn(),
270+
syncTextFromRows: jest.fn(),
271+
validateRows: jest.fn(() => ({ errors: [] })),
272+
getSchemaText: jest.fn(() => ''),
273+
};
274+
275+
await bootstrapWriterSchemaPage({
276+
documentObj: dom.window.document,
277+
WriterCtor: null,
278+
navigatorObj,
279+
createThemeToggleComponentFn: () => ({ destroy: jest.fn() }),
280+
createSharedSchemaDefinitionComponentFn: () => schemaComponent,
281+
});
282+
283+
dom.window.document.getElementById('writer-schema-copy-flag').click();
284+
await new Promise((resolve) => setTimeout(resolve, 0));
285+
286+
expect(navigatorObj.clipboard.writeText).toHaveBeenCalledWith('chrome://flags/#writer-api-for-gemini-nano');
287+
expect(dom.window.document.getElementById('writer-schema-generation-status').textContent).toContain(
288+
'Copied the Chrome flags URL to the clipboard.'
289+
);
290+
});
291+
197292
test('bootstrap generates rows and populates the shared schema component', async () => {
198293
const schemaComponent = {
199294
destroy: jest.fn(),
@@ -219,6 +314,7 @@ describe('writer schema prototype page', () => {
219314
const page = await bootstrapWriterSchemaPage({
220315
documentObj: dom.window.document,
221316
WriterCtor,
317+
navigatorObj,
222318
createThemeToggleComponentFn: () => ({ destroy: jest.fn() }),
223319
createSharedSchemaDefinitionComponentFn: () => schemaComponent,
224320
});
@@ -233,7 +329,7 @@ describe('writer schema prototype page', () => {
233329
);
234330
expect(schemaComponent.syncTextFromRows).toHaveBeenCalledTimes(1);
235331
expect(dom.window.document.getElementById('writer-schema-generation-status').textContent).toContain(
236-
'Generated 1 schema fields'
332+
'Processed Writer API output into 1 schema fields.'
237333
);
238334
expect(dom.window.document.getElementById('writer-schema-json-output').textContent).toContain('Book Title');
239335
expect(dom.window.document.getElementById('writer-schema-request-output').textContent).toContain(
@@ -244,7 +340,19 @@ describe('writer schema prototype page', () => {
244340
);
245341
expect(dom.window.document.getElementById('writer-schema-error-output').textContent).toBe('No errors yet.');
246342
expect(dom.window.document.getElementById('writer-schema-progress-output').textContent).toContain(
247-
'Schema generation completed successfully.'
343+
'Processed Writer API output successfully.'
344+
);
345+
346+
await page.copyLatestRequestJson();
347+
await page.copyLatestRequestAsPrompt();
348+
349+
expect(navigatorObj.clipboard.writeText).toHaveBeenNthCalledWith(
350+
1,
351+
expect.stringContaining('"promptText": "Create 10 fields that represent the inventory of a bookshop"')
352+
);
353+
expect(navigatorObj.clipboard.writeText).toHaveBeenNthCalledWith(
354+
2,
355+
expect.stringContaining('Generate an AnyWayData schema response using the following request details.')
248356
);
249357
});
250358

@@ -272,6 +380,7 @@ describe('writer schema prototype page', () => {
272380
const page = await bootstrapWriterSchemaPage({
273381
documentObj: dom.window.document,
274382
WriterCtor,
383+
navigatorObj,
275384
createThemeToggleComponentFn: () => ({ destroy: jest.fn() }),
276385
createSharedSchemaDefinitionComponentFn: () => schemaComponent,
277386
});
@@ -325,6 +434,7 @@ describe('writer schema prototype page', () => {
325434
const page = await bootstrapWriterSchemaPage({
326435
documentObj: dom.window.document,
327436
WriterCtor,
437+
navigatorObj,
328438
createThemeToggleComponentFn: () => ({ destroy: jest.fn() }),
329439
createSharedSchemaDefinitionComponentFn: () => schemaComponent,
330440
});
@@ -341,7 +451,43 @@ describe('writer schema prototype page', () => {
341451
'unsupported command "commerce.publisher"'
342452
);
343453
expect(dom.window.document.getElementById('writer-schema-progress-output').textContent).toContain(
344-
'Completed with partial recovery.'
454+
'Processed Writer API output with partial recovery.'
455+
);
456+
});
457+
458+
test('bootstrap processes a pasted AI response into schema rows', async () => {
459+
const schemaComponent = {
460+
destroy: jest.fn(),
461+
replaceRows: jest.fn(),
462+
setTextMode: jest.fn(),
463+
render: jest.fn(),
464+
syncTextFromRows: jest.fn(),
465+
validateRows: jest.fn(() => ({ errors: [] })),
466+
getSchemaText: jest.fn(() => 'Book Title\ncommerce.productName()'),
467+
};
468+
469+
const page = await bootstrapWriterSchemaPage({
470+
documentObj: dom.window.document,
471+
WriterCtor: null,
472+
navigatorObj,
473+
createThemeToggleComponentFn: () => ({ destroy: jest.fn() }),
474+
createSharedSchemaDefinitionComponentFn: () => schemaComponent,
475+
});
476+
477+
dom.window.document.getElementById('writer-schema-process-response').value = JSON.stringify({
478+
schemaFields: [{ name: 'Book Title', sourceType: 'domain', command: 'commerce.productName' }],
479+
});
480+
481+
await page.createSchemaFromResponse();
482+
483+
expect(schemaComponent.replaceRows).toHaveBeenCalledWith(
484+
expect.arrayContaining([expect.objectContaining({ name: 'Book Title', command: 'commerce.productName' })])
485+
);
486+
expect(dom.window.document.getElementById('writer-schema-generation-status').textContent).toContain(
487+
'Processed pasted AI response into 1 schema fields.'
488+
);
489+
expect(dom.window.document.getElementById('writer-schema-raw-output').textContent).toContain(
490+
'commerce.productName'
345491
);
346492
});
347493
});

0 commit comments

Comments
 (0)