/** * 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 { ae_app_local_data_defaults } from './_helpers/ae_defaults'; import { testing_event_id, testing_account_id, mock_site_domain } from './_helpers/env'; import { inject_badge_and_template } from './_helpers/idb_helpers'; 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, event_id: event_id, event_id_random: event_id, event_badge_template_id: template_id, event_badge_template_id_random: template_id, 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 }) => { page.on('pageerror', (err) => console.error(`BROWSER ERROR: ${err.message}`)); await page.route('**/v3/**', async (route) => { const url = route.request().url(); const method = route.request().method(); if (url.includes('site_domain/search')) { return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: [mock_site_domain] }) }); } if (url.includes(`/v3/crud/event/${event_id}`) && !url.includes('event_badge') && method === 'GET') { return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: { id: event_id, event_id, name: 'Test Event', cfg_json: {}, mod_badges_json: {}, mod_pres_mgmt_json: {}, mod_abstracts_json: {}, mod_exhibits_json: {}, mod_meetings_json: {}, }}) }); } return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: [] }) }); }); await page.addInitScript(([defaults, e_id, a_id]) => { const data = { ...defaults, account_id: a_id, allow_access: true, authenticated_access: true, trusted_access: true, manager_access: true, edit_mode: false, } as any; data.mod = { ...defaults.mod, events: { ...defaults.mod.events, event_id: e_id } }; window.localStorage.setItem('ae_loc', JSON.stringify(data)); }, [ae_app_local_data_defaults, event_id, testing_account_id] as const); }); 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); }); });