import { ChartDefinition, ChartRangeDataSource, Model } from "../../../src";
import {
  DEFAULT_CELL_HEIGHT,
  DEFAULT_CELL_WIDTH,
  DEFAULT_FIGURE_HEIGHT,
  DEFAULT_FIGURE_WIDTH,
} from "../../../src/constants";
import { toXC } from "../../../src/helpers/coordinates";
import { zoneToXc } from "../../../src/helpers/zones";
import { ViewportsStore } from "../../../src/stores/viewports_store";
import { SpreadsheetChildEnv } from "../../../src/types/spreadsheet_env";
import { Store } from "../../../src/types/store_engine";
import { toChartDataSource } from "../../test_helpers/chart_helpers";
import {
  addColumns,
  addRows,
  freezeColumns,
  freezeRows,
  resizeSheetView,
  setCellContent,
  setSelection,
  setViewportOffset,
} from "../../test_helpers/commands_helpers";
import {
  doAction,
  makeTestEnv,
  mockChart,
  mountSpreadsheet,
  nextTick,
  spyModelDispatch,
} from "../../test_helpers/helpers";

describe("Insert chart menu item", () => {
  const data = {
    sheets: [
      {
        name: "Sheet1",
        rows: {},
        cells: {
          A2: "P1",
          A3: "P2",
          A4: "P3",
          A5: "P4",

          B1: "first column dataset",
          B2: "10",
          B3: "11",
          B4: "12",
          B5: "13",

          C1: "",
          C2: "2",
          C3: "4",
          C4: "6",

          D1: "=sum()",
          D2: "3",
          D3: "2",
          D4: "5",

          E1: "Title1",
          E2: "10",
          E3: "11",
          E4: "12",

          F1: "=sum(1,2)",
          F2: "7",
          F3: "8",
          F4: "9",

          G1: "",
          G2: "7",
          G3: "8",
          G4: "9",

          H1: "Title2",
          H2: "7",
          H3: "8",
          H4: "9",
        },
      },
    ],
  };

  let dispatchSpy: jest.SpyInstance;
  let defaultPayload: any;
  let model: Model;
  let env: SpreadsheetChildEnv;
  let openSidePanelSpy: jest.Mock<any, any>;
  let viewStore: Store<ViewportsStore>;

  async function insertChart() {
    await doAction(["insert", "insert_chart"], env);
  }

  async function mountTestSpreadsheet() {
    ({ model, env, viewStore } = await mountSpreadsheet({ model: new Model(data) }));
    dispatchSpy = spyModelDispatch(model);
  }

  beforeEach(async () => {
    openSidePanelSpy = jest.fn();
    env = makeTestEnv({
      model: new Model(data),
      openSidePanel: (type, props) => openSidePanelSpy(type, props),
    });
    model = env.model;
    viewStore = env.getStore(ViewportsStore);

    mockChart();
    dispatchSpy = spyModelDispatch(model);
    defaultPayload = {
      col: expect.any(Number),
      row: expect.any(Number),
      offset: expect.any(Object),
      size: expect.any(Object),
      figureId: expect.any(String),
      chartId: expect.any(String),
      sheetId: expect.any(String),
      definition: {
        ...toChartDataSource({
          dataSets: [{ dataRange: "A1", yAxisId: "y" }],
          dataSetsHaveTitle: false,
        }),
        stacked: false,
        legendPosition: "none",
        title: {},
        type: "bar",
        humanize: true,
      },
    };
  });

  test("Chart is inserted at correct position", async () => {
    setSelection(model, ["B2"]);
    await insertChart();
    const { width, height } = viewStore.sheetViewDimension;
    const figureUI = viewStore.visibleFigures[0];

    const payload = { ...defaultPayload };
    payload.definition = expect.any(Object);
    const x = (width - figureUI.width) / 2;
    const y = (height - figureUI.height) / 2;
    payload.offset = {
      x: x % DEFAULT_CELL_WIDTH,
      y: y % DEFAULT_CELL_HEIGHT,
    };
    (payload.col = Math.floor(x / DEFAULT_CELL_WIDTH)),
      (payload.row = Math.floor(y / DEFAULT_CELL_HEIGHT)),
      expect(dispatchSpy).toHaveBeenCalledWith("CREATE_CHART", payload);
    expect(figureUI).toMatchObject({ x, y });
  });

  test("Chart is selected and focused at insertion", async () => {
    await mountTestSpreadsheet();
    setSelection(model, ["B2"]);
    await insertChart();
    const figureId = model.getters.getFigures(model.getters.getActiveSheetId())[0].id;
    expect(dispatchSpy).toHaveBeenCalledWith("SELECT_FIGURE", { figureId });
    await nextTick();
    expect(document.activeElement?.classList).toContain("o-figure");
  });

  test("Chart side panel was opened at chart insertion", async () => {
    setSelection(model, ["B2"]);
    await insertChart();
    expect(openSidePanelSpy).toHaveBeenCalledWith("ChartPanel", undefined);
  });

  test("Chart is inserted at correct position for rows freeze", async () => {
    const sheetId = model.getters.getActiveSheetId();
    freezeRows(model, 5, sheetId);
    setSelection(model, ["B2"]);
    await insertChart();
    const { width, height } = viewStore.sheetViewDimension;
    const payload = { ...defaultPayload };
    payload.definition = expect.any(Object);
    payload.offset = {
      x: 40,
      y: 10.5,
    };
    payload.col = 2;
    payload.row = 14;
    expect(dispatchSpy).toHaveBeenCalledWith("CREATE_CHART", payload);
    const figureUI = viewStore.visibleFigures[0];
    expect({ x: figureUI.x, y: figureUI.y }).toStrictEqual({
      x: (width - figureUI.width) / 2,
      y: (height - figureUI.height) / 2,
    });
  });

  test("Chart is inserted at correct position inside bottomRight pane for columns freeze", async () => {
    const sheetId = model.getters.getActiveSheetId();
    freezeColumns(model, 4, sheetId);
    setSelection(model, ["B2"]);
    await insertChart();
    const { width, height } = viewStore.sheetViewDimension;
    const payload = { ...defaultPayload };
    payload.definition = expect.any(Object);
    payload.offset = {
      x: 40,
      y: 10.5,
    };
    payload.col = 2;
    payload.row = 14; // Position at the center of the viewport
    expect(dispatchSpy).toHaveBeenCalledWith("CREATE_CHART", payload);
    const figureUI = viewStore.visibleFigures[0];
    expect({ x: figureUI.x, y: figureUI.y }).toStrictEqual({
      x: (width - figureUI.width) / 2,
      y: (height - figureUI.height) / 2,
    });
  });

  test("Chart is inserted at correct position inside bottomRight pane for both freeze", async () => {
    const sheetId = model.getters.getActiveSheetId();
    freezeColumns(model, 4, sheetId);
    freezeRows(model, 5, sheetId);
    setSelection(model, ["B2"]);
    await insertChart();
    const { width, height } = viewStore.sheetViewDimension;
    const payload = { ...defaultPayload };
    payload.definition = expect.any(Object);
    payload.offset = {
      x: 40,
      y: 10.5,
    };
    payload.col = 2;
    payload.row = 14; // Position at the center of the viewport
    expect(dispatchSpy).toHaveBeenCalledWith("CREATE_CHART", payload);
    const figureUI = viewStore.visibleFigures[0];
    expect({ x: figureUI.x, y: figureUI.y }).toStrictEqual({
      x: (width - figureUI.width) / 2,
      y: (height - figureUI.height) / 2,
    });
  });

  test("Chart is inserted at the top left of the viewport when too small", async () => {
    setSelection(model, ["B2"]);
    resizeSheetView(env, { height: DEFAULT_FIGURE_HEIGHT / 2, width: DEFAULT_FIGURE_WIDTH / 2 });
    await insertChart();
    const payload = { ...defaultPayload };
    payload.definition = expect.any(Object);
    payload.offset = {
      x: 0,
      y: 0,
    };
    payload.col = 0;
    payload.row = 0;
    expect(dispatchSpy).toHaveBeenCalledWith("CREATE_CHART", payload);
    const figureUI = viewStore.visibleFigures[0];
    expect({ x: figureUI.x, y: figureUI.y }).toStrictEqual({
      x: 0,
      y: 0,
    });
  });

  test("Chart is inserted inside frozen pane if middle is frozen pane", async () => {
    addRows(model, "before", 0, 100);
    setSelection(model, ["B2"]);
    resizeSheetView(env, {
      height: DEFAULT_FIGURE_HEIGHT * 1.5,
      width: DEFAULT_FIGURE_WIDTH * 1.5,
    });
    const { bottom, right } = viewStore.activeMainViewport;
    freezeColumns(model, Math.floor(right / 2));
    freezeRows(model, Math.floor(bottom / 2));
    await insertChart();
    const { width, height } = viewStore.sheetViewDimension;
    const payload = { ...defaultPayload };
    payload.definition = expect.any(Object);
    payload.offset = {
      x: 38,
      y: 14.75,
    };
    payload.col = 1;
    payload.row = 3; // Position inside frozen pane
    expect(dispatchSpy).toHaveBeenCalledWith("CREATE_CHART", payload);
    const figureUI = viewStore.visibleFigures[0];
    expect({ x: figureUI.x, y: figureUI.y }).toStrictEqual({
      x: (width - figureUI.width) / 2,
      y: (height - figureUI.height) / 2,
    });
  });

  test("Chart is inserted at correct position on a scrolled viewport", async () => {
    setSelection(model, ["B2:B3"]);
    const { width, height } = viewStore.sheetViewDimension;
    addColumns(model, "after", "D", 100);
    addRows(model, "after", 4, 100);
    setViewportOffset(env, 2 * DEFAULT_CELL_WIDTH, 4 * DEFAULT_CELL_HEIGHT);
    await insertChart();
    const payload = { ...defaultPayload };
    payload.definition = expect.any(Object);
    payload.offset = {
      x: 40,
      y: 10.5,
    };
    payload.col = 4;
    payload.row = 18; // Position at the center of the viewport
    expect(dispatchSpy).toHaveBeenCalledWith("CREATE_CHART", payload);
    const figureUI = viewStore.visibleFigures[0];
    expect({ x: figureUI.x, y: figureUI.y }).toStrictEqual({
      x: 2 * DEFAULT_CELL_WIDTH + (width - figureUI.width) / 2,
      y: 4 * DEFAULT_CELL_HEIGHT + (height - figureUI.height) / 2,
    });
  });

  test("Chart is inserted at correct position on a scrolled viewport with frozen rows", async () => {
    const sheetId = model.getters.getActiveSheetId();
    freezeRows(model, 5, sheetId);
    setSelection(model, ["B2:B3"]);
    const { width, height } = viewStore.sheetViewDimension;
    addColumns(model, "after", "D", 100);
    addRows(model, "after", 4, 100);
    setViewportOffset(env, 2 * DEFAULT_CELL_WIDTH, 4 * DEFAULT_CELL_HEIGHT);
    await insertChart();
    const payload = { ...defaultPayload };
    payload.definition = expect.any(Object);
    payload.offset = {
      x: 40,
      y: 10.5,
    };
    payload.col = 4;
    payload.row = 18; // Position at the center of the viewport
    expect(dispatchSpy).toHaveBeenCalledWith("CREATE_CHART", payload);
    const figureUI = viewStore.visibleFigures[0];
    expect({ x: figureUI.x, y: figureUI.y }).toStrictEqual({
      x: 2 * DEFAULT_CELL_WIDTH + (width - figureUI.width) / 2,
      y: 4 * DEFAULT_CELL_HEIGHT + (height - figureUI.height) / 2,
    });
  });

  test("Chart is inserted at correct position on a scrolled viewport with columns frozen", async () => {
    const sheetId = model.getters.getActiveSheetId();
    freezeColumns(model, 4, sheetId);
    setSelection(model, ["B2:B3"]);
    const { width, height } = viewStore.sheetViewDimension;
    addColumns(model, "after", "D", 100);
    addRows(model, "after", 4, 100);
    setViewportOffset(env, 2 * DEFAULT_CELL_WIDTH, 4 * DEFAULT_CELL_HEIGHT);
    await insertChart();
    const payload = { ...defaultPayload };
    payload.definition = expect.any(Object);
    payload.offset = {
      x: 40,
      y: 10.5,
    };
    payload.col = 2;
    payload.row = 18; // Position at the center of the viewport
    expect(dispatchSpy).toHaveBeenCalledWith("CREATE_CHART", payload);
    const figureUI = viewStore.visibleFigures[0];
    expect({ x: figureUI.x, y: figureUI.y }).toStrictEqual({
      x: (width - figureUI.width) / 2, // figure is inside left frozen pane
      y: 4 * DEFAULT_CELL_HEIGHT + (height - figureUI.height) / 2,
    });
  });

  test("Chart is inserted at correct position on a scrolled viewport with both directions frozen", async () => {
    const sheetId = model.getters.getActiveSheetId();
    freezeColumns(model, 4, sheetId);
    freezeRows(model, 5, sheetId);
    setSelection(model, ["B2:B3"]);
    const { width, height } = viewStore.sheetViewDimension;
    addColumns(model, "after", "D", 100);
    addRows(model, "after", 4, 100);
    setViewportOffset(env, 2 * DEFAULT_CELL_WIDTH, 4 * DEFAULT_CELL_HEIGHT);
    await insertChart();
    const payload = { ...defaultPayload };
    payload.definition = expect.any(Object);
    payload.offset = {
      x: 40,
      y: 10.5,
    };
    payload.col = 2;
    payload.row = 18; // Position at the center of the viewport
    expect(dispatchSpy).toHaveBeenCalledWith("CREATE_CHART", payload);
    const figureUI = viewStore.visibleFigures[0];
    expect({ x: figureUI.x, y: figureUI.y }).toStrictEqual({
      x: (width - figureUI.width) / 2, // figure is inside left frozen pane
      y: 4 * DEFAULT_CELL_HEIGHT + (height - figureUI.height) / 2,
    });
  });

  test("Chart of single cell will extend the selection to find a 'table'", async () => {
    setSelection(model, ["A2"]);
    await insertChart();
    const payload = {
      ...defaultPayload,
      definition: {
        ...defaultPayload.definition,
        legendPosition: "top",
        ...toChartDataSource({
          dataSets: [
            { dataRange: "B1:B5" },
            { dataRange: "C1:C5" },
            { dataRange: "D1:D5" },
            { dataRange: "E1:E5" },
            { dataRange: "F1:F5" },
            { dataRange: "G1:G5" },
            { dataRange: "H1:H5" },
          ],
          labelRange: "A1:A5",
          dataSetsHaveTitle: true,
        }),
      },
    };
    expect(dispatchSpy).toHaveBeenCalledWith("CREATE_CHART", payload);
    expect(zoneToXc(model.getters.getSelectedZone())).toBe("A1:H5");
  });

  test("Chart can be inserted with unbounded ranges", async () => {
    setSelection(model, ["A1:B100"], { unbounded: true });
    await insertChart();
    const chartId = model.getters.getChartIds(model.getters.getActiveSheetId())[0];
    expect(model.getters.getChartDefinition(chartId)).toMatchObject(
      toChartDataSource({
        dataSets: [{ dataRange: "B:B" }],
        labelRange: "A:A",
      })
    );
  });
});

describe("Smart chart type detection", () => {
  type DatasetDescriptor = string[];

  let model: Model;
  let env: SpreadsheetChildEnv;

  beforeEach(() => {
    model = new Model();
    env = makeTestEnv({ model });
  });

  /**
   * Create a dataset according to the given pattern. The pattern is a list of column types, with possible modifiers
   * (eg. ["text_with_header", "number_repeated", "empty", "date"]) would create a dataset of 4 columns.
   */
  function createDatasetFromDescription(description: DatasetDescriptor) {
    for (let col = 0; col < description.length; col++) {
      const colDescription = description[col];
      const hasHeader = colDescription.includes("_with_header");
      const repeatedValues = colDescription.includes("_repeated");
      const type = colDescription.replace("_with_header", "").replace("_repeated", "");

      for (let row = 0; row < 6; row++) {
        const xc = toXC(col, row);
        if (row === 0 && hasHeader) {
          setCellContent(model, xc, `Header${col}`);
          continue;
        }
        if (type === "empty") {
          continue;
        }
        const generator = repeatedValues ? row % 3 : row;
        if (type === "text") {
          setCellContent(model, xc, `Text${generator}`);
        } else if (type === "number") {
          setCellContent(model, xc, `${generator}`);
        } else if (type === "date") {
          setCellContent(model, xc, `2022-10-${generator + 1}`);
        } else if (type === "percentage") {
          setCellContent(model, xc, `${generator * 10}%`);
        } else if (type === "boolean") {
          setCellContent(model, xc, generator % 2 === 0 ? "=TRUE()" : "=FALSE()");
        }
      }
    }
  }

  test("Single cell: create a scorecard", async () => {
    setCellContent(model, "C3", "100");
    setSelection(model, ["C3"]);
    await doAction(["insert", "insert_chart"], env);
    const chartId = model.getters.getChartIds(model.getters.getActiveSheetId())[0];
    expect(model.getters.getChartDefinition(chartId)).toMatchObject({
      type: "scorecard",
      keyValue: "=C3",
    });
  });

  test.each([
    [["percentage"], { type: "pie", dataSetsHaveTitle: false }],
    [["number"], { type: "bar", dataSetsHaveTitle: false }],
    [["text"], { type: "pie", labelRange: "A1:A6", aggregated: true, dataSetsHaveTitle: true }], // categorical pie chart, the data range is also the label range
    [["boolean"], { type: "pie", labelRange: "A1:A6", aggregated: true, dataSetsHaveTitle: false }],
    [["percentage_with_header"], { type: "pie", dataSetsHaveTitle: true }],
  ] as const)("Single column %s creates chart", async (datasetPattern, expected) => {
    createDatasetFromDescription([...datasetPattern]);
    await doAction(["insert", "insert_chart"], env);

    const chartId = model.getters.getChartIds(model.getters.getActiveSheetId())[0];

    const definition = model.getters.getChartDefinition(chartId);
    expect(definition).toMatchObject({
      type: expected.type,
      ...toChartDataSource({
        dataSets: [{ dataRange: "A1:A6" }],
        labelRange: "labelRange" in expected ? expected.labelRange : undefined,
        dataSetsHaveTitle: expected.dataSetsHaveTitle,
      }),
    });
  });

  test.each([
    [["date"], undefined],
    [["date_with_header"], "Header0"],
  ] as const)("Single date column %s creates a calendar chart", async (datasetPattern, title) => {
    createDatasetFromDescription([...datasetPattern]);
    await doAction(["insert", "insert_chart"], env);

    const chartId = model.getters.getChartIds(model.getters.getActiveSheetId())[0];
    const definition = model.getters.getChartDefinition(chartId);
    expect(definition).toMatchObject({
      type: "calendar",
      title: title ? { text: title } : {},
      dataSource: {
        type: "range",
        dataSets: [],
        labelRange: "A1:A6",
      },
      horizontalGroupBy: "day_of_week",
      verticalGroupBy: "month_number",
    });
  });

  test.each([
    [["text", "percentage"], { type: "pie", dataSetsHaveTitle: false }],
    [["number", "percentage"], { type: "pie", dataSetsHaveTitle: false }],
    [["date", "percentage"], { type: "pie", dataSetsHaveTitle: false }],
    [["number", "number"], { type: "scatter", dataSetsHaveTitle: false }],
    [["date", "number"], { type: "line", dataSetsHaveTitle: false }],
    [["text", "number"], { type: "bar", dataSetsHaveTitle: false }],
    [["text_repeated", "number"], { type: "treemap", dataSetsHaveTitle: false }],
    [["text", "date"], { type: "bar", dataSetsHaveTitle: false }],
    [["number", "text"], { type: "bar", dataSetsHaveTitle: true }],
    [["text", "number_with_header"], { type: "bar", dataSetsHaveTitle: true }],
    [["number", "number_with_header"], { type: "scatter", dataSetsHaveTitle: true }],
  ])("Two columns %s creates %s chart", async (datasetPattern, expected) => {
    createDatasetFromDescription(datasetPattern);
    await doAction(["insert", "insert_chart"], env);

    const expectedDataset =
      expected.type === "treemap" ? [{ dataRange: "A1:A6" }] : [{ dataRange: "B1:B6" }];
    const expectedLabelRange = expected.type === "treemap" ? "B1:B6" : "A1:A6";

    const chartId = model.getters.getChartIds(model.getters.getActiveSheetId())[0];
    expect(model.getters.getChartDefinition(chartId)).toMatchObject({
      type: expected.type,
      ...toChartDataSource({
        dataSets: expectedDataset,
        labelRange: expectedLabelRange,
        dataSetsHaveTitle: expected.dataSetsHaveTitle,
      }),
    });
  });

  test.each([
    [["text", "text", "number"], { type: "treemap", dataSetsHaveTitle: false }],
    [["text", "text", "text", "number"], { type: "sunburst", dataSetsHaveTitle: false }],
    [["text", "text", "percentage"], { type: "treemap", dataSetsHaveTitle: false }],
    [["text", "text", "text", "percentage"], { type: "sunburst", dataSetsHaveTitle: false }],
    [["text", "text", "text", "number_with_header"], { type: "sunburst", dataSetsHaveTitle: true }],
  ])(
    "Multiple text columns  %s create a %s hierarchical chart",
    async (datasetPattern, expected) => {
      createDatasetFromDescription(datasetPattern);
      await doAction(["insert", "insert_chart"], env);

      const datasetLastCol = datasetPattern.findIndex((p) => !p.includes("text"));
      const expectedDatasets: ChartRangeDataSource<string>["dataSets"] = [];
      for (let i = 0; i < datasetLastCol; i++) {
        expectedDatasets.push({
          dataRange: toXC(i, 0) + ":" + toXC(i, 5),
          dataSetId: i.toString(),
        });
      }
      const expectedLabelRange = toXC(datasetLastCol, 0) + ":" + toXC(datasetLastCol, 5);

      const chartId = model.getters.getChartIds(model.getters.getActiveSheetId())[0];
      expect(model.getters.getChartDefinition(chartId)).toMatchObject({
        type: expected.type,
        ...toChartDataSource({
          dataSets: expectedDatasets,
          labelRange: expectedLabelRange,
          dataSetsHaveTitle: expected.dataSetsHaveTitle,
        }),
      });
    }
  );

  test.each([
    [["text", "percentage", "percentage"], { type: "pie", dataSetsHaveTitle: false }],
    [
      ["number", "percentage", "percentage", "percentage"],
      { type: "pie", dataSetsHaveTitle: false },
    ],
    [["date", "number", "number"], { type: "line", dataSetsHaveTitle: false }],
    // Any other combination should give a bar chart with correct datasets
    [["text", "number", "percentage"], { type: "bar", dataSetsHaveTitle: false }],
    [["text", "number", "date"], { type: "bar", dataSetsHaveTitle: false }],
    [["text", "number", "number"], { type: "bar", dataSetsHaveTitle: false }],
    [["number", "number", "number"], { type: "bar", dataSetsHaveTitle: false }],
    [["date", "date", "number", "text"], { type: "bar", dataSetsHaveTitle: false }],
    [["text", "number_with_header", "percentage"], { type: "bar", dataSetsHaveTitle: true }],
    [["text", "number", "date_with_header"], { type: "bar", dataSetsHaveTitle: true }],
  ])("Multiple columns  %s create a %s chart", async (datasetPattern, expected) => {
    createDatasetFromDescription(datasetPattern);
    await doAction(["insert", "insert_chart"], env);

    const expectedDatasets: ChartRangeDataSource<string>["dataSets"] = [];
    for (let i = 1; i < datasetPattern.length; i++) {
      expectedDatasets.push({
        dataRange: toXC(i, 0) + ":" + toXC(i, 5),
        dataSetId: (i - 1).toString(),
      });
    }

    const chartId = model.getters.getChartIds(model.getters.getActiveSheetId())[0];
    expect(model.getters.getChartDefinition(chartId)).toMatchObject({
      type: expected.type,
      ...toChartDataSource({
        dataSets: expectedDatasets,
        labelRange: "A1:A6",
        dataSetsHaveTitle: expected.dataSetsHaveTitle,
      }),
    });
  });

  test("Empty columns are passed in the chart dataset if the whole selection is empty", async () => {
    setSelection(model, ["A1:B6"]);
    await doAction(["insert", "insert_chart"], env);
    const chartId = model.getters.getChartIds(model.getters.getActiveSheetId())[0];
    expect(model.getters.getChartDefinition(chartId)).toMatchObject({
      type: "bar",
      ...toChartDataSource({
        dataSets: [{ dataRange: "A1:A6" }, { dataRange: "B1:B6" }],
        dataSetsHaveTitle: false,
      }),
    });
  });

  test("Empty columns are ignored in the chart dataset if other columns are not empty", async () => {
    createDatasetFromDescription(["number", "empty", "number"]);
    setSelection(model, ["A1:C6"]);
    await doAction(["insert", "insert_chart"], env);
    const chartId = model.getters.getChartIds(model.getters.getActiveSheetId())[0];
    expect(model.getters.getChartDefinition(chartId)).toMatchObject({
      type: "scatter",
      ...toChartDataSource({
        dataSets: [{ dataRange: "C1:C6" }],
        labelRange: "A1:A6",
        dataSetsHaveTitle: false,
      }),
    });
  });

  test.each<[DatasetDescriptor, Partial<ChartDefinition>]>([
    [["number"], { legendPosition: "none" }],
    [["text", "number"], { legendPosition: "none" }],
    [["date", "number", "number"], { legendPosition: "top" }],
    [["text", "number", "number"], { legendPosition: "top" }],
  ])(
    "Pie charts and charts with more than one column in their dataset %s have a legend",
    async (datasetPattern, expected) => {
      createDatasetFromDescription(datasetPattern);
      await doAction(["insert", "insert_chart"], env);

      const chartId = model.getters.getChartIds(model.getters.getActiveSheetId())[0];
      expect(model.getters.getChartDefinition(chartId)).toMatchObject(expected);
    }
  );
});
