@arbor-education/design-system.components 0.3.0 → 0.3.2

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.
Files changed (98) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.d.ts +1 -0
  3. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.d.ts.map +1 -1
  4. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.js +19 -12
  5. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.js.map +1 -1
  6. package/dist/components/table/DSDefaultColDef.d.ts +5 -0
  7. package/dist/components/table/DSDefaultColDef.d.ts.map +1 -0
  8. package/dist/components/table/DSDefaultColDef.js +32 -0
  9. package/dist/components/table/DSDefaultColDef.js.map +1 -0
  10. package/dist/components/table/Table.d.ts +6 -6
  11. package/dist/components/table/Table.d.ts.map +1 -1
  12. package/dist/components/table/Table.js +43 -22
  13. package/dist/components/table/Table.js.map +1 -1
  14. package/dist/components/table/Table.stories.d.ts +3 -0
  15. package/dist/components/table/Table.stories.d.ts.map +1 -1
  16. package/dist/components/table/Table.stories.js +265 -24
  17. package/dist/components/table/Table.stories.js.map +1 -1
  18. package/dist/components/table/Table.test.js +362 -7
  19. package/dist/components/table/Table.test.js.map +1 -1
  20. package/dist/components/table/cellColorStyles.d.ts +34 -0
  21. package/dist/components/table/cellColorStyles.d.ts.map +1 -0
  22. package/dist/components/table/cellColorStyles.js +52 -0
  23. package/dist/components/table/cellColorStyles.js.map +1 -0
  24. package/dist/components/table/cellRenderers/ButtonCellRenderer.d.ts.map +1 -1
  25. package/dist/components/table/cellRenderers/ButtonCellRenderer.js +15 -2
  26. package/dist/components/table/cellRenderers/ButtonCellRenderer.js.map +1 -1
  27. package/dist/components/table/cellRenderers/DefaultCellRenderer.d.ts +3 -0
  28. package/dist/components/table/cellRenderers/DefaultCellRenderer.d.ts.map +1 -0
  29. package/dist/components/table/cellRenderers/DefaultCellRenderer.js +11 -0
  30. package/dist/components/table/cellRenderers/DefaultCellRenderer.js.map +1 -0
  31. package/dist/components/table/cellRenderers/InlineTextCellRenderer.d.ts +7 -0
  32. package/dist/components/table/cellRenderers/InlineTextCellRenderer.d.ts.map +1 -0
  33. package/dist/components/table/cellRenderers/InlineTextCellRenderer.js +7 -0
  34. package/dist/components/table/cellRenderers/InlineTextCellRenderer.js.map +1 -0
  35. package/dist/components/table/cellRenderers/SelectDropdownCellEditor.d.ts +8 -0
  36. package/dist/components/table/cellRenderers/SelectDropdownCellEditor.d.ts.map +1 -0
  37. package/dist/components/table/cellRenderers/SelectDropdownCellEditor.js +19 -0
  38. package/dist/components/table/cellRenderers/SelectDropdownCellEditor.js.map +1 -0
  39. package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.d.ts +8 -0
  40. package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.d.ts.map +1 -0
  41. package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.js +18 -0
  42. package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.js.map +1 -0
  43. package/dist/components/table/pagination/TableSettingsDropdown.js +4 -5
  44. package/dist/components/table/pagination/TableSettingsDropdown.js.map +1 -1
  45. package/dist/components/table/theme/baseThemeParams.d.ts +14 -0
  46. package/dist/components/table/theme/baseThemeParams.d.ts.map +1 -0
  47. package/dist/components/table/{tableTheme.js → theme/baseThemeParams.js} +3 -7
  48. package/dist/components/table/theme/baseThemeParams.js.map +1 -0
  49. package/dist/components/table/theme/defaultTheme.d.ts +2 -0
  50. package/dist/components/table/theme/defaultTheme.d.ts.map +1 -0
  51. package/dist/components/table/theme/defaultTheme.js +9 -0
  52. package/dist/components/table/theme/defaultTheme.js.map +1 -0
  53. package/dist/components/table/theme/tidyTheme.d.ts +2 -0
  54. package/dist/components/table/theme/tidyTheme.d.ts.map +1 -0
  55. package/dist/components/table/theme/tidyTheme.js +10 -0
  56. package/dist/components/table/theme/tidyTheme.js.map +1 -0
  57. package/dist/components/table/useTableSettings.d.ts +5 -2
  58. package/dist/components/table/useTableSettings.d.ts.map +1 -1
  59. package/dist/components/table/useTableSettings.js +11 -3
  60. package/dist/components/table/useTableSettings.js.map +1 -1
  61. package/dist/index.css +14 -0
  62. package/dist/index.css.map +1 -1
  63. package/dist/index.d.ts +2 -0
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +2 -0
  66. package/dist/index.js.map +1 -1
  67. package/dist/utils/focusFirstFocusableElement.d.ts +2 -0
  68. package/dist/utils/focusFirstFocusableElement.d.ts.map +1 -0
  69. package/dist/utils/focusFirstFocusableElement.js +12 -0
  70. package/dist/utils/focusFirstFocusableElement.js.map +1 -0
  71. package/dist/utils/focusFirstFocusableElement.test.d.ts +2 -0
  72. package/dist/utils/focusFirstFocusableElement.test.d.ts.map +1 -0
  73. package/dist/utils/focusFirstFocusableElement.test.js +109 -0
  74. package/dist/utils/focusFirstFocusableElement.test.js.map +1 -0
  75. package/package.json +1 -1
  76. package/src/components/formField/inputs/selectDropdown/SelectDropdown.tsx +20 -14
  77. package/src/components/table/DSDefaultColDef.ts +37 -0
  78. package/src/components/table/Table.stories.tsx +284 -28
  79. package/src/components/table/Table.test.tsx +543 -7
  80. package/src/components/table/Table.tsx +55 -23
  81. package/src/components/table/cellColorStyles.ts +70 -0
  82. package/src/components/table/cellRenderers/ButtonCellRenderer.tsx +16 -2
  83. package/src/components/table/cellRenderers/DefaultCellRenderer.tsx +23 -0
  84. package/src/components/table/cellRenderers/InlineTextCellRenderer.tsx +10 -0
  85. package/src/components/table/cellRenderers/SelectDropdownCellEditor.tsx +43 -0
  86. package/src/components/table/cellRenderers/SelectDropdownCellRenderer.tsx +37 -0
  87. package/src/components/table/pagination/TableSettingsDropdown.tsx +5 -5
  88. package/src/components/table/table.scss +16 -0
  89. package/src/components/table/{tableTheme.ts → theme/baseThemeParams.ts} +2 -7
  90. package/src/components/table/theme/defaultTheme.ts +9 -0
  91. package/src/components/table/theme/tidyTheme.ts +10 -0
  92. package/src/components/table/useTableSettings.ts +15 -3
  93. package/src/index.ts +2 -0
  94. package/src/utils/focusFirstFocusableElement.test.ts +150 -0
  95. package/src/utils/focusFirstFocusableElement.ts +21 -0
  96. package/dist/components/table/tableTheme.d.ts +0 -2
  97. package/dist/components/table/tableTheme.d.ts.map +0 -1
  98. package/dist/components/table/tableTheme.js.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, expectTypeOf, test, vi } from 'vitest';
2
- import { render, screen, waitFor } from '@testing-library/react';
2
+ import { act, render, screen, waitFor } from '@testing-library/react';
3
3
  import { Table, TABLE_SPACING } from './Table';
4
4
  import '@testing-library/jest-dom/vitest';
5
5
  import { BulkActionsDropdown } from 'Components/table/BulkActionsDropdown';
@@ -7,6 +7,7 @@ import { HideColumnsDropdown } from 'Components/table/HideColumnsDropdown';
7
7
  import { TableSettingsDropdown } from './pagination/TableSettingsDropdown';
8
8
  import userEvent from '@testing-library/user-event';
9
9
  import type { GridApi } from 'ag-grid-enterprise';
10
+ import * as focusFirstFocusableElementModule from 'Utils/focusFirstFocusableElement';
10
11
 
11
12
  describe('Table', () => {
12
13
  test('renders table container', () => {
@@ -594,7 +595,7 @@ describe('Table', () => {
594
595
  });
595
596
  });
596
597
 
597
- test('Cell colours checkbox is unchecked by default', async () => {
598
+ test('Cell colours checkbox is checked by default', async () => {
598
599
  render(
599
600
  <Table
600
601
  headerContent={<TableSettingsDropdown />}
@@ -605,11 +606,11 @@ describe('Table', () => {
605
606
 
606
607
  await waitFor(() => {
607
608
  const cellColoursCheckbox = screen.getByLabelText('Cell colours') as HTMLInputElement;
608
- expect(cellColoursCheckbox.checked).toBe(false);
609
+ expect(cellColoursCheckbox.checked).toBe(true);
609
610
  });
610
611
  });
611
612
 
612
- test('can toggle Cell colours checkbox on', async () => {
613
+ test('can toggle Cell colours checkbox off', async () => {
613
614
  render(
614
615
  <Table
615
616
  headerContent={<TableSettingsDropdown />}
@@ -622,11 +623,11 @@ describe('Table', () => {
622
623
  await userEvent.click(cellColoursCheckbox);
623
624
 
624
625
  await waitFor(() => {
625
- expect((cellColoursCheckbox as HTMLInputElement).checked).toBe(true);
626
+ expect((cellColoursCheckbox as HTMLInputElement).checked).toBe(false);
626
627
  });
627
628
  });
628
629
 
629
- test('can toggle Cell colours checkbox off after turning it on', async () => {
630
+ test('can toggle Cell colours checkbox on after turning it off', async () => {
630
631
  render(
631
632
  <Table
632
633
  headerContent={<TableSettingsDropdown />}
@@ -640,7 +641,7 @@ describe('Table', () => {
640
641
  await userEvent.click(cellColoursCheckbox);
641
642
 
642
643
  await waitFor(() => {
643
- expect((cellColoursCheckbox as HTMLInputElement).checked).toBe(false);
644
+ expect((cellColoursCheckbox as HTMLInputElement).checked).toBe(true);
644
645
  });
645
646
  });
646
647
 
@@ -960,6 +961,215 @@ describe('Table', () => {
960
961
  expect(onColumnBordersChanged).toHaveBeenCalledWith(false);
961
962
  });
962
963
  });
964
+
965
+ test('applies both foreground and background colours when enabled', async () => {
966
+ const columnDefs = [
967
+ {
968
+ field: 'status',
969
+ headerName: 'Status',
970
+ },
971
+ ];
972
+
973
+ const rowData = [
974
+ {
975
+ status: {
976
+ foregroundColor: 'rgb(255, 255, 255)',
977
+ backgroundColor: 'rgb(0, 0, 255)',
978
+ },
979
+ },
980
+ ];
981
+
982
+ const { container } = render(
983
+ <Table
984
+ columnDefs={columnDefs}
985
+ rowData={rowData}
986
+ headerContent={<TableSettingsDropdown />}
987
+ />,
988
+ );
989
+
990
+ await waitFor(() => expect(screen.getByRole('grid')).toBeInTheDocument());
991
+
992
+ // Check that both colours are applied by default
993
+ await waitFor(() => {
994
+ const cells = container.querySelectorAll('.ag-cell');
995
+ const statusCell = Array.from(cells).find(cell =>
996
+ cell.getAttribute('col-id') === 'status',
997
+ ) as HTMLElement;
998
+ expect(statusCell).toBeDefined();
999
+ expect(statusCell.style.color).toBe('rgb(255, 255, 255)');
1000
+ expect(statusCell.style.backgroundColor).toBe('rgb(0, 0, 255)');
1001
+ });
1002
+ });
1003
+
1004
+ test('re-applies colours when cell colours is re-enabled', async () => {
1005
+ const columnDefs = [
1006
+ {
1007
+ field: 'status',
1008
+ headerName: 'Status',
1009
+ },
1010
+ ];
1011
+
1012
+ const rowData = [
1013
+ { status: { backgroundColor: 'rgb(255, 165, 0)' } },
1014
+ ];
1015
+
1016
+ const { container } = render(
1017
+ <Table
1018
+ columnDefs={columnDefs}
1019
+ rowData={rowData}
1020
+ headerContent={<TableSettingsDropdown />}
1021
+ />,
1022
+ );
1023
+
1024
+ await waitFor(() => expect(screen.getByRole('grid')).toBeInTheDocument());
1025
+
1026
+ // Disable and then re-enable cell colours
1027
+ await userEvent.click(screen.getByRole('button', { name: /Table settings/i }));
1028
+ const cellColoursCheckbox = screen.getByLabelText('Cell colours');
1029
+ await userEvent.click(cellColoursCheckbox);
1030
+ await userEvent.click(cellColoursCheckbox);
1031
+
1032
+ // Check that background colour is re-applied
1033
+ await waitFor(() => {
1034
+ const cells = container.querySelectorAll('.ag-cell');
1035
+ const statusCell = Array.from(cells).find(cell =>
1036
+ cell.getAttribute('col-id') === 'status',
1037
+ ) as HTMLElement;
1038
+ expect(statusCell).toBeDefined();
1039
+ expect(statusCell.style.backgroundColor).toBe('rgb(255, 165, 0)');
1040
+ });
1041
+ });
1042
+
1043
+ test('cell colours are safe for cells with nullish/empty values', async () => {
1044
+ const columnDefs = [
1045
+ {
1046
+ field: 'status',
1047
+ headerName: 'Status',
1048
+ },
1049
+ ];
1050
+
1051
+ const rowData = [
1052
+ { },
1053
+ ];
1054
+ await expect(async () => {
1055
+ render(
1056
+ <Table
1057
+ headerContent={<TableSettingsDropdown />}
1058
+ columnDefs={columnDefs}
1059
+ rowData={rowData}
1060
+ />,
1061
+ );
1062
+ }).not.toThrow();
1063
+ });
1064
+
1065
+ test('onCellColorsChanged gets called when cell colours toggle', async () => {
1066
+ const onCellColorsChanged = vi.fn();
1067
+
1068
+ render(
1069
+ <Table
1070
+ headerContent={<TableSettingsDropdown />}
1071
+ onCellColorsChanged={onCellColorsChanged}
1072
+ />,
1073
+ );
1074
+
1075
+ await userEvent.click(screen.getByRole('button', { name: /Table settings/i }));
1076
+
1077
+ const cellColoursCheckbox = screen.getByLabelText('Cell colours');
1078
+ await userEvent.click(cellColoursCheckbox);
1079
+
1080
+ await waitFor(() => {
1081
+ expect(onCellColorsChanged).toHaveBeenCalledExactlyOnceWith(false);
1082
+ });
1083
+
1084
+ await userEvent.click(cellColoursCheckbox);
1085
+
1086
+ await waitFor(() => {
1087
+ expect(onCellColorsChanged).toHaveBeenCalledTimes(2);
1088
+ expect(onCellColorsChanged).toHaveBeenNthCalledWith(2, true);
1089
+ });
1090
+ });
1091
+
1092
+ test('onCellColorsChanged is called when reset button is clicked', async () => {
1093
+ const onCellColorsChanged = vi.fn();
1094
+
1095
+ render(
1096
+ <Table
1097
+ headerContent={<TableSettingsDropdown />}
1098
+ onCellColorsChanged={onCellColorsChanged}
1099
+ />,
1100
+ );
1101
+
1102
+ await userEvent.click(screen.getByRole('button', { name: /Table settings/i }));
1103
+
1104
+ const cellColoursCheckbox = screen.getByLabelText('Cell colours');
1105
+ await userEvent.click(cellColoursCheckbox);
1106
+
1107
+ await waitFor(() => {
1108
+ expect(onCellColorsChanged).toHaveBeenCalledWith(false);
1109
+ });
1110
+
1111
+ const resetButton = screen.getByRole('button', { name: /Reset Table Settings/i });
1112
+ await userEvent.click(resetButton);
1113
+
1114
+ await waitFor(() => {
1115
+ expect(onCellColorsChanged).toHaveBeenCalledWith(true);
1116
+ });
1117
+ });
1118
+
1119
+ test('reset button resets cell colours to default (on)', async () => {
1120
+ render(
1121
+ <Table
1122
+ headerContent={<TableSettingsDropdown />}
1123
+ />,
1124
+ );
1125
+
1126
+ await userEvent.click(screen.getByRole('button', { name: /Table settings/i }));
1127
+
1128
+ // Turn off cell colours
1129
+ const cellColoursCheckbox = screen.getByLabelText('Cell colours');
1130
+ await userEvent.click(cellColoursCheckbox);
1131
+
1132
+ await waitFor(() => {
1133
+ expect((cellColoursCheckbox as HTMLInputElement).checked).toBe(false);
1134
+ });
1135
+
1136
+ // Click reset button
1137
+ const resetButton = screen.getByRole('button', { name: /Reset Table Settings/i });
1138
+ await userEvent.click(resetButton);
1139
+
1140
+ await waitFor(() => {
1141
+ expect((cellColoursCheckbox as HTMLInputElement).checked).toBe(true);
1142
+ });
1143
+ });
1144
+
1145
+ test('cell colours setting persists when dropdown is closed and reopened', async () => {
1146
+ render(
1147
+ <Table
1148
+ headerContent={<TableSettingsDropdown />}
1149
+ />,
1150
+ );
1151
+
1152
+ // Open dropdown and toggle cell colours
1153
+ await userEvent.click(screen.getByRole('button', { name: /Table settings/i }));
1154
+
1155
+ const cellColoursCheckbox = screen.getByLabelText('Cell colours');
1156
+ await userEvent.click(cellColoursCheckbox);
1157
+
1158
+ await waitFor(() => {
1159
+ expect((cellColoursCheckbox as HTMLInputElement).checked).toBe(false);
1160
+ });
1161
+
1162
+ // Close dropdown
1163
+ await userEvent.keyboard('{Escape}');
1164
+
1165
+ // Reopen dropdown
1166
+ await userEvent.click(screen.getByRole('button', { name: /Table settings/i }));
1167
+
1168
+ // Check setting persisted
1169
+ await waitFor(() => {
1170
+ expect((screen.getByLabelText('Cell colours') as HTMLInputElement).checked).toBe(false);
1171
+ });
1172
+ });
963
1173
  });
964
1174
 
965
1175
  describe('ButtonCellRenderer', () => {
@@ -1025,4 +1235,330 @@ describe('Table', () => {
1025
1235
  expect(handleClick2).toHaveBeenCalledTimes(1);
1026
1236
  });
1027
1237
  });
1238
+
1239
+ describe('SelectDropdownCellRenderer', () => {
1240
+ const options = [{ label: 'Option 1', value: 'option1' }, { label: 'Option 2', value: 'option2' }];
1241
+ const columnDefs = [{
1242
+ field: 'selectField',
1243
+ headerName: 'Select Field',
1244
+ cellRenderer: 'dsSelectDropdownCellRenderer',
1245
+ cellRendererParams: { options: options, placeholder: 'Placeholder Text' },
1246
+ }];
1247
+
1248
+ test('renders with placeholder', () => {
1249
+ const rowData = [{ selectField: null }];
1250
+ render(
1251
+ <Table
1252
+ columnDefs={columnDefs}
1253
+ rowData={rowData}
1254
+ />,
1255
+ );
1256
+ expect(screen.getByText('Placeholder Text')).toBeInTheDocument();
1257
+ });
1258
+
1259
+ test('renders with option', () => {
1260
+ const rowData = [{ selectField: 'option1' }];
1261
+ render(
1262
+ <Table
1263
+ columnDefs={columnDefs}
1264
+ rowData={rowData}
1265
+ />,
1266
+ );
1267
+ expect(screen.getByText('Option 1')).toBeInTheDocument();
1268
+ });
1269
+
1270
+ test('renders with value not in options as raw string', () => {
1271
+ const rowData = [{ selectField: 'unknown-value' }];
1272
+ render(
1273
+ <Table
1274
+ columnDefs={columnDefs}
1275
+ rowData={rowData}
1276
+ />,
1277
+ );
1278
+ expect(screen.getByText('unknown-value')).toBeInTheDocument();
1279
+ });
1280
+
1281
+ test('renders with empty string shows placeholder', () => {
1282
+ const rowData = [{ selectField: '' }];
1283
+ render(
1284
+ <Table
1285
+ columnDefs={columnDefs}
1286
+ rowData={rowData}
1287
+ />,
1288
+ );
1289
+ expect(screen.getByText('Placeholder Text')).toBeInTheDocument();
1290
+ });
1291
+
1292
+ test('renders dropdown button variant in cell', () => {
1293
+ const rowData = [{ selectField: 'option1' }];
1294
+ const { container } = render(
1295
+ <Table
1296
+ columnDefs={columnDefs}
1297
+ rowData={rowData}
1298
+ />,
1299
+ );
1300
+ const dropdownButton = container.querySelector('.ds-table__select-dropdown-cell.ds-button--dropdown');
1301
+ expect(dropdownButton).toBeInTheDocument();
1302
+ });
1303
+ });
1304
+
1305
+ describe('SelectDropdownCellEditor', () => {
1306
+ const options = [
1307
+ { label: 'Option 1', value: 'option1' },
1308
+ { label: 'Option 2', value: 'option2' },
1309
+ { label: 'Option 3', value: 'option3' },
1310
+ ];
1311
+ const editableSelectColumnDefs = [{
1312
+ field: 'selectField',
1313
+ headerName: 'Select Field',
1314
+ cellRenderer: 'dsSelectDropdownCellRenderer',
1315
+ cellEditor: 'dsSelectDropdownCellEditor',
1316
+ cellRendererParams: { options, placeholder: 'Placeholder Text' },
1317
+ cellEditorParams: { options, placeholder: 'Placeholder Text' },
1318
+ editable: true,
1319
+ }];
1320
+
1321
+ async function renderAndStartEditing(rowData: { selectField: string }[]) {
1322
+ let gridApi: GridApi | null = null;
1323
+ render(
1324
+ <Table
1325
+ columnDefs={editableSelectColumnDefs}
1326
+ rowData={rowData}
1327
+ onGridReady={(e) => {
1328
+ gridApi = e.api;
1329
+ }}
1330
+ />,
1331
+ );
1332
+ await waitFor(() => expect(gridApi).toBeTruthy());
1333
+ await act(async () => {
1334
+ gridApi!.startEditingCell({ rowIndex: 0, colKey: 'selectField' });
1335
+ });
1336
+ const editorWrapper = await waitFor(() => document.querySelector('.ds-table__select-dropdown-editor'));
1337
+ return editorWrapper!.querySelector('button')!;
1338
+ }
1339
+
1340
+ test('opens editor with dropdown; selecting option updates cell', async () => {
1341
+ const trigger = await renderAndStartEditing([{ selectField: 'option1' }]);
1342
+ expect(trigger).toHaveTextContent('Option 1');
1343
+ await userEvent.click(trigger);
1344
+ await userEvent.click(screen.getByText('Option 2'));
1345
+ await waitFor(() => expect(screen.getByText('Option 2')).toBeInTheDocument());
1346
+ });
1347
+
1348
+ test('Escape closes dropdown without changing value', async () => {
1349
+ const trigger = await renderAndStartEditing([{ selectField: 'option1' }]);
1350
+ await userEvent.click(trigger);
1351
+ await waitFor(() => expect(screen.getByText('Option 2')).toBeInTheDocument());
1352
+ await userEvent.keyboard('{Escape}');
1353
+ expect(screen.queryByText('Option 2')).not.toBeInTheDocument();
1354
+ expect(screen.getByText('Option 1')).toBeInTheDocument();
1355
+ });
1356
+ });
1357
+
1358
+ describe('Cell Editing', () => {
1359
+ describe('with literal data values', () => {
1360
+ test('supports editing text fields', async () => {
1361
+ const onCellValueChanged = vi.fn();
1362
+ const columnDefs = [{
1363
+ field: 'name',
1364
+ headerName: 'Name',
1365
+ editable: true,
1366
+ }];
1367
+ const rowData = [
1368
+ { id: 1, name: 'John Doe' },
1369
+ { id: 2, name: 'Jane Smith' },
1370
+ ];
1371
+
1372
+ render(
1373
+ <Table
1374
+ columnDefs={columnDefs}
1375
+ rowData={rowData}
1376
+ onCellValueChanged={onCellValueChanged}
1377
+ />,
1378
+ );
1379
+
1380
+ await waitFor(() => expect(screen.getByRole('grid')).toBeInTheDocument());
1381
+ await waitFor(() => expect(screen.getByText('John Doe')).toBeInTheDocument());
1382
+
1383
+ const cell = screen.getByText('John Doe');
1384
+ await userEvent.dblClick(cell);
1385
+
1386
+ await userEvent.keyboard('John Updated{Enter}');
1387
+
1388
+ await waitFor(() => {
1389
+ expect(onCellValueChanged).toHaveBeenCalled();
1390
+ });
1391
+
1392
+ expect(onCellValueChanged).toHaveBeenLastCalledWith(expect.objectContaining({ newValue: 'John Updated', oldValue: 'John Doe' }));
1393
+ });
1394
+
1395
+ test('supports editing number fields', async () => {
1396
+ const onCellValueChanged = vi.fn();
1397
+ const columnDefs = [{
1398
+ field: 'age',
1399
+ headerName: 'Age',
1400
+ editable: true,
1401
+ cellDataType: 'number',
1402
+ }];
1403
+ const rowData = [
1404
+ { id: 1, name: 'John Doe', age: 30 },
1405
+ { id: 2, name: 'Jane Smith', age: 25 },
1406
+ ];
1407
+
1408
+ render(
1409
+ <Table
1410
+ columnDefs={columnDefs}
1411
+ rowData={rowData}
1412
+ onCellValueChanged={onCellValueChanged}
1413
+ />,
1414
+ );
1415
+
1416
+ await waitFor(() => expect(screen.getByRole('grid')).toBeInTheDocument());
1417
+ await waitFor(() => expect(screen.getByText('30')).toBeInTheDocument());
1418
+
1419
+ const cell = screen.getByText('30');
1420
+ await userEvent.dblClick(cell);
1421
+
1422
+ await userEvent.keyboard('35{Enter}');
1423
+
1424
+ await waitFor(() => {
1425
+ expect(onCellValueChanged).toHaveBeenCalled();
1426
+ });
1427
+
1428
+ expect(onCellValueChanged).toHaveBeenLastCalledWith(expect.objectContaining({ oldValue: 30, newValue: 35 }));
1429
+ });
1430
+ });
1431
+
1432
+ /**
1433
+ * Note: when editing object values, the table will just insert the new value and
1434
+ * override the object. It will be the consumer's responsibility within the data layer
1435
+ * to do any processing on cell updates to maintain or alter other fields on the data
1436
+ * as necessary (e.g. cell colours).
1437
+ */
1438
+ describe('with object data values (value field)', () => {
1439
+ test('supports editing text fields with object values', async () => {
1440
+ const onCellValueChanged = vi.fn();
1441
+ const columnDefs = [{
1442
+ field: 'name',
1443
+ headerName: 'Name',
1444
+ editable: true,
1445
+ valueFormatter: Table.DefaultValueFormatter,
1446
+ }];
1447
+ const rowData = [
1448
+ { id: 1, name: { value: 'John Doe' } },
1449
+ { id: 2, name: { value: 'Jane Smith' } },
1450
+ ];
1451
+
1452
+ render(
1453
+ <Table
1454
+ columnDefs={columnDefs}
1455
+ rowData={rowData}
1456
+ onCellValueChanged={onCellValueChanged}
1457
+ defaultColDef={{
1458
+ cellEditorParams: {
1459
+ useFormatter: true,
1460
+ },
1461
+ }}
1462
+ />,
1463
+ );
1464
+
1465
+ await waitFor(() => expect(screen.getByRole('grid')).toBeInTheDocument());
1466
+ await waitFor(() => expect(screen.getByText('John Doe')).toBeInTheDocument());
1467
+
1468
+ const cell = screen.getByText('John Doe');
1469
+ await userEvent.dblClick(cell);
1470
+
1471
+ await userEvent.keyboard('John Updated{Enter}');
1472
+
1473
+ await waitFor(() => {
1474
+ expect(onCellValueChanged).toHaveBeenCalled();
1475
+ });
1476
+
1477
+ expect(onCellValueChanged).toHaveBeenLastCalledWith(expect.objectContaining({ newValue: 'John Updated', oldValue: { value: 'John Doe' } }));
1478
+ });
1479
+
1480
+ test('supports editing number fields with object values', async () => {
1481
+ const onCellValueChanged = vi.fn();
1482
+ const columnDefs = [{
1483
+ field: 'age',
1484
+ headerName: 'Age',
1485
+ editable: true,
1486
+ cellDataType: 'number',
1487
+ valueFormatter: Table.DefaultValueFormatter,
1488
+ }];
1489
+ const rowData = [
1490
+ { id: 1, name: { value: 'John Doe' }, age: { value: 30 } },
1491
+ { id: 2, name: { value: 'Jane Smith' }, age: { value: 25 } },
1492
+ ];
1493
+
1494
+ render(
1495
+ <Table
1496
+ columnDefs={columnDefs}
1497
+ rowData={rowData}
1498
+ onCellValueChanged={onCellValueChanged}
1499
+ defaultColDef={{
1500
+ cellEditorParams: {
1501
+ useFormatter: true,
1502
+ },
1503
+ }}
1504
+ />,
1505
+ );
1506
+
1507
+ await waitFor(() => expect(screen.getByRole('grid')).toBeInTheDocument());
1508
+ await waitFor(() => expect(screen.getByText('30')).toBeInTheDocument());
1509
+
1510
+ const cells = screen.getByText('30');
1511
+ await userEvent.dblClick(cells);
1512
+
1513
+ await userEvent.keyboard('35{Enter}');
1514
+
1515
+ await waitFor(() => {
1516
+ expect(onCellValueChanged).toHaveBeenCalled();
1517
+ });
1518
+
1519
+ expect(onCellValueChanged).toHaveBeenLastCalledWith(expect.objectContaining({ newValue: 35, oldValue: { value: 30 } }));
1520
+ });
1521
+ });
1522
+ });
1523
+
1524
+ describe('supressCellFocusAndFocusFirstElement', () => {
1525
+ const handleClick = vi.fn();
1526
+ const columnDefs = [{
1527
+ field: 'action',
1528
+ headerName: 'Action',
1529
+ cellRenderer: 'dsButtonCellRenderer',
1530
+ cellRendererParams: {
1531
+ supressCellFocusAndFocusFirstElement: true,
1532
+ },
1533
+ }];
1534
+ const rowData = [{
1535
+ action: {
1536
+ children: 'Im a lovely button',
1537
+ onClick: handleClick,
1538
+ },
1539
+ }];
1540
+
1541
+ test('setting supressCellFocusAndFocusFirstElement to true should sadd the ds-table__cell--supress-focus class', async () => {
1542
+ render(<Table columnDefs={columnDefs} rowData={rowData} />);
1543
+ await waitFor(() => expect(screen.getByRole('grid')).toBeInTheDocument());
1544
+ });
1545
+
1546
+ test('setting supressCellFocusAndFocusFirstElement to true should suppress cell focus and focus the first element', async () => {
1547
+ const focusFirstFocusableElementSpy = vi.spyOn(
1548
+ focusFirstFocusableElementModule,
1549
+ 'focusFirstFocusableElement',
1550
+ );
1551
+ render(<Table columnDefs={columnDefs} rowData={rowData} />);
1552
+ await waitFor(() => expect(screen.getByRole('grid')).toBeInTheDocument());
1553
+ await waitFor(() => expect(screen.getByText('Im a lovely button')).toBeInTheDocument());
1554
+ // first tab should focus the table heading
1555
+ await userEvent.tab();
1556
+ expect(screen.getByRole('columnheader')).toHaveFocus();
1557
+ // second tab should focus the button bypassing the cell
1558
+ await userEvent.tab();
1559
+ expect(focusFirstFocusableElementSpy).toHaveBeenCalledTimes(1);
1560
+ const button = screen.getByText('Im a lovely button');
1561
+ expect(button).toHaveFocus();
1562
+ });
1563
+ });
1028
1564
  });