Skip to content

Commit 9e91b73

Browse files
authored
Add tests (#88)
1 parent 6007877 commit 9e91b73

File tree

9 files changed

+276
-12
lines changed

9 files changed

+276
-12
lines changed

__tests__/assets/index.css

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
body,
2-
html {
2+
html,
3+
#app {
34
height: 100%;
45
}
56

@@ -11,9 +12,16 @@ body {
1112
margin: 0;
1213
}
1314

15+
#app {
16+
align-items: center;
17+
display: flex;
18+
justify-content: center;
19+
}
20+
1421
.react-scroll-to-bottom {
1522
background: White;
1623
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
17-
height: 640px;
24+
height: 100%;
25+
max-height: 640px;
1826
width: 360px;
1927
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title></title>
5+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
6+
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
7+
<script src="/react-scroll-to-bottom.development.js"></script>
8+
<script src="/test-harness.js"></script>
9+
<script src="/assets/page-object-model.js"></script>
10+
</head>
11+
<body>
12+
<div id="app"></div>
13+
</body>
14+
<script type="text/babel" data-presets="react">
15+
'use strict';
16+
17+
const RunFunction = ({ fn }) => {
18+
fn && fn();
19+
20+
return false;
21+
};
22+
23+
async function render(paragraphs, fn) {
24+
let called;
25+
const once =
26+
fn &&
27+
(() => {
28+
!called && fn();
29+
called = 1;
30+
});
31+
32+
await new Promise(resolve =>
33+
ReactDOM.render(
34+
<div className="react-scroll-to-bottom">
35+
{/* Set checkInterval to 1 to increase the probability of race condition. */}
36+
<ReactScrollToBottom.Composer checkInterval={1}>
37+
<ReactScrollToBottom.Panel className="scrollable">
38+
{paragraphs.map((paragraph, index) => (
39+
<div key={index}>
40+
{index + 1}: {paragraph}
41+
</div>
42+
))}
43+
</ReactScrollToBottom.Panel>
44+
<RunFunction fn={once} />
45+
</ReactScrollToBottom.Composer>
46+
</div>,
47+
document.getElementById('app'),
48+
resolve
49+
)
50+
);
51+
}
52+
53+
run(async function () {
54+
let paragraphs = pageObjects.paragraphs;
55+
56+
for (let i = 0; i < 20; i++) {
57+
// Preparation: scrollable should stay at the bottom before start.
58+
await render(paragraphs, () => ReactScrollToBottom.useScrollTo()('100%', { behavior: 'auto' }));
59+
await pageObjects.scrollStabilizedAtBottom();
60+
61+
const scrollable = document.getElementsByClassName('scrollable')[0];
62+
63+
// Call scrollTo() and wait until the first "scroll" event.
64+
const scrolling = new Promise(resolve => scrollable.addEventListener('scroll', resolve, { once: true }));
65+
66+
// Alternate between discrete and smooth scrolling.
67+
scrollable.scrollTo({ behavior: i % 2 ? 'auto' : 'smooth', top: 0 });
68+
69+
// "scroll" event dispatched after we call scrollTo(), it should be safe to append a new element.
70+
// Withou the "scroll" event, the component can't guarantee if it is safe to append, due to technical difficulties.
71+
await scrolling;
72+
73+
// Add a new paragraph.
74+
paragraphs = [...paragraphs, pageObjects.paragraphs[~~(Math.random() * pageObjects.paragraphs.length)]];
75+
76+
await render(paragraphs);
77+
78+
// After a new paragraph is added, it should continue to scroll to the top.
79+
await pageObjects.scrollStabilizedAtTop();
80+
}
81+
});
82+
</script>
83+
</html>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/** @jest-environment ./packages/test-harness/JestEnvironment */
2+
3+
// Tests for race condition will take longer time to run.
4+
jest.setTimeout(30000);
5+
6+
describe('when under heavy load', () => {
7+
test('append an element while scrolling to the top should continue to scroll', () =>
8+
runHTML('race-condition-scroll-into-view'));
9+
});
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title></title>
5+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
6+
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
7+
<script src="/react-scroll-to-bottom.development.js"></script>
8+
<script src="/test-harness.js"></script>
9+
<script src="/assets/page-object-model.js"></script>
10+
</head>
11+
<body>
12+
<div id="app"></div>
13+
</body>
14+
<script type="text/babel" data-presets="react">
15+
'use strict';
16+
17+
const Panel = () => {
18+
const [sticky] = ReactScrollToBottom.useSticky();
19+
20+
return (
21+
<ReactScrollToBottom.Panel className={`scrollable ${sticky ? 'sticky' : ''}`}>
22+
{pageObjects.paragraphs.map(paragraph => (
23+
<div key={paragraph}>
24+
<div>{paragraph}</div>
25+
{/* Adds an element with complex layout to slow down rendering. */}
26+
{new Array(1000).fill().map((_, index) => (
27+
<div key={index} />
28+
))}
29+
</div>
30+
))}
31+
</ReactScrollToBottom.Panel>
32+
);
33+
};
34+
35+
const RunFunction = ({ fn }) => {
36+
fn && fn();
37+
38+
return false;
39+
};
40+
41+
async function render(fn) {
42+
let called;
43+
const once =
44+
fn &&
45+
(() => {
46+
!called && fn();
47+
called = 1;
48+
});
49+
50+
await new Promise(resolve =>
51+
ReactDOM.render(
52+
<div className="react-scroll-to-bottom">
53+
{/* Set checkInterval to 1 to increase the probability of race condition. */}
54+
<ReactScrollToBottom.Composer checkInterval={1}>
55+
<Panel />
56+
<RunFunction fn={once} />
57+
</ReactScrollToBottom.Composer>
58+
</div>,
59+
document.getElementById('app'),
60+
resolve
61+
)
62+
);
63+
}
64+
65+
run(async function () {
66+
await render();
67+
68+
for (let i = 0; i < 20; i++) {
69+
await pageObjects.scrollStabilizedAtBottom();
70+
await became('sticky', () => document.getElementsByClassName('sticky').length, 5000);
71+
72+
if (i % 2) {
73+
document.getElementsByClassName('scrollable')[0].scrollTo({ behavior: 'auto', top: 0 });
74+
} else {
75+
document.getElementsByClassName('scrollable')[0].children[0].scrollIntoView({ behavior: 'auto' });
76+
}
77+
78+
// The race condition occurs in this place:
79+
// - When under load
80+
// - Call `scrollTo` or `scrollIntoView` with `behavior` set to `auto`, it will scroll up
81+
// - The race condition will cause it to scroll back down immediately
82+
83+
await pageObjects.scrollStabilizedAtTop();
84+
await became('not sticky', () => !document.getElementsByClassName('sticky').length, 5000);
85+
86+
await render(() => ReactScrollToBottom.useScrollTo()('100%', { behavior: 'smooth' }));
87+
}
88+
});
89+
</script>
90+
</html>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/** @jest-environment ./packages/test-harness/JestEnvironment */
2+
3+
// Tests for race condition will take longer time to run.
4+
jest.setTimeout(30000);
5+
6+
describe('when under heavy load', () => {
7+
test('calling scrollIntoView should scroll properly', () => runHTML('race-condition-scroll-into-view'));
8+
});

__tests__/resize.html

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title></title>
5+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
6+
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
7+
<script src="/react-scroll-to-bottom.development.js"></script>
8+
<script src="/test-harness.js"></script>
9+
<script src="/assets/page-object-model.js"></script>
10+
<style type="text/css">
11+
.react-scroll-to-bottom.small {
12+
height: 320px;
13+
}
14+
</style>
15+
</head>
16+
<body>
17+
<div id="app"></div>
18+
</body>
19+
<script type="text/babel" data-presets="react">
20+
'use strict';
21+
22+
function appendParagraph(paragraphs) {
23+
return [...paragraphs, pageObjects.paragraphs[~~(Math.random() * pageObjects.paragraphs.length)]];
24+
}
25+
26+
async function render(paragraphs, className) {
27+
await new Promise(resolve =>
28+
ReactDOM.render(
29+
<ReactScrollToBottom.default
30+
className={`react-scroll-to-bottom ${className || ''}`}
31+
followButtonClassName="follow"
32+
scrollViewClassName="scrollable"
33+
>
34+
{paragraphs.map((paragraph, index) => (
35+
<p key={index}>
36+
{index}: {paragraph}
37+
</p>
38+
))}
39+
</ReactScrollToBottom.default>,
40+
document.getElementById('app'),
41+
resolve
42+
)
43+
);
44+
}
45+
46+
// This is testing of 4-1-5-1-1 on the test page. It should not lose stickiness.
47+
run(async function () {
48+
let paragraphs = pageObjects.paragraphs;
49+
50+
await render(paragraphs);
51+
52+
await render(paragraphs, 'small');
53+
await pageObjects.scrollStabilizedAtBottom();
54+
55+
paragraphs = appendParagraph(paragraphs);
56+
await render(paragraphs, 'small');
57+
await pageObjects.scrollStabilizedAtBottom();
58+
59+
await render(paragraphs);
60+
await pageObjects.scrollStabilizedAtBottom();
61+
62+
paragraphs = appendParagraph(paragraphs);
63+
await render(paragraphs);
64+
await pageObjects.scrollStabilizedAtBottom();
65+
66+
paragraphs = appendParagraph(paragraphs);
67+
await render(paragraphs);
68+
await pageObjects.scrollStabilizedAtBottom();
69+
});
70+
</script>
71+
</html>

__tests__/resize.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/** @jest-environment ./packages/test-harness/JestEnvironment */
2+
3+
test('resizing should not lose stickiness', () => runHTML('resize'));

packages/test-harness/src/browser/assertions/became.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
import sleep from '../../common/utils/sleep';
2-
3-
const CHECK_INTERVAL = 100;
4-
51
export default async function became(message, fn, timeout) {
62
if (typeof timeout !== 'number') {
73
throw new Error('"timeout" argument must be set.');
@@ -12,7 +8,7 @@ export default async function became(message, fn, timeout) {
128
return;
139
}
1410

15-
await sleep(CHECK_INTERVAL);
11+
await new Promise(requestAnimationFrame);
1612
}
1713

1814
throw new Error(`Timed out while waiting for page condition "${message}" after ${timeout / 1000} seconds.`);

packages/test-harness/src/browser/assertions/stabilized.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
11
import became from './became';
2-
import sleep from '../../common/utils/sleep';
3-
4-
// 17 ms is a frame on a display with 60 Hz refresh rate.
5-
const WAIT_INTERVAL = 17;
62

73
export default function stabilized(name, getValue, count, timeout) {
84
const values = [];
@@ -26,7 +22,7 @@ export default function stabilized(name, getValue, count, timeout) {
2622
}
2723

2824
// If not, sleep for a frame and check again.
29-
await sleep(WAIT_INTERVAL);
25+
await new Promise(requestAnimationFrame);
3026

3127
return false;
3228
},

0 commit comments

Comments
 (0)