2026-04-16 01:57:02 -04:00
|
|
|
|
// ── NatusWheelSpec.js ─────────────────────────────────────────────────────────
|
|
|
|
|
|
//
|
|
|
|
|
|
// Unit specs for natus-wheel.js — planet hover tooltips.
|
|
|
|
|
|
//
|
|
|
|
|
|
// DOM contract assumed:
|
|
|
|
|
|
// <svg id="id_natus_svg"> — target for NatusWheel.draw()
|
|
|
|
|
|
// <div id="id_natus_tooltip"> — tooltip portal (position:fixed on page)
|
|
|
|
|
|
//
|
|
|
|
|
|
// Public API under test:
|
|
|
|
|
|
// NatusWheel.draw(svgEl, data) — renders wheel; attaches hover listeners
|
|
|
|
|
|
// NatusWheel.clear() — empties the SVG (used in afterEach)
|
|
|
|
|
|
//
|
|
|
|
|
|
// Hover contract:
|
|
|
|
|
|
// mouseover on [data-planet] group → adds .nw-planet--hover class
|
|
|
|
|
|
// shows #id_natus_tooltip with
|
|
|
|
|
|
// planet name, in-sign degree, sign name
|
|
|
|
|
|
// and ℞ if retrograde
|
|
|
|
|
|
// mouseout on [data-planet] group → removes .nw-planet--hover
|
|
|
|
|
|
// hides #id_natus_tooltip
|
|
|
|
|
|
//
|
|
|
|
|
|
// In-sign degree: ecliptic_longitude % 30 (e.g. 338.4° → 8.4° Pisces)
|
|
|
|
|
|
//
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
2026-04-19 00:16:05 -04:00
|
|
|
|
// Shared conjunction chart — Sun and Venus 3.4° apart in Gemini
|
|
|
|
|
|
const CONJUNCTION_CHART = {
|
|
|
|
|
|
planets: {
|
|
|
|
|
|
Sun: { sign: "Gemini", degree: 66.7, retrograde: false },
|
|
|
|
|
|
Venus: { sign: "Gemini", degree: 63.3, retrograde: false },
|
|
|
|
|
|
Mars: { sign: "Leo", degree: 132.0, retrograde: false },
|
|
|
|
|
|
},
|
|
|
|
|
|
houses: {
|
|
|
|
|
|
cusps: [180, 210, 240, 270, 300, 330, 0, 30, 60, 90, 120, 150],
|
|
|
|
|
|
asc: 180.0, mc: 90.0,
|
|
|
|
|
|
},
|
|
|
|
|
|
elements: { Fire: 1, Stone: 0, Air: 2, Water: 0, Time: 0, Space: 0 },
|
|
|
|
|
|
aspects: [],
|
|
|
|
|
|
distinctions: {
|
|
|
|
|
|
"1": 0, "2": 0, "3": 2, "4": 0, "5": 0, "6": 0,
|
|
|
|
|
|
"7": 0, "8": 0, "9": 1, "10": 0, "11": 0, "12": 0,
|
|
|
|
|
|
},
|
|
|
|
|
|
house_system: "O",
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-04-16 01:57:02 -04:00
|
|
|
|
describe("NatusWheel — planet tooltips", () => {
|
|
|
|
|
|
|
|
|
|
|
|
const SYNTHETIC_CHART = {
|
|
|
|
|
|
planets: {
|
|
|
|
|
|
Sun: { sign: "Pisces", degree: 338.4, retrograde: false },
|
|
|
|
|
|
Moon: { sign: "Capricorn", degree: 295.1, retrograde: false },
|
|
|
|
|
|
Mercury: { sign: "Aquarius", degree: 312.8, retrograde: true },
|
|
|
|
|
|
},
|
|
|
|
|
|
houses: {
|
|
|
|
|
|
cusps: [0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330],
|
|
|
|
|
|
asc: 0,
|
|
|
|
|
|
mc: 270,
|
|
|
|
|
|
},
|
|
|
|
|
|
elements: { Fire: 1, Stone: 2, Air: 1, Water: 3, Time: 1, Space: 2 },
|
|
|
|
|
|
aspects: [],
|
|
|
|
|
|
distinctions: {
|
|
|
|
|
|
"1": 0, "2": 0, "3": 0, "4": 0,
|
|
|
|
|
|
"5": 0, "6": 0, "7": 0, "8": 0,
|
|
|
|
|
|
"9": 0, "10": 0, "11": 0, "12": 0,
|
|
|
|
|
|
},
|
|
|
|
|
|
house_system: "P",
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let svgEl, tooltipEl;
|
|
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
|
// SVG element — D3 draws into this
|
|
|
|
|
|
svgEl = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
|
|
|
|
svgEl.setAttribute("id", "id_natus_svg");
|
|
|
|
|
|
svgEl.setAttribute("width", "400");
|
|
|
|
|
|
svgEl.setAttribute("height", "400");
|
|
|
|
|
|
svgEl.style.width = "400px";
|
|
|
|
|
|
svgEl.style.height = "400px";
|
|
|
|
|
|
document.body.appendChild(svgEl);
|
|
|
|
|
|
|
|
|
|
|
|
// Tooltip portal — same markup as _natus_overlay.html
|
|
|
|
|
|
tooltipEl = document.createElement("div");
|
|
|
|
|
|
tooltipEl.id = "id_natus_tooltip";
|
|
|
|
|
|
tooltipEl.className = "tt";
|
|
|
|
|
|
tooltipEl.style.display = "none";
|
|
|
|
|
|
document.body.appendChild(tooltipEl);
|
|
|
|
|
|
|
|
|
|
|
|
NatusWheel.draw(svgEl, SYNTHETIC_CHART);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
|
|
NatusWheel.clear();
|
|
|
|
|
|
svgEl.remove();
|
|
|
|
|
|
tooltipEl.remove();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ── T3 ── hover planet shows name / sign / in-sign degree + glow ─────────
|
|
|
|
|
|
|
|
|
|
|
|
it("T3: hovering a planet group adds the glow class and shows the tooltip with name, sign, and in-sign degree", () => {
|
|
|
|
|
|
const sun = svgEl.querySelector("[data-planet='Sun']");
|
|
|
|
|
|
expect(sun).not.toBeNull("expected [data-planet='Sun'] to exist in the SVG");
|
|
|
|
|
|
|
|
|
|
|
|
sun.dispatchEvent(new MouseEvent("mouseover", { bubbles: true }));
|
|
|
|
|
|
|
|
|
|
|
|
expect(sun.classList.contains("nw-planet--hover")).toBe(true);
|
|
|
|
|
|
expect(tooltipEl.style.display).toBe("block");
|
|
|
|
|
|
|
|
|
|
|
|
const text = tooltipEl.textContent;
|
|
|
|
|
|
expect(text).toContain("Sun");
|
|
|
|
|
|
expect(text).toContain("Pisces");
|
|
|
|
|
|
// in-sign degree: 338.4° ecliptic − 330° (Pisces start) = 8.4°
|
|
|
|
|
|
expect(text).toContain("8.4");
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ── T4 ── retrograde planet shows ℞ ──────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
it("T4: hovering a retrograde planet shows ℞ in the tooltip", () => {
|
|
|
|
|
|
const mercury = svgEl.querySelector("[data-planet='Mercury']");
|
|
|
|
|
|
expect(mercury).not.toBeNull("expected [data-planet='Mercury'] to exist in the SVG");
|
|
|
|
|
|
|
|
|
|
|
|
mercury.dispatchEvent(new MouseEvent("mouseover", { bubbles: true }));
|
|
|
|
|
|
|
|
|
|
|
|
expect(tooltipEl.style.display).toBe("block");
|
|
|
|
|
|
expect(tooltipEl.textContent).toContain("℞");
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ── T5 ── mouseout hides tooltip and removes glow ─────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
it("T5: mouseout hides the tooltip and removes the glow class", () => {
|
|
|
|
|
|
const sun = svgEl.querySelector("[data-planet='Sun']");
|
|
|
|
|
|
expect(sun).not.toBeNull("expected [data-planet='Sun'] to exist in the SVG");
|
|
|
|
|
|
|
|
|
|
|
|
sun.dispatchEvent(new MouseEvent("mouseover", { bubbles: true }));
|
|
|
|
|
|
expect(tooltipEl.style.display).toBe("block");
|
|
|
|
|
|
|
|
|
|
|
|
// relatedTarget is document.body — outside the planet group
|
|
|
|
|
|
sun.dispatchEvent(new MouseEvent("mouseout", {
|
|
|
|
|
|
bubbles: true,
|
|
|
|
|
|
relatedTarget: document.body,
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
expect(tooltipEl.style.display).toBe("none");
|
|
|
|
|
|
expect(sun.classList.contains("nw-planet--hover")).toBe(false);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
2026-04-19 00:16:05 -04:00
|
|
|
|
|
|
|
|
|
|
describe("NatusWheel — conjunction features", () => {
|
|
|
|
|
|
|
|
|
|
|
|
let svgEl2, tooltipEl, tooltip2El;
|
|
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
|
svgEl2 = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
|
|
|
|
svgEl2.setAttribute("id", "id_natus_svg_conj");
|
|
|
|
|
|
svgEl2.setAttribute("width", "400");
|
|
|
|
|
|
svgEl2.setAttribute("height", "400");
|
|
|
|
|
|
svgEl2.style.width = "400px";
|
|
|
|
|
|
svgEl2.style.height = "400px";
|
|
|
|
|
|
document.body.appendChild(svgEl2);
|
|
|
|
|
|
|
|
|
|
|
|
tooltipEl = document.createElement("div");
|
|
|
|
|
|
tooltipEl.id = "id_natus_tooltip";
|
|
|
|
|
|
tooltipEl.className = "tt";
|
|
|
|
|
|
tooltipEl.style.display = "none";
|
|
|
|
|
|
tooltipEl.style.position = "fixed";
|
|
|
|
|
|
document.body.appendChild(tooltipEl);
|
|
|
|
|
|
|
|
|
|
|
|
tooltip2El = document.createElement("div");
|
|
|
|
|
|
tooltip2El.id = "id_natus_tooltip_2";
|
|
|
|
|
|
tooltip2El.className = "tt";
|
|
|
|
|
|
tooltip2El.style.display = "none";
|
|
|
|
|
|
tooltip2El.style.position = "fixed";
|
|
|
|
|
|
document.body.appendChild(tooltip2El);
|
|
|
|
|
|
|
|
|
|
|
|
NatusWheel.draw(svgEl2, CONJUNCTION_CHART);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
|
|
NatusWheel.clear();
|
|
|
|
|
|
svgEl2.remove();
|
|
|
|
|
|
tooltipEl.remove();
|
|
|
|
|
|
tooltip2El.remove();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ── T7 ── tick extends past zodiac ring ───────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
it("T7: each planet has a tick line whose outer endpoint extends past the sign ring", () => {
|
|
|
|
|
|
const tick = svgEl2.querySelector(".nw-planet-tick");
|
|
|
|
|
|
expect(tick).not.toBeNull("expected at least one .nw-planet-tick element");
|
|
|
|
|
|
|
|
|
|
|
|
const cx = 200, cy = 200;
|
|
|
|
|
|
const x2 = parseFloat(tick.getAttribute("x2"));
|
|
|
|
|
|
const y2 = parseFloat(tick.getAttribute("y2"));
|
|
|
|
|
|
const rOuter = Math.sqrt((x2 - cx) ** 2 + (y2 - cy) ** 2);
|
|
|
|
|
|
// _r = Math.min(400,400) * 0.46 = 184; signOuter = _r * 0.90 = 165.6
|
|
|
|
|
|
const signOuter = 400 * 0.46 * 0.90;
|
|
|
|
|
|
expect(rOuter).toBeGreaterThan(signOuter);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ── T8 ── hover raises planet to front ────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
it("T8: hovering a planet raises it to the last DOM position (visually on top)", () => {
|
|
|
|
|
|
const sun = svgEl2.querySelector("[data-planet='Sun']");
|
|
|
|
|
|
const venus = svgEl2.querySelector("[data-planet='Venus']");
|
|
|
|
|
|
expect(sun).not.toBeNull("expected [data-planet='Sun']");
|
|
|
|
|
|
expect(venus).not.toBeNull("expected [data-planet='Venus']");
|
|
|
|
|
|
|
|
|
|
|
|
sun.dispatchEvent(new MouseEvent("mouseover", { bubbles: true, relatedTarget: document.body }));
|
|
|
|
|
|
venus.dispatchEvent(new MouseEvent("mouseover", { bubbles: true, relatedTarget: document.body }));
|
|
|
|
|
|
|
|
|
|
|
|
const groups = Array.from(svgEl2.querySelectorAll(".nw-planet-group"));
|
|
|
|
|
|
expect(groups[groups.length - 1].getAttribute("data-planet")).toBe("Venus");
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ── T9j ── dual tooltip fires for conjunct planet ─────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
it("T9j: hovering a conjunct planet shows a second tooltip for its partner", () => {
|
|
|
|
|
|
const sun = svgEl2.querySelector("[data-planet='Sun']");
|
|
|
|
|
|
expect(sun).not.toBeNull("expected [data-planet='Sun']");
|
|
|
|
|
|
|
|
|
|
|
|
sun.dispatchEvent(new MouseEvent("mouseover", { bubbles: true, relatedTarget: document.body }));
|
|
|
|
|
|
|
|
|
|
|
|
expect(tooltipEl.style.display).toBe("block");
|
|
|
|
|
|
expect(tooltip2El.style.display).toBe("block");
|
|
|
|
|
|
expect(tooltip2El.textContent).toContain("Venus");
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|