- QR scanner (single + multi): detect previously-removed leads via IDB enable flag; route to 'reenable' state instead of duplicate error; offer Re-activate button - API fallback: if create fails and no IDB record, search API for disabled tracking record by event_exhibit_id + event_badge_id (adds qry_badge_id param to search__exhibit_tracking) - Lead detail page: Replace raw enable checkbox with Remove Lead (two-click confirm, navigates back after) and Restore Lead card (shown when enable is falsy) - Fix flash of disabled records in leads list: filter !enable in both filtered_lead_li derived and local IDB fast-path in handle_search_refresh - eslint.config.js: disable svelte/no-navigation-without-resolve (no base path configured) - Also includes _random field annotation cleanup (db_events, ae_types), iframe layout fixes, badge view tweaks, test updates, and doc updates from prior session Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
157 lines
7.2 KiB
TypeScript
157 lines
7.2 KiB
TypeScript
/**
|
|
* 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_v3_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<string, any> = {}) {
|
|
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<string, any> = {}) {
|
|
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);
|
|
});
|
|
});
|