- Fix site_domain mock to return array { data: [mock_site_domain] } so
ae_api.headers['x-account-id'] gets a valid 11-char account_id.
Previously returned { data: {} } causing layout to fall back to 'ghost'
(5 chars) and the real API PATCH rejected the request with 422.
- Add integration test describe block (Real Backend Save) with
pass_through_event_patch option to let PATCH reach the real API.
- Extract localStorage injection into setup_idaa_auth() helper.
- Fix test name: 'sends PUT' → 'sends PATCH' (the API uses PATCH).
- All 14 tests pass.
589 lines
23 KiB
TypeScript
589 lines
23 KiB
TypeScript
/**
|
|
* Playwright tests: IDAA Recovery Meetings — Edit Form
|
|
*
|
|
* These tests cover the edit/create form for IDAA Recovery Meeting events.
|
|
* They simulate an authenticated IDAA member with trusted-level access.
|
|
*
|
|
* Test data:
|
|
* - Recovery Meeting event ID: 'IDAA_RM_TEST01' (fixture — not a real record)
|
|
* - Novi UUID (trusted member): 'c9ea07b5-06b0-4a43-a2d0-8d06558c8a82'
|
|
*
|
|
* NOTE: These fixtures are synthetic. API calls are fully mocked so no
|
|
* real backend traffic is required.
|
|
*/
|
|
|
|
import { test, expect } from '@playwright/test';
|
|
import { ae_app_local_data_defaults } from './_helpers/ae_defaults';
|
|
import { testing_account_id, mock_site_domain } from './_helpers/env';
|
|
|
|
// --- Test fixtures -----------------------------------------------------------
|
|
|
|
const TEST_EVENT_ID = '1Pkd025vvxU';// Per README test data
|
|
const TEST_NOVI_UUID = 'c9ea07b5-06b0-4a43-a2d0-8d06558c8a82'; // in default novi_trusted_li
|
|
const TEST_NOVI_NAME = 'IDAA Test Novi Member';
|
|
const TEST_NOVI_EMAIL = 'test+novi-member@oneskyit.com';
|
|
|
|
/** Minimal mock event object returned by the API. */
|
|
const mock_event = {
|
|
id: TEST_EVENT_ID,
|
|
event_id: TEST_EVENT_ID,
|
|
account_id: testing_account_id,
|
|
name: 'Thursday Night Serenity Group',
|
|
type: 'IDAA',
|
|
physical: true,
|
|
virtual: false,
|
|
address_name: 'Community Center',
|
|
address_city: 'Chicago',
|
|
address_country_subdivision_code: 'US-IL',
|
|
address_country_alpha_2_code: 'US',
|
|
timezone: 'US/Central',
|
|
recurring: true,
|
|
recurring_pattern: 'Weekly',
|
|
weekday_thursday: true,
|
|
recurring_start_time: '19:00',
|
|
recurring_end_time: '20:00',
|
|
recurring_text: '*gen* Weekly: Thursday at 7:00 PM US/Central',
|
|
contact_li_json: [
|
|
{ full_name: TEST_NOVI_NAME, email: TEST_NOVI_EMAIL, phone_mobile: null },
|
|
{ full_name: null, email: null }
|
|
],
|
|
location_text: '<p>Main entrance on Oak Street.</p>',
|
|
attend_text: null,
|
|
attend_json: {},
|
|
notes: null,
|
|
enable: true,
|
|
hide: false,
|
|
priority: false,
|
|
sort: null,
|
|
cfg_json: {},
|
|
data_json: {}
|
|
};
|
|
|
|
// --- Shared setup helpers ----------------------------------------------------
|
|
|
|
/**
|
|
* Build the ae_idaa_loc localStorage value for a trusted IDAA member who has
|
|
* the edit form open.
|
|
*/
|
|
function build_idaa_loc_defaults(opts: { edit_event_obj?: boolean } = {}) {
|
|
return {
|
|
ver: '2024-08-21_1646',
|
|
ver_idb: '2024-08-21_1645',
|
|
novi_uuid: TEST_NOVI_UUID,
|
|
novi_email: TEST_NOVI_EMAIL,
|
|
novi_full_name: TEST_NOVI_NAME,
|
|
novi_admin_li: ['2b078deb-b4e7-4203-99da-9f7cd62159a5'],
|
|
novi_trusted_li: [TEST_NOVI_UUID, '58db22ee-4b0a-49a7-9f34-53d2ba85a84b'],
|
|
novi_jitsi_mod_li: [],
|
|
novi_meetings_base_url: 'https://www.idaa.org/idaa-meetings',
|
|
recovery_meetings: {
|
|
edit__event_obj: opts.edit_event_obj ?? false,
|
|
qry__enabled: 'enabled',
|
|
qry__hidden: 'not_hidden',
|
|
qry__limit: 150,
|
|
qry__order_by: 'updated_on',
|
|
qry__order_by_li: { priority: 'DESC', sort: 'DESC', updated_on: 'DESC' },
|
|
qry__offset: 0,
|
|
qry__fulltext_str: null,
|
|
qry__physical: null,
|
|
qry__type: null,
|
|
qry__virtual: null
|
|
},
|
|
bb: { edit_kv: {}, edit__post_obj: null, edit__post_comment_obj: null },
|
|
archives: { edit_kv: {}, edit__archive_obj: null, edit__archive_content_obj: null },
|
|
ds: {},
|
|
idaa_cfg_json: {}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Inject ae_loc + ae_idaa_loc into localStorage so the IDAA layout
|
|
* authenticates as a trusted member with the edit form open.
|
|
* Must be called via page.addInitScript BEFORE any navigation.
|
|
*/
|
|
async function setup_idaa_auth(page: any) {
|
|
await page.addInitScript(
|
|
({ ae_defaults, account_id, idaa_loc_defaults }: any) => {
|
|
const ae_loc_data = {
|
|
...ae_defaults,
|
|
account_id,
|
|
authenticated_access: true,
|
|
trusted_access: true,
|
|
administrator_access: false,
|
|
access_type: 'trusted',
|
|
iframe: false,
|
|
site_cfg_json: {
|
|
slct__event_id: null,
|
|
novi_admin_li: ['2b078deb-b4e7-4203-99da-9f7cd62159a5'],
|
|
novi_trusted_li: [
|
|
'c9ea07b5-06b0-4a43-a2d0-8d06558c8a82',
|
|
'58db22ee-4b0a-49a7-9f34-53d2ba85a84b'
|
|
],
|
|
admin_email: 'admin@oneskyit.com',
|
|
noreply_email: 'noreply@oneskyit.com'
|
|
}
|
|
};
|
|
window.localStorage.setItem('ae_loc', JSON.stringify(ae_loc_data));
|
|
window.localStorage.setItem('ae_idaa_loc', JSON.stringify(idaa_loc_defaults));
|
|
},
|
|
{
|
|
ae_defaults: ae_app_local_data_defaults,
|
|
account_id: testing_account_id,
|
|
idaa_loc_defaults: build_idaa_loc_defaults({ edit_event_obj: true })
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Register route mocks for the V3 CRUD API and common lookup tables.
|
|
* Called inside each test after page creation.
|
|
*
|
|
* @param pass_through_event_patch - When true, the event PATCH (save) is NOT
|
|
* intercepted and will reach the real backend. The GET is still mocked so
|
|
* the form loads immediately from seeded IDB. Use this for integration
|
|
* tests that need to verify actual persistence.
|
|
*/
|
|
async function setup_api_mocks(
|
|
page: any,
|
|
event_id: string,
|
|
opts: { pass_through_event_patch?: boolean } = {}
|
|
) {
|
|
await page.route('**/v3/**', async (route: any) => {
|
|
const url = route.request().url();
|
|
const method = route.request().method();
|
|
|
|
// Event GET by ID
|
|
if (url.includes(`/v3/crud/event/${event_id}`) && method === 'GET') {
|
|
return route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ data: mock_event })
|
|
});
|
|
}
|
|
|
|
// Event PATCH (update)
|
|
if (url.includes(`/v3/crud/event/${event_id}`) && method === 'PATCH') {
|
|
if (opts.pass_through_event_patch) return route.continue();
|
|
return route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ data: { ...mock_event, event_id } })
|
|
});
|
|
}
|
|
|
|
// Event POST (create new)
|
|
if (url.includes('/v3/crud/event') && method === 'POST') {
|
|
return route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ data: { ...mock_event, event_id: 'NEW_EVENT_123' } })
|
|
});
|
|
}
|
|
|
|
// Event list search (POST to search endpoint)
|
|
if (url.includes('/v3/crud/event') && url.includes('search') && method === 'POST') {
|
|
return route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ data: [mock_event], meta: { total: 1 } })
|
|
});
|
|
}
|
|
|
|
// Lookup: time zones
|
|
if (url.includes('/v3/') && url.includes('time_zone')) {
|
|
return route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({
|
|
data: [
|
|
{ id: 'tz1', code: 'US/Central', name: 'US/Central' },
|
|
{ id: 'tz2', code: 'US/Eastern', name: 'US/Eastern' },
|
|
{ id: 'tz3', code: 'US/Pacific', name: 'US/Pacific' }
|
|
]
|
|
})
|
|
});
|
|
}
|
|
|
|
// Lookup: countries
|
|
if (url.includes('/v3/') && url.includes('country') && !url.includes('subdivision')) {
|
|
return route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({
|
|
data: [
|
|
{ id: 'c1', alpha_2_code: 'US', english_short_name: 'United States' },
|
|
{ id: 'c2', alpha_2_code: 'CA', english_short_name: 'Canada' }
|
|
]
|
|
})
|
|
});
|
|
}
|
|
|
|
// Lookup: country subdivisions (states/provinces)
|
|
if (url.includes('/v3/') && url.includes('country_subdivision')) {
|
|
return route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({
|
|
data: [
|
|
{ id: 's1', code: 'US-IL', name: 'Illinois' },
|
|
{ id: 's2', code: 'US-NY', name: 'New York' }
|
|
]
|
|
})
|
|
});
|
|
}
|
|
|
|
// Lookup: event types
|
|
if (url.includes('/v3/') && url.includes('event_type')) {
|
|
return route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({
|
|
data: [
|
|
{ id: 'et1', code: 'aa', name: 'AA' },
|
|
{ id: 'et2', code: 'al-anon', name: 'Al-Anon' }
|
|
]
|
|
})
|
|
});
|
|
}
|
|
|
|
// Layout/site domain lookup — must return a proper account_id so the
|
|
// layout builds ae_api.headers['x-account-id'] correctly.
|
|
// search_ae_obj_v3 expects { data: [array] }, not a single object.
|
|
if (url.includes('/v3/crud/site_domain') || url.includes('/v3/lookup/')) {
|
|
return route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ data: [mock_site_domain] })
|
|
});
|
|
}
|
|
|
|
// Let everything else pass through or return a generic empty success
|
|
return route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ data: [] })
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Seed the ae_events_db IndexedDB with the test event so liveQuery picks it up.
|
|
*/
|
|
async function seed_event_idb(page: any, event: typeof mock_event) {
|
|
await page.evaluate((ev: typeof mock_event) => {
|
|
return new Promise<void>((resolve) => {
|
|
try {
|
|
const req = indexedDB.open('ae_events_db', 6);
|
|
req.onupgradeneeded = (e) => {
|
|
const db = (e.target as IDBOpenDBRequest).result;
|
|
if (!db.objectStoreNames.contains('event')) {
|
|
db.createObjectStore('event', { keyPath: 'id' });
|
|
}
|
|
};
|
|
req.onsuccess = () => {
|
|
const db = req.result;
|
|
try {
|
|
const tx = db.transaction('event', 'readwrite');
|
|
tx.objectStore('event').put(ev);
|
|
tx.oncomplete = () => { db.close(); resolve(); };
|
|
tx.onerror = () => { db.close(); resolve(); };
|
|
} catch {
|
|
db.close(); resolve();
|
|
}
|
|
};
|
|
req.onerror = () => resolve();
|
|
} catch { resolve(); }
|
|
});
|
|
}, event);
|
|
}
|
|
|
|
// --- Test suite --------------------------------------------------------------
|
|
|
|
test.describe('IDAA Recovery Meetings — Edit Form', () => {
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
page.on('pageerror', (err: Error) =>
|
|
console.error(`BROWSER ERROR: ${err.message}`)
|
|
);
|
|
await setup_idaa_auth(page);
|
|
await setup_api_mocks(page, TEST_EVENT_ID);
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Section 1: Page load and form structure
|
|
// -------------------------------------------------------------------------
|
|
|
|
test('edit form renders after navigation to event page', async ({ page }) => {
|
|
await page.goto(`/`);
|
|
await seed_event_idb(page, mock_event);
|
|
await page.goto(`/idaa/recovery_meetings/${TEST_EVENT_ID}`);
|
|
|
|
// The edit form section has the class 'edit__event_obj'
|
|
const form = page.locator('section.edit__event_obj');
|
|
await expect(form).toBeVisible({ timeout: 10000 });
|
|
});
|
|
|
|
test('general information section is present', async ({ page }) => {
|
|
await page.goto(`/`);
|
|
await seed_event_idb(page, mock_event);
|
|
await page.goto(`/idaa/recovery_meetings/${TEST_EVENT_ID}`);
|
|
|
|
await page.waitForSelector('section.edit__event_obj', { timeout: 10000 });
|
|
|
|
// Meeting name input
|
|
const name_input = page.locator('input[name="name"]');
|
|
await expect(name_input).toBeVisible();
|
|
|
|
// Type select (IDAA meeting types)
|
|
const type_select = page.locator('select[name="type"]');
|
|
await expect(type_select).toBeVisible();
|
|
// Verify real option values exist
|
|
await expect(type_select.locator('option[value="IDAA"]')).toBeAttached();
|
|
await expect(type_select.locator('option[value="Caduceus"]')).toBeAttached();
|
|
});
|
|
|
|
test('timing section shows day-of-week checkboxes', async ({ page }) => {
|
|
await page.goto(`/`);
|
|
await seed_event_idb(page, mock_event);
|
|
await page.goto(`/idaa/recovery_meetings/${TEST_EVENT_ID}`);
|
|
|
|
await page.waitForSelector('section.edit__event_obj', { timeout: 10000 });
|
|
|
|
// All seven weekday checkboxes must be present
|
|
for (const day of [
|
|
'weekday_sunday', 'weekday_monday', 'weekday_tuesday',
|
|
'weekday_wednesday', 'weekday_thursday', 'weekday_friday',
|
|
'weekday_saturday'
|
|
]) {
|
|
await expect(page.locator(`input[name="${day}"]`)).toBeAttached();
|
|
}
|
|
});
|
|
|
|
test('start and end time inputs are present', async ({ page }) => {
|
|
await page.goto(`/`);
|
|
await seed_event_idb(page, mock_event);
|
|
await page.goto(`/idaa/recovery_meetings/${TEST_EVENT_ID}`);
|
|
|
|
await page.waitForSelector('section.edit__event_obj', { timeout: 10000 });
|
|
|
|
await expect(page.locator('input[name="recurring_start_time"]')).toBeAttached();
|
|
await expect(page.locator('input[name="recurring_end_time"]')).toBeAttached();
|
|
});
|
|
|
|
test('contact section shows contact_1 fields', async ({ page }) => {
|
|
await page.goto(`/`);
|
|
await seed_event_idb(page, mock_event);
|
|
await page.goto(`/idaa/recovery_meetings/${TEST_EVENT_ID}`);
|
|
|
|
await page.waitForSelector('section.edit__event_obj', { timeout: 10000 });
|
|
|
|
await expect(page.locator('input[name="contact_1_full_name"]')).toBeAttached();
|
|
await expect(page.locator('input[name="contact_1_email"]')).toBeAttached();
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Section 2: Field interactions
|
|
// -------------------------------------------------------------------------
|
|
|
|
test('meeting name field accepts text input', async ({ page }) => {
|
|
await page.goto(`/`);
|
|
await seed_event_idb(page, mock_event);
|
|
await page.goto(`/idaa/recovery_meetings/${TEST_EVENT_ID}`);
|
|
|
|
await page.waitForSelector('section.edit__event_obj', { timeout: 10000 });
|
|
|
|
const name_input = page.locator('input[name="name"]');
|
|
await name_input.fill('Updated Meeting Name');
|
|
await expect(name_input).toHaveValue('Updated Meeting Name');
|
|
});
|
|
|
|
test('weekday checkbox can be checked and unchecked', async ({ page }) => {
|
|
await page.goto(`/`);
|
|
await seed_event_idb(page, mock_event);
|
|
await page.goto(`/idaa/recovery_meetings/${TEST_EVENT_ID}`);
|
|
|
|
await page.waitForSelector('section.edit__event_obj', { timeout: 10000 });
|
|
|
|
const wednesday_cb = page.locator('input[name="weekday_wednesday"]');
|
|
const initial_state = await wednesday_cb.isChecked();
|
|
|
|
await wednesday_cb.click();
|
|
await expect(wednesday_cb).toBeChecked({ checked: !initial_state });
|
|
|
|
// Toggle back
|
|
await wednesday_cb.click();
|
|
await expect(wednesday_cb).toBeChecked({ checked: initial_state });
|
|
});
|
|
|
|
test('physical checkbox shows/hides address section', async ({ page }) => {
|
|
await page.goto(`/`);
|
|
await seed_event_idb(page, mock_event);
|
|
await page.goto(`/idaa/recovery_meetings/${TEST_EVENT_ID}`);
|
|
|
|
await page.waitForSelector('section.edit__event_obj', { timeout: 10000 });
|
|
|
|
// Address fieldset is present in the DOM (visibility driven by $idaa_slct.event_obj.physical)
|
|
const address_fieldset = page.locator('fieldset#physical_address');
|
|
await expect(address_fieldset).toBeAttached();
|
|
|
|
// Address fieldset should not have the 'hidden' class since physical = true in mock event
|
|
// Wait for the reactivity to settle by waiting for the address_name input to be present
|
|
await expect(address_fieldset).not.toHaveClass(/hidden/, { timeout: 5000 });
|
|
});
|
|
|
|
test('start time field accepts a time value', async ({ page }) => {
|
|
await page.goto(`/`);
|
|
await seed_event_idb(page, mock_event);
|
|
await page.goto(`/idaa/recovery_meetings/${TEST_EVENT_ID}`);
|
|
|
|
await page.waitForSelector('section.edit__event_obj', { timeout: 10000 });
|
|
|
|
const start_time = page.locator('input[name="recurring_start_time"]');
|
|
await start_time.fill('19:30');
|
|
await expect(start_time).toHaveValue('19:30');
|
|
});
|
|
|
|
test('contact_1 full name field accepts text input', async ({ page }) => {
|
|
await page.goto(`/`);
|
|
await seed_event_idb(page, mock_event);
|
|
await page.goto(`/idaa/recovery_meetings/${TEST_EVENT_ID}`);
|
|
|
|
await page.waitForSelector('section.edit__event_obj', { timeout: 10000 });
|
|
|
|
const contact_name = page.locator('input[name="contact_1_full_name"]');
|
|
await contact_name.fill('Jane Smith');
|
|
await expect(contact_name).toHaveValue('Jane Smith');
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Section 3: Submit behavior
|
|
// -------------------------------------------------------------------------
|
|
|
|
test('save button is present and clickable', async ({ page }) => {
|
|
await page.goto(`/`);
|
|
await seed_event_idb(page, mock_event);
|
|
await page.goto(`/idaa/recovery_meetings/${TEST_EVENT_ID}`);
|
|
|
|
await page.waitForSelector('section.edit__event_obj', { timeout: 10000 });
|
|
|
|
// The form's submit button — visible at the bottom of the form
|
|
const save_btn = page.locator('button[type="submit"]').filter({ hasText: /save|submit/i }).first();
|
|
await expect(save_btn).toBeVisible();
|
|
await expect(save_btn).toBeEnabled();
|
|
});
|
|
|
|
test('form submission sends PATCH request to event API', async ({ page }) => {
|
|
await page.goto(`/`);
|
|
await seed_event_idb(page, mock_event);
|
|
await page.goto(`/idaa/recovery_meetings/${TEST_EVENT_ID}`);
|
|
|
|
await page.waitForSelector('section.edit__event_obj', { timeout: 10000 });
|
|
|
|
// Track API calls
|
|
const api_calls: Array<{ url: string; method: string }> = [];
|
|
page.on('request', (req: any) => {
|
|
if (req.url().includes('/v3/') && req.method() === 'PATCH') {
|
|
api_calls.push({ url: req.url(), method: req.method() });
|
|
}
|
|
});
|
|
|
|
// Make a visible change then submit
|
|
const name_input = page.locator('input[name="name"]');
|
|
await name_input.fill('Updated Thursday Group');
|
|
|
|
const save_btn = page.locator('button[type="submit"]').filter({ hasText: /save|submit/i }).first();
|
|
await save_btn.click();
|
|
|
|
// Wait briefly for the async submit to fire
|
|
await page.waitForTimeout(1500);
|
|
|
|
// Expect a PATCH was made to the event endpoint
|
|
const patch_to_event = api_calls.some(c => c.url.includes(`/v3/crud/event/${TEST_EVENT_ID}`));
|
|
expect(patch_to_event, 'Expected PATCH request to event API endpoint').toBe(true);
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Section 4: Virtual / Zoom platform section
|
|
// -------------------------------------------------------------------------
|
|
|
|
|
|
test('virtual checkbox reveals virtual/platform section', async ({ page }) => {
|
|
await page.goto(`/`);
|
|
// Use a virtual-only event
|
|
const virtual_event = {
|
|
...mock_event,
|
|
id: TEST_EVENT_ID,
|
|
physical: false,
|
|
virtual: true,
|
|
attend_json: { zoom: null }
|
|
};
|
|
await seed_event_idb(page, virtual_event);
|
|
await page.goto(`/idaa/recovery_meetings/${TEST_EVENT_ID}`);
|
|
|
|
await page.waitForSelector('section.edit__event_obj', { timeout: 10000 });
|
|
|
|
// The virtual checkbox (id="virtual") should be present and checked
|
|
const virtual_cb = page.locator('input#virtual');
|
|
await expect(virtual_cb).toBeAttached();
|
|
});
|
|
|
|
});
|
|
|
|
// =============================================================================
|
|
// Integration tests — real backend
|
|
// These tests use the real event ID and let CRUD calls reach the actual API.
|
|
// They will modify real data in the database.
|
|
// =============================================================================
|
|
|
|
test.describe('IDAA Recovery Meetings — Real Backend Save (Integration)', () => {
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
page.on('pageerror', (err: Error) =>
|
|
console.error(`BROWSER ERROR: ${err.message}`)
|
|
);
|
|
await setup_idaa_auth(page);
|
|
// pass_through_event_patch=true: only PATCH for this event reaches the real API.
|
|
// Lookups and GET are still mocked so the form loads instantly from seeded IDB.
|
|
await setup_api_mocks(page, TEST_EVENT_ID, { pass_through_event_patch: true });
|
|
});
|
|
|
|
test('save updated meeting name persists to backend', async ({ page }) => {
|
|
// Seed IDB so the form populates immediately without waiting for the real API GET.
|
|
// The mock_event name will appear in the field; we overwrite it before saving.
|
|
await page.goto(`/`);
|
|
await seed_event_idb(page, mock_event);
|
|
await page.goto(`/idaa/recovery_meetings/${TEST_EVENT_ID}`);
|
|
|
|
// Wait for the form field to be populated from IDB via liveQuery
|
|
const name_input = page.locator('input[name="name"]');
|
|
await expect(name_input).toBeVisible({ timeout: 15000 });
|
|
await expect(name_input).not.toHaveValue('', { timeout: 8000 });
|
|
|
|
// Read the current name and append a test marker (idempotent: strip any previous marker first)
|
|
const current_name = await name_input.inputValue();
|
|
const base_name = current_name.replace(/ \[PW\]$/, '').trim();
|
|
const new_name = `${base_name} [PW]`;
|
|
|
|
await name_input.fill(new_name);
|
|
|
|
const save_btn = page.locator('button[type="submit"]').filter({ hasText: /save/i }).first();
|
|
await expect(save_btn).toBeEnabled();
|
|
|
|
// Click Save and wait for the real PATCH response
|
|
const [response] = await Promise.all([
|
|
page.waitForResponse(
|
|
(resp) =>
|
|
resp.url().includes(`/v3/crud/event/${TEST_EVENT_ID}`) &&
|
|
resp.request().method() === 'PATCH',
|
|
{ timeout: 15000 }
|
|
),
|
|
save_btn.click()
|
|
]);
|
|
|
|
const body = await response.json();
|
|
expect(response.status(), 'PATCH should return HTTP 200').toBe(200);
|
|
expect(body?.data?.name ?? body?.data?.event_id, 'Response should include event data').toBeTruthy();
|
|
});
|
|
|
|
});
|