// Tests for FlowYamlCodeController's type-token recolouring. // // The controller post-processes the YAML highlighter's span // tree to recolour any `key: ` value where the value is // one of `text|json|bytes|file|number|integer` and the key sits // at an indented depth (so top-level `name: text` doesn't // false-match). We can't easily inspect TextSpan colours without // rendering, so the tests below render the controller into a // CodeField inside a TestWidget tree and walk the produced // span tree. import 'package:chain_studio_flow_editor/src/flow_yaml_controller.dart'; import 'package:chain_studio_flow_editor/src/wire_colors.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { group('FlowYamlCodeController type-token recoloring', () { testWidgets('colours indented `field: bytes` value with bytes wire hue', ( tester, ) async { final c = FlowYamlCodeController(); addTearDown(c.dispose); c.fullText = ''' inputs: document: bytes '''; late TextSpan span; late ThemeData theme; await tester.pumpWidget( MaterialApp( home: Builder( builder: (context) { theme = Theme.of(context); span = c.buildTextSpan(context: context, withComposing: false); return const SizedBox(); }, ), ), ); // Let CodeController's 500ms analyzer debounce fire so // the test teardown isn't blocked by a pending timer. await tester.pump(const Duration(seconds: 1)); final tokens = _flattenLeaves(span); // Find the leaf whose text is the word "bytes" and assert // it carries the wire-bytes colour from the shared palette. final bytesLeaf = tokens.firstWhere( (t) => t.text == 'bytes', orElse: () => const _Leaf('', null), ); expect(bytesLeaf.text, 'bytes', reason: 'expected a "bytes" leaf'); final expected = wireColor('bytes', theme); expect(bytesLeaf.style?.color, expected); expect(bytesLeaf.style?.fontWeight, FontWeight.w600); }); testWidgets('does NOT recolour top-level `name: text` (not indented)', ( tester, ) async { final c = FlowYamlCodeController(); addTearDown(c.dispose); // The flow's top-level `name: hello` is also not a type // declaration; we use `text` here to make the trap // explicit. The leading `name:` is at column 0 — not // indented — so the recolour must skip it. c.fullText = 'name: text\n'; late TextSpan span; late ThemeData theme; await tester.pumpWidget( MaterialApp( home: Builder( builder: (context) { theme = Theme.of(context); span = c.buildTextSpan(context: context, withComposing: false); return const SizedBox(); }, ), ), ); // Let CodeController's 500ms analyzer debounce fire so // the test teardown isn't blocked by a pending timer. await tester.pump(const Duration(seconds: 1)); final tokens = _flattenLeaves(span); final textLeaf = tokens.firstWhere( (t) => t.text == 'text', orElse: () => const _Leaf('', null), ); // At top level the highlighter does paint it as a string // (green-ish, palette-derived), but it must NOT match the // explicit wire-text hue + 600 weight combo our recolour // applies — that's the signal we'd be incorrectly tagging // it as a type declaration. final wireText = wireColor('text', theme); final isRecoloured = textLeaf.style?.color == wireText && textLeaf.style?.fontWeight == FontWeight.w600; expect( isRecoloured, isFalse, reason: 'top-level `name: text` must not be flagged as a type decl', ); }); testWidgets('colours `type: text` value (explicit-type module shape)', ( tester, ) async { final c = FlowYamlCodeController(); addTearDown(c.dispose); c.fullText = ''' inputs: - name: pdf type: bytes '''; late TextSpan span; late ThemeData theme; await tester.pumpWidget( MaterialApp( home: Builder( builder: (context) { theme = Theme.of(context); span = c.buildTextSpan(context: context, withComposing: false); return const SizedBox(); }, ), ), ); // Let CodeController's 500ms analyzer debounce fire so // the test teardown isn't blocked by a pending timer. await tester.pump(const Duration(seconds: 1)); final tokens = _flattenLeaves(span); final bytesLeaves = tokens.where((t) => t.text == 'bytes').toList(); expect(bytesLeaves, isNotEmpty); final expected = wireColor('bytes', theme); // Every `bytes` leaf in this snippet is the type value of // the `type: bytes` line. for (final leaf in bytesLeaves) { expect(leaf.style?.color, expected); } }); }); } class _Leaf { final String text; final TextStyle? style; const _Leaf(this.text, this.style); } List<_Leaf> _flattenLeaves(InlineSpan span) { final out = <_Leaf>[]; void visit(InlineSpan s, TextStyle? inherited) { if (s is! TextSpan) return; final merged = inherited == null ? s.style : (s.style == null ? inherited : inherited.merge(s.style)); final t = s.text; if (t != null && t.isNotEmpty) out.add(_Leaf(t, merged)); final children = s.children; if (children != null) { for (final c in children) visit(c, merged); } } visit(span, null); return out; }