浏览代码

Merge branch 'develop' into docs/extension_chart

Nam Nguyen 5 月之前
父节点
当前提交
db4176f09c

文件差异内容过多而无法显示
+ 263 - 266
frontend/taipy-gui/package-lock.json


+ 3 - 3
frontend/taipy-gui/package.json

@@ -116,9 +116,9 @@
     "ts-jest": "^29.0.0",
     "ts-jest-mock-import-meta": "^1.2.0",
     "ts-loader": "^9.2.6",
-    "typedoc": "^0.26.3",
-    "typedoc-plugin-markdown": "^4.1.1",
-    "typescript": "^5.5.3",
+    "typedoc": "^0.27.5",
+    "typedoc-plugin-markdown": "^4.3.2",
+    "typescript": "^5.7.2",
     "webpack": "^5.61.0",
     "webpack-cli": "^5.0.0"
   }

+ 2 - 2
frontend/taipy-gui/src/components/Taipy/Chart.tsx

@@ -280,8 +280,8 @@ const updateArrays = (sel: number[][], val: number[], idx: number) => {
     return sel;
 };
 
-const getDataKey = (columns: Record<string, ColumnDesc>, decimators?: string[]): [string[], string] => {
-    const backCols = Object.values(columns).map((col) => col.dfid);
+const getDataKey = (columns?: Record<string, ColumnDesc>, decimators?: string[]): [string[], string] => {
+    const backCols = columns ? Object.values(columns).map((col) => col.dfid) : [];
     return [backCols, backCols.join("-") + (decimators ? `--${decimators.join("")}` : "")];
 };
 

+ 31 - 16
frontend/taipy-gui/src/components/Taipy/Dialog.spec.tsx

@@ -44,7 +44,7 @@ describe("Dialog Component", () => {
         const { getByText } = render(
             <HelmetProvider>
                 <Dialog title="Dialog-Test-Title" page="page" open={true} />
-            </HelmetProvider>,
+            </HelmetProvider>
         );
         const elt = getByText("Dialog-Test-Title");
         expect(elt.tagName).toBe("H2");
@@ -54,7 +54,7 @@ describe("Dialog Component", () => {
         const { queryAllByText } = render(
             <HelmetProvider>
                 <Dialog title="Dialog-Test-Title" page="page" open={false} />
-            </HelmetProvider>,
+            </HelmetProvider>
         );
         expect(queryAllByText("Dialog-Test-Title")).toHaveLength(0);
         const divs = document.getElementsByTagName("div");
@@ -65,7 +65,7 @@ describe("Dialog Component", () => {
         const wrapper = render(
             <HelmetProvider>
                 <Dialog title="Dialog-Test-Title" page="page" open={true} className="taipy-dialog" />
-            </HelmetProvider>,
+            </HelmetProvider>
         );
         const elt = document.querySelector(".MuiDialog-root");
         expect(elt).toHaveClass("taipy-dialog");
@@ -79,7 +79,7 @@ describe("Dialog Component", () => {
                     defaultOpen="true"
                     open={undefined as unknown as boolean}
                 />
-            </HelmetProvider>,
+            </HelmetProvider>
         );
         getByText("Dialog-Test-Title");
     });
@@ -92,7 +92,7 @@ describe("Dialog Component", () => {
                     defaultOpen="true"
                     open={undefined as unknown as boolean}
                 />
-            </HelmetProvider>,
+            </HelmetProvider>
         );
         expect(getAllByRole("button")).toHaveLength(1);
     });
@@ -106,7 +106,7 @@ describe("Dialog Component", () => {
                     open={undefined as unknown as boolean}
                     labels={JSON.stringify(["toto"])}
                 />
-            </HelmetProvider>,
+            </HelmetProvider>
         );
         expect(getAllByRole("button")).toHaveLength(2);
     });
@@ -120,7 +120,7 @@ describe("Dialog Component", () => {
                     open={undefined as unknown as boolean}
                     labels={JSON.stringify(["toto", "titi", "toto"])}
                 />
-            </HelmetProvider>,
+            </HelmetProvider>
         );
         expect(getAllByRole("button")).toHaveLength(4);
     });
@@ -134,7 +134,7 @@ describe("Dialog Component", () => {
                     active={false}
                     labels={JSON.stringify(["testValidate", "testCancel"])}
                 />
-            </HelmetProvider>,
+            </HelmetProvider>
         );
         expect(getByText("testValidate")).toBeDisabled();
         expect(getByText("testCancel")).toBeDisabled();
@@ -148,7 +148,7 @@ describe("Dialog Component", () => {
                     open={true}
                     labels={JSON.stringify(["testValidate", "testCancel"])}
                 />
-            </HelmetProvider>,
+            </HelmetProvider>
         );
         expect(getByText("testValidate")).not.toBeDisabled();
         expect(getByText("testCancel")).not.toBeDisabled();
@@ -163,7 +163,7 @@ describe("Dialog Component", () => {
                     active={true}
                     labels={JSON.stringify(["testValidate", "testCancel"])}
                 />
-            </HelmetProvider>,
+            </HelmetProvider>
         );
         expect(getByText("testValidate")).not.toBeDisabled();
         expect(getByText("testCancel")).not.toBeDisabled();
@@ -183,7 +183,7 @@ describe("Dialog Component", () => {
                         onAction="testValidateAction"
                     />
                 </HelmetProvider>
-            </TaipyContext.Provider>,
+            </TaipyContext.Provider>
         );
         await userEvent.click(getByTitle("Close"));
         expect(dispatch).toHaveBeenLastCalledWith({
@@ -208,7 +208,7 @@ describe("Dialog Component", () => {
                         onAction="testValidateAction"
                     />
                 </HelmetProvider>
-            </TaipyContext.Provider>,
+            </TaipyContext.Provider>
         );
         await userEvent.click(getByText("testValidate"));
         expect(dispatch).toHaveBeenLastCalledWith({
@@ -233,7 +233,7 @@ describe("Dialog Component", () => {
                         onAction="testValidateAction"
                     />
                 </HelmetProvider>
-            </TaipyContext.Provider>,
+            </TaipyContext.Provider>
         );
         await userEvent.click(getByText("Another One"));
         expect(dispatch).toHaveBeenLastCalledWith({
@@ -259,13 +259,28 @@ describe("Dialog Component", () => {
         expect(computedStyles.width).not.toBe("500px");
         expect(computedStyles.height).not.toBe("300px");
     });
-    it("calls localAction prop when handleAction is triggered", () => {
+    it("calls localAction prop when handleAction is triggered", async () => {
         const localActionMock = jest.fn();
         const { getByLabelText } = render(
-            <Dialog id="test-dialog" title="Test Dialog" localAction={localActionMock} open={true} />,
+            <Dialog id="test-dialog" title="Test Dialog" localAction={localActionMock} open={true} />
         );
         const closeButton = getByLabelText("close");
-        fireEvent.click(closeButton);
+        await fireEvent.click(closeButton);
+        expect(localActionMock).toHaveBeenCalledWith(-1);
+    });
+    it("shows a popup", async () => {
+        const localActionMock = jest.fn();
+        const { getByText } = render(
+            <>
+                <Dialog id="test-dialog" title="" open={true} popup={true} localAction={localActionMock}>
+                    Hello
+                </Dialog>
+                <div>Outside</div>
+            </>
+        );
+        const Hello = getByText("Hello");
+        const Outside = getByText("Outside");
+        await userEvent.keyboard("{Escape}")
         expect(localActionMock).toHaveBeenCalledWith(-1);
     });
 });

+ 49 - 5
frontend/taipy-gui/src/components/Taipy/Dialog.tsx

@@ -17,10 +17,11 @@ import DialogTitle from "@mui/material/DialogTitle";
 import MuiDialog from "@mui/material/Dialog";
 import DialogActions from "@mui/material/DialogActions";
 import DialogContent from "@mui/material/DialogContent";
-import Tooltip from "@mui/material/Tooltip";
 import IconButton from "@mui/material/IconButton";
-import CloseIcon from "@mui/icons-material/Close";
+import Popover, { PopoverOrigin } from "@mui/material/Popover";
+import Tooltip from "@mui/material/Tooltip";
 import { SxProps, Theme } from "@mui/system";
+import CloseIcon from "@mui/icons-material/Close";
 
 import { createSendActionNameAction } from "../../context/taipyReducers";
 import TaipyRendered from "../pages/TaipyRendered";
@@ -41,6 +42,9 @@ interface DialogProps extends TaipyActiveProps {
     height?: string | number;
     width?: string | number;
     localAction?: (idx: number) => void;
+    refId?: string;
+    defaultRefId?: string;
+    popup?: boolean;
 }
 
 const closeSx: SxProps<Theme> = {
@@ -51,6 +55,29 @@ const closeSx: SxProps<Theme> = {
 };
 const titleSx = { m: 0, p: 2, display: "flex", paddingRight: "0.1em" };
 
+const virtualElt = {
+    nodeType: 1,
+    getBoundingClientRect: () => {
+        const x = (document.body.offsetWidth - document.body.offsetLeft) / 2;
+        const y = (document.body.offsetHeight - document.body.offsetTop) / 2;
+        return {
+            x,
+            y,
+            width: 0,
+            height: 0,
+            top: y,
+            left: x,
+            bottom: y,
+            right: x,
+        };
+    },
+} as Element;
+
+const popoverAnchor: PopoverOrigin = {
+    vertical: "center",
+    horizontal: "center",
+};
+
 const Dialog = (props: DialogProps) => {
     const {
         id,
@@ -64,6 +91,7 @@ const Dialog = (props: DialogProps) => {
         partial,
         width,
         height,
+        popup = false,
     } = props;
     const dispatch = useDispatch();
     const module = useModule();
@@ -71,6 +99,7 @@ const Dialog = (props: DialogProps) => {
     const className = useClassNames(props.libClassName, props.dynamicClassName, props.className);
     const active = useDynamicProperty(props.active, props.defaultActive, true);
     const hover = useDynamicProperty(props.hoverText, props.defaultHoverText, undefined);
+    const refId = useDynamicProperty(props.refId, props.defaultRefId, undefined);
 
     const handleAction = useCallback(
         (evt: MouseEvent<HTMLElement>) => {
@@ -110,7 +139,22 @@ const Dialog = (props: DialogProps) => {
         return {};
     }, [width, height]);
 
-    return (
+    const getAnchorEl = useCallback(() => (refId && document.querySelector(refId)) || virtualElt, [refId]);
+
+    return popup ? (
+        <Popover
+            id={id}
+            onClose={handleAction}
+            open={open === undefined ? defaultOpen === "true" || defaultOpen === true : !!open}
+            className={`${className} ${getComponentClassName(props.children)}`}
+            sx={paperProps.sx}
+            anchorEl={getAnchorEl}
+            anchorOrigin={popoverAnchor}
+        >
+            {page ? <TaipyRendered path={"/" + page} partial={partial} fromBlock={true} /> : null}
+            {props.children}
+        </Popover>
+    ) : (
         <MuiDialog
             id={id}
             onClose={handleAction}
@@ -133,9 +177,9 @@ const Dialog = (props: DialogProps) => {
             </DialogContent>
             {labels.length ? (
                 <DialogActions>
-                    {labels.map((l, i) => (
+                    {labels.map((label, i) => (
                         <Button onClick={handleAction} disabled={!active} key={"label" + i} data-idx={i}>
-                            {l}
+                            {label}
                         </Button>
                     ))}
                 </DialogActions>

+ 6 - 0
frontend/taipy-gui/src/components/Taipy/Login.spec.tsx

@@ -65,4 +65,10 @@ describe("Login Component", () => {
             type: "SEND_ACTION_ACTION",
         });
     });
+    it("shows additional buttons", async () => {
+        const { getByText } = render(<Login labels={JSON.stringify(["Button one", "Second button"])} />);
+        const elt = getByText("Button one");
+        expect(elt).toBeInTheDocument();
+        expect(elt.tagName).toBe("BUTTON");
+    });
 });

+ 20 - 3
frontend/taipy-gui/src/components/Taipy/Login.tsx

@@ -40,6 +40,7 @@ interface LoginProps extends TaipyBaseProps {
     onAction?: string;
     defaultMessage?: string;
     message?: string;
+    labels?: string;
 }
 
 const closeSx: SxProps<Theme> = {
@@ -64,13 +65,24 @@ const Login = (props: LoginProps) => {
 
     const className = useClassNames(props.libClassName, props.dynamicClassName, props.className);
 
+    const labels = useMemo(() => {
+        if (props.labels) {
+            try {
+                return JSON.parse(props.labels) as string[];
+            } catch (e) {
+                console.info(`Error parsing login.labels\n${(e as Error).message || e}`);
+            }
+        }
+        return [];
+    }, [props.labels]);
+
     const handleAction = useCallback(
         (evt: MouseEvent<HTMLElement>) => {
-            const { close } = evt?.currentTarget.dataset || {};
+            const { close, idx } = evt?.currentTarget.dataset || {};
             const args = close
                 ? [null, null, document.location.pathname.substring(1)]
-                : [user, password, document.location.pathname.substring(1)];
-            setShowProgress(true);
+                : idx ? [user, password, document.location.pathname.substring(1), parseInt(idx, 10)]: [user, password, document.location.pathname.substring(1)];
+            setShowProgress(!idx);
             dispatch(createSendActionNameAction(id, module, onAction, ...args));
         },
         [user, password, dispatch, id, onAction, module]
@@ -168,6 +180,11 @@ const Login = (props: LoginProps) => {
                 <DialogContentText>{message || defaultMessage}</DialogContentText>
             </DialogContent>
             <DialogActions>
+                {labels.map((label, i) => (
+                    <Button onClick={handleAction} key={"label" + i} data-idx={i}>
+                        {label}
+                    </Button>
+                ))}
                 <Button
                     variant="outlined"
                     className={getSuffixedClassNames(className, "-button")}

+ 3 - 29
frontend/taipy-gui/src/components/Taipy/Metric.spec.tsx

@@ -274,14 +274,13 @@ describe("Metric Component", () => {
         });
     });
 
-    it("applies style correctly when height is undefined", async () => {
+    it("applies style correctly when height and width are undefined", async () => {
         const { container } = render(<Metric />);
         await waitFor(() => {
             const elt = container.querySelector(".js-plotly-plot");
             expect(elt).toHaveStyle({
-                width: "100%",
-                position: "relative",
-                display: "inline-block",
+                width: "20vw",
+                height: "20vh",
             });
         });
     });
@@ -291,10 +290,7 @@ describe("Metric Component", () => {
         await waitFor(() => {
             const elt = container.querySelector(".js-plotly-plot");
             expect(elt).toHaveStyle({
-                width: "100%",
                 height: "100px",
-                position: "relative",
-                display: "inline-block",
             });
         });
     });
@@ -304,10 +300,7 @@ describe("Metric Component", () => {
         await waitFor(() => {
             const elt = container.querySelector(".js-plotly-plot");
             expect(elt).toHaveStyle({
-                width: "100%",
                 height: "30em",
-                position: "relative",
-                display: "inline-block",
             });
         });
     });
@@ -317,10 +310,7 @@ describe("Metric Component", () => {
         await waitFor(() => {
             const elt = container.querySelector(".js-plotly-plot");
             expect(elt).toHaveStyle({
-                width: "100%",
                 height: "30%",
-                position: "relative",
-                display: "inline-block",
             });
         });
     });
@@ -330,10 +320,7 @@ describe("Metric Component", () => {
         await waitFor(() => {
             const elt = container.querySelector(".js-plotly-plot");
             expect(elt).toHaveStyle({
-                width: "100%",
                 height: "30vh",
-                position: "relative",
-                display: "inline-block",
             });
         });
     });
@@ -343,10 +330,7 @@ describe("Metric Component", () => {
         await waitFor(() => {
             const elt = container.querySelector(".js-plotly-plot");
             expect(elt).toHaveStyle({
-                width: "100%",
                 height: "30vw",
-                position: "relative",
-                display: "inline-block",
             });
         });
     });
@@ -357,8 +341,6 @@ describe("Metric Component", () => {
             const elt = container.querySelector(".js-plotly-plot");
             expect(elt).toHaveStyle({
                 width: "100px",
-                position: "relative",
-                display: "inline-block",
             });
         });
     });
@@ -369,8 +351,6 @@ describe("Metric Component", () => {
             const elt = container.querySelector(".js-plotly-plot");
             expect(elt).toHaveStyle({
                 width: "30em",
-                position: "relative",
-                display: "inline-block",
             });
         });
     });
@@ -381,8 +361,6 @@ describe("Metric Component", () => {
             const elt = container.querySelector(".js-plotly-plot");
             expect(elt).toHaveStyle({
                 width: "30%",
-                position: "relative",
-                display: "inline-block",
             });
         });
     });
@@ -393,8 +371,6 @@ describe("Metric Component", () => {
             const elt = container.querySelector(".js-plotly-plot");
             expect(elt).toHaveStyle({
                 width: "30vh",
-                position: "relative",
-                display: "inline-block",
             });
         });
     });
@@ -405,8 +381,6 @@ describe("Metric Component", () => {
             const elt = container.querySelector(".js-plotly-plot");
             expect(elt).toHaveStyle({
                 width: "30vw",
-                position: "relative",
-                display: "inline-block",
             });
         });
     });

+ 34 - 27
frontend/taipy-gui/src/components/Taipy/Metric.tsx

@@ -52,11 +52,25 @@ interface MetricProps extends TaipyBaseProps, TaipyHoverProps {
     template_Light_?: string;
 }
 
-const emptyLayout = {} as Partial<Layout>;
-const defaultStyle = { position: "relative", display: "inline-block", width: "100%" } as CSSProperties;
+const defaultLayout = { margin: { l: 50, r: 50, t: 50, b: 50 } } as Partial<Layout>;
+const defaultStyle = {
+    position: "relative",
+    display: "inline-block",
+    /*
+    * When updating the width and height, be sure to adjust the corresponding Metric values in the viselements.json file accordingly.
+    * */
+    width: "20vw",
+    height: "20vh",
+} as CSSProperties;
 const skeletonStyle = { ...defaultStyle, minHeight: "7em" };
 const plotConfig = { displaylogo: false };
-const boxStyle = { height: "100vh" };
+
+const normalizeSize = (val: string | number | undefined): string | undefined => {
+    if (typeof val === "number" || (typeof val === "string" && /^\d+$/.test(val))) {
+        return `${val}px`;
+    }
+    return val;
+};
 
 const Metric = (props: MetricProps) => {
     const { showValue = true } = props;
@@ -64,7 +78,7 @@ const Metric = (props: MetricProps) => {
     const threshold = useDynamicProperty(props.threshold, props.defaultThreshold, undefined);
     const delta = useDynamicProperty(props.delta, props.defaultDelta, undefined);
     const className = useClassNames(props.libClassName, props.dynamicClassName, props.className);
-    const baseLayout = useDynamicJsonProperty(props.layout, props.defaultLayout || "", emptyLayout);
+    const baseLayout = useDynamicJsonProperty(props.layout, props.defaultLayout || "", defaultLayout);
     const hover = useDynamicProperty(props.hoverText, props.defaultHoverText, undefined);
     const theme = useTheme();
 
@@ -192,34 +206,27 @@ const Metric = (props: MetricProps) => {
     ]);
 
     const style = useMemo(() => {
-        const normalizeSize = (val: string | number | undefined): string | undefined => {
-            if (typeof val === "number" || (typeof val === "string" && /^\d+$/.test(val))) {
-                return `${val}px`;
-            }
-            return val;
-        };
-
-        const width = props.width ? normalizeSize(props.width) : "100%";
-        const height = props.height ? normalizeSize(props.height) : undefined;
+        const width = props.width ? normalizeSize(props.width) : defaultStyle.width;
+        const height = props.height ? normalizeSize(props.height) : defaultStyle.height;
 
         return { ...defaultStyle, width, height };
     }, [props.width, props.height]);
 
     return (
-            <Tooltip title={hover || ""}>
-                <Box className={`${className} ${getComponentClassName(props.children)}`} style={boxStyle}>
-                    <Suspense fallback={<Skeleton key="skeleton" sx={skeletonStyle} />}>
-                        <Plot
-                            data={data}
-                            layout={layout}
-                            style={style}
-                            config={plotConfig}
-                            useResizeHandler
-                        />
-                    </Suspense>
-                    {props.children}
-                </Box>
-            </Tooltip>
+        <Tooltip title={hover || ""}>
+            <Box className={`${className} ${getComponentClassName(props.children)}`}>
+                <Suspense fallback={<Skeleton key="skeleton" sx={skeletonStyle} />}>
+                    <Plot
+                        data={data}
+                        layout={layout}
+                        style={style}
+                        config={plotConfig}
+                        useResizeHandler
+                    />
+                </Suspense>
+                {props.children}
+            </Box>
+        </Tooltip>
     );
 };
 

+ 14 - 0
taipy/core/config/checkers/_task_config_checker.py

@@ -41,6 +41,7 @@ class _TaskConfigChecker(_ConfigChecker):
                 self._check_inputs(task_config_id, task_config)
                 self._check_outputs(task_config_id, task_config)
                 self._check_if_children_config_id_is_overlapping_with_properties(task_config_id, task_config)
+                self._check_required_properties(task_config_id, task_config)
         return self._collector
 
     def _check_if_children_config_id_is_overlapping_with_properties(self, task_config_id: str, task_config: TaskConfig):
@@ -88,3 +89,16 @@ class _TaskConfigChecker(_ConfigChecker):
                 f"{task_config._FUNCTION} field of TaskConfig `{task_config_id}` must be"
                 f" populated with Callable value.",
             )
+
+    def _check_required_properties(self, task_config_id: str, task_config: TaskConfig):
+        task_config_properties = task_config.properties
+        for task_type, required_keys in TaskConfig._REQUIRED_PROPERTIES.items():
+            if task_config_properties.get(task_type, False):
+                for required_key in required_keys:
+                    if task_config_properties.get(required_key, None) is None:
+                        self._error(
+                            required_key,
+                            None,
+                            f"TaskConfig `{task_config_id}` is either missing the required property "
+                            f"`{required_key}` or the value is set to None.",
+                        )

+ 3 - 0
taipy/core/config/task_config.py

@@ -50,6 +50,9 @@ class TaskConfig(Section):
     (*exposed_type* field) of the input data nodes and returning results compatible with the
     data type (*exposed_type* field) of the outputs list."""
 
+    # NOTE: # {task_type: ["required_key1"]}
+    _REQUIRED_PROPERTIES: Dict[str, List[str]] = {}
+
     def __init__(
         self,
         id: str,

+ 3 - 0
taipy/gui/_renderers/factory.py

@@ -215,6 +215,8 @@ class _Factory:
                 ("width", PropertyType.string_or_number),
                 ("height", PropertyType.string_or_number),
                 ("hover_text", PropertyType.dynamic_string),
+                ("ref_id", PropertyType.dynamic_string),
+                ("popup", PropertyType.boolean),
             ]
         )
         ._set_propagate(),
@@ -349,6 +351,7 @@ class _Factory:
             [
                 ("message", PropertyType.dynamic_string),
                 ("on_action", PropertyType.function, "on_login"),
+                ("labels", PropertyType.string_list),
             ]
         ),
         "menu": lambda gui, control_type, attrs: _Builder(

+ 11 - 5
taipy/gui/viselements.json

@@ -1427,14 +1427,14 @@
                     {
                         "name": "width",
                         "type": "Union[str,number]",
-                        "default_value": "None",
-                        "doc": "The width of the metric control, in CSS units."
+                        "default_value": "\"20vw\"",
+                        "doc": "The width of the metric control, in CSS units"
                     },
                     {
                         "name": "height",
                         "type": "Union[str,number]",
-                        "default_value": "None",
-                        "doc": "The height of the metric control, in CSS units."
+                        "default_value": "\"20vh\"",
+                        "doc": "The height of the metric control, in CSS units"
                     },
                     {
                         "name": "layout",
@@ -1760,6 +1760,11 @@
                         "name": "message",
                         "type": "dynamic(str)",
                         "doc": "The message shown in the dialog."
+                    },
+                    {
+                        "name": "labels",
+                        "type": "Union[str,list[str]]",
+                        "doc": "A list of labels to show in a row of buttons at the bottom of the dialog. The index of the button in the list is reported as args in the <code>on_action</code> callback (that index is -1 for the <i>close</i> icon)."
                     }
                 ]
             }
@@ -2013,7 +2018,8 @@
                         "name": "height",
                         "type": "Union[str,int,float]",
                         "doc": "The height of the dialog, in CSS units."
-                    }
+                    },
+                    {"name": "ref_id", "type": "dynamic(str)", "doc": "TODO an id or a query selector that allows to identify an HTML component that would be the anchor for the dialog."}
                 ]
             }
         ],

+ 48 - 0
tests/core/config/checkers/test_task_config_checker.py

@@ -311,3 +311,51 @@ class TestTaskConfigChecker:
         Config.check()
         assert len(Config._collector.errors) == 0
         assert len(Config._collector.warnings) == 2
+
+    def test_check_required_property(self, caplog):
+        prev_required_properties = TaskConfig._REQUIRED_PROPERTIES.copy()
+
+        TaskConfig._REQUIRED_PROPERTIES = {"task_type": ["required_key_1", "required_key_2"]}
+
+        config = Config._applied_config
+        Config._compile_configs()
+
+        config._sections[TaskConfig.name]["default"]._outputs = "bar"
+        Config._collector = IssueCollector()
+        Config.check()
+        assert len(Config._collector.errors) == 0
+        assert len(Config._collector.warnings) == 0
+
+        config._sections[TaskConfig.name]["new"] = config._sections[TaskConfig.name]["default"]
+        config._sections[TaskConfig.name]["new"].id = "new"
+        config._sections[TaskConfig.name]["new"].function = print
+        config._sections[TaskConfig.name]["new"]._outputs = [DataNodeConfig("bar")]
+        config._sections[TaskConfig.name]["new"]._properties = {"task_type": True, "required_key_1": None}
+        with pytest.raises(SystemExit):
+            Config._collector = IssueCollector()
+            Config.check()
+        assert len(Config._collector.errors) == 2
+        expected_error_message_1 = (
+            "TaskConfig `new` is either missing the required property `required_key_1` or the value is set to None."
+        )
+        assert expected_error_message_1 in caplog.text
+        expected_error_message_2 = (
+            "TaskConfig `new` is either missing the required property `required_key_2` or the value is set to None."
+        )
+        assert expected_error_message_2 in caplog.text
+        assert len(Config._collector.warnings) == 1
+
+        TaskConfig._REQUIRED_PROPERTIES = prev_required_properties
+
+        config._sections[TaskConfig.name]["new"] = config._sections[TaskConfig.name]["default"]
+        config._sections[TaskConfig.name]["new"].id = "new"
+        config._sections[TaskConfig.name]["new"].function = print
+        config._sections[TaskConfig.name]["new"]._outputs = [DataNodeConfig("bar")]
+        config._sections[TaskConfig.name]["new"]._properties = {
+            "task_type": True,
+            "required_key_1": "sthg",
+            "required_key_2": "sthg",
+        }
+        Config._collector = IssueCollector()
+        Config.check()
+        assert len(Config._collector.errors) == 0

+ 37 - 0
tests/gui/e2e/test_resizing.py

@@ -0,0 +1,37 @@
+import inspect
+from importlib import util
+
+import pandas
+import pytest
+
+if util.find_spec("playwright"):
+    from playwright._impl._page import Page
+
+from taipy.gui import Gui
+
+
+@pytest.mark.teste2e
+def test_has_default_value(page: Page, gui: Gui, helpers):
+    percentages = [
+        (1852, 50.83),
+        (1856, 45.29),
+        (1860, 39.65),
+        (1864, 55.03),
+    ]
+    data = pandas.DataFrame(percentages, columns=["Year", "%"]) # noqa: F841
+    page_md = "<|{data}|chart|type=bar|x=Year|y=%|>"
+    gui._set_frame(inspect.currentframe())
+    gui.add_page(name="test",page=page_md)
+    helpers.run_e2e(gui)
+    page.goto("./test")
+    page.wait_for_timeout(3000)
+    page.wait_for_selector(".plot-container")
+    page.set_viewport_size({"width": 800, "height": 600})
+    elements = page.locator(
+        'path[style*="vector-effect: non-scaling-stroke; opacity: 1; stroke-width: 0px; fill: rgb(99, 110, 250); fill-opacity: 1;"]') # noqa: E501
+    first_element = elements.first
+    box_before = first_element.bounding_box()
+    page.set_viewport_size({"width": 1920, "height": 1080})
+    page.wait_for_timeout(1000)
+    box_after = first_element.bounding_box()
+    assert box_after["width"] > box_before["width"]

部分文件因为文件数量过多而无法显示