forked from mui/base-ui
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
f95c8a8
commit 2e53e49
Showing
6 changed files
with
348 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', () => {}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters