text: import MockAdapter from 'axios-mock-adapter';
  import testAction from 'helpers/vuex_action_helper';
  import { backoffMockImplementation } from 'jest/helpers/backoff_helper';
  import Tracking from '~/tracking';
  import axios from '~/lib/utils/axios_utils';
  import statusCodes from '~/lib/utils/http_status';
  import * as commonUtils from '~/lib/utils/common_utils';
  import { deprecatedCreateFlash as createFlash } from '~/flash';
  import { defaultTimeRange } from '~/vue_shared/constants';
  import * as getters from '~/monitoring/stores/getters';
  import { ENVIRONMENT_AVAILABLE_STATE } from '~/monitoring/constants';
  
  import { createStore } from '~/monitoring/stores';
  import * as types from '~/monitoring/stores/mutation_types';
  import {
    setGettingStartedEmptyState,
    setInitialState,
    setExpandedPanel,
    clearExpandedPanel,
    filterEnvironments,
    fetchData,
    fetchDashboard,
    receiveMetricsDashboardSuccess,
    fetchDashboardData,
    fetchPrometheusMetric,
    fetchDeploymentsData,
    fetchEnvironmentsData,
    fetchAnnotations,
    fetchDashboardValidationWarnings,
    toggleStarredValue,
    duplicateSystemDashboard,
    updateVariablesAndFetchData,
    fetchVariableMetricLabelValues,
    fetchPanelPreview,
  } from '~/monitoring/stores/actions';
  import {
    gqClient,
    parseEnvironmentsResponse,
    parseAnnotationsResponse,
  } from '~/monitoring/stores/utils';
  import getEnvironments from '~/monitoring/queries/getEnvironments.query.graphql';
  import getAnnotations from '~/monitoring/queries/getAnnotations.query.graphql';
  import getDashboardValidationWarnings from '~/monitoring/queries/getDashboardValidationWarnings.query.graphql';
  import storeState from '~/monitoring/stores/state';
  import {
    deploymentData,
    environmentData,
    annotationsData,
    dashboardGitResponse,
    mockDashboardsErrorResponse,
  } from '../mock_data';
  import {
    metricsDashboardResponse,
    metricsDashboardViewModel,
    metricsDashboardPanelCount,
  } from '../fixture_data';
  
  jest.mock('~/flash');
  
  describe('Monitoring store actions', () => {
    const { convertObjectPropsToCamelCase } = commonUtils;
  
    let mock;
    let store;
    let state;
  
    let dispatch;
    let commit;
  
    beforeEach(() => {
      store = createStore({ getters });
      state = store.state.monitoringDashboard;
      mock = new MockAdapter(axios);
  
      commit = jest.fn();
      dispatch = jest.fn();
  
      jest.spyOn(commonUtils, 'backOff').mockImplementation(backoffMockImplementation);
    });
  
    afterEach(() => {
      mock.reset();
  
      commonUtils.backOff.mockReset();
      createFlash.mockReset();
    });
  
    // Setup
  
    describe('setGettingStartedEmptyState', () => {
      it('should commit SET_GETTING_STARTED_EMPTY_STATE mutation', done => {
        testAction(
          setGettingStartedEmptyState,
          null,
          state,
          [
            {
              type: types.SET_GETTING_STARTED_EMPTY_STATE,
            },
          ],
          [],
          done,
        );
      });
    });
  
    describe('setInitialState', () => {
      it('should commit SET_INITIAL_STATE mutation', done => {
        testAction(
          setInitialState,
          {
            currentDashboard: '.gitlab/dashboards/dashboard.yml',
            deploymentsEndpoint: 'deployments.json',
          },
          state,
          [
            {
              type: types.SET_INITIAL_STATE,
              payload: {
                currentDashboard: '.gitlab/dashboards/dashboard.yml',
                deploymentsEndpoint: 'deployments.json',
              },
            },
          ],
          [],
          done,
        );
      });
    });
  
    describe('setExpandedPanel', () => {
      it('Sets a panel as expanded', () => {
        const group = 'group_1';
        const panel = { title: 'A Panel' };
  
        return testAction(
          setExpandedPanel,
          { group, panel },
          state,
          [{ type: types.SET_EXPANDED_PANEL, payload: { group, panel } }],
          [],
        );
      });
    });
  
    describe('clearExpandedPanel', () => {
      it('Clears a panel as expanded', () => {
        return testAction(
          clearExpandedPanel,
          undefined,
          state,
          [{ type: types.SET_EXPANDED_PANEL, payload: { group: null, panel: null } }],
          [],
        );
      });
    });
  
    // All Data
  
    describe('fetchData', () => {
      it('dispatches fetchEnvironmentsData and fetchEnvironmentsData', () => {
        return testAction(
          fetchData,
          null,
          state,
          [],
          [
            { type: 'fetchEnvironmentsData' },
            { type: 'fetchDashboard' },
            { type: 'fetchAnnotations' },
          ],
        );
      });
  
      it('dispatches when feature metricsDashboardAnnotations is on', () => {
        const origGon = window.gon;
        window.gon = { features: { metricsDashboardAnnotations: true } };
  
        return testAction(
          fetchData,
          null,
          state,
          [],
          [
            { type: 'fetchEnvironmentsData' },
            { type: 'fetchDashboard' },
            { type: 'fetchAnnotations' },
          ],
        ).then(() => {
          window.gon = origGon;
        });
      });
    });
  
    // Metrics dashboard
  
    describe('fetchDashboard', () => {
      const response = metricsDashboardResponse;
      beforeEach(() => {
        state.dashboardEndpoint = '/dashboard';
      });
  
      it('on success, dispatches receive and success actions, then fetches dashboard warnings', () => {
        document.body.dataset.page = 'projects:environments:metrics';
        mock.onGet(state.dashboardEndpoint).reply(200, response);
  
        return testAction(
          fetchDashboard,
          null,
          state,
          [],
          [
            { type: 'requestMetricsDashboard' },
            {
              type: 'receiveMetricsDashboardSuccess',
              payload: { response },
            },
            { type: 'fetchDashboardValidationWarnings' },
          ],
        );
      });
  
      describe('on failure', () => {
        let result;
        beforeEach(() => {
          const params = {};
          const localGetters = {
            fullDashboardPath: store.getters['monitoringDashboard/fullDashboardPath'],
          };
          result = () => {
            mock.onGet(state.dashboardEndpoint).replyOnce(500, mockDashboardsErrorResponse);
            return fetchDashboard({ state, commit, dispatch, getters: localGetters }, params);
          };
        });
  
        it('dispatches a failure', done => {
          result()
            .then(() => {
              expect(commit).toHaveBeenCalledWith(
                types.SET_ALL_DASHBOARDS,
                mockDashboardsErrorResponse.all_dashboards,
              );
              expect(dispatch).toHaveBeenCalledWith(
                'receiveMetricsDashboardFailure',
                new Error('Request failed with status code 500'),
              );
              expect(createFlash).toHaveBeenCalled();
              done();
            })
            .catch(done.fail);
        });
  
        it('dispatches a failure action when a message is returned', done => {
          result()
            .then(() => {
              expect(dispatch).toHaveBeenCalledWith(
                'receiveMetricsDashboardFailure',
                new Error('Request failed with status code 500'),
              );
              expect(createFlash).toHaveBeenCalledWith(
                expect.stringContaining(mockDashboardsErrorResponse.message),
              );
              done();
            })
            .catch(done.fail);
        });
  
        it('does not show a flash error when showErrorBanner is disabled', done => {
          state.showErrorBanner = false;
  
          result()
            .then(() => {
              expect(dispatch).toHaveBeenCalledWith(
                'receiveMetricsDashboardFailure',
                new Error('Request failed with status code 500'),
              );
              expect(createFlash).not.toHaveBeenCalled();
              done();
            })
            .catch(done.fail);
        });
      });
    });
  
    describe('receiveMetricsDashboardSuccess', () => {
      it('stores groups', () => {
        const response = metricsDashboardResponse;
        receiveMetricsDashboardSuccess({ state, commit, dispatch }, { response });
        expect(commit).toHaveBeenCalledWith(
          types.RECEIVE_METRICS_DASHBOARD_SUCCESS,
  
          metricsDashboardResponse.dashboard,
        );
        expect(dispatch).toHaveBeenCalledWith('fetchDashboardData');
      });
  
      it('sets the dashboards loaded from the repository', () => {
        const params = {};
        const response = metricsDashboardResponse;
        response.all_dashboards = dashboardGitResponse;
        receiveMetricsDashboardSuccess(
          {
            state,
            commit,
            dispatch,
          },
          {
            response,
            params,
          },
        );
        expect(commit).toHaveBeenCalledWith(types.SET_ALL_DASHBOARDS, dashboardGitResponse);
      });
    });
  
    // Metrics
  
    describe('fetchDashboardData', () => {
      beforeEach(() => {
        jest.spyOn(Tracking, 'event');
  
        state.timeRange = defaultTimeRange;
      });
  
      it('commits empty state when state.groups is empty', done => {
        const localGetters = {
          metricsWithData: () => [],
        };
        fetchDashboardData({ state, commit, dispatch, getters: localGetters })
          .then(() => {
            expect(Tracking.event).toHaveBeenCalledWith(
              document.body.dataset.page,
              'dashboard_fetch',
              {
                label: 'custom_metrics_dashboard',
                property: 'count',
                value: 0,
              },
            );
            expect(dispatch).toHaveBeenCalledTimes(2);
            expect(dispatch).toHaveBeenCalledWith('fetchDeploymentsData');
            expect(dispatch).toHaveBeenCalledWith('fetchVariableMetricLabelValues', {
              defaultQueryParams: {
                start_time: expect.any(String),
                end_time: expect.any(String),
                step: expect.any(Number),
              },
            });
  
            expect(createFlash).not.toHaveBeenCalled();
            done();
          })
          .catch(done.fail);
      });
  
      it('dispatches fetchPrometheusMetric for each panel query', done => {
        state.dashboard.panelGroups = convertObjectPropsToCamelCase(
          metricsDashboardResponse.dashboard.panel_groups,
        );
  
        const [metric] = state.dashboard.panelGroups[0].panels[0].metrics;
        const localGetters = {
          metricsWithData: () => [metric.id],
        };
  
        fetchDashboardData({ state, commit, dispatch, getters: localGetters })
          .then(() => {
            expect(dispatch).toHaveBeenCalledWith('fetchPrometheusMetric', {
              metric,
              defaultQueryParams: {
                start_time: expect.any(String),
                end_time: expect.any(String),
                step: expect.any(Number),
              },
            });
  
            expect(Tracking.event).toHaveBeenCalledWith(
              document.body.dataset.page,
              'dashboard_fetch',
              {
                label: 'custom_metrics_dashboard',
                property: 'count',
                value: 1,
              },
            );
  
            done();
          })
          .catch(done.fail);
        done();
      });
  
      it('dispatches fetchPrometheusMetric for each panel query, handles an error', done => {
        state.dashboard.panelGroups = metricsDashboardViewModel.panelGroups;
        const metric = state.dashboard.panelGroups[0].panels[0].metrics[0];
  
        dispatch.mockResolvedValueOnce(); // fetchDeploymentsData
        dispatch.mockResolvedValueOnce(); // fetchVariableMetricLabelValues
        // Mock having one out of four metrics failing
        dispatch.mockRejectedValueOnce(new Error('Error fetching this metric'));
        dispatch.mockResolvedValue();
  
        fetchDashboardData({ state, commit, dispatch })
          .then(() => {
            const defaultQueryParams = {
              start_time: expect.any(String),
              end_time: expect.any(String),
              step: expect.any(Number),
            };
  
            expect(dispatch).toHaveBeenCalledTimes(metricsDashboardPanelCount + 2); // plus 1 for deployments
            expect(dispatch).toHaveBeenCalledWith('fetchDeploymentsData');
            expect(dispatch).toHaveBeenCalledWith('fetchVariableMetricLabelValues', {
              defaultQueryParams,
            });
            expect(dispatch).toHaveBeenCalledWith('fetchPrometheusMetric', {
              metric,
              defaultQueryParams,
            });
  
            expect(createFlash).toHaveBeenCalledTimes(1);
  
            done();
          })
          .catch(done.fail);
        done();
      });
    });
  
    describe('fetchPrometheusMetric', () => {
      const defaultQueryParams = {
        start_time: '2019-08-06T12:40:02.184Z',
        end_time: '2019-08-06T20:40:02.184Z',
        step: 60,
      };
      let metric;
      let data;
      let prometheusEndpointPath;
  
      beforeEach(() => {
        state = storeState();
        [metric] = metricsDashboardViewModel.panelGroups[0].panels[0].metrics;
  
        prometheusEndpointPath = metric.prometheusEndpointPath;
  
        data = {
          metricId: metric.metricId,
          result: [1582065167.353, 5, 1582065599.353],
        };
      });
  
      it('commits result', done => {
        mock.onGet(prometheusEndpointPath).reply(200, { data }); // One attempt
  
        testAction(
          fetchPrometheusMetric,
          { metric, defaultQueryParams },
          state,
          [
            {
              type: types.REQUEST_METRIC_RESULT,
              payload: {
                metricId: metric.metricId,
              },
            },
            {
              type: types.RECEIVE_METRIC_RESULT_SUCCESS,
              payload: {
                metricId: metric.metricId,
                data,
              },
            },
          ],
          [],
          () => {
            done();
          },
        ).catch(done.fail);
      });
  
      describe('without metric defined step', () => {
        const expectedParams = {
          start_time: '2019-08-06T12:40:02.184Z',
          end_time: '2019-08-06T20:40:02.184Z',
          step: 60,
        };
  
        it('uses calculated step', done => {
          mock.onGet(prometheusEndpointPath).reply(200, { data }); // One attempt
  
          testAction(
            fetchPrometheusMetric,
            { metric, defaultQueryParams },
            state,
            [
              {
                type: types.REQUEST_METRIC_RESULT,
                payload: {
                  metricId: metric.metricId,
                },
              },
              {
                type: types.RECEIVE_METRIC_RESULT_SUCCESS,
                payload: {
                  metricId: metric.metricId,
                  data,
                },
              },
            ],
            [],
            () => {
              expect(mock.history.get[0].params).toEqual(expectedParams);
              done();
            },
          ).catch(done.fail);
        });
      });
  
      describe('with metric defined step', () => {
        beforeEach(() => {
          metric.step = 7;
        });
  
        const expectedParams = {
          start_time: '2019-08-06T12:40:02.184Z',
          end_time: '2019-08-06T20:40:02.184Z',
          step: 7,
        };
  
        it('uses metric step', done => {
          mock.onGet(prometheusEndpointPath).reply(200, { data }); // One attempt
  
          testAction(
            fetchPrometheusMetric,
            { metric, defaultQueryParams },
            state,
            [
              {
                type: types.REQUEST_METRIC_RESULT,
                payload: {
                  metricId: metric.metricId,
                },
              },
              {
                type: types.RECEIVE_METRIC_RESULT_SUCCESS,
                payload: {
                  metricId: metric.metricId,
                  data,
                },
              },
            ],
            [],
            () => {
              expect(mock.history.get[0].params).toEqual(expectedParams);
              done();
            },
          ).catch(done.fail);
        });
      });
  
      it('commits failure, when waiting for results and getting a server error', done => {
        mock.onGet(prometheusEndpointPath).reply(500);
  
        const error = new Error('Request failed with status code 500');
  
        testAction(
          fetchPrometheusMetric,
          { metric, defaultQueryParams },
          state,
          [
            {
              type: types.REQUEST_METRIC_RESULT,
              payload: {
                metricId: metric.metricId,
              },
            },
            {
              type: types.RECEIVE_METRIC_RESULT_FAILURE,
              payload: {
                metricId: metric.metricId,
                error,
              },
            },
          ],
          [],
        ).catch(e => {
          expect(e).toEqual(error);
          done();
        });
      });
    });
  
    // Deployments
  
    describe('fetchDeploymentsData', () => {
      it('dispatches receiveDeploymentsDataSuccess on success', () => {
        state.deploymentsEndpoint = '/success';
        mock.onGet(state.deploymentsEndpoint).reply(200, {
          deployments: deploymentData,
        });
  
        return testAction(
          fetchDeploymentsData,
          null,
          state,
          [],
          [{ type: 'receiveDeploymentsDataSuccess', payload: deploymentData }],
        );
      });
      it('dispatches receiveDeploymentsDataFailure on error', () => {
        state.deploymentsEndpoint = '/error';
        mock.onGet(state.deploymentsEndpoint).reply(500);
  
        return testAction(
          fetchDeploymentsData,
          null,
          state,
          [],
          [{ type: 'receiveDeploymentsDataFailure' }],
          () => {
            expect(createFlash).toHaveBeenCalled();
          },
        );
      });
    });
  
    // Environments
  
    describe('fetchEnvironmentsData', () => {
      beforeEach(() => {
        state.projectPath = 'gitlab-org/gitlab-test';
      });
  
      it('setting SET_ENVIRONMENTS_FILTER should dispatch fetchEnvironmentsData', () => {
        jest.spyOn(gqClient, 'mutate').mockReturnValue({
          data: {
            project: {
              data: {
                environments: [],
              },
            },
          },
        });
  
        return testAction(
          filterEnvironments,
          {},
          state,
          [
            {
              type: 'SET_ENVIRONMENTS_FILTER',
              payload: {},
            },
          ],
          [
            {
              type: 'fetchEnvironmentsData',
            },
          ],
        );
      });
  
      it('fetch environments data call takes in search param', () => {
        const mockMutate = jest.spyOn(gqClient, 'mutate');
        const searchTerm = 'Something';
        const mutationVariables = {
          mutation: getEnvironments,
          variables: {
            projectPath: state.projectPath,
            search: searchTerm,
            states: [ENVIRONMENT_AVAILABLE_STATE],
          },
        };
        state.environmentsSearchTerm = searchTerm;
        mockMutate.mockResolvedValue({});
  
        return testAction(
          fetchEnvironmentsData,
          null,
          state,
          [],
          [
            { type: 'requestEnvironmentsData' },
            { type: 'receiveEnvironmentsDataSuccess', payload: [] },
          ],
          () => {
            expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
          },
        );
      });
  
      it('dispatches receiveEnvironmentsDataSuccess on success', () => {
        jest.spyOn(gqClient, 'mutate').mockResolvedValue({
          data: {
            project: {
              data: {
                environments: environmentData,
              },
            },
          },
        });
  
        return testAction(
          fetchEnvironmentsData,
          null,
          state,
          [],
          [
            { type: 'requestEnvironmentsData' },
            {
              type: 'receiveEnvironmentsDataSuccess',
              payload: parseEnvironmentsResponse(environmentData, state.projectPath),
            },
          ],
        );
      });
  
      it('dispatches receiveEnvironmentsDataFailure on error', () => {
        jest.spyOn(gqClient, 'mutate').mockRejectedValue({});
  
        return testAction(
          fetchEnvironmentsData,
          null,
          state,
          [],
          [{ type: 'requestEnvironmentsData' }, { type: 'receiveEnvironmentsDataFailure' }],
        );
      });
    });
  
    describe('fetchAnnotations', () => {
      beforeEach(() => {
        state.timeRange = {
          start: '2020-04-15T12:54:32.137Z',
          end: '2020-08-15T12:54:32.137Z',
        };
        state.projectPath = 'gitlab-org/gitlab-test';
        state.currentEnvironmentName = 'production';
        state.currentDashboard = '.gitlab/dashboards/custom_dashboard.yml';
        // testAction doesn't have access to getters. The state is passed in as getters
        // instead of the actual getters inside the testAction method implementation.
        // All methods downstream that needs access to getters will throw and error.
        // For that reason, the result of the getter is set as a state variable.
        state.fullDashboardPath = store.getters['monitoringDashboard/fullDashboardPath'];
      });
  
      it('fetches annotations data and dispatches receiveAnnotationsSuccess', () => {
        const mockMutate = jest.spyOn(gqClient, 'mutate');
        const mutationVariables = {
          mutation: getAnnotations,
          variables: {
            projectPath: state.projectPath,
            environmentName: state.currentEnvironmentName,
            dashboardPath: state.currentDashboard,
            startingFrom: state.timeRange.start,
          },
        };
        const parsedResponse = parseAnnotationsResponse(annotationsData);
  
        mockMutate.mockResolvedValue({
          data: {
            project: {
              environments: {
                nodes: [
                  {
                    metricsDashboard: {
                      annotations: {
                        nodes: parsedResponse,
                      },
                    },
                  },
                ],
              },
            },
          },
        });
  
        return testAction(
          fetchAnnotations,
          null,
          state,
          [],
          [{ type: 'receiveAnnotationsSuccess', payload: parsedResponse }],
          () => {
            expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
          },
        );
      });
  
      it('dispatches receiveAnnotationsFailure if the annotations API call fails', () => {
        const mockMutate = jest.spyOn(gqClient, 'mutate');
        const mutationVariables = {
          mutation: getAnnotations,
          variables: {
            projectPath: state.projectPath,
            environmentName: state.currentEnvironmentName,
            dashboardPath: state.currentDashboard,
            startingFrom: state.timeRange.start,
          },
        };
  
        mockMutate.mockRejectedValue({});
  
        return testAction(
          fetchAnnotations,
          null,
          state,
          [],
          [{ type: 'receiveAnnotationsFailure' }],
          () => {
            expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
          },
        );
      });
    });
  
    describe('fetchDashboardValidationWarnings', () => {
      let mockMutate;
      let mutationVariables;
  
      beforeEach(() => {
        state.projectPath = 'gitlab-org/gitlab-test';
        state.currentEnvironmentName = 'production';
        state.currentDashboard = '.gitlab/dashboards/dashboard_with_warnings.yml';
        // testAction doesn't have access to getters. The state is passed in as getters
        // instead of the actual getters inside the testAction method implementation.
        // All methods downstream that needs access to getters will throw and error.
        // For that reason, the result of the getter is set as a state variable.
        state.fullDashboardPath = store.getters['monitoringDashboard/fullDashboardPath'];
  
        mockMutate = jest.spyOn(gqClient, 'mutate');
        mutationVariables = {
          mutation: getDashboardValidationWarnings,
          variables: {
            projectPath: state.projectPath,
            environmentName: state.currentEnvironmentName,
            dashboardPath: state.fullDashboardPath,
          },
        };
      });
  
      it('dispatches receiveDashboardValidationWarningsSuccess with true payload when there are warnings', () => {
        mockMutate.mockResolvedValue({
          data: {
            project: {
              id: 'gid://gitlab/Project/29',
              environments: {
                nodes: [
                  {
                    name: 'production',
                    metricsDashboard: {
                      path: '.gitlab/dashboards/dashboard_errors_test.yml',
                      schemaValidationWarnings: ["unit: can't be blank"],
                    },
                  },
                ],
              },
            },
          },
        });
  
        return testAction(
          fetchDashboardValidationWarnings,
          null,
          state,
          [],
          [{ type: 'receiveDashboardValidationWarningsSuccess', payload: true }],
          () => {
            expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
          },
        );
      });
  
      it('dispatches receiveDashboardValidationWarningsSuccess with false payload when there are no warnings', () => {
        mockMutate.mockResolvedValue({
          data: {
            project: {
              id: 'gid://gitlab/Project/29',
              environments: {
                nodes: [
                  {
                    name: 'production',
                    metricsDashboard: {
                      path: '.gitlab/dashboards/dashboard_errors_test.yml',
                      schemaValidationWarnings: [],
                    },
                  },
                ],
              },
            },
          },
        });
  
        return testAction(
          fetchDashboardValidationWarnings,
          null,
          state,
          [],
          [{ type: 'receiveDashboardValidationWarningsSuccess', payload: false }],
          () => {
            expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
          },
        );
      });
  
      it('dispatches receiveDashboardValidationWarningsSuccess with false payload when the response is empty ', () => {
        mockMutate.mockResolvedValue({
          data: {
            project: null,
          },
        });
  
        return testAction(
          fetchDashboardValidationWarnings,
          null,
          state,
          [],
          [{ type: 'receiveDashboardValidationWarningsSuccess', payload: false }],
          () => {
            expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
          },
        );
      });
  
      it('dispatches receiveDashboardValidationWarningsFailure if the warnings API call fails', () => {
        mockMutate.mockRejectedValue({});
  
        return testAction(
          fetchDashboardValidationWarnings,
          null,
          state,
          [],
          [{ type: 'receiveDashboardValidationWarningsFailure' }],
          () => {
            expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
          },
        );
      });
    });
  
    // Dashboard manipulation
  
    describe('toggleStarredValue', () => {
      let unstarredDashboard;
      let starredDashboard;
  
      beforeEach(() => {
        state.isUpdatingStarredValue = false;
        [unstarredDashboard, starredDashboard] = dashboardGitResponse;
      });
  
      it('performs no changes if no dashboard is selected', () => {
        return testAction(toggleStarredValue, null, state, [], []);
      });
  
      it('performs no changes if already changing starred value', () => {
        state.selectedDashboard = unstarredDashboard;
        state.isUpdatingStarredValue = true;
        return testAction(toggleStarredValue, null, state, [], []);
      });
  
      it('stars dashboard if it is not starred', () => {
        state.selectedDashboard = unstarredDashboard;
        mock.onPost(unstarredDashboard.user_starred_path).reply(200);
  
        return testAction(toggleStarredValue, null, state, [
          { type: types.REQUEST_DASHBOARD_STARRING },
          {
            type: types.RECEIVE_DASHBOARD_STARRING_SUCCESS,
            payload: {
              newStarredValue: true,
              selectedDashboard: unstarredDashboard,
            },
          },
        ]);
      });
  
      it('unstars dashboard if it is starred', () => {
        state.selectedDashboard = starredDashboard;
        mock.onPost(starredDashboard.user_starred_path).reply(200);
  
        return testAction(toggleStarredValue, null, state, [
          { type: types.REQUEST_DASHBOARD_STARRING },
          { type: types.RECEIVE_DASHBOARD_STARRING_FAILURE },
        ]);
      });
    });
  
    describe('duplicateSystemDashboard', () => {
      beforeEach(() => {
        state.dashboardsEndpoint = '/dashboards.json';
      });
  
      it('Succesful POST request resolves', done => {
        mock.onPost(state.dashboardsEndpoint).reply(statusCodes.CREATED, {
          dashboard: dashboardGitResponse[1],
        });
  
        testAction(duplicateSystemDashboard, {}, state, [], [])
          .then(() => {
            expect(mock.history.post).toHaveLength(1);
            done();
          })
          .catch(done.fail);
      });
  
      it('Succesful POST request resolves to a dashboard', done => {
        const mockCreatedDashboard = dashboardGitResponse[1];
  
        const params = {
          dashboard: 'my-dashboard',
          fileName: 'file-name.yml',
          branch: 'my-new-branch',
          commitMessage: 'A new commit message',
        };
  
        const expectedPayload = JSON.stringify({
          dashboard: 'my-dashboard',
          file_name: 'file-name.yml',
          branch: 'my-new-branch',
          commit_message: 'A new commit message',
        });
  
        mock.onPost(state.dashboardsEndpoint).reply(statusCodes.CREATED, {
          dashboard: mockCreatedDashboard,
        });
  
        testAction(duplicateSystemDashboard, params, state, [], [])
          .then(result => {
            expect(mock.history.post).toHaveLength(1);
            expect(mock.history.post[0].data).toEqual(expectedPayload);
            expect(result).toEqual(mockCreatedDashboard);
  
            done();
          })
          .catch(done.fail);
      });
  
      it('Failed POST request throws an error', done => {
        mock.onPost(state.dashboardsEndpoint).reply(statusCodes.BAD_REQUEST);
  
        testAction(duplicateSystemDashboard, {}, state, [], []).catch(err => {
          expect(mock.history.post).toHaveLength(1);
          expect(err).toEqual(expect.any(String));
  
          done();
        });
      });
  
      it('Failed POST request throws an error with a description', done => {
        const backendErrorMsg = 'This file already exists!';
  
        mock.onPost(state.dashboardsEndpoint).reply(statusCodes.BAD_REQUEST, {
          error: backendErrorMsg,
        });
  
        testAction(duplicateSystemDashboard, {}, state, [], []).catch(err => {
          expect(mock.history.post).toHaveLength(1);
          expect(err).toEqual(expect.any(String));
          expect(err).toEqual(expect.stringContaining(backendErrorMsg));
  
          done();
        });
      });
    });
  
    // Variables manipulation
  
    describe('updateVariablesAndFetchData', () => {
      it('should commit UPDATE_VARIABLE_VALUE mutation and fetch data', done => {
        testAction(
          updateVariablesAndFetchData,
          { pod: 'POD' },
          state,
          [
            {
              type: types.UPDATE_VARIABLE_VALUE,
              payload: { pod: 'POD' },
            },
          ],
          [
            {
              type: 'fetchDashboardData',
            },
          ],
          done,
        );
      });
    });
  
    describe('fetchVariableMetricLabelValues', () => {
      const variable = {
        type: 'metric_label_values',
        name: 'label1',
        options: {
          prometheusEndpointPath: '/series?match[]=metric_name',
          label: 'job',
        },
      };
  
      const defaultQueryParams = {
        start_time: '2019-08-06T12:40:02.184Z',
        end_time: '2019-08-06T20:40:02.184Z',
      };
  
      beforeEach(() => {
        state = {
          ...state,
          timeRange: defaultTimeRange,
          variables: [variable],
        };
      });
  
      it('should commit UPDATE_VARIABLE_METRIC_LABEL_VALUES mutation and fetch data', () => {
        const data = [
          {
            __name__: 'up',
            job: 'prometheus',
          },
          {
            __name__: 'up',
            job: 'POD',
          },
        ];
  
        mock.onGet('/series?match[]=metric_name').reply(200, {
          status: 'success',
          data,
        });
  
        return testAction(
          fetchVariableMetricLabelValues,
          { defaultQueryParams },
          state,
          [
            {
              type: types.UPDATE_VARIABLE_METRIC_LABEL_VALUES,
              payload: { variable, label: 'job', data },
            },
          ],
          [],
        );
      });
  
      it('should notify the user that dynamic options were not loaded', () => {
        mock.onGet('/series?match[]=metric_name').reply(500);
  
        return testAction(fetchVariableMetricLabelValues, { defaultQueryParams }, state, [], []).then(
          () => {
            expect(createFlash).toHaveBeenCalledTimes(1);
            expect(createFlash).toHaveBeenCalledWith(
              expect.stringContaining('error getting options for variable "label1"'),
            );
          },
        );
      });
    });
  
    describe('fetchPanelPreview', () => {
      const panelPreviewEndpoint = '/builder.json';
      const mockYmlContent = 'mock yml content';
  
      beforeEach(() => {
        state.panelPreviewEndpoint = panelPreviewEndpoint;
      });
  
      it('should not commit or dispatch if payload is empty', () => {
        testAction(fetchPanelPreview, '', state, [], []);
      });
  
      it('should store the panel and fetch metric results', () => {
        const mockPanel = {
          title: 'Go heap size',
          type: 'area-chart',
        };
  
        mock
          .onPost(panelPreviewEndpoint, { panel_yaml: mockYmlContent })
          .reply(statusCodes.OK, mockPanel);
  
        testAction(
          fetchPanelPreview,
          mockYmlContent,
          state,
          [
            { type: types.SET_PANEL_PREVIEW_IS_SHOWN, payload: true },
            { type: types.REQUEST_PANEL_PREVIEW, payload: mockYmlContent },
            { type: types.RECEIVE_PANEL_PREVIEW_SUCCESS, payload: mockPanel },
          ],
          [{ type: 'fetchPanelPreviewMetrics' }],
        );
      });
  
      it('should display a validation error when the backend cannot process the yml', () => {
        const mockErrorMsg = 'Each "metric" must define one of :query or :query_range';
  
        mock
          .onPost(panelPreviewEndpoint, { panel_yaml: mockYmlContent })
          .reply(statusCodes.UNPROCESSABLE_ENTITY, {
            message: mockErrorMsg,
          });
  
        testAction(fetchPanelPreview, mockYmlContent, state, [
          { type: types.SET_PANEL_PREVIEW_IS_SHOWN, payload: true },
          { type: types.REQUEST_PANEL_PREVIEW, payload: mockYmlContent },
          { type: types.RECEIVE_PANEL_PREVIEW_FAILURE, payload: mockErrorMsg },
        ]);
      });
  
      it('should display a generic error when the backend fails', () => {
        mock.onPost(panelPreviewEndpoint, { panel_yaml: mockYmlContent }).reply(500);
  
        testAction(fetchPanelPreview, mockYmlContent, state, [
          { type: types.SET_PANEL_PREVIEW_IS_SHOWN, payload: true },
          { type: types.REQUEST_PANEL_PREVIEW, payload: mockYmlContent },
          {
            type: types.RECEIVE_PANEL_PREVIEW_FAILURE,
            payload: 'Request failed with status code 500',
          },
        ]);
      });
    });
  });
  