File size: 4,557 Bytes
2857cf3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
from __future__ import annotations

from io import StringIO
from pathlib import Path

from PIL import Image
import pytest
from rich.console import Console
from typer.testing import CliRunner

import kneiff.cli.image as cli_image
from kneiff.utils.image import concat as concat_utils
from kneiff.utils.image import png2jpg as png2jpg_utils
from kneiff.progress import ProgressUpdate
from tests._cli_helpers import invoke_cli


def test_png2jpg_composites_alpha_and_writes_subdirectory(tmp_path: Path) -> None:
    """Cover PNG alpha handling and destination routing."""
    image_path = tmp_path / "source.png"
    image = Image.new("RGBA", (2, 1), color=(255, 0, 0, 128))
    image.save(image_path)

    exit_code = png2jpg_utils.png2jpg(
        tmp_path,
        out_subdir="jpg",
        quality=95,
        subsampling=0,
        bg="#ffffff",
    )

    jpg_path = tmp_path / "jpg" / "source.jpg"
    assert exit_code == 0
    assert jpg_path.exists()
    with Image.open(jpg_path) as converted:
        assert converted.mode == "RGB"
        red, green, blue = converted.getpixel((0, 0))
    assert red > green
    assert red > blue


def test_png2jpg_dry_run_does_not_write_files(tmp_path: Path) -> None:
    """Ensure dry-run mode reports success without touching output."""
    image_path = tmp_path / "source.png"
    Image.new("RGB", (2, 1), color="red").save(image_path)

    exit_code = png2jpg_utils.png2jpg(tmp_path, dry_run=True)

    assert exit_code == 0
    assert not (tmp_path / "jpg" / "source.jpg").exists()


def test_convert_png_jobs_reports_every_status_and_progress(tmp_path: Path) -> None:
    converted_source = tmp_path / "converted.png"
    skipped_source = tmp_path / "skipped.png"
    other_source = tmp_path / "other.png"
    for path in (converted_source, skipped_source, other_source):
        Image.new("RGB", (2, 1), color="red").save(path)

    jobs = png2jpg_utils.discover_png_conversion_jobs(tmp_path, out_subdir="jpg")
    skipped_output = tmp_path / "jpg" / "skipped.jpg"
    skipped_output.parent.mkdir()
    Image.new("RGB", (2, 1), color="blue").save(skipped_output)
    updates: list[ProgressUpdate] = []

    results = png2jpg_utils.convert_png_jobs(
        jobs,
        quality=95,
        subsampling=0,
        bg_rgb=(255, 255, 255),
        overwrite=False,
        dry_run=False,
        progress_callback=updates.append,
    )

    assert [result.status for result in results] == [
        "converted",
        "converted",
        "skipped",
    ]
    assert len(updates) == 3
    assert sum(update.advance for update in updates) == 3


def test_img_png2jpg_redirected_output_keeps_legacy_lines(
    tmp_path: Path,
    isolated_cli_project: Path,
) -> None:
    del isolated_cli_project
    source = tmp_path / "source.png"
    Image.new("RGB", (2, 1), color="red").save(source)

    result = invoke_cli(CliRunner(), ["img", "png2jpg", str(tmp_path)])

    assert result.exit_code == 0, result.output
    assert f"[ok]   {source}  ->  {tmp_path / 'jpg' / 'source.jpg'}" in result.output
    assert "\x1b[" not in result.output


def test_img_png2jpg_terminal_replaces_item_lines_with_progress(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
    isolated_cli_project: Path,
) -> None:
    del isolated_cli_project
    source = tmp_path / "source.png"
    Image.new("RGB", (2, 1), color="red").save(source)
    terminal_output = StringIO()
    console = Console(
        file=terminal_output,
        force_terminal=True,
        color_system=None,
        width=100,
    )
    monkeypatch.setattr(cli_image, "stderr_progress_console", lambda: console)

    result = invoke_cli(CliRunner(), ["img", "png2jpg", str(tmp_path)])

    assert result.exit_code == 0, result.output
    assert "[ok]" not in result.output
    assert "PNG conversion complete" in terminal_output.getvalue()
    assert "1/1" in terminal_output.getvalue()


def test_concat_imgs_scales_to_exact_width(tmp_path: Path) -> None:
    """Cover horizontal stacking and rounding drift correction."""
    left = tmp_path / "left.png"
    right = tmp_path / "right.png"
    output = tmp_path / "collage.png"
    Image.new("RGBA", (10, 5), color=(255, 0, 0, 255)).save(left)
    Image.new("RGBA", (20, 10), color=(0, 255, 0, 255)).save(right)

    result = concat_utils.concat_imgs(
        [left, right],
        output=output,
        width=33,
        gap=3,
        bg="transparent",
        align="bottom",
    )

    assert result == output
    with Image.open(output) as collage:
        assert collage.size == (33, 10)