@cssints/apg-specs 0.0.0-stage → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +65 -2
- package/dist/apg/accordion.bridge.d.mts +35 -0
- package/dist/apg/accordion.bridge.mjs +326 -0
- package/dist/apg/accordion.model.json +8397 -0
- package/dist/apg/alert.bridge.d.mts +22 -0
- package/dist/apg/alert.bridge.mjs +86 -0
- package/dist/apg/alert.model.json +871 -0
- package/dist/apg/alertdialog.bridge.d.mts +21 -0
- package/dist/apg/alertdialog.bridge.mjs +221 -0
- package/dist/apg/alertdialog.model.json +5232 -0
- package/dist/apg/breadcrumb.bridge.d.mts +26 -0
- package/dist/apg/breadcrumb.bridge.mjs +120 -0
- package/dist/apg/breadcrumb.model.json +574 -0
- package/dist/apg/button.bridge.d.mts +35 -0
- package/dist/apg/button.bridge.mjs +208 -0
- package/dist/apg/button.model.json +5814 -0
- package/dist/apg/carousel.bridge.d.mts +44 -0
- package/dist/apg/carousel.bridge.mjs +262 -0
- package/dist/apg/carousel.model.json +8963 -0
- package/dist/apg/checkbox-group.bridge.d.mts +25 -0
- package/dist/apg/checkbox-group.bridge.mjs +276 -0
- package/dist/apg/checkbox-group.model.json +3863 -0
- package/dist/apg/checkbox.bridge.d.mts +34 -0
- package/dist/apg/checkbox.bridge.mjs +253 -0
- package/dist/apg/checkbox.model.json +3158 -0
- package/dist/apg/combobox-autocomplete.bridge.d.mts +14 -0
- package/dist/apg/combobox-autocomplete.bridge.mjs +326 -0
- package/dist/apg/combobox-autocomplete.model.json +11586 -0
- package/dist/apg/combobox-dialog.bridge.d.mts +17 -0
- package/dist/apg/combobox-dialog.bridge.mjs +250 -0
- package/dist/apg/combobox-dialog.model.json +8981 -0
- package/dist/apg/combobox-grid.bridge.d.mts +17 -0
- package/dist/apg/combobox-grid.bridge.mjs +227 -0
- package/dist/apg/combobox-grid.model.json +8554 -0
- package/dist/apg/combobox-select-only.bridge.d.mts +24 -0
- package/dist/apg/combobox-select-only.bridge.mjs +304 -0
- package/dist/apg/combobox-select-only.model.json +7846 -0
- package/dist/apg/combobox-tree.bridge.d.mts +17 -0
- package/dist/apg/combobox-tree.bridge.mjs +211 -0
- package/dist/apg/combobox-tree.model.json +9345 -0
- package/dist/apg/dialog-modal.bridge.d.mts +35 -0
- package/dist/apg/dialog-modal.bridge.mjs +186 -0
- package/dist/apg/dialog-modal.model.json +7580 -0
- package/dist/apg/disclosure.bridge.d.mts +25 -0
- package/dist/apg/disclosure.bridge.mjs +202 -0
- package/dist/apg/disclosure.model.json +4703 -0
- package/dist/apg/feed.bridge.d.mts +31 -0
- package/dist/apg/feed.bridge.mjs +246 -0
- package/dist/apg/feed.model.json +4523 -0
- package/dist/apg/grid-selection.bridge.d.mts +13 -0
- package/dist/apg/grid-selection.bridge.mjs +160 -0
- package/dist/apg/grid-selection.model.json +6974 -0
- package/dist/apg/grid-sort.bridge.d.mts +12 -0
- package/dist/apg/grid-sort.bridge.mjs +138 -0
- package/dist/apg/grid-sort.model.json +3799 -0
- package/dist/apg/grid.bridge.d.mts +54 -0
- package/dist/apg/grid.bridge.mjs +441 -0
- package/dist/apg/grid.model.json +3802 -0
- package/dist/apg/link.bridge.d.mts +24 -0
- package/dist/apg/link.bridge.mjs +126 -0
- package/dist/apg/link.model.json +694 -0
- package/dist/apg/listbox-multi.bridge.d.mts +10 -0
- package/dist/apg/listbox-multi.bridge.mjs +261 -0
- package/dist/apg/listbox-multi.model.json +10893 -0
- package/dist/apg/listbox-rearrangeable.bridge.d.mts +20 -0
- package/dist/apg/listbox-rearrangeable.bridge.mjs +237 -0
- package/dist/apg/listbox-rearrangeable.model.json +17592 -0
- package/dist/apg/listbox-shared.d.mts +58 -0
- package/dist/apg/listbox-shared.mjs +171 -0
- package/dist/apg/listbox-single.bridge.d.mts +10 -0
- package/dist/apg/listbox-single.bridge.mjs +196 -0
- package/dist/apg/listbox-single.model.json +8024 -0
- package/dist/apg/listbox-typeahead.bridge.d.mts +10 -0
- package/dist/apg/listbox-typeahead.bridge.mjs +153 -0
- package/dist/apg/listbox-typeahead.model.json +5615 -0
- package/dist/apg/menu-button.bridge.d.mts +29 -0
- package/dist/apg/menu-button.bridge.mjs +259 -0
- package/dist/apg/menu-button.model.json +5706 -0
- package/dist/apg/menu-checkable.bridge.d.mts +25 -0
- package/dist/apg/menu-checkable.bridge.mjs +226 -0
- package/dist/apg/menu-checkable.model.json +4277 -0
- package/dist/apg/menu-menubar.bridge.d.mts +27 -0
- package/dist/apg/menu-menubar.bridge.mjs +273 -0
- package/dist/apg/menu-menubar.model.json +9540 -0
- package/dist/apg/meter.bridge.d.mts +28 -0
- package/dist/apg/meter.bridge.mjs +159 -0
- package/dist/apg/meter.model.json +1185 -0
- package/dist/apg/player.bridge.d.mts +24 -0
- package/dist/apg/player.bridge.mjs +342 -0
- package/dist/apg/player.model.json +11013 -0
- package/dist/apg/radio.bridge.d.mts +35 -0
- package/dist/apg/radio.bridge.mjs +371 -0
- package/dist/apg/radio.model.json +5011 -0
- package/dist/apg/slider-multithumb.bridge.d.mts +34 -0
- package/dist/apg/slider-multithumb.bridge.mjs +365 -0
- package/dist/apg/slider-multithumb.model.json +5774 -0
- package/dist/apg/slider.bridge.d.mts +54 -0
- package/dist/apg/slider.bridge.mjs +359 -0
- package/dist/apg/slider.model.json +4881 -0
- package/dist/apg/spinbutton.bridge.d.mts +41 -0
- package/dist/apg/spinbutton.bridge.mjs +327 -0
- package/dist/apg/spinbutton.model.json +7571 -0
- package/dist/apg/switch.bridge.d.mts +30 -0
- package/dist/apg/switch.bridge.mjs +243 -0
- package/dist/apg/switch.model.json +3159 -0
- package/dist/apg/tabs.bridge.d.mts +41 -0
- package/dist/apg/tabs.bridge.mjs +294 -0
- package/dist/apg/tabs.model.json +7659 -0
- package/dist/apg/timeline.bridge.d.mts +36 -0
- package/dist/apg/timeline.bridge.mjs +265 -0
- package/dist/apg/timeline.model.json +6189 -0
- package/dist/apg/toolbar.bridge.d.mts +25 -0
- package/dist/apg/toolbar.bridge.mjs +307 -0
- package/dist/apg/toolbar.model.json +6616 -0
- package/dist/apg/tooltip.bridge.d.mts +36 -0
- package/dist/apg/tooltip.bridge.mjs +208 -0
- package/dist/apg/tooltip.model.json +8295 -0
- package/dist/apg/treegrid.bridge.d.mts +28 -0
- package/dist/apg/treegrid.bridge.mjs +311 -0
- package/dist/apg/treegrid.model.json +17358 -0
- package/dist/apg/treeview.bridge.d.mts +26 -0
- package/dist/apg/treeview.bridge.mjs +396 -0
- package/dist/apg/treeview.model.json +13745 -0
- package/dist/apg/windowsplitter.bridge.d.mts +36 -0
- package/dist/apg/windowsplitter.bridge.mjs +242 -0
- package/dist/apg/windowsplitter.model.json +6253 -0
- package/dist/canvas/camera.model.json +6568 -0
- package/dist/canvas/canvas.bridge.d.mts +190 -0
- package/dist/canvas/canvas.bridge.mjs +729 -0
- package/dist/canvas/pointer.model.json +17095 -0
- package/dist/scenario.d.mts +233 -0
- package/dist/scenario.mjs +313 -0
- package/package.json +57 -4
- package/scenarios/apg/README.md +221 -0
- package/scenarios/apg/accordion.base-ui.json +1005 -0
- package/scenarios/apg/accordion.react-aria.json +929 -0
- package/scenarios/apg/alert.base-ui.json +81 -0
- package/scenarios/apg/alert.react-aria.json +98 -0
- package/scenarios/apg/alertdialog.base-ui.json +331 -0
- package/scenarios/apg/alertdialog.react-aria.json +225 -0
- package/scenarios/apg/breadcrumb.react-aria.json +312 -0
- package/scenarios/apg/button.ariakit.json +136 -0
- package/scenarios/apg/button.base-ui.json +747 -0
- package/scenarios/apg/button.react-aria.json +443 -0
- package/scenarios/apg/carousel.apg.json +909 -0
- package/scenarios/apg/checkbox.ariakit.json +299 -0
- package/scenarios/apg/checkbox.base-ui.json +1126 -0
- package/scenarios/apg/checkbox.react-aria.json +518 -0
- package/scenarios/apg/combobox.ariakit.json +945 -0
- package/scenarios/apg/combobox.base-ui.json +2444 -0
- package/scenarios/apg/combobox.react-aria.json +845 -0
- package/scenarios/apg/dialog-modal.ariakit.json +1102 -0
- package/scenarios/apg/dialog-modal.base-ui.json +775 -0
- package/scenarios/apg/dialog-modal.react-aria.json +638 -0
- package/scenarios/apg/disclosure.apg.json +349 -0
- package/scenarios/apg/disclosure.ariakit.json +163 -0
- package/scenarios/apg/disclosure.base-ui.json +451 -0
- package/scenarios/apg/disclosure.react-aria.json +643 -0
- package/scenarios/apg/feed.apg.json +442 -0
- package/scenarios/apg/grid.ariakit.json +197 -0
- package/scenarios/apg/grid.base-ui.json +720 -0
- package/scenarios/apg/grid.react-aria.json +969 -0
- package/scenarios/apg/landmarks.ariakit.json +93 -0
- package/scenarios/apg/landmarks.react-aria.json +330 -0
- package/scenarios/apg/link.ariakit.json +83 -0
- package/scenarios/apg/link.base-ui.json +88 -0
- package/scenarios/apg/link.react-aria.json +186 -0
- package/scenarios/apg/listbox.ariakit.json +78 -0
- package/scenarios/apg/listbox.base-ui.json +163 -0
- package/scenarios/apg/listbox.react-aria.json +1118 -0
- package/scenarios/apg/menu-button.ariakit.json +924 -0
- package/scenarios/apg/menu-button.base-ui.json +1155 -0
- package/scenarios/apg/menu-button.react-aria.json +1080 -0
- package/scenarios/apg/menubar.ariakit.json +758 -0
- package/scenarios/apg/menubar.base-ui.json +673 -0
- package/scenarios/apg/meter.base-ui.json +243 -0
- package/scenarios/apg/meter.react-aria.json +201 -0
- package/scenarios/apg/radio.ariakit.json +459 -0
- package/scenarios/apg/radio.base-ui.json +581 -0
- package/scenarios/apg/radio.react-aria.json +576 -0
- package/scenarios/apg/slider-multithumb.base-ui.json +316 -0
- package/scenarios/apg/slider-multithumb.react-aria.json +363 -0
- package/scenarios/apg/slider.base-ui.json +544 -0
- package/scenarios/apg/slider.react-aria.json +456 -0
- package/scenarios/apg/spinbutton.base-ui.json +1688 -0
- package/scenarios/apg/spinbutton.react-aria.json +426 -0
- package/scenarios/apg/switch.base-ui.json +456 -0
- package/scenarios/apg/switch.react-aria.json +303 -0
- package/scenarios/apg/table.ariakit.json +65 -0
- package/scenarios/apg/table.react-aria.json +221 -0
- package/scenarios/apg/tabs.ariakit.json +329 -0
- package/scenarios/apg/tabs.base-ui.json +875 -0
- package/scenarios/apg/tabs.react-aria.json +644 -0
- package/scenarios/apg/toolbar.ariakit.json +595 -0
- package/scenarios/apg/toolbar.base-ui.json +1189 -0
- package/scenarios/apg/toolbar.react-aria.json +383 -0
- package/scenarios/apg/tooltip.ariakit.json +441 -0
- package/scenarios/apg/tooltip.base-ui.json +411 -0
- package/scenarios/apg/tooltip.react-aria.json +454 -0
- package/scenarios/apg/treegrid.apg.json +2558 -0
- package/scenarios/apg/treegrid.react-aria.json +5094 -0
- package/scenarios/apg/treeview.apg.json +928 -0
- package/scenarios/apg/windowsplitter.apg.json +557 -0
- package/scenarios/canvas/README.md +69 -0
- package/scenarios/canvas/camera.contract.json +237 -0
- package/scenarios/canvas/camera.excalidraw.json +57 -0
- package/scenarios/canvas/marquee.excalidraw.json +122 -0
- package/scenarios/canvas/pan.excalidraw.json +46 -0
- package/scenarios/canvas/pointer.contract.json +528 -0
- package/scenarios/parts.json +291 -0
- package/shared/aria.qnt +186 -0
- package/shared/details-group.qnt +50 -0
- package/shared/focusgroup.qnt +152 -0
- package/shared/invokers.qnt +81 -0
- package/shared/popover.qnt +71 -0
- package/specs/apg/STATIC.md +103 -0
- package/specs/apg/accordion.qnt +578 -0
- package/specs/apg/alert.qnt +131 -0
- package/specs/apg/alertdialog.qnt +455 -0
- package/specs/apg/breadcrumb.qnt +88 -0
- package/specs/apg/button.qnt +445 -0
- package/specs/apg/carousel.qnt +732 -0
- package/specs/apg/checkbox-group.qnt +388 -0
- package/specs/apg/checkbox.qnt +394 -0
- package/specs/apg/combobox-autocomplete.qnt +764 -0
- package/specs/apg/combobox-dialog.qnt +627 -0
- package/specs/apg/combobox-grid.qnt +632 -0
- package/specs/apg/combobox-select-only.qnt +626 -0
- package/specs/apg/combobox-tree.qnt +633 -0
- package/specs/apg/dialog-modal.qnt +499 -0
- package/specs/apg/disclosure.qnt +309 -0
- package/specs/apg/feed.qnt +485 -0
- package/specs/apg/grid-selection.qnt +512 -0
- package/specs/apg/grid-sort.qnt +330 -0
- package/specs/apg/grid.qnt +360 -0
- package/specs/apg/link.qnt +119 -0
- package/specs/apg/listbox-multi.qnt +799 -0
- package/specs/apg/listbox-rearrangeable.qnt +801 -0
- package/specs/apg/listbox-single.qnt +511 -0
- package/specs/apg/listbox-typeahead.qnt +396 -0
- package/specs/apg/menu-button.qnt +363 -0
- package/specs/apg/menu-checkable.qnt +304 -0
- package/specs/apg/menu-menubar.qnt +531 -0
- package/specs/apg/meter.qnt +129 -0
- package/specs/apg/player.qnt +1081 -0
- package/specs/apg/radio.qnt +394 -0
- package/specs/apg/slider-multithumb.qnt +441 -0
- package/specs/apg/slider.qnt +396 -0
- package/specs/apg/spinbutton.qnt +494 -0
- package/specs/apg/switch.qnt +389 -0
- package/specs/apg/tabs.qnt +412 -0
- package/specs/apg/timeline.qnt +612 -0
- package/specs/apg/toolbar.qnt +364 -0
- package/specs/apg/tooltip.qnt +553 -0
- package/specs/apg/treegrid.qnt +983 -0
- package/specs/apg/treeview.qnt +1004 -0
- package/specs/apg/windowsplitter.qnt +487 -0
- package/specs/canvas/camera.qnt +407 -0
- package/specs/canvas/pointer.qnt +841 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { QuintEvent, QuintModelValue, QuintReferenceModel } from "@quint-machine/core";
|
|
2
|
+
import { Schema } from "effect";
|
|
3
|
+
//#region src/scenario.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Typed step fields a bridge must opt into (`SpecBridge.stepFields`): replaying
|
|
6
|
+
* a step while ignoring one of these would test a different step.
|
|
7
|
+
*/
|
|
8
|
+
export declare const EXTENDED_STEP_FIELDS: readonly ["position", "modifiers", "phase", "button", "buttons", "pointerType", "pointerId", "detail", "count", "canceled", "defaultPrevented", "isComposing", "inputEvent", "set"];
|
|
9
|
+
export type ExtendedStepField = (typeof EXTENDED_STEP_FIELDS)[number];
|
|
10
|
+
declare const ActionStep: Schema.StructWithRest<Schema.Struct<{
|
|
11
|
+
readonly event: Schema.String;
|
|
12
|
+
readonly target: Schema.optionalKey<Schema.String>;
|
|
13
|
+
readonly key: Schema.optionalKey<Schema.String>;
|
|
14
|
+
readonly text: Schema.optionalKey<Schema.String>;
|
|
15
|
+
readonly prop: Schema.optionalKey<Schema.String>;
|
|
16
|
+
readonly value: Schema.optionalKey<Schema.Codec<Schema.Json, Schema.Json, never, never>>;
|
|
17
|
+
readonly repeat: Schema.optionalKey<Schema.Union<readonly [Schema.Boolean, Schema.Number]>>;
|
|
18
|
+
readonly ms: Schema.optionalKey<Schema.Number>;
|
|
19
|
+
readonly position: Schema.optionalKey<Schema.Number>;
|
|
20
|
+
readonly modifiers: Schema.optionalKey<Schema.$Array<Schema.Literals<readonly ["Alt", "Control", "Meta", "Shift"]>>>;
|
|
21
|
+
readonly phase: Schema.optionalKey<Schema.Literals<readonly ["down", "up"]>>;
|
|
22
|
+
readonly button: Schema.optionalKey<Schema.Number>;
|
|
23
|
+
readonly buttons: Schema.optionalKey<Schema.Number>;
|
|
24
|
+
readonly pointerType: Schema.optionalKey<Schema.Literals<readonly ["mouse", "pen", "touch"]>>;
|
|
25
|
+
readonly pointerId: Schema.optionalKey<Schema.Number>;
|
|
26
|
+
readonly detail: Schema.optionalKey<Schema.Number>;
|
|
27
|
+
readonly count: Schema.optionalKey<Schema.Number>;
|
|
28
|
+
readonly canceled: Schema.optionalKey<Schema.Boolean>;
|
|
29
|
+
readonly defaultPrevented: Schema.optionalKey<Schema.Boolean>;
|
|
30
|
+
readonly isComposing: Schema.optionalKey<Schema.Boolean>;
|
|
31
|
+
readonly inputEvent: Schema.optionalKey<Schema.String>;
|
|
32
|
+
readonly set: Schema.optionalKey<Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>>;
|
|
33
|
+
}>, readonly [Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>]>;
|
|
34
|
+
type ActionStep = typeof ActionStep.Type;
|
|
35
|
+
export type Json = Schema.Json;
|
|
36
|
+
export interface ScenarioConfig {
|
|
37
|
+
readonly [key: string]: Schema.Json;
|
|
38
|
+
}
|
|
39
|
+
declare const Scenario: Schema.Struct<{
|
|
40
|
+
readonly id: Schema.String;
|
|
41
|
+
readonly upstream: Schema.optionalKey<Schema.String>;
|
|
42
|
+
readonly upstreamTest: Schema.optionalKey<Schema.String>;
|
|
43
|
+
readonly apg: Schema.optionalKey<Schema.String>;
|
|
44
|
+
readonly config: Schema.optionalKey<Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>>;
|
|
45
|
+
readonly steps: Schema.$Array<Schema.Union<readonly [Schema.StructWithRest<Schema.Struct<{
|
|
46
|
+
readonly event: Schema.String;
|
|
47
|
+
readonly target: Schema.optionalKey<Schema.String>;
|
|
48
|
+
readonly key: Schema.optionalKey<Schema.String>;
|
|
49
|
+
readonly text: Schema.optionalKey<Schema.String>;
|
|
50
|
+
readonly prop: Schema.optionalKey<Schema.String>;
|
|
51
|
+
readonly value: Schema.optionalKey<Schema.Codec<Schema.Json, Schema.Json, never, never>>;
|
|
52
|
+
readonly repeat: Schema.optionalKey<Schema.Union<readonly [Schema.Boolean, Schema.Number]>>;
|
|
53
|
+
readonly ms: Schema.optionalKey<Schema.Number>;
|
|
54
|
+
readonly position: Schema.optionalKey<Schema.Number>;
|
|
55
|
+
readonly modifiers: Schema.optionalKey<Schema.$Array<Schema.Literals<readonly ["Alt", "Control", "Meta", "Shift"]>>>;
|
|
56
|
+
readonly phase: Schema.optionalKey<Schema.Literals<readonly ["down", "up"]>>;
|
|
57
|
+
readonly button: Schema.optionalKey<Schema.Number>;
|
|
58
|
+
readonly buttons: Schema.optionalKey<Schema.Number>;
|
|
59
|
+
readonly pointerType: Schema.optionalKey<Schema.Literals<readonly ["mouse", "pen", "touch"]>>;
|
|
60
|
+
readonly pointerId: Schema.optionalKey<Schema.Number>;
|
|
61
|
+
readonly detail: Schema.optionalKey<Schema.Number>;
|
|
62
|
+
readonly count: Schema.optionalKey<Schema.Number>;
|
|
63
|
+
readonly canceled: Schema.optionalKey<Schema.Boolean>;
|
|
64
|
+
readonly defaultPrevented: Schema.optionalKey<Schema.Boolean>;
|
|
65
|
+
readonly isComposing: Schema.optionalKey<Schema.Boolean>;
|
|
66
|
+
readonly inputEvent: Schema.optionalKey<Schema.String>;
|
|
67
|
+
readonly set: Schema.optionalKey<Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>>;
|
|
68
|
+
}>, readonly [Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>]>, Schema.Struct<{
|
|
69
|
+
readonly expect: Schema.StructWithRest<Schema.Struct<{
|
|
70
|
+
readonly focus: Schema.optionalKey<Schema.NullOr<Schema.String>>;
|
|
71
|
+
readonly attrs: Schema.optionalKey<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.NullOr<Schema.String>>>>;
|
|
72
|
+
readonly visible: Schema.optionalKey<Schema.$Array<Schema.String>>;
|
|
73
|
+
readonly hidden: Schema.optionalKey<Schema.$Array<Schema.String>>;
|
|
74
|
+
readonly checked: Schema.optionalKey<Schema.NullOr<Schema.String>>;
|
|
75
|
+
readonly value: Schema.optionalKey<Schema.Codec<Schema.Json, Schema.Json, never, never>>;
|
|
76
|
+
readonly events: Schema.optionalKey<Schema.$Record<Schema.String, Schema.$Array<Schema.Codec<Schema.Json, Schema.Json, never, never>>>>;
|
|
77
|
+
}>, readonly [Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>]>;
|
|
78
|
+
}>]>>;
|
|
79
|
+
readonly notes: Schema.optionalKey<Schema.String>;
|
|
80
|
+
}>;
|
|
81
|
+
type Scenario = typeof Scenario.Type;
|
|
82
|
+
/** An upstream behaviour deliberately not ported (`upstream*` when it has a single source). */
|
|
83
|
+
declare const Skipped: Schema.Struct<{
|
|
84
|
+
readonly id: Schema.optionalKey<Schema.String>;
|
|
85
|
+
readonly upstream: Schema.optionalKey<Schema.String>;
|
|
86
|
+
readonly upstreamTest: Schema.optionalKey<Schema.String>;
|
|
87
|
+
readonly reason: Schema.String;
|
|
88
|
+
}>;
|
|
89
|
+
type Skipped = typeof Skipped.Type;
|
|
90
|
+
declare const ScenarioFile: Schema.Struct<{
|
|
91
|
+
readonly pattern: Schema.String;
|
|
92
|
+
readonly source: Schema.String;
|
|
93
|
+
readonly commit: Schema.optionalKey<Schema.NullOr<Schema.String>>;
|
|
94
|
+
readonly license: Schema.optionalKey<Schema.String>;
|
|
95
|
+
readonly upstream: Schema.optionalKey<Schema.String>;
|
|
96
|
+
readonly retrieved: Schema.optionalKey<Schema.String>;
|
|
97
|
+
readonly fixture: Schema.optionalKey<Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>>;
|
|
98
|
+
readonly notes: Schema.optionalKey<Schema.String>;
|
|
99
|
+
readonly skipped: Schema.optionalKey<Schema.$Array<Schema.Struct<{
|
|
100
|
+
readonly id: Schema.optionalKey<Schema.String>;
|
|
101
|
+
readonly upstream: Schema.optionalKey<Schema.String>;
|
|
102
|
+
readonly upstreamTest: Schema.optionalKey<Schema.String>;
|
|
103
|
+
readonly reason: Schema.String;
|
|
104
|
+
}>>>;
|
|
105
|
+
readonly scenarios: Schema.$Array<Schema.Struct<{
|
|
106
|
+
readonly id: Schema.String;
|
|
107
|
+
readonly upstream: Schema.optionalKey<Schema.String>;
|
|
108
|
+
readonly upstreamTest: Schema.optionalKey<Schema.String>;
|
|
109
|
+
readonly apg: Schema.optionalKey<Schema.String>;
|
|
110
|
+
readonly config: Schema.optionalKey<Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>>;
|
|
111
|
+
readonly steps: Schema.$Array<Schema.Union<readonly [Schema.StructWithRest<Schema.Struct<{
|
|
112
|
+
readonly event: Schema.String;
|
|
113
|
+
readonly target: Schema.optionalKey<Schema.String>;
|
|
114
|
+
readonly key: Schema.optionalKey<Schema.String>;
|
|
115
|
+
readonly text: Schema.optionalKey<Schema.String>;
|
|
116
|
+
readonly prop: Schema.optionalKey<Schema.String>;
|
|
117
|
+
readonly value: Schema.optionalKey<Schema.Codec<Schema.Json, Schema.Json, never, never>>;
|
|
118
|
+
readonly repeat: Schema.optionalKey<Schema.Union<readonly [Schema.Boolean, Schema.Number]>>;
|
|
119
|
+
readonly ms: Schema.optionalKey<Schema.Number>;
|
|
120
|
+
readonly position: Schema.optionalKey<Schema.Number>;
|
|
121
|
+
readonly modifiers: Schema.optionalKey<Schema.$Array<Schema.Literals<readonly ["Alt", "Control", "Meta", "Shift"]>>>;
|
|
122
|
+
readonly phase: Schema.optionalKey<Schema.Literals<readonly ["down", "up"]>>;
|
|
123
|
+
readonly button: Schema.optionalKey<Schema.Number>;
|
|
124
|
+
readonly buttons: Schema.optionalKey<Schema.Number>;
|
|
125
|
+
readonly pointerType: Schema.optionalKey<Schema.Literals<readonly ["mouse", "pen", "touch"]>>;
|
|
126
|
+
readonly pointerId: Schema.optionalKey<Schema.Number>;
|
|
127
|
+
readonly detail: Schema.optionalKey<Schema.Number>;
|
|
128
|
+
readonly count: Schema.optionalKey<Schema.Number>;
|
|
129
|
+
readonly canceled: Schema.optionalKey<Schema.Boolean>;
|
|
130
|
+
readonly defaultPrevented: Schema.optionalKey<Schema.Boolean>;
|
|
131
|
+
readonly isComposing: Schema.optionalKey<Schema.Boolean>;
|
|
132
|
+
readonly inputEvent: Schema.optionalKey<Schema.String>;
|
|
133
|
+
readonly set: Schema.optionalKey<Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>>;
|
|
134
|
+
}>, readonly [Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>]>, Schema.Struct<{
|
|
135
|
+
readonly expect: Schema.StructWithRest<Schema.Struct<{
|
|
136
|
+
readonly focus: Schema.optionalKey<Schema.NullOr<Schema.String>>;
|
|
137
|
+
readonly attrs: Schema.optionalKey<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.NullOr<Schema.String>>>>;
|
|
138
|
+
readonly visible: Schema.optionalKey<Schema.$Array<Schema.String>>;
|
|
139
|
+
readonly hidden: Schema.optionalKey<Schema.$Array<Schema.String>>;
|
|
140
|
+
readonly checked: Schema.optionalKey<Schema.NullOr<Schema.String>>;
|
|
141
|
+
readonly value: Schema.optionalKey<Schema.Codec<Schema.Json, Schema.Json, never, never>>;
|
|
142
|
+
readonly events: Schema.optionalKey<Schema.$Record<Schema.String, Schema.$Array<Schema.Codec<Schema.Json, Schema.Json, never, never>>>>;
|
|
143
|
+
}>, readonly [Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>]>;
|
|
144
|
+
}>]>>;
|
|
145
|
+
readonly notes: Schema.optionalKey<Schema.String>;
|
|
146
|
+
}>>;
|
|
147
|
+
}>;
|
|
148
|
+
type ScenarioFile = typeof ScenarioFile.Type;
|
|
149
|
+
/** Decode a scenario JSON document (the trust boundary for ported data). */
|
|
150
|
+
export declare const parseScenarioFile: (input: Schema.Json) => ScenarioFile;
|
|
151
|
+
/** A step or observation the target does not model. Skips, never fails. */
|
|
152
|
+
export declare class Unsupported extends Error {}
|
|
153
|
+
/** The target disagreed with the scenario (or, in a walk, with the other target). */
|
|
154
|
+
export declare class ScenarioMismatch extends Error {}
|
|
155
|
+
export interface Observer {
|
|
156
|
+
/** Part target holding focus (`trigger`, `tab:1`), or `null`. */
|
|
157
|
+
readonly focus: () => string | null;
|
|
158
|
+
/** Attribute value; `null` = absent. Throw `Unsupported` when not modelled. */
|
|
159
|
+
readonly attr: (target: string, name: string) => string | null;
|
|
160
|
+
/** The parts currently visible. */
|
|
161
|
+
readonly visible: () => ReadonlyArray<string>;
|
|
162
|
+
/** Callback payloads fired since mount (setup excluded), in order. */
|
|
163
|
+
readonly events: (name: string) => ReadonlyArray<Schema.Json>;
|
|
164
|
+
/** The pattern's observable value (`expect.value`), when it has one. */
|
|
165
|
+
readonly value?: () => Schema.Json;
|
|
166
|
+
}
|
|
167
|
+
export interface Driver {
|
|
168
|
+
readonly perform: (step: ActionStep) => void | Promise<void>;
|
|
169
|
+
readonly observer: Observer;
|
|
170
|
+
readonly dispose?: () => void;
|
|
171
|
+
}
|
|
172
|
+
export type ReplayResult = {
|
|
173
|
+
readonly status: "passed";
|
|
174
|
+
readonly skipped: ReadonlyArray<string>;
|
|
175
|
+
} | {
|
|
176
|
+
readonly status: "unsupported";
|
|
177
|
+
readonly reason: string;
|
|
178
|
+
};
|
|
179
|
+
/**
|
|
180
|
+
* Run one scenario against a driver. The bridge is the shared vocabulary gate:
|
|
181
|
+
* a config or step it cannot translate makes the scenario `unsupported` for
|
|
182
|
+
* every target alike, before anything runs. Throws `ScenarioMismatch` on a
|
|
183
|
+
* failed expectation.
|
|
184
|
+
*/
|
|
185
|
+
export declare const replayScenario: (scenario: Scenario, bridge: Pick<SpecBridge, "setup" | "event" | "stepFields">, makeDriver: (config: ScenarioConfig) => Driver | Promise<Driver>) => Promise<ReplayResult>;
|
|
186
|
+
/** What a pattern teaches the spec driver: scenario vocabulary <-> spec events/state. */
|
|
187
|
+
export interface SpecBridge {
|
|
188
|
+
/** Spec events that bring a fresh model to the scenario's `config` (not observed as events). */
|
|
189
|
+
readonly setup: (config: ScenarioConfig) => ReadonlyArray<QuintEvent>;
|
|
190
|
+
/**
|
|
191
|
+
* The spec events for a step (`[]` = no effect on this pattern; throw
|
|
192
|
+
* `Unsupported` otherwise). Convention: `key` / `type` on a target first
|
|
193
|
+
* focuses it, as a user would.
|
|
194
|
+
*/
|
|
195
|
+
readonly event: (step: ActionStep) => ReadonlyArray<QuintEvent>;
|
|
196
|
+
/**
|
|
197
|
+
* The `EXTENDED_STEP_FIELDS` this bridge honours in `event`. A step carrying
|
|
198
|
+
* any other typed field is `Unsupported` for every target alike.
|
|
199
|
+
*/
|
|
200
|
+
readonly stepFields?: ReadonlyArray<ExtendedStepField>;
|
|
201
|
+
/** Canonical callback name (`onOpenChange` -> `onExpandedChange`). */
|
|
202
|
+
readonly eventName?: (name: string) => string;
|
|
203
|
+
/** `expect.value` over the model state. */
|
|
204
|
+
readonly value?: (state: QuintModelValue) => Schema.Json;
|
|
205
|
+
readonly focus: (state: QuintModelValue) => string | null;
|
|
206
|
+
readonly attr: (state: QuintModelValue, target: string, name: string) => string | null;
|
|
207
|
+
readonly visible: (state: QuintModelValue) => ReadonlyArray<string>;
|
|
208
|
+
/** Callbacks the implementation would have fired during one transition. */
|
|
209
|
+
readonly events: (before: QuintModelValue, after: QuintModelValue) => ReadonlyArray<readonly [name: string, payload: Schema.Json]>;
|
|
210
|
+
}
|
|
211
|
+
/** A driver over the generated reference model. Checks every invariant after every step. */
|
|
212
|
+
export declare const specDriver: (model: QuintReferenceModel, bridge: SpecBridge, config?: ScenarioConfig) => Driver;
|
|
213
|
+
export interface Probe {
|
|
214
|
+
readonly attrs: ReadonlyArray<readonly [target: string, name: string]>;
|
|
215
|
+
readonly events: ReadonlyArray<string>;
|
|
216
|
+
}
|
|
217
|
+
export interface WalkOptions {
|
|
218
|
+
/** Steps the walk draws from (a pattern's user-level alphabet). */
|
|
219
|
+
readonly alphabet: ReadonlyArray<ActionStep>;
|
|
220
|
+
readonly probe: Probe;
|
|
221
|
+
/** A fresh [reference, candidate] pair per run, both at the same `config`. */
|
|
222
|
+
readonly makePair: () => Promise<readonly [Driver, Driver]>;
|
|
223
|
+
readonly runs?: number;
|
|
224
|
+
readonly steps?: number;
|
|
225
|
+
readonly seed?: number;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Differential test: the same random steps into the reference and the
|
|
229
|
+
* candidate; every probed observation must agree after every step.
|
|
230
|
+
*/
|
|
231
|
+
export declare const differentialWalk: (options: WalkOptions) => Promise<void>;
|
|
232
|
+
//#endregion
|
|
233
|
+
export { ActionStep, Scenario, ScenarioFile, Skipped };
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { violatedInvariants } from "@quint-machine/core";
|
|
2
|
+
import { Schema } from "effect";
|
|
3
|
+
//#region src/scenario.ts
|
|
4
|
+
/**
|
|
5
|
+
* The scenario bridge: one replay engine, two drivers.
|
|
6
|
+
*
|
|
7
|
+
* A scenario (`scenarios/apg/<pattern>.<source>.json`) is a list of user-level
|
|
8
|
+
* steps and expectations. `replayScenario` runs it against any `Driver`:
|
|
9
|
+
*
|
|
10
|
+
* - `specDriver` — steps become spec events (via a per-pattern `SpecBridge`),
|
|
11
|
+
* the generated reference model is stepped, every spec
|
|
12
|
+
* invariant is checked after every step;
|
|
13
|
+
* - a DOM driver — lives next to the implementation (packages/headless) and
|
|
14
|
+
* performs the same steps on real elements.
|
|
15
|
+
*
|
|
16
|
+
* `differentialWalk` then drives both with the same seeded random steps and
|
|
17
|
+
* compares their observations after each one, so the implementation is checked
|
|
18
|
+
* far beyond the ported scenarios. Pure TypeScript, no DOM: it loads in both
|
|
19
|
+
* Node and the browser.
|
|
20
|
+
*/
|
|
21
|
+
const Rest = [Schema.Record(Schema.String, Schema.Json)];
|
|
22
|
+
/** Expectation keys the replay engine checks against an `Observer`. */
|
|
23
|
+
const CHECKED_EXPECTATION_KEYS = /* @__PURE__ */ new Set([
|
|
24
|
+
"focus",
|
|
25
|
+
"attrs",
|
|
26
|
+
"visible",
|
|
27
|
+
"value",
|
|
28
|
+
"events"
|
|
29
|
+
]);
|
|
30
|
+
const Expectation = Schema.StructWithRest(Schema.Struct({
|
|
31
|
+
focus: Schema.optionalKey(Schema.NullOr(Schema.String)),
|
|
32
|
+
attrs: Schema.optionalKey(Schema.Record(Schema.String, Schema.Record(Schema.String, Schema.NullOr(Schema.String)))),
|
|
33
|
+
visible: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
34
|
+
hidden: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
35
|
+
checked: Schema.optionalKey(Schema.NullOr(Schema.String)),
|
|
36
|
+
value: Schema.optionalKey(Schema.Json),
|
|
37
|
+
events: Schema.optionalKey(Schema.Record(Schema.String, Schema.Array(Schema.Json)))
|
|
38
|
+
}), Rest);
|
|
39
|
+
/** Step fields every bridge understands; `ms` only ever rides on `wait`. */
|
|
40
|
+
const CORE_STEP_FIELDS = /* @__PURE__ */ new Set([
|
|
41
|
+
"event",
|
|
42
|
+
"target",
|
|
43
|
+
"key",
|
|
44
|
+
"text",
|
|
45
|
+
"prop",
|
|
46
|
+
"value",
|
|
47
|
+
"repeat",
|
|
48
|
+
"ms"
|
|
49
|
+
]);
|
|
50
|
+
/**
|
|
51
|
+
* Typed step fields a bridge must opt into (`SpecBridge.stepFields`): replaying
|
|
52
|
+
* a step while ignoring one of these would test a different step.
|
|
53
|
+
*/
|
|
54
|
+
const EXTENDED_STEP_FIELDS = [
|
|
55
|
+
"position",
|
|
56
|
+
"modifiers",
|
|
57
|
+
"phase",
|
|
58
|
+
"button",
|
|
59
|
+
"buttons",
|
|
60
|
+
"pointerType",
|
|
61
|
+
"pointerId",
|
|
62
|
+
"detail",
|
|
63
|
+
"count",
|
|
64
|
+
"canceled",
|
|
65
|
+
"defaultPrevented",
|
|
66
|
+
"isComposing",
|
|
67
|
+
"inputEvent",
|
|
68
|
+
"set"
|
|
69
|
+
];
|
|
70
|
+
const Modifier = Schema.Literals([
|
|
71
|
+
"Alt",
|
|
72
|
+
"Control",
|
|
73
|
+
"Meta",
|
|
74
|
+
"Shift"
|
|
75
|
+
]);
|
|
76
|
+
const PointerType = Schema.Literals([
|
|
77
|
+
"mouse",
|
|
78
|
+
"pen",
|
|
79
|
+
"touch"
|
|
80
|
+
]);
|
|
81
|
+
const KeyPhase = Schema.Literals(["down", "up"]);
|
|
82
|
+
const ActionStep = Schema.StructWithRest(Schema.Struct({
|
|
83
|
+
event: Schema.String,
|
|
84
|
+
target: Schema.optionalKey(Schema.String),
|
|
85
|
+
key: Schema.optionalKey(Schema.String),
|
|
86
|
+
text: Schema.optionalKey(Schema.String),
|
|
87
|
+
prop: Schema.optionalKey(Schema.String),
|
|
88
|
+
value: Schema.optionalKey(Schema.Json),
|
|
89
|
+
repeat: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Number])),
|
|
90
|
+
ms: Schema.optionalKey(Schema.Number),
|
|
91
|
+
position: Schema.optionalKey(Schema.Number),
|
|
92
|
+
modifiers: Schema.optionalKey(Schema.Array(Modifier)),
|
|
93
|
+
phase: Schema.optionalKey(KeyPhase),
|
|
94
|
+
button: Schema.optionalKey(Schema.Number),
|
|
95
|
+
buttons: Schema.optionalKey(Schema.Number),
|
|
96
|
+
pointerType: Schema.optionalKey(PointerType),
|
|
97
|
+
pointerId: Schema.optionalKey(Schema.Number),
|
|
98
|
+
detail: Schema.optionalKey(Schema.Number),
|
|
99
|
+
count: Schema.optionalKey(Schema.Number),
|
|
100
|
+
canceled: Schema.optionalKey(Schema.Boolean),
|
|
101
|
+
defaultPrevented: Schema.optionalKey(Schema.Boolean),
|
|
102
|
+
isComposing: Schema.optionalKey(Schema.Boolean),
|
|
103
|
+
inputEvent: Schema.optionalKey(Schema.String),
|
|
104
|
+
set: Schema.optionalKey(Schema.Record(Schema.String, Schema.Json))
|
|
105
|
+
}), Rest);
|
|
106
|
+
const ExpectStep = Schema.Struct({ expect: Expectation });
|
|
107
|
+
const Step = Schema.Union([ActionStep, ExpectStep]);
|
|
108
|
+
const isExpectStep = (step) => !("event" in step);
|
|
109
|
+
const Scenario = Schema.Struct({
|
|
110
|
+
id: Schema.String,
|
|
111
|
+
upstream: Schema.optionalKey(Schema.String),
|
|
112
|
+
upstreamTest: Schema.optionalKey(Schema.String),
|
|
113
|
+
apg: Schema.optionalKey(Schema.String),
|
|
114
|
+
config: Schema.optionalKey(Schema.Record(Schema.String, Schema.Json)),
|
|
115
|
+
steps: Schema.Array(Step),
|
|
116
|
+
notes: Schema.optionalKey(Schema.String)
|
|
117
|
+
});
|
|
118
|
+
/** An upstream behaviour deliberately not ported (`upstream*` when it has a single source). */
|
|
119
|
+
const Skipped = Schema.Struct({
|
|
120
|
+
id: Schema.optionalKey(Schema.String),
|
|
121
|
+
upstream: Schema.optionalKey(Schema.String),
|
|
122
|
+
upstreamTest: Schema.optionalKey(Schema.String),
|
|
123
|
+
reason: Schema.String
|
|
124
|
+
});
|
|
125
|
+
const ScenarioFile = Schema.Struct({
|
|
126
|
+
pattern: Schema.String,
|
|
127
|
+
source: Schema.String,
|
|
128
|
+
commit: Schema.optionalKey(Schema.NullOr(Schema.String)),
|
|
129
|
+
license: Schema.optionalKey(Schema.String),
|
|
130
|
+
upstream: Schema.optionalKey(Schema.String),
|
|
131
|
+
retrieved: Schema.optionalKey(Schema.String),
|
|
132
|
+
fixture: Schema.optionalKey(Schema.Record(Schema.String, Schema.Json)),
|
|
133
|
+
notes: Schema.optionalKey(Schema.String),
|
|
134
|
+
skipped: Schema.optionalKey(Schema.Array(Skipped)),
|
|
135
|
+
scenarios: Schema.Array(Scenario)
|
|
136
|
+
});
|
|
137
|
+
/** Decode a scenario JSON document (the trust boundary for ported data). */
|
|
138
|
+
const parseScenarioFile = (input) => Schema.decodeUnknownSync(ScenarioFile)(input);
|
|
139
|
+
/** A step or observation the target does not model. Skips, never fails. */
|
|
140
|
+
var Unsupported = class extends Error {};
|
|
141
|
+
/** The target disagreed with the scenario (or, in a walk, with the other target). */
|
|
142
|
+
var ScenarioMismatch = class extends Error {};
|
|
143
|
+
const normalize = (target) => target === null ? null : target.replace(/:0$/, "");
|
|
144
|
+
const partName = (target) => target.replace(/:0$/, "");
|
|
145
|
+
const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
146
|
+
const mismatch = (what, expected, actual) => {
|
|
147
|
+
throw new ScenarioMismatch(`${what}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
|
|
148
|
+
};
|
|
149
|
+
const check = (expectation, observer, skipped) => {
|
|
150
|
+
const attempt = (label, run) => {
|
|
151
|
+
try {
|
|
152
|
+
run();
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (!(error instanceof Unsupported)) throw error;
|
|
155
|
+
skipped.push(`${label}: ${error.message}`);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
for (const key of Object.keys(expectation)) if (!CHECKED_EXPECTATION_KEYS.has(key)) skipped.push(`${key}: expectation not supported by the replay engine`);
|
|
159
|
+
if (expectation.focus !== void 0) {
|
|
160
|
+
const { focus } = expectation;
|
|
161
|
+
attempt("focus", () => {
|
|
162
|
+
const actual = normalize(observer.focus());
|
|
163
|
+
if (actual !== normalize(focus)) mismatch("focus", focus, actual);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
for (const [target, attrs] of Object.entries(expectation.attrs ?? {})) for (const [name, expected] of Object.entries(attrs)) attempt(`${target}[${name}]`, () => {
|
|
167
|
+
const actual = observer.attr(target, name);
|
|
168
|
+
if (actual !== expected) mismatch(`${target}[${name}]`, expected, actual);
|
|
169
|
+
});
|
|
170
|
+
if (expectation.visible !== void 0) {
|
|
171
|
+
const { visible } = expectation;
|
|
172
|
+
attempt("visible", () => {
|
|
173
|
+
const expected = visible.map(partName).sort();
|
|
174
|
+
const actual = observer.visible().map(partName).sort();
|
|
175
|
+
if (!same(actual, expected)) mismatch("visible", expected, actual);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
for (const [name, expected] of Object.entries(expectation.events ?? {})) attempt(`events.${name}`, () => {
|
|
179
|
+
const actual = observer.events(name);
|
|
180
|
+
if (!same(actual, expected)) mismatch(`events.${name}`, expected, actual);
|
|
181
|
+
});
|
|
182
|
+
if (expectation.value !== void 0) {
|
|
183
|
+
const { value } = expectation;
|
|
184
|
+
attempt("value", () => {
|
|
185
|
+
if (observer.value === void 0) throw new Unsupported("no observable value");
|
|
186
|
+
const actual = observer.value();
|
|
187
|
+
if (!same(actual, value)) mismatch("value", value, actual);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
/**
|
|
192
|
+
* Run one scenario against a driver. The bridge is the shared vocabulary gate:
|
|
193
|
+
* a config or step it cannot translate makes the scenario `unsupported` for
|
|
194
|
+
* every target alike, before anything runs. Throws `ScenarioMismatch` on a
|
|
195
|
+
* failed expectation.
|
|
196
|
+
*/
|
|
197
|
+
const replayScenario = async (scenario, bridge, makeDriver) => {
|
|
198
|
+
const config = scenario.config ?? {};
|
|
199
|
+
const understood = /* @__PURE__ */ new Set([...CORE_STEP_FIELDS, ...bridge.stepFields ?? []]);
|
|
200
|
+
try {
|
|
201
|
+
bridge.setup(config);
|
|
202
|
+
for (const step of scenario.steps) if (!isExpectStep(step)) {
|
|
203
|
+
const unknown = Object.keys(step).find((k) => !understood.has(k));
|
|
204
|
+
if (unknown !== void 0) throw new Unsupported(`step field "${unknown}"`);
|
|
205
|
+
bridge.event(step);
|
|
206
|
+
}
|
|
207
|
+
} catch (error) {
|
|
208
|
+
if (error instanceof Unsupported) return {
|
|
209
|
+
status: "unsupported",
|
|
210
|
+
reason: error.message
|
|
211
|
+
};
|
|
212
|
+
throw error;
|
|
213
|
+
}
|
|
214
|
+
const driver = await makeDriver(config);
|
|
215
|
+
const skipped = [];
|
|
216
|
+
try {
|
|
217
|
+
for (const [index, step] of scenario.steps.entries()) try {
|
|
218
|
+
if (isExpectStep(step)) check(step.expect, driver.observer, skipped);
|
|
219
|
+
else await driver.perform(step);
|
|
220
|
+
} catch (error) {
|
|
221
|
+
if (error instanceof ScenarioMismatch) throw new ScenarioMismatch(`${scenario.id} step ${index}: ${error.message}`);
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
} finally {
|
|
225
|
+
driver.dispose?.();
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
status: "passed",
|
|
229
|
+
skipped
|
|
230
|
+
};
|
|
231
|
+
};
|
|
232
|
+
/** A driver over the generated reference model. Checks every invariant after every step. */
|
|
233
|
+
const specDriver = (model, bridge, config = {}) => {
|
|
234
|
+
const apply = (state, event) => {
|
|
235
|
+
const next = model.transition(state, event);
|
|
236
|
+
const violated = violatedInvariants(model, next);
|
|
237
|
+
if (violated.length > 0) throw new ScenarioMismatch(`spec invariant violated after ${JSON.stringify(event)}: ${violated.join(", ")}`);
|
|
238
|
+
return next;
|
|
239
|
+
};
|
|
240
|
+
const { value } = bridge;
|
|
241
|
+
let state = bridge.setup(config).reduce(apply, model.initial);
|
|
242
|
+
const fired = /* @__PURE__ */ new Map();
|
|
243
|
+
return {
|
|
244
|
+
perform: (step) => {
|
|
245
|
+
const before = state;
|
|
246
|
+
state = bridge.event(step).reduce(apply, state);
|
|
247
|
+
for (const [name, payload] of bridge.events(before, state)) fired.set(name, [...fired.get(name) ?? [], payload]);
|
|
248
|
+
},
|
|
249
|
+
observer: {
|
|
250
|
+
focus: () => bridge.focus(state),
|
|
251
|
+
attr: (target, name) => bridge.attr(state, target, name),
|
|
252
|
+
visible: () => bridge.visible(state),
|
|
253
|
+
events: (name) => fired.get(bridge.eventName?.(name) ?? name) ?? [],
|
|
254
|
+
...value === void 0 ? {} : { value: () => value(state) }
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
};
|
|
258
|
+
const observed = (read) => {
|
|
259
|
+
try {
|
|
260
|
+
return read();
|
|
261
|
+
} catch (error) {
|
|
262
|
+
if (error instanceof Unsupported) return "?";
|
|
263
|
+
throw error;
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
const snapshot = (observer, probe) => [
|
|
267
|
+
["focus", observed(() => normalize(observer.focus()))],
|
|
268
|
+
["visible", observer.visible().map(partName).sort()],
|
|
269
|
+
...probe.attrs.map(([target, name]) => [`${target}[${name}]`, observed(() => observer.attr(target, name))]),
|
|
270
|
+
...probe.events.map((name) => [`events.${name}`, [...observer.events(name)]])
|
|
271
|
+
];
|
|
272
|
+
const LCG_MULTIPLIER = 1664525;
|
|
273
|
+
const LCG_INCREMENT = 1013904223;
|
|
274
|
+
const UINT32 = 4294967296;
|
|
275
|
+
const lcg = (seed) => {
|
|
276
|
+
let state = seed >>> 0;
|
|
277
|
+
return () => {
|
|
278
|
+
state = Math.imul(state, LCG_MULTIPLIER) + LCG_INCREMENT >>> 0;
|
|
279
|
+
return state / UINT32;
|
|
280
|
+
};
|
|
281
|
+
};
|
|
282
|
+
/**
|
|
283
|
+
* Differential test: the same random steps into the reference and the
|
|
284
|
+
* candidate; every probed observation must agree after every step.
|
|
285
|
+
*/
|
|
286
|
+
const differentialWalk = async (options) => {
|
|
287
|
+
const rand = lcg(options.seed ?? 0);
|
|
288
|
+
for (let run = 0; run < (options.runs ?? 20); run += 1) {
|
|
289
|
+
const [reference, candidate] = await options.makePair();
|
|
290
|
+
const path = [];
|
|
291
|
+
try {
|
|
292
|
+
for (let index = 0; index < (options.steps ?? 30); index += 1) {
|
|
293
|
+
const step = options.alphabet[Math.floor(rand() * options.alphabet.length)];
|
|
294
|
+
if (step === void 0) return;
|
|
295
|
+
path.push(step);
|
|
296
|
+
await reference.perform(step);
|
|
297
|
+
await candidate.perform(step);
|
|
298
|
+
const expected = snapshot(reference.observer, options.probe);
|
|
299
|
+
const actual = snapshot(candidate.observer, options.probe);
|
|
300
|
+
const differing = expected.flatMap(([label, value], position) => {
|
|
301
|
+
const other = actual[position]?.[1] ?? "?";
|
|
302
|
+
return value !== "?" && other !== "?" && !same(value, other) ? [label] : [];
|
|
303
|
+
});
|
|
304
|
+
if (differing.length > 0) throw new ScenarioMismatch(`${differing.join(", ")} differ: spec ${JSON.stringify(expected)} vs implementation ${JSON.stringify(actual)} after ${JSON.stringify(path)}`);
|
|
305
|
+
}
|
|
306
|
+
} finally {
|
|
307
|
+
reference.dispose?.();
|
|
308
|
+
candidate.dispose?.();
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
//#endregion
|
|
313
|
+
export { EXTENDED_STEP_FIELDS, ScenarioMismatch, Unsupported, differentialWalk, parseScenarioFile, replayScenario, specDriver };
|
package/package.json
CHANGED
|
@@ -1,6 +1,59 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cssints/apg-specs",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.5.1",
|
|
4
|
+
"description": "APG widget specs: Quint sources, generated models, scenario bridges and ported scenario sets, the test oracle of headless widgets",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/alexeiaccio/css-in-ts-compiler.git",
|
|
9
|
+
"directory": "packages/apg-specs"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://cssints.dev",
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"specs",
|
|
15
|
+
"shared",
|
|
16
|
+
"scenarios"
|
|
17
|
+
],
|
|
18
|
+
"type": "module",
|
|
19
|
+
"sideEffects": false,
|
|
20
|
+
"exports": {
|
|
21
|
+
"./scenario": {
|
|
22
|
+
"types": "./dist/scenario.d.mts",
|
|
23
|
+
"default": "./dist/scenario.mjs"
|
|
24
|
+
},
|
|
25
|
+
"./apg/listbox-shared": {
|
|
26
|
+
"types": "./dist/apg/listbox-shared.d.mts",
|
|
27
|
+
"default": "./dist/apg/listbox-shared.mjs"
|
|
28
|
+
},
|
|
29
|
+
"./apg/*.bridge": {
|
|
30
|
+
"types": "./dist/apg/*.bridge.d.mts",
|
|
31
|
+
"default": "./dist/apg/*.bridge.mjs"
|
|
32
|
+
},
|
|
33
|
+
"./apg/*.model.json": "./dist/apg/*.model.json",
|
|
34
|
+
"./canvas/*.bridge": {
|
|
35
|
+
"types": "./dist/canvas/*.bridge.d.mts",
|
|
36
|
+
"default": "./dist/canvas/*.bridge.mjs"
|
|
37
|
+
},
|
|
38
|
+
"./canvas/*.model.json": "./dist/canvas/*.model.json",
|
|
39
|
+
"./scenarios/apg/*.json": "./scenarios/apg/*.json",
|
|
40
|
+
"./scenarios/canvas/*.json": "./scenarios/canvas/*.json",
|
|
41
|
+
"./scenarios/parts.json": "./scenarios/parts.json",
|
|
42
|
+
"./specs/apg/*.qnt": "./specs/apg/*.qnt",
|
|
43
|
+
"./specs/canvas/*.qnt": "./specs/canvas/*.qnt",
|
|
44
|
+
"./shared/*.qnt": "./shared/*.qnt",
|
|
45
|
+
"./package.json": "./package.json"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@quint-machine/core": "^0.1.0"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"effect": "^4.0.0"
|
|
52
|
+
},
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=22"
|
|
55
|
+
},
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"access": "public"
|
|
58
|
+
}
|
|
59
|
+
}
|