"""Integration test: TintEffect in the pipeline.""" import queue from engine.data_sources.sources import ListDataSource, SourceItem from engine.effects.plugins.tint import TintEffect from engine.effects.types import EffectConfig from engine.pipeline import Pipeline, PipelineConfig from engine.pipeline.adapters import ( DataSourceStage, DisplayStage, EffectPluginStage, SourceItemsToBufferStage, ) from engine.pipeline.core import PipelineContext from engine.pipeline.params import PipelineParams class QueueDisplay: """Stub display that captures every frame into a queue.""" def __init__(self): self.frames: queue.Queue[list[str]] = queue.Queue() self.width = 80 self.height = 24 self._init_called = False def init(self, width: int, height: int, reuse: bool = False) -> None: self.width = width self.height = height self._init_called = True def show(self, buffer: list[str], border: bool = False) -> None: self.frames.put(list(buffer)) def clear(self) -> None: pass def cleanup(self) -> None: pass def get_dimensions(self) -> tuple[int, int]: return (self.width, self.height) def _build_pipeline( items: list[SourceItem], tint_config: EffectConfig | None = None, width: int = 80, height: int = 24, ) -> tuple[Pipeline, QueueDisplay, PipelineContext]: """Build pipeline: source -> render -> tint effect -> display.""" display = QueueDisplay() ctx = PipelineContext() params = PipelineParams() params.viewport_width = width params.viewport_height = height params.frame_number = 0 ctx.params = params ctx.set("items", items) pipeline = Pipeline( config=PipelineConfig(enable_metrics=True), context=ctx, ) # Source source = ListDataSource(items, name="test-source") pipeline.add_stage("source", DataSourceStage(source, name="test-source")) # Render (simple) pipeline.add_stage("render", SourceItemsToBufferStage(name="items-to-buffer")) # Tint effect tint_effect = TintEffect() if tint_config is not None: tint_effect.configure(tint_config) pipeline.add_stage("tint", EffectPluginStage(tint_effect, name="tint")) # Display pipeline.add_stage("display", DisplayStage(display, name="queue")) pipeline.build() pipeline.initialize() return pipeline, display, ctx class TestTintAcceptance: """Test TintEffect in a full pipeline.""" def test_tint_applies_default_color(self): """Default tint should apply ANSI color codes to output.""" items = [SourceItem(content="Hello World", source="test", timestamp="0")] pipeline, display, ctx = _build_pipeline(items) result = pipeline.execute(items) assert result.success, f"Pipeline failed: {result.error}" frame = display.frames.get(timeout=1) text = "\n".join(frame) assert "\033[" in text, f"Expected ANSI codes in frame: {frame}" assert "Hello World" in text def test_tint_applies_red_color(self): """Configured red tint should produce red ANSI code (196-197).""" items = [SourceItem(content="Red Text", source="test", timestamp="0")] config = EffectConfig( enabled=True, intensity=1.0, params={"r": 255, "g": 0, "b": 0, "a": 0.8}, ) pipeline, display, ctx = _build_pipeline(items, tint_config=config) result = pipeline.execute(items) assert result.success frame = display.frames.get(timeout=1) line = frame[0] # Should contain red ANSI code (196 or 197 in 256 color) assert "\033[38;5;196m" in line or "\033[38;5;197m" in line, ( f"Missing red tint: {line}" ) assert "Red Text" in line def test_tint_disabled_does_nothing(self): """Disabled tint stage should pass through buffer unchanged.""" items = [SourceItem(content="Plain Text", source="test", timestamp="0")] pipeline, display, ctx = _build_pipeline(items) # Disable the tint stage stage = pipeline.get_stage("tint") stage.set_enabled(False) result = pipeline.execute(items) assert result.success frame = display.frames.get(timeout=1) text = "\n".join(frame) # Should contain Plain Text with NO ANSI color codes assert "Plain Text" in text assert "\033[" not in text, f"Unexpected ANSI codes in frame: {frame}" def test_tint_zero_transparency(self): """Alpha=0 should pass through buffer unchanged (no tint).""" items = [SourceItem(content="Transparent", source="test", timestamp="0")] config = EffectConfig( enabled=True, intensity=1.0, params={"r": 255, "g": 128, "b": 64, "a": 0.0}, ) pipeline, display, ctx = _build_pipeline(items, tint_config=config) result = pipeline.execute(items) assert result.success frame = display.frames.get(timeout=1) text = "\n".join(frame) assert "Transparent" in text assert "\033[" not in text, f"Expected no ANSI codes with alpha=0: {frame}" def test_tint_with_multiples_lines(self): """Tint should apply to all non-empty lines.""" items = [ SourceItem(content="Line1\nLine2\n\nLine4", source="test", timestamp="0") ] config = EffectConfig( enabled=True, intensity=1.0, params={"r": 0, "g": 255, "b": 0, "a": 0.7}, ) pipeline, display, ctx = _build_pipeline(items, tint_config=config) result = pipeline.execute(items) assert result.success frame = display.frames.get(timeout=1) # All non-empty lines should have green ANSI codes green_codes = ["\033[38;5;", "m"] for line in frame: if line.strip(): assert green_codes[0] in line and green_codes[1] in line, ( f"Missing green tint: {line}" ) else: assert line == "", f"Empty lines should be exactly empty: {line}" def test_tint_preserves_empty_lines(self): """Empty lines should remain empty (no ANSI codes).""" items = [SourceItem(content="A\n\nB", source="test", timestamp="0")] pipeline, display, ctx = _build_pipeline(items) result = pipeline.execute(items) assert result.success frame = display.frames.get(timeout=1) assert frame[0].strip() != "" assert frame[1] == "" # Empty line unchanged assert frame[2].strip() != ""