mirror of
				https://gitlab.nic.cz/turris/reforis/foris-js.git
				synced 2025-11-03 23:00:31 +01:00 
			
		
		
		
	Loading and errors HOCs
This commit is contained in:
		@@ -28,7 +28,7 @@ export function Spinner({
 | 
				
			|||||||
}) {
 | 
					}) {
 | 
				
			||||||
    if (!fullScreen) {
 | 
					    if (!fullScreen) {
 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
            <div className={`spinner-wrapper ${className || ""}`} {...props}>
 | 
					            <div className={`spinner-wrapper ${className || "my-3 text-center"}`} {...props}>
 | 
				
			||||||
                <SpinnerElement>{children}</SpinnerElement>
 | 
					                <SpinnerElement>{children}</SpinnerElement>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,16 +5,19 @@
 | 
				
			|||||||
 * See /LICENSE for more information.
 | 
					 * See /LICENSE for more information.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import React, { useEffect, useState } from "react";
 | 
					import React, { useEffect } from "react";
 | 
				
			||||||
import PropTypes from "prop-types";
 | 
					import PropTypes from "prop-types";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Spinner } from "bootstrap/Spinner";
 | 
					import { Spinner } from "bootstrap/Spinner";
 | 
				
			||||||
import { useAPIPost } from "api/hooks";
 | 
					import { useAPIPost } from "api/hooks";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Prompt } from "react-router";
 | 
					import { Prompt } from "react-router";
 | 
				
			||||||
 | 
					import { API_STATE } from "api/utils";
 | 
				
			||||||
 | 
					import { ErrorMessage } from "utils/ErrorMessage";
 | 
				
			||||||
 | 
					import { useAlert } from "alertContext/AlertContext";
 | 
				
			||||||
 | 
					import { ALERT_TYPES } from "bootstrap/Alert";
 | 
				
			||||||
import { useForisModule, useForm } from "../hooks";
 | 
					import { useForisModule, useForm } from "../hooks";
 | 
				
			||||||
import { STATES as SUBMIT_BUTTON_STATES, SubmitButton } from "./SubmitButton";
 | 
					import { STATES as SUBMIT_BUTTON_STATES, SubmitButton } from "./SubmitButton";
 | 
				
			||||||
import { FailAlert, SuccessAlert } from "./alerts";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
ForisForm.propTypes = {
 | 
					ForisForm.propTypes = {
 | 
				
			||||||
    /** WebSocket object see `scr/common/WebSockets.js`. */
 | 
					    /** WebSocket object see `scr/common/WebSockets.js`. */
 | 
				
			||||||
@@ -69,22 +72,34 @@ export function ForisForm({
 | 
				
			|||||||
    children,
 | 
					    children,
 | 
				
			||||||
}) {
 | 
					}) {
 | 
				
			||||||
    const [formState, onFormChangeHandler, resetFormData] = useForm(validator, prepData);
 | 
					    const [formState, onFormChangeHandler, resetFormData] = useForm(validator, prepData);
 | 
				
			||||||
 | 
					    const [setAlert] = useAlert();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const [forisModuleState] = useForisModule(ws, forisConfig);
 | 
					    const [forisModuleState] = useForisModule(ws, forisConfig);
 | 
				
			||||||
    useEffect(() => {
 | 
					    useEffect(() => {
 | 
				
			||||||
        if (forisModuleState.data) {
 | 
					        if (forisModuleState.state === API_STATE.SUCCESS) {
 | 
				
			||||||
            resetFormData(forisModuleState.data);
 | 
					            resetFormData(forisModuleState.data);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }, [forisModuleState.data, resetFormData, prepData]);
 | 
					    }, [forisModuleState, resetFormData, prepData]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const [postState, post] = useAPIPost(forisConfig.endpoint);
 | 
					    const [postState, post] = useAPIPost(forisConfig.endpoint);
 | 
				
			||||||
    useEffect(() => {
 | 
					    useEffect(() => {
 | 
				
			||||||
        if (postState.isSuccess) postCallback();
 | 
					        if (postState.state === API_STATE.SUCCESS) {
 | 
				
			||||||
    }, [postCallback, postState.isSuccess]);
 | 
					            postCallback();
 | 
				
			||||||
 | 
					            setAlert(_("Settings saved successfully"), ALERT_TYPES.SUCCESS);
 | 
				
			||||||
 | 
					        } else if (postState.state === API_STATE.ERROR) {
 | 
				
			||||||
 | 
					            setAlert(_("Cannot save settings"));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }, [postCallback, postState.state, setAlert]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (forisModuleState.state === API_STATE.ERROR) {
 | 
				
			||||||
 | 
					        return <ErrorMessage />;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (!formState.data) {
 | 
				
			||||||
 | 
					        return <Spinner />;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function onSubmitHandler(e) {
 | 
					    function onSubmitHandler(event) {
 | 
				
			||||||
        e.preventDefault();
 | 
					        event.preventDefault();
 | 
				
			||||||
        resetFormData();
 | 
					        resetFormData();
 | 
				
			||||||
        const copiedFormData = JSON.parse(JSON.stringify(formState.data));
 | 
					        const copiedFormData = JSON.parse(JSON.stringify(formState.data));
 | 
				
			||||||
        const preparedData = prepDataToSubmit(copiedFormData);
 | 
					        const preparedData = prepDataToSubmit(copiedFormData);
 | 
				
			||||||
@@ -92,16 +107,18 @@ export function ForisForm({
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function getSubmitButtonState() {
 | 
					    function getSubmitButtonState() {
 | 
				
			||||||
        if (postState.isSending) return SUBMIT_BUTTON_STATES.SAVING;
 | 
					        if (postState.state === API_STATE.SENDING) {
 | 
				
			||||||
        if (forisModuleState.isLoading) return SUBMIT_BUTTON_STATES.LOAD;
 | 
					            return SUBMIT_BUTTON_STATES.SAVING;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (forisModuleState.state === API_STATE.SENDING) {
 | 
				
			||||||
 | 
					            return SUBMIT_BUTTON_STATES.LOAD;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        return SUBMIT_BUTTON_STATES.READY;
 | 
					        return SUBMIT_BUTTON_STATES.READY;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const [alertIsDismissed, setAlertIsDismissed] = useState(false);
 | 
					    const formIsDisabled = (disabled
 | 
				
			||||||
 | 
					        || forisModuleState.state === API_STATE.SENDING
 | 
				
			||||||
    if (!formState.data) return <Spinner className="row justify-content-center" />;
 | 
					        || postState.state === API_STATE.SENDING);
 | 
				
			||||||
 | 
					 | 
				
			||||||
    const formIsDisabled = disabled || forisModuleState.isLoading || postState.isSending;
 | 
					 | 
				
			||||||
    const submitButtonIsDisabled = disabled || !!formState.errors;
 | 
					    const submitButtonIsDisabled = disabled || !!formState.errors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const childrenWithFormProps = React.Children.map(
 | 
					    const childrenWithFormProps = React.Children.map(
 | 
				
			||||||
@@ -123,19 +140,9 @@ export function ForisForm({
 | 
				
			|||||||
        return _("Changes you made may not be saved. Are you sure you want to leave?");
 | 
					        return _("Changes you made may not be saved. Are you sure you want to leave?");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let alert = null;
 | 
					 | 
				
			||||||
    if (!alertIsDismissed) {
 | 
					 | 
				
			||||||
        if (postState.isSuccess) {
 | 
					 | 
				
			||||||
            alert = <SuccessAlert onDismiss={() => setAlertIsDismissed(true)} />;
 | 
					 | 
				
			||||||
        } else if (postState.isError) {
 | 
					 | 
				
			||||||
            alert = <FailAlert onDismiss={() => setAlertIsDismissed(true)} />;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <>
 | 
					        <>
 | 
				
			||||||
            <Prompt message={getMessageOnLeavingPage} />
 | 
					            <Prompt message={getMessageOnLeavingPage} />
 | 
				
			||||||
            {alert}
 | 
					 | 
				
			||||||
            <form onSubmit={onSubmit}>
 | 
					            <form onSubmit={onSubmit}>
 | 
				
			||||||
                {childrenWithFormProps}
 | 
					                {childrenWithFormProps}
 | 
				
			||||||
                <SubmitButton
 | 
					                <SubmitButton
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,46 +0,0 @@
 | 
				
			|||||||
/*
 | 
					 | 
				
			||||||
 * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
					 | 
				
			||||||
 * See /LICENSE for more information.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
import PropTypes from "prop-types";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { Alert } from "bootstrap/Alert";
 | 
					 | 
				
			||||||
import { Portal } from "utils/Portal";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
SuccessAlert.propTypes = {
 | 
					 | 
				
			||||||
    onDismiss: PropTypes.func.isRequired,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const ALERT_CONTAINER_ID = "alert-container";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function SuccessAlert({ onDismiss }) {
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
        <Portal containerId={ALERT_CONTAINER_ID}>
 | 
					 | 
				
			||||||
            <Alert
 | 
					 | 
				
			||||||
                type="success"
 | 
					 | 
				
			||||||
                message={_("Settings were successfully saved.")}
 | 
					 | 
				
			||||||
                onDismiss={onDismiss}
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
        </Portal>
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
FailAlert.propTypes = {
 | 
					 | 
				
			||||||
    onDismiss: PropTypes.func.isRequired,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function FailAlert({ onDismiss }) {
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
        <Portal containerId={ALERT_CONTAINER_ID}>
 | 
					 | 
				
			||||||
            <Alert
 | 
					 | 
				
			||||||
                type="danger"
 | 
					 | 
				
			||||||
                message={_("Settings update was failed.")}
 | 
					 | 
				
			||||||
                onDismiss={onDismiss}
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
        </Portal>
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -50,6 +50,10 @@ export { WebSockets } from "webSockets/WebSockets";
 | 
				
			|||||||
// Utils
 | 
					// Utils
 | 
				
			||||||
export { Portal } from "utils/Portal";
 | 
					export { Portal } from "utils/Portal";
 | 
				
			||||||
export { undefinedIfEmpty, withoutUndefinedKeys, onlySpecifiedKeys } from "utils/objectHelpers";
 | 
					export { undefinedIfEmpty, withoutUndefinedKeys, onlySpecifiedKeys } from "utils/objectHelpers";
 | 
				
			||||||
 | 
					export {
 | 
				
			||||||
 | 
					    withEither, withSpinner, withSending, withSpinnerOnSending, withError, withErrorMessage,
 | 
				
			||||||
 | 
					} from "utils/conditionalHOCs";
 | 
				
			||||||
 | 
					export { ErrorMessage } from "utils/ErrorMessage";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Foris URL
 | 
					// Foris URL
 | 
				
			||||||
export { ForisURLs, REFORIS_URL_PREFIX } from "forisUrls";
 | 
					export { ForisURLs, REFORIS_URL_PREFIX } from "forisUrls";
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										16
									
								
								src/utils/ErrorMessage.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/utils/ErrorMessage.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This is free software, licensed under the GNU General Public License v3.
 | 
				
			||||||
 | 
					 * See /LICENSE for more information.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import React from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function ErrorMessage() {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <p className="text-center text-danger">
 | 
				
			||||||
 | 
					            {_("An error occurred while fetching data.")}
 | 
				
			||||||
 | 
					        </p>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,61 @@
 | 
				
			|||||||
 | 
					// Jest Snapshot v1, https://goo.gl/fbAQLP
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exports[`conditional HOCs withError should render error message 1`] = `
 | 
				
			||||||
 | 
					<div>
 | 
				
			||||||
 | 
					  <p
 | 
				
			||||||
 | 
					    class="text-center text-danger"
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    An error occurred while fetching data.
 | 
				
			||||||
 | 
					  </p>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exports[`conditional HOCs withErrorMessage should render error message 1`] = `
 | 
				
			||||||
 | 
					<div>
 | 
				
			||||||
 | 
					  <p
 | 
				
			||||||
 | 
					    class="text-center text-danger"
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    An error occurred while fetching data.
 | 
				
			||||||
 | 
					  </p>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exports[`conditional HOCs withSpinner should render spinner 1`] = `
 | 
				
			||||||
 | 
					<div>
 | 
				
			||||||
 | 
					  <div
 | 
				
			||||||
 | 
					    class="spinner-wrapper my-3 text-center"
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    <div
 | 
				
			||||||
 | 
					      class="spinner-border "
 | 
				
			||||||
 | 
					      role="status"
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      <span
 | 
				
			||||||
 | 
					        class="sr-only"
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <div
 | 
				
			||||||
 | 
					      class="spinner-text"
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exports[`conditional HOCs withSpinnerOnSending should render spinner 1`] = `
 | 
				
			||||||
 | 
					<div>
 | 
				
			||||||
 | 
					  <div
 | 
				
			||||||
 | 
					    class="spinner-wrapper my-3 text-center"
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    <div
 | 
				
			||||||
 | 
					      class="spinner-border "
 | 
				
			||||||
 | 
					      role="status"
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      <span
 | 
				
			||||||
 | 
					        class="sr-only"
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <div
 | 
				
			||||||
 | 
					      class="spinner-text"
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					`;
 | 
				
			||||||
							
								
								
									
										110
									
								
								src/utils/__tests__/conditionalHOCs.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								src/utils/__tests__/conditionalHOCs.test.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,110 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This is free software, licensed under the GNU General Public License v3.
 | 
				
			||||||
 | 
					 * See /LICENSE for more information.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import React from "react";
 | 
				
			||||||
 | 
					import { render } from "customTestRender";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    withEither, withSpinner, withSending, withSpinnerOnSending, withError, withErrorMessage,
 | 
				
			||||||
 | 
					} from "../conditionalHOCs";
 | 
				
			||||||
 | 
					import { API_STATE } from "api/utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe("conditional HOCs", () => {
 | 
				
			||||||
 | 
					    const First = () => <p>First</p>;
 | 
				
			||||||
 | 
					    const Alternative = () => <p>Alternative</p>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    describe("withEither", () => {
 | 
				
			||||||
 | 
					        it("should render First component", () => {
 | 
				
			||||||
 | 
					            const withAlternative = withEither(() => false, Alternative);
 | 
				
			||||||
 | 
					            const FirstWithConditional = withAlternative(First);
 | 
				
			||||||
 | 
					            const { getByText } = render(<FirstWithConditional />);
 | 
				
			||||||
 | 
					            expect(getByText("First")).toBeDefined();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it("should render Alternative component", () => {
 | 
				
			||||||
 | 
					            const withAlternative = withEither(() => true, Alternative);
 | 
				
			||||||
 | 
					            const FirstWithConditional = withAlternative(First);
 | 
				
			||||||
 | 
					            const { getByText } = render(<FirstWithConditional />);
 | 
				
			||||||
 | 
					            expect(getByText("Alternative")).toBeDefined();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    describe("withSpinner", () => {
 | 
				
			||||||
 | 
					        it("should render First component", () => {
 | 
				
			||||||
 | 
					            const withSpinnerHidden = withSpinner(() => false);
 | 
				
			||||||
 | 
					            const FirstWithConditional = withSpinnerHidden(First);
 | 
				
			||||||
 | 
					            const { getByText } = render(<FirstWithConditional />);
 | 
				
			||||||
 | 
					            expect(getByText("First")).toBeDefined();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it("should render spinner", () => {
 | 
				
			||||||
 | 
					            const withSpinnerVisible = withSpinner(() => true);
 | 
				
			||||||
 | 
					            const FirstWithConditional = withSpinnerVisible(First);
 | 
				
			||||||
 | 
					            const { container } = render(<FirstWithConditional />);
 | 
				
			||||||
 | 
					            expect(container).toMatchSnapshot();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    describe("withSending", () => {
 | 
				
			||||||
 | 
					        it("should render First component", () => {
 | 
				
			||||||
 | 
					            const withAlternative = withSending(Alternative);
 | 
				
			||||||
 | 
					            const FirstWithConditional = withAlternative(First);
 | 
				
			||||||
 | 
					            const { getByText } = render(<FirstWithConditional apiState={API_STATE.SUCCESS} />);
 | 
				
			||||||
 | 
					            expect(getByText("First")).toBeDefined();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it("should render Alternative component", () => {
 | 
				
			||||||
 | 
					            const withAlternative = withSending(Alternative);
 | 
				
			||||||
 | 
					            const FirstWithConditional = withAlternative(First);
 | 
				
			||||||
 | 
					            const { getByText } = render(<FirstWithConditional apiState={API_STATE.SENDING} />);
 | 
				
			||||||
 | 
					            expect(getByText("Alternative")).toBeDefined();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    describe("withSpinnerOnSending", () => {
 | 
				
			||||||
 | 
					        it("should render First component", () => {
 | 
				
			||||||
 | 
					            const FirstWithConditional = withSpinnerOnSending(First);
 | 
				
			||||||
 | 
					            const { getByText } = render(<FirstWithConditional apiState={API_STATE.SUCCESS} />);
 | 
				
			||||||
 | 
					            expect(getByText("First")).toBeDefined();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it("should render spinner", () => {
 | 
				
			||||||
 | 
					            const FirstWithConditional = withSpinnerOnSending(First);
 | 
				
			||||||
 | 
					            const { container } = render(<FirstWithConditional apiState={API_STATE.SENDING} />);
 | 
				
			||||||
 | 
					            expect(container).toMatchSnapshot();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    describe("withError", () => {
 | 
				
			||||||
 | 
					        it("should render First component", () => {
 | 
				
			||||||
 | 
					            const withErrorHidden = withError(() => false);
 | 
				
			||||||
 | 
					            const FirstWithConditional = withErrorHidden(First);
 | 
				
			||||||
 | 
					            const { getByText } = render(<FirstWithConditional />);
 | 
				
			||||||
 | 
					            expect(getByText("First")).toBeDefined();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it("should render error message", () => {
 | 
				
			||||||
 | 
					            const withErrorVisible = withError(() => true);
 | 
				
			||||||
 | 
					            const FirstWithConditional = withErrorVisible(First);
 | 
				
			||||||
 | 
					            const { container } = render(<FirstWithConditional />);
 | 
				
			||||||
 | 
					            expect(container).toMatchSnapshot();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    describe("withErrorMessage", () => {
 | 
				
			||||||
 | 
					        it("should render First component", () => {
 | 
				
			||||||
 | 
					            const FirstWithConditional = withErrorMessage(First);
 | 
				
			||||||
 | 
					            const { getByText } = render(<FirstWithConditional apiState={API_STATE.SUCCESS} />);
 | 
				
			||||||
 | 
					            expect(getByText("First")).toBeDefined();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it("should render error message", () => {
 | 
				
			||||||
 | 
					            const FirstWithConditional = withErrorMessage(First);
 | 
				
			||||||
 | 
					            const { container } = render(<FirstWithConditional apiState={API_STATE.ERROR} />);
 | 
				
			||||||
 | 
					            expect(container).toMatchSnapshot();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										52
									
								
								src/utils/conditionalHOCs.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/utils/conditionalHOCs.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This is free software, licensed under the GNU General Public License v3.
 | 
				
			||||||
 | 
					 * See /LICENSE for more information.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import React from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Spinner } from "bootstrap/Spinner";
 | 
				
			||||||
 | 
					import { API_STATE } from "api/utils";
 | 
				
			||||||
 | 
					import { ErrorMessage } from "./ErrorMessage";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function withEither(conditionalFn, Either) {
 | 
				
			||||||
 | 
					    return (Component) => (props) => {
 | 
				
			||||||
 | 
					        if (conditionalFn(props)) {
 | 
				
			||||||
 | 
					            return <Either />;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return <Component {...props} />;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Loading
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function isSending(props) {
 | 
				
			||||||
 | 
					    if (Array.isArray(props.apiState)) {
 | 
				
			||||||
 | 
					        return props.apiState.some(
 | 
				
			||||||
 | 
					            (state) => [API_STATE.INIT, API_STATE.SENDING].includes(state),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return [API_STATE.INIT, API_STATE.SENDING].includes(props.apiState);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const withSpinner = (conditionalFn) => withEither(conditionalFn, Spinner);
 | 
				
			||||||
 | 
					const withSending = (Either) => withEither(isSending, Either);
 | 
				
			||||||
 | 
					const withSpinnerOnSending = withSpinner(isSending);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Error handling
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const withError = (conditionalFn) => withEither(conditionalFn, ErrorMessage);
 | 
				
			||||||
 | 
					const withErrorMessage = withError(
 | 
				
			||||||
 | 
					    (props) => {
 | 
				
			||||||
 | 
					        if (Array.isArray(props.apiState)) {
 | 
				
			||||||
 | 
					            return props.apiState.includes(API_STATE.ERROR);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return props.apiState === API_STATE.ERROR;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {
 | 
				
			||||||
 | 
					    withEither, withSpinner, withSending, withSpinnerOnSending, withError, withErrorMessage,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
		Reference in New Issue
	
	Block a user