Commit 37e9b424 authored by Lyza Danger Gardner's avatar Lyza Danger Gardner

Add utility for calculating visible threads

parent 36d6ce94
import { calculateVisibleThreads } from '../visible-threads';
describe('sidebar/util/visible-threads', () => {
let fakeThreads;
let fakeThreadHeights;
let fakeWindowHeight;
let fakeDefaultDimensions;
beforeEach(() => {
fakeThreads = [
{ id: 't1' },
{ id: 't2' },
{ id: 't3' },
{ id: 't4' },
{ id: 't5' },
{ id: 't6' },
{ id: 't7' },
{ id: 't8' },
{ id: 't9' },
{ id: 't10' },
];
fakeThreadHeights = {};
fakeWindowHeight = 100;
fakeDefaultDimensions = {
defaultHeight: 200,
marginAbove: 800,
marginBelow: 800,
};
});
describe('calculateVisibleThreads', () => {
it('There should be no `offscreenUpperHeight` if `scrollpos` is 0', () => {
// No threads will be rendered above the viewport when scrollpos is 0
const calculated = calculateVisibleThreads(
fakeThreads,
fakeThreadHeights,
0, // scrollpos
fakeWindowHeight,
fakeDefaultDimensions
);
assert.equal(calculated.offscreenUpperHeight, 0);
});
it('should calculate visible threads when at 0 scrollpos', () => {
// Default margins above and below are 800px each; default height of each thread: 200px
const calculated = calculateVisibleThreads(
fakeThreads,
fakeThreadHeights,
0,
200,
fakeDefaultDimensions
);
const visibleIds = calculated.visibleThreads.map(thread => thread.id);
// There are 10 threads, each taking up an estimated 200px of space. There
// is no "above the viewport" space because the scroll is already at the
// top of the viewport. Thus, visible threads should be the one that takes
// up the 200px of the window height itself, plus 800 margin below / 200
// (4 additional threads): thus, the first 5 threads should be considered
// "visible."
assert.deepEqual(visibleIds, ['t1', 't2', 't3', 't4', 't5']);
// The space offscreen below should be the remaining 5 threads at their
// estimated 200px heights:
assert.equal(calculated.offscreenLowerHeight, 1000);
});
it('should calculate visible threads when at non-0 scrollpos', () => {
const calculated = calculateVisibleThreads(
fakeThreads,
fakeThreadHeights,
1200, // scrollPos
100,
fakeDefaultDimensions
);
const visibleIds = calculated.visibleThreads.map(thread => thread.id);
// The very first thread has "scrolled above" the viewport + margin
assert.deepEqual(visibleIds, [
't2',
't3',
't4',
't5',
't6',
't7',
't8',
't9',
't10',
]);
// That first thread's space...
assert.equal(calculated.offscreenUpperHeight, 200);
// The rest are rendered within viewport + lower margin, so:
assert.equal(calculated.offscreenLowerHeight, 0);
});
describe('calculating visible threads without margins', () => {
beforeEach(() => {
fakeDefaultDimensions = {
defaultHeight: 100,
marginAbove: 0,
marginBelow: 0,
};
});
[
{
scrollPos: 0,
windowHeight: 1000,
expectedVisibleThreadIds: [
't1',
't2',
't3',
't4',
't5',
't6',
't7',
't8',
't9',
't10',
],
offscreenUpperHeight: 0,
offscreenLowerHeight: 0,
},
{
scrollPos: 0,
windowHeight: 100,
expectedVisibleThreadIds: ['t1'],
offscreenUpperHeight: 0,
offscreenLowerHeight: 900,
},
{
scrollPos: 101,
windowHeight: 199,
expectedVisibleThreadIds: ['t2', 't3'],
offscreenUpperHeight: 100,
offscreenLowerHeight: 700,
},
].forEach(testCase => {
it('should calculate the threads that would fit in the viewport', () => {
const calculated = calculateVisibleThreads(
fakeThreads,
fakeThreadHeights,
testCase.scrollPos,
testCase.windowHeight,
fakeDefaultDimensions
);
const visibleIds = calculated.visibleThreads.map(thread => thread.id);
assert.deepEqual(visibleIds, testCase.expectedVisibleThreadIds);
assert.equal(
calculated.offscreenUpperHeight,
testCase.offscreenUpperHeight
);
assert.equal(
calculated.offscreenLowerHeight,
testCase.offscreenLowerHeight
);
});
});
});
});
});
export const THREAD_DIMENSION_DEFAULTS = {
// When we don't have a real measurement of a thread card's height (yet)
// from the browser, use this as an approximate value, in pixels.
defaultHeight: 200,
// Space above the viewport in pixels which should be considered 'on-screen'
// when calculating the set of visible threads
marginAbove: 800,
// Same as MARGIN_ABOVE but for the space below the viewport
marginBelow: 800,
};
/**
* Calculate the set of `ThreadCard`s that should be rendered by
* estimating which of the threads are within or near the viewport.
*
* @param {Thread[]} threads - List of threads in the order they appear
* @param {Object} threadHeights - Map of thread ID to measured height
* @param {number} scrollPos - Vertical scroll offset of scrollable container
* @param {number} windowHeight -
* Height of the visible area of the scrollable container.
* @param {Object} options - Dimensional overrides (in px) for defaults
*/
export function calculateVisibleThreads(
threads,
threadHeights,
scrollPos,
windowHeight,
options = THREAD_DIMENSION_DEFAULTS
) {
const { defaultHeight, marginAbove, marginBelow } = options;
const visibleThreads = [];
// Total height used up by the top-level thread cards
let totalHeight = 0;
// Estimated height, in px, of the thread cards above and below the viewport
let offscreenUpperHeight = 0;
let offscreenLowerHeight = 0;
threads.forEach(thread => {
const threadHeight = threadHeights[thread.id] || defaultHeight;
const threadIsAboveViewport =
totalHeight + threadHeight < scrollPos - marginAbove;
const threadIsVisible =
totalHeight < scrollPos + windowHeight + marginBelow;
if (threadIsAboveViewport) {
offscreenUpperHeight += threadHeight;
} else if (threadIsVisible) {
visibleThreads.push(thread);
} else {
// thread is below visible viewport
offscreenLowerHeight += threadHeight;
}
totalHeight += threadHeight;
});
return {
visibleThreads,
offscreenUpperHeight,
offscreenLowerHeight,
};
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment