Improved dashboard page test

This commit is contained in:
Torkel Ödegaard
2019-02-06 21:04:18 +01:00
parent dd0afd0a0b
commit a4841a72d9
4 changed files with 505 additions and 89 deletions
@@ -10,117 +10,205 @@ jest.mock('sass/_variables.scss', () => ({
panelVerticalPadding: 10,
}));
jest.mock('app/features/dashboard/components/DashboardSettings/SettingsCtrl', () => ({
}));
jest.mock('app/features/dashboard/components/DashboardSettings/SettingsCtrl', () => ({}));
function setup(propOverrides?: Partial<Props>): ShallowWrapper<Props, State, DashboardPage> {
const props: Props = {
urlUid: '11',
urlSlug: 'my-dash',
$scope: {},
$injector: {},
routeInfo: DashboardRouteInfo.Normal,
urlEdit: false,
urlFullscreen: false,
initPhase: DashboardInitPhase.Completed,
isInitSlow: false,
initDashboard: jest.fn(),
updateLocation: jest.fn(),
notifyApp: jest.fn(),
dashboard: null,
cleanUpDashboard: cleanUpDashboard,
};
interface ScenarioContext {
dashboard?: DashboardModel;
setDashboardProp: (overrides?: any, metaOverrides?: any) => void;
wrapper?: ShallowWrapper<Props, State, DashboardPage>;
mount: (propOverrides?: Partial<Props>) => void;
setup?: (fn: () => void) => void;
}
Object.assign(props, propOverrides);
return shallow(<DashboardPage {...props} />);
function getTestDashboard(overrides?: any, metaOverrides?: any): DashboardModel {
const data = Object.assign({
title: 'My dashboard',
panels: [
{
id: 1,
type: 'graph',
title: 'My graph',
gridPos: { x: 0, y: 0, w: 1, h: 1 },
},
],
}, overrides);
const meta = Object.assign({ canSave: true, canEdit: true }, metaOverrides);
return new DashboardModel(data, meta);
}
function dashboardPageScenario(description, scenarioFn: (ctx: ScenarioContext) => void) {
describe(description, () => {
let setupFn: () => void;
const ctx: ScenarioContext = {
setup: fn => {
setupFn = fn;
},
setDashboardProp: (overrides?: any, metaOverrides?: any) => {
ctx.dashboard = getTestDashboard(overrides, metaOverrides);
ctx.wrapper.setProps({ dashboard: ctx.dashboard });
},
mount: (propOverrides?: Partial<Props>) => {
const props: Props = {
urlSlug: 'my-dash',
$scope: {},
urlUid: '11',
$injector: {},
routeInfo: DashboardRouteInfo.Normal,
urlEdit: false,
urlFullscreen: false,
initPhase: DashboardInitPhase.NotStarted,
isInitSlow: false,
initDashboard: jest.fn(),
updateLocation: jest.fn(),
notifyApp: jest.fn(),
cleanUpDashboard: cleanUpDashboard,
dashboard: null,
};
Object.assign(props, propOverrides);
ctx.dashboard = props.dashboard;
ctx.wrapper = shallow(<DashboardPage {...props} />);
}
};
beforeEach(() => {
setupFn();
});
scenarioFn(ctx);
});
}
describe('DashboardPage', () => {
let wrapper: ShallowWrapper<Props, State, DashboardPage>;
beforeEach(() => {
wrapper = setup();
});
dashboardPageScenario("Given initial state", (ctx) => {
ctx.setup(() => {
ctx.mount();
});
describe('Given dashboard has not loaded yet', () => {
it('should render nothing', () => {
expect(wrapper).toMatchSnapshot();
expect(ctx.wrapper).toMatchSnapshot();
});
});
describe('Given dashboard model', () => {
let dashboard: DashboardModel;
beforeEach(() => {
dashboard = new DashboardModel({
title: 'My dashboard',
panels: [
{
id: 1,
type: 'graph',
title: 'My graph',
gridPos: { x: 0, y: 0, w: 1, h: 1 }
}
]
}, {
canEdit: true,
canSave: true,
dashboardPageScenario("Dashboard is fetching slowly", (ctx) => {
ctx.setup(() => {
ctx.mount();
ctx.wrapper.setProps({
isInitSlow: true,
initPhase: DashboardInitPhase.Fetching,
});
wrapper.setProps({ dashboard, initPhase: DashboardInitPhase.Completed });
});
it('should render slow init state', () => {
expect(ctx.wrapper).toMatchSnapshot();
});
});
dashboardPageScenario("Dashboard init completed ", (ctx) => {
ctx.setup(() => {
ctx.mount();
ctx.setDashboardProp();
});
it('Should update title', () => {
expect(document.title).toBe('My dashboard - Grafana');
});
it('After render dashboard', () => {
expect(wrapper).toMatchSnapshot();
it('Should render dashboard grid', () => {
expect(ctx.wrapper).toMatchSnapshot();
});
});
dashboardPageScenario("where user goes into panel edit", (ctx) => {
ctx.setup(() => {
ctx.mount();
ctx.setDashboardProp();
ctx.wrapper.setProps({
urlFullscreen: true,
urlEdit: true,
urlPanelId: '1',
});
});
describe('Given user has scrolled down and goes into fullscreen edit', () => {
beforeEach(() => {
wrapper.setState({ scrollTop: 100 });
wrapper.setProps({
urlFullscreen: true,
urlEdit: true,
urlPanelId: '1',
});
it('Should update model state to fullscreen & edit', () => {
expect(ctx.dashboard.meta.fullscreen).toBe(true);
expect(ctx.dashboard.meta.isEditing).toBe(true);
});
it('Should update component state to fullscreen and edit', () => {
const state = ctx.wrapper.state();
expect(state.isEditing).toBe(true);
expect(state.isFullscreen).toBe(true);
expect(state.rememberScrollTop).toBe(100);
});
});
dashboardPageScenario("where user goes back to dashboard from panel edit", (ctx) => {
ctx.setup(() => {
ctx.mount();
ctx.setDashboardProp();
ctx.wrapper.setState({ scrollTop: 100 });
ctx.wrapper.setProps({
urlFullscreen: true,
urlEdit: true,
urlPanelId: '1',
});
it('Should update model state to fullscreen & edit', () => {
expect(dashboard.meta.fullscreen).toBe(true);
expect(dashboard.meta.isEditing).toBe(true);
ctx.wrapper.setProps({
urlFullscreen: false,
urlEdit: false,
urlPanelId: null,
});
});
it('Should update component state to fullscreen and edit', () => {
const state = wrapper.state();
expect(state.isEditing).toBe(true);
expect(state.isFullscreen).toBe(true);
expect(state.rememberScrollTop).toBe(100);
it('Should update model state normal state', () => {
expect(ctx.dashboard.meta.fullscreen).toBe(false);
expect(ctx.dashboard.meta.isEditing).toBe(false);
});
it('Should update component state to normal and restore scrollTop', () => {
const state = ctx.wrapper.state();
expect(state.isEditing).toBe(false);
expect(state.isFullscreen).toBe(false);
expect(state.scrollTop).toBe(100);
});
});
dashboardPageScenario("When dashboard has editview url state", (ctx) => {
ctx.setup(() => {
ctx.mount();
ctx.setDashboardProp();
ctx.wrapper.setProps({
editview: 'settings',
});
});
describe('Given user goes back to dashboard', () => {
beforeEach(() => {
wrapper.setState({ scrollTop: 0 });
wrapper.setProps({
urlFullscreen: false,
urlEdit: false,
urlPanelId: null,
});
});
it('should render settings view', () => {
expect(ctx.wrapper).toMatchSnapshot();
});
it('Should update model state normal state', () => {
expect(dashboard.meta.fullscreen).toBe(false);
expect(dashboard.meta.isEditing).toBe(false);
});
it('should set animation state', () => {
expect(ctx.wrapper.state().isSettingsOpening).toBe(true);
});
});
it('Should update component state to normal and restore scrollTop', () => {
const state = wrapper.state();
expect(state.isEditing).toBe(false);
expect(state.isFullscreen).toBe(false);
expect(state.scrollTop).toBe(100);
});
});
dashboardPageScenario("When adding panel", (ctx) => {
ctx.setup(() => {
ctx.mount();
ctx.setDashboardProp();
ctx.wrapper.setState({ scrollTop: 100 });
ctx.wrapper.instance().onAddPanel();
});
it('should set scrollTop to 0', () => {
expect(ctx.wrapper.state().scrollTop).toBe(0);
});
it('should add panel widget to dashboard panels', () => {
expect(ctx.dashboard.panels[0].type).toBe('add-panel');
});
});
});
@@ -90,8 +90,8 @@ export class DashboardPage extends PureComponent<Props, State> {
}
componentWillUnmount() {
console.log('unmount', this.props.cleanUpDashboard);
if (this.props.dashboard) {
this.props.dashboard.destroy();
this.props.cleanUpDashboard();
}
}
@@ -1,8 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`DashboardPage Given dashboard has not loaded yet should render nothing 1`] = `""`;
exports[`DashboardPage Given dashboard model After render dashboard 1`] = `
exports[`DashboardPage Dashboard init completed Should render dashboard grid 1`] = `
<div
className=""
>
@@ -218,3 +216,331 @@ exports[`DashboardPage Given dashboard model After render dashboard 1`] = `
</div>
</div>
`;
exports[`DashboardPage Dashboard is fetching slowly should render slow init state 1`] = `
<div
className="dashboard-loading"
>
<div
className="dashboard-loading__text"
>
<i
className="fa fa-spinner fa-spin"
/>
Fetching
</div>
</div>
`;
exports[`DashboardPage Given initial state should render nothing 1`] = `""`;
exports[`DashboardPage When dashboard has editview url state should render settings view 1`] = `
<div
className="dashboard-page--settings-opening"
>
<Connect(DashNav)
$injector={Object {}}
dashboard={
DashboardModel {
"annotations": Object {
"list": Array [
Object {
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard",
},
],
},
"autoUpdate": undefined,
"description": undefined,
"editable": true,
"events": Emitter {
"emitter": EventEmitter {
"_events": Object {},
"_eventsCount": 0,
},
},
"gnetId": null,
"graphTooltip": 0,
"id": null,
"links": Array [],
"meta": Object {
"canEdit": true,
"canMakeEditable": false,
"canSave": true,
"canShare": true,
"canStar": true,
"fullscreen": false,
"isEditing": false,
"showSettings": true,
},
"originalTemplating": Array [],
"originalTime": Object {
"from": "now-6h",
"to": "now",
},
"panels": Array [
PanelModel {
"cachedPluginOptions": Object {},
"datasource": null,
"events": Emitter {
"emitter": EventEmitter {
"_events": Object {},
"_eventsCount": 0,
},
},
"gridPos": Object {
"h": 1,
"w": 1,
"x": 0,
"y": 0,
},
"id": 1,
"targets": Array [
Object {
"refId": "A",
},
],
"title": "My graph",
"transparent": false,
"type": "graph",
},
],
"refresh": undefined,
"revision": undefined,
"schemaVersion": 17,
"snapshot": undefined,
"style": "dark",
"tags": Array [],
"templating": Object {
"list": Array [],
},
"time": Object {
"from": "now-6h",
"to": "now",
},
"timepicker": Object {},
"timezone": "",
"title": "My dashboard",
"uid": null,
"version": 0,
}
}
editview="settings"
isEditing={false}
isFullscreen={false}
onAddPanel={[Function]}
/>
<div
className="scroll-canvas scroll-canvas--dashboard"
>
<CustomScrollbar
autoHeightMax="100%"
autoHeightMin="100%"
autoHide={false}
autoHideDuration={200}
autoHideTimeout={200}
customClassName="custom-scrollbars"
hideTracksWhenNotNeeded={false}
scrollTop={0}
setScrollTop={[Function]}
>
<DashboardSettings
dashboard={
DashboardModel {
"annotations": Object {
"list": Array [
Object {
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard",
},
],
},
"autoUpdate": undefined,
"description": undefined,
"editable": true,
"events": Emitter {
"emitter": EventEmitter {
"_events": Object {},
"_eventsCount": 0,
},
},
"gnetId": null,
"graphTooltip": 0,
"id": null,
"links": Array [],
"meta": Object {
"canEdit": true,
"canMakeEditable": false,
"canSave": true,
"canShare": true,
"canStar": true,
"fullscreen": false,
"isEditing": false,
"showSettings": true,
},
"originalTemplating": Array [],
"originalTime": Object {
"from": "now-6h",
"to": "now",
},
"panels": Array [
PanelModel {
"cachedPluginOptions": Object {},
"datasource": null,
"events": Emitter {
"emitter": EventEmitter {
"_events": Object {},
"_eventsCount": 0,
},
},
"gridPos": Object {
"h": 1,
"w": 1,
"x": 0,
"y": 0,
},
"id": 1,
"targets": Array [
Object {
"refId": "A",
},
],
"title": "My graph",
"transparent": false,
"type": "graph",
},
],
"refresh": undefined,
"revision": undefined,
"schemaVersion": 17,
"snapshot": undefined,
"style": "dark",
"tags": Array [],
"templating": Object {
"list": Array [],
},
"time": Object {
"from": "now-6h",
"to": "now",
},
"timepicker": Object {},
"timezone": "",
"title": "My dashboard",
"uid": null,
"version": 0,
}
}
/>
<div
className="dashboard-container"
>
<DashboardGrid
dashboard={
DashboardModel {
"annotations": Object {
"list": Array [
Object {
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard",
},
],
},
"autoUpdate": undefined,
"description": undefined,
"editable": true,
"events": Emitter {
"emitter": EventEmitter {
"_events": Object {},
"_eventsCount": 0,
},
},
"gnetId": null,
"graphTooltip": 0,
"id": null,
"links": Array [],
"meta": Object {
"canEdit": true,
"canMakeEditable": false,
"canSave": true,
"canShare": true,
"canStar": true,
"fullscreen": false,
"isEditing": false,
"showSettings": true,
},
"originalTemplating": Array [],
"originalTime": Object {
"from": "now-6h",
"to": "now",
},
"panels": Array [
PanelModel {
"cachedPluginOptions": Object {},
"datasource": null,
"events": Emitter {
"emitter": EventEmitter {
"_events": Object {},
"_eventsCount": 0,
},
},
"gridPos": Object {
"h": 1,
"w": 1,
"x": 0,
"y": 0,
},
"id": 1,
"targets": Array [
Object {
"refId": "A",
},
],
"title": "My graph",
"transparent": false,
"type": "graph",
},
],
"refresh": undefined,
"revision": undefined,
"schemaVersion": 17,
"snapshot": undefined,
"style": "dark",
"tags": Array [],
"templating": Object {
"list": Array [],
},
"time": Object {
"from": "now-6h",
"to": "now",
},
"timepicker": Object {},
"timezone": "",
"title": "My dashboard",
"uid": null,
"version": 0,
}
}
isEditing={false}
isFullscreen={false}
/>
</div>
</CustomScrollbar>
</div>
</div>
`;
@@ -70,7 +70,9 @@ export const dashboardReducer = reducerFactory(initialState)
.addMapper({
filter: cleanUpDashboard,
mapper: (state, action) => {
// tear down current dashboard
// Destroy current DashboardModel
// Very important as this removes all dashboard event listeners
state.model.destroy();
return {