From d3491869bd8edb0cfc2207dda0000b9fd4a22b19 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 19 May 2019 12:54:19 +0200 Subject: [PATCH 1/2] Created tags list reducer test --- src/tags/reducers/tagsList.js | 8 +- test/tags/reducers/tagsList.test.js | 144 ++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 test/tags/reducers/tagsList.test.js diff --git a/src/tags/reducers/tagsList.js b/src/tags/reducers/tagsList.js index 622389c1..684a64d3 100644 --- a/src/tags/reducers/tagsList.js +++ b/src/tags/reducers/tagsList.js @@ -4,10 +4,10 @@ import { TAG_DELETED } from './tagDelete'; import { TAG_EDITED } from './tagEdit'; /* eslint-disable padding-line-between-statements */ -const LIST_TAGS_START = 'shlink/tagsList/LIST_TAGS_START'; -const LIST_TAGS_ERROR = 'shlink/tagsList/LIST_TAGS_ERROR'; -const LIST_TAGS = 'shlink/tagsList/LIST_TAGS'; -const FILTER_TAGS = 'shlink/tagsList/FILTER_TAGS'; +export const LIST_TAGS_START = 'shlink/tagsList/LIST_TAGS_START'; +export const LIST_TAGS_ERROR = 'shlink/tagsList/LIST_TAGS_ERROR'; +export const LIST_TAGS = 'shlink/tagsList/LIST_TAGS'; +export const FILTER_TAGS = 'shlink/tagsList/FILTER_TAGS'; /* eslint-enable padding-line-between-statements */ const initialState = { diff --git a/test/tags/reducers/tagsList.test.js b/test/tags/reducers/tagsList.test.js new file mode 100644 index 00000000..e3fe168c --- /dev/null +++ b/test/tags/reducers/tagsList.test.js @@ -0,0 +1,144 @@ +import reducer, { + FILTER_TAGS, + filterTags, + LIST_TAGS, + LIST_TAGS_ERROR, + LIST_TAGS_START, listTags, +} from '../../../src/tags/reducers/tagsList'; +import { TAG_DELETED } from '../../../src/tags/reducers/tagDelete'; +import { TAG_EDITED } from '../../../src/tags/reducers/tagEdit'; + +describe('tagsListReducer', () => { + describe('reducer', () => { + it('returns loading on LIST_TAGS_START', () => { + expect(reducer({}, { type: LIST_TAGS_START })).toEqual({ + loading: true, + error: false, + }); + }); + + it('returns error on LIST_TAGS_ERROR', () => { + expect(reducer({}, { type: LIST_TAGS_ERROR })).toEqual({ + loading: false, + error: true, + }); + }); + + it('returns provided tags as filtered and regular tags on LIST_TAGS', () => { + const tags = [ 'foo', 'bar', 'baz' ]; + + expect(reducer({}, { type: LIST_TAGS, tags })).toEqual({ + tags, + filteredTags: tags, + loading: false, + error: false, + }); + }); + + it('removes provided tag from filtered and regular tags on TAG_DELETED', () => { + const tags = [ 'foo', 'bar', 'baz' ]; + const tag = 'foo'; + const expectedTags = [ 'bar', 'baz' ]; + + expect(reducer({ tags, filteredTags: tags }, { type: TAG_DELETED, tag })).toEqual({ + tags: expectedTags, + filteredTags: expectedTags, + }); + }); + + it('renames provided tag from filtered and regular tags on TAG_EDITED', () => { + const tags = [ 'foo', 'bar', 'baz' ]; + const oldName = 'bar'; + const newName = 'renamed'; + const expectedTags = [ 'foo', 'renamed', 'baz' ].sort(); + + expect(reducer({ tags, filteredTags: tags }, { type: TAG_EDITED, oldName, newName })).toEqual({ + tags: expectedTags, + filteredTags: expectedTags, + }); + }); + + it('filters original list of tags by provided search term on FILTER_TAGS', () => { + const tags = [ 'foo', 'bar', 'baz', 'foo2', 'fo' ]; + const searchTerm = 'fo'; + const filteredTags = [ 'foo', 'foo2', 'fo' ]; + + expect(reducer({ tags }, { type: FILTER_TAGS, searchTerm })).toEqual({ + tags, + filteredTags, + }); + }); + }); + + describe('filterTags', () => + it('creates expected action', () => expect(filterTags('foo')).toEqual({ type: FILTER_TAGS, searchTerm: 'foo' }))); + + describe('listTags', () => { + const dispatch = jest.fn(); + const getState = jest.fn(() => ({})); + const buildShlinkApiClient = jest.fn(); + const listTagsMock = jest.fn(); + + afterEach(() => { + dispatch.mockReset(); + getState.mockClear(); + buildShlinkApiClient.mockReset(); + listTagsMock.mockReset(); + }); + + const assertNoAction = async (tagsList) => { + getState.mockReturnValue({ tagsList }); + + await listTags(buildShlinkApiClient, false)()(dispatch, getState); + + expect(buildShlinkApiClient).not.toHaveBeenCalled(); + expect(dispatch).not.toHaveBeenCalled(); + expect(getState).toHaveBeenCalledTimes(1); + }; + + it('does nothing when loading', async () => await assertNoAction({ loading: true })); + it('does nothing when list is not empty', async () => await assertNoAction({ loading: false, tags: [ 'foo', 'bar' ] })); + + it('dispatches loaded lists when no error occurs', async () => { + const tags = [ 'foo', 'bar', 'baz' ]; + + listTagsMock.mockResolvedValue(tags); + buildShlinkApiClient.mockResolvedValue({ listTags: listTagsMock }); + + await listTags(buildShlinkApiClient, true)()(dispatch, getState); + + expect(buildShlinkApiClient).toHaveBeenCalledTimes(1); + expect(getState).toHaveBeenCalledTimes(1); + expect(dispatch).toHaveBeenCalledTimes(2); + expect(dispatch).toHaveBeenNthCalledWith(1, { type: LIST_TAGS_START }); + expect(dispatch).toHaveBeenNthCalledWith(2, { type: LIST_TAGS, tags }); + }); + + const assertErrorResult = async () => { + await listTags(buildShlinkApiClient, true)()(dispatch, getState); + + expect(buildShlinkApiClient).toHaveBeenCalledTimes(1); + expect(getState).toHaveBeenCalledTimes(1); + expect(dispatch).toHaveBeenCalledTimes(2); + expect(dispatch).toHaveBeenNthCalledWith(1, { type: LIST_TAGS_START }); + expect(dispatch).toHaveBeenNthCalledWith(2, { type: LIST_TAGS_ERROR }); + }; + + it('dispatches error when error occurs on list call', async () => { + listTagsMock.mockRejectedValue(new Error()); + buildShlinkApiClient.mockResolvedValue({ listTags: listTagsMock }); + + await assertErrorResult(); + + expect(listTagsMock).toHaveBeenCalledTimes(1); + }); + + it('dispatches error when error occurs on build call', async () => { + buildShlinkApiClient.mockRejectedValue(new Error()); + + await assertErrorResult(); + + expect(listTagsMock).not.toHaveBeenCalled(); + }); + }); +}); From fb94077260db7d813d7a1c2be6befdb0e1dc9333 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 19 May 2019 13:22:16 +0200 Subject: [PATCH 2/2] Created shortUrlTags reducer test --- src/short-urls/reducers/shortUrlTags.js | 3 +- test/short-urls/reducers/shortUrlTags.test.js | 104 ++++++++++++++++++ 2 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 test/short-urls/reducers/shortUrlTags.test.js diff --git a/src/short-urls/reducers/shortUrlTags.js b/src/short-urls/reducers/shortUrlTags.js index 3109f0f0..09dcdec4 100644 --- a/src/short-urls/reducers/shortUrlTags.js +++ b/src/short-urls/reducers/shortUrlTags.js @@ -1,6 +1,5 @@ import { createAction, handleActions } from 'redux-actions'; import PropTypes from 'prop-types'; -import { pick } from 'ramda'; /* eslint-disable padding-line-between-statements */ export const EDIT_SHORT_URL_TAGS_START = 'shlink/shortUrlTags/EDIT_SHORT_URL_TAGS_START'; @@ -27,7 +26,7 @@ const initialState = { export default handleActions({ [EDIT_SHORT_URL_TAGS_START]: (state) => ({ ...state, saving: true, error: false }), [EDIT_SHORT_URL_TAGS_ERROR]: (state) => ({ ...state, saving: false, error: true }), - [EDIT_SHORT_URL_TAGS]: (state, action) => ({ ...pick([ 'shortCode', 'tags' ], action), saving: false, error: false }), + [EDIT_SHORT_URL_TAGS]: (state, { shortCode, tags }) => ({ shortCode, tags, saving: false, error: false }), [RESET_EDIT_SHORT_URL_TAGS]: () => initialState, }, initialState); diff --git a/test/short-urls/reducers/shortUrlTags.test.js b/test/short-urls/reducers/shortUrlTags.test.js new file mode 100644 index 00000000..412803d3 --- /dev/null +++ b/test/short-urls/reducers/shortUrlTags.test.js @@ -0,0 +1,104 @@ +import reducer, { + EDIT_SHORT_URL_TAGS, + EDIT_SHORT_URL_TAGS_ERROR, + EDIT_SHORT_URL_TAGS_START, editShortUrlTags, + RESET_EDIT_SHORT_URL_TAGS, + resetShortUrlsTags, + SHORT_URL_TAGS_EDITED, + shortUrlTagsEdited, +} from '../../../src/short-urls/reducers/shortUrlTags'; + +describe('shortUrlTagsReducer', () => { + const tags = [ 'foo', 'bar', 'baz' ]; + const shortCode = 'abc123'; + + describe('reducer', () => { + it('returns loading on EDIT_SHORT_URL_TAGS_START', () => { + expect(reducer({}, { type: EDIT_SHORT_URL_TAGS_START })).toEqual({ + saving: true, + error: false, + }); + }); + + it('returns error on EDIT_SHORT_URL_TAGS_ERROR', () => { + expect(reducer({}, { type: EDIT_SHORT_URL_TAGS_ERROR })).toEqual({ + saving: false, + error: true, + }); + }); + + it('returns provided tags and shortCode on EDIT_SHORT_URL_TAGS', () => { + expect(reducer({}, { type: EDIT_SHORT_URL_TAGS, tags, shortCode })).toEqual({ + tags, + shortCode, + saving: false, + error: false, + }); + }); + + it('goes back to initial state on RESET_EDIT_SHORT_URL_TAGS', () => { + expect(reducer({}, { type: RESET_EDIT_SHORT_URL_TAGS })).toEqual({ + tags: [], + shortCode: null, + saving: false, + error: false, + }); + }); + }); + + describe('resetShortUrlsTags', () => + it('creates expected action', () => expect(resetShortUrlsTags()).toEqual({ type: RESET_EDIT_SHORT_URL_TAGS }))); + + describe('shortUrlTagsEdited', () => + it('creates expected action', () => expect(shortUrlTagsEdited(shortCode, tags)).toEqual({ + tags, + shortCode, + type: SHORT_URL_TAGS_EDITED, + }))); + + describe('editShortUrlTags', () => { + const updateShortUrlTags = jest.fn(); + const buildShlinkApiClient = jest.fn().mockResolvedValue({ updateShortUrlTags }); + const dispatch = jest.fn(); + + afterEach(() => { + updateShortUrlTags.mockReset(); + buildShlinkApiClient.mockClear(); + dispatch.mockReset(); + }); + + it('dispatches normalized tags on success', async () => { + const normalizedTags = [ 'bar', 'foo' ]; + + updateShortUrlTags.mockResolvedValue(normalizedTags); + + await editShortUrlTags(buildShlinkApiClient)(shortCode, tags)(dispatch); + + expect(buildShlinkApiClient).toHaveBeenCalledTimes(1); + expect(updateShortUrlTags).toHaveBeenCalledTimes(1); + expect(updateShortUrlTags).toHaveBeenCalledWith(shortCode, tags); + expect(dispatch).toHaveBeenCalledTimes(2); + expect(dispatch).toHaveBeenNthCalledWith(1, { type: EDIT_SHORT_URL_TAGS_START }); + expect(dispatch).toHaveBeenNthCalledWith(2, { type: EDIT_SHORT_URL_TAGS, tags: normalizedTags, shortCode }); + }); + + it('dispatches error on failure', async () => { + const error = new Error(); + + updateShortUrlTags.mockRejectedValue(error); + + try { + await editShortUrlTags(buildShlinkApiClient)(shortCode, tags)(dispatch); + } catch (e) { + expect(e).toBe(error); + } + + expect(buildShlinkApiClient).toHaveBeenCalledTimes(1); + expect(updateShortUrlTags).toHaveBeenCalledTimes(1); + expect(updateShortUrlTags).toHaveBeenCalledWith(shortCode, tags); + expect(dispatch).toHaveBeenCalledTimes(2); + expect(dispatch).toHaveBeenNthCalledWith(1, { type: EDIT_SHORT_URL_TAGS_START }); + expect(dispatch).toHaveBeenNthCalledWith(2, { type: EDIT_SHORT_URL_TAGS_ERROR }); + }); + }); +});