Skip to content

Commit 3784195

Browse files
committed
test(fortal): cover Code variant colours, rings, and link focus
Closes a coverage gap carried over from the port: the suite verified Code's font-size math but never its variant fills, foregrounds, or outline rings. Adds all four variants across light/dark and highContrast, the one-ring / two-ring outline split, ghost's opt-in accent (upstream gates it on data-accent-color for ghost alone), Code truncation reaching the label, Kbd's min-width, and the link focus outline drawing without shifting layout.
1 parent ff01db1 commit 3784195

2 files changed

Lines changed: 227 additions & 34 deletions

File tree

packages/remix_fortal/test/components/checkbox/checkbox_group_widget_test.dart

Lines changed: 32 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -47,39 +47,40 @@ void main() {
4747
}
4848
});
4949

50-
testWidgets('fortalCheckboxGroupItemStyle delegates across the whole matrix', (
51-
tester,
52-
) async {
53-
// The wrapper's only job is to forward variant/size/highContrast into the
54-
// checkbox recipe, so every combination must resolve identically — not
55-
// just the default one. `CheckboxStyler` has no value equality (unlike
56-
// `ToggleGroupStyler`), so this compares the resolved specs instead.
57-
for (final variant in FortalCheckboxVariant.values) {
58-
for (final size in FortalCheckboxSize.values) {
59-
for (final highContrast in const [false, true]) {
60-
final reason = '$variant/$size/highContrast=$highContrast';
61-
final wrapped = await _resolveCheckboxStyle(
62-
tester,
63-
fortalCheckboxGroupItemStyle(
64-
variant: variant,
65-
size: size,
66-
highContrast: highContrast,
67-
),
68-
);
69-
final direct = await _resolveCheckboxStyle(
70-
tester,
71-
fortalCheckboxStyle(
72-
variant: variant,
73-
size: size,
74-
highContrast: highContrast,
75-
),
76-
);
50+
testWidgets(
51+
'fortalCheckboxGroupItemStyle delegates across the whole matrix',
52+
(tester) async {
53+
// The wrapper's only job is to forward variant/size/highContrast into the
54+
// checkbox recipe, so every combination must resolve identically — not
55+
// just the default one. `CheckboxStyler` has no value equality (unlike
56+
// `ToggleGroupStyler`), so this compares the resolved specs instead.
57+
for (final variant in FortalCheckboxVariant.values) {
58+
for (final size in FortalCheckboxSize.values) {
59+
for (final highContrast in const [false, true]) {
60+
final reason = '$variant/$size/highContrast=$highContrast';
61+
final wrapped = await _resolveCheckboxStyle(
62+
tester,
63+
fortalCheckboxGroupItemStyle(
64+
variant: variant,
65+
size: size,
66+
highContrast: highContrast,
67+
),
68+
);
69+
final direct = await _resolveCheckboxStyle(
70+
tester,
71+
fortalCheckboxStyle(
72+
variant: variant,
73+
size: size,
74+
highContrast: highContrast,
75+
),
76+
);
7777

78-
expect(wrapped, equals(direct), reason: reason);
78+
expect(wrapped, equals(direct), reason: reason);
79+
}
7980
}
8081
}
81-
}
82-
});
82+
},
83+
);
8384

8485
testWidgets('FortalCheckboxGroupItem participates in group selection', (
8586
tester,
@@ -90,10 +91,7 @@ void main() {
9091
builder: (context, setState) => RemixCheckboxGroup<String>(
9192
values: values,
9293
onChanged: (next) => setState(() => values = next),
93-
child: const FortalCheckboxGroupItem<String>(
94-
value: 'a',
95-
label: 'A',
96-
),
94+
child: const FortalCheckboxGroupItem<String>(value: 'a', label: 'A'),
9795
),
9896
),
9997
);

packages/remix_fortal/test/components/typography/typography_test.dart

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,149 @@ void main() {
222222
});
223223
});
224224

225+
group('code variants', () {
226+
testWidgets('every variant resolves the pinned fill and foreground', (
227+
tester,
228+
) async {
229+
for (final brightness in Brightness.values) {
230+
final tokens = await _accentTokens(tester, brightness);
231+
for (final highContrast in [false, true]) {
232+
for (final variant in FortalCodeVariant.values) {
233+
await _pump(
234+
tester,
235+
FortalCode(
236+
'${variant.name}-$highContrast',
237+
variant: variant,
238+
highContrast: highContrast,
239+
),
240+
brightness: brightness,
241+
);
242+
final fill = _boxColor(_surface(tester));
243+
final fg = _text(
244+
tester,
245+
'${variant.name}-$highContrast',
246+
).style?.color;
247+
248+
switch (variant) {
249+
case FortalCodeVariant.solid:
250+
expect(fill, highContrast ? tokens.accent12 : tokens.accentA9);
251+
expect(
252+
fg,
253+
highContrast ? tokens.accent1 : tokens.accentContrast,
254+
);
255+
case FortalCodeVariant.soft:
256+
expect(fill, tokens.accentA3);
257+
expect(fg, highContrast ? tokens.accent12 : tokens.accentA11);
258+
case FortalCodeVariant.outline:
259+
expect(fg, highContrast ? tokens.accent12 : tokens.accentA11);
260+
case FortalCodeVariant.ghost:
261+
// Upstream gates ghost's colour on an explicit accent, so an
262+
// opt-out ghost inherits the ambient foreground.
263+
expect(fill, isNull);
264+
expect(fg, isNull);
265+
}
266+
}
267+
}
268+
}
269+
});
270+
271+
testWidgets('outline draws one ring, two at high contrast', (tester) async {
272+
final tokens = await _accentTokens(tester, Brightness.light);
273+
for (final highContrast in [false, true]) {
274+
await _pump(
275+
tester,
276+
FortalCode.outline(
277+
'ring-$highContrast',
278+
size: FortalTextSize.size9,
279+
highContrast: highContrast,
280+
),
281+
);
282+
final shadows = _surface(
283+
tester,
284+
).containerEffects!.behindContent!.shadows;
285+
expect(shadows, hasLength(highContrast ? 2 : 1));
286+
expect(
287+
shadows.first.color,
288+
highContrast ? tokens.accentA7 : tokens.accentA8,
289+
);
290+
for (final shadow in shadows) {
291+
expect(shadow.spreadRadius, closeTo(0.033 * 60 * 0.95 * 0.95, 1e-9));
292+
}
293+
}
294+
});
295+
296+
testWidgets('ghost gains the accent foreground only when opted in', (
297+
tester,
298+
) async {
299+
final tokens = await _accentTokens(tester, Brightness.light);
300+
await _pump(
301+
tester,
302+
const Column(
303+
children: [
304+
FortalCode.ghost('plain ghost'),
305+
FortalCode.ghost('accent ghost', accent: true),
306+
FortalCode.ghost(
307+
'accent hc ghost',
308+
accent: true,
309+
highContrast: true,
310+
),
311+
],
312+
),
313+
);
314+
expect(_text(tester, 'plain ghost').style?.color, isNull);
315+
expect(_text(tester, 'accent ghost').style?.color, tokens.accentA11);
316+
expect(_text(tester, 'accent hc ghost').style?.color, tokens.accent12);
317+
});
318+
319+
testWidgets('truncation and kbd min-width reach the rendered box', (
320+
tester,
321+
) async {
322+
await _pump(
323+
tester,
324+
const SizedBox(
325+
width: 60,
326+
child: FortalCode.soft('a very long code sample', truncate: true),
327+
),
328+
);
329+
final code = _text(tester, 'a very long code sample');
330+
expect(code.maxLines, 1);
331+
expect(code.overflow, TextOverflow.ellipsis);
332+
333+
await _pump(
334+
tester,
335+
const FortalKbd.soft('x', size: FortalTextSize.size2),
336+
);
337+
expect(
338+
_surface(tester).container.spec.constraints?.minWidth,
339+
closeTo(1.75 * 14 * 0.8, 1e-9),
340+
);
341+
});
342+
});
343+
344+
group('focus', () {
345+
testWidgets('focused link draws the outline without moving layout', (
346+
tester,
347+
) async {
348+
final focusNode = FocusNode();
349+
addTearDown(focusNode.dispose);
350+
await _pump(
351+
tester,
352+
FortalLink('focus me', focusNode: focusNode, onPressed: _noop),
353+
);
354+
final idleSize = tester.getSize(find.text('focus me'));
355+
356+
focusNode.requestFocus();
357+
await tester.pump();
358+
359+
final effects = _surface(tester).containerEffects!;
360+
expect(effects.outline.width, 2);
361+
expect(effects.outlineOffset, 2);
362+
expect(tester.getSize(find.text('focus me')), idleSize);
363+
// Focus replaces the underline rather than stacking both.
364+
expect(_underlined(tester, 'focus me'), isFalse);
365+
});
366+
});
367+
225368
group('theme', () {
226369
for (final brightness in Brightness.values) {
227370
testWidgets('kbd classic keeps all six ${brightness.name} layers', (
@@ -438,5 +581,57 @@ BadgeSpec _surface(WidgetTester tester) {
438581
return widget.style.resolve(tester.element(find.byType(RemixBadge))).spec;
439582
}
440583

584+
Color? _boxColor(BadgeSpec spec) =>
585+
(spec.container.spec.decoration as BoxDecoration?)?.color;
586+
587+
Future<
588+
({
589+
Color accent1,
590+
Color accent12,
591+
Color accentA3,
592+
Color accentA7,
593+
Color accentA8,
594+
Color accentA9,
595+
Color accentA11,
596+
Color accentContrast,
597+
})
598+
>
599+
_accentTokens(WidgetTester tester, Brightness brightness) async {
600+
late ({
601+
Color accent1,
602+
Color accent12,
603+
Color accentA3,
604+
Color accentA7,
605+
Color accentA8,
606+
Color accentA9,
607+
Color accentA11,
608+
Color accentContrast,
609+
})
610+
result;
611+
await tester.pumpWidget(
612+
FortalScope(
613+
brightness: brightness,
614+
child: Builder(
615+
builder: (context) {
616+
Color token(ColorToken value) => MixScope.tokenOf(value, context);
617+
result = (
618+
accent1: token(FortalTokens.accent1),
619+
accent12: token(FortalTokens.accent12),
620+
accentA3: token(FortalTokens.accentA3),
621+
accentA7: token(FortalTokens.accentA7),
622+
accentA8: token(FortalTokens.accentA8),
623+
accentA9: token(FortalTokens.accentA9),
624+
accentA11: token(FortalTokens.accentA11),
625+
accentContrast: token(FortalTokens.accentContrast),
626+
);
627+
return const SizedBox.shrink();
628+
},
629+
),
630+
),
631+
);
632+
633+
return result;
634+
}
635+
441636
bool _underlined(WidgetTester tester, String text) =>
442637
_text(tester, text).style?.decoration == TextDecoration.underline;

0 commit comments

Comments
 (0)