@elibrary-inno/bookreader
Version:
The Internet Archive BookReader.
373 lines (321 loc) • 12.3 kB
JavaScript
import sinon from 'sinon';
import { deepCopy } from '../utils.js';
import { BookModel } from '@/src/BookReader/BookModel.js';
import { NAMED_REDUCE_SETS } from '@/src/BookReader/ReduceSet.js';
/** @typedef {import('@/src/BookReader/options.js').BookReaderOptions} BookReaderOptions */
afterEach(() => {
sinon.restore();
});
/** @type {BookReaderOptions['data']} */
const SAMPLE_DATA = [
[
{ width: 123, height: 123, uri: 'https://archive.org/image0.jpg', pageNum: '1' },
],
[
{ width: 123, height: 123, uri: 'https://archive.org/image1.jpg', pageNum: '2' },
{ width: 123, height: 123, uri: 'https://archive.org/image2.jpg', pageNum: '3' },
],
[
{ width: 123, height: 123, uri: 'https://archive.org/image3.jpg', pageNum: '4' },
],
];
describe('getMedianPageSizeInches', () => {
test('handles single page data', () => {
const bm = new BookModel({ data: SAMPLE_DATA.slice(0, 1), options: {ppi: 10} });
expect(bm.getMedianPageSizeInches()).toEqual({ width: 12.3, height: 12.3 });
});
test('handles odd pages data', () => {
const sizes = [
{width: 300, height: 2200},
{width: 200, height: 2100},
{width: 100, height: 2300},
];
const data = deepCopy(SAMPLE_DATA);
delete data[2];
Object.assign(data[0][0], sizes[0]);
Object.assign(data[1][0], sizes[1]);
Object.assign(data[1][1], sizes[2]);
const bm = new BookModel({ data, options: {ppi: 10} });
expect(bm.getMedianPageSizeInches()).toEqual({ width: 20, height: 220 });
});
test('handles even pages data', () => {
const sizes = [
{width: 300, height: 2200},
{width: 200, height: 2100},
{width: 100, height: 2300},
{width: 400, height: 2400},
];
const data = deepCopy(SAMPLE_DATA);
Object.assign(data[0][0], sizes[0]);
Object.assign(data[1][0], sizes[1]);
Object.assign(data[1][1], sizes[2]);
Object.assign(data[2][0], sizes[3]);
const bm = new BookModel({ data, options: {ppi: 10} });
expect(bm.getMedianPageSizeInches()).toEqual({ width: 30, height: 230 });
});
test('does not lexicographic sort for median', () => {
const sizes = [
{width: 100, height: 100},
{width: 20, height: 20},
{width: 30, height: 30},
];
const data = deepCopy(SAMPLE_DATA);
delete data[2];
Object.assign(data[0][0], sizes[0]);
Object.assign(data[1][0], sizes[1]);
Object.assign(data[1][1], sizes[2]);
const bm = new BookModel({ data, options: {ppi: 10} });
expect(bm.getMedianPageSizeInches()).toEqual({ width: 3, height: 3 });
});
test('caches result', () => {
const bm = new BookModel({ data: SAMPLE_DATA });
const firstResult = bm.getMedianPageSizeInches();
expect(bm.getMedianPageSizeInches()).toBe(firstResult);
expect(bm.getMedianPageSizeInches()).toBe(firstResult);
expect(bm.getMedianPageSizeInches()).toBe(firstResult);
});
});
describe('getPageIndices', () => {
test('handles unique indices', () => {
const bm = new BookModel({ data: SAMPLE_DATA });
expect(bm.getPageIndices('1')).toEqual([0]);
});
test('handles invalid indices', () => {
const bm = new BookModel({ data: SAMPLE_DATA });
expect(bm.getPageIndices('foo')).toEqual([]);
expect(bm.getPageIndices('123')).toEqual([]);
});
test('handles non-unique indices', () => {
const data = deepCopy(SAMPLE_DATA);
data[0][0].pageNum = '2';
const bm = new BookModel({ data });
expect(bm.getPageIndices('2')).toEqual([0, 1]);
});
test('handles n-prefixed pages', () => {
const data = deepCopy(SAMPLE_DATA);
data[0][0].pageNum = 'n0';
const bm = new BookModel({ data });
expect(bm.getPageIndices('n0')).toEqual([0]);
});
});
test('getPageName', () => {
const bm = new BookModel({ data: SAMPLE_DATA });
expect(bm.getPageName(0)).toEqual('Page 1');
expect(bm.getPageName(1)).toEqual('Page 2');
});
describe('numLeafs', () => {
test('reads from data', () => {
const bm = new BookModel({ data: SAMPLE_DATA });
expect(bm.getNumLeafs()).toBe(4);
});
test('numLeafs can be overriden', () => {
const bm = new BookModel({ data: SAMPLE_DATA, numLeafs: 12 });
expect(bm.getNumLeafs()).toBe(12);
});
});
test('getPageNum', () => {
const bm = new BookModel({ data: SAMPLE_DATA });
expect(bm.getPageNum(0)).toBe('1');
});
test('getPageProp gets values from correct document', () => {
const bm = new BookModel({ data: SAMPLE_DATA });
expect(bm.getPageProp(1, 'uri')).toBe(SAMPLE_DATA[1][0].uri);
});
describe('getSpreadIndices', () => {
test('Works for left to right text', () => {
const bm = new BookModel({ data: SAMPLE_DATA, pageProgression: 'lr' });
expect(bm.getSpreadIndices(1)).toEqual([1, 2]);
expect(bm.getSpreadIndices(2)).toEqual([1, 2]);
});
});
describe('leafNumToIndex', () => {
test('returns input if no leafNum in data', () => {
const bm = new BookModel({ data: SAMPLE_DATA });
expect(bm.leafNumToIndex(1)).toBe(1);
expect(bm.leafNumToIndex(2)).toBe(2);
expect(bm.leafNumToIndex(3)).toBe(3);
});
test('returns true index when leafNum does not point to index', () => {
const data = deepCopy(SAMPLE_DATA);
for (const spread of data) {
for (const page of spread) {
page.leafNum = parseFloat(page.pageNum) + 2;
}
}
const bm = new BookModel({ data });
expect(bm.leafNumToIndex(3)).toBe(0);
});
});
describe('parsePageString', () => {
test('handles leaf-prefixed PageString', () => {
const data = deepCopy(SAMPLE_DATA);
for (const spread of data) {
for (const page of spread) {
page.leafNum = parseFloat(page.pageNum) + 2;
}
}
const bm = new BookModel({ data });
expect(bm.parsePageString('leaf3')).toBe(0);
});
});
describe('pagesIterator', () => {
test('Goes through all pages', () => {
const bm = new BookModel({ data: SAMPLE_DATA });
let i = 0;
for (const page of bm.pagesIterator()) {
expect(page.index).toBe(i++);
}
});
test('Can combine unviewables', () => {
const data = deepCopy(SAMPLE_DATA);
// add some more pages
data.splice(2, 0, deepCopy(data[0]));
data[1].forEach(page => page.viewable = false);
const bm = new BookModel({ data });
for (const page of bm.pagesIterator({combineConsecutiveUnviewables: true})) {
expect(page.isConsecutiveUnviewable).toBe(false);
}
});
});
describe('_getDataFlattened', () => {
test('Assigns correct page sides', () => {
const bm = new BookModel({ data: SAMPLE_DATA });
expect(bm._getDataFlattened().map(page => page.pageSide)).toEqual(['R', 'L', 'R', 'L']);
});
test('Memoized based on data length', () => {
const data = deepCopy(SAMPLE_DATA);
const bm = new BookModel({ data });
const firstResult = bm._getDataFlattened();
expect(bm._getDataFlattened()).toBe(firstResult);
expect(bm._getDataFlattened()).toBe(firstResult);
bm.br.data = data.slice(0, 1);
expect(bm._getDataFlattened()).not.toBe(firstResult);
});
test('Assigns unviewablesStart', () => {
const data = deepCopy(SAMPLE_DATA);
data.slice(1, -1)
.forEach(spread => spread.forEach(page => {
page.viewable = false;
}));
const bm = new BookModel({ data });
const pages = bm._getDataFlattened();
expect(pages[0].unviewablesStart).toBeUndefined();
expect(pages[1].unviewablesStart).toBe(1);
expect(pages[2].unviewablesStart).toBe(1);
expect(pages[3].unviewablesStart).toBeUndefined();
});
});
describe('getPage', () => {
test('loops around by default', () => {
const bm = new BookModel({ data: SAMPLE_DATA });
expect(bm.getPage(-1).index).toBe(3);
expect(bm.getPage(-2).index).toBe(2);
expect(bm.getPage(-3).index).toBe(1);
expect(bm.getPage(4).index).toBe(0);
expect(bm.getPage(5).index).toBe(1);
});
test('does not loop if loop=false', () => {
const bm = new BookModel({ data: SAMPLE_DATA });
expect(bm.getPage(-1, false)).toBeUndefined();
expect(bm.getPage(-2, false)).toBeUndefined();
expect(bm.getPage(4, false)).toBeUndefined();
expect(bm.getPage(5, false)).toBeUndefined();
});
});
describe('PageModel', () => {
test('constructor copies fields from book model', () => {
const bm = new BookModel({ data: SAMPLE_DATA });
const spy = sinon.spy(bm, 'getPageWidth');
const page = bm.getPage(0);
expect(spy.callCount).toBe(1);
expect(page.width).toBe(SAMPLE_DATA[0][0].width);
});
test('prev at start of book returns null', () => {
const bm = new BookModel({ data: SAMPLE_DATA });
expect(bm.getPage(0).prev).toBeUndefined();
});
test('prev to return previous', () => {
const bm = new BookModel({ data: SAMPLE_DATA });
expect(bm.getPage(1).prev.index).toBe(0);
expect(bm.getPage(3).prev.prev.index).toBe(1);
});
test('next at end of book returns null', () => {
const bm = new BookModel({ data: SAMPLE_DATA });
expect(bm.getPage(-1).next).toBeUndefined();
});
test('next to return next page', () => {
const bm = new BookModel({ data: SAMPLE_DATA });
expect(bm.getPage(0).next.index).toBe(1);
expect(bm.getPage(1).next.next.index).toBe(3);
});
describe('findNext combineConsecutiveUnviewables=true', () => {
const data = deepCopy(SAMPLE_DATA);
// add some more pages
data.splice(2, 0, deepCopy(data[0]));
data[1].forEach(page => page.viewable = false);
const bm = new BookModel({ data });
test('does not skip the first unviewable page', () => {
expect(bm.getPage(0).findNext({ combineConsecutiveUnviewables: true }).index).toBe(1);
});
test('skips consecutive unviewables', () => {
expect(bm.getPage(1).findNext({ combineConsecutiveUnviewables: true }).index).toBe(3);
});
test('at end is undefined', () => {
expect(bm.getPage(-1).findNext({ combineConsecutiveUnviewables: true })).toBeUndefined();
});
});
describe('findPrev', () => {
const data = deepCopy(SAMPLE_DATA);
// add some more pages
data.splice(2, 0, deepCopy(data[0]));
data[1].forEach(page => page.viewable = false);
const bm = new BookModel({ data });
test('works if called on first unviewable', () => {
expect(bm.getPage(1).findPrev({ combineConsecutiveUnviewables: true }).index).toBe(0);
});
test('works if called within unviewable chunk', () => {
expect(bm.getPage(2).findPrev({ combineConsecutiveUnviewables: true }).index).toBe(1);
});
test('at start is undefined', () => {
expect(bm.getPage(0).findPrev({ combineConsecutiveUnviewables: true })).toBeUndefined();
});
});
describe('findLeft/findRight', () => {
const data = deepCopy(SAMPLE_DATA);
test('Calls findNext/findPrev based on progression', () => {
const bm = new BookModel({ data });
const page = bm.getPage(0);
const findNextStub = sinon.stub(page, 'findNext');
const findPrevStub = sinon.stub(page, 'findPrev');
bm.pageProgression = 'lr';
page.findLeft();
expect(findPrevStub.callCount).toBe(1);
expect(findNextStub.callCount).toBe(0);
page.findRight();
expect(findPrevStub.callCount).toBe(1);
expect(findNextStub.callCount).toBe(1);
bm.pageProgression = 'rl';
page.findLeft();
expect(findPrevStub.callCount).toBe(1);
expect(findNextStub.callCount).toBe(2);
page.findRight();
expect(findPrevStub.callCount).toBe(2);
expect(findNextStub.callCount).toBe(2);
});
});
describe('getURISrcSet', () => {
const data = deepCopy(SAMPLE_DATA);
const bm = new BookModel({ data, reduceSet: NAMED_REDUCE_SETS.pow2 });
bm.getPageURI = (index, scale, rotate) => `correctURL.png?scale=${scale}`;
const page = bm.getPage(0);
test('with 0 elements in srcset', () => {
expect(page.getURISrcSet(1)).toBe("");
});
test('with 2 elements in srcset', () => {
expect(page.getURISrcSet(5)).toBe("correctURL.png?scale=2 2x, correctURL.png?scale=1 4x");
});
test('with the most elements in srcset', () => {
expect(page.getURISrcSet(35)).toBe("correctURL.png?scale=16 2x, correctURL.png?scale=8 4x, correctURL.png?scale=4 8x, correctURL.png?scale=2 16x, correctURL.png?scale=1 32x");
});
});
});