Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions src/frontend/screens/Exchange/ExchangeScreenContent.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import * as React from 'react';
import type {SyncState} from '@comapeo/core-react';
import {
createNavigationContainerRef,
NavigationContainer,
} from '@react-navigation/native';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import {QueryClient, QueryClientProvider} from '@tanstack/react-query';
import {act, render, screen} from '@testing-library/react-native';
import {IntlProvider} from 'react-intl';
import {Text} from 'react-native';

import {ExchangeScreenContent} from './ExchangeScreenContent';

const mockConnectServers = jest.fn();
const mockDisconnectServers = jest.fn();
const mockSetAutostopDataSyncTimeout = jest.fn();

jest.mock('@comapeo/core-react', () => ({
...jest.requireActual('@comapeo/core-react'),
useDataSyncProgress: () => null,
useIsArchiveDevice: () => ({data: false}),
useStartSync: () => ({mutate: jest.fn()}),
useStopSync: () => ({mutate: jest.fn()}),
useConnectSyncServers: () => ({mutate: mockConnectServers}),
useDisconnectSyncServers: () => ({mutate: mockDisconnectServers}),
useSetAutostopDataSyncTimeout: () => ({
mutate: mockSetAutostopDataSyncTimeout,
}),
}));

jest.mock('../../contexts/ActiveProjectContext', () => ({
useActiveProject: () => ({projectId: 'project-id'}),
}));

jest.mock('../../hooks/server/projects', () => ({
useActiveArchiveServer: () => undefined,
}));

jest.mock('../../hooks/useLocalDiscoveryState', () => ({
useLocalDiscoveryState: () => 'CoMapeo Test Wi-Fi',
}));

const syncState = {
initial: {isSyncEnabled: true},
data: {isSyncEnabled: false},
remoteDeviceSyncState: {},
} satisfies SyncState;

type StackParams = {Sync: undefined; Other: undefined};

const Stack = createNativeStackNavigator<StackParams>();

const queryClient = new QueryClient();

const Wrapper = ({children}: {children: React.ReactNode}) => (
<QueryClientProvider client={queryClient}>
<IntlProvider locale="en">{children}</IntlProvider>
</QueryClientProvider>
);

test('connects servers on focus and disconnects them on blur', async () => {
const navigationRef = createNavigationContainerRef<StackParams>();

await render(
<React.Suspense fallback={null}>
<NavigationContainer ref={navigationRef}>
<Stack.Navigator>
<Stack.Screen name="Sync">
{() => <ExchangeScreenContent syncState={syncState} />}
</Stack.Screen>
<Stack.Screen name="Other">{() => <Text>Other</Text>}</Stack.Screen>
</Stack.Navigator>
</NavigationContainer>
</React.Suspense>,
{wrapper: Wrapper},
);

await expect(screen.findByText('No devices found.')).resolves.toBeVisible();

expect(mockSetAutostopDataSyncTimeout).toHaveBeenCalledTimes(1);
expect(mockSetAutostopDataSyncTimeout).toHaveBeenCalledWith({after: null});
expect(mockConnectServers).toHaveBeenCalledTimes(1);
expect(mockDisconnectServers).not.toHaveBeenCalled();

await act(async () => {
navigationRef.navigate('Other');
});

await expect(screen.findByText('Other')).resolves.toBeVisible();

expect(mockDisconnectServers).toHaveBeenCalledTimes(1);
expect(mockConnectServers).toHaveBeenCalledTimes(1);
});
32 changes: 21 additions & 11 deletions src/frontend/screens/Exchange/ExchangeScreenContent.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import * as React from 'react';
import {
useConnectSyncServers,
useDataSyncProgress,
useDisconnectSyncServers,
useIsArchiveDevice,
useSetAutostopDataSyncTimeout,
useStartSync,
useStopSync,
type SyncState,
} from '@comapeo/core-react';
import {useFocusEffect} from '@react-navigation/native';
Expand Down Expand Up @@ -128,9 +132,15 @@ export const ExchangeScreenContent = ({syncState}: {syncState: SyncState}) => {
const {formatMessage: t} = useIntl();
const queryClient = useQueryClient();
const navigation = useNavigationFromRoot();
const {projectApi, projectId} = useActiveProject();
const {projectId} = useActiveProject();
const progress = useDataSyncProgress({projectId});
const startSync = useStartSync({projectId});
const stopSync = useStopSync({projectId});
const {mutate: connectServers} = useConnectSyncServers({projectId});
const {mutate: disconnectServers} = useDisconnectSyncServers({projectId});
const {mutate: setAutostopDataSyncTimeout} = useSetAutostopDataSyncTimeout({
projectId,
});
const {data: isArchive} = useIsArchiveDevice();

const currentMediaSetting = isArchive ? 'everything' : 'previews';
Expand All @@ -157,13 +167,13 @@ export const ExchangeScreenContent = ({syncState}: {syncState: SyncState}) => {
useFocusEffect(
React.useCallback(() => {
// Unset sync autostop when screen mounts
projectApi.$sync.setAutostopDataSyncTimeout(null);
setAutostopDataSyncTimeout({after: null});
// Connects to Servers
projectApi.$sync.connectServers();
connectServers();
return () => {
projectApi.$sync.disconnectServers();
disconnectServers();
};
}, [projectApi]),
}, [connectServers, disconnectServers, setAutostopDataSyncTimeout]),
);

const shouldAutostopSyncWhenLeavingScreen =
Expand All @@ -174,7 +184,7 @@ export const ExchangeScreenContent = ({syncState}: {syncState: SyncState}) => {
React.useCallback(() => {
const unsubscribe = navigation.addListener('beforeRemove', () => {
if (shouldAutostopSyncWhenLeavingScreen) {
projectApi.$sync.setAutostopDataSyncTimeout(30_000);
setAutostopDataSyncTimeout({after: 30_000});
}
queryClient.invalidateQueries({
queryKey: [ROOT_QUERY_KEY, 'projects', projectId],
Expand All @@ -187,7 +197,7 @@ export const ExchangeScreenContent = ({syncState}: {syncState: SyncState}) => {
};
}, [
navigation,
projectApi,
setAutostopDataSyncTimeout,
queryClient,
projectId,
shouldAutostopSyncWhenLeavingScreen,
Expand Down Expand Up @@ -245,7 +255,7 @@ export const ExchangeScreenContent = ({syncState}: {syncState: SyncState}) => {
fullSize={true}
onPress={() => {
// TODO: Catch/surface error
projectApi.$sync.stop();
stopSync.mutate(undefined);
}}
text={t(m.stop)}
renderIcon={({size, color}) => (
Expand All @@ -271,7 +281,7 @@ export const ExchangeScreenContent = ({syncState}: {syncState: SyncState}) => {
fullSize={true}
onPress={() => {
// TODO: Catch/surface error
projectApi.$sync.stop();
stopSync.mutate(undefined);
}}
text={t(m.stop)}
renderIcon={({size, color}) => (
Expand Down Expand Up @@ -299,7 +309,7 @@ export const ExchangeScreenContent = ({syncState}: {syncState: SyncState}) => {
fullSize={true}
onPress={() => {
// TODO: Catch/surface error
projectApi.$sync.stop();
stopSync.mutate(undefined);
}}
text={t(m.stop)}
renderIcon={({size, color}) => (
Expand All @@ -325,7 +335,7 @@ export const ExchangeScreenContent = ({syncState}: {syncState: SyncState}) => {
fullSize={true}
onPress={() => {
// TODO: Catch/surface error
projectApi.$sync.stop();
stopSync.mutate(undefined);
}}
text={t(m.stop)}
renderIcon={({size, color}) => (
Expand Down
Loading