Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -976,5 +976,5 @@
}
],
"marker": "{/* GENERATED FILE: DO NOT EDIT. Regenerate via `pnpm run docapi:sync`. */}",
"sourceHash": "3b3a367f04f06a39426291c6d41ab669a58f346f6520fde2f67e1c8e84abfad5"
"sourceHash": "5eb339719530fd6ff1e69c9c90c36637fedce9fc426b3aba84f973c73facf3e0"
}
98 changes: 80 additions & 18 deletions apps/docs/document-api/reference/tables/unmerge-cells.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,30 @@ Returns a TableMutationResult receipt; reports NO_OP if the cell is not merged.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `target` | TableCellAddress | yes | TableCellAddress |
| `nodeId` | string | no | |
| `target` | TableCellAddress | no | TableCellAddress |
| `target.kind` | `"block"` | no | Constant: `"block"` |
| `target.nodeId` | string | no | |
| `target.nodeType` | `"tableCell"` | no | Constant: `"tableCell"` |

### Variant 2 (target.nodeType="table")

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `columnIndex` | integer | yes | |
| `rowIndex` | integer | yes | |
| `target` | TableAddress | yes | TableAddress |
| `target.kind` | `"block"` | yes | Constant: `"block"` |
| `target.nodeId` | string | yes | |
| `target.nodeType` | `"tableCell"` | yes | Constant: `"tableCell"` |
| `target.nodeType` | `"table"` | yes | Constant: `"table"` |

### Variant 2 (nodeId)
### Variant 3 (nodeId, rowIndex, columnIndex)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `columnIndex` | integer | yes | |
| `nodeId` | string | yes | |
| `rowIndex` | integer | yes | |

### Example request

Expand Down Expand Up @@ -116,28 +130,76 @@ When present, `result.table` is the follow-up address to reuse after this call.
<Accordion title="Raw input schema">
```json
{
"additionalProperties": false,
"oneOf": [
{
"required": [
"target"
]
"additionalProperties": false,
"oneOf": [
{
"required": [
"target"
]
},
{
"required": [
"nodeId"
]
}
],
"properties": {
"nodeId": {
"type": "string"
},
"target": {
"$ref": "#/$defs/TableCellAddress"
}
},
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"columnIndex": {
"minimum": 0,
"type": "integer"
},
"rowIndex": {
"minimum": 0,
"type": "integer"
},
"target": {
"$ref": "#/$defs/TableAddress"
}
},
"required": [
"nodeId"
]
}
],
"properties": {
"nodeId": {
"type": "string"
"target",
"rowIndex",
"columnIndex"
],
"type": "object"
},
"target": {
"$ref": "#/$defs/TableCellAddress"
{
"additionalProperties": false,
"properties": {
"columnIndex": {
"minimum": 0,
"type": "integer"
},
"nodeId": {
"type": "string"
},
"rowIndex": {
"minimum": 0,
"type": "integer"
}
},
"required": [
"nodeId",
"rowIndex",
"columnIndex"
],
"type": "object"
}
},
"type": "object"
]
}
```
</Accordion>
Expand Down
22 changes: 20 additions & 2 deletions packages/document-api/src/contract/contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ describe('document-api contract catalog', () => {
properties?: { address?: { $ref?: string } };
};
const unmergeInput = schemas.operations['tables.unmergeCells'].input as {
properties?: { target?: { $ref?: string } };
oneOf?: Array<Record<string, unknown>>;
};
const setBorderInput = schemas.operations['tables.setBorder'].input as {
properties?: { target?: { $ref?: string } };
Expand All @@ -205,7 +205,25 @@ describe('document-api contract catalog', () => {

expect(tablesGetInput.properties?.target?.$ref).toBe('#/$defs/TableAddress');
expect(tablesGetOutput.properties?.address?.$ref).toBe('#/$defs/TableAddress');
expect(unmergeInput.properties?.target?.$ref).toBe('#/$defs/TableCellAddress');

// unmergeCells input is a oneOf: [cellLocator, tableScopedCellLocator (target), tableScopedCellLocator (nodeId)]
expect(unmergeInput.oneOf).toHaveLength(3);
const [cellBranch, tableTargetBranch, tableNodeIdBranch] = unmergeInput.oneOf as Array<{
properties?: { target?: { $ref?: string }; nodeId?: unknown; rowIndex?: unknown; columnIndex?: unknown };
required?: string[];
}>;
// First branch: direct cell locator (target.$ref → TableCellAddress)
expect(cellBranch.properties?.target?.$ref).toBe('#/$defs/TableCellAddress');
// Second branch: table-scoped with target (target.$ref → TableAddress + coordinates)
expect(tableTargetBranch.properties?.target?.$ref).toBe('#/$defs/TableAddress');
expect(tableTargetBranch.required).toContain('rowIndex');
expect(tableTargetBranch.required).toContain('columnIndex');
// Third branch: table-scoped with nodeId + coordinates
expect(tableNodeIdBranch.properties?.nodeId).toBeDefined();
expect(tableNodeIdBranch.required).toContain('nodeId');
expect(tableNodeIdBranch.required).toContain('rowIndex');
expect(tableNodeIdBranch.required).toContain('columnIndex');

expect(setBorderInput.properties?.target?.$ref).toBe('#/$defs/TableOrCellAddress');
expect(insertRowSuccess.properties?.table?.$ref).toBe('#/$defs/TableAddress');
});
Expand Down
28 changes: 27 additions & 1 deletion packages/document-api/src/contract/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@
const trackedChangeAddressSchema = ref('TrackedChangeAddress');
const entityAddressSchema = ref('EntityAddress');
const selectionTargetSchema = ref('SelectionTarget');
const targetLocatorSchema = ref('TargetLocator');

Check warning on line 509 in packages/document-api/src/contract/schemas.ts

View workflow job for this annotation

GitHub Actions / validate

'targetLocatorSchema' is assigned a value but never used. Allowed unused vars must match /^_/u
const deleteBehaviorSchema = ref('DeleteBehavior');
const resolvedHandleSchema = ref('ResolvedHandle');
const pageInfoSchema = ref('PageInfo');
Expand Down Expand Up @@ -770,7 +770,7 @@
text: { type: 'string' },
});

const nodeInfoSchema: JsonSchema = {

Check warning on line 773 in packages/document-api/src/contract/schemas.ts

View workflow job for this annotation

GitHub Actions / validate

'nodeInfoSchema' is assigned a value but never used. Allowed unused vars must match /^_/u
type: 'object',
required: ['nodeType', 'kind'],
properties: {
Expand All @@ -786,7 +786,7 @@
additionalProperties: false,
};

const matchContextSchema = objectSchema(

Check warning on line 789 in packages/document-api/src/contract/schemas.ts

View workflow job for this annotation

GitHub Actions / validate

'matchContextSchema' is assigned a value but never used. Allowed unused vars must match /^_/u
{
address: nodeAddressSchema,
snippet: { type: 'string' },
Expand All @@ -797,7 +797,7 @@
['address', 'snippet', 'highlightRange'],
);

const unknownNodeDiagnosticSchema = objectSchema(

Check warning on line 800 in packages/document-api/src/contract/schemas.ts

View workflow job for this annotation

GitHub Actions / validate

'unknownNodeDiagnosticSchema' is assigned a value but never used. Allowed unused vars must match /^_/u
{
message: { type: 'string' },
address: nodeAddressSchema,
Expand Down Expand Up @@ -1507,6 +1507,32 @@
oneOf: [{ required: ['target'] }, { required: ['nodeId'] }],
};

/**
* Accepts either a direct cell locator (target/nodeId pointing at a cell)
* or a table-scoped cell locator (target/nodeId pointing at a table + rowIndex + columnIndex).
*/
const cellOrTableScopedCellLocatorSchema: JsonSchema = {
oneOf: [
cellLocatorSchema,
objectSchema(
{
target: tableAddressSchema,
rowIndex: { type: 'integer', minimum: 0 },
columnIndex: { type: 'integer', minimum: 0 },
},
['target', 'rowIndex', 'columnIndex'],
),
objectSchema(
{
nodeId: { type: 'string' },
rowIndex: { type: 'integer', minimum: 0 },
columnIndex: { type: 'integer', minimum: 0 },
},
['nodeId', 'rowIndex', 'columnIndex'],
),
],
};

const tableOrCellLocatorSchema: JsonSchema = {
...objectSchema({
target: tableOrCellAddressSchema,
Expand Down Expand Up @@ -4925,7 +4951,7 @@
failure: tableMutationFailureSchema,
},
'tables.unmergeCells': {
input: cellLocatorSchema,
input: cellOrTableScopedCellLocatorSchema,
output: tableMutationResultSchema,
success: tableMutationSuccessSchema,
failure: tableMutationFailureSchema,
Expand Down
88 changes: 88 additions & 0 deletions packages/document-api/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2742,6 +2742,94 @@ describe('createDocumentApi', () => {
).not.toThrow();
});

// -- unmergeCells mixed cell/table-scoped locator validation --

it('accepts direct cell nodeId for unmergeCells', () => {
const api = makeApi();
expect(() => api.tables.unmergeCells({ nodeId: 'cell-1' })).not.toThrow();
});

it('accepts direct cell target for unmergeCells', () => {
const api = makeApi();
const target = { kind: 'block' as const, nodeType: 'tableCell' as const, nodeId: 'c1' };
expect(() => api.tables.unmergeCells({ target })).not.toThrow();
});

it('treats explicit null coordinates as absent for direct cell target on unmergeCells', () => {
const api = makeApi();
const target = { kind: 'block' as const, nodeType: 'tableCell' as const, nodeId: 'c1' };
expect(() => api.tables.unmergeCells({ target, rowIndex: null, columnIndex: null } as any)).not.toThrow();
});

it('accepts table-scoped locator (nodeId + rowIndex + columnIndex) for unmergeCells', () => {
const api = makeApi();
expect(() => api.tables.unmergeCells({ nodeId: 'table-1', rowIndex: 0, columnIndex: 0 })).not.toThrow();
});

it('accepts table-scoped locator (target + rowIndex + columnIndex) for unmergeCells', () => {
const api = makeApi();
const target = { kind: 'block' as const, nodeType: 'table' as const, nodeId: 't1' };
expect(() => api.tables.unmergeCells({ target, rowIndex: 0, columnIndex: 0 })).not.toThrow();
});

it('treats explicit undefined coordinates as a direct cell call for unmergeCells', () => {
const api = makeApi();
// { nodeId, rowIndex: undefined, columnIndex: undefined } must pass validation
// as a direct-cell call — the keys exist but the values are absent.
expect(() =>
api.tables.unmergeCells({ nodeId: 'cell-1', rowIndex: undefined, columnIndex: undefined } as any),
).not.toThrow();
});

it('rejects unmergeCells with only rowIndex (missing columnIndex)', () => {
const api = makeApi();
expect(() => api.tables.unmergeCells({ nodeId: 'table-1', rowIndex: 0 } as any)).toThrow(
/both rowIndex and columnIndex/,
);
});

it('rejects unmergeCells with only columnIndex (missing rowIndex)', () => {
const api = makeApi();
expect(() => api.tables.unmergeCells({ nodeId: 'table-1', columnIndex: 0 } as any)).toThrow(
/both rowIndex and columnIndex/,
);
});

it('rejects unmergeCells with cell target plus coordinates', () => {
const api = makeApi();
const target = { kind: 'block' as const, nodeType: 'tableCell' as const, nodeId: 'c1' };
expect(() => api.tables.unmergeCells({ target, rowIndex: 0, columnIndex: 0 } as any)).toThrow(
/must not be provided when target is a cell node/,
);
});

it('rejects unmergeCells with table target without coordinates', () => {
const api = makeApi();
const target = { kind: 'block' as const, nodeType: 'table' as const, nodeId: 't1' };
expect(() => api.tables.unmergeCells({ target } as any)).toThrow(
/rowIndex and columnIndex are required when target is a table/,
);
});

it('rejects unmergeCells with table target and null coordinates', () => {
const api = makeApi();
const target = { kind: 'block' as const, nodeType: 'table' as const, nodeId: 't1' };
expect(() => api.tables.unmergeCells({ target, rowIndex: null, columnIndex: null } as any)).toThrow(
/rowIndex and columnIndex are required when target is a table/,
);
});

it('rejects unmergeCells with table target and mixed null coordinates', () => {
const api = makeApi();
const target = { kind: 'block' as const, nodeType: 'table' as const, nodeId: 't1' };
expect(() => api.tables.unmergeCells({ target, rowIndex: null, columnIndex: 0 } as any)).toThrow(
/both rowIndex and columnIndex/,
);
expect(() => api.tables.unmergeCells({ target, rowIndex: 0, columnIndex: null } as any)).toThrow(
/both rowIndex and columnIndex/,
);
});

// -- create.table locator validation --

it('rejects ambiguous create.table at locator (both target + nodeId)', () => {
Expand Down
9 changes: 7 additions & 2 deletions packages/document-api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,12 @@ import type {
DiffApplyInput,
DiffApplyOptions,
} from './diff/diff.types.js';
import { executeTableLocatorOp, executeRowLocatorOp, executeDocumentLevelTableOp } from './tables/tables.js';
import {
executeTableLocatorOp,
executeRowLocatorOp,
executeCellOrTableScopedCellLocatorOp,
executeDocumentLevelTableOp,
} from './tables/tables.js';
import type {
ParagraphsAdapter,
ParagraphFormatApi,
Expand Down Expand Up @@ -2321,7 +2326,7 @@ export function createDocumentApi(adapters: DocumentApiAdapters): DocumentApi {
);
},
unmergeCells(input, options?) {
return executeTableLocatorOp(
return executeCellOrTableScopedCellLocatorOp(
'tables.unmergeCells',
adapters.tables.unmergeCells.bind(adapters.tables),
input,
Expand Down
Loading
Loading