Search / tests /test_reader_heading_cache.js
vomebook
refactor: organize project layout and Reader responsibilities
2c1d035
Raw History Blame Contribute Delete
3.04 kB
const fs = require('fs');
const vm = require('vm');
const assert = require('assert');
const path = require('path');
const root = process.argv[2] ? path.resolve(process.argv[2]) : path.resolve(__dirname, '..');
const source = require('./reader_source_contract').readReaderSource(root);
const helper = source.match(/function createHeadingLocationCache\(root, entries\) \{[\s\S]*?\n\}/)[0];
const observers = [], listeners = new Map();
class Observer {
constructor(callback) { this.callback = callback; this.records = []; this.closed = false; observers.push(this); }
observe() {}
disconnect() { this.closed = true; }
takeRecords() { return this.records.splice(0); }
}
const add=(name,fn)=>{if(!listeners.has(name))listeners.set(name,new Set());listeners.get(name).add(fn);};
const remove=(name,fn)=>{listeners.get(name)?.delete(fn);if(!listeners.get(name)?.size)listeners.delete(name);};
const region = {clientWidth:400, scrollHeight:10000,
addEventListener:add,removeEventListener:remove,
ownerDocument:{defaultView:{getComputedStyle:node=>({position:node.position||'static'})},
fonts:{addEventListener:add,removeEventListener:remove}}};
const context = {MutationObserver:Observer, ResizeObserver:Observer, documentState:{zoom:100}};
vm.runInNewContext(helper, context);
let reads = 0, scroll = 0;
const entries = Array.from({length:1000}, (_,index)=>({target:{isConnected:true,
getBoundingClientRect:()=>{reads++;return {top:index*10-scroll};}}}));
const cache = context.createHeadingLocationCache(region, entries);
assert.equal(cache.at(8,scroll),0);
assert.equal(reads,1000);
reads=0;
for(let index=1;index<100;index++){scroll=index*10;assert.equal(cache.at(8,scroll),index);}
assert.equal(reads,0);
entries[99].target={isConnected:true,getBoundingClientRect:()=>{reads++;return {top:1005-scroll};}};
assert.equal(cache.at(8,scroll),98);
assert.equal(reads,1000);
reads=0;entries[98].target.isConnected=false;
assert.equal(cache.at(8,scroll),97);
assert.equal(reads,999);
const unordered=[entries[500],entries[10],entries[200],entries[200]];
const alternative=context.createHeadingLocationCache(region,unordered);
scroll=2000;
assert.equal(alternative.at(0,scroll),3);
scroll=0;assert.equal(alternative.at(0,scroll),1);
for(const change of [()=>observers[0].records.push({}),()=>listeners.get('loadingdone').forEach(fn=>fn()),
()=>region.clientWidth++,()=>context.documentState.zoom++]) {
reads=0;change();cache.at(8,scroll);assert.equal(reads,999);
}
cache.dispose();alternative.dispose();
const pinned=context.createHeadingLocationCache(region,[
{target:{isConnected:true,position:'fixed',getBoundingClientRect:()=>({top:0})}},
{target:{isConnected:true,getBoundingClientRect:()=>({top:500-scroll})}}]);
scroll=0;assert.equal(pinned.at(8,scroll),0);
scroll=510;assert.equal(pinned.at(8,scroll),0);
pinned.dispose();
assert.ok(observers.every(observer=>observer.closed));
assert.equal(listeners.size,0);
console.log('heading geometry reuse, unordered ties, mutation/layout invalidation and cleanup passed');