Initial flame_lua_runtime package
This commit is contained in:
121
test/runtime/models/game_diff_test.dart
Normal file
121
test/runtime/models/game_diff_test.dart
Normal file
@@ -0,0 +1,121 @@
|
||||
import 'package:flame_lua_runtime/runtime/models/game_diff.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
group('GameDiff', () {
|
||||
test('parses render, ui and commands', () {
|
||||
final diff = GameDiff.fromMap({
|
||||
'render': {
|
||||
'creates': [
|
||||
{'id': 'board', 'type': 'image', 'asset': 'board'},
|
||||
],
|
||||
'updates': [
|
||||
{
|
||||
'id': 'piece_red_1',
|
||||
'props': {'x': 100, 'y': 120},
|
||||
},
|
||||
],
|
||||
'removes': ['old_piece'],
|
||||
},
|
||||
'ui': {
|
||||
'creates': [
|
||||
{'id': 'dice_button', 'type': 'button', 'text': 'Roll'},
|
||||
],
|
||||
},
|
||||
'commands': [
|
||||
{'type': 'move_path', 'target': 'piece_red_1', 'duration': 0.5},
|
||||
],
|
||||
});
|
||||
|
||||
expect(diff.render.creates.single.id, 'board');
|
||||
expect(diff.render.updates.single.id, 'piece_red_1');
|
||||
expect(diff.render.updates.single.props['x'], 100);
|
||||
expect(diff.render.removes.single.id, 'old_piece');
|
||||
expect(diff.ui.creates.single.id, 'dice_button');
|
||||
expect(diff.commands.single.type, 'move_path');
|
||||
expect(diff.commands.single.target, 'piece_red_1');
|
||||
expect(diff.commands.single.payload['duration'], 0.5);
|
||||
});
|
||||
|
||||
test('treats missing sections as empty diffs', () {
|
||||
final diff = GameDiff.fromMap({});
|
||||
|
||||
expect(diff.render.creates, isEmpty);
|
||||
expect(diff.render.updates, isEmpty);
|
||||
expect(diff.render.removes, isEmpty);
|
||||
expect(diff.ui.creates, isEmpty);
|
||||
expect(diff.commands, isEmpty);
|
||||
});
|
||||
|
||||
test('accepts Lua numeric-key tables as lists', () {
|
||||
final diff = GameDiff.fromMap({
|
||||
'render': {
|
||||
'creates': {
|
||||
2: {'id': 'b', 'type': 'text'},
|
||||
1: {'id': 'a', 'type': 'text'},
|
||||
},
|
||||
'updates': {},
|
||||
'removes': {
|
||||
1: {'id': 'old_a'},
|
||||
2: 'old_b',
|
||||
},
|
||||
},
|
||||
'commands': {
|
||||
1: {'type': 'toast', 'message': 'hi'},
|
||||
},
|
||||
});
|
||||
|
||||
expect(diff.render.creates.map((node) => node.id), ['a', 'b']);
|
||||
expect(diff.render.removes.map((node) => node.id), ['old_a', 'old_b']);
|
||||
expect(diff.commands.single.type, 'toast');
|
||||
expect(diff.commands.single.payload['message'], 'hi');
|
||||
});
|
||||
|
||||
test('rejects malformed diff fields', () {
|
||||
expect(
|
||||
() => GameDiff.fromMap({
|
||||
'render': {'creates': 'bad'},
|
||||
}),
|
||||
throwsFormatException,
|
||||
);
|
||||
expect(
|
||||
() => GameDiff.fromMap({
|
||||
'render': {
|
||||
'updates': [
|
||||
{'id': 'node', 'props': 'bad'},
|
||||
],
|
||||
},
|
||||
}),
|
||||
throwsFormatException,
|
||||
);
|
||||
expect(
|
||||
() => GameDiff.fromMap({
|
||||
'commands': [
|
||||
{'type': ''},
|
||||
],
|
||||
}),
|
||||
throwsFormatException,
|
||||
);
|
||||
expect(() => GameDiff.fromMap({'unknown': {}}), throwsFormatException);
|
||||
expect(
|
||||
() => GameDiff.fromMap({
|
||||
'render': {'createz': []},
|
||||
}),
|
||||
throwsFormatException,
|
||||
);
|
||||
expect(
|
||||
() => GameDiff.fromMap({
|
||||
'render': {
|
||||
'updates': [
|
||||
{
|
||||
'id': 'node',
|
||||
'props': {'interative': true},
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
throwsFormatException,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
77
test/runtime/models/runtime_event_command_test.dart
Normal file
77
test/runtime/models/runtime_event_command_test.dart
Normal file
@@ -0,0 +1,77 @@
|
||||
import 'package:flame_lua_runtime/runtime/models/runtime_command.dart';
|
||||
import 'package:flame_lua_runtime/runtime/models/runtime_event.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
group('RuntimeEvent', () {
|
||||
test('serializes only present fields', () {
|
||||
final event = RuntimeEvent(
|
||||
type: 'tap',
|
||||
target: 'dice_button',
|
||||
handler: 'roll_dice',
|
||||
x: 10,
|
||||
y: 20,
|
||||
data: {'pointer': 1},
|
||||
);
|
||||
|
||||
expect(event.toMap(), {
|
||||
'type': 'tap',
|
||||
'target': 'dice_button',
|
||||
'handler': 'roll_dice',
|
||||
'x': 10,
|
||||
'y': 20,
|
||||
'data': {'pointer': 1},
|
||||
});
|
||||
});
|
||||
|
||||
test('omits null and empty optional fields', () {
|
||||
expect(const RuntimeEvent(type: 'animation_done').toMap(), {
|
||||
'type': 'animation_done',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('RuntimeCommand', () {
|
||||
test('parses command payload without type and target', () {
|
||||
final command = RuntimeCommand.fromMap({
|
||||
'type': 'move_path',
|
||||
'target': 'piece_red_1',
|
||||
'duration': 0.5,
|
||||
'onComplete': 'done',
|
||||
});
|
||||
|
||||
expect(command.type, 'move_path');
|
||||
expect(command.target, 'piece_red_1');
|
||||
expect(command.payload, {'duration': 0.5, 'onComplete': 'done'});
|
||||
});
|
||||
|
||||
test('rejects invalid command shape', () {
|
||||
expect(() => RuntimeCommand.fromMap({'type': ''}), throwsFormatException);
|
||||
expect(
|
||||
() => RuntimeCommand.fromMap({'type': 'toast', 'target': 1}),
|
||||
throwsFormatException,
|
||||
);
|
||||
expect(
|
||||
() => RuntimeCommand.fromMap({'type': 'unknown'}),
|
||||
throwsFormatException,
|
||||
);
|
||||
expect(
|
||||
() => RuntimeCommand.fromMap({
|
||||
'type': 'move_to',
|
||||
'target': 'piece',
|
||||
'x': 1,
|
||||
'y': 2,
|
||||
'duraton': 0.5,
|
||||
}),
|
||||
throwsFormatException,
|
||||
);
|
||||
expect(
|
||||
() => RuntimeCommand.fromMap({
|
||||
'type': 'preload_resources',
|
||||
'groups': 'pieces',
|
||||
}),
|
||||
throwsFormatException,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
324
test/runtime/models/runtime_node_test.dart
Normal file
324
test/runtime/models/runtime_node_test.dart
Normal file
@@ -0,0 +1,324 @@
|
||||
import 'package:flame_lua_runtime/runtime/models/runtime_node.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
group('RuntimeNode', () {
|
||||
test('parses required and optional fields', () {
|
||||
final node = RuntimeNode.fromMap({
|
||||
'id': 'dice_button',
|
||||
'type': 'button',
|
||||
'parent': 'top_bar',
|
||||
'asset': 'dice_normal',
|
||||
'pressedAsset': 'dice_pressed',
|
||||
'disabledAsset': 'dice_disabled',
|
||||
'animation': 'idle',
|
||||
'skin': 'red',
|
||||
'loop': false,
|
||||
'text': 'Roll',
|
||||
'x': 10,
|
||||
'y': 20.5,
|
||||
'width': 120,
|
||||
'height': 48,
|
||||
'paddingLeft': 4,
|
||||
'paddingTop': 5,
|
||||
'paddingRight': 6,
|
||||
'paddingBottom': 7,
|
||||
'anchor': 'center',
|
||||
'layer': 3,
|
||||
'visible': false,
|
||||
'alpha': 0.7,
|
||||
'scale': 1.2,
|
||||
'rotation': 0.4,
|
||||
'color': '#112233',
|
||||
'fontSize': 18,
|
||||
'textAlign': 'left',
|
||||
'radius': 10,
|
||||
'strokeWidth': 3,
|
||||
'value': 0.6,
|
||||
'scrollX': 15,
|
||||
'scrollY': 20,
|
||||
'contentWidth': 220,
|
||||
'contentHeight': 180,
|
||||
'virtualized': true,
|
||||
'cacheExtent': 12,
|
||||
'inertia': false,
|
||||
'scrollbarThumbColor': '#abcdef',
|
||||
'scrollbarTrackColor': '#123456',
|
||||
'scrollbarThickness': 6,
|
||||
'scrollbarVisible': false,
|
||||
'interactive': true,
|
||||
'onTap': 'roll_dice',
|
||||
'onScroll': 'list_scrolled',
|
||||
'preset': 'burst',
|
||||
'count': 32,
|
||||
'duration': 0.6,
|
||||
'speedMin': 60,
|
||||
'speedMax': 180,
|
||||
'gravityX': 0,
|
||||
'gravityY': 120,
|
||||
'spread': 360,
|
||||
'colorTo': '#00ffcc33',
|
||||
'radiusTo': 0,
|
||||
'autoRemove': false,
|
||||
'fadeOut': false,
|
||||
});
|
||||
|
||||
expect(node.id, 'dice_button');
|
||||
expect(node.type, 'button');
|
||||
expect(node.parent, 'top_bar');
|
||||
expect(node.asset, 'dice_normal');
|
||||
expect(node.pressedAsset, 'dice_pressed');
|
||||
expect(node.disabledAsset, 'dice_disabled');
|
||||
expect(node.animation, 'idle');
|
||||
expect(node.skin, 'red');
|
||||
expect(node.loop, isFalse);
|
||||
expect(node.text, 'Roll');
|
||||
expect(node.x, 10);
|
||||
expect(node.y, 20.5);
|
||||
expect(node.width, 120);
|
||||
expect(node.height, 48);
|
||||
expect(node.paddingLeft, 4);
|
||||
expect(node.paddingTop, 5);
|
||||
expect(node.paddingRight, 6);
|
||||
expect(node.paddingBottom, 7);
|
||||
expect(node.anchor, 'center');
|
||||
expect(node.layer, 3);
|
||||
expect(node.visible, isFalse);
|
||||
expect(node.alpha, 0.7);
|
||||
expect(node.scale, 1.2);
|
||||
expect(node.rotation, 0.4);
|
||||
expect(node.color, const Color(0xff112233));
|
||||
expect(node.fontSize, 18);
|
||||
expect(node.textAlign, 'left');
|
||||
expect(node.radius, 10);
|
||||
expect(node.strokeWidth, 3);
|
||||
expect(node.value, 0.6);
|
||||
expect(node.scrollX, 15);
|
||||
expect(node.scrollY, 20);
|
||||
expect(node.contentWidth, 220);
|
||||
expect(node.contentHeight, 180);
|
||||
expect(node.virtualized, isTrue);
|
||||
expect(node.cacheExtent, 12);
|
||||
expect(node.inertia, isFalse);
|
||||
expect(node.scrollbarThumbColor, const Color(0xffabcdef));
|
||||
expect(node.scrollbarTrackColor, const Color(0xff123456));
|
||||
expect(node.scrollbarThickness, 6);
|
||||
expect(node.scrollbarVisible, isFalse);
|
||||
expect(node.interactive, isTrue);
|
||||
expect(node.onTap, 'roll_dice');
|
||||
expect(node.onScroll, 'list_scrolled');
|
||||
expect(node.preset, 'burst');
|
||||
expect(node.count, 32);
|
||||
expect(node.duration, 0.6);
|
||||
expect(node.speedMin, 60);
|
||||
expect(node.speedMax, 180);
|
||||
expect(node.gravityX, 0);
|
||||
expect(node.gravityY, 120);
|
||||
expect(node.spread, 360);
|
||||
expect(node.colorTo, const Color(0x00ffcc33));
|
||||
expect(node.radiusTo, 0);
|
||||
expect(node.autoRemove, isFalse);
|
||||
expect(node.fadeOut, isFalse);
|
||||
});
|
||||
|
||||
test('applies default values', () {
|
||||
final node = RuntimeNode.fromMap({'id': 'label', 'type': 'text'});
|
||||
|
||||
expect(node.x, 0);
|
||||
expect(node.y, 0);
|
||||
expect(node.anchor, 'topLeft');
|
||||
expect(node.layer, 0);
|
||||
expect(node.visible, isTrue);
|
||||
expect(node.alpha, 1);
|
||||
expect(node.scale, 1);
|
||||
expect(node.rotation, 0);
|
||||
expect(node.loop, isTrue);
|
||||
expect(node.textAlign, 'center');
|
||||
expect(node.scrollbarVisible, isTrue);
|
||||
expect(node.paddingLeft, 0);
|
||||
expect(node.paddingTop, 0);
|
||||
expect(node.paddingRight, 0);
|
||||
expect(node.paddingBottom, 0);
|
||||
expect(node.autoRemove, isTrue);
|
||||
expect(node.fadeOut, isTrue);
|
||||
expect(node.interactive, isFalse);
|
||||
});
|
||||
|
||||
test('copies only provided props', () {
|
||||
final node = RuntimeNode.fromMap({
|
||||
'id': 'piece',
|
||||
'type': 'circle',
|
||||
'x': 1,
|
||||
'y': 2,
|
||||
'color': '#ff0000',
|
||||
});
|
||||
|
||||
final updated = node.copyWithProps({
|
||||
'x': 20,
|
||||
'parent': 'board',
|
||||
'visible': false,
|
||||
'color': '#8000ff00',
|
||||
'radius': 8,
|
||||
'strokeWidth': 2,
|
||||
'value': 0.75,
|
||||
'width': 70,
|
||||
'height': 60,
|
||||
'paddingLeft': 8,
|
||||
'paddingTop': 9,
|
||||
'paddingRight': 10,
|
||||
'paddingBottom': 11,
|
||||
'contentWidth': 120,
|
||||
'contentHeight': 100,
|
||||
'pressedAsset': 'button_pressed',
|
||||
'disabledAsset': 'button_disabled',
|
||||
'scrollX': 90,
|
||||
'scrollY': 80,
|
||||
'textAlign': 'right',
|
||||
'preset': 'trail',
|
||||
'count': 12,
|
||||
});
|
||||
|
||||
expect(updated.id, 'piece');
|
||||
expect(updated.type, 'circle');
|
||||
expect(updated.parent, 'board');
|
||||
expect(updated.x, 20);
|
||||
expect(updated.y, 2);
|
||||
expect(updated.visible, isFalse);
|
||||
expect(updated.color, const Color(0x8000ff00));
|
||||
expect(updated.radius, 8);
|
||||
expect(updated.strokeWidth, 2);
|
||||
expect(updated.value, 0.75);
|
||||
expect(updated.width, 70);
|
||||
expect(updated.height, 60);
|
||||
expect(updated.paddingLeft, 8);
|
||||
expect(updated.paddingTop, 9);
|
||||
expect(updated.paddingRight, 10);
|
||||
expect(updated.paddingBottom, 11);
|
||||
expect(updated.contentWidth, 120);
|
||||
expect(updated.contentHeight, 100);
|
||||
expect(updated.pressedAsset, 'button_pressed');
|
||||
expect(updated.disabledAsset, 'button_disabled');
|
||||
expect(updated.scrollX, 68);
|
||||
expect(updated.scrollY, 60);
|
||||
expect(updated.textAlign, 'right');
|
||||
expect(updated.preset, 'trail');
|
||||
expect(updated.count, 12);
|
||||
});
|
||||
|
||||
test('rejects invalid values', () {
|
||||
expect(
|
||||
() => RuntimeNode.fromMap({'id': '', 'type': 'text'}),
|
||||
throwsFormatException,
|
||||
);
|
||||
expect(
|
||||
() => RuntimeNode.fromMap({'id': 'a', 'type': 'text', 'x': 'bad'}),
|
||||
throwsFormatException,
|
||||
);
|
||||
expect(
|
||||
() => RuntimeNode.fromMap({'id': 'a', 'type': 'text', 'color': 'red'}),
|
||||
throwsFormatException,
|
||||
);
|
||||
expect(
|
||||
() => RuntimeNode.fromMap({'id': 'a', 'type': 'unknown'}),
|
||||
throwsFormatException,
|
||||
);
|
||||
expect(
|
||||
() => RuntimeNode.fromMap({
|
||||
'id': 'a',
|
||||
'type': 'text',
|
||||
'anchor': 'middle',
|
||||
}),
|
||||
throwsFormatException,
|
||||
);
|
||||
expect(
|
||||
() => RuntimeNode.fromMap({'id': 'a', 'type': 'progress', 'value': 2}),
|
||||
throwsFormatException,
|
||||
);
|
||||
expect(
|
||||
() => RuntimeNode.fromMap({
|
||||
'id': 'a',
|
||||
'type': 'text',
|
||||
'textAlign': 'justify',
|
||||
}),
|
||||
throwsFormatException,
|
||||
);
|
||||
expect(
|
||||
() =>
|
||||
RuntimeNode.fromMap({'id': 'a', 'type': 'listView', 'scrollY': -1}),
|
||||
throwsFormatException,
|
||||
);
|
||||
expect(
|
||||
() => RuntimeNode.fromMap({
|
||||
'id': 'a',
|
||||
'type': 'particle',
|
||||
'preset': 'unknown',
|
||||
}),
|
||||
throwsFormatException,
|
||||
);
|
||||
expect(
|
||||
() => RuntimeNode.fromMap({'id': 'a', 'type': 'particle', 'count': 0}),
|
||||
throwsFormatException,
|
||||
);
|
||||
expect(
|
||||
() => RuntimeNode.fromMap({
|
||||
'id': 'a',
|
||||
'type': 'listView',
|
||||
'paddingTop': -1,
|
||||
}),
|
||||
throwsFormatException,
|
||||
);
|
||||
expect(
|
||||
() => RuntimeNode.fromMap({
|
||||
'id': 'a',
|
||||
'type': 'listView',
|
||||
'cacheExtent': -1,
|
||||
}),
|
||||
throwsFormatException,
|
||||
);
|
||||
expect(
|
||||
() => RuntimeNode.fromMap({
|
||||
'id': 'a',
|
||||
'type': 'rect',
|
||||
'interactive': 'yes',
|
||||
}),
|
||||
throwsFormatException,
|
||||
);
|
||||
expect(
|
||||
() => RuntimeNode.fromMap({
|
||||
'id': 'a',
|
||||
'type': 'button',
|
||||
'pressedAsset': 1,
|
||||
}),
|
||||
throwsFormatException,
|
||||
);
|
||||
expect(
|
||||
() => RuntimeNode.fromMap({'id': 'a', 'type': 'rect', 'parent': 1}),
|
||||
throwsFormatException,
|
||||
);
|
||||
expect(
|
||||
() => RuntimeNode.fromMap({'id': 'a', 'type': 'spine', 'loop': 'yes'}),
|
||||
throwsFormatException,
|
||||
);
|
||||
expect(
|
||||
() => RuntimeNode.fromMap({'id': 'a', 'type': 'rect', 'parent': 'a'}),
|
||||
throwsFormatException,
|
||||
);
|
||||
expect(
|
||||
() => RuntimeNode.fromMap({
|
||||
'id': 'a',
|
||||
'type': 'rect',
|
||||
'interative': true,
|
||||
}),
|
||||
throwsFormatException,
|
||||
);
|
||||
expect(
|
||||
() => RuntimeNode.fromMap({
|
||||
'id': 'a',
|
||||
'type': 'rect',
|
||||
}).copyWithProps({'screenWitdh': 720}),
|
||||
throwsFormatException,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user