diff --git a/packages/main/cypress/specs/TimePicker.cy.tsx b/packages/main/cypress/specs/TimePicker.cy.tsx new file mode 100644 index 000000000000..c213f7d8c17c --- /dev/null +++ b/packages/main/cypress/specs/TimePicker.cy.tsx @@ -0,0 +1,392 @@ +import "../../src/Assets.js"; +import { setLanguage } from "@ui5/webcomponents-base/dist/config/Language.js"; +import TimePicker from "../../src/TimePicker.js"; +import Label from "../../src/Label.js"; +import ResponsivePopover from "../../src/ResponsivePopover.js"; + +function pressKeyNTimes(key: "ArrowDown" | "ArrowUp" | "Space" | "Tab" | "Enter", n: number) { + for (let i = 0; i < n; i++) { + cy.realPress(key); + } +} + +describe("TimePicker Tests", () => { + it("input receives value in format pattern depending on the set language", () => { + cy.wrap({ setLanguage }) + .then(api => { + return api.setLanguage("bg") + }) + + cy.mount(); + + cy.get("[ui5-time-picker]") + .as("timePicker"); + + cy.get("@timePicker") + .ui5TimePickerGetInnerInput() + .should("have.value", "3:16:16 ч."); + + cy.wrap({ setLanguage }) + .then(api => { + return api.setLanguage("en"); + }); + }); + + it("tests clocks value", () => { + cy.mount(); + + cy.get("[ui5-time-picker]") + .as("timePicker") + .ui5TimePickerValueHelpIconPress(); + + cy.get("@timePicker") + .ui5TimePickerGetClock("hours") + .should("have.prop", "valueNow", 11); + + cy.get("@timePicker") + .ui5TimePickerGetClock("minutes") + .should("have.prop", "valueNow", 12); + + cy.get("@timePicker") + .ui5TimePickerGetClock("seconds") + .should("have.prop", "valueNow", 13); + }); + + it("tests clocks submit value", () => { + cy.mount(); + + cy.get("[ui5-time-picker]") + .as("timePicker") + .ui5TimePickerValueHelpIconPress(); + + cy.get("@timePicker") + .ui5TimePickerGetClock("hours") + .realClick() + .should("be.focused") + + + pressKeyNTimes("ArrowDown", 2); + cy.realPress("Space"); + + cy.get("@timePicker") + .ui5TimePickerGetClock("minutes"); + + pressKeyNTimes("ArrowDown", 5); + pressKeyNTimes("ArrowUp", 2); + + cy.realPress("Space"); + + cy.get("@timePicker") + .ui5TimePickerGetClock("seconds"); + + pressKeyNTimes("ArrowUp", 4); + + cy.realPress("Tab"); + cy.realPress("Enter"); + + cy.get("@timePicker") + .should("have.value", "10:57:05"); + }); + + it("tests submit wrong value", () => { + cy.mount(); + + cy.get("[ui5-time-picker]") + .as("timePicker") + .ui5TimePickerGetInnerInput() + .realClick() + .should("be.focused"); + + cy.realType("123123123"); + cy.realPress("Enter"); + + cy.get("@timePicker") + .shadow() + .find("[ui5-datetime-input]") + .should("have.attr", "value-state", "Negative"); + }); + + it("tests change event", () => { + cy.mount(); + + cy.get("[ui5-time-picker]") + .as("timePicker"); + + // Open picker and submit without changes + cy.get("@timePicker") + .ui5TimePickerValueHelpIconPress(); + + cy.get("@timePicker") + .ui5TimePickerGetSubmitButton() + .realClick(); + + // Assert no change event was fired + cy.get("@changeStub").should("not.have.been.called"); + + // Open picker, change time and submit + cy.get("@timePicker") + .ui5TimePickerValueHelpIconPress(); + + cy.get("@timePicker") + .ui5TimePickerGetClock("hours") + .realClick() + .should("be.focused") + + cy.realPress("PageDown"); + + pressKeyNTimes("ArrowDown", 2); + cy.realPress("Space"); + + cy.get("@timePicker") + .ui5TimePickerGetSubmitButton() + .realClick(); + + // Assert change event was fired once + cy.get("@changeStub").should("have.been.calledOnce"); + + // Open picker and submit without changes + cy.get("@timePicker") + .ui5TimePickerValueHelpIconPress(); + + cy.get("@timePicker") + .ui5TimePickerGetSubmitButton() + .realClick(); + + // Assert change event was not fired again + cy.get("@changeStub").should("have.been.calledOnce"); + + // Open picker, change time and submit + cy.get("@timePicker") + .ui5TimePickerValueHelpIconPress(); + + cy.get("@timePicker") + .ui5TimePickerGetClock("hours") + .realClick() + .should("be.focused") + .realPress("ArrowDown") // select 00 + .realPress("Space"); + + cy.get("@timePicker") + .ui5TimePickerGetSubmitButton() + .realClick(); + + // Assert change event was fired again + cy.get("@changeStub").should("have.been.calledTwice"); + + // Test direct input change + cy.get("@timePicker") + .ui5TimePickerGetInnerInput() + .realClick() + .should("be.focused") + .realPress("Backspace") + .realType("7") + .realPress("Enter"); + + // Assert change event was fired again + cy.get("@changeStub").should("have.been.calledThrice"); + }); + + it("tests value state", () => { + cy.mount(); + + cy.get("[ui5-time-picker]") + .as("timePicker") + .ui5TimePickerGetInnerInput() + .realClick(); + + // Clear the input + cy.get("@timePicker") + .ui5TimePickerGetInnerInput() + .realClick({ clickCount: 2 }) + .realPress("Backspace"); + + cy.get("body").realClick(); // Click outside to trigger blur + + cy.get("@timePicker") + .shadow() + .find("[ui5-datetime-input]") + .should("have.attr", "value-state", "None"); + }); + + it("tests input keyboard handling", () => { + cy.mount(); + + cy.get("[ui5-time-picker]") + .as("timePicker") + .ui5TimePickerGetInnerInput() + .realClick() + .should("be.focused") + .realPress(["Shift", "PageUp"]); + + cy.get("@timePicker") + .ui5TimePickerGetInnerInput() + .should("have.value", "02:41:05"); + + cy.get("@timePicker") + .ui5TimePickerGetInnerInput() + .realClick() + .should("be.focused") + .realPress(["Shift", "PageDown"]); + + cy.get("@timePicker") + .ui5TimePickerGetInnerInput() + .should("have.value", "02:40:05"); + + cy.get("@timePicker") + .ui5TimePickerGetInnerInput() + .realClick() + .should("be.focused") + .realPress("PageUp"); + + cy.get("@timePicker") + .ui5TimePickerGetInnerInput() + .should("have.value", "03:40:05"); + + cy.get("@timePicker") + .ui5TimePickerGetInnerInput() + .realClick() + .should("be.focused") + .realPress("PageDown"); + + cy.get("@timePicker") + .ui5TimePickerGetInnerInput() + .should("have.value", "02:40:05"); + + cy.get("@timePicker") + .ui5TimePickerGetInnerInput() + .realClick() + .should("be.focused") + .realPress(["Shift", "Control", "PageUp"]); + + cy.get("@timePicker") + .ui5TimePickerGetInnerInput() + .should("have.value", "02:40:06"); + + cy.get("@timePicker") + .ui5TimePickerGetInnerInput() + .realClick() + .should("be.focused") + .realPress(["Shift", "Control", "PageDown"]); + + cy.get("@timePicker") + .ui5TimePickerGetInnerInput() + .should("have.value", "02:40:05"); + }); + + it("test closing the picker with the keyboard", () => { + cy.mount(); + + cy.get("[ui5-time-picker]") + .as("timePicker") + .ui5TimePickerValueHelpIconPress(); + + cy.realPress(["Alt", "ArrowUp"]); + + cy.get("@timePicker") + .should("not.have.attr", "open"); + }); + + it("the value 'now' returns the current time, instead of the string 'now'", () => { + cy.mount(); + + cy.get("[ui5-time-picker]") + .as("timePicker") + .ui5TimePickerGetInnerInput() + .realClick() + .should("be.focused") + .realType("now") + .realPress("Enter"); + + cy.get("@timePicker") + .ui5TimePickerGetInnerInput() + .should("not.have.value", "now"); + }); + + it("opening time picker's value-help, sets the 'open' property to true", () => { + cy.mount(); + + cy.get("[ui5-time-picker]") + .as("timePicker") + .ui5TimePickerValueHelpIconPress(); + + cy.get("@timePicker") + .should("have.attr", "open"); + }); + + it("setting time picker's open property to true, opens the value-help", () => { + cy.mount(); + + cy.get("[ui5-time-picker]") + .as("timePicker"); + + cy.get("@timePicker") + .invoke("prop", "open", true); + + cy.get("@timePicker") + .shadow() + .find("[ui5-responsive-popover]") + .ui5ResponsivePopoverOpened() + }); + + it("picker popover should have accessible name", () => { + cy.mount(); + + cy.get("[ui5-time-picker]") + .as("timePicker") + .ui5TimePickerGetInnerInput() + .realClick() + .should("be.focused") + .realPress("F4"); + + cy.get("@timePicker") + .shadow() + .find("[ui5-responsive-popover]") + .ui5ResponsivePopoverOpened(); + + cy.get("@timePicker") + .shadow() + .find("[ui5-responsive-popover]") + .should("have.attr", "accessible-name", "Choose Time"); + }); + + it("input should have accessible name", () => { + cy.mount( + <> + + + + ); + + cy.get("[ui5-time-picker]") + .ui5TimePickerGetInnerInput() + .should("have.attr", "aria-label", "Pick a time"); + }); + + it("should apply aria-label from the accessibleName property", () => { + cy.mount(); + + cy.get("[ui5-time-picker]") + .ui5TimePickerGetInnerInput() + .should("have.attr", "aria-label", "Pick a time"); + }); + + it("displays value state message header in popover when value state is set", () => { + cy.mount(); + + cy.get("[ui5-time-picker]") + .as("timePicker") + .ui5TimePickerValueHelpIconPress(); + + cy.get("@timePicker") + .shadow() + .find("[ui5-responsive-popover]") + .ui5ResponsivePopoverOpened(); + + cy.get("@timePicker") + .shadow() + .find("[ui5-responsive-popover]") + .find(".ui5-valuestatemessage-header") + .should("exist") + .and("have.class", "ui5-valuestatemessage--error"); + }); +}); \ No newline at end of file diff --git a/packages/main/cypress/support/commands.ts b/packages/main/cypress/support/commands.ts index 88e3fabf48a4..39da858e816a 100644 --- a/packages/main/cypress/support/commands.ts +++ b/packages/main/cypress/support/commands.ts @@ -56,6 +56,7 @@ import "./commands/StepInput.commands.js"; import "./commands/TabContainer.commands.js"; import "./commands/TimeSelectionClocks.commands.js"; import "./commands/ToggleButton.commands.js"; +import "./commands/TimePicker.commands.js"; import Orientation from "@ui5/webcomponents-base/dist/types/Orientation.js"; import type MovePlacement from "@ui5/webcomponents-base/dist/types/MovePlacement.js"; diff --git a/packages/main/cypress/support/commands/TimePicker.commands.ts b/packages/main/cypress/support/commands/TimePicker.commands.ts new file mode 100644 index 000000000000..fb341547115b --- /dev/null +++ b/packages/main/cypress/support/commands/TimePicker.commands.ts @@ -0,0 +1,75 @@ +import Button from "../../../src/Button.js"; +import TimePicker from "../../../src/TimePicker.js"; + +Cypress.Commands.add("ui5TimePickerGetInnerInput", { prevSubject: true }, subject => { + cy.wrap(subject) + .as("timePicker"); + + cy.get("@timePicker") + .shadow() + .find("[ui5-datetime-input]") + .shadow() + .find("input") + .as("innerInput"); + + return cy.get("@innerInput"); +}); + +Cypress.Commands.add("ui5TimePickerValueHelpIconPress", { prevSubject: true }, subject => { + cy.wrap(subject) + .as("timePicker"); + + cy.get("@timePicker") + .shadow() + .find("[ui5-datetime-input]") + .find(".ui5-time-picker-input-icon-button") + .realClick(); +}); + +Cypress.Commands.add("ui5TimePickerGetClock", { prevSubject: true }, (subject, clockType) => { + cy.wrap(subject) + .as("timePicker"); + + cy.get("@timePicker") + .should("have.attr", "open"); + + return cy.get("@timePicker") + .shadow() + .find("[ui5-responsive-popover]") + .find("[ui5-time-selection-clocks]") + .shadow() + .find(`ui5-toggle-spin-button[data-ui5-clock="${clockType}"]`); +}); + +Cypress.Commands.add("ui5TimePickerGetSubmitButton", { prevSubject: true }, subject => { + cy.wrap(subject) + .as("timePicker"); + + cy.get("@timePicker") + .should("have.attr", "open"); + + return cy.get("@timePicker") + .shadow() + .find("[ui5-responsive-popover]") + .find("#submit"); +}); + +declare global { + namespace Cypress { + interface Chainable { + ui5TimePickerGetInnerInput( + this: Chainable> + ): Chainable>; + ui5TimePickerValueHelpIconPress( + this: Chainable> + ): Chainable; + ui5TimePickerGetClock( + this: Chainable>, + clockType: string + ): Chainable>; + ui5TimePickerGetSubmitButton( + this: Chainable> + ): Chainable>; + } + } +} \ No newline at end of file diff --git a/packages/main/test/specs/TimePicker.spec.js b/packages/main/test/specs/TimePicker.spec.js deleted file mode 100644 index 7e9c7bafd2cc..000000000000 --- a/packages/main/test/specs/TimePicker.spec.js +++ /dev/null @@ -1,262 +0,0 @@ -import { assert } from "chai"; - -describe("TimePicker general interaction", () => { - before(async () => { - await browser.url(`test/pages/TimePicker.html?sap-ui-language=bg`); - }); - - it("input receives value in format pattern depending on the set language", async () => { - const timepicker = await browser.$("#timepickerSetTime"); - const setTimeButton = await browser.$("#setTimeButton"); - - await setTimeButton.click(); - - assert.equal(await timepicker.shadow$("ui5-datetime-input").getValue(), "3:16:16 ч."); - }); - - it("tests clocks value", async () => { - const timepicker = await browser.$("#timepicker"); - const timepickerPopover = await timepicker.shadow$("ui5-responsive-popover"); - - // act - await timepicker.setProperty("value", "11:12:13"); - await timepicker.shadow$("ui5-datetime-input").$(".ui5-time-picker-input-icon-button").click(); - - const hoursClockValue = await timepickerPopover.$("ui5-time-selection-clocks").shadow$(`ui5-time-picker-clock[data-ui5-clock="hours"]`).getProperty("selectedValue"); - const minutesClockValue = await timepickerPopover.$("ui5-time-selection-clocks").shadow$(`ui5-time-picker-clock[data-ui5-clock="minutes"]`).getProperty("selectedValue"); - const secondsClockValue = await timepickerPopover.$("ui5-time-selection-clocks").shadow$(`ui5-time-picker-clock[data-ui5-clock="seconds"]`).getProperty("selectedValue"); - - // assert - assert.strictEqual(hoursClockValue, 11, "Hours are equal"); - assert.strictEqual(minutesClockValue, 12, "Minutes are equal"); - assert.strictEqual(secondsClockValue, 13, "Seconds are equal"); - }); - - it("tests clocks submit value", async () => { - const timepicker = await browser.$("#timepicker5"); - const picker = await timepicker.shadow$("ui5-responsive-popover"); - - // act - await timepicker.shadow$("ui5-datetime-input").$(".ui5-time-picker-input-icon-button").click(); - await browser.keys("Escape"); - await timepicker.shadow$("ui5-datetime-input").$(".ui5-time-picker-input-icon-button").click(); - - // hours clock is displayed - for (let i=1; i<= 10; i++) await browser.keys("ArrowDown"); // Select 02 - - // switch to minutes clock - await browser.keys("Space"); - for (let i=1; i<= 20; i++) await browser.keys("ArrowDown"); // Select 40 - - // switch to seconds clock - await browser.keys("Space"); - for (let i=1; i<= 4; i++) await browser.keys("ArrowUp"); // Select 5 - - await browser.keys("Tab"); // Move to submit - await browser.keys("Enter"); // Enter on submit - - const textValue = await timepicker.shadow$("ui5-datetime-input").getValue(); - assert.strictEqual(textValue.substring(0,2), "02", "Hours are equal"); - assert.strictEqual(textValue.substring(3,5), "40", "Minutes are equal"); - assert.strictEqual(textValue.substring(6,8), "05", "Seconds are equal"); - }); - - it("tests submit wrong value", async () => { - const timepicker = await browser.$("#timepicker"); - - await timepicker.click(); - await browser.keys("123123123"); - await browser.keys("Enter"); - - assert.strictEqual(await timepicker.shadow$("ui5-datetime-input").getProperty("valueState"), "Negative", "The value state is on error"); - }); - - it("tests change event", async () => { - const timepicker = await browser.$("#timepickerChange"); - const input = await timepicker.shadow$("ui5-datetime-input"); - const icon = await input.$("ui5-icon"); - const changeResult = await browser.$("#changeResult"); - - // act - submit the same time - await icon.click(); - const timepickerPopover = await timepicker.shadow$("ui5-responsive-popover"); - await timepickerPopover.$("#submit").click(); - - // assert - assert.strictEqual(await changeResult.getProperty("value"), "0", "Change not fired as expected"); - - // act - submit value after changing time - await icon.click(); - - await browser.keys("PageDown"); // select 11 - for (let i=1; i<= 10; i++) await browser.keys("ArrowDown"); // Select 1 - - await timepickerPopover.$("#submit").click(); - - // assert - assert.strictEqual(await changeResult.getProperty("value"), "1", "Change fired as expected"); - - // act - submit the same time - await icon.click(); - await timepickerPopover.$("#submit").click(); - - // assert - assert.strictEqual(await changeResult.getProperty("value"), "1", "Change not fired as expected"); - - // act - submit value after changing time - await icon.click(); - await browser.keys("ArrowDown"); // select 00 - - await timepickerPopover.$("#submit").click(); // click submit (the other test tests Enter, this one tests click) - - // assert - assert.strictEqual(await changeResult.getProperty("value"), "2", "Change fired as expected"); - - //act - await input.click(); - await browser.keys("Backspace"); - await browser.keys("7"); - await browser.keys("Enter"); - - // assert - assert.strictEqual(await changeResult.getProperty("value"), "3", "Change fired as expected"); - }); - - it("tests value state", async () => { - const timepicker = await browser.$("#timepickerEmptyValue"); - const button = await browser.$("#testBtn"); - - // act - await timepicker.click(); - while(await timepicker.shadow$("ui5-datetime-input").getProperty("value") !== ""){ - await browser.keys("Backspace"); - } - await button.click(); - - // assert - assert.strictEqual(await timepicker.shadow$("ui5-datetime-input").getProperty("valueState"), "None", "The value state is None"); - }); - - it("tests input keyboard handling", async () => { - const timepicker = await browser.$("#timepicker5"); - - // act - await timepicker.click(); - await browser.keys(['Shift', 'PageUp']); - await browser.keys('Shift'); - - // assert - assert.strictEqual(await timepicker.shadow$("ui5-datetime-input").getProperty("value"), "02:41:05", "The value of minutes is +1"); - // act - await timepicker.click(); - await browser.keys(['Shift', 'PageDown']); - await browser.keys('Shift'); - - // assert - assert.strictEqual(await timepicker.shadow$("ui5-datetime-input").getProperty("value"), "02:40:05", "The value of minutes is -1"); - - // act - await timepicker.click(); - await browser.keys('PageUp'); - - // assert - assert.strictEqual(await timepicker.shadow$("ui5-datetime-input").getProperty("value"), "03:40:05", "The value of hours is +1"); - // act - await timepicker.click(); - await browser.keys('PageDown'); - - // assert - assert.strictEqual(await timepicker.shadow$("ui5-datetime-input").getProperty("value"), "02:40:05", "The value of hours is -1"); - - // act - await timepicker.click(); - await browser.keys(['Shift', 'Control', 'PageUp']); - await browser.keys('Shift'); - await browser.keys('Control'); - - // assert - assert.strictEqual(await timepicker.shadow$("ui5-datetime-input").getProperty("value"), "02:40:06", "The value of seconds is +1"); - // act - await timepicker.click(); - await browser.keys(['Shift', 'Control', 'PageDown']); - await browser.keys('Shift'); - await browser.keys('Control'); - - // assert - assert.strictEqual(await timepicker.shadow$("ui5-datetime-input").getProperty("value"), "02:40:05", "The value of seconds is -1"); - }); - - it("test closing the picker with the keyboard", async () => { - const timepicker = await browser.$("#timepicker3"); - - // act - await timepicker.shadow$("ui5-datetime-input").$(".ui5-time-picker-input-icon-button").click(); - await browser.keys(["Alt", "ArrowUp"]); - - const timepickerPopover = await timepicker.shadow$("ui5-responsive-popover"); - - // assert - assert.notOk(await timepickerPopover.isDisplayed(), "the picker should be collapsed"); - }); - - it("the value 'now' returns the current time, instead of the string 'now'", async () => { - await browser.url(`test/pages/TimePicker.html`); - - const timepicker = await browser.$("#timepicker"); - - // act - await timepicker.click(); - await browser.keys("now"); - await browser.keys("Enter"); - - // assert that the value in the input is different than the string 'now' - assert.notStrictEqual(await timepicker.shadow$("ui5-datetime-input").getProperty("value"), "now", "the value is not 'now'"); - }); - - it("opening time picker's value-help, sets the 'open' property to true", async () => { - const timepicker = await browser.$("#timepicker"); - const timepickerPopover = await timepicker.shadow$("ui5-responsive-popover"); - - // act - await timepicker.shadow$("ui5-datetime-input").$(".ui5-time-picker-input-icon-button").click(); - - assert.strictEqual(await timepicker.getProperty("open"), true, "The timepicker's open property is set to true"); - assert.strictEqual(await timepickerPopover.getProperty("open"), true, "The popover is opened"); - }) - - it("setting time picker's open property to true, opens the value-help", async () => { - const timepicker = await browser.$("#timepicker"); - const timepickerPopover = await timepicker.shadow$("ui5-responsive-popover"); - - // act - await timepicker.setProperty("open", true); - - assert.strictEqual(await timepickerPopover.getProperty("open"), true, "The popover is opened"); - }) - - it("picker popover should have accessible name", async () => { - const timepicker = await browser.$("#timepicker"); - await timepicker.click(); - await browser.keys("F4"); - - const popover = await timepicker.shadow$("ui5-responsive-popover"); - - assert.strictEqual(await popover.getAttribute("accessible-name"), "Choose Time", "Picker popover has an accessible name"); - - await browser.keys("Escape"); - }); - - it("input should have accessible name", async () => { - const timepicker = await browser.$("#timepickerAccessibleNameRef"); - const innerInput = await timepicker.shadow$("ui5-datetime-input").shadow$("input"); - - assert.strictEqual(await innerInput.getAttribute("aria-label"), "Pick a time", "Input aria-label attribute is correct."); - }); - - it("should apply aria-label from the accessibleNameRef property", async () => { - const timepicker = await browser.$("#timepickerAccessibleName"); - const innerInput = await timepicker.shadow$("ui5-datetime-input").shadow$("input"); - - assert.strictEqual(await innerInput.getAttribute("aria-label"), "Pick a time", "Input aria-label attribute is correct."); - }); -});