Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/slimy-rules-nail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@shopware-ag/meteor-component-library": minor
---

Fix timezone issue in `mt-datepicker`
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ export default {
title: "Interaction Tests/Form/mt-datepicker",
} as MtDatepickerMeta;

export const VisualTestDatepickerDefault: MtDatepickerStory = {
name: "Render datepicker",
args: {
label: "Datepicker",
},
};

export const TestDatepickerShouldOpen: MtDatepickerStory = {
name: "Should open datepicker",
args: {
Expand Down Expand Up @@ -417,3 +424,17 @@ export const VisualTestMinDateDisabledDays: MtDatepickerStory = {
expect(day15).toBeDefined();
},
};

export const VisualTestHelpText: MtDatepickerStory = {
name: "Should display help text",
args: {
label: "Datepicker",
helpText: "This is a help text",
},
play: async () => {
const canvas = within(document.body);
await userEvent.tab();

expect(canvas.getByRole("tooltip")).toBeInTheDocument();
},
};
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { describe, it, expect } from "vitest";
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen } from "@testing-library/vue";
import MtDatepicker from "./mt-datepicker.vue";
import userEvent from "@testing-library/user-event";
import { waitUntil } from "@/_internal/test-helper";
import { getByRole } from "@storybook/test";

describe("mt-datepicker", () => {
beforeEach(() => {
// Set system time to ensure consistent test results
vi.setSystemTime(new Date("2024-07-15T09:00:00Z"));
});
it("is enabled by default", () => {
// ARRANGE
render(MtDatepicker);
Expand Down Expand Up @@ -158,4 +164,314 @@ describe("mt-datepicker", () => {
// ASSERT - Check that the component renders without errors when minDate is provided
expect(screen.getByRole("textbox")).toBeInTheDocument();
});

it("emits date when value when typed into input", async () => {
// ARRANGE
const handler = vi.fn();
render(MtDatepicker, {
props: {
dateType: "date",
textInput: true,
timeZone: "UTC",
"onUpdate:modelValue": handler,
},
});

const input = screen.getByRole("textbox");

// ACT
await userEvent.clear(input);
await userEvent.type(input, "2025/01/01");
await userEvent.keyboard("{enter}");

// ASSERT
expect(handler).toHaveBeenLastCalledWith("2025-01-01T00:00:00.000Z");
});

it("should accept an ISO string as modelValue", async () => {
// ARRANGE
const handler = vi.fn();
await render(MtDatepicker, {
props: {
dateType: "datetime",
modelValue: "2024-03-20T14:30:00+01:00",
timeZone: "UTC",
"onUpdate:modelValue": handler,
},
});

// ASSERT
expect(screen.getByRole("textbox")).toHaveValue("2024/03/20, 13:30");
});

it("should accept a date object as modelValue", async () => {
const date = new Date("2024-03-20T14:30:00+01:00");

// ARRANGE
await render(MtDatepicker, {
props: {
dateType: "datetime",
modelValue: date,
},
});

// ASSERT
expect(screen.getByRole("textbox")).toHaveValue("2024/03/20, 13:30");
});

it("should accept ISO string array as modelValue", async () => {
// ARRANGE
const handler = vi.fn();
await render(MtDatepicker, {
props: {
dateType: "datetime",
range: true,
modelValue: ["2024-03-20T14:30:00+01:00", "2024-03-21T14:30:00+01:00"],
timeZone: "UTC",
"onUpdate:modelValue": handler,
},
});

// ASSERT
expect(screen.getByRole("textbox")).toHaveValue("2024/03/20, 13:30 - 2024/03/21, 13:30");
});

it("converts time correctly when timezone changes", async () => {
// ARRANGE - Set a specific UTC time
const { rerender } = await render(MtDatepicker, {
props: {
dateType: "datetime",
timeZone: "UTC",
modelValue: "2024-12-31T13:00:00Z",
},
});

// ACT - Change timezone to America/New_York (UTC-5 in November)
await rerender({
dateType: "datetime",
timeZone: "Australia/Sydney",
});

// ASSERT - 10:30 UTC should become 05:30 EST
const input = document.querySelector(".dp__input") as HTMLInputElement;
expect(input.value).toBe("2025/01/01, 00:00");
});

it("should emit an iso string with the correct converted time", async () => {
const handler = vi.fn();
// ARRANGE
render(MtDatepicker, {
props: {
dateType: "datetime",
timeZone: "America/New_York",
modelValue: "2023-07-18T09:15:00.000Z",
"onUpdate:modelValue": handler,
},
});

// ACT - select a datetime
await userEvent.click(screen.getByRole("textbox"));
await waitUntil(() => document.getElementsByClassName("dp__menu").length > 0);
await waitUntil(() => document.getElementsByClassName("dp__time_input") !== null);

// Set the hours
await userEvent.click(
document.querySelector('[data-test-id="hours-toggle-overlay-btn-0"]') as HTMLElement,
);
await waitUntil(() => document.querySelector(".dp__overlay") !== null);
await userEvent.click(document.querySelector('[data-test-id="00"]') as HTMLElement);
await waitUntil(() => !document.querySelector(".dp__overlay"));
await waitUntil(() =>
expect(
document.querySelector('[data-test-id="hours-toggle-overlay-btn-0"]'),
).toHaveTextContent("00"),
);

// Set the minutes
await userEvent.click(
document.querySelector('[data-test-id="minutes-toggle-overlay-btn-0"]') as HTMLElement,
);
await waitUntil(() => document.querySelector(".dp__overlay") !== null);
await userEvent.click(document.querySelector('[data-test-id="00"]') as HTMLElement);
await waitUntil(() => !document.querySelector(".dp__overlay"));
expect(
document.querySelector('[data-test-id="minutes-toggle-overlay-btn-0"]'),
).toHaveTextContent("00");

// Set the month
await userEvent.click(
document.querySelector('[data-test-id="month-toggle-overlay-0"]') as HTMLElement,
);
await waitUntil(() => document.querySelector(".dp__overlay") !== null);
await userEvent.click(document.querySelector('[data-test-id="Mär"]') as HTMLElement);
await waitUntil(() => !document.querySelector(".dp__overlay"));
expect(document.querySelector('[data-test-id="month-toggle-overlay-0"]')).toHaveTextContent(
"Mär",
);

// Set the year
await userEvent.click(
document.querySelector('[data-test-id="year-toggle-overlay-0"]') as HTMLElement,
);
await waitUntil(() => document.querySelector(".dp__overlay") !== null);
await userEvent.click(document.querySelector('[data-test-id="2025"]') as HTMLElement);
await waitUntil(() => !document.querySelector(".dp__overlay"));

// Set the day
await waitUntil(() => document.getElementById("2025-03-15") !== null);
const dayElement = document.getElementById("2025-03-15") as HTMLElement;
await userEvent.click(dayElement);

// Wait and check if menu closed
await new Promise((resolve) => setTimeout(resolve, 100));
await waitUntil(() => !document.querySelector(".dp__menu"));

// ASSERT - The handler was called with the correct date "2025-03-15T04:00:00Z"
// The input is "2025-03-15, 00:00" and timeZone is "America/New_York" so the utcOffset is "+04:00"
expect(handler).toHaveBeenLastCalledWith("2025-03-15T04:00:00.000Z");
});

it("should emit iso string with the correct time", async () => {
const handler = vi.fn();
// ARRANGE
render(MtDatepicker, {
props: {
dateType: "datetime",
"onUpdate:modelValue": handler,
},
});

// ACT - select a datetime
await userEvent.click(screen.getByRole("textbox"));
await waitUntil(() => document.getElementsByClassName("dp__menu").length > 0);
await waitUntil(() => document.getElementsByClassName("dp__time_input") !== null);

// Set the hours
await userEvent.click(
document.querySelector('[data-test-id="hours-toggle-overlay-btn-0"]') as HTMLElement,
);
await waitUntil(() => document.querySelector(".dp__overlay") !== null);
await userEvent.click(document.querySelector('[data-test-id="08"]') as HTMLElement);
await waitUntil(() => !document.querySelector(".dp__overlay"));
await waitUntil(() =>
expect(
document.querySelector('[data-test-id="hours-toggle-overlay-btn-0"]'),
).toHaveTextContent("08"),
);

// Set the minutes
await userEvent.click(
document.querySelector('[data-test-id="minutes-toggle-overlay-btn-0"]') as HTMLElement,
);
await waitUntil(() => document.querySelector(".dp__overlay") !== null);
await userEvent.click(document.querySelector('[data-test-id="05"]') as HTMLElement);
await waitUntil(() => !document.querySelector(".dp__overlay"));
expect(
document.querySelector('[data-test-id="minutes-toggle-overlay-btn-0"]'),
).toHaveTextContent("05");

// Set the month
await userEvent.click(
document.querySelector('[data-test-id="month-toggle-overlay-0"]') as HTMLElement,
);
await waitUntil(() => document.querySelector(".dp__overlay") !== null);
await userEvent.click(document.querySelector('[data-test-id="Jan"]') as HTMLElement);
await waitUntil(() => !document.querySelector(".dp__overlay"));
expect(document.querySelector('[data-test-id="month-toggle-overlay-0"]')).toHaveTextContent(
"Jan",
);

// Set the year
await userEvent.click(
document.querySelector('[data-test-id="year-toggle-overlay-0"]') as HTMLElement,
);
await waitUntil(() => document.querySelector(".dp__overlay") !== null);
await userEvent.click(document.querySelector('[data-test-id="2025"]') as HTMLElement);
await waitUntil(() => !document.querySelector(".dp__overlay"));

// Set the day
await waitUntil(() => document.getElementById("2025-01-01") !== null);
const dayElement = document.getElementById("2025-01-01") as HTMLElement;
await userEvent.click(dayElement);

// Wait and check if menu closed
await new Promise((resolve) => setTimeout(resolve, 100));
await waitUntil(() => !document.querySelector(".dp__menu"));

// ASSERT - The handler was called with the correct date
// The input is "2025-01-01, 08:05" so the output should be "2025-01-01T08:05:00.000Z"
expect(handler).toHaveBeenLastCalledWith("2025-01-01T08:05:00.000Z");
});

it("should increment the hours overlay by the prop value", async () => {
const incrementValue = 2;
// ARRANGE
render(MtDatepicker, {
props: {
dateType: "datetime",
modelValue: "2023-07-18T09:15:00.000Z",
hourIncrement: incrementValue,
},
});

// ACT - select a datetime
await userEvent.click(screen.getByRole("textbox"));
await waitUntil(() => document.getElementsByClassName("dp__menu").length > 0);
await waitUntil(() => document.getElementsByClassName("dp__time_input") !== null);

// Set the hours
await userEvent.click(
document.querySelector('[data-test-id="hours-toggle-overlay-btn-0"]') as HTMLElement,
);
await waitUntil(() => document.querySelector(".dp__overlay") !== null);

// Get an array of the overlay cell values
const overlayCells = Array.from(document.getElementsByClassName("dp__overlay_cell")).map((el) =>
parseInt(el.textContent || "0"),
);

// Check each value difference
const allDifferencesMatch = overlayCells
.slice(1)
.every((value, index) => value - overlayCells[index] === incrementValue);

// ASSERT - The differences between each consecutive pair should match the increment value
expect(allDifferencesMatch).toBe(true);
});

it("should increment the minutes overlay by the prop value", async () => {
const incrementValue = 4;
// ARRANGE
render(MtDatepicker, {
props: {
dateType: "datetime",
modelValue: "2023-07-18T09:15:00.000Z",
minuteIncrement: incrementValue,
},
});

// ACT - select a datetime
await userEvent.click(screen.getByRole("textbox"));
await waitUntil(() => document.getElementsByClassName("dp__menu").length > 0);
await waitUntil(() => document.getElementsByClassName("dp__time_input") !== null);

// Set the minutes
await userEvent.click(
document.querySelector('[data-test-id="minutes-toggle-overlay-btn-0"]') as HTMLElement,
);
await waitUntil(() => document.querySelector(".dp__overlay") !== null);

// Get an array of the overlay cell values
const overlayCells = Array.from(document.getElementsByClassName("dp__overlay_cell")).map((el) =>
parseInt(el.textContent || "0"),
);

// Check each value difference
const allDifferencesMatch = overlayCells
.slice(1)
.every((value, index) => value - overlayCells[index] === incrementValue);

// ASSERT - The differences between each consecutive pair should match the increment value
expect(allDifferencesMatch).toBe(true);
});
});
Loading
Loading