Commit 6a84a85a by Hugo Häggmark

Added reducerTester, reducer tests and tests

parent 2f47b225
import { reducerTester } from 'test/core/redux/reducerTester';
import { dataSourcesReducer, initialState } from './reducers';
import {
dataSourcesLoaded,
dataSourceLoaded,
setDataSourcesSearchQuery,
setDataSourcesLayoutMode,
dataSourceTypesLoad,
dataSourceTypesLoaded,
setDataSourceTypeSearchQuery,
dataSourceMetaLoaded,
setDataSourceName,
setIsDefault,
} from './actions';
import { getMockDataSources, getMockDataSource } from '../__mocks__/dataSourcesMocks';
import { LayoutModes } from 'app/core/components/LayoutSelector/LayoutSelector';
import { DataSourcesState } from 'app/types';
import { PluginMetaInfo } from '@grafana/ui';
const mockPlugin = () => ({
defaultNavUrl: 'defaultNavUrl',
enabled: true,
hasUpdate: true,
id: 'id',
info: {} as PluginMetaInfo,
latestVersion: 'latestVersion',
name: 'name',
pinned: true,
state: 'state',
type: 'type',
module: {},
});
describe('dataSourcesReducer', () => {
describe('when dataSourcesLoaded is dispatched', () => {
it('then state should be correct', () => {
const dataSources = getMockDataSources(0);
reducerTester()
.givenReducer(dataSourcesReducer, initialState)
.whenActionIsDispatched(dataSourcesLoaded(dataSources))
.thenStateShouldEqual({ ...initialState, hasFetched: true, dataSources, dataSourcesCount: 1 });
});
});
describe('when dataSourceLoaded is dispatched', () => {
it('then state should be correct', () => {
const dataSource = getMockDataSource();
reducerTester()
.givenReducer(dataSourcesReducer, initialState)
.whenActionIsDispatched(dataSourceLoaded(dataSource))
.thenStateShouldEqual({ ...initialState, dataSource });
});
});
describe('when setDataSourcesSearchQuery is dispatched', () => {
it('then state should be correct', () => {
reducerTester()
.givenReducer(dataSourcesReducer, initialState)
.whenActionIsDispatched(setDataSourcesSearchQuery('some query'))
.thenStateShouldEqual({ ...initialState, searchQuery: 'some query' });
});
});
describe('when setDataSourcesLayoutMode is dispatched', () => {
it('then state should be correct', () => {
const layoutMode: LayoutModes = LayoutModes.Grid;
reducerTester()
.givenReducer(dataSourcesReducer, initialState)
.whenActionIsDispatched(setDataSourcesLayoutMode(layoutMode))
.thenStateShouldEqual({ ...initialState, layoutMode: LayoutModes.Grid });
});
});
describe('when dataSourceTypesLoad is dispatched', () => {
it('then state should be correct', () => {
const state: DataSourcesState = { ...initialState, dataSourceTypes: [mockPlugin()] };
reducerTester()
.givenReducer(dataSourcesReducer, state)
.whenActionIsDispatched(dataSourceTypesLoad())
.thenStateShouldEqual({ ...initialState, dataSourceTypes: [], isLoadingDataSources: true });
});
});
describe('when dataSourceTypesLoaded is dispatched', () => {
it('then state should be correct', () => {
const dataSourceTypes = [mockPlugin()];
const state: DataSourcesState = { ...initialState, isLoadingDataSources: true };
reducerTester()
.givenReducer(dataSourcesReducer, state)
.whenActionIsDispatched(dataSourceTypesLoaded(dataSourceTypes))
.thenStateShouldEqual({ ...initialState, dataSourceTypes, isLoadingDataSources: false });
});
});
describe('when setDataSourceTypeSearchQuery is dispatched', () => {
it('then state should be correct', () => {
reducerTester()
.givenReducer(dataSourcesReducer, initialState)
.whenActionIsDispatched(setDataSourceTypeSearchQuery('type search query'))
.thenStateShouldEqual({ ...initialState, dataSourceTypeSearchQuery: 'type search query' });
});
});
describe('when dataSourceMetaLoaded is dispatched', () => {
it('then state should be correct', () => {
const dataSourceMeta = mockPlugin();
reducerTester()
.givenReducer(dataSourcesReducer, initialState)
.whenActionIsDispatched(dataSourceMetaLoaded(dataSourceMeta))
.thenStateShouldEqual({ ...initialState, dataSourceMeta });
});
});
describe('when setDataSourceName is dispatched', () => {
it('then state should be correct', () => {
reducerTester()
.givenReducer(dataSourcesReducer, initialState)
.whenActionIsDispatched(setDataSourceName('some name'))
.thenStateShouldEqual({ ...initialState, dataSource: { name: 'some name' } });
});
});
describe('when setIsDefault is dispatched', () => {
it('then state should be correct', () => {
reducerTester()
.givenReducer(dataSourcesReducer, initialState)
.whenActionIsDispatched(setIsDefault(true))
.thenStateShouldEqual({ ...initialState, dataSource: { isDefault: true } });
});
});
});
......@@ -15,7 +15,7 @@ import {
import { LayoutModes } from 'app/core/components/LayoutSelector/LayoutSelector';
import { reducerFactory } from 'app/core/redux';
const initialState: DataSourcesState = {
export const initialState: DataSourcesState = {
dataSources: [],
dataSource: {} as DataSourceSettings,
layoutMode: LayoutModes.List,
......
import { reducerFactory, actionCreatorFactory } from 'app/core/redux';
import { reducerTester } from './reducerTester';
interface DummyState {
data: string[];
}
const initialState: DummyState = {
data: [],
};
const dummyAction = actionCreatorFactory<string>('dummyAction').create();
const mutatingReducer = reducerFactory(initialState)
.addMapper({
filter: dummyAction,
mapper: (state, action) => {
state.data.push(action.payload);
return state;
},
})
.create();
const okReducer = reducerFactory(initialState)
.addMapper({
filter: dummyAction,
mapper: (state, action) => {
return {
...state,
data: state.data.concat(action.payload),
};
},
})
.create();
describe('reducerTester', () => {
describe('when reducer mutates state', () => {
it('then it should throw', () => {
expect(() => {
reducerTester()
.givenReducer(mutatingReducer, initialState)
.whenActionIsDispatched(dummyAction('some string'))
.thenStateShouldEqual({ ...initialState, data: ['some string'] });
}).toThrow();
});
});
describe('when reducer does not mutate state', () => {
it('then it should not throw', () => {
expect(() => {
reducerTester()
.givenReducer(okReducer, initialState)
.whenActionIsDispatched(dummyAction('some string'))
.thenStateShouldEqual({ ...initialState, data: ['some string'] });
}).not.toThrow();
});
});
});
import { Reducer } from 'redux';
import { ActionOf } from 'app/core/redux/actionCreatorFactory';
export interface Given<State> {
givenReducer: (reducer: Reducer<State, ActionOf<any>>, state: State) => When<State>;
}
export interface When<State> {
whenActionIsDispatched: (action: ActionOf<any>) => Then<State>;
}
export interface Then<State> {
thenStateShouldEqual: (state: State) => Then<State>;
}
interface ObjectType extends Object {
[key: string]: any;
}
const deepFreeze = <T>(obj: T): T => {
Object.freeze(obj);
const isNotException = (object: any, propertyName: any) =>
typeof object === 'function'
? propertyName !== 'caller' && propertyName !== 'callee' && propertyName !== 'arguments'
: true;
const hasOwnProp = Object.prototype.hasOwnProperty;
if (obj && obj instanceof Object) {
const object: ObjectType = obj;
Object.getOwnPropertyNames(object).forEach(propertyName => {
const objectProperty: any = object[propertyName];
if (
hasOwnProp.call(object, propertyName) &&
isNotException(object, propertyName) &&
objectProperty &&
(typeof objectProperty === 'object' || typeof objectProperty === 'function') &&
Object.isFrozen(objectProperty) === false
) {
deepFreeze(objectProperty);
}
});
}
return obj;
};
interface ReducerTester<State> extends Given<State>, When<State>, Then<State> {}
export const reducerTester = <State>(): Given<State> => {
let reducerUnderTest: Reducer<State, ActionOf<any>> = null;
let resultingState: State = null;
let initialState: State = null;
const givenReducer = (reducer: Reducer<State, ActionOf<any>>, state: State): When<State> => {
reducerUnderTest = reducer;
initialState = { ...state };
initialState = deepFreeze(initialState);
return instance;
};
const whenActionIsDispatched = (action: ActionOf<any>): Then<State> => {
resultingState = reducerUnderTest(initialState, action);
return instance;
};
const thenStateShouldEqual = (state: State): Then<State> => {
expect(state).toEqual(resultingState);
return instance;
};
const instance: ReducerTester<State> = { thenStateShouldEqual, givenReducer, whenActionIsDispatched };
return instance;
};
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