@dxos/react-ui-table 0.8.3 → 0.8.4-main.1068cf700f
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.
- package/dist/lib/browser/chunk-GTGYBRJA.mjs +1748 -0
- package/dist/lib/browser/chunk-GTGYBRJA.mjs.map +7 -0
- package/dist/lib/browser/chunk-OZBLY7XM.mjs +76 -0
- package/dist/lib/browser/chunk-OZBLY7XM.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1156 -2508
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +114 -0
- package/dist/lib/browser/testing/index.mjs.map +7 -0
- package/dist/lib/browser/types/index.mjs +3 -5
- package/dist/lib/node-esm/chunk-DPNSZHU5.mjs +1750 -0
- package/dist/lib/node-esm/chunk-DPNSZHU5.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-WNZSKIRF.mjs +77 -0
- package/dist/lib/node-esm/chunk-WNZSKIRF.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1156 -2508
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +115 -0
- package/dist/lib/node-esm/testing/index.mjs.map +7 -0
- package/dist/lib/node-esm/types/index.mjs +3 -5
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +2 -3
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts +4 -2
- package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts +7 -7
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +37 -3
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +111 -0
- package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -0
- package/dist/types/src/components/Table/RowActionsMenu.d.ts +1 -1
- package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.d.ts +13 -9
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +108 -4
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +2 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +14 -9
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +12 -18
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +39 -4
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/index.d.ts +1 -0
- package/dist/types/src/components/TableCellEditor/index.d.ts.map +1 -1
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +4 -4
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +26 -56
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useAddRow.d.ts +13 -0
- package/dist/types/src/hooks/useAddRow.d.ts.map +1 -0
- package/dist/types/src/hooks/useProjectionModel.d.ts +6 -0
- package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -0
- package/dist/types/src/hooks/useTableModel.d.ts +11 -10
- package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/model/modal-controller.d.ts +5 -8
- package/dist/types/src/model/modal-controller.d.ts.map +1 -1
- package/dist/types/src/model/selection-model.d.ts +17 -7
- package/dist/types/src/model/selection-model.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +149 -32
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +13 -2
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/playwright/TableManager.d.ts +2 -1
- package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
- package/dist/types/src/playwright/playwright.config.d.ts +3 -0
- package/dist/types/src/playwright/playwright.config.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +2 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/testing/useTestTableModel.d.ts +25 -0
- package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +5 -6
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/Table.d.ts +31 -0
- package/dist/types/src/types/Table.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts +1 -1
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/src/util/dynamic-table.d.ts +15 -14
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- package/dist/types/src/util/dynamic-table.test.d.ts +2 -0
- package/dist/types/src/util/dynamic-table.test.d.ts.map +1 -0
- package/dist/types/src/util/index.d.ts +2 -1
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/query-ast.d.ts +7 -0
- package/dist/types/src/util/query-ast.d.ts.map +1 -0
- package/dist/types/src/util/schema.d.ts +1 -1
- package/dist/types/src/util/schema.d.ts.map +1 -1
- package/dist/types/src/util/sort.d.ts +12 -0
- package/dist/types/src/util/sort.d.ts.map +1 -0
- package/dist/types/src/util/table-buttons.d.ts +15 -1
- package/dist/types/src/util/table-buttons.d.ts.map +1 -1
- package/dist/types/src/util/table-controls.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +69 -56
- package/src/components/Table/ColumnActionsMenu.tsx +11 -7
- package/src/components/Table/ColumnSettings.tsx +16 -12
- package/src/components/Table/DynamicTable.stories.tsx +29 -25
- package/src/components/Table/DynamicTable.tsx +34 -22
- package/src/components/Table/Relations.stories.tsx +237 -0
- package/src/components/Table/RowActionsMenu.tsx +26 -17
- package/src/components/Table/Table.stories.tsx +171 -194
- package/src/components/Table/Table.tsx +364 -286
- package/src/components/TableCellEditor/CellValidationMessage.tsx +8 -3
- package/src/components/TableCellEditor/FormCellEditor.tsx +146 -49
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +36 -76
- package/src/components/TableCellEditor/TableCellEditor.tsx +106 -198
- package/src/components/TableCellEditor/index.ts +1 -0
- package/src/components/TableToolbar/TableToolbar.tsx +49 -33
- package/src/components/testing.ts +41 -32
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useAddRow.ts +36 -0
- package/src/hooks/useProjectionModel.ts +39 -0
- package/src/hooks/useTableModel.ts +41 -35
- package/src/index.ts +1 -2
- package/src/model/modal-controller.ts +24 -41
- package/src/model/selection-model.ts +75 -41
- package/src/model/table-model.test.ts +92 -56
- package/src/model/table-model.ts +628 -162
- package/src/model/table-presentation.test.ts +70 -59
- package/src/model/table-presentation.ts +278 -149
- package/src/playwright/TableManager.ts +13 -6
- package/src/playwright/playwright.config.ts +17 -0
- package/src/playwright/smoke.spec.ts +180 -41
- package/src/testing/index.ts +5 -0
- package/src/testing/useTestTableModel.ts +133 -0
- package/src/translations.ts +7 -5
- package/src/types/Table.ts +90 -0
- package/src/types/index.ts +2 -2
- package/src/util/dynamic-table.test.ts +70 -0
- package/src/util/dynamic-table.ts +50 -36
- package/src/util/index.ts +2 -1
- package/src/util/query-ast.ts +19 -0
- package/src/util/schema.ts +4 -4
- package/src/util/sort.ts +90 -0
- package/src/util/table-buttons.ts +28 -4
- package/src/util/table-controls.ts +4 -1
- package/dist/lib/browser/chunk-TL7CKQQV.mjs +0 -27
- package/dist/lib/browser/chunk-TL7CKQQV.mjs.map +0 -7
- package/dist/lib/node/chunk-O2VPBNS5.cjs +0 -50
- package/dist/lib/node/chunk-O2VPBNS5.cjs.map +0 -7
- package/dist/lib/node/index.cjs +0 -2739
- package/dist/lib/node/index.cjs.map +0 -7
- package/dist/lib/node/meta.json +0 -1
- package/dist/lib/node/types/index.cjs +0 -31
- package/dist/lib/node/types/index.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-GBT6GD4G.mjs +0 -29
- package/dist/lib/node-esm/chunk-GBT6GD4G.mjs.map +0 -7
- package/dist/types/src/components/Table/CreateRefPanel.d.ts +0 -9
- package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +0 -1
- package/dist/types/src/components/Table/relations.stories.d.ts +0 -9
- package/dist/types/src/components/Table/relations.stories.d.ts.map +0 -1
- package/dist/types/src/components/TableCellEditor/extension.d.ts +0 -13
- package/dist/types/src/components/TableCellEditor/extension.d.ts.map +0 -1
- package/dist/types/src/model/table-sorting.d.ts +0 -53
- package/dist/types/src/model/table-sorting.d.ts.map +0 -1
- package/dist/types/src/types/table.d.ts +0 -56
- package/dist/types/src/types/table.d.ts.map +0 -1
- package/dist/types/src/util/table.d.ts +0 -14
- package/dist/types/src/util/table.d.ts.map +0 -1
- package/src/components/Table/CreateRefPanel.tsx +0 -84
- package/src/components/Table/relations.stories.tsx +0 -124
- package/src/components/TableCellEditor/extension.ts +0 -61
- package/src/model/table-sorting.ts +0 -216
- package/src/playwright/playwright.config.cts +0 -18
- package/src/types/table.ts +0 -27
- package/src/util/table.ts +0 -127
|
@@ -4,16 +4,20 @@
|
|
|
4
4
|
|
|
5
5
|
import { expect, test } from '@playwright/test';
|
|
6
6
|
|
|
7
|
+
import { type DxGrid } from '@dxos/lit-grid';
|
|
8
|
+
import { faker } from '@dxos/random';
|
|
7
9
|
import { setupPage, storybookUrl } from '@dxos/test-utils/playwright';
|
|
8
10
|
|
|
9
11
|
import { TableManager } from './TableManager';
|
|
10
12
|
|
|
11
|
-
const storyUrl = storybookUrl('ui-react-ui-table-table--default');
|
|
13
|
+
const storyUrl = storybookUrl('ui-react-ui-table-table--default', 9004);
|
|
14
|
+
const relationsStoryUrl = storybookUrl('ui-react-ui-table-relations--default', 9004);
|
|
12
15
|
|
|
13
16
|
// NOTE(ZaymonFC): This test suite relies on the faker seed being set to 0 in the story.
|
|
14
17
|
test.describe('Table', () => {
|
|
15
18
|
test('Loads', async ({ browser, browserName }) => {
|
|
16
19
|
test.skip(browserName === 'webkit');
|
|
20
|
+
test.skip(browserName === 'firefox');
|
|
17
21
|
const { page } = await setupPage(browser, { url: storyUrl });
|
|
18
22
|
const table = new TableManager(page);
|
|
19
23
|
|
|
@@ -23,22 +27,24 @@ test.describe('Table', () => {
|
|
|
23
27
|
|
|
24
28
|
test('sort', async ({ browser, browserName }) => {
|
|
25
29
|
test.skip(browserName === 'webkit');
|
|
30
|
+
test.skip(browserName === 'firefox');
|
|
26
31
|
const { page } = await setupPage(browser, { url: storyUrl });
|
|
27
32
|
const table = new TableManager(page);
|
|
28
33
|
|
|
29
34
|
await table.grid.ready();
|
|
30
35
|
await table.sortColumn(0, 'descending');
|
|
31
|
-
await expect(table.grid.cell(0, 0, 'grid')).toHaveText('
|
|
32
|
-
await expect(table.grid.cell(0, 9, 'grid')).toContainText('
|
|
36
|
+
await expect(table.grid.cell(0, 0, 'grid')).toHaveText('Sit.');
|
|
37
|
+
await expect(table.grid.cell(0, 9, 'grid')).toContainText('Aut.');
|
|
33
38
|
|
|
34
39
|
await table.sortColumn(0, 'ascending');
|
|
35
|
-
await expect(table.grid.cell(0, 0, 'grid')).toContainText('
|
|
36
|
-
await expect(table.grid.cell(0, 9, 'grid')).toHaveText('
|
|
40
|
+
await expect(table.grid.cell(0, 0, 'grid')).toContainText('Aut.');
|
|
41
|
+
await expect(table.grid.cell(0, 9, 'grid')).toHaveText('Sit.');
|
|
37
42
|
await page.close();
|
|
38
43
|
});
|
|
39
44
|
|
|
40
45
|
test('selection', async ({ browser, browserName }) => {
|
|
41
46
|
test.skip(browserName === 'webkit');
|
|
47
|
+
test.skip(browserName === 'firefox');
|
|
42
48
|
const { page } = await setupPage(browser, { url: storyUrl });
|
|
43
49
|
const table = new TableManager(page);
|
|
44
50
|
|
|
@@ -60,17 +66,20 @@ test.describe('Table', () => {
|
|
|
60
66
|
|
|
61
67
|
test('delete row', async ({ browser, browserName }) => {
|
|
62
68
|
test.skip(browserName === 'webkit');
|
|
69
|
+
test.skip(browserName === 'firefox');
|
|
63
70
|
const { page } = await setupPage(browser, { url: storyUrl });
|
|
64
71
|
const table = new TableManager(page);
|
|
65
72
|
|
|
66
73
|
await table.grid.ready();
|
|
74
|
+
await expect(page.getByRole('gridcell', { name: 'Sapiente.' })).toHaveCount(1);
|
|
67
75
|
await table.deleteRow(0);
|
|
68
|
-
await expect(page.getByRole('gridcell', { name: '
|
|
76
|
+
await expect(page.getByRole('gridcell', { name: 'Sapiente.' })).toHaveCount(0);
|
|
69
77
|
await page.close();
|
|
70
78
|
});
|
|
71
79
|
|
|
72
80
|
test('delete row--select all', async ({ browser, browserName }) => {
|
|
73
81
|
test.skip(browserName === 'webkit');
|
|
82
|
+
test.skip(browserName === 'firefox');
|
|
74
83
|
const { page } = await setupPage(browser, { url: storyUrl });
|
|
75
84
|
const table = new TableManager(page);
|
|
76
85
|
|
|
@@ -80,14 +89,15 @@ test.describe('Table', () => {
|
|
|
80
89
|
// Delete action affects all selected rows.
|
|
81
90
|
await table.deleteRow(0);
|
|
82
91
|
|
|
83
|
-
await expect(page.getByRole('gridcell', { name: '
|
|
84
|
-
await expect(page.getByRole('gridcell', { name: '
|
|
92
|
+
await expect(page.getByRole('gridcell', { name: 'Aut.' })).toHaveCount(0);
|
|
93
|
+
await expect(page.getByRole('gridcell', { name: 'Beatae.' })).toHaveCount(0);
|
|
85
94
|
await expect(table.grid.cellsWithinPlane('grid')).toHaveCount(0);
|
|
86
95
|
await page.close();
|
|
87
96
|
});
|
|
88
97
|
|
|
89
98
|
test('delete column', async ({ browser, browserName }) => {
|
|
90
99
|
test.skip(browserName === 'webkit');
|
|
100
|
+
test.skip(browserName === 'firefox');
|
|
91
101
|
const { page } = await setupPage(browser, { url: storyUrl });
|
|
92
102
|
const table = new TableManager(page);
|
|
93
103
|
|
|
@@ -111,58 +121,39 @@ test.describe('Table', () => {
|
|
|
111
121
|
await page.close();
|
|
112
122
|
});
|
|
113
123
|
|
|
114
|
-
|
|
115
|
-
// TODO(ZaymonFC): Restore this after fixing format selection.
|
|
116
|
-
test('add column', async ({ browser, browserName }) => {
|
|
124
|
+
test('add column without changing format', async ({ browser, browserName }) => {
|
|
117
125
|
test.skip(browserName === 'webkit');
|
|
126
|
+
test.skip(browserName === 'firefox');
|
|
118
127
|
const { page } = await setupPage(browser, { url: storyUrl });
|
|
119
128
|
const table = new TableManager(page);
|
|
120
129
|
|
|
121
130
|
await table.grid.ready();
|
|
122
|
-
const newColumnLabel = '
|
|
131
|
+
const newColumnLabel = 'DEFAULT FORMAT COL';
|
|
123
132
|
|
|
124
|
-
await table.addColumn({ label: newColumnLabel
|
|
133
|
+
await table.addColumn({ label: newColumnLabel });
|
|
125
134
|
|
|
126
135
|
await expect(page.getByRole('gridcell', { name: newColumnLabel })).toBeVisible();
|
|
127
136
|
await page.close();
|
|
128
137
|
});
|
|
129
138
|
|
|
130
|
-
test('
|
|
139
|
+
test('add column with format', async ({ browser, browserName }) => {
|
|
131
140
|
test.skip(browserName === 'webkit');
|
|
141
|
+
test.skip(browserName === 'firefox');
|
|
132
142
|
const { page } = await setupPage(browser, { url: storyUrl });
|
|
133
143
|
const table = new TableManager(page);
|
|
134
144
|
|
|
135
145
|
await table.grid.ready();
|
|
146
|
+
const newColumnLabel = 'TEST LABEL';
|
|
136
147
|
|
|
137
|
-
|
|
138
|
-
// Scroll over to the manager column.
|
|
139
|
-
await table.grid.panByWheel(10000, 0);
|
|
140
|
-
|
|
141
|
-
await table.grid.cell(4, 0, 'grid').click();
|
|
142
|
-
await page.keyboard.press('A');
|
|
143
|
-
await page.keyboard.press('N');
|
|
144
|
-
await page.getByRole('option', { name: 'Anita Mayer' }).click();
|
|
145
|
-
|
|
146
|
-
// Assert that the value is shown in the cell.
|
|
147
|
-
await expect(page.getByRole('gridcell', { name: 'Anita Mayer' }).first()).toBeVisible();
|
|
148
|
-
|
|
149
|
-
// Create new object.
|
|
150
|
-
await table.grid.cell(4, 1, 'grid').click();
|
|
151
|
-
await page.keyboard.type('t');
|
|
152
|
-
await page.keyboard.type('e');
|
|
153
|
-
await page.keyboard.type('s');
|
|
154
|
-
await page.keyboard.type('t');
|
|
155
|
-
await page.getByText('Create new object', { exact: false }).click();
|
|
156
|
-
await page.getByTestId('save-button').click();
|
|
157
|
-
|
|
158
|
-
// Scroll to the left.
|
|
159
|
-
await page.getByRole('gridcell', { name: 'test' }).first().click();
|
|
148
|
+
await table.addColumn({ label: newColumnLabel, format: 'Number' });
|
|
160
149
|
|
|
150
|
+
await expect(page.getByRole('gridcell', { name: newColumnLabel })).toBeVisible();
|
|
161
151
|
await page.close();
|
|
162
152
|
});
|
|
163
153
|
|
|
164
|
-
test('test toggles', async ({ browser, browserName }) => {
|
|
154
|
+
test.skip('test toggles', async ({ browser, browserName }) => {
|
|
165
155
|
test.skip(browserName === 'webkit');
|
|
156
|
+
test.skip(browserName === 'firefox');
|
|
166
157
|
const { page } = await setupPage(browser, { url: storyUrl });
|
|
167
158
|
const table = new TableManager(page);
|
|
168
159
|
|
|
@@ -172,13 +163,161 @@ test.describe('Table', () => {
|
|
|
172
163
|
await page.getByTestId('table-switch').nth(7).click();
|
|
173
164
|
|
|
174
165
|
// Test that checks are durable in the data model by sorting.
|
|
175
|
-
await table.sortColumn(
|
|
166
|
+
await table.sortColumn(1, 'descending');
|
|
176
167
|
|
|
177
168
|
// Assert the first two switch checkboxes are checked.
|
|
178
169
|
await expect(page.getByTestId('table-switch').first()).toBeChecked();
|
|
179
170
|
await expect(page.getByTestId('table-switch').nth(1)).toBeChecked();
|
|
180
|
-
await expect(table.grid.cell(0, 0, 'grid')).toHaveText('
|
|
181
|
-
await expect(table.grid.cell(0, 1, 'grid')).toHaveText('
|
|
171
|
+
await expect(table.grid.cell(0, 0, 'grid')).toHaveText('Aut.');
|
|
172
|
+
await expect(table.grid.cell(0, 1, 'grid')).toHaveText('Beatae.');
|
|
173
|
+
|
|
174
|
+
await page.close();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('add row and edit cell', async ({ browser, browserName }) => {
|
|
178
|
+
test.skip(browserName === 'webkit');
|
|
179
|
+
test.skip(browserName === 'firefox');
|
|
180
|
+
const { page } = await setupPage(browser, { url: storyUrl });
|
|
181
|
+
const table = new TableManager(page);
|
|
182
|
+
|
|
183
|
+
await table.grid.ready();
|
|
184
|
+
|
|
185
|
+
// Click the CTA row at the bottom to add a new row.
|
|
186
|
+
await table.grid.cell(0, 0, 'frozenRowsEnd').click();
|
|
187
|
+
|
|
188
|
+
// All schema fields are optional so the row is added directly to the grid (not as a draft).
|
|
189
|
+
const newCell = table.grid.cell(0, 10, 'grid');
|
|
190
|
+
await newCell.waitFor({ state: 'visible' });
|
|
191
|
+
|
|
192
|
+
// Focus should already be on the new row; engage edit mode.
|
|
193
|
+
await page.keyboard.press('Enter');
|
|
194
|
+
await page.getByTestId('grid.cell-editor').waitFor({ state: 'visible' });
|
|
195
|
+
|
|
196
|
+
// Type text and confirm.
|
|
197
|
+
const cellContent = 'New row content';
|
|
198
|
+
await page.keyboard.type(cellContent);
|
|
199
|
+
await page.keyboard.press('Enter');
|
|
200
|
+
|
|
201
|
+
// Assert the cell content is saved.
|
|
202
|
+
await expect(newCell).toHaveText(cellContent);
|
|
203
|
+
await page.close();
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// TODO(wittjosiah): Remove? Conflicts with story play function which is run as a unit test anyways.
|
|
207
|
+
test.skip('extant relations work as expected', async ({ browser, browserName }) => {
|
|
208
|
+
test.skip(browserName === 'webkit');
|
|
209
|
+
test.skip(browserName === 'firefox');
|
|
210
|
+
const { page } = await setupPage(browser, { url: relationsStoryUrl });
|
|
211
|
+
|
|
212
|
+
// Wait for the page to load
|
|
213
|
+
await page.locator('dx-grid > .dx-grid').nth(1).waitFor({ state: 'visible' });
|
|
214
|
+
|
|
215
|
+
// Find the dx-grid element for the contactModel (second table)
|
|
216
|
+
// The contactModel is used in the second Table.Main component in the story
|
|
217
|
+
const dxGrid = page.locator('dx-grid').nth(1);
|
|
218
|
+
await dxGrid.waitFor({ state: 'visible' });
|
|
219
|
+
|
|
220
|
+
// Scroll to the last column (column 4)
|
|
221
|
+
await dxGrid.evaluate(async (dxGridElement: DxGrid) => {
|
|
222
|
+
dxGridElement.scrollToColumn(4);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// Click on the cell at aria-rowindex=0 aria-colindex=4 to focus it
|
|
226
|
+
const targetCell = dxGrid.locator('[data-dx-grid-plane="grid"] [aria-rowindex="0"][aria-colindex="4"]');
|
|
227
|
+
await targetCell.click();
|
|
228
|
+
|
|
229
|
+
// Click again to engage edit mode
|
|
230
|
+
await page.keyboard.press('Enter');
|
|
231
|
+
|
|
232
|
+
// Click on the combobox to open options
|
|
233
|
+
await page.getByRole('combobox').click();
|
|
234
|
+
await page.pause();
|
|
235
|
+
await page.getByPlaceholder('Search…').focus();
|
|
236
|
+
|
|
237
|
+
// Type the first few letters of an org name.
|
|
238
|
+
const orgName =
|
|
239
|
+
(await page
|
|
240
|
+
.locator('dx-grid')
|
|
241
|
+
.nth(0)
|
|
242
|
+
.locator('[data-dx-grid-plane="grid"] [aria-rowindex="0"][aria-colindex="0"] .dx-grid__cell__content')
|
|
243
|
+
.textContent()) ?? 'never';
|
|
244
|
+
await page.keyboard.type(orgName.substring(0, 4), { delay: 500 });
|
|
245
|
+
|
|
246
|
+
// Assert that there is an element with aria-selected on the page
|
|
247
|
+
await expect(page.locator('[role="option"][aria-selected]')).toBeVisible();
|
|
248
|
+
|
|
249
|
+
// Type the enter key
|
|
250
|
+
await page.keyboard.press('Enter');
|
|
251
|
+
|
|
252
|
+
// Save the result
|
|
253
|
+
await page.getByTestId('save-button').click();
|
|
254
|
+
|
|
255
|
+
// Assert that the cell element has the org name
|
|
256
|
+
await expect(targetCell).toHaveText(orgName);
|
|
257
|
+
|
|
258
|
+
await page.close();
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// TODO(wittjosiah): Remove? Conflicts with story play function which is run as a unit test anyways.
|
|
262
|
+
test.skip('new relations work as expected', async ({ browser, browserName }) => {
|
|
263
|
+
test.skip(browserName === 'webkit');
|
|
264
|
+
test.skip(browserName === 'firefox');
|
|
265
|
+
const { page } = await setupPage(browser, { url: relationsStoryUrl });
|
|
266
|
+
|
|
267
|
+
// Wait for the page to load
|
|
268
|
+
await page.locator('dx-grid > .dx-grid').nth(1).waitFor({ state: 'visible' });
|
|
269
|
+
|
|
270
|
+
// Find the dx-grid element for the contactModel (second table)
|
|
271
|
+
// The contactModel is used in the second Table.Main component in the story
|
|
272
|
+
const dxGrid = page.locator('dx-grid').nth(1);
|
|
273
|
+
await dxGrid.waitFor({ state: 'visible' });
|
|
274
|
+
|
|
275
|
+
// Scroll to the last column (column 4)
|
|
276
|
+
await dxGrid.evaluate(async (dxGridElement: DxGrid) => {
|
|
277
|
+
dxGridElement.scrollToColumn(4);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Click on the cell at aria-rowindex=0 aria-colindex=8 to focus it
|
|
281
|
+
const targetCell = dxGrid.locator('[data-dx-grid-plane="grid"] [aria-rowindex="0"][aria-colindex="4"]');
|
|
282
|
+
await targetCell.click();
|
|
283
|
+
|
|
284
|
+
// Click again to engage edit mode
|
|
285
|
+
await page.keyboard.press('Enter');
|
|
286
|
+
|
|
287
|
+
// Click on the combobox to open options
|
|
288
|
+
await page.getByRole('combobox').click();
|
|
289
|
+
await page.getByPlaceholder('Search…').focus();
|
|
290
|
+
|
|
291
|
+
// Type the first few letters of an org name.
|
|
292
|
+
const orgName = 'Sally';
|
|
293
|
+
await page.keyboard.type(orgName, { delay: 500 });
|
|
294
|
+
|
|
295
|
+
// Assert that there is an element with aria-selected on the page
|
|
296
|
+
await expect(page.locator('[role="option"][aria-selected]')).toBeVisible();
|
|
297
|
+
|
|
298
|
+
// Type the enter key
|
|
299
|
+
await page.keyboard.press('Enter');
|
|
300
|
+
await page.pause();
|
|
301
|
+
|
|
302
|
+
// Click the save button in the popover
|
|
303
|
+
await page.getByTestId('create-referenced-object-form').getByTestId('save-button').click();
|
|
304
|
+
|
|
305
|
+
// Assert that the cell element has the org name
|
|
306
|
+
await expect(targetCell).toHaveText(orgName);
|
|
307
|
+
|
|
308
|
+
// Make a change to a non-ref cell to check that populated refs don’t cause problems with snapshots or schemas.
|
|
309
|
+
await dxGrid.evaluate(async (dxGridElement: DxGrid) => {
|
|
310
|
+
dxGridElement.scrollToColumn(6);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
const nonRefContent = faker.lorem.words(3);
|
|
314
|
+
const nonRefCell = dxGrid.locator('[data-dx-grid-plane="grid"] [aria-rowindex="0"][aria-colindex="6"]');
|
|
315
|
+
await nonRefCell.click();
|
|
316
|
+
await page.keyboard.press('Enter');
|
|
317
|
+
await page.getByTestId('grid.cell-editor').waitFor({ state: 'visible' });
|
|
318
|
+
await page.keyboard.type(nonRefContent, { delay: 500 });
|
|
319
|
+
await page.keyboard.press('Enter');
|
|
320
|
+
await expect(nonRefCell).toHaveText(nonRefContent);
|
|
182
321
|
|
|
183
322
|
await page.close();
|
|
184
323
|
});
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { RegistryContext } from '@effect-atom/atom-react';
|
|
6
|
+
import { type RefObject, useCallback, useContext, useMemo, useRef } from 'react';
|
|
7
|
+
|
|
8
|
+
import { type Database, type Type } from '@dxos/echo';
|
|
9
|
+
import { isMutable } from '@dxos/echo/internal';
|
|
10
|
+
import { faker } from '@dxos/random';
|
|
11
|
+
import { Filter, useQuery, useSchema } from '@dxos/react-client/echo';
|
|
12
|
+
import { useClientStory } from '@dxos/react-client/testing';
|
|
13
|
+
import { useGlobalFilteredObjects } from '@dxos/react-ui-searchlist';
|
|
14
|
+
import { type ProjectionModel, getTypenameFromQuery } from '@dxos/schema';
|
|
15
|
+
|
|
16
|
+
import { type TableController } from '../components';
|
|
17
|
+
import { useAddRow, useProjectionModel, useTableModel } from '../hooks';
|
|
18
|
+
import { type TableModel, TablePresentation } from '../model';
|
|
19
|
+
import { Table } from '../types';
|
|
20
|
+
|
|
21
|
+
faker.seed(0); // NOTE(ZaymonFC): Required for smoke tests.
|
|
22
|
+
|
|
23
|
+
export type TestTableModel<T extends Type.Obj.Any = Type.Obj.Any> = {
|
|
24
|
+
schema: T | undefined;
|
|
25
|
+
table: Table.Table | undefined;
|
|
26
|
+
projection: ProjectionModel | undefined;
|
|
27
|
+
tableRef: RefObject<TableController | null>;
|
|
28
|
+
model: TableModel | undefined;
|
|
29
|
+
presentation: TablePresentation | undefined;
|
|
30
|
+
db: Database.Database | undefined;
|
|
31
|
+
handleInsertRow: () => void;
|
|
32
|
+
handleSaveView: () => void;
|
|
33
|
+
handleDeleteRows: (rowIndex: number, objects: any[]) => void;
|
|
34
|
+
handleDeleteColumn: (fieldId: string) => void;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Custom hook to create and manage a test table model for storybook demonstrations.
|
|
39
|
+
* Provides table data, schema, and handlers for table operations.
|
|
40
|
+
*/
|
|
41
|
+
export const useTestTableModel = <T extends Type.Obj.Any = Type.Obj.Any>(): TestTableModel<T> => {
|
|
42
|
+
const registry = useContext(RegistryContext);
|
|
43
|
+
const { space } = useClientStory();
|
|
44
|
+
const db = space?.db;
|
|
45
|
+
|
|
46
|
+
const tables = useQuery(space?.db, Filter.type(Table.Table));
|
|
47
|
+
const table = tables.at(0);
|
|
48
|
+
const typename = table?.view.target?.query ? getTypenameFromQuery(table.view.target.query.ast) : undefined;
|
|
49
|
+
const schema = useSchema<T>(space?.db, typename);
|
|
50
|
+
const projection = useProjectionModel(schema, table, registry);
|
|
51
|
+
|
|
52
|
+
const features = useMemo(
|
|
53
|
+
() => ({
|
|
54
|
+
selection: { enabled: true, mode: 'multiple' as const },
|
|
55
|
+
dataEditable: true,
|
|
56
|
+
schemaEditable: schema && isMutable(schema),
|
|
57
|
+
}),
|
|
58
|
+
[schema],
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const objects = useQuery(db, schema ? Filter.type(schema) : Filter.nothing());
|
|
62
|
+
const filteredObjects = useGlobalFilteredObjects(objects);
|
|
63
|
+
|
|
64
|
+
const tableRef = useRef<TableController>(null);
|
|
65
|
+
const handleCellUpdate = useCallback((cell: any) => {
|
|
66
|
+
tableRef.current?.update?.(cell);
|
|
67
|
+
}, []);
|
|
68
|
+
|
|
69
|
+
const handleRowOrderChange = useCallback(() => {
|
|
70
|
+
tableRef.current?.update?.();
|
|
71
|
+
}, []);
|
|
72
|
+
|
|
73
|
+
const addRow = useAddRow({ db, schema });
|
|
74
|
+
|
|
75
|
+
const handleDeleteRows = useCallback(
|
|
76
|
+
(_: number, objects: any[]) => {
|
|
77
|
+
for (const object of objects) {
|
|
78
|
+
db?.remove(object);
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
[db],
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const handleDeleteColumn = useCallback(
|
|
85
|
+
(fieldId: string) => {
|
|
86
|
+
if (projection) {
|
|
87
|
+
projection.deleteFieldProjection(fieldId);
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
[projection],
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const model = useTableModel({
|
|
94
|
+
object: table,
|
|
95
|
+
projection,
|
|
96
|
+
features,
|
|
97
|
+
rows: filteredObjects,
|
|
98
|
+
onInsertRow: addRow,
|
|
99
|
+
onDeleteRows: handleDeleteRows,
|
|
100
|
+
onColumnDelete: handleDeleteColumn,
|
|
101
|
+
onCellUpdate: handleCellUpdate,
|
|
102
|
+
onRowOrderChange: handleRowOrderChange,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const handleInsertRow = useCallback(() => {
|
|
106
|
+
const insertResult = model?.insertRow();
|
|
107
|
+
tableRef.current?.handleInsertRowResult?.(insertResult);
|
|
108
|
+
}, [model]);
|
|
109
|
+
|
|
110
|
+
const handleSaveView = useCallback(() => {
|
|
111
|
+
model?.saveView();
|
|
112
|
+
}, [model]);
|
|
113
|
+
|
|
114
|
+
const presentation = useMemo(() => {
|
|
115
|
+
if (model) {
|
|
116
|
+
return new TablePresentation(registry, model);
|
|
117
|
+
}
|
|
118
|
+
}, [registry, model]);
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
schema,
|
|
122
|
+
table,
|
|
123
|
+
projection,
|
|
124
|
+
tableRef,
|
|
125
|
+
model,
|
|
126
|
+
presentation,
|
|
127
|
+
db,
|
|
128
|
+
handleInsertRow,
|
|
129
|
+
handleSaveView,
|
|
130
|
+
handleDeleteRows,
|
|
131
|
+
handleDeleteColumn,
|
|
132
|
+
};
|
|
133
|
+
};
|
package/src/translations.ts
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import { type Resource } from '@dxos/react-ui';
|
|
6
6
|
|
|
7
|
-
export
|
|
7
|
+
export const translationKey = '@dxos/react-ui-table';
|
|
8
|
+
|
|
9
|
+
export const translations = [
|
|
8
10
|
{
|
|
9
11
|
'en-US': {
|
|
10
12
|
[translationKey]: {
|
|
@@ -12,8 +14,8 @@ export default [
|
|
|
12
14
|
'create object label': 'Create table',
|
|
13
15
|
'table name placeholder': 'Table name',
|
|
14
16
|
'settings title': 'Table settings',
|
|
15
|
-
'table schema label': '
|
|
16
|
-
'new schema': 'New
|
|
17
|
+
'table schema label': 'Type',
|
|
18
|
+
'new schema': 'New type',
|
|
17
19
|
'continue label': 'Continue',
|
|
18
20
|
'create stack section label': 'Create table',
|
|
19
21
|
'add row label': 'Add row',
|
|
@@ -32,4 +34,4 @@ export default [
|
|
|
32
34
|
},
|
|
33
35
|
},
|
|
34
36
|
},
|
|
35
|
-
] as const;
|
|
37
|
+
] as const satisfies Resource[];
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Match from 'effect/Match';
|
|
6
|
+
import * as Schema from 'effect/Schema';
|
|
7
|
+
import * as SchemaAST from 'effect/SchemaAST';
|
|
8
|
+
|
|
9
|
+
import { Obj, Ref, Type } from '@dxos/echo';
|
|
10
|
+
import { FormInputAnnotation, type JsonPath, type JsonSchemaType, LabelAnnotation } from '@dxos/echo/internal';
|
|
11
|
+
import { View, ViewAnnotation } from '@dxos/schema';
|
|
12
|
+
|
|
13
|
+
export const Table = Schema.Struct({
|
|
14
|
+
name: Schema.String.pipe(Schema.optional),
|
|
15
|
+
|
|
16
|
+
view: Type.Ref(View.View).pipe(FormInputAnnotation.set(false)),
|
|
17
|
+
|
|
18
|
+
sizes: Schema.Record({
|
|
19
|
+
// TODO(wittjosiah): Should be JsonPath.
|
|
20
|
+
key: Schema.String,
|
|
21
|
+
value: Schema.Number,
|
|
22
|
+
}).pipe(Schema.mutable, FormInputAnnotation.set(false)),
|
|
23
|
+
}).pipe(
|
|
24
|
+
Type.object({
|
|
25
|
+
typename: 'dxos.org/type/Table',
|
|
26
|
+
version: '0.2.0',
|
|
27
|
+
}),
|
|
28
|
+
LabelAnnotation.set(['name']),
|
|
29
|
+
ViewAnnotation.set(true),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
export interface Table extends Schema.Schema.Type<typeof Table> {}
|
|
33
|
+
|
|
34
|
+
type MakeProps = {
|
|
35
|
+
name?: string;
|
|
36
|
+
sizes?: Record<string, number>;
|
|
37
|
+
view: View.View;
|
|
38
|
+
/** Required to auto-size columns. */
|
|
39
|
+
jsonSchema?: JsonSchemaType;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Make a table as a view of a data set.
|
|
44
|
+
*/
|
|
45
|
+
export const make = ({ name, sizes = {}, view, jsonSchema }: MakeProps): Table => {
|
|
46
|
+
const table = Obj.make(Table, { name, view: Ref.make(view), sizes } as Obj.MakeProps<typeof Table>);
|
|
47
|
+
|
|
48
|
+
// Preset sizes.
|
|
49
|
+
if (jsonSchema) {
|
|
50
|
+
const schema = Type.toEffectSchema(jsonSchema);
|
|
51
|
+
const properties = SchemaAST.getPropertySignatures(schema.ast);
|
|
52
|
+
for (const property of properties) {
|
|
53
|
+
const name = property.name.toString() as JsonPath;
|
|
54
|
+
if (sizes?.[name]) {
|
|
55
|
+
table.sizes[name] = sizes[name];
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
Match.type<SchemaAST.AST>().pipe(
|
|
60
|
+
Match.when({ _tag: 'BooleanKeyword' }, () => {
|
|
61
|
+
table.sizes[name] = 100;
|
|
62
|
+
}),
|
|
63
|
+
Match.when({ _tag: 'NumberKeyword' }, () => {
|
|
64
|
+
table.sizes[name] = 100;
|
|
65
|
+
}),
|
|
66
|
+
Match.orElse(() => {
|
|
67
|
+
// Noop.
|
|
68
|
+
}),
|
|
69
|
+
)(property.type);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return table;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
//
|
|
77
|
+
// V1
|
|
78
|
+
//
|
|
79
|
+
|
|
80
|
+
export const TableV1 = Schema.Struct({
|
|
81
|
+
sizes: Schema.Record({
|
|
82
|
+
key: Schema.String,
|
|
83
|
+
value: Schema.Number,
|
|
84
|
+
}).pipe(Schema.mutable),
|
|
85
|
+
}).pipe(
|
|
86
|
+
Type.object({
|
|
87
|
+
typename: 'dxos.org/type/Table',
|
|
88
|
+
version: '0.1.0',
|
|
89
|
+
}),
|
|
90
|
+
);
|
package/src/types/index.ts
CHANGED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Registry } from '@effect-atom/atom-react';
|
|
6
|
+
import { describe, expect, test } from 'vitest';
|
|
7
|
+
|
|
8
|
+
import { Format } from '@dxos/echo';
|
|
9
|
+
|
|
10
|
+
import { type TablePropertyDefinition, getBaseSchema, makeDynamicTable } from './dynamic-table';
|
|
11
|
+
|
|
12
|
+
describe('makeDynamicTable', () => {
|
|
13
|
+
/**
|
|
14
|
+
* Base case: plain jsonSchema (not from Echo / Type.toJsonSchema). Does not exercise the path
|
|
15
|
+
* where projection or schema are reactive, so this does not reproduce the Obj.change regression.
|
|
16
|
+
* Confirms makeDynamicTable + setProperties (title) works with plain objects.
|
|
17
|
+
*/
|
|
18
|
+
test('makeDynamicTable with plain jsonSchema and properties with title does not throw', () => {
|
|
19
|
+
const registry = Registry.make();
|
|
20
|
+
const jsonSchema = {
|
|
21
|
+
type: 'object' as const,
|
|
22
|
+
properties: {
|
|
23
|
+
id: { type: 'string' as const },
|
|
24
|
+
name: { type: 'string' as const },
|
|
25
|
+
},
|
|
26
|
+
required: ['id'],
|
|
27
|
+
};
|
|
28
|
+
const properties: TablePropertyDefinition[] = [
|
|
29
|
+
{ name: 'id', format: Format.TypeFormat.String },
|
|
30
|
+
{ name: 'name', format: Format.TypeFormat.String, title: 'Name' },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
expect(() => {
|
|
34
|
+
const { projection, object } = makeDynamicTable({
|
|
35
|
+
registry,
|
|
36
|
+
jsonSchema,
|
|
37
|
+
properties,
|
|
38
|
+
});
|
|
39
|
+
expect(projection).toBeDefined();
|
|
40
|
+
expect(object).toBeDefined();
|
|
41
|
+
expect(projection.getFields().length).toBeGreaterThan(0);
|
|
42
|
+
}).not.toThrow();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Regression test for the path where jsonSchema comes from getBaseSchema(typename, properties).
|
|
47
|
+
* That path uses Echo (View.make, Type.toJsonSchema); mutations must run inside Obj.change and on a
|
|
48
|
+
* cloned schema. This test ensures that flow does not throw.
|
|
49
|
+
*/
|
|
50
|
+
test('makeDynamicTable with jsonSchema from getBaseSchema(typename, properties) and properties with title does not throw', () => {
|
|
51
|
+
const registry = Registry.make();
|
|
52
|
+
const typename = 'example.com/type/DynamicTableTest';
|
|
53
|
+
const properties: TablePropertyDefinition[] = [
|
|
54
|
+
{ name: 'id', format: Format.TypeFormat.String },
|
|
55
|
+
{ name: 'name', format: Format.TypeFormat.String, title: 'Name' },
|
|
56
|
+
];
|
|
57
|
+
const { jsonSchema } = getBaseSchema({ typename, properties });
|
|
58
|
+
|
|
59
|
+
expect(() => {
|
|
60
|
+
const { projection, object } = makeDynamicTable({
|
|
61
|
+
registry,
|
|
62
|
+
jsonSchema,
|
|
63
|
+
properties,
|
|
64
|
+
});
|
|
65
|
+
expect(projection).toBeDefined();
|
|
66
|
+
expect(object).toBeDefined();
|
|
67
|
+
expect(projection.getFields().length).toBeGreaterThan(0);
|
|
68
|
+
}).not.toThrow();
|
|
69
|
+
});
|
|
70
|
+
});
|