g1t/apps/web/app/lib/pending.test.ts

38 lines1,927 bytesCodeBlame
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import { isPending } from "./pending.ts";
5
6const posted = (entries: Record<string, string>) => {
7 const form = new FormData();
8 for (const [name, value] of Object.entries(entries)) form.set(name, value);
9 return form;
10};
11
12test("nothing is pending while the page is idle", () => {
13 assert.equal(isPending({ state: "idle" }), false);
14 assert.equal(isPending({ state: "idle", formMethod: "POST", formData: posted({ intent: "save" }) }, { intent: "save" }), false);
15});
16
17test("a post is pending while it is sent and while the page reloads after it", () => {
18 const formData = posted({ intent: "save" });
19 assert.equal(isPending({ state: "submitting", formMethod: "POST", formData }), true);
20 assert.equal(isPending({ state: "loading", formMethod: "POST", formData }), true);
21 assert.equal(isPending({ state: "loading", formMethod: "post", formData }, { intent: "save" }), true);
22});
23
24test("following a link, or a GET form, is not a pending write", () => {
25 assert.equal(isPending({ state: "loading" }), false);
26 assert.equal(isPending({ state: "loading", formMethod: "GET", formData: posted({ q: "x" }) }), false);
27 assert.equal(isPending({ state: "submitting", formMethod: "GET", formData: posted({ q: "x" }) }), false);
28});
29
30test("only the submission that matches is pending, so one row's button does not speak for another's", () => {
31 const formData = posted({ intent: "delete", id: "hook_1" });
32 const sending = { state: "submitting" as const, formMethod: "POST", formData };
33 assert.equal(isPending(sending, { intent: "delete", id: "hook_1" }), true);
34 assert.equal(isPending(sending, { intent: "delete", id: "hook_2" }), false);
35 assert.equal(isPending(sending, { intent: "ping" }), false);
36 // A field left out (undefined) does not have to match.
37 assert.equal(isPending(sending, { intent: "delete", id: undefined }), true);
38});