import 'package:flame/components.dart'; import 'package:flame_lua_runtime/runtime/models/runtime_node.dart'; import 'package:flame_lua_runtime/runtime/protocol/runtime_protocol.dart'; import 'package:flame_lua_runtime/runtime/rendering/runtime_component.dart'; import 'package:flame_lua_runtime/runtime/resources/game_resource_manager.dart'; import 'package:flutter/material.dart' show Color; import 'package:flutter_test/flutter_test.dart'; void main() { group('RuntimeComponent', () { test('applies base transform and priority from node', () { final component = RuntimeComponent( node: const RuntimeNode( id: 'rect', type: RuntimeNodeType.rect, x: 10, y: 20, width: 120, height: 48, scale: 1.5, rotation: 0.25, anchor: RuntimeAnchorValue.center, layer: 7, ), resources: GameResourceManager(), onNodeTap: (_, __) {}, ); expect(component.position, Vector2(10, 20)); expect(component.size, Vector2(120, 48)); expect(component.scale, Vector2.all(1.5)); expect(component.angle, 0.25); expect(component.anchor, Anchor.center); expect(component.priority, 7); expect(component.isVisible, isTrue); }); test('updates node and transform', () { final component = RuntimeComponent( node: const RuntimeNode(id: 'node', type: RuntimeNodeType.rect), resources: GameResourceManager(), onNodeTap: (_, __) {}, ); component.updateNode( const RuntimeNode( id: 'node', type: RuntimeNodeType.progress, x: 30, y: 40, width: 200, height: 16, value: 0.5, layer: 3, ), ); expect(component.node.type, RuntimeNodeType.progress); expect(component.node.value, 0.5); expect(component.position, Vector2(30, 40)); expect(component.size, Vector2(200, 16)); expect(component.priority, 3); expect(component.isVisible, isTrue); }); test('visibility hides component subtree and disables hit testing', () { final component = RuntimeComponent( node: const RuntimeNode( id: 'button', type: RuntimeNodeType.button, text: 'Hidden', width: 100, height: 40, visible: false, interactive: true, ), resources: GameResourceManager(), onNodeTap: (_, __) {}, ); expect(component.isVisible, isFalse); expect(component.containsLocalPoint(Vector2(10, 10)), isFalse); component.updateNode( const RuntimeNode( id: 'button', type: RuntimeNodeType.button, text: 'Shown', width: 100, height: 40, visible: true, interactive: true, ), ); expect(component.isVisible, isTrue); expect(component.containsLocalPoint(Vector2(10, 10)), isTrue); }); test('supports runtime alpha override for fade commands', () { final component = RuntimeComponent( node: const RuntimeNode( id: 'panel', type: RuntimeNodeType.rect, alpha: 0.8, ), resources: GameResourceManager(), onNodeTap: (_, __) {}, ); expect(component.renderAlpha, 0.8); component.setRuntimeAlpha(0.25); expect(component.renderAlpha, 0.25); component.setRuntimeAlpha(2); expect(component.renderAlpha, 1); }); test('multiplies color alpha with node and runtime alpha', () { expect( composeRuntimeColorAlpha(const Color(0xffffffff), 1).a, closeTo(1, 0.001), ); expect( composeRuntimeColorAlpha(const Color(0x80ffffff), 1).a, closeTo(0.5, 0.003), ); expect( composeRuntimeColorAlpha(const Color(0x80ffffff), 0.5).a, closeTo(0.25, 0.003), ); expect( composeRuntimeColorAlpha(const Color(0x00ffffff), 1).a, closeTo(0, 0.001), ); expect( composeRuntimeColorAlpha(const Color(0x80ffffff), 0.25).a, closeTo(0.125, 0.003), ); }); test('multi-line non-button text is top aligned', () { final component = RuntimeComponent( node: const RuntimeNode( id: 'text', type: RuntimeNodeType.text, text: 'line1\nline2', width: 120, height: 80, textAlign: RuntimeTextAlignValue.left, ), resources: GameResourceManager(), onNodeTap: (_, __) {}, ); final text = component.children.whereType().single; expect(text.anchor, Anchor.topLeft); expect(text.position, Vector2.zero()); }); test('only interactive nodes contain local points', () { final passive = RuntimeComponent( node: const RuntimeNode( id: 'passive', type: RuntimeNodeType.rect, width: 100, height: 40, ), resources: GameResourceManager(), onNodeTap: (_, __) {}, ); final interactive = RuntimeComponent( node: const RuntimeNode( id: 'button', type: RuntimeNodeType.button, width: 100, height: 40, interactive: true, ), resources: GameResourceManager(), onNodeTap: (_, __) {}, ); expect(passive.containsLocalPoint(Vector2(10, 10)), isFalse); expect(interactive.containsLocalPoint(Vector2(10, 10)), isTrue); expect(interactive.containsLocalPoint(Vector2(101, 10)), isFalse); expect(interactive.containsLocalPoint(Vector2(10, 41)), isFalse); }); }); }