react-form-manage 1.0.8-beta.26 → 1.0.8-beta.28
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 +35 -0
- package/dist/constants/form.d.ts +1 -1
- package/dist/hooks/useFormItemControl.d.ts +5 -2
- package/dist/providers/Form.d.ts +11 -1
- package/dist/providers/Form.js +160 -41
- package/dist/test/TestWatchNormalize.d.ts +3 -0
- package/dist/test/TestWatchNormalize.js +23 -0
- package/dist/test/testSetValue/TestCase10_SetFieldValues_ComplexNested.d.ts +21 -0
- package/dist/test/testSetValue/TestCase10_SetFieldValues_ComplexNested.js +61 -0
- package/dist/test/testSetValue/TestCase1_PlainObjectToPrimitives.d.ts +16 -0
- package/dist/test/testSetValue/TestCase1_PlainObjectToPrimitives.js +18 -0
- package/dist/test/testSetValue/TestCase2_PlainObjectToFormList.d.ts +21 -0
- package/dist/test/testSetValue/TestCase2_PlainObjectToFormList.js +33 -0
- package/dist/test/testSetValue/TestCase3_ArrayNonListenerToPrimitives.d.ts +21 -0
- package/dist/test/testSetValue/TestCase3_ArrayNonListenerToPrimitives.js +26 -0
- package/dist/test/testSetValue/TestCase4_PlainObjectRemovedFields.d.ts +20 -0
- package/dist/test/testSetValue/TestCase4_PlainObjectRemovedFields.js +32 -0
- package/dist/test/testSetValue/TestCase5_FormListRemovedItems.d.ts +22 -0
- package/dist/test/testSetValue/TestCase5_FormListRemovedItems.js +29 -0
- package/dist/test/testSetValue/TestCase6_NestedFormListRemoved.d.ts +28 -0
- package/dist/test/testSetValue/TestCase6_NestedFormListRemoved.js +36 -0
- package/dist/test/testSetValue/TestCase7_SetFieldValues_MixedStructure.d.ts +17 -0
- package/dist/test/testSetValue/TestCase7_SetFieldValues_MixedStructure.js +33 -0
- package/dist/test/testSetValue/TestCase8_SetFieldValues_NestedObject.d.ts +27 -0
- package/dist/test/testSetValue/TestCase8_SetFieldValues_NestedObject.js +57 -0
- package/dist/test/testSetValue/TestCase9_SetFieldValues_MultipleArrays.d.ts +25 -0
- package/dist/test/testSetValue/TestCase9_SetFieldValues_MultipleArrays.js +46 -0
- package/dist/test/testSetValue/index.d.ts +2 -0
- package/dist/test/testSetValue/index.js +28 -0
- package/dist/utils/obj.util.d.ts +28 -0
- package/dist/utils/obj.util.js +43 -0
- package/package.json +1 -1
- package/src/App.tsx +30 -18
- package/src/DEEP_TRIGGER_LOGIC.md +573 -0
- package/src/constants/form.ts +1 -1
- package/src/hooks/useFormItemControl.ts +6 -1
- package/src/providers/Form.tsx +384 -52
- package/src/test/TestWatchNormalize.tsx +32 -0
- package/src/test/testSetValue/TestCase10_SetFieldValues_ComplexNested.tsx +206 -0
- package/src/test/testSetValue/TestCase1_PlainObjectToPrimitives.tsx +72 -0
- package/src/test/testSetValue/TestCase2_PlainObjectToFormList.tsx +114 -0
- package/src/test/testSetValue/TestCase3_ArrayNonListenerToPrimitives.tsx +99 -0
- package/src/test/testSetValue/TestCase4_PlainObjectRemovedFields.tsx +112 -0
- package/src/test/testSetValue/TestCase5_FormListRemovedItems.tsx +119 -0
- package/src/test/testSetValue/TestCase6_NestedFormListRemoved.tsx +185 -0
- package/src/test/testSetValue/TestCase7_SetFieldValues_MixedStructure.tsx +110 -0
- package/src/test/testSetValue/TestCase8_SetFieldValues_NestedObject.tsx +162 -0
- package/src/test/testSetValue/TestCase9_SetFieldValues_MultipleArrays.tsx +173 -0
- package/src/test/testSetValue/index.tsx +100 -0
- package/src/utils/obj.util.ts +109 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.0.8-beta.28] - 2026-02-09
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
- **Deep Trigger Set Value**: Implement recursive value setting with comprehensive listener triggering
|
|
10
|
+
- `setFieldValue` with `deepTrigger: true` option for recursive listener notification
|
|
11
|
+
- Support for plain objects, arrays, FormLists, and nested structures
|
|
12
|
+
- Proper cleanup for removed fields and array items
|
|
13
|
+
- Edge case handling: removed object fields, nested FormList items
|
|
14
|
+
- **Enhanced setFieldValues**: Improved batch value setting logic
|
|
15
|
+
- Automatically detects arrays and uses deep trigger
|
|
16
|
+
- Traverses object structure and stops at arrays
|
|
17
|
+
- Primitives trigger onChange, arrays use handleDeepTriggerSet
|
|
18
|
+
- **Utility Functions**: New path collection utilities in obj.util.ts
|
|
19
|
+
- `getAllPathsIncludingContainers`: Get all paths including intermediate containers
|
|
20
|
+
- `getAllPathsStopAtArray`: Get paths but stop traversal at arrays
|
|
21
|
+
- **Array Index Notation**: Changed from `[index]` to `.index` notation for consistency
|
|
22
|
+
- **Documentation**: Added comprehensive DEEP_TRIGGER_LOGIC.md with 6 detailed case studies
|
|
23
|
+
- **Test Suite**: Created 10 test cases covering all deep trigger scenarios
|
|
24
|
+
|
|
25
|
+
### Technical Details
|
|
26
|
+
|
|
27
|
+
- Deep trigger handles 5 core value types: primitives, plain objects, array listeners (FormList), non-listener arrays, and class instances
|
|
28
|
+
- Hierarchical trigger order: field level → item/index level → property level
|
|
29
|
+
- Recursive cleanup for removed nested structures
|
|
30
|
+
- Options support for controlling dirty state triggering
|
|
31
|
+
|
|
32
|
+
## [1.0.8-beta.27] - 2026-02-04
|
|
33
|
+
|
|
34
|
+
### Features
|
|
35
|
+
|
|
36
|
+
- Add `useWatchNormalize` hook for watching and normalizing form data
|
|
37
|
+
- Support custom normalization functions for automatic value transformation
|
|
38
|
+
- Simplifies data format conversion during form value watching
|
|
39
|
+
|
|
5
40
|
## [1.0.8-beta.25] - 2026-02-04
|
|
6
41
|
|
|
7
42
|
### Changes
|
package/dist/constants/form.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { type RefObject } from "react";
|
|
2
2
|
import type { FormInstance } from "../stores/formStore";
|
|
3
3
|
import type { FormFieldError, SubmitState, ValidationRule } from "../types/public";
|
|
4
|
-
type AnyObject = Record<string, any>;
|
|
5
4
|
interface UseFormItemControlProps {
|
|
6
5
|
formName?: string;
|
|
7
6
|
form?: FormInstance;
|
|
@@ -13,9 +12,13 @@ interface UseFormItemControlProps {
|
|
|
13
12
|
hidden?: boolean;
|
|
14
13
|
collectOnHidden?: boolean;
|
|
15
14
|
}
|
|
15
|
+
export interface OnChangeOptions {
|
|
16
|
+
notTriggerDirty?: boolean;
|
|
17
|
+
initiedData?: boolean;
|
|
18
|
+
}
|
|
16
19
|
export interface UseFormItemControlReturn {
|
|
17
20
|
value: any;
|
|
18
|
-
onChange: (value: any, options?:
|
|
21
|
+
onChange: (value: any, options?: OnChangeOptions) => void;
|
|
19
22
|
state: any;
|
|
20
23
|
errors: FormFieldError[];
|
|
21
24
|
onFocus: () => void;
|
package/dist/providers/Form.d.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import type { ComponentType, FormHTMLAttributes, ReactNode } from "react";
|
|
2
2
|
import FormItem from "../components/Form/FormItem";
|
|
3
|
+
import { OnChangeOptions } from "../hooks/useFormItemControl";
|
|
3
4
|
import type { PublicFormInstance, UseFormItemStateWatchReturn } from "../types/public";
|
|
4
|
-
export type { FormFieldError, SubmitState, UseFormItemStateWatchReturn, ValidationRule
|
|
5
|
+
export type { FormFieldError, SubmitState, UseFormItemStateWatchReturn, ValidationRule } from "../types/public";
|
|
5
6
|
export declare const FormContext: import("react").Context<any>;
|
|
7
|
+
export interface SetFieldValueOptions extends OnChangeOptions {
|
|
8
|
+
deepTrigger?: boolean;
|
|
9
|
+
}
|
|
6
10
|
export interface FormProps<T = any> extends Omit<FormHTMLAttributes<HTMLFormElement>, "onSubmit"> {
|
|
7
11
|
collectHiddenFields?: boolean;
|
|
8
12
|
children: ReactNode;
|
|
@@ -22,6 +26,7 @@ declare namespace Form {
|
|
|
22
26
|
var useForm: typeof import("./Form").useForm;
|
|
23
27
|
var Item: typeof FormItem;
|
|
24
28
|
var useWatch: typeof import("./Form").useWatch;
|
|
29
|
+
var useWatchNormalized: typeof import("./Form").useWatchNormalized;
|
|
25
30
|
var useSubmitDataWatch: typeof import("./Form").useSubmitDataWatch;
|
|
26
31
|
var useFormStateWatch: <T = any>(formNameOrFormInstance?: string | PublicFormInstance<T>) => any;
|
|
27
32
|
var useFormItemStateWatch: <T = any>(nameOrFormItemId: string, formNameOrFormInstance?: string | PublicFormInstance<T>) => UseFormItemStateWatchReturn;
|
|
@@ -30,6 +35,11 @@ export default Form;
|
|
|
30
35
|
export declare function useFormContext(): any;
|
|
31
36
|
export declare function useForm<T = any>(formNameOrFormInstance?: string | PublicFormInstance<T>): PublicFormInstance<T>[];
|
|
32
37
|
export declare function useWatch<T = any>(name: keyof T & string, formNameOrFormInstance?: string | PublicFormInstance<T>): T[keyof T] | undefined;
|
|
38
|
+
export declare function useWatchNormalized<T, TFn extends (v: any) => any>({ name, normalizeFn, formNameOrFormInstance, }: {
|
|
39
|
+
name: keyof T & string;
|
|
40
|
+
normalizeFn: TFn;
|
|
41
|
+
formNameOrFormInstance?: string | PublicFormInstance<T>;
|
|
42
|
+
}): ReturnType<TFn>;
|
|
33
43
|
export declare function useSubmitDataWatch<T = any>({ formNameOrFormInstance, triggerWhenChange, mapFn, }: {
|
|
34
44
|
formNameOrFormInstance?: string | PublicFormInstance<T>;
|
|
35
45
|
triggerWhenChange?: boolean;
|
package/dist/providers/Form.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { cloneDeep, filter, get, isEqual, isNil, isPlainObject, last, set, uniqBy } from "lodash";
|
|
2
|
+
import { cloneDeep, filter, get, isArray, isEqual, isNil, isPlainObject, last, set, uniqBy } from "lodash";
|
|
3
3
|
import { useTask } from "minh-custom-hooks-release";
|
|
4
|
-
import { createContext, useContext, useEffect, useState } from "react";
|
|
4
|
+
import { createContext, useContext, useEffect, useMemo, useState } from "react";
|
|
5
5
|
import { flushSync } from "react-dom";
|
|
6
6
|
import { useShallow } from "zustand/react/shallow";
|
|
7
7
|
import FormItem from "../components/Form/FormItem";
|
|
8
8
|
import { SUBMIT_STATE } from "../constants/form";
|
|
9
9
|
import { useFormStore } from "../stores/formStore";
|
|
10
|
-
import { getAllNoneObjStringPath } from "../utils/obj.util";
|
|
10
|
+
import { getAllNoneObjStringPath, getAllPathsIncludingContainers, getAllPathsStopAtArray } from "../utils/obj.util";
|
|
11
11
|
const FormContext = createContext(null);
|
|
12
12
|
function Form({ children, formName, initialValues, onFinish, onReject, onFinally, FormElement, collectHiddenFields: formCollectHiddenFields = true, ...props }) {
|
|
13
13
|
const {
|
|
@@ -44,7 +44,137 @@ function Form({ children, formName, initialValues, onFinish, onReject, onFinally
|
|
|
44
44
|
getListeners: state.getListeners
|
|
45
45
|
};
|
|
46
46
|
}));
|
|
47
|
+
const handlePrimitiveValue = (name, value, options) => {
|
|
48
|
+
const listener = getListeners().find((l) => l.name === name && l.formName === formName);
|
|
49
|
+
if (listener) {
|
|
50
|
+
listener.onChange(value, options);
|
|
51
|
+
} else {
|
|
52
|
+
setData(formName, name, value);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
const handleArrayListener = (name, value, options, coreRecursive) => {
|
|
56
|
+
const listener = getListeners().find((l) => l.name === name && l.formName === formName);
|
|
57
|
+
if (!listener)
|
|
58
|
+
return;
|
|
59
|
+
const currentValue = getFormItemValue(formName, name);
|
|
60
|
+
if (!isEqual(currentValue, value)) {
|
|
61
|
+
listener.onArrayChange(value, options);
|
|
62
|
+
value.forEach((item, index) => {
|
|
63
|
+
const itemName = `${name}.${index}`;
|
|
64
|
+
const itemListener = getListeners().find((l) => l.name === itemName && l.formName === formName);
|
|
65
|
+
if (itemListener) {
|
|
66
|
+
itemListener.onChange(item, options);
|
|
67
|
+
}
|
|
68
|
+
const nestedPaths = getAllPathsIncludingContainers(item);
|
|
69
|
+
nestedPaths.forEach((p) => {
|
|
70
|
+
if (coreRecursive) {
|
|
71
|
+
coreRecursive(`${itemName}.${p}`, get(item, p), options);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
if (isArray(currentValue) && currentValue.length > value.length) {
|
|
76
|
+
for (let index = value.length; index < currentValue.length; index++) {
|
|
77
|
+
const itemName = `${name}.${index}`;
|
|
78
|
+
if (coreRecursive) {
|
|
79
|
+
coreRecursive(itemName, void 0, options);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
const handlePlainObject = (name, value, options, coreRecursive) => {
|
|
86
|
+
const listener = getListeners().find((l) => l.name === name && l.formName === formName);
|
|
87
|
+
if (listener && listener.type !== "array") {
|
|
88
|
+
listener.onChange(value, options);
|
|
89
|
+
}
|
|
90
|
+
const allPaths = getAllPathsIncludingContainers(value);
|
|
91
|
+
allPaths.forEach((p) => {
|
|
92
|
+
if (coreRecursive) {
|
|
93
|
+
const fullPath = `${name}.${p}`;
|
|
94
|
+
coreRecursive(fullPath, get(value, p), options);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
const previousValue = getFieldValue(name);
|
|
98
|
+
if (isPlainObject(previousValue)) {
|
|
99
|
+
const previousKeys = Object.keys(previousValue);
|
|
100
|
+
const newKeys = Object.keys(value);
|
|
101
|
+
const removedKeys = previousKeys.filter((k) => !newKeys.includes(k));
|
|
102
|
+
removedKeys.forEach((key) => {
|
|
103
|
+
if (coreRecursive) {
|
|
104
|
+
coreRecursive(`${name}.${key}`, void 0, options);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
const handleNonListenerArray = (name, value, options, coreRecursive) => {
|
|
110
|
+
const previousValues = getFieldValue(name);
|
|
111
|
+
if (isArray(previousValues)) {
|
|
112
|
+
if (previousValues.length !== value.length || !isEqual(previousValues, value)) {
|
|
113
|
+
setData(formName, name, value);
|
|
114
|
+
}
|
|
115
|
+
value.forEach((item, index) => {
|
|
116
|
+
if (!isEqual(previousValues[index], item)) {
|
|
117
|
+
const itemName = `${name}.${index}`;
|
|
118
|
+
const itemListener = getListeners().find((l) => l.name === itemName && l.formName === formName);
|
|
119
|
+
if (itemListener) {
|
|
120
|
+
itemListener.onChange(item, options);
|
|
121
|
+
}
|
|
122
|
+
const nestedPaths = getAllPathsIncludingContainers(item);
|
|
123
|
+
nestedPaths.forEach((p) => {
|
|
124
|
+
if (coreRecursive) {
|
|
125
|
+
coreRecursive(`${itemName}.${p}`, get(item, p), options);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
if (previousValues.length > value.length) {
|
|
131
|
+
for (let index = value.length; index < previousValues.length; index++) {
|
|
132
|
+
const itemName = `${name}.${index}`;
|
|
133
|
+
const removedItem = previousValues[index];
|
|
134
|
+
const nestedPaths = getAllPathsIncludingContainers(removedItem);
|
|
135
|
+
nestedPaths.forEach((p) => {
|
|
136
|
+
if (coreRecursive) {
|
|
137
|
+
coreRecursive(`${itemName}.${p}`, void 0, options);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
if (coreRecursive) {
|
|
141
|
+
coreRecursive(itemName, void 0, options);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
const deepTriggerSetCore = (name, value, options) => {
|
|
148
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null || value === void 0) {
|
|
149
|
+
handlePrimitiveValue(name, value, options);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (isPlainObject(value)) {
|
|
153
|
+
const listener = getListeners().find((l) => l.name === name && l.formName === formName);
|
|
154
|
+
handlePlainObject(name, value, options, deepTriggerSetCore);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (Array.isArray(value)) {
|
|
158
|
+
const listener = getListeners().find((l) => l.name === name && l.formName === formName);
|
|
159
|
+
if (listener && listener.type === "array") {
|
|
160
|
+
handleArrayListener(name, value, options, deepTriggerSetCore);
|
|
161
|
+
} else if (!listener) {
|
|
162
|
+
handleNonListenerArray(name, value, options, deepTriggerSetCore);
|
|
163
|
+
} else {
|
|
164
|
+
handlePlainObject(name, value, options, deepTriggerSetCore);
|
|
165
|
+
}
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
handlePrimitiveValue(name, value, options);
|
|
169
|
+
};
|
|
170
|
+
const handleDeepTriggerSet = (name, value, options) => {
|
|
171
|
+
deepTriggerSetCore(name, value, options);
|
|
172
|
+
};
|
|
47
173
|
const setFieldValue = (name, value, options) => {
|
|
174
|
+
if (options == null ? void 0 : options.deepTrigger) {
|
|
175
|
+
handleDeepTriggerSet(name, value, options);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
48
178
|
const listener = getListeners().find((l) => l.name === name && l.formName === formName);
|
|
49
179
|
if (listener) {
|
|
50
180
|
if (listener.type === "array") {
|
|
@@ -64,52 +194,28 @@ function Form({ children, formName, initialValues, onFinish, onReject, onFinally
|
|
|
64
194
|
listener.onChange(value, options);
|
|
65
195
|
}
|
|
66
196
|
} else {
|
|
67
|
-
|
|
68
|
-
const allStringPath = getAllNoneObjStringPath(value);
|
|
69
|
-
allStringPath.forEach((p) => {
|
|
70
|
-
const findListener = getListeners().find((l) => l.name === `${name}.${p}` && l.formName === formName);
|
|
71
|
-
if (findListener) {
|
|
72
|
-
findListener.onChange(get(value, p), options);
|
|
73
|
-
} else {
|
|
74
|
-
setData(formName, `${name}.${p}`, get(value, p));
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
} else {
|
|
78
|
-
setData(formName, name, value);
|
|
79
|
-
}
|
|
197
|
+
setData(formName, name, value);
|
|
80
198
|
}
|
|
81
199
|
};
|
|
82
200
|
const setFieldValues = (values, options = { notTriggerDirty: false }) => {
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
const
|
|
201
|
+
const allPaths = getAllPathsStopAtArray(values);
|
|
202
|
+
allPaths.forEach((path) => {
|
|
203
|
+
const pathValue = get(values, path);
|
|
204
|
+
if (Array.isArray(pathValue)) {
|
|
205
|
+
handleDeepTriggerSet(path, pathValue, options);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const listener = getListeners().find((l) => l.name === path && l.formName === formName);
|
|
86
209
|
if (listener) {
|
|
87
|
-
|
|
88
|
-
if (!isEqual(getFormItemValue(formName, p), get(values, p))) {
|
|
89
|
-
listener.onArrayChange(get(values, listener.name), options);
|
|
90
|
-
const nestedAllStringPath = getAllNoneObjStringPath(get(values, p));
|
|
91
|
-
nestedAllStringPath.forEach((np) => {
|
|
92
|
-
{
|
|
93
|
-
const findListener = getListeners().find((l) => l.name === `${p}.${np}` && l.formName === formName);
|
|
94
|
-
if (findListener) {
|
|
95
|
-
findListener.onChange(get(values, `${p}.${np}`), options);
|
|
96
|
-
} else {
|
|
97
|
-
setData(formName, `${p}.${np}`, get(values, `${p}.${np}`));
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
} else {
|
|
103
|
-
listener.onChange(get(values, listener.name), options);
|
|
104
|
-
}
|
|
210
|
+
listener.onChange(pathValue, options);
|
|
105
211
|
} else {
|
|
106
|
-
setData(formName,
|
|
212
|
+
setData(formName, path, pathValue);
|
|
107
213
|
}
|
|
108
214
|
});
|
|
109
215
|
};
|
|
110
|
-
|
|
216
|
+
function getFieldValue(name) {
|
|
111
217
|
return getFormItemValue(formName, name);
|
|
112
|
-
}
|
|
218
|
+
}
|
|
113
219
|
const getFieldValues = (names = []) => {
|
|
114
220
|
return names.map((name) => ({
|
|
115
221
|
name,
|
|
@@ -333,6 +439,17 @@ function useWatch(name, formNameOrFormInstance) {
|
|
|
333
439
|
});
|
|
334
440
|
return value;
|
|
335
441
|
}
|
|
442
|
+
function useWatchNormalized({ name, normalizeFn, formNameOrFormInstance }) {
|
|
443
|
+
const [formInstance] = useForm(formNameOrFormInstance);
|
|
444
|
+
const rawValue = useFormStore((state) => {
|
|
445
|
+
var _a;
|
|
446
|
+
return state.getFormItemValue((_a = formInstance == null ? void 0 : formInstance.formName) != null ? _a : formNameOrFormInstance, name);
|
|
447
|
+
});
|
|
448
|
+
const normalizedValue = useMemo(() => {
|
|
449
|
+
return normalizeFn(rawValue);
|
|
450
|
+
}, [rawValue, normalizeFn]);
|
|
451
|
+
return normalizedValue;
|
|
452
|
+
}
|
|
336
453
|
function useSubmitDataWatch({ formNameOrFormInstance, triggerWhenChange = false, mapFn }) {
|
|
337
454
|
const [formInstance] = useForm(formNameOrFormInstance);
|
|
338
455
|
const value = useFormStore((state) => {
|
|
@@ -368,6 +485,7 @@ const useFormItemStateWatch = (nameOrFormItemId, formNameOrFormInstance) => {
|
|
|
368
485
|
Form.useForm = useForm;
|
|
369
486
|
Form.Item = FormItem;
|
|
370
487
|
Form.useWatch = useWatch;
|
|
488
|
+
Form.useWatchNormalized = useWatchNormalized;
|
|
371
489
|
Form.useSubmitDataWatch = useSubmitDataWatch;
|
|
372
490
|
Form.useFormStateWatch = useFormStateWatch;
|
|
373
491
|
Form.useFormItemStateWatch = useFormItemStateWatch;
|
|
@@ -379,5 +497,6 @@ export {
|
|
|
379
497
|
useFormItemStateWatch,
|
|
380
498
|
useFormStateWatch,
|
|
381
499
|
useSubmitDataWatch,
|
|
382
|
-
useWatch
|
|
500
|
+
useWatch,
|
|
501
|
+
useWatchNormalized
|
|
383
502
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Input } from "antd";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import Form from "../providers/Form";
|
|
5
|
+
function TestWatchNormalize({}) {
|
|
6
|
+
const normalizeData = Form.useWatchNormalized({
|
|
7
|
+
name: "normalizeItem",
|
|
8
|
+
formNameOrFormInstance: "testNormalize",
|
|
9
|
+
normalizeFn: (value) => {
|
|
10
|
+
if (typeof value === "string") {
|
|
11
|
+
return value.toUpperCase();
|
|
12
|
+
}
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
React.useEffect(() => {
|
|
17
|
+
}, [normalizeData]);
|
|
18
|
+
return _jsxs(Form, { formName: "testNormalize", children: ["Test Watch Normalize", _jsx(Form.Item, { name: "normalizeItem", children: _jsx(Input, {}) })] });
|
|
19
|
+
}
|
|
20
|
+
var stdin_default = TestWatchNormalize;
|
|
21
|
+
export {
|
|
22
|
+
stdin_default as default
|
|
23
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
type Props = {};
|
|
2
|
+
/**
|
|
3
|
+
* Test Case 10: setFieldValues với Complex Nested Structure
|
|
4
|
+
*
|
|
5
|
+
* Cấu trúc phức tạp:
|
|
6
|
+
* - config.appName (string)
|
|
7
|
+
* - config.version (string)
|
|
8
|
+
* - config.features (array) - deepTrigger
|
|
9
|
+
* - users (FormList array) - deepTrigger
|
|
10
|
+
* - users.0.name (string)
|
|
11
|
+
* - users.0.roles (array) - nested array trong FormList
|
|
12
|
+
*
|
|
13
|
+
* Test: setFieldValues với structure phức tạp nhiều cấp
|
|
14
|
+
*
|
|
15
|
+
* Kỳ vọng:
|
|
16
|
+
* - Primitives trigger onChange
|
|
17
|
+
* - Arrays ở mọi level đều gọi handleDeepTriggerSet
|
|
18
|
+
* - Nested arrays trong FormList được xử lý đúng
|
|
19
|
+
*/
|
|
20
|
+
declare function TestCase10_SetFieldValues_ComplexNested({}: Props): import("react/jsx-runtime").JSX.Element;
|
|
21
|
+
export default TestCase10_SetFieldValues_ComplexNested;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box } from "@mui/material";
|
|
3
|
+
import { Button, Input, Typography } from "antd";
|
|
4
|
+
import FormList from "../../components/Form/FormList";
|
|
5
|
+
import Form from "../../providers/Form";
|
|
6
|
+
function TestCase10_SetFieldValues_ComplexNested({}) {
|
|
7
|
+
const [form] = Form.useForm("testCase10");
|
|
8
|
+
const handleTestSetFieldValues = () => {
|
|
9
|
+
form == null ? void 0 : form.setFieldValues({
|
|
10
|
+
config: {
|
|
11
|
+
appName: "My App",
|
|
12
|
+
version: "1.0.0",
|
|
13
|
+
features: ["auth", "api", "ui"]
|
|
14
|
+
},
|
|
15
|
+
users: [
|
|
16
|
+
{ name: "John", roles: ["admin", "editor"] },
|
|
17
|
+
{ name: "Jane", roles: ["viewer"] }
|
|
18
|
+
]
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
const handleTestUpdateStructure = () => {
|
|
22
|
+
form == null ? void 0 : form.setFieldValues({
|
|
23
|
+
config: {
|
|
24
|
+
appName: "Updated App",
|
|
25
|
+
version: "2.0.0",
|
|
26
|
+
features: ["auth", "api"]
|
|
27
|
+
// removed "ui"
|
|
28
|
+
},
|
|
29
|
+
users: [
|
|
30
|
+
{ name: "John", roles: ["admin"] }
|
|
31
|
+
// removed editor role + removed Jane user
|
|
32
|
+
]
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
const handleClear = () => {
|
|
36
|
+
form == null ? void 0 : form.setFieldValues({
|
|
37
|
+
config: {
|
|
38
|
+
appName: "",
|
|
39
|
+
version: "",
|
|
40
|
+
features: []
|
|
41
|
+
},
|
|
42
|
+
users: []
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
return _jsxs(Box, { sx: { p: 2 }, children: [_jsx(Typography.Title, { level: 4, children: "Test Case 10: setFieldValues - Complex Nested" }), _jsx(Typography.Paragraph, { children: "Test setFieldValues v\u1EDBi structure ph\u1EE9c t\u1EA1p: nested objects + arrays + FormList" }), _jsxs(Form, { formName: "testCase10", children: [_jsxs(Box, { sx: { border: "2px solid #1890ff", p: 2, mb: 2, borderRadius: 1 }, children: [_jsx(Typography.Text, { strong: true, style: { fontSize: 16 }, children: "Config" }), _jsx(Form.Item, { name: "config.appName", label: "App Name", children: _jsx(Input, { placeholder: "Enter app name" }) }), _jsx(Form.Item, { name: "config.version", label: "Version", children: _jsx(Input, { placeholder: "Enter version" }) }), _jsxs(Box, { sx: { mt: 1 }, children: [_jsx(Typography.Text, { children: "Features (Array):" }), _jsx(Form.Item, { name: "config.features.0", children: _jsx(Input, { placeholder: "Feature 1" }) }), _jsx(Form.Item, { name: "config.features.1", children: _jsx(Input, { placeholder: "Feature 2" }) }), _jsx(Form.Item, { name: "config.features.2", children: _jsx(Input, { placeholder: "Feature 3" }) })] })] }), _jsxs(Box, { sx: { border: "2px solid #52c41a", p: 2, mb: 2, borderRadius: 1 }, children: [_jsx(Typography.Text, { strong: true, style: { fontSize: 16 }, children: "Users (FormList)" }), _jsx(FormList, { name: "users", children: (fields, { add, remove }) => _jsxs(Box, { children: [fields.map((field, index) => _jsxs(Box, { sx: {
|
|
46
|
+
border: "1px dashed #52c41a",
|
|
47
|
+
p: 1,
|
|
48
|
+
mb: 1,
|
|
49
|
+
borderRadius: 1
|
|
50
|
+
}, children: [_jsxs(Typography.Text, { children: ["User ", index + 1] }), _jsx(Form.Item, { name: `users.${index}.name`, label: "Name", children: _jsx(Input, { placeholder: "User name" }) }), _jsxs(Box, { sx: { ml: 2, bgcolor: "#f6ffed", p: 1, borderRadius: 1 }, children: [_jsx(Typography.Text, { children: "Roles (Nested Array):" }), _jsx(Form.Item, { name: `users.${index}.roles.0`, children: _jsx(Input, { placeholder: "Role 1" }) }), _jsx(Form.Item, { name: `users.${index}.roles.1`, children: _jsx(Input, { placeholder: "Role 2" }) })] }), _jsx(Button, { danger: true, size: "small", onClick: () => remove({ key: field.key }), children: "Remove User" })] }, field.key)), _jsx(Button, { type: "dashed", onClick: () => add(), children: "Add User" })] }) })] }), _jsxs(Box, { sx: { mt: 2, display: "flex", gap: 2, flexDirection: "column" }, children: [_jsx(Button, { type: "primary", onClick: handleTestSetFieldValues, children: "1. Set Complex Structure" }), _jsx(Button, { onClick: handleTestUpdateStructure, children: "2. Update with Cleanup" }), _jsx(Button, { danger: true, onClick: handleClear, children: "3. Clear All" })] }), _jsxs(Box, { sx: {
|
|
51
|
+
mt: 2,
|
|
52
|
+
p: 2,
|
|
53
|
+
bgcolor: "#e6fffb",
|
|
54
|
+
border: "1px solid #13c2c2",
|
|
55
|
+
borderRadius: 1
|
|
56
|
+
}, children: [_jsx(Typography.Text, { strong: true, children: "Expected Behavior:" }), _jsxs(Typography.Paragraph, { children: [_jsx("strong", { children: "setFieldValues logic:" }), _jsx("br", {}), "- Traverse object, d\u1EEBng t\u1EA1i m\u1ED7i array", _jsx("br", {}), "- Primitives: trigger onChange", _jsx("br", {}), "- Arrays: g\u1ECDi handleDeepTriggerSet", _jsx("br", {}), _jsx("br", {}), _jsx("strong", { children: "Update with Cleanup:" }), _jsx("br", {}), "- config.features.2 \u2192 undefined (removed)", _jsx("br", {}), "- users.1 \u2192 undefined (Jane removed)", _jsx("br", {}), "- users.0.roles.1 \u2192 undefined (editor removed)"] })] })] })] });
|
|
57
|
+
}
|
|
58
|
+
var stdin_default = TestCase10_SetFieldValues_ComplexNested;
|
|
59
|
+
export {
|
|
60
|
+
stdin_default as default
|
|
61
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
type Props = {};
|
|
2
|
+
/**
|
|
3
|
+
* Test Case 1: Plain Object → Primitive Listeners
|
|
4
|
+
*
|
|
5
|
+
* Cấu trúc:
|
|
6
|
+
* - user.name (string)
|
|
7
|
+
* - user.age (number)
|
|
8
|
+
*
|
|
9
|
+
* Test: setFieldValue("user", {name: "John", age: 30}, {deepTrigger: true})
|
|
10
|
+
* Kỳ vọng:
|
|
11
|
+
* - Trigger "user" listener (nếu có)
|
|
12
|
+
* - Trigger "user.name" listener
|
|
13
|
+
* - Trigger "user.age" listener
|
|
14
|
+
*/
|
|
15
|
+
declare function TestCase1_PlainObjectToPrimitives({}: Props): import("react/jsx-runtime").JSX.Element;
|
|
16
|
+
export default TestCase1_PlainObjectToPrimitives;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box } from "@mui/material";
|
|
3
|
+
import { Button, Input, Typography } from "antd";
|
|
4
|
+
import Form from "../../providers/Form";
|
|
5
|
+
function TestCase1_PlainObjectToPrimitives({}) {
|
|
6
|
+
const [form] = Form.useForm("testCase1");
|
|
7
|
+
const handleTestDeepTrigger = () => {
|
|
8
|
+
form == null ? void 0 : form.setFieldValue("user", { name: "John Doe", age: 30 }, { deepTrigger: true });
|
|
9
|
+
};
|
|
10
|
+
const handleTestNormalSet = () => {
|
|
11
|
+
form == null ? void 0 : form.setFieldValue("user", { name: "Jane Doe", age: 25 });
|
|
12
|
+
};
|
|
13
|
+
return _jsxs(Box, { sx: { p: 2 }, children: [_jsx(Typography.Title, { level: 4, children: "Test Case 1: Plain Object \u2192 Primitive Listeners" }), _jsxs(Typography.Paragraph, { children: ['Test setFieldValue("user", ', "{name, age}", ") v\u1EDBi deepTrigger=true"] }), _jsxs(Form, { formName: "testCase1", children: [_jsx(Form.Item, { name: "user", children: _jsx(Input, { placeholder: "User Object (JSON)", disabled: true }) }), _jsx(Form.Item, { name: "user.name", label: "Name", children: _jsx(Input, { placeholder: "Enter name" }) }), _jsx(Form.Item, { name: "user.age", label: "Age", children: _jsx(Input, { type: "number", placeholder: "Enter age" }) }), _jsxs(Box, { sx: { mt: 2, display: "flex", gap: 2 }, children: [_jsx(Button, { type: "primary", onClick: handleTestDeepTrigger, children: "Test Deep Trigger" }), _jsx(Button, { onClick: handleTestNormalSet, children: "Test Normal Set" })] })] })] });
|
|
14
|
+
}
|
|
15
|
+
var stdin_default = TestCase1_PlainObjectToPrimitives;
|
|
16
|
+
export {
|
|
17
|
+
stdin_default as default
|
|
18
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
type Props = {};
|
|
2
|
+
/**
|
|
3
|
+
* Test Case 2: Plain Object → Array Listener (FormList) → Primitives
|
|
4
|
+
*
|
|
5
|
+
* Cấu trúc:
|
|
6
|
+
* - user.name (string)
|
|
7
|
+
* - user.items (array - FormList)
|
|
8
|
+
* - user.items.0.id (number)
|
|
9
|
+
* - user.items.0.title (string)
|
|
10
|
+
*
|
|
11
|
+
* Test: setFieldValue("user", {name: "John", items: [{id: 1, title: "Item 1"}]}, {deepTrigger: true})
|
|
12
|
+
* Kỳ vọng:
|
|
13
|
+
* - Trigger "user" listener
|
|
14
|
+
* - Trigger "user.name" listener
|
|
15
|
+
* - Trigger "user.items" onArrayChange
|
|
16
|
+
* - Trigger "user.items.0" listener
|
|
17
|
+
* - Trigger "user.items.0.id" listener
|
|
18
|
+
* - Trigger "user.items.0.title" listener
|
|
19
|
+
*/
|
|
20
|
+
declare function TestCase2_PlainObjectToFormList({}: Props): import("react/jsx-runtime").JSX.Element;
|
|
21
|
+
export default TestCase2_PlainObjectToFormList;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box } from "@mui/material";
|
|
3
|
+
import { Button, Input, Typography } from "antd";
|
|
4
|
+
import FormList from "../../components/Form/FormList";
|
|
5
|
+
import Form from "../../providers/Form";
|
|
6
|
+
function TestCase2_PlainObjectToFormList({}) {
|
|
7
|
+
const [form] = Form.useForm("testCase2");
|
|
8
|
+
const handleTestDeepTrigger = () => {
|
|
9
|
+
form == null ? void 0 : form.setFieldValue("user", {
|
|
10
|
+
name: "John Doe",
|
|
11
|
+
items: [
|
|
12
|
+
{ id: 1, title: "Item 1" },
|
|
13
|
+
{ id: 2, title: "Item 2" }
|
|
14
|
+
]
|
|
15
|
+
}, { deepTrigger: true });
|
|
16
|
+
};
|
|
17
|
+
const handleTestNormalSet = () => {
|
|
18
|
+
form == null ? void 0 : form.setFieldValue("user", {
|
|
19
|
+
name: "Jane Doe",
|
|
20
|
+
items: [{ id: 3, title: "Item 3" }]
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
return _jsxs(Box, { sx: { p: 2 }, children: [_jsx(Typography.Title, { level: 4, children: "Test Case 2: Plain Object \u2192 FormList \u2192 Primitives" }), _jsxs(Typography.Paragraph, { children: ['Test setFieldValue("user", ', "{name, items: [...]}", ") v\u1EDBi nested FormList"] }), _jsxs(Form, { formName: "testCase2", children: [_jsx(Form.Item, { name: "user.name", label: "User Name", children: _jsx(Input, { placeholder: "Enter user name" }) }), _jsx(FormList, { name: "user.items", children: (fields, { add, remove }) => _jsxs(Box, { children: [fields.map((field, index) => _jsxs(Box, { sx: {
|
|
24
|
+
border: "1px solid #d9d9d9",
|
|
25
|
+
p: 2,
|
|
26
|
+
mb: 2,
|
|
27
|
+
borderRadius: 1
|
|
28
|
+
}, children: [_jsxs(Typography.Text, { strong: true, children: ["Item ", index + 1] }), _jsx(Form.Item, { name: `user.items.${index}.id`, label: "ID", children: _jsx(Input, { placeholder: "Item ID" }) }), _jsx(Form.Item, { name: `user.items.${index}.title`, label: "Title", children: _jsx(Input, { placeholder: "Item title" }) }), _jsx(Button, { danger: true, onClick: () => remove({ key: field.key }), children: "Remove" })] }, field.key)), _jsx(Button, { type: "dashed", onClick: () => add(), children: "Add Item" })] }) }), _jsxs(Box, { sx: { mt: 2, display: "flex", gap: 2 }, children: [_jsx(Button, { type: "primary", onClick: handleTestDeepTrigger, children: "Test Deep Trigger" }), _jsx(Button, { onClick: handleTestNormalSet, children: "Test Normal Set" })] })] })] });
|
|
29
|
+
}
|
|
30
|
+
var stdin_default = TestCase2_PlainObjectToFormList;
|
|
31
|
+
export {
|
|
32
|
+
stdin_default as default
|
|
33
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
type Props = {};
|
|
2
|
+
/**
|
|
3
|
+
* Test Case 3: Array Non-Listener → Primitives
|
|
4
|
+
*
|
|
5
|
+
* Cấu trúc:
|
|
6
|
+
* - data (array - KHÔNG phải FormList, chỉ là field bình thường)
|
|
7
|
+
* - data.0.name (string)
|
|
8
|
+
* - data.0.value (number)
|
|
9
|
+
*
|
|
10
|
+
* Test: setFieldValue("data", [{name: "A", value: 1}, {name: "B", value: 2}], {deepTrigger: true})
|
|
11
|
+
* Kỳ vọng:
|
|
12
|
+
* - Set data array vào store
|
|
13
|
+
* - Trigger "data.0" listener (nếu có)
|
|
14
|
+
* - Trigger "data.0.name" listener
|
|
15
|
+
* - Trigger "data.0.value" listener
|
|
16
|
+
* - Trigger "data.1" listener (nếu có)
|
|
17
|
+
* - Trigger "data.1.name" listener
|
|
18
|
+
* - Trigger "data.1.value" listener
|
|
19
|
+
*/
|
|
20
|
+
declare function TestCase3_ArrayNonListenerToPrimitives({}: Props): import("react/jsx-runtime").JSX.Element;
|
|
21
|
+
export default TestCase3_ArrayNonListenerToPrimitives;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box } from "@mui/material";
|
|
3
|
+
import { Button, Input, Typography } from "antd";
|
|
4
|
+
import Form from "../../providers/Form";
|
|
5
|
+
function TestCase3_ArrayNonListenerToPrimitives({}) {
|
|
6
|
+
const [form] = Form.useForm("testCase3");
|
|
7
|
+
const handleTestDeepTrigger = () => {
|
|
8
|
+
form == null ? void 0 : form.setFieldValue("data", [
|
|
9
|
+
{ name: "Item A", value: 100 },
|
|
10
|
+
{ name: "Item B", value: 200 }
|
|
11
|
+
], { deepTrigger: true });
|
|
12
|
+
};
|
|
13
|
+
const handleTestRemoveItems = () => {
|
|
14
|
+
form == null ? void 0 : form.setFieldValue("data", [{ name: "Item A", value: 100 }], {
|
|
15
|
+
deepTrigger: true
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
const handleTestNormalSet = () => {
|
|
19
|
+
form == null ? void 0 : form.setFieldValue("data", [{ name: "Item C", value: 300 }]);
|
|
20
|
+
};
|
|
21
|
+
return _jsxs(Box, { sx: { p: 2 }, children: [_jsx(Typography.Title, { level: 4, children: "Test Case 3: Array Non-Listener \u2192 Primitives" }), _jsxs(Typography.Paragraph, { children: ['Test setFieldValue("data", ', "[{name, value}, ...]", ") v\u1EDBi array kh\xF4ng ph\u1EA3i FormList"] }), _jsxs(Form, { formName: "testCase3", children: [_jsxs(Box, { sx: { border: "1px solid #d9d9d9", p: 2, mb: 2, borderRadius: 1 }, children: [_jsx(Typography.Text, { strong: true, children: "Item 0" }), _jsx(Form.Item, { name: "data.0.name", label: "Name", children: _jsx(Input, { placeholder: "Item 0 name" }) }), _jsx(Form.Item, { name: "data.0.value", label: "Value", children: _jsx(Input, { type: "number", placeholder: "Item 0 value" }) })] }), _jsxs(Box, { sx: { border: "1px solid #d9d9d9", p: 2, mb: 2, borderRadius: 1 }, children: [_jsx(Typography.Text, { strong: true, children: "Item 1" }), _jsx(Form.Item, { name: "data.1.name", label: "Name", children: _jsx(Input, { placeholder: "Item 1 name" }) }), _jsx(Form.Item, { name: "data.1.value", label: "Value", children: _jsx(Input, { type: "number", placeholder: "Item 1 value" }) })] }), _jsxs(Box, { sx: { mt: 2, display: "flex", gap: 2 }, children: [_jsx(Button, { type: "primary", onClick: handleTestDeepTrigger, children: "Test Deep Trigger (2 items)" }), _jsx(Button, { onClick: handleTestRemoveItems, children: "Test Remove Items (1 item)" }), _jsx(Button, { onClick: handleTestNormalSet, children: "Test Normal Set" })] })] })] });
|
|
22
|
+
}
|
|
23
|
+
var stdin_default = TestCase3_ArrayNonListenerToPrimitives;
|
|
24
|
+
export {
|
|
25
|
+
stdin_default as default
|
|
26
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
type Props = {};
|
|
2
|
+
/**
|
|
3
|
+
* Test Case 4: Plain Object with Removed Fields (Edge Case)
|
|
4
|
+
*
|
|
5
|
+
* Cấu trúc ban đầu:
|
|
6
|
+
* - profile.name (string)
|
|
7
|
+
* - profile.city (string)
|
|
8
|
+
* - profile.country (string)
|
|
9
|
+
*
|
|
10
|
+
* Test:
|
|
11
|
+
* 1. Set initial: {name: "John", city: "NY", country: "USA"}
|
|
12
|
+
* 2. Set removed: {name: "John"} (city và country bị xóa)
|
|
13
|
+
*
|
|
14
|
+
* Kỳ vọng:
|
|
15
|
+
* - Trigger "profile.name" với "John"
|
|
16
|
+
* - Trigger "profile.city" với undefined (cleanup)
|
|
17
|
+
* - Trigger "profile.country" với undefined (cleanup)
|
|
18
|
+
*/
|
|
19
|
+
declare function TestCase4_PlainObjectRemovedFields({}: Props): import("react/jsx-runtime").JSX.Element;
|
|
20
|
+
export default TestCase4_PlainObjectRemovedFields;
|