@arbor-education/design-system.components 0.3.2 → 0.3.4
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/CHANGELOG.md +14 -0
- package/dist/components/formField/fieldset/Fieldset.d.ts +1 -1
- package/dist/components/formField/fieldset/Fieldset.d.ts.map +1 -1
- package/dist/components/formField/fieldset/Fieldset.js +1 -1
- package/dist/components/formField/fieldset/Fieldset.js.map +1 -1
- package/dist/components/formField/inputs/number/NumberInput.d.ts.map +1 -1
- package/dist/components/formField/inputs/number/NumberInput.js +21 -12
- package/dist/components/formField/inputs/number/NumberInput.js.map +1 -1
- package/dist/components/formField/inputs/number/NumberInput.test.js +92 -3
- package/dist/components/formField/inputs/number/NumberInput.test.js.map +1 -1
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.d.ts.map +1 -1
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.js +2 -2
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.js.map +1 -1
- package/dist/components/pill/Pill.d.ts +2 -1
- package/dist/components/pill/Pill.d.ts.map +1 -1
- package/dist/components/pill/Pill.js +2 -2
- package/dist/components/pill/Pill.js.map +1 -1
- package/dist/components/pill/Pill.stories.d.ts +8 -0
- package/dist/components/pill/Pill.stories.d.ts.map +1 -1
- package/dist/components/pill/Pill.stories.js +8 -0
- package/dist/components/pill/Pill.stories.js.map +1 -1
- package/dist/components/pill/Pill.test.js +9 -0
- package/dist/components/pill/Pill.test.js.map +1 -1
- package/dist/components/table/DSDefaultColDef.d.ts.map +1 -1
- package/dist/components/table/DSDefaultColDef.js +32 -2
- package/dist/components/table/DSDefaultColDef.js.map +1 -1
- package/dist/components/table/Table.d.ts.map +1 -1
- package/dist/components/table/Table.js +5 -1
- package/dist/components/table/Table.js.map +1 -1
- package/dist/components/table/Table.stories.d.ts.map +1 -1
- package/dist/components/table/Table.stories.js +17 -16
- package/dist/components/table/Table.stories.js.map +1 -1
- package/dist/components/table/Table.test.js +15 -27
- package/dist/components/table/Table.test.js.map +1 -1
- package/dist/components/table/columnFilters/BooleanFilter/BooleanFilter.d.ts +13 -0
- package/dist/components/table/columnFilters/BooleanFilter/BooleanFilter.d.ts.map +1 -0
- package/dist/components/table/columnFilters/BooleanFilter/BooleanFilter.js +68 -0
- package/dist/components/table/columnFilters/BooleanFilter/BooleanFilter.js.map +1 -0
- package/dist/components/table/columnFilters/BooleanFilter/BooleanFilter.test.d.ts +2 -0
- package/dist/components/table/columnFilters/BooleanFilter/BooleanFilter.test.d.ts.map +1 -0
- package/dist/components/table/columnFilters/BooleanFilter/BooleanFilter.test.js +87 -0
- package/dist/components/table/columnFilters/BooleanFilter/BooleanFilter.test.js.map +1 -0
- package/dist/components/table/columnFilters/TimeFilter/TimeFilter.d.ts +14 -0
- package/dist/components/table/columnFilters/TimeFilter/TimeFilter.d.ts.map +1 -0
- package/dist/components/table/columnFilters/TimeFilter/TimeFilter.js +73 -0
- package/dist/components/table/columnFilters/TimeFilter/TimeFilter.js.map +1 -0
- package/dist/components/table/columnFilters/TimeFilter/TimeFilter.test.d.ts +2 -0
- package/dist/components/table/columnFilters/TimeFilter/TimeFilter.test.d.ts.map +1 -0
- package/dist/components/table/columnFilters/TimeFilter/TimeFilter.test.js +102 -0
- package/dist/components/table/columnFilters/TimeFilter/TimeFilter.test.js.map +1 -0
- package/dist/components/table/columnFilters/TimeFilter/utils/compareTimeByFilterType.d.ts +8 -0
- package/dist/components/table/columnFilters/TimeFilter/utils/compareTimeByFilterType.d.ts.map +1 -0
- package/dist/components/table/columnFilters/TimeFilter/utils/compareTimeByFilterType.js +21 -0
- package/dist/components/table/columnFilters/TimeFilter/utils/compareTimeByFilterType.js.map +1 -0
- package/dist/components/table/columnFilters/TimeFilter/utils/compareTimeByFilterType.test.d.ts +2 -0
- package/dist/components/table/columnFilters/TimeFilter/utils/compareTimeByFilterType.test.d.ts.map +1 -0
- package/dist/components/table/columnFilters/TimeFilter/utils/compareTimeByFilterType.test.js +129 -0
- package/dist/components/table/columnFilters/TimeFilter/utils/compareTimeByFilterType.test.js.map +1 -0
- package/dist/components/table/columnFilters/TimeFilter/utils/transformTimeStringToDate.d.ts +2 -0
- package/dist/components/table/columnFilters/TimeFilter/utils/transformTimeStringToDate.d.ts.map +1 -0
- package/dist/components/table/columnFilters/TimeFilter/utils/transformTimeStringToDate.js +18 -0
- package/dist/components/table/columnFilters/TimeFilter/utils/transformTimeStringToDate.js.map +1 -0
- package/dist/components/table/columnFilters/TimeFilter/utils/transformTimeStringToDate.test.d.ts +2 -0
- package/dist/components/table/columnFilters/TimeFilter/utils/transformTimeStringToDate.test.d.ts.map +1 -0
- package/dist/components/table/columnFilters/TimeFilter/utils/transformTimeStringToDate.test.js +100 -0
- package/dist/components/table/columnFilters/TimeFilter/utils/transformTimeStringToDate.test.js.map +1 -0
- package/dist/components/table/columnFilters/filterResetButton/FilterResetButton.d.ts +7 -0
- package/dist/components/table/columnFilters/filterResetButton/FilterResetButton.d.ts.map +1 -0
- package/dist/components/table/columnFilters/filterResetButton/FilterResetButton.js +7 -0
- package/dist/components/table/columnFilters/filterResetButton/FilterResetButton.js.map +1 -0
- package/dist/index.css +70 -1
- package/dist/index.css.map +1 -1
- package/package.json +2 -1
- package/src/components/formField/fieldset/Fieldset.tsx +2 -2
- package/src/components/formField/inputs/number/NumberInput.test.tsx +113 -3
- package/src/components/formField/inputs/number/NumberInput.tsx +26 -15
- package/src/components/formField/inputs/selectDropdown/SelectDropdown.tsx +14 -2
- package/src/components/pill/Pill.stories.tsx +9 -0
- package/src/components/pill/Pill.test.tsx +10 -0
- package/src/components/pill/Pill.tsx +3 -2
- package/src/components/table/DSDefaultColDef.ts +34 -4
- package/src/components/table/Table.stories.tsx +20 -16
- package/src/components/table/Table.test.tsx +12 -28
- package/src/components/table/Table.tsx +5 -0
- package/src/components/table/columnFilters/BooleanFilter/BooleanFilter.test.tsx +102 -0
- package/src/components/table/columnFilters/BooleanFilter/BooleanFilter.tsx +104 -0
- package/src/components/table/columnFilters/TimeFilter/TimeFilter.test.tsx +131 -0
- package/src/components/table/columnFilters/TimeFilter/TimeFilter.tsx +122 -0
- package/src/components/table/columnFilters/TimeFilter/utils/compareTimeByFilterType.test.ts +183 -0
- package/src/components/table/columnFilters/TimeFilter/utils/compareTimeByFilterType.ts +30 -0
- package/src/components/table/columnFilters/TimeFilter/utils/transformTimeStringToDate.test.ts +133 -0
- package/src/components/table/columnFilters/TimeFilter/utils/transformTimeStringToDate.ts +21 -0
- package/src/components/table/columnFilters/columnFilters.scss +71 -0
- package/src/components/table/columnFilters/filterResetButton/FilterResetButton.tsx +21 -0
- package/src/components/table/table.scss +1 -1
- package/src/index.scss +1 -0
|
@@ -1,25 +1,55 @@
|
|
|
1
1
|
import type { CellClassParams, CellFocusedParams, ColDef, SuppressMouseEventHandlingParams, ValueFormatterFunc } from 'ag-grid-community';
|
|
2
2
|
import { cellColorStyles } from './cellColorStyles';
|
|
3
3
|
|
|
4
|
+
// checks if the value is wrapped in a object with a value property
|
|
5
|
+
const isWrappedValue = (value: unknown): value is { value: unknown } => {
|
|
6
|
+
return value != null && typeof value === 'object' && 'value' in value;
|
|
7
|
+
};
|
|
8
|
+
|
|
4
9
|
export const defaultValueFormatter: ValueFormatterFunc = (params) => {
|
|
5
10
|
const { value } = params;
|
|
6
|
-
if (value
|
|
11
|
+
if (isWrappedValue(value)) {
|
|
7
12
|
return value.value;
|
|
8
13
|
}
|
|
9
14
|
|
|
10
15
|
return value;
|
|
11
16
|
};
|
|
12
17
|
|
|
13
|
-
export const shouldSuppressFocus = (params: SuppressMouseEventHandlingParams | CellClassParams | CellFocusedParams) =>
|
|
18
|
+
export const shouldSuppressFocus = (params: SuppressMouseEventHandlingParams | CellClassParams | CellFocusedParams) => {
|
|
19
|
+
if (typeof params.column !== 'object') {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
return params.column?.getColDef().cellRendererParams.supressCellFocusAndFocusFirstElement ?? false;
|
|
23
|
+
};
|
|
14
24
|
|
|
15
25
|
export const DSDefaultColDef: ColDef = {
|
|
16
26
|
cellStyle: cellColorStyles,
|
|
17
27
|
valueGetter: (params) => {
|
|
18
28
|
const { data, colDef: { field } } = params;
|
|
19
|
-
|
|
20
|
-
|
|
29
|
+
if (data == null || field == null) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
return data[field];
|
|
21
33
|
},
|
|
22
34
|
valueFormatter: defaultValueFormatter,
|
|
35
|
+
filterValueGetter: (params) => {
|
|
36
|
+
const { data, colDef: { field } } = params;
|
|
37
|
+
if (data == null || field == null) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
const cellValue = data[field];
|
|
41
|
+
if (isWrappedValue(cellValue)) {
|
|
42
|
+
return cellValue.value;
|
|
43
|
+
}
|
|
44
|
+
return cellValue;
|
|
45
|
+
},
|
|
46
|
+
getQuickFilterText: (params) => {
|
|
47
|
+
const { value } = params;
|
|
48
|
+
if (isWrappedValue(value)) {
|
|
49
|
+
return value.value;
|
|
50
|
+
}
|
|
51
|
+
return value;
|
|
52
|
+
},
|
|
23
53
|
cellEditorParams: {
|
|
24
54
|
useFormatter: true,
|
|
25
55
|
},
|
|
@@ -14,6 +14,8 @@ import { ModalManager } from 'Components/modal/modalManager/ModalManager';
|
|
|
14
14
|
import { Modal } from 'Components/modal/Modal';
|
|
15
15
|
import { ModalUtils } from 'Utils/ModalUtils';
|
|
16
16
|
import type { SemanticColorKey } from './cellColorStyles';
|
|
17
|
+
import { BooleanFilter, doesBooleanFilterPass } from './columnFilters/BooleanFilter/BooleanFilter';
|
|
18
|
+
import { doesTimeFilterPass, TimeFilter } from './columnFilters/TimeFilter/TimeFilter';
|
|
17
19
|
|
|
18
20
|
type TableProps = ComponentProps<typeof Table>;
|
|
19
21
|
|
|
@@ -44,14 +46,17 @@ interface RowData {
|
|
|
44
46
|
email: { value: string } & CellColors;
|
|
45
47
|
role: { value: string } & CellColors;
|
|
46
48
|
status: { value: string } & CellColors;
|
|
49
|
+
active: boolean;
|
|
50
|
+
time: string;
|
|
51
|
+
dateOfBirth: Date;
|
|
47
52
|
}
|
|
48
53
|
|
|
49
54
|
const sampleData: RowData[] = [
|
|
50
|
-
{ id: 1, name: { value: 'Alice Johnson' }, email: { value: 'alice.johnson@example.com' }, role: { value: 'Developer' }, status: { value: 'Active' } },
|
|
51
|
-
{ id: 2, name: { value: 'Bob Smith' }, email: { value: 'bob.smith@example.com' }, role: { value: 'Designer' }, status: { value: 'Active' } },
|
|
52
|
-
{ id: 3, name: { value: 'Charlie Brown' }, email: { value: 'charlie.brown@example.com' }, role: { value: 'Manager' }, status: { value: 'Inactive' } },
|
|
53
|
-
{ id: 4, name: { value: 'Diana Prince' }, email: { value: 'diana.prince@example.com' }, role: { value: 'Developer' }, status: { value: 'Active' } },
|
|
54
|
-
{ id: 5, name: { value: 'Ethan Hunt' }, email: { value: 'ethan.hunt@example.com' }, role: { value: 'Analyst' }, status: { value: 'Active' } },
|
|
55
|
+
{ id: 1, name: { value: 'Alice Johnson' }, email: { value: 'alice.johnson@example.com' }, role: { value: 'Developer' }, status: { value: 'Active' }, active: true, time: '2026-01-28 12:00:00', dateOfBirth: new Date(1990, 0, 1) },
|
|
56
|
+
{ id: 2, name: { value: 'Bob Smith' }, email: { value: 'bob.smith@example.com' }, role: { value: 'Designer' }, status: { value: 'Active' }, active: false, time: '2026-01-28 13:00:00', dateOfBirth: new Date(1991, 1, 2) },
|
|
57
|
+
{ id: 3, name: { value: 'Charlie Brown' }, email: { value: 'charlie.brown@example.com' }, role: { value: 'Manager' }, status: { value: 'Inactive' }, active: true, time: '2026-01-28 14:00:00', dateOfBirth: new Date(1992, 2, 3) },
|
|
58
|
+
{ id: 4, name: { value: 'Diana Prince' }, email: { value: 'diana.prince@example.com' }, role: { value: 'Developer' }, status: { value: 'Active' }, active: false, time: '2026-01-28 15:00:00', dateOfBirth: new Date(1993, 3, 4) },
|
|
59
|
+
{ id: 5, name: { value: 'Ethan Hunt' }, email: { value: 'ethan.hunt@example.com' }, role: { value: 'Analyst' }, status: { value: 'Active' }, active: false, time: '2026-01-28 16:00:00', dateOfBirth: new Date(1994, 4, 5) },
|
|
55
60
|
];
|
|
56
61
|
|
|
57
62
|
const defaultColDef: ColDef = {
|
|
@@ -60,26 +65,25 @@ const defaultColDef: ColDef = {
|
|
|
60
65
|
resizable: true,
|
|
61
66
|
minWidth: 100,
|
|
62
67
|
flex: 1,
|
|
68
|
+
suppressHeaderFilterButton: true,
|
|
63
69
|
};
|
|
64
70
|
|
|
65
71
|
const sampleColumnDefs: (ColDef | ColGroupDef)[] = [
|
|
66
72
|
{
|
|
67
73
|
headerName: 'Details',
|
|
68
|
-
children: [
|
|
69
|
-
field: 'name',
|
|
70
|
-
headerTooltip: 'The
|
|
71
|
-
|
|
72
|
-
field: '
|
|
73
|
-
|
|
74
|
-
}, {
|
|
75
|
-
field: 'role',
|
|
76
|
-
headerTooltip: 'The role of the user',
|
|
77
|
-
}],
|
|
74
|
+
children: [
|
|
75
|
+
{ field: 'name', headerTooltip: 'The name of the user', filter: 'agSetColumnFilter' },
|
|
76
|
+
{ field: 'email', headerTooltip: 'The email of the user', filter: 'agSetColumnFilter' },
|
|
77
|
+
{ field: 'role', headerTooltip: 'The role of the user', filter: 'agSetColumnFilter' },
|
|
78
|
+
{ field: 'dateOfBirth', filter: 'agDateColumnFilter', filterParams: { buttons: ['clear', 'apply'] }, headerTooltip: 'The date of birth of the user', valueFormatter: params => params.value instanceof Date ? params.value.toLocaleDateString() : params.value },
|
|
79
|
+
],
|
|
78
80
|
// Value formatters from the defaultColDef are not respected for non-string value
|
|
79
81
|
// so until AG Grid fixes it we have to do it explicitly on each colDef
|
|
80
82
|
valueFormatter: Table.DefaultValueFormatter,
|
|
81
83
|
},
|
|
82
84
|
{ field: 'status', valueFormatter: Table.DefaultValueFormatter },
|
|
85
|
+
{ field: 'active', filter: { component: BooleanFilter, doesFilterPass: doesBooleanFilterPass }, cellDataType: 'boolean', editable: false },
|
|
86
|
+
{ field: 'time', filter: { component: TimeFilter, doesFilterPass: doesTimeFilterPass }, cellDataType: 'string', editable: false },
|
|
83
87
|
];
|
|
84
88
|
|
|
85
89
|
const footerContent = [
|
|
@@ -665,7 +669,7 @@ const colDefsWithValidationClasses: (ColDef | ColGroupDef)[] = [
|
|
|
665
669
|
maxLength: 10,
|
|
666
670
|
},
|
|
667
671
|
cellClassRules: {
|
|
668
|
-
'ds-table__cell--invalid': params => params.value
|
|
672
|
+
'ds-table__cell--invalid': params => params.value?.value?.length > 10,
|
|
669
673
|
},
|
|
670
674
|
}, {
|
|
671
675
|
field: 'email',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, expectTypeOf, test, vi } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import { 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';
|
|
@@ -23,7 +23,7 @@ describe('Table', () => {
|
|
|
23
23
|
expect(screen.getByRole('grid')).toBeInTheDocument();
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
test('
|
|
26
|
+
test('applies wrapperClassName to table container', () => {
|
|
27
27
|
const { container } = render(
|
|
28
28
|
<Table wrapperClassName="custom-class" />,
|
|
29
29
|
);
|
|
@@ -1319,20 +1319,15 @@ describe('Table', () => {
|
|
|
1319
1319
|
}];
|
|
1320
1320
|
|
|
1321
1321
|
async function renderAndStartEditing(rowData: { selectField: string }[]) {
|
|
1322
|
-
let gridApi: GridApi | null = null;
|
|
1323
1322
|
render(
|
|
1324
1323
|
<Table
|
|
1325
1324
|
columnDefs={editableSelectColumnDefs}
|
|
1326
1325
|
rowData={rowData}
|
|
1327
|
-
onGridReady={(e) => {
|
|
1328
|
-
gridApi = e.api;
|
|
1329
|
-
}}
|
|
1330
1326
|
/>,
|
|
1331
1327
|
);
|
|
1332
|
-
await waitFor(() => expect(
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
});
|
|
1328
|
+
await waitFor(() => expect(screen.getByRole('grid')).toBeInTheDocument());
|
|
1329
|
+
const cell = screen.getByText('Option 1');
|
|
1330
|
+
await userEvent.dblClick(cell);
|
|
1336
1331
|
const editorWrapper = await waitFor(() => document.querySelector('.ds-table__select-dropdown-editor'));
|
|
1337
1332
|
return editorWrapper!.querySelector('button')!;
|
|
1338
1333
|
}
|
|
@@ -1454,11 +1449,6 @@ describe('Table', () => {
|
|
|
1454
1449
|
columnDefs={columnDefs}
|
|
1455
1450
|
rowData={rowData}
|
|
1456
1451
|
onCellValueChanged={onCellValueChanged}
|
|
1457
|
-
defaultColDef={{
|
|
1458
|
-
cellEditorParams: {
|
|
1459
|
-
useFormatter: true,
|
|
1460
|
-
},
|
|
1461
|
-
}}
|
|
1462
1452
|
/>,
|
|
1463
1453
|
);
|
|
1464
1454
|
|
|
@@ -1483,7 +1473,6 @@ describe('Table', () => {
|
|
|
1483
1473
|
field: 'age',
|
|
1484
1474
|
headerName: 'Age',
|
|
1485
1475
|
editable: true,
|
|
1486
|
-
cellDataType: 'number',
|
|
1487
1476
|
valueFormatter: Table.DefaultValueFormatter,
|
|
1488
1477
|
}];
|
|
1489
1478
|
const rowData = [
|
|
@@ -1496,11 +1485,6 @@ describe('Table', () => {
|
|
|
1496
1485
|
columnDefs={columnDefs}
|
|
1497
1486
|
rowData={rowData}
|
|
1498
1487
|
onCellValueChanged={onCellValueChanged}
|
|
1499
|
-
defaultColDef={{
|
|
1500
|
-
cellEditorParams: {
|
|
1501
|
-
useFormatter: true,
|
|
1502
|
-
},
|
|
1503
|
-
}}
|
|
1504
1488
|
/>,
|
|
1505
1489
|
);
|
|
1506
1490
|
|
|
@@ -1515,8 +1499,6 @@ describe('Table', () => {
|
|
|
1515
1499
|
await waitFor(() => {
|
|
1516
1500
|
expect(onCellValueChanged).toHaveBeenCalled();
|
|
1517
1501
|
});
|
|
1518
|
-
|
|
1519
|
-
expect(onCellValueChanged).toHaveBeenLastCalledWith(expect.objectContaining({ newValue: 35, oldValue: { value: 30 } }));
|
|
1520
1502
|
});
|
|
1521
1503
|
});
|
|
1522
1504
|
});
|
|
@@ -1538,9 +1520,11 @@ describe('Table', () => {
|
|
|
1538
1520
|
},
|
|
1539
1521
|
}];
|
|
1540
1522
|
|
|
1541
|
-
test('setting supressCellFocusAndFocusFirstElement to true should
|
|
1542
|
-
render(<Table columnDefs={columnDefs} rowData={rowData} />);
|
|
1543
|
-
await
|
|
1523
|
+
test('setting supressCellFocusAndFocusFirstElement to true should add the ds-table__cell--supress-focus class', async () => {
|
|
1524
|
+
const { container } = render(<Table columnDefs={columnDefs} rowData={rowData} />);
|
|
1525
|
+
await screen.findByRole('grid', {}, { timeout: 10000 });
|
|
1526
|
+
const cellWithClass = container.querySelector('.ds-table__cell--supress-focus');
|
|
1527
|
+
expect(cellWithClass).toBeInTheDocument();
|
|
1544
1528
|
});
|
|
1545
1529
|
|
|
1546
1530
|
test('setting supressCellFocusAndFocusFirstElement to true should suppress cell focus and focus the first element', async () => {
|
|
@@ -1549,8 +1533,8 @@ describe('Table', () => {
|
|
|
1549
1533
|
'focusFirstFocusableElement',
|
|
1550
1534
|
);
|
|
1551
1535
|
render(<Table columnDefs={columnDefs} rowData={rowData} />);
|
|
1552
|
-
await
|
|
1553
|
-
await
|
|
1536
|
+
await screen.findByRole('grid', {}, { timeout: 10000 });
|
|
1537
|
+
await screen.findByText('Im a lovely button', {}, { timeout: 10000 });
|
|
1554
1538
|
// first tab should focus the table heading
|
|
1555
1539
|
await userEvent.tab();
|
|
1556
1540
|
expect(screen.getByRole('columnheader')).toHaveFocus();
|
|
@@ -22,6 +22,8 @@ import { SelectDropdownCellRenderer } from './cellRenderers/SelectDropdownCellRe
|
|
|
22
22
|
import { SelectDropdownCellEditor } from './cellRenderers/SelectDropdownCellEditor';
|
|
23
23
|
import { tidyTheme } from './theme/tidyTheme';
|
|
24
24
|
import { focusFirstFocusableElement } from 'Utils/focusFirstFocusableElement';
|
|
25
|
+
import { BooleanFilter } from './columnFilters/BooleanFilter/BooleanFilter';
|
|
26
|
+
import { TimeFilter } from './columnFilters/TimeFilter/TimeFilter';
|
|
25
27
|
|
|
26
28
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
27
29
|
type TableProps<TData = any> = {
|
|
@@ -158,6 +160,7 @@ export const Table = (props: TableProps) => {
|
|
|
158
160
|
setGridApi(api);
|
|
159
161
|
onGridReady?.(event);
|
|
160
162
|
}}
|
|
163
|
+
enableFilterHandlers
|
|
161
164
|
suppressPaginationPanel
|
|
162
165
|
onCellSelectionChanged={(event) => {
|
|
163
166
|
if ((!disableDragSelect || !enableSimultaneousRangeAndRowSelection) && gridApi && event.finished) {
|
|
@@ -190,6 +193,8 @@ export const Table = (props: TableProps) => {
|
|
|
190
193
|
dsInlineTextCellRenderer: InlineTextCellRenderer,
|
|
191
194
|
dsSelectDropdownCellRenderer: SelectDropdownCellRenderer,
|
|
192
195
|
dsSelectDropdownCellEditor: SelectDropdownCellEditor,
|
|
196
|
+
dsBooleanFilter: BooleanFilter,
|
|
197
|
+
dsTimeFilter: TimeFilter,
|
|
193
198
|
...components,
|
|
194
199
|
}}
|
|
195
200
|
{...rest}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import '@testing-library/jest-dom/vitest';
|
|
2
|
+
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import { BooleanFilter, doesBooleanFilterPass } from './BooleanFilter';
|
|
5
|
+
import userEvent from '@testing-library/user-event';
|
|
6
|
+
|
|
7
|
+
type BooleanFilterModel = { allowTrue: boolean; allowFalse: boolean };
|
|
8
|
+
|
|
9
|
+
const createMockParams = (model: BooleanFilterModel | null, cellValue: boolean | null) => ({
|
|
10
|
+
model,
|
|
11
|
+
node: {},
|
|
12
|
+
handlerParams: {
|
|
13
|
+
getValue: vi.fn().mockReturnValue(cellValue),
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('BooleanFilter', () => {
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
vi.clearAllMocks();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const onModelChange = vi.fn();
|
|
23
|
+
|
|
24
|
+
test('renders BooleanFilter with null model (defaults to Not set)', () => {
|
|
25
|
+
render(<BooleanFilter model={null} onModelChange={onModelChange} />);
|
|
26
|
+
expect(screen.getByRole('radio', { name: 'Not set' })).toBeChecked();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('renders BooleanFilter with Yes, No and Not set radio options', () => {
|
|
30
|
+
render(<BooleanFilter model={{ allowTrue: true, allowFalse: true }} onModelChange={onModelChange} />);
|
|
31
|
+
expect(screen.getByRole('radio', { name: 'Yes' })).toBeInTheDocument();
|
|
32
|
+
expect(screen.getByRole('radio', { name: 'No' })).toBeInTheDocument();
|
|
33
|
+
expect(screen.getByRole('radio', { name: 'Not set' })).toBeInTheDocument();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('renders with Not set selected when model has both allowTrue and allowFalse', () => {
|
|
37
|
+
render(<BooleanFilter model={{ allowTrue: true, allowFalse: true }} onModelChange={onModelChange} />);
|
|
38
|
+
expect(screen.getByRole('radio', { name: 'Not set' })).toBeChecked();
|
|
39
|
+
expect(screen.getByRole('radio', { name: 'Yes' })).not.toBeChecked();
|
|
40
|
+
expect(screen.getByRole('radio', { name: 'No' })).not.toBeChecked();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('renders with Yes selected when model has allowTrue only', () => {
|
|
44
|
+
render(<BooleanFilter model={{ allowTrue: true, allowFalse: false }} onModelChange={onModelChange} />);
|
|
45
|
+
expect(screen.getByRole('radio', { name: 'Yes' })).toBeChecked();
|
|
46
|
+
expect(screen.getByRole('radio', { name: 'No' })).not.toBeChecked();
|
|
47
|
+
expect(screen.getByRole('radio', { name: 'Not set' })).not.toBeChecked();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('renders with No selected when model has allowFalse only', () => {
|
|
51
|
+
render(<BooleanFilter model={{ allowTrue: false, allowFalse: true }} onModelChange={onModelChange} />);
|
|
52
|
+
expect(screen.getByRole('radio', { name: 'No' })).toBeChecked();
|
|
53
|
+
expect(screen.getByRole('radio', { name: 'Yes' })).not.toBeChecked();
|
|
54
|
+
expect(screen.getByRole('radio', { name: 'Not set' })).not.toBeChecked();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('calls onModelChange when Yes is selected', async () => {
|
|
58
|
+
render(<BooleanFilter model={{ allowTrue: true, allowFalse: true }} onModelChange={onModelChange} />);
|
|
59
|
+
await userEvent.click(screen.getByRole('radio', { name: 'Yes' }));
|
|
60
|
+
expect(onModelChange).toHaveBeenLastCalledWith({ allowTrue: true, allowFalse: false });
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('calls onModelChange when No is selected', async () => {
|
|
64
|
+
render(<BooleanFilter model={{ allowTrue: true, allowFalse: true }} onModelChange={onModelChange} />);
|
|
65
|
+
await userEvent.click(screen.getByRole('radio', { name: 'No' }));
|
|
66
|
+
expect(onModelChange).toHaveBeenLastCalledWith({ allowTrue: false, allowFalse: true });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('calls onModelChange when Not set is selected', async () => {
|
|
70
|
+
render(<BooleanFilter model={{ allowTrue: true, allowFalse: false }} onModelChange={onModelChange} />);
|
|
71
|
+
await userEvent.click(screen.getByRole('radio', { name: 'Not set' }));
|
|
72
|
+
expect(onModelChange).toHaveBeenLastCalledWith({ allowTrue: true, allowFalse: true });
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('doesBooleanFilterPass', () => {
|
|
77
|
+
test('returns true when model is null (show all rows)', () => {
|
|
78
|
+
const params = createMockParams(null, true);
|
|
79
|
+
expect(doesBooleanFilterPass(params as never)).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('when cell value is true: passes if allowTrue, fails if !allowTrue', () => {
|
|
83
|
+
expect(doesBooleanFilterPass(createMockParams({ allowTrue: true, allowFalse: true }, true) as never)).toBe(true);
|
|
84
|
+
expect(doesBooleanFilterPass(createMockParams({ allowTrue: true, allowFalse: false }, true) as never)).toBe(true);
|
|
85
|
+
expect(doesBooleanFilterPass(createMockParams({ allowTrue: false, allowFalse: true }, true) as never)).toBe(false);
|
|
86
|
+
expect(doesBooleanFilterPass(createMockParams({ allowTrue: false, allowFalse: false }, true) as never)).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('when cell value is false: passes if allowFalse, fails if !allowFalse', () => {
|
|
90
|
+
expect(doesBooleanFilterPass(createMockParams({ allowTrue: true, allowFalse: true }, false) as never)).toBe(true);
|
|
91
|
+
expect(doesBooleanFilterPass(createMockParams({ allowTrue: false, allowFalse: true }, false) as never)).toBe(true);
|
|
92
|
+
expect(doesBooleanFilterPass(createMockParams({ allowTrue: true, allowFalse: false }, false) as never)).toBe(false);
|
|
93
|
+
expect(doesBooleanFilterPass(createMockParams({ allowTrue: false, allowFalse: false }, false) as never)).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('when cell value is null/undefined: passes only when both allowTrue and allowFalse', () => {
|
|
97
|
+
expect(doesBooleanFilterPass(createMockParams({ allowTrue: true, allowFalse: true }, null) as never)).toBe(true);
|
|
98
|
+
expect(doesBooleanFilterPass(createMockParams({ allowTrue: true, allowFalse: false }, null) as never)).toBe(false);
|
|
99
|
+
expect(doesBooleanFilterPass(createMockParams({ allowTrue: false, allowFalse: true }, null) as never)).toBe(false);
|
|
100
|
+
expect(doesBooleanFilterPass(createMockParams({ allowTrue: false, allowFalse: false }, null) as never)).toBe(false);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { DoesFilterPassParams } from 'ag-grid-community';
|
|
2
|
+
import type { ChangeEvent } from 'react';
|
|
3
|
+
import { useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { RadioButtonGroup } from 'Components/formField/inputs/radio/RadioButtonGroup';
|
|
5
|
+
import { PopupParentContext } from 'Utils/PopupParentContext';
|
|
6
|
+
|
|
7
|
+
type BooleanFilterModel = { allowTrue: boolean; allowFalse: boolean };
|
|
8
|
+
|
|
9
|
+
enum BOOLEAN_FILTER_VALUE {
|
|
10
|
+
YES = 'yes',
|
|
11
|
+
NO = 'no',
|
|
12
|
+
NOT_SET = 'not-set',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type BooleanFilterValue = BOOLEAN_FILTER_VALUE;
|
|
16
|
+
|
|
17
|
+
function modelToSelectedValue(model: BooleanFilterModel): BooleanFilterValue {
|
|
18
|
+
if (model.allowTrue && model.allowFalse) {
|
|
19
|
+
return BOOLEAN_FILTER_VALUE.NOT_SET;
|
|
20
|
+
}
|
|
21
|
+
if (model.allowTrue) {
|
|
22
|
+
return BOOLEAN_FILTER_VALUE.YES;
|
|
23
|
+
}
|
|
24
|
+
return BOOLEAN_FILTER_VALUE.NO;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function selectedValueToModel(value: BooleanFilterValue): BooleanFilterModel {
|
|
28
|
+
switch (value) {
|
|
29
|
+
case BOOLEAN_FILTER_VALUE.YES:
|
|
30
|
+
return { allowTrue: true, allowFalse: false };
|
|
31
|
+
case BOOLEAN_FILTER_VALUE.NO:
|
|
32
|
+
return { allowTrue: false, allowFalse: true };
|
|
33
|
+
case BOOLEAN_FILTER_VALUE.NOT_SET:
|
|
34
|
+
default:
|
|
35
|
+
return { allowTrue: true, allowFalse: true };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const doesBooleanFilterPass: ({
|
|
40
|
+
model,
|
|
41
|
+
node,
|
|
42
|
+
handlerParams,
|
|
43
|
+
}: DoesFilterPassParams<BooleanFilterModel>) => boolean = ({ model, node, handlerParams }) => {
|
|
44
|
+
const value = handlerParams.getValue(node);
|
|
45
|
+
if (!model) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
if (value === true) {
|
|
49
|
+
return model.allowTrue;
|
|
50
|
+
}
|
|
51
|
+
else if (value === false) {
|
|
52
|
+
return model.allowFalse;
|
|
53
|
+
}
|
|
54
|
+
return model.allowTrue && model.allowFalse;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const DEFAULT_MODEL: BooleanFilterModel = { allowTrue: true, allowFalse: true };
|
|
58
|
+
|
|
59
|
+
type BooleanFilterProps = {
|
|
60
|
+
model: BooleanFilterModel | null;
|
|
61
|
+
onModelChange: (model: BooleanFilterModel) => void;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const RADIO_NAME = 'boolean-column-filter';
|
|
65
|
+
|
|
66
|
+
export const BooleanFilter = (props: BooleanFilterProps) => {
|
|
67
|
+
const { model, onModelChange } = props;
|
|
68
|
+
const safeModel = model ?? DEFAULT_MODEL;
|
|
69
|
+
const [selectedValue, setSelectedValue] = useState<BooleanFilterValue>(() =>
|
|
70
|
+
modelToSelectedValue(safeModel),
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
setSelectedValue((prev) => {
|
|
75
|
+
const fromModel = modelToSelectedValue(safeModel);
|
|
76
|
+
return prev !== fromModel ? fromModel : prev;
|
|
77
|
+
});
|
|
78
|
+
}, [safeModel.allowTrue, safeModel.allowFalse]);
|
|
79
|
+
|
|
80
|
+
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
|
81
|
+
const newValue = event.target.value as BooleanFilterValue;
|
|
82
|
+
setSelectedValue(newValue);
|
|
83
|
+
onModelChange(selectedValueToModel(newValue));
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const popupParentRef = useRef<HTMLDivElement>(null);
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<PopupParentContext.Provider value={popupParentRef}>
|
|
90
|
+
<div className="ds-column-filter__container" ref={popupParentRef}>
|
|
91
|
+
<RadioButtonGroup
|
|
92
|
+
name={RADIO_NAME}
|
|
93
|
+
options={[
|
|
94
|
+
{ id: `${RADIO_NAME}-${BOOLEAN_FILTER_VALUE.YES}`, value: BOOLEAN_FILTER_VALUE.YES, label: 'Yes' },
|
|
95
|
+
{ id: `${RADIO_NAME}-${BOOLEAN_FILTER_VALUE.NO}`, value: BOOLEAN_FILTER_VALUE.NO, label: 'No' },
|
|
96
|
+
{ id: `${RADIO_NAME}-${BOOLEAN_FILTER_VALUE.NOT_SET}`, value: BOOLEAN_FILTER_VALUE.NOT_SET, label: 'Not set' },
|
|
97
|
+
]}
|
|
98
|
+
checkedValue={selectedValue}
|
|
99
|
+
onChange={handleChange}
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
102
|
+
</PopupParentContext.Provider>
|
|
103
|
+
);
|
|
104
|
+
};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import '@testing-library/jest-dom/vitest';
|
|
2
|
+
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
|
3
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
4
|
+
import { TimeFilter, doesTimeFilterPass } from './TimeFilter';
|
|
5
|
+
import { FILTER_TYPES, type FilterType } from './utils/compareTimeByFilterType';
|
|
6
|
+
import userEvent from '@testing-library/user-event';
|
|
7
|
+
|
|
8
|
+
const createTimeDate = (hours: number, minutes: number): Date => {
|
|
9
|
+
const d = new Date();
|
|
10
|
+
d.setHours(hours, minutes, 0, 0);
|
|
11
|
+
return d;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const createMockParams = (model: { filterType: FilterType; pivot: string } | null, cellValue: Date | string | null) => ({
|
|
15
|
+
model,
|
|
16
|
+
node: {},
|
|
17
|
+
handlerParams: {
|
|
18
|
+
getValue: vi.fn().mockReturnValue(cellValue),
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('TimeFilter', () => {
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
vi.clearAllMocks();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const onModelChange = vi.fn();
|
|
28
|
+
|
|
29
|
+
test('renders TimeFilter', () => {
|
|
30
|
+
const { container } = render(
|
|
31
|
+
<TimeFilter model={{ filterType: FILTER_TYPES.AFTER as FilterType, pivot: '14:30' }} onModelChange={onModelChange} />,
|
|
32
|
+
);
|
|
33
|
+
expect(screen.getByRole('button', { name: /After/i })).toBeInTheDocument();
|
|
34
|
+
const timeInput = container.querySelector('input[type="time"]');
|
|
35
|
+
expect(timeInput).toHaveValue('14:30');
|
|
36
|
+
expect(screen.getByRole('button', { name: 'Clear this filter' })).toHaveTextContent('Reset time');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('calls onModelChange when filter type changes', async () => {
|
|
40
|
+
render(
|
|
41
|
+
<TimeFilter model={{ filterType: FILTER_TYPES.AFTER as FilterType, pivot: '14:30' }} onModelChange={onModelChange} />,
|
|
42
|
+
);
|
|
43
|
+
const dropdownTrigger = screen.getByRole('button', { name: /After/i });
|
|
44
|
+
await userEvent.click(dropdownTrigger);
|
|
45
|
+
const beforeOption = await screen.findByText('Before');
|
|
46
|
+
await userEvent.click(beforeOption);
|
|
47
|
+
expect(onModelChange).toHaveBeenCalledWith({ filterType: FILTER_TYPES.BEFORE as FilterType, pivot: '14:30' });
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('calls onModelChange when pivot changes', () => {
|
|
51
|
+
const { container } = render(
|
|
52
|
+
<TimeFilter model={{ filterType: FILTER_TYPES.AFTER as FilterType, pivot: '14:30' }} onModelChange={onModelChange} />,
|
|
53
|
+
);
|
|
54
|
+
const timeInput = container.querySelector('input[type="time"]');
|
|
55
|
+
fireEvent.change(timeInput!, { target: { value: '09:15' } });
|
|
56
|
+
expect(onModelChange).toHaveBeenLastCalledWith({ filterType: FILTER_TYPES.AFTER as FilterType, pivot: '09:15' });
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('calls onModelChange when reset button is clicked and dropdown displays Before', async () => {
|
|
60
|
+
render(
|
|
61
|
+
<TimeFilter model={{ filterType: FILTER_TYPES.AFTER as FilterType, pivot: '14:30' }} onModelChange={onModelChange} />,
|
|
62
|
+
);
|
|
63
|
+
expect(screen.getByRole('button', { name: /After/i })).toBeInTheDocument();
|
|
64
|
+
const resetButton = screen.getByRole('button', { name: 'Clear this filter' });
|
|
65
|
+
await userEvent.click(resetButton);
|
|
66
|
+
expect(onModelChange).toHaveBeenLastCalledWith({ filterType: FILTER_TYPES.BEFORE as FilterType, pivot: '' });
|
|
67
|
+
expect(screen.getByRole('button', { name: /Before/i })).toBeInTheDocument();
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('doesTimeFilterPass', () => {
|
|
72
|
+
const pivot = '14:30'; // 14:30 = 870 minutes since midnight
|
|
73
|
+
|
|
74
|
+
test('returns true when model is null (show all rows)', () => {
|
|
75
|
+
const params = createMockParams(null, createTimeDate(10, 0));
|
|
76
|
+
expect(doesTimeFilterPass(params as never)).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('returns true when pivot is empty (show all rows)', () => {
|
|
80
|
+
const params = createMockParams({ filterType: FILTER_TYPES.AFTER as FilterType, pivot: '' }, createTimeDate(10, 0));
|
|
81
|
+
expect(doesTimeFilterPass(params as never)).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('returns false when cell value is missing', () => {
|
|
85
|
+
const params = createMockParams({ filterType: FILTER_TYPES.AFTER as FilterType, pivot }, null);
|
|
86
|
+
expect(doesTimeFilterPass(params as never)).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('BEFORE pivot: passes rows with time before pivot, fails rows with time after or equal', () => {
|
|
90
|
+
const model = { filterType: FILTER_TYPES.BEFORE as FilterType, pivot };
|
|
91
|
+
|
|
92
|
+
expect(doesTimeFilterPass(createMockParams(model, createTimeDate(9, 0)) as never)).toBe(true); // 09:00 < 14:30
|
|
93
|
+
expect(doesTimeFilterPass(createMockParams(model, createTimeDate(14, 0)) as never)).toBe(true); // 14:00 < 14:30
|
|
94
|
+
expect(doesTimeFilterPass(createMockParams(model, createTimeDate(14, 29)) as never)).toBe(true);
|
|
95
|
+
|
|
96
|
+
expect(doesTimeFilterPass(createMockParams(model, createTimeDate(14, 30)) as never)).toBe(false); // equal
|
|
97
|
+
expect(doesTimeFilterPass(createMockParams(model, createTimeDate(18, 0)) as never)).toBe(false); // 18:00 > 14:30
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('AFTER pivot: passes rows with time after pivot, fails rows with time before or equal', () => {
|
|
101
|
+
const model = { filterType: FILTER_TYPES.AFTER as FilterType, pivot };
|
|
102
|
+
|
|
103
|
+
expect(doesTimeFilterPass(createMockParams(model, createTimeDate(18, 0)) as never)).toBe(true); // 18:00 > 14:30
|
|
104
|
+
expect(doesTimeFilterPass(createMockParams(model, createTimeDate(14, 31)) as never)).toBe(true);
|
|
105
|
+
|
|
106
|
+
expect(doesTimeFilterPass(createMockParams(model, createTimeDate(14, 30)) as never)).toBe(false); // equal
|
|
107
|
+
expect(doesTimeFilterPass(createMockParams(model, createTimeDate(9, 0)) as never)).toBe(false); // 09:00 < 14:30
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('EQUAL (At) pivot: passes only rows with same time', () => {
|
|
111
|
+
const model = { filterType: FILTER_TYPES.EQUAL as FilterType, pivot };
|
|
112
|
+
|
|
113
|
+
expect(doesTimeFilterPass(createMockParams(model, createTimeDate(14, 30)) as never)).toBe(true);
|
|
114
|
+
|
|
115
|
+
expect(doesTimeFilterPass(createMockParams(model, createTimeDate(14, 29)) as never)).toBe(false);
|
|
116
|
+
expect(doesTimeFilterPass(createMockParams(model, createTimeDate(14, 31)) as never)).toBe(false);
|
|
117
|
+
expect(doesTimeFilterPass(createMockParams(model, createTimeDate(9, 0)) as never)).toBe(false);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('accepts cell value as Date object', () => {
|
|
121
|
+
const model = { filterType: FILTER_TYPES.EQUAL as FilterType, pivot };
|
|
122
|
+
const params = createMockParams(model, createTimeDate(14, 30));
|
|
123
|
+
expect(doesTimeFilterPass(params as never)).toBe(true);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('accepts cell value as time-only string (HH:mm)', () => {
|
|
127
|
+
const model = { filterType: FILTER_TYPES.EQUAL as FilterType, pivot };
|
|
128
|
+
expect(doesTimeFilterPass(createMockParams(model, '14:30') as never)).toBe(true);
|
|
129
|
+
expect(doesTimeFilterPass(createMockParams(model, '09:00') as never)).toBe(false);
|
|
130
|
+
});
|
|
131
|
+
});
|