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}