|
|
|
@@ -91,6 +91,12 @@ function LocationProbe() {
|
|
|
|
</>;
|
|
|
|
</>;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function settleDeferred() {
|
|
|
|
|
|
|
|
for (let attempt = 0; attempt < 100 && [...container.querySelectorAll('[role="status"]')].some(node => node.textContent === "Loading…"); attempt++) {
|
|
|
|
|
|
|
|
await act(async () => { await new Promise(resolve => setTimeout(resolve, 1)); });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function render(path = "/design-system") {
|
|
|
|
async function render(path = "/design-system") {
|
|
|
|
await act(async () =>
|
|
|
|
await act(async () =>
|
|
|
|
root.render(
|
|
|
|
root.render(
|
|
|
|
@@ -100,6 +106,7 @@ async function render(path = "/design-system") {
|
|
|
|
</MemoryRouter>,
|
|
|
|
</MemoryRouter>,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function connectAccount() {
|
|
|
|
function connectAccount() {
|
|
|
|
@@ -131,6 +138,7 @@ describe("homepage", () => {
|
|
|
|
const opener = buttonNamed("Settings");
|
|
|
|
const opener = buttonNamed("Settings");
|
|
|
|
opener.focus();
|
|
|
|
opener.focus();
|
|
|
|
await act(async () => opener.click());
|
|
|
|
await act(async () => opener.click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
const dialog = container.querySelector<HTMLDialogElement>("dialog#account-settings")!;
|
|
|
|
const dialog = container.querySelector<HTMLDialogElement>("dialog#account-settings")!;
|
|
|
|
expect(dialog.open).toBe(true);
|
|
|
|
expect(dialog.open).toBe(true);
|
|
|
|
expect(dialog.contains(document.activeElement)).toBe(true);
|
|
|
|
expect(dialog.contains(document.activeElement)).toBe(true);
|
|
|
|
@@ -158,6 +166,7 @@ describe("homepage", () => {
|
|
|
|
expect(inner.open).toBe(true);
|
|
|
|
expect(inner.open).toBe(true);
|
|
|
|
expect(inner.querySelector<HTMLButtonElement>('[aria-label="Close dialog"]')?.disabled).toBe(true);
|
|
|
|
expect(inner.querySelector<HTMLButtonElement>('[aria-label="Close dialog"]')?.disabled).toBe(true);
|
|
|
|
await act(async () => buttonNamed("Finish save").click());
|
|
|
|
await act(async () => buttonNamed("Finish save").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
await act(async () => inner.dispatchEvent(new dom.Event("cancel", { cancelable: true }) as unknown as Event));
|
|
|
|
await act(async () => inner.dispatchEvent(new dom.Event("cancel", { cancelable: true }) as unknown as Event));
|
|
|
|
expect(container.querySelector("#inner")).toBeNull();
|
|
|
|
expect(container.querySelector("#inner")).toBeNull();
|
|
|
|
expect(container.querySelector<HTMLDialogElement>("#outer")?.open).toBe(true);
|
|
|
|
expect(container.querySelector<HTMLDialogElement>("#outer")?.open).toBe(true);
|
|
|
|
@@ -175,10 +184,13 @@ describe("homepage", () => {
|
|
|
|
await f.request(`/habits/${h.id}`, "DELETE");
|
|
|
|
await f.request(`/habits/${h.id}`, "DELETE");
|
|
|
|
await render("/");
|
|
|
|
await render("/");
|
|
|
|
await act(async () => buttonNamed("Archived habits").click());
|
|
|
|
await act(async () => buttonNamed("Archived habits").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
expect(buttonNamed("Restore Reading")).toBeDefined();
|
|
|
|
expect(buttonNamed("Restore Reading")).toBeDefined();
|
|
|
|
await act(async () => buttonNamed("View history").click());
|
|
|
|
await act(async () => buttonNamed("View history").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
expect(container.querySelector('[aria-label="2026-09-03: 1 of 1 completion · Complete"]')).not.toBeNull();
|
|
|
|
expect(container.querySelector('[aria-label="2026-09-03: 1 of 1 completion · Complete"]')).not.toBeNull();
|
|
|
|
await act(async () => buttonNamed("Restore Reading").click());
|
|
|
|
await act(async () => buttonNamed("Restore Reading").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
expect(container.textContent).toContain("No archived habits.");
|
|
|
|
expect(container.textContent).toContain("No archived habits.");
|
|
|
|
expect((await f.json("/today")).habits[0].name).toBe("Reading");
|
|
|
|
expect((await f.json("/today")).habits[0].name).toBe("Reading");
|
|
|
|
expect((await f.json(`/habits/${h.id}/days/2026-09-03`)).complete).toBe(true);
|
|
|
|
expect((await f.json(`/habits/${h.id}/days/2026-09-03`)).complete).toBe(true);
|
|
|
|
@@ -190,12 +202,16 @@ describe("homepage", () => {
|
|
|
|
await f.request(`/habits/${habit.id}`, "DELETE");
|
|
|
|
await f.request(`/habits/${habit.id}`, "DELETE");
|
|
|
|
await render("/");
|
|
|
|
await render("/");
|
|
|
|
await act(async () => buttonNamed("Archived habits").click());
|
|
|
|
await act(async () => buttonNamed("Archived habits").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
await act(async () => buttonNamed("Delete permanently Discard me").click());
|
|
|
|
await act(async () => buttonNamed("Delete permanently Discard me").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
expect(container.querySelector("#delete-archived-habit")?.textContent).toContain("This cannot be undone.");
|
|
|
|
expect(container.querySelector("#delete-archived-habit")?.textContent).toContain("This cannot be undone.");
|
|
|
|
expect(document.activeElement).toBe(buttonNamed("Keep habit"));
|
|
|
|
expect(document.activeElement).toBe(buttonNamed("Keep habit"));
|
|
|
|
await act(async () => buttonNamed("Keep habit").click());
|
|
|
|
await act(async () => buttonNamed("Keep habit").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
expect((await f.request(`/habits/${habit.id}`)).status).toBe(200);
|
|
|
|
expect((await f.request(`/habits/${habit.id}`)).status).toBe(200);
|
|
|
|
await act(async () => buttonNamed("Delete permanently Discard me").click());
|
|
|
|
await act(async () => buttonNamed("Delete permanently Discard me").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
const confirm = () => container.querySelector<HTMLButtonElement>("#delete-archived-habit .ds-action-row button:last-child")!;
|
|
|
|
const confirm = () => container.querySelector<HTMLButtonElement>("#delete-archived-habit .ds-action-row button:last-child")!;
|
|
|
|
fetchMock.mockResolvedValueOnce(Response.json({ error: "Temporary failure" }, { status: 500 }));
|
|
|
|
fetchMock.mockResolvedValueOnce(Response.json({ error: "Temporary failure" }, { status: 500 }));
|
|
|
|
await act(async () => confirm().click());
|
|
|
|
await act(async () => confirm().click());
|
|
|
|
@@ -220,6 +236,7 @@ describe("homepage", () => {
|
|
|
|
await act(async () => container.querySelector<HTMLButtonElement>('[aria-label^="2026-09-03:"]')!.click());
|
|
|
|
await act(async () => container.querySelector<HTMLButtonElement>('[aria-label^="2026-09-03:"]')!.click());
|
|
|
|
expect(buttonNamed("Edit selected check-in").closest(".ds-date-inspector")?.textContent).toContain("September 3, 2026");
|
|
|
|
expect(buttonNamed("Edit selected check-in").closest(".ds-date-inspector")?.textContent).toContain("September 3, 2026");
|
|
|
|
await act(async () => buttonNamed("Edit selected check-in").click());
|
|
|
|
await act(async () => buttonNamed("Edit selected check-in").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
const past = [...container.querySelectorAll<HTMLInputElement>('input[type="checkbox"]')].find(input => input.closest("label")?.textContent === "Completed on this date")!;
|
|
|
|
const past = [...container.querySelectorAll<HTMLInputElement>('input[type="checkbox"]')].find(input => input.closest("label")?.textContent === "Completed on this date")!;
|
|
|
|
expect(past).toBeDefined();
|
|
|
|
expect(past).toBeDefined();
|
|
|
|
await act(async () => past.click());
|
|
|
|
await act(async () => past.click());
|
|
|
|
@@ -233,6 +250,7 @@ describe("homepage", () => {
|
|
|
|
expect(container.querySelector("h1")?.textContent).toBe("Small steps.Lasting rhythm.");
|
|
|
|
expect(container.querySelector("h1")?.textContent).toBe("Small steps.Lasting rhythm.");
|
|
|
|
expect(fetchMock.mock.calls.map(call => call[0])).toEqual(["/api/me"]);
|
|
|
|
expect(fetchMock.mock.calls.map(call => call[0])).toEqual(["/api/me"]);
|
|
|
|
await act(async () => buttonNamed("Increase glasses of water").click());
|
|
|
|
await act(async () => buttonNamed("Increase glasses of water").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
expect(container.querySelector<HTMLInputElement>('[aria-label="Total glasses of water"]')?.value).toBe("4");
|
|
|
|
expect(container.querySelector<HTMLInputElement>('[aria-label="Total glasses of water"]')?.value).toBe("4");
|
|
|
|
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
|
|
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
@@ -264,6 +282,7 @@ describe("homepage", () => {
|
|
|
|
expect(container.textContent).toContain("Account unavailable");
|
|
|
|
expect(container.textContent).toContain("Account unavailable");
|
|
|
|
expect(container.querySelector("#welcome-title")).toBeNull();
|
|
|
|
expect(container.querySelector("#welcome-title")).toBeNull();
|
|
|
|
await act(async () => buttonNamed("Try again").click());
|
|
|
|
await act(async () => buttonNamed("Try again").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
expect(container.querySelector("#welcome-title")).not.toBeNull();
|
|
|
|
expect(container.querySelector("#welcome-title")).not.toBeNull();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
@@ -273,6 +292,7 @@ describe("homepage", () => {
|
|
|
|
expect(container.textContent).toContain("Welcome back, alice.");
|
|
|
|
expect(container.textContent).toContain("Welcome back, alice.");
|
|
|
|
expect(container.textContent).toContain("Start with one habit.");
|
|
|
|
expect(container.textContent).toContain("Start with one habit.");
|
|
|
|
await act(async () => buttonNamed("Add a habit +").click());
|
|
|
|
await act(async () => buttonNamed("Add a habit +").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
const input = container.querySelector<HTMLInputElement>("form input")!;
|
|
|
|
const input = container.querySelector<HTMLInputElement>("form input")!;
|
|
|
|
expect(document.activeElement).toBe(input);
|
|
|
|
expect(document.activeElement).toBe(input);
|
|
|
|
await act(async () => {
|
|
|
|
await act(async () => {
|
|
|
|
@@ -285,6 +305,7 @@ describe("homepage", () => {
|
|
|
|
expect(container.textContent).not.toContain("Read a little created.");
|
|
|
|
expect(container.textContent).not.toContain("Read a little created.");
|
|
|
|
expect(container.querySelector(".ds-habit-chart")).not.toBeNull();
|
|
|
|
expect(container.querySelector(".ds-habit-chart")).not.toBeNull();
|
|
|
|
await act(async () => buttonNamed("Sign out").click());
|
|
|
|
await act(async () => buttonNamed("Sign out").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
expect(container.querySelector("#welcome-title")).not.toBeNull();
|
|
|
|
expect(container.querySelector("#welcome-title")).not.toBeNull();
|
|
|
|
expect((await f.request("/me")).status).toBe(401);
|
|
|
|
expect((await f.request("/me")).status).toBe(401);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
@@ -310,12 +331,14 @@ describe("homepage", () => {
|
|
|
|
expect(container.querySelectorAll('.ds-goal--tasks .ds-routine-items > li')).toHaveLength(1);
|
|
|
|
expect(container.querySelectorAll('.ds-goal--tasks .ds-routine-items > li')).toHaveLength(1);
|
|
|
|
|
|
|
|
|
|
|
|
await act(async () => buttonNamed("Increase Water").click());
|
|
|
|
await act(async () => buttonNamed("Increase Water").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
expect(container.querySelector(".ds-welcome-progress")?.textContent).toContain("1 / 3");
|
|
|
|
expect(container.querySelector(".ds-welcome-progress")?.textContent).toContain("1 / 3");
|
|
|
|
expect(container.querySelectorAll(".ds-goal-graph .ds-calendar")).toHaveLength(3);
|
|
|
|
expect(container.querySelectorAll(".ds-goal-graph .ds-calendar")).toHaveLength(3);
|
|
|
|
expect(countGoal.querySelector(".ds-goal-status")?.textContent).toBe("Complete");
|
|
|
|
expect(countGoal.querySelector(".ds-goal-status")?.textContent).toBe("Complete");
|
|
|
|
expect(container.querySelector(`#history-${water.id} .ds-date-inspector`)?.textContent).toContain("8 of 8 glasses");
|
|
|
|
expect(container.querySelector(`#history-${water.id} .ds-date-inspector`)?.textContent).toContain("8 of 8 glasses");
|
|
|
|
expect(container.textContent).not.toContain("Sunday walk");
|
|
|
|
expect(container.textContent).not.toContain("Sunday walk");
|
|
|
|
await act(async () => buttonNamed("All habits · 4").click());
|
|
|
|
await act(async () => buttonNamed("All habits · 4").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
const checkbox = (name: string) => [...container.querySelectorAll<HTMLInputElement>('input[type="checkbox"]')].find(input => (input.getAttribute("aria-label") || input.closest("label")?.textContent) === name)!;
|
|
|
|
const checkbox = (name: string) => [...container.querySelectorAll<HTMLInputElement>('input[type="checkbox"]')].find(input => (input.getAttribute("aria-label") || input.closest("label")?.textContent) === name)!;
|
|
|
|
expect(checkbox("Sunday walk done").disabled).toBe(true);
|
|
|
|
expect(checkbox("Sunday walk done").disabled).toBe(true);
|
|
|
|
await act(async () => checkbox("Reading done").click());
|
|
|
|
await act(async () => checkbox("Reading done").click());
|
|
|
|
@@ -323,6 +346,7 @@ describe("homepage", () => {
|
|
|
|
expect(container.querySelector(".ds-welcome-progress")?.textContent).toContain("3 / 3");
|
|
|
|
expect(container.querySelector(".ds-welcome-progress")?.textContent).toContain("3 / 3");
|
|
|
|
expect((await f.json("/today")).completed).toBe(3);
|
|
|
|
expect((await f.json("/today")).completed).toBe(3);
|
|
|
|
await act(async () => buttonNamed("Decrease Water").click());
|
|
|
|
await act(async () => buttonNamed("Decrease Water").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
expect((await f.json("/today")).completed).toBe(2);
|
|
|
|
expect((await f.json("/today")).completed).toBe(2);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
@@ -339,6 +363,7 @@ describe("homepage", () => {
|
|
|
|
expect(calendarRequests()[0]).toContain("compact=true");
|
|
|
|
expect(calendarRequests()[0]).toContain("compact=true");
|
|
|
|
const parse = spyOn(Date, "parse");
|
|
|
|
const parse = spyOn(Date, "parse");
|
|
|
|
await act(async () => buttonNamed("All habits · 2").click());
|
|
|
|
await act(async () => buttonNamed("All habits · 2").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
expect(parse).not.toHaveBeenCalled();
|
|
|
|
expect(parse).not.toHaveBeenCalled();
|
|
|
|
parse.mockRestore();
|
|
|
|
parse.mockRestore();
|
|
|
|
fetchMock.mockClear();
|
|
|
|
fetchMock.mockClear();
|
|
|
|
@@ -380,6 +405,7 @@ describe("homepage", () => {
|
|
|
|
}, { preconnect: fetch.preconnect }));
|
|
|
|
}, { preconnect: fetch.preconnect }));
|
|
|
|
fetchMock.mockClear();
|
|
|
|
fetchMock.mockClear();
|
|
|
|
await act(async () => buttonNamed("Increase Water").click());
|
|
|
|
await act(async () => buttonNamed("Increase Water").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
expect(buttonNamed("Increase Water").disabled).toBe(true);
|
|
|
|
expect(buttonNamed("Increase Water").disabled).toBe(true);
|
|
|
|
const check = container.querySelector<HTMLInputElement>('[aria-label="Reading done"]')!;
|
|
|
|
const check = container.querySelector<HTMLInputElement>('[aria-label="Reading done"]')!;
|
|
|
|
expect(check.disabled).toBe(false);
|
|
|
|
expect(check.disabled).toBe(false);
|
|
|
|
@@ -406,6 +432,7 @@ describe("homepage", () => {
|
|
|
|
f.setTime("2026-09-04T12:00:00Z");
|
|
|
|
f.setTime("2026-09-04T12:00:00Z");
|
|
|
|
await render("/");
|
|
|
|
await render("/");
|
|
|
|
await act(async () => buttonNamed("Edit habit Water").click());
|
|
|
|
await act(async () => buttonNamed("Edit habit Water").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
const form = container.querySelector<HTMLFormElement>('form[aria-label="Edit habit Water"]')!;
|
|
|
|
const form = container.querySelector<HTMLFormElement>('form[aria-label="Edit habit Water"]')!;
|
|
|
|
expect(document.activeElement).toBe(form.querySelector("input"));
|
|
|
|
expect(document.activeElement).toBe(form.querySelector("input"));
|
|
|
|
await act(async () => {
|
|
|
|
await act(async () => {
|
|
|
|
@@ -423,11 +450,15 @@ describe("homepage", () => {
|
|
|
|
expect(container.querySelector("form")).toBeNull();
|
|
|
|
expect(container.querySelector("form")).toBeNull();
|
|
|
|
expect(container.querySelector("h3")?.textContent).toContain("Daily water");
|
|
|
|
expect(container.querySelector("h3")?.textContent).toContain("Daily water");
|
|
|
|
await act(async () => buttonNamed("Archive habit Daily water").click());
|
|
|
|
await act(async () => buttonNamed("Archive habit Daily water").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
expect((await f.json("/habits")).habits).toHaveLength(1);
|
|
|
|
expect((await f.json("/habits")).habits).toHaveLength(1);
|
|
|
|
await act(async () => buttonNamed("Cancel").click());
|
|
|
|
await act(async () => buttonNamed("Cancel").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
expect((await f.json("/habits")).habits).toHaveLength(1);
|
|
|
|
expect((await f.json("/habits")).habits).toHaveLength(1);
|
|
|
|
await act(async () => buttonNamed("Archive habit Daily water").click());
|
|
|
|
await act(async () => buttonNamed("Archive habit Daily water").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
await act(async () => buttonNamed("Archive habit").click());
|
|
|
|
await act(async () => buttonNamed("Archive habit").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
expect((await f.json("/habits")).habits).toHaveLength(0);
|
|
|
|
expect((await f.json("/habits")).habits).toHaveLength(0);
|
|
|
|
expect(container.querySelector(".ds-habit-chart")).toBeNull();
|
|
|
|
expect(container.querySelector(".ds-habit-chart")).toBeNull();
|
|
|
|
const previous = await f.json(`/habits/${habit.id}/days/2026-09-03`);
|
|
|
|
const previous = await f.json(`/habits/${habit.id}/days/2026-09-03`);
|
|
|
|
@@ -446,6 +477,7 @@ describe("homepage", () => {
|
|
|
|
await f.json(`/habits/${habit.id}/days/2026-09-04/tasks/${task.id}`, "PUT", { done: true });
|
|
|
|
await f.json(`/habits/${habit.id}/days/2026-09-04/tasks/${task.id}`, "PUT", { done: true });
|
|
|
|
await render("/");
|
|
|
|
await render("/");
|
|
|
|
await act(async () => buttonNamed("Edit task Stretch").click());
|
|
|
|
await act(async () => buttonNamed("Edit task Stretch").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
const form = container.querySelector<HTMLFormElement>('form[aria-label="Edit task Stretch"]')!;
|
|
|
|
const form = container.querySelector<HTMLFormElement>('form[aria-label="Edit task Stretch"]')!;
|
|
|
|
await act(async () => {
|
|
|
|
await act(async () => {
|
|
|
|
const input = form.querySelector("input")!;
|
|
|
|
const input = form.querySelector("input")!;
|
|
|
|
@@ -458,7 +490,9 @@ describe("homepage", () => {
|
|
|
|
expect(saved.schedule).toEqual({ type: "weekdays", days: [4, 5] });
|
|
|
|
expect(saved.schedule).toEqual({ type: "weekdays", days: [4, 5] });
|
|
|
|
expect((await f.json("/today")).habits[0].value).toBe(1);
|
|
|
|
expect((await f.json("/today")).habits[0].value).toBe(1);
|
|
|
|
await act(async () => buttonNamed("Delete task Stretch gently").click());
|
|
|
|
await act(async () => buttonNamed("Delete task Stretch gently").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
await act(async () => buttonNamed("Delete task").click());
|
|
|
|
await act(async () => buttonNamed("Delete task").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
const today = (await f.json("/today")).habits[0];
|
|
|
|
const today = (await f.json("/today")).habits[0];
|
|
|
|
expect(today.target).toBe(1);
|
|
|
|
expect(today.target).toBe(1);
|
|
|
|
expect(today.value).toBe(0);
|
|
|
|
expect(today.value).toBe(0);
|
|
|
|
@@ -474,9 +508,11 @@ describe("homepage", () => {
|
|
|
|
await render("/");
|
|
|
|
await render("/");
|
|
|
|
expect(container.querySelector(".ds-habit-chart")).toBeNull();
|
|
|
|
expect(container.querySelector(".ds-habit-chart")).toBeNull();
|
|
|
|
await act(async () => buttonNamed("All habits · 1").click());
|
|
|
|
await act(async () => buttonNamed("All habits · 1").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
expect(container.textContent).toContain("Not scheduled today");
|
|
|
|
expect(container.textContent).toContain("Not scheduled today");
|
|
|
|
expect(container.querySelector<HTMLInputElement>('.ds-editable-task input[type="checkbox"]')?.disabled).toBe(true);
|
|
|
|
expect(container.querySelector<HTMLInputElement>('.ds-editable-task input[type="checkbox"]')?.disabled).toBe(true);
|
|
|
|
await act(async () => buttonNamed("Edit task Walk").click());
|
|
|
|
await act(async () => buttonNamed("Edit task Walk").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
const form = container.querySelector<HTMLFormElement>('form[aria-label="Edit task Walk"]')!;
|
|
|
|
const form = container.querySelector<HTMLFormElement>('form[aria-label="Edit task Walk"]')!;
|
|
|
|
await act(async () => {
|
|
|
|
await act(async () => {
|
|
|
|
const input = form.querySelector("input")!;
|
|
|
|
const input = form.querySelector("input")!;
|
|
|
|
@@ -516,6 +552,7 @@ describe("homepage", () => {
|
|
|
|
{ type: "daily" },
|
|
|
|
{ type: "daily" },
|
|
|
|
] as const) {
|
|
|
|
] as const) {
|
|
|
|
await act(async () => buttonNamed("Edit task Walk").click());
|
|
|
|
await act(async () => buttonNamed("Edit task Walk").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
const form = container.querySelector<HTMLFormElement>('form[aria-label="Edit task Walk"]')!;
|
|
|
|
const form = container.querySelector<HTMLFormElement>('form[aria-label="Edit task Walk"]')!;
|
|
|
|
await selectValue(form.querySelector("select")!, schedule.type);
|
|
|
|
await selectValue(form.querySelector("select")!, schedule.type);
|
|
|
|
if (schedule.type === "interval" || schedule.type === "weekly") {
|
|
|
|
if (schedule.type === "interval" || schedule.type === "weekly") {
|
|
|
|
@@ -528,6 +565,7 @@ describe("homepage", () => {
|
|
|
|
if (schedule.type === "interval") expect(buttonNamed("All habits · 1").getAttribute("aria-pressed")).toBe("true");
|
|
|
|
if (schedule.type === "interval") expect(buttonNamed("All habits · 1").getAttribute("aria-pressed")).toBe("true");
|
|
|
|
expect(container.querySelector("form")).toBeNull();
|
|
|
|
expect(container.querySelector("form")).toBeNull();
|
|
|
|
await act(async () => buttonNamed("Edit task Walk").click());
|
|
|
|
await act(async () => buttonNamed("Edit task Walk").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
const reopened = container.querySelector<HTMLFormElement>('form[aria-label="Edit task Walk"]')!;
|
|
|
|
const reopened = container.querySelector<HTMLFormElement>('form[aria-label="Edit task Walk"]')!;
|
|
|
|
expect(reopened.querySelector("select")?.value).toBe(schedule.type);
|
|
|
|
expect(reopened.querySelector("select")?.value).toBe(schedule.type);
|
|
|
|
if (schedule.type === "interval" || schedule.type === "weekly") {
|
|
|
|
if (schedule.type === "interval" || schedule.type === "weekly") {
|
|
|
|
@@ -535,6 +573,7 @@ describe("homepage", () => {
|
|
|
|
expect(reopened.querySelector<HTMLInputElement>('input[type="date"]')?.value).toBe(schedule.anchor);
|
|
|
|
expect(reopened.querySelector<HTMLInputElement>('input[type="date"]')?.value).toBe(schedule.anchor);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
await act(async () => buttonNamed("Cancel").click());
|
|
|
|
await act(async () => buttonNamed("Cancel").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const previous = await f.json(`/habits/${habit.id}/days/2026-09-03`);
|
|
|
|
const previous = await f.json(`/habits/${habit.id}/days/2026-09-03`);
|
|
|
|
expect(previous.tasks[0].done).toBe(true);
|
|
|
|
expect(previous.tasks[0].done).toBe(true);
|
|
|
|
@@ -554,17 +593,20 @@ describe("homepage", () => {
|
|
|
|
};
|
|
|
|
};
|
|
|
|
const submit = async () => act(async () => container.querySelector("form")!.dispatchEvent(new dom.Event("submit", { bubbles: true, cancelable: true }) as unknown as Event));
|
|
|
|
const submit = async () => act(async () => container.querySelector("form")!.dispatchEvent(new dom.Event("submit", { bubbles: true, cancelable: true }) as unknown as Event));
|
|
|
|
await act(async () => buttonNamed("Edit habit Reading").click());
|
|
|
|
await act(async () => buttonNamed("Edit habit Reading").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
await selectMethod("count");
|
|
|
|
await selectMethod("count");
|
|
|
|
await act(async () => container.querySelector<HTMLInputElement>('form input[type="checkbox"]')!.click());
|
|
|
|
await act(async () => container.querySelector<HTMLInputElement>('form input[type="checkbox"]')!.click());
|
|
|
|
await submit();
|
|
|
|
await submit();
|
|
|
|
expect((await f.json(`/habits/${habit.id}`)).method).toBe("count");
|
|
|
|
expect((await f.json(`/habits/${habit.id}`)).method).toBe("count");
|
|
|
|
expect((await f.json(`/habits/${habit.id}`)).carryPartialProgress).toBe(true);
|
|
|
|
expect((await f.json(`/habits/${habit.id}`)).carryPartialProgress).toBe(true);
|
|
|
|
await act(async () => buttonNamed("Edit habit Reading").click());
|
|
|
|
await act(async () => buttonNamed("Edit habit Reading").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
expect(container.querySelector<HTMLInputElement>('form input[type="checkbox"]')?.checked).toBe(true);
|
|
|
|
expect(container.querySelector<HTMLInputElement>('form input[type="checkbox"]')?.checked).toBe(true);
|
|
|
|
await selectMethod("tasks");
|
|
|
|
await selectMethod("tasks");
|
|
|
|
await submit();
|
|
|
|
await submit();
|
|
|
|
expect((await f.json(`/habits/${habit.id}`)).method).toBe("tasks");
|
|
|
|
expect((await f.json(`/habits/${habit.id}`)).method).toBe("tasks");
|
|
|
|
await act(async () => buttonNamed("Add task").click());
|
|
|
|
await act(async () => buttonNamed("Add task").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
const form = container.querySelector<HTMLFormElement>('form[aria-label="Add task"]')!;
|
|
|
|
const form = container.querySelector<HTMLFormElement>('form[aria-label="Add task"]')!;
|
|
|
|
await act(async () => {
|
|
|
|
await act(async () => {
|
|
|
|
const input = form.querySelector("input")!;
|
|
|
|
const input = form.querySelector("input")!;
|
|
|
|
@@ -584,16 +626,19 @@ describe("homepage", () => {
|
|
|
|
const habit = await f.json("/habits", "POST", { name: "Reading", method: "manual" }, 201);
|
|
|
|
const habit = await f.json("/habits", "POST", { name: "Reading", method: "manual" }, 201);
|
|
|
|
await render("/");
|
|
|
|
await render("/");
|
|
|
|
await act(async () => buttonNamed("Archive habit Reading").click());
|
|
|
|
await act(async () => buttonNamed("Archive habit Reading").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
fetchMock.mockImplementationOnce((async (input, init) => {
|
|
|
|
fetchMock.mockImplementationOnce((async (input, init) => {
|
|
|
|
return f.request(String(input).replace("/api", ""), init?.method);
|
|
|
|
return f.request(String(input).replace("/api", ""), init?.method);
|
|
|
|
}) as typeof fetch);
|
|
|
|
}) as typeof fetch);
|
|
|
|
fetchMock.mockResolvedValueOnce(new Response(null, { status: 500 }));
|
|
|
|
fetchMock.mockResolvedValueOnce(new Response(null, { status: 500 }));
|
|
|
|
await act(async () => buttonNamed("Archive habit").click());
|
|
|
|
await act(async () => buttonNamed("Archive habit").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
expect(container.querySelector("form")).toBeNull();
|
|
|
|
expect(container.querySelector("form")).toBeNull();
|
|
|
|
expect(container.textContent).toContain("Your change was saved, but the dashboard could not refresh");
|
|
|
|
expect(container.textContent).toContain("Your change was saved, but the dashboard could not refresh");
|
|
|
|
expect(buttonNamed("Archive habit Reading").disabled).toBe(true);
|
|
|
|
expect(buttonNamed("Archive habit Reading").disabled).toBe(true);
|
|
|
|
expect((await f.json(`/habits/${habit.id}`)).archived).toBe(true);
|
|
|
|
expect((await f.json(`/habits/${habit.id}`)).archived).toBe(true);
|
|
|
|
await act(async () => buttonNamed("Try again").click());
|
|
|
|
await act(async () => buttonNamed("Try again").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
expect(container.querySelector(".ds-habit-chart")).toBeNull();
|
|
|
|
expect(container.querySelector(".ds-habit-chart")).toBeNull();
|
|
|
|
expect(fetchMock.mock.calls.filter(call => call[1]?.method === "DELETE")).toHaveLength(1);
|
|
|
|
expect(fetchMock.mock.calls.filter(call => call[1]?.method === "DELETE")).toHaveLength(1);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
@@ -604,10 +649,12 @@ describe("homepage", () => {
|
|
|
|
await render("/");
|
|
|
|
await render("/");
|
|
|
|
fetchMock.mockResolvedValueOnce(Response.json({ error: "Save unavailable" }, { status: 500 }));
|
|
|
|
fetchMock.mockResolvedValueOnce(Response.json({ error: "Save unavailable" }, { status: 500 }));
|
|
|
|
await act(async () => buttonNamed("Increase Water").click());
|
|
|
|
await act(async () => buttonNamed("Increase Water").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
expect(container.querySelector('[role="alert"]')?.textContent).toContain("Save unavailable");
|
|
|
|
expect(container.querySelector('[role="alert"]')?.textContent).toContain("Save unavailable");
|
|
|
|
expect(container.querySelector<HTMLInputElement>('[aria-label="Total Water"]')?.value).toBe("0");
|
|
|
|
expect(container.querySelector<HTMLInputElement>('[aria-label="Total Water"]')?.value).toBe("0");
|
|
|
|
expect(buttonNamed("Increase Water").disabled).toBe(false);
|
|
|
|
expect(buttonNamed("Increase Water").disabled).toBe(false);
|
|
|
|
await act(async () => buttonNamed("Increase Water").click());
|
|
|
|
await act(async () => buttonNamed("Increase Water").click());
|
|
|
|
|
|
|
|
await settleDeferred();
|
|
|
|
expect(container.querySelector<HTMLInputElement>('[aria-label="Total Water"]')?.value).toBe("1");
|
|
|
|
expect(container.querySelector<HTMLInputElement>('[aria-label="Total Water"]')?.value).toBe("1");
|
|
|
|
expect((await f.json("/today")).habits[0].value).toBe(1);
|
|
|
|
expect((await f.json("/today")).habits[0].value).toBe(1);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|