Skip to content

Commit

Permalink
test: Increase code coverage for Linker (#1215)
Browse files Browse the repository at this point in the history
  • Loading branch information
jason-wang24 authored Apr 15, 2023
1 parent 8ac7dc8 commit 8c7207c
Show file tree
Hide file tree
Showing 7 changed files with 602 additions and 9 deletions.
2 changes: 1 addition & 1 deletion packages/components/src/DragUtils.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import DragUtils from './DragUtils';

function makeItems(count = 5) {
const items = [];
const items: number[] = [];

for (let i = 0; i < count; i += 1) {
items.push(i);
Expand Down
162 changes: 162 additions & 0 deletions packages/dashboard-core-plugins/src/linker/Linker.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import {
OpenedPanelMap,
PanelComponent,
PanelManager,
} from '@deephaven/dashboard';
import GoldenLayout, { Config } from '@deephaven/golden-layout';
import { TypeValue as FilterTypeValue } from '@deephaven/filters';
import ToolType from './ToolType';
import { Linker } from './Linker';
import { Link, LinkPoint, LinkType } from './LinkerUtils';

// Disable CSSTransition delays to make testing simpler
jest.mock('react-transition-group', () => ({
// eslint-disable-next-line react/display-name, react/prop-types
Transition: ({ children, in: inProp }) =>
inProp !== false ? children : null,
// eslint-disable-next-line react/display-name, react/prop-types
CSSTransition: ({ children, in: inProp }) =>
inProp !== false ? children : null,
}));

function makeLayout() {
return new GoldenLayout({} as Config, undefined);
}

function makePanelManager(layout = makeLayout()) {
const PANEL_ID_A = 'PANEL_ID_A';
const PANEL_ID_B = 'PANEL_ID_B';
const openedMap: OpenedPanelMap = new Map([
[
PANEL_ID_A,
{
getCoordinateForColumn: jest.fn(() => {
const coordinate = [5, 5];
return coordinate; // make coordinates here
}),
} as PanelComponent,
],
[
PANEL_ID_B,
{
getCoordinateForColumn: jest.fn(() => {
const coordinate = [50, 50];
return coordinate; // make coordinates here
}),
} as PanelComponent,
],
]);
return new PanelManager(layout, undefined, undefined, openedMap);
}

function makeLinkPoint(
panelId: string | string[],
columnName: string,
columnType: string | null,
panelComponent?: string | null
): LinkPoint {
return { panelId, panelComponent, columnName, columnType };
}

function makeLink(
start: LinkPoint,
end: LinkPoint,
id: string,
type: LinkType,
isReversed?: boolean | undefined,
operator?: FilterTypeValue | undefined
): Link {
return { start, end, id, isReversed, type, operator };
}

function mountLinker({
links = [] as Link[],
timeZone = 'TIMEZONE',
activeTool = ToolType.LINKER,
localDashboardId = 'TEST_ID',
layout = makeLayout(),
panelManager = makePanelManager(),
setActiveTool = jest.fn(),
setDashboardLinks = jest.fn(),
addDashboardLinks = jest.fn(),
deleteDashboardLinks = jest.fn(),
setDashboardIsolatedLinkerPanelId = jest.fn(),
setDashboardColumnSelectionValidator = jest.fn(),
} = {}) {
return render(
<Linker
links={links}
timeZone={timeZone}
activeTool={activeTool}
localDashboardId={localDashboardId}
layout={layout}
panelManager={panelManager}
setActiveTool={setActiveTool}
setDashboardLinks={setDashboardLinks}
addDashboardLinks={addDashboardLinks}
deleteDashboardLinks={deleteDashboardLinks}
setDashboardIsolatedLinkerPanelId={setDashboardIsolatedLinkerPanelId}
setDashboardColumnSelectionValidator={
setDashboardColumnSelectionValidator
}
/>
);
}

it('closes Linker when escape key or Done button is pressed', async () => {
const setActiveTool = jest.fn();
mountLinker({ setActiveTool });
const dialog = screen.getByTestId('linker-toast-dialog');
const buttons = await screen.findAllByRole('button');
expect(buttons).toHaveLength(3);

const doneButton = screen.getByRole('button', { name: 'Done' });
fireEvent.click(doneButton);
expect(setActiveTool).toHaveBeenCalledWith(ToolType.DEFAULT);

fireEvent.keyDown(dialog, { key: 'Escape' });
expect(setActiveTool).toHaveBeenCalledWith(ToolType.DEFAULT);
});

describe('tests link operations', () => {
const deleteDashboardLinks = jest.fn();
const setDashboardLinks = jest.fn();
let linkPaths: HTMLElement[] = [];

beforeEach(async () => {
const links: Link[] = [];
for (let i = 0; i < 4; i += 1) {
const start = makeLinkPoint(
'PANEL_ID_A',
`COLUMN_A`,
'int',
'PANEL_COMPONENT'
);
const end = makeLinkPoint(
'PANEL_ID_B',
`COLUMN_B_${i}`,
'long',
'PANEL_COMPONENT'
);
const link = makeLink(start, end, `LINK_ID_${i}`, 'tableLink');
links.push(link);
}
mountLinker({ links, deleteDashboardLinks, setDashboardLinks });
linkPaths = screen.getAllByTestId('link-select');
expect(linkPaths).toHaveLength(4);
});

it('deletes correct link with alt+click', async () => {
expect(linkPaths).toHaveLength(4);
fireEvent.click(linkPaths[0], { altKey: true });
expect(deleteDashboardLinks).toHaveBeenCalledWith('TEST_ID', ['LINK_ID_0']);
});

it('deletes all links when Clear All is clicked', async () => {
const clearAllButton = screen.getByRole('button', { name: 'Clear All' });
fireEvent.click(clearAllButton);
expect(setDashboardLinks).toHaveBeenCalledWith('TEST_ID', []);
});
});
163 changes: 163 additions & 0 deletions packages/dashboard-core-plugins/src/linker/LinkerLink.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
import { Type as FilterType } from '@deephaven/filters';
import LinkerLink from './LinkerLink';

function makeLinkerLink({
x1 = 10,
x2 = 50,
y1 = 10,
y2 = 10,
isSelected = true,
startColumnType = 'int',
id = 'LINK_ID',
className = 'linker-link link-is-selected',
operator = FilterType.eq,
onClick = jest.fn(),
onDelete = jest.fn(),
onOperatorChanged = jest.fn(),
} = {}) {
return render(
<LinkerLink
x1={x1}
x2={x2}
y1={y1}
y2={y2}
id={id}
className={className}
operator={operator}
isSelected={isSelected}
startColumnType={startColumnType}
onClick={onClick}
onDelete={onDelete}
onOperatorChanged={onOperatorChanged}
/>
);
}

it('mounts and renders correct comparison operators for strings', async () => {
const onOperatorChanged = jest.fn();
const props = {
startColumnType: 'java.lang.String',
operator: FilterType.startsWith,
onOperatorChanged,
};
makeLinkerLink(props);

const dropdownAndDeleteButton = await screen.findAllByRole('button');
expect(dropdownAndDeleteButton[0]).toHaveTextContent('a*');

dropdownAndDeleteButton[0].click();
const dropdownMenu = await screen.findAllByRole('button');
expect(dropdownMenu).toHaveLength(8); // includes dropdown and delete button
expect(dropdownMenu[2]).toHaveTextContent('is exactly');
expect(dropdownMenu[3]).toHaveTextContent('is not exactly');
expect(dropdownMenu[4]).toHaveTextContent('contains');
expect(dropdownMenu[5]).toHaveTextContent('does not contain');
expect(dropdownMenu[6]).toHaveTextContent('starts with');
expect(dropdownMenu[7]).toHaveTextContent('ends with');

dropdownMenu[4].click();
expect(onOperatorChanged).toHaveBeenCalledWith(
'LINK_ID',
FilterType.contains
);
});

it('renders correct symbol for endsWith', async () => {
makeLinkerLink({ operator: FilterType.endsWith });
const dropdownAndDeleteButton = await screen.findAllByRole('button');
expect(dropdownAndDeleteButton[0]).toHaveTextContent('*z');
});

it('mounts and renders correct comparison operators for numbers', async () => {
const props = {
x1: 10,
x2: 10,
y1: 30,
y2: 50,
startColumnType: 'long',
operator: FilterType.notEq,
};
makeLinkerLink(props);
const dropdownAndDeleteButton = await screen.findAllByRole('button');
expect(dropdownAndDeleteButton[0]).toHaveTextContent('!=');

dropdownAndDeleteButton[0].click();
const dropdownMenu = await screen.findAllByRole('button');
expect(dropdownMenu).toHaveLength(8); // includes dropdown and delete button
expect(dropdownMenu[2]).toHaveTextContent('is equal to');
expect(dropdownMenu[3]).toHaveTextContent('is not equal to');
expect(dropdownMenu[4]).toHaveTextContent('greater than');
expect(dropdownMenu[5]).toHaveTextContent('greater than or equal to');
expect(dropdownMenu[6]).toHaveTextContent('less than');
expect(dropdownMenu[7]).toHaveTextContent('less than or equal to');
});

it('mounts and renders correct comparison operators for date/time', async () => {
const props = {
x1: 10,
x2: 20,
y1: 50,
y2: 30,
startColumnType: 'io.deephaven.time.DateTime',
operator: FilterType.lessThan,
};
makeLinkerLink(props);
const dropdownAndDeleteButton = await screen.findAllByRole('button');
expect(dropdownAndDeleteButton[0]).toHaveTextContent('<');

dropdownAndDeleteButton[0].click();
const dropdownMenu = await screen.findAllByRole('button');
expect(dropdownMenu).toHaveLength(8); // includes dropdown and delete button
expect(dropdownMenu[2]).toHaveTextContent('date is');
expect(dropdownMenu[3]).toHaveTextContent('date is not');
expect(dropdownMenu[4]).toHaveTextContent('date is after');
expect(dropdownMenu[5]).toHaveTextContent('date is after or equal');
expect(dropdownMenu[6]).toHaveTextContent('date is before');
expect(dropdownMenu[7]).toHaveTextContent('date is before or equal');
});

it('mounts and renders correct comparison operators for booleans', async () => {
const props = {
x1: 10,
x2: 20,
y1: 30,
y2: 100,
startColumnType: 'boolean',
operator: FilterType.greaterThanOrEqualTo,
};
makeLinkerLink(props);
const dropdownAndDeleteButton = await screen.findAllByRole('button');
expect(dropdownAndDeleteButton[0]).toHaveTextContent('>=');

dropdownAndDeleteButton[0].click();
const dropdownMenu = await screen.findAllByRole('button');
expect(dropdownMenu).toHaveLength(4); // includes dropdown and delete button
expect(dropdownMenu[2]).toHaveTextContent('is equal to');
expect(dropdownMenu[3]).toHaveTextContent('is not equal to');
});

it('returns an empty label for invalid column type', async () => {
const startColumnType = 'INVALID_TYPE';
makeLinkerLink({ startColumnType });
expect(LinkerLink.getLabelForLinkFilter(startColumnType, FilterType.eq)).toBe(
''
);
});

it('calls onClick when the link is clicked and onDelete on alt-click and button press', async () => {
const onClick = jest.fn();
const onDelete = jest.fn();
makeLinkerLink({ onClick, onDelete });

const linkPath = screen.getByTestId('link-select');
fireEvent.click(linkPath);
expect(onClick).toHaveBeenCalledTimes(1);

fireEvent.click(linkPath, { altKey: true });
expect(onDelete).toHaveBeenCalledTimes(1);
const dropdownAndDeleteButton = await screen.findAllByRole('button');
dropdownAndDeleteButton[1].click();
expect(onDelete).toHaveBeenCalledTimes(2);
});
1 change: 1 addition & 0 deletions packages/dashboard-core-plugins/src/linker/LinkerLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ export class LinkerLink extends Component<LinkerLinkProps, LinkerLinkState> {
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
clipPath={`url(#${clipPathId})`}
data-testid="link-select"
/>
<path className="link-background" d={path} />
<path className="link-foreground" d={path} />
Expand Down
Loading

0 comments on commit 8c7207c

Please sign in to comment.