chore: test redux components with jest
This commit is contained in:
@@ -1,12 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render } from '@testing-library/react';
|
import { renderWithProviders } from '../utils/test-utils';
|
||||||
import MainList from './MainList';
|
import MainList from './MainList';
|
||||||
|
|
||||||
type onClick = (event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => void;
|
type onClick = (event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => void;
|
||||||
describe('MainList', () => {
|
describe('MainList', () => {
|
||||||
let disableLogging: onClick;
|
let disableLogging: onClick;
|
||||||
let enableLogging: onClick;
|
let enableLogging: onClick;
|
||||||
let loggedIn: boolean;
|
|
||||||
let loggingEnabled: boolean;
|
let loggingEnabled: boolean;
|
||||||
let logoutUser: onClick;
|
let logoutUser: onClick;
|
||||||
let totalTimeLoggedToday: string;
|
let totalTimeLoggedToday: string;
|
||||||
@@ -14,17 +13,15 @@ describe('MainList', () => {
|
|||||||
disableLogging = jest.fn();
|
disableLogging = jest.fn();
|
||||||
enableLogging = jest.fn();
|
enableLogging = jest.fn();
|
||||||
loggingEnabled = false;
|
loggingEnabled = false;
|
||||||
loggedIn = false;
|
|
||||||
logoutUser = jest.fn();
|
logoutUser = jest.fn();
|
||||||
totalTimeLoggedToday = '1/1/1999';
|
totalTimeLoggedToday = '1/1/1999';
|
||||||
});
|
});
|
||||||
it('should render properly', () => {
|
it('should render properly', () => {
|
||||||
const { container } = render(
|
const { container } = renderWithProviders(
|
||||||
<MainList
|
<MainList
|
||||||
disableLogging={disableLogging}
|
disableLogging={disableLogging}
|
||||||
enableLogging={enableLogging}
|
enableLogging={enableLogging}
|
||||||
loggingEnabled={loggingEnabled}
|
loggingEnabled={loggingEnabled}
|
||||||
loggedIn={loggedIn}
|
|
||||||
logoutUser={logoutUser}
|
logoutUser={logoutUser}
|
||||||
totalTimeLoggedToday={totalTimeLoggedToday}
|
totalTimeLoggedToday={totalTimeLoggedToday}
|
||||||
/>,
|
/>,
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { ReduxSelector } from '../types/store';
|
||||||
|
import { User } from '../types/user';
|
||||||
|
|
||||||
export interface MainListProps {
|
export interface MainListProps {
|
||||||
disableLogging: (event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => void;
|
disableLogging: (event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => void;
|
||||||
enableLogging: (event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => void;
|
enableLogging: (event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => void;
|
||||||
loggedIn: boolean;
|
|
||||||
loggingEnabled: boolean;
|
loggingEnabled: boolean;
|
||||||
logoutUser: (event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => void;
|
logoutUser: (event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => void;
|
||||||
totalTimeLoggedToday?: string;
|
totalTimeLoggedToday?: string;
|
||||||
@@ -15,14 +17,17 @@ const openOptionsPage = async (): Promise<void> => {
|
|||||||
export default function MainList({
|
export default function MainList({
|
||||||
disableLogging,
|
disableLogging,
|
||||||
enableLogging,
|
enableLogging,
|
||||||
loggedIn,
|
|
||||||
loggingEnabled,
|
loggingEnabled,
|
||||||
logoutUser,
|
logoutUser,
|
||||||
totalTimeLoggedToday,
|
totalTimeLoggedToday,
|
||||||
}: MainListProps): JSX.Element {
|
}: MainListProps): JSX.Element {
|
||||||
|
const user: User | undefined = useSelector(
|
||||||
|
(selector: ReduxSelector) => selector.currentUser.user,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{loggedIn && (
|
{user && (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-xs-12">
|
<div className="col-xs-12">
|
||||||
<blockquote>
|
<blockquote>
|
||||||
@@ -34,7 +39,7 @@ export default function MainList({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{loggingEnabled && loggedIn && (
|
{loggingEnabled && user && (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-xs-12">
|
<div className="col-xs-12">
|
||||||
<p>
|
<p>
|
||||||
@@ -45,7 +50,7 @@ export default function MainList({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!loggingEnabled && loggedIn && (
|
{!loggingEnabled && user && (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-xs-12">
|
<div className="col-xs-12">
|
||||||
<p>
|
<p>
|
||||||
@@ -61,7 +66,7 @@ export default function MainList({
|
|||||||
<i className="fa fa-fw fa-cogs"></i>
|
<i className="fa fa-fw fa-cogs"></i>
|
||||||
Options
|
Options
|
||||||
</a>
|
</a>
|
||||||
{loggedIn && (
|
{user && (
|
||||||
<div>
|
<div>
|
||||||
<a href="#" className="list-group-item" onClick={logoutUser}>
|
<a href="#" className="list-group-item" onClick={logoutUser}>
|
||||||
<i className="fa fa-fw fa-sign-out"></i>
|
<i className="fa fa-fw fa-sign-out"></i>
|
||||||
@@ -69,7 +74,7 @@ export default function MainList({
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!loggedIn && (
|
{!user && (
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { setValue } from '../reducers/apiKey';
|
import { configLogout, setApiKey } from '../reducers/configReducer';
|
||||||
|
import { userLogout } from '../reducers/currentUser';
|
||||||
import { ReduxSelector } from '../types/store';
|
import { ReduxSelector } from '../types/store';
|
||||||
import { User } from '../types/user';
|
import { User } from '../types/user';
|
||||||
import config from '../config/config';
|
import config from '../config/config';
|
||||||
import apiKeyInvalid from '../utils/apiKey';
|
import apiKeyInvalid from '../utils/apiKey';
|
||||||
import WakaTimeCore from '../core/WakaTimeCore';
|
import { fetchUserData } from '../utils/user';
|
||||||
import { setUser } from '../reducers/currentUser';
|
|
||||||
import changeExtensionState from '../utils/changeExtensionState';
|
|
||||||
|
|
||||||
export default function NavBar(): JSX.Element {
|
export default function NavBar(): JSX.Element {
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
@@ -158,15 +157,11 @@ export default function NavBar(): JSX.Element {
|
|||||||
if (state.apiKeyError === '' && state.apiKey !== '') {
|
if (state.apiKeyError === '' && state.apiKey !== '') {
|
||||||
setState({ ...state, loading: true });
|
setState({ ...state, loading: true });
|
||||||
await browser.storage.sync.set({ apiKey: state.apiKey });
|
await browser.storage.sync.set({ apiKey: state.apiKey });
|
||||||
dispatch(setValue(state.apiKey));
|
dispatch(configLogout());
|
||||||
|
dispatch(userLogout());
|
||||||
|
dispatch(setApiKey(state.apiKey));
|
||||||
|
|
||||||
try {
|
await fetchUserData(state.apiKey, dispatch);
|
||||||
const data = await WakaTimeCore.checkAuth(state.apiKey);
|
|
||||||
dispatch(setUser(data));
|
|
||||||
} catch (err: unknown) {
|
|
||||||
dispatch(setUser(undefined));
|
|
||||||
await changeExtensionState('notSignedIn');
|
|
||||||
}
|
|
||||||
setState({ ...state, loading: false });
|
setState({ ...state, loading: false });
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,69 +1,28 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import { setValue } from '../reducers/apiKey';
|
import { ApiKeyReducer, ReduxSelector } from '../types/store';
|
||||||
import { ReduxSelector } from '../types/store';
|
|
||||||
import { setUser } from '../reducers/currentUser';
|
|
||||||
import WakaTimeCore from '../core/WakaTimeCore';
|
|
||||||
import config from '../config/config';
|
import config from '../config/config';
|
||||||
|
import { fetchUserData } from '../utils/user';
|
||||||
import changeExtensionState from '../utils/changeExtensionState';
|
import changeExtensionState from '../utils/changeExtensionState';
|
||||||
import NavBar from './NavBar';
|
import NavBar from './NavBar';
|
||||||
import MainList from './MainList';
|
import MainList from './MainList';
|
||||||
|
|
||||||
const API_KEY = 'waka_3766d693-bff3-4c63-8bf5-b439f3e12301';
|
|
||||||
|
|
||||||
export default function WakaTime(): JSX.Element {
|
export default function WakaTime(): JSX.Element {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
loggedIn: false,
|
|
||||||
loggingEnabled: config.loggingEnabled,
|
loggingEnabled: config.loggingEnabled,
|
||||||
totalTimeLoggedToday: '0 minutes',
|
totalTimeLoggedToday: '0 minutes',
|
||||||
};
|
};
|
||||||
const [state, setState] = useState(defaultState);
|
const [state, setState] = useState(defaultState);
|
||||||
const apiKeyFromRedux: string = useSelector((selector: ReduxSelector) => selector.apiKey.value);
|
const {
|
||||||
|
apiKey: apiKeyFromRedux,
|
||||||
const fetchUserData = async (): Promise<void> => {
|
loggingEnabled,
|
||||||
// await browser.storage.sync.set({ apiKey: API_KEY });
|
totalTimeLoggedToday,
|
||||||
let apiKey = '';
|
}: ApiKeyReducer = useSelector((selector: ReduxSelector) => selector.config);
|
||||||
if (!apiKeyFromRedux) {
|
|
||||||
const storage = await browser.storage.sync.get({
|
|
||||||
apiKey: config.apiKey,
|
|
||||||
});
|
|
||||||
apiKey = storage.apiKey as string;
|
|
||||||
dispatch(setValue(apiKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!apiKey) {
|
|
||||||
await changeExtensionState('notSignedIn');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const data = await WakaTimeCore.checkAuth(apiKey);
|
|
||||||
dispatch(setUser(data));
|
|
||||||
const items = await browser.storage.sync.get({ loggingEnabled: config.loggingEnabled });
|
|
||||||
|
|
||||||
if (items.loggingEnabled === true) {
|
|
||||||
await changeExtensionState('allGood');
|
|
||||||
} else {
|
|
||||||
await changeExtensionState('notLogging');
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalTimeLoggedToday = await WakaTimeCore.getTotalTimeLoggedToday(apiKey);
|
|
||||||
setState({
|
|
||||||
...state,
|
|
||||||
loggedIn: true,
|
|
||||||
loggingEnabled: items.loggingEnabled as boolean,
|
|
||||||
totalTimeLoggedToday: totalTimeLoggedToday.text,
|
|
||||||
});
|
|
||||||
|
|
||||||
await WakaTimeCore.recordHeartbeat();
|
|
||||||
} catch (err: unknown) {
|
|
||||||
await changeExtensionState('notSignedIn');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchUserData();
|
fetchUserData(apiKeyFromRedux, dispatch);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const disableLogging = async () => {
|
const disableLogging = async () => {
|
||||||
@@ -109,10 +68,9 @@ export default function WakaTime(): JSX.Element {
|
|||||||
<MainList
|
<MainList
|
||||||
disableLogging={disableLogging}
|
disableLogging={disableLogging}
|
||||||
enableLogging={enableLogging}
|
enableLogging={enableLogging}
|
||||||
loggingEnabled={state.loggingEnabled}
|
loggingEnabled={loggingEnabled}
|
||||||
totalTimeLoggedToday={state.totalTimeLoggedToday}
|
totalTimeLoggedToday={totalTimeLoggedToday}
|
||||||
logoutUser={logoutUser}
|
logoutUser={logoutUser}
|
||||||
loggedIn={state.loggedIn}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
import { createSlice } from '@reduxjs/toolkit';
|
|
||||||
|
|
||||||
interface setValueAction {
|
|
||||||
payload: string;
|
|
||||||
type: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ApiKey {
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
export const initialState: ApiKey = { value: '' };
|
|
||||||
|
|
||||||
const apiKeySlice = createSlice({
|
|
||||||
initialState,
|
|
||||||
name: 'spiKey',
|
|
||||||
reducers: {
|
|
||||||
setValue: (state, action: setValueAction) => {
|
|
||||||
state.value = action.payload;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const actions = apiKeySlice.actions;
|
|
||||||
export const { setValue } = apiKeySlice.actions;
|
|
||||||
export default apiKeySlice.reducer;
|
|
||||||
50
src/reducers/configReducer.ts
Normal file
50
src/reducers/configReducer.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
import config from '../config/config';
|
||||||
|
import { ApiKeyReducer } from '../types/store';
|
||||||
|
|
||||||
|
interface SetApiKeyAction {
|
||||||
|
payload: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SetLoggingEnabledAction {
|
||||||
|
payload: boolean;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SetTotalTimeLoggedTodayAction {
|
||||||
|
payload: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const initialConfigState: ApiKeyReducer = {
|
||||||
|
apiKey: '',
|
||||||
|
loggingEnabled: config.loggingEnabled,
|
||||||
|
totalTimeLoggedToday: '0 minutes',
|
||||||
|
};
|
||||||
|
|
||||||
|
const apiKeySlice = createSlice({
|
||||||
|
initialState: initialConfigState,
|
||||||
|
name: 'configReducer',
|
||||||
|
reducers: {
|
||||||
|
configLogout: (state) => {
|
||||||
|
state.apiKey = '';
|
||||||
|
state.loggingEnabled = config.loggingEnabled;
|
||||||
|
state.totalTimeLoggedToday = '0 minutes';
|
||||||
|
},
|
||||||
|
setApiKey: (state, action: SetApiKeyAction) => {
|
||||||
|
state.apiKey = action.payload;
|
||||||
|
},
|
||||||
|
setLoggingEnabled: (state, action: SetLoggingEnabledAction) => {
|
||||||
|
state.loggingEnabled = action.payload;
|
||||||
|
},
|
||||||
|
setTotalTimeLoggedToday: (state, action: SetTotalTimeLoggedTodayAction) => {
|
||||||
|
state.totalTimeLoggedToday = action.payload;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const actions = apiKeySlice.actions;
|
||||||
|
export const { configLogout, setApiKey, setLoggingEnabled, setTotalTimeLoggedToday } =
|
||||||
|
apiKeySlice.actions;
|
||||||
|
export default apiKeySlice.reducer;
|
||||||
@@ -39,9 +39,12 @@ const currentUser = createSlice({
|
|||||||
setUser: (state, action: setUserAction) => {
|
setUser: (state, action: setUserAction) => {
|
||||||
state.user = action.payload;
|
state.user = action.payload;
|
||||||
},
|
},
|
||||||
|
userLogout: (state) => {
|
||||||
|
state.user = undefined;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const actions = currentUser.actions;
|
export const actions = currentUser.actions;
|
||||||
export const { setUser } = currentUser.actions;
|
export const { setUser, userLogout } = currentUser.actions;
|
||||||
export default currentUser.reducer;
|
export default currentUser.reducer;
|
||||||
|
|||||||
@@ -1,22 +1,25 @@
|
|||||||
import { configureStore, Store } from '@reduxjs/toolkit';
|
import { configureStore, Store, combineReducers } from '@reduxjs/toolkit';
|
||||||
import { logger } from 'redux-logger';
|
import { logger } from 'redux-logger';
|
||||||
import { reduxBatch } from '@manaflair/redux-batch';
|
import { reduxBatch } from '@manaflair/redux-batch';
|
||||||
import devToolsEnhancer from 'remote-redux-devtools';
|
import devToolsEnhancer from 'remote-redux-devtools';
|
||||||
import currentUserReducer, { initialState as InitalCurrentUser } from '../reducers/currentUser';
|
import currentUserReducer, { initialState as InitalCurrentUser } from '../reducers/currentUser';
|
||||||
import apiKeyReducer from '../reducers/apiKey';
|
import configReducer, { initialConfigState } from '../reducers/configReducer';
|
||||||
import isProd from '../utils/isProd';
|
import isProd from '../utils/isProd';
|
||||||
import { CurrentUser } from '../types/user';
|
|
||||||
|
|
||||||
export interface RootState {
|
// Create the root reducer separately so we can extract the RootState type
|
||||||
currentUser: CurrentUser;
|
const rootReducer = combineReducers({
|
||||||
}
|
config: configReducer,
|
||||||
|
currentUser: currentUserReducer,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type RootState = ReturnType<typeof rootReducer>;
|
||||||
|
|
||||||
const preloadedState: RootState = {
|
const preloadedState: RootState = {
|
||||||
|
config: initialConfigState,
|
||||||
currentUser: InitalCurrentUser,
|
currentUser: InitalCurrentUser,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RootStore = Store<RootState>;
|
export default (appName: string): Store<RootState> => {
|
||||||
export default (appName: string): RootStore => {
|
|
||||||
const enhancers = [];
|
const enhancers = [];
|
||||||
enhancers.push(reduxBatch);
|
enhancers.push(reduxBatch);
|
||||||
if (!isProd()) {
|
if (!isProd()) {
|
||||||
@@ -29,10 +32,7 @@ export default (appName: string): RootStore => {
|
|||||||
enhancers,
|
enhancers,
|
||||||
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger),
|
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger),
|
||||||
preloadedState,
|
preloadedState,
|
||||||
reducer: {
|
reducer: rootReducer,
|
||||||
apiKey: apiKeyReducer,
|
|
||||||
currentUser: currentUserReducer,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return store;
|
return store;
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { CurrentUser } from './user';
|
import { CurrentUser } from './user';
|
||||||
|
|
||||||
export interface ApiKeyReducer {
|
export interface ApiKeyReducer {
|
||||||
value: string;
|
apiKey: string;
|
||||||
|
loggingEnabled: boolean;
|
||||||
|
totalTimeLoggedToday: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ReduxSelector {
|
export interface ReduxSelector {
|
||||||
apiKey: ApiKeyReducer;
|
config: ApiKeyReducer;
|
||||||
currentUser: CurrentUser;
|
currentUser: CurrentUser;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { RootStore } from '../stores/createStore';
|
import { Store } from '@reduxjs/toolkit';
|
||||||
|
import { RootState } from '../stores/createStore';
|
||||||
import { fetchCurrentUser } from '../reducers/currentUser';
|
import { fetchCurrentUser } from '../reducers/currentUser';
|
||||||
import { ReduxSelector } from '../types/store';
|
import { ReduxSelector } from '../types/store';
|
||||||
|
|
||||||
type unsub = () => void;
|
type unsub = () => void;
|
||||||
export default (store: RootStore) =>
|
export default (store: Store<RootState>) =>
|
||||||
(time: number): unsub => {
|
(time: number): unsub => {
|
||||||
const fetchUser = () => {
|
const fetchUser = () => {
|
||||||
const apiKey: string = (store.getState() as ReduxSelector).apiKey.value;
|
const apiKey: string = (store.getState() as ReduxSelector).config.apiKey;
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
store.dispatch(fetchCurrentUser(apiKey));
|
store.dispatch(fetchCurrentUser(apiKey));
|
||||||
|
|||||||
43
src/utils/test-utils.tsx
Normal file
43
src/utils/test-utils.tsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import React, { PropsWithChildren } from 'react';
|
||||||
|
import { render } from '@testing-library/react';
|
||||||
|
import type { RenderOptions } from '@testing-library/react';
|
||||||
|
import { combineReducers, configureStore, Store } from '@reduxjs/toolkit';
|
||||||
|
import type { PreloadedState } from '@reduxjs/toolkit';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import { RootState } from '../stores/createStore';
|
||||||
|
|
||||||
|
// As a basic setup, import your same slice reducers
|
||||||
|
import userReducer, { initialState as InitalCurrentUser } from '../reducers/currentUser';
|
||||||
|
import configReducer, { initialConfigState } from '../reducers/configReducer';
|
||||||
|
|
||||||
|
// This type interface extends the default options for render from RTL, as well
|
||||||
|
// as allows the user to specify other things such as initialState, store.
|
||||||
|
interface ExtendedRenderOptions extends Omit<RenderOptions, 'queries'> {
|
||||||
|
preloadedState?: PreloadedState<RootState>;
|
||||||
|
store?: Store<RootState>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rootReducer = combineReducers({
|
||||||
|
config: configReducer,
|
||||||
|
currentUser: userReducer,
|
||||||
|
});
|
||||||
|
|
||||||
|
export function renderWithProviders(
|
||||||
|
ui: React.ReactElement,
|
||||||
|
{
|
||||||
|
preloadedState = {
|
||||||
|
config: initialConfigState,
|
||||||
|
currentUser: InitalCurrentUser,
|
||||||
|
},
|
||||||
|
// Automatically create a store instance if no store was passed in
|
||||||
|
store = configureStore({ preloadedState, reducer: rootReducer }),
|
||||||
|
...renderOptions
|
||||||
|
}: ExtendedRenderOptions = {},
|
||||||
|
): any {
|
||||||
|
function Wrapper({ children }: PropsWithChildren<Record<string, unknown>>): JSX.Element {
|
||||||
|
return <Provider store={store}>{children}</Provider>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return an object with the store and all of RTL's query functions
|
||||||
|
return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) };
|
||||||
|
}
|
||||||
45
src/utils/user.ts
Normal file
45
src/utils/user.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { AnyAction, Dispatch } from '@reduxjs/toolkit';
|
||||||
|
import { setApiKey, setLoggingEnabled, setTotalTimeLoggedToday } from '../reducers/configReducer';
|
||||||
|
import config from '../config/config';
|
||||||
|
import WakaTimeCore from '../core/WakaTimeCore';
|
||||||
|
import { setUser } from '../reducers/currentUser';
|
||||||
|
import changeExtensionState from './changeExtensionState';
|
||||||
|
|
||||||
|
export const fetchUserData = async (
|
||||||
|
apiKey: string,
|
||||||
|
dispatch: Dispatch<AnyAction>,
|
||||||
|
): Promise<void> => {
|
||||||
|
if (!apiKey) {
|
||||||
|
const storage = await browser.storage.sync.get({
|
||||||
|
apiKey: config.apiKey,
|
||||||
|
});
|
||||||
|
apiKey = storage.apiKey as string;
|
||||||
|
dispatch(setApiKey(apiKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!apiKey) {
|
||||||
|
await changeExtensionState('notSignedIn');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [data, totalTimeLoggedTodayResponse, items] = await Promise.all([
|
||||||
|
WakaTimeCore.checkAuth(apiKey),
|
||||||
|
WakaTimeCore.getTotalTimeLoggedToday(apiKey),
|
||||||
|
browser.storage.sync.get({ loggingEnabled: config.loggingEnabled }),
|
||||||
|
]);
|
||||||
|
dispatch(setUser(data));
|
||||||
|
|
||||||
|
if (items.loggingEnabled === true) {
|
||||||
|
await changeExtensionState('allGood');
|
||||||
|
} else {
|
||||||
|
await changeExtensionState('notLogging');
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(setLoggingEnabled(items.loggingEnabled as boolean));
|
||||||
|
dispatch(setTotalTimeLoggedToday(totalTimeLoggedTodayResponse.text));
|
||||||
|
|
||||||
|
await WakaTimeCore.recordHeartbeat();
|
||||||
|
} catch (err: unknown) {
|
||||||
|
await changeExtensionState('notSignedIn');
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user