@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,1253 @@
1
+ import { toZone } from "../../src/helpers";
2
+ import { Model } from "../../src/model";
3
+ import { CommandResult } from "../../src/types";
4
+ import "../canvas.mock";
5
+ import { setInputValueAndTrigger, triggerMouseEvent } from "../dom_helper";
6
+ import {
7
+ createColorScale,
8
+ createEqualCF,
9
+ GridParent,
10
+ makeTestFixture,
11
+ mockUuidV4To,
12
+ nextTick,
13
+ target,
14
+ } from "../helpers";
15
+ jest.mock("../../src/helpers/uuid", () => require("../__mocks__/uuid"));
16
+
17
+ let model: Model;
18
+
19
+ beforeEach(() => {
20
+ model = new Model();
21
+ });
22
+
23
+ describe("conditional format", () => {
24
+ test("works", () => {
25
+ model.dispatch("SET_VALUE", { xc: "A1", text: "1" });
26
+ model.dispatch("SET_VALUE", { xc: "A2", text: "2" });
27
+ model.dispatch("SET_VALUE", { xc: "A3", text: "3" });
28
+ model.dispatch("SET_VALUE", { xc: "A4", text: "4" });
29
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
30
+ cf: createEqualCF(["A1:A4"], "2", { fillColor: "#FF0000" }, "1"),
31
+ sheet: model.getters.getActiveSheet(),
32
+ });
33
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
34
+ cf: createEqualCF(["A1:A4"], "4", { fillColor: "#0000FF" }, "2"),
35
+ sheet: model.getters.getActiveSheet(),
36
+ });
37
+ expect(model.getters.getConditionalFormats()).toEqual([
38
+ {
39
+ rule: {
40
+ values: ["2"],
41
+ operator: "Equal",
42
+ type: "CellIsRule",
43
+ style: {
44
+ fillColor: "#FF0000",
45
+ },
46
+ },
47
+ id: "1",
48
+ ranges: ["A1:A4"],
49
+ },
50
+ {
51
+ rule: {
52
+ values: ["4"],
53
+ operator: "Equal",
54
+ type: "CellIsRule",
55
+ style: {
56
+ fillColor: "#0000FF",
57
+ },
58
+ },
59
+ id: "2",
60
+ ranges: ["A1:A4"],
61
+ },
62
+ ]);
63
+ expect(model.getters.getConditionalStyle("A1")).toBeUndefined();
64
+ expect(model.getters.getConditionalStyle("A2")).toEqual({ fillColor: "#FF0000" });
65
+ expect(model.getters.getConditionalStyle("A3")).toBeUndefined();
66
+ expect(model.getters.getConditionalStyle("A4")).toEqual({ fillColor: "#0000FF" });
67
+ });
68
+
69
+ test("Add conditional formating on inactive sheet", () => {
70
+ model = new Model();
71
+ model.dispatch("CREATE_SHEET", { id: "42" });
72
+ const [activeSheet, sheet] = model.getters.getSheets();
73
+ expect(sheet.id).not.toBe(model.getters.getActiveSheet());
74
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
75
+ cf: createEqualCF(["A1:A4"], "4", { fillColor: "#0000FF" }, "2"),
76
+ sheet: sheet.id,
77
+ });
78
+ model.dispatch("ACTIVATE_SHEET", {
79
+ from: activeSheet.id,
80
+ to: "42",
81
+ });
82
+ expect(model.getters.getConditionalFormats()).toEqual([
83
+ {
84
+ rule: {
85
+ values: ["4"],
86
+ operator: "Equal",
87
+ type: "CellIsRule",
88
+ style: {
89
+ fillColor: "#0000FF",
90
+ },
91
+ },
92
+ id: "2",
93
+ ranges: ["A1:A4"],
94
+ },
95
+ ]);
96
+ });
97
+
98
+ test("remove a conditional format rule", () => {
99
+ model.dispatch("SET_VALUE", { xc: "A1", text: "1" });
100
+ model.dispatch("SET_VALUE", { xc: "A2", text: "2" });
101
+ model.dispatch("SET_VALUE", { xc: "A3", text: "3" });
102
+ model.dispatch("SET_VALUE", { xc: "A4", text: "4" });
103
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
104
+ cf: createEqualCF(["A1:A4"], "2", { fillColor: "#FF0000" }, "1"),
105
+ sheet: model.getters.getActiveSheet(),
106
+ });
107
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
108
+ cf: createEqualCF(["A1:A4"], "4", { fillColor: "#0000FF" }, "2"),
109
+ sheet: model.getters.getActiveSheet(),
110
+ });
111
+ expect(model.getters.getConditionalFormats()).toEqual([
112
+ {
113
+ rule: {
114
+ values: ["2"],
115
+ operator: "Equal",
116
+ type: "CellIsRule",
117
+ style: {
118
+ fillColor: "#FF0000",
119
+ },
120
+ },
121
+ id: "1",
122
+ ranges: ["A1:A4"],
123
+ },
124
+ {
125
+ rule: {
126
+ values: ["4"],
127
+ operator: "Equal",
128
+ type: "CellIsRule",
129
+ style: {
130
+ fillColor: "#0000FF",
131
+ },
132
+ },
133
+ id: "2",
134
+ ranges: ["A1:A4"],
135
+ },
136
+ ]);
137
+ expect(model.getters.getConditionalStyle("A1")).toBeUndefined();
138
+ expect(model.getters.getConditionalStyle("A2")).toEqual({ fillColor: "#FF0000" });
139
+ expect(model.getters.getConditionalStyle("A3")).toBeUndefined();
140
+ expect(model.getters.getConditionalStyle("A4")).toEqual({ fillColor: "#0000FF" });
141
+ model.dispatch("REMOVE_CONDITIONAL_FORMAT", {
142
+ id: "2",
143
+ sheet: model.getters.getActiveSheet(),
144
+ });
145
+ expect(model.getters.getConditionalStyle("A1")).toBeUndefined();
146
+ expect(model.getters.getConditionalStyle("A2")).toEqual({ fillColor: "#FF0000" });
147
+ expect(model.getters.getConditionalStyle("A3")).toBeUndefined();
148
+ expect(model.getters.getConditionalStyle("A4")).toBeUndefined();
149
+ });
150
+
151
+ test("works on multiple ranges", () => {
152
+ model.dispatch("SET_VALUE", { xc: "A1", text: "1" });
153
+ model.dispatch("SET_VALUE", { xc: "A2", text: "1" });
154
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
155
+ cf: createEqualCF(["A1", "A2"], "1", { fillColor: "#FF0000" }, "1"),
156
+ sheet: model.getters.getActiveSheet(),
157
+ });
158
+ expect(model.getters.getConditionalStyle("A1")).toEqual({ fillColor: "#FF0000" });
159
+ expect(model.getters.getConditionalStyle("A2")).toEqual({ fillColor: "#FF0000" });
160
+ });
161
+
162
+ test("can be undo/redo", () => {
163
+ model.dispatch("SET_VALUE", { xc: "A1", text: "1" });
164
+ model.dispatch("SET_VALUE", { xc: "A2", text: "1" });
165
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
166
+ cf: createEqualCF(["A1", "A2"], "1", { fillColor: "#FF0000" }, "1"),
167
+ sheet: model.getters.getActiveSheet(),
168
+ });
169
+ expect(model.getters.getConditionalStyle("A1")).toEqual({ fillColor: "#FF0000" });
170
+ expect(model.getters.getConditionalStyle("A2")).toEqual({ fillColor: "#FF0000" });
171
+
172
+ model.dispatch("UNDO");
173
+
174
+ expect(model.getters.getConditionalStyle("A1")).toBeUndefined();
175
+ expect(model.getters.getConditionalStyle("A2")).toBeUndefined();
176
+
177
+ model.dispatch("REDO");
178
+
179
+ expect(model.getters.getConditionalStyle("A1")).toEqual({ fillColor: "#FF0000" });
180
+ expect(model.getters.getConditionalStyle("A2")).toEqual({ fillColor: "#FF0000" });
181
+ });
182
+
183
+ test("Still correct after ADD_COLUMNS and UNDO/REDO", () => {
184
+ const sheet = model.getters.getActiveSheet();
185
+ model.dispatch("SET_VALUE", { xc: "B1", text: "1" });
186
+ model.dispatch("SET_VALUE", { xc: "B2", text: "1" });
187
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
188
+ cf: createEqualCF(["B1", "B2"], "1", { fillColor: "#FF0000" }, "1"),
189
+ sheet,
190
+ });
191
+ expect(model.getters.getConditionalStyle("B1")).toEqual({ fillColor: "#FF0000" });
192
+ expect(model.getters.getConditionalStyle("B2")).toEqual({ fillColor: "#FF0000" });
193
+ model.dispatch("ADD_COLUMNS", { sheet, column: 0, position: "after", quantity: 1 });
194
+ expect(model.getters.getConditionalStyle("B1")).toBeUndefined();
195
+ expect(model.getters.getConditionalStyle("B2")).toBeUndefined();
196
+ expect(model.getters.getConditionalStyle("C1")).toEqual({ fillColor: "#FF0000" });
197
+ expect(model.getters.getConditionalStyle("C2")).toEqual({ fillColor: "#FF0000" });
198
+
199
+ model.dispatch("UNDO");
200
+ expect(model.getters.getConditionalStyle("B1")).toEqual({ fillColor: "#FF0000" });
201
+ expect(model.getters.getConditionalStyle("B2")).toEqual({ fillColor: "#FF0000" });
202
+ expect(model.getters.getConditionalStyle("C1")).toBeUndefined();
203
+ expect(model.getters.getConditionalStyle("C2")).toBeUndefined();
204
+
205
+ model.dispatch("REDO");
206
+ expect(model.getters.getConditionalStyle("B1")).toBeUndefined();
207
+ expect(model.getters.getConditionalStyle("B2")).toBeUndefined();
208
+ expect(model.getters.getConditionalStyle("C1")).toEqual({ fillColor: "#FF0000" });
209
+ expect(model.getters.getConditionalStyle("C2")).toEqual({ fillColor: "#FF0000" });
210
+ });
211
+
212
+ test("is saved/restored", () => {
213
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
214
+ cf: createEqualCF(["A1:A4"], "2", { fillColor: "#FF0000" }, "1"),
215
+ sheet: model.getters.getActiveSheet(),
216
+ });
217
+ const workbookData = model.exportData();
218
+ const newModel = new Model(workbookData);
219
+ expect(newModel.getters.getConditionalFormats()).toBe(model.getters.getConditionalFormats());
220
+ });
221
+
222
+ test("works after value update", () => {
223
+ model.dispatch("SET_VALUE", { xc: "A1", text: "1" });
224
+ model.dispatch("SET_VALUE", { xc: "A2", text: "2" });
225
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
226
+ cf: createEqualCF(["A1"], "2", { fillColor: "#FF0000" }, "1"),
227
+ sheet: model.getters.getActiveSheet(),
228
+ });
229
+ expect(model.getters.getConditionalStyle("A1")).toBeUndefined();
230
+ model.dispatch("SET_VALUE", { xc: "A1", text: "2" });
231
+ expect(model.getters.getConditionalStyle("A1")).toEqual({ fillColor: "#FF0000" });
232
+ model.dispatch("SET_VALUE", { xc: "A1", text: "1" });
233
+ expect(model.getters.getConditionalStyle("A1")).toBeUndefined();
234
+ model.dispatch("SET_VALUE", { xc: "A1", text: "=A2" });
235
+ expect(model.getters.getConditionalStyle("A1")).toEqual({ fillColor: "#FF0000" });
236
+ });
237
+
238
+ test("works when cells are in error", () => {
239
+ model.dispatch("SET_VALUE", { xc: "A1", text: "2" });
240
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
241
+ cf: createEqualCF(["A1"], "2", { fillColor: "#FF0000" }, "1"),
242
+ sheet: model.getters.getActiveSheet(),
243
+ });
244
+ model.dispatch("SET_VALUE", { xc: "A1", text: "=BLA" });
245
+ expect(model.getters.getConditionalStyle("A1")).toBeUndefined();
246
+ });
247
+
248
+ test("multiple conditional formats for one cell", () => {
249
+ model.dispatch("SET_VALUE", { xc: "A1", text: "2" });
250
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
251
+ cf: createEqualCF(["A1"], "2", { fillColor: "#FF0000" }, "1"),
252
+ sheet: model.getters.getActiveSheet(),
253
+ });
254
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
255
+ cf: createEqualCF(["A1"], "2", { textColor: "#445566" }, "2"),
256
+ sheet: model.getters.getActiveSheet(),
257
+ });
258
+ expect(model.getters.getConditionalStyle("A1")).toEqual({
259
+ fillColor: "#FF0000",
260
+ textColor: "#445566",
261
+ });
262
+ });
263
+
264
+ test("multiple conditional formats with same style", () => {
265
+ model.dispatch("SET_VALUE", { xc: "A1", text: "2" });
266
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
267
+ cf: createEqualCF(["A1"], "2", { fillColor: "#FF0000" }, "1"),
268
+ sheet: model.getters.getActiveSheet(),
269
+ });
270
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
271
+ cf: createEqualCF(["A1"], "2", { fillColor: "#FF0000" }, "2"),
272
+ sheet: model.getters.getActiveSheet(),
273
+ });
274
+ expect(model.getters.getConditionalStyle("A1")).toEqual({ fillColor: "#FF0000" });
275
+ });
276
+
277
+ test.skip("multiple conditional formats using stopIfTrue flag", () => {
278
+ model.dispatch("SET_VALUE", { xc: "A1", text: "2" });
279
+
280
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
281
+ cf: {
282
+ ranges: ["A1"],
283
+ rule: {
284
+ values: ["2"],
285
+ operator: "Equal",
286
+ type: "CellIsRule",
287
+ style: { fillColor: "#FF0000" },
288
+ },
289
+ id: "1",
290
+ stopIfTrue: true,
291
+ },
292
+ sheet: model.getters.getActiveSheet(),
293
+ });
294
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
295
+ cf: createEqualCF(["A1"], "2", { fillColor: "#445566" }, "2"),
296
+ sheet: model.getters.getActiveSheet(),
297
+ });
298
+ expect(model.getters.getConditionalStyle("A1")).toEqual({ fillColor: "#FF0000" });
299
+ });
300
+
301
+ test("Set conditionalFormat on empty cell", () => {
302
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
303
+ cf: createEqualCF(["A1"], "", { fillColor: "#FF0000" }, "1"),
304
+ sheet: model.getters.getActiveSheet(),
305
+ });
306
+ expect(model.getters.getConditionalStyle("A1")).toEqual({ fillColor: "#FF0000" });
307
+ });
308
+
309
+ describe("Grid Manipulation", () => {
310
+ const rule = {
311
+ values: ["42"],
312
+ operator: "Equal",
313
+ type: "CellIsRule",
314
+ style: { fillColor: "orange" },
315
+ };
316
+ test("On row deletion", () => {
317
+ model = new Model({
318
+ sheets: [
319
+ {
320
+ colNumber: 7,
321
+ rowNumber: 4,
322
+ cells: {
323
+ C3: { content: "42" },
324
+ },
325
+ conditionalFormats: [
326
+ { id: "1", ranges: ["A1:A1"], rule },
327
+ { id: "2", ranges: ["B2:B2"], rule },
328
+ { id: "3", ranges: ["C3:C3"], rule },
329
+ { id: "4", ranges: ["D1:D2"], rule },
330
+ { id: "5", ranges: ["E2:E3"], rule },
331
+ { id: "6", ranges: ["F1:F3"], rule },
332
+ { id: "7", ranges: ["G1:G4"], rule },
333
+ ],
334
+ },
335
+ ],
336
+ });
337
+ model.dispatch("REMOVE_ROWS", {
338
+ sheet: model.getters.getActiveSheet(),
339
+ rows: [1, 3],
340
+ });
341
+ expect(model.getters.getConditionalFormats()).toEqual([
342
+ { id: "1", ranges: ["A1:A1"], rule },
343
+ { id: "3", ranges: ["C2:C2"], rule },
344
+ { id: "4", ranges: ["D1:D1"], rule },
345
+ { id: "5", ranges: ["E2:E2"], rule },
346
+ { id: "6", ranges: ["F1:F2"], rule },
347
+ { id: "7", ranges: ["G1:G2"], rule },
348
+ ]);
349
+ expect(model.getters.getConditionalStyle("B2")).toBeUndefined();
350
+ expect(model.getters.getConditionalStyle("C2")!.fillColor).toBe("orange");
351
+ expect(model.getters.getConditionalStyle("C3")).toBeUndefined();
352
+ });
353
+ test("On column deletion", () => {
354
+ model = new Model({
355
+ sheets: [
356
+ {
357
+ colNumber: 4,
358
+ rowNumber: 7,
359
+ cells: {
360
+ C3: { content: "42" },
361
+ },
362
+ conditionalFormats: [
363
+ { id: "1", ranges: ["A1:A1"], rule },
364
+ { id: "2", ranges: ["B2:B2"], rule },
365
+ { id: "3", ranges: ["C3:C3"], rule },
366
+ { id: "4", ranges: ["A4:B4"], rule },
367
+ { id: "5", ranges: ["B5:C5"], rule },
368
+ { id: "6", ranges: ["A6:C6"], rule },
369
+ { id: "7", ranges: ["A7:D7"], rule },
370
+ ],
371
+ },
372
+ ],
373
+ });
374
+ model.dispatch("REMOVE_COLUMNS", {
375
+ sheet: model.getters.getActiveSheet(),
376
+ columns: [1, 3],
377
+ });
378
+ expect(model.getters.getConditionalFormats()).toEqual([
379
+ { id: "1", ranges: ["A1:A1"], rule },
380
+ { id: "3", ranges: ["B3:B3"], rule },
381
+ { id: "4", ranges: ["A4:A4"], rule },
382
+ { id: "5", ranges: ["B5:B5"], rule },
383
+ { id: "6", ranges: ["A6:B6"], rule },
384
+ { id: "7", ranges: ["A7:B7"], rule },
385
+ ]);
386
+ expect(model.getters.getConditionalStyle("B2")).toBeUndefined();
387
+ expect(model.getters.getConditionalStyle("B3")!.fillColor).toBe("orange");
388
+ expect(model.getters.getConditionalStyle("C3")).toBeUndefined();
389
+ });
390
+ test("On column addition", () => {
391
+ model = new Model({
392
+ sheets: [
393
+ {
394
+ colNumber: 3,
395
+ rowNumber: 4,
396
+ cells: {
397
+ B4: { content: "42" },
398
+ },
399
+ conditionalFormats: [
400
+ { id: "1", ranges: ["A1:C1"], rule },
401
+ { id: "2", ranges: ["A2:B2"], rule },
402
+ { id: "3", ranges: ["B3:C3"], rule },
403
+ { id: "4", ranges: ["B4:B4"], rule },
404
+ ],
405
+ },
406
+ ],
407
+ });
408
+ model.dispatch("ADD_COLUMNS", {
409
+ sheet: model.getters.getActiveSheet(),
410
+ column: 1,
411
+ position: "before",
412
+ quantity: 1,
413
+ });
414
+ model.dispatch("ADD_COLUMNS", {
415
+ sheet: model.getters.getActiveSheet(),
416
+ column: 2,
417
+ position: "after",
418
+ quantity: 2,
419
+ });
420
+ expect(model.getters.getConditionalFormats()).toEqual([
421
+ { id: "1", ranges: ["A1:F1"], rule },
422
+ { id: "2", ranges: ["A2:C2"], rule },
423
+ { id: "3", ranges: ["C3:F3"], rule },
424
+ { id: "4", ranges: ["C4:C4"], rule },
425
+ ]);
426
+ expect(model.getters.getConditionalStyle("B4")).toBeUndefined();
427
+ expect(model.getters.getConditionalStyle("C4")!.fillColor).toBe("orange");
428
+ });
429
+ test("On row addition", () => {
430
+ model = new Model({
431
+ sheets: [
432
+ {
433
+ colNumber: 4,
434
+ rowNumber: 3,
435
+ cells: {
436
+ D2: { content: "42" },
437
+ },
438
+ conditionalFormats: [
439
+ { id: "1", ranges: ["A1:A3"], rule },
440
+ { id: "2", ranges: ["B1:B2"], rule },
441
+ { id: "3", ranges: ["C2:C3"], rule },
442
+ { id: "4", ranges: ["D2:D2"], rule },
443
+ ],
444
+ },
445
+ ],
446
+ });
447
+ model.dispatch("ADD_ROWS", {
448
+ sheet: model.getters.getActiveSheet(),
449
+ row: 1,
450
+ position: "before",
451
+ quantity: 1,
452
+ });
453
+ model.dispatch("ADD_ROWS", {
454
+ sheet: model.getters.getActiveSheet(),
455
+ row: 2,
456
+ position: "after",
457
+ quantity: 2,
458
+ });
459
+ expect(model.getters.getConditionalFormats()).toEqual([
460
+ { id: "1", ranges: ["A1:A6"], rule },
461
+ { id: "2", ranges: ["B1:B3"], rule },
462
+ { id: "3", ranges: ["C3:C6"], rule },
463
+ { id: "4", ranges: ["D3:D3"], rule },
464
+ ]);
465
+ expect(model.getters.getConditionalStyle("D2")).toBeUndefined();
466
+ expect(model.getters.getConditionalStyle("D3")!.fillColor).toBe("orange");
467
+ });
468
+ });
469
+ });
470
+
471
+ describe("conditional formats types", () => {
472
+ describe("CellIs condition", () => {
473
+ test("Operator BeginsWith", () => {
474
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
475
+ cf: {
476
+ rule: {
477
+ type: "CellIsRule",
478
+ operator: "BeginsWith",
479
+ values: ["qsdf"],
480
+ style: { fillColor: "#ff0f0f" },
481
+ },
482
+ ranges: ["A1"],
483
+ id: "11",
484
+ },
485
+ sheet: model.getters.getActiveSheet(),
486
+ });
487
+
488
+ model.dispatch("SET_VALUE", { xc: "A1", text: "aaa" });
489
+ expect(model.getters.getConditionalStyle("A1")).toBeUndefined();
490
+
491
+ model.dispatch("SET_VALUE", { xc: "A1", text: "qsdfmlkqjsdf" });
492
+ expect(model.getters.getConditionalStyle("A1")).toEqual({ fillColor: "#ff0f0f" });
493
+ });
494
+
495
+ test("Operator Between", () => {
496
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
497
+ cf: {
498
+ rule: {
499
+ type: "CellIsRule",
500
+ operator: "Between",
501
+ values: ["1", "3"],
502
+ style: { fillColor: "#ff0f0f" },
503
+ },
504
+ ranges: ["A1"],
505
+ id: "11",
506
+ },
507
+ sheet: model.getters.getActiveSheet(),
508
+ });
509
+
510
+ model.dispatch("SET_VALUE", { xc: "A1", text: "0" });
511
+ expect(model.getters.getConditionalStyle("A1")).toBeUndefined();
512
+
513
+ model.dispatch("SET_VALUE", { xc: "A1", text: "1" });
514
+ expect(model.getters.getConditionalStyle("A1")).toEqual({ fillColor: "#ff0f0f" });
515
+
516
+ model.dispatch("SET_VALUE", { xc: "A1", text: "1.5" });
517
+ expect(model.getters.getConditionalStyle("A1")).toEqual({ fillColor: "#ff0f0f" });
518
+
519
+ model.dispatch("SET_VALUE", { xc: "A1", text: "3" });
520
+ expect(model.getters.getConditionalStyle("A1")).toEqual({ fillColor: "#ff0f0f" });
521
+
522
+ model.dispatch("SET_VALUE", { xc: "A1", text: "3.5" });
523
+ expect(model.getters.getConditionalStyle("A1")).toBeUndefined();
524
+ });
525
+
526
+ test("Operator ContainsText", () => {
527
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
528
+ cf: {
529
+ rule: {
530
+ type: "CellIsRule",
531
+ operator: "ContainsText",
532
+ values: ["abc"],
533
+ style: { fillColor: "#ff0f0f" },
534
+ },
535
+ ranges: ["A1"],
536
+ id: "11",
537
+ },
538
+ sheet: model.getters.getActiveSheet(),
539
+ });
540
+ model.dispatch("SET_VALUE", { xc: "A1", text: "hello" });
541
+ expect(model.getters.getConditionalStyle("A1")).toBeUndefined();
542
+
543
+ model.dispatch("SET_VALUE", { xc: "A1", text: "helabclo" });
544
+ expect(model.getters.getConditionalStyle("A1")).toEqual({ fillColor: "#ff0f0f" });
545
+ });
546
+
547
+ test("Operator EndsWith", () => {
548
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
549
+ cf: {
550
+ rule: {
551
+ type: "CellIsRule",
552
+ operator: "EndsWith",
553
+ values: ["qsdf"],
554
+ style: { fillColor: "#ff0f0f" },
555
+ },
556
+ ranges: ["A1"],
557
+ id: "11",
558
+ },
559
+ sheet: model.getters.getActiveSheet(),
560
+ });
561
+
562
+ model.dispatch("SET_VALUE", { xc: "A1", text: "hello" });
563
+ expect(model.getters.getConditionalStyle("A1")).toBeUndefined();
564
+
565
+ model.dispatch("SET_VALUE", { xc: "A1", text: "helloqsdf" });
566
+ expect(model.getters.getConditionalStyle("A1")).toEqual({ fillColor: "#ff0f0f" });
567
+ });
568
+
569
+ test("Operator GreaterThan", () => {
570
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
571
+ cf: {
572
+ rule: {
573
+ type: "CellIsRule",
574
+ operator: "GreaterThan",
575
+ values: ["12"],
576
+ style: { fillColor: "#ff0f0f" },
577
+ },
578
+ ranges: ["A1"],
579
+ id: "11",
580
+ },
581
+ sheet: model.getters.getActiveSheet(),
582
+ });
583
+ model.dispatch("SET_VALUE", { xc: "A1", text: "5" });
584
+ expect(model.getters.getConditionalStyle("A1")).toBeUndefined();
585
+
586
+ model.dispatch("SET_VALUE", { xc: "A1", text: "12" });
587
+ expect(model.getters.getConditionalStyle("A1")).toBeUndefined();
588
+
589
+ model.dispatch("SET_VALUE", { xc: "A1", text: "13" });
590
+ expect(model.getters.getConditionalStyle("A1")).toEqual({ fillColor: "#ff0f0f" });
591
+ });
592
+
593
+ test("Operator GreaterThanOrEqual", () => {
594
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
595
+ cf: {
596
+ rule: {
597
+ type: "CellIsRule",
598
+ operator: "GreaterThanOrEqual",
599
+ values: ["12"],
600
+ style: { fillColor: "#ff0f0f" },
601
+ },
602
+ ranges: ["A1"],
603
+ id: "11",
604
+ },
605
+ sheet: model.getters.getActiveSheet(),
606
+ });
607
+
608
+ model.dispatch("SET_VALUE", { xc: "A1", text: "5" });
609
+ expect(model.getters.getConditionalStyle("A1")).toBeUndefined();
610
+
611
+ model.dispatch("SET_VALUE", { xc: "A1", text: "12" });
612
+ expect(model.getters.getConditionalStyle("A1")).toEqual({ fillColor: "#ff0f0f" });
613
+
614
+ model.dispatch("SET_VALUE", { xc: "A1", text: "13" });
615
+ expect(model.getters.getConditionalStyle("A1")).toEqual({ fillColor: "#ff0f0f" });
616
+ });
617
+
618
+ test("Operator LessThan", () => {
619
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
620
+ cf: {
621
+ rule: {
622
+ type: "CellIsRule",
623
+ operator: "LessThan",
624
+ values: ["10"],
625
+ style: { fillColor: "#ff0f0f" },
626
+ },
627
+ ranges: ["A1"],
628
+ id: "11",
629
+ },
630
+ sheet: model.getters.getActiveSheet(),
631
+ });
632
+
633
+ model.dispatch("SET_VALUE", { xc: "A1", text: "11" });
634
+ expect(model.getters.getConditionalStyle("A1")).toBeUndefined();
635
+
636
+ model.dispatch("SET_VALUE", { xc: "A1", text: "10" });
637
+ expect(model.getters.getConditionalStyle("A1")).toBeUndefined();
638
+
639
+ model.dispatch("SET_VALUE", { xc: "A1", text: "9" });
640
+ expect(model.getters.getConditionalStyle("A1")).toEqual({ fillColor: "#ff0f0f" });
641
+ });
642
+
643
+ test("Operator LessThanOrEqual", () => {
644
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
645
+ cf: {
646
+ rule: {
647
+ type: "CellIsRule",
648
+ operator: "LessThanOrEqual",
649
+ values: ["10"],
650
+ style: { fillColor: "#ff0f0f" },
651
+ },
652
+ ranges: ["A1"],
653
+ id: "11",
654
+ },
655
+ sheet: model.getters.getActiveSheet(),
656
+ });
657
+
658
+ model.dispatch("SET_VALUE", { xc: "A1", text: "11" });
659
+ expect(model.getters.getConditionalStyle("A1")).toBeUndefined();
660
+
661
+ model.dispatch("SET_VALUE", { xc: "A1", text: "10" });
662
+ expect(model.getters.getConditionalStyle("A1")).toEqual({ fillColor: "#ff0f0f" });
663
+
664
+ model.dispatch("SET_VALUE", { xc: "A1", text: "9" });
665
+ expect(model.getters.getConditionalStyle("A1")).toEqual({ fillColor: "#ff0f0f" });
666
+ });
667
+
668
+ test("Operator NotBetween", () => {
669
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
670
+ cf: {
671
+ rule: {
672
+ type: "CellIsRule",
673
+ operator: "NotBetween",
674
+ values: ["5", "10"],
675
+ style: { fillColor: "#ff0f0f" },
676
+ },
677
+ ranges: ["A1"],
678
+ id: "11",
679
+ },
680
+ sheet: model.getters.getActiveSheet(),
681
+ });
682
+
683
+ model.dispatch("SET_VALUE", { xc: "A1", text: "4" });
684
+ expect(model.getters.getConditionalStyle("A1")).toEqual({ fillColor: "#ff0f0f" });
685
+
686
+ model.dispatch("SET_VALUE", { xc: "A1", text: "0" });
687
+ expect(model.getters.getConditionalStyle("A1")).toEqual({ fillColor: "#ff0f0f" });
688
+
689
+ model.dispatch("SET_VALUE", { xc: "A1", text: "5" });
690
+ expect(model.getters.getConditionalStyle("A1")).toBeUndefined();
691
+
692
+ model.dispatch("SET_VALUE", { xc: "A1", text: "10" });
693
+ expect(model.getters.getConditionalStyle("A1")).toBeUndefined();
694
+
695
+ model.dispatch("SET_VALUE", { xc: "A1", text: "10.1" });
696
+ expect(model.getters.getConditionalStyle("A1")).toEqual({ fillColor: "#ff0f0f" });
697
+ });
698
+
699
+ test("Operator NotContains", () => {
700
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
701
+ cf: {
702
+ rule: {
703
+ type: "CellIsRule",
704
+ operator: "NotContains",
705
+ values: ["qsdf"],
706
+ style: { fillColor: "#ff0f0f" },
707
+ },
708
+ ranges: ["A1"],
709
+ id: "11",
710
+ },
711
+ sheet: model.getters.getActiveSheet(),
712
+ });
713
+
714
+ model.dispatch("SET_VALUE", { xc: "A1", text: "hellqsdfo" });
715
+ expect(model.getters.getConditionalStyle("A1")).toBeUndefined();
716
+
717
+ model.dispatch("SET_VALUE", { xc: "A1", text: "hello" });
718
+ expect(model.getters.getConditionalStyle("A1")).toEqual({ fillColor: "#ff0f0f" });
719
+ });
720
+
721
+ test("Operator NotEqual", () => {
722
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
723
+ cf: {
724
+ rule: {
725
+ type: "CellIsRule",
726
+ operator: "NotEqual",
727
+ values: ["qsdf"],
728
+ style: { fillColor: "#ff0f0f" },
729
+ },
730
+ ranges: ["A1"],
731
+ id: "11",
732
+ },
733
+ sheet: model.getters.getActiveSheet(),
734
+ });
735
+
736
+ model.dispatch("SET_VALUE", { xc: "A1", text: "hellqsdfo" });
737
+ expect(model.getters.getConditionalStyle("A1")).toEqual({ fillColor: "#ff0f0f" });
738
+
739
+ model.dispatch("SET_VALUE", { xc: "A1", text: "hello" });
740
+ expect(model.getters.getConditionalStyle("A1")).toEqual({ fillColor: "#ff0f0f" });
741
+
742
+ model.dispatch("SET_VALUE", { xc: "A1", text: "qsdf" });
743
+ expect(model.getters.getConditionalStyle("A1")).toBeUndefined();
744
+ });
745
+ });
746
+
747
+ describe("color scale", () => {
748
+ test("1 point, value scale", () => {
749
+ model.dispatch("SET_VALUE", { xc: "A1", text: "10" });
750
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
751
+ cf: createColorScale(
752
+ "1",
753
+ ["A1"],
754
+ { type: "value", color: 0xff00ff },
755
+ { type: "value", color: 0x123456 }
756
+ ),
757
+ sheet: model.getters.getActiveSheet(),
758
+ });
759
+ expect(model.getters.getConditionalStyle("A1")).toEqual(undefined);
760
+ });
761
+
762
+ test("2 points, value scale", () => {
763
+ model.dispatch("SET_VALUE", { xc: "A1", text: "10" });
764
+ model.dispatch("SET_VALUE", { xc: "A2", text: "11" });
765
+ model.dispatch("SET_VALUE", { xc: "A3", text: "17" });
766
+ model.dispatch("SET_VALUE", { xc: "A4", text: "19" });
767
+ model.dispatch("SET_VALUE", { xc: "A5", text: "20" });
768
+
769
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
770
+ cf: createColorScale(
771
+ "1",
772
+ ["A1:A5"],
773
+ { type: "value", color: 0xff00ff },
774
+ { type: "value", color: 0x123456 }
775
+ ),
776
+ sheet: model.getters.getActiveSheet(),
777
+ });
778
+
779
+ expect(model.getters.getConditionalStyle("A1")).toEqual({ fillColor: "#ff00ff" });
780
+ expect(model.getters.getConditionalStyle("A2")).toEqual({ fillColor: "#e705ee" });
781
+ expect(model.getters.getConditionalStyle("A3")).toEqual({ fillColor: "#592489" });
782
+ expect(model.getters.getConditionalStyle("A4")).toEqual({ fillColor: "#2a2f67" });
783
+ expect(model.getters.getConditionalStyle("A5")).toEqual({ fillColor: "#123456" });
784
+ });
785
+
786
+ test("2 points, value scale with other min and max", () => {
787
+ model.dispatch("SET_VALUE", { xc: "A1", text: "100" });
788
+ model.dispatch("SET_VALUE", { xc: "A2", text: "110" });
789
+ model.dispatch("SET_VALUE", { xc: "A3", text: "170" });
790
+ model.dispatch("SET_VALUE", { xc: "A4", text: "190" });
791
+ model.dispatch("SET_VALUE", { xc: "A5", text: "200" });
792
+
793
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
794
+ cf: createColorScale(
795
+ "1",
796
+ ["A1:A5"],
797
+ { type: "value", color: 0xff00ff },
798
+ { type: "value", color: 0x123456 }
799
+ ),
800
+ sheet: model.getters.getActiveSheet(),
801
+ });
802
+
803
+ expect(model.getters.getConditionalStyle("A1")).toEqual({ fillColor: "#ff00ff" });
804
+ expect(model.getters.getConditionalStyle("A2")).toEqual({ fillColor: "#e705ee" });
805
+ expect(model.getters.getConditionalStyle("A3")).toEqual({ fillColor: "#592489" });
806
+ expect(model.getters.getConditionalStyle("A4")).toEqual({ fillColor: "#2a2f67" });
807
+ expect(model.getters.getConditionalStyle("A5")).toEqual({ fillColor: "#123456" });
808
+ });
809
+
810
+ test("2 points, value scale with minimum = 0", () => {
811
+ model.dispatch("SET_VALUE", { xc: "A1", text: "0" });
812
+ model.dispatch("SET_VALUE", { xc: "A2", text: "1" });
813
+ model.dispatch("SET_VALUE", { xc: "A3", text: "7" });
814
+ model.dispatch("SET_VALUE", { xc: "A4", text: "9" });
815
+ model.dispatch("SET_VALUE", { xc: "A5", text: "10" });
816
+
817
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
818
+ cf: createColorScale(
819
+ "1",
820
+ ["A1:A5"],
821
+ { type: "value", color: 0xff00ff },
822
+ { type: "value", color: 0x123456 }
823
+ ),
824
+ sheet: model.getters.getActiveSheet(),
825
+ });
826
+
827
+ expect(model.getters.getConditionalStyle("A1")).toEqual({ fillColor: "#ff00ff" });
828
+ expect(model.getters.getConditionalStyle("A2")).toEqual({ fillColor: "#e705ee" });
829
+ expect(model.getters.getConditionalStyle("A3")).toEqual({ fillColor: "#592489" });
830
+ expect(model.getters.getConditionalStyle("A4")).toEqual({ fillColor: "#2a2f67" });
831
+ expect(model.getters.getConditionalStyle("A5")).toEqual({ fillColor: "#123456" });
832
+ });
833
+
834
+ test("2 points, value scale with any value 0", () => {
835
+ model.dispatch("SET_VALUE", { xc: "A1", text: "0" });
836
+ model.dispatch("SET_VALUE", { xc: "A2", text: "-5" });
837
+ model.dispatch("SET_VALUE", { xc: "A3", text: "5" });
838
+
839
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
840
+ cf: createColorScale(
841
+ "1",
842
+ ["A1:A5"],
843
+ { type: "value", color: 0xff0000 },
844
+ { type: "value", color: 0x00ff00 }
845
+ ),
846
+ sheet: model.getters.getActiveSheet(),
847
+ });
848
+
849
+ expect(model.getters.getConditionalStyle("A1")).toEqual({ fillColor: "#808000" });
850
+ expect(model.getters.getConditionalStyle("A2")).toEqual({ fillColor: "#ff0000" });
851
+ expect(model.getters.getConditionalStyle("A3")).toEqual({ fillColor: "#00ff00" });
852
+ });
853
+
854
+ test("2 points, value scale with same min/max", () => {
855
+ model.dispatch("SET_VALUE", { xc: "A1", text: "10" });
856
+ model.dispatch("SET_VALUE", { xc: "A2", text: "10" });
857
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
858
+ cf: createColorScale(
859
+ "1",
860
+ ["A1:A2"],
861
+ { type: "value", color: 0xff00ff },
862
+ { type: "value", color: 0x123456 }
863
+ ),
864
+ sheet: model.getters.getActiveSheet(),
865
+ });
866
+
867
+ expect(model.getters.getConditionalStyle("A1")).toEqual(undefined);
868
+ expect(model.getters.getConditionalStyle("A2")).toEqual(undefined);
869
+ });
870
+
871
+ test("async values do not crash the evaluation", () => {
872
+ model.dispatch("SET_VALUE", { xc: "A1", text: "=wait(100)" });
873
+ model.dispatch("SET_VALUE", { xc: "A2", text: "110" });
874
+
875
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
876
+ cf: createColorScale(
877
+ "1",
878
+ ["A1:A2"],
879
+ { type: "value", color: 0xff00ff },
880
+ { type: "value", color: 0x123456 }
881
+ ),
882
+ sheet: model.getters.getActiveSheet(),
883
+ });
884
+ expect(true).toBeTruthy(); // no error
885
+ });
886
+ });
887
+ describe("icon scale", () => {});
888
+ });
889
+
890
+ describe("UI of conditional formats", () => {
891
+ let fixture: HTMLElement;
892
+ let parent: GridParent;
893
+
894
+ beforeEach(async () => {
895
+ fixture = makeTestFixture();
896
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
897
+ cf: createEqualCF(["A1:A2"], "2", { fillColor: "#FF0000" }, "1"),
898
+ sheet: model.getters.getActiveSheet(),
899
+ });
900
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
901
+ cf: createColorScale(
902
+ "2",
903
+ ["B1:B5"],
904
+ { type: "value", color: 0xff00ff },
905
+ { type: "value", color: 0x123456 }
906
+ ),
907
+ sheet: model.getters.getActiveSheet(),
908
+ });
909
+ parent = new GridParent(model);
910
+ await parent.mount(fixture);
911
+
912
+ parent.env.openSidePanel("ConditionalFormatting");
913
+ await nextTick();
914
+ });
915
+
916
+ afterEach(() => {
917
+ fixture.remove();
918
+ parent.destroy();
919
+ });
920
+
921
+ const selectors = {
922
+ listPreview: ".o-cf .o-cf-preview",
923
+ ruleEditor: {
924
+ range: ".o-cf .o-cf-ruleEditor .o-cf-range input",
925
+ editor: {
926
+ operatorInput: ".o-cf .o-cf-ruleEditor .o-cf-editor .o-cell-is-operator",
927
+ valueInput: ".o-cf .o-cf-ruleEditor .o-cf-editor .o-cell-is-value",
928
+ bold: ".o-cf .o-cf-ruleEditor .o-cf-editor .o-tools div.o-tool[title='Bold']",
929
+ italic: ".o-cf .o-cf-ruleEditor .o-cf-editor .o-tools div.o-tool[title='Italic']",
930
+ strikethrough:
931
+ ".o-cf .o-cf-ruleEditor .o-cf-editor .o-tools div.o-tool[title='Strikethrough']",
932
+ colorDropdown:
933
+ ".o-cf .o-cf-ruleEditor .o-cf-editor .o-tools .o-tool.o-dropdown.o-with-color span",
934
+ },
935
+ },
936
+ previewImage: ".o-cf-preview-image",
937
+ description: {
938
+ ruletype: {
939
+ rule: ".o-cf-preview-description-rule",
940
+ values: ".o-cf-preview-description-values",
941
+ },
942
+ range: ".o-cf-preview-range",
943
+ },
944
+ colorScaleEditor: {
945
+ minColor: ".o-threshold-minimum .o-tool.o-dropdown.o-with-color span",
946
+ minType: ".o-threshold-minimum > select",
947
+ minValue: ".o-threshold-minimum .o-threshold-value",
948
+ maxColor: ".o-threshold-maximum .o-tool.o-dropdown.o-with-color span",
949
+ maxType: ".o-threshold-maximum > select",
950
+ maxValue: ".o-threshold-maximum .o-threshold-value",
951
+ colorPickerBlue: ".o-color-picker div[data-color='#0000ff']",
952
+ colorPickerYellow: ".o-color-picker div[data-color='#ffff00']",
953
+ },
954
+ cfTabSelector: ".o-cf-type-selector .o-cf-type-tab",
955
+ buttonSave: ".o-sidePanelButtons .o-cf-save",
956
+ buttonDelete: ".o-cf-delete-button",
957
+ buttonAdd: ".o-cf-add",
958
+ closePanel: ".o-sidePanelClose",
959
+ };
960
+
961
+ test("simple snapshot", () => {
962
+ expect(fixture.querySelector(".o-sidePanel")!).toMatchSnapshot();
963
+ });
964
+
965
+ test("the list of CF has a correct preview", () => {
966
+ // check the html of the list (especially the colors)
967
+ let previews = document.querySelectorAll(selectors.listPreview);
968
+ expect(previews).toHaveLength(2);
969
+
970
+ // --> should be the style for CellIsRule
971
+ expect(previews[0].querySelector(selectors.description.ruletype.rule)!.textContent).toBe(
972
+ "Is equal to"
973
+ );
974
+ expect(previews[0].querySelector(selectors.description.ruletype.values)!.textContent).toBe("2");
975
+ expect(previews[0].querySelector(selectors.description.range)!.textContent).toBe("A1:A2");
976
+ expect(
977
+ window.getComputedStyle(previews[0].querySelector(selectors.previewImage)!).backgroundColor
978
+ ).toBe("rgb(255, 0, 0)");
979
+
980
+ // --> should be a nothing of color gradient for ColorScaleRule
981
+ expect(previews[1].querySelector(selectors.description.range)!.textContent).toBe("B1:B5");
982
+ expect(
983
+ window.getComputedStyle(previews[1].querySelector(selectors.previewImage)!).backgroundColor
984
+ ).toBe("");
985
+ // TODO VSC: see how we can test the gradient background image
986
+ });
987
+
988
+ test("can edit an existing CellIsRule", async () => {
989
+ triggerMouseEvent(document.querySelectorAll(selectors.listPreview)[0], "click");
990
+ await nextTick();
991
+
992
+ // change every value
993
+ setInputValueAndTrigger(selectors.ruleEditor.range, "A1:A3", "change");
994
+ setInputValueAndTrigger(selectors.ruleEditor.range, "A1:A3", "input");
995
+ setInputValueAndTrigger(selectors.ruleEditor.editor.operatorInput, "BeginsWith", "change");
996
+ setInputValueAndTrigger(selectors.ruleEditor.editor.valueInput, "3", "input");
997
+
998
+ triggerMouseEvent(selectors.ruleEditor.editor.bold, "click");
999
+ triggerMouseEvent(selectors.ruleEditor.editor.italic, "click");
1000
+ triggerMouseEvent(selectors.ruleEditor.editor.strikethrough, "click");
1001
+
1002
+ parent.env.dispatch = jest.fn((command) => ({ status: "SUCCESS" } as CommandResult));
1003
+ // click save
1004
+ triggerMouseEvent(selectors.buttonSave, "click");
1005
+ await nextTick();
1006
+
1007
+ expect(parent.env.dispatch).toHaveBeenCalledWith("ADD_CONDITIONAL_FORMAT", {
1008
+ cf: {
1009
+ id: "1",
1010
+ ranges: ["A1:A3"],
1011
+ rule: {
1012
+ operator: "BeginsWith",
1013
+ stopIfTrue: false,
1014
+ style: { bold: true, fillColor: "#FF0000", italic: true, strikethrough: true },
1015
+ type: "CellIsRule",
1016
+ values: ["3", ""],
1017
+ },
1018
+ },
1019
+ sheet: model.getters.getActiveSheet(),
1020
+ });
1021
+ });
1022
+
1023
+ test("the preview should be bold when the rule is bold", async () => {
1024
+ parent.env.dispatch = jest.fn((command) => ({ status: "SUCCESS" } as CommandResult));
1025
+
1026
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
1027
+ cf: createEqualCF(["C1:C5"], "2", { bold: true, fillColor: "#ff0000" }, "99"),
1028
+ sheet: model.getters.getActiveSheet(),
1029
+ });
1030
+
1031
+ await nextTick();
1032
+
1033
+ let previews = document.querySelectorAll(selectors.listPreview);
1034
+ let line = previews[2].querySelector(selectors.previewImage);
1035
+ expect(line!.getAttribute("style")).toMatch("font-weight:bold;");
1036
+ });
1037
+
1038
+ test("can edit an existing ColorScaleRule", async () => {
1039
+ triggerMouseEvent(document.querySelectorAll(selectors.listPreview)[1], "click");
1040
+ await nextTick();
1041
+ // change every value
1042
+ setInputValueAndTrigger(selectors.ruleEditor.range, "B2:B5", "change");
1043
+
1044
+ triggerMouseEvent(selectors.colorScaleEditor.minColor, "click");
1045
+ await nextTick();
1046
+ triggerMouseEvent(selectors.colorScaleEditor.colorPickerBlue, "click");
1047
+ triggerMouseEvent(selectors.colorScaleEditor.maxColor, "click");
1048
+ await nextTick();
1049
+ triggerMouseEvent(selectors.colorScaleEditor.colorPickerYellow, "click");
1050
+
1051
+ setInputValueAndTrigger(selectors.colorScaleEditor.minValue, "33", "input");
1052
+
1053
+ parent.env.dispatch = jest.fn((command) => ({ status: "SUCCESS" } as CommandResult));
1054
+ // click save
1055
+ triggerMouseEvent(selectors.buttonSave, "click");
1056
+ await nextTick();
1057
+
1058
+ expect(parent.env.dispatch).toHaveBeenCalledWith("ADD_CONDITIONAL_FORMAT", {
1059
+ cf: {
1060
+ id: "2",
1061
+ ranges: ["B2:B5"],
1062
+ rule: {
1063
+ maximum: {
1064
+ color: 0xffff00,
1065
+ type: "value",
1066
+ },
1067
+ midpoint: undefined,
1068
+ minimum: {
1069
+ color: 0x0000ff,
1070
+ type: "value",
1071
+ value: "33",
1072
+ },
1073
+ type: "ColorScaleRule",
1074
+ },
1075
+ },
1076
+ sheet: model.getters.getActiveSheet(),
1077
+ });
1078
+ });
1079
+
1080
+ test("toggle color-picker", async () => {
1081
+ triggerMouseEvent(document.querySelectorAll(selectors.listPreview)[0], "click");
1082
+ await nextTick();
1083
+ triggerMouseEvent(selectors.ruleEditor.editor.colorDropdown, "click");
1084
+ await nextTick();
1085
+ expect(fixture.querySelector(".o-color-picker")).toBeTruthy();
1086
+ triggerMouseEvent(selectors.ruleEditor.editor.colorDropdown, "click");
1087
+ await nextTick();
1088
+ expect(fixture.querySelector(".o-color-picker")).toBeFalsy();
1089
+ });
1090
+
1091
+ test("color-picker closes when click elsewhere", async () => {
1092
+ triggerMouseEvent(document.querySelectorAll(selectors.listPreview)[0], "click");
1093
+ await nextTick();
1094
+ triggerMouseEvent(selectors.ruleEditor.editor.colorDropdown, "click");
1095
+ await nextTick();
1096
+ expect(fixture.querySelector(".o-color-picker")).toBeTruthy();
1097
+ triggerMouseEvent(".o-cf-preview-line", "click");
1098
+ await nextTick();
1099
+ expect(fixture.querySelector(".o-color-picker")).toBeFalsy();
1100
+ });
1101
+
1102
+ test("can create a new CellIsRule", async () => {
1103
+ mockUuidV4To("42");
1104
+
1105
+ triggerMouseEvent(selectors.buttonAdd, "click");
1106
+ await nextTick();
1107
+
1108
+ // change every value
1109
+ setInputValueAndTrigger(selectors.ruleEditor.range, "A1:A3", "change");
1110
+ setInputValueAndTrigger(selectors.ruleEditor.editor.operatorInput, "BeginsWith", "change");
1111
+ setInputValueAndTrigger(selectors.ruleEditor.editor.valueInput, "3", "input");
1112
+
1113
+ triggerMouseEvent(selectors.ruleEditor.editor.bold, "click");
1114
+ triggerMouseEvent(selectors.ruleEditor.editor.italic, "click");
1115
+ triggerMouseEvent(selectors.ruleEditor.editor.strikethrough, "click");
1116
+
1117
+ parent.env.dispatch = jest.fn((command) => ({ status: "SUCCESS" } as CommandResult));
1118
+ // click save
1119
+ triggerMouseEvent(selectors.buttonSave, "click");
1120
+ await nextTick();
1121
+ expect(parent.env.dispatch).toHaveBeenCalledWith("ADD_CONDITIONAL_FORMAT", {
1122
+ cf: {
1123
+ id: "47",
1124
+ ranges: ["A1:A3"],
1125
+ rule: {
1126
+ operator: "BeginsWith",
1127
+ stopIfTrue: false,
1128
+ style: { bold: true, fillColor: "#FF0000", italic: true, strikethrough: true },
1129
+ type: "CellIsRule",
1130
+ values: ["3", ""],
1131
+ },
1132
+ },
1133
+ sheet: model.getters.getActiveSheet(),
1134
+ });
1135
+ });
1136
+
1137
+ test("displayed range is updated if range changes", async () => {
1138
+ const previews = document.querySelectorAll(selectors.listPreview);
1139
+ expect(previews[0].querySelector(selectors.description.range)!.textContent).toBe("A1:A2");
1140
+ model.dispatch("COPY", { target: target("A1:A2") });
1141
+ model.dispatch("PASTE", { target: target("C1") });
1142
+ await nextTick();
1143
+ expect(previews[0].querySelector(selectors.description.range)!.textContent).toBe("A1:A2,C1:C2");
1144
+ });
1145
+
1146
+ test("can delete Rule", async () => {
1147
+ parent.env.dispatch = jest.fn((command) => ({ status: "SUCCESS" } as CommandResult));
1148
+ const previews = document.querySelectorAll(selectors.listPreview);
1149
+ triggerMouseEvent(previews[0].querySelector(selectors.buttonDelete), "click");
1150
+ await nextTick();
1151
+ expect(parent.env.dispatch).toHaveBeenCalledWith("REMOVE_CONDITIONAL_FORMAT", {
1152
+ id: "1",
1153
+ sheet: model.getters.getActiveSheet(),
1154
+ });
1155
+ });
1156
+
1157
+ test("can create a new ColorScaleRule", async () => {
1158
+ mockUuidV4To("43");
1159
+
1160
+ triggerMouseEvent(selectors.buttonAdd, "click");
1161
+ await nextTick();
1162
+
1163
+ triggerMouseEvent(document.querySelectorAll(selectors.cfTabSelector)[1], "click");
1164
+ await nextTick();
1165
+
1166
+ // change every value
1167
+ setInputValueAndTrigger(selectors.ruleEditor.range, "B2:B5", "change");
1168
+
1169
+ triggerMouseEvent(selectors.colorScaleEditor.minColor, "click");
1170
+ await nextTick();
1171
+ triggerMouseEvent(selectors.colorScaleEditor.colorPickerBlue, "click");
1172
+ triggerMouseEvent(selectors.colorScaleEditor.maxColor, "click");
1173
+ await nextTick();
1174
+ triggerMouseEvent(selectors.colorScaleEditor.colorPickerYellow, "click");
1175
+
1176
+ setInputValueAndTrigger(selectors.colorScaleEditor.minValue, "33", "input");
1177
+
1178
+ parent.env.dispatch = jest.fn((command) => ({ status: "SUCCESS" } as CommandResult));
1179
+ // click save
1180
+ triggerMouseEvent(selectors.buttonSave, "click");
1181
+ await nextTick();
1182
+
1183
+ expect(parent.env.dispatch).toHaveBeenCalledWith("ADD_CONDITIONAL_FORMAT", {
1184
+ cf: {
1185
+ id: "51",
1186
+ ranges: ["B2:B5"],
1187
+ rule: {
1188
+ maximum: {
1189
+ color: 0xffff00,
1190
+ type: "value",
1191
+ },
1192
+ midpoint: undefined,
1193
+ minimum: {
1194
+ color: 0x0000ff,
1195
+ type: "value",
1196
+ value: "33",
1197
+ },
1198
+ type: "ColorScaleRule",
1199
+ },
1200
+ },
1201
+ sheet: model.getters.getActiveSheet(),
1202
+ });
1203
+ });
1204
+
1205
+ test("Make a multiple selection, open CF panel, create a rule => Should create one line per selection", async () => {
1206
+ triggerMouseEvent(selectors.closePanel, "click");
1207
+ await nextTick();
1208
+ const zone1 = { bottom: 1, left: 1, right: 1, top: 1 };
1209
+ const zone2 = { bottom: 2, left: 2, right: 2, top: 2 };
1210
+ model.dispatch("SET_SELECTION", {
1211
+ anchor: [1, 1],
1212
+ zones: [zone1, zone2],
1213
+ });
1214
+ parent.env.openSidePanel("ConditionalFormatting");
1215
+ await nextTick();
1216
+ triggerMouseEvent(selectors.buttonAdd, "click");
1217
+ await nextTick();
1218
+ const ranges = document.querySelectorAll(selectors.ruleEditor.range);
1219
+ expect(ranges).toHaveLength(2);
1220
+ expect(ranges[0]["value"]).toBe("B2");
1221
+ expect(ranges[1]["value"]).toBe("C3");
1222
+ });
1223
+
1224
+ test("Open CF panel, make a multiple selection, open CF panel, create a rule => Should create one line per selection", async () => {
1225
+ const zone1 = { bottom: 1, left: 1, right: 1, top: 1 };
1226
+ const zone2 = { bottom: 2, left: 2, right: 2, top: 2 };
1227
+ model.dispatch("SET_SELECTION", {
1228
+ anchor: [1, 1],
1229
+ zones: [zone1, zone2],
1230
+ });
1231
+ triggerMouseEvent(selectors.buttonAdd, "click");
1232
+ await nextTick();
1233
+ const ranges = document.querySelectorAll(selectors.ruleEditor.range);
1234
+ expect(ranges).toHaveLength(2);
1235
+ expect(ranges[0]["value"]).toBe("B2");
1236
+ expect(ranges[1]["value"]).toBe("C3");
1237
+ });
1238
+
1239
+ test("switching sheet resets CF Editor to list", async () => {
1240
+ triggerMouseEvent(selectors.closePanel, "click");
1241
+ model.dispatch("CREATE_SHEET", { id: "42" });
1242
+ await nextTick();
1243
+ const zone = toZone("A1:A2");
1244
+ parent.env.openSidePanel("ConditionalFormatting", { selection: [zone] });
1245
+ await nextTick();
1246
+ expect(fixture.querySelector(selectors.listPreview)).toBeNull();
1247
+ expect(fixture.querySelector(selectors.ruleEditor.range! as "input")!.value).toBe("A1:A2");
1248
+ model.dispatch("ACTIVATE_SHEET", { from: model.getters.getActiveSheet(), to: "42" });
1249
+ await nextTick();
1250
+ expect(fixture.querySelector(selectors.ruleEditor.range)).toBeNull();
1251
+ expect(fixture.querySelector(selectors.listPreview)).toBeDefined();
1252
+ });
1253
+ });