Unverified Commit a0ed1805 authored by Tamika Tannis's avatar Tamika Tannis Committed by GitHub

Redux unit tests: feedback, log, popularTables, search (#201)

* ducks unit tests: feedback, log, popularTables, search

* Lint fix

* Cleanup previous tests

* Syntax fixes

* Fix build errors

* Update tests based on feedback

* Update tests based on merge

* Lint

* Type fix
parent e2c39864
...@@ -13,10 +13,10 @@ module.exports = { ...@@ -13,10 +13,10 @@ module.exports = {
statements: 50, // 75 statements: 50, // 75
}, },
'./js/ducks': { './js/ducks': {
branches: 35, // 75 branches: 50, // 75
functions: 30, // 75 functions: 50, // 75
lines: 35, // 75 lines: 55, // 75
statements: 35, // 75 statements: 50, // 75
}, },
'./js/fixtures': { './js/fixtures': {
branches: 100, branches: 100,
......
...@@ -6,6 +6,12 @@ import { GetAllTags, GetAllTagsRequest, GetAllTagsResponse } from './types'; ...@@ -6,6 +6,12 @@ import { GetAllTags, GetAllTagsRequest, GetAllTagsResponse } from './types';
export function getAllTags(): GetAllTagsRequest { export function getAllTags(): GetAllTagsRequest {
return { type: GetAllTags.REQUEST }; return { type: GetAllTags.REQUEST };
}; };
export function getAllTagsFailure(): GetAllTagsResponse {
return { type: GetAllTags.FAILURE, payload: { tags: [] } };
};
export function getAllTagsSuccess(tags: Tag[]): GetAllTagsResponse {
return { type: GetAllTags.SUCCESS, payload: { tags } };
};
/* REDUCER */ /* REDUCER */
export interface AllTagsReducerState { export interface AllTagsReducerState {
......
...@@ -2,14 +2,15 @@ import { SagaIterator } from 'redux-saga'; ...@@ -2,14 +2,15 @@ import { SagaIterator } from 'redux-saga';
import { call, put, takeEvery } from 'redux-saga/effects'; import { call, put, takeEvery } from 'redux-saga/effects';
import { metadataAllTags } from './api/v0'; import { metadataAllTags } from './api/v0';
import { getAllTagsFailure, getAllTagsSuccess } from './reducer';
import { GetAllTags } from './types'; import { GetAllTags } from './types';
export function* getAllTagsWorker(): SagaIterator { export function* getAllTagsWorker(): SagaIterator {
try { try {
const tags = yield call(metadataAllTags); const tags = yield call(metadataAllTags);
yield put({ type: GetAllTags.SUCCESS, payload: { tags }}); yield put(getAllTagsSuccess(tags));
} catch (e) { } catch (e) {
yield put({ type: GetAllTags.FAILURE, payload: { tags: [] }}); yield put(getAllTagsFailure());
} }
} }
export function* getAllTagsWatcher(): SagaIterator { export function* getAllTagsWatcher(): SagaIterator {
......
...@@ -3,17 +3,34 @@ import * as matchers from 'redux-saga-test-plan/matchers'; ...@@ -3,17 +3,34 @@ import * as matchers from 'redux-saga-test-plan/matchers';
import { throwError } from 'redux-saga-test-plan/providers'; import { throwError } from 'redux-saga-test-plan/providers';
import { metadataAllTags } from '../api/v0'; import { metadataAllTags } from '../api/v0';
import reducer, { getAllTags, initialState, AllTagsReducerState } from '../reducer'; import reducer, {
getAllTags, getAllTagsFailure, getAllTagsSuccess,
initialState, AllTagsReducerState
} from '../reducer';
import { getAllTagsWatcher, getAllTagsWorker } from '../sagas'; import { getAllTagsWatcher, getAllTagsWorker } from '../sagas';
import { GetAllTags } from '../types'; import { GetAllTags } from '../types';
describe('allTags ducks', () => { describe('allTags ducks', () => {
describe('actions', () => { describe('actions', () => {
describe('getAllTags', () => { it('getAllTags - returns the action to get all tags', () => {
it('should return action of type GetAllTagsRequest', () => { const action = getAllTags();
expect(getAllTags()).toEqual({ type: GetAllTags.REQUEST }); expect(action.type).toEqual(GetAllTags.REQUEST);
}); });
})
it('getAllTagsFailure - returns the action to process failure', () => {
const action = getAllTagsFailure();
const { payload } = action;
expect(action.type).toBe(GetAllTags.FAILURE);
expect(payload.tags).toEqual([]);
});
it('getAllTagsSuccess - returns the action to process success', () => {
const expectedTags = [{tag_count: 2, tag_name: 'test'}, {tag_count: 1, tag_name: 'test2'}];
const action = getAllTagsSuccess(expectedTags);
const { payload } = action;
expect(action.type).toBe(GetAllTags.SUCCESS);
expect(payload.tags).toBe(expectedTags);
});
}); });
describe('reducer', () => { describe('reducer', () => {
...@@ -29,7 +46,7 @@ describe('allTags ducks', () => { ...@@ -29,7 +46,7 @@ describe('allTags ducks', () => {
}); });
it('should handle GetAllTags.REQUEST', () => { it('should handle GetAllTags.REQUEST', () => {
expect(reducer(testState, { type: GetAllTags.REQUEST })).toEqual({ expect(reducer(testState, getAllTags())).toEqual({
allTags: [], allTags: [],
isLoading: true, isLoading: true,
}); });
...@@ -37,14 +54,14 @@ describe('allTags ducks', () => { ...@@ -37,14 +54,14 @@ describe('allTags ducks', () => {
it('should handle GetAllTags.SUCCESS', () => { it('should handle GetAllTags.SUCCESS', () => {
const expectedTags = [{tag_count: 2, tag_name: 'test'}, {tag_count: 1, tag_name: 'test2'}]; const expectedTags = [{tag_count: 2, tag_name: 'test'}, {tag_count: 1, tag_name: 'test2'}];
expect(reducer(testState, { type: GetAllTags.SUCCESS, payload: { tags: expectedTags }})).toEqual({ expect(reducer(testState, getAllTagsSuccess(expectedTags))).toEqual({
allTags: expectedTags, allTags: expectedTags,
isLoading: false, isLoading: false,
}); });
}); });
it('should return the initialState if GetAllTags.FAILURE', () => { it('should return the initialState if GetAllTags.FAILURE', () => {
expect(reducer(testState, { type: GetAllTags.FAILURE, payload: { tags: [] } })).toEqual(initialState); expect(reducer(testState, getAllTagsFailure())).toEqual(initialState);
}); });
}); });
...@@ -53,7 +70,7 @@ describe('allTags ducks', () => { ...@@ -53,7 +70,7 @@ describe('allTags ducks', () => {
it('takes GetAllTags.REQUEST with getAllTagsWorker', () => { it('takes GetAllTags.REQUEST with getAllTagsWorker', () => {
testSaga(getAllTagsWatcher) testSaga(getAllTagsWatcher)
.next() .next()
.takeEveryEffect(GetAllTags.REQUEST, getAllTagsWorker); .takeEvery(GetAllTags.REQUEST, getAllTagsWorker);
}); });
}); });
...@@ -64,10 +81,7 @@ describe('allTags ducks', () => { ...@@ -64,10 +81,7 @@ describe('allTags ducks', () => {
.provide([ .provide([
[matchers.call.fn(metadataAllTags), mockTags], [matchers.call.fn(metadataAllTags), mockTags],
]) ])
.put({ .put(getAllTagsSuccess(mockTags))
type: GetAllTags.SUCCESS,
payload: { tags: mockTags }
})
.run(); .run();
}); });
...@@ -76,10 +90,7 @@ describe('allTags ducks', () => { ...@@ -76,10 +90,7 @@ describe('allTags ducks', () => {
.provide([ .provide([
[matchers.call.fn(metadataAllTags), throwError(new Error())], [matchers.call.fn(metadataAllTags), throwError(new Error())],
]) ])
.put({ .put(getAllTagsFailure())
type: GetAllTags.FAILURE,
payload: { tags: [] }
})
.run(); .run();
}); });
}); });
......
...@@ -6,6 +6,12 @@ import { GetAnnouncements, GetAnnouncementsRequest, GetAnnouncementsResponse } f ...@@ -6,6 +6,12 @@ import { GetAnnouncements, GetAnnouncementsRequest, GetAnnouncementsResponse } f
export function getAnnouncements(): GetAnnouncementsRequest { export function getAnnouncements(): GetAnnouncementsRequest {
return { type: GetAnnouncements.REQUEST }; return { type: GetAnnouncements.REQUEST };
}; };
export function getAnnouncementsFailure(): GetAnnouncementsResponse {
return { type: GetAnnouncements.FAILURE, payload: { posts: [] } };
};
export function getAnnouncementsSuccess(posts: AnnouncementPost[]): GetAnnouncementsResponse {
return { type: GetAnnouncements.SUCCESS, payload: { posts } };
};
/* REDUCER */ /* REDUCER */
export interface AnnouncementsReducerState { export interface AnnouncementsReducerState {
......
...@@ -2,14 +2,15 @@ import { SagaIterator } from 'redux-saga'; ...@@ -2,14 +2,15 @@ import { SagaIterator } from 'redux-saga';
import { call, put, takeEvery } from 'redux-saga/effects'; import { call, put, takeEvery } from 'redux-saga/effects';
import { announcementsGet } from './api/v0'; import { announcementsGet } from './api/v0';
import { getAnnouncementsFailure, getAnnouncementsSuccess } from './reducer';
import { GetAnnouncements } from './types'; import { GetAnnouncements } from './types';
export function* getAnnouncementsWorker(): SagaIterator { export function* getAnnouncementsWorker(): SagaIterator {
try { try {
const posts = yield call(announcementsGet); const posts = yield call(announcementsGet);
yield put({ type: GetAnnouncements.SUCCESS, payload: { posts } }); yield put(getAnnouncementsSuccess(posts));
} catch (e) { } catch (e) {
yield put({ type: GetAnnouncements.FAILURE, payload: { posts: [] } }); yield put(getAnnouncementsFailure());
} }
} }
export function* getAnnouncementsWatcher(): SagaIterator { export function* getAnnouncementsWatcher(): SagaIterator {
......
...@@ -3,17 +3,34 @@ import * as matchers from 'redux-saga-test-plan/matchers'; ...@@ -3,17 +3,34 @@ import * as matchers from 'redux-saga-test-plan/matchers';
import { throwError } from 'redux-saga-test-plan/providers'; import { throwError } from 'redux-saga-test-plan/providers';
import { announcementsGet } from '../api/v0'; import { announcementsGet } from '../api/v0';
import reducer, { getAnnouncements, initialState, AnnouncementsReducerState } from '../reducer'; import reducer, {
getAnnouncements, getAnnouncementsFailure, getAnnouncementsSuccess,
initialState, AnnouncementsReducerState
} from '../reducer';
import { getAnnouncementsWatcher, getAnnouncementsWorker } from '../sagas'; import { getAnnouncementsWatcher, getAnnouncementsWorker } from '../sagas';
import { GetAnnouncements } from '../types'; import { GetAnnouncements } from '../types';
describe('announcements ducks', () => { describe('announcements ducks', () => {
describe('actions', () => { describe('actions', () => {
describe('getAnnouncements', () => { it('getAnnouncements - returns the action to get all tags', () => {
it('should return action of type GetAnnouncementsRequest', () => { const action = getAnnouncements();
expect(getAnnouncements()).toEqual({ type: GetAnnouncements.REQUEST }); expect(action.type).toBe(GetAnnouncements.REQUEST);
}); });
})
it('getAnnouncementsFailure - returns the action to process failure', () => {
const action = getAnnouncementsFailure();
const { payload } = action;
expect(action.type).toBe(GetAnnouncements.FAILURE);
expect(payload.posts).toEqual([]);
});
it('getAllTagsSuccess - returns the action to process success', () => {
const expectedPosts = [{ date: '12/31/1999', title: 'Test', html_content: '<div>Test content</div>' }];
const action = getAnnouncementsSuccess(expectedPosts);
const { payload } = action;
expect(action.type).toBe(GetAnnouncements.SUCCESS);
expect(payload.posts).toBe(expectedPosts);
});
}); });
describe('reducer', () => { describe('reducer', () => {
...@@ -29,13 +46,13 @@ describe('announcements ducks', () => { ...@@ -29,13 +46,13 @@ describe('announcements ducks', () => {
it('should handle GetAnnouncements.SUCCESS', () => { it('should handle GetAnnouncements.SUCCESS', () => {
const expectedPosts = [{ date: '12/31/1999', title: 'Test', html_content: '<div>Test content</div>' }]; const expectedPosts = [{ date: '12/31/1999', title: 'Test', html_content: '<div>Test content</div>' }];
expect(reducer(testState, { type: GetAnnouncements.SUCCESS, payload: { posts: expectedPosts }})).toEqual({ expect(reducer(testState, getAnnouncementsSuccess(expectedPosts))).toEqual({
posts: expectedPosts, posts: expectedPosts,
}); });
}); });
it('should return the initialState if GetAnnouncements.FAILURE', () => { it('should return the initialState if GetAnnouncements.FAILURE', () => {
expect(reducer(testState, { type: GetAnnouncements.FAILURE, payload: { posts: [] } })).toEqual(initialState); expect(reducer(testState, getAnnouncementsFailure())).toEqual(initialState);
}); });
}); });
...@@ -44,7 +61,7 @@ describe('announcements ducks', () => { ...@@ -44,7 +61,7 @@ describe('announcements ducks', () => {
it('takes GetAnnouncements.REQUEST with getAnnouncementsWorker', () => { it('takes GetAnnouncements.REQUEST with getAnnouncementsWorker', () => {
testSaga(getAnnouncementsWatcher) testSaga(getAnnouncementsWatcher)
.next() .next()
.takeEveryEffect(GetAnnouncements.REQUEST, getAnnouncementsWorker); .takeEvery(GetAnnouncements.REQUEST, getAnnouncementsWorker);
}); });
}); });
...@@ -55,10 +72,7 @@ describe('announcements ducks', () => { ...@@ -55,10 +72,7 @@ describe('announcements ducks', () => {
.provide([ .provide([
[matchers.call.fn(announcementsGet), mockPosts], [matchers.call.fn(announcementsGet), mockPosts],
]) ])
.put({ .put(getAnnouncementsSuccess(mockPosts))
type: GetAnnouncements.SUCCESS,
payload: { posts: mockPosts }
})
.run(); .run();
}); });
...@@ -67,10 +81,7 @@ describe('announcements ducks', () => { ...@@ -67,10 +81,7 @@ describe('announcements ducks', () => {
.provide([ .provide([
[matchers.call.fn(announcementsGet), throwError(new Error())], [matchers.call.fn(announcementsGet), throwError(new Error())],
]) ])
.put({ .put(getAnnouncementsFailure())
type: GetAnnouncements.FAILURE,
payload: { posts: [] }
})
.run(); .run();
}); });
}); });
......
...@@ -3,6 +3,7 @@ import { Bookmark } from 'interfaces'; ...@@ -3,6 +3,7 @@ import { Bookmark } from 'interfaces';
import { import {
AddBookmark, AddBookmark,
AddBookmarkRequest, AddBookmarkRequest,
AddBookmarkResponse,
GetBookmarks, GetBookmarks,
GetBookmarksRequest, GetBookmarksRequest,
GetBookmarksResponse, GetBookmarksResponse,
...@@ -24,6 +25,13 @@ export function addBookmark(resourceKey: string, resourceType: string): AddBookm ...@@ -24,6 +25,13 @@ export function addBookmark(resourceKey: string, resourceType: string): AddBookm
type: AddBookmark.REQUEST, type: AddBookmark.REQUEST,
} }
} }
export function addBookmarkFailure(): AddBookmarkResponse {
return { type: AddBookmark.FAILURE };
}
export function addBookmarkSuccess(bookmarks: Bookmark[]): AddBookmarkResponse {
return { type: AddBookmark.SUCCESS, payload: { bookmarks } };
}
export function removeBookmark(resourceKey: string, resourceType: string): RemoveBookmarkRequest { export function removeBookmark(resourceKey: string, resourceType: string): RemoveBookmarkRequest {
return { return {
payload: { payload: {
...@@ -33,11 +41,25 @@ export function removeBookmark(resourceKey: string, resourceType: string): Remov ...@@ -33,11 +41,25 @@ export function removeBookmark(resourceKey: string, resourceType: string): Remov
type: RemoveBookmark.REQUEST, type: RemoveBookmark.REQUEST,
} }
} }
export function removeBookmarkFailure(): RemoveBookmarkResponse {
return { type: RemoveBookmark.FAILURE };
}
export function removeBookmarkSuccess(resourceKey: string, resourceType: string): RemoveBookmarkResponse {
return { type: RemoveBookmark.SUCCESS, payload: { resourceKey, resourceType } };
}
export function getBookmarks(): GetBookmarksRequest { export function getBookmarks(): GetBookmarksRequest {
return { return {
type: GetBookmarks.REQUEST, type: GetBookmarks.REQUEST,
} }
} }
export function getBookmarksFailure(): GetBookmarksResponse {
return { type: GetBookmarks.FAILURE, payload: { bookmarks: [] } };
}
export function getBookmarksSuccess(bookmarks: Bookmark[]): GetBookmarksResponse {
return { type: GetBookmarks.SUCCESS, payload: { bookmarks } };
}
export function getBookmarksForUser(userId: string): GetBookmarksForUserRequest { export function getBookmarksForUser(userId: string): GetBookmarksForUserRequest {
return { return {
payload: { payload: {
...@@ -46,6 +68,12 @@ export function getBookmarksForUser(userId: string): GetBookmarksForUserRequest ...@@ -46,6 +68,12 @@ export function getBookmarksForUser(userId: string): GetBookmarksForUserRequest
type: GetBookmarksForUser.REQUEST, type: GetBookmarksForUser.REQUEST,
} }
} }
export function getBookmarksForUserFailure(): GetBookmarksForUserResponse {
return { type: GetBookmarksForUser.FAILURE, payload: { bookmarks: [] } };
}
export function getBookmarksForUserSuccess(bookmarks: Bookmark[]): GetBookmarksForUserResponse {
return { type: GetBookmarksForUser.SUCCESS, payload: { bookmarks } };
}
/* REDUCER */ /* REDUCER */
export interface BookmarkReducerState { export interface BookmarkReducerState {
...@@ -62,12 +90,6 @@ export const initialState: BookmarkReducerState = { ...@@ -62,12 +90,6 @@ export const initialState: BookmarkReducerState = {
export default function reducer(state: BookmarkReducerState = initialState, action): BookmarkReducerState { export default function reducer(state: BookmarkReducerState = initialState, action): BookmarkReducerState {
switch(action.type) { switch(action.type) {
case RemoveBookmark.SUCCESS:
const { resourceKey } = (<RemoveBookmarkResponse>action).payload;
return {
...state,
myBookmarks: state.myBookmarks.filter((bookmark) => bookmark.key !== resourceKey)
};
case AddBookmark.SUCCESS: case AddBookmark.SUCCESS:
case GetBookmarks.SUCCESS: case GetBookmarks.SUCCESS:
return { return {
...@@ -75,24 +97,27 @@ export default function reducer(state: BookmarkReducerState = initialState, acti ...@@ -75,24 +97,27 @@ export default function reducer(state: BookmarkReducerState = initialState, acti
myBookmarks: (<GetBookmarksResponse>action).payload.bookmarks, myBookmarks: (<GetBookmarksResponse>action).payload.bookmarks,
myBookmarksIsLoaded: true, myBookmarksIsLoaded: true,
}; };
case GetBookmarksForUser.REQUEST: case GetBookmarksForUser.REQUEST:
return { return {
...state, ...state,
bookmarksForUser: [], bookmarksForUser: [],
}; };
case GetBookmarksForUser.SUCCESS: case GetBookmarksForUser.SUCCESS:
case GetBookmarksForUser.FAILURE:
return { return {
...state, ...state,
bookmarksForUser: (<GetBookmarksForUserResponse>action).payload.bookmarks, bookmarksForUser: (<GetBookmarksForUserResponse>action).payload.bookmarks,
}; };
case RemoveBookmark.SUCCESS:
const { resourceKey } = (<RemoveBookmarkResponse>action).payload;
return {
...state,
myBookmarks: state.myBookmarks.filter((bookmark) => bookmark.key !== resourceKey)
};
case AddBookmark.FAILURE: case AddBookmark.FAILURE:
case GetBookmarks.FAILURE: case GetBookmarks.FAILURE:
case GetBookmarksForUser.FAILURE:
case RemoveBookmark.FAILURE: case RemoveBookmark.FAILURE:
default: default:
return state; return state;
} }
} }
import { SagaIterator } from 'redux-saga'; import { SagaIterator } from 'redux-saga';
import { call, put, takeEvery } from 'redux-saga/effects'; import { call, put, takeEvery } from 'redux-saga/effects';
import {
addBookmark,
removeBookmark,
getBookmarks,
} from './api/v0';
import {
addBookmarkFailure,
addBookmarkSuccess,
getBookmarksFailure,
getBookmarksSuccess,
getBookmarksForUserFailure,
getBookmarksForUserSuccess,
removeBookmarkFailure,
removeBookmarkSuccess,
} from './reducer';
import { import {
AddBookmark, AddBookmark,
AddBookmarkRequest, AddBookmarkRequest,
...@@ -12,12 +29,6 @@ import { ...@@ -12,12 +29,6 @@ import {
RemoveBookmarkRequest RemoveBookmarkRequest
} from './types'; } from './types';
import {
addBookmark,
removeBookmark,
getBookmarks,
} from './api/v0';
export function* addBookmarkWorker(action: AddBookmarkRequest): SagaIterator { export function* addBookmarkWorker(action: AddBookmarkRequest): SagaIterator {
let response; let response;
const { resourceKey, resourceType } = action.payload; const { resourceKey, resourceType } = action.payload;
...@@ -27,9 +38,9 @@ export function* addBookmarkWorker(action: AddBookmarkRequest): SagaIterator { ...@@ -27,9 +38,9 @@ export function* addBookmarkWorker(action: AddBookmarkRequest): SagaIterator {
// TODO - Consider adding the newly bookmarked resource directly to local store. This would save a round trip. // TODO - Consider adding the newly bookmarked resource directly to local store. This would save a round trip.
response = yield call(getBookmarks); response = yield call(getBookmarks);
yield put({ type: AddBookmark.SUCCESS, payload: { bookmarks: response.bookmarks } }); yield put(addBookmarkSuccess(response.bookmarks));
} catch(e) { } catch(e) {
yield put({ type: AddBookmark.FAILURE, payload: { bookmarks: [] } }); yield put(addBookmarkFailure());
} }
} }
export function* addBookmarkWatcher(): SagaIterator { export function* addBookmarkWatcher(): SagaIterator {
...@@ -42,9 +53,9 @@ export function* removeBookmarkWorker(action: RemoveBookmarkRequest): SagaIterat ...@@ -42,9 +53,9 @@ export function* removeBookmarkWorker(action: RemoveBookmarkRequest): SagaIterat
const { resourceKey, resourceType } = action.payload; const { resourceKey, resourceType } = action.payload;
try { try {
response = yield call(removeBookmark, resourceKey, resourceType); response = yield call(removeBookmark, resourceKey, resourceType);
yield put({ type: RemoveBookmark.SUCCESS, payload: { resourceKey, resourceType }}); yield put(removeBookmarkSuccess(resourceKey, resourceType));
} catch(e) { } catch(e) {
yield put({ type: RemoveBookmark.FAILURE, payload: { resourceKey, resourceType } }); yield put(removeBookmarkFailure());
} }
} }
export function* removeBookmarkWatcher(): SagaIterator { export function* removeBookmarkWatcher(): SagaIterator {
...@@ -56,9 +67,9 @@ export function* getBookmarksWorker(action: GetBookmarksRequest): SagaIterator { ...@@ -56,9 +67,9 @@ export function* getBookmarksWorker(action: GetBookmarksRequest): SagaIterator {
let response; let response;
try { try {
response = yield call(getBookmarks); response = yield call(getBookmarks);
yield put({ type: GetBookmarks.SUCCESS, payload: { bookmarks: response.bookmarks } }); yield put(getBookmarksSuccess(response.bookmarks));
} catch(e) { } catch(e) {
yield put({ type: GetBookmarks.FAILURE, payload: { bookmarks: [] } }); yield put(getBookmarksFailure());
} }
} }
export function* getBookmarksWatcher(): SagaIterator { export function* getBookmarksWatcher(): SagaIterator {
...@@ -71,9 +82,9 @@ export function* getBookmarkForUserWorker(action: GetBookmarksForUserRequest): S ...@@ -71,9 +82,9 @@ export function* getBookmarkForUserWorker(action: GetBookmarksForUserRequest): S
const { userId } = action.payload; const { userId } = action.payload;
try { try {
response = yield call(getBookmarks, userId); response = yield call(getBookmarks, userId);
yield put({ type: GetBookmarksForUser.SUCCESS, payload: { userId, bookmarks: response.bookmarks } }); yield put(getBookmarksForUserSuccess(response.bookmarks));
} catch(e) { } catch(e) {
yield put({ type: GetBookmarksForUser.FAILURE, payload: { userId, bookmarks: [] } }); yield put(getBookmarksForUserFailure());
} }
} }
export function* getBookmarksForUserWatcher(): SagaIterator { export function* getBookmarksForUserWatcher(): SagaIterator {
......
...@@ -14,7 +14,7 @@ export interface AddBookmarkRequest { ...@@ -14,7 +14,7 @@ export interface AddBookmarkRequest {
} }
export interface AddBookmarkResponse { export interface AddBookmarkResponse {
type: AddBookmark.SUCCESS | AddBookmark.FAILURE; type: AddBookmark.SUCCESS | AddBookmark.FAILURE;
payload: { payload?: {
bookmarks: Bookmark[]; bookmarks: Bookmark[];
} }
} }
...@@ -33,7 +33,7 @@ export interface RemoveBookmarkRequest { ...@@ -33,7 +33,7 @@ export interface RemoveBookmarkRequest {
} }
export interface RemoveBookmarkResponse { export interface RemoveBookmarkResponse {
type: RemoveBookmark.SUCCESS | RemoveBookmark.FAILURE; type: RemoveBookmark.SUCCESS | RemoveBookmark.FAILURE;
payload: { payload?: {
resourceKey: string; resourceKey: string;
resourceType: string; resourceType: string;
}; };
......
import axios from 'axios';
import { feedbackSubmit } from '../v0';
jest.mock('axios');
describe('feedbackSubmit', () => {
let formData: FormData;
beforeAll(() => {
formData = new FormData();
feedbackSubmit(formData);
});
it('calls axios with expected payload', () => {
expect(axios).toHaveBeenCalledWith({
data: formData,
method: 'post',
url: '/api/mail/v0/feedback',
timeout: 5000,
headers: {'Content-Type': 'multipart/form-data' }
})
});
});
...@@ -2,7 +2,7 @@ import { SendingState } from 'interfaces'; ...@@ -2,7 +2,7 @@ import { SendingState } from 'interfaces';
import { import {
ResetFeedback, ResetFeedbackRequest, ResetFeedback, ResetFeedbackRequest,
SubmitFeedback, SubmitFeedbackRequest, SubmitFeedback, SubmitFeedbackRequest, SubmitFeedbackResponse
} from './types'; } from './types';
/* ACTIONS */ /* ACTIONS */
...@@ -14,6 +14,17 @@ export function submitFeedback(formData: FormData): SubmitFeedbackRequest { ...@@ -14,6 +14,17 @@ export function submitFeedback(formData: FormData): SubmitFeedbackRequest {
type: SubmitFeedback.REQUEST, type: SubmitFeedback.REQUEST,
}; };
}; };
export function submitFeedbackFailure(): SubmitFeedbackResponse {
return {
type: SubmitFeedback.FAILURE,
};
};
export function submitFeedbackSuccess(): SubmitFeedbackResponse {
return {
type: SubmitFeedback.SUCCESS,
};
};
export function resetFeedback(): ResetFeedbackRequest { export function resetFeedback(): ResetFeedbackRequest {
return { return {
type: ResetFeedback.REQUEST, type: ResetFeedback.REQUEST,
......
// TODO - Import 'delay' from 'redux-saga/effects' if we upgrade to 1.0 import { SagaIterator } from 'redux-saga';
import { delay, SagaIterator } from 'redux-saga'; import { call, delay, put, takeEvery } from 'redux-saga/effects';
import { call, put, takeEvery } from 'redux-saga/effects';
import { feedbackSubmit } from './api/v0'; import { feedbackSubmit } from './api/v0';
import { submitFeedbackFailure, submitFeedbackSuccess, resetFeedback } from './reducer';
import { SubmitFeedback, SubmitFeedbackRequest } from './types';
import { ResetFeedback, SubmitFeedback, SubmitFeedbackRequest } from './types'; export function* submitFeedbackWorker(action: SubmitFeedbackRequest): SagaIterator {
function* submitFeedbackWorker(action: SubmitFeedbackRequest): SagaIterator {
try { try {
yield call(feedbackSubmit, action.payload.data); yield call(feedbackSubmit, action.payload.data);
yield put({ type: SubmitFeedback.SUCCESS }); yield put(submitFeedbackSuccess());
// TODO - yield delay(2000) on redux-saga upgrade yield delay(2000);
yield call(delay, 2000); yield put(resetFeedback());
yield put({ type: ResetFeedback.REQUEST });
} catch(error) { } catch(error) {
yield put({ type: SubmitFeedback.FAILURE }); yield put(submitFeedbackFailure());
// TODO - yield delay(2000) on redux-saga upgrade yield delay(2000);
yield call(delay, 2000); yield put(resetFeedback());
yield put({ type: ResetFeedback.REQUEST });
} }
} }
......
import { expectSaga, testSaga } from 'redux-saga-test-plan';
import * as matchers from 'redux-saga-test-plan/matchers';
import { throwError } from 'redux-saga-test-plan/providers';
import { SendingState } from 'interfaces';
import { feedbackSubmit } from '../api/v0';
import reducer, {
submitFeedback,
submitFeedbackFailure,
submitFeedbackSuccess,
resetFeedback,
FeedbackReducerState
} from '../reducer';
import {
submitFeedbackWorker, submitFeedbackWatcher,
} from '../sagas';
import {
SubmitFeedback, SubmitFeedbackRequest,
ResetFeedback,
} from '../types';
describe('feedback ducks', () => {
let formData: FormData;
beforeAll(() => {
formData = new FormData();
const testData = { rating: 10, comment: 'This is a test' };
Object.keys(testData).forEach(key => formData.append(key, testData[key]));
});
describe('actions', () => {
it('submitFeedback - returns the action to submit feedback', () => {
const action = submitFeedback(formData);
const { payload } = action;
expect(action.type).toBe(SubmitFeedback.REQUEST);
expect(payload.data).toBe(formData);
});
it('submitFeedbackFailure - returns the action to process failure', () => {
const action = submitFeedbackFailure();
expect(action.type).toBe(SubmitFeedback.FAILURE);
});
it('submitFeedbackSuccess - returns the action to process success', () => {
const action = submitFeedbackSuccess();
expect(action.type).toBe(SubmitFeedback.SUCCESS);
});
it('resetFeedback - returns the action to reset feedback', () => {
const action = resetFeedback();
expect(action.type).toBe(ResetFeedback.REQUEST);
});
});
describe('reducer', () => {
let testState: FeedbackReducerState;
beforeAll(() => {
testState = { sendState: SendingState.IDLE };
});
it('should return the existing state if action is not handled', () => {
expect(reducer(testState, { type: 'INVALID.ACTION' })).toEqual(testState);
});
it('should handle SubmitFeedback.REQUEST', () => {
expect(reducer(testState, submitFeedback(formData))).toEqual({ sendState: SendingState.WAITING });
});
it('should handle SubmitFeedback.SUCCESS', () => {
expect(reducer(testState, submitFeedbackSuccess())).toEqual({ sendState: SendingState.COMPLETE });
});
it('should handle SubmitFeedback.FAILURE', () => {
expect(reducer(testState, submitFeedbackFailure())).toEqual({ sendState: SendingState.ERROR });
});
it('should handle ResetFeedback.REQUEST', () => {
expect(reducer(testState, resetFeedback())).toEqual({ sendState: SendingState.IDLE });
});
});
describe('sagas', () => {
let action: SubmitFeedbackRequest;
beforeAll(() => {
action = submitFeedback(formData);
});
describe('submitFeedbackWatcher', () => {
it('takes every SubmitFeedback.REQUEST with submitFeedbackWorker', () => {
testSaga(submitFeedbackWatcher)
.next()
.takeEvery(SubmitFeedback.REQUEST, submitFeedbackWorker);
});
});
describe('submitFeedbackWorker', () => {
it('executes submit feedback flow', () => {
testSaga(submitFeedbackWorker, action)
.next()
.call(feedbackSubmit, formData)
.next()
.put(submitFeedbackSuccess())
.next()
.delay(2000)
.next()
.put(resetFeedback())
.next()
.isDone();
});
it('handles request error', () => {
testSaga(submitFeedbackWorker, action)
.next()
.throw(new Error())
.put(submitFeedbackFailure())
.next()
.delay(2000)
.next()
.put(resetFeedback())
.next()
.isDone();
});
});
});
});
import axios from 'axios';
import { postActionLog, BASE_URL, ActionLogParams } from '../v0';
jest.mock('axios');
describe('postActionLog', () => {
let axiosMock;
let params: ActionLogParams;
beforeAll(() => {
axiosMock = jest.spyOn(axios, 'post').mockImplementation(() => Promise.resolve());
params = {};
postActionLog(params);
});
it('calls axios with expected parameters',() => {
expect(axiosMock).toHaveBeenCalledWith(BASE_URL, params);
});
afterAll(() => {
axiosMock.mockClear();
})
});
...@@ -9,7 +9,7 @@ export interface ActionLogParams { ...@@ -9,7 +9,7 @@ export interface ActionLogParams {
value?: string; value?: string;
} }
const BASE_URL = '/api/log/v0/log_event'; export const BASE_URL = '/api/log/v0/log_event';
/* TODO: Consider what we want to do on success/failure, if anything */ /* TODO: Consider what we want to do on success/failure, if anything */
export function postActionLog(params: ActionLogParams) { export function postActionLog(params: ActionLogParams) {
......
import axios, { AxiosResponse } from 'axios';
import globalState from 'fixtures/globalState';
import { TableResource } from 'interfaces';
import { metadataPopularTables, PopularTablesAPI } from '../v0';
jest.mock('axios');
describe('metadataPopularTables', () => {
let axiosMock;
let expectedTables: TableResource[];
let mockGetResponse: AxiosResponse<PopularTablesAPI>;
beforeAll(() => {
expectedTables = globalState.popularTables;
mockGetResponse = {
data: {
results: expectedTables,
msg: 'Success'
},
status: 200,
statusText: '',
headers: {},
config: {}
};
axiosMock = jest.spyOn(axios, 'get').mockImplementation(() => Promise.resolve(mockGetResponse));
});
it('resolves with array of table resources from response.data on success', async () => {
expect.assertions(1);
await metadataPopularTables().then(results => {
expect(results).toEqual(expectedTables);
});
});
});
...@@ -10,6 +10,12 @@ import { ...@@ -10,6 +10,12 @@ import {
export function getPopularTables(): GetPopularTablesRequest { export function getPopularTables(): GetPopularTablesRequest {
return { type: GetPopularTables.REQUEST }; return { type: GetPopularTables.REQUEST };
} }
export function getPopularTablesFailure(): GetPopularTablesResponse {
return { type: GetPopularTables.FAILURE, payload: { tables: [] } };
}
export function getPopularTablesSuccess(tables: TableResource[]): GetPopularTablesResponse {
return { type: GetPopularTables.SUCCESS, payload: { tables } };
}
/* REDUCER */ /* REDUCER */
export type PopularTablesReducerState = TableResource[]; export type PopularTablesReducerState = TableResource[];
......
...@@ -2,15 +2,15 @@ import { SagaIterator } from 'redux-saga'; ...@@ -2,15 +2,15 @@ import { SagaIterator } from 'redux-saga';
import { call, put, takeEvery } from 'redux-saga/effects'; import { call, put, takeEvery } from 'redux-saga/effects';
import { metadataPopularTables} from './api/v0'; import { metadataPopularTables} from './api/v0';
import { getPopularTablesFailure, getPopularTablesSuccess } from './reducer';
import { GetPopularTables } from './types'; import { GetPopularTables } from './types';
export function* getPopularTablesWorker(): SagaIterator { export function* getPopularTablesWorker(): SagaIterator {
try { try {
const popularTables = yield call(metadataPopularTables); const popularTables = yield call(metadataPopularTables);
yield put({ type: GetPopularTables.SUCCESS, payload: { tables: popularTables } }); yield put(getPopularTablesSuccess(popularTables));
} catch (e) { } catch (e) {
yield put({ type: GetPopularTables.FAILURE, payload: { tables: [] } }); yield put(getPopularTablesFailure());
} }
} }
......
import { expectSaga, testSaga } from 'redux-saga-test-plan';
import * as matchers from 'redux-saga-test-plan/matchers';
import { throwError } from 'redux-saga-test-plan/providers';
import { TableResource } from 'interfaces';
import globalState from 'fixtures/globalState';
import { metadataPopularTables } from '../api/v0';
import reducer, {
getPopularTables,
getPopularTablesFailure,
getPopularTablesSuccess,
PopularTablesReducerState
} from '../reducer';
import {
getPopularTablesWorker, getPopularTablesWatcher
} from '../sagas';
import {
GetPopularTables, GetPopularTablesRequest, GetPopularTablesResponse,
} from '../types';
describe('popularTables ducks', () => {
let expectedTables: TableResource[];
beforeAll(() => {
expectedTables = globalState.popularTables;
});
describe('actions', () => {
it('getPopularTables - returns the action to get popular tables', () => {
const action = getPopularTables();
expect(action.type).toBe(GetPopularTables.REQUEST);
});
it('getPopularTablesFailure - returns the action to process failure', () => {
const action = getPopularTablesFailure();
const { payload } = action;
expect(action.type).toBe(GetPopularTables.FAILURE);
expect(payload.tables).toEqual([]);
});
it('getPopularTablesSuccess - returns the action to process success', () => {
const action = getPopularTablesSuccess(expectedTables);
const { payload } = action;
expect(action.type).toBe(GetPopularTables.SUCCESS);
expect(payload.tables).toBe(expectedTables);
});
});
describe('reducer', () => {
let testState: PopularTablesReducerState;
beforeAll(() => {
testState = [];
});
it('should return the existing state if action is not handled', () => {
expect(reducer(testState, { type: 'INVALID.ACTION' })).toEqual(testState);
});
it('should handle GetPopularTables.SUCCESS', () => {
expect(reducer(testState, getPopularTablesSuccess(expectedTables))).toEqual(expectedTables);
});
it('should handle GetPopularTables.FAILURE', () => {
expect(reducer(testState, getPopularTablesFailure())).toEqual([]);
});
});
describe('sagas', () => {
describe('getPopularTablesWatcher', () => {
it('takes every GetPopularTables.REQUEST with getPopularTablesWorker', () => {
testSaga(getPopularTablesWatcher)
.next()
.takeEvery(GetPopularTables.REQUEST, getPopularTablesWorker);
});
});
describe('getPopularTablesWorker', () => {
it('executes flow for returning tables', () => {
testSaga(getPopularTablesWorker)
.next()
.call(metadataPopularTables)
.next(expectedTables)
.put(getPopularTablesSuccess(expectedTables))
.next()
.isDone();
});
it('handles request error', () => {
testSaga(getPopularTablesWorker)
.next()
.throw(new Error())
.put(getPopularTablesFailure())
.next()
.isDone();
});
});
});
});
import axios, { AxiosResponse } from 'axios';
import { DashboardSearchResults, TableSearchResults, UserSearchResults } from 'ducks/search/types';
import globalState from 'fixtures/globalState';
import { ResourceType, SearchAllOptions } from 'interfaces';
import { searchResource, searchResourceHelper, SearchAPI, BASE_URL } from '../v0';
jest.mock('axios');
describe('searchResource', () => {
let axiosMockGet;
let mockTableResponse: AxiosResponse<SearchAPI>;
beforeAll(() => {
mockTableResponse = {
data: {
msg: 'Success',
status_code: 200,
search_term: globalState.search.search_term,
tables: globalState.search.tables,
users: globalState.search.users,
},
status: 200,
statusText: '',
headers: {},
config: {},
};
axiosMockGet = jest.spyOn(axios, 'get').mockImplementation(() => Promise.resolve(mockTableResponse));
});
it('calls axios get with request for a resource', async () => {
const pageIndex = 0;
const resourceType = ResourceType.table;
const term = 'test';
await searchResource(pageIndex, resourceType, term);
expect(axiosMockGet).toHaveBeenCalledWith(`${BASE_URL}/${resourceType}?query=${term}&page_index=${pageIndex}`);
});
/*
TODO: Not set up to test this.
it('calls searchResourceHelper with resolved results', async () => {
await searchResource(0, ResourceType.table, 'test');
expect(searchResourceHelper).toHaveBeenCalledWith(mockTableResponse);
});
*/
describe('searchResourceHelper', () => {
it('returns expected object', () => {
expect(searchResourceHelper(mockTableResponse)).toEqual({
searchTerm: mockTableResponse.data.search_term,
tables: mockTableResponse.data.tables,
users: mockTableResponse.data.users,
});
});
});
});
...@@ -5,9 +5,9 @@ import { ResourceType } from 'interfaces'; ...@@ -5,9 +5,9 @@ import { ResourceType } from 'interfaces';
import { DashboardSearchResults, TableSearchResults, UserSearchResults } from '../types'; import { DashboardSearchResults, TableSearchResults, UserSearchResults } from '../types';
const BASE_URL = '/api/search/v0'; export const BASE_URL = '/api/search/v0';
interface SearchAPI { export interface SearchAPI {
msg: string; msg: string;
status_code: number; status_code: number;
search_term: string; search_term: string;
...@@ -16,20 +16,22 @@ interface SearchAPI { ...@@ -16,20 +16,22 @@ interface SearchAPI {
users?: UserSearchResults; users?: UserSearchResults;
}; };
export const searchResourceHelper = (response: AxiosResponse<SearchAPI>) => {
const { data } = response;
const ret = { searchTerm: data.search_term };
['tables', 'users'].forEach((key) => {
if (data[key]) {
ret[key] = data[key];
}
});
return ret;
};
export function searchResource(pageIndex: number, resource: ResourceType, term: string) { export function searchResource(pageIndex: number, resource: ResourceType, term: string) {
if (resource === ResourceType.dashboard || if (resource === ResourceType.dashboard ||
(resource === ResourceType.user && !AppConfig.indexUsers.enabled)) { (resource === ResourceType.user && !AppConfig.indexUsers.enabled)) {
return Promise.resolve({}); return Promise.resolve({});
} }
return axios.get(`${BASE_URL}/${resource}?query=${term}&page_index=${pageIndex}`) return axios.get(`${BASE_URL}/${resource}?query=${term}&page_index=${pageIndex}`)
.then((response: AxiosResponse<SearchAPI>) => { .then(searchResourceHelper);
const { data } = response;
const ret = { searchTerm: data.search_term };
['tables', 'users', 'dashboards'].forEach((key) => {
if (data[key]) {
ret[key] = data[key];
}
});
return ret;
});
}; };
import { ResourceType, SearchAllOptions } from 'interfaces'; import { ResourceType, SearchAllOptions } from 'interfaces';
import { import {
SearchResponsePayload,
SearchAll, SearchAll,
SearchAllRequest, SearchAllRequest,
SearchAllReset, SearchAllReset,
...@@ -22,7 +23,7 @@ export interface SearchReducerState { ...@@ -22,7 +23,7 @@ export interface SearchReducerState {
}; };
/* ACTIONS */ /* ACTIONS */
export function searchAll(term: string, options: SearchAllOptions = {}): SearchAllRequest { export function searchAll(term: string, options: SearchAllOptions): SearchAllRequest {
return { return {
payload: { payload: {
options, options,
...@@ -31,6 +32,13 @@ export function searchAll(term: string, options: SearchAllOptions = {}): SearchA ...@@ -31,6 +32,13 @@ export function searchAll(term: string, options: SearchAllOptions = {}): SearchA
type: SearchAll.REQUEST, type: SearchAll.REQUEST,
}; };
}; };
export function searchAllSuccess(searchResults: SearchResponsePayload): SearchAllResponse {
return { type: SearchAll.SUCCESS, payload: searchResults };
};
export function searchAllFailure(): SearchAllResponse {
return { type: SearchAll.FAILURE };
};
export function searchResource(resource: ResourceType, term: string, pageIndex: number): SearchResourceRequest { export function searchResource(resource: ResourceType, term: string, pageIndex: number): SearchResourceRequest {
return { return {
payload: { payload: {
...@@ -41,14 +49,21 @@ export function searchResource(resource: ResourceType, term: string, pageIndex: ...@@ -41,14 +49,21 @@ export function searchResource(resource: ResourceType, term: string, pageIndex:
type: SearchResource.REQUEST, type: SearchResource.REQUEST,
}; };
}; };
export function searchResourceSuccess(searchResults: SearchResponsePayload): SearchResourceResponse {
return { type: SearchResource.SUCCESS, payload: searchResults };
};
export function searchResourceFailure(): SearchResourceResponse {
return { type: SearchResource.FAILURE };
};
export function searchReset(): SearchAllReset { export function searchReset(): SearchAllReset {
return { return {
type: SearchAll.RESET, type: SearchAll.RESET,
}; };
} };
/* REDUCER */ /* REDUCER */
const initialState: SearchReducerState = { export const initialState: SearchReducerState = {
search_term: '', search_term: '',
isLoading: false, isLoading: false,
dashboards: { dashboards: {
......
...@@ -13,6 +13,11 @@ import { ...@@ -13,6 +13,11 @@ import {
} from './api/v0'; } from './api/v0';
import { ResourceType } from 'interfaces/Resources'; import { ResourceType } from 'interfaces/Resources';
import {
searchAllSuccess, searchAllFailure,
searchResourceSuccess, searchResourceFailure,
} from './reducer';
export function* searchAllWorker(action: SearchAllRequest): SagaIterator { export function* searchAllWorker(action: SearchAllRequest): SagaIterator {
const { options, term } = action.payload; const { options, term } = action.payload;
try { try {
...@@ -29,7 +34,7 @@ export function* searchAllWorker(action: SearchAllRequest): SagaIterator { ...@@ -29,7 +34,7 @@ export function* searchAllWorker(action: SearchAllRequest): SagaIterator {
}; };
yield put({ type: SearchAll.SUCCESS, payload: searchAllResponse }); yield put({ type: SearchAll.SUCCESS, payload: searchAllResponse });
} catch (e) { } catch (e) {
yield put({ type: SearchAll.FAILURE }); yield put(searchAllFailure());
} }
}; };
export function* searchAllWatcher(): SagaIterator { export function* searchAllWatcher(): SagaIterator {
...@@ -40,9 +45,9 @@ export function* searchResourceWorker(action: SearchResourceRequest): SagaIterat ...@@ -40,9 +45,9 @@ export function* searchResourceWorker(action: SearchResourceRequest): SagaIterat
const { pageIndex, resource, term } = action.payload; const { pageIndex, resource, term } = action.payload;
try { try {
const searchResults = yield call(searchResource, pageIndex, resource, term); const searchResults = yield call(searchResource, pageIndex, resource, term);
yield put({ type: SearchResource.SUCCESS, payload: searchResults }); yield put(searchResourceSuccess(searchResults));
} catch (e) { } catch (e) {
yield put({ type: SearchResource.FAILURE }); yield put(searchResourceFailure());
} }
} }
export function* searchResourceWatcher(): SagaIterator { export function* searchResourceWatcher(): SagaIterator {
......
import { expectSaga, testSaga } from 'redux-saga-test-plan';
import * as matchers from 'redux-saga-test-plan/matchers';
import { throwError } from 'redux-saga-test-plan/providers';
import { ResourceType } from 'interfaces';
import globalState from 'fixtures/globalState';
import { searchResource as srchResource } from '../api/v0';
import reducer, {
searchAll, searchAllSuccess, searchAllFailure,
searchResource, searchResourceSuccess, searchResourceFailure,
searchReset,
initialState, SearchReducerState,
} from '../reducer';
import {
searchAllWatcher, searchAllWorker,
searchResourceWatcher, searchResourceWorker
} from '../sagas';
import {
SearchAll, SearchAllRequest, SearchAllResponse,
SearchResource, SearchResourceRequest, SearchResourceResponse,
SearchResponsePayload,
} from '../types';
describe('search ducks', () => {
let expectedSearchResults: SearchResponsePayload;
beforeAll(() => {
expectedSearchResults = globalState.search;
});
describe('actions', () => {
it('searchAll - returns the action to search all resources', () => {
const term = 'test';
const options = {};
const action = searchAll(term, options);
const { payload } = action;
expect(action.type).toBe(SearchAll.REQUEST);
expect(payload.options).toBe(options);
expect(payload.term).toBe(term);
});
it('searchAllSuccess - returns the action to process the success', () => {
const action = searchAllSuccess(expectedSearchResults);
const { payload } = action;
expect(action.type).toBe(SearchAll.SUCCESS);
expect(payload).toBe(expectedSearchResults);
});
it('searchAllFailure - returns the action to process the failure', () => {
const action = searchAllFailure();
expect(action.type).toBe(SearchAll.FAILURE);
});
it('searchResource - returns the action to search all resources', () => {
const pageIndex = 0;
const resource = ResourceType.table;
const term = 'test';
const action = searchResource(resource, term, pageIndex);
const { payload } = action;
expect(action.type).toBe(SearchResource.REQUEST);
expect(payload.resource).toBe(resource);
expect(payload.term).toBe(term);
expect(payload.pageIndex).toBe(pageIndex);
});
it('searchResourceSuccess - returns the action to process the success', () => {
const action = searchResourceSuccess(expectedSearchResults);
const { payload } = action;
expect(action.type).toBe(SearchResource.SUCCESS);
expect(payload).toBe(expectedSearchResults);
});
it('searchResourceFailure - returns the action to process the failure', () => {
const action = searchResourceFailure();
expect(action.type).toBe(SearchResource.FAILURE);
});
it('searchReset - returns the action to reset search state', () => {
const action = searchReset();
expect(action.type).toBe(SearchAll.RESET);
});
});
describe('reducer', () => {
let testState: SearchReducerState;
beforeAll(() => {
testState = initialState;
});
it('should return the existing state if action is not handled', () => {
expect(reducer(testState, { type: 'INVALID.ACTION' })).toEqual(testState);
});
it('should handle SearchAll.REQUEST', () => {
const term = 'testSearch';
const options = {};
expect(reducer(testState, searchAll(term, options))).toEqual({
...testState,
search_term: term,
isLoading: true,
});
});
it('should handle SearchAll.SUCCESS', () => {
expect(reducer(testState, searchAllSuccess(expectedSearchResults))).toEqual({
...initialState,
...expectedSearchResults,
isLoading: false,
});
});
it('should handle SearchAll.FAILURE', () => {
expect(reducer(testState, searchAllFailure())).toEqual({
...initialState,
isLoading: false,
});
});
it('should handle SearchAll.RESET', () => {
expect(reducer(testState, searchReset())).toEqual(initialState);
});
it('should handle SearchResource.REQUEST', () => {
expect(reducer(testState, searchResource(ResourceType.table, 'test', 0))).toEqual({
...initialState,
isLoading: true,
});
});
it('should handle SearchResource.SUCCESS', () => {
expect(reducer(testState, searchResourceSuccess(expectedSearchResults))).toEqual({
...initialState,
...expectedSearchResults,
isLoading: false,
});
});
it('should handle SearchResource.FAILURE', () => {
expect(reducer(testState, searchResourceFailure())).toEqual({
...initialState,
isLoading: false,
});
});
});
describe('sagas', () => {
describe('searchAllWatcher', () => {
it('takes every SearchAll.REQUEST with searchAllWorker', () => {
testSaga(searchAllWatcher)
.next()
.takeEvery(SearchAll.REQUEST, searchAllWorker);
});
});
describe('searchAllWorker', () => {
/* TODO - Improve this test
it('executes flow for returning search results', () => {
const term = 'testSearch';
const options = {};
testSaga(searchAllWorker, searchAll(term, options))
.next()
.call(srchAll, options, term)
.next(expectedSearchResults)
.put(searchAllSuccess(expectedSearchResults))
.next()
.isDone();
});*/
it('handles request error', () => {
testSaga(searchAllWorker, searchAll('test', {}))
.next()
.throw(new Error())
.put(searchAllFailure())
.next()
.isDone();
});
});
describe('searchResourceWatcher', () => {
it('takes every SearchResource.REQUEST with searchResourceWorker', () => {
testSaga(searchResourceWatcher)
.next()
.takeEvery(SearchResource.REQUEST, searchResourceWorker);
});
});
describe('searchResourceWorker', () => {
it('executes flow for returning search results', () => {
const pageIndex = 0;
const resource = ResourceType.table;
const term = 'test';
testSaga(searchResourceWorker, searchResource(resource, term, pageIndex))
.next()
.call(srchResource, pageIndex, resource, term)
.next(expectedSearchResults)
.put(searchResourceSuccess(expectedSearchResults))
.next()
.isDone();
});
it('handles request error', () => {
testSaga(searchResourceWorker, searchResource(ResourceType.table, 'test', 0))
.next()
.throw(new Error())
.put(searchResourceFailure())
.next()
.isDone();
});
});
});
});
...@@ -16,6 +16,14 @@ export type DashboardSearchResults = SearchResults<DashboardResource>; ...@@ -16,6 +16,14 @@ export type DashboardSearchResults = SearchResults<DashboardResource>;
export type TableSearchResults = SearchResults<TableResource>; export type TableSearchResults = SearchResults<TableResource>;
export type UserSearchResults = SearchResults<UserResource>; export type UserSearchResults = SearchResults<UserResource>;
export interface SearchResponsePayload {
search_term: string;
isLoading: boolean;
dashboards: DashboardSearchResults;
tables: TableSearchResults;
users: UserSearchResults;
};
export enum SearchAll { export enum SearchAll {
REQUEST = 'amundsen/search/SEARCH_ALL_REQUEST', REQUEST = 'amundsen/search/SEARCH_ALL_REQUEST',
SUCCESS = 'amundsen/search/SEARCH_ALL_SUCCESS', SUCCESS = 'amundsen/search/SEARCH_ALL_SUCCESS',
...@@ -31,13 +39,7 @@ export interface SearchAllRequest { ...@@ -31,13 +39,7 @@ export interface SearchAllRequest {
}; };
export interface SearchAllResponse { export interface SearchAllResponse {
type: SearchAll.SUCCESS | SearchAll.FAILURE; type: SearchAll.SUCCESS | SearchAll.FAILURE;
payload?: { payload?: SearchResponsePayload;
search_term: string;
isLoading: boolean;
dashboards: DashboardSearchResults;
tables: TableSearchResults;
users: UserSearchResults;
};
}; };
export interface SearchAllReset { export interface SearchAllReset {
type: SearchAll.RESET; type: SearchAll.RESET;
...@@ -58,11 +60,5 @@ export interface SearchResourceRequest { ...@@ -58,11 +60,5 @@ export interface SearchResourceRequest {
}; };
export interface SearchResourceResponse { export interface SearchResourceResponse {
type: SearchResource.SUCCESS | SearchResource.FAILURE; type: SearchResource.SUCCESS | SearchResource.FAILURE;
payload?: { payload?: SearchResponsePayload;
search_term: string;
isLoading: boolean;
dashboards: DashboardSearchResults;
tables: TableSearchResults;
users: UserSearchResults;
};
}; };
import axios, { AxiosResponse, AxiosError } from 'axios'; import axios, { AxiosResponse, AxiosError } from 'axios';
import { Effect } from 'redux-saga';
import { import {
GetPreviewDataRequest, GetTableDataRequest, UpdateTableOwnerRequest, UpdateTagsRequest, GetPreviewDataRequest, GetTableDataRequest, UpdateTableOwnerRequest, UpdateTagsRequest,
......
...@@ -69,6 +69,23 @@ ...@@ -69,6 +69,23 @@
} }
} }
}, },
"@babel/runtime": {
"version": "7.4.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.5.tgz",
"integrity": "sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==",
"dev": true,
"requires": {
"regenerator-runtime": "^0.13.2"
},
"dependencies": {
"regenerator-runtime": {
"version": "0.13.2",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz",
"integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==",
"dev": true
}
}
},
"@babel/types": { "@babel/types": {
"version": "7.2.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.0.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.0.tgz",
...@@ -164,6 +181,59 @@ ...@@ -164,6 +181,59 @@
"tslib": "^1.8.1" "tslib": "^1.8.1"
} }
}, },
"@redux-saga/core": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@redux-saga/core/-/core-1.0.3.tgz",
"integrity": "sha512-zf8h5N0oTzaNeSMxOWH9GJMB9IRSM8JubDsrZVsvVltXjzFFSR8DNt7tbPoRJUK0hFfQB1it+bL+dEMWpD7wXA==",
"dev": true,
"requires": {
"@babel/runtime": "^7.0.0",
"@redux-saga/deferred": "^1.0.1",
"@redux-saga/delay-p": "^1.0.1",
"@redux-saga/is": "^1.0.2",
"@redux-saga/symbols": "^1.0.1",
"@redux-saga/types": "^1.0.2",
"redux": ">=0.10 <5",
"typescript-tuple": "^2.1.0"
}
},
"@redux-saga/deferred": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@redux-saga/deferred/-/deferred-1.0.1.tgz",
"integrity": "sha512-+gW5xQ93QXOOmRLAmX8x2Hx1HpbTG6CM6+HcdTSbJovh4uMWaGyeDECnqXSt8QqA/ja3s2nqYXLqXFKepIQ1hw==",
"dev": true
},
"@redux-saga/delay-p": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@redux-saga/delay-p/-/delay-p-1.0.1.tgz",
"integrity": "sha512-0SnNDyDLUyB4NThtptAwiprNOnbCNhoed/Rp5JwS7SB+a/AdWynVgg/E6BmjsggLFNr07KW0bzn05tsPRBuU7Q==",
"dev": true,
"requires": {
"@redux-saga/symbols": "^1.0.1"
}
},
"@redux-saga/is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@redux-saga/is/-/is-1.0.2.tgz",
"integrity": "sha512-WnaUOwYvPK2waWjzebT4uhL8zY76XNkzzpJ2EQJe8bN1tByvAjvT7MuJZTSshOhdHL5PsRO0MsH224XIXBJidQ==",
"dev": true,
"requires": {
"@redux-saga/symbols": "^1.0.1",
"@redux-saga/types": "^1.0.2"
}
},
"@redux-saga/symbols": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@redux-saga/symbols/-/symbols-1.0.1.tgz",
"integrity": "sha512-akKkzcVnb1RzJaZV2LQFbi51abvdICMuAKwwLoCjjxLbLAGIw9EJxk5ucNnWSSCEsoEQMeol5tkAcK+Xzuv1Bg==",
"dev": true
},
"@redux-saga/types": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@redux-saga/types/-/types-1.0.2.tgz",
"integrity": "sha512-8/qcMh15507AnXJ3lBeuhsdFwnWQqnp68EpUuHlYPixJ5vjVmls7/Jq48cnUlrZI8Jd9U1jkhfCl0gaT5KMgVw==",
"dev": true
},
"@types/anymatch": { "@types/anymatch": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.0.tgz", "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.0.tgz",
...@@ -5435,7 +5505,7 @@ ...@@ -5435,7 +5505,7 @@
}, },
"tar": { "tar": {
"version": "4.4.1", "version": "4.4.1",
"resolved": false, "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.1.tgz",
"integrity": "sha512-O+v1r9yN4tOsvl90p5HAP4AEqbYhx4036AGMm075fH9F8Qwi3oJ+v4u50FkT/KkvywNGtwkk0zRI+8eYm1X/xg==", "integrity": "sha512-O+v1r9yN4tOsvl90p5HAP4AEqbYhx4036AGMm075fH9F8Qwi3oJ+v4u50FkT/KkvywNGtwkk0zRI+8eYm1X/xg==",
"optional": true, "optional": true,
"requires": { "requires": {
...@@ -11803,7 +11873,7 @@ ...@@ -11803,7 +11873,7 @@
"react-avatar": { "react-avatar": {
"version": "2.5.1", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/react-avatar/-/react-avatar-2.5.1.tgz", "resolved": "https://registry.npmjs.org/react-avatar/-/react-avatar-2.5.1.tgz",
"integrity": "sha1-W76MHQpSWT1GCPs9hinamV7rcJU=", "integrity": "sha512-bwH5pWY6uxaKZt+IZBfD+SU3Dpy3FaKbmAzrOI4N8SATUPLXOdGaJHWUl6Vl8hHSwWSsoLh/m7xYHdnn0lofZw==",
"requires": { "requires": {
"babel-runtime": ">=5.0.0", "babel-runtime": ">=5.0.0",
"is-retina": "^1.0.3", "is-retina": "^1.0.3",
...@@ -12159,15 +12229,18 @@ ...@@ -12159,15 +12229,18 @@
} }
}, },
"redux-saga": { "redux-saga": {
"version": "0.16.2", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-0.16.2.tgz", "resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-1.0.3.tgz",
"integrity": "sha512-iIjKnRThI5sKPEASpUvySemjzwqwI13e3qP7oLub+FycCRDysLSAOwt958niZW6LhxfmS6Qm1BzbU70w/Koc4w==", "integrity": "sha512-ORo16st4cvrS+tpthMi6mJz1mMIuM//0EKFPAZjKTvDglRDebc7vTOJa0oeqT0iAZeYupusAstprFb0+3YDw/w==",
"dev": true "dev": true,
"requires": {
"@redux-saga/core": "^1.0.3"
}
}, },
"redux-saga-test-plan": { "redux-saga-test-plan": {
"version": "3.7.0", "version": "4.0.0-beta.3",
"resolved": "https://registry.npmjs.org/redux-saga-test-plan/-/redux-saga-test-plan-3.7.0.tgz", "resolved": "https://registry.npmjs.org/redux-saga-test-plan/-/redux-saga-test-plan-4.0.0-beta.3.tgz",
"integrity": "sha512-et9kCnME01kjoKXFfSk4FkozgOPPvllt9TlpL6A7ZYIS/WgoEFMLXk/UYww8KWXbmk5Qo2IF6xCc/IS1KmvP6A==", "integrity": "sha512-nOA2MsUHlp5JGlGhDo8IbVnKXIh1qLCE4vZE5rz1wvdXLMEk6F9QwfI1LbdaBT0XdrxjeI+JjeUAMZ5sgYfeTA==",
"dev": true, "dev": true,
"requires": { "requires": {
"core-js": "^2.4.1", "core-js": "^2.4.1",
...@@ -14727,11 +14800,35 @@ ...@@ -14727,11 +14800,35 @@
"dev": true "dev": true
}, },
"typescript": { "typescript": {
"version": "2.9.2", "version": "3.5.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.2.tgz",
"integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==", "integrity": "sha512-7KxJovlYhTX5RaRbUdkAXN1KUZ8PwWlTzQdHV6xNqvuFOs7+WBo10TQUqT19Q/Jz2hk5v9TQDIhyLhhJY4p5AA==",
"dev": true "dev": true
}, },
"typescript-compare": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/typescript-compare/-/typescript-compare-0.0.2.tgz",
"integrity": "sha512-8ja4j7pMHkfLJQO2/8tut7ub+J3Lw2S3061eJLFQcvs3tsmJKp8KG5NtpLn7KcY2w08edF74BSVN7qJS0U6oHA==",
"dev": true,
"requires": {
"typescript-logic": "^0.0.0"
}
},
"typescript-logic": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/typescript-logic/-/typescript-logic-0.0.0.tgz",
"integrity": "sha512-zXFars5LUkI3zP492ls0VskH3TtdeHCqu0i7/duGt60i5IGPIpAHE/DWo5FqJ6EjQ15YKXrt+AETjv60Dat34Q==",
"dev": true
},
"typescript-tuple": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/typescript-tuple/-/typescript-tuple-2.2.1.tgz",
"integrity": "sha512-Zcr0lbt8z5ZdEzERHAMAniTiIKerFCMgd7yjq1fPnDJ43et/k9twIFQMUYff9k5oXcsQ0WpvFcgzK2ZKASoW6Q==",
"dev": true,
"requires": {
"typescript-compare": "^0.0.2"
}
},
"ua-parser-js": { "ua-parser-js": {
"version": "0.7.17", "version": "0.7.17",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz",
......
...@@ -57,8 +57,8 @@ ...@@ -57,8 +57,8 @@
"postcss": "^7.0.6", "postcss": "^7.0.6",
"prettier": "^1.12.1", "prettier": "^1.12.1",
"redux-mock-store": "^1.5.3", "redux-mock-store": "^1.5.3",
"redux-saga": "^0.16.2", "redux-saga": "^1.0.0",
"redux-saga-test-plan": "^3.7.0", "redux-saga-test-plan": "4.0.0-beta.3",
"sass-loader": "^7.1.0", "sass-loader": "^7.1.0",
"style-loader": "^0.20.3", "style-loader": "^0.20.3",
"terser-webpack-plugin": "^1.1.0", "terser-webpack-plugin": "^1.1.0",
...@@ -71,7 +71,7 @@ ...@@ -71,7 +71,7 @@
"tslint-config-prettier": "^1.12.0", "tslint-config-prettier": "^1.12.0",
"tslint-eslint-rules": "^5.2.0", "tslint-eslint-rules": "^5.2.0",
"tslint-react": "^3.6.0", "tslint-react": "^3.6.0",
"typescript": "^2.8.3", "typescript": "^3.1.1",
"uglifyjs-webpack-plugin": "^1.1.0", "uglifyjs-webpack-plugin": "^1.1.0",
"webpack": "^4.19.0", "webpack": "^4.19.0",
"webpack-cli": "^3.1.1", "webpack-cli": "^3.1.1",
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
"import-name": [ "import-name": [
false false
], ],
"no-parameter-reassignment": false,
"prefer-array-literal": [ "prefer-array-literal": [
false false
] ]
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment