foldkit 0.133.0 → 0.135.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.
- package/README.md +13 -17
- package/dist/canvas/view.d.ts +18 -11
- package/dist/canvas/view.d.ts.map +1 -1
- package/dist/canvas/view.js +18 -11
- package/dist/customElement/index.d.ts +12 -6
- package/dist/customElement/index.d.ts.map +1 -1
- package/dist/customElement/index.js +17 -4
- package/dist/html/index.d.ts +625 -1601
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +52 -15
- package/dist/html/public.d.ts +2 -2
- package/dist/html/public.d.ts.map +1 -1
- package/dist/html/public.js +1 -1
- package/dist/html/runtimeSingleton.d.ts +1 -1
- package/dist/html/runtimeSingleton.d.ts.map +1 -1
- package/dist/html/runtimeSingleton.js +1 -1
- package/dist/html/submodel.d.ts +51 -19
- package/dist/html/submodel.d.ts.map +1 -1
- package/dist/html/submodel.js +37 -13
- package/dist/managedResource/managedResource.d.ts +21 -10
- package/dist/managedResource/managedResource.d.ts.map +1 -1
- package/dist/managedResource/managedResource.js +6 -1
- package/dist/runtime/crashUI.d.ts.map +1 -1
- package/dist/runtime/crashUI.js +29 -30
- package/dist/runtime/runtime.d.ts +9 -6
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +9 -8
- package/dist/test/apps/attributes.d.ts +2 -2
- package/dist/test/apps/attributes.d.ts.map +1 -1
- package/dist/test/apps/attributes.js +1 -3
- package/dist/test/apps/bubbling.d.ts +2 -2
- package/dist/test/apps/bubbling.d.ts.map +1 -1
- package/dist/test/apps/bubbling.js +1 -3
- package/dist/test/apps/colorPicker.d.ts +28 -0
- package/dist/test/apps/colorPicker.d.ts.map +1 -0
- package/dist/test/apps/colorPicker.js +43 -0
- package/dist/test/apps/counter.d.ts +5 -3
- package/dist/test/apps/counter.d.ts.map +1 -1
- package/dist/test/apps/counter.js +12 -7
- package/dist/test/apps/crashOnRender.d.ts +2 -2
- package/dist/test/apps/crashOnRender.d.ts.map +1 -1
- package/dist/test/apps/crashOnRender.js +1 -3
- package/dist/test/apps/drafts.d.ts +2 -2
- package/dist/test/apps/drafts.d.ts.map +1 -1
- package/dist/test/apps/drafts.js +1 -3
- package/dist/test/apps/feedSocket.d.ts +74 -0
- package/dist/test/apps/feedSocket.d.ts.map +1 -0
- package/dist/test/apps/feedSocket.js +69 -0
- package/dist/test/apps/fileUpload.d.ts +2 -2
- package/dist/test/apps/fileUpload.d.ts.map +1 -1
- package/dist/test/apps/fileUpload.js +1 -3
- package/dist/test/apps/interactions.d.ts +2 -2
- package/dist/test/apps/interactions.d.ts.map +1 -1
- package/dist/test/apps/interactions.js +1 -3
- package/dist/test/apps/keypress.d.ts +2 -2
- package/dist/test/apps/keypress.d.ts.map +1 -1
- package/dist/test/apps/keypress.js +1 -3
- package/dist/test/apps/login.d.ts +2 -2
- package/dist/test/apps/login.d.ts.map +1 -1
- package/dist/test/apps/login.js +1 -3
- package/dist/test/apps/logoutButton.d.ts +2 -2
- package/dist/test/apps/logoutButton.d.ts.map +1 -1
- package/dist/test/apps/logoutButton.js +1 -3
- package/dist/test/apps/mountPanel.d.ts +4 -4
- package/dist/test/apps/mountPanel.d.ts.map +1 -1
- package/dist/test/apps/mountPanel.js +3 -7
- package/dist/test/apps/multiRole.d.ts +2 -2
- package/dist/test/apps/multiRole.d.ts.map +1 -1
- package/dist/test/apps/multiRole.js +1 -3
- package/dist/test/apps/pointer.d.ts +2 -2
- package/dist/test/apps/pointer.d.ts.map +1 -1
- package/dist/test/apps/pointer.js +1 -3
- package/dist/test/apps/resumeUpload.d.ts +2 -2
- package/dist/test/apps/resumeUpload.d.ts.map +1 -1
- package/dist/test/apps/resumeUpload.js +3 -6
- package/dist/test/apps/scorePanel.d.ts +32 -0
- package/dist/test/apps/scorePanel.d.ts.map +1 -0
- package/dist/test/apps/scorePanel.js +19 -0
- package/dist/test/apps/uploads.d.ts +2 -2
- package/dist/test/apps/uploads.d.ts.map +1 -1
- package/dist/test/apps/uploads.js +1 -3
- package/dist/test/scene.d.ts +81 -9
- package/dist/test/scene.d.ts.map +1 -1
- package/dist/test/scene.js +206 -26
- package/dist/test/story.d.ts +4 -5
- package/dist/test/story.d.ts.map +1 -1
- package/dist/test/story.js +1 -2
- package/package.json +1 -1
package/dist/test/scene.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { Array, Context, Effect, Function, Option, Predicate, String as String_, pipe, } from 'effect';
|
|
1
|
+
import { Array, Context, Effect, Equal, Function, Option, Predicate, String as String_, pipe, } from 'effect';
|
|
2
2
|
import { dual } from 'effect/Function';
|
|
3
|
-
import {
|
|
3
|
+
import { kebabToPascal } from '../customElement/index.js';
|
|
4
|
+
import { FOLDKIT_MOUNT_KEY, FileHandlerSymbol, __clearRuntime as clearHtmlRuntime, __htmlBuilder as htmlBuilderFor, __setRuntime as setHtmlRuntime, } from '../html/index.js';
|
|
4
5
|
import { MountTracker } from '../mount/index.js';
|
|
5
6
|
import { Dispatch } from '../runtime/index.js';
|
|
6
7
|
import { assertAllCommandsResolved, assertAllMountsResolved, assertAllUnmountsAcknowledged, assertExactCommands, assertExactMounts, assertHasCommands, assertHasMounts, assertNoUnacknowledgedUnmounts, assertNoUnresolvedCommands, assertNoUnresolvedMounts, assertResolveUnambiguous, assertZeroCommands, assertZeroMounts, formatCommand, formatMatcher, formatMountList, formatMountMatcher, mountMatches, resolveAllInternal, resolveByMatcher, resolveMountByMatcher, } from './internal.js';
|
|
@@ -128,7 +129,7 @@ const renderView = (viewFn, model, dispatch) => {
|
|
|
128
129
|
setHtmlRuntime(dispatch.dispatchSync, sceneContext);
|
|
129
130
|
let result;
|
|
130
131
|
try {
|
|
131
|
-
result = viewFn(model);
|
|
132
|
+
result = viewFn(model, htmlBuilderFor());
|
|
132
133
|
}
|
|
133
134
|
finally {
|
|
134
135
|
clearHtmlRuntime();
|
|
@@ -157,7 +158,7 @@ const EVENT_NAMES = {
|
|
|
157
158
|
pointerdown: 'OnPointerDown',
|
|
158
159
|
pointerup: 'OnPointerUp',
|
|
159
160
|
};
|
|
160
|
-
const
|
|
161
|
+
const maybeCaptureFromElement = (simulation, element, description, eventName, invokeHandler) => {
|
|
161
162
|
const internal = toInternal(simulation);
|
|
162
163
|
const maybeHandler = Option.fromNullishOr(element.data?.on?.[eventName]);
|
|
163
164
|
if (Option.isNone(maybeHandler)) {
|
|
@@ -168,27 +169,9 @@ const captureFromElement = (simulation, element, description, eventName, invokeH
|
|
|
168
169
|
internal.capturingDispatch.reset();
|
|
169
170
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
170
171
|
invokeHandler(maybeHandler.value);
|
|
171
|
-
|
|
172
|
-
if (Predicate.isUndefined(captured)) {
|
|
173
|
-
return simulation;
|
|
174
|
-
}
|
|
175
|
-
assertNoUnresolvedCommands(internal.commands, 'when an interaction dispatched a new Message');
|
|
176
|
-
assertNoUnresolvedMounts(internal.mounts, 'when an interaction dispatched a new Message');
|
|
177
|
-
assertNoUnacknowledgedUnmounts(unacknowledgedEndedMountsOf(internal.mountSlots), 'when an interaction dispatched a new Message');
|
|
178
|
-
/* eslint-disable @typescript-eslint/consistent-type-assertions */
|
|
179
|
-
const capturedMessage = captured;
|
|
180
|
-
const result = internal.updateFn(internal.model, capturedMessage);
|
|
181
|
-
const [nextModel, commands] = result;
|
|
182
|
-
const outMessage = result.length === 3 ? result[2] : internal.outMessage;
|
|
183
|
-
return {
|
|
184
|
-
...internal,
|
|
185
|
-
model: nextModel,
|
|
186
|
-
message: capturedMessage,
|
|
187
|
-
commands: Array.appendAll(internal.commands, commands),
|
|
188
|
-
outMessage,
|
|
189
|
-
};
|
|
190
|
-
/* eslint-enable @typescript-eslint/consistent-type-assertions */
|
|
172
|
+
return Option.map(Option.fromNullishOr(internal.capturingDispatch.getCapturedMessage()), captured => applyExternalMessage(simulation, captured, 'when an interaction dispatched a new Message'));
|
|
191
173
|
};
|
|
174
|
+
const captureFromElement = (simulation, element, description, eventName, invokeHandler) => Option.getOrElse(maybeCaptureFromElement(simulation, element, description, eventName, invokeHandler), () => simulation);
|
|
192
175
|
const invokeAndCapture = (simulation, target, eventName, invokeHandler) => {
|
|
193
176
|
const internal = toInternal(simulation);
|
|
194
177
|
const scopedTarget = applyScopeToTarget(internal.scope, target);
|
|
@@ -262,8 +245,7 @@ const assertFileHandler = (handler, expectedTag, helperName) => {
|
|
|
262
245
|
};
|
|
263
246
|
// STEPS
|
|
264
247
|
/** Sets the initial Model for a scene test. */
|
|
265
|
-
export
|
|
266
|
-
const with_ = (model) => {
|
|
248
|
+
export const given = (model) => {
|
|
267
249
|
const step = (simulation) => {
|
|
268
250
|
const internal = toInternal(simulation);
|
|
269
251
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
@@ -415,6 +397,119 @@ const expectEndedMountsStep = (...matchers) => (simulation) => {
|
|
|
415
397
|
};
|
|
416
398
|
/* eslint-enable @typescript-eslint/consistent-type-assertions */
|
|
417
399
|
};
|
|
400
|
+
const applyExternalMessage = (simulation, message, context) => {
|
|
401
|
+
const internal = toInternal(simulation);
|
|
402
|
+
assertNoUnresolvedCommands(internal.commands, context);
|
|
403
|
+
assertNoUnresolvedMounts(internal.mounts, context);
|
|
404
|
+
assertNoUnacknowledgedUnmounts(unacknowledgedEndedMountsOf(internal.mountSlots), context);
|
|
405
|
+
/* eslint-disable @typescript-eslint/consistent-type-assertions */
|
|
406
|
+
const messageAsParent = message;
|
|
407
|
+
const result = internal.updateFn(internal.model, messageAsParent);
|
|
408
|
+
const [nextModel, commands] = result;
|
|
409
|
+
const outMessage = result.length === 3 ? result[2] : internal.outMessage;
|
|
410
|
+
return {
|
|
411
|
+
...internal,
|
|
412
|
+
model: nextModel,
|
|
413
|
+
message: messageAsParent,
|
|
414
|
+
commands: Array.appendAll(internal.commands, commands),
|
|
415
|
+
outMessage,
|
|
416
|
+
};
|
|
417
|
+
/* eslint-enable @typescript-eslint/consistent-type-assertions */
|
|
418
|
+
};
|
|
419
|
+
/** Feeds a Message through update as if a Subscription had emitted it,
|
|
420
|
+
* then re-renders. Use it for Messages whose real cause is a Subscription
|
|
421
|
+
* (a timer tick, a WebSocket frame, a global listener), which have no
|
|
422
|
+
* element in the rendered tree to interact with. Do NOT reach for it when
|
|
423
|
+
* the Message has a DOM affordance: click the actual button instead, so
|
|
424
|
+
* the test exercises the handler wiring this step skips. Like an
|
|
425
|
+
* interaction, it throws if unresolved Commands, unresolved Mounts, or
|
|
426
|
+
* unacknowledged unmounts are pending. */
|
|
427
|
+
const emitSubscriptionMessage = (message) => (simulation) => applyExternalMessage(simulation, message, 'when a Subscription emitted a new Message');
|
|
428
|
+
const MANAGED_RESOURCE_CONTEXT = 'when a ManagedResource dispatched a new Message';
|
|
429
|
+
/** Declares that a ManagedResource's acquire succeeded, feeding the entry's
|
|
430
|
+
* `onAcquired` Message through update the way the runtime would. The step
|
|
431
|
+
* takes exactly the arguments the entry's `onAcquired` declares: a handler
|
|
432
|
+
* that consumes the acquired value needs one here, and a handler that
|
|
433
|
+
* ignores it needs none. Requires the current Model to request the
|
|
434
|
+
* resource: the runtime only acquires while `modelToMaybeRequirements`
|
|
435
|
+
* returns Some, so drive the Model into that state through real steps
|
|
436
|
+
* first. */
|
|
437
|
+
const acquireManagedResource = (entry, ...args) => (simulation) => {
|
|
438
|
+
const internal = toInternal(simulation);
|
|
439
|
+
const maybeRequirements = entry.modelToMaybeRequirements(internal.model);
|
|
440
|
+
if (Option.isNone(maybeRequirements)) {
|
|
441
|
+
throw new Error(`I tried to acquire the ManagedResource "${entry.resource.key}" but the current Model does not request it.\n\n` +
|
|
442
|
+
'modelToMaybeRequirements returned None. The runtime only acquires while the Model requests the resource, so drive that transition through real steps first.');
|
|
443
|
+
}
|
|
444
|
+
return applyExternalMessage(simulation, entry.onAcquired(...args), MANAGED_RESOURCE_CONTEXT);
|
|
445
|
+
};
|
|
446
|
+
/** Declares that a ManagedResource's acquire failed, feeding the entry's
|
|
447
|
+
* `onAcquireError(error)` Message through update the way the runtime
|
|
448
|
+
* would. Requires the current Model to request the resource, the same as
|
|
449
|
+
* {@link acquireManagedResource}: the runtime only attempts acquisition
|
|
450
|
+
* while `modelToMaybeRequirements` returns Some. */
|
|
451
|
+
const failAcquireManagedResource = (entry, error) => (simulation) => {
|
|
452
|
+
const internal = toInternal(simulation);
|
|
453
|
+
const maybeRequirements = entry.modelToMaybeRequirements(internal.model);
|
|
454
|
+
if (Option.isNone(maybeRequirements)) {
|
|
455
|
+
throw new Error(`I tried to fail acquiring the ManagedResource "${entry.resource.key}" but the current Model does not request it.\n\n` +
|
|
456
|
+
'modelToMaybeRequirements returned None. The runtime only attempts acquisition while the Model requests the resource, so drive that transition through real steps first.');
|
|
457
|
+
}
|
|
458
|
+
return applyExternalMessage(simulation, entry.onAcquireError(error), MANAGED_RESOURCE_CONTEXT);
|
|
459
|
+
};
|
|
460
|
+
/** Declares that a ManagedResource was released, feeding the entry's
|
|
461
|
+
* `onReleased()` Message through update. Models the runtime's Some to
|
|
462
|
+
* None transition, which releases and then dispatches `onReleased()`, so
|
|
463
|
+
* it requires the current Model to no longer request the resource: drive
|
|
464
|
+
* the Model out of the requesting state through real steps first. The
|
|
465
|
+
* runtime's Some to Some re-acquire (structurally changed requirements),
|
|
466
|
+
* which also dispatches `onReleased()` and then `onAcquired(value)` while
|
|
467
|
+
* the Model still requests the resource, has no step yet. */
|
|
468
|
+
const releaseManagedResource = (entry) => (simulation) => {
|
|
469
|
+
const internal = toInternal(simulation);
|
|
470
|
+
const maybeRequirements = entry.modelToMaybeRequirements(internal.model);
|
|
471
|
+
if (Option.isSome(maybeRequirements)) {
|
|
472
|
+
throw new Error(`I tried to release the ManagedResource "${entry.resource.key}" but the current Model still requests it.\n\n` +
|
|
473
|
+
'modelToMaybeRequirements returned Some. This step models the Some to None transition, so drive the Model out of the requesting state through real steps first.');
|
|
474
|
+
}
|
|
475
|
+
return applyExternalMessage(simulation, entry.onReleased(), MANAGED_RESOURCE_CONTEXT);
|
|
476
|
+
};
|
|
477
|
+
/** Dispatches a CustomEvent a rendered custom element declares, feeding the
|
|
478
|
+
* Message its `On*` event mapping produces through update. The event name
|
|
479
|
+
* and detail are typed by the spec's event Schemas. The element must be in
|
|
480
|
+
* the rendered tree with the event's attribute attached, so the test
|
|
481
|
+
* exercises the same mapping the browser event would. */
|
|
482
|
+
const emitCustomElementEvent = (spec, target, eventName, detail) => (simulation) => {
|
|
483
|
+
if (!Object.hasOwn(spec.events, eventName)) {
|
|
484
|
+
const declared = Object.keys(spec.events).join(', ');
|
|
485
|
+
throw new Error(`I tried to emit "${eventName}" but the '${spec.tag}' element does not declare it.\n\n` +
|
|
486
|
+
`Declared events: ${String_.isEmpty(declared) ? '(none)' : declared}.`);
|
|
487
|
+
}
|
|
488
|
+
const internal = toInternal(simulation);
|
|
489
|
+
const scopedTarget = applyScopeToTarget(internal.scope, target);
|
|
490
|
+
const { maybeElement, description } = resolveTarget(internal.html, scopedTarget);
|
|
491
|
+
if (Option.isNone(maybeElement)) {
|
|
492
|
+
throw new Error(`I could not find an element matching ${description}.\n\n` +
|
|
493
|
+
'Check that your selector matches an element in the current view.');
|
|
494
|
+
}
|
|
495
|
+
const element = maybeElement.value;
|
|
496
|
+
if (element.data?.on?.[eventName] === undefined) {
|
|
497
|
+
throw new Error(`I found an element matching ${description} but it has no ${eventName} handler.\n\n` +
|
|
498
|
+
`Make sure the element has the On${kebabToPascal(eventName)} attribute from its CustomElement builder.`);
|
|
499
|
+
}
|
|
500
|
+
// NOTE: the OnCustomEvent handler only dispatches when the event is a
|
|
501
|
+
// real CustomEvent instance, so the synthetic event must be constructed
|
|
502
|
+
// with `new CustomEvent(...)`, never a plain object literal like the
|
|
503
|
+
// other interaction helpers use. A None capture means nothing was
|
|
504
|
+
// dispatched, and since the handler dispatches unconditionally for a
|
|
505
|
+
// genuine CustomEvent, that can only be the instanceof check failing
|
|
506
|
+
// (a CustomEvent realm mismatch in the test environment).
|
|
507
|
+
const maybeNext = maybeCaptureFromElement(simulation, element, description, eventName, handler => {
|
|
508
|
+
handler(new CustomEvent(eventName, { detail }));
|
|
509
|
+
});
|
|
510
|
+
return Option.getOrThrowWith(maybeNext, () => new Error(`I dispatched "${eventName}" on the element matching ${description} but its handler produced no Message.\n\n` +
|
|
511
|
+
"The OnCustomEvent handler only dispatches for CustomEvent instances, so the synthetic event failed the runtime's instanceof check. This points to a CustomEvent realm mismatch in the test environment."));
|
|
512
|
+
};
|
|
418
513
|
/** Steps that operate on the pending Commands of a scene simulation.
|
|
419
514
|
* Destructure as `const { Command } = Scene` for concise call sites. */
|
|
420
515
|
export const Command = {
|
|
@@ -450,6 +545,73 @@ export const Mount = {
|
|
|
450
545
|
* every Mount that fires and then unmounts during a scene. */
|
|
451
546
|
expectEnded: expectEndedMountsStep,
|
|
452
547
|
};
|
|
548
|
+
/** Steps that model Messages arriving from a Subscription.
|
|
549
|
+
* Destructure as `const { Subscription } = Scene` for concise call sites. */
|
|
550
|
+
export const Subscription = {
|
|
551
|
+
/** Feeds a Message through update as if a Subscription had emitted it,
|
|
552
|
+
* then re-renders. Only for Messages whose real cause is a Subscription;
|
|
553
|
+
* if the Message has a DOM affordance, click it instead. */
|
|
554
|
+
emit: emitSubscriptionMessage,
|
|
555
|
+
};
|
|
556
|
+
/** Steps that model the lifecycle Messages of a ManagedResource. The test
|
|
557
|
+
* declares the lifecycle outcome the way `Scene.Command.resolve` declares a
|
|
558
|
+
* Command result, and each step checks the current Model against the
|
|
559
|
+
* entry's `modelToMaybeRequirements` gate first, mirroring the runtime's
|
|
560
|
+
* None to Some and Some to None transitions. The Some to Some re-acquire
|
|
561
|
+
* transition has no step yet. Destructure as
|
|
562
|
+
* `const { ManagedResource } = Scene` for concise call sites. */
|
|
563
|
+
export const ManagedResource = {
|
|
564
|
+
/** Declares a successful acquire, feeding the entry's `onAcquired` Message
|
|
565
|
+
* through update. Takes exactly the arguments `onAcquired` declares. The
|
|
566
|
+
* current Model must request the resource. */
|
|
567
|
+
acquire: acquireManagedResource,
|
|
568
|
+
/** Declares a failed acquire, feeding `onAcquireError(error)` through
|
|
569
|
+
* update. The current Model must request the resource. */
|
|
570
|
+
failAcquire: failAcquireManagedResource,
|
|
571
|
+
/** Declares a release, feeding `onReleased()` through update. The current
|
|
572
|
+
* Model must no longer request the resource. */
|
|
573
|
+
release: releaseManagedResource,
|
|
574
|
+
};
|
|
575
|
+
/** Steps that model CustomEvents arriving from a rendered custom element.
|
|
576
|
+
* Destructure as `const { CustomElement } = Scene` for concise call sites. */
|
|
577
|
+
export const CustomElement = {
|
|
578
|
+
/** Dispatches a declared CustomEvent through the element's event mapping.
|
|
579
|
+
* The event name and detail are typed by the spec's event Schemas. */
|
|
580
|
+
emit: emitCustomElementEvent,
|
|
581
|
+
};
|
|
582
|
+
/** Asserts that the OutMessage is Some with the expected value.
|
|
583
|
+
*
|
|
584
|
+
* The tracked OutMessage is the third element of the most recent update
|
|
585
|
+
* result that had one. An update branch that returns a two-tuple leaves
|
|
586
|
+
* the previous value latched in place, so keep every branch of an
|
|
587
|
+
* OutMessage-returning update on the three-tuple shape, returning
|
|
588
|
+
* `Option.none()` when there is nothing to report. */
|
|
589
|
+
export const expectOutMessage = (expected) => (simulation) => {
|
|
590
|
+
const internal = toInternal(simulation);
|
|
591
|
+
const outMessage = internal.outMessage;
|
|
592
|
+
if (!Option.isOption(outMessage) ||
|
|
593
|
+
Option.isNone(outMessage) ||
|
|
594
|
+
!Equal.equals(outMessage.value, expected)) {
|
|
595
|
+
throw new Error(`Expected OutMessage:\n\n Some(${JSON.stringify(expected)})\n\nBut got:\n\n ${JSON.stringify(outMessage)}`);
|
|
596
|
+
}
|
|
597
|
+
return simulation;
|
|
598
|
+
};
|
|
599
|
+
/** Asserts that the OutMessage is None.
|
|
600
|
+
*
|
|
601
|
+
* The tracked OutMessage is the third element of the most recent update
|
|
602
|
+
* result that had one. An update branch that returns a two-tuple leaves
|
|
603
|
+
* the previous value latched in place, so keep every branch of an
|
|
604
|
+
* OutMessage-returning update on the three-tuple shape, returning
|
|
605
|
+
* `Option.none()` when there is nothing to report. */
|
|
606
|
+
export const expectNoOutMessage = () => (simulation) => {
|
|
607
|
+
const internal = toInternal(simulation);
|
|
608
|
+
const outMessage = internal.outMessage;
|
|
609
|
+
if (!Predicate.isUndefined(outMessage) &&
|
|
610
|
+
!(Option.isOption(outMessage) && Option.isNone(outMessage))) {
|
|
611
|
+
throw new Error(`Expected no OutMessage but got:\n\n ${JSON.stringify(outMessage)}`);
|
|
612
|
+
}
|
|
613
|
+
return simulation;
|
|
614
|
+
};
|
|
453
615
|
/** Runs a function for side effects (e.g. assertions) without breaking the step chain. */
|
|
454
616
|
export const tap = (f) => (simulation) => {
|
|
455
617
|
f(simulation);
|
|
@@ -960,6 +1122,24 @@ export const expectAll = (locatorAll) => ({
|
|
|
960
1122
|
...buildExpectAllChain(locatorAll, false),
|
|
961
1123
|
not: buildExpectAllChain(locatorAll, true),
|
|
962
1124
|
});
|
|
1125
|
+
// VIEW ADAPTERS
|
|
1126
|
+
/** Adapts a Submodel view that declares `ViewInputs` to the `(model, h)`
|
|
1127
|
+
* shape `Scene.scene` takes. `defaults` supplies the full `ViewInputs`
|
|
1128
|
+
* once; the returned factory accepts per-test overrides for everything
|
|
1129
|
+
* except `toView`, so tests vary value inputs while the renderer stays
|
|
1130
|
+
* pinned:
|
|
1131
|
+
*
|
|
1132
|
+
* ```ts
|
|
1133
|
+
* const sceneView = Scene.withViewInputs(view, {
|
|
1134
|
+
* value: 5,
|
|
1135
|
+
* toView: testToView,
|
|
1136
|
+
* })
|
|
1137
|
+
*
|
|
1138
|
+
* Scene.scene({ update, view: sceneView() }, ...)
|
|
1139
|
+
* Scene.scene({ update, view: sceneView({ isDisabled: true }) }, ...)
|
|
1140
|
+
* ```
|
|
1141
|
+
*/
|
|
1142
|
+
export const withViewInputs = (view, defaults) => (overrides) => (model, h) => view(model, { ...defaults, ...overrides }, h);
|
|
963
1143
|
// SCENE
|
|
964
1144
|
/** Executes a scene test. Throws if any Commands remain unresolved. */
|
|
965
1145
|
export const scene = (config, ...steps) => {
|
package/dist/test/story.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ export type StorySimulation<Model, Message, OutMessage = undefined> = Readonly<{
|
|
|
26
26
|
outMessage: OutMessage;
|
|
27
27
|
}>;
|
|
28
28
|
/** A callable step that sets the initial Model. Carries phantom type for compile-time validation. */
|
|
29
|
-
export type
|
|
29
|
+
export type GivenStep<Model> = Readonly<{
|
|
30
30
|
_phantomModel: Model;
|
|
31
31
|
}> & (<M, Message, OutMessage = undefined>(simulation: StorySimulation<M, Message, OutMessage>) => StorySimulation<M, Message, OutMessage>);
|
|
32
32
|
/** A model-assertion step produced by {@link model}. */
|
|
@@ -34,12 +34,11 @@ export type ModelStep<Model> = Readonly<{
|
|
|
34
34
|
readonly _tag: 'ModelStep';
|
|
35
35
|
readonly assert: (model: Model) => void;
|
|
36
36
|
}>;
|
|
37
|
-
/** A single step in a story: a {@link
|
|
37
|
+
/** A single step in a story: a {@link GivenStep}, a {@link ModelStep},
|
|
38
38
|
* or a simulation transform. */
|
|
39
|
-
export type StoryStep<Model> =
|
|
39
|
+
export type StoryStep<Model> = GivenStep<NoInfer<Model>> | ModelStep<NoInfer<Model>> | ((sim: StorySimulation<any, any, any>) => StorySimulation<any, any, any>);
|
|
40
40
|
/** Sets the initial Model for a test story. */
|
|
41
|
-
export
|
|
42
|
-
declare const with_: <Model>(model: Model) => WithStep<Model>;
|
|
41
|
+
export declare const given: <Model>(model: Model) => GivenStep<Model>;
|
|
43
42
|
/** Sends a Message through update. Commands stay pending until resolve or
|
|
44
43
|
* resolveAll. */
|
|
45
44
|
export declare const message: <MessageInput>(message_: MessageInput) => <Model, Message, OutMessage>(simulation: StorySimulation<Model, Message, OutMessage>) => StorySimulation<Model, Message, OutMessage>;
|
package/dist/test/story.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"story.d.ts","sourceRoot":"","sources":["../../src/test/story.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,MAAM,EAAS,MAAM,EAAmB,MAAM,QAAQ,CAAA;AAEtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAC5D,OAAO,KAAK,KAAK,aAAa,MAAM,mCAAmC,CAAA;AACvE,OAAO,KAAK,EACV,UAAU,EAEV,cAAc,EACd,QAAQ,EAET,MAAM,eAAe,CAAA;AActB,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAA;AAEpD;;8EAE8E;AAC9E,KAAK,kBAAkB,CAAC,aAAa,GAAG,OAAO,IAAI,QAAQ,CAAC;IAC1D,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;CAC/C,CAAC,CAAA;AAEF;;;;oEAIoE;AACpE,KAAK,2BAA2B,CAAC,IAAI,SAAS,MAAM,EAAE,aAAa,IAC/D,iBAAiB,CAAC,IAAI,EAAE,aAAa,CAAC,GACtC,aAAa,CAAC,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,GAC5E,aAAa,CAAC,kBAAkB,CAC9B,IAAI,EACJ,GAAG,EACH,GAAG,EACH,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,GAAG,EAAE,GAAG,CAAC,CACvC,GACD,aAAa,CAAC,2BAA2B,CACvC,IAAI,EACJ,GAAG,EACH,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,GAAG,EAAE,GAAG,CAAC,CACvC,CAAA;AAEL,yDAAyD;AACzD,MAAM,MAAM,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,UAAU,GAAG,SAAS,IAAI,QAAQ,CAAC;IAC7E,iEAAiE;IACjE,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,KAAK,EAAE,KAAK,CAAA;IACZ,QAAQ,EAAE,aAAa,CAAC,UAAU,CAAC,CAAA;IACnC,UAAU,EAAE,UAAU,CAAA;CACvB,CAAC,CAAA;AAEF,qGAAqG;AACrG,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"story.d.ts","sourceRoot":"","sources":["../../src/test/story.ts"],"names":[],"mappings":"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"}
|
package/dist/test/story.js
CHANGED
|
@@ -5,8 +5,7 @@ const toInternal = (simulation) =>
|
|
|
5
5
|
simulation;
|
|
6
6
|
// STEPS
|
|
7
7
|
/** Sets the initial Model for a test story. */
|
|
8
|
-
export
|
|
9
|
-
const with_ = (model) => {
|
|
8
|
+
export const given = (model) => {
|
|
10
9
|
const step = (simulation) => {
|
|
11
10
|
const internal = toInternal(simulation);
|
|
12
11
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|