/* * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ import React, { forwardRef, useRef } from "react"; import { act, fireEvent, render, waitFor } from "@testing-library/react"; import { renderHook } from "@testing-library/react-hooks"; import userEvent from "@testing-library/user-event"; import { EuiButton } from "@elastic/eui"; import FormGenerator, { IFormGeneratorProps, IFormGeneratorRef } from "./index"; import { ValidateResults } from "../../lib/field"; const testFormFields: IFormGeneratorProps<{ test: string; test_component: string; test_component_2: string; test_component_3: string; }>["formFields"] = [ { rowProps: { label: "test", }, name: "test", type: "Input", options: { rules: [ { validator: (rule, value, values) => { // let's use a validation that the values.test_component should be the same as values.test // you can custom validation here as you want if (values.test_component !== values.test) { // do not pass the validation // return a rejected promise with error message return Promise.reject("values.test_component !== values.test"); } // pass the validation // return a resolved promise return Promise.resolve(); }, }, ], }, }, { rowProps: { label: "component test", }, name: "test_component", options: { rules: [ { required: false, }, ], }, component: forwardRef(({ onChange, value, ...others }, ref: React.Ref) => ( onChange(e.target.value)} /> )), }, { rowProps: { label: "component test", }, name: "test_component_2", type: "Input", }, { rowProps: { label: "Text component", }, name: "test_component_3", type: "Text", }, ]; describe(" spec", () => { it("render the component", async () => { render(); // EuiOverlayMask appends an element to the body so we should have three (used to be two, after upgrading appears to have 3 now), an empty div from react-test-library // and our EuiOverlayMask element await waitFor(() => {}); expect(document.body.children).toMatchSnapshot(); }); it("shows error when invalid", async () => { const onChangeMock = jest.fn(); const { result } = renderHook(() => { const ref = useRef(null); const renderResult = render( ); return { ref, renderResult, }; }); const { ref } = result.current; const { getByTestId } = result.current.renderResult; userEvent.type(getByTestId("form-name-test").querySelector("input") as Element, "3"); let validateResult: ValidateResults | undefined; await act(async () => { validateResult = await ref.current?.validatePromise(); }); fireEvent.focus(getByTestId("formNameAdvancedSettings").querySelector(".ace_text-input") as HTMLElement); const value = (getByTestId("jsonEditor-valueDisplay") as HTMLTextAreaElement).value; const valueLength = value.length; expect(JSON.parse(value)).toEqual({}); for (let i = 0; i < valueLength; i++) { await fireEvent( getByTestId("formNameAdvancedSettings").querySelector(".ace_text-input") as HTMLElement, new KeyboardEvent("keydown", { keyCode: 40, }) ); } for (let i = 0; i < valueLength; i++) { await fireEvent( getByTestId("formNameAdvancedSettings").querySelector(".ace_text-input") as HTMLElement, new KeyboardEvent("keydown", { keyCode: 8, }) ); } (getByTestId("formNameAdvancedSettings").querySelector(".ace_text-input") as HTMLTextAreaElement).value = '{ "test": "1" }'; fireEvent( getByTestId("formNameAdvancedSettings").querySelector(".ace_text-input") as HTMLElement, new InputEvent("input", { data: '{ "test": "1" }', }) ); fireEvent.blur(getByTestId("formNameAdvancedSettings").querySelector(".ace_text-input") as HTMLElement); await waitFor( () => { expect(validateResult?.errors).toEqual({ test: ["values.test_component !== values.test"], }); expect(onChangeMock).toBeCalledWith( { test: "3", }, "test", "3" ); expect(onChangeMock).toBeCalledWith( { test: "3", }, undefined, {} ); }, { timeout: 3000, } ); userEvent.type(getByTestId("form-name-test_component").querySelector("input") as Element, "1"); await waitFor(() => { expect(onChangeMock).toBeCalledWith( { test: "3", test_component: "1", }, "test_component", "1" ); }); }); it("shows error with custom validation in class component", async () => { class FormGeneratorUsedInClassComponent extends React.Component<{ onValidate: (validateResult: any) => void }> { formRef: IFormGeneratorRef | null = null; onSubmit = async () => { // trigger the validation manually here const validateResult = await this.formRef?.validatePromise(); this.props.onValidate(validateResult); const { errors } = validateResult || {}; if (errors) { return; } // you can submit your data here. }; render() { return ( <> (this.formRef = ref)} value={{ test: "1", test_component: "", }} /> submit here ); } } const onValidate = jest.fn(); const { getByTestId } = render(); await act(async () => { await userEvent.click(getByTestId("submit")); }); expect(onValidate).toBeCalledWith({ errors: { test: ["values.test_component !== values.test"], }, values: { test: "1", test_component: "", }, }); await act(async () => { await userEvent.clear(getByTestId("form-name-test").querySelector("input") as Element); await userEvent.click(getByTestId("submit")); }); expect(onValidate).toBeCalledWith({ errors: null, values: { test: "", test_component: "", }, }); }); it("shows error with custom validation in function component", async () => { const FormGeneratorUsedInFunctionComponent = function (props: { onValidate: (validateResult: any) => void }) { const formRef = useRef(null); const onSubmit = async () => { // trigger the validation manually here const validateResult = await formRef.current?.validatePromise(); props.onValidate(validateResult); const { errors } = validateResult || {}; if (errors) { return; } // you can submit your data here. }; return ( <> submit here ); }; const onValidate = jest.fn(); const { getByTestId } = render(); await act(async () => { await userEvent.click(getByTestId("submit")); }); expect(onValidate).toBeCalledWith({ errors: { test: ["values.test_component !== values.test"], }, values: { test: "1", test_component: "", }, }); await act(async () => { await userEvent.clear(getByTestId("form-name-test").querySelector("input") as Element); await userEvent.click(getByTestId("submit")); }); expect(onValidate).toBeCalledWith({ errors: null, values: { test: "", test_component: "", }, }); }); });