Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tree: Const schema type parameters, more tests tree and misc cleanups #22927

Merged
merged 2 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When combined with the recent corrections to schema variance, this change improves handling of passing array literals to this function as the schema for a union, since it removes the need to specify "as const" at the call site.

};

// @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).
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

neither of these links works yet, but this is the API thats getting added in #22566

*
* 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
Loading