Skip to content

Commit

Permalink
WIP tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mj12albert committed May 18, 2024
1 parent f95c8a8 commit 2e53e49
Show file tree
Hide file tree
Showing 6 changed files with 348 additions and 2 deletions.
327 changes: 327 additions & 0 deletions packages/mui-base/src/Slider2/Root/SliderRoot.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,327 @@
import { expect } from 'chai';
import * as React from 'react';
import { spy, stub } from 'sinon';
import { createRenderer, fireEvent, screen } from '@mui/internal-test-utils';
import * as Slider from '@base_ui/react/Slider2';
import { describeConformance } from '../../../test/describeConformance';

type Touches = Array<Pick<Touch, 'identifier' | 'clientX' | 'clientY'>>;

function createTouches(touches: Touches) {
return {
changedTouches: touches.map(
(touch) =>
new Touch({
target: document.body,
...touch,
}),
),
};
}

describe('<Slider.Root />', () => {
before(function beforeHook() {
if (typeof Touch === 'undefined') {
this.skip();
}

// PointerEvent not fully implemented in jsdom, causing
// fireEvent.pointer* to ignore options
// https://github.com/jsdom/jsdom/issues/2527
(window as any).PointerEvent = window.MouseEvent;
});

const { render } = createRenderer();

describeConformance(<Slider.Root defaultValue={50} />, () => ({
inheritComponent: 'div',
render,
refInstanceof: window.HTMLDivElement,
}));

it('renders a slider', () => {
render(
<Slider.Root defaultValue={30}>
<Slider.Output />
<Slider.Track>
<Slider.Thumb />
</Slider.Track>
</Slider.Root>,
);

expect(screen.getByRole('slider')).to.have.attribute('aria-valuenow', '30');
});

describe('ARIA attributes', () => {
it('it has the correct aria attributes', () => {
const { container, getByRole, getByTestId } = render(
<Slider.Root defaultValue={30} aria-labelledby="labelId" data-testid="root">
<Slider.Output />
<Slider.Track>
<Slider.Thumb />
</Slider.Track>
</Slider.Root>,
);

const root = getByTestId('root');
const slider = getByRole('slider');
const input = container.querySelector('input');

expect(root).not.to.have.attribute('aria-labelledby');

expect(slider).to.have.attribute('aria-valuenow', '30');
expect(slider).to.have.attribute('aria-valuemin', '0');
expect(slider).to.have.attribute('aria-valuemax', '100');
expect(slider).to.have.attribute('aria-orientation', 'horizontal');

expect(input).to.have.attribute('aria-labelledby', 'labelId');
expect(input).to.have.attribute('aria-valuenow', '30');

// TODO: aria-label should be somewhere
});
});

describe('prop: disabled', () => {
it('should render data-disabled on the root, track, output and thumb', () => {
const { getByTestId } = render(
<Slider.Root defaultValue={30} disabled data-testid="root">
<Slider.Output data-testid="output" />
<Slider.Track data-testid="track">
<Slider.Thumb data-testid="thumb" />
</Slider.Track>
</Slider.Root>,
);

const root = getByTestId('root');
const output = getByTestId('output');
const track = getByTestId('track');
const thumb = getByTestId('thumb');

expect(root).to.have.attribute('data-disabled', 'true');
expect(output).to.have.attribute('data-disabled', 'true');
expect(track).to.have.attribute('data-disabled', 'true');
expect(thumb).to.have.attribute('data-disabled', 'true');
});

function TestSlider(props) {
return (
<Slider.Root {...props}>
<Slider.Track data-testid="track">
<Slider.Thumb />
</Slider.Track>
</Slider.Root>
);
}

it('should not respond to drag events after becoming disabled', function test() {
// TODO: Don't skip once a fix for https://github.com/jsdom/jsdom/issues/3029 is released.
if (/jsdom/.test(window.navigator.userAgent)) {
this.skip();
}

const { getByRole, setProps, getByTestId } = render(
<TestSlider defaultValue={0} data-testid="slider-root" />,
);

const sliderTrack = getByTestId('track');

stub(sliderTrack, 'getBoundingClientRect').callsFake(() => ({
width: 100,
height: 10,
bottom: 10,
left: 0,
x: 0,
y: 0,
top: 0,
right: 0,
toJSON() {},
}));
fireEvent.touchStart(
sliderTrack,
createTouches([{ identifier: 1, clientX: 21, clientY: 0 }]),
);

const thumb = getByRole('slider');

expect(thumb).to.have.attribute('aria-valuenow', '21');
expect(thumb).toHaveFocus();

setProps({ disabled: true });
expect(thumb).not.toHaveFocus();
// expect(thumb).not.to.have.class(classes.active);

fireEvent.touchMove(sliderTrack, createTouches([{ identifier: 1, clientX: 30, clientY: 0 }]));

expect(thumb).to.have.attribute('aria-valuenow', '21');
});

it('should not respond to drag events if disabled', function test() {
// TODO: Don't skip once a fix for https://github.com/jsdom/jsdom/issues/3029 is released.
if (/jsdom/.test(window.navigator.userAgent)) {
this.skip();
}

const { getByRole, getByTestId } = render(
<TestSlider defaultValue={21} data-testid="slider-root" disabled />,
);

const thumb = getByRole('slider');
const sliderTrack = getByTestId('track');

stub(sliderTrack, 'getBoundingClientRect').callsFake(() => ({
width: 100,
height: 10,
bottom: 10,
left: 0,
x: 0,
y: 0,
top: 0,
right: 0,
toJSON() {},
}));

fireEvent.touchStart(
sliderTrack,
createTouches([{ identifier: 1, clientX: 21, clientY: 0 }]),
);

fireEvent.touchMove(
document.body,
createTouches([{ identifier: 1, clientX: 30, clientY: 0 }]),
);

fireEvent.touchEnd(
document.body,
createTouches([{ identifier: 1, clientX: 30, clientY: 0 }]),
);

expect(thumb).to.have.attribute('aria-valuenow', '21');
});
});

describe('prop: marks', () => {
it('does not cause unknown-prop error', () => {
const marks = [
{
value: 33,
},
];
expect(() => {
render(<Slider.Root marks={marks} />);
}).not.to.throw();
});
});

describe('prop: orientation', () => {
function VerticalSlider() {
return (
<Slider.Root orientation="vertical">
<Slider.Track>
<Slider.Thumb />
</Slider.Track>
</Slider.Root>
);
}

it('sets the orientation via ARIA', () => {
render(<VerticalSlider />);

const sliderRoot = screen.getByRole('slider');
expect(sliderRoot).to.have.attribute('aria-orientation', 'vertical');
});

it('does not set the orientation via appearance for WebKit browsers', function test() {
if (/jsdom/.test(window.navigator.userAgent) || !/WebKit/.test(window.navigator.userAgent)) {
this.skip();
}

render(<VerticalSlider />);

const slider = screen.getByRole('slider');

expect(slider).to.have.property('tagName', 'INPUT');
expect(slider).to.have.property('type', 'range');
// Only relevant if we implement `[role="slider"]` with `input[type="range"]`
// We're not setting this by default because it changes horizontal keyboard navigation in WebKit: https://bugs.chromium.org/p/chromium/issues/detail?id=1162640
expect(slider).not.toHaveComputedStyle({ webkitAppearance: 'slider-vertical' });
});
});

describe('prop: onValueChange', () => {
function TestSlider(props: { onValueChange: () => void }) {
return (
<Slider.Root defaultValue={50} {...props}>
<Slider.Track data-testid="track">
<Slider.Thumb data-testid="thumb" />
</Slider.Track>
</Slider.Root>
);
}

it('is called when clicking on the track', () => {
const handleValueChange = spy();
render(<TestSlider onValueChange={handleValueChange} />);

const sliderTrack = screen.getByTestId('track');

stub(sliderTrack, 'getBoundingClientRect').callsFake(() => ({
width: 100,
height: 10,
bottom: 10,
left: 0,
x: 0,
y: 0,
right: 0,
top: 0,
toJSON() {},
}));

fireEvent.pointerDown(sliderTrack, {
buttons: 1,
clientX: 41,
clientY: 5,
});

expect(handleValueChange.callCount).to.equal(1);
});

it('is not called when clicking on the thumb', () => {
const handleValueChange = spy();
render(<TestSlider onValueChange={handleValueChange} />);

const sliderTrack = screen.getByTestId('track');
const sliderThumb = screen.getByTestId('thumb');

stub(sliderTrack, 'getBoundingClientRect').callsFake(() => ({
width: 100,
height: 10,
bottom: 10,
left: 0,
x: 0,
y: 0,
right: 0,
top: 0,
toJSON() {},
}));

fireEvent.pointerDown(sliderThumb, {
buttons: 1,
clientX: 51,
});

expect(handleValueChange.callCount).to.equal(0);
});
});

describe('keyboard interactions', () => {
it('should support Shift + Left Arrow / Right Arrow keys', () => {});

it('should support Shift + Up Arrow / Down Arrow keys', () => {});

it('should support PageUp / PageDown keys', () => {});

it('should support Shift + Left Arrow / Right Arrow keys by taking acount step and shiftStep', () => {});

it('should stop at max/min when using Shift + Left Arrow / Right Arrow keys', () => {});
});
});
4 changes: 4 additions & 0 deletions packages/mui-base/src/Slider2/Root/SliderRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ const SliderRoot = React.forwardRef(function SliderRoot(
forwardedRef: React.ForwardedRef<HTMLDivElement>,
) {
const {
'aria-labelledby': ariaLabelledby,
className,
defaultValue,
disabled = false,
render: renderProp,
onValueChange,
orientation,
value,
...otherProps
} = props;
Expand All @@ -31,9 +33,11 @@ const SliderRoot = React.forwardRef(function SliderRoot(
const mergedRef = useRenderPropForkRef(render, forwardedRef);

const { getRootProps, ...slider } = useSliderRoot({
'aria-labelledby': ariaLabelledby,
defaultValue,
disabled,
onValueChange,
orientation,
rootRef: mergedRef,
value,
...otherProps,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export function useSliderThumb(parameters: UseSliderThumbParameters) {
'aria-orientation': orientation,
'aria-valuemax': scale(max),
'aria-valuemin': scale(min),
'aria-valuenow': scale(thumbValue),
disabled,
name,
id: compoundItemId,
Expand Down
13 changes: 13 additions & 0 deletions packages/mui-base/src/Slider2/SliderTrack/SliderTrack.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,16 @@ import { BaseUIComponentProps } from '../../utils/BaseUI.types';
import { SliderRootOwnerState } from '../Root/SliderRoot.types';

export interface SliderTrackProps extends BaseUIComponentProps<'div', SliderRootOwnerState> {}

export interface UseSliderTrackParameters {
/**
* The ref attached to the track of the Slider.
*/
rootRef?: React.Ref<Element>;
}

export interface UseSliderTrackReturnValue {
getRootProps: (
externalProps?: React.ComponentPropsWithRef<'div'>,
) => React.ComponentPropsWithRef<'div'>;
}
3 changes: 2 additions & 1 deletion packages/mui-base/src/Slider2/SliderTrack/useSliderTrack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import { useSliderContext } from '../Root/SliderContext';
import { useEventCallback } from '../../utils/useEventCallback';
import { roundValueToStep, valueToPercent } from '../utils';
import { areValuesEqual, focusThumb, trackFinger } from '../Root/useSliderRoot';
import { UseSliderTrackParameters, UseSliderTrackReturnValue } from './SliderTrack.types';

const INTENTIONAL_DRAG_COUNT_THRESHOLD = 2;

export function useSliderTrack(parameters: any) {
export function useSliderTrack(parameters: UseSliderTrackParameters): UseSliderTrackReturnValue {
const { rootRef: externalRef } = parameters;

const trackRef = React.useRef<HTMLElement>(null);
Expand Down
2 changes: 1 addition & 1 deletion packages/mui-base/src/Slider2/index.barrel.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export { Slider } from './Root/SliderRoot';
export {
export type {
SliderRootOwnerState,
SliderRootProps,
UseSliderParameters,
Expand Down

0 comments on commit 2e53e49

Please sign in to comment.