Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
dc1af3c
d26c89d298678ecb7299b9539ab1f2a5ce77ab52
jamesmisson Apr 1, 2025
e1eaded
remove settings button test
Saira-A Apr 2, 2025
0d0f2c3
add to headerpanel
Saira-A Apr 2, 2025
30e1c69
move goto out of leftOptions into pagingheaderpanel
jamesmisson Apr 2, 2025
a000eb1
comment out pagingheaderpanelright
Saira-A Oct 16, 2025
b9ffe48
autocomplete basic functionality and styling
jamesmisson Apr 2, 2025
3db9334
keyboard navigation
jamesmisson Apr 2, 2025
2c7101b
refactor to multiple createElemts in pagingheaderpanel, add empty sea…
jamesmisson Apr 2, 2025
1b9b311
remove old root render
jamesmisson Apr 2, 2025
c6d8d2e
add functionality to search
jamesmisson Apr 2, 2025
631dd3d
icon styling
jamesmisson Apr 3, 2025
ca36194
move icons
Saira-A Apr 4, 2025
3eeabb9
config
Saira-A Apr 8, 2025
5459b00
add icon library, insert icons, add tooltips
jamesmisson Apr 8, 2025
9bbb531
show labels by default, tooltips for buttons
jamesmisson Apr 9, 2025
1eae566
uncomment test
Saira-A Oct 17, 2025
861d81e
bump build
jamesmisson Apr 9, 2025
fe68d29
config / labels
Saira-A Apr 10, 2025
8bd1ec1
lint
jamesmisson Apr 11, 2025
459171f
close dropdown on window scroll
jamesmisson Apr 16, 2025
f0025fc
change image contro, buttons to svg
jamesmisson Apr 16, 2025
8d7cd63
remove unused svg files
jamesmisson Apr 16, 2025
7a75e3b
adjust title left padding
jamesmisson Apr 16, 2025
1e3a1e0
add new icons
Saira-A Apr 17, 2025
4de8224
fix utils
Saira-A Oct 17, 2025
01921f5
remove old search input
jamesmisson Apr 22, 2025
96e3b78
render goto and search conditionally
jamesmisson Apr 22, 2025
fe0c8b1
remove old goto
jamesmisson Apr 22, 2025
aa8cde3
fix debounce error
jamesmisson Apr 22, 2025
a9a99c2
fix image adjustment button alignment
jamesmisson Apr 22, 2025
20c3d30
change center panel icons
Saira-A Apr 23, 2025
dc32fa3
update
jamesmisson Apr 23, 2025
32d50a3
remove config duplicates
Saira-A Apr 23, 2025
9e77976
gallery button
Saira-A Apr 28, 2025
47b648d
move css
Saira-A Apr 28, 2025
9a1f136
reduce footer
Saira-A Apr 28, 2025
bf12312
fade toggles
Saira-A Apr 28, 2025
9540619
delete tsx
Saira-A Apr 28, 2025
76fb670
disable footer
Saira-A Apr 29, 2025
ea72d10
move buttons
Saira-A Apr 30, 2025
9d193e6
make paging toggle
Saira-A May 2, 2025
7a26047
toggle svgs
Saira-A May 6, 2025
31f9e75
remove help dialogue
Saira-A Oct 20, 2025
8bc53e9
Commit from GitHub Actions (Lint and Prettify code)
github-actions[bot] Oct 21, 2025
140c7ef
Merge remote-tracking branch 'upstream/dev' into BL-UI
Saira-A Oct 21, 2025
89634f5
package-lock
Saira-A Oct 21, 2025
ceef196
Merge branch 'BL-UI' of https://github.com/Saira-A/universalviewer in…
Saira-A Oct 21, 2025
ee531df
comment out failing test
Saira-A Oct 23, 2025
3d39f67
Merge remote-tracking branch 'origin' into BL-UI
Saira-A Apr 22, 2026
3247a61
Revert "Merge remote-tracking branch 'origin' into BL-UI"
Saira-A Apr 22, 2026
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
4 changes: 2 additions & 2 deletions .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ jobs:

name: Node ${{ matrix.node }} build
steps:
- uses: actions/checkout@v6
- uses: actions/cache@v5
- uses: actions/checkout@v5
- uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-${{ matrix.node }}-npm-${{ hashFiles('**/package-lock.lock') }}
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/lint-and-prettify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
node-version: "20"

- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v5

- name: Install npm dependencies
run: npm install
Expand All @@ -29,7 +29,7 @@ jobs:
run: npm run prettify

- name: Auto-commit fixes
uses: EndBug/add-and-commit@v10
uses: EndBug/add-and-commit@v9
with:
default_author: github_actions
add: "['src/']"
53 changes: 28 additions & 25 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,32 @@ on:
push:
tags: ['v*']

permissions:
id-token: write # Required for OIDC
contents: read

jobs:
publish:
build:
runs-on: ubuntu-latest
name: Node build
steps:
- uses: actions/checkout@v5
- uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-${{ matrix.node }}-npm-${{ hashFiles('**/package-lock.lock') }}
restore-keys: |
${{ runner.os }}-npm-
- name: Setup node
uses: actions/setup-node@v6
with:
node-version: '20.x'
- run: npm ci
- run: npm run build

release:
needs: [build]
runs-on: ubuntu-latest
name: Release
steps:
- uses: actions/checkout@v6
- uses: actions/cache@v5
- uses: actions/checkout@v5
- uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-${{ matrix.node }}-npm-${{ hashFiles('**/package-lock.lock') }}
Expand All @@ -23,27 +38,15 @@ jobs:
- name: Setup node
uses: actions/setup-node@v6
with:
node-version: '24'
registry-url: 'https://registry.npmjs.org'
node-version: '20.x'

- name: Set tag
id: tagName
run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT

# Ensure npm 11.5.1 or later is installed
- name: Update npm
run: npm install -g npm@latest
run: echo ::set-output name=tag::${GITHUB_REF#refs/*/}

- run: npm ci
- run: npm run prepublishOnly

- name: Publish a release candidate
if: contains(steps.tagName.outputs.tag, 'rc')
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npm publish --tag=rc

- name: Publish a regular release
if: false == contains(steps.tagName.outputs.tag, 'rc')
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npm publish
- uses: JS-DevTools/npm-publish@v4
with:
token: ${{ secrets.NPM_TOKEN }}
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v14.18.1
1 change: 0 additions & 1 deletion .stylelintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
"rules": {
"block-no-empty": true,
"at-rule-no-unknown": null,
"color-function-notation": null,
"color-no-invalid-hex": true,
"less/color-no-invalid-hex": null,
"rule-empty-line-before": null,
Expand Down
263 changes: 10 additions & 253 deletions __tests__/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,72 +6,6 @@ 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 waitForRotation = async (expectedRotation) => {
await page.waitForFunction(
(expected) => {
const el = document.querySelector(".displayregioncontainer");
if (!el) return false;

const transform = el.style.transform || "";
const match = transform.match(/rotate\((-?\d+(?:\.\d+)?)deg\)/);
const current = match ? Number(match[1]) : 0;

return ((current % 360) + 360) % 360 === expected;
},
{},
expectedRotation
);
};

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 @@ -133,193 +67,16 @@ describe("Universal Viewer", () => {
expect(labelOverflowAfterToggle).toBe("visible");
});

it("can toggle gallery view", async () => {
// gallery view is not default view
const galleryViewBeforeToggle = await page.evaluate(() => {
const galleryViewOverlay = document.querySelector(
".iiif-gallery-component .header"
);
return getComputedStyle(galleryViewOverlay).overflowX;
});
expect(galleryViewBeforeToggle).toBe("hidden");
// it('settings button is visible', async () => {

// gallery toggle icon is visible
await page.waitForSelector(".uv-icon-gallery");
const galleryViewToggle = await page.evaluate(() => {
const toggle = document.querySelector(".uv-icon-gallery");
return getComputedStyle(toggle).overflowX;
});
expect(galleryViewToggle).toBe("visible");
// await page.waitForSelector('.btn.imageBtn.settings');

// gallery view can be toggled on
await page.evaluate(() => {
document.querySelector(".uv-icon-gallery").click();
});
const galleryViewAfterToggle = await page.evaluate(() => {
const galleryViewOverlay = document.querySelector(
".iiif-gallery-component"
);
return getComputedStyle(galleryViewOverlay).overflowX;
});
expect(galleryViewAfterToggle).toBe("visible");

// gallery view can be toggled off
await page.evaluate(() => {
document.querySelector(".uv-icon-two-up").click();
});
const galleryViewAfterTwoUpToggle = await page.evaluate(() => {
const galleryViewOverlay = document.querySelector(
".iiif-gallery-component .header"
);
return getComputedStyle(galleryViewOverlay).overflowX;
});
expect(galleryViewAfterTwoUpToggle).toBe("hidden");
});

it("settings button is visible", async () => {
await page.waitForSelector(".btn.imageBtn.settings");

const isSettingsButtonVisible = await page.evaluate(() => {
const settingsButton = document.querySelector(".btn.imageBtn.settings");
const style = window.getComputedStyle(settingsButton);
return (
style.getPropertyValue("visibility") !== "hidden" &&
style.getPropertyValue("display") !== "none"
);
});

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

describe("viewer controls", () => {
afterEach(async () => {
await page.goto("http://localhost:4444");
});
// const isSettingsButtonVisible = await page.evaluate(() => {
// const settingsButton = document.querySelector('.btn.imageBtn.settings');
// const style = window.getComputedStyle(settingsButton);
// return style.getPropertyValue('visibility') !== 'hidden' && style.getPropertyValue('display') !== 'none';
// });

// can navigate back and forth
it("can navigate back and forth", 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();

await page.click(".rotate.viewportNavButton");
await waitForRotation(90);

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.$eval(closeBtn, (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);
});
});
});
// expect(isSettingsButtonVisible).toBe(true);
// });
});
Loading
Loading