mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-07 04:12:04 +00:00
test(e2e): log a mood at a chosen time against the real server
The mood timestamp spec answered the POST and the list read itself. It now runs on the mood journey's account: the instant typed into the form is the instant stored, and the entry is in the list, also after a reload.
This commit is contained in:
1 parent
7445b69842
commit
f185855d76
2 files changed
+67
-33
No files matched your search
+66
-32
@@ -1,39 +1,53 @@
|
||||
import type { Page } from "@playwright/test";
|
||||
|
||||
import { expect, test } from "./setup/test";
|
||||
|
||||
import { STORAGE_STATE_PATH } from "./setup/global-setup";
|
||||
import {
|
||||
criticalAccount,
|
||||
criticalStorageState,
|
||||
resetCriticalAccount,
|
||||
} from "./setup/critical-fixture";
|
||||
|
||||
/**
|
||||
* Logging a mood through the form, against the real server: the time stays
|
||||
* visible while the details are collapsed, the instant typed into it is the
|
||||
* instant stored, and the entry is in the list afterwards.
|
||||
*
|
||||
* Until v1.46 this spec answered the POST and the list read itself, so the
|
||||
* list was always empty and the POST always succeeded. The account is now the
|
||||
* mood journey's own, one per project (`critical-fixture.ts`), emptied before
|
||||
* the test; the empty state the form opens from is the account's real one.
|
||||
*/
|
||||
|
||||
/** The account's mood entries, as the list route answers them. */
|
||||
async function storedMoods(
|
||||
page: Page,
|
||||
): Promise<Array<{ mood: string; moodLoggedAt: string }>> {
|
||||
const res = await page.request.get("/api/mood-entries");
|
||||
expect(res.status()).toBe(200);
|
||||
const { data } = (await res.json()) as {
|
||||
data: { entries: Array<{ mood: string; moodLoggedAt: string }> };
|
||||
};
|
||||
return data.entries.map(({ mood, moodLoggedAt }) => ({ mood, moodLoggedAt }));
|
||||
}
|
||||
|
||||
test.describe("mood timestamp capture", () => {
|
||||
test.use({ storageState: STORAGE_STATE_PATH });
|
||||
test.use({ storageState: criticalStorageState("mood") });
|
||||
|
||||
test("keeps Time visible while details are collapsed and submits the selected instant", async ({
|
||||
test.beforeEach(async ({}, testInfo) => {
|
||||
// Fixture hygiene: what a previous run or a retry left on the account.
|
||||
await resetCriticalAccount(criticalAccount("mood", testInfo), [
|
||||
"DELETE FROM mood_entry_rollups WHERE user_id = $1",
|
||||
"DELETE FROM mood_entries WHERE user_id = $1",
|
||||
]);
|
||||
});
|
||||
|
||||
test("a mood logged at a chosen time is stored at that instant and listed @critical", async ({
|
||||
page,
|
||||
}) => {
|
||||
let submitted: Record<string, unknown> | null = null;
|
||||
|
||||
await page.route("**/api/mood-entries*", async (route) => {
|
||||
if (route.request().method() === "POST") {
|
||||
submitted = route.request().postDataJSON() as Record<string, unknown>;
|
||||
await route.fulfill({
|
||||
status: 201,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({ data: { id: "created-mood" }, error: null }),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
data: { entries: [], meta: { total: 0, limit: 50, offset: 0 } },
|
||||
error: null,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto("/mood", { waitUntil: "domcontentloaded" });
|
||||
// The list is mocked empty, so its empty state carries the add action
|
||||
// (the header drops its own while there is nothing to list).
|
||||
// An empty list carries the add action in its empty state (the header
|
||||
// drops its own while there is nothing to list).
|
||||
await page.getByRole("button", { name: "Log your first mood" }).click();
|
||||
await page.getByRole("radio", { name: "Amazing" }).click();
|
||||
|
||||
@@ -53,12 +67,32 @@ test.describe("mood timestamp capture", () => {
|
||||
await timeInput.fill("09:45");
|
||||
await timeInput.blur();
|
||||
|
||||
const created = page.waitForResponse(
|
||||
(res) =>
|
||||
new URL(res.url()).pathname === "/api/mood-entries" &&
|
||||
res.request().method() === "POST",
|
||||
);
|
||||
await page.getByRole("button", { name: "Save", exact: true }).click();
|
||||
expect((await created).status()).toBe(201);
|
||||
|
||||
await expect.poll(() => submitted).not.toBeNull();
|
||||
expect(submitted).toMatchObject({
|
||||
mood: "SUPER_GUT",
|
||||
moodLoggedAt: "2026-01-15T08:45:00.000Z",
|
||||
// 09:45 in Berlin in January is 08:45 UTC.
|
||||
expect(await storedMoods(page)).toEqual([
|
||||
{ mood: "SUPER_GUT", moodLoggedAt: "2026-01-15T08:45:00.000Z" },
|
||||
]);
|
||||
|
||||
// The list shows it, at either width (the table and the phone cards each
|
||||
// carry the marker; one of them is on screen).
|
||||
const rows = page.locator('[data-slot="mood-rows"]').filter({
|
||||
visible: true,
|
||||
});
|
||||
await expect(rows).toBeVisible();
|
||||
await expect(rows).toContainText("Amazing");
|
||||
await expect(rows).toContainText("09:45");
|
||||
|
||||
// And still after a reload, which reads the list from the server again
|
||||
// rather than from the cache the mutation updated.
|
||||
await page.reload({ waitUntil: "domcontentloaded" });
|
||||
await expect(rows).toContainText("Amazing");
|
||||
await expect(rows).toContainText("09:45");
|
||||
});
|
||||
});
|
||||
@@ -49,7 +49,7 @@ const E2E = join(process.cwd(), "e2e");
|
||||
* the set is the release gate, so a journey that loses its tag is a change
|
||||
* somebody should see here.
|
||||
*/
|
||||
const CRITICAL_TEST_FLOOR = 11;
|
||||
const CRITICAL_TEST_FLOOR = 12;
|
||||
|
||||
/** A test whose title literal carries the tag, however the call is spelled. */
|
||||
const CRITICAL_TEST =
|
||||
|
||||
Reference in new issue
Block a user