Skip to content

Commit 9808bc5

Browse files
lodyai[bot]zxch3n
andauthored
fix(core): preserve movable root containers from initial state (#81)
Co-authored-by: Zixuan Chen <zx@loro.dev>
1 parent 36fec86 commit 9808bc5

2 files changed

Lines changed: 72 additions & 12 deletions

File tree

packages/core/src/core/mirror.ts

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -460,20 +460,46 @@ export class Mirror<S extends SchemaType> {
460460
string,
461461
unknown
462462
>;
463+
const rootSchema =
464+
this.schema && this.schema.type === "schema"
465+
? (this.schema as RootSchemaType<
466+
Record<string, ContainerSchemaType>
467+
>)
468+
: undefined;
463469
for (const [key, value] of Object.entries(init)) {
464-
let container: Container | null = null;
465-
if (Array.isArray(value)) {
466-
container = this.doc.getList(key);
467-
} else if (typeof value === "string") {
468-
container = this.doc.getText(key);
469-
} else if (isObject(value)) {
470-
container = this.doc.getMap(key);
471-
}
472-
if (container) {
473-
this.rootPathById.set(container.id, [key]);
474-
this.registerContainerWithRegistry(container.id, undefined);
475-
}
470+
const fieldSchema = rootSchema?.definition[key];
471+
const containerType =
472+
(fieldSchema
473+
? schemaToContainerType(fieldSchema)
474+
: undefined) ??
475+
this.inferRootContainerTypeFromInitialValue(value);
476+
if (!containerType) continue;
477+
478+
const container = getRootContainerByType(
479+
this.doc,
480+
key,
481+
containerType,
482+
);
483+
this.rootPathById.set(container.id, [key]);
484+
this.registerContainerWithRegistry(container.id, undefined);
485+
}
486+
}
487+
488+
private inferRootContainerTypeFromInitialValue(
489+
value: unknown,
490+
): ContainerType | undefined {
491+
if (Array.isArray(value)) {
492+
return this.options.inferOptions?.defaultMovableList
493+
? "MovableList"
494+
: "List";
495+
}
496+
if (typeof value === "string") {
497+
return "Text";
498+
}
499+
if (isObject(value)) {
500+
return "Map";
476501
}
502+
return undefined;
477503
}
478504

479505
/**

packages/core/tests/mirror-movable-list.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,40 @@ describe("MovableList (inferred)", () => {
753753
);
754754
});
755755

756+
it("keeps initialState root arrays snapshot-safe with defaultMovableList", async () => {
757+
const doc = new LoroDoc();
758+
const mirror = new Mirror({
759+
doc,
760+
initialState: {
761+
payload: {},
762+
items: [],
763+
},
764+
inferOptions: { defaultMovableList: true },
765+
validateUpdates: false,
766+
});
767+
const state = {
768+
payload: { title: "hello", count: 42 },
769+
items: [
770+
{ id: "a", value: 1 },
771+
{ id: "b", value: 2 },
772+
],
773+
};
774+
775+
mirror.setState(state);
776+
await waitForSync();
777+
778+
const serialized = doc.getDeepValueWithID() as Record<string, unknown>;
779+
expect(
780+
valueIsContainerOfType(serialized["items"], ":MovableList"),
781+
).toBe(true);
782+
783+
const restored = new LoroDoc();
784+
restored.import(doc.export({ mode: "snapshot" }));
785+
786+
expect(restored.toJSON()).toStrictEqual(state);
787+
expect(restored.toJSON()).toStrictEqual(doc.toJSON());
788+
});
789+
756790
it("preserves nested map container identity for object items", async () => {
757791
const doc = new LoroDoc();
758792
const mirror = new Mirror({

0 commit comments

Comments
 (0)