diff --git a/__tests__/configuration_options.js b/__tests__/configuration_options.js deleted file mode 100644 index 07a3425b9..000000000 --- a/__tests__/configuration_options.js +++ /dev/null @@ -1,50 +0,0 @@ -const { BASE_URL } = require("../scripts/testBaseUrl"); - -describe("Configuration options", () => { - describe("thumb cache invalidation", () => { - beforeEach(async () => { - await page.goto(BASE_URL); - await page.waitForSelector("#thumb0"); - }); - it.skip("when set to false does not provide timestamp", async () => { - await page.evaluate(() => - uv.set({ - config: { - modules: { - contentLeftPanel: { - options: { thumbsCacheInvalidation: { enabled: false } }, - }, - }, - }, - }) - ); - await page.waitForSelector("#thumb0"); - const imageSrc = await page.$eval("#thumb0 img", (e) => e.src); - expect(imageSrc).toEqual( - expect.stringMatching( - "https://dlcs.io/iiif-img/wellcome/1/ff2085d5-a9c7-412e-9dbe-dda87712228d/full/90,/0/default.jpg" - ) - ); - }); - it.skip("has a configurable parameter type", async () => { - await page.evaluate(() => - uv.set({ - config: { - modules: { - contentLeftPanel: { - options: { thumbsCacheInvalidation: { paramType: "#" } }, - }, - }, - }, - }) - ); - await page.waitForSelector("#thumb0"); - const imageSrc = await page.$eval("#thumb0 img", (e) => e.src); - expect(imageSrc).toEqual( - expect.stringContaining( - "https://dlcs.io/iiif-img/wellcome/1/ff2085d5-a9c7-412e-9dbe-dda87712228d/full/90,/0/default.jpg#t=" - ) - ); - }); - }); -}); diff --git a/__tests__/configuration_tests.js b/__tests__/configuration_tests.js new file mode 100644 index 000000000..30d268cdf --- /dev/null +++ b/__tests__/configuration_tests.js @@ -0,0 +1,73 @@ +const { BASE_URL } = require("../scripts/testBaseUrl"); + +const FIRST_THUMB_SRC = + "https://iiif.wellcomecollection.org/image/b18035723_0001.JP2/full/90,/0/default.jpg"; + +// Applies custom config through the examples page's Configuration tab: +// the #customConfig JSON is merged into the viewer config by a +// uv.on("configure") handler when the Apply Configurations button +// re-initialises the viewer. +const applyCustomConfig = async (config) => { + await page.evaluate((cfg) => { + document.getElementById("customConfig").value = cfg; + document.getElementById("clearStorageCheckbox").checked = true; + document.getElementById("setConfigButton").click(); + }, JSON.stringify(config)); +}; + +describe("Configuration options", () => { + describe("thumb cache invalidation", () => { + beforeEach(async () => { + await page.goto(BASE_URL); + await page.waitForSelector("#thumb-0 img"); + }); + + it("when set to false does not provide timestamp", async () => { + await applyCustomConfig({ + modules: { + contentLeftPanel: { + options: { thumbsCacheInvalidation: { enabled: false } }, + }, + }, + }); + + // wait for the viewer to re-render thumbs without the timestamp + await page.waitForFunction( + (src) => { + const img = document.querySelector("#thumb-0 img"); + return img && img.src === src; + }, + {}, + FIRST_THUMB_SRC + ); + + const imageSrc = await page.$eval("#thumb-0 img", (e) => e.src); + expect(imageSrc).toEqual(FIRST_THUMB_SRC); + }); + + it("has a configurable parameter type", async () => { + await applyCustomConfig({ + modules: { + contentLeftPanel: { + options: { thumbsCacheInvalidation: { paramType: "#" } }, + }, + }, + }); + + // wait for the viewer to re-render thumbs with a #t= timestamp + await page.waitForFunction( + (src) => { + const img = document.querySelector("#thumb-0 img"); + return img && img.src.startsWith(`${src}#t=`); + }, + {}, + FIRST_THUMB_SRC + ); + + const imageSrc = await page.$eval("#thumb-0 img", (e) => e.src); + expect(imageSrc).toEqual( + expect.stringContaining(`${FIRST_THUMB_SRC}#t=`) + ); + }); + }); +}); diff --git a/__tests__/test.js b/__tests__/test.js index 1f050fcb1..8c65f3873 100644 --- a/__tests__/test.js +++ b/__tests__/test.js @@ -483,4 +483,4 @@ describe("Universal Viewer", () => { expect(page.url()).toContain("cv=1"); }); }); -}); +}); \ No newline at end of file diff --git a/src/content-handlers/iiif/extensions/config/ContentLeftPanel.ts b/src/content-handlers/iiif/extensions/config/ContentLeftPanel.ts index a7f2b46ae..d91e3ce28 100644 --- a/src/content-handlers/iiif/extensions/config/ContentLeftPanel.ts +++ b/src/content-handlers/iiif/extensions/config/ContentLeftPanel.ts @@ -1,7 +1,7 @@ import { ModuleConfig } from "../../BaseConfig"; import { ExpandPanelContent, ExpandPanelOptions } from "./ExpandPanel"; -type ThumbsCacheInvalidation = { +export type ThumbsCacheInvalidation = { /** Determines if cache invalidation is enabled */ enabled: boolean; /** Type of the parameter for cache invalidation */ diff --git a/src/content-handlers/iiif/modules/uv-contentleftpanel-module/ContentLeftPanel.ts b/src/content-handlers/iiif/modules/uv-contentleftpanel-module/ContentLeftPanel.ts index ab58980f0..fef15cb61 100644 --- a/src/content-handlers/iiif/modules/uv-contentleftpanel-module/ContentLeftPanel.ts +++ b/src/content-handlers/iiif/modules/uv-contentleftpanel-module/ContentLeftPanel.ts @@ -520,6 +520,7 @@ export class ContentLeftPanel extends LeftPanel { this.thumbsRoot.render( createElement(ThumbsView, { + cacheInvalidation: this.config.options.thumbsCacheInvalidation, thumbs, paged, viewingDirection: viewingDirection || ViewingDirection.LEFT_TO_RIGHT, diff --git a/src/content-handlers/iiif/modules/uv-contentleftpanel-module/ThumbsView.tsx b/src/content-handlers/iiif/modules/uv-contentleftpanel-module/ThumbsView.tsx index e97f5a535..cb94aa46a 100644 --- a/src/content-handlers/iiif/modules/uv-contentleftpanel-module/ThumbsView.tsx +++ b/src/content-handlers/iiif/modules/uv-contentleftpanel-module/ThumbsView.tsx @@ -1,10 +1,13 @@ import { ViewingDirection, ViewingHint } from "@iiif/vocabulary"; import cx from "classnames"; import { Thumb } from "manifesto.js"; -import React, { useEffect, useRef } from "react"; +import React, { useEffect, useMemo, useRef } from "react"; import { useInView } from "react-intersection-observer"; +import { ThumbsCacheInvalidation } from "../../extensions/config/ContentLeftPanel"; +import { Dates } from "../../Utils"; const ThumbImage = ({ + cacheInvalidation, first, onClick, onKeyDown, @@ -14,6 +17,7 @@ const ThumbImage = ({ truncateThumbnailLabels, viewingDirection, }: { + cacheInvalidation?: ThumbsCacheInvalidation; first: boolean; onClick: (thumb: Thumb) => void; onKeyDown: (thumb: Thumb) => void; @@ -29,6 +33,14 @@ const ThumbImage = ({ triggerOnce: true, }); + // memoised so re-renders don't generate a new timestamp and re-request the image + const src = useMemo(() => { + if (thumb.uri && cacheInvalidation && cacheInvalidation.enabled) { + return `${thumb.uri}${cacheInvalidation.paramType}t=${Dates.getTimeStamp()}`; + } + return thumb.uri; + }, [thumb.uri, cacheInvalidation]); + var keydownHandler = (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); @@ -62,7 +74,7 @@ const ThumbImage = ({ height: thumb.height + 8 + "px", }} > - {inView && {thumb.label}} + {inView && {thumb.label}}
@@ -77,6 +89,7 @@ const ThumbImage = ({ }; const Thumbnails = ({ + cacheInvalidation, onClick, onKeyDown, paged, @@ -86,6 +99,7 @@ const Thumbnails = ({ viewingDirection, truncateThumbnailLabels, }: { + cacheInvalidation?: ThumbsCacheInvalidation; onClick: (thumb: Thumb) => void; onKeyDown: (thumb: Thumb) => void; paged: boolean; @@ -149,6 +163,7 @@ const Thumbnails = ({ className="thumb-container" >