foldkit 0.43.1 → 0.44.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 (48) hide show
  1. package/dist/devtools/overlay.d.ts.map +1 -1
  2. package/dist/devtools/overlay.js +4 -67
  3. package/dist/devtools/store.d.ts +10 -2
  4. package/dist/devtools/store.d.ts.map +1 -1
  5. package/dist/devtools/store.js +79 -2
  6. package/dist/index.d.ts +2 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +2 -1
  9. package/dist/runtime/runtime.d.ts.map +1 -1
  10. package/dist/runtime/runtime.js +2 -2
  11. package/dist/test/apps/counter.d.ts +38 -0
  12. package/dist/test/apps/counter.d.ts.map +1 -0
  13. package/dist/test/apps/counter.js +25 -0
  14. package/dist/test/apps/formChild.d.ts +69 -0
  15. package/dist/test/apps/formChild.d.ts.map +1 -0
  16. package/dist/test/apps/formChild.js +84 -0
  17. package/dist/test/apps/keypress.d.ts +23 -0
  18. package/dist/test/apps/keypress.d.ts.map +1 -0
  19. package/dist/test/apps/keypress.js +39 -0
  20. package/dist/test/apps/login.d.ts +46 -0
  21. package/dist/test/apps/login.d.ts.map +1 -0
  22. package/dist/test/apps/login.js +83 -0
  23. package/dist/test/apps/logoutButton.d.ts +17 -0
  24. package/dist/test/apps/logoutButton.d.ts.map +1 -0
  25. package/dist/test/apps/logoutButton.js +22 -0
  26. package/dist/test/internal.d.ts +25 -0
  27. package/dist/test/internal.d.ts.map +1 -0
  28. package/dist/test/internal.js +39 -0
  29. package/dist/test/matchers.d.ts +94 -0
  30. package/dist/test/matchers.d.ts.map +1 -0
  31. package/dist/test/matchers.js +283 -0
  32. package/dist/test/public.d.ts +2 -2
  33. package/dist/test/public.d.ts.map +1 -1
  34. package/dist/test/public.js +2 -1
  35. package/dist/test/query.d.ts +87 -0
  36. package/dist/test/query.d.ts.map +1 -0
  37. package/dist/test/query.js +226 -0
  38. package/dist/test/scene.d.ts +106 -0
  39. package/dist/test/scene.d.ts.map +1 -0
  40. package/dist/test/scene.js +373 -0
  41. package/dist/test/{index.d.ts → story.d.ts} +13 -17
  42. package/dist/test/story.d.ts.map +1 -0
  43. package/dist/test/{index.js → story.js} +8 -36
  44. package/dist/test/vitest-setup.d.ts +19 -0
  45. package/dist/test/vitest-setup.d.ts.map +1 -0
  46. package/dist/test/vitest-setup.js +3 -0
  47. package/package.json +5 -5
  48. package/dist/test/index.d.ts.map +0 -1
@@ -0,0 +1,46 @@
1
+ import { Schema as S } from 'effect';
2
+ import * as Command from '../../command';
3
+ import { type Html } from '../../html';
4
+ export declare const Model: S.Struct<{
5
+ email: typeof S.String;
6
+ password: typeof S.String;
7
+ status: S.Literal<["Idle", "Submitting", "LoggedIn", "Error"]>;
8
+ username: typeof S.String;
9
+ error: typeof S.String;
10
+ }>;
11
+ export type Model = typeof Model.Type;
12
+ export declare const UpdatedEmail: import("../../schema").CallableTaggedStruct<"UpdatedEmail", {
13
+ value: typeof S.String;
14
+ }>;
15
+ export declare const UpdatedPassword: import("../../schema").CallableTaggedStruct<"UpdatedPassword", {
16
+ value: typeof S.String;
17
+ }>;
18
+ export declare const SubmittedLogin: import("../../schema").CallableTaggedStruct<"SubmittedLogin", {}>;
19
+ export declare const SucceededAuthenticate: import("../../schema").CallableTaggedStruct<"SucceededAuthenticate", {
20
+ username: typeof S.String;
21
+ }>;
22
+ export declare const FailedAuthenticate: import("../../schema").CallableTaggedStruct<"FailedAuthenticate", {
23
+ error: typeof S.String;
24
+ }>;
25
+ export declare const ClickedLogout: import("../../schema").CallableTaggedStruct<"ClickedLogout", {}>;
26
+ export declare const Message: S.Union<[import("../../schema").CallableTaggedStruct<"UpdatedEmail", {
27
+ value: typeof S.String;
28
+ }>, import("../../schema").CallableTaggedStruct<"UpdatedPassword", {
29
+ value: typeof S.String;
30
+ }>, import("../../schema").CallableTaggedStruct<"SubmittedLogin", {}>, import("../../schema").CallableTaggedStruct<"SucceededAuthenticate", {
31
+ username: typeof S.String;
32
+ }>, import("../../schema").CallableTaggedStruct<"FailedAuthenticate", {
33
+ error: typeof S.String;
34
+ }>, import("../../schema").CallableTaggedStruct<"ClickedLogout", {}>]>;
35
+ export type Message = typeof Message.Type;
36
+ export declare const Authenticate: Command.CommandDefinition<"Authenticate", {
37
+ readonly _tag: "SucceededAuthenticate";
38
+ readonly username: string;
39
+ } | {
40
+ readonly _tag: "FailedAuthenticate";
41
+ readonly error: string;
42
+ }>;
43
+ export declare const initialModel: Model;
44
+ export declare const update: (model: Model, message: Message) => readonly [Model, ReadonlyArray<Command.Command<Message>>];
45
+ export declare const view: (model: Model) => Html;
46
+ //# sourceMappingURL=login.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../../src/test/apps/login.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,MAAM,IAAI,CAAC,EAAE,MAAM,QAAQ,CAAA;AAExD,OAAO,KAAK,OAAO,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,KAAK,IAAI,EAAQ,MAAM,YAAY,CAAA;AAK5C,eAAO,MAAM,KAAK;;;;;;EAMhB,CAAA;AAEF,MAAM,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC,IAAI,CAAA;AAIrC,eAAO,MAAM,YAAY;;EAAyC,CAAA;AAClE,eAAO,MAAM,eAAe;;EAA4C,CAAA;AACxE,eAAO,MAAM,cAAc,mEAAsB,CAAA;AACjD,eAAO,MAAM,qBAAqB;;EAEhC,CAAA;AACF,eAAO,MAAM,kBAAkB;;EAA+C,CAAA;AAC9E,eAAO,MAAM,aAAa,kEAAqB,CAAA;AAE/C,eAAO,MAAM,OAAO;;;;;;;;sEAOnB,CAAA;AACD,MAAM,MAAM,OAAO,GAAG,OAAO,OAAO,CAAC,IAAI,CAAA;AAIzC,eAAO,MAAM,YAAY;;;;;;EAIxB,CAAA;AAQD,eAAO,MAAM,YAAY,EAAE,KAM1B,CAAA;AAID,eAAO,MAAM,MAAM,GACjB,OAAO,KAAK,EACZ,SAAS,OAAO,KACf,SAAS,CAAC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAyBxD,CAAA;AA2BH,eAAO,MAAM,IAAI,GAAI,OAAO,KAAK,KAAG,IAkEjC,CAAA"}
@@ -0,0 +1,83 @@
1
+ import { Effect, Match as M, Schema as S } from 'effect';
2
+ import * as Command from '../../command';
3
+ import { html } from '../../html';
4
+ import { m } from '../../message';
5
+ // MODEL
6
+ export const Model = S.Struct({
7
+ email: S.String,
8
+ password: S.String,
9
+ status: S.Literal('Idle', 'Submitting', 'LoggedIn', 'Error'),
10
+ username: S.String,
11
+ error: S.String,
12
+ });
13
+ // MESSAGE
14
+ export const UpdatedEmail = m('UpdatedEmail', { value: S.String });
15
+ export const UpdatedPassword = m('UpdatedPassword', { value: S.String });
16
+ export const SubmittedLogin = m('SubmittedLogin');
17
+ export const SucceededAuthenticate = m('SucceededAuthenticate', {
18
+ username: S.String,
19
+ });
20
+ export const FailedAuthenticate = m('FailedAuthenticate', { error: S.String });
21
+ export const ClickedLogout = m('ClickedLogout');
22
+ export const Message = S.Union(UpdatedEmail, UpdatedPassword, SubmittedLogin, SucceededAuthenticate, FailedAuthenticate, ClickedLogout);
23
+ // COMMAND
24
+ export const Authenticate = Command.define('Authenticate', SucceededAuthenticate, FailedAuthenticate);
25
+ const authenticate = Authenticate(Effect.sync(() => SucceededAuthenticate({ username: 'alice' })));
26
+ // INIT
27
+ export const initialModel = {
28
+ email: '',
29
+ password: '',
30
+ status: 'Idle',
31
+ username: '',
32
+ error: '',
33
+ };
34
+ // UPDATE
35
+ export const update = (model, message) => M.value(message).pipe(M.withReturnType(), M.tagsExhaustive({
36
+ UpdatedEmail: ({ value }) => [{ ...model, email: value }, []],
37
+ UpdatedPassword: ({ value }) => [{ ...model, password: value }, []],
38
+ SubmittedLogin: () => [
39
+ { ...model, status: 'Submitting' },
40
+ [authenticate],
41
+ ],
42
+ SucceededAuthenticate: ({ username }) => [
43
+ { ...model, status: 'LoggedIn', username },
44
+ [],
45
+ ],
46
+ FailedAuthenticate: ({ error }) => [
47
+ { ...model, status: 'Error', error },
48
+ [],
49
+ ],
50
+ ClickedLogout: () => [
51
+ { ...model, status: 'Idle', username: '', email: '', password: '' },
52
+ [],
53
+ ],
54
+ }));
55
+ // VIEW
56
+ const { div, form, input, button, span, label, p, OnInput, OnSubmit, OnClick, Id, For, Class, Type, Value, Placeholder, Role, AriaExpanded, AriaLabel, Disabled, } = html();
57
+ export const view = (model) => div([Id('app')], [
58
+ M.value(model.status).pipe(M.withReturnType(), M.when('Submitting', () => form([Class('login-form'), Disabled(true)], [button([Type('submit'), Disabled(true)], ['Signing in...'])])), M.when('LoggedIn', () => div([Class('logged-in'), Role('region'), AriaLabel('User session')], [
59
+ span([Class('greeting'), Role('status')], [`Welcome, ${model.username}!`]),
60
+ button([OnClick(ClickedLogout()), Role('button'), AriaExpanded(false)], ['Log out']),
61
+ ])), M.when('Error', () => div([], [
62
+ p([Class('error'), Role('alert')], [model.error]),
63
+ button([OnClick(SubmittedLogin()), Class('retry')], ['Retry']),
64
+ ])), M.when('Idle', () => form([OnSubmit(SubmittedLogin()), Class('login-form')], [
65
+ label([For('email'), Class('sr-only')], ['Email']),
66
+ input([
67
+ Id('email'),
68
+ Type('email'),
69
+ Placeholder('Email'),
70
+ Value(model.email),
71
+ OnInput(value => UpdatedEmail({ value })),
72
+ ]),
73
+ label([For('password'), Class('sr-only')], ['Password']),
74
+ input([
75
+ Id('password'),
76
+ Type('password'),
77
+ Placeholder('Password'),
78
+ Value(model.password),
79
+ OnInput(value => UpdatedPassword({ value })),
80
+ ]),
81
+ button([Type('submit'), Class('primary'), Disabled(false)], ['Sign in']),
82
+ ])), M.exhaustive),
83
+ ]);
@@ -0,0 +1,17 @@
1
+ import { Option, Schema as S } from 'effect';
2
+ import type { Html } from '../../html';
3
+ export declare const Model: S.Struct<{
4
+ label: typeof S.String;
5
+ }>;
6
+ export type Model = typeof Model.Type;
7
+ export declare const ClickedLogout: import("../../schema").CallableTaggedStruct<"ClickedLogout", {}>;
8
+ export declare const CompletedAction: import("../../schema").CallableTaggedStruct<"CompletedAction", {}>;
9
+ export declare const Message: S.Union<[import("../../schema").CallableTaggedStruct<"ClickedLogout", {}>, import("../../schema").CallableTaggedStruct<"CompletedAction", {}>]>;
10
+ export type Message = typeof Message.Type;
11
+ export declare const RequestedLogout: import("../../schema").CallableTaggedStruct<"RequestedLogout", {}>;
12
+ export declare const OutMessage: import("../../schema").CallableTaggedStruct<"RequestedLogout", {}>;
13
+ export type OutMessage = typeof OutMessage.Type;
14
+ export declare const initialModel: Model;
15
+ export declare const update: (model: Model, message: Message) => readonly [Model, ReadonlyArray<never>, Option.Option<OutMessage>];
16
+ export declare const view: (model: Model) => Html;
17
+ //# sourceMappingURL=logoutButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logoutButton.d.ts","sourceRoot":"","sources":["../../../src/test/apps/logoutButton.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,MAAM,EAAE,MAAM,IAAI,CAAC,EAAE,MAAM,QAAQ,CAAA;AAExD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAMtC,eAAO,MAAM,KAAK;;EAAgC,CAAA;AAClD,MAAM,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC,IAAI,CAAA;AAIrC,eAAO,MAAM,aAAa,kEAAqB,CAAA;AAC/C,eAAO,MAAM,eAAe,oEAAuB,CAAA;AAEnD,eAAO,MAAM,OAAO,iJAA0C,CAAA;AAC9D,MAAM,MAAM,OAAO,GAAG,OAAO,OAAO,CAAC,IAAI,CAAA;AAIzC,eAAO,MAAM,eAAe,oEAAuB,CAAA;AAEnD,eAAO,MAAM,UAAU,oEAA2B,CAAA;AAClD,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC,IAAI,CAAA;AAI/C,eAAO,MAAM,YAAY,EAAE,KAA4B,CAAA;AAIvD,eAAO,MAAM,MAAM,GACjB,OAAO,KAAK,EACZ,SAAS,OAAO,KACf,SAAS,CAAC,KAAK,EAAE,aAAa,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAShE,CAAA;AAMH,eAAO,MAAM,IAAI,GAAI,OAAO,KAAK,KAAG,IAC0C,CAAA"}
@@ -0,0 +1,22 @@
1
+ import { Match as M, Option, Schema as S } from 'effect';
2
+ import { html } from '../../html';
3
+ import { m } from '../../message';
4
+ // MODEL
5
+ export const Model = S.Struct({ label: S.String });
6
+ // MESSAGE
7
+ export const ClickedLogout = m('ClickedLogout');
8
+ export const CompletedAction = m('CompletedAction');
9
+ export const Message = S.Union(ClickedLogout, CompletedAction);
10
+ // OUT MESSAGE
11
+ export const RequestedLogout = m('RequestedLogout');
12
+ export const OutMessage = S.Union(RequestedLogout);
13
+ // INIT
14
+ export const initialModel = { label: 'Log out' };
15
+ // UPDATE
16
+ export const update = (model, message) => M.value(message).pipe(M.withReturnType(), M.tagsExhaustive({
17
+ ClickedLogout: () => [model, [], Option.some(RequestedLogout())],
18
+ CompletedAction: () => [model, [], Option.none()],
19
+ }));
20
+ // VIEW
21
+ const { div, button, OnClick, Role } = html();
22
+ export const view = (model) => div([], [button([OnClick(ClickedLogout()), Role('button')], [model.label])]);
@@ -0,0 +1,25 @@
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
+ }>;
6
+ type UpdateResult<Model, OutMessage> = readonly [Model, ReadonlyArray<AnyCommand>] | readonly [Model, ReadonlyArray<AnyCommand>, OutMessage];
7
+ /** A Command definition paired with the result Message to resolve it with. */
8
+ export type ResolverPair<Name extends string = string, ResultMessage = unknown> = readonly [CommandDefinition<Name, ResultMessage>, ResultMessage];
9
+ /** Base shape of an internal simulation — shared between Story and Scene. */
10
+ export type BaseInternal<Model, Message, OutMessage = undefined> = Readonly<{
11
+ model: Model;
12
+ message: Message | undefined;
13
+ commands: ReadonlyArray<AnyCommand>;
14
+ outMessage: OutMessage;
15
+ updateFn: (model: Model, message: Message) => UpdateResult<Model, OutMessage>;
16
+ resolvers: Record<string, Message>;
17
+ }>;
18
+ /** Resolves a single command by name and feeds its result through update. */
19
+ export declare const resolveByName: <Model, Message>(internal: BaseInternal<Model, Message, unknown>, commandName: string, resolverMessage: Message) => BaseInternal<Model, Message, unknown> | undefined;
20
+ /** Throws when trying to send a message with unresolved Commands. */
21
+ export declare const assertNoUnresolvedCommands: (commands: ReadonlyArray<AnyCommand>, context: string) => void;
22
+ /** Throws when Commands remain at the end of a test. */
23
+ export declare const assertAllCommandsResolved: (commands: ReadonlyArray<AnyCommand>) => void;
24
+ export {};
25
+ //# sourceMappingURL=internal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../../src/test/internal.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAEnD,0DAA0D;AAC1D,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAEnD,KAAK,YAAY,CAAC,KAAK,EAAE,UAAU,IAC/B,SAAS,CAAC,KAAK,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC,GAC3C,SAAS,CAAC,KAAK,EAAE,aAAa,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC,CAAA;AAE3D,8EAA8E;AAC9E,MAAM,MAAM,YAAY,CACtB,IAAI,SAAS,MAAM,GAAG,MAAM,EAC5B,aAAa,GAAG,OAAO,IACrB,SAAS,CAAC,iBAAiB,CAAC,IAAI,EAAE,aAAa,CAAC,EAAE,aAAa,CAAC,CAAA;AAEpE,6EAA6E;AAC7E,MAAM,MAAM,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,UAAU,GAAG,SAAS,IAAI,QAAQ,CAAC;IAC1E,KAAK,EAAE,KAAK,CAAA;IACZ,OAAO,EAAE,OAAO,GAAG,SAAS,CAAA;IAC5B,QAAQ,EAAE,aAAa,CAAC,UAAU,CAAC,CAAA;IACnC,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,KAAK,YAAY,CAAC,KAAK,EAAE,UAAU,CAAC,CAAA;IAC7E,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC,CAAC,CAAA;AAEF,6EAA6E;AAC7E,eAAO,MAAM,aAAa,GAAI,KAAK,EAAE,OAAO,EAC1C,UAAU,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAC/C,aAAa,MAAM,EACnB,iBAAiB,OAAO,KACvB,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,SAyBxC,CAAA;AAEH,qEAAqE;AACrE,eAAO,MAAM,0BAA0B,GACrC,UAAU,aAAa,CAAC,UAAU,CAAC,EACnC,SAAS,MAAM,KACd,IAcF,CAAA;AAED,wDAAwD;AACxD,eAAO,MAAM,yBAAyB,GACpC,UAAU,aAAa,CAAC,UAAU,CAAC,KAClC,IAcF,CAAA"}
@@ -0,0 +1,39 @@
1
+ import { Array, Option, pipe } from 'effect';
2
+ /** Resolves a single command by name and feeds its result through update. */
3
+ export const resolveByName = (internal, commandName, resolverMessage) => pipe(internal.commands, Array.findFirstIndex(({ name }) => name === commandName), Option.match({
4
+ onNone: () => undefined,
5
+ onSome: commandIndex => {
6
+ const remainingCommands = Array.remove(internal.commands, commandIndex);
7
+ const [nextModel, newCommands, ...rest] = internal.updateFn(internal.model, resolverMessage);
8
+ const outMessage = Array.isNonEmptyArray(rest)
9
+ ? rest[0]
10
+ : internal.outMessage;
11
+ return {
12
+ ...internal,
13
+ model: nextModel,
14
+ message: resolverMessage,
15
+ commands: Array.appendAll(remainingCommands, newCommands),
16
+ outMessage,
17
+ };
18
+ },
19
+ }));
20
+ /** Throws when trying to send a message with unresolved Commands. */
21
+ export const assertNoUnresolvedCommands = (commands, context) => {
22
+ if (Array.isNonEmptyReadonlyArray(commands)) {
23
+ const names = pipe(commands, Array.map(({ name }) => ` ${name}`), Array.join('\n'));
24
+ throw new Error(`I found unresolved Commands ${context}:\n\n${names}\n\n` +
25
+ 'Resolve all Commands before sending the next Message.\n' +
26
+ 'Use resolve(Definition, ResultMessage) for each one,\n' +
27
+ 'or resolveAll([...pairs]) to resolve them all at once.');
28
+ }
29
+ };
30
+ /** Throws when Commands remain at the end of a test. */
31
+ export const assertAllCommandsResolved = (commands) => {
32
+ if (Array.isNonEmptyReadonlyArray(commands)) {
33
+ const names = pipe(commands, Array.map(({ name }) => ` ${name}`), Array.join('\n'));
34
+ throw new Error(`I found Commands without resolvers:\n\n${names}\n\n` +
35
+ 'Every Command produced by update needs to be resolved.\n' +
36
+ 'Use resolve(Definition, ResultMessage) for each one,\n' +
37
+ 'or resolveAll([...pairs]) to resolve them all at once.');
38
+ }
39
+ };
@@ -0,0 +1,94 @@
1
+ import { Option } from 'effect';
2
+ import type { VNode } from '../vdom';
3
+ /** Custom Vitest matchers for scene testing. Register with `expect.extend(Scene.sceneMatchers)`. */
4
+ export declare const sceneMatchers: {
5
+ toHaveText(received: Option.Option<VNode>, expected: string): {
6
+ pass: boolean;
7
+ message: () => string;
8
+ } | {
9
+ pass: boolean;
10
+ message: () => string;
11
+ };
12
+ toContainText(received: Option.Option<VNode>, expected: string): {
13
+ pass: boolean;
14
+ message: () => string;
15
+ } | {
16
+ pass: boolean;
17
+ message: () => string;
18
+ };
19
+ toHaveClass(received: Option.Option<VNode>, expected: string): {
20
+ pass: boolean;
21
+ message: () => string;
22
+ } | {
23
+ pass: boolean;
24
+ message: () => string;
25
+ };
26
+ toHaveAttr(received: Option.Option<VNode>, name: string, expectedValue?: string): {
27
+ pass: boolean;
28
+ message: () => string;
29
+ } | {
30
+ pass: boolean;
31
+ message: () => string;
32
+ };
33
+ toExist(received: Option.Option<VNode>): {
34
+ pass: boolean;
35
+ message: () => "Expected element not to exist but it does." | "Expected element to exist but it does not.";
36
+ };
37
+ toBeAbsent(received: Option.Option<VNode>): {
38
+ pass: boolean;
39
+ message: () => "Expected element not to be absent but it is." | "Expected element to be absent but it exists.";
40
+ };
41
+ toHaveStyle(received: Option.Option<VNode>, name: string, expectedValue?: string): {
42
+ pass: boolean;
43
+ message: () => string;
44
+ } | {
45
+ pass: boolean;
46
+ message: () => string;
47
+ };
48
+ toHaveHook(received: Option.Option<VNode>, name: string): {
49
+ pass: boolean;
50
+ message: () => string;
51
+ } | {
52
+ pass: boolean;
53
+ message: () => string;
54
+ };
55
+ toHaveHandler(received: Option.Option<VNode>, name: string): {
56
+ pass: boolean;
57
+ message: () => string;
58
+ } | {
59
+ pass: boolean;
60
+ message: () => string;
61
+ };
62
+ toHaveValue(received: Option.Option<VNode>, expected: string): {
63
+ pass: boolean;
64
+ message: () => string;
65
+ } | {
66
+ pass: false;
67
+ message: () => string;
68
+ } | {
69
+ pass: boolean;
70
+ message: () => string;
71
+ };
72
+ toBeDisabled(received: Option.Option<VNode>): {
73
+ pass: boolean;
74
+ message: () => "Expected element to be disabled but the element does not exist.";
75
+ } | {
76
+ pass: boolean;
77
+ message: () => "Expected element not to be disabled but it is." | "Expected element to be disabled but it is not.";
78
+ };
79
+ toBeEnabled(received: Option.Option<VNode>): {
80
+ pass: boolean;
81
+ message: () => "Expected element to be enabled but the element does not exist.";
82
+ } | {
83
+ pass: boolean;
84
+ message: () => "Expected element not to be enabled but it is." | "Expected element to be enabled but it is disabled.";
85
+ };
86
+ toBeChecked(received: Option.Option<VNode>): {
87
+ pass: boolean;
88
+ message: () => "Expected element to be checked but the element does not exist.";
89
+ } | {
90
+ pass: boolean;
91
+ message: () => "Expected element not to be checked but it is." | "Expected element to be checked but it is not.";
92
+ };
93
+ };
94
+ //# sourceMappingURL=matchers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matchers.d.ts","sourceRoot":"","sources":["../../src/test/matchers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAE/B,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAKpC,oGAAoG;AACpG,eAAO,MAAM,aAAa;yBACH,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,MAAM;;;;;;;4BAqBnC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,MAAM;;;;;;;0BAqBxC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,MAAM;;;;;;;yBAmBhD,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,QACxB,MAAM,kBACI,MAAM;;;;;;;sBA2CN,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;;;;yBAWjB,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;;;;0BAY7B,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,QACxB,MAAM,kBACI,MAAM;;;;;;;yBA8CH,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,MAAM;;;;;;;4BAsB/B,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,MAAM;;;;;;;0BAkBpC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,MAAM;;;;;;;;;;2BA4BrC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;;;;;;;0BAyBrB,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;;;;;;;0BAyBpB,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;;;;;;;CAwB3C,CAAA"}
@@ -0,0 +1,283 @@
1
+ import { Option } from 'effect';
2
+ import { attr, textContent } from './query';
3
+ /** Custom Vitest matchers for scene testing. Register with `expect.extend(Scene.sceneMatchers)`. */
4
+ export const sceneMatchers = {
5
+ toHaveText(received, expected) {
6
+ return Option.match(received, {
7
+ onNone: () => ({
8
+ pass: false,
9
+ message: () => `Expected element to have text "${expected}" but the element does not exist.`,
10
+ }),
11
+ onSome: vnode => {
12
+ const actualText = textContent(vnode);
13
+ return {
14
+ pass: actualText === expected,
15
+ message: () =>
16
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
17
+ this.isNot
18
+ ? `Expected element not to have text "${expected}" but it does.`
19
+ : `Expected element to have text "${expected}" but received "${actualText}".`,
20
+ };
21
+ },
22
+ });
23
+ },
24
+ toContainText(received, expected) {
25
+ return Option.match(received, {
26
+ onNone: () => ({
27
+ pass: false,
28
+ message: () => `Expected element to contain text "${expected}" but the element does not exist.`,
29
+ }),
30
+ onSome: vnode => {
31
+ const actualText = textContent(vnode);
32
+ return {
33
+ pass: actualText.includes(expected),
34
+ message: () =>
35
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
36
+ this.isNot
37
+ ? `Expected element not to contain text "${expected}" but it does.`
38
+ : `Expected element to contain text "${expected}" but received "${actualText}".`,
39
+ };
40
+ },
41
+ });
42
+ },
43
+ toHaveClass(received, expected) {
44
+ return Option.match(received, {
45
+ onNone: () => ({
46
+ pass: false,
47
+ message: () => `Expected element to have class "${expected}" but the element does not exist.`,
48
+ }),
49
+ onSome: vnode => ({
50
+ pass: vnode.data?.class?.[expected] === true,
51
+ message: () =>
52
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
53
+ this.isNot
54
+ ? `Expected element not to have class "${expected}" but it does.`
55
+ : `Expected element to have class "${expected}" but it does not.`,
56
+ }),
57
+ });
58
+ },
59
+ toHaveAttr(received, name, expectedValue) {
60
+ return Option.match(received, {
61
+ onNone: () => ({
62
+ pass: false,
63
+ message: () => expectedValue === undefined
64
+ ? `Expected element to have attribute "${name}" but the element does not exist.`
65
+ : `Expected element to have attribute ${name}="${expectedValue}" but the element does not exist.`,
66
+ }),
67
+ onSome: vnode => {
68
+ const actualValue = attr(vnode, name);
69
+ if (expectedValue === undefined) {
70
+ return {
71
+ pass: Option.isSome(actualValue),
72
+ message: () =>
73
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
74
+ this.isNot
75
+ ? `Expected element not to have attribute "${name}" but it does.`
76
+ : `Expected element to have attribute "${name}" but it is not present.`,
77
+ };
78
+ }
79
+ return Option.match(actualValue, {
80
+ onNone: () => ({
81
+ pass: false,
82
+ message: () => `Expected element to have attribute ${name}="${expectedValue}" but the attribute is not present.`,
83
+ }),
84
+ onSome: actual => ({
85
+ pass: actual === expectedValue,
86
+ message: () =>
87
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
88
+ this.isNot
89
+ ? `Expected element not to have attribute ${name}="${expectedValue}" but it does.`
90
+ : `Expected element to have attribute ${name}="${expectedValue}" but received "${actual}".`,
91
+ }),
92
+ });
93
+ },
94
+ });
95
+ },
96
+ toExist(received) {
97
+ return {
98
+ pass: Option.isSome(received),
99
+ message: () =>
100
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
101
+ this.isNot
102
+ ? 'Expected element not to exist but it does.'
103
+ : 'Expected element to exist but it does not.',
104
+ };
105
+ },
106
+ toBeAbsent(received) {
107
+ return {
108
+ pass: Option.isNone(received),
109
+ message: () =>
110
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
111
+ this.isNot
112
+ ? 'Expected element not to be absent but it is.'
113
+ : 'Expected element to be absent but it exists.',
114
+ };
115
+ },
116
+ toHaveStyle(received, name, expectedValue) {
117
+ return Option.match(received, {
118
+ onNone: () => ({
119
+ pass: false,
120
+ message: () => expectedValue === undefined
121
+ ? `Expected element to have style "${name}" but the element does not exist.`
122
+ : `Expected element to have style ${name}="${expectedValue}" but the element does not exist.`,
123
+ }),
124
+ onSome: vnode => {
125
+ const maybeActualValue = Option.fromNullable(vnode.data?.style?.[name]);
126
+ if (expectedValue === undefined) {
127
+ return {
128
+ pass: Option.isSome(maybeActualValue),
129
+ message: () =>
130
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
131
+ this.isNot
132
+ ? `Expected element not to have style "${name}" but it does.`
133
+ : `Expected element to have style "${name}" but it is not present.`,
134
+ };
135
+ }
136
+ return Option.match(maybeActualValue, {
137
+ onNone: () => ({
138
+ pass: false,
139
+ message: () => `Expected element to have style ${name}="${expectedValue}" but the style is not present.`,
140
+ }),
141
+ onSome: actualValue => {
142
+ const actual = String(actualValue);
143
+ return {
144
+ pass: actual === expectedValue,
145
+ message: () =>
146
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
147
+ this.isNot
148
+ ? `Expected element not to have style ${name}="${expectedValue}" but it does.`
149
+ : `Expected element to have style ${name}="${expectedValue}" but received "${actual}".`,
150
+ };
151
+ },
152
+ });
153
+ },
154
+ });
155
+ },
156
+ toHaveHook(received, name) {
157
+ return Option.match(received, {
158
+ onNone: () => ({
159
+ pass: false,
160
+ message: () => `Expected element to have hook "${name}" but the element does not exist.`,
161
+ }),
162
+ onSome: vnode => {
163
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
164
+ const hooks = vnode.data?.hook;
165
+ return {
166
+ pass: typeof hooks?.[name] === 'function',
167
+ message: () =>
168
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
169
+ this.isNot
170
+ ? `Expected element not to have hook "${name}" but it does.`
171
+ : `Expected element to have hook "${name}" but it is not present.`,
172
+ };
173
+ },
174
+ });
175
+ },
176
+ toHaveHandler(received, name) {
177
+ return Option.match(received, {
178
+ onNone: () => ({
179
+ pass: false,
180
+ message: () => `Expected element to have handler "${name}" but the element does not exist.`,
181
+ }),
182
+ onSome: vnode => ({
183
+ pass: vnode.data?.on?.[name] !== undefined,
184
+ message: () =>
185
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
186
+ this.isNot
187
+ ? `Expected element not to have handler "${name}" but it does.`
188
+ : `Expected element to have handler "${name}" but it is not present.`,
189
+ }),
190
+ });
191
+ },
192
+ toHaveValue(received, expected) {
193
+ return Option.match(received, {
194
+ onNone: () => ({
195
+ pass: false,
196
+ message: () => `Expected element to have value "${expected}" but the element does not exist.`,
197
+ }),
198
+ onSome: vnode => {
199
+ const actualValue = attr(vnode, 'value');
200
+ return Option.match(actualValue, {
201
+ onNone: () => ({
202
+ pass: false,
203
+ message: () => `Expected element to have value "${expected}" but the element has no value.`,
204
+ }),
205
+ onSome: actual => ({
206
+ pass: actual === expected,
207
+ message: () =>
208
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
209
+ this.isNot
210
+ ? `Expected element not to have value "${expected}" but it does.`
211
+ : `Expected element to have value "${expected}" but received "${actual}".`,
212
+ }),
213
+ });
214
+ },
215
+ });
216
+ },
217
+ toBeDisabled(received) {
218
+ return Option.match(received, {
219
+ onNone: () => ({
220
+ pass: false,
221
+ message: () => 'Expected element to be disabled but the element does not exist.',
222
+ }),
223
+ onSome: vnode => {
224
+ const disabled = attr(vnode, 'disabled');
225
+ const ariaDisabled = attr(vnode, 'aria-disabled');
226
+ const pass = (Option.isSome(disabled) && disabled.value !== 'false') ||
227
+ (Option.isSome(ariaDisabled) && ariaDisabled.value === 'true');
228
+ return {
229
+ pass,
230
+ message: () =>
231
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
232
+ this.isNot
233
+ ? 'Expected element not to be disabled but it is.'
234
+ : 'Expected element to be disabled but it is not.',
235
+ };
236
+ },
237
+ });
238
+ },
239
+ toBeEnabled(received) {
240
+ return Option.match(received, {
241
+ onNone: () => ({
242
+ pass: false,
243
+ message: () => 'Expected element to be enabled but the element does not exist.',
244
+ }),
245
+ onSome: vnode => {
246
+ const disabled = attr(vnode, 'disabled');
247
+ const ariaDisabled = attr(vnode, 'aria-disabled');
248
+ const isDisabled = (Option.isSome(disabled) && disabled.value !== 'false') ||
249
+ (Option.isSome(ariaDisabled) && ariaDisabled.value === 'true');
250
+ return {
251
+ pass: !isDisabled,
252
+ message: () =>
253
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
254
+ this.isNot
255
+ ? 'Expected element not to be enabled but it is.'
256
+ : 'Expected element to be enabled but it is disabled.',
257
+ };
258
+ },
259
+ });
260
+ },
261
+ toBeChecked(received) {
262
+ return Option.match(received, {
263
+ onNone: () => ({
264
+ pass: false,
265
+ message: () => 'Expected element to be checked but the element does not exist.',
266
+ }),
267
+ onSome: vnode => {
268
+ const checked = attr(vnode, 'checked');
269
+ const ariaChecked = attr(vnode, 'aria-checked');
270
+ const pass = (Option.isSome(checked) && checked.value !== 'false') ||
271
+ (Option.isSome(ariaChecked) && ariaChecked.value === 'true');
272
+ return {
273
+ pass,
274
+ message: () =>
275
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
276
+ this.isNot
277
+ ? 'Expected element not to be checked but it is.'
278
+ : 'Expected element to be checked but it is not.',
279
+ };
280
+ },
281
+ });
282
+ },
283
+ };
@@ -1,3 +1,3 @@
1
- export type { AnyCommand, ResolverPair, Simulation, StoryStep, WithStep, } from './index';
2
- export { message, resolve, resolveAll, story, tap, with } from './index';
1
+ export * as Story from './story';
2
+ export * as Scene from './scene';
3
3
  //# sourceMappingURL=public.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/test/public.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,UAAU,EACV,YAAY,EACZ,UAAU,EACV,SAAS,EACT,QAAQ,GACT,MAAM,SAAS,CAAA;AAChB,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA"}
1
+ {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/test/public.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,SAAS,CAAA;AAChC,OAAO,KAAK,KAAK,MAAM,SAAS,CAAA"}
@@ -1 +1,2 @@
1
- export { message, resolve, resolveAll, story, tap, with } from './index';
1
+ export * as Story from './story';
2
+ export * as Scene from './scene';