Skip to content

Commit

Permalink
test(DualListSelectorTree): reenable integration tests + update demo app
Browse files Browse the repository at this point in the history
  • Loading branch information
adamviktora committed Oct 9, 2024
1 parent a596815 commit 7c7a81d
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,18 @@ describe('Dual List Selector Tree Demo Test', () => {
cy.get('.pf-v6-c-dual-list-selector__list').eq(1).find('li').should('have.length', 2);
});

xit('Verify add all filtered options works', () => {
it('Verify add all filtered options works', () => {
cy.get('.pf-v6-c-dual-list-selector__list').eq(0).find('li').should('have.length', 2);
cy.get('.pf-v6-c-dual-list-selector__tools-filter input').eq(0).type('Fru');
cy.get('.pf-v6-c-dual-list-selector__list').eq(0).find('li').should('have.length', 1);
cy.get('.pf-v6-c-dual-list-selector__list').eq(0).find('li').should('have.length', 6);
cy.get('.pf-v6-c-dual-list-selector__controls-item').eq(1).click();
cy.get('.pf-v6-c-dual-list-selector__list').eq(1).find('li').should('have.length', 3);
cy.get('.pf-v6-c-dual-list-selector__status-text').eq(0).should('have.text', '0 of 0 options selected');
cy.get('.pf-v6-c-empty-state').eq(0).should('exist');
cy.get('.pf-v6-c-dual-list-selector__list').eq(0).find('li').should('have.length', 3); // "Chosen" list is at index 0, because "Available" displays empty state instead
cy.get('.pf-v6-c-dual-list-selector__status-text').eq(1).should('have.text', '0 of 9 options selected');
cy.get('.pf-v6-c-dual-list-selector__tools-filter input').eq(0).type('{backspace}{backspace}{backspace}');
cy.get('.pf-v6-c-dual-list-selector__list').eq(0).find('li').should('have.length', 1);
cy.get('.pf-v6-c-dual-list-selector__status-text').eq(0).should('have.text', '0 of 2 options selected');
});

it('Verify chosen search works', () => {
Expand All @@ -49,16 +53,16 @@ describe('Dual List Selector Tree Demo Test', () => {
cy.get('.pf-v6-c-dual-list-selector__menu').eq(1).find('li').should('have.length', 1);
});

xit('Verify remove all filtered options works', () => {
cy.get('.pf-v6-c-dual-list-selector__list').eq(0).find('li').should('have.length', 0);
cy.get('.pf-v6-c-dual-list-selector__list').eq(1).find('li').should('have.length', 1);
it('Verify remove all filtered options works', () => {
cy.get('.pf-v6-c-dual-list-selector__menu').eq(0).should('be.empty');
cy.get('.pf-v6-c-dual-list-selector__list').eq(0).find('li').should('have.length', 1); // "Chosen" list is at index 0, because "Available" is empty
cy.get('.pf-v6-c-dual-list-selector__controls-item').eq(2).click();
cy.get('.pf-v6-c-dual-list-selector__list').eq(0).find('li').should('have.length', 1);
cy.get('.pf-v6-c-dual-list-selector__list').eq(1).find('li').should('have.length', 0);
cy.get('.pf-v6-c-empty-state').eq(0).should('exist');
cy.get('.pf-v6-c-dual-list-selector__tools-filter input').eq(1).type('{backspace}{backspace}{backspace}');
cy.get('.pf-v6-c-dual-list-selector__list').eq(1).find('li').should('have.length', 3);
cy.get('.pf-v6-c-dual-list-selector__controls-item').eq(2).click();
cy.get('.pf-v6-c-dual-list-selector__list').eq(0).find('li').should('have.length', 4);
cy.get('.pf-v6-c-dual-list-selector__list').eq(1).find('li').should('have.length', 0);
cy.get('.pf-v6-c-dual-list-selector__menu').eq(1).should('be.empty');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ const DualListSelectorComposableTree: React.FunctionComponent<ExampleProps> = ({
const [chosenLeafIds, setChosenLeafIds] = React.useState<string[]>(['beans', 'beef', 'chicken', 'tofu']);
const [chosenFilter, setChosenFilter] = React.useState<string>('');
const [availableFilter, setAvailableFilter] = React.useState<string>('');
let hiddenChosen: string[] = [];
let hiddenAvailable: string[] = [];

// helper function to build memoized lists
const buildTextById = (node: FoodNode): { [key: string]: string } => {
Expand Down Expand Up @@ -82,7 +80,7 @@ const DualListSelectorComposableTree: React.FunctionComponent<ExampleProps> = ({
};

// Builds a map of child leaf nodes by node id - memoized so that it only rebuilds the list if the data changes.
const { memoizedLeavesById, memoizedAllLeaves, memoizedNodeText } = React.useMemo(() => {
const { memoizedLeavesById, memoizedAllLeaves, memoizedNodeTexts } = React.useMemo(() => {
let leavesById: { [key: string]: string[] } = {};
let allLeaves: string[] = [];
let nodeTexts: { [key: string]: string } = {};
Expand All @@ -94,32 +92,49 @@ const DualListSelectorComposableTree: React.FunctionComponent<ExampleProps> = ({
return {
memoizedLeavesById: leavesById,
memoizedAllLeaves: allLeaves,
memoizedNodeText: nodeTexts
memoizedNodeTexts: nodeTexts
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [data]);

const matchesFilter = (value: string, filter: string) => value.toLowerCase().includes(filter.trim().toLowerCase());

const getVisibleLeafIds = (leafIds: string[], filter: string) => {
const filterMatchingNodeIds = Object.keys(memoizedLeavesById).filter((nodeId) =>
matchesFilter(memoizedNodeTexts[nodeId], filter)
);
const filterMatchingLeafIds = filterMatchingNodeIds.map((nodeId) => memoizedLeavesById[nodeId]).flat();
return leafIds.filter((leafId) => filterMatchingLeafIds.includes(leafId));
};

const availableLeafIds = memoizedAllLeaves.filter((leafId) => !chosenLeafIds.includes(leafId));
const visibleChosenLeafIds = getVisibleLeafIds(chosenLeafIds, chosenFilter);
const visibleAvailableLeafIds = getVisibleLeafIds(availableLeafIds, availableFilter);

const moveChecked = (toChosen: boolean) => {
const visibleCheckedChosenLeafIds = checkedLeafIds.filter((leafId) => visibleChosenLeafIds.includes(leafId));
const visibleCheckedAvailableLeafIds = checkedLeafIds.filter((leafId) => visibleAvailableLeafIds.includes(leafId));

setChosenLeafIds(
(prevChosenIds) =>
toChosen
? [...prevChosenIds, ...checkedLeafIds] // add checked ids to chosen list
: [...prevChosenIds.filter((x) => !checkedLeafIds.includes(x))] // remove checked ids from chosen list
? [...prevChosenIds, ...visibleCheckedAvailableLeafIds] // add visible checked ids to chosen list
: prevChosenIds.filter((x) => !visibleCheckedChosenLeafIds.includes(x)) // remove visible checked ids from chosen list
);

// uncheck checked ids that just moved
setCheckedLeafIds((prevChecked) =>
toChosen
? [...prevChecked.filter((x) => chosenLeafIds.includes(x))]
: [...prevChecked.filter((x) => !chosenLeafIds.includes(x))]
? prevChecked.filter((x) => !visibleCheckedAvailableLeafIds.includes(x))
: prevChecked.filter((x) => !visibleCheckedChosenLeafIds.includes(x))
);
};

const moveAll = (toChosen: boolean) => {
if (toChosen) {
setChosenLeafIds(memoizedAllLeaves);
setChosenLeafIds((prevChosenIds) => [...prevChosenIds, ...visibleAvailableLeafIds]);
} else {
setChosenLeafIds([]);
setChosenLeafIds((prevChosenIds) => prevChosenIds.filter((id) => !visibleChosenLeafIds.includes(id)));
}
};

Expand Down Expand Up @@ -149,15 +164,9 @@ const DualListSelectorComposableTree: React.FunctionComponent<ExampleProps> = ({
isChosen: boolean
) => {
const nodeIdsToCheck = memoizedLeavesById[node.id].filter((id) =>
isChosen
? chosenLeafIds.includes(id) && !hiddenChosen.includes(id)
: !chosenLeafIds.includes(id) && !hiddenAvailable.includes(id)
isChosen ? visibleChosenLeafIds.includes(id) : visibleAvailableLeafIds.includes(id)
);
if (isChosen) {
hiddenChosen = [];
} else {
hiddenAvailable = [];
}

setCheckedLeafIds((prevChecked) => {
const otherCheckedNodeNames = prevChecked.filter((id) => !nodeIdsToCheck.includes(id));
return !isChecked ? otherCheckedNodeNames : [...otherCheckedNodeNames, ...nodeIdsToCheck];
Expand Down Expand Up @@ -189,16 +198,15 @@ const DualListSelectorComposableTree: React.FunctionComponent<ExampleProps> = ({

const isChecked = isNodeChecked(node, isChosen);

const filterValue = (isChosen ? chosenFilter : availableFilter).toLowerCase().trim();
const filterValue = isChosen ? chosenFilter : availableFilter;
const descendentLeafIds = memoizedLeavesById[node.id];
const descendentsOnThisPane = isChosen
? descendentLeafIds.filter((id) => chosenLeafIds.includes(id))
: descendentLeafIds.filter((id) => !chosenLeafIds.includes(id));

const hasMatchingChildren =
filterValue && descendentsOnThisPane.some((id) => memoizedNodeText[id].toLowerCase().includes(filterValue));
const isFilterMatch =
filterValue && node.text.toLowerCase().includes(filterValue) && descendentsOnThisPane.length > 0;
filterValue && descendentsOnThisPane.some((id) => matchesFilter(memoizedNodeTexts[id], filterValue));
const isFilterMatch = filterValue && matchesFilter(node.text, filterValue) && descendentsOnThisPane.length > 0;

// A node is displayed if either of the following is true:
// - There is no filter value and this node or its descendents belong on this pane
Expand All @@ -209,14 +217,6 @@ const DualListSelectorComposableTree: React.FunctionComponent<ExampleProps> = ({
(hasParentMatch && descendentsOnThisPane.length > 0) ||
isFilterMatch;

if (!isDisplayed) {
if (isChosen) {
hiddenChosen.push(node.id);
} else {
hiddenAvailable.push(node.id);
}
}

return [
...(isDisplayed
? [
Expand All @@ -243,9 +243,9 @@ const DualListSelectorComposableTree: React.FunctionComponent<ExampleProps> = ({

const buildPane = (isChosen: boolean): React.ReactNode => {
const options: DualListSelectorTreeItemData[] = buildOptions(isChosen, data, false);
const numOptions = isChosen ? chosenLeafIds.length : memoizedAllLeaves.length - chosenLeafIds.length;
const numOptions = isChosen ? visibleChosenLeafIds.length : visibleAvailableLeafIds.length;
const numSelected = checkedLeafIds.filter((id) =>
isChosen ? chosenLeafIds.includes(id) : !chosenLeafIds.includes(id)
isChosen ? visibleChosenLeafIds.includes(id) : visibleAvailableLeafIds.includes(id)
).length;
const status = `${numSelected} of ${numOptions} options selected`;
const filterApplied = isChosen ? chosenFilter !== '' : availableFilter !== '';
Expand Down Expand Up @@ -286,7 +286,7 @@ const DualListSelectorComposableTree: React.FunctionComponent<ExampleProps> = ({
{buildPane(false)}
<DualListSelectorControlsWrapper>
<DualListSelectorControl
isDisabled={!checkedLeafIds.filter((x) => !chosenLeafIds.includes(x)).length}
isDisabled={!checkedLeafIds.filter((x) => visibleAvailableLeafIds.includes(x)).length}
onClick={() => moveChecked(true)}
aria-label="Add selected"
icon={<AngleRightIcon />}
Expand All @@ -305,7 +305,7 @@ const DualListSelectorComposableTree: React.FunctionComponent<ExampleProps> = ({
/>
<DualListSelectorControl
onClick={() => moveChecked(false)}
isDisabled={!checkedLeafIds.filter((x) => !!chosenLeafIds.includes(x)).length}
isDisabled={!checkedLeafIds.filter((x) => visibleChosenLeafIds.includes(x)).length}
aria-label="Remove selected"
icon={<AngleLeftIcon />}
/>
Expand Down

0 comments on commit 7c7a81d

Please sign in to comment.