diff --git a/src/lib/ae_events/ae_events__event_badge.spec.ts b/src/lib/ae_events/ae_events__event_badge.spec.ts new file mode 100644 index 00000000..d310b397 --- /dev/null +++ b/src/lib/ae_events/ae_events__event_badge.spec.ts @@ -0,0 +1,97 @@ +import { describe, it, expect, vi } from 'vitest'; + +// Use hoist-safe factories for vi.mock +vi.mock('$lib/api/api', () => ({ + api: { + create_nested_obj_v3: vi.fn(), + update_nested_obj_v3: vi.fn(), + delete_nested_ae_obj_v3: vi.fn(), + search_ae_obj_v3: vi.fn() + } +})); +vi.mock('$lib/ae_core/core__idb_dexie', () => ({ db_save_ae_obj_li__ae_obj: vi.fn() })); +vi.mock('$lib/ae_events/db_events', () => ({ db_events: { badge: { get: vi.fn(), delete: vi.fn() } } })); +vi.mock('$lib/ae_events/ae_events__event_badge_template', () => ({ load_ae_obj_id__event_badge_template: vi.fn() })); + +import { create_ae_obj__event_badge } from './ae_events__event_badge'; + +describe('create_ae_obj__event_badge', () => { + it('calls api.create_nested_obj_v3 with the correct params and returns the result', async () => { + const mocked = await import('$lib/api/api'); + const mockCreateNested = mocked.api.create_nested_obj_v3 as jest.MockedFunction; + + const fakeResult = { event_badge_id: 'eb123', full_name: 'Test User' }; + mockCreateNested.mockResolvedValue(fakeResult); + + const api_cfg = { base_url: 'http://localhost', headers: {} }; + const event_id = 'evt1'; + const data_kv = { full_name_override: 'Test User', email: 't@example.com' }; + + const result = await create_ae_obj__event_badge({ api_cfg, event_id, data_kv, try_cache: false }); + + expect(mockCreateNested).toHaveBeenCalled(); + const calledWith = mockCreateNested.mock.calls[0][0]; + expect(calledWith.parent_type).toBe('event'); + expect(calledWith.parent_id).toBe(event_id); + expect(calledWith.child_type).toBe('event_badge'); + expect(calledWith.fields).toEqual(data_kv); + + expect(result).toEqual(fakeResult); + }); +}); + +describe('update_ae_obj__event_badge', () => { + it('calls api.update_nested_obj_v3 with correct params and returns result', async () => { + const mocked = await import('$lib/api/api'); + const mockUpdate = mocked.api.update_nested_obj_v3 as jest.MockedFunction; + const fakeResult = { event_badge_id: 'eb999', full_name: 'Updated' }; + mockUpdate.mockResolvedValue(fakeResult); + + const { update_ae_obj__event_badge } = await import('./ae_events__event_badge'); + const api_cfg = { base_url: 'http://localhost', headers: {} }; + const res = await update_ae_obj__event_badge({ api_cfg, event_id: 'evt1', event_badge_id: 'eb999', data_kv: { full_name_override: 'Updated' }, try_cache: false }); + + expect(mockUpdate).toHaveBeenCalled(); + const called = mockUpdate.mock.calls[0][0]; + expect(called.child_id).toBe('eb999'); + expect(called.fields).toEqual({ full_name_override: 'Updated' }); + expect(res).toEqual(fakeResult); + }); +}); + +describe('delete_ae_obj_id__event_badge', () => { + it('calls api.delete_nested_ae_obj_v3 and deletes from local DB when try_cache true', async () => { + const mocked = await import('$lib/api/api'); + const mockDelete = mocked.api.delete_nested_ae_obj_v3 as jest.MockedFunction; + mockDelete.mockResolvedValue({ success: true }); + + const db = await import('$lib/ae_events/db_events'); + const dbDelete = db.db_events.badge.delete as jest.MockedFunction; + + const { delete_ae_obj_id__event_badge } = await import('./ae_events__event_badge'); + + const api_cfg = { base_url: 'http://localhost', headers: {} }; + const res = await delete_ae_obj_id__event_badge({ api_cfg, event_id: 'evt1', event_badge_id: 'ebDel', try_cache: true }); + + expect(mockDelete).toHaveBeenCalled(); + expect(dbDelete).toHaveBeenCalledWith('ebDel'); + expect(res).toEqual({ success: true }); + }); +}); + +describe('search__event_badge', () => { + it('calls api.search_ae_obj_v3 and returns list (handles data envelope)', async () => { + const mocked = await import('$lib/api/api'); + const mockSearch = mocked.api.search_ae_obj_v3 as jest.MockedFunction; + const fakeList = [{ event_badge_id: 'eb1' }, { event_badge_id: 'eb2' }]; + mockSearch.mockResolvedValue({ data: fakeList }); + + const { search__event_badge } = await import('./ae_events__event_badge'); + const api_cfg = { base_url: 'http://localhost', headers: {} }; + const res = await search__event_badge({ api_cfg, event_id: 'evt1', fulltext_search_qry_str: 'Test', try_cache: false }); + + expect(mockSearch).toHaveBeenCalled(); + expect(Array.isArray(res)).toBe(true); + expect((res as any).length).toBe(2); + }); +}); diff --git a/src/routes/events/[event_id]/(badges)/badges/+layout.svelte b/src/routes/events/[event_id]/(badges)/badges/+layout.svelte index 16603b08..8a03d32a 100644 --- a/src/routes/events/[event_id]/(badges)/badges/+layout.svelte +++ b/src/routes/events/[event_id]/(badges)/badges/+layout.svelte @@ -9,46 +9,6 @@ let { data, children, log_lvl = 0 }: Props = $props(); // *** Import Svelte specific - // import { liveQuery } from 'dexie'; - - // import type { key_val } from '$lib/stores/ae_stores'; - // import { ae_util } from '$lib/ae_utils/ae_utils'; - - // import { core_func } from '$lib/ae_core_functions'; - // import { db_events } from '$lib/ae_events/db_events'; - // import { - // ae_snip, - // ae_loc, - // ae_sess, - // ae_api, - // ae_trig, - // slct, - // slct_trigger - // } from '$lib/stores/ae_stores'; - // import { - // events_loc, - // events_sess, - // events_slct, - // events_trigger - // } from '$lib/stores/ae_events_stores'; - // import { events_func } from '$lib/ae_events_functions'; - - // let lq__event_obj = $derived( - // liveQuery(async () => { - // if (log_lvl) { - // console.log( - // `*** LiveQuery: lq__event_obj *** event_id=${$events_slct.event_id}` - // ); - // } - // let results = await db_events.event.get( - // $events_slct?.event_id ?? '' - // ); - - // return results; - // }) - // ); - - // let nav_y_height = $state(0); let box: any; let xLeft = $state(0); @@ -73,10 +33,6 @@ } // *** Functions and Logic - // $effect(() => { - // // if ($events_trigger == 'load__event_badge_obj_li' && $events_slct.event_id) { - // // } - // }); - {@render children?.()} diff --git a/tests/README.md b/tests/README.md index 880def0d..e84ae32c 100644 --- a/tests/README.md +++ b/tests/README.md @@ -55,3 +55,11 @@ git commit -m "test: add " Help - If a test fails due to external network calls or platform-specific behavior, try mocking the relevant endpoints and move the test to `tests/disabled` if it cannot be made deterministic. + + +Development / Testing / Demo environment information +Use snake_case (or Snake_Case or Snake_case or test_NASA_example or test_API_key) +Aether test/demo base URL: 'http://demo.localhost:5173' +Aether development API: 'https://dev-api.oneskyit.com' +Aether test/demo account: '_XY7DXtc9MY' (1) "One Sky IT Demo" +Aether test/demo event: 'pjrcghqwert' (1) "Demo One Sky IT Conference" \ No newline at end of file diff --git a/tests/_helpers/env.ts b/tests/_helpers/env.ts new file mode 100644 index 00000000..b2b81977 --- /dev/null +++ b/tests/_helpers/env.ts @@ -0,0 +1,17 @@ +/** + * Centralized test environment constants for Playwright and unit tests. + * Use snake_case names per project preference. + */ +export const test_base_url = 'http://demo.localhost:5173'; +export const dev_api_base = 'https://dev-api.oneskyit.com'; +export const demo_account_id = '_XY7DXtc9MY'; +export const demo_event_id = 'pjrcghqwert'; + +export const TEST_ENV = { + test_base_url, + dev_api_base, + demo_account_id, + demo_event_id +}; + +export default TEST_ENV; diff --git a/tests/_helpers/minimal_v3_mocks.ts b/tests/_helpers/minimal_v3_mocks.ts new file mode 100644 index 00000000..7d305a29 --- /dev/null +++ b/tests/_helpers/minimal_v3_mocks.ts @@ -0,0 +1,73 @@ +import { Page } from '@playwright/test'; + +export const minimal_event = (event_id: string) => ({ + data: { + id: event_id, + event_id: event_id, + name: 'Test Event (minimal)', + cfg_json: {}, + mod_pres_mgmt_json: {}, + mod_badges_json: {}, + mod_abstracts_json: {}, + mod_exhibits_json: {}, + mod_meetings_json: {}, + }, +}); + +export const minimal_badge = (overrides: Record = {}) => ({ + event_badge_id: 'badge-1', + full_name_override: 'Test User', + email: 'test@example.com', + badge_type: 'test', + ...overrides, +}); + +/** + * Attach minimal V3 route handlers to a Playwright `page`. + * - Responds to event GET, badge search/create/update/delete, and a simple site_domain/search. + * - Keeps created badge state in-memory for the page session. + */ +export async function attach_minimal_v3_routes(page: Page, event_id: string) { + let created_badge: any = null; + + await page.route('**/v3/**', async (route) => { + const req = route.request(); + const url = req.url(); + const method = req.method(); + + if (url.includes('site_domain/search')) { + return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: [{ id: 'td', site_id: 'ts' }] }) }); + } + + if (url.includes(`/v3/crud/event/${event_id}`) && method === 'GET') { + return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(minimal_event(event_id)) }); + } + + if (url.includes(`/v3/crud/event/${event_id}/event_badge/search`) && method === 'POST') { + return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: created_badge ? [created_badge] : [] }) }); + } + + if (url.includes(`/v3/crud/event/${event_id}/event_badge`) && method === 'POST') { + const post = await req.postData(); + const body = post ? JSON.parse(post) : {}; + created_badge = { ...minimal_badge(body) }; + return route.fulfill({ status: 201, contentType: 'application/json', body: JSON.stringify({ data: created_badge }) }); + } + + if (url.match(new RegExp(`/v3/crud/event/${event_id}/event_badge/.+`)) && (method === 'PATCH' || method === 'PUT')) { + const post = await req.postData(); + const body = post ? JSON.parse(post) : {}; + if (created_badge) created_badge = { ...created_badge, ...body }; + return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: created_badge }) }); + } + + if (url.match(new RegExp(`/v3/crud/event/${event_id}/event_badge/.+`)) && method === 'DELETE') { + created_badge = null; + return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: true }) }); + } + + return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: [] }) }); + }); +} + +export default { minimal_event, minimal_badge, attach_minimal_v3_routes }; diff --git a/tests/event_badge.test.ts b/tests/event_badge.test.ts index fbcca36c..d1ed6f2a 100644 --- a/tests/event_badge.test.ts +++ b/tests/event_badge.test.ts @@ -1,5 +1,9 @@ import { test, expect } from '@playwright/test'; import { ae_app_local_data_defaults } from './_helpers/ae_defaults'; +import { demo_event_id, demo_account_id } from './_helpers/env'; +import { attach_minimal_v3_routes } from './_helpers/minimal_v3_mocks'; + +const demo_event = demo_event_id; test.describe('Event Badge Page - smoke', () => { test.beforeEach(async ({ page }) => { @@ -8,25 +12,20 @@ test.describe('Event Badge Page - smoke', () => { if (msg.type() === 'error' || msg.type() === 'warn') console.error(`BROWSER [${msg.type().toUpperCase()}]: ${msg.text()}`); }); - await page.route('**/*oneskyit.com/**', async (route) => { - const url = route.request().url(); - if (url.includes('site_domain/search')) { - return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: [{ id: 'td', site_id: 'ts' }] }) }); - } - return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: [] }) }); - }); + await attach_minimal_v3_routes(page, demo_event); - await page.addInitScript((defaults) => { - const testData = { ...defaults, account_id: 'smoke-account', manager_access: true }; - window.localStorage.setItem('ae_loc', JSON.stringify(testData)); - }, ae_app_local_data_defaults); + await page.addInitScript((defaults, event_id, account_id) => { + const test_data = { ...defaults, account_id: account_id, manager_access: true }; + test_data.mod = { ...defaults.mod, events: { ...defaults.mod.events, event_id } }; + window.localStorage.setItem('ae_loc', JSON.stringify(test_data)); + }, ae_app_local_data_defaults, demo_event, demo_account_id); }); test('loads badges list without console errors', async ({ page }) => { const errors: string[] = []; page.on('pageerror', (err) => errors.push(err.message)); - await page.goto('/events/test-event/badges'); + await page.goto(`/events/${demo_event}/badges`); await page.waitForResponse((r) => r.url().includes('site_domain/search') && r.status() === 200); expect(errors).toHaveLength(0); diff --git a/tests/event_badge_crud.test.ts b/tests/event_badge_crud.test.ts new file mode 100644 index 00000000..7e552b39 --- /dev/null +++ b/tests/event_badge_crud.test.ts @@ -0,0 +1,136 @@ +import { test, expect } from '@playwright/test'; +import { ae_app_local_data_defaults } from './_helpers/ae_defaults'; +import { demo_event_id, demo_account_id } from './_helpers/env'; + +const test_event_id = demo_event_id; + +test.describe('event_badge_crud (create, find, edit, delete)', () => { + test.beforeEach(async ({ page }) => { + page.on('pageerror', (err) => console.error(`BROWSER ERROR: ${err.message}`)); + page.on('console', (msg) => { + if (msg.type() === 'error' || msg.type() === 'warn') + console.error(`BROWSER [${msg.type().toUpperCase()}]: ${msg.text()}`); + }); + + // Minimal in-test state for created badge + let created_badge: any = null; + + await page.route('**/v3/**', async (route) => { + const req = route.request(); + const url = req.url(); + const method = req.method(); + + if (url.includes('site_domain/search')) { + return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: [{ id: 'td', site_id: 'ts' }] }) }); + } + + // Minimal event GET payload (include cfg_json and mod_* for bindings) + if (url.includes(`/v3/crud/event/${test_event_id}`) && method === 'GET') { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ data: { id: test_event_id, event_id: test_event_id, name: 'Test Event for Badge CRUD', cfg_json: {}, mod_pres_mgmt_json: {}, mod_badges_json: {}, mod_abstracts_json: {}, mod_exhibits_json: {}, mod_meetings_json: {} } }) + }); + } + + // Search/list badges + if (url.includes(`/v3/crud/event/${test_event_id}/event_badge/search`) && method === 'POST') { + return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: created_badge ? [created_badge] : [] }) }); + } + + // Create badge - return created envelope with fixed id + if (url.includes(`/v3/crud/event/${test_event_id}/event_badge`) && method === 'POST') { + const post = await req.postData(); + const body = post ? JSON.parse(post) : {}; + created_badge = { ...body, event_badge_id: 'badge-1' }; + return route.fulfill({ status: 201, contentType: 'application/json', body: JSON.stringify({ data: created_badge }) }); + } + + // Update badge + if (url.match(new RegExp(`/v3/crud/event/${test_event_id}/event_badge/.+`)) && (method === 'PATCH' || method === 'PUT')) { + const post = await req.postData(); + const body = post ? JSON.parse(post) : {}; + if (created_badge) created_badge = { ...created_badge, ...body }; + return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: created_badge }) }); + } + + // Delete badge + if (url.match(new RegExp(`/v3/crud/event/${test_event_id}/event_badge/.+`)) && method === 'DELETE') { + created_badge = null; + return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: true }) }); + } + + return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: [] }) }); + }); + + page.on('dialog', async (dialog) => { await dialog.accept(); }); + + await page.addInitScript(({ defaults, eventId, accountId }) => { + const test_data = { ...defaults, account_id: accountId, manager_access: true, administrator_access: true, edit_mode: true, mod: { ...defaults.mod, events: { ...defaults.mod.events, event_id: eventId } } }; + window.localStorage.setItem('ae_loc', JSON.stringify(test_data)); + }, { defaults: ae_app_local_data_defaults, eventId: test_event_id, accountId: demo_account_id }); + }); + + test('create -> find -> edit -> delete badge', async ({ page }) => { + // Create via Settings modal UI + await page.goto(`/events/${test_event_id}/settings`); + const add_btn = page.getByRole('button', { name: 'Add New Badge' }); + await expect(add_btn).toBeVisible(); + await add_btn.click(); + + const form = page.locator('form:has-text("Create Badge")'); + await expect(form).toBeVisible(); + await form.locator('label:has-text("Full Name Override") input').fill('CRUD User'); + await form.locator('label:has-text("Email") input').fill('crud@example.com'); + await form.locator('label:has-text("Badge Type") select').selectOption('test'); + + // Wait for POST create request and response + const [create_req, create_resp] = await Promise.all([ + page.waitForRequest((r) => r.url().includes(`/v3/crud/event/${test_event_id}/event_badge`) && r.method() === 'POST'), + page.waitForResponse((r) => r.url().includes(`/v3/crud/event/${test_event_id}/event_badge`) && (r.status() === 201 || r.status() === 200)), + page.getByRole('button', { name: 'Create Badge' }).click() + ]); + + const post_body = create_req.postData(); + const post_json = post_body ? JSON.parse(post_body) : {}; + expect(post_json.email).toBe('crud@example.com'); + + const created = await create_resp.json(); + expect(created.data).toBeDefined(); + const badge_id = created.data.event_badge_id || created.data.id || 'badge-1'; + + // Find badges via search endpoint (simulate listing page) using in-page fetch + await page.goto(`/events/${test_event_id}/badges`); + const search_json = await page.evaluate(async (eventId) => { + const r = await fetch(`/v3/crud/event/${eventId}/event_badge/search`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}) }); + return await r.json(); + }, test_event_id); + expect(Array.isArray(search_json.data)).toBeTruthy(); + expect(search_json.data.length).toBeGreaterThanOrEqual(1); + + // Edit badge via browser fetch (exercise nested update) + const edit_resp = await page.evaluate(async (args) => { + const { eventId, badgeId } = args; + const r = await fetch(`/v3/crud/event/${eventId}/event_badge/${badgeId}/`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ full_name_override: 'Edited User' }) }); + return { status: r.status, json: await r.json() }; + }, { eventId: test_event_id, badgeId: badge_id }); + expect(edit_resp.status === 200).toBeTruthy(); + expect(edit_resp.json.data.full_name_override).toBe('Edited User'); + + // Delete badge via browser fetch + const del_resp = await page.evaluate(async (args) => { + const { eventId, badgeId } = args; + const r = await fetch(`/v3/crud/event/${eventId}/event_badge/${badgeId}/`, { method: 'DELETE' }); + return { status: r.status, ok: r.ok }; + }, { eventId: test_event_id, badgeId: badge_id }); + expect(del_resp.ok).toBeTruthy(); + + // Confirm search returns no items + const post_delete_json = await page.evaluate(async (eventId) => { + const r = await fetch(`/v3/crud/event/${eventId}/event_badge/search`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}) }); + return await r.json(); + }, test_event_id); + expect(Array.isArray(post_delete_json.data)).toBeTruthy(); + expect(post_delete_json.data.length).toBe(0); + }); +}); diff --git a/tests/event_badge_interaction.test.ts b/tests/event_badge_interaction.test.ts index b9c02ac4..3dc617ab 100644 --- a/tests/event_badge_interaction.test.ts +++ b/tests/event_badge_interaction.test.ts @@ -1,7 +1,8 @@ import { test, expect } from '@playwright/test'; import { ae_app_local_data_defaults } from './_helpers/ae_defaults'; +import { demo_event_id, demo_account_id } from './_helpers/env'; -const testEventId = 'pjrcghqwert'; +const demo_event = demo_event_id; test.describe('Event Badge - interaction', () => { test.beforeEach(async ({ page }) => { @@ -11,25 +12,19 @@ test.describe('Event Badge - interaction', () => { console.error(`BROWSER [${msg.type().toUpperCase()}]: ${msg.text()}`); }); + // Minimal network mock: fulfill only what's necessary for the UI to render await page.route('**/v3/**', async (route) => { const req = route.request(); const url = req.url(); - if (url.includes('site_domain/search')) { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ data: [{ id: 'test-site-domain-id', site_id: 'test-site-id', account_id: '_XY7DXtc9MY' }] }) - }); - } - - if (url.includes(`/v3/crud/event/${testEventId}`) && req.method() === 'GET') { + // Provide the minimal event payload for the settings page to render + if (url.includes(`/v3/crud/event/${demo_event}`) && req.method() === 'GET') { return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: { - id: testEventId, - event_id: testEventId, + id: demo_event, + event_id: demo_event, name: 'Test Event for Badge Interaction', cfg_json: {}, mod_pres_mgmt_json: {}, @@ -41,14 +36,12 @@ test.describe('Event Badge - interaction', () => { }); } - if (url.includes(`/v3/crud/event/${testEventId}/event_badge`) && req.method() === 'POST') { - const post = await req.postData(); - console.log('Captured POST to event_badge endpoint:', url, post ? post.slice(0,200) : ''); - const body = post ? JSON.parse(post) : {}; - const created = { ...body, event_badge_id: 'new-badge-1' }; - return route.fulfill({ status: 201, contentType: 'application/json', body: JSON.stringify({ data: created }) }); + // For badge create requests, return a simple created envelope with a fixed id. + if (url.includes(`/v3/crud/event/${demo_event}/event_badge`) && req.method() === 'POST') { + return route.fulfill({ status: 201, contentType: 'application/json', body: JSON.stringify({ data: { event_badge_id: 'new-badge-1' } }) }); } + // Default: empty envelope so other calls don't error return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: [] }) }); }); @@ -57,58 +50,60 @@ test.describe('Event Badge - interaction', () => { }); await page.addInitScript( - ({ defaults, eventId }) => { + ({ defaults, event_id, account_id }) => { const testData = { ...defaults, - account_id: '_XY7DXtc9MY', + account_id: account_id, manager_access: true, administrator_access: true, edit_mode: true, - mod: { ...defaults.mod, events: { ...defaults.mod.events, event_id: eventId } } + mod: { ...defaults.mod, events: { ...defaults.mod.events, event_id: event_id } } }; window.localStorage.setItem('ae_loc', JSON.stringify(testData)); }, - { defaults: ae_app_local_data_defaults, eventId: testEventId } + { defaults: ae_app_local_data_defaults, event_id: demo_event, account_id: demo_account_id } ); }); test('creates a badge via UI and posts to nested endpoint', async ({ page }) => { - await page.goto(`/events/${testEventId}/settings`); + await page.goto(`/events/${demo_event}/settings`); - const addBtn = page.getByRole('button', { name: 'Add New Badge' }); - await expect(addBtn).toBeVisible(); - await addBtn.click(); + const add_btn = page.getByRole('button', { name: 'Add New Badge' }); + await expect(add_btn).toBeVisible(); + await add_btn.click(); const form = page.locator('form:has-text("Create Badge")'); await expect(form).toBeVisible(); - const fullNameInput = form.locator('label:has-text("Full Name Override") input'); - await expect(fullNameInput).toBeVisible(); - await fullNameInput.fill('Test User'); + const full_name_input = form.locator('label:has-text("Full Name Override") input'); + await expect(full_name_input).toBeVisible(); + await full_name_input.fill('Test User'); - const emailInput = form.locator('label:has-text("Email") input'); - await emailInput.fill('testuser@example.com'); + const email_input = form.locator('label:has-text("Email") input'); + await email_input.fill('testuser@example.com'); - const select = form.locator('label:has-text("Badge Type") select'); - await select.selectOption('test'); + const badge_type_select = form.locator('label:has-text("Badge Type") select'); + await badge_type_select.selectOption('test'); - const checkbox = form.locator('label:has-text("Allow Tracking") input[type="checkbox"]'); - await checkbox.check(); + const allow_tracking_checkbox = form.locator('label:has-text("Allow Tracking") input[type="checkbox"]'); + await allow_tracking_checkbox.check(); - const [request] = await Promise.all([ - page.waitForRequest((r) => r.url().includes(`/v3/crud/event/${testEventId}/event_badge`) && r.method() === 'POST'), - page.getByRole('button', { name: 'Create Badge' }).click() - ]); + const [create_request] = await Promise.all([ + page.waitForRequest((r) => r.url().includes(`/v3/crud/event/${demo_event}/event_badge`) && r.method() === 'POST'), + page.getByRole('button', { name: 'Create Badge' }).click() + ]); - const postBody = request.postData(); - const postJson = postBody ? JSON.parse(postBody) : {}; - expect(postJson.email).toBe('testuser@example.com'); - expect(postJson.full_name_override).toBe('Test User'); + const post_body = create_request.postData(); + const post_json = post_body ? JSON.parse(post_body) : {}; + expect(post_json.email).toBe('testuser@example.com'); + expect(post_json.full_name_override).toBe('Test User'); - const response = await page.waitForResponse((r) => r.url().includes(`/v3/crud/event/${testEventId}/event_badge`) && (r.status() === 201 || r.status() === 200)); - const respJson = await response.json(); - expect(respJson).toBeDefined(); - expect(respJson.data).toBeDefined(); - expect(respJson.data.event_badge_id).toBe('new-badge-1'); + // Use the request's response for reliability (page.request bypasses page.route otherwise) + const response = await create_request.response(); + expect(response).not.toBeNull(); + const resp_json = response ? await response.json() : {}; + expect(resp_json).toBeDefined(); + expect(resp_json.data).toBeDefined(); + expect(resp_json.data.event_badge_id).toBe('new-badge-1'); }); }); diff --git a/tests/event_presenter.test.ts b/tests/event_presenter.test.ts index ec3dbf84..726fec03 100644 --- a/tests/event_presenter.test.ts +++ b/tests/event_presenter.test.ts @@ -1,5 +1,9 @@ import { test, expect } from '@playwright/test'; import { ae_app_local_data_defaults } from './_helpers/ae_defaults'; +import { demo_event_id, demo_account_id } from './_helpers/env'; +import { attach_minimal_v3_routes } from './_helpers/minimal_v3_mocks'; + +const demo_event = demo_event_id; test.describe('Event Presenter Page - smoke', () => { test.beforeEach(async ({ page }) => { @@ -8,28 +12,21 @@ test.describe('Event Presenter Page - smoke', () => { if (msg.type() === 'error' || msg.type() === 'warn') console.error(`BROWSER [${msg.type().toUpperCase()}]: ${msg.text()}`); }); - await page.route('**/*oneskyit.com/**', async (route) => { - const url = route.request().url(); - if (url.includes('site_domain/search')) { - return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: [{ id: 'td', site_id: 'ts' }] }) }); - } - if (url.includes('/v3/crud/event/')) { - return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: { id: 'test-event', name: 'Test' } }) }); - } - return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: [] }) }); - }); + await attach_minimal_v3_routes(page, demo_event); - await page.addInitScript((defaults) => { - const testData = { ...defaults, account_id: 'smoke-account', manager_access: true }; - window.localStorage.setItem('ae_loc', JSON.stringify(testData)); - }, ae_app_local_data_defaults); + await page.addInitScript((defaults, event_id, account_id) => { + const test_data = { ...defaults, account_id: account_id, manager_access: true }; + // ensure the events module has the event id available + test_data.mod = { ...defaults.mod, events: { ...defaults.mod.events, event_id } }; + window.localStorage.setItem('ae_loc', JSON.stringify(test_data)); + }, ae_app_local_data_defaults, demo_event, demo_account_id); }); test('opens presenter editor without throwing', async ({ page }) => { const errors: string[] = []; page.on('pageerror', (e) => errors.push(e.message)); - await page.goto('/events/test-event/presenters'); + await page.goto(`/events/${demo_event}/presenters`); await page.waitForResponse((r) => r.url().includes('site_domain/search') && r.status() === 200); expect(errors).toHaveLength(0); diff --git a/tests/unit/create_event_badge.spec.ts b/tests/unit/create_event_badge.spec.ts new file mode 100644 index 00000000..a98411ae --- /dev/null +++ b/tests/unit/create_event_badge.spec.ts @@ -0,0 +1,42 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +// Aether test/demo base URL: 'http://demo.localhost:5173' +// Aether development API: 'https://dev-api.oneskyit.com' +// Aether test/demo account: '_XY7DXtc9MY' (1) "One Sky IT Demo" +// Aether test/demo event: 'pjrcghqwert' (1) "Demo One Sky IT Conference" + +// Mock the api module used by the ae_events file +const mockCreateNested = vi.fn(); +vi.mock('$lib/api/api', () => ({ api: { create_nested_obj_v3: mockCreateNested } })); + +// Avoid touching IndexedDB caching in this unit test +vi.mock('$lib/ae_core/core__idb_dexie', () => ({ db_save_ae_obj_li__ae_obj: vi.fn() })); + +// Import the function under test after mocks +import { create_ae_obj__event_badge } from '../../../src/lib/ae_events/ae_events__event_badge'; + +describe('create_ae_obj__event_badge', () => { + beforeEach(() => { + mockCreateNested.mockReset(); + }); + + it('calls api.create_nested_obj_v3 with the correct params and returns the result', async () => { + const fakeResult = { event_badge_id: 'eb123', full_name: 'Test User' }; + mockCreateNested.mockResolvedValue(fakeResult); + + const api_cfg = { base_url: 'http://localhost', headers: {} }; + const event_id = 'evt1'; + const data_kv = { full_name_override: 'Test User', email: 't@example.com' }; + + const result = await create_ae_obj__event_badge({ api_cfg, event_id, data_kv, try_cache: false }); + + expect(mockCreateNested).toHaveBeenCalled(); + const calledWith = mockCreateNested.mock.calls[0][0]; + expect(calledWith.parent_type).toBe('event'); + expect(calledWith.parent_id).toBe(event_id); + expect(calledWith.child_type).toBe('event_badge'); + expect(calledWith.fields).toEqual(data_kv); + + expect(result).toEqual(fakeResult); + }); +});