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: added slider tests #126

Merged
merged 13 commits into from
Nov 2, 2020
Merged
8 changes: 7 additions & 1 deletion babel-config.js → babel.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const BABEL_ENV = process.env.BABEL_ENV;
const isCommonJS = BABEL_ENV !== undefined && BABEL_ENV === "cjs";
const isESM = BABEL_ENV !== undefined && BABEL_ENV === "esm";
const isBuild = !!BABEL_ENV;

module.exports = function (api) {
api.cache(true);
Expand Down Expand Up @@ -29,6 +30,11 @@ module.exports = function (api) {
return {
presets,
plugins,
ignore: ["**/*/__tests__", "**/*/stories"],
env: {
test: {
presets: [["@babel/env", { targets: { node: "current" } }]],
},
},
ignore: isBuild ? ["**/*/__tests__", "**/*/stories"] : [],
};
};
8 changes: 6 additions & 2 deletions jest.config.js → jest.config.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
/*
* For a detailed explanation regarding each configuration property and type check, visit:
* https://jestjs.io/docs/en/configuration.html
*/
const { join } = require("path");
const pkg = require("./package.json");

module.exports = {
export default {
rootDir: __dirname,
displayName: pkg.name,
testMatch: [join(__dirname, "src/**/*.test.{js,ts,tsx}")],
setupFilesAfterEnv: ["<rootDir>/jest.setup.js"],
preset: "ts-jest",
moduleNameMapper: {
"\\.(css|less|sass|scss)$": "<rootDir>/src/__mocks__/styleMock.js",
},
Expand All @@ -17,4 +20,5 @@ module.exports = {
"<rootDir>/src/meter/__examples__/index.ts",
"<rootDir>/src/meter/__examples__/__tests__/statehook-test-data.ts",
],
clearMocks: true,
};
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
"prebuild": "rimraf dist",
"build": "concurrently yarn:build:*",
"build-storybook": "build-storybook --no-dll",
"build:cjs": "cross-env BABEL_ENV=cjs babel src --extensions .ts,.tsx --config-file ./babel-config.js -d dist/cjs --source-maps",
"build:esm": "cross-env BABEL_ENV=esm babel src --extensions .ts,.tsx --config-file ./babel-config.js -d dist/esm --source-maps",
"build:cjs": "cross-env BABEL_ENV=cjs babel src --extensions .ts,.tsx -d dist/cjs --source-maps",
"build:esm": "cross-env BABEL_ENV=esm babel src --extensions .ts,.tsx -d dist/esm --source-maps",
"build:types": "tsc --emitDeclarationOnly",
"check-types": "tsc --noEmit",
"commit": "gacp",
Expand All @@ -42,7 +42,7 @@
"release:github": "conventional-github-releaser -p angular",
"release:tags": "git push --follow-tags origin master",
"storybook": "start-storybook -p 6006 --no-dll",
"test": "jest"
"test": "jest --config ./jest.config.ts --no-cache"
},
"dependencies": {
"@chakra-ui/counter": "1.0.0-rc.7",
Expand Down Expand Up @@ -86,6 +86,7 @@
"@typescript-eslint/eslint-plugin": "4.6.0",
"@typescript-eslint/parser": "4.6.0",
"babel-eslint": "10.1.0",
"babel-jest": "^26.6.1",
"babel-loader": "^8.1.0",
"babel-plugin-chakra-ui": "1.0.0-rc.7",
"babel-plugin-date-fns": "^2.0.0",
Expand Down Expand Up @@ -128,6 +129,7 @@
"storybook-addon-preview": "^2.0.1",
"ts-jest": "26.4.3",
"ts-morph": "8.1.2",
"ts-node": "^9.0.0",
"typescript": "4.0.5"
},
"peerDependencies": {
Expand Down
182 changes: 182 additions & 0 deletions src/slider/__tests__/Slider.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
// IMPORTANT Reference:
// https://github.com/adobe/react-spectrum/blob/main/packages/%40react-aria/slider/test/useSliderThumb.test.js

/**

NOTES on Testing slider component.

### The Error:
TypeError : Class constructor MouseEvent cannot be invoed with "new"
https://github.com/kulshekhar/ts-jest/issues/571#issuecomment-719352005


## Why this error is happening:
https://stackoverflow.com/questions/51860043/javascript-es6-typeerror-class-constructor-client-cannot-be-invoked-without-ne


### Possible Solutions:
Accordion to some github issues setting target: "ES2015" should fix the issues but did not worked in this project for some reason


## Solution:
Adding env preset in the babel config and setting the targets to node: "current" seems to be fixing the issue,
note that we are only setting this on test env.
```js
env: {
test: {
presets: [["@babel/env", { targets: { node: "current" } }]],
},
},
```

Now our project had custom babel-(DASH)-config.js file because of storybook config, but jest won't pick that file up
So i had to rename the file to babel.config.js which seems to be working

Along the way i also stumbed upon this bug too: https://github.com/facebook/jest/issues/9292
This possibly was because of wrong jest config.
*/

import React from "react";
import { VisuallyHidden } from "reakit";
import { axe, render, press, fireEvent } from "reakit-test-utils";
import {
SliderTrack,
SliderThumb,
SliderInput,
useSliderState,
} from "../index";
import { SliderInitialState } from "../SliderState";

import { installMouseEvent } from "../../utils/test-utils";

export const SliderComponent = (props: SliderInitialState) => {
const state = useSliderState(props);
const {
values,
getValuePercent,
getThumbPercent,
getThumbValueLabel,
} = state;

const trackWidth = `${
(getValuePercent(Math.max(values[0], state.min)) -
getValuePercent(Math.min(values[0], state.min))) *
100
}%`;
const trackLeft = `${getValuePercent(Math.min(values[0], state.min)) * 100}%`;
const labelValue = getThumbValueLabel(0);

return (
<div
role="group"
className="chakra-slider-group"
aria-label="styled-slider"
>
<div className="slider-label">
<label className="label" htmlFor="styled-slider">
Minimal slider
</label>
<div data-testid="slider-value" className="value">
{labelValue}
</div>
</div>

<div className={`slider`}>
<SliderTrack {...state} className="slider-track-container">
<div className="slider-track" />
<div
className="slider-filled-track"
style={{
width: trackWidth,
left: trackLeft,
}}
/>
</SliderTrack>
<div
className="slider-thumb"
style={{
top: "26px",
left: `calc(${getThumbPercent(0) * 100}% - 7px)`,
}}
>
<SliderThumb
className="slider-thumb-handle"
data-testid="slider-thumb"
index={0}
{...state}
>
<VisuallyHidden>
<SliderInput
index={0}
id="styled-slider"
aria-label={`Thumb-${0}`}
{...state}
/>
</VisuallyHidden>
</SliderThumb>
</div>
</div>
</div>
);
};

describe("Slider", () => {
// IMPORTANT!
// We need to mock HTMLElement.offsetWidth & offsetHeight,
// since without them we cannot click on a target with specific clientX/pageX
let widthStub: jest.SpyInstance<number, []>,
heightStub: jest.SpyInstance<number, []>;
beforeAll(() => {
widthStub = jest
.spyOn(window.HTMLElement.prototype, "offsetWidth", "get")
.mockImplementation(() => 100);
heightStub = jest
.spyOn(window.HTMLElement.prototype, "offsetHeight", "get")
.mockImplementation(() => 100);
});
afterAll(() => {
widthStub.mockReset();
heightStub.mockReset();
});

// Now let's mock the mouse event
installMouseEvent();

// installPointerEvent();
it("should drag and change slider value", () => {
const onStart = jest.fn();
const onEnd = jest.fn();
const { getByTestId: testId } = render(
<SliderComponent
onChangeStart={onStart}
onChangeEnd={onEnd}
min={0}
max={100}
step={1}
/>,
);

const sliderValue = testId("slider-value");
const sliderThumb = testId("slider-thumb");

expect(sliderValue).toHaveTextContent("50");

fireEvent.mouseDown(sliderThumb, { clientX: 10, pageX: 10 });
expect(onStart).toHaveBeenLastCalledWith([50]);
expect(onEnd).not.toHaveBeenCalled();

fireEvent.mouseMove(sliderThumb, { clientX: 20, pageX: 20 });
expect(onEnd).not.toHaveBeenCalled();
expect(sliderValue).toHaveTextContent("60");

fireEvent.mouseMove(sliderThumb, { clientX: 30, pageX: 30 });
expect(onEnd).not.toHaveBeenCalled();
expect(sliderValue).toHaveTextContent("70");

fireEvent.mouseMove(sliderThumb, { clientX: 40, pageX: 40 });
fireEvent.mouseUp(sliderThumb, { clientX: 40, pageX: 40 });
expect(onStart).toHaveBeenLastCalledWith([50]);
expect(onEnd).toHaveBeenLastCalledWith([80]);
expect(sliderValue).toHaveTextContent("80");
});
});
3 changes: 2 additions & 1 deletion src/slider/stories/Slider.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,10 @@ const Base: Story<ChakraSliderProps> = args => {
aria-labelledby="styled-slider"
anuraghazra marked this conversation as resolved.
Show resolved Hide resolved
>
<div className="slider-label">
<label className="label" id="styled-slider">
<label className="label">
{`${args.label ? args.label : "Styled"} Slider`}
</label>

<div className="value">
{!isMulti ? labelValue : JSON.stringify(state.values)}
</div>
Expand Down
63 changes: 63 additions & 0 deletions src/utils/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,66 @@ export const repeat = (cb: Function, times: number) => {
cb();
}
};

/**
* Enables reading pageX/pageY from fireEvent.mouse*(..., {pageX: ..., pageY: ...}).
*/
export function installMouseEvent() {
beforeAll(() => {
const oldMouseEvent = MouseEvent;
// @ts-ignore
global.MouseEvent = class FakeMouseEvent extends MouseEvent {
_init: { pageX: number; pageY: number };
constructor(name: any, init: any) {
super(name, init);
this._init = init;
}
get pageX() {
return this._init.pageX;
}
get pageY() {
return this._init.pageY;
}
};
// @ts-ignore
global.MouseEvent.oldMouseEvent = oldMouseEvent;
});
afterAll(() => {
// @ts-ignore
global.MouseEvent = global.MouseEvent.oldMouseEvent;
});
}

export function installPointerEvent() {
beforeAll(() => {
// @ts-ignore
global.PointerEvent = class FakePointerEvent extends MouseEvent {
_init: {
pageX: number;
pageY: number;
pointerType: string;
pointerId: number;
};
constructor(name: any, init: any) {
super(name, init);
this._init = init;
}
get pointerType() {
return this._init.pointerType;
}
get pointerId() {
return this._init.pointerId;
}
get pageX() {
return this._init.pageX;
}
get pageY() {
return this._init.pageY;
}
};
});
afterAll(() => {
// @ts-ignore
delete global.PointerEvent;
});
}