From a42a0019c6bbf790d8368d64ffe39e0ea6be4f67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fred=20Lef=C3=A9v=C3=A8re-Laoide?= <90181748+FredLL-Avaiga@users.noreply.github.com> Date: Fri, 22 Sep 2023 19:45:41 +0200 Subject: [PATCH 01/10] before download callback when content is None (#925) (#926) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * #925 before download callback when content is None * fix test warning * mui/lab =>mui/x-tree-view * pass args as a list * fix test * test empty url * mock xmlhttprequest * mock xmlhttprequest * fix test * add test with url --------- Co-authored-by: Fred Lefévère-Laoide --- gui/package-lock.json | 65 +++++-------------- gui/package.json | 3 +- .../components/Taipy/FileDownload.spec.tsx | 37 +++++++++-- gui/src/components/Taipy/FileDownload.tsx | 34 ++++++---- gui/src/components/Taipy/GuiDownload.spec.tsx | 13 +++- gui/src/components/Taipy/GuiDownload.tsx | 4 +- gui/src/components/Taipy/TableFilter.spec.tsx | 4 +- gui/src/components/Taipy/TreeView.tsx | 4 +- gui/webpack.config.js | 2 +- src/taipy/gui/viselements.json | 4 +- 10 files changed, 94 insertions(+), 76 deletions(-) diff --git a/gui/package-lock.json b/gui/package-lock.json index e916b921c..db7b7ac08 100644 --- a/gui/package-lock.json +++ b/gui/package-lock.json @@ -11,9 +11,9 @@ "@emotion/react": "^11.10.0", "@emotion/styled": "^11.10.0", "@mui/icons-material": "^5.0.5", - "@mui/lab": "^5.0.0-alpha.53", "@mui/material": "^5.0.6", "@mui/x-date-pickers": "^6.0.0", + "@mui/x-tree-view": "^6.0.0-alpha.3", "apache-arrow": "^10.0.1", "axios": "^1.2.0", "date-fns": "^2.25.0", @@ -74,6 +74,7 @@ "jest-websocket-mock": "^2.2.1", "lint-staged": "^14.0.1", "mock-socket": "^9.0.7", + "mock-xmlhttprequest": "^8.2.0", "ts-jest": "^29.0.0", "ts-loader": "^9.2.6", "typedoc": "^0.25.1", @@ -1674,47 +1675,6 @@ } } }, - "node_modules/@mui/lab": { - "version": "5.0.0-alpha.144", - "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.144.tgz", - "integrity": "sha512-CS/mBxfX9eSsrvatYBNphYCSCM4tIAIb4jZ3LiX1vSZ32DjRlNi+2U56+ObnBaVTjMMYdegMsT38uxYHSVXszA==", - "dependencies": { - "@babel/runtime": "^7.22.15", - "@mui/base": "5.0.0-beta.15", - "@mui/system": "^5.14.9", - "@mui/types": "^7.2.4", - "@mui/utils": "^5.14.9", - "@mui/x-tree-view": "6.0.0-alpha.1", - "clsx": "^2.0.0", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui" - }, - "peerDependencies": { - "@emotion/react": "^11.5.0", - "@emotion/styled": "^11.3.0", - "@mui/material": "^5.0.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, "node_modules/@mui/material": { "version": "5.14.9", "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.9.tgz", @@ -1962,12 +1922,13 @@ } }, "node_modules/@mui/x-tree-view": { - "version": "6.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/@mui/x-tree-view/-/x-tree-view-6.0.0-alpha.1.tgz", - "integrity": "sha512-JUG3HmBrmGEALbCFg1b+i7h726e1dWYZs4db3syO1j+Q++E3nbvE4Lehp5yGTFm+8esH0Tny50tuJaa4WX6VSA==", + "version": "6.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/@mui/x-tree-view/-/x-tree-view-6.0.0-alpha.3.tgz", + "integrity": "sha512-j6vPD3e4Y4dd8v19bnDkPBXdAD8Qo5pbSEAwB3XYh60tprphBU+YVjCfUo4ZZkYEqhGBen6gKsJhFz98H2v2kg==", "dependencies": { - "@babel/runtime": "^7.22.6", - "@mui/utils": "^5.14.3", + "@babel/runtime": "^7.22.15", + "@mui/base": "^5.0.0-beta.14", + "@mui/utils": "^5.14.8", "@types/react-transition-group": "^4.4.6", "clsx": "^2.0.0", "prop-types": "^15.8.1", @@ -1983,7 +1944,6 @@ "peerDependencies": { "@emotion/react": "^11.9.0", "@emotion/styled": "^11.8.1", - "@mui/base": "^5.0.0-alpha.87", "@mui/material": "^5.8.6", "@mui/system": "^5.8.0", "react": "^17.0.0 || ^18.0.0", @@ -9206,6 +9166,15 @@ "node": ">= 8" } }, + "node_modules/mock-xmlhttprequest": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/mock-xmlhttprequest/-/mock-xmlhttprequest-8.2.0.tgz", + "integrity": "sha512-agEokniRxw/MhrmIl9Ytn5wuj6kK+pu4KaGPV2fGEKStHXH9HIzYPChth2Jb/MB3TMX0ER1F1QMsvMA3BBe3qA==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/mouse-change": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/mouse-change/-/mouse-change-1.4.0.tgz", diff --git a/gui/package.json b/gui/package.json index b33ce81ba..9e9d6b352 100644 --- a/gui/package.json +++ b/gui/package.json @@ -6,9 +6,9 @@ "@emotion/react": "^11.10.0", "@emotion/styled": "^11.10.0", "@mui/icons-material": "^5.0.5", - "@mui/lab": "^5.0.0-alpha.53", "@mui/material": "^5.0.6", "@mui/x-date-pickers": "^6.0.0", + "@mui/x-tree-view": "^6.0.0-alpha.3", "apache-arrow": "^10.0.1", "axios": "^1.2.0", "date-fns": "^2.25.0", @@ -109,6 +109,7 @@ "jest-websocket-mock": "^2.2.1", "lint-staged": "^14.0.1", "mock-socket": "^9.0.7", + "mock-xmlhttprequest": "^8.2.0", "ts-jest": "^29.0.0", "ts-loader": "^9.2.6", "typedoc": "^0.25.1", diff --git a/gui/src/components/Taipy/FileDownload.spec.tsx b/gui/src/components/Taipy/FileDownload.spec.tsx index e403fee25..a7bc44083 100644 --- a/gui/src/components/Taipy/FileDownload.spec.tsx +++ b/gui/src/components/Taipy/FileDownload.spec.tsx @@ -15,6 +15,7 @@ import React from "react"; import { render, waitFor } from "@testing-library/react"; import "@testing-library/jest-dom"; import userEvent from "@testing-library/user-event"; +import { newServer } from 'mock-xmlhttprequest'; import FileDownload from "./FileDownload"; import { TaipyContext } from "../../context/taipyContext"; @@ -59,21 +60,45 @@ describe("FileDownload Component", () => { const elt = getByRole("button"); expect(elt).not.toHaveClass("Mui-disabled"); }); - it("dispatch a well formed message", async () => { + it("dispatch a well formed message when content is empty", async () => { const dispatch = jest.fn(); const state: TaipyState = INITIAL_STATE; const { getByText } = render( - + ); const elt = getByText("label"); await userEvent.click(elt); - await waitFor(() => expect(dispatch).toHaveBeenCalled()); - expect(dispatch).toHaveBeenCalledWith({ + await waitFor(() => expect(dispatch).toHaveBeenCalledWith({ name: "anId", - payload: { args: ["aName"], action: "on_action" }, + payload: { args: ["from.png", ""], action: "on_action" }, type: "SEND_ACTION_ACTION", - }); + })); + }); + it("dispatch a well formed message when content is not empty", async () => { + const server = newServer({ + get: ['/some/link/to.png', { + // status: 200 is the default + //headers: { 'Content-Type': 'application/json' }, + body: '{ "message": "Success!" }', + }], + }); + server.install(); + const dispatch = jest.fn(); + const state: TaipyState = INITIAL_STATE; + const { getByText } = render( + + + + ); + const elt = getByText("label"); + await userEvent.click(elt); + await waitFor(() => expect(dispatch).toHaveBeenCalledWith({ + name: "anId", + payload: { args: ["from.png", "/some/link/to.png"], action: "on_action" }, + type: "SEND_ACTION_ACTION", + })); + server.remove(); }); }); diff --git a/gui/src/components/Taipy/FileDownload.tsx b/gui/src/components/Taipy/FileDownload.tsx index 0ae83b213..ea7a16f9e 100644 --- a/gui/src/components/Taipy/FileDownload.tsx +++ b/gui/src/components/Taipy/FileDownload.tsx @@ -36,7 +36,7 @@ interface FileDownloadProps extends TaipyActiveProps { } const FileDownload = (props: FileDownloadProps) => { - const { id, auto, name, bypassPreview, onAction, label, defaultLabel = "" } = props; + const { id, auto, name = "", bypassPreview, onAction, label, defaultLabel = "" } = props; const aRef = useRef(null); const dispatch = useDispatch(); const module = useModule(); @@ -62,13 +62,31 @@ const FileDownload = (props: FileDownloadProps) => { useEffect(() => { if (auto && aRef.current && active && render) { - runXHR(aRef.current, url, name, onAction ? (() => dispatch(createSendActionNameAction(id, module, onAction, name))) : undefined); + if (url) { + runXHR( + aRef.current, + url, + name, + onAction ? () => dispatch(createSendActionNameAction(id, module, onAction, name, url)) : undefined + ); + } else { + onAction && dispatch(createSendActionNameAction(id, module, onAction, name, url)); + } } }, [active, render, auto, name, url, dispatch, id, onAction, module]); const clickHandler = useCallback(() => { - if (aRef.current && url) { - runXHR(aRef.current, url, name, onAction ? (() => dispatch(createSendActionNameAction(id, module, onAction, name))) : undefined); + if (aRef.current) { + if (url) { + runXHR( + aRef.current, + url, + name, + onAction ? () => dispatch(createSendActionNameAction(id, module, onAction, name, url)) : undefined + ); + } else { + onAction && dispatch(createSendActionNameAction(id, module, onAction, name, url)); + } } }, [url, name, dispatch, id, onAction, module]); @@ -79,13 +97,7 @@ const FileDownload = (props: FileDownloadProps) => { {auto ? null : ( - diff --git a/gui/src/components/Taipy/GuiDownload.spec.tsx b/gui/src/components/Taipy/GuiDownload.spec.tsx index c371454e8..5bfa24f83 100644 --- a/gui/src/components/Taipy/GuiDownload.spec.tsx +++ b/gui/src/components/Taipy/GuiDownload.spec.tsx @@ -14,6 +14,7 @@ import React from "react"; import { render, waitFor } from "@testing-library/react"; import "@testing-library/jest-dom"; +import { newServer } from 'mock-xmlhttprequest'; import GuiDownload from "./GuiDownload"; import { TaipyContext } from "../../context/taipyContext"; @@ -21,6 +22,14 @@ import { TaipyState, INITIAL_STATE } from "../../context/taipyReducers"; describe("GuiDownload Component", () => { it("emits a well formed message", async () => { + const server = newServer({ + get: ['/some/link/to.png', { + // status: 200 is the default + //headers: { 'Content-Type': 'application/json' }, + body: '{ "message": "Success!" }', + }], + }); + server.install(); const dispatch = jest.fn(); const state: TaipyState = INITIAL_STATE; render( @@ -31,9 +40,11 @@ describe("GuiDownload Component", () => { await waitFor(() => expect(dispatch).toHaveBeenCalledWith({ name: "Gui.download", - payload: { args: ["from.png"], action: "onActionMsg" }, + context: undefined, + payload: { args: ["from.png", "/some/link/to.png"], action: "onActionMsg" }, type: "SEND_ACTION_ACTION", }) ); + server.remove(); }); }); diff --git a/gui/src/components/Taipy/GuiDownload.tsx b/gui/src/components/Taipy/GuiDownload.tsx index 18739a885..eb16da869 100644 --- a/gui/src/components/Taipy/GuiDownload.tsx +++ b/gui/src/components/Taipy/GuiDownload.tsx @@ -22,13 +22,13 @@ interface GuiDownloadProps { } const GuiDownload = ({ download }: GuiDownloadProps) => { - const { name, onAction, content } = download || {}; + const { name = "", onAction, content } = download || {}; const dispatch = useDispatch(); const module = useModule(); useEffect(() => { if (content) { - runXHR(undefined, content, name, onAction ? (() => dispatch(createSendActionNameAction("Gui.download", module, onAction, name))) : undefined); + runXHR(undefined, content, name, onAction ? (() => dispatch(createSendActionNameAction("Gui.download", module, onAction, name, content))) : undefined); dispatch(createDownloadAction()); } }, [content, name, dispatch, onAction, module]); diff --git a/gui/src/components/Taipy/TableFilter.spec.tsx b/gui/src/components/Taipy/TableFilter.spec.tsx index 7f32aab98..a27e731f0 100644 --- a/gui/src/components/Taipy/TableFilter.spec.tsx +++ b/gui/src/components/Taipy/TableFilter.spec.tsx @@ -217,7 +217,7 @@ describe("Table Filter Component", () => { it("reset filters", async () => { const onValidate = jest.fn(); const { getByText, getByTestId } = render( - + ); const elt = getByTestId("FilterListIcon"); await userEvent.click(elt); @@ -228,7 +228,7 @@ describe("Table Filter Component", () => { }); it("ignores unapplicable filters", async () => { const { getByText, getByTestId } = render( - + ); const elt = getByTestId("FilterListIcon"); await userEvent.click(elt); diff --git a/gui/src/components/Taipy/TreeView.tsx b/gui/src/components/Taipy/TreeView.tsx index 6f95ecd78..499b0c598 100644 --- a/gui/src/components/Taipy/TreeView.tsx +++ b/gui/src/components/Taipy/TreeView.tsx @@ -13,10 +13,10 @@ import React, { useState, useCallback, useEffect, useMemo, SyntheticEvent, HTMLAttributes, forwardRef, Ref, CSSProperties } from "react"; import Box from "@mui/material/Box"; -import MuiTreeView from "@mui/lab/TreeView"; +import {TreeView as MuiTreeView} from "@mui/x-tree-view/TreeView"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import ChevronRightIcon from "@mui/icons-material/ChevronRight"; -import TreeItem, { TreeItemContentProps, useTreeItem, TreeItemProps } from "@mui/lab/TreeItem"; +import { TreeItem, TreeItemContentProps, useTreeItem, TreeItemProps } from "@mui/x-tree-view/TreeItem"; import Paper from "@mui/material/Paper"; import TextField from "@mui/material/TextField"; import Tooltip from "@mui/material/Tooltip"; diff --git a/gui/webpack.config.js b/gui/webpack.config.js index a72a2847e..32258c2d8 100644 --- a/gui/webpack.config.js +++ b/gui/webpack.config.js @@ -46,7 +46,7 @@ module.exports = (env, options) => { name: reactBundleName, entry: ["react", "react-dom", "@emotion/react","@emotion/styled", - "@mui/icons-material","@mui/lab","@mui/material","@mui/x-date-pickers"], + "@mui/icons-material","@mui/material","@mui/x-date-pickers", "@mui/x-tree-view"], output: { filename: reactBundle + ".dll.js", path: webAppPath, diff --git a/src/taipy/gui/viselements.json b/src/taipy/gui/viselements.json index f5b111582..c3c1088a2 100644 --- a/src/taipy/gui/viselements.json +++ b/src/taipy/gui/viselements.json @@ -526,8 +526,8 @@ { "name": "on_action", "type": "Callback", - "doc": "The name of a function that is triggered when the download is initiated.
All the parameters of that function are optional:\n
    \n
  • state (State^): the state instance.
  • \n
  • id (optional[str]): the identifier of the button.
  • \n
  • action (optional[str]): the name of the action that provoked the change.
  • \n
", - "signature": [["state", "State"], ["id", "str"], ["action", "str"]] + "doc": "The name of a function that is triggered when the download is terminated (or on user action if content is None).
All the parameters of that function are optional:\n
    \n
  • state (State^): the state instance.
  • \n
  • id (optional[str]): the identifier of the button.
  • \n
  • action (optional[str]): the name of the action that provoked the change.
  • \n
  • payload (dict): the details on this callback's invocation.
    \nThis dictionary has the following keys:\n
      \n
    • args: A list of two elements: args[0] reflects the name property and args[1] holds the file URL.
    • \n
    \n
  • \n
", + "signature": [["state", "State"], ["id", "str"], ["action", "str"], ["payload", "dict"]] }, { "name": "auto", From 014df2bf9c4d4081f4894cc68c171a22e0d9793f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fred=20Lef=C3=A9v=C3=A8re-Laoide?= <90181748+FredLL-Avaiga@users.noreply.github.com> Date: Mon, 25 Sep 2023 18:42:48 +0200 Subject: [PATCH 02/10] #927 set hover text on top for dropdown selector (#928) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fred Lefévère-Laoide --- gui/src/components/Taipy/Selector.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/src/components/Taipy/Selector.tsx b/gui/src/components/Taipy/Selector.tsx index 121fdd2e4..04f496aa6 100644 --- a/gui/src/components/Taipy/Selector.tsx +++ b/gui/src/components/Taipy/Selector.tsx @@ -246,7 +246,7 @@ const Selector = (props: SelTreeProps) => { {props.label} ) : null} - + {dropdown ? (