overhaul tests
This commit is contained in:
parent
e58c9875e0
commit
baebafea59
22 changed files with 2637 additions and 52 deletions
55
test/models/category_test.dart
Normal file
55
test/models/category_test.dart
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:frosty/models/category.dart';
|
||||
|
||||
void main() {
|
||||
group('CategoryTwitch', () {
|
||||
test('fromJson deserializes snake_case fields', () {
|
||||
final category = CategoryTwitch.fromJson(const {
|
||||
'box_art_url': 'https://cdn/boxart.jpg',
|
||||
'id': '509658',
|
||||
'name': 'Just Chatting',
|
||||
});
|
||||
|
||||
expect(category.boxArtUrl, 'https://cdn/boxart.jpg');
|
||||
expect(category.id, '509658');
|
||||
expect(category.name, 'Just Chatting');
|
||||
});
|
||||
});
|
||||
|
||||
group('CategoriesTwitch', () {
|
||||
test('fromJson deserializes data list and pagination', () {
|
||||
final categories = CategoriesTwitch.fromJson(const {
|
||||
'data': [
|
||||
{
|
||||
'box_art_url': 'https://cdn/boxart.jpg',
|
||||
'id': '509658',
|
||||
'name': 'Just Chatting',
|
||||
},
|
||||
],
|
||||
'pagination': {'cursor': 'cat_cursor'},
|
||||
});
|
||||
|
||||
expect(categories.data.length, 1);
|
||||
expect(categories.data.first.name, 'Just Chatting');
|
||||
expect(categories.pagination?['cursor'], 'cat_cursor');
|
||||
});
|
||||
|
||||
test('fromJson handles empty data list', () {
|
||||
final categories = CategoriesTwitch.fromJson(const {
|
||||
'data': <dynamic>[],
|
||||
'pagination': <String, String>{},
|
||||
});
|
||||
|
||||
expect(categories.data, isEmpty);
|
||||
});
|
||||
|
||||
test('fromJson handles null pagination', () {
|
||||
final categories = CategoriesTwitch.fromJson(const {
|
||||
'data': <dynamic>[],
|
||||
'pagination': null,
|
||||
});
|
||||
|
||||
expect(categories.pagination, isNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
49
test/models/channel_test.dart
Normal file
49
test/models/channel_test.dart
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:frosty/models/channel.dart';
|
||||
|
||||
import '../fixtures/api_responses.dart';
|
||||
|
||||
void main() {
|
||||
group('Channel', () {
|
||||
test('fromJson deserializes snake_case fields', () {
|
||||
final channel = Channel.fromJson(
|
||||
(twitchChannelResponse['data']! as List).first as Map<String, dynamic>,
|
||||
);
|
||||
|
||||
expect(channel.broadcasterId, '12345');
|
||||
expect(channel.broadcasterLogin, 'testuser');
|
||||
expect(channel.broadcasterName, 'TestUser');
|
||||
expect(channel.broadcasterLanguage, 'en');
|
||||
expect(channel.title, 'Test Stream');
|
||||
expect(channel.gameId, '509658');
|
||||
expect(channel.gameName, 'Just Chatting');
|
||||
});
|
||||
});
|
||||
|
||||
group('ChannelQuery', () {
|
||||
test('fromJson deserializes snake_case fields', () {
|
||||
final query = ChannelQuery.fromJson(
|
||||
(twitchChannelQueryResponse['data']! as List).first
|
||||
as Map<String, dynamic>,
|
||||
);
|
||||
|
||||
expect(query.broadcasterLogin, 'testuser');
|
||||
expect(query.displayName, 'TestUser');
|
||||
expect(query.id, '12345');
|
||||
expect(query.isLive, isTrue);
|
||||
expect(query.startedAt, '2024-01-01T00:00:00Z');
|
||||
});
|
||||
|
||||
test('fromJson handles isLive false', () {
|
||||
final query = ChannelQuery.fromJson(const {
|
||||
'broadcaster_login': 'offline',
|
||||
'display_name': 'Offline',
|
||||
'id': '99999',
|
||||
'is_live': false,
|
||||
'started_at': '',
|
||||
});
|
||||
|
||||
expect(query.isLive, isFalse);
|
||||
});
|
||||
});
|
||||
}
|
||||
36
test/models/chatters_test.dart
Normal file
36
test/models/chatters_test.dart
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:frosty/models/chatters.dart';
|
||||
|
||||
import '../fixtures/api_responses.dart';
|
||||
|
||||
void main() {
|
||||
group('ChatUsers', () {
|
||||
test('fromJson deserializes chatter count and chatters', () {
|
||||
final chatUsers = ChatUsers.fromJson(
|
||||
chattersResponse as Map<String, dynamic>,
|
||||
);
|
||||
|
||||
expect(chatUsers.chatterCount, 5);
|
||||
expect(chatUsers.chatters.broadcaster, ['streamer']);
|
||||
expect(chatUsers.chatters.vips, ['vip1']);
|
||||
expect(chatUsers.chatters.moderators, ['mod1']);
|
||||
expect(chatUsers.chatters.viewers, ['viewer1', 'viewer2']);
|
||||
});
|
||||
});
|
||||
|
||||
group('Chatters', () {
|
||||
test('fromJson handles all role lists', () {
|
||||
final chatters = Chatters.fromJson(
|
||||
chattersResponse['chatters'] as Map<String, dynamic>,
|
||||
);
|
||||
|
||||
expect(chatters.broadcaster, hasLength(1));
|
||||
expect(chatters.vips, hasLength(1));
|
||||
expect(chatters.moderators, hasLength(1));
|
||||
expect(chatters.staff, isEmpty);
|
||||
expect(chatters.admins, isEmpty);
|
||||
expect(chatters.globalMods, isEmpty);
|
||||
expect(chatters.viewers, hasLength(2));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -34,6 +34,19 @@ void main() {
|
|||
|
||||
expect(emote.ownerId, '67890');
|
||||
});
|
||||
|
||||
test('handles null owner ID', () {
|
||||
final twitchEmote = EmoteTwitch(
|
||||
'25',
|
||||
'Kappa',
|
||||
'bitstier',
|
||||
null,
|
||||
);
|
||||
|
||||
final emote = Emote.fromTwitch(twitchEmote, EmoteType.twitchGlobal);
|
||||
|
||||
expect(emote.ownerId, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('Emote.fromBTTV', () {
|
||||
|
|
@ -47,53 +60,21 @@ void main() {
|
|||
expect(emote.type, EmoteType.bttvGlobal);
|
||||
});
|
||||
|
||||
test('marks SoSnowy as zero-width', () {
|
||||
final bttvEmote = EmoteBTTV('snowyid', 'SoSnowy');
|
||||
|
||||
final emote = Emote.fromBTTV(bttvEmote, EmoteType.bttvGlobal);
|
||||
|
||||
expect(emote.zeroWidth, isTrue);
|
||||
});
|
||||
|
||||
test('marks IceCold as zero-width', () {
|
||||
final bttvEmote = EmoteBTTV('iceid', 'IceCold');
|
||||
|
||||
final emote = Emote.fromBTTV(bttvEmote, EmoteType.bttvChannel);
|
||||
|
||||
expect(emote.zeroWidth, isTrue);
|
||||
});
|
||||
|
||||
test('marks SantaHat as zero-width', () {
|
||||
final bttvEmote = EmoteBTTV('santaid', 'SantaHat');
|
||||
|
||||
final emote = Emote.fromBTTV(bttvEmote, EmoteType.bttvShared);
|
||||
|
||||
expect(emote.zeroWidth, isTrue);
|
||||
});
|
||||
|
||||
test('marks TopHat as zero-width', () {
|
||||
final bttvEmote = EmoteBTTV('topid', 'TopHat');
|
||||
|
||||
final emote = Emote.fromBTTV(bttvEmote, EmoteType.bttvGlobal);
|
||||
|
||||
expect(emote.zeroWidth, isTrue);
|
||||
});
|
||||
|
||||
test('marks cvMask as zero-width', () {
|
||||
final bttvEmote = EmoteBTTV('maskid', 'cvMask');
|
||||
|
||||
final emote = Emote.fromBTTV(bttvEmote, EmoteType.bttvGlobal);
|
||||
|
||||
expect(emote.zeroWidth, isTrue);
|
||||
});
|
||||
|
||||
test('marks cvHazmat as zero-width', () {
|
||||
final bttvEmote = EmoteBTTV('hazmatid', 'cvHazmat');
|
||||
|
||||
final emote = Emote.fromBTTV(bttvEmote, EmoteType.bttvGlobal);
|
||||
|
||||
expect(emote.zeroWidth, isTrue);
|
||||
});
|
||||
for (final name in [
|
||||
'SoSnowy',
|
||||
'IceCold',
|
||||
'SantaHat',
|
||||
'TopHat',
|
||||
'ReinDeer',
|
||||
'CandyCane',
|
||||
'cvMask',
|
||||
'cvHazmat',
|
||||
]) {
|
||||
test('marks $name as zero-width', () {
|
||||
final emote = Emote.fromBTTV(EmoteBTTV('id', name), EmoteType.bttvGlobal);
|
||||
expect(emote.zeroWidth, isTrue);
|
||||
});
|
||||
}
|
||||
|
||||
test('regular emotes are not zero-width', () {
|
||||
final bttvEmote = EmoteBTTV('regularid', 'RegularEmote');
|
||||
|
|
@ -214,6 +195,29 @@ void main() {
|
|||
// Should use animated 2x since animated 4x is not available
|
||||
expect(emote.url, 'https://cdn.ffz.net/anim/2x.gif');
|
||||
});
|
||||
|
||||
test('animated with only 1x falls back to animated 1x', () {
|
||||
final ffzEmote = EmoteFFZ(
|
||||
'AnimOnly1x',
|
||||
24,
|
||||
24,
|
||||
const OwnerFFZ(displayName: 'Owner', name: 'owner'),
|
||||
const ImagesFFZ(
|
||||
'https://cdn.ffz.net/static/1x.png',
|
||||
'https://cdn.ffz.net/static/2x.png',
|
||||
'https://cdn.ffz.net/static/4x.png',
|
||||
),
|
||||
const ImagesFFZ(
|
||||
'https://cdn.ffz.net/anim/1x.gif',
|
||||
null, // No animated 2x
|
||||
null, // No animated 4x
|
||||
),
|
||||
);
|
||||
|
||||
final emote = Emote.fromFFZ(ffzEmote, EmoteType.ffzGlobal);
|
||||
|
||||
expect(emote.url, 'https://cdn.ffz.net/anim/1x.gif');
|
||||
});
|
||||
});
|
||||
|
||||
group('Emote.from7TV', () {
|
||||
|
|
@ -473,6 +477,27 @@ void main() {
|
|||
});
|
||||
});
|
||||
|
||||
group('EmoteBTTVChannel', () {
|
||||
test('combines channel and shared emotes', () {
|
||||
final json = {
|
||||
'channelEmotes': [
|
||||
{'id': 'ch1', 'code': 'ChannelEmote'},
|
||||
],
|
||||
'sharedEmotes': [
|
||||
{'id': 'sh1', 'code': 'SharedEmote1'},
|
||||
{'id': 'sh2', 'code': 'SharedEmote2'},
|
||||
],
|
||||
};
|
||||
|
||||
final result = EmoteBTTVChannel.fromJson(json);
|
||||
|
||||
expect(result.channelEmotes.length, 1);
|
||||
expect(result.channelEmotes.first.code, 'ChannelEmote');
|
||||
expect(result.sharedEmotes.length, 2);
|
||||
expect(result.sharedEmotes.first.code, 'SharedEmote1');
|
||||
});
|
||||
});
|
||||
|
||||
group('Emote JSON serialization', () {
|
||||
test('roundtrip serialization works', () {
|
||||
const original = Emote(
|
||||
|
|
|
|||
110
test/models/events_test.dart
Normal file
110
test/models/events_test.dart
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:frosty/models/events.dart';
|
||||
|
||||
import '../fixtures/api_responses.dart';
|
||||
|
||||
void main() {
|
||||
group('SevenTVEvent', () {
|
||||
test('fromJson deserializes op, t, and d fields', () {
|
||||
final event = SevenTVEvent.fromJson(
|
||||
sevenTVEventJson as Map<String, dynamic>,
|
||||
);
|
||||
|
||||
expect(event.op, 0);
|
||||
expect(event.t, 1);
|
||||
expect(event.d.type, 'emote_set.update');
|
||||
});
|
||||
|
||||
test('toJson roundtrip preserves data', () {
|
||||
final event = SevenTVEvent.fromJson(
|
||||
sevenTVEventJson as Map<String, dynamic>,
|
||||
);
|
||||
|
||||
// Use jsonEncode/jsonDecode for a true roundtrip, since toJson()
|
||||
// doesn't recursively serialize nested objects without explicitToJson.
|
||||
final json = jsonDecode(jsonEncode(event.toJson()))
|
||||
as Map<String, dynamic>;
|
||||
final restored = SevenTVEvent.fromJson(json);
|
||||
|
||||
expect(restored.op, event.op);
|
||||
expect(restored.t, event.t);
|
||||
expect(restored.d.type, event.d.type);
|
||||
});
|
||||
|
||||
test('fromJson handles null t field', () {
|
||||
final event = SevenTVEvent.fromJson(const {
|
||||
'op': 2,
|
||||
'd': {'type': null},
|
||||
});
|
||||
|
||||
expect(event.op, 2);
|
||||
expect(event.t, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('SevenTVEventData', () {
|
||||
test('fromJson deserializes type and condition', () {
|
||||
final data = SevenTVEventData.fromJson(
|
||||
(sevenTVEventJson as Map<String, dynamic>)['d']
|
||||
as Map<String, dynamic>,
|
||||
);
|
||||
|
||||
expect(data.type, 'emote_set.update');
|
||||
expect(data.condition?['object_id'], 'set_abc123');
|
||||
});
|
||||
|
||||
test('fromJson with null type', () {
|
||||
final data = SevenTVEventData.fromJson(const {
|
||||
'type': null,
|
||||
'condition': null,
|
||||
});
|
||||
|
||||
expect(data.type, isNull);
|
||||
expect(data.condition, isNull);
|
||||
});
|
||||
|
||||
test('toJson produces valid output', () {
|
||||
const data = SevenTVEventData(
|
||||
type: 'emote_set.update',
|
||||
condition: {'object_id': 'test'},
|
||||
);
|
||||
|
||||
final json = data.toJson();
|
||||
|
||||
expect(json['type'], 'emote_set.update');
|
||||
expect(json['condition'], {'object_id': 'test'});
|
||||
});
|
||||
});
|
||||
|
||||
group('SevenTVEventEmoteSetBody', () {
|
||||
test('fromJson deserializes actor and pushed/pulled lists', () {
|
||||
final body = SevenTVEventEmoteSetBody.fromJson(const {
|
||||
'actor': {
|
||||
'username': 'moderator',
|
||||
'display_name': 'Moderator',
|
||||
},
|
||||
'pushed': <dynamic>[],
|
||||
'pulled': <dynamic>[],
|
||||
});
|
||||
|
||||
expect(body.actor.username, 'moderator');
|
||||
expect(body.actor.displayName, 'Moderator');
|
||||
expect(body.pushed, isEmpty);
|
||||
expect(body.pulled, isEmpty);
|
||||
});
|
||||
|
||||
test('fromJson handles null pushed/pulled', () {
|
||||
final body = SevenTVEventEmoteSetBody.fromJson(const {
|
||||
'actor': {
|
||||
'username': 'mod',
|
||||
'display_name': 'Mod',
|
||||
},
|
||||
});
|
||||
|
||||
expect(body.pushed, isNull);
|
||||
expect(body.pulled, isNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:frosty/constants.dart';
|
||||
import 'package:frosty/models/irc.dart';
|
||||
|
||||
import '../fixtures/irc_messages.dart';
|
||||
|
|
@ -173,7 +174,7 @@ void main() {
|
|||
expect(msg.mention, isFalse);
|
||||
});
|
||||
|
||||
test('mention is null when userLogin not provided', () {
|
||||
test('mention is false when userLogin not provided', () {
|
||||
final msg = IRCMessage.fromString(basicPrivmsg);
|
||||
expect(msg.mention, isFalse);
|
||||
});
|
||||
|
|
@ -228,6 +229,17 @@ void main() {
|
|||
expect(msg.tags['source-room-id'], '99999');
|
||||
expect(msg.tags['room-id'], '12345');
|
||||
});
|
||||
|
||||
test('parses message with URL in split', () {
|
||||
final msg = IRCMessage.fromString(messageWithUrl);
|
||||
|
||||
expect(msg.message, contains('https://twitch.tv/channel'));
|
||||
expect(msg.split, isNotNull);
|
||||
expect(
|
||||
msg.split!.any((word) => regexLink.hasMatch(word)),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -456,6 +468,126 @@ void main() {
|
|||
});
|
||||
});
|
||||
|
||||
group('IRCMessage.clearChat — buffered messages', () {
|
||||
test('marks buffered messages from banned user', () {
|
||||
final messages = <IRCMessage>[];
|
||||
final bufferedMessages = [
|
||||
IRCMessage(
|
||||
raw: '',
|
||||
command: Command.privateMessage,
|
||||
tags: {'id': 'buf-1'},
|
||||
user: 'banneduser',
|
||||
message: 'Buffered msg',
|
||||
),
|
||||
IRCMessage(
|
||||
raw: '',
|
||||
command: Command.privateMessage,
|
||||
tags: {'id': 'buf-2'},
|
||||
user: 'innocentuser',
|
||||
message: 'Other msg',
|
||||
),
|
||||
];
|
||||
|
||||
final clearChatMsg = IRCMessage(
|
||||
raw: '',
|
||||
command: Command.clearChat,
|
||||
tags: {'ban-duration': '600'},
|
||||
message: 'banneduser',
|
||||
);
|
||||
|
||||
IRCMessage.clearChat(
|
||||
messages: messages,
|
||||
bufferedMessages: bufferedMessages,
|
||||
ircMessage: clearChatMsg,
|
||||
);
|
||||
|
||||
expect(bufferedMessages[0].command, Command.clearChat);
|
||||
expect(bufferedMessages[0].tags['ban-duration'], '600');
|
||||
expect(bufferedMessages[1].command, Command.privateMessage);
|
||||
});
|
||||
|
||||
test('full chat clear also clears buffered messages', () {
|
||||
final messages = [
|
||||
IRCMessage(
|
||||
raw: '',
|
||||
command: Command.privateMessage,
|
||||
tags: {},
|
||||
message: 'msg',
|
||||
),
|
||||
];
|
||||
final bufferedMessages = [
|
||||
IRCMessage(
|
||||
raw: '',
|
||||
command: Command.privateMessage,
|
||||
tags: {},
|
||||
message: 'buffered',
|
||||
),
|
||||
];
|
||||
|
||||
final clearAllMsg = IRCMessage(
|
||||
raw: '',
|
||||
command: Command.clearChat,
|
||||
tags: {},
|
||||
);
|
||||
|
||||
IRCMessage.clearChat(
|
||||
messages: messages,
|
||||
bufferedMessages: bufferedMessages,
|
||||
ircMessage: clearAllMsg,
|
||||
);
|
||||
|
||||
expect(bufferedMessages, isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
group('IRCMessage.clearMessage — buffered messages', () {
|
||||
test('marks buffered message by ID', () {
|
||||
final messages = <IRCMessage>[];
|
||||
final bufferedMessages = [
|
||||
IRCMessage(
|
||||
raw: '',
|
||||
command: Command.privateMessage,
|
||||
tags: {'id': 'buf-target'},
|
||||
user: 'someuser',
|
||||
message: 'To be deleted',
|
||||
),
|
||||
];
|
||||
|
||||
final clearMsg = IRCMessage(
|
||||
raw: '',
|
||||
command: Command.clearMessage,
|
||||
tags: {'target-msg-id': 'buf-target'},
|
||||
);
|
||||
|
||||
IRCMessage.clearMessage(
|
||||
messages: messages,
|
||||
bufferedMessages: bufferedMessages,
|
||||
ircMessage: clearMsg,
|
||||
);
|
||||
|
||||
expect(bufferedMessages.first.command, Command.clearMessage);
|
||||
});
|
||||
});
|
||||
|
||||
group('IRCMessage edge cases', () {
|
||||
test('parses message with empty body', () {
|
||||
final msg = IRCMessage.fromString(messageEmptyBody);
|
||||
|
||||
expect(msg.command, Command.privateMessage);
|
||||
// Empty trailing content after the colon
|
||||
expect(msg.message, isEmpty);
|
||||
});
|
||||
|
||||
test('parses emote-only message', () {
|
||||
final msg = IRCMessage.fromString(messageOnlyEmotes);
|
||||
|
||||
expect(msg.command, Command.privateMessage);
|
||||
expect(msg.message, 'Kappa Kappa');
|
||||
expect(msg.localEmotes, isNotNull);
|
||||
expect(msg.localEmotes!.containsKey('Kappa'), isTrue);
|
||||
});
|
||||
});
|
||||
|
||||
group('USERSTATE', () {
|
||||
test('creates default USERSTATE', () {
|
||||
const userstate = USERSTATE();
|
||||
|
|
|
|||
41
test/models/shared_chat_session_test.dart
Normal file
41
test/models/shared_chat_session_test.dart
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:frosty/models/shared_chat_session.dart';
|
||||
|
||||
import '../fixtures/api_responses.dart';
|
||||
|
||||
void main() {
|
||||
group('SharedChatSession', () {
|
||||
test('fromJson deserializes all fields', () {
|
||||
final session = SharedChatSession.fromJson(
|
||||
(twitchSharedChatSessionResponse['data']! as List).first
|
||||
as Map<String, dynamic>,
|
||||
);
|
||||
|
||||
expect(session.sessionId, 'session_123');
|
||||
expect(session.hostBroadcasterId, '12345');
|
||||
expect(session.participants.length, 2);
|
||||
expect(session.createdAt, '2024-01-01T00:00:00Z');
|
||||
expect(session.updatedAt, '2024-01-01T01:00:00Z');
|
||||
});
|
||||
});
|
||||
|
||||
group('Participant', () {
|
||||
test('fromJson deserializes broadcaster_id', () {
|
||||
final participant = Participant.fromJson(const {
|
||||
'broadcaster_id': '67890',
|
||||
});
|
||||
|
||||
expect(participant.broadcasterId, '67890');
|
||||
});
|
||||
|
||||
test('participants from shared chat session are correct', () {
|
||||
final session = SharedChatSession.fromJson(
|
||||
(twitchSharedChatSessionResponse['data']! as List).first
|
||||
as Map<String, dynamic>,
|
||||
);
|
||||
|
||||
expect(session.participants[0].broadcasterId, '12345');
|
||||
expect(session.participants[1].broadcasterId, '67890');
|
||||
});
|
||||
});
|
||||
}
|
||||
45
test/models/stream_test.dart
Normal file
45
test/models/stream_test.dart
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:frosty/models/stream.dart';
|
||||
|
||||
import '../fixtures/api_responses.dart';
|
||||
|
||||
void main() {
|
||||
group('StreamTwitch', () {
|
||||
test('fromJson deserializes snake_case fields', () {
|
||||
final stream = StreamTwitch.fromJson(
|
||||
(twitchStreamResponse['data']! as List).first as Map<String, dynamic>,
|
||||
);
|
||||
|
||||
expect(stream.userId, '12345');
|
||||
expect(stream.userLogin, 'testuser');
|
||||
expect(stream.userName, 'TestUser');
|
||||
expect(stream.gameId, '509658');
|
||||
expect(stream.gameName, 'Just Chatting');
|
||||
expect(stream.title, 'Test Stream');
|
||||
expect(stream.viewerCount, 1000);
|
||||
expect(stream.startedAt, '2024-01-01T00:00:00Z');
|
||||
expect(stream.thumbnailUrl, 'https://cdn/thumb.jpg');
|
||||
});
|
||||
});
|
||||
|
||||
group('StreamsTwitch', () {
|
||||
test('fromJson deserializes data and pagination', () {
|
||||
final streams = StreamsTwitch.fromJson(
|
||||
twitchStreamResponse as Map<String, dynamic>,
|
||||
);
|
||||
|
||||
expect(streams.data.length, 1);
|
||||
expect(streams.data.first.userName, 'TestUser');
|
||||
expect(streams.pagination['cursor'], 'next_cursor');
|
||||
});
|
||||
|
||||
test('fromJson handles empty data list', () {
|
||||
final streams = StreamsTwitch.fromJson(const {
|
||||
'data': <dynamic>[],
|
||||
'pagination': <String, String>{},
|
||||
});
|
||||
|
||||
expect(streams.data, isEmpty);
|
||||
});
|
||||
});
|
||||
}
|
||||
44
test/models/user_test.dart
Normal file
44
test/models/user_test.dart
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:frosty/models/user.dart';
|
||||
|
||||
import '../fixtures/api_responses.dart';
|
||||
|
||||
void main() {
|
||||
group('UserTwitch', () {
|
||||
test('fromJson deserializes snake_case fields', () {
|
||||
final user = UserTwitch.fromJson(
|
||||
(twitchUserResponse['data']! as List).first as Map<String, dynamic>,
|
||||
);
|
||||
|
||||
expect(user.id, '12345');
|
||||
expect(user.login, 'testuser');
|
||||
expect(user.displayName, 'TestUser');
|
||||
expect(user.profileImageUrl, 'https://cdn/profile.png');
|
||||
});
|
||||
});
|
||||
|
||||
group('UserBlockedTwitch', () {
|
||||
test('fromJson deserializes snake_case fields', () {
|
||||
final blocked = UserBlockedTwitch.fromJson(
|
||||
(twitchBlockedUsersResponse['data']! as List).first
|
||||
as Map<String, dynamic>,
|
||||
);
|
||||
|
||||
expect(blocked.userId, '99999');
|
||||
expect(blocked.userLogin, 'blockeduser');
|
||||
expect(blocked.displayName, 'BlockedUser');
|
||||
});
|
||||
|
||||
test('fromJson with different values', () {
|
||||
final blocked = UserBlockedTwitch.fromJson(const {
|
||||
'user_id': '11111',
|
||||
'user_login': 'another_blocked',
|
||||
'display_name': 'AnotherBlocked',
|
||||
});
|
||||
|
||||
expect(blocked.userId, '11111');
|
||||
expect(blocked.userLogin, 'another_blocked');
|
||||
expect(blocked.displayName, 'AnotherBlocked');
|
||||
});
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue