From 0273dd28972e7bb18d7b52f76b742e6bb7942ca6 Mon Sep 17 00:00:00 2001 From: mayokunl <153923029+mayokunl@users.noreply.github.com> Date: Mon, 14 Sep 2026 13:22:22 -0500 Subject: [PATCH 1/3] Worked on Issue #248, deselecting UI behavior wasnt showing paret info, now it does --- templates/js/dropdowns.js | 19 +++++-- templates/tests/dropdowns.test.js | 89 +++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 templates/tests/dropdowns.test.js diff --git a/templates/js/dropdowns.js b/templates/js/dropdowns.js index 498a033..53c86d5 100644 --- a/templates/js/dropdowns.js +++ b/templates/js/dropdowns.js @@ -155,12 +155,19 @@ boneSelect.addEventListener("change", (e) => { loadDescription(selectedBoneId); const opts = { boneId: selectedBoneId }; - + loadBoneImages(selectedBoneId, opts); } else { const stage = getImageStage(); if (stage) { clearAnnotations(stage); stage.classList.remove("with-annotations"); } - showPlaceholder(); + + const selectedBonesetId = bonesetSelect.value; + if (selectedBonesetId) { + loadDescription(selectedBonesetId); + loadBoneImages(selectedBonesetId, { boneId: selectedBonesetId }); + } else { + showPlaceholder(); + } } }); @@ -184,7 +191,13 @@ subboneSelect.addEventListener("change", (e) => { loadBoneImages(selectedSubboneId, opts); } else { - showPlaceholder(); + const selectedBoneId = boneSelect.value; + if (selectedBoneId) { + loadDescription(selectedBoneId); + loadBoneImages(selectedBoneId, { boneId: selectedBoneId }); + } else { + showPlaceholder(); + } } }); diff --git a/templates/tests/dropdowns.test.js b/templates/tests/dropdowns.test.js new file mode 100644 index 0000000..7f67fe1 --- /dev/null +++ b/templates/tests/dropdowns.test.js @@ -0,0 +1,89 @@ +jest.mock("../js/description.js", () => ({ + loadDescription: jest.fn(), +})); +jest.mock("../js/imageDisplay.js", () => ({ + displayBoneImages: jest.fn(), + showPlaceholder: jest.fn(), +})); +jest.mock("../js/annotationOverlay.js", () => ({ + clearAnnotations: jest.fn(), +})); +jest.mock("../js/api.js", () => ({ + fetchBoneData: jest.fn(() => Promise.resolve({ images: [{ url: "test.jpg" }] })), +})); + +const { loadDescription } = require("../js/description.js"); +const { showPlaceholder } = require("../js/imageDisplay.js"); +const { setupDropdownListeners, populateBonesetDropdown } = require("../js/dropdowns.js"); + +const combinedData = { + bonesets: [{ id: "bony_pelvis", name: "Bony Pelvis" }], + bones: [{ id: "ilium", name: "Ilium", boneset: "bony_pelvis" }], + subbones: [{ id: "iliac_crest", name: "Iliac Crest", bone: "ilium" }], +}; + +function renderDropdownsHTML() { + document.body.innerHTML = ` + + + +
+ `; +} + +function selectValue(select, value) { + select.value = value; + select.dispatchEvent(new Event("change")); +} + +describe("Deselecting a bone/sub-bone reverts to parent info - Issue 248", () => { + let bonesetSelect, boneSelect, subboneSelect; + + beforeEach(() => { + jest.clearAllMocks(); + renderDropdownsHTML(); + + bonesetSelect = document.getElementById("boneset-select"); + boneSelect = document.getElementById("bone-select"); + subboneSelect = document.getElementById("subbone-select"); + + // Populate the boneset options; the bone/sub-bone options are populated + // dynamically by the listeners themselves as each level is selected. + populateBonesetDropdown(combinedData.bonesets); + setupDropdownListeners(combinedData); + }); + + test("deselecting a bone falls back to the boneset info instead of the placeholder", async () => { + selectValue(bonesetSelect, "bony_pelvis"); + selectValue(boneSelect, "ilium"); + loadDescription.mockClear(); + showPlaceholder.mockClear(); + + selectValue(boneSelect, ""); + await new Promise(process.nextTick); + + expect(loadDescription).toHaveBeenCalledWith("bony_pelvis"); + expect(showPlaceholder).not.toHaveBeenCalled(); + }); + + test("deselecting a sub-bone falls back to the parent bone info instead of the placeholder", async () => { + selectValue(bonesetSelect, "bony_pelvis"); + selectValue(boneSelect, "ilium"); + selectValue(subboneSelect, "iliac_crest"); + loadDescription.mockClear(); + showPlaceholder.mockClear(); + + selectValue(subboneSelect, ""); + await new Promise(process.nextTick); + + expect(loadDescription).toHaveBeenCalledWith("ilium"); + expect(showPlaceholder).not.toHaveBeenCalled(); + }); + + test("deselecting a bone with no boneset selected still shows the placeholder", async () => { + selectValue(boneSelect, ""); + await new Promise(process.nextTick); + + expect(showPlaceholder).toHaveBeenCalled(); + }); +}); From b4dc17f9d081500aadbcae00075eef32cf4ea595 Mon Sep 17 00:00:00 2001 From: mayokunl <153923029+mayokunl@users.noreply.github.com> Date: Mon, 14 Sep 2026 14:27:12 -0500 Subject: [PATCH 2/3] foxed issue #248 falling back to parent info instead of blanking the display --- templates/js/dropdowns.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/templates/js/dropdowns.js b/templates/js/dropdowns.js index 53c86d5..ff75bc1 100644 --- a/templates/js/dropdowns.js +++ b/templates/js/dropdowns.js @@ -161,6 +161,8 @@ boneSelect.addEventListener("change", (e) => { const stage = getImageStage(); if (stage) { clearAnnotations(stage); stage.classList.remove("with-annotations"); } + // Deselecting the bone shouldn't blank the display if a boneset is still + // selected (Issue #248) - fall back to showing that boneset's info. const selectedBonesetId = bonesetSelect.value; if (selectedBonesetId) { loadDescription(selectedBonesetId); @@ -191,6 +193,8 @@ subboneSelect.addEventListener("change", (e) => { loadBoneImages(selectedSubboneId, opts); } else { + // Same fallback as the bone-change listener above, one level down: + // deselecting the sub-bone should reveal the parent bone's info, not go blank. const selectedBoneId = boneSelect.value; if (selectedBoneId) { loadDescription(selectedBoneId); From f9645e57d4792df5a0477a86665fa8dc4b162536 Mon Sep 17 00:00:00 2001 From: mayokunl <153923029+mayokunl@users.noreply.github.com> Date: Mon, 14 Sep 2026 15:24:40 -0500 Subject: [PATCH 3/3] worked on issue 249, making sure discription matches submenu --- templates/js/main.js | 3 +- templates/js/navigation.js | 36 +++++++------ templates/tests/navigation.test.js | 83 ++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 18 deletions(-) create mode 100644 templates/tests/navigation.test.js diff --git a/templates/js/main.js b/templates/js/main.js index 9403eb5..cd86c75 100644 --- a/templates/js/main.js +++ b/templates/js/main.js @@ -2,7 +2,6 @@ import { fetchCombinedData, fetchBoneData } from "./api.js"; import { populateBonesetDropdown, setupDropdownListeners } from "./dropdowns.js"; import {initializeSidebar} from "./sidebar.js"; import { setupNavigation, setBoneAndSubbones, disableButtons } from "./navigation.js"; -import { loadDescription } from "./description.js"; import { displayBoneData, clearViewer } from "./viewer.js"; import { initializeSearch } from "./search.js"; import quizManager from "./quiz.js"; @@ -47,7 +46,7 @@ document.addEventListener("DOMContentLoaded", async () => { // Log fetched data for troubleshooting when dropdowns are empty console.debug("combinedData:", combinedData); - setupNavigation(prevButton, nextButton, subboneDropdown, loadDescription); + setupNavigation(prevButton, nextButton, subboneDropdown); // Initialize button states disableButtons(prevButton, nextButton); diff --git a/templates/js/navigation.js b/templates/js/navigation.js index 785d08c..8d5210d 100644 --- a/templates/js/navigation.js +++ b/templates/js/navigation.js @@ -7,20 +7,16 @@ let subbones = []; * @param {HTMLButtonElement} prevButton - The "previous" navigation button. * @param {HTMLButtonElement} nextButton - The "next" navigation button. * @param {HTMLSelectElement} subboneDropdown - The subbone `