Skip to content

Commit

Permalink
bugfix(react-tree): fix parent navigation after independency from id (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
bsunderhus authored Apr 20, 2023
1 parent 04c2e8a commit acbc5fc
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 138 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "bugfix: fix parent navigation after independency from id",
"packageName": "@fluentui/react-tree",
"email": "[email protected]",
"dependentChangeType": "patch"
}
231 changes: 101 additions & 130 deletions packages/react-components/react-tree/src/components/Tree/Tree.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,82 +18,50 @@ const mount = (element: JSX.Element) => {
mountBase(<FluentProvider theme={teamsLightTheme}>{element}</FluentProvider>);
};

const treeItems = (
<>
<TreeItem value="item1" data-testid="item1">
<TreeItemLayout>level 1, item 1</TreeItemLayout>
<Tree>
<TreeItem value="item1__item1" data-testid="item1__item1">
<TreeItemLayout>level 2, item 1</TreeItemLayout>
</TreeItem>
<TreeItem value="item1__item2" data-testid="item1__item2">
<TreeItemLayout>level 2, item 2</TreeItemLayout>
</TreeItem>
<TreeItem value="item1__item3" data-testid="item1__item3">
<TreeItemLayout>level 2, item 3</TreeItemLayout>
</TreeItem>
</Tree>
</TreeItem>
<TreeItem value="item2" data-testid="item2">
<TreeItemLayout>level 1, item 2</TreeItemLayout>
<Tree>
<TreeItem value="item2__item1" data-testid="item2__item1">
<TreeItemLayout>level 2, item 1</TreeItemLayout>
<Tree>
<TreeItem value="item2__item1__item1" data-testid="item2__item1__item1">
<TreeItemLayout>level 3, item 1</TreeItemLayout>
</TreeItem>
</Tree>
</TreeItem>
</Tree>
</TreeItem>
</>
);

const NestedTree: React.FC<TreeProps> = props => {
return (
<Tree id="tree" aria-label="Tree" {...props}>
{props.children ?? (
<>
<TreeItem id="item1">
<TreeItemLayout>level 1, item 1</TreeItemLayout>
<Tree>
<TreeItem id="item1__item1">
<TreeItemLayout>level 2, item 1</TreeItemLayout>
</TreeItem>
<TreeItem id="item1__item2">
<TreeItemLayout>level 2, item 2</TreeItemLayout>
</TreeItem>
<TreeItem id="item1__item3">
<TreeItemLayout>level 2, item 3</TreeItemLayout>
</TreeItem>
</Tree>
</TreeItem>
<TreeItem id="item2">
<TreeItemLayout>level 1, item 2</TreeItemLayout>
<Tree>
<TreeItem id="item2__item1">
<TreeItemLayout>level 2, item 1</TreeItemLayout>
<Tree>
<TreeItem id="item2__item1__item1">
<TreeItemLayout>level 3, item 1</TreeItemLayout>
</TreeItem>
</Tree>
</TreeItem>
</Tree>
</TreeItem>
</>
)}
{props.children ?? treeItems}
</Tree>
);
};
NestedTree.displayName = 'NestedTree';

const FlatTree: React.FC<TreeProps> = (props: TreeProps) => {
const flatTree = useFlatTree_unstable(
flattenTreeFromElement(
props.children ? (
<>{props.children}</>
) : (
<>
<TreeItem id="item1">
<TreeItemLayout>level 1, item 1</TreeItemLayout>
<Tree>
<TreeItem id="item1__item1">
<TreeItemLayout>level 2, item 1</TreeItemLayout>
</TreeItem>
<TreeItem id="item1__item2">
<TreeItemLayout>level 2, item 2</TreeItemLayout>
</TreeItem>
<TreeItem id="item1__item3">
<TreeItemLayout>level 2, item 3</TreeItemLayout>
</TreeItem>
</Tree>
</TreeItem>
<TreeItem id="item2">
<TreeItemLayout>level 1, item 2</TreeItemLayout>
<Tree>
<TreeItem id="item2__item1">
<TreeItemLayout>level 2, item 1</TreeItemLayout>
<Tree>
<TreeItem id="item2__item1__item1">
<TreeItemLayout>level 3, item 1</TreeItemLayout>
</TreeItem>
</Tree>
</TreeItem>
</Tree>
</TreeItem>
</>
),
),
flattenTreeFromElement(props.children ? <>{props.children}</> : treeItems),
props,
);
return (
Expand All @@ -110,30 +78,30 @@ for (const TreeTest of [NestedTree, FlatTree]) {
describe(TreeTest.displayName!, () => {
it('should have all but first level items hidden', () => {
mount(<TreeTest />);
cy.get('#item1__item1').should('not.exist');
cy.get('#item1__item2').should('not.exist');
cy.get('#item1__item3').should('not.exist');
cy.get('#item2__item1').should('not.exist');
cy.get('#item2__item1__item1').should('not.exist');
cy.get('[data-testid="item1__item1"]').should('not.exist');
cy.get('[data-testid="item1__item2"]').should('not.exist');
cy.get('[data-testid="item1__item3"]').should('not.exist');
cy.get('[data-testid="item2__item1"]').should('not.exist');
cy.get('[data-testid="item2__item1__item1"]').should('not.exist');
});

it('should have all items visible', () => {
mount(<TreeTest defaultOpenItems={['item1', 'item2', 'item2__item1']} />);
cy.get('#item1__item1').should('exist');
cy.get('#item1__item2').should('exist');
cy.get('#item1__item3').should('exist');
cy.get('#item2__item1').should('exist');
cy.get('#item2__item1__item1').should('exist');
cy.get('[data-testid="item1__item1"]').should('exist');
cy.get('[data-testid="item1__item2"]').should('exist');
cy.get('[data-testid="item1__item3"]').should('exist');
cy.get('[data-testid="item2__item1"]').should('exist');
cy.get('[data-testid="item2__item1__item1"]').should('exist');
});

describe('Mouse interactions', () => {
it('should expand/collapse item on layout click', () => {
mount(<TreeTest />);
cy.get('#item1__item1').should('not.exist');
cy.get(`#item1 .${treeItemLayoutClassNames.root}`).realClick();
cy.get('#item1__item1').should('exist');
cy.get(`#item1 .${treeItemLayoutClassNames.root}`).realClick();
cy.get('#item1__item1').should('not.exist');
cy.get('[data-testid="item1__item1"]').should('not.exist');
cy.get(`[data-testid="item1"] .${treeItemLayoutClassNames.root}`).realClick();
cy.get('[data-testid="item1__item1"]').should('exist');
cy.get(`[data-testid="item1"] .${treeItemLayoutClassNames.root}`).realClick();
cy.get('[data-testid="item1__item1"]').should('not.exist');
});
it('should expand/collapse item on expandIcon click only', () => {
mount(
Expand All @@ -145,14 +113,14 @@ for (const TreeTest of [NestedTree, FlatTree]) {
}}
/>,
);
cy.get('#item1__item1').should('not.exist');
cy.get(`#item1 .${treeItemClassNames.expandIcon}`).realClick();
cy.get('#item1__item1').should('exist');
cy.get(`#item1 .${treeItemClassNames.expandIcon}`).realClick();
cy.get('#item1__item1').should('not.exist');
cy.get('[data-testid="item1__item1"]').should('not.exist');
cy.get(`[data-testid="item1"] .${treeItemClassNames.expandIcon}`).realClick();
cy.get('[data-testid="item1__item1"]').should('exist');
cy.get(`[data-testid="item1"] .${treeItemClassNames.expandIcon}`).realClick();
cy.get('[data-testid="item1__item1"]').should('not.exist');

cy.get(`#item1 .${treeItemLayoutClassNames.root}`).realClick();
cy.get('#item1__item1').should('not.exist');
cy.get(`[data-testid="item1"] .${treeItemLayoutClassNames.root}`).realClick();
cy.get('[data-testid="item1__item1"]').should('not.exist');
});
it('should not expand/collapse item on actions click', () => {
mount(
Expand All @@ -163,46 +131,47 @@ for (const TreeTest of [NestedTree, FlatTree]) {
<Button id="action">action</Button>
</>
}
id="item1"
value="item1"
data-testid="item1"
>
<TreeItemLayout>level 1, item 1</TreeItemLayout>
<Tree>
<TreeItem id="item1__item1">
<TreeItem value="item1__item1" data-testid="item1__item1">
<TreeItemLayout>level 2, item 1</TreeItemLayout>
</TreeItem>
</Tree>
</TreeItem>
</TreeTest>,
);
cy.get('#item1__item1').should('not.exist');
cy.get('#item1').focus();
cy.get('[data-testid="item1__item1"]').should('not.exist');
cy.get('[data-testid="item1"]').focus();
cy.get(`#action`).realClick();
cy.get('#item1__item1').should('not.exist');
cy.get('[data-testid="item1__item1"]').should('not.exist');
});
});
describe('Keyboard interactions', () => {
it('should expand/collapse item on Enter key', () => {
mount(<TreeTest />);
cy.get('#item1').focus();
cy.get('#item1__item1').should('not.exist');
cy.get(`#item1 .${treeItemLayoutClassNames.root}`).realPress('{enter}');
cy.get('#item1__item1').should('exist');
cy.get('[data-testid="item1"]').focus();
cy.get('[data-testid="item1__item1"]').should('not.exist');
cy.get(`[data-testid="item1"] .${treeItemLayoutClassNames.root}`).realPress('{enter}');
cy.get('[data-testid="item1__item1"]').should('exist');
});
it('should expand item on Right key', () => {
mount(<TreeTest />);
cy.get('#item1').focus();
cy.get('#item1__item1').should('not.exist');
cy.get(`#item1 .${treeItemLayoutClassNames.root}`).realPress('{rightarrow}');
cy.get('#item1__item1').should('exist');
cy.get('[data-testid="item1"]').focus();
cy.get('[data-testid="item1__item1"]').should('not.exist');
cy.get(`[data-testid="item1"] .${treeItemLayoutClassNames.root}`).realPress('{rightarrow}');
cy.get('[data-testid="item1__item1"]').should('exist');
});
it('should collapse item on Left key', () => {
mount(<TreeTest />);
cy.get('#item1').focus();
cy.get('#item1__item1').should('not.exist');
cy.get(`#item1 .${treeItemLayoutClassNames.root}`).realPress('{rightarrow}');
cy.get('#item1__item1').should('exist');
cy.get(`#item1 .${treeItemLayoutClassNames.root}`).realPress('{leftarrow}');
cy.get('#item1__item1').should('not.exist');
cy.get('[data-testid="item1"]').focus();
cy.get('[data-testid="item1__item1"]').should('not.exist');
cy.get(`[data-testid="item1"] .${treeItemLayoutClassNames.root}`).realPress('{rightarrow}');
cy.get('[data-testid="item1__item1"]').should('exist');
cy.get(`[data-testid="item1"] .${treeItemLayoutClassNames.root}`).realPress('{leftarrow}');
cy.get('[data-testid="item1__item1"]').should('not.exist');
});
it('should focus on actions when pressing tab key', () => {
mount(
Expand All @@ -213,11 +182,12 @@ for (const TreeTest of [NestedTree, FlatTree]) {
<Button id="action">action</Button>
</>
}
id="item1"
value="item1"
data-testid="item1"
>
<TreeItemLayout>level 1, item 1</TreeItemLayout>
<Tree>
<TreeItem id="item1__item1">
<TreeItem value="item1__item1" data-testid="item1__item1">
<TreeItemLayout>level 2, item 1</TreeItemLayout>
</TreeItem>
</Tree>
Expand All @@ -226,7 +196,7 @@ for (const TreeTest of [NestedTree, FlatTree]) {
);
cy.focused().should('not.exist');
cy.document().realPress('Tab');
cy.get('#item1').should('be.focused');
cy.get('[data-testid="item1"]').should('be.focused');
cy.document().realPress('Tab');
cy.get('#action').should('be.focused');
});
Expand All @@ -239,11 +209,12 @@ for (const TreeTest of [NestedTree, FlatTree]) {
<Button id="action">action</Button>
</>
}
id="item1"
value="item1"
data-testid="item1"
>
<TreeItemLayout>level 1, item 1</TreeItemLayout>
<Tree>
<TreeItem id="item1__item1">
<TreeItem value="item1__item1" data-testid="item1__item1">
<TreeItemLayout>level 2, item 1</TreeItemLayout>
</TreeItem>
</Tree>
Expand All @@ -252,53 +223,53 @@ for (const TreeTest of [NestedTree, FlatTree]) {
);
cy.focused().should('not.exist');
cy.document().realPress('Tab');
cy.get('#item1').should('be.focused');
cy.get('[data-testid="item1"]').should('be.focused');
cy.document().realPress('Tab');
cy.get('#action').should('be.focused').realPress('{enter}');
cy.get('#item1__item1').should('not.exist');
cy.get('[data-testid="item1__item1"]').should('not.exist');
cy.get('#action').should('be.focused').realPress('Space');
cy.get('#item1__item1').should('not.exist');
cy.get('[data-testid="item1__item1"]').should('not.exist');
});
it('should focus on first item when pressing tab key', () => {
mount(<TreeTest />);
cy.focused().should('not.exist');
cy.document().realPress('Tab');
cy.get('#item1').should('be.focused');
cy.get('[data-testid="item1"]').should('be.focused');
});
it('should focus out of tree when pressing tab key inside tree.', () => {
mount(<TreeTest />);
cy.focused().should('not.exist');
cy.document().realPress('Tab');
cy.get('#item1').should('be.focused');
cy.get('[data-testid="item1"]').should('be.focused');
cy.focused().realPress('Tab');
cy.focused().should('not.exist');
});
describe('Navigation', () => {
it('should move with Up/Down keys', () => {
mount(<TreeTest />);
cy.get('#item1').focus().realPress('{downarrow}');
cy.get('#item2').should('be.focused');
cy.get('[data-testid="item1"]').focus().realPress('{downarrow}');
cy.get('[data-testid="item2"]').should('be.focused');
cy.focused().realPress('Tab').should('not.exist');
});
it('should move with Left/Right keys', () => {
mount(<TreeTest defaultOpenItems={['item2', 'item2__item1']} />);
cy.get('#item1').focus().realPress('{downarrow}');
cy.get('#item2').should('be.focused').realPress('{rightarrow}');
cy.get('#item2__item1').should('be.focused').realPress('{rightarrow}');
cy.get('#item2__item1__item1').should('be.focused').realPress('{leftarrow}');
cy.get('#item2__item1').should('be.focused').realPress('{leftarrow}').realPress('{leftarrow}');
cy.get('#item2').should('be.focused');
cy.get('[data-testid="item1"]').focus().realPress('{downarrow}');
cy.get('[data-testid="item2"]').should('be.focused').realPress('{rightarrow}');
cy.get('[data-testid="item2__item1"]').should('be.focused').realPress('{rightarrow}');
cy.get('[data-testid="item2__item1__item1"]').should('be.focused').realPress('{leftarrow}');
cy.get('[data-testid="item2__item1"]').should('be.focused').realPress('{leftarrow}').realPress('{leftarrow}');
cy.get('[data-testid="item2"]').should('be.focused');
});
it('should move to last item with End key', () => {
mount(<TreeTest defaultOpenItems={['item1', 'item2', 'item2__item1']} />);
cy.get('#item1').focus().realPress('{end}');
cy.get('#item2__item1__item1').should('be.focused');
cy.get('[data-testid="item1"]').focus().realPress('{end}');
cy.get('[data-testid="item2__item1__item1"]').should('be.focused');
});
it('should move to first item with Home key', () => {
mount(<TreeTest defaultOpenItems={['item1', 'item2', 'item2__item1']} />);
cy.get('#item1').focus().realPress('{end}');
cy.get('#item2__item1__item1').should('be.focused').realPress('{home}');
cy.get('#item1').should('be.focused');
cy.get('[data-testid="item1"]').focus().realPress('{end}');
cy.get('[data-testid="item2__item1__item1"]').should('be.focused').realPress('{home}');
cy.get('[data-testid="item1"]').should('be.focused');
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export type MutableFlatTreeItem<Value = string> = {
index: number;
value: Value;
level: number;
ref: React.RefObject<HTMLDivElement>;
getTreeItemProps(): Required<
Pick<TreeItemProps<Value>, 'value' | 'aria-setsize' | 'aria-level' | 'aria-posinset' | 'leaf'>
> &
Expand Down
Loading

0 comments on commit acbc5fc

Please sign in to comment.