shadcn-rails 0.2.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 +66 -2
- data/README.md +21 -8
- data/__mocks__/@floating-ui/dom.js +67 -0
- data/app/assets/javascripts/shadcn/controllers/combobox_controller.js +23 -2
- data/app/assets/javascripts/shadcn/controllers/context_menu_controller.js +4 -31
- data/app/assets/javascripts/shadcn/controllers/dropdown_controller.js +32 -41
- data/app/assets/javascripts/shadcn/controllers/hover_card_controller.js +29 -55
- data/app/assets/javascripts/shadcn/controllers/popover_controller.js +29 -54
- data/app/assets/javascripts/shadcn/controllers/select_controller.js +26 -8
- data/app/assets/javascripts/shadcn/controllers/tooltip_controller.js +28 -59
- data/app/assets/javascripts/shadcn/index.js +7 -1
- data/app/assets/javascripts/shadcn/utils/floating.js +179 -0
- data/app/assets/stylesheets/shadcn/base.css +32 -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/context_menu_component.html.erb +11 -0
- data/app/components/shadcn/context_menu_component.rb +6 -26
- 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_component.html.erb +14 -0
- data/app/components/shadcn/dropdown_menu_component.rb +9 -29
- 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/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/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 +6 -80
- 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 +47 -45
- data/.dockerignore +0 -40
- data/CLAUDE.md +0 -612
- data/PROGRESS.md +0 -495
- data/Rakefile +0 -95
- 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 -971
- data/__tests__/controllers/context_menu_controller.test.js +0 -905
- 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 -737
- data/__tests__/controllers/navigation_menu_controller.test.js +0 -599
- data/__tests__/controllers/popover_controller.test.js +0 -982
- 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 -678
- 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/bump +0 -321
- data/bin/console +0 -11
- data/bin/release +0 -205
- data/bin/setup +0 -8
- data/bin/test +0 -75
- 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 -7438
- data/package.json +0 -71
- data/rollup.config.js +0 -29
|
@@ -1,636 +0,0 @@
|
|
|
1
|
-
import { Application } from "@hotwired/stimulus"
|
|
2
|
-
import DatePickerController from "../../app/assets/javascripts/shadcn/controllers/date_picker_controller.js"
|
|
3
|
-
|
|
4
|
-
describe("DatePickerController", () => {
|
|
5
|
-
let application
|
|
6
|
-
let element
|
|
7
|
-
let controller
|
|
8
|
-
|
|
9
|
-
const datePickerHTML = `
|
|
10
|
-
<div data-controller="date-picker"
|
|
11
|
-
data-date-picker-open-value="false"
|
|
12
|
-
data-date-picker-month-value="2024-11-01"
|
|
13
|
-
data-date-picker-selected-value=""
|
|
14
|
-
data-date-picker-format-value="medium"
|
|
15
|
-
data-date-picker-placeholder-value="Pick a date">
|
|
16
|
-
<button data-date-picker-target="trigger" type="button">
|
|
17
|
-
<span data-date-picker-target="displayValue" class="text-muted-foreground">Pick a date</span>
|
|
18
|
-
</button>
|
|
19
|
-
<div data-date-picker-target="content" style="display: none;">
|
|
20
|
-
<div data-date-picker-target="monthYear"></div>
|
|
21
|
-
<div data-date-picker-target="grid"></div>
|
|
22
|
-
</div>
|
|
23
|
-
<input type="hidden" data-date-picker-target="hiddenInput">
|
|
24
|
-
</div>
|
|
25
|
-
`
|
|
26
|
-
|
|
27
|
-
beforeEach(async () => {
|
|
28
|
-
application = Application.start()
|
|
29
|
-
application.register("date-picker", DatePickerController)
|
|
30
|
-
document.body.innerHTML = datePickerHTML
|
|
31
|
-
|
|
32
|
-
await new Promise(resolve => requestAnimationFrame(resolve))
|
|
33
|
-
|
|
34
|
-
element = document.querySelector('[data-controller="date-picker"]')
|
|
35
|
-
controller = application.getControllerForElementAndIdentifier(element, "date-picker")
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
afterEach(() => {
|
|
39
|
-
if (application) {
|
|
40
|
-
application.stop()
|
|
41
|
-
}
|
|
42
|
-
document.body.innerHTML = ""
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
describe("parseLocalDate", () => {
|
|
46
|
-
test("parses date string as local date, not UTC", () => {
|
|
47
|
-
const date = controller.parseLocalDate("2024-11-26")
|
|
48
|
-
|
|
49
|
-
expect(date.getFullYear()).toBe(2024)
|
|
50
|
-
expect(date.getMonth()).toBe(10) // November is month 10 (0-indexed)
|
|
51
|
-
expect(date.getDate()).toBe(26)
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
test("returns null for empty string", () => {
|
|
55
|
-
expect(controller.parseLocalDate("")).toBeNull()
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
test("returns null for null input", () => {
|
|
59
|
-
expect(controller.parseLocalDate(null)).toBeNull()
|
|
60
|
-
})
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
describe("formatDateString", () => {
|
|
64
|
-
test("formats date as YYYY-MM-DD", () => {
|
|
65
|
-
const date = new Date(2024, 10, 26)
|
|
66
|
-
expect(controller.formatDateString(date)).toBe("2024-11-26")
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
test("pads single digit months and days", () => {
|
|
70
|
-
const date = new Date(2024, 0, 5)
|
|
71
|
-
expect(controller.formatDateString(date)).toBe("2024-01-05")
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
test("returns empty string for null", () => {
|
|
75
|
-
expect(controller.formatDateString(null)).toBe("")
|
|
76
|
-
})
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
describe("formatDate", () => {
|
|
80
|
-
test("formats with medium style by default", () => {
|
|
81
|
-
const date = new Date(2024, 10, 26)
|
|
82
|
-
const formatted = controller.formatDate(date)
|
|
83
|
-
// "November 26, 2024"
|
|
84
|
-
expect(formatted).toContain("November")
|
|
85
|
-
expect(formatted).toContain("26")
|
|
86
|
-
expect(formatted).toContain("2024")
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
test("formats with short style", () => {
|
|
90
|
-
controller.formatValue = "short"
|
|
91
|
-
const date = new Date(2024, 10, 26)
|
|
92
|
-
const formatted = controller.formatDate(date)
|
|
93
|
-
// "11/26/2024"
|
|
94
|
-
expect(formatted).toBe("11/26/2024")
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
test("formats with long style", () => {
|
|
98
|
-
controller.formatValue = "long"
|
|
99
|
-
const date = new Date(2024, 10, 26)
|
|
100
|
-
const formatted = controller.formatDate(date)
|
|
101
|
-
// "Tuesday, November 26, 2024"
|
|
102
|
-
expect(formatted).toContain("Tuesday")
|
|
103
|
-
expect(formatted).toContain("November")
|
|
104
|
-
expect(formatted).toContain("26")
|
|
105
|
-
expect(formatted).toContain("2024")
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
test("formats with iso style", () => {
|
|
109
|
-
controller.formatValue = "iso"
|
|
110
|
-
const date = new Date(2024, 10, 26)
|
|
111
|
-
const formatted = controller.formatDate(date)
|
|
112
|
-
expect(formatted).toBe("2024-11-26")
|
|
113
|
-
})
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
describe("connect", () => {
|
|
117
|
-
test("initializes currentMonth from monthValue", () => {
|
|
118
|
-
expect(controller.currentMonth.getFullYear()).toBe(2024)
|
|
119
|
-
expect(controller.currentMonth.getMonth()).toBe(10) // November
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
test("initializes selectedDate as null when no selectedValue", () => {
|
|
123
|
-
expect(controller.selectedDate).toBeNull()
|
|
124
|
-
})
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
describe("toggle", () => {
|
|
128
|
-
test("opens when closed", () => {
|
|
129
|
-
expect(controller.openValue).toBe(false)
|
|
130
|
-
controller.toggle()
|
|
131
|
-
expect(controller.openValue).toBe(true)
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
test("closes when open", () => {
|
|
135
|
-
controller.openValue = true
|
|
136
|
-
controller.toggle()
|
|
137
|
-
expect(controller.openValue).toBe(false)
|
|
138
|
-
})
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
describe("open", () => {
|
|
142
|
-
test("sets openValue to true", () => {
|
|
143
|
-
controller.open()
|
|
144
|
-
expect(controller.openValue).toBe(true)
|
|
145
|
-
})
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
describe("close", () => {
|
|
149
|
-
test("sets openValue to false", () => {
|
|
150
|
-
controller.openValue = true
|
|
151
|
-
controller.close()
|
|
152
|
-
expect(controller.openValue).toBe(false)
|
|
153
|
-
})
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
describe("openValueChanged", () => {
|
|
157
|
-
test("shows content when open", () => {
|
|
158
|
-
controller.openValue = true
|
|
159
|
-
controller.openValueChanged()
|
|
160
|
-
|
|
161
|
-
const content = element.querySelector('[data-date-picker-target="content"]')
|
|
162
|
-
expect(content.style.display).toBe("block")
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
test("hides content when closed", () => {
|
|
166
|
-
controller.openValue = false
|
|
167
|
-
controller.openValueChanged()
|
|
168
|
-
|
|
169
|
-
const content = element.querySelector('[data-date-picker-target="content"]')
|
|
170
|
-
expect(content.style.display).toBe("none")
|
|
171
|
-
})
|
|
172
|
-
|
|
173
|
-
test("sets aria-expanded on trigger", () => {
|
|
174
|
-
controller.openValue = true
|
|
175
|
-
controller.openValueChanged()
|
|
176
|
-
|
|
177
|
-
const trigger = element.querySelector('[data-date-picker-target="trigger"]')
|
|
178
|
-
expect(trigger.getAttribute("aria-expanded")).toBe("true")
|
|
179
|
-
|
|
180
|
-
controller.openValue = false
|
|
181
|
-
controller.openValueChanged()
|
|
182
|
-
expect(trigger.getAttribute("aria-expanded")).toBe("false")
|
|
183
|
-
})
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
describe("previousMonth", () => {
|
|
187
|
-
test("moves to the previous month", () => {
|
|
188
|
-
controller.previousMonth()
|
|
189
|
-
expect(controller.currentMonth.getMonth()).toBe(9) // October
|
|
190
|
-
})
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
describe("nextMonth", () => {
|
|
194
|
-
test("moves to the next month", () => {
|
|
195
|
-
controller.nextMonth()
|
|
196
|
-
expect(controller.currentMonth.getMonth()).toBe(11) // December
|
|
197
|
-
})
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
describe("selectDay", () => {
|
|
201
|
-
test("selects the clicked date", () => {
|
|
202
|
-
const mockEvent = {
|
|
203
|
-
currentTarget: {
|
|
204
|
-
dataset: { date: "2024-11-15" }
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
controller.selectDay(mockEvent)
|
|
209
|
-
|
|
210
|
-
expect(controller.selectedDate).not.toBeNull()
|
|
211
|
-
expect(controller.selectedDate.getDate()).toBe(15)
|
|
212
|
-
expect(controller.selectedDate.getMonth()).toBe(10)
|
|
213
|
-
expect(controller.selectedDate.getFullYear()).toBe(2024)
|
|
214
|
-
})
|
|
215
|
-
|
|
216
|
-
test("updates the hidden input value", () => {
|
|
217
|
-
const mockEvent = {
|
|
218
|
-
currentTarget: {
|
|
219
|
-
dataset: { date: "2024-11-20" }
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
controller.selectDay(mockEvent)
|
|
224
|
-
|
|
225
|
-
const hiddenInput = element.querySelector('[data-date-picker-target="hiddenInput"]')
|
|
226
|
-
expect(hiddenInput.value).toBe("2024-11-20")
|
|
227
|
-
})
|
|
228
|
-
|
|
229
|
-
test("updates the display value", () => {
|
|
230
|
-
const mockEvent = {
|
|
231
|
-
currentTarget: {
|
|
232
|
-
dataset: { date: "2024-11-15" }
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
controller.selectDay(mockEvent)
|
|
237
|
-
|
|
238
|
-
const displayValue = element.querySelector('[data-date-picker-target="displayValue"]')
|
|
239
|
-
expect(displayValue.textContent).toContain("November")
|
|
240
|
-
expect(displayValue.textContent).toContain("15")
|
|
241
|
-
expect(displayValue.classList.contains("text-muted-foreground")).toBe(false)
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
test("closes the popover after selection", () => {
|
|
245
|
-
controller.openValue = true
|
|
246
|
-
|
|
247
|
-
const mockEvent = {
|
|
248
|
-
currentTarget: {
|
|
249
|
-
dataset: { date: "2024-11-15" }
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
controller.selectDay(mockEvent)
|
|
254
|
-
|
|
255
|
-
expect(controller.openValue).toBe(false)
|
|
256
|
-
})
|
|
257
|
-
|
|
258
|
-
test("dispatches select event with date details", () => {
|
|
259
|
-
let eventDetail = null
|
|
260
|
-
element.addEventListener("date-picker:select", (e) => {
|
|
261
|
-
eventDetail = e.detail
|
|
262
|
-
})
|
|
263
|
-
|
|
264
|
-
const mockEvent = {
|
|
265
|
-
currentTarget: {
|
|
266
|
-
dataset: { date: "2024-11-10" }
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
controller.selectDay(mockEvent)
|
|
271
|
-
|
|
272
|
-
expect(eventDetail).not.toBeNull()
|
|
273
|
-
expect(eventDetail.dateString).toBe("2024-11-10")
|
|
274
|
-
expect(eventDetail.date.getDate()).toBe(10)
|
|
275
|
-
})
|
|
276
|
-
|
|
277
|
-
test("does nothing if no date in event", () => {
|
|
278
|
-
const mockEvent = {
|
|
279
|
-
currentTarget: {
|
|
280
|
-
dataset: {}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
controller.selectDay(mockEvent)
|
|
285
|
-
|
|
286
|
-
expect(controller.selectedDate).toBeNull()
|
|
287
|
-
})
|
|
288
|
-
})
|
|
289
|
-
|
|
290
|
-
describe("render", () => {
|
|
291
|
-
test("updates month/year display", () => {
|
|
292
|
-
controller.render()
|
|
293
|
-
|
|
294
|
-
const monthYearDisplay = element.querySelector('[data-date-picker-target="monthYear"]')
|
|
295
|
-
expect(monthYearDisplay.textContent).toBe("November 2024")
|
|
296
|
-
})
|
|
297
|
-
|
|
298
|
-
test("renders day buttons in grid", () => {
|
|
299
|
-
controller.render()
|
|
300
|
-
|
|
301
|
-
const grid = element.querySelector('[data-date-picker-target="grid"]')
|
|
302
|
-
const dayButtons = grid.querySelectorAll('button[data-date]')
|
|
303
|
-
expect(dayButtons.length).toBeGreaterThanOrEqual(28)
|
|
304
|
-
expect(dayButtons.length).toBeLessThanOrEqual(42)
|
|
305
|
-
})
|
|
306
|
-
})
|
|
307
|
-
|
|
308
|
-
describe("closeOnClickOutside", () => {
|
|
309
|
-
test("closes when clicking outside the component", () => {
|
|
310
|
-
controller.openValue = true
|
|
311
|
-
|
|
312
|
-
// Create an outside element
|
|
313
|
-
const outsideElement = document.createElement("div")
|
|
314
|
-
document.body.appendChild(outsideElement)
|
|
315
|
-
|
|
316
|
-
const event = {
|
|
317
|
-
target: outsideElement
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
controller.closeOnClickOutside(event)
|
|
321
|
-
|
|
322
|
-
expect(controller.openValue).toBe(false)
|
|
323
|
-
})
|
|
324
|
-
|
|
325
|
-
test("does not close when clicking inside the component", () => {
|
|
326
|
-
controller.openValue = true
|
|
327
|
-
|
|
328
|
-
const event = {
|
|
329
|
-
target: element.querySelector('[data-date-picker-target="content"]')
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
controller.closeOnClickOutside(event)
|
|
333
|
-
|
|
334
|
-
expect(controller.openValue).toBe(true)
|
|
335
|
-
})
|
|
336
|
-
|
|
337
|
-
test("does nothing when already closed", () => {
|
|
338
|
-
controller.openValue = false
|
|
339
|
-
|
|
340
|
-
const outsideElement = document.createElement("div")
|
|
341
|
-
document.body.appendChild(outsideElement)
|
|
342
|
-
|
|
343
|
-
const event = {
|
|
344
|
-
target: outsideElement
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// Should not throw or change anything
|
|
348
|
-
controller.closeOnClickOutside(event)
|
|
349
|
-
|
|
350
|
-
expect(controller.openValue).toBe(false)
|
|
351
|
-
})
|
|
352
|
-
})
|
|
353
|
-
|
|
354
|
-
describe("timezone handling", () => {
|
|
355
|
-
test("selecting a date preserves the correct day regardless of timezone", () => {
|
|
356
|
-
const mockEvent = {
|
|
357
|
-
currentTarget: {
|
|
358
|
-
dataset: { date: "2024-11-15" }
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
controller.selectDay(mockEvent)
|
|
363
|
-
|
|
364
|
-
expect(controller.selectedDate.getDate()).toBe(15)
|
|
365
|
-
expect(controller.selectedDate.getMonth()).toBe(10)
|
|
366
|
-
expect(controller.selectedValue).toBe("2024-11-15")
|
|
367
|
-
})
|
|
368
|
-
|
|
369
|
-
test("initializing with a selected value preserves the correct day", async () => {
|
|
370
|
-
application.stop()
|
|
371
|
-
document.body.innerHTML = ""
|
|
372
|
-
|
|
373
|
-
document.body.innerHTML = `
|
|
374
|
-
<div data-controller="date-picker"
|
|
375
|
-
data-date-picker-month-value="2024-11-01"
|
|
376
|
-
data-date-picker-selected-value="2024-11-26">
|
|
377
|
-
<div data-date-picker-target="grid"></div>
|
|
378
|
-
</div>
|
|
379
|
-
`
|
|
380
|
-
|
|
381
|
-
application = Application.start()
|
|
382
|
-
application.register("date-picker", DatePickerController)
|
|
383
|
-
|
|
384
|
-
await new Promise(resolve => requestAnimationFrame(resolve))
|
|
385
|
-
|
|
386
|
-
const newElement = document.querySelector('[data-controller="date-picker"]')
|
|
387
|
-
const newController = application.getControllerForElementAndIdentifier(newElement, "date-picker")
|
|
388
|
-
|
|
389
|
-
expect(newController.selectedDate.getDate()).toBe(26)
|
|
390
|
-
expect(newController.selectedDate.getMonth()).toBe(10)
|
|
391
|
-
})
|
|
392
|
-
|
|
393
|
-
test("parseLocalDate avoids UTC timezone shift for DST dates", () => {
|
|
394
|
-
const dstDates = [
|
|
395
|
-
"2024-03-10", // DST start (US)
|
|
396
|
-
"2024-11-03", // DST end (US)
|
|
397
|
-
]
|
|
398
|
-
|
|
399
|
-
dstDates.forEach(dateStr => {
|
|
400
|
-
const [year, month, day] = dateStr.split('-').map(Number)
|
|
401
|
-
const parsed = controller.parseLocalDate(dateStr)
|
|
402
|
-
|
|
403
|
-
expect(parsed.getFullYear()).toBe(year)
|
|
404
|
-
expect(parsed.getMonth()).toBe(month - 1)
|
|
405
|
-
expect(parsed.getDate()).toBe(day)
|
|
406
|
-
})
|
|
407
|
-
})
|
|
408
|
-
})
|
|
409
|
-
|
|
410
|
-
describe("monthValueChanged", () => {
|
|
411
|
-
test("updates currentMonth when value changes", () => {
|
|
412
|
-
controller.monthValue = "2024-06-01"
|
|
413
|
-
controller.monthValueChanged()
|
|
414
|
-
|
|
415
|
-
expect(controller.currentMonth.getMonth()).toBe(5)
|
|
416
|
-
expect(controller.currentMonth.getFullYear()).toBe(2024)
|
|
417
|
-
})
|
|
418
|
-
})
|
|
419
|
-
|
|
420
|
-
describe("selectedValueChanged", () => {
|
|
421
|
-
test("updates selectedDate when value changes", () => {
|
|
422
|
-
controller.selectedValue = "2024-07-20"
|
|
423
|
-
controller.selectedValueChanged()
|
|
424
|
-
|
|
425
|
-
expect(controller.selectedDate.getDate()).toBe(20)
|
|
426
|
-
expect(controller.selectedDate.getMonth()).toBe(6)
|
|
427
|
-
})
|
|
428
|
-
})
|
|
429
|
-
|
|
430
|
-
describe("MONTHS constant", () => {
|
|
431
|
-
test("contains all 12 months in order", () => {
|
|
432
|
-
expect(DatePickerController.MONTHS).toEqual([
|
|
433
|
-
"January", "February", "March", "April", "May", "June",
|
|
434
|
-
"July", "August", "September", "October", "November", "December"
|
|
435
|
-
])
|
|
436
|
-
})
|
|
437
|
-
})
|
|
438
|
-
|
|
439
|
-
describe("isDateDisabled", () => {
|
|
440
|
-
test("returns false for dates within valid range", () => {
|
|
441
|
-
controller.minDateValue = "2024-11-01"
|
|
442
|
-
controller.maxDateValue = "2024-11-30"
|
|
443
|
-
|
|
444
|
-
const date = new Date(2024, 10, 15)
|
|
445
|
-
expect(controller.isDateDisabled(date)).toBe(false)
|
|
446
|
-
})
|
|
447
|
-
|
|
448
|
-
test("returns true for dates before minDate", () => {
|
|
449
|
-
controller.minDateValue = "2024-11-10"
|
|
450
|
-
|
|
451
|
-
const date = new Date(2024, 10, 5)
|
|
452
|
-
expect(controller.isDateDisabled(date)).toBe(true)
|
|
453
|
-
})
|
|
454
|
-
|
|
455
|
-
test("returns true for dates after maxDate", () => {
|
|
456
|
-
controller.maxDateValue = "2024-11-20"
|
|
457
|
-
|
|
458
|
-
const date = new Date(2024, 10, 25)
|
|
459
|
-
expect(controller.isDateDisabled(date)).toBe(true)
|
|
460
|
-
})
|
|
461
|
-
|
|
462
|
-
test("returns true for dates in disabledDates list", () => {
|
|
463
|
-
controller.disabledDatesValue = "2024-11-15,2024-11-16,2024-11-17"
|
|
464
|
-
|
|
465
|
-
expect(controller.isDateDisabled(new Date(2024, 10, 15))).toBe(true)
|
|
466
|
-
expect(controller.isDateDisabled(new Date(2024, 10, 18))).toBe(false)
|
|
467
|
-
})
|
|
468
|
-
|
|
469
|
-
test("returns true for disabled days of week", () => {
|
|
470
|
-
controller.disabledDaysOfWeekValue = "0,6" // Sunday and Saturday
|
|
471
|
-
|
|
472
|
-
// November 16, 2024 is a Saturday
|
|
473
|
-
expect(controller.isDateDisabled(new Date(2024, 10, 16))).toBe(true)
|
|
474
|
-
// November 17, 2024 is a Sunday
|
|
475
|
-
expect(controller.isDateDisabled(new Date(2024, 10, 17))).toBe(true)
|
|
476
|
-
// November 18, 2024 is a Monday
|
|
477
|
-
expect(controller.isDateDisabled(new Date(2024, 10, 18))).toBe(false)
|
|
478
|
-
})
|
|
479
|
-
})
|
|
480
|
-
|
|
481
|
-
describe("disabled dates in DatePicker", () => {
|
|
482
|
-
test("clicking a disabled date does not select it", () => {
|
|
483
|
-
controller.disabledDaysOfWeekValue = "0,6" // Weekends
|
|
484
|
-
|
|
485
|
-
// Try to select a Saturday
|
|
486
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-16" } } })
|
|
487
|
-
|
|
488
|
-
expect(controller.selectedDate).toBeNull()
|
|
489
|
-
})
|
|
490
|
-
|
|
491
|
-
test("disabled dates have correct CSS after render", () => {
|
|
492
|
-
controller.disabledDaysOfWeekValue = "0,6" // Weekends
|
|
493
|
-
controller.render()
|
|
494
|
-
|
|
495
|
-
const grid = element.querySelector('[data-date-picker-target="grid"]')
|
|
496
|
-
const saturdayButton = grid.querySelector('[data-date="2024-11-16"]')
|
|
497
|
-
|
|
498
|
-
expect(saturdayButton.classList.contains("cursor-not-allowed")).toBe(true)
|
|
499
|
-
expect(saturdayButton.hasAttribute("disabled")).toBe(true)
|
|
500
|
-
expect(saturdayButton.getAttribute("aria-disabled")).toBe("true")
|
|
501
|
-
})
|
|
502
|
-
|
|
503
|
-
test("disabled dates persist after selecting a date", () => {
|
|
504
|
-
controller.disabledDaysOfWeekValue = "0,6" // Weekends
|
|
505
|
-
controller.render()
|
|
506
|
-
|
|
507
|
-
// Select a weekday
|
|
508
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-18" } } })
|
|
509
|
-
|
|
510
|
-
// Weekends should still be disabled
|
|
511
|
-
const grid = element.querySelector('[data-date-picker-target="grid"]')
|
|
512
|
-
const saturdayButton = grid.querySelector('[data-date="2024-11-16"]')
|
|
513
|
-
|
|
514
|
-
expect(saturdayButton.hasAttribute("disabled")).toBe(true)
|
|
515
|
-
expect(saturdayButton.classList.contains("cursor-not-allowed")).toBe(true)
|
|
516
|
-
})
|
|
517
|
-
})
|
|
518
|
-
|
|
519
|
-
describe("showOutsideDays in DatePicker", () => {
|
|
520
|
-
test("renders empty placeholders when showOutsideDays is false", async () => {
|
|
521
|
-
application.stop()
|
|
522
|
-
document.body.innerHTML = ""
|
|
523
|
-
|
|
524
|
-
document.body.innerHTML = `
|
|
525
|
-
<div data-controller="date-picker"
|
|
526
|
-
data-date-picker-month-value="2024-11-01"
|
|
527
|
-
data-date-picker-show-outside-days-value="false">
|
|
528
|
-
<div data-date-picker-target="grid"></div>
|
|
529
|
-
</div>
|
|
530
|
-
`
|
|
531
|
-
|
|
532
|
-
application = Application.start()
|
|
533
|
-
application.register("date-picker", DatePickerController)
|
|
534
|
-
|
|
535
|
-
await new Promise(resolve => requestAnimationFrame(resolve))
|
|
536
|
-
|
|
537
|
-
element = document.querySelector('[data-controller="date-picker"]')
|
|
538
|
-
controller = application.getControllerForElementAndIdentifier(element, "date-picker")
|
|
539
|
-
|
|
540
|
-
controller.render()
|
|
541
|
-
|
|
542
|
-
const grid = element.querySelector('[data-date-picker-target="grid"]')
|
|
543
|
-
|
|
544
|
-
// First button should be November 1 (October days replaced with empty divs)
|
|
545
|
-
const firstButton = grid.querySelector('button[data-date]')
|
|
546
|
-
expect(firstButton.dataset.date).toBe("2024-11-01")
|
|
547
|
-
|
|
548
|
-
// Empty divs should exist for October days
|
|
549
|
-
const emptyDivs = grid.querySelectorAll('div.h-8.w-8:not([data-date])')
|
|
550
|
-
expect(emptyDivs.length).toBeGreaterThan(0)
|
|
551
|
-
})
|
|
552
|
-
|
|
553
|
-
test("showOutsideDays persists after month navigation", async () => {
|
|
554
|
-
application.stop()
|
|
555
|
-
document.body.innerHTML = ""
|
|
556
|
-
|
|
557
|
-
document.body.innerHTML = `
|
|
558
|
-
<div data-controller="date-picker"
|
|
559
|
-
data-date-picker-month-value="2024-11-01"
|
|
560
|
-
data-date-picker-show-outside-days-value="false">
|
|
561
|
-
<div data-date-picker-target="grid"></div>
|
|
562
|
-
</div>
|
|
563
|
-
`
|
|
564
|
-
|
|
565
|
-
application = Application.start()
|
|
566
|
-
application.register("date-picker", DatePickerController)
|
|
567
|
-
|
|
568
|
-
await new Promise(resolve => requestAnimationFrame(resolve))
|
|
569
|
-
|
|
570
|
-
element = document.querySelector('[data-controller="date-picker"]')
|
|
571
|
-
controller = application.getControllerForElementAndIdentifier(element, "date-picker")
|
|
572
|
-
|
|
573
|
-
// Navigate to December
|
|
574
|
-
controller.nextMonth()
|
|
575
|
-
|
|
576
|
-
// First button should be December 1
|
|
577
|
-
let grid = element.querySelector('[data-date-picker-target="grid"]')
|
|
578
|
-
let firstButton = grid.querySelector('button[data-date]')
|
|
579
|
-
expect(firstButton.dataset.date).toBe("2024-12-01")
|
|
580
|
-
|
|
581
|
-
// Navigate back
|
|
582
|
-
controller.previousMonth()
|
|
583
|
-
|
|
584
|
-
// First button should still be November 1
|
|
585
|
-
grid = element.querySelector('[data-date-picker-target="grid"]')
|
|
586
|
-
firstButton = grid.querySelector('button[data-date]')
|
|
587
|
-
expect(firstButton.dataset.date).toBe("2024-11-01")
|
|
588
|
-
})
|
|
589
|
-
})
|
|
590
|
-
|
|
591
|
-
describe("month navigation with disabled dates", () => {
|
|
592
|
-
test("disabled days of week persist across month navigation", () => {
|
|
593
|
-
controller.disabledDaysOfWeekValue = "0,6" // Weekends
|
|
594
|
-
controller.render()
|
|
595
|
-
|
|
596
|
-
// Navigate to December
|
|
597
|
-
controller.nextMonth()
|
|
598
|
-
|
|
599
|
-
// December 7 is a Saturday
|
|
600
|
-
const grid = element.querySelector('[data-date-picker-target="grid"]')
|
|
601
|
-
const decSat = grid.querySelector('[data-date="2024-12-07"]')
|
|
602
|
-
expect(decSat.hasAttribute("disabled")).toBe(true)
|
|
603
|
-
|
|
604
|
-
// Navigate back to November
|
|
605
|
-
controller.previousMonth()
|
|
606
|
-
|
|
607
|
-
// November 16 is still Saturday and should be disabled
|
|
608
|
-
const novSat = element.querySelector('[data-date="2024-11-16"]')
|
|
609
|
-
expect(novSat.hasAttribute("disabled")).toBe(true)
|
|
610
|
-
})
|
|
611
|
-
|
|
612
|
-
test("minDate/maxDate constraints persist across navigation", () => {
|
|
613
|
-
controller.minDateValue = "2024-11-10"
|
|
614
|
-
controller.maxDateValue = "2024-12-20"
|
|
615
|
-
controller.render()
|
|
616
|
-
|
|
617
|
-
// November 5 should be disabled (before minDate)
|
|
618
|
-
let date5 = element.querySelector('[data-date="2024-11-05"]')
|
|
619
|
-
expect(date5.hasAttribute("disabled")).toBe(true)
|
|
620
|
-
|
|
621
|
-
// Navigate to December
|
|
622
|
-
controller.nextMonth()
|
|
623
|
-
|
|
624
|
-
// December 25 should be disabled (after maxDate)
|
|
625
|
-
const date25 = element.querySelector('[data-date="2024-12-25"]')
|
|
626
|
-
expect(date25.hasAttribute("disabled")).toBe(true)
|
|
627
|
-
|
|
628
|
-
// Navigate back to November
|
|
629
|
-
controller.previousMonth()
|
|
630
|
-
|
|
631
|
-
// November 5 should still be disabled
|
|
632
|
-
date5 = element.querySelector('[data-date="2024-11-05"]')
|
|
633
|
-
expect(date5.hasAttribute("disabled")).toBe(true)
|
|
634
|
-
})
|
|
635
|
-
})
|
|
636
|
-
})
|