"""Unit tests for the shared render style and its editable subset.""" from __future__ import annotations import dataclasses import pytest from otko.core import ElasticBeamColumn, Node, Project, TrussElement, ZeroLengthElement from otko.views.canvas3d.style import ( EDITABLE_FIELDS, SELECTED_STATE, STYLE_FIELDS, RenderStyle, element_family_index, family_palette, ) def test_defaults_are_the_documented_palette() -> None: style = RenderStyle() assert style.node_color == "#d9d9d9" assert style.selected_color == "#00ffff" # opstool-derived element palette. assert style.element_beam_color == "#0652ff" assert style.element_truss_color == "#FF8C00" assert style.element_link_color == "#39FF14" assert len(style.response_scale_colors) == 10 assert style.response_scale_colors[0] == "#313695" # cold end assert style.response_scale_colors[-1] == "#a50026" # warm end def test_with_updates_is_immutable() -> None: original = RenderStyle() updated = original.with_updates(node_color="#123456", label_font_size=20) assert updated is not original assert updated.node_color == "#123456" assert updated.label_font_size == 20 # The source instance is untouched (frozen dataclass contract). assert original.node_color == "#d9d9d9" assert original.label_font_size == 12 def test_editable_subset_matches_the_field_table() -> None: style = RenderStyle() assert RenderStyle.EDITABLE == EDITABLE_FIELDS assert set(style.editable_values()) == set(EDITABLE_FIELDS) # Every advertised field must exist on the dataclass with the right kind. kinds = {name: kind for name, _label, kind in STYLE_FIELDS} for name in EDITABLE_FIELDS: assert hasattr(style, name), name assert kinds[name] in {"color", "float", "int", "bool"} def test_response_colorscale_is_an_evenly_spaced_mapping() -> None: style = RenderStyle() scale = style.response_colorscale() assert len(scale) == len(style.response_scale_colors) assert scale[0] == (0.0, style.response_scale_colors[0]) assert scale[-1] == (1.0, style.response_scale_colors[-1]) positions = [position for position, _color in scale] assert positions == sorted(positions) @pytest.mark.parametrize( ("element", "expected"), [ (ElasticBeamColumn(id=1, nodes=(1, 2), section_id=1), 0), (TrussElement(id=2, nodes=(1, 2), area=0.01, material_id=1), 1), (ZeroLengthElement(id=3, nodes=(1, 1), material_ids=(1,), dofs=(1,)), 2), ], ) def test_element_family_index(element: object, expected: int) -> None: assert element_family_index(element) == expected def test_family_palette_order_and_selected_slot() -> None: style = RenderStyle(node_color="#010101") palette = family_palette(style) assert palette == [ style.element_beam_color, style.element_truss_color, style.element_link_color, style.selected_color, ] assert len(palette) - 1 == SELECTED_STATE def test_editable_values_round_trip_through_replace() -> None: style = RenderStyle() values = style.editable_values() assert RenderStyle(**values) == style # And the persisted shape is JSON-friendly (str/int/float only). import json assert json.loads(json.dumps(values)) == values def test_style_is_a_frozen_slots_dataclass() -> None: assert dataclasses.is_dataclass(RenderStyle) with pytest.raises(dataclasses.FrozenInstanceError): RenderStyle().node_color = "#000000" # type: ignore[misc] def test_project_builder_helper_is_unused_but_valid() -> None: """A style constructed from a project's elements classifies every family.""" project = Project( ndm=2, ndf=3, nodes=[Node(id=1, coords=(0, 0, 0)), Node(id=2, coords=(1, 0, 0))], elements=[ ElasticBeamColumn(id=1, nodes=(1, 2), section_id=1), TrussElement(id=2, nodes=(1, 2), area=0.01, material_id=1), ], ) indices = [element_family_index(el) for el in project.elements] assert indices == [0, 1]