Skip to content

GetImage solution for import of iconsΒ #36

@jurra

Description

@jurra

🧩 Issue: Refactor GetImage to Dynamically Load Component Images Based on Naming Convention


πŸ“Œ Goal

Refactor the GetImage module to eliminate the need for hardcoding each circuit component's image imports. Instead, the base ElementRenderer should dynamically resolve the appropriate image(s) for each subclass (e.g., ResistorRenderer, CapacitorRenderer) based on a consistent naming convention.

This change aims to:

  • βœ… Eliminate repetitive image imports.
  • βœ… Scale gracefully as new elements are added.
  • βœ… Centralize and standardize icon lookup logic.
  • βœ… Let the renderer resolve variants (hover, selected, etc.) without hardcoding.

🧠 Context

Right now, we manually do:

import R from '../../../assets/R.png';
...
this.image.src = GetImage("R.png");

We want to switch to:

this.image = this.loadImageVariant("default");

Where loadImageVariant uses either the renderer class name (ResistorRenderer) or the element type ("resistor") to resolve the correct icon path.

This allows consistent handling of image variants and removes boilerplate from every renderer.


βœ… Acceptance Criteria

  • The GetImage utility dynamically resolves image paths using naming conventions.
  • Renderers do not manually import or map image paths.
  • ElementRenderer can initialize all icon variants: default, hover, selected, hover_selected.- - If an asset is missing, a descriptive error is thrown.
  • The logic is fully tested using unit tests and follows a TDD approach.

πŸ§ͺ Test-Driven Development Plan

Start by writing the following unit tests in tests/utils/getImagePath.test.js:

describe("getImagePath", () => {
  it("resolves default image path from type name", () => {
    expect(getImagePath("resistor")).toContain("R.png");
  });

  it("resolves hover image path correctly", () => {
    expect(getImagePath("resistor", "hover")).toContain("R_hover.png");
  });

  it("throws if type is empty or unknown", () => {
    expect(() => getImagePath("")).toThrow("Invalid or unknown type");
  });

  it("resolves all image variants for a valid type", () => {
    const variants = ["default", "hover", "selected", "hover_selected"];
    const paths = variants.map(v => getImagePath("junction", v));
    expect(paths).toEqual(expect.arrayContaining([
      expect.stringContaining("J.png"),
      expect.stringContaining("J_hover.png"),
      expect.stringContaining("J_selected.png"),
      expect.stringContaining("J_hover_selected.png"),
    ]));
  });
});

βš™οΈ Suggested Refactor Strategy (pseudocode)

1. Create a utility to resolve base icon letter from type:

function getBaseImageNameFromType(type) {
  return type.charAt(0).toUpperCase(); // "resistor" β†’ "R"
}

2. Create a getImagePath utility:

// utils/getImagePath.js
export function getImagePath(type, variant = "default") {
  if (!type || typeof type !== "string") throw new Error("Invalid or unknown type");

  const base = getBaseImageNameFromType(type);
  const suffix = variant === "default" ? "" : `_${variant}`;
  return new URL(`../../../assets/${base}${suffix}.png`, import.meta.url).href;
}

3. In ElementRenderer:

initializeImageAssets(type) {
  this.images = {};
  const variants = ["default", "hover", "selected", "hover_selected"];

  variants.forEach(variant => {
    const img = new Image();
    img.src = getImagePath(type, variant);
    this.images[variant] = img;

    img.onload = () => {
      if (variant === "default") this.imageLoaded = true;
    };
  });
}

4. In ResistorRenderer (and others):

constructor(context) {
  super(context);
  this.initializeImageAssets("resistor");
}

πŸ”¬ Optional Phase 2 Tests

πŸ”¬ Optional Phase 2 Tests

  • Ensure image objects are cached and reused if needed.
  • Simulate image loading in the browser (e.g. with jest-canvas-mock).
  • Test rendering fallback or missing icon behavior.

πŸ”§ Bonus Improvements

  • Preload assets from a manifest or dynamically import via Vite/Webpack.
  • Replace Image() instances with a shared ImageCache utility.
  • Use renderer class name inference (this.constructor.name) to determine type automatically.

🧱 Dependencies

None. This refactor introduces no new dependencies and is a modular upgrade to the current image system.


🧠 Notes

This issue helps centralize and abstract rendering logic, aligning with our Hexagonal Architecture by improving adapter reusability and enforcing naming consistency.

Let me know if you'd like help scaffolding the initial test file or setting up an asset loader abstraction.

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions