Skip to content
Draft
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
24 changes: 23 additions & 1 deletion packages/app/src/__tests__/DimensionMapper.browser.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect, test } from 'vitest';
import { page } from 'vitest/browser';
import { commands, page } from 'vitest/browser';

import { getDimMappingBtn, renderApp } from '../test-utils';
import { Vis } from '../vis-packs/core/visualizations';
Expand Down Expand Up @@ -108,6 +108,28 @@ test('slice through 2D dataset', async () => {
expect(d0Slider).toHaveValue(1);
});

test('slice through 2D dataset by dragging slider thumb', async () => {
await renderApp({ initialPath: '/arrays/twoD', preferredVis: Vis.Line });

const d0Slider = page.getByRole('slider', { name: 'D0' });
const { left, top, width } = d0Slider.element().getBoundingClientRect();
const [x, y] = [left + width / 2, top + 2]; // grab thumb above its centre

// Thumb doesn't jump when grabbed off-centre
await commands.mouseDrag([
[x, y],
[x, y - 1],
]);
await expect.element(d0Slider).toHaveValue(0);

// Drag thumb to top of slider
await commands.mouseDrag([
[x, y],
[x, y - 1000],
]);
await expect.element(d0Slider).toHaveValue(19);
});

test('slice through 2D opaque dataset', async () => {
const { user } = await renderApp('/arrays/twoD_opaque');

Expand Down
41 changes: 40 additions & 1 deletion packages/app/src/__tests__/DomainWidget.browser.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { expect, test } from 'vitest';
import { page } from 'vitest/browser';

import { renderApp } from '../test-utils';
import { dragHorizontally, renderApp } from '../test-utils';

test('show slider with two thumbs and reveal popup on hover', async () => {
const { user } = await renderApp('/nexus/NXdata');
Expand Down Expand Up @@ -94,6 +94,45 @@ test('move thumbs with keyboard to update domain', async () => {
expect(minInput).toHaveValue('−2.30818e+2'); // unaffected
});

test('move thumbs with pointer to update domain', async () => {
await renderApp('/nexus/NXdata');

const minThumb = page.getByRole('slider', { name: /min/ });
const maxThumb = page.getByRole('slider', { name: /max/ });
const minInput = page.getByLabelText('min', { exact: true });
const maxInput = page.getByLabelText('max', { exact: true });
const minBtn = page.getByRole('button', { name: 'Min', exact: true });
const maxBtn = page.getByRole('button', { name: 'Max', exact: true });

// Drag min thumb and bring it back to its initial position
await dragHorizontally(minThumb, [20, 0]);
await expect.element(minInput).toHaveValue('−9.5e+1'); // not changed
expect(minThumb).toHaveValue(20);
expect(minBtn).toHaveAttribute('aria-pressed', 'true'); // still autoscaling

// Drag min thumb past max thumb, then back a little
const { left: minLeft } = minThumb.element().getBoundingClientRect();
const { left: maxLeft } = maxThumb.element().getBoundingClientRect();
await dragHorizontally(minThumb, [maxLeft - minLeft + 20, maxLeft - minLeft]);

// Min thumb pushes max thumb and keeps focus
await expect.element(minBtn).toHaveAttribute('aria-pressed', 'false');
expect(maxBtn).toHaveAttribute('aria-pressed', 'false');
expect(minInput).toHaveValue('3.71154e+2');
expect(maxInput).toHaveValue('4.81114e+3');
expect(minThumb).toHaveFocus();
});

test('toggle popup when clicking left edge of edit button', async () => {
const { user } = await renderApp('/nexus/NXdata');

const editBtn = page.getByRole('button', { name: 'Edit domain' });
await user.click(editBtn, { position: { x: 1, y: 10 } });

expect(editBtn).toHaveAttribute('aria-pressed', 'true');
expect(page.getByRole('slider', { name: /max/ })).toHaveValue(81);
});

test('edit bounds manually', async () => {
const { user } = await renderApp('/nexus/NXdata');

Expand Down
28 changes: 27 additions & 1 deletion packages/app/src/test-utils.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { assertDefined, assertNonNull } from '@h5web/shared/guards';
import { expect, type MockInstance, vi } from 'vitest';
import { type Locator, page, userEvent } from 'vitest/browser';
import { commands, type Locator, page, userEvent } from 'vitest/browser';
import { render, type RenderResult } from 'vitest-browser-react';

import App from './App';
Expand All @@ -9,6 +9,12 @@ import { delay } from './providers/mock/utils';
import { type Vis } from './vis-packs/core/visualizations';
import { type NxDataVis } from './vis-packs/nexus/visualizations';

declare module 'vitest/browser' {
interface BrowserCommands {
mouseDrag: (path: [number, number][]) => Promise<void>;
}
}

interface RenderAppResult extends RenderResult {
user: ReturnType<typeof userEvent.setup>;
selectExplorerNode: (name: string) => Promise<void>;
Expand Down Expand Up @@ -135,6 +141,26 @@ export function getSelectedVisTab(): string {
return selectedTab.textContent;
}

/**
* Drag element with real mouse events: press mouse button at centre of element,
* move pointer horizontally through the given offsets (in px), then release.
*/
export async function dragHorizontally(
locator: Locator,
offsets: number[],
): Promise<void> {
const { left, top, width, height } = locator
.element()
.getBoundingClientRect();

const x = left + width / 2;
const y = top + height / 2;
await commands.mouseDrag([
[x, y],
...offsets.map((dx): [number, number] => [x + dx, y]),
]);
}

export function getDimMappingBtn(axis: 'x' | 'y', dim: number): Locator {
return page
.getByLabelText(`Dimension as ${axis} axis`)
Expand Down
22 changes: 21 additions & 1 deletion packages/app/vitest.browser.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { playwright } from '@vitest/browser-playwright';
import { defineBrowserCommand, playwright } from '@vitest/browser-playwright';
import reactFallbackThrottlePlugin from 'vite-plugin-react-fallback-throttle';
import { defineConfig, mergeConfig } from 'vitest/config';

Expand All @@ -8,6 +8,25 @@ const browser = process.env.CI
? 'chromium' // Firefox doesn't work in CI: https://github.com/microsoft/playwright/issues/11566
: process.env.VITE_TEST_BROWSER;

// Press mouse button at first point of path, move through other points, then release
// (coordinates are relative to the viewport of the test iframe)
const mouseDrag = defineBrowserCommand(async (ctx, path) => {
const iframe = ctx.iframe.owner();
const box = await iframe.boundingBox();
const scale = box.width / (await iframe.evaluate((elem) => elem.clientWidth));
const [start, ...rest] = path.map(([x, y]) => [
box.x + x * scale,
box.y + y * scale,
]);

await ctx.page.mouse.move(...start);
await ctx.page.mouse.down();
for (const point of rest) {
await ctx.page.mouse.move(...point, { steps: 5 }); // eslint-disable-line no-await-in-loop -- moves must be sequential
}
await ctx.page.mouse.up();
});

export default mergeConfig(
viteConfig,
defineConfig({
Expand All @@ -27,6 +46,7 @@ export default mergeConfig(
instances: [{ browser }],
viewport: { width: 1920, height: 1080 },
ui: false,
commands: { mouseDrag },
},
},
}),
Expand Down
2 changes: 0 additions & 2 deletions packages/lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@
"@types/d3-scale": "~4.0.9",
"@types/d3-scale-chromatic": "~3.1.0",
"@types/ndarray": "1.1.0",
"@types/react-slider": "~1.3.6",
"@visx/axis": "4.0.0",
"@visx/drag": "4.0.0",
"@visx/grid": "4.0.0",
Expand All @@ -82,7 +81,6 @@
"react-icons": "5.7.0",
"react-is": "^19.3.0",
"react-keyed-flatten-children": "5.1.3",
"react-slider": "2.0.6",
"react-window": "2.2.7",
"zustand": "5.0.14"
},
Expand Down
12 changes: 10 additions & 2 deletions packages/lib/src/dimension-mapper/SlicingSlider.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,21 @@
align-items: center;
min-height: 5rem;
cursor: pointer;
touch-action: pan-x;
}

.track {
position: relative;
flex: 1;
width: var(--track-width);
margin: calc(var(--thumb-size) / 2) 0;
border-left: var(--track-width) solid var(--track-color);
background-color: var(--track-color);
border-radius: var(--track-width);
}

.thumb {
position: absolute;
left: 50%;
display: flex;
align-items: center;
justify-content: center;
Expand All @@ -49,6 +54,7 @@
border-radius: calc(var(--thumb-size) / 2);
height: var(--thumb-size);
min-width: var(--thumb-size);
transform: translate(-50%, 50%);
cursor: grab;
}

Expand All @@ -57,6 +63,8 @@
}

.mark {
position: absolute;
left: 50%;
width: 0.5rem;
height: 0.5rem;
background-color: var(
Expand All @@ -66,7 +74,7 @@
border: 2px solid
var(--h5w-slicingSlider-mark--borderColor, var(--track-color));
border-radius: 50%;
transform: translateY(-100%);
transform: translate(-50%, 50%);
}

.slicingControls {
Expand Down
Loading
Loading