Skip to content

Commit

Permalink
add form onEvaluate callback (#2797)
Browse files Browse the repository at this point in the history
Co-authored-by: hoppe <hoppewang@microsoft.com>
  • Loading branch information
2 people authored and gingi committed Nov 9, 2023
1 parent a3e2a75 commit ca80d6b
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 20 deletions.
12 changes: 12 additions & 0 deletions packages/bonito-core/src/form/__tests__/form.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ describe("Form tests", () => {
const { form, hideMessageParam, messageParam } =
createDynamicParamsForm();

const onEvaluateSpy = jest.fn();
form.on("evaluate", onEvaluateSpy);

// Test dynamic property evaluation
expect(messageParam.hidden).toBe(false);
expect(messageParam.label).toEqual("Not evaluated yet");
Expand All @@ -63,6 +66,8 @@ describe("Form tests", () => {
hideMessageParam.value = true;
expect(messageParam.hidden).toBe(true);
expect(messageParam.label).toEqual("Hidden message");

expect(onEvaluateSpy).toHaveBeenCalledTimes(1);
});

test("Subform dynamic parameters", () => {
Expand All @@ -75,6 +80,10 @@ describe("Form tests", () => {
},
});

const onEvaluateSpy = jest.fn();
form.on("evaluate", onEvaluateSpy);
parentForm.on("evaluate", onEvaluateSpy);

parentForm.subForm("child", form);
// Test dynamic property evaluation
expect(messageParam.hidden).toBe(false);
Expand All @@ -85,6 +94,9 @@ describe("Form tests", () => {
hideMessageParam.value = true;
expect(messageParam.hidden).toBe(true);
expect(messageParam.label).toEqual("Hidden message");

// one for the parent form, one for the child
expect(onEvaluateSpy).toHaveBeenCalledTimes(2);
});

test("Multiple parameters with nested sections", async () => {
Expand Down
1 change: 1 addition & 0 deletions packages/bonito-core/src/form/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export type ValidationOpts = {
export type FormEventMap<V extends FormValues> = {
change: (newValues: V, oldValues: V) => void;
validate: (snapshot: ValidationSnapshot<V>) => void;
evaluate: (propsChanged: boolean) => void;
};

/**
Expand Down
10 changes: 6 additions & 4 deletions packages/bonito-core/src/form/internal/form-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,7 @@ export class FormImpl<V extends FormValues> implements Form<V> {

private _forcedValidationStatus?: ValidationStatus;

_emitter = new EventEmitter() as TypedEventEmitter<{
change: (newValues: V, oldValues: V) => void;
validate: (snapshot: ValidationSnapshot<V>) => void;
}>;
_emitter = new EventEmitter() as TypedEventEmitter<FormEventMap<V>>;

get childEntriesCount(): number {
return this._childEntries.size;
Expand Down Expand Up @@ -206,6 +203,7 @@ export class FormImpl<V extends FormValues> implements Form<V> {
if (propsChanged) {
this._emitChangeEvent(this.values, this.values);
}
this._emitEvaluateEvent(propsChanged);
return propsChanged;
}

Expand Down Expand Up @@ -255,6 +253,10 @@ export class FormImpl<V extends FormValues> implements Form<V> {
this._emitter.emit("validate", snapshot);
}

private _emitEvaluateEvent(propsChanged: boolean) {
this._emitter.emit("evaluate", propsChanged);
}

setValues(values: V): void {
const oldValues = this._values;
if (oldValues === values) {
Expand Down
30 changes: 16 additions & 14 deletions packages/bonito-ui/src/hooks/__tests__/use-form.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { renderHook } from "@testing-library/react-hooks";
import { initMockBrowserEnvironment } from "../../environment";
import { useForm } from "../use-form";

describe("useFormParameter hook", () => {
describe("useForm hook", () => {
beforeEach(() => initMockEnvironment());

beforeEach(() => initMockBrowserEnvironment());
Expand All @@ -35,30 +35,29 @@ describe("useFormParameter hook", () => {
},
});

let changeCount = 0;
let validateCount = 0;
const onChangeSpy = jest.fn();
const onValidateSpy = jest.fn();
const onEvaluateSpy = jest.fn();

const { result, waitForNextUpdate } = renderHook(() => {
return useForm(form, {
onFormChange: () => {
changeCount++;
},
onValidate: () => {
validateCount++;
},
onFormChange: onChangeSpy,
onValidate: onValidateSpy,
onEvaluate: onEvaluateSpy,
});
});

expect(changeCount).toBe(0);
expect(validateCount).toBe(0);
expect(onChangeSpy).toBeCalledTimes(0);
expect(onValidateSpy).toBeCalledTimes(0);
expect(onEvaluateSpy).toBeCalledTimes(0);
expect(result.current.validationSnapshot).toBeUndefined();

act(() => {
form.updateValue("orderNumber", form.values.orderNumber + 1);
});

// Sync validation has happened
expect(validateCount).toBe(1);
expect(onValidateSpy).toBeCalledTimes(1);
expect(result.current.validationSnapshot?.syncValidationComplete).toBe(
true
);
Expand All @@ -69,8 +68,11 @@ describe("useFormParameter hook", () => {
await waitForNextUpdate();

// Async validation has happened
expect(changeCount).toBe(1);
expect(validateCount).toBe(2);
expect(onChangeSpy).toBeCalledTimes(1);
expect(onValidateSpy).toBeCalledTimes(2);
// No manual evaluation, so no onEvaluate call
expect(onEvaluateSpy).toBeCalledTimes(0);

expect(result.current.validationSnapshot?.asyncValidationComplete).toBe(
true
);
Expand Down
17 changes: 15 additions & 2 deletions packages/bonito-ui/src/hooks/use-form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,15 @@ export function useForm<V extends FormValues, DataResult = never>(
* @param snapshot The latest validation snapshot
*/
onValidate?: (snapshot?: ValidationSnapshot<V>) => void;

/**
* Callback for whenever the form.evaluate() is called
* @param propsChanged True if the props have changed during evaluation
*/
onEvaluate?: (propsChanged?: boolean) => void;
} = {}
) {
const { onFormChange, onValidate } = opts;
const { onFormChange, onValidate, onEvaluate } = opts;

const [values, setValues] = useState<V>(form.values);
const [validationSnapshot, setValidationSnapshot] = useState<
Expand All @@ -61,11 +67,18 @@ export function useForm<V extends FormValues, DataResult = never>(
}
});

const evaluateHandler = form.on("evaluate", (propsChanged) => {
if (onEvaluate) {
onEvaluate(propsChanged);
}
});

return () => {
form.off("change", changeHandler);
form.off("validate", validationHandler);
form.off("evaluate", evaluateHandler);
};
}, [form, onFormChange, onValidate]);
}, [form, onFormChange, onValidate, onEvaluate]);

return {
validationSnapshot,
Expand Down

0 comments on commit ca80d6b

Please sign in to comment.