Skip to content

Commit

Permalink
fix: Display all options for CollectionPreferences content display
Browse files Browse the repository at this point in the history
  • Loading branch information
youdz committed Oct 4, 2024
1 parent 35a8b43 commit db05485
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ describe('Content Display preference', () => {
testOption({ wrapper, option: options[i], index: i });
}
});

it('renders all options even if a partial list is provided in preferences', () => {
const wrapper = renderContentDisplay({ preferences: { contentDisplay: [{ id: 'id1', visible: true }] } });
expect(wrapper.findOptions()).toHaveLength(4);
});
});

describe('Content visibility', () => {
Expand Down Expand Up @@ -85,6 +90,12 @@ describe('Content Display preference', () => {
expect(toggleInput.getElement()).toBeChecked();
expect(toggleInput.getElement()).toBeDisabled();
});

it('allows toggling options that are not part of the current preferences', () => {
const wrapper = renderContentDisplay({ preferences: { contentDisplay: [{ id: 'id1', visible: true }] } });
wrapper.findOptionByIndex(3)!.findVisibilityToggle().findNativeInput().click();
expectVisibilityStatus({ wrapper, statuses: [true, false, true, false] });
});
});

describe('Content reordering', () => {
Expand Down Expand Up @@ -172,6 +183,32 @@ describe('Content Display preference', () => {
testOrder({ wrapper, order: [0, 1, 2, 3] });
await expectAnnouncement(wrapper, 'Reordering canceled');
});

it('moves item between options that are not part of the current preferences', async () => {
const wrapper = renderContentDisplay({ preferences: { contentDisplay: [{ id: 'id1', visible: true }] } });
const dragHandle = wrapper.findOptionByIndex(1)!.findDragHandle().getElement();
pressKey(dragHandle, 'Space');
await expectAnnouncement(wrapper, 'Picked up item at position 1 of 4');
pressKey(dragHandle, 'ArrowDown');
await expectAnnouncement(wrapper, 'Moving item to position 2 of 4');
pressKey(dragHandle, 'ArrowDown');
await expectAnnouncement(wrapper, 'Moving item to position 3 of 4');
pressKey(dragHandle, 'Space');
testOrder({ wrapper, order: [1, 2, 0, 3] });
await expectAnnouncement(wrapper, 'Item moved from position 1 to position 3 of 4');
});

it('moves an item not part of the current preferences above the ones that are', async () => {
const wrapper = renderContentDisplay({ preferences: { contentDisplay: [{ id: 'id1', visible: true }] } });
const dragHandle = wrapper.findOptionByIndex(2)!.findDragHandle().getElement();
pressKey(dragHandle, 'Space');
await expectAnnouncement(wrapper, 'Picked up item at position 2 of 4');
pressKey(dragHandle, 'ArrowUp');
await expectAnnouncement(wrapper, 'Moving item to position 1 of 4');
pressKey(dragHandle, 'Space');
testOrder({ wrapper, order: [1, 0, 2, 3] });
await expectAnnouncement(wrapper, 'Item moved from position 2 to position 1 of 4');
});
});

describe.each<boolean>([false, true])('Filtering (with i18n = %s)', withI18nProvider => {
Expand Down
20 changes: 20 additions & 0 deletions src/collection-preferences/content-display/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,24 @@ describe('getSortedOptions', () => {
},
]);
});

it('adds options not in the state at the end of the list', () => {
const options = [
{ id: 'a', label: 'a' },
{ id: 'b', label: 'b' },
{ id: 'c', label: 'c' },
{ id: 'd', label: 'd' },
];
const contentDisplay = [
{ id: 'c', visible: false },
{ id: 'b', visible: true },
];
const result = getSortedOptions({ options, contentDisplay });
expect(result).toEqual([
{ id: 'c', label: 'c', visible: false },
{ id: 'b', label: 'b', visible: true },
{ id: 'a', label: 'a', visible: false },
{ id: 'd', label: 'd', visible: false },
]);
});
});
35 changes: 23 additions & 12 deletions src/collection-preferences/content-display/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,20 +52,30 @@ export default function ContentDisplayPreference({
const i18n = useInternalI18n('collection-preferences');
const [columnFilteringText, setColumnFilteringText] = useState('');

const onToggle = (option: OptionWithVisibility) => {
onChange(value.map(item => (item.id === option.id ? { ...item, visible: !option.visible } : item)));
};

const titleId = `${idPrefix}-title`;
const descriptionId = `${idPrefix}-description`;

const sortedAndFilteredOptions = useMemo(
() =>
getSortedOptions({ options, contentDisplay: value }).filter(option =>
option.label.toLowerCase().trim().includes(columnFilteringText.toLowerCase().trim())
),
[columnFilteringText, options, value]
);
const [sortedOptions, sortedAndFilteredOptions] = useMemo(() => {
const sorted = getSortedOptions({ options, contentDisplay: value });
const filtered = sorted.filter(option =>
option.label.toLowerCase().trim().includes(columnFilteringText.toLowerCase().trim())
);
return [sorted, filtered];
}, [columnFilteringText, options, value]);

const addMissingOptionsInValue = () => {
// If the options prop includes options that are not in the value, we generate
// a new value with the missing options as non-visible added after the known ones.
if (value.length < sortedOptions.length) {
return sortedOptions.map(({ id, visible }) => ({ id, visible }));
}
return value;
};

const onToggle = (option: OptionWithVisibility) => {
const value = addMissingOptionsInValue();
onChange(value.map(item => (item.id === option.id ? { ...item, visible: !option.visible } : item)));
};

const { activeItem, collisionDetection, handleKeyDown, sensors, setActiveItem } = useDragAndDropReorder({
sortedOptions: sortedAndFilteredOptions,
Expand Down Expand Up @@ -96,7 +106,7 @@ export default function ContentDisplayPreference({
'contentDisplayPreference.liveAnnouncementDndDiscarded',
liveAnnouncementDndDiscarded
),
sortedOptions: value,
sortedOptions: sortedAndFilteredOptions,
});

const renderedDragHandleAriaDescription = i18n(
Expand Down Expand Up @@ -179,6 +189,7 @@ export default function ContentDisplayPreference({
const { active, over } = event;

if (over && active.id !== over.id) {
const value = addMissingOptionsInValue();
const oldIndex = value.findIndex(({ id }) => id === active.id);
const newIndex = value.findIndex(({ id }) => id === over.id);
onChange(arrayMove([...value], oldIndex, newIndex));
Expand Down
25 changes: 14 additions & 11 deletions src/collection-preferences/content-display/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { CollectionPreferencesProps } from '../interfaces';

export interface OptionWithVisibility extends CollectionPreferencesProps.ContentDisplayOption {
visible?: boolean;
visible: boolean;
}

export function getSortedOptions({
Expand All @@ -13,14 +13,17 @@ export function getSortedOptions({
options: ReadonlyArray<CollectionPreferencesProps.ContentDisplayOption>;
contentDisplay: ReadonlyArray<CollectionPreferencesProps.ContentDisplayItem>;
}): ReadonlyArray<OptionWithVisibility> {
const optionsById: Record<string, CollectionPreferencesProps.ContentDisplayOption> = options.reduce(
(currentValue, option) => ({ ...currentValue, [option.id]: option }),
{}
);
return contentDisplay
.map(({ id, visible }: CollectionPreferencesProps.ContentDisplayItem) => ({
...optionsById[id],
visible,
}))
.filter(Boolean);
// By using a Map, we are guaranteed to preserve insertion order on future iteration.
const optionsById = new Map<string, OptionWithVisibility>();
// We insert contentDisplay first so we respect the currently selected order
for (const { id, visible } of contentDisplay) {
// If an option is provided in contentDisplay and not options, we default the label to the id
optionsById.set(id, { id, label: id, visible });
}
// We merge options data, and insert any that were not in contentDisplay as non-visible
for (const option of options) {
const existing = optionsById.get(option.id);
optionsById.set(option.id, { ...option, visible: !!existing?.visible });
}
return Array.from(optionsById.values());
}

0 comments on commit db05485

Please sign in to comment.