document_test.go
1package ygo_test
2
3import (
4 "encoding/json"
5 "git.kilimanjaro.io/ygo"
6 "testing"
7)
8
9func TestNewDoc(t *testing.T) {
10 doc, err := ygo.NewDoc()
11 if err != nil {
12 t.Fatalf("failed to create doc: %v", err)
13 }
14 defer doc.Destroy()
15
16 if doc.ClientID() == 0 {
17 t.Error("expected non-zero client ID")
18 }
19
20 guid := doc.GUID()
21 if guid == "" {
22 t.Error("expected non-empty GUID")
23 }
24}
25
26func TestNewDocWithOptions(t *testing.T) {
27 opts := ygo.DocOptions{
28 ClientID: 123,
29 GUID: "test-guid",
30 Encoding: ygo.OffsetUTF16,
31 SkipGC: true,
32 }
33 doc, err := ygo.NewDocWithOptions(opts)
34 if err != nil {
35 t.Fatalf("failed to create doc with options: %v", err)
36 }
37 defer doc.Destroy()
38
39 if doc.ClientID() != 123 {
40 t.Errorf("expected client ID 123, got %d", doc.ClientID())
41 }
42
43 if doc.GUID() != "test-guid" {
44 t.Errorf("expected GUID 'test-guid', got %s", doc.GUID())
45 }
46}
47
48func TestDocClone(t *testing.T) {
49 doc, err := ygo.NewDoc()
50 if err != nil {
51 t.Fatalf("failed to create doc: %v", err)
52 }
53 defer doc.Destroy()
54
55 clone, err := doc.Clone()
56 if err != nil {
57 t.Fatalf("failed to clone doc: %v", err)
58 }
59 defer clone.Destroy()
60
61 if clone.ClientID() != doc.ClientID() {
62 t.Error("clone should have same client ID")
63 }
64}
65
66func TestDocDeepClone(t *testing.T) {
67 doc, err := ygo.NewDoc()
68 if err != nil {
69 t.Fatalf("failed to create doc: %v", err)
70 }
71 defer doc.Destroy()
72
73 txt, err := doc.GetText("content")
74 if err != nil {
75 t.Fatalf("failed to get text: %v", err)
76 }
77 defer txt.Destroy()
78
79 err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
80 return txt.Push(txn, "Hello")
81 })
82 if err != nil {
83 t.Fatalf("transaction failed: %v", err)
84 }
85
86 clone, err := doc.DeepClone()
87 if err != nil {
88 t.Fatalf("DeepClone failed: %v", err)
89 }
90 defer clone.Destroy()
91
92 // Verify clone has same content - get text BEFORE transaction
93 txt2, err := clone.GetText("content")
94 if err != nil {
95 t.Fatalf("failed to get text from clone: %v", err)
96 }
97 defer txt2.Destroy()
98
99 var cloneContent string
100 err = clone.WithReadTransaction(func(txn *ygo.Transaction) error {
101 cloneContent, err = txt2.String(txn)
102 return err
103 })
104 if err != nil {
105 t.Fatalf("failed to read clone: %v", err)
106 }
107 if cloneContent != "Hello" {
108 t.Errorf("expected 'Hello', got '%s'", cloneContent)
109 }
110
111 // Verify independence - change original doesn't affect clone
112 err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
113 return txt.Push(txn, " World")
114 })
115 if err != nil {
116 t.Fatalf("failed to modify original: %v", err)
117 }
118
119 // Clone should still have old content
120 if cloneContent != "Hello" {
121 t.Error("clone was affected by change to original - not independent")
122 }
123}
124
125func TestDocDestroy(t *testing.T) {
126 doc, err := ygo.NewDoc()
127 if err != nil {
128 t.Fatalf("failed to create doc: %v", err)
129 }
130 doc.Destroy()
131
132 // Second destroy should be safe
133 doc.Destroy()
134}
135
136func TestMarshalUnmarshalRoundTrip(t *testing.T) {
137 // Create document with content
138 doc1, err := ygo.NewDoc()
139 if err != nil {
140 t.Fatalf("failed to create doc1: %v", err)
141 }
142 defer doc1.Destroy()
143
144 txt1, err := doc1.GetText("content")
145 if err != nil {
146 t.Fatalf("failed to get text: %v", err)
147 }
148 defer txt1.Destroy()
149
150 // Add some content
151 err = doc1.WithWriteTransaction(func(txn *ygo.Transaction) error {
152 txt1.Insert(txn, 0, "Hello World!")
153 return nil
154 })
155 if err != nil {
156 t.Fatalf("failed to insert text: %v", err)
157 }
158
159 // Marshal the document
160 data, err := doc1.MarshalBinary()
161 if err != nil {
162 t.Fatalf("failed to marshal: %v", err)
163 }
164 if len(data) == 0 {
165 t.Fatal("marshal returned empty data")
166 }
167
168 // Create new document and unmarshal
169 doc2, err := ygo.NewDoc()
170 if err != nil {
171 t.Fatalf("failed to create doc2: %v", err)
172 }
173 defer doc2.Destroy()
174
175 // IMPORTANT: Initialize shared types before unmarshaling
176 txt2, err := doc2.GetText("content")
177 if err != nil {
178 t.Fatalf("failed to get text2: %v", err)
179 }
180 defer txt2.Destroy()
181
182 // Unmarshal the data
183 if err := doc2.UnmarshalBinary(data); err != nil {
184 t.Fatalf("failed to unmarshal: %v", err)
185 }
186
187 // Verify content matches
188 var content string
189 err = doc2.WithReadTransaction(func(txn *ygo.Transaction) error {
190 var err error
191 content, err = txt2.String(txn)
192 return err
193 })
194 if err != nil {
195 t.Fatalf("failed to read content: %v", err)
196 }
197
198 if content != "Hello World!" {
199 t.Errorf("expected 'Hello World!', got '%s'", content)
200 }
201}
202
203func TestMarshalNilDocument(t *testing.T) {
204 var doc *ygo.Doc
205 data, err := doc.MarshalBinary()
206 if err == nil {
207 t.Error("expected error when marshaling nil document")
208 }
209 if data != nil {
210 t.Error("expected nil data when marshaling nil document")
211 }
212}
213
214func TestUnmarshalNilDocument(t *testing.T) {
215 var doc *ygo.Doc
216 err := doc.UnmarshalBinary([]byte{1, 2, 3})
217 if err == nil {
218 t.Error("expected error when unmarshaling to nil document")
219 }
220}
221
222func TestUnmarshalEmptyData(t *testing.T) {
223 doc, err := ygo.NewDoc()
224 if err != nil {
225 t.Fatalf("failed to create doc: %v", err)
226 }
227 defer doc.Destroy()
228
229 if err := doc.UnmarshalBinary([]byte{}); err != nil {
230 t.Fatalf("failed to unmarshal empty data: %v", err)
231 }
232 if err := doc.UnmarshalBinary(nil); err != nil {
233 t.Fatalf("failed to unmarshal nil data: %v", err)
234 }
235}
236
237func TestMarshalEmptyDocument(t *testing.T) {
238 doc, err := ygo.NewDoc()
239 if err != nil {
240 t.Fatalf("failed to create doc: %v", err)
241 }
242 defer doc.Destroy()
243
244 data, err := doc.MarshalBinary()
245 if err != nil {
246 t.Fatalf("failed to marshal empty doc: %v", err)
247 }
248 if len(data) == 0 {
249 t.Error("marshal of empty document should return some data (metadata)")
250 }
251}
252
253func TestDocumentMarshalJSONEmpty(t *testing.T) {
254 doc, err := ygo.NewDoc()
255 if err != nil {
256 t.Fatalf("failed to create doc: %v", err)
257 }
258 defer doc.Destroy()
259
260 jsonBytes, err := doc.MarshalJSON()
261 if err != nil {
262 t.Fatalf("marshal failed: %v", err)
263 }
264
265 if string(jsonBytes) != "{}" {
266 t.Errorf("expected {}, got %s", string(jsonBytes))
267 }
268}
269
270func TestDocumentMarshalJSONNonexistent(t *testing.T) {
271 doc, err := ygo.NewDoc()
272 if err != nil {
273 t.Fatalf("failed to create doc: %v", err)
274 }
275 defer doc.Destroy()
276
277 jsonBytes, err := doc.MarshalJSON(ygo.WithMap("nonexistent"))
278 if err != nil {
279 t.Fatalf("marshal should succeed for nonexistent roots (ygo creates them): %v", err)
280 }
281
282 var result map[string]interface{}
283 if err := json.Unmarshal(jsonBytes, &result); err != nil {
284 t.Fatalf("failed to unmarshal: %v", err)
285 }
286
287 if _, ok := result["nonexistent"]; !ok {
288 t.Error("expected nonexistent map to be created and included")
289 }
290}
291
292func TestDocumentMarshalJSONWithOptions(t *testing.T) {
293 doc, err := ygo.NewDoc()
294 if err != nil {
295 t.Fatalf("failed to create doc: %v", err)
296 }
297 defer doc.Destroy()
298
299 m, err := doc.GetMap("users")
300 if err != nil {
301 t.Fatalf("failed to get map: %v", err)
302 }
303 defer m.Destroy()
304
305 arr, err := doc.GetArray("items")
306 if err != nil {
307 t.Fatalf("failed to get array: %v", err)
308 }
309 defer arr.Destroy()
310
311 err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
312 m.Insert(txn, "name", ygo.String("Alice"))
313 m.Insert(txn, "age", ygo.Int(30))
314 arr.Push(txn, ygo.String("item1"))
315 arr.Push(txn, ygo.String("item2"))
316 return nil
317 })
318 if err != nil {
319 t.Fatalf("transaction failed: %v", err)
320 }
321
322 jsonBytes, err := doc.MarshalJSON(
323 ygo.WithMap("users"),
324 ygo.WithArray("items"),
325 )
326 if err != nil {
327 t.Fatalf("marshal failed: %v", err)
328 }
329
330 var result map[string]interface{}
331 if err := json.Unmarshal(jsonBytes, &result); err != nil {
332 t.Fatalf("failed to unmarshal: %v", err)
333 }
334
335 users, ok := result["users"].(map[string]interface{})
336 if !ok {
337 t.Fatal("expected users to be a map")
338 }
339 if users["name"] != "Alice" {
340 t.Errorf("expected users.name=Alice, got %v", users["name"])
341 }
342 if users["age"] != float64(30) {
343 t.Errorf("expected users.age=30, got %v", users["age"])
344 }
345
346 items, ok := result["items"].([]interface{})
347 if !ok {
348 t.Fatal("expected items to be an array")
349 }
350 if len(items) != 2 {
351 t.Errorf("expected 2 items, got %d", len(items))
352 }
353 if items[0] != "item1" {
354 t.Errorf("expected items[0]=item1, got %v", items[0])
355 }
356 if items[1] != "item2" {
357 t.Errorf("expected items[1]=item2, got %v", items[1])
358 }
359}
360
361func TestDocumentMarshalJSONWithXmlFragment(t *testing.T) {
362 doc, err := ygo.NewDoc()
363 if err != nil {
364 t.Fatalf("failed to create doc: %v", err)
365 }
366 defer doc.Destroy()
367
368 f, err := doc.GetXmlFragment("content")
369 if err != nil {
370 t.Fatalf("failed to get xml fragment: %v", err)
371 }
372 defer f.Destroy()
373
374 err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
375 elem, err := f.InsertElement(txn, 0, "note")
376 if err != nil {
377 return err
378 }
379 defer elem.Destroy()
380
381 txt, err := elem.InsertText(txn, 0)
382 if err != nil {
383 return err
384 }
385 defer txt.Destroy()
386
387 txt.Insert(txn, 0, "Hello World")
388 return nil
389 })
390 if err != nil {
391 t.Fatalf("transaction failed: %v", err)
392 }
393
394 jsonBytes, err := doc.MarshalJSON(ygo.WithXmlFragment("content"))
395 if err != nil {
396 t.Fatalf("marshal failed: %v", err)
397 }
398
399 var result map[string]interface{}
400 if err := json.Unmarshal(jsonBytes, &result); err != nil {
401 t.Fatalf("failed to unmarshal: %v", err)
402 }
403
404 content, ok := result["content"].([]interface{})
405 if !ok {
406 t.Fatal("expected content to be an array")
407 }
408 if len(content) == 0 {
409 t.Fatal("expected at least one element in content array")
410 }
411}
412
413func TestDocumentMarshalJSONWithText(t *testing.T) {
414 doc, err := ygo.NewDoc()
415 if err != nil {
416 t.Fatalf("failed to create doc: %v", err)
417 }
418 defer doc.Destroy()
419
420 txt, err := doc.GetText("schema")
421 if err != nil {
422 t.Fatalf("failed to get text: %v", err)
423 }
424 defer txt.Destroy()
425
426 err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
427 return txt.Push(txn, "some-schema-value")
428 })
429 if err != nil {
430 t.Fatalf("transaction failed: %v", err)
431 }
432
433 jsonBytes, err := doc.MarshalJSON(ygo.WithText("schema"))
434 if err != nil {
435 t.Fatalf("marshal failed: %v", err)
436 }
437
438 var result map[string]interface{}
439 if err := json.Unmarshal(jsonBytes, &result); err != nil {
440 t.Fatalf("failed to unmarshal: %v", err)
441 }
442
443 if result["schema"] != "some-schema-value" {
444 t.Errorf("expected schema=some-schema-value, got %v", result["schema"])
445 }
446}