Files
OSIT-AE-App-Svelte/tests/event_badge_print_layout.test.ts
Scott Idem af02e38528 test: badge E2E tests — fix __version wipe, extract idb_helpers, add render + workflow tests
Root cause fix: tests/_helpers/ae_defaults.ts was missing __version: 1, causing
store_versions.ts to wipe ae_loc from localStorage on every test page load. This
made trusted_access fall back to false, hiding the print button (can_print guard)
and failing all attendee workflow tests.

Changes:
- ae_defaults.ts: add __version: 1 with comment explaining the store_versions guard
- idb_helpers.ts: extract inject_badge_and_template() from print layout test into
  shared helper; now used by all three print/render test files
- event_badge_render.test.ts: new — 4 tests covering full_name_override priority,
  full_name fallback, duplex=0 hides badge back, duplex=1 shows badge back
- event_badge_attendee_workflow.test.ts: cleaned up (diagnostic code removed);
  all 3 tests now pass
- event_badge_print_layout.test.ts: renamed from badge_print_layout.test.ts;
  inline inject_idb() replaced with shared idb_helpers import
- event_badge_smoke.test.ts: renamed from event_badge.test.ts
- playwright.config.ts: use system /usr/bin/chromium on Arch Linux (avoids
  Playwright's bundled Chromium which requires Ubuntu libs not present on Arch)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 16:58:55 -04:00

217 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Badge Print Page — Print Layout CSS Tests
*
* Verifies that the badge is horizontally (and vertically) centered on the page
* when print media CSS is active, using Playwright's emulateMedia({ media: 'print' }).
*
* Strategy: inject badge + template data directly into IndexedDB so liveQuery
* fires without needing a full API round-trip, then check bounding boxes.
*
* Cross-browser: add Firefox to playwright.config.ts projects to catch the
* overflow:auto + display:contents incompatibility that Firefox enforces strictly.
*/
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;
// Use the demo badge/template IDs from tests/README.md
const badge_id = 'UIJT-73-63-61';
const template_id = 'jgfixEpYp1B';
// Minimal badge record — both *_id and *_id_random set to the same value so
// db_events.badge.get(event_badge_id) finds the record via the Dexie primary key.
const mock_badge = {
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_override: 'Scott Idem',
given_name: 'Scott',
family_name: 'Idem',
email: 'scott@example.com',
badge_type: 'presenter',
badge_type_code: 'presenter',
print_count: 0,
affiliations: 'One Sky IT',
location: 'Minneapolis, MN',
enable: true,
};
// Minimal template record — layout drives the @page size injection
const mock_template = {
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: 'Dev Demo 202x',
layout: 'badge_3.5x5.5_pvc',
cfg_json: '{}',
enable: true,
};
test.describe('Badge Print Page — print layout centering', () => {
test.beforeEach(async ({ page }) => {
page.on('pageerror', (err) => console.error(`BROWSER ERROR: ${err.message}`));
// Minimal API mocks — enough for the events layout to render without errors
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] }) });
}
// Return a minimal valid event so the layout header doesn't error
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: {} }
}) });
}
// All other calls: empty list (no-op)
return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: [] }) });
});
// Seed localStorage auth so the app renders
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('badge is horizontally centered on a letter-size page', async ({ page }) => {
// Letter paper: 8.5in × 11in at 96 dpi = 816 × 1056 px
await page.setViewportSize({ width: 816, height: 1056 });
// First navigation initializes the Dexie schema; inject_idb then writes the data.
// After injection we reload so liveQuery starts fresh against the populated IDB —
// direct IDB writes bypass Dexie's notification system and won't trigger a re-query.
await page.goto(`/events/${event_id}/badges/${badge_id}/print`);
await page.waitForLoadState('domcontentloaded');
await page.evaluate(inject_badge_and_template, { badge: mock_badge, template: mock_template });
await page.reload();
await page.waitForLoadState('domcontentloaded');
// Wait for LiveQuery to render the badge wrapper
await page.waitForSelector('.event_badge_wrapper', { timeout: 8000 });
// Apply print media CSS (activates @media print rules)
await page.emulateMedia({ media: 'print' });
// Wait for layout to settle under print CSS
await page.waitForFunction(() => {
const el = document.querySelector('.event_badge_wrapper') as HTMLElement | null;
return el !== null && el.offsetWidth > 0;
}, { timeout: 5000 });
const badge_box = await page.locator('.event_badge_wrapper').boundingBox();
expect(badge_box, 'badge wrapper must have a bounding box').not.toBeNull();
const viewport = page.viewportSize()!;
const badge_center_x = badge_box!.x + badge_box!.width / 2;
const page_center_x = viewport.width / 2;
// Badge horizontal center must be within 5px of the page center
expect(
Math.abs(badge_center_x - page_center_x),
`badge center (${badge_center_x.toFixed(1)}px) should equal page center (${page_center_x}px)`
).toBeLessThan(5);
});
test('badge is vertically centered on a letter-size page', async ({ page }) => {
await page.setViewportSize({ width: 816, height: 1056 });
await page.goto(`/events/${event_id}/badges/${badge_id}/print`);
await page.waitForLoadState('domcontentloaded');
await page.evaluate(inject_badge_and_template, { badge: mock_badge, template: mock_template });
await page.reload();
await page.waitForLoadState('domcontentloaded');
await page.waitForSelector('.event_badge_wrapper', { timeout: 8000 });
await page.emulateMedia({ media: 'print' });
await page.waitForFunction(() => {
const el = document.querySelector('.event_badge_wrapper') as HTMLElement | null;
return el !== null && el.offsetHeight > 0;
}, { timeout: 5000 });
const badge_box = await page.locator('.event_badge_wrapper').boundingBox();
expect(badge_box).not.toBeNull();
const viewport = page.viewportSize()!;
const badge_center_y = badge_box!.y + badge_box!.height / 2;
const page_center_y = viewport.height / 2;
// Badge vertical center must be within 5px of the page center
expect(
Math.abs(badge_center_y - page_center_y),
`badge center Y (${badge_center_y.toFixed(1)}px) should equal page center Y (${page_center_y}px)`
).toBeLessThan(5);
});
test('#ae_main_content is a full-page positioned block in print mode', async ({ page }) => {
// Verifies overflow:visible (overflow:auto fix) and position:relative (centering context).
await page.goto(`/events/${event_id}/badges/${badge_id}/print`);
// domcontentloaded is too early — wait for the element to exist after hydration.
await page.waitForSelector('#ae_main_content', { timeout: 8000 });
await page.emulateMedia({ media: 'print' });
const styles = await page.evaluate(() => {
const el = document.getElementById('ae_main_content');
if (!el) return null;
const cs = getComputedStyle(el);
return { overflow: cs.overflow, position: cs.position };
});
expect(styles, '#ae_main_content must exist').not.toBeNull();
expect(styles!.overflow, '#ae_main_content overflow should be visible in print').toBe('visible');
// position:static — print CSS strips #ae_main_content's overflow clipping by setting
// display:block + overflow:visible + position:static so it doesn't interfere with centering.
expect(styles!.position, '#ae_main_content should be static in print').toBe('static');
});
test('badge wrapper is absolutely positioned and centered in print mode', async ({ page }) => {
await page.setViewportSize({ width: 816, height: 1056 });
await page.goto(`/events/${event_id}/badges/${badge_id}/print`);
await page.waitForLoadState('domcontentloaded');
await page.evaluate(inject_badge_and_template, { badge: mock_badge, template: mock_template });
await page.reload();
await page.waitForLoadState('domcontentloaded');
await page.waitForSelector('.event_badge_wrapper', { timeout: 8000 });
await page.emulateMedia({ media: 'print' });
const styles = await page.evaluate(() => {
const el = document.querySelector('.event_badge_wrapper') as HTMLElement | null;
if (!el) return null;
const cs = getComputedStyle(el);
return {
position: cs.position,
transform: cs.transform,
};
});
expect(styles).not.toBeNull();
// position:fixed — anchors to the @page content area in print, bypassing all ancestor
// overflow/height constraints. This is intentional (see print layout architecture docs).
expect(styles!.position, 'badge wrapper should be fixed in print').toBe('fixed');
// transform should contain a matrix (translate(-50%,-50%) for centering)
expect(styles!.transform, 'badge wrapper should have a transform applied').not.toBe('none');
});
});