For the complete documentation index, see llms.txt. This page is also available as Markdown.

IIIF Viewer Context Refactoring - Testing Guide

Related: See refactoring-iiif-viewer-context.md for the main refactoring plan.

This document contains detailed test examples for the IIIF Viewer context refactoring. All tests should use proper TypeScript types.

Phase 1 Testing

1.1 Context Provider Unit Tests

File: content/webapp/contexts/ItemViewerContextV2/ItemViewerContextV2.test.tsx

import { render, screen } from '@testing-library/react';
import type { FC } from 'react';

import ItemViewerContextV2, { ItemViewerContextV2Props } from './index';
import { mockDefaultContext } from './test-utils';

// Test component that displays context values
const ContextValueDisplay: FC = () => {
  const {
    currentCanvas,
    currentCanvasIndex,
    mainImageService,
    hasMultipleCanvases,
    isCurrentCanvasRestricted,
    isFirstCanvas,
    isLastCanvas,
    canNavigateNext,
    canNavigatePrevious,
    hasIiifImageService,
    isImageZoomable,
    isWorkBornDigital,
  } = ItemViewerContextV2._currentValue;

  return (
    <div>
      <div data-testid="currentCanvasIndex">{currentCanvasIndex}</div>
      <div data-testid="currentCanvasId">{currentCanvas?.id || 'none'}</div>
      <div data-testid="mainImageServiceId">{mainImageService['@id'] || 'none'}</div>
      <div data-testid="hasMultipleCanvases">{String(hasMultipleCanvases)}</div>
      <div data-testid="isCurrentCanvasRestricted">{String(isCurrentCanvasRestricted)}</div>
      <div data-testid="isFirstCanvas">{String(isFirstCanvas)}</div>
      <div data-testid="isLastCanvas">{String(isLastCanvas)}</div>
      <div data-testid="canNavigateNext">{String(canNavigateNext)}</div>
      <div data-testid="canNavigatePrevious">{String(canNavigatePrevious)}</div>
      <div data-testid="hasIiifImageService">{String(hasIiifImageService)}</div>
      <div data-testid="isImageZoomable">{String(isImageZoomable)}</div>
      <div data-testid="isWorkBornDigital">{String(isWorkBornDigital)}</div>
    </div>
  );
};

describe('ItemViewerContextV2 - Derived Canvas Data', () => {
  it('should calculate currentCanvasIndex from canvas query param', () => {
    const contextValue: ItemViewerContextV2Props = {
      ...mockDefaultContext,
      query: { canvas: 3, manifest: 1, page: 1, shouldScrollToCanvas: true, query: '' },
      currentCanvasIndex: 2, // canvas=3 to index 2
    };

    render(
      <ItemViewerContextV2.Provider value={contextValue}>
        <ContextValueDisplay />
      </ItemViewerContextV2.Provider>
    );

    expect(screen.getByTestId('currentCanvasIndex')).toHaveTextContent('2');
  });

  it('should handle mainImageService with empty string fallback', () => {
    const contextValue: ItemViewerContextV2Props = {
      ...mockDefaultContext,
      currentCanvas: { id: 'canvas-1', imageServiceId: undefined },
      mainImageService: { '@id': '' },
    };

    render(
      <ItemViewerContextV2.Provider value={contextValue}>
        <ContextValueDisplay />
      </ItemViewerContextV2.Provider>
    );

    expect(screen.getByTestId('mainImageServiceId')).toHaveTextContent('none');
  });

  it('should detect multiple canvases correctly', () => {
    const contextValue: ItemViewerContextV2Props = {
      ...mockDefaultContext,
      transformedManifest: {
        canvases: [
          { id: 'c1', label: 'Canvas 1' },
          { id: 'c2', label: 'Canvas 2' },
        ],
      },
      hasMultipleCanvases: true,
    };

    render(
      <ItemViewerContextV2.Provider value={contextValue}>
        <ContextValueDisplay />
      </ItemViewerContextV2.Provider>
    );

    expect(screen.getByTestId('hasMultipleCanvases')).toHaveTextContent('true');
  });

  it('should correctly identify restricted canvas', () => {
    const contextValue: ItemViewerContextV2Props = {
      ...mockDefaultContext,
      currentCanvas: {
        id: 'canvas-1',
        label: 'Canvas 1',
        accessConditions: [{ type: 'RestrictedAccess' }],
      },
      isCurrentCanvasRestricted: true,
    };

    render(
      <ItemViewerContextV2.Provider value={contextValue}>
        <ContextValueDisplay />
      </ItemViewerContextV2.Provider>
    );

    expect(screen.getByTestId('isCurrentCanvasRestricted')).toHaveTextContent('true');
  });

  it('should correctly calculate navigation booleans for first canvas', () => {
    const contextValue: ItemViewerContextV2Props = {
      ...mockDefaultContext,
      currentCanvasIndex: 0,
      transformedManifest: {
        canvases: [
          { id: 'c1', label: 'Canvas 1' },
          { id: 'c2', label: 'Canvas 2' },
          { id: 'c3', label: 'Canvas 3' },
        ],
      },
      hasMultipleCanvases: true,
      isFirstCanvas: true,
      isLastCanvas: false,
      canNavigateNext: true,
      canNavigatePrevious: false,
    };

    render(
      <ItemViewerContextV2.Provider value={contextValue}>
        <ContextValueDisplay />
      </ItemViewerContextV2.Provider>
    );

    expect(screen.getByTestId('isFirstCanvas')).toHaveTextContent('true');
    expect(screen.getByTestId('isLastCanvas')).toHaveTextContent('false');
    expect(screen.getByTestId('canNavigateNext')).toHaveTextContent('true');
    expect(screen.getByTestId('canNavigatePrevious')).toHaveTextContent('false');
  });

  it('should correctly calculate navigation booleans for last canvas', () => {
    const contextValue: ItemViewerContextV2Props = {
      ...mockDefaultContext,
      currentCanvasIndex: 2,
      transformedManifest: {
        canvases: [
          { id: 'c1', label: 'Canvas 1' },
          { id: 'c2', label: 'Canvas 2' },
          { id: 'c3', label: 'Canvas 3' },
        ],
      },
      hasMultipleCanvases: true,
      isFirstCanvas: false,
      isLastCanvas: true,
      canNavigateNext: false,
      canNavigatePrevious: true,
    };

    render(
      <ItemViewerContextV2.Provider value={contextValue}>
        <ContextValueDisplay />
      </ItemViewerContextV2.Provider>
    );

    expect(screen.getByTestId('isFirstCanvas')).toHaveTextContent('false');
    expect(screen.getByTestId('isLastCanvas')).toHaveTextContent('true');
    expect(screen.getByTestId('canNavigateNext')).toHaveTextContent('false');
    expect(screen.getByTestId('canNavigatePrevious')).toHaveTextContent('true');
  });

  it('should detect IIIF image service correctly', () => {
    const contextValue: ItemViewerContextV2Props = {
      ...mockDefaultContext,
      currentCanvas: {
        id: 'canvas-1',
        label: 'Canvas 1',
        imageServiceId: 'https://iiif.example.com/image/123',
      },
      hasIiifImageService: true,
      isImageZoomable: true,
    };

    render(
      <ItemViewerContextV2.Provider value={contextValue}>
        <ContextValueDisplay />
      </ItemViewerContextV2.Provider>
    );

    expect(screen.getByTestId('hasIiifImageService')).toHaveTextContent('true');
    expect(screen.getByTestId('isImageZoomable')).toHaveTextContent('true');
  });

  it('should detect born digital works', () => {
    const contextValue: ItemViewerContextV2Props = {
      ...mockDefaultContext,
      work: {
        id: 'work-1',
        title: 'Test Work',
        production: [{ type: 'BornDigital' }],
      },
      isWorkBornDigital: true,
    };

    render(
      <ItemViewerContextV2.Provider value={contextValue}>
        <ContextValueDisplay />
      </ItemViewerContextV2.Provider>
    );

    expect(screen.getByTestId('isWorkBornDigital')).toHaveTextContent('true');
  });
});

1.2 Mock Utilities with Types

File: content/webapp/contexts/ItemViewerContextV2/test-utils.ts

1.3 Component Integration Tests

File: content/webapp/views/pages/works/work/IIIFViewer/IIIFViewer.refactored.test.tsx

File: content/webapp/views/pages/works/work/IIIFViewer/ViewerTopBar.refactored.test.tsx

Manual Testing Checklist

See 14-testing-strategy.md for the complete manual testing checklist.


Last Updated: 1 April 2026

Last updated