@opentf/web-form 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,84 @@
1
+ import { expect, test, describe } from "bun:test";
2
+ import { render, userEvent } from "@opentf/web-test";
3
+ import { createForm } from "../index.js";
4
+ import { sleep } from "@opentf/std";
5
+
6
+ const StateForm = ({ onSubmit }) => {
7
+ const form = createForm({
8
+ mode: "onChange", // Use onChange for easier testing of reactive text
9
+ initialValues: { username: "alice" },
10
+ validate: (v) => v.username.length < 3 ? { username: "Too short" } : {}
11
+ });
12
+
13
+ return (
14
+ <form onsubmit={form.handleSubmit(onSubmit)}>
15
+ <input {...form.register("username")} data-testid="username" />
16
+
17
+ <div data-testid="status-valid">{form.isValid ? "Valid" : "Invalid"}</div>
18
+ <div data-testid="status-changed">{form.isChanged ? "Changed" : "Unchanged"}</div>
19
+ <div data-testid="status-touched">{form.isTouched ? "Touched" : "Untouched"}</div>
20
+
21
+ {form.errors.username && <span data-testid="error">{form.errors.username}</span>}
22
+
23
+ <button type="button" onclick={() => form.reset()} data-testid="reset">Reset</button>
24
+ <button type="submit" data-testid="submit">Submit</button>
25
+
26
+ {form.isSubmitted && <div data-testid="success">Submitted!</div>}
27
+ </form>
28
+ );
29
+
30
+ };
31
+
32
+ const waitFor = async (fn, timeout = 1000) => {
33
+ const start = Date.now();
34
+ while (Date.now() - start < timeout) {
35
+ try {
36
+ fn();
37
+ return;
38
+ } catch (e) {
39
+ await sleep(10);
40
+ }
41
+ }
42
+ fn();
43
+ };
44
+
45
+ describe("Form State UI Reactivity", () => {
46
+ test("updates UI based on state helpers", async () => {
47
+ let submitted = false;
48
+ const { getByTestId, queryByTestId } = render(StateForm, { onSubmit: async () => {
49
+ submitted = true;
50
+ }});
51
+ const user = userEvent.setup();
52
+
53
+ const input = getByTestId("username");
54
+ const statusValid = getByTestId("status-valid");
55
+ const statusChanged = getByTestId("status-changed");
56
+ const statusTouched = getByTestId("status-touched");
57
+ const resetBtn = getByTestId("reset");
58
+ const submitBtn = getByTestId("submit");
59
+
60
+ // Initial state
61
+ expect(statusValid.textContent).toBe("Valid");
62
+
63
+ // Change value to invalid
64
+ await user.clear(input);
65
+ await user.type(input, "ab");
66
+ expect(statusValid.textContent).toBe("Invalid");
67
+ expect(statusChanged.textContent).toBe("Changed");
68
+ expect(getByTestId("error").textContent).toBe("Too short");
69
+
70
+ // Submit valid data
71
+ await user.clear(input);
72
+ await user.type(input, "bob");
73
+ await user.click(submitBtn);
74
+
75
+ await waitFor(() => expect(submitted).toBe(true));
76
+ await waitFor(() => expect(getByTestId("success")).toBeTruthy());
77
+
78
+ // Reset
79
+ await user.click(resetBtn);
80
+ expect(input.value).toBe("alice");
81
+ expect(statusChanged.textContent).toBe("Unchanged");
82
+ expect(queryByTestId("success")).toBeNull();
83
+ });
84
+ });
@@ -0,0 +1,84 @@
1
+ import { expect, test, describe } from "bun:test";
2
+ import { createForm } from "../index.js";
3
+
4
+ describe("Web App Framework Forms Library - Validation Modes", () => {
5
+ test("mode: onBlur (default)", () => {
6
+ const form = createForm({
7
+ initialValues: { name: "" },
8
+ validate: (v) => v.name.length < 3 ? { name: "Short" } : {}
9
+ });
10
+
11
+ expect(form.errors.name).toBeUndefined();
12
+
13
+ // oninput should NOT trigger validation
14
+ form.register("name").oninput({ target: { value: "a" } });
15
+ expect(form.errors.name).toBeUndefined();
16
+
17
+ // onblur SHOULD trigger validation
18
+ form.register("name").onblur();
19
+ expect(form.errors.name).toBe("Short");
20
+ });
21
+
22
+ test("mode: onChange", () => {
23
+ const form = createForm({
24
+ mode: "onChange",
25
+ initialValues: { name: "" },
26
+ validate: (v) => v.name.length < 3 ? { name: "Short" } : {}
27
+ });
28
+
29
+ expect(form.errors.name).toBe("Short"); // Validates on init
30
+
31
+ form.register("name").oninput({ target: { value: "abc" } });
32
+ expect(form.errors.name).toBeUndefined();
33
+ });
34
+
35
+ test("mode: onSubmit", async () => {
36
+ const form = createForm({
37
+ mode: "onSubmit",
38
+ initialValues: { name: "" },
39
+ validate: (v) => v.name.length < 3 ? { name: "Short" } : {}
40
+ });
41
+
42
+ form.register("name").oninput({ target: { value: "a" } });
43
+ form.register("name").onblur();
44
+ expect(form.errors.name).toBeUndefined();
45
+
46
+ await form.handleSubmit(() => {})();
47
+ expect(form.errors.name).toBe("Short");
48
+ });
49
+
50
+ test("reValidateMode: onChange (default)", () => {
51
+ const form = createForm({
52
+ mode: "onBlur", // Validate on blur first
53
+ initialValues: { name: "" },
54
+ validate: (v) => v.name.length < 3 ? { name: "Short" } : {}
55
+ });
56
+
57
+ form.register("name").onblur();
58
+ expect(form.errors.name).toBe("Short");
59
+
60
+ // Once error exists, oninput SHOULD trigger re-validation
61
+ form.register("name").oninput({ target: { value: "abc" } });
62
+ expect(form.errors.name).toBeUndefined();
63
+ });
64
+
65
+ test("reValidateMode: onBlur", () => {
66
+ const form = createForm({
67
+ mode: "onBlur",
68
+ reValidateMode: "onBlur",
69
+ initialValues: { name: "" },
70
+ validate: (v) => v.name.length < 3 ? { name: "Short" } : {}
71
+ });
72
+
73
+ form.register("name").onblur();
74
+ expect(form.errors.name).toBe("Short");
75
+
76
+ // Should NOT re-validate on input
77
+ form.register("name").oninput({ target: { value: "abc" } });
78
+ expect(form.errors.name).toBe("Short");
79
+
80
+ // Should re-validate on blur
81
+ form.register("name").onblur();
82
+ expect(form.errors.name).toBeUndefined();
83
+ });
84
+ });