-
Notifications
You must be signed in to change notification settings - Fork 35
feat(ColumnManagementModal): add support for extended columns #929
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
adc7bde
3137822
618b60c
c8ac82e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,9 @@ | ||
| import '@testing-library/jest-dom' | ||
| import { render, screen, fireEvent } from '@testing-library/react'; | ||
| import '@testing-library/jest-dom'; | ||
| import { render, screen, fireEvent, within } from '@testing-library/react'; | ||
| import userEvent from '@testing-library/user-event'; | ||
| import ColumnManagementModal, { ColumnManagementModalColumn } from './ColumnManagementModal'; | ||
|
|
||
| const DEFAULT_COLUMNS : ColumnManagementModalColumn[] = [ | ||
| const DEFAULT_COLUMNS: ColumnManagementModalColumn[] = [ | ||
| { | ||
| title: 'ID', | ||
| key: 'id', | ||
|
|
@@ -31,23 +31,35 @@ const DEFAULT_COLUMNS : ColumnManagementModalColumn[] = [ | |
| } | ||
| ]; | ||
|
|
||
| interface ExtendedColumn extends ColumnManagementModalColumn { | ||
| dataKey: string; | ||
| } | ||
|
|
||
| const EXTENDED_COLUMNS: ExtendedColumn[] = DEFAULT_COLUMNS.map((col) => ({ | ||
| ...col, | ||
| dataKey: `row.${col.key}` | ||
| })); | ||
|
|
||
| const onClose = jest.fn(); | ||
| const setColumns = jest.fn(); | ||
|
|
||
| // Simple mock to track when DragDropSort is used | ||
| jest.mock('@patternfly/react-drag-drop', () => ({ | ||
| DragDropSort: ({ children }) => <div data-testid="drag-drop-sort">{children}</div>, | ||
| Droppable: ({ wrapper }) => wrapper, | ||
| Droppable: ({ wrapper }) => wrapper | ||
| })); | ||
|
|
||
| const renderColumnManagementModal = (props = {}) => render(<ColumnManagementModal | ||
| appliedColumns={DEFAULT_COLUMNS} | ||
| applyColumns={newColumns => setColumns(newColumns)} | ||
| isOpen | ||
| onClose={onClose} | ||
| data-testid="column-mgmt-modal" | ||
| {...props} | ||
| />); | ||
| const renderColumnManagementModal = (props = {}) => | ||
| render( | ||
| <ColumnManagementModal | ||
| appliedColumns={DEFAULT_COLUMNS} | ||
| applyColumns={(newColumns) => setColumns(newColumns)} | ||
| isOpen | ||
| onClose={onClose} | ||
| data-testid="column-mgmt-modal" | ||
| {...props} | ||
| /> | ||
| ); | ||
|
|
||
| beforeEach(() => { | ||
| jest.clearAllMocks(); | ||
|
|
@@ -56,17 +68,21 @@ beforeEach(() => { | |
|
|
||
| const getCheckboxesState = () => { | ||
| // Get only the column checkboxes (exclude the BulkSelect checkbox) | ||
| const checkboxes = screen.getByTestId('column-mgmt-modal').querySelectorAll('input[type="checkbox"][data-testid^="column-check-"]'); | ||
| return (Array.from(checkboxes) as HTMLInputElement[]).map(c => c.checked); | ||
| } | ||
| const checkboxes = screen | ||
| .getByTestId('column-mgmt-modal') | ||
| .querySelectorAll('input[type="checkbox"][data-testid^="column-check-"]'); | ||
| return (Array.from(checkboxes) as HTMLInputElement[]).map((c) => c.checked); | ||
| }; | ||
|
|
||
| describe('ColumnManagementModal component', () => { | ||
| it('should have disabled and checked checkboxes for columns that should always be shown', () => { | ||
| expect(getCheckboxesState()).toEqual(DEFAULT_COLUMNS.map(c => c.isShownByDefault)); | ||
| expect(getCheckboxesState()).toEqual(DEFAULT_COLUMNS.map((c) => c.isShownByDefault)); | ||
| }); | ||
|
|
||
| it('should have checkbox checked if column is shown by default', () => { | ||
| const idCheckbox = screen.getByTestId('column-mgmt-modal').querySelector('input[type="checkbox"][data-testid="column-check-id"]'); | ||
| const idCheckbox = screen | ||
| .getByTestId('column-mgmt-modal') | ||
| .querySelector('input[type="checkbox"][data-testid="column-check-id"]'); | ||
|
|
||
| expect(idCheckbox).toHaveAttribute('disabled'); | ||
| expect(idCheckbox).toHaveAttribute('checked'); | ||
|
|
@@ -81,18 +97,20 @@ describe('ColumnManagementModal component', () => { | |
|
|
||
| fireEvent.click(screen.getByText('Reset to default')); | ||
|
|
||
| expect(getCheckboxesState()).toEqual(DEFAULT_COLUMNS.map(c => c.isShownByDefault)); | ||
| expect(getCheckboxesState()).toEqual(DEFAULT_COLUMNS.map((c) => c.isShownByDefault)); | ||
| }); | ||
|
|
||
| it('should call onReset callback when reset to default is clicked', () => { | ||
| const onResetMock = jest.fn(); | ||
| render(<ColumnManagementModal | ||
| appliedColumns={DEFAULT_COLUMNS} | ||
| applyColumns={setColumns} | ||
| isOpen | ||
| onClose={onClose} | ||
| onReset={onResetMock} | ||
| />); | ||
| render( | ||
| <ColumnManagementModal | ||
| appliedColumns={DEFAULT_COLUMNS} | ||
| applyColumns={setColumns} | ||
| isOpen | ||
| onClose={onClose} | ||
| onReset={onResetMock} | ||
| /> | ||
| ); | ||
|
|
||
| const resetButtons = screen.getAllByText('Reset to default'); | ||
| // Click the last one rendered (the new modal) | ||
|
|
@@ -103,13 +121,15 @@ describe('ColumnManagementModal component', () => { | |
|
|
||
| it('should display custom reset button label', () => { | ||
| const customLabel = 'Restaurer par défaut'; | ||
| render(<ColumnManagementModal | ||
| appliedColumns={DEFAULT_COLUMNS} | ||
| applyColumns={setColumns} | ||
| isOpen | ||
| onClose={onClose} | ||
| resetToDefaultLabel={customLabel} | ||
| />); | ||
| render( | ||
| <ColumnManagementModal | ||
| appliedColumns={DEFAULT_COLUMNS} | ||
| applyColumns={setColumns} | ||
| isOpen | ||
| onClose={onClose} | ||
| resetToDefaultLabel={customLabel} | ||
| /> | ||
| ); | ||
|
|
||
| expect(screen.getByText(customLabel)).toBeInTheDocument(); | ||
| }); | ||
|
|
@@ -124,15 +144,15 @@ describe('ColumnManagementModal component', () => { | |
| const selectAllButton = screen.getByText('Select all (4)'); | ||
| await userEvent.click(selectAllButton); | ||
|
|
||
| expect(getCheckboxesState()).toEqual(DEFAULT_COLUMNS.map(_ => true)); | ||
| expect(getCheckboxesState()).toEqual(DEFAULT_COLUMNS.map((_) => true)); | ||
| }); | ||
|
|
||
| it('should change columns on save', () => { | ||
| fireEvent.click(screen.getByText('Impact')); | ||
| fireEvent.click(screen.getByText('Save')); | ||
|
|
||
| const expectedColumns = DEFAULT_COLUMNS; | ||
| const impactColumn = expectedColumns.find(c => c.title === 'Impact'); | ||
| const impactColumn = expectedColumns.find((c) => c.title === 'Impact'); | ||
|
|
||
| if (impactColumn === undefined) { | ||
| throw new Error('Expected to find "Impact" column in "DEFAULT_COLUMNS"'); | ||
|
|
@@ -153,6 +173,35 @@ describe('ColumnManagementModal component', () => { | |
| expect(setColumns).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('should preserve extended column fields when saving', () => { | ||
| const applyColumnsMock = jest.fn(); | ||
| render( | ||
| <ColumnManagementModal<ExtendedColumn> | ||
| appliedColumns={EXTENDED_COLUMNS} | ||
| applyColumns={applyColumnsMock} | ||
| isOpen | ||
| onClose={onClose} | ||
| data-testid="extended-column-modal" | ||
| /> | ||
| ); | ||
|
|
||
| const modal = screen.getByTestId('extended-column-modal'); | ||
| fireEvent.click(within(modal).getByTestId('column-check-impact')); | ||
| fireEvent.click(within(modal).getByRole('button', { name: 'Save' })); | ||
|
|
||
| expect(applyColumnsMock).toHaveBeenCalledTimes(1); | ||
| const saved = applyColumnsMock.mock.calls[0][0] as ExtendedColumn[]; | ||
|
|
||
| // Test that we have same number of columns | ||
| expect(saved).toHaveLength(EXTENDED_COLUMNS.length); | ||
|
|
||
| // Test that extension keys were preserved | ||
| saved.forEach((col) => { | ||
| const source = EXTENDED_COLUMNS.find((aec) => aec.key === col.key); | ||
| expect(source?.dataKey).toBe(col.dataKey); | ||
| }); | ||
| }); | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Would be good to also add a test for Something like: it('should preserve extended column fields after drag-drop reorder and save', () => {
const applyColumnsMock = jest.fn();
const reorderedExtended: ExtendedColumn[] = [
EXTENDED_COLUMNS[3],
EXTENDED_COLUMNS[0],
EXTENDED_COLUMNS[2],
EXTENDED_COLUMNS[1]
];
render(
<ColumnManagementModal<ExtendedColumn>
appliedColumns={reorderedExtended}
applyColumns={applyColumnsMock}
enableDragDrop
isOpen
onClose={onClose}
/>
);
// reset + save triggers applyColumns
const resetButtons = screen.getAllByText('Reset to default');
fireEvent.click(resetButtons[resetButtons.length - 1]);
fireEvent.click(screen.getAllByText('Save').pop()!);
const saved = applyColumnsMock.mock.calls[0][0] as ExtendedColumn[];
saved.forEach((col) => {
expect(col.dataKey).toBeDefined();
});
});
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test and some others plus some comments are assuming resetToDefault reorders columns to default order but this is not the case. If I'm not mistaken this functionality is missing. Hence I'd skip this coverage until we have resetToDefault order ready. Or should I add it preemptively? |
||
| describe('enableDragDrop prop', () => { | ||
| it('should default enableDragDrop to false', () => { | ||
| // Default behavior should not enable drag and drop | ||
|
|
@@ -175,16 +224,18 @@ describe('ColumnManagementModal component', () => { | |
| DEFAULT_COLUMNS[3], // Score (last -> first) | ||
| DEFAULT_COLUMNS[0], // ID | ||
| DEFAULT_COLUMNS[2], // Impact | ||
| DEFAULT_COLUMNS[1], // Publish date | ||
| DEFAULT_COLUMNS[1] // Publish date | ||
| ]; | ||
|
|
||
| const applyColumnsMock = jest.fn(); | ||
| render(<ColumnManagementModal | ||
| appliedColumns={reorderedColumns} | ||
| applyColumns={applyColumnsMock} | ||
| isOpen | ||
| onClose={onClose} | ||
| />); | ||
| render( | ||
| <ColumnManagementModal | ||
| appliedColumns={reorderedColumns} | ||
| applyColumns={applyColumnsMock} | ||
| isOpen | ||
| onClose={onClose} | ||
| /> | ||
| ); | ||
|
|
||
| // Click reset to default - get all buttons and click the last one | ||
| const resetButtons = screen.getAllByText('Reset to default'); | ||
|
|
@@ -197,7 +248,7 @@ describe('ColumnManagementModal component', () => { | |
| // Verify that the saved columns match the original reordered columns order | ||
| // (after reset, it should restore the order from appliedColumns which is reorderedColumns) | ||
| expect(applyColumnsMock).toHaveBeenCalledWith( | ||
| reorderedColumns.map(col => ({ | ||
| reorderedColumns.map((col) => ({ | ||
| ...col, | ||
| isShown: col.isShownByDefault | ||
| })) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: Consider replacing the
forEach+ individualexpectwith a singletoEqualassertion. This is more robust — it would also catch unexpected property mutations or additions on the base fields, not just verify thatdataKeysurvived:This covers the full shape of every column in one shot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this suggestion, Thanks! I just used
isShown: col.key === 'impact' ? !col.isShown : col.isShown, ascol.isShownis the value we depend on primarily. This doesn't matter in current EXTENDED_COLUMNS setup but in some it could.