mirror of
				https://gitlab.nic.cz/turris/reforis/foris-js.git
				synced 2025-11-03 23:00:31 +01:00 
			
		
		
		
	Global alert
This commit is contained in:
		@@ -14,4 +14,12 @@ module.exports = {
 | 
			
		||||
            },
 | 
			
		||||
        }],
 | 
			
		||||
    ],
 | 
			
		||||
    env: {
 | 
			
		||||
        development: {
 | 
			
		||||
            ignore: ["**/__tests__", "./scripts"],
 | 
			
		||||
        },
 | 
			
		||||
        test: {
 | 
			
		||||
            ignore: ["./scripts"],
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "foris",
 | 
			
		||||
  "version": "1.2.0",
 | 
			
		||||
  "version": "1.3.1",
 | 
			
		||||
  "lockfileVersion": 1,
 | 
			
		||||
  "requires": true,
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "foris",
 | 
			
		||||
  "version": "1.2.0",
 | 
			
		||||
  "version": "1.3.1",
 | 
			
		||||
  "description": "Set of components and utils for Foris and its plugins.",
 | 
			
		||||
  "author": "CZ.NIC, z.s.p.o.",
 | 
			
		||||
  "repository": {
 | 
			
		||||
@@ -66,8 +66,8 @@
 | 
			
		||||
    "webpack": "^4.41.0"
 | 
			
		||||
  },
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "build": "rm -rf dist; babel src --out-dir dist --ignore '**/__tests__' --source-maps inline --copy-files",
 | 
			
		||||
    "build:watch": "babel src --verbose --watch --out-dir dist --ignore '**/__tests__' --source-maps inline --copy-files",
 | 
			
		||||
    "build": "rm -rf dist; babel src --out-dir dist --source-maps inline --copy-files",
 | 
			
		||||
    "build:watch": "babel src --verbose --watch --out-dir dist --source-maps inline --copy-files",
 | 
			
		||||
    "prepare": "rm -rf ./dist && npm run build",
 | 
			
		||||
    "lint": "eslint src",
 | 
			
		||||
    "test": "jest",
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ then
 | 
			
		||||
    echo "\$NPM_TOKEN is not set"
 | 
			
		||||
    exit 1
 | 
			
		||||
else
 | 
			
		||||
    # Need to replace "_" with "_" as GitLab CI won't accept secret vars with "-"
 | 
			
		||||
    # Need to replace "_" with "-" as GitLab CI won't accept secret vars with "-"
 | 
			
		||||
    echo "//registry.npmjs.org/:_authToken=$(echo "$NPM_TOKEN" | tr _ -)" > .npmrc
 | 
			
		||||
    echo "unsafe-perm = true" >> ~/.npmrc
 | 
			
		||||
    if test "$1" = "beta"
 | 
			
		||||
 
 | 
			
		||||
@@ -5,10 +5,10 @@
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React, { useState } from "react";
 | 
			
		||||
import React, { useState, useContext, useCallback } from "react";
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
 | 
			
		||||
import { Alert } from "bootstrap/Alert";
 | 
			
		||||
import { Alert, ALERT_TYPES } from "bootstrap/Alert";
 | 
			
		||||
 | 
			
		||||
const AlertContext = React.createContext();
 | 
			
		||||
 | 
			
		||||
@@ -22,14 +22,28 @@ AlertContextProvider.propTypes = {
 | 
			
		||||
function AlertContextProvider({ children }) {
 | 
			
		||||
    const [alert, setAlert] = useState(null);
 | 
			
		||||
 | 
			
		||||
    const setAlertWrapper = useCallback((message, type = ALERT_TYPES.DANGER) => {
 | 
			
		||||
        setAlert({ message, type });
 | 
			
		||||
    }, [setAlert]);
 | 
			
		||||
 | 
			
		||||
    const dismissAlert = useCallback(() => setAlert(null), [setAlert]);
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
            {alert && <Alert type="danger" message={alert} onDismiss={() => setAlert(null)} />}
 | 
			
		||||
            <AlertContext.Provider value={setAlert}>
 | 
			
		||||
            {alert && (
 | 
			
		||||
                <Alert type={alert.type} onDismiss={dismissAlert} floating>
 | 
			
		||||
                    {alert.message}
 | 
			
		||||
                </Alert>
 | 
			
		||||
            )}
 | 
			
		||||
            <AlertContext.Provider value={[setAlertWrapper, dismissAlert]}>
 | 
			
		||||
                { children }
 | 
			
		||||
            </AlertContext.Provider>
 | 
			
		||||
        </>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export { AlertContext, AlertContextProvider };
 | 
			
		||||
function useAlert() {
 | 
			
		||||
    return useContext(AlertContext);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export { AlertContext, AlertContextProvider, useAlert };
 | 
			
		||||
 
 | 
			
		||||
@@ -5,14 +5,19 @@
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React, { useContext } from "react";
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { render, getByText, queryByText, fireEvent } from "customTestRender";
 | 
			
		||||
 | 
			
		||||
import { AlertContext, AlertContextProvider } from "../AlertContext";
 | 
			
		||||
import { useAlert, AlertContextProvider } from "../AlertContext";
 | 
			
		||||
 | 
			
		||||
function AlertTest() {
 | 
			
		||||
    const setAlert = useContext(AlertContext);
 | 
			
		||||
    return <button onClick={() => setAlert("Alert content")}>Set alert</button>;
 | 
			
		||||
    const [setAlert, dismissAlert] = useAlert();
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
            <button onClick={() => setAlert("Alert content")}>Set alert</button>
 | 
			
		||||
            <button onClick={dismissAlert}>Dismiss alert</button>
 | 
			
		||||
        </>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
describe("AlertContext", () => {
 | 
			
		||||
@@ -36,7 +41,7 @@ describe("AlertContext", () => {
 | 
			
		||||
        expect(componentContainer).toMatchSnapshot();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("should dismiss alert", () => {
 | 
			
		||||
    it("should dismiss alert with alert button", () => {
 | 
			
		||||
        fireEvent.click(getByText(componentContainer, "Set alert"));
 | 
			
		||||
        // Alert is present
 | 
			
		||||
        expect(getByText(componentContainer, "Alert content")).toBeDefined();
 | 
			
		||||
@@ -45,4 +50,14 @@ describe("AlertContext", () => {
 | 
			
		||||
        // Alert is gone
 | 
			
		||||
        expect(queryByText(componentContainer, "Alert content")).toBeNull();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("should dismiss alert with external button", () => {
 | 
			
		||||
        fireEvent.click(getByText(componentContainer, "Set alert"));
 | 
			
		||||
        // Alert is present
 | 
			
		||||
        expect(getByText(componentContainer, "Alert content")).toBeDefined();
 | 
			
		||||
 | 
			
		||||
        fireEvent.click(getByText(componentContainer, "Dismiss alert"));
 | 
			
		||||
        // Alert is gone
 | 
			
		||||
        expect(queryByText(componentContainer, "Alert content")).toBeNull();
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
exports[`AlertContext should render alert 1`] = `
 | 
			
		||||
<div>
 | 
			
		||||
  <div
 | 
			
		||||
    class="alert alert-dismissible alert-danger"
 | 
			
		||||
    class="alert alert-dismissible alert-danger fixed-top floating-alert"
 | 
			
		||||
  >
 | 
			
		||||
    <button
 | 
			
		||||
      class="close"
 | 
			
		||||
@@ -16,6 +16,9 @@ exports[`AlertContext should render alert 1`] = `
 | 
			
		||||
  <button>
 | 
			
		||||
    Set alert
 | 
			
		||||
  </button>
 | 
			
		||||
  <button>
 | 
			
		||||
    Dismiss alert
 | 
			
		||||
  </button>
 | 
			
		||||
</div>
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
@@ -24,5 +27,8 @@ exports[`AlertContext should render component without alert 1`] = `
 | 
			
		||||
  <button>
 | 
			
		||||
    Set alert
 | 
			
		||||
  </button>
 | 
			
		||||
  <button>
 | 
			
		||||
    Dismiss alert
 | 
			
		||||
  </button>
 | 
			
		||||
</div>
 | 
			
		||||
`;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										17
									
								
								src/bootstrap/Alert.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/bootstrap/Alert.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
.floating-alert {
 | 
			
		||||
    /* Display alert above other components */
 | 
			
		||||
    z-index: 2000;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media(max-width: 768px) {
 | 
			
		||||
    .floating-alert {
 | 
			
		||||
        margin: 0.5rem;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media(min-width: 769px) {
 | 
			
		||||
    .floating-alert {
 | 
			
		||||
        width: 75%;
 | 
			
		||||
        margin: 0.5rem auto;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -8,11 +8,22 @@
 | 
			
		||||
import React from "react";
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
 | 
			
		||||
import "./Alert.css";
 | 
			
		||||
 | 
			
		||||
export const ALERT_TYPES = Object.freeze({
 | 
			
		||||
    PRIMARY: "primary",
 | 
			
		||||
    SECONDARY: "secondary",
 | 
			
		||||
    SUCCESS: "success",
 | 
			
		||||
    DANGER: "danger",
 | 
			
		||||
    WARNING: "warning",
 | 
			
		||||
    INFO: "info",
 | 
			
		||||
    LIGHT: "light",
 | 
			
		||||
    DARK: "dark",
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
Alert.propTypes = {
 | 
			
		||||
    /** Type of the alert it adds as `alert-${type}` class. */
 | 
			
		||||
    type: PropTypes.string.isRequired,
 | 
			
		||||
    /** Alert message. */
 | 
			
		||||
    message: PropTypes.string,
 | 
			
		||||
    type: PropTypes.oneOf(Object.values(ALERT_TYPES)),
 | 
			
		||||
    /** Alert content. */
 | 
			
		||||
    children: PropTypes.oneOfType([
 | 
			
		||||
        PropTypes.arrayOf(PropTypes.node),
 | 
			
		||||
@@ -20,15 +31,21 @@ Alert.propTypes = {
 | 
			
		||||
    ]),
 | 
			
		||||
    /** onDismiss handler. */
 | 
			
		||||
    onDismiss: PropTypes.func,
 | 
			
		||||
    /** Floating alerts stay on top of the page */
 | 
			
		||||
    floating: PropTypes.bool,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Alert.defaultProps = {
 | 
			
		||||
    type: ALERT_TYPES.DANGER,
 | 
			
		||||
    floating: false,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function Alert({
 | 
			
		||||
    type, message, onDismiss, children,
 | 
			
		||||
    type, onDismiss, floating, children,
 | 
			
		||||
}) {
 | 
			
		||||
    return (
 | 
			
		||||
        <div className={`alert alert-dismissible alert-${type}`}>
 | 
			
		||||
        <div className={`alert alert-dismissible alert-${type} ${floating ? "fixed-top floating-alert" : ""}`.trim()}>
 | 
			
		||||
            {onDismiss ? <button type="button" className="close" onClick={onDismiss}>×</button> : false}
 | 
			
		||||
            {message}
 | 
			
		||||
            {children}
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
 
 | 
			
		||||
@@ -41,7 +41,7 @@ export function Modal({ shown, setShown, children }) {
 | 
			
		||||
    return (
 | 
			
		||||
        <Portal containerId="modal-container">
 | 
			
		||||
            <div className={`modal fade ${shown ? "show" : ""}`} role="dialog">
 | 
			
		||||
                <div ref={dialogRef} className="modal-dialog" role="document">
 | 
			
		||||
                <div ref={dialogRef} className="modal-dialog modal-dialog-centered" role="document">
 | 
			
		||||
                    <div className="modal-content">
 | 
			
		||||
                        {children}
 | 
			
		||||
                    </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ export { useAPIDelete } from "api/delete";
 | 
			
		||||
export { useAPIPatch } from "api/patch";
 | 
			
		||||
 | 
			
		||||
// Bootstrap
 | 
			
		||||
export { Alert } from "bootstrap/Alert";
 | 
			
		||||
export { Alert, ALERT_TYPES } from "bootstrap/Alert";
 | 
			
		||||
export { Button } from "bootstrap/Button";
 | 
			
		||||
export { CheckBox } from "bootstrap/CheckBox";
 | 
			
		||||
export { DownloadButton } from "bootstrap/DownloadButton";
 | 
			
		||||
@@ -66,4 +66,8 @@ export {
 | 
			
		||||
} from "validations";
 | 
			
		||||
 | 
			
		||||
// Alert context
 | 
			
		||||
export { AlertContext, AlertContextProvider } from "alertContext/AlertContext";
 | 
			
		||||
export { AlertContext, AlertContextProvider, useAlert } from "alertContext/AlertContext";
 | 
			
		||||
 | 
			
		||||
// Testing utilities
 | 
			
		||||
export { mockJSONError } from "testUtils/network";
 | 
			
		||||
export { mockSetAlert, mockDismissAlert } from "testUtils/alertContextMock";
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										23
									
								
								src/testUtils/alertContextMock.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/testUtils/alertContextMock.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
/*
 | 
			
		||||
 * 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 { AlertContext } from "../alertContext/AlertContext";
 | 
			
		||||
 | 
			
		||||
const mockSetAlert = jest.fn();
 | 
			
		||||
const mockDismissAlert = jest.fn();
 | 
			
		||||
 | 
			
		||||
function AlertContextMock({ children }) {
 | 
			
		||||
    return (
 | 
			
		||||
        <AlertContext.Provider value={[mockSetAlert, mockDismissAlert]}>
 | 
			
		||||
                { children }
 | 
			
		||||
        </AlertContext.Provider>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export { AlertContextMock, mockSetAlert, mockDismissAlert };
 | 
			
		||||
@@ -7,31 +7,37 @@
 | 
			
		||||
 | 
			
		||||
/* eslint import/export: "off" */
 | 
			
		||||
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
import {UIDReset} from 'react-uid';
 | 
			
		||||
import {StaticRouter} from 'react-router';
 | 
			
		||||
import {render} from '@testing-library/react'
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { UIDReset } from "react-uid";
 | 
			
		||||
import { StaticRouter } from "react-router";
 | 
			
		||||
import { render } from "@testing-library/react";
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
 | 
			
		||||
import { AlertContextMock } from "alertContextMock";
 | 
			
		||||
 | 
			
		||||
Wrapper.propTypes = {
 | 
			
		||||
    children: PropTypes.oneOfType([
 | 
			
		||||
        PropTypes.arrayOf(PropTypes.node),
 | 
			
		||||
        PropTypes.node
 | 
			
		||||
    ])
 | 
			
		||||
        PropTypes.node,
 | 
			
		||||
    ]),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function Wrapper({children}) {
 | 
			
		||||
    return <StaticRouter>
 | 
			
		||||
        <UIDReset>
 | 
			
		||||
            {children}
 | 
			
		||||
        </UIDReset>
 | 
			
		||||
    </StaticRouter>
 | 
			
		||||
function Wrapper({ children }) {
 | 
			
		||||
    return (
 | 
			
		||||
        <AlertContextMock>
 | 
			
		||||
            <StaticRouter>
 | 
			
		||||
                <UIDReset>
 | 
			
		||||
                    {children}
 | 
			
		||||
                </UIDReset>
 | 
			
		||||
            </StaticRouter>
 | 
			
		||||
        </AlertContextMock>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const customTestRender = (ui, options) => render(ui, {wrapper: Wrapper, ...options});
 | 
			
		||||
const customTestRender = (ui, options) => render(ui, { wrapper: Wrapper, ...options });
 | 
			
		||||
 | 
			
		||||
// re-export everything
 | 
			
		||||
export * from '@testing-library/react'
 | 
			
		||||
export * from "@testing-library/react";
 | 
			
		||||
 | 
			
		||||
// override render method
 | 
			
		||||
export {customTestRender as render}
 | 
			
		||||
export { customTestRender as render };
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								src/testUtils/network.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/testUtils/network.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
/*
 | 
			
		||||
 * 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 mockAxios from 'jest-mock-axios';
 | 
			
		||||
 | 
			
		||||
export function mockJSONError(data) {
 | 
			
		||||
    mockAxios.mockError({ response: { data, headers: { "content-type": "application/json" } } });
 | 
			
		||||
}
 | 
			
		||||
@@ -15,12 +15,15 @@ global.afterEach(() => {
 | 
			
		||||
 | 
			
		||||
// Mock babel (gettext)
 | 
			
		||||
global._ = str => str;
 | 
			
		||||
global.ngettext = str => str;
 | 
			
		||||
global.babel = {format: (str) => str};
 | 
			
		||||
global.ForisTranslations = {};
 | 
			
		||||
 | 
			
		||||
// Mock web sockets
 | 
			
		||||
window.WebSocket = jest.fn();
 | 
			
		||||
 | 
			
		||||
// Mock scrollIntoView
 | 
			
		||||
global.HTMLElement.prototype.scrollIntoView = () => {
 | 
			
		||||
};
 | 
			
		||||
global.HTMLElement.prototype.scrollIntoView = () => {};
 | 
			
		||||
 | 
			
		||||
jest.doMock('moment', () => {
 | 
			
		||||
    moment.tz.setDefault('UTC');
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user