237 lines
9.3 KiB
TypeScript
237 lines
9.3 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
import { testing_event_id, mock_site_domain } from './_helpers/env';
|
|
import {
|
|
minimal_event_for_leads,
|
|
seed_ae_loc,
|
|
} from './_helpers/leads_helpers';
|
|
|
|
const event_id = testing_event_id;
|
|
const config_url = `/events/${event_id}/leads/config`;
|
|
|
|
/**
|
|
* Minimal route mock for the Leads Config page.
|
|
*
|
|
* The config page only needs the event record (not exhibit or badge data) plus
|
|
* the site_domain init call. A PATCH handler is included for save tests.
|
|
*
|
|
* @param patch_handler Optional callback invoked when a PATCH to the event is intercepted.
|
|
* Defaults to returning 200 OK.
|
|
*/
|
|
async function setup_config_routes(
|
|
page: import('@playwright/test').Page,
|
|
opts: {
|
|
event_data_overrides?: Record<string, any>;
|
|
on_patch?: (body: any) => void;
|
|
} = {}
|
|
) {
|
|
const { event_data_overrides = {}, on_patch } = opts;
|
|
|
|
await page.route('**/v3/**', async (route) => {
|
|
const req = route.request();
|
|
const url = req.url();
|
|
const method = req.method();
|
|
|
|
// Site domain init
|
|
if (url.includes('site_domain/search')) {
|
|
return route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ data: [mock_site_domain] }),
|
|
});
|
|
}
|
|
|
|
// Event GET — provides mod_exhibits_json for the draft
|
|
if (url.includes(`/v3/crud/event/${event_id}`) && method === 'GET') {
|
|
return route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify(minimal_event_for_leads(event_id, event_data_overrides)),
|
|
});
|
|
}
|
|
|
|
// Event PATCH — save config
|
|
if (url.includes(`/v3/crud/event/${event_id}`) && method === 'PATCH') {
|
|
const raw = await req.postData();
|
|
const body = raw ? JSON.parse(raw) : {};
|
|
on_patch?.(body);
|
|
return route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ data: { id: event_id } }),
|
|
});
|
|
}
|
|
|
|
return route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ data: [] }),
|
|
});
|
|
});
|
|
}
|
|
|
|
test.describe('Leads — Config Page', () => {
|
|
// -----------------------------------------------------------------------
|
|
// 1. Non-admin user sees access-denied screen
|
|
//
|
|
// The config page is administrator_access only. Any other access level must
|
|
// see the Lock icon and "Administrator access required." — no form fields.
|
|
// -----------------------------------------------------------------------
|
|
test('non-admin user sees access-denied message, no form', async ({ page }) => {
|
|
page.on('pageerror', (err) => console.error(`BROWSER ERROR: ${err.message}`));
|
|
await setup_config_routes(page);
|
|
await seed_ae_loc(page, { allow_access: true, trusted_access: true });
|
|
|
|
await page.goto(config_url);
|
|
|
|
// Access denied block must be visible
|
|
await expect(
|
|
page.locator('text=Administrator access required.')
|
|
).toBeVisible({ timeout: 10_000 });
|
|
|
|
// Form / save button must NOT be visible
|
|
await expect(page.locator('text=Leads Config')).not.toBeVisible();
|
|
await expect(page.locator('button:has-text("Save")')).not.toBeVisible();
|
|
});
|
|
|
|
// -----------------------------------------------------------------------
|
|
// 2. Admin user sees config form
|
|
//
|
|
// administrator_access = true reveals the full form with both sections
|
|
// (Payment and Stripe Keys) and the Save button.
|
|
// -----------------------------------------------------------------------
|
|
test('admin user sees config form with Payment and Stripe sections', async ({ page }) => {
|
|
page.on('pageerror', (err) => console.error(`BROWSER ERROR: ${err.message}`));
|
|
await setup_config_routes(page);
|
|
await seed_ae_loc(page, {
|
|
allow_access: true,
|
|
authenticated_access: true,
|
|
trusted_access: true,
|
|
administrator_access: true,
|
|
});
|
|
|
|
await page.goto(config_url);
|
|
|
|
// Heading confirms page rendered
|
|
await expect(page.locator('h1:has-text("Leads Config")')).toBeVisible({ timeout: 10_000 });
|
|
|
|
// Both collapsible sections are visible (default open)
|
|
// Use the checkbox label text — it's unique inside the Payment section
|
|
await expect(page.locator('text=Require Payment (Stripe)')).toBeVisible({ timeout: 5_000 });
|
|
await expect(page.locator('text=Stripe Keys')).toBeVisible({ timeout: 5_000 });
|
|
|
|
// Save button is present — two exist (header + bottom), check the header one
|
|
await expect(page.locator('button:has-text("Save")').first()).toBeVisible({ timeout: 5_000 });
|
|
});
|
|
|
|
// -----------------------------------------------------------------------
|
|
// 3. Save button disabled until form is dirty
|
|
//
|
|
// On load, is_dirty = false because draft JSON === initial JSON.
|
|
// After toggling the "Require Payment" checkbox, is_dirty = true and the
|
|
// Save button becomes enabled.
|
|
// -----------------------------------------------------------------------
|
|
test('save button disabled by default, enabled after checkbox change', async ({ page }) => {
|
|
page.on('pageerror', (err) => console.error(`BROWSER ERROR: ${err.message}`));
|
|
// Start with leads_require_payment: false so toggling it makes a real change
|
|
await setup_config_routes(page, {
|
|
event_data_overrides: {
|
|
mod_exhibits_json: { leads_require_payment: false },
|
|
},
|
|
});
|
|
await seed_ae_loc(page, {
|
|
allow_access: true,
|
|
authenticated_access: true,
|
|
trusted_access: true,
|
|
administrator_access: true,
|
|
});
|
|
|
|
await page.goto(config_url);
|
|
|
|
// Wait for event to load and draft to initialize
|
|
await page.waitForResponse(
|
|
(r) => r.url().includes(`crud/event/${event_id}`) && r.status() === 200,
|
|
{ timeout: 8_000 }
|
|
);
|
|
|
|
// Wait for the form to render (draft_initialized)
|
|
await expect(
|
|
page.locator('input[type="checkbox"]').first()
|
|
).toBeVisible({ timeout: 8_000 });
|
|
|
|
// Save must be disabled (not dirty yet)
|
|
await expect(page.locator('button:has-text("Save Config")')).toBeDisabled({ timeout: 3_000 });
|
|
|
|
// Toggle the "Require Payment" checkbox
|
|
await page.locator('input[type="checkbox"]').first().click();
|
|
|
|
// Save must now be enabled
|
|
await expect(page.locator('button:has-text("Save Config")')).toBeEnabled({ timeout: 3_000 });
|
|
});
|
|
|
|
// -----------------------------------------------------------------------
|
|
// 4. Save sends PATCH with mod_exhibits_json and shows "Saved" badge
|
|
//
|
|
// Clicking the Save button must PATCH event.mod_exhibits_json via the V3
|
|
// API. The "Saved" badge must appear confirming success.
|
|
// -----------------------------------------------------------------------
|
|
test('save sends PATCH to event and shows Saved badge', async ({ page }) => {
|
|
page.on('pageerror', (err) => console.error(`BROWSER ERROR: ${err.message}`));
|
|
|
|
let patched_body: any = null;
|
|
await setup_config_routes(page, {
|
|
event_data_overrides: {
|
|
mod_exhibits_json: { leads_require_payment: false },
|
|
},
|
|
on_patch: (body) => {
|
|
patched_body = body;
|
|
},
|
|
});
|
|
await seed_ae_loc(page, {
|
|
allow_access: true,
|
|
authenticated_access: true,
|
|
trusted_access: true,
|
|
administrator_access: true,
|
|
});
|
|
|
|
await page.goto(config_url);
|
|
|
|
// Wait for form to initialize
|
|
await page.waitForResponse(
|
|
(r) => r.url().includes(`crud/event/${event_id}`) && r.status() === 200,
|
|
{ timeout: 8_000 }
|
|
);
|
|
await expect(
|
|
page.locator('input[type="checkbox"]').first()
|
|
).toBeVisible({ timeout: 8_000 });
|
|
|
|
// Make a change to unlock save
|
|
await page.locator('input[type="checkbox"]').first().click();
|
|
|
|
// Intercept the PATCH before clicking save
|
|
const patch_promise = page.waitForRequest(
|
|
(r) =>
|
|
r.url().includes(`crud/event/${event_id}`) &&
|
|
r.method() === 'PATCH',
|
|
{ timeout: 5_000 }
|
|
);
|
|
|
|
await page.locator('button:has-text("Save Config")').click();
|
|
|
|
// PATCH must have been made
|
|
await patch_promise;
|
|
|
|
// "Saved" badge must appear
|
|
await expect(page.locator('text=Saved')).toBeVisible({ timeout: 5_000 });
|
|
|
|
// The patched body must include mod_exhibits_json.
|
|
// update_ae_obj auto-serializes *_json fields to strings before the PATCH,
|
|
// so parse it back to an object before asserting field values.
|
|
expect(patched_body?.mod_exhibits_json).toBeDefined();
|
|
const patched_cfg =
|
|
typeof patched_body.mod_exhibits_json === 'string'
|
|
? JSON.parse(patched_body.mod_exhibits_json)
|
|
: patched_body.mod_exhibits_json;
|
|
expect(patched_cfg?.leads_require_payment).toBe(true);
|
|
});
|
|
});
|