@pascal-app/editor 1.0.4 → 1.0.6
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 +106 -0
- package/package.json +40 -38
- package/src/components/editor/__bench__/room-label-placement.bench.ts +108 -0
- package/src/components/editor/bake-thumbnail.tsx +6 -15
- package/src/components/editor/construction-reveal-audio.test.tsx +150 -0
- package/src/components/editor/construction-reveal-audio.tsx +40 -0
- package/src/components/editor/custom-camera-controls.tsx +81 -8
- package/src/components/editor/editor-layout-mobile.tsx +1 -1
- package/src/components/editor/editor-layout-v2.tsx +1 -1
- package/src/components/editor/export-manager.tsx +14 -19
- package/src/components/editor/first-person/build-collider-world.test.ts +269 -0
- package/src/components/editor/first-person/build-collider-world.ts +22 -5
- package/src/components/editor/first-person/ground-holes.test.ts +128 -0
- package/src/components/editor/first-person/ground-holes.ts +130 -0
- package/src/components/editor/first-person/mechanism-targets.test.ts +89 -2
- package/src/components/editor/first-person/mechanism-targets.ts +52 -0
- package/src/components/editor/first-person-controls.tsx +19 -11
- package/src/components/editor/floor-aware-camera-controls.test.ts +24 -0
- package/src/components/editor/floorplan-mode-coordinator.tsx +2 -2
- package/src/components/editor/floorplan-panel.tsx +72 -23
- package/src/components/editor/follow-pascal.test.tsx +912 -0
- package/src/components/editor/follow-pascal.tsx +354 -0
- package/src/components/editor/group-actions.ts +2 -0
- package/src/components/editor/group-move-3d.ts +2 -0
- package/src/components/editor/group-rotate-handle.tsx +2 -0
- package/src/components/editor/group-transform-shared.test.ts +460 -0
- package/src/components/editor/index.tsx +166 -79
- package/src/components/editor/riser-diagram-panel.tsx +14 -7
- package/src/components/editor/room-highlight.tsx +13 -5
- package/src/components/editor/room-labels.test.ts +621 -128
- package/src/components/editor/room-labels.tsx +1090 -190
- package/src/components/editor/selection-manager.tsx +38 -117
- package/src/components/editor/slab-hole-highlights.tsx +2 -2
- package/src/components/editor/snapshot-capture-overlay.tsx +3 -2
- package/src/components/editor/snapshot-capture.test.ts +69 -58
- package/src/components/editor/snapshot-capture.ts +7 -1
- package/src/components/editor/thumbnail-generator.test.ts +350 -4
- package/src/components/editor/thumbnail-generator.tsx +128 -54
- package/src/components/editor/use-floorplan-background-placement.ts +3 -1
- package/src/components/editor/views/plugins-view.tsx +1 -8
- package/src/components/editor/views/view-bar.test.tsx +54 -0
- package/src/components/editor/views/view-bar.tsx +140 -17
- package/src/components/editor/wall-snap-beacon-layer.tsx +5 -4
- package/src/components/editor-2d/floorplan-group-move.tsx +3 -0
- package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +2 -2
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +3 -4
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +1 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +29 -7
- package/src/components/editor-2d/renderers/floorplan-reveal.test.tsx +254 -0
- package/src/components/editor-2d/renderers/floorplan-reveal.ts +141 -0
- package/src/components/point-ask/bubble.tsx +538 -0
- package/src/components/point-ask/hover-chip.tsx +91 -0
- package/src/components/point-ask/mode-pill.tsx +92 -0
- package/src/components/point-ask/optional-motion.ts +22 -0
- package/src/components/point-ask/outline-corners.tsx +93 -0
- package/src/components/point-ask/pins.tsx +711 -0
- package/src/components/point-ask/point-ask-layer.tsx +83 -0
- package/src/components/point-ask/point-ask-styles.ts +105 -0
- package/src/components/point-ask/region-marquee-styles.ts +15 -0
- package/src/components/point-ask/region-marquee.tsx +64 -0
- package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +12 -2
- package/src/components/systems/roof/roof-edit-system.tsx +1 -0
- package/src/components/tools/distribution/connection-compatibility.test.ts +34 -0
- package/src/components/tools/distribution/connection-compatibility.ts +43 -0
- package/src/components/tools/distribution/distribution-public.ts +9 -0
- package/src/components/tools/distribution/distribution-run-contract.test.ts +26 -0
- package/src/components/tools/distribution/distribution-run-contract.ts +94 -0
- package/src/components/tools/distribution/distribution-run-tool.test.ts +241 -0
- package/src/components/tools/distribution/distribution-run-tool.test.tsx +125 -0
- package/src/components/tools/distribution/distribution-run-tool.tsx +1323 -0
- package/src/components/tools/distribution/draw-alignment.ts +35 -0
- package/src/components/tools/distribution/ports.test.ts +56 -0
- package/src/components/tools/distribution/ports.ts +378 -0
- package/src/components/tools/distribution/run-cursor.test.ts +135 -0
- package/src/components/tools/distribution/run-cursor.ts +36 -0
- package/src/components/tools/distribution/run-direction-feedback.test.ts +60 -0
- package/src/components/tools/distribution/run-direction-feedback.tsx +251 -0
- package/src/components/tools/distribution/run-port-snap.test.ts +79 -0
- package/src/components/tools/distribution/run-port-snap.ts +25 -0
- package/src/components/tools/distribution/run-screen-direction.test.ts +23 -0
- package/src/components/tools/distribution/run-screen-direction.ts +30 -0
- package/src/components/tools/elevator/elevator-tool.tsx +3 -6
- package/src/components/tools/elevator/move-elevator-tool.tsx +2 -3
- package/src/components/tools/item/item-surface-strategy-resolver.test.ts +67 -68
- package/src/components/tools/item/placement-history.test.tsx +38 -0
- package/src/components/tools/item/placement-strategies.test.ts +46 -1
- package/src/components/tools/item/placement-strategies.ts +3 -6
- package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +56 -56
- package/src/components/tools/item/use-draft-node.test.tsx +94 -94
- package/src/components/tools/item/use-placement-coordinator.tsx +55 -49
- package/src/components/tools/registry/item-surface-move.test.ts +62 -63
- package/src/components/tools/registry/item-surface-move.ts +2 -0
- package/src/components/tools/registry/item-surface-resolver.test.ts +26 -26
- package/src/components/tools/registry/move-registry-node-tool.test.ts +268 -1
- package/src/components/tools/registry/move-registry-node-tool.tsx +63 -29
- package/src/components/tools/tool-manager.tsx +14 -5
- package/src/components/tools/wall/wall-drafting.test.ts +170 -57
- package/src/components/ui/action-menu/action-button.tsx +6 -1
- package/src/components/ui/action-menu/camera-actions.tsx +7 -3
- package/src/components/ui/action-menu/control-modes.tsx +13 -7
- package/src/components/ui/action-menu/follow-pascal-pill.tsx +32 -0
- package/src/components/ui/action-menu/follow-pascal-toggle.tsx +36 -0
- package/src/components/ui/action-menu/index.tsx +56 -33
- package/src/components/ui/action-menu/measurement-control.tsx +11 -9
- package/src/components/ui/action-menu/placement.ts +20 -0
- package/src/components/ui/action-menu/point-ask-button.tsx +52 -0
- package/src/components/ui/action-menu/view-toggles.tsx +47 -42
- package/src/components/ui/build-panel/build-panel.tsx +10 -3
- package/src/components/ui/command-palette/editor-commands.tsx +3 -12
- package/src/components/ui/command-palette/index.tsx +45 -15
- package/src/components/ui/command-palette/ranked-groups.test.ts +31 -0
- package/src/components/ui/controls/action-button.tsx +5 -2
- package/src/components/ui/controls/metric-control.tsx +1 -1
- package/src/components/ui/controls/segmented-control.tsx +21 -8
- package/src/components/ui/controls/select-control.tsx +1 -1
- package/src/components/ui/controls/slider-control.tsx +1 -1
- package/src/components/ui/controls/toggle-control.tsx +3 -3
- package/src/components/ui/controls/tool-options-panel.tsx +1 -1
- package/src/components/ui/floating-level-selector.tsx +150 -45
- package/src/components/ui/helpers/contextual-helper-panel.tsx +32 -7
- package/src/components/ui/helpers/helper-manager.test.tsx +54 -2
- package/src/components/ui/helpers/helper-manager.tsx +7 -1
- package/src/components/ui/helpers/item-helper.tsx +9 -1
- package/src/components/ui/panels/collections/collections-popover.tsx +8 -8
- package/src/components/ui/panels/mobile-panel-sheet.tsx +2 -2
- package/src/components/ui/panels/mobile-selection-bar.tsx +6 -3
- package/src/components/ui/panels/multi-selection-panel.tsx +120 -3
- package/src/components/ui/panels/panel-wrapper.tsx +53 -27
- package/src/components/ui/panels/parametric-field-control.tsx +2 -2
- package/src/components/ui/panels/parametric-inspector.tsx +3 -3
- package/src/components/ui/panels/room-panel.tsx +65 -0
- package/src/components/ui/primitives/badge.tsx +8 -0
- package/src/components/ui/primitives/sidebar.tsx +1 -1
- package/src/components/ui/right-stack.tsx +21 -2
- package/src/components/ui/sidebar/app-sidebar.tsx +1 -1
- package/src/components/ui/sidebar/icon-rail.tsx +3 -3
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +6 -6
- package/src/components/ui/sidebar/panels/settings-panel/animation-speed-setting.tsx +40 -0
- package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +7 -8
- package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.test.tsx +170 -0
- package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +92 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +107 -23
- package/src/components/ui/sidebar/panels/settings-panel/interface-theme.test.tsx +20 -0
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +4 -10
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -11
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +5 -12
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +13 -10
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +2 -2
- package/src/components/ui/sidebar/tab-bar.tsx +4 -4
- package/src/components/viewer/viewer-controls-bar.tsx +18 -6
- package/src/components/viewer/viewer-rooms.tsx +16 -0
- package/src/hooks/use-auto-save.integration.test.tsx +96 -38
- package/src/hooks/use-auto-save.ts +26 -1
- package/src/hooks/use-ceiling-events.test.ts +123 -118
- package/src/hooks/use-grid-events.test.ts +105 -105
- package/src/hooks/use-keyboard.test.ts +65 -16
- package/src/hooks/use-keyboard.ts +29 -12
- package/src/hooks/use-placement-notice.test.ts +24 -1
- package/src/hooks/use-placement-notice.ts +37 -6
- package/src/index.tsx +71 -24
- package/src/lib/__fixtures__/owner-victor-seattle-parking.json +1 -0
- package/src/lib/__fixtures__/owner-villa-aurelia-basement.json +1 -0
- package/src/lib/add-storey.test.ts +177 -0
- package/src/lib/add-storey.ts +59 -0
- package/src/lib/below-grade.test.tsx +171 -0
- package/src/lib/below-grade.ts +133 -0
- package/src/lib/camera-facing.test.ts +24 -0
- package/src/lib/camera-facing.ts +24 -0
- package/src/lib/direct-manipulation.ts +2 -0
- package/src/lib/download-project-file.ts +17 -0
- package/src/lib/editor-level-display.ts +9 -2
- package/src/lib/editor-views.ts +35 -1
- package/src/lib/export-texture-utils.ts +2 -4
- package/src/lib/floor-foundation-support.test.ts +1 -0
- package/src/lib/floorplan/floorplan-pdfkit-document.ts +31 -16
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +39 -0
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +4 -0
- package/src/lib/floorplan/pdfkit-standard-fonts.d.ts +5 -0
- package/src/lib/floorplan/sheet-export.test.ts +24 -0
- package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +34 -45
- package/src/lib/floorplan/site-plan/coverage.ts +2 -4
- package/src/lib/floorplan/site-plan/front-edge.ts +2 -1
- package/src/lib/floorplan/site-plan/geometry.test.ts +9 -15
- package/src/lib/floorplan/site-plan/geometry.ts +1 -416
- package/src/lib/floorplan/site-plan/index.ts +0 -16
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +117 -0
- package/src/lib/floorplan/site-plan/site-annotations.ts +3 -2
- package/src/lib/floorplan/site-plan/site-parts.ts +14 -106
- package/src/lib/floorplan/site-plan/yards.test.ts +2 -1
- package/src/lib/follow-pascal-exterior.test-fixture.ts +17 -0
- package/src/lib/follow-pascal-exterior.test.ts +171 -0
- package/src/lib/follow-pascal-exterior.ts +39 -0
- package/src/lib/follow-pascal-presentation.test.ts +203 -0
- package/src/lib/follow-pascal-presentation.ts +129 -0
- package/src/lib/follow-pascal-sight.ts +84 -0
- package/src/lib/follow-pascal-targets.test.ts +406 -0
- package/src/lib/follow-pascal-targets.ts +198 -0
- package/src/lib/follow-pascal.test.ts +350 -0
- package/src/lib/follow-pascal.ts +284 -0
- package/src/lib/fresh-planar-placement.test.ts +112 -0
- package/src/lib/fresh-planar-placement.ts +57 -3
- package/src/lib/geometry-script/author.ts +6 -1
- package/src/lib/geometry-script/client.ts +8 -1
- package/src/lib/geometry-script/finish-scripts.ts +127 -0
- package/src/lib/geometry-script/protocol.ts +1 -0
- package/src/lib/glb-export.test.ts +187 -162
- package/src/lib/glb-export.ts +4 -1
- package/src/lib/history.test.ts +12 -7
- package/src/lib/interaction/scope.ts +4 -0
- package/src/lib/level-selection.test.ts +57 -0
- package/src/lib/level-selection.ts +16 -1
- package/src/lib/lot/clean-ring.test.ts +2 -1
- package/src/lib/lot/drop-in.ts +9 -24
- package/src/lib/lot/lot-patch.test.ts +1 -1
- package/src/lib/lot/lot-patch.ts +2 -2
- package/src/lib/measurement-kind.test.ts +6 -8
- package/src/lib/measurements.test.ts +23 -0
- package/src/lib/orbit-floor-clearance.test.ts +36 -0
- package/src/lib/orbit-floor-clearance.ts +48 -0
- package/src/lib/paint-pass-through.test.ts +11 -0
- package/src/lib/paint-pass-through.ts +4 -2
- package/src/lib/paint-scope.ts +1 -0
- package/src/lib/parametric-node-update.ts +16 -7
- package/src/lib/plugin-chat.ts +15 -0
- package/src/lib/point-ask/__fixtures__/consumer-browser.tsx +76 -0
- package/src/lib/point-ask/__fixtures__/house.ts +81 -0
- package/src/lib/point-ask/ask-created.test.ts +195 -0
- package/src/lib/point-ask/ask-flow.test.ts +380 -0
- package/src/lib/point-ask/build-context.test.ts +173 -0
- package/src/lib/point-ask/build-context.ts +94 -0
- package/src/lib/point-ask/capture-crop.ts +129 -0
- package/src/lib/point-ask/choreography.ts +69 -0
- package/src/lib/point-ask/compare.test.ts +145 -0
- package/src/lib/point-ask/compare.ts +73 -0
- package/src/lib/point-ask/describe-target.test.ts +165 -0
- package/src/lib/point-ask/describe-target.ts +234 -0
- package/src/lib/point-ask/dictation.ts +79 -0
- package/src/lib/point-ask/edge-marker.test.ts +61 -0
- package/src/lib/point-ask/edge-marker.ts +27 -0
- package/src/lib/point-ask/frame-loop.ts +25 -0
- package/src/lib/point-ask/history-marks.test.ts +94 -0
- package/src/lib/point-ask/history-marks.ts +39 -0
- package/src/lib/point-ask/host-api.ts +32 -0
- package/src/lib/point-ask/outline-box.test.ts +30 -0
- package/src/lib/point-ask/outline-box.ts +11 -0
- package/src/lib/point-ask/overlay-metrics.ts +6 -0
- package/src/lib/point-ask/pick.test.ts +220 -0
- package/src/lib/point-ask/pick.ts +121 -0
- package/src/lib/point-ask/pin-machine.test.ts +494 -0
- package/src/lib/point-ask/pin-machine.ts +217 -0
- package/src/lib/point-ask/placement.test.ts +203 -0
- package/src/lib/point-ask/placement.ts +141 -0
- package/src/lib/point-ask/pointer-tracker.ts +34 -0
- package/src/lib/point-ask/projector.ts +83 -0
- package/src/lib/point-ask/region.test.ts +157 -0
- package/src/lib/point-ask/region.ts +136 -0
- package/src/lib/point-ask/retry.test.ts +217 -0
- package/src/lib/point-ask/retry.ts +37 -0
- package/src/lib/point-ask/scene-commit.test.ts +312 -0
- package/src/lib/point-ask/scene-commit.ts +32 -0
- package/src/lib/point-ask/session.ts +1391 -0
- package/src/lib/point-ask/spring-keyframes.test.ts +45 -0
- package/src/lib/point-ask/spring-keyframes.ts +38 -0
- package/src/lib/point-ask/spring.test.ts +63 -0
- package/src/lib/point-ask/spring.ts +107 -0
- package/src/lib/point-ask/suggestions.test.ts +80 -0
- package/src/lib/point-ask/suggestions.ts +125 -0
- package/src/lib/point-ask/target-scope.test.ts +622 -0
- package/src/lib/point-ask/target-scope.ts +338 -0
- package/src/lib/point-ask/types.ts +48 -0
- package/src/lib/portable-export.test.ts +34 -1
- package/src/lib/portable-export.ts +119 -53
- package/src/lib/print-feature-thickness.ts +10 -1
- package/src/lib/print-roof-solids.test.ts +39 -1
- package/src/lib/print-roof-solids.ts +43 -10
- package/src/lib/print-shell-compiler-manifold-core.ts +1 -1
- package/src/lib/room-assembly-overlay.ts +2 -3
- package/src/lib/room-selection.test.ts +45 -1
- package/src/lib/scene-clipboard.test.ts +246 -1
- package/src/lib/scene-clipboard.ts +44 -14
- package/src/lib/selection-routing.ts +1 -1
- package/src/lib/sfx-bus.ts +13 -0
- package/src/lib/sfx-player.test.ts +70 -9
- package/src/lib/sfx-player.ts +6 -0
- package/src/lib/stair-levels.test.ts +25 -0
- package/src/lib/stair-levels.ts +22 -3
- package/src/lib/terrace-draft.ts +2 -1
- package/src/lib/viewer-selection.ts +22 -11
- package/src/lib/wall-placement-pointer.test.ts +42 -0
- package/src/lib/wall-placement-pointer.ts +57 -0
- package/src/lib/window-changes.ts +35 -0
- package/src/store/use-audio.test.ts +146 -0
- package/src/store/use-audio.tsx +127 -3
- package/src/store/use-construction-reveal.test.ts +173 -0
- package/src/store/use-construction-reveal.ts +145 -0
- package/src/store/use-corner-snap-hint.test.ts +24 -0
- package/src/store/use-corner-snap-hint.ts +47 -0
- package/src/store/use-follow-pascal.test.ts +62 -0
- package/src/store/use-follow-pascal.ts +53 -0
- package/src/store/use-point-ask.ts +247 -0
- package/src/store/use-stair-build-preview.ts +25 -5
- package/src/store/view-layout.test.ts +27 -1
- package/src/store/view-layout.ts +4 -1
- package/src/test-utils/render-current-markup.tsx +16 -0
- package/src/components/tools/stair/stair-click-guard.test.ts +0 -58
- package/src/components/tools/stair/stair-click-guard.ts +0 -72
- package/src/components/tools/stair/stair-defaults.ts +0 -15
- package/src/components/tools/stair/stair-tool.tsx +0 -682
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import {
|
|
3
|
+
alsoChangedLine,
|
|
4
|
+
cardCarriedFromPointer,
|
|
5
|
+
cardTitle,
|
|
6
|
+
createPin,
|
|
7
|
+
type PinEvent,
|
|
8
|
+
type PinModel,
|
|
9
|
+
reducePin,
|
|
10
|
+
} from './pin-machine'
|
|
11
|
+
|
|
12
|
+
// The pin keeps the ask's promise in place (spec 2.6-2.8). Written first: a pin that turns to a
|
|
13
|
+
// check for a step that wrote nothing; commits counted before the turn started;
|
|
14
|
+
// a question shown as "no change"; Undo from a state with nothing to undo; a late event undoing a
|
|
15
|
+
// finished pin.
|
|
16
|
+
|
|
17
|
+
const ASK = 'ask_1'
|
|
18
|
+
|
|
19
|
+
const fresh = () => createPin({ askId: ASK, text: 'Make it wider', targetIds: ['window_1'] })
|
|
20
|
+
|
|
21
|
+
const status = (more: Record<string, unknown>): PinEvent =>
|
|
22
|
+
({ type: 'status', status: { askId: ASK, ...more } }) as PinEvent
|
|
23
|
+
|
|
24
|
+
const play = (pin: PinModel, ...events: PinEvent[]) => events.reduce(reducePin, pin)
|
|
25
|
+
|
|
26
|
+
const commit = (ids: string[], inTargets: (id: string) => boolean = () => false): PinEvent => ({
|
|
27
|
+
type: 'commit',
|
|
28
|
+
changedNodeIds: ids,
|
|
29
|
+
isInTargets: inTargets,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const working = (pin: PinModel = fresh()) => play(pin, status({ status: 'working' }))
|
|
33
|
+
|
|
34
|
+
describe('a new pin', () => {
|
|
35
|
+
test('starts as sent, with nothing landed', () => {
|
|
36
|
+
const pin = fresh()
|
|
37
|
+
|
|
38
|
+
expect(pin).toMatchObject({
|
|
39
|
+
askId: ASK,
|
|
40
|
+
text: 'Make it wider',
|
|
41
|
+
targetIds: ['window_1'],
|
|
42
|
+
state: 'sent',
|
|
43
|
+
landed: 0,
|
|
44
|
+
changedTargetIds: [],
|
|
45
|
+
alsoChangedIds: [],
|
|
46
|
+
ended: false,
|
|
47
|
+
undone: false,
|
|
48
|
+
kept: false,
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test('the sent event changes nothing', () => {
|
|
53
|
+
expect(play(fresh(), { type: 'sent' })).toEqual(fresh())
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
describe('queued and working', () => {
|
|
58
|
+
test('queued while Pascal is on another turn', () => {
|
|
59
|
+
const pin = play(fresh(), status({ status: 'queued', turnId: 't1' }))
|
|
60
|
+
|
|
61
|
+
expect(pin.state).toBe('queued')
|
|
62
|
+
expect(pin.turnId).toBe('t1')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('working from sent or from queued, with the live sentence', () => {
|
|
66
|
+
const direct = play(fresh(), status({ status: 'working', sentence: 'Widening the window' }))
|
|
67
|
+
const queued = play(fresh(), status({ status: 'queued' }), status({ status: 'working' }))
|
|
68
|
+
|
|
69
|
+
expect(direct.state).toBe('working')
|
|
70
|
+
expect(direct.sentence).toBe('Widening the window')
|
|
71
|
+
expect(queued.state).toBe('working')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('a new sentence updates it and keeps the state', () => {
|
|
75
|
+
const pin = play(
|
|
76
|
+
working(),
|
|
77
|
+
status({ status: 'working', sentence: 'Looking at the wall' }),
|
|
78
|
+
status({ status: 'working', sentence: 'Widening the window' }),
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
expect(pin.state).toBe('working')
|
|
82
|
+
expect(pin.sentence).toBe('Widening the window')
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test('an event for another ask is none of its business', () => {
|
|
86
|
+
const other: PinEvent = { type: 'status', status: { askId: 'ask_2', status: 'working' } }
|
|
87
|
+
|
|
88
|
+
expect(play(fresh(), other)).toEqual(fresh())
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test('a late queued does not pull a working pin back', () => {
|
|
92
|
+
expect(play(working(), status({ status: 'queued' })).state).toBe('working')
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
describe('commits while it works', () => {
|
|
97
|
+
test('are ignored before the turn starts and after it ends', () => {
|
|
98
|
+
const before = play(fresh(), commit(['window_1']))
|
|
99
|
+
const queued = play(fresh(), status({ status: 'queued' }), commit(['window_1']))
|
|
100
|
+
const after = play(
|
|
101
|
+
working(),
|
|
102
|
+
status({ status: 'ended', outcome: 'completed', asked: false, wroteScene: false }),
|
|
103
|
+
commit(['window_1']),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
expect(before.landed).toBe(0)
|
|
107
|
+
expect(queued.landed).toBe(0)
|
|
108
|
+
expect(after.landed).toBe(0)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
test('one that touches a target is a step landing: a pop, and the target changed', () => {
|
|
112
|
+
const pin = play(working(), commit(['window_1']))
|
|
113
|
+
|
|
114
|
+
expect(pin.landed).toBe(1)
|
|
115
|
+
expect(pin.changedTargetIds).toEqual(['window_1'])
|
|
116
|
+
expect(pin.alsoChangedIds).toEqual([])
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
test('each landing step counts once, the ids once', () => {
|
|
120
|
+
const pin = play(working(), commit(['window_1']), commit(['window_1']), commit(['window_1']))
|
|
121
|
+
|
|
122
|
+
expect(pin.landed).toBe(3)
|
|
123
|
+
expect(pin.changedTargetIds).toEqual(['window_1'])
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
test('a node inside a target’s subtree is the target changing', () => {
|
|
127
|
+
const inside = (id: string) => id === 'glass_1' || id === 'frame_1'
|
|
128
|
+
const pin = play(working(), commit(['glass_1', 'frame_1'], inside))
|
|
129
|
+
|
|
130
|
+
expect(pin.landed).toBe(1)
|
|
131
|
+
expect(pin.changedTargetIds).toEqual(['glass_1', 'frame_1'])
|
|
132
|
+
expect(pin.alsoChangedIds).toEqual([])
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test('what is not the target is "also changed", and is no landing', () => {
|
|
136
|
+
const pin = play(working(), commit(['wall_9', 'item_3']))
|
|
137
|
+
|
|
138
|
+
expect(pin.landed).toBe(0)
|
|
139
|
+
expect(pin.changedTargetIds).toEqual([])
|
|
140
|
+
expect(pin.alsoChangedIds).toEqual(['wall_9', 'item_3'])
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
test('a mixed commit does both, once each', () => {
|
|
144
|
+
const pin = play(working(), commit(['window_1', 'wall_9']), commit(['wall_9']))
|
|
145
|
+
|
|
146
|
+
expect(pin.landed).toBe(1)
|
|
147
|
+
expect(pin.changedTargetIds).toEqual(['window_1'])
|
|
148
|
+
expect(pin.alsoChangedIds).toEqual(['wall_9'])
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
test('a member moving into scope stops being an also-changed element', () => {
|
|
152
|
+
const pin = play(
|
|
153
|
+
working(),
|
|
154
|
+
commit(['item_3']),
|
|
155
|
+
commit(['item_3'], () => true),
|
|
156
|
+
commit(['item_3']),
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
expect(pin.changedTargetIds).toEqual(['item_3'])
|
|
160
|
+
expect(pin.alsoChangedIds).toEqual([])
|
|
161
|
+
expect(pin.landed).toBe(2)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
test('an empty commit is nothing', () => {
|
|
165
|
+
expect(play(working(), commit([]))).toEqual(working())
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
describe('a question that waits', () => {
|
|
170
|
+
test('asking is needs-input with the question, and the turn is not over', () => {
|
|
171
|
+
const pin = play(working(), status({ status: 'asking', question: 'Which window?' }))
|
|
172
|
+
|
|
173
|
+
expect(pin.state).toBe('needs-input')
|
|
174
|
+
expect(pin.question).toBe('Which window?')
|
|
175
|
+
expect(pin.ended).toBe(false)
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
test('the answer resumes the turn: working again, the question cleared', () => {
|
|
179
|
+
const pin = play(
|
|
180
|
+
working(),
|
|
181
|
+
status({ status: 'asking', question: 'Which window?' }),
|
|
182
|
+
status({ status: 'working', sentence: 'Widening the north window' }),
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
expect(pin.state).toBe('working')
|
|
186
|
+
expect(pin.question).toBeUndefined()
|
|
187
|
+
expect(pin.sentence).toBe('Widening the north window')
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
test('commits after the answer count, and a turn that ends still asking needs them', () => {
|
|
191
|
+
const resumed = play(
|
|
192
|
+
working(),
|
|
193
|
+
status({ status: 'asking', question: 'Which?' }),
|
|
194
|
+
status({ status: 'working' }),
|
|
195
|
+
commit(['window_1']),
|
|
196
|
+
)
|
|
197
|
+
expect(resumed.landed).toBe(1)
|
|
198
|
+
|
|
199
|
+
const abandoned = play(
|
|
200
|
+
working(),
|
|
201
|
+
status({ status: 'asking', question: 'Which?' }),
|
|
202
|
+
status({ status: 'ended', outcome: 'cancelled', asked: true, wroteScene: false }),
|
|
203
|
+
)
|
|
204
|
+
expect(abandoned.state).toBe('needs-input')
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
test('commits while it waits are not the turn writing', () => {
|
|
208
|
+
const pin = play(
|
|
209
|
+
working(),
|
|
210
|
+
status({ status: 'asking', question: 'Which?' }),
|
|
211
|
+
commit(['window_1']),
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
expect(pin.landed).toBe(0)
|
|
215
|
+
})
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
describe('the turn ends', () => {
|
|
219
|
+
const ended = (more: Record<string, unknown>) =>
|
|
220
|
+
status({ status: 'ended', outcome: 'completed', asked: false, wroteScene: true, ...more })
|
|
221
|
+
|
|
222
|
+
test('completed with a target changed is done', () => {
|
|
223
|
+
const pin = play(working(), commit(['window_1']), ended({}))
|
|
224
|
+
|
|
225
|
+
expect(pin.state).toBe('done')
|
|
226
|
+
expect(pin.ended).toBe(true)
|
|
227
|
+
expect(pin.sentence).toBeUndefined()
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
test('a done pin keeps the last live sentence as its card title', () => {
|
|
231
|
+
const pin = play(
|
|
232
|
+
working(),
|
|
233
|
+
status({ status: 'working', sentence: 'Widened the window to 1.80 m' }),
|
|
234
|
+
commit(['window_1']),
|
|
235
|
+
ended({}),
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
expect(pin.state).toBe('done')
|
|
239
|
+
expect(cardTitle(pin)).toBe('Widened the window to 1.80 m')
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
test('completed on a question to the person needs them', () => {
|
|
243
|
+
const pin = play(working(), ended({ asked: true, answer: 'Which window?' }))
|
|
244
|
+
|
|
245
|
+
expect(pin.state).toBe('needs-input')
|
|
246
|
+
expect(pin.answer).toBe('Which window?')
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
test('completed with no scene write and a reply is an answer', () => {
|
|
250
|
+
const pin = play(working(), ended({ wroteScene: false, answer: 'It is 14.2 m².' }))
|
|
251
|
+
|
|
252
|
+
expect(pin.state).toBe('answered')
|
|
253
|
+
expect(pin.answer).toBe('It is 14.2 m².')
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
test('completed that changed only other elements is done, with Undo and an honest count', () => {
|
|
257
|
+
const pin = play(working(), commit(['wall_9']), ended({}))
|
|
258
|
+
|
|
259
|
+
expect(pin.state).toBe('done')
|
|
260
|
+
expect(pin.landed).toBe(0)
|
|
261
|
+
expect(pin.alsoChangedIds).toEqual(['wall_9'])
|
|
262
|
+
expect(cardTitle(pin)).toBe('Pascal changed 1 element')
|
|
263
|
+
expect(play(pin, { type: 'undo' }).state).toBe('undone')
|
|
264
|
+
expect(play(pin, { type: 'undo' }, { type: 'redo' }).state).toBe('done')
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
test('a real material or collection commit is done even without node changes', () => {
|
|
268
|
+
const pin = play(working(), { type: 'commit', changedNodeIds: [], committed: true }, ended({}))
|
|
269
|
+
|
|
270
|
+
expect(pin.state).toBe('done')
|
|
271
|
+
expect(pin.landed).toBe(0)
|
|
272
|
+
expect(pin.changedTargetIds).toEqual([])
|
|
273
|
+
expect(pin.alsoChangedIds).toEqual([])
|
|
274
|
+
expect(cardTitle(pin)).toBe('Pascal updated the scene')
|
|
275
|
+
expect(play(pin, { type: 'undo' }).state).toBe('undone')
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
test('calling a writing tool without a durable commit still means no change', () => {
|
|
279
|
+
expect(play(working(), ended({})).state).toBe('no-change')
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
test('completed with nothing to say and nothing written is no change', () => {
|
|
283
|
+
expect(play(working(), ended({ wroteScene: false })).state).toBe('no-change')
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
test('a changed target beats a question', () => {
|
|
287
|
+
const pin = play(working(), commit(['window_1']), ended({ asked: true }))
|
|
288
|
+
|
|
289
|
+
expect(pin.state).toBe('done')
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
test('errored is an error, with the words', () => {
|
|
293
|
+
const pin = play(
|
|
294
|
+
working(),
|
|
295
|
+
status({
|
|
296
|
+
status: 'ended',
|
|
297
|
+
outcome: 'errored',
|
|
298
|
+
asked: false,
|
|
299
|
+
wroteScene: false,
|
|
300
|
+
error: 'The window is wider than the wall.',
|
|
301
|
+
}),
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
expect(pin.state).toBe('error')
|
|
305
|
+
expect(pin.error).toBe('The window is wider than the wall.')
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
test('cancelled is done if something landed, no change if not', () => {
|
|
309
|
+
const cancelled = status({
|
|
310
|
+
status: 'ended',
|
|
311
|
+
outcome: 'cancelled',
|
|
312
|
+
asked: false,
|
|
313
|
+
wroteScene: true,
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
expect(play(working(), commit(['window_1']), cancelled).state).toBe('done')
|
|
317
|
+
expect(play(working(), commit(['wall_9']), cancelled).state).toBe('done')
|
|
318
|
+
expect(play(working(), cancelled).state).toBe('no-change')
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
test('can end from sent or queued, a turn that never reported working', () => {
|
|
322
|
+
expect(play(fresh(), ended({ wroteScene: false })).state).toBe('no-change')
|
|
323
|
+
expect(play(fresh(), status({ status: 'queued' }), ended({ wroteScene: false })).state).toBe(
|
|
324
|
+
'no-change',
|
|
325
|
+
)
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
test('ends once: a second end, or a late working, changes nothing', () => {
|
|
329
|
+
const done = play(working(), commit(['window_1']), ended({}))
|
|
330
|
+
|
|
331
|
+
expect(play(done, ended({ asked: true }))).toEqual(done)
|
|
332
|
+
expect(play(done, status({ status: 'working', sentence: 'late' }))).toEqual(done)
|
|
333
|
+
expect(play(done, status({ status: 'queued' }))).toEqual(done)
|
|
334
|
+
})
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
describe('Keep and Undo', () => {
|
|
338
|
+
const done = () =>
|
|
339
|
+
play(
|
|
340
|
+
working(),
|
|
341
|
+
commit(['window_1']),
|
|
342
|
+
status({ status: 'ended', outcome: 'completed', asked: false, wroteScene: true }),
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
test('Undo turns a done pin to undone', () => {
|
|
346
|
+
const pin = play(done(), { type: 'undo' })
|
|
347
|
+
|
|
348
|
+
expect(pin.state).toBe('undone')
|
|
349
|
+
expect(pin.undone).toBe(true)
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
test('Undo means nothing from any other state', () => {
|
|
353
|
+
for (const pin of [
|
|
354
|
+
fresh(),
|
|
355
|
+
working(),
|
|
356
|
+
play(
|
|
357
|
+
working(),
|
|
358
|
+
status({ status: 'ended', outcome: 'errored', asked: false, wroteScene: false }),
|
|
359
|
+
),
|
|
360
|
+
])
|
|
361
|
+
expect(play(pin, { type: 'undo' })).toEqual(pin)
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
test('an undo that failed puts the pin back to done', () => {
|
|
365
|
+
const pin = play(done(), { type: 'undo' }, { type: 'undone', ok: false })
|
|
366
|
+
|
|
367
|
+
expect(pin.state).toBe('done')
|
|
368
|
+
expect(pin.undone).toBe(false)
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
test('an undo that worked stays undone, and a stray undone changes nothing else', () => {
|
|
372
|
+
const undone = play(done(), { type: 'undo' }, { type: 'undone', ok: true })
|
|
373
|
+
|
|
374
|
+
expect(undone.state).toBe('undone')
|
|
375
|
+
expect(play(done(), { type: 'undone', ok: false })).toEqual(done())
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
test('Keep marks it kept and leaves the state', () => {
|
|
379
|
+
const pin = play(done(), { type: 'keep' })
|
|
380
|
+
|
|
381
|
+
expect(pin.kept).toBe(true)
|
|
382
|
+
expect(pin.state).toBe('done')
|
|
383
|
+
expect(play(pin, { type: 'keep' })).toEqual(pin)
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
test('commits after Undo are ignored', () => {
|
|
387
|
+
const pin = play(done(), { type: 'undo' }, commit(['window_1']))
|
|
388
|
+
|
|
389
|
+
expect(pin.landed).toBe(1)
|
|
390
|
+
})
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
describe('reducePin is pure', () => {
|
|
394
|
+
test('returns a new pin and leaves the old one alone', () => {
|
|
395
|
+
const pin = fresh()
|
|
396
|
+
const next = play(pin, status({ status: 'working' }), commit(['window_1']))
|
|
397
|
+
|
|
398
|
+
expect(next).not.toBe(pin)
|
|
399
|
+
expect(pin).toEqual(fresh())
|
|
400
|
+
})
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
describe('what the card says', () => {
|
|
404
|
+
test('the title the caller built, else the fallback', () => {
|
|
405
|
+
const titled = { ...play(working(), commit(['window_1'])), title: 'Widened to 1.20 m' }
|
|
406
|
+
|
|
407
|
+
expect(cardTitle(titled, 'Done')).toBe('Widened to 1.20 m')
|
|
408
|
+
expect(cardTitle(fresh())).toBe('Done')
|
|
409
|
+
expect(cardTitle(fresh(), 'Pascal didn’t change this window.')).toBe(
|
|
410
|
+
'Pascal didn’t change this window.',
|
|
411
|
+
)
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
test('the error’s words for an error, the answer’s first two lines for an answer', () => {
|
|
415
|
+
const error = {
|
|
416
|
+
...fresh(),
|
|
417
|
+
state: 'error' as const,
|
|
418
|
+
error: 'The window is wider than the wall.',
|
|
419
|
+
}
|
|
420
|
+
const answered = {
|
|
421
|
+
...fresh(),
|
|
422
|
+
state: 'answered' as const,
|
|
423
|
+
answer: 'It is 14.2 m².\nThat is about 150 square feet.\nA third line.',
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
expect(cardTitle(error, 'Done')).toBe('The window is wider than the wall.')
|
|
427
|
+
expect(cardTitle(answered, 'Done')).toBe('It is 14.2 m².\nThat is about 150 square feet.')
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
test('undone says so', () => {
|
|
431
|
+
expect(cardTitle({ ...fresh(), state: 'undone' as const }, 'Done')).toBe('Undone')
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
test('the "also changed" line, singular and plural', () => {
|
|
435
|
+
expect(alsoChangedLine(fresh())).toBeNull()
|
|
436
|
+
expect(alsoChangedLine({ ...fresh(), alsoChangedIds: ['a'] })).toBe(
|
|
437
|
+
'Also changed 1 other element',
|
|
438
|
+
)
|
|
439
|
+
expect(alsoChangedLine({ ...fresh(), alsoChangedIds: ['a', 'b'] })).toBe(
|
|
440
|
+
'Also changed 2 other elements',
|
|
441
|
+
)
|
|
442
|
+
})
|
|
443
|
+
})
|
|
444
|
+
|
|
445
|
+
describe('redo', () => {
|
|
446
|
+
const done = () =>
|
|
447
|
+
play(
|
|
448
|
+
working(),
|
|
449
|
+
commit(['window_1']),
|
|
450
|
+
status({ status: 'ended', outcome: 'completed', asked: false, wroteScene: true }),
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
test('brings an undone pin back to done', () => {
|
|
454
|
+
const pin = play(done(), { type: 'undo' }, { type: 'redo' })
|
|
455
|
+
|
|
456
|
+
expect(pin.state).toBe('done')
|
|
457
|
+
expect(pin.undone).toBe(false)
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
test('is nothing for a pin that was not undone', () => {
|
|
461
|
+
expect(play(done(), { type: 'redo' })).toEqual(done())
|
|
462
|
+
})
|
|
463
|
+
})
|
|
464
|
+
|
|
465
|
+
describe('a card carried out from under the pointer', () => {
|
|
466
|
+
// Boundary events come before the move that caused them, so the leave of a person who moves
|
|
467
|
+
// after a long pause arrives with the old last position: only where the pointer is, against where
|
|
468
|
+
// it was last seen, tells a pointer that walked out from a card that was taken from it.
|
|
469
|
+
const last = { x: 640, y: 380 }
|
|
470
|
+
|
|
471
|
+
test('is a mouse that did not move: the camera flew, the person did not leave', () => {
|
|
472
|
+
expect(cardCarriedFromPointer({ pointerType: 'mouse', at: { x: 640, y: 380 }, last })).toBe(
|
|
473
|
+
true,
|
|
474
|
+
)
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
test('is not a mouse that moved out, however long it had been still', () => {
|
|
478
|
+
expect(cardCarriedFromPointer({ pointerType: 'mouse', at: { x: 700, y: 560 }, last })).toBe(
|
|
479
|
+
false,
|
|
480
|
+
)
|
|
481
|
+
})
|
|
482
|
+
|
|
483
|
+
test('is not a mouse we never saw move', () => {
|
|
484
|
+
expect(
|
|
485
|
+
cardCarriedFromPointer({ pointerType: 'mouse', at: { x: 640, y: 380 }, last: null }),
|
|
486
|
+
).toBe(false)
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
test('is never a finger: lifting it leaves without a move', () => {
|
|
490
|
+
expect(cardCarriedFromPointer({ pointerType: 'touch', at: { x: 640, y: 380 }, last })).toBe(
|
|
491
|
+
false,
|
|
492
|
+
)
|
|
493
|
+
})
|
|
494
|
+
})
|