Skip to content

Commit

Permalink
feat: remove rc-tree and develop the new Tree component (#418)
Browse files Browse the repository at this point in the history
* feat: remove rc-tree and develop the new Tree component

* feat: support onDropTree in tree component

* fix: add data-* for treeNode

* fix: improve select in tree component

* fix: reset the dragProvider in workbench

* feat: support to expand editable node

* test: update snapshot

* fix: remove useless styleSheet

* chore: remove rc-tree dependency

* test: improve test for paneView which is on top of TreeView

* fix: remove the style of rc-tree

* test: improve unit test for tree component

* fix: prevent drag to itself will expand itself

* feat: support to controll the expand keys

* feat: support to distinguish loadData in tree

* fix: improve the color theme for indent

* feat: support treeClick event for reset the current active node

* fix: improve the porblems caused by rebase

* chore: remove useless theme color

* refactor: rename onSelectNode to onSelect
  • Loading branch information
mortalYoung authored Oct 11, 2021
1 parent 8626600 commit 8690180
Show file tree
Hide file tree
Showing 30 changed files with 1,077 additions and 552 deletions.
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
"rc-dialog": "8.2.1",
"rc-textarea": "~0.3.1",
"rc-tooltip": "^5.1.1",
"rc-tree": "~3.10.0",
"rc-util": "~5.5.0",
"react-dnd": "^9.3.4",
"react-dnd-html5-backend": "^9.3.4",
Expand Down
8 changes: 8 additions & 0 deletions src/common/event/eventBus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,12 @@ export abstract class GlobalEvent {
public emit(name: string, ...args: any) {
EventBus.emit(name, ...args);
}

/**
* Count the service event
* @param name Event name
*/
public count(name: string) {
return EventBus.count(name);
}
}
5 changes: 5 additions & 0 deletions src/common/event/eventEmitter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
export class EventEmitter {
private _events = new Map<string, Function[]>();

public count(name: string) {
const events = this._events.get(name) || [];
return events.length;
}

public emit(name: string, ...args) {
const events = this._events.get(name);
if (events && events.length > 0) {
Expand Down
249 changes: 226 additions & 23 deletions src/components/tree/__tests__/tree.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import React from 'react';
import { cleanup, fireEvent, render, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import TreeView, { ITreeNodeItemProps } from '../index';
import { dragToTargetNode } from '@test/utils';
import { dragToTargetNode, expectFnCalled, sleep } from '@test/utils';
import { act } from 'react-test-renderer';
import { unexpandTreeNodeClassName, expandTreeNodeClassName } from '../base';

const mockData: ITreeNodeItemProps[] = [
{
Expand All @@ -23,6 +25,15 @@ const mockData: ITreeNodeItemProps[] = [
},
];

// mock Scrollable component
jest.mock('lodash', () => {
const originalModule = jest.requireActual('lodash');
return {
...originalModule,
debounce: (fn) => fn,
};
});

describe('Test the Tree component', () => {
afterEach(cleanup);

Expand Down Expand Up @@ -85,7 +96,7 @@ describe('Test the Tree component', () => {

const parentIcon = container
.querySelector<HTMLDivElement>('div[data-id="mo_treeNode_1"]')
?.querySelector('span.codicon-chevron-right');
?.querySelector('span.codicon-chevron-down');

const childNode = await waitFor(() =>
container.querySelector<HTMLDivElement>(
Expand Down Expand Up @@ -191,6 +202,25 @@ describe('Test the Tree component', () => {
expect(await findByTitle('test2')).toBeInTheDocument();
});

test('Should support to drag into children', async () => {
const data = [
{
id: '1',
name: 'test1',
children: [
{
id: '2',
name: 'test2',
isEditable: true,
},
],
},
];
const { findByTitle } = render(<TreeView data={data} />);

expect(await findByTitle('test2')).toBeInTheDocument();
});

test('Should NOT support to sort via drag', async () => {
const data = [
{ id: '1', name: 'test1', isLeaf: true },
Expand Down Expand Up @@ -220,15 +250,12 @@ describe('Test the Tree component', () => {
},
];
const mockFn = jest.fn();
const { findByTitle } = render(
<TreeView
draggable
onDropTree={mockFn}
defaultExpandAll
data={data}
/>
const { findByTitle, getByTitle } = render(
<TreeView draggable onDropTree={mockFn} data={data} />
);

fireEvent.click(getByTitle('test1'));

dragToTargetNode(
await findByTitle('test1-1'),
await findByTitle('test1')
Expand All @@ -250,15 +277,12 @@ describe('Test the Tree component', () => {
source,
];
const mockFn = jest.fn();
const { findByTitle } = render(
<TreeView
draggable
onDropTree={mockFn}
defaultExpandAll
data={data}
/>
const { findByTitle, getByTitle } = render(
<TreeView draggable onDropTree={mockFn} data={data} />
);

fireEvent.click(getByTitle('test1'));

dragToTargetNode(
await findByTitle(source.name),
await findByTitle(target.name)
Expand Down Expand Up @@ -287,15 +311,13 @@ describe('Test the Tree component', () => {
},
];
const mockFn = jest.fn();
const { findByTitle } = render(
<TreeView
draggable
onDropTree={mockFn}
defaultExpandAll
data={data}
/>
const { findByTitle, getByTitle } = render(
<TreeView draggable onDropTree={mockFn} data={data} />
);

fireEvent.click(getByTitle('test1'));
fireEvent.click(getByTitle('test2'));

dragToTargetNode(
await findByTitle(source.name),
await findByTitle(target.name)
Expand All @@ -310,4 +332,185 @@ describe('Test the Tree component', () => {
children: [target],
});
});

test('Should NOT drag node to its parent node or drag node to its siblings or drag node to itself', async () => {
const data = [
{
id: '1',
name: 'test1',
children: [
{ id: '1-1', isLeaf: true, name: 'test1-1' },
{ id: '1-2', isLeaf: true, name: 'test1-2' },
],
},
{
id: '2',
name: 'test2',
isLeaf: true,
},
];
const mockFn = jest.fn();
const { findByTitle } = render(
<TreeView draggable onDropTree={mockFn} data={data} />
);

fireEvent.click(await findByTitle('test1'));

dragToTargetNode(
await findByTitle('test1-1'),
await findByTitle('test1')
);

expect(mockFn).not.toBeCalled();

dragToTargetNode(
await findByTitle('test1-2'),
await findByTitle('test1-1')
);
expect(mockFn).not.toBeCalled();

dragToTargetNode(
await findByTitle('test1'),
await findByTitle('test1')
);
expect(mockFn).not.toBeCalled();
});

test('Should end drop when drag node out of tree', async () => {
const data = [
{
id: '1',
name: 'test1',
children: [
{ id: '1-1', isLeaf: true, name: 'test1-1' },
{ id: '1-2', isLeaf: true, name: 'test1-2' },
],
},
];
const mockFn = jest.fn();
const { findByTitle, container, getByTestId } = render(
<TreeView draggable onDropTree={mockFn} data={data} />
);

// creat a dom insert into body as the drop node
const outOfTree = document.createElement('div');
outOfTree.dataset.testid = 'outOfTree';
outOfTree.style.width = '100px';
outOfTree.style.height = '100px';
container.appendChild(outOfTree);

// expand the parent node
fireEvent.click(await findByTitle('test1'));
fireEvent.dragStart(await findByTitle('test1'));
fireEvent.dragOver(await findByTitle('test1'));

expect(container.querySelectorAll('.drag-over').length).not.toBe(0);

// drag node out of tree and drop it
fireEvent.dragOver(getByTestId('outOfTree'));
fireEvent.dragEnd(getByTestId('outOfTree'));

expect(container.querySelectorAll('.drag-over').length).toBe(0);
});

test('Should expand the drop node if this node is a folder', async () => {
const data = [
{
id: '1',
name: 'test1',
children: [{ id: '1-1', isLeaf: true, name: 'test1-1' }],
},
{ id: '2', isLeaf: true, name: 'test2' },
];
const { getByText } = render(<TreeView draggable data={data} />);

expect(getByText('test1').parentElement!.classList).toContain(
unexpandTreeNodeClassName
);
dragToTargetNode(getByText('test2'), getByText('test1'));

expect(getByText('test1').parentElement!.classList).toContain(
expandTreeNodeClassName
);

// drag to itself won't expand
dragToTargetNode(getByText('test1'), getByText('test1'));
expect(getByText('test1').parentElement!.classList).toContain(
unexpandTreeNodeClassName
);
});

test('Should support to loadData in sync', async () => {
const data = [
{
id: '1',
name: 'test1',
isLeaf: false,
children: [],
},
];
const mockFn = jest.fn().mockImplementation(() => sleep(1000));
const { getByText, container } = render(
<TreeView data={data} onLoadData={mockFn} />
);

act(() => {
fireEvent.click(getByText('test1'));
});

expect(mockFn).toBeCalledTimes(1);
expect(container.querySelector('.codicon-spin')).toBeInTheDocument();
await sleep(1000);
expect(container.querySelector('.codicon-spin')).toBeNull();

act(() => {
// unfold it and open it again
fireEvent.click(getByText('test1'));
fireEvent.click(getByText('test1'));
});

// didn't trigger onLoadData this time
expect(mockFn).toBeCalledTimes(1);
});

test('Should support to be controlled', () => {
const mockFn = jest.fn();
const { getByText, rerender } = render(
<TreeView data={mockData} expandKeys={[]} onExpand={mockFn} />
);

expect(getByText('test1').parentElement?.classList).toContain(
unexpandTreeNodeClassName
);

fireEvent.click(getByText('test1'));

expect(getByText('test1').parentElement?.classList).toContain(
unexpandTreeNodeClassName
);
expect(mockFn).toBeCalled();

rerender(
<TreeView
data={mockData}
expandKeys={[mockData[0].key!]}
onExpand={mockFn}
/>
);

expect(getByText('test1').parentElement?.classList).toContain(
expandTreeNodeClassName
);
});

test('Should support to trigger tree click event', () => {
expectFnCalled((fn) => {
const { getByRole } = render(
<TreeView data={mockData} onTreeClick={fn} />
);

const wrapper = getByRole('tree');
fireEvent.click(wrapper);
});
});
});
33 changes: 33 additions & 0 deletions src/components/tree/base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {
getBEMElement,
getBEMModifier,
prefixClaName,
} from 'mo/common/className';

export const defaultTreeClassName = prefixClaName('tree');
export const defaultTreeNodeClassName = getBEMElement(
defaultTreeClassName,
'treenode'
);
export const activeTreeNodeClassName = getBEMModifier(
defaultTreeNodeClassName,
'active'
);

export const expandTreeNodeClassName = getBEMModifier(
defaultTreeNodeClassName,
'open'
);

export const unexpandTreeNodeClassName = getBEMModifier(
defaultTreeNodeClassName,
'close'
);

export const indentClassName = getBEMElement(defaultTreeClassName, 'indent');
export const indentGuideClassName = getBEMElement(indentClassName, 'guide');

export const treeNodeTitleClassName = getBEMElement(
defaultTreeNodeClassName,
'title'
);
Loading

0 comments on commit 8690180

Please sign in to comment.