Feature/slider #47
@@ -1,9 +1,31 @@
|
||||
import {
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
} from '@testing-library/svelte';
|
||||
import {
|
||||
beforeAll,
|
||||
vi,
|
||||
} from 'vitest';
|
||||
import Slider from './Slider.svelte';
|
||||
|
||||
// jsdom lacks PointerEvent; back it with MouseEvent so clientX/clientY survive.
|
||||
beforeAll(() => {
|
||||
if (typeof PointerEvent === 'undefined') {
|
||||
class PointerEventPolyfill extends MouseEvent {
|
||||
pointerId: number;
|
||||
constructor(type: string, params: PointerEventInit = {}) {
|
||||
super(type, params);
|
||||
this.pointerId = params.pointerId ?? 1;
|
||||
}
|
||||
}
|
||||
// @ts-expect-error assigning polyfill to global
|
||||
global.PointerEvent = PointerEventPolyfill;
|
||||
}
|
||||
HTMLElement.prototype.setPointerCapture = vi.fn();
|
||||
HTMLElement.prototype.releasePointerCapture = vi.fn();
|
||||
});
|
||||
|
||||
describe('Slider', () => {
|
||||
describe('Rendering', () => {
|
||||
it('renders a slider element', () => {
|
||||
@@ -60,3 +82,91 @@ describe('Slider', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Keyboard', () => {
|
||||
it('increments by step on ArrowRight / ArrowUp', async () => {
|
||||
const onValueChange = vi.fn();
|
||||
render(Slider, { value: 50, step: 5, onValueChange });
|
||||
const thumb = screen.getByRole('slider');
|
||||
await fireEvent.keyDown(thumb, { key: 'ArrowRight' });
|
||||
expect(thumb).toHaveAttribute('aria-valuenow', '55');
|
||||
expect(onValueChange).toHaveBeenCalledWith(55);
|
||||
});
|
||||
|
||||
it('decrements by step on ArrowLeft / ArrowDown', async () => {
|
||||
render(Slider, { value: 50, step: 5 });
|
||||
const thumb = screen.getByRole('slider');
|
||||
await fireEvent.keyDown(thumb, { key: 'ArrowDown' });
|
||||
expect(thumb).toHaveAttribute('aria-valuenow', '45');
|
||||
});
|
||||
|
||||
it('jumps to min on Home and max on End', async () => {
|
||||
render(Slider, { value: 50, min: 10, max: 90 });
|
||||
const thumb = screen.getByRole('slider');
|
||||
await fireEvent.keyDown(thumb, { key: 'Home' });
|
||||
expect(thumb).toHaveAttribute('aria-valuenow', '10');
|
||||
await fireEvent.keyDown(thumb, { key: 'End' });
|
||||
expect(thumb).toHaveAttribute('aria-valuenow', '90');
|
||||
});
|
||||
|
||||
it('moves by step*10 on PageUp / PageDown', async () => {
|
||||
render(Slider, { value: 50, step: 2 });
|
||||
const thumb = screen.getByRole('slider');
|
||||
await fireEvent.keyDown(thumb, { key: 'PageUp' });
|
||||
expect(thumb).toHaveAttribute('aria-valuenow', '70');
|
||||
await fireEvent.keyDown(thumb, { key: 'PageDown' });
|
||||
expect(thumb).toHaveAttribute('aria-valuenow', '50');
|
||||
});
|
||||
|
||||
it('clamps at the bounds', async () => {
|
||||
render(Slider, { value: 98, max: 100, step: 5 });
|
||||
const thumb = screen.getByRole('slider');
|
||||
await fireEvent.keyDown(thumb, { key: 'End' });
|
||||
expect(thumb).toHaveAttribute('aria-valuenow', '100');
|
||||
});
|
||||
|
||||
it('does nothing when disabled', async () => {
|
||||
const onValueChange = vi.fn();
|
||||
render(Slider, { value: 50, disabled: true, onValueChange });
|
||||
const thumb = screen.getByRole('slider');
|
||||
await fireEvent.keyDown(thumb, { key: 'ArrowRight' });
|
||||
expect(thumb).toHaveAttribute('aria-valuenow', '50');
|
||||
expect(onValueChange).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Pointer', () => {
|
||||
/**
|
||||
* Force a deterministic track rect since jsdom has no layout.
|
||||
*/
|
||||
function mockTrackRect(container: HTMLElement) {
|
||||
const track = container.querySelector('[role="presentation"]') as HTMLElement;
|
||||
track.getBoundingClientRect = () =>
|
||||
({ left: 0, right: 200, top: 0, bottom: 20, width: 200, height: 20 }) as DOMRect;
|
||||
return track;
|
||||
}
|
||||
|
||||
it('seeks to the clicked position (click-to-seek)', async () => {
|
||||
const onValueChange = vi.fn();
|
||||
const { container } = render(Slider, { value: 0, min: 0, max: 100, onValueChange });
|
||||
const track = mockTrackRect(container);
|
||||
await fireEvent.pointerDown(track, { clientX: 100, clientY: 10, pointerId: 1 });
|
||||
expect(screen.getByRole('slider')).toHaveAttribute('aria-valuenow', '50');
|
||||
expect(onValueChange).toHaveBeenCalledWith(50);
|
||||
});
|
||||
|
||||
it('updates while dragging after pointerdown', async () => {
|
||||
const { container } = render(Slider, { value: 0, min: 0, max: 100 });
|
||||
const track = mockTrackRect(container);
|
||||
await fireEvent.pointerDown(track, { clientX: 50, clientY: 10, pointerId: 1 });
|
||||
await fireEvent.pointerMove(track, { clientX: 150, clientY: 10, pointerId: 1 });
|
||||
expect(screen.getByRole('slider')).toHaveAttribute('aria-valuenow', '75');
|
||||
});
|
||||
|
||||
it('ignores pointer when disabled', async () => {
|
||||
const { container } = render(Slider, { value: 0, disabled: true });
|
||||
const track = mockTrackRect(container);
|
||||
await fireEvent.pointerDown(track, { clientX: 100, clientY: 10, pointerId: 1 });
|
||||
expect(screen.getByRole('slider')).toHaveAttribute('aria-valuenow', '0');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user