("[ui5-busy-indicator]").should(($el) => {
+ const timeoutId = $el[0]._busyTimeoutId;
+ expect(timeoutId).to.exist;
+ }).then(($el) => {
+ cy.wrap($el[0]._busyTimeoutId).as("timeoutId");
+ });
+
+ const clearTimeoutSpy = cy.spy(window, "clearTimeout");
+
+ cy.get("[ui5-busy-indicator]").invoke("removeAttr", "active");
+
+ cy.get("@timeoutId").should((timeoutId) => {
+ expect(clearTimeoutSpy).to.have.been.calledWith(timeoutId);
+ });
+
+ cy.get("[ui5-busy-indicator]")
+ .shadow()
+ .find(".ui5-busy-indicator-busy-area")
+ .should("not.exist");
+
+ cy.get("[ui5-busy-indicator]")
+ .invoke("prop", "_isBusy")
+ .should("eq", false);
+
+ cy.get("[ui5-busy-indicator]").then(($el) => {
+ const element = $el[0] as any;
+
+ element.active = true;
+ element.onBeforeRendering();
+
+ expect(element._isBusy).to.be.false;
+ expect(element._busyTimeoutId).to.exist;
+
+ element.active = false;
+ element.onBeforeRendering();
+
+ expect(element._busyTimeoutId).to.be.undefined;
+ expect(element._isBusy).to.be.false;
+ });
+ });
+
+ it("should handle zero delay", () => {
+ cy.mount(
+
+ Content
+
+ );
+
+ // Should become busy immediately
+ cy.get("[ui5-busy-indicator]")
+ .shadow()
+ .find(".ui5-busy-indicator-busy-area")
+ .should("exist");
+ });
+});
+
+describe("Different Sizes", () => {
+ Object.values(BusyIndicatorSize).forEach(size => {
+ it(`should render with size ${size}`, () => {
+ cy.mount(
+
+ Content
+
+ );
+
+ cy.get("[ui5-busy-indicator]")
+ .shadow()
+ .find(".ui5-busy-indicator-busy-area")
+ .should("exist");
+ });
+ });
+});
+
+describe("Desktop-specific Behavior", () => {
+ it("should set desktop attribute on desktop devices", () => {
+ // This test depends on the isDesktop() function
+ cy.mount(
+
+ Content
+
+ );
+
+ // On most test environments, this should be true
+ cy.get("[ui5-busy-indicator]")
+ .should("have.attr", "desktop");
+ });
+});
+
+describe("Complex Keyboard Navigation", () => {
+ it("should prevent events when busy and not when inactive", () => {
+ const onClickStub = cy.stub().as("clickStub");
+
+ cy.mount(
+
+
+
+
+
+
+
+ );
+
+ // Test when not busy - should allow interaction
+ cy.get("#innerBtn").realClick();
+ cy.get("@clickStub").should("have.been.called");
+
+ // Reset stub
+ cy.get("@clickStub").then((stub) => {
+ stub.resetHistory();
+ });
+
+ // Activate busy indicator
+ cy.get("[ui5-busy-indicator]").invoke("attr", "active", "");
+
+ // Since delay is 0, it should become busy immediately
+ cy.get("[ui5-busy-indicator]")
+ .shadow()
+ .find(".ui5-busy-indicator-busy-area")
+ .should("exist");
+
+ // Try to interact with inner button - should be prevented
+ cy.get("#innerBtn").realClick();
+ cy.get("@clickStub").should("not.have.been.called");
+ });
+
+ it("should handle Shift+Tab focus redirection", () => {
+ cy.mount(
+
+
+
+
+
+
+
+ );
+
+ // Focus the after button using realClick instead of focus()
+ cy.get("#afterBtn").realClick();
+
+ // Use Shift+Tab to go backwards
+ cy.realPress(["Shift", "Tab"]);
+
+ // Should focus the busy indicator's busy area
+ cy.get("[ui5-busy-indicator]")
+ .shadow()
+ .find(".ui5-busy-indicator-busy-area")
+ .should("have.focus");
+ });
+
+ it("should handle key events other than Tab", () => {
+ cy.mount(
+
+
+
+ );
+
+ // Focus the busy area
+ cy.get("[ui5-busy-indicator]")
+ .shadow()
+ .find(".ui5-busy-indicator-busy-area")
+ .focus();
+
+ // Try pressing other keys - should be prevented but not cause errors
+ cy.realPress("Enter");
+ cy.realPress("Space");
+ cy.realPress("Escape");
+ cy.realPress("ArrowDown");
+
+ // Busy indicator should still be focused and working
+ cy.get("[ui5-busy-indicator]")
+ .shadow()
+ .find(".ui5-busy-indicator-busy-area")
+ .should("have.focus");
+ });
+});
+
+describe("Component Lifecycle", () => {
+ it("should properly clean up on DOM exit", () => {
+ // Spy on clearTimeout to verify it's called during onExitDOM
+ cy.window().then((win) => {
+ cy.spy(win, 'clearTimeout').as('clearTimeoutSpy');
+ });
+
+ cy.mount(
+
+ );
+
+ // Verify the component has a pending timeout
+ cy.get("[ui5-busy-indicator]").then(($el) => {
+ const element = $el[0] as any;
+ expect(element._busyTimeoutId).to.exist;
+ });
+
+ // Remove the component before timeout fires - this triggers onExitDOM
+ cy.get("#container").then(($container) => {
+ $container.empty();
+ });
+
+ // Verify clearTimeout was called (onExitDOM logic)
+ cy.get('@clearTimeoutSpy').should('have.been.called');
+ });
+});
+
+describe("Edge Cases", () => {
+ it("should handle rapid active/inactive toggles", () => {
+ cy.mount(
+
+ Content
+
+ );
+
+ // Rapidly toggle active state
+ for (let i = 0; i < 5; i++) {
+ cy.get("[ui5-busy-indicator]").invoke("attr", "active", "");
+ cy.get("[ui5-busy-indicator]").invoke("removeAttr", "active");
+ }
+
+ // Final state should be inactive
+ cy.get("[ui5-busy-indicator]")
+ .shadow()
+ .find(".ui5-busy-indicator-busy-area")
+ .should("not.exist");
+ });
+
+ it("should handle text changes dynamically", () => {
+ cy.mount(
+
+ Content
+
+ );
+
+ // Initial text
+ cy.get("[ui5-busy-indicator]")
+ .shadow()
+ .find(".ui5-busy-indicator-text")
+ .should("contain.text", "Initial");
+
+ // Change text
+ cy.get("[ui5-busy-indicator]").invoke("attr", "text", "Updated");
+
+ cy.get("[ui5-busy-indicator]")
+ .shadow()
+ .find(".ui5-busy-indicator-text")
+ .should("contain.text", "Updated");
+
+ // Remove text
+ cy.get("[ui5-busy-indicator]").invoke("removeAttr", "text");
+
+ cy.get("[ui5-busy-indicator]")
+ .shadow()
+ .find(".ui5-busy-indicator-text")
+ .should("not.exist");
+ });
+});
+
+describe("Accessibility", () => {
+ it("should have proper ARIA attributes", () => {
+ cy.mount(
+
+ Content
+
+ );
+
+ cy.get("[ui5-busy-indicator]")
+ .shadow()
+ .find(".ui5-busy-indicator-busy-area")
+ .should("have.attr", "role", "progressbar")
+ .and("have.attr", "aria-valuemin", "0")
+ .and("have.attr", "aria-valuemax", "100")
+ .and("have.attr", "aria-valuetext", "Busy")
+ .and("have.attr", "tabindex", "0")
+ .and("have.attr", "data-sap-focus-ref");
+ });
+
+ it("should have proper title attribute", () => {
+ cy.mount(
+
+ Content
+
+ );
+
+ cy.get("[ui5-busy-indicator]")
+ .shadow()
+ .find(".ui5-busy-indicator-busy-area")
+ .should("have.attr", "title")
+ .and("not.be.empty");
+ });
+
+ it("should generate correct labelId when text is present", () => {
+ cy.mount(
+
+ Content
+
+ );
+
+ cy.get("[ui5-busy-indicator]")
+ .shadow()
+ .find(".ui5-busy-indicator-busy-area")
+ .should("have.attr", "aria-labelledby");
+
+ cy.get("[ui5-busy-indicator]")
+ .shadow()
+ .find(".ui5-busy-indicator-text")
+ .should("have.attr", "id");
+
+ // Verify they match each other
+ cy.get("[ui5-busy-indicator]")
+ .shadow()
+ .find(".ui5-busy-indicator-busy-area")
+ .invoke("attr", "aria-labelledby")
+ .then((labelledBy) => {
+ cy.get("[ui5-busy-indicator]")
+ .shadow()
+ .find(".ui5-busy-indicator-text")
+ .should("have.attr", "id", labelledBy);
+ });
+ });
+
+ it("should not have labelId when no text is present", () => {
+ cy.mount(
+
+ Content
+
+ );
+
+ cy.get("[ui5-busy-indicator]")
+ .shadow()
+ .find(".ui5-busy-indicator-busy-area")
+ .should("not.have.attr", "aria-labelledby");
+ });
+});