Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: Increase code coverage for Linker #1215

Merged
merged 1 commit into from
Apr 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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