scratch-www/test/unit/redux/infinite-list.test.js
2021-05-10 10:22:18 -04:00

188 lines
7.4 KiB
JavaScript

/* global Promise */
import InfiniteList from '../../../src/redux/infinite-list';
const module = InfiniteList('test-key');
let initialState;
describe('Infinite List redux module', () => {
beforeEach(() => {
initialState = module.reducer(undefined, {}); // eslint-disable-line no-undefined
});
describe('reducer', () => {
test('module contains a reducer', () => {
expect(typeof module.reducer).toBe('function');
});
test('initial state', () => {
expect(initialState).toMatchObject({
loading: true,
error: null,
items: [],
moreToLoad: false
});
});
describe('LOADING', () => {
let action;
beforeEach(() => {
action = module.actions.loading();
initialState.loading = false;
initialState.items = [1, 2, 3];
initialState.error = new Error();
});
test('sets the loading state', () => {
const newState = module.reducer(initialState, action);
expect(newState.loading).toBe(true);
});
test('maintains any existing data', () => {
const newState = module.reducer(initialState, action);
expect(newState.items).toBe(initialState.items);
});
test('clears any existing error', () => {
const newState = module.reducer(initialState, action);
expect(newState.error).toBe(null);
});
});
describe('APPEND', () => {
let action;
beforeEach(() => {
action = module.actions.append([4, 5, 6], true);
});
test('appends the new items', () => {
initialState.items = [1, 2, 3];
const newState = module.reducer(initialState, action);
expect(newState.items).toEqual([1, 2, 3, 4, 5, 6]);
});
test('sets the moreToLoad state', () => {
initialState.moreToLoad = false;
const newState = module.reducer(initialState, action);
expect(newState.moreToLoad).toEqual(true);
});
test('clears any existing error and loading state', () => {
initialState.error = new Error();
initialState.loading = true;
const newState = module.reducer(initialState, action);
expect(newState.error).toBe(null);
expect(newState.error).toBe(null);
});
});
describe('REPLACE', () => {
let action;
beforeEach(() => {
action = module.actions.replace(2, 55);
});
test('replaces the given index with the new item', () => {
initialState.items = [8, 9, 10, 11];
const newState = module.reducer(initialState, action);
expect(newState.items).toEqual([8, 9, 55, 11]);
});
});
describe('REMOVE', () => {
let action;
beforeEach(() => {
action = module.actions.remove(2);
});
test('removes the given index', () => {
initialState.items = [8, 9, 10, 11];
const newState = module.reducer(initialState, action);
expect(newState.items).toEqual([8, 9, 11]);
});
});
describe('CREATE', () => {
let action;
beforeEach(() => {
action = module.actions.create(7);
});
test('prepends the given item', () => {
initialState.items = [8, 9, 10, 11];
const newState = module.reducer(initialState, action);
expect(newState.items).toEqual([7, 8, 9, 10, 11]);
});
});
describe('CLEAR', () => {
test('resets everything back to the initial state', () => {
const state = {
error: new Error(),
items: [1, 2, 3],
loading: 'something not initial',
moreToLoad: 'something not initial'
};
const newState = module.reducer(state, module.actions.clear());
expect(newState).toEqual(initialState);
});
});
describe('ERROR', () => {
let action;
let error = new Error();
beforeEach(() => {
action = module.actions.error(error);
});
test('sets the error state', () => {
const newState = module.reducer(initialState, action);
expect(newState.error).toBe(error);
});
test('resets loading to false', () => {
initialState.loading = true;
const newState = module.reducer(initialState, action);
expect(newState.loading).toBe(false);
});
test('maintains any existing data', () => {
initialState.items = [1, 2, 3];
const newState = module.reducer(initialState, action);
expect(newState.items).toEqual([1, 2, 3]);
});
});
});
describe('action creators', () => {
test('module contains actions creators', () => {
// The actual action creators are tested above in the reducer tests
for (let key in module.actions) {
expect(typeof module.actions[key]).toBe('function');
}
});
describe('loadMore', () => {
test('returns a thunk function, rather than a standard action object', () => {
expect(typeof module.actions.loadMore()).toBe('function');
});
test('calls loading and the fetcher', () => {
let dispatch = jest.fn();
let fetcher = jest.fn(() => new Promise(() => { })); // that never resolves
module.actions.loadMore(fetcher)(dispatch);
expect(dispatch).toHaveBeenCalledWith(module.actions.loading());
expect(fetcher).toHaveBeenCalled();
});
test('calls append with resolved result from fetcher', async () => {
let dispatch = jest.fn();
let fetcher = jest.fn(() => Promise.resolve({items: ['a', 'b'], moreToLoad: false}));
await module.actions.loadMore(fetcher)(dispatch);
expect(dispatch.mock.calls[1][0]) // the second call to dispatch, after LOADING
.toEqual(module.actions.append(['a', 'b'], false));
});
test('calls error with rejecting promise from fetcher', async () => {
let error = new Error();
let dispatch = jest.fn();
let fetcher = jest.fn(() => Promise.reject(error));
await module.actions.loadMore(fetcher)(dispatch);
expect(dispatch.mock.calls[1][0]) // the second call to dispatch, after LOADING
.toEqual(module.actions.error(error));
});
});
});
describe('selector', () => {
test('will return the slice of state defined by the key', () => {
const state = {
[module.key]: initialState
};
expect(module.selector(state)).toBe(initialState);
});
});
});