@balby/booking-search 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +254 -0
- package/dist/index.css +1668 -0
- package/dist/index.js +12681 -0
- package/package.json +84 -0
- package/src/components/booking-search/date-range-picker.tsx +240 -0
- package/src/components/booking-search/guest-selector.tsx +181 -0
- package/src/components/booking-search/index.tsx +199 -0
- package/src/components/booking-search/location-combobox.tsx +93 -0
- package/src/components/booking-search/tests/date-range-picker.test.tsx +308 -0
- package/src/components/booking-search/tests/guest-selector.test.tsx +358 -0
- package/src/components/booking-search/tests/index.test.tsx +424 -0
- package/src/components/booking-search/tests/location-combobox.test.tsx +263 -0
- package/src/components/booking-search/ui/command.tsx +100 -0
- package/src/components/booking-search/ui/dialog.tsx +88 -0
- package/src/components/booking-search/ui/popover.tsx +28 -0
- package/src/demo.tsx +198 -0
- package/src/index.ts +27 -0
- package/src/lib/utils.ts +6 -0
- package/src/styles/globals.css +158 -0
- package/src/styles/output.css +4 -0
- package/src/styles/output.css.map +1 -0
- package/src/types/booking.ts +155 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import { test, expect, describe, beforeEach } from "bun:test"
|
|
2
|
+
import React from "react"
|
|
3
|
+
import { render, screen, fireEvent, waitFor } from "@testing-library/react"
|
|
4
|
+
import { GuestSelector } from "../guest-selector"
|
|
5
|
+
import type { GuestData } from "../../../types/booking"
|
|
6
|
+
|
|
7
|
+
describe("GuestSelector", () => {
|
|
8
|
+
const mockValue: GuestData = {
|
|
9
|
+
adults: 2,
|
|
10
|
+
children: 0,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const mockOnChange = (guests: GuestData) => {}
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
// Reset any state between tests if needed
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test("renders with default guest count", () => {
|
|
20
|
+
render(<GuestSelector value={mockValue} onChange={mockOnChange} />)
|
|
21
|
+
|
|
22
|
+
expect(screen.getByText("Guests")).toBeDefined()
|
|
23
|
+
expect(screen.getByText("2 adulti")).toBeDefined()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test("displays singular form for one adult", () => {
|
|
27
|
+
render(
|
|
28
|
+
<GuestSelector
|
|
29
|
+
value={{ adults: 1, children: 0 }}
|
|
30
|
+
onChange={mockOnChange}
|
|
31
|
+
/>
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
expect(screen.getByText("1 adulto")).toBeDefined()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test("displays children count when children > 0", () => {
|
|
38
|
+
render(
|
|
39
|
+
<GuestSelector
|
|
40
|
+
value={{ adults: 2, children: 1 }}
|
|
41
|
+
onChange={mockOnChange}
|
|
42
|
+
/>
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
expect(screen.getByText("2 adulti, 1 bambino")).toBeDefined()
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test("displays plural form for multiple children", () => {
|
|
49
|
+
render(
|
|
50
|
+
<GuestSelector
|
|
51
|
+
value={{ adults: 2, children: 3 }}
|
|
52
|
+
onChange={mockOnChange}
|
|
53
|
+
/>
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
expect(screen.getByText("2 adulti, 3 bambini")).toBeDefined()
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test("opens popover when button is clicked", async () => {
|
|
60
|
+
render(<GuestSelector value={mockValue} onChange={mockOnChange} />)
|
|
61
|
+
|
|
62
|
+
const button = screen.getByRole("button", { name: "Select guests" })
|
|
63
|
+
fireEvent.click(button)
|
|
64
|
+
|
|
65
|
+
await waitFor(() => {
|
|
66
|
+
expect(screen.getByText("Adulti")).toBeDefined()
|
|
67
|
+
expect(screen.getByText("Bambini")).toBeDefined()
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test("displays guest steppers in popover", async () => {
|
|
72
|
+
render(<GuestSelector value={mockValue} onChange={mockOnChange} />)
|
|
73
|
+
|
|
74
|
+
const button = screen.getByRole("button", { name: "Select guests" })
|
|
75
|
+
fireEvent.click(button)
|
|
76
|
+
|
|
77
|
+
await waitFor(() => {
|
|
78
|
+
expect(screen.getByText("Adulti")).toBeDefined()
|
|
79
|
+
expect(screen.getByText("Età 18+")).toBeDefined()
|
|
80
|
+
expect(screen.getByText("Bambini")).toBeDefined()
|
|
81
|
+
expect(screen.getByText("Età 0-17")).toBeDefined()
|
|
82
|
+
})
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test("increments adults when plus button is clicked", async () => {
|
|
86
|
+
let currentValue = { adults: 2, children: 0 }
|
|
87
|
+
const handleChange = (guests: GuestData) => {
|
|
88
|
+
currentValue = guests
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
render(<GuestSelector value={currentValue} onChange={handleChange} />)
|
|
92
|
+
|
|
93
|
+
const button = screen.getByRole("button", { name: "Select guests" })
|
|
94
|
+
fireEvent.click(button)
|
|
95
|
+
|
|
96
|
+
await waitFor(() => {
|
|
97
|
+
const incrementButton = screen.getByLabelText("Increase adulti")
|
|
98
|
+
fireEvent.click(incrementButton)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
const confirmButton = screen.getByText("Conferma")
|
|
102
|
+
fireEvent.click(confirmButton)
|
|
103
|
+
|
|
104
|
+
await waitFor(() => {
|
|
105
|
+
expect(currentValue.adults).toBe(3)
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test("decrements adults when minus button is clicked", async () => {
|
|
110
|
+
let currentValue = { adults: 2, children: 0 }
|
|
111
|
+
const handleChange = (guests: GuestData) => {
|
|
112
|
+
currentValue = guests
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
render(<GuestSelector value={currentValue} onChange={handleChange} />)
|
|
116
|
+
|
|
117
|
+
const button = screen.getByRole("button", { name: "Select guests" })
|
|
118
|
+
fireEvent.click(button)
|
|
119
|
+
|
|
120
|
+
await waitFor(() => {
|
|
121
|
+
const decrementButton = screen.getByLabelText("Decrease adulti")
|
|
122
|
+
fireEvent.click(decrementButton)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
const confirmButton = screen.getByText("Conferma")
|
|
126
|
+
fireEvent.click(confirmButton)
|
|
127
|
+
|
|
128
|
+
await waitFor(() => {
|
|
129
|
+
expect(currentValue.adults).toBe(1)
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
test("increments children when plus button is clicked", async () => {
|
|
134
|
+
let currentValue = { adults: 2, children: 0 }
|
|
135
|
+
const handleChange = (guests: GuestData) => {
|
|
136
|
+
currentValue = guests
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
render(<GuestSelector value={currentValue} onChange={handleChange} />)
|
|
140
|
+
|
|
141
|
+
const button = screen.getByRole("button", { name: "Select guests" })
|
|
142
|
+
fireEvent.click(button)
|
|
143
|
+
|
|
144
|
+
await waitFor(() => {
|
|
145
|
+
const incrementButton = screen.getByLabelText("Increase bambini")
|
|
146
|
+
fireEvent.click(incrementButton)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
const confirmButton = screen.getByText("Conferma")
|
|
150
|
+
fireEvent.click(confirmButton)
|
|
151
|
+
|
|
152
|
+
await waitFor(() => {
|
|
153
|
+
expect(currentValue.children).toBe(1)
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
test("decrements children when minus button is clicked", async () => {
|
|
158
|
+
let currentValue = { adults: 2, children: 2 }
|
|
159
|
+
const handleChange = (guests: GuestData) => {
|
|
160
|
+
currentValue = guests
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
render(<GuestSelector value={currentValue} onChange={handleChange} />)
|
|
164
|
+
|
|
165
|
+
const button = screen.getByRole("button", { name: "Select guests" })
|
|
166
|
+
fireEvent.click(button)
|
|
167
|
+
|
|
168
|
+
await waitFor(() => {
|
|
169
|
+
const decrementButton = screen.getByLabelText("Decrease bambini")
|
|
170
|
+
fireEvent.click(decrementButton)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
const confirmButton = screen.getByText("Conferma")
|
|
174
|
+
fireEvent.click(confirmButton)
|
|
175
|
+
|
|
176
|
+
await waitFor(() => {
|
|
177
|
+
expect(currentValue.children).toBe(1)
|
|
178
|
+
})
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
test("prevents decrementing adults below 1", async () => {
|
|
182
|
+
render(
|
|
183
|
+
<GuestSelector
|
|
184
|
+
value={{ adults: 1, children: 0 }}
|
|
185
|
+
onChange={mockOnChange}
|
|
186
|
+
/>
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
const button = screen.getByRole("button", { name: "Select guests" })
|
|
190
|
+
fireEvent.click(button)
|
|
191
|
+
|
|
192
|
+
await waitFor(() => {
|
|
193
|
+
const decrementButton = screen.getByLabelText("Decrease adulti")
|
|
194
|
+
expect(decrementButton.hasAttribute("disabled")).toBe(true)
|
|
195
|
+
})
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
test("prevents decrementing children below 0", async () => {
|
|
199
|
+
render(
|
|
200
|
+
<GuestSelector
|
|
201
|
+
value={{ adults: 2, children: 0 }}
|
|
202
|
+
onChange={mockOnChange}
|
|
203
|
+
/>
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
const button = screen.getByRole("button", { name: "Select guests" })
|
|
207
|
+
fireEvent.click(button)
|
|
208
|
+
|
|
209
|
+
await waitFor(() => {
|
|
210
|
+
const decrementButton = screen.getByLabelText("Decrease bambini")
|
|
211
|
+
expect(decrementButton.hasAttribute("disabled")).toBe(true)
|
|
212
|
+
})
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
test("prevents incrementing adults above maxAdults", async () => {
|
|
216
|
+
render(
|
|
217
|
+
<GuestSelector
|
|
218
|
+
value={{ adults: 5, children: 0 }}
|
|
219
|
+
onChange={mockOnChange}
|
|
220
|
+
maxAdults={5}
|
|
221
|
+
/>
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
const button = screen.getByRole("button", { name: "Select guests" })
|
|
225
|
+
fireEvent.click(button)
|
|
226
|
+
|
|
227
|
+
await waitFor(() => {
|
|
228
|
+
const incrementButton = screen.getByLabelText("Increase adulti")
|
|
229
|
+
expect(incrementButton.hasAttribute("disabled")).toBe(true)
|
|
230
|
+
})
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
test("prevents incrementing children above maxChildren", async () => {
|
|
234
|
+
render(
|
|
235
|
+
<GuestSelector
|
|
236
|
+
value={{ adults: 2, children: 3 }}
|
|
237
|
+
onChange={mockOnChange}
|
|
238
|
+
maxChildren={3}
|
|
239
|
+
/>
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
const button = screen.getByRole("button", { name: "Select guests" })
|
|
243
|
+
fireEvent.click(button)
|
|
244
|
+
|
|
245
|
+
await waitFor(() => {
|
|
246
|
+
const incrementButton = screen.getByLabelText("Increase bambini")
|
|
247
|
+
expect(incrementButton.hasAttribute("disabled")).toBe(true)
|
|
248
|
+
})
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
test("does not call onChange until confirm is clicked", async () => {
|
|
252
|
+
let changeCallCount = 0
|
|
253
|
+
const handleChange = (guests: GuestData) => {
|
|
254
|
+
changeCallCount++
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
render(
|
|
258
|
+
<GuestSelector
|
|
259
|
+
value={{ adults: 2, children: 0 }}
|
|
260
|
+
onChange={handleChange}
|
|
261
|
+
/>
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
const button = screen.getByRole("button", { name: "Select guests" })
|
|
265
|
+
fireEvent.click(button)
|
|
266
|
+
|
|
267
|
+
await waitFor(() => {
|
|
268
|
+
const incrementButton = screen.getByLabelText("Increase adulti")
|
|
269
|
+
fireEvent.click(incrementButton)
|
|
270
|
+
fireEvent.click(incrementButton)
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
// onChange should not have been called yet
|
|
274
|
+
expect(changeCallCount).toBe(0)
|
|
275
|
+
|
|
276
|
+
const confirmButton = screen.getByText("Conferma")
|
|
277
|
+
fireEvent.click(confirmButton)
|
|
278
|
+
|
|
279
|
+
// Now onChange should be called
|
|
280
|
+
await waitFor(() => {
|
|
281
|
+
expect(changeCallCount).toBe(1)
|
|
282
|
+
})
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
test("resets to original value when popover is closed without confirming", async () => {
|
|
286
|
+
const { rerender } = render(
|
|
287
|
+
<GuestSelector
|
|
288
|
+
value={{ adults: 2, children: 0 }}
|
|
289
|
+
onChange={mockOnChange}
|
|
290
|
+
/>
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
const button = screen.getByRole("button", { name: "Select guests" })
|
|
294
|
+
fireEvent.click(button)
|
|
295
|
+
|
|
296
|
+
await waitFor(() => {
|
|
297
|
+
const incrementButton = screen.getByLabelText("Increase adulti")
|
|
298
|
+
fireEvent.click(incrementButton)
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
// Close popover without confirming (click outside)
|
|
302
|
+
fireEvent.keyDown(document, { key: "Escape" })
|
|
303
|
+
|
|
304
|
+
// Re-open and verify value is still 2
|
|
305
|
+
await waitFor(() => {
|
|
306
|
+
fireEvent.click(button)
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
await waitFor(() => {
|
|
310
|
+
const adultsValue = screen.getAllByText("2")[0]
|
|
311
|
+
expect(adultsValue).toBeDefined()
|
|
312
|
+
})
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
test("disables the button when disabled prop is true", () => {
|
|
316
|
+
render(
|
|
317
|
+
<GuestSelector
|
|
318
|
+
value={mockValue}
|
|
319
|
+
onChange={mockOnChange}
|
|
320
|
+
disabled={true}
|
|
321
|
+
/>
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
const button = screen.getByRole("button", { name: "Select guests" })
|
|
325
|
+
expect(button.hasAttribute("disabled")).toBe(true)
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
test("applies custom className", () => {
|
|
329
|
+
const { container } = render(
|
|
330
|
+
<GuestSelector
|
|
331
|
+
value={mockValue}
|
|
332
|
+
onChange={mockOnChange}
|
|
333
|
+
className="custom-class"
|
|
334
|
+
/>
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
const button = container.querySelector("button")
|
|
338
|
+
expect(button?.classList.contains("custom-class")).toBe(true)
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
test("closes popover after confirming selection", async () => {
|
|
342
|
+
render(<GuestSelector value={mockValue} onChange={mockOnChange} />)
|
|
343
|
+
|
|
344
|
+
const button = screen.getByRole("button", { name: "Select guests" })
|
|
345
|
+
fireEvent.click(button)
|
|
346
|
+
|
|
347
|
+
await waitFor(() => {
|
|
348
|
+
expect(screen.getByText("Conferma")).toBeDefined()
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
const confirmButton = screen.getByText("Conferma")
|
|
352
|
+
fireEvent.click(confirmButton)
|
|
353
|
+
|
|
354
|
+
await waitFor(() => {
|
|
355
|
+
expect(screen.queryByText("Conferma")).toBeNull()
|
|
356
|
+
})
|
|
357
|
+
})
|
|
358
|
+
})
|