undo_test.go
1package ygo_test
2
3import (
4 "git.kilimanjaro.io/ygo"
5 "testing"
6)
7
8func TestUndoManagerBasic(t *testing.T) {
9 d1, err := ygo.NewDocWithOptions(ygo.DocOptions{ClientID: 1})
10 if err != nil {
11 t.Fatalf("failed to create doc: %v", err)
12 }
13 defer d1.Destroy()
14
15 txt1, err := d1.GetText("test")
16 if err != nil {
17 t.Fatalf("failed to get text: %v", err)
18 }
19 defer txt1.Destroy()
20
21 // Create undo manager with 0 timeout to capture each change separately
22 opts := &ygo.UndoManagerOptions{CaptureTimeoutMillis: 0}
23 mgr, err := ygo.NewUndoManager(opts)
24 if err != nil {
25 t.Fatalf("failed to create undo manager: %v", err)
26 }
27 defer mgr.Destroy()
28
29 // Add scope
30 if err := mgr.AddScope(d1, txt1); err != nil {
31 t.Fatalf("failed to add scope: %v", err)
32 }
33
34 // Make changes with explicit stops to separate undo items
35 err = d1.WithWriteTransaction(func(txn *ygo.Transaction) error {
36 txt1.Insert(txn, 0, "test")
37 return nil
38 })
39 if err != nil {
40 t.Fatalf("failed to create transaction: %v", err)
41 }
42 if err := mgr.Stop(); err != nil {
43 t.Fatalf("failed to stop: %v", err)
44 }
45
46 err = d1.WithWriteTransaction(func(txn *ygo.Transaction) error {
47 txt1.RemoveRange(txn, 0, 4)
48 return nil
49 })
50 if err != nil {
51 t.Fatalf("failed to create transaction: %v", err)
52 }
53 if err := mgr.Stop(); err != nil {
54 t.Fatalf("failed to stop: %v", err)
55 }
56
57 // Undo the delete
58 ok, err := mgr.Undo()
59 if err != nil {
60 t.Fatalf("failed to undo: %v", err)
61 }
62 if !ok {
63 t.Error("expected first undo to succeed")
64 }
65
66 // Verify undo - should be back to "test"
67 var str string
68 err = d1.WithReadTransaction(func(txn *ygo.Transaction) error {
69 str, err = txt1.String(txn)
70 return err
71 })
72 if err != nil {
73 t.Fatalf("failed to get string: %v", err)
74 }
75
76 if str != "test" {
77 t.Errorf("expected 'test' after first undo, got '%s'", str)
78 }
79
80 // Second undo - should remove the insert
81 ok, err = mgr.Undo()
82 if err != nil {
83 t.Fatalf("failed to undo: %v", err)
84 }
85 if !ok {
86 t.Error("expected second undo to succeed")
87 }
88
89 err = d1.WithReadTransaction(func(txn *ygo.Transaction) error {
90 str, err = txt1.String(txn)
91 return err
92 })
93 if err != nil {
94 t.Fatalf("failed to get string: %v", err)
95 }
96
97 // Should be empty now
98 if str != "" {
99 t.Errorf("expected empty string, got '%s'", str)
100 }
101
102 // Redo the insert
103 ok, err = mgr.Redo()
104 if err != nil {
105 t.Fatalf("failed to redo: %v", err)
106 }
107 if !ok {
108 t.Error("expected first redo to succeed")
109 }
110
111 err = d1.WithReadTransaction(func(txn *ygo.Transaction) error {
112 str, err = txt1.String(txn)
113 return err
114 })
115 if err != nil {
116 t.Fatalf("failed to get string: %v", err)
117 }
118
119 if str != "test" {
120 t.Errorf("expected 'test' after redo, got '%s'", str)
121 }
122}
123
124func TestUndoManagerStop(t *testing.T) {
125 d1, err := ygo.NewDoc()
126 if err != nil {
127 t.Fatalf("failed to create doc: %v", err)
128 }
129 defer d1.Destroy()
130
131 txt1, err := d1.GetText("test")
132 if err != nil {
133 t.Fatalf("failed to get text: %v", err)
134 }
135 defer txt1.Destroy()
136
137 mgr, err := ygo.NewUndoManager(nil)
138 if err != nil {
139 t.Fatalf("failed to create undo manager: %v", err)
140 }
141 defer mgr.Destroy()
142 if err := mgr.AddScope(d1, txt1); err != nil {
143 t.Fatalf("failed to add scope: %v", err)
144 }
145
146 // Make first set of changes
147 err = d1.WithWriteTransaction(func(txn *ygo.Transaction) error {
148 txt1.Insert(txn, 0, "a")
149 return nil
150 })
151 if err != nil {
152 t.Fatalf("failed to create transaction: %v", err)
153 }
154
155 if err := mgr.Stop(); err != nil {
156 t.Fatalf("failed to stop: %v", err)
157 }
158
159 // Make second set of changes
160 err = d1.WithWriteTransaction(func(txn *ygo.Transaction) error {
161 txt1.Insert(txn, 1, "b")
162 return nil
163 })
164 if err != nil {
165 t.Fatalf("failed to create transaction: %v", err)
166 }
167
168 if err := mgr.Stop(); err != nil {
169 t.Fatalf("failed to stop: %v", err)
170 }
171
172 // Should have 2 undo stack items
173 stackLen, err := mgr.UndoStackLen()
174 if err != nil {
175 t.Fatalf("failed to get undo stack length: %v", err)
176 }
177 if stackLen != 2 {
178 t.Errorf("expected 2 undo items, got %d", stackLen)
179 }
180}
181
182func TestUndoManagerWithRemoteChanges(t *testing.T) {
183 // NOTE: This test is simplified due to Rust library concurrency constraints.
184 // The full test scenario requires more careful transaction coordination.
185
186 d1, err := ygo.NewDocWithOptions(ygo.DocOptions{ClientID: 1})
187 if err != nil {
188 t.Fatalf("failed to create doc: %v", err)
189 }
190 defer d1.Destroy()
191
192 txt1, err := d1.GetText("test")
193 if err != nil {
194 t.Fatalf("failed to get text: %v", err)
195 }
196 defer txt1.Destroy()
197
198 mgr, err := ygo.NewUndoManager(&ygo.UndoManagerOptions{CaptureTimeoutMillis: 0})
199 if err != nil {
200 t.Fatalf("failed to create undo manager: %v", err)
201 }
202 defer mgr.Destroy()
203 if err := mgr.AddScope(d1, txt1); err != nil {
204 t.Fatalf("failed to add scope: %v", err)
205 }
206
207 // Make local changes
208 err = d1.WithWriteTransaction(func(txn *ygo.Transaction) error {
209 txt1.Insert(txn, 0, "abc")
210 return nil
211 })
212 if err != nil {
213 t.Fatalf("failed to create transaction: %v", err)
214 }
215 if err := mgr.Stop(); err != nil {
216 t.Fatalf("failed to stop: %v", err)
217 }
218
219 err = d1.WithWriteTransaction(func(txn *ygo.Transaction) error {
220 txt1.Insert(txn, 3, "xyz")
221 return nil
222 })
223 if err != nil {
224 t.Fatalf("failed to create transaction: %v", err)
225 }
226 if err := mgr.Stop(); err != nil {
227 t.Fatalf("failed to stop: %v", err)
228 }
229
230 // Verify initial state
231 var str1 string
232 err = d1.WithReadTransaction(func(txn *ygo.Transaction) error {
233 str1, err = txt1.String(txn)
234 return err
235 })
236 if err != nil {
237 t.Fatalf("failed to get string: %v", err)
238 }
239
240 if str1 != "abcxyz" {
241 t.Errorf("expected 'abcxyz', got '%s'", str1)
242 }
243
244 // Undo last change
245 mgr.Undo()
246
247 err = d1.WithReadTransaction(func(txn *ygo.Transaction) error {
248 str1, err = txt1.String(txn)
249 return err
250 })
251 if err != nil {
252 t.Fatalf("failed to get string: %v", err)
253 }
254
255 // After undo, should be "abc"
256 if str1 != "abc" {
257 t.Errorf("expected 'abc' after undo, got '%s'", str1)
258 }
259
260 // Undo first change
261 mgr.Undo()
262
263 err = d1.WithReadTransaction(func(txn *ygo.Transaction) error {
264 str1, err = txt1.String(txn)
265 return err
266 })
267 if err != nil {
268 t.Fatalf("failed to get string: %v", err)
269 }
270
271 // Should be empty now
272 if str1 != "" {
273 t.Errorf("expected empty after second undo, got '%s'", str1)
274 }
275
276 // Redo
277 mgr.Redo()
278
279 err = d1.WithReadTransaction(func(txn *ygo.Transaction) error {
280 str1, err = txt1.String(txn)
281 return err
282 })
283 if err != nil {
284 t.Fatalf("failed to get string: %v", err)
285 }
286
287 if str1 != "abc" {
288 t.Errorf("expected 'abc' after redo, got '%s'", str1)
289 }
290}