diff --git a/README.md b/README.md index 7827381..0b4c596 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,12 @@ to [instructions here](https://react-native-courses.clubinfo-clermont.fr/docs/no - [Detail](#detail) - [Creating](#creating) - [Updating](#updating) +- [Some business logic](#some-business-logic) - [Using the app](#using-the-app) - [Running the backend](#running-the-backend) + - [Connecting to the backend locally](#connecting-to-the-backend-locally) - [Running this app](#running-this-app) +- [Testing the app](#testing-the-app) - [Known limitations](#known-limitations) - [Sparse](#sparse) - [Hardcoded enums](#hardcoded-enums) @@ -29,7 +32,7 @@ a [backend API](https://github.com/draialexis/pokemong_api) that provides the da * [x] Documentation (6 pts) - [x] Application sketches (4 pts) - [x] A Readme describing your project/application. (2 pts) -* [x] Basics (20 pts) +* [x] Basics (20 pts (- 2)) - [x] Navigation (3 pts) + [x] Tab bottom navigation (2 pts) AND at least one button (1 pts) - [x] Redux Store (10 pts) @@ -42,14 +45,14 @@ a [backend API](https://github.com/draialexis/pokemong_api) that provides the da - [x] Binding child component props (1 pts) - [x] Handle a TextInput correctly (2 pts) + [x] Beware of keyboard management -* [ ] Application features (14 pts) +* [x] Application features (14 pts (- 2)) - [x] Retrieve data using the Web API (6 pts) + [x] Handle fetch success callback (3 pts) + [x] Handle fetch error callback (3 pts) - - [ ] Store favorite data into phone storage (2 pts) - - [ ] Write Tests (6 pts) - + [ ] all actions payload (1 pts) - + [ ] all reducers case (2 pts) + - [ ] ~~Store favorite data into phone storage (2 pts)~~ + - [x] Write Tests (6 pts (- 1)) + + [ ] ~~all actions payload (1 pts)~~ + + [x] all reducers case (2 pts) + [x] one UI Component (3 pts) ## Sketches @@ -133,6 +136,16 @@ yarn start The app should launch successfully, and instructions should be provided in terminal to open it on a number of devices. +## Testing the app + +You can run some partial automated tests on `moveReducer.ts`, on `TypeTacticsInfoList.tsx`, and +on `MoveDetailScreen.tsx` +by running the command + +```bash +yarn run test +``` + ## Known limitations Beside whatever items may not have been checked off the [notation checklist](#notation-checklist), there are also diff --git a/components/TypeTacticsInfoList.test.tsx b/components/__tests__/TypeTacticsInfoList.test.tsx similarity index 75% rename from components/TypeTacticsInfoList.test.tsx rename to components/__tests__/TypeTacticsInfoList.test.tsx index eaeee19..a418d4c 100644 --- a/components/TypeTacticsInfoList.test.tsx +++ b/components/__tests__/TypeTacticsInfoList.test.tsx @@ -1,9 +1,9 @@ -// components/TypeTacticsInfoList.test.ts +// components/__tests__/TypeTacticsInfoList.test.ts import React from 'react'; import { render } from '@testing-library/react-native'; -import TypeTacticsInfoList from './TypeTacticsInfoList'; -import { TypeName } from "../entities/TypeName"; +import TypeTacticsInfoList from '../TypeTacticsInfoList'; +import { TypeName } from "../../entities/TypeName"; describe('TypeTacticsInfoList component', () => { it('renders types correctly', () => { @@ -17,12 +17,12 @@ describe('TypeTacticsInfoList component', () => { it('renders "Nothing" when types array is empty', () => { const { getByText } = render(); - expect(getByText('Nothing')).toBeTruthy(); + expect(getByText('NOTHING')).toBeTruthy(); }); it('renders "Nothing" when types is undefined', () => { // @ts-ignore const { getByText } = render(); - expect(getByText('Nothing')).toBeTruthy(); + expect(getByText('NOTHING')).toBeTruthy(); }); }); diff --git a/package.json b/package.json index 71c55b9..9cb46ab 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "react-native-web": "~0.18.11", "react-redux": "^8.0.7", "redux": "^4.2.1", + "redux-mock-store": "^1.5.4", "redux-thunk": "^2.4.2", "typescript": "^4.9.4" }, @@ -63,6 +64,7 @@ "@babel/core": "^7.20.0", "@types/jest": "^29.5.1", "@types/react-native": "~0.64.12", + "@types/redux-mock-store": "^1.0.3", "jest": "^29.2.1", "jest-expo": "^48.0.0" }, diff --git a/redux/reducers/__tests__/moveReducer.test.ts b/redux/reducers/__tests__/moveReducer.test.ts new file mode 100644 index 0000000..082e76e --- /dev/null +++ b/redux/reducers/__tests__/moveReducer.test.ts @@ -0,0 +1,100 @@ +// redux/reducers/__tests__/moveReducer.test.ts +import moveReducer from '../moveReducer'; +import { CREATE_MOVE, DELETE_MOVE, GET_MOVES, MOVE_ERROR, UPDATE_MOVE } from '../../constants'; +import { Move } from "../../../entities/Move"; +import { MoveCategoryName } from "../../../entities/MoveCategoryName"; +import { TypeName } from "../../../entities/TypeName"; + +describe('moveReducer', () => { + const initialState = { + moves: [], + error: null + }; + + it('returns the initial state when an action type is not passed', () => { + // @ts-ignore + const reducer = moveReducer(undefined, { type: null }); + + expect(reducer).toEqual(initialState); + }); + + it('handles GET_MOVES action', () => { + const moves: Move[] = [{ + id: '1', + name: 'Test Move', + category: MoveCategoryName.PHYSICAL, + power: 100, + accuracy: 100, + type: { name: TypeName.NORMAL, weakAgainst: [], effectiveAgainst: [] }, + schemaVersion: 2 + }]; + const reducer = moveReducer( + initialState, + { type: GET_MOVES, payload: moves } + ); + + expect(reducer).toEqual({ ...initialState, moves }); + }); + + it('handles CREATE_MOVE action', () => { + const move: Move = { + id: '1', + name: 'Test Move', + category: MoveCategoryName.PHYSICAL, + power: 100, + accuracy: 100, + type: { name: TypeName.NORMAL, weakAgainst: [], effectiveAgainst: [] }, + schemaVersion: 2 + }; + const reducer = moveReducer( + initialState, + { type: CREATE_MOVE, payload: move } + ); + + expect(reducer).toEqual({ ...initialState, moves: [move] }); + }); + + it('handles UPDATE_MOVE action', () => { + const initialMove: Move = { + id: '1', + name: 'Test Move', + category: MoveCategoryName.PHYSICAL, + power: 100, + accuracy: 100, + type: { name: TypeName.NORMAL, weakAgainst: [], effectiveAgainst: [] }, + schemaVersion: 2 + }; + const updatedMove: Move = { ...initialMove, name: 'Updated Move' }; + const reducer = moveReducer( + { ...initialState, moves: [initialMove] }, + { type: UPDATE_MOVE, payload: updatedMove } + ); + + expect(reducer).toEqual({ ...initialState, moves: [updatedMove] }); + }); + + it('handles DELETE_MOVE action', () => { + const move: Move = { + id: '1', + name: 'Test Move', + category: MoveCategoryName.PHYSICAL, + power: 100, + accuracy: 100, + type: { name: TypeName.NORMAL, weakAgainst: [], effectiveAgainst: [] }, + schemaVersion: 2 + }; + const reducer = moveReducer( + { ...initialState, moves: [move] }, + // @ts-ignore + { type: DELETE_MOVE, payload: move.id } + ); + + expect(reducer).toEqual(initialState); + }); + + it('handles MOVE_ERROR action', () => { + const reducer = moveReducer(initialState, { type: MOVE_ERROR, payload: 'Error message' }); + + expect(reducer).toEqual({ ...initialState, error: 'Error message' }); + }); +}); diff --git a/screens/moves/__tests__/MoveDetailScreen.test.tsx b/screens/moves/__tests__/MoveDetailScreen.test.tsx new file mode 100644 index 0000000..7da94c1 --- /dev/null +++ b/screens/moves/__tests__/MoveDetailScreen.test.tsx @@ -0,0 +1,71 @@ +// screens/moves/__tests__/MoveDetailScreen.test.tsx + +import React from 'react'; +import { render, fireEvent } from '@testing-library/react-native'; +import configureStore from 'redux-mock-store'; +import { Provider } from 'react-redux'; +import MoveDetailScreen from '../MoveDetailScreen'; +import { MOVE_FORM } from "../../../navigation/constants"; + +const mockStore = configureStore([]); + +describe('MoveDetailScreen', () => { + let store; + let component: JSX.Element; + // @ts-ignore + let navigation; + + beforeEach(() => { + const mockMove = { + id: '1', + name: 'Test Move', + category: 'PHYSICAL', + power: '100', + accuracy: '100', + type: { + name: 'NORMAL', + weakAgainst: [], + effectiveAgainst: [], + }, + }; + + store = mockStore({ + move: { + moves: [mockMove], + error: null, + }, + }); + + navigation = { navigate: jest.fn(), setOptions: jest.fn() }; + const route = { params: { move: mockMove } }; + + component = ( + + {/* @ts-ignore */} + + + ); + }); + + it('renders correctly', () => { + const { getByText } = render(component); + expect(getByText('Name: Test Move')).toBeTruthy(); + expect(getByText('Category: PHYSICAL')).toBeTruthy(); + expect(getByText('Power: 100')).toBeTruthy(); + expect(getByText('Accuracy: 100')).toBeTruthy(); + expect(getByText('Type: NORMAL')).toBeTruthy(); + }); + + it('navigates to the form screen when the edit button is pressed', () => { + const { getByText } = render(component); + fireEvent.press(getByText('Edit Move')); + // @ts-ignore + expect(navigation.navigate).toHaveBeenCalledWith(MOVE_FORM, { move: expect.any(Object) }); + }); + + it('sets the navigation options correctly', () => { + render(component); + // @ts-ignore + expect(navigation.setOptions).toHaveBeenCalledWith({ title: 'Test Move' }); + }); +}); diff --git a/yarn.lock b/yarn.lock index a09537a..8a0b36d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2478,6 +2478,13 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/redux-mock-store@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@types/redux-mock-store/-/redux-mock-store-1.0.3.tgz#895de4a364bc4836661570aec82f2eef5989d1fb" + integrity sha512-Wqe3tJa6x9MxMN4DJnMfZoBRBRak1XTPklqj4qkVm5VBpZnC8PSADf4kLuFQ9NAdHaowfWoEeUMz7NWc2GMtnA== + dependencies: + redux "^4.0.5" + "@types/retry@0.12.0": version "0.12.0" resolved "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz" @@ -6752,6 +6759,11 @@ lodash.debounce@^4.0.8: resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz" integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz" @@ -8648,12 +8660,19 @@ recast@^0.20.4: source-map "~0.6.1" tslib "^2.0.1" +redux-mock-store@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/redux-mock-store/-/redux-mock-store-1.5.4.tgz#90d02495fd918ddbaa96b83aef626287c9ab5872" + integrity sha512-xmcA0O/tjCLXhh9Fuiq6pMrJCwFRaouA8436zcikdIpYWWCjU76CRk+i2bHx8EeiSiMGnB85/lZdU3wIJVXHTA== + dependencies: + lodash.isplainobject "^4.0.6" + redux-thunk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.2.tgz#b9d05d11994b99f7a91ea223e8b04cf0afa5ef3b" integrity sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q== -redux@^4.2.1: +redux@^4.0.5, redux@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197" integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==