@auto-engineer/narrative 0.11.20 → 0.12.1

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 (69) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +24 -0
  3. package/dist/src/fluent-builder.d.ts.map +1 -1
  4. package/dist/src/fluent-builder.js +9 -12
  5. package/dist/src/fluent-builder.js.map +1 -1
  6. package/dist/src/getNarratives.specs.js +43 -27
  7. package/dist/src/getNarratives.specs.js.map +1 -1
  8. package/dist/src/id/addAutoIds.specs.js +11 -25
  9. package/dist/src/id/addAutoIds.specs.js.map +1 -1
  10. package/dist/src/id/hasAllIds.specs.js +12 -12
  11. package/dist/src/index.d.ts +2 -1
  12. package/dist/src/index.d.ts.map +1 -1
  13. package/dist/src/index.js +1 -1
  14. package/dist/src/index.js.map +1 -1
  15. package/dist/src/model-to-narrative.specs.js +110 -108
  16. package/dist/src/model-to-narrative.specs.js.map +1 -1
  17. package/dist/src/narrative-context.d.ts +4 -2
  18. package/dist/src/narrative-context.d.ts.map +1 -1
  19. package/dist/src/narrative-context.js +98 -95
  20. package/dist/src/narrative-context.js.map +1 -1
  21. package/dist/src/narrative.d.ts +7 -1
  22. package/dist/src/narrative.d.ts.map +1 -1
  23. package/dist/src/narrative.js +27 -6
  24. package/dist/src/narrative.js.map +1 -1
  25. package/dist/src/samples/items.narrative.js +7 -7
  26. package/dist/src/samples/items.narrative.js.map +1 -1
  27. package/dist/src/samples/place-order.narrative.js +4 -4
  28. package/dist/src/samples/place-order.narrative.js.map +1 -1
  29. package/dist/src/samples/questionnaires.narrative.js +16 -18
  30. package/dist/src/samples/questionnaires.narrative.js.map +1 -1
  31. package/dist/src/samples/seasonal-assistant.schema.json +37 -31
  32. package/dist/src/schema.d.ts +91 -462
  33. package/dist/src/schema.d.ts.map +1 -1
  34. package/dist/src/schema.js +22 -24
  35. package/dist/src/schema.js.map +1 -1
  36. package/dist/src/slice-builder.js +2 -2
  37. package/dist/src/slice-builder.js.map +1 -1
  38. package/dist/src/transformers/model-to-narrative/generators/flow.d.ts.map +1 -1
  39. package/dist/src/transformers/model-to-narrative/generators/flow.js +34 -10
  40. package/dist/src/transformers/model-to-narrative/generators/flow.js.map +1 -1
  41. package/dist/src/transformers/model-to-narrative/generators/imports.d.ts +1 -1
  42. package/dist/src/transformers/model-to-narrative/generators/imports.d.ts.map +1 -1
  43. package/dist/src/transformers/model-to-narrative/generators/imports.js +2 -1
  44. package/dist/src/transformers/model-to-narrative/generators/imports.js.map +1 -1
  45. package/dist/src/transformers/narrative-to-model/index.d.ts.map +1 -1
  46. package/dist/src/transformers/narrative-to-model/index.js +4 -8
  47. package/dist/src/transformers/narrative-to-model/index.js.map +1 -1
  48. package/dist/src/transformers/narrative-to-model/type-inference.specs.js +3 -3
  49. package/dist/src/transformers/narrative-to-model/type-inference.specs.js.map +1 -1
  50. package/dist/tsconfig.tsbuildinfo +1 -1
  51. package/package.json +5 -5
  52. package/src/fluent-builder.ts +9 -12
  53. package/src/getNarratives.specs.ts +43 -28
  54. package/src/id/addAutoIds.specs.ts +11 -25
  55. package/src/id/hasAllIds.specs.ts +12 -12
  56. package/src/index.ts +2 -1
  57. package/src/model-to-narrative.specs.ts +110 -108
  58. package/src/narrative-context.ts +103 -101
  59. package/src/narrative.ts +44 -6
  60. package/src/samples/items.narrative.ts +7 -7
  61. package/src/samples/place-order.narrative.ts +4 -4
  62. package/src/samples/questionnaires.narrative.ts +17 -18
  63. package/src/samples/seasonal-assistant.schema.json +37 -31
  64. package/src/schema.ts +33 -24
  65. package/src/slice-builder.ts +2 -2
  66. package/src/transformers/model-to-narrative/generators/flow.ts +53 -23
  67. package/src/transformers/model-to-narrative/generators/imports.ts +2 -1
  68. package/src/transformers/narrative-to-model/index.ts +4 -7
  69. package/src/transformers/narrative-to-model/type-inference.specs.ts +3 -3
@@ -1,7 +1,8 @@
1
1
  import createDebug from 'debug';
2
2
  import type { DataSinkItem, DataSourceItem, DataItem, DataSink, DataSource } from './types';
3
3
  import type { GivenTypeInfo } from './loader/ts-utils';
4
- import { Narrative, Slice, Example } from './index';
4
+ import { Narrative, Slice, Example, CommandSlice, QuerySlice, ExperienceSlice } from './index';
5
+ import type { ClientSpecNode } from './schema';
5
6
 
6
7
  function normalizeContext(context?: Partial<Record<string, string>>): Record<string, string> | undefined {
7
8
  if (!context) return undefined;
@@ -25,6 +26,7 @@ interface NarrativeContext {
25
26
  currentSpecIndex: number | null;
26
27
  currentRuleIndex: number | null;
27
28
  currentExampleIndex: number | null;
29
+ clientSpecStack: ClientSpecNode[];
28
30
  }
29
31
 
30
32
  let context: NarrativeContext | null = null;
@@ -65,6 +67,7 @@ export function startNarrative(name: string, id?: string): Narrative {
65
67
  currentSpecIndex: null,
66
68
  currentRuleIndex: null,
67
69
  currentExampleIndex: null,
70
+ clientSpecStack: [],
68
71
  };
69
72
  return narrative;
70
73
  }
@@ -88,13 +91,6 @@ export function addSlice(slice: Slice): void {
88
91
  context.currentSliceIndex = context.narrative.slices.length - 1;
89
92
  }
90
93
 
91
- function getClientSpecs(slice: Slice): { name: string; rules: string[] } | undefined {
92
- if (slice.type === 'command' || slice.type === 'query') {
93
- return slice.client.specs;
94
- }
95
- return undefined;
96
- }
97
-
98
94
  function getServerSpecs(
99
95
  slice: Slice,
100
96
  ): { name: string; rules: { id?: string; description: string; examples: Example[] }[] } | undefined {
@@ -104,21 +100,6 @@ function getServerSpecs(
104
100
  return undefined;
105
101
  }
106
102
 
107
- function getCurrentSpecs(
108
- slice: Slice,
109
- ): { name: string; rules: string[] | { id?: string; description: string; examples: Example[] }[] } | undefined {
110
- if (!context?.currentSpecTarget) return undefined;
111
-
112
- switch (context.currentSpecTarget) {
113
- case 'client':
114
- return getClientSpecs(slice);
115
- case 'server':
116
- return getServerSpecs(slice);
117
- default:
118
- return undefined;
119
- }
120
- }
121
-
122
103
  function getCurrentExample(slice: Slice): Example | undefined {
123
104
  if (
124
105
  !context ||
@@ -129,39 +110,40 @@ function getCurrentExample(slice: Slice): Example | undefined {
129
110
  return undefined;
130
111
  }
131
112
 
132
- const spec = getCurrentSpecs(slice);
113
+ const spec = getServerSpecs(slice);
133
114
  if (!spec) return undefined;
134
115
 
135
- // Only server specs have object rules with examples
136
- if (context.currentSpecTarget === 'server') {
137
- const objectRules = spec.rules as { id?: string; description: string; examples: Example[] }[];
138
- return objectRules[context.currentRuleIndex]?.examples[context.currentExampleIndex];
139
- }
140
-
141
- return undefined;
116
+ const objectRules = spec.rules as { id?: string; description: string; examples: Example[] }[];
117
+ return objectRules[context.currentRuleIndex]?.examples[context.currentExampleIndex];
142
118
  }
143
119
 
144
- export function startClientBlock(slice: Slice, description: string = ''): void {
120
+ export function startClientBlock(slice: Slice): void {
145
121
  if (!context) throw new Error('No active flow context');
146
122
 
147
- if (slice.type === 'command' || slice.type === 'query') {
148
- slice.client = {
149
- description,
150
- specs: undefined,
151
- };
152
- context.currentSpecTarget = 'client';
153
- } else if (slice.type === 'experience') {
123
+ if (slice.type === 'command' || slice.type === 'query' || slice.type === 'experience') {
154
124
  slice.client = {
155
- description: description || undefined,
156
- specs: undefined,
125
+ specs: [],
157
126
  };
158
127
  context.currentSpecTarget = 'client';
128
+ context.clientSpecStack = [];
159
129
  }
160
130
  }
161
131
 
162
132
  export function endClientBlock(): void {
163
133
  if (context) {
134
+ if (context.clientSpecStack.length > 0) {
135
+ const unclosedCount = context.clientSpecStack.length;
136
+ const unclosedTitles = context.clientSpecStack
137
+ .map((n) => {
138
+ if (n.title !== undefined && n.title !== '') return n.title;
139
+ if (n.id !== undefined && n.id !== '') return n.id;
140
+ return 'unnamed';
141
+ })
142
+ .join(', ');
143
+ throw new Error(`${unclosedCount} unclosed describe block(s): ${unclosedTitles}`);
144
+ }
164
145
  context.currentSpecTarget = null;
146
+ context.clientSpecStack = [];
165
147
  }
166
148
  }
167
149
 
@@ -197,23 +179,6 @@ export function endServerBlock(): void {
197
179
  }
198
180
  }
199
181
 
200
- function initializeClientSpecs(slice: Slice, description: string): void {
201
- if (slice.type === 'command' || slice.type === 'query') {
202
- slice.client.specs = {
203
- name: description,
204
- rules: [],
205
- };
206
- } else if (slice.type === 'experience') {
207
- if (slice.client == null) {
208
- slice.client = { description: '', specs: undefined };
209
- }
210
- slice.client.specs = {
211
- name: description,
212
- rules: [],
213
- };
214
- }
215
- }
216
-
217
182
  function initializeServerSpecs(slice: Slice, description: string): void {
218
183
  if ('server' in slice && slice.server != null) {
219
184
  slice.server.specs = {
@@ -229,31 +194,74 @@ export function pushSpec(description: string): void {
229
194
  const slice = getCurrentSlice();
230
195
  if (!slice) throw new Error('No active slice');
231
196
 
232
- switch (context.currentSpecTarget) {
233
- case 'client':
234
- initializeClientSpecs(slice, description);
235
- break;
236
- case 'server':
237
- initializeServerSpecs(slice, description);
238
- break;
239
- }
240
- }
241
-
242
- export function recordShouldBlock(description?: string): void {
243
- if (typeof description === 'string' && context?.currentSpecTarget === 'client') {
244
- const slice = getCurrentSlice();
245
- if (slice && (slice.type === 'command' || slice.type === 'query' || slice.type === 'experience')) {
246
- if (!slice.client.specs) {
247
- slice.client.specs = {
248
- name: '',
249
- rules: [],
250
- };
251
- }
252
- slice.client.specs.rules.push(description);
197
+ if (context.currentSpecTarget === 'server') {
198
+ initializeServerSpecs(slice, description);
199
+ }
200
+ }
201
+
202
+ export function pushDescribe(id?: string, title?: string): void {
203
+ if (!context) throw new Error('No active narrative context');
204
+
205
+ const describeNode: ClientSpecNode = {
206
+ type: 'describe',
207
+ ...(id !== undefined && id !== '' ? { id } : {}),
208
+ ...(title !== undefined && title !== '' ? { title } : {}),
209
+ children: [],
210
+ };
211
+
212
+ context.clientSpecStack.push(describeNode);
213
+ }
214
+
215
+ function validateSliceSupportsClientSpecs(slice: Slice): void {
216
+ if (slice.type !== 'command' && slice.type !== 'query' && slice.type !== 'experience') {
217
+ throw new Error('Client specs can only be added to command, query, or experience slices');
218
+ }
219
+ }
220
+
221
+ function addNodeToParentOrRoot(node: ClientSpecNode, slice: CommandSlice | QuerySlice | ExperienceSlice): void {
222
+ if (!context) return;
223
+
224
+ if (context.clientSpecStack.length === 0) {
225
+ slice.client.specs.push(node);
226
+ } else {
227
+ const parent = context.clientSpecStack[context.clientSpecStack.length - 1];
228
+ if (parent.type === 'describe') {
229
+ if (!parent.children) parent.children = [];
230
+ parent.children.push(node);
253
231
  }
254
232
  }
255
233
  }
256
234
 
235
+ export function popDescribe(): void {
236
+ if (!context) throw new Error('No active narrative context');
237
+ if (context.clientSpecStack.length === 0) throw new Error('No active describe block');
238
+
239
+ const completedDescribe = context.clientSpecStack.pop();
240
+ if (!completedDescribe) return;
241
+
242
+ const slice = getCurrentSlice();
243
+ if (!slice) throw new Error('No active slice');
244
+
245
+ validateSliceSupportsClientSpecs(slice);
246
+ addNodeToParentOrRoot(completedDescribe, slice as CommandSlice | QuerySlice | ExperienceSlice);
247
+ }
248
+
249
+ export function recordIt(id?: string, title: string = ''): void {
250
+ if (!context) throw new Error('No active narrative context');
251
+
252
+ const itNode: ClientSpecNode = {
253
+ type: 'it',
254
+ ...(id !== undefined && id !== '' ? { id } : {}),
255
+ title,
256
+ };
257
+
258
+ const slice = getCurrentSlice();
259
+ if (!slice) throw new Error('No active slice');
260
+
261
+ validateSliceSupportsClientSpecs(slice);
262
+ addNodeToParentOrRoot(itNode, slice as CommandSlice | QuerySlice | ExperienceSlice);
263
+ }
264
+
257
265
  export function setQueryRequest(request: string): void {
258
266
  const slice = getCurrentSlice();
259
267
  if (!slice || slice.type !== 'query') throw new Error('Request can only be set on query slices');
@@ -290,19 +298,16 @@ export function recordRule(description: string, id?: string): void {
290
298
  const slice = getCurrentSlice();
291
299
  if (!slice) throw new Error('No active slice');
292
300
 
293
- const spec = getCurrentSpecs(slice);
301
+ const spec = getServerSpecs(slice);
294
302
  if (!spec) throw new Error('No active specs for current slice');
295
303
 
296
- // Only server specs have object rules with examples
297
- if (context.currentSpecTarget === 'server') {
298
- const objectRules = spec.rules as { id?: string; description: string; examples: Example[] }[];
299
- objectRules.push({
300
- id,
301
- description,
302
- examples: [],
303
- });
304
- context.currentRuleIndex = objectRules.length - 1;
305
- }
304
+ const objectRules = spec.rules as { id?: string; description: string; examples: Example[] }[];
305
+ objectRules.push({
306
+ id,
307
+ description,
308
+ examples: [],
309
+ });
310
+ context.currentRuleIndex = objectRules.length - 1;
306
311
  }
307
312
 
308
313
  export function recordExample(description: string): void {
@@ -312,19 +317,16 @@ export function recordExample(description: string): void {
312
317
  const slice = getCurrentSlice();
313
318
  if (!slice) throw new Error('No active slice');
314
319
 
315
- const spec = getCurrentSpecs(slice);
320
+ const spec = getServerSpecs(slice);
316
321
  if (!spec) throw new Error('No active specs for current slice');
317
322
 
318
- // Only server specs have object rules with examples
319
- if (context.currentSpecTarget === 'server') {
320
- const objectRules = spec.rules as { id?: string; description: string; examples: Example[] }[];
321
- const rule = objectRules[context.currentRuleIndex];
322
- rule.examples.push({
323
- description,
324
- then: [],
325
- });
326
- context.currentExampleIndex = rule.examples.length - 1;
327
- }
323
+ const objectRules = spec.rules as { id?: string; description: string; examples: Example[] }[];
324
+ const rule = objectRules[context.currentRuleIndex];
325
+ rule.examples.push({
326
+ description,
327
+ then: [],
328
+ });
329
+ context.currentExampleIndex = rule.examples.length - 1;
328
330
  }
329
331
 
330
332
  function processItemWithASTMatch(
package/src/narrative.ts CHANGED
@@ -8,7 +8,9 @@ import {
8
8
  startServerBlock,
9
9
  endServerBlock,
10
10
  pushSpec,
11
- recordShouldBlock,
11
+ pushDescribe,
12
+ popDescribe,
13
+ recordIt,
12
14
  setSliceData,
13
15
  recordRule,
14
16
  recordExample,
@@ -45,7 +47,7 @@ export function narrative(name: string, idOrFn: string | (() => void), fn?: () =
45
47
  export const client = (fn: () => void) => {
46
48
  const slice = getCurrentSlice();
47
49
  if (slice) {
48
- startClientBlock(slice, '');
50
+ startClientBlock(slice);
49
51
  fn();
50
52
  endClientBlock();
51
53
  }
@@ -64,9 +66,46 @@ export const request = (_query: unknown) => ({
64
66
  with: (..._dependencies: unknown[]) => {},
65
67
  });
66
68
 
67
- export const should = (description: string) => {
68
- recordShouldBlock(description);
69
- };
69
+ export function describe(fn: () => void): void;
70
+ export function describe(title: string, fn: () => void): void;
71
+ export function describe(id: string, title: string, fn: () => void): void;
72
+ export function describe(
73
+ idOrTitleOrFn: string | (() => void),
74
+ titleOrFn?: string | (() => void),
75
+ fn?: () => void,
76
+ ): void {
77
+ if (typeof idOrTitleOrFn === 'function') {
78
+ const slice = getCurrentSlice();
79
+ const inferredTitle = slice?.name ?? '';
80
+ pushDescribe(undefined, inferredTitle);
81
+ idOrTitleOrFn();
82
+ popDescribe();
83
+ return;
84
+ }
85
+
86
+ const hasId = typeof titleOrFn === 'string';
87
+ const id = hasId ? idOrTitleOrFn : undefined;
88
+ const title = hasId ? titleOrFn : idOrTitleOrFn;
89
+ const callback = hasId ? fn! : (titleOrFn as () => void);
90
+
91
+ pushDescribe(id, title);
92
+ callback();
93
+ popDescribe();
94
+ }
95
+
96
+ export function it(title: string): void;
97
+ export function it(id: string, title: string): void;
98
+ export function it(idOrTitle: string, title?: string): void {
99
+ const hasId = title !== undefined;
100
+ recordIt(hasId ? idOrTitle : undefined, hasId ? title : idOrTitle);
101
+ }
102
+
103
+ export function should(title: string): void;
104
+ export function should(id: string, title: string): void;
105
+ export function should(idOrTitle: string, title?: string): void {
106
+ const hasId = title !== undefined;
107
+ recordIt(hasId ? idOrTitle : undefined, hasId ? title : idOrTitle);
108
+ }
70
109
 
71
110
  export function specs(description: string, fn: () => void): void;
72
111
  export function specs(fn: () => void): void;
@@ -75,7 +114,6 @@ export function specs(descriptionOrFn: string | (() => void), fn?: () => void):
75
114
  const callback = typeof descriptionOrFn === 'function' ? descriptionOrFn : fn!;
76
115
 
77
116
  pushSpec(description);
78
- recordShouldBlock();
79
117
  callback();
80
118
  }
81
119
 
@@ -1,4 +1,4 @@
1
- import { data, flow, should, specs, rule, example } from '../narrative';
1
+ import { data, flow, describe, it, specs, rule, example } from '../narrative';
2
2
  import { command, query } from '../fluent-builder';
3
3
  import gql from 'graphql-tag';
4
4
  import { source } from '../data-narrative-builders';
@@ -33,8 +33,8 @@ flow('items', () => {
33
33
  command('Create item')
34
34
  .stream('item-${id}')
35
35
  .client(() => {
36
- specs('A form that allows users to add items', () => {
37
- should('have fields for id and description');
36
+ describe('A form that allows users to add items', () => {
37
+ it('have fields for id and description');
38
38
  });
39
39
  })
40
40
  .server(() => {
@@ -64,10 +64,10 @@ flow('items', () => {
64
64
  }
65
65
  `)
66
66
  .client(() => {
67
- specs('view Items Screen', () => {
68
- should('display all items');
69
- should('show quantity selectors for each item');
70
- should('allow removing items');
67
+ describe('view Items Screen', () => {
68
+ it('display all items');
69
+ it('show quantity selectors for each item');
70
+ it('allow removing items');
71
71
  });
72
72
  })
73
73
  .server(() => {
@@ -1,4 +1,4 @@
1
- import { flow, should, specs, rule, example } from '../narrative';
1
+ import { flow, describe, it, specs, rule, example } from '../narrative';
2
2
  import { command } from '../fluent-builder';
3
3
 
4
4
  export interface OrderPlaced {
@@ -32,9 +32,9 @@ flow('Place order', () => {
32
32
  command('Submit order')
33
33
  .stream('order-${orderId}')
34
34
  .client(() => {
35
- specs('Order submission form', () => {
36
- should('allow product selection');
37
- should('allow quantity input');
35
+ describe('Order submission form', () => {
36
+ it('allow product selection');
37
+ it('allow quantity input');
38
38
  });
39
39
  })
40
40
  .server(() => {
@@ -3,7 +3,8 @@ import {
3
3
  query,
4
4
  experience,
5
5
  flow,
6
- should,
6
+ it,
7
+ describe,
7
8
  specs,
8
9
  rule,
9
10
  example,
@@ -96,10 +97,8 @@ type QuestionnaireProgress = State<
96
97
 
97
98
  flow('Questionnaires', 'AUTO-Q9m2Kp4Lx', () => {
98
99
  experience('Homepage', 'AUTO-H1a4Bn6Cy').client(() => {
99
- specs(() => {
100
- should('show a hero section with a welcome message');
101
- should('allow user to start the questionnaire');
102
- });
100
+ it('show a hero section with a welcome message');
101
+ it('allow user to start the questionnaire');
103
102
  });
104
103
 
105
104
  query('views the questionnaire', 'AUTO-V7n8Rq5M')
@@ -163,11 +162,11 @@ flow('Questionnaires', 'AUTO-Q9m2Kp4Lx', () => {
163
162
  }
164
163
  `)
165
164
  .client(() => {
166
- specs('Questionnaire Progress', () => {
167
- should('focus on the current question based on the progress state');
168
- should('display the list of answered questions');
169
- should('display the list of remaining questions');
170
- should('show a progress indicator that is always visible as the user scrolls');
165
+ describe('Questionnaire Progress', () => {
166
+ it('focus on the current question based on the progress state');
167
+ it('display the list of answered questions');
168
+ it('display the list of remaining questions');
169
+ it('show a progress indicator that is always visible as the user scrolls');
171
170
  });
172
171
  });
173
172
 
@@ -223,9 +222,9 @@ flow('Questionnaires', 'AUTO-Q9m2Kp4Lx', () => {
223
222
  }
224
223
  `)
225
224
  .client(() => {
226
- specs('Submissions', () => {
227
- should('displays a success message when the answer is submitted');
228
- should('display an error message when the answer submission is rejected');
225
+ describe('Submissions', () => {
226
+ it('displays a success message when the answer is submitted');
227
+ it('display an error message when the answer submission is rejected');
229
228
  });
230
229
  });
231
230
 
@@ -314,9 +313,9 @@ flow('Questionnaires', 'AUTO-Q9m2Kp4Lx', () => {
314
313
  }
315
314
  `)
316
315
  .client(() => {
317
- specs('Submission Readiness', () => {
318
- should('enable the submit button when all questions are answered');
319
- should('disable the submit button when all questions have not been answered');
316
+ describe('Submission Readiness', () => {
317
+ it('enable the submit button when all questions are answered');
318
+ it('disable the submit button when all questions have not been answered');
320
319
  });
321
320
  });
322
321
 
@@ -346,8 +345,8 @@ flow('Questionnaires', 'AUTO-Q9m2Kp4Lx', () => {
346
345
  }
347
346
  `)
348
347
  .client(() => {
349
- specs('Submission Confirmation', () => {
350
- should('display a confirmation message upon successful submission');
348
+ describe('Submission Confirmation', () => {
349
+ it('display a confirmation message upon successful submission');
351
350
  });
352
351
  });
353
352
  });
@@ -8,18 +8,20 @@
8
8
  "name": "enters shopping criteria into assistant",
9
9
  "type": "command",
10
10
  "client": {
11
- "description": "",
12
- "specs": {
13
- "name": "Assistant Chat Interface",
14
- "rules": [
15
- "allow shopper to describe their shopping needs in natural language",
16
- "provide a text input for entering criteria",
17
- "show examples of what to include (age, interests, budget)",
18
- "show a button to submit the criteria",
19
- "generate a persisted session id for a visit",
20
- "show the header on top of the page"
21
- ]
22
- }
11
+ "specs": [
12
+ {
13
+ "type": "describe",
14
+ "title": "Assistant Chat Interface",
15
+ "children": [
16
+ { "type": "it", "title": "allow shopper to describe their shopping needs in natural language" },
17
+ { "type": "it", "title": "provide a text input for entering criteria" },
18
+ { "type": "it", "title": "show examples of what to include (age, interests, budget)" },
19
+ { "type": "it", "title": "show a button to submit the criteria" },
20
+ { "type": "it", "title": "generate a persisted session id for a visit" },
21
+ { "type": "it", "title": "show the header on top of the page" }
22
+ ]
23
+ }
24
+ ]
23
25
  },
24
26
  "request": "mutation EnterShoppingCriteria($input: EnterShoppingCriteriaInput!) {\n enterShoppingCriteria(input: $input) {\n success\n error {\n type\n message\n }\n }\n}",
25
27
  "server": {
@@ -250,16 +252,18 @@
250
252
  "name": "views suggested items",
251
253
  "type": "query",
252
254
  "client": {
253
- "description": "",
254
- "specs": {
255
- "name": "Suggested Items Screen",
256
- "rules": [
257
- "display all suggested items with names and reasons",
258
- "show quantity selectors for each item",
259
- "have an \"Add to Cart\" button for selected items",
260
- "allow removing items from the suggestions"
261
- ]
262
- }
255
+ "specs": [
256
+ {
257
+ "type": "describe",
258
+ "title": "Suggested Items Screen",
259
+ "children": [
260
+ { "type": "it", "title": "display all suggested items with names and reasons" },
261
+ { "type": "it", "title": "show quantity selectors for each item" },
262
+ { "type": "it", "title": "have an \"Add to Cart\" button for selected items" },
263
+ { "type": "it", "title": "allow removing items from the suggestions" }
264
+ ]
265
+ }
266
+ ]
263
267
  },
264
268
  "request": "query GetSuggestedItems($sessionId: ID!) {\n suggestedItems(sessionId: $sessionId) {\n items {\n productId\n name\n quantity\n reason\n }\n }\n}",
265
269
  "server": {
@@ -362,15 +366,17 @@
362
366
  "name": "accepts items and adds to their cart",
363
367
  "type": "command",
364
368
  "client": {
365
- "description": "",
366
- "specs": {
367
- "name": "Suggested Items Screen",
368
- "rules": [
369
- "allow selecting specific items to add",
370
- "update quantities before adding to cart",
371
- "provide feedback when items are added"
372
- ]
373
- }
369
+ "specs": [
370
+ {
371
+ "type": "describe",
372
+ "title": "Suggested Items Screen",
373
+ "children": [
374
+ { "type": "it", "title": "allow selecting specific items to add" },
375
+ { "type": "it", "title": "update quantities before adding to cart" },
376
+ { "type": "it", "title": "provide feedback when items are added" }
377
+ ]
378
+ }
379
+ ]
374
380
  },
375
381
  "server": {
376
382
  "description": "",