/*****************************************************************************
 * Open MCT, Copyright (c) 2014-2024, United States Government
 * as represented by the Administrator of the National Aeronautics and Space
 * Administration. All rights reserved.
 *
 * Open MCT is licensed under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0.
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 * Open MCT includes source code licensed under additional open source
 * licenses. See the Open Source Licenses file (LICENSES.md) included with
 * this source code distribution or the Licensing information page available
 * at runtime from the About dialog for additional information.
 *****************************************************************************/

import { EventEmitter } from 'eventemitter3';
import mount from 'utils/mount';
import {
  createMouseEvent,
  createOpenMct,
  renderWhenVisible,
  resetApplicationState,
  spyOnBuiltins
} from 'utils/testing';
import { nextTick } from 'vue';

import configStore from './configuration/ConfigStore.js';
import PlotConfigurationModel from './configuration/PlotConfigurationModel.js';
import PlotOptions from './inspector/PlotOptions.vue';
import PlotVuePlugin from './plugin.js';

const TEST_KEY_ID = 'some-other-key';

describe('the plugin', function () {
  let element;
  let child;
  let openmct;
  let telemetryPromise;
  let telemetryPromiseResolve;
  let mockObjectPath;
  let telemetrylimitProvider;

  beforeEach((done) => {
    mockObjectPath = [
      {
        name: 'mock folder',
        type: 'fake-folder',
        identifier: {
          key: 'mock-folder',
          namespace: ''
        }
      },
      {
        name: 'mock parent folder',
        type: 'time-strip',
        identifier: {
          key: 'mock-parent-folder',
          namespace: ''
        }
      }
    ];
    const testTelemetry = [
      {
        utc: 1,
        'some-key': 'some-value 1',
        'some-other-key': 'some-other-value 1'
      },
      {
        utc: 2,
        'some-key': 'some-value 2',
        'some-other-key': 'some-other-value 2'
      },
      {
        utc: 3,
        'some-key': 'some-value 3',
        'some-other-key': 'some-other-value 3'
      }
    ];

    const timeSystem = {
      timeSystemKey: 'utc',
      bounds: {
        start: 0,
        end: 4
      }
    };

    openmct = createOpenMct(timeSystem);

    telemetryPromise = new Promise((resolve) => {
      telemetryPromiseResolve = resolve;
    });

    spyOn(openmct.telemetry, 'request').and.callFake(() => {
      telemetryPromiseResolve(testTelemetry);

      return telemetryPromise;
    });

    telemetrylimitProvider = jasmine.createSpyObj('telemetrylimitProvider', [
      'supportsLimits',
      'getLimits',
      'getLimitEvaluator'
    ]);
    telemetrylimitProvider.supportsLimits.and.returnValue(true);
    telemetrylimitProvider.getLimits.and.returnValue({
      limits: function () {
        return Promise.resolve({
          WARNING: {
            low: {
              cssClass: 'is-limit--lwr is-limit--yellow',
              'some-key': -0.5
            },
            high: {
              cssClass: 'is-limit--upr is-limit--yellow',
              'some-key': 0.5
            }
          },
          DISTRESS: {
            low: {
              cssClass: 'is-limit--lwr is-limit--red',
              'some-key': -0.9
            },
            high: {
              cssClass: 'is-limit--upr is-limit--red',
              'some-key': 0.9
            }
          }
        });
      }
    });
    telemetrylimitProvider.getLimitEvaluator.and.returnValue({
      evaluate: function () {
        return {};
      }
    });
    openmct.telemetry.addProvider(telemetrylimitProvider);

    openmct.install(new PlotVuePlugin());

    element = document.createElement('div');
    element.style.width = '640px';
    element.style.height = '480px';
    child = document.createElement('div');
    child.style.width = '640px';
    child.style.height = '480px';
    element.appendChild(child);
    document.body.appendChild(element);

    openmct.types.addType('test-object', {
      creatable: true
    });

    spyOnBuiltins(['requestAnimationFrame']);
    window.requestAnimationFrame.and.callFake((callBack) => {
      callBack();
    });

    openmct.on('start', done);
    openmct.startHeadless();
  });

  afterEach(async () => {
    openmct.time.setTimeSystem('utc', {
      start: 0,
      end: 2
    });

    await nextTick();
    configStore.deleteAll();
    return resetApplicationState(openmct);
  });

  describe('the plot views', () => {
    it('provides a plot view for objects with telemetry', () => {
      const testTelemetryObject = {
        id: 'test-object',
        type: 'test-object',
        telemetry: {
          values: [
            {
              key: 'some-key',
              hints: {
                domain: 1
              }
            },
            {
              key: 'other-key',
              hints: {
                range: 1
              }
            },
            {
              key: 'yet-another-key',
              format: 'string',
              hints: {
                range: 2
              }
            }
          ]
        }
      };

      const applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath);
      const plotView = applicableViews.find((viewProvider) => viewProvider.key === 'plot-single');

      expect(plotView).toBeDefined();
    });

    it('does not provide a plot view if the telemetry is entirely non numeric', () => {
      const testTelemetryObject = {
        id: 'test-object',
        type: 'test-object',
        telemetry: {
          values: [
            {
              key: 'some-key',
              hints: {
                domain: 1
              }
            },
            {
              key: 'other-key',
              format: 'string',
              hints: {
                range: 1
              }
            },
            {
              key: 'yet-another-key',
              format: 'string',
              hints: {
                range: 1
              }
            }
          ]
        }
      };

      const applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath);
      const plotView = applicableViews.find((viewProvider) => viewProvider.key === 'plot-single');

      expect(plotView).toBeUndefined();
    });

    it('provides an overlay plot view for objects with telemetry', () => {
      const testTelemetryObject = {
        id: 'test-object',
        type: 'telemetry.plot.overlay',
        telemetry: {
          values: [
            {
              key: 'some-key'
            }
          ]
        }
      };

      const applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath);
      let plotView = applicableViews.find((viewProvider) => viewProvider.key === 'plot-overlay');
      expect(plotView).toBeDefined();
    });

    it('provides an inspector view for overlay plots', () => {
      let selection = [
        [
          {
            context: {
              item: {
                id: 'test-object',
                type: 'telemetry.plot.overlay',
                telemetry: {
                  values: [
                    {
                      key: 'some-key'
                    }
                  ]
                }
              }
            }
          },
          {
            context: {
              item: {
                type: 'time-strip'
              }
            }
          }
        ]
      ];
      const applicableInspectorViews = openmct.inspectorViews.get(selection);
      const plotInspectorView = applicableInspectorViews.find(
        (view) => view.key === 'plots-inspector'
      );

      expect(plotInspectorView).toBeDefined();
    });

    it('provides a stacked plot view for objects with telemetry', () => {
      const testTelemetryObject = {
        id: 'test-object',
        type: 'telemetry.plot.stacked',
        telemetry: {
          values: [
            {
              key: 'some-key'
            }
          ]
        }
      };

      const applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath);
      let plotView = applicableViews.find((viewProvider) => viewProvider.key === 'plot-stacked');
      expect(plotView).toBeDefined();
    });
  });

  describe('The single plot view', () => {
    let testTelemetryObject;
    let applicableViews;
    let plotViewProvider;
    let plotView;

    beforeEach(() => {
      openmct.time.timeSystem('utc', {
        start: 0,
        end: 4
      });
      testTelemetryObject = {
        identifier: {
          namespace: '',
          key: 'test-object'
        },
        type: 'test-object',
        name: 'Test Object',
        telemetry: {
          values: [
            {
              key: 'utc',
              format: 'utc',
              name: 'Time',
              hints: {
                domain: 1
              }
            },
            {
              key: 'some-key',
              name: 'Some attribute',
              hints: {
                range: 1
              }
            },
            {
              key: 'some-other-key',
              name: 'Another attribute',
              hints: {
                range: 2
              }
            }
          ]
        }
      };

      openmct.router.path = [testTelemetryObject];

      applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath);
      plotViewProvider = applicableViews.find((viewProvider) => viewProvider.key === 'plot-single');
      plotView = plotViewProvider.view(testTelemetryObject, []);
      plotView.show(child, true, { renderWhenVisible });

      return nextTick();
    });

    afterEach(() => {
      openmct.router.path = null;
    });

    it('Makes only one request for telemetry on load', () => {
      expect(openmct.telemetry.request).toHaveBeenCalledTimes(1);
    });

    it('Renders a collapsed legend for every telemetry', async () => {
      await nextTick();
      let legend = element.querySelectorAll('.plot-wrapper-collapsed-legend .plot-series-name');
      expect(legend.length).toBe(1);
      expect(legend[0].innerHTML).toEqual('Test Object');
    });

    it('Renders an expanded legend for every telemetry', async () => {
      await nextTick();
      let legendControl = element.querySelector(
        '.c-plot-legend__view-control.gl-plot-legend__view-control.c-disclosure-triangle'
      );
      const clickEvent = createMouseEvent('click');

      legendControl.dispatchEvent(clickEvent);
      await nextTick();

      let legend = element.querySelectorAll('.plot-wrapper-expanded-legend .plot-legend-item td');
      expect(legend.length).toBe(6);
    });

    it('Renders X-axis ticks for the telemetry object', (done) => {
      const configId = openmct.objects.makeKeyString(testTelemetryObject.identifier);
      const config = configStore.get(configId);
      config.xAxis.set('displayRange', {
        min: 0,
        max: 4
      });

      nextTick(() => {
        let xAxisElement = element.querySelectorAll(
          '.gl-plot-axis-area.gl-plot-x .gl-plot-tick-wrapper'
        );
        expect(xAxisElement.length).toBe(1);

        let ticks = xAxisElement[0].querySelectorAll('.gl-plot-tick');
        expect(ticks.length).toBe(9);

        done();
      });
    });

    it('Renders Y-axis options for the telemetry object', async () => {
      await nextTick();
      let yAxisElement = element.querySelectorAll(
        '.gl-plot-axis-area.gl-plot-y .gl-plot-y-label__select'
      );
      expect(yAxisElement.length).toBe(1);
      //Object{name: "Some attribute", key: "some-key"}, Object{name: "Another attribute", key: "some-other-key"}
      let options = yAxisElement[0].querySelectorAll('option');
      expect(options.length).toBe(2);
      expect(options[0].value).toBe('Some attribute');
      expect(options[1].value).toBe('Another attribute');
    });

    xit('Updates the Y-axis label when changed', () => {
      const configId = openmct.objects.makeKeyString(testTelemetryObject.identifier);
      const config = configStore.get(configId);
      const yAxisElement = element.querySelectorAll('.gl-plot-axis-area.gl-plot-y')[0].__vue__;
      config.yAxis.seriesCollection.models.forEach((plotSeries) => {
        expect(plotSeries.model.yKey).toBe('some-key');
      });

      yAxisElement.$emit('y-key-changed', TEST_KEY_ID, 1);
      config.yAxis.seriesCollection.models.forEach((plotSeries) => {
        expect(plotSeries.model.yKey).toBe(TEST_KEY_ID);
      });
    });

    it('hides the pause and play controls', () => {
      let pauseEl = element.querySelectorAll('.c-button-set .icon-pause');
      let playEl = element.querySelectorAll('.c-button-set .icon-arrow-right');
      expect(pauseEl.length).toBe(0);
      expect(playEl.length).toBe(0);
    });

    describe('pause and play controls', () => {
      beforeEach(() => {
        openmct.time.setClock('local');
        openmct.time.setClockOffsets({
          start: -1000,
          end: 100
        });

        return nextTick();
      });

      it('shows the pause controls', (done) => {
        nextTick(() => {
          let pauseEl = element.querySelectorAll('.c-button-set .icon-pause');
          expect(pauseEl.length).toBe(1);
          done();
        });
      });

      it('shows the play control if plot is paused', (done) => {
        let pauseEl = element.querySelector('.c-button-set .icon-pause');
        const clickEvent = createMouseEvent('click');

        pauseEl.dispatchEvent(clickEvent);
        nextTick(() => {
          let playEl = element.querySelectorAll('.c-button-set .is-paused');
          expect(playEl.length).toBe(1);
          done();
        });
      });
    });

    describe('resume actions on errant click', () => {
      beforeEach(() => {
        openmct.time.setClock('local');
        openmct.time.setClockOffsets({
          start: -1000,
          end: 100
        });

        return nextTick();
      });

      it('clicking the plot view without movement resumes the plot while active', async () => {
        const pauseEl = element.querySelectorAll('.c-button-set .icon-pause');
        // if the pause button is present, the chart is running
        expect(pauseEl.length).toBe(1);

        // simulate an errant mouse click
        // the second item is the canvas we need to use
        const canvas = element.querySelectorAll('canvas')[1];
        const mouseDownEvent = new MouseEvent('mousedown');
        const mouseUpEvent = new MouseEvent('mouseup');
        canvas.dispatchEvent(mouseDownEvent);
        // mouseup event is bound to the window
        window.dispatchEvent(mouseUpEvent);
        await nextTick();

        const pauseElAfterClick = element.querySelectorAll('.c-button-set .icon-pause');
        console.log('pauseElAfterClick', pauseElAfterClick);
        expect(pauseElAfterClick.length).toBe(1);
      });

      it('clicking the plot view without movement leaves the plot paused', async () => {
        const pauseEl = element.querySelector('.c-button-set .icon-pause');
        // pause the plot
        pauseEl.dispatchEvent(createMouseEvent('click'));
        await nextTick();

        const playEl = element.querySelectorAll('.c-button-set .is-paused');
        expect(playEl.length).toBe(1);

        // simulate an errant mouse click
        // the second item is the canvas we need to use
        const canvas = element.querySelectorAll('canvas')[1];
        const mouseDownEvent = new MouseEvent('mousedown');
        const mouseUpEvent = new MouseEvent('mouseup');
        canvas.dispatchEvent(mouseDownEvent);
        // mouseup event is bound to the window
        window.dispatchEvent(mouseUpEvent);
        await nextTick();

        const playElAfterChartClick = element.querySelectorAll('.c-button-set .is-paused');
        expect(playElAfterChartClick.length).toBe(1);
      });

      it('clicking the plot does not request historical data', async () => {
        expect(openmct.telemetry.request).toHaveBeenCalledTimes(2);

        // simulate an errant mouse click
        // the second item is the canvas we need to use
        const canvas = element.querySelectorAll('canvas')[1];
        const mouseDownEvent = new MouseEvent('mousedown');
        const mouseUpEvent = new MouseEvent('mouseup');
        canvas.dispatchEvent(mouseDownEvent);
        // mouseup event is bound to the window
        window.dispatchEvent(mouseUpEvent);
        await nextTick();

        expect(openmct.telemetry.request).toHaveBeenCalledTimes(2);
      });

      describe('limits', () => {
        it('lines are not displayed by default', () => {
          let limitEl = element.querySelectorAll('.js-limit-area .js-limit-line');
          expect(limitEl.length).toBe(0);
        });

        it('lines are displayed when configuration is set to true', async () => {
          await nextTick();
          const configId = openmct.objects.makeKeyString(testTelemetryObject.identifier);
          const config = configStore.get(configId);
          config.yAxis.set('displayRange', {
            min: 0,
            max: 4
          });
          config.series.models[0].set('limitLines', true);

          await nextTick();
          let limitEl = element.querySelectorAll('.js-limit-area .js-limit-line');
          expect(limitEl.length).toBe(4);
        });
      });
    });

    describe('controls in time strip view', () => {
      it('zoom controls are hidden', () => {
        let pauseEl = element.querySelectorAll('.c-button-set .js-zoom');
        expect(pauseEl.length).toBe(0);
      });

      it('pan controls are hidden', () => {
        let pauseEl = element.querySelectorAll('.c-button-set .js-pan');
        expect(pauseEl.length).toBe(0);
      });

      it('pause/play controls are hidden', () => {
        let pauseEl = element.querySelectorAll('.c-button-set .js-pause');
        expect(pauseEl.length).toBe(0);
      });
    });
  });

  describe('resizing the plot', () => {
    let plotContainerResizeObserver;
    let resizePromiseResolve;
    let testTelemetryObject;
    let applicableViews;
    let plotViewProvider;
    let plotView;
    let resizePromise;

    beforeEach(() => {
      testTelemetryObject = {
        identifier: {
          namespace: '',
          key: 'test-object'
        },
        type: 'test-object',
        name: 'Test Object',
        telemetry: {
          values: [
            {
              key: 'utc',
              format: 'utc',
              name: 'Time',
              hints: {
                domain: 1
              }
            },
            {
              key: 'some-key',
              name: 'Some attribute',
              hints: {
                range: 1
              }
            },
            {
              key: 'some-other-key',
              name: 'Another attribute',
              hints: {
                range: 2
              }
            }
          ]
        }
      };

      openmct.router.path = [testTelemetryObject];

      applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath);
      plotViewProvider = applicableViews.find((viewProvider) => viewProvider.key === 'plot-single');
      plotView = plotViewProvider.view(testTelemetryObject, []);

      plotView.show(child, true, { renderWhenVisible });

      resizePromise = new Promise((resolve) => {
        resizePromiseResolve = resolve;
      });

      const handlePlotResize = _.debounce(() => {
        resizePromiseResolve(true);
      }, 600);

      plotContainerResizeObserver = new ResizeObserver(handlePlotResize);
      plotContainerResizeObserver.observe(
        plotView.getComponent().$refs.plotComponent.$refs.plotWrapper
      );

      return nextTick(() => {
        plotView.getComponent().$refs.plotComponent.$refs.mctPlot.stopFollowingTimeContext();
        spyOn(
          plotView.getComponent().$refs.plotComponent.$refs.mctPlot,
          'loadSeriesData'
        ).and.callThrough();
      });
    });

    afterEach(() => {
      plotContainerResizeObserver.disconnect();
      openmct.router.path = null;
    });

    xit('requests historical data when over the threshold', async () => {
      await nextTick();
      element.style.width = '680px';
      await resizePromise;
      expect(
        plotView.getComponent().$refs.plotComponent.$refs.mctPlot.loadSeriesData
      ).toHaveBeenCalledTimes(1);
    });

    it('does not request historical data when under the threshold', async () => {
      element.style.width = '644px';
      await resizePromise;
      expect(
        plotView.getComponent().$refs.plotComponent.$refs.mctPlot.loadSeriesData
      ).not.toHaveBeenCalled();
    });
  });

  describe('the inspector view', () => {
    let component;
    let viewComponentObject;
    let mockComposition;
    let testTelemetryObject;
    let selection;
    let config;
    beforeEach((done) => {
      testTelemetryObject = {
        identifier: {
          namespace: '',
          key: 'test-object'
        },
        type: 'test-object',
        name: 'Test Object',
        telemetry: {
          values: [
            {
              key: 'utc',
              format: 'utc',
              name: 'Time',
              hints: {
                domain: 1
              }
            },
            {
              key: 'some-key',
              name: 'Some attribute',
              hints: {
                range: 1
              }
            },
            {
              key: 'some-other-key',
              name: 'Another attribute',
              hints: {
                range: 2
              }
            }
          ]
        }
      };

      selection = [
        [
          {
            context: {
              item: {
                id: 'test-object',
                identifier: {
                  key: 'test-object',
                  namespace: ''
                },
                type: 'telemetry.plot.overlay',
                configuration: {
                  series: [
                    {
                      identifier: {
                        key: 'test-object',
                        namespace: ''
                      }
                    }
                  ]
                },
                composition: []
              }
            }
          },
          {
            context: {
              item: {
                type: 'time-strip',
                identifier: {
                  key: 'some-other-key',
                  namespace: ''
                }
              }
            }
          }
        ]
      ];

      openmct.router.path = [testTelemetryObject];
      mockComposition = new EventEmitter();
      mockComposition.load = () => {
        mockComposition.emit('add', testTelemetryObject);

        return [testTelemetryObject];
      };

      spyOn(openmct.composition, 'get').and.returnValue(mockComposition);

      const configId = openmct.objects.makeKeyString(selection[0][0].context.item.identifier);
      config = new PlotConfigurationModel({
        id: configId,
        domainObject: selection[0][0].context.item,
        openmct: openmct
      });
      configStore.add(configId, config);

      let viewContainer = document.createElement('div');
      child.append(viewContainer);
      const { vNode } = mount(
        {
          components: {
            PlotOptions
          },
          provide: {
            openmct: openmct,
            domainObject: selection[0][0].context.item,
            path: [selection[0][0].context.item, selection[0][1].context.item],
            renderWhenVisible
          },
          template: '<plot-options ref="root"/>'
        },
        {
          element: viewContainer
        }
      );
      component = vNode.componentInstance;

      nextTick(() => {
        viewComponentObject = component.$refs.root;
        done();
      });
    });

    afterEach(() => {
      openmct.router.path = null;
    });

    describe('in view only mode', () => {
      let browseOptionsEl;
      let editOptionsEl;
      beforeEach(() => {
        browseOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-browse');
        editOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-edit');
      });

      it('does not show the edit options', () => {
        expect(editOptionsEl).toBeNull();
      });

      it('shows the name', () => {
        const seriesEl = browseOptionsEl.querySelector('.c-object-label__name');
        expect(seriesEl.innerHTML).toEqual(testTelemetryObject.name);
      });

      it('shows in collapsed mode', () => {
        const seriesEl = browseOptionsEl.querySelectorAll('.c-disclosure-triangle--expanded');
        expect(seriesEl.length).toEqual(0);
      });

      it('shows in expanded mode', () => {
        let expandControl = browseOptionsEl.querySelector('.c-disclosure-triangle');
        const clickEvent = createMouseEvent('click');
        expandControl.dispatchEvent(clickEvent);

        const plotOptionsProperties = browseOptionsEl.querySelectorAll(
          '.js-plot-options-browse-properties .grid-row'
        );
        expect(plotOptionsProperties.length).toEqual(6);
      });
    });

    describe('in edit mode', () => {
      let editOptionsEl;
      let browseOptionsEl;

      beforeEach((done) => {
        viewComponentObject.setEditState(true);
        nextTick(() => {
          editOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-edit');
          browseOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-browse');
          done();
        });
      });

      it('does not show the browse options', () => {
        expect(browseOptionsEl).toBeNull();
      });

      it('shows the name', () => {
        const seriesEl = editOptionsEl.querySelector('.c-object-label__name');
        expect(seriesEl.innerHTML).toEqual(testTelemetryObject.name);
      });

      it('shows in collapsed mode', () => {
        const seriesEl = editOptionsEl.querySelectorAll('.c-disclosure-triangle--expanded');
        expect(seriesEl.length).toEqual(0);
      });

      it('shows in collapsed mode', () => {
        const seriesEl = editOptionsEl.querySelectorAll('.c-disclosure-triangle--expanded');
        expect(seriesEl.length).toEqual(0);
      });

      it('renders expanded', () => {
        const expandControl = editOptionsEl.querySelector('.c-disclosure-triangle');
        const clickEvent = createMouseEvent('click');
        expandControl.dispatchEvent(clickEvent);

        const plotOptionsProperties = editOptionsEl.querySelectorAll(
          '.js-plot-options-edit-properties .grid-row'
        );
        expect(plotOptionsProperties.length).toEqual(8);
      });

      it('shows yKeyOptions', () => {
        const expandControl = editOptionsEl.querySelector('.c-disclosure-triangle');
        const clickEvent = createMouseEvent('click');
        expandControl.dispatchEvent(clickEvent);

        const plotOptionsProperties = editOptionsEl.querySelectorAll(
          '.js-plot-options-edit-properties .grid-row'
        );

        const yKeySelection = plotOptionsProperties[0].querySelector('select');
        const options = Array.from(yKeySelection.options).map((option) => {
          return option.value;
        });
        expect(options).toEqual([
          testTelemetryObject.telemetry.values[1].key,
          testTelemetryObject.telemetry.values[2].key
        ]);
      });

      it('shows yAxis options', () => {
        const expandControl = editOptionsEl.querySelector('.c-disclosure-triangle');
        const clickEvent = createMouseEvent('click');
        expandControl.dispatchEvent(clickEvent);

        const yAxisProperties = editOptionsEl.querySelectorAll(
          'div.grid-properties:first-of-type .l-inspector-part'
        );

        // TODO better test
        expect(yAxisProperties.length).toEqual(2);
      });

      it('renders color palette options', () => {
        const colorSwatch = editOptionsEl.querySelector('.c-click-swatch');
        expect(colorSwatch).toBeDefined();
      });
    });
  });
});
