""" Tests for the QTR .quad file parser. """ import pytest from pathlib import Path from ptpd_calibration.curves.parser import ( QuadProfile, QuadFileParser, ChannelCurve, load_quad_file, load_quad_string, ) class TestChannelCurve: """Tests for ChannelCurve dataclass.""" def test_channel_curve_creation(self): """Test creating a channel curve.""" values = list(range(256)) curve = ChannelCurve(name="K", values=values) assert curve.name == "K" assert len(curve.values) == 256 assert curve.enabled is True def test_as_normalized(self): """Test normalized output conversion.""" values = [0, 128, 255] curve = ChannelCurve(name="K", values=values) inputs, outputs = curve.as_normalized assert len(inputs) == 3 assert len(outputs) == 3 assert inputs[0] == 0.0 assert inputs[-1] == pytest.approx(1.0, rel=0.01) assert outputs[0] == 0.0 assert outputs[1] == pytest.approx(0.502, rel=0.01) assert outputs[2] == 1.0 def test_to_curve_data(self): """Test conversion to CurveData model.""" values = [0, 64, 128, 192, 255] curve = ChannelCurve(name="K", values=values) curve_data = curve.to_curve_data(" - Test Profile") assert "K - Test Profile" in curve_data.name assert len(curve_data.input_values) == 5 assert len(curve_data.output_values) == 5 class TestQuadProfile: """Tests for QuadProfile dataclass.""" def test_profile_defaults(self): """Test default profile values.""" profile = QuadProfile() assert profile.profile_name == "Untitled" assert profile.resolution == 2880 assert profile.ink_limit == 100.0 assert len(profile.channels) == 0 def test_primary_channel(self): """Test primary channel accessor.""" profile = QuadProfile() profile.channels["K"] = ChannelCurve(name="K", values=[0] * 256) assert profile.primary_channel is not None assert profile.primary_channel.name == "K" def test_active_channels(self): """Test active channel detection.""" profile = QuadProfile() profile.channels["K"] = ChannelCurve(name="K", values=[0, 10, 20, 30], enabled=True) profile.channels["C"] = ChannelCurve(name="C", values=[0, 0, 0, 0], enabled=True) profile.channels["M"] = ChannelCurve(name="M", values=[0, 5, 10, 15], enabled=False) active = profile.active_channels assert "K" in active assert "C" not in active # All zeros assert "M" not in active # Disabled def test_get_channel(self): """Test channel retrieval.""" profile = QuadProfile() profile.channels["K"] = ChannelCurve(name="K", values=[0] * 256) assert profile.get_channel("K") is not None assert profile.get_channel("k") is not None # Case insensitive assert profile.get_channel("X") is None def test_summary(self): """Test summary generation.""" profile = QuadProfile(profile_name="Test Profile", resolution=1440) profile.channels["K"] = ChannelCurve(name="K", values=[i for i in range(256)]) summary = profile.summary() assert "Test Profile" in summary assert "1440" in summary class TestQuadFileParser: """Tests for QuadFileParser.""" def test_parser_initialization(self): """Test parser initialization.""" parser = QuadFileParser() assert parser._current_section is None assert parser._profile is None def test_parse_simple_quad_content(self): """Test parsing simple .quad content.""" content = """ [General] ProfileName=Test Profile Resolution=2880 InkLimit=95.0 [K] 0=0 1=5 2=10 255=255 """ parser = QuadFileParser() profile = parser.parse_string(content, "Test") assert profile.profile_name == "Test Profile" assert profile.resolution == 2880 assert profile.ink_limit == 95.0 assert "K" in profile.channels def test_parse_multiple_channels(self): """Test parsing multiple channels.""" content = """ [General] ProfileName=Multi-Channel [K] 0=0 255=255 [C] 0=0 255=128 [M] 0=0 255=64 """ parser = QuadFileParser() profile = parser.parse_string(content, "Multi") assert "K" in profile.channels assert "C" in profile.channels assert "M" in profile.channels def test_parse_comments(self): """Test parsing comments.""" content = """ # This is a comment ; This is also a comment [General] ProfileName=Commented Profile """ parser = QuadFileParser() profile = parser.parse_string(content) assert len(profile.comments) == 2 assert "This is a comment" in profile.comments[0] def test_parse_media_settings(self): """Test parsing media settings.""" content = """ [General] MediaType=Glossy Photo MediaSetting=Premium """ parser = QuadFileParser() profile = parser.parse_string(content) assert profile.media_type == "Glossy Photo" assert profile.media_setting == "Premium" def test_parse_curve_values_in_range(self): """Test that curve values are clamped to valid range.""" content = """ [K] 0=0 1=300 2=-10 """ parser = QuadFileParser() profile = parser.parse_string(content) curve = profile.channels["K"] assert curve.values[0] == 0 assert curve.values[1] == 255 # Clamped from 300 assert curve.values[2] == 0 # Clamped from -10 class TestConvenienceFunctions: """Tests for convenience functions.""" def test_load_quad_string(self): """Test load_quad_string function.""" content = """ [General] ProfileName=String Profile [K] 0=0 255=255 """ profile = load_quad_string(content, "Named Profile") assert profile.profile_name == "String Profile" def test_load_quad_file_not_found(self, tmp_path): """Test load_quad_file with non-existent file.""" with pytest.raises(FileNotFoundError): load_quad_file(tmp_path / "nonexistent.quad") def test_load_quad_file(self, tmp_path): """Test load_quad_file with actual file.""" content = """ [General] ProfileName=File Profile Resolution=1440 [K] 0=0 128=100 255=255 """ file_path = tmp_path / "test.quad" file_path.write_text(content) profile = load_quad_file(file_path) assert profile.profile_name == "File Profile" assert profile.resolution == 1440 assert profile.source_path == file_path def test_to_curve_data_conversion(self): """Test converting profile channel to CurveData.""" content = """ [General] ProfileName=Curve Test MediaType=Arches Platine [K] 0=0 64=48 128=96 192=168 255=255 """ profile = load_quad_string(content) curve_data = profile.to_curve_data("K") assert "K" in curve_data.name assert curve_data.paper_type == "Arches Platine" assert len(curve_data.input_values) == 256 def test_to_curve_data_invalid_channel(self): """Test error when requesting invalid channel.""" content = """ [General] ProfileName=Test [K] 0=0 """ profile = load_quad_string(content) with pytest.raises(ValueError, match="not found"): profile.to_curve_data("X")