State_test.res
1// test/State_test.res
2open VitestHelpers
3
4// Helper to push waypoints to Y.Array via raw JS
5let pushWaypoint = (yarray: Y.Array.t, waypoint: JSON.t): unit => {
6 let _ = %raw(`(ya, wp) => { ya.push([wp]); }`)(yarray, waypoint)
7}
8
9// Helper to create a waypoint JSON object
10let makeWaypointJSON = (~id, ~lon, ~lat, ~label, ~gid, ~country, ~from=?, ~cons=?, ~nonroutable=?, ()) => {
11 let dict = Dict.fromArray([
12 ("type", JSON.Encode.string("waypoint")),
13 ("id", JSON.Encode.string(id)),
14 ("point", JSON.Encode.array([JSON.Encode.float(lon), JSON.Encode.float(lat)])),
15 ("label", JSON.Encode.string(label)),
16 ("gid", JSON.Encode.string(gid)),
17 ("country", JSON.Encode.string(country)),
18 ])
19
20 // Add optional from field (now map[string]route format)
21 switch from {
22 | Some(fromDict) => dict->Dict.set("from", JSON.Encode.object(fromDict))
23 | None => ()
24 }
25
26 // Add optional cons field (now array<string>)
27 switch cons {
28 | Some(consArr) =>
29 dict->Dict.set("cons", JSON.Encode.array(consArr->Array.map(JSON.Encode.string)))
30 | None => ()
31 }
32
33 // Add nonroutable field (default to false)
34 dict->Dict.set("nonroutable", JSON.Encode.bool(nonroutable->Option.getOr(false)))
35
36 dict->JSON.Encode.object
37}
38
39// Helper to set up observer for a document (simulates Editor.res behavior)
40let setupObserver = (docID: string, ydoc: Y.Doc.t): Preact.Signal.t<array<State.waypoint>> => {
41 let waypointsArray = ydoc->Y.Array.get("waypoints")
42 let waypointsSignal = State.getOrCreateWaypointsSignal(docID)
43
44 waypointsArray->Y.Array.observe(_event => {
45 let jsonArray = waypointsArray->Y.Array.toJSON
46 let waypoints = Waypoint.fromYjsArray(jsonArray)
47 waypointsSignal->Preact.Signal.set(waypoints)
48 })
49
50 waypointsSignal
51}
52
53describe("State.waypoints Map", () => {
54 testAsync("signal starts empty for new docID", async t => {
55 let signal = State.getOrCreateWaypointsSignal("doc1")
56 t->expect(signal->Preact.Signal.get->Array.length)->toEqual(0)
57 })
58
59 testAsync("getOrCreateWaypointsSignal returns same signal for same docID", async t => {
60 let signal1 = State.getOrCreateWaypointsSignal("doc2")
61 let signal2 = State.getOrCreateWaypointsSignal("doc2")
62 t->expect(signal1)->toBe(signal2)
63 })
64
65 testAsync("getOrCreateWaypointsSignal returns different signals for different docIDs", async t => {
66 let signal1 = State.getOrCreateWaypointsSignal("doc3")
67 let signal2 = State.getOrCreateWaypointsSignal("doc4")
68 t->expect(signal1)->not_->toBe(signal2)
69 })
70
71 testAsync("observer updates signal when waypoints are added", async t => {
72 let doc = Y.Doc.make()
73 let waypointsSignal = setupObserver("doc5", doc)
74 let waypointsArray = doc->Y.Array.get("waypoints")
75
76 // Add a waypoint
77 let wp1 = makeWaypointJSON(
78 ~id="wp1",
79 ~lon=85.324,
80 ~lat=27.717,
81 ~label="Kathmandu",
82 ~gid="kathmandu_gid",
83 ~country="NP",
84 (),
85 )
86 pushWaypoint(waypointsArray, wp1)
87
88 // Verify signal was updated
89 let waypoints = waypointsSignal->Preact.Signal.get
90 t->expect(waypoints->Array.length)->toEqual(1)
91
92 switch waypoints->Array.get(0) {
93 | Some(wp) => {
94 t->expect(wp.id)->toEqual("wp1")
95 t->expect(wp.point)->toEqual((85.324, 27.717))
96 t->expect(wp.label)->toEqual("Kathmandu")
97 t->expect(wp.gid)->toEqual("kathmandu_gid")
98 t->expect(wp.country)->toEqual("NP")
99 }
100 | None => t->expect("waypoint not found")->toEqual("")
101 }
102 })
103
104 testAsync("multiple documents have independent waypoints", async t => {
105 let docA = Y.Doc.make()
106 let docB = Y.Doc.make()
107
108 let signalA = setupObserver("docA", docA)
109 let signalB = setupObserver("docB", docB)
110
111 let arrayA = docA->Y.Array.get("waypoints")
112 let arrayB = docB->Y.Array.get("waypoints")
113
114 // Add waypoint to docA only
115 let wpA = makeWaypointJSON(
116 ~id="wpA",
117 ~lon=85.0,
118 ~lat=27.0,
119 ~label="Location A",
120 ~gid="gidA",
121 ~country="NP",
122 (),
123 )
124 pushWaypoint(arrayA, wpA)
125
126 // Verify docA has 1 waypoint, docB has 0
127 t->expect(signalA->Preact.Signal.get->Array.length)->toEqual(1)
128 t->expect(signalB->Preact.Signal.get->Array.length)->toEqual(0)
129
130 // Add waypoint to docB
131 let wpB = makeWaypointJSON(
132 ~id="wpB",
133 ~lon=83.0,
134 ~lat=28.0,
135 ~label="Location B",
136 ~gid="gidB",
137 ~country="NP",
138 (),
139 )
140 pushWaypoint(arrayB, wpB)
141
142 // Verify both docs have 1 waypoint each
143 t->expect(signalA->Preact.Signal.get->Array.length)->toEqual(1)
144 t->expect(signalB->Preact.Signal.get->Array.length)->toEqual(1)
145 })
146
147 testAsync("deserializes constraints correctly", async t => {
148 let doc = Y.Doc.make()
149 let waypointsSignal = setupObserver("doc6", doc)
150 let waypointsArray = doc->Y.Array.get("waypoints")
151
152 // Add waypoint with constraints (now array<string>)
153 let wp = makeWaypointJSON(
154 ~id="wp2",
155 ~lon=83.0,
156 ~lat=28.0,
157 ~label="Pokhara",
158 ~gid="pokhara_gid",
159 ~country="NP",
160 ~cons=["adv:high", "max-speed:80"],
161 (),
162 )
163 pushWaypoint(waypointsArray, wp)
164
165 let waypoints = waypointsSignal->Preact.Signal.get
166 t->expect(waypoints->Array.length)->toEqual(1)
167
168 switch waypoints->Array.get(0) {
169 | Some(wp) => {
170 // cons is now array<string>, not option<array<Constraint.t>>
171 t->expect(wp.cons->Array.length)->toEqual(2)
172 switch wp.cons->Array.get(0) {
173 | Some(c1) => {
174 t->expect(c1)->toEqual("adv:high")
175 }
176 | None => ()
177 }
178 }
179 | None => t->expect("waypoint not found")->toEqual("")
180 }
181 })
182})