Skip to content

Commit

Permalink
Port DownloadStore/DownloadModel to zustand
Browse files Browse the repository at this point in the history
  • Loading branch information
Johnathan Bell authored and Johnathan Bell committed Dec 10, 2024
1 parent 31c6cda commit d7d441a
Show file tree
Hide file tree
Showing 16 changed files with 436 additions and 149 deletions.
6 changes: 3 additions & 3 deletions App.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import './i18n';

const App = observer(({ skipLoadingScreen }) => {
const [ isSplashReady, setIsSplashReady ] = useState(false);
const { rootStore } = useStores();
const { rootStore, downloadStore } = useStores();
const { theme } = useContext(ThemeContext);

rootStore.settingStore.systemThemeId = useColorScheme();
Expand Down Expand Up @@ -163,13 +163,13 @@ const App = observer(({ skipLoadingScreen }) => {
});
};

rootStore.downloadStore.downloads
downloadStore.downloads
.forEach(download => {
if (!download.isComplete && !download.isDownloading) {
downloadFile(download);
}
});
}, [ rootStore.deviceId, rootStore.downloadStore.downloads.size ]);
}, [ rootStore.deviceId, downloadStore.downloads.size ]);

if (!(isSplashReady && rootStore.storeLoaded) && !skipLoadingScreen) {
return null;
Expand Down
65 changes: 65 additions & 0 deletions __mocks__/zustand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// __mocks__/zustand.ts
import { act } from '@testing-library/react'
import type * as ZustandExportedTypes from 'zustand'
export * from 'zustand'

const { create: actualCreate, createStore: actualCreateStore } =
jest.requireActual<typeof ZustandExportedTypes>('zustand')

// a variable to hold reset functions for all stores declared in the app
export const storeResetFns = new Set<() => void>()

const createUncurried = <T>(
stateCreator: ZustandExportedTypes.StateCreator<T>,
) => {
const store = actualCreate(stateCreator)
const initialState = store.getInitialState()
storeResetFns.add(() => {
store.setState(initialState, true)
})
return store
}

// when creating a store, we get its initial state, create a reset function and add it in the set
export const create = (<T>(
stateCreator: ZustandExportedTypes.StateCreator<T>,
) => {
console.log('zustand create mock')

// to support curried version of create
return typeof stateCreator === 'function'
? createUncurried(stateCreator)
: createUncurried
}) as typeof ZustandExportedTypes.create

const createStoreUncurried = <T>(
stateCreator: ZustandExportedTypes.StateCreator<T>,
) => {
const store = actualCreateStore(stateCreator)
const initialState = store.getInitialState()
storeResetFns.add(() => {
store.setState(initialState, true)
})
return store
}

// when creating a store, we get its initial state, create a reset function and add it in the set
export const createStore = (<T>(
stateCreator: ZustandExportedTypes.StateCreator<T>,
) => {
console.log('zustand createStore mock')

// to support curried version of createStore
return typeof stateCreator === 'function'
? createStoreUncurried(stateCreator)
: createStoreUncurried
}) as typeof ZustandExportedTypes.createStore

// reset all stores after each test run
afterEach(() => {
act(() => {
storeResetFns.forEach((resetFn) => {
resetFn()
})
})
})
4 changes: 2 additions & 2 deletions components/NativeShellWebView.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import RefreshWebView from './RefreshWebView';

const NativeShellWebView = observer(
function NativeShellWebView(props, ref) {
const { rootStore } = useStores();
const { rootStore, downloadStore } = useStores();
const [ isRefreshing, setIsRefreshing ] = useState(false);

const server = rootStore.serverStore.servers[rootStore.settingStore.activeServer];
Expand Down Expand Up @@ -96,7 +96,7 @@ true;
const url = new URL(data.item.url);
const apiKey = url.searchParams.get('api_key');
/* eslint-enable no-case-declarations */
rootStore.downloadStore.add(new DownloadModel(
downloadStore.add(new DownloadModel(
data.item.itemId,
data.item.serverId,
server.urlString,
Expand Down
6 changes: 5 additions & 1 deletion hooks/useStores.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
import { useDownloadStore } from '../stores/DownloadStore';
import { useRootStore } from '../stores/RootStore';

// Compatibility for zustand conversion
export const useStores = () => { return { rootStore: useRootStore() } }
export const useStores = () => ({
rootStore: useRootStore(),
downloadStore: useDownloadStore()
})
2 changes: 2 additions & 0 deletions jest.setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

// import '@testing-library/jest-dom'

/* AsyncStorage Mock */
import mockAsyncStorage from '@react-native-async-storage/async-storage/jest/async-storage-mock';
jest.mock('@react-native-async-storage/async-storage', () => mockAsyncStorage);
Expand Down
38 changes: 19 additions & 19 deletions models/DownloadModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
*/

import * as FileSystem from 'expo-file-system';
import { computed, decorate, observable } from 'mobx';
import { ignore } from 'mobx-sync-lite';
// import { computed, decorate, observable } from 'mobx';
// import { ignore } from 'mobx-sync-lite';
import { v4 as uuidv4 } from 'uuid';

export default class DownloadModel {
Expand Down Expand Up @@ -77,20 +77,20 @@ export default class DownloadModel {
}
}

decorate(DownloadModel, {
isComplete: observable,
isDownloading: [ ignore, observable ],
isNew: observable,
apiKey: observable,
itemId: observable,
sessionId: observable,
serverId: observable,
serverUrl: observable,
title: observable,
filename: observable,
downloadUrl: observable,
key: computed,
localFilename: computed,
localPath: computed,
uri: computed
});
// decorate(DownloadModel, {
// isComplete: observable,
// isDownloading: [ ignore, observable ],
// isNew: observable,
// apiKey: observable,
// itemId: observable,
// sessionId: observable,
// serverId: observable,
// serverUrl: observable,
// title: observable,
// filename: observable,
// downloadUrl: observable,
// key: computed,
// localFilename: computed,
// localPath: computed,
// uri: computed
// });
4 changes: 2 additions & 2 deletions navigation/TabNavigator.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ function TabIcon(routeName, focused, color, size) {
const Tab = createBottomTabNavigator();

const TabNavigator = observer(() => {
const { rootStore } = useStores();
const { rootStore, downloadStore } = useStores();
const insets = useSafeAreaInsets();
const { t } = useTranslation();
const { theme } = useContext(ThemeContext);
Expand Down Expand Up @@ -83,7 +83,7 @@ const TabNavigator = observer(() => {
options={{
title: t('headings.downloads'),
headerShown: true,
tabBarBadge: rootStore.downloadStore.newDownloadCount > 0 ? rootStore.downloadStore.newDownloadCount : null
tabBarBadge: downloadStore.newDownloadCount > 0 ? downloadStore.newDownloadCount : null
}}
/>
)}
Expand Down
146 changes: 143 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit d7d441a

Please sign in to comment.