From 7889b86d4ab5cd35200117abed9d41469b368e6c Mon Sep 17 00:00:00 2001 From: Yogesh Kumar Baljeet Singh Date: Thu, 12 Dec 2024 14:10:02 +0100 Subject: [PATCH 01/14] Add initial MUI slider component --- .../packages/lib/src/plugins/mui/Slider.tsx | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 chartlets.js/packages/lib/src/plugins/mui/Slider.tsx diff --git a/chartlets.js/packages/lib/src/plugins/mui/Slider.tsx b/chartlets.js/packages/lib/src/plugins/mui/Slider.tsx new file mode 100644 index 00000000..ffee9951 --- /dev/null +++ b/chartlets.js/packages/lib/src/plugins/mui/Slider.tsx @@ -0,0 +1,30 @@ +import type { ElementType } from "react"; +import MuiSlider from "@mui/material/Slider"; + +import type { ComponentProps, ComponentState } from "@/index"; + +interface SliderState extends ComponentState { + component?: ElementType; +} + +interface SliderProps extends ComponentProps, SliderState { + defaultValue: number; +} + +export const Slider = ({ + id, + style, + defaultValue, + component, + // onChange, +}: SliderProps) => { + return ( + + ); +}; From d12c5a3c1d6957781507ab3e6f7d9db24070a25a Mon Sep 17 00:00:00 2001 From: Yogesh Kumar Baljeet Singh Date: Mon, 16 Dec 2024 09:11:00 +0100 Subject: [PATCH 02/14] Test for CI --- chartlets.js/packages/lib/src/plugins/mui/Slider.tsx | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/chartlets.js/packages/lib/src/plugins/mui/Slider.tsx b/chartlets.js/packages/lib/src/plugins/mui/Slider.tsx index ffee9951..687cecbf 100644 --- a/chartlets.js/packages/lib/src/plugins/mui/Slider.tsx +++ b/chartlets.js/packages/lib/src/plugins/mui/Slider.tsx @@ -11,19 +11,12 @@ interface SliderProps extends ComponentProps, SliderState { defaultValue: number; } -export const Slider = ({ - id, - style, - defaultValue, - component, - // onChange, -}: SliderProps) => { +export const Slider = ({ id, style, defaultValue, component }: SliderProps) => { return ( ); From 3d1d146d899358a792320e573b341d5cfb6c0a08 Mon Sep 17 00:00:00 2001 From: Yogesh Kumar Baljeet Singh Date: Mon, 16 Dec 2024 14:41:34 +0100 Subject: [PATCH 03/14] Add sliders dataclass --- chartlets.py/chartlets/components/slider.py | 93 +++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 chartlets.py/chartlets/components/slider.py diff --git a/chartlets.py/chartlets/components/slider.py b/chartlets.py/chartlets/components/slider.py new file mode 100644 index 00000000..31ec3cf1 --- /dev/null +++ b/chartlets.py/chartlets/components/slider.py @@ -0,0 +1,93 @@ +from dataclasses import dataclass +from typing import Literal, TypedDict, Callable + +from chartlets import Component + + +@dataclass(frozen=True) +class Slider(Component): + """Sliders allow users to make selections from a range of values along a + bar.""" + + aria_label: str | None = None + """The label of the slider.""" + + color: str | None = None + """The color of the component. It supports both default and custom theme + colors + """ + + defaultValue: [int] | int | None = None + """The default value. Use when the component is not controlled. If used + as an array, it will create multiple sliding points on the bar + """ + + disableSwap: bool | None = None + """If true, the active thumb doesn't swap when moving pointer over a thumb + while dragging another thumb. + """ + + getAriaValueText: Callable[[int, int], str] | None = None + """Accepts a function which returns a string value that provides a + user-friendly name for the current value of the slider. This is important + for screen reader users. + + Signature: + function(value: number, index: number) => string + + value: The thumb label's value to format. + index: The thumb label's index to format. + """ + + min: int | None = None + """The minimum allowed value of the slider. Should not be equal to max.""" + + max: int | None = None + """The maximum allowed value of the slider. Should not be equal to min.""" + + marks: (bool | + list[TypedDict("marks", {"value": int, "label": str})] | + None) = None + """Marks indicate predetermined values to which the user can move the + slider. If true the marks are spaced according the value of the step + prop. If an array, it should contain objects with value and an optional + label keys. + """ + + orientation: Literal["horizontal", "vertical"] | None = None + """The component orientation.""" + + step: int | None = None + """The granularity with which the slider can step through values. (A + "discrete" slider.) The min prop serves as the origin for the valid values. + We recommend (max - min) to be evenly divisible by the step. When step is + null, the thumb can only be slid onto marks provided with the marks prop. + """ + + size: str | None = None + """The size of the slider.""" + + tooltip: str | None = None + """Tooltip title. Optional.""" + + track: Literal["inverted", "normal"] | False | None = None + """The track presentation: + + `normal`: the track will render a bar representing the slider value. + `inverted`: the track will render a bar representing the remaining slider + value. + `false`: the track will render without a bar. + """ + + value: bool | None = None + """The value of the slider. For ranged sliders, provide an array with two + values. + """ + + valueLabelDisplay: Literal['auto', 'on', 'off'] | None = None + """Controls when the value label is displayed: + + `auto` the value label will display when the thumb is hovered or focused. + `on` will display persistently. + `off` will never display. + """ From 2482aa7c733da6893b6bcbd3520a6659a6ddd56f Mon Sep 17 00:00:00 2001 From: Yogesh Kumar Baljeet Singh Date: Mon, 16 Dec 2024 15:28:20 +0100 Subject: [PATCH 04/14] Add slider in init --- chartlets.py/chartlets/components/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chartlets.py/chartlets/components/__init__.py b/chartlets.py/chartlets/components/__init__.py index 8b1c0ada..615d95c0 100644 --- a/chartlets.py/chartlets/components/__init__.py +++ b/chartlets.py/chartlets/components/__init__.py @@ -2,14 +2,15 @@ from .button import Button from .button import IconButton from .checkbox import Checkbox +from .charts.vega import VegaChart from .progress import CircularProgress from .progress import CircularProgressWithLabel from .progress import LinearProgress from .progress import LinearProgressWithLabel -from .charts.vega import VegaChart from .radiogroup import Radio from .radiogroup import RadioGroup from .select import Select +from .slider import Slider from .switch import Switch from .tabs import Tab from .tabs import Tabs From 420a3c6d69919a0cdb90803d29a91f09ca839bee Mon Sep 17 00:00:00 2001 From: Yogesh Kumar Baljeet Singh Date: Mon, 16 Dec 2024 15:29:20 +0100 Subject: [PATCH 05/14] Remove slider in init --- chartlets.py/chartlets/components/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chartlets.py/chartlets/components/__init__.py b/chartlets.py/chartlets/components/__init__.py index 615d95c0..f1a24a57 100644 --- a/chartlets.py/chartlets/components/__init__.py +++ b/chartlets.py/chartlets/components/__init__.py @@ -10,7 +10,7 @@ from .radiogroup import Radio from .radiogroup import RadioGroup from .select import Select -from .slider import Slider +# from .slider import Slider from .switch import Switch from .tabs import Tab from .tabs import Tabs From a912fe36de0f905edbf820de060555dcc1baae1b Mon Sep 17 00:00:00 2001 From: Yogesh Kumar Baljeet Singh Date: Mon, 16 Dec 2024 15:34:31 +0100 Subject: [PATCH 06/14] Update Slider.tsx [test for CI] --- .../packages/lib/src/plugins/mui/Slider.tsx | 18 ++++++++++-------- .../packages/lib/src/plugins/mui/index.ts | 2 ++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/chartlets.js/packages/lib/src/plugins/mui/Slider.tsx b/chartlets.js/packages/lib/src/plugins/mui/Slider.tsx index 687cecbf..0a35ee9f 100644 --- a/chartlets.js/packages/lib/src/plugins/mui/Slider.tsx +++ b/chartlets.js/packages/lib/src/plugins/mui/Slider.tsx @@ -1,23 +1,25 @@ -import type { ElementType } from "react"; import MuiSlider from "@mui/material/Slider"; import type { ComponentProps, ComponentState } from "@/index"; -interface SliderState extends ComponentState { - component?: ElementType; -} +interface SliderState extends ComponentState {} interface SliderProps extends ComponentProps, SliderState { - defaultValue: number; + defaultValue?: number; } -export const Slider = ({ id, style, defaultValue, component }: SliderProps) => { +export const Slider = ({ + id, + style, + defaultValue, + // onChange, +}: SliderProps) => { return ( + // onChange={onChange} + /> ); }; diff --git a/chartlets.js/packages/lib/src/plugins/mui/index.ts b/chartlets.js/packages/lib/src/plugins/mui/index.ts index 3b76cff9..a72f6c64 100644 --- a/chartlets.js/packages/lib/src/plugins/mui/index.ts +++ b/chartlets.js/packages/lib/src/plugins/mui/index.ts @@ -9,6 +9,7 @@ import { Select } from "./Select"; import { Switch } from "./Switch"; import { Tabs } from "./Tabs"; import { Typography } from "./Typography"; +import { Slider } from "./Slider"; export default function mui(): Plugin { return { @@ -20,6 +21,7 @@ export default function mui(): Plugin { ["IconButton", IconButton], ["RadioGroup", RadioGroup], ["Select", Select], + ["Slider", Slider], ["Switch", Switch], ["Tabs", Tabs], ["Typography", Typography], From 8656f77684fcaa497069b4f6b441a1e3d23bd882 Mon Sep 17 00:00:00 2001 From: Yogesh Kumar Baljeet Singh Date: Mon, 16 Dec 2024 15:38:37 +0100 Subject: [PATCH 07/14] Test for CI --- .../packages/lib/src/plugins/mui/Slider.tsx | 16 ++-------------- chartlets.py/chartlets/components/slider.py | 2 +- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/chartlets.js/packages/lib/src/plugins/mui/Slider.tsx b/chartlets.js/packages/lib/src/plugins/mui/Slider.tsx index 0a35ee9f..bdec9132 100644 --- a/chartlets.js/packages/lib/src/plugins/mui/Slider.tsx +++ b/chartlets.js/packages/lib/src/plugins/mui/Slider.tsx @@ -8,18 +8,6 @@ interface SliderProps extends ComponentProps, SliderState { defaultValue?: number; } -export const Slider = ({ - id, - style, - defaultValue, - // onChange, -}: SliderProps) => { - return ( - - ); +export const Slider = ({ id, style, defaultValue }: SliderProps) => { + return ; }; diff --git a/chartlets.py/chartlets/components/slider.py b/chartlets.py/chartlets/components/slider.py index 31ec3cf1..a0a016b0 100644 --- a/chartlets.py/chartlets/components/slider.py +++ b/chartlets.py/chartlets/components/slider.py @@ -7,7 +7,7 @@ @dataclass(frozen=True) class Slider(Component): """Sliders allow users to make selections from a range of values along a - bar.""" + bar.""" aria_label: str | None = None """The label of the slider.""" From b02a2a690fa1ea832267121f30e4f7391e666271 Mon Sep 17 00:00:00 2001 From: Yogesh Kumar Baljeet Singh Date: Mon, 16 Dec 2024 15:51:17 +0100 Subject: [PATCH 08/14] test for CI --- .../packages/lib/src/plugins/mui/Slider.tsx | 16 ++++++++++++++-- chartlets.py/chartlets/components/slider.py | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/chartlets.js/packages/lib/src/plugins/mui/Slider.tsx b/chartlets.js/packages/lib/src/plugins/mui/Slider.tsx index bdec9132..0a35ee9f 100644 --- a/chartlets.js/packages/lib/src/plugins/mui/Slider.tsx +++ b/chartlets.js/packages/lib/src/plugins/mui/Slider.tsx @@ -8,6 +8,18 @@ interface SliderProps extends ComponentProps, SliderState { defaultValue?: number; } -export const Slider = ({ id, style, defaultValue }: SliderProps) => { - return ; +export const Slider = ({ + id, + style, + defaultValue, + // onChange, +}: SliderProps) => { + return ( + + ); }; diff --git a/chartlets.py/chartlets/components/slider.py b/chartlets.py/chartlets/components/slider.py index a0a016b0..31ec3cf1 100644 --- a/chartlets.py/chartlets/components/slider.py +++ b/chartlets.py/chartlets/components/slider.py @@ -7,7 +7,7 @@ @dataclass(frozen=True) class Slider(Component): """Sliders allow users to make selections from a range of values along a - bar.""" + bar.""" aria_label: str | None = None """The label of the slider.""" From 596eb366b6d8a647deb78d2cc07a2c2eccb6218f Mon Sep 17 00:00:00 2001 From: Yogesh Kumar Baljeet Singh Date: Tue, 17 Dec 2024 10:43:48 +0100 Subject: [PATCH 09/14] Add Slider.py test --- chartlets.py/chartlets/components/__init__.py | 2 +- chartlets.py/chartlets/components/slider.py | 2 +- chartlets.py/tests/components/slider_test.py | 29 +++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 chartlets.py/tests/components/slider_test.py diff --git a/chartlets.py/chartlets/components/__init__.py b/chartlets.py/chartlets/components/__init__.py index f1a24a57..615d95c0 100644 --- a/chartlets.py/chartlets/components/__init__.py +++ b/chartlets.py/chartlets/components/__init__.py @@ -10,7 +10,7 @@ from .radiogroup import Radio from .radiogroup import RadioGroup from .select import Select -# from .slider import Slider +from .slider import Slider from .switch import Switch from .tabs import Tab from .tabs import Tabs diff --git a/chartlets.py/chartlets/components/slider.py b/chartlets.py/chartlets/components/slider.py index 31ec3cf1..be891aad 100644 --- a/chartlets.py/chartlets/components/slider.py +++ b/chartlets.py/chartlets/components/slider.py @@ -17,7 +17,7 @@ class Slider(Component): colors """ - defaultValue: [int] | int | None = None + defaultValue: list[int] | int | None = None """The default value. Use when the component is not controlled. If used as an array, it will create multiple sliding points on the bar """ diff --git a/chartlets.py/tests/components/slider_test.py b/chartlets.py/tests/components/slider_test.py new file mode 100644 index 00000000..096a92a4 --- /dev/null +++ b/chartlets.py/tests/components/slider_test.py @@ -0,0 +1,29 @@ +from chartlets.components import Slider +from tests.component_test import make_base + + +class SliderTest(make_base(Slider)): + def test_is_json_serializable(self): + self.assert_is_json_serializable( + self.cls( + aria_label="Temperature", + color="primary", + min=0, + max=50, + step=5, + marks=[5, 15, 50], + tooltip="Choose a temperature", + valueLabelDisplay="on", + ), + { + "type": "Slider", + "aria_label": "Temperature", + "color": "primary", + "min": 0, + "max": 50, + "step": 5, + "marks": [5, 15, 50], + "tooltip": "Choose a temperature", + "valueLabelDisplay": "on", + }, + ) From 700819e96ed105c24b77d06bc72904bccd14fae6 Mon Sep 17 00:00:00 2001 From: Yogesh Kumar Baljeet Singh Date: Tue, 17 Dec 2024 18:01:01 +0100 Subject: [PATCH 10/14] Add py test --- chartlets.py/chartlets/components/slider.py | 2 +- chartlets.py/demo/my_extension/my_panel_4.py | 58 ++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 chartlets.py/demo/my_extension/my_panel_4.py diff --git a/chartlets.py/chartlets/components/slider.py b/chartlets.py/chartlets/components/slider.py index be891aad..6f3ca4e7 100644 --- a/chartlets.py/chartlets/components/slider.py +++ b/chartlets.py/chartlets/components/slider.py @@ -79,7 +79,7 @@ class Slider(Component): `false`: the track will render without a bar. """ - value: bool | None = None + value: list[int] | int | None = None """The value of the slider. For ranged sliders, provide an array with two values. """ diff --git a/chartlets.py/demo/my_extension/my_panel_4.py b/chartlets.py/demo/my_extension/my_panel_4.py new file mode 100644 index 00000000..c087f04e --- /dev/null +++ b/chartlets.py/demo/my_extension/my_panel_4.py @@ -0,0 +1,58 @@ +from chartlets import Component, Input, State, Output +from chartlets.components import Box, Slider, Typography + +from server.context import Context +from server.panel import Panel + + +panel = Panel(__name__, title="Panel D") + + +@panel.layout() +def render_panel( + ctx: Context, +) -> Component: + marks = [ + { + "value": 0, + "label": "0", + }, + { + "value": 20, + "label": "20", + }, + { + "value": 37, + "label": "37", + }, + { + "value": 100, + "label": "100", + }, + ] + slider = Slider( + id="slider", min=0, max=100, step=5, marks=marks, valueLabelDisplay="auto" + ) + + info_text = Typography(id="info_text", children=["Move the slider."]) + + return Box( + style={ + "display": "flex", + "flexDirection": "column", + "width": "100%", + "height": "100%", + "gap": "6px", + }, + children=[slider, info_text], + ) + + +# noinspection PyUnusedLocal +@panel.callback(Input("slider"), Output("info_text", "children")) +def update_info_text( + ctx: Context, + slider: int, +) -> list[str]: + slider = slider or 0 + return [f"The value is {slider}."] From 47868e6eac88e6cef287668f26b4dbb114bb0e76 Mon Sep 17 00:00:00 2001 From: Yogesh Kumar Baljeet Singh Date: Tue, 17 Dec 2024 18:01:46 +0100 Subject: [PATCH 11/14] Add demo in init --- chartlets.py/demo/my_extension/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/chartlets.py/demo/my_extension/__init__.py b/chartlets.py/demo/my_extension/__init__.py index 032838da..0f969eeb 100644 --- a/chartlets.py/demo/my_extension/__init__.py +++ b/chartlets.py/demo/my_extension/__init__.py @@ -2,8 +2,10 @@ from .my_panel_1 import panel as my_panel_1 from .my_panel_2 import panel as my_panel_2 from .my_panel_3 import panel as my_panel_3 +from .my_panel_4 import panel as my_panel_4 ext = Extension(__name__) ext.add(my_panel_1) ext.add(my_panel_2) ext.add(my_panel_3) +ext.add(my_panel_4) From 65c7b17eadb30fc15b5b060ffedd7109ee6dd1ab Mon Sep 17 00:00:00 2001 From: Yogesh Kumar Baljeet Singh Date: Tue, 17 Dec 2024 18:02:41 +0100 Subject: [PATCH 12/14] [WIP] Add tests for slider.tsx --- .../lib/src/plugins/mui/Slider.test.tsx | 71 ++++++++++++++++++ .../packages/lib/src/plugins/mui/Slider.tsx | 72 +++++++++++++++++-- 2 files changed, 137 insertions(+), 6 deletions(-) create mode 100644 chartlets.js/packages/lib/src/plugins/mui/Slider.test.tsx diff --git a/chartlets.js/packages/lib/src/plugins/mui/Slider.test.tsx b/chartlets.js/packages/lib/src/plugins/mui/Slider.test.tsx new file mode 100644 index 00000000..9154ce46 --- /dev/null +++ b/chartlets.js/packages/lib/src/plugins/mui/Slider.test.tsx @@ -0,0 +1,71 @@ +import { describe, expect, it } from "vitest"; +import { fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { Slider } from "./Slider"; +import { createChangeHandler } from "@/plugins/mui/common.test"; +import "@testing-library/jest-dom"; +import { createTheme, ThemeProvider } from "@mui/material"; + +describe("Slider", () => { + it("should render the Slider component", async () => { + render( + {}} + style={{ width: "300px" }} + />, + ); + + const slider = await waitFor(() => screen.getByRole("slider")); + expect(slider).toBeDefined(); + + expect(slider.getAttribute("aria-orientation")).toEqual("horizontal"); + expect(slider.getAttribute("min")).toEqual("0"); + expect(slider.getAttribute("max")).toEqual("100"); + }); + + it("should fire 'value' property", async () => { + const { recordedEvents, onChange } = createChangeHandler(); + const theme = createTheme(); + render( + + + , + ); + await waitFor(() => { + const slider = screen.getByRole("slider"); + expect(slider).toBeInTheDocument(); + }); + + const slider = screen.getByRole("slider"); + expect(slider).toBeInTheDocument(); + + const thumb = document.querySelector(".MuiSlider-thumb"); + expect(thumb).toBeInTheDocument(); + + const input = document.querySelector("input")?.value; + expect(input).toEqual("60"); + + if (thumb) { + fireEvent.mouseDown(slider, { clientX: 0.1 }); + fireEvent.mouseMove(slider, { clientX: 0.1 }); + fireEvent.mouseUp(slider); + expect(recordedEvents.length).toEqual(1); + expect(recordedEvents[0]).toEqual({ + componentType: "Slider", + id: "sliderId", + property: "value", + value: 2, //expect.any(Number), + }); + } + }); +}); diff --git a/chartlets.js/packages/lib/src/plugins/mui/Slider.tsx b/chartlets.js/packages/lib/src/plugins/mui/Slider.tsx index 0a35ee9f..643cb651 100644 --- a/chartlets.js/packages/lib/src/plugins/mui/Slider.tsx +++ b/chartlets.js/packages/lib/src/plugins/mui/Slider.tsx @@ -1,25 +1,85 @@ import MuiSlider from "@mui/material/Slider"; +import type { OverridableStringUnion } from "@mui/types"; import type { ComponentProps, ComponentState } from "@/index"; +import type { ReactNode } from "react"; -interface SliderState extends ComponentState {} - -interface SliderProps extends ComponentProps, SliderState { +interface SliderState extends ComponentState { defaultValue?: number; + ariaLabel?: string; + color?: OverridableStringUnion< + "primary" | "secondary" | "success" | "error" | "info" | "warning", + string + >; + disableSwap?: boolean; + getAriaValueText?: (value: number, index: number) => string; + min?: number; + max?: number; + marks?: boolean | { value: number; label?: ReactNode }[]; + orientation?: "horizontal" | "vertical"; + step?: number; + size?: OverridableStringUnion<"small" | "medium", string>; + track?: "inverted" | "normal" | false; + value?: number | number[]; + valueLabelDisplay?: "auto" | "on" | "off"; } +interface SliderProps extends ComponentProps, SliderState {} + export const Slider = ({ + type, id, style, defaultValue, - // onChange, + ariaLabel, + color, + disableSwap, + getAriaValueText, + min, + max, + marks, + orientation, + step, + size, + track, + value, + valueLabelDisplay, + onChange, }: SliderProps) => { + const handleSlide = ( + _event: Event, + value: number | number[], + _activeThumb: number, + ) => { + console.log(_event, value, _activeThumb); + if (id) { + onChange({ + componentType: type, + id: id, + property: "value", + value: value, + }); + } + }; return ( ); }; From ed04e35145105d350b71aa97327ed626defda0b9 Mon Sep 17 00:00:00 2001 From: Yogesh Kumar Baljeet Singh Date: Wed, 18 Dec 2024 14:49:57 +0100 Subject: [PATCH 13/14] Update tests --- .../lib/src/plugins/mui/Slider.test.tsx | 118 ++++++++++++------ .../packages/lib/src/plugins/mui/Slider.tsx | 9 +- 2 files changed, 89 insertions(+), 38 deletions(-) diff --git a/chartlets.js/packages/lib/src/plugins/mui/Slider.test.tsx b/chartlets.js/packages/lib/src/plugins/mui/Slider.test.tsx index 9154ce46..f3960a47 100644 --- a/chartlets.js/packages/lib/src/plugins/mui/Slider.test.tsx +++ b/chartlets.js/packages/lib/src/plugins/mui/Slider.test.tsx @@ -1,12 +1,13 @@ -import { describe, expect, it } from "vitest"; -import { fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import { fireEvent, render, screen } from "@testing-library/react"; import { Slider } from "./Slider"; -import { createChangeHandler } from "@/plugins/mui/common.test"; import "@testing-library/jest-dom"; -import { createTheme, ThemeProvider } from "@mui/material"; +import { createChangeHandler } from "@/plugins/mui/common.test"; +import { useState } from "react"; +import type { ComponentChangeEvent } from "@/types/state/event"; describe("Slider", () => { - it("should render the Slider component", async () => { + it("should render the Slider component", () => { render( { max={100} value={50} onChange={() => {}} - style={{ width: "300px" }} />, ); - const slider = await waitFor(() => screen.getByRole("slider")); + const slider = screen.getByRole("slider"); expect(slider).toBeDefined(); expect(slider.getAttribute("aria-orientation")).toEqual("horizontal"); expect(slider.getAttribute("min")).toEqual("0"); expect(slider.getAttribute("max")).toEqual("100"); + expect(slider.getAttribute("value")).toEqual("50"); }); - it("should fire 'value' property", async () => { - const { recordedEvents, onChange } = createChangeHandler(); - const theme = createTheme(); + it("should render the Slider component", () => { render( - + {}} + />, + ); + + const slider = screen.getByRole("slider"); + expect(slider).toBeDefined(); + + expect(slider.getAttribute("aria-orientation")).toEqual("horizontal"); + expect(slider.getAttribute("min")).toEqual("0"); + expect(slider.getAttribute("max")).toEqual("100"); + expect(slider.getAttribute("value")).toEqual("50"); + }); + + it("should fire 'value' property", () => { + const { recordedEvents, onChange } = createChangeHandler(); + + const TestSlider = () => { + const [sliderValue, setSliderValue] = useState(60); + + const handleChange = (event: ComponentChangeEvent) => { + setSliderValue(event.value as number); + onChange(event); + }; + + return ( - , - ); - await waitFor(() => { - const slider = screen.getByRole("slider"); - expect(slider).toBeInTheDocument(); - }); + ); + }; - const slider = screen.getByRole("slider"); + render(); + const slider = screen.getByTestId("sliderId"); expect(slider).toBeInTheDocument(); - - const thumb = document.querySelector(".MuiSlider-thumb"); - expect(thumb).toBeInTheDocument(); + expect(screen.getByRole("slider")).toHaveValue("60"); const input = document.querySelector("input")?.value; expect(input).toEqual("60"); - if (thumb) { - fireEvent.mouseDown(slider, { clientX: 0.1 }); - fireEvent.mouseMove(slider, { clientX: 0.1 }); - fireEvent.mouseUp(slider); - expect(recordedEvents.length).toEqual(1); - expect(recordedEvents[0]).toEqual({ - componentType: "Slider", - id: "sliderId", - property: "value", - value: 2, //expect.any(Number), - }); - } + const sliderBounds = { + left: 100, + width: 200, + top: 0, + bottom: 0, + height: 20, + }; + vi.spyOn(slider, "getBoundingClientRect").mockReturnValue( + sliderBounds as DOMRect, + ); + + // The value selected should be 100 + const clientX = sliderBounds.left + sliderBounds.width * 0.1; + + fireEvent.mouseDown(slider, { clientX: clientX }); + fireEvent.mouseMove(slider, { clientX: clientX }); + fireEvent.mouseUp(slider); + expect(recordedEvents.length).toEqual(1); + + expect(recordedEvents[0]).toEqual({ + componentType: "Slider", + id: "sliderId", + property: "value", + value: 100, + }); + + expect(screen.getByRole("slider")).toHaveValue("100"); + const updated_input = document.querySelector("input"); + expect(updated_input?.value).toEqual("100"); }); }); diff --git a/chartlets.js/packages/lib/src/plugins/mui/Slider.tsx b/chartlets.js/packages/lib/src/plugins/mui/Slider.tsx index 643cb651..b85d447d 100644 --- a/chartlets.js/packages/lib/src/plugins/mui/Slider.tsx +++ b/chartlets.js/packages/lib/src/plugins/mui/Slider.tsx @@ -22,6 +22,7 @@ interface SliderState extends ComponentState { track?: "inverted" | "normal" | false; value?: number | number[]; valueLabelDisplay?: "auto" | "on" | "off"; + ["data-testid"]?: string; } interface SliderProps extends ComponentProps, SliderState {} @@ -45,13 +46,18 @@ export const Slider = ({ value, valueLabelDisplay, onChange, + ...props }: SliderProps) => { + // We need to drop children prop because we want to access the data-testid for + // tests and slider does not accept children components + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { children: _, ...sliderProps } = props; + const handleSlide = ( _event: Event, value: number | number[], _activeThumb: number, ) => { - console.log(_event, value, _activeThumb); if (id) { onChange({ componentType: type, @@ -63,6 +69,7 @@ export const Slider = ({ }; return ( Date: Wed, 18 Dec 2024 15:18:28 +0100 Subject: [PATCH 14/14] update CHANGES.md --- chartlets.js/CHANGES.md | 1 + chartlets.py/CHANGES.md | 1 + 2 files changed, 2 insertions(+) diff --git a/chartlets.js/CHANGES.md b/chartlets.js/CHANGES.md index 4318f0e1..7e5fd11f 100644 --- a/chartlets.js/CHANGES.md +++ b/chartlets.js/CHANGES.md @@ -42,6 +42,7 @@ - `RadioGroup` and `Radio` - `Switch` - `Tabs` + - `Slider` * Supporting `tooltip` property for interactive MUI components. diff --git a/chartlets.py/CHANGES.md b/chartlets.py/CHANGES.md index ba05df62..6dd0fc46 100644 --- a/chartlets.py/CHANGES.md +++ b/chartlets.py/CHANGES.md @@ -22,6 +22,7 @@ - `Switch` - `RadioGroup` and `Radio` - `Tabs` + - `Slider` ## Version 0.0.29 (from 2024/11/26)