Skip to content

Commit

Permalink
chore: Added redux, updated test renderer.
Browse files Browse the repository at this point in the history
  • Loading branch information
willtp87 committed Jun 4, 2024
1 parent 81a340a commit 24d0ed5
Show file tree
Hide file tree
Showing 15 changed files with 1,581 additions and 743 deletions.
10 changes: 10 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,15 @@ module.exports = {
rules: {
// Ensures props and state inside functions are always up-to-date
"react-hooks/exhaustive-deps": "warn",
"no-restricted-imports": "off",
"@typescript-eslint/no-restricted-imports": [
"warn",
{
name: "react-redux",
importNames: ["useSelector", "useDispatch"],
message:
"Use typed hooks `useAppDispatch` and `useAppSelector` instead.",
},
],
},
};
2 changes: 2 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{
}
8 changes: 4 additions & 4 deletions App.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { render } from "@testing-library/react-native";
import React from "react";
import renderer from "react-test-renderer";

import App from "./App";

describe("<App />", () => {
it("has 1 child", () => {
const tree: any = renderer.create(<App />).toJSON();
expect(tree.children.length).toBe(1);
it("Has expected number of children.", () => {
const tree: any = render(<App />).toJSON();
expect(tree.children.length).toBe(2);
});
});
19 changes: 13 additions & 6 deletions App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,25 @@ import "intl-pluralrules";
import "./i18n/i18n";
import { useTranslation } from "react-i18next";
import { StyleSheet, View } from "react-native";
import { Provider } from "react-redux";

import TimeInApp from "./components/TimeInApp";
import { store } from "./store";

const theme = createTheme({});

export default function App() {
const { t } = useTranslation();
return (
<ThemeProvider theme={theme}>
<View style={styles.container}>
<Text>{t("helloWorld")}</Text>
<StatusBar style="auto" />
</View>
</ThemeProvider>
<Provider store={store}>
<ThemeProvider theme={theme}>
<View style={styles.container}>
<Text>{t("helloWorld")}</Text>
<StatusBar style="auto" />
<TimeInApp />
</View>
</ThemeProvider>
</Provider>
);
}

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ If you would like to contribute to this repo create an issue or pull request.
* [Expo](https://expo.dev/)
* [React Native Elements](https://reactnativeelements.com/docs)
* [i18next](https://www.i18next.com/)
* [Redux Toolkit](https://redux-toolkit.js.org/)

### Building

Expand Down Expand Up @@ -43,4 +44,4 @@ This project uses Jest and has associated scripts.

## License

[GPLv3](http://www.gnu.org/licenses/gpl-3.0.txt)
[GPLv3](http://www.gnu.org/licenses/gpl-3.0.txt)
49 changes: 49 additions & 0 deletions components/TimeInApp/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//import { screen, act } from "@testing-library/react";
import { render, screen, act } from "@testing-library/react-native";
import React from "react";
import { Provider } from "react-redux";

import TimeInApp from ".";
import { store } from "../../store";
describe("<TimeInApp />", () => {
it("increments the timer each second", () => {
render(
<Provider store={store}>
<TimeInApp />
</Provider>,
);
expect(screen.queryByText("0")).toBeTruthy();

act(() => {
jest.advanceTimersByTime(1000);
});
expect(screen.queryByText("1")).toBeTruthy();

act(() => {
jest.advanceTimersByTime(1000);
});
expect(screen.queryByText("2")).toBeTruthy();

act(() => {
jest.advanceTimersByTime(500);
});
expect(screen.queryByText("2")).toBeTruthy();

act(() => {
jest.advanceTimersByTime(500);
});
expect(screen.queryByText("3")).toBeTruthy();
});

it("clears the interval when the component is unmounted", () => {
const clearIntervalMock = jest.spyOn(window, "clearInterval");
const { unmount } = render(
<Provider store={store}>
<TimeInApp />
</Provider>,
);

unmount();
expect(clearIntervalMock).toHaveBeenCalled();
});
});
21 changes: 21 additions & 0 deletions components/TimeInApp/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Text } from "@rneui/themed";
import React, { useEffect } from "react";

import { useAppSelector, useAppDispatch } from "../../store/hooks";
import { increment, selectCount } from "../../store/timeInAppSlice";

// This component will show the time in seconds that the user has been in the app.
export default function TimeInApp() {
const timeInApp = useAppSelector(selectCount);
const dispatch = useAppDispatch();

useEffect(() => {
const interval = setInterval(() => {
dispatch(increment());
}, 1000);

return () => clearInterval(interval);
}, [dispatch]);

return <Text>{timeInApp}</Text>;
}
3 changes: 2 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ module.exports = {
transformIgnorePatterns: [
"node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg|@rneui)",
],
setupFiles: ["./jest.setup.ts"],
setupFilesAfterEnv: ["./jest.setup.ts"],
fakeTimers: { enableGlobally: true },
};
19 changes: 16 additions & 3 deletions jest.setup.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
// set/clear Immediate not available by default.
global.setImmediate = jest.useRealTimers as unknown as typeof setImmediate;
global.clearImmediate = jest.useRealTimers as unknown as typeof clearImmediate;
// Make toBeInTheDocument() available globally.
import "@testing-library/jest-dom";

// Prevent timeouts in tests.
global.beforeEach(() => {
// Set/clear Immediate not available by default. The use of fake and real timers here is intentional. Each prevents different issues.
global.setImmediate = jest.useFakeTimers as unknown as typeof setImmediate;
global.clearImmediate =
jest.useRealTimers as unknown as typeof clearImmediate;

jest.useFakeTimers();
});
global.afterEach(() => {
jest.useRealTimers();
});

// https://github.com/expo/expo/issues/27496
jest.mock("expo-localization", () => ({
getLocales: jest.fn(() => {
Expand Down
13 changes: 9 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
"ios": "expo start --ios"
},
"dependencies": {
"@rneui/base": "^4.0.0-rc.8",
"@rneui/themed": "^4.0.0-rc.8",
"@babel/preset-env": "^7.1.6",
"@reduxjs/toolkit": "^2.2.5",
"@rneui/base": "4.0.0-rc.7",
"@rneui/themed": "4.0.0-rc.8",
"expo": "~51",
"expo-localization": "^15.0.3",
"expo-status-bar": "~1.11.1",
Expand All @@ -21,19 +23,22 @@
"react-i18next": "^14.1.1",
"react-native": "0.73.6",
"react-native-safe-area-context": "^4.10.1",
"react-native-vector-icons": "^10.1.0",
"react-redux": "^9.1.2",
"typescript": "^5.3.0"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@testing-library/jest-dom": "^6.4.5",
"@testing-library/react-native": "^12.5.1",
"@types/jest": "^29.5.12",
"@types/react-test-renderer": "^18.3.0",
"@types/react": "^18.2",
"eslint": "8",
"eslint-config-universe": "^12.0.1",
"expo-modules-core": "^1.12.9",
"jest": "^29.7.0",
"jest-expo": "^51.0.1",
"prettier": "^3.2.5",
"react-test-renderer": "^18.3.1",
"ts-jest": "^29.1.2"
},
"private": true
Expand Down
9 changes: 9 additions & 0 deletions store/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { useDispatch, useSelector, useStore } from "react-redux";

import type { AppDispatch, AppStore, RootState } from "../store";

// Use these for typescript instead of plain hooks.
export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
export const useAppSelector = useSelector.withTypes<RootState>();
export const useAppStore = useStore.withTypes<AppStore>();
14 changes: 14 additions & 0 deletions store/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { configureStore } from "@reduxjs/toolkit";

import timeInAppReducer from "./timeInAppSlice";

export const store = configureStore({
reducer: {
timeInApp: timeInAppReducer,
},
});

// Export types from our store.
export type AppStore = typeof store;
export type RootState = ReturnType<AppStore["getState"]>;
export type AppDispatch = AppStore["dispatch"];
34 changes: 34 additions & 0 deletions store/timeInAppSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";

import type { RootState } from ".";

// Typing for `state`.
interface timeInAppState {
value: number;
}
const initialState: timeInAppState = {
value: 0,
};

// Slice definition.
export const timeInAppSlice = createSlice({
name: "timeInApp",
initialState,
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload;
},
},
});

// Exports.
export const { increment, decrement, incrementByAmount } =
timeInAppSlice.actions;
export const selectCount = (state: RootState) => state.timeInApp.value;
export default timeInAppSlice.reducer;
8 changes: 6 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"jsx": "react-native",
"lib": ["dom", "esnext"],
"lib": [
"dom",
"esnext"
],
"moduleResolution": "node",
"noEmit": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"strict": true,
"esModuleInterop": true
}
},
"extends": "expo/tsconfig.base"
}
Loading

0 comments on commit 24d0ed5

Please sign in to comment.