Y_test.res

  1// test/Y_test.res
  2open VitestHelpers
  3
  4// Record types for unmarshalling tests
  5type route = {from: string, to: string, distance: int}
  6
  7type tripInfo = {
  8  origin: string,
  9  destination: string,
 10  distanceKm: int,
 11}
 12
 13// Helper to insert text via raw JS (bindings are read-only)
 14let insertText = (ytext: Y.Text.t, index: int, text: string): unit => {
 15  let _ = %raw(`(yt, idx, str) => { yt.insert(idx, str); }`)(ytext, index, text)
 16}
 17
 18// Helper to push to Y.Array via raw JS
 19let pushArray = (yarray: Y.Array.t, values: array<'a>): unit => {
 20  let _ = %raw(`(ya, vals) => { ya.push(vals); }`)(yarray, values)
 21}
 22
 23// Helper to set on Y.Map via raw JS
 24let setMap = (ymap: Y.Map.t, key: string, value: 'a): unit => {
 25  let _ = %raw(`(ym, k, v) => { ym.set(k, v); }`)(ymap, key, value)
 26}
 27
 28describe("Y.Doc", () => {
 29  testAsync("make creates a doc instance", async t => {
 30    let doc = Y.Doc.make()
 31    t->expect(doc)->not_->toBeNull
 32  })
 33
 34  testAsync("onAfterTransaction fires after a transaction", async t => {
 35    let doc = Y.Doc.make()
 36    let events = ref([])
 37    doc->Y.Doc.onAfterTransaction(event => {
 38      events := events.contents->Array.concat([event])
 39    })
 40    // Modify content to trigger a transaction
 41    let ytext = doc->Y.Text.get("trigger")
 42    insertText(ytext, 0, "test")
 43    t->expect(events.contents->Array.length)->toBeGreaterThan(0)
 44  })
 45
 46  testAsync("onAfterTransaction receives doc and transaction", async t => {
 47    let doc = Y.Doc.make()
 48    let receivedDoc = ref(None)
 49    doc->Y.Doc.onAfterTransaction(event => {
 50      receivedDoc := Some(event.doc)
 51    })
 52    let _ = doc->Y.Text.get("trigger")
 53    t->expect(receivedDoc.contents)->not_->toBeNull
 54  })
 55})
 56
 57describe("Y.Text", () => {
 58  testAsync("get returns a Y.Text instance", async t => {
 59    let doc = Y.Doc.make()
 60    let ytext = doc->Y.Text.get("mytext")
 61    t->expect(ytext)->not_->toBeNull
 62  })
 63
 64  testAsync("toString returns string content", async t => {
 65    let doc = Y.Doc.make()
 66    let ytext = doc->Y.Text.get("mytext")
 67    insertText(ytext, 0, "hello world")
 68    let result = ytext->Y.Text.toString
 69    t->expect(result)->toEqual("hello world")
 70  })
 71
 72  testAsync("multiple text types in one doc", async t => {
 73    let doc = Y.Doc.make()
 74    let title = doc->Y.Text.get("title")
 75    let body = doc->Y.Text.get("body")
 76    insertText(title, 0, "Trip Plan")
 77    insertText(body, 0, "Details here...")
 78    t->expect(title->Y.Text.toString)->toEqual("Trip Plan")
 79    t->expect(body->Y.Text.toString)->toEqual("Details here...")
 80  })
 81})
 82
 83describe("Y.Array", () => {
 84  testAsync("get returns a Y.Array instance", async t => {
 85    let doc = Y.Doc.make()
 86    let yarray = doc->Y.Array.get("myarray")
 87    t->expect(yarray)->not_->toBeNull
 88  })
 89
 90  testAsync("toJSON returns array of JSON values", async t => {
 91    let doc = Y.Doc.make()
 92    let yarray = doc->Y.Array.get("myarray")
 93    pushArray(yarray, [1, 2, 3])
 94    let result = yarray->Y.Array.toJSON
 95    t->expect(result->Array.length)->toEqual(3)
 96    t->expect(result->Array.get(0)->Option.flatMap(JSON.Decode.float))->toEqual(Some(1.0))
 97    t->expect(result->Array.get(1)->Option.flatMap(JSON.Decode.float))->toEqual(Some(2.0))
 98    t->expect(result->Array.get(2)->Option.flatMap(JSON.Decode.float))->toEqual(Some(3.0))
 99  })
100
101  testAsync("toJSON with objects can be unmarshalled to records", async t => {
102    let doc = Y.Doc.make()
103    let yarray = doc->Y.Array.get("routes")
104
105    // Push JS objects into the array via raw JS
106    let pushRouteFn: Y.Array.t => unit = %raw(`function(ya) {
107      ya.push([{from: 'A', to: 'B', distance: 120}, {from: 'B', to: 'C', distance: 85}]);
108    }`)
109    pushRouteFn(yarray)
110
111    let routes: array<route> = yarray->Y.Array.toJSON->Array.map(json => {
112      switch json->JSON.Decode.object {
113      | Some(dict) => {
114          let from = dict->Dict.get("from")->Option.flatMap(JSON.Decode.string)->Option.getOr("")
115          let to = dict->Dict.get("to")->Option.flatMap(JSON.Decode.string)->Option.getOr("")
116          let distance = dict->Dict.get("distance")->Option.flatMap(JSON.Decode.float)->Option.map(Float.toInt)->Option.getOr(0)
117          {from: from, to: to, distance: distance}
118        }
119      | None => {from: "", to: "", distance: 0}
120      }
121    })
122
123    t->expect(routes->Array.length)->toEqual(2)
124    t->expect(routes->Array.get(0))->toEqual(Some({from: "A", to: "B", distance: 120}))
125    t->expect(routes->Array.get(1))->toEqual(Some({from: "B", to: "C", distance: 85}))
126  })
127})
128
129describe("Y.Map", () => {
130  testAsync("get returns a Y.Map instance", async t => {
131    let doc = Y.Doc.make()
132    let ymap = doc->Y.Map.get("mymap")
133    t->expect(ymap)->not_->toBeNull
134  })
135
136  testAsync("toJSON returns JSON object", async t => {
137    let doc = Y.Doc.make()
138    let ymap = doc->Y.Map.get("mymap")
139    setMap(ymap, "key", "value")
140    let result = ymap->Y.Map.toJSON
141    switch result->JSON.Decode.object {
142    | Some(dict) =>
143      switch dict->Dict.get("key") {
144      | Some(v) =>
145        switch v->JSON.Decode.string {
146        | Some("value") => t->expect(true)->toEqual(true)
147        | _ => t->expect("decode string failed")->toEqual("")
148        }
149      | None => t->expect("key missing")->toEqual("")
150      }
151    | None => t->expect("not object")->toEqual("")
152    }
153  })
154
155  testAsync("toJSON can be unmarshalled to a record", async t => {
156    let doc = Y.Doc.make()
157    let ymap = doc->Y.Map.get("tripInfo")
158
159    // Set nested values via raw JS
160    let setTripInfoFn: Y.Map.t => unit = %raw(`function(ym) {
161      ym.set('origin', 'Kathmandu');
162      ym.set('destination', 'Pokhara');
163      ym.set('distanceKm', 200);
164    }`)
165    setTripInfoFn(ymap)
166
167    let info = switch ymap->Y.Map.toJSON->JSON.Decode.object {
168    | Some(dict) => {
169        origin: dict->Dict.get("origin")->Option.flatMap(JSON.Decode.string)->Option.getOr(""),
170        destination: dict->Dict.get("destination")->Option.flatMap(JSON.Decode.string)->Option.getOr(""),
171        distanceKm: dict->Dict.get("distanceKm")->Option.flatMap(JSON.Decode.float)->Option.map(Float.toInt)->Option.getOr(0),
172      }
173    | None => {origin: "", destination: "", distanceKm: 0}
174    }
175
176    t->expect(info.origin)->toEqual("Kathmandu")
177    t->expect(info.destination)->toEqual("Pokhara")
178    t->expect(info.distanceKm)->toEqual(200)
179  })
180})
181
182describe("Y.Doc with multiple shared types", () => {
183  testAsync("doc can hold Text, Array, and Map simultaneously", async t => {
184    let doc = Y.Doc.make()
185
186    let ytext = doc->Y.Text.get("notes")
187    let yarray = doc->Y.Array.get("waypoints")
188    let ymap = doc->Y.Map.get("metadata")
189
190    insertText(ytext, 0, "Day 1: Kathmandu to Pokhara")
191    pushArray(yarray, ["Kathmandu", "Pokhara"])
192    setMap(ymap, "author", "Rider One")
193
194    t->expect(ytext->Y.Text.toString)->toEqual("Day 1: Kathmandu to Pokhara")
195    t->expect(yarray->Y.Array.toJSON->Array.length)->toEqual(2)
196
197    switch ymap->Y.Map.toJSON->JSON.Decode.object {
198    | Some(dict) =>
199      switch dict->Dict.get("author")->Option.flatMap(JSON.Decode.string) {
200      | Some("Rider One") => t->expect(true)->toEqual(true)
201      | _ => t->expect("author decode failed")->toEqual("")
202      }
203    | None => t->expect("not object")->toEqual("")
204    }
205  })
206
207  testAsync("onAfterTransaction fires for any shared type change", async t => {
208    let doc = Y.Doc.make()
209    let changeCount = ref(0)
210    doc->Y.Doc.onAfterTransaction(_event => {
211      changeCount := changeCount.contents + 1
212    })
213
214    let _ = doc->Y.Text.get("text1")
215    let _ = doc->Y.Array.get("array1")
216    let _ = doc->Y.Map.get("map1")
217
218    t->expect(changeCount.contents)->toBeGreaterThan(0)
219  })
220})