@odoo/o-spreadsheet 14.0.0 → 14.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/.eslintrc.json +20 -0
  2. package/.husky/post-checkout +9 -0
  3. package/.husky/pre-commit +39 -0
  4. package/.prettierignore +1 -0
  5. package/astToFormula.patch +0 -0
  6. package/demo/data.js +255 -0
  7. package/demo/index.html +9 -0
  8. package/demo/libs/Chart.bundle.js +20755 -0
  9. package/demo/main.js +101 -0
  10. package/doc/readme.md +46 -0
  11. package/package.json +3 -17
  12. package/rollup.config.js +23 -0
  13. package/src/base_plugin.ts +102 -0
  14. package/src/components/autofill.ts +146 -0
  15. package/src/components/bottom_bar.ts +199 -0
  16. package/src/components/color_picker.ts +150 -0
  17. package/src/components/composer/autocomplete_dropdown.ts +135 -0
  18. package/src/components/composer/composer.ts +487 -0
  19. package/src/components/composer/content_editable_helper.ts +146 -0
  20. package/src/components/figures/chart.ts +70 -0
  21. package/src/components/figures/container.ts +268 -0
  22. package/src/components/figures/text_figure.ts +36 -0
  23. package/src/components/grid.ts +635 -0
  24. package/src/components/helpers/dom_helpers.ts +7 -0
  25. package/src/components/helpers/drag_and_drop.ts +17 -0
  26. package/src/components/icons.ts +36 -0
  27. package/src/components/index.ts +1 -0
  28. package/src/components/menu.ts +288 -0
  29. package/src/components/overlay.ts +457 -0
  30. package/src/components/scrollbar.ts +22 -0
  31. package/src/components/selection_input.ts +168 -0
  32. package/src/components/side_panel/cell_is_rule_editor.ts +169 -0
  33. package/src/components/side_panel/chart_panel.ts +104 -0
  34. package/src/components/side_panel/color_scale_rule_editor.ts +145 -0
  35. package/src/components/side_panel/conditional_formatting.ts +396 -0
  36. package/src/components/side_panel/side_panel.ts +127 -0
  37. package/src/components/side_panel/translations_terms.ts +45 -0
  38. package/src/components/spreadsheet.ts +198 -0
  39. package/src/components/top_bar.ts +535 -0
  40. package/src/constants.ts +26 -0
  41. package/src/data.ts +149 -0
  42. package/src/fonts.ts +41 -0
  43. package/src/formulas/compiler.ts +258 -0
  44. package/src/formulas/composer_tokenizer.ts +70 -0
  45. package/src/formulas/index.ts +15 -0
  46. package/src/formulas/normalize.ts +52 -0
  47. package/src/formulas/parser.ts +280 -0
  48. package/src/formulas/range_tokenizer.ts +97 -0
  49. package/src/formulas/tokenizer.ts +242 -0
  50. package/src/functions/arguments.ts +114 -0
  51. package/src/functions/dates.ts +302 -0
  52. package/src/functions/helpers.ts +592 -0
  53. package/src/functions/index.ts +52 -0
  54. package/src/functions/module_database.ts +282 -0
  55. package/src/functions/module_date.ts +743 -0
  56. package/src/functions/module_info.ts +69 -0
  57. package/src/functions/module_logical.ts +208 -0
  58. package/src/functions/module_lookup.ts +185 -0
  59. package/src/functions/module_math.ts +1252 -0
  60. package/src/functions/module_operators.ts +291 -0
  61. package/src/functions/module_statistical.ts +882 -0
  62. package/src/functions/module_text.ts +370 -0
  63. package/src/helpers/color.ts +30 -0
  64. package/src/helpers/coordinates.ts +71 -0
  65. package/src/helpers/grid_manipulation.ts +68 -0
  66. package/src/helpers/index.ts +7 -0
  67. package/src/helpers/misc.ts +76 -0
  68. package/src/helpers/numbers.ts +117 -0
  69. package/src/helpers/uuid.ts +19 -0
  70. package/src/helpers/zones.ts +263 -0
  71. package/src/history.ts +186 -0
  72. package/src/index.ts +84 -0
  73. package/src/model.ts +261 -0
  74. package/src/plugins/autofill.ts +412 -0
  75. package/src/plugins/chart.ts +404 -0
  76. package/src/plugins/clipboard.ts +461 -0
  77. package/src/plugins/conditional_format.ts +445 -0
  78. package/src/plugins/core.ts +1502 -0
  79. package/src/plugins/edition.ts +157 -0
  80. package/src/plugins/evaluation.ts +428 -0
  81. package/src/plugins/figures.ts +123 -0
  82. package/src/plugins/formatting.ts +630 -0
  83. package/src/plugins/highlight.ts +187 -0
  84. package/src/plugins/index.ts +32 -0
  85. package/src/plugins/merge.ts +524 -0
  86. package/src/plugins/renderer.ts +571 -0
  87. package/src/plugins/selection.ts +562 -0
  88. package/src/plugins/selection_inputs.ts +326 -0
  89. package/src/registries/autofill_modifiers.ts +76 -0
  90. package/src/registries/autofill_rules.ts +98 -0
  91. package/src/registries/figure_registry.ts +30 -0
  92. package/src/registries/index.ts +7 -0
  93. package/src/registries/menu_items_registry.ts +110 -0
  94. package/src/registries/menus/cell_menu_registry.ts +79 -0
  95. package/src/registries/menus/col_menu_registry.ts +63 -0
  96. package/src/registries/menus/index.ts +6 -0
  97. package/src/registries/menus/menu_items_actions.ts +425 -0
  98. package/src/registries/menus/row_menu_registry.ts +63 -0
  99. package/src/registries/menus/sheet_menu_registry.ts +75 -0
  100. package/src/registries/menus/topbar_menu_registry.ts +204 -0
  101. package/src/registries/side_panel_registry.ts +26 -0
  102. package/src/registries/topbar_component_registry.ts +12 -0
  103. package/src/registry.ts +51 -0
  104. package/src/translation.ts +44 -0
  105. package/src/types/autofill.ts +48 -0
  106. package/src/types/chart.ts +26 -0
  107. package/src/types/commands.ts +716 -0
  108. package/src/types/conditional_formatting.ts +124 -0
  109. package/src/types/env.ts +13 -0
  110. package/src/types/functions.ts +35 -0
  111. package/src/types/getters.ts +103 -0
  112. package/src/types/index.ts +20 -0
  113. package/src/types/misc.ts +143 -0
  114. package/src/types/rendering.ts +41 -0
  115. package/src/types/workbook_data.ts +62 -0
  116. package/tests/__mocks__/uuid.ts +7 -0
  117. package/tests/canvas.mock.ts +34 -0
  118. package/tests/components/__mocks__/content_editable_helper.ts +79 -0
  119. package/tests/components/__mocks__/scrollbar.ts +18 -0
  120. package/tests/components/__snapshots__/autocomplete_dropdown_test.ts.snap +29 -0
  121. package/tests/components/__snapshots__/bottom_bar_test.ts.snap +75 -0
  122. package/tests/components/__snapshots__/context_menu_test.ts.snap +88 -0
  123. package/tests/components/__snapshots__/grid_test.ts.snap +60 -0
  124. package/tests/components/__snapshots__/spreadsheet_test.ts.snap +455 -0
  125. package/tests/components/__snapshots__/top_bar_test.ts.snap +705 -0
  126. package/tests/components/autocomplete_dropdown_test.ts +315 -0
  127. package/tests/components/autofill_test.ts +167 -0
  128. package/tests/components/bottom_bar_test.ts +262 -0
  129. package/tests/components/chart_side_panel_test.ts +123 -0
  130. package/tests/components/composer_test.ts +400 -0
  131. package/tests/components/context_menu_test.ts +825 -0
  132. package/tests/components/grid_manipulation_test.ts +163 -0
  133. package/tests/components/grid_test.ts +581 -0
  134. package/tests/components/overlay_test.ts +541 -0
  135. package/tests/components/selection_input_test.ts +259 -0
  136. package/tests/components/side_panel_test.ts +148 -0
  137. package/tests/components/spreadsheet_test.ts +194 -0
  138. package/tests/components/top_bar_test.ts +427 -0
  139. package/tests/data_test.ts +175 -0
  140. package/tests/dom_helper.ts +42 -0
  141. package/tests/formulas/__snapshots__/compiler_test.ts.snap +302 -0
  142. package/tests/formulas/compiler_test.ts +266 -0
  143. package/tests/formulas/composer_tokenizer_test.ts +137 -0
  144. package/tests/formulas/expressions_test.ts +57 -0
  145. package/tests/formulas/formulas_test.ts +80 -0
  146. package/tests/formulas/normalize_test.ts +82 -0
  147. package/tests/formulas/parser_test.ts +224 -0
  148. package/tests/formulas/range_tokenizer_test.ts +187 -0
  149. package/tests/formulas/tokenizer_test.ts +164 -0
  150. package/tests/functions/arguments_test.ts +141 -0
  151. package/tests/functions/dates_test.ts +1081 -0
  152. package/tests/functions/functions_test.ts +53 -0
  153. package/tests/functions/helper_test.ts +98 -0
  154. package/tests/functions/module_database_test.ts +311 -0
  155. package/tests/functions/module_date_test.ts +941 -0
  156. package/tests/functions/module_info_test.ts +139 -0
  157. package/tests/functions/module_logical_test.ts +369 -0
  158. package/tests/functions/module_lookup_test.ts +541 -0
  159. package/tests/functions/module_math_test.ts +2482 -0
  160. package/tests/functions/module_operators_test.ts +955 -0
  161. package/tests/functions/module_statistical_test.ts +2014 -0
  162. package/tests/functions/module_text_test.ts +667 -0
  163. package/tests/helpers/coordinates_test.ts +25 -0
  164. package/tests/helpers/misc_test.ts +95 -0
  165. package/tests/helpers/numbers_test.ts +196 -0
  166. package/tests/helpers/zones_test.ts +97 -0
  167. package/tests/helpers.ts +384 -0
  168. package/tests/menu_items_registry_test.ts +540 -0
  169. package/tests/model/plugins/__snapshots__/chart_test.ts.snap +90 -0
  170. package/tests/model/plugins/chart_test.ts +562 -0
  171. package/tests/model_test.ts +82 -0
  172. package/tests/plugins/__snapshots__/conditional_formatting_test.ts.snap +131 -0
  173. package/tests/plugins/__snapshots__/renderer_test.ts.snap +371 -0
  174. package/tests/plugins/autofill_test.ts +457 -0
  175. package/tests/plugins/borders_test.ts +295 -0
  176. package/tests/plugins/clipboard_test.ts +1180 -0
  177. package/tests/plugins/conditional_formatting_test.ts +1253 -0
  178. package/tests/plugins/core_test.ts +614 -0
  179. package/tests/plugins/edition_test.ts +100 -0
  180. package/tests/plugins/evaluation_async_test.ts +332 -0
  181. package/tests/plugins/evaluation_dates_test.ts +79 -0
  182. package/tests/plugins/evaluation_test.ts +901 -0
  183. package/tests/plugins/figures_test.ts +327 -0
  184. package/tests/plugins/formatting_test.ts +391 -0
  185. package/tests/plugins/grid_manipulation_test.ts +1366 -0
  186. package/tests/plugins/highlight_test.ts +413 -0
  187. package/tests/plugins/history_test.ts +142 -0
  188. package/tests/plugins/import_export_test.ts +276 -0
  189. package/tests/plugins/merges_test.ts +490 -0
  190. package/tests/plugins/navigation_test.ts +181 -0
  191. package/tests/plugins/plugins/evaluation_test.ts +111 -0
  192. package/tests/plugins/renderer_test.ts +598 -0
  193. package/tests/plugins/resizing_test.ts +152 -0
  194. package/tests/plugins/selection_input_test.ts +409 -0
  195. package/tests/plugins/selection_test.ts +442 -0
  196. package/tests/plugins/sheets_test.ts +643 -0
  197. package/tests/plugins/style_test.ts +90 -0
  198. package/tsconfig.json +22 -0
@@ -0,0 +1,1366 @@
1
+ import { DEFAULT_CELL_HEIGHT, DEFAULT_CELL_WIDTH } from "../../src/constants";
2
+ import { Model } from "../../src/model";
3
+ import { getSheet, makeTestFixture } from "../helpers";
4
+ let model: Model;
5
+
6
+ function undo() {
7
+ model.dispatch("UNDO");
8
+ }
9
+
10
+ function redo() {
11
+ model.dispatch("REDO");
12
+ }
13
+
14
+ function clearColumns(indexes: number[]) {
15
+ const target = indexes.map((index) => {
16
+ return model.getters.getColsZone(index, index);
17
+ });
18
+ model.dispatch("DELETE_CONTENT", {
19
+ target,
20
+ sheet: model["workbook"].activeSheet.id,
21
+ });
22
+ }
23
+
24
+ function clearRows(indexes: number[]) {
25
+ const target = indexes.map((index) => {
26
+ return model.getters.getRowsZone(index, index);
27
+ });
28
+ model.dispatch("DELETE_CONTENT", {
29
+ target,
30
+ sheet: model["workbook"].activeSheet.id,
31
+ });
32
+ }
33
+
34
+ function removeColumns(columns: number[]) {
35
+ model.dispatch("REMOVE_COLUMNS", { sheet: model.getters.getActiveSheet(), columns });
36
+ }
37
+
38
+ function removeRows(rows: number[]) {
39
+ model.dispatch("REMOVE_ROWS", { sheet: model.getters.getActiveSheet(), rows });
40
+ }
41
+
42
+ function addColumns(column: number, position: "before" | "after", quantity: number) {
43
+ model.dispatch("ADD_COLUMNS", {
44
+ sheet: model.getters.getActiveSheet(),
45
+ position,
46
+ column,
47
+ quantity,
48
+ });
49
+ }
50
+
51
+ function addRows(row: number, position: "before" | "after", quantity: number) {
52
+ model.dispatch("ADD_ROWS", {
53
+ sheet: model.getters.getActiveSheet(),
54
+ position,
55
+ row,
56
+ quantity,
57
+ });
58
+ }
59
+
60
+ const fullData = {
61
+ sheets: [
62
+ {
63
+ colNumber: 4,
64
+ rowNumber: 4,
65
+ cells: {
66
+ A1: { content: "1", style: 1, border: 1 },
67
+ A2: { content: "2", style: 1, border: 1 },
68
+ A3: { content: "3", style: 1, border: 1 },
69
+ A4: { content: "4", style: 1, border: 1 },
70
+ B1: { content: "1", style: 1, border: 1 },
71
+ B2: { content: "2", style: 1 },
72
+ B3: { content: "3", border: 1 },
73
+ C1: { content: "1" },
74
+ C3: { content: "3" },
75
+ },
76
+ merges: ["A4:D4", "C1:D2"],
77
+ cols: { 1: { size: 42 } },
78
+ rows: { 1: { size: 42 } },
79
+ },
80
+ ],
81
+ styles: { 1: { textColor: "#fe0000" } },
82
+ borders: { 1: { top: ["thin", "#000"] } },
83
+ };
84
+
85
+ //------------------------------------------------------------------------------
86
+ // Clear
87
+ //------------------------------------------------------------------------------
88
+
89
+ describe("Clear columns", () => {
90
+ test("Can clear multiple column", () => {
91
+ model = new Model({
92
+ sheets: [
93
+ {
94
+ colNumber: 3,
95
+ rowNumber: 3,
96
+ cells: {
97
+ A1: { content: "A1" },
98
+ A2: { content: "A2" },
99
+ A3: { content: "A3" },
100
+ B1: { content: "B1", style: 1, border: 1 },
101
+ B2: { content: "B2" },
102
+ C1: { content: "C1", style: 1 },
103
+ C2: { content: "C2", border: 1 },
104
+ },
105
+ },
106
+ ],
107
+ styles: { 1: { textColor: "#fe0000" } },
108
+ borders: { 1: { right: ["thin", "#000"] } },
109
+ merges: ["A3:B3"],
110
+ });
111
+
112
+ clearColumns([1, 2]);
113
+ expect(model["workbook"].activeSheet.cells.B2).toBeUndefined();
114
+ expect(model["workbook"].activeSheet.cells).toMatchObject({
115
+ A1: { content: "A1" },
116
+ A2: { content: "A2" },
117
+ A3: { content: "A3" },
118
+ B1: { content: "", style: 1, border: 1 },
119
+ C1: { content: "", style: 1 },
120
+ C2: { content: "", border: 1 },
121
+ });
122
+ });
123
+ });
124
+
125
+ describe("Clear rows", () => {
126
+ test("Can clear multiple rows", () => {
127
+ model = new Model({
128
+ sheets: [
129
+ {
130
+ colNumber: 3,
131
+ rowNumber: 3,
132
+ cells: {
133
+ A1: { content: "A1" },
134
+ A2: { content: "A2", style: 1, border: 1 },
135
+ A3: { content: "A3", border: 1 },
136
+ B1: { content: "B1" },
137
+ B2: { content: "B2" },
138
+ C1: { content: "C1" },
139
+ C2: { content: "C2", style: 1 },
140
+ },
141
+ },
142
+ ],
143
+ styles: { 1: { textColor: "#fe0000" } },
144
+ borders: { 1: { right: ["thin", "#000"] } },
145
+ merges: ["C1:C2"],
146
+ });
147
+
148
+ clearRows([1, 2]);
149
+ expect(model["workbook"].activeSheet.cells.B2).toBeUndefined();
150
+ expect(model["workbook"].activeSheet.cells).toMatchObject({
151
+ A1: { content: "A1" },
152
+ A2: { content: "", style: 1, border: 1 },
153
+ A3: { content: "", border: 1 },
154
+ B1: { content: "B1" },
155
+ C1: { content: "C1" },
156
+ C2: { content: "", style: 1 },
157
+ });
158
+ });
159
+ });
160
+
161
+ //------------------------------------------------------------------------------
162
+ // Columns
163
+ //------------------------------------------------------------------------------
164
+
165
+ describe("Columns", () => {
166
+ beforeAll(() => {
167
+ makeTestFixture();
168
+ });
169
+ describe("Correctly update size, name, order and number", () => {
170
+ beforeEach(() => {
171
+ model = new Model({
172
+ sheets: [
173
+ {
174
+ colNumber: 4,
175
+ rowNumber: 1,
176
+ cols: {
177
+ 1: { size: 10 },
178
+ 2: { size: 20 },
179
+ },
180
+ },
181
+ ],
182
+ });
183
+ });
184
+ test("On deletion", () => {
185
+ removeColumns([0, 2]);
186
+ expect(model["workbook"].activeSheet.cols).toEqual([
187
+ { start: 0, end: 10, size: 10, name: "A" },
188
+ { start: 10, end: 10 + DEFAULT_CELL_WIDTH, size: DEFAULT_CELL_WIDTH, name: "B" },
189
+ ]);
190
+ expect(model["workbook"].activeSheet.colNumber).toBe(2);
191
+ });
192
+ test("On delete cols in inactive sheet", () => {
193
+ model = new Model({
194
+ sheets: [
195
+ {
196
+ colNumber: 3,
197
+ rowNumber: 3,
198
+ cells: {
199
+ B2: { content: "B2 in sheet1" },
200
+ },
201
+ },
202
+ {
203
+ colNumber: 3,
204
+ rowNumber: 3,
205
+ cells: {
206
+ B2: { content: "B2 in sheet2" },
207
+ },
208
+ },
209
+ ],
210
+ });
211
+ const [sheet1, sheet2] = model.getters.getSheets();
212
+ expect(sheet2).not.toBe(model.getters.getActiveSheet());
213
+ model.dispatch("REMOVE_COLUMNS", {
214
+ columns: [0],
215
+ sheet: sheet2.id,
216
+ });
217
+ expect(sheet1.cells.B2.content).toBe("B2 in sheet1");
218
+ expect(sheet2.cells.A2.content).toBe("B2 in sheet2");
219
+ });
220
+ test("On addition before", () => {
221
+ addColumns(1, "before", 2);
222
+ const size = DEFAULT_CELL_WIDTH;
223
+ expect(model["workbook"].activeSheet.cols).toEqual([
224
+ { start: 0, end: size, size, name: "A" },
225
+ { start: size, end: size + 10, size: 10, name: "B" },
226
+ { start: size + 10, end: size + 20, size: 10, name: "C" },
227
+ { start: size + 20, end: size + 30, size: 10, name: "D" },
228
+ { start: size + 30, end: size + 50, size: 20, name: "E" },
229
+ { start: size + 50, end: 2 * size + 50, size, name: "F" },
230
+ ]);
231
+ expect(model["workbook"].activeSheet.colNumber).toBe(6);
232
+ });
233
+ test("On addition after", () => {
234
+ addColumns(2, "after", 2);
235
+ const size = DEFAULT_CELL_WIDTH;
236
+ expect(model["workbook"].activeSheet.cols).toEqual([
237
+ { start: 0, end: size, size, name: "A" },
238
+ { start: size, end: size + 10, size: 10, name: "B" },
239
+ { start: size + 10, end: size + 30, size: 20, name: "C" },
240
+ { start: size + 30, end: size + 50, size: 20, name: "D" },
241
+ { start: size + 50, end: size + 70, size: 20, name: "E" },
242
+ { start: size + 70, end: 2 * size + 70, size, name: "F" },
243
+ ]);
244
+ expect(model["workbook"].activeSheet.colNumber).toBe(6);
245
+ });
246
+ });
247
+
248
+ describe("Correctly update merges", () => {
249
+ beforeEach(() => {
250
+ model = new Model({
251
+ sheets: [
252
+ {
253
+ colNumber: 5,
254
+ rowNumber: 4,
255
+ merges: ["A1:E1", "B2:E2", "C3:E3", "B4:D4"],
256
+ },
257
+ ],
258
+ });
259
+ });
260
+
261
+ test("On deletion", () => {
262
+ removeColumns([1, 3]);
263
+ expect(model["workbook"].activeSheet.merges).toEqual({
264
+ 5: { id: 5, topLeft: "A1", top: 0, bottom: 0, left: 0, right: 2 },
265
+ 6: { id: 6, topLeft: "B2", top: 1, bottom: 1, left: 1, right: 2 },
266
+ 7: { id: 7, topLeft: "B3", top: 2, bottom: 2, left: 1, right: 2 },
267
+ });
268
+ expect(model["workbook"].activeSheet.mergeCellMap).toEqual({
269
+ A1: 5,
270
+ B1: 5,
271
+ C1: 5,
272
+ B2: 6,
273
+ C2: 6,
274
+ B3: 7,
275
+ C3: 7,
276
+ });
277
+ });
278
+
279
+ test("On addition", () => {
280
+ addColumns(1, "before", 1);
281
+ addColumns(0, "after", 1);
282
+ expect(model["workbook"].activeSheet.merges).toEqual({
283
+ 9: { id: 9, topLeft: "A1", top: 0, bottom: 0, left: 0, right: 6 },
284
+ 10: { id: 10, topLeft: "D2", top: 1, bottom: 1, left: 3, right: 6 },
285
+ 11: { id: 11, topLeft: "E3", top: 2, bottom: 2, left: 4, right: 6 },
286
+ 12: { id: 12, topLeft: "D4", top: 3, bottom: 3, left: 3, right: 5 },
287
+ });
288
+ expect(model["workbook"].activeSheet.mergeCellMap).toEqual({
289
+ A1: 9,
290
+ B1: 9,
291
+ C1: 9,
292
+ D1: 9,
293
+ E1: 9,
294
+ F1: 9,
295
+ G1: 9,
296
+ D2: 10,
297
+ E2: 10,
298
+ F2: 10,
299
+ G2: 10,
300
+ E3: 11,
301
+ F3: 11,
302
+ G3: 11,
303
+ D4: 12,
304
+ E4: 12,
305
+ F4: 12,
306
+ });
307
+ });
308
+ });
309
+
310
+ describe("Correctly update border and style", () => {
311
+ beforeEach(() => {
312
+ model = new Model({
313
+ sheets: [
314
+ {
315
+ colNumber: 4,
316
+ rowNumber: 4,
317
+ cells: {
318
+ A1: { style: 1 },
319
+ A2: { border: 1 },
320
+ A3: { style: 1, border: 1 },
321
+ B1: { style: 1 },
322
+ B2: { border: 1 },
323
+ B3: { style: 1, border: 1, format: "0.00%" },
324
+ B4: { style: 1, border: 1 },
325
+ D1: { style: 1 },
326
+ D2: { border: 1 },
327
+ D3: { style: 1, border: 1 },
328
+ },
329
+ merges: ["B4:C4"],
330
+ },
331
+ ],
332
+ styles: { 1: { textColor: "#fe0000" } },
333
+ borders: { 1: { top: ["thin", "#000"] } },
334
+ });
335
+ });
336
+ test("On deletion", () => {
337
+ removeColumns([1]);
338
+ expect(model["workbook"].activeSheet.cells.B1).toBeUndefined();
339
+ expect(model["workbook"].activeSheet.cells.B2).toBeUndefined();
340
+ expect(model["workbook"].activeSheet.cells.B3).toBeUndefined();
341
+ expect(model["workbook"].activeSheet.cells).toMatchObject({
342
+ A1: { style: 1 },
343
+ A2: { border: 1 },
344
+ A3: { style: 1, border: 1 },
345
+ B4: { style: 1, border: 1 },
346
+ C1: { style: 1 },
347
+ C2: { border: 1 },
348
+ C3: { style: 1, border: 1 },
349
+ });
350
+ });
351
+ test("On addition", () => {
352
+ addColumns(1, "before", 1);
353
+ addColumns(2, "after", 2);
354
+ expect(model["workbook"].activeSheet.cells).toMatchObject({
355
+ A1: { style: 1 },
356
+ A2: { border: 1 },
357
+ A3: { style: 1, border: 1 },
358
+ B1: { style: 1 },
359
+ B2: { border: 1 },
360
+ B3: { style: 1, border: 1, format: "0.00%" },
361
+ B4: { style: 1 },
362
+ C1: { style: 1 },
363
+ C2: { border: 1 },
364
+ C3: { style: 1, border: 1, format: "0.00%" },
365
+ C4: { style: 1, border: 1 },
366
+ E1: { style: 1 },
367
+ });
368
+ expect(Object.values(model["workbook"].activeSheet.merges)[0]).toMatchObject({
369
+ left: 2,
370
+ right: 5,
371
+ topLeft: "C4",
372
+ });
373
+ });
374
+ });
375
+
376
+ describe("Correctly update references", () => {
377
+ beforeEach(() => {
378
+ model = new Model({
379
+ sheets: [
380
+ {
381
+ colNumber: 7,
382
+ rowNumber: 7,
383
+ cells: {
384
+ A1: { content: "=B1" },
385
+ A2: { content: "=Sheet1!B1" },
386
+ A3: { content: "=Sheet2!B1" },
387
+ D1: { content: "=A1" },
388
+ D2: { content: "=B1" },
389
+ D3: { content: "=$E1" },
390
+ D4: { content: "=D3" },
391
+ A5: { content: "=SUM(A1:D1)" },
392
+ A6: { content: "=SUM(C1:D1)" },
393
+ A7: { content: "=SUM(B1:B2)" },
394
+ },
395
+ },
396
+ {
397
+ colNumber: 1,
398
+ rowNumber: 3,
399
+ cells: {
400
+ A1: { content: "=B1" },
401
+ A2: { content: "=Sheet1!B1" },
402
+ A3: { content: "=Sheet2!B1" },
403
+ },
404
+ },
405
+ ],
406
+ });
407
+ });
408
+ test("On deletion", () => {
409
+ removeColumns([1, 2]);
410
+ expect(getSheet(model, 0).cells).toMatchObject({
411
+ A1: { content: "=#REF" },
412
+ A2: { content: "=#REF" },
413
+ A3: { content: "=Sheet2!B1" },
414
+ B1: { content: "=A1" },
415
+ B2: { content: "=#REF" },
416
+ B3: { content: "=$C1" },
417
+ B4: { content: "=B3" },
418
+ });
419
+ expect(getSheet(model, 1).cells).toMatchObject({
420
+ A1: { content: "=B1" },
421
+ A2: { content: "=#REF" },
422
+ A3: { content: "=Sheet2!B1" },
423
+ });
424
+ });
425
+ test("delete col on inactive sheet", () => {
426
+ const model = new Model({
427
+ sheets: [
428
+ {
429
+ colNumber: 4,
430
+ rowNumber: 7,
431
+ cells: {
432
+ B2: { content: "=Sheet1!B3" },
433
+ C1: { content: "=Sheet2!B3" },
434
+ },
435
+ },
436
+ {
437
+ colNumber: 3,
438
+ rowNumber: 3,
439
+ cells: {
440
+ A2: { content: "=B2" },
441
+ B2: { content: "=Sheet1!B2" },
442
+ C2: { content: "=Sheet2!B2" },
443
+ },
444
+ },
445
+ ],
446
+ });
447
+ const [sheet1, sheet2] = model.getters.getSheets();
448
+ expect(sheet2.id).not.toBe(model.getters.getActiveSheet());
449
+ model.dispatch("REMOVE_COLUMNS", {
450
+ columns: [0],
451
+ sheet: sheet2.id,
452
+ });
453
+ expect(sheet1.cells).toMatchObject({
454
+ B2: { content: "=Sheet1!B3" },
455
+ C1: { content: "=Sheet2!A3" },
456
+ });
457
+ expect(sheet2.cells).toMatchObject({
458
+ A2: { content: "=Sheet1!B2" },
459
+ B2: { content: "=Sheet2!A2" },
460
+ });
461
+ });
462
+ test("On first col deletion", () => {
463
+ model = new Model({
464
+ sheets: [
465
+ {
466
+ colNumber: 3,
467
+ rowNumber: 3,
468
+ cells: {
469
+ B2: { content: "=SUM(A1:C1)" },
470
+ },
471
+ },
472
+ ],
473
+ });
474
+ removeColumns([0]);
475
+ expect(getSheet(model, 0).cells).toMatchObject({
476
+ A2: { content: "=SUM(A1:B1)" },
477
+ });
478
+ });
479
+ test("On multiple col deletion including the first one", () => {
480
+ model = new Model({
481
+ sheets: [
482
+ {
483
+ colNumber: 3,
484
+ rowNumber: 3,
485
+ cells: {
486
+ C2: { content: "=SUM(A1:D1)" },
487
+ },
488
+ },
489
+ ],
490
+ });
491
+ removeColumns([0, 1]);
492
+ expect(getSheet(model, 0).cells).toMatchObject({
493
+ A2: { content: "=SUM(A1:B1)" },
494
+ });
495
+ });
496
+ test("On last col deletion", () => {
497
+ model = new Model({
498
+ sheets: [
499
+ {
500
+ colNumber: 3,
501
+ rowNumber: 3,
502
+ cells: {
503
+ A2: { content: "=SUM(A1:C1)" },
504
+ },
505
+ },
506
+ ],
507
+ });
508
+ removeColumns([2]);
509
+ expect(getSheet(model, 0).cells).toMatchObject({
510
+ A2: { content: "=SUM(A1:B1)" },
511
+ });
512
+ });
513
+ test("delete all columns of a range", () => {
514
+ model = new Model({
515
+ sheets: [
516
+ {
517
+ colNumber: 9,
518
+ rowNumber: 5,
519
+ cells: {
520
+ A1: { content: "=SUM(B2:E5)" },
521
+ A2: { content: "=SUM(F1:F2)" }, // single column range
522
+ },
523
+ },
524
+ ],
525
+ });
526
+
527
+ removeColumns([5]);
528
+ expect(getSheet(model, 0).cells.A2.content).toBe("=SUM(#REF)");
529
+ removeColumns([1, 2, 3, 4]);
530
+ expect(getSheet(model, 0).cells.A1.content).toBe("=SUM(#REF)");
531
+ });
532
+ test("update cross sheet range on column deletion", () => {
533
+ model = new Model({
534
+ sheets: [
535
+ { name: "Sheet1", colNumber: 5, rowNumber: 5 },
536
+ {
537
+ name: "Sheet2",
538
+ colNumber: 3,
539
+ rowNumber: 9,
540
+ cells: {
541
+ A1: { content: "=SUM(Sheet1!B1:D3)" },
542
+ },
543
+ },
544
+ ],
545
+ });
546
+ removeColumns([0]);
547
+ expect(model.getters.getCell(0, 0, "Sheet2")!.content).toBe("=SUM(Sheet1!A1:C3)");
548
+ });
549
+ test("update cross sheet range on column deletion in inactive sheet", () => {
550
+ model = new Model({
551
+ sheets: [
552
+ { name: "Sheet0", colNumber: 1, rowNumber: 1 }, // <-- less column than Sheet1
553
+ { name: "Sheet1", colNumber: 5, rowNumber: 5 },
554
+ {
555
+ name: "Sheet2",
556
+ colNumber: 5,
557
+ rowNumber: 9,
558
+ cells: {
559
+ A1: { content: "=SUM(Sheet1!B1:D3)" },
560
+ },
561
+ },
562
+ ],
563
+ });
564
+ const sheet1Id = model.getters.getSheetIdByName("Sheet1");
565
+ model.dispatch("REMOVE_COLUMNS", { sheet: sheet1Id!, columns: [0] });
566
+ expect(model.getters.getCell(0, 0, "Sheet2")!.content).toBe("=SUM(Sheet1!A1:C3)");
567
+ });
568
+ test("On multiple col deletion including the last one", () => {
569
+ model = new Model({
570
+ sheets: [
571
+ {
572
+ colNumber: 3,
573
+ rowNumber: 3,
574
+ cells: {
575
+ A2: { content: "=SUM(A1:D1)" },
576
+ },
577
+ },
578
+ ],
579
+ });
580
+ removeColumns([2, 3]);
581
+ expect(getSheet(model, 0).cells).toMatchObject({
582
+ A2: { content: "=SUM(A1:B1)" },
583
+ });
584
+ });
585
+ test("On addition", () => {
586
+ addColumns(1, "before", 1);
587
+ addColumns(0, "after", 1);
588
+ expect(getSheet(model, 0).cells).toMatchObject({
589
+ A1: { content: "=D1" },
590
+ A2: { content: "=Sheet1!D1" },
591
+ A3: { content: "=Sheet2!B1" },
592
+ F1: { content: "=A1" },
593
+ F2: { content: "=D1" },
594
+ F3: { content: "=$G1" },
595
+ F4: { content: "=F3" },
596
+ A5: { content: "=SUM(A1:F1)" },
597
+ A6: { content: "=SUM(E1:F1)" },
598
+ A7: { content: "=SUM(D1:D2)" },
599
+ });
600
+ expect(getSheet(model, 1).cells).toMatchObject({
601
+ A1: { content: "=B1" },
602
+ A2: { content: "=Sheet1!D1" },
603
+ A3: { content: "=Sheet2!B1" },
604
+ });
605
+ });
606
+ });
607
+
608
+ describe("Correctly handle undo/redo", () => {
609
+ test("On deletion", () => {
610
+ model = new Model(fullData);
611
+ const beforeRemove = model.exportData();
612
+ removeColumns([0, 2]);
613
+ const afterRemove = model.exportData();
614
+ undo();
615
+ expect(model.exportData()).toEqual(beforeRemove);
616
+ redo();
617
+ expect(model.exportData()).toEqual(afterRemove);
618
+ });
619
+ test("On addition", () => {
620
+ model = new Model(fullData);
621
+ const beforeAdd = model.exportData();
622
+ addColumns(1, "before", 4);
623
+ const afterAdd1 = model.exportData();
624
+ addColumns(4, "after", 4);
625
+ const afterAdd2 = model.exportData();
626
+ undo();
627
+ expect(model.exportData()).toEqual(afterAdd1);
628
+ redo();
629
+ expect(model.exportData()).toEqual(afterAdd2);
630
+ undo();
631
+ undo();
632
+ expect(model.exportData()).toEqual(beforeAdd);
633
+ });
634
+ });
635
+
636
+ describe("Correctly update selection", () => {
637
+ test("On add left 1", () => {
638
+ model = new Model(fullData);
639
+ const zone = { left: 1, right: 3, top: 0, bottom: 2 };
640
+ model.dispatch("SET_SELECTION", {
641
+ zones: [zone],
642
+ anchor: [zone.left, zone.top],
643
+ strict: true,
644
+ });
645
+ expect(model.getters.getSelectedZone()).toEqual({ bottom: 2, left: 1, right: 3, top: 0 });
646
+ addColumns(1, "before", 1);
647
+ expect(model.getters.getSelectedZone()).toEqual({ bottom: 2, left: 2, right: 4, top: 0 });
648
+ });
649
+ test("On add left 3", () => {
650
+ model = new Model(fullData);
651
+ const zone = { left: 1, right: 3, top: 0, bottom: 2 };
652
+ model.dispatch("SET_SELECTION", {
653
+ zones: [zone],
654
+ anchor: [zone.left, zone.top],
655
+ strict: true,
656
+ });
657
+ expect(model.getters.getSelectedZone()).toEqual({ bottom: 2, left: 1, right: 3, top: 0 });
658
+ addColumns(1, "before", 3);
659
+ expect(model.getters.getSelectedZone()).toEqual({ bottom: 2, left: 4, right: 6, top: 0 });
660
+ });
661
+ test("On add right 1", () => {
662
+ model = new Model(fullData);
663
+ const zone = { left: 1, right: 3, top: 0, bottom: 2 };
664
+ model.dispatch("SET_SELECTION", {
665
+ zones: [zone],
666
+ anchor: [zone.left, zone.top],
667
+ strict: true,
668
+ });
669
+ expect(model.getters.getSelectedZone()).toEqual({ bottom: 2, left: 1, right: 3, top: 0 });
670
+ addColumns(1, "after", 1);
671
+ expect(model.getters.getSelectedZone()).toEqual({ bottom: 2, left: 1, right: 3, top: 0 });
672
+ });
673
+ test("On add right 3", () => {
674
+ model = new Model(fullData);
675
+ const zone = { left: 1, right: 3, top: 0, bottom: 2 };
676
+ model.dispatch("SET_SELECTION", {
677
+ zones: [zone],
678
+ anchor: [zone.left, zone.top],
679
+ strict: true,
680
+ });
681
+ expect(model.getters.getSelectedZone()).toEqual({ bottom: 2, left: 1, right: 3, top: 0 });
682
+ addColumns(1, "after", 1);
683
+ expect(model.getters.getSelectedZone()).toEqual({ bottom: 2, left: 1, right: 3, top: 0 });
684
+ });
685
+ });
686
+ });
687
+
688
+ //------------------------------------------------------------------------------
689
+ // Rows
690
+ //------------------------------------------------------------------------------
691
+
692
+ describe("Rows", () => {
693
+ beforeAll(() => {
694
+ makeTestFixture();
695
+ });
696
+ describe("Correctly update size, name, order and number", () => {
697
+ beforeEach(() => {
698
+ model = new Model({
699
+ sheets: [
700
+ {
701
+ colNumber: 1,
702
+ rowNumber: 4,
703
+ rows: {
704
+ 1: { size: 10 },
705
+ 2: { size: 20 },
706
+ },
707
+ },
708
+ ],
709
+ });
710
+ });
711
+ test("On deletion", () => {
712
+ removeRows([0, 2]);
713
+ const size = DEFAULT_CELL_HEIGHT;
714
+ expect(model["workbook"].activeSheet.rows).toEqual([
715
+ { start: 0, end: 10, size: 10, name: "1", cells: {} },
716
+ { start: 10, end: size + 10, size, name: "2", cells: {} },
717
+ ]);
718
+ expect(model["workbook"].activeSheet.rowNumber).toBe(2);
719
+ });
720
+ test("On delete row in inactive sheet", () => {
721
+ model = new Model({
722
+ sheets: [
723
+ {
724
+ colNumber: 3,
725
+ rowNumber: 3,
726
+ cells: {
727
+ B2: { content: "B2 in sheet1" },
728
+ },
729
+ },
730
+ {
731
+ colNumber: 3,
732
+ rowNumber: 3,
733
+ cells: {
734
+ B2: { content: "B2 in sheet2" },
735
+ },
736
+ },
737
+ ],
738
+ });
739
+ const [sheet1, sheet2] = model.getters.getSheets();
740
+ expect(sheet2).not.toBe(model.getters.getActiveSheet());
741
+ model.dispatch("REMOVE_ROWS", {
742
+ rows: [0],
743
+ sheet: sheet2.id,
744
+ });
745
+ expect(sheet1.cells.B2.content).toBe("B2 in sheet1");
746
+ expect(sheet2.cells.B1.content).toBe("B2 in sheet2");
747
+ });
748
+ test("On deletion batch", () => {
749
+ model = new Model({
750
+ sheets: [
751
+ {
752
+ colNumber: 1,
753
+ rowNumber: 4,
754
+ cells: {
755
+ A1: { content: "A1" },
756
+ A2: { content: "A2" },
757
+ A3: { content: "A3" },
758
+ A4: { content: "A4" },
759
+ },
760
+ },
761
+ ],
762
+ });
763
+ removeRows([0, 2, 3]);
764
+ expect(getSheet(model, 0).cells).toMatchObject({
765
+ A1: { content: "A2" },
766
+ });
767
+ });
768
+ test("delete all rows of a range", () => {
769
+ model = new Model({
770
+ sheets: [
771
+ {
772
+ colNumber: 5,
773
+ rowNumber: 9,
774
+ cells: {
775
+ A1: { content: "=SUM(A2:A5)" },
776
+ B1: { content: "=SUM(B6:C6)" }, // single line range
777
+ },
778
+ },
779
+ ],
780
+ });
781
+ removeRows([5]);
782
+ expect(getSheet(model, 0).cells.B1.content).toBe("=SUM(#REF)");
783
+ removeRows([1, 2, 3, 4]);
784
+ expect(getSheet(model, 0).cells.A1.content).toBe("=SUM(#REF)");
785
+ });
786
+ test("update cross sheet range on row deletion", () => {
787
+ model = new Model({
788
+ sheets: [
789
+ { name: "Sheet1", colNumber: 5, rowNumber: 5 },
790
+ {
791
+ name: "Sheet2",
792
+ colNumber: 3,
793
+ rowNumber: 9,
794
+ cells: {
795
+ A1: { content: "=SUM(Sheet1!A2:A3)" },
796
+ },
797
+ },
798
+ ],
799
+ });
800
+ removeRows([0]);
801
+ expect(model.getters.getCell(0, 0, "Sheet2")!.content).toBe("=SUM(Sheet1!A1:A2)");
802
+ });
803
+ test("update cross sheet range on row deletion in inactive sheet", () => {
804
+ model = new Model({
805
+ sheets: [
806
+ { name: "Sheet0", colNumber: 1, rowNumber: 1 }, // <-- less rows than Sheet1
807
+ { name: "Sheet1", colNumber: 5, rowNumber: 5 },
808
+ {
809
+ name: "Sheet2",
810
+ colNumber: 5,
811
+ rowNumber: 9,
812
+ cells: {
813
+ A1: { content: "=SUM(Sheet1!A2:A3)" },
814
+ },
815
+ },
816
+ ],
817
+ });
818
+ const sheet1Id = model.getters.getSheetIdByName("Sheet1");
819
+ model.dispatch("REMOVE_ROWS", { sheet: sheet1Id!, rows: [0] });
820
+ expect(model.getters.getCell(0, 0, "Sheet2")!.content).toBe("=SUM(Sheet1!A1:A2)");
821
+ });
822
+ test("On addition before", () => {
823
+ addRows(1, "before", 2);
824
+ const size = DEFAULT_CELL_HEIGHT;
825
+ expect(model["workbook"].activeSheet.rows).toEqual([
826
+ { start: 0, end: size, size, name: "1", cells: {} },
827
+ { start: size, end: size + 10, size: 10, name: "2", cells: {} },
828
+ { start: size + 10, end: size + 20, size: 10, name: "3", cells: {} },
829
+ { start: size + 20, end: size + 30, size: 10, name: "4", cells: {} },
830
+ { start: size + 30, end: size + 50, size: 20, name: "5", cells: {} },
831
+ { start: size + 50, end: 2 * size + 50, size, name: "6", cells: {} },
832
+ ]);
833
+ const dimensions = model.getters.getGridSize();
834
+ expect(dimensions).toEqual([192, 124]);
835
+ expect(model["workbook"].activeSheet.rowNumber).toBe(6);
836
+ });
837
+ test("On addition after", () => {
838
+ addRows(2, "after", 2);
839
+ const size = DEFAULT_CELL_HEIGHT;
840
+ expect(model["workbook"].activeSheet.rows).toEqual([
841
+ { start: 0, end: size, size, name: "1", cells: {} },
842
+ { start: size, end: size + 10, size: 10, name: "2", cells: {} },
843
+ { start: size + 10, end: size + 30, size: 20, name: "3", cells: {} },
844
+ { start: size + 30, end: size + 50, size: 20, name: "4", cells: {} },
845
+ { start: size + 50, end: size + 70, size: 20, name: "5", cells: {} },
846
+ { start: size + 70, end: 2 * size + 70, size, name: "6", cells: {} },
847
+ ]);
848
+ const dimensions = model.getters.getGridSize();
849
+ expect(dimensions).toEqual([192, 144]);
850
+ expect(model["workbook"].activeSheet.rowNumber).toBe(6);
851
+ });
852
+
853
+ test("activate Sheet: same size", () => {
854
+ addRows(2, "after", 1);
855
+ let dimensions = model.getters.getGridSize();
856
+ expect(dimensions).toEqual([192, 124]);
857
+ const to = model.getters.getActiveSheet();
858
+ model.dispatch("CREATE_SHEET", { activate: true, id: "42" });
859
+ const from = model.getters.getActiveSheet();
860
+ model.dispatch("ACTIVATE_SHEET", { from, to });
861
+ dimensions = model.getters.getGridSize();
862
+ expect(dimensions).toEqual([192, 124]);
863
+ });
864
+ });
865
+
866
+ describe("Correctly update merges", () => {
867
+ beforeEach(() => {
868
+ model = new Model({
869
+ sheets: [
870
+ {
871
+ colNumber: 4,
872
+ rowNumber: 5,
873
+ merges: ["A1:A5", "B2:B5", "C3:C5", "D2:D4"],
874
+ },
875
+ ],
876
+ });
877
+ });
878
+ test("On deletion", () => {
879
+ removeRows([1, 3]);
880
+ expect(model["workbook"].activeSheet.merges).toEqual({
881
+ 5: { id: 5, topLeft: "A1", top: 0, bottom: 2, left: 0, right: 0 },
882
+ 6: { id: 6, topLeft: "B2", top: 1, bottom: 2, left: 1, right: 1 },
883
+ 7: { id: 7, topLeft: "C2", top: 1, bottom: 2, left: 2, right: 2 },
884
+ });
885
+ expect(model["workbook"].activeSheet.mergeCellMap).toEqual({
886
+ A1: 5,
887
+ A2: 5,
888
+ A3: 5,
889
+ B2: 6,
890
+ B3: 6,
891
+ C2: 7,
892
+ C3: 7,
893
+ });
894
+ });
895
+ test("On addition", () => {
896
+ addRows(1, "before", 1);
897
+ addRows(0, "after", 1);
898
+ expect(model["workbook"].activeSheet.merges).toEqual({
899
+ 9: { id: 9, topLeft: "A1", top: 0, bottom: 6, left: 0, right: 0 },
900
+ 10: { id: 10, topLeft: "B4", top: 3, bottom: 6, left: 1, right: 1 },
901
+ 11: { id: 11, topLeft: "C5", top: 4, bottom: 6, left: 2, right: 2 },
902
+ 12: { id: 12, topLeft: "D4", top: 3, bottom: 5, left: 3, right: 3 },
903
+ });
904
+ expect(model["workbook"].activeSheet.mergeCellMap).toEqual({
905
+ A1: 9,
906
+ A2: 9,
907
+ A3: 9,
908
+ A4: 9,
909
+ A5: 9,
910
+ A6: 9,
911
+ A7: 9,
912
+ B4: 10,
913
+ B5: 10,
914
+ B6: 10,
915
+ B7: 10,
916
+ C5: 11,
917
+ C6: 11,
918
+ C7: 11,
919
+ D4: 12,
920
+ D5: 12,
921
+ D6: 12,
922
+ });
923
+ });
924
+ });
925
+
926
+ describe("Correctly update border and style", () => {
927
+ beforeEach(() => {
928
+ model = new Model({
929
+ sheets: [
930
+ {
931
+ colNumber: 4,
932
+ rowNumber: 4,
933
+ cells: {
934
+ A1: { style: 1 },
935
+ A2: { style: 1 },
936
+ A4: { style: 1 },
937
+ B1: { border: 1 },
938
+ B2: { border: 1 },
939
+ B4: { border: 1 },
940
+ C1: { style: 1, border: 1 },
941
+ C2: { style: 1, border: 1, format: "0.00%" },
942
+ C4: { style: 1, border: 1 },
943
+ D2: { style: 1, border: 1 },
944
+ },
945
+ merges: ["D2:D3"],
946
+ },
947
+ ],
948
+ styles: { 1: { textColor: "#fe0000" } },
949
+ borders: { 1: { top: ["thin", "#000"] } },
950
+ });
951
+ });
952
+ test("On deletion", () => {
953
+ removeRows([1]);
954
+ expect(model["workbook"].activeSheet.cells.A2).toBeUndefined();
955
+ expect(model["workbook"].activeSheet.cells.B2).toBeUndefined();
956
+ expect(model["workbook"].activeSheet.cells.C2).toBeUndefined();
957
+ expect(model["workbook"].activeSheet.cells).toMatchObject({
958
+ A1: { style: 1 },
959
+ A3: { style: 1 },
960
+ B1: { border: 1 },
961
+ B3: { border: 1 },
962
+ C1: { style: 1, border: 1 },
963
+ C3: { style: 1, border: 1 },
964
+ D2: { style: 1, border: 1 },
965
+ });
966
+ });
967
+
968
+ test("On addition", () => {
969
+ addRows(1, "before", 1);
970
+ addRows(2, "after", 2);
971
+ expect(model["workbook"].activeSheet.cells).toMatchObject({
972
+ A1: { style: 1 },
973
+ B1: { border: 1 },
974
+ C1: { style: 1, border: 1 },
975
+ A2: { style: 1 },
976
+ B2: { border: 1 },
977
+ C2: { style: 1, border: 1, format: "0.00%" },
978
+ D2: { style: 1 },
979
+ A3: { style: 1 },
980
+ B3: { border: 1 },
981
+ C3: { style: 1, border: 1, format: "0.00%" },
982
+ D3: { style: 1, border: 1 },
983
+ A5: { style: 1 },
984
+ });
985
+ expect(Object.values(model["workbook"].activeSheet.merges)[0]).toMatchObject({
986
+ top: 2,
987
+ bottom: 5,
988
+ topLeft: "D3",
989
+ });
990
+ });
991
+ });
992
+
993
+ describe("Correctly update references", () => {
994
+ beforeEach(() => {
995
+ model = new Model({
996
+ sheets: [
997
+ {
998
+ colNumber: 7,
999
+ rowNumber: 7,
1000
+ cells: {
1001
+ A1: { content: "=A2" },
1002
+ A4: { content: "=A1" },
1003
+ B1: { content: "=Sheet1!A2" },
1004
+ B4: { content: "=B1" },
1005
+ C1: { content: "=Sheet2!A2" },
1006
+ C4: { content: "=A$5" },
1007
+ D4: { content: "=C4" },
1008
+ E1: { content: "=SUM(A1:A4)" },
1009
+ F1: { content: "=SUM(A3:A4)" },
1010
+ G1: { content: "=SUM(B2:C2)" },
1011
+ },
1012
+ },
1013
+ {
1014
+ colNumber: 3,
1015
+ rowNumber: 1,
1016
+ cells: {
1017
+ A1: { content: "=A2" },
1018
+ B1: { content: "=Sheet1!A2" },
1019
+ C1: { content: "=Sheet2!A2" },
1020
+ },
1021
+ },
1022
+ ],
1023
+ });
1024
+ });
1025
+
1026
+ test("On deletion", () => {
1027
+ removeRows([1, 2]);
1028
+ expect(getSheet(model, 0).cells).toMatchObject({
1029
+ A1: { content: "=#REF" },
1030
+ A2: { content: "=A1" },
1031
+ B1: { content: "=#REF" },
1032
+ B2: { content: "=B1" },
1033
+ C1: { content: "=Sheet2!A2" },
1034
+ C2: { content: "=A$3" },
1035
+ D2: { content: "=C2" },
1036
+ });
1037
+ expect(getSheet(model, 1).cells).toMatchObject({
1038
+ A1: { content: "=A2" },
1039
+ B1: { content: "=#REF" },
1040
+ C1: { content: "=Sheet2!A2" },
1041
+ });
1042
+ });
1043
+ test("delete row on inactive sheet", () => {
1044
+ const model = new Model({
1045
+ sheets: [
1046
+ {
1047
+ colNumber: 4,
1048
+ rowNumber: 7,
1049
+ cells: {
1050
+ B2: { content: "=Sheet1!A2" },
1051
+ C1: { content: "=Sheet2!A2" },
1052
+ },
1053
+ },
1054
+ {
1055
+ colNumber: 3,
1056
+ rowNumber: 2,
1057
+ cells: {
1058
+ A2: { content: "=B2" },
1059
+ B2: { content: "=Sheet1!A2" },
1060
+ C2: { content: "=Sheet2!A2" },
1061
+ },
1062
+ },
1063
+ ],
1064
+ });
1065
+ const [sheet1, sheet2] = model.getters.getSheets();
1066
+ expect(sheet2.id).not.toBe(model.getters.getActiveSheet()),
1067
+ model.dispatch("REMOVE_ROWS", {
1068
+ rows: [0],
1069
+ sheet: sheet2.id,
1070
+ });
1071
+ expect(sheet1.cells).toMatchObject({
1072
+ B2: { content: "=Sheet1!A2" },
1073
+ C1: { content: "=Sheet2!A1" },
1074
+ });
1075
+ expect(sheet2.cells).toMatchObject({
1076
+ A1: { content: "=B1" },
1077
+ B1: { content: "=Sheet1!A2" },
1078
+ C1: { content: "=Sheet2!A1" },
1079
+ });
1080
+ });
1081
+ test("On first row deletion", () => {
1082
+ model = new Model({
1083
+ sheets: [
1084
+ {
1085
+ colNumber: 3,
1086
+ rowNumber: 3,
1087
+ cells: {
1088
+ B2: { content: "=SUM(A1:A3)" },
1089
+ },
1090
+ },
1091
+ ],
1092
+ });
1093
+ removeRows([0]);
1094
+ expect(getSheet(model, 0).cells).toMatchObject({
1095
+ B1: { content: "=SUM(A1:A2)" },
1096
+ });
1097
+ });
1098
+
1099
+ test("with space in the sheet name", () => {
1100
+ model = new Model({
1101
+ sheets: [
1102
+ {
1103
+ id: "sheet1",
1104
+ name: "Sheet 1",
1105
+ colNumber: 3,
1106
+ rowNumber: 3,
1107
+ cells: {
1108
+ C2: { content: "=SUM('Sheet 1'!B2)" },
1109
+ C3: { content: "=SUM('Sheet 1'!B2:B3)" },
1110
+ },
1111
+ },
1112
+ ],
1113
+ });
1114
+ removeRows([0]);
1115
+ expect(getSheet(model, 0).cells).toMatchObject({
1116
+ C1: { content: "=SUM('Sheet 1'!B1)" },
1117
+ C2: { content: "=SUM('Sheet 1'!B1:B2)" },
1118
+ });
1119
+ removeColumns([0]);
1120
+ expect(getSheet(model, 0).cells).toMatchObject({
1121
+ B1: { content: "=SUM('Sheet 1'!A1)" },
1122
+ B2: { content: "=SUM('Sheet 1'!A1:A2)" },
1123
+ });
1124
+ addColumns(0, "before", 1);
1125
+ expect(getSheet(model, 0).cells).toMatchObject({
1126
+ C1: { content: "=SUM('Sheet 1'!B1)" },
1127
+ C2: { content: "=SUM('Sheet 1'!B1:B2)" },
1128
+ });
1129
+ addRows(0, "before", 1);
1130
+ expect(getSheet(model, 0).cells).toMatchObject({
1131
+ C2: { content: "=SUM('Sheet 1'!B2)" },
1132
+ C3: { content: "=SUM('Sheet 1'!B2:B3)" },
1133
+ });
1134
+ });
1135
+ test("On multiple row deletion including the first one", () => {
1136
+ model = new Model({
1137
+ sheets: [
1138
+ {
1139
+ colNumber: 3,
1140
+ rowNumber: 6,
1141
+ cells: {
1142
+ B1: { content: "=SUM(A2:A5)" },
1143
+ },
1144
+ },
1145
+ ],
1146
+ });
1147
+ removeRows([1, 2]);
1148
+ expect(getSheet(model, 0).cells).toMatchObject({
1149
+ B1: { content: "=SUM(A2:A3)" },
1150
+ });
1151
+ });
1152
+ test("strange test in Odoo", () => {
1153
+ model = new Model({
1154
+ sheets: [
1155
+ {
1156
+ colNumber: 1,
1157
+ rowNumber: 1000,
1158
+ cells: {
1159
+ A5: { content: "=SUM(A6:A255)" },
1160
+ A256: { content: "=SUM(A257:A506)" },
1161
+ },
1162
+ },
1163
+ ],
1164
+ });
1165
+ const rows: number[] = [];
1166
+ for (let i = 6; i <= 254; i++) {
1167
+ rows.push(i);
1168
+ }
1169
+ for (let i = 257; i <= 605; i++) {
1170
+ rows.push(i);
1171
+ }
1172
+
1173
+ removeRows(rows);
1174
+ expect(getSheet(model, 0).cells).toMatchObject({
1175
+ A5: { content: "=SUM(A6)" },
1176
+ A7: { content: "=SUM(A8)" },
1177
+ });
1178
+ });
1179
+ test("On last row deletion", () => {
1180
+ model = new Model({
1181
+ sheets: [
1182
+ {
1183
+ colNumber: 3,
1184
+ rowNumber: 3,
1185
+ cells: {
1186
+ B1: { content: "=SUM(A1:A3)" },
1187
+ },
1188
+ },
1189
+ ],
1190
+ });
1191
+ removeRows([2]);
1192
+ expect(getSheet(model, 0).cells).toMatchObject({
1193
+ B1: { content: "=SUM(A1:A2)" },
1194
+ });
1195
+ });
1196
+ test("On multiple row", () => {
1197
+ model = new Model({
1198
+ sheets: [
1199
+ {
1200
+ colNumber: 1,
1201
+ rowNumber: 8,
1202
+ cells: {
1203
+ A1: { content: "=SUM(A2:A5)" },
1204
+ },
1205
+ },
1206
+ ],
1207
+ });
1208
+ removeRows([2, 3]);
1209
+ expect(getSheet(model, 0).cells).toMatchObject({
1210
+ A1: { content: "=SUM(A2:A3)" },
1211
+ });
1212
+ });
1213
+ test("On multiple rows (7)", () => {
1214
+ model = new Model({
1215
+ sheets: [
1216
+ {
1217
+ colNumber: 1,
1218
+ rowNumber: 8,
1219
+ cells: {
1220
+ A1: { content: "=SUM(A2:A8)" },
1221
+ },
1222
+ },
1223
+ ],
1224
+ });
1225
+ removeRows([1, 2, 3, 4, 5, 6]);
1226
+ expect(getSheet(model, 0).cells).toMatchObject({
1227
+ A1: { content: "=SUM(A2)" },
1228
+ });
1229
+ });
1230
+ test("On multiple row deletion including the last one", () => {
1231
+ model = new Model({
1232
+ sheets: [
1233
+ {
1234
+ colNumber: 4,
1235
+ rowNumber: 4,
1236
+ cells: {
1237
+ B1: { content: "=SUM(A1:A4)" },
1238
+ },
1239
+ },
1240
+ ],
1241
+ });
1242
+ removeRows([2, 3]);
1243
+ expect(getSheet(model, 0).cells).toMatchObject({
1244
+ B1: { content: "=SUM(A1:A2)" },
1245
+ });
1246
+ });
1247
+ test("On multiple row deletion including the last and beyond", () => {
1248
+ model = new Model({
1249
+ sheets: [
1250
+ {
1251
+ colNumber: 2,
1252
+ rowNumber: 8,
1253
+ cells: {
1254
+ B2: { content: "=SUM(A1:A4)" },
1255
+ },
1256
+ },
1257
+ ],
1258
+ });
1259
+ removeRows([3, 4, 5, 6, 7]);
1260
+ expect(getSheet(model, 0).cells).toMatchObject({
1261
+ B2: { content: "=SUM(A1:A3)" },
1262
+ });
1263
+ });
1264
+ test("On addition", () => {
1265
+ addRows(1, "before", 1);
1266
+ addRows(0, "after", 1);
1267
+ expect(getSheet(model, 0).cells).toMatchObject({
1268
+ A1: { content: "=A4" },
1269
+ A6: { content: "=A1" },
1270
+ B1: { content: "=Sheet1!A4" },
1271
+ B6: { content: "=B1" },
1272
+ C1: { content: "=Sheet2!A2" },
1273
+ C6: { content: "=A$7" },
1274
+ D6: { content: "=C6" },
1275
+ E1: { content: "=SUM(A1:A6)" },
1276
+ F1: { content: "=SUM(A5:A6)" },
1277
+ G1: { content: "=SUM(B4:C4)" },
1278
+ });
1279
+ expect(getSheet(model, 1).cells).toMatchObject({
1280
+ A1: { content: "=A2" },
1281
+ B1: { content: "=Sheet1!A4" },
1282
+ C1: { content: "=Sheet2!A2" },
1283
+ });
1284
+ });
1285
+ });
1286
+
1287
+ describe("Correctly handle undo/redo", () => {
1288
+ test("On deletion", () => {
1289
+ model = new Model(fullData);
1290
+ const beforeRemove = model.exportData();
1291
+ removeRows([0, 2]);
1292
+ const afterRemove = model.exportData();
1293
+ undo();
1294
+ expect(model.exportData()).toEqual(beforeRemove);
1295
+ redo();
1296
+ expect(model.exportData()).toEqual(afterRemove);
1297
+ });
1298
+
1299
+ test("On addition", () => {
1300
+ model = new Model(fullData);
1301
+ const beforeAdd = model.exportData();
1302
+ addRows(2, "before", 2);
1303
+ const afterAdd1 = model.exportData();
1304
+ addRows(4, "after", 4);
1305
+ const afterAdd2 = model.exportData();
1306
+ undo();
1307
+ expect(model.exportData()).toEqual(afterAdd1);
1308
+ redo();
1309
+ expect(model.exportData()).toEqual(afterAdd2);
1310
+ undo();
1311
+ undo();
1312
+ expect(model.exportData()).toEqual(beforeAdd);
1313
+ });
1314
+ });
1315
+
1316
+ describe("Correctly update selection", () => {
1317
+ test("On add top 1", () => {
1318
+ model = new Model(fullData);
1319
+ const zone = { left: 2, right: 3, top: 0, bottom: 1 };
1320
+ model.dispatch("SET_SELECTION", {
1321
+ zones: [zone],
1322
+ anchor: [zone.left, zone.top],
1323
+ strict: true,
1324
+ });
1325
+ expect(model.getters.getSelectedZone()).toEqual({ bottom: 1, left: 2, right: 3, top: 0 });
1326
+ addRows(0, "before", 1);
1327
+ expect(model.getters.getSelectedZone()).toEqual({ bottom: 2, left: 2, right: 3, top: 1 });
1328
+ });
1329
+ test("On add top 3", () => {
1330
+ model = new Model(fullData);
1331
+ const zone = { left: 2, right: 3, top: 0, bottom: 1 };
1332
+ model.dispatch("SET_SELECTION", {
1333
+ zones: [zone],
1334
+ anchor: [zone.left, zone.top],
1335
+ strict: true,
1336
+ });
1337
+ expect(model.getters.getSelectedZone()).toEqual({ bottom: 1, left: 2, right: 3, top: 0 });
1338
+ addRows(0, "before", 3);
1339
+ expect(model.getters.getSelectedZone()).toEqual({ bottom: 4, left: 2, right: 3, top: 3 });
1340
+ });
1341
+ test("On add bottom 1", () => {
1342
+ model = new Model(fullData);
1343
+ const zone = { left: 2, right: 3, top: 0, bottom: 1 };
1344
+ model.dispatch("SET_SELECTION", {
1345
+ zones: [zone],
1346
+ anchor: [zone.left, zone.top],
1347
+ strict: true,
1348
+ });
1349
+ expect(model.getters.getSelectedZone()).toEqual({ bottom: 1, left: 2, right: 3, top: 0 });
1350
+ addRows(0, "after", 1);
1351
+ expect(model.getters.getSelectedZone()).toEqual({ bottom: 1, left: 2, right: 3, top: 0 });
1352
+ });
1353
+ test("On add bottom 3", () => {
1354
+ model = new Model(fullData);
1355
+ const zone = { left: 1, right: 3, top: 0, bottom: 2 };
1356
+ model.dispatch("SET_SELECTION", {
1357
+ zones: [zone],
1358
+ anchor: [zone.left, zone.top],
1359
+ strict: true,
1360
+ });
1361
+ expect(model.getters.getSelectedZone()).toEqual({ bottom: 2, left: 1, right: 3, top: 0 });
1362
+ addRows(0, "after", 3);
1363
+ expect(model.getters.getSelectedZone()).toEqual({ bottom: 2, left: 1, right: 3, top: 0 });
1364
+ });
1365
+ });
1366
+ });