diff --git a/packages/x-tree-view/src/TreeItem/TreeItem.test.tsx b/packages/x-tree-view/src/TreeItem/TreeItem.test.tsx index d811edeb77b6..5ea5973c0010 100644 --- a/packages/x-tree-view/src/TreeItem/TreeItem.test.tsx +++ b/packages/x-tree-view/src/TreeItem/TreeItem.test.tsx @@ -201,497 +201,6 @@ describe('', () => { expect(getByRole('group')).to.contain(getByText('test2')); }); - describe('Navigation', () => { - describe('right arrow interaction', () => { - it('should open the item and not move the focus if focus is on a closed item', () => { - const { getByTestId } = render( - - - - - , - ); - - expect(getByTestId('one')).to.have.attribute('aria-expanded', 'false'); - - act(() => { - getByTestId('one').focus(); - }); - fireEvent.keyDown(getByTestId('one'), { key: 'ArrowRight' }); - - expect(getByTestId('one')).to.have.attribute('aria-expanded', 'true'); - expect(getByTestId('one')).toHaveFocus(); - }); - - it('should move focus to the first child if focus is on an open item', () => { - const { getByTestId } = render( - - - - - , - ); - - expect(getByTestId('one')).to.have.attribute('aria-expanded', 'true'); - - act(() => { - getByTestId('one').focus(); - }); - fireEvent.keyDown(getByTestId('one'), { key: 'ArrowRight' }); - - expect(getByTestId('two')).toHaveFocus(); - }); - - it('should do nothing if focus is on an end item', () => { - const { getByTestId } = render( - - - - - , - ); - - act(() => { - getByTestId('two').focus(); - }); - - expect(getByTestId('two')).toHaveFocus(); - fireEvent.keyDown(getByTestId('two'), { key: 'ArrowRight' }); - - expect(getByTestId('two')).toHaveFocus(); - }); - }); - - describe('left arrow interaction', () => { - it('should close the item if focus is on an open item', () => { - const { getByTestId, getByText } = render( - - - - - , - ); - - fireEvent.click(getByText('one')); - act(() => { - getByTestId('one').focus(); - }); - - expect(getByTestId('one')).to.have.attribute('aria-expanded', 'true'); - - fireEvent.keyDown(getByTestId('one'), { key: 'ArrowLeft' }); - - expect(getByTestId('one')).to.have.attribute('aria-expanded', 'false'); - expect(getByTestId('one')).toHaveFocus(); - }); - - it("should move focus to the item's parent item if focus is on a child item that is an end item", () => { - const { getByTestId } = render( - - - - - , - ); - - expect(getByTestId('one')).to.have.attribute('aria-expanded', 'true'); - - act(() => { - getByTestId('two').focus(); - }); - - expect(getByTestId('two')).toHaveFocus(); - fireEvent.keyDown(getByTestId('two'), { key: 'ArrowLeft' }); - - expect(getByTestId('one')).toHaveFocus(); - expect(getByTestId('one')).to.have.attribute('aria-expanded', 'true'); - }); - - it("should move focus to the item's parent item if focus is on a child item that is closed", () => { - const { getByTestId } = render( - - - - - - - , - ); - - expect(getByTestId('one')).to.have.attribute('aria-expanded', 'true'); - - act(() => { - getByTestId('two').focus(); - }); - - expect(getByTestId('two')).toHaveFocus(); - - fireEvent.keyDown(getByTestId('two'), { key: 'ArrowLeft' }); - - expect(getByTestId('one')).toHaveFocus(); - expect(getByTestId('one')).to.have.attribute('aria-expanded', 'true'); - }); - - it('should do nothing if focus is on a root item that is closed', () => { - const { getByTestId } = render( - - - - - , - ); - - act(() => { - getByTestId('one').focus(); - }); - - expect(getByTestId('one')).to.have.attribute('aria-expanded', 'false'); - fireEvent.keyDown(getByTestId('one'), { key: 'ArrowLeft' }); - expect(getByTestId('one')).toHaveFocus(); - }); - - it('should do nothing if focus is on a root item that is an end item', () => { - const { getByTestId } = render( - - - , - ); - - act(() => { - getByTestId('one').focus(); - }); - fireEvent.keyDown(getByTestId('one'), { key: 'ArrowLeft' }); - - expect(getByTestId('one')).toHaveFocus(); - }); - }); - - describe('down arrow interaction', () => { - it('moves focus to a sibling item', () => { - const { getByTestId } = render( - - - - , - ); - - act(() => { - getByTestId('one').focus(); - }); - fireEvent.keyDown(getByTestId('one'), { key: 'ArrowDown' }); - - expect(getByTestId('two')).toHaveFocus(); - }); - - it('moves focus to a child item', () => { - const { getByTestId } = render( - - - - - , - ); - - expect(getByTestId('one')).to.have.attribute('aria-expanded', 'true'); - - act(() => { - getByTestId('one').focus(); - }); - fireEvent.keyDown(getByTestId('one'), { key: 'ArrowDown' }); - - expect(getByTestId('two')).toHaveFocus(); - }); - - it('moves focus to a child item works with a dynamic tree', () => { - function TestComponent() { - const [hide, setState] = React.useState(false); - - return ( - - - - {!hide && ( - - - - )} - - - - ); - } - - const { queryByTestId, getByTestId, getByText } = render(); - - expect(getByTestId('one')).not.to.equal(null); - fireEvent.click(getByText('Toggle Hide')); - expect(queryByTestId('one')).to.equal(null); - fireEvent.click(getByText('Toggle Hide')); - expect(getByTestId('one')).not.to.equal(null); - - act(() => { - getByTestId('one').focus(); - }); - fireEvent.keyDown(getByTestId('one'), { key: 'ArrowDown' }); - - expect(getByTestId('two')).toHaveFocus(); - }); - - it("moves focus to a parent's sibling", () => { - const { getByTestId } = render( - - - - - - , - ); - - expect(getByTestId('one')).to.have.attribute('aria-expanded', 'true'); - - act(() => { - getByTestId('two').focus(); - }); - - expect(getByTestId('two')).toHaveFocus(); - - fireEvent.keyDown(getByTestId('two'), { key: 'ArrowDown' }); - - expect(getByTestId('three')).toHaveFocus(); - }); - }); - - describe('up arrow interaction', () => { - it('moves focus to a sibling item', () => { - const { getByTestId } = render( - - - - , - ); - - act(() => { - getByTestId('two').focus(); - }); - - expect(getByTestId('two')).toHaveFocus(); - - fireEvent.keyDown(getByTestId('two'), { key: 'ArrowUp' }); - - expect(getByTestId('one')).toHaveFocus(); - }); - - it('moves focus to a parent', () => { - const { getByTestId } = render( - - - - - , - ); - - expect(getByTestId('one')).to.have.attribute('aria-expanded', 'true'); - - act(() => { - getByTestId('two').focus(); - }); - - expect(getByTestId('two')).toHaveFocus(); - - fireEvent.keyDown(getByTestId('two'), { key: 'ArrowUp' }); - - expect(getByTestId('one')).toHaveFocus(); - }); - - it("moves focus to a sibling's child", () => { - const { getByTestId } = render( - - - - - - , - ); - - expect(getByTestId('one')).to.have.attribute('aria-expanded', 'true'); - - act(() => { - getByTestId('three').focus(); - }); - - expect(getByTestId('three')).toHaveFocus(); - - fireEvent.keyDown(getByTestId('three'), { key: 'ArrowUp' }); - - expect(getByTestId('two')).toHaveFocus(); - }); - }); - - describe('home key interaction', () => { - it('moves focus to the first item in the tree', () => { - const { getByTestId } = render( - - - - - - , - ); - - act(() => { - getByTestId('four').focus(); - }); - - expect(getByTestId('four')).toHaveFocus(); - - fireEvent.keyDown(getByTestId('four'), { key: 'Home' }); - - expect(getByTestId('one')).toHaveFocus(); - }); - }); - - describe('end key interaction', () => { - it('moves focus to the last item in the tree without expanded items', () => { - const { getByTestId } = render( - - - - - - , - ); - - act(() => { - getByTestId('one').focus(); - }); - - expect(getByTestId('one')).toHaveFocus(); - - fireEvent.keyDown(getByTestId('one'), { key: 'End' }); - - expect(getByTestId('four')).toHaveFocus(); - }); - - it('moves focus to the last item in the tree with expanded items', () => { - const { getByTestId } = render( - - - - - - - - - - , - ); - - act(() => { - getByTestId('one').focus(); - }); - - expect(getByTestId('one')).toHaveFocus(); - - fireEvent.keyDown(getByTestId('one'), { key: 'End' }); - - expect(getByTestId('six')).toHaveFocus(); - }); - }); - - describe('asterisk key interaction', () => { - it('expands all siblings that are at the same level as the current item', () => { - const onExpandedItemsChange = spy(); - - const { getByTestId } = render( - - - - - - - - - - - - - - , - ); - - act(() => { - getByTestId('one').focus(); - }); - - expect(getByTestId('one')).to.have.attribute('aria-expanded', 'false'); - expect(getByTestId('three')).to.have.attribute('aria-expanded', 'false'); - expect(getByTestId('five')).to.have.attribute('aria-expanded', 'false'); - - fireEvent.keyDown(getByTestId('one'), { key: '*' }); - - expect(onExpandedItemsChange.args[0][1]).to.have.length(3); - - expect(getByTestId('one')).to.have.attribute('aria-expanded', 'true'); - expect(getByTestId('three')).to.have.attribute('aria-expanded', 'true'); - expect(getByTestId('five')).to.have.attribute('aria-expanded', 'true'); - expect(getByTestId('six')).to.have.attribute('aria-expanded', 'false'); - expect(getByTestId('eight')).not.to.have.attribute('aria-expanded'); - }); - }); - }); - - describe('Expansion', () => { - describe('enter key interaction', () => { - it('expands an item with children', () => { - const { getByTestId } = render( - - - - - , - ); - - act(() => { - getByTestId('one').focus(); - }); - - expect(getByTestId('one')).to.have.attribute('aria-expanded', 'false'); - - fireEvent.keyDown(getByTestId('one'), { key: 'Enter' }); - - expect(getByTestId('one')).to.have.attribute('aria-expanded', 'true'); - }); - - it('collapses an item with children', () => { - const { getByTestId } = render( - - - - - , - ); - - act(() => { - getByTestId('one').focus(); - }); - - expect(getByTestId('one')).to.have.attribute('aria-expanded', 'false'); - - fireEvent.keyDown(getByTestId('one'), { key: 'Enter' }); - expect(getByTestId('one')).to.have.attribute('aria-expanded', 'true'); - - fireEvent.keyDown(getByTestId('one'), { key: 'Enter' }); - expect(getByTestId('one')).to.have.attribute('aria-expanded', 'false'); - }); - }); - }); - describe('Single Selection', () => { describe('keyboard', () => { it('should select an item when space is pressed', () => { diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.test.tsx b/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.test.tsx index 5b5c7a4990f5..6cf0805ab397 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.test.tsx +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.test.tsx @@ -3,206 +3,553 @@ import { expect } from 'chai'; import { act, fireEvent } from '@mui-internal/test-utils'; import { describeTreeView } from 'test/utils/tree-view/describeTreeView'; import { + UseTreeViewExpansionSignature, UseTreeViewItemsSignature, UseTreeViewKeyboardNavigationSignature, } from '@mui/x-tree-view/internals'; -describeTreeView<[UseTreeViewKeyboardNavigationSignature, UseTreeViewItemsSignature]>( - 'useTreeViewKeyboardNavigation', - ({ render, treeViewComponent }) => { - describe('Type-ahead', () => { - it('should move the focus to the next item with a name that starts with the typed character', () => { - const { getFocusedItemId, getItemRoot } = render({ - items: [ - { id: '1', label: 'one' }, - { id: '2', label: 'two' }, - { id: '3', label: 'three' }, - { id: '4', label: 'four' }, - ], +describeTreeView< + [UseTreeViewKeyboardNavigationSignature, UseTreeViewItemsSignature, UseTreeViewExpansionSignature] +>('useTreeViewKeyboardNavigation', ({ render, treeViewComponent }) => { + describe('Navigation (focus and expansion)', () => { + describe('key: ArrowDown', () => { + it('should move the focus to a sibling item', () => { + const response = render({ + items: [{ id: '1' }, { id: '2' }], }); act(() => { - getItemRoot('1').focus(); + response.getItemRoot('1').focus(); }); - expect(getFocusedItemId()).to.equal('1'); - - fireEvent.keyDown(getItemRoot('1'), { key: 't' }); - expect(getFocusedItemId()).to.equal('2'); + fireEvent.keyDown(response.getItemRoot('1'), { key: 'ArrowDown' }); + expect(response.getFocusedItemId()).to.equal('2'); + }); - fireEvent.keyDown(getItemRoot('2'), { key: 'f' }); - expect(getFocusedItemId()).to.equal('4'); + it('should move the focus to a child item', () => { + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }], + defaultExpandedItems: ['1'], + }); - fireEvent.keyDown(getItemRoot('4'), { key: 'o' }); - expect(getFocusedItemId()).to.equal('1'); + act(() => { + response.getItemRoot('1').focus(); + }); + fireEvent.keyDown(response.getItemRoot('1'), { key: 'ArrowDown' }); + expect(response.getFocusedItemId()).to.equal('1.1'); }); - it('should move to the next item in the displayed order when typing the same starting character', () => { - const { getFocusedItemId, getItemRoot } = render({ - items: [{ id: 'A1' }, { id: 'B1' }, { id: 'A2' }, { id: 'B3' }, { id: 'B2' }], + it('should move the focus to a child item with a dynamic tree', () => { + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], + defaultExpandedItems: ['1'], }); + response.setItems([{ id: '2' }]); + response.setItems([{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }]); act(() => { - getItemRoot('A1').focus(); + response.getItemRoot('1').focus(); }); - expect(getFocusedItemId()).to.equal('A1'); + fireEvent.keyDown(response.getItemRoot('1'), { key: 'ArrowDown' }); + expect(response.getFocusedItemId()).to.equal('1.1'); + }); - fireEvent.keyDown(getItemRoot('A1'), { key: 'b' }); - expect(getFocusedItemId()).to.equal('B1'); + it("should move the focus to a parent's sibling", () => { + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }, { id: '2' }] }], + defaultExpandedItems: ['1'], + }); - fireEvent.keyDown(getItemRoot('B1'), { key: 'b' }); - expect(getFocusedItemId()).to.equal('B3'); + act(() => { + response.getItemRoot('1.1').focus(); + }); + fireEvent.keyDown(response.getItemRoot('1.1'), { key: 'ArrowDown' }); + expect(response.getFocusedItemId()).to.equal('2'); + }); + }); - fireEvent.keyDown(getItemRoot('B3'), { key: 'b' }); - expect(getFocusedItemId()).to.equal('B2'); + describe('key: ArrowUp', () => { + it('should move the focus to a sibling item', () => { + const response = render({ + items: [{ id: '1' }, { id: '2' }], + }); - fireEvent.keyDown(getItemRoot('B2'), { key: 'b' }); - expect(getFocusedItemId()).to.equal('B1'); + act(() => { + response.getItemRoot('2').focus(); + }); + fireEvent.keyDown(response.getItemRoot('2'), { key: 'ArrowUp' }); + expect(response.getFocusedItemId()).to.equal('1'); }); - it('should work with capitalized label', () => { - const { getFocusedItemId, getItemRoot } = render({ - items: [ - { id: '1', label: 'One' }, - { id: '2', label: 'Two' }, - { id: '3', label: 'Three' }, - { id: '4', label: 'Four' }, - ], + it('should move the focus to a parent', () => { + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }], + defaultExpandedItems: ['1'], }); act(() => { - getItemRoot('1').focus(); + response.getItemRoot('1.1').focus(); }); - expect(getFocusedItemId()).to.equal('1'); + fireEvent.keyDown(response.getItemRoot('1.1'), { key: 'ArrowUp' }); + expect(response.getFocusedItemId()).to.equal('1'); + }); - fireEvent.keyDown(getItemRoot('1'), { key: 't' }); - expect(getFocusedItemId()).to.equal('2'); + it("should move the focus to a sibling's child", () => { + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], + defaultExpandedItems: ['1'], + }); - fireEvent.keyDown(getItemRoot('2'), { key: 'f' }); - expect(getFocusedItemId()).to.equal('4'); + act(() => { + response.getItemRoot('2').focus(); + }); + fireEvent.keyDown(response.getItemRoot('2'), { key: 'ArrowUp' }); + expect(response.getFocusedItemId()).to.equal('1.1'); + }); + }); - fireEvent.keyDown(getItemRoot('4'), { key: 'o' }); - expect(getFocusedItemId()).to.equal('1'); + describe('key: ArrowRight', () => { + it('should open the item and not move the focus if the focus is on a closed item', () => { + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }], + }); + + act(() => { + response.getItemRoot('1').focus(); + }); + fireEvent.keyDown(response.getItemRoot('1'), { key: 'ArrowRight' }); + expect(response.isItemExpanded('1')).to.equal(true); + expect(response.getFocusedItemId()).to.equal('1'); }); - it('should work with ReactElement label', function test() { - // Only the SimpleTreeView can have React Element labels. - if (treeViewComponent !== 'SimpleTreeView') { - this.skip(); - } + it('should move the focus to the first child if the focus is on an open item', () => { + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }], + defaultExpandedItems: ['1'], + }); - const { getFocusedItemId, getItemRoot } = render({ - items: [ - { id: '1', label: one }, - { id: '2', label: two }, - { id: '3', label: three }, - { id: '4', label: four }, - ], + act(() => { + response.getItemRoot('1').focus(); + }); + fireEvent.keyDown(response.getItemRoot('1'), { key: 'ArrowRight' }); + expect(response.getFocusedItemId()).to.equal('1.1'); + }); + + it('should do nothing if the focus is on a leaf', () => { + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }], + defaultExpandedItems: ['1'], }); act(() => { - getItemRoot('1').focus(); + response.getItemRoot('1.1').focus(); }); - expect(getFocusedItemId()).to.equal('1'); + fireEvent.keyDown(response.getItemRoot('1.1'), { key: 'ArrowRight' }); + expect(response.getFocusedItemId()).to.equal('1.1'); + }); + }); - fireEvent.keyDown(getItemRoot('1'), { key: 't' }); - expect(getFocusedItemId()).to.equal('2'); + describe('key: ArrowLeft', () => { + it('should close the item if the focus is on an open item', () => { + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }], + defaultExpandedItems: ['1'], + }); - fireEvent.keyDown(getItemRoot('2'), { key: 'f' }); - expect(getFocusedItemId()).to.equal('4'); + act(() => { + response.getItemRoot('1').focus(); + }); + fireEvent.keyDown(response.getItemRoot('1'), { key: 'ArrowLeft' }); + expect(response.isItemExpanded('1')).to.equal(false); + }); - fireEvent.keyDown(getItemRoot('4'), { key: 'o' }); - expect(getFocusedItemId()).to.equal('1'); + it("should move focus to the item's parent if the focus is on a child item that is a leaf", () => { + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }], + defaultExpandedItems: ['1'], + }); + + act(() => { + response.getItemRoot('1.1').focus(); + }); + fireEvent.keyDown(response.getItemRoot('1.1'), { key: 'ArrowLeft' }); + expect(response.getFocusedItemId()).to.equal('1'); + expect(response.isItemExpanded('1')).to.equal(true); }); - it('should work after adding / removing items', () => { - const { getFocusedItemId, getItemRoot, setItems } = render({ - items: [{ id: '1' }, { id: '2' }, { id: '3' }, { id: '4' }], + it("should move the focus to the item's parent if the focus is on a child item that is closed", () => { + const response = render({ + items: [{ id: '1', children: [{ id: '1.1', children: [{ id: '1.1.1' }] }] }], + defaultExpandedItems: ['1'], }); act(() => { - getItemRoot('1').focus(); + response.getItemRoot('1.1').focus(); }); + fireEvent.keyDown(response.getItemRoot('1.1'), { key: 'ArrowLeft' }); + expect(response.getFocusedItemId()).to.equal('1'); + expect(response.isItemExpanded('1')).to.equal(true); + }); - fireEvent.keyDown(getItemRoot('1'), { key: '4' }); - expect(getFocusedItemId()).to.equal('4'); + it('should do nothing if the focus is on a root item that is closed', () => { + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }], + }); - setItems([{ id: '1' }, { id: '2' }, { id: '3' }]); - expect(getFocusedItemId()).to.equal('1'); + act(() => { + response.getItemRoot('1').focus(); + }); + fireEvent.keyDown(response.getItemRoot('1'), { key: 'ArrowLeft' }); + expect(response.getFocusedItemId()).to.equal('1'); + expect(response.isItemExpanded('1')).to.equal(false); + }); - fireEvent.keyDown(getItemRoot('1'), { key: '2' }); - expect(getFocusedItemId()).to.equal('2'); + it('should do nothing if the focus is on a root item that is a leaf', () => { + const response = render({ + items: [{ id: '1' }], + }); + + act(() => { + response.getItemRoot('1').focus(); + }); + fireEvent.keyDown(response.getItemRoot('1'), { key: 'ArrowLeft' }); + expect(response.getFocusedItemId()).to.equal('1'); + }); + }); - setItems([{ id: '1' }, { id: '2' }, { id: '3' }, { id: '4' }]); - expect(getFocusedItemId()).to.equal('2'); + describe('key: Home', () => { + it('should move the focus to the first item in the tree', () => { + const response = render({ + items: [{ id: '1' }, { id: '2' }, { id: '3' }, { id: '4' }], + }); - fireEvent.keyDown(getItemRoot('2'), { key: '4' }); - expect(getFocusedItemId()).to.equal('4'); + act(() => { + response.getItemRoot('4').focus(); + }); + fireEvent.keyDown(response.getItemRoot('4'), { key: 'Home' }); + expect(response.getFocusedItemId()).to.equal('1'); }); + }); - it('should not move focus when pressing a modifier key and a letter', () => { - const { getFocusedItemId, getItemRoot } = render({ + describe('key: End', () => { + it('should move the focus to the last item in the tree when the last item is not expanded', () => { + const response = render({ items: [ - { id: '1', label: 'one' }, - { id: '2', label: 'two' }, - { id: '3', label: 'three' }, - { id: '4', label: 'four' }, + { id: '1' }, + { id: '2' }, + { id: '3' }, + { id: '4', children: [{ id: '4.1' }, { id: '4.2' }] }, ], }); act(() => { - getItemRoot('1').focus(); + response.getItemRoot('1').focus(); }); - expect(getFocusedItemId()).to.equal('1'); - - fireEvent.keyDown(getItemRoot('1'), { key: 't', ctrlKey: true }); - expect(getFocusedItemId()).to.equal('1'); + fireEvent.keyDown(response.getItemRoot('1'), { key: 'End' }); + expect(response.getFocusedItemId()).to.equal('4'); + }); - fireEvent.keyDown(getItemRoot('1'), { key: 't', metaKey: true }); - expect(getFocusedItemId()).to.equal('1'); + it('should move the focus to the last item in the tree when the last item is expanded', () => { + const response = render({ + items: [ + { id: '1' }, + { id: '2' }, + { id: '3' }, + { id: '4', children: [{ id: '4.1', children: [{ id: '4.1.1' }] }] }, + ], + defaultExpandedItems: ['4', '4.1'], + }); - fireEvent.keyDown(getItemRoot('1'), { key: 't', shiftKey: true }); - expect(getFocusedItemId()).to.equal('1'); + act(() => { + response.getItemRoot('1').focus(); + }); + fireEvent.keyDown(response.getItemRoot('1'), { key: 'End' }); + expect(response.getFocusedItemId()).to.equal('4.1.1'); }); + }); - it('should work on disabled item when disabledItemsFocusable={true}', () => { - const { getFocusedItemId, getItemRoot } = render({ + describe('key: * (asterisk)', () => { + it('should expand all items that are at the same depth as the current item (depth = 0)', () => { + const response = render({ items: [ - { id: '1', label: 'one', disabled: true }, - { id: '2', label: 'two', disabled: true }, - { id: '3', label: 'three', disabled: true }, - { id: '4', label: 'four', disabled: true }, + { id: '1', children: [{ id: '1.1' }] }, + { id: '2', children: [{ id: '2.1' }] }, + { id: '3', children: [{ id: '3.1', children: [{ id: '3.1.1' }] }] }, + { id: '4' }, ], - disabledItemsFocusable: true, }); act(() => { - getItemRoot('1').focus(); + response.getItemRoot('1').focus(); }); - expect(getFocusedItemId()).to.equal('1'); - fireEvent.keyDown(getItemRoot('1'), { key: 't' }); - expect(getFocusedItemId()).to.equal('2'); + expect(response.isItemExpanded('1')).to.equal(false); + expect(response.isItemExpanded('2')).to.equal(false); + expect(response.isItemExpanded('3')).to.equal(false); + + fireEvent.keyDown(response.getItemRoot('1'), { key: '*' }); + expect(response.isItemExpanded('1')).to.equal(true); + expect(response.isItemExpanded('2')).to.equal(true); + expect(response.isItemExpanded('3')).to.equal(true); + expect(response.isItemExpanded('3.1')).to.equal(false); }); - it('should not move focus on disabled item when disabledItemsFocusable={false}', () => { - const { getFocusedItemId, getItemRoot } = render({ + it('should expand all items that are at the same depth as the current item (depth = 1)', () => { + const response = render({ items: [ - { id: '1', label: 'one', disabled: true }, - { id: '2', label: 'two', disabled: true }, - { id: '3', label: 'three', disabled: true }, - { id: '4', label: 'four', disabled: true }, + { id: '1', children: [{ id: '1.1' }] }, + { id: '2', children: [{ id: '2.1' }] }, + { + id: '3', + children: [ + { + id: '3.1', + children: [{ id: '3.1.1' }, { id: '3.1.2', children: [{ id: '3.1.2.1' }] }], + }, + ], + }, + { id: '4' }, ], - disabledItemsFocusable: false, + defaultExpandedItems: ['3'], + }); + + act(() => { + response.getItemRoot('3.1').focus(); + }); + + expect(response.isItemExpanded('1')).to.equal(false); + expect(response.isItemExpanded('2')).to.equal(false); + expect(response.isItemExpanded('3')).to.equal(true); + expect(response.isItemExpanded('3.1')).to.equal(false); + + fireEvent.keyDown(response.getItemRoot('3.1'), { key: '*' }); + expect(response.isItemExpanded('1')).to.equal(false); + expect(response.isItemExpanded('2')).to.equal(false); + expect(response.isItemExpanded('3')).to.equal(true); + expect(response.isItemExpanded('3.1')).to.equal(true); + expect(response.isItemExpanded('3.1.2')).to.equal(false); + }); + }); + + describe('key: Enter', () => { + it('should expand an item with children if it is collapsed', () => { + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }], }); act(() => { - getItemRoot('1').focus(); + response.getItemRoot('1').focus(); }); - expect(getFocusedItemId()).to.equal('1'); + fireEvent.keyDown(response.getItemRoot('1'), { key: 'Enter' }); + expect(response.isItemExpanded('1')).to.equal(true); + }); - fireEvent.keyDown(getItemRoot('1'), { key: 't' }); - expect(getFocusedItemId()).to.equal('1'); + it('should collapse an item with children if it is expanded', () => { + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }], + defaultExpandedItems: ['1'], + }); + + act(() => { + response.getItemRoot('1').focus(); + }); + fireEvent.keyDown(response.getItemRoot('1'), { key: 'Enter' }); + expect(response.isItemExpanded('1')).to.equal(false); + }); + }); + }); + + describe('Type-ahead', () => { + it('should move the focus to the next item with a name that starts with the typed character', () => { + const response = render({ + items: [ + { id: '1', label: 'one' }, + { id: '2', label: 'two' }, + { id: '3', label: 'three' }, + { id: '4', label: 'four' }, + ], + }); + + act(() => { + response.getItemRoot('1').focus(); }); + expect(response.getFocusedItemId()).to.equal('1'); + + fireEvent.keyDown(response.getItemRoot('1'), { key: 't' }); + expect(response.getFocusedItemId()).to.equal('2'); + + fireEvent.keyDown(response.getItemRoot('2'), { key: 'f' }); + expect(response.getFocusedItemId()).to.equal('4'); + + fireEvent.keyDown(response.getItemRoot('4'), { key: 'o' }); + expect(response.getFocusedItemId()).to.equal('1'); + }); + + it('should move to the next item in the displayed order when typing the same starting character', () => { + const response = render({ + items: [{ id: 'A1' }, { id: 'B1' }, { id: 'A2' }, { id: 'B3' }, { id: 'B2' }], + }); + + act(() => { + response.getItemRoot('A1').focus(); + }); + expect(response.getFocusedItemId()).to.equal('A1'); + + fireEvent.keyDown(response.getItemRoot('A1'), { key: 'b' }); + expect(response.getFocusedItemId()).to.equal('B1'); + + fireEvent.keyDown(response.getItemRoot('B1'), { key: 'b' }); + expect(response.getFocusedItemId()).to.equal('B3'); + + fireEvent.keyDown(response.getItemRoot('B3'), { key: 'b' }); + expect(response.getFocusedItemId()).to.equal('B2'); + + fireEvent.keyDown(response.getItemRoot('B2'), { key: 'b' }); + expect(response.getFocusedItemId()).to.equal('B1'); + }); + + it('should work with capitalized label', () => { + const response = render({ + items: [ + { id: '1', label: 'One' }, + { id: '2', label: 'Two' }, + { id: '3', label: 'Three' }, + { id: '4', label: 'Four' }, + ], + }); + + act(() => { + response.getItemRoot('1').focus(); + }); + expect(response.getFocusedItemId()).to.equal('1'); + + fireEvent.keyDown(response.getItemRoot('1'), { key: 't' }); + expect(response.getFocusedItemId()).to.equal('2'); + + fireEvent.keyDown(response.getItemRoot('2'), { key: 'f' }); + expect(response.getFocusedItemId()).to.equal('4'); + + fireEvent.keyDown(response.getItemRoot('4'), { key: 'o' }); + expect(response.getFocusedItemId()).to.equal('1'); + }); + + it('should work with ReactElement label', function test() { + // Only the SimpleTreeView can have React Element labels. + if (treeViewComponent !== 'SimpleTreeView') { + this.skip(); + } + + const response = render({ + items: [ + { id: '1', label: one }, + { id: '2', label: two }, + { id: '3', label: three }, + { id: '4', label: four }, + ], + }); + + act(() => { + response.getItemRoot('1').focus(); + }); + expect(response.getFocusedItemId()).to.equal('1'); + + fireEvent.keyDown(response.getItemRoot('1'), { key: 't' }); + expect(response.getFocusedItemId()).to.equal('2'); + + fireEvent.keyDown(response.getItemRoot('2'), { key: 'f' }); + expect(response.getFocusedItemId()).to.equal('4'); + + fireEvent.keyDown(response.getItemRoot('4'), { key: 'o' }); + expect(response.getFocusedItemId()).to.equal('1'); + }); + + it('should work after adding / removing items', () => { + const response = render({ + items: [{ id: '1' }, { id: '2' }, { id: '3' }, { id: '4' }], + }); + + act(() => { + response.getItemRoot('1').focus(); + }); + + fireEvent.keyDown(response.getItemRoot('1'), { key: '4' }); + expect(response.getFocusedItemId()).to.equal('4'); + + response.setItems([{ id: '1' }, { id: '2' }, { id: '3' }]); + expect(response.getFocusedItemId()).to.equal('1'); + + fireEvent.keyDown(response.getItemRoot('1'), { key: '2' }); + expect(response.getFocusedItemId()).to.equal('2'); + + response.setItems([{ id: '1' }, { id: '2' }, { id: '3' }, { id: '4' }]); + expect(response.getFocusedItemId()).to.equal('2'); + + fireEvent.keyDown(response.getItemRoot('2'), { key: '4' }); + expect(response.getFocusedItemId()).to.equal('4'); + }); + + it('should not move focus when pressing a modifier key and a letter', () => { + const response = render({ + items: [ + { id: '1', label: 'one' }, + { id: '2', label: 'two' }, + { id: '3', label: 'three' }, + { id: '4', label: 'four' }, + ], + }); + + act(() => { + response.getItemRoot('1').focus(); + }); + expect(response.getFocusedItemId()).to.equal('1'); + + fireEvent.keyDown(response.getItemRoot('1'), { key: 't', ctrlKey: true }); + expect(response.getFocusedItemId()).to.equal('1'); + + fireEvent.keyDown(response.getItemRoot('1'), { key: 't', metaKey: true }); + expect(response.getFocusedItemId()).to.equal('1'); + + fireEvent.keyDown(response.getItemRoot('1'), { key: 't', shiftKey: true }); + expect(response.getFocusedItemId()).to.equal('1'); + }); + + it('should work on disabled item when disabledItemsFocusable={true}', () => { + const response = render({ + items: [ + { id: '1', label: 'one', disabled: true }, + { id: '2', label: 'two', disabled: true }, + { id: '3', label: 'three', disabled: true }, + { id: '4', label: 'four', disabled: true }, + ], + disabledItemsFocusable: true, + }); + + act(() => { + response.getItemRoot('1').focus(); + }); + expect(response.getFocusedItemId()).to.equal('1'); + + fireEvent.keyDown(response.getItemRoot('1'), { key: 't' }); + expect(response.getFocusedItemId()).to.equal('2'); + }); + + it('should not move focus on disabled item when disabledItemsFocusable={false}', () => { + const response = render({ + items: [ + { id: '1', label: 'one', disabled: true }, + { id: '2', label: 'two', disabled: true }, + { id: '3', label: 'three', disabled: true }, + { id: '4', label: 'four', disabled: true }, + ], + disabledItemsFocusable: false, + }); + + act(() => { + response.getItemRoot('1').focus(); + }); + expect(response.getFocusedItemId()).to.equal('1'); + + fireEvent.keyDown(response.getItemRoot('1'), { key: 't' }); + expect(response.getFocusedItemId()).to.equal('1'); }); - }, -); + }); +});