567 lines
18 KiB
Dart
567 lines
18 KiB
Dart
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();
|
|
});
|
|
}
|