diff --git a/packages/core/src/core/mirror.ts b/packages/core/src/core/mirror.ts index 3f9e04e..f0c2946 100644 --- a/packages/core/src/core/mirror.ts +++ b/packages/core/src/core/mirror.ts @@ -460,20 +460,46 @@ export class Mirror { string, unknown >; + const rootSchema = + this.schema && this.schema.type === "schema" + ? (this.schema as RootSchemaType< + Record + >) + : undefined; for (const [key, value] of Object.entries(init)) { - let container: Container | null = null; - if (Array.isArray(value)) { - container = this.doc.getList(key); - } else if (typeof value === "string") { - container = this.doc.getText(key); - } else if (isObject(value)) { - container = this.doc.getMap(key); - } - if (container) { - this.rootPathById.set(container.id, [key]); - this.registerContainerWithRegistry(container.id, undefined); - } + const fieldSchema = rootSchema?.definition[key]; + const containerType = + (fieldSchema + ? schemaToContainerType(fieldSchema) + : undefined) ?? + this.inferRootContainerTypeFromInitialValue(value); + if (!containerType) continue; + + const container = getRootContainerByType( + this.doc, + key, + containerType, + ); + this.rootPathById.set(container.id, [key]); + this.registerContainerWithRegistry(container.id, undefined); + } + } + + private inferRootContainerTypeFromInitialValue( + value: unknown, + ): ContainerType | undefined { + if (Array.isArray(value)) { + return this.options.inferOptions?.defaultMovableList + ? "MovableList" + : "List"; + } + if (typeof value === "string") { + return "Text"; + } + if (isObject(value)) { + return "Map"; } + return undefined; } /** diff --git a/packages/core/tests/mirror-movable-list.test.ts b/packages/core/tests/mirror-movable-list.test.ts index 29c8a2a..0f097cc 100644 --- a/packages/core/tests/mirror-movable-list.test.ts +++ b/packages/core/tests/mirror-movable-list.test.ts @@ -753,6 +753,40 @@ describe("MovableList (inferred)", () => { ); }); + it("keeps initialState root arrays snapshot-safe with defaultMovableList", async () => { + const doc = new LoroDoc(); + const mirror = new Mirror({ + doc, + initialState: { + payload: {}, + items: [], + }, + inferOptions: { defaultMovableList: true }, + validateUpdates: false, + }); + const state = { + payload: { title: "hello", count: 42 }, + items: [ + { id: "a", value: 1 }, + { id: "b", value: 2 }, + ], + }; + + mirror.setState(state); + await waitForSync(); + + const serialized = doc.getDeepValueWithID() as Record; + expect( + valueIsContainerOfType(serialized["items"], ":MovableList"), + ).toBe(true); + + const restored = new LoroDoc(); + restored.import(doc.export({ mode: "snapshot" })); + + expect(restored.toJSON()).toStrictEqual(state); + expect(restored.toJSON()).toStrictEqual(doc.toJSON()); + }); + it("preserves nested map container identity for object items", async () => { const doc = new LoroDoc(); const mirror = new Mirror({