Skip to content

Commit

Permalink
Work
Browse files Browse the repository at this point in the history
  • Loading branch information
flaviendelangle committed Apr 3, 2024
1 parent 7a01f9a commit 78413e1
Show file tree
Hide file tree
Showing 22 changed files with 713 additions and 415 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
import { expect } from 'chai';
import { spy } from 'sinon';
import { describeTreeView } from 'test/utils/tree-view/describeTreeView';
import { UseTreeViewExpansionSignature } from '@mui/x-tree-view/internals';
import { act, fireEvent } from '@mui-internal/test-utils';
import {
TreeItem2,
TreeItem2Props,
UseTreeItem2ContentSlotOwnProps,
useTreeItem2Utils,
} from '@mui/x-tree-view';
import * as React from 'react';

/**
* All tests related to keyboard navigation (e.g.: expanding using "Enter" and "ArrowRight")
* are located in the `useTreeViewKeyboardNavigation.test.tsx` file.
*/
describeTreeView<UseTreeViewExpansionSignature>(
'useTreeViewExpansion plugin',
({ render, setup }) => {
describe('model props (expandedItems, defaultExpandedItems, onExpandedItemsChange)', () => {
it('should not expand items when no default state and no control state are defined', () => {
const response = render({
items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }],
});

expect(response.isItemExpanded('1')).to.equal(false);
expect(response.getAllItemRoots()).to.have.length(2);
});

it('should use the default state when defined', () => {
const response = render({
items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }],
defaultExpandedItems: ['1'],
});

expect(response.isItemExpanded('1')).to.equal(true);
expect(response.getAllItemRoots()).to.have.length(3);
});

it('should use the control state when defined', () => {
const response = render({
items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }],
expandedItems: ['1'],
});

expect(response.isItemExpanded('1')).to.equal(true);
expect(response.getItemRoot('1.1')).toBeVisible();
});

it('should use the control state upon the default state when both are defined', () => {
const response = render({
items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }],
expandedItems: ['1'],
defaultExpandedItems: ['2'],
});

expect(response.isItemExpanded('1')).to.equal(true);
});

it('should react to control state update', () => {
const response = render({
items: [{ id: '1', children: [{ id: '1.1' }] }],
expandedItems: [],
});

response.setProps({ expandedItems: ['1'] });
expect(response.isItemExpanded('1')).to.equal(true);
});

it('should call the onExpandedItemsChange callback when the model is updated (add expanded item to empty list)', () => {
const onExpandedItemsChange = spy();

const response = render({
items: [{ id: '1', children: [{ id: '1.1' }] }],
onExpandedItemsChange,
});

fireEvent.click(response.getItemContent('1'));
act(() => {
response.getRoot().focus();
});

expect(onExpandedItemsChange.callCount).to.equal(1);
expect(onExpandedItemsChange.lastCall.args[1]).to.deep.equal(['1']);
});

it('should call the onExpandedItemsChange callback when the model is updated (add expanded item no non-empty list)', () => {
const onExpandedItemsChange = spy();

const response = render({
items: [
{ id: '1', children: [{ id: '1.1' }] },
{ id: '2', children: [{ id: '2.1' }] },
],
onExpandedItemsChange,
defaultExpandedItems: ['1'],
});

fireEvent.click(response.getItemContent('2'));
act(() => {
response.getRoot().focus();
});

expect(onExpandedItemsChange.callCount).to.equal(1);
expect(onExpandedItemsChange.lastCall.args[1]).to.deep.equal(['2', '1']);
});

it('should call the onExpandedItemsChange callback when the model is updated (remove expanded item)', () => {
const onExpandedItemsChange = spy();

const response = render({
items: [
{ id: '1', children: [{ id: '1.1' }] },
{ id: '2', children: [{ id: '2.1' }] },
],
onExpandedItemsChange,
defaultExpandedItems: ['1'],
});

fireEvent.click(response.getItemContent('1'));
act(() => {
response.getRoot().focus();
});

expect(onExpandedItemsChange.callCount).to.equal(1);
expect(onExpandedItemsChange.lastCall.args[1]).to.deep.equal([]);
});

it('should warn when switching from controlled to uncontrolled', () => {
const response = render({
items: [{ id: '1' }],
expandedItems: [],
});

expect(() => {
response.setProps({ expandedItems: undefined });
}).toErrorDev(
'MUI X: A component is changing the controlled expandedItems state of TreeView to be uncontrolled.',
);
});

it('should warn and not react to update when updating the default state', () => {
const response = render({
items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }],
defaultExpandedItems: ['1'],
});

expect(() => {
response.setProps({ defaultExpandedItems: ['2'] });
expect(response.isItemExpanded('1')).to.equal(true);
expect(response.isItemExpanded('2')).to.equal(false);
}).toErrorDev(
'MUI X: A component is changing the default expandedItems state of an uncontrolled TreeView after being initialized. To suppress this warning opt to use a controlled TreeView.',
);
});
});

describe('click interactions', () => {
it('should expand collapsed item when clicking on an item content', () => {
const response = render({
items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }],
});

expect(response.isItemExpanded('1')).to.equal(false);
fireEvent.click(response.getItemContent('1'));
expect(response.isItemExpanded('1')).to.equal(true);
});

it('should collapse expanded item when clicking on an item content', () => {
const response = render({
items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }],
defaultExpandedItems: ['1'],
});

expect(response.isItemExpanded('1')).to.equal(true);
fireEvent.click(response.getItemContent('1'));
expect(response.isItemExpanded('1')).to.equal(false);
});

it('should not expand collapsed item when clicking on a disabled item content', () => {
const response = render({
items: [{ id: '1', disabled: true, children: [{ id: '1.1' }] }, { id: '2' }],
});

expect(response.isItemExpanded('1')).to.equal(false);
fireEvent.click(response.getItemContent('1'));
expect(response.isItemExpanded('1')).to.equal(false);
});

it('should not collapse expanded item when clicking on a disabled item', () => {
const response = render({
items: [{ id: '1', disabled: true, children: [{ id: '1.1' }] }, { id: '2' }],
defaultExpandedItems: ['1'],
});

expect(response.isItemExpanded('1')).to.equal(true);
fireEvent.click(response.getItemContent('1'));
expect(response.isItemExpanded('1')).to.equal(true);
});

it('should expand collapsed item when clicking on an item label', () => {
const response = render({
items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }],
});

expect(response.isItemExpanded('1')).to.equal(false);
fireEvent.click(response.getItemLabel('1'));
expect(response.isItemExpanded('1')).to.equal(true);
});

it('should expand collapsed item when clicking on an item icon container', () => {
const response = render({
items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }],
});

expect(response.isItemExpanded('1')).to.equal(false);
fireEvent.click(response.getItemIconContainer('1'));
expect(response.isItemExpanded('1')).to.equal(true);
});

it('should be able to limit the expansion to the icon', function test() {
// This test is not relevant for the TreeItem component.
// We could create the equivalent test for it,
// but it's not worth the effort given the complexity of the old behavior override.
if (!setup.includes('TreeItem2')) {
this.skip();
}

const CustomTreeItem = React.forwardRef(function MyTreeItem(
props: TreeItem2Props,
ref: React.Ref<HTMLLIElement>,
) {
const { interactions } = useTreeItem2Utils({
itemId: props.itemId,
children: props.children,
});

const handleContentClick: UseTreeItem2ContentSlotOwnProps['onClick'] = (event) => {
event.defaultMuiPrevented = true;
interactions.handleSelection(event);
};

const handleIconContainerClick = (event: React.MouseEvent) => {
interactions.handleExpansion(event);
};

return (
<TreeItem2
{...props}
ref={ref}
slotProps={{
content: { onClick: handleContentClick },
iconContainer: { onClick: handleIconContainerClick },
}}
/>
);
});

const response = render({
items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }],
slots: { item: CustomTreeItem },
});

expect(response.isItemExpanded('1')).to.equal(false);
fireEvent.click(response.getItemContent('1'));
expect(response.isItemExpanded('1')).to.equal(false);
fireEvent.click(response.getItemIconContainer('1'));
expect(response.isItemExpanded('1')).to.equal(true);
});
});

describe('onItemExpansionToggle prop', () => {
it('should call the onItemExpansionToggle callback when expanding an item', () => {
const onItemExpansionToggle = spy();

const response = render({
items: [{ id: '1', children: [{ id: '1.1' }] }],
onItemExpansionToggle,
});

fireEvent.click(response.getItemContent('1'));
expect(onItemExpansionToggle.callCount).to.equal(1);
expect(onItemExpansionToggle.lastCall.args[1]).to.equal('1');
expect(onItemExpansionToggle.lastCall.args[2]).to.equal(true);
});

it('should call the onItemExpansionToggle callback when collapsing an item', () => {
const onItemExpansionToggle = spy();

const response = render({
items: [{ id: '1', children: [{ id: '1.1' }] }],
defaultExpandedItems: ['1'],
onItemExpansionToggle,
});

fireEvent.click(response.getItemContent('1'));
expect(onItemExpansionToggle.callCount).to.equal(1);
expect(onItemExpansionToggle.lastCall.args[1]).to.equal('1');
expect(onItemExpansionToggle.lastCall.args[2]).to.equal(false);
});
});
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const useTreeViewExpansion: TreeViewPlugin<UseTreeViewExpansionSignature>
models.expandedItems.setControlledValue(value);
};

const isNodeExpanded = React.useCallback(
const isItemExpanded = React.useCallback(
(itemId: string) => {
return Array.isArray(models.expandedItems.value)
? models.expandedItems.value.indexOf(itemId) !== -1
Expand All @@ -22,12 +22,12 @@ export const useTreeViewExpansion: TreeViewPlugin<UseTreeViewExpansionSignature>
[models.expandedItems.value],
);

const isNodeExpandable = React.useCallback(
const isItemExpandable = React.useCallback(
(itemId: string) => !!instance.getNode(itemId)?.expandable,
[instance],
);

const toggleNodeExpansion = useEventCallback(
const toggleItemExpansion = useEventCallback(
(event: React.SyntheticEvent, itemId: string | null) => {
if (itemId == null) {
return;
Expand Down Expand Up @@ -55,7 +55,7 @@ export const useTreeViewExpansion: TreeViewPlugin<UseTreeViewExpansionSignature>
const siblings = instance.getChildrenIds(node.parentId);

const diff = siblings.filter(
(child) => instance.isNodeExpandable(child) && !instance.isNodeExpanded(child),
(child) => instance.isItemExpandable(child) && !instance.isItemExpanded(child),
);

const newExpanded = models.expandedItems.value.concat(diff);
Expand All @@ -71,14 +71,6 @@ export const useTreeViewExpansion: TreeViewPlugin<UseTreeViewExpansionSignature>
}
};

<<<<<<< Updated upstream
populateInstance<UseTreeViewExpansionSignature>(instance, {
isNodeExpanded,
isNodeExpandable,
toggleNodeExpansion,
expandAllSiblings,
});
=======
return {
instance: {
isItemExpanded,
Expand All @@ -87,7 +79,6 @@ export const useTreeViewExpansion: TreeViewPlugin<UseTreeViewExpansionSignature>
expandAllSiblings,
},
};
>>>>>>> Stashed changes
};

useTreeViewExpansion.models = {
Expand All @@ -96,11 +87,11 @@ useTreeViewExpansion.models = {
},
};

const DEFAULT_EXPANDED_NODES: string[] = [];
const DEFAULT_EXPANDED_ITEMS: string[] = [];

useTreeViewExpansion.getDefaultizedParams = (params) => ({
...params,
defaultExpandedItems: params.defaultExpandedItems ?? DEFAULT_EXPANDED_NODES,
defaultExpandedItems: params.defaultExpandedItems ?? DEFAULT_EXPANDED_ITEMS,
});

useTreeViewExpansion.params = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import * as React from 'react';
import { DefaultizedProps, TreeViewPluginSignature } from '../../models';
import { UseTreeViewNodesSignature } from '../useTreeViewNodes';
import { UseTreeViewItemsSignature } from '../useTreeViewItems';

export interface UseTreeViewExpansionInstance {
isNodeExpanded: (itemId: string) => boolean;
isNodeExpandable: (itemId: string) => boolean;
toggleNodeExpansion: (event: React.SyntheticEvent, value: string) => void;
isItemExpanded: (itemId: string) => boolean;
isItemExpandable: (itemId: string) => boolean;
toggleItemExpansion: (event: React.SyntheticEvent, value: string) => void;
expandAllSiblings: (event: React.KeyboardEvent, itemId: string) => void;
}

Expand Down Expand Up @@ -50,5 +50,5 @@ export type UseTreeViewExpansionSignature = TreeViewPluginSignature<{
defaultizedParams: UseTreeViewExpansionDefaultizedParameters;
instance: UseTreeViewExpansionInstance;
modelNames: 'expandedItems';
dependantPlugins: [UseTreeViewNodesSignature];
dependantPlugins: [UseTreeViewItemsSignature];
}>;
Loading

0 comments on commit 78413e1

Please sign in to comment.