shadcn-rails 0.1.0 → 0.2.1
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 +4 -4
- data/CHANGELOG.md +69 -2
- data/README.md +102 -1398
- data/__mocks__/@floating-ui/dom.js +67 -0
- data/app/assets/javascripts/shadcn/controllers/base_menu_controller.js +266 -0
- data/app/assets/javascripts/shadcn/controllers/combobox_controller.js +34 -8
- data/app/assets/javascripts/shadcn/controllers/command_controller.js +5 -1
- data/app/assets/javascripts/shadcn/controllers/context_menu_controller.js +64 -135
- data/app/assets/javascripts/shadcn/controllers/dropdown_controller.js +56 -186
- data/app/assets/javascripts/shadcn/controllers/hover_card_controller.js +29 -55
- data/app/assets/javascripts/shadcn/controllers/menubar_controller.js +10 -7
- data/app/assets/javascripts/shadcn/controllers/navigation_menu_controller.js +10 -6
- data/app/assets/javascripts/shadcn/controllers/popover_controller.js +35 -60
- data/app/assets/javascripts/shadcn/controllers/select_controller.js +37 -17
- data/app/assets/javascripts/shadcn/controllers/sidebar_controller.js +24 -14
- data/app/assets/javascripts/shadcn/controllers/tooltip_controller.js +28 -59
- data/app/assets/javascripts/shadcn/index.js +9 -1
- data/app/assets/javascripts/shadcn/utils/floating.js +179 -0
- data/app/assets/stylesheets/shadcn/base.css +32 -0
- data/app/assets/stylesheets/shadcn/components.css +12 -0
- data/app/components/shadcn/accordion_component.html.erb +8 -0
- data/app/components/shadcn/accordion_component.rb +6 -15
- data/app/components/shadcn/alert_component.html.erb +6 -0
- data/app/components/shadcn/alert_component.rb +0 -18
- data/app/components/shadcn/alert_dialog_component.html.erb +12 -0
- data/app/components/shadcn/alert_dialog_component.rb +7 -27
- data/app/components/shadcn/aspect_ratio_component.html.erb +7 -0
- data/app/components/shadcn/aspect_ratio_component.rb +4 -19
- data/app/components/shadcn/avatar_component.html.erb +20 -0
- data/app/components/shadcn/avatar_component.rb +8 -36
- data/app/components/shadcn/badge_component.html.erb +1 -0
- data/app/components/shadcn/badge_component.rb +0 -11
- data/app/components/shadcn/base_component.rb +15 -2
- data/app/components/shadcn/breadcrumb_component.html.erb +5 -0
- data/app/components/shadcn/breadcrumb_component.rb +6 -16
- data/app/components/shadcn/button_component.html.erb +18 -0
- data/app/components/shadcn/button_component.rb +1 -41
- data/app/components/shadcn/card_component.html.erb +8 -0
- data/app/components/shadcn/card_component.rb +2 -6
- data/app/components/shadcn/checkbox_component.html.erb +32 -0
- data/app/components/shadcn/checkbox_component.rb +4 -43
- data/app/components/shadcn/collapsible_component.html.erb +8 -0
- data/app/components/shadcn/collapsible_component.rb +6 -15
- data/app/components/shadcn/command_list_component.rb +29 -14
- data/app/components/shadcn/context_menu_checkbox_item_component.rb +76 -0
- data/app/components/shadcn/context_menu_component.html.erb +11 -0
- data/app/components/shadcn/context_menu_component.rb +6 -26
- data/app/components/shadcn/context_menu_content_component.rb +37 -14
- data/app/components/shadcn/context_menu_item_component.rb +3 -2
- data/app/components/shadcn/context_menu_radio_group_component.rb +42 -0
- data/app/components/shadcn/context_menu_radio_item_component.rb +76 -0
- data/app/components/shadcn/dialog_component.html.erb +14 -0
- data/app/components/shadcn/dialog_component.rb +8 -29
- data/app/components/shadcn/drawer_component.html.erb +12 -0
- data/app/components/shadcn/drawer_component.rb +7 -27
- data/app/components/shadcn/dropdown_menu_checkbox_item_component.rb +76 -0
- data/app/components/shadcn/dropdown_menu_component.html.erb +14 -0
- data/app/components/shadcn/dropdown_menu_component.rb +9 -29
- data/app/components/shadcn/dropdown_menu_content_component.rb +45 -16
- data/app/components/shadcn/dropdown_menu_radio_group_component.rb +42 -0
- data/app/components/shadcn/dropdown_menu_radio_item_component.rb +76 -0
- data/app/components/shadcn/field_component.rb +7 -8
- data/app/components/shadcn/hover_card_component.html.erb +12 -0
- data/app/components/shadcn/hover_card_component.rb +7 -26
- data/app/components/shadcn/input_component.html.erb +18 -0
- data/app/components/shadcn/input_component.rb +2 -27
- data/app/components/shadcn/input_otp_component.rb +3 -3
- data/app/components/shadcn/kbd_component.html.erb +1 -0
- data/app/components/shadcn/kbd_component.rb +3 -10
- data/app/components/shadcn/label_component.html.erb +3 -0
- data/app/components/shadcn/label_component.rb +2 -18
- data/app/components/shadcn/menubar_component.html.erb +6 -0
- data/app/components/shadcn/menubar_component.rb +4 -15
- data/app/components/shadcn/menubar_content_component.rb +45 -20
- data/app/components/shadcn/menubar_sub_content_component.rb +21 -8
- data/app/components/shadcn/native_select_component.html.erb +22 -0
- data/app/components/shadcn/native_select_component.rb +9 -39
- data/app/components/shadcn/navigation_menu_component.html.erb +6 -0
- data/app/components/shadcn/navigation_menu_component.rb +4 -15
- data/app/components/shadcn/pagination_component.html.erb +5 -0
- data/app/components/shadcn/pagination_component.rb +11 -15
- data/app/components/shadcn/popover_component.html.erb +15 -0
- data/app/components/shadcn/popover_component.rb +10 -30
- data/app/components/shadcn/progress_component.html.erb +13 -0
- data/app/components/shadcn/progress_component.rb +6 -26
- data/app/components/shadcn/radio_group_component.html.erb +8 -0
- data/app/components/shadcn/radio_group_component.rb +12 -26
- data/app/components/shadcn/radio_group_item_component.rb +32 -6
- data/app/components/shadcn/resizable_panel_group_component.rb +27 -16
- data/app/components/shadcn/scroll_area_component.html.erb +7 -0
- data/app/components/shadcn/scroll_area_component.rb +4 -16
- data/app/components/shadcn/select_component.html.erb +46 -0
- data/app/components/shadcn/select_component.rb +29 -86
- data/app/components/shadcn/separator_component.html.erb +5 -0
- data/app/components/shadcn/separator_component.rb +6 -14
- data/app/components/shadcn/sheet_component.html.erb +12 -0
- data/app/components/shadcn/sheet_component.rb +7 -27
- data/app/components/shadcn/sidebar_component.rb +2 -2
- data/app/components/shadcn/skeleton_component.html.erb +1 -0
- data/app/components/shadcn/skeleton_component.rb +4 -2
- data/app/components/shadcn/slider_component.html.erb +12 -0
- data/app/components/shadcn/slider_component.rb +2 -21
- data/app/components/shadcn/spinner_component.html.erb +18 -0
- data/app/components/shadcn/spinner_component.rb +2 -30
- data/app/components/shadcn/switch_component.html.erb +72 -0
- data/app/components/shadcn/switch_component.rb +4 -82
- data/app/components/shadcn/table_component.html.erb +9 -0
- data/app/components/shadcn/table_component.rb +2 -10
- data/app/components/shadcn/tabs_component.html.erb +8 -0
- data/app/components/shadcn/tabs_component.rb +4 -17
- data/app/components/shadcn/textarea_component.html.erb +13 -0
- data/app/components/shadcn/textarea_component.rb +6 -22
- data/app/components/shadcn/toast_component.html.erb +36 -0
- data/app/components/shadcn/toast_component.rb +6 -54
- data/app/components/shadcn/toggle_component.html.erb +12 -0
- data/app/components/shadcn/toggle_component.rb +6 -21
- data/app/components/shadcn/toggle_group_component.html.erb +14 -0
- data/app/components/shadcn/toggle_group_component.rb +6 -29
- data/app/components/shadcn/tooltip_component.html.erb +20 -0
- data/app/components/shadcn/tooltip_component.rb +13 -38
- data/lib/generators/shadcn/add/USAGE +24 -0
- data/lib/generators/shadcn/add/add_generator.rb +279 -0
- data/lib/generators/shadcn/install/USAGE +22 -0
- data/lib/generators/shadcn/install/install_generator.rb +8 -3
- data/lib/generators/shadcn/install/templates/initializer.rb.tt +7 -27
- data/lib/generators/shadcn/install/templates/shadcn.yml.tt +15 -31
- data/lib/shadcn/rails/version.rb +1 -1
- metadata +54 -42
- data/.dockerignore +0 -40
- data/CLAUDE.md +0 -463
- data/PROGRESS.md +0 -485
- data/Rakefile +0 -29
- data/__tests__/controllers/__snapshots__/calendar_controller.test.js.snap +0 -13
- data/__tests__/controllers/__snapshots__/popover_controller.test.js.snap +0 -46
- data/__tests__/controllers/__snapshots__/sheet_controller.test.js.snap +0 -111
- data/__tests__/controllers/__snapshots__/tabs_controller.test.js.snap +0 -27
- data/__tests__/controllers/accordion_controller.test.js +0 -904
- data/__tests__/controllers/calendar_controller.test.js +0 -1370
- data/__tests__/controllers/carousel_controller.test.js +0 -912
- data/__tests__/controllers/checkbox_controller.test.js +0 -454
- data/__tests__/controllers/collapsible_controller.test.js +0 -407
- data/__tests__/controllers/combobox_controller.test.js +0 -966
- data/__tests__/controllers/context_menu_controller.test.js +0 -627
- data/__tests__/controllers/date_picker_controller.test.js +0 -636
- data/__tests__/controllers/dialog_controller.test.js +0 -878
- data/__tests__/controllers/drawer_controller.test.js +0 -995
- data/__tests__/controllers/menubar_controller.test.js +0 -736
- data/__tests__/controllers/navigation_menu_controller.test.js +0 -598
- data/__tests__/controllers/popover_controller.test.js +0 -1007
- data/__tests__/controllers/radio_group_controller.test.js +0 -640
- data/__tests__/controllers/resizable_controller.test.js +0 -680
- data/__tests__/controllers/select_controller.test.js +0 -674
- data/__tests__/controllers/sheet_controller.test.js +0 -986
- data/__tests__/controllers/slider_controller.test.js +0 -1036
- data/__tests__/controllers/switch_controller.test.js +0 -424
- data/__tests__/controllers/tabs_controller.test.js +0 -907
- data/__tests__/controllers/toggle_group_controller.test.js +0 -839
- data/__tests__/controllers/tooltip_controller.test.js +0 -808
- data/__tests__/helpers/stimulus-test-helper.js +0 -203
- data/babel.config.cjs +0 -5
- data/bin/console +0 -11
- data/bin/setup +0 -8
- data/jest.config.js +0 -19
- data/jest.setup.js +0 -8
- data/lib/generators/shadcn/component/component_generator.rb +0 -188
- data/lib/generators/shadcn/theme/theme_generator.rb +0 -128
- data/package-lock.json +0 -7415
- data/package.json +0 -68
- data/rollup.config.js +0 -29
|
@@ -1,839 +0,0 @@
|
|
|
1
|
-
import { Application } from "@hotwired/stimulus"
|
|
2
|
-
import ToggleGroupController from "../../app/assets/javascripts/shadcn/controllers/toggle_group_controller.js"
|
|
3
|
-
import { setupController, cleanupController, click, wait, nextFrame, keydown, waitForEvent } from '../helpers/stimulus-test-helper.js'
|
|
4
|
-
|
|
5
|
-
describe("ToggleGroupController", () => {
|
|
6
|
-
let application
|
|
7
|
-
let element
|
|
8
|
-
let controller
|
|
9
|
-
|
|
10
|
-
afterEach(() => {
|
|
11
|
-
cleanupController(application)
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
describe("single mode", () => {
|
|
15
|
-
const singleModeHTML = `
|
|
16
|
-
<div data-controller="shadcn--toggle-group"
|
|
17
|
-
data-shadcn--toggle-group-type-value="single">
|
|
18
|
-
<button data-shadcn--toggle-group-target="item"
|
|
19
|
-
data-value="bold"
|
|
20
|
-
data-action="click->shadcn--toggle-group#toggle">Bold</button>
|
|
21
|
-
<button data-shadcn--toggle-group-target="item"
|
|
22
|
-
data-value="italic"
|
|
23
|
-
data-action="click->shadcn--toggle-group#toggle">Italic</button>
|
|
24
|
-
<button data-shadcn--toggle-group-target="item"
|
|
25
|
-
data-value="underline"
|
|
26
|
-
data-action="click->shadcn--toggle-group#toggle">Underline</button>
|
|
27
|
-
</div>
|
|
28
|
-
`
|
|
29
|
-
|
|
30
|
-
beforeEach(async () => {
|
|
31
|
-
const setup = await setupController(ToggleGroupController, singleModeHTML, 'shadcn--toggle-group')
|
|
32
|
-
application = setup.application
|
|
33
|
-
element = setup.element
|
|
34
|
-
controller = setup.controller
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
test("initializes with type 'single' by default", () => {
|
|
38
|
-
expect(controller.typeValue).toBe("single")
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
test("initializes with empty value", () => {
|
|
42
|
-
expect(controller.valueValue).toBe("")
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
test("initializes all items with data-state='off'", () => {
|
|
46
|
-
controller.itemTargets.forEach(item => {
|
|
47
|
-
expect(item.getAttribute("data-state")).toBe("off")
|
|
48
|
-
expect(item.getAttribute("aria-pressed")).toBe("false")
|
|
49
|
-
})
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
test("selects an item when clicked", async () => {
|
|
53
|
-
const boldButton = controller.itemTargets[0]
|
|
54
|
-
click(boldButton)
|
|
55
|
-
await nextFrame()
|
|
56
|
-
|
|
57
|
-
expect(controller.valueValue).toBe("bold")
|
|
58
|
-
expect(boldButton.getAttribute("data-state")).toBe("on")
|
|
59
|
-
expect(boldButton.getAttribute("aria-pressed")).toBe("true")
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
test("deselects other items when selecting new item", async () => {
|
|
63
|
-
const boldButton = controller.itemTargets[0]
|
|
64
|
-
const italicButton = controller.itemTargets[1]
|
|
65
|
-
|
|
66
|
-
// Select bold
|
|
67
|
-
click(boldButton)
|
|
68
|
-
await nextFrame()
|
|
69
|
-
expect(controller.valueValue).toBe("bold")
|
|
70
|
-
expect(boldButton.getAttribute("data-state")).toBe("on")
|
|
71
|
-
|
|
72
|
-
// Select italic - bold should be deselected
|
|
73
|
-
click(italicButton)
|
|
74
|
-
await nextFrame()
|
|
75
|
-
expect(controller.valueValue).toBe("italic")
|
|
76
|
-
expect(italicButton.getAttribute("data-state")).toBe("on")
|
|
77
|
-
expect(boldButton.getAttribute("data-state")).toBe("off")
|
|
78
|
-
expect(boldButton.getAttribute("aria-pressed")).toBe("false")
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
test("toggles off when clicking selected item", async () => {
|
|
82
|
-
const boldButton = controller.itemTargets[0]
|
|
83
|
-
|
|
84
|
-
// Select bold
|
|
85
|
-
click(boldButton)
|
|
86
|
-
await nextFrame()
|
|
87
|
-
expect(controller.valueValue).toBe("bold")
|
|
88
|
-
expect(boldButton.getAttribute("data-state")).toBe("on")
|
|
89
|
-
|
|
90
|
-
// Click again to deselect
|
|
91
|
-
click(boldButton)
|
|
92
|
-
await nextFrame()
|
|
93
|
-
expect(controller.valueValue).toBe("")
|
|
94
|
-
expect(boldButton.getAttribute("data-state")).toBe("off")
|
|
95
|
-
expect(boldButton.getAttribute("aria-pressed")).toBe("false")
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
test("dispatches change event when selection changes", async () => {
|
|
99
|
-
const boldButton = controller.itemTargets[0]
|
|
100
|
-
let eventDetail = null
|
|
101
|
-
|
|
102
|
-
element.addEventListener("shadcn--toggle-group:change", (e) => {
|
|
103
|
-
eventDetail = e.detail
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
click(boldButton)
|
|
107
|
-
await nextFrame()
|
|
108
|
-
|
|
109
|
-
expect(eventDetail).not.toBeNull()
|
|
110
|
-
expect(eventDetail.value).toEqual(["bold"])
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
test("dispatches change event on deselection", async () => {
|
|
114
|
-
const boldButton = controller.itemTargets[0]
|
|
115
|
-
let changeCount = 0
|
|
116
|
-
let lastEventDetail = null
|
|
117
|
-
|
|
118
|
-
element.addEventListener("shadcn--toggle-group:change", (e) => {
|
|
119
|
-
changeCount++
|
|
120
|
-
lastEventDetail = e.detail
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
// Select
|
|
124
|
-
click(boldButton)
|
|
125
|
-
await nextFrame()
|
|
126
|
-
expect(changeCount).toBe(1)
|
|
127
|
-
expect(lastEventDetail.value).toEqual(["bold"])
|
|
128
|
-
|
|
129
|
-
// Deselect
|
|
130
|
-
click(boldButton)
|
|
131
|
-
await nextFrame()
|
|
132
|
-
expect(changeCount).toBe(2)
|
|
133
|
-
expect(lastEventDetail.value).toEqual([])
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
test("only one item can be selected at a time", async () => {
|
|
137
|
-
const boldButton = controller.itemTargets[0]
|
|
138
|
-
const italicButton = controller.itemTargets[1]
|
|
139
|
-
const underlineButton = controller.itemTargets[2]
|
|
140
|
-
|
|
141
|
-
click(boldButton)
|
|
142
|
-
await nextFrame()
|
|
143
|
-
expect(controller.getValues()).toEqual(["bold"])
|
|
144
|
-
|
|
145
|
-
click(italicButton)
|
|
146
|
-
await nextFrame()
|
|
147
|
-
expect(controller.getValues()).toEqual(["italic"])
|
|
148
|
-
|
|
149
|
-
click(underlineButton)
|
|
150
|
-
await nextFrame()
|
|
151
|
-
expect(controller.getValues()).toEqual(["underline"])
|
|
152
|
-
|
|
153
|
-
// Verify only underline is on
|
|
154
|
-
expect(boldButton.getAttribute("data-state")).toBe("off")
|
|
155
|
-
expect(italicButton.getAttribute("data-state")).toBe("off")
|
|
156
|
-
expect(underlineButton.getAttribute("data-state")).toBe("on")
|
|
157
|
-
})
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
describe("multiple mode", () => {
|
|
161
|
-
const multipleModeHTML = `
|
|
162
|
-
<div data-controller="shadcn--toggle-group"
|
|
163
|
-
data-shadcn--toggle-group-type-value="multiple">
|
|
164
|
-
<button data-shadcn--toggle-group-target="item"
|
|
165
|
-
data-value="bold"
|
|
166
|
-
data-action="click->shadcn--toggle-group#toggle">Bold</button>
|
|
167
|
-
<button data-shadcn--toggle-group-target="item"
|
|
168
|
-
data-value="italic"
|
|
169
|
-
data-action="click->shadcn--toggle-group#toggle">Italic</button>
|
|
170
|
-
<button data-shadcn--toggle-group-target="item"
|
|
171
|
-
data-value="underline"
|
|
172
|
-
data-action="click->shadcn--toggle-group#toggle">Underline</button>
|
|
173
|
-
</div>
|
|
174
|
-
`
|
|
175
|
-
|
|
176
|
-
beforeEach(async () => {
|
|
177
|
-
const setup = await setupController(ToggleGroupController, multipleModeHTML, 'shadcn--toggle-group')
|
|
178
|
-
application = setup.application
|
|
179
|
-
element = setup.element
|
|
180
|
-
controller = setup.controller
|
|
181
|
-
})
|
|
182
|
-
|
|
183
|
-
test("initializes with type 'multiple'", () => {
|
|
184
|
-
expect(controller.typeValue).toBe("multiple")
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
test("can select multiple items", async () => {
|
|
188
|
-
const boldButton = controller.itemTargets[0]
|
|
189
|
-
const italicButton = controller.itemTargets[1]
|
|
190
|
-
|
|
191
|
-
click(boldButton)
|
|
192
|
-
await nextFrame()
|
|
193
|
-
expect(controller.valueValue).toBe("bold")
|
|
194
|
-
expect(boldButton.getAttribute("data-state")).toBe("on")
|
|
195
|
-
|
|
196
|
-
click(italicButton)
|
|
197
|
-
await nextFrame()
|
|
198
|
-
expect(controller.valueValue).toBe("bold,italic")
|
|
199
|
-
expect(boldButton.getAttribute("data-state")).toBe("on")
|
|
200
|
-
expect(italicButton.getAttribute("data-state")).toBe("on")
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
test("can select all items", async () => {
|
|
204
|
-
const boldButton = controller.itemTargets[0]
|
|
205
|
-
const italicButton = controller.itemTargets[1]
|
|
206
|
-
const underlineButton = controller.itemTargets[2]
|
|
207
|
-
|
|
208
|
-
click(boldButton)
|
|
209
|
-
await nextFrame()
|
|
210
|
-
click(italicButton)
|
|
211
|
-
await nextFrame()
|
|
212
|
-
click(underlineButton)
|
|
213
|
-
await nextFrame()
|
|
214
|
-
|
|
215
|
-
expect(controller.valueValue).toBe("bold,italic,underline")
|
|
216
|
-
expect(controller.getValues()).toEqual(["bold", "italic", "underline"])
|
|
217
|
-
|
|
218
|
-
// All should be on
|
|
219
|
-
controller.itemTargets.forEach(item => {
|
|
220
|
-
expect(item.getAttribute("data-state")).toBe("on")
|
|
221
|
-
expect(item.getAttribute("aria-pressed")).toBe("true")
|
|
222
|
-
})
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
test("can deselect individual items", async () => {
|
|
226
|
-
const boldButton = controller.itemTargets[0]
|
|
227
|
-
const italicButton = controller.itemTargets[1]
|
|
228
|
-
|
|
229
|
-
// Select both
|
|
230
|
-
click(boldButton)
|
|
231
|
-
await nextFrame()
|
|
232
|
-
click(italicButton)
|
|
233
|
-
await nextFrame()
|
|
234
|
-
expect(controller.valueValue).toBe("bold,italic")
|
|
235
|
-
|
|
236
|
-
// Deselect bold
|
|
237
|
-
click(boldButton)
|
|
238
|
-
await nextFrame()
|
|
239
|
-
expect(controller.valueValue).toBe("italic")
|
|
240
|
-
expect(boldButton.getAttribute("data-state")).toBe("off")
|
|
241
|
-
expect(italicButton.getAttribute("data-state")).toBe("on")
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
test("maintains order when toggling items", async () => {
|
|
245
|
-
const boldButton = controller.itemTargets[0]
|
|
246
|
-
const italicButton = controller.itemTargets[1]
|
|
247
|
-
const underlineButton = controller.itemTargets[2]
|
|
248
|
-
|
|
249
|
-
// Select in specific order: italic, bold, underline
|
|
250
|
-
click(italicButton)
|
|
251
|
-
await nextFrame()
|
|
252
|
-
click(boldButton)
|
|
253
|
-
await nextFrame()
|
|
254
|
-
click(underlineButton)
|
|
255
|
-
await nextFrame()
|
|
256
|
-
|
|
257
|
-
// Should maintain selection order
|
|
258
|
-
expect(controller.valueValue).toBe("italic,bold,underline")
|
|
259
|
-
})
|
|
260
|
-
|
|
261
|
-
test("removes item from middle of selection", async () => {
|
|
262
|
-
const boldButton = controller.itemTargets[0]
|
|
263
|
-
const italicButton = controller.itemTargets[1]
|
|
264
|
-
const underlineButton = controller.itemTargets[2]
|
|
265
|
-
|
|
266
|
-
// Select all three
|
|
267
|
-
click(boldButton)
|
|
268
|
-
await nextFrame()
|
|
269
|
-
click(italicButton)
|
|
270
|
-
await nextFrame()
|
|
271
|
-
click(underlineButton)
|
|
272
|
-
await nextFrame()
|
|
273
|
-
expect(controller.valueValue).toBe("bold,italic,underline")
|
|
274
|
-
|
|
275
|
-
// Deselect middle item
|
|
276
|
-
click(italicButton)
|
|
277
|
-
await nextFrame()
|
|
278
|
-
expect(controller.valueValue).toBe("bold,underline")
|
|
279
|
-
})
|
|
280
|
-
|
|
281
|
-
test("handles deselecting all items", async () => {
|
|
282
|
-
const boldButton = controller.itemTargets[0]
|
|
283
|
-
const italicButton = controller.itemTargets[1]
|
|
284
|
-
|
|
285
|
-
// Select two items
|
|
286
|
-
click(boldButton)
|
|
287
|
-
await nextFrame()
|
|
288
|
-
click(italicButton)
|
|
289
|
-
await nextFrame()
|
|
290
|
-
expect(controller.valueValue).toBe("bold,italic")
|
|
291
|
-
|
|
292
|
-
// Deselect both
|
|
293
|
-
click(boldButton)
|
|
294
|
-
await nextFrame()
|
|
295
|
-
click(italicButton)
|
|
296
|
-
await nextFrame()
|
|
297
|
-
|
|
298
|
-
expect(controller.valueValue).toBe("")
|
|
299
|
-
expect(controller.getValues()).toEqual([])
|
|
300
|
-
controller.itemTargets.forEach(item => {
|
|
301
|
-
expect(item.getAttribute("data-state")).toBe("off")
|
|
302
|
-
})
|
|
303
|
-
})
|
|
304
|
-
|
|
305
|
-
test("dispatches change event with array of selected values", async () => {
|
|
306
|
-
const boldButton = controller.itemTargets[0]
|
|
307
|
-
const italicButton = controller.itemTargets[1]
|
|
308
|
-
let eventDetail = null
|
|
309
|
-
|
|
310
|
-
element.addEventListener("shadcn--toggle-group:change", (e) => {
|
|
311
|
-
eventDetail = e.detail
|
|
312
|
-
})
|
|
313
|
-
|
|
314
|
-
click(boldButton)
|
|
315
|
-
await nextFrame()
|
|
316
|
-
expect(eventDetail.value).toEqual(["bold"])
|
|
317
|
-
|
|
318
|
-
click(italicButton)
|
|
319
|
-
await nextFrame()
|
|
320
|
-
expect(eventDetail.value).toEqual(["bold", "italic"])
|
|
321
|
-
})
|
|
322
|
-
|
|
323
|
-
test("comma-separated values are parsed correctly", async () => {
|
|
324
|
-
// Manually set value
|
|
325
|
-
controller.valueValue = "bold,italic,underline"
|
|
326
|
-
await nextFrame()
|
|
327
|
-
|
|
328
|
-
expect(controller.getValues()).toEqual(["bold", "italic", "underline"])
|
|
329
|
-
|
|
330
|
-
// All items should be marked as on
|
|
331
|
-
controller.itemTargets.forEach(item => {
|
|
332
|
-
expect(item.getAttribute("data-state")).toBe("on")
|
|
333
|
-
})
|
|
334
|
-
})
|
|
335
|
-
})
|
|
336
|
-
|
|
337
|
-
describe("value initialization", () => {
|
|
338
|
-
test("initializes with pre-selected value in single mode", async () => {
|
|
339
|
-
const html = `
|
|
340
|
-
<div data-controller="shadcn--toggle-group"
|
|
341
|
-
data-shadcn--toggle-group-type-value="single"
|
|
342
|
-
data-shadcn--toggle-group-value-value="italic">
|
|
343
|
-
<button data-shadcn--toggle-group-target="item" data-value="bold">Bold</button>
|
|
344
|
-
<button data-shadcn--toggle-group-target="item" data-value="italic">Italic</button>
|
|
345
|
-
</div>
|
|
346
|
-
`
|
|
347
|
-
|
|
348
|
-
const setup = await setupController(ToggleGroupController, html, 'shadcn--toggle-group')
|
|
349
|
-
application = setup.application
|
|
350
|
-
element = setup.element
|
|
351
|
-
controller = setup.controller
|
|
352
|
-
|
|
353
|
-
expect(controller.valueValue).toBe("italic")
|
|
354
|
-
const italicButton = controller.itemTargets[1]
|
|
355
|
-
expect(italicButton.getAttribute("data-state")).toBe("on")
|
|
356
|
-
expect(italicButton.getAttribute("aria-pressed")).toBe("true")
|
|
357
|
-
})
|
|
358
|
-
|
|
359
|
-
test("initializes with pre-selected values in multiple mode", async () => {
|
|
360
|
-
const html = `
|
|
361
|
-
<div data-controller="shadcn--toggle-group"
|
|
362
|
-
data-shadcn--toggle-group-type-value="multiple"
|
|
363
|
-
data-shadcn--toggle-group-value-value="bold,underline">
|
|
364
|
-
<button data-shadcn--toggle-group-target="item" data-value="bold">Bold</button>
|
|
365
|
-
<button data-shadcn--toggle-group-target="item" data-value="italic">Italic</button>
|
|
366
|
-
<button data-shadcn--toggle-group-target="item" data-value="underline">Underline</button>
|
|
367
|
-
</div>
|
|
368
|
-
`
|
|
369
|
-
|
|
370
|
-
const setup = await setupController(ToggleGroupController, html, 'shadcn--toggle-group')
|
|
371
|
-
application = setup.application
|
|
372
|
-
element = setup.element
|
|
373
|
-
controller = setup.controller
|
|
374
|
-
|
|
375
|
-
expect(controller.valueValue).toBe("bold,underline")
|
|
376
|
-
expect(controller.getValues()).toEqual(["bold", "underline"])
|
|
377
|
-
|
|
378
|
-
const boldButton = controller.itemTargets[0]
|
|
379
|
-
const italicButton = controller.itemTargets[1]
|
|
380
|
-
const underlineButton = controller.itemTargets[2]
|
|
381
|
-
|
|
382
|
-
expect(boldButton.getAttribute("data-state")).toBe("on")
|
|
383
|
-
expect(italicButton.getAttribute("data-state")).toBe("off")
|
|
384
|
-
expect(underlineButton.getAttribute("data-state")).toBe("on")
|
|
385
|
-
})
|
|
386
|
-
})
|
|
387
|
-
|
|
388
|
-
describe("hidden input synchronization", () => {
|
|
389
|
-
test("updates hidden input value in single mode", async () => {
|
|
390
|
-
const html = `
|
|
391
|
-
<div data-controller="shadcn--toggle-group"
|
|
392
|
-
data-shadcn--toggle-group-type-value="single">
|
|
393
|
-
<button data-shadcn--toggle-group-target="item" data-value="bold"
|
|
394
|
-
data-action="click->shadcn--toggle-group#toggle">Bold</button>
|
|
395
|
-
<input type="hidden" data-shadcn--toggle-group-target="input" name="format">
|
|
396
|
-
</div>
|
|
397
|
-
`
|
|
398
|
-
|
|
399
|
-
const setup = await setupController(ToggleGroupController, html, 'shadcn--toggle-group')
|
|
400
|
-
application = setup.application
|
|
401
|
-
element = setup.element
|
|
402
|
-
controller = setup.controller
|
|
403
|
-
|
|
404
|
-
const input = element.querySelector('input[type="hidden"]')
|
|
405
|
-
expect(input.value).toBe("")
|
|
406
|
-
|
|
407
|
-
const boldButton = controller.itemTargets[0]
|
|
408
|
-
click(boldButton)
|
|
409
|
-
await nextFrame()
|
|
410
|
-
|
|
411
|
-
expect(input.value).toBe("bold")
|
|
412
|
-
})
|
|
413
|
-
|
|
414
|
-
test("updates hidden input value in multiple mode", async () => {
|
|
415
|
-
const html = `
|
|
416
|
-
<div data-controller="shadcn--toggle-group"
|
|
417
|
-
data-shadcn--toggle-group-type-value="multiple">
|
|
418
|
-
<button data-shadcn--toggle-group-target="item" data-value="bold"
|
|
419
|
-
data-action="click->shadcn--toggle-group#toggle">Bold</button>
|
|
420
|
-
<button data-shadcn--toggle-group-target="item" data-value="italic"
|
|
421
|
-
data-action="click->shadcn--toggle-group#toggle">Italic</button>
|
|
422
|
-
<input type="hidden" data-shadcn--toggle-group-target="input" name="formats">
|
|
423
|
-
</div>
|
|
424
|
-
`
|
|
425
|
-
|
|
426
|
-
const setup = await setupController(ToggleGroupController, html, 'shadcn--toggle-group')
|
|
427
|
-
application = setup.application
|
|
428
|
-
element = setup.element
|
|
429
|
-
controller = setup.controller
|
|
430
|
-
|
|
431
|
-
const input = element.querySelector('input[type="hidden"]')
|
|
432
|
-
const boldButton = controller.itemTargets[0]
|
|
433
|
-
const italicButton = controller.itemTargets[1]
|
|
434
|
-
|
|
435
|
-
click(boldButton)
|
|
436
|
-
await nextFrame()
|
|
437
|
-
expect(input.value).toBe("bold")
|
|
438
|
-
|
|
439
|
-
click(italicButton)
|
|
440
|
-
await nextFrame()
|
|
441
|
-
expect(input.value).toBe("bold,italic")
|
|
442
|
-
})
|
|
443
|
-
|
|
444
|
-
test("initializes hidden input with pre-selected value", async () => {
|
|
445
|
-
const html = `
|
|
446
|
-
<div data-controller="shadcn--toggle-group"
|
|
447
|
-
data-shadcn--toggle-group-type-value="single"
|
|
448
|
-
data-shadcn--toggle-group-value-value="bold">
|
|
449
|
-
<button data-shadcn--toggle-group-target="item" data-value="bold">Bold</button>
|
|
450
|
-
<input type="hidden" data-shadcn--toggle-group-target="input">
|
|
451
|
-
</div>
|
|
452
|
-
`
|
|
453
|
-
|
|
454
|
-
const setup = await setupController(ToggleGroupController, html, 'shadcn--toggle-group')
|
|
455
|
-
application = setup.application
|
|
456
|
-
element = setup.element
|
|
457
|
-
controller = setup.controller
|
|
458
|
-
|
|
459
|
-
const input = element.querySelector('input[type="hidden"]')
|
|
460
|
-
expect(input.value).toBe("bold")
|
|
461
|
-
})
|
|
462
|
-
})
|
|
463
|
-
|
|
464
|
-
describe("valueValueChanged callback", () => {
|
|
465
|
-
const html = `
|
|
466
|
-
<div data-controller="shadcn--toggle-group"
|
|
467
|
-
data-shadcn--toggle-group-type-value="single">
|
|
468
|
-
<button data-shadcn--toggle-group-target="item" data-value="bold">Bold</button>
|
|
469
|
-
<button data-shadcn--toggle-group-target="item" data-value="italic">Italic</button>
|
|
470
|
-
</div>
|
|
471
|
-
`
|
|
472
|
-
|
|
473
|
-
beforeEach(async () => {
|
|
474
|
-
const setup = await setupController(ToggleGroupController, html, 'shadcn--toggle-group')
|
|
475
|
-
application = setup.application
|
|
476
|
-
element = setup.element
|
|
477
|
-
controller = setup.controller
|
|
478
|
-
})
|
|
479
|
-
|
|
480
|
-
test("updates item states when value changes programmatically", async () => {
|
|
481
|
-
controller.valueValue = "bold"
|
|
482
|
-
await nextFrame()
|
|
483
|
-
|
|
484
|
-
const boldButton = controller.itemTargets[0]
|
|
485
|
-
expect(boldButton.getAttribute("data-state")).toBe("on")
|
|
486
|
-
expect(boldButton.getAttribute("aria-pressed")).toBe("true")
|
|
487
|
-
})
|
|
488
|
-
|
|
489
|
-
test("updates states when value changes from external source", async () => {
|
|
490
|
-
// Simulate external value change
|
|
491
|
-
controller.valueValue = "italic"
|
|
492
|
-
await nextFrame()
|
|
493
|
-
|
|
494
|
-
const italicButton = controller.itemTargets[1]
|
|
495
|
-
expect(italicButton.getAttribute("data-state")).toBe("on")
|
|
496
|
-
|
|
497
|
-
const boldButton = controller.itemTargets[0]
|
|
498
|
-
expect(boldButton.getAttribute("data-state")).toBe("off")
|
|
499
|
-
})
|
|
500
|
-
})
|
|
501
|
-
|
|
502
|
-
describe("edge cases and error handling", () => {
|
|
503
|
-
const html = `
|
|
504
|
-
<div data-controller="shadcn--toggle-group"
|
|
505
|
-
data-shadcn--toggle-group-type-value="single">
|
|
506
|
-
<button data-shadcn--toggle-group-target="item" data-value="bold">Bold</button>
|
|
507
|
-
<button data-shadcn--toggle-group-target="item" data-value="italic">Italic</button>
|
|
508
|
-
</div>
|
|
509
|
-
`
|
|
510
|
-
|
|
511
|
-
beforeEach(async () => {
|
|
512
|
-
const setup = await setupController(ToggleGroupController, html, 'shadcn--toggle-group')
|
|
513
|
-
application = setup.application
|
|
514
|
-
element = setup.element
|
|
515
|
-
controller = setup.controller
|
|
516
|
-
})
|
|
517
|
-
|
|
518
|
-
test("handles empty value gracefully", async () => {
|
|
519
|
-
controller.valueValue = ""
|
|
520
|
-
await nextFrame()
|
|
521
|
-
|
|
522
|
-
expect(controller.getValues()).toEqual([])
|
|
523
|
-
controller.itemTargets.forEach(item => {
|
|
524
|
-
expect(item.getAttribute("data-state")).toBe("off")
|
|
525
|
-
})
|
|
526
|
-
})
|
|
527
|
-
|
|
528
|
-
test("handles value with only commas", async () => {
|
|
529
|
-
controller.valueValue = ",,,"
|
|
530
|
-
await nextFrame()
|
|
531
|
-
|
|
532
|
-
// Should filter out empty strings
|
|
533
|
-
expect(controller.getValues()).toEqual([])
|
|
534
|
-
})
|
|
535
|
-
|
|
536
|
-
test("handles value with trailing comma", async () => {
|
|
537
|
-
controller.valueValue = "bold,"
|
|
538
|
-
await nextFrame()
|
|
539
|
-
|
|
540
|
-
expect(controller.getValues()).toEqual(["bold"])
|
|
541
|
-
const boldButton = controller.itemTargets[0]
|
|
542
|
-
expect(boldButton.getAttribute("data-state")).toBe("on")
|
|
543
|
-
})
|
|
544
|
-
|
|
545
|
-
test("handles value with leading comma", async () => {
|
|
546
|
-
controller.valueValue = ",bold"
|
|
547
|
-
await nextFrame()
|
|
548
|
-
|
|
549
|
-
expect(controller.getValues()).toEqual(["bold"])
|
|
550
|
-
const boldButton = controller.itemTargets[0]
|
|
551
|
-
expect(boldButton.getAttribute("data-state")).toBe("on")
|
|
552
|
-
})
|
|
553
|
-
|
|
554
|
-
test("handles non-existent value gracefully", async () => {
|
|
555
|
-
controller.valueValue = "strikethrough"
|
|
556
|
-
await nextFrame()
|
|
557
|
-
|
|
558
|
-
// Should not error, just no items will be marked as on
|
|
559
|
-
controller.itemTargets.forEach(item => {
|
|
560
|
-
expect(item.getAttribute("data-state")).toBe("off")
|
|
561
|
-
})
|
|
562
|
-
})
|
|
563
|
-
|
|
564
|
-
test("handles multiple commas between values", async () => {
|
|
565
|
-
controller.typeValue = "multiple"
|
|
566
|
-
controller.valueValue = "bold,,,italic"
|
|
567
|
-
await nextFrame()
|
|
568
|
-
|
|
569
|
-
expect(controller.getValues()).toEqual(["bold", "italic"])
|
|
570
|
-
})
|
|
571
|
-
})
|
|
572
|
-
|
|
573
|
-
describe("connect lifecycle", () => {
|
|
574
|
-
test("updates states on connect", async () => {
|
|
575
|
-
const html = `
|
|
576
|
-
<div data-controller="shadcn--toggle-group"
|
|
577
|
-
data-shadcn--toggle-group-type-value="single"
|
|
578
|
-
data-shadcn--toggle-group-value-value="bold">
|
|
579
|
-
<button data-shadcn--toggle-group-target="item" data-value="bold">Bold</button>
|
|
580
|
-
<button data-shadcn--toggle-group-target="item" data-value="italic">Italic</button>
|
|
581
|
-
</div>
|
|
582
|
-
`
|
|
583
|
-
|
|
584
|
-
const setup = await setupController(ToggleGroupController, html, 'shadcn--toggle-group')
|
|
585
|
-
application = setup.application
|
|
586
|
-
element = setup.element
|
|
587
|
-
controller = setup.controller
|
|
588
|
-
|
|
589
|
-
// After connect, states should be updated
|
|
590
|
-
const boldButton = controller.itemTargets[0]
|
|
591
|
-
expect(boldButton.getAttribute("data-state")).toBe("on")
|
|
592
|
-
expect(boldButton.getAttribute("aria-pressed")).toBe("true")
|
|
593
|
-
})
|
|
594
|
-
|
|
595
|
-
test("initializes all aria-pressed attributes on connect", async () => {
|
|
596
|
-
const html = `
|
|
597
|
-
<div data-controller="shadcn--toggle-group">
|
|
598
|
-
<button data-shadcn--toggle-group-target="item" data-value="a">A</button>
|
|
599
|
-
<button data-shadcn--toggle-group-target="item" data-value="b">B</button>
|
|
600
|
-
<button data-shadcn--toggle-group-target="item" data-value="c">C</button>
|
|
601
|
-
</div>
|
|
602
|
-
`
|
|
603
|
-
|
|
604
|
-
const setup = await setupController(ToggleGroupController, html, 'shadcn--toggle-group')
|
|
605
|
-
application = setup.application
|
|
606
|
-
element = setup.element
|
|
607
|
-
controller = setup.controller
|
|
608
|
-
|
|
609
|
-
controller.itemTargets.forEach(item => {
|
|
610
|
-
expect(item.hasAttribute("aria-pressed")).toBe(true)
|
|
611
|
-
expect(item.hasAttribute("data-state")).toBe(true)
|
|
612
|
-
})
|
|
613
|
-
})
|
|
614
|
-
})
|
|
615
|
-
|
|
616
|
-
describe("type safety and filtering", () => {
|
|
617
|
-
const html = `
|
|
618
|
-
<div data-controller="shadcn--toggle-group"
|
|
619
|
-
data-shadcn--toggle-group-type-value="multiple">
|
|
620
|
-
<button data-shadcn--toggle-group-target="item" data-value="a">A</button>
|
|
621
|
-
<button data-shadcn--toggle-group-target="item" data-value="b">B</button>
|
|
622
|
-
</div>
|
|
623
|
-
`
|
|
624
|
-
|
|
625
|
-
beforeEach(async () => {
|
|
626
|
-
const setup = await setupController(ToggleGroupController, html, 'shadcn--toggle-group')
|
|
627
|
-
application = setup.application
|
|
628
|
-
element = setup.element
|
|
629
|
-
controller = setup.controller
|
|
630
|
-
})
|
|
631
|
-
|
|
632
|
-
test("getValues filters out empty strings", () => {
|
|
633
|
-
controller.valueValue = "a,,b,,"
|
|
634
|
-
expect(controller.getValues()).toEqual(["a", "b"])
|
|
635
|
-
})
|
|
636
|
-
|
|
637
|
-
test("getValues handles whitespace-only values", () => {
|
|
638
|
-
controller.valueValue = "a, ,b"
|
|
639
|
-
// Note: This will include the space as a value
|
|
640
|
-
// If trimming is desired, the controller would need to implement it
|
|
641
|
-
const values = controller.getValues()
|
|
642
|
-
expect(values.length).toBe(3)
|
|
643
|
-
expect(values).toEqual(["a", " ", "b"])
|
|
644
|
-
})
|
|
645
|
-
})
|
|
646
|
-
|
|
647
|
-
describe("rapid toggling", () => {
|
|
648
|
-
const html = `
|
|
649
|
-
<div data-controller="shadcn--toggle-group"
|
|
650
|
-
data-shadcn--toggle-group-type-value="single">
|
|
651
|
-
<button data-shadcn--toggle-group-target="item" data-value="a"
|
|
652
|
-
data-action="click->shadcn--toggle-group#toggle">A</button>
|
|
653
|
-
<button data-shadcn--toggle-group-target="item" data-value="b"
|
|
654
|
-
data-action="click->shadcn--toggle-group#toggle">B</button>
|
|
655
|
-
</div>
|
|
656
|
-
`
|
|
657
|
-
|
|
658
|
-
beforeEach(async () => {
|
|
659
|
-
const setup = await setupController(ToggleGroupController, html, 'shadcn--toggle-group')
|
|
660
|
-
application = setup.application
|
|
661
|
-
element = setup.element
|
|
662
|
-
controller = setup.controller
|
|
663
|
-
})
|
|
664
|
-
|
|
665
|
-
test("handles rapid clicks correctly", async () => {
|
|
666
|
-
const buttonA = controller.itemTargets[0]
|
|
667
|
-
const buttonB = controller.itemTargets[1]
|
|
668
|
-
|
|
669
|
-
// Rapid fire clicks
|
|
670
|
-
click(buttonA)
|
|
671
|
-
click(buttonB)
|
|
672
|
-
click(buttonA)
|
|
673
|
-
await nextFrame()
|
|
674
|
-
|
|
675
|
-
// Final state should be 'a'
|
|
676
|
-
expect(controller.valueValue).toBe("a")
|
|
677
|
-
expect(buttonA.getAttribute("data-state")).toBe("on")
|
|
678
|
-
expect(buttonB.getAttribute("data-state")).toBe("off")
|
|
679
|
-
})
|
|
680
|
-
|
|
681
|
-
test("handles rapid toggle on/off in single mode", async () => {
|
|
682
|
-
const buttonA = controller.itemTargets[0]
|
|
683
|
-
|
|
684
|
-
click(buttonA) // on
|
|
685
|
-
click(buttonA) // off
|
|
686
|
-
click(buttonA) // on
|
|
687
|
-
click(buttonA) // off
|
|
688
|
-
await nextFrame()
|
|
689
|
-
|
|
690
|
-
expect(controller.valueValue).toBe("")
|
|
691
|
-
expect(buttonA.getAttribute("data-state")).toBe("off")
|
|
692
|
-
})
|
|
693
|
-
|
|
694
|
-
test("handles rapid multiple selections in multiple mode", async () => {
|
|
695
|
-
controller.typeValue = "multiple"
|
|
696
|
-
const buttonA = controller.itemTargets[0]
|
|
697
|
-
const buttonB = controller.itemTargets[1]
|
|
698
|
-
|
|
699
|
-
click(buttonA)
|
|
700
|
-
click(buttonB)
|
|
701
|
-
click(buttonA) // deselect
|
|
702
|
-
click(buttonA) // reselect
|
|
703
|
-
await nextFrame()
|
|
704
|
-
|
|
705
|
-
expect(controller.valueValue).toBe("b,a")
|
|
706
|
-
})
|
|
707
|
-
})
|
|
708
|
-
|
|
709
|
-
describe("event detail validation", () => {
|
|
710
|
-
const html = `
|
|
711
|
-
<div data-controller="shadcn--toggle-group"
|
|
712
|
-
data-shadcn--toggle-group-type-value="multiple">
|
|
713
|
-
<button data-shadcn--toggle-group-target="item" data-value="x"
|
|
714
|
-
data-action="click->shadcn--toggle-group#toggle">X</button>
|
|
715
|
-
<button data-shadcn--toggle-group-target="item" data-value="y"
|
|
716
|
-
data-action="click->shadcn--toggle-group#toggle">Y</button>
|
|
717
|
-
</div>
|
|
718
|
-
`
|
|
719
|
-
|
|
720
|
-
beforeEach(async () => {
|
|
721
|
-
const setup = await setupController(ToggleGroupController, html, 'shadcn--toggle-group')
|
|
722
|
-
application = setup.application
|
|
723
|
-
element = setup.element
|
|
724
|
-
controller = setup.controller
|
|
725
|
-
})
|
|
726
|
-
|
|
727
|
-
test("change event detail contains array of values", async () => {
|
|
728
|
-
let receivedDetail = null
|
|
729
|
-
element.addEventListener("shadcn--toggle-group:change", (e) => {
|
|
730
|
-
receivedDetail = e.detail
|
|
731
|
-
})
|
|
732
|
-
|
|
733
|
-
const buttonX = controller.itemTargets[0]
|
|
734
|
-
click(buttonX)
|
|
735
|
-
await nextFrame()
|
|
736
|
-
|
|
737
|
-
expect(receivedDetail).toBeTruthy()
|
|
738
|
-
expect(receivedDetail.value).toBeInstanceOf(Array)
|
|
739
|
-
expect(receivedDetail.value).toEqual(["x"])
|
|
740
|
-
})
|
|
741
|
-
|
|
742
|
-
test("change event bubbles", async () => {
|
|
743
|
-
let eventCaught = false
|
|
744
|
-
document.body.addEventListener("shadcn--toggle-group:change", () => {
|
|
745
|
-
eventCaught = true
|
|
746
|
-
})
|
|
747
|
-
|
|
748
|
-
const buttonX = controller.itemTargets[0]
|
|
749
|
-
click(buttonX)
|
|
750
|
-
await nextFrame()
|
|
751
|
-
|
|
752
|
-
expect(eventCaught).toBe(true)
|
|
753
|
-
})
|
|
754
|
-
})
|
|
755
|
-
|
|
756
|
-
describe("type switching (edge case)", () => {
|
|
757
|
-
const html = `
|
|
758
|
-
<div data-controller="shadcn--toggle-group"
|
|
759
|
-
data-shadcn--toggle-group-type-value="single"
|
|
760
|
-
data-shadcn--toggle-group-value-value="a,b">
|
|
761
|
-
<button data-shadcn--toggle-group-target="item" data-value="a">A</button>
|
|
762
|
-
<button data-shadcn--toggle-group-target="item" data-value="b">B</button>
|
|
763
|
-
</div>
|
|
764
|
-
`
|
|
765
|
-
|
|
766
|
-
beforeEach(async () => {
|
|
767
|
-
const setup = await setupController(ToggleGroupController, html, 'shadcn--toggle-group')
|
|
768
|
-
application = setup.application
|
|
769
|
-
element = setup.element
|
|
770
|
-
controller = setup.controller
|
|
771
|
-
})
|
|
772
|
-
|
|
773
|
-
test("handles multiple values in single mode gracefully", async () => {
|
|
774
|
-
// Even though we're in single mode with multiple values, it should parse them
|
|
775
|
-
const values = controller.getValues()
|
|
776
|
-
expect(values).toEqual(["a", "b"])
|
|
777
|
-
|
|
778
|
-
// Both should be marked as on (edge case behavior)
|
|
779
|
-
const buttonA = controller.itemTargets[0]
|
|
780
|
-
const buttonB = controller.itemTargets[1]
|
|
781
|
-
expect(buttonA.getAttribute("data-state")).toBe("on")
|
|
782
|
-
expect(buttonB.getAttribute("data-state")).toBe("on")
|
|
783
|
-
})
|
|
784
|
-
|
|
785
|
-
test("clicking selected item in single mode with multiple values clears all", async () => {
|
|
786
|
-
// Start with multiple values selected (edge case - shouldn't happen in normal use)
|
|
787
|
-
controller.valueValue = "a,b"
|
|
788
|
-
await nextFrame()
|
|
789
|
-
|
|
790
|
-
// Manually call toggle like a click would
|
|
791
|
-
const buttonA = controller.itemTargets[0]
|
|
792
|
-
controller.toggle({ currentTarget: buttonA })
|
|
793
|
-
await nextFrame()
|
|
794
|
-
|
|
795
|
-
// Since currentValues ["a", "b"] includes "a", it sets to empty string
|
|
796
|
-
expect(controller.valueValue).toBe("")
|
|
797
|
-
})
|
|
798
|
-
})
|
|
799
|
-
|
|
800
|
-
describe("item without data-value attribute", () => {
|
|
801
|
-
const html = `
|
|
802
|
-
<div data-controller="shadcn--toggle-group"
|
|
803
|
-
data-shadcn--toggle-group-type-value="single">
|
|
804
|
-
<button data-shadcn--toggle-group-target="item" data-value="a"
|
|
805
|
-
data-action="click->shadcn--toggle-group#toggle">A</button>
|
|
806
|
-
<button data-shadcn--toggle-group-target="item"
|
|
807
|
-
data-action="click->shadcn--toggle-group#toggle">No Value</button>
|
|
808
|
-
</div>
|
|
809
|
-
`
|
|
810
|
-
|
|
811
|
-
beforeEach(async () => {
|
|
812
|
-
const setup = await setupController(ToggleGroupController, html, 'shadcn--toggle-group')
|
|
813
|
-
application = setup.application
|
|
814
|
-
element = setup.element
|
|
815
|
-
controller = setup.controller
|
|
816
|
-
})
|
|
817
|
-
|
|
818
|
-
test("handles item without data-value gracefully", async () => {
|
|
819
|
-
const noValueButton = controller.itemTargets[1]
|
|
820
|
-
|
|
821
|
-
// Should not error when clicking
|
|
822
|
-
expect(() => {
|
|
823
|
-
click(noValueButton)
|
|
824
|
-
}).not.toThrow()
|
|
825
|
-
})
|
|
826
|
-
|
|
827
|
-
test("treats undefined value as empty string or undefined", async () => {
|
|
828
|
-
const noValueButton = controller.itemTargets[1]
|
|
829
|
-
click(noValueButton)
|
|
830
|
-
await nextFrame()
|
|
831
|
-
|
|
832
|
-
// Behavior depends on implementation - typically would add undefined
|
|
833
|
-
// The filter(Boolean) in the controller would filter it out
|
|
834
|
-
const values = controller.getValues()
|
|
835
|
-
// undefined gets converted to string "undefined" or filtered
|
|
836
|
-
expect(values.length).toBeLessThanOrEqual(1)
|
|
837
|
-
})
|
|
838
|
-
})
|
|
839
|
-
})
|