Import IKAR messenger client and server

This commit is contained in:
sevenhill
2026-10-07 22:06:07 +03:00
commit 69ea5091eb
1951 changed files with 424546 additions and 0 deletions
+566
View File
@@ -0,0 +1,566 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:ikar/frontend/screens/chats/chat_admin/admin_section.dart';
import 'package:ikar/frontend/screens/chats/chat_admin/chat_admin_state.dart';
import 'package:ikar/frontend/screens/chats/chat_admin/admins_screen.dart';
import 'package:ikar/frontend/screens/chats/chat_admin/channel_followers_screen.dart';
import 'package:ikar/frontend/screens/chats/chat_admin/channel_type_link_screen.dart';
import 'package:ikar/frontend/screens/chats/chat_admin/chat_settings_screen.dart';
import 'package:ikar/frontend/screens/chats/chat_admin/member_actions.dart';
import 'package:ikar/frontend/screens/chats/chat_admin/member_permissions_screen.dart';
import 'package:ikar/frontend/screens/chats/chat_admin/ownership_transfer.dart';
import 'package:ikar/frontend/widgets/settings_card.dart';
import 'package:ikar/l10n/app_localizations.dart';
import 'package:ikar/main.dart' show api;
import 'package:ikar/models/chat_info.dart';
import 'package:material_symbols_icons/symbols.dart';
const _ownerId = 11;
const _adminId = 22;
ChatAdminState _state({
required int myId,
int adminPermissions = 1828,
String type = 'CHANNEL',
Map<String, bool> options = const {'JOIN_REQUEST': false},
}) => ChatAdminState(
chatId: -1000,
myId: myId,
name: 'Synthetic channel',
imageUrl: '',
info: ChatInfo.fromMap({
'id': -1000,
'type': type,
'owner': _ownerId,
'access': 'PRIVATE',
'link': 'https://example.test/join/synthetic',
'participantsCount': 3,
'adminParticipants': {
'$_ownerId': {'id': _ownerId, 'permissions': 4095},
'$_adminId': {'id': _adminId, 'permissions': adminPermissions},
},
'options': options,
}),
);
Future<void> _pump(WidgetTester tester, Widget home) async {
tester.view.physicalSize = const Size(1080, 2400);
tester.view.devicePixelRatio = 2.5;
addTearDown(tester.view.reset);
await tester.pumpWidget(
MaterialApp(
locale: const Locale('ru'),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: home,
),
);
await tester.pump();
}
void main() {
setUpAll(() => api.state);
testWidgets('the info section shows both counters and statistics', (
tester,
) async {
await _pump(
tester,
Scaffold(
body: AdminSection(
state: _state(myId: _ownerId),
onLeave: () {},
),
),
);
expect(find.widgetWithText(SettingsNavTile, 'Администраторы'), findsOne);
expect(find.widgetWithText(SettingsNavTile, '2'), findsOne);
expect(find.widgetWithText(SettingsNavTile, 'Подписчики'), findsOne);
expect(find.widgetWithText(SettingsNavTile, '3'), findsOne);
expect(find.text('Статистика канала'), findsOne);
await tester.tap(find.text('Статистика канала'));
await tester.pump();
expect(find.text('Статистика канала пока недоступна'), findsOne);
await tester.pump(const Duration(seconds: 3));
});
testWidgets('the owner sees every admin and can add more', (tester) async {
await _pump(tester, AdminsScreen(state: _state(myId: _ownerId)));
expect(find.text('Добавить администратора'), findsOne);
expect(find.text('Владелец (вы)'), findsOne);
expect(find.text('Админ'), findsOne);
});
testWidgets('an admin without the right cannot add admins', (tester) async {
await _pump(
tester,
AdminsScreen(state: _state(myId: _adminId, adminPermissions: 16)),
);
expect(find.text('Добавить администратора'), findsNothing);
expect(find.text('Владелец'), findsOne);
expect(find.text('Админ (вы)'), findsOne);
});
testWidgets('the invite link screen offers every link action', (
tester,
) async {
await _pump(tester, ChannelTypeLinkScreen(state: _state(myId: _ownerId)));
expect(find.text('example.test/join/synthetic'), findsOne);
expect(find.text('Отправить в ИКАР'), findsOne);
expect(find.text('Показать QR-код'), findsOne);
expect(find.text('Заявки на вступление'), findsOne);
expect(find.byIcon(Symbols.more_horiz), findsOne);
await tester.tap(find.byIcon(Symbols.more_horiz));
await tester.pumpAndSettle();
expect(find.text('Перевыпустить ссылку'), findsOne);
});
testWidgets('an admin without follower rights only shares the link', (
tester,
) async {
await _pump(
tester,
ChannelTypeLinkScreen(
state: _state(myId: _adminId, adminPermissions: 32),
),
);
expect(find.text('Отправить в ИКАР'), findsOne);
expect(find.text('Заявки на вступление'), findsNothing);
expect(find.byIcon(Symbols.more_horiz), findsNothing);
});
testWidgets('the followers screen offers adding and inviting', (
tester,
) async {
await _pump(tester, ChannelFollowersScreen(state: _state(myId: _ownerId)));
await tester.pump();
expect(find.text('Найти по имени'), findsOne);
expect(find.text('Добавить подписчиков'), findsOne);
expect(find.text('Пригласить по ссылке'), findsOne);
});
testWidgets('followers without the right are read-only', (tester) async {
await _pump(
tester,
ChannelFollowersScreen(
state: _state(myId: _adminId, adminPermissions: 32),
),
);
await tester.pump();
expect(find.text('Добавить подписчиков'), findsNothing);
expect(find.text('Пригласить по ссылке'), findsOne);
});
testWidgets('a group shows admins and settings in one card', (tester) async {
await _pump(
tester,
Scaffold(
body: AdminSection(
state: _state(myId: _ownerId, type: 'CHAT'),
onLeave: () {},
),
),
);
expect(find.widgetWithText(SettingsNavTile, 'Администраторы'), findsOne);
expect(find.widgetWithText(SettingsNavTile, 'Настройки группы'), findsOne);
expect(find.text('Подписчики'), findsNothing);
expect(find.text('Статистика канала'), findsNothing);
});
testWidgets('the owner sees every group setting', (tester) async {
await _pump(
tester,
ChatSettingsScreen(
state: _state(myId: _ownerId, type: 'CHAT'),
onLeave: () {},
),
);
await tester.pump();
expect(find.text('Synthetic channel'), findsOne);
expect(find.text('НАЗВАНИЕ ЧАТА'), findsOne);
expect(find.text('ОПИСАНИЕ ЧАТА'), findsOne);
expect(find.text('Реакции'), findsOne);
expect(find.text('Передать права владельца'), findsOne);
expect(find.text('Покинуть чат'), findsOne);
expect(find.text('Разрешения участников'), findsOne);
});
testWidgets('saving waits for a real change', (tester) async {
await _pump(
tester,
ChatSettingsScreen(
state: _state(myId: _ownerId, type: 'CHAT'),
onLeave: () {},
),
);
IconButton save() => tester.widget<IconButton>(
find.widgetWithIcon(IconButton, Symbols.check),
);
expect(save().onPressed, isNull);
await tester.enterText(find.byType(TextField).first, 'Synthetic renamed');
await tester.pump();
expect(save().onPressed, isNotNull);
await tester.enterText(find.byType(TextField).first, ' ');
await tester.pump();
expect(save().onPressed, isNull);
});
testWidgets('a member with edit rights only edits the basics', (
tester,
) async {
await _pump(
tester,
ChatSettingsScreen(
state: _state(
myId: 99,
type: 'CHAT',
options: {'ONLY_OWNER_CAN_CHANGE_ICON_TITLE': false},
),
onLeave: () {},
),
);
expect(find.text('Реакции'), findsNothing);
expect(find.text('Передать права владельца'), findsNothing);
expect(find.text('Разрешения участников'), findsNothing);
expect(find.text('Покинуть чат'), findsOne);
});
testWidgets('member permissions mirror the chat options', (tester) async {
await _pump(
tester,
MemberPermissionsScreen(
state: _state(
myId: _ownerId,
type: 'CHAT',
options: {
'ONLY_OWNER_CAN_CHANGE_ICON_TITLE': true,
'ONLY_ADMIN_CAN_ADD_MEMBER': false,
'ALL_CAN_PIN_MESSAGE': true,
'MEMBERS_CAN_SEE_PRIVATE_LINK': false,
'ONLY_ADMIN_CAN_CALL': false,
},
),
),
);
bool value(String label) => tester
.widget<SettingsToggleTile>(
find.widgetWithText(SettingsToggleTile, label),
)
.value;
expect(value('Изменять название, фото и описание чата'), isFalse);
expect(value('Добавлять участников'), isTrue);
expect(value('Закреплять сообщения'), isTrue);
expect(value('Приглашать по ссылке'), isFalse);
expect(value('Звонить в чате'), isTrue);
});
testWidgets('the owner must hand over the chat before leaving', (
tester,
) async {
final owner = _state(myId: _ownerId, type: 'CHAT');
late BuildContext context;
await _pump(
tester,
Builder(
builder: (built) {
context = built;
return const SizedBox();
},
),
);
final result = transferBeforeLeaving(context, owner);
await tester.pumpAndSettle();
expect(find.text('Вы владелец'), findsOne);
expect(
find.text(
'Чтобы покинуть группу, сначала передайте права владельца '
'другому участнику.',
),
findsOne,
);
await tester.tap(find.text('Отмена'));
await tester.pumpAndSettle();
expect(await result, isFalse);
});
testWidgets('anyone else leaves without the ownership step', (tester) async {
late BuildContext context;
await _pump(
tester,
Builder(
builder: (built) {
context = built;
return const SizedBox();
},
),
);
expect(
await transferBeforeLeaving(
context,
_state(myId: _adminId, type: 'CHAT'),
),
isTrue,
);
});
testWidgets('a sole owner leaves without handing anything over', (
tester,
) async {
late BuildContext context;
await _pump(
tester,
Builder(
builder: (built) {
context = built;
return const SizedBox();
},
),
);
final alone = ChatAdminState(
chatId: -2000,
myId: _ownerId,
name: 'Synthetic alone',
imageUrl: '',
info: ChatInfo.fromMap({
'id': -2000,
'type': 'CHAT',
'owner': _ownerId,
'participantsCount': 1,
}),
);
expect(await transferBeforeLeaving(context, alone), isTrue);
expect(find.text('Вы владелец'), findsNothing);
});
testWidgets('group restrictions show the server values', (tester) async {
await _pump(
tester,
ChatSettingsScreen(
state: _state(
myId: _ownerId,
type: 'CHAT',
options: {
'DISABLE_FORWARD': true,
'MESSAGE_COPY_NOT_ALLOWED': false,
'CONFIRM_BEFORE_SEND': true,
},
),
onLeave: () {},
),
);
bool value(String label) => tester
.widget<SettingsToggleTile>(
find.widgetWithText(SettingsToggleTile, label),
)
.value;
expect(find.text('ОГРАНИЧЕНИЯ'), findsOne);
expect(value('Запретить пересылку'), isTrue);
expect(value('Запретить копирование'), isFalse);
expect(value('Подтверждать отправку'), isTrue);
});
testWidgets('members do not see group restrictions', (tester) async {
await _pump(
tester,
ChatSettingsScreen(
state: _state(
myId: 99,
type: 'CHAT',
options: {'ONLY_OWNER_CAN_CHANGE_ICON_TITLE': false},
),
onLeave: () {},
),
);
expect(find.text('ОГРАНИЧЕНИЯ'), findsNothing);
expect(find.text('Запретить пересылку'), findsNothing);
});
testWidgets('a new channel greets with its type and link', (tester) async {
await _pump(
tester,
ChannelTypeLinkScreen(state: _state(myId: _ownerId), justCreated: true),
);
expect(find.text('Приватный канал создан'), findsOne);
expect(find.text('Публичный для бизнеса'), findsOne);
expect(find.text('Ссылка-приглашение в ваш канал'), findsOne);
expect(find.text('Копировать ссылку'), findsOne);
expect(find.text('Заявки на вступление'), findsNothing);
await tester.tap(find.text('Публичный'));
await tester.pump();
await tester.pump(const Duration(milliseconds: 200));
expect(find.text('Публичные каналы пока недоступны'), findsOne);
await tester.pump(const Duration(seconds: 3));
await tester.pumpAndSettle();
});
testWidgets('the channel owner sees every channel setting', (tester) async {
var deleted = false;
await _pump(
tester,
ChatSettingsScreen(
state: _state(myId: _ownerId),
onLeave: () {},
onClearHistory: () {},
onDelete: () => deleted = true,
),
);
await tester.pump();
expect(find.text('Настройки канала'), findsOne);
expect(find.text('НАЗВАНИЕ КАНАЛА'), findsOne);
expect(find.text('Подтверждать публикацию'), findsOne);
expect(
find.widgetWithText(SettingsNavTile, 'Тип канала и ссылка'),
findsOne,
);
expect(find.widgetWithText(SettingsNavTile, 'Приватный'), findsOne);
expect(find.text('Комментарии'), findsOne);
expect(find.text('Передать права владельца'), findsOne);
expect(find.text('Очистить историю'), findsOne);
expect(find.text('Покинуть канал'), findsOne);
expect(find.text('Разрешения участников'), findsNothing);
await tester.scrollUntilVisible(
find.text('Удалить канал'),
200,
scrollable: find
.descendant(
of: find.byType(ListView),
matching: find.byType(Scrollable),
)
.first,
);
await tester.tap(find.text('Удалить канал'));
await tester.pumpAndSettle();
expect(find.text('Удалить канал?'), findsOne);
expect(find.text('Передать права и выйти'), findsOne);
await tester.tap(find.text('Удалить канал').last);
await tester.pumpAndSettle();
expect(deleted, isTrue);
});
testWidgets('comments ask before they are switched on', (tester) async {
await _pump(
tester,
ChatSettingsScreen(
state: _state(myId: _ownerId),
onLeave: () {},
),
);
await tester.tap(find.widgetWithText(SettingsToggleTile, 'Комментарии'));
await tester.pumpAndSettle();
expect(find.text('Комментарии — часть вашего канала'), findsOne);
await tester.tap(find.text('Не включать'));
await tester.pumpAndSettle();
expect(find.text('Комментарии — часть вашего канала'), findsNothing);
expect(
tester
.widget<SettingsToggleTile>(
find.widgetWithText(SettingsToggleTile, 'Комментарии'),
)
.value,
isFalse,
);
});
testWidgets('a channel admin opens settings from the info section', (
tester,
) async {
await _pump(
tester,
Scaffold(
body: AdminSection(
state: _state(myId: _ownerId),
onLeave: () {},
),
),
);
expect(find.widgetWithText(SettingsNavTile, 'Настройки канала'), findsOne);
});
test('member actions follow the admin rights', () {
final owner = _state(myId: _ownerId, type: 'CHAT');
expect(memberActionsFor(owner, userId: 44, isContact: false), [
MemberAction.addContact,
MemberAction.appointAdmin,
MemberAction.remove,
]);
expect(memberActionsFor(owner, userId: 44, isContact: true), [
MemberAction.appointAdmin,
MemberAction.remove,
]);
expect(memberActionsFor(owner, userId: _adminId, isContact: true), isEmpty);
expect(
memberActionsFor(owner, userId: _ownerId, isContact: false),
isEmpty,
);
final plainMember = _state(myId: 99, type: 'CHAT');
expect(memberActionsFor(plainMember, userId: 44, isContact: false), [
MemberAction.addContact,
]);
final followerManager = _state(myId: _adminId, adminPermissions: 2 | 128);
expect(memberActionsFor(followerManager, userId: 44, isContact: true), [
MemberAction.remove,
]);
});
testWidgets('a group member menu asks before removing', (tester) async {
await _pump(
tester,
Scaffold(
body: Center(
child: MemberActionsButton(
state: _state(myId: _ownerId, type: 'CHAT'),
userId: 44,
name: 'Synthetic member',
isContact: false,
onDone: (_) {},
),
),
),
);
await tester.tap(find.byIcon(Symbols.more_horiz));
await tester.pumpAndSettle();
expect(find.text('Добавить в контакты'), findsOne);
expect(find.text('Назначить администратором'), findsOne);
await tester.tap(find.text('Удалить'));
await tester.pumpAndSettle();
expect(find.text('Удалить участника'), findsOne);
expect(find.text('Synthetic member будет удалён из группы.'), findsOne);
await tester.tap(find.text('Отмена'));
await tester.pumpAndSettle();
});
}