/** * Badge Render — Content and Visibility Tests * * Verifies badge rendering logic using IDB injection: * navigate → inject IDB → reload → assert rendered content * * These tests protect specific business rules that have caused real bugs: * * 1. full_name_override renders in preference to full_name * The badge displays `full_name_override ?? full_name`. If override priority * breaks (e.g. wrong field precedence in display_name derived value), the * wrong name prints on the badge. * * 2. full_name renders when no override is set (sanity) * Confirms the fallback chain works — no override should still show a name. * * 3. duplex=0 hides the badge back section * The Zebra ZC10L PVC workflow uses single-sided cards (duplex=0). If the * show_badge_back derived value regresses, the back section prints on * single-sided stock and wastes/jams cards. * * 4. duplex=1 (or unset) shows the badge back section * Standard fanfold badges are two-sided. Confirms the default is duplex-on. */ import { test, expect } from '@playwright/test'; import { testing_event_id } from './_helpers/env'; import { inject_badge_and_template } from './_helpers/idb_helpers'; import { setup_badge_test_page } from './_helpers/minimal_ae_api_mocks'; const event_id = testing_event_id; // Each test uses a distinct badge_id so IDB records from parallel or sequential // tests don't collide within the same browser context. const BADGE_OVERRIDE = 'test-render-override-001'; const BADGE_NO_OVERRIDE = 'test-render-no-override-001'; const BADGE_DUPLEX_OFF = 'test-render-duplex-off-001'; const BADGE_DUPLEX_ON = 'test-render-duplex-on-001'; const TMPL_STANDARD = 'test-render-tmpl-standard-001'; const TMPL_DUPLEX_OFF = 'test-render-tmpl-duplex-off-001'; function make_badge(badge_id: string, template_id: string, overrides: Record = {}) { return { id: badge_id, event_badge_id: badge_id, event_badge_id_random: badge_id,// NO LONGER USE "_random" event_id: event_id, event_id_random: event_id,// NO LONGER USE "_random" event_badge_template_id: template_id, event_badge_template_id_random: template_id,// NO LONGER USE "_random" full_name: 'Jane Doe', full_name_override: null, given_name: 'Jane', family_name: 'Doe', email: 'jane@example.com', badge_type: 'member', badge_type_code: 'current_member', print_count: 0, affiliations: 'Test Org', location: 'Minneapolis, MN', enable: true, ...overrides, }; } function make_template(template_id: string, overrides: Record = {}) { return { id: template_id, id_random: template_id, event_badge_template_id: template_id, badge_template_id: template_id, event_id: event_id, event_id_random: event_id, name: 'Test Template', layout: 'badge_4x5_fanfold', cfg_json: '{}', enable: true, ...overrides, }; } test.describe('Badge Render — content and visibility', () => { test.beforeEach(async ({ page }) => { await setup_badge_test_page(page, event_id); }); test('full_name_override renders in preference to full_name', async ({ page }) => { const badge = make_badge(BADGE_OVERRIDE, TMPL_STANDARD, { full_name_override: 'Dr. Jane Override' }); const template = make_template(TMPL_STANDARD); await page.goto(`/events/${event_id}/badges/${BADGE_OVERRIDE}/print`); await page.waitForLoadState('domcontentloaded'); await page.evaluate(inject_badge_and_template, { badge, template }); await page.reload(); await page.waitForLoadState('domcontentloaded'); await page.waitForSelector('.event_badge_wrapper', { timeout: 8000 }); const name_text = await page.locator('.full_name_override').textContent(); // Override name must be shown — not the base full_name expect(name_text?.trim()).toBe('Dr. Jane Override'); expect(name_text?.trim()).not.toBe('Jane Doe'); }); test('full_name renders when no override is set', async ({ page }) => { // full_name_override is null — should fall back to full_name const badge = make_badge(BADGE_NO_OVERRIDE, TMPL_STANDARD, { full_name: 'Jane Doe', full_name_override: null }); const template = make_template(TMPL_STANDARD); await page.goto(`/events/${event_id}/badges/${BADGE_NO_OVERRIDE}/print`); await page.waitForLoadState('domcontentloaded'); await page.evaluate(inject_badge_and_template, { badge, template }); await page.reload(); await page.waitForLoadState('domcontentloaded'); await page.waitForSelector('.event_badge_wrapper', { timeout: 8000 }); const name_text = await page.locator('.full_name_override').textContent(); expect(name_text?.trim()).toBe('Jane Doe'); }); test('duplex=0 hides the badge back section (single-sided PVC)', async ({ page }) => { // duplex=0 → show_badge_back is false → .badge_back must not be in DOM. // Critical for Zebra ZC10L PVC single-sided cards — if back renders, it // prints on the card surface and ruins the card stock. const badge = make_badge(BADGE_DUPLEX_OFF, TMPL_DUPLEX_OFF); const template = make_template(TMPL_DUPLEX_OFF, { duplex: 0 }); await page.goto(`/events/${event_id}/badges/${BADGE_DUPLEX_OFF}/print`); await page.waitForLoadState('domcontentloaded'); await page.evaluate(inject_badge_and_template, { badge, template }); await page.reload(); await page.waitForLoadState('domcontentloaded'); await page.waitForSelector('.event_badge_wrapper', { timeout: 8000 }); const back_count = await page.locator('.badge_back').count(); expect(back_count, 'badge_back must not be in DOM when duplex=0').toBe(0); }); test('duplex=1 shows the badge back section (duplex fanfold)', async ({ page }) => { // duplex=1 (or null) → show_badge_back is true → .badge_back must be present. const badge = make_badge(BADGE_DUPLEX_ON, TMPL_STANDARD); const template = make_template(TMPL_STANDARD, { duplex: 1 }); await page.goto(`/events/${event_id}/badges/${BADGE_DUPLEX_ON}/print`); await page.waitForLoadState('domcontentloaded'); await page.evaluate(inject_badge_and_template, { badge, template }); await page.reload(); await page.waitForLoadState('domcontentloaded'); await page.waitForSelector('.event_badge_wrapper', { timeout: 8000 }); const back_count = await page.locator('.badge_back').count(); expect(back_count, 'badge_back must be present when duplex=1').toBeGreaterThan(0); }); });