test.web/presentation/components/tab.component.test.ts
test.web/presentation/components/tab.component.test.ts is a file in Codebase Testing. 101 lines of code and 0 definitions.
import { afterEach, describe, expect, it, vi } from "vitest";
import {
createJumpButton,
createTabButton,
scrollToId,
scroller,
} from "@banes-lab/web/presentation/components/tab.component.ts";
import { ACTIVE_CLASS } from "@banes-lab/web/configuration/constants/element.constants.ts";
import { MAIN_ID } from "@banes-lab/web/core/ids/site.ids.ts";
import { TAB_SELECTED } from "@banes-lab/web/core/ids/tab.ids.ts";
import type { Tab } from "@banes-lab/web/types/document.types.ts";
import { createElement } from "@banes-lab/web/core/factories/element.factory.ts";
import { subscribeEvent } from "@banes-lab/web/core/buses/base.bus.ts";
const PAGE = "grammar";
const TARGET = "target-section";
const SYMBOL = "A1.2";
const LABEL = "Jump";
const TAB: Tab = { icon: "bi-book", id: "guide", label: "Guide", sections: [] };
const HREF = "/grammar/guide";
const mountMain = function mountMain(): HTMLElement {
const main = createElement("main", { attributes: { id: MAIN_ID } });
document.body.append(main);
return main;
};
afterEach(() => {
document.body.replaceChildren();
});
describe("scroller", () => {
it("finds the main scroll container by its id", () => {
expect(scroller()).toBeNull();
const main = mountMain();
expect(scroller()).toBe(main);
});
});
describe("scrollToId", () => {
it("scrolls the main container smoothly so the target sits centered, and re-measures once the scroll ends", () => {
const main = mountMain();
const scrolled = vi.fn();
let targetTop = 500;
Object.defineProperty(main, "scrollTo", { configurable: true, value: scrolled });
Object.defineProperty(main, "getBoundingClientRect", {
configurable: true,
value: () => ({ height: 800, top: 0 }),
});
const target = createElement("section", { attributes: { id: TARGET } });
Object.defineProperty(target, "getBoundingClientRect", {
configurable: true,
value: () => ({ height: 100, top: targetTop }),
});
main.append(target);
scrollToId(TARGET);
expect(scrolled).toHaveBeenCalledWith({ behavior: "smooth", top: 150 });
targetTop = 900;
main.dispatchEvent(new Event("scrollend"));
main.dispatchEvent(new Event("scrollend"));
expect(scrolled).toHaveBeenCalledTimes(2);
expect(scrolled).toHaveBeenLastCalledWith({ behavior: "smooth", top: 550 });
});
it("leaves the scroll position alone when the target is already centered", () => {
const main = mountMain();
const scrolled = vi.fn();
Object.defineProperty(main, "scrollTo", { configurable: true, value: scrolled });
main.append(createElement("section", { attributes: { id: TARGET } }));
scrollToId(TARGET);
expect(scrolled).not.toHaveBeenCalled();
});
it("opens the target's own collapsed details and every collapsed ancestor before scrolling", () => {
const main = mountMain();
Object.defineProperty(main, "scrollTo", { configurable: true, value: vi.fn() });
const outer = createElement("details");
const target = createElement("section", { attributes: { id: TARGET } });
const own = createElement("details");
target.append(own);
outer.append(target);
main.append(outer);
scrollToId(TARGET);
expect(outer.open).toBe(true);
expect(own.open).toBe(true);
});
});
describe("createTabButton", () => {
it("marks the active tab and emits the selection on click", () => {
const selected: string[] = [];
const dispose = subscribeEvent(TAB_SELECTED, (event) => {
selected.push(event.tab);
});
const button = createTabButton(PAGE, TAB, true, HREF);
expect(button.classList.contains(ACTIVE_CLASS)).toBe(true);
expect(button.getAttribute("href")).toBe(HREF);
expect(button.textContent.includes(TAB.label)).toBe(true);
button.click();
dispose();
expect(selected).toStrictEqual([TAB.id]);
});
});
describe("createJumpButton", () => {
it("shows the symbol and label", () => {
const button = createJumpButton(SYMBOL, LABEL, TARGET);
expect(button.textContent).toBe(SYMBOL + LABEL);
});
});