Repository: melt-ui/melt-ui (TypeScript)

Issue:
🐛Bug: Calendar: navigating with keyboard makes it possible to access disabled months
**Describe the bug**
Calendar: navigating with keyboard makes it possible to access disabled months

**To Reproduce**
Steps to reproduce the behavior:
1. Go to the [calendar documentation](https://www.melt-ui.com/docs/builders/calendar)
2. Click on Minimum & Maximum Values
3. In the example, focus any date. Notice how it's not possible to paginate to March since a max value is set.
4. Using the keyboard, press right arrow key until you do end up in March anyways, where there are no available dates to select.

**Expected behaviour**
Navigating to disabled months outside the min/max values should not be allowed

**Screenshots**
If applicable, add screenshots to help explain your problem.
<img width="935" alt="Screenshot 2023-11-16 at 19 35 54 1" src="https://github.com/melt-ui/melt-ui/assets/31876997/5e746c87-bcb0-445a-934b-145950577e80">

**Desktop (please complete the following information):**
 - OS: Mac OS
 - Browser Chrome and Safari


Discussion:
Yep, definitely an issue! Will investigate as soon as I can, if anyone else wants to take a look feel free! We should also have a test case for this scenario.
I could try to work on this myself today! I'll let you know if I get stuck
I'm not sure this is entirely unexpected to be honest. Otherwise people might think the calendar is broken, when in fact you only have some disabled dates.
I think it’s fair to assume that we should be consistent with the disabling of the next/prev month buttons though?

if those buttons are disabled, then it should also not be possible to circumvent this with the keyboard?
a keyboard user also gets stuck if this happens as there is no way to navigate back with the arrow keys
Hmm true! That's fair.
using the prev/next buttons as a source of truth, it was easy to disable navigation with the keyboard.

however, I'm having some troubles with adding a test for this, see #748. maybe I'm missing something obvious 



Patch:
diff --git a/.changeset/eighty-cats-check.md b/.changeset/eighty-cats-check.md
new file mode 100644
index 0000000000..46a248a5e0
--- /dev/null
+++ b/.changeset/eighty-cats-check.md
@@ -0,0 +1,5 @@
+---
+"@melt-ui/svelte": patch
+---
+
+Calendar: Fix bug where keyboard navigation would allow navigating to disabled months (closes #745)
diff --git a/src/lib/builders/calendar/create.ts b/src/lib/builders/calendar/create.ts
index c3e23ce84b..a82f179b2b 100644
--- a/src/lib/builders/calendar/create.ts
+++ b/src/lib/builders/calendar/create.ts
@@ -954,7 +954,8 @@ import { generateIds } from '../../internal/helpers/id'
 			 * of the array.
 			 */
 
-			// shift the calendar back a month
+			// shift the calendar back a month unless previous month is disabled
+			if (get(isPrevButtonDisabled)) return;
 
 			const $months = get(months);
 			const firstMonth = $months[0].value;
@@ -992,6 +993,9 @@ import { generateIds } from '../../internal/helpers/id'
 			 * shift focus by the nextIndex amount.
 			 */
 
+			// shift the calendar forward a month unless next month is disabled
+			if (get(isNextButtonDisabled)) return;
+
 			const $months = get(months);
 			const firstMonth = $months[0].value;
 			const $numberOfMonths = get(numberOfMonths);
diff --git a/src/lib/builders/range-calendar/create.ts b/src/lib/builders/range-calendar/create.ts
index 04e9af0ea4..a4071d08e7 100644
--- a/src/lib/builders/range-calendar/create.ts
+++ b/src/lib/builders/range-calendar/create.ts
@@ -867,7 +867,9 @@ import { generateIds } from '../../internal/helpers/id'
 			 * focus by the difference between the nextIndex
 			 */
 
-			// shift the calendar back a month
+			// shift the calendar back a month unless previous month is disabled
+			if (get(isPrevButtonDisabled)) return;
+
 			const $months = get(months);
 			const firstMonth = $months[0].value;
 			const $numberOfMonths = get(numberOfMonths);
@@ -901,7 +903,9 @@ import { generateIds } from '../../internal/helpers/id'
 			 * shift focus by the nextIndex amount.
 			 */
 
-			// shift the calendar forward a month
+			// shift the calendar forward a month unless next month is disabled
+			if (get(isNextButtonDisabled)) return;
+
 			const $months = get(months);
 			const firstMonth = $months[0].value;
 			const $numberOfMonths = get(numberOfMonths);


Test patch:
diff --git a/src/tests/calendar/Calendar.spec.ts b/src/tests/calendar/Calendar.spec.ts
index 1bad214d57..fa8f9b52d5 100644
--- a/src/tests/calendar/Calendar.spec.ts
+++ b/src/tests/calendar/Calendar.spec.ts
@@ -55,8 +55,6 @@ describe('Calendar', () => {
 			defaultValue: calendarDate,
 		});
 
-		expect(calendar).toBeVisible();
-
 		const selectedDay = calendar.querySelector('[data-selected]');
 		expect(selectedDay).toHaveTextContent(String(calendarDate.day));
 
@@ -145,7 +143,7 @@ describe('Calendar', () => {
 	});
 
 	test('allow deselection', async () => {
-		const { calendar, queryByTestId, user } = setup({
+		const { queryByTestId, user, calendar } = setup({
 			defaultValue: zonedDateTime,
 		});
 
@@ -164,7 +162,7 @@ describe('Calendar', () => {
 			defaultPlaceholder: zonedDateTime,
 		});
 
-		const secondDayInMonth = getByTestId('month-0-date-2');
+		const secondDayInMonth = getByTestId('month-1-date-2');
 		await user.click(secondDayInMonth);
 		expect(secondDayInMonth).toHaveAttribute('data-selected');
 
@@ -178,7 +176,7 @@ describe('Calendar', () => {
 			defaultPlaceholder: zonedDateTime,
 		});
 
-		const secondDayInMonth = getByTestId('month-0-date-2');
+		const secondDayInMonth = getByTestId('month-1-date-2');
 		secondDayInMonth.focus();
 		await user.keyboard(kbd.SPACE);
 		expect(secondDayInMonth).toHaveAttribute('data-selected');
@@ -188,7 +186,7 @@ describe('Calendar', () => {
 		await user.keyboard(kbd.ARROW_RIGHT);
 		await user.keyboard(kbd.ENTER);
 
-		const thirdDayInMonth = getByTestId('month-0-date-3');
+		const thirdDayInMonth = getByTestId('month-1-date-3');
 		expect(thirdDayInMonth).toHaveAttribute('data-selected');
 		const newDate2 = zonedDateTime.set({ day: 3 });
 		const insideValue2 = getByTestId('inside-value');
@@ -209,11 +207,11 @@ describe('Calendar', () => {
 
 		const firstMonthDayDateStr = calendarDateTime.set({ day: 12 }).toString();
 
-		const firstMonthDay = getByTestId('month-0-date-12');
+		const firstMonthDay = getByTestId('month-1-date-12');
 		expect(firstMonthDay).toHaveTextContent('12');
 		expect(firstMonthDay).toHaveAttribute('data-value', firstMonthDayDateStr);
 
-		const secondMonthDay = getByTestId('month-1-date-15');
+		const secondMonthDay = getByTestId('month-2-date-15');
 
 		const secondMonthDayDateStr = calendarDateTime.set({ day: 15, month: 2 }).toString();
 
@@ -248,11 +246,11 @@ describe('Calendar', () => {
 
 		const firstMonthDayDateStr = calendarDateTime.set({ day: 12 }).toString();
 
-		const firstMonthDay = getByTestId('month-0-date-12');
+		const firstMonthDay = getByTestId('month-1-date-12');
 		expect(firstMonthDay).toHaveTextContent('12');
 		expect(firstMonthDay).toHaveAttribute('data-value', firstMonthDayDateStr);
 
-		const secondMonthDay = getByTestId('month-1-date-15');
+		const secondMonthDay = getByTestId('month-2-date-15');
 
 		const secondMonthDayDateStr = calendarDateTime.set({ day: 15, month: 2 }).toString();
 
@@ -381,18 +379,72 @@ describe('Calendar', () => {
 		expect(heading).toHaveTextContent('April 1980');
 	});
 
+	test('calendar does not navigate after maxValue (with keyboard)', async () => {
+		const { getByTestId, user } = setup({
+			defaultValue: calendarDate,
+			maxValue: new CalendarDate(1980, 3, 31),
+		});
+
+		const firstDayInMonth = getByTestId('month-1-date-1');
+		firstDayInMonth.focus();
+		expect(firstDayInMonth).toHaveFocus();
+
+		const heading = getByTestId('heading');
+		expect(heading).toHaveTextContent('January 1980');
+
+		// five keypresses to get to February 1980
+		await user.keyboard(kbd.ARROW_DOWN);
+		expect(getByTestId('month-1-date-8')).toHaveFocus();
+		await user.keyboard(kbd.ARROW_DOWN);
+		expect(getByTestId('month-1-date-15')).toHaveFocus();
+		await user.keyboard(kbd.ARROW_DOWN);
+		expect(getByTestId('month-1-date-22')).toHaveFocus();
+		await user.keyboard(kbd.ARROW_DOWN);
+		expect(getByTestId('month-1-date-29')).toHaveFocus();
+		await user.keyboard(kbd.ARROW_DOWN);
+		expect(getByTestId('month-2-date-5')).toHaveFocus();
+		expect(heading).toHaveTextContent('February 1980');
+
+		// four keypresses to get to March 1980
+		await user.keyboard(kbd.ARROW_DOWN);
+		expect(getByTestId('month-2-date-12')).toHaveFocus();
+		await user.keyboard(kbd.ARROW_DOWN);
+		expect(getByTestId('month-2-date-19')).toHaveFocus();
+		await user.keyboard(kbd.ARROW_DOWN);
+		expect(getByTestId('month-2-date-26')).toHaveFocus();
+		await user.keyboard(kbd.ARROW_DOWN);
+		expect(getByTestId('month-3-date-4')).toHaveFocus();
+		expect(heading).toHaveTextContent('March 1980');
+
+		// four keypresses to get to April 1980
+		await user.keyboard(kbd.ARROW_DOWN);
+		expect(getByTestId('month-3-date-11')).toHaveFocus();
+		await user.keyboard(kbd.ARROW_DOWN);
+		expect(getByTestId('month-3-date-18')).toHaveFocus();
+		await user.keyboard(kbd.ARROW_DOWN);
+		expect(getByTestId('month-3-date-25')).toHaveFocus();
+		await user.keyboard(kbd.ARROW_DOWN);
+		expect(getByTestId('month-3-date-25')).toHaveFocus();
+
+		// again for good measure
+		await user.keyboard(kbd.ARROW_DOWN);
+		await user.keyboard(kbd.ARROW_DOWN);
+		await user.keyboard(kbd.ARROW_DOWN);
+		await user.keyboard(kbd.ARROW_DOWN);
+		await user.keyboard(kbd.ARROW_DOWN);
+		expect(getByTestId('month-3-date-25')).toHaveFocus();
+		expect(heading).toHaveTextContent('March 1980');
+	});
+
 	test('multiple select default Value', async () => {
 		const day1 = new CalendarDate(1980, 1, 2);
 		const day2 = new CalendarDate(1980, 1, 5);
 
-		const { getByTestId, container } = setupMulti({
+		const { calendar } = setupMulti({
 			defaultValue: [day1, day2],
 		});
 
-		const calendar = getByTestId('calendar');
-		expect(calendar).toBeVisible();
-
-		const selectedDays = container.querySelectorAll('[data-selected]');
+		const selectedDays = calendar.querySelectorAll('[data-selected]');
 		expect(selectedDays).toHaveLength(2);
 
 		expect(selectedDays[0]).toHaveTextContent(String(day1.day));
@@ -403,7 +455,7 @@ describe('Calendar', () => {
 		const day1 = new CalendarDate(1980, 1, 2);
 		const day2 = new CalendarDate(1980, 1, 5);
 		const value = writable([day1, day2]);
-		const { getByTestId, container } = render(CalendarMultiTest, {
+		const { getByTestId, container } = setupMulti({
 			value,
 		});
 
@@ -463,14 +515,11 @@ describe('Calendar', () => {
 		const day1 = new CalendarDate(1980, 1, 2);
 		const day2 = new CalendarDate(1980, 1, 5);
 
-		const { getByTestId, container } = setupMulti({
+		const { calendar } = setupMulti({
 			defaultValue: [day1, day2],
 		});
 
-		const calendar = getByTestId('calendar');
-		expect(calendar).toBeVisible();
-
-		const selectedDays = container.querySelectorAll('[data-selected]');
+		const selectedDays = calendar.querySelectorAll('[data-selected]');
 		expect(selectedDays).toHaveLength(2);
 	});
 
@@ -490,14 +539,14 @@ describe('Calendar', () => {
 		const selectedDays = calendar.querySelectorAll('[data-selected]');
 		expect(selectedDays).toHaveLength(1);
 
-		const thirdDayInMonth = getByTestId('month-0-date-3');
+		const thirdDayInMonth = getByTestId('month-1-date-3');
 
 		await user.click(thirdDayInMonth);
 
 		const selectedDaysAfterClick = calendar.querySelectorAll('[data-selected]');
 		expect(selectedDaysAfterClick).toHaveLength(2);
 
-		const fifthDayInMonth = getByTestId('month-0-date-5');
+		const fifthDayInMonth = getByTestId('month-1-date-5');
 
 		await user.click(fifthDayInMonth);
 
@@ -518,7 +567,7 @@ describe('Calendar', () => {
 		const selectedDay = calendar.querySelector('[data-selected]');
 		expect(selectedDay).toHaveTextContent(String(calendarDate.day));
 
-		const thirdDayInMonth = getByTestId('month-0-date-3');
+		const thirdDayInMonth = getByTestId('month-1-date-3');
 		await user.click(thirdDayInMonth);
 
 		const selectedDayAfterClick = calendar.querySelector('[data-selected]');
@@ -533,7 +582,7 @@ describe('Calendar', () => {
 			},
 		});
 
-		const thirdDayInMonth = getByTestId('month-0-date-3');
+		const thirdDayInMonth = getByTestId('month-1-date-3');
 		expect(thirdDayInMonth).toHaveAttribute('data-unavailable');
 		expect(thirdDayInMonth).toHaveAttribute('aria-disabled', 'true');
 		await user.click(thirdDayInMonth);
@@ -546,14 +595,11 @@ describe('Calendar', () => {
 			disabled: true,
 		});
 
-		const calendar = getByTestId('calendar');
-		expect(calendar).toHaveAttribute('data-disabled');
-
 		const grid = getByTestId('grid-0');
 		expect(grid).toHaveAttribute('aria-disabled', 'true');
 		expect(grid).toHaveAttribute('data-disabled');
 
-		const firstDayOfMonth = getByTestId('month-0-date-1');
+		const firstDayOfMonth = getByTestId('month-1-date-1');
 		await user.click(firstDayOfMonth);
 		expect(firstDayOfMonth).not.toHaveFocus();
 	});
@@ -564,14 +610,11 @@ describe('Calendar', () => {
 			readonly: true,
 		});
 
-		const calendar = getByTestId('calendar');
-		expect(calendar).toHaveAttribute('data-readonly');
-
 		const grid = getByTestId('grid-0');
 		expect(grid).toHaveAttribute('aria-readonly', 'true');
 		expect(grid).toHaveAttribute('data-readonly');
 
-		const firstDayOfMonth = getByTestId('month-0-date-1');
+		const firstDayOfMonth = getByTestId('month-1-date-1');
 		await user.click(firstDayOfMonth);
 		expect(firstDayOfMonth).toHaveFocus();
 		expect(firstDayOfMonth).not.toHaveAttribute('data-selected');
diff --git a/src/tests/calendar/CalendarMultiTest.svelte b/src/tests/calendar/CalendarMultiTest.svelte
index 103aef8a26..c84c91757e 100644
--- a/src/tests/calendar/CalendarMultiTest.svelte
+++ b/src/tests/calendar/CalendarMultiTest.svelte
@@ -92,7 +92,7 @@
 											<div
 												use:melt={$cell(date, month.value)}
 												class="cell"
-												data-testid="month-{i}-date-{date.day}"
+												data-testid="month-{date.month}-date-{date.day}"
 											>
 												{date.day}
 											</div>
diff --git a/src/tests/calendar/CalendarTest.svelte b/src/tests/calendar/CalendarTest.svelte
index a3e2df41a8..7fd9f97cca 100644
--- a/src/tests/calendar/CalendarTest.svelte
+++ b/src/tests/calendar/CalendarTest.svelte
@@ -2,6 +2,7 @@
 	import { createCalendar, type CreateCalendarProps } from '$lib/builders';
 	import { ChevronRight, ChevronLeft } from 'lucide-svelte';
 	import { melt } from '$lib';
+	import { removeUndefined } from '../utils';
 
 	export let value: CreateCalendarProps['value'] = undefined;
 	export let defaultValue: CreateCalendarProps['defaultValue'] = undefined;
@@ -27,28 +28,30 @@
 	const {
 		elements: { calendar, heading, grid, cell, prevButton, nextButton },
 		states: { value: insideValue, months, headingValue, daysOfWeek },
-	} = createCalendar({
-		value,
-		defaultValue,
-		defaultPlaceholder,
-		onValueChange,
-		onPlaceholderChange,
-		isDateUnavailable,
-		isDateDisabled,
-		locale,
-		calendarLabel,
-		preventDeselect,
-		numberOfMonths,
-		pagedNavigation,
-		placeholder,
-		weekStartsOn,
-		fixedWeeks,
-		minValue,
-		maxValue,
-		multiple,
-		disabled,
-		readonly,
-	});
+	} = createCalendar(
+		removeUndefined({
+			value,
+			defaultValue,
+			defaultPlaceholder,
+			onValueChange,
+			onPlaceholderChange,
+			isDateUnavailable,
+			isDateDisabled,
+			locale,
+			calendarLabel,
+			preventDeselect,
+			numberOfMonths,
+			pagedNavigation,
+			placeholder,
+			weekStartsOn,
+			fixedWeeks,
+			minValue,
+			maxValue,
+			multiple,
+			disabled,
+			readonly,
+		})
+	);
 </script>
 
 <main class="flex h-full">
@@ -70,93 +73,90 @@
 						<ChevronRight />
 					</button>
 				</header>
-				{#each $months as month, i (i)}
-					{@const { weeks } = month}
-					<table use:melt={$grid} class="w-full" data-testid="grid-{i}">
-						<thead aria-hidden="true">
-							<tr>
-								{#each $daysOfWeek as day, idx}
-									<th class="text-sm font-semibold text-magnum-800">
-										<div
-											class="flex h-6 w-6 items-center justify-center p-4"
-											data-testid="day-of-week-{idx}"
-										>
-											{day}
-										</div>
-									</th>
-								{/each}
-							</tr>
-						</thead>
-						<tbody>
-							{#each weeks as days, idx}
-								<tr data-testid="week-{idx + 1}">
-									{#each days as date}
-										<td role="gridcell">
+				<div>
+					{#each $months as month, i}
+						<table use:melt={$grid} class="w-full" data-testid="grid-{i}">
+							<thead aria-hidden="true">
+								<tr>
+									{#each $daysOfWeek as day, idx}
+										<th class="text-sm font-semibold text-magnum-800">
 											<div
-												use:melt={$cell(date, month.value)}
-												class="cell"
-												data-testid="month-{i}-date-{date.day}"
+												class="flex h-6 w-6 items-center justify-center p-4"
+												data-testid="day-of-week-{idx}"
 											>
-												{date.day}
+												{day}
 											</div>
-										</td>
+										</th>
 									{/each}
 								</tr>
-							{/each}
-						</tbody>
-					</table>
-				{/each}
+							</thead>
+							<tbody>
+								{#each month.weeks as days, idx}
+									<tr data-testid="week-{idx + 1}">
+										{#each days as date}
+											<td role="gridcell">
+												<div
+													use:melt={$cell(date, month.value)}
+													class="cell"
+													data-testid="month-{date.month}-date-{date.day}"
+												>
+													{date.day}
+												</div>
+											</td>
+										{/each}
+									</tr>
+								{/each}
+							</tbody>
+						</table>
+					{/each}
+				</div>
 			</div>
 		</div>
 	</div>
 </main>
 
 <style lang="postcss">
-	.input {
-		@apply flex h-8 w-full rounded-md border border-magnum-800 bg-transparent px-2.5 text-sm;
-		@apply ring-offset-magnum-300 focus-visible:ring;
-		@apply focus-visible:ring-magnum-400 focus-visible:ring-offset-1;
-		@apply flex-1 items-center justify-center;
-		@apply px-2.5 text-sm leading-none text-magnum-700;
+	[data-melt-calendar] {
+		@apply w-full rounded-lg bg-white p-3 text-magnum-800 shadow-sm;
 	}
 
-	.trigger {
-		@apply inline-flex w-64 items-center  justify-center rounded bg-white p-0 px-2 py-1 text-sm font-medium;
-		@apply text-magnum-900 transition-colors hover:bg-white/90;
-		@apply focus-visible:ring focus-visible:ring-magnum-400 focus-visible:ring-offset-2;
+	header {
+		@apply flex items-center justify-between pb-2;
 	}
 
-	.close {
-		@apply absolute right-1.5 top-1.5 flex h-7 w-7 items-center justify-center rounded-full;
-		@apply text-magnum-900 transition-colors hover:bg-magnum-500/10;
-		@apply focus-visible:ring focus-visible:ring-magnum-400 focus-visible:ring-offset-2;
-		@apply bg-white p-0 text-sm font-medium;
+	header + div {
+		@apply flex items-center gap-8;
 	}
 
-	.button {
-		@apply flex h-6 w-6 items-center justify-center rounded-full;
-		@apply text-magnum-900 transition-colors hover:bg-magnum-500/10;
-		@apply focus-visible:ring-1 focus-visible:ring-magnum-400;
-		@apply bg-white p-0 text-sm font-medium;
+	[data-melt-calendar-prevbutton] {
+		@apply rounded-lg p-1 transition-all hover:bg-magnum-100;
 	}
 
-	.content {
-		@apply z-10 w-60 rounded-[4px] bg-white p-5 shadow-sm;
+	[data-melt-calendar-nextbutton] {
+		@apply rounded-lg p-1 transition-all hover:bg-magnum-100;
 	}
 
-	.buttons-wrapper {
-		@apply flex items-center justify-between border-y border-magnum-700 py-1;
+	[data-melt-calendar-heading] {
+		@apply font-semibold text-magnum-800;
+	}
+
+	th {
+		@apply text-sm font-semibold text-magnum-800;
+
+		& div {
+			@apply flex h-6 w-6 items-center justify-center p-4;
+		}
 	}
 
-	.cell {
-		@apply flex h-6 w-6 cursor-pointer select-none items-center justify-center rounded p-4 hover:bg-magnum-100 focus:ring focus:ring-magnum-400 data-[outside-month]:pointer-events-none data-[outside-month]:cursor-default data-[range-highlighted]:bg-magnum-200 data-[selected]:bg-magnum-300 data-[disabled]:opacity-40 data-[outside-month]:opacity-40 data-[outside-month]:hover:bg-transparent;
+	[data-melt-calendar-grid] {
+		@apply w-full;
 	}
 
-	.segment {
-		@apply data-[segment="dayPeriod"]:pl-0.5 data-[segment="hour"]:pl-1 data-[segment="timeZoneName"]:pl-1;
+	[data-melt-calendar-cell] {
+		@apply flex h-6 w-6 cursor-pointer select-none items-center justify-center rounded-lg p-4 hover:bg-magnum-100 focus:ring focus:ring-magnum-400 data-[outside-visible-months]:pointer-events-none data-[outside-visible-months]:cursor-default data-[range-highlighted]:bg-magnum-200 data-[selected]:bg-magnum-300 data-[selected]:text-magnum-900 data-[disabled]:opacity-40 data-[outside-visible-months]:opacity-40 data-[outside-visible-months]:hover:bg-transparent;
 	}
 
-	.btn {
-		@apply rounded bg-magnum-600 p-1 text-xs text-white;
+	[data-melt-calendar-cell][data-outside-month='true'][data-outside-visible-months='true'] {
+		@apply opacity-0;
 	}
 </style>
diff --git a/src/tests/range-calendar/RangeCalendar.spec.ts b/src/tests/range-calendar/RangeCalendar.spec.ts
index 76e590240b..31032b833b 100644
--- a/src/tests/range-calendar/RangeCalendar.spec.ts
+++ b/src/tests/range-calendar/RangeCalendar.spec.ts
@@ -1,4 +1,4 @@
-import { kbd } from '$lib/internal/helpers/keyboard.js';
+import { testKbd as kbd } from '../utils.js';
 import { render } from '@testing-library/svelte';
 import userEvent from '@testing-library/user-event';
 import { axe } from 'jest-axe';
@@ -151,7 +151,7 @@ describe('Range Calendar', () => {
 		expect(startValue).toHaveTextContent(String(calendarDateRange.start));
 		expect(endValue).toHaveTextContent(String(calendarDateRange.end));
 
-		const fifthDayInMonth = getByTestId('month-0-date-5');
+		const fifthDayInMonth = getByTestId('month-1-date-5');
 		await user.click(fifthDayInMonth);
 		expect(fifthDayInMonth).toHaveFocus();
 
@@ -159,7 +159,7 @@ describe('Range Calendar', () => {
 		expect(selectedDays).toHaveLength(1);
 		expect(startValue).toHaveTextContent(String(undefined));
 		expect(endValue).toHaveTextContent(String(undefined));
-		const seventhDayInMonth = getByTestId('month-0-date-7');
+		const seventhDayInMonth = getByTestId('month-1-date-7');
 		await user.click(seventhDayInMonth);
 		await tick();
 		expect(calendar.querySelectorAll('[data-selected]')).toHaveLength(3);
@@ -170,22 +170,22 @@ describe('Range Calendar', () => {
 			defaultPlaceholder: calendarDateRange.start,
 		});
 
-		const secondDayInMonth = getByTestId('month-0-date-2');
-		const thirdDayInMonth = getByTestId('month-0-date-3');
-		const fourthDayInMonth = getByTestId('month-0-date-4');
+		const secondDayInMonth = getByTestId('month-1-date-2');
+		const thirdDayInMonth = getByTestId('month-1-date-3');
+		const fourthDayInMonth = getByTestId('month-1-date-4');
 		secondDayInMonth.focus();
-		await user.keyboard(`{${kbd.SPACE}}`);
+		await user.keyboard(kbd.SPACE);
 		expect(secondDayInMonth).toHaveAttribute('data-selected');
 
-		await user.keyboard(`{${kbd.ARROW_RIGHT}}`);
+		await user.keyboard(kbd.ARROW_RIGHT);
 		expect(thirdDayInMonth).toHaveFocus();
 		await tick();
 		expect(thirdDayInMonth).toHaveAttribute('data-focused');
-		await user.keyboard(`{${kbd.ARROW_RIGHT}}`);
+		await user.keyboard(kbd.ARROW_RIGHT);
 		await tick();
 		expect(thirdDayInMonth).toHaveAttribute('data-highlighted');
 		expect(fourthDayInMonth).toHaveFocus();
-		await user.keyboard(`{${kbd.SPACE}}`);
+		await user.keyboard(kbd.SPACE);
 		expect(fourthDayInMonth).toHaveAttribute('data-selected');
 
 		const selectedDays = calendar.querySelectorAll('[data-selected]');
@@ -201,7 +201,7 @@ describe('Range Calendar', () => {
 		const heading = getByTestId('heading');
 		expect(heading).toHaveTextContent('January - February 1980');
 
-		const firstMonthDay = getByTestId('month-0-date-12');
+		const firstMonthDay = getByTestId('month-1-date-12');
 		expect(firstMonthDay).toHaveTextContent('12');
 
 		const secondMonthDay = getByTestId('month-1-date-15');
@@ -232,11 +232,11 @@ describe('Range Calendar', () => {
 
 		const firstMonthDayDateStr = calendarDateRange.start.set({ day: 12 }).toString();
 
-		const firstMonthDay = getByTestId('month-0-date-12');
+		const firstMonthDay = getByTestId('month-1-date-12');
 		expect(firstMonthDay).toHaveTextContent('12');
 		expect(firstMonthDay).toHaveAttribute('data-value', firstMonthDayDateStr);
 
-		const secondMonthDay = getByTestId('month-1-date-15');
+		const secondMonthDay = getByTestId('month-2-date-15');
 
 		const secondMonthDayDateStr = calendarDateRange.start.set({ day: 15, month: 2 }).toString();
 
@@ -385,10 +385,10 @@ describe('Range Calendar', () => {
 		const selectedDays = calendar.querySelectorAll('[data-selected]');
 		expect(selectedDays).toHaveLength(6);
 
-		const thirdDayInMonth = getByTestId('month-0-date-3');
+		const thirdDayInMonth = getByTestId('month-1-date-3');
 		await user.click(thirdDayInMonth);
 		await tick();
-		const fourthDayInMonth = getByTestId('month-0-date-4');
+		const fourthDayInMonth = getByTestId('month-1-date-4');
 		await user.click(fourthDayInMonth);
 		await tick();
 
@@ -404,7 +404,7 @@ describe('Range Calendar', () => {
 			},
 		});
 
-		const thirdDayInMonth = getByTestId('month-0-date-3');
+		const thirdDayInMonth = getByTestId('month-1-date-3');
 		expect(thirdDayInMonth).toHaveAttribute('data-unavailable');
 		expect(thirdDayInMonth).toHaveAttribute('aria-disabled', 'true');
 		await user.click(thirdDayInMonth);
@@ -419,7 +419,7 @@ describe('Range Calendar', () => {
 			},
 		});
 
-		const thirdDayInMonth = getByTestId('month-0-date-3');
+		const thirdDayInMonth = getByTestId('month-1-date-3');
 		expect(thirdDayInMonth).toHaveAttribute('data-disabled');
 		expect(thirdDayInMonth).toHaveAttribute('aria-disabled', 'true');
 		await user.click(thirdDayInMonth);
@@ -427,23 +427,20 @@ describe('Range Calendar', () => {
 	});
 
 	test('Selection through disabled with double click doesnt select dates', async () => {
-		const { getByTestId, container, user } = setup({
+		const { getByTestId, calendar, user } = setup({
 			defaultPlaceholder: calendarDateRange.start,
 			isDateDisabled: (date) => {
 				return date.day === 3;
 			},
 		});
 
-		const calendar = getByTestId('calendar');
-		expect(calendar).toBeVisible();
-
-		const firstDayInMonth = getByTestId('month-0-date-1');
+		const firstDayInMonth = getByTestId('month-1-date-1');
 		await user.click(firstDayInMonth);
 		expect(firstDayInMonth).toHaveAttribute('data-selected');
 
-		const fifthDayInMonth = getByTestId('month-0-date-5');
+		const fifthDayInMonth = getByTestId('month-1-date-5');
 		await user.dblClick(fifthDayInMonth);
 
-		expect(container.querySelectorAll('[data-selected]')).toHaveLength(1);
+		expect(calendar.querySelectorAll('[data-selected]')).toHaveLength(1);
 	});
 });
diff --git a/src/tests/range-calendar/RangeCalendarTest.svelte b/src/tests/range-calendar/RangeCalendarTest.svelte
index 463ed11153..bdc3b38b93 100644
--- a/src/tests/range-calendar/RangeCalendarTest.svelte
+++ b/src/tests/range-calendar/RangeCalendarTest.svelte
@@ -91,7 +91,7 @@
 											<div
 												use:melt={$cell(date, month.value)}
 												class="cell"
-												data-testid="month-{i}-date-{date.day}"
+												data-testid="month-{date.month}-date-{date.day}"
 											>
 												{date.day}
 											</div>
diff --git a/src/tests/utils.ts b/src/tests/utils.ts
index 693004d610..41235e2581 100644
--- a/src/tests/utils.ts
+++ b/src/tests/utils.ts
@@ -9,3 +9,14 @@ export const testKbd: Record<KbdKeys, string> = Object.entries(kbd).reduce((acc,
 	acc[key as KbdKeys] = `{${value}}`;
 	return acc;
 }, {} as Record<KbdKeys, string>);
+
+export function removeUndefined<T extends object>(obj: T): T {
+	const result = {} as T;
+	for (const key in obj) {
+		const value = obj[key];
+		if (value !== undefined) {
+			result[key] = value;
+		}
+	}
+	return result;
+}