From 37b037aa03a4361922b4df34f9e43a662177278c Mon Sep 17 00:00:00 2001 From: Marek Mihok Date: Thu, 31 Aug 2023 17:09:36 +0200 Subject: [PATCH 01/19] feat: show empty groups for custom groups #2103 --- ui/src/table.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/ui/src/table.tsx b/ui/src/table.tsx index 86b9709399..4af2430447 100644 --- a/ui/src/table.tsx +++ b/ui/src/table.tsx @@ -565,7 +565,8 @@ const onToggleCollapseAll, onRenderHeader: onRenderGroupHeader, headerProps: { onToggleCollapse }, - isAllGroupsCollapsed: m.groups?.every(({ collapsed = true }) => collapsed) + isAllGroupsCollapsed: m.groups?.every(({ collapsed = true }) => collapsed), + showEmptyGroups: true }} getGroupHeight={getGroupHeight} selection={selection} @@ -733,11 +734,12 @@ export const groupedBy: Dict = [] if (m.groups) { - groups = filteredItems.reduce((acc, { group }, idx) => { - const prevGroup = acc[acc.length - 1] - prevGroup?.key === group - ? prevGroup.count++ - : acc.push({ key: group, name: group, startIndex: idx, count: 1, isCollapsed: getIsCollapsed(group, expandedRefs.current) }) + groups = m.groups.reduce((acc, { label }) => { + const + prevGroup = acc[acc.length - 1], + startIndex = prevGroup ? prevGroup.startIndex + prevGroup.count : 0, + count = filteredItems.filter(({ group }) => group === label).length + acc.push({ key: label, name: label, startIndex, count, isCollapsed: getIsCollapsed(label, expandedRefs.current) }) return acc }, [] as Fluent.IGroup[]) } else { From 080adfd8923366f179c70f1a4c9d8521e2fa824d Mon Sep 17 00:00:00 2001 From: Marek Mihok Date: Wed, 6 Sep 2023 15:53:23 +0200 Subject: [PATCH 02/19] chore: show empty groups - groupBy #2103 --- ui/src/table.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/ui/src/table.tsx b/ui/src/table.tsx index 4af2430447..7a3193efb3 100644 --- a/ui/src/table.tsx +++ b/ui/src/table.tsx @@ -241,11 +241,13 @@ const 'on-hover': Fluent.CheckboxVisibility.onHover, 'hidden': Fluent.CheckboxVisibility.hidden, }, - groupByF = function >(arr: T[], key: S): Dict { - return arr.reduce((rv, x: T) => { - (rv[x[key]] = rv[x[key]] || []).push(x) - return rv - }, {} as Dict) + groupByF = function >(items: T[], filteredItems: T[], key: S): Dict { + const values = items.reduce((arr, item) => { + if (!arr[item.status]) arr[item.status] = [] + return arr + }, [] as any) + filteredItems.forEach(item => values[item[key]].push(item)) + return values }, sortingF = (column: WaveColumn, sortAsc: B) => (rowA: any, rowB: any) => { let a = rowA[column.key], b = rowB[column.key] @@ -744,7 +746,8 @@ export const }, [] as Fluent.IGroup[]) } else { let prevSum = 0 - groupedBy = groupByF(filteredItems, groupByKey) + groupedBy = groupByF(items, filteredItems, groupByKey) + console.log(groupedBy) const groupedByKeys = Object.keys(groupedBy), groupByColType = m.columns.find(c => c.name === groupByKey)?.data_type From 6284c0acd4dd66defa2154c12f7ca01df0633339 Mon Sep 17 00:00:00 2001 From: Marek Mihok Date: Wed, 6 Sep 2023 15:54:01 +0200 Subject: [PATCH 03/19] chore: remove log #2103 --- ui/src/table.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/src/table.tsx b/ui/src/table.tsx index 7a3193efb3..d2ea12e8db 100644 --- a/ui/src/table.tsx +++ b/ui/src/table.tsx @@ -747,7 +747,6 @@ export const } else { let prevSum = 0 groupedBy = groupByF(items, filteredItems, groupByKey) - console.log(groupedBy) const groupedByKeys = Object.keys(groupedBy), groupByColType = m.columns.find(c => c.name === groupByKey)?.data_type From d8ce7b8eb99ec4ec158f1abe11a5b3d3eba61324 Mon Sep 17 00:00:00 2001 From: Marek Mihok Date: Wed, 6 Sep 2023 19:05:58 +0200 Subject: [PATCH 04/19] feat: add missing dependency #2103 --- ui/src/table.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/table.tsx b/ui/src/table.tsx index d2ea12e8db..9222d431d2 100644 --- a/ui/src/table.tsx +++ b/ui/src/table.tsx @@ -771,7 +771,7 @@ export const } return { groupedBy, groups } - }, [m.columns, m.groups]), + }, [items, m.columns, m.groups]), initGroups = React.useCallback(() => { setGroupByKey(groupByKey => { setFilteredItems(filteredItems => { From a37b2286829f0f9333c9ee3a4ac330f7fbc97669 Mon Sep 17 00:00:00 2001 From: Marek Mihok Date: Wed, 6 Sep 2023 23:03:07 +0200 Subject: [PATCH 05/19] chore: refactor grouping function #2103 --- ui/src/table.tsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/ui/src/table.tsx b/ui/src/table.tsx index 9222d431d2..d94680ff3a 100644 --- a/ui/src/table.tsx +++ b/ui/src/table.tsx @@ -242,12 +242,9 @@ const 'hidden': Fluent.CheckboxVisibility.hidden, }, groupByF = function >(items: T[], filteredItems: T[], key: S): Dict { - const values = items.reduce((arr, item) => { - if (!arr[item.status]) arr[item.status] = [] - return arr - }, [] as any) - filteredItems.forEach(item => values[item[key]].push(item)) - return values + const groupedItems = [...new Set(items.map(item => item[key]))].reduce((arr, val) => { arr[val] = []; return arr }, [] as Dict) + filteredItems.forEach(item => groupedItems[item[key]].push(item)) + return groupedItems }, sortingF = (column: WaveColumn, sortAsc: B) => (rowA: any, rowB: any) => { let a = rowA[column.key], b = rowB[column.key] From 2e4d07719b81860691c2b6c96c58da9e638f5da9 Mon Sep 17 00:00:00 2001 From: Marek Mihok Date: Wed, 6 Sep 2023 23:23:58 +0200 Subject: [PATCH 06/19] chore: update tests #2103 --- ui/src/table.test.tsx | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/ui/src/table.test.tsx b/ui/src/table.test.tsx index d9e9667f70..5be3535d1c 100644 --- a/ui/src/table.test.tsx +++ b/ui/src/table.test.tsx @@ -23,7 +23,6 @@ const cell21 = 'Jumps over a dog.', cell31 = 'Wooo hooo.', headerRow = 1, - groupHeaderRow = 1, groupHeaderRowsCount = 2, filteredItem = 1, emitMock = jest.fn(), @@ -720,7 +719,7 @@ describe('Table.tsx', () => { // Search expect(getAllByRole('row')).toHaveLength(tableProps.rows!.length + headerRow + groupHeaderRowsCount) fireEvent.change(getByTestId('search'), { target: { value: 'No match!' } }) - expect(getAllByRole('row')).toHaveLength(headerRow) + expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount) fireEvent.change(getByTestId('search'), { target: { value: '' } }) expect(getAllByRole('row')).toHaveLength(tableProps.rows!.length + headerRow + groupHeaderRowsCount) @@ -1202,7 +1201,7 @@ describe('Table.tsx', () => { expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + tableProps.rows!.length) fireEvent.change(getByTestId('search'), { target: { value: cell21 } }) - expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRow + filteredItem) + expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + filteredItem) }) it('Filters grouped list - single option', () => { @@ -1215,7 +1214,7 @@ describe('Table.tsx', () => { expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + tableProps.rows!.length) fireEvent.click(container.querySelector('.ms-DetailsHeader-filterChevron')!) fireEvent.click(getAllByText('Group2')[2].parentElement!) - expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRow + filteredItem) + expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + filteredItem) }) it('Filters grouped list - multiple options', () => { @@ -1273,7 +1272,7 @@ describe('Table.tsx', () => { const { getByTestId, getAllByRole } = render() fireEvent.change(getByTestId('search'), { target: { value: cell21 } }) - expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRow + filteredItem) + expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + filteredItem) }) it('Sorts rows inside the group of the grouped list', () => { @@ -1290,7 +1289,7 @@ describe('Table.tsx', () => { fireEvent.click(container.querySelector('.ms-DetailsHeader-filterChevron')!) fireEvent.click(getAllByText('Group1')[1].parentElement!) - expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRow + filteredItem) + expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + filteredItem) }) it('Filters grouped list - multiple options', () => { @@ -1346,7 +1345,7 @@ describe('Table.tsx', () => { fireEvent.click(container.querySelector('.ms-DetailsHeader-filterChevron')!) fireEvent.click(getAllByText('Group1')[0].parentElement!) - expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRow) + expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount) fireEvent.click(getAllByText('Group1')[0].parentElement!) expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + filteredItem) @@ -1356,7 +1355,7 @@ describe('Table.tsx', () => { const { getByTestId, getAllByRole } = render() fireEvent.change(getByTestId('search'), { target: { value: cell21 } }) - expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRow + filteredItem) + expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + filteredItem) }) it('Checks if expanded state is preserved after search', () => { @@ -1372,7 +1371,7 @@ describe('Table.tsx', () => { fireEvent.click(container.querySelector('.ms-DetailsHeader-collapseButton')!) fireEvent.change(getByTestId('search'), { target: { value: cell21 } }) - expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRow) + expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount) }) it('Checks if collapsed state is preserved after search', () => { @@ -1399,7 +1398,7 @@ describe('Table.tsx', () => { fireEvent.click(container.querySelector('.ms-DetailsHeader-collapseButton')!) fireEvent.change(getByTestId('search'), { target: { value: cell31 } }) - fireEvent.click(container.querySelector('.ms-GroupHeader-expand')!) + fireEvent.click(container.querySelectorAll('.ms-GroupHeader-expand')[1]!) fireEvent.change(getByTestId('search'), { target: { value: '' } }) expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + filteredItem) }) From e133b712b6138c7eddeed886f71d0e45751e74a1 Mon Sep 17 00:00:00 2001 From: Marek Mihok Date: Thu, 7 Sep 2023 15:28:36 +0200 Subject: [PATCH 07/19] chore: add tests #2103 --- ui/src/table.test.tsx | 70 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 3 deletions(-) diff --git a/ui/src/table.test.tsx b/ui/src/table.test.tsx index 5be3535d1c..090b177100 100644 --- a/ui/src/table.test.tsx +++ b/ui/src/table.test.tsx @@ -1233,7 +1233,10 @@ describe('Table.tsx', () => { }) describe('Groups', () => { - const items = 3 + const + items = 3, + firstGroupLabel = 'GroupA', + secondGroupLabel = 'GroupB' beforeEach(() => { tableProps = { name, @@ -1243,7 +1246,7 @@ describe('Table.tsx', () => { ], groups: [ { - label: "GroupA", + label: firstGroupLabel, rows: [ { name: 'rowname1', cells: [cell11, 'Group2'] }, { name: 'rowname2', cells: [cell21, 'Group1'] }, @@ -1251,7 +1254,7 @@ describe('Table.tsx', () => { collapsed: false }, { - label: "GroupB", + label: secondGroupLabel, rows: [ { name: 'rowname3', cells: [cell31, 'Group2'] } ], @@ -1402,6 +1405,67 @@ describe('Table.tsx', () => { fireEvent.change(getByTestId('search'), { target: { value: '' } }) expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + filteredItem) }) + + it("Checks if empty groups are shown - filter", () => { + const + { container, getByText, getAllByText, getAllByRole } = render(), + groupHeaders = container.querySelectorAll('.ms-GroupHeader-title'), + expectAllGroupsToBeVisible = () => { + expect(getByText(firstGroupLabel)).toBeVisible() + expect(getByText(secondGroupLabel)).toBeVisible() + }, + expectAllItemsToBePresent = () => { + expect(groupHeaders[0]).toHaveTextContent(`${firstGroupLabel}(2)`) + expect(groupHeaders[1]).toHaveTextContent(`${secondGroupLabel}(1)`) + expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + items) + } + + expectAllGroupsToBeVisible() + expectAllItemsToBePresent() + + fireEvent.click(container.querySelector('.ms-DetailsHeader-filterChevron')!) + fireEvent.click(getAllByText('Group1')[1].parentElement!) + + expectAllGroupsToBeVisible() + expect(groupHeaders[0]).toHaveTextContent(`${firstGroupLabel}(1)`) + expect(groupHeaders[1]).toHaveTextContent(`${secondGroupLabel}(0)`) + expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + filteredItem) + + fireEvent.click(getAllByText('Group1')[1].parentElement!) + + expectAllGroupsToBeVisible() + expectAllItemsToBePresent() + }) + + it("Checks if empty groups are shown - search", () => { + const + { container, getByText, getByTestId, getAllByRole } = render(), + groupHeaders = container.querySelectorAll('.ms-GroupHeader-title'), + expectAllGroupsToBeVisible = () => { + expect(getByText(firstGroupLabel)).toBeVisible() + expect(getByText(secondGroupLabel)).toBeVisible() + }, + expectAllItemsToBePresent = () => { + expect(groupHeaders[0]).toHaveTextContent(`${firstGroupLabel}(2)`) + expect(groupHeaders[1]).toHaveTextContent(`${secondGroupLabel}(1)`) + expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + items) + } + + expectAllGroupsToBeVisible() + expectAllItemsToBePresent() + + fireEvent.change(getByTestId('search'), { target: { value: cell31 } }) + + expectAllGroupsToBeVisible() + expect(groupHeaders[0]).toHaveTextContent(`${firstGroupLabel}(0)`) + expect(groupHeaders[1]).toHaveTextContent(`${secondGroupLabel}(1)`) + expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + filteredItem) + + fireEvent.change(getByTestId('search'), { target: { value: '' } }) + + expectAllGroupsToBeVisible() + expectAllItemsToBePresent() + }) }) describe('Reset', () => { From 89d475e6e2392257729971edaf39ef4856af40ed Mon Sep 17 00:00:00 2001 From: Marek Mihok Date: Thu, 7 Sep 2023 15:40:48 +0200 Subject: [PATCH 08/19] chore: update examples and screenshot #2103 --- py/examples/table_groups.py | 3 ++- website/docs/examples/assets/table-groups.png | Bin 14960 -> 54893 bytes website/widgets/form/table.md | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/py/examples/table_groups.py b/py/examples/table_groups.py index e63530ef59..7cc72781e6 100644 --- a/py/examples/table_groups.py +++ b/py/examples/table_groups.py @@ -21,7 +21,8 @@ async def serve(q: Q): ui.table_row(name='row3', cells=['Issue3']), ui.table_row(name='row4', cells=['Issue4']), ui.table_row(name='row5', cells=['Issue5']), - ], collapsed=False)], + ], collapsed=False), + ui.table_group("Jane", [])], height='500px' ) ]) diff --git a/website/docs/examples/assets/table-groups.png b/website/docs/examples/assets/table-groups.png index 7c64ba9c2b2d6347f23f24dea5455a4524469c21..ce3f5e10605274887ba72920e144f0b6d9262abc 100644 GIT binary patch literal 54893 zcmeFYbzD^47C#I~NTZa1gwiS@odbx1Bi&unjdUZRAdPf`ba!`2cMaX$Il#cc@8I*? zd!PHhf4%YNozG|HoH=Ljz0O{H?G@j(PJpbmI5q|;1_A;CHu&Rvc?5(6A>jAXi5%3Hff3LqQ!|K4O zq-7QULEL8(gQU`D0B219r z-E3{itcxb$lrdF45q&dmY;>CzdBHsx{6dPE+x+qC&6h0ex_aI`Vf+{OlHK>r*Dhls zlvAboMiT97hIW1~4e|VM<^7ai91$L{wKgKzCf4@5 z{edl1@pl%;Uve2(_&)U-8(@g?;&l-ENIxO6efna`-}zS>6^)2#g&Nyi?H$ab{-{#5 zPj}B%US7UFbI?gh?3CYa86vWYg2kzSar}(YsrZ8_=v(h7UV+HIVt686UL}cI(qJe> z<$cMwCLJ2Pj6bhpjQvTB`~7*dwuF#bzFOTwW3*y zTRrbObWwN2uZL35aw)eqijKZJ1*XWh zqC5>pL2i}n#R~oS^YX_sCnABb(2J*EeSUorIn_pF@VSM&Q2ye89Dlm_^7@D7G4iTA z9{TBWlOpI7$2t>xfP^ib#ST#IAo^4Dd4C_Kf;`8ITanGc& zXlGaaYK0Di#M{iN3X6I0FDa6`o!r{--_ftUSRwrtWMl4QQ@ozTAnwhaOPN3Lp)0Je zwdhwi8pXF85r^F1y;Z-~Crur_ks|xDsN~u1Y z@*|I3`89c=+FXy?^t*qp_d8#^*Az~VVznM0GyTGGNLzqX(!qQDP8j(IVgMCku>YuW zhXXTWWSc!JTGUszQmjJMy*9d1!hK}7Rzz9cP;Z6hH?@!XS~+ddic_qPk!zTEjp^zvP1kG%nfykDX46tzaUnSqckGVk{! zalW4#J&!WcTteL6y;UTf6bq3vw@2=KuVzA%@FP^TVgzjRmXA`)r}#s`$bt#ED@M}~ zZPA7iViO@$RzgW)_KxRo>AJc;mvX$pkJM!^lP`Q;&~8>Hc9m43tEI{UeL}x#pXANo zPWU{Oenr6jDF|ODxTM=@)xjCl6UFQ6<;v1y4xcA&-piuPcw+eFk0a4(zuC4sw(GT% z)W+7MAN_cr&OGY3jdJ<<)mwj_klMIf@7h@Bh-t9<`+fSKA5m%0A}AUCI%x?bK7HK# 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�OmXu2Hlgp(`GUy|_jQWU>5S@jS zS)yy1&@15Z(~GQ!fNvl>b6QPpsmU15^_kxlde96UMwMV%;Uh0Z_ie0?9XBTF8=SW> zM)8x=^ZLj~ThHN@;B~^H($aBmaVYj*5F|NAdUDt_2jPj!*RMBX!Z=j!?&RMA`c@6!Fy@HfM9D&=6SGFnB)=X2$Hhszjjp}&-)%bc7GNeHf z-73kn)b`HV bLa;%O88kU}(glx@BBY+liDx{~{`mg@P05ze diff --git a/website/widgets/form/table.md b/website/widgets/form/table.md index 24301a429a..441493871f 100644 --- a/website/widgets/form/table.md +++ b/website/widgets/form/table.md @@ -393,7 +393,8 @@ q.page['example'] = ui.form_card(box='1 1 3 4', items=[ ui.table_row(name='row3', cells=['Task3', 'High']), ui.table_row(name='row4', cells=['Task4', 'Low']), ui.table_row(name='row5', cells=['Task5', 'Very High']) - ]) + ]), + ui.table_group("Assigned to Mary", []), ]) ]) ``` From c3be9571a20ae518034300a6670501314d4a85e0 Mon Sep 17 00:00:00 2001 From: Marek Mihok Date: Fri, 8 Sep 2023 14:36:54 +0200 Subject: [PATCH 09/19] chore: add tests for groupBy #2103 --- ui/src/table.test.tsx | 73 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/ui/src/table.test.tsx b/ui/src/table.test.tsx index 090b177100..98a5ae7782 100644 --- a/ui/src/table.test.tsx +++ b/ui/src/table.test.tsx @@ -1078,6 +1078,75 @@ describe('Table.tsx', () => { expect(getAllByRole('row')).toHaveLength(headerRow + tableProps.rows!.length) }) + it("Checks if empty groups are shown - filter", () => { + const + { container, getAllByText, getAllByRole, getByTestId } = render(), + expectAllGroupsToBeVisible = () => { + expect(getAllByText('Group1')[0]).toBeVisible() + expect(getAllByText('Group2')[0]).toBeVisible() + }, + expectAllItemsToBePresent = () => { + const [firstGroupHeader, secondGroupHeader] = container.querySelectorAll('.ms-GroupHeader-title') + expect(firstGroupHeader).toHaveTextContent('Group1(2)') + expect(secondGroupHeader).toHaveTextContent('Group2(1)') + expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + tableProps.rows!.length) + } + + fireEvent.click(getByTestId('groupby')) + fireEvent.click(getAllByText('Col2')[1]!) + fireEvent.click(container.querySelector('.ms-DetailsHeader-collapseButton')!) + + expectAllGroupsToBeVisible() + expectAllItemsToBePresent() + + fireEvent.click(container.querySelector('.ms-DetailsHeader-filterChevron')!) + fireEvent.click(getAllByText('Group1')[3].parentElement!) + + expectAllGroupsToBeVisible() + expect(container.querySelectorAll('.ms-GroupHeader-title')[0]).toHaveTextContent('Group1(2)') + expect(container.querySelectorAll('.ms-GroupHeader-title')[1]).toHaveTextContent('Group2(0)') + expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + tableProps.rows!.length - 1) + + fireEvent.click(getAllByText('Group1')[3].parentElement!) + + expectAllGroupsToBeVisible() + expectAllItemsToBePresent() + }) + + it("Checks if empty groups are shown - search", () => { + const + { container, getAllByText, getAllByRole, getByTestId } = render(), + expectAllGroupsToBeVisible = () => { + expect(getAllByText('Group1')[0]).toBeVisible() + expect(getAllByText('Group2')[0]).toBeVisible() + }, + expectAllItemsToBePresent = () => { + const [firstGroupHeader, secondGroupHeader] = container.querySelectorAll('.ms-GroupHeader-title') + expect(firstGroupHeader).toHaveTextContent('Group1(2)') + expect(secondGroupHeader).toHaveTextContent('Group2(1)') + expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + tableProps.rows!.length) + } + + fireEvent.click(getByTestId('groupby')) + fireEvent.click(getAllByText('Col2')[1]!) + fireEvent.click(container.querySelector('.ms-DetailsHeader-collapseButton')!) + + expectAllGroupsToBeVisible() + expectAllItemsToBePresent() + + fireEvent.change(getByTestId('search'), { target: { value: cell31 } }) + + expectAllGroupsToBeVisible() + expect(container.querySelectorAll('.ms-GroupHeader-title')[0]).toHaveTextContent('Group1(0)') + expect(container.querySelectorAll('.ms-GroupHeader-title')[1]).toHaveTextContent('Group2(1)') + expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + filteredItem) + + fireEvent.change(getByTestId('search'), { target: { value: '' } }) + + expectAllGroupsToBeVisible() + expectAllItemsToBePresent() + }) + it('Does not render group by dropdown when groups are set but pagination is not', () => { const { queryByTestId } = render() expect(queryByTestId('groupby')).not.toBeInTheDocument() @@ -1406,7 +1475,7 @@ describe('Table.tsx', () => { expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + filteredItem) }) - it("Checks if empty groups are shown - filter", () => { + it("Checks if empty custom groups are shown - filter", () => { const { container, getByText, getAllByText, getAllByRole } = render(), groupHeaders = container.querySelectorAll('.ms-GroupHeader-title'), @@ -1437,7 +1506,7 @@ describe('Table.tsx', () => { expectAllItemsToBePresent() }) - it("Checks if empty groups are shown - search", () => { + it("Checks if empty custom groups are shown - search", () => { const { container, getByText, getByTestId, getAllByRole } = render(), groupHeaders = container.querySelectorAll('.ms-GroupHeader-title'), From 074d014ea3a9c81f92bc2b9f276b5929186d56f2 Mon Sep 17 00:00:00 2001 From: Marek Mihok Date: Wed, 13 Sep 2023 10:58:16 +0200 Subject: [PATCH 10/19] chore: re-compute group list only if necessary #2103 --- ui/src/table.tsx | 53 +++++++++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/ui/src/table.tsx b/ui/src/table.tsx index d94680ff3a..2abdfc97db 100644 --- a/ui/src/table.tsx +++ b/ui/src/table.tsx @@ -241,10 +241,11 @@ const 'on-hover': Fluent.CheckboxVisibility.onHover, 'hidden': Fluent.CheckboxVisibility.hidden, }, - groupByF = function >(items: T[], filteredItems: T[], key: S): Dict { - const groupedItems = [...new Set(items.map(item => item[key]))].reduce((arr, val) => { arr[val] = []; return arr }, [] as Dict) - filteredItems.forEach(item => groupedItems[item[key]].push(item)) - return groupedItems + groupByF = function >(arr: T[], key: S): Dict { + return arr.reduce((rv, x: T) => { + (rv[x[key]] = rv[x[key]] || []).push(x) + return rv + }, {} as Dict) }, sortingF = (column: WaveColumn, sortAsc: B) => (rowA: any, rowB: any) => { let a = rowA[column.key], b = rowB[column.key] @@ -727,36 +728,51 @@ export const const expandedRef = expandedRefs[key] return expandedRef === undefined || expandedRef }, + groupList = React.useMemo(() => { + return m.groups + ? m.groups.reduce((acc, { label }) => { + acc[label] = ({ key: label, name: label, startIndex: 0, count: 0 }) + return acc + }, {} as Dict) + : groupByKey !== '*' + ? items.reduce((acc, item) => { + const group = (item as Fluent.IObjectWithKey & Dict)[groupByKey] + if (!acc[group]) acc[group] = ({ key: group, name: group, startIndex: 0, count: 0 }) + return acc + }, {} as Dict) + : {} + }, [m.groups, groupByKey, items]), makeGroups = React.useCallback((groupByKey: S, filteredItems: (Fluent.IObjectWithKey & Dict)[]) => { + const allGroups: Dict = { ...groupList } let groups: Fluent.IGroup[], groupedBy: Dict = [] if (m.groups) { - groups = m.groups.reduce((acc, { label }) => { - const - prevGroup = acc[acc.length - 1], - startIndex = prevGroup ? prevGroup.startIndex + prevGroup.count : 0, - count = filteredItems.filter(({ group }) => group === label).length - acc.push({ key: label, name: label, startIndex, count, isCollapsed: getIsCollapsed(label, expandedRefs.current) }) - return acc - }, [] as Fluent.IGroup[]) + filteredItems.forEach(({ group }, idx) => { + const prevGroup = allGroups[group] + prevGroup.count === 0 + ? allGroups[group] = ({ key: group, name: group, startIndex: idx, count: 1, isCollapsed: getIsCollapsed(group, expandedRefs.current) }) + : prevGroup.count++ + }) + groups = Object.values(allGroups) } else { let prevSum = 0 - groupedBy = groupByF(items, filteredItems, groupByKey) + groupedBy = groupByF(filteredItems, groupByKey) const groupedByKeys = Object.keys(groupedBy), groupByColType = m.columns.find(c => c.name === groupByKey)?.data_type - groups = groupedByKeys.map((key, i) => { + groupedByKeys.forEach((key, i) => { if (i !== 0) { const prevKey = groupedByKeys[i - 1] prevSum += groupedBy[prevKey].length } - const name = groupByColType === 'time' ? new Date(key).toLocaleString() : key - return { key, name, startIndex: prevSum, count: groupedBy[key].length, isCollapsed: getIsCollapsed(key, expandedRefs.current) } - }).sort(({ name: name1 }, { name: name2 }) => { + allGroups[key] = { key, name, startIndex: prevSum, count: groupedBy[key].length, isCollapsed: getIsCollapsed(key, expandedRefs.current) } + }) + + groups = Object.values(allGroups).sort(({ name: name1 }, { name: name2 }) => { const numName1 = Number(name1), numName2 = Number(name2) if (!isNaN(numName1) && !isNaN(numName2)) return numName1 - numName2 @@ -766,9 +782,8 @@ export const return name2 < name1 ? 1 : -1 }) } - return { groupedBy, groups } - }, [items, m.columns, m.groups]), + }, [groupList, m.columns, m.groups]), initGroups = React.useCallback(() => { setGroupByKey(groupByKey => { setFilteredItems(filteredItems => { From 1a70c664816c7be9986153669d18aa7e5b3b7735 Mon Sep 17 00:00:00 2001 From: Marek Mihok Date: Wed, 13 Sep 2023 12:05:33 +0200 Subject: [PATCH 11/19] chore: rename dict #2103 --- ui/src/table.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/src/table.tsx b/ui/src/table.tsx index 2abdfc97db..d8d095ab76 100644 --- a/ui/src/table.tsx +++ b/ui/src/table.tsx @@ -728,7 +728,7 @@ export const const expandedRef = expandedRefs[key] return expandedRef === undefined || expandedRef }, - groupList = React.useMemo(() => { + groupsDict = React.useMemo(() => { return m.groups ? m.groups.reduce((acc, { label }) => { acc[label] = ({ key: label, name: label, startIndex: 0, count: 0 }) @@ -743,7 +743,7 @@ export const : {} }, [m.groups, groupByKey, items]), makeGroups = React.useCallback((groupByKey: S, filteredItems: (Fluent.IObjectWithKey & Dict)[]) => { - const allGroups: Dict = { ...groupList } + const allGroups: Dict = { ...groupsDict } let groups: Fluent.IGroup[], groupedBy: Dict = [] @@ -783,7 +783,7 @@ export const }) } return { groupedBy, groups } - }, [groupList, m.columns, m.groups]), + }, [groupsDict, m.columns, m.groups]), initGroups = React.useCallback(() => { setGroupByKey(groupByKey => { setFilteredItems(filteredItems => { From 234d55592e734d1edc58606756384a655d2a9ddf Mon Sep 17 00:00:00 2001 From: Marek Mihok Date: Wed, 13 Sep 2023 12:12:39 +0200 Subject: [PATCH 12/19] chore: minor refactor #2103 --- ui/src/table.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ui/src/table.tsx b/ui/src/table.tsx index d8d095ab76..593d0b6d07 100644 --- a/ui/src/table.tsx +++ b/ui/src/table.tsx @@ -750,10 +750,9 @@ export const if (m.groups) { filteredItems.forEach(({ group }, idx) => { - const prevGroup = allGroups[group] - prevGroup.count === 0 + allGroups[group].count === 0 ? allGroups[group] = ({ key: group, name: group, startIndex: idx, count: 1, isCollapsed: getIsCollapsed(group, expandedRefs.current) }) - : prevGroup.count++ + : allGroups[group].count++ }) groups = Object.values(allGroups) } else { From 6c530f81bcc631c7036d9376fd95b70dfaeb0990 Mon Sep 17 00:00:00 2001 From: Marek Mihok Date: Wed, 13 Sep 2023 15:01:50 +0200 Subject: [PATCH 13/19] chore: rename dict to initialGroups #2103 --- ui/src/table.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/src/table.tsx b/ui/src/table.tsx index 593d0b6d07..f5e14ce3b7 100644 --- a/ui/src/table.tsx +++ b/ui/src/table.tsx @@ -728,7 +728,7 @@ export const const expandedRef = expandedRefs[key] return expandedRef === undefined || expandedRef }, - groupsDict = React.useMemo(() => { + initialGroups = React.useMemo(() => { return m.groups ? m.groups.reduce((acc, { label }) => { acc[label] = ({ key: label, name: label, startIndex: 0, count: 0 }) @@ -743,7 +743,7 @@ export const : {} }, [m.groups, groupByKey, items]), makeGroups = React.useCallback((groupByKey: S, filteredItems: (Fluent.IObjectWithKey & Dict)[]) => { - const allGroups: Dict = { ...groupsDict } + const allGroups: Dict = { ...initialGroups } let groups: Fluent.IGroup[], groupedBy: Dict = [] @@ -782,7 +782,7 @@ export const }) } return { groupedBy, groups } - }, [groupsDict, m.columns, m.groups]), + }, [initialGroups, m.columns, m.groups]), initGroups = React.useCallback(() => { setGroupByKey(groupByKey => { setFilteredItems(filteredItems => { From 44e8f3f2bc374d3f29a508e827f6cb32a402157f Mon Sep 17 00:00:00 2001 From: Marek Mihok Date: Mon, 18 Sep 2023 15:18:16 +0200 Subject: [PATCH 14/19] chore: store group names only instead of intial groups #2103 --- ui/src/table.tsx | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/ui/src/table.tsx b/ui/src/table.tsx index f5e14ce3b7..715eb2971e 100644 --- a/ui/src/table.tsx +++ b/ui/src/table.tsx @@ -728,22 +728,18 @@ export const const expandedRef = expandedRefs[key] return expandedRef === undefined || expandedRef }, - initialGroups = React.useMemo(() => { + groupNames = React.useMemo(() => { return m.groups - ? m.groups.reduce((acc, { label }) => { - acc[label] = ({ key: label, name: label, startIndex: 0, count: 0 }) - return acc - }, {} as Dict) + ? m.groups.reduce((acc, { label }) => acc.add(label), new Set()) : groupByKey !== '*' - ? items.reduce((acc, item) => { - const group = (item as Fluent.IObjectWithKey & Dict)[groupByKey] - if (!acc[group]) acc[group] = ({ key: group, name: group, startIndex: 0, count: 0 }) - return acc - }, {} as Dict) - : {} + ? (items as Dict[]).reduce((acc, item) => acc.add(item[groupByKey]), new Set()) + : new Set() }, [m.groups, groupByKey, items]), makeGroups = React.useCallback((groupByKey: S, filteredItems: (Fluent.IObjectWithKey & Dict)[]) => { - const allGroups: Dict = { ...initialGroups } + const allGroups = [...groupNames].reduce((acc, groupName) => { + acc[groupName] = { key: groupName, name: groupName, startIndex: 0, count: 0, isCollapsed: getIsCollapsed(groupName, expandedRefs.current) } + return acc + }, {} as Dict) let groups: Fluent.IGroup[], groupedBy: Dict = [] @@ -751,7 +747,7 @@ export const if (m.groups) { filteredItems.forEach(({ group }, idx) => { allGroups[group].count === 0 - ? allGroups[group] = ({ key: group, name: group, startIndex: idx, count: 1, isCollapsed: getIsCollapsed(group, expandedRefs.current) }) + ? allGroups[group] = { ...allGroups[group], startIndex: idx, count: 1 } : allGroups[group].count++ }) groups = Object.values(allGroups) @@ -767,6 +763,7 @@ export const const prevKey = groupedByKeys[i - 1] prevSum += groupedBy[prevKey].length } + if (groupByColType === 'time') console.log(new Date(key).toLocaleString(), key) const name = groupByColType === 'time' ? new Date(key).toLocaleString() : key allGroups[key] = { key, name, startIndex: prevSum, count: groupedBy[key].length, isCollapsed: getIsCollapsed(key, expandedRefs.current) } }) @@ -782,7 +779,7 @@ export const }) } return { groupedBy, groups } - }, [initialGroups, m.columns, m.groups]), + }, [groupNames, m.columns, m.groups]), initGroups = React.useCallback(() => { setGroupByKey(groupByKey => { setFilteredItems(filteredItems => { From 41579bd65aff431cfaa67608760a363ac13eed47 Mon Sep 17 00:00:00 2001 From: Marek Mihok Date: Mon, 18 Sep 2023 20:18:08 +0200 Subject: [PATCH 15/19] fix: group header name broken when grouped by column with time data type #2103 --- ui/src/table.test.tsx | 24 ++++++++++++++++++++++++ ui/src/table.tsx | 12 ++++++------ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/ui/src/table.test.tsx b/ui/src/table.test.tsx index 98a5ae7782..93fb1539af 100644 --- a/ui/src/table.test.tsx +++ b/ui/src/table.test.tsx @@ -1299,6 +1299,30 @@ describe('Table.tsx', () => { fireEvent.click(getAllByText('Group2')[0].parentElement!) expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + tableProps.rows!.length) }) + + it('Checks if group name is correct when grouped by column width time data', () => { + tableProps = { + ...tableProps, + groupable: true, + columns: [ + { name: 'colname1', label: 'Col1' }, + { name: 'colname2', label: 'Col2', data_type: 'time' }, + ], + rows: [ + { name: 'rowname1', cells: [cell11, '1655927271'] }, + { name: 'rowname2', cells: [cell21, '1655927271000'] }, + ] + } + const { container, getAllByText, getByTestId, getAllByRole } = render() + + fireEvent.click(getByTestId('groupby')) + fireEvent.click(getAllByText('Col2')[1]!) + fireEvent.click(container.querySelector('.ms-DetailsHeader-collapseButton')!) + + expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + tableProps.rows!.length) + expect(container.querySelectorAll('.ms-GroupHeader-title')[0]).toHaveTextContent('1/20/1970, 4:58:47 AM(1)') + expect(container.querySelectorAll('.ms-GroupHeader-title')[1]).toHaveTextContent('6/22/2022, 8:47:51 PM(1)') + }) }) describe('Groups', () => { diff --git a/ui/src/table.tsx b/ui/src/table.tsx index 715eb2971e..e73d71dbb4 100644 --- a/ui/src/table.tsx +++ b/ui/src/table.tsx @@ -270,6 +270,10 @@ const : b > a ? 1 : -1 }, formatNum = (num: U) => num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","), + valueToDateString = (value: S) => { + const epoch = Number(value) + return new Date(isNaN(epoch) ? value : epoch).toLocaleString() + }, toCSV = (data: unknown[][]): S => data.map(row => { const line = JSON.stringify(row) return line.substr(1, line.length - 2) @@ -509,10 +513,7 @@ const ) - if (col.dataType === 'time') { - const epoch = Number(v) - v = new Date(isNaN(epoch) ? v : epoch).toLocaleString() - } + if (col.dataType === 'time') v = valueToDateString(v) if (col.key === primaryColumnKey) { const onClick = () => { @@ -763,8 +764,7 @@ export const const prevKey = groupedByKeys[i - 1] prevSum += groupedBy[prevKey].length } - if (groupByColType === 'time') console.log(new Date(key).toLocaleString(), key) - const name = groupByColType === 'time' ? new Date(key).toLocaleString() : key + const name = groupByColType === 'time' ? valueToDateString(key) : key allGroups[key] = { key, name, startIndex: prevSum, count: groupedBy[key].length, isCollapsed: getIsCollapsed(key, expandedRefs.current) } }) From 1b1f7a7c3b30af95675654b3c0d41a761da8da79 Mon Sep 17 00:00:00 2001 From: Marek Mihok Date: Mon, 18 Sep 2023 21:54:03 +0200 Subject: [PATCH 16/19] fix: Use correct names in filter contextual menu when col data type is time #2103 --- ui/src/table.test.tsx | 29 +++++++++++++++++++++++++++++ ui/src/table.tsx | 4 ++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/ui/src/table.test.tsx b/ui/src/table.test.tsx index 93fb1539af..b645248360 100644 --- a/ui/src/table.test.tsx +++ b/ui/src/table.test.tsx @@ -1323,6 +1323,35 @@ describe('Table.tsx', () => { expect(container.querySelectorAll('.ms-GroupHeader-title')[0]).toHaveTextContent('1/20/1970, 4:58:47 AM(1)') expect(container.querySelectorAll('.ms-GroupHeader-title')[1]).toHaveTextContent('6/22/2022, 8:47:51 PM(1)') }) + + it.only('Checks if name of empty group is correct when grouped by column width time data', () => { + tableProps = { + ...tableProps, + groupable: true, + columns: [ + { name: 'colname1', label: 'Col1' }, + { name: 'colname2', label: 'Col2', data_type: 'time', filterable: true }, + ], + rows: [ + { name: 'rowname1', cells: [cell11, '1655927271'] }, + { name: 'rowname2', cells: [cell21, '2655927271000'] }, // TODO: Fix case if values are similar: '1655927271000' + ] + } + const { container, getAllByText, getByTestId, getAllByRole } = render() + + fireEvent.click(getByTestId('groupby')) + fireEvent.click(getAllByText('Col2')[1]!) + fireEvent.click(container.querySelector('.ms-DetailsHeader-collapseButton')!) + + expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + tableProps.rows!.length) + + fireEvent.click(container.querySelector('.ms-DetailsHeader-filterChevron')!) + fireEvent.click(getAllByText('2/28/2054, 9:34:31 PM')[2].parentElement!) + + expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + filteredItem) + expect(container.querySelectorAll('.ms-GroupHeader-title')[0]).toHaveTextContent('1/20/1970, 4:58:47 AM(0)') + expect(container.querySelectorAll('.ms-GroupHeader-title')[1]).toHaveTextContent('2/28/2054, 9:34:31 PM(1)') + }) }) describe('Groups', () => { diff --git a/ui/src/table.tsx b/ui/src/table.tsx index e73d71dbb4..d096f7387c 100644 --- a/ui/src/table.tsx +++ b/ui/src/table.tsx @@ -321,7 +321,7 @@ const menuFilters.map(({ key, data, checked }) => ( Date: Tue, 19 Sep 2023 11:11:16 +0200 Subject: [PATCH 17/19] fix: Use valueToDateString for name prop of empty groups as well #2103 --- ui/src/table.test.tsx | 2 +- ui/src/table.tsx | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ui/src/table.test.tsx b/ui/src/table.test.tsx index b645248360..14a9772360 100644 --- a/ui/src/table.test.tsx +++ b/ui/src/table.test.tsx @@ -1324,7 +1324,7 @@ describe('Table.tsx', () => { expect(container.querySelectorAll('.ms-GroupHeader-title')[1]).toHaveTextContent('6/22/2022, 8:47:51 PM(1)') }) - it.only('Checks if name of empty group is correct when grouped by column width time data', () => { + it('Checks if name of empty group is correct when grouped by column width time data', () => { tableProps = { ...tableProps, groupable: true, diff --git a/ui/src/table.tsx b/ui/src/table.tsx index d096f7387c..e178589129 100644 --- a/ui/src/table.tsx +++ b/ui/src/table.tsx @@ -764,10 +764,11 @@ export const const prevKey = groupedByKeys[i - 1] prevSum += groupedBy[prevKey].length } - const name = groupByColType === 'time' ? valueToDateString(key) : key // TODO: Use valueToDateString for empty groups as well. - allGroups[key] = { key, name, startIndex: prevSum, count: groupedBy[key].length, isCollapsed: getIsCollapsed(key, expandedRefs.current) } + allGroups[key] = { key, name: key, startIndex: prevSum, count: groupedBy[key].length, isCollapsed: getIsCollapsed(key, expandedRefs.current) } }) + if (groupByColType === 'time') Object.keys(allGroups).forEach(key => { allGroups[key].name = valueToDateString(key) }) + groups = Object.values(allGroups).sort(({ name: name1 }, { name: name2 }) => { const numName1 = Number(name1), numName2 = Number(name2) if (!isNaN(numName1) && !isNaN(numName2)) return numName1 - numName2 From 0b5531983b4f73299ecf4187d190488c4bbd2707 Mon Sep 17 00:00:00 2001 From: Marek Mihok Date: Tue, 19 Sep 2023 14:50:14 +0200 Subject: [PATCH 18/19] fix: Do not merge groups when grouping by multiple times in a row #2103 --- ui/src/table.test.tsx | 24 ++++++++++++++++++++++++ ui/src/table.tsx | 2 ++ 2 files changed, 26 insertions(+) diff --git a/ui/src/table.test.tsx b/ui/src/table.test.tsx index 14a9772360..bbe8dc1749 100644 --- a/ui/src/table.test.tsx +++ b/ui/src/table.test.tsx @@ -1162,6 +1162,30 @@ describe('Table.tsx', () => { expect(emitMock).toHaveBeenCalledTimes(1) }) + it('Checks if groups are correct when grouping by multiple times', () => { + const + { container, getAllByText, getByTestId, getAllByRole } = render(), + groupBy = (col: string) => { + fireEvent.click(getByTestId('groupby')) + fireEvent.click(getAllByText(col)[1]!) + fireEvent.click(container.querySelector('.ms-DetailsHeader-collapseButton')!) + } + + groupBy('Col1') + + expect(getAllByRole('row')).toHaveLength(headerRow + 2 * tableProps.rows!.length) + const groupHeaders = container.querySelectorAll('.ms-GroupHeader-title') + expect(groupHeaders[0]).toHaveTextContent(`${cell21}(1)`) + expect(groupHeaders[1]).toHaveTextContent(`${cell11}(1)`) + expect(groupHeaders[2]).toHaveTextContent(`${cell31}(1)`) + + groupBy('Col2') + + expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + tableProps.rows!.length) + expect(container.querySelectorAll('.ms-GroupHeader-title')[0]).toHaveTextContent(`${'Group1'}(2)`) + expect(container.querySelectorAll('.ms-GroupHeader-title')[1]).toHaveTextContent(`${'Group2'}(1)`) + }) + it('Renders alphabetically sorted group by list - strings', () => { const { container, getAllByText, getByTestId } = render() diff --git a/ui/src/table.tsx b/ui/src/table.tsx index e178589129..20c5f08938 100644 --- a/ui/src/table.tsx +++ b/ui/src/table.tsx @@ -988,6 +988,8 @@ export const if (!m.pagination) reset() }, [items]) + useUpdateOnlyEffect(() => { initGroups() }, [groupNames, initGroups]) + React.useEffect(() => { if (m.groups) { expandedRefs.current = m.groups?.reduce((acc, { label, collapsed = true }) => { From cfdb186315fc20a7cbfbae0de24f8f0c78b9aa50 Mon Sep 17 00:00:00 2001 From: Marek Mihok Date: Tue, 19 Sep 2023 14:55:43 +0200 Subject: [PATCH 19/19] chore: Remove unnecessary todo #2103 --- ui/src/table.test.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/src/table.test.tsx b/ui/src/table.test.tsx index bbe8dc1749..39e2fc3803 100644 --- a/ui/src/table.test.tsx +++ b/ui/src/table.test.tsx @@ -1358,7 +1358,7 @@ describe('Table.tsx', () => { ], rows: [ { name: 'rowname1', cells: [cell11, '1655927271'] }, - { name: 'rowname2', cells: [cell21, '2655927271000'] }, // TODO: Fix case if values are similar: '1655927271000' + { name: 'rowname2', cells: [cell21, '1655927271000'] }, ] } const { container, getAllByText, getByTestId, getAllByRole } = render() @@ -1370,11 +1370,11 @@ describe('Table.tsx', () => { expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + tableProps.rows!.length) fireEvent.click(container.querySelector('.ms-DetailsHeader-filterChevron')!) - fireEvent.click(getAllByText('2/28/2054, 9:34:31 PM')[2].parentElement!) + fireEvent.click(getAllByText('6/22/2022, 8:47:51 PM')[2].parentElement!) expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + filteredItem) expect(container.querySelectorAll('.ms-GroupHeader-title')[0]).toHaveTextContent('1/20/1970, 4:58:47 AM(0)') - expect(container.querySelectorAll('.ms-GroupHeader-title')[1]).toHaveTextContent('2/28/2054, 9:34:31 PM(1)') + expect(container.querySelectorAll('.ms-GroupHeader-title')[1]).toHaveTextContent('6/22/2022, 8:47:51 PM(1)') }) })