Skip to content
Open
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
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
]
},
"devDependencies": {
"axios-mock-adapter": "^1.18.1",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.2",
"react-test-renderer": "^16.13.1"
}
}
41 changes: 37 additions & 4 deletions src/components/Task/Task.test.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,50 @@
import React from 'react';
import Task from './';
import {renderWithTheme} from "../../helpers/renderWithTheme";
import {shallow} from "enzyme";
import TaskTitle from "./styles/TaskTitle";

// We simply testing that our component Task is rendering here, snapshot testing might also include more features...
// Each time we run a snapshot test, it will create a snapshot files inside __snapshots__.
// Snapshot test will notify if the HTML produced by our component has been changes since the last time we run the test.
// If it is wanted that our changes will modify the HTML output, we shall update the tests by using 'yarn test -u'
describe("Task", () => {
// If it is wanted that our changes will modify the HTML output, we shall update the tests by using 'yarn test -u'.
describe("Task snapshot test.", () => {
// "describe" is use to creates a block that groups together several related tests. It is not mandatory but can make the test more readable.
// "test" is used to assert that the function passed as argument is executed correctly, first arg is describing the test.
test('Simply test if the component is rendering', () => {
const component = renderWithTheme(<Task title="Hey I'm a test task!"/>);
const component = renderWithTheme(setupTask({title: "Hey I'm a test task!"}));
let tree = component.toJSON();
// "expect" like "describe" is a function related to Jest, it might looks unintuitive but no need to import it here.
// Jest will make sure when running that this kind of functions are imported
// Jest will make sure when running that this kind of functions are imported.
expect(tree).toMatchSnapshot();
});
})
// "shallow" is provided by Enzyme, it renders only the single component, not including its children.
// This is useful to isolate the component for pure unit testing.
const setupTask = (props = {}) => shallow(<Task {...props}/>)

describe("Task DOM testing with Enzyme.", () => {
let componentToTest
const title = "Hey I'm a second test task!"
// "beforeEach" is a function that Jest will run before each test of the current block.
beforeEach(() => {
// Initialize the Task component with some props.
componentToTest = setupTask({title})
})
// "it" is a alias for "test" function, it allows us to read the test call as "it should render a remove button".
// Here we assume that the remove button is a really important Task component's feature and its presence need to be test.
it('should render a remove button', () => {
// This will prove that our Task component contains a Button.
expect(componentToTest.find('button'))
// But we might want to be more specific by using a custom attribute to identify the node we are looking for.
expect(componentToTest.find("[data-test='remove-button-task-test']"))
})

it('should render a title with the expected text', () => {
// We might find an imported sub-component like this:
const titleCompo = componentToTest.find(TaskTitle);
expect(titleCompo)
expect(titleCompo.text()).toContain(title)
expect(titleCompo.text()).not.toContain('what ever text not expected')
})
})
3 changes: 2 additions & 1 deletion src/components/Task/__snapshots__/Task.test.js.snap
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Task Simply test if the component is rendering 1`] = `
exports[`Task snapshot test. Simply test if the component is rendering 1`] = `
<div
className="sc-AykKD WZuFE"
>
Expand All @@ -11,6 +11,7 @@ exports[`Task Simply test if the component is rendering 1`] = `
Hey I'm a test task!
</span>
<button
data-test="remove-button-task-test"
onClick={[Function]}
>
X
Expand Down
2 changes: 1 addition & 1 deletion src/components/Task/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const Task = ({complete, completed, id, title, remove}) => {
return <TaskContainer completed={completed}>
{console.log('Rendering the Task')}
<TaskTitle onClick={completeSelf}>{title}</TaskTitle>
<button onClick={removeSelf}>X</button>
<button onClick={removeSelf} data-test='remove-button-task-test'>X</button>
</TaskContainer>
}

Expand Down
2 changes: 1 addition & 1 deletion src/components/TodolistClass.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class TodolistClass extends Component {
{
tasks.length > 0 &&
map(tasks, task =>
<Task {...task} complete={this.completeTaskForId} remove={this.removeTaskForId}/>
<Task key={task.id} {...task} complete={this.completeTaskForId} remove={this.removeTaskForId}/>
)
}
</div>
Expand Down
8 changes: 8 additions & 0 deletions src/helpers/getTestStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {applyMiddleware, createStore} from 'redux'
import rootReducer from '../redux/reducers/index'
import {middlewares} from '../redux/configureStore'
// This helper provide an instance of a similar store used in the app.
export const getTestStore = initialState => {
const createStoreWithMiddleware = applyMiddleware(...middlewares)(createStore)
return createStoreWithMiddleware(rootReducer, initialState)
}
2 changes: 1 addition & 1 deletion src/helpers/renderWithTheme.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ThemeProvider } from 'styled-components';
// Helpers creating a component to be tested with styled component Theme injected
export function renderWithTheme(component) {
return renderer.create(
<ThemeProvider theme={theme}>
<ThemeProvider theme={theme} key={component.key}>
{component}
</ThemeProvider>
);
Expand Down
6 changes: 2 additions & 4 deletions src/pages/HomePage/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import LoginForm from "./styles/LoginForm";
import {validateEmail} from "../../helpers/isEmailValid";
import Input from "../../components/Input";
import {useDispatch} from "react-redux";
import {setListTodos, setToken, setUser} from "../../redux/actions";
import {fetchTodos, setToken, setUser} from "../../redux/actions";

const Home = () => {
const history = useHistory();
Expand Down Expand Up @@ -60,9 +60,7 @@ const Home = () => {
// Go to sample Overview page using history react-router hook
history.push('/overview')
// Load the todos in the store
axios.get("https://reqres.in/api/todos", {}).then(res => {
dispatch(setListTodos(res.data))
})
dispatch(fetchTodos())
})
.catch(err => console.log(err))

Expand Down
2 changes: 1 addition & 1 deletion src/pages/OverviewPage/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const OverviewPage = () => {
<h2>Here you can find your Todo lists:</h2>
<ul>
{
map(todos, todo => <StyledLink to={`/todolist/${todo.name}`}><li>{todo.name}</li></StyledLink>)
map(todos, todo => <StyledLink key={todo.name} to={`/todolist/${todo.name}`}><li>{todo.name}</li></StyledLink>)
}
</ul>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import {
ADD_TASK,
REPLACE_TASK,
REMOVE_TASK,
} from "../actionTypes";
import {getFakeTodosPromise} from "../../helpers/getFakeTodosPromise";
import getGUID from "../../helpers/getGUID";
import * as notifications from "../../helpers/notificationTypes";
import {ERROR} from "../../helpers/notificationTypes";
import {notifyForTypeAndMessage} from "./notificationActions";
} from "../../actionTypes";
import {getFakeTodosPromise} from "../../../helpers/getFakeTodosPromise";
import getGUID from "../../../helpers/getGUID";
import * as notifications from "../../../helpers/notificationTypes";
import {ERROR} from "../../../helpers/notificationTypes";
import {notifyForTypeAndMessage} from "../notificationActions";

export const setTasksLoading = (isLoading) => ({
type: SET_TASKS_LOADING,
Expand Down
40 changes: 40 additions & 0 deletions src/redux/actions/tasksActions/tasksActions.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {getTestStore} from "../../../helpers/getTestStore";
import {fetchTodos} from "../todoListsActions";
import MockAdapter from "axios-mock-adapter";
import axios from "axios";

// This sets the mock adapter on the default instance
const mock = new MockAdapter(axios);

describe("Test that asynchronous action creators related to tasks are working correctly", () => {
it('should update the state correctly after fetching tasks', () => {
const expectedState = [
{
title: 'test1',
completed: false,
id: 'test-1-id'
},
{
title: 'test2',
completed: true,
id: 'test-2-id'
},
{
title: 'test3',
completed: false,
id: 'test-3-id'
},
]

const store = getTestStore()
mock.onGet("https://reqres.in/api/todos").reply(200, {
data: expectedState,
}
);
return store.dispatch(fetchTodos())
.then(() => {
const newState = store.getState()
expect(newState.todoLists.todos).toEqual(expectedState)
})
})
})
9 changes: 7 additions & 2 deletions src/redux/actions/todoListsActions.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import {
SET_LISTS_TODOS,
} from "../actionTypes";
import axios from "axios";

export const setListTodos = dataTodos => ({
type: SET_LISTS_TODOS,
payload: {
...dataTodos.data
list: dataTodos.data
}
})
})
export const fetchTodos = () => async dispatch =>
await axios.get("https://reqres.in/api/todos", {}).then(res => {
dispatch(setListTodos(res.data))
})
9 changes: 5 additions & 4 deletions src/redux/configureStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ const persistConfig = {
storage,
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
// List of middlewares used by the store
export const middlewares = [ReduxThunk, persistToken, notify]
// Create store and bind redux devtools extension window var, compose the multiple middleware as applyMiddleware receive only one enhancer
export const store = createStore(persistedReducer, compose(
applyMiddleware(ReduxThunk),
applyMiddleware(persistToken),
applyMiddleware(notify),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
applyMiddleware(...middlewares),
// Use devtools middleware conditionally as test are not run on a browser with the extension installed.
...(window.__REDUX_DEVTOOLS_EXTENSION__ ? [window.__REDUX_DEVTOOLS_EXTENSION__()] : [])
)
)
export const persistor = persistStore(store)
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {filter, map} from "lodash";
import {ADD_TASK, SET_TASKS, SET_TASKS_LOADING, REPLACE_TASK, REMOVE_TASK} from "../actionTypes";
import {ADD_TASK, SET_TASKS, SET_TASKS_LOADING, REPLACE_TASK, REMOVE_TASK} from "../../actionTypes";

const initialState = {tasks: []};

Expand Down
69 changes: 69 additions & 0 deletions src/redux/reducers/tasks/tasks.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import {ADD_TASK, REMOVE_TASK, REPLACE_TASK} from "../../actionTypes";
import tasksReducer from "./";

describe("Test of Tasks reducer.", () => {
// Use a new task generator for our test
const getNewTask = (id = "my-test-id") => ({
title: 'my test',
completed: false,
id,
})
const newTask = getNewTask(),
newTask2 = getNewTask('task-to-delete'),
newTask3 = getNewTask('task-to-be-replaced'),
newTask4 = getNewTask('task-to-replace')

let state = undefined

it('should return default state', () => {
expect(tasksReducer(state, {})).toEqual({tasks: []})
})

it('should return an array of task with our new task', () => {
state = tasksReducer(undefined, {
type: ADD_TASK,
payload: {
newTask
}
})
expect(state).toEqual({tasks: [newTask]})
})

it('should be possible to insert several tasks', () => {
state = tasksReducer(state, {
type: ADD_TASK,
payload: {
newTask: newTask2
}
})
expect(state).toEqual({tasks: [newTask, newTask2]})
state = tasksReducer(state, {
type: ADD_TASK,
payload: {
newTask: newTask3
}
})
expect(state).toEqual({tasks: [newTask, newTask2, newTask3]})
})

it('should remove a specific task', () => {
state = tasksReducer(state, {
type: REMOVE_TASK,
payload: {
idToRemove: newTask2.id
}
})
expect(state).toEqual({tasks: [newTask, newTask3]})
})

it('should replace a specific task by a new one', () => {
state = tasksReducer(state, {
type: REPLACE_TASK,
payload: {
idToReplace: newTask3.id,
newValue: newTask4,
}
})
expect(state).toEqual({tasks: [newTask, newTask4]})
})
})
2 changes: 1 addition & 1 deletion src/redux/reducers/todoLists.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default function(state = initialState, action) {
case SET_LISTS_TODOS: {
return {
...state,
todos: action.payload
todos: [...action.payload.list]
};
}
default:
Expand Down
4 changes: 4 additions & 0 deletions src/setupTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect';
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
// Configure the correct adapter related to our React version
configure({ adapter: new Adapter() });
Loading