Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/SDK/Language/Flutter.php
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,11 @@ public function getFiles(): array
'destination' => '/test/src/realtime_response_test.dart',
'template' => 'flutter/test/src/realtime_response_test.dart.twig',
],
[
'scope' => 'default',
'destination' => '/test/src/realtime_message_test.dart',
'template' => 'flutter/test/src/realtime_message_test.dart.twig',
],
[
'scope' => 'default',
'destination' => '/test/src/realtime_response_connected_test.dart',
Expand Down
38 changes: 35 additions & 3 deletions templates/flutter/lib/src/realtime_message.dart.twig
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import 'package:flutter/foundation.dart';
/// Realtime Message
class RealtimeMessage {
/// All permutations of the system event that triggered this message
///
///
/// The first event in the list is the most specfic event without wildcards.
final List<String> events;

Expand Down Expand Up @@ -54,13 +54,45 @@ class RealtimeMessage {
/// Initializes a [RealtimeMessage] from a [Map<String, dynamic>].
factory RealtimeMessage.fromMap(Map<String, dynamic> map) {
return RealtimeMessage(
events: List<String>.from(map['events'] ?? []),
events: _stringListFrom(map['events']),
payload: Map<String, dynamic>.from(map['payload'] ?? <String, dynamic>{}),
channels: List<String>.from(map['channels'] ?? []),
channels: _stringListFrom(map['channels']),
timestamp: map['timestamp'],
);
}

static List<String> _stringListFrom(dynamic value) {
if (value == null) {
return [];
}

if (value is Iterable) {
return List<String>.from(value);
}
Comment thread
greptile-apps[bot] marked this conversation as resolved.

if (value is Map) {
if (value.keys.every(_isArrayIndexKey)) {
return List<String>.from(value.values);
}

return List<String>.from(value.keys);
}

return [];
}

static bool _isArrayIndexKey(dynamic key) {
if (key is int) {
return key >= 0;
}

if (key is String) {
return int.tryParse(key) != null;
}

return false;
}

/// Converts a [RealtimeMessage] to a JSON [String].
String toJson() => json.encode(toMap());

Expand Down
157 changes: 157 additions & 0 deletions templates/flutter/test/src/realtime_message_test.dart.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:{{language.params.packageName}}/src/realtime_message.dart';

void main() {
group('RealtimeMessage', () {
final events = ['databases.*.collections.*.documents.*.create'];
final payload = {'\$id': 'message-id'};
final channels = ['databases.default.collections.messages.documents'];
final timestamp = '2026-02-26T12:00:00.000+00:00';
final message = RealtimeMessage(
events: events,
payload: payload,
channels: channels,
timestamp: timestamp,
);

test('toMap should return a map representation of the message', () {
final messageMap = message.toMap();

expect(messageMap['events'], equals(events));
expect(messageMap['payload'], equals(payload));
expect(messageMap['channels'], equals(channels));
expect(messageMap['timestamp'], equals(timestamp));
});

test('fromMap should create an instance from lists', () {
final messageMap = {
'events': events,
'payload': payload,
'channels': channels,
'timestamp': timestamp,
};

final result = RealtimeMessage.fromMap(messageMap);

expect(result.events, equals(events));
expect(result.payload, equals(payload));
expect(result.channels, equals(channels));
expect(result.timestamp, equals(timestamp));
});

test('fromMap should create an instance from maps', () {
final messageMap = {
'events': {'0': events.first},
'payload': payload,
'channels': {'0': channels.first},
Comment thread
greptile-apps[bot] marked this conversation as resolved.
'timestamp': timestamp,
};

final result = RealtimeMessage.fromMap(messageMap);

expect(result.events, equals(events));
expect(result.payload, equals(payload));
expect(result.channels, equals(channels));
expect(result.timestamp, equals(timestamp));
});

test('fromMap should use map values when map keys are integers', () {
final messageMap = {
'events': {0: events.first},
'payload': payload,
'channels': {0: channels.first},
'timestamp': timestamp,
};

final result = RealtimeMessage.fromMap(messageMap);

expect(result.events, equals(events));
expect(result.channels, equals(channels));
});

test('fromMap should use map keys when map values are not strings', () {
final messageMap = {
'events': {events.first: true},
'payload': payload,
'channels': {channels.first: true},
'timestamp': timestamp,
};

final result = RealtimeMessage.fromMap(messageMap);

expect(result.events, equals(events));
expect(result.channels, equals(channels));
});

test('fromMap should use map keys for mixed-value maps', () {
final messageMap = {
'events': {
events.first: 'someDesc',
'databases.*.collections.*.documents.*.update': null,
},
'payload': payload,
'channels': {channels.first: 'someDesc'},
'timestamp': timestamp,
};

final result = RealtimeMessage.fromMap(messageMap);

expect(
result.events,
equals([events.first, 'databases.*.collections.*.documents.*.update']),
);
expect(result.channels, equals(channels));
});

test(
'fromMap should use empty lists when events and channels are null',
() {
final messageMap = {
'events': null,
'payload': payload,
'channels': null,
'timestamp': timestamp,
};

final result = RealtimeMessage.fromMap(messageMap);

expect(result.events, isEmpty);
expect(result.channels, isEmpty);
},
);

test(
'fromMap should use empty lists when events and channels are absent',
() {
final messageMap = {'payload': payload, 'timestamp': timestamp};

final result = RealtimeMessage.fromMap(messageMap);

expect(result.events, isEmpty);
expect(result.channels, isEmpty);
},
);

test('fromMap should throw when list items are not strings', () {
final messageMap = {
'events': [true],
'payload': payload,
'channels': channels,
'timestamp': timestamp,
};

expect(
() => RealtimeMessage.fromMap(messageMap),
throwsA(isA<TypeError>()),
);
});

test('toJson and fromJson should convert to/from JSON', () {
final jsonString = message.toJson();

final result = RealtimeMessage.fromJson(jsonString);

expect(result, equals(message));
});
});
}