import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { mount } from "@vue/test-utils"; import { createI18n } from "vue-i18n"; import enLocale from "@/locales/languages/en-US.json"; import ORefreshButton from "./ORefreshButton.vue"; import { raw } from "@/types/i18n"; // The component calls useI18n() in setup, so an i18n instance must be installed // or vue-i18n throws "Need to install with app.use function". Source the real // en.json so refreshButton.* labels resolve as they do in the app. const i18n = createI18n({ locale: "en", legacy: false, messages: { en: enLocale }, }); // OTooltip is stubbed so the forwarded shortcut id can be asserted without mounting reka-ui. const stubs = { OButton: { template: '' }, OTooltip: { props: ["content", "shortcutId"], template: '{{ content }}', }, }; const globalConfig = { stubs, plugins: [i18n] }; describe("ORefreshButton", () => { beforeEach(() => { vi.useFakeTimers(); }); afterEach(() => { vi.useRealTimers(); }); // --- Default rendering --- it("renders the refresh button", () => { const wrapper = mount(ORefreshButton, { global: globalConfig }); expect(wrapper.find('[data-test="refresh-button"]').exists()).toBe(true); }); it("does not render a timestamp span when lastRunAt is not provided", () => { const wrapper = mount(ORefreshButton, { global: globalConfig }); expect(wrapper.find("span.tabular-nums").exists()).toBe(false); }); it("renders a timestamp span when lastRunAt is provided", () => { const ts = Date.now() - 10_000; const wrapper = mount(ORefreshButton, { props: { lastRunAt: ts }, global: globalConfig, }); expect(wrapper.find("span.tabular-nums").exists()).toBe(true); }); // --- dot color classes --- it("uses idle dot color when no lastRunAt", () => { const wrapper = mount(ORefreshButton, { global: globalConfig }); const dot = wrapper.find(".rounded-full"); expect(dot.classes()).toContain("bg-refresh-dot-idle"); }); it("uses fresh dot color when lastRunAt < 30s ago", () => { const wrapper = mount(ORefreshButton, { props: { lastRunAt: Date.now() - 5_000 }, global: globalConfig, }); const dot = wrapper.find(".rounded-full"); expect(dot.classes()).toContain("bg-refresh-dot-fresh"); }); it("uses stale dot color when lastRunAt is 30s–5min ago", () => { const wrapper = mount(ORefreshButton, { props: { lastRunAt: Date.now() - 60_000 }, global: globalConfig, }); const dot = wrapper.find(".rounded-full"); expect(dot.classes()).toContain("bg-refresh-dot-stale"); }); it("uses critical dot color when lastRunAt > 5min ago", () => { const wrapper = mount(ORefreshButton, { props: { lastRunAt: Date.now() - 400_000 }, global: globalConfig, }); const dot = wrapper.find(".rounded-full"); expect(dot.classes()).toContain("bg-refresh-dot-critical"); }); it("uses idle dot color when loading=true regardless of lastRunAt", () => { const wrapper = mount(ORefreshButton, { props: { lastRunAt: Date.now() - 5_000, loading: true }, global: globalConfig, }); const dot = wrapper.find(".rounded-full"); expect(dot.classes()).toContain("bg-refresh-dot-idle"); }); // --- emits --- it("emits click when button is clicked", async () => { const wrapper = mount(ORefreshButton, { global: globalConfig }); await wrapper.find('[data-test="refresh-button"]').trigger("click"); expect(wrapper.emitted("click")).toHaveLength(1); }); it("does not emit click when loading=true", async () => { const wrapper = mount(ORefreshButton, { props: { loading: true }, global: globalConfig, }); await wrapper.find('[data-test="refresh-button"]').trigger("click"); expect(wrapper.emitted("click")).toBeUndefined(); }); it("does not emit click when disabled=true", async () => { const wrapper = mount(ORefreshButton, { props: { disabled: true }, global: globalConfig, }); await wrapper.find('[data-test="refresh-button"]').trigger("click"); expect(wrapper.emitted("click")).toBeUndefined(); }); // --- accessible name --- it("names the button Refresh, with the age once it has run", () => { const never = mount(ORefreshButton, { global: globalConfig }); expect(never.find("button").attributes("aria-label")).toBe("Refresh"); const ran = mount(ORefreshButton, { props: { layout: "inline", lastRunAt: Date.now() - 57_000 }, global: globalConfig, }); expect(ran.find("button").attributes("aria-label")).toBe("Refresh, last refreshed 57s ago"); }); it("shows a passed tooltip instead of the last-refreshed time", () => { const wrapper = mount(ORefreshButton, { props: { lastRunAt: Date.now() - 3_000, tooltip: raw("Data as of 16:19") }, global: globalConfig, }); const tips = wrapper.findAll("[data-stub-tooltip]").map((tip) => tip.text()); expect(tips).toContain("Data as of 16:19"); expect(tips.some((tip) => tip.startsWith("Last refreshed"))).toBe(false); }); // --- layout="inline" --- describe("size", () => { const button = (props = {}) => mount(ORefreshButton, { props, global: globalConfig }).find('[data-test="refresh-button"]'); it("uses the toolbar icon size by default", () => { expect(button().attributes("size")).toBe("icon-toolbar"); expect(button().classes()).not.toContain("size-5.5!"); }); it("inset renders the icon size with size-5.5", () => { const inset = button({ size: "inset" }); expect(inset.attributes("size")).toBe("icon"); expect(inset.classes()).toContain("size-5.5!"); }); }); describe("layout=inline", () => { const inline = (props: Record = {}) => mount(ORefreshButton, { props: { layout: "inline", ...props }, global: globalConfig, }); it("renders no staleness dot", () => { const wrapper = inline({ lastRunAt: Date.now() - 5_000 }); expect(wrapper.find(".rounded-full").exists()).toBe(false); }); it("puts the age inside the button with a divider and a reserved width", () => { const wrapper = inline({ lastRunAt: Date.now() - 10_000 }); const button = wrapper.find("button"); expect(button.find("span.w-px").exists()).toBe(true); const age = button.find("span.tabular-nums"); expect(age.exists()).toBe(true); expect(age.classes()).toContain("min-w-12"); expect(age.text()).toBe("10s ago"); }); it("renders neither divider nor age when lastRunAt is null", () => { const wrapper = inline({ lastRunAt: null }); expect(wrapper.find("button").exists()).toBe(true); expect(wrapper.find("span.w-px").exists()).toBe(false); expect(wrapper.find("span.tabular-nums").exists()).toBe(false); }); it("treats a zero timestamp as never fetched", () => { const wrapper = inline({ lastRunAt: 0 }); expect(wrapper.find("span.tabular-nums").exists()).toBe(false); expect(wrapper.find("[data-stub-tooltip]").text()).toBe("Not yet refreshed"); }); it("forwards dataTest onto the button", () => { const wrapper = inline({ dataTest: "pipeline-list-refresh-btn" }); expect(wrapper.find('[data-test="pipeline-list-refresh-btn"]').exists()).toBe(true); expect(wrapper.find('[data-test="refresh-button"]').exists()).toBe(false); }); it("forwards shortcutId and the exact time to the tooltip", () => { const ts = Date.now() - 3_000; const wrapper = inline({ lastRunAt: ts, shortcutId: "pipelinesRefresh" }); const tip = wrapper.find("[data-stub-tooltip]"); expect(tip.attributes("data-shortcut")).toBe("pipelinesRefresh"); expect(tip.text()).toBe(`Last refreshed: ${new Date(ts).toLocaleTimeString()}`); }); it("emits click, and not while loading", async () => { const live = inline({ dataTest: "x-refresh" }); await live.find('[data-test="x-refresh"]').trigger("click"); expect(live.emitted("click")).toHaveLength(1); const busy = inline({ dataTest: "x-refresh", loading: true }); await busy.find('[data-test="x-refresh"]').trigger("click"); expect(busy.emitted("click")).toBeUndefined(); }); }); });