From 961c9e8c65662467ce655d715f427039c2d6b780 Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Tue, 27 Aug 2024 16:15:13 -0700 Subject: [PATCH] tree: More schema cleanup (#22322) ## Description Reduce use of SchemaBuilderInternal. Fix missing sort when persisting field schema found while editing test schema: this is not a correctness issue, but should improve consistency and thus blob reuse in the encoded format. --- .../dds/tree/src/core/schema-stored/schema.ts | 3 +- .../chunkEncodingEndToEnd.spec.ts | 5 +- .../schema-index/schemaSummarizer.spec.ts | 6 +- .../dds/tree/src/test/sequenceRootUtils.ts | 71 ++++++++ .../tree/src/test/shared-tree/editing.spec.ts | 153 +++++++++--------- .../shared-tree/schematizingTreeView.spec.ts | 16 +- .../src/test/shared-tree/sharedTree.bench.ts | 2 +- .../src/test/shared-tree/sharedTree.spec.ts | 91 +---------- .../src/test/shared-tree/treeCheckout.spec.ts | 10 +- .../tree/src/test/shared-tree/undo.spec.ts | 28 ++-- .../simple encoded schema.json | 29 ++-- .../v1/optional-field-scenarios-final.json | 4 +- .../v2/optional-field-scenarios-final.json | 4 +- .../v3/optional-field-scenarios-final.json | 4 +- .../v1/optional-field-scenarios-final.json | 4 +- .../v2/optional-field-scenarios-final.json | 4 +- .../v3/optional-field-scenarios-final.json | 4 +- packages/dds/tree/src/test/utils.ts | 59 +------ 18 files changed, 226 insertions(+), 271 deletions(-) create mode 100644 packages/dds/tree/src/test/sequenceRootUtils.ts diff --git a/packages/dds/tree/src/core/schema-stored/schema.ts b/packages/dds/tree/src/core/schema-stored/schema.ts index 219bb808f519..fdc260e4abf1 100644 --- a/packages/dds/tree/src/core/schema-stored/schema.ts +++ b/packages/dds/tree/src/core/schema-stored/schema.ts @@ -292,7 +292,8 @@ function decodeValueSchema(inMemory: PersistedValueSchema): ValueSchema { export function encodeFieldSchema(schema: TreeFieldStoredSchema): FieldSchemaFormat { return { kind: schema.kind, - types: [...schema.types], + // Types are sorted by identifier to improve stability of persisted data to increase chance of schema blob reuse. + types: [...schema.types].sort(), }; } diff --git a/packages/dds/tree/src/test/feature-libraries/chunked-forest/chunkEncodingEndToEnd.spec.ts b/packages/dds/tree/src/test/feature-libraries/chunked-forest/chunkEncodingEndToEnd.spec.ts index 2c6b22b6fae5..99c11522530d 100644 --- a/packages/dds/tree/src/test/feature-libraries/chunked-forest/chunkEncodingEndToEnd.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/chunked-forest/chunkEncodingEndToEnd.spec.ts @@ -53,6 +53,7 @@ import { checkoutWithContent, cursorFromInsertableTreeField, flexTreeViewWithContent, + forestWithContent, numberSequenceRootSchema, testIdCompressor, } from "../../utils.js"; @@ -206,13 +207,13 @@ describe("End to end chunked encoding", () => { const chunk = new UniformChunk(numberShape.withTopLevelLength(4), [1, 2, 3, 4]); assert(!chunk.isShared()); - const flexTree = flexTreeViewWithContent({ + const forest = forestWithContent({ schema: numberSequenceRootSchema, initialTree: chunk.cursor(), }); const forestSummarizer = new ForestSummarizer( - flexTree.context.checkout.forest as IEditableForest, + forest, revisionTagCodec, fieldBatchCodec, context, diff --git a/packages/dds/tree/src/test/feature-libraries/schema-index/schemaSummarizer.spec.ts b/packages/dds/tree/src/test/feature-libraries/schema-index/schemaSummarizer.spec.ts index ad3f87205a8b..f834b785a8fc 100644 --- a/packages/dds/tree/src/test/feature-libraries/schema-index/schemaSummarizer.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/schema-index/schemaSummarizer.spec.ts @@ -4,13 +4,13 @@ */ import { storedEmptyFieldSchema } from "../../../core/index.js"; -import { intoStoredSchema } from "../../../feature-libraries/index.js"; import { encodeTreeSchema, // eslint-disable-next-line import/no-internal-modules } from "../../../feature-libraries/schema-index/schemaSummarizer.js"; +import { toStoredSchema } from "../../../simple-tree/index.js"; import { takeJsonSnapshot, useSnapshotDirectory } from "../../snapshots/index.js"; -import { jsonSequenceRootSchema } from "../../utils.js"; +import { JsonUnion } from "../../utils.js"; describe("schemaSummarizer", () => { describe("encodeTreeSchema", () => { @@ -24,7 +24,7 @@ describe("schemaSummarizer", () => { }); it("simple encoded schema", () => { - const encoded = encodeTreeSchema(intoStoredSchema(jsonSequenceRootSchema)); + const encoded = encodeTreeSchema(toStoredSchema(JsonUnion)); takeJsonSnapshot(encoded); }); }); diff --git a/packages/dds/tree/src/test/sequenceRootUtils.ts b/packages/dds/tree/src/test/sequenceRootUtils.ts new file mode 100644 index 000000000000..190da1e5c704 --- /dev/null +++ b/packages/dds/tree/src/test/sequenceRootUtils.ts @@ -0,0 +1,71 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { + type TreeStoredSchema, + rootFieldKey, + type MapTree, + type TreeNodeSchemaIdentifier, +} from "../core/index.js"; +import { leaf, singleJsonCursor } from "../domains/index.js"; +import { FieldKinds, cursorForMapTreeField } from "../feature-libraries/index.js"; +import type { ITreeCheckout } from "../shared-tree/index.js"; +import { toStoredSchema } from "../simple-tree/index.js"; +import { brand, type JsonCompatible } from "../util/index.js"; +import { checkoutWithContent, JsonUnion } from "./utils.js"; +// eslint-disable-next-line import/no-internal-modules +import { normalizeAllowedTypes } from "../simple-tree/schemaTypes.js"; + +// This file provides utilities for testing sequence fields using documents where the root is the sequence being tested. +// This pattern is not expressible using the public simple-tree API, and is only for testing internal details. + +const rootJsonSequenceSchema: TreeStoredSchema = { + nodeSchema: toStoredSchema(JsonUnion).nodeSchema, + rootFieldSchema: { + kind: FieldKinds.sequence.identifier, + types: new Set( + [...normalizeAllowedTypes(JsonUnion)].map((s) => + brand(s.identifier), + ), + ), + }, +}; + +/** + * Helper function to insert node at a given index. + * + * @param tree - The tree on which to perform the insert. + * @param index - The index in the root field at which to insert. + * @param value - The value of the inserted nodes. + */ +export function insert(tree: ITreeCheckout, index: number, ...values: string[]): void { + const fieldEditor = tree.editor.sequenceField({ field: rootFieldKey, parent: undefined }); + fieldEditor.insert( + index, + cursorForMapTreeField( + values.map((value): MapTree => ({ fields: new Map(), type: leaf.string.name, value })), + ), + ); +} + +/** + * Removes `count` items from the root field of `tree`. + */ +export function remove(tree: ITreeCheckout, index: number, count: number): void { + const field = tree.editor.sequenceField({ parent: undefined, field: rootFieldKey }); + field.remove(index, count); +} + +/** + * Creates a sequence field at the root. + */ +export function makeTreeFromJsonSequence(json: JsonCompatible[]): ITreeCheckout { + const cursors = json.map(singleJsonCursor); + const tree = checkoutWithContent({ + schema: rootJsonSequenceSchema, + initialTree: cursors, + }); + return tree; +} diff --git a/packages/dds/tree/src/test/shared-tree/editing.spec.ts b/packages/dds/tree/src/test/shared-tree/editing.spec.ts index 47c80b811d21..5d0eaaad2fab 100644 --- a/packages/dds/tree/src/test/shared-tree/editing.spec.ts +++ b/packages/dds/tree/src/test/shared-tree/editing.spec.ts @@ -24,17 +24,19 @@ import { } from "../../core/index.js"; import { jsonObject, leaf, singleJsonCursor } from "../../domains/index.js"; import { cursorForJsonableTreeNode } from "../../feature-libraries/index.js"; -import type { ITreeCheckout } from "../../shared-tree/index.js"; +import type { ITreeCheckout, TreeStoredContent } from "../../shared-tree/index.js"; import { type JsonCompatible, brand, makeArray } from "../../util/index.js"; import { + checkoutWithContent, createTestUndoRedoStacks, expectJsonTree, - insert, + JsonUnion, makeTreeFromJson, moveWithin, - remove, validateUsageError, } from "../utils.js"; +import { insert, makeTreeFromJsonSequence, remove } from "../sequenceRootUtils.js"; +import { SchemaFactory, toStoredSchema } from "../../simple-tree/index.js"; const rootField: FieldUpPath = { parent: undefined, @@ -53,10 +55,15 @@ const rootNode2: UpPath = { parentIndex: 1, }; +const emptyJsonContent: TreeStoredContent = { + schema: toStoredSchema(new SchemaFactory("").optional(JsonUnion)), + initialTree: undefined, +}; + describe("Editing", () => { describe("Sequence Field", () => { it("concurrent inserts", () => { - const tree1 = makeTreeFromJson([]); + const tree1 = makeTreeFromJsonSequence([]); insert(tree1, 0, "y"); const tree2 = tree1.fork(); @@ -71,7 +78,7 @@ describe("Editing", () => { }); it("can rebase remove over move", () => { - const tree1 = makeTreeFromJson([]); + const tree1 = makeTreeFromJsonSequence([]); const tree2 = tree1.fork(); insert(tree1, 0, "a", "b"); tree2.rebaseOnto(tree1); @@ -90,7 +97,7 @@ describe("Editing", () => { }); it("can rebase intra-field move over inter-field move of same node and its parent", () => { - const tree1 = makeTreeFromJson([[], ["X", "Y"]]); + const tree1 = makeTreeFromJsonSequence([[], ["X", "Y"]]); const tree2 = tree1.fork(); tree1.transaction.start(); @@ -113,7 +120,7 @@ describe("Editing", () => { }); it("can rebase remove over cross-field move", () => { - const tree1 = makeTreeFromJson([ + const tree1 = makeTreeFromJsonSequence([ { foo: ["a", "b", "c"], bar: ["d", "e"], @@ -159,7 +166,7 @@ describe("Editing", () => { }); it("can rebase cross-field move over remove", () => { - const tree1 = makeTreeFromJson([ + const tree1 = makeTreeFromJsonSequence([ { foo: ["a", "b", "c"], bar: ["d", "e"], @@ -207,7 +214,7 @@ describe("Editing", () => { }); it("can order concurrent inserts within concurrently removed content", () => { - const tree = makeTreeFromJson(["A", "B", "C", "D"]); + const tree = makeTreeFromJsonSequence(["A", "B", "C", "D"]); const delAB = tree.fork(); const delCD = tree.fork(); const addX = tree.fork(); @@ -234,7 +241,7 @@ describe("Editing", () => { }); it("can rebase a change under a node whose insertion is also rebased", () => { - const tree1 = makeTreeFromJson(["B"]); + const tree1 = makeTreeFromJsonSequence(["B"]); const tree2 = tree1.fork(); const tree3 = tree1.fork(); @@ -258,7 +265,7 @@ describe("Editing", () => { it("can handle competing removes", () => { for (const index of [0, 1, 2, 3]) { const startingState = ["A", "B", "C", "D"]; - const tree = makeTreeFromJson(startingState); + const tree = makeTreeFromJsonSequence(startingState); const tree1 = tree.fork(); const tree2 = tree.fork(); const tree3 = tree.fork(); @@ -282,7 +289,7 @@ describe("Editing", () => { }); it("can rebase local dependent inserts", () => { - const tree1 = makeTreeFromJson(["y"]); + const tree1 = makeTreeFromJsonSequence(["y"]); const tree2 = tree1.fork(); insert(tree1, 0, "x"); @@ -316,7 +323,7 @@ describe("Editing", () => { }); it("can rebase a local remove", () => { - const addW = makeTreeFromJson(["x", "y"]); + const addW = makeTreeFromJsonSequence(["x", "y"]); const delY = addW.fork(); remove(delY, 1, 1); @@ -358,7 +365,7 @@ describe("Editing", () => { }); it("inserts that concurrently target the same insertion point do not interleave their contents", () => { - const tree = makeTreeFromJson([]); + const tree = makeTreeFromJsonSequence([]); const abc = tree.fork(); const rst = tree.fork(); const xyz = tree.fork(); @@ -379,7 +386,7 @@ describe("Editing", () => { }); it("merge-left tie-breaking does not interleave concurrent left to right inserts", () => { - const tree = makeTreeFromJson([]); + const tree = makeTreeFromJsonSequence([]); const a = tree.fork(); const r = tree.fork(); const x = tree.fork(); @@ -425,7 +432,7 @@ describe("Editing", () => { // which is the desired outcome for RTL text. // TODO: update and activate this test once merge-right is supported. it.skip("merge-right tie-breaking does not interleave concurrent right to left inserts", () => { - const tree = makeTreeFromJson([]); + const tree = makeTreeFromJsonSequence([]); const c = tree.fork(); const t = tree.fork(); const z = tree.fork(); @@ -466,7 +473,7 @@ describe("Editing", () => { }); it("intra-field move", () => { - const tree1 = makeTreeFromJson(["A", "B"]); + const tree1 = makeTreeFromJsonSequence(["A", "B"]); moveWithin(tree1.editor, rootField, 0, 1, 2); @@ -474,7 +481,7 @@ describe("Editing", () => { }); it("can rebase insert and remove over insert in the same gap", () => { - const tree1 = makeTreeFromJson([]); + const tree1 = makeTreeFromJsonSequence([]); const tree2 = tree1.fork(); insert(tree1, 0, "B"); @@ -488,7 +495,7 @@ describe("Editing", () => { }); it("concurrent insert with nested change", () => { - const tree1 = makeTreeFromJson([]); + const tree1 = makeTreeFromJsonSequence([]); const tree2 = tree1.fork(); insert(tree1, 0, "a"); @@ -509,7 +516,7 @@ describe("Editing", () => { }); it("can rebase intra-field move over insert", () => { - const tree1 = makeTreeFromJson(["A", "B"]); + const tree1 = makeTreeFromJsonSequence(["A", "B"]); const tree2 = tree1.fork(); insert(tree1, 2, "C"); @@ -523,7 +530,7 @@ describe("Editing", () => { }); it("can concurrently edit and move a subtree", () => { - const tree1 = makeTreeFromJson(["A", { foo: "B" }]); + const tree1 = makeTreeFromJsonSequence(["A", { foo: "B" }]); const tree2 = tree1.fork(); const parent = { parent: undefined, parentField: rootFieldKey, parentIndex: 1 }; @@ -542,7 +549,7 @@ describe("Editing", () => { }); it("can concurrently edit and move a subtree (Move first)", () => { - const tree1 = makeTreeFromJson(["A", { foo: "B" }]); + const tree1 = makeTreeFromJsonSequence(["A", { foo: "B" }]); const tree2 = tree1.fork(); // Move B before A. @@ -567,7 +574,7 @@ describe("Editing", () => { }); it("can concurrently edit and move a subtree (Move first) in a list under a node", () => { - const tree1 = makeTreeFromJson([{ seq: [{ foo: "A" }, "B"] }]); + const tree1 = makeTreeFromJson({ seq: [{ foo: "A" }, "B"] }); const tree2 = tree1.fork(); const seqList: UpPath = { parent: rootNode, parentField: brand("seq"), parentIndex: 0 }; @@ -688,7 +695,7 @@ describe("Editing", () => { }); it("move under move-out", () => { - const tree1 = makeTreeFromJson([{ foo: ["a", "b"] }, "x"]); + const tree1 = makeTreeFromJsonSequence([{ foo: ["a", "b"] }, "x"]); tree1.transaction.start(); @@ -707,7 +714,7 @@ describe("Editing", () => { }); it("move, remove, restore", () => { - const tree1 = makeTreeFromJson(["a", "b"]); + const tree1 = makeTreeFromJsonSequence(["a", "b"]); const tree2 = tree1.fork(); const cursor = tree1.forest.allocateCursor(); @@ -741,7 +748,7 @@ describe("Editing", () => { }); it("move adjacent nodes to separate destinations", () => { - const tree = makeTreeFromJson(["A", "B", "C", "D"]); + const tree = makeTreeFromJsonSequence(["A", "B", "C", "D"]); const tree2 = tree.fork(); tree2.transaction.start(); @@ -754,7 +761,7 @@ describe("Editing", () => { }); it("move separate nodes to adjacent destinations", () => { - const tree = makeTreeFromJson(["A", "B", "C", "D"]); + const tree = makeTreeFromJsonSequence(["A", "B", "C", "D"]); const tree2 = tree.fork(); tree2.transaction.start(); @@ -767,7 +774,7 @@ describe("Editing", () => { }); it("ancestor of move destination removed", () => { - const tree = makeTreeFromJson([{ foo: ["a"] }, {}]); + const tree = makeTreeFromJsonSequence([{ foo: ["a"] }, {}]); const tree2 = tree.fork(); const first: UpPath = { @@ -810,7 +817,7 @@ describe("Editing", () => { }); it("ancestor of move source removed", () => { - const tree = makeTreeFromJson([{ foo: ["a"] }, {}]); + const tree = makeTreeFromJsonSequence([{ foo: ["a"] }, {}]); const tree2 = tree.fork(); const first: UpPath = { @@ -853,7 +860,7 @@ describe("Editing", () => { }); it("ancestor of move source removed then revived", () => { - const tree = makeTreeFromJson([{ foo: ["a"] }, {}]); + const tree = makeTreeFromJsonSequence([{ foo: ["a"] }, {}]); const tree2 = tree.fork(); const { undoStack, unsubscribe } = createTestUndoRedoStacks(tree.events); @@ -892,7 +899,7 @@ describe("Editing", () => { }); it("node being concurrently moved and removed with source ancestor revived", () => { - const tree = makeTreeFromJson([{ foo: ["a"] }, {}]); + const tree = makeTreeFromJsonSequence([{ foo: ["a"] }, {}]); const tree2 = tree.fork(); const { undoStack, unsubscribe } = createTestUndoRedoStacks(tree.events); @@ -934,7 +941,7 @@ describe("Editing", () => { }); it("remove, undo, childchange rebased over childchange", () => { - const tree = makeTreeFromJson([{ foo: ["b"] }]); + const tree = makeTreeFromJsonSequence([{ foo: ["b"] }]); const tree2 = tree.fork(); const { undoStack, unsubscribe } = createTestUndoRedoStacks(tree2.events); @@ -969,7 +976,7 @@ describe("Editing", () => { }); it("childchange rebase over remove, undo, childchange", () => { - const tree = makeTreeFromJson([{ foo: ["b"] }]); + const tree = makeTreeFromJsonSequence([{ foo: ["b"] }]); const tree2 = tree.fork(); const { undoStack, redoStack, unsubscribe } = createTestUndoRedoStacks(tree.events); @@ -1006,7 +1013,7 @@ describe("Editing", () => { }); it("node being concurrently moved and revived with source ancestor removed", () => { - const tree = makeTreeFromJson([{ foo: ["a"] }, {}]); + const tree = makeTreeFromJsonSequence([{ foo: ["a"] }, {}]); const tree2 = tree.fork(); const { undoStack, unsubscribe } = createTestUndoRedoStacks(tree.events); @@ -1048,7 +1055,7 @@ describe("Editing", () => { }); it("remove ancestor of return source", () => { - const tree = makeTreeFromJson([{ foo: ["a"] }, {}]); + const tree = makeTreeFromJsonSequence([{ foo: ["a"] }, {}]); const first: UpPath = { parent: undefined, parentIndex: 0, @@ -1098,7 +1105,7 @@ describe("Editing", () => { }); it("remove ancestor of return destination", () => { - const tree = makeTreeFromJson([{ foo: ["a"] }, {}]); + const tree = makeTreeFromJsonSequence([{ foo: ["a"] }, {}]); const first: UpPath = { parent: undefined, parentIndex: 0, @@ -1176,7 +1183,7 @@ describe("Editing", () => { }); it("can handle concurrent moves of the same node", () => { - const tree1 = makeTreeFromJson([{ foo: [], bar: [] }, "A"]); + const tree1 = makeTreeFromJsonSequence([{ foo: [], bar: [] }, "A"]); const tree2 = tree1.fork(); const fooList: UpPath = { parent: rootNode, parentField: brand("foo"), parentIndex: 0 }; @@ -1556,7 +1563,7 @@ describe("Editing", () => { }); it("can move a node out from a field and into a field under a sibling", () => { - const tree = makeTreeFromJson(["A", {}]); + const tree = makeTreeFromJsonSequence(["A", {}]); tree.editor.move(rootField, 0, 1, { parent: rootNode2, field: brand("foo") }, 0); const expectedState: JsonCompatible = [{ foo: "A" }]; expectJsonTree(tree, expectedState); @@ -1770,7 +1777,7 @@ describe("Editing", () => { }); it("concurrent cycle creating move", () => { - const tree = makeTreeFromJson([["foo"], ["bar"]]); + const tree = makeTreeFromJsonSequence([["foo"], ["bar"]]); const tree2 = tree.fork(); const fooList: UpPath = { @@ -1798,7 +1805,7 @@ describe("Editing", () => { }); it("rebase insert within revive", () => { - const tree = makeTreeFromJson(["y"]); + const tree = makeTreeFromJsonSequence(["y"]); const tree1 = tree.fork(); const { undoStack } = createTestUndoRedoStacks(tree1.events); @@ -1821,7 +1828,7 @@ describe("Editing", () => { it("repro scenario that requires correct rebase metadata", () => { const startState = [{ seq: ["A"] }, { seq: [] }, { seq: ["B"] }]; - const tree = makeTreeFromJson(startState); + const tree = makeTreeFromJsonSequence(startState); const [root0Array, root1Array, root2Array]: FieldUpPath[] = makeArray(3, (i) => ({ parent: { @@ -2003,7 +2010,7 @@ describe("Editing", () => { // The number of remaining undos available for each peer. const undoQueues: number[][] = makeArray(nbPeers, () => []); - const tree = makeTreeFromJson(startState); + const tree = makeTreeFromJsonSequence(startState); const peers = makeArray(nbPeers, () => tree.fork()); const peerUndoStacks = peers.map((peer) => createTestUndoRedoStacks(peer.events)); for (const step of scenario) { @@ -2101,7 +2108,7 @@ describe("Editing", () => { for (const disruption of disruptions) { if (action.nodeDst !== undefined) { it(`even if it was ${disruption.title} before the revert`, () => { - const tree = makeTreeFromJson([{ foo: "X" }]); + const tree = makeTreeFromJson({ foo: "X" }); const { undoStack, unsubscribe } = createTestUndoRedoStacks(tree.events); action.delegate(tree); @@ -2116,7 +2123,7 @@ describe("Editing", () => { } it(`even if it was ${disruption.title} concurrently to (and sequenced before) the revert`, () => { - const tree1 = makeTreeFromJson([{ foo: "X" }]); + const tree1 = makeTreeFromJson({ foo: "X" }); const tree2 = tree1.fork(); const { undoStack, unsubscribe } = createTestUndoRedoStacks(tree1.events); @@ -2138,7 +2145,7 @@ describe("Editing", () => { }); it(`even if it was ${disruption.title} concurrently to (and sequenced before) the ${action.title}`, () => { - const tree1 = makeTreeFromJson([{ foo: "X" }]); + const tree1 = makeTreeFromJson({ foo: "X" }); const tree2 = tree1.fork(); disruption.delegate(tree1, fooField); @@ -2167,7 +2174,7 @@ describe("Editing", () => { describe("Optional Field", () => { describe("can rebase a set over another set", () => { it("from a non-empty state", () => { - const tree1 = makeTreeFromJson([{ foo: "1" }]); + const tree1 = makeTreeFromJson({ foo: "1" }); const tree2 = tree1.fork(); const tree3 = tree1.fork(); @@ -2188,7 +2195,7 @@ describe("Editing", () => { }); it("from an empty state", () => { - const tree1 = makeTreeFromJson([{}]); + const tree1 = makeTreeFromJson({}); const tree2 = tree1.fork(); const tree3 = tree1.fork(); @@ -2209,7 +2216,7 @@ describe("Editing", () => { }); it("can rebase a node replacement and a dependent edit to the new node", () => { - const tree1 = makeTreeFromJson([]); + const tree1 = checkoutWithContent(emptyJsonContent); const tree2 = tree1.fork(); tree1.editor.optionalField(rootField).set(singleJsonCursor("41"), true); @@ -2230,7 +2237,7 @@ describe("Editing", () => { }); it("can rebase a node replacement and a dependent edit to the new node incrementally", () => { - const tree1 = makeTreeFromJson([]); + const tree1 = checkoutWithContent(emptyJsonContent); const tree2 = tree1.fork(); tree1.editor.optionalField(rootField).set(singleJsonCursor("41"), true); @@ -2249,7 +2256,7 @@ describe("Editing", () => { }); it("can rebase a node edit over an unrelated edit", () => { - const tree1 = makeTreeFromJson([{ foo: "40", bar: "123" }]); + const tree1 = makeTreeFromJson({ foo: "40", bar: "123" }); const tree2 = tree1.fork(); tree1.editor @@ -2269,7 +2276,7 @@ describe("Editing", () => { }); it("can rebase a node edit over the node being replaced and restored", () => { - const tree1 = makeTreeFromJson([{ foo: "40" }]); + const tree1 = makeTreeFromJson({ foo: "40" }); const tree2 = tree1.fork(); const { undoStack, unsubscribe } = createTestUndoRedoStacks(tree1.events); @@ -2288,7 +2295,7 @@ describe("Editing", () => { }); it("can rebase over successive sets", () => { - const tree1 = makeTreeFromJson([]); + const tree1 = checkoutWithContent(emptyJsonContent); const tree2 = tree1.fork(); tree1.editor.optionalField(rootField).set(singleJsonCursor("1"), true); @@ -2302,7 +2309,7 @@ describe("Editing", () => { }); it("can replace and restore a node", () => { - const tree1 = makeTreeFromJson(["42"]); + const tree1 = makeTreeFromJson("42"); const { undoStack, unsubscribe } = createTestUndoRedoStacks(tree1.events); tree1.editor.optionalField(rootField).set(singleJsonCursor("43"), false); @@ -2369,7 +2376,7 @@ describe("Editing", () => { describe(`reverting [${action.title}] restores A`, () => { for (const disruption of disruptions) { it(`even if it was ${disruption.title} before the revert`, () => { - const tree = makeTreeFromJson(["A"]); + const tree = makeTreeFromJson("A"); const { undoStack, unsubscribe } = createTestUndoRedoStacks(tree.events); action.delegate(tree); @@ -2383,7 +2390,7 @@ describe("Editing", () => { }); it(`even if it was ${disruption.title} concurrently to (and sequenced before) the revert`, () => { - const tree1 = makeTreeFromJson(["A"]); + const tree1 = makeTreeFromJson("A"); const tree2 = tree1.fork(); const { undoStack, unsubscribe } = createTestUndoRedoStacks(tree1.events); @@ -2405,7 +2412,7 @@ describe("Editing", () => { }); it(`even if it was ${disruption.title} concurrently to (and sequenced before) the ${action.title}`, () => { - const tree1 = makeTreeFromJson(["A"]); + const tree1 = makeTreeFromJson("A"); const tree2 = tree1.fork(); disruption.delegate(tree1, false); @@ -2431,7 +2438,7 @@ describe("Editing", () => { }); it("undo restores the removed node even when that node has been concurrently replaced", () => { - const tree = makeTreeFromJson(["42"]); + const tree = makeTreeFromJson("42"); const tree2 = tree.fork(); const { undoStack, unsubscribe } = createTestUndoRedoStacks(tree2.events); @@ -2456,7 +2463,7 @@ describe("Editing", () => { describe("Transactions", () => { // Exercises a scenario where a transaction's inverse must be computed as part of a rebase sandwich. it("Can rebase a series of edits including a transaction", () => { - const tree = makeTreeFromJson(["42"]); + const tree = makeTreeFromJson("42"); const tree2 = tree.fork(); tree2.transaction.start(); @@ -2475,7 +2482,7 @@ describe("Editing", () => { }); it("can rebase a transaction containing a node replacement and a dependent edit to the new node", () => { - const tree1 = makeTreeFromJson([]); + const tree1 = checkoutWithContent(emptyJsonContent); const tree2 = tree1.fork(); tree1.editor.optionalField(rootField).set(singleJsonCursor("41"), true); @@ -2500,7 +2507,7 @@ describe("Editing", () => { }); it("Can set and remove a node within a transaction", () => { - const tree = makeTreeFromJson([]); + const tree = checkoutWithContent(emptyJsonContent); const tree2 = tree.fork(); tree2.transaction.start(); @@ -2567,7 +2574,7 @@ describe("Editing", () => { }); it("simplified repro for 0x7cf from anchors-undo-redo fuzz seed 0", () => { - const tree = makeTreeFromJson([1]); + const tree = makeTreeFromJson(1); const fork = tree.fork(); tree.editor.optionalField(rootField).set(singleJsonCursor(2), false); @@ -2586,7 +2593,7 @@ describe("Editing", () => { describe("Constraints", () => { describe("Node existence constraint", () => { it("handles ancestor revive", () => { - const tree = makeTreeFromJson([]); + const tree = checkoutWithContent(emptyJsonContent); const { undoStack, unsubscribe } = createTestUndoRedoStacks(tree.events); const rootSequence = tree.editor.sequenceField(rootField); @@ -2627,7 +2634,7 @@ describe("Editing", () => { }); it("handles ancestor remove", () => { - const tree = makeTreeFromJson([]); + const tree = checkoutWithContent(emptyJsonContent); const rootSequence = tree.editor.sequenceField(rootField); rootSequence.insert(0, cursorForJsonableTreeNode({ type: jsonObject.name })); @@ -2676,7 +2683,7 @@ describe("Editing", () => { }); it("sequence field node exists constraint", () => { - const tree = makeTreeFromJson([]); + const tree = checkoutWithContent(emptyJsonContent); const { undoStack, unsubscribe } = createTestUndoRedoStacks(tree.events); insert(tree, 0, "A", "D"); @@ -2725,7 +2732,7 @@ describe("Editing", () => { }); it("optional field node exists constraint", () => { - const tree = makeTreeFromJson([]); + const tree = checkoutWithContent(emptyJsonContent); const rootSequence = tree.editor.sequenceField(rootField); rootSequence.insert(0, cursorForJsonableTreeNode({ type: jsonObject.name })); const optional = tree.editor.optionalField({ @@ -2757,7 +2764,7 @@ describe("Editing", () => { }); it("revived optional field node exists constraint", () => { - const tree = makeTreeFromJson([]); + const tree = checkoutWithContent(emptyJsonContent); const { undoStack, unsubscribe } = createTestUndoRedoStacks(tree.events); const rootSequence = tree.editor.sequenceField(rootField); rootSequence.insert(0, cursorForJsonableTreeNode({ type: jsonObject.name })); @@ -2792,7 +2799,7 @@ describe("Editing", () => { }); it("existence constraint on node inserted in prior transaction", () => { - const tree = makeTreeFromJson([]); + const tree = checkoutWithContent(emptyJsonContent); const tree2 = tree.fork(); // Insert "a" @@ -2826,7 +2833,7 @@ describe("Editing", () => { }); it("can add constraint to node inserted in same transaction", () => { - const tree = makeTreeFromJson([{}]); + const tree = makeTreeFromJsonSequence([{}]); const tree2 = tree.fork(); // Constrain on "a" existing and insert "b" if it does @@ -2865,7 +2872,7 @@ describe("Editing", () => { }); it("a change can depend on the existence of a node that is built in a prior change whose constraint was violated", () => { - const tree = makeTreeFromJson([]); + const tree = checkoutWithContent(emptyJsonContent); const rootSequence = tree.editor.sequenceField(rootField); rootSequence.insert(0, cursorForJsonableTreeNode({ type: jsonObject.name })); const optional = tree.editor.optionalField({ @@ -2909,7 +2916,7 @@ describe("Editing", () => { }); it("transaction dropped when constrained node is inserted under a concurrently removed ancestor", () => { - const tree = makeTreeFromJson([{}]); + const tree = makeTreeFromJsonSequence([{}]); const tree2 = tree.fork(); // Remove node from root sequence @@ -2944,7 +2951,7 @@ describe("Editing", () => { }); it("not violated by move out under remove", () => { - const tree = makeTreeFromJson([{ foo: ["a"] }, {}]); + const tree = makeTreeFromJsonSequence([{ foo: ["a"] }, {}]); const tree2 = tree.fork(); const firstPath = { @@ -2991,7 +2998,7 @@ describe("Editing", () => { }); it("transaction dropped when constrained node is moved under a concurrently removed ancestor", () => { - const tree = makeTreeFromJson([{ foo: ["a"] }, {}]); + const tree = makeTreeFromJsonSequence([{ foo: ["a"] }, {}]); const tree2 = tree.fork(); const firstPath = { @@ -3192,7 +3199,7 @@ describe("Editing", () => { }); it("invert a composite change that include a mix of nested changes in a field that requires an amend pass", () => { - const tree = makeTreeFromJson([{}]); + const tree = makeTreeFromJson({}); tree.transaction.start(); tree.transaction.start(); diff --git a/packages/dds/tree/src/test/shared-tree/schematizingTreeView.spec.ts b/packages/dds/tree/src/test/shared-tree/schematizingTreeView.spec.ts index ab47f3aab377..dc9964751699 100644 --- a/packages/dds/tree/src/test/shared-tree/schematizingTreeView.spec.ts +++ b/packages/dds/tree/src/test/shared-tree/schematizingTreeView.spec.ts @@ -30,9 +30,9 @@ import { checkoutWithContent, createTestUndoRedoStacks, cursorFromInsertableTreeField, - insert, validateUsageError, } from "../utils.js"; +import { insert } from "../sequenceRootUtils.js"; import type { TreeCheckout, TreeStoredContent } from "../../shared-tree/index.js"; const schema = new SchemaFactory("com.example"); @@ -275,4 +275,18 @@ describe("SchematizingSimpleTreeView", () => { assert.equal(undoStack.length, 0); assert.equal(redoStack.length, 1); }); + + it("schemaChanged event", () => { + const content = { + schema: toStoredSchema([]), + initialTree: undefined, + }; + const checkout = checkoutWithContent(content); + const view = new SchematizingSimpleTreeView(checkout, config, new MockNodeKeyManager()); + const log: string[] = []; + view.events.on("schemaChanged", () => log.push("changed")); + assert.deepEqual(log, []); + view.upgradeSchema(); + assert.deepEqual(log, ["changed"]); + }); }); diff --git a/packages/dds/tree/src/test/shared-tree/sharedTree.bench.ts b/packages/dds/tree/src/test/shared-tree/sharedTree.bench.ts index 484f9a6278bb..8299a69599f0 100644 --- a/packages/dds/tree/src/test/shared-tree/sharedTree.bench.ts +++ b/packages/dds/tree/src/test/shared-tree/sharedTree.bench.ts @@ -45,9 +45,9 @@ import { TestTreeProviderLite, checkoutWithContent, flexTreeViewWithContent, - insert, toJsonableTree, } from "../utils.js"; +import { insert } from "../sequenceRootUtils.js"; import { cursorFromInsertable } from "../../simple-tree/index.js"; // number of nodes in test for wide trees diff --git a/packages/dds/tree/src/test/shared-tree/sharedTree.spec.ts b/packages/dds/tree/src/test/shared-tree/sharedTree.spec.ts index 68ef97203f23..8d1d5532bd3f 100644 --- a/packages/dds/tree/src/test/shared-tree/sharedTree.spec.ts +++ b/packages/dds/tree/src/test/shared-tree/sharedTree.spec.ts @@ -39,16 +39,11 @@ import { } from "../../feature-libraries/chunked-forest/chunkedForest.js"; import { FieldKinds, - FlexFieldSchema, - type FlexTreeSchema, MockNodeKeyManager, SchemaBuilderBase, - SchemaBuilderInternal, TreeCompressionStrategy, TreeStatus, - ViewSchema, cursorForJsonableTreeNode, - defaultSchemaPolicy, intoStoredSchema, } from "../../feature-libraries/index.js"; import { @@ -56,7 +51,6 @@ import { // eslint-disable-next-line import/no-internal-modules } from "../../feature-libraries/object-forest/objectForest.js"; import { - type FlexTreeView, ForestType, type ISharedTree, type InitializeAndSchematizeConfiguration, @@ -66,7 +60,6 @@ import { type TreeCheckout, } from "../../shared-tree/index.js"; import { - requireSchema, SchematizingSimpleTreeView, // eslint-disable-next-line import/no-internal-modules } from "../../shared-tree/schematizingTreeView.js"; @@ -79,7 +72,7 @@ import { type TreeView, TreeViewConfiguration, } from "../../simple-tree/index.js"; -import { disposeSymbol, fail } from "../../util/index.js"; +import { fail } from "../../util/index.js"; import { type ConnectionSetter, type ITestTreeProvider, @@ -91,7 +84,6 @@ import { createTestUndoRedoStacks, expectSchemaEqual, schematizeFlexTree, - stringSequenceRootSchema, treeTestFactory, validateTreeConsistency, validateTreeContent, @@ -104,6 +96,8 @@ import { import { configuredSharedTree } from "../../treeFactory.js"; import type { ISharedObjectKind } from "@fluidframework/shared-object-base/internal"; import { TestAnchor } from "../testAnchor.js"; +// eslint-disable-next-line import/no-internal-modules +import { stringSchema } from "../../simple-tree/leafNodeSchema.js"; const enableSchemaValidation = true; @@ -143,7 +137,7 @@ describe("SharedTree", () => { it("concurrent Schematize", () => { const provider = new TestTreeProviderLite(2); const content = { - schema: stringSequenceRootSchema, + schema: toFlexSchema(stringSchema), allowedSchemaModifications: AllowedUpdateType.Initialize, initialTree: [singleJsonCursor("x")], } satisfies InitializeAndSchematizeConfiguration; @@ -221,83 +215,6 @@ describe("SharedTree", () => { }); }); - describe("requireSchema", () => { - function assertSchema( - tree: SharedTree, - schema: FlexTreeSchema, - onDispose: () => void = () => assert.fail(), - ): FlexTreeView { - const viewSchema = new ViewSchema(defaultSchemaPolicy, {}, intoStoredSchema(schema)); - const view = requireSchema( - tree.checkout, - viewSchema, - onDispose, - new MockNodeKeyManager(), - schema, - ); - const unregister = tree.checkout.storedSchema.on("afterSchemaChange", () => { - unregister(); - view[disposeSymbol](); - }); - return view; - } - - const factory = new SharedTreeFactory({ - jsonValidator: typeboxValidator, - forest: ForestType.Reference, - }); - const schemaEmpty = new SchemaBuilderInternal({ - scope: "com.fluidframework.test", - lint: { rejectEmpty: false, rejectForbidden: false }, - }).intoSchema(FlexFieldSchema.empty); - - function updateSchema(tree: SharedTree, schema: FlexTreeSchema): void { - tree.checkout.updateSchema(intoStoredSchema(schema)); - // Workaround to trigger for schema update batching kludge in afterSchemaChanges - tree.checkout.events.emit("afterBatch"); - } - - it("empty", () => { - const tree = factory.create( - new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), - "the tree", - ); - const view = assertSchema(tree, schemaEmpty); - assert.equal(view.flexTree.length, 0); - }); - - it("differing schema errors and schema change callback", () => { - const tree = factory.create( - new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), - "the tree", - ); - const builder = new SchemaBuilderBase(FieldKinds.optional, { - scope: "test", - libraries: [leaf.library], - }); - const schemaGeneralized = builder.intoSchema([leaf.number, leaf.string]); - assert.throws(() => assertSchema(tree, schemaGeneralized)); - - const log: string[] = []; - { - assertSchema(tree, schemaEmpty, () => log.push("empty")); - } - assert.deepEqual(log, []); - updateSchema(tree, schemaGeneralized); - - assert.deepEqual(log, ["empty"]); - - assertSchema(tree, schemaGeneralized, () => - // TypeScript's type narrowing turned "log" into never[] here since it assumes methods never modify anything, so we have to cast it back to a string[]: - (log as string[]).push("general"), - ); - - assert.deepEqual(log, ["empty"]); - updateSchema(tree, schemaEmpty); - assert.deepEqual(log, ["empty", "general"]); - }); - }); - it("handle in op", async () => { // TODO: ADO#7111 schema should be specified to enable compressed encoding. const provider = await TestTreeProvider.create( diff --git a/packages/dds/tree/src/test/shared-tree/treeCheckout.spec.ts b/packages/dds/tree/src/test/shared-tree/treeCheckout.spec.ts index 7dd4b194de48..efb46c049a02 100644 --- a/packages/dds/tree/src/test/shared-tree/treeCheckout.spec.ts +++ b/packages/dds/tree/src/test/shared-tree/treeCheckout.spec.ts @@ -38,7 +38,6 @@ import { expectSchemaEqual, forkView, getView, - numberSequenceRootSchema, validateUsageError, viewCheckout, } from "../utils.js"; @@ -53,7 +52,9 @@ import { import { getOrCreateInnerNode } from "../../simple-tree/proxyBinding.js"; // eslint-disable-next-line import/no-internal-modules import type { SchematizingSimpleTreeView } from "../../shared-tree/schematizingTreeView.js"; -import { toFlexSchema } from "../../simple-tree/index.js"; +import { toFlexSchema, toStoredSchema } from "../../simple-tree/index.js"; +// eslint-disable-next-line import/no-internal-modules +import { numberSchema } from "../../simple-tree/leafNodeSchema.js"; const rootField: FieldUpPath = { parent: undefined, @@ -753,9 +754,8 @@ describe("sharedTreeView", () => { fork.checkout[disposeSymbol](); assert.throws(() => fork.root.insertAtStart("A")); - assert.throws(() => - fork.checkout.updateSchema(intoStoredSchema(numberSequenceRootSchema)), - ); + const targetSchema = toStoredSchema(numberSchema); + assert.throws(() => fork.checkout.updateSchema(targetSchema)); assert.throws(() => fork.checkout[disposeSymbol]()); }); diff --git a/packages/dds/tree/src/test/shared-tree/undo.spec.ts b/packages/dds/tree/src/test/shared-tree/undo.spec.ts index 8c9696643449..50b34e502f9a 100644 --- a/packages/dds/tree/src/test/shared-tree/undo.spec.ts +++ b/packages/dds/tree/src/test/shared-tree/undo.spec.ts @@ -7,14 +7,8 @@ import { type UpPath, rootFieldKey } from "../../core/index.js"; import { singleJsonCursor } from "../../domains/index.js"; import type { ITreeCheckout } from "../../shared-tree/index.js"; import { type JsonCompatible, brand } from "../../util/index.js"; -import { - createTestUndoRedoStacks, - expectJsonTree, - insert, - makeTreeFromJson, - moveWithin, - remove, -} from "../utils.js"; +import { createTestUndoRedoStacks, expectJsonTree, moveWithin } from "../utils.js"; +import { insert, makeTreeFromJsonSequence, remove } from "../sequenceRootUtils.js"; const rootPath: UpPath = { parent: undefined, @@ -158,7 +152,7 @@ describe("Undo and redo", () => { const count = undoCount ?? 1; const itFn = skip ? it.skip : it; itFn(`${name} (act on fork undo on fork)`, () => { - const view = makeTreeFromJson(initialState); + const view = makeTreeFromJsonSequence(initialState); const fork = view.fork(); const { undoStack, redoStack, unsubscribe } = createTestUndoRedoStacks(fork.events); @@ -185,7 +179,7 @@ describe("Undo and redo", () => { // TODO: unskip once forking revertibles is supported it.skip(`${name} (act on view undo on fork)`, () => { - const view = makeTreeFromJson(initialState); + const view = makeTreeFromJsonSequence(initialState); const fork = view.fork(); const { undoStack, redoStack, unsubscribe } = createTestUndoRedoStacks(fork.events); @@ -211,7 +205,7 @@ describe("Undo and redo", () => { }); itFn(`${name} (act on view undo on view)`, () => { - const view = makeTreeFromJson(initialState); + const view = makeTreeFromJsonSequence(initialState); const fork = view.fork(); const { undoStack, redoStack, unsubscribe } = createTestUndoRedoStacks(view.events); @@ -238,7 +232,7 @@ describe("Undo and redo", () => { // TODO: unskip once forking revertibles is supported it.skip(`${name} (act on fork undo on view)`, () => { - const view = makeTreeFromJson(initialState); + const view = makeTreeFromJsonSequence(initialState); const fork = view.fork(); const { undoStack, redoStack, unsubscribe } = createTestUndoRedoStacks(view.events); @@ -264,7 +258,7 @@ describe("Undo and redo", () => { }); it(`${name} multiple times`, () => { - const tree = makeTreeFromJson(initialState); + const tree = makeTreeFromJsonSequence(initialState); const fork = tree.fork(); const { undoStack, redoStack, unsubscribe } = createTestUndoRedoStacks(tree.events); @@ -293,7 +287,7 @@ describe("Undo and redo", () => { } it("can undo before and after rebasing a branch", () => { - const tree1 = makeTreeFromJson([0, 0, 0]); + const tree1 = makeTreeFromJsonSequence([0, 0, 0]); const tree2 = tree1.fork(); const { undoStack, unsubscribe } = createTestUndoRedoStacks(tree2.events); @@ -311,7 +305,7 @@ describe("Undo and redo", () => { // TODO: unskip once forking revertibles is supported it.skip("can undo after forking a branch", () => { - const tree1 = makeTreeFromJson(["A", "B", "C"]); + const tree1 = makeTreeFromJsonSequence(["A", "B", "C"]); const { undoStack: undoStack1, unsubscribe: unsubscribe1 } = createTestUndoRedoStacks( tree1.events, @@ -334,7 +328,7 @@ describe("Undo and redo", () => { // TODO: unskip once forking revertibles is supported it.skip("can redo after forking a branch", () => { - const tree1 = makeTreeFromJson(["B"]); + const tree1 = makeTreeFromJsonSequence(["B"]); const { undoStack: undoStack1, unsubscribe: unsubscribe1 } = createTestUndoRedoStacks( tree1.events, @@ -358,7 +352,7 @@ describe("Undo and redo", () => { }); it("can undo/redo a transaction", () => { - const tree = makeTreeFromJson(["A", "B"]); + const tree = makeTreeFromJsonSequence(["A", "B"]); const { undoStack, redoStack, unsubscribe } = createTestUndoRedoStacks(tree.events); tree.transaction.start(); diff --git a/packages/dds/tree/src/test/snapshots/encodeTreeSchema/simple encoded schema.json b/packages/dds/tree/src/test/snapshots/encodeTreeSchema/simple encoded schema.json index caacaca613fd..35252a8eb35e 100644 --- a/packages/dds/tree/src/test/snapshots/encodeTreeSchema/simple encoded schema.json +++ b/packages/dds/tree/src/test/snapshots/encodeTreeSchema/simple encoded schema.json @@ -6,12 +6,12 @@ "": { "kind": "Sequence", "types": [ - "com.fluidframework.json.object", "com.fluidframework.json.array", - "com.fluidframework.leaf.number", + "com.fluidframework.json.object", "com.fluidframework.leaf.boolean", - "com.fluidframework.leaf.string", - "com.fluidframework.leaf.null" + "com.fluidframework.leaf.null", + "com.fluidframework.leaf.number", + "com.fluidframework.leaf.string" ] } } @@ -20,21 +20,18 @@ "map": { "kind": "Optional", "types": [ - "com.fluidframework.json.object", "com.fluidframework.json.array", - "com.fluidframework.leaf.number", + "com.fluidframework.json.object", "com.fluidframework.leaf.boolean", - "com.fluidframework.leaf.string", - "com.fluidframework.leaf.null" + "com.fluidframework.leaf.null", + "com.fluidframework.leaf.number", + "com.fluidframework.leaf.string" ] } }, "com.fluidframework.leaf.boolean": { "leaf": 2 }, - "com.fluidframework.leaf.handle": { - "leaf": 3 - }, "com.fluidframework.leaf.null": { "leaf": 4 }, @@ -46,14 +43,14 @@ } }, "root": { - "kind": "Sequence", + "kind": "Value", "types": [ - "com.fluidframework.json.object", "com.fluidframework.json.array", - "com.fluidframework.leaf.number", + "com.fluidframework.json.object", "com.fluidframework.leaf.boolean", - "com.fluidframework.leaf.string", - "com.fluidframework.leaf.null" + "com.fluidframework.leaf.null", + "com.fluidframework.leaf.number", + "com.fluidframework.leaf.string" ] } } \ No newline at end of file diff --git a/packages/dds/tree/src/test/snapshots/summary/Compressed/v1/optional-field-scenarios-final.json b/packages/dds/tree/src/test/snapshots/summary/Compressed/v1/optional-field-scenarios-final.json index 9a31d9cff53f..22f48dd42923 100644 --- a/packages/dds/tree/src/test/snapshots/summary/Compressed/v1/optional-field-scenarios-final.json +++ b/packages/dds/tree/src/test/snapshots/summary/Compressed/v1/optional-field-scenarios-final.json @@ -424,8 +424,8 @@ "map": { "kind": "Optional", "types": [ - "com.fluidframework.leaf.string", - "com.fluidframework.leaf.number" + "com.fluidframework.leaf.number", + "com.fluidframework.leaf.string" ] } } diff --git a/packages/dds/tree/src/test/snapshots/summary/Compressed/v2/optional-field-scenarios-final.json b/packages/dds/tree/src/test/snapshots/summary/Compressed/v2/optional-field-scenarios-final.json index 586026371b49..a9f818a6f19c 100644 --- a/packages/dds/tree/src/test/snapshots/summary/Compressed/v2/optional-field-scenarios-final.json +++ b/packages/dds/tree/src/test/snapshots/summary/Compressed/v2/optional-field-scenarios-final.json @@ -390,8 +390,8 @@ "map": { "kind": "Optional", "types": [ - "com.fluidframework.leaf.string", - "com.fluidframework.leaf.number" + "com.fluidframework.leaf.number", + "com.fluidframework.leaf.string" ] } } diff --git a/packages/dds/tree/src/test/snapshots/summary/Compressed/v3/optional-field-scenarios-final.json b/packages/dds/tree/src/test/snapshots/summary/Compressed/v3/optional-field-scenarios-final.json index 4d4a5b3c62ff..5bf6c68278a5 100644 --- a/packages/dds/tree/src/test/snapshots/summary/Compressed/v3/optional-field-scenarios-final.json +++ b/packages/dds/tree/src/test/snapshots/summary/Compressed/v3/optional-field-scenarios-final.json @@ -390,8 +390,8 @@ "map": { "kind": "Optional", "types": [ - "com.fluidframework.leaf.string", - "com.fluidframework.leaf.number" + "com.fluidframework.leaf.number", + "com.fluidframework.leaf.string" ] } } diff --git a/packages/dds/tree/src/test/snapshots/summary/Uncompressed/v1/optional-field-scenarios-final.json b/packages/dds/tree/src/test/snapshots/summary/Uncompressed/v1/optional-field-scenarios-final.json index 970b7952b84f..e2821bc14c91 100644 --- a/packages/dds/tree/src/test/snapshots/summary/Uncompressed/v1/optional-field-scenarios-final.json +++ b/packages/dds/tree/src/test/snapshots/summary/Uncompressed/v1/optional-field-scenarios-final.json @@ -436,8 +436,8 @@ "map": { "kind": "Optional", "types": [ - "com.fluidframework.leaf.string", - "com.fluidframework.leaf.number" + "com.fluidframework.leaf.number", + "com.fluidframework.leaf.string" ] } } diff --git a/packages/dds/tree/src/test/snapshots/summary/Uncompressed/v2/optional-field-scenarios-final.json b/packages/dds/tree/src/test/snapshots/summary/Uncompressed/v2/optional-field-scenarios-final.json index 8b8a015d8df2..c841f10580b9 100644 --- a/packages/dds/tree/src/test/snapshots/summary/Uncompressed/v2/optional-field-scenarios-final.json +++ b/packages/dds/tree/src/test/snapshots/summary/Uncompressed/v2/optional-field-scenarios-final.json @@ -402,8 +402,8 @@ "map": { "kind": "Optional", "types": [ - "com.fluidframework.leaf.string", - "com.fluidframework.leaf.number" + "com.fluidframework.leaf.number", + "com.fluidframework.leaf.string" ] } } diff --git a/packages/dds/tree/src/test/snapshots/summary/Uncompressed/v3/optional-field-scenarios-final.json b/packages/dds/tree/src/test/snapshots/summary/Uncompressed/v3/optional-field-scenarios-final.json index 2a2b7abe204f..1d537684541a 100644 --- a/packages/dds/tree/src/test/snapshots/summary/Uncompressed/v3/optional-field-scenarios-final.json +++ b/packages/dds/tree/src/test/snapshots/summary/Uncompressed/v3/optional-field-scenarios-final.json @@ -402,8 +402,8 @@ "map": { "kind": "Optional", "types": [ - "com.fluidframework.leaf.string", - "com.fluidframework.leaf.number" + "com.fluidframework.leaf.number", + "com.fluidframework.leaf.string" ] } } diff --git a/packages/dds/tree/src/test/utils.ts b/packages/dds/tree/src/test/utils.ts index 36a372abae34..7a8f16cf2fc3 100644 --- a/packages/dds/tree/src/test/utils.ts +++ b/packages/dds/tree/src/test/utils.ts @@ -45,7 +45,6 @@ import { import { type ICodecFamily, type IJsonCodec, withSchemaValidation } from "../codec/index.js"; import { - AllowedUpdateType, type AnnouncedVisitor, type ChangeFamily, type ChangeFamilyEditor, @@ -81,14 +80,12 @@ import { mapCursorField, moveToDetachedField, revisionMetadataSourceFromInfo, - rootFieldKey, type Anchor, type AnchorNode, type AnchorSetRootEvents, type TreeStoredSchemaSubscription, type ITreeCursorSynchronous, CursorLocationType, - type MapTree, } from "../core/index.js"; import { cursorToJsonObject, @@ -150,6 +147,7 @@ import { TreeViewConfiguration, SchemaFactory, type InsertableTreeFieldFromImplicitField, + toStoredSchema, } from "../simple-tree/index.js"; import { type JsonCompatible, @@ -780,40 +778,19 @@ export const jsonSequenceRootSchema = new SchemaBuilderBase(FieldKinds.sequence, libraries: [jsonSchema], }).intoSchema(jsonRoot); -export const stringSequenceRootSchema = new SchemaBuilderBase(FieldKinds.sequence, { - libraries: [leaf.library], - scope: "StringSequenceRoot", -}).intoSchema(leaf.string); - export const numberSequenceRootSchema = new SchemaBuilderBase(FieldKinds.sequence, { libraries: [leaf.library], scope: "NumberSequenceRoot", }).intoSchema(leaf.number); -export const emptyJsonSequenceConfig = { - schema: jsonSequenceRootSchema, - allowedSchemaModifications: AllowedUpdateType.Initialize, - initialTree: [], -} satisfies InitializeAndSchematizeConfiguration; - -export const emptyStringSequenceConfig = { - schema: stringSequenceRootSchema, - allowedSchemaModifications: AllowedUpdateType.Initialize, - initialTree: [], -} satisfies InitializeAndSchematizeConfiguration; - /** - * If the root is an array, this creates a sequence field at the root instead of a JSON array node. - * - * If the root is not an array, a single item root sequence is used. + * Crates a tree using the Json domain with a required root field. */ -export function makeTreeFromJson(json: JsonCompatible[] | JsonCompatible): ITreeCheckout { - const cursors = (Array.isArray(json) ? json : [json]).map(singleJsonCursor); - const tree = checkoutWithContent({ - schema: intoStoredSchema(jsonSequenceRootSchema), - initialTree: cursors, +export function makeTreeFromJson(json: JsonCompatible): ITreeCheckout { + return checkoutWithContent({ + schema: toStoredSchema(JsonUnion), + initialTree: singleJsonCursor(json), }); - return tree; } export function toJsonableTree(tree: ITreeCheckout): JsonableTree[] { @@ -835,30 +812,6 @@ export function jsonTreeFromForest(forest: IForestSubscription): JsonCompatible[ return copy; } -/** - * Helper function to insert node at a given index. - * - * TODO: delete once the JSON editing API is ready for use. - * - * @param tree - The tree on which to perform the insert. - * @param index - The index in the root field at which to insert. - * @param value - The value of the inserted nodes. - */ -export function insert(tree: ITreeCheckout, index: number, ...values: string[]): void { - const fieldEditor = tree.editor.sequenceField({ field: rootFieldKey, parent: undefined }); - fieldEditor.insert( - index, - cursorForMapTreeField( - values.map((value): MapTree => ({ fields: new Map(), type: leaf.string.name, value })), - ), - ); -} - -export function remove(tree: ITreeCheckout, index: number, count: number): void { - const field = tree.editor.sequenceField({ parent: undefined, field: rootFieldKey }); - field.remove(index, count); -} - export function expectJsonTree( actual: ITreeCheckout | ITreeCheckout[], expected: JsonCompatible[],