// ── NatusWheelSpec.js ───────────────────────────────────────────────────────── // // Unit specs for natus-wheel.js — planet hover tooltips. // // DOM contract assumed: // — target for NatusWheel.draw() //
— 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) // // ───────────────────────────────────────────────────────────────────────────── // 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", }; 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); }); }); 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"); }); });