Преглед на файлове

Merge branch 'develop' into feature/add-pyproject

João André преди 10 месеца
родител
ревизия
19e2a747d3

+ 2 - 2
frontend/taipy-gui/base/src/app.ts

@@ -49,7 +49,7 @@ export class TaipyApp {
         path: string | undefined = undefined,
         socket: Socket | undefined = undefined,
     ) {
-        socket = socket || io("/", { autoConnect: false, path: `${getBase()}socket.io` });
+        socket = socket || io("/", { autoConnect: false, path: `${this.getBaseUrl()}socket.io` });
         this.onInit = onInit;
         this.onChange = onChange;
         this.variableData = undefined;
@@ -232,7 +232,7 @@ export class TaipyApp {
 
     updateContext(path: string | undefined = "") {
         if (!path || path === "") {
-            path = window.location.pathname.slice(1);
+            path = window.location.pathname.replace(this.getBaseUrl(), "") || "/"
         }
         this.sendWsMessage("GMC", "get_module_context", { path: path || "/" });
     }

+ 1 - 1
frontend/taipy-gui/base/src/utils.ts

@@ -1,3 +1,3 @@
 export const getBase = () => {
-    return document.getElementsByTagName("base")[0].getAttribute("href");
+    return document.getElementsByTagName("base")[0].getAttribute("href") || "/";
 };

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

@@ -15,6 +15,7 @@ import { useContext, useEffect } from "react";
 import { useLocation, useNavigate } from "react-router-dom";
 import { TaipyContext } from "../../context/taipyContext";
 import { createNavigateAction } from "../../context/taipyReducers";
+import { getBaseURL } from "../../utils";
 
 interface NavigateProps {
     to?: string;
@@ -32,6 +33,7 @@ const Navigate = ({ to, params, tab, force }: NavigateProps) => {
     useEffect(() => {
         if (to) {
             const tos = to === "/" ? to : "/" + to;
+            const navigatePath = getBaseURL() + tos.slice(1)
             const filteredParams = params
                 ? Object.keys(params).reduce((acc, key) => {
                       if (!SPECIAL_PARAMS.includes(key)) {
@@ -56,10 +58,10 @@ const Navigate = ({ to, params, tab, force }: NavigateProps) => {
             // Regular navigate cases
             if (Object.keys(state.locations || {}).some((route) => tos === route)) {
                 const searchParamsLocation = new URLSearchParams(location.search);
-                if (force && location.pathname === tos && searchParamsLocation.toString() === searchParams.toString()) {
+                if (force && location.pathname === navigatePath  && searchParamsLocation.toString() === searchParams.toString()) {
                     navigate(0);
                 } else {
-                    navigate({ pathname: to, search: `?${searchParams.toString()}` });
+                    navigate({ pathname: navigatePath, search: `?${searchParams.toString()}` });
                     // Handle Resource Handler Id
                     const tprh = params?.tprh;
                     if (tprh !== undefined) {

+ 196 - 4
frontend/taipy-gui/src/components/Taipy/Slider.spec.tsx

@@ -18,6 +18,7 @@ import "@testing-library/jest-dom";
 import Slider from "./Slider";
 import { TaipyContext } from "../../context/taipyContext";
 import { TaipyState, INITIAL_STATE } from "../../context/taipyReducers";
+import { LoVElt } from "./lovUtils";
 
 describe("Slider Component", () => {
     it("renders", async () => {
@@ -83,7 +84,9 @@ describe("Slider Component", () => {
         });
     });
     it("holds a numeric range", async () => {
-        const { getByDisplayValue } = render(<Slider defaultValue={"[10,90]"} value={undefined as unknown as number[]} />);
+        const { getByDisplayValue } = render(
+            <Slider defaultValue={"[10,90]"} value={undefined as unknown as number[]} />
+        );
         const elt1 = getByDisplayValue("10");
         expect(elt1.tagName).toBe("INPUT");
         const elt2 = getByDisplayValue("90");
@@ -99,8 +102,8 @@ describe("Slider Component", () => {
         );
         const elts = getAllByText("Item 1");
         expect(elts).toHaveLength(3);
-        expect(elts[0].tagName).toBe("P")
-        expect(elts[1].tagName).toBe("P")
+        expect(elts[0].tagName).toBe("P");
+        expect(elts[1].tagName).toBe("P");
     });
     it("doesn't show text when_text_anchor is none", async () => {
         const { getAllByText } = render(
@@ -115,7 +118,9 @@ describe("Slider Component", () => {
         expect(elts[0].tagName).toBe("P");
     });
     it("holds a lov range", async () => {
-        const { getByDisplayValue } = render(<Slider value={["B", "C"]} defaultLov={'[["A", "A"], ["B", "B"], ["C", "C"], ["D", "D"]]'} />);
+        const { getByDisplayValue } = render(
+            <Slider value={["B", "C"]} defaultLov={'[["A", "A"], ["B", "B"], ["C", "C"], ["D", "D"]]'} />
+        );
         const elt1 = getByDisplayValue("1");
         expect(elt1.tagName).toBe("INPUT");
         const elt2 = getByDisplayValue("2");
@@ -150,4 +155,191 @@ describe("Slider Component", () => {
             type: "SEND_UPDATE_ACTION",
         });
     });
+    it("calls props.onChange when update is true and changeDelay is set", async () => {
+        const dispatch = jest.fn();
+        const state: TaipyState = INITIAL_STATE;
+
+        const { getByRole } = render(
+            <TaipyContext.Provider value={{ state, dispatch }}>
+                <Slider value={33} changeDelay={100} />
+            </TaipyContext.Provider>
+        );
+
+        const slider = getByRole("slider");
+        fireEvent.change(slider, { target: { value: 50 } });
+
+        // Wait for the changeDelay timeout
+        await new Promise((r) => setTimeout(r, 150));
+        expect(dispatch).toHaveBeenCalledWith({
+            name: "",
+            payload: { value: 50 },
+            propagate: true,
+            type: "SEND_UPDATE_ACTION",
+        });
+    });
+    it("should handle change when continuous is set to true", async () => {
+        const dispatch = jest.fn();
+        const state: TaipyState = INITIAL_STATE;
+        const lovArray: [string, string][] = [
+            ["Item 1", "Description 1"],
+            ["Item 2", "Description 2"],
+            ["Item 3", "Description 3"],
+        ];
+        const { getByRole } = render(
+            <TaipyContext.Provider value={{ state, dispatch }}>
+                <Slider value={33} continuous={false} lov={lovArray} />
+            </TaipyContext.Provider>
+        );
+        const slider = getByRole("slider");
+        fireEvent.change(slider, { target: { value: 50 } });
+        expect(dispatch).toHaveBeenCalledWith({
+            name: "",
+            payload: { value: "Item 3" },
+            propagate: true,
+            type: "SEND_UPDATE_ACTION",
+        });
+    });
+    it("returns correct text position and style when textAnchor is set to top", async () => {
+        const dispatch = jest.fn();
+        const state: TaipyState = INITIAL_STATE;
+        const lovList: LoVElt[] = [
+            ["Item 1", "Description 1"],
+            ["Item 2", "Description 2"],
+            ["Item 3", "Description 3"],
+        ];
+        const { container } = render(
+            <TaipyContext.Provider value={{ state, dispatch }}>
+                <Slider changeDelay={0} lov={lovList} textAnchor="top" />
+            </TaipyContext.Provider>
+        );
+        expect(container).toHaveTextContent("Description 1");
+        const sliderContainer = container.querySelector("div");
+        expect(sliderContainer).toHaveStyle("display: inline-grid");
+        expect(sliderContainer).toHaveStyle("text-align: center");
+    });
+    it("returns correct style for textAnchor 'left'", () => {
+        const lovList: LoVElt[] = [
+            ["Item 1", "Description 1"],
+            ["Item 2", "Description 2"],
+            ["Item 3", "Description 3"],
+        ];
+        const { container } = render(<Slider lov={lovList} textAnchor="left" />);
+        const slider = container.querySelector("div");
+        expect(slider).toBeInTheDocument();
+        expect(slider).toHaveStyle("display: inline-grid");
+        expect(slider).toHaveStyle("grid-template-columns: auto 1fr");
+        expect(slider).toHaveStyle("align-items: center");
+    });
+    it("should change the orientation of the slider to horizontal", async () => {
+        render(<Slider value={5} orientation="horizontal" />);
+        const horizontalInputs = document.querySelectorAll('input[aria-orientation="horizontal"]');
+        expect(horizontalInputs).toHaveLength(1);
+    });
+    it("should change the orientation of the slider to vertical", async () => {
+        render(<Slider value={5} orientation="vertical" />);
+        const verticalInputs = document.querySelectorAll('input[aria-orientation="vertical"]');
+        expect(verticalInputs).toHaveLength(1);
+    });
+    it("should change the orientation of the slider to vertical when default value is an array", async () => {
+        render(<Slider orientation="vertical" defaultValue={[1, 2]} />);
+        const verticalInputs = document.querySelectorAll('input[aria-orientation="vertical"]');
+        expect(verticalInputs).toHaveLength(2);
+    });
+    it("should return an array of number when value is an array of number and no lov is defined", async () => {
+        const { getAllByRole } = render(<Slider value={[1, 2]} />);
+        const sliders = getAllByRole("slider");
+        expect(sliders).toHaveLength(2);
+    });
+    it("handles case when label is out of range", async () => {
+        const dispatch = jest.fn();
+        const state: TaipyState = INITIAL_STATE;
+        const lovList: LoVElt[] = [
+            ["Item 1", "Description 1"],
+            ["Item 2", "Description 2"],
+            ["Item 3", "Description 3"],
+        ];
+        const labels = {
+            "Item 4": "Label for Item 4",
+        };
+        const { container } = render(
+            <TaipyContext.Provider value={{ state, dispatch }}>
+                <Slider changeDelay={0} lov={lovList} labels={JSON.stringify(labels)} />
+            </TaipyContext.Provider>
+        );
+        expect(container).not.toHaveTextContent("Label for Item 4");
+    });
+    it("should parse lov when default value is greater than 2 values", async () => {
+        const lovList: LoVElt[] = [
+            ["Item 1", "Description 1"],
+            ["Item 2", "Description 2"],
+            ["Item 3", "Description 3"],
+        ];
+        const { container } = render(<Slider defaultValue='["Item 2", "Item 3"]' lov={lovList} />);
+        const slider = container.querySelector("div");
+        expect(slider).toBeInTheDocument();
+    });
+    it("should parse lov when default value is less than 2 values", async () => {
+        const lovList: LoVElt[] = [
+            ["Item 1", "Description 1"],
+            ["Item 2", "Description 2"],
+            ["Item 3", "Description 3"],
+        ];
+
+        const { container } = render(<Slider defaultValue='["Item 3"]' lov={lovList} />);
+        const slider = container.querySelector("div");
+        expect(slider).toBeInTheDocument();
+    });
+    it("throws an error when defaultValue is an invalid JSON string", () => {
+        const lovList: LoVElt[] = [
+            ["Item 1", "Description 1"],
+            ["Item 2", "Description 2"],
+            ["Item 3", "Description 3"],
+        ];
+
+        const errorSpy = jest.spyOn(global, "Error");
+
+        expect(() => {
+            render(<Slider defaultValue="invalid-json" lov={lovList} />);
+        }).toThrow("Slider lov value couldn't be parsed");
+        expect(errorSpy).toHaveBeenCalledWith("Slider lov value couldn't be parsed");
+        errorSpy.mockRestore();
+    });
+    it("throws an error when defaultValue contains non-numeric values", () => {
+        const errorSpy = jest.spyOn(global, "Error");
+        render(<Slider defaultValue='["Item 1", "Item 2"]' />);
+        expect(errorSpy).toHaveBeenCalledWith("Slider values should all be numbers");
+    });
+    it("should return number when default value is a number", async () => {
+        const { container } = render(<Slider defaultValue={1} />);
+        const slider = container.querySelector("div");
+        expect(slider).toBeInTheDocument();
+    });
+    it("should return number when default value is a number", async () => {
+        const { getByRole } = render(<Slider defaultValue={"10"} />);
+        const inputElement = getByRole("slider", { hidden: true }) as HTMLInputElement;
+        expect(inputElement).toHaveValue("10");
+    });
+    it("should return an array numbers when value is an array of number", async () => {
+        const dispatch = jest.fn();
+        const state: TaipyState = INITIAL_STATE;
+        const lovList: LoVElt[] = [
+            ["1", "Description 1"],
+            ["2", "Description 2"],
+            ["3", "Description 3"],
+        ];
+
+        const { getAllByRole } = render(
+            <TaipyContext.Provider value={{ state, dispatch }}>
+                <Slider value={["1", "2"]} lov={lovList} />
+            </TaipyContext.Provider>
+        );
+        const sliders = getAllByRole("slider");
+        fireEvent.change(sliders[0], { target: { value: "2" } });
+        expect(dispatch).toHaveBeenCalledWith({
+            name: "",
+            payload: { value: ["2", "3"] },
+            propagate: true,
+            type: "SEND_UPDATE_ACTION",
+        });
+    });
 });