Download
90 lines 2.7 KiBJavaScriptModified 2026-08-01 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
// Drives the meter toolkit (peek, segment fill) through its interfaces
// with fake timers.

import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import {
  METER_SETTINGS,
  meterValueCss,
  makePeek,
  segmentFill,
} from "../src/lib/meter.js";

beforeEach(() => {
  vi.useFakeTimers();
});

afterEach(() => {
  vi.useRealTimers();
});

describe("makePeek", () => {
  it("reveals the value and re-collapses after the delay", () => {
    const el = document.createElement("div");
    el.classList.add("compact");
    const peek = makePeek(el);

    peek.show();
    expect(el.classList.contains("compact")).toBe(false);
    vi.advanceTimersByTime(5000);
    expect(el.classList.contains("compact")).toBe(true);
  });

  it("another change extends the reveal instead of collapsing early", () => {
    const el = document.createElement("div");
    const peek = makePeek(el);

    peek.show();
    vi.advanceTimersByTime(4000);
    peek.show(); // change arrives while revealed
    vi.advanceTimersByTime(4000);
    expect(el.classList.contains("compact")).toBe(false);
    vi.advanceTimersByTime(1000);
    expect(el.classList.contains("compact")).toBe(true);
  });

  it("cancel() stops the pending collapse", () => {
    const el = document.createElement("div");
    const peek = makePeek(el);

    peek.show();
    peek.cancel();
    vi.advanceTimersByTime(10000);
    expect(el.classList.contains("compact")).toBe(false);
  });
});

describe("segmentFill", () => {
  const opacities = (els) => els.map((el) => Number(el.style.opacity));

  it("fades each element across its own slice of the range", () => {
    const els = [1, 2, 3].map(() => document.createElement("i"));
    segmentFill(els, 50, 0.15);
    // Slice = 33.3: first full, second halfway, third at the floor.
    expect(opacities(els)[0]).toBeCloseTo(1.0, 2);
    expect(opacities(els)[1]).toBeCloseTo(0.575, 2);
    expect(opacities(els)[2]).toBeCloseTo(0.15, 2);
  });

  it("full and empty values hit the bounds", () => {
    const els = [1, 2, 3, 4].map(() => document.createElement("i"));
    segmentFill(els, 100, 0.3);
    expect(opacities(els)).toEqual([1, 1, 1, 1]);
    segmentFill(els, 0, 0.3);
    expect(opacities(els)).toEqual([0.3, 0.3, 0.3, 0.3]);
  });
});

describe("shared schema and CSS", () => {
  it("exposes the showPercent/peekOnChange settings pair", () => {
    expect(METER_SETTINGS.map((s) => s.key)).toEqual([
      "showPercent",
      "peekOnChange",
    ]);
  });

  it("scopes the value CSS to the given widget class", () => {
    const css = meterValueCss("widget-volume");
    expect(css).toContain(".widget-volume .value");
    expect(css).toContain(".widget-volume.compact .value");
  });
});