Skip to content
Open
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
323 changes: 309 additions & 14 deletions packages/fiori/cypress/specs/Timeline.cy.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import Timeline from "../../src/Timeline.js";
import type { TimelineSearchEventDetail, TimelineSortEventDetail } from "../../src/Timeline.js";
import TimelineItem from "../../src/TimelineItem.js";
import TimelineGroupItem from "../../src/TimelineGroupItem.js";
import TimelineHeaderBar from "../../src/TimelineHeaderBar.js";
import TimelineFilterOption from "../../src/TimelineFilterOption.js";
import accept from "@ui5/webcomponents-icons/dist/accept.js";
import calendar from "@ui5/webcomponents-icons/dist/calendar.js";
import messageInformation from "@ui5/webcomponents-icons/dist/message-information.js";
import Label from "@ui5/webcomponents/dist/Label.js";
import Avatar from "@ui5/webcomponents/dist/Avatar.js";
import UI5Element from "@ui5/webcomponents-base";
import Button from "@ui5/webcomponents/dist/Button.js";
import Input from "@ui5/webcomponents/dist/Input.js";

Expand Down Expand Up @@ -478,11 +480,12 @@ describe("Timeline - getFocusDomRef", () => {
</Timeline>
);

cy.get<UI5Element>("[ui5-timeline], #firstItem")
.then(($el) => {
const timeline = $el[0],
firstItem = $el[1];
expect(timeline.getFocusDomRef()).to.equal(firstItem.getFocusDomRef());
cy.get<Timeline>("[ui5-timeline]")
.then(($timeline) => {
cy.get<TimelineItem>("#firstItem")
.then(($firstItem) => {
expect($timeline[0].getFocusDomRef()).to.equal($firstItem[0].getFocusDomRef());
});
});
});

Expand All @@ -495,21 +498,313 @@ describe("Timeline - getFocusDomRef", () => {
</Timeline>
);

cy.get("[ui5-timeline]")
.as("timeline");

cy.get("[ui5-timeline]")
.find("#lastItem")
.realClick();

cy.get<UI5Element>("[ui5-timeline], #lastItem")
.then(($el) => {
const timeline = $el[0],
lastItem = $el[1];
expect(timeline.getFocusDomRef()).to.equal(lastItem.getFocusDomRef());
cy.get<Timeline>("[ui5-timeline]")
.then(($timeline) => {
cy.get<TimelineItem>("#lastItem")
.then(($lastItem) => {
expect($timeline[0].getFocusDomRef()).to.equal($lastItem[0].getFocusDomRef());
});
});
});
});

describe("Timeline Header Bar", () => {
describe("Search functionality", () => {
it("should show header bar when slotted", () => {
cy.mount(
<Timeline>
<TimelineHeaderBar slot="headerBar" showSearch />
<TimelineItem titleText="Meeting" />
<TimelineItem titleText="Call" />
</Timeline>
);

cy.get("[ui5-timeline]")
.shadow()
.find(".ui5-timeline-header-bar-wrapper")
.should("exist");
});

it("should fire search event when user types in search input", () => {
cy.mount(
<Timeline>
<TimelineHeaderBar slot="headerBar" showSearch />
<TimelineItem titleText="Meeting" />
</Timeline>
);

cy.get("[ui5-timeline]").then($timeline => {
$timeline.get(0).addEventListener("search", cy.stub().as("searchEvent"));
});

cy.get("[ui5-timeline-header-bar]")
.shadow()
.find("[ui5-input]")
.realClick();

cy.realType("Meeting");

cy.get("@searchEvent").should("have.been.called");
});

it("should include search value in event detail", () => {
cy.mount(
<Timeline>
<TimelineHeaderBar slot="headerBar" showSearch />
<TimelineItem titleText="Meeting" />
</Timeline>
);

let searchValue = "";
cy.get("[ui5-timeline]").then($timeline => {
$timeline.get(0).addEventListener("search", (e: CustomEvent<TimelineSearchEventDetail>) => {
searchValue = e.detail.value;
});
});

cy.get("[ui5-timeline-header-bar]")
.shadow()
.find("[ui5-input]")
.realClick();

cy.realType("Test");

cy.wrap(null).then(() => {
expect(searchValue).to.equal("Test");
});
});
});

describe("Filter functionality", () => {
it("should show filter dropdown when showFilter is true", () => {
cy.mount(
<Timeline>
<TimelineHeaderBar slot="headerBar" showFilter filterBy="Status">
<TimelineFilterOption text="All" selected />
<TimelineFilterOption text="Open" />
</TimelineHeaderBar>
<TimelineItem titleText="Meeting" />
</Timeline>
);

cy.get("[ui5-timeline-header-bar]")
.shadow()
.find("[ui5-select]")
.should("exist");
});

it("should fire filter event when filter selection changes", () => {
cy.mount(
<Timeline>
<TimelineHeaderBar slot="headerBar" showFilter filterBy="Status">
<TimelineFilterOption text="All" selected />
<TimelineFilterOption text="Open" />
</TimelineHeaderBar>
<TimelineItem titleText="Meeting" />
</Timeline>
);

cy.get("[ui5-timeline]").then($timeline => {
$timeline.get(0).addEventListener("filter", cy.stub().as("filterEvent"));
});

// Click on the select to open it
cy.get("[ui5-timeline-header-bar]")
.shadow()
.find("[ui5-select]")
.realClick();

// Use keyboard to select next option
cy.realPress("ArrowDown");
cy.realPress("Enter");

cy.get("@filterEvent").should("have.been.called");
});
});

describe("Sort functionality", () => {
it("should show sort button when showSort is true", () => {
cy.mount(
<Timeline>
<TimelineHeaderBar slot="headerBar" showSort />
<TimelineItem titleText="Meeting" />
</Timeline>
);

cy.get("[ui5-timeline-header-bar]")
.shadow()
.find("[ui5-button]")
.should("exist");
});

it("should fire sort event when user clicks sort button", () => {
cy.mount(
<Timeline>
<TimelineHeaderBar slot="headerBar" showSort />
<TimelineItem titleText="Meeting" />
</Timeline>
);

cy.get("[ui5-timeline]").then($timeline => {
$timeline.get(0).addEventListener("sort", cy.stub().as("sortEvent"));
});

cy.get("[ui5-timeline-header-bar]")
.shadow()
.find("[ui5-button]")
.realClick();

cy.get("@sortEvent").should("have.been.called");
});

it("should toggle sort order on consecutive clicks", () => {
cy.mount(
<Timeline>
<TimelineHeaderBar slot="headerBar" showSort />
<TimelineItem titleText="Meeting" />
</Timeline>
);

const sortOrders: string[] = [];
cy.get("[ui5-timeline]").then($timeline => {
$timeline.get(0).addEventListener("sort", (e: CustomEvent<TimelineSortEventDetail>) => {
sortOrders.push(e.detail.sortOrder);
});
});

cy.get("[ui5-timeline-header-bar]")
.shadow()
.find("[ui5-button]")
.as("sortButton");

cy.get("@sortButton").realClick();
cy.get("@sortButton").realClick();
cy.get("@sortButton").realClick();

cy.wrap(null).then(() => {
expect(sortOrders).to.deep.equal(["Ascending", "Descending", "Ascending"]);
});
});
});

describe("Accessibility", () => {
it("should have correct ARIA role on header bar", () => {
cy.mount(
<Timeline>
<TimelineHeaderBar slot="headerBar" showSearch showFilter showSort />
<TimelineItem titleText="Meeting" />
</Timeline>
);

cy.get("[ui5-timeline-header-bar]")
.shadow()
.find(".ui5-timeline-header-bar")
.should("have.attr", "role", "toolbar");
});

it("should have accessible name on search input", () => {
cy.mount(
<Timeline>
<TimelineHeaderBar slot="headerBar" showSearch />
<TimelineItem titleText="Meeting" />
</Timeline>
);

cy.get("[ui5-timeline-header-bar]")
.shadow()
.find("[ui5-input]")
.should("have.attr", "accessible-name");
});
});

describe("Combined features", () => {
it("should support all features together", () => {
cy.mount(
<Timeline>
<TimelineHeaderBar slot="headerBar" showSearch showFilter showSort filterBy="Category">
<TimelineFilterOption text="All" selected />
<TimelineFilterOption text="Work" />
<TimelineFilterOption text="Personal" />
</TimelineHeaderBar>
<TimelineItem titleText="Work Meeting" />
<TimelineItem titleText="Personal Call" />
</Timeline>
);

// Search input should exist
cy.get("[ui5-timeline-header-bar]")
.shadow()
.find("[ui5-input]")
.should("exist");

// Filter select should exist
cy.get("[ui5-timeline-header-bar]")
.shadow()
.find("[ui5-select]")
.should("exist");

// Sort button should exist
cy.get("[ui5-timeline-header-bar]")
.shadow()
.find("[ui5-button]")
.should("exist");
});
});

describe("Application-side filtering", () => {
it("application can remove items from DOM based on search event", () => {
cy.mount(
<Timeline id="appFilterTimeline">
<TimelineHeaderBar slot="headerBar" showSearch />
<TimelineItem titleText="Meeting with John" />
<TimelineItem titleText="Call with Jane" />
</Timeline>
);

// Application handles filtering by removing non-matching items from DOM
cy.get("[ui5-timeline]").then($timeline => {
const timeline = $timeline.get(0) as Timeline;
const allItems = Array.from(timeline.querySelectorAll("[ui5-timeline-item]")) as TimelineItem[];

timeline.addEventListener("search", (e: CustomEvent<TimelineSearchEventDetail>) => {
const searchValue = e.detail.value.toLowerCase();

if (searchValue === "") {
// Restore all items when search is cleared
allItems.forEach(item => {
if (!item.parentElement) {
timeline.appendChild(item);
}
});
} else {
// Remove non-matching items from DOM
allItems.forEach(item => {
const titleText = item.titleText?.toLowerCase() || "";
if (!titleText.includes(searchValue)) {
item.remove();
} else if (!item.parentElement) {
timeline.appendChild(item);
}
});
}
});
});

// Type in search
cy.get("[ui5-timeline-header-bar]")
.shadow()
.find("[ui5-input]")
.realClick();

cy.realType("Meeting");

// Application filtered - only matching item remains in DOM
cy.get("[ui5-timeline-item]").should("have.length", 1);
cy.get("[ui5-timeline-item]").eq(0).should("have.attr", "title-text", "Meeting with John");
});
});
});
Loading
Loading