#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Regression tests for color-mode coherence in design_system.py (issue #428). Style, palette and anti-patterns used to be resolved independently, so a dark-primary style could be returned alongside a light palette and a "Dark mode by default" anti-pattern. Stdlib-only (unittest, not pytest) to match test_core.py -- this project ships with zero external dependencies. Run with: python -m unittest discover -s scripts/tests -v or directly: python scripts/tests/test_design_system_mode.py """ import sys import unittest from pathlib import Path SCRIPTS_DIR = Path(__file__).resolve().parent.parent sys.path.insert(0, str(SCRIPTS_DIR)) from design_system import ( # noqa: E402 _filter_anti_patterns_for_mode, _palette_is_dark, _query_wants_dark, _relative_luminance, _resolve_color_mode, _select_palette_for_mode, _style_is_dark_primary, DesignSystemGenerator, ) # noqa: I001 - private helpers first, public class last LIGHT_PALETTE = {"Product Type": "SaaS", "Background": "#F8FAFC", "Foreground": "#020617"} DARK_PALETTE = {"Product Type": "Fintech/Crypto", "Background": "#0F172A", "Foreground": "#F8FAFC"} # Verbatim from styles.csv row "Modern Dark (Cinema Mobile)". DARK_PRIMARY_STYLE = { "Style Category": "Modern Dark (Cinema Mobile)", "Light Mode ✓": "✓ Light mode only as exception", "Dark Mode ✓": "✓ Dark Mode Primary", } DUAL_MODE_STYLE = { "Style Category": "Minimalism", "Light Mode ✓": "✓ Full", "Dark Mode ✓": "✓ Full", } class TestLuminance(unittest.TestCase): def test_parses_six_and_three_digit_hex(self): self.assertAlmostEqual(_relative_luminance("#FFFFFF"), 1.0, places=6) self.assertAlmostEqual(_relative_luminance("#000000"), 0.0, places=6) self.assertAlmostEqual(_relative_luminance("#FFF"), 1.0, places=6) def test_returns_none_for_unparseable(self): for value in ("", "nope", "#12", "#GGGGGG", None): self.assertIsNone(_relative_luminance(value)) def test_classifies_backgrounds_from_the_shipped_data(self): # Lightest dark background and darkest light background in colors.csv. self.assertTrue(_palette_is_dark({"Background": "#1F2937"})) self.assertFalse(_palette_is_dark({"Background": "#E8ECF1"})) def test_missing_background_is_not_dark(self): self.assertFalse(_palette_is_dark({})) self.assertFalse(_palette_is_dark(None)) class TestModeResolution(unittest.TestCase): def test_dark_primary_style_detected(self): self.assertTrue(_style_is_dark_primary(DARK_PRIMARY_STYLE)) def test_dual_mode_style_is_not_dark_primary(self): self.assertFalse(_style_is_dark_primary(DUAL_MODE_STYLE)) self.assertFalse(_style_is_dark_primary({})) def test_query_keywords(self): self.assertTrue(_query_wants_dark("fintech B2B professional dark mode")) self.assertTrue(_query_wants_dark("gaming app OLED")) self.assertFalse(_query_wants_dark("healthcare clinic booking app")) self.assertFalse(_query_wants_dark("")) def test_either_signal_resolves_dark(self): self.assertEqual(_resolve_color_mode("saas dark mode", DUAL_MODE_STYLE), "dark") self.assertEqual(_resolve_color_mode("saas", DARK_PRIMARY_STYLE), "dark") self.assertEqual(_resolve_color_mode("saas", DUAL_MODE_STYLE), "light") class TestPaletteSelection(unittest.TestCase): def test_dark_mode_skips_light_palettes(self): chosen = _select_palette_for_mode([LIGHT_PALETTE, DARK_PALETTE], "dark") self.assertEqual(chosen["Background"], "#0F172A") def test_dark_mode_falls_back_to_top_hit_when_no_dark_ramp_exists(self): chosen = _select_palette_for_mode([LIGHT_PALETTE], "dark") self.assertEqual(chosen["Background"], "#F8FAFC") def test_light_mode_keeps_the_existing_top_hit_behaviour(self): chosen = _select_palette_for_mode([DARK_PALETTE, LIGHT_PALETTE], "light") self.assertEqual(chosen["Background"], "#0F172A") def test_empty_results(self): self.assertEqual(_select_palette_for_mode([], "dark"), {}) class TestAntiPatternGating(unittest.TestCase): def test_dark_clause_dropped_others_kept(self): result = _filter_anti_patterns_for_mode( "Excessive animation + Dark mode by default", "dark") self.assertEqual(result, "Excessive animation") def test_light_mode_is_a_no_op(self): original = "Excessive animation + Dark mode by default" self.assertEqual(_filter_anti_patterns_for_mode(original, "light"), original) def test_unrelated_anti_patterns_survive_dark_mode(self): original = "Complex jargon + Tiny tap targets" self.assertEqual(_filter_anti_patterns_for_mode(original, "dark"), original) def test_empty_input(self): self.assertEqual(_filter_anti_patterns_for_mode("", "dark"), "") class TestEndToEndCoherence(unittest.TestCase): """The exact reproduction from issue #428.""" QUERY = "SaaS invoicing fintech B2B professional dark mode" def test_dark_query_gets_a_dark_background(self): ds = DesignSystemGenerator().generate(self.QUERY) background = ds["colors"]["background"] self.assertTrue( _palette_is_dark({"Background": background}), "dark-mode query returned a light background: {}".format(background), ) def test_dark_query_foreground_is_lighter_than_background(self): ds = DesignSystemGenerator().generate(self.QUERY) background = _relative_luminance(ds["colors"]["background"]) foreground = _relative_luminance(ds["colors"]["foreground"]) self.assertIsNotNone(background) self.assertIsNotNone(foreground) self.assertGreater(foreground, background) def test_dark_query_does_not_advise_against_dark_mode(self): ds = DesignSystemGenerator().generate(self.QUERY) self.assertNotIn("dark mode", ds["anti_patterns"].lower()) def test_light_query_keeps_a_light_background(self): ds = DesignSystemGenerator().generate("healthcare clinic booking app") self.assertFalse(_palette_is_dark({"Background": ds["colors"]["background"]})) if __name__ == "__main__": unittest.main(verbosity=2)