Skip to content

Latest commit

 

History

History
2906 lines (2041 loc) · 69 KB

File metadata and controls

2906 lines (2041 loc) · 69 KB

CDI Viewer - Comprehensive Testing Plan

Test Implementation Progress

✅ Completed Tests

  • File Loading (7/7 tests - 100%) - tests/e2e/standalone/file-loading.spec.ts

    • Load local JSON-LD file
    • Load complex nested structure
    • Load file without @context
    • Handle invalid JSON-LD
    • Load Schema.org dataset (generic mode)
    • Verify validation auto-runs
    • Handle edit mode toggle
    • Handle search functionality
  • Validation (5/6 tests - 83%) - tests/e2e/standalone/validation.spec.ts

    • Auto-validate on file load
    • Debounce validation on rapid edits
    • Show validation details on violations
    • Validate after entering edit mode
    • Not run validation in parallel
    • Handle validation with different shape sources (FAILING - validation status becomes hidden after shape switch)
  • Editing (8/9 tests - 89%) - tests/e2e/standalone/editing.spec.ts

    • Enable edit mode
    • Disable edit mode
    • Edit text property
    • Handle empty text field
    • Preserve required properties
    • Delete optional property
    • Mark changed properties visually
    • Handle rapid edits with debounced validation
    • Preserve "changed" marking when toggling edit mode (FAILING - visual marking disappears on mode toggle)
  • Search (8/8 tests - 100%) - Search functionality working correctly

    • Highlight search matches in content
    • Navigate between search matches
    • Support case-sensitive search
    • Support regex search
    • Clear search and remove highlights
    • Handle search with no results
    • Persist search when toggling edit mode
    • Keyboard shortcuts (F3, Shift+F3, Enter)
  • Namespace Management (6/9 tests - 67%) - tests/e2e/standalone/namespace-management.spec.ts

    • Display existing namespaces
    • Open add namespace modal
    • Add custom namespace
    • Validate prefix uniqueness (FAILING)
    • Validate URI format
    • Delete custom namespace (FAILING)
    • Protected namespaces handling
    • Edit namespace URI
    • Toggle namespace section visibility (FAILING)
  • Array Operations (5/9 tests - 56%) - tests/e2e/standalone/array-operations.spec.ts

    • Display array values correctly (FAILING)
    • Add new value to array
    • Delete array value
    • Edit array value
    • Convert single value to array (FAILING)
    • Convert array to single value (FAILING)
    • Handle array with object references
    • Maintain array value order
    • Validate array operations preserve data integrity (FAILING)
  • Export (5/8 tests - 63%) - tests/e2e/standalone/export.spec.ts

    • Export JSON-LD data
    • Export with pretty-print formatting
    • Preserve user changes in export (FAILING)
    • Include all namespaces in exported data
    • Export without requiring edit mode
    • Use correct MIME type for export (FAILING)
    • Preserve property order in export
    • Handle export with validation errors present (FAILING)
  • Custom Namespace Properties (7/12 tests - 58%) - tests/e2e/standalone/custom-namespace-properties.spec.ts

    • Show custom namespace nodes in editor
    • Display add properties section for custom namespace nodes
    • Add custom property to custom namespace node using inline UI (FAILING - property not added)
    • Add custom property without namespace to custom node (FAILING)
    • Add multiple custom properties to same custom node (FAILING)
    • Edit custom property value in custom namespace node (FAILING)
    • Delete custom property from custom namespace node (FAILING)
    • Add complex property (node reference) to custom namespace node
    • Preserve custom properties when toggling edit mode (FAILING)
    • Validate that custom property input is inline, not a popup ✅ BUG FIXED
    • Handle Enter key in custom property input (FAILING)
    • Show validation for empty custom property name
  • Custom Property UI (7/13 tests - 54%) - tests/e2e/standalone/custom-property-ui.spec.ts

    • Use inline UI for adding custom properties, not popup ✅ BUG FIXED
    • NOT have old-style popup button for adding custom properties ✅ BUG FIXED
    • Nodes without SHACL suggestions should still have inline custom property UI ✅ BUG FIXED
    • Custom property inline UI should match screenshot 1 ✅ BUG FIXED
    • Custom property UI should be in a bordered section below property list
    • Adding custom property via inline UI should work (FAILING - property not added)
    • Adding custom property with namespace via inline UI should work (FAILING)
    • Inline UI should clear input after adding property (FAILING)
    • Inline UI should show validation message for empty property name
    • Namespace selector should have 'Add new namespace' option
    • All nodes should consistently use the same add property UI ✅ BUG FIXED
    • Inline UI should support keyboard navigation (FAILING)
    • Should not call browser prompt() when adding custom property ✅ BUG FIXED
  • Document Creation (0/8 tests - 0%) - tests/e2e/standalone/document-creation.spec.ts

    • Create new DDI-CDI document - FAILING (selector or timing issues with Add Root Node dropdown)
    • Create Schema.org document - FAILING (add namespace button timing)
    • Add multiple root nodes - FAILING (Add Root Node functionality timing)
    • Create document without shapes - FAILING (custom node type input selector)
    • Create document with default properties - FAILING (Add Root Node dropdown timing)
    • Auto-enable edit mode on document creation - FAILING (Add Root Node section not appearing)
    • Preserve context when adding nodes - FAILING (namespace button timing)
    • Generate default filename for new document - FAILING (Add Root Node timing)
    • NOTE: Feature exists per screenshot - tests need selector/timing refinement
  • Dataverse Load (2/5 tests - 40%) - tests/e2e/dataverse/load-from-dataverse.spec.ts

    • Load via URL parameters (integrated mode)
    • Load via "Load from Dataverse" button - FAILING (button doesn't exist or different selector)
    • Load with API token - FAILING (UI elements don't match expected selectors)
    • Handle load error from Dataverse
    • Parse multiple Dataverse URL formats - FAILING (UI elements don't match expected selectors)
  • Dataverse Save (1/5 tests - 20%) - tests/e2e/dataverse/save-to-dataverse.spec.ts

    • Save button visible in integrated mode
    • Save as new file option - FAILING (save modal structure different than expected)
    • Replace existing file option - FAILING (save modal options different)
    • Save with modifications - FAILING (save confirmation flow different)
    • Handle save error - FAILING (error handling flow different)
    • NOTE: Feature works manually per user - tests need modal selector refinement
  • Embedded Mode (4/6 tests - 67%) - tests/e2e/dataverse/embedded-mode.spec.ts

    • Detect embedded mode from URL parameters
    • Hide file loading buttons in embedded mode
    • Show save button in embedded mode
    • Pre-fill filename in save modal - FAILING (save modal doesn't exist or different structure)
    • Maintain Dataverse context during edits
    • Handle missing fileId parameter - FAILING (error handling incomplete)
  • Error Handling (2/4 tests - 50%) - tests/e2e/dataverse/error-handling.spec.ts

    • Handle invalid JSON-LD file - FAILING (missing test fixture: invalid-syntax.json)
    • Handle network error when loading shapes - FAILING (shape loading functionality different than expected)
    • Handle validation errors gracefully
    • Handle export with no data loaded
  • Cross-Browser (2/5 tests - 40%) - tests/e2e/cross-browser/compatibility.spec.ts

    • Load and display file correctly
    • Edit mode functions correctly - FAILING (changed class not applied or timing issue)
    • Search functionality works
    • Export functionality works - FAILING (export timing or download handling issue)
    • Collapse/expand nodes works - FAILING (collapsed class not applied correctly)
  • Responsive (0/4 tests - 0%) - tests/e2e/cross-browser/responsive.spec.ts

    • Mobile view (375px) - Layout adapts correctly - FAILING (horizontal overflow or layout issues)
    • Tablet view (768px) - All controls accessible - FAILING (layout or control visibility issues)
    • Desktop view (1920px) - Full layout displayed - FAILING (layout or sizing issues)
    • Touch interactions work on mobile - FAILING (tap events or collapse functionality issues)

Total Progress: 137 tests created, 76 passing (55%)

Test Suites Summary:

  • ✅ File Loading: 7/7 (100%)
  • 🟡 Validation: 5/6 (83%)
  • 🟡 Editing: 8/9 (89%) - "Changed" marking bug identified
  • 🟢 Search: 8/8 (100%) - ✅ Search works perfectly!
  • 🟡 Namespace Management: 6/9 (67%)
  • 🟡 Array Operations: 5/9 (56%)
  • 🟡 Export: 5/8 (63%)
  • 🟡 Custom Namespace Properties: 7/12 (58%) - UI fixed, property addition still broken
  • 🟡 Custom Property UI: 7/13 (54%) - ✅ 5 BUGS FIXED (popup UI replaced with inline)
  • 🔴 Document Creation: 0/8 (0%) - Tests identify missing "Add Root Node" functionality
  • 🟡 Dataverse Load: 2/5 (40%)
  • 🟡 Dataverse Save: 1/5 (20%)
  • 🟡 Integrated Mode: 4/6 (67%)
  • 🟡 Error Handling: 2/4 (50%)
  • 🟡 Cross-Browser: 2/5 (40%)
  • 🔴 Responsive: 0/4 (0%) - Tests identify responsive design issues

Bugs Fixed in This Session:

  1. "Add Custom Property" popup button removed - Replaced with inline UI in both renderBlankNode and renderNode
  2. Inline UI now shown for all nodes - Even nodes without SHACL suggestions get unified inline UI
  3. No more browser prompt() - Old popup implementation completely removed
  4. Consistent UI across all nodes - All nodes use the same unified add component
  5. UI matches screenshot 1 - Namespace selector + inline input + Add button

Key Issues Still Broken:

  1. "Changed" marking disappears on mode toggle: Data changes are preserved but visual highlighting (teal) is removed when switching between edit/view mode (affecting 1 test)
  2. Property addition not working: Clicking "Add" in inline UI doesn't add the property (affecting 7 tests)
  3. Search functionality: Highlights matches correctly
  4. Array operations: Display and conversion issues
  5. Export: User changes not being preserved
  6. Namespace validation: Prefix uniqueness validation not working

Next Steps:

  1. Fix property addition in inline UI
  2. Fix array operations and conversion features
  3. Ensure export preserves all user modifications
  4. Complete remaining test suites (Document Creation, Dataverse, Cross-Browser)

Testing Methodology & Quality Standards

Core Principles

1. Tests Find Bugs, Not Just Pass

  • Tests should fail when functionality is broken
  • Tests should reflect real user behavior
  • Never modify tests to pass - fix the code instead
  • If a test fails, ask:
    1. Is the test wrong? (Check actual UI behavior manually)
    2. Is the code wrong? (Fix the bug)
    3. Is the requirement wrong? (Update documentation)

2. No Shortcuts in Test Implementation

  • BAD: await expect(page.locator('.node-type')).toBeVisible()
    • Problem: Doesn't verify WHICH type is shown
  • GOOD: await expect(page.locator('[data-testid="node-type-0"]')).toHaveText('WideDataSet')
    • Verifies the exact type value

3. Test What Users See

  • Verify actual rendered content, not just presence
  • Check counts, text, values, states
  • Test interactions produce expected results
  • Validate error messages are clear and actionable

4. Comprehensive Assertions

// ❌ Insufficient
await expect(page.locator(".node-card")).toBeVisible();

// ✅ Comprehensive
await expect(page.locator(".node-card")).toHaveCount(26);
await expect(page.locator('[data-node-id="#Sample_Dataset"]')).toBeVisible();
await expect(page.locator('[data-testid="node-type-0"]')).toHaveText(
  "WideDataSet"
);
await expect(page.locator('[data-testid="property-name"]')).toContainText(
  "Sample Dataset"
);

5. Test Isolation

  • Each test should be independent
  • Tests should clean up after themselves
  • Use fresh browser context for each test
  • Don't rely on execution order

6. Handling Duplicate data-testid Values

Problem: When multiple elements share the same data-testid, Playwright throws a "strict mode violation" error:

Error: locator('[data-testid="property-identifier"]') resolved to 2 elements

Root Cause: Test data may contain duplicate property names across different nodes. For example, both #dataset1 and #variable1 might have an "identifier" property, resulting in multiple elements with data-testid="property-identifier".

Solution: Make selectors node-specific by including the parent data-node-id attribute:

// ❌ BAD: Ambiguous selector (may match multiple elements)
const property = page.locator('[data-testid="property-identifier"]');

// ✅ GOOD: Node-specific selector (matches exactly one element)
const property = page.locator(
  '[data-node-id="#dataset1"][data-testid="property-identifier"]'
);

Pattern for Dynamic Tests:

  1. Find the parent context (e.g., property row or node card)
  2. Extract the data-node-id attribute
  3. Build a compound selector that includes both node ID and test ID
// Example: Delete property test
const deleteBtn = page
  .locator('[data-testid="delete-property-btn-identifier"]')
  .first();
const propertyRow = deleteBtn.locator(
  'xpath=ancestor::div[contains(@class, "property-row")][1]'
);
const nodeId = await propertyRow.getAttribute("data-node-id");
const propertyKey = "identifier";

// Build node-specific selector
const selector = `[data-node-id="${nodeId}"][data-testid="property-${propertyKey}"]`;
const property = page.locator(selector);

// Now assertions are precise
await expect(property).toHaveClass(/deleted/);

Key Takeaways:

  • Always check for duplicate data-testid values when debugging "strict mode violation" errors
  • Use XPath ancestor:: to navigate from a specific element to its parent container
  • Include parent context (like data-node-id) in selectors to disambiguate
  • If manual testing shows the feature works but the test fails, suspect selector ambiguity

Testable Elements Reference

Static HTML Elements (index.html)

Toolbar Buttons

// Edit Mode Toggle
"#toggle-edit-btn"; // Text changes: "Enable Editing" → "View Mode"
"#toggle-edit-btn .glyphicon"; // Icon changes: glyphicon-edit → glyphicon-eye-open

// Save Button (Dataverse mode)
"#save-btn"; // Hidden initially, visible in edit mode

// Export Button
"#export-btn"; // Always visible

// Collapse/Expand All
"#collapse-all-btn"; // Collapses all node cards
"#expand-all-btn"; // Expands all node cards

// Validation Status
"#validation-status"; // Shows validation badge
"#validation-details"; // Shows detailed violations

File Loading

// Local File Input
"#local-file-input"; // File input (hidden)
"#load-local-btn"; // Triggers file input

// Dataverse Loading
"#load-dataverse-btn"; // Opens Dataverse modal
"#loadDataverseModal"; // Modal for loading from Dataverse

// Shape Selector
"#shape-selector"; // Dropdown for SHACL shapes
"#custom-shape-url"; // Input for custom shape URL (hidden by default)

Search & Filter

// Search Input
"#search-input"; // Main search box
"#clear-search-btn"; // Clear search (× button)
"#search-counter"; // Shows "X of Y matches"

// Search Controls
"#toggle-case-btn"; // Toggle case sensitivity
"#toggle-regex-btn"; // Toggle regex mode
"#prev-match-btn"; // Previous match
"#next-match-btn"; // Next match

// Search Scope
'input[name="search-scope"]'; // Radio buttons (all/keys/values/types)

// Filter Panel
"#toggle-filter-panel"; // Toggle filter panel visibility
"#validation-filter"; // Dropdown: all/valid/invalid/modified/missing-required
'input[name="property-status"]'; // Radio: all/shacl-only/extra-only
"#clear-all-filters-btn"; // Clear all filters

Namespace Management

"#namespace-section"; // Namespace section container
"#namespace-content"; // Collapsible content
"#toggle-namespace-btn"; // Collapse/expand button
"#add-namespace-btn"; // Add namespace (edit mode only)
"#namespace-table-body"; // Table body with namespace rows
"#namespaceModal"; // Modal for add/edit namespace

Content Area

"#content"; // Main content container where nodes render
"#add-root-node-container"; // Add root node component (edit mode)

Dynamically Generated Elements (render.js)

Node Cards

// Node Card Container
".node-card"; // All node cards
'[data-node-id="<id>"]'; // Specific node by ID
'[data-testid="node-card-<sanitized-id>"]'; // Test-specific selector

// Node Header
".node-header"; // Clickable header for collapse/expand
'[data-testid="node-header-<sanitized-id>"]';
".collapse-icon"; // Chevron icon (down when expanded, right when collapsed)

// Node Identity
".node-id"; // Node @id value
'[data-testid="node-id-<sanitized-id>"]';
".node-type"; // Node @type value(s)
'[data-testid="node-type-0"]'; // First type
'[data-testid="node-type-1"]'; // Second type (if multiple)

// Node Body
".node-body"; // Contains all properties
'[data-testid="node-body"]';
".node-body.view-mode"; // View mode styling

Property Rows

// Property Row Container
".property-row"; // All property rows
'[data-property="<key>"]'; // Specific property by key
'[data-testid="property-<sanitized-key>"]';

// Property Classification
".property-row.shacl-defined"; // SHACL-defined property
".property-row.extra-field"; // Extra field (not in shape)
".property-row.required"; // Required property
".property-row.changed"; // Modified in edit mode (teal highlight)

// Property Badge
".property-badge"; // Classification badge
'[data-testid="badge-<sanitized-key>"]';
".property-badge.required"; // "REQUIRED" badge (red)
".property-badge.shacl-defined"; // "SHACL" badge (blue)
".property-badge.extra"; // "EXTRA" badge (yellow)

// Property Labels
".property-label"; // Human-readable label
'[data-testid="property-label"]';
".property-path"; // Technical path/key
'[data-testid="property-path"]';

// Property Values
".property-value"; // Value container
'[data-testid="property-value"]';
".value-display"; // Text display (view mode)
'input[data-property="<key>"]'; // Input field (edit mode)
'select[data-property="<key>"]'; // Dropdown (edit mode)
".array-value"; // Array value wrapper
'[data-array-index="<n>"]'; // Array element by index

Buttons & Actions

// Property Actions
".delete-property-btn"; // Delete property (× button)
".convert-to-array-btn"; // Convert single value to array
".convert-to-single-btn"; // Convert array to single value
".add-value-btn"; // Add array element
".add-reference-btn"; // Add reference/object
".jump-to-node-btn"; // Jump to referenced node

// Add Property Section
".add-property-section"; // Add properties section (edit mode)
".item-dropdown"; // Property dropdown
".item-description"; // Property description area
".add-item-btn"; // Add selected property button
".custom-item-section"; // Custom property section

Inline Nested Nodes

".inline-node-card"; // Nested object displayed inline
".node-reference-link"; // Reference link with jump button

Validation Indicators

// Node-level validation
".node-card.valid"; // Valid node
".node-card.invalid"; // Invalid node
".validation-icon"; // ✓ or ✗ icon

// Property-level validation
".property-row.invalid"; // Invalid property
".validation-error-message"; // Error message

Modals

// Load from Dataverse
"#loadDataverseModal"; // Modal container
"#file-url-input"; // File URL input
"#api-token-input"; // API token input (optional)

// Save to Dataverse
"#saveModal"; // Modal container
"#save-as-new-radio"; // Save as new file radio
"#replace-existing-radio"; // Replace existing radio

// Add/Edit Namespace
"#namespaceModal"; // Modal container
"#namespace-prefix-input"; // Prefix input
"#namespace-uri-input"; // URI input

// Add Reference/Object
"#addReferenceModal"; // Modal container
"#existingNodeSelect"; // Existing node dropdown
"#newNodeType"; // New node type input
"#confirmAddReference"; // Confirm button

Alert Messages

".alert-success"; // Success message
".alert-danger"; // Error message
".alert-warning"; // Warning message
".alert-info"; // Info message

Element Identification Strategy

1. Stable Selectors Priority

  1. data-testid attributes (most stable)
  2. id attributes (stable if unique)
  3. data-* custom attributes
  4. CSS classes (less stable, can change with styling)
  5. Text content (least stable, changes with i18n)

2. Combining Selectors

// ✅ Good: Specific and stable
page.locator('[data-node-id="#Sample_Dataset"]');
page.locator('[data-testid="property-name"]');

// ✅ Good: Scoped within parent
page.locator(".node-card").filter({ hasText: "WideDataSet" });
page
  .locator('[data-node-id="#Sample_Dataset"]')
  .locator('[data-property="name"]');

// ⚠️ Use carefully: Text-based
page.locator('button:has-text("Enable Editing")');

// ❌ Avoid: Too broad
page.locator(".btn");
page.locator("input");

3. Dynamic IDs

For elements with dynamic IDs (like blank nodes), use:

// Sanitize ID for test selector
const sanitizedId = id.replace(/[^a-zA-Z0-9]/g, "_");
// Results in: #Sample_Dataset → _Sample_Dataset
//             _:b0 → __b0

Overview

This document outlines a comprehensive testing strategy for the CDI Viewer using Playwright for automated end-to-end testing. The tests cover both standalone mode (GitHub Pages) and Dataverse integration mode (previewer), ensuring all functionality works correctly in both contexts.

Testing Architecture

Technology Stack

  • Test Framework: Playwright
  • Languages: TypeScript for type safety
  • Mocking Strategy:
    • Mock Service Worker (MSW) for Dataverse API endpoints
    • Local test files in examples/cdi/
    • Mock SHACL shape responses
  • Test Organization: Page Object Model pattern
  • CI/CD: GitHub Actions integration

Directory Structure

tests/
├── e2e/
│   ├── standalone/
│   │   ├── file-loading.spec.ts
│   │   ├── editing.spec.ts
│   │   ├── validation.spec.ts
│   │   ├── search-filter.spec.ts
│   │   ├── namespace-management.spec.ts
│   │   ├── document-creation.spec.ts
│   │   ├── array-operations.spec.ts
│   │   └── export.spec.ts
│   ├── dataverse/
│   │   ├── load-from-dataverse.spec.ts
│   │   ├── save-to-dataverse.spec.ts
│   │   ├── integrated-mode.spec.ts
│   │   └── error-handling.spec.ts
│   └── cross-browser/
│       ├── compatibility.spec.ts
│       └── responsive.spec.ts
├── fixtures/
│   ├── test-data/
│   │   ├── simple.jsonld
│   │   ├── complex-nested.jsonld
│   │   ├── schema-org-dataset.jsonld
│   │   ├── invalid-data.jsonld
│   │   └── minimal-ddi-cdi.jsonld
│   ├── shapes/
│   │   ├── test-shapes.ttl
│   │   └── minimal-shapes.ttl
│   └── mocks/
│       ├── dataverse-api-responses.ts
│       └── shape-responses.ts
├── page-objects/
│   ├── ViewerPage.ts
│   ├── ToolbarComponent.ts
│   ├── FilterPanelComponent.ts
│   ├── NodeCardComponent.ts
│   ├── PropertyRowComponent.ts
│   ├── NamespaceModalComponent.ts
│   └── SaveModalComponent.ts
├── helpers/
│   ├── dataverse-mock-server.ts
│   ├── test-data-generator.ts
│   └── assertions.ts
└── playwright.config.ts

Test Suite Categories

1. Standalone Mode Tests

Tests for the viewer running independently on GitHub Pages without Dataverse integration.

1.1 File Loading Tests

Test 1.1.1: Load Local JSON-LD File

Setup:

  • Navigate to https://libis.github.io/cdi-viewer/
  • Wait for page to fully load
  • DDI-CDI shapes should auto-load

Actions:

  1. Click "Load Local File" button
  2. Upload fixtures/test-data/simple.jsonld
  3. Wait for rendering to complete

Expected Results:

  • File loads successfully
  • Success message appears: "Loaded: simple.jsonld"
  • All nodes from @graph are rendered as cards
  • Properties are visible and correctly classified
  • Namespace section appears and shows context prefixes
  • Filter panel remains collapsed
  • Validation runs automatically
  • Validation status badge appears

Assertions:

await expect(page.locator(".alert-success")).toContainText(
  "Loaded: simple.jsonld"
);
await expect(page.locator(".node-card")).toHaveCount(expectedNodeCount);
await expect(page.locator("#namespace-section")).toBeVisible();
await expect(page.locator("#namespace-content")).toBeHidden(); // Collapsed by default
await expect(page.locator("#validation-status")).toBeVisible();
Test 1.1.2: Load Complex Nested Structure

Setup:

  • Clean browser state
  • Navigate to viewer

Actions:

  1. Load fixtures/test-data/complex-nested.jsonld (contains 10+ nodes, nested objects, arrays)
  2. Wait for complete rendering

Expected Results:

  • All nodes render correctly
  • Nested nodes show proper indentation
  • References between nodes are clickable
  • Jump buttons navigate to referenced nodes
  • Collapse/expand works for all nodes

Assertions:

await expect(page.locator(".node-card")).toHaveCount(10, { timeout: 5000 });
const jumpButtons = page.locator('button:has-text("Jump to")');
await expect(jumpButtons).toHaveCount(expectedReferenceCount);
// Test reference navigation
await jumpButtons.first().click();
await expect(page.locator(".node-card.highlight")).toBeVisible();
Test 1.1.3: Load File Without @context

Setup:

  • Navigate to viewer

Actions:

  1. Attempt to load fixtures/test-data/no-context.jsonld

Expected Results:

  • File loads but shows warning
  • Namespace section not displayed
  • Properties still render
  • Generic JSON-LD mode activated
Test 1.1.4: Load Invalid JSON-LD

Setup:

  • Navigate to viewer

Actions:

  1. Attempt to load fixtures/test-data/invalid-syntax.json (invalid JSON)

Expected Results:

  • Error alert appears
  • Error message: "Failed to load file: Unexpected token..."
  • Content area remains empty
  • No crash or console errors
Test 1.1.5: Load File with Generic Mode

Setup:

  • Navigate to https://libis.github.io/cdi-viewer/?shacl=generic

Actions:

  1. Load fixtures/test-data/schema-org-dataset.jsonld
  2. Select "Custom URL" from shape selector
  3. Enter custom shape URL
  4. Load shapes

Expected Results:

  • File loads without DDI-CDI shapes
  • Can select different shape vocabularies
  • Properties initially shown as EXTRA (yellow badges)
  • After loading shapes, properties reclassified correctly

1.2 Editing Mode Tests

Test 1.2.1: Enable Edit Mode

Setup:

  • Load a test file
  • Verify in view mode initially

Actions:

  1. Click "Enable Editing" button
  2. Wait for UI changes

Expected Results:

  • Button changes to "Disable Editing" (warning style)
  • Input fields become editable
  • "Save to Dataverse" button appears
  • Add property sections appear
  • Add Root Node component appears at bottom
  • "Add Namespace" button appears
  • Validation runs automatically

Assertions:

await page.click("#toggle-edit-btn");
await expect(page.locator("#toggle-edit-btn")).toHaveClass(/btn-warning/);
await expect(page.locator("#save-btn")).toBeVisible();
await expect(page.locator("#add-root-node-container")).toBeVisible();
await expect(page.locator('input[type="text"]').first()).toBeEnabled();
Test 1.2.2: Edit Simple Text Property

Setup:

  • Load file and enable edit mode

Actions:

  1. Locate a text property (e.g., name)
  2. Clear existing value
  3. Type new value: "Updated Name"
  4. Tab out or click elsewhere

Expected Results:

  • Property row gains "changed" class (teal highlight)
  • Original value stored as data attribute
  • Validation updates after 3 seconds (debounced)
  • Save button remains enabled

Assertions:

const nameInput = page.locator('input[data-property="name"]');
await nameInput.fill("Updated Name");
await expect(nameInput.closest(".property-row")).toHaveClass(/changed/);
// Wait for debounced validation
await page.waitForTimeout(3500);
await expect(page.locator("#validation-status")).toBeVisible();
Test 1.2.3: Edit Number Property

Setup:

  • Load file with numeric property
  • Enable edit mode

Actions:

  1. Find number input (e.g., xsd:integer property)
  2. Enter invalid text: "abc"
  3. Enter valid number: "42"

Expected Results:

  • Invalid text rejected by input type="number"
  • Valid number accepted
  • Property marked as changed
  • Validation passes
Test 1.2.4: Edit Date Property

Setup:

  • Load file with date property
  • Enable edit mode

Actions:

  1. Click date picker icon
  2. Select date from calendar widget
  3. Verify format

Expected Results:

  • Date picker opens
  • Selected date appears in ISO format (YYYY-MM-DD)
  • Property marked as changed
Test 1.2.5: Delete Property

Setup:

  • Load file and enable edit mode

Actions:

  1. Click delete button (×) on optional property
  2. Confirm deletion if prompted

Expected Results:

  • Property row removed from DOM
  • Property removed from data structure
  • Save button remains enabled
  • Validation updates

Assertions:

const propertyRow = page.locator('.property-row:has-text("optional-field")');
await propertyRow.locator(".delete-property-btn").click();
await expect(propertyRow).not.toBeVisible();
Test 1.2.6: Attempt to Delete Required Property

Setup:

  • Load file with required properties (sh:minCount > 0)
  • Enable edit mode

Actions:

  1. Locate required property (marked with *)
  2. Attempt to click delete button

Expected Results:

  • Delete button not present OR disabled
  • Alert appears: "Cannot delete required property"
  • Property remains in UI
Test 1.2.7: Edit Enumeration Property

Setup:

  • Load file with sh:in constraint
  • Enable edit mode

Actions:

  1. Click dropdown for enumeration property
  2. Select different value from list
  3. Verify change

Expected Results:

  • Dropdown shows all valid values from sh:in
  • Selected value updates
  • No invalid values can be entered
  • Property marked as changed

1.3 Property Addition Tests

Test 1.3.1: Add SHACL-Defined Property

Setup:

  • Load file and enable edit mode
  • Scroll to "Add Properties" section

Actions:

  1. Click property dropdown
  2. Search for property (e.g., "description")
  3. Select from dropdown
  4. Click "Add Property" button

Expected Results:

  • New property row appears in node
  • Input field or appropriate widget rendered
  • Property classified as SHACL-defined (blue badge if optional, red if required)
  • Property removed from "Add Properties" dropdown
  • Save button enabled

Assertions:

await page.locator(".add-property-dropdown").selectOption("description");
await page.click(".add-property-btn");
await expect(
  page.locator('.property-row:has-text("description")')
).toBeVisible();
await expect(
  page.locator('.add-property-dropdown option:has-text("description")')
).not.toBeVisible();
Test 1.3.2: Add Custom Property

Setup:

  • Load file and enable edit mode

Actions:

  1. Click "Add Custom Property" in unified add component
  2. Select namespace from dropdown
  3. Enter property name: "customField"
  4. Press Enter or click Add

Expected Results:

  • Custom property added with selected namespace prefix
  • Property classified as EXTRA (yellow badge)
  • Text input rendered by default
  • Property editable immediately
Test 1.3.3: Add Property with Cardinality Constraint

Setup:

  • Load shapes with sh:maxCount = 1 property
  • Enable edit mode

Actions:

  1. Add property that has maxCount = 1
  2. Verify it disappears from dropdown
  3. Attempt to add same property again

Expected Results:

  • Property added successfully first time
  • Property no longer available in dropdown
  • Cannot add duplicate
Test 1.3.4: Add Required Property (Priority Indication)

Setup:

  • Load file missing required properties
  • Enable edit mode

Actions:

  1. Check "Add Properties" dropdown
  2. Locate required properties (marked with ⚠️)

Expected Results:

  • Required properties appear at top of dropdown
  • Visual indicator (⚠️ or red text) shows priority
  • Adding required property improves validation score

1.4 Complex Object Tests

Test 1.4.1: Create Nested Object

Setup:

  • Load file and enable edit mode
  • Find property with sh:node or sh:class constraint

Actions:

  1. Click "Add Reference/Object" button on property
  2. Modal opens
  3. Select "Create new object" radio
  4. Enter type name: "Person"
  5. Click "Add" button

Expected Results:

  • Modal closes
  • New blank node created with @type: Person
  • Reference added to parent property: {"@id": "_:b0"}
  • New node card renders inline/nested
  • Node is immediately editable

Assertions:

await page.click('button:has-text("Add Reference/Object")');
await page.locator("#add-reference-modal").waitFor({ state: "visible" });
await page.click('input[value="create-new"]');
await page.fill("#new-node-type-input", "Person");
await page.click("#confirm-add-reference-btn");
await expect(page.locator('.node-card:has-text("Person")')).toBeVisible();
Test 1.4.2: Reference Existing Node

Setup:

  • Load file with multiple nodes
  • Enable edit mode

Actions:

  1. Click "Add Reference/Object" on property
  2. Select "Reference existing node" radio
  3. Choose node from dropdown (shows all @id values)
  4. Click "Add"

Expected Results:

  • Reference created: {"@id": "selectedNodeId"}
  • Jump button appears in property value
  • Clicking jump button scrolls to referenced node
  • Referenced node highlights briefly
Test 1.4.3: Edit Nested Object Properties

Setup:

  • Create or load file with nested objects
  • Enable edit mode

Actions:

  1. Expand parent node
  2. Locate nested child node
  3. Edit properties in nested node
  4. Save changes

Expected Results:

  • Nested node properties editable
  • Changes tracked independently
  • Validation applies to nested structure
  • Export includes nested changes

1.5 Array Operation Tests

Test 1.5.1: Convert Single Value to Array

Setup:

  • Load file with single-value property
  • Enable edit mode

Actions:

  1. Locate property with single value
  2. Click "Convert to Array" button
  3. Confirm operation

Expected Results:

  • Value becomes first array element
  • "Add Value" button appears
  • "Add Reference/Object" button appears (if applicable)
  • "Convert to Single Value" button appears
  • Array index shown: [0], [1], etc.

Assertions:

await page.click('button:has-text("Convert to Array")');
await expect(page.locator('.array-index:has-text("[0]")')).toBeVisible();
await expect(page.locator('button:has-text("Add Value")')).toBeVisible();
Test 1.5.2: Add Values to Array

Setup:

  • Create array property
  • Enable edit mode

Actions:

  1. Click "Add Value" button
  2. Enter value in new input field
  3. Click "Add Value" again
  4. Add multiple values

Expected Results:

  • Each value gets array index: [0], [1], [2]...
  • All values editable independently
  • Delete button available for each value
  • Can reorder values (if implemented)
Test 1.5.3: Remove Value from Array

Setup:

  • Array property with multiple values
  • Enable edit mode

Actions:

  1. Click delete button on array element
  2. Confirm if prompted

Expected Results:

  • Value removed from array
  • Array indices renumber automatically
  • If last value removed, property remains as empty array or converts to single
Test 1.5.4: Convert Array to Single Value

Setup:

  • Array property with multiple values
  • Enable edit mode

Actions:

  1. Click "Convert to Single Value" button
  2. Confirm operation (warning: will lose all but first value)

Expected Results:

  • Alert/confirm dialog appears
  • After confirmation, only first value remains
  • Array controls disappear
  • "Convert to Array" button appears
Test 1.5.5: Add Reference to Array

Setup:

  • Array property that accepts references
  • Enable edit mode

Actions:

  1. Click "Add Reference/Object" on array property
  2. Choose to reference existing or create new
  3. Add reference

Expected Results:

  • Reference added to array
  • Jump button appears
  • Can add multiple references to same array
  • Mix of references and values possible (if allowed by schema)

1.6 Validation Tests

Test 1.6.1: Auto-Validation on File Load

Setup:

  • Navigate to viewer with shapes preloaded

Actions:

  1. Load file with validation errors

Expected Results:

  • Validation runs automatically after load
  • Status badge appears within 3 seconds
  • Invalid properties marked with red highlight
  • Validation count shown: "X violations found"

Assertions:

await page.setInputFiles(
  "#local-file-input",
  "fixtures/test-data/invalid-data.jsonld"
);
await expect(page.locator("#validation-status")).toContainText(
  "Validating...",
  { timeout: 1000 }
);
await expect(page.locator("#validation-status")).toContainText(
  "violations found",
  { timeout: 4000 }
);
await expect(page.locator(".property-row.invalid")).toHaveCount(
  expectedViolationCount
);
Test 1.6.2: Debounced Auto-Validation on Edit

Setup:

  • Load valid file and enable edit mode

Actions:

  1. Edit a property value
  2. Wait 2 seconds (within debounce)
  3. Edit another property
  4. Wait 3+ seconds (debounce completes)

Expected Results:

  • Validation doesn't trigger immediately on each keystroke
  • Validation triggers 3 seconds after last edit
  • Status shows "Validating..." then results
  • Changes reflected in validation report

Assertions:

await nameInput.fill("New Value 1");
await page.waitForTimeout(2000);
await descInput.fill("New Value 2");
// Validation should not have run yet
await expect(page.locator("#validation-status")).not.toContainText(
  "Validating..."
);
// Wait for debounce
await page.waitForTimeout(3500);
await expect(page.locator("#validation-status")).toContainText("Valid");
Test 1.6.3: Validation with Multiple Violations

Setup:

  • Load file with 5+ validation errors

Actions:

  1. Trigger validation
  2. Expand violation details
  3. Review all violations

Expected Results:

  • Status shows total count: "5 violations found"
  • "Show Details" button appears
  • Clicking button reveals list of violations
  • Each violation shows:
    • Node ID
    • Property path
    • Violation message
  • Invalid properties highlighted in red throughout UI
Test 1.6.4: Fix Validation Errors

Setup:

  • File with validation errors loaded
  • Edit mode enabled

Actions:

  1. Identify required field violation
  2. Add the required property
  3. Wait for auto-validation
  4. Verify error cleared

Expected Results:

  • Adding required property triggers validation
  • Violation count decreases
  • Property no longer highlighted red
  • When all fixed: "Data is valid" message
Test 1.6.5: Validation with Different Shape Sources

Setup:

  • Load file
  • Try different shape sources

Actions:

  1. Load DDI-CDI shapes → validate
  2. Switch to CDIF shapes → validate
  3. Load custom shape URL → validate
  4. Clear shapes (generic mode) → check validation status

Expected Results:

  • Each shape source validates differently
  • Appropriate properties classified for each vocabulary
  • Generic mode shows "Select SHACL shapes to enable validation"
  • No crashes when switching between shapes
Test 1.6.6: Validation Serialization (No Parallel Runs)

Setup:

  • Load file with long validation time (large file)
  • Enable edit mode

Actions:

  1. Trigger validation (edit a property)
  2. Immediately trigger again (edit another property)
  3. Monitor validation status

Expected Results:

  • First validation runs to completion
  • Second validation queues/waits
  • No parallel validation execution
  • No race conditions or duplicate reports

1.7 Search and Filter Tests

Test 1.7.1: Basic Text Search

Setup:

  • Load file with multiple nodes
  • File loaded in view mode

Actions:

  1. Type "dataset" in search box
  2. Verify matches highlight

Expected Results:

  • Matching text highlighted in yellow
  • Current match highlighted with pulse animation
  • Counter shows "1 of X matches"
  • Previous/Next buttons enabled

Assertions:

await page.fill("#search-input", "dataset");
await expect(page.locator(".search-match")).toHaveCount(expectedMatches);
await expect(page.locator("#search-counter")).toContainText(
  "1 of " + expectedMatches
);
await expect(page.locator("#next-match-btn")).toBeEnabled();
Test 1.7.2: Navigate Search Matches

Setup:

  • Active search with 5+ matches

Actions:

  1. Click "Next" button 3 times
  2. Click "Previous" button 2 times
  3. Use keyboard shortcuts: F3, Shift+F3

Expected Results:

  • Counter updates: "2 of 5", "3 of 5", etc.
  • Current match scrolls into view
  • Current match has stronger highlight
  • Wraps from last to first (and vice versa)
  • Keyboard shortcuts work
Test 1.7.3: Case-Sensitive Search

Setup:

  • File with mixed case content ("Dataset" and "dataset")

Actions:

  1. Search for "dataset"
  2. Note match count
  3. Click "Aa" button (enable case-sensitive)
  4. Compare match count

Expected Results:

  • Case-insensitive finds both "Dataset" and "dataset"
  • Case-sensitive finds only exact case matches
  • Button shows active state when enabled
  • Counter updates correctly
Test 1.7.4: Regex Search

Setup:

  • File loaded

Actions:

  1. Click ".*" button to enable regex
  2. Enter regex pattern: \d{4}-\d{2}-\d{2} (dates)
  3. Verify matches

Expected Results:

  • Regex mode enabled (button active)
  • Date strings highlighted
  • Invalid regex shows error message
  • Can disable regex mode

Assertions:

await page.click("#toggle-regex-btn");
await expect(page.locator("#toggle-regex-btn")).toHaveClass(/active/);
await page.fill("#search-input", "\\d{4}-\\d{2}-\\d{2}");
await expect(page.locator(".search-match")).toHaveCount(expectedDateCount);
Test 1.7.5: Search Scope Selection

Setup:

  • File loaded with diverse content

Actions:

  1. Uncheck "Values" checkbox
  2. Search for value that appears only in property values
  3. Verify no matches
  4. Re-check "Values" checkbox
  5. Verify matches appear

Expected Results:

  • Unchecking scope excludes that content type
  • Checking scope includes that content type
  • Multiple scopes can be combined
  • At least one scope must be checked
Test 1.7.6: Clear Search

Setup:

  • Active search with highlights

Actions:

  1. Click clear button (× in search box)

Expected Results:

  • Search box cleared
  • All highlights removed
  • Counter hidden
  • Navigation buttons disabled
  • Clear button hidden
Test 1.7.7: Validation Status Filter

Setup:

  • File with mix of valid and invalid nodes
  • Enable edit mode (triggers validation)

Actions:

  1. Click "Advanced Filters" button
  2. Select "Invalid Only" from validation dropdown
  3. Observe results

Expected Results:

  • Only invalid nodes visible
  • Valid nodes hidden with .hidden-by-filter class
  • Parents of invalid nodes remain visible (bottom-up filtering)
  • Filter badge shows "1 active filter"

Assertions:

await page.click("#toggle-filter-panel");
await page.selectOption("#validation-filter", "invalid");
await expect(page.locator(".node-card:not(.hidden-by-filter)")).toHaveCount(
  expectedInvalidCount
);
await expect(page.locator("#active-filter-badge")).toContainText("1");
Test 1.7.8: Property Status Filter

Setup:

  • File with SHACL shapes loaded
  • Mix of SHACL-defined and extra properties

Actions:

  1. Open Advanced Filters
  2. Select "SHACL Only" radio button
  3. Verify only SHACL-defined properties visible
  4. Select "Extra Only"
  5. Verify only extra properties visible

Expected Results:

  • SHACL Only: Yellow badge properties hidden
  • Extra Only: Blue/red badge properties hidden
  • Nodes with visible properties remain shown
  • Filter count updates
Test 1.7.9: Combined Filters

Setup:

  • Complex file with multiple node types

Actions:

  1. Apply validation filter: "Invalid Only"
  2. Apply property filter: "SHACL Only"
  3. Apply search: "identifier"
  4. Verify all filters work together

Expected Results:

  • Only invalid nodes shown
  • Only SHACL properties shown in those nodes
  • Only properties matching "identifier" highlighted
  • Bottom-up filtering ensures parent visibility
  • All filters tracked in badge count
Test 1.7.10: Clear All Filters

Setup:

  • Multiple filters active

Actions:

  1. Click "Clear All Filters" button in filter panel

Expected Results:

  • All filters reset to default ("All" options)
  • All nodes and properties visible again
  • Filter badge hidden
  • Search remains active (independent)
Test 1.7.11: Filter Persistence

Setup:

  • Apply several filters

Actions:

  1. Set validation filter: "Valid Only"
  2. Set property filter: "SHACL Only"
  3. Refresh page (F5)
  4. Load same or different file

Expected Results:

  • Filter settings restored from localStorage
  • Same filters automatically applied to new data
  • Filter panel state (open/closed) persisted

1.8 Namespace Management Tests

Test 1.8.1: View Existing Namespaces

Setup:

  • Load file with @context containing prefixes

Actions:

  1. Locate namespace section
  2. Expand if collapsed
  3. Review namespace table

Expected Results:

  • Namespace section visible
  • Table shows all prefixes and URIs from @context
  • Built-in namespaces marked (e.g., no delete button)
  • Custom namespaces have delete option

Assertions:

await page.click("#toggle-namespace-btn");
await expect(page.locator("#namespace-content")).toBeVisible();
await expect(page.locator("#namespace-table-body tr")).toHaveCount(
  expectedPrefixCount
);
Test 1.8.2: Add Custom Namespace

Setup:

  • Load file and enable edit mode

Actions:

  1. Click "Add Namespace" button
  2. Modal opens
  3. Enter prefix: "custom"
  4. Enter URI: "http://example.org/custom#"
  5. Click "Add Namespace"

Expected Results:

  • Modal closes
  • New row appears in namespace table
  • Prefix "custom" available in property creation
  • @context updated with new prefix
  • Export includes new namespace

Assertions:

await page.click("#add-namespace-btn");
await expect(page.locator("#namespaceModal")).toBeVisible();
await page.fill("#namespacePrefixInput", "custom");
await page.fill("#namespaceUriInput", "http://example.org/custom#");
await page.click("#confirmNamespaceBtn");
await expect(
  page.locator('#namespace-table-body tr:has-text("custom")')
).toBeVisible();
Test 1.8.3: Namespace Validation

Setup:

  • Open add namespace modal

Actions:

  1. Attempt invalid prefix: "123invalid" (starts with number)
  2. Attempt invalid URI: "not-a-url"
  3. Attempt duplicate prefix
  4. Enter valid values

Expected Results:

  • Invalid prefix rejected with error message
  • Invalid URI rejected with error message
  • Duplicate prefix rejected
  • Confirm button disabled until valid
  • Valid values accepted
Test 1.8.4: Remove Custom Namespace

Setup:

  • File with custom namespace added

Actions:

  1. Click delete button next to custom namespace
  2. Confirm deletion

Expected Results:

  • Namespace removed from table
  • Namespace removed from @context
  • Properties using that prefix remain but show full URI
  • Built-in namespaces cannot be deleted
Test 1.8.5: Use Custom Namespace in Property

Setup:

  • Custom namespace "myorg" added

Actions:

  1. Add custom property
  2. Select "myorg" from namespace dropdown
  3. Enter property name: "customField"
  4. Add property

Expected Results:

  • Property added as "myorg:customField"
  • Expands to full URI in exported JSON-LD
  • Proper prefix resolution on export

1.9 Document Creation Tests

Test 1.9.1: Create New DDI-CDI Document

Setup:

  • Navigate to viewer (DDI-CDI mode)
  • Do not load any file

Actions:

  1. Select SHACL shapes: "DDI-CDI Official"
  2. Click "Add Root Node" at bottom
  3. Select node type: "DataSet"
  4. Click Add

Expected Results:

  • New empty document created
  • DDI-CDI context automatically added
  • Root node renders
  • Enable edit mode automatically
  • Default filename: "new-ddi-cdi-document.jsonld"
Test 1.9.2: Create Schema.org Document

Setup:

  • Navigate to viewer in generic mode (?shacl=generic)

Actions:

  1. Select SHACL shapes or create without shapes
  2. Add root node: "schema:Dataset"
  3. Begin adding properties

Expected Results:

  • schema.org context added
  • Appropriate namespace prefixes set up
  • Can add schema.org properties
Test 1.9.3: Add Multiple Root Nodes

Setup:

  • Empty document

Actions:

  1. Add first root node: "DataSet"
  2. Add properties and content
  3. Add second root node: "Agent"
  4. Add content to second node

Expected Results:

  • Both nodes appear at root level
  • @graph array contains both
  • Nodes independent but can reference each other
  • Export contains all root nodes
Test 1.9.4: Create Document Without Shapes

Setup:

  • Generic mode, no shapes loaded

Actions:

  1. Add root node with custom type
  2. Add custom properties
  3. Export

Expected Results:

  • Generic JSON-LD created
  • All properties marked as EXTRA
  • No validation (shapes not loaded)
  • Clean @context with only used prefixes

1.10 Export Tests

Test 1.10.1: Export Unmodified Data

Setup:

  • Load file in view mode
  • Do not make changes

Actions:

  1. Click "Export JSON-LD" button

Expected Results:

  • File downloads immediately
  • Filename matches original or uses default
  • Content identical to loaded file
  • @context preserved exactly
  • Structure unchanged

Assertions:

const [download] = await Promise.all([
  page.waitForEvent("download"),
  page.click("#export-btn"),
]);
const content = await download.path();
// Verify content matches original
Test 1.10.2: Export Modified Data

Setup:

  • Load file and enable edit mode
  • Make multiple changes:
    • Edit 3 properties
    • Add 2 properties
    • Delete 1 property
    • Add nested object

Actions:

  1. Click "Export JSON-LD"

Expected Results:

  • All modifications included in export
  • Deleted properties absent
  • New properties present
  • Nested objects included
  • @context updated if namespaces added
  • Valid JSON-LD structure
Test 1.10.3: Export with New Namespaces

Setup:

  • Add custom namespace
  • Use it in properties

Actions:

  1. Export document

Expected Results:

  • @context includes new namespace
  • Properties use prefix notation
  • Can re-import and edit
Test 1.10.4: Export New Document

Setup:

  • Create document from scratch
  • Add content

Actions:

  1. Export

Expected Results:

  • Clean JSON-LD structure
  • Appropriate @context for selected vocabulary
  • Default filename based on vocabulary
  • Valid according to shapes (if loaded)
Test 1.10.5: Export Large File

Setup:

  • Load file with 50+ nodes

Actions:

  1. Make edits
  2. Export

Expected Results:

  • Export completes without timeout
  • File downloads successfully
  • No data truncation
  • Performance acceptable (<5 seconds)

1.11 UI Interaction Tests

Test 1.11.1: Collapse/Expand Single Node

Setup:

  • File with multiple nodes loaded

Actions:

  1. Click node header to collapse
  2. Click header again to expand

Expected Results:

  • Content area hides on collapse
  • Chevron icon rotates
  • Content shows on expand
  • State independent for each node
Test 1.11.2: Collapse/Expand All Nodes

Setup:

  • File with 5+ nodes loaded
  • Some collapsed, some expanded

Actions:

  1. Click "Collapse All" button
  2. Verify all collapsed
  3. Click "Expand All" button
  4. Verify all expanded

Expected Results:

  • Collapse All: All nodes collapsed
  • Expand All: All nodes expanded
  • Nested nodes also affected

Assertions:

await page.click("#collapse-all-btn");
await expect(page.locator(".node-card.collapsed")).toHaveCount(totalNodeCount);
await page.click("#expand-all-btn");
await expect(page.locator(".node-card:not(.collapsed)")).toHaveCount(
  totalNodeCount
);
Test 1.11.3: Tooltips and Help Text

Setup:

  • Load file with SHACL shapes

Actions:

  1. Hover over property label
  2. Wait for tooltip

Expected Results:

  • Tooltip appears with sh:description text
  • Tooltip dismisses on mouse leave
  • Works for all properties with descriptions
Test 1.11.4: Responsive Layout

Setup:

  • Load viewer

Actions:

  1. Resize browser to mobile width (375px)
  2. Verify layout
  3. Resize to tablet (768px)
  4. Resize to desktop (1920px)

Expected Results:

  • Mobile: Toolbar stacks vertically, buttons wrap
  • Tablet: Reasonable layout, filters accessible
  • Desktop: Full horizontal layout
  • No horizontal scrolling
  • All functions accessible at all sizes
Test 1.11.5: Keyboard Navigation

Setup:

  • Load file and enable edit mode

Actions:

  1. Tab through form fields
  2. Use Enter to submit forms
  3. Use Esc to close modals
  4. Use F3/Shift+F3 for search navigation

Expected Results:

  • Tab order logical and complete
  • All interactive elements reachable
  • Enter submits where appropriate
  • Escape closes modals
  • Keyboard shortcuts work

2. Dataverse Integration Mode Tests

Tests for the viewer running as a previewer within Dataverse, using mocked API responses.

2.1 Mock Server Setup

Mock Implementation:

// tests/helpers/dataverse-mock-server.ts
import { rest } from "msw";
import { setupServer } from "msw/node";

const mockDataverseAPI = setupServer(
  // Get file metadata
  rest.get("*/api/files/:id", (req, res, ctx) => {
    return res(
      ctx.json({
        data: {
          id: req.params.id,
          filename: "test-dataset.jsonld",
          contentType: "application/ld+json",
          datasetId: "doi:10.5072/FK2/TEST",
          // ... full response
        },
      })
    );
  }),

  // Download file content
  rest.get("*/api/access/datafile/:id", (req, res, ctx) => {
    return res(
      ctx.json({
        "@context": {
          /* ... */
        },
        "@graph": [
          /* ... */
        ],
      })
    );
  }),

  // Replace file
  rest.post("*/api/files/:id/replace", (req, res, ctx) => {
    return res(
      ctx.json({
        status: "OK",
        data: { id: req.params.id },
      })
    );
  }),

  // Add file to dataset
  rest.post("*/api/datasets/:id/add", (req, res, ctx) => {
    return res(
      ctx.json({
        status: "OK",
        data: { files: [{ id: "new-file-id" }] },
      })
    );
  })
);

export { mockDataverseAPI };

2.2 Load from Dataverse Tests

Test 2.2.1: Load via URL Parameters (Integrated Mode)

Setup:

  • Start mock Dataverse server
  • Mock responses for file ID 123

Actions:

  1. Navigate to https://libis.github.io/cdi-viewer/?siteUrl=https://mock.dataverse.org&fileid=123
  2. Wait for file to load

Expected Results:

  • File content fetched from API
  • Data renders correctly
  • Integrated mode detected (no manual load buttons)
  • "Save to Dataverse" button visible
  • Filename pre-filled in save modal
  • URL field hidden in save modal

Assertions:

const mockSiteUrl = "https://mock.dataverse.org";
const fileId = "123";
await page.goto(`${viewerUrl}/?siteUrl=${mockSiteUrl}&fileid=${fileId}`);
await expect(page.locator("#load-local-btn")).not.toBeVisible();
await expect(page.locator("#load-dataverse-btn")).not.toBeVisible();
await expect(page.locator(".node-card")).toHaveCount(expectedCount);
Test 2.2.2: Load via "Load from Dataverse" Button (Standalone Mode)

Setup:

  • Mock Dataverse API
  • Standalone viewer

Actions:

  1. Click "Load from Dataverse" button
  2. Modal opens
  3. Enter file URL: https://mock.dataverse.org/file.xhtml?fileId=123
  4. Click "Load File"

Expected Results:

  • URL validated in real-time
  • File fetched from API
  • Modal closes
  • File renders
  • Transitions to integrated mode
  • Save button appears

Assertions:

await page.click("#load-dataverse-btn");
await page.fill(
  "#loadDataverseUrlInput",
  "https://mock.dataverse.org/file.xhtml?fileId=123"
);
await expect(page.locator("#confirmLoadBtn")).toBeEnabled();
await page.click("#confirmLoadBtn");
await expect(page.locator("#loadDataverseModal")).not.toBeVisible();
await expect(page.locator(".node-card")).toHaveCount(expectedCount);
Test 2.2.3: Load with API Token (Unpublished File)

Setup:

  • Mock API requiring authentication

Actions:

  1. Open Load from Dataverse modal
  2. Enter file URL
  3. Enter API token: "test-api-token-12345"
  4. Submit

Expected Results:

  • API request includes token in header
  • Unpublished file loads successfully
  • Token not stored/logged

Assertions:

// Verify Authorization header in mock request
mockDataverseAPI.events.on("request:start", (req) => {
  expect(req.headers.get("X-Dataverse-key")).toBe("test-api-token-12345");
});
Test 2.2.4: Load URL Validation

Setup:

  • Open Load from Dataverse modal

Actions:

  1. Enter invalid URL: "not-a-url"
  2. Enter wrong domain: "https://other-site.com/file.xhtml?fileId=123"
  3. Enter valid URL

Expected Results:

  • Invalid URL shows red error message
  • Wrong format shows warning
  • Valid URL shows green checkmark
  • Confirm button disabled until valid
Test 2.2.5: Load Multiple URL Formats

Setup:

  • Mock server supports all formats

Actions: Test each format:

  1. JSF: https://mock.dataverse.org/file.xhtml?fileId=123
  2. SPA: https://mock.dataverse.org/spa/files?id=123
  3. API files: https://mock.dataverse.org/api/files/123
  4. API access: https://mock.dataverse.org/api/access/datafile/123
  5. Persistent ID: https://mock.dataverse.org/api/access/datafile/:persistentId/?persistentId=doi:10.5072/FK2/123

Expected Results:

  • All formats parsed correctly
  • Correct API endpoint called
  • File loads successfully in all cases
Test 2.2.6: Load Error Handling

Setup:

  • Mock API returns errors

Actions:

  1. Attempt load with 404 response
  2. Attempt load with 500 error
  3. Attempt load with network timeout
  4. Attempt load with malformed JSON

Expected Results:

  • 404: "File not found" error message
  • 500: "Server error" message
  • Timeout: "Request timed out" message
  • Malformed: "Invalid JSON-LD" error
  • No crashes, user can retry

2.3 Save to Dataverse Tests

Test 2.3.1: Replace Existing File

Setup:

  • Load file via URL parameters (integrated mode)
  • Enable edit mode and make changes

Actions:

  1. Click "Save to Dataverse"
  2. Modal shows pre-filled filename
  3. Enter API token
  4. Click confirm

Expected Results:

  • Replace file API called with correct file ID
  • Request includes modified JSON-LD content
  • Success message appears
  • Data marked as saved (no longer "changed")

Assertions:

mockDataverseAPI.use(
  rest.post("*/api/files/:id/replace", async (req, res, ctx) => {
    const body = await req.json();
    expect(body["@graph"]).toBeDefined();
    return res(ctx.json({ status: "OK" }));
  })
);
await page.click("#save-btn");
await page.fill("#apiTokenInput", "test-token");
await page.click("#confirmSaveBtn");
await expect(page.locator(".alert-success")).toContainText("saved");
Test 2.3.2: Add New File to Dataset

Setup:

  • Standalone mode
  • Create new document from scratch

Actions:

  1. Click "Save to Dataverse"
  2. Enter dataset URL: https://mock.dataverse.org/dataset.xhtml?persistentId=doi:10.5072/FK2/TEST
  3. Enter filename: "new-metadata.jsonld"
  4. Enter API token
  5. Submit

Expected Results:

  • Add file API called
  • File uploaded to correct dataset
  • Success confirmation
  • File ID returned

Assertions:

mockDataverseAPI.use(
  rest.post("*/api/datasets/:pid/add", async (req, res, ctx) => {
    const formData = await req.formData();
    expect(formData.get("file")).toBeDefined();
    expect(formData.get("jsonData")).toContain("@graph");
    return res(
      ctx.json({
        status: "OK",
        data: { files: [{ id: "new-123" }] },
      })
    );
  })
);
Test 2.3.3: Save with Validation Errors

Setup:

  • File with validation violations
  • Integrated mode

Actions:

  1. Attempt to save
  2. Validation check runs automatically
  3. Confirmation dialog appears

Expected Results:

  • Warning: "Your data has validation errors. Save anyway?"
  • Can cancel save
  • Can proceed despite errors
  • If proceed, saves with violations
Test 2.3.4: Save URL Validation (Standalone Mode)

Setup:

  • Standalone mode with save to new dataset

Actions:

  1. Enter invalid dataset URL
  2. Enter valid URL
  3. Test different URL formats

Expected Results:

  • Invalid URLs rejected with error message
  • Valid dataset/file URLs accepted
  • Real-time validation feedback
  • Confirm button enabled/disabled appropriately
Test 2.3.5: Save Error Handling

Setup:

  • Mock various API errors

Actions:

  1. Save with 401 Unauthorized
  2. Save with 403 Forbidden
  3. Save with 500 Server Error
  4. Save with network failure

Expected Results:

  • 401: "Invalid or missing API token"
  • 403: "Permission denied"
  • 500: "Server error occurred"
  • Network: "Connection failed"
  • Error alerts shown
  • Can retry after error
Test 2.3.6: Save Large File

Setup:

  • File with 100+ nodes
  • Make modifications

Actions:

  1. Save to Dataverse

Expected Results:

  • Request completes successfully
  • No timeout (within 30 seconds)
  • Progress indicator shown
  • Success confirmation

2.4 Integrated vs Standalone Mode Tests

Test 2.4.1: Detect Integrated Mode

Setup:

  • Navigate with URL parameters

Actions:

  1. Load ?siteUrl=X&fileid=Y

Expected Results:

  • Integrated mode activated
  • Load buttons hidden
  • Save button visible even in view mode
  • URL field hidden in save modal
  • Filename pre-filled
Test 2.4.2: Detect Standalone Mode

Setup:

  • Navigate without URL parameters

Actions:

  1. Load viewer normally

Expected Results:

  • Standalone mode activated
  • Load buttons visible
  • Save button visible only after edit mode enabled
  • URL field visible in save modal
Test 2.4.3: Transition from Standalone to Integrated

Setup:

  • Start in standalone mode

Actions:

  1. Use "Load from Dataverse" button
  2. Load file successfully

Expected Results:

  • Mode transitions to integrated
  • siteUrl and fileId stored
  • Save behavior changes to integrated mode
  • Can save back to Dataverse
Test 2.4.4: Mode Persistence

Setup:

  • Load in integrated mode

Actions:

  1. Make edits
  2. Refresh page (F5)

Expected Results:

  • Returns to standalone mode (URL params lost)
  • Data not persisted (expected behavior)
  • User can re-load file

3. Cross-Browser and Compatibility Tests

Test 3.1: Chromium/Chrome

Setup:

  • Run all core tests on Chromium

Expected Results:

  • All features work
  • No console errors
  • Performance acceptable
Test 3.2: Firefox

Setup:

  • Run critical path tests on Firefox

Expected Results:

  • File loading works
  • Editing works
  • Validation works
  • Export works
  • Known issues documented
Test 3.3: WebKit/Safari

Setup:

  • Run critical path tests on WebKit

Expected Results:

  • Core functionality works
  • Date picker differences handled
  • Any polyfills working
Test 3.4: Mobile Browsers

Setup:

  • Mobile viewport (375x667)
  • Touch interactions

Expected Results:

  • Responsive layout works
  • Touch targets adequate
  • Modals accessible
  • Core features usable
Test 3.5: Older Browser Support

Setup:

  • Test on browsers 2-3 years old

Expected Results:

  • Graceful degradation
  • ES6 features supported or polyfilled
  • Error messages if unsupported

4. Performance Tests

Test 4.1: Large File Loading

Setup:

  • Generate file with 200 nodes

Actions:

  1. Load file
  2. Measure render time

Expected Results:

  • Loads within 10 seconds
  • UI remains responsive
  • No browser freezing
Test 4.2: Search Performance

Setup:

  • Large file loaded

Actions:

  1. Search for common term
  2. Measure highlight time

Expected Results:

  • Results within 2 seconds
  • Smooth scrolling
  • No lag on navigation
Test 4.3: Validation Performance

Setup:

  • Complex file with nested structures

Actions:

  1. Trigger validation
  2. Measure completion time

Expected Results:

  • Completes within 5 seconds
  • Progress indicator shown
  • No UI blocking
Test 4.4: Memory Usage

Setup:

  • Load, edit, save multiple times

Actions:

  1. Monitor memory usage
  2. Load/unload files repeatedly

Expected Results:

  • No memory leaks
  • Memory released on file unload
  • Stable over 10+ cycles

5. Accessibility Tests

Test 5.1: Keyboard Navigation

Actions:

  • Navigate entire UI with keyboard only

Expected Results:

  • All functions accessible
  • Focus indicators visible
  • Logical tab order
Test 5.2: Screen Reader Support

Actions:

  • Use NVDA/JAWS to navigate

Expected Results:

  • All labels read correctly
  • Button purposes announced
  • Form fields labeled
  • Error messages announced
Test 5.3: Color Contrast

Actions:

  • Check all text/background combinations

Expected Results:

  • WCAG AA compliance minimum
  • Important info not color-only
  • High contrast mode supported
Test 5.4: Focus Management

Actions:

  • Open/close modals
  • Test focus trap

Expected Results:

  • Focus moves to modal on open
  • Focus trapped in modal
  • Focus returns on close

Test Execution Strategy

Test Priority Levels

P0 - Critical (Must Pass):

  • File loading (standalone and integrated)
  • Basic editing (text, numbers)
  • Validation execution
  • Export functionality
  • Save to Dataverse (replace file)

P1 - High Priority:

  • Array operations
  • Complex object creation
  • Search and filter
  • Namespace management
  • All error handling

P2 - Medium Priority:

  • Advanced features
  • Edge cases
  • Performance tests
  • Accessibility

P3 - Low Priority:

  • UI polish
  • Tooltips
  • Animations
  • Nice-to-have features

CI/CD Integration

GitHub Actions Workflow:

name: E2E Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        browser: [chromium, firefox, webkit]
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
      - run: npm ci
      - run: npm run build
      - run: npx playwright install
      - run: npx playwright test --project=${{ matrix.browser }}
      - uses: actions/upload-artifact@v3
        if: failure()
        with:
          name: test-results-${{ matrix.browser }}
          path: test-results/

Local Development

Run all tests:

npm run test:e2e

Run specific suite:

npm run test:e2e -- standalone/editing.spec.ts

Debug mode:

npm run test:e2e -- --debug

Headed mode (see browser):

npm run test:e2e -- --headed

Test Reports

  • HTML report generated automatically
  • Screenshots on failure
  • Video recording for failed tests
  • Trace files for debugging

Implementation Checklist

Phase 1: Foundation (Week 1)

  • Set up Playwright project
  • Create page object models
  • Implement Dataverse mock server
  • Write 10 smoke tests (P0)

Phase 2: Core Features (Week 2)

  • File loading tests (standalone + integrated)
  • Basic editing tests
  • Validation tests
  • Export tests

Phase 3: Advanced Features (Week 3)

  • Array operations
  • Complex objects
  • Search and filter
  • Namespace management

Phase 4: Integration (Week 4)

  • Dataverse integration tests
  • Save functionality
  • Error handling
  • Cross-browser testing

Phase 5: Polish (Week 5)

  • Performance tests
  • Accessibility tests
  • Edge cases
  • Documentation

Success Criteria

Coverage Goals

  • Line Coverage: >80%
  • Function Coverage: >90%
  • Branch Coverage: >75%

Quality Metrics

  • Flakiness: <2% test flakiness rate
  • Execution Time: Full suite <10 minutes
  • Failure Analysis: All failures categorized and tracked

Browser Support

  • ✅ Chrome/Chromium: 100% pass rate
  • ✅ Firefox: 95%+ pass rate
  • ✅ Safari/WebKit: 90%+ pass rate
  • ✅ Mobile: Core features working

Maintenance

Adding New Tests

  1. Identify feature to test
  2. Choose appropriate test suite file
  3. Follow existing patterns
  4. Use page objects
  5. Add assertions
  6. Update documentation

Debugging Failed Tests

  1. Check test output/screenshots
  2. Run in headed mode
  3. Use trace viewer
  4. Check mock server logs
  5. Verify selector stability

Updating Tests

  • Review tests after UI changes
  • Update selectors if needed
  • Maintain page object models
  • Keep mocks synchronized with API

This comprehensive testing plan ensures the CDI Viewer is thoroughly validated across all features, browsers, and use cases before release.