Spaces:
Sleeping
Sleeping
File size: 6,135 Bytes
766d85d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | // FE-COMP-JOURNEYPDF-001 to FE-COMP-JOURNEYPDF-006
//
// JourneyBookPDF.tsx exports an async function `downloadJourneyBookPDF(journey)`
// that renders a PDF preview in an srcdoc iframe overlay (Safari-safe pattern).
// Tests verify the overlay DOM structure and HTML content.
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
// Mock `marked` so we don't need the real markdown parser
vi.mock('marked', () => ({
marked: {
parse: (str: string) => `<p>${str}</p>`,
},
}));
import { downloadJourneyBookPDF } from './JourneyBookPDF';
import type { JourneyDetail } from '../../store/journeyStore';
// ββ Helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
function buildJourney(overrides: Partial<JourneyDetail> = {}): JourneyDetail {
return {
id: 1,
user_id: 1,
title: 'Iceland Ring Road',
subtitle: 'Two weeks around the island',
status: 'active',
cover_image: null,
cover_gradient: null,
created_at: Date.now(),
updated_at: Date.now(),
entries: [
{
id: 10,
journey_id: 1,
author_id: 1,
type: 'entry',
title: 'Golden Circle',
story: 'An incredible day of geysers and waterfalls.',
entry_date: '2026-07-01',
entry_time: '09:00',
location_name: 'Thingvellir',
location_lat: 64.255,
location_lng: -21.13,
mood: 'excited',
weather: 'sunny',
tags: [],
pros_cons: { pros: ['Amazing views'], cons: ['Crowded'] },
visibility: 'private',
sort_order: 0,
created_at: Date.now(),
updated_at: Date.now(),
source_trip_id: null,
source_place_id: null,
source_trip_name: null,
photos: [
{
id: 100,
entry_id: 10,
provider: 'local',
file_path: 'journey/geyser.jpg',
thumbnail_path: null,
asset_id: null,
owner_id: null,
shared: 0,
caption: 'Strokkur erupting',
sort_order: 0,
created_at: Date.now(),
},
],
},
],
trips: [],
contributors: [],
stats: { entries: 1, photos: 1, cities: 1 },
...overrides,
} as unknown as JourneyDetail;
}
// ββ Helpers to inspect the overlay βββββββββββββββββββββββββββββββββββββββββββ
function getOverlay(): HTMLElement | null {
return document.getElementById('journey-pdf-overlay');
}
function getIframe(): HTMLIFrameElement | null {
return getOverlay()?.querySelector('iframe') ?? null;
}
// ββ Setup ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
afterEach(() => {
document.getElementById('journey-pdf-overlay')?.remove();
vi.restoreAllMocks();
});
// ββ Tests ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
describe('downloadJourneyBookPDF', () => {
it('FE-COMP-JOURNEYPDF-001: appends overlay to document body', async () => {
await downloadJourneyBookPDF(buildJourney());
expect(getOverlay()).not.toBeNull();
expect(document.body.contains(getOverlay())).toBe(true);
});
it('FE-COMP-JOURNEYPDF-002: overlay contains an iframe with srcdoc HTML', async () => {
await downloadJourneyBookPDF(buildJourney());
const iframe = getIframe();
expect(iframe).not.toBeNull();
const html = iframe!.srcdoc;
expect(html).toContain('<!DOCTYPE html>');
expect(html).toContain('</html>');
});
it('FE-COMP-JOURNEYPDF-003: overlay has close and save buttons', async () => {
await downloadJourneyBookPDF(buildJourney());
const overlay = getOverlay()!;
expect(overlay.querySelector('#journey-pdf-close')).not.toBeNull();
expect(overlay.querySelector('#journey-pdf-save')).not.toBeNull();
});
it('FE-COMP-JOURNEYPDF-004: HTML contains the journey title', async () => {
await downloadJourneyBookPDF(buildJourney());
const html = getIframe()!.srcdoc;
expect(html).toContain('Iceland Ring Road');
});
it('FE-COMP-JOURNEYPDF-005: HTML contains entry content', async () => {
await downloadJourneyBookPDF(buildJourney());
const html = getIframe()!.srcdoc;
expect(html).toContain('Golden Circle');
// Story text is rendered via markdown
expect(html).toContain('An incredible day of geysers and waterfalls.');
// Pros/cons verdict cards are included
expect(html).toContain('Amazing views');
expect(html).toContain('Crowded');
});
it('FE-COMP-JOURNEYPDF-006: handles empty entries gracefully', async () => {
const journey = buildJourney({ entries: [] });
await downloadJourneyBookPDF(journey);
expect(getOverlay()).not.toBeNull();
const html = getIframe()!.srcdoc;
expect(html).toContain('Iceland Ring Road');
// No entry pages, but cover and closing page are still present
expect(html).toContain('Journey Book');
expect(html).toContain('The End');
});
it('FE-COMP-JOURNEYPDF-007: sanitises HTML injected via an entry story and keeps the iframe script-free', async () => {
const journey = buildJourney();
journey.entries[0].story = 'Hello <script>alert(1)</script> <img src=x onerror="alert(2)"> world';
await downloadJourneyBookPDF(journey);
const iframe = getIframe()!;
const html = iframe.srcdoc;
// The script tag, image beacon and event handler are stripped from the story.
expect(html).not.toContain('<script');
expect(html).not.toContain('onerror');
expect(html).not.toContain('alert(2)');
// Benign prose survives.
expect(html).toContain('Hello');
expect(html).toContain('world');
});
});
|