Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
216 changes: 215 additions & 1 deletion __tests__/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,55 @@ describe("Universal Viewer", () => {
let browser;
let page;

const getRotationFromNavigator = async () => {
return await page.evaluate(() => {
const el = document.querySelector(".displayregioncontainer");
if (!el) return 0;

const transform = el.style.transform || "";
const match = transform.match(/rotate\((-?\d+(?:\.\d+)?)deg\)/);
if (!match) return 0;

const deg = Number(match[1]);
return ((deg % 360) + 360) % 360;
});
};

const getCanvasValue = (url) => {
const match = url.match(/(?:^|[?&#])(cv|canvas|page)=([^&#]*)/);
if (!match) return 0;

const rawValue = match[2];
if (rawValue === "") return 0;

const value = Number(rawValue);
if (Number.isNaN(value)) return 0;

return value;
};

const waitForCanvasValue = async (page, expected) => {
await page.waitForFunction(
(expectedValue) => {
const match = window.location.href.match(/(?:^|[?&#])(cv|canvas|page)=([^&#]*)/);
if (!match) return expectedValue === 0;

const rawValue = match[2];
if (rawValue === "") return expectedValue === 0;

const value = Number(rawValue);
return value === expectedValue;
},
{},
expected
);
};

const getXywhValue = (url) => {
const match = url.match(/[?&#]xywh=([^&]+)/);
return match ? decodeURIComponent(match[1]) : null;
};

beforeAll(async () => {
browser = await puppeteer.launch();
page = await browser.newPage();
Expand Down Expand Up @@ -124,4 +173,169 @@ describe("Universal Viewer", () => {

expect(isSettingsButtonVisible).toBe(true);
});
});

describe("viewer controls", () => {
afterEach(async () => {
await page.goto("http://localhost:4444");
});

// navigate to next image
it("can navigate to next image", async () => {
await page.waitForSelector(".btn.imageBtn.next", { visible: true });

const urlBefore = page.url();
const before = getCanvasValue(urlBefore);

await page.click(".btn.imageBtn.next");
await waitForCanvasValue(page, 1);

const urlAfter = page.url();
const after = getCanvasValue(urlAfter);

expect(before).toBe(0);
expect(after).toBe(1);
});
Comment on lines +182 to +197
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this test? It seems to be mostly redundant with the following one. Should we rename that from "can navigate to previous image" to "can navigate back and forth" and then delete this first test?


// navigate to previous image
it("can navigate to previous image", async () => {
await page.waitForSelector(".btn.imageBtn.next", { visible: true });
await page.waitForSelector(".btn.imageBtn.prev", { visible: true });

const startValue = getCanvasValue(page.url());

const isPrevDisabledInitially = await page.$eval(
".btn.imageBtn.prev",
(btn) => btn.disabled
);
expect(isPrevDisabledInitially).toBe(true);

await page.click(".btn.imageBtn.next");
await waitForCanvasValue(page, 1);

const nextValue = getCanvasValue(page.url());
const isPrevDisabledAfterNext = await page.$eval(
".btn.imageBtn.prev",
(btn) => btn.disabled
);
expect(isPrevDisabledAfterNext).toBe(false);

await page.click(".btn.imageBtn.prev");
await waitForCanvasValue(page, 0);

const previousValue = getCanvasValue(page.url());
const isPrevDisabledAgain = await page.$eval(
".btn.imageBtn.prev",
(btn) => btn.disabled
);
expect(isPrevDisabledAgain).toBe(true);

expect(startValue).toBe(0);
expect(nextValue).toBe(1);
expect(previousValue).toBe(0);

});

// zoom in and zoom out
it("can zoom in and zoom out", async () => {
await page.waitForSelector(".zoomIn.viewportNavButton", {
visible: true,
});
await page.waitForSelector(".zoomOut.viewportNavButton", {
visible: true,
});

const initialUrl = page.url();
const initialXywh = getXywhValue(initialUrl);

await page.click(".zoomIn.viewportNavButton");
await page.waitForFunction(
(prev) => window.location.href !== prev,
{},
initialUrl
);

const zoomInUrl = page.url();
const zoomInXywh = getXywhValue(zoomInUrl);

expect(zoomInXywh).not.toBeNull();
expect(zoomInXywh).not.toBe(initialXywh);
expect(zoomInXywh).toMatch(/^-?\d+,-?\d+,\d+,\d+$/);

await page.click(".zoomOut.viewportNavButton");
await page.waitForFunction(
(prev) => window.location.href !== prev,
{},
zoomInUrl
);

const zoomOutUrl = page.url();
const zoomOutXywh = getXywhValue(zoomOutUrl);

expect(zoomOutXywh).not.toBeNull();
expect(zoomOutXywh).not.toBe(zoomInXywh);
expect(zoomOutXywh).toMatch(/^-?\d+,-?\d+,\d+,\d+$/);
});

// rotate image
it("can rotate image", async () => {
await page.waitForSelector(".rotate.viewportNavButton", {
visible: true,
});

const initialRot = await getRotationFromNavigator();
const initialTransform = await page.evaluate(() => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason not to use getRotationFromNavigator instead of directly accessing the .displayregioncontainer transform style in multiple places? I imagine it's possible that a future OpenSeadragon release might change the way this works, so having it centralized in a single support method would make it easier to fix if something breaks it in the future. Of course, if there's a reason you need "raw" access to the value, I can live with it as-is. :-)

return (
document.querySelector(".displayregioncontainer")?.style.transform || ""
);
});

await page.click(".rotate.viewportNavButton");
await page.waitForFunction(
(prev) => {
const current = document.querySelector(".displayregioncontainer")?.style.transform || "";
return current !== prev;
},
{},
initialTransform
);
const rotatedRot = await getRotationFromNavigator();
expect(initialRot).toBe(0);
expect(rotatedRot).toBe(90);
});

// open and close adjust image control
it("can open and close adjust image control", async () => {
const btn = "button.viewportNavButton.adjustImage";
const overlay = "div.overlay.adjustImage";
const heading = "div.overlay.adjustImage .content .heading";
const closeBtn = ".btn.btn-default.close";

await page.waitForSelector(btn, { visible: true });
await page.click(btn);

await page.waitForSelector(overlay, { visible: true });

const text = await page.$eval(heading, (el) => el.textContent.trim());
expect(text).toBe("Adjust image");

await page.evaluate((selector) => {
const el = document.querySelector(selector);
if (el) el.click();
}, closeBtn);
Comment on lines +321 to +324
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand why evaluate is being used here. Can this be simplified? If nothing else, maybe this would be less confusing (if I understand the syntax correctly):

Suggested change
await page.evaluate((selector) => {
const el = document.querySelector(selector);
if (el) el.click();
}, closeBtn);
await page.evaluate(() => {
const el = document.querySelector(closeBtn);
if (el) el.click();
});


const isOverlayVisible = await page.evaluate(() => {
const isOverlayVisible = document.querySelector(
"div.overlay.adjustImage"
);
const style = window.getComputedStyle(isOverlayVisible);

return (
style.getPropertyValue("display") === "none" ||
style.getPropertyValue("visibility") === "hidden"
);
});

expect(isOverlayVisible).toBe(false);
});
});
});