mirror of
https://github.com/Quad4-Software/MeshChatX.git
synced 2026-08-18 09:49:09 -04:00
190 lines
6.7 KiB
JavaScript
190 lines
6.7 KiB
JavaScript
// SPDX-License-Identifier: 0BSD
|
|
|
|
import { describe, it, expect } from "vitest";
|
|
import {
|
|
BADGE_SCALE_HOVER,
|
|
BADGE_SCALE_NORMAL,
|
|
DEFAULT_MAP_PIN_PATH,
|
|
DEFAULT_PEER_FACE,
|
|
MARKER_LABEL_MIN_ZOOM,
|
|
buildPeerBadgeSvg,
|
|
clusterBadgeStyle,
|
|
clusterBand,
|
|
contrastGlyphColor,
|
|
contrastRatio,
|
|
encodeSvgDataUrl,
|
|
getCachedClusterStyle,
|
|
getCachedPeerBadgeStyle,
|
|
peerBadgeStyle,
|
|
relativeLuminance,
|
|
shouldShowMarkerLabel,
|
|
} from "@/components/map/internal/markerStyles.js";
|
|
|
|
describe("relativeLuminance / contrastGlyphColor", () => {
|
|
it("rates white brighter than black", () => {
|
|
expect(relativeLuminance("#ffffff")).toBeGreaterThan(relativeLuminance("#000000"));
|
|
});
|
|
|
|
it("keeps a preferred glyph when contrast is strong", () => {
|
|
expect(contrastGlyphColor("#3730a3", "#ffffff")).toBe("#ffffff");
|
|
expect(contrastRatio("#ffffff", "#3730a3")).toBeGreaterThanOrEqual(3);
|
|
});
|
|
|
|
it("forces a readable glyph when preferred contrast is weak", () => {
|
|
// Light yellow on light yellow face would fail contrast.
|
|
const glyph = contrastGlyphColor("#fef9c3", "#fef08a");
|
|
expect(contrastRatio(glyph, "#fef9c3")).toBeGreaterThanOrEqual(3);
|
|
expect(glyph).toBe("#0f172a");
|
|
});
|
|
|
|
it("picks white on dark faces without a preferred color", () => {
|
|
expect(contrastGlyphColor("#0f172a")).toBe("#ffffff");
|
|
});
|
|
});
|
|
|
|
describe("buildPeerBadgeSvg", () => {
|
|
it("includes dual halo rims and a shadow", () => {
|
|
const svg = buildPeerBadgeSvg({
|
|
face: DEFAULT_PEER_FACE,
|
|
glyph: "#ffffff",
|
|
pathD: DEFAULT_MAP_PIN_PATH,
|
|
});
|
|
expect(svg).toContain('class="badge-rim-dark"');
|
|
expect(svg).toContain('class="badge-rim-light"');
|
|
expect(svg).toContain('class="badge-shadow"');
|
|
expect(svg).toContain(DEFAULT_MAP_PIN_PATH);
|
|
expect(svg).not.toContain("<animate");
|
|
});
|
|
|
|
it("mutes stale badges and skips pulse", () => {
|
|
const svg = buildPeerBadgeSvg({
|
|
face: "#ff0000",
|
|
glyph: "#00ff00",
|
|
stale: true,
|
|
tracking: true,
|
|
});
|
|
expect(svg).toContain('opacity="0.72"');
|
|
expect(svg).toContain("#64748b");
|
|
expect(svg).toContain("#e2e8f0");
|
|
expect(svg).not.toContain("<animate");
|
|
});
|
|
|
|
it("adds pulse animation only when tracking and not stale", () => {
|
|
const svg = buildPeerBadgeSvg({
|
|
face: DEFAULT_PEER_FACE,
|
|
glyph: "#ffffff",
|
|
tracking: true,
|
|
stale: false,
|
|
});
|
|
expect(svg).toContain("<animate");
|
|
expect(svg).toContain('stroke="#38bdf8"');
|
|
});
|
|
});
|
|
|
|
describe("encodeSvgDataUrl", () => {
|
|
it("round-trips a tiny SVG through base64", () => {
|
|
const svg = '<svg xmlns="http://www.w3.org/2000/svg"><circle r="1"/></svg>';
|
|
const url = encodeSvgDataUrl(svg);
|
|
expect(url.startsWith("data:image/svg+xml;base64,")).toBe(true);
|
|
const b64 = url.slice("data:image/svg+xml;base64,".length);
|
|
expect(atob(b64)).toBe(svg);
|
|
});
|
|
});
|
|
|
|
describe("clusterBand", () => {
|
|
it("returns size bands with monotonic radius", () => {
|
|
const small = clusterBand(2);
|
|
const mid = clusterBand(10);
|
|
const large = clusterBand(50);
|
|
expect(small.bandId).toBe("s");
|
|
expect(mid.bandId).toBe("m");
|
|
expect(large.bandId).toBe("l");
|
|
expect(small.radius).toBeLessThan(mid.radius);
|
|
expect(mid.radius).toBeLessThan(large.radius);
|
|
expect(small.face).not.toBe(large.face);
|
|
});
|
|
|
|
it("treats boundaries correctly", () => {
|
|
expect(clusterBand(9).bandId).toBe("s");
|
|
expect(clusterBand(10).bandId).toBe("m");
|
|
expect(clusterBand(49).bandId).toBe("m");
|
|
expect(clusterBand(50).bandId).toBe("l");
|
|
});
|
|
});
|
|
|
|
describe("clusterBadgeStyle / getCachedClusterStyle", () => {
|
|
it("returns layered styles with count text", () => {
|
|
const styles = clusterBadgeStyle({ count: 12, hovered: false });
|
|
expect(Array.isArray(styles)).toBe(true);
|
|
expect(styles).toHaveLength(2);
|
|
expect(styles[1].getText().getText()).toBe("12");
|
|
});
|
|
|
|
it("returns the same cached object for identical inputs", () => {
|
|
const cache = {};
|
|
const a = getCachedClusterStyle(cache, { count: 7, hovered: false });
|
|
const b = getCachedClusterStyle(cache, { count: 7, hovered: false });
|
|
const c = getCachedClusterStyle(cache, { count: 7, hovered: true });
|
|
expect(a).toBe(b);
|
|
expect(a).not.toBe(c);
|
|
});
|
|
});
|
|
|
|
describe("peerBadgeStyle / getCachedPeerBadgeStyle", () => {
|
|
it("builds a center-anchored Icon style", () => {
|
|
const style = peerBadgeStyle({
|
|
face: DEFAULT_PEER_FACE,
|
|
glyph: "#ffffff",
|
|
label: "Alice",
|
|
showLabel: true,
|
|
scale: BADGE_SCALE_NORMAL,
|
|
});
|
|
const image = style.getImage();
|
|
expect(image.getScale()).toBe(BADGE_SCALE_NORMAL);
|
|
expect(image.getSrc().startsWith("data:image/svg+xml;base64,")).toBe(true);
|
|
expect(image.anchorXUnits_).toBe("fraction");
|
|
expect(image.anchorYUnits_).toBe("fraction");
|
|
expect(image.anchor_).toEqual([0.5, 0.5]);
|
|
expect(style.getText().getText()).toBe("Alice");
|
|
});
|
|
|
|
it("omits text when showLabel is false", () => {
|
|
const style = peerBadgeStyle({
|
|
face: DEFAULT_PEER_FACE,
|
|
label: "Hidden",
|
|
showLabel: false,
|
|
});
|
|
expect(style.getText()).toBeNull();
|
|
});
|
|
|
|
it("caches peer styles by discrete key", () => {
|
|
const cache = {};
|
|
const a = getCachedPeerBadgeStyle(cache, {
|
|
bgColor: DEFAULT_PEER_FACE,
|
|
iconColor: "#ffffff",
|
|
scale: BADGE_SCALE_HOVER,
|
|
showLabel: false,
|
|
});
|
|
const b = getCachedPeerBadgeStyle(cache, {
|
|
bgColor: DEFAULT_PEER_FACE,
|
|
iconColor: "#ffffff",
|
|
scale: BADGE_SCALE_HOVER,
|
|
showLabel: false,
|
|
});
|
|
expect(a).toBe(b);
|
|
});
|
|
});
|
|
|
|
describe("shouldShowMarkerLabel", () => {
|
|
it("shows labels at or above the min zoom", () => {
|
|
expect(shouldShowMarkerLabel({ zoom: MARKER_LABEL_MIN_ZOOM })).toBe(true);
|
|
expect(shouldShowMarkerLabel({ zoom: MARKER_LABEL_MIN_ZOOM + 1 })).toBe(true);
|
|
expect(shouldShowMarkerLabel({ zoom: MARKER_LABEL_MIN_ZOOM - 1 })).toBe(false);
|
|
});
|
|
|
|
it("shows labels when hovered or selected even at low zoom", () => {
|
|
expect(shouldShowMarkerLabel({ zoom: 3, hovered: true })).toBe(true);
|
|
expect(shouldShowMarkerLabel({ zoom: 3, selected: true })).toBe(true);
|
|
expect(shouldShowMarkerLabel({ zoom: 3 })).toBe(false);
|
|
});
|
|
});
|