Add Prettier for TypeScript formatting
- Install prettier - Configure .prettierrc.json and .prettierignore - Add npm scripts: format, format:check - Add Makefile target: format-frontend - Format all frontend files
This commit is contained in:
parent
4b394b0698
commit
37de6f70e0
44 changed files with 906 additions and 856 deletions
|
|
@ -4,7 +4,7 @@ import { API_URL, REGULAR_USER, ADMIN_USER, clearAuth, loginUser } from "./helpe
|
|||
|
||||
/**
|
||||
* Availability Page E2E Tests
|
||||
*
|
||||
*
|
||||
* Tests for the admin availability management page.
|
||||
*/
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ test.describe("Availability Page - Admin Access", () => {
|
|||
|
||||
test("admin can access availability page", async ({ page }) => {
|
||||
await page.goto("/admin/availability");
|
||||
|
||||
|
||||
await expect(page).toHaveURL("/admin/availability");
|
||||
await expect(page.getByRole("heading", { name: "Availability" })).toBeVisible();
|
||||
await expect(page.getByText("Configure your available time slots")).toBeVisible();
|
||||
|
|
@ -31,29 +31,29 @@ test.describe("Availability Page - Admin Access", () => {
|
|||
|
||||
test("admin sees Availability link in nav", async ({ page }) => {
|
||||
await page.goto("/audit");
|
||||
|
||||
|
||||
const availabilityLink = page.locator('a[href="/admin/availability"]');
|
||||
await expect(availabilityLink).toBeVisible();
|
||||
});
|
||||
|
||||
test("availability page shows calendar grid", async ({ page }) => {
|
||||
await page.goto("/admin/availability");
|
||||
|
||||
|
||||
// Should show tomorrow's date in the calendar
|
||||
const tomorrowText = getTomorrowDisplay();
|
||||
await expect(page.getByText(tomorrowText)).toBeVisible();
|
||||
|
||||
|
||||
// Should show "No availability" for days without slots
|
||||
await expect(page.getByText("No availability").first()).toBeVisible();
|
||||
});
|
||||
|
||||
test("can open edit modal by clicking a day", async ({ page }) => {
|
||||
await page.goto("/admin/availability");
|
||||
|
||||
|
||||
// Click on the first day card
|
||||
const tomorrowText = getTomorrowDisplay();
|
||||
await page.getByText(tomorrowText).click();
|
||||
|
||||
|
||||
// Modal should appear
|
||||
await expect(page.getByRole("heading", { name: /Edit Availability/ })).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Save" })).toBeVisible();
|
||||
|
|
@ -62,133 +62,142 @@ test.describe("Availability Page - Admin Access", () => {
|
|||
|
||||
test("can add availability slot", async ({ page }) => {
|
||||
await page.goto("/admin/availability");
|
||||
|
||||
|
||||
// Wait for initial data load to complete
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
|
||||
// Find a day card with "No availability" and click on it
|
||||
// This ensures we're clicking on a day without existing slots
|
||||
const dayCardWithNoAvailability = page.locator('[data-testid^="day-card-"]').filter({
|
||||
has: page.getByText("No availability")
|
||||
}).first();
|
||||
const dayCardWithNoAvailability = page
|
||||
.locator('[data-testid^="day-card-"]')
|
||||
.filter({
|
||||
has: page.getByText("No availability"),
|
||||
})
|
||||
.first();
|
||||
await dayCardWithNoAvailability.click();
|
||||
|
||||
|
||||
// Wait for modal
|
||||
await expect(page.getByRole("heading", { name: /Edit Availability/ })).toBeVisible();
|
||||
|
||||
|
||||
// Set up listeners for both PUT and GET before clicking Save to avoid race condition
|
||||
const putPromise = page.waitForResponse(resp =>
|
||||
resp.url().includes("/api/admin/availability") && resp.request().method() === "PUT"
|
||||
const putPromise = page.waitForResponse(
|
||||
(resp) => resp.url().includes("/api/admin/availability") && resp.request().method() === "PUT"
|
||||
);
|
||||
const getPromise = page.waitForResponse(resp =>
|
||||
resp.url().includes("/api/admin/availability") && resp.request().method() === "GET"
|
||||
const getPromise = page.waitForResponse(
|
||||
(resp) => resp.url().includes("/api/admin/availability") && resp.request().method() === "GET"
|
||||
);
|
||||
await page.getByRole("button", { name: "Save" }).click();
|
||||
await putPromise;
|
||||
await getPromise;
|
||||
|
||||
|
||||
// Wait for modal to close
|
||||
await expect(page.getByRole("heading", { name: /Edit Availability/ })).not.toBeVisible();
|
||||
|
||||
|
||||
// Should now show the slot (the card we clicked should now have this slot)
|
||||
await expect(page.getByText("09:00 - 17:00")).toBeVisible();
|
||||
});
|
||||
|
||||
test("can clear availability", async ({ page }) => {
|
||||
await page.goto("/admin/availability");
|
||||
|
||||
|
||||
// Wait for initial data load to complete
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
|
||||
// Find a day card with "No availability" and click on it
|
||||
const dayCardWithNoAvailability = page.locator('[data-testid^="day-card-"]').filter({
|
||||
has: page.getByText("No availability")
|
||||
}).first();
|
||||
|
||||
const dayCardWithNoAvailability = page
|
||||
.locator('[data-testid^="day-card-"]')
|
||||
.filter({
|
||||
has: page.getByText("No availability"),
|
||||
})
|
||||
.first();
|
||||
|
||||
// Get the testid so we can find the same card later
|
||||
const testId = await dayCardWithNoAvailability.getAttribute('data-testid');
|
||||
const testId = await dayCardWithNoAvailability.getAttribute("data-testid");
|
||||
const targetCard = page.locator(`[data-testid="${testId}"]`);
|
||||
|
||||
|
||||
// First add availability
|
||||
await dayCardWithNoAvailability.click();
|
||||
await expect(page.getByRole("heading", { name: /Edit Availability/ })).toBeVisible();
|
||||
|
||||
|
||||
// Set up listeners for both PUT and GET before clicking Save to avoid race condition
|
||||
const savePutPromise = page.waitForResponse(resp =>
|
||||
resp.url().includes("/api/admin/availability") && resp.request().method() === "PUT"
|
||||
const savePutPromise = page.waitForResponse(
|
||||
(resp) => resp.url().includes("/api/admin/availability") && resp.request().method() === "PUT"
|
||||
);
|
||||
const saveGetPromise = page.waitForResponse(resp =>
|
||||
resp.url().includes("/api/admin/availability") && resp.request().method() === "GET"
|
||||
const saveGetPromise = page.waitForResponse(
|
||||
(resp) => resp.url().includes("/api/admin/availability") && resp.request().method() === "GET"
|
||||
);
|
||||
await page.getByRole("button", { name: "Save" }).click();
|
||||
await savePutPromise;
|
||||
await saveGetPromise;
|
||||
await expect(page.getByRole("heading", { name: /Edit Availability/ })).not.toBeVisible();
|
||||
|
||||
|
||||
// Verify slot exists in the specific card we clicked
|
||||
await expect(targetCard.getByText("09:00 - 17:00")).toBeVisible();
|
||||
|
||||
|
||||
// Now clear it - click on the same card using the testid
|
||||
await targetCard.click();
|
||||
await expect(page.getByRole("heading", { name: /Edit Availability/ })).toBeVisible();
|
||||
|
||||
|
||||
// Set up listeners for both PUT and GET before clicking Clear to avoid race condition
|
||||
const clearPutPromise = page.waitForResponse(resp =>
|
||||
resp.url().includes("/api/admin/availability") && resp.request().method() === "PUT"
|
||||
const clearPutPromise = page.waitForResponse(
|
||||
(resp) => resp.url().includes("/api/admin/availability") && resp.request().method() === "PUT"
|
||||
);
|
||||
const clearGetPromise = page.waitForResponse(resp =>
|
||||
resp.url().includes("/api/admin/availability") && resp.request().method() === "GET"
|
||||
const clearGetPromise = page.waitForResponse(
|
||||
(resp) => resp.url().includes("/api/admin/availability") && resp.request().method() === "GET"
|
||||
);
|
||||
await page.getByRole("button", { name: "Clear All" }).click();
|
||||
await clearPutPromise;
|
||||
await clearGetPromise;
|
||||
|
||||
|
||||
// Wait for modal to close
|
||||
await expect(page.getByRole("heading", { name: /Edit Availability/ })).not.toBeVisible();
|
||||
|
||||
|
||||
// Slot should be gone from this specific card
|
||||
await expect(targetCard.getByText("09:00 - 17:00")).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("can add multiple slots", async ({ page }) => {
|
||||
await page.goto("/admin/availability");
|
||||
|
||||
|
||||
// Wait for initial data load to complete
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
|
||||
// Find a day card with "No availability" and click on it (to avoid conflicts with booking tests)
|
||||
const dayCardWithNoAvailability = page.locator('[data-testid^="day-card-"]').filter({
|
||||
has: page.getByText("No availability")
|
||||
}).first();
|
||||
const testId = await dayCardWithNoAvailability.getAttribute('data-testid');
|
||||
const dayCardWithNoAvailability = page
|
||||
.locator('[data-testid^="day-card-"]')
|
||||
.filter({
|
||||
has: page.getByText("No availability"),
|
||||
})
|
||||
.first();
|
||||
const testId = await dayCardWithNoAvailability.getAttribute("data-testid");
|
||||
const targetCard = page.locator(`[data-testid="${testId}"]`);
|
||||
await dayCardWithNoAvailability.click();
|
||||
|
||||
|
||||
await expect(page.getByRole("heading", { name: /Edit Availability/ })).toBeVisible();
|
||||
|
||||
|
||||
// First slot is 09:00-17:00 by default - change it to morning only
|
||||
const timeSelects = page.locator("select");
|
||||
await timeSelects.nth(1).selectOption("12:00"); // Change first slot end to 12:00
|
||||
|
||||
|
||||
// Add another slot for afternoon
|
||||
await page.getByText("+ Add Time Range").click();
|
||||
|
||||
|
||||
// Change second slot times to avoid overlap
|
||||
await timeSelects.nth(2).selectOption("14:00"); // Second slot start
|
||||
await timeSelects.nth(3).selectOption("17:00"); // Second slot end
|
||||
|
||||
|
||||
// Set up listeners for both PUT and GET before clicking Save to avoid race condition
|
||||
const putPromise = page.waitForResponse(resp =>
|
||||
resp.url().includes("/api/admin/availability") && resp.request().method() === "PUT"
|
||||
const putPromise = page.waitForResponse(
|
||||
(resp) => resp.url().includes("/api/admin/availability") && resp.request().method() === "PUT"
|
||||
);
|
||||
const getPromise = page.waitForResponse(resp =>
|
||||
resp.url().includes("/api/admin/availability") && resp.request().method() === "GET"
|
||||
const getPromise = page.waitForResponse(
|
||||
(resp) => resp.url().includes("/api/admin/availability") && resp.request().method() === "GET"
|
||||
);
|
||||
await page.getByRole("button", { name: "Save" }).click();
|
||||
await putPromise;
|
||||
await getPromise;
|
||||
await expect(page.getByRole("heading", { name: /Edit Availability/ })).not.toBeVisible();
|
||||
|
||||
|
||||
// Should see both slots in the card we clicked
|
||||
await expect(targetCard.getByText("09:00 - 12:00")).toBeVisible();
|
||||
await expect(targetCard.getByText("14:00 - 17:00")).toBeVisible();
|
||||
|
|
@ -199,9 +208,9 @@ test.describe("Availability Page - Access Control", () => {
|
|||
test("regular user cannot access availability page", async ({ page }) => {
|
||||
await clearAuth(page);
|
||||
await loginUser(page, REGULAR_USER.email, REGULAR_USER.password);
|
||||
|
||||
|
||||
await page.goto("/admin/availability");
|
||||
|
||||
|
||||
// Should be redirected (to counter/home for regular users)
|
||||
await expect(page).not.toHaveURL("/admin/availability");
|
||||
});
|
||||
|
|
@ -209,18 +218,18 @@ test.describe("Availability Page - Access Control", () => {
|
|||
test("regular user does not see Availability link", async ({ page }) => {
|
||||
await clearAuth(page);
|
||||
await loginUser(page, REGULAR_USER.email, REGULAR_USER.password);
|
||||
|
||||
|
||||
await page.goto("/");
|
||||
|
||||
|
||||
const availabilityLink = page.locator('a[href="/admin/availability"]');
|
||||
await expect(availabilityLink).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("unauthenticated user redirected to login", async ({ page }) => {
|
||||
await clearAuth(page);
|
||||
|
||||
|
||||
await page.goto("/admin/availability");
|
||||
|
||||
|
||||
await expect(page).toHaveURL("/login");
|
||||
});
|
||||
});
|
||||
|
|
@ -229,13 +238,13 @@ test.describe("Availability API", () => {
|
|||
test("admin can set availability via API", async ({ page, request }) => {
|
||||
await clearAuth(page);
|
||||
await loginUser(page, ADMIN_USER.email, ADMIN_USER.password);
|
||||
|
||||
|
||||
const cookies = await page.context().cookies();
|
||||
const authCookie = cookies.find(c => c.name === "auth_token");
|
||||
|
||||
const authCookie = cookies.find((c) => c.name === "auth_token");
|
||||
|
||||
if (authCookie) {
|
||||
const dateStr = getTomorrowDateStr();
|
||||
|
||||
|
||||
const response = await request.put(`${API_URL}/api/admin/availability`, {
|
||||
headers: {
|
||||
Cookie: `auth_token=${authCookie.value}`,
|
||||
|
|
@ -246,7 +255,7 @@ test.describe("Availability API", () => {
|
|||
slots: [{ start_time: "10:00:00", end_time: "12:00:00" }],
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
const data = await response.json();
|
||||
expect(data.date).toBe(dateStr);
|
||||
|
|
@ -257,13 +266,13 @@ test.describe("Availability API", () => {
|
|||
test("regular user cannot access availability API", async ({ page, request }) => {
|
||||
await clearAuth(page);
|
||||
await loginUser(page, REGULAR_USER.email, REGULAR_USER.password);
|
||||
|
||||
|
||||
const cookies = await page.context().cookies();
|
||||
const authCookie = cookies.find(c => c.name === "auth_token");
|
||||
|
||||
const authCookie = cookies.find((c) => c.name === "auth_token");
|
||||
|
||||
if (authCookie) {
|
||||
const dateStr = getTomorrowDateStr();
|
||||
|
||||
|
||||
const response = await request.get(
|
||||
`${API_URL}/api/admin/availability?from=${dateStr}&to=${dateStr}`,
|
||||
{
|
||||
|
|
@ -272,9 +281,8 @@ test.describe("Availability API", () => {
|
|||
},
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
expect(response.status()).toBe(403);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue