MM-30430 Detox/E2E: MM-T3189, MM-T3231, MM-T3233 (#5293)
This commit is contained in:
parent
9c043f1b9f
commit
7c36bfb6cc
13 changed files with 156 additions and 48 deletions
|
|
@ -481,6 +481,7 @@ export default class PostList extends PureComponent {
|
|||
initialIndex,
|
||||
deepLinkURL,
|
||||
showMoreMessagesButton,
|
||||
testID,
|
||||
} = this.props;
|
||||
|
||||
const refreshControl = (
|
||||
|
|
@ -526,6 +527,7 @@ export default class PostList extends PureComponent {
|
|||
minimumViewTime: 100,
|
||||
}}
|
||||
onViewableItemsChanged={this.onViewableItemsChanged}
|
||||
testID={testID}
|
||||
/>
|
||||
{showMoreMessagesButton &&
|
||||
<MoreMessagesButton
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
"binaryPath": "../ios/Build/Products/Debug-iphonesimulator/Mattermost.app",
|
||||
"type": "ios.simulator",
|
||||
"device": {
|
||||
"type": "iPhone 11"
|
||||
"type": "iPhone 12"
|
||||
}
|
||||
},
|
||||
"ios.sim.release": {
|
||||
|
|
@ -14,13 +14,13 @@
|
|||
"binaryPath": "../ios/Build/Products/Release-iphonesimulator/Mattermost.app",
|
||||
"build": "cd ../fastlane && NODE_ENV=production bundle exec fastlane ios simulator && cd ../detox",
|
||||
"device": {
|
||||
"type": "iPhone 11"
|
||||
"type": "iPhone 12"
|
||||
}
|
||||
},
|
||||
"android.emu.debug": {
|
||||
"type": "android.emulator",
|
||||
"binaryPath": "../android/app/build/outputs/apk/debug/app-debug.apk",
|
||||
"build": "cd .. && ./node_modules/.bin/jetify && cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug && cd ../detox",
|
||||
"build": "cd .. && ./node_modules/.bin/jetify && cd android && ./gradlew clean assembleDebug assembleAndroidTest -DtestBuildType=debug && cd ../detox",
|
||||
"device": {
|
||||
"avdName": "detox_emu_api_30"
|
||||
}
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
"android.emu.release": {
|
||||
"type": "android.emulator",
|
||||
"binaryPath": "../android/app/build/outputs/apk/release/app-release.apk",
|
||||
"build": "cd .. && ./node_modules/.bin/jetify && cd android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release && cd ../detox",
|
||||
"build": "cd .. && ./node_modules/.bin/jetify && cd android && ./gradlew clean assembleRelease assembleAndroidTest -DtestBuildType=release && cd ../detox",
|
||||
"device": {
|
||||
"avdName": "detox_emu_api_30"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ class Alert {
|
|||
removeMembersTitle = isAndroid() ? element(by.text('Remove Members')) : element(by.label('Remove Members')).atIndex(0);
|
||||
|
||||
// alert buttons
|
||||
cancelButton = isAndroid() ? element(by.text('CANCEL')) : element(by.label('Cancel')).atIndex(0);
|
||||
cancelButton = isAndroid() ? element(by.text('CANCEL')) : element(by.label('Cancel')).atIndex(1);
|
||||
deleteButton = isAndroid() ? element(by.text('DELETE')) : element(by.label('Delete')).atIndex(0);
|
||||
joinButton = isAndroid() ? element(by.text('JOIN')) : element(by.label('Join')).atIndex(0);
|
||||
noButton = isAndroid() ? element(by.text('NO')) : element(by.label('No')).atIndex(0);
|
||||
noButton = isAndroid() ? element(by.text('NO')) : element(by.label('No')).atIndex(1);
|
||||
yesButton = isAndroid() ? element(by.text('YES')) : element(by.label('Yes')).atIndex(0);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class PostOptions {
|
|||
await expect(this.postOptions).not.toBeVisible();
|
||||
}
|
||||
|
||||
deletePost = async (confirm = true) => {
|
||||
deletePost = async ({confirm = true} = {}) => {
|
||||
// # Swipe up panel on Android
|
||||
if (isAndroid()) {
|
||||
await this.slideUpPanel.swipe('up');
|
||||
|
|
@ -60,13 +60,18 @@ class PostOptions {
|
|||
deleteButton,
|
||||
} = Alert;
|
||||
await expect(deletePostTitle).toBeVisible();
|
||||
await expect(cancelButton).toBeVisible();
|
||||
await expect(deleteButton).toBeVisible();
|
||||
if (confirm) {
|
||||
deleteButton.tap();
|
||||
await wait(timeouts.ONE_SEC);
|
||||
await expect(this.postOptions).not.toBeVisible();
|
||||
} else {
|
||||
cancelButton.tap();
|
||||
await wait(timeouts.ONE_SEC);
|
||||
await expect(this.postOptions).toBeVisible();
|
||||
await this.close();
|
||||
}
|
||||
await wait(timeouts.ONE_SEC);
|
||||
await expect(this.postOptions).not.toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ class ChannelScreen {
|
|||
testID = {
|
||||
channelScreenPrefix: 'channel.',
|
||||
channelScreen: 'channel.screen',
|
||||
channelPostList: 'channel.post_list',
|
||||
mainSidebarDrawerButton: 'main_sidebar_drawer.button',
|
||||
mainSidebarDrawerButtonBadge: 'main_sidebar_drawer.button.badge',
|
||||
mainSidebarDrawerButtonBadgeUnreadCount: 'main_sidebar_drawer.button.badge.unread_count',
|
||||
|
|
@ -37,6 +38,7 @@ class ChannelScreen {
|
|||
}
|
||||
|
||||
channelScreen = element(by.id(this.testID.channelScreen));
|
||||
channelPostList = element(by.id(this.testID.channelPostList));
|
||||
mainSidebarDrawerButton = element(by.id(this.testID.mainSidebarDrawerButton));
|
||||
mainSidebarDrawerButtonBadge = element(by.id(this.testID.mainSidebarDrawerButtonBadge));
|
||||
mainSidebarDrawerButtonBadgeUnreadCount = element(by.id(this.testID.mainSidebarDrawerButtonBadgeUnreadCount));
|
||||
|
|
@ -131,11 +133,11 @@ class ChannelScreen {
|
|||
await this.closeMainSidebar();
|
||||
}
|
||||
|
||||
deletePost = async (postId, text, confirm = true) => {
|
||||
deletePost = async (postId, text, {confirm = true} = {}) => {
|
||||
await this.openPostOptionsFor(postId, text);
|
||||
|
||||
// # Delete post
|
||||
await PostOptions.deletePost(confirm);
|
||||
await PostOptions.deletePost({confirm});
|
||||
await this.toBeVisible();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ class ChannelAddMembersScreen {
|
|||
await expect(this.channelAddMembersScreen).not.toBeVisible();
|
||||
}
|
||||
|
||||
removeMembers = async (displayUsernameList, confirm = true) => {
|
||||
removeMembers = async (displayUsernameList, {confirm = true} = {}) => {
|
||||
displayUsernameList.forEach(async (displayUsername) => {
|
||||
await this.getUserByDisplayUsername(`@${displayUsername}`).tap();
|
||||
});
|
||||
|
|
@ -84,13 +84,17 @@ class ChannelAddMembersScreen {
|
|||
yesButton,
|
||||
} = Alert;
|
||||
await expect(removeMembersTitle).toBeVisible();
|
||||
await expect(noButton).toBeVisible();
|
||||
await expect(yesButton).toBeVisible();
|
||||
if (confirm) {
|
||||
yesButton.tap();
|
||||
await wait(timeouts.ONE_SEC);
|
||||
await expect(this.channelAddMembersScreen).not.toBeVisible();
|
||||
} else {
|
||||
noButton.tap();
|
||||
await wait(timeouts.ONE_SEC);
|
||||
await expect(this.channelAddMembersScreen).toBeVisible();
|
||||
}
|
||||
await wait(timeouts.ONE_SEC);
|
||||
await expect(this.channelAddMembersScreen).not.toBeVisible();
|
||||
}
|
||||
|
||||
hasUserDisplayUsernameAtIndex = async (index, displayUsername) => {
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ class ChannelInfoScreen {
|
|||
await expect(this.channelInfoScreen).not.toBeVisible();
|
||||
}
|
||||
|
||||
archiveChannel = async (confirm = true) => {
|
||||
archiveChannel = async ({confirm = true} = {}) => {
|
||||
await this.channelInfoScrollView.scrollTo('bottom');
|
||||
await this.archiveAction.tap();
|
||||
const {
|
||||
|
|
@ -87,13 +87,17 @@ class ChannelInfoScreen {
|
|||
yesButton,
|
||||
} = Alert;
|
||||
await expect(archivePublicChannelTitle).toBeVisible();
|
||||
await expect(noButton).toBeVisible();
|
||||
await expect(yesButton).toBeVisible();
|
||||
if (confirm) {
|
||||
yesButton.tap();
|
||||
await wait(timeouts.ONE_SEC);
|
||||
await expect(this.channelInfoScreen).not.toBeVisible();
|
||||
} else {
|
||||
noButton.tap();
|
||||
await wait(timeouts.ONE_SEC);
|
||||
await expect(this.channelInfoScreen).toBeVisible();
|
||||
}
|
||||
await wait(timeouts.ONE_SEC);
|
||||
await expect(this.channelInfoScreen).not.toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ class ChannelMembersScreen {
|
|||
await expect(this.channelMembersScreen).not.toBeVisible();
|
||||
}
|
||||
|
||||
removeMembers = async (displayUsernameList, confirm = true) => {
|
||||
removeMembers = async (displayUsernameList, {confirm = true} = {}) => {
|
||||
displayUsernameList.forEach(async (displayUsername) => {
|
||||
await this.getUserByDisplayUsername(`@${displayUsername}`).tap();
|
||||
});
|
||||
|
|
@ -84,13 +84,17 @@ class ChannelMembersScreen {
|
|||
yesButton,
|
||||
} = Alert;
|
||||
await expect(removeMembersTitle).toBeVisible();
|
||||
await expect(noButton).toBeVisible();
|
||||
await expect(yesButton).toBeVisible();
|
||||
if (confirm) {
|
||||
yesButton.tap();
|
||||
await wait(timeouts.ONE_SEC);
|
||||
await expect(this.channelMembersScreen).not.toBeVisible();
|
||||
} else {
|
||||
noButton.tap();
|
||||
await wait(timeouts.ONE_SEC);
|
||||
await expect(this.channelMembersScreen).toBeVisible();
|
||||
}
|
||||
await wait(timeouts.ONE_SEC);
|
||||
await expect(this.channelMembersScreen).not.toBeVisible();
|
||||
}
|
||||
|
||||
hasUserDisplayUsernameAtIndex = async (index, displayUsername) => {
|
||||
|
|
|
|||
|
|
@ -8,10 +8,12 @@ class PermalinkScreen {
|
|||
testID = {
|
||||
permalinkScreenPrefix: 'permalink.',
|
||||
permalinkScreen: 'permalink.screen',
|
||||
permalinkPostList: 'permalink.post_list',
|
||||
searchJump: 'permalink.search.jump',
|
||||
}
|
||||
|
||||
permalinkScreen = element(by.id(this.testID.permalinkScreen));
|
||||
permalinkPostList = element(by.id(this.testID.permalinkPostList));
|
||||
searchJump = element(by.id(this.testID.searchJump));
|
||||
|
||||
postList = new PostList(this.testID.permalinkScreenPrefix);
|
||||
|
|
|
|||
|
|
@ -18,10 +18,12 @@ class ThreadScreen {
|
|||
testID = {
|
||||
threadScreenPrefix: 'thread.',
|
||||
threadScreen: 'thread.screen',
|
||||
threadPostList: 'thread.post_list',
|
||||
backButton: 'screen.back.button',
|
||||
}
|
||||
|
||||
threadScreen = element(by.id(this.testID.threadScreen));
|
||||
threadPostList = element(by.id(this.testID.threadPostList));
|
||||
backButton = element(by.id(this.testID.backButton));
|
||||
|
||||
// convenience props
|
||||
|
|
@ -72,11 +74,11 @@ class ThreadScreen {
|
|||
await expect(this.threadScreen).not.toBeVisible();
|
||||
}
|
||||
|
||||
deletePost = async (postId, text, confirm = true, isParentPost = true) => {
|
||||
deletePost = async (postId, text, {confirm = true, isParentPost = true} = {}) => {
|
||||
await this.openPostOptionsFor(postId, text);
|
||||
|
||||
// # Delete post
|
||||
await PostOptions.deletePost(confirm);
|
||||
await PostOptions.deletePost({confirm});
|
||||
if (isParentPost) {
|
||||
await expect(this.threadScreen).not.toBeVisible();
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,12 @@ import {
|
|||
} from '@support/server_api';
|
||||
|
||||
describe('Message Deletion', () => {
|
||||
const {
|
||||
channelScreen,
|
||||
deletePost,
|
||||
openReplyThreadFor,
|
||||
postMessage,
|
||||
} = ChannelScreen;
|
||||
let testChannel;
|
||||
|
||||
beforeAll(async () => {
|
||||
|
|
@ -34,13 +40,21 @@ describe('Message Deletion', () => {
|
|||
await ChannelScreen.logout();
|
||||
});
|
||||
|
||||
it('MM-T3232 should remove message from channel when deleted', async () => {
|
||||
const {
|
||||
channelScreen,
|
||||
deletePost,
|
||||
postMessage,
|
||||
} = ChannelScreen;
|
||||
it('MM-T3231 should be able to delete message and cancel', async () => {
|
||||
// # Post a message
|
||||
const message = Date.now().toString();
|
||||
await postMessage(message);
|
||||
|
||||
// # Delete post and cancel
|
||||
const {post} = await Post.apiGetLastPostInChannel(testChannel.id);
|
||||
await deletePost(post.id, message, {confirm: false});
|
||||
|
||||
// * Verify post is not deleted
|
||||
await expect(channelScreen).toBeVisible();
|
||||
await expect(element(by.text(message))).toBeVisible();
|
||||
});
|
||||
|
||||
it('MM-T3232 should remove message from channel when deleted', async () => {
|
||||
// # Post a message
|
||||
const message = Date.now().toString();
|
||||
await postMessage(message);
|
||||
|
|
@ -54,16 +68,36 @@ describe('Message Deletion', () => {
|
|||
await expect(element(by.text(message))).not.toBeVisible();
|
||||
});
|
||||
|
||||
it('MM-T3234 should be able to delete parent post from reply thread view', async () => {
|
||||
const {
|
||||
channelScreen,
|
||||
openReplyThreadFor,
|
||||
postMessage,
|
||||
} = ChannelScreen;
|
||||
const {
|
||||
deletePost,
|
||||
} = ThreadScreen;
|
||||
it('MM-T3233 should be able to delete reply post from reply thread view', async () => {
|
||||
// # Post a message
|
||||
const parentMessage = Date.now().toString();
|
||||
await postMessage(parentMessage);
|
||||
|
||||
// # Open reply thread for post
|
||||
const {post} = await Post.apiGetLastPostInChannel(testChannel.id);
|
||||
await openReplyThreadFor(post.id, parentMessage);
|
||||
|
||||
// # Post a reply message
|
||||
const replyMessage = `reply ${parentMessage}`;
|
||||
await ThreadScreen.postMessage(replyMessage);
|
||||
|
||||
// # Delete reply post from reply thread
|
||||
const {post: replyPost} = await Post.apiGetLastPostInChannel(testChannel.id);
|
||||
await ThreadScreen.deletePost(replyPost.id, replyMessage, {isParentPost: false});
|
||||
|
||||
// * Verify reply post is deleted but parent post is still visible in reply thread
|
||||
await ThreadScreen.toBeVisible();
|
||||
await expect(element(by.text(replyMessage))).not.toBeVisible();
|
||||
await expect(element(by.text(parentMessage))).toBeVisible();
|
||||
|
||||
// * Verify reply post is deleted but parent post is still visible in main thread
|
||||
await ThreadScreen.back();
|
||||
await expect(channelScreen).toBeVisible();
|
||||
await expect(element(by.text(replyMessage))).not.toBeVisible();
|
||||
await expect(element(by.text(parentMessage))).toBeVisible();
|
||||
});
|
||||
|
||||
it('MM-T3234 should be able to delete parent post from reply thread view', async () => {
|
||||
// # Post a message
|
||||
const message = Date.now().toString();
|
||||
await postMessage(message);
|
||||
|
|
@ -73,7 +107,7 @@ describe('Message Deletion', () => {
|
|||
await openReplyThreadFor(post.id, message);
|
||||
|
||||
// # Delete parent post from reply thread
|
||||
await deletePost(post.id, message);
|
||||
await ThreadScreen.deletePost(post.id, message);
|
||||
|
||||
// * Verify post is deleted
|
||||
await expect(channelScreen).toBeVisible();
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
// - Use element testID when selecting an element. Create one if none.
|
||||
// *******************************************************************
|
||||
|
||||
import moment from 'moment-timezone';
|
||||
|
||||
import {
|
||||
ChannelScreen,
|
||||
LongPostScreen,
|
||||
|
|
@ -16,22 +18,25 @@ import {
|
|||
Post,
|
||||
Setup,
|
||||
} from '@support/server_api';
|
||||
import {isAndroid} from '@support/utils';
|
||||
|
||||
describe('Message Posting', () => {
|
||||
const longMessage = 'The quick brown fox jumps over the lazy dog.'.repeat(30);
|
||||
const {
|
||||
getLongPostItem,
|
||||
getPostListPostItem,
|
||||
goToChannel,
|
||||
postMessage,
|
||||
} = ChannelScreen;
|
||||
let testChannel;
|
||||
let townSquareChannel;
|
||||
|
||||
beforeAll(async () => {
|
||||
const {team, user} = await Setup.apiInit();
|
||||
|
||||
const {channel} = await Channel.apiGetChannelByName(team.name, 'town-square');
|
||||
const {channel, team, user} = await Setup.apiInit();
|
||||
testChannel = channel;
|
||||
|
||||
({channel: townSquareChannel} = await Channel.apiGetChannelByName(team.name, 'town-square'));
|
||||
|
||||
// # Open channel screen
|
||||
await ChannelScreen.open(user);
|
||||
});
|
||||
|
|
@ -45,7 +50,7 @@ describe('Message Posting', () => {
|
|||
await postMessage(longMessage, {quickReplace: true});
|
||||
|
||||
// * Verify message is posted
|
||||
const {post} = await Post.apiGetLastPostInChannel(testChannel.id);
|
||||
const {post} = await Post.apiGetLastPostInChannel(townSquareChannel.id);
|
||||
const {
|
||||
postListPostItem,
|
||||
postListPostItemShowMoreButton,
|
||||
|
|
@ -56,7 +61,7 @@ describe('Message Posting', () => {
|
|||
|
||||
it('MM-T3229 should be able to open long post via show more', async () => {
|
||||
// # Open long post via show more
|
||||
const {post} = await Post.apiGetLastPostInChannel(testChannel.id);
|
||||
const {post} = await Post.apiGetLastPostInChannel(townSquareChannel.id);
|
||||
const {postListPostItemShowMoreButton} = await getPostListPostItem(post.id, longMessage);
|
||||
await postListPostItemShowMoreButton.tap();
|
||||
|
||||
|
|
@ -74,7 +79,7 @@ describe('Message Posting', () => {
|
|||
await postMessage(message, {quickReplace: true});
|
||||
|
||||
// * Verify message is posted
|
||||
const {post} = await Post.apiGetLastPostInChannel(testChannel.id);
|
||||
const {post} = await Post.apiGetLastPostInChannel(townSquareChannel.id);
|
||||
const {postListPostItemImage} = await getPostListPostItem(post.id);
|
||||
await expect(postListPostItemImage).toBeVisible();
|
||||
});
|
||||
|
|
@ -85,8 +90,52 @@ describe('Message Posting', () => {
|
|||
await postMessage(message);
|
||||
|
||||
// * Verify message is posted
|
||||
const {post} = await Post.apiGetLastPostInChannel(testChannel.id);
|
||||
const {post} = await Post.apiGetLastPostInChannel(townSquareChannel.id);
|
||||
const {postListPostItem} = await getPostListPostItem(post.id, '🦊');
|
||||
await expect(postListPostItem).toBeVisible();
|
||||
});
|
||||
|
||||
it('MM-T3189 should be able to scroll up in channel with long history', async () => {
|
||||
// # Post messages
|
||||
await goToChannel(testChannel.display_name);
|
||||
const size = 50;
|
||||
const firstMessageDate = moment().subtract(size + 1, 'days').toDate();
|
||||
const firstMessage = `${firstMessageDate} first`;
|
||||
const firstPost = await Post.apiCreatePost({
|
||||
channelId: testChannel.id,
|
||||
message: firstMessage,
|
||||
createAt: firstMessageDate.getTime(),
|
||||
});
|
||||
[...Array(size).keys()].forEach(async (key) => {
|
||||
const messageDate = moment().subtract(key + 1, 'days').toDate();
|
||||
const message = `${messageDate}-${key}.`.repeat(10);
|
||||
await Post.apiCreatePost({
|
||||
channelId: testChannel.id,
|
||||
message,
|
||||
createAt: messageDate.getTime(),
|
||||
});
|
||||
});
|
||||
const lastMessageDate = moment().toDate();
|
||||
const lastMessage = `${lastMessageDate} last`;
|
||||
const lastPost = await Post.apiCreatePost({
|
||||
channelId: testChannel.id,
|
||||
message: lastMessage,
|
||||
createAt: lastMessageDate.getTime(),
|
||||
});
|
||||
|
||||
// Detox is having trouble scrolling
|
||||
if (isAndroid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// * Verify last message is posted
|
||||
await goToChannel(townSquareChannel.display_name);
|
||||
await goToChannel(testChannel.display_name);
|
||||
const {postListPostItem: lastPostItem} = await getPostListPostItem(lastPost.post.id, lastMessage);
|
||||
await waitFor(lastPostItem).toBeVisible().whileElement(by.id(ChannelScreen.testID.channelPostList)).scroll(1000, 'down');
|
||||
|
||||
// * Verify user can scroll up multiple times until first matching post is seen
|
||||
const {postListPostItem: firstPostItem} = await getPostListPostItem(firstPost.post.id, firstMessage);
|
||||
await waitFor(firstPostItem).toBeVisible().whileElement(by.id(ChannelScreen.testID.channelPostList)).scroll(1000, 'up');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -651,10 +651,10 @@ EXTERNAL SOURCES:
|
|||
SPEC CHECKSUMS:
|
||||
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
|
||||
BVLinearGradient: e3aad03778a456d77928f594a649e96995f1c872
|
||||
DoubleConversion: cf9b38bf0b2d048436d9a82ad2abe1404f11e7de
|
||||
DoubleConversion: cde416483dac037923206447da6e1454df403714
|
||||
FBLazyVector: 49cbe4b43e445b06bf29199b6ad2057649e4c8f5
|
||||
FBReactNativeSpec: a804c9d6c798f94831713302354003ee54ea18cb
|
||||
glog: 73c2498ac6884b13ede40eda8228cb1eee9d9d62
|
||||
FBReactNativeSpec: 4862b23f27a9cdb8fdb1710af411fa677dcdd3cf
|
||||
glog: 40a13f7840415b9a77023fbcae0f1e6f43192af3
|
||||
jail-monkey: 80c9e34da2cd54023e5ad08bf7051ec75bd43d5b
|
||||
libwebp: 946cb3063cea9236285f7e9a8505d806d30e07f3
|
||||
MMKV: 22e5136f7d00197bc0fc9694b7f71519f0d1ca12
|
||||
|
|
|
|||
Loading…
Reference in a new issue