mirror of
https://github.com/grafana/grafana.git
synced 2025-01-04 13:17:16 -06:00
Added reducerTester, reducer tests and tests
This commit is contained in:
parent
2f47b225a0
commit
6a84a85a80
137
public/app/features/datasources/state/reducers.test.ts
Normal file
137
public/app/features/datasources/state/reducers.test.ts
Normal file
@ -0,0 +1,137 @@
|
||||
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,
|
||||
|
58
public/test/core/redux/reducerTester.test.ts
Normal file
58
public/test/core/redux/reducerTester.test.ts
Normal file
@ -0,0 +1,58 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
79
public/test/core/redux/reducerTester.ts
Normal file
79
public/test/core/redux/reducerTester.ts
Normal file
@ -0,0 +1,79 @@
|
||||
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;
|
||||
};
|
Loading…
Reference in New Issue
Block a user