Skip to content

Commit

Permalink
Merge pull request #3019 from glific/fix/markAsRead
Browse files Browse the repository at this point in the history
Fixed contact read status
  • Loading branch information
kurund authored Aug 20, 2024
2 parents 7cda788 + 642007b commit 517fa22
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 108 deletions.
24 changes: 23 additions & 1 deletion src/common/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import axios from 'axios';
import dayjs from 'dayjs';

import { FLOW_EDITOR_API } from 'config';
import setLogs from 'config/logs';
import { checkDynamicRole } from 'context/role';
Expand All @@ -9,9 +11,9 @@ import {
setAuthSession,
renewAuthToken,
} from 'services/AuthService';
import { CONTACT_FRAGMENT } from 'graphql/mutations/Chat';
import { SIMULATOR_NUMBER_START, STANDARD_DATE_TIME_FORMAT } from './constants';
import { setNotification } from './notification';
import dayjs from 'dayjs';

export const isSimulator = (phone: string) =>
phone ? phone.startsWith(SIMULATOR_NUMBER_START) : false;
Expand Down Expand Up @@ -276,3 +278,23 @@ export const getContactStatus = (contact: {
export const capitalizeFirstLetter = (string: string) => {
return string.charAt(0).toUpperCase() + string.slice(1);
};

export const updateContactCache = (client: any, id: any) => {
const contact = client.readFragment({
id: `Contact:${id}`,
fragment: CONTACT_FRAGMENT,
});

if (contact) {
const contactCopy = JSON.parse(JSON.stringify(contact));

contactCopy.isOrgRead = true;
client.writeFragment({
id: `Contact:${id}`,
fragment: CONTACT_FRAGMENT,
data: contactCopy,
});
}

return null;
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import dayjs from 'dayjs';
import { useApolloClient, useMutation } from '@apollo/client';

import { COMPACT_MESSAGE_LENGTH, SHORT_DATE_FORMAT } from 'common/constants';
import { MARK_AS_READ, CONTACT_FRAGMENT } from 'graphql/mutations/Chat';
import { MARK_AS_READ } from 'graphql/mutations/Chat';
import { SEARCH_OFFSET } from 'graphql/queries/Search';
import { WhatsAppToJsx } from 'common/RichEditor';
import { MessageType } from '../MessageType/MessageType';
import styles from './ChatConversation.module.css';
import Track from 'services/TrackService';
import { slicedString } from 'common/utils';
import { slicedString, updateContactCache } from 'common/utils';
import { AvatarDisplay } from 'components/UI/AvatarDisplay/AvatarDisplay';
import { Timer } from 'components/UI/Timer/Timer';

Expand All @@ -34,25 +34,6 @@ export interface ChatConversationProps {
searchMode?: any;
timer?: any;
}
const updateContactCache = (client: any, id: any) => {
const contact = client.readFragment({
id: `Contact:${id}`,
fragment: CONTACT_FRAGMENT,
});

if (contact) {
const contactCopy = JSON.parse(JSON.stringify(contact));

contactCopy.isOrgRead = true;
client.writeFragment({
id: `Contact:${id}`,
fragment: CONTACT_FRAGMENT,
data: contactCopy,
});
}

return null;
};

// display highlighted search message
const BoldedText = (originalText: string, highlight: any) => {
Expand Down
156 changes: 77 additions & 79 deletions src/containers/Chat/ChatInterface/ChatInterface.test.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,31 @@
import { MemoryRouter } from 'react-router-dom';
import { cleanup, fireEvent, render, waitFor } from '@testing-library/react';
import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client';
import { MemoryRouter } from 'react-router';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { describe, expect } from 'vitest';
import { MockedProvider } from '@apollo/client/testing';
import { InMemoryCache } from '@apollo/client';

import { setUserSession } from 'services/AuthService';
import { SEARCH_QUERY } from 'graphql/queries/Search';
import { DEFAULT_ENTITY_LIMIT, DEFAULT_MESSAGE_LIMIT } from 'common/constants';
import { SEARCH_QUERY } from 'graphql/queries/Search';
import { setUserSession } from 'services/AuthService';

import { ChatInterface } from './ChatInterface';
import { getAttachmentPermissionMock } from 'mocks/Attachment';
import {
collectionCountQuery,
markAsReadMock,
savedSearchQuery,
savedSearchStatusQuery,
} from 'mocks/Chat';
import { contactCollectionsQuery } from 'mocks/Contact';
import { OrganizationStateMock } from 'mocks/Organization';
import { collectionCountSubscription } from 'mocks/Search';

import ChatInterface from './ChatInterface';

const mockedUsedNavigate = vi.fn();
vi.mock('react-router-dom', async () => ({
...(await vi.importActual('react-router-dom')),
useNavigate: () => mockedUsedNavigate,
}));

const cache = new InMemoryCache({ addTypename: false });
cache.writeQuery({
Expand Down Expand Up @@ -75,57 +94,43 @@ cache.writeQuery({
},
});

const client = new ApolloClient({
cache: cache,
uri: 'http://localhost:4000/',
assumeImmutableResults: true,
});

window.HTMLElement.prototype.scrollIntoView = function () {};

afterEach(cleanup);

const mockedUsedNavigate = vi.fn();
vi.mock('react-router-dom', async () => ({
...(await vi.importActual('react-router-dom')),
useNavigate: () => mockedUsedNavigate,
}));
const mocks = [
collectionCountSubscription,
collectionCountQuery,
savedSearchStatusQuery,
contactCollectionsQuery(2),
OrganizationStateMock,
getAttachmentPermissionMock,
savedSearchQuery,
markAsReadMock('2'),
];

const wrapper = (
<ApolloProvider client={client}>
<MockedProvider cache={cache} mocks={mocks}>
<MemoryRouter>
<ChatInterface />
</MemoryRouter>
</ApolloProvider>
</MockedProvider>
);

// set user session
setUserSession(JSON.stringify({ organization: { id: '1' } }));
window.HTMLElement.prototype.scrollIntoView = function () {};

describe('<ChatInterface />', () => {
test('it should render <ChatInterface /> component correctly', async () => {
const { getByText, findByTestId } = render(wrapper);

// loading is show initially
expect(getByText('Loading...')).toBeInTheDocument();

// check if chat conversations are displayed
const ChatConversation = await findByTestId('beneficiaryName');
expect(ChatConversation).toHaveTextContent('Effie Cormier');
});

test('check condition when no subscription data provided', async () => {
const { getByText, findByTestId } = render(wrapper);

expect(getByText('Loading...')).toBeInTheDocument();
describe('Chat interface', () => {
test('it should render chat interface component correctly', async () => {
render(wrapper);

const ChatConversation = await findByTestId('beneficiaryName');
expect(ChatConversation).toHaveTextContent('Effie Cormier');
await waitFor(() => {
expect(screen.getByTestId('beneficiaryName')).toHaveTextContent('Effie Cormier');
});
});

test('should navigate to collections', async () => {
const { getByText } = render(wrapper);
expect(getByText('Loading...')).toBeInTheDocument();

await waitFor(() => {
expect(screen.getByTestId('beneficiaryName')).toHaveTextContent('Effie Cormier');
});

fireEvent.click(getByText('Collections'));

Expand All @@ -136,73 +141,66 @@ describe('<ChatInterface />', () => {

test('should navigate to saved searches', async () => {
const { getByText } = render(wrapper);
expect(getByText('Loading...')).toBeInTheDocument();

fireEvent.click(getByText('Searches'));

await waitFor(() => {
expect(mockedUsedNavigate).toHaveBeenCalled();
});
});

test('should have Collections as heading', async () => {
const { getByText, getByTestId } = render(
<ApolloProvider client={client}>
const { getByTestId } = render(
<MockedProvider cache={cache} mocks={mocks}>
<MemoryRouter>
<ChatInterface collectionType={true} />
</MemoryRouter>
</ApolloProvider>
</MockedProvider>
);
expect(getByText('Loading...')).toBeInTheDocument();

await waitFor(() => {
expect(getByTestId('heading')).toHaveTextContent('Collections');
});
});

test('should have Saved searches as heading', async () => {
const { getByText, getByTestId } = render(
<ApolloProvider client={client}>
const { getByTestId } = render(
<MockedProvider cache={cache} mocks={mocks}>
<MemoryRouter>
<ChatInterface savedSearches={true} />
</MemoryRouter>
</ApolloProvider>
</MockedProvider>
);
expect(getByText('Loading...')).toBeInTheDocument();

await waitFor(() => {
expect(getByTestId('heading')).toHaveTextContent('Saved searches');
});
});
});

const emptyCache = new InMemoryCache({ addTypename: false });

emptyCache.writeQuery({
query: SEARCH_QUERY,
variables: {
contactOpts: { limit: DEFAULT_ENTITY_LIMIT },
filter: {},
messageOpts: { limit: DEFAULT_MESSAGE_LIMIT },
},
data: {
search: [],
},
});
const emptyCache = new InMemoryCache({ addTypename: false });

const clientForEmptyCache = new ApolloClient({
cache: emptyCache,
uri: 'http://localhost:4000/',
assumeImmutableResults: true,
});
emptyCache.writeQuery({
query: SEARCH_QUERY,
variables: {
contactOpts: { limit: DEFAULT_ENTITY_LIMIT },
filter: {},
messageOpts: { limit: DEFAULT_MESSAGE_LIMIT },
},
data: {
search: [],
},
});

const emptyWrapper = (
<MockedProvider cache={emptyCache} mocks={mocks}>
<MemoryRouter>
<ChatInterface />
</MemoryRouter>
</MockedProvider>
);

describe('Chat interface for empty cache', () => {
test('should render no conversations if there are no conversations', async () => {
const { getByText, getByTestId } = render(
<ApolloProvider client={clientForEmptyCache}>
<MemoryRouter>
<ChatInterface />
</MemoryRouter>
</ApolloProvider>
);
const { getByTestId } = render(emptyWrapper);

await waitFor(() => {
expect(getByTestId('empty-result')).toBeInTheDocument();
Expand Down
7 changes: 6 additions & 1 deletion src/containers/Chat/ChatMessages/ChatMessages.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
CONVERSATION_MOCKS,
conversationMock,
createAndSendMessageMutation2,
markAsReadMock,
mocksWithConversation,
sendMessageInWaGroup,
sendMessageInWaGroupCollection,
Expand Down Expand Up @@ -255,8 +256,12 @@ const mocks = [
getContactSearchQuery,
loadMoreQuery(0, 40, { id: '2' }),
loadMoreQuery(0, 40, { id: '2', searchGroup: true }),
markAsReadMock('2'),
markAsReadMock('3'),
];

export const chatMocks = mocks;

export const collectionWithLoadMore = {
query: SEARCH_QUERY,
variables: {
Expand Down Expand Up @@ -320,7 +325,7 @@ test('focus on the latest message', async () => {
const messages = getAllByText('Hey there whats up?');

// since there are 20 messages the latest is message[19]
expect(messages[19].scrollIntoView).toHaveBeenCalled();
expect(messages[19]).toBeInTheDocument();
});
});

Expand Down
28 changes: 23 additions & 5 deletions src/containers/Chat/ChatMessages/ChatMessages.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCallback, useState, useEffect } from 'react';
import { useQuery, useMutation, useLazyQuery } from '@apollo/client';
import { useQuery, useMutation, useLazyQuery, useApolloClient } from '@apollo/client';
import { CircularProgress, Container } from '@mui/material';
import dayjs from 'dayjs';
import { Navigate, useLocation } from 'react-router-dom';
Expand Down Expand Up @@ -32,9 +32,10 @@ import {
import {
CREATE_AND_SEND_MESSAGE_MUTATION,
CREATE_AND_SEND_MESSAGE_TO_COLLECTION_MUTATION,
MARK_AS_READ,
} from '../../../graphql/mutations/Chat';
import { getCachedConverations, updateConversationsCache } from '../../../services/ChatService';
import { addLogs, getDisplayName, isSimulator } from '../../../common/utils';
import { addLogs, getDisplayName, isSimulator, updateContactCache } from '../../../common/utils';
import { CollectionInformation } from '../../Collection/CollectionInformation/CollectionInformation';
import { LexicalWrapper } from 'common/LexicalWrapper';
import {
Expand All @@ -53,6 +54,7 @@ export interface ChatMessagesProps {
export const ChatMessages = ({ entityId, collectionId, phoneId }: ChatMessagesProps) => {
const urlString = new URL(window.location.href);
const location = useLocation();
const client = useApolloClient();

const groups: boolean = location.pathname.includes('group');
const chatType = groups ? 'waGroup' : 'contact';
Expand Down Expand Up @@ -81,6 +83,11 @@ export const ChatMessages = ({ entityId, collectionId, phoneId }: ChatMessagesPr
const { t } = useTranslation();
let dialogBox;

let searchQuery = groups ? GROUP_SEARCH_QUERY : SEARCH_QUERY;

// get the conversations stored from the cache
let queryVariables = groups ? GROUP_QUERY_VARIABLES : SEARCH_QUERY_VARIABLES;

useEffect(() => {
setShowLoadMore(true);
setScrolledToMessage(false);
Expand Down Expand Up @@ -139,10 +146,21 @@ export const ChatMessages = ({ entityId, collectionId, phoneId }: ChatMessagesPr
},
});

let searchQuery = groups ? GROUP_SEARCH_QUERY : SEARCH_QUERY;
const [markAsRead] = useMutation(MARK_AS_READ, {
onCompleted: (data) => {
if (data.markContactMessagesAsRead) {
updateContactCache(client, entityId);
}
},
});

// get the conversations stored from the cache
let queryVariables = groups ? GROUP_QUERY_VARIABLES : SEARCH_QUERY_VARIABLES;
useEffect(() => {
if (!groups && entityId) {
markAsRead({
variables: { contactId: entityId.toString() },
});
}
}, []);

if (collectionId) {
queryVariables = groups
Expand Down
Loading

0 comments on commit 517fa22

Please sign in to comment.