Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve mark-seen-logic #271

Merged
merged 13 commits into from
May 27, 2024
4 changes: 2 additions & 2 deletions .detoxrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ module.exports = {
type: 'ios.app',
binaryPath: 'ios/build/Build/Products/Debug-iphonesimulator/ylitse.app',
build:
"xcodebuild -workspace ios/ylitse.xcworkspace -configuration Debug -scheme ylitse -destination 'platform=iOS Simulator,name=iPhone 14,OS=16.4' -derivedDataPath ios/build",
"xcodebuild -workspace ios/ylitse.xcworkspace -configuration Debug -scheme ylitse -destination 'platform=iOS Simulator,name=iPhone 15,OS=17.2' -derivedDataPath ios/build",
},
'android.debug': {
type: 'android.apk',
Expand All @@ -32,7 +32,7 @@ module.exports = {
devices: {
'ios.simulator': {
type: 'ios.simulator',
device: { type: 'iPhone 14' },
device: { type: 'iPhone 15' },
},
'android.emulator': {
type: 'android.emulator',
Expand Down
171 changes: 164 additions & 7 deletions e2e/chatTest.spec.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
import { by, element, device, expect } from 'detox';
import { describe, it, beforeEach, beforeAll } from '@jest/globals';
import { describe, it, beforeEach, expect as jestExpect } from '@jest/globals';
import accountFixtures from './fixtures/accounts.json';

import {
APISignUpMentee,
APISignUpMentor,
APIGetSendInfo,
APIDeleteAccounts,
APIDeleteAccount,
APISendMessage,
waitAndTypeText,
signIn,
forceLogout,
countElements,
sleep,
scrollUpAndFindText,
} from './helpers';

describe('Chat', () => {
beforeAll(async () => {
await device.launchApp();
jest.setTimeout(200000);
});
beforeEach(async () => {
await APIDeleteAccounts();
await device.reloadReactNative();
});
afterEach(async () => {
await forceLogout();
});

it('with new mentor', async () => {
const mentee = accountFixtures.mentees[0];
Expand All @@ -32,8 +37,8 @@ describe('Chat', () => {

await signIn(mentee);

await element(by.text('Show mentor')).tap();
await element(by.text('Chat')).tap();
await element(by.id('components.mentorCard.readMore')).tap();
await element(by.id('main.mentorCardExpanded.button')).tap();

await waitAndTypeText('main.chat.input.input', message_from_mentee, true);
await element(by.id('main.chat.input.button')).tap();
Expand All @@ -59,4 +64,156 @@ describe('Chat', () => {
await expect(element(by.text(message_from_mentee))).toBeVisible();
await expect(element(by.text(message_from_mentor))).toBeVisible();
});

const sendMultiple = async (
from: string,
to: string,
headers: { Authorization: string },
content: string,
amount: number,
) => {
for (let i = 0; i < amount; i++) {
await APISendMessage({
sender_id: from,
recipient_id: to,
content: `${content} ${i}`,
headers,
});
}
};

it('marks message unseen', async () => {
const mentee = accountFixtures.mentees[0];
await APISignUpMentee(mentee);
const mentee2 = accountFixtures.mentees[1];
await APISignUpMentee(mentee2);
const mentor = accountFixtures.mentors[0];
await APISignUpMentor(mentor);

const {
sender_id: menteeId,
recipient_id: mentorId,
senderHeaders: menteeHeaders,
} = await APIGetSendInfo(mentee, mentor);
await sendMultiple(menteeId, mentorId, menteeHeaders, 'Hello', 5);

const { sender_id: mentee2Id, senderHeaders: mentee2Headers } =
await APIGetSendInfo(mentee2, mentor);
await sendMultiple(mentee2Id, mentorId, mentee2Headers, 'Hello', 10);

await signIn(mentor);
await element(by.id('tabs.chats')).tap();

const unseenDotsAmountBefore = await countElements(
by.id('main.buddyList.button.unseenDot'),
);
jestExpect(unseenDotsAmountBefore).toBe(2);

await element(by.text(mentee.displayName)).tap();
await expect(element(by.text('Hello 0'))).toBeVisible();
await expect(element(by.text('Hello 4'))).toBeVisible();

await element(by.id('chat.back.button')).tap();

const unseenDotsAmountAfter = await countElements(
by.id('main.buddyList.button.unseenDot'),
);
jestExpect(unseenDotsAmountAfter).toBe(1);
});

it('marks message unseen only if fully visible', async () => {
const mentee = accountFixtures.mentees[0];
await APISignUpMentee(mentee);
const mentor = accountFixtures.mentors[0];
await APISignUpMentor(mentor);

const {
sender_id: menteeId,
recipient_id: mentorId,
senderHeaders: menteeHeaders,
} = await APIGetSendInfo(mentee, mentor);
await sendMultiple(menteeId, mentorId, menteeHeaders, 'Hello', 10);

await signIn(mentor);
await element(by.id('tabs.chats')).tap();
await element(by.text(mentee.displayName)).tap();

await expect(element(by.text('Hello 0'))).not.toBeVisible();
await expect(element(by.text('Hello 9'))).toBeVisible();

await element(by.id('chat.back.button')).tap();
await expect(element(by.id('main.tabs.unseenDot'))).toBeVisible();
});

it('loads more messages if all newest unread', async () => {
const mentee = accountFixtures.mentees[0];
await APISignUpMentee(mentee);
const mentor = accountFixtures.mentors[0];
await APISignUpMentor(mentor);

const {
sender_id: menteeId,
recipient_id: mentorId,
senderHeaders: menteeHeaders,
} = await APIGetSendInfo(mentee, mentor);
await sendMultiple(menteeId, mentorId, menteeHeaders, 'Hello', 20);

await signIn(mentor);
await element(by.id('tabs.chats')).tap();

// wait for 2 message-polls, so all messages are fetched
await sleep(10);
await element(by.text(mentee.displayName)).tap();
await expect(element(by.text('Hello 19'))).toBeVisible();
await scrollUpAndFindText('Hello 0', 'main.buddy.messageList');
});

it('if buddy with most recent message deletes account, can receive still messages from other users', async () => {
const mentee = accountFixtures.mentees[0];
await APISignUpMentee(mentee);
const mentor = accountFixtures.mentors[0];
await APISignUpMentor(mentor);

// mentee sends a msg to mentor
const {
sender_id: menteeId,
sender_info: mentee_info,
recipient_id: mentorId,
senderHeaders: menteeHeaders,
} = await APIGetSendInfo(mentee, mentor);
await APISendMessage({
sender_id: menteeId,
recipient_id: mentorId,
content: 'Hi first',
headers: menteeHeaders,
});

await signIn(mentor);
await element(by.id('tabs.chats')).tap();
await element(by.text(mentee.displayName)).tap();
await element(by.id('chat.back.button')).tap();

// delete mentee account
await APIDeleteAccount(mentee_info.account_id, menteeHeaders);

// new mentee
const newMentee = accountFixtures.mentees[1];
await APISignUpMentee(newMentee);

const { sender_id: newMenteeId, senderHeaders: newMenteeHeaders } =
await APIGetSendInfo(newMentee, mentor);
await APISendMessage({
sender_id: newMenteeId,
recipient_id: mentorId,
content: 'Hi second',
headers: newMenteeHeaders,
});

await sleep(5);
await expect(element(by.text(mentee.displayName))).not.toBeVisible();
await element(by.text(newMentee.displayName)).tap();
await waitFor(element(by.text('Hi second')))
.toBeVisible()
.withTimeout(10000);
});
});
9 changes: 8 additions & 1 deletion e2e/fixtures/accounts.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,17 @@
},
{
"loginName": "mentee1",
"displayName": "mentee1_nick",
"displayName": "mentee_mummo",
"password": "Menteementee!",
"email": "[email protected]",
"role": "mentee"
},
{
"loginName": "mentee2",
"displayName": "mentee_seppo",
"password": "Menteementee!",
"email": "[email protected]",
"role": "mentee"
}
],
"mentors": [
Expand Down
47 changes: 47 additions & 0 deletions e2e/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { by, element, waitFor, device } from 'detox';
import { NativeMatcher } from 'detox/detox';
import { generateToken } from 'node-2fa';

const API_URL = process.env.YLITSE_API_URL || 'http://127.0.0.1:8080';
Expand Down Expand Up @@ -65,6 +66,18 @@ export async function scrollUpTo(elementId: string, viewId: string) {
.scroll(100, 'up', 0.1, 0.2);
}

/**
* Scrolls view up until element with text is found
*/
export async function scrollUpAndFindText(text: string, viewId: string) {
await waitFor(element(by.text(text)))
.toBeVisible()
.whileElement(by.id(viewId))
// Needs to scroll from x=0.1, y=0.2
// because of the big sticky toolbar
.scroll(100, 'up', 0.1, 0.2);
}

/**
* Waits until input is visible and then types given text
*/
Expand Down Expand Up @@ -208,6 +221,19 @@ export async function APIDeleteAccounts() {
}
}

/**
* Makes HTTP API calls to delete user
*/
export async function APIDeleteAccount(
id: string,
headers: Record<string, string>,
) {
await fetch(`${API_URL}/accounts/${id}`, {
method: 'DELETE',
headers,
});
}

/**
* SignUp new mentee
*/
Expand Down Expand Up @@ -370,6 +396,7 @@ export async function APIGetSendInfo(sender: any, reciever: any) {

return {
sender_id: senderInfo.id,
sender_info: senderInfo,
recipient_id: recieverInfo.id,
senderHeaders: toHeader(accessTokenSender),
recieverHeaders: toHeader(accessTokenReciever),
Expand Down Expand Up @@ -507,3 +534,23 @@ export async function APIUpdateMentor(mentorName: string, mentor: any) {
body: JSON.stringify(updatedMentor),
});
}

export async function countElements(matcher: NativeMatcher) {
try {
const attributes = await element(matcher)?.getAttributes();

if ('elements' in attributes) {
return attributes.elements.length;
} else {
return 1;
}
} catch (e) {
return 0;
}
}

export async function sleep(seconds: number) {
return new Promise(resolve => {
setTimeout(resolve, seconds * 1000);
});
}
13 changes: 11 additions & 2 deletions e2e/statusMessageTest.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ describe('Change status message', () => {
await element(by.id('tabs.settings')).tap();

await scrollDownAndTap(
'main.settings.account.status.title',
'main.settings.account.status.input',
'main.settings.index.view',
);
// @ts-ignore
Expand All @@ -115,8 +115,17 @@ describe('Change status message', () => {
0.2,
);

await expect(element(by.text(newStatusMessage))).toBeVisible();
// first tap is for closing keyboard (multiline), second for save
await element(by.id('main.settings.account.status.save')).tap();

await forceLogout();
await scrollDownAndTap(
'onboarding.welcome.button',
'onboarding.welcome.view',
);

// Show updated status message in mentor list...
await expect(element(by.id('components.mentorList'))).toBeVisible();
await expect(element(by.text(newStatusMessage))).toBeVisible();
});
});
12 changes: 1 addition & 11 deletions src/Screens/Main/Chat/MessageList/Message.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import React from 'react';
import RN from 'react-native';
import * as redux from 'redux';
import { useDispatch } from 'react-redux';

import { markSeen } from '../../../../state/reducers/markSeen';
import * as actions from '../../../../state/actions';

import colors from '../../../components/colors';
import fonts from '../../../components/fonts';
Expand All @@ -16,16 +11,11 @@ export type MessageProps = {
type: 'Message';
value: messageApi.Message;
id: string;
isSeen: boolean;
};

const Message = ({ value: message }: MessageProps) => {
const { content, sentTime, type } = message;
const dispatch = useDispatch<redux.Dispatch<actions.Action>>();
React.useEffect(() => {
if (!message.isSeen && message.type === 'Received') {
dispatch(markSeen({ message }));
}
}, []);

const bubbleStyle =
type === 'Received' ? styles.leftBubble : styles.rightBubble;
Expand Down
Loading
Loading