UNPKG

@spectrum-web-components/button

Version:

An `<sp-button>` represents an action a user can take. sp-buttons can be clicked or tapped to perform an action or to navigate to another page. sp-buttons in Spectrum have several variations for different uses and multiple levels of loudness for various a

610 lines (609 loc) 18.9 kB
"use strict"; import "@spectrum-web-components/button/sp-button.js"; import { elementUpdated, expect, fixture, html, nextFrame, waitUntil } from "@open-wc/testing"; import { testForLitDevWarnings } from "../../../test/testing-helpers.js"; import { stub } from "sinon"; import { a11ySnapshot, findAccessibilityNode, sendKeys } from "@web/test-runner-commands"; import { spy } from "sinon"; describe("Button", () => { testForLitDevWarnings( async () => await fixture(html` <sp-button tabindex="0">Button</sp-button> `) ); describe("dev mode", () => { let consoleWarnStub; before(() => { window.__swc.verbose = true; consoleWarnStub = stub(console, "warn"); }); afterEach(() => { consoleWarnStub.resetHistory(); }); after(() => { window.__swc.verbose = false; consoleWarnStub.restore(); }); it("warns in devMode when white/black variant is provided", async () => { const el = await fixture(html` <sp-button tabindex="0" variant="white">Button</sp-button> `); await elementUpdated(el); expect(consoleWarnStub.called).to.be.true; const spyCall = consoleWarnStub.getCall(0); expect( spyCall.args.at(0).includes("deprecated"), "confirm deprecated variant warning" ).to.be.true; expect(spyCall.args.at(-1), "confirm `data` shape").to.deep.equal({ data: { localName: "sp-button", type: "api", level: "default" } }); }); }); it("loads default", async () => { const el = await fixture(html` <sp-button tabindex="0">Button</sp-button> `); await elementUpdated(el); expect(el).to.not.be.undefined; expect(el.textContent).to.include("Button"); await expect(el).to.be.accessible(); expect(el.variant).to.equal("accent"); expect(el.getAttribute("variant")).to.equal("accent"); }); it("loads default w/ element content", async () => { const el = await fixture(html` <sp-button label="Button"><svg></svg></sp-button> `); await elementUpdated(el); expect(el).to.not.be.undefined; await expect(el).to.be.accessible(); }); it("loads default w/ an icon", async () => { const el = await fixture(html` <sp-button label=""> Button <svg slot="icon"></svg> </sp-button> `); await elementUpdated(el); expect(el).to.not.be.undefined; expect(el.textContent).to.include("Button"); expect(!el.hasIcon); await expect(el).to.be.accessible(); }); it("loads default only icon", async () => { const el = await fixture(html` <sp-button label="Button" icon-only> <svg slot="icon"></svg> </sp-button> `); await elementUpdated(el); expect(el).to.not.be.undefined; await expect(el).to.be.accessible(); }); it("has a stable/predictable `updateComplete`", async () => { const test = await fixture(html` <div></div> `); let keydownTime = -1; let updateComplete1 = -1; let updateComplete2 = -1; const el = document.createElement("sp-button"); el.autofocus = true; el.addEventListener("keydown", () => { keydownTime = performance.now(); }); el.updateComplete.then(() => { updateComplete1 = performance.now(); }); el.updateComplete.then(() => { updateComplete2 = performance.now(); }); test.append(el); await nextFrame(); await nextFrame(); await nextFrame(); await nextFrame(); expect(keydownTime, "keydown happened").to.not.eq(-1); expect(updateComplete1, "first update complete happened").to.not.eq(-1); expect(updateComplete2, "first update complete happened").to.not.eq(-1); expect(updateComplete1).lte(updateComplete2); expect(updateComplete2).lte(keydownTime); }); it('manages "role"', async () => { const el = await fixture(html` <sp-button>Button</sp-button> `); await elementUpdated(el); expect(el.getAttribute("role")).to.equal("button"); el.setAttribute("href", "#"); await elementUpdated(el); expect(el.getAttribute("role")).to.equal("link"); el.removeAttribute("href"); await elementUpdated(el); expect(el.getAttribute("role")).to.equal("button"); }); it("allows label to be toggled", async () => { const testNode = document.createTextNode("Button"); const el = await fixture(html` <sp-button> ${testNode} <svg slot="icon"></svg> </sp-button> `); await elementUpdated(el); const labelTestableEl = el; expect(labelTestableEl.hasLabel, "starts with label").to.be.true; testNode.textContent = ""; await elementUpdated(el); await waitUntil(() => !labelTestableEl.hasLabel, "label is removed"); testNode.textContent = "Button"; await elementUpdated(el); expect(labelTestableEl.hasLabel, "label is returned").to.be.true; }); it("loads with href", async () => { const el = await fixture(html` <sp-button href="test_url">With Href</sp-button> `); await elementUpdated(el); expect(el).to.not.be.undefined; expect(el.textContent).to.include("With Href"); }); it("loads with href and target", async () => { const el = await fixture(html` <sp-button href="test_url" target="_blank">With Target</sp-button> `); await elementUpdated(el); expect(el).to.not.be.undefined; expect(el.textContent).to.include("With Target"); }); it("accepts shit+tab interactions", async () => { let focusedCount = 0; const el = await fixture(html` <sp-button href="test_url" target="_blank">With Target</sp-button> `); await elementUpdated(el); const input = document.createElement("input"); el.insertAdjacentElement("beforebegin", input); input.focus(); expect(document.activeElement === input).to.be.true; el.addEventListener("focus", () => { focusedCount += 1; }); expect(focusedCount).to.equal(0); await sendKeys({ press: "Tab" }); await elementUpdated(el); expect(document.activeElement === el).to.be.true; expect(focusedCount).to.equal(1); await sendKeys({ press: "Shift+Tab" }); await elementUpdated(el); expect(focusedCount).to.equal(1); expect(document.activeElement === input).to.be.true; }); it("manages `disabled`", async () => { const clickSpy = spy(); const el = await fixture(html` <sp-button @click=${() => clickSpy()}>Button</sp-button> `); await elementUpdated(el); el.click(); await elementUpdated(el); expect(clickSpy.calledOnce).to.be.true; clickSpy.resetHistory(); el.disabled = true; await elementUpdated(el); el.click(); await elementUpdated(el); expect(clickSpy.callCount).to.equal(0); clickSpy.resetHistory(); await elementUpdated(el); el.dispatchEvent(new Event("click", {})); await elementUpdated(el); expect(clickSpy.callCount).to.equal(0); clickSpy.resetHistory(); el.disabled = false; el.click(); await elementUpdated(el); expect(clickSpy.calledOnce).to.be.true; }); it("`disabled` manages `tabindex`", async () => { const el = await fixture(html` <sp-button disabled>Button</sp-button> `); await elementUpdated(el); expect(el.tabIndex).to.equal(-1); expect(el.getAttribute("tabindex")).to.equal("-1"); el.disabled = false; await elementUpdated(el); expect(el.tabIndex).to.equal(0); expect(el.getAttribute("tabindex")).to.equal("0"); el.disabled = true; await elementUpdated(el); expect(el.tabIndex).to.equal(-1); expect(el.getAttribute("tabindex")).to.equal("-1"); }); it("manages `aria-disabled`", async () => { const el = await fixture(html` <sp-button href="test_url" target="_blank">With Target</sp-button> `); await elementUpdated(el); expect(el.hasAttribute("aria-disabled"), "initially not").to.be.false; el.disabled = true; await elementUpdated(el); expect(el.getAttribute("aria-disabled")).to.equal("true"); el.disabled = false; await elementUpdated(el); expect(el.hasAttribute("aria-disabled"), "finally not").to.be.false; }); it("manages aria-label from disabled state", async () => { const el = await fixture(html` <sp-button href="test_url" target="_blank" label="clickable" disabled pending-label="Pending Button" > Click me </sp-button> `); await elementUpdated(el); expect(el.getAttribute("aria-label")).to.equal("clickable"); el.pending = true; await elementUpdated(el); expect(el.getAttribute("aria-label")).to.equal("clickable"); el.disabled = false; await elementUpdated(el); expect(el.getAttribute("aria-label")).to.equal("Pending Button"); el.pending = false; await elementUpdated(el); expect(el.getAttribute("aria-label")).to.equal("clickable"); }); it("manages aria-label from pending state", async () => { const el = await fixture(html` <sp-button href="test_url" target="_blank" label="clickable" pending > Click me </sp-button> `); await elementUpdated(el); expect(el.getAttribute("aria-label")).to.equal("Pending"); el.disabled = true; await elementUpdated(el); expect(el.getAttribute("aria-label")).to.equal("clickable"); el.pending = false; await elementUpdated(el); expect(el.getAttribute("aria-label")).to.equal("clickable"); el.disabled = false; await elementUpdated(el); expect(el.getAttribute("aria-label")).to.equal("clickable"); }); it("manages aria-label set from outside", async () => { const el = await fixture(html` <sp-button href="test_url" target="_blank" aria-label="test" pending-label="Pending Button" > Click me </sp-button> `); await elementUpdated(el); expect(el.getAttribute("aria-label")).to.equal("test"); el.pending = true; await elementUpdated(el); expect(el.getAttribute("aria-label")).to.equal("Pending Button"); el.disabled = true; await elementUpdated(el); expect(el.getAttribute("aria-label")).to.equal("test"); el.disabled = false; await elementUpdated(el); expect(el.getAttribute("aria-label")).to.equal("Pending Button"); el.pending = false; await elementUpdated(el); expect(el.getAttribute("aria-label")).to.equal("test"); }); it("updates pending label accessibly", async () => { const el = await fixture(html` <sp-button href="test_url" target="_blank">Button</sp-button> `); await elementUpdated(el); el.pending = true; await elementUpdated(el); await nextFrame(); let snapshot = await a11ySnapshot({}); expect( findAccessibilityNode( snapshot, (node) => node.name === "Pending" ), "`Pending` is the label text" ).to.not.be.null; expect(el.pending).to.be.true; el.pending = false; await elementUpdated(el); await nextFrame(); snapshot = await a11ySnapshot({}); expect( findAccessibilityNode( snapshot, (node) => node.name === "Button" ), "`Button` is the label text" ).to.not.be.null; expect(el.pending).to.be.false; }); it("manages tabIndex while disabled", async () => { const el = await fixture(html` <sp-button href="test_url" target="_blank">With Target</sp-button> `); await elementUpdated(el); expect(el.tabIndex).to.equal(0); el.disabled = true; await elementUpdated(el); expect(el.tabIndex).to.equal(-1); el.tabIndex = 2; await elementUpdated(el); expect(el.tabIndex).to.equal(-1); el.disabled = false; await elementUpdated(el); expect(el.tabIndex).to.equal(2); }); it("swallows `click` interaction when `[disabled]`", async () => { const clickSpy = spy(); const el = await fixture(html` <sp-button disabled @click=${() => clickSpy()}>Button</sp-button> `); await elementUpdated(el); expect(clickSpy.callCount).to.equal(0); el.click(); await elementUpdated(el); expect(clickSpy.callCount).to.equal(0); }); it("translates keyboard interactions to click", async () => { const clickSpy = spy(); const el = await fixture(html` <sp-button @click=${() => clickSpy()}>Button</sp-button> `); await elementUpdated(el); el.dispatchEvent( new KeyboardEvent("keypress", { bubbles: true, composed: true, cancelable: true, code: "Enter", key: "Enter" }) ); await elementUpdated(el); expect(clickSpy.callCount).to.equal(1); clickSpy.resetHistory(); el.dispatchEvent( new KeyboardEvent("keypress", { bubbles: true, composed: true, cancelable: true, code: "NumpadEnter", key: "NumpadEnter" }) ); await elementUpdated(el); expect(clickSpy.callCount).to.equal(1); clickSpy.resetHistory(); el.dispatchEvent( new KeyboardEvent("keypress", { bubbles: true, composed: true, cancelable: true, code: "Space", key: "Space" }) ); await elementUpdated(el); expect(clickSpy.callCount).to.equal(0); clickSpy.resetHistory(); el.dispatchEvent( new KeyboardEvent("keydown", { bubbles: true, composed: true, cancelable: true, code: "Space", key: "Space" }) ); el.dispatchEvent( new KeyboardEvent("keyup", { bubbles: true, composed: true, cancelable: true, code: "Space", key: "Space" }) ); await elementUpdated(el); expect(clickSpy.callCount).to.equal(1); clickSpy.resetHistory(); el.dispatchEvent( new KeyboardEvent("keydown", { bubbles: true, composed: true, cancelable: true, code: "Space", key: "Space" }) ); el.dispatchEvent( new KeyboardEvent("keyup", { bubbles: true, composed: true, cancelable: true, code: "KeyG", key: "g" }) ); await elementUpdated(el); expect(clickSpy.callCount).to.equal(0); el.dispatchEvent( new KeyboardEvent("keyup", { bubbles: true, composed: true, cancelable: true, code: "Space", key: "Space" }) ); clickSpy.resetHistory(); el.dispatchEvent( new KeyboardEvent("keydown", { bubbles: true, composed: true, cancelable: true, code: "KeyG", key: "g" }) ); el.dispatchEvent( new KeyboardEvent("keyup", { bubbles: true, composed: true, cancelable: true, code: "Space", key: "Space" }) ); await elementUpdated(el); expect(clickSpy.callCount).to.equal(0); }); it('proxies clicks by "type"', async () => { const submitSpy = spy(); const resetSpy = spy(); const test = await fixture(html` <form @submit=${(event) => { event.preventDefault(); submitSpy(); }} @reset=${(event) => { event.preventDefault(); resetSpy(); }} > <sp-button>Button</sp-button> </form> `); const el = test.querySelector("sp-button"); await elementUpdated(el); el.type = "submit"; await elementUpdated(el); el.click(); expect(submitSpy.callCount).to.equal(1); expect(resetSpy.callCount).to.equal(0); el.type = "reset"; await elementUpdated(el); el.click(); expect(submitSpy.callCount).to.equal(1); expect(resetSpy.callCount).to.equal(1); el.type = "button"; await elementUpdated(el); el.click(); expect(submitSpy.callCount).to.equal(1); expect(resetSpy.callCount).to.equal(1); }); it("proxies click by [href]", async () => { const clickSpy = spy(); const el = await fixture(html` <sp-button href="test_url">With Href</sp-button> `); await elementUpdated(el); el.anchorElement.addEventListener("click", (event) => { event.preventDefault(); event.stopPropagation(); clickSpy(); }); expect(clickSpy.callCount).to.equal(0); el.click(); await elementUpdated(el); expect(clickSpy.callCount).to.equal(1); }); it('manages "active" while focused', async () => { const el = await fixture(html` <sp-button label="Button"> <svg slot="icon"></svg> </sp-button> `); await elementUpdated(el); el.focus(); await elementUpdated(el); await sendKeys({ down: "Space" }); await elementUpdated(el); expect(el.active).to.be.true; await sendKeys({ up: "Space" }); await elementUpdated(el); expect(el.active).to.be.false; }); describe("deprecated variants and attributes", () => { it("manages [quiet]", async () => { const el = await fixture(html` <sp-button quiet>Button</sp-button> `); await elementUpdated(el); expect(el.treatment).to.equal("outline"); el.quiet = false; await elementUpdated(el); expect(el.treatment).to.equal("fill"); }); it('upgrades [variant="cta"] to [variant="accent"]', async () => { const el = await fixture(html` <sp-button variant="cta">Button</sp-button> `); await elementUpdated(el); expect(el.variant).to.equal("accent"); }); it('manages [variant="overBackground"]', async () => { const el = await fixture(html` <sp-button variant="overBackground">Button</sp-button> `); await elementUpdated(el); expect(el.hasAttribute("variant")).to.not.equal("overBackground"); expect(el.treatment).to.equal("outline"); expect(el.static).to.equal("white"); }); it('forces [variant="accent"]', async () => { const el = await fixture(html` <sp-button variant="not-supported">Button</sp-button> `); await elementUpdated(el); expect(el.variant).to.equal("accent"); }); }); }); //# sourceMappingURL=button.test.js.map