Files
python-tdd/src/static_src/tests/NatusWheelSpec.js
Disco DeDisco fbf260b148
All checks were successful
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline was successful
NATUS WHEEL: tick lines + dual conjunction tooltip — TDD
- _computeConjunctions(planets, threshold=8) detects conjunct pairs
- Tick lines (nw-planet-tick) radiate from each planet circle outward
  past the zodiac ring; animated via attrTween; styled with --pri* colours
- planetEl.raise() on mouseover puts hovered planet on top in SVG z-order
- Dual tooltip: hovering a conjunct planet shows #id_natus_tooltip_2 beside
  the primary, populated with the hidden partner's sign/degree/retrograde data
- #id_natus_tooltip_2 added to home.html, sky.html, room.html
- _natus.scss: tick line rules + both tooltip IDs share all selectors;
  #id_natus_confirm gets position:relative/z-index:1 to fix click intercept
- NatusWheelSpec.js: T7 (tick extends past zodiac), T8 (raise to front),
  T9j (conjunction dual tooltip) in new conjunction describe block
- FT T3 trimmed to element-ring hover only; planet/conjunction hover
  delegated to Jasmine (ActionChains planet-circle hover unreliable in Firefox)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 00:16:05 -04:00

226 lines
9.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ── 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)
//
// ─────────────────────────────────────────────────────────────────────────────
// 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");
});
});