@fluidframework/azure-end-to-end-tests 2.0.0-rc.5.0.0 → 2.1.0-274160

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,8 +5,8 @@
5
5
  import { strict as assert } from "node:assert";
6
6
  import { ConnectionState } from "@fluidframework/container-loader";
7
7
  import { timeoutPromise } from "@fluidframework/test-utils/internal";
8
- import { TreeViewConfiguration, SchemaFactory, } from "@fluidframework/tree";
9
- import { SharedTree } from "@fluidframework/tree/internal";
8
+ import { TreeViewConfiguration, SchemaFactory } from "@fluidframework/tree";
9
+ import { SharedTree, Tree, TreeStatus } from "@fluidframework/tree/internal";
10
10
  import { createAzureClient, createContainerFromPayload, getContainerIdFromPayloadResponse, } from "./AzureClientFactory.js";
11
11
  import * as ephemeralSummaryTrees from "./ephemeralSummaryTrees.js";
12
12
  import { getTestMatrix } from "./utils.js";
@@ -50,35 +50,41 @@ for (const testOpts of testMatrix) {
50
50
  beforeEach("createAzureClient", () => {
51
51
  client = createAzureClient();
52
52
  });
53
+ async function waitForConnection(container) {
54
+ if (container.connectionState !== ConnectionState.Connected) {
55
+ await timeoutPromise((resolve) => container.once("connected", () => resolve()), {
56
+ durationMs: connectTimeoutMs,
57
+ errorMsg: "container1 connect() timeout",
58
+ });
59
+ }
60
+ }
53
61
  /**
54
- * Scenario: test when an Azure Client container is created,
55
- * it can set the initial objects to SharedTree and do basic SharedTree ops.
56
- *
57
- * Expected behavior: an error should not be thrown nor should a rejected promise
58
- * be returned.
62
+ * Either creates a new azure client with a SharedTree, or loads an azure client from the existing summary tree (the "ephemeral" case).
59
63
  */
60
- it("can create a container with SharedTree and do basic ops", async () => {
64
+ async function createOrLoad(summaryTree) {
61
65
  let containerId;
62
- let container1;
63
66
  let treeData;
64
- if (isEphemeral) {
65
- const containerResponse = await createContainerFromPayload(ephemeralSummaryTrees.createContainerWithSharedTree, "test-user-id-1", "test-user-name-1");
66
- containerId = getContainerIdFromPayloadResponse(containerResponse);
67
- ({ container: container1 } = await client.getContainer(containerId, schema, "2"));
68
- treeData = container1.initialObjects.tree1.viewWith(treeConfiguration);
69
- }
70
- else {
71
- ({ container: container1 } = await client.createContainer(schema, "2"));
72
- treeData = container1.initialObjects.tree1.viewWith(treeConfiguration);
67
+ if (summaryTree === undefined) {
68
+ const { container } = await client.createContainer(schema, "2");
69
+ treeData = container.initialObjects.tree1.viewWith(treeConfiguration);
73
70
  treeData.initialize(new StringArray([]));
74
- containerId = await container1.attach();
71
+ containerId = await container.attach();
72
+ await waitForConnection(container);
75
73
  }
76
- if (container1.connectionState !== ConnectionState.Connected) {
77
- await timeoutPromise((resolve) => container1.once("connected", () => resolve()), {
78
- durationMs: connectTimeoutMs,
79
- errorMsg: "container1 connect() timeout",
80
- });
74
+ else {
75
+ const containerResponse = await createContainerFromPayload(summaryTree, "test-user-id-1", "test-user-name-1");
76
+ containerId = getContainerIdFromPayloadResponse(containerResponse);
77
+ const { container } = await client.getContainer(containerId, schema, "2");
78
+ treeData = container.initialObjects.tree1.viewWith(treeConfiguration);
79
+ await waitForConnection(container);
81
80
  }
81
+ return {
82
+ containerId,
83
+ treeData,
84
+ };
85
+ }
86
+ it("can create/load a container with SharedTree and do basic ops", async () => {
87
+ const { treeData } = await createOrLoad(isEphemeral ? ephemeralSummaryTrees.createContainerWithSharedTree : undefined);
82
88
  treeData.root.insertNew("test string 1");
83
89
  assert.strictEqual(treeData.root.length, 1);
84
90
  assert.strictEqual(treeData.root.at(0), "test string 1");
@@ -90,36 +96,9 @@ for (const testOpts of testMatrix) {
90
96
  assert.strictEqual(treeData.root.length, 1);
91
97
  assert.strictEqual(treeData.root.at(0), "test string 1");
92
98
  });
93
- /**
94
- * Scenario: test when an Azure Client container is created,
95
- * and it can be loaded by another container with SharedTree and do basic SharedTree ops.
96
- *
97
- * Expected behavior: an error should not be thrown nor should a rejected promise
98
- * be returned.
99
- */
100
99
  it("can create/load a container with SharedTree collaborate with basic ops", async () => {
101
- let containerId;
102
- let container1;
103
- let treeData1;
104
- if (isEphemeral) {
105
- const containerResponse = await createContainerFromPayload(ephemeralSummaryTrees.createLoadContainerWithSharedTree, "test-user-id-1", "test-user-name-1");
106
- containerId = getContainerIdFromPayloadResponse(containerResponse);
107
- ({ container: container1 } = await client.getContainer(containerId, schema, "2"));
108
- treeData1 = container1.initialObjects.tree1.viewWith(treeConfiguration);
109
- }
110
- else {
111
- ({ container: container1 } = await client.createContainer(schema, "2"));
112
- treeData1 = container1.initialObjects.tree1.viewWith(treeConfiguration);
113
- treeData1.initialize(new StringArray([]));
114
- containerId = await container1.attach();
115
- }
116
- if (container1.connectionState !== ConnectionState.Connected) {
117
- await timeoutPromise((resolve) => container1.once("connected", () => resolve()), {
118
- durationMs: connectTimeoutMs,
119
- errorMsg: "container1 connect() timeout",
120
- });
121
- }
122
- treeData1.root.insertNew("test string 1");
100
+ const { containerId, treeData } = await createOrLoad(isEphemeral ? ephemeralSummaryTrees.createLoadContainerWithSharedTree : undefined);
101
+ treeData.root.insertNew("test string 1");
123
102
  const resources = client.getContainer(containerId, schema, "2");
124
103
  await assert.doesNotReject(resources, () => true, "container cannot be retrieved from Azure Fluid Relay");
125
104
  const { container: container2 } = await resources;
@@ -134,6 +113,157 @@ for (const testOpts of testMatrix) {
134
113
  assert.strictEqual(treeData2.root.length, 1);
135
114
  assert.strictEqual(treeData2.root.at(0), "test string 1");
136
115
  });
116
+ if (!isEphemeral) {
117
+ {
118
+ class Nicknames extends sf.array("Nicknames", sf.string) {
119
+ }
120
+ class UserData extends sf.map("UserData", [sf.string, sf.number, sf.boolean]) {
121
+ }
122
+ class User extends sf.object("User", {
123
+ name: sf.string,
124
+ nicknames: Nicknames,
125
+ data: UserData,
126
+ }) {
127
+ }
128
+ it("can read and edit data", async () => {
129
+ const { container } = await client.createContainer(schema, "2");
130
+ await container.attach();
131
+ const view = container.initialObjects.tree1.viewWith(new TreeViewConfiguration({ schema: User, enableSchemaValidation: true }));
132
+ const tags = [
133
+ ["Age", 32],
134
+ ["Favorite Snack", "Potato Chips"],
135
+ ["Awake", true],
136
+ ];
137
+ view.initialize(new User({
138
+ name: "Pardes",
139
+ nicknames: ["Alex", "Duder"],
140
+ data: new Map(tags),
141
+ }));
142
+ const user = view.root;
143
+ // Read data
144
+ assert.equal(user.name, "Pardes");
145
+ assert.deepEqual([...user.nicknames], ["Alex", "Duder"]);
146
+ assert.equal(user.data.get("Age"), 32);
147
+ assert.equal(user.data.get("Favorite Snack"), "Potato Chips");
148
+ assert.equal(user.data.get("Awake"), true);
149
+ // Mutate data
150
+ user.name = "Pardesio";
151
+ user.nicknames.insertAt(1, "Alexp");
152
+ user.data.set("Awake", false);
153
+ user.data.set("Favorite Sport", "Ultimate Frisbee");
154
+ // Read mutated data
155
+ assert.equal(user.name, "Pardesio");
156
+ assert.deepEqual([...user.nicknames], ["Alex", "Alexp", "Duder"]);
157
+ assert.equal(user.data.get("Age"), 32);
158
+ assert.equal(user.data.get("Awake"), false);
159
+ assert.equal(user.data.get("Favorite Sport"), "Ultimate Frisbee");
160
+ });
161
+ it("can handle undo/redo and transactions", async () => {
162
+ const { container } = await client.createContainer(schema, "2");
163
+ await container.attach();
164
+ const view = container.initialObjects.tree1.viewWith(new TreeViewConfiguration({ schema: User, enableSchemaValidation: true }));
165
+ view.initialize(new User({
166
+ name: "Shrek",
167
+ nicknames: ["Ogre"],
168
+ data: new Map([["Color", "a5bf2e"]]),
169
+ }));
170
+ const user = view.root;
171
+ // Capture the Revertible so that changes can be undone
172
+ let revertible;
173
+ view.events.on("commitApplied", (_, getRevertible) => {
174
+ assert(getRevertible !== undefined);
175
+ revertible = getRevertible();
176
+ });
177
+ // Change a field, then revert the change
178
+ user.name = "Donkey";
179
+ assert.equal(user.name, "Donkey");
180
+ assert(revertible !== undefined);
181
+ revertible.revert();
182
+ assert.equal(user.name, "Shrek");
183
+ // Run a transaction which changes multiple fields, then revert it
184
+ Tree.runTransaction(user, (u) => {
185
+ u.name = "Donkey";
186
+ u.nicknames.removeRange();
187
+ u.data.set("Color", "8e8170");
188
+ });
189
+ assert.equal(user.name, "Donkey");
190
+ assert.equal(user.nicknames.length, 0);
191
+ assert.equal(user.data.get("Color"), "8e8170");
192
+ revertible.revert();
193
+ assert.equal(user.name, "Shrek");
194
+ assert.equal(user.nicknames[0], "Ogre");
195
+ assert.equal(user.data.get("Color"), "a5bf2e");
196
+ });
197
+ }
198
+ it("can use identifiers and the static Tree APIs", async () => {
199
+ class Widget extends sf.object("Widget", { id: sf.identifier }) {
200
+ }
201
+ const { container } = await client.createContainer(schema, "2");
202
+ await container.attach();
203
+ const view = container.initialObjects.tree1.viewWith(new TreeViewConfiguration({
204
+ schema: sf.array(Widget),
205
+ enableSchemaValidation: true,
206
+ }));
207
+ view.initialize([new Widget({}), new Widget({ id: "fidget" })]);
208
+ const widget = view.root.at(0);
209
+ assert(widget !== undefined);
210
+ const fidget = view.root.at(-1);
211
+ assert(fidget !== undefined);
212
+ // Test various Tree.* APIs and ensure they are working
213
+ assert.equal(Tree.contains(view.root, widget), true);
214
+ assert.equal(Tree.contains(fidget, widget), false);
215
+ assert.equal(Tree.is(fidget, Widget), true);
216
+ assert.equal(Tree.is(view.root, Widget), false);
217
+ assert.equal(Tree.key(widget), 0);
218
+ assert.equal(Tree.key(fidget), 1);
219
+ assert.equal(Tree.parent(widget), view.root);
220
+ assert.equal(Tree.schema(fidget), Widget);
221
+ assert.equal(typeof Tree.shortId(widget), "number");
222
+ assert.equal(Tree.shortId(fidget), "fidget");
223
+ assert.equal(Tree.status(widget), TreeStatus.InDocument);
224
+ });
225
+ it("can listen to events on a recursive tree", async () => {
226
+ class Doll extends sf.objectRecursive("Matryoshka", {
227
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
228
+ nested: sf.optionalRecursive([() => Doll]),
229
+ }) {
230
+ }
231
+ const { container } = await client.createContainer(schema, "2");
232
+ await container.attach();
233
+ const view = container.initialObjects.tree1.viewWith(new TreeViewConfiguration({ schema: Doll, enableSchemaValidation: true }));
234
+ // These nodes in the initial tree are unhydrated...
235
+ const depth1 = new Doll({ nested: new Doll({}) });
236
+ const depth0 = new Doll({ nested: depth1 });
237
+ view.initialize(depth0);
238
+ // ...and confirmed to be the same nodes we get when we read the tree after initialization
239
+ assert.equal(view.root, depth0);
240
+ assert.equal(view.root.nested, depth1);
241
+ // Record a list of the node events fired
242
+ const eventLog = [];
243
+ Tree.on(depth0, "nodeChanged", () => {
244
+ eventLog.push("depth0.nested changed");
245
+ });
246
+ Tree.on(depth1, "nodeChanged", () => {
247
+ eventLog.push("depth1.nested changed");
248
+ });
249
+ // This event registration happens on the unhydrated (not yet inserted) node
250
+ const newDepth2 = new Doll({});
251
+ Tree.on(newDepth2, "nodeChanged", () => {
252
+ eventLog.push("depth2.nested changed");
253
+ });
254
+ // Fire the events by doing mutations
255
+ depth1.nested = newDepth2; // "depth1.nested changed"
256
+ assert.equal(depth1.nested, newDepth2);
257
+ depth1.nested.nested = new Doll({}); // "depth2.nested changed"
258
+ depth0.nested = new Doll({}); // "depth0.nested changed"
259
+ // Ensure the events fired in the expected order
260
+ assert.deepEqual(eventLog, [
261
+ "depth1.nested changed",
262
+ "depth2.nested changed",
263
+ "depth0.nested changed",
264
+ ]);
265
+ });
266
+ }
137
267
  });
138
268
  }
139
269
  //# sourceMappingURL=tree.spec.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tree.spec.js","sourceRoot":"","sources":["../../src/test/tree.spec.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { strict as assert } from \"node:assert\";\n\nimport { AzureClient } from \"@fluidframework/azure-client\";\nimport { ConnectionState } from \"@fluidframework/container-loader\";\nimport { ContainerSchema, type IFluidContainer } from \"@fluidframework/fluid-static\";\nimport { timeoutPromise } from \"@fluidframework/test-utils/internal\";\nimport {\n\tTreeViewConfiguration,\n\tSchemaFactory,\n\ttype TreeView,\n\ttype ITree,\n} from \"@fluidframework/tree\";\nimport { SharedTree } from \"@fluidframework/tree/internal\";\nimport type { AxiosResponse } from \"axios\";\n\nimport {\n\tcreateAzureClient,\n\tcreateContainerFromPayload,\n\tgetContainerIdFromPayloadResponse,\n} from \"./AzureClientFactory.js\";\nimport * as ephemeralSummaryTrees from \"./ephemeralSummaryTrees.js\";\nimport { getTestMatrix } from \"./utils.js\";\n\nconst sf = new SchemaFactory(\"d302b84c-75f6-4ecd-9663-524f467013e3\");\n\n/**\n * Define a class that is an array of strings\n * This class is used to create an array in the SharedTree\n */\nclass StringArray extends sf.array(\"StringArray\", sf.string) {\n\t/**\n\t * Remove the first item in the list if the list is not empty\n\t */\n\tpublic removeFirst(): void {\n\t\tif (this.length > 0) this.removeAt(0);\n\t}\n\n\t/**\n\t * Add an item to the beginning of the list\n\t */\n\tpublic insertNew(str: string): void {\n\t\tthis.insertAtStart(str);\n\t}\n}\n\n/**\n * This object is passed into the SharedTree via the schematize method.\n */\nconst treeConfiguration = new TreeViewConfiguration(\n\t// Specify the root type - StringArray.\n\t{ schema: StringArray },\n);\n\nconst testMatrix = getTestMatrix();\nfor (const testOpts of testMatrix) {\n\tdescribe(`SharedTree with AzureClient (${testOpts.variant})`, () => {\n\t\tconst connectTimeoutMs = 10_000;\n\t\tconst isEphemeral: boolean = testOpts.options.isEphemeral;\n\t\tlet client: AzureClient;\n\t\tconst schema = {\n\t\t\tinitialObjects: {\n\t\t\t\ttree1: SharedTree,\n\t\t\t},\n\t\t} satisfies ContainerSchema;\n\n\t\tbeforeEach(\"createAzureClient\", () => {\n\t\t\tclient = createAzureClient();\n\t\t});\n\n\t\t/**\n\t\t * Scenario: test when an Azure Client container is created,\n\t\t * it can set the initial objects to SharedTree and do basic SharedTree ops.\n\t\t *\n\t\t * Expected behavior: an error should not be thrown nor should a rejected promise\n\t\t * be returned.\n\t\t */\n\t\tit(\"can create a container with SharedTree and do basic ops\", async () => {\n\t\t\tlet containerId: string;\n\t\t\tlet container1: IFluidContainer;\n\t\t\tlet treeData: TreeView<typeof StringArray>;\n\t\t\tif (isEphemeral) {\n\t\t\t\tconst containerResponse: AxiosResponse | undefined = await createContainerFromPayload(\n\t\t\t\t\tephemeralSummaryTrees.createContainerWithSharedTree,\n\t\t\t\t\t\"test-user-id-1\",\n\t\t\t\t\t\"test-user-name-1\",\n\t\t\t\t);\n\t\t\t\tcontainerId = getContainerIdFromPayloadResponse(containerResponse);\n\t\t\t\t({ container: container1 } = await client.getContainer(containerId, schema, \"2\"));\n\n\t\t\t\ttreeData = (container1.initialObjects.tree1 as ITree).viewWith(\n\t\t\t\t\ttreeConfiguration, // This is defined in schema.ts\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\t({ container: container1 } = await client.createContainer(schema, \"2\"));\n\n\t\t\t\ttreeData = (container1.initialObjects.tree1 as ITree).viewWith(\n\t\t\t\t\ttreeConfiguration, // This is defined in schema.ts\n\t\t\t\t);\n\t\t\t\ttreeData.initialize(new StringArray([]));\n\n\t\t\t\tcontainerId = await container1.attach();\n\t\t\t}\n\n\t\t\tif (container1.connectionState !== ConnectionState.Connected) {\n\t\t\t\tawait timeoutPromise((resolve) => container1.once(\"connected\", () => resolve()), {\n\t\t\t\t\tdurationMs: connectTimeoutMs,\n\t\t\t\t\terrorMsg: \"container1 connect() timeout\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\ttreeData.root.insertNew(\"test string 1\");\n\t\t\tassert.strictEqual(treeData.root.length, 1);\n\t\t\tassert.strictEqual(treeData.root.at(0), \"test string 1\");\n\n\t\t\ttreeData.root.insertNew(\"test string 2\");\n\t\t\tassert.strictEqual(treeData.root.length, 2);\n\t\t\tassert.strictEqual(treeData.root.at(0), \"test string 2\");\n\t\t\tassert.strictEqual(treeData.root.at(1), \"test string 1\");\n\n\t\t\ttreeData.root.removeFirst();\n\t\t\tassert.strictEqual(treeData.root.length, 1);\n\t\t\tassert.strictEqual(treeData.root.at(0), \"test string 1\");\n\t\t});\n\n\t\t/**\n\t\t * Scenario: test when an Azure Client container is created,\n\t\t * and it can be loaded by another container with SharedTree and do basic SharedTree ops.\n\t\t *\n\t\t * Expected behavior: an error should not be thrown nor should a rejected promise\n\t\t * be returned.\n\t\t */\n\t\tit(\"can create/load a container with SharedTree collaborate with basic ops\", async () => {\n\t\t\tlet containerId: string;\n\t\t\tlet container1: IFluidContainer;\n\t\t\tlet treeData1: TreeView<typeof StringArray>;\n\t\t\tif (isEphemeral) {\n\t\t\t\tconst containerResponse: AxiosResponse | undefined = await createContainerFromPayload(\n\t\t\t\t\tephemeralSummaryTrees.createLoadContainerWithSharedTree,\n\t\t\t\t\t\"test-user-id-1\",\n\t\t\t\t\t\"test-user-name-1\",\n\t\t\t\t);\n\t\t\t\tcontainerId = getContainerIdFromPayloadResponse(containerResponse);\n\t\t\t\t({ container: container1 } = await client.getContainer(containerId, schema, \"2\"));\n\n\t\t\t\ttreeData1 = (container1.initialObjects.tree1 as ITree).viewWith(treeConfiguration);\n\t\t\t} else {\n\t\t\t\t({ container: container1 } = await client.createContainer(schema, \"2\"));\n\n\t\t\t\ttreeData1 = (container1.initialObjects.tree1 as ITree).viewWith(treeConfiguration);\n\t\t\t\ttreeData1.initialize(new StringArray([]));\n\n\t\t\t\tcontainerId = await container1.attach();\n\t\t\t}\n\n\t\t\tif (container1.connectionState !== ConnectionState.Connected) {\n\t\t\t\tawait timeoutPromise((resolve) => container1.once(\"connected\", () => resolve()), {\n\t\t\t\t\tdurationMs: connectTimeoutMs,\n\t\t\t\t\terrorMsg: \"container1 connect() timeout\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\ttreeData1.root.insertNew(\"test string 1\");\n\n\t\t\tconst resources = client.getContainer(containerId, schema, \"2\");\n\t\t\tawait assert.doesNotReject(\n\t\t\t\tresources,\n\t\t\t\t() => true,\n\t\t\t\t\"container cannot be retrieved from Azure Fluid Relay\",\n\t\t\t);\n\t\t\tconst { container: container2 } = await resources;\n\t\t\tassert.deepStrictEqual(\n\t\t\t\tObject.keys(container2.initialObjects),\n\t\t\t\tObject.keys(schema.initialObjects),\n\t\t\t);\n\n\t\t\tif (container2.connectionState !== ConnectionState.Connected) {\n\t\t\t\tawait timeoutPromise((resolve) => container2.once(\"connected\", () => resolve()), {\n\t\t\t\t\tdurationMs: connectTimeoutMs,\n\t\t\t\t\terrorMsg: \"container2 connect() timeout\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst treeData2 = container2.initialObjects.tree1.viewWith(treeConfiguration);\n\t\t\tassert.strictEqual(treeData2.root.length, 1);\n\t\t\tassert.strictEqual(treeData2.root.at(0), \"test string 1\");\n\t\t});\n\t});\n}\n"]}
1
+ {"version":3,"file":"tree.spec.js","sourceRoot":"","sources":["../../src/test/tree.spec.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { strict as assert } from \"node:assert\";\n\nimport { AzureClient } from \"@fluidframework/azure-client\";\nimport { ConnectionState } from \"@fluidframework/container-loader\";\nimport { ContainerSchema, type IFluidContainer } from \"@fluidframework/fluid-static\";\nimport { timeoutPromise } from \"@fluidframework/test-utils/internal\";\nimport { TreeViewConfiguration, SchemaFactory, type TreeView } from \"@fluidframework/tree\";\nimport { SharedTree, Tree, TreeStatus, type Revertible } from \"@fluidframework/tree/internal\";\nimport type { AxiosResponse } from \"axios\";\n\nimport {\n\tcreateAzureClient,\n\tcreateContainerFromPayload,\n\tgetContainerIdFromPayloadResponse,\n} from \"./AzureClientFactory.js\";\nimport * as ephemeralSummaryTrees from \"./ephemeralSummaryTrees.js\";\nimport { getTestMatrix } from \"./utils.js\";\n\nconst sf = new SchemaFactory(\"d302b84c-75f6-4ecd-9663-524f467013e3\");\n\n/**\n * Define a class that is an array of strings\n * This class is used to create an array in the SharedTree\n */\nclass StringArray extends sf.array(\"StringArray\", sf.string) {\n\t/**\n\t * Remove the first item in the list if the list is not empty\n\t */\n\tpublic removeFirst(): void {\n\t\tif (this.length > 0) this.removeAt(0);\n\t}\n\n\t/**\n\t * Add an item to the beginning of the list\n\t */\n\tpublic insertNew(str: string): void {\n\t\tthis.insertAtStart(str);\n\t}\n}\n\n/**\n * This object is passed into the SharedTree via the schematize method.\n */\nconst treeConfiguration = new TreeViewConfiguration(\n\t// Specify the root type - StringArray.\n\t{ schema: StringArray },\n);\n\nconst testMatrix = getTestMatrix();\nfor (const testOpts of testMatrix) {\n\tdescribe(`SharedTree with AzureClient (${testOpts.variant})`, () => {\n\t\tconst connectTimeoutMs = 10_000;\n\t\tconst isEphemeral: boolean = testOpts.options.isEphemeral;\n\t\tlet client: AzureClient;\n\t\tconst schema = {\n\t\t\tinitialObjects: {\n\t\t\t\ttree1: SharedTree,\n\t\t\t},\n\t\t} satisfies ContainerSchema;\n\n\t\tbeforeEach(\"createAzureClient\", () => {\n\t\t\tclient = createAzureClient();\n\t\t});\n\n\t\tasync function waitForConnection(container: IFluidContainer): Promise<void> {\n\t\t\tif (container.connectionState !== ConnectionState.Connected) {\n\t\t\t\tawait timeoutPromise((resolve) => container.once(\"connected\", () => resolve()), {\n\t\t\t\t\tdurationMs: connectTimeoutMs,\n\t\t\t\t\terrorMsg: \"container1 connect() timeout\",\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Either creates a new azure client with a SharedTree, or loads an azure client from the existing summary tree (the \"ephemeral\" case).\n\t\t */\n\t\tasync function createOrLoad(\n\t\t\tsummaryTree?: (typeof ephemeralSummaryTrees)[keyof typeof ephemeralSummaryTrees],\n\t\t): Promise<{ containerId: string; treeData: TreeView<typeof StringArray> }> {\n\t\t\tlet containerId: string;\n\t\t\tlet treeData: TreeView<typeof StringArray>;\n\n\t\t\tif (summaryTree === undefined) {\n\t\t\t\tconst { container } = await client.createContainer(schema, \"2\");\n\t\t\t\ttreeData = container.initialObjects.tree1.viewWith(treeConfiguration);\n\t\t\t\ttreeData.initialize(new StringArray([]));\n\t\t\t\tcontainerId = await container.attach();\n\t\t\t\tawait waitForConnection(container);\n\t\t\t} else {\n\t\t\t\tconst containerResponse: AxiosResponse | undefined = await createContainerFromPayload(\n\t\t\t\t\tsummaryTree,\n\t\t\t\t\t\"test-user-id-1\",\n\t\t\t\t\t\"test-user-name-1\",\n\t\t\t\t);\n\n\t\t\t\tcontainerId = getContainerIdFromPayloadResponse(containerResponse);\n\t\t\t\tconst { container } = await client.getContainer(containerId, schema, \"2\");\n\t\t\t\ttreeData = container.initialObjects.tree1.viewWith(treeConfiguration);\n\t\t\t\tawait waitForConnection(container);\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tcontainerId,\n\t\t\t\ttreeData,\n\t\t\t};\n\t\t}\n\n\t\tit(\"can create/load a container with SharedTree and do basic ops\", async () => {\n\t\t\tconst { treeData } = await createOrLoad(\n\t\t\t\tisEphemeral ? ephemeralSummaryTrees.createContainerWithSharedTree : undefined,\n\t\t\t);\n\n\t\t\ttreeData.root.insertNew(\"test string 1\");\n\t\t\tassert.strictEqual(treeData.root.length, 1);\n\t\t\tassert.strictEqual(treeData.root.at(0), \"test string 1\");\n\n\t\t\ttreeData.root.insertNew(\"test string 2\");\n\t\t\tassert.strictEqual(treeData.root.length, 2);\n\t\t\tassert.strictEqual(treeData.root.at(0), \"test string 2\");\n\t\t\tassert.strictEqual(treeData.root.at(1), \"test string 1\");\n\n\t\t\ttreeData.root.removeFirst();\n\t\t\tassert.strictEqual(treeData.root.length, 1);\n\t\t\tassert.strictEqual(treeData.root.at(0), \"test string 1\");\n\t\t});\n\n\t\tit(\"can create/load a container with SharedTree collaborate with basic ops\", async () => {\n\t\t\tconst { containerId, treeData } = await createOrLoad(\n\t\t\t\tisEphemeral ? ephemeralSummaryTrees.createLoadContainerWithSharedTree : undefined,\n\t\t\t);\n\n\t\t\ttreeData.root.insertNew(\"test string 1\");\n\n\t\t\tconst resources = client.getContainer(containerId, schema, \"2\");\n\t\t\tawait assert.doesNotReject(\n\t\t\t\tresources,\n\t\t\t\t() => true,\n\t\t\t\t\"container cannot be retrieved from Azure Fluid Relay\",\n\t\t\t);\n\t\t\tconst { container: container2 } = await resources;\n\t\t\tassert.deepStrictEqual(\n\t\t\t\tObject.keys(container2.initialObjects),\n\t\t\t\tObject.keys(schema.initialObjects),\n\t\t\t);\n\n\t\t\tif (container2.connectionState !== ConnectionState.Connected) {\n\t\t\t\tawait timeoutPromise((resolve) => container2.once(\"connected\", () => resolve()), {\n\t\t\t\t\tdurationMs: connectTimeoutMs,\n\t\t\t\t\terrorMsg: \"container2 connect() timeout\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst treeData2 = container2.initialObjects.tree1.viewWith(treeConfiguration);\n\t\t\tassert.strictEqual(treeData2.root.length, 1);\n\t\t\tassert.strictEqual(treeData2.root.at(0), \"test string 1\");\n\t\t});\n\n\t\tif (!isEphemeral) {\n\t\t\t{\n\t\t\t\tclass Nicknames extends sf.array(\"Nicknames\", sf.string) {}\n\t\t\t\tclass UserData extends sf.map(\"UserData\", [sf.string, sf.number, sf.boolean]) {}\n\t\t\t\tclass User extends sf.object(\"User\", {\n\t\t\t\t\tname: sf.string,\n\t\t\t\t\tnicknames: Nicknames,\n\t\t\t\t\tdata: UserData,\n\t\t\t\t}) {}\n\n\t\t\t\tit(\"can read and edit data\", async () => {\n\t\t\t\t\tconst { container } = await client.createContainer(schema, \"2\");\n\t\t\t\t\tawait container.attach();\n\t\t\t\t\tconst view = container.initialObjects.tree1.viewWith(\n\t\t\t\t\t\tnew TreeViewConfiguration({ schema: User, enableSchemaValidation: true }),\n\t\t\t\t\t);\n\n\t\t\t\t\tconst tags: [string, string | number | boolean][] = [\n\t\t\t\t\t\t[\"Age\", 32],\n\t\t\t\t\t\t[\"Favorite Snack\", \"Potato Chips\"],\n\t\t\t\t\t\t[\"Awake\", true],\n\t\t\t\t\t];\n\n\t\t\t\t\tview.initialize(\n\t\t\t\t\t\tnew User({\n\t\t\t\t\t\t\tname: \"Pardes\",\n\t\t\t\t\t\t\tnicknames: [\"Alex\", \"Duder\"],\n\t\t\t\t\t\t\tdata: new Map(tags),\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\n\t\t\t\t\tconst user = view.root;\n\t\t\t\t\t// Read data\n\t\t\t\t\tassert.equal(user.name, \"Pardes\");\n\t\t\t\t\tassert.deepEqual([...user.nicknames], [\"Alex\", \"Duder\"]);\n\t\t\t\t\tassert.equal(user.data.get(\"Age\"), 32);\n\t\t\t\t\tassert.equal(user.data.get(\"Favorite Snack\"), \"Potato Chips\");\n\t\t\t\t\tassert.equal(user.data.get(\"Awake\"), true);\n\t\t\t\t\t// Mutate data\n\t\t\t\t\tuser.name = \"Pardesio\";\n\t\t\t\t\tuser.nicknames.insertAt(1, \"Alexp\");\n\t\t\t\t\tuser.data.set(\"Awake\", false);\n\t\t\t\t\tuser.data.set(\"Favorite Sport\", \"Ultimate Frisbee\");\n\t\t\t\t\t// Read mutated data\n\t\t\t\t\tassert.equal(user.name, \"Pardesio\");\n\t\t\t\t\tassert.deepEqual([...user.nicknames], [\"Alex\", \"Alexp\", \"Duder\"]);\n\t\t\t\t\tassert.equal(user.data.get(\"Age\"), 32);\n\t\t\t\t\tassert.equal(user.data.get(\"Awake\"), false);\n\t\t\t\t\tassert.equal(user.data.get(\"Favorite Sport\"), \"Ultimate Frisbee\");\n\t\t\t\t});\n\n\t\t\t\tit(\"can handle undo/redo and transactions\", async () => {\n\t\t\t\t\tconst { container } = await client.createContainer(schema, \"2\");\n\t\t\t\t\tawait container.attach();\n\t\t\t\t\tconst view = container.initialObjects.tree1.viewWith(\n\t\t\t\t\t\tnew TreeViewConfiguration({ schema: User, enableSchemaValidation: true }),\n\t\t\t\t\t);\n\n\t\t\t\t\tview.initialize(\n\t\t\t\t\t\tnew User({\n\t\t\t\t\t\t\tname: \"Shrek\",\n\t\t\t\t\t\t\tnicknames: [\"Ogre\"],\n\t\t\t\t\t\t\tdata: new Map([[\"Color\", \"a5bf2e\"]]),\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\n\t\t\t\t\tconst user = view.root;\n\t\t\t\t\t// Capture the Revertible so that changes can be undone\n\t\t\t\t\tlet revertible: Revertible | undefined;\n\t\t\t\t\tview.events.on(\"commitApplied\", (_, getRevertible) => {\n\t\t\t\t\t\tassert(getRevertible !== undefined);\n\t\t\t\t\t\trevertible = getRevertible();\n\t\t\t\t\t});\n\t\t\t\t\t// Change a field, then revert the change\n\t\t\t\t\tuser.name = \"Donkey\";\n\t\t\t\t\tassert.equal(user.name, \"Donkey\");\n\t\t\t\t\tassert(revertible !== undefined);\n\t\t\t\t\trevertible.revert();\n\t\t\t\t\tassert.equal(user.name, \"Shrek\");\n\t\t\t\t\t// Run a transaction which changes multiple fields, then revert it\n\t\t\t\t\tTree.runTransaction(user, (u) => {\n\t\t\t\t\t\tu.name = \"Donkey\";\n\t\t\t\t\t\tu.nicknames.removeRange();\n\t\t\t\t\t\tu.data.set(\"Color\", \"8e8170\");\n\t\t\t\t\t});\n\n\t\t\t\t\tassert.equal(user.name, \"Donkey\");\n\t\t\t\t\tassert.equal(user.nicknames.length, 0);\n\t\t\t\t\tassert.equal(user.data.get(\"Color\"), \"8e8170\");\n\t\t\t\t\trevertible.revert();\n\t\t\t\t\tassert.equal(user.name, \"Shrek\");\n\t\t\t\t\tassert.equal(user.nicknames[0], \"Ogre\");\n\t\t\t\t\tassert.equal(user.data.get(\"Color\"), \"a5bf2e\");\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tit(\"can use identifiers and the static Tree APIs\", async () => {\n\t\t\t\tclass Widget extends sf.object(\"Widget\", { id: sf.identifier }) {}\n\n\t\t\t\tconst { container } = await client.createContainer(schema, \"2\");\n\t\t\t\tawait container.attach();\n\t\t\t\tconst view = container.initialObjects.tree1.viewWith(\n\t\t\t\t\tnew TreeViewConfiguration({\n\t\t\t\t\t\tschema: sf.array(Widget),\n\t\t\t\t\t\tenableSchemaValidation: true,\n\t\t\t\t\t}),\n\t\t\t\t);\n\n\t\t\t\tview.initialize([new Widget({}), new Widget({ id: \"fidget\" })]);\n\n\t\t\t\tconst widget = view.root.at(0);\n\t\t\t\tassert(widget !== undefined);\n\t\t\t\tconst fidget = view.root.at(-1);\n\t\t\t\tassert(fidget !== undefined);\n\t\t\t\t// Test various Tree.* APIs and ensure they are working\n\t\t\t\tassert.equal(Tree.contains(view.root, widget), true);\n\t\t\t\tassert.equal(Tree.contains(fidget, widget), false);\n\t\t\t\tassert.equal(Tree.is(fidget, Widget), true);\n\t\t\t\tassert.equal(Tree.is(view.root, Widget), false);\n\t\t\t\tassert.equal(Tree.key(widget), 0);\n\t\t\t\tassert.equal(Tree.key(fidget), 1);\n\t\t\t\tassert.equal(Tree.parent(widget), view.root);\n\t\t\t\tassert.equal(Tree.schema(fidget), Widget);\n\t\t\t\tassert.equal(typeof Tree.shortId(widget), \"number\");\n\t\t\t\tassert.equal(Tree.shortId(fidget), \"fidget\");\n\t\t\t\tassert.equal(Tree.status(widget), TreeStatus.InDocument);\n\t\t\t});\n\n\t\t\tit(\"can listen to events on a recursive tree\", async () => {\n\t\t\t\tclass Doll extends sf.objectRecursive(\"Matryoshka\", {\n\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/explicit-function-return-type\n\t\t\t\t\tnested: sf.optionalRecursive([() => Doll]),\n\t\t\t\t}) {}\n\n\t\t\t\tconst { container } = await client.createContainer(schema, \"2\");\n\t\t\t\tawait container.attach();\n\t\t\t\tconst view = container.initialObjects.tree1.viewWith(\n\t\t\t\t\tnew TreeViewConfiguration({ schema: Doll, enableSchemaValidation: true }),\n\t\t\t\t);\n\n\t\t\t\t// These nodes in the initial tree are unhydrated...\n\t\t\t\tconst depth1 = new Doll({ nested: new Doll({}) });\n\t\t\t\tconst depth0 = new Doll({ nested: depth1 });\n\t\t\t\tview.initialize(depth0);\n\t\t\t\t// ...and confirmed to be the same nodes we get when we read the tree after initialization\n\t\t\t\tassert.equal(view.root, depth0);\n\t\t\t\tassert.equal(view.root.nested, depth1);\n\t\t\t\t// Record a list of the node events fired\n\t\t\t\tconst eventLog: string[] = [];\n\t\t\t\tTree.on(depth0, \"nodeChanged\", () => {\n\t\t\t\t\teventLog.push(\"depth0.nested changed\");\n\t\t\t\t});\n\t\t\t\tTree.on(depth1, \"nodeChanged\", () => {\n\t\t\t\t\teventLog.push(\"depth1.nested changed\");\n\t\t\t\t});\n\t\t\t\t// This event registration happens on the unhydrated (not yet inserted) node\n\t\t\t\tconst newDepth2 = new Doll({});\n\t\t\t\tTree.on(newDepth2, \"nodeChanged\", () => {\n\t\t\t\t\teventLog.push(\"depth2.nested changed\");\n\t\t\t\t});\n\t\t\t\t// Fire the events by doing mutations\n\t\t\t\tdepth1.nested = newDepth2; // \"depth1.nested changed\"\n\t\t\t\tassert.equal(depth1.nested, newDepth2);\n\t\t\t\tdepth1.nested.nested = new Doll({}); // \"depth2.nested changed\"\n\t\t\t\tdepth0.nested = new Doll({}); // \"depth0.nested changed\"\n\t\t\t\t// Ensure the events fired in the expected order\n\t\t\t\tassert.deepEqual(eventLog, [\n\t\t\t\t\t\"depth1.nested changed\",\n\t\t\t\t\t\"depth2.nested changed\",\n\t\t\t\t\t\"depth0.nested changed\",\n\t\t\t\t]);\n\t\t\t});\n\t\t}\n\t});\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/azure-end-to-end-tests",
3
- "version": "2.0.0-rc.5.0.0",
3
+ "version": "2.1.0-274160",
4
4
  "description": "Azure client end to end tests",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -33,27 +33,27 @@
33
33
  "temp-directory": "nyc/.nyc_output"
34
34
  },
35
35
  "dependencies": {
36
- "@fluid-experimental/data-objects": ">=2.0.0-rc.5.0.0 <2.0.0-rc.5.1.0",
37
- "@fluid-internal/client-utils": ">=2.0.0-rc.5.0.0 <2.0.0-rc.5.1.0",
38
- "@fluid-internal/mocha-test-setup": ">=2.0.0-rc.5.0.0 <2.0.0-rc.5.1.0",
39
- "@fluidframework/aqueduct": ">=2.0.0-rc.5.0.0 <2.0.0-rc.5.1.0",
40
- "@fluidframework/azure-client": ">=2.0.0-rc.5.0.0 <2.0.0-rc.5.1.0",
36
+ "@fluid-experimental/data-objects": "2.1.0-274160",
37
+ "@fluid-internal/client-utils": "2.1.0-274160",
38
+ "@fluid-internal/mocha-test-setup": "2.1.0-274160",
39
+ "@fluidframework/aqueduct": "2.1.0-274160",
40
+ "@fluidframework/azure-client": "2.1.0-274160",
41
41
  "@fluidframework/azure-client-legacy": "npm:@fluidframework/azure-client@^1.2.0",
42
- "@fluidframework/container-definitions": ">=2.0.0-rc.5.0.0 <2.0.0-rc.5.1.0",
43
- "@fluidframework/container-loader": ">=2.0.0-rc.5.0.0 <2.0.0-rc.5.1.0",
44
- "@fluidframework/core-interfaces": ">=2.0.0-rc.5.0.0 <2.0.0-rc.5.1.0",
45
- "@fluidframework/counter": ">=2.0.0-rc.5.0.0 <2.0.0-rc.5.1.0",
46
- "@fluidframework/datastore-definitions": ">=2.0.0-rc.5.0.0 <2.0.0-rc.5.1.0",
47
- "@fluidframework/fluid-static": ">=2.0.0-rc.5.0.0 <2.0.0-rc.5.1.0",
48
- "@fluidframework/map": ">=2.0.0-rc.5.0.0 <2.0.0-rc.5.1.0",
42
+ "@fluidframework/container-definitions": "2.1.0-274160",
43
+ "@fluidframework/container-loader": "2.1.0-274160",
44
+ "@fluidframework/core-interfaces": "2.1.0-274160",
45
+ "@fluidframework/counter": "2.1.0-274160",
46
+ "@fluidframework/datastore-definitions": "2.1.0-274160",
47
+ "@fluidframework/fluid-static": "2.1.0-274160",
48
+ "@fluidframework/map": "2.1.0-274160",
49
49
  "@fluidframework/map-legacy": "npm:@fluidframework/map@^1.4.0",
50
- "@fluidframework/matrix": ">=2.0.0-rc.5.0.0 <2.0.0-rc.5.1.0",
51
- "@fluidframework/runtime-definitions": ">=2.0.0-rc.5.0.0 <2.0.0-rc.5.1.0",
52
- "@fluidframework/sequence": ">=2.0.0-rc.5.0.0 <2.0.0-rc.5.1.0",
53
- "@fluidframework/telemetry-utils": ">=2.0.0-rc.5.0.0 <2.0.0-rc.5.1.0",
54
- "@fluidframework/test-runtime-utils": ">=2.0.0-rc.5.0.0 <2.0.0-rc.5.1.0",
55
- "@fluidframework/test-utils": ">=2.0.0-rc.5.0.0 <2.0.0-rc.5.1.0",
56
- "@fluidframework/tree": ">=2.0.0-rc.5.0.0 <2.0.0-rc.5.1.0",
50
+ "@fluidframework/matrix": "2.1.0-274160",
51
+ "@fluidframework/runtime-definitions": "2.1.0-274160",
52
+ "@fluidframework/sequence": "2.1.0-274160",
53
+ "@fluidframework/telemetry-utils": "2.1.0-274160",
54
+ "@fluidframework/test-runtime-utils": "2.1.0-274160",
55
+ "@fluidframework/test-utils": "2.1.0-274160",
56
+ "@fluidframework/tree": "2.1.0-274160",
57
57
  "axios": "^1.6.2",
58
58
  "cross-env": "^7.0.3",
59
59
  "mocha": "^10.2.0",
@@ -9,13 +9,8 @@ import { AzureClient } from "@fluidframework/azure-client";
9
9
  import { ConnectionState } from "@fluidframework/container-loader";
10
10
  import { ContainerSchema, type IFluidContainer } from "@fluidframework/fluid-static";
11
11
  import { timeoutPromise } from "@fluidframework/test-utils/internal";
12
- import {
13
- TreeViewConfiguration,
14
- SchemaFactory,
15
- type TreeView,
16
- type ITree,
17
- } from "@fluidframework/tree";
18
- import { SharedTree } from "@fluidframework/tree/internal";
12
+ import { TreeViewConfiguration, SchemaFactory, type TreeView } from "@fluidframework/tree";
13
+ import { SharedTree, Tree, TreeStatus, type Revertible } from "@fluidframework/tree/internal";
19
14
  import type { AxiosResponse } from "axios";
20
15
 
21
16
  import {
@@ -72,46 +67,53 @@ for (const testOpts of testMatrix) {
72
67
  client = createAzureClient();
73
68
  });
74
69
 
70
+ async function waitForConnection(container: IFluidContainer): Promise<void> {
71
+ if (container.connectionState !== ConnectionState.Connected) {
72
+ await timeoutPromise((resolve) => container.once("connected", () => resolve()), {
73
+ durationMs: connectTimeoutMs,
74
+ errorMsg: "container1 connect() timeout",
75
+ });
76
+ }
77
+ }
78
+
75
79
  /**
76
- * Scenario: test when an Azure Client container is created,
77
- * it can set the initial objects to SharedTree and do basic SharedTree ops.
78
- *
79
- * Expected behavior: an error should not be thrown nor should a rejected promise
80
- * be returned.
80
+ * Either creates a new azure client with a SharedTree, or loads an azure client from the existing summary tree (the "ephemeral" case).
81
81
  */
82
- it("can create a container with SharedTree and do basic ops", async () => {
82
+ async function createOrLoad(
83
+ summaryTree?: (typeof ephemeralSummaryTrees)[keyof typeof ephemeralSummaryTrees],
84
+ ): Promise<{ containerId: string; treeData: TreeView<typeof StringArray> }> {
83
85
  let containerId: string;
84
- let container1: IFluidContainer;
85
86
  let treeData: TreeView<typeof StringArray>;
86
- if (isEphemeral) {
87
+
88
+ if (summaryTree === undefined) {
89
+ const { container } = await client.createContainer(schema, "2");
90
+ treeData = container.initialObjects.tree1.viewWith(treeConfiguration);
91
+ treeData.initialize(new StringArray([]));
92
+ containerId = await container.attach();
93
+ await waitForConnection(container);
94
+ } else {
87
95
  const containerResponse: AxiosResponse | undefined = await createContainerFromPayload(
88
- ephemeralSummaryTrees.createContainerWithSharedTree,
96
+ summaryTree,
89
97
  "test-user-id-1",
90
98
  "test-user-name-1",
91
99
  );
92
- containerId = getContainerIdFromPayloadResponse(containerResponse);
93
- ({ container: container1 } = await client.getContainer(containerId, schema, "2"));
94
-
95
- treeData = (container1.initialObjects.tree1 as ITree).viewWith(
96
- treeConfiguration, // This is defined in schema.ts
97
- );
98
- } else {
99
- ({ container: container1 } = await client.createContainer(schema, "2"));
100
100
 
101
- treeData = (container1.initialObjects.tree1 as ITree).viewWith(
102
- treeConfiguration, // This is defined in schema.ts
103
- );
104
- treeData.initialize(new StringArray([]));
105
-
106
- containerId = await container1.attach();
101
+ containerId = getContainerIdFromPayloadResponse(containerResponse);
102
+ const { container } = await client.getContainer(containerId, schema, "2");
103
+ treeData = container.initialObjects.tree1.viewWith(treeConfiguration);
104
+ await waitForConnection(container);
107
105
  }
108
106
 
109
- if (container1.connectionState !== ConnectionState.Connected) {
110
- await timeoutPromise((resolve) => container1.once("connected", () => resolve()), {
111
- durationMs: connectTimeoutMs,
112
- errorMsg: "container1 connect() timeout",
113
- });
114
- }
107
+ return {
108
+ containerId,
109
+ treeData,
110
+ };
111
+ }
112
+
113
+ it("can create/load a container with SharedTree and do basic ops", async () => {
114
+ const { treeData } = await createOrLoad(
115
+ isEphemeral ? ephemeralSummaryTrees.createContainerWithSharedTree : undefined,
116
+ );
115
117
 
116
118
  treeData.root.insertNew("test string 1");
117
119
  assert.strictEqual(treeData.root.length, 1);
@@ -127,44 +129,12 @@ for (const testOpts of testMatrix) {
127
129
  assert.strictEqual(treeData.root.at(0), "test string 1");
128
130
  });
129
131
 
130
- /**
131
- * Scenario: test when an Azure Client container is created,
132
- * and it can be loaded by another container with SharedTree and do basic SharedTree ops.
133
- *
134
- * Expected behavior: an error should not be thrown nor should a rejected promise
135
- * be returned.
136
- */
137
132
  it("can create/load a container with SharedTree collaborate with basic ops", async () => {
138
- let containerId: string;
139
- let container1: IFluidContainer;
140
- let treeData1: TreeView<typeof StringArray>;
141
- if (isEphemeral) {
142
- const containerResponse: AxiosResponse | undefined = await createContainerFromPayload(
143
- ephemeralSummaryTrees.createLoadContainerWithSharedTree,
144
- "test-user-id-1",
145
- "test-user-name-1",
146
- );
147
- containerId = getContainerIdFromPayloadResponse(containerResponse);
148
- ({ container: container1 } = await client.getContainer(containerId, schema, "2"));
149
-
150
- treeData1 = (container1.initialObjects.tree1 as ITree).viewWith(treeConfiguration);
151
- } else {
152
- ({ container: container1 } = await client.createContainer(schema, "2"));
153
-
154
- treeData1 = (container1.initialObjects.tree1 as ITree).viewWith(treeConfiguration);
155
- treeData1.initialize(new StringArray([]));
156
-
157
- containerId = await container1.attach();
158
- }
159
-
160
- if (container1.connectionState !== ConnectionState.Connected) {
161
- await timeoutPromise((resolve) => container1.once("connected", () => resolve()), {
162
- durationMs: connectTimeoutMs,
163
- errorMsg: "container1 connect() timeout",
164
- });
165
- }
133
+ const { containerId, treeData } = await createOrLoad(
134
+ isEphemeral ? ephemeralSummaryTrees.createLoadContainerWithSharedTree : undefined,
135
+ );
166
136
 
167
- treeData1.root.insertNew("test string 1");
137
+ treeData.root.insertNew("test string 1");
168
138
 
169
139
  const resources = client.getContainer(containerId, schema, "2");
170
140
  await assert.doesNotReject(
@@ -189,5 +159,179 @@ for (const testOpts of testMatrix) {
189
159
  assert.strictEqual(treeData2.root.length, 1);
190
160
  assert.strictEqual(treeData2.root.at(0), "test string 1");
191
161
  });
162
+
163
+ if (!isEphemeral) {
164
+ {
165
+ class Nicknames extends sf.array("Nicknames", sf.string) {}
166
+ class UserData extends sf.map("UserData", [sf.string, sf.number, sf.boolean]) {}
167
+ class User extends sf.object("User", {
168
+ name: sf.string,
169
+ nicknames: Nicknames,
170
+ data: UserData,
171
+ }) {}
172
+
173
+ it("can read and edit data", async () => {
174
+ const { container } = await client.createContainer(schema, "2");
175
+ await container.attach();
176
+ const view = container.initialObjects.tree1.viewWith(
177
+ new TreeViewConfiguration({ schema: User, enableSchemaValidation: true }),
178
+ );
179
+
180
+ const tags: [string, string | number | boolean][] = [
181
+ ["Age", 32],
182
+ ["Favorite Snack", "Potato Chips"],
183
+ ["Awake", true],
184
+ ];
185
+
186
+ view.initialize(
187
+ new User({
188
+ name: "Pardes",
189
+ nicknames: ["Alex", "Duder"],
190
+ data: new Map(tags),
191
+ }),
192
+ );
193
+
194
+ const user = view.root;
195
+ // Read data
196
+ assert.equal(user.name, "Pardes");
197
+ assert.deepEqual([...user.nicknames], ["Alex", "Duder"]);
198
+ assert.equal(user.data.get("Age"), 32);
199
+ assert.equal(user.data.get("Favorite Snack"), "Potato Chips");
200
+ assert.equal(user.data.get("Awake"), true);
201
+ // Mutate data
202
+ user.name = "Pardesio";
203
+ user.nicknames.insertAt(1, "Alexp");
204
+ user.data.set("Awake", false);
205
+ user.data.set("Favorite Sport", "Ultimate Frisbee");
206
+ // Read mutated data
207
+ assert.equal(user.name, "Pardesio");
208
+ assert.deepEqual([...user.nicknames], ["Alex", "Alexp", "Duder"]);
209
+ assert.equal(user.data.get("Age"), 32);
210
+ assert.equal(user.data.get("Awake"), false);
211
+ assert.equal(user.data.get("Favorite Sport"), "Ultimate Frisbee");
212
+ });
213
+
214
+ it("can handle undo/redo and transactions", async () => {
215
+ const { container } = await client.createContainer(schema, "2");
216
+ await container.attach();
217
+ const view = container.initialObjects.tree1.viewWith(
218
+ new TreeViewConfiguration({ schema: User, enableSchemaValidation: true }),
219
+ );
220
+
221
+ view.initialize(
222
+ new User({
223
+ name: "Shrek",
224
+ nicknames: ["Ogre"],
225
+ data: new Map([["Color", "a5bf2e"]]),
226
+ }),
227
+ );
228
+
229
+ const user = view.root;
230
+ // Capture the Revertible so that changes can be undone
231
+ let revertible: Revertible | undefined;
232
+ view.events.on("commitApplied", (_, getRevertible) => {
233
+ assert(getRevertible !== undefined);
234
+ revertible = getRevertible();
235
+ });
236
+ // Change a field, then revert the change
237
+ user.name = "Donkey";
238
+ assert.equal(user.name, "Donkey");
239
+ assert(revertible !== undefined);
240
+ revertible.revert();
241
+ assert.equal(user.name, "Shrek");
242
+ // Run a transaction which changes multiple fields, then revert it
243
+ Tree.runTransaction(user, (u) => {
244
+ u.name = "Donkey";
245
+ u.nicknames.removeRange();
246
+ u.data.set("Color", "8e8170");
247
+ });
248
+
249
+ assert.equal(user.name, "Donkey");
250
+ assert.equal(user.nicknames.length, 0);
251
+ assert.equal(user.data.get("Color"), "8e8170");
252
+ revertible.revert();
253
+ assert.equal(user.name, "Shrek");
254
+ assert.equal(user.nicknames[0], "Ogre");
255
+ assert.equal(user.data.get("Color"), "a5bf2e");
256
+ });
257
+ }
258
+
259
+ it("can use identifiers and the static Tree APIs", async () => {
260
+ class Widget extends sf.object("Widget", { id: sf.identifier }) {}
261
+
262
+ const { container } = await client.createContainer(schema, "2");
263
+ await container.attach();
264
+ const view = container.initialObjects.tree1.viewWith(
265
+ new TreeViewConfiguration({
266
+ schema: sf.array(Widget),
267
+ enableSchemaValidation: true,
268
+ }),
269
+ );
270
+
271
+ view.initialize([new Widget({}), new Widget({ id: "fidget" })]);
272
+
273
+ const widget = view.root.at(0);
274
+ assert(widget !== undefined);
275
+ const fidget = view.root.at(-1);
276
+ assert(fidget !== undefined);
277
+ // Test various Tree.* APIs and ensure they are working
278
+ assert.equal(Tree.contains(view.root, widget), true);
279
+ assert.equal(Tree.contains(fidget, widget), false);
280
+ assert.equal(Tree.is(fidget, Widget), true);
281
+ assert.equal(Tree.is(view.root, Widget), false);
282
+ assert.equal(Tree.key(widget), 0);
283
+ assert.equal(Tree.key(fidget), 1);
284
+ assert.equal(Tree.parent(widget), view.root);
285
+ assert.equal(Tree.schema(fidget), Widget);
286
+ assert.equal(typeof Tree.shortId(widget), "number");
287
+ assert.equal(Tree.shortId(fidget), "fidget");
288
+ assert.equal(Tree.status(widget), TreeStatus.InDocument);
289
+ });
290
+
291
+ it("can listen to events on a recursive tree", async () => {
292
+ class Doll extends sf.objectRecursive("Matryoshka", {
293
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
294
+ nested: sf.optionalRecursive([() => Doll]),
295
+ }) {}
296
+
297
+ const { container } = await client.createContainer(schema, "2");
298
+ await container.attach();
299
+ const view = container.initialObjects.tree1.viewWith(
300
+ new TreeViewConfiguration({ schema: Doll, enableSchemaValidation: true }),
301
+ );
302
+
303
+ // These nodes in the initial tree are unhydrated...
304
+ const depth1 = new Doll({ nested: new Doll({}) });
305
+ const depth0 = new Doll({ nested: depth1 });
306
+ view.initialize(depth0);
307
+ // ...and confirmed to be the same nodes we get when we read the tree after initialization
308
+ assert.equal(view.root, depth0);
309
+ assert.equal(view.root.nested, depth1);
310
+ // Record a list of the node events fired
311
+ const eventLog: string[] = [];
312
+ Tree.on(depth0, "nodeChanged", () => {
313
+ eventLog.push("depth0.nested changed");
314
+ });
315
+ Tree.on(depth1, "nodeChanged", () => {
316
+ eventLog.push("depth1.nested changed");
317
+ });
318
+ // This event registration happens on the unhydrated (not yet inserted) node
319
+ const newDepth2 = new Doll({});
320
+ Tree.on(newDepth2, "nodeChanged", () => {
321
+ eventLog.push("depth2.nested changed");
322
+ });
323
+ // Fire the events by doing mutations
324
+ depth1.nested = newDepth2; // "depth1.nested changed"
325
+ assert.equal(depth1.nested, newDepth2);
326
+ depth1.nested.nested = new Doll({}); // "depth2.nested changed"
327
+ depth0.nested = new Doll({}); // "depth0.nested changed"
328
+ // Ensure the events fired in the expected order
329
+ assert.deepEqual(eventLog, [
330
+ "depth1.nested changed",
331
+ "depth2.nested changed",
332
+ "depth0.nested changed",
333
+ ]);
334
+ });
335
+ }
192
336
  });
193
337
  }