react-form-manage 1.1.0-beta.1 → 1.1.0-beta.3
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 +46 -0
- package/dist/test/testFormInstance/TestCase1_FormWithInstance.js +1 -1
- package/dist/test/testFormInstance/TestCase3_SafeFormMethods.js +4 -1
- package/dist/test/testFormInstance/TestCase4_UseFormStatusFlag.js +11 -2
- package/dist/test/testFormInstance/TestCase5_UseFormUtils.js +7 -2
- package/dist/test/testFormInstance/TestCase6_FormWithoutWrapper.js +11 -1
- package/dist/test/testFormInstance/TestCase7_SetValueInEffect.d.ts +1 -0
- package/dist/test/testFormInstance/TestCase7_SetValueInEffect.js +76 -0
- package/dist/test/testFormInstance/TestCase8_TypeSafetyImprovements.d.ts +1 -0
- package/dist/test/testFormInstance/TestCase8_TypeSafetyImprovements.js +102 -0
- package/dist/test/testFormInstance/index.d.ts +2 -0
- package/dist/test/testFormInstance/index.js +18 -1
- package/dist/types/public.d.ts +13 -8
- package/package.json +2 -2
- package/src/test/testFormInstance/TestCase1_FormWithInstance.tsx +11 -7
- package/src/test/testFormInstance/TestCase2_AutoGenerateFormName.tsx +16 -13
- package/src/test/testFormInstance/TestCase3_SafeFormMethods.tsx +32 -15
- package/src/test/testFormInstance/TestCase4_UseFormStatusFlag.tsx +37 -11
- package/src/test/testFormInstance/TestCase5_UseFormUtils.tsx +35 -17
- package/src/test/testFormInstance/TestCase6_FormWithoutWrapper.tsx +64 -30
- package/src/test/testFormInstance/TestCase7_SetValueInEffect.tsx +307 -0
- package/src/test/testFormInstance/TestCase8_TypeSafetyImprovements.tsx +390 -0
- package/src/test/testFormInstance/index.tsx +48 -9
- package/src/types/public.ts +16 -8
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,52 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.1.0-beta.3] - 2026-03-03
|
|
6
|
+
|
|
7
|
+
### Type Fix
|
|
8
|
+
|
|
9
|
+
- **PublicFormInstance.setFieldValues**: Corrected options parameter type from `SetFieldValueOptions` to `OnChangeOptions`
|
|
10
|
+
- `setFieldValues` does not support `deepTrigger` option (only available in `setFieldValue`)
|
|
11
|
+
- Now correctly reflects the actual implementation
|
|
12
|
+
|
|
13
|
+
### Technical Details
|
|
14
|
+
|
|
15
|
+
- `SetFieldValueOptions` extends `OnChangeOptions` with additional `deepTrigger?: boolean`
|
|
16
|
+
- `setFieldValue` (singular): Supports `deepTrigger` for nested field updates
|
|
17
|
+
- `setFieldValues` (plural): Batch updates without `deepTrigger` support
|
|
18
|
+
|
|
19
|
+
## [1.1.0-beta.2] - 2026-03-03
|
|
20
|
+
|
|
21
|
+
### Type Improvements
|
|
22
|
+
|
|
23
|
+
- **PublicFormInstance Type Safety**: Enhanced type definitions for better developer experience
|
|
24
|
+
- `name` parameters: Changed from `keyof T & string` to `keyof T | (string & {})` for dynamic field name support
|
|
25
|
+
- Applied to: `setFieldValue`, `getFieldValue`, `setFieldFocus`, `getFieldValues`, `getFieldErrors`
|
|
26
|
+
- `getFieldErrors` return type: Changed from `Record<string, any>` to `Array<{ name: string; errors: FormFieldError[] }>` to match actual implementation
|
|
27
|
+
- `submitAsync` return type: Changed from `Promise<any>` to `Promise<void>` for better type safety
|
|
28
|
+
- **UseFormItemProps**: Updated `name` property type to `keyof T | (string & {})` for consistency
|
|
29
|
+
|
|
30
|
+
### New Test Cases
|
|
31
|
+
|
|
32
|
+
- **TestCase7: Set Value in useEffect with Timeout**: Test async behavior with setTimeout
|
|
33
|
+
- Verify setValue works correctly when called from async callbacks
|
|
34
|
+
- Test cleanup behavior when component unmounts before timeout
|
|
35
|
+
- Configurable delay with remount functionality
|
|
36
|
+
- Real-time event logging
|
|
37
|
+
- **TestCase8: Type Safety Improvements**: Comprehensive type validation test
|
|
38
|
+
- Test typed vs dynamic field names
|
|
39
|
+
- Validate getFieldErrors return type structure
|
|
40
|
+
- Test submitAsync Promise<void> return type
|
|
41
|
+
- Table display for field errors
|
|
42
|
+
- Comprehensive validation scenarios
|
|
43
|
+
|
|
44
|
+
### Technical Details
|
|
45
|
+
|
|
46
|
+
- All form instance methods now consistently support dynamic field names
|
|
47
|
+
- Type definitions match actual runtime behavior
|
|
48
|
+
- Enhanced IntelliSense and autocomplete for form methods
|
|
49
|
+
- 8 comprehensive test cases covering all Form Instance features
|
|
50
|
+
|
|
5
51
|
## [1.1.0-beta.1] - 2026-03-03
|
|
6
52
|
|
|
7
53
|
### Breaking Changes
|
|
@@ -13,7 +13,7 @@ function TestCase1_FormWithInstance() {
|
|
|
13
13
|
const handleReset = () => {
|
|
14
14
|
form.resetFields();
|
|
15
15
|
};
|
|
16
|
-
return _jsxs("div", { style: { padding: 24 }, children: [_jsx(Title, { level: 3, children: "TestCase1: Form with Instance Prop" }), _jsxs(Paragraph, { children: [_jsx(Text, { strong: true, children: "Breaking Change:" }), " Form component c\xF3 th\u1EC3 nh\u1EADn prop ", _jsx(Text, { code: true, children: "form" }), " thay v\xEC ", _jsx(Text, { code: true, children: "formName" })] }), _jsx(Paragraph, { children: _jsx(Text, { type: "secondary", children: "Form instance \u0111\u01B0\u1EE3c t\u1EA1o b\u1EB1ng Form.useForm() v\xE0 truy\u1EC1n v\xE0o Form component. Instance n\xE0y c\xF3 th\u1EC3 \u0111\u01B0\u1EE3c s\u1EED d\u1EE5ng tr\u01B0\u1EDBc khi Form mount." }) }), _jsxs(Space, { style: { marginBottom: 24 }, children: [_jsx(Button, { onClick: handleSetValueBeforeMount, type: "primary", children: "Set Values Before Mount" }), _jsx(Button, { onClick: handleGetValues, children: "Get Current Values (Check Console)" }), _jsx(Button, { onClick: handleReset, danger: true, children: "Reset Form" })] }), _jsxs(Form, { form, initialValues: { username: "", email: "" }, onFinish: (values) => {
|
|
16
|
+
return _jsxs("div", { style: { padding: 24 }, children: [_jsx(Title, { level: 3, children: "TestCase1: Form with Instance Prop" }), _jsxs(Paragraph, { children: [_jsx(Text, { strong: true, children: "Breaking Change:" }), " Form component c\xF3 th\u1EC3 nh\u1EADn prop", " ", _jsx(Text, { code: true, children: "form" }), " thay v\xEC ", _jsx(Text, { code: true, children: "formName" })] }), _jsx(Paragraph, { children: _jsx(Text, { type: "secondary", children: "Form instance \u0111\u01B0\u1EE3c t\u1EA1o b\u1EB1ng Form.useForm() v\xE0 truy\u1EC1n v\xE0o Form component. Instance n\xE0y c\xF3 th\u1EC3 \u0111\u01B0\u1EE3c s\u1EED d\u1EE5ng tr\u01B0\u1EDBc khi Form mount." }) }), _jsxs(Space, { style: { marginBottom: 24 }, children: [_jsx(Button, { onClick: handleSetValueBeforeMount, type: "primary", children: "Set Values Before Mount" }), _jsx(Button, { onClick: handleGetValues, children: "Get Current Values (Check Console)" }), _jsx(Button, { onClick: handleReset, danger: true, children: "Reset Form" })] }), _jsxs(Form, { form, initialValues: { username: "", email: "" }, onFinish: (values) => {
|
|
17
17
|
}, children: [_jsx(Form.Item, { name: "username", label: "Username", children: _jsx("input", { type: "text", placeholder: "Username", style: { padding: 8, width: 300 } }) }), _jsx(Form.Item, { name: "email", label: "Email", children: _jsx("input", { type: "email", placeholder: "Email", style: { padding: 8, width: 300 } }) }), _jsx(Button, { type: "primary", htmlType: "submit", children: "Submit" })] }), _jsxs("div", { style: { marginTop: 24, padding: 16, background: "#f5f5f5" }, children: [_jsx(Title, { level: 5, children: "Expected Results:" }), _jsxs("ul", { children: [_jsx("li", { children: '\u2705 Click "Set Values Before Mount" \u2192 Values \u0111\u01B0\u1EE3c set v\xE0 log ra console' }), _jsx("li", { children: '\u2705 Click "Get Current Values" \u2192 Values hi\u1EC7n t\u1EA1i \u0111\u01B0\u1EE3c log ra' }), _jsx("li", { children: "\u2705 Form render v\u1EDBi values \u0111\xE3 set" }), _jsx("li", { children: '\u2705 Click "Reset Form" \u2192 Form reset v\u1EC1 initialValues' }), _jsx("li", { children: "\u2705 Submit form \u2192 Console log submitted values" })] })] })] });
|
|
18
18
|
}
|
|
19
19
|
export {
|
|
@@ -8,7 +8,10 @@ function TestCase3_SafeFormMethods() {
|
|
|
8
8
|
const [formMounted, setFormMounted] = useState(false);
|
|
9
9
|
const [logs, setLogs] = useState([]);
|
|
10
10
|
const addLog = (message) => {
|
|
11
|
-
setLogs((prev) => [
|
|
11
|
+
setLogs((prev) => [
|
|
12
|
+
...prev,
|
|
13
|
+
`[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] ${message}`
|
|
14
|
+
]);
|
|
12
15
|
};
|
|
13
16
|
const handleSetValueBeforeMount = () => {
|
|
14
17
|
addLog("\u{1F534} Calling setFieldValue BEFORE Form mount");
|
|
@@ -33,7 +33,12 @@ function TestCase4_UseFormStatusFlag() {
|
|
|
33
33
|
const clearHistory = () => {
|
|
34
34
|
setStatusHistory([]);
|
|
35
35
|
};
|
|
36
|
-
return _jsxs("div", { style: { padding: 24 }, children: [_jsx(Title, { level: 3, children: "TestCase4: useForm Returns Status Flag" }), _jsxs(Paragraph, { children: [_jsx(Text, { strong: true, children: "Breaking Change:" }), " useForm() b\xE2y gi\u1EDD tr\u1EA3 v\u1EC1 tuple [instance, isReady]"] }), _jsx(Paragraph, { children: _jsx(Text, { type: "secondary", children: "isReady flag cho bi\u1EBFt Form \u0111\xE3 mount v\xE0 s\u1EB5n s\xE0ng nh\u1EADn calls hay ch\u01B0a. C\xF3 th\u1EC3 d\xF9ng \u0111\u1EC3 conditionally enable/disable features d\u1EF1a tr\xEAn Form state." }) }), _jsx("div", { style: {
|
|
36
|
+
return _jsxs("div", { style: { padding: 24 }, children: [_jsx(Title, { level: 3, children: "TestCase4: useForm Returns Status Flag" }), _jsxs(Paragraph, { children: [_jsx(Text, { strong: true, children: "Breaking Change:" }), " useForm() b\xE2y gi\u1EDD tr\u1EA3 v\u1EC1 tuple [instance, isReady]"] }), _jsx(Paragraph, { children: _jsx(Text, { type: "secondary", children: "isReady flag cho bi\u1EBFt Form \u0111\xE3 mount v\xE0 s\u1EB5n s\xE0ng nh\u1EADn calls hay ch\u01B0a. C\xF3 th\u1EC3 d\xF9ng \u0111\u1EC3 conditionally enable/disable features d\u1EF1a tr\xEAn Form state." }) }), _jsx("div", { style: {
|
|
37
|
+
marginBottom: 24,
|
|
38
|
+
padding: 16,
|
|
39
|
+
background: "#fafafa",
|
|
40
|
+
border: "1px solid #d9d9d9"
|
|
41
|
+
}, children: _jsxs(Space, { size: "large", children: [_jsxs("div", { children: [_jsx(Text, { strong: true, children: "Form Ready Status: " }), _jsx(Badge, { status: isFormReady ? "success" : "error", text: _jsx(Text, { strong: true, style: { color: isFormReady ? "#52c41a" : "#ff4d4f" }, children: isFormReady ? "READY" : "NOT READY" }) })] }), _jsxs("div", { children: [_jsx(Text, { strong: true, children: "Form Mounted: " }), _jsx(Tag, { color: formMounted ? "green" : "red", children: formMounted ? "YES" : "NO" })] }), _jsxs("div", { children: [_jsx(Text, { strong: true, children: "FormName: " }), _jsx(Text, { code: true, children: form.formName })] })] }) }), _jsxs(Space, { style: { marginBottom: 24 }, wrap: true, children: [!formMounted ? _jsx(Button, { onClick: handleMountForm, type: "primary", size: "large", children: "Mount Form" }) : _jsx(Button, { onClick: handleUnmountForm, danger: true, size: "large", children: "Unmount Form" }), _jsxs(Button, { onClick: handleSetValue, disabled: !isFormReady, type: isFormReady ? "primary" : "default", children: ["Set Values ", !isFormReady && "(Disabled - Form Not Ready)"] }), _jsx(Button, { onClick: clearHistory, children: "Clear History" })] }), _jsxs("div", { style: { display: "flex", gap: 24 }, children: [_jsxs("div", { style: { flex: 1 }, children: [_jsx(Title, { level: 5, children: "Status Change History:" }), _jsx("div", { style: {
|
|
37
42
|
border: "1px solid #d9d9d9",
|
|
38
43
|
padding: 16,
|
|
39
44
|
height: 300,
|
|
@@ -42,7 +47,11 @@ function TestCase4_UseFormStatusFlag() {
|
|
|
42
47
|
}, children: statusHistory.length === 0 ? _jsx(Text, { type: "secondary", children: "No status changes yet..." }) : _jsxs("table", { style: { width: "100%", borderCollapse: "collapse" }, children: [_jsx("thead", { children: _jsxs("tr", { style: { borderBottom: "2px solid #d9d9d9" }, children: [_jsx("th", { style: { padding: 8, textAlign: "left" }, children: "Time" }), _jsx("th", { style: { padding: 8, textAlign: "left" }, children: "Status" }), _jsx("th", { style: { padding: 8, textAlign: "left" }, children: "Event" })] }) }), _jsx("tbody", { children: statusHistory.map((entry, index) => _jsxs("tr", { style: {
|
|
43
48
|
borderBottom: "1px solid #f0f0f0",
|
|
44
49
|
background: index % 2 === 0 ? "#fafafa" : "#ffffff"
|
|
45
|
-
}, children: [_jsx("td", { style: {
|
|
50
|
+
}, children: [_jsx("td", { style: {
|
|
51
|
+
padding: 8,
|
|
52
|
+
fontFamily: "monospace",
|
|
53
|
+
fontSize: 12
|
|
54
|
+
}, children: entry.time }), _jsx("td", { style: { padding: 8 }, children: _jsx(Tag, { color: entry.status ? "green" : "red", children: entry.status ? "TRUE" : "FALSE" }) }), _jsx("td", { style: { padding: 8 }, children: entry.event })] }, index)) })] }) })] }), _jsxs("div", { style: { flex: 1 }, children: [_jsx(Title, { level: 5, children: "Form Panel:" }), formMounted ? _jsx("div", { style: { border: "2px solid #52c41a", padding: 16 }, children: _jsxs(Form, { form, initialValues: { email: "", message: "" }, onFinish: (values) => {
|
|
46
55
|
alert(`Submitted: ${JSON.stringify(values, null, 2)}`);
|
|
47
56
|
}, children: [_jsx(Form.Item, { name: "email", label: "Email", children: _jsx("input", { type: "email", placeholder: "Enter email", style: { padding: 8, width: "100%" } }) }), _jsx(Form.Item, { name: "message", label: "Message", children: _jsx("textarea", { placeholder: "Enter message", rows: 4, style: { padding: 8, width: "100%" } }) }), _jsx(Button, { type: "primary", htmlType: "submit", children: "Submit" })] }) }) : _jsx("div", { style: {
|
|
48
57
|
border: "2px dashed #d9d9d9",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Button,
|
|
2
|
+
import { Button, Card, Input, message, Space, Typography } from "antd";
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import Form from "../../providers/Form";
|
|
5
5
|
const { Title, Paragraph, Text } = Typography;
|
|
@@ -58,7 +58,12 @@ function TestCase5_UseFormUtils() {
|
|
|
58
58
|
message.success(`Form 1 submitted: ${JSON.stringify(values)}`);
|
|
59
59
|
}, children: [_jsx(Form.Item, { name: "username", label: "Username", children: _jsx(Input, { placeholder: "Username" }) }), _jsx(Form.Item, { name: "email", label: "Email", children: _jsx(Input, { type: "email", placeholder: "Email" }) }), _jsx(Button, { type: "primary", htmlType: "submit", children: "Submit Form 1" })] }) }) }), _jsx("div", { style: { flex: 1 }, children: _jsx(Card, { title: "Form 2 (formName='form2')", children: _jsxs(Form, { formName: "form2", initialValues: { username: "", email: "" }, onFinish: (values) => {
|
|
60
60
|
message.success(`Form 2 submitted: ${JSON.stringify(values)}`);
|
|
61
|
-
}, children: [_jsx(Form.Item, { name: "username", label: "Username", children: _jsx(Input, { placeholder: "Username" }) }), _jsx(Form.Item, { name: "email", label: "Email", children: _jsx(Input, { type: "email", placeholder: "Email" }) }), _jsx(Button, { type: "primary", htmlType: "submit", children: "Submit Form 2" })] }) }) })] }), _jsxs("div", { style: { marginTop: 24, padding: 16, background: "#f5f5f5" }, children: [_jsx(Title, { level: 5, children: "Expected Results:" }), _jsxs("ul", { children: [_jsx("li", { children: '\u2705 Control panel starts with target "form1"' }), _jsx("li", { children: `\u2705 Click "Set Single Value" \u2192 Form 1's username field updates to "admin"` }), _jsx("li", { children: `\u2705 Click "Set Multiple Values" \u2192 Form 1's both fields update` }), _jsx("li", { children: '\u2705 Click "Get Form Instance" \u2192 Console logs form instance and current values' }), _jsx("li", { children: '\u2705 Change target to "form2" in input' }), _jsx("li", { children: `\u2705 Click "Set Single Value" \u2192 Form 2's username field updates` }), _jsx("li", { children: '\u2705 Click "Reset Form" \u2192 Target form resets to initialValues' }), _jsx("li", { children: '\u2705 Click "Submit Form" \u2192 Target form submits programmatically' }), _jsx("li", { children: '\u26A0\uFE0F Enter invalid formName \u2192 Error message shows "Form instance not found"' })] })] }), _jsxs("div", { style: {
|
|
61
|
+
}, children: [_jsx(Form.Item, { name: "username", label: "Username", children: _jsx(Input, { placeholder: "Username" }) }), _jsx(Form.Item, { name: "email", label: "Email", children: _jsx(Input, { type: "email", placeholder: "Email" }) }), _jsx(Button, { type: "primary", htmlType: "submit", children: "Submit Form 2" })] }) }) })] }), _jsxs("div", { style: { marginTop: 24, padding: 16, background: "#f5f5f5" }, children: [_jsx(Title, { level: 5, children: "Expected Results:" }), _jsxs("ul", { children: [_jsx("li", { children: '\u2705 Control panel starts with target "form1"' }), _jsx("li", { children: `\u2705 Click "Set Single Value" \u2192 Form 1's username field updates to "admin"` }), _jsx("li", { children: `\u2705 Click "Set Multiple Values" \u2192 Form 1's both fields update` }), _jsx("li", { children: '\u2705 Click "Get Form Instance" \u2192 Console logs form instance and current values' }), _jsx("li", { children: '\u2705 Change target to "form2" in input' }), _jsx("li", { children: `\u2705 Click "Set Single Value" \u2192 Form 2's username field updates` }), _jsx("li", { children: '\u2705 Click "Reset Form" \u2192 Target form resets to initialValues' }), _jsx("li", { children: '\u2705 Click "Submit Form" \u2192 Target form submits programmatically' }), _jsx("li", { children: '\u26A0\uFE0F Enter invalid formName \u2192 Error message shows "Form instance not found"' })] })] }), _jsxs("div", { style: {
|
|
62
|
+
marginTop: 16,
|
|
63
|
+
padding: 16,
|
|
64
|
+
background: "#e6f7ff",
|
|
65
|
+
border: "1px solid #91d5ff"
|
|
66
|
+
}, children: [_jsx(Title, { level: 5, children: "Use Cases:" }), _jsxs("ul", { style: { marginBottom: 0 }, children: [_jsx("li", { children: "Global control panels \u0111\u1EC3 qu\u1EA3n l\xFD multiple forms" }), _jsx("li", { children: "External triggers (t\u1EEB API response, WebSocket, events...)" }), _jsx("li", { children: "Cross-component form manipulation" }), _jsx("li", { children: "Testing v\xE0 debugging tools" }), _jsx("li", { children: "Admin dashboards v\u1EDBi form management" })] })] })] });
|
|
62
67
|
}
|
|
63
68
|
export {
|
|
64
69
|
TestCase5_UseFormUtils as default
|
|
@@ -38,7 +38,17 @@ function TestCase6_FormWithoutWrapper() {
|
|
|
38
38
|
form.submit();
|
|
39
39
|
};
|
|
40
40
|
const fullName = `${firstName || ""} ${lastName || ""}`.trim() || "(empty)";
|
|
41
|
-
return _jsxs("div", { style: { padding: 24 }, children: [_jsx(Title, { level: 3, children: "TestCase6: Form Without Wrapper Component" }), _jsxs(Paragraph, { children: [_jsx(Text, { strong: true, children: "Use Case:" }), " S\u1EED d\u1EE5ng form instance nh\u01B0 state manager m\xE0 kh\xF4ng c\u1EA7n Form component"] }), _jsx(Paragraph, { children: _jsx(Text, { type: "secondary", children: "Form instance c\xF3 th\u1EC3 ho\u1EA1t \u0111\u1ED9ng \u0111\u1ED9c l\u1EADp nh\u01B0 m\u1ED9t state manager. H\u1EEFu \xEDch cho custom form implementations, headless forms, ho\u1EB7c khi b\u1EA1n mu\u1ED1n t\u1EF1 build UI m\xE0 kh\xF4ng d\xF9ng Form component wrapper." }) }), _jsxs("div", { style: { display: "flex", gap: 24 }, children: [_jsx(Card, { title: "\u{1F4DD} Manual Inputs (Not Connected to Form)", style: { flex: 1 }, children: _jsxs(Space, { direction: "vertical", style: { width: "100%" }, children: [_jsxs("div", { children: [_jsx(Text, { strong: true, children: "First Name:" }), _jsx(Input, { value: manualFirstName, onChange: (e) => setManualFirstName(e.target.value), placeholder: "Enter first name" })] }), _jsxs("div", { children: [_jsx(Text, { strong: true, children: "Last Name:" }), _jsx(Input, { value: manualLastName, onChange: (e) => setManualLastName(e.target.value), placeholder: "Enter last name" })] }), _jsxs("div", { children: [_jsx(Text, { strong: true, children: "Age:" }), _jsx(Input, { type: "number", value: manualAge, onChange: (e) => setManualAge(e.target.value), placeholder: "Enter age" })] }), _jsx(Button, { type: "primary", onClick: handleSetFromManualInputs, block: true, children: "Set Form Values" })] }) }), _jsx(Card, { title: "\u{1F441}\uFE0F Form State (via useWatch)", style: { flex: 1 }, children: _jsxs(Space, { direction: "vertical", style: { width: "100%" }, children: [_jsxs("div", { children: [_jsx(Text, { strong: true, children: "First Name: " }), _jsx(Text, { code: true, children: firstName || "(empty)" })] }), _jsxs("div", { children: [_jsx(Text, { strong: true, children: "Last Name: " }), _jsx(Text, { code: true, children: lastName || "(empty)" })] }), _jsxs("div", { children: [_jsx(Text, { strong: true, children: "Age: " }), _jsx(Text, { code: true, children: age != null ? age : "(empty)" })] }), _jsxs("div", { style: { marginTop: 16, padding: 12, background: "#f0f0f0" }, children: [_jsx(Text, { strong: true, children: "Computed Full Name: " }), _jsx(Text, { style: { fontSize: 16 }, children: fullName })] })] }) })] }), _jsxs(Card, { title: "\u{1F3AE} Form Controls", style: { marginTop: 24 }, children: [_jsxs(Space, { wrap: true, children: [_jsx(Button, { onClick: handleSetBatch, type: "primary", children: "Set Batch Values (John Doe, 30)" }), _jsx(Button, { onClick: handleGetValues, children: "Get All Values (Alert)" }), _jsx(Button, { onClick: handleReset, danger: true, children: "Reset Form" }), _jsx(Button, { onClick: handleSubmit, type: "dashed", children: "Submit Form (Will Log Warning)" })] }), _jsxs("div", { style: {
|
|
41
|
+
return _jsxs("div", { style: { padding: 24 }, children: [_jsx(Title, { level: 3, children: "TestCase6: Form Without Wrapper Component" }), _jsxs(Paragraph, { children: [_jsx(Text, { strong: true, children: "Use Case:" }), " S\u1EED d\u1EE5ng form instance nh\u01B0 state manager m\xE0 kh\xF4ng c\u1EA7n Form component"] }), _jsx(Paragraph, { children: _jsx(Text, { type: "secondary", children: "Form instance c\xF3 th\u1EC3 ho\u1EA1t \u0111\u1ED9ng \u0111\u1ED9c l\u1EADp nh\u01B0 m\u1ED9t state manager. H\u1EEFu \xEDch cho custom form implementations, headless forms, ho\u1EB7c khi b\u1EA1n mu\u1ED1n t\u1EF1 build UI m\xE0 kh\xF4ng d\xF9ng Form component wrapper." }) }), _jsxs("div", { style: { display: "flex", gap: 24 }, children: [_jsx(Card, { title: "\u{1F4DD} Manual Inputs (Not Connected to Form)", style: { flex: 1 }, children: _jsxs(Space, { direction: "vertical", style: { width: "100%" }, children: [_jsxs("div", { children: [_jsx(Text, { strong: true, children: "First Name:" }), _jsx(Input, { value: manualFirstName, onChange: (e) => setManualFirstName(e.target.value), placeholder: "Enter first name" })] }), _jsxs("div", { children: [_jsx(Text, { strong: true, children: "Last Name:" }), _jsx(Input, { value: manualLastName, onChange: (e) => setManualLastName(e.target.value), placeholder: "Enter last name" })] }), _jsxs("div", { children: [_jsx(Text, { strong: true, children: "Age:" }), _jsx(Input, { type: "number", value: manualAge, onChange: (e) => setManualAge(e.target.value), placeholder: "Enter age" })] }), _jsx(Button, { type: "primary", onClick: handleSetFromManualInputs, block: true, children: "Set Form Values" })] }) }), _jsx(Card, { title: "\u{1F441}\uFE0F Form State (via useWatch)", style: { flex: 1 }, children: _jsxs(Space, { direction: "vertical", style: { width: "100%" }, children: [_jsxs("div", { children: [_jsx(Text, { strong: true, children: "First Name: " }), _jsx(Text, { code: true, children: firstName || "(empty)" })] }), _jsxs("div", { children: [_jsx(Text, { strong: true, children: "Last Name: " }), _jsx(Text, { code: true, children: lastName || "(empty)" })] }), _jsxs("div", { children: [_jsx(Text, { strong: true, children: "Age: " }), _jsx(Text, { code: true, children: age != null ? age : "(empty)" })] }), _jsxs("div", { style: { marginTop: 16, padding: 12, background: "#f0f0f0" }, children: [_jsx(Text, { strong: true, children: "Computed Full Name: " }), _jsx(Text, { style: { fontSize: 16 }, children: fullName })] })] }) })] }), _jsxs(Card, { title: "\u{1F3AE} Form Controls", style: { marginTop: 24 }, children: [_jsxs(Space, { wrap: true, children: [_jsx(Button, { onClick: handleSetBatch, type: "primary", children: "Set Batch Values (John Doe, 30)" }), _jsx(Button, { onClick: handleGetValues, children: "Get All Values (Alert)" }), _jsx(Button, { onClick: handleReset, danger: true, children: "Reset Form" }), _jsx(Button, { onClick: handleSubmit, type: "dashed", children: "Submit Form (Will Log Warning)" })] }), _jsxs("div", { style: {
|
|
42
|
+
marginTop: 16,
|
|
43
|
+
padding: 12,
|
|
44
|
+
background: "#fff7e6",
|
|
45
|
+
border: "1px solid #ffd591"
|
|
46
|
+
}, children: [_jsx(Text, { strong: true, children: "\u26A0\uFE0F Note: " }), _jsx(Text, { children: "Kh\xF4ng c\xF3 Form component \u0111\u01B0\u1EE3c render. Form instance ho\u1EA1t \u0111\u1ED9ng nh\u01B0 pure state manager. Submit s\u1EBD log warning v\xEC kh\xF4ng c\xF3 onFinish handler." })] })] }), _jsxs("div", { style: { marginTop: 24, padding: 16, background: "#f5f5f5" }, children: [_jsx(Title, { level: 5, children: "Expected Results:" }), _jsxs("ul", { children: [_jsx("li", { children: "\u2705 Type in manual inputs \u2192 Values NOT auto-sync to form state" }), _jsx("li", { children: '\u2705 Click "Set Form Values" \u2192 Form state updates, display panel shows new values' }), _jsx("li", { children: "\u2705 Display panel updates reactively via useWatch" }), _jsx("li", { children: '\u2705 Click "Set Batch Values" \u2192 All fields update at once' }), _jsx("li", { children: '\u2705 Click "Get All Values" \u2192 Alert shows current form values' }), _jsx("li", { children: '\u2705 Click "Reset Form" \u2192 All fields reset (empty)' }), _jsx("li", { children: "\u2705 Computed Full Name updates based on firstName + lastName" }), _jsx("li", { children: '\u26A0\uFE0F Click "Submit Form" \u2192 Console shows warning (no Form component mounted)' })] })] }), _jsxs("div", { style: {
|
|
47
|
+
marginTop: 16,
|
|
48
|
+
padding: 16,
|
|
49
|
+
background: "#e6f7ff",
|
|
50
|
+
border: "1px solid #91d5ff"
|
|
51
|
+
}, children: [_jsx(Title, { level: 5, children: "Use Cases:" }), _jsxs("ul", { style: { marginBottom: 0 }, children: [_jsxs("li", { children: [_jsx("strong", { children: "Headless Forms:" }), " Build custom UI m\xE0 kh\xF4ng b\u1ECB constraint b\u1EDFi Form component"] }), _jsxs("li", { children: [_jsx("strong", { children: "State Manager:" }), " S\u1EED d\u1EE5ng nh\u01B0 lightweight state manager v\u1EDBi validation support"] }), _jsxs("li", { children: [_jsx("strong", { children: "Multi-step Forms:" }), " Qu\u1EA3n l\xFD form state across multiple steps/pages"] }), _jsxs("li", { children: [_jsx("strong", { children: "Form Wizards:" }), " Share form instance between different wizard steps"] }), _jsxs("li", { children: [_jsx("strong", { children: "Custom Integrations:" }), " Integrate v\u1EDBi third-party UI libraries"] })] })] })] });
|
|
42
52
|
}
|
|
43
53
|
export {
|
|
44
54
|
TestCase6_FormWithoutWrapper as default
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function TestCase7_SetValueInEffect(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Badge, Button, Card, Space, Tag, Typography } from "antd";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import Form from "../../providers/Form";
|
|
5
|
+
const { Title, Paragraph, Text } = Typography;
|
|
6
|
+
function FormWithTimeout({ delay = 2e3 }) {
|
|
7
|
+
const [form] = Form.useForm();
|
|
8
|
+
const [logs, setLogs] = useState([]);
|
|
9
|
+
const [timeoutTriggered, setTimeoutTriggered] = useState(false);
|
|
10
|
+
const addLog = (message) => {
|
|
11
|
+
const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString();
|
|
12
|
+
setLogs((prev) => [...prev, `[${timestamp}] ${message}`]);
|
|
13
|
+
};
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
addLog(`\u23F0 setTimeout created with ${delay}ms delay`);
|
|
16
|
+
const timer = setTimeout(() => {
|
|
17
|
+
addLog("\u{1F514} Timeout triggered! Calling setFieldValue...");
|
|
18
|
+
form.setFieldValue("username", "admin_from_timeout");
|
|
19
|
+
form.setFieldValue("email", "admin@timeout.com");
|
|
20
|
+
form.setFieldValue("status", "loaded");
|
|
21
|
+
addLog("\u2705 setFieldValue completed successfully");
|
|
22
|
+
setTimeoutTriggered(true);
|
|
23
|
+
}, delay);
|
|
24
|
+
return () => {
|
|
25
|
+
clearTimeout(timer);
|
|
26
|
+
addLog("\u{1F9F9} Timeout cleared (cleanup)");
|
|
27
|
+
};
|
|
28
|
+
}, [delay, form]);
|
|
29
|
+
const handleManualSet = () => {
|
|
30
|
+
form.setFieldValue("username", "manual_user");
|
|
31
|
+
form.setFieldValue("email", "manual@example.com");
|
|
32
|
+
form.setFieldValue("status", "manual");
|
|
33
|
+
addLog("\u270B Manual setValue called");
|
|
34
|
+
};
|
|
35
|
+
const handleReset = () => {
|
|
36
|
+
form.resetFields();
|
|
37
|
+
addLog("\u{1F504} Form reset");
|
|
38
|
+
};
|
|
39
|
+
return _jsxs("div", { style: { display: "flex", gap: 24 }, children: [_jsx("div", { style: { flex: 1 }, children: _jsx(Card, { title: "\u{1F4DD} Event Logs", extra: _jsx(Tag, { color: timeoutTriggered ? "success" : "processing", children: timeoutTriggered ? "Timeout Triggered" : "Waiting..." }), children: _jsx("div", { style: {
|
|
40
|
+
height: 400,
|
|
41
|
+
overflow: "auto",
|
|
42
|
+
background: "#fafafa",
|
|
43
|
+
padding: 12,
|
|
44
|
+
fontFamily: "monospace",
|
|
45
|
+
fontSize: 12,
|
|
46
|
+
border: "1px solid #d9d9d9"
|
|
47
|
+
}, children: logs.length === 0 ? _jsx(Text, { type: "secondary", children: "No logs yet..." }) : logs.map((log, index) => _jsx("div", { style: { marginBottom: 4 }, children: log }, index)) }) }) }), _jsx("div", { style: { flex: 1 }, children: _jsx(Card, { title: "\u{1F4CB} Form", extra: _jsxs(Space, { children: [_jsx(Button, { onClick: handleManualSet, size: "small", children: "Manual Set" }), _jsx(Button, { onClick: handleReset, size: "small", danger: true, children: "Reset" })] }), children: _jsxs(Form, { form, initialValues: { username: "", email: "", status: "pending" }, onFinish: (values) => {
|
|
48
|
+
addLog(`\u{1F4E4} Form submitted: ${JSON.stringify(values)}`);
|
|
49
|
+
}, children: [_jsx(Form.Item, { name: "username", label: "Username", children: _jsx("input", { type: "text", placeholder: "Username", style: { padding: 8, width: "100%" } }) }), _jsx(Form.Item, { name: "email", label: "Email", children: _jsx("input", { type: "email", placeholder: "Email", style: { padding: 8, width: "100%" } }) }), _jsx(Form.Item, { name: "status", label: "Status", children: _jsx("input", { type: "text", placeholder: "Status", style: { padding: 8, width: "100%" } }) }), _jsx(Button, { type: "primary", htmlType: "submit", children: "Submit Form" })] }) }) })] });
|
|
50
|
+
}
|
|
51
|
+
function TestCase7_SetValueInEffect() {
|
|
52
|
+
const [mounted, setMounted] = useState(true);
|
|
53
|
+
const [delay, setDelay] = useState(2e3);
|
|
54
|
+
const [key, setKey] = useState(0);
|
|
55
|
+
const handleRemount = () => {
|
|
56
|
+
setMounted(false);
|
|
57
|
+
setTimeout(() => {
|
|
58
|
+
setKey((k) => k + 1);
|
|
59
|
+
setMounted(true);
|
|
60
|
+
}, 100);
|
|
61
|
+
};
|
|
62
|
+
return _jsxs("div", { style: { padding: 24 }, children: [_jsx(Title, { level: 3, children: "TestCase7: Set Value in useEffect with Timeout" }), _jsxs(Paragraph, { children: [_jsx(Text, { strong: true, children: "Test Scenario:" }), " setValue \u0111\u01B0\u1EE3c g\u1ECDi trong useEffect v\u1EDBi setTimeout"] }), _jsx(Paragraph, { children: _jsx(Text, { type: "secondary", children: "Test case n\xE0y verify r\u1EB1ng form instance methods v\u1EABn ho\u1EA1t \u0111\u1ED9ng b\xECnh th\u01B0\u1EDDng khi \u0111\u01B0\u1EE3c g\u1ECDi t\u1EEB async callbacks (setTimeout). Timeout \u0111\u01B0\u1EE3c t\u1EA1o ngay khi component mount, v\xE0 sau khi trigger, setValue ph\u1EA3i ho\u1EA1t \u0111\u1ED9ng ch\xEDnh x\xE1c." }) }), _jsxs(Card, { title: "\u{1F3AE} Control Panel", style: { marginBottom: 24 }, extra: _jsx(Badge, { status: mounted ? "success" : "error", text: mounted ? "Mounted" : "Unmounted" }), children: [_jsxs(Space, { wrap: true, children: [_jsxs("div", { children: [_jsx(Text, { strong: true, children: "Delay (ms): " }), _jsx("input", { type: "number", value: delay, onChange: (e) => setDelay(Number(e.target.value)), style: { padding: 4, width: 100 }, min: 500, max: 1e4, step: 500 })] }), _jsx(Button, { onClick: handleRemount, type: "primary", children: "Remount Component (Reset Test)" }), mounted && _jsx(Button, { onClick: () => setMounted(false), danger: true, children: "Unmount (Test Cleanup)" }), !mounted && _jsx(Button, { onClick: () => setMounted(true), type: "primary", children: "Mount" })] }), _jsx("div", { style: {
|
|
63
|
+
marginTop: 16,
|
|
64
|
+
padding: 12,
|
|
65
|
+
background: "#fff7e6",
|
|
66
|
+
border: "1px solid #ffd591"
|
|
67
|
+
}, children: _jsxs(Text, { children: ["\u{1F4A1} ", _jsx("strong", { children: "Tip:" }), " Try unmounting the component before timeout triggers to test cleanup behavior"] }) })] }), mounted && _jsx(FormWithTimeout, { delay }, key), _jsxs("div", { style: { marginTop: 24, padding: 16, background: "#f5f5f5" }, children: [_jsx(Title, { level: 5, children: "Expected Results:" }), _jsxs("ul", { children: [_jsxs("li", { children: ['\u2705 Component mounts \u2192 Logs show "setTimeout created with ', delay, 'ms delay"'] }), _jsxs("li", { children: ["\u2705 After ", delay, 'ms \u2192 Logs show "Timeout triggered!" and "setFieldValue completed successfully"'] }), _jsx("li", { children: '\u2705 Form values update to: username="admin_from_timeout", etc.' }), _jsx("li", { children: '\u2705 Click "Manual Set" \u2192 Values change immediately' }), _jsx("li", { children: '\u2705 Click "Reset" \u2192 Form resets to initialValues' }), _jsx("li", { children: '\u2705 Click "Unmount" before timeout \u2192 Logs show cleanup, no error when timeout would trigger' }), _jsx("li", { children: "\u2705 Change delay and remount \u2192 New timeout with new delay" }), _jsx("li", { children: "\u2705 Submit form after timeout \u2192 Form submission works with updated values" })] })] }), _jsxs("div", { style: {
|
|
68
|
+
marginTop: 16,
|
|
69
|
+
padding: 16,
|
|
70
|
+
background: "#e6f7ff",
|
|
71
|
+
border: "1px solid #91d5ff"
|
|
72
|
+
}, children: [_jsx(Title, { level: 5, children: "Use Cases:" }), _jsxs("ul", { style: { marginBottom: 0 }, children: [_jsxs("li", { children: [_jsx("strong", { children: "Async Data Loading:" }), " Load form data from API after component mount"] }), _jsxs("li", { children: [_jsx("strong", { children: "Delayed Initialization:" }), " Initialize form values after some delay"] }), _jsxs("li", { children: [_jsx("strong", { children: "Auto-save:" }), " Save form values periodically using setInterval"] }), _jsxs("li", { children: [_jsx("strong", { children: "Debounced Updates:" }), " Update form values with debounce/throttle"] }), _jsxs("li", { children: [_jsx("strong", { children: "WebSocket Updates:" }), " Update form when receiving real-time data"] })] })] })] });
|
|
73
|
+
}
|
|
74
|
+
export {
|
|
75
|
+
TestCase7_SetValueInEffect as default
|
|
76
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function TestCase8_TypeSafetyImprovements(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Badge, Button, Card, Space, Table, Typography } from "antd";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import Form from "../../providers/Form";
|
|
5
|
+
const { Title, Paragraph, Text } = Typography;
|
|
6
|
+
function TestCase8_TypeSafetyImprovements() {
|
|
7
|
+
const [form] = Form.useForm();
|
|
8
|
+
const [logs, setLogs] = useState([]);
|
|
9
|
+
const [fieldErrors, setFieldErrors] = useState([]);
|
|
10
|
+
const addLog = (message) => {
|
|
11
|
+
const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString();
|
|
12
|
+
setLogs((prev) => [...prev, `[${timestamp}] ${message}`]);
|
|
13
|
+
};
|
|
14
|
+
const handleSetTypedFields = () => {
|
|
15
|
+
form.setFieldValue("username", "admin");
|
|
16
|
+
form.setFieldValue("email", "admin@test.com");
|
|
17
|
+
form.setFieldValue("age", 25);
|
|
18
|
+
addLog("\u2705 Set typed fields: username, email, age");
|
|
19
|
+
};
|
|
20
|
+
const handleSetDynamicFields = () => {
|
|
21
|
+
form.setFieldValue("dynamicField1", "value1");
|
|
22
|
+
form.setFieldValue("dynamicField2", "value2");
|
|
23
|
+
addLog("\u2705 Set dynamic fields: dynamicField1, dynamicField2");
|
|
24
|
+
};
|
|
25
|
+
const handleGetValues = () => {
|
|
26
|
+
const username = form.getFieldValue("username");
|
|
27
|
+
const email = form.getFieldValue("email");
|
|
28
|
+
const age = form.getFieldValue("age");
|
|
29
|
+
const dynamic = form.getFieldValue("dynamicField1");
|
|
30
|
+
addLog(`\u{1F4D6} Get values: username=${username}, email=${email}, age=${age}, dynamic=${dynamic}`);
|
|
31
|
+
};
|
|
32
|
+
const handleGetFieldErrors = () => {
|
|
33
|
+
const errors = form.getFieldErrors(["username", "email", "age"]);
|
|
34
|
+
setFieldErrors(errors);
|
|
35
|
+
addLog(`\u{1F50D} getFieldErrors returned ${errors.length} results (check table)`);
|
|
36
|
+
};
|
|
37
|
+
const handleSubmitAsync = async () => {
|
|
38
|
+
try {
|
|
39
|
+
addLog("\u{1F680} Calling submitAsync...");
|
|
40
|
+
const result = await form.submitAsync();
|
|
41
|
+
addLog(`\u2705 submitAsync completed. Result type: ${typeof result} (should be undefined)`);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
addLog(`\u274C submitAsync error: ${error}`);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const handleGetMultipleValues = () => {
|
|
47
|
+
const values = form.getFieldValues(["username", "email", "age"]);
|
|
48
|
+
addLog(`\u{1F4DA} getFieldValues: ${JSON.stringify(values)}`);
|
|
49
|
+
};
|
|
50
|
+
const clearLogs = () => {
|
|
51
|
+
setLogs([]);
|
|
52
|
+
};
|
|
53
|
+
const clearErrors = () => {
|
|
54
|
+
setFieldErrors([]);
|
|
55
|
+
};
|
|
56
|
+
const errorColumns = [
|
|
57
|
+
{
|
|
58
|
+
title: "Field Name",
|
|
59
|
+
dataIndex: "name",
|
|
60
|
+
key: "name",
|
|
61
|
+
render: (text) => _jsx(Text, { code: true, children: text })
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
title: "Errors Count",
|
|
65
|
+
dataIndex: "errors",
|
|
66
|
+
key: "count",
|
|
67
|
+
render: (errors) => _jsx(Badge, { count: errors.length, showZero: true, color: errors.length > 0 ? "red" : "green" })
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
title: "Error Details",
|
|
71
|
+
dataIndex: "errors",
|
|
72
|
+
key: "details",
|
|
73
|
+
render: (errors) => errors.length > 0 ? _jsx("div", { children: errors.map((err, idx) => _jsx("div", { style: { fontSize: 12 }, children: err.message }, idx)) }) : _jsx(Text, { type: "secondary", children: "No errors" })
|
|
74
|
+
}
|
|
75
|
+
];
|
|
76
|
+
return _jsxs("div", { style: { padding: 24 }, children: [_jsx(Title, { level: 3, children: "TestCase8: Type Safety Improvements" }), _jsxs(Paragraph, { children: [_jsx(Text, { strong: true, children: "Test Focus:" }), " Verify type improvements trong PublicFormInstance"] }), _jsx(Paragraph, { children: _jsxs(Text, { type: "secondary", children: ["Test case n\xE0y validate c\xE1c type changes m\u1EDBi: dynamic field names ", `(keyof T |
|
|
77
|
+
(string & {}))`, ", getFieldErrors return type, v\xE0 submitAsync Promise", "<", "void", ">", "."] }) }), _jsx(Card, { title: "\u{1F4DD} Type Information", style: { marginBottom: 24 }, children: _jsxs("div", { style: { background: "#f5f5f5", padding: 16, fontFamily: "monospace", fontSize: 12 }, children: [_jsxs("div", { children: [_jsx(Text, { strong: true, children: "Form Type:" }), " ", _jsxs(Text, { code: true, children: ["StrictFormValues = ", `{ username: string; email: string; age: number }`] })] }), _jsx("div", { style: { marginTop: 8 }, children: _jsx(Text, { strong: true, children: "New Types:" }) }), _jsxs("ul", { style: { marginLeft: 20, marginTop: 4 }, children: [_jsxs("li", { children: [_jsx(Text, { code: true, children: `name: keyof T | (string & {})` }), " - Supports dynamic fields"] }), _jsx("li", { children: _jsx(Text, { code: true, children: `getFieldErrors: (...) => Array<{ name: string; errors: FormFieldError[] }>` }) }), _jsx("li", { children: _jsx(Text, { code: true, children: `submitAsync: (...) => Promise<void>` }) })] })] }) }), _jsxs("div", { style: { display: "flex", gap: 24 }, children: [_jsx("div", { style: { flex: 1 }, children: _jsx(Card, { title: "\u{1F3AE} Test Controls", children: _jsxs(Space, { direction: "vertical", style: { width: "100%" }, size: "middle", children: [_jsxs("div", { children: [_jsx(Text, { strong: true, children: "1. Typed Field Names:" }), _jsx("div", { style: { marginTop: 8 }, children: _jsx(Button, { onClick: handleSetTypedFields, type: "primary", block: true, children: "Set Typed Fields (username, email, age)" }) })] }), _jsxs("div", { children: [_jsx(Text, { strong: true, children: "2. Dynamic Field Names:" }), _jsx("div", { style: { marginTop: 8 }, children: _jsx(Button, { onClick: handleSetDynamicFields, block: true, children: "Set Dynamic Fields (not in type)" }) })] }), _jsxs("div", { children: [_jsx(Text, { strong: true, children: "3. Get Values:" }), _jsx("div", { style: { marginTop: 8 }, children: _jsx(Button, { onClick: handleGetValues, block: true, children: "Get All Field Values" }) })] }), _jsxs("div", { children: [_jsx(Text, { strong: true, children: "4. Get Field Errors:" }), _jsx("div", { style: { marginTop: 8 }, children: _jsx(Button, { onClick: handleGetFieldErrors, block: true, children: "Get Field Errors (New Type)" }) })] }), _jsxs("div", { children: [_jsx(Text, { strong: true, children: "5. Multiple Values:" }), _jsx("div", { style: { marginTop: 8 }, children: _jsx(Button, { onClick: handleGetMultipleValues, block: true, children: "Get Multiple Values" }) })] }), _jsxs("div", { children: [_jsx(Text, { strong: true, children: "6. Submit Async:" }), _jsx("div", { style: { marginTop: 8 }, children: _jsxs(Button, { onClick: handleSubmitAsync, type: "dashed", block: true, children: ["Test submitAsync (Promise", "<", "void", ">", ")"] }) })] }), _jsxs(Space, { style: { width: "100%", justifyContent: "space-between" }, children: [_jsx(Button, { onClick: clearLogs, size: "small", children: "Clear Logs" }), _jsx(Button, { onClick: clearErrors, size: "small", children: "Clear Errors" })] })] }) }) }), _jsx("div", { style: { flex: 1 }, children: _jsx(Card, { title: "\u{1F4CB} Action Logs", children: _jsx("div", { style: {
|
|
78
|
+
height: 400,
|
|
79
|
+
overflow: "auto",
|
|
80
|
+
background: "#fafafa",
|
|
81
|
+
padding: 12,
|
|
82
|
+
fontFamily: "monospace",
|
|
83
|
+
fontSize: 12,
|
|
84
|
+
border: "1px solid #d9d9d9"
|
|
85
|
+
}, children: logs.length === 0 ? _jsx(Text, { type: "secondary", children: "No logs yet..." }) : logs.map((log, index) => _jsx("div", { style: { marginBottom: 4 }, children: log }, index)) }) }) })] }), _jsx(Card, { title: "\u{1F4CB} Form", style: { marginTop: 24 }, children: _jsxs(Form, { form, initialValues: { username: "", email: "", age: 0 }, onFinish: (values) => {
|
|
86
|
+
addLog(`\u2705 Form submitted: ${JSON.stringify(values)}`);
|
|
87
|
+
}, children: [_jsx(Form.Item, { name: "username", label: "Username", rules: [{ required: true, message: "Username is required" }], children: _jsx("input", { type: "text", placeholder: "Username", style: { padding: 8, width: "100%" } }) }), _jsx(Form.Item, { name: "email", label: "Email", rules: [
|
|
88
|
+
{ required: true, message: "Email is required" },
|
|
89
|
+
{ isEmail: true, message: "Invalid email format" }
|
|
90
|
+
], children: _jsx("input", { type: "email", placeholder: "Email", style: { padding: 8, width: "100%" } }) }), _jsx(Form.Item, { name: "age", label: "Age", rules: [
|
|
91
|
+
{ required: true, message: "Age is required" },
|
|
92
|
+
{ min: 18, message: "Must be at least 18" }
|
|
93
|
+
], children: _jsx("input", { type: "number", placeholder: "Age", style: { padding: 8, width: "100%" } }) }), _jsx(Button, { type: "primary", htmlType: "submit", children: "Submit Form" })] }) }), fieldErrors.length > 0 && _jsxs(Card, { title: "\u{1F50D} Field Errors (getFieldErrors Result)", style: { marginTop: 24 }, children: [_jsx(Table, { dataSource: fieldErrors, columns: errorColumns, rowKey: "name", pagination: false, size: "small" }), _jsx("div", { style: { marginTop: 16, padding: 12, background: "#f0f0f0" }, children: _jsxs(Text, { type: "secondary", children: ["\u2705 Return type:", " ", _jsx(Text, { code: true, children: `Array<{ name: string; errors: FormFieldError[] }>` })] }) })] }), _jsxs("div", { style: { marginTop: 24, padding: 16, background: "#f5f5f5" }, children: [_jsx(Title, { level: 5, children: "Expected Results:" }), _jsxs("ul", { children: [_jsx("li", { children: '\u2705 Click "Set Typed Fields" \u2192 Values set for username, email, age' }), _jsx("li", { children: '\u2705 Click "Set Dynamic Fields" \u2192 Dynamic fields work (not in StrictFormValues type)' }), _jsx("li", { children: '\u2705 Click "Get All Field Values" \u2192 Shows all field values including dynamic' }), _jsx("li", { children: '\u2705 Click "Get Field Errors" \u2192 Returns array with name and errors properties' }), _jsxs("li", { children: ['\u2705 Click "Get Multiple Values" \u2192 Returns array of ', `{ name, value }`, " objects"] }), _jsxs("li", { children: ['\u2705 Click "Test submitAsync" \u2192 Returns Promise', "<", "void", ">", " (result is undefined)"] }), _jsx("li", { children: "\u2705 Submit form with empty fields \u2192 Validation errors appear in getFieldErrors" }), _jsx("li", { children: "\u2705 Fill form and submit \u2192 onFinish triggered, no validation errors" })] })] }), _jsxs("div", { style: {
|
|
94
|
+
marginTop: 16,
|
|
95
|
+
padding: 16,
|
|
96
|
+
background: "#e6f7ff",
|
|
97
|
+
border: "1px solid #91d5ff"
|
|
98
|
+
}, children: [_jsx(Title, { level: 5, children: "Type Improvements Summary:" }), _jsxs("ul", { style: { marginBottom: 0 }, children: [_jsxs("li", { children: [_jsx("strong", { children: "Dynamic Field Names:" }), " ", _jsx(Text, { code: true, children: `keyof T | (string & {})` }), " cho ph\xE9p s\u1EED d\u1EE5ng \u0111\u1ED9ng field names kh\xF4ng c\xF3 trong type"] }), _jsxs("li", { children: [_jsx("strong", { children: "Proper getFieldErrors Type:" }), " Tr\u1EA3 v\u1EC1 array thay v\xEC Record, ch\xEDnh x\xE1c v\u1EDBi implementation"] }), _jsxs("li", { children: [_jsx("strong", { children: "submitAsync Type:" }), " ", _jsx(Text, { code: true, children: `Promise<void>` }), " thay v\xEC", " ", _jsx(Text, { code: true, children: `Promise<any>` }), " for better type safety"] }), _jsxs("li", { children: [_jsx("strong", { children: "Consistency:" }), " T\u1EA5t c\u1EA3 methods gi\u1EDD support c\xF9ng type pattern cho field names"] })] })] })] });
|
|
99
|
+
}
|
|
100
|
+
export {
|
|
101
|
+
TestCase8_TypeSafetyImprovements as default
|
|
102
|
+
};
|
|
@@ -8,5 +8,7 @@
|
|
|
8
8
|
* - useForm trả về [instance, isReady] tuple
|
|
9
9
|
* - useFormUtils hook mới
|
|
10
10
|
* - Form instance hoạt động độc lập như state manager
|
|
11
|
+
* - setValue trong useEffect với setTimeout (async behavior)
|
|
12
|
+
* - Type safety improvements (dynamic field names, getFieldErrors type, submitAsync type)
|
|
11
13
|
*/
|
|
12
14
|
export default function FormInstanceTestSuite(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -7,6 +7,8 @@ import TestCase3_SafeFormMethods from "./TestCase3_SafeFormMethods";
|
|
|
7
7
|
import TestCase4_UseFormStatusFlag from "./TestCase4_UseFormStatusFlag";
|
|
8
8
|
import TestCase5_UseFormUtils from "./TestCase5_UseFormUtils";
|
|
9
9
|
import TestCase6_FormWithoutWrapper from "./TestCase6_FormWithoutWrapper";
|
|
10
|
+
import TestCase7_SetValueInEffect from "./TestCase7_SetValueInEffect";
|
|
11
|
+
import TestCase8_TypeSafetyImprovements from "./TestCase8_TypeSafetyImprovements";
|
|
10
12
|
const { Title, Paragraph, Text } = Typography;
|
|
11
13
|
function FormInstanceTestSuite() {
|
|
12
14
|
const [activeTab, setActiveTab] = useState("1");
|
|
@@ -40,9 +42,24 @@ function FormInstanceTestSuite() {
|
|
|
40
42
|
key: "6",
|
|
41
43
|
label: "TestCase6: Form Without Wrapper",
|
|
42
44
|
children: _jsx(TestCase6_FormWithoutWrapper, {})
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
key: "7",
|
|
48
|
+
label: "TestCase7: Set Value in Effect",
|
|
49
|
+
children: _jsx(TestCase7_SetValueInEffect, {})
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
key: "8",
|
|
53
|
+
label: "TestCase8: Type Safety",
|
|
54
|
+
children: _jsx(TestCase8_TypeSafetyImprovements, {})
|
|
43
55
|
}
|
|
44
56
|
];
|
|
45
|
-
return _jsxs("div", { style: { padding: 24, maxWidth: 1400, margin: "0 auto" }, children: [_jsxs("div", { style: { marginBottom: 32 }, children: [_jsx(Title, { level: 2, children: "\u{1F9EA} Form Instance Test Suite" }), _jsx(Paragraph, { children: _jsx(Text, { children: "Test suite cho breaking changes v\xE0 new features trong version m\u1EDBi. M\u1ED7i test case cover m\u1ED9t aspect c\u1EE5 th\u1EC3 c\u1EE7a Form Instance behavior." }) })] }), _jsxs("div", { style: {
|
|
57
|
+
return _jsxs("div", { style: { padding: 24, maxWidth: 1400, margin: "0 auto" }, children: [_jsxs("div", { style: { marginBottom: 32 }, children: [_jsx(Title, { level: 2, children: "\u{1F9EA} Form Instance Test Suite" }), _jsx(Paragraph, { children: _jsx(Text, { children: "Test suite cho breaking changes v\xE0 new features trong version m\u1EDBi. M\u1ED7i test case cover m\u1ED9t aspect c\u1EE5 th\u1EC3 c\u1EE7a Form Instance behavior." }) })] }), _jsxs("div", { style: {
|
|
58
|
+
marginBottom: 24,
|
|
59
|
+
padding: 16,
|
|
60
|
+
background: "#e6f7ff",
|
|
61
|
+
border: "1px solid #91d5ff"
|
|
62
|
+
}, children: [_jsx(Title, { level: 4, children: "\u{1F4CB} Breaking Changes & New Features:" }), _jsxs("ul", { style: { marginBottom: 0 }, children: [_jsxs("li", { children: [_jsx(Text, { strong: true, children: 'Form Prop "form":' }), " Form component c\xF3 th\u1EC3 nh\u1EADn", " ", _jsx(Text, { code: true, children: "form" }), " instance thay v\xEC ", _jsx(Text, { code: true, children: "formName" })] }), _jsxs("li", { children: [_jsx(Text, { strong: true, children: "Auto-generate FormName:" }), " Form.useForm() t\u1EF1 \u0111\u1ED9ng t\u1EA1o UUID n\u1EBFu kh\xF4ng truy\u1EC1n tham s\u1ED1"] }), _jsxs("li", { children: [_jsx(Text, { strong: true, children: "Safe Methods:" }), " Form methods c\xF3 th\u1EC3 g\u1ECDi an to\xE0n tr\u01B0\u1EDBc khi Form mount (proxy instance)"] }), _jsxs("li", { children: [_jsx(Text, { strong: true, children: "useForm Tuple:" }), " useForm() tr\u1EA3 v\u1EC1", " ", _jsx(Text, { code: true, children: "[instance, isReady]" }), " thay v\xEC ch\u1EC9", " ", _jsx(Text, { code: true, children: "[instance]" })] }), _jsxs("li", { children: [_jsx(Text, { strong: true, children: "useFormUtils Hook:" }), " Hook m\u1EDBi cho ph\xE9p thao t\xE1c form t\u1EEB b\xEAn ngo\xE0i component"] }), _jsxs("li", { children: [_jsx(Text, { strong: true, children: "Headless Forms:" }), " Form instance ho\u1EA1t \u0111\u1ED9ng \u0111\u1ED9c l\u1EADp nh\u01B0 state manager"] }), _jsxs("li", { children: [_jsx(Text, { strong: true, children: "Async Behavior:" }), " Form methods ho\u1EA1t \u0111\u1ED9ng v\u1EDBi async callbacks (setTimeout, Promise, etc.)"] }), _jsxs("li", { children: [_jsx(Text, { strong: true, children: "Type Improvements:" }), " Dynamic field names", " ", _jsx(Text, { code: true, children: `keyof T | (string & {})` }), ", proper getFieldErrors return type, submitAsync ", _jsx(Text, { code: true, children: "Promise<void>" })] })] })] }), _jsx(Tabs, { activeKey: activeTab, onChange: setActiveTab, items, size: "large", type: "card" }), _jsxs("div", { style: { marginTop: 32, padding: 16, background: "#f5f5f5" }, children: [_jsx(Title, { level: 5, children: "\u{1F4A1} Testing Tips:" }), _jsxs("ul", { style: { marginBottom: 0 }, children: [_jsx("li", { children: "M\u1EDF Browser Console \u0111\u1EC3 xem detailed logs v\xE0 warnings" }), _jsx("li", { children: "M\u1ED7i test case c\xF3 Expected Results section \u0111\u1EC3 verify behavior" }), _jsx("li", { children: "Test cases c\xF3 th\u1EC3 test \u0111\u1ED9c l\u1EADp, kh\xF4ng \u1EA3nh h\u01B0\u1EDFng l\u1EABn nhau" }), _jsx("li", { children: "Ch\xFA \xFD c\xE1c warning messages khi form methods \u0111\u01B0\u1EE3c g\u1ECDi sai timing" }), _jsx("li", { children: "Use Cases sections gi\u1EA3i th\xEDch khi n\xE0o n\xEAn d\xF9ng feature \u0111\xF3" })] })] })] });
|
|
46
63
|
}
|
|
47
64
|
export {
|
|
48
65
|
FormInstanceTestSuite as default
|
package/dist/types/public.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { SUBMIT_STATE } from "../constants/form";
|
|
2
|
+
import { OnChangeOptions } from "../hooks/useFormItemControl";
|
|
3
|
+
import { SetFieldValueOptions } from "../providers/Form";
|
|
2
4
|
import type { GetConstantType } from "./util";
|
|
3
5
|
export type FormValues<T = any> = T;
|
|
4
6
|
export interface FormFieldError {
|
|
@@ -33,21 +35,24 @@ export interface PublicFormInstance<T = any> {
|
|
|
33
35
|
formName: string;
|
|
34
36
|
resetFields: (values?: Partial<T>) => void;
|
|
35
37
|
submit: (values?: T) => void;
|
|
36
|
-
submitAsync: (values?: T) => Promise<
|
|
37
|
-
setFieldValue: (name: keyof T &
|
|
38
|
-
setFieldValues: (values: Partial<T>, options?:
|
|
39
|
-
getFieldValue: (name: keyof T &
|
|
40
|
-
getFieldValues: (names?: Array<keyof T &
|
|
38
|
+
submitAsync: (values?: T) => Promise<void>;
|
|
39
|
+
setFieldValue: (name: keyof T | (string & {}), value: any, options?: SetFieldValueOptions) => void;
|
|
40
|
+
setFieldValues: (values: Partial<T>, options?: OnChangeOptions) => void;
|
|
41
|
+
getFieldValue: (name: keyof T | (string & {})) => any;
|
|
42
|
+
getFieldValues: (names?: Array<keyof T | (string & {})>) => Array<{
|
|
41
43
|
name: string;
|
|
42
44
|
value: any;
|
|
43
45
|
}>;
|
|
44
|
-
getFieldErrors: (names?: Array<keyof T &
|
|
45
|
-
|
|
46
|
+
getFieldErrors: (names?: Array<keyof T | (string & {})>) => Array<{
|
|
47
|
+
name: string;
|
|
48
|
+
errors: FormFieldError[];
|
|
49
|
+
}>;
|
|
50
|
+
setFieldFocus: (name: keyof T | (string & {})) => void;
|
|
46
51
|
}
|
|
47
52
|
export interface UseFormItemProps<T = any> {
|
|
48
53
|
formName?: string;
|
|
49
54
|
form?: PublicFormInstance<T>;
|
|
50
|
-
name?: keyof T &
|
|
55
|
+
name?: keyof T | (string & {});
|
|
51
56
|
initialValue?: any;
|
|
52
57
|
formItemId?: string;
|
|
53
58
|
rules?: ValidationRule<any, T>[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-form-manage",
|
|
3
|
-
"version": "1.1.0-beta.
|
|
3
|
+
"version": "1.1.0-beta.3",
|
|
4
4
|
"description": "Lightweight React form management with list and listener support.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -54,4 +54,4 @@
|
|
|
54
54
|
"typescript": "^5.9.3",
|
|
55
55
|
"vite": "^6.0.1"
|
|
56
56
|
}
|
|
57
|
-
}
|
|
57
|
+
}
|
|
@@ -5,12 +5,12 @@ const { Title, Paragraph, Text } = Typography;
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* TestCase1: Form with Instance Prop
|
|
8
|
-
*
|
|
8
|
+
*
|
|
9
9
|
* Mô tả:
|
|
10
10
|
* - Test breaking change: Form component nhận prop "form" thay vì chỉ có "formName"
|
|
11
11
|
* - Form instance được tạo trước bằng Form.useForm()
|
|
12
12
|
* - Các methods được bind vào instance trước khi Form mount
|
|
13
|
-
*
|
|
13
|
+
*
|
|
14
14
|
* Expected Behavior:
|
|
15
15
|
* - Có thể gọi form.setFieldValue() trước khi Form render
|
|
16
16
|
* - Form sử dụng formName từ instance
|
|
@@ -45,15 +45,16 @@ export default function TestCase1_FormWithInstance() {
|
|
|
45
45
|
return (
|
|
46
46
|
<div style={{ padding: 24 }}>
|
|
47
47
|
<Title level={3}>TestCase1: Form with Instance Prop</Title>
|
|
48
|
-
|
|
48
|
+
|
|
49
49
|
<Paragraph>
|
|
50
|
-
<Text strong>Breaking Change:</Text> Form component có thể nhận prop
|
|
50
|
+
<Text strong>Breaking Change:</Text> Form component có thể nhận prop{" "}
|
|
51
|
+
<Text code>form</Text> thay vì <Text code>formName</Text>
|
|
51
52
|
</Paragraph>
|
|
52
53
|
|
|
53
54
|
<Paragraph>
|
|
54
55
|
<Text type="secondary">
|
|
55
|
-
Form instance được tạo bằng Form.useForm() và truyền vào Form
|
|
56
|
-
Instance này có thể được sử dụng trước khi Form mount.
|
|
56
|
+
Form instance được tạo bằng Form.useForm() và truyền vào Form
|
|
57
|
+
component. Instance này có thể được sử dụng trước khi Form mount.
|
|
57
58
|
</Text>
|
|
58
59
|
</Paragraph>
|
|
59
60
|
|
|
@@ -100,7 +101,10 @@ export default function TestCase1_FormWithInstance() {
|
|
|
100
101
|
<div style={{ marginTop: 24, padding: 16, background: "#f5f5f5" }}>
|
|
101
102
|
<Title level={5}>Expected Results:</Title>
|
|
102
103
|
<ul>
|
|
103
|
-
<li
|
|
104
|
+
<li>
|
|
105
|
+
✅ Click "Set Values Before Mount" → Values được set và log ra
|
|
106
|
+
console
|
|
107
|
+
</li>
|
|
104
108
|
<li>✅ Click "Get Current Values" → Values hiện tại được log ra</li>
|
|
105
109
|
<li>✅ Form render với values đã set</li>
|
|
106
110
|
<li>✅ Click "Reset Form" → Form reset về initialValues</li>
|