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,1370 +0,0 @@
|
|
|
1
|
-
import { Application } from "@hotwired/stimulus"
|
|
2
|
-
import CalendarController from "../../app/assets/javascripts/shadcn/controllers/calendar_controller.js"
|
|
3
|
-
|
|
4
|
-
describe("CalendarController", () => {
|
|
5
|
-
let application
|
|
6
|
-
let element
|
|
7
|
-
let controller
|
|
8
|
-
|
|
9
|
-
const calendarHTML = `
|
|
10
|
-
<div data-controller="calendar"
|
|
11
|
-
data-calendar-month-value="2024-11-01"
|
|
12
|
-
data-calendar-selected-value="">
|
|
13
|
-
<div data-calendar-target="monthYear"></div>
|
|
14
|
-
<select data-calendar-target="monthSelect"></select>
|
|
15
|
-
<select data-calendar-target="yearSelect"></select>
|
|
16
|
-
<div data-calendar-target="grid"></div>
|
|
17
|
-
<input type="hidden" data-calendar-target="hiddenInput">
|
|
18
|
-
</div>
|
|
19
|
-
`
|
|
20
|
-
|
|
21
|
-
beforeEach(async () => {
|
|
22
|
-
application = Application.start()
|
|
23
|
-
application.register("calendar", CalendarController)
|
|
24
|
-
document.body.innerHTML = calendarHTML
|
|
25
|
-
|
|
26
|
-
await new Promise(resolve => requestAnimationFrame(resolve))
|
|
27
|
-
|
|
28
|
-
element = document.querySelector('[data-controller="calendar"]')
|
|
29
|
-
controller = application.getControllerForElementAndIdentifier(element, "calendar")
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
afterEach(() => {
|
|
33
|
-
if (application) {
|
|
34
|
-
application.stop()
|
|
35
|
-
}
|
|
36
|
-
document.body.innerHTML = ""
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
describe("parseLocalDate", () => {
|
|
40
|
-
test("parses date string as local date, not UTC", () => {
|
|
41
|
-
const date = controller.parseLocalDate("2024-11-26")
|
|
42
|
-
|
|
43
|
-
expect(date.getFullYear()).toBe(2024)
|
|
44
|
-
expect(date.getMonth()).toBe(10) // November is month 10 (0-indexed)
|
|
45
|
-
expect(date.getDate()).toBe(26)
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
test("returns null for empty string", () => {
|
|
49
|
-
expect(controller.parseLocalDate("")).toBeNull()
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
test("returns null for null input", () => {
|
|
53
|
-
expect(controller.parseLocalDate(null)).toBeNull()
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
test("handles first day of month", () => {
|
|
57
|
-
const date = controller.parseLocalDate("2024-01-01")
|
|
58
|
-
|
|
59
|
-
expect(date.getFullYear()).toBe(2024)
|
|
60
|
-
expect(date.getMonth()).toBe(0) // January
|
|
61
|
-
expect(date.getDate()).toBe(1)
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
test("handles last day of month", () => {
|
|
65
|
-
const date = controller.parseLocalDate("2024-12-31")
|
|
66
|
-
|
|
67
|
-
expect(date.getFullYear()).toBe(2024)
|
|
68
|
-
expect(date.getMonth()).toBe(11) // December
|
|
69
|
-
expect(date.getDate()).toBe(31)
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
test("handles leap year February 29", () => {
|
|
73
|
-
const date = controller.parseLocalDate("2024-02-29")
|
|
74
|
-
|
|
75
|
-
expect(date.getFullYear()).toBe(2024)
|
|
76
|
-
expect(date.getMonth()).toBe(1) // February
|
|
77
|
-
expect(date.getDate()).toBe(29)
|
|
78
|
-
})
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
describe("formatDateString", () => {
|
|
82
|
-
test("formats date as YYYY-MM-DD", () => {
|
|
83
|
-
const date = new Date(2024, 10, 26) // November 26, 2024
|
|
84
|
-
expect(controller.formatDateString(date)).toBe("2024-11-26")
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
test("pads single digit months", () => {
|
|
88
|
-
const date = new Date(2024, 0, 15) // January 15, 2024
|
|
89
|
-
expect(controller.formatDateString(date)).toBe("2024-01-15")
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
test("pads single digit days", () => {
|
|
93
|
-
const date = new Date(2024, 10, 5) // November 5, 2024
|
|
94
|
-
expect(controller.formatDateString(date)).toBe("2024-11-05")
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
test("returns empty string for null", () => {
|
|
98
|
-
expect(controller.formatDateString(null)).toBe("")
|
|
99
|
-
})
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
describe("connect", () => {
|
|
103
|
-
test("initializes currentMonth from monthValue", () => {
|
|
104
|
-
expect(controller.currentMonth.getFullYear()).toBe(2024)
|
|
105
|
-
expect(controller.currentMonth.getMonth()).toBe(10) // November
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
test("initializes selectedDate as null when no selectedValue", () => {
|
|
109
|
-
expect(controller.selectedDate).toBeNull()
|
|
110
|
-
})
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
describe("previousMonth", () => {
|
|
114
|
-
test("moves to the previous month", () => {
|
|
115
|
-
controller.previousMonth()
|
|
116
|
-
|
|
117
|
-
expect(controller.currentMonth.getMonth()).toBe(9) // October
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
test("wraps to previous year from January", () => {
|
|
121
|
-
controller.currentMonth = new Date(2024, 0, 1) // January 2024
|
|
122
|
-
controller.previousMonth()
|
|
123
|
-
|
|
124
|
-
expect(controller.currentMonth.getMonth()).toBe(11) // December
|
|
125
|
-
expect(controller.currentMonth.getFullYear()).toBe(2023)
|
|
126
|
-
})
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
describe("nextMonth", () => {
|
|
130
|
-
test("moves to the next month", () => {
|
|
131
|
-
controller.nextMonth()
|
|
132
|
-
|
|
133
|
-
expect(controller.currentMonth.getMonth()).toBe(11) // December
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
test("wraps to next year from December", () => {
|
|
137
|
-
controller.currentMonth = new Date(2024, 11, 1) // December 2024
|
|
138
|
-
controller.nextMonth()
|
|
139
|
-
|
|
140
|
-
expect(controller.currentMonth.getMonth()).toBe(0) // January
|
|
141
|
-
expect(controller.currentMonth.getFullYear()).toBe(2025)
|
|
142
|
-
})
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
describe("selectDay", () => {
|
|
146
|
-
test("selects the clicked date", () => {
|
|
147
|
-
// Create a mock event with the date data
|
|
148
|
-
const mockEvent = {
|
|
149
|
-
currentTarget: {
|
|
150
|
-
dataset: { date: "2024-11-15" }
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
controller.selectDay(mockEvent)
|
|
155
|
-
|
|
156
|
-
expect(controller.selectedDate).not.toBeNull()
|
|
157
|
-
expect(controller.selectedDate.getDate()).toBe(15)
|
|
158
|
-
expect(controller.selectedDate.getMonth()).toBe(10) // November
|
|
159
|
-
expect(controller.selectedDate.getFullYear()).toBe(2024)
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
test("updates the hidden input value", () => {
|
|
163
|
-
const mockEvent = {
|
|
164
|
-
currentTarget: {
|
|
165
|
-
dataset: { date: "2024-11-20" }
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
controller.selectDay(mockEvent)
|
|
170
|
-
|
|
171
|
-
const hiddenInput = element.querySelector('[data-calendar-target="hiddenInput"]')
|
|
172
|
-
expect(hiddenInput.value).toBe("2024-11-20")
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
test("dispatches select event with date details", () => {
|
|
176
|
-
let eventDetail = null
|
|
177
|
-
element.addEventListener("calendar:select", (e) => {
|
|
178
|
-
eventDetail = e.detail
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
const mockEvent = {
|
|
182
|
-
currentTarget: {
|
|
183
|
-
dataset: { date: "2024-11-10" }
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
controller.selectDay(mockEvent)
|
|
188
|
-
|
|
189
|
-
expect(eventDetail).not.toBeNull()
|
|
190
|
-
expect(eventDetail.dateString).toBe("2024-11-10")
|
|
191
|
-
expect(eventDetail.date.getDate()).toBe(10)
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
test("does nothing if no date in event", () => {
|
|
195
|
-
const mockEvent = {
|
|
196
|
-
currentTarget: {
|
|
197
|
-
dataset: {}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
controller.selectDay(mockEvent)
|
|
202
|
-
|
|
203
|
-
expect(controller.selectedDate).toBeNull()
|
|
204
|
-
})
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
describe("render", () => {
|
|
208
|
-
test("updates month/year display", () => {
|
|
209
|
-
controller.render()
|
|
210
|
-
|
|
211
|
-
const monthYearDisplay = element.querySelector('[data-calendar-target="monthYear"]')
|
|
212
|
-
expect(monthYearDisplay.textContent).toBe("November 2024")
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
test("renders correct number of day buttons (6 weeks = 42 days)", () => {
|
|
216
|
-
controller.render()
|
|
217
|
-
|
|
218
|
-
// Use a more flexible selector since targets are set via data attributes
|
|
219
|
-
const grid = element.querySelector('[data-calendar-target="grid"]')
|
|
220
|
-
const dayButtons = grid.querySelectorAll('button[data-date]')
|
|
221
|
-
// Should be between 28 and 42 days depending on month layout
|
|
222
|
-
expect(dayButtons.length).toBeGreaterThanOrEqual(28)
|
|
223
|
-
expect(dayButtons.length).toBeLessThanOrEqual(42)
|
|
224
|
-
})
|
|
225
|
-
|
|
226
|
-
test("marks today with special styling", () => {
|
|
227
|
-
// Set current month to today's month
|
|
228
|
-
const today = new Date()
|
|
229
|
-
controller.currentMonth = new Date(today.getFullYear(), today.getMonth(), 1)
|
|
230
|
-
controller.render()
|
|
231
|
-
|
|
232
|
-
const todayStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`
|
|
233
|
-
const todayButton = element.querySelector(`[data-date="${todayStr}"]`)
|
|
234
|
-
|
|
235
|
-
expect(todayButton).not.toBeNull()
|
|
236
|
-
expect(todayButton.classList.contains("bg-accent")).toBe(true)
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
test("marks selected date with primary styling", () => {
|
|
240
|
-
controller.selectedDate = new Date(2024, 10, 15)
|
|
241
|
-
controller.selectedValue = "2024-11-15"
|
|
242
|
-
controller.render()
|
|
243
|
-
|
|
244
|
-
const selectedButton = element.querySelector('[data-date="2024-11-15"]')
|
|
245
|
-
expect(selectedButton.classList.contains("bg-primary")).toBe(true)
|
|
246
|
-
expect(selectedButton.getAttribute("aria-selected")).toBe("true")
|
|
247
|
-
})
|
|
248
|
-
|
|
249
|
-
test("marks outside month dates with muted styling", () => {
|
|
250
|
-
controller.render()
|
|
251
|
-
|
|
252
|
-
// November 2024 starts on Friday, so there should be days from October
|
|
253
|
-
const grid = element.querySelector('[data-calendar-target="grid"]')
|
|
254
|
-
const allDays = grid.querySelectorAll("button[data-date]")
|
|
255
|
-
const outsideDays = Array.from(allDays).filter(btn => {
|
|
256
|
-
const dateStr = btn.dataset.date
|
|
257
|
-
if (!dateStr) return false
|
|
258
|
-
const month = parseInt(dateStr.split('-')[1], 10)
|
|
259
|
-
return month !== 11 // Not November
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
// Check at least some outside days exist and have the styling
|
|
263
|
-
if (outsideDays.length > 0) {
|
|
264
|
-
expect(outsideDays[0].classList.contains("text-muted-foreground")).toBe(true)
|
|
265
|
-
expect(outsideDays[0].classList.contains("opacity-50")).toBe(true)
|
|
266
|
-
}
|
|
267
|
-
})
|
|
268
|
-
})
|
|
269
|
-
|
|
270
|
-
describe("timezone handling", () => {
|
|
271
|
-
test("selecting a date preserves the correct day regardless of timezone", () => {
|
|
272
|
-
// This is the critical test for the timezone bug
|
|
273
|
-
// When parsing "2024-11-26" via new Date("2024-11-26"), it interprets
|
|
274
|
-
// as UTC midnight, which becomes Nov 25 in western timezones
|
|
275
|
-
|
|
276
|
-
const mockEvent = {
|
|
277
|
-
currentTarget: {
|
|
278
|
-
dataset: { date: "2024-11-15" }
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
controller.selectDay(mockEvent)
|
|
283
|
-
|
|
284
|
-
// The selected date should be exactly November 15, not November 14
|
|
285
|
-
expect(controller.selectedDate.getDate()).toBe(15)
|
|
286
|
-
expect(controller.selectedDate.getMonth()).toBe(10) // November
|
|
287
|
-
expect(controller.selectedValue).toBe("2024-11-15")
|
|
288
|
-
})
|
|
289
|
-
|
|
290
|
-
test("initializing with a selected value preserves the correct day", async () => {
|
|
291
|
-
// Test that selectedValue initialization doesn't shift the date
|
|
292
|
-
application.stop()
|
|
293
|
-
document.body.innerHTML = ""
|
|
294
|
-
|
|
295
|
-
document.body.innerHTML = `
|
|
296
|
-
<div data-controller="calendar"
|
|
297
|
-
data-calendar-month-value="2024-11-01"
|
|
298
|
-
data-calendar-selected-value="2024-11-26">
|
|
299
|
-
<div data-calendar-target="grid"></div>
|
|
300
|
-
</div>
|
|
301
|
-
`
|
|
302
|
-
|
|
303
|
-
application = Application.start()
|
|
304
|
-
application.register("calendar", CalendarController)
|
|
305
|
-
|
|
306
|
-
await new Promise(resolve => requestAnimationFrame(resolve))
|
|
307
|
-
|
|
308
|
-
const newElement = document.querySelector('[data-controller="calendar"]')
|
|
309
|
-
const newController = application.getControllerForElementAndIdentifier(newElement, "calendar")
|
|
310
|
-
|
|
311
|
-
expect(newController.selectedDate.getDate()).toBe(26)
|
|
312
|
-
expect(newController.selectedDate.getMonth()).toBe(10) // November
|
|
313
|
-
})
|
|
314
|
-
|
|
315
|
-
test("parseLocalDate avoids UTC timezone shift for any date", () => {
|
|
316
|
-
// Test a variety of dates that could be affected by timezone
|
|
317
|
-
const testDates = [
|
|
318
|
-
"2024-01-01", // New Year
|
|
319
|
-
"2024-06-15", // Mid-year
|
|
320
|
-
"2024-12-31", // End of year
|
|
321
|
-
"2024-03-10", // DST transition day (US)
|
|
322
|
-
"2024-11-03", // DST transition day (US)
|
|
323
|
-
]
|
|
324
|
-
|
|
325
|
-
testDates.forEach(dateStr => {
|
|
326
|
-
const [year, month, day] = dateStr.split('-').map(Number)
|
|
327
|
-
const parsed = controller.parseLocalDate(dateStr)
|
|
328
|
-
|
|
329
|
-
expect(parsed.getFullYear()).toBe(year)
|
|
330
|
-
expect(parsed.getMonth()).toBe(month - 1)
|
|
331
|
-
expect(parsed.getDate()).toBe(day)
|
|
332
|
-
})
|
|
333
|
-
})
|
|
334
|
-
})
|
|
335
|
-
|
|
336
|
-
describe("monthValueChanged", () => {
|
|
337
|
-
test("updates currentMonth when value changes", () => {
|
|
338
|
-
controller.monthValue = "2024-06-01"
|
|
339
|
-
controller.monthValueChanged()
|
|
340
|
-
|
|
341
|
-
expect(controller.currentMonth.getMonth()).toBe(5) // June
|
|
342
|
-
expect(controller.currentMonth.getFullYear()).toBe(2024)
|
|
343
|
-
})
|
|
344
|
-
})
|
|
345
|
-
|
|
346
|
-
describe("selectedValueChanged", () => {
|
|
347
|
-
test("updates selectedDate when value changes", () => {
|
|
348
|
-
controller.selectedValue = "2024-07-20"
|
|
349
|
-
controller.selectedValueChanged()
|
|
350
|
-
|
|
351
|
-
expect(controller.selectedDate.getDate()).toBe(20)
|
|
352
|
-
expect(controller.selectedDate.getMonth()).toBe(6) // July
|
|
353
|
-
})
|
|
354
|
-
})
|
|
355
|
-
|
|
356
|
-
describe("MONTHS constant", () => {
|
|
357
|
-
test("contains all 12 months in order", () => {
|
|
358
|
-
expect(CalendarController.MONTHS).toEqual([
|
|
359
|
-
"January", "February", "March", "April", "May", "June",
|
|
360
|
-
"July", "August", "September", "October", "November", "December"
|
|
361
|
-
])
|
|
362
|
-
})
|
|
363
|
-
})
|
|
364
|
-
|
|
365
|
-
describe("WEEKDAYS constant", () => {
|
|
366
|
-
test("contains all 7 weekdays starting with Sunday", () => {
|
|
367
|
-
expect(CalendarController.WEEKDAYS).toEqual([
|
|
368
|
-
"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"
|
|
369
|
-
])
|
|
370
|
-
})
|
|
371
|
-
})
|
|
372
|
-
|
|
373
|
-
describe("isDateDisabled", () => {
|
|
374
|
-
test("returns false for dates within valid range", () => {
|
|
375
|
-
controller.minDateValue = "2024-11-01"
|
|
376
|
-
controller.maxDateValue = "2024-11-30"
|
|
377
|
-
|
|
378
|
-
const date = new Date(2024, 10, 15)
|
|
379
|
-
expect(controller.isDateDisabled(date)).toBe(false)
|
|
380
|
-
})
|
|
381
|
-
|
|
382
|
-
test("returns true for dates before minDate", () => {
|
|
383
|
-
controller.minDateValue = "2024-11-10"
|
|
384
|
-
|
|
385
|
-
const date = new Date(2024, 10, 5)
|
|
386
|
-
expect(controller.isDateDisabled(date)).toBe(true)
|
|
387
|
-
})
|
|
388
|
-
|
|
389
|
-
test("returns true for dates after maxDate", () => {
|
|
390
|
-
controller.maxDateValue = "2024-11-20"
|
|
391
|
-
|
|
392
|
-
const date = new Date(2024, 10, 25)
|
|
393
|
-
expect(controller.isDateDisabled(date)).toBe(true)
|
|
394
|
-
})
|
|
395
|
-
|
|
396
|
-
test("returns true for dates in disabledDates list", () => {
|
|
397
|
-
controller.disabledDatesValue = "2024-11-15,2024-11-16,2024-11-17"
|
|
398
|
-
|
|
399
|
-
expect(controller.isDateDisabled(new Date(2024, 10, 15))).toBe(true)
|
|
400
|
-
expect(controller.isDateDisabled(new Date(2024, 10, 18))).toBe(false)
|
|
401
|
-
})
|
|
402
|
-
|
|
403
|
-
test("returns true for disabled days of week", () => {
|
|
404
|
-
controller.disabledDaysOfWeekValue = "0,6" // Sunday and Saturday
|
|
405
|
-
|
|
406
|
-
// November 16, 2024 is a Saturday
|
|
407
|
-
expect(controller.isDateDisabled(new Date(2024, 10, 16))).toBe(true)
|
|
408
|
-
// November 17, 2024 is a Sunday
|
|
409
|
-
expect(controller.isDateDisabled(new Date(2024, 10, 17))).toBe(true)
|
|
410
|
-
// November 18, 2024 is a Monday
|
|
411
|
-
expect(controller.isDateDisabled(new Date(2024, 10, 18))).toBe(false)
|
|
412
|
-
})
|
|
413
|
-
})
|
|
414
|
-
|
|
415
|
-
describe("multiple selection mode", () => {
|
|
416
|
-
beforeEach(async () => {
|
|
417
|
-
application.stop()
|
|
418
|
-
document.body.innerHTML = ""
|
|
419
|
-
|
|
420
|
-
document.body.innerHTML = `
|
|
421
|
-
<div data-controller="calendar"
|
|
422
|
-
data-calendar-month-value="2024-11-01"
|
|
423
|
-
data-calendar-mode-value="multiple"
|
|
424
|
-
data-calendar-selected-value="">
|
|
425
|
-
<div data-calendar-target="grid"></div>
|
|
426
|
-
<input type="hidden" data-calendar-target="hiddenInput">
|
|
427
|
-
</div>
|
|
428
|
-
`
|
|
429
|
-
|
|
430
|
-
application = Application.start()
|
|
431
|
-
application.register("calendar", CalendarController)
|
|
432
|
-
|
|
433
|
-
await new Promise(resolve => requestAnimationFrame(resolve))
|
|
434
|
-
|
|
435
|
-
element = document.querySelector('[data-controller="calendar"]')
|
|
436
|
-
controller = application.getControllerForElementAndIdentifier(element, "calendar")
|
|
437
|
-
})
|
|
438
|
-
|
|
439
|
-
test("initializes with empty array", () => {
|
|
440
|
-
expect(controller.selectedDate).toEqual([])
|
|
441
|
-
})
|
|
442
|
-
|
|
443
|
-
test("can select multiple dates", () => {
|
|
444
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-10" } } })
|
|
445
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-15" } } })
|
|
446
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-20" } } })
|
|
447
|
-
|
|
448
|
-
expect(controller.selectedDate.length).toBe(3)
|
|
449
|
-
expect(controller.selectedValue).toBe("2024-11-10,2024-11-15,2024-11-20")
|
|
450
|
-
})
|
|
451
|
-
|
|
452
|
-
test("can deselect dates by clicking again", () => {
|
|
453
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-10" } } })
|
|
454
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-15" } } })
|
|
455
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-10" } } }) // deselect
|
|
456
|
-
|
|
457
|
-
expect(controller.selectedDate.length).toBe(1)
|
|
458
|
-
expect(controller.selectedValue).toBe("2024-11-15")
|
|
459
|
-
})
|
|
460
|
-
|
|
461
|
-
test("dispatches select event with dates array", () => {
|
|
462
|
-
let eventDetail = null
|
|
463
|
-
element.addEventListener("calendar:select", (e) => {
|
|
464
|
-
eventDetail = e.detail
|
|
465
|
-
})
|
|
466
|
-
|
|
467
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-10" } } })
|
|
468
|
-
|
|
469
|
-
expect(eventDetail.dates).toBeDefined()
|
|
470
|
-
expect(eventDetail.dateStrings).toContain("2024-11-10")
|
|
471
|
-
})
|
|
472
|
-
})
|
|
473
|
-
|
|
474
|
-
describe("range selection mode", () => {
|
|
475
|
-
beforeEach(async () => {
|
|
476
|
-
application.stop()
|
|
477
|
-
document.body.innerHTML = ""
|
|
478
|
-
|
|
479
|
-
document.body.innerHTML = `
|
|
480
|
-
<div data-controller="calendar"
|
|
481
|
-
data-calendar-month-value="2024-11-01"
|
|
482
|
-
data-calendar-mode-value="range"
|
|
483
|
-
data-calendar-selected-value="">
|
|
484
|
-
<div data-calendar-target="grid"></div>
|
|
485
|
-
<input type="hidden" data-calendar-target="hiddenInput">
|
|
486
|
-
</div>
|
|
487
|
-
`
|
|
488
|
-
|
|
489
|
-
application = Application.start()
|
|
490
|
-
application.register("calendar", CalendarController)
|
|
491
|
-
|
|
492
|
-
await new Promise(resolve => requestAnimationFrame(resolve))
|
|
493
|
-
|
|
494
|
-
element = document.querySelector('[data-controller="calendar"]')
|
|
495
|
-
controller = application.getControllerForElementAndIdentifier(element, "calendar")
|
|
496
|
-
})
|
|
497
|
-
|
|
498
|
-
test("first click sets range start", () => {
|
|
499
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-10" } } })
|
|
500
|
-
|
|
501
|
-
expect(controller.rangeStart).not.toBeNull()
|
|
502
|
-
expect(controller.rangeStart.getDate()).toBe(10)
|
|
503
|
-
expect(controller.rangeEnd).toBeNull()
|
|
504
|
-
})
|
|
505
|
-
|
|
506
|
-
test("second click sets range end", () => {
|
|
507
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-10" } } })
|
|
508
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-20" } } })
|
|
509
|
-
|
|
510
|
-
expect(controller.rangeStart.getDate()).toBe(10)
|
|
511
|
-
expect(controller.rangeEnd.getDate()).toBe(20)
|
|
512
|
-
expect(controller.selectedValue).toBe("2024-11-10,2024-11-20")
|
|
513
|
-
})
|
|
514
|
-
|
|
515
|
-
test("swaps start and end if end is before start", () => {
|
|
516
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-20" } } })
|
|
517
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-10" } } })
|
|
518
|
-
|
|
519
|
-
expect(controller.rangeStart.getDate()).toBe(10)
|
|
520
|
-
expect(controller.rangeEnd.getDate()).toBe(20)
|
|
521
|
-
})
|
|
522
|
-
|
|
523
|
-
test("third click starts new range", () => {
|
|
524
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-10" } } })
|
|
525
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-20" } } })
|
|
526
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-25" } } })
|
|
527
|
-
|
|
528
|
-
expect(controller.rangeStart.getDate()).toBe(25)
|
|
529
|
-
expect(controller.rangeEnd).toBeNull()
|
|
530
|
-
})
|
|
531
|
-
|
|
532
|
-
test("isDateInRange returns true for dates between start and end", () => {
|
|
533
|
-
controller.rangeStart = new Date(2024, 10, 10)
|
|
534
|
-
controller.rangeEnd = new Date(2024, 10, 20)
|
|
535
|
-
|
|
536
|
-
expect(controller.isDateInRange(new Date(2024, 10, 15))).toBe(true)
|
|
537
|
-
expect(controller.isDateInRange(new Date(2024, 10, 10))).toBe(false) // start
|
|
538
|
-
expect(controller.isDateInRange(new Date(2024, 10, 20))).toBe(false) // end
|
|
539
|
-
expect(controller.isDateInRange(new Date(2024, 10, 5))).toBe(false) // before
|
|
540
|
-
expect(controller.isDateInRange(new Date(2024, 10, 25))).toBe(false) // after
|
|
541
|
-
})
|
|
542
|
-
})
|
|
543
|
-
|
|
544
|
-
describe("required mode", () => {
|
|
545
|
-
beforeEach(async () => {
|
|
546
|
-
application.stop()
|
|
547
|
-
document.body.innerHTML = ""
|
|
548
|
-
|
|
549
|
-
document.body.innerHTML = `
|
|
550
|
-
<div data-controller="calendar"
|
|
551
|
-
data-calendar-month-value="2024-11-01"
|
|
552
|
-
data-calendar-required-value="true"
|
|
553
|
-
data-calendar-selected-value="2024-11-15">
|
|
554
|
-
<div data-calendar-target="grid"></div>
|
|
555
|
-
</div>
|
|
556
|
-
`
|
|
557
|
-
|
|
558
|
-
application = Application.start()
|
|
559
|
-
application.register("calendar", CalendarController)
|
|
560
|
-
|
|
561
|
-
await new Promise(resolve => requestAnimationFrame(resolve))
|
|
562
|
-
|
|
563
|
-
element = document.querySelector('[data-controller="calendar"]')
|
|
564
|
-
controller = application.getControllerForElementAndIdentifier(element, "calendar")
|
|
565
|
-
})
|
|
566
|
-
|
|
567
|
-
test("prevents deselection when required is true", () => {
|
|
568
|
-
expect(controller.selectedDate.getDate()).toBe(15)
|
|
569
|
-
|
|
570
|
-
// Try to deselect by clicking the same date
|
|
571
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-15" } } })
|
|
572
|
-
|
|
573
|
-
// Should still be selected
|
|
574
|
-
expect(controller.selectedDate.getDate()).toBe(15)
|
|
575
|
-
})
|
|
576
|
-
|
|
577
|
-
test("allows selecting a different date", () => {
|
|
578
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-20" } } })
|
|
579
|
-
|
|
580
|
-
expect(controller.selectedDate.getDate()).toBe(20)
|
|
581
|
-
})
|
|
582
|
-
})
|
|
583
|
-
|
|
584
|
-
describe("disabled date selection", () => {
|
|
585
|
-
test("does not select disabled dates", () => {
|
|
586
|
-
controller.disabledDatesValue = "2024-11-15"
|
|
587
|
-
|
|
588
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-15" } } })
|
|
589
|
-
|
|
590
|
-
expect(controller.selectedDate).toBeNull()
|
|
591
|
-
})
|
|
592
|
-
})
|
|
593
|
-
|
|
594
|
-
describe("disabled days CSS rendering", () => {
|
|
595
|
-
test("disabled dates have correct CSS classes", () => {
|
|
596
|
-
controller.disabledDatesValue = "2024-11-15"
|
|
597
|
-
controller.render()
|
|
598
|
-
|
|
599
|
-
const disabledButton = element.querySelector('[data-date="2024-11-15"]')
|
|
600
|
-
expect(disabledButton).not.toBeNull()
|
|
601
|
-
expect(disabledButton.classList.contains("text-muted-foreground")).toBe(true)
|
|
602
|
-
expect(disabledButton.classList.contains("opacity-50")).toBe(true)
|
|
603
|
-
expect(disabledButton.classList.contains("cursor-not-allowed")).toBe(true)
|
|
604
|
-
})
|
|
605
|
-
|
|
606
|
-
test("disabled dates have aria-disabled attribute", () => {
|
|
607
|
-
controller.disabledDatesValue = "2024-11-15"
|
|
608
|
-
controller.render()
|
|
609
|
-
|
|
610
|
-
const disabledButton = element.querySelector('[data-date="2024-11-15"]')
|
|
611
|
-
expect(disabledButton.getAttribute("aria-disabled")).toBe("true")
|
|
612
|
-
})
|
|
613
|
-
|
|
614
|
-
test("enabled dates do not have disabled CSS classes", () => {
|
|
615
|
-
controller.disabledDatesValue = "2024-11-15"
|
|
616
|
-
controller.render()
|
|
617
|
-
|
|
618
|
-
const enabledButton = element.querySelector('[data-date="2024-11-20"]')
|
|
619
|
-
expect(enabledButton).not.toBeNull()
|
|
620
|
-
expect(enabledButton.classList.contains("cursor-not-allowed")).toBe(false)
|
|
621
|
-
expect(enabledButton.getAttribute("aria-disabled")).toBeNull()
|
|
622
|
-
})
|
|
623
|
-
|
|
624
|
-
test("minDate disables earlier dates with correct CSS", () => {
|
|
625
|
-
controller.minDateValue = "2024-11-10"
|
|
626
|
-
controller.render()
|
|
627
|
-
|
|
628
|
-
const disabledButton = element.querySelector('[data-date="2024-11-05"]')
|
|
629
|
-
expect(disabledButton).not.toBeNull()
|
|
630
|
-
expect(disabledButton.classList.contains("text-muted-foreground")).toBe(true)
|
|
631
|
-
expect(disabledButton.classList.contains("cursor-not-allowed")).toBe(true)
|
|
632
|
-
expect(disabledButton.getAttribute("aria-disabled")).toBe("true")
|
|
633
|
-
|
|
634
|
-
const enabledButton = element.querySelector('[data-date="2024-11-15"]')
|
|
635
|
-
expect(enabledButton.classList.contains("cursor-not-allowed")).toBe(false)
|
|
636
|
-
})
|
|
637
|
-
|
|
638
|
-
test("maxDate disables later dates with correct CSS", () => {
|
|
639
|
-
controller.maxDateValue = "2024-11-20"
|
|
640
|
-
controller.render()
|
|
641
|
-
|
|
642
|
-
const disabledButton = element.querySelector('[data-date="2024-11-25"]')
|
|
643
|
-
expect(disabledButton).not.toBeNull()
|
|
644
|
-
expect(disabledButton.classList.contains("text-muted-foreground")).toBe(true)
|
|
645
|
-
expect(disabledButton.classList.contains("cursor-not-allowed")).toBe(true)
|
|
646
|
-
expect(disabledButton.getAttribute("aria-disabled")).toBe("true")
|
|
647
|
-
|
|
648
|
-
const enabledButton = element.querySelector('[data-date="2024-11-15"]')
|
|
649
|
-
expect(enabledButton.classList.contains("cursor-not-allowed")).toBe(false)
|
|
650
|
-
})
|
|
651
|
-
|
|
652
|
-
test("disabledDaysOfWeek disables weekends with correct CSS", () => {
|
|
653
|
-
controller.disabledDaysOfWeekValue = "0,6" // Sunday and Saturday
|
|
654
|
-
controller.render()
|
|
655
|
-
|
|
656
|
-
// November 16, 2024 is a Saturday
|
|
657
|
-
const saturdayButton = element.querySelector('[data-date="2024-11-16"]')
|
|
658
|
-
expect(saturdayButton).not.toBeNull()
|
|
659
|
-
expect(saturdayButton.classList.contains("text-muted-foreground")).toBe(true)
|
|
660
|
-
expect(saturdayButton.classList.contains("cursor-not-allowed")).toBe(true)
|
|
661
|
-
expect(saturdayButton.getAttribute("aria-disabled")).toBe("true")
|
|
662
|
-
|
|
663
|
-
// November 17, 2024 is a Sunday
|
|
664
|
-
const sundayButton = element.querySelector('[data-date="2024-11-17"]')
|
|
665
|
-
expect(sundayButton.classList.contains("cursor-not-allowed")).toBe(true)
|
|
666
|
-
|
|
667
|
-
// November 18, 2024 is a Monday - should be enabled
|
|
668
|
-
const mondayButton = element.querySelector('[data-date="2024-11-18"]')
|
|
669
|
-
expect(mondayButton.classList.contains("cursor-not-allowed")).toBe(false)
|
|
670
|
-
})
|
|
671
|
-
|
|
672
|
-
test("multiple disabled dates have correct CSS", () => {
|
|
673
|
-
controller.disabledDatesValue = "2024-11-10,2024-11-15,2024-11-20"
|
|
674
|
-
controller.render()
|
|
675
|
-
|
|
676
|
-
const disabledDates = ["2024-11-10", "2024-11-15", "2024-11-20"]
|
|
677
|
-
disabledDates.forEach(dateStr => {
|
|
678
|
-
const button = element.querySelector(`[data-date="${dateStr}"]`)
|
|
679
|
-
expect(button.classList.contains("cursor-not-allowed")).toBe(true)
|
|
680
|
-
expect(button.getAttribute("aria-disabled")).toBe("true")
|
|
681
|
-
})
|
|
682
|
-
|
|
683
|
-
// Check an enabled date between them
|
|
684
|
-
const enabledButton = element.querySelector('[data-date="2024-11-12"]')
|
|
685
|
-
expect(enabledButton.classList.contains("cursor-not-allowed")).toBe(false)
|
|
686
|
-
})
|
|
687
|
-
})
|
|
688
|
-
|
|
689
|
-
describe("snapshots", () => {
|
|
690
|
-
test("renders default calendar grid correctly", () => {
|
|
691
|
-
controller.render()
|
|
692
|
-
|
|
693
|
-
const grid = element.querySelector('[data-calendar-target="grid"]')
|
|
694
|
-
expect(grid.innerHTML).toMatchSnapshot()
|
|
695
|
-
})
|
|
696
|
-
|
|
697
|
-
test("renders calendar with selected date correctly", () => {
|
|
698
|
-
controller.selectedDate = new Date(2024, 10, 15)
|
|
699
|
-
controller.selectedValue = "2024-11-15"
|
|
700
|
-
controller.render()
|
|
701
|
-
|
|
702
|
-
const grid = element.querySelector('[data-calendar-target="grid"]')
|
|
703
|
-
expect(grid.innerHTML).toMatchSnapshot()
|
|
704
|
-
})
|
|
705
|
-
|
|
706
|
-
test("renders calendar with disabled dates correctly", () => {
|
|
707
|
-
controller.disabledDatesValue = "2024-11-10,2024-11-15,2024-11-20"
|
|
708
|
-
controller.render()
|
|
709
|
-
|
|
710
|
-
const grid = element.querySelector('[data-calendar-target="grid"]')
|
|
711
|
-
expect(grid.innerHTML).toMatchSnapshot()
|
|
712
|
-
})
|
|
713
|
-
|
|
714
|
-
test("renders calendar with min and max dates correctly", () => {
|
|
715
|
-
controller.minDateValue = "2024-11-05"
|
|
716
|
-
controller.maxDateValue = "2024-11-25"
|
|
717
|
-
controller.render()
|
|
718
|
-
|
|
719
|
-
const grid = element.querySelector('[data-calendar-target="grid"]')
|
|
720
|
-
expect(grid.innerHTML).toMatchSnapshot()
|
|
721
|
-
})
|
|
722
|
-
|
|
723
|
-
test("renders calendar with disabled weekends correctly", () => {
|
|
724
|
-
controller.disabledDaysOfWeekValue = "0,6"
|
|
725
|
-
controller.render()
|
|
726
|
-
|
|
727
|
-
const grid = element.querySelector('[data-calendar-target="grid"]')
|
|
728
|
-
expect(grid.innerHTML).toMatchSnapshot()
|
|
729
|
-
})
|
|
730
|
-
})
|
|
731
|
-
|
|
732
|
-
describe("disabled dates persist after interaction (regression tests)", () => {
|
|
733
|
-
test("disabledDaysOfWeek remains enforced after selecting a date", () => {
|
|
734
|
-
controller.disabledDaysOfWeekValue = "0,6" // Sunday and Saturday
|
|
735
|
-
controller.render()
|
|
736
|
-
|
|
737
|
-
// Get weekend buttons before interaction
|
|
738
|
-
const saturdayBefore = element.querySelector('[data-date="2024-11-16"]') // Saturday
|
|
739
|
-
const sundayBefore = element.querySelector('[data-date="2024-11-17"]') // Sunday
|
|
740
|
-
|
|
741
|
-
// Verify initially disabled
|
|
742
|
-
expect(saturdayBefore.classList.contains("cursor-not-allowed")).toBe(true)
|
|
743
|
-
expect(saturdayBefore.hasAttribute("disabled")).toBe(true)
|
|
744
|
-
expect(sundayBefore.classList.contains("cursor-not-allowed")).toBe(true)
|
|
745
|
-
expect(sundayBefore.hasAttribute("disabled")).toBe(true)
|
|
746
|
-
|
|
747
|
-
// Select a weekday date (triggers re-render)
|
|
748
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-18" } } }) // Monday
|
|
749
|
-
|
|
750
|
-
// Check weekends are STILL disabled after re-render
|
|
751
|
-
const saturdayAfter = element.querySelector('[data-date="2024-11-16"]')
|
|
752
|
-
const sundayAfter = element.querySelector('[data-date="2024-11-17"]')
|
|
753
|
-
|
|
754
|
-
expect(saturdayAfter.classList.contains("cursor-not-allowed")).toBe(true)
|
|
755
|
-
expect(saturdayAfter.hasAttribute("disabled")).toBe(true)
|
|
756
|
-
expect(saturdayAfter.getAttribute("aria-disabled")).toBe("true")
|
|
757
|
-
expect(sundayAfter.classList.contains("cursor-not-allowed")).toBe(true)
|
|
758
|
-
expect(sundayAfter.hasAttribute("disabled")).toBe(true)
|
|
759
|
-
expect(sundayAfter.getAttribute("aria-disabled")).toBe("true")
|
|
760
|
-
})
|
|
761
|
-
|
|
762
|
-
test("disabledDates remains enforced after selecting a date", () => {
|
|
763
|
-
controller.disabledDatesValue = "2024-11-15,2024-11-20"
|
|
764
|
-
controller.render()
|
|
765
|
-
|
|
766
|
-
// Verify initially disabled
|
|
767
|
-
const disabled15Before = element.querySelector('[data-date="2024-11-15"]')
|
|
768
|
-
const disabled20Before = element.querySelector('[data-date="2024-11-20"]')
|
|
769
|
-
|
|
770
|
-
expect(disabled15Before.hasAttribute("disabled")).toBe(true)
|
|
771
|
-
expect(disabled20Before.hasAttribute("disabled")).toBe(true)
|
|
772
|
-
|
|
773
|
-
// Select a different date (triggers re-render)
|
|
774
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-18" } } })
|
|
775
|
-
|
|
776
|
-
// Check disabled dates are STILL disabled after re-render
|
|
777
|
-
const disabled15After = element.querySelector('[data-date="2024-11-15"]')
|
|
778
|
-
const disabled20After = element.querySelector('[data-date="2024-11-20"]')
|
|
779
|
-
|
|
780
|
-
expect(disabled15After.hasAttribute("disabled")).toBe(true)
|
|
781
|
-
expect(disabled15After.getAttribute("aria-disabled")).toBe("true")
|
|
782
|
-
expect(disabled15After.classList.contains("cursor-not-allowed")).toBe(true)
|
|
783
|
-
expect(disabled20After.hasAttribute("disabled")).toBe(true)
|
|
784
|
-
expect(disabled20After.getAttribute("aria-disabled")).toBe("true")
|
|
785
|
-
expect(disabled20After.classList.contains("cursor-not-allowed")).toBe(true)
|
|
786
|
-
})
|
|
787
|
-
|
|
788
|
-
test("minDate remains enforced after selecting a date", () => {
|
|
789
|
-
controller.minDateValue = "2024-11-10"
|
|
790
|
-
controller.render()
|
|
791
|
-
|
|
792
|
-
// Verify initially disabled
|
|
793
|
-
const disabled5Before = element.querySelector('[data-date="2024-11-05"]')
|
|
794
|
-
expect(disabled5Before.hasAttribute("disabled")).toBe(true)
|
|
795
|
-
|
|
796
|
-
// Select a valid date (triggers re-render)
|
|
797
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-15" } } })
|
|
798
|
-
|
|
799
|
-
// Check dates before minDate are STILL disabled
|
|
800
|
-
const disabled5After = element.querySelector('[data-date="2024-11-05"]')
|
|
801
|
-
expect(disabled5After.hasAttribute("disabled")).toBe(true)
|
|
802
|
-
expect(disabled5After.getAttribute("aria-disabled")).toBe("true")
|
|
803
|
-
expect(disabled5After.classList.contains("cursor-not-allowed")).toBe(true)
|
|
804
|
-
})
|
|
805
|
-
|
|
806
|
-
test("maxDate remains enforced after selecting a date", () => {
|
|
807
|
-
controller.maxDateValue = "2024-11-20"
|
|
808
|
-
controller.render()
|
|
809
|
-
|
|
810
|
-
// Verify initially disabled
|
|
811
|
-
const disabled25Before = element.querySelector('[data-date="2024-11-25"]')
|
|
812
|
-
expect(disabled25Before.hasAttribute("disabled")).toBe(true)
|
|
813
|
-
|
|
814
|
-
// Select a valid date (triggers re-render)
|
|
815
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-15" } } })
|
|
816
|
-
|
|
817
|
-
// Check dates after maxDate are STILL disabled
|
|
818
|
-
const disabled25After = element.querySelector('[data-date="2024-11-25"]')
|
|
819
|
-
expect(disabled25After.hasAttribute("disabled")).toBe(true)
|
|
820
|
-
expect(disabled25After.getAttribute("aria-disabled")).toBe("true")
|
|
821
|
-
expect(disabled25After.classList.contains("cursor-not-allowed")).toBe(true)
|
|
822
|
-
})
|
|
823
|
-
|
|
824
|
-
test("disabled buttons do not have click action for date selection", () => {
|
|
825
|
-
controller.disabledDaysOfWeekValue = "0,6" // Weekends
|
|
826
|
-
controller.render()
|
|
827
|
-
|
|
828
|
-
// Disabled buttons should not have the click->selectDay action
|
|
829
|
-
const saturdayButton = element.querySelector('[data-date="2024-11-16"]')
|
|
830
|
-
const dataAction = saturdayButton.getAttribute("data-action")
|
|
831
|
-
|
|
832
|
-
expect(dataAction).not.toContain("click->")
|
|
833
|
-
// But should still have focus/blur handlers for keyboard
|
|
834
|
-
expect(dataAction).toContain("focus->")
|
|
835
|
-
expect(dataAction).toContain("blur->")
|
|
836
|
-
})
|
|
837
|
-
|
|
838
|
-
test("enabled buttons have click action for date selection", () => {
|
|
839
|
-
controller.disabledDaysOfWeekValue = "0,6" // Weekends
|
|
840
|
-
controller.render()
|
|
841
|
-
|
|
842
|
-
// Monday should have click action
|
|
843
|
-
const mondayButton = element.querySelector('[data-date="2024-11-18"]')
|
|
844
|
-
const dataAction = mondayButton.getAttribute("data-action")
|
|
845
|
-
|
|
846
|
-
expect(dataAction).toContain("click->")
|
|
847
|
-
})
|
|
848
|
-
|
|
849
|
-
test("clicking a disabled date does not select it", () => {
|
|
850
|
-
controller.disabledDaysOfWeekValue = "0,6"
|
|
851
|
-
controller.render()
|
|
852
|
-
|
|
853
|
-
// Try to select a Saturday
|
|
854
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-16" } } })
|
|
855
|
-
|
|
856
|
-
expect(controller.selectedDate).toBeNull()
|
|
857
|
-
expect(controller.selectedValue).toBe("")
|
|
858
|
-
})
|
|
859
|
-
|
|
860
|
-
test("multiple interactions preserve disabled state", () => {
|
|
861
|
-
controller.disabledDaysOfWeekValue = "0,6" // Weekends
|
|
862
|
-
controller.render()
|
|
863
|
-
|
|
864
|
-
// Select multiple weekday dates in sequence
|
|
865
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-18" } } }) // Monday
|
|
866
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-19" } } }) // Tuesday
|
|
867
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-20" } } }) // Wednesday
|
|
868
|
-
|
|
869
|
-
// Weekend should still be disabled after all interactions
|
|
870
|
-
const saturdayButton = element.querySelector('[data-date="2024-11-16"]')
|
|
871
|
-
const sundayButton = element.querySelector('[data-date="2024-11-17"]')
|
|
872
|
-
|
|
873
|
-
expect(saturdayButton.hasAttribute("disabled")).toBe(true)
|
|
874
|
-
expect(sundayButton.hasAttribute("disabled")).toBe(true)
|
|
875
|
-
})
|
|
876
|
-
|
|
877
|
-
test("navigation preserves disabled state", () => {
|
|
878
|
-
controller.disabledDaysOfWeekValue = "0,6" // Weekends
|
|
879
|
-
controller.render()
|
|
880
|
-
|
|
881
|
-
// Navigate to next month
|
|
882
|
-
controller.nextMonth()
|
|
883
|
-
|
|
884
|
-
// December 2024 - check a Saturday (Dec 7) and Sunday (Dec 8)
|
|
885
|
-
const saturday = element.querySelector('[data-date="2024-12-07"]')
|
|
886
|
-
const sunday = element.querySelector('[data-date="2024-12-08"]')
|
|
887
|
-
|
|
888
|
-
expect(saturday.hasAttribute("disabled")).toBe(true)
|
|
889
|
-
expect(saturday.classList.contains("cursor-not-allowed")).toBe(true)
|
|
890
|
-
expect(sunday.hasAttribute("disabled")).toBe(true)
|
|
891
|
-
expect(sunday.classList.contains("cursor-not-allowed")).toBe(true)
|
|
892
|
-
})
|
|
893
|
-
})
|
|
894
|
-
|
|
895
|
-
describe("showOutsideDays", () => {
|
|
896
|
-
test("renders empty placeholders when showOutsideDays is false", async () => {
|
|
897
|
-
application.stop()
|
|
898
|
-
document.body.innerHTML = ""
|
|
899
|
-
|
|
900
|
-
document.body.innerHTML = `
|
|
901
|
-
<div data-controller="calendar"
|
|
902
|
-
data-calendar-month-value="2024-11-01"
|
|
903
|
-
data-calendar-show-outside-days-value="false">
|
|
904
|
-
<div data-calendar-target="grid"></div>
|
|
905
|
-
</div>
|
|
906
|
-
`
|
|
907
|
-
|
|
908
|
-
application = Application.start()
|
|
909
|
-
application.register("calendar", CalendarController)
|
|
910
|
-
|
|
911
|
-
await new Promise(resolve => requestAnimationFrame(resolve))
|
|
912
|
-
|
|
913
|
-
element = document.querySelector('[data-controller="calendar"]')
|
|
914
|
-
controller = application.getControllerForElementAndIdentifier(element, "calendar")
|
|
915
|
-
|
|
916
|
-
controller.render()
|
|
917
|
-
|
|
918
|
-
const grid = element.querySelector('[data-calendar-target="grid"]')
|
|
919
|
-
|
|
920
|
-
// November 2024 starts on Friday, so first 5 cells should be empty divs
|
|
921
|
-
const emptyDivs = grid.querySelectorAll('div.h-8.w-8:not([data-date])')
|
|
922
|
-
expect(emptyDivs.length).toBeGreaterThan(0)
|
|
923
|
-
|
|
924
|
-
// First day button should be November 1
|
|
925
|
-
const firstButton = grid.querySelector('button[data-date]')
|
|
926
|
-
expect(firstButton.dataset.date).toBe("2024-11-01")
|
|
927
|
-
})
|
|
928
|
-
|
|
929
|
-
test("showOutsideDays persists after month navigation cycle", async () => {
|
|
930
|
-
application.stop()
|
|
931
|
-
document.body.innerHTML = ""
|
|
932
|
-
|
|
933
|
-
document.body.innerHTML = `
|
|
934
|
-
<div data-controller="calendar"
|
|
935
|
-
data-calendar-month-value="2024-11-01"
|
|
936
|
-
data-calendar-show-outside-days-value="false">
|
|
937
|
-
<div data-calendar-target="grid"></div>
|
|
938
|
-
</div>
|
|
939
|
-
`
|
|
940
|
-
|
|
941
|
-
application = Application.start()
|
|
942
|
-
application.register("calendar", CalendarController)
|
|
943
|
-
|
|
944
|
-
await new Promise(resolve => requestAnimationFrame(resolve))
|
|
945
|
-
|
|
946
|
-
element = document.querySelector('[data-controller="calendar"]')
|
|
947
|
-
controller = application.getControllerForElementAndIdentifier(element, "calendar")
|
|
948
|
-
|
|
949
|
-
// Navigate forward to December
|
|
950
|
-
controller.nextMonth()
|
|
951
|
-
|
|
952
|
-
// Verify empty placeholders in December (starts on Sunday, so first day should be Dec 1)
|
|
953
|
-
let grid = element.querySelector('[data-calendar-target="grid"]')
|
|
954
|
-
let firstButton = grid.querySelector('button[data-date]')
|
|
955
|
-
expect(firstButton.dataset.date).toBe("2024-12-01")
|
|
956
|
-
|
|
957
|
-
// Navigate back to November
|
|
958
|
-
controller.previousMonth()
|
|
959
|
-
|
|
960
|
-
// Verify empty placeholders still work in November
|
|
961
|
-
grid = element.querySelector('[data-calendar-target="grid"]')
|
|
962
|
-
firstButton = grid.querySelector('button[data-date]')
|
|
963
|
-
expect(firstButton.dataset.date).toBe("2024-11-01")
|
|
964
|
-
|
|
965
|
-
// Verify empty divs are still present
|
|
966
|
-
const emptyDivs = grid.querySelectorAll('div.h-8.w-8:not([data-date])')
|
|
967
|
-
expect(emptyDivs.length).toBeGreaterThan(0)
|
|
968
|
-
})
|
|
969
|
-
|
|
970
|
-
test("showOutsideDays: true (default) shows outside days", () => {
|
|
971
|
-
controller.showOutsideDaysValue = true
|
|
972
|
-
controller.render()
|
|
973
|
-
|
|
974
|
-
const grid = element.querySelector('[data-calendar-target="grid"]')
|
|
975
|
-
|
|
976
|
-
// November 2024 starts on Friday, so there should be October days before
|
|
977
|
-
const octDates = grid.querySelectorAll('button[data-date^="2024-10"]')
|
|
978
|
-
expect(octDates.length).toBeGreaterThan(0)
|
|
979
|
-
})
|
|
980
|
-
})
|
|
981
|
-
|
|
982
|
-
describe("month navigation with selection and disabled dates", () => {
|
|
983
|
-
test("disabled dates persist after navigating forward then back", () => {
|
|
984
|
-
controller.disabledDaysOfWeekValue = "0,6" // Weekends
|
|
985
|
-
controller.render()
|
|
986
|
-
|
|
987
|
-
// Verify weekends disabled in November
|
|
988
|
-
const novSat = element.querySelector('[data-date="2024-11-16"]')
|
|
989
|
-
expect(novSat.hasAttribute("disabled")).toBe(true)
|
|
990
|
-
|
|
991
|
-
// Navigate to December
|
|
992
|
-
controller.nextMonth()
|
|
993
|
-
|
|
994
|
-
// Verify weekends disabled in December (Dec 7 is Saturday)
|
|
995
|
-
const decSat = element.querySelector('[data-date="2024-12-07"]')
|
|
996
|
-
expect(decSat.hasAttribute("disabled")).toBe(true)
|
|
997
|
-
|
|
998
|
-
// Navigate back to November
|
|
999
|
-
controller.previousMonth()
|
|
1000
|
-
|
|
1001
|
-
// Verify weekends STILL disabled in November
|
|
1002
|
-
const novSatAgain = element.querySelector('[data-date="2024-11-16"]')
|
|
1003
|
-
expect(novSatAgain.hasAttribute("disabled")).toBe(true)
|
|
1004
|
-
expect(novSatAgain.classList.contains("cursor-not-allowed")).toBe(true)
|
|
1005
|
-
})
|
|
1006
|
-
|
|
1007
|
-
test("selection persists across month navigation", () => {
|
|
1008
|
-
// Select a date in November
|
|
1009
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-15" } } })
|
|
1010
|
-
expect(controller.selectedDate.getDate()).toBe(15)
|
|
1011
|
-
|
|
1012
|
-
// Navigate to December
|
|
1013
|
-
controller.nextMonth()
|
|
1014
|
-
expect(controller.currentMonth.getMonth()).toBe(11) // December
|
|
1015
|
-
|
|
1016
|
-
// Selection should still exist
|
|
1017
|
-
expect(controller.selectedDate.getDate()).toBe(15)
|
|
1018
|
-
expect(controller.selectedDate.getMonth()).toBe(10) // November
|
|
1019
|
-
|
|
1020
|
-
// Navigate back to November
|
|
1021
|
-
controller.previousMonth()
|
|
1022
|
-
expect(controller.currentMonth.getMonth()).toBe(10) // November
|
|
1023
|
-
|
|
1024
|
-
// Selected date should be visually marked
|
|
1025
|
-
const selectedButton = element.querySelector('[data-date="2024-11-15"]')
|
|
1026
|
-
expect(selectedButton.classList.contains("bg-primary")).toBe(true)
|
|
1027
|
-
expect(selectedButton.getAttribute("aria-selected")).toBe("true")
|
|
1028
|
-
})
|
|
1029
|
-
|
|
1030
|
-
test("minDate/maxDate persist across year navigation", () => {
|
|
1031
|
-
controller.minDateValue = "2024-06-01"
|
|
1032
|
-
controller.maxDateValue = "2024-12-31"
|
|
1033
|
-
controller.render()
|
|
1034
|
-
|
|
1035
|
-
// Navigate far back to April 2024
|
|
1036
|
-
controller.previousMonth() // October
|
|
1037
|
-
controller.previousMonth() // September
|
|
1038
|
-
controller.previousMonth() // August
|
|
1039
|
-
controller.previousMonth() // July
|
|
1040
|
-
controller.previousMonth() // June
|
|
1041
|
-
controller.previousMonth() // May
|
|
1042
|
-
controller.previousMonth() // April
|
|
1043
|
-
|
|
1044
|
-
// April 2024 should have all dates disabled (before minDate June 1)
|
|
1045
|
-
const aprilDate = element.querySelector('[data-date="2024-04-15"]')
|
|
1046
|
-
expect(aprilDate.hasAttribute("disabled")).toBe(true)
|
|
1047
|
-
expect(aprilDate.classList.contains("cursor-not-allowed")).toBe(true)
|
|
1048
|
-
|
|
1049
|
-
// Navigate forward to January 2025 (9 months from April 2024)
|
|
1050
|
-
for (let i = 0; i < 9; i++) {
|
|
1051
|
-
controller.nextMonth()
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
// January 2025 should have dates disabled (after maxDate Dec 31, 2024)
|
|
1055
|
-
const janDate = element.querySelector('[data-date="2025-01-15"]')
|
|
1056
|
-
expect(janDate).not.toBeNull()
|
|
1057
|
-
expect(janDate.hasAttribute("disabled")).toBe(true)
|
|
1058
|
-
expect(janDate.classList.contains("cursor-not-allowed")).toBe(true)
|
|
1059
|
-
})
|
|
1060
|
-
|
|
1061
|
-
test("combined interaction: select date, navigate, select another, navigate back", () => {
|
|
1062
|
-
controller.disabledDaysOfWeekValue = "0,6"
|
|
1063
|
-
controller.render()
|
|
1064
|
-
|
|
1065
|
-
// Select Monday Nov 18
|
|
1066
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-18" } } })
|
|
1067
|
-
expect(controller.selectedDate.getDate()).toBe(18)
|
|
1068
|
-
|
|
1069
|
-
// Navigate to December
|
|
1070
|
-
controller.nextMonth()
|
|
1071
|
-
|
|
1072
|
-
// Weekends should still be disabled
|
|
1073
|
-
const decSat = element.querySelector('[data-date="2024-12-07"]')
|
|
1074
|
-
expect(decSat.hasAttribute("disabled")).toBe(true)
|
|
1075
|
-
|
|
1076
|
-
// Select a Wednesday in December (Dec 11)
|
|
1077
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-12-11" } } })
|
|
1078
|
-
expect(controller.selectedDate.getDate()).toBe(11)
|
|
1079
|
-
expect(controller.selectedDate.getMonth()).toBe(11) // December
|
|
1080
|
-
|
|
1081
|
-
// Navigate back to November
|
|
1082
|
-
controller.previousMonth()
|
|
1083
|
-
|
|
1084
|
-
// Old selection should no longer be marked (we selected Dec 11)
|
|
1085
|
-
const novDate = element.querySelector('[data-date="2024-11-18"]')
|
|
1086
|
-
expect(novDate.classList.contains("bg-primary")).toBe(false)
|
|
1087
|
-
|
|
1088
|
-
// Weekends should still be disabled
|
|
1089
|
-
const novSat = element.querySelector('[data-date="2024-11-16"]')
|
|
1090
|
-
expect(novSat.hasAttribute("disabled")).toBe(true)
|
|
1091
|
-
})
|
|
1092
|
-
})
|
|
1093
|
-
|
|
1094
|
-
describe("weekStartsOn", () => {
|
|
1095
|
-
test("default weekStartsOn is 0 (Sunday)", () => {
|
|
1096
|
-
expect(controller.weekStartsOnValue).toBe(0)
|
|
1097
|
-
})
|
|
1098
|
-
|
|
1099
|
-
test("renders grid starting from Sunday by default", () => {
|
|
1100
|
-
controller.render()
|
|
1101
|
-
|
|
1102
|
-
const grid = element.querySelector('[data-calendar-target="grid"]')
|
|
1103
|
-
const allDays = grid.querySelectorAll("button[data-date]")
|
|
1104
|
-
|
|
1105
|
-
// November 2024 starts on Friday, with Sunday start the first day should be Oct 27
|
|
1106
|
-
expect(allDays[0].dataset.date).toBe("2024-10-27")
|
|
1107
|
-
})
|
|
1108
|
-
|
|
1109
|
-
test("weekStartsOn=1 starts grid from Monday", async () => {
|
|
1110
|
-
application.stop()
|
|
1111
|
-
document.body.innerHTML = ""
|
|
1112
|
-
|
|
1113
|
-
document.body.innerHTML = `
|
|
1114
|
-
<div data-controller="calendar"
|
|
1115
|
-
data-calendar-month-value="2024-11-01"
|
|
1116
|
-
data-calendar-week-starts-on-value="1">
|
|
1117
|
-
<div data-calendar-target="grid"></div>
|
|
1118
|
-
</div>
|
|
1119
|
-
`
|
|
1120
|
-
|
|
1121
|
-
application = Application.start()
|
|
1122
|
-
application.register("calendar", CalendarController)
|
|
1123
|
-
|
|
1124
|
-
await new Promise(resolve => requestAnimationFrame(resolve))
|
|
1125
|
-
|
|
1126
|
-
element = document.querySelector('[data-controller="calendar"]')
|
|
1127
|
-
controller = application.getControllerForElementAndIdentifier(element, "calendar")
|
|
1128
|
-
|
|
1129
|
-
controller.render()
|
|
1130
|
-
|
|
1131
|
-
const grid = element.querySelector('[data-calendar-target="grid"]')
|
|
1132
|
-
const allDays = grid.querySelectorAll("button[data-date]")
|
|
1133
|
-
|
|
1134
|
-
// November 2024 starts on Friday, with Monday start the first day should be Oct 28
|
|
1135
|
-
expect(allDays[0].dataset.date).toBe("2024-10-28")
|
|
1136
|
-
})
|
|
1137
|
-
|
|
1138
|
-
test("weekStartsOn=1 correctly positions November 1st", async () => {
|
|
1139
|
-
application.stop()
|
|
1140
|
-
document.body.innerHTML = ""
|
|
1141
|
-
|
|
1142
|
-
document.body.innerHTML = `
|
|
1143
|
-
<div data-controller="calendar"
|
|
1144
|
-
data-calendar-month-value="2024-11-01"
|
|
1145
|
-
data-calendar-week-starts-on-value="1">
|
|
1146
|
-
<div data-calendar-target="grid"></div>
|
|
1147
|
-
</div>
|
|
1148
|
-
`
|
|
1149
|
-
|
|
1150
|
-
application = Application.start()
|
|
1151
|
-
application.register("calendar", CalendarController)
|
|
1152
|
-
|
|
1153
|
-
await new Promise(resolve => requestAnimationFrame(resolve))
|
|
1154
|
-
|
|
1155
|
-
element = document.querySelector('[data-controller="calendar"]')
|
|
1156
|
-
controller = application.getControllerForElementAndIdentifier(element, "calendar")
|
|
1157
|
-
|
|
1158
|
-
controller.render()
|
|
1159
|
-
|
|
1160
|
-
const grid = element.querySelector('[data-calendar-target="grid"]')
|
|
1161
|
-
const allDays = grid.querySelectorAll("button[data-date]")
|
|
1162
|
-
|
|
1163
|
-
// November 1, 2024 is Friday
|
|
1164
|
-
// With Monday start: Mon=0, Tue=1, Wed=2, Thu=3, Fri=4
|
|
1165
|
-
// First row: Oct 28 (Mon), Oct 29 (Tue), Oct 30 (Wed), Oct 31 (Thu), Nov 1 (Fri), Nov 2 (Sat), Nov 3 (Sun)
|
|
1166
|
-
expect(allDays[4].dataset.date).toBe("2024-11-01")
|
|
1167
|
-
})
|
|
1168
|
-
|
|
1169
|
-
test("weekStartsOn=1 December 2024 starts correctly", async () => {
|
|
1170
|
-
application.stop()
|
|
1171
|
-
document.body.innerHTML = ""
|
|
1172
|
-
|
|
1173
|
-
document.body.innerHTML = `
|
|
1174
|
-
<div data-controller="calendar"
|
|
1175
|
-
data-calendar-month-value="2024-12-01"
|
|
1176
|
-
data-calendar-week-starts-on-value="1">
|
|
1177
|
-
<div data-calendar-target="grid"></div>
|
|
1178
|
-
</div>
|
|
1179
|
-
`
|
|
1180
|
-
|
|
1181
|
-
application = Application.start()
|
|
1182
|
-
application.register("calendar", CalendarController)
|
|
1183
|
-
|
|
1184
|
-
await new Promise(resolve => requestAnimationFrame(resolve))
|
|
1185
|
-
|
|
1186
|
-
element = document.querySelector('[data-controller="calendar"]')
|
|
1187
|
-
controller = application.getControllerForElementAndIdentifier(element, "calendar")
|
|
1188
|
-
|
|
1189
|
-
controller.render()
|
|
1190
|
-
|
|
1191
|
-
const grid = element.querySelector('[data-calendar-target="grid"]')
|
|
1192
|
-
const allDays = grid.querySelectorAll("button[data-date]")
|
|
1193
|
-
|
|
1194
|
-
// December 1, 2024 is Sunday
|
|
1195
|
-
// With Monday start, first day should be Nov 25 (Monday)
|
|
1196
|
-
expect(allDays[0].dataset.date).toBe("2024-11-25")
|
|
1197
|
-
|
|
1198
|
-
// December 1 should be at position 6 (Sunday = last day of week when starting Monday)
|
|
1199
|
-
expect(allDays[6].dataset.date).toBe("2024-12-01")
|
|
1200
|
-
})
|
|
1201
|
-
|
|
1202
|
-
test("weekStartsOn persists after month navigation", async () => {
|
|
1203
|
-
application.stop()
|
|
1204
|
-
document.body.innerHTML = ""
|
|
1205
|
-
|
|
1206
|
-
document.body.innerHTML = `
|
|
1207
|
-
<div data-controller="calendar"
|
|
1208
|
-
data-calendar-month-value="2024-11-01"
|
|
1209
|
-
data-calendar-week-starts-on-value="1">
|
|
1210
|
-
<div data-calendar-target="grid"></div>
|
|
1211
|
-
</div>
|
|
1212
|
-
`
|
|
1213
|
-
|
|
1214
|
-
application = Application.start()
|
|
1215
|
-
application.register("calendar", CalendarController)
|
|
1216
|
-
|
|
1217
|
-
await new Promise(resolve => requestAnimationFrame(resolve))
|
|
1218
|
-
|
|
1219
|
-
element = document.querySelector('[data-controller="calendar"]')
|
|
1220
|
-
controller = application.getControllerForElementAndIdentifier(element, "calendar")
|
|
1221
|
-
|
|
1222
|
-
// Navigate to December
|
|
1223
|
-
controller.nextMonth()
|
|
1224
|
-
|
|
1225
|
-
const grid = element.querySelector('[data-calendar-target="grid"]')
|
|
1226
|
-
const allDays = grid.querySelectorAll("button[data-date]")
|
|
1227
|
-
|
|
1228
|
-
// Should still start from Monday (Nov 25)
|
|
1229
|
-
expect(allDays[0].dataset.date).toBe("2024-11-25")
|
|
1230
|
-
|
|
1231
|
-
// Navigate back to November
|
|
1232
|
-
controller.previousMonth()
|
|
1233
|
-
|
|
1234
|
-
const gridAfter = element.querySelector('[data-calendar-target="grid"]')
|
|
1235
|
-
const allDaysAfter = gridAfter.querySelectorAll("button[data-date]")
|
|
1236
|
-
|
|
1237
|
-
// Should still start from Monday (Oct 28)
|
|
1238
|
-
expect(allDaysAfter[0].dataset.date).toBe("2024-10-28")
|
|
1239
|
-
})
|
|
1240
|
-
})
|
|
1241
|
-
|
|
1242
|
-
describe("month navigation", () => {
|
|
1243
|
-
test("navigating to next month preserves selection", () => {
|
|
1244
|
-
// Select a date
|
|
1245
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-15" } } })
|
|
1246
|
-
|
|
1247
|
-
// Navigate to December
|
|
1248
|
-
controller.nextMonth()
|
|
1249
|
-
|
|
1250
|
-
// Selection should still exist
|
|
1251
|
-
expect(controller.selectedDate.getDate()).toBe(15)
|
|
1252
|
-
expect(controller.selectedDate.getMonth()).toBe(10) // November
|
|
1253
|
-
})
|
|
1254
|
-
|
|
1255
|
-
test("navigating back to previous month shows selection", () => {
|
|
1256
|
-
// Select a date
|
|
1257
|
-
controller.selectDay({ currentTarget: { dataset: { date: "2024-11-15" } } })
|
|
1258
|
-
|
|
1259
|
-
// Navigate to December and back
|
|
1260
|
-
controller.nextMonth()
|
|
1261
|
-
controller.previousMonth()
|
|
1262
|
-
|
|
1263
|
-
// Check selection is visible
|
|
1264
|
-
const selectedButton = element.querySelector('[data-date="2024-11-15"]')
|
|
1265
|
-
expect(selectedButton.classList.contains("bg-primary")).toBe(true)
|
|
1266
|
-
})
|
|
1267
|
-
|
|
1268
|
-
test("selectMonth changes month", () => {
|
|
1269
|
-
// Select June (index 5)
|
|
1270
|
-
controller.selectMonth({ target: { value: "5" } })
|
|
1271
|
-
|
|
1272
|
-
expect(controller.currentMonth.getMonth()).toBe(5) // June
|
|
1273
|
-
})
|
|
1274
|
-
|
|
1275
|
-
test("selectYear changes year", () => {
|
|
1276
|
-
// Select 2025
|
|
1277
|
-
controller.selectYear({ target: { value: "2025" } })
|
|
1278
|
-
|
|
1279
|
-
expect(controller.currentMonth.getFullYear()).toBe(2025)
|
|
1280
|
-
})
|
|
1281
|
-
|
|
1282
|
-
test("navigating through multiple months maintains state", () => {
|
|
1283
|
-
controller.disabledDaysOfWeekValue = "0,6"
|
|
1284
|
-
|
|
1285
|
-
// Navigate forward several months
|
|
1286
|
-
for (let i = 0; i < 6; i++) {
|
|
1287
|
-
controller.nextMonth()
|
|
1288
|
-
}
|
|
1289
|
-
|
|
1290
|
-
// May 2025
|
|
1291
|
-
expect(controller.currentMonth.getMonth()).toBe(4)
|
|
1292
|
-
expect(controller.currentMonth.getFullYear()).toBe(2025)
|
|
1293
|
-
|
|
1294
|
-
// Navigate back
|
|
1295
|
-
for (let i = 0; i < 6; i++) {
|
|
1296
|
-
controller.previousMonth()
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
// Back to November 2024
|
|
1300
|
-
expect(controller.currentMonth.getMonth()).toBe(10)
|
|
1301
|
-
expect(controller.currentMonth.getFullYear()).toBe(2024)
|
|
1302
|
-
|
|
1303
|
-
// Disabled weekends should still work
|
|
1304
|
-
const saturday = element.querySelector('[data-date="2024-11-16"]')
|
|
1305
|
-
expect(saturday.hasAttribute("disabled")).toBe(true)
|
|
1306
|
-
})
|
|
1307
|
-
})
|
|
1308
|
-
|
|
1309
|
-
describe("range mode CSS rendering", () => {
|
|
1310
|
-
beforeEach(async () => {
|
|
1311
|
-
application.stop()
|
|
1312
|
-
document.body.innerHTML = ""
|
|
1313
|
-
|
|
1314
|
-
document.body.innerHTML = `
|
|
1315
|
-
<div data-controller="calendar"
|
|
1316
|
-
data-calendar-month-value="2024-11-01"
|
|
1317
|
-
data-calendar-mode-value="range"
|
|
1318
|
-
data-calendar-selected-value="">
|
|
1319
|
-
<div data-calendar-target="grid"></div>
|
|
1320
|
-
<input type="hidden" data-calendar-target="hiddenInput">
|
|
1321
|
-
</div>
|
|
1322
|
-
`
|
|
1323
|
-
|
|
1324
|
-
application = Application.start()
|
|
1325
|
-
application.register("calendar", CalendarController)
|
|
1326
|
-
|
|
1327
|
-
await new Promise(resolve => requestAnimationFrame(resolve))
|
|
1328
|
-
|
|
1329
|
-
element = document.querySelector('[data-controller="calendar"]')
|
|
1330
|
-
controller = application.getControllerForElementAndIdentifier(element, "calendar")
|
|
1331
|
-
})
|
|
1332
|
-
|
|
1333
|
-
test("range start has rounded-l-md class", () => {
|
|
1334
|
-
controller.rangeStart = new Date(2024, 10, 10)
|
|
1335
|
-
controller.rangeEnd = new Date(2024, 10, 15)
|
|
1336
|
-
controller.render()
|
|
1337
|
-
|
|
1338
|
-
const startButton = element.querySelector('[data-date="2024-11-10"]')
|
|
1339
|
-
expect(startButton.classList.contains("rounded-l-md")).toBe(true)
|
|
1340
|
-
})
|
|
1341
|
-
|
|
1342
|
-
test("range end has rounded-r-md class", () => {
|
|
1343
|
-
controller.rangeStart = new Date(2024, 10, 10)
|
|
1344
|
-
controller.rangeEnd = new Date(2024, 10, 15)
|
|
1345
|
-
controller.render()
|
|
1346
|
-
|
|
1347
|
-
const endButton = element.querySelector('[data-date="2024-11-15"]')
|
|
1348
|
-
expect(endButton.classList.contains("rounded-r-md")).toBe(true)
|
|
1349
|
-
})
|
|
1350
|
-
|
|
1351
|
-
test("dates in range have accent background", () => {
|
|
1352
|
-
controller.rangeStart = new Date(2024, 10, 10)
|
|
1353
|
-
controller.rangeEnd = new Date(2024, 10, 15)
|
|
1354
|
-
controller.render()
|
|
1355
|
-
|
|
1356
|
-
// Nov 12 is in the middle of the range
|
|
1357
|
-
const middleButton = element.querySelector('[data-date="2024-11-12"]')
|
|
1358
|
-
expect(middleButton.classList.contains("bg-accent/50")).toBe(true)
|
|
1359
|
-
})
|
|
1360
|
-
|
|
1361
|
-
test("range mode snapshot", () => {
|
|
1362
|
-
controller.rangeStart = new Date(2024, 10, 10)
|
|
1363
|
-
controller.rangeEnd = new Date(2024, 10, 15)
|
|
1364
|
-
controller.render()
|
|
1365
|
-
|
|
1366
|
-
const grid = element.querySelector('[data-calendar-target="grid"]')
|
|
1367
|
-
expect(grid.innerHTML).toMatchSnapshot()
|
|
1368
|
-
})
|
|
1369
|
-
})
|
|
1370
|
-
})
|