import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:miler/views/helpers/constants/Colorconstants.dart'; import 'package:miler/views/helpers/constants/miler_surface.dart'; /// ───────────────────────────────────────────────────────────────────────── /// THE LAYERS HAVE TO BE VISIBLE TO BE LAYERS /// /// The app looked flat for a measurable reason, not an aesthetic one: white on /// the page ground was a **1.124 : 1** step. A tonal difference does not begin /// to read as a *different surface* until roughly 1.25, and on the budget LCD /// this app ships to it needs the top of that band. So every screen that /// wanted its content to separate grew a border or a shadow to do the ground's /// job — which is how an app ends up looking like a stack of rounded /// rectangles while its ground does nothing. /// /// There were also five near-identical light tokens, three of them within /// 1.03 of the canvas: indistinguishable, and therefore five names for two /// decisions. /// /// These pin the arithmetic. A contrast rule that lives only in a comment is a /// rule that gets undone by the next person who nudges a hex. /// ───────────────────────────────────────────────────────────────────────── double _luminance(Color c) { double channel(double v) => v <= 0.03928 ? v / 12.92 : math.pow((v + 0.055) / 1.055, 2.4).toDouble(); return 0.2126 * channel(c.r) + 0.7152 * channel(c.g) + 0.0722 * channel(c.b); } double contrast(Color a, Color b) { final la = _luminance(a); final lb = _luminance(b); final hi = math.max(la, lb); final lo = math.min(la, lb); return (hi + 0.05) / (lo + 0.05); } void main() { group('the ladder has real rungs', () { test('a working surface separates from the canvas without a border', () { final step = contrast(MilerSurface.working, MilerSurface.canvas); expect( step, greaterThanOrEqualTo(1.25), reason: 'white on the canvas is $step : 1 — below ~1.25 a surface does ' 'not read as a surface, and screens start drawing their own ' 'outlines to compensate', ); // And not so far that the page turns into a dark frame around content. expect(step, lessThan(1.6)); }); test('the canvas is never white', () { expect(MilerSurface.canvas, isNot(MilerSurface.working)); expect(MilerSurface.canvas, isNot(const Color(0xFFFFFFFF))); }); test('raised and floating carry lift, working does not', () { // What separates an action surface from a reading surface is that it // appears to sit above the page — the fill is the same on purpose. expect(MilerSurface.raisedShadow, isNotEmpty); expect(MilerSurface.floatingShadow, isNotEmpty); expect(MilerSurface.raised, MilerSurface.working); }); test('the scrim carries the modal separation', () { // A sheet that needs a heavy shadow to be legible is a sheet whose scrim // is too weak. expect(MilerSurface.scrim.a, greaterThan(0.3)); }); }); group('text stays readable on every layer', () { // AA body is 4.5:1. Every operational colour is asserted on the canvas — // the darkest ground text sits on — so a screen cannot fail by moving // content from a card onto the page. final grounds = { 'canvas': MilerSurface.canvas, 'working': MilerSurface.working, }; final inks = { 'slateText': ColorConstants.slateText, 'secondaryText': ColorConstants.secondaryText, 'onSurfaceVariant': ColorConstants.onSurfaceVariant, 'primary': ColorConstants.primary, 'acceptGreen': ColorConstants.acceptGreen, 'warning': ColorConstants.warning, 'errorRed': ColorConstants.errorRed, }; for (final g in grounds.entries) { for (final ink in inks.entries) { test('${ink.key} on ${g.key} clears AA', () { final r = contrast(ink.value, g.value); expect( r, greaterThanOrEqualTo(4.5), reason: '${ink.key} on ${g.key} is ${r.toStringAsFixed(2)} : 1', ); }); } } }); group('the near-duplicate tokens are gone or justified', () { test('the inset tone is distinguishable from the surface it sits on', () { // The only nesting the system allows. If it cannot be seen against the // working surface it is furniture, not a layer. final step = contrast(MilerSurface.inset, MilerSurface.working); expect(step, greaterThan(1.05)); }); test('brand red is identity, error red is not the same colour', () { // Red must not mean two things. Navigation/primary and failure have to // be tellable apart at a glance, outdoors. expect(ColorConstants.primary, isNot(ColorConstants.errorRed)); expect( contrast(ColorConstants.primary, ColorConstants.errorRed), greaterThan(1.15), reason: 'brand red and error red must not read as the same ink', ); }); }); }