Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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',
Expand Down Expand Up @@ -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();
Expand All @@ -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');
Expand All @@ -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)
Expand All @@ -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();
});
Expand All @@ -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"');
Expand All @@ -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);
});
});

describe('enableDragDrop prop', () => {
it('should default enableDragDrop to false', () => {
// Default behavior should not enable drag and drop
Expand All @@ -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');
Expand All @@ -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
}))
Expand Down
81 changes: 38 additions & 43 deletions packages/module/src/ColumnManagementModal/ColumnManagementModal.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
import type { FunctionComponent } from 'react';
import { useState, useEffect } from 'react';
import {
Button,
Content,
ContentVariants,
ButtonVariant,
} from '@patternfly/react-core';
import { Button, Content, ContentVariants, ButtonVariant } from '@patternfly/react-core';
import { ModalProps, Modal, ModalVariant } from '@patternfly/react-core/deprecated';
import ListManager, { ListManagerItem } from '../ListManager/ListManager';

Expand All @@ -23,15 +17,16 @@ export interface ColumnManagementModalColumn {
}

/** extends ModalProps */
export interface ColumnManagementModalProps extends Omit<ModalProps, 'ref' | 'children'> {
export interface ColumnManagementModalProps<T extends ColumnManagementModalColumn = ColumnManagementModalColumn>
extends Omit<ModalProps, 'ref' | 'children'> {
/** Flag to show the modal */
isOpen?: boolean;
/** Invoked when modal visibility is changed */
onClose?: (event: KeyboardEvent | React.MouseEvent) => void;
/** Current column state */
appliedColumns: ColumnManagementModalColumn[];
appliedColumns: T[];
/** Invoked with new column state after save button is clicked */
applyColumns: (newColumns: ColumnManagementModalColumn[]) => void;
applyColumns: (newColumns: T[]) => void;
/* Modal description text */
description?: string;
/* Modal title text */
Expand All @@ -46,30 +41,32 @@ export interface ColumnManagementModalProps extends Omit<ModalProps, 'ref' | 'ch
resetToDefaultLabel?: string;
}

const ColumnManagementModal: FunctionComponent<ColumnManagementModalProps> = (
{ title = 'Manage columns',
description = 'Selected categories will be displayed in the table.',
isOpen = false,
onClose = () => undefined,
appliedColumns,
applyColumns,
ouiaId = 'ColumnManagementModal',
enableDragDrop = false,
onReset,
resetToDefaultLabel = 'Reset to default',
...props }: ColumnManagementModalProps) => {

const [ currentColumns, setCurrentColumns ] = useState(() =>
appliedColumns.map(column => ({ ...column, isShown: column.isShown ?? column.isShownByDefault }))
const ColumnManagementModal = <T extends ColumnManagementModalColumn = ColumnManagementModalColumn>({
title = 'Manage columns',
description = 'Selected categories will be displayed in the table.',
isOpen = false,
onClose = () => undefined,
appliedColumns,
applyColumns,
ouiaId = 'ColumnManagementModal',
enableDragDrop = false,
onReset,
resetToDefaultLabel = 'Reset to default',
...props
}: ColumnManagementModalProps<T>) => {
const [ currentColumns, setCurrentColumns ] = useState<T[]>(() =>
appliedColumns.map((column) => ({ ...column, isShown: column.isShown ?? column.isShownByDefault }))
);

// Sync with appliedColumns when they change
useEffect(() => {
setCurrentColumns(appliedColumns.map(column => ({ ...column, isShown: column.isShown ?? column.isShownByDefault })));
setCurrentColumns(
appliedColumns.map((column) => ({ ...column, isShown: column.isShown ?? column.isShownByDefault }))
);
}, [ appliedColumns ]);

// Convert ColumnManagementModalColumn to ListManagerItem
const listManagerItems: ListManagerItem[] = currentColumns.map(column => ({
const listManagerItems: ListManagerItem[] = currentColumns.map((column) => ({
key: column.key,
title: column.title,
isSelected: column.isShown,
Expand All @@ -79,16 +76,14 @@ const ColumnManagementModal: FunctionComponent<ColumnManagementModalProps> = (

const resetToDefault = () => {
// Reset both visibility and order to match the original appliedColumns
setCurrentColumns(appliedColumns.map(column => ({ ...column, isShown: column.isShownByDefault ?? false })));
setCurrentColumns(appliedColumns.map((column) => ({ ...column, isShown: column.isShownByDefault ?? false })));
onReset?.();
};

const updateColumns = (items: ListManagerItem[]) => {
const newColumns = currentColumns.map(column => {
const matchingItem = items.find(item => item.key === column.key);
return matchingItem
? { ...column, isShown: matchingItem.isSelected ?? column.isShownByDefault }
: column;
const newColumns = currentColumns.map((column) => {
const matchingItem = items.find((item) => item.key === column.key);
return matchingItem ? { ...column, isShown: matchingItem.isSelected ?? column.isShownByDefault } : column;
});
setCurrentColumns(newColumns);
};
Expand All @@ -103,8 +98,8 @@ const ColumnManagementModal: FunctionComponent<ColumnManagementModalProps> = (

const handleOrderChange = (items: ListManagerItem[]) => {
// Update the order of currentColumns based on the new order from ListManager
const newColumns = items.map(item => {
const originalColumn = currentColumns.find(col => col.key === item.key);
const newColumns = items.map((item) => {
const originalColumn = currentColumns.find((col) => col.key === item.key);
if (!originalColumn) {
throw new Error(`Column with key ${item.key} not found`);
}
Expand All @@ -114,13 +109,13 @@ const ColumnManagementModal: FunctionComponent<ColumnManagementModalProps> = (
};

const handleSave = (items: ListManagerItem[]) => {
const updatedColumns = items.map(item => ({
key: item.key,
title: item.title,
isShown: item.isSelected,
isShownByDefault: item.isShownByDefault,
isUntoggleable: item.isUntoggleable
}));
const updatedColumns = items.map((item) => {
const originalColumn = currentColumns.find((col) => col.key === item.key);
if (!originalColumn) {
throw new Error(`Column with key ${item.key} not found`);
}
return { ...originalColumn, isShown: item.isSelected };
});
applyColumns(updatedColumns);
onClose({} as KeyboardEvent);
};
Expand Down Expand Up @@ -158,6 +153,6 @@ const ColumnManagementModal: FunctionComponent<ColumnManagementModalProps> = (
/>
</Modal>
);
}
};

export default ColumnManagementModal;
Loading