diff --git a/src/ui/pages/events/ManageEvent.page.tsx b/src/ui/pages/events/ManageEvent.page.tsx index 6686e5e9..5e186765 100644 --- a/src/ui/pages/events/ManageEvent.page.tsx +++ b/src/ui/pages/events/ManageEvent.page.tsx @@ -180,32 +180,26 @@ const EventFormComponent: React.FC = ({ Array<{ id: string; key: string; value: string }> >([]); - // Sync metadata entries with form values + // Track if we've initialized from form data to avoid re-syncing on user edits + const initializedRef = React.useRef(false); + + // Sync metadata entries with form values only when loading/initializing useEffect(() => { const currentMetadata = form.values.metadata || {}; const currentKeys = Object.keys(currentMetadata); - // Only update if keys have changed - const entriesKeys = metadataEntries.map((e) => e.key); - const keysChanged = - currentKeys.length !== entriesKeys.length || - !currentKeys.every((k) => entriesKeys.includes(k)); - - if (keysChanged) { + // Only sync if we haven't initialized yet and there's metadata to load + if (!initializedRef.current && currentKeys.length > 0) { setMetadataEntries( - currentKeys.map((key) => { - const existing = metadataEntries.find((e) => e.key === key); - return { - id: - existing?.id || - `meta-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, - key, - value: currentMetadata[key], - }; - }), + currentKeys.map((key) => ({ + id: `meta-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + key, + value: currentMetadata[key], + })), ); + initializedRef.current = true; } - }, [Object.keys(form.values.metadata || {}).join(",")]); + }, [form.values.metadata]); const addMetadataField = () => { const currentMetadata = { ...form.values.metadata }; @@ -221,49 +215,74 @@ const EventFormComponent: React.FC = ({ tempKey = `key${parseInt(tempKey.replace("key", ""), 10) + 1}`; } - form.setValues({ - ...form.values, - metadata: { - ...currentMetadata, - [tempKey]: "", - }, + const newId = `meta-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; + + // Update both form metadata and entries together + form.setFieldValue("metadata", { + ...currentMetadata, + [tempKey]: "", }); + + setMetadataEntries([ + ...metadataEntries, + { id: newId, key: tempKey, value: "" }, + ]); }; - const updateMetadataValue = (oldKey: string, value: string) => { - form.setValues({ - ...form.values, - metadata: { - ...form.values.metadata, - [oldKey]: value, - }, + const updateMetadataValue = (entryId: string, value: string) => { + const entry = metadataEntries.find((e) => e.id === entryId); + if (!entry) { + return; + } + + // Update form metadata + form.setFieldValue("metadata", { + ...form.values.metadata, + [entry.key]: value, }); + + // Update entry value + setMetadataEntries( + metadataEntries.map((e) => (e.id === entryId ? { ...e, value } : e)), + ); }; - const updateMetadataKey = (oldKey: string, newKey: string) => { - if (oldKey === newKey) { + const updateMetadataKey = (entryId: string, newKey: string) => { + const entry = metadataEntries.find((e) => e.id === entryId); + if (!entry || entry.key === newKey) { return; } + // Update form metadata const metadata = { ...form.values.metadata }; - const value = metadata[oldKey]; - delete metadata[oldKey]; + const value = metadata[entry.key]; + delete metadata[entry.key]; metadata[newKey] = value; - form.setValues({ - ...form.values, - metadata, - }); + form.setFieldValue("metadata", metadata); + + // Update entry key + setMetadataEntries( + metadataEntries.map((e) => + e.id === entryId ? { ...e, key: newKey } : e, + ), + ); }; - const removeMetadataField = (key: string) => { + const removeMetadataField = (entryId: string) => { + const entry = metadataEntries.find((e) => e.id === entryId); + if (!entry) { + return; + } + + // Update form metadata const currentMetadata = { ...form.values.metadata }; - delete currentMetadata[key]; + delete currentMetadata[entry.key]; - form.setValues({ - ...form.values, - metadata: currentMetadata, - }); + form.setFieldValue("metadata", currentMetadata); + + // Remove entry + setMetadataEntries(metadataEntries.filter((e) => e.id !== entryId)); }; return ( @@ -452,7 +471,7 @@ const EventFormComponent: React.FC = ({ updateMetadataKey(key, e.currentTarget.value)} + onChange={(e) => updateMetadataKey(id, e.currentTarget.value)} error={keyError} style={{ flex: 1 }} /> @@ -462,7 +481,7 @@ const EventFormComponent: React.FC = ({ label="Value" value={value} onChange={(e) => - updateMetadataValue(key, e.currentTarget.value) + updateMetadataValue(id, e.currentTarget.value) } error={valueError} /> @@ -472,7 +491,7 @@ const EventFormComponent: React.FC = ({ removeMetadataField(key)} + onClick={() => removeMetadataField(id)} mt={30} > diff --git a/tests/e2e/events.spec.ts b/tests/e2e/events.spec.ts index a230de61..58d9dd56 100644 --- a/tests/e2e/events.spec.ts +++ b/tests/e2e/events.spec.ts @@ -2,7 +2,7 @@ import { expect } from "@playwright/test"; import { capitalizeFirstLetter, getUpcomingEvents, test } from "./base.js"; import { describe } from "node:test"; -describe("Events tests", () => { +describe("Events page load test", () => { test("A user can login and view the upcoming events", async ({ page, becomeUser, @@ -50,3 +50,68 @@ describe("Events tests", () => { expect(page.url()).toEqual("https://core.aws.qa.acmuiuc.org/events/manage"); }); }); + +test.describe.serial("Event lifecycle test", () => { + const testId = `Events-E2E-${Date.now()}`; + test("A user can create an event", async ({ page, becomeUser }) => { + await becomeUser(page); + await page.locator("a").filter({ hasText: "Events" }).click(); + await page.getByRole("button", { name: "Create Event" }).click(); + await page.getByRole("tab", { name: "From Scratch" }).click(); + await page.getByRole("textbox", { name: "Event Title" }).click(); + await page.getByRole("textbox", { name: "Event Title" }).fill(testId); + await page.getByRole("textbox", { name: "Event Description" }).click(); + await page + .getByRole("textbox", { name: "Event Description" }) + .fill("E2E Testing Event"); + await page.getByRole("button", { name: "Start Date & Time" }).click(); + await page.getByRole("spinbutton", { name: "--" }).nth(0).click(); + await page.getByRole("spinbutton", { name: "--" }).nth(0).fill("023"); + await page.getByRole("spinbutton", { name: "--" }).nth(1).click(); + await page.getByRole("spinbutton", { name: "--" }).nth(1).fill("030"); + await page.getByRole("button").filter({ hasText: /^$/ }).nth(2).click(); + await page.getByRole("button", { name: "End Date & Time" }).click(); + await page.getByRole("spinbutton", { name: "--" }).first().click(); + await page.getByRole("spinbutton", { name: "--" }).first().fill("023"); + await page.getByRole("spinbutton", { name: "--" }).nth(1).fill("059"); + await page.getByRole("button").filter({ hasText: /^$/ }).nth(2).click(); + await page.getByRole("textbox", { name: "Event Location" }).dblclick(); + await page + .getByRole("textbox", { name: "Event Location" }) + .fill("ACM Room"); + await page.getByRole("textbox", { name: "Host" }).click(); + await page.getByRole("textbox", { name: "Host" }).fill("Infrastructure"); + await page.getByText("Infrastructure Committee").click(); + await page.getByRole("textbox", { name: "Paid Event ID" }).click(); + await page.getByRole("textbox", { name: "Paid Event ID" }).fill("abcd123"); + await page.getByRole("button", { name: "Add Field" }).click(); + await page.getByRole("textbox", { name: "Key" }).click(); + await page.getByRole("textbox", { name: "Key" }).fill("form1"); + await page.getByRole("textbox", { name: "Value" }).click(); + await page.getByRole("textbox", { name: "Value" }).fill("value1"); + await page.getByRole("button", { name: "Add Field" }).click(); + await page.getByRole("textbox", { name: "Key" }).nth(1).fill("form2"); + await page.getByRole("textbox", { name: "Value" }).nth(1).click(); + await page.getByRole("textbox", { name: "Value" }).nth(1).fill("value2"); + await page.getByRole("button", { name: "Create Event" }).click(); + }); + test("A user can delete an event", async ({ page, becomeUser }) => { + await becomeUser(page); + await page.locator("a").filter({ hasText: "Events" }).click(); + const table = page.getByTestId("events-table"); + await expect(table).toBeVisible(); + const row = table.locator(`tbody tr:has-text("${testId}")`); + await expect(row).toBeVisible(); + await row.getByRole("button", { name: "Delete" }).click(); + await expect( + page.locator(':text("Are you sure you want to delete the event")'), + ).toBeVisible(); + await page + .getByRole("dialog") + .getByRole("button", { name: "Delete" }) + .click(); + await expect( + page.locator(':text("The event was successfully deleted.")'), + ).toBeVisible(); + }); +});