import { BorderDescr, CommandResult } from "../../src";
import { DEFAULT_BORDER_DESC } from "../../src/constants";
import { Model } from "../../src/model";
import { ClipboardStore } from "../../src/stores/clipboard_store";
import {
  activateSheet,
  addColumns,
  addRows,
  clearFormatting,
  cut,
  deleteCells,
  deleteColumns,
  deleteContent,
  deleteRows,
  duplicateSheet,
  merge,
  moveColumns,
  moveRows,
  paste,
  selectCell,
  setAnchorCorner,
  setBorders,
  setBordersOnTarget,
  setCellContent,
  setZoneBorders,
  undo,
} from "../test_helpers/commands_helpers";
import {
  getBorder,
  getCell,
  getCellContent,
  getComputedBorder,
} from "../test_helpers/getters_helpers";
import "../test_helpers/helpers"; // to have getcontext mocks
import { makeStoreWithModel } from "../test_helpers/stores";

describe("borders", () => {
  test("can add and remove a border, on empty cell", () => {
    const model = new Model();

    // select B2, set its top border, then clear it
    selectCell(model, "B2");
    setZoneBorders(model, { position: "top" });
    expect(getBorder(model, "B2")).toEqual({ top: DEFAULT_BORDER_DESC });
    setZoneBorders(model, { position: "clear" });

    expect(getBorder(model, "B2")).toBeNull();

    // select B2, set its left border, then clear it
    selectCell(model, "B2");
    setZoneBorders(model, { position: "left" });
    expect(getBorder(model, "B2")).toEqual({ left: DEFAULT_BORDER_DESC });
    setZoneBorders(model, { position: "clear" });
    expect(getCell(model, "B2")).toBeUndefined();

    // select B2, set its bottom border, then clear it
    selectCell(model, "B2");
    setZoneBorders(model, { position: "bottom" });
    expect(getBorder(model, "B2")).toEqual({ bottom: DEFAULT_BORDER_DESC });
    setZoneBorders(model, { position: "clear" });
    expect(getCell(model, "B2")).toBeUndefined();

    // select B2, set its right border, then clear it
    selectCell(model, "B2");
    setZoneBorders(model, { position: "right" });
    expect(getBorder(model, "B2")).toEqual({ right: DEFAULT_BORDER_DESC });
    setZoneBorders(model, { position: "clear" });
    expect(getCell(model, "B2")).toBeUndefined();
  });

  test("can add and remove a top border, on existing cell", () => {
    const model = new Model();

    // select B2
    setCellContent(model, "B2", "content");
    selectCell(model, "B2");

    // set a border top
    setZoneBorders(model, { position: "top" });

    expect(getBorder(model, "B2")).toEqual({ top: DEFAULT_BORDER_DESC });

    // clear borders
    setZoneBorders(model, { position: "clear" });
    expect(getBorder(model, "B2")).toBeNull();
  });

  test("can add and remove a top border, on a selection", () => {
    const model = new Model();

    // select B2:C2
    selectCell(model, "B2");
    setAnchorCorner(model, "C2");

    // set a border top
    setZoneBorders(model, { position: "top" });

    expect(getBorder(model, "B2")).toEqual({ top: DEFAULT_BORDER_DESC });
    expect(getBorder(model, "C2")).toEqual({ top: DEFAULT_BORDER_DESC });
  });

  test("can clear a zone", () => {
    const model = new Model();

    // select C3 and add a border
    selectCell(model, "C3");
    setZoneBorders(model, { position: "top" });
    expect(getBorder(model, "C3")).toBeDefined();

    // select A1:F6
    selectCell(model, "A1");
    setAnchorCorner(model, "F6");

    // clear all borders
    setZoneBorders(model, { position: "clear" });

    expect(getCell(model, "C3")).toBeUndefined();
  });

  test("set the same border twice is cancelled", () => {
    const model = new Model();
    const border = { top: DEFAULT_BORDER_DESC };
    setBorders(model, "A1", border);
    expect(setBorders(model, "A1", border)).toBeCancelledBecause(CommandResult.NoChanges);
  });

  test("reset border when there is no border is cancelled", () => {
    const model = new Model();
    expect(setBorders(model, "A1", undefined)).toBeCancelledBecause(CommandResult.NoChanges);
    expect(setBorders(model, "A1", { top: undefined })).toBeCancelledBecause(
      CommandResult.NoChanges
    );
  });

  test("Can set border on a target", () => {
    const model = new Model();
    const border = { top: DEFAULT_BORDER_DESC };
    setBordersOnTarget(model, ["A1:A2", "B2:B3"], border);
    expect(getBorder(model, "A1")).toEqual({
      top: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "A2")).toEqual({
      top: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "B2")).toEqual({
      top: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "B3")).toEqual({
      top: DEFAULT_BORDER_DESC,
    });
  });

  test("Preserves side borders when combining external and all via command", () => {
    const model = new Model();
    const defaultBorder = DEFAULT_BORDER_DESC;

    setZoneBorders(model, { position: "all" }, ["C3"]);
    setBordersOnTarget(model, ["C2"], {
      top: defaultBorder,
      right: defaultBorder,
      left: defaultBorder,
    });
    setBordersOnTarget(model, ["C4"], {
      bottom: defaultBorder,
      left: defaultBorder,
      right: defaultBorder,
    });
    setBordersOnTarget(model, ["B3"], {
      top: defaultBorder,
      bottom: defaultBorder,
      left: defaultBorder,
    });
    setBordersOnTarget(model, ["D3"], {
      top: defaultBorder,
      bottom: defaultBorder,
      right: defaultBorder,
    });

    expect(getBorder(model, "C3")).toEqual({
      top: defaultBorder,
      bottom: defaultBorder,
      left: defaultBorder,
      right: defaultBorder,
    });
    expect(getBorder(model, "C2")).toEqual({
      top: defaultBorder,
      left: defaultBorder,
      right: defaultBorder,
    });
    expect(getBorder(model, "C4")).toEqual({
      bottom: defaultBorder,
      left: defaultBorder,
      right: defaultBorder,
    });
    expect(getBorder(model, "B3")).toEqual({
      top: defaultBorder,
      left: defaultBorder,
      bottom: defaultBorder,
    });
    expect(getBorder(model, "D3")).toEqual({
      top: defaultBorder,
      bottom: defaultBorder,
      right: defaultBorder,
    });
  });

  test("import preserves merged cell borders", () => {
    const b = DEFAULT_BORDER_DESC;
    const allSides = { top: b, bottom: b, left: b, right: b };
    const model = new Model();

    setZoneBorders(model, { position: "all" }, ["B2:C3"]);
    merge(model, "C2:C3");

    const importedModel = new Model(model.exportData());

    expect(getBorder(importedModel, "B2")).toEqual(allSides);
    expect(getBorder(importedModel, "B3")).toEqual(allSides);
    expect(getBorder(importedModel, "C2")).toEqual({ left: b, top: b, right: b });
    expect(getBorder(importedModel, "C3")).toEqual({ left: b, bottom: b, right: b });
  });

  test("can set all borders in a zone", () => {
    const model = new Model();

    // select B2, then expand selection to B2:C3
    selectCell(model, "B2");
    setAnchorCorner(model, "C3");

    // set all borders
    setZoneBorders(model, { position: "all" });
    const all = {
      left: DEFAULT_BORDER_DESC,
      top: DEFAULT_BORDER_DESC,
      bottom: DEFAULT_BORDER_DESC,
      right: DEFAULT_BORDER_DESC,
    };
    expect(getBorder(model, "B2")).toEqual(all);
    expect(getBorder(model, "B3")).toEqual(all);
    expect(getBorder(model, "C2")).toEqual(all);
    expect(getBorder(model, "C3")).toEqual(all);
  });

  test("setting top border in a zone only set top row", () => {
    const model = new Model();

    // select B2, then expand selection to B2:C3
    selectCell(model, "B2");
    setAnchorCorner(model, "C3");

    // set all borders
    setZoneBorders(model, { position: "top" });
    const border = {
      top: DEFAULT_BORDER_DESC,
    };
    expect(getBorder(model, "B2")).toEqual(border);
    expect(getBorder(model, "C2")).toEqual(border);
    expect(getCell(model, "B3")).toBeUndefined();
    expect(getCell(model, "C3")).toBeUndefined();
  });

  test("clearing a common border in a neighbour cell", () => {
    const model = new Model();

    // select B2, then set its right border
    selectCell(model, "B2");
    setZoneBorders(model, { position: "right" });
    expect(getBorder(model, "B2")).toEqual({ right: DEFAULT_BORDER_DESC });

    // select C2 then clear it
    selectCell(model, "C2");
    setZoneBorders(model, { position: "clear" });
    expect(getCell(model, "B2")).toBeUndefined();
  });

  test("setting external border in a zone works", () => {
    const model = new Model();

    // select B2, then expand selection to B2:D4
    selectCell(model, "B2");
    setAnchorCorner(model, "D4");

    // set external borders
    setZoneBorders(model, { position: "external" });
    expect(getBorder(model, "B2")).toEqual({ top: DEFAULT_BORDER_DESC, left: DEFAULT_BORDER_DESC });
    expect(getBorder(model, "C2")).toEqual({ top: DEFAULT_BORDER_DESC });
    expect(getBorder(model, "D2")).toEqual({
      top: DEFAULT_BORDER_DESC,
      right: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "B3")).toEqual({ left: DEFAULT_BORDER_DESC });
    expect(getCell(model, "C3")).toBeUndefined();
    expect(getBorder(model, "D3")).toEqual({ right: DEFAULT_BORDER_DESC });
    expect(getBorder(model, "B4")).toEqual({
      bottom: DEFAULT_BORDER_DESC,
      left: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "C4")).toEqual({ bottom: DEFAULT_BORDER_DESC });
    expect(getBorder(model, "D4")).toEqual({
      bottom: DEFAULT_BORDER_DESC,
      right: DEFAULT_BORDER_DESC,
    });
  });

  test("setting internal horizontal borders in a zone works", () => {
    const model = new Model();

    // select B2, then expand selection to B2:C4
    selectCell(model, "B2");
    setAnchorCorner(model, "C4");

    setZoneBorders(model, { position: "h" });
    expect(getBorder(model, "B2")).toEqual({ bottom: DEFAULT_BORDER_DESC });
    expect(getBorder(model, "C2")).toEqual({ bottom: DEFAULT_BORDER_DESC });
    expect(getBorder(model, "B3")).toEqual({
      top: DEFAULT_BORDER_DESC,
      bottom: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "C3")).toEqual({
      top: DEFAULT_BORDER_DESC,
      bottom: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "B4")).toEqual({ top: DEFAULT_BORDER_DESC });
    expect(getBorder(model, "C4")).toEqual({ top: DEFAULT_BORDER_DESC });
  });

  test("setting internal horizontal border on a sincel cell does nothing", () => {
    const model = new Model();
    setZoneBorders(model, { position: "h" }, ["B2"]);
    expect(getBorder(model, "B1")).toBeNull();
    expect(getBorder(model, "B2")).toBeNull();
    expect(getBorder(model, "B3")).toBeNull();
  });

  test("setting internal vertical borders in a zone works", () => {
    const model = new Model();

    // select B2, then expand selection to B2:D4
    selectCell(model, "B2");
    setAnchorCorner(model, "D4");

    setZoneBorders(model, { position: "v" });
    expect(getBorder(model, "B2")).toEqual({ right: DEFAULT_BORDER_DESC });
    expect(getBorder(model, "B3")).toEqual({ right: DEFAULT_BORDER_DESC });
    expect(getBorder(model, "B4")).toEqual({ right: DEFAULT_BORDER_DESC });
    expect(getBorder(model, "C2")).toEqual({
      left: DEFAULT_BORDER_DESC,
      right: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "C3")).toEqual({
      left: DEFAULT_BORDER_DESC,
      right: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "C4")).toEqual({
      left: DEFAULT_BORDER_DESC,
      right: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "D2")).toEqual({ left: DEFAULT_BORDER_DESC });
    expect(getBorder(model, "D3")).toEqual({ left: DEFAULT_BORDER_DESC });
    expect(getBorder(model, "D4")).toEqual({ left: DEFAULT_BORDER_DESC });
  });

  test("setting internal vertical border on a sincel cell does nothing", () => {
    const model = new Model();
    setZoneBorders(model, { position: "v" }, ["B2"]);
    expect(getBorder(model, "A2")).toBeNull();
    expect(getBorder(model, "B2")).toBeNull();
    expect(getBorder(model, "C2")).toBeNull();
  });

  test("setting internal  borders in a zone works", () => {
    const model = new Model();

    // select B2, then expand selection to B2:D4
    selectCell(model, "B2");
    setAnchorCorner(model, "D4");

    setZoneBorders(model, { position: "hv" });
    expect(getBorder(model, "B2")).toEqual({
      bottom: DEFAULT_BORDER_DESC,
      right: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "B3")).toEqual({
      bottom: DEFAULT_BORDER_DESC,
      top: DEFAULT_BORDER_DESC,
      right: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "B4")).toEqual({
      top: DEFAULT_BORDER_DESC,
      right: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "C2")).toEqual({
      left: DEFAULT_BORDER_DESC,
      right: DEFAULT_BORDER_DESC,
      bottom: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "C3")).toEqual({
      top: DEFAULT_BORDER_DESC,
      left: DEFAULT_BORDER_DESC,
      bottom: DEFAULT_BORDER_DESC,
      right: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "C4")).toEqual({
      top: DEFAULT_BORDER_DESC,
      left: DEFAULT_BORDER_DESC,
      right: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "D2")).toEqual({
      left: DEFAULT_BORDER_DESC,
      bottom: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "D3")).toEqual({
      top: DEFAULT_BORDER_DESC,
      left: DEFAULT_BORDER_DESC,
      bottom: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "D4")).toEqual({ top: DEFAULT_BORDER_DESC, left: DEFAULT_BORDER_DESC });
  });

  test("setting internal border on a sincel cell does nothing", () => {
    const model = new Model();
    setZoneBorders(model, { position: "hv" }, ["B2"]);
    expect(getBorder(model, "A2")).toBeNull();
    expect(getBorder(model, "B1")).toBeNull();
    expect(getBorder(model, "B2")).toBeNull();
    expect(getBorder(model, "B3")).toBeNull();
    expect(getBorder(model, "C2")).toBeNull();
  });

  test("deleting a cell with a border does not remove the border", () => {
    const model = new Model();

    // select B2 and set its top border
    setCellContent(model, "B2", "content");
    selectCell(model, "B2");
    setZoneBorders(model, { position: "top" });

    expect(getBorder(model, "B2")).toBeDefined();
    deleteContent(model, ["B2"]);
    expect(getBorder(model, "B2")).toBeDefined();
  });

  test("can undo and redo a setBorder operation on an non empty cell", () => {
    const model = new Model();
    setCellContent(model, "B2", "some content");
    selectCell(model, "B2");
    setZoneBorders(model, { position: "all" });

    expect(getCellContent(model, "B2")).toBe("some content");
    expect(getBorder(model, "B2")).toBeDefined();
    undo(model);
    expect(getCellContent(model, "B2")).toBe("some content");
    expect(getBorder(model, "B2")).toBeNull();
  });

  test("can clear formatting (border)", () => {
    const model = new Model();
    setCellContent(model, "B1", "b1");
    selectCell(model, "B1");
    setZoneBorders(model, { position: "all" });

    expect(getBorder(model, "B1")).toBeDefined();
    clearFormatting(model, "B1");
    expect(getBorder(model, "B1")).toBeNull();
  });

  test("can clear formatting (border) after selecting all cells", () => {
    const model = new Model();
    selectCell(model, "A1");

    setAnchorCorner(model, "Z100");
    const activeSheetId = model.getters.getActiveSheetId();
    expect(model.getters.getSelectedZones()[0]).toEqual({
      left: 0,
      top: 0,
      right: model.getters.getNumberCols(activeSheetId) - 1,
      bottom: model.getters.getNumberRows(activeSheetId) - 1,
    });
    setZoneBorders(model, { position: "all" });
    expect(getBorder(model, "B1")).toBeDefined();
    setZoneBorders(model, { position: "clear" });
    expect(getCell(model, "B1")).toBeUndefined();
  });

  test("set all border of a cell", () => {
    const model = new Model();
    const s: BorderDescr = { style: "medium", color: "#FF0000" };
    setBorders(model, "A1", { bottom: s, top: s, left: s, right: s });
    expect(getBorder(model, "A1")).toEqual({ bottom: s, top: s, left: s, right: s });
  });

  test("cut & paste a border", () => {
    const model = new Model();
    makeStoreWithModel(model, ClipboardStore);
    setZoneBorders(model, { position: "external" }, ["B2"]);
    expect(getBorder(model, "B2")).toEqual({
      top: DEFAULT_BORDER_DESC,
      bottom: DEFAULT_BORDER_DESC,
      right: DEFAULT_BORDER_DESC,
      left: DEFAULT_BORDER_DESC,
    });
    cut(model, "B2");
    paste(model, "C4");
    expect(getBorder(model, "C4")).toEqual({
      top: DEFAULT_BORDER_DESC,
      bottom: DEFAULT_BORDER_DESC,
      right: DEFAULT_BORDER_DESC,
      left: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "B2")).toBeNull();
  });
});

describe("Grid manipulation", () => {
  let model: Model;
  beforeEach(() => {
    model = new Model();
    makeStoreWithModel(model, ClipboardStore);
  });

  test("ADD_COLUMNS_ROWS with dimension col before with external borders", () => {
    setZoneBorders(model, { position: "external" }, ["B2"]);
    addColumns(model, "before", "B", 1);
    expect(getBorder(model, "B2")).toBeNull();
    expect(getBorder(model, "C2")).toEqual({
      top: DEFAULT_BORDER_DESC,
      left: DEFAULT_BORDER_DESC,
      right: DEFAULT_BORDER_DESC,
      bottom: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "D2")).toBeNull();
  });

  test("move duplicated border when col is inserted before", () => {
    const model = new Model();
    const firstSheetId = model.getters.getActiveSheetId();
    const secondSheetId = "42";
    setZoneBorders(model, { position: "external" }, ["B2"]);
    expect(getBorder(model, "B2", firstSheetId)).toEqual({
      top: DEFAULT_BORDER_DESC,
      left: DEFAULT_BORDER_DESC,
      right: DEFAULT_BORDER_DESC,
      bottom: DEFAULT_BORDER_DESC,
    });
    duplicateSheet(model, firstSheetId, secondSheetId);
    addColumns(model, "before", "A", 1, secondSheetId);
    expect(getBorder(model, "B2", firstSheetId)).toEqual({
      top: DEFAULT_BORDER_DESC,
      left: DEFAULT_BORDER_DESC,
      right: DEFAULT_BORDER_DESC,
      bottom: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "B2", secondSheetId)).toBeNull();
    expect(getBorder(model, "C2", secondSheetId)).toEqual({
      top: DEFAULT_BORDER_DESC,
      left: DEFAULT_BORDER_DESC,
      right: DEFAULT_BORDER_DESC,
      bottom: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "D2", secondSheetId)).toBeNull();
  });

  test("move duplicated border when row is inserted before", () => {
    const model = new Model();
    const firstSheetId = model.getters.getActiveSheetId();
    const secondSheetId = "42";
    setZoneBorders(model, { position: "external" }, ["B2"]);
    duplicateSheet(model, firstSheetId, secondSheetId);
    addRows(model, "before", 0, 1, secondSheetId);
    expect(getBorder(model, "B2", firstSheetId)).toEqual({
      top: DEFAULT_BORDER_DESC,
      left: DEFAULT_BORDER_DESC,
      right: DEFAULT_BORDER_DESC,
      bottom: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "B3", secondSheetId)).toEqual({
      top: DEFAULT_BORDER_DESC,
      left: DEFAULT_BORDER_DESC,
      right: DEFAULT_BORDER_DESC,
      bottom: DEFAULT_BORDER_DESC,
    });
  });

  test("ADD_COLUMNS_ROWS with dimension col before with external borders in the column before", () => {
    setZoneBorders(model, { position: "external" }, ["B2"]);
    addColumns(model, "before", "C", 1);
    expect(getBorder(model, "A2")).toBeNull();
    expect(getBorder(model, "B2")).toEqual({
      top: DEFAULT_BORDER_DESC,
      left: DEFAULT_BORDER_DESC,
      right: DEFAULT_BORDER_DESC,
      bottom: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "C2")).toBeNull();
    expect(getBorder(model, "D2")).toBeNull();
  });

  test("ADD_COLUMNS_ROWS with dimension col before with external borders in the column after", () => {
    setZoneBorders(model, { position: "external" }, ["B2"]);
    addColumns(model, "before", "A", 1);
    expect(getBorder(model, "A2")).toBeNull();
    expect(getBorder(model, "B2")).toBeNull();
    expect(getBorder(model, "C2")).toEqual({
      top: DEFAULT_BORDER_DESC,
      left: DEFAULT_BORDER_DESC,
      right: DEFAULT_BORDER_DESC,
      bottom: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "D2")).toBeNull();
  });

  test("ADD_COLUMNS_ROWS with dimension row before with external borders", () => {
    setZoneBorders(model, { position: "external" }, ["B2"]);
    addRows(model, "before", 1, 1);
    expect(getBorder(model, "B2")).toBeNull();
    expect(getBorder(model, "B3")).toEqual({
      top: DEFAULT_BORDER_DESC,
      left: DEFAULT_BORDER_DESC,
      right: DEFAULT_BORDER_DESC,
      bottom: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "C2")).toBeNull();
    expect(getBorder(model, "B4")).toBeNull();
  });

  test("ADD_COLUMNS_ROWS with dimension row before with external borders in the column before", () => {
    setZoneBorders(model, { position: "external" }, ["B2"]);
    addRows(model, "before", 2, 1);
    expect(getBorder(model, "A2")).toBeNull();
    expect(getBorder(model, "B2")).toEqual({
      top: DEFAULT_BORDER_DESC,
      left: DEFAULT_BORDER_DESC,
      right: DEFAULT_BORDER_DESC,
      bottom: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "C2")).toBeNull();
    expect(getBorder(model, "B3")).toBeNull();
    expect(getBorder(model, "B4")).toBeNull();
  });

  test("ADD_COLUMNS_ROWS with dimension row before with external borders in the column after", () => {
    setZoneBorders(model, { position: "external" }, ["B2"]);
    addRows(model, "before", 0, 1);
    expect(getBorder(model, "B2")).toBeNull();
    expect(getBorder(model, "B3")).toEqual({
      top: DEFAULT_BORDER_DESC,
      left: DEFAULT_BORDER_DESC,
      right: DEFAULT_BORDER_DESC,
      bottom: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "C2")).toBeNull();
    expect(getBorder(model, "B4")).toBeNull();
  });

  test("Remove multiple headers before the borders", () => {
    const b = DEFAULT_BORDER_DESC;
    setZoneBorders(model, { position: "external" }, ["C3"]);
    deleteRows(model, [0, 1]);
    expect(getBorder(model, "B1")).toBeNull();
    expect(getBorder(model, "C1")).toEqual({ top: b, left: b, right: b, bottom: b });
    expect(getBorder(model, "D1")).toBeNull();
    expect(getBorder(model, "C2")).toBeNull();

    deleteColumns(model, ["A", "B"]);
    expect(getBorder(model, "A1")).toEqual({ top: b, left: b, right: b, bottom: b });
    expect(getBorder(model, "B1")).toBeNull();
    expect(getBorder(model, "A2")).toBeNull();
  });

  test("Borders are correctly duplicated on sheet dup", () => {
    setZoneBorders(model, { position: "external" }, ["B2"]);
    const sheetId = model.getters.getActiveSheetId();
    const sheetIdTo = "42";
    duplicateSheet(model, sheetId, sheetIdTo);
    activateSheet(model, sheetIdTo);
    expect(getBorder(model, "B2")).toEqual({
      top: DEFAULT_BORDER_DESC,
      left: DEFAULT_BORDER_DESC,
      right: DEFAULT_BORDER_DESC,
      bottom: DEFAULT_BORDER_DESC,
    });
  });

  test("Delete cell correctly move borders on shift up", () => {
    setZoneBorders(model, { position: "external" }, ["C3:D4"]);
    deleteCells(model, "C1", "up");
    expect(getBorder(model, "C1")).toBeNull();
    expect(getBorder(model, "C2")).toEqual({ top: DEFAULT_BORDER_DESC, left: DEFAULT_BORDER_DESC });
    expect(getBorder(model, "C3")).toEqual({
      bottom: DEFAULT_BORDER_DESC,
      left: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "D2")).toBeNull();
    expect(getBorder(model, "D3")).toEqual({
      top: DEFAULT_BORDER_DESC,
      right: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "D4")).toEqual({
      bottom: DEFAULT_BORDER_DESC,
      right: DEFAULT_BORDER_DESC,
    });
  });

  test("Delete a cell correctly move all the borders on shift up", () => {
    setZoneBorders(model, { position: "external" }, ["C3"]);
    deleteCells(model, "C1", "up");
    expect(getBorder(model, "C1")).toBeNull();
    expect(getBorder(model, "C2")).toEqual({
      bottom: DEFAULT_BORDER_DESC,
      top: DEFAULT_BORDER_DESC,
      left: DEFAULT_BORDER_DESC,
      right: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "C3")).toBeNull();
  });

  test("Delete cell correctly move borders on shift left", () => {
    setZoneBorders(model, { position: "external" }, ["C3:D4"]);
    deleteCells(model, "A3", "left");
    expect(getBorder(model, "A3")).toBeNull();
    expect(getBorder(model, "B3")).toEqual({ left: DEFAULT_BORDER_DESC, top: DEFAULT_BORDER_DESC });
    expect(getBorder(model, "C3")).toEqual({
      right: DEFAULT_BORDER_DESC,
      top: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "B4")).toBeNull();
    expect(getBorder(model, "C4")).toEqual({
      left: DEFAULT_BORDER_DESC,
      bottom: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "D4")).toEqual({
      right: DEFAULT_BORDER_DESC,
      bottom: DEFAULT_BORDER_DESC,
    });
  });

  test("Delete a cell correctly move all the borders on shift left", () => {
    setZoneBorders(model, { position: "external" }, ["C3"]);
    deleteCells(model, "A3", "left");
    expect(getBorder(model, "A3")).toBeNull();
    expect(getBorder(model, "B3")).toEqual({
      bottom: DEFAULT_BORDER_DESC,
      top: DEFAULT_BORDER_DESC,
      left: DEFAULT_BORDER_DESC,
      right: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "C3")).toBeNull();
  });

  test("Remove multiple rows before borders at the bottom of the sheet starting from the first column", () => {
    const b = DEFAULT_BORDER_DESC;
    setZoneBorders(model, { position: "external" }, ["A98:C100"]);
    deleteRows(model, [0, 1, 2, 3]);
    expect(getBorder(model, "A94")).toEqual({ left: b, top: b });
    expect(getBorder(model, "B94")).toEqual({ top: b });
    expect(getBorder(model, "C94")).toEqual({ top: b, right: b });
    expect(getBorder(model, "A95")).toEqual({ left: b });
    expect(getBorder(model, "C95")).toEqual({ right: b });
    expect(getBorder(model, "A96")).toEqual({ bottom: b, left: b });
    expect(getBorder(model, "B96")).toEqual({ bottom: b });
    expect(getBorder(model, "C96")).toEqual({ right: b, bottom: b });
  });

  test("Remove multiple rows before borders at the bottom of the sheet starting from the second column", () => {
    const b = DEFAULT_BORDER_DESC;
    setZoneBorders(model, { position: "external" }, ["B98:D100"]);
    deleteRows(model, [0, 1, 2, 3]);
    expect(getBorder(model, "B94")).toEqual({ left: b, top: b });
    expect(getBorder(model, "C94")).toEqual({ top: b });
    expect(getBorder(model, "D94")).toEqual({ top: b, right: b });
    expect(getBorder(model, "B95")).toEqual({ left: b });
    expect(getBorder(model, "D95")).toEqual({ right: b });
    expect(getBorder(model, "B96")).toEqual({ bottom: b, left: b });
    expect(getBorder(model, "C96")).toEqual({ bottom: b });
    expect(getBorder(model, "D96")).toEqual({ right: b, bottom: b });
  });

  test("Removing multiple rows removes internal borders", () => {
    setZoneBorders(model, { position: "hv" }, ["B42:E45"]);
    deleteRows(model, [41, 42, 43, 44]);
    expect(model.exportData().borders).toEqual({});
  });

  test("Removing multiple columns removes internal borders", () => {
    setZoneBorders(model, { position: "hv" }, ["B42:E45"]);
    deleteColumns(model, ["B", "C", "D", "E"]);
    expect(model.exportData().borders).toEqual({});
  });

  test("Removing top row removes top border", () => {
    setZoneBorders(model, { position: "top" }, ["A1:J1"]);
    deleteRows(model, [0]);
    expect(model.exportData().borders).toEqual({});
  });

  test("Removing bottom row removes bottom border", () => {
    setZoneBorders(model, { position: "bottom" }, ["A100:J100"]);
    deleteRows(model, [99]);
    expect(model.exportData().borders).toEqual({});
  });

  test("Removing left-most cost removes left-most border", () => {
    setZoneBorders(model, { position: "left" }, ["A1:A6"]);
    deleteColumns(model, ["A"]);
    expect(model.exportData().borders).toEqual({});
  });

  test("Removing right-most row removes right-most border", () => {
    setZoneBorders(model, { position: "right" }, ["Z1:Z6"]);
    deleteColumns(model, ["Z"]);
    expect(model.exportData().borders).toEqual({});
  });

  test("Removing bottom two row removes bottom border", () => {
    setZoneBorders(model, { position: "bottom" }, ["A100:J100"]);
    deleteRows(model, [98, 99]);
    expect(model.exportData().borders).toEqual({});
  });

  test("Adding a border on a cell removes it on the adjacent cells if it differs", () => {
    const model = new Model();
    const b = DEFAULT_BORDER_DESC;
    setZoneBorders(model, { position: "bottom", color: "red", style: "dashed" }, ["B2"]);
    setZoneBorders(model, { position: "right", color: "red", style: "dashed" }, ["A3"]);
    setZoneBorders(model, { position: "left", color: "red", style: "dashed" }, ["C3"]);
    setZoneBorders(model, { position: "top" }, ["D2"]);
    setZoneBorders(model, { position: "external" }, ["B3"]);
    expect(getBorder(model, "B3")).toEqual({ top: b, bottom: b, left: b, right: b });
    // deleted as the borders are different
    expect(getBorder(model, "B2")).toBeNull();
    expect(getBorder(model, "A3")).toBeNull();
    expect(getBorder(model, "C3")).toBeNull();
    // untouched as the border are the same
    expect(getBorder(model, "D2")).toEqual({ top: DEFAULT_BORDER_DESC });
  });

  test("Moving top row", () => {
    setZoneBorders(model, { position: "external" }, ["B2:D4"]);
    moveRows(model, 9, [1], "after");

    expect(getBorder(model, "B2")).toEqual({ left: DEFAULT_BORDER_DESC });
    expect(getBorder(model, "B3")).toEqual({
      left: DEFAULT_BORDER_DESC,
      bottom: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "D2")).toEqual({ right: DEFAULT_BORDER_DESC });
    expect(getBorder(model, "D3")).toEqual({
      right: DEFAULT_BORDER_DESC,
      bottom: DEFAULT_BORDER_DESC,
    });

    expect(getBorder(model, "B10")).toEqual({
      left: DEFAULT_BORDER_DESC,
      top: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "D10")).toEqual({
      right: DEFAULT_BORDER_DESC,
      top: DEFAULT_BORDER_DESC,
    });
  });

  test("Moving bottom row", () => {
    setZoneBorders(model, { position: "external" }, ["B2:D4"]);
    moveRows(model, 9, [3], "after");

    expect(getBorder(model, "B2")).toEqual({ left: DEFAULT_BORDER_DESC, top: DEFAULT_BORDER_DESC });
    expect(getBorder(model, "B3")).toEqual({ left: DEFAULT_BORDER_DESC });
    expect(getBorder(model, "D2")).toEqual({
      right: DEFAULT_BORDER_DESC,
      top: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "D3")).toEqual({ right: DEFAULT_BORDER_DESC });

    expect(getBorder(model, "B10")).toEqual({
      left: DEFAULT_BORDER_DESC,
      bottom: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "D10")).toEqual({
      right: DEFAULT_BORDER_DESC,
      bottom: DEFAULT_BORDER_DESC,
    });
  });

  test("Moving left col", () => {
    setZoneBorders(model, { position: "external" }, ["B2:D4"]);
    moveColumns(model, "F", ["B"], "after");

    expect(getBorder(model, "B2")).toEqual({ top: DEFAULT_BORDER_DESC });
    expect(getBorder(model, "B4")).toEqual({ bottom: DEFAULT_BORDER_DESC });
    expect(getBorder(model, "C2")).toEqual({
      right: DEFAULT_BORDER_DESC,
      top: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "C4")).toEqual({
      right: DEFAULT_BORDER_DESC,
      bottom: DEFAULT_BORDER_DESC,
    });

    expect(getBorder(model, "F2")).toEqual({
      left: DEFAULT_BORDER_DESC,
      top: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "F3")).toEqual({ left: DEFAULT_BORDER_DESC });
    expect(getBorder(model, "F4")).toEqual({
      left: DEFAULT_BORDER_DESC,
      bottom: DEFAULT_BORDER_DESC,
    });
  });

  test("Moving right col", () => {
    setZoneBorders(model, { position: "external" }, ["B2:D4"]);
    moveColumns(model, "F", ["D"], "after");
    expect(getBorder(model, "B2")).toEqual({ top: DEFAULT_BORDER_DESC, left: DEFAULT_BORDER_DESC });
    expect(getBorder(model, "B4")).toEqual({
      bottom: DEFAULT_BORDER_DESC,
      left: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "C2")).toEqual({ top: DEFAULT_BORDER_DESC });
    expect(getBorder(model, "C4")).toEqual({ bottom: DEFAULT_BORDER_DESC });

    expect(getBorder(model, "F2")).toEqual({
      right: DEFAULT_BORDER_DESC,
      top: DEFAULT_BORDER_DESC,
    });
    expect(getBorder(model, "F3")).toEqual({ right: DEFAULT_BORDER_DESC });
    expect(getBorder(model, "F4")).toEqual({
      right: DEFAULT_BORDER_DESC,
      bottom: DEFAULT_BORDER_DESC,
    });
  });

  test("Setting a *clear* border on a cell removes the adjacent border cell", () => {
    const model = new Model();
    const b = DEFAULT_BORDER_DESC;
    setZoneBorders(model, { position: "all" }, ["A1:C3"]);
    setZoneBorders(model, { position: "clear" }, ["B2"]);
    expect(getBorder(model, "A1")).toEqual({ top: b, bottom: b, left: b, right: b });
    expect(getBorder(model, "A2")).toEqual({ top: b, bottom: b, left: b });
    expect(getBorder(model, "A3")).toEqual({ top: b, bottom: b, left: b, right: b });
    expect(getBorder(model, "B1")).toEqual({ top: b, left: b, right: b });
    expect(getBorder(model, "B2")).toBeNull();
    expect(getBorder(model, "B3")).toEqual({ bottom: b, left: b, right: b });
    expect(getBorder(model, "C1")).toEqual({ top: b, bottom: b, left: b, right: b });
    expect(getBorder(model, "C2")).toEqual({ top: b, bottom: b, right: b });
    expect(getBorder(model, "C3")).toEqual({ top: b, bottom: b, left: b, right: b });
  });

  describe("manipulate borders on boundaries of the sheet", () => {
    const b = DEFAULT_BORDER_DESC;
    const defaultBorder = { top: b, bottom: b, left: b, right: b };
    test("delete a border on the top left of sheet", () => {
      setZoneBorders(model, { position: "all" }, ["A1"]);
      expect(getBorder(model, "A1")).toEqual(defaultBorder);
      deleteColumns(model, ["A"]);
      expect(getBorder(model, "A1")).toBeNull();
      undo(model);
      expect(getBorder(model, "A1")).toEqual(defaultBorder);
      deleteRows(model, [0]);
      expect(getBorder(model, "A1")).toBeNull();
    });

    test("delete a border on the bottom left of the sheet", () => {
      setZoneBorders(model, { position: "all" }, ["A100"]);
      expect(getBorder(model, "A100")).toEqual(defaultBorder);
      deleteColumns(model, ["A"]);
      expect(getBorder(model, "A100")).toBeNull();
      undo(model);
      expect(getBorder(model, "A100")).toEqual(defaultBorder);
      deleteRows(model, [99]);
      expect(getBorder(model, "A100")).toBeNull();
    });

    test("delete a border on the top right of the sheet", () => {
      setZoneBorders(model, { position: "all" }, ["Z1"]);
      expect(getBorder(model, "Z1")).toEqual(defaultBorder);
      deleteColumns(model, ["Z"]);
      expect(getBorder(model, "Z1")).toBeNull();
      undo(model);
      expect(getBorder(model, "Z1")).toEqual(defaultBorder);
      deleteRows(model, [0]);
      expect(getBorder(model, "Z1")).toBeNull();
    });

    test("delete a border on the bottom right of the sheet", () => {
      setZoneBorders(model, { position: "all" }, ["Z100"]);
      expect(getBorder(model, "Z100")).toEqual(defaultBorder);
      deleteColumns(model, ["Z"]);
      expect(getBorder(model, "Z100")).toBeNull();
      undo(model);
      expect(getBorder(model, "Z100")).toEqual(defaultBorder);
      deleteRows(model, [99]);
      expect(getBorder(model, "Z100")).toBeNull();
    });
  });

  test("no lingering border when deleting a cell with a border", () => {
    const sheetId = model.getters.getActiveSheetId();
    setZoneBorders(model, { position: "top", color: "#123456" }, ["C3"]);
    expect(model.getters.getBordersColors(sheetId)).toEqual(["#123456"]);
    deleteRows(model, [2]);
    expect(model.getters.getBordersColors(sheetId)).toEqual([]);
  });

  test("Borders are not shared through adjacent cells", () => {
    setZoneBorders(model, { position: "all" }, ["B2"]);
    expect(getBorder(model, "B1")).toBeNull();
    expect(getBorder(model, "A2")).toBeNull();
    expect(getBorder(model, "C2")).toBeNull();
    expect(getBorder(model, "B3")).toBeNull();
  });
});

describe("Border continuity", () => {
  const border = {
    top: DEFAULT_BORDER_DESC,
    left: DEFAULT_BORDER_DESC,
    right: DEFAULT_BORDER_DESC,
    bottom: DEFAULT_BORDER_DESC,
  };
  test("border continuity is preserved when adding a row before", () => {
    const model = new Model();
    setZoneBorders(model, { position: "external" }, ["A1"]);
    setZoneBorders(model, { position: "external" }, ["A2"]);
    expect(getBorder(model, "A1")).toEqual(border);
    expect(getBorder(model, "A2")).toEqual(border);
    expect(getBorder(model, "A3")).toBeNull();
    addRows(model, "before", 1, 1);
    expect(getBorder(model, "A1")).toEqual(border);
    expect(getBorder(model, "A2")).toEqual(border);
    expect(getBorder(model, "A3")).toEqual(border);
  });

  test("border continuity is preserved when adding a row after", () => {
    const model = new Model();
    setZoneBorders(model, { position: "external" }, ["A1"]);
    setZoneBorders(model, { position: "external" }, ["A2"]);
    expect(getBorder(model, "A1")).toEqual(border);
    expect(getBorder(model, "A2")).toEqual(border);
    expect(getBorder(model, "A3")).toBeNull();
    addRows(model, "after", 0, 1);
    expect(getBorder(model, "A1")).toEqual(border);
    expect(getBorder(model, "A2")).toEqual(border);
    expect(getBorder(model, "A3")).toEqual(border);
  });

  test("border continuity is preserved when adding a column before", () => {
    const model = new Model();
    setZoneBorders(model, { position: "external" }, ["A1"]);
    setZoneBorders(model, { position: "external" }, ["B1"]);
    expect(getBorder(model, "A1")).toEqual(border);
    expect(getBorder(model, "B1")).toEqual(border);
    expect(getBorder(model, "C1")).toBeNull();
    addColumns(model, "before", "B", 1);
    expect(getBorder(model, "A1")).toEqual(border);
    expect(getBorder(model, "B1")).toEqual(border);
    expect(getBorder(model, "C1")).toEqual(border);
  });

  test("border continuity is preserved when adding a column after", () => {
    const model = new Model();
    setZoneBorders(model, { position: "external" }, ["A1"]);
    setZoneBorders(model, { position: "external" }, ["B1"]);
    expect(getBorder(model, "A1")).toEqual(border);
    expect(getBorder(model, "B1")).toEqual(border);
    expect(getBorder(model, "C1")).toBeNull();
    addColumns(model, "after", "A", 1);
    expect(getBorder(model, "A1")).toEqual(border);
    expect(getBorder(model, "B1")).toEqual(border);
    expect(getBorder(model, "C1")).toEqual(border);
  });
});

test("Cells that have undefined borders don't override borders of neighboring cells at import", () => {
  const data = {
    sheets: [
      {
        id: "Sheet1",
        name: "Sheet1",
        colNumber: 26,
        rowNumber: 100,
        cells: {
          B2: "5",
          B1: "3",
          A2: "3",
          B3: "3",
          C2: "3",
        },
        borders: {
          B2: 1,
          B1: 2,
          A2: 2,
          B3: 2,
          C2: 2,
        },
      },
    ],
    borders: {
      "1": {
        top: { style: "thin", color: "#000" },
        bottom: { style: "thin", color: "#000" },
        left: { style: "thin", color: "#000" },
        right: { style: "thin", color: "#000" },
      },
      "2": {
        top: undefined,
        bottom: undefined,
        left: undefined,
        right: undefined,
      },
    },
  };
  const model = new Model(data);
  expect(model.getters.getCellBorder({ sheetId: "Sheet1", col: 1, row: 1 })).toEqual({
    top: { style: "thin", color: "#000" },
    bottom: { style: "thin", color: "#000" },
    left: { style: "thin", color: "#000" },
    right: { style: "thin", color: "#000" },
  });
});

describe("Borders formatting", () => {
  let model: Model;

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

  test("Can set a border with style and color", () => {
    setZoneBorders(model, { position: "top", color: "#FF0000", style: "thick" }, ["A1"]);
    expect(getBorder(model, "A1")).toEqual({ top: { style: "thick", color: "#FF0000" } });
  });

  test("Can set a border only with style", () => {
    setZoneBorders(model, { position: "bottom", style: "medium" }, ["A1"]);
    expect(getBorder(model, "A1")).toEqual({ bottom: { style: "medium", color: "#000000" } });
  });

  test("Can overwrite a border", () => {
    setZoneBorders(model, { position: "all" }, ["A1"]);
    setZoneBorders(model, { position: "left", style: "dotted", color: "#00FF00" }, ["A1"]);
    setZoneBorders(model, { position: "right", style: "dashed", color: "#0000FF" }, ["A1"]);
    expect(getBorder(model, "A1")).toEqual({
      top: DEFAULT_BORDER_DESC,
      left: { style: "dotted", color: "#00FF00" },
      bottom: DEFAULT_BORDER_DESC,
      right: { style: "dashed", color: "#0000FF" },
    });
  });
});

describe("Computed borders", () => {
  test("SET_BORDER command recomputes the borders", () => {
    const model = new Model();
    expect(getComputedBorder(model, "A1")).toBeNull();
    setBorders(model, "A1", { top: DEFAULT_BORDER_DESC });
    expect(getComputedBorder(model, "A1")).not.toBeNull();
  });

  test("SET_ZONE_BORDERS command recomputes the borders", () => {
    const model = new Model();
    expect(getComputedBorder(model, "A1")).toBeNull();
    setZoneBorders(model, { position: "all" }, ["A1"]);
    expect(getComputedBorder(model, "A1")).not.toBeNull();
  });

  test("SET_BORDERS_ON_TARGET command recomputes the borders", () => {
    const model = new Model();
    const border = { top: DEFAULT_BORDER_DESC };
    expect(getComputedBorder(model, "A1")).toBeNull();
    setBordersOnTarget(model, ["A1"], border);
    expect(getComputedBorder(model, "A1")).not.toBeNull();
  });
});
