114 lines
3.2 KiB
JavaScript
114 lines
3.2 KiB
JavaScript
|
|
import { test, expect } from "@playwright/test";
|
||
|
|
import pg from "pg";
|
||
|
|
|
||
|
|
const DATABASE_URL = process.env.DATABASE_URL;
|
||
|
|
if (!DATABASE_URL) {
|
||
|
|
throw new Error(
|
||
|
|
"DATABASE_URL must be set to run the executive-meetings UI test",
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
const pool = new pg.Pool({ connectionString: DATABASE_URL });
|
||
|
|
const createdMeetingIds = [];
|
||
|
|
const today = new Date().toISOString().slice(0, 10);
|
||
|
|
|
||
|
|
async function loginViaUi(page, username, password) {
|
||
|
|
await page.goto("/login");
|
||
|
|
await page.locator("#username").fill(username);
|
||
|
|
await page.locator("#password").fill(password);
|
||
|
|
await Promise.all([
|
||
|
|
page.waitForURL((url) => !url.pathname.endsWith("/login"), {
|
||
|
|
timeout: 15_000,
|
||
|
|
}),
|
||
|
|
page.locator('form button[type="submit"]').click(),
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
test.afterAll(async () => {
|
||
|
|
if (createdMeetingIds.length > 0) {
|
||
|
|
await pool.query(
|
||
|
|
`DELETE FROM executive_meeting_attendees WHERE meeting_id = ANY($1::int[])`,
|
||
|
|
[createdMeetingIds],
|
||
|
|
);
|
||
|
|
await pool.query(
|
||
|
|
`DELETE FROM executive_meeting_audit_logs WHERE entity_type = 'meeting' AND entity_id = ANY($1::int[])`,
|
||
|
|
[createdMeetingIds],
|
||
|
|
);
|
||
|
|
await pool.query(
|
||
|
|
`DELETE FROM executive_meetings WHERE id = ANY($1::int[])`,
|
||
|
|
[createdMeetingIds],
|
||
|
|
);
|
||
|
|
}
|
||
|
|
await pool.end();
|
||
|
|
});
|
||
|
|
|
||
|
|
test("Manage tab: admin can create a meeting from the UI and see it in the list", async ({
|
||
|
|
page,
|
||
|
|
}) => {
|
||
|
|
await page.addInitScript(() => {
|
||
|
|
try {
|
||
|
|
window.localStorage.setItem("tx-lang", "en");
|
||
|
|
} catch {
|
||
|
|
/* localStorage may not be ready before navigation */
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
await loginViaUi(page, "admin", "admin123");
|
||
|
|
await page.goto("/executive-meetings");
|
||
|
|
|
||
|
|
await page.getByTestId("em-nav-manage").click();
|
||
|
|
|
||
|
|
const addBtn = page.getByTestId("em-add-meeting");
|
||
|
|
await expect(addBtn).toBeVisible();
|
||
|
|
|
||
|
|
const createPromise = page.waitForResponse(
|
||
|
|
(resp) => {
|
||
|
|
const u = new URL(resp.url());
|
||
|
|
return (
|
||
|
|
u.pathname === "/api/executive-meetings" &&
|
||
|
|
resp.request().method() === "POST"
|
||
|
|
);
|
||
|
|
},
|
||
|
|
{ timeout: 15_000 },
|
||
|
|
);
|
||
|
|
|
||
|
|
await addBtn.click();
|
||
|
|
|
||
|
|
const uniq = `${Date.now().toString(36)}_${Math.random()
|
||
|
|
.toString(36)
|
||
|
|
.slice(2, 6)}`;
|
||
|
|
const titleAr = `اجتماع واجهة ${uniq}`;
|
||
|
|
const titleEn = `UI Meeting ${uniq}`;
|
||
|
|
|
||
|
|
await page.getByTestId("em-form-titleAr").fill(titleAr);
|
||
|
|
const dialog = page.getByRole("dialog");
|
||
|
|
const titleEnInput = dialog
|
||
|
|
.locator("input")
|
||
|
|
.nth(1); // titleAr=0, titleEn=1, meetingDate=2 (date), dailyNumber=3
|
||
|
|
await titleEnInput.fill(titleEn);
|
||
|
|
|
||
|
|
await dialog.locator('input[type="date"]').first().fill(today);
|
||
|
|
|
||
|
|
await page.getByTestId("em-form-save").click();
|
||
|
|
|
||
|
|
const resp = await createPromise;
|
||
|
|
expect(resp.status()).toBe(201);
|
||
|
|
const created = await resp.json();
|
||
|
|
expect(created.id).toBeTruthy();
|
||
|
|
createdMeetingIds.push(created.id);
|
||
|
|
|
||
|
|
await expect(dialog).toBeHidden({ timeout: 10_000 });
|
||
|
|
|
||
|
|
const { rows } = await pool.query(
|
||
|
|
`SELECT id, title_en, meeting_date FROM executive_meetings WHERE id = $1`,
|
||
|
|
[created.id],
|
||
|
|
);
|
||
|
|
expect(rows.length).toBe(1);
|
||
|
|
expect(rows[0].title_en).toBe(titleEn);
|
||
|
|
const isoDate =
|
||
|
|
rows[0].meeting_date instanceof Date
|
||
|
|
? rows[0].meeting_date.toISOString().slice(0, 10)
|
||
|
|
: String(rows[0].meeting_date).slice(0, 10);
|
||
|
|
expect(isoDate).toBe(today);
|
||
|
|
});
|