@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.
- package/CHANGELOG.md +8 -0
- package/README.md +141 -0
- package/bunfig.toml +6 -0
- package/index.js +343 -129
- package/package.json +12 -5
- package/tests/Basic.test.jsx +37 -0
- package/tests/BasicForm.jsx +29 -0
- package/tests/Component.test.jsx +79 -0
- package/tests/DynamicArrayForm.jsx +32 -0
- package/tests/DynamicArrays.test.jsx +33 -0
- package/tests/DynamicListForm.jsx +20 -0
- package/tests/Logic.test.js +153 -0
- package/tests/NestedFields.test.jsx +28 -0
- package/tests/NestedForm.jsx +32 -0
- package/tests/Reactivity.test.jsx +26 -0
- package/tests/RegisterMetadata.test.js +83 -0
- package/tests/State.test.js +93 -0
- package/tests/StateComponent.test.jsx +84 -0
- package/tests/ValidationModes.test.js +84 -0
|
@@ -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
|
+
});
|