Skip to content

Commit

Permalink
test: add unit test for folderTree in workbench (#391)
Browse files Browse the repository at this point in the history
* test: add unit test for folderTree in workbench

* fix: improve the params of the onContextMenu method

* test: improve folderTree test in workbench

* test: make jest.mock scrollable component to be global

* test: remove useless code

* test: prevent mock Scrollable in scrollable.test.tsx
  • Loading branch information
mortalYoung authored Sep 2, 2021
1 parent f8eddab commit 3066baa
Show file tree
Hide file tree
Showing 9 changed files with 301 additions and 43 deletions.
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@ module.exports = {
'^monaco-editor$': '<rootDir>/mock/monacoMock.js',
'^@test/(.*)$': '<rootDir>/test/$1',
},
setupFiles: ['jest-canvas-mock', './test/setupTests.ts'],
setupFiles: ['jest-canvas-mock', './test/setupTests.tsx'],
};
8 changes: 8 additions & 0 deletions src/components/scrollable/__tests__/scrollable.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ import '@testing-library/jest-dom';

import { IScrollbarProps, Scrollable } from '../index';

// to make sure the Scrollable component not be mocked by global
jest.mock('mo/components/scrollable', () => {
const originalModule = jest.requireActual('mo/components/scrollable');
return {
...originalModule,
};
});

function TestScrollable(props: IScrollbarProps) {
return (
<div>
Expand Down
4 changes: 2 additions & 2 deletions src/controller/explorer/explorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,11 @@ export class ExplorerController
const toolbarId = item.id;
switch (toolbarId) {
case NEW_FILE_COMMAND_ID: {
this.folderTreeController.createTreeNode(FileTypes.File);
this.folderTreeController.createTreeNode?.(FileTypes.File);
break;
}
case NEW_FOLDER_COMMAND_ID: {
this.folderTreeController.createTreeNode(FileTypes.Folder);
this.folderTreeController.createTreeNode?.(FileTypes.Folder);
break;
}
case REMOVE_COMMAND_ID: {
Expand Down
26 changes: 13 additions & 13 deletions src/controller/explorer/folderTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,16 @@ export interface LoadEventData extends EventDataNode {
}

export interface IFolderTreeController {
readonly createTreeNode: (type: FileType) => void;
readonly onClickContextMenu: (
readonly createTreeNode?: (type: FileType) => void;
readonly onClickContextMenu?: (
contextMenu: IMenuItemProps,
treeNode: ITreeNodeItemProps
treeNode?: ITreeNodeItemProps
) => void;
readonly onUpdateFileName?: (file: ITreeNodeItemProps) => void;
readonly onSelectFile: (file: ITreeNodeItemProps) => void;
readonly onSelectFile?: (file: ITreeNodeItemProps) => void;
readonly onDropTree?: (treeNode: ITreeNodeItemProps[]) => void;
readonly onLoadData?: (treeNode: LoadEventData) => Promise<void>;

onRightClick(treeNode: ITreeNodeItemProps): IMenuItemProps[];
readonly onRightClick?: (treeNode: ITreeNodeItemProps) => IMenuItemProps[];
}

@singleton()
Expand Down Expand Up @@ -86,16 +85,17 @@ export class FolderTreeController

public readonly onClickContextMenu = (
contextMenu: IMenuItemProps,
treeNode: ITreeNodeItemProps
treeNode?: ITreeNodeItemProps
) => {
const menuId = contextMenu.id;
const { id: nodeId } = treeNode;
switch (menuId) {
case RENAME_COMMAND_ID: {
const { id: nodeId } = treeNode!;
this.onRename(nodeId);
break;
}
case DELETE_COMMAND_ID: {
const { id: nodeId } = treeNode!;
this.onDelete(nodeId);
break;
}
Expand All @@ -108,11 +108,11 @@ export class FolderTreeController
break;
}
case OPEN_TO_SIDE_COMMAND_ID: {
this.onSelectFile(treeNode);
this.onSelectFile(treeNode!);
break;
}
default: {
this.onContextMenuClick(treeNode, contextMenu);
this.onContextMenuClick(contextMenu, treeNode);
}
}
};
Expand Down Expand Up @@ -141,10 +141,10 @@ export class FolderTreeController
};

private onContextMenuClick = (
treeNode: ITreeNodeItemProps,
contextMenu: IMenuItemProps
contextMenu: IMenuItemProps,
treeNode?: ITreeNodeItemProps
) => {
this.emit(FolderTreeEvent.onContextMenuClick, treeNode, contextMenu);
this.emit(FolderTreeEvent.onContextMenuClick, contextMenu, treeNode);
};

private onRename = (id: number) => {
Expand Down
8 changes: 4 additions & 4 deletions src/services/workbench/explorer/folderTreeService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ export interface IFolderTreeService extends Component<IFolderTree> {
*/
onContextMenu(
callback: (
treeNode: ITreeNodeItemProps,
contextMenu: IMenuItemProps
contextMenu: IMenuItemProps,
treeNode?: ITreeNodeItemProps
) => void
): void;
/**
Expand Down Expand Up @@ -369,8 +369,8 @@ export class FolderTreeService

public onContextMenu = (
callback: (
treeNode: ITreeNodeItemProps,
contextMenu: IMenuItemProps
contextMenu: IMenuItemProps,
treeNode?: ITreeNodeItemProps
) => void
) => {
this.subscribe(FolderTreeEvent.onContextMenuClick, callback);
Expand Down
9 changes: 0 additions & 9 deletions src/workbench/activityBar/__tests__/activityBar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,6 @@ const mockData: IActivityBarItem[] = [
},
];

// mock Scrollable component
jest.mock('mo/components/scrollable', () => {
const originalModule = jest.requireActual('mo/components/scrollable');
return {
...originalModule,
Scrollable: ({ children }) => <>{children}</>,
};
});

describe('The ActivityBar Component', () => {
test('match the snapshot', () => {
const component = renderer.create(<ActivityBar data={mockData} />);
Expand Down
239 changes: 239 additions & 0 deletions src/workbench/sidebar/__tests__/folderTree.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
import React from 'react';
import { cleanup, fireEvent, render } from '@testing-library/react';
import '@testing-library/jest-dom';
import { expectFnCalled } from '@test/utils';
import { FolderTree } from '../explore';
import type { IFolderTreeProps } from '../explore/folderTree';
import type { ITreeNodeItemProps } from 'mo/components';
import { dragToTargetNode } from 'mo/components/tabs/__tests__/tab.test';
import { folderTreeClassName, folderTreeEditClassName } from '../explore/base';

function FolderTreeViewPanel(props: Omit<IFolderTreeProps, 'panel'>) {
return <FolderTree panel={{ id: 'test', name: 'test' }} {...props} />;
}

const mockFile = {
id: 'file',
name: 'file',
isLeaf: true,
};

const mockFileInFolder = {
id: 'folder-file',
name: 'folder-file',
isLeaf: true,
};

const mockEditFile = {
...mockFileInFolder,
name: 'folder-file.tsx',
isEditable: true,
};

const mockFolder = {
id: 'folder',
name: 'folder',
isLeaf: false,
children: [mockFileInFolder],
};

const mockTreeData: ITreeNodeItemProps[] = [
{
id: 'root',
name: 'root',
isLeaf: false,
children: [mockFile, mockFolder],
},
];

const mockTreeEditData = [
{
...mockTreeData[0],
children: [{ ...mockFolder, children: [mockEditFile] }],
},
];

describe('The FolderTree Component', () => {
afterEach(cleanup);

test('Should render welcome page without data', () => {
expectFnCalled((mockFn) => {
const { rerender, getByTestId, container } = render(
<FolderTreeViewPanel createTreeNode={mockFn} />
);
const wrapper = container.querySelector('div[data-content="test"]');

expect(wrapper?.innerHTML).toContain(
'you have not yet opened a folder'
);

fireEvent.click(wrapper?.querySelector('a')!);

rerender(
<FolderTreeViewPanel
entry={<div data-testid="welcome">welcome</div>}
/>
);
expect(getByTestId('welcome')).toBeInTheDocument();
});
});

test('Should support to the right click event for tree item', () => {
const mockFn = jest
.fn()
.mockImplementationOnce(() => [])
.mockImplementation(() => [{ id: 'test', name: 'test' }]);
const { getByTitle, container, getByRole } = render(
<FolderTreeViewPanel
folderTree={{ data: mockTreeData }}
onRightClick={mockFn}
/>
);

const file = getByTitle('file');
expect(file).toBeInTheDocument();

fireEvent.contextMenu(file);

expect(mockFn).toBeCalled();
expect(container.querySelector('div[role="menu"]')).toBeNull();

fireEvent.contextMenu(file);
expect(mockFn).toBeCalledTimes(2);
expect(getByRole('menu')).toBeInTheDocument();
});

test('Should support to trigger onClickContextMenu event', () => {
const contextMenu = { id: 'test', name: 'test' };
const mockFn = jest.fn().mockImplementation(() => [contextMenu]);
const mockContextMenuFn = jest.fn();

const { getByTitle, getByRole } = render(
<FolderTreeViewPanel
folderTree={{ data: mockTreeData }}
onRightClick={mockFn}
onClickContextMenu={mockContextMenuFn}
/>
);
const file = getByTitle('file');
fireEvent.contextMenu(file);

const menu = getByRole('menu');
fireEvent.click(menu.firstElementChild!);

expect(mockContextMenuFn).toBeCalled();
expect(mockContextMenuFn.mock.calls[0][0]).toEqual(
expect.objectContaining(contextMenu)
);
expect(mockContextMenuFn.mock.calls[0][1]).toEqual(
expect.objectContaining(mockFile)
);
});

test('Should support to render a input for the editing node', async () => {
const { getByRole, container } = render(
<FolderTreeViewPanel folderTree={{ data: mockTreeEditData }} />
);

const input = getByRole('input') as HTMLInputElement;

expect(input).toBeInTheDocument();
// expect to pass through name into input's value
expect(input.value).toBe(mockEditFile.name);

// expect to select the file name automatically
expect(input.selectionStart).toBe(0);
expect(input.selectionEnd).toBe(11);
expect(
container.querySelector(`.${folderTreeClassName}`)?.classList
).toContain(folderTreeEditClassName);
});

test('Should support to update file name via blur or keypress', () => {
const mockFn = jest.fn();
const { getByRole } = render(
<FolderTreeViewPanel
folderTree={{ data: mockTreeEditData }}
onUpdateFileName={mockFn}
/>
);

const input = getByRole('input');
const mockEnterValue = 'test-enter';
fireEvent.keyDown(input, {
keyCode: 13,
target: { value: mockEnterValue },
});
expect(mockFn).toBeCalled();
expect(mockFn.mock.calls[0][0]).toEqual(
expect.objectContaining({ name: mockEnterValue })
);

const mockEscValue = 'test-esc';
fireEvent.keyDown(input, {
keyCode: 27,
target: { value: mockEscValue },
});
expect(mockFn).toBeCalledTimes(2);
expect(mockFn.mock.calls[1][0]).toEqual(
expect.objectContaining({ name: mockEscValue })
);

const mockBlurValue = 'test-blur';
fireEvent.blur(input, {
target: { value: mockBlurValue },
});
expect(mockFn).toBeCalledTimes(3);
expect(mockFn.mock.calls[2][0]).toEqual(
expect.objectContaining({ name: mockBlurValue })
);
});

test('Should support to drag tree node', () => {
const mockFn = jest.fn();
const { getByTitle } = render(
<FolderTreeViewPanel
folderTree={{ data: mockTreeData }}
onDropTree={mockFn}
/>
);

dragToTargetNode(getByTitle('file'), getByTitle('folder'));

expect(mockFn).toBeCalled();
expect(mockFn.mock.calls[0][0]).toEqual([
{
...mockTreeData[0],
children: [mockFolder, mockFile],
},
]);
});

test('Should suppor to init contextMenu', () => {
const contextMenu = { id: 'test', name: 'test' };
const mockFn = jest.fn();
const { container, getByRole } = render(
<FolderTreeViewPanel
folderTree={{
data: mockTreeData,
folderPanelContextMenu: [contextMenu],
}}
onClickContextMenu={mockFn}
/>
);

const wrapper = container.querySelector(`.${folderTreeClassName}`)!;

fireEvent.contextMenu(wrapper);

const menu = getByRole('menu');
expect(menu).toBeInTheDocument();

fireEvent.click(menu.firstElementChild!);
expect(mockFn).toBeCalled();
expect(mockFn.mock.calls[0][0]).toEqual(
expect.objectContaining(contextMenu)
);
expect(mockFn.mock.calls[0][1]).toBeUndefined();
});
});
Loading

0 comments on commit 3066baa

Please sign in to comment.