Skip to content

Commit fb6f9d0

Browse files
committed
writes unit tests with Jest and Enzyme, #1
1 parent 33b3da4 commit fb6f9d0

File tree

9 files changed

+2318
-232
lines changed

9 files changed

+2318
-232
lines changed

dist/build.bundle.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

jest.config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module.exports = {
2+
verbose: true,
3+
setupFilesAfterEnv: ['./src/index.test.js'],
4+
testPathIgnorePatterns: ['./src/index.test.js'],
5+
};

package.json

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@trbl/react-scroll-info",
3-
"version": "0.0.16",
3+
"version": "1.0.0",
44
"main": "dist/build.bundle.js",
55
"repository": "git@github.com:trouble/react-scroll-info.git",
66
"description": "A roll, as of parchment or papyrus, used especially in the writing of a document.",
@@ -15,10 +15,10 @@
1515
"ux"
1616
],
1717
"scripts": {
18-
"build": "yarn run lint && webpack --config webpack.production.config.js",
18+
"build": "yarn lint && yarn test && webpack --config webpack.production.config.js",
1919
"dev": "webpack-dev-server --hot --inline --config webpack.development.config.js",
2020
"lint": "eslint .",
21-
"test": "echo \"No test specified\""
21+
"test": "jest"
2222
},
2323
"peerDependencies": {
2424
"react": "^16.8.0"
@@ -33,16 +33,21 @@
3333
"@babel/plugin-proposal-class-properties": "^7.5.5",
3434
"@babel/preset-env": "^7.7.7",
3535
"@babel/preset-react": "^7.0.0",
36-
"@trbl/eslint-config": "^1.1.4",
36+
"@trbl/eslint-config": "1.2.1",
3737
"babel-eslint": "^10.0.3",
3838
"babel-loader": "^8.0.6",
39-
"eslint": "^5.16.0",
39+
"enzyme": "^3.11.0",
40+
"enzyme-adapter-react-16": "^1.15.2",
41+
"eslint": "^6.8.0",
4042
"eslint-loader": "^3.0.3",
41-
"eslint-plugin-import": "^2.17.2",
42-
"eslint-plugin-jsx-a11y": "^6.2.1",
43-
"eslint-plugin-react": "^7.12.4",
43+
"eslint-plugin-import": "^2.19.1",
44+
"eslint-plugin-jest": "^23.3.0",
45+
"eslint-plugin-jest-dom": "^1.4.2",
46+
"eslint-plugin-jsx-a11y": "^6.2.3",
47+
"eslint-plugin-react": "^7.17.0",
4448
"eslint-plugin-react-hooks": "^2.3.0",
4549
"html-webpack-plugin": "^3.2.0",
50+
"jest": "^24.9.0",
4651
"react-hot-loader": "^4.12.14",
4752
"webpack": "^4.41.4",
4853
"webpack-cli": "^3.3.9",
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import React from 'react';
2+
import { mount } from 'enzyme';
3+
4+
import ScrollInfoProvider from '.';
5+
6+
describe('ScrollInfoProvider with a cached scroll', () => {
7+
// To simulate the behavior of Chrome, the window.scrollTo method needs to be called twice,
8+
// once before mount and then once after -- see comment on lines 55-60 of ScrollInfoProvider.
9+
window.scrollTo(534, 390);
10+
11+
const wrapper = mount(
12+
<ScrollInfoProvider />,
13+
);
14+
15+
window.scrollTo(534, 390);
16+
17+
it('rendered with an initial state of correct shape and value', () => {
18+
const state = wrapper.state();
19+
20+
expect(state).toMatchObject({
21+
x: 534,
22+
y: 390,
23+
xDifference: 534,
24+
yDifference: 390,
25+
xDirection: 'right',
26+
yDirection: 'down',
27+
xPercentage: 36.60041124057574,
28+
yPercentage: 9.215500945179585,
29+
totalPercentage: 22.90795609287766,
30+
eventsFired: 1,
31+
});
32+
});
33+
});

src/ScrollInfoProvider/index.js

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,12 @@ class ScrollInfoProvider extends Component {
5050
hasScrolled: prevHasScrolled,
5151
} = this.state;
5252

53-
// Set to zero on first render and mount for cross-browser compatibility --
53+
// Set currentScroll to zero on mount for cross-browser compatibility --
5454
// Some browsers populate the cached window.pageOffset at different points of the component lifecycle.
55-
// Chrome mounts with the cached window.pageOffset, while Safari and FireFox both only
56-
// populate it on first scroll event -- which is triggered by the browser in some cases.
57-
// The presence of a timestamp indicates that it wasn't first render or first mount,
58-
// but a true requestAnimationFrame scroll event. Keep at zero otherwise.
55+
// Chrome mounts with cached window.pageOffset coordinates even before firing the respective cached scroll event.
56+
// Neither Safari and FireFox populate these coordinates until this cached scroll (natively triggered by the browser).
57+
// The presence of a timestamp indicates that the caller of this method was not componentDidMount,
58+
// but rather a true scroll event via requestAnimationFrame. Keep at zero otherwise.
5959
const hasScrolled = prevHasScrolled || Boolean(timestamp);
6060
const currentScrollX = hasScrolled ? window.pageXOffset : 0;
6161
const currentScrollY = hasScrolled ? window.pageYOffset : 0;
@@ -69,9 +69,8 @@ class ScrollInfoProvider extends Component {
6969
const yPercentage = (currentScrollY / (document.body.scrollHeight - window.innerHeight)) * 100;
7070
const totalPercentage = (xPercentage + yPercentage) / 2;
7171

72-
/* eslint-disable no-nested-ternary */
73-
const xDirection = xDifference > 0 ? 'right' : xDifference < 0 ? 'left' : prevXDirection;
74-
const yDirection = yDifference > 0 ? 'down' : yDifference < 0 ? 'up' : prevYDirection;
72+
const xDirection = xDifference > 0 ? 'right' : xDifference < 0 ? 'left' : prevXDirection; // eslint-disable-line no-nested-ternary
73+
const yDirection = yDifference > 0 ? 'down' : yDifference < 0 ? 'up' : prevYDirection; // eslint-disable-line no-nested-ternary
7574

7675
this.setState({
7776
x: currentScrollX,
@@ -97,16 +96,18 @@ class ScrollInfoProvider extends Component {
9796

9897
return (
9998
<ScrollInfoContext.Provider value={{ scrollInfo }}>
100-
{children}
99+
{children && children}
101100
</ScrollInfoContext.Provider>
102101
);
103102
}
104103
}
105104

106-
ScrollInfoProvider.defaultProps = {};
105+
ScrollInfoProvider.defaultProps = {
106+
children: undefined,
107+
};
107108

108109
ScrollInfoProvider.propTypes = {
109-
children: PropTypes.node.isRequired,
110+
children: PropTypes.node,
110111
};
111112

112113
export default ScrollInfoProvider;
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import React from 'react';
2+
import { mount } from 'enzyme';
3+
4+
import ScrollInfoProvider from '.';
5+
6+
describe('ScrollInfoProvider', () => {
7+
const wrapper = mount(
8+
<ScrollInfoProvider />,
9+
);
10+
11+
it('rendered with an initial state of correct shape and value', () => {
12+
const state = wrapper.state();
13+
14+
expect(state).toMatchObject({
15+
x: 0,
16+
y: 0,
17+
xDifference: 0,
18+
yDifference: 0,
19+
xDirection: '',
20+
yDirection: '',
21+
xPercentage: 0,
22+
yPercentage: 0,
23+
totalPercentage: 0,
24+
eventsFired: 0,
25+
});
26+
});
27+
28+
it('responded to a window scroll event with an internal state update', () => {
29+
window.scrollTo(534, 390);
30+
const state = wrapper.state();
31+
32+
expect(state).toMatchObject({
33+
x: 534,
34+
y: 390,
35+
xDifference: 534,
36+
yDifference: 390,
37+
xDirection: 'right',
38+
yDirection: 'down',
39+
xPercentage: 36.60041124057574,
40+
yPercentage: 9.215500945179585,
41+
totalPercentage: 22.90795609287766,
42+
eventsFired: 1,
43+
hasScrolled: true,
44+
animationScheduled: false,
45+
});
46+
});
47+
});

src/index.test.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { configure } from 'enzyme';
2+
import Adapter from 'enzyme-adapter-react-16';
3+
4+
configure({ adapter: new Adapter() });
5+
6+
window.scrollTo = function scrollTo(xCoord, yCoord) {
7+
Object.assign(this, {
8+
pageXOffset: xCoord,
9+
pageYOffset: yCoord,
10+
}).dispatchEvent(new this.Event('scroll'));
11+
};
12+
13+
window.requestAnimationFrame = (callback) => {
14+
const timestamp = performance.now();
15+
callback(timestamp);
16+
};
17+
18+
const addSettersToBody = (properties) => {
19+
if (properties && properties.length > 0) {
20+
properties.forEach((property) => {
21+
Object.defineProperty(window.document.body, property, {
22+
writable: true,
23+
set property(value) {
24+
this[property] = value;
25+
},
26+
});
27+
});
28+
}
29+
};
30+
31+
addSettersToBody(['scrollWidth', 'scrollHeight']);
32+
33+
window.document.body.scrollWidth = 2483;
34+
window.document.body.scrollHeight = 5000;

src/withScrollInfo/index.test.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import React from 'react';
2+
import { shallow } from 'enzyme';
3+
4+
import ScrollInfoProvider from '../ScrollInfoProvider';
5+
import withScrollInfo from '.';
6+
7+
describe('withScrollInfo', () => {
8+
const WithScrollInfo = withScrollInfo(() => (
9+
<code>
10+
Hello, world!
11+
</code>
12+
));
13+
14+
// Note: when .props() is called on a shallow wrapper, the returned values will be
15+
// of the root node that the wrapper component renders — not the component itself.
16+
// See https://airbnb.io/enzyme/docs/api/ShallowWrapper/props.html
17+
const wrapper = shallow(
18+
<ScrollInfoProvider>
19+
<WithScrollInfo />
20+
</ScrollInfoProvider>,
21+
);
22+
23+
it('rendered with an initial state of correct shape and value', () => {
24+
const { value: { scrollInfo } } = wrapper.props();
25+
26+
expect(scrollInfo).toMatchObject({
27+
x: 0,
28+
y: 0,
29+
xDifference: 0,
30+
yDifference: 0,
31+
xDirection: '',
32+
yDirection: '',
33+
xPercentage: 0,
34+
yPercentage: 0,
35+
totalPercentage: 0,
36+
eventsFired: 0,
37+
});
38+
});
39+
40+
it('received an updated scrollInfo prop after a window scroll event', () => {
41+
window.scrollTo(233, 325);
42+
const { value: { scrollInfo } } = wrapper.props();
43+
44+
expect(scrollInfo).toMatchObject({
45+
x: 233,
46+
y: 325,
47+
xDifference: 233,
48+
yDifference: 325,
49+
xDirection: 'right',
50+
yDirection: 'down',
51+
xPercentage: 15.969842357779301,
52+
yPercentage: 7.6795841209829865,
53+
totalPercentage: 11.824713239381143,
54+
eventsFired: 1,
55+
});
56+
});
57+
});

0 commit comments

Comments
 (0)