feat(tokens): add design tokens, type scale, push scaffold, and root nav state

This commit is contained in:
2026-07-28 20:01:23 +02:00
parent 6dba89432e
commit 172b2a42ac
17 changed files with 856 additions and 30 deletions
+62
View File
@@ -0,0 +1,62 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pluriwave/tema/pluriwave_tokens.dart';
/// Design ADR-1: 3 new colour fields join the existing PluriWaveTokens
/// extension (colours belong here because they must participate in lerp).
/// Dedicated file under test/tema/ so the WU1 verify command
/// (`flutter test test/tema/ ...`) discovers it; the single pre-existing
/// PluriWaveTokens assertion inside pluriwave_foundations_test.dart is left
/// untouched.
void main() {
group('PluriWaveTokens — redesign colours', () {
test('dark keeps the pre-existing base palette', () {
expect(PluriWaveTokens.dark.deepViolet, const Color(0xFF07121A));
expect(PluriWaveTokens.dark.radiusMd, 22);
expect(PluriWaveTokens.dark.spacingMd, 16);
});
test(
'dark exposes listSurface and liveGreen de-literalised from theme.dart',
() {
// Design ADR-1: these match the theme's previous raw literals
// (surfaceContainerLow / secondary) exactly, so promoting them to
// named tokens keeps rendered output byte-identical.
expect(PluriWaveTokens.dark.listSurface, const Color(0xFF102532));
expect(PluriWaveTokens.dark.liveGreen, const Color(0xFF7EE4C2));
},
);
test('dark exposes offlineAccent as a genuinely new colour', () {
expect(PluriWaveTokens.dark.offlineAccent, const Color(0xFF94A3B8));
});
test('copyWith overrides only the new colour fields', () {
final overridden = PluriWaveTokens.dark.copyWith(
listSurface: Colors.black,
liveGreen: Colors.black,
offlineAccent: Colors.black,
);
expect(overridden.listSurface, Colors.black);
expect(overridden.liveGreen, Colors.black);
expect(overridden.offlineAccent, Colors.black);
// Untouched fields keep their original value.
expect(overridden.deepViolet, PluriWaveTokens.dark.deepViolet);
});
test('lerp interpolates listSurface, liveGreen and offlineAccent', () {
const a = PluriWaveTokens.dark;
final b = a.copyWith(
listSurface: Colors.white,
liveGreen: Colors.white,
offlineAccent: Colors.white,
);
final mid = a.lerp(b, 0.5);
expect(mid.listSurface, Color.lerp(a.listSurface, Colors.white, 0.5));
expect(mid.liveGreen, Color.lerp(a.liveGreen, Colors.white, 0.5));
expect(mid.offlineAccent, Color.lerp(a.offlineAccent, Colors.white, 0.5));
});
});
}
+87
View File
@@ -0,0 +1,87 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pluriwave/tema/pluriwave_theme.dart';
import 'package:pluriwave/tema/pluriwave_typography.dart';
/// Design ADR-1: the named type scale is a second ThemeExtension, closed at
/// six styles. Pins the documented size/weight/letter-spacing per style and
/// confirms PluriWaveTheme.dark() registers it, mirroring how
/// PluriWaveTokens/PluriWaveMotion are already tested.
///
/// PluriWaveTheme.dark() is only ever resolved from inside a testWidgets
/// callback (never from setUpAll or a plain test()). GoogleFonts.
/// plusJakartaSansTextTheme fires a background network fetch per text role;
/// TestWidgetsFlutterBinding only installs its HTTP-blocking override
/// inside an active test-widgets zone, so calling PluriWaveTheme.dark()
/// outside one lets a real (and here unreachable) network request escape
/// and fail asynchronously. The numeric values asserted below (fontSize/
/// weight/letterSpacing/height) are set synchronously by
/// PluriWaveTypography.from regardless of that background fetch's outcome.
void main() {
late PluriWaveTypography type;
group('PluriWaveTypography', () {
testWidgets('PluriWaveTheme.dark() registers the extension', (
tester,
) async {
final theme = PluriWaveTheme.dark();
final extension = theme.extension<PluriWaveTypography>();
expect(extension, isNotNull);
type = extension!;
});
test('heroTime is 88 / w800 / height 1.0 / letter-spacing -2.0', () {
expect(type.heroTime.fontSize, 88);
expect(type.heroTime.fontWeight, FontWeight.w800);
expect(type.heroTime.height, 1.0);
expect(type.heroTime.letterSpacing, -2.0);
});
test('sectionTitle is 23 / w800 / letter-spacing -0.6', () {
expect(type.sectionTitle.fontSize, 23);
expect(type.sectionTitle.fontWeight, FontWeight.w800);
expect(type.sectionTitle.letterSpacing, -0.6);
});
test('screenTitle is 19 / w800 / letter-spacing -0.4', () {
expect(type.screenTitle.fontSize, 19);
expect(type.screenTitle.fontWeight, FontWeight.w800);
expect(type.screenTitle.letterSpacing, -0.4);
});
test('cardTitle is 14.5 / w700', () {
expect(type.cardTitle.fontSize, 14.5);
expect(type.cardTitle.fontWeight, FontWeight.w700);
});
test('bodyStrong is 13 / w600', () {
expect(type.bodyStrong.fontSize, 13);
expect(type.bodyStrong.fontWeight, FontWeight.w600);
});
test('eyebrowLabel is 11 / w800 / letter-spacing 0.8', () {
expect(type.eyebrowLabel.fontSize, 11);
expect(type.eyebrowLabel.fontWeight, FontWeight.w800);
expect(type.eyebrowLabel.letterSpacing, 0.8);
});
testWidgets('context.pluriType exposes the registered extension', (
tester,
) async {
late BuildContext capturedContext;
await tester.pumpWidget(
MaterialApp(
theme: PluriWaveTheme.dark(),
home: Builder(
builder: (context) {
capturedContext = context;
return const SizedBox.shrink();
},
),
),
);
expect(capturedContext.pluriType.cardTitle.fontSize, 14.5);
});
});
}