← Files DataARCHIVED FILE

templates/data-app/base/tests/slider.test.mjs

10.9 KB · Sep 30, 2026 · 23:19 UTC

↓ Download file

import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import test from "node:test";

import {
  clampSliderValue,
  layoutRangeSliderLabels,
  layoutSingleSliderLabels,
  normalizeMinDistance,
  normalizeRangeValue,
  normalizeSliderBounds,
  sliderPercent,
  sliderValueAtPosition,
  updateRangeValue,
} from "../src/components/slider-math.js";

test("slider math handles fractional and collapsed bounds", () => {
  assert.equal(sliderPercent(0, 0, 0.5), 0);
  assert.equal(sliderPercent(0.25, 0, 0.5), 50);
  assert.equal(sliderPercent(0.5, 0, 0.5), 100);
  assert.equal(sliderPercent(4, 4, 4), 0);
  assert.ok(Number.isFinite(sliderPercent(4, 4, 4)));
  assert.equal(clampSliderValue(3, 4, 4), 4);
});

test("touch slider positions account for the wider thumb and snap to steps", () => {
  const rect = { left: 20, width: 244 };
  assert.equal(sliderValueAtPosition(20, rect, -20, 30, 5, 44), -20);
  assert.equal(sliderValueAtPosition(142, rect, -20, 30, 5, 44), 5);
  assert.equal(sliderValueAtPosition(264, rect, -20, 30, 5, 44), 30);
});

test("slider math normalizes bounds and clamps values", () => {
  assert.deepEqual(normalizeSliderBounds(10, -10), { minimum: -10, maximum: 10 });
  assert.equal(clampSliderValue(-20, -10, 10), -10);
  assert.equal(clampSliderValue(20, -10, 10), 10);
  assert.equal(clampSliderValue(Number.NaN, -10, 10), -10);
});

test("range values stay ordered and may touch by default", () => {
  assert.deepEqual(normalizeRangeValue([80, 20], 0, 100), [20, 80]);
  assert.deepEqual(normalizeRangeValue([50, 50], 0, 100), [50, 50]);
  assert.deepEqual(normalizeRangeValue([3, 5], 4, 4, 10), [4, 4]);
  assert.deepEqual(updateRangeValue([40, 60], "lower", 80, 0, 100), [60, 60]);
  assert.deepEqual(updateRangeValue([40, 60], "upper", 20, 0, 100), [40, 40]);
});

test("range values respect a clamped minimum distance", () => {
  assert.equal(normalizeMinDistance(-5, 0, 100), 0);
  assert.equal(normalizeMinDistance(200, 0, 100), 100);
  assert.deepEqual(normalizeRangeValue([45, 50], 0, 100, 10), [45, 55]);
  assert.deepEqual(normalizeRangeValue([98, 99], 0, 100, 10), [90, 100]);
  assert.deepEqual(updateRangeValue([40, 60], "lower", 58, 0, 100, 10), [50, 60]);
  assert.deepEqual(updateRangeValue([40, 60], "upper", 42, 0, 100, 10), [40, 50]);
});

test("range label layout separates touching values on narrow controls", () => {
  const layout = layoutRangeSliderLabels({
    width: 100,
    startPercent: 50,
    endPercent: 50,
    lowerWidth: 34,
    upperWidth: 34,
    minimumBoundWidth: 18,
    maximumBoundWidth: 24,
  });
  assert.ok(layout.upperLeft - layout.lowerLeft >= 46);
  assert.equal(layout.lowerPlacement, "edge");
  assert.equal(layout.upperPlacement, "edge");
});

test("single-slider labels fit inside the track without overlapping visible endpoints", () => {
  for (const width of [100, 280, 720]) for (const percent of [0, 10, 40, 70, 100]) {
    const layout = layoutSingleSliderLabels({ width, percent, valueWidth: 34, minimumBoundWidth: 18, maximumBoundWidth: 30 });
    assert.ok(layout.valueLeft >= 8);
    assert.ok(layout.valueLeft + 34 <= width - 8);
    if (layout.showMinimum) assert.ok(layout.valueLeft >= 8 + 18 + 6);
    if (layout.showMaximum) assert.ok(layout.valueLeft + 34 + 6 <= width - 8 - 30);
  }
});

test("Slider and RangeSlider preserve the shared source contract", async () => {
  const [component, styles] = await Promise.all([
    readFile(new URL("../src/components/Slider.jsx", import.meta.url), "utf8"),
    readFile(new URL("../src/components/form-controls.css", import.meta.url), "utf8"),
  ]);

  assert.match(component, /export function Slider\(/u);
  assert.match(component, /export function RangeSlider\(/u);
  assert.equal((component.match(/labelPlacement = "outside"/gu) ?? []).length, 2,
    "Both controls must support the same label-placement API");
  assert.equal((component.match(/layout: layoutMode = "stacked"/gu) ?? []).length, 2,
    "Both controls must expose the same reusable inline-layout option");
  assert.equal((component.match(/showBounds = false/gu) ?? []).length, 2,
    "Single and double-range sliders must keep endpoint labels hidden by default");
  assert.equal((component.match(/data-layout=\{resolvedLayout\}/gu) ?? []).length, 2);
  assert.equal((component.match(/type="range"/gu) ?? []).length, 3,
    "The single slider and both range thumbs must use native range inputs");
  assert.match(component, /minDistance = 0/u);
  assert.match(component, /aria-labelledby=\{labelId\}/u);
  assert.match(component, /aria-labelledby=\{`\$\{labelId\} \$\{lowerLabelId\}`\}/u);
  assert.match(component, /aria-labelledby=\{`\$\{labelId\} \$\{upperLabelId\}`\}/u);
  assert.match(component, /new ResizeObserver\(update\)/u);
  assert.equal((component.match(/data-slider data-selection-mode=/gu) ?? []).length, 2,
    "Both public controls need the stable drag and editing exclusion boundary");
  assert.match(component, /data-active-thumb=\{renderedActiveThumb\}/u);
  assert.equal((component.match(/"--data-slider-end-position": `\$\{[^}]+\}%`/gu) ?? []).length, 2,
    "Single and dual fills must use raw percentages and reach the visible track end");
  assert.match(component, /name=\{lowerName \?\? name\}/u);
  assert.match(component, /name=\{upperName \?\? name\}/u,
    "A shared range name must submit two ordered values unless explicit names are supplied");
  assert.equal((component.match(/type="range" min=\{bounds\.minimum\} max=\{bounds\.maximum\}/gu) ?? []).length, 2,
    "Both range thumbs must retain the same coordinate system and step base");
  assert.match(component, /boundaryActiveThumb \?\? activeThumb \?\? "upper"/u,
    "A collapsed boundary must remain reopenable after controlled parent updates");
  assert.match(component, /onChange\?\.\(updateRangeValue\(/u,
    "RangeSlider must emit its normalized pair on every native change");
  assert.match(styles, /data-active-thumb="lower"[\s\S]*data-slider-input--lower/u);
  assert.match(styles, /data-active-thumb="upper"[\s\S]*data-slider-input--upper/u);
  assert.match(styles,
    /\.data-slider-handle--upper\s*\{[^}]*left:\s*clamp\(8px,[^}]*100% - 8px/su,
    "The visible pip must stay eight pixels from either edge");
  assert.match(component, /"--data-slider-start-position": `\$\{startPercent\}%`/u);
  assert.match(component, /"--data-slider-end-position": `\$\{endPercent\}%`/u,
    "The dual-range fill and pips must share the same percentage geometry");
  assert.match(styles,
    /\.data-slider--range \.data-slider-value\s*\{[^}]*font-size:\s*14px/su,
    "Dual-range values must remain 14px");
  assert.match(styles,
    /\.data-slider--single \.data-slider-selection\s*\{[^}]*min-width:\s*min\(16px, 100%\)/su,
    "A zero-value single slider must retain a sixteen-pixel cap around its eight-pixel pip inset");
  assert.match(styles,
    /\.data-slider:not\(\[data-disabled\]\) \.data-slider-control:hover \.data-slider-selection\s*\{[^}]*var\(--text\) 14%/su,
    "Hover must strengthen only the selected-area hairline");
  assert.match(styles,
    /\.data-slider-handle\s*\{[^}]*opacity:\s*\.55/su,
    "Slider pips must remain visible at rest");
  assert.match(styles,
    /\.data-slider-label\s*\{[^}]*color:\s*var\(--secondary\)/su,
    "The label for the whole control must retain the secondary text color");
  assert.match(styles,
    /\.data-slider--range \.data-slider-value\s*\{[^}]*color:\s*var\(--text\)/su,
    "The two selected range values must use the primary text color");
  assert.match(styles,
    /--data-slider-control-height:\s*var\(--data-control-height, 32px\);[\s\S]*--data-slider-control-padding:\s*var\(--space-3, 12px\)/u,
    "Sliders must share the default control height and horizontal inset");
  assert.match(styles, /\.data-slider-control\s*\{[^}]*border-radius:\s*var\(--control-radius\)/su);
  assert.match(styles,
    /\.data-slider\[data-layout="inline"\]\s*\{[^}]*grid-template-columns:[^}]*--data-slider-inline-label-width[^}]*minmax\(0, 1fr\)[^}]*column-gap:\s*var\(--data-slider-inline-gap\)/su,
    "Inline sliders must reserve a left label column and a deliberately inset control column");
  assert.match(component,
    /placement !== "inline" && <SliderLabel controlId=\{controlId\}/u,
    "The single slider label must remain above the control unless inline placement is requested");
  assert.match(component,
    /<div ref=\{controlRef\} className="data-slider-control"[\s\S]*?>\s*\{placement === "inline" && <SliderLabel/u,
    "The inline single-slider label must sit inside the left side of the control");
  assert.match(component,
    /<output ref=\{valueRef\} htmlFor=\{controlId\}[\s\S]*data-slider-value--end/u,
    "The default single slider retains the pinned current value");
  assert.match(styles,
    /\.data-slider-value--end\s*\{[^}]*right:\s*var\(--data-slider-control-padding\)[^}]*font-size:\s*14px/su,
    "The pinned current value must remain 14px");
  assert.match(component, /data-show-bounds=\{showBounds \|\| undefined\}/u);
  assert.match(component, /showBounds && valueLayout \? \{ left: valueLayout.valueLeft, right: "auto" \}/u,
    "Optional bounds use collision-aware placement inside the track");
  assert.match(styles,
    /\.data-slider\[data-label-placement="inline"\] \.data-slider-label--inline\s*\{[^}]*position:\s*absolute[^}]*left:\s*var\(--data-slider-control-padding\)/su,
    "Inline labels for single and dual sliders must be pinned to the left edge of the control");
  assert.equal((component.match(/placement === "inline" && <SliderLabel/gu) ?? []).length, 2,
    "Single and dual sliders must render inline labels inside their controls");
  assert.match(component, /"--data-slider-midpoint": `\$\{\(startPercent \+ endPercent\) \/ 2\}%`/u);
  assert.match(component, /next\.centerInlineLabel = inlineLabelWidth > 0[\s\S]*control\.clientWidth \* startPercent/u,
    "The inline label must move only when the active band overlaps it in pixels");
  assert.match(component, /centerInlineLabel[\s\S]*?left: "var\(--data-slider-control-padding\)"[\s\S]*?right: "var\(--data-slider-control-padding\)"/u,
    "Centered-label dual-range values must stay pinned to the two outer edges");
  assert.match(styles,
    /\.data-slider--range\[data-center-inline-label\] \.data-slider-label--inline\s*\{[^}]*left:\s*var\(--data-slider-midpoint\)[^}]*translateX\(-50%\)/su,
    "The inline dual-range label must stay centered within its selected band");
  assert.match(styles,
    /\.data-slider-selection\s*\{[^}]*box-shadow:[^;}]*inset 0 0 0 1px[^}]*var\(--text\) 9%/su,
    "The selected range must retain the reference hairline shadow");
  assert.match(styles,
    /\.data-slider-track\s*\{[^}]*inset:\s*-1px[^}]*overflow:\s*visible/su,
    "The fill must extend over the control border so its hairline replaces rather than doubles it");
  assert.match(styles,
    /@container data-slider \(max-width:\s*520px\)[\s\S]*\.data-slider\[data-layout="inline"\] > \.data-slider-label--outside[\s\S]*grid-column:\s*1 \/ -1/su,
    "Inline sliders must stack based on their own available width");
  assert.match(styles, /prefers-reduced-motion:\s*reduce/u);
});

SHA-256: a4571d49ab692379f342a5c6417ab6a953cf111c1496285963e602ecc90dc8b4