Source code for tests.test_logo

# Copyright Kevin Deldycke <kevin@deldycke.com> and contributors.
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
"""Test the terminal rendition of the Click Extra brand mark.

The layout it is drawn into belongs to {class}`~click_extra.version.VersionScreen`
and is tested in `test_version.py`. What is checked here is the mark itself: that its
geometry lands on the grid, that its palette follows the system it claims to, and
that both survive the terminal.
"""

from __future__ import annotations

import colorsys
import importlib.metadata
import re
from pathlib import Path

import pytest

from click_extra.ansi import nearest_256, strip_ansi
from click_extra.logo import (
    BRAND_HUES,
    BRAND_SCREEN,
    FACE_COLORS,
    LEVELS,
    LOGO_LINES,
    LOGO_WIDTH,
    PLANE_LUMINANCE,
    SATURATION,
    UNIT,
    faces,
    geometry,
    render_logo,
    size,
    sub_pixels,
)
from click_extra.styling import _relative_luminance

WHITE = (255, 255, 255)
BLACK = (0, 0, 0)

SILHOUETTE_FLOOR = 2.0
"""Contrast a face must keep against either background to hold its silhouette.

Below the 3:1 WCAG asks of non-text graphics, deliberately: that threshold assumes a
shape whose only cue is its edge against the page, and this one also has two
neighbouring planes to be read against. What it rules out is a face disappearing.
"""


[docs] def contrast(a, b) -> float: high, low = sorted((_relative_luminance(a), _relative_luminance(b)), reverse=True) return (high + 0.05) / (low + 0.05)
[docs] def rgb(value: str) -> tuple[int, int, int]: channels = (int(value[i : i + 2], 16) for i in (1, 3, 5)) return tuple(channels) # type: ignore[return-value]
ARTWORK = Path(__file__).parent.parent / "docs" / "assets" / "logo-square.svg" """The SVG the terminal rendition shares its palette with. Present in a source checkout and in the sdist, absent from the wheel, which is the whole reason `click_extra.logo` repeats the colors instead of reading them. """ PLANE_NAMES = {"top": "t", "left": "l", "right": "r"} """How the artwork's CSS spells the planes `click_extra.logo` keys by initial."""
[docs] @pytest.mark.skipif(not ARTWORK.exists(), reason="artwork not shipped in the wheel") def test_palette_matches_the_artwork(): """The terminal mark and the artwork paint the same eighteen colors. Both are flat-shaded, so nothing calls for two palettes, and nothing but this notices when one is recolored alone. """ rules = re.findall( r"\.cube(\d)-(top|left|right)\{fill:(#[0-9A-Fa-f]{6});\}", ARTWORK.read_text(encoding="UTF-8"), ) assert len(rules) == len(LEVELS) * 3, f"artwork declares {len(rules)} faces" artwork = { (int(slot) - 1, PLANE_NAMES[plane]): value.upper() for slot, plane, value in rules } declared = { (slot, plane): value.upper() for slot, planes in enumerate(FACE_COLORS) for plane, value in planes.items() } assert artwork == declared
[docs] def test_geometry_lands_on_whole_sub_pixels(): """Every vertex is an integer, so the fill has nothing to round. A polygon landing between sub-pixels is what turns a straight edge into an irregular stair, and no amount of resolution fixes it. """ for slot, plane, polygon in faces(): for x, y in polygon: assert x == int(x) and y == int(y), f"cube {slot} {plane} is off-grid"
[docs] def test_edges_advance_two_sub_pixels_per_row(): """The lid edges are 2:1, which is the whole reason for the projection. The artwork is a true 30-degree isometric, whose edges advance 1.732 sub-pixels per row. No grid holds that, so it comes out as a stair of alternating two- and four-wide treads that reads as fraying. Two across per one down tiles exactly. """ box = geometry() for slot, plane, polygon in faces(): if plane != "t": continue far, right, _near, left = polygon for corner in (right, left): across = abs(corner[0] - far[0]) down = abs(corner[1] - far[1]) assert across == 2 * down, f"cube {slot} lid edge is {across}:{down}" assert right[0] - left[0] == box["width"]
[docs] def test_rows_are_seated_not_floating(): """A cube rests on the two below it, rather than hovering over them. The artwork's own row pitch is wider than a stack allows, which shows the background between the rows. A cube meets the pair beneath it where their lids meet, exactly half a lid below their rims. """ box = geometry() assert box["pitch"] == box["body"] + box["half_lid"]
[docs] def test_mark_fits_its_declared_box(): """Every line is exactly as wide as declared, trailing blanks included.""" width, height = size() assert (width, height // 2) == (LOGO_WIDTH, LOGO_LINES) assert height % 2 == 0, "half blocks pair two rows, so the count must be even" lines = render_logo() assert len(lines) == LOGO_LINES ragged = { index: len(strip_ansi(line)) for index, line in enumerate(lines) if len(strip_ansi(line)) != LOGO_WIDTH } assert not ragged, f"lines not {LOGO_WIDTH} columns wide: {ragged}"
[docs] def test_palette_follows_its_derivation(): """Every face sits at its cube's hue and its plane's luminance. The palette is written out rather than computed, so it stays greppable and a designer can tune one value. Nothing but this notices a tuned value drifting off the system it claims to belong to. """ for slot, planes in enumerate(FACE_COLORS): assert set(planes) == set(PLANE_LUMINANCE) for plane, value in planes.items(): color = rgb(value) hue, _light, sat = colorsys.rgb_to_hls(*(c / 255 for c in color)) assert round(hue * 360) == pytest.approx(BRAND_HUES[slot], abs=1), ( f"cube {slot} {plane} is hue {round(hue * 360)}, not {BRAND_HUES[slot]}" ) assert sat == pytest.approx(SATURATION, abs=0.02) assert _relative_luminance(color) == pytest.approx( PLANE_LUMINANCE[plane], abs=0.01 ), f"cube {slot} {plane} is off its plane's luminance"
[docs] def test_every_cube_shows_exactly_its_three_faces(): """The grid carries the eighteen declared colors and nothing between them. Rasterizing the mark and reading pixels back would let the antialiaser mint colors belonging to no face, over a hundred of them at this size. Hit-testing the polygons is what keeps the count to what the palette declares. """ used = {cell for row in sub_pixels() for cell in row if cell is not None} declared = {rgb(value) for planes in FACE_COLORS for value in planes.values()} assert used == declared assert len(declared) == len(LEVELS) * 3 for slot, planes in enumerate(FACE_COLORS): present = {rgb(value) for value in planes.values()} & used assert len(present) == 3, f"cube {slot} shows {len(present)} of its 3 faces"
[docs] @pytest.mark.parametrize("background", (WHITE, BLACK), ids=("white", "black")) def test_mark_keeps_its_distance_from_both_backgrounds(background): """One palette has to serve a light terminal and a dark one. Flat faces are what make that possible: the shape lives in the difference between a cube's three planes, so only the silhouette has to clear the background, and that fits in the middle of the range. """ faint = { value: round(contrast(rgb(value), background), 2) for planes in FACE_COLORS for value in planes.values() if contrast(rgb(value), background) < SILHOUETTE_FLOOR } assert not faint, f"below {SILHOUETTE_FLOOR}:1 on this background: {faint}"
[docs] def test_neighbouring_faces_survive_a_256_color_terminal(): """No two faces that touch collapse onto the same palette index. The mark is emitted in 24-bit color, so a terminal that downsamples is the degraded case rather than the target. It still has to show six cubes. """ grid = sub_pixels() touching = set() for row, line in enumerate(grid): for column, here in enumerate(line): if here is None: continue for down, across in ((0, 1), (1, 0)): y, x = row + down, column + across if y >= len(grid) or x >= len(line): continue there = grid[y][x] if there is not None and there != here: touching.add(tuple(sorted((here, there)))) merged = [ pair for pair in touching if nearest_256(*pair[0]) == nearest_256(*pair[1]) ] assert not merged, f"pairs that collide once downsampled: {merged}"
def _is_released(distribution: str) -> bool: """Is *distribution* an installed release rather than a branch build? A dependency installed from a Git branch reports a PEP 440 development release (`.devN`) or carries a local version identifier (`+<hash>`), either of which is longer than the release string it stands in for. """ version = importlib.metadata.version(distribution) return ".dev" not in version and "+" not in version SCREEN_DEPENDENCIES_ARE_RELEASES = all(map(_is_released, ("click", "cloup"))) """Whether the dependencies the version screen names are installed releases. The screen's `Built on` row prints the installed Click and Cloup versions, so a branch build widens it past the docs URL that is otherwise the longest row. The width below is a design constraint on the configuration this project *ships*, and the screen already falls back to the plain message when it does not fit, so asserting it against a dev string measures upstream's version scheme rather than this package. """
[docs] @pytest.mark.skipif( not SCREEN_DEPENDENCIES_ARE_RELEASES, reason="A branch-built Click or Cloup widens the screen's `Built on` row.", ) def test_screen_fits_an_eighty_column_terminal(): """The mark is sized so the screen it decorates can actually be drawn. A wider mark is not a bigger logo, it is no logo: the screen measures itself against the terminal and falls back to the plain message when it does not fit. """ rows = BRAND_SCREEN.rows("click-extra", "8.10.0", {}) needed = ( BRAND_SCREEN.width + len(BRAND_SCREEN.gutter) + max(len(plain) for plain, _ in rows) ) assert needed <= 80, f"the screen wants {needed} columns" assert BRAND_SCREEN.width == 12 * UNIT
[docs] def test_mark_is_painted_on_demand_and_once(): """The screen holds the painter, not the painting, and the painter caches. Importing the module costs no rasterization: the mark is painted by the first `--version` that draws the screen, and every later draw reads it back. """ assert BRAND_SCREEN.logo is render_logo assert render_logo() is render_logo() assert BRAND_SCREEN.lines == render_logo()