Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 68 additions & 49 deletions src/ui/pages/events/ManageEvent.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -180,32 +180,26 @@ const EventFormComponent: React.FC<EventFormProps> = ({
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 };
Expand All @@ -221,49 +215,74 @@ const EventFormComponent: React.FC<EventFormProps> = ({
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 (
Expand Down Expand Up @@ -452,7 +471,7 @@ const EventFormComponent: React.FC<EventFormProps> = ({
<TextInput
label="Key"
value={key}
onChange={(e) => updateMetadataKey(key, e.currentTarget.value)}
onChange={(e) => updateMetadataKey(id, e.currentTarget.value)}
error={keyError}
style={{ flex: 1 }}
/>
Expand All @@ -462,7 +481,7 @@ const EventFormComponent: React.FC<EventFormProps> = ({
label="Value"
value={value}
onChange={(e) =>
updateMetadataValue(key, e.currentTarget.value)
updateMetadataValue(id, e.currentTarget.value)
}
error={valueError}
/>
Expand All @@ -472,7 +491,7 @@ const EventFormComponent: React.FC<EventFormProps> = ({
<ActionIcon
color="red"
variant="light"
onClick={() => removeMetadataField(key)}
onClick={() => removeMetadataField(id)}
mt={30}
>
<IconTrash size={16} />
Expand Down
67 changes: 66 additions & 1 deletion tests/e2e/events.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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();
});
});
Loading