Skip to content

Commit b54d016

Browse files
Add tests for multiple clients
1 parent 8617906 commit b54d016

File tree

1 file changed

+135
-52
lines changed

1 file changed

+135
-52
lines changed

packages/dds/sequence/src/test/sharedString.rollback.spec.ts

Lines changed: 135 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
type MockContainerRuntime,
1515
} from "@fluidframework/test-runtime-utils/internal";
1616

17-
import { SharedStringFactory } from "../sequenceFactory.js";
17+
import { SharedStringFactory, type SharedString } from "../sequenceFactory.js";
1818
import { SharedStringClass } from "../sharedString.js";
1919

2020
function setupSharedStringRollbackTest() {
@@ -65,41 +65,6 @@ function createAdditionalClient(
6565
return { sharedString, dataStoreRuntime, containerRuntime };
6666
}
6767

68-
describe("SharedString rollback change events", () => {
69-
it("rollback insert emits remove", () => {
70-
const { sharedString, containerRuntimeFactory, containerRuntime } =
71-
setupSharedStringRollbackTest();
72-
73-
// Apply insert we will rollback
74-
sharedString.insertText(0, "abc");
75-
containerRuntimeFactory.processAllMessages();
76-
assert.equal(sharedString.getText(), "abc", "text after insert");
77-
78-
containerRuntime.rollback?.();
79-
80-
// Expect rollback event to be a remove
81-
assert.equal(sharedString.getText(), "", "text reverted after rollback");
82-
});
83-
84-
it("rollback remove emits insert", () => {
85-
const { sharedString, containerRuntimeFactory, containerRuntime } =
86-
setupSharedStringRollbackTest();
87-
sharedString.insertText(0, "hello");
88-
containerRuntimeFactory.processAllMessages();
89-
containerRuntime.flush();
90-
assert.equal(sharedString.getText(), "hello");
91-
92-
// Apply remove we will rollback
93-
sharedString.removeText(0, 5);
94-
assert.equal(sharedString.getText(), "", "text after remove");
95-
96-
containerRuntime.rollback?.();
97-
98-
// Expect rollback event to be an insert
99-
assert.equal(sharedString.getText(), "hello", "rollback discards remove");
100-
});
101-
});
102-
10368
describe("SharedString rollback with multiple clients (insert/remove)", () => {
10469
it("Client1 insert + Client2 insert + rollback on Client1", () => {
10570
const {
@@ -555,12 +520,13 @@ describe("SharedString annotate with rollback", () => {
555520
});
556521

557522
describe("SharedString rollback triggers correct sequenceDelta events with text", () => {
558-
it("insert, remove, annotate, and replaceText rollback trigger correct sequenceDelta events", () => {
559-
const { sharedString, containerRuntimeFactory, containerRuntime } =
560-
setupSharedStringRollbackTest();
523+
interface Event {
524+
op: string;
525+
text: string;
526+
}
561527

562-
const events: { op: string; text: string }[] = [];
563-
sharedString.on("sequenceDelta", ({ deltaOperation, ranges, isLocal }) => {
528+
function setupDeltaListener(sharedString: SharedString, events: Event[]) {
529+
sharedString.on("sequenceDelta", ({ deltaOperation, isLocal }) => {
564530
if (!isLocal) return;
565531
switch (deltaOperation) {
566532
case MergeTreeDeltaType.INSERT:
@@ -576,51 +542,168 @@ describe("SharedString rollback triggers correct sequenceDelta events with text"
576542
throw new Error(`Unexpected deltaOperation: ${deltaOperation}`);
577543
}
578544
});
545+
}
546+
547+
it("rollback of insert triggers remove", () => {
548+
const { sharedString, containerRuntimeFactory, containerRuntime } =
549+
setupSharedStringRollbackTest();
550+
const events: Event[] = [];
551+
setupDeltaListener(sharedString, events);
579552

580-
// --- Insert and rollback ---
581553
sharedString.insertText(0, "hello");
582554
containerRuntimeFactory.processAllMessages();
583555
assert.equal(sharedString.getText(), "hello");
556+
584557
containerRuntime.rollback?.();
558+
585559
assert(
586560
events.some((e) => e.op === "remove" && e.text === ""),
587561
"Rollback of insert should trigger remove of correct text",
588562
);
563+
});
589564

590-
events.length = 0;
565+
it("rollback of remove triggers insert", () => {
566+
const { sharedString, containerRuntimeFactory, containerRuntime } =
567+
setupSharedStringRollbackTest();
568+
const events: Event[] = [];
569+
setupDeltaListener(sharedString, events);
591570

592-
// --- Remove and rollback ---
593571
sharedString.insertText(0, "world");
594572
containerRuntimeFactory.processAllMessages();
595573
sharedString.removeText(0, 5);
596574
assert.equal(sharedString.getText(), "");
575+
597576
containerRuntime.rollback?.();
577+
598578
assert(
599579
events.some((e) => e.op === "insert" && e.text === "world"),
600580
"Rollback of remove should trigger insert of correct text",
601581
);
582+
});
602583

603-
events.length = 0;
584+
it("rollback of annotate clears properties", () => {
585+
const { sharedString, containerRuntimeFactory, containerRuntime } =
586+
setupSharedStringRollbackTest();
587+
const events: Event[] = [];
588+
setupDeltaListener(sharedString, events);
604589

605-
// --- Annotate and rollback ---
606590
sharedString.insertText(0, "abc");
607591
containerRuntimeFactory.processAllMessages();
592+
608593
const styleProps = { style: "bold" };
609594
sharedString.annotateRange(0, 3, styleProps);
610-
Array.from({ length: 3 }).forEach((_, i) =>
611-
assert.deepEqual({ ...sharedString.getPropertiesAtPosition(i) }, { ...styleProps }),
612-
);
595+
for (let i = 0; i < 3; i++) {
596+
assert.deepEqual({ ...sharedString.getPropertiesAtPosition(i) }, styleProps);
597+
}
598+
613599
containerRuntime.rollback?.();
614-
Array.from({ length: 3 }).forEach((_, i) =>
600+
601+
for (let i = 0; i < 3; i++) {
615602
assert.deepEqual(
616603
{ ...sharedString.getPropertiesAtPosition(i) },
617604
{},
618605
"Rollback of annotate should clear properties",
619-
),
620-
);
606+
);
607+
}
608+
621609
assert(
622610
events.some((e) => e.op === "annotate" && e.text === "abc"),
623611
"Rollback of annotate should trigger annotate event with correct text",
624612
);
625613
});
614+
615+
it("multi-client: rollback of insert triggers remove", () => {
616+
const {
617+
sharedString: client1,
618+
containerRuntimeFactory,
619+
containerRuntime: cr1,
620+
} = setupSharedStringRollbackTest();
621+
const { sharedString: client2 } = createAdditionalClient(containerRuntimeFactory, "2");
622+
623+
const eventsClient1: Event[] = [];
624+
setupDeltaListener(client1, eventsClient1);
625+
626+
client1.insertText(0, "hello");
627+
cr1.flush();
628+
containerRuntimeFactory.processAllMessages();
629+
assert.equal(client1.getText(), "hello");
630+
assert.equal(client2.getText(), "hello");
631+
632+
client1.insertText(5, "world");
633+
cr1.rollback?.();
634+
635+
assert(
636+
eventsClient1.some((e) => e.op === "remove" && e.text === "hello"),
637+
"Rollback of insert should trigger remove of correct text on client1",
638+
);
639+
assert.equal(client1.getText(), "hello");
640+
assert.equal(client2.getText(), "hello");
641+
});
642+
643+
it("multi-client: rollback of remove triggers insert", () => {
644+
const {
645+
sharedString: client1,
646+
containerRuntimeFactory,
647+
containerRuntime: cr1,
648+
} = setupSharedStringRollbackTest();
649+
const { sharedString: client2 } = createAdditionalClient(containerRuntimeFactory, "2");
650+
651+
const eventsClient1: Event[] = [];
652+
setupDeltaListener(client1, eventsClient1);
653+
654+
client1.insertText(0, "world");
655+
cr1.flush();
656+
containerRuntimeFactory.processAllMessages();
657+
658+
client1.removeText(0, 5);
659+
assert.equal(client1.getText(), "");
660+
assert.equal(client2.getText(), "world");
661+
662+
cr1.rollback?.();
663+
664+
assert(
665+
eventsClient1.some((e) => e.op === "insert" && e.text === "world"),
666+
"Rollback of remove should trigger insert of correct text on client1",
667+
);
668+
assert.equal(client1.getText(), "world");
669+
assert.equal(client2.getText(), "world");
670+
});
671+
672+
it("multi-client: rollback of annotate clears properties", () => {
673+
const {
674+
sharedString: client1,
675+
containerRuntimeFactory,
676+
containerRuntime: cr1,
677+
} = setupSharedStringRollbackTest();
678+
createAdditionalClient(containerRuntimeFactory, "2");
679+
680+
const eventsClient1: Event[] = [];
681+
setupDeltaListener(client1, eventsClient1);
682+
683+
client1.insertText(0, "abc");
684+
cr1.flush();
685+
containerRuntimeFactory.processAllMessages();
686+
687+
const styleProps = { style: "bold" };
688+
client1.annotateRange(0, 3, styleProps);
689+
690+
for (let i = 0; i < 3; i++) {
691+
assert.deepEqual({ ...client1.getPropertiesAtPosition(i) }, styleProps);
692+
}
693+
694+
cr1.rollback?.();
695+
696+
for (let i = 0; i < 3; i++) {
697+
assert.deepEqual(
698+
{ ...client1.getPropertiesAtPosition(i) },
699+
{},
700+
"Rollback of annotate should clear properties",
701+
);
702+
}
703+
704+
assert(
705+
eventsClient1.some((e) => e.op === "annotate" && e.text === "abc"),
706+
"Rollback of annotate should trigger annotate event with correct text",
707+
);
708+
});
626709
});

0 commit comments

Comments
 (0)