$theme]); preg_match_all('/(--[\w-]+): ([^;]+);/', theme_css_vars(), $matches); return array_combine($matches[1], $matches[2]); } /** Relative luminance (WCAG) of an "r g b" CSS variable value. */ private function luminance(string $rgb): float { $channels = array_map(function ($value) { $c = ((int) $value) / 255; return $c <= 0.03928 ? $c / 12.92 : (($c + 0.055) / 1.055) ** 2.4; }, explode(' ', trim($rgb))); return 0.2126 * $channels[0] + 0.7152 * $channels[1] + 0.0722 * $channels[2]; } public function test_every_theme_has_readable_secondary_buttons(): void { foreach (array_keys(config('themes.themes')) as $theme) { $vars = $this->vars($theme); $this->assertArrayHasKey('--color-button-secondary', $vars, $theme); $this->assertArrayHasKey('--color-button-secondary-text', $vars, $theme); $light = $this->luminance($vars['--color-button-secondary']); $dark = $this->luminance($vars['--color-button-secondary-text']); $contrast = (max($light, $dark) + 0.05) / (min($light, $dark) + 0.05); $this->assertGreaterThanOrEqual(4.5, $contrast, "$theme: secondary button text contrast is $contrast"); } } public function test_default_theme_keeps_its_look(): void { $vars = $this->vars('timebank_cc'); $this->assertSame($vars['--color-secondary'], $vars['--color-button-secondary']); $this->assertSame($vars['--color-text-primary'], $vars['--color-button-secondary-text']); } public function test_theme_without_button_colours_falls_back_to_secondary(): void { $custom = config('themes.themes.timebank_cc'); unset($custom['colors']['button-secondary'], $custom['colors']['button-secondary-text']); $custom['colors']['secondary'] = '#123456'; config(['themes.themes.custom' => $custom]); $vars = $this->vars('custom'); $this->assertSame('18 52 86', $vars['--color-button-secondary']); $this->assertSame($vars['--color-text-primary'], $vars['--color-button-secondary-text']); } }