Skip to content
Merged
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
@@ -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