From 50fa8bf9afe9719e0bf9042bd00e3749e67c792a Mon Sep 17 00:00:00 2001 From: Roland Osborne Date: Mon, 13 Feb 2023 21:53:44 -0800 Subject: [PATCH] testing conversation context for mobile app --- app/mobile/src/context/useCardContext.hook.js | 2 + .../context/useConversationContext.hook.js | 82 ++++++++---------- .../src/context/useStoreContext.hook.js | 20 +---- app/mobile/test/Conversation.test.js | 85 +++++++++++++++++++ 4 files changed, 125 insertions(+), 64 deletions(-) create mode 100644 app/mobile/test/Conversation.test.js diff --git a/app/mobile/src/context/useCardContext.hook.js b/app/mobile/src/context/useCardContext.hook.js index c903d8ed..01e3e3ef 100644 --- a/app/mobile/src/context/useCardContext.hook.js +++ b/app/mobile/src/context/useCardContext.hook.js @@ -109,6 +109,7 @@ export function useCardContext() { const { notifiedView, notifiedProfile, notifiedArticle, notifiedChannel } = card.data || {}; const cardRevision = { view: notifiedView, profile: notifiedProfile, artcile: notifiedArticle, channel: notifiedChannel }; await syncCard(server, token, guid, entry, cardRevision); + await store.action.clearCardItemOffsync(guid, cardId); entry.card.offsync = false; } @@ -164,6 +165,7 @@ export function useCardContext() { catch (err) { console.log(err); entry.offsync = true; + await store.action.setCardItemOffsync(guid, card.id); } } cards.current.set(card.id, entry); diff --git a/app/mobile/src/context/useConversationContext.hook.js b/app/mobile/src/context/useConversationContext.hook.js index 18673cad..29846397 100644 --- a/app/mobile/src/context/useConversationContext.hook.js +++ b/app/mobile/src/context/useConversationContext.hook.js @@ -24,7 +24,6 @@ export function useConversationContext() { const force = useRef(false); const syncing = useRef(false); const update = useRef(false); - const loaded = useRef(false); const conversationId = useRef(null); const topics = useRef(new Map()); @@ -44,7 +43,7 @@ export function useConversationContext() { syncing.current = true; update.current = false; force.current = false; - loadMore.current = false; + more.current = false; if (reset.current) { reset.current = false; @@ -55,44 +54,28 @@ export function useConversationContext() { if (conversation) { const { cardId, channelId } = conversation; + const cardValue = cardId ? card.state.cards.get(cardId) : null; + const channelValue = cardId ? cardValue?.get(channelId) : channel.state.channels.get(channelId); + if (channelValue) { + const { topicRevision, syncRevision, topicMarker } = channelValue; + curRevision = topicRevision; + setRevision = syncRevision; + marker = topicMarker; + updateState({ card: cardValue, channel: channelValue }); - if (loaded.current) { - const cardValue = cardId ? card.state.cards.get(cardId) : null; - const channelValue = cardId ? cardValue?.get(channelId) : channel.state.channels.get(channelId); - if (channelValue) { - const { topicRevision, syncRevision, topicMarker } = channelValue; - curRevision = topicRevision; - setRevision = syncRevision; - marker = topicMarker; - updateState({ card: cardValue, channel: channelValue }); - } - else { - console.log("failed to load conversation"); - sysncing.current = false; - return; - } - } - - if (!loaded.current) { - const cardValue = cardId ? card.state.cards.get(cardId) : null; - const channelValue = cardId ? cardValue?.get(channelId) : channel.state.channels.get(channelId); - if (channelValue) { - const { topicRevision, syncRevision, topicMarker } = channelValue; - curRevision = topicRevision; - setRevision = syncRevision; - marker = topicMarker; + if (!loaded.current) { const topicItems = await getTopicItems(cardId, channelId); - for (let topic: topicItems) { + for (let topic of topicItems) { topics.current.set(topic.topicId, topic); } - updateState({ card: cardValue, channel: channelValue, topics: topics.current }); + updateState({ offsync: false, topics: topics.current }); loaded.current = true; } - else { - console.log("failed to load conversation"); - syncing.current = false; - return; - } + } + else { + console.log("failed to load conversation"); + syncing.current = false; + return; } try { @@ -125,7 +108,7 @@ export function useConversationContext() { } } - const setTopicDelta(cardId, channelId, entries) => { + const setTopicDelta = async (cardId, channelId, entries) => { for (let entry of entries) { if (entry.data) { if (entry.data.detail) { @@ -139,6 +122,7 @@ export function useConversationContext() { } } else { + topics.current.delete(entry.id); clearTopicItem(entry.id); } } @@ -157,7 +141,7 @@ export function useConversationContext() { reset.current = true; await sync(); }, - clearConversation: async () + clearConversation: async () => { conversationId.current = null; reset.current = true; await sync(); @@ -216,14 +200,22 @@ export function useConversationContext() { await channel.actions.clearChannelCard(channelId, id); } }, + setChannelReadRevision: async (revision) => { + const { cardId, channelId } = conversationId.current || {}; + if (cardId) { + await card.actions.setChannelReadRevision(cardId, channelId, revision); + } + else if (channelId) { + await channel.actions.setReadRevision(channelId, revision); + } + }, addChannelAlert: async () => { const { cardId, channelId } = conversationId.current || {}; - if (cardId) { - return await card.actions.addChannelAlert(cardId, channelId); - } - else if (channelId) { - return await channel.actions.addChannelAlert(channelId); - } + if (cardId) { + return await card.actions.addChannelAlert(cardId, channelId); + } + else if (channelId) { + return await channel.actions.addChannelAlert(channelId); } }, setChannelFlag: async () => { @@ -271,7 +263,7 @@ export function useConversationContext() { if (cardId) { await card.actions.setUnsealedTopicSubject(cardId, channelId, topicId, revision, unsealed); } - else {channelId) { + else if (channelId) { await channel.actions.setUnsealedTopicSubject(channelId, topicId, revision, unsealed); } setTopicField(topicId, 'unsaledDetail', unsealed); @@ -390,8 +382,8 @@ export function useConversationContext() { return { topicId: entry.id, revision: entry.revision, - detailRevision = entry.data?.detailRevision, - detail = entry.data?.topicDetail, + detailRevision: entry.data?.detailRevision, + detail: entry.data?.topicDetail, }; }; diff --git a/app/mobile/src/context/useStoreContext.hook.js b/app/mobile/src/context/useStoreContext.hook.js index 040a0ab6..86c1d1d6 100644 --- a/app/mobile/src/context/useStoreContext.hook.js +++ b/app/mobile/src/context/useStoreContext.hook.js @@ -103,7 +103,7 @@ export function useStoreContext() { }, setCardItem: async (guid, card) => { const { id, revision, data } = card; - await db.current.executeSql(`INSERT OR REPLACE INTO card_${guid} (card_id, revision, detail_revision, profile_revision, detail, profile, notified_view, notified_profile, notified_article, notified_channel) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);`, [id, revision, data.detailRevision, data.profileRevision, encodeObject(data.cardDetail), encodeObject(data.cardProfile), null, null, null, null]); + await db.current.executeSql(`INSERT OR REPLACE INTO card_${guid} (card_id, revision, detail_revision, profile_revision, detail, profile, notified_view, notified_profile, notified_article, notified_channel) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);`, [card.cardId, card.revision, card.detailRevision, card.profileRevision, encodeObject(card.detail), encodeObject(card.profile), null, null, null, null]); }, clearCardItem: async (guid, cardId) => { await db.current.executeSql(`DELETE FROM card_${guid} WHERE card_id=?`, [cardId]); @@ -141,24 +141,6 @@ export function useStoreContext() { setCardItemProfile: async (guid, cardId, revision, profile) => { await db.current.executeSql(`UPDATE card_${guid} set profile_revision=?, profile=? where card_id=?`, [revision, encodeObject(profile), cardId]); }, - getCardItemStatus: async (guid, cardId) => { - const values = await getAppValues(db.current, `SELECT detail, profile, profile_revision, detail_revision, notified_view, notified_article, notified_profile, notified_channel, offsync FROM card_${guid} WHERE card_id=?`, [cardId]); - if (!values.length) { - return null; - } - return { - detail: decodeObject(values[0].detail), - profile: decodeObject(values[0].profile), - profileRevision: values[0].profile_revision, - detailRevision: values[0].detail_revision, - notifiedView: values[0].notified_view, - notifiedArticle: values[0].notified_article, - notifiedProfile: values[0].notified_profile, - notifiedChannel: values[0].notified_channel, - offsync: values[0].offsync, - blocked: values[0].blocked, - }; - }, getCardItems: async (guid) => { const values = await getAppValues(db.current, `SELECT card_id, revision, detail_revision, profile_revision, detail, profile, offsync, blocked, notified_view, notified_profile, notified_article, notified_channel FROM card_${guid}`, []); return values.map(card => ({ diff --git a/app/mobile/test/Conversation.test.js b/app/mobile/test/Conversation.test.js new file mode 100644 index 00000000..5a85b618 --- /dev/null +++ b/app/mobile/test/Conversation.test.js @@ -0,0 +1,85 @@ +import React, { useState, useEffect, useContext } from 'react'; +import { View, Text } from 'react-native'; +import { useTestStoreContext } from './useTestStoreContext.hook'; +import { prettyDOM } from '@testing-library/dom'; +import {render, act, screen, waitFor, fireEvent} from '@testing-library/react-native'; +import { StoreContext } from 'context/StoreContext'; +import { ConversationContextProvider, ConversationContext } from 'context/ConversationContext'; +import { CardContextProvider, CardContext } from 'context/CardContext'; +import { ChannelContextProvider, ChannelContext } from 'context/ChannelContext'; +import * as fetchUtil from 'api/fetchUtil'; + +function ConversationView() { + const [renderCount, setRenderCount] = useState(0); + const conversation = useContext(ConversationContext); + const channel = useContext(ChannelContext); + const card = useContext(CardContext); + const [topics, setTopics] = useState([]); + + useEffect(() => { + setRenderCount(renderCount + 1); + const rendered = []; + }, [conversation.state]); + + return ( + + + ); +} + +function ConversationTestApp() { + return ( + + + + + + + + ) +} + +const realUseContext = React.useContext; +const realFetchWithTimeout = fetchUtil.fetchWithTimeout; +const realFetchWithCustomTimeout = fetchUtil.fetchWithCustomTimeout; +beforeEach(() => { + const mockUseContext = jest.fn().mockImplementation((ctx) => { + if (ctx === StoreContext) { + return useTestStoreContext(); + } + return realUseContext(ctx); + }); + React.useContext = mockUseContext; + + const mockFetch = jest.fn().mockImplementation((url, options) => { + return Promise.resolve({ + json: () => Promise.resolve([]) + }); + }); + fetchUtil.fetchWithTimeout = mockFetch; + fetchUtil.fetchWithCustomTimeout = mockFetch; +}); + +afterEach(() => { + React.useContext = realUseContext; + fetchUtil.fetchWithTimeout = realFetchWithTimeout; + fetchUtil.fetchWithCustomTimeout = realFetchWithCustomTimeout; +}); + +test('test conversation', async () => { + + render() + + await act(async () => { + const channel = screen.getByTestId('conversation').props.channel; + await channel.actions.setSession({ guid: 'abc', server: 'test.org', token: '123' }); + await channel.actions.setRevision(1); + + const card = screen.getByTestId('conversation').props.card; + await card.actions.setSession({ guid: 'abc', server: 'test.org', token: '123' }); + await card.actions.setRevision(1); + //const conversation = screen.getByTestId('conversation').props.conversation; + }); +}); +