/* global QUnit, testLog */ (function() { let viewer; QUnit.module(`Data Manipulation Across Drawers`, { beforeEach: function () { $('
').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(); }); }); } }());