diff --git a/examples/utils/import-testing/src/test/importer.spec.ts b/examples/utils/import-testing/src/test/importer.spec.ts index 74a89aaa9e7e..5682359a3ce3 100644 --- a/examples/utils/import-testing/src/test/importer.spec.ts +++ b/examples/utils/import-testing/src/test/importer.spec.ts @@ -5,7 +5,8 @@ import { strict as assert } from "node:assert"; -import { JsonAsTree } from "@fluidframework/tree/alpha"; +// eslint-disable-next-line import/no-internal-modules +import { JsonAsTree, SchemaFactoryAlpha, TableSchema } from "@fluidframework/tree/internal"; import type { areSafelyAssignable, requireTrue, @@ -26,6 +27,57 @@ describe("import tests", () => { assert.equal(r[0], 1); }); + // The TableSchema code is complex and previously had issues where invalid `.d.ts` files were generated by TypeScript. + // These tests exist to ensure we don't inadvertently check in changes that regress users' ability to consume that code. + it("TableSchema", () => { + const schemaFactory = new SchemaFactoryAlpha("com.example"); + + class Cell extends schemaFactory.object("table-cell", { + value: schemaFactory.string, + }) {} + + class ColumnProps extends schemaFactory.object("table-column-props", { + label: schemaFactory.optional(schemaFactory.string), + }) {} + class Column extends TableSchema.createColumn(schemaFactory, ColumnProps) {} + + class RowProps extends schemaFactory.object("table-row-props", { + label: schemaFactory.optional(schemaFactory.string), + }) {} + class Row extends TableSchema.createRow(schemaFactory, Cell, RowProps) {} + + class Table extends TableSchema.createTable(schemaFactory, Cell, Column, Row) {} + + const _table = new Table({ + columns: [ + new Column({ + props: { + label: "Column 0", + }, + }), + new Column({ + props: { + label: "Column 1", + }, + }), + ], + rows: [ + new Row({ + cells: {}, + props: { + label: "Row 0", + }, + }), + new Row({ + cells: {}, + props: { + label: "Row 1", + }, + }), + ], + }); + }); + // See also the unit tests for JsonAsTree in tree's jsonDomainSchema.spec.ts it("Iterator types", () => { type ImportedArrayNodeIterator = ReturnType; diff --git a/packages/dds/tree/src/tableSchema.ts b/packages/dds/tree/src/tableSchema.ts index faa2d682b786..8676bf30633e 100644 --- a/packages/dds/tree/src/tableSchema.ts +++ b/packages/dds/tree/src/tableSchema.ts @@ -43,22 +43,34 @@ export namespace TableSchema { * @remarks Implemented by the schema class returned from {@link TableSchema.createColumn}. * @sealed @internal */ - export interface IColumn { + export interface IColumn { /** * The unique identifier of the column. * @remarks Uniquely identifies the node within the entire tree, not just the table. */ readonly id: string; + + /** + * User-provided column properties. + */ + get props(): TreeNodeFromImplicitAllowedTypes; + set props(value: InsertableTreeNodeFromImplicitAllowedTypes); } /** * Factory for creating new table column schema. + * @privateRemarks + * TODO: + * - Add overloads to make propsSchema optional. + * - Take field schema rather than node schema for `propsSchema`, in particular to allow making + * the additional properties optional. * @internal */ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type -- Return type is too complex to be reasonable to specify - export function createColumn( - inputSchemaFactory: SchemaFactoryAlpha, - ) { + export function createColumn< + const TInputScope extends string | undefined, + const TPropsSchema extends ImplicitAllowedTypes, + >(inputSchemaFactory: SchemaFactoryAlpha, propsSchema: TPropsSchema) { const schemaFactory = inputSchemaFactory.scopedFactory(tableSchemaFactorySubScope); type Scope = ScopedSchemaName; @@ -66,17 +78,24 @@ export namespace TableSchema { * {@link Column} fields. * @remarks Extracted for re-use in returned type signature defined later in this function. * The implicit typing is intentional. + * Note: ideally we would add a satisfies clause here to ensure that this satisfies + * `Record`, but doing so causes TypeScript to prematurely and incorrectly evaluate the type of `propsSchema`. + * Likely related to the following issue: https://github.com/microsoft/TypeScript/issues/52394 */ const columnFields = { id: schemaFactory.identifier, - } as const satisfies Record; + props: schemaFactory.required(propsSchema), + } as const; /** * A column in a table. */ - class Column extends schemaFactory.object("Column", columnFields) implements IColumn {} + class Column extends schemaFactory.object("Column", columnFields) {} + + type ColumnValueType = TreeNode & + IColumn & + WithType>; - type ColumnValueType = TreeNode & IColumn & WithType>; type ColumnInsertableType = InsertableObjectFromSchemaRecord; // Returning SingletonSchema without a type conversion results in TypeScript generating something like `readonly "__#124291@#brand": unknown;` @@ -100,9 +119,10 @@ export namespace TableSchema { * Base column schema type. * @sealed @system @internal */ - export type ColumnSchemaBase = ReturnType< - typeof createColumn - >; + export type ColumnSchemaBase< + TScope extends string | undefined, + TPropsSchema extends ImplicitAllowedTypes, + > = ReturnType>; // #endregion @@ -115,7 +135,7 @@ export namespace TableSchema { */ export interface IRow< TCellSchema extends ImplicitAllowedTypes, - TColumnSchema extends ImplicitAllowedTypes, + TPropsSchema extends ImplicitAllowedTypes = ImplicitAllowedTypes, > { /** * The unique identifier of the row. @@ -124,45 +144,71 @@ export namespace TableSchema { readonly id: string; /** - * Gets the cell in the specified column + * Gets the cell in the specified column. + * @returns The cell if it exists, otherwise undefined. + */ + getCell(column: IColumn): TreeNodeFromImplicitAllowedTypes | undefined; + /** + * Gets the cell in the specified column, denoted by column ID. * @returns The cell if it exists, otherwise undefined. - * @privateRemarks TODO: add overload that takes column ID. */ - getCell( - column: TreeNodeFromImplicitAllowedTypes, - ): TreeNodeFromImplicitAllowedTypes | undefined; + getCell(columnId: string): TreeNodeFromImplicitAllowedTypes | undefined; /** * Sets the cell in the specified column. - * @remarks To remove a cell, call {@link TableSchema.IRow.removeCell} instead. - * @privateRemarks TODO: add overload that takes column ID. + * @remarks To remove a cell, call {@link TableSchema.IRow.(removeCell:1)} instead. */ setCell( - column: TreeNodeFromImplicitAllowedTypes, + column: IColumn, + value: InsertableTreeNodeFromImplicitAllowedTypes, + ): void; + /** + * Sets the cell in the specified column, denoted by column ID. + * @remarks To remove a cell, call {@link TableSchema.IRow.(removeCell:2)} instead. + */ + setCell( + columnId: string, value: InsertableTreeNodeFromImplicitAllowedTypes, ): void; /** * Removes the cell in the specified column. - * @privateRemarks TODO: add overload that takes column ID. + * @privateRemarks TODO: return removed cell + */ + removeCell(column: IColumn): void; + /** + * Removes the cell in the specified column, denoted by column ID. + * @privateRemarks TODO: return removed cell */ - removeCell(column: TreeNodeFromImplicitAllowedTypes): void; + removeCell(columnId: string): void; + + /** + * User-provided row properties. + */ + get props(): TreeNodeFromImplicitAllowedTypes; + set props(value: InsertableTreeNodeFromImplicitAllowedTypes); } /** * Factory for creating new table row schema. - * @privateRemarks TODO: add overloads to make column schema optional. + * + * @privateRemarks + * TODO: + * - Add overloads to make propsSchema optional. + * - Take field schema rather than node schema for `propsSchema`, in particular to allow making + * the additional properties optional. + * * @sealed @internal */ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type -- Return type is too complex to be reasonable to specify export function createRow< const TInputScope extends string | undefined, const TCellSchema extends ImplicitAllowedTypes, - const TColumnSchema extends ColumnSchemaBase = ColumnSchemaBase, + const TPropsSchema extends ImplicitAllowedTypes, >( inputSchemaFactory: SchemaFactoryAlpha, cellSchema: TCellSchema, - _columnSchema: TColumnSchema, + propsSchema: TPropsSchema, ) { const schemaFactory = inputSchemaFactory.scopedFactory(tableSchemaFactorySubScope); type Scope = ScopedSchemaName; @@ -170,41 +216,51 @@ export namespace TableSchema { type CellValueType = TreeNodeFromImplicitAllowedTypes; type CellInsertableType = InsertableTreeNodeFromImplicitAllowedTypes; - type ColumnValueType = TreeNodeFromImplicitAllowedTypes; - /** * {@link Row} fields. * @remarks Extracted for re-use in returned type signature defined later in this function. * The implicit typing is intentional. + * Note: ideally we would add a satisfies clause here to ensure that this satisfies + * `Record`, but doing so causes TypeScript to prematurely and incorrectly evaluate the type of `propsSchema`. + * Likely related to the following issue: https://github.com/microsoft/TypeScript/issues/52394 */ const rowFields = { id: schemaFactory.identifier, cells: schemaFactory.map("Row.cells", cellSchema), - } as const satisfies Record; + props: schemaFactory.required(propsSchema), + } as const; /** * The Row schema - this is a map of Cells where the key is the column id */ class Row extends schemaFactory.object("Row", rowFields) - implements IRow + implements IRow { - public getCell(column: ColumnValueType): CellValueType | undefined { - return this.cells.get(column.id) as CellValueType | undefined; + public getCell(columnOrId: IColumn | string): CellValueType | undefined { + const columnId = typeof columnOrId === "string" ? columnOrId : columnOrId.id; + return this.cells.get(columnId) as CellValueType | undefined; } - public setCell(column: ColumnValueType, value: CellInsertableType | undefined): void { - this.cells.set(column.id, value); + public setCell( + columnOrId: IColumn | string, + value: CellInsertableType | undefined, + ): void { + const columnId = typeof columnOrId === "string" ? columnOrId : columnOrId.id; + this.cells.set(columnId, value); } - public removeCell(column: ColumnValueType): void { - if (!this.cells.has(column.id)) return; - this.cells.delete(column.id); + public removeCell(columnOrId: IColumn | string): void { + const columnId = typeof columnOrId === "string" ? columnOrId : columnOrId.id; + if (!this.cells.has(columnId)) { + return; + } + this.cells.delete(columnId); } } type RowValueType = TreeNode & - IRow & + IRow & WithType>; type RowInsertableType = InsertableObjectFromSchemaRecord; @@ -232,8 +288,8 @@ export namespace TableSchema { export type RowSchemaBase< TScope extends string | undefined, TCellSchema extends ImplicitAllowedTypes, - TColumnSchema extends ColumnSchemaBase = ColumnSchemaBase, - > = ReturnType>; + TPropsSchema extends ImplicitAllowedTypes, + > = ReturnType>; // #endregion @@ -408,13 +464,13 @@ export namespace TableSchema { _cellSchema: TCell, ): ReturnType>; /** - * Factory for creating new table schema without specifyint row schema + * Factory for creating new table schema without specifying row schema * @internal */ export function createTable< const TInputScope extends string | undefined, const TCell extends ImplicitAllowedTypes, - const TColumn extends ColumnSchemaBase, + const TColumn extends ColumnSchemaBase, >( inputSchemaFactory: SchemaFactoryAlpha, _cellSchema: TCell, @@ -427,31 +483,33 @@ export namespace TableSchema { export function createTable< const TInputScope extends string | undefined, const TCell extends ImplicitAllowedTypes, - const TColumn extends ColumnSchemaBase, - const TRow extends RowSchemaBase, + const TColumn extends ColumnSchemaBase, + const TRow extends RowSchemaBase, >( inputSchemaFactory: SchemaFactoryAlpha, _cellSchema: TCell, columnSchema: TColumn, rowSchema: TRow, ): ReturnType>; + /** `createTable` implementation */ export function createTable< const TInputScope extends string | undefined, const TCell extends ImplicitAllowedTypes, - const TColumn extends ColumnSchemaBase, - const TRow extends RowSchemaBase, + const TColumn extends ColumnSchemaBase, + const TRow extends RowSchemaBase, >( inputSchemaFactory: SchemaFactoryAlpha, _cellSchema: TCell, columnSchema?: TColumn, rowSchema?: TRow, ): TreeNodeSchema { - const column = columnSchema ?? createColumn(inputSchemaFactory); + const column = columnSchema ?? createColumn(inputSchemaFactory, inputSchemaFactory.null); return createTableInternal( inputSchemaFactory, _cellSchema, column as TColumn, - rowSchema ?? (createRow(inputSchemaFactory, _cellSchema, column) as TRow), + rowSchema ?? + (createRow(inputSchemaFactory, _cellSchema, inputSchemaFactory.null) as TRow), ); } @@ -463,11 +521,14 @@ export namespace TableSchema { export function createTableInternal< const TInputScope extends string | undefined, const TCell extends ImplicitAllowedTypes, - const TColumn extends ColumnSchemaBase = ColumnSchemaBase, - const TRow extends RowSchemaBase = RowSchemaBase< + const TColumn extends ColumnSchemaBase< + TInputScope, + ImplicitAllowedTypes + > = ColumnSchemaBase, + const TRow extends RowSchemaBase = RowSchemaBase< TInputScope, TCell, - TColumn + ImplicitAllowedTypes >, >( inputSchemaFactory: SchemaFactoryAlpha, @@ -526,7 +587,7 @@ export namespace TableSchema { if (row !== undefined) { const column = this.getColumn(columnId); if (column !== undefined) { - return row.getCell(column); + return row.getCell(column.id); } } // If the cell does not exist return undefined @@ -579,7 +640,7 @@ export namespace TableSchema { if (row !== undefined) { const column = this.getColumn(columnId); if (column !== undefined) { - row.setCell(column, cell); + row.setCell(column.id, cell); } } } @@ -624,7 +685,7 @@ export namespace TableSchema { if (row !== undefined) { const column = this.getColumn(columnId); if (column !== undefined) { - row.removeCell(column); + row.removeCell(column.id); } } } @@ -660,8 +721,15 @@ export namespace TableSchema { export type TableSchemaBase< TScope extends string | undefined, TCell extends ImplicitAllowedTypes, - TColumn extends ColumnSchemaBase = ColumnSchemaBase, - TRow extends RowSchemaBase = RowSchemaBase, + TColumn extends ColumnSchemaBase = ColumnSchemaBase< + TScope, + ImplicitAllowedTypes + >, + TRow extends RowSchemaBase = RowSchemaBase< + TScope, + TCell, + ImplicitAllowedTypes + >, > = ReturnType>; // #endregion diff --git a/packages/dds/tree/src/test/tableSchema.spec.ts b/packages/dds/tree/src/test/tableSchema.spec.ts index c2fa2d63647c..e5c1a0b51dc4 100644 --- a/packages/dds/tree/src/test/tableSchema.spec.ts +++ b/packages/dds/tree/src/test/tableSchema.spec.ts @@ -25,9 +25,15 @@ describe("TableFactory unit tests", () => { value: schemaFactory.string, }) {} - class Column extends TableSchema.createColumn(schemaFactory) {} + class ColumnProps extends schemaFactory.object("table-column-props", { + label: schemaFactory.optional(schemaFactory.string), + }) {} + class Column extends TableSchema.createColumn(schemaFactory, ColumnProps) {} - class Row extends TableSchema.createRow(schemaFactory, Cell, Column) {} + class RowProps extends schemaFactory.object("table-row-props", { + label: schemaFactory.optional(schemaFactory.string), + }) {} + class Row extends TableSchema.createRow(schemaFactory, Cell, RowProps) {} class Table extends TableSchema.createTable(schemaFactory, Cell, Column, Row) {} @@ -66,34 +72,48 @@ describe("TableFactory unit tests", () => { }); it("Non-empty", () => { - const { treeView } = createTableTree(); - - treeView.initialize({ - columns: [{ id: "column-0" }, { id: "column-1" }], - rows: [ - { id: "row-0", cells: {} }, - { - id: "row-1", - cells: { - "column-1": { value: "Hello world!" }, + const { treeView, Table, Column } = createTableTree(); + + treeView.initialize( + new Table({ + columns: [ + new Column({ + id: "column-0", + props: { + label: "Column 0", + }, + }), + new Column({ id: "column-1", props: { label: "Column 1" } }), + ], + rows: [ + { id: "row-0", cells: {}, props: {} }, + { + id: "row-1", + cells: { + "column-1": { value: "Hello world!" }, + }, + props: {}, }, - }, - ], - }); + ], + }), + ); assertEqualTrees(treeView.root, { columns: [ { id: "column-0", + props: { label: "Column 0" }, }, { id: "column-1", + props: { label: "Column 1" }, }, ], rows: [ { id: "row-0", cells: {}, + props: {}, }, { id: "row-1", @@ -102,6 +122,7 @@ describe("TableFactory unit tests", () => { value: "Hello world!", }, }, + props: {}, }, ], }); @@ -113,12 +134,16 @@ describe("TableFactory unit tests", () => { const { treeView } = createTableTree(); treeView.initialize({ rows: [], columns: [] }); - treeView.root.insertColumn({ index: 0, column: { id: "column-0" } }); + treeView.root.insertColumn({ + index: 0, + column: { id: "column-0", props: {} }, + }); assertEqualTrees(treeView.root, { columns: [ { id: "column-0", + props: {}, }, ], rows: [], @@ -127,20 +152,32 @@ describe("TableFactory unit tests", () => { it("Insert new column into non-empty list", () => { const { treeView } = createTableTree(); - treeView.initialize({ rows: [], columns: [{ id: "column-a" }, { id: "column-b" }] }); + treeView.initialize({ + rows: [], + columns: [ + { id: "column-a", props: {} }, + { id: "column-b", props: {} }, + ], + }); - treeView.root.insertColumn({ index: 1, column: { id: "column-c" } }); + treeView.root.insertColumn({ + index: 1, + column: { id: "column-c", props: {} }, + }); assertEqualTrees(treeView.root, { columns: [ { id: "column-a", + props: {}, }, { id: "column-c", + props: {}, }, { id: "column-b", + props: {}, }, ], rows: [], @@ -149,21 +186,32 @@ describe("TableFactory unit tests", () => { it("Append new column", () => { const { treeView } = createTableTree(); - treeView.initialize({ rows: [], columns: [{ id: "column-a" }, { id: "column-b" }] }); + treeView.initialize({ + rows: [], + columns: [ + { id: "column-a", props: {} }, + { id: "column-b", props: {} }, + ], + }); // By not specifying an index, the column should be appended to the end of the list. - treeView.root.insertColumn({ column: { id: "column-c" } }); + treeView.root.insertColumn({ + column: { id: "column-c", props: {} }, + }); assertEqualTrees(treeView.root, { columns: [ { id: "column-a", + props: {}, }, { id: "column-b", + props: {}, }, { id: "column-c", + props: {}, }, ], rows: [], @@ -174,10 +222,19 @@ describe("TableFactory unit tests", () => { // Once that work is finished, the usage error in this test should be updated, and the test can be unskipped. it.skip("Appending existing column errors", () => { const { treeView } = createTableTree(); - treeView.initialize({ rows: [], columns: [{ id: "column-a" }, { id: "column-b" }] }); + treeView.initialize({ + rows: [], + columns: [ + { id: "column-a", props: {} }, + { id: "column-b", props: {} }, + ], + }); assert.throws( - () => treeView.root.insertColumn({ column: { id: "column-b" } }), + () => + treeView.root.insertColumn({ + column: { id: "column-b", props: {} }, + }), validateUsageError(/Placeholder usage error/), ); }); @@ -206,6 +263,7 @@ describe("TableFactory unit tests", () => { { id: "row-0", cells: {}, + props: {}, }, ], }); @@ -216,6 +274,7 @@ describe("TableFactory unit tests", () => { { id: "row-0", cells: {}, + props: {}, }, ], }); @@ -228,10 +287,12 @@ describe("TableFactory unit tests", () => { { id: "row-a", cells: {}, + props: {}, }, { id: "row-b", cells: {}, + props: {}, }, ], columns: [], @@ -243,10 +304,12 @@ describe("TableFactory unit tests", () => { { id: "row-c", cells: {}, + props: {}, }, { id: "row-d", cells: {}, + props: {}, }, ], }); @@ -257,18 +320,22 @@ describe("TableFactory unit tests", () => { { id: "row-a", cells: {}, + props: {}, }, { id: "row-c", cells: {}, + props: {}, }, { id: "row-d", cells: {}, + props: {}, }, { id: "row-b", cells: {}, + props: {}, }, ], }); @@ -281,10 +348,12 @@ describe("TableFactory unit tests", () => { { id: "row-a", cells: {}, + props: {}, }, { id: "row-b", cells: {}, + props: {}, }, ], columns: [], @@ -295,10 +364,12 @@ describe("TableFactory unit tests", () => { { id: "row-c", cells: {}, + props: {}, }, { id: "row-d", cells: {}, + props: {}, }, ], }); @@ -309,18 +380,22 @@ describe("TableFactory unit tests", () => { { id: "row-a", cells: {}, + props: {}, }, { id: "row-b", cells: {}, + props: {}, }, { id: "row-c", cells: {}, + props: {}, }, { id: "row-d", cells: {}, + props: {}, }, ], }); @@ -335,10 +410,12 @@ describe("TableFactory unit tests", () => { { id: "row-a", cells: {}, + props: {}, }, { id: "row-b", cells: {}, + props: {}, }, ], columns: [], @@ -351,6 +428,7 @@ describe("TableFactory unit tests", () => { { id: "row-a", cells: {}, + props: {}, }, ], }), @@ -366,12 +444,14 @@ describe("TableFactory unit tests", () => { columns: [ { id: "column-0", + props: {}, }, ], rows: [ { id: "row-0", cells: {}, + props: {}, }, ], }); @@ -389,6 +469,7 @@ describe("TableFactory unit tests", () => { columns: [ { id: "column-0", + props: {}, }, ], rows: [ @@ -399,25 +480,28 @@ describe("TableFactory unit tests", () => { value: "Hello world!", }, }, + props: {}, }, ], }); }); // TODO: There is currently no policy from prohibiting insertion of an invalid cell. - // Once that work is finished, the usage error in this test should be updated, and the test can be unskipped. + // Once that work is finished, the usage error in this test should be updated, and the test can be un-skipped. it.skip("setting cell in an invalid location errors", () => { const { treeView } = createTableTree(); treeView.initialize({ columns: [ { id: "column-0", + props: {}, }, ], rows: [ { id: "row-0", cells: {}, + props: {}, }, ], }); @@ -443,12 +527,14 @@ describe("TableFactory unit tests", () => { columns: [ { id: "column-0", + props: { label: "Column 0" }, }, ], rows: [ { id: "row-0", cells: {}, + props: {}, }, ], }); @@ -460,13 +546,14 @@ describe("TableFactory unit tests", () => { { id: "row-0", cells: {}, + props: {}, }, ], }); }); - // TODO: There is currently no policy from prohibiting removal of non-existant columns. - // Once that work is finished, the usage error in this test should be updated, and the test can be unskipped. + // TODO: There is currently no policy from prohibiting removal of non-existent columns. + // Once that work is finished, the usage error in this test should be updated, and the test can be un-skipped. it.skip("removing column that does not exist on table errors", () => { const { treeView, Column } = createTableTree(); treeView.initialize({ @@ -475,7 +562,7 @@ describe("TableFactory unit tests", () => { }); assert.throws( - () => treeView.root.removeColumn(new Column({ id: "unhydrated-column" })), + () => treeView.root.removeColumn(new Column({ id: "unhydrated-column", props: {} })), validateUsageError(/Placeholder usage error/), ); }); @@ -498,8 +585,8 @@ describe("TableFactory unit tests", () => { it("remove single row", () => { const { treeView, Row } = createTableTree(); - const row0 = new Row({ id: "row-0", cells: {} }); - const row1 = new Row({ id: "row-1", cells: {} }); + const row0 = new Row({ id: "row-0", cells: {}, props: {} }); + const row1 = new Row({ id: "row-1", cells: {}, props: {} }); treeView.initialize({ columns: [], rows: [row0, row1], @@ -509,7 +596,7 @@ describe("TableFactory unit tests", () => { treeView.root.removeRows([row0]); assertEqualTrees(treeView.root, { columns: [], - rows: [{ id: "row-1", cells: {} }], + rows: [{ id: "row-1", cells: {}, props: {} }], }); // Remove row1 @@ -522,10 +609,10 @@ describe("TableFactory unit tests", () => { it("remove multiple rows", () => { const { treeView, Row } = createTableTree(); - const row0 = new Row({ id: "row-0", cells: {} }); - const row1 = new Row({ id: "row-1", cells: {} }); - const row2 = new Row({ id: "row-2", cells: {} }); - const row3 = new Row({ id: "row-3", cells: {} }); + const row0 = new Row({ id: "row-0", cells: {}, props: {} }); + const row1 = new Row({ id: "row-1", cells: {}, props: {} }); + const row2 = new Row({ id: "row-2", cells: {}, props: {} }); + const row3 = new Row({ id: "row-3", cells: {}, props: {} }); treeView.initialize({ columns: [], rows: [row0, row1, row2, row3], @@ -539,10 +626,12 @@ describe("TableFactory unit tests", () => { { id: "row-0", cells: {}, + props: {}, }, { id: "row-2", cells: {}, + props: {}, }, ], }); @@ -563,7 +652,7 @@ describe("TableFactory unit tests", () => { }); assert.throws( - () => treeView.root.removeRows([new Row({ id: "row-0", cells: {} })]), + () => treeView.root.removeRows([new Row({ id: "row-0", cells: {}, props: {} })]), validateUsageError(/Expected non-negative index, got -1./), ); }); @@ -578,8 +667,8 @@ describe("TableFactory unit tests", () => { assert.throws( () => treeView.root.removeRows([ - new Row({ id: "row-0", cells: {} }), - new Row({ id: "row-1", cells: {} }), + new Row({ id: "row-0", cells: {}, props: {} }), + new Row({ id: "row-1", cells: {}, props: {} }), ]), // TODO: The usage error here comes from the arrayNode layer. // Once removeRows gets updated to return a usage error that makes more sense, update usage error here. @@ -595,12 +684,14 @@ describe("TableFactory unit tests", () => { columns: [ { id: "column-0", + props: {}, }, ], rows: [ { id: "row-0", cells: {}, + props: {}, }, ], }); @@ -617,12 +708,14 @@ describe("TableFactory unit tests", () => { columns: [ { id: "column-0", + props: {}, }, ], rows: [ { id: "row-0", cells: {}, + props: {}, }, ], }); @@ -634,12 +727,14 @@ describe("TableFactory unit tests", () => { columns: [ { id: "column-0", + props: {}, }, ], rows: [ { id: "row-0", cells: {}, + props: {}, }, ], }); @@ -652,31 +747,35 @@ describe("TableFactory unit tests", () => { columns: [ { id: "column-0", + props: {}, }, ], rows: [ { id: "row-0", cells: {}, + props: {}, }, ], }); }); // TODO: There is currently no usage error for deleting invalid cells. - // Once that work is finished, the usage error in this test should be updated, and the test can be unskipped. + // Once that work is finished, the usage error in this test should be updated, and the test can be un-skipped. it.skip("removing cell from nonexistent row and column errors", () => { const { treeView } = createTableTree(); treeView.initialize({ columns: [ { id: "column-0", + props: {}, }, ], rows: [ { id: "row-0", cells: {}, + props: {}, }, ], }); @@ -692,12 +791,38 @@ describe("TableFactory unit tests", () => { }); }); + it("can read column props", () => { + const { treeView, Column } = createTableTree(); + + const column = new Column({ id: "column-0", props: { label: "Column 0" } }); + + treeView.initialize({ + columns: [column], + rows: [], + }); + + assert.equal(column.props?.label, "Column 0"); + }); + + it("can read row props", () => { + const { treeView, Row } = createTableTree(); + + const row = new Row({ id: "row-0", cells: {}, props: { label: "Row 0" } }); + + treeView.initialize({ + columns: [], + rows: [row], + }); + + assert.equal(row.props?.label, "Row 0"); + }); + it("gets proper table elements with getter methods", () => { const { treeView, Column, Row, Cell } = createTableTree(); const cell0 = new Cell({ value: "Hello World!" }); - const column0 = new Column({ id: "column-0" }); - const row0 = new Row({ id: "row-0", cells: { "column-0": cell0 } }); + const column0 = new Column({ id: "column-0", props: {} }); + const row0 = new Row({ id: "row-0", cells: { "column-0": cell0 }, props: {} }); treeView.initialize({ columns: [column0],