import {expect, test} from '@playwright/test'; import dedent from 'dedent'; import * as fs from 'fs'; import {mockGedcomResponse, setupHermeticEnvironment} from './helpers'; test.describe('Details panel visual validation @visual', () => { test.beforeEach(async ({context}) => { await setupHermeticEnvironment(context); }); test('Complex Names Test', async ({page, context}) => { const complexNameGedcom = dedent` 0 HEAD 1 GEDC 2 VERS 5.5.1 2 FORM Lineage-Linked 1 CHAR UTF-8 0 @I1@ INDI 1 NAME Dr. Bonifacy "Boni" /Gibbs/ III 2 NPFX Dr. 2 GIVN Bonifacy 2 NICK Boni 2 SURN Gibbs 2 NSFX III 2 _RUFNAME Bonifacy 1 SEX M 1 FAMS @F1@ 0 @F1@ FAM 1 HUSB @I1@ 0 TRLR `; await mockGedcomResponse(context, complexNameGedcom); await page.goto('/#/view?url=https://example.org/family.ged'); const sidebar = page.locator('#sidebar'); await sidebar.waitFor(); await expect(sidebar).toHaveScreenshot('details-complex-name.png'); }); test('Image / Photo Rendering Test', async ({page, context}) => { const photoGedcom = dedent` 0 HEAD 1 GEDC 2 VERS 5.5.1 2 FORM Lineage-Linked 1 CHAR UTF-8 0 @I1@ INDI 1 NAME Bonifacy /Gibbs/ 1 SEX M 1 FAMS @F1@ 1 OBJE @O1@ 0 @O1@ OBJE 1 FILE http://example.org/photos/I1.jpg 2 FORM jpeg 0 @F1@ FAM 1 HUSB @I1@ 0 TRLR `; await mockGedcomResponse(context, photoGedcom); await context.route('**/photos/I1.jpg', async (route) => { const imageBuffer = fs.readFileSync( 'docker/examples/photos/photos/I1.jpg', ); await route.fulfill({ status: 200, contentType: 'image/jpeg', body: imageBuffer, }); }); await page.goto('/#/view?url=https://example.org/family.ged'); const sidebar = page.locator('#sidebar'); await sidebar.waitFor(); // Wait for image loading to complete const img = sidebar.locator('img').first(); await img.waitFor({state: 'visible'}); await img.evaluate((image) => { return new Promise((resolve, reject) => { if ((image as HTMLImageElement).complete) resolve(true); image.addEventListener('load', () => resolve(true)); image.addEventListener('error', () => reject(new Error('Image failed to load')), ); }); }); await expect(sidebar).toHaveScreenshot('details-photo-render.png'); }); test('Custom Facts & Citations Test', async ({page, context}) => { const customFactsGedcom = dedent` 0 HEAD 1 GEDC 2 VERS 5.5.1 2 FORM Lineage-Linked 1 CHAR UTF-8 0 @I1@ INDI 1 NAME Bonifacy /Gibbs/ 1 SEX M 1 FAMS @F1@ 1 FACT Custom fact data 2 TYPE Custom Fact Type 2 SOUR @S1@ 3 PAGE 42 3 DATA 4 DATE 12 JAN 1850 2 NOTE This is a note nested under a custom fact. 1 BIRT 2 DATE 1 JAN 1800 2 PLAC Paris, France 2 SOUR @S1@ 3 PAGE 10 2 NOTE Birth event note. 1 DEAT 2 DATE 31 DEC 1880 2 PLAC London, UK 1 NOTE This is a top-level note for the individual. 0 @S1@ SOUR 1 TITL Great Genealogy Book 1 AUTH John Doe 1 PUBL London Publishing, 1890 0 @F1@ FAM 1 HUSB @I1@ 0 TRLR `; await mockGedcomResponse(context, customFactsGedcom); await page.goto('/#/view?url=https://example.org/family.ged'); const sidebar = page.locator('#sidebar'); await sidebar.waitFor(); await expect(sidebar).toHaveScreenshot('details-events-sources.png'); }); test('Immediate Family Rendering Test', async ({page, context}) => { const immediateFamilyGedcom = dedent` 0 HEAD 1 GEDC 2 VERS 5.5.1 2 FORM Lineage-Linked 1 CHAR UTF-8 0 @I1@ INDI 1 NAME Focus /Person/ 1 SEX M 1 FAMC @F1@ 1 FAMS @F2@ 1 FAMS @F3@ 0 @I2@ INDI 1 NAME Father /Person/ 1 SEX M 1 FAMS @F1@ 0 @I3@ INDI 1 NAME Mother /Person/ 1 SEX F 1 FAMS @F1@ 0 @I4@ INDI 1 NAME Spouse /Person/ 1 SEX F 1 FAMS @F2@ 0 @I5@ INDI 1 NAME Older /Child/ 1 SEX M 1 BIRT 2 DATE 1 JAN 2000 1 FAMC @F2@ 0 @I6@ INDI 1 NAME Younger /Child/ 1 SEX F 1 BIRT 2 DATE 1 JAN 2005 1 FAMC @F2@ 0 @I7@ INDI 1 NAME Half /Child/ 1 SEX M 1 BIRT 2 DATE 1 JAN 2010 1 FAMC @F3@ 0 @F1@ FAM 1 HUSB @I2@ 1 WIFE @I3@ 1 CHIL @I1@ 0 @F2@ FAM 1 HUSB @I1@ 1 WIFE @I4@ 1 CHIL @I5@ 1 CHIL @I6@ 0 @F3@ FAM 1 HUSB @I1@ 1 CHIL @I7@ 0 TRLR `; await mockGedcomResponse(context, immediateFamilyGedcom); await page.goto('/#/view?url=https://example.org/family.ged'); const sidebar = page.locator('#sidebar'); await sidebar.waitFor(); await expect(sidebar).toHaveScreenshot('details-immediate-family.png'); }); test('Media Fallback Rendering Test', async ({page, context}) => { const fallbackGedcom = dedent` 0 HEAD 1 GEDC 2 VERS 5.5.1 2 FORM Lineage-Linked 1 CHAR UTF-8 0 @I1@ INDI 1 NAME Bonifacy /Gibbs/ 1 SEX M 1 FAMS @F1@ 1 OBJE @O1@ 0 @O1@ OBJE 1 FILE photos/not_uploaded_image.jpg 2 FORM jpeg 2 TITL A Not Uploaded Image 0 @F1@ FAM 1 HUSB @I1@ 0 TRLR `; await mockGedcomResponse(context, fallbackGedcom); await page.goto('/#/view?url=https://example.org/family.ged'); const sidebar = page.locator('#sidebar'); await sidebar.waitFor(); // Verify fallback placeholder text is present await expect(sidebar.getByText('File not uploaded').first()).toBeVisible(); await expect( sidebar.getByText('A Not Uploaded Image').first(), ).toBeVisible(); await expect(sidebar).toHaveScreenshot('details-media-fallback.png'); }); });