import 'package:flutter_test/flutter_test.dart'; import 'package:latlong2/latlong.dart'; import 'package:meshcore_sar_app/models/map_drawing.dart'; import 'package:meshcore_sar_app/utils/drawing_message_parser.dart'; void main() { group('DrawingMessageParser - Critical String Formatting Tests', () { test('createDrawingMessage returns raw string, not Object', () { final drawing = LineDrawing( id: 'test-123', color: DrawingColors.palette[0], createdAt: DateTime.now(), points: [ LatLng(37.7749, -122.4194), LatLng(37.7750, -122.4195), ], ); final message = DrawingMessageParser.createDrawingMessage(drawing); // CRITICAL: Must be a String type expect(message, isA()); // CRITICAL: Must NOT contain "Instance of" or "Object" expect(message, isNot(contains('Instance of'))); expect(message, isNot(contains('Object'))); // CRITICAL: Must start with D: prefix expect(message, startsWith('D:')); // CRITICAL: Must be valid JSON after prefix final jsonPart = message.substring(2); expect(() => jsonPart, returnsNormally); }); test('createDrawingMessage produces parseable JSON string', () { final drawing = LineDrawing( id: 'test-456', color: DrawingColors.palette[2], // green createdAt: DateTime.now(), points: [ LatLng(40.7128, -74.0060), LatLng(40.7129, -74.0061), LatLng(40.7130, -74.0062), ], ); final message = DrawingMessageParser.createDrawingMessage(drawing); // Should be parseable back final parsed = DrawingMessageParser.parseDrawingMessage( message, senderName: 'Test Sender', messageId: 'msg-123', ); expect(parsed, isNotNull); expect(parsed, isA()); expect((parsed as LineDrawing).points.length, equals(3)); }); test('createDrawingMessage handles rectangle with proper string format', () { final drawing = RectangleDrawing( id: 'rect-789', color: DrawingColors.palette[4], // orange createdAt: DateTime.now(), topLeft: LatLng(45.5231, -122.6765), bottomRight: LatLng(45.5100, -122.6600), ); final message = DrawingMessageParser.createDrawingMessage(drawing); // CRITICAL: Must be pure string expect(message, isA()); expect(message, startsWith('D:')); // Should contain compact JSON format expect(message, contains('"t":')); expect(message, contains('"c":')); expect(message, contains('"b":')); // Must NOT contain any object representations expect(message, isNot(contains('RectangleDrawing'))); expect(message, isNot(contains('Instance'))); }); test('JSON encoding produces string with coordinates as numbers', () { final drawing = LineDrawing( id: 'coord-test', color: DrawingColors.palette[1], // blue createdAt: DateTime.now(), points: [ LatLng(37.77490, -122.41940), ], ); final message = DrawingMessageParser.createDrawingMessage(drawing); // Extract JSON part final jsonStr = message.substring(2); // CRITICAL: Coordinates must be numbers, not strings // Format: {"t":0,"c":1,"p":[37.7749,-122.4194]} expect(jsonStr, contains('37.7749')); expect(jsonStr, contains('-122.4194')); // Should NOT have coordinates as quoted strings expect(jsonStr, isNot(contains('"37.7749"'))); expect(jsonStr, isNot(contains('"-122.4194"'))); }); test('isDrawingMessage correctly identifies valid drawing messages', () { expect(DrawingMessageParser.isDrawingMessage('D:{"t":0,"c":1,"p":[1,2]}'), isTrue); expect(DrawingMessageParser.isDrawingMessage('S:🧑:37,-122'), isFalse); expect(DrawingMessageParser.isDrawingMessage('Plain text'), isFalse); expect(DrawingMessageParser.isDrawingMessage('D:'), isTrue); }); test('parseDrawingMessage returns null for malformed messages', () { expect( DrawingMessageParser.parseDrawingMessage('Not a drawing'), isNull, ); expect( DrawingMessageParser.parseDrawingMessage('D:invalid json'), isNull, ); }); test('round-trip: create -> parse -> create produces consistent output', () { final original = LineDrawing( id: 'roundtrip-1', color: DrawingColors.palette[3], // yellow createdAt: DateTime.now(), points: [ LatLng(51.5074, -0.1278), LatLng(51.5075, -0.1279), ], ); // Create message final message1 = DrawingMessageParser.createDrawingMessage(original); // Parse it final parsed = DrawingMessageParser.parseDrawingMessage( message1, senderName: 'TestUser', messageId: 'msg-rt1', ); expect(parsed, isNotNull); // Create message again from parsed final message2 = DrawingMessageParser.createDrawingMessage(parsed!); // Both messages should have same structure (excluding metadata like IDs) expect(message1.substring(0, 10), equals(message2.substring(0, 10))); }); test('color indices are preserved as integers in JSON', () { for (int i = 0; i < DrawingColors.palette.length; i++) { final drawing = LineDrawing( id: 'color-$i', color: DrawingColors.palette[i], createdAt: DateTime.now(), points: [LatLng(0, 0)], ); final message = DrawingMessageParser.createDrawingMessage(drawing); final jsonStr = message.substring(2); // Color index should be integer in JSON expect(jsonStr, contains('"c":$i')); } }); test('type indices are preserved correctly', () { // Line = type 0 final line = LineDrawing( id: 'line-type', color: DrawingColors.palette[0], createdAt: DateTime.now(), points: [LatLng(1, 1), LatLng(2, 2)], ); final lineMsg = DrawingMessageParser.createDrawingMessage(line); expect(lineMsg, contains('"t":0')); // Rectangle = type 1 final rect = RectangleDrawing( id: 'rect-type', color: DrawingColors.palette[0], createdAt: DateTime.now(), topLeft: LatLng(1, 1), bottomRight: LatLng(2, 2), ); final rectMsg = DrawingMessageParser.createDrawingMessage(rect); expect(rectMsg, contains('"t":1')); }); test('coordinates are rounded to 5 decimal places', () { final drawing = LineDrawing( id: 'precision-test', color: DrawingColors.palette[0], createdAt: DateTime.now(), points: [ LatLng(37.774901234567, -122.419401234567), ], ); final message = DrawingMessageParser.createDrawingMessage(drawing); final jsonStr = message.substring(2); // Should be rounded to 5 decimals expect(jsonStr, contains('37.7749')); expect(jsonStr, contains('-122.4194')); // Should NOT contain extra precision expect(jsonStr, isNot(contains('37.774901234567'))); }); test('empty points array produces valid message', () { final drawing = LineDrawing( id: 'empty-line', color: DrawingColors.palette[0], createdAt: DateTime.now(), points: [], ); final message = DrawingMessageParser.createDrawingMessage(drawing); expect(message, isA()); expect(message, startsWith('D:')); expect(message, contains('"p":[]')); }); test('large coordinate values are handled correctly', () { final drawing = LineDrawing( id: 'large-coords', color: DrawingColors.palette[0], createdAt: DateTime.now(), points: [ LatLng(89.99999, 179.99999), // Near max valid coords LatLng(-89.99999, -179.99999), // Near min valid coords ], ); final message = DrawingMessageParser.createDrawingMessage(drawing); expect(message, isA()); expect(message, contains('89.99999')); expect(message, contains('179.99999')); expect(message, contains('-89.99999')); expect(message, contains('-179.99999')); }); test('getDrawingTypeDisplay returns correct type names', () { final lineMsg = 'D:{"t":0,"c":1,"p":[1,2,3,4]}'; final rectMsg = 'D:{"t":1,"c":2,"b":[1,2,3,4]}'; expect(DrawingMessageParser.getDrawingTypeDisplay(lineMsg), equals('Line')); expect(DrawingMessageParser.getDrawingTypeDisplay(rectMsg), equals('Rectangle')); expect(DrawingMessageParser.getDrawingTypeDisplay('Invalid'), isNull); }); test('getColorName returns correct color names', () { for (int i = 0; i < 8; i++) { final msg = 'D:{"t":0,"c":$i,"p":[0,0]}'; final colorName = DrawingMessageParser.getColorName(msg); expect(colorName, isNotNull); expect(colorName, isA()); } expect(DrawingMessageParser.getColorName('Invalid'), isNull); }); test('getDrawingMetadata extracts correct information', () { final lineMsg = 'D:{"t":0,"c":2,"p":[1,2,3,4,5,6]}'; final metadata = DrawingMessageParser.getDrawingMetadata(lineMsg); expect(metadata, isNotNull); expect(metadata!['type'], equals('Line')); expect(metadata['color'], equals('Green')); expect(metadata['pointCount'], equals(3)); // 6 values = 3 points }); test('message does not contain sender metadata in JSON', () { final drawing = LineDrawing( id: 'no-sender', color: DrawingColors.palette[0], createdAt: DateTime.now(), points: [LatLng(1, 2)], senderName: 'TestSender', // Should NOT be in network JSON ); final message = DrawingMessageParser.createDrawingMessage(drawing); // CRITICAL: Sender name should NOT be in the message expect(message, isNot(contains('sender'))); expect(message, isNot(contains('TestSender'))); // Should only contain compact fields: t, c, p (or b) final jsonStr = message.substring(2); expect(jsonStr, contains('"t":')); expect(jsonStr, contains('"c":')); expect(jsonStr, matches(RegExp(r'"p":|"b":'))); }); test('special characters in metadata do not break format', () { // Even though sender name isn't included in network JSON, // test that it doesn't affect the message creation final drawing = LineDrawing( id: 'special-chars-"quotes"', color: DrawingColors.palette[0], createdAt: DateTime.now(), points: [LatLng(1, 2)], ); final message = DrawingMessageParser.createDrawingMessage(drawing); expect(message, isA()); expect(message, startsWith('D:')); // Should still be parseable expect(() => DrawingMessageParser.parseDrawingMessage(message), returnsNormally); }); }); }