Skip to content

Commit

Permalink
tree: Const schema type parameters, more tests tree and misc cleanups (
Browse files Browse the repository at this point in the history
…microsoft#22927)

## Description

A collection of minor improvements split out from
microsoft#22566 for simplified
and more targeted review.
  • Loading branch information
CraigMacomber authored Oct 29, 2024
1 parent 2f1175f commit 4a730a0
Show file tree
Hide file tree
Showing 19 changed files with 248 additions and 40 deletions.
1 change: 1 addition & 0 deletions packages/dds/tree/.vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"covariantly",
"deprioritized",
"endregion",
"fluidframework",
"insertable",
"reentrantly",
"typeparam",
Expand Down
2 changes: 1 addition & 1 deletion packages/dds/tree/api-report/tree.alpha.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -648,7 +648,7 @@ export interface TreeArrayNodeUnsafe<TAllowedTypes extends Unenforced<ImplicitAl
// @beta @sealed
export const TreeBeta: {
on<K extends keyof TreeChangeEventsBeta<TNode>, TNode extends TreeNode>(node: TNode, eventName: K, listener: NoInfer<TreeChangeEventsBeta<TNode>[K]>): () => void;
clone<TSchema extends ImplicitFieldSchema>(node: TreeFieldFromImplicitField<TSchema>): TreeFieldFromImplicitField<TSchema>;
clone<const TSchema extends ImplicitFieldSchema>(node: TreeFieldFromImplicitField<TSchema>): TreeFieldFromImplicitField<TSchema>;
};

// @alpha @sealed
Expand Down
2 changes: 1 addition & 1 deletion packages/dds/tree/api-report/tree.beta.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ export interface TreeArrayNodeUnsafe<TAllowedTypes extends Unenforced<ImplicitAl
// @beta @sealed
export const TreeBeta: {
on<K extends keyof TreeChangeEventsBeta<TNode>, TNode extends TreeNode>(node: TNode, eventName: K, listener: NoInfer<TreeChangeEventsBeta<TNode>[K]>): () => void;
clone<TSchema extends ImplicitFieldSchema>(node: TreeFieldFromImplicitField<TSchema>): TreeFieldFromImplicitField<TSchema>;
clone<const TSchema extends ImplicitFieldSchema>(node: TreeFieldFromImplicitField<TSchema>): TreeFieldFromImplicitField<TSchema>;
};

// @public @sealed
Expand Down
2 changes: 1 addition & 1 deletion packages/dds/tree/src/simple-tree/api/conciseTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { getUnhydratedContext } from "../createContext.js";
* @remarks
* This is "concise" meaning that explicit type information is omitted.
* If the schema is compatible with {@link ITreeConfigurationOptions.preventAmbiguity},
* types will be lossless and compatible with {@link TreeBeta.create} (unless the options are used to customize it).
* types will be lossless and compatible with {@link TreeAlpha.create} (unless the options are used to customize it).
*
* Every {@link TreeNode} is an array or object.
* Any IFluidHandle values have been replaced by `THandle`.
Expand Down
27 changes: 19 additions & 8 deletions packages/dds/tree/src/simple-tree/api/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type {
import {
getOrCreateNodeFromInnerNode,
UnhydratedFlexTreeNode,
type TreeNode,
type Unhydrated,
} from "../core/index.js";
import {
Expand Down Expand Up @@ -50,18 +51,28 @@ import { getUnhydratedContext } from "../createContext.js";
* such as when `undefined` might be allowed (for an optional field), or when the type should be inferred from the data when more than one type is possible.
*
* Like with {@link TreeNodeSchemaClass}'s constructor, its an error to provide an existing node to this API.
* TODO: For that case, use we should provide `Tree.clone`.
* @privateRemarks
* This could be exposed as a public `Tree.create` function.
* For that case, use {@link TreeBeta.clone}.
*/
export function createFromInsertable<TSchema extends ImplicitFieldSchema>(
schema: TSchema,
export function createFromInsertable<
const TSchema extends ImplicitFieldSchema | UnsafeUnknownSchema,
>(
schema: UnsafeUnknownSchema extends TSchema
? ImplicitFieldSchema
: TSchema & ImplicitFieldSchema,
data: InsertableField<TSchema>,
context?: NodeKeyManager | undefined,
): Unhydrated<TreeFieldFromImplicitField<TSchema>> {
): Unhydrated<
TSchema extends ImplicitFieldSchema
? TreeFieldFromImplicitField<TSchema>
: TreeNode | TreeLeafValue | undefined
> {
const cursor = cursorFromInsertable(schema, data, context);
const result = cursor === undefined ? undefined : createFromCursor(schema, cursor);
return result as Unhydrated<TreeFieldFromImplicitField<TSchema>>;
return result as Unhydrated<
TSchema extends ImplicitFieldSchema
? TreeFieldFromImplicitField<TSchema>
: TreeNode | TreeLeafValue | undefined
>;
}

/**
Expand Down Expand Up @@ -150,7 +161,7 @@ export function createFromVerbose<TSchema extends ImplicitFieldSchema, THandle>(
/**
* Creates an unhydrated simple-tree field from a cursor in nodes mode.
*/
export function createFromCursor<TSchema extends ImplicitFieldSchema>(
export function createFromCursor<const TSchema extends ImplicitFieldSchema>(
schema: TSchema,
cursor: ITreeCursorSynchronous | undefined,
): Unhydrated<TreeFieldFromImplicitField<TSchema>> {
Expand Down
17 changes: 10 additions & 7 deletions packages/dds/tree/src/simple-tree/api/treeApiBeta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,16 +113,19 @@ export const TreeBeta: {
): () => void;

/**
* Clones the persisted data associated with a node. Some key things to note:
* Clones the persisted data associated with a node.
*
* @param node - The node to clone.
* @returns A new unhydrated node with the same persisted data as the original node.
* @remarks
* Some key things to note:
*
* - Local state, such as properties added to customized schema classes, will not be cloned. However, they will be
* initialized to their default state just as if the node had been created via its constructor.
* - Value node types (i.e., numbers, strings, booleans, nulls and Fluid handles) will be returned as is.
* - The identifiers in the node's subtree will be preserved, i.e., they are not replaced with new values.
*
* @param node - The node to clone.
* @returns A new unhydrated node with the same persisted data as the original node.
*/
clone<TSchema extends ImplicitFieldSchema>(
clone<const TSchema extends ImplicitFieldSchema>(
node: TreeFieldFromImplicitField<TSchema>,
): TreeFieldFromImplicitField<TSchema>;
} = {
Expand All @@ -133,10 +136,10 @@ export const TreeBeta: {
): () => void {
return treeNodeApi.on(node, eventName, listener);
},
clone<TSchema extends ImplicitFieldSchema>(
clone<const TSchema extends ImplicitFieldSchema>(
node: TreeFieldFromImplicitField<TSchema>,
): Unhydrated<TreeFieldFromImplicitField<TSchema>> {
/* The only non-TreeNode cases are {@link Value} (for an empty optional field) which can be returned as is. */
/** The only non-TreeNode cases are {@link TreeLeafValue} and `undefined` (for an empty optional field) which can be returned as is. */
if (!isTreeNode(node)) {
return node;
}
Expand Down
9 changes: 6 additions & 3 deletions packages/dds/tree/src/test/simple-tree/object.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ function testObjectLike(testCases: TestCaseErased[]) {
describe("satisfies 'deepEqual'", () => {
unsafeMapErased(
testCases,
<TSchema extends ImplicitFieldSchema>(item: TestCase<TSchema>) => {
<const TSchema extends ImplicitFieldSchema>(item: TestCase<TSchema>) => {
it(item.name ?? pretty(item.initialTree).toString(), () => {
const proxy = hydrate(item.schema, item.initialTree);
assert.deepEqual(proxy, item.initialTree, "Proxy must satisfy 'deepEqual'.");
Expand All @@ -86,7 +86,10 @@ function testObjectLike(testCases: TestCaseErased[]) {

unsafeMapErased(
testCases,
<TSchema extends ImplicitFieldSchema>({ initialTree, schema }: TestCase<TSchema>) => {
<const TSchema extends ImplicitFieldSchema>({
initialTree,
schema,
}: TestCase<TSchema>) => {
describe("instanceof Object", () => {
it(`${pretty(initialTree)} -> true`, () => {
const root = hydrate(schema, initialTree);
Expand Down Expand Up @@ -150,7 +153,7 @@ function testObjectLike(testCases: TestCaseErased[]) {
function test1(fn: (subject: object) => unknown) {
unsafeMapErased(
testCases,
<TSchema extends ImplicitFieldSchema>({
<const TSchema extends ImplicitFieldSchema>({
initialTree,
schema,
name,
Expand Down
18 changes: 9 additions & 9 deletions packages/dds/tree/src/test/simple-tree/primitives.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe("Primitives", () => {
* @param schema - Schema to use for the test (must include the type of 'value'.)
* @param value - The value to be written/read/verified.
*/
function checkExact<TSchema extends ImplicitFieldSchema>(
function checkExact<const TSchema extends ImplicitFieldSchema>(
schema: TSchema,
value: InsertableTreeFieldFromImplicitField<TSchema>,
) {
Expand All @@ -45,9 +45,9 @@ describe("Primitives", () => {
});

// TODO: Consider improving coverage with more variations:
// - reading/writting an object field
// - reading/writting a list element
// - reading/writting a map entry
// - reading/writing an object field
// - reading/writing a list element
// - reading/writing a map entry
// - optional
}

Expand Down Expand Up @@ -75,7 +75,7 @@ describe("Primitives", () => {
* @param schema - Schema to use for the test (must include the coerced type of 'value'.)
* @param value - The value to be written/read/verified.
*/
function checkCoerced<TSchema extends ImplicitFieldSchema>(
function checkCoerced<const TSchema extends ImplicitFieldSchema>(
schema: TSchema,
value: InsertableTreeFieldFromImplicitField<TSchema>,
) {
Expand All @@ -93,9 +93,9 @@ describe("Primitives", () => {
});

// TODO: Consider improving coverage with more variations:
// - reading/writting an object field
// - reading/writting a list element
// - reading/writting a map entry
// - reading/writing an object field
// - reading/writing a list element
// - reading/writing a map entry
// - optional
}

Expand All @@ -106,7 +106,7 @@ describe("Primitives", () => {
* @param schema - Schema to use for the test (must include the coerced type of 'value'.)
* @param value - The value to be written/read/verified.
*/
function checkThrows<TSchema extends ImplicitFieldSchema>(
function checkThrows<const TSchema extends ImplicitFieldSchema>(
schema: TSchema,
value: InsertableTreeFieldFromImplicitField<TSchema>,
) {
Expand Down
2 changes: 1 addition & 1 deletion packages/dds/tree/src/test/simple-tree/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ export function pretty(arg: unknown): number | string {
* @returns A new tree view for a branch of the input tree view, and an {@link TreeCheckoutFork} object that can be
* used to merge the branch back into the original view.
*/
export function getViewForForkedBranch<TSchema extends ImplicitFieldSchema>(
export function getViewForForkedBranch<const TSchema extends ImplicitFieldSchema>(
originalView: SchematizingSimpleTreeView<TSchema>,
): { forkView: SchematizingSimpleTreeView<TSchema>; forkCheckout: TreeCheckout } {
const forkCheckout = originalView.checkout.branch();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"version": 1,
"identifiers": [],
"shapes": [
{
"c": {
"type": "test.hasAmbiguousField",
"value": false,
"fields": [
[
"field",
2
]
]
}
},
{
"c": {
"type": "test.minimal",
"value": false
}
},
{
"d": 0
}
],
"data": [
[
0,
1
]
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"version": 1,
"identifiers": [],
"shapes": [
{
"c": {
"type": "test.hasRenamedField",
"value": false,
"fields": [
[
"stored-name",
1
]
]
}
},
{
"c": {
"type": "test.minimal",
"value": false
}
}
],
"data": [
[
0
]
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"version": 1,
"nodes": {
"test.hasAmbiguousField": {
"object": {
"field": {
"kind": "Value",
"types": [
"test.minimal",
"test.minimal2"
]
}
}
},
"test.minimal": {
"object": {}
},
"test.minimal2": {
"object": {}
}
},
"root": {
"kind": "Value",
"types": [
"test.hasAmbiguousField"
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"version": 1,
"nodes": {
"test.hasRenamedField": {
"object": {
"stored-name": {
"kind": "Value",
"types": [
"test.minimal"
]
}
}
},
"test.minimal": {
"object": {}
}
},
"root": {
"kind": "Value",
"types": [
"test.hasRenamedField"
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"version": 1,
"nodes": {
"test.hasAmbiguousField": {
"object": {
"field": {
"kind": "Value",
"types": [
"test.minimal",
"test.minimal2"
]
}
}
},
"test.minimal": {
"object": {}
},
"test.minimal2": {
"object": {}
}
},
"root": {
"kind": "Value",
"types": [
"test.hasAmbiguousField"
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"version": 1,
"nodes": {
"test.hasRenamedField": {
"object": {
"stored-name": {
"kind": "Value",
"types": [
"test.minimal"
]
}
}
},
"test.minimal": {
"object": {}
}
},
"root": {
"kind": "Value",
"types": [
"test.hasRenamedField"
]
}
}
Loading

0 comments on commit 4a730a0

Please sign in to comment.