From 5573befa183a85165c19b04699ef977881cf2ae4 Mon Sep 17 00:00:00 2001 From: Topdev97 Date: Wed, 25 May 2022 08:22:30 +0000 Subject: [PATCH] [FIX] clipboard: invalidate clipboard when inserting col/row before cut zone Adding or removing col/rows before/inside a cut area will now invalidate the clipboard. 266221d only fixed col/rows inserted inside the cut area. Ideally we would want to adapt the clipboard zone instead of invalidating the clipboard, but it's a bigger change that won't be done in 15.0 (task 2712533). Odoo task 2862400 closes odoo/o-spreadsheet#1431 X-original-commit: c0d8ae7e3914a9cf89a4ca8593f067db01fa3c3f Signed-off-by: Pierre Rousseau (pro) --- src/plugins/ui/clipboard.ts | 62 +++++---- tests/plugins/clipboard.test.ts | 234 +++++++++++++++++++++++++------- 2 files changed, 223 insertions(+), 73 deletions(-) diff --git a/src/plugins/ui/clipboard.ts b/src/plugins/ui/clipboard.ts index aef6b4c3e..e0c35b1d4 100644 --- a/src/plugins/ui/clipboard.ts +++ b/src/plugins/ui/clipboard.ts @@ -104,15 +104,35 @@ export class ClipboardPlugin extends UIPlugin { break; } case "ADD_COLUMNS_ROWS": { - // If we add a col/row inside a cut clipped area, we invalidate the clipboard - const isClipboardDirty = this.isAddRowColDirtyingClipboard( - cmd.base, - cmd.position, + this.status = "invisible"; + + // If we add a col/row inside or before the cut area, we invalidate the clipboard + if (this.state?.operation !== "CUT") { + return; + } + const isClipboardDirty = this.isColRowDirtyingClipboard( + cmd.position === "before" ? cmd.base : cmd.base + 1, cmd.dimension ); - if (this.state && this.state.operation == "CUT" && isClipboardDirty) { + if (isClipboardDirty) { this.state = undefined; } + break; + } + case "REMOVE_COLUMNS_ROWS": { + this.status = "invisible"; + + // If we remove a col/row inside or before the cut area, we invalidate the clipboard + if (this.state?.operation !== "CUT") { + return; + } + for (let el of cmd.elements) { + const isClipboardDirty = this.isColRowDirtyingClipboard(el, cmd.dimension); + if (isClipboardDirty) { + this.state = undefined; + break; + } + } this.status = "invisible"; break; } @@ -634,31 +654,19 @@ export class ClipboardPlugin extends UIPlugin { } /** - * Check if a ADD_COLUMNS_ROWS command is affecting an area inside the clipboard + * Check if a col/row added/removed at the given position is dirtying the clipboard */ - private isAddRowColDirtyingClipboard( - base: number, - position: "before" | "after", - dimension: Dimension - ) { + private isColRowDirtyingClipboard(position: number, dimension: Dimension) { if (!this.state || !this.state.zones) return false; - const sheet = this.getters.getActiveSheet(); - const affectedZone: Zone = { - top: 0, - bottom: sheet.rows.length - 1, - left: 0, - right: sheet.cols.length - 1, - }; - if (dimension === "COL") { - const affectedCol = position === "before" ? base - 1 : base + 1; - affectedZone.left = affectedCol; - affectedZone.right = affectedCol; - } else { - const affectedRow = position === "before" ? base - 1 : base + 1; - affectedZone.top = affectedRow; - affectedZone.bottom = affectedRow; + for (let zone of this.state.zones) { + if (dimension === "COL" && position <= zone.right) { + return true; + } + if (dimension === "ROW" && position <= zone.bottom) { + return true; + } } - return this.isZoneOverlapClippedZone(this.state.zones, affectedZone); + return false; } // --------------------------------------------------------------------------- diff --git a/tests/plugins/clipboard.test.ts b/tests/plugins/clipboard.test.ts index e8e800a45..aeae400fd 100644 --- a/tests/plugins/clipboard.test.ts +++ b/tests/plugins/clipboard.test.ts @@ -1625,59 +1625,201 @@ describe("clipboard: pasting outside of sheet", () => { expect(getCellContent(model, "D3")).toBe("Patrick"); }); - test("adding a column inside a cut zone is invalidating the clipboard", () => { - const model = new Model(); - setCellContent(model, "A1", "1"); - setCellContent(model, "B1", "2"); + describe("add col/row can invalidate the clipboard of cut", () => { + test("adding a column before a cut zone is invalidating the clipboard", () => { + const model = new Model(); + setCellContent(model, "A1", "1"); + setCellContent(model, "B1", "2"); - model.dispatch("CUT", { target: target("A1:B1") }); - addColumns(model, "after", "A", 1); - model.dispatch("PASTE", { target: [toZone("A2")] }); - expect(getCellContent(model, "A1")).toBe("1"); - expect(getCellContent(model, "C1")).toBe("2"); - expect(getCellContent(model, "A2")).toBe(""); - expect(getCellContent(model, "C2")).toBe(""); - }); + model.dispatch("CUT", { target: target("A1:B1") }); + addColumns(model, "before", "A", 1); + model.dispatch("PASTE", { target: [toZone("A2")] }); + expect(getCellContent(model, "B1")).toBe("1"); + expect(getCellContent(model, "C1")).toBe("2"); + expect(getCellContent(model, "A2")).toBe(""); + expect(getCellContent(model, "B2")).toBe(""); + expect(getCellContent(model, "C2")).toBe(""); + }); - test("adding multipe columns inside a cut zone is invalidating the clipboard", () => { - const model = new Model(); - setCellContent(model, "A1", "1"); - setCellContent(model, "B1", "2"); + test("adding a column after a cut zone is not invalidating the clipboard", () => { + const model = new Model(); + setCellContent(model, "A1", "1"); + setCellContent(model, "B1", "2"); - model.dispatch("CUT", { target: target("A1:B1") }); - addColumns(model, "after", "A", 5); - model.dispatch("PASTE", { target: [toZone("A2")] }); - expect(getCellContent(model, "A1")).toBe("1"); - expect(getCellContent(model, "G1")).toBe("2"); - expect(getCellContent(model, "A2")).toBe(""); - expect(getCellContent(model, "C2")).toBe(""); - }); + model.dispatch("CUT", { target: target("A1:B1") }); + addColumns(model, "after", "B", 1); + model.dispatch("PASTE", { target: [toZone("A2")] }); + expect(getCellContent(model, "A1")).toBe(""); + expect(getCellContent(model, "B1")).toBe(""); + expect(getCellContent(model, "A2")).toBe("1"); + expect(getCellContent(model, "B2")).toBe("2"); + }); - test("adding a row inside a cut zone is invalidating the clipboard", () => { - const model = new Model(); - setCellContent(model, "A1", "1"); - setCellContent(model, "A2", "2"); + test("adding a column inside a cut zone is invalidating the clipboard", () => { + const model = new Model(); + setCellContent(model, "A1", "1"); + setCellContent(model, "B1", "2"); - model.dispatch("CUT", { target: target("A1:A2") }); - addRows(model, "after", 0, 1); - model.dispatch("PASTE", { target: [toZone("C1")] }); - expect(getCellContent(model, "A1")).toBe("1"); - expect(getCellContent(model, "A3")).toBe("2"); - expect(getCellContent(model, "C1")).toBe(""); - expect(getCellContent(model, "C3")).toBe(""); + model.dispatch("CUT", { target: target("A1:B1") }); + addColumns(model, "after", "A", 1); + model.dispatch("PASTE", { target: [toZone("A2")] }); + expect(getCellContent(model, "A1")).toBe("1"); + expect(getCellContent(model, "C1")).toBe("2"); + expect(getCellContent(model, "A2")).toBe(""); + expect(getCellContent(model, "C2")).toBe(""); + }); + + test("adding multipe columns inside a cut zone is invalidating the clipboard", () => { + const model = new Model(); + setCellContent(model, "A1", "1"); + setCellContent(model, "B1", "2"); + + model.dispatch("CUT", { target: target("A1:B1") }); + addColumns(model, "after", "A", 5); + model.dispatch("PASTE", { target: [toZone("A2")] }); + expect(getCellContent(model, "A1")).toBe("1"); + expect(getCellContent(model, "G1")).toBe("2"); + expect(getCellContent(model, "A2")).toBe(""); + expect(getCellContent(model, "C2")).toBe(""); + }); + + test("adding a row before a cut zone is invalidating the clipboard", () => { + const model = new Model(); + setCellContent(model, "A1", "1"); + setCellContent(model, "A2", "2"); + + model.dispatch("CUT", { target: target("A1:A2") }); + addRows(model, "before", 0, 1); + model.dispatch("PASTE", { target: [toZone("C1")] }); + expect(getCellContent(model, "A2")).toBe("1"); + expect(getCellContent(model, "A3")).toBe("2"); + expect(getCellContent(model, "C1")).toBe(""); + expect(getCellContent(model, "C2")).toBe(""); + expect(getCellContent(model, "C3")).toBe(""); + }); + + test("adding a row after a cut zone is not invalidating the clipboard", () => { + const model = new Model(); + setCellContent(model, "A1", "1"); + setCellContent(model, "A2", "2"); + + model.dispatch("CUT", { target: target("A1:A2") }); + addRows(model, "after", 2, 1); + model.dispatch("PASTE", { target: [toZone("C1")] }); + expect(getCellContent(model, "A1")).toBe(""); + expect(getCellContent(model, "A2")).toBe(""); + expect(getCellContent(model, "C1")).toBe("1"); + expect(getCellContent(model, "C2")).toBe("2"); + }); + + test("adding a row inside a cut zone is invalidating the clipboard", () => { + const model = new Model(); + setCellContent(model, "A1", "1"); + setCellContent(model, "A2", "2"); + + model.dispatch("CUT", { target: target("A1:A2") }); + addRows(model, "after", 0, 1); + model.dispatch("PASTE", { target: [toZone("C1")] }); + expect(getCellContent(model, "A1")).toBe("1"); + expect(getCellContent(model, "A3")).toBe("2"); + expect(getCellContent(model, "C1")).toBe(""); + expect(getCellContent(model, "C3")).toBe(""); + }); + + test("adding multiple rows inside a cut zone is invalidating the clipboard", () => { + const model = new Model(); + setCellContent(model, "A1", "1"); + setCellContent(model, "A2", "2"); + + model.dispatch("CUT", { target: target("A1:A2") }); + addRows(model, "after", 0, 5); + model.dispatch("PASTE", { target: [toZone("C1")] }); + expect(getCellContent(model, "A1")).toBe("1"); + expect(getCellContent(model, "A7")).toBe("2"); + expect(getCellContent(model, "C1")).toBe(""); + expect(getCellContent(model, "C3")).toBe(""); + }); }); - test("adding multiple rows inside a cut zone is invalidating the clipboard", () => { - const model = new Model(); - setCellContent(model, "A1", "1"); - setCellContent(model, "A2", "2"); + describe("remove col/row can invalidate the clipboard of cut", () => { + test("removing a column before a cut zone is invalidating the clipboard", () => { + const model = new Model(); + setCellContent(model, "B2", "1"); + setCellContent(model, "C2", "2"); - model.dispatch("CUT", { target: target("A1:A2") }); - addRows(model, "after", 0, 5); - model.dispatch("PASTE", { target: [toZone("C1")] }); - expect(getCellContent(model, "A1")).toBe("1"); - expect(getCellContent(model, "A7")).toBe("2"); - expect(getCellContent(model, "C1")).toBe(""); - expect(getCellContent(model, "C3")).toBe(""); + model.dispatch("CUT", { target: target("B2:C2") }); + deleteColumns(model, ["A"]); + model.dispatch("PASTE", { target: [toZone("D1")] }); + expect(getCellContent(model, "A2")).toBe("1"); + expect(getCellContent(model, "B2")).toBe("2"); + expect(getCellContent(model, "D1")).toBe(""); + expect(getCellContent(model, "E1")).toBe(""); + }); + + test("removing a column after a cut zone is not invalidating the clipboard", () => { + const model = new Model(); + setCellContent(model, "B2", "1"); + setCellContent(model, "C2", "2"); + + model.dispatch("CUT", { target: target("B2:C2") }); + deleteColumns(model, ["D"]); + model.dispatch("PASTE", { target: [toZone("D1")] }); + expect(getCellContent(model, "B2")).toBe(""); + expect(getCellContent(model, "C2")).toBe(""); + expect(getCellContent(model, "D1")).toBe("1"); + expect(getCellContent(model, "E1")).toBe("2"); + }); + + test("removing a column inside a cut zone is invalidating the clipboard", () => { + const model = new Model(); + setCellContent(model, "B2", "1"); + setCellContent(model, "C2", "2"); + + model.dispatch("CUT", { target: target("B2:C2") }); + deleteColumns(model, ["C"]); + model.dispatch("PASTE", { target: [toZone("D1")] }); + expect(getCellContent(model, "B2")).toBe("1"); + expect(getCellContent(model, "D1")).toBe(""); + }); + + test("removing a row before a cut zone is invalidating the clipboard", () => { + const model = new Model(); + setCellContent(model, "B2", "1"); + setCellContent(model, "C2", "2"); + + model.dispatch("CUT", { target: target("B2:C2") }); + deleteRows(model, [0]); + model.dispatch("PASTE", { target: [toZone("D1")] }); + expect(getCellContent(model, "B1")).toBe("1"); + expect(getCellContent(model, "C1")).toBe("2"); + expect(getCellContent(model, "D1")).toBe(""); + expect(getCellContent(model, "E1")).toBe(""); + }); + + test("removing a row after a cut zone is not invalidating the clipboard", () => { + const model = new Model(); + setCellContent(model, "B2", "1"); + setCellContent(model, "C2", "2"); + + model.dispatch("CUT", { target: target("B2:C2") }); + deleteRows(model, [3]); + model.dispatch("PASTE", { target: [toZone("D1")] }); + expect(getCellContent(model, "B2")).toBe(""); + expect(getCellContent(model, "C2")).toBe(""); + expect(getCellContent(model, "D1")).toBe("1"); + expect(getCellContent(model, "E1")).toBe("2"); + }); + + test("removing a row inside a cut zone is invalidating the clipboard", () => { + const model = new Model(); + setCellContent(model, "B2", "1"); + setCellContent(model, "B3", "2"); + + model.dispatch("CUT", { target: target("B2:B3") }); + deleteRows(model, [2]); + model.dispatch("PASTE", { target: [toZone("D1")] }); + expect(getCellContent(model, "B2")).toBe("1"); + expect(getCellContent(model, "D1")).toBe(""); + }); }); });