from __future__ import annotations import importlib.util import json import tempfile import unittest from pathlib import Path from unittest.mock import patch WORKER_PATH = ( Path(__file__).resolve().parents[1] / "windows-node" / "adapters" / "origin.plot@v2" / "worker.py" ) ADAPTER_MANIFEST_PATH = ( Path(__file__).resolve().parents[1] / "windows-node" / "adapters" / "origin.plot@v2" / "adapter.json" ) SPEC = importlib.util.spec_from_file_location("zcbot_origin_worker", WORKER_PATH) assert SPEC and SPEC.loader worker = importlib.util.module_from_spec(SPEC) SPEC.loader.exec_module(worker) class OriginWorkerUnitTests(unittest.TestCase): @staticmethod def _request() -> dict: return { "inputs": [{"key": "sample"}], "operation": { "plot": { "type": "line", "series": [{"input": "sample", "x": "x", "y": "y"}], } }, "outputs": [{"key": "figure_png", "format": "png"}], } def test_worker_version_matches_adapter_manifest(self) -> None: manifest = json.loads(ADAPTER_MANIFEST_PATH.read_text(encoding="utf-8")) self.assertEqual(worker.ADAPTER_VERSION, manifest["adapter_version"]) def test_registered_origin_version_comes_from_com_server_executable(self) -> None: class FakeKey: def __init__(self, path: str): self.path = path def __enter__(self): return self def __exit__(self, *_): return None class FakeRegistry: HKEY_CLASSES_ROOT = object() values = { r"Origin.ApplicationSI\CLSID": "{origin-clsid}", r"CLSID\{origin-clsid}\LocalServer32": ( r'"C:\Program Files\OriginLab\Origin2025\Origin64.exe" /Automation' ), } @classmethod def OpenKey(cls, _root, path): return FakeKey(path) @classmethod def QueryValueEx(cls, key, _name): return cls.values[key.path], 1 with patch.object(worker, "_windows_file_version", return_value="2025") as read: self.assertEqual(worker._registered_origin_version(FakeRegistry), "2025") read.assert_called_once_with(Path(r"C:\Program Files\OriginLab\Origin2025\Origin64.exe")) FakeRegistry.values[r"CLSID\{origin-clsid}\LocalServer32"] = "invalid" self.assertIsNone(worker._registered_origin_version(FakeRegistry)) def test_csv_and_json_inputs_are_read_without_origin(self) -> None: with tempfile.TemporaryDirectory() as directory: root = Path(directory) csv_path = root / "input.csv" csv_path.write_text("x,y\n1,2\n3,4\n", encoding="utf-8") self.assertEqual( worker._read_rows(csv_path, None), (["x", "y"], [["1", "2"], ["3", "4"]]), ) json_path = root / "input.json" json_path.write_text(json.dumps([{"x": 1, "y": 2}, {"x": 3, "y": 4}]), encoding="utf-8") self.assertEqual(worker._read_rows(json_path, None), (["x", "y"], [[1, 2], [3, 4]])) def test_manifest_uses_stable_id_and_streaming_digest(self) -> None: with tempfile.TemporaryDirectory() as directory: path = Path(directory) / "plot-spec.json" path.write_text("{}", encoding="utf-8") manifest = worker._manifest(path, "application/json") self.assertEqual(manifest["artifact_id"], "plot_spec") self.assertEqual(manifest["sha256"], worker._file_sha256(path)) self.assertEqual(manifest["size_bytes"], 2) def test_axis_title_includes_units(self) -> None: self.assertEqual( worker._axis_title({"title": "Stress", "unit": "MPa"}, "Y"), "Stress (MPa)" ) self.assertEqual(worker._axis_title(None, "Time"), "Time") def test_layout_helpers_apply_validated_values(self) -> None: class FakeAxis: title = "" scale = "linear" limits = None def set_limits(self, begin, end, step): self.limits = (begin, end, step) class FakeLayer: def __init__(self): self.axes = {name: FakeAxis() for name in ("x", "y", "z")} self.values = {} self.labels = {} def axis(self, name): return self.axes[name] def set_float(self, name, value): self.values[name] = value def set_int(self, name, value): self.values[name] = value def label(self, name): class FakeLabel: def set_int(_, prop, value): self.labels[(name, prop)] = value return FakeLabel() layer = FakeLayer() worker._apply_axis( layer, "x", { "title": "Time", "unit": "d", "scale": "log10", "minimum": 1, "maximum": 100, "major_step": 1, "tick_label_angle": 45, "tick_label_font_size": 10, "title_font_size": 12, "grid": "major_minor", }, "X", ) self.assertEqual(layer.axes["x"].title, "Time (d)") self.assertEqual(layer.axes["x"].scale, "log10") self.assertEqual(layer.axes["x"].limits, (1, 100, 1)) self.assertEqual( layer.values, { "x.label.rotate": 45, "x.label.pt": 10, "x.grid.show": 3, }, ) self.assertEqual(layer.labels, {("xb", "fsize"): 12}) def test_rescale_applies_log_scales_before_deriving_limits(self) -> None: class FakeAxis: def __init__(self, name, events): self.name = name self.events = events self._scale = "linear" @property def scale(self): return self._scale @scale.setter def scale(self, value): self._scale = value self.events.append(("scale", self.name, value)) class FakeLayer: def __init__(self): self.events = [] self.axes = {name: FakeAxis(name, self.events) for name in ("x", "y")} def axis(self, name): return self.axes[name] def rescale(self): self.events.append(("rescale",)) layer = FakeLayer() worker._rescale_with_axis_scales( layer, [ ("x", {"scale": "log10", "minimum": 1, "maximum": 100}), ("y", {"scale": "ln"}), ], ) self.assertEqual( layer.events, [ ("scale", "x", "log10"), ("scale", "y", "ln"), ("rescale",), ], ) def test_rescale_does_not_apply_explicit_limits_before_rescaling(self) -> None: class FakeAxis: scale = "linear" def set_limits(self, *_): raise AssertionError("limits must be applied after rescale") class FakeLayer: def __init__(self): self.axis_value = FakeAxis() self.rescaled = False def axis(self, _name): return self.axis_value def rescale(self): self.rescaled = True layer = FakeLayer() worker._rescale_with_axis_scales( layer, [ ("x", {"minimum": 1, "maximum": 100}), ], ) self.assertTrue(layer.rescaled) self.assertEqual(layer.axis_value.scale, "linear") def test_series_style_maps_to_origin_properties(self) -> None: class FakePlot: def __init__(self): self.values = {} self.color = None self.symbol_kind = None self.symbol_size = None self.transparency = None def set_float(self, name, value): self.values[name] = value def set_int(self, name, value): self.values[name] = value plot = FakePlot() worker._apply_series_style( plot, { "color": "#3366CC", "line_width": 1.5, "line_style": "dash_dot", "symbol": "diamond", "symbol_size": 8, "transparency": 20, }, ) self.assertEqual(plot.color, (51, 102, 204)) self.assertEqual(plot.values, {"line.width": 1.5, "line.type": 4}) self.assertEqual(plot.symbol_kind, 5) self.assertEqual(plot.symbol_size, 8) self.assertEqual(plot.transparency, 20) def test_origin_symbol_ids_match_labtalk_values(self) -> None: self.assertEqual( worker.SYMBOLS, { "square": 1, "circle": 2, "triangle_up": 3, "diamond": 5, "plus": 6, "cross": 7, }, ) def test_keyed_input_directory_requires_exactly_one_file(self) -> None: with tempfile.TemporaryDirectory() as directory: root = Path(directory) keyed = root / "input" / "sample" keyed.mkdir(parents=True) path = keyed / "data.csv" path.write_text("x,y\n1,2\n", encoding="utf-8") self.assertEqual(worker._input_file(root, "sample"), path) (keyed / "extra.csv").write_text("x,y\n3,4\n", encoding="utf-8") with self.assertRaisesRegex(ValueError, "INPUT_FILE_COUNT_INVALID:sample"): worker._input_file(root, "sample") def test_series_are_resolved_against_each_input(self) -> None: input_data = { "first": (["time", "strength"], [[1, 10]]), "second": (["temperature", "value"], [[20, 30]]), } series = [ {"input": "first", "x": "time", "y": "strength", "label": "7 d"}, {"input": "second", "x": "temperature", "y": "value", "label": "28 d"}, ] resolved, labels = worker._resolve_series(input_data, series) self.assertEqual( resolved, [ {"input": "first", "label": "7 d", "x": 0, "y": 1}, {"input": "second", "label": "28 d", "x": 0, "y": 1}, ], ) self.assertEqual(labels, {("first", 1): "7 d", ("second", 1): "28 d"}) def test_shared_y_column_rejects_conflicting_labels(self) -> None: input_data = {"sample": (["x1", "x2", "y"], [[1, 2, 3]])} with self.assertRaisesRegex(ValueError, "SERIES_LABEL_CONFLICT"): worker._resolve_series( input_data, [ {"input": "sample", "x": "x1", "y": "y", "label": "First"}, {"input": "sample", "x": "x2", "y": "y", "label": "Second"}, ], ) def test_worker_owns_cross_field_semantic_validation(self) -> None: request = self._request() worker._validate_semantics(request) request["operation"]["plot"]["x_axis"] = { "scale": "log10", "minimum": 0, "maximum": 100, } with self.assertRaisesRegex(ValueError, "X_AXIS_LOG_LIMIT_INVALID"): worker._validate_semantics(request) request = self._request() request["inputs"].append({"key": "unused"}) with self.assertRaisesRegex(ValueError, "INPUT_BINDINGS_MUST_BE_USED_EXACTLY"): worker._validate_semantics(request) def test_multi_panel_semantics_cover_layout_axes_and_total_series(self) -> None: request = { "inputs": [{"key": "sample"}], "operation": { "plot": { "type": "multi_panel", "layout": {"rows": 1, "columns": 2, "share_x": True}, "panels": [ { "key": "strength", "series": [ { "input": "sample", "x": "age", "y": "strength", "kind": "scatter", "y_error": "sd", } ], }, { "key": "thermal", "series": [ { "input": "sample", "x": "temperature", "y": "tg", "kind": "line", }, { "input": "sample", "x": "temperature", "y": "dtg", "kind": "line", "y_axis": "right", }, ], "right_y_axis": {"title": "DTG"}, }, ], } }, "outputs": [{"key": "figure_png", "format": "png"}], } worker._validate_semantics(request) request["operation"]["plot"]["layout"] = {"rows": 1, "columns": 1} with self.assertRaisesRegex(ValueError, "PANEL_LAYOUT_TOO_SMALL"): worker._validate_semantics(request) request["operation"]["plot"]["layout"] = { "rows": 1, "columns": 2, "share_x": True, } request["operation"]["plot"]["panels"][0]["x_axis"] = { "scale": "log10", "minimum": 0, } with self.assertRaisesRegex(ValueError, "X_AXIS_LOG_LIMIT_INVALID"): worker._validate_semantics(request) request["operation"]["plot"]["panels"][0]["x_axis"] = {"minimum": 1} with self.assertRaisesRegex(ValueError, "SHARED_X_AXIS_CONFIG_MISMATCH"): worker._validate_semantics(request) request["operation"]["plot"]["panels"][0].pop("x_axis") request["operation"]["plot"]["x_axis"] = { "scale": "log10", "minimum": 0, } with self.assertRaisesRegex(ValueError, "X_AXIS_LOG_LIMIT_INVALID"): worker._validate_semantics(request) def test_recipe_uses_the_composition_semantics(self) -> None: request = { "inputs": [{"key": "sample"}], "operation": { "plot": { "type": "recipe", "recipe_version": 1, "layout": {"rows": 1, "columns": 1}, "panels": [ { "key": "main", "series": [ { "input": "sample", "x": "age", "y": "strength", "kind": "line_scatter", } ], } ], } }, "outputs": [{"key": "figure_png", "format": "png"}], } worker._validate_semantics(request) self.assertIn("recipe", worker.COMPOSITION_PLOT_TYPES) def test_panel_settings_inherit_plot_defaults_and_allow_overrides(self) -> None: plot = { "x_axis": {"title": "Age", "unit": "d"}, "y_axis": {"title": "Value"}, "legend": {"enabled": False, "font_size": 8}, } panel = { "y_axis": {"title": "Strength", "unit": "MPa"}, "legend": {"enabled": True}, } self.assertEqual( worker._panel_setting(plot, panel, "x_axis"), {"title": "Age", "unit": "d"}, ) self.assertEqual( worker._panel_setting(plot, panel, "y_axis"), {"title": "Strength", "unit": "MPa"}, ) self.assertEqual( worker._panel_legend(plot, panel), { "enabled": True, "position": "top_right", "font_size": 8, }, ) def test_panel_y_axis_fallback_prefers_title_then_column_name(self) -> None: series = [{"y": "CS_20C"}] self.assertEqual( worker._panel_y_axis_fallback({"title": "抗压强度 (MPa)"}, series), "抗压强度 (MPa)", ) self.assertEqual( worker._panel_y_axis_fallback({}, series), "CS_20C", ) def test_mixed_xy_series_pass_kind_axis_and_error_columns_to_origin(self) -> None: class FakePlot: pass class FakeLayer: def __init__(self): self.calls = [] self.grouped = False self.plots = [] def add_plot(self, worksheet, **arguments): self.calls.append((worksheet, arguments)) plot = FakePlot() self.plots.append(plot) if "colxerr" in arguments: self.plots.append(FakePlot()) if "colyerr" in arguments: self.plots.append(FakePlot()) return plot def plot_list(self): return self.plots def group(self, enabled=True): self.grouped = enabled layer = FakeLayer() series = [ { "input": "sample", "x": "age", "y": "strength", "kind": "scatter", "x_error": "age_sd", "y_error": "strength_sd", }, {"input": "sample", "x": "age", "y": "fit", "kind": "line"}, ] resolved = [ {"input": "sample", "x": 0, "y": 1, "x_error": 2, "y_error": 3}, {"input": "sample", "x": 0, "y": 4}, ] worker._add_xy_plots(layer, {"sample": "worksheet"}, series, resolved) self.assertEqual( layer.calls, [ ( "worksheet", {"coly": 1, "colx": 0, "type": "s", "colxerr": 2, "colyerr": 3}, ), ("worksheet", {"coly": 4, "colx": 0, "type": "l"}), ], ) self.assertFalse(layer.grouped) def test_series_support_xyz_and_error_roles(self) -> None: resolved, labels = worker._resolve_series( {"sample": (["x", "y", "z", "x_sd", "y_sd"], [[0, 1, 2, 0.1, 0.2]])}, [ { "input": "sample", "x": "x", "y": "y", "z": "z", "x_error": "x_sd", "y_error": "y_sd", "label": "测量值", } ], ) self.assertEqual( resolved, [ { "input": "sample", "label": "测量值", "x": 0, "y": 1, "z": 2, "x_error": 3, "y_error": 4, } ], ) self.assertEqual(labels, {("sample", 1): "测量值"}) def test_series_support_second_batch_roles(self) -> None: resolved, _ = worker._resolve_series( { "sample": ( ["x", "mean", "size", "lower", "upper"], [[0, 1, 2, 0.5, 1.5]], ) }, [ { "input": "sample", "x": "x", "y": "mean", "size": "size", "lower": "lower", "upper": "upper", } ], ) self.assertEqual( resolved, [ { "input": "sample", "label": None, "x": 0, "y": 1, "size": 2, "lower": 3, "upper": 4, } ], ) def test_second_batch_value_validation_rejects_invalid_data(self) -> None: input_data = {"sample": (["x", "y", "size", "lower", "upper"], [[0, 1, 0, 2, 1]])} resolved = [ { "input": "sample", "x": 0, "y": 1, "size": 2, "lower": 3, "upper": 4, } ] with self.assertRaisesRegex(ValueError, "SIZE_VALUE_NOT_POSITIVE"): worker._validate_bubble_values(input_data, resolved) with self.assertRaisesRegex(ValueError, "BAND_BOUNDS_INVERTED"): worker._validate_band_values(input_data, resolved) def test_band_graph_adds_bounds_fill_and_center_line(self) -> None: class FakePlot: def __init__(self): self.fill = None def set_fill_area(self, **arguments): self.fill = arguments class FakeLayer: def __init__(self): self.calls = [] def add_plot(self, worksheet, **arguments): plot = FakePlot() self.calls.append((worksheet, arguments, plot)) return plot class FakeGraph: def __init__(self): self.layer = FakeLayer() def __getitem__(self, index): self.assert_index = index return self.layer class FakeOp: def __init__(self): self.graph = FakeGraph() def new_graph(self, *, template): self.template = template return self.graph op = FakeOp() graph, layer, centers, legend = worker._build_band_graph( op, {"sample": "worksheet"}, [{"input": "sample", "x": "x", "y": "mean", "label": "95% CI"}], [{"input": "sample", "x": 0, "y": 1, "lower": 2, "upper": 3}], ) self.assertIs(graph, op.graph) self.assertIs(layer, op.graph.layer) self.assertEqual(op.template, "line") self.assertEqual([item[1]["coly"] for item in layer.calls], [3, 2, 1]) self.assertEqual(layer.calls[0][2].fill, {"type": 9}) self.assertEqual(centers, [layer.calls[2][2]]) self.assertEqual(legend, r"\l(3) 95% CI") def test_second_batch_plot_ids_and_templates_are_fixed(self) -> None: self.assertEqual(worker.PLOT_CONFIG["box"], ("box", 206)) self.assertEqual(worker.PLOT_CONFIG["histogram"], ("hist", 219)) self.assertEqual(worker.PLOT_CONFIG["stacked_column"], ("StackColumn", 213)) self.assertEqual(worker.PLOT_CONFIG["bubble"], ("scatter", "s")) def test_bubble_execution_parameters(self) -> None: class FakeOp: @staticmethod def modi_col(offset): return ("modifier", offset) class FakePlot: symbol_size = None symbol_sizefactor = None plot = FakePlot() worker._configure_bubble_plot( FakeOp(), plot, {"style": {"symbol_size": 12}}, {"y": 1, "size": 4} ) self.assertEqual(plot.symbol_size, ("modifier", 3)) self.assertEqual(plot.symbol_sizefactor, 12) def test_stacked_graph_uses_one_native_xyy_range(self) -> None: class FakeSheet: def __init__(self): self.columns = [] def from_list(self, index, values, *, lname): self.columns.append((index, values, lname)) @staticmethod def lt_range(_use_name): return "[Book1]1" class FakeLayer: def __init__(self): self.calls = [] self.obj = self def add_plot(self, data_range, *, type): self.calls.append((data_range, type)) def activate(self): self.activated = True def group(self, enabled, begin, end): self.group_call = (enabled, begin, end) def LT_execute(self, command): self.command = command @staticmethod def plot_list(): return ["a", "b"] class FakeGraph: def __init__(self): self.layer = FakeLayer() def __getitem__(self, index): self.index = index return self.layer class FakeOp: def __init__(self): self.sheet = FakeSheet() self.graph = FakeGraph() def new_sheet(self, kind, *, lname): self.sheet_call = (kind, lname) return self.sheet def new_graph(self, *, template): self.template = template return self.graph op = FakeOp() graph, layer, plots = worker._build_stacked_graph( op, {"sample": (["x", "a", "b"], [[1, 10, 5], [2, 12, 6]])}, [ {"input": "sample", "x": "x", "y": "a", "label": "A"}, {"input": "sample", "x": "x", "y": "b", "label": "B"}, ], [ {"input": "sample", "x": 0, "y": 1}, {"input": "sample", "x": 0, "y": 2}, ], "StackColumn", 213, ) self.assertIs(graph, op.graph) self.assertIs(layer, op.graph.layer) self.assertEqual(plots, ["a", "b"]) self.assertEqual(op.template, "StackColumn") self.assertEqual(layer.calls, [("[Book1]1!(1,2:3)", 213)]) self.assertEqual(layer.group_call, (True, 0, 1)) self.assertTrue(layer.activated) self.assertEqual(layer.command, "layer -b s 1") self.assertEqual( op.sheet.columns[1:], [ (1, [10, 12], "A"), (2, [5, 6], "B"), ], ) def test_stacked_graph_rejects_mismatched_x_values(self) -> None: with self.assertRaisesRegex(ValueError, "STACKED_PLOT_X_VALUES_MISMATCH"): worker._build_stacked_graph( object(), { "a": (["x", "y"], [[1, 10], [2, 12]]), "b": (["x", "y"], [[1, 5], [3, 6]]), }, [ {"input": "a", "x": "x", "y": "y"}, {"input": "b", "x": "x", "y": "y"}, ], [ {"input": "a", "x": 0, "y": 1}, {"input": "b", "x": 0, "y": 1}, ], "StackColumn", 213, ) def test_third_batch_plot_ids_and_templates_are_fixed(self) -> None: self.assertEqual(worker.PLOT_CONFIG["area"], ("area", 204)) self.assertEqual(worker.PLOT_CONFIG["stacked_area"], ("stackarea", 214)) self.assertEqual(worker.PLOT_CONFIG["polar"], ("polar", 192)) self.assertEqual(worker.PLOT_CONFIG["pie"], ("pie", 225)) self.assertEqual(worker.PLOT_CONFIG["stacked_bar"], ("bar", 216)) def test_stacked_legend_only_contains_requested_series(self) -> None: class Label: text = "" class Layer: legend = Label() def label(self, name): self.name = name return self.legend layer = Layer() worker._replace_series_legend( layer, [ {"y": "a", "label": "Phase A"}, {"y": "phase_b"}, ], ) self.assertEqual(layer.name, "Legend") self.assertEqual(layer.legend.text, r"\l(1) Phase A" "\n" r"\l(2) phase_b") def test_multi_panel_legend_uses_actual_data_plot_indices(self) -> None: class Label: text = "" class Layer: legend = Label() def label(self, _name): return self.legend class Plot: def __init__(self, value): self.value = value def index(self): return self.value layer = Layer() worker._replace_series_legend( layer, [ {"y": "a", "label": "5 C"}, {"y": "b", "label": "20 C"}, ], [Plot(0), Plot(2)], ) self.assertEqual( layer.legend.text, r"\l(1) 5 C" "\n" r"\l(3) 20 C", ) def test_panel_title_and_legend_use_page_pixel_coordinates(self) -> None: class Label: def __init__(self): self.values = {} self.show = None def set_int(self, name, value): self.values[name] = value def set_float(self, name, value): self.values[name] = value class Layer: def __init__(self): self.legend = Label() self.title = None def label(self, _name): return self.legend def add_label(self, _text): self.title = Label() return self.title @staticmethod def get_float(name): return { "left": 10, "top": 20, "width": 40, "height": 30, }[name] class Graph: @staticmethod def get_float(name): return { "width": 1800, "height": 1450, "resx": 254, "resy": 254, }[name] layer = Layer() graph = Graph() worker._apply_legend( layer, {"enabled": True, "position": "top_right"}, attach_to_layer=True, graph=graph, geometry=(10, 20, 40, 30), ) worker._apply_panel_title( graph, layer, (10, 20, 40, 30), "Strength", {"font_size": 12}, ) self.assertEqual(layer.legend.values["attach"], 1) self.assertEqual(layer.legend.values["background"], 0) self.assertEqual(layer.legend.values["left"], 655) self.assertEqual(layer.legend.values["top"], 307) self.assertEqual(layer.title.values["attach"], 1) self.assertEqual(layer.title.values["background"], 0) self.assertEqual(layer.title.values["fsize"], 12) self.assertEqual(layer.title.values["left"], 382) self.assertEqual(layer.title.values["top"], 229) def test_origin_session_disables_printed_text_baselines(self) -> None: class Origin: def __init__(self): self.variables = [] def set_lt_var(self, name, value): self.variables.append((name, value)) origin = Origin() worker._configure_origin_session(origin) self.assertEqual(origin.variables, [("@U", 1)]) def test_origin_arranges_panel_layers_and_reports_resulting_geometry(self) -> None: class Layer: def __init__(self, geometry): self.geometry = geometry self.values = {} self.commands = [] self.activated = False self.obj = self def set_int(self, name, value): self.values[name] = value def activate(self): self.activated = True def LT_execute(self, command): self.commands.append(command) def get_float(self, name): return self.geometry[name] layers = [ Layer({"left": 9, "top": 14, "width": 38, "height": 32}), Layer({"left": 58, "top": 14, "width": 38, "height": 32}), Layer({"left": 9, "top": 56, "width": 38, "height": 32}), Layer({"left": 58, "top": 56, "width": 38, "height": 32}), ] geometries = worker._arrange_panel_layers(layers, (2, 2)) self.assertTrue(layers[0].activated) self.assertTrue(all(layer.values["unit"] == 1 for layer in layers)) self.assertEqual( layers[0].commands, ["layarrange row:=2 col:=2 left:=9 right:=4 top:=14 bottom:=12 xgap:=11 ygap:=10;"], ) self.assertEqual( geometries, [ (9, 14, 38, 32), (58, 14, 38, 32), (9, 56, 38, 32), (58, 56, 38, 32), ], ) def test_shared_x_hides_upper_row_title_and_tick_labels(self) -> None: class Title: show = True class Layer: def __init__(self): self.title = Title() self.commands = [] self.obj = self def label(self, name): return self.title if name == "xb" else None def activate(self): return None def LT_execute(self, command): self.commands.append(command) upper = Layer() lower = Layer() worker._apply_shared_x_presentation(upper, panel_index=0, rows=2, columns=2, share_x=True) worker._apply_shared_x_presentation(lower, panel_index=2, rows=2, columns=2, share_x=True) self.assertFalse(upper.title.show) self.assertEqual(upper.commands, ["axis -ps X L 0;"]) self.assertTrue(lower.title.show) self.assertEqual(lower.commands, []) def test_auto_legend_delegates_positioning_to_origin(self) -> None: class Label: def __init__(self): self.values = {} self.show = None def set_int(self, name, value): self.values[name] = value class Layer: def __init__(self): self.legend = Label() def label(self, _name): return self.legend layer = Layer() worker._apply_legend(layer, {"enabled": True, "position": "auto"}) self.assertEqual(layer.legend.values["smartpos"], 1) self.assertNotIn("left", layer.legend.values) self.assertNotIn("top", layer.legend.values) def test_heatmap_matrix_accepts_complete_unordered_grid(self) -> None: matrix, xy_map = worker._heatmap_matrix( [[1, 20, 4], [0, 10, 1], [1, 10, 2], [0, 20, 3]], {"x": 0, "y": 1, "z": 2}, ) self.assertEqual(matrix, [[1.0, 2.0], [3.0, 4.0]]) self.assertEqual(xy_map, (0.0, 1.0, 10.0, 20.0)) def test_heatmap_matrix_rejects_invalid_grid(self) -> None: with self.assertRaisesRegex(ValueError, "HEATMAP_GRID_INCOMPLETE"): worker._heatmap_matrix( [[0, 10, 1], [1, 10, 2], [0, 20, 3]], {"x": 0, "y": 1, "z": 2}, ) with self.assertRaisesRegex(ValueError, "HEATMAP_COORDINATES_DUPLICATED"): worker._heatmap_matrix([[0, 10, 1], [0, 10, 2]], {"x": 0, "y": 1, "z": 2}) with self.assertRaisesRegex(ValueError, "HEATMAP_GRID_NOT_REGULAR"): worker._heatmap_matrix( [ [0, 10, 1], [1, 10, 2], [3, 10, 3], [0, 20, 4], [1, 20, 5], [3, 20, 6], ], {"x": 0, "y": 1, "z": 2}, ) if __name__ == "__main__": unittest.main()