mirror of
https://github.com/balzack/databag.git
synced 2025-03-13 00:50:03 +00:00
refactor account context in mobile app
This commit is contained in:
parent
726ca2ebc8
commit
aeca7d9ead
@ -8,11 +8,12 @@ import { setAccountLogin } from 'api/setAccountLogin';
|
||||
|
||||
export function useAccountContext() {
|
||||
const [state, setState] = useState({
|
||||
offsync: false,
|
||||
status: {},
|
||||
});
|
||||
const store = useContext(StoreContext);
|
||||
|
||||
const session = useRef(null);
|
||||
const access = useRef(null);
|
||||
const curRevision = useRef(null);
|
||||
const setRevision = useRef(null);
|
||||
const syncing = useRef(false);
|
||||
@ -22,13 +23,12 @@ export function useAccountContext() {
|
||||
}
|
||||
|
||||
const sync = async () => {
|
||||
if (!syncing.current && setRevision.current !== curRevision.current) {
|
||||
if (access.current && !syncing.current && setRevision.current !== curRevision.current) {
|
||||
syncing.current = true;
|
||||
|
||||
try {
|
||||
const revision = curRevision.current;
|
||||
const { server, appToken, guid } = session.current;
|
||||
const status = await getAccountStatus(server, appToken);
|
||||
const { server, token, guid } = access.current;
|
||||
const status = await getAccountStatus(server, token);
|
||||
await store.actions.setAccountStatus(guid, status);
|
||||
await store.actions.setAccountRevision(guid, revision);
|
||||
updateState({ status });
|
||||
@ -46,47 +46,53 @@ export function useAccountContext() {
|
||||
};
|
||||
|
||||
const actions = {
|
||||
setSession: async (access) => {
|
||||
const { guid, server, appToken } = access;
|
||||
setSession: async (session) => {
|
||||
if (access.current || syncing.current) {
|
||||
throw new Error('invalid account state');
|
||||
}
|
||||
access.current = session;
|
||||
const { guid, server, token } = session;
|
||||
const status = await store.actions.getAccountStatus(guid);
|
||||
const sealKey = await store.actions.getAccountSealKey(guid);
|
||||
const revision = await store.actions.getAccountRevision(guid);
|
||||
updateState({ status, sealKey });
|
||||
setRevision.current = revision;
|
||||
curRevision.current = revision;
|
||||
session.current = access;
|
||||
},
|
||||
clearSession: () => {
|
||||
session.current = {};
|
||||
updateState({ account: null });
|
||||
access.current = null;
|
||||
updateState({ account: {} });
|
||||
},
|
||||
setRevision: (rev) => {
|
||||
curRevision.current = rev;
|
||||
sync();
|
||||
},
|
||||
setNotifications: async (flag) => {
|
||||
const { server, appToken } = session.current;
|
||||
await setAccountNotifications(server, appToken, flag);
|
||||
const { server, token } = access.current;
|
||||
await setAccountNotifications(server, token, flag);
|
||||
},
|
||||
setSearchable: async (flag) => {
|
||||
const { server, appToken } = session.current;
|
||||
await setAccountSearchable(server, appToken, flag);
|
||||
const { server, token } = access.current;
|
||||
await setAccountSearchable(server, token, flag);
|
||||
},
|
||||
setAccountSeal: async (seal, key) => {
|
||||
const { guid, server, appToken } = session.current;
|
||||
await setAccountSeal(server, appToken, seal);
|
||||
const { guid, server, token } = access.current;
|
||||
await setAccountSeal(server, token, seal);
|
||||
await store.actions.setAccountSealKey(guid, key);
|
||||
updateState({ sealKey: key });
|
||||
},
|
||||
unlockAccountSeal: async (key) => {
|
||||
const { guid } = session.current;
|
||||
const { guid } = access.current;
|
||||
await store.actions.setAccountSealKey(guid, key);
|
||||
updateState({ sealKey: key });
|
||||
},
|
||||
setLogin: async (username, password) => {
|
||||
const { server, appToken } = session.current;
|
||||
await setAccountLogin(server, appToken, username, password);
|
||||
const { server, token } = access.current;
|
||||
await setAccountLogin(server, token, username, password);
|
||||
},
|
||||
resync: async () => {
|
||||
await sync();
|
||||
}
|
||||
}
|
||||
|
||||
return { state, actions }
|
||||
|
@ -41,7 +41,6 @@ export function useCardContext() {
|
||||
const curRevision = useRef(null);
|
||||
const cards = useRef(new Map());
|
||||
const syncing = useRef(false);
|
||||
const force = useRef(false);
|
||||
const store = useContext(StoreContext);
|
||||
|
||||
const updateState = (value) => {
|
||||
@ -94,17 +93,7 @@ export function useCardContext() {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const resync = async () => {
|
||||
try {
|
||||
force.current = true;
|
||||
await sync();
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const resyncCard = async (cardId) => {
|
||||
if (!syncing.current) {
|
||||
syncing.current = true;
|
||||
@ -132,10 +121,8 @@ export function useCardContext() {
|
||||
}
|
||||
|
||||
const sync = async () => {
|
||||
if (!syncing.current && (setRevision.current !== curRevision.current || force.current)) {
|
||||
if (access.current && !syncing.current && setRevision.current !== curRevision.current) {
|
||||
syncing.current = true;
|
||||
force.current = false;
|
||||
|
||||
try {
|
||||
const { server, token, guid } = access.current;
|
||||
const revision = curRevision.current;
|
||||
@ -297,9 +284,9 @@ export function useCardContext() {
|
||||
clearSession: () => {
|
||||
access.current = null;
|
||||
},
|
||||
setRevision: async (revision) => {
|
||||
setRevision: (revision) => {
|
||||
curRevision.current = revision;
|
||||
await sync();
|
||||
sync();
|
||||
},
|
||||
addCard: async (message) => {
|
||||
const { server, token } = access.current;
|
||||
@ -489,7 +476,7 @@ export function useCardContext() {
|
||||
await store.actions.setCardChannelTopicItemUnsealedDetail(guid, cardId, channelId, topicId, revision, unsealed);
|
||||
},
|
||||
resync: async () => {
|
||||
await resync();
|
||||
await sync();
|
||||
},
|
||||
resyncCard: async (cardId) => {
|
||||
await resyncCard(cardId);
|
||||
|
@ -30,7 +30,6 @@ export function useChannelContext() {
|
||||
const curRevision = useRef(null);
|
||||
const channels = useRef(new Map());
|
||||
const syncing = useRef(false);
|
||||
const force = useRef(false);
|
||||
const store = useContext(StoreContext);
|
||||
|
||||
const updateState = (value) => {
|
||||
@ -55,21 +54,9 @@ export function useChannelContext() {
|
||||
updateState({ channels: channels.current });
|
||||
};
|
||||
|
||||
const resync = async () => {
|
||||
try {
|
||||
force.current = true;
|
||||
await sync();
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
};
|
||||
|
||||
const sync = async () => {
|
||||
|
||||
if (!syncing.current && (setRevision.current !== curRevision.current || force.current)) {
|
||||
if (access.current && !syncing.current && setRevision.current !== curRevision.current) {
|
||||
syncing.current = true;
|
||||
force.current = false;
|
||||
try {
|
||||
const revision = curRevision.current;
|
||||
const { server, token, guid } = access.current;
|
||||
@ -111,10 +98,11 @@ export function useChannelContext() {
|
||||
|
||||
setRevision.current = revision;
|
||||
await store.actions.setChannelRevision(guid, revision);
|
||||
updateState({ channels: channels.current });
|
||||
updateState({ offsync: false, channels: channels.current });
|
||||
}
|
||||
catch(err) {
|
||||
console.log(err);
|
||||
updateState({ offsync: true });
|
||||
syncing.current = false;
|
||||
return;
|
||||
}
|
||||
@ -144,9 +132,9 @@ export function useChannelContext() {
|
||||
clearSession: () => {
|
||||
access.current = null;
|
||||
},
|
||||
setRevision: async (rev) => {
|
||||
setRevision: (rev) => {
|
||||
curRevision.current = rev;
|
||||
await sync();
|
||||
sync();
|
||||
},
|
||||
addChannel: async (type, subject, cards) => {
|
||||
const { server, token } = access.current;
|
||||
@ -169,7 +157,7 @@ export function useChannelContext() {
|
||||
return await clearChannelCard(server, token, channelId, cardId);
|
||||
},
|
||||
addTopic: async (channelId, type, message, files) => {
|
||||
const { server, token } = session.current;
|
||||
const { server, token } = access.current;
|
||||
if (files?.length > 0) {
|
||||
const topicId = await addChannelTopic(server, token, channelId, null, null, null);
|
||||
upload.actions.addTopic(server, token, channelId, topicId, files, async (assets) => {
|
||||
@ -210,81 +198,79 @@ export function useChannelContext() {
|
||||
return await getChannelTopic(server, token, channelId, topicId);
|
||||
},
|
||||
resync: async () => {
|
||||
await resync();
|
||||
await sync();
|
||||
},
|
||||
|
||||
getNotifications: async (channelId) => {
|
||||
const { server, token } = session.current;
|
||||
const { server, token } = access.current;
|
||||
return await getChannelNotifications(server, token, channelId);
|
||||
},
|
||||
setNotifications: async (channelId, notify) => {
|
||||
const { server, token } = session.current;
|
||||
const { server, token } = access.current;
|
||||
return await setChannelNotifications(server, token, channelId, notify);
|
||||
},
|
||||
|
||||
setReadRevision: async (channelId, revision) => {
|
||||
const { guid } = access.current;
|
||||
await store.actions.setChannelItemReadRevision(guid, channelId, revision);
|
||||
setChannelField(channelId, 'readRevision', revision);
|
||||
},
|
||||
setSyncRevision: async (channelId, revision) => {
|
||||
const { guid } = session.current;
|
||||
const { guid } = access.current;
|
||||
await store.actions.setChannelItemSyncRevision(guid, channelId, revision);
|
||||
setChannelField(channelId, 'syncRevision', revision);
|
||||
},
|
||||
setTopicMarker: async (channelId, marker) => {
|
||||
const { guid } = session.current;
|
||||
const { guid } = access.current;
|
||||
await store.actions.setChannelItemTopicMarker(guid, channelId, revision);
|
||||
setChannelField(channelId, 'topicMarker', marker);
|
||||
},
|
||||
setChannelFlag: async (channelId) => {
|
||||
const { guid } = session.current;
|
||||
const { guid } = access.current;
|
||||
await store.actions.setChannelItemBlocked(guid, channelId);
|
||||
setChannelField(channelId, 'blocked', true);
|
||||
},
|
||||
clearChannelFlag: async (channelId) => {
|
||||
const { guid } = session.current;
|
||||
const { guid } = access.current;
|
||||
await store.actions.clearChannelItemBlocked(guid, channelId);
|
||||
setChannelField(channelId, 'blocked', false);
|
||||
},
|
||||
setTopicFlag: async (channelId, topicId) => {
|
||||
const { guid } = session.current;
|
||||
const { guid } = access.current;
|
||||
await store.actions.setChannelTopicBlocked(guid, channelId, topicId, true);
|
||||
},
|
||||
clearTopicFlag: async (channelId, topicId) => {
|
||||
const { guid } = session.current;
|
||||
const { guid } = access.current;
|
||||
await store.actions.setChannelTopicBlocked(guid, channelId, topicId, false);
|
||||
},
|
||||
addChannelAlert: async (channelId) => {
|
||||
const { server, guid } = session.current;
|
||||
const { server, guid } = access.current;
|
||||
return await addFlag(server, guid, channelId);
|
||||
},
|
||||
addTopicAlert: async (channelId, topicId) => {
|
||||
const { server, guid } = session.current;
|
||||
const { server, guid } = access.current;
|
||||
return await addFlag(server, guid, channelId, topicId);
|
||||
},
|
||||
getTopicItems: async (channelId, revision, count, begin, end) => {
|
||||
const { guid } = session.current;
|
||||
const { guid } = access.current;
|
||||
return await store.actions.getChannelTopicItems(guid, channelId);
|
||||
},
|
||||
setTopicItem: async (channelId, topic) => {
|
||||
const { guid } = session.current;
|
||||
const { guid } = access.current;
|
||||
return await store.actions.setChannelTopicItem(guid, channelId, topic);
|
||||
},
|
||||
clearTopicItem: async (channelId, topicId) => {
|
||||
const { guid } = session.current;
|
||||
const { guid } = access.current;
|
||||
return await store.actions.clearChannelTopicItem(guid, channelId, topicId);
|
||||
},
|
||||
setUnsealedChannelSubject: async (channelId, revision, unsealed) => {
|
||||
const { guid } = session.current;
|
||||
const { guid } = access.current;
|
||||
await store.actions.setChannelItemUnsealedDetail(guid, channelId, revision, unsealed);
|
||||
},
|
||||
setUnsealedChannelSummary: async (channelId, revision, unsealed) => {
|
||||
const { guid } = session.current;
|
||||
const { guid } = access.current;
|
||||
await store.actions.setChannelItemUnsealedSummary(guid, channelId, revision, unsealed);
|
||||
},
|
||||
setUnsealedTopicSubject: async (channelId, topicId, revision, unsealed) => {
|
||||
const { guid } = session.current;
|
||||
const { guid } = access.current;
|
||||
await store.actions.setChannelTopicItemUnsealedDetail(guid, channelId, topicId, revision, unsealed);
|
||||
},
|
||||
};
|
||||
|
@ -8,12 +8,13 @@ import { StoreContext } from 'context/StoreContext';
|
||||
|
||||
export function useProfileContext() {
|
||||
const [state, setState] = useState({
|
||||
offsync: false,
|
||||
identity: {},
|
||||
imageUrl: null,
|
||||
});
|
||||
const store = useContext(StoreContext);
|
||||
|
||||
const session = useRef(null);
|
||||
const access = useRef(null);
|
||||
const curRevision = useRef(null);
|
||||
const setRevision = useRef(null);
|
||||
const syncing = useRef(false);
|
||||
@ -23,21 +24,22 @@ export function useProfileContext() {
|
||||
}
|
||||
|
||||
const sync = async () => {
|
||||
if (!syncing.current && setRevision.current !== curRevision.current) {
|
||||
if (access.current && !syncing.current && setRevision.current !== curRevision.current) {
|
||||
syncing.current = true;
|
||||
|
||||
try {
|
||||
const revision = curRevision.current;
|
||||
const { server, token, guid } = session.current;
|
||||
const { server, token, guid } = access.current;
|
||||
const identity = await getProfile(server, token);
|
||||
const imageUrl = identity?.image ? getProfileImageUrl(server, token, revision) : null;
|
||||
await store.actions.setProfile(guid, identity);
|
||||
await store.actions.setProfileRevision(guid, revision);
|
||||
updateState({ identity, imageUrl });
|
||||
updateState({ offsync: false, identity, imageUrl });
|
||||
setRevision.current = revision;
|
||||
}
|
||||
catch(err) {
|
||||
console.log(err);
|
||||
updateState({ offsync: true });
|
||||
syncing.current = false;
|
||||
return;
|
||||
}
|
||||
@ -48,34 +50,33 @@ export function useProfileContext() {
|
||||
};
|
||||
|
||||
const actions = {
|
||||
setSession: async (access) => {
|
||||
const { guid, server, token } = access;
|
||||
setSession: async (session) => {
|
||||
const { guid, server, token } = session;
|
||||
const identity = await store.actions.getProfile(guid);
|
||||
const revision = await store.actions.getProfileRevision(guid);
|
||||
const imageUrl = identity?.image ? getProfileImageUrl(server, token, revision) : null;
|
||||
updateState({ identity, imageUrl });
|
||||
updateState({ offsync: false, identity, imageUrl });
|
||||
setRevision.current = revision;
|
||||
curRevision.current = revision;
|
||||
session.current = access;
|
||||
access.current = session;
|
||||
},
|
||||
clearSession: () => {
|
||||
session.current = {};
|
||||
updateState({ identity: {}, imageUrl: null });
|
||||
access.current = null;
|
||||
},
|
||||
setRevision: (rev) => {
|
||||
curRevision.current = rev;
|
||||
sync();
|
||||
},
|
||||
setProfileData: async (name, location, description) => {
|
||||
const { server, token } = session.current;
|
||||
const { server, token } = access.current;
|
||||
await setProfileData(server, token, name, location, description);
|
||||
},
|
||||
setProfileImage: async (image) => {
|
||||
const { server, token } = session.current;
|
||||
const { server, token } = access.current;
|
||||
await setProfileImage(server, token, image);
|
||||
},
|
||||
getHandleStatus: async (name) => {
|
||||
const { server, token } = session.current;
|
||||
const { server, token } = access.current;
|
||||
return await getHandle(server, token, name);
|
||||
},
|
||||
}
|
||||
|
91
app/mobile/test/Account.test.js
Normal file
91
app/mobile/test/Account.test.js
Normal file
@ -0,0 +1,91 @@
|
||||
import React, { useState, useEffect, useContext } from 'react';
|
||||
import { View, Text } from 'react-native';
|
||||
import { useTestStoreContext } from './useTestStoreContext.hook';
|
||||
import {render, act, screen, waitFor, fireEvent} from '@testing-library/react-native'
|
||||
import { AccountContextProvider, AccountContext } from 'context/AccountContext';
|
||||
import * as fetchUtil from 'api/fetchUtil';
|
||||
|
||||
function AccountView() {
|
||||
const [renderCount, setRenderCount] = useState(0);
|
||||
const account = useContext(AccountContext);
|
||||
|
||||
useEffect(() => {
|
||||
setRenderCount(renderCount + 1);
|
||||
}, [account.state]);
|
||||
|
||||
return (
|
||||
<View testID="account" account={account} renderCount={renderCount}>
|
||||
<Text testID="searchable">{ account.state.status?.searchable }</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
function AccountTestApp() {
|
||||
return (
|
||||
<AccountContextProvider>
|
||||
<AccountView />
|
||||
</AccountContextProvider>
|
||||
)
|
||||
}
|
||||
|
||||
let fetchStatus;
|
||||
const realUseContext = React.useContext;
|
||||
const realFetchWithTimeout = fetchUtil.fetchWithTimeout;
|
||||
const realFetchWithCustomTimeout = fetchUtil.fetchWithCustomTimeout;
|
||||
beforeEach(() => {
|
||||
const mockUseContext = jest.fn().mockImplementation((ctx) => {
|
||||
return useTestStoreContext();
|
||||
});
|
||||
React.useContext = mockUseContext;
|
||||
|
||||
fetchStatus = {};
|
||||
const mockFetch = jest.fn().mockImplementation((url, options) => {
|
||||
return Promise.resolve({
|
||||
json: () => Promise.resolve(fetchStatus)
|
||||
});
|
||||
});
|
||||
fetchUtil.fetchWithTimeout = mockFetch;
|
||||
fetchUtil.fetchWithCustomTimeout = mockFetch;
|
||||
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
React.useContext = realUseContext;
|
||||
fetchUtil.fetchWithTimeout = realFetchWithTimeout;
|
||||
fetchUtil.fetchWithCustomTimeout = realFetchWithCustomTimeout;
|
||||
});
|
||||
|
||||
test('testing', async () => {
|
||||
render(<AccountTestApp />)
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(screen.getByTestId('searchable').props.children).toBe(undefined);
|
||||
});
|
||||
|
||||
fetchStatus = { searchable: true };
|
||||
|
||||
await act(async () => {
|
||||
const account = screen.getByTestId('account').props.account;
|
||||
await account.actions.setSession({ guid: 'abc', server: 'test.org', token: '123' });
|
||||
await account.actions.setRevision(1);
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(screen.getByTestId('searchable').props.children).toBe(true);
|
||||
});
|
||||
|
||||
fetchStatus = { searchable: false };
|
||||
|
||||
await act(async () => {
|
||||
const account = screen.getByTestId('account').props.account;
|
||||
await account.actions.setRevision(2);
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(screen.getByTestId('searchable').props.children).toBe(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user