Skip to content

Commit 4b50116

Browse files
Rollback tests for cell
1 parent 9451072 commit 4b50116

File tree

1 file changed

+187
-0
lines changed

1 file changed

+187
-0
lines changed

packages/dds/cell/src/test/cell.rollback.spec.ts

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,191 @@
33
* Licensed under the MIT License.
44
*/
55

6+
import { strict as assert } from "node:assert";
67

8+
import { AttachState } from "@fluidframework/container-definitions";
9+
import {
10+
MockContainerRuntimeFactory,
11+
MockFluidDataStoreRuntime,
12+
MockStorage,
13+
type MockContainerRuntime,
14+
} from "@fluidframework/test-runtime-utils/internal";
15+
16+
import { CellFactory } from "../cellFactory.js";
17+
import type { ISharedCell } from "../interfaces.js";
18+
19+
interface RollbackTestSetup {
20+
cell: ISharedCell;
21+
dataStoreRuntime: MockFluidDataStoreRuntime;
22+
containerRuntimeFactory: MockContainerRuntimeFactory;
23+
containerRuntime: MockContainerRuntime;
24+
}
25+
26+
const mapFactory = new CellFactory();
27+
28+
function setupRollbackTest(id: string): RollbackTestSetup {
29+
const containerRuntimeFactory = new MockContainerRuntimeFactory({ flushMode: 1 });
30+
const dataStoreRuntime = new MockFluidDataStoreRuntime({ clientId: "1" });
31+
const containerRuntime = containerRuntimeFactory.createContainerRuntime(dataStoreRuntime);
32+
const cell = mapFactory.create(dataStoreRuntime, id);
33+
dataStoreRuntime.setAttachState(AttachState.Attached);
34+
cell.connect({
35+
deltaConnection: dataStoreRuntime.createDeltaConnection(),
36+
objectStorage: new MockStorage(),
37+
});
38+
return {
39+
cell,
40+
dataStoreRuntime,
41+
containerRuntimeFactory,
42+
containerRuntime,
43+
};
44+
}
45+
46+
// Helper to create another client attached to the same containerRuntimeFactory
47+
function createAdditionalClient(
48+
containerRuntimeFactory: MockContainerRuntimeFactory,
49+
id: string = "client-2",
50+
): {
51+
cell: ISharedCell;
52+
dataStoreRuntime: MockFluidDataStoreRuntime;
53+
containerRuntime: MockContainerRuntime;
54+
} {
55+
const dataStoreRuntime = new MockFluidDataStoreRuntime({ clientId: id });
56+
const containerRuntime = containerRuntimeFactory.createContainerRuntime(dataStoreRuntime);
57+
const cell = mapFactory.create(dataStoreRuntime, `cell-${id}`);
58+
dataStoreRuntime.setAttachState(AttachState.Attached);
59+
cell.connect({
60+
deltaConnection: dataStoreRuntime.createDeltaConnection(),
61+
objectStorage: new MockStorage(),
62+
});
63+
return { cell, dataStoreRuntime, containerRuntime };
64+
}
65+
66+
describe("Cell with rollback", () => {
67+
it("should emit valueChanged on set and rollback should re-emit previous value", async () => {
68+
const { cell, containerRuntime } = setupRollbackTest("client-1");
69+
70+
const events: (string | undefined)[] = [];
71+
72+
cell.on("valueChanged", (value) => events.push("valueChanged"));
73+
74+
cell.set(10);
75+
assert.equal(cell.get(), 10);
76+
77+
containerRuntime.rollback?.();
78+
79+
assert.equal(cell.get(), undefined);
80+
81+
assert.deepEqual(events, ["valueChanged"]);
82+
});
83+
84+
it("should emit delete on delete, and rollback should re-emit last valueChanged", async () => {
85+
const { cell, containerRuntimeFactory, containerRuntime } = setupRollbackTest("client-1");
86+
87+
const events: (string | undefined)[] = [];
88+
89+
cell.on("valueChanged", (value) => events.push("valueChanged"));
90+
cell.on("delete", () => events.push("delete"));
91+
92+
cell.set(42);
93+
containerRuntime.flush();
94+
containerRuntimeFactory.processAllMessages();
95+
96+
cell.delete();
97+
assert(cell.empty());
98+
99+
// rollback delete
100+
containerRuntime.rollback?.();
101+
assert.equal(cell.get(), 42);
102+
103+
// delete triggers delete event, rollback restores valueChanged
104+
assert.deepEqual(events, ["valueChanged", "delete", "valueChanged"]);
105+
});
106+
});
107+
108+
describe("SharedCell rollback events with multiple clients", () => {
109+
it("should emit valueChanged on set and rollback should re-emit previous value across clients", async () => {
110+
// Setup two clients
111+
const {
112+
cell: cell1,
113+
containerRuntimeFactory,
114+
containerRuntime: runtime1,
115+
} = setupRollbackTest("client-1");
116+
const { cell: cell2 } = createAdditionalClient(containerRuntimeFactory);
117+
118+
const events1: string[] = [];
119+
const events2: string[] = [];
120+
121+
// Listen to valueChanged events on both clients
122+
cell1.on("valueChanged", () => events1.push("valueChanged"));
123+
cell2.on("valueChanged", () => events2.push("valueChanged"));
124+
125+
// Client 1 sets a value
126+
cell1.set(10);
127+
assert.equal(cell1.get(), 10);
128+
129+
// Propagate ops to client 2
130+
runtime1.flush();
131+
containerRuntimeFactory.processAllMessages();
132+
133+
assert.equal(cell2.get(), 10);
134+
135+
cell1.set(100);
136+
assert.equal(cell1.get(), 100);
137+
assert.equal(cell2.get(), 10);
138+
139+
// Rollback on client 1
140+
runtime1.rollback?.();
141+
142+
assert.equal(cell1.get(), 10);
143+
assert.equal(cell2.get(), 10);
144+
145+
// Both clients should have seen the events
146+
assert.deepEqual(events1, ["valueChanged", "valueChanged", "valueChanged"]);
147+
assert.deepEqual(events2, ["valueChanged"]);
148+
});
149+
150+
it("should emit delete on delete, and rollback should re-emit last valueChanged across clients", async () => {
151+
// Setup two clients
152+
const {
153+
cell: cell1,
154+
containerRuntimeFactory,
155+
containerRuntime: runtime1,
156+
} = setupRollbackTest("client-1");
157+
const { cell: cell2 } = createAdditionalClient(containerRuntimeFactory);
158+
159+
const events1: string[] = [];
160+
const events2: string[] = [];
161+
162+
// Attach listeners
163+
cell1.on("valueChanged", () => events1.push("valueChanged"));
164+
cell1.on("delete", () => events1.push("delete"));
165+
166+
cell2.on("valueChanged", () => events2.push("valueChanged"));
167+
cell2.on("delete", () => events2.push("delete"));
168+
169+
// Set initial value and propagate
170+
cell1.set(42);
171+
runtime1.flush();
172+
containerRuntimeFactory.processAllMessages();
173+
174+
assert.equal(cell2.get(), 42);
175+
176+
// Delete the value
177+
cell1.delete();
178+
179+
assert(cell1.empty());
180+
assert.equal(cell2.get(), 42);
181+
182+
// Rollback delete
183+
runtime1.rollback?.();
184+
185+
// After rollback, value is restored
186+
assert.equal(cell1.get(), 42);
187+
assert.equal(cell2.get(), 42);
188+
189+
// Event order
190+
assert.deepEqual(events1, ["valueChanged", "delete", "valueChanged"]);
191+
assert.deepEqual(events2, ["valueChanged"]);
192+
});
193+
});

0 commit comments

Comments
 (0)