foldkit 0.43.2 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/index.d.ts +2 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +2 -1
  4. package/dist/test/apps/counter.d.ts +38 -0
  5. package/dist/test/apps/counter.d.ts.map +1 -0
  6. package/dist/test/apps/counter.js +25 -0
  7. package/dist/test/apps/formChild.d.ts +69 -0
  8. package/dist/test/apps/formChild.d.ts.map +1 -0
  9. package/dist/test/apps/formChild.js +84 -0
  10. package/dist/test/apps/keypress.d.ts +23 -0
  11. package/dist/test/apps/keypress.d.ts.map +1 -0
  12. package/dist/test/apps/keypress.js +39 -0
  13. package/dist/test/apps/login.d.ts +46 -0
  14. package/dist/test/apps/login.d.ts.map +1 -0
  15. package/dist/test/apps/login.js +83 -0
  16. package/dist/test/apps/logoutButton.d.ts +17 -0
  17. package/dist/test/apps/logoutButton.d.ts.map +1 -0
  18. package/dist/test/apps/logoutButton.js +22 -0
  19. package/dist/test/internal.d.ts +25 -0
  20. package/dist/test/internal.d.ts.map +1 -0
  21. package/dist/test/internal.js +39 -0
  22. package/dist/test/matchers.d.ts +94 -0
  23. package/dist/test/matchers.d.ts.map +1 -0
  24. package/dist/test/matchers.js +283 -0
  25. package/dist/test/public.d.ts +2 -2
  26. package/dist/test/public.d.ts.map +1 -1
  27. package/dist/test/public.js +2 -1
  28. package/dist/test/query.d.ts +88 -0
  29. package/dist/test/query.d.ts.map +1 -0
  30. package/dist/test/query.js +227 -0
  31. package/dist/test/scene.d.ts +106 -0
  32. package/dist/test/scene.d.ts.map +1 -0
  33. package/dist/test/scene.js +373 -0
  34. package/dist/test/{index.d.ts → story.d.ts} +13 -17
  35. package/dist/test/story.d.ts.map +1 -0
  36. package/dist/test/{index.js → story.js} +8 -36
  37. package/dist/test/vitest-setup.d.ts +19 -0
  38. package/dist/test/vitest-setup.d.ts.map +1 -0
  39. package/dist/test/vitest-setup.js +3 -0
  40. package/dist/ui/input/index.d.ts +0 -2
  41. package/dist/ui/input/index.d.ts.map +1 -1
  42. package/dist/ui/input/index.js +2 -5
  43. package/dist/ui/input/public.d.ts +1 -1
  44. package/dist/ui/input/public.d.ts.map +1 -1
  45. package/dist/ui/input/public.js +1 -1
  46. package/dist/ui/select/index.d.ts +0 -2
  47. package/dist/ui/select/index.d.ts.map +1 -1
  48. package/dist/ui/select/index.js +2 -5
  49. package/dist/ui/select/public.d.ts +1 -1
  50. package/dist/ui/select/public.d.ts.map +1 -1
  51. package/dist/ui/select/public.js +1 -1
  52. package/dist/ui/textarea/index.d.ts +0 -2
  53. package/dist/ui/textarea/index.d.ts.map +1 -1
  54. package/dist/ui/textarea/index.js +2 -5
  55. package/dist/ui/textarea/public.d.ts +1 -1
  56. package/dist/ui/textarea/public.d.ts.map +1 -1
  57. package/dist/ui/textarea/public.js +1 -1
  58. package/package.json +5 -5
  59. package/dist/test/index.d.ts.map +0 -1
@@ -0,0 +1,373 @@
1
+ import { Array, Effect, Function, Option, Predicate, pipe } from 'effect';
2
+ import { dual } from 'effect/Function';
3
+ import { Dispatch } from '../runtime';
4
+ import { assertAllCommandsResolved, assertNoUnresolvedCommands, resolveByName, } from './internal';
5
+ import { attr, resolveTarget, textContent } from './query';
6
+ export { find, findAll, textContent, attr, getByRole, getAllByRole, getByText, getByPlaceholder, getByLabel, role, placeholder, label, selector, text, within, } from './query';
7
+ export { sceneMatchers } from './matchers';
8
+ const UNINITIALIZED = Symbol('uninitialized');
9
+ const toInternal = (simulation) =>
10
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
11
+ simulation;
12
+ // CAPTURING DISPATCH
13
+ const createCapturingDispatch = () => {
14
+ let capturedMessage;
15
+ return {
16
+ dispatch: Dispatch.of({
17
+ dispatchAsync: () => Effect.void,
18
+ dispatchSync: (dispatchedMessage) => {
19
+ capturedMessage = dispatchedMessage;
20
+ },
21
+ }),
22
+ getCapturedMessage: () => capturedMessage,
23
+ reset: () => {
24
+ capturedMessage = undefined;
25
+ },
26
+ };
27
+ };
28
+ // RENDERING
29
+ const renderView = (viewFn, model, dispatch) => {
30
+ const maybeVNode = Effect.runSync(Effect.provideService(viewFn(model), Dispatch, dispatch));
31
+ if (Predicate.isNull(maybeVNode)) {
32
+ throw new Error('The view function returned null.\n\n' +
33
+ 'Scene tests require a non-null view. ' +
34
+ 'If you need to test null-view states, use Story.story instead.');
35
+ }
36
+ return maybeVNode;
37
+ };
38
+ // INTERACTION HELPERS
39
+ const EVENT_NAMES = {
40
+ click: 'OnClick',
41
+ submit: 'OnSubmit',
42
+ input: 'OnInput',
43
+ keydown: 'OnKeyDown or OnKeyDownPreventDefault',
44
+ };
45
+ const invokeAndCapture = (simulation, target, eventName, invokeHandler) => {
46
+ const internal = toInternal(simulation);
47
+ const { maybeElement, description } = resolveTarget(internal.html, target);
48
+ if (Option.isNone(maybeElement)) {
49
+ throw new Error(`I could not find an element matching ${description}.\n\n` +
50
+ 'Check that your selector matches an element in the current view.');
51
+ }
52
+ const element = maybeElement.value;
53
+ const maybeHandler = Option.fromNullable(element.data?.on?.[eventName]);
54
+ if (Option.isNone(maybeHandler)) {
55
+ const attributeName = EVENT_NAMES[eventName] ?? eventName;
56
+ throw new Error(`I found an element matching ${description} but it has no ${eventName} handler.\n\n` +
57
+ `Make sure the element has an ${attributeName} attribute.`);
58
+ }
59
+ internal.capturingDispatch.reset();
60
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
61
+ invokeHandler(maybeHandler.value);
62
+ const captured = internal.capturingDispatch.getCapturedMessage();
63
+ if (Predicate.isUndefined(captured)) {
64
+ return simulation;
65
+ }
66
+ assertNoUnresolvedCommands(internal.commands, 'when an interaction dispatched a new Message');
67
+ /* eslint-disable @typescript-eslint/consistent-type-assertions */
68
+ const capturedMessage = captured;
69
+ const result = internal.updateFn(internal.model, capturedMessage);
70
+ const [nextModel, commands] = result;
71
+ const outMessage = result.length === 3 ? result[2] : internal.outMessage;
72
+ return {
73
+ ...internal,
74
+ model: nextModel,
75
+ message: capturedMessage,
76
+ commands: Array.appendAll(internal.commands, commands),
77
+ outMessage,
78
+ };
79
+ /* eslint-enable @typescript-eslint/consistent-type-assertions */
80
+ };
81
+ const DEFAULT_KEYBOARD_MODIFIERS = {
82
+ shiftKey: false,
83
+ ctrlKey: false,
84
+ altKey: false,
85
+ metaKey: false,
86
+ };
87
+ // STEPS
88
+ /** Sets the initial Model for a scene test. */
89
+ export { with_ as with };
90
+ const with_ = (model) => {
91
+ const step = (simulation) => {
92
+ const internal = toInternal(simulation);
93
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
94
+ return { ...internal, model };
95
+ };
96
+ /* eslint-disable @typescript-eslint/consistent-type-assertions */
97
+ return Object.assign(step, {
98
+ _phantomModel: undefined,
99
+ });
100
+ /* eslint-enable @typescript-eslint/consistent-type-assertions */
101
+ };
102
+ /** Resolves a specific pending Command with the given result Message. */
103
+ export const resolve = (definition, resultMessage, toParentMessage) => (simulation) => {
104
+ /* eslint-disable @typescript-eslint/consistent-type-assertions */
105
+ const internal = toInternal(simulation);
106
+ const messageForUpdate = (Predicate.isUndefined(toParentMessage)
107
+ ? resultMessage
108
+ : toParentMessage(resultMessage));
109
+ const next = resolveByName(internal, definition.name, messageForUpdate);
110
+ if (Predicate.isUndefined(next)) {
111
+ const pending = Array.isNonEmptyReadonlyArray(internal.commands)
112
+ ? pipe(internal.commands, Array.map(({ name }) => ` ${name}`), Array.join('\n'))
113
+ : ' (none)';
114
+ throw new Error(`I tried to resolve "${definition.name}" but it wasn't in the pending Commands.\n\n` +
115
+ `Pending Commands:\n${pending}\n\n` +
116
+ 'Make sure the previous Message produced this Command.');
117
+ }
118
+ return next;
119
+ /* eslint-enable @typescript-eslint/consistent-type-assertions */
120
+ };
121
+ /** Resolves all listed Commands with their result Messages. Handles cascading resolution. */
122
+ export const resolveAll = (pairs) => (simulation) => {
123
+ const internal = toInternal(simulation);
124
+ const resolvers = {};
125
+ for (const [definition, resultMessage] of pairs) {
126
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
127
+ resolvers[definition.name] = resultMessage;
128
+ }
129
+ /* eslint-disable @typescript-eslint/consistent-type-assertions */
130
+ let current = {
131
+ ...internal,
132
+ resolvers: { ...internal.resolvers, ...resolvers },
133
+ };
134
+ const MAX_CASCADE_DEPTH = 100;
135
+ for (let depth = 0; depth < MAX_CASCADE_DEPTH; depth++) {
136
+ const resolvable = current.commands.find(({ name }) => name in current.resolvers);
137
+ if (Predicate.isUndefined(resolvable)) {
138
+ break;
139
+ }
140
+ const next = resolveByName(current, resolvable.name, current.resolvers[resolvable.name]);
141
+ if (Predicate.isUndefined(next)) {
142
+ break;
143
+ }
144
+ current = next;
145
+ if (depth === MAX_CASCADE_DEPTH - 1) {
146
+ throw new Error('resolveAll hit the maximum cascade depth (100). ' +
147
+ 'This usually means Commands are producing Commands in an infinite cycle.');
148
+ }
149
+ }
150
+ return current;
151
+ /* eslint-enable @typescript-eslint/consistent-type-assertions */
152
+ };
153
+ /** Runs a function for side effects (e.g. assertions) without breaking the step chain. */
154
+ export const tap = (f) => (simulation) => {
155
+ f(simulation);
156
+ return simulation;
157
+ };
158
+ // INTERACTION STEPS
159
+ /** Simulates a click on the element matching the target. */
160
+ export const click = (target) => (simulation) => invokeAndCapture(simulation, target, 'click', handler => {
161
+ handler();
162
+ });
163
+ /** Simulates form submission on the element matching the target. */
164
+ export const submit = (target) => (simulation) => invokeAndCapture(simulation, target, 'submit', handler => {
165
+ handler({ preventDefault: Function.constVoid });
166
+ });
167
+ /** Simulates typing a value into the input matching the target.
168
+ * Dual: `type(target, value)` or `type(value)` for data-last piping. */
169
+ export { type_ as type };
170
+ const type_ = dual(2, (target, value) => (simulation) => invokeAndCapture(simulation, target, 'input', handler => {
171
+ handler({ target: { value } });
172
+ }));
173
+ /** Simulates a keydown event on the element matching the target.
174
+ * Dual: `keydown(target, key, modifiers?)` or `keydown(key, modifiers?)` for data-last piping. */
175
+ export const keydown = dual((args) => args.length >= 2 && typeof args[1] === 'string', (target, key, modifiers) => (simulation) => invokeAndCapture(simulation, target, 'keydown', handler => {
176
+ handler({
177
+ key,
178
+ ...DEFAULT_KEYBOARD_MODIFIERS,
179
+ ...modifiers,
180
+ preventDefault: Function.constVoid,
181
+ });
182
+ }));
183
+ const wrapAssertion = (locator, assertion, isNot) => (simulation) => {
184
+ const internal = toInternal(simulation);
185
+ assertion(locator(internal.html), locator.description, isNot);
186
+ return simulation;
187
+ };
188
+ const assertOnElement = (check, expectation) => (maybeElement, description, isNot) => {
189
+ if (Option.isNone(maybeElement)) {
190
+ if (!isNot) {
191
+ throw new Error(`Expected element matching ${description} to ${expectation} but the element does not exist.`);
192
+ }
193
+ return;
194
+ }
195
+ const { pass, actual } = check(maybeElement.value);
196
+ if (isNot ? pass : !pass) {
197
+ throw new Error(isNot
198
+ ? `Expected element matching ${description} not to ${expectation} but it does.`
199
+ : `Expected element matching ${description} to ${expectation} but ${actual}.`);
200
+ }
201
+ };
202
+ const assertExists = (maybeElement, description, isNot) => {
203
+ const exists = Option.isSome(maybeElement);
204
+ if (isNot ? exists : !exists) {
205
+ throw new Error(isNot
206
+ ? `Expected element matching ${description} not to exist but it does.`
207
+ : `Expected element matching ${description} to exist but it does not.`);
208
+ }
209
+ };
210
+ const assertAbsent = (maybeElement, description, isNot) => {
211
+ const absent = Option.isNone(maybeElement);
212
+ if (isNot ? absent : !absent) {
213
+ throw new Error(isNot
214
+ ? `Expected element matching ${description} not to be absent but it is.`
215
+ : `Expected element matching ${description} to be absent but it exists.`);
216
+ }
217
+ };
218
+ const assertHasText = (expected) => assertOnElement(vnode => ({
219
+ pass: textContent(vnode) === expected,
220
+ actual: `received "${textContent(vnode)}"`,
221
+ }), `have text "${expected}"`);
222
+ const assertContainsText = (expected) => assertOnElement(vnode => ({
223
+ pass: textContent(vnode).includes(expected),
224
+ actual: `received "${textContent(vnode)}"`,
225
+ }), `contain text "${expected}"`);
226
+ const assertHasAttr = (name, value) => assertOnElement(vnode => {
227
+ const actualValue = attr(vnode, name);
228
+ if (Predicate.isUndefined(value)) {
229
+ return {
230
+ pass: Option.isSome(actualValue),
231
+ actual: 'the attribute is not present',
232
+ };
233
+ }
234
+ return Option.match(actualValue, {
235
+ onNone: () => ({
236
+ pass: false,
237
+ actual: 'the attribute is not present',
238
+ }),
239
+ onSome: actual => ({
240
+ pass: actual === value,
241
+ actual: `received "${actual}"`,
242
+ }),
243
+ });
244
+ }, Predicate.isUndefined(value)
245
+ ? `have attribute "${name}"`
246
+ : `have attribute ${name}="${value}"`);
247
+ const assertHasClass = (expected) => assertOnElement(vnode => ({
248
+ pass: vnode.data?.class?.[expected] === true,
249
+ actual: 'it does not',
250
+ }), `have class "${expected}"`);
251
+ const assertHasStyle = (name, value) => assertOnElement(vnode => {
252
+ const maybeActualValue = Option.fromNullable(vnode.data?.style?.[name]);
253
+ if (Predicate.isUndefined(value)) {
254
+ return {
255
+ pass: Option.isSome(maybeActualValue),
256
+ actual: 'it is not present',
257
+ };
258
+ }
259
+ return Option.match(maybeActualValue, {
260
+ onNone: () => ({ pass: false, actual: 'it is not present' }),
261
+ onSome: actualValue => ({
262
+ pass: String(actualValue) === value,
263
+ actual: `received "${actualValue}"`,
264
+ }),
265
+ });
266
+ }, Predicate.isUndefined(value)
267
+ ? `have style "${name}"`
268
+ : `have style ${name}="${value}"`);
269
+ const assertHasHook = (name) => assertOnElement(vnode => {
270
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
271
+ const hooks = vnode.data?.hook;
272
+ return {
273
+ pass: typeof hooks?.[name] === 'function',
274
+ actual: 'it is not present',
275
+ };
276
+ }, `have hook "${name}"`);
277
+ const assertHasHandler = (name) => assertOnElement(vnode => ({
278
+ pass: vnode.data?.on?.[name] !== undefined,
279
+ actual: 'it is not present',
280
+ }), `have handler "${name}"`);
281
+ const assertHasValue = (expected) => assertOnElement(vnode => {
282
+ const actualValue = attr(vnode, 'value');
283
+ return Option.match(actualValue, {
284
+ onNone: () => ({
285
+ pass: false,
286
+ actual: 'the element has no value',
287
+ }),
288
+ onSome: actual => ({
289
+ pass: actual === expected,
290
+ actual: `received "${actual}"`,
291
+ }),
292
+ });
293
+ }, `have value "${expected}"`);
294
+ const isDisabled = (vnode) => {
295
+ const disabled = attr(vnode, 'disabled');
296
+ if (Option.isSome(disabled) && disabled.value !== 'false') {
297
+ return true;
298
+ }
299
+ const ariaDisabled = attr(vnode, 'aria-disabled');
300
+ return Option.isSome(ariaDisabled) && ariaDisabled.value === 'true';
301
+ };
302
+ const assertIsDisabled = assertOnElement(vnode => ({
303
+ pass: isDisabled(vnode),
304
+ actual: 'it is not disabled',
305
+ }), 'be disabled');
306
+ const assertIsEnabled = assertOnElement(vnode => ({
307
+ pass: !isDisabled(vnode),
308
+ actual: 'it is disabled',
309
+ }), 'be enabled');
310
+ const assertIsChecked = assertOnElement(vnode => {
311
+ const checked = attr(vnode, 'checked');
312
+ const ariaChecked = attr(vnode, 'aria-checked');
313
+ const pass = (Option.isSome(checked) && checked.value !== 'false') ||
314
+ (Option.isSome(ariaChecked) && ariaChecked.value === 'true');
315
+ return { pass, actual: 'it is not checked' };
316
+ }, 'be checked');
317
+ const buildExpectChain = (locator, isNot) => ({
318
+ toExist: () => wrapAssertion(locator, assertExists, isNot),
319
+ toBeAbsent: () => wrapAssertion(locator, assertAbsent, isNot),
320
+ toHaveText: (expected) => wrapAssertion(locator, assertHasText(expected), isNot),
321
+ toContainText: (expected) => wrapAssertion(locator, assertContainsText(expected), isNot),
322
+ toHaveAttr: (name, value) => wrapAssertion(locator, assertHasAttr(name, value), isNot),
323
+ toHaveClass: (expected) => wrapAssertion(locator, assertHasClass(expected), isNot),
324
+ toHaveStyle: (name, value) => wrapAssertion(locator, assertHasStyle(name, value), isNot),
325
+ toHaveHook: (name) => wrapAssertion(locator, assertHasHook(name), isNot),
326
+ toHaveHandler: (name) => wrapAssertion(locator, assertHasHandler(name), isNot),
327
+ toHaveValue: (expected) => wrapAssertion(locator, assertHasValue(expected), isNot),
328
+ toBeDisabled: () => wrapAssertion(locator, assertIsDisabled, isNot),
329
+ toBeEnabled: () => wrapAssertion(locator, assertIsEnabled, isNot),
330
+ toBeChecked: () => wrapAssertion(locator, assertIsChecked, isNot),
331
+ });
332
+ /** Creates an inline assertion step. Resolves the Locator against
333
+ * the current view and asserts on the result. */
334
+ export { expect_ as expect };
335
+ const expect_ = (locator) => ({
336
+ ...buildExpectChain(locator, false),
337
+ not: buildExpectChain(locator, true),
338
+ });
339
+ // SUBMODEL VIEW ADAPTER
340
+ /** Adapts a submodel view for Scene testing. In the Submodel pattern, the view
341
+ * takes a `toParentMessage` function that maps child Messages to parent Messages.
342
+ * Scene tests the child in isolation, so `childView` passes the identity function
343
+ * and erases the parent type. */
344
+ export const childView = (viewFn) => model => viewFn(model, message => message);
345
+ // SCENE
346
+ /** Executes a scene test. Throws if any Commands remain unresolved. */
347
+ export const scene = (config, ...steps) => {
348
+ const capturingDispatch = createCapturingDispatch();
349
+ /* eslint-disable @typescript-eslint/consistent-type-assertions */
350
+ const seed = {
351
+ model: UNINITIALIZED,
352
+ message: undefined,
353
+ commands: [],
354
+ outMessage: undefined,
355
+ updateFn: config.update,
356
+ resolvers: {},
357
+ html: undefined,
358
+ viewFn: config.view,
359
+ capturingDispatch,
360
+ };
361
+ const result = steps.reduce((current, step) => {
362
+ const next = step(current);
363
+ const internal = toInternal(next);
364
+ if (internal.model !== UNINITIALIZED) {
365
+ const html = renderView(internal.viewFn, internal.model, internal.capturingDispatch.dispatch);
366
+ return { ...internal, html };
367
+ }
368
+ return next;
369
+ }, seed);
370
+ /* eslint-enable @typescript-eslint/consistent-type-assertions */
371
+ const internal = toInternal(result);
372
+ assertAllCommandsResolved(internal.commands);
373
+ };
@@ -1,10 +1,8 @@
1
1
  import type { CommandDefinition } from '../command';
2
- /** A Command in a test simulation, identified by name. */
3
- export type AnyCommand = Readonly<{
4
- name: string;
5
- }>;
2
+ import type { AnyCommand, ResolverPair } from './internal';
3
+ export type { AnyCommand, ResolverPair };
6
4
  /** An immutable test simulation of a Foldkit program. */
7
- export type Simulation<Model, Message, OutMessage = undefined> = Readonly<{
5
+ export type StorySimulation<Model, Message, OutMessage = undefined> = Readonly<{
8
6
  model: Model;
9
7
  message: Message | undefined;
10
8
  commands: ReadonlyArray<AnyCommand>;
@@ -13,28 +11,26 @@ export type Simulation<Model, Message, OutMessage = undefined> = Readonly<{
13
11
  /** A callable step that sets the initial Model. Carries phantom type for compile-time validation. */
14
12
  export type WithStep<Model> = Readonly<{
15
13
  _phantomModel: Model;
16
- }> & (<M, Message, OutMessage = undefined>(simulation: Simulation<M, Message, OutMessage>) => Simulation<M, Message, OutMessage>);
14
+ }> & (<M, Message, OutMessage = undefined>(simulation: StorySimulation<M, Message, OutMessage>) => StorySimulation<M, Message, OutMessage>);
15
+ /** A single step in a story — either a {@link WithStep} or a simulation transform. */
16
+ export type StoryStep<Model, Message, OutMessage> = WithStep<NoInfer<Model>> | ((simulation: StorySimulation<Model, Message, OutMessage>) => StorySimulation<Model, Message, OutMessage>);
17
17
  /** Sets the initial Model for a test story. */
18
18
  export { with_ as with };
19
19
  declare const with_: <Model>(model: Model) => WithStep<Model>;
20
20
  /** Sends a Message through update. Commands stay pending until resolve or resolveAll. */
21
- export declare const message: <Message>(message_: NoInfer<Message>) => <Model, OutMessage = undefined>(simulation: Simulation<Model, Message, OutMessage>) => Simulation<Model, Message, OutMessage>;
21
+ export declare const message: <Message>(message_: NoInfer<Message>) => <Model, OutMessage = undefined>(simulation: StorySimulation<Model, Message, OutMessage>) => StorySimulation<Model, Message, OutMessage>;
22
22
  /** Resolves a specific pending Command with the given result Message. */
23
23
  export declare const resolve: {
24
- <Name extends string, ResultMessage>(definition: CommandDefinition<Name, ResultMessage>, resultMessage: ResultMessage): <Model, Message, OutMessage = undefined>(simulation: Simulation<Model, Message, OutMessage>) => Simulation<Model, Message, OutMessage>;
25
- <Name extends string, ResultMessage, ParentMessage>(definition: CommandDefinition<Name, ResultMessage>, resultMessage: ResultMessage, toParentMessage: (message: ResultMessage) => ParentMessage): <Model, Message, OutMessage = undefined>(simulation: Simulation<Model, Message, OutMessage>) => Simulation<Model, Message, OutMessage>;
24
+ <Name extends string, ResultMessage>(definition: CommandDefinition<Name, ResultMessage>, resultMessage: ResultMessage): <Model, Message, OutMessage = undefined>(simulation: StorySimulation<Model, Message, OutMessage>) => StorySimulation<Model, Message, OutMessage>;
25
+ <Name extends string, ResultMessage, ParentMessage>(definition: CommandDefinition<Name, ResultMessage>, resultMessage: ResultMessage, toParentMessage: (message: ResultMessage) => ParentMessage): <Model, Message, OutMessage = undefined>(simulation: StorySimulation<Model, Message, OutMessage>) => StorySimulation<Model, Message, OutMessage>;
26
26
  };
27
- /** A Command definition paired with the result Message to resolve it with. */
28
- export type ResolverPair<Name extends string = string, ResultMessage = unknown> = readonly [CommandDefinition<Name, ResultMessage>, ResultMessage];
29
27
  /** Resolves all listed Commands with their result Messages. Handles cascading resolution. */
30
- export declare const resolveAll: (pairs: ReadonlyArray<ResolverPair>) => <Model, Message, OutMessage = undefined>(simulation: Simulation<Model, Message, OutMessage>) => Simulation<Model, Message, OutMessage>;
31
- /** Runs a function for side effects (e.g. assertions) without breaking the pipe chain. */
32
- export declare const tap: <Model, Message, OutMessage = undefined>(f: (simulation: Simulation<Model, Message, OutMessage>) => void) => (simulation: Simulation<Model, Message, OutMessage>) => Simulation<Model, Message, OutMessage>;
33
- /** A single step in a test story — either a {@link WithStep} or a simulation transform. */
34
- export type StoryStep<Model, Message, OutMessage> = WithStep<NoInfer<Model>> | ((simulation: Simulation<Model, Message, OutMessage>) => Simulation<Model, Message, OutMessage>);
28
+ export declare const resolveAll: (pairs: ReadonlyArray<ResolverPair>) => <Model, Message, OutMessage = undefined>(simulation: StorySimulation<Model, Message, OutMessage>) => StorySimulation<Model, Message, OutMessage>;
29
+ /** Runs a function for side effects (e.g. assertions) without breaking the step chain. */
30
+ export declare const tap: <Model, Message, OutMessage = undefined>(f: (simulation: StorySimulation<Model, Message, OutMessage>) => void) => (simulation: StorySimulation<Model, Message, OutMessage>) => StorySimulation<Model, Message, OutMessage>;
35
31
  /** Executes a test story. Throws if any Commands remain unresolved. */
36
32
  export declare const story: {
37
33
  <Model, Message, OutMessage>(updateFn: (model: Model, message: Message) => readonly [Model, ReadonlyArray<AnyCommand>, OutMessage], ...steps: ReadonlyArray<StoryStep<Model, Message, OutMessage>>): void;
38
34
  <Model, Message>(updateFn: (model: Model, message: Message) => readonly [Model, ReadonlyArray<AnyCommand>], ...steps: ReadonlyArray<StoryStep<Model, Message, undefined>>): void;
39
35
  };
40
- //# sourceMappingURL=index.d.ts.map
36
+ //# sourceMappingURL=story.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"story.d.ts","sourceRoot":"","sources":["../../src/test/story.ts"],"names":[],"mappings":"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"}
@@ -1,26 +1,9 @@
1
1
  import { Array, Predicate, pipe } from 'effect';
2
+ import { assertAllCommandsResolved, assertNoUnresolvedCommands, resolveByName, } from './internal';
2
3
  const toInternal = (simulation) =>
3
4
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
4
5
  simulation;
5
- const resolveByName = (internal, commandName, resolverMessage) => {
6
- const commandIndex = internal.commands.findIndex(({ name }) => name === commandName);
7
- if (commandIndex === -1) {
8
- return undefined;
9
- }
10
- const remainingCommands = Array.remove(internal.commands, commandIndex);
11
- const result = internal.updateFn(internal.model, resolverMessage);
12
- const nextModel = result[0];
13
- const newCommands = result[1];
14
- const outMessage = result.length === 3 ? result[2] : internal.outMessage;
15
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
16
- return {
17
- ...internal,
18
- model: nextModel,
19
- message: resolverMessage,
20
- commands: Array.appendAll(remainingCommands, newCommands),
21
- outMessage,
22
- };
23
- };
6
+ // STEPS
24
7
  /** Sets the initial Model for a test story. */
25
8
  export { with_ as with };
26
9
  const with_ = (model) => {
@@ -38,13 +21,7 @@ const with_ = (model) => {
38
21
  /** Sends a Message through update. Commands stay pending until resolve or resolveAll. */
39
22
  export const message = (message_) => (simulation) => {
40
23
  const internal = toInternal(simulation);
41
- if (Array.isNonEmptyReadonlyArray(internal.commands)) {
42
- const names = pipe(internal.commands, Array.map(({ name }) => ` ${name}`), Array.join('\n'));
43
- throw new Error(`I found unresolved Commands when you sent a new Message:\n\n${names}\n\n` +
44
- 'Resolve all Commands before sending the next Message.\n' +
45
- 'Use Test.resolve(Definition, ResultMessage) for each one,\n' +
46
- 'or Test.resolveAll([...pairs]) to resolve them all at once.');
47
- }
24
+ assertNoUnresolvedCommands(internal.commands, 'when you sent a new Message');
48
25
  const result = internal.updateFn(internal.model, message_);
49
26
  const nextModel = result[0];
50
27
  const commands = result[1];
@@ -85,6 +62,7 @@ export const resolveAll = (pairs) => (simulation) => {
85
62
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
86
63
  resolvers[definition.name] = resultMessage;
87
64
  }
65
+ /* eslint-disable @typescript-eslint/consistent-type-assertions */
88
66
  let current = {
89
67
  ...internal,
90
68
  resolvers: { ...internal.resolvers, ...resolvers },
@@ -99,21 +77,21 @@ export const resolveAll = (pairs) => (simulation) => {
99
77
  if (Predicate.isUndefined(next)) {
100
78
  break;
101
79
  }
102
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
103
80
  current = next;
104
81
  if (depth === MAX_CASCADE_DEPTH - 1) {
105
82
  throw new Error('resolveAll hit the maximum cascade depth (100). ' +
106
83
  'This usually means Commands are producing Commands in an infinite cycle.');
107
84
  }
108
85
  }
109
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
110
86
  return current;
87
+ /* eslint-enable @typescript-eslint/consistent-type-assertions */
111
88
  };
112
- /** Runs a function for side effects (e.g. assertions) without breaking the pipe chain. */
89
+ /** Runs a function for side effects (e.g. assertions) without breaking the step chain. */
113
90
  export const tap = (f) => (simulation) => {
114
91
  f(simulation);
115
92
  return simulation;
116
93
  };
94
+ // STORY
117
95
  /** Executes a test story. Throws if any Commands remain unresolved. */
118
96
  export const story = (updateFn, ...steps) => {
119
97
  /* eslint-disable @typescript-eslint/consistent-type-assertions */
@@ -128,11 +106,5 @@ export const story = (updateFn, ...steps) => {
128
106
  const result = steps.reduce((current, step) => step(current), seed);
129
107
  /* eslint-enable @typescript-eslint/consistent-type-assertions */
130
108
  const internal = toInternal(result);
131
- if (Array.isNonEmptyReadonlyArray(internal.commands)) {
132
- const names = pipe(internal.commands, Array.map(({ name }) => ` ${name}`), Array.join('\n'));
133
- throw new Error(`I found Commands without resolvers:\n\n${names}\n\n` +
134
- 'Every Command produced by update needs to be resolved.\n' +
135
- 'Use Test.resolve(Definition, ResultMessage) for each one,\n' +
136
- 'or Test.resolveAll([...pairs]) to resolve them all at once.');
137
- }
109
+ assertAllCommandsResolved(internal.commands);
138
110
  };
@@ -0,0 +1,19 @@
1
+ declare module 'vitest' {
2
+ interface Assertion<T> {
3
+ toHaveText(expected: string): this;
4
+ toContainText(expected: string): this;
5
+ toHaveClass(expected: string): this;
6
+ toHaveAttr(name: string, value?: string): this;
7
+ toHaveStyle(name: string, value?: string): this;
8
+ toHaveHook(name: string): this;
9
+ toHaveHandler(name: string): this;
10
+ toHaveValue(expected: string): this;
11
+ toBeDisabled(): this;
12
+ toBeEnabled(): this;
13
+ toBeChecked(): this;
14
+ toExist(): this;
15
+ toBeAbsent(): this;
16
+ }
17
+ }
18
+ export {};
19
+ //# sourceMappingURL=vitest-setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vitest-setup.d.ts","sourceRoot":"","sources":["../../src/test/vitest-setup.ts"],"names":[],"mappings":"AAIA,OAAO,QAAQ,QAAQ,CAAC;IAEtB,UAAU,SAAS,CAAC,CAAC;QACnB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;QAClC,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;QACrC,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;QACnC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QAC9C,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QAC/C,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;QAC9B,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;QACjC,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;QACnC,YAAY,IAAI,IAAI,CAAA;QACpB,WAAW,IAAI,IAAI,CAAA;QACnB,WAAW,IAAI,IAAI,CAAA;QACnB,OAAO,IAAI,IAAI,CAAA;QACf,UAAU,IAAI,IAAI,CAAA;KACnB;CACF"}
@@ -0,0 +1,3 @@
1
+ import { expect } from 'vitest';
2
+ import { sceneMatchers } from './matchers';
3
+ expect.extend(sceneMatchers);
@@ -19,8 +19,6 @@ export type ViewConfig<Message> = Readonly<{
19
19
  type?: string;
20
20
  placeholder?: string;
21
21
  }>;
22
- /** Generates the label element ID from the input's base ID. */
23
- export declare const labelId: (id: string) => string;
24
22
  /** Generates the description element ID from the input's base ID. */
25
23
  export declare const descriptionId: (id: string) => string;
26
24
  /** Renders an accessible input by building ARIA attribute groups and delegating layout to the consumer's `toView` callback. */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/input/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAE3C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAItC,yFAAyF;AACzF,MAAM,MAAM,eAAe,CAAC,OAAO,IAAI,QAAQ,CAAC;IAC9C,KAAK,EAAE,aAAa,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;IACxC,KAAK,EAAE,aAAa,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;IACxC,WAAW,EAAE,aAAa,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;CAC/C,CAAC,CAAA;AAEF,wDAAwD;AACxD,MAAM,MAAM,UAAU,CAAC,OAAO,IAAI,QAAQ,CAAC;IACzC,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,CAAC,UAAU,EAAE,eAAe,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;IACtD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAA;IACpC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,CAAC,CAAA;AAEF,+DAA+D;AAC/D,eAAO,MAAM,OAAO,GAAI,IAAI,MAAM,KAAG,MAAuB,CAAA;AAE5D,qEAAqE;AACrE,eAAO,MAAM,aAAa,GAAI,IAAI,MAAM,KAAG,MAA6B,CAAA;AAExE,+HAA+H;AAC/H,eAAO,MAAM,IAAI,GAAI,OAAO,EAAE,QAAQ,UAAU,CAAC,OAAO,CAAC,KAAG,IA0E3D,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/input/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAE3C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAItC,yFAAyF;AACzF,MAAM,MAAM,eAAe,CAAC,OAAO,IAAI,QAAQ,CAAC;IAC9C,KAAK,EAAE,aAAa,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;IACxC,KAAK,EAAE,aAAa,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;IACxC,WAAW,EAAE,aAAa,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;CAC/C,CAAC,CAAA;AAEF,wDAAwD;AACxD,MAAM,MAAM,UAAU,CAAC,OAAO,IAAI,QAAQ,CAAC;IACzC,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,CAAC,UAAU,EAAE,eAAe,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;IACtD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAA;IACpC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,CAAC,CAAA;AAEF,qEAAqE;AACrE,eAAO,MAAM,aAAa,GAAI,IAAI,MAAM,KAAG,MAA6B,CAAA;AAExE,+HAA+H;AAC/H,eAAO,MAAM,IAAI,GAAI,OAAO,EAAE,QAAQ,UAAU,CAAC,OAAO,CAAC,KAAG,IAyE3D,CAAA"}
@@ -1,12 +1,10 @@
1
1
  import { Predicate } from 'effect';
2
2
  import { html } from '../../html';
3
- /** Generates the label element ID from the input's base ID. */
4
- export const labelId = (id) => `${id}-label`;
5
3
  /** Generates the description element ID from the input's base ID. */
6
4
  export const descriptionId = (id) => `${id}-description`;
7
5
  /** Renders an accessible input by building ARIA attribute groups and delegating layout to the consumer's `toView` callback. */
8
6
  export const view = (config) => {
9
- const { AriaDescribedBy, AriaDisabled, AriaInvalid, AriaLabelledBy, Autofocus, DataAttribute, Disabled, Id, Name, OnInput, Placeholder, Type, Value, } = html();
7
+ const { AriaDescribedBy, AriaDisabled, AriaInvalid, Autofocus, DataAttribute, Disabled, For, Id, Name, OnInput, Placeholder, Type, Value, } = html();
10
8
  const { toView, id, onInput, value, isDisabled = false, isInvalid = false, isAutofocus = false, name, type = 'text', placeholder, } = config;
11
9
  const disabledAttributes = isDisabled
12
10
  ? [AriaDisabled(true), Disabled(true), DataAttribute('disabled', '')]
@@ -24,7 +22,6 @@ export const view = (config) => {
24
22
  const allInputAttributes = [
25
23
  Id(id),
26
24
  Type(type),
27
- AriaLabelledBy(labelId(id)),
28
25
  AriaDescribedBy(descriptionId(id)),
29
26
  ...disabledAttributes,
30
27
  ...invalidAttributes,
@@ -34,7 +31,7 @@ export const view = (config) => {
34
31
  ...nameAttributes,
35
32
  ...placeholderAttributes,
36
33
  ];
37
- const labelAttributes = [Id(labelId(id))];
34
+ const labelAttributes = [For(id)];
38
35
  const descriptionAttributes = [Id(descriptionId(id))];
39
36
  return toView({
40
37
  input: allInputAttributes,
@@ -1,3 +1,3 @@
1
- export { view, labelId, descriptionId } from './index';
1
+ export { view, descriptionId } from './index';
2
2
  export type { ViewConfig, InputAttributes } from './index';
3
3
  //# sourceMappingURL=public.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../../src/ui/input/public.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAEtD,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA"}
1
+ {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../../src/ui/input/public.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAE7C,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA"}
@@ -1 +1 @@
1
- export { view, labelId, descriptionId } from './index';
1
+ export { view, descriptionId } from './index';
@@ -17,8 +17,6 @@ export type ViewConfig<Message> = Readonly<{
17
17
  isAutofocus?: boolean;
18
18
  name?: string;
19
19
  }>;
20
- /** Generates the label element ID from the select's base ID. */
21
- export declare const labelId: (id: string) => string;
22
20
  /** Generates the description element ID from the select's base ID. */
23
21
  export declare const descriptionId: (id: string) => string;
24
22
  /** Renders an accessible select by building ARIA attribute groups and delegating layout to the consumer's `toView` callback. */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/select/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAE3C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAItC,0FAA0F;AAC1F,MAAM,MAAM,gBAAgB,CAAC,OAAO,IAAI,QAAQ,CAAC;IAC/C,MAAM,EAAE,aAAa,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;IACzC,KAAK,EAAE,aAAa,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;IACxC,WAAW,EAAE,aAAa,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;CAC/C,CAAC,CAAA;AAEF,wDAAwD;AACxD,MAAM,MAAM,UAAU,CAAC,OAAO,IAAI,QAAQ,CAAC;IACzC,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,CAAC,UAAU,EAAE,gBAAgB,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;IACvD,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAA;IACrC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAC,CAAA;AAEF,gEAAgE;AAChE,eAAO,MAAM,OAAO,GAAI,IAAI,MAAM,KAAG,MAAuB,CAAA;AAE5D,sEAAsE;AACtE,eAAO,MAAM,aAAa,GAAI,IAAI,MAAM,KAAG,MAA6B,CAAA;AAExE,gIAAgI;AAChI,eAAO,MAAM,IAAI,GAAI,OAAO,EAAE,QAAQ,UAAU,CAAC,OAAO,CAAC,KAAG,IAkE3D,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/select/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAE3C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAItC,0FAA0F;AAC1F,MAAM,MAAM,gBAAgB,CAAC,OAAO,IAAI,QAAQ,CAAC;IAC/C,MAAM,EAAE,aAAa,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;IACzC,KAAK,EAAE,aAAa,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;IACxC,WAAW,EAAE,aAAa,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;CAC/C,CAAC,CAAA;AAEF,wDAAwD;AACxD,MAAM,MAAM,UAAU,CAAC,OAAO,IAAI,QAAQ,CAAC;IACzC,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,CAAC,UAAU,EAAE,gBAAgB,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;IACvD,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAA;IACrC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAC,CAAA;AAEF,sEAAsE;AACtE,eAAO,MAAM,aAAa,GAAI,IAAI,MAAM,KAAG,MAA6B,CAAA;AAExE,gIAAgI;AAChI,eAAO,MAAM,IAAI,GAAI,OAAO,EAAE,QAAQ,UAAU,CAAC,OAAO,CAAC,KAAG,IAiE3D,CAAA"}