shadcn-rails 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.dockerignore +40 -0
- data/CHANGELOG.md +54 -0
- data/CLAUDE.md +463 -0
- data/PROGRESS.md +485 -0
- data/README.md +1483 -0
- data/Rakefile +29 -0
- data/__tests__/controllers/__snapshots__/calendar_controller.test.js.snap +13 -0
- data/__tests__/controllers/__snapshots__/popover_controller.test.js.snap +46 -0
- data/__tests__/controllers/__snapshots__/sheet_controller.test.js.snap +111 -0
- data/__tests__/controllers/__snapshots__/tabs_controller.test.js.snap +27 -0
- data/__tests__/controllers/accordion_controller.test.js +904 -0
- data/__tests__/controllers/calendar_controller.test.js +1370 -0
- data/__tests__/controllers/carousel_controller.test.js +912 -0
- data/__tests__/controllers/checkbox_controller.test.js +454 -0
- data/__tests__/controllers/collapsible_controller.test.js +407 -0
- data/__tests__/controllers/combobox_controller.test.js +966 -0
- data/__tests__/controllers/context_menu_controller.test.js +627 -0
- data/__tests__/controllers/date_picker_controller.test.js +636 -0
- data/__tests__/controllers/dialog_controller.test.js +878 -0
- data/__tests__/controllers/drawer_controller.test.js +995 -0
- data/__tests__/controllers/menubar_controller.test.js +736 -0
- data/__tests__/controllers/navigation_menu_controller.test.js +598 -0
- data/__tests__/controllers/popover_controller.test.js +1007 -0
- data/__tests__/controllers/radio_group_controller.test.js +640 -0
- data/__tests__/controllers/resizable_controller.test.js +680 -0
- data/__tests__/controllers/select_controller.test.js +674 -0
- data/__tests__/controllers/sheet_controller.test.js +986 -0
- data/__tests__/controllers/slider_controller.test.js +1036 -0
- data/__tests__/controllers/switch_controller.test.js +424 -0
- data/__tests__/controllers/tabs_controller.test.js +907 -0
- data/__tests__/controllers/toggle_group_controller.test.js +839 -0
- data/__tests__/controllers/tooltip_controller.test.js +808 -0
- data/__tests__/helpers/stimulus-test-helper.js +203 -0
- data/app/assets/config/manifest.js +1 -0
- data/app/assets/javascripts/shadcn/controllers/accordion_controller.d.ts +53 -0
- data/app/assets/javascripts/shadcn/controllers/accordion_controller.js +140 -0
- data/app/assets/javascripts/shadcn/controllers/avatar_controller.d.ts +22 -0
- data/app/assets/javascripts/shadcn/controllers/avatar_controller.js +26 -0
- data/app/assets/javascripts/shadcn/controllers/calendar_controller.js +592 -0
- data/app/assets/javascripts/shadcn/controllers/carousel_controller.js +263 -0
- data/app/assets/javascripts/shadcn/controllers/checkbox_controller.d.ts +31 -0
- data/app/assets/javascripts/shadcn/controllers/checkbox_controller.js +48 -0
- data/app/assets/javascripts/shadcn/controllers/collapsible_controller.d.ts +43 -0
- data/app/assets/javascripts/shadcn/controllers/collapsible_controller.js +73 -0
- data/app/assets/javascripts/shadcn/controllers/combobox_controller.js +234 -0
- data/app/assets/javascripts/shadcn/controllers/command_controller.js +141 -0
- data/app/assets/javascripts/shadcn/controllers/command_dialog_controller.js +162 -0
- data/app/assets/javascripts/shadcn/controllers/context_menu_controller.js +202 -0
- data/app/assets/javascripts/shadcn/controllers/date_picker_controller.js +282 -0
- data/app/assets/javascripts/shadcn/controllers/dialog_controller.d.ts +67 -0
- data/app/assets/javascripts/shadcn/controllers/dialog_controller.js +187 -0
- data/app/assets/javascripts/shadcn/controllers/drawer_controller.d.ts +58 -0
- data/app/assets/javascripts/shadcn/controllers/drawer_controller.js +112 -0
- data/app/assets/javascripts/shadcn/controllers/dropdown_controller.d.ts +83 -0
- data/app/assets/javascripts/shadcn/controllers/dropdown_controller.js +225 -0
- data/app/assets/javascripts/shadcn/controllers/hover_card_controller.d.ts +59 -0
- data/app/assets/javascripts/shadcn/controllers/hover_card_controller.js +143 -0
- data/app/assets/javascripts/shadcn/controllers/input_otp_controller.d.ts +44 -0
- data/app/assets/javascripts/shadcn/controllers/input_otp_controller.js +206 -0
- data/app/assets/javascripts/shadcn/controllers/menubar_controller.js +323 -0
- data/app/assets/javascripts/shadcn/controllers/navigation_menu_controller.js +251 -0
- data/app/assets/javascripts/shadcn/controllers/popover_controller.d.ts +56 -0
- data/app/assets/javascripts/shadcn/controllers/popover_controller.js +141 -0
- data/app/assets/javascripts/shadcn/controllers/radio_group_controller.d.ts +47 -0
- data/app/assets/javascripts/shadcn/controllers/radio_group_controller.js +108 -0
- data/app/assets/javascripts/shadcn/controllers/resizable_controller.js +272 -0
- data/app/assets/javascripts/shadcn/controllers/scroll_area_controller.d.ts +44 -0
- data/app/assets/javascripts/shadcn/controllers/scroll_area_controller.js +74 -0
- data/app/assets/javascripts/shadcn/controllers/select_controller.d.ts +84 -0
- data/app/assets/javascripts/shadcn/controllers/select_controller.js +222 -0
- data/app/assets/javascripts/shadcn/controllers/sheet_controller.d.ts +60 -0
- data/app/assets/javascripts/shadcn/controllers/sheet_controller.js +151 -0
- data/app/assets/javascripts/shadcn/controllers/sidebar_controller.js +148 -0
- data/app/assets/javascripts/shadcn/controllers/slider_controller.d.ts +102 -0
- data/app/assets/javascripts/shadcn/controllers/slider_controller.js +364 -0
- data/app/assets/javascripts/shadcn/controllers/switch_controller.d.ts +46 -0
- data/app/assets/javascripts/shadcn/controllers/switch_controller.js +78 -0
- data/app/assets/javascripts/shadcn/controllers/tabs_controller.d.ts +51 -0
- data/app/assets/javascripts/shadcn/controllers/tabs_controller.js +126 -0
- data/app/assets/javascripts/shadcn/controllers/toast_controller.d.ts +37 -0
- data/app/assets/javascripts/shadcn/controllers/toast_controller.js +58 -0
- data/app/assets/javascripts/shadcn/controllers/toggle_controller.d.ts +27 -0
- data/app/assets/javascripts/shadcn/controllers/toggle_controller.js +42 -0
- data/app/assets/javascripts/shadcn/controllers/toggle_group_controller.d.ts +44 -0
- data/app/assets/javascripts/shadcn/controllers/toggle_group_controller.js +68 -0
- data/app/assets/javascripts/shadcn/controllers/tooltip_controller.d.ts +56 -0
- data/app/assets/javascripts/shadcn/controllers/tooltip_controller.js +117 -0
- data/app/assets/javascripts/shadcn/index.d.ts +74 -0
- data/app/assets/javascripts/shadcn/index.js +133 -0
- data/app/assets/stylesheets/.keep +0 -0
- data/app/assets/stylesheets/shadcn/base.css +445 -0
- data/app/assets/stylesheets/shadcn/components.css +513 -0
- data/app/assets/stylesheets/shadcn/index.css +18 -0
- data/app/assets/stylesheets/shadcn/themes/gray.css +68 -0
- data/app/assets/stylesheets/shadcn/themes/slate.css +68 -0
- data/app/assets/stylesheets/shadcn/themes/stone.css +68 -0
- data/app/assets/stylesheets/shadcn/themes/zinc.css +68 -0
- data/app/components/shadcn/accordion_component.rb +63 -0
- data/app/components/shadcn/accordion_content_component.rb +29 -0
- data/app/components/shadcn/accordion_item_component.rb +40 -0
- data/app/components/shadcn/accordion_trigger_component.rb +49 -0
- data/app/components/shadcn/alert_component.rb +75 -0
- data/app/components/shadcn/alert_description_component.rb +12 -0
- data/app/components/shadcn/alert_dialog_action_component.rb +24 -0
- data/app/components/shadcn/alert_dialog_cancel_component.rb +24 -0
- data/app/components/shadcn/alert_dialog_component.rb +71 -0
- data/app/components/shadcn/alert_dialog_content_component.rb +57 -0
- data/app/components/shadcn/alert_dialog_description_component.rb +12 -0
- data/app/components/shadcn/alert_dialog_footer_component.rb +19 -0
- data/app/components/shadcn/alert_dialog_header_component.rb +19 -0
- data/app/components/shadcn/alert_dialog_title_component.rb +12 -0
- data/app/components/shadcn/alert_title_component.rb +12 -0
- data/app/components/shadcn/aspect_ratio_component.rb +49 -0
- data/app/components/shadcn/avatar_component.rb +107 -0
- data/app/components/shadcn/avatar_fallback_component.rb +17 -0
- data/app/components/shadcn/badge_component.rb +49 -0
- data/app/components/shadcn/base_component.rb +100 -0
- data/app/components/shadcn/breadcrumb_component.rb +70 -0
- data/app/components/shadcn/breadcrumb_item_component.rb +50 -0
- data/app/components/shadcn/button_component.rb +141 -0
- data/app/components/shadcn/button_group_component.rb +69 -0
- data/app/components/shadcn/calendar_component.rb +337 -0
- data/app/components/shadcn/card_action_component.rb +10 -0
- data/app/components/shadcn/card_component.rb +63 -0
- data/app/components/shadcn/card_content_component.rb +19 -0
- data/app/components/shadcn/card_description_component.rb +12 -0
- data/app/components/shadcn/card_footer_component.rb +12 -0
- data/app/components/shadcn/card_header_component.rb +24 -0
- data/app/components/shadcn/card_title_component.rb +18 -0
- data/app/components/shadcn/carousel_component.rb +275 -0
- data/app/components/shadcn/checkbox_component.rb +103 -0
- data/app/components/shadcn/collapsible_component.rb +66 -0
- data/app/components/shadcn/collapsible_content_component.rb +28 -0
- data/app/components/shadcn/combobox_component.rb +322 -0
- data/app/components/shadcn/command_component.rb +52 -0
- data/app/components/shadcn/command_dialog_component.rb +76 -0
- data/app/components/shadcn/command_empty_component.rb +12 -0
- data/app/components/shadcn/command_group_component.rb +34 -0
- data/app/components/shadcn/command_input_component.rb +59 -0
- data/app/components/shadcn/command_item_component.rb +48 -0
- data/app/components/shadcn/command_list_component.rb +38 -0
- data/app/components/shadcn/command_separator_component.rb +12 -0
- data/app/components/shadcn/command_shortcut_component.rb +12 -0
- data/app/components/shadcn/context_menu_component.rb +64 -0
- data/app/components/shadcn/context_menu_content_component.rb +44 -0
- data/app/components/shadcn/context_menu_item_component.rb +63 -0
- data/app/components/shadcn/context_menu_label_component.rb +18 -0
- data/app/components/shadcn/context_menu_separator_component.rb +12 -0
- data/app/components/shadcn/context_menu_shortcut_component.rb +12 -0
- data/app/components/shadcn/date_picker_component.rb +368 -0
- data/app/components/shadcn/dialog_component.rb +77 -0
- data/app/components/shadcn/dialog_content_component.rb +91 -0
- data/app/components/shadcn/dialog_description_component.rb +12 -0
- data/app/components/shadcn/dialog_footer_component.rb +12 -0
- data/app/components/shadcn/dialog_header_component.rb +19 -0
- data/app/components/shadcn/dialog_title_component.rb +12 -0
- data/app/components/shadcn/drawer_component.rb +72 -0
- data/app/components/shadcn/drawer_content_component.rb +76 -0
- data/app/components/shadcn/drawer_description_component.rb +12 -0
- data/app/components/shadcn/drawer_footer_component.rb +12 -0
- data/app/components/shadcn/drawer_header_component.rb +19 -0
- data/app/components/shadcn/drawer_title_component.rb +12 -0
- data/app/components/shadcn/dropdown_menu_component.rb +75 -0
- data/app/components/shadcn/dropdown_menu_content_component.rb +49 -0
- data/app/components/shadcn/dropdown_menu_group_component.rb +10 -0
- data/app/components/shadcn/dropdown_menu_item_component.rb +63 -0
- data/app/components/shadcn/dropdown_menu_label_component.rb +18 -0
- data/app/components/shadcn/dropdown_menu_separator_component.rb +12 -0
- data/app/components/shadcn/dropdown_menu_shortcut_component.rb +12 -0
- data/app/components/shadcn/empty_component.rb +48 -0
- data/app/components/shadcn/empty_content_component.rb +12 -0
- data/app/components/shadcn/empty_description_component.rb +12 -0
- data/app/components/shadcn/empty_header_component.rb +29 -0
- data/app/components/shadcn/empty_media_component.rb +21 -0
- data/app/components/shadcn/empty_title_component.rb +12 -0
- data/app/components/shadcn/field_component.rb +113 -0
- data/app/components/shadcn/hover_card_component.rb +64 -0
- data/app/components/shadcn/hover_card_content_component.rb +36 -0
- data/app/components/shadcn/input_component.rb +108 -0
- data/app/components/shadcn/input_group_component.rb +70 -0
- data/app/components/shadcn/input_otp_component.rb +183 -0
- data/app/components/shadcn/item_actions_component.rb +12 -0
- data/app/components/shadcn/item_component.rb +98 -0
- data/app/components/shadcn/item_content_component.rb +24 -0
- data/app/components/shadcn/item_description_component.rb +12 -0
- data/app/components/shadcn/item_footer_component.rb +12 -0
- data/app/components/shadcn/item_group_component.rb +24 -0
- data/app/components/shadcn/item_header_component.rb +12 -0
- data/app/components/shadcn/item_media_component.rb +22 -0
- data/app/components/shadcn/item_separator_component.rb +12 -0
- data/app/components/shadcn/item_title_component.rb +12 -0
- data/app/components/shadcn/kbd_component.rb +36 -0
- data/app/components/shadcn/label_component.rb +49 -0
- data/app/components/shadcn/menubar_checkbox_item_component.rb +76 -0
- data/app/components/shadcn/menubar_component.rb +56 -0
- data/app/components/shadcn/menubar_content_component.rb +64 -0
- data/app/components/shadcn/menubar_item_component.rb +65 -0
- data/app/components/shadcn/menubar_label_component.rb +27 -0
- data/app/components/shadcn/menubar_menu_component.rb +34 -0
- data/app/components/shadcn/menubar_radio_group_component.rb +42 -0
- data/app/components/shadcn/menubar_radio_item_component.rb +76 -0
- data/app/components/shadcn/menubar_separator_component.rb +22 -0
- data/app/components/shadcn/menubar_shortcut_component.rb +21 -0
- data/app/components/shadcn/menubar_sub_component.rb +38 -0
- data/app/components/shadcn/menubar_sub_content_component.rb +45 -0
- data/app/components/shadcn/menubar_sub_trigger_component.rb +59 -0
- data/app/components/shadcn/menubar_trigger_component.rb +31 -0
- data/app/components/shadcn/native_select_component.rb +150 -0
- data/app/components/shadcn/navigation_menu_component.rb +76 -0
- data/app/components/shadcn/navigation_menu_content_component.rb +30 -0
- data/app/components/shadcn/navigation_menu_item_component.rb +39 -0
- data/app/components/shadcn/navigation_menu_link_component.rb +38 -0
- data/app/components/shadcn/navigation_menu_list_component.rb +29 -0
- data/app/components/shadcn/navigation_menu_trigger_component.rb +59 -0
- data/app/components/shadcn/pagination_component.rb +195 -0
- data/app/components/shadcn/pagination_content_component.rb +47 -0
- data/app/components/shadcn/pagination_ellipsis_component.rb +30 -0
- data/app/components/shadcn/pagination_item_component.rb +53 -0
- data/app/components/shadcn/pagination_next_component.rb +48 -0
- data/app/components/shadcn/pagination_previous_component.rb +48 -0
- data/app/components/shadcn/popover_component.rb +76 -0
- data/app/components/shadcn/popover_content_component.rb +25 -0
- data/app/components/shadcn/progress_component.rb +77 -0
- data/app/components/shadcn/radio_group_component.rb +129 -0
- data/app/components/shadcn/radio_group_item_component.rb +109 -0
- data/app/components/shadcn/resizable_handle_component.rb +98 -0
- data/app/components/shadcn/resizable_panel_component.rb +56 -0
- data/app/components/shadcn/resizable_panel_group_component.rb +94 -0
- data/app/components/shadcn/scroll_area_component.rb +110 -0
- data/app/components/shadcn/select_component.rb +151 -0
- data/app/components/shadcn/select_group_component.rb +32 -0
- data/app/components/shadcn/select_item_component.rb +59 -0
- data/app/components/shadcn/select_separator_component.rb +12 -0
- data/app/components/shadcn/separator_component.rb +54 -0
- data/app/components/shadcn/sheet_component.rb +82 -0
- data/app/components/shadcn/sheet_content_component.rb +95 -0
- data/app/components/shadcn/sheet_description_component.rb +12 -0
- data/app/components/shadcn/sheet_footer_component.rb +12 -0
- data/app/components/shadcn/sheet_header_component.rb +19 -0
- data/app/components/shadcn/sheet_title_component.rb +12 -0
- data/app/components/shadcn/sidebar_component.rb +180 -0
- data/app/components/shadcn/sidebar_content_component.rb +32 -0
- data/app/components/shadcn/sidebar_footer_component.rb +24 -0
- data/app/components/shadcn/sidebar_group_action_component.rb +26 -0
- data/app/components/shadcn/sidebar_group_component.rb +38 -0
- data/app/components/shadcn/sidebar_group_content_component.rb +32 -0
- data/app/components/shadcn/sidebar_group_label_component.rb +25 -0
- data/app/components/shadcn/sidebar_header_component.rb +24 -0
- data/app/components/shadcn/sidebar_inset_component.rb +25 -0
- data/app/components/shadcn/sidebar_menu_action_component.rb +37 -0
- data/app/components/shadcn/sidebar_menu_badge_component.rb +25 -0
- data/app/components/shadcn/sidebar_menu_button_component.rb +52 -0
- data/app/components/shadcn/sidebar_menu_component.rb +32 -0
- data/app/components/shadcn/sidebar_menu_item_component.rb +41 -0
- data/app/components/shadcn/sidebar_menu_skeleton_component.rb +46 -0
- data/app/components/shadcn/sidebar_menu_sub_button_component.rb +43 -0
- data/app/components/shadcn/sidebar_menu_sub_component.rb +33 -0
- data/app/components/shadcn/sidebar_menu_sub_item_component.rb +30 -0
- data/app/components/shadcn/sidebar_provider_component.rb +57 -0
- data/app/components/shadcn/sidebar_rail_component.rb +30 -0
- data/app/components/shadcn/sidebar_separator_component.rb +24 -0
- data/app/components/shadcn/sidebar_trigger_component.rb +51 -0
- data/app/components/shadcn/skeleton_component.rb +29 -0
- data/app/components/shadcn/slider_component.rb +76 -0
- data/app/components/shadcn/spinner_component.rb +67 -0
- data/app/components/shadcn/switch_component.rb +147 -0
- data/app/components/shadcn/table_body_component.rb +16 -0
- data/app/components/shadcn/table_caption_component.rb +12 -0
- data/app/components/shadcn/table_cell_component.rb +12 -0
- data/app/components/shadcn/table_component.rb +57 -0
- data/app/components/shadcn/table_footer_component.rb +16 -0
- data/app/components/shadcn/table_head_component.rb +12 -0
- data/app/components/shadcn/table_header_component.rb +16 -0
- data/app/components/shadcn/table_row_component.rb +40 -0
- data/app/components/shadcn/tabs_component.rb +78 -0
- data/app/components/shadcn/tabs_content_component.rb +32 -0
- data/app/components/shadcn/tabs_list_component.rb +30 -0
- data/app/components/shadcn/tabs_trigger_component.rb +37 -0
- data/app/components/shadcn/textarea_component.rb +84 -0
- data/app/components/shadcn/toast_action_component.rb +18 -0
- data/app/components/shadcn/toast_component.rb +114 -0
- data/app/components/shadcn/toast_description_component.rb +12 -0
- data/app/components/shadcn/toast_title_component.rb +12 -0
- data/app/components/shadcn/toast_viewport_component.rb +12 -0
- data/app/components/shadcn/toggle_component.rb +77 -0
- data/app/components/shadcn/toggle_group_component.rb +96 -0
- data/app/components/shadcn/toggle_group_item_component.rb +62 -0
- data/app/components/shadcn/tooltip_component.rb +89 -0
- data/app/components/shadcn/typography_component.rb +112 -0
- data/babel.config.cjs +5 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/config/importmap.rb +5 -0
- data/fly.toml +26 -0
- data/jest.config.js +19 -0
- data/jest.setup.js +8 -0
- data/lib/generators/shadcn/component/component_generator.rb +188 -0
- data/lib/generators/shadcn/install/install_generator.rb +140 -0
- data/lib/generators/shadcn/install/templates/initializer.rb.tt +35 -0
- data/lib/generators/shadcn/install/templates/shadcn.yml.tt +35 -0
- data/lib/generators/shadcn/theme/theme_generator.rb +128 -0
- data/lib/shadcn/rails/class_merger.rb +228 -0
- data/lib/shadcn/rails/configuration.rb +341 -0
- data/lib/shadcn/rails/engine.rb +59 -0
- data/lib/shadcn/rails/helpers/class_name_helper.rb +35 -0
- data/lib/shadcn/rails/helpers/component_helper.rb +60 -0
- data/lib/shadcn/rails/helpers/pagination_helper.rb +187 -0
- data/lib/shadcn/rails/version.rb +7 -0
- data/lib/shadcn/rails.rb +179 -0
- data/package-lock.json +7415 -0
- data/package.json +68 -0
- data/rollup.config.js +29 -0
- data/sig/shadcn/rails.rbs +6 -0
- metadata +526 -0
|
@@ -0,0 +1,1036 @@
|
|
|
1
|
+
import { Application } from "@hotwired/stimulus"
|
|
2
|
+
import SliderController from "../../app/assets/javascripts/shadcn/controllers/slider_controller.js"
|
|
3
|
+
import { setupController, cleanupController, click, nextFrame, keydown } from '../helpers/stimulus-test-helper.js'
|
|
4
|
+
|
|
5
|
+
describe("SliderController", () => {
|
|
6
|
+
let application
|
|
7
|
+
let element
|
|
8
|
+
let controller
|
|
9
|
+
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
cleanupController(application)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
describe("basic rendering and initialization", () => {
|
|
15
|
+
const basicHTML = `
|
|
16
|
+
<div data-controller="shadcn--slider"
|
|
17
|
+
data-shadcn--slider-min-value="0"
|
|
18
|
+
data-shadcn--slider-max-value="100"
|
|
19
|
+
data-shadcn--slider-step-value="1"
|
|
20
|
+
data-shadcn--slider-value-value="50"
|
|
21
|
+
role="slider"
|
|
22
|
+
aria-valuemin="0"
|
|
23
|
+
aria-valuemax="100"
|
|
24
|
+
aria-valuenow="50">
|
|
25
|
+
<div data-shadcn--slider-target="track" style="width: 200px; height: 8px;">
|
|
26
|
+
<div data-shadcn--slider-target="range" style="width: 50%;"></div>
|
|
27
|
+
</div>
|
|
28
|
+
<div data-shadcn--slider-target="thumb"
|
|
29
|
+
tabindex="0"
|
|
30
|
+
data-action="keydown->shadcn--slider#handleKeydown"
|
|
31
|
+
style="left: calc(50% - 8px);"></div>
|
|
32
|
+
<input type="hidden" data-shadcn--slider-target="input" name="volume">
|
|
33
|
+
</div>
|
|
34
|
+
`
|
|
35
|
+
|
|
36
|
+
beforeEach(async () => {
|
|
37
|
+
const setup = await setupController(SliderController, basicHTML, 'shadcn--slider')
|
|
38
|
+
application = setup.application
|
|
39
|
+
element = setup.element
|
|
40
|
+
controller = setup.controller
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test("initializes with default values", () => {
|
|
44
|
+
expect(controller.minValue).toBe(0)
|
|
45
|
+
expect(controller.maxValue).toBe(100)
|
|
46
|
+
expect(controller.stepValue).toBe(1)
|
|
47
|
+
expect(controller.valueValue).toBe(50)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test("initializes with disabled false by default", () => {
|
|
51
|
+
expect(controller.disabledValue).toBe(false)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test("calculates percentage correctly", () => {
|
|
55
|
+
expect(controller.percentage).toBe(50)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test("updates hidden input value on init", () => {
|
|
59
|
+
expect(controller.inputTarget.value).toBe("50")
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
describe("percentage calculation", () => {
|
|
64
|
+
const percentHTML = `
|
|
65
|
+
<div data-controller="shadcn--slider"
|
|
66
|
+
data-shadcn--slider-min-value="0"
|
|
67
|
+
data-shadcn--slider-max-value="100"
|
|
68
|
+
data-shadcn--slider-value-value="25">
|
|
69
|
+
<div data-shadcn--slider-target="track" style="width: 200px;"></div>
|
|
70
|
+
<div data-shadcn--slider-target="range"></div>
|
|
71
|
+
<div data-shadcn--slider-target="thumb"></div>
|
|
72
|
+
</div>
|
|
73
|
+
`
|
|
74
|
+
|
|
75
|
+
beforeEach(async () => {
|
|
76
|
+
const setup = await setupController(SliderController, percentHTML, 'shadcn--slider')
|
|
77
|
+
application = setup.application
|
|
78
|
+
element = setup.element
|
|
79
|
+
controller = setup.controller
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test("calculates percentage at 25%", () => {
|
|
83
|
+
expect(controller.percentage).toBe(25)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test("calculates percentage at 0%", async () => {
|
|
87
|
+
controller.valueValue = 0
|
|
88
|
+
await nextFrame()
|
|
89
|
+
expect(controller.percentage).toBe(0)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test("calculates percentage at 100%", async () => {
|
|
93
|
+
controller.valueValue = 100
|
|
94
|
+
await nextFrame()
|
|
95
|
+
expect(controller.percentage).toBe(100)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test("handles equal min and max gracefully", async () => {
|
|
99
|
+
controller.minValue = 50
|
|
100
|
+
controller.maxValue = 50
|
|
101
|
+
await nextFrame()
|
|
102
|
+
expect(controller.percentage).toBe(0)
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
describe("custom range", () => {
|
|
107
|
+
const customRangeHTML = `
|
|
108
|
+
<div data-controller="shadcn--slider"
|
|
109
|
+
data-shadcn--slider-min-value="10"
|
|
110
|
+
data-shadcn--slider-max-value="50"
|
|
111
|
+
data-shadcn--slider-value-value="30">
|
|
112
|
+
<div data-shadcn--slider-target="track" style="width: 200px;"></div>
|
|
113
|
+
<div data-shadcn--slider-target="range"></div>
|
|
114
|
+
<div data-shadcn--slider-target="thumb"></div>
|
|
115
|
+
</div>
|
|
116
|
+
`
|
|
117
|
+
|
|
118
|
+
beforeEach(async () => {
|
|
119
|
+
const setup = await setupController(SliderController, customRangeHTML, 'shadcn--slider')
|
|
120
|
+
application = setup.application
|
|
121
|
+
element = setup.element
|
|
122
|
+
controller = setup.controller
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test("calculates percentage for custom range", () => {
|
|
126
|
+
// 30 is halfway between 10 and 50
|
|
127
|
+
expect(controller.percentage).toBe(50)
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
describe("step snapping", () => {
|
|
132
|
+
const stepHTML = `
|
|
133
|
+
<div data-controller="shadcn--slider"
|
|
134
|
+
data-shadcn--slider-min-value="0"
|
|
135
|
+
data-shadcn--slider-max-value="100"
|
|
136
|
+
data-shadcn--slider-step-value="10"
|
|
137
|
+
data-shadcn--slider-value-value="0">
|
|
138
|
+
<div data-shadcn--slider-target="track" style="width: 200px;"></div>
|
|
139
|
+
<div data-shadcn--slider-target="range"></div>
|
|
140
|
+
<div data-shadcn--slider-target="thumb"></div>
|
|
141
|
+
</div>
|
|
142
|
+
`
|
|
143
|
+
|
|
144
|
+
beforeEach(async () => {
|
|
145
|
+
const setup = await setupController(SliderController, stepHTML, 'shadcn--slider')
|
|
146
|
+
application = setup.application
|
|
147
|
+
element = setup.element
|
|
148
|
+
controller = setup.controller
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
test("snaps to nearest step", () => {
|
|
152
|
+
expect(controller.snapToStep(23)).toBe(20)
|
|
153
|
+
expect(controller.snapToStep(27)).toBe(30)
|
|
154
|
+
expect(controller.snapToStep(25)).toBe(30)
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
test("snaps to min when below range", () => {
|
|
158
|
+
expect(controller.snapToStep(-5)).toBe(0)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
test("snaps to max when above range", () => {
|
|
162
|
+
expect(controller.snapToStep(105)).toBe(100)
|
|
163
|
+
})
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
describe("decimal step snapping", () => {
|
|
167
|
+
const decimalStepHTML = `
|
|
168
|
+
<div data-controller="shadcn--slider"
|
|
169
|
+
data-shadcn--slider-min-value="0"
|
|
170
|
+
data-shadcn--slider-max-value="1"
|
|
171
|
+
data-shadcn--slider-step-value="0.1"
|
|
172
|
+
data-shadcn--slider-value-value="0.5">
|
|
173
|
+
<div data-shadcn--slider-target="track" style="width: 200px;"></div>
|
|
174
|
+
<div data-shadcn--slider-target="range"></div>
|
|
175
|
+
<div data-shadcn--slider-target="thumb"></div>
|
|
176
|
+
</div>
|
|
177
|
+
`
|
|
178
|
+
|
|
179
|
+
beforeEach(async () => {
|
|
180
|
+
const setup = await setupController(SliderController, decimalStepHTML, 'shadcn--slider')
|
|
181
|
+
application = setup.application
|
|
182
|
+
element = setup.element
|
|
183
|
+
controller = setup.controller
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
test("handles decimal values correctly", () => {
|
|
187
|
+
expect(controller.valueValue).toBe(0.5)
|
|
188
|
+
expect(controller.percentage).toBe(50)
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
test("snaps to decimal steps", () => {
|
|
192
|
+
expect(controller.snapToStep(0.23)).toBeCloseTo(0.2, 5)
|
|
193
|
+
expect(controller.snapToStep(0.27)).toBeCloseTo(0.3, 5)
|
|
194
|
+
})
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
describe("keyboard navigation", () => {
|
|
198
|
+
const keyboardHTML = `
|
|
199
|
+
<div data-controller="shadcn--slider"
|
|
200
|
+
data-shadcn--slider-min-value="0"
|
|
201
|
+
data-shadcn--slider-max-value="100"
|
|
202
|
+
data-shadcn--slider-step-value="1"
|
|
203
|
+
data-shadcn--slider-value-value="50">
|
|
204
|
+
<div data-shadcn--slider-target="track" style="width: 200px;"></div>
|
|
205
|
+
<div data-shadcn--slider-target="range"></div>
|
|
206
|
+
<div data-shadcn--slider-target="thumb"
|
|
207
|
+
tabindex="0"
|
|
208
|
+
data-action="keydown->shadcn--slider#handleKeydown"></div>
|
|
209
|
+
<input type="hidden" data-shadcn--slider-target="input">
|
|
210
|
+
</div>
|
|
211
|
+
`
|
|
212
|
+
|
|
213
|
+
beforeEach(async () => {
|
|
214
|
+
const setup = await setupController(SliderController, keyboardHTML, 'shadcn--slider')
|
|
215
|
+
application = setup.application
|
|
216
|
+
element = setup.element
|
|
217
|
+
controller = setup.controller
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
test("increases value with ArrowRight", () => {
|
|
221
|
+
controller.handleKeydown({ key: "ArrowRight", preventDefault: jest.fn() })
|
|
222
|
+
expect(controller.valueValue).toBe(51)
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
test("increases value with ArrowUp", () => {
|
|
226
|
+
controller.handleKeydown({ key: "ArrowUp", preventDefault: jest.fn() })
|
|
227
|
+
expect(controller.valueValue).toBe(51)
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
test("decreases value with ArrowLeft", () => {
|
|
231
|
+
controller.handleKeydown({ key: "ArrowLeft", preventDefault: jest.fn() })
|
|
232
|
+
expect(controller.valueValue).toBe(49)
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
test("decreases value with ArrowDown", () => {
|
|
236
|
+
controller.handleKeydown({ key: "ArrowDown", preventDefault: jest.fn() })
|
|
237
|
+
expect(controller.valueValue).toBe(49)
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
test("jumps by 10% with PageUp", () => {
|
|
241
|
+
controller.handleKeydown({ key: "PageUp", preventDefault: jest.fn() })
|
|
242
|
+
expect(controller.valueValue).toBe(60)
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
test("jumps by 10% with PageDown", () => {
|
|
246
|
+
controller.handleKeydown({ key: "PageDown", preventDefault: jest.fn() })
|
|
247
|
+
expect(controller.valueValue).toBe(40)
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
test("jumps to min with Home", () => {
|
|
251
|
+
controller.handleKeydown({ key: "Home", preventDefault: jest.fn() })
|
|
252
|
+
expect(controller.valueValue).toBe(0)
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
test("jumps to max with End", () => {
|
|
256
|
+
controller.handleKeydown({ key: "End", preventDefault: jest.fn() })
|
|
257
|
+
expect(controller.valueValue).toBe(100)
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
test("does not exceed max value", () => {
|
|
261
|
+
controller.valueValue = 100
|
|
262
|
+
controller.handleKeydown({ key: "ArrowRight", preventDefault: jest.fn() })
|
|
263
|
+
expect(controller.valueValue).toBe(100)
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
test("does not go below min value", () => {
|
|
267
|
+
controller.valueValue = 0
|
|
268
|
+
controller.handleKeydown({ key: "ArrowLeft", preventDefault: jest.fn() })
|
|
269
|
+
expect(controller.valueValue).toBe(0)
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
test("dispatches change event on keyboard navigation", () => {
|
|
273
|
+
let eventDetail = null
|
|
274
|
+
element.addEventListener("shadcn--slider:change", (e) => {
|
|
275
|
+
eventDetail = e.detail
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
controller.handleKeydown({ key: "ArrowRight", preventDefault: jest.fn() })
|
|
279
|
+
|
|
280
|
+
expect(eventDetail).not.toBeNull()
|
|
281
|
+
expect(eventDetail.value).toBe(51)
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
test("ignores unrelated keys", () => {
|
|
285
|
+
const preventDefault = jest.fn()
|
|
286
|
+
controller.handleKeydown({ key: "Tab", preventDefault })
|
|
287
|
+
expect(preventDefault).not.toHaveBeenCalled()
|
|
288
|
+
expect(controller.valueValue).toBe(50)
|
|
289
|
+
})
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
describe("disabled state", () => {
|
|
293
|
+
const disabledHTML = `
|
|
294
|
+
<div data-controller="shadcn--slider"
|
|
295
|
+
data-shadcn--slider-min-value="0"
|
|
296
|
+
data-shadcn--slider-max-value="100"
|
|
297
|
+
data-shadcn--slider-value-value="50"
|
|
298
|
+
data-shadcn--slider-disabled-value="true">
|
|
299
|
+
<div data-shadcn--slider-target="track" style="width: 200px;"></div>
|
|
300
|
+
<div data-shadcn--slider-target="range"></div>
|
|
301
|
+
<div data-shadcn--slider-target="thumb"></div>
|
|
302
|
+
</div>
|
|
303
|
+
`
|
|
304
|
+
|
|
305
|
+
beforeEach(async () => {
|
|
306
|
+
const setup = await setupController(SliderController, disabledHTML, 'shadcn--slider')
|
|
307
|
+
application = setup.application
|
|
308
|
+
element = setup.element
|
|
309
|
+
controller = setup.controller
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
test("ignores keyboard when disabled", () => {
|
|
313
|
+
controller.handleKeydown({ key: "ArrowRight", preventDefault: jest.fn() })
|
|
314
|
+
expect(controller.valueValue).toBe(50)
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
test("ignores drag when disabled", () => {
|
|
318
|
+
const event = { preventDefault: jest.fn(), type: "mousedown" }
|
|
319
|
+
controller.startDrag(event)
|
|
320
|
+
expect(controller.isDragging).toBeFalsy()
|
|
321
|
+
})
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
describe("visual updates", () => {
|
|
325
|
+
const visualHTML = `
|
|
326
|
+
<div data-controller="shadcn--slider"
|
|
327
|
+
data-shadcn--slider-min-value="0"
|
|
328
|
+
data-shadcn--slider-max-value="100"
|
|
329
|
+
data-shadcn--slider-value-value="50">
|
|
330
|
+
<div data-shadcn--slider-target="track" style="width: 200px;"></div>
|
|
331
|
+
<div data-shadcn--slider-target="range" style="width: 0%;"></div>
|
|
332
|
+
<div data-shadcn--slider-target="thumb" style="left: 0;"></div>
|
|
333
|
+
<input type="hidden" data-shadcn--slider-target="input">
|
|
334
|
+
</div>
|
|
335
|
+
`
|
|
336
|
+
|
|
337
|
+
beforeEach(async () => {
|
|
338
|
+
const setup = await setupController(SliderController, visualHTML, 'shadcn--slider')
|
|
339
|
+
application = setup.application
|
|
340
|
+
element = setup.element
|
|
341
|
+
controller = setup.controller
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
test("updates range width on value change", async () => {
|
|
345
|
+
controller.valueValue = 75
|
|
346
|
+
controller.updateVisuals()
|
|
347
|
+
await nextFrame()
|
|
348
|
+
|
|
349
|
+
expect(controller.rangeTarget.style.width).toBe("75%")
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
test("updates thumb position on value change", async () => {
|
|
353
|
+
controller.valueValue = 75
|
|
354
|
+
controller.updateVisuals()
|
|
355
|
+
await nextFrame()
|
|
356
|
+
|
|
357
|
+
expect(controller.thumbTarget.style.left).toBe("calc(75% - 8px)")
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
test("updates aria-valuenow on value change", async () => {
|
|
361
|
+
controller.valueValue = 75
|
|
362
|
+
controller.updateVisuals()
|
|
363
|
+
await nextFrame()
|
|
364
|
+
|
|
365
|
+
expect(element.getAttribute("aria-valuenow")).toBe("75")
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
test("updates hidden input on value change", async () => {
|
|
369
|
+
controller.valueValue = 75
|
|
370
|
+
controller.updateVisuals()
|
|
371
|
+
await nextFrame()
|
|
372
|
+
|
|
373
|
+
expect(controller.inputTarget.value).toBe("75")
|
|
374
|
+
})
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
describe("output formatting", () => {
|
|
378
|
+
const outputHTML = `
|
|
379
|
+
<div data-controller="shadcn--slider"
|
|
380
|
+
data-shadcn--slider-min-value="0"
|
|
381
|
+
data-shadcn--slider-max-value="100"
|
|
382
|
+
data-shadcn--slider-value-value="50"
|
|
383
|
+
data-shadcn--slider-output-format-value="{value}%">
|
|
384
|
+
<div data-shadcn--slider-target="track" style="width: 200px;"></div>
|
|
385
|
+
<div data-shadcn--slider-target="range"></div>
|
|
386
|
+
<div data-shadcn--slider-target="thumb"></div>
|
|
387
|
+
<span data-shadcn--slider-target="output"></span>
|
|
388
|
+
</div>
|
|
389
|
+
`
|
|
390
|
+
|
|
391
|
+
beforeEach(async () => {
|
|
392
|
+
const setup = await setupController(SliderController, outputHTML, 'shadcn--slider')
|
|
393
|
+
application = setup.application
|
|
394
|
+
element = setup.element
|
|
395
|
+
controller = setup.controller
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
test("formats output with value", async () => {
|
|
399
|
+
controller.updateVisuals()
|
|
400
|
+
await nextFrame()
|
|
401
|
+
|
|
402
|
+
expect(controller.outputTarget.textContent).toBe("50%")
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
test("updates output on value change", async () => {
|
|
406
|
+
controller.valueValue = 75
|
|
407
|
+
controller.updateVisuals()
|
|
408
|
+
await nextFrame()
|
|
409
|
+
|
|
410
|
+
expect(controller.outputTarget.textContent).toBe("75%")
|
|
411
|
+
})
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
describe("output formatting with percent", () => {
|
|
415
|
+
const percentOutputHTML = `
|
|
416
|
+
<div data-controller="shadcn--slider"
|
|
417
|
+
data-shadcn--slider-min-value="0"
|
|
418
|
+
data-shadcn--slider-max-value="200"
|
|
419
|
+
data-shadcn--slider-value-value="100"
|
|
420
|
+
data-shadcn--slider-output-format-value="Value: {value}, Progress: {percent}%">
|
|
421
|
+
<div data-shadcn--slider-target="track" style="width: 200px;"></div>
|
|
422
|
+
<div data-shadcn--slider-target="range"></div>
|
|
423
|
+
<div data-shadcn--slider-target="thumb"></div>
|
|
424
|
+
<span data-shadcn--slider-target="output"></span>
|
|
425
|
+
</div>
|
|
426
|
+
`
|
|
427
|
+
|
|
428
|
+
beforeEach(async () => {
|
|
429
|
+
const setup = await setupController(SliderController, percentOutputHTML, 'shadcn--slider')
|
|
430
|
+
application = setup.application
|
|
431
|
+
element = setup.element
|
|
432
|
+
controller = setup.controller
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
test("formats output with both value and percent", async () => {
|
|
436
|
+
controller.updateVisuals()
|
|
437
|
+
await nextFrame()
|
|
438
|
+
|
|
439
|
+
expect(controller.outputTarget.textContent).toBe("Value: 100, Progress: 50%")
|
|
440
|
+
})
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
describe("drag functionality", () => {
|
|
444
|
+
const dragHTML = `
|
|
445
|
+
<div data-controller="shadcn--slider"
|
|
446
|
+
data-shadcn--slider-min-value="0"
|
|
447
|
+
data-shadcn--slider-max-value="100"
|
|
448
|
+
data-shadcn--slider-step-value="1"
|
|
449
|
+
data-shadcn--slider-value-value="50">
|
|
450
|
+
<div data-shadcn--slider-target="track" style="width: 200px; height: 8px;"></div>
|
|
451
|
+
<div data-shadcn--slider-target="range"></div>
|
|
452
|
+
<div data-shadcn--slider-target="thumb"
|
|
453
|
+
data-action="mousedown->shadcn--slider#startDrag"></div>
|
|
454
|
+
<input type="hidden" data-shadcn--slider-target="input">
|
|
455
|
+
</div>
|
|
456
|
+
`
|
|
457
|
+
|
|
458
|
+
beforeEach(async () => {
|
|
459
|
+
const setup = await setupController(SliderController, dragHTML, 'shadcn--slider')
|
|
460
|
+
application = setup.application
|
|
461
|
+
element = setup.element
|
|
462
|
+
controller = setup.controller
|
|
463
|
+
|
|
464
|
+
// Mock getBoundingClientRect for track
|
|
465
|
+
controller.trackTarget.getBoundingClientRect = jest.fn().mockReturnValue({
|
|
466
|
+
left: 0,
|
|
467
|
+
right: 200,
|
|
468
|
+
width: 200,
|
|
469
|
+
top: 0,
|
|
470
|
+
bottom: 8,
|
|
471
|
+
height: 8
|
|
472
|
+
})
|
|
473
|
+
})
|
|
474
|
+
|
|
475
|
+
test("starts drag on mousedown", () => {
|
|
476
|
+
const event = {
|
|
477
|
+
preventDefault: jest.fn(),
|
|
478
|
+
type: "mousedown",
|
|
479
|
+
clientX: 100
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
controller.startDrag(event)
|
|
483
|
+
|
|
484
|
+
expect(controller.isDragging).toBe(true)
|
|
485
|
+
})
|
|
486
|
+
|
|
487
|
+
test("updates value during drag", () => {
|
|
488
|
+
controller.isDragging = true
|
|
489
|
+
|
|
490
|
+
const event = {
|
|
491
|
+
type: "mousemove",
|
|
492
|
+
clientX: 150
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
controller.handleDrag(event)
|
|
496
|
+
|
|
497
|
+
expect(controller.valueValue).toBe(75)
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
test("clamps value to track bounds", () => {
|
|
501
|
+
controller.isDragging = true
|
|
502
|
+
|
|
503
|
+
// Beyond right edge
|
|
504
|
+
controller.handleDrag({ type: "mousemove", clientX: 300 })
|
|
505
|
+
expect(controller.valueValue).toBe(100)
|
|
506
|
+
|
|
507
|
+
// Beyond left edge
|
|
508
|
+
controller.handleDrag({ type: "mousemove", clientX: -50 })
|
|
509
|
+
expect(controller.valueValue).toBe(0)
|
|
510
|
+
})
|
|
511
|
+
|
|
512
|
+
test("stops drag and removes listeners", () => {
|
|
513
|
+
controller.isDragging = true
|
|
514
|
+
controller.boundHandleDrag = jest.fn()
|
|
515
|
+
controller.boundStopDrag = jest.fn()
|
|
516
|
+
|
|
517
|
+
const removeEventListenerSpy = jest.spyOn(document, 'removeEventListener')
|
|
518
|
+
|
|
519
|
+
controller.stopDrag()
|
|
520
|
+
|
|
521
|
+
expect(controller.isDragging).toBe(false)
|
|
522
|
+
expect(removeEventListenerSpy).toHaveBeenCalledWith("mousemove", controller.boundHandleDrag)
|
|
523
|
+
expect(removeEventListenerSpy).toHaveBeenCalledWith("mouseup", controller.boundStopDrag)
|
|
524
|
+
})
|
|
525
|
+
})
|
|
526
|
+
|
|
527
|
+
describe("touch events", () => {
|
|
528
|
+
const touchHTML = `
|
|
529
|
+
<div data-controller="shadcn--slider"
|
|
530
|
+
data-shadcn--slider-min-value="0"
|
|
531
|
+
data-shadcn--slider-max-value="100"
|
|
532
|
+
data-shadcn--slider-value-value="50">
|
|
533
|
+
<div data-shadcn--slider-target="track" style="width: 200px;"></div>
|
|
534
|
+
<div data-shadcn--slider-target="range"></div>
|
|
535
|
+
<div data-shadcn--slider-target="thumb"></div>
|
|
536
|
+
</div>
|
|
537
|
+
`
|
|
538
|
+
|
|
539
|
+
beforeEach(async () => {
|
|
540
|
+
const setup = await setupController(SliderController, touchHTML, 'shadcn--slider')
|
|
541
|
+
application = setup.application
|
|
542
|
+
element = setup.element
|
|
543
|
+
controller = setup.controller
|
|
544
|
+
|
|
545
|
+
controller.trackTarget.getBoundingClientRect = jest.fn().mockReturnValue({
|
|
546
|
+
left: 0,
|
|
547
|
+
right: 200,
|
|
548
|
+
width: 200,
|
|
549
|
+
top: 0,
|
|
550
|
+
bottom: 8,
|
|
551
|
+
height: 8
|
|
552
|
+
})
|
|
553
|
+
})
|
|
554
|
+
|
|
555
|
+
test("handles touch events", () => {
|
|
556
|
+
controller.isDragging = true
|
|
557
|
+
|
|
558
|
+
const event = {
|
|
559
|
+
type: "touchmove",
|
|
560
|
+
touches: [{ clientX: 100 }]
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
controller.handleDrag(event)
|
|
564
|
+
|
|
565
|
+
expect(controller.valueValue).toBe(50)
|
|
566
|
+
})
|
|
567
|
+
})
|
|
568
|
+
|
|
569
|
+
describe("native input range support (updateStyle)", () => {
|
|
570
|
+
const nativeInputHTML = `
|
|
571
|
+
<div data-controller="shadcn--slider"
|
|
572
|
+
data-shadcn--slider-value-value="50">
|
|
573
|
+
<input type="range"
|
|
574
|
+
min="0"
|
|
575
|
+
max="100"
|
|
576
|
+
value="50"
|
|
577
|
+
data-action="input->shadcn--slider#updateStyle">
|
|
578
|
+
<span data-shadcn--slider-target="output"></span>
|
|
579
|
+
</div>
|
|
580
|
+
`
|
|
581
|
+
|
|
582
|
+
beforeEach(async () => {
|
|
583
|
+
const setup = await setupController(SliderController, nativeInputHTML, 'shadcn--slider')
|
|
584
|
+
application = setup.application
|
|
585
|
+
element = setup.element
|
|
586
|
+
controller = setup.controller
|
|
587
|
+
})
|
|
588
|
+
|
|
589
|
+
test("updates CSS custom property on input", () => {
|
|
590
|
+
const input = element.querySelector('input[type="range"]')
|
|
591
|
+
input.value = "75"
|
|
592
|
+
|
|
593
|
+
const setPropertySpy = jest.spyOn(input.style, 'setProperty')
|
|
594
|
+
|
|
595
|
+
controller.updateStyle({ target: input })
|
|
596
|
+
|
|
597
|
+
expect(setPropertySpy).toHaveBeenCalledWith("--slider-fill", "75%")
|
|
598
|
+
})
|
|
599
|
+
|
|
600
|
+
test("dispatches change event on native input", () => {
|
|
601
|
+
let eventDetail = null
|
|
602
|
+
element.addEventListener("shadcn--slider:change", (e) => {
|
|
603
|
+
eventDetail = e.detail
|
|
604
|
+
})
|
|
605
|
+
|
|
606
|
+
const input = element.querySelector('input[type="range"]')
|
|
607
|
+
input.value = "75"
|
|
608
|
+
controller.updateStyle({ target: input })
|
|
609
|
+
|
|
610
|
+
expect(eventDetail).not.toBeNull()
|
|
611
|
+
expect(eventDetail.value).toBe(75)
|
|
612
|
+
expect(eventDetail.percentage).toBe(75)
|
|
613
|
+
})
|
|
614
|
+
})
|
|
615
|
+
|
|
616
|
+
describe("ID-based output targeting (data-output-target)", () => {
|
|
617
|
+
let outputDisplay
|
|
618
|
+
|
|
619
|
+
beforeEach(async () => {
|
|
620
|
+
const idOutputHTML = `
|
|
621
|
+
<div data-controller="shadcn--slider"
|
|
622
|
+
data-shadcn--slider-value-value="50">
|
|
623
|
+
<input type="range"
|
|
624
|
+
min="0"
|
|
625
|
+
max="100"
|
|
626
|
+
value="50"
|
|
627
|
+
data-output-target="slider-value-display"
|
|
628
|
+
data-output-format="{value}%"
|
|
629
|
+
data-action="input->shadcn--slider#updateStyle">
|
|
630
|
+
</div>
|
|
631
|
+
`
|
|
632
|
+
|
|
633
|
+
const setup = await setupController(SliderController, idOutputHTML, 'shadcn--slider')
|
|
634
|
+
application = setup.application
|
|
635
|
+
element = setup.element
|
|
636
|
+
controller = setup.controller
|
|
637
|
+
|
|
638
|
+
// Create output element AFTER setupController (which clears body.innerHTML)
|
|
639
|
+
outputDisplay = document.createElement('span')
|
|
640
|
+
outputDisplay.id = "slider-value-display"
|
|
641
|
+
outputDisplay.textContent = "50"
|
|
642
|
+
document.body.appendChild(outputDisplay)
|
|
643
|
+
})
|
|
644
|
+
|
|
645
|
+
afterEach(() => {
|
|
646
|
+
if (outputDisplay && outputDisplay.parentNode) {
|
|
647
|
+
outputDisplay.parentNode.removeChild(outputDisplay)
|
|
648
|
+
}
|
|
649
|
+
})
|
|
650
|
+
|
|
651
|
+
test("updates external element by ID on input", () => {
|
|
652
|
+
const input = element.querySelector('input[type="range"]')
|
|
653
|
+
|
|
654
|
+
input.value = "75"
|
|
655
|
+
controller.updateStyle({ target: input })
|
|
656
|
+
|
|
657
|
+
expect(outputDisplay.textContent).toBe("75%")
|
|
658
|
+
})
|
|
659
|
+
|
|
660
|
+
test("uses format string with {value} placeholder", () => {
|
|
661
|
+
const input = element.querySelector('input[type="range"]')
|
|
662
|
+
|
|
663
|
+
input.value = "30"
|
|
664
|
+
controller.updateStyle({ target: input })
|
|
665
|
+
|
|
666
|
+
expect(outputDisplay.textContent).toBe("30%")
|
|
667
|
+
})
|
|
668
|
+
|
|
669
|
+
test("supports {percent} placeholder in format string", () => {
|
|
670
|
+
const input = element.querySelector('input[type="range"]')
|
|
671
|
+
input.dataset.outputFormat = "{percent}% complete"
|
|
672
|
+
|
|
673
|
+
input.value = "50"
|
|
674
|
+
controller.updateStyle({ target: input })
|
|
675
|
+
|
|
676
|
+
expect(outputDisplay.textContent).toBe("50% complete")
|
|
677
|
+
})
|
|
678
|
+
|
|
679
|
+
test("handles missing output element gracefully", () => {
|
|
680
|
+
const input = element.querySelector('input[type="range"]')
|
|
681
|
+
input.dataset.outputTarget = "non-existent-id"
|
|
682
|
+
|
|
683
|
+
expect(() => {
|
|
684
|
+
input.value = "75"
|
|
685
|
+
controller.updateStyle({ target: input })
|
|
686
|
+
}).not.toThrow()
|
|
687
|
+
})
|
|
688
|
+
|
|
689
|
+
test("defaults to {value} format when not specified", () => {
|
|
690
|
+
const input = element.querySelector('input[type="range"]')
|
|
691
|
+
delete input.dataset.outputFormat
|
|
692
|
+
|
|
693
|
+
input.value = "42"
|
|
694
|
+
controller.updateStyle({ target: input })
|
|
695
|
+
|
|
696
|
+
expect(outputDisplay.textContent).toBe("42")
|
|
697
|
+
})
|
|
698
|
+
})
|
|
699
|
+
|
|
700
|
+
describe("valueValueChanged callback", () => {
|
|
701
|
+
const callbackHTML = `
|
|
702
|
+
<div data-controller="shadcn--slider"
|
|
703
|
+
data-shadcn--slider-value-value="50">
|
|
704
|
+
<div data-shadcn--slider-target="track" style="width: 200px;"></div>
|
|
705
|
+
<div data-shadcn--slider-target="range"></div>
|
|
706
|
+
<div data-shadcn--slider-target="thumb"></div>
|
|
707
|
+
</div>
|
|
708
|
+
`
|
|
709
|
+
|
|
710
|
+
beforeEach(async () => {
|
|
711
|
+
const setup = await setupController(SliderController, callbackHTML, 'shadcn--slider')
|
|
712
|
+
application = setup.application
|
|
713
|
+
element = setup.element
|
|
714
|
+
controller = setup.controller
|
|
715
|
+
})
|
|
716
|
+
|
|
717
|
+
test("updates visuals when value changes programmatically", async () => {
|
|
718
|
+
const updateVisualsSpy = jest.spyOn(controller, 'updateVisuals')
|
|
719
|
+
|
|
720
|
+
controller.valueValue = 75
|
|
721
|
+
await nextFrame()
|
|
722
|
+
|
|
723
|
+
expect(updateVisualsSpy).toHaveBeenCalled()
|
|
724
|
+
})
|
|
725
|
+
})
|
|
726
|
+
|
|
727
|
+
describe("two-way input binding (data-input-target)", () => {
|
|
728
|
+
let linkedInput
|
|
729
|
+
|
|
730
|
+
beforeEach(async () => {
|
|
731
|
+
const twoWayHTML = `
|
|
732
|
+
<div data-controller="shadcn--slider"
|
|
733
|
+
data-shadcn--slider-value-value="50">
|
|
734
|
+
<input type="range"
|
|
735
|
+
id="volume-slider"
|
|
736
|
+
min="0"
|
|
737
|
+
max="100"
|
|
738
|
+
step="1"
|
|
739
|
+
value="50"
|
|
740
|
+
data-input-target="volume-input"
|
|
741
|
+
data-action="input->shadcn--slider#updateStyle">
|
|
742
|
+
</div>
|
|
743
|
+
`
|
|
744
|
+
|
|
745
|
+
const setup = await setupController(SliderController, twoWayHTML, 'shadcn--slider')
|
|
746
|
+
application = setup.application
|
|
747
|
+
element = setup.element
|
|
748
|
+
controller = setup.controller
|
|
749
|
+
|
|
750
|
+
// Create linked input element AFTER setupController (which clears body.innerHTML)
|
|
751
|
+
linkedInput = document.createElement('input')
|
|
752
|
+
linkedInput.type = "number"
|
|
753
|
+
linkedInput.id = "volume-input"
|
|
754
|
+
linkedInput.value = "50"
|
|
755
|
+
linkedInput.min = "0"
|
|
756
|
+
linkedInput.max = "100"
|
|
757
|
+
document.body.appendChild(linkedInput)
|
|
758
|
+
|
|
759
|
+
// Re-run setup to bind the new input
|
|
760
|
+
controller.setupTwoWayBindings()
|
|
761
|
+
})
|
|
762
|
+
|
|
763
|
+
afterEach(() => {
|
|
764
|
+
if (linkedInput && linkedInput.parentNode) {
|
|
765
|
+
linkedInput.parentNode.removeChild(linkedInput)
|
|
766
|
+
}
|
|
767
|
+
})
|
|
768
|
+
|
|
769
|
+
test("syncs slider value to linked input (slider → input)", () => {
|
|
770
|
+
const rangeInput = element.querySelector('input[type="range"]')
|
|
771
|
+
|
|
772
|
+
rangeInput.value = "75"
|
|
773
|
+
controller.updateStyle({ target: rangeInput })
|
|
774
|
+
|
|
775
|
+
expect(linkedInput.value).toBe("75")
|
|
776
|
+
})
|
|
777
|
+
|
|
778
|
+
test("syncs linked input value to slider (input → slider)", () => {
|
|
779
|
+
const rangeInput = element.querySelector('input[type="range"]')
|
|
780
|
+
|
|
781
|
+
linkedInput.value = "25"
|
|
782
|
+
linkedInput.dispatchEvent(new Event('input', { bubbles: true }))
|
|
783
|
+
|
|
784
|
+
expect(rangeInput.value).toBe("25")
|
|
785
|
+
})
|
|
786
|
+
|
|
787
|
+
test("clamps linked input value to max", () => {
|
|
788
|
+
const rangeInput = element.querySelector('input[type="range"]')
|
|
789
|
+
|
|
790
|
+
linkedInput.value = "150"
|
|
791
|
+
linkedInput.dispatchEvent(new Event('input', { bubbles: true }))
|
|
792
|
+
|
|
793
|
+
expect(rangeInput.value).toBe("100")
|
|
794
|
+
expect(linkedInput.value).toBe("100")
|
|
795
|
+
})
|
|
796
|
+
|
|
797
|
+
test("clamps linked input value to min", () => {
|
|
798
|
+
const rangeInput = element.querySelector('input[type="range"]')
|
|
799
|
+
|
|
800
|
+
linkedInput.value = "-10"
|
|
801
|
+
linkedInput.dispatchEvent(new Event('input', { bubbles: true }))
|
|
802
|
+
|
|
803
|
+
expect(rangeInput.value).toBe("0")
|
|
804
|
+
expect(linkedInput.value).toBe("0")
|
|
805
|
+
})
|
|
806
|
+
|
|
807
|
+
test("snaps linked input value to step", () => {
|
|
808
|
+
const rangeInput = element.querySelector('input[type="range"]')
|
|
809
|
+
rangeInput.step = "10"
|
|
810
|
+
|
|
811
|
+
linkedInput.value = "27"
|
|
812
|
+
linkedInput.dispatchEvent(new Event('input', { bubbles: true }))
|
|
813
|
+
|
|
814
|
+
expect(rangeInput.value).toBe("30")
|
|
815
|
+
expect(linkedInput.value).toBe("30")
|
|
816
|
+
})
|
|
817
|
+
|
|
818
|
+
test("handles invalid linked input value", () => {
|
|
819
|
+
const rangeInput = element.querySelector('input[type="range"]')
|
|
820
|
+
|
|
821
|
+
linkedInput.value = "invalid"
|
|
822
|
+
linkedInput.dispatchEvent(new Event('input', { bubbles: true }))
|
|
823
|
+
|
|
824
|
+
expect(rangeInput.value).toBe("0")
|
|
825
|
+
expect(linkedInput.value).toBe("0")
|
|
826
|
+
})
|
|
827
|
+
|
|
828
|
+
test("updates CSS fill when syncing from linked input", () => {
|
|
829
|
+
const rangeInput = element.querySelector('input[type="range"]')
|
|
830
|
+
const setPropertySpy = jest.spyOn(rangeInput.style, 'setProperty')
|
|
831
|
+
|
|
832
|
+
linkedInput.value = "75"
|
|
833
|
+
linkedInput.dispatchEvent(new Event('input', { bubbles: true }))
|
|
834
|
+
|
|
835
|
+
expect(setPropertySpy).toHaveBeenCalledWith("--slider-fill", "75%")
|
|
836
|
+
})
|
|
837
|
+
|
|
838
|
+
test("dispatches change event when syncing from linked input", () => {
|
|
839
|
+
let eventDetail = null
|
|
840
|
+
element.addEventListener("shadcn--slider:change", (e) => {
|
|
841
|
+
eventDetail = e.detail
|
|
842
|
+
})
|
|
843
|
+
|
|
844
|
+
linkedInput.value = "60"
|
|
845
|
+
linkedInput.dispatchEvent(new Event('input', { bubbles: true }))
|
|
846
|
+
|
|
847
|
+
expect(eventDetail).not.toBeNull()
|
|
848
|
+
expect(eventDetail.value).toBe(60)
|
|
849
|
+
expect(eventDetail.percentage).toBe(60)
|
|
850
|
+
})
|
|
851
|
+
|
|
852
|
+
test("handles missing linked input gracefully", () => {
|
|
853
|
+
const rangeInput = element.querySelector('input[type="range"]')
|
|
854
|
+
rangeInput.dataset.inputTarget = "non-existent-id"
|
|
855
|
+
|
|
856
|
+
expect(() => {
|
|
857
|
+
rangeInput.value = "75"
|
|
858
|
+
controller.updateStyle({ target: rangeInput })
|
|
859
|
+
}).not.toThrow()
|
|
860
|
+
})
|
|
861
|
+
|
|
862
|
+
test("cleans up event listeners on disconnect", () => {
|
|
863
|
+
const removeEventListenerSpy = jest.spyOn(linkedInput, 'removeEventListener')
|
|
864
|
+
|
|
865
|
+
controller.teardownTwoWayBindings()
|
|
866
|
+
|
|
867
|
+
expect(removeEventListenerSpy).toHaveBeenCalledWith('input', expect.any(Function))
|
|
868
|
+
expect(removeEventListenerSpy).toHaveBeenCalledWith('change', expect.any(Function))
|
|
869
|
+
})
|
|
870
|
+
})
|
|
871
|
+
|
|
872
|
+
describe("two-way binding with output sync", () => {
|
|
873
|
+
let linkedInput
|
|
874
|
+
let outputDisplay
|
|
875
|
+
|
|
876
|
+
beforeEach(async () => {
|
|
877
|
+
const combinedHTML = `
|
|
878
|
+
<div data-controller="shadcn--slider"
|
|
879
|
+
data-shadcn--slider-value-value="50">
|
|
880
|
+
<input type="range"
|
|
881
|
+
id="combined-slider"
|
|
882
|
+
min="0"
|
|
883
|
+
max="100"
|
|
884
|
+
value="50"
|
|
885
|
+
data-input-target="combined-input"
|
|
886
|
+
data-output-target="combined-output"
|
|
887
|
+
data-output-format="{value}%"
|
|
888
|
+
data-action="input->shadcn--slider#updateStyle">
|
|
889
|
+
</div>
|
|
890
|
+
`
|
|
891
|
+
|
|
892
|
+
const setup = await setupController(SliderController, combinedHTML, 'shadcn--slider')
|
|
893
|
+
application = setup.application
|
|
894
|
+
element = setup.element
|
|
895
|
+
controller = setup.controller
|
|
896
|
+
|
|
897
|
+
// Create linked input and output elements
|
|
898
|
+
linkedInput = document.createElement('input')
|
|
899
|
+
linkedInput.type = "number"
|
|
900
|
+
linkedInput.id = "combined-input"
|
|
901
|
+
linkedInput.value = "50"
|
|
902
|
+
document.body.appendChild(linkedInput)
|
|
903
|
+
|
|
904
|
+
outputDisplay = document.createElement('span')
|
|
905
|
+
outputDisplay.id = "combined-output"
|
|
906
|
+
outputDisplay.textContent = "50%"
|
|
907
|
+
document.body.appendChild(outputDisplay)
|
|
908
|
+
|
|
909
|
+
controller.setupTwoWayBindings()
|
|
910
|
+
})
|
|
911
|
+
|
|
912
|
+
afterEach(() => {
|
|
913
|
+
if (linkedInput && linkedInput.parentNode) {
|
|
914
|
+
linkedInput.parentNode.removeChild(linkedInput)
|
|
915
|
+
}
|
|
916
|
+
if (outputDisplay && outputDisplay.parentNode) {
|
|
917
|
+
outputDisplay.parentNode.removeChild(outputDisplay)
|
|
918
|
+
}
|
|
919
|
+
})
|
|
920
|
+
|
|
921
|
+
test("updates both linked input and output when slider changes", () => {
|
|
922
|
+
const rangeInput = element.querySelector('input[type="range"]')
|
|
923
|
+
|
|
924
|
+
rangeInput.value = "80"
|
|
925
|
+
controller.updateStyle({ target: rangeInput })
|
|
926
|
+
|
|
927
|
+
expect(linkedInput.value).toBe("80")
|
|
928
|
+
expect(outputDisplay.textContent).toBe("80%")
|
|
929
|
+
})
|
|
930
|
+
|
|
931
|
+
test("updates both slider and output when linked input changes", () => {
|
|
932
|
+
const rangeInput = element.querySelector('input[type="range"]')
|
|
933
|
+
|
|
934
|
+
linkedInput.value = "30"
|
|
935
|
+
linkedInput.dispatchEvent(new Event('input', { bubbles: true }))
|
|
936
|
+
|
|
937
|
+
expect(rangeInput.value).toBe("30")
|
|
938
|
+
expect(outputDisplay.textContent).toBe("30%")
|
|
939
|
+
})
|
|
940
|
+
})
|
|
941
|
+
|
|
942
|
+
describe("two-way binding when controller IS the input element (regression test)", () => {
|
|
943
|
+
// This tests the case where data-controller is on the <input type="range"> itself,
|
|
944
|
+
// not on a wrapper element. This is how SliderComponent actually renders.
|
|
945
|
+
let linkedInput
|
|
946
|
+
|
|
947
|
+
beforeEach(async () => {
|
|
948
|
+
// Simulate how SliderComponent renders: controller on the input element itself
|
|
949
|
+
const directInputHTML = `
|
|
950
|
+
<input type="range"
|
|
951
|
+
data-controller="shadcn--slider"
|
|
952
|
+
id="direct-slider"
|
|
953
|
+
min="0"
|
|
954
|
+
max="100"
|
|
955
|
+
step="1"
|
|
956
|
+
value="50"
|
|
957
|
+
data-input-target="direct-linked-input"
|
|
958
|
+
data-action="input->shadcn--slider#updateStyle">
|
|
959
|
+
`
|
|
960
|
+
|
|
961
|
+
const setup = await setupController(SliderController, directInputHTML, 'shadcn--slider')
|
|
962
|
+
application = setup.application
|
|
963
|
+
element = setup.element
|
|
964
|
+
controller = setup.controller
|
|
965
|
+
|
|
966
|
+
// Create linked input element AFTER setupController
|
|
967
|
+
linkedInput = document.createElement('input')
|
|
968
|
+
linkedInput.type = "number"
|
|
969
|
+
linkedInput.id = "direct-linked-input"
|
|
970
|
+
linkedInput.value = "50"
|
|
971
|
+
linkedInput.min = "0"
|
|
972
|
+
linkedInput.max = "100"
|
|
973
|
+
document.body.appendChild(linkedInput)
|
|
974
|
+
|
|
975
|
+
// Re-run setup to bind the new input
|
|
976
|
+
controller.setupTwoWayBindings()
|
|
977
|
+
})
|
|
978
|
+
|
|
979
|
+
afterEach(() => {
|
|
980
|
+
if (linkedInput && linkedInput.parentNode) {
|
|
981
|
+
linkedInput.parentNode.removeChild(linkedInput)
|
|
982
|
+
}
|
|
983
|
+
})
|
|
984
|
+
|
|
985
|
+
test("detects that controller element itself is a range input with data-input-target", () => {
|
|
986
|
+
// The element should match the selector for range inputs with data-input-target
|
|
987
|
+
expect(element.matches('input[type="range"][data-input-target]')).toBe(true)
|
|
988
|
+
})
|
|
989
|
+
|
|
990
|
+
test("sets up binding when controller element is the range input", () => {
|
|
991
|
+
// Should have one binding
|
|
992
|
+
expect(controller.inputBindings.length).toBe(1)
|
|
993
|
+
expect(controller.inputBindings[0].rangeInput).toBe(element)
|
|
994
|
+
expect(controller.inputBindings[0].linkedInput).toBe(linkedInput)
|
|
995
|
+
})
|
|
996
|
+
|
|
997
|
+
test("syncs slider value to linked input (slider → input)", () => {
|
|
998
|
+
element.value = "75"
|
|
999
|
+
controller.updateStyle({ target: element })
|
|
1000
|
+
|
|
1001
|
+
expect(linkedInput.value).toBe("75")
|
|
1002
|
+
})
|
|
1003
|
+
|
|
1004
|
+
test("syncs linked input value to slider (input → slider)", () => {
|
|
1005
|
+
linkedInput.value = "25"
|
|
1006
|
+
linkedInput.dispatchEvent(new Event('input', { bubbles: true }))
|
|
1007
|
+
|
|
1008
|
+
expect(element.value).toBe("25")
|
|
1009
|
+
})
|
|
1010
|
+
|
|
1011
|
+
test("updates CSS fill when linked input changes", () => {
|
|
1012
|
+
const setPropertySpy = jest.spyOn(element.style, 'setProperty')
|
|
1013
|
+
|
|
1014
|
+
linkedInput.value = "60"
|
|
1015
|
+
linkedInput.dispatchEvent(new Event('input', { bubbles: true }))
|
|
1016
|
+
|
|
1017
|
+
expect(setPropertySpy).toHaveBeenCalledWith("--slider-fill", "60%")
|
|
1018
|
+
})
|
|
1019
|
+
|
|
1020
|
+
test("clamps value when linked input exceeds max", () => {
|
|
1021
|
+
linkedInput.value = "150"
|
|
1022
|
+
linkedInput.dispatchEvent(new Event('input', { bubbles: true }))
|
|
1023
|
+
|
|
1024
|
+
expect(element.value).toBe("100")
|
|
1025
|
+
expect(linkedInput.value).toBe("100")
|
|
1026
|
+
})
|
|
1027
|
+
|
|
1028
|
+
test("clamps value when linked input is below min", () => {
|
|
1029
|
+
linkedInput.value = "-10"
|
|
1030
|
+
linkedInput.dispatchEvent(new Event('input', { bubbles: true }))
|
|
1031
|
+
|
|
1032
|
+
expect(element.value).toBe("0")
|
|
1033
|
+
expect(linkedInput.value).toBe("0")
|
|
1034
|
+
})
|
|
1035
|
+
})
|
|
1036
|
+
})
|