openseadragon/test/modules/data-manipulation.js

260 lines
10 KiB
JavaScript

/* global QUnit, testLog */
(function() {
let viewer;
QUnit.module(`Data Manipulation Across Drawers`, {
beforeEach: function () {
$('<div id="example"></div>').appendTo("#qunit-fixture");
testLog.reset();
},
afterEach: function () {
if (viewer && viewer.close) {
viewer.close();
}
viewer = null;
}
});
OpenSeadragon.getBuiltInDrawersForTest().forEach(testDrawer);
// If yuu want to debug a specific drawer, use instead:
// ['webgl'].forEach(testDrawer);
function getPluginCode(overlayColor = "rgba(0,0,255,0.5)") {
return async function(e) {
const tile = e.tile;
const ctx = await tile.getData('context2d'), canvas = ctx.canvas;
ctx.fillStyle = overlayColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
await tile.setData(ctx, 'context2d');
};
}
function getResetTileDataCode() {
return async function(e) {
const tile = e.tile;
tile.restore();
};
}
function getTileDescription(t) {
return `${t.level}/${t.x}-${t.y}`;
}
function testDrawer(type) {
function whiteViewport() {
viewer = OpenSeadragon({
id: 'example',
prefixUrl: '/build/openseadragon/images/',
maxImageCacheCount: 200,
springStiffness: 100,
drawer: type
});
viewer.open({
width: 24,
height: 24,
tileSize: 24,
minLevel: 1,
// This is a crucial test feature: all tiles share the same URL, so there are plenty collisions
getTileUrl: (x, y, l) => "",
getTilePostData: () => "",
downloadTileStart: (context) => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = context.tile.size.x;
canvas.height = context.tile.size.y;
ctx.fillStyle = "#ffffff";
ctx.fillRect(0, 0, context.tile.size.x, context.tile.size.y);
context.finish(ctx, null, "context2d");
}
});
}
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
// we test middle of the canvas, so that we can test both tiles or the output canvas of canvas drawer :)
async function readTileData() {
if (type === "canvas") {
//test with the underlying canvas instead
const canvas = viewer.drawer.canvas;
return viewer.drawer.canvas.getContext("2d").getImageData(canvas.width/2, canvas.height/2, 1, 1);
}
await sleep(200);
//else incompatible drawer for data getting
const tile = viewer.world.getItemAt(0).getTilesToDraw()[0];
const cache = tile.tile.getCache();
if (!cache || !cache.loaded) return null;
const ctx = await cache.getDataAs("context2d");
if (!ctx) return null;
return ctx.getImageData(ctx.canvas.width/2, ctx.canvas.height/2, 1, 1)
}
QUnit.test(type + ' drawer: basic scenario.', function(assert) {
whiteViewport();
const done = assert.async();
const fnA = getPluginCode("rgba(0,0,255,1)");
const fnB = getPluginCode("rgba(255,0,0,1)");
const crashTest = () => assert.ok(false, "Tile Invalidated event should not be called");
viewer.addHandler('tile-loaded', fnA);
viewer.addHandler('tile-invalidated', crashTest);
viewer.addHandler('tile-loaded', fnB);
viewer.addHandler('tile-invalidated', crashTest);
viewer.addHandler('open', async () => {
await viewer.waitForFinishedJobsForTest();
let data = await readTileData();
assert.equal(data.data[0], 255);
assert.equal(data.data[1], 0);
assert.equal(data.data[2], 0);
assert.equal(data.data[3], 255);
// Thorough testing of the cache state
for (let tile of viewer.tileCache._tilesLoaded) {
const caches = Object.entries(tile._caches);
assert.equal(caches.length, 2, `Tile ${getTileDescription(tile)} has only two caches - main & original`);
for (let [key, value] of caches) {
assert.ok(value.loaded, `Attached cache '${key}' is ready.`);
assert.notOk(value._destroyed, `Attached cache '${key}' is not destroyed.`);
assert.ok(value._tiles.includes(tile), `Attached cache '${key}' reference is bidirectional.`);
}
assert.notOk(tile.getCache(tile._wcKey), "Tile cache working key is unset");
}
done();
});
});
QUnit.test(type + ' drawer: basic scenario with priorities + events addition.', function(assert) {
whiteViewport();
const done = assert.async();
// FNA gets applied last since it has low priority
const fnA = getPluginCode("rgba(0,0,255,1)");
const fnB = getPluginCode("rgba(255,0,0,1)");
viewer.addHandler('tile-loaded', fnA);
viewer.addHandler('tile-invalidated', fnA);
viewer.addHandler('tile-loaded', fnB, null, 1);
viewer.addHandler('tile-invalidated', fnB, null, 1);
// const promise = viewer.requestInvalidate();
viewer.addHandler('open', async () => {
await viewer.waitForFinishedJobsForTest();
let data = await readTileData();
assert.equal(data.data[0], 0);
assert.equal(data.data[1], 0);
assert.equal(data.data[2], 255);
assert.equal(data.data[3], 255);
// Test swap
viewer.addHandler('tile-loaded', fnB);
viewer.addHandler('tile-invalidated', fnB);
await viewer.requestInvalidate();
data = await readTileData();
// suddenly B is applied since it was added with same priority but later
assert.equal(data.data[0], 255);
assert.equal(data.data[1], 0);
assert.equal(data.data[2], 0);
assert.equal(data.data[3], 255);
// Now B gets applied last! Red
viewer.addHandler('tile-loaded', fnB, null, -1);
viewer.addHandler('tile-invalidated', fnB, null, -1);
await viewer.requestInvalidate();
// no change
data = await readTileData();
assert.equal(data.data[0], 255);
assert.equal(data.data[1], 0);
assert.equal(data.data[2], 0);
assert.equal(data.data[3], 255);
// Thorough testing of the cache state
for (let tile of viewer.tileCache._tilesLoaded) {
const caches = Object.entries(tile._caches);
assert.equal(caches.length, 2, `Tile ${getTileDescription(tile)} has only two caches - main & original`);
for (let [key, value] of caches) {
assert.ok(value.loaded, `Attached cache '${key}' is ready.`);
assert.notOk(value._destroyed, `Attached cache '${key}' is not destroyed.`);
assert.ok(value._tiles.includes(tile), `Attached cache '${key}' reference is bidirectional.`);
}
assert.notOk(tile.getCache(tile._wcKey), "Tile cache working key is unset");
}
done();
});
});
QUnit.test(type + ' drawer: one calls tile restore.', function(assert) {
whiteViewport();
const done = assert.async();
const fnA = getPluginCode("rgba(0,255,0,1)");
const fnB = getResetTileDataCode();
viewer.addHandler('tile-loaded', fnA);
viewer.addHandler('tile-invalidated', fnA);
viewer.addHandler('tile-loaded', fnB, null, 1);
viewer.addHandler('tile-invalidated', fnB, null, 1);
// const promise = viewer.requestInvalidate();
viewer.addHandler('open', async () => {
await viewer.waitForFinishedJobsForTest();
let data = await readTileData();
assert.equal(data.data[0], 0);
assert.equal(data.data[1], 255);
assert.equal(data.data[2], 0);
assert.equal(data.data[3], 255);
// Test swap - suddenly B applied since it was added later
viewer.addHandler('tile-loaded', fnB);
viewer.addHandler('tile-invalidated', fnB);
await viewer.requestInvalidate();
data = await readTileData();
assert.equal(data.data[0], 255);
assert.equal(data.data[1], 255);
assert.equal(data.data[2], 255);
assert.equal(data.data[3], 255);
viewer.addHandler('tile-loaded', fnB, null, -1);
viewer.addHandler('tile-invalidated', fnB, null, -1);
await viewer.requestInvalidate();
data = await readTileData();
//Erased!
assert.equal(data.data[0], 255);
assert.equal(data.data[1], 255);
assert.equal(data.data[2], 255);
assert.equal(data.data[3], 255);
// Thorough testing of the cache state
for (let tile of viewer.tileCache._tilesLoaded) {
const caches = Object.entries(tile._caches);
assert.equal(caches.length, 1, `Tile ${getTileDescription(tile)} has only single, original cache`);
for (let [key, value] of caches) {
assert.ok(value.loaded, `Attached cache '${key}' is ready.`);
assert.notOk(value._destroyed, `Attached cache '${key}' is not destroyed.`);
assert.ok(value._tiles.includes(tile), `Attached cache '${key}' reference is bidirectional.`);
}
assert.notOk(tile.getCache(tile._wcKey), "Tile cache working key is unset");
}
done();
});
});
}
}());