import { mount } from "@vue/test-utils"; import type { MaybeRefOrGetter } from "@vueuse/core"; import { afterEach, describe, expect, it, vi } from "vitest"; import { defineComponent, nextTick, ref } from "vue"; import { useRoundRobinSelector } from "@/composables/roundRobinSelector"; vi.useFakeTimers(); function mountWithComposable(items: MaybeRefOrGetter, pollInterval = 1000) { const exposed = { currentItem: ref(null), next: () => {}, stop: () => {}, start: () => {}, }; const TestComponent = defineComponent({ setup() { const result = useRoundRobinSelector(items, pollInterval); Object.assign(exposed, result); return () => null; // no template/render needed }, }); mount(TestComponent); return exposed; } function advanceTimersAndTick(ms: number) { vi.advanceTimersByTime(ms); return nextTick(); } describe("useRoundRobinSelector", () => { afterEach(() => { vi.clearAllTimers(); }); it("initializes with the first item", async () => { const { currentItem } = mountWithComposable(["a", "b", "c"]); await nextTick(); expect(currentItem.value).toBe("a"); }); it("cycles through items over time", async () => { const { currentItem } = mountWithComposable(["x", "y", "z"]); await nextTick(); expect(currentItem.value).toBe("x"); await advanceTimersAndTick(1000); expect(currentItem.value).toBe("y"); await advanceTimersAndTick(1000); expect(currentItem.value).toBe("z"); await advanceTimersAndTick(1000); expect(currentItem.value).toBe("x"); }); it("can manually go to next item", async () => { const { currentItem, next } = mountWithComposable(["apple", "banana"]); await nextTick(); expect(currentItem.value).toBe("apple"); next(); expect(currentItem.value).toBe("banana"); next(); expect(currentItem.value).toBe("apple"); }); it("stops interval after stop() is called", async () => { const { currentItem, stop } = mountWithComposable(["a", "b", "c"]); await nextTick(); expect(currentItem.value).toBe("a"); stop(); await advanceTimersAndTick(3000); expect(currentItem.value).toBe("a"); // unchanged }); it("handles empty list", async () => { const { currentItem, next } = mountWithComposable([]); await nextTick(); expect(currentItem.value).toBe(null); next(); expect(currentItem.value).toBe(null); }); it("resets to first item when items change", async () => { const items = ref(["apple", "banana"]); const { currentItem, next } = mountWithComposable(items); await nextTick(); expect(currentItem.value).toBe("apple"); items.value = ["cherry", "date"]; await nextTick(); expect(currentItem.value).toBe("cherry"); next(); expect(currentItem.value).toBe("date"); }); it("starts interval when items are added", async () => { const items = ref([]); const { currentItem, start } = mountWithComposable(items); await nextTick(); expect(currentItem.value).toBe(null); items.value = ["a", "b"]; start(); await advanceTimersAndTick(1000); expect(currentItem.value).toBe("a"); await advanceTimersAndTick(1000); expect(currentItem.value).toBe("b"); }); it("stops interval when items are cleared", async () => { const items = ref(["a", "b"]); const { currentItem } = mountWithComposable(items); await nextTick(); expect(currentItem.value).toBe("a"); items.value = []; await advanceTimersAndTick(1000); expect(currentItem.value).toBe(null); // should not change }); });