Tests: fix IDAA recovery meeting test suite
Multiple failures caused by the SWR background fetch pattern and collapsed UI sections: 1. IDB settle wait: add waitForFunction that polls IndexedDB for tmp_sort_1 on the event record. tmp_sort_1 is only written by _process_generic_props, so its presence signals the background API fetch has completed and no further liveQuery re-fires will overwrite form inputs the test is about to fill. 2. JSON.parse for _json fields: update_ae_obj_v3 auto-serializes any key ending in _json to a JSON string before sending the PATCH. Tests now parse location_address_json, attend_json, and contact_li_json from the captured body before asserting field values. 3. Contact 2 section: collapsed by default when contact_2.full_name and email are null. Collapsed state renders type='hidden' inputs which Playwright's fill() rejects. Add click on the 'Contact 2 (Optional)' toggle before filling those fields. 4. Admin Options section: same collapse pattern. Add click on the 'Admin Options' toggle before filling status/sort/group/hide fields. 5. Increase suite timeout to 60 s: open_edit_form awaits real lookup API responses (pass_through_lookups=true) which can take 20-25 s on slow network, leaving no margin at the default 30 s limit.
This commit is contained in:
@@ -667,6 +667,11 @@ test.describe('IDAA Recovery Meetings — Real Backend Save (Integration)', () =
|
||||
// =============================================================================
|
||||
|
||||
test.describe('IDAA Recovery Meetings — Field Save Payload Verification', () => {
|
||||
// Each test calls open_edit_form which waits for real lookup API responses
|
||||
// (pass_through_lookups=true). On slow network or under load those can take
|
||||
// 20–25 s, leaving little margin at the default 30 s limit. 60 s gives a safe
|
||||
// buffer without masking genuine hangs.
|
||||
test.setTimeout(60000);
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
page.on('pageerror', (err: Error) =>
|
||||
@@ -743,6 +748,44 @@ test.describe('IDAA Recovery Meetings — Field Save Payload Verification', () =
|
||||
},
|
||||
{ timeout: 5000 }
|
||||
);
|
||||
// Wait for the background SWR list fetch (load_ae_obj_li__event in +layout.ts) to
|
||||
// have completed and written processed event data back to IDB.
|
||||
//
|
||||
// WHY THIS MATTERS: The layout fires load_ae_obj_li__event on every page load.
|
||||
// It returns the IDB cache immediately (SWR), then fires a background API fetch.
|
||||
// When the fetch completes, _process_generic_props processes the result and saves
|
||||
// it to IDB — which triggers a liveQuery re-fire that resets reactive form inputs
|
||||
// back to IDB values. If a test fills an input BEFORE this re-fire, the liveQuery
|
||||
// overwrites it with the API value (e.g., address_name → undefined, attend_json → {}).
|
||||
//
|
||||
// _process_generic_props always computes tmp_sort_1 as part of processing. Its
|
||||
// presence in IDB is the reliable signal that the background fetch has settled and
|
||||
// no further liveQuery re-fires will overwrite values the test is about to fill.
|
||||
await page.waitForFunction(
|
||||
(event_id: string) => {
|
||||
return new Promise<boolean>((resolve) => {
|
||||
try {
|
||||
const req = indexedDB.open('ae_events_db', 6);
|
||||
req.onerror = () => resolve(false);
|
||||
req.onsuccess = (e: Event) => {
|
||||
try {
|
||||
const db = (e.target as IDBOpenDBRequest).result;
|
||||
const tx = db.transaction(['event'], 'readonly');
|
||||
const store = tx.objectStore('event');
|
||||
const get_req = store.get(event_id);
|
||||
get_req.onerror = () => resolve(false);
|
||||
get_req.onsuccess = (e2: Event) => {
|
||||
const rec = (e2.target as IDBRequest).result;
|
||||
resolve(rec != null && rec.tmp_sort_1 != null);
|
||||
};
|
||||
} catch { resolve(false); }
|
||||
};
|
||||
} catch { resolve(false); }
|
||||
});
|
||||
},
|
||||
TEST_EVENT_ID,
|
||||
{ timeout: 5000 }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -811,7 +854,10 @@ test.describe('IDAA Recovery Meetings — Field Save Payload Verification', () =
|
||||
|
||||
const body = await capture_patch_body(page);
|
||||
|
||||
const addr = body.location_address_json;
|
||||
// _json fields are auto-serialized to JSON strings by update_ae_obj_v3 before
|
||||
// the PATCH is sent (Standard Aether Pattern: any key ending in _json is
|
||||
// JSON.stringify'd so the backend receives a string, not an object).
|
||||
const addr = JSON.parse(body.location_address_json);
|
||||
expect(addr, 'location_address_json is present').toBeTruthy();
|
||||
expect(addr.name, 'address_name → .name').toBe('Test Community Hall');
|
||||
expect(addr.line_1, 'address_line_1 → .line_1').toBe('100 Test Boulevard');
|
||||
@@ -845,7 +891,9 @@ test.describe('IDAA Recovery Meetings — Field Save Payload Verification', () =
|
||||
|
||||
const body = await capture_patch_body(page);
|
||||
|
||||
expect(body.attend_json?.zoom, 'attend_json.zoom is present').toBeTruthy();
|
||||
// attend_json is auto-serialized to a JSON string by update_ae_obj_v3.
|
||||
const attend = JSON.parse(body.attend_json);
|
||||
expect(attend?.zoom, 'attend_json.zoom is present').toBeTruthy();
|
||||
expect(body.attend_url_code, 'Zoom meeting ID').toBe('82345678901');
|
||||
expect(body.attend_url_passcode, 'Zoom passcode').toBe('mypassword');
|
||||
});
|
||||
@@ -928,17 +976,26 @@ test.describe('IDAA Recovery Meetings — Field Save Payload Verification', () =
|
||||
await page.locator('input[name="contact_1_email"]').fill('acarter@idaa-test.org');
|
||||
await page.locator('input[name="contact_1_phone_mobile"]').fill('312-555-0001');
|
||||
|
||||
// Contact 2 is collapsed by default when both contact_2.full_name and
|
||||
// contact_2.email are null (as in mock_event). The collapsed {:else} block renders
|
||||
// type="hidden" inputs — Playwright's fill() rejects these. Expand the section first.
|
||||
const contact_2_toggle = page.getByRole('button', { name: /Contact 2 \(Optional\)/ });
|
||||
await contact_2_toggle.click();
|
||||
await expect(page.locator('input[name="contact_2_full_name"]')).toBeVisible({ timeout: 2000 });
|
||||
|
||||
await page.locator('input[name="contact_2_full_name"]').fill('Dr. Bob Lee');
|
||||
await page.locator('input[name="contact_2_email"]').fill('blee@idaa-test.org');
|
||||
|
||||
const body = await capture_patch_body(page);
|
||||
|
||||
expect(Array.isArray(body.contact_li_json), 'contact_li_json is an array').toBe(true);
|
||||
expect(body.contact_li_json[0]?.full_name, 'contact 1 name').toBe('Dr. Alice Carter');
|
||||
expect(body.contact_li_json[0]?.email, 'contact 1 email').toBe('acarter@idaa-test.org');
|
||||
expect(body.contact_li_json[0]?.phone_mobile, 'contact 1 mobile').toBe('312-555-0001');
|
||||
expect(body.contact_li_json[1]?.full_name, 'contact 2 name').toBe('Dr. Bob Lee');
|
||||
expect(body.contact_li_json[1]?.email, 'contact 2 email').toBe('blee@idaa-test.org');
|
||||
// contact_li_json is auto-serialized to a JSON string by update_ae_obj_v3.
|
||||
const contacts = JSON.parse(body.contact_li_json);
|
||||
expect(Array.isArray(contacts), 'contact_li_json is an array').toBe(true);
|
||||
expect(contacts[0]?.full_name, 'contact 1 name').toBe('Dr. Alice Carter');
|
||||
expect(contacts[0]?.email, 'contact 1 email').toBe('acarter@idaa-test.org');
|
||||
expect(contacts[0]?.phone_mobile, 'contact 1 mobile').toBe('312-555-0001');
|
||||
expect(contacts[1]?.full_name, 'contact 2 name').toBe('Dr. Bob Lee');
|
||||
expect(contacts[1]?.email, 'contact 2 email').toBe('blee@idaa-test.org');
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -946,9 +1003,16 @@ test.describe('IDAA Recovery Meetings — Field Save Payload Verification', () =
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
test('admin fields (status, sort, group, hide, priority) are in PATCH payload', async ({ page }) => {
|
||||
// Admin section is visible because trusted_access=true in test setup
|
||||
// The Admin Options section is rendered (trusted_access=true) but collapsed by default
|
||||
// (show_admin_options starts false). Collapsed state renders type="hidden" inputs —
|
||||
// Playwright's fill() rejects these and isChecked() always returns false for hidden
|
||||
// checkboxes. Expand the section before interacting with any admin fields.
|
||||
await open_edit_form(page);
|
||||
|
||||
const admin_toggle = page.getByRole('button', { name: /Admin Options/ });
|
||||
await admin_toggle.click();
|
||||
await expect(page.locator('input[name="status"]')).toBeVisible({ timeout: 2000 });
|
||||
|
||||
await page.locator('input[name="status"]').fill('active');
|
||||
await page.locator('input[name="sort"]').fill('30');
|
||||
await page.locator('input[name="group"]').fill('International');
|
||||
|
||||
Reference in New Issue
Block a user