Browse Source

Improved dashboard page test

Torkel Ödegaard 6 years ago
parent
commit
a4841a72d9

+ 174 - 86
public/app/features/dashboard/containers/DashboardPage.test.tsx

@@ -10,117 +10,205 @@ jest.mock('sass/_variables.scss', () => ({
   panelVerticalPadding: 10,
   panelVerticalPadding: 10,
 }));
 }));
 
 
-jest.mock('app/features/dashboard/components/DashboardSettings/SettingsCtrl', () => ({
-}));
+jest.mock('app/features/dashboard/components/DashboardSettings/SettingsCtrl', () => ({}));
+
+interface ScenarioContext {
+  dashboard?: DashboardModel;
+  setDashboardProp: (overrides?: any, metaOverrides?: any) => void;
+  wrapper?: ShallowWrapper<Props, State, DashboardPage>;
+  mount: (propOverrides?: Partial<Props>) => void;
+  setup?: (fn: () => void) => void;
+}
 
 
-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,
-  };
-
-  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);
 }
 }
 
 
-describe('DashboardPage', () => {
-  let wrapper: ShallowWrapper<Props, State, DashboardPage>;
+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();
+    });
 
 
-  beforeEach(() => {
-    wrapper = setup();
+    scenarioFn(ctx);
   });
   });
+}
+
+describe('DashboardPage', () => {
+
+  dashboardPageScenario("Given initial state", (ctx) => {
+    ctx.setup(() => {
+      ctx.mount();
+    });
 
 
-  describe('Given dashboard has not loaded yet', () => {
     it('should render nothing', () => {
     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', () => {
     it('Should update title', () => {
       expect(document.title).toBe('My dashboard - Grafana');
       expect(document.title).toBe('My dashboard - Grafana');
     });
     });
 
 
-    it('After render dashboard', () => {
-      expect(wrapper).toMatchSnapshot();
+    it('Should render dashboard grid', () => {
+      expect(ctx.wrapper).toMatchSnapshot();
     });
     });
+  });
 
 
-    describe('Given user has scrolled down and goes into fullscreen edit', () => {
-      beforeEach(() => {
-        wrapper.setState({ scrollTop: 100 });
-        wrapper.setProps({
-          urlFullscreen: true,
-          urlEdit: true,
-          urlPanelId: '1',
-        });
+  dashboardPageScenario("where user goes into panel edit", (ctx) => {
+    ctx.setup(() => {
+      ctx.mount();
+      ctx.setDashboardProp();
+      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);
-      });
+    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 = wrapper.state();
-        expect(state.isEditing).toBe(true);
-        expect(state.isFullscreen).toBe(true);
-        expect(state.rememberScrollTop).toBe(100);
+    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',
       });
       });
+      ctx.wrapper.setProps({
+        urlFullscreen: false,
+        urlEdit: false,
+        urlPanelId: null,
+      });
+    });
 
 
-      describe('Given user goes back to dashboard', () => {
-        beforeEach(() => {
-          wrapper.setState({ scrollTop: 0 });
-          wrapper.setProps({
-            urlFullscreen: false,
-            urlEdit: false,
-            urlPanelId: null,
-          });
-        });
-
-        it('Should update model state normal state', () => {
-          expect(dashboard.meta.fullscreen).toBe(false);
-          expect(dashboard.meta.isEditing).toBe(false);
-        });
-
-        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);
-        });
+    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',
       });
       });
     });
     });
+
+    it('should render settings view', () => {
+      expect(ctx.wrapper).toMatchSnapshot();
+    });
+
+    it('should set animation state', () => {
+      expect(ctx.wrapper.state().isSettingsOpening).toBe(true);
+    });
+  });
+
+  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');
+    });
   });
   });
 });
 });

+ 1 - 1
public/app/features/dashboard/containers/DashboardPage.tsx

@@ -90,8 +90,8 @@ export class DashboardPage extends PureComponent<Props, State> {
   }
   }
 
 
   componentWillUnmount() {
   componentWillUnmount() {
+    console.log('unmount', this.props.cleanUpDashboard);
     if (this.props.dashboard) {
     if (this.props.dashboard) {
-      this.props.dashboard.destroy();
       this.props.cleanUpDashboard();
       this.props.cleanUpDashboard();
     }
     }
   }
   }

+ 329 - 3
public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap

@@ -1,8 +1,6 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 // 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
 <div
   className=""
   className=""
 >
 >
@@ -218,3 +216,331 @@ exports[`DashboardPage Given dashboard model After render dashboard 1`] = `
   </div>
   </div>
 </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>
+`;

+ 3 - 1
public/app/features/dashboard/state/reducers.ts

@@ -70,7 +70,9 @@ export const dashboardReducer = reducerFactory(initialState)
   .addMapper({
   .addMapper({
     filter: cleanUpDashboard,
     filter: cleanUpDashboard,
     mapper: (state, action) => {
     mapper: (state, action) => {
-      // tear down current dashboard
+
+      // Destroy current DashboardModel
+      // Very important as this removes all dashboard event listeners
       state.model.destroy();
       state.model.destroy();
 
 
       return {
       return {