@odoo/o-spreadsheet 1.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 (199) 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 +78 -0
  12. package/readme.md +39 -0
  13. package/rollup.config.js +23 -0
  14. package/src/base_plugin.ts +102 -0
  15. package/src/components/autofill.ts +146 -0
  16. package/src/components/bottom_bar.ts +199 -0
  17. package/src/components/color_picker.ts +150 -0
  18. package/src/components/composer/autocomplete_dropdown.ts +135 -0
  19. package/src/components/composer/composer.ts +487 -0
  20. package/src/components/composer/content_editable_helper.ts +146 -0
  21. package/src/components/figures/chart.ts +70 -0
  22. package/src/components/figures/container.ts +268 -0
  23. package/src/components/figures/text_figure.ts +36 -0
  24. package/src/components/grid.ts +635 -0
  25. package/src/components/helpers/dom_helpers.ts +7 -0
  26. package/src/components/helpers/drag_and_drop.ts +17 -0
  27. package/src/components/icons.ts +36 -0
  28. package/src/components/index.ts +1 -0
  29. package/src/components/menu.ts +288 -0
  30. package/src/components/overlay.ts +457 -0
  31. package/src/components/scrollbar.ts +22 -0
  32. package/src/components/selection_input.ts +168 -0
  33. package/src/components/side_panel/cell_is_rule_editor.ts +169 -0
  34. package/src/components/side_panel/chart_panel.ts +104 -0
  35. package/src/components/side_panel/color_scale_rule_editor.ts +145 -0
  36. package/src/components/side_panel/conditional_formatting.ts +396 -0
  37. package/src/components/side_panel/side_panel.ts +127 -0
  38. package/src/components/side_panel/translations_terms.ts +45 -0
  39. package/src/components/spreadsheet.ts +198 -0
  40. package/src/components/top_bar.ts +535 -0
  41. package/src/constants.ts +26 -0
  42. package/src/data.ts +149 -0
  43. package/src/fonts.ts +41 -0
  44. package/src/formulas/compiler.ts +258 -0
  45. package/src/formulas/composer_tokenizer.ts +70 -0
  46. package/src/formulas/index.ts +15 -0
  47. package/src/formulas/normalize.ts +52 -0
  48. package/src/formulas/parser.ts +280 -0
  49. package/src/formulas/range_tokenizer.ts +97 -0
  50. package/src/formulas/tokenizer.ts +242 -0
  51. package/src/functions/arguments.ts +114 -0
  52. package/src/functions/dates.ts +302 -0
  53. package/src/functions/helpers.ts +592 -0
  54. package/src/functions/index.ts +52 -0
  55. package/src/functions/module_database.ts +282 -0
  56. package/src/functions/module_date.ts +743 -0
  57. package/src/functions/module_info.ts +69 -0
  58. package/src/functions/module_logical.ts +208 -0
  59. package/src/functions/module_lookup.ts +185 -0
  60. package/src/functions/module_math.ts +1252 -0
  61. package/src/functions/module_operators.ts +291 -0
  62. package/src/functions/module_statistical.ts +882 -0
  63. package/src/functions/module_text.ts +370 -0
  64. package/src/helpers/color.ts +30 -0
  65. package/src/helpers/coordinates.ts +71 -0
  66. package/src/helpers/grid_manipulation.ts +68 -0
  67. package/src/helpers/index.ts +7 -0
  68. package/src/helpers/misc.ts +76 -0
  69. package/src/helpers/numbers.ts +117 -0
  70. package/src/helpers/uuid.ts +19 -0
  71. package/src/helpers/zones.ts +263 -0
  72. package/src/history.ts +186 -0
  73. package/src/index.ts +84 -0
  74. package/src/model.ts +261 -0
  75. package/src/plugins/autofill.ts +412 -0
  76. package/src/plugins/chart.ts +404 -0
  77. package/src/plugins/clipboard.ts +461 -0
  78. package/src/plugins/conditional_format.ts +445 -0
  79. package/src/plugins/core.ts +1502 -0
  80. package/src/plugins/edition.ts +157 -0
  81. package/src/plugins/evaluation.ts +428 -0
  82. package/src/plugins/figures.ts +123 -0
  83. package/src/plugins/formatting.ts +630 -0
  84. package/src/plugins/highlight.ts +187 -0
  85. package/src/plugins/index.ts +32 -0
  86. package/src/plugins/merge.ts +524 -0
  87. package/src/plugins/renderer.ts +571 -0
  88. package/src/plugins/selection.ts +562 -0
  89. package/src/plugins/selection_inputs.ts +326 -0
  90. package/src/registries/autofill_modifiers.ts +76 -0
  91. package/src/registries/autofill_rules.ts +98 -0
  92. package/src/registries/figure_registry.ts +30 -0
  93. package/src/registries/index.ts +7 -0
  94. package/src/registries/menu_items_registry.ts +110 -0
  95. package/src/registries/menus/cell_menu_registry.ts +79 -0
  96. package/src/registries/menus/col_menu_registry.ts +63 -0
  97. package/src/registries/menus/index.ts +6 -0
  98. package/src/registries/menus/menu_items_actions.ts +425 -0
  99. package/src/registries/menus/row_menu_registry.ts +63 -0
  100. package/src/registries/menus/sheet_menu_registry.ts +75 -0
  101. package/src/registries/menus/topbar_menu_registry.ts +204 -0
  102. package/src/registries/side_panel_registry.ts +26 -0
  103. package/src/registries/topbar_component_registry.ts +12 -0
  104. package/src/registry.ts +51 -0
  105. package/src/translation.ts +44 -0
  106. package/src/types/autofill.ts +48 -0
  107. package/src/types/chart.ts +26 -0
  108. package/src/types/commands.ts +716 -0
  109. package/src/types/conditional_formatting.ts +124 -0
  110. package/src/types/env.ts +13 -0
  111. package/src/types/functions.ts +35 -0
  112. package/src/types/getters.ts +103 -0
  113. package/src/types/index.ts +20 -0
  114. package/src/types/misc.ts +143 -0
  115. package/src/types/rendering.ts +41 -0
  116. package/src/types/workbook_data.ts +62 -0
  117. package/tests/__mocks__/uuid.ts +7 -0
  118. package/tests/canvas.mock.ts +34 -0
  119. package/tests/components/__mocks__/content_editable_helper.ts +79 -0
  120. package/tests/components/__mocks__/scrollbar.ts +18 -0
  121. package/tests/components/__snapshots__/autocomplete_dropdown_test.ts.snap +29 -0
  122. package/tests/components/__snapshots__/bottom_bar_test.ts.snap +75 -0
  123. package/tests/components/__snapshots__/context_menu_test.ts.snap +88 -0
  124. package/tests/components/__snapshots__/grid_test.ts.snap +60 -0
  125. package/tests/components/__snapshots__/spreadsheet_test.ts.snap +455 -0
  126. package/tests/components/__snapshots__/top_bar_test.ts.snap +705 -0
  127. package/tests/components/autocomplete_dropdown_test.ts +315 -0
  128. package/tests/components/autofill_test.ts +167 -0
  129. package/tests/components/bottom_bar_test.ts +262 -0
  130. package/tests/components/chart_side_panel_test.ts +123 -0
  131. package/tests/components/composer_test.ts +400 -0
  132. package/tests/components/context_menu_test.ts +825 -0
  133. package/tests/components/grid_manipulation_test.ts +163 -0
  134. package/tests/components/grid_test.ts +581 -0
  135. package/tests/components/overlay_test.ts +541 -0
  136. package/tests/components/selection_input_test.ts +259 -0
  137. package/tests/components/side_panel_test.ts +148 -0
  138. package/tests/components/spreadsheet_test.ts +194 -0
  139. package/tests/components/top_bar_test.ts +427 -0
  140. package/tests/data_test.ts +175 -0
  141. package/tests/dom_helper.ts +42 -0
  142. package/tests/formulas/__snapshots__/compiler_test.ts.snap +302 -0
  143. package/tests/formulas/compiler_test.ts +266 -0
  144. package/tests/formulas/composer_tokenizer_test.ts +137 -0
  145. package/tests/formulas/expressions_test.ts +57 -0
  146. package/tests/formulas/formulas_test.ts +80 -0
  147. package/tests/formulas/normalize_test.ts +82 -0
  148. package/tests/formulas/parser_test.ts +224 -0
  149. package/tests/formulas/range_tokenizer_test.ts +187 -0
  150. package/tests/formulas/tokenizer_test.ts +164 -0
  151. package/tests/functions/arguments_test.ts +141 -0
  152. package/tests/functions/dates_test.ts +1081 -0
  153. package/tests/functions/functions_test.ts +53 -0
  154. package/tests/functions/helper_test.ts +98 -0
  155. package/tests/functions/module_database_test.ts +311 -0
  156. package/tests/functions/module_date_test.ts +941 -0
  157. package/tests/functions/module_info_test.ts +139 -0
  158. package/tests/functions/module_logical_test.ts +369 -0
  159. package/tests/functions/module_lookup_test.ts +541 -0
  160. package/tests/functions/module_math_test.ts +2482 -0
  161. package/tests/functions/module_operators_test.ts +955 -0
  162. package/tests/functions/module_statistical_test.ts +2014 -0
  163. package/tests/functions/module_text_test.ts +667 -0
  164. package/tests/helpers/coordinates_test.ts +25 -0
  165. package/tests/helpers/misc_test.ts +95 -0
  166. package/tests/helpers/numbers_test.ts +196 -0
  167. package/tests/helpers/zones_test.ts +97 -0
  168. package/tests/helpers.ts +384 -0
  169. package/tests/menu_items_registry_test.ts +540 -0
  170. package/tests/model/plugins/__snapshots__/chart_test.ts.snap +90 -0
  171. package/tests/model/plugins/chart_test.ts +562 -0
  172. package/tests/model_test.ts +82 -0
  173. package/tests/plugins/__snapshots__/conditional_formatting_test.ts.snap +131 -0
  174. package/tests/plugins/__snapshots__/renderer_test.ts.snap +371 -0
  175. package/tests/plugins/autofill_test.ts +457 -0
  176. package/tests/plugins/borders_test.ts +295 -0
  177. package/tests/plugins/clipboard_test.ts +1180 -0
  178. package/tests/plugins/conditional_formatting_test.ts +1253 -0
  179. package/tests/plugins/core_test.ts +614 -0
  180. package/tests/plugins/edition_test.ts +100 -0
  181. package/tests/plugins/evaluation_async_test.ts +332 -0
  182. package/tests/plugins/evaluation_dates_test.ts +79 -0
  183. package/tests/plugins/evaluation_test.ts +901 -0
  184. package/tests/plugins/figures_test.ts +327 -0
  185. package/tests/plugins/formatting_test.ts +391 -0
  186. package/tests/plugins/grid_manipulation_test.ts +1366 -0
  187. package/tests/plugins/highlight_test.ts +413 -0
  188. package/tests/plugins/history_test.ts +142 -0
  189. package/tests/plugins/import_export_test.ts +276 -0
  190. package/tests/plugins/merges_test.ts +490 -0
  191. package/tests/plugins/navigation_test.ts +181 -0
  192. package/tests/plugins/plugins/evaluation_test.ts +111 -0
  193. package/tests/plugins/renderer_test.ts +598 -0
  194. package/tests/plugins/resizing_test.ts +152 -0
  195. package/tests/plugins/selection_input_test.ts +409 -0
  196. package/tests/plugins/selection_test.ts +442 -0
  197. package/tests/plugins/sheets_test.ts +643 -0
  198. package/tests/plugins/style_test.ts +90 -0
  199. package/tsconfig.json +22 -0
@@ -0,0 +1,111 @@
1
+ import { args, functionRegistry } from "../../../src/functions/index";
2
+ import { Model } from "../../../src/model";
3
+ import "../../canvas.mock";
4
+ import { getCell } from "../../helpers";
5
+
6
+ let model: Model = new Model();
7
+ beforeEach(() => {
8
+ model = new Model();
9
+ });
10
+
11
+ describe("evaluate formula getter", () => {
12
+ test("a ref in the current sheet", () => {
13
+ model.dispatch("SET_VALUE", { xc: "A1", text: "12" });
14
+ expect(model.getters.evaluateFormula("=A1")).toBe(12);
15
+ });
16
+
17
+ test("in another sheet", () => {
18
+ model.dispatch("CREATE_SHEET", { id: "42" });
19
+ const sheet2 = model["workbook"].visibleSheets[1];
20
+ model.dispatch("SET_VALUE", { xc: "A1", text: "11", sheetId: sheet2 });
21
+ expect(model.getters.evaluateFormula("=Sheet2!A1")).toBe(11);
22
+ });
23
+
24
+ // i think these formulas should throw
25
+ test("in a not existing sheet", () => {
26
+ expect(() => model.getters.evaluateFormula("=Sheet99!A1")).toThrow();
27
+ });
28
+
29
+ test("evaluate a cell in error", () => {
30
+ model.dispatch("SET_VALUE", { xc: "A1", text: "=mqsdlkjfqsdf(((--" });
31
+ expect(() => model.getters.evaluateFormula("=A1")).toThrow();
32
+ });
33
+
34
+ test("evaluate a pending cell (async)", () => {
35
+ model.dispatch("SET_VALUE", { xc: "A1", text: "=wait(99999)" });
36
+ expect(() => model.getters.evaluateFormula("=A1")).toThrow();
37
+ });
38
+
39
+ test("EVALUATE_CELLS with no argument re-evaluate all the cells", () => {
40
+ let value = 1;
41
+ functionRegistry.add("GETVALUE", {
42
+ description: "Get value",
43
+ compute: () => value,
44
+ args: args(``),
45
+ returns: ["NUMBER"],
46
+ });
47
+ model.dispatch("SET_VALUE", { xc: "A1", text: "=GETVALUE()" });
48
+ expect(getCell(model, "A1")!.value).toBe(1);
49
+ value = 2;
50
+ model.dispatch("EVALUATE_CELLS");
51
+ expect(getCell(model, "A1")!.value).toBe(2);
52
+ });
53
+
54
+ test("using cells in other sheets", () => {
55
+ model.dispatch("CREATE_SHEET", { id: "42" });
56
+ const s = model.getters.getSheets();
57
+ model.dispatch("ACTIVATE_SHEET", { from: s[1].id, to: s[0].id });
58
+ model.dispatch("SET_VALUE", { sheetId: s[1].id, xc: "A1", text: "12" });
59
+ model.dispatch("SET_VALUE", { sheetId: s[1].id, xc: "A2", text: "=A1" });
60
+ model.dispatch("SET_VALUE", { sheetId: s[0].id, xc: "A2", text: "=Sheet2!A1" });
61
+ expect(model.getters.getCell(0, 1, "Sheet1")!.value).toBe(12);
62
+ });
63
+
64
+ test("evaluate references with fixed indexes", () => {
65
+ model.dispatch("SET_VALUE", { xc: "A1", text: "1" });
66
+ model.dispatch("SET_VALUE", { xc: "A2", text: "2" });
67
+ model.dispatch("SET_VALUE", { xc: "A3", text: "=A1" });
68
+ model.dispatch("SET_VALUE", { xc: "A4", text: "=$A1" });
69
+ model.dispatch("SET_VALUE", { xc: "A5", text: "=A$1" });
70
+ model.dispatch("SET_VALUE", { xc: "A6", text: "=$A$1" });
71
+ model.dispatch("SET_VALUE", { xc: "B3", text: "=sum(A$1:A2)" });
72
+ model.dispatch("SET_VALUE", { xc: "B4", text: "=sum(A$1:A$2)" });
73
+ model.dispatch("SET_VALUE", { xc: "B5", text: "=sum($A$1:A2)" });
74
+ model.dispatch("SET_VALUE", { xc: "B6", text: "=sum($A$1:$A$2)" });
75
+
76
+ expect(model.getters.getCell(0, 2, "Sheet1")!.value).toBe(1);
77
+ expect(model.getters.getCell(0, 3, "Sheet1")!.value).toBe(1);
78
+ expect(model.getters.getCell(0, 4, "Sheet1")!.value).toBe(1);
79
+ expect(model.getters.getCell(0, 5, "Sheet1")!.value).toBe(1);
80
+
81
+ expect(model.getters.getCell(1, 2, "Sheet1")!.value).toBe(3);
82
+ expect(model.getters.getCell(1, 3, "Sheet1")!.value).toBe(3);
83
+ expect(model.getters.getCell(1, 4, "Sheet1")!.value).toBe(3);
84
+ expect(model.getters.getCell(1, 5, "Sheet1")!.value).toBe(3);
85
+ });
86
+
87
+ test("evaluate function with fixed reference to another sheet", () => {
88
+ model.dispatch("CREATE_SHEET", { id: "42", name: "second sheet" });
89
+
90
+ model.dispatch("SET_VALUE", { xc: "A1", text: "1", sheetId: "42" });
91
+ model.dispatch("SET_VALUE", { xc: "A2", text: "2", sheetId: "42" });
92
+ model.dispatch("SET_VALUE", { xc: "A3", text: "='second sheet'!A1" });
93
+ model.dispatch("SET_VALUE", { xc: "A4", text: "='second sheet'!$A1" });
94
+ model.dispatch("SET_VALUE", { xc: "A5", text: "='second sheet'!A$1" });
95
+ model.dispatch("SET_VALUE", { xc: "A6", text: "='second sheet'!$A$1" });
96
+ model.dispatch("SET_VALUE", { xc: "B3", text: "=sum('second sheet'!A$1:A2)" });
97
+ model.dispatch("SET_VALUE", { xc: "B4", text: "=sum('second sheet'!A$1:A$2)" });
98
+ model.dispatch("SET_VALUE", { xc: "B5", text: "=sum('second sheet'!$A$1:A2)" });
99
+ model.dispatch("SET_VALUE", { xc: "B6", text: "=sum('second sheet'!$A$1:$A$2)" });
100
+
101
+ expect(model.getters.getCell(0, 2, "Sheet1")!.value).toBe(1);
102
+ expect(model.getters.getCell(0, 3, "Sheet1")!.value).toBe(1);
103
+ expect(model.getters.getCell(0, 4, "Sheet1")!.value).toBe(1);
104
+ expect(model.getters.getCell(0, 5, "Sheet1")!.value).toBe(1);
105
+
106
+ expect(model.getters.getCell(1, 2, "Sheet1")!.value).toBe(3);
107
+ expect(model.getters.getCell(1, 3, "Sheet1")!.value).toBe(3);
108
+ expect(model.getters.getCell(1, 4, "Sheet1")!.value).toBe(3);
109
+ expect(model.getters.getCell(1, 5, "Sheet1")!.value).toBe(3);
110
+ });
111
+ });
@@ -0,0 +1,598 @@
1
+ import {
2
+ DEFAULT_CELL_HEIGHT,
3
+ DEFAULT_CELL_WIDTH,
4
+ HEADER_HEIGHT,
5
+ HEADER_WIDTH,
6
+ } from "../../src/constants";
7
+ import { toCartesian, toZone } from "../../src/helpers";
8
+ import { Model } from "../../src/model";
9
+ import { RendererPlugin } from "../../src/plugins/renderer";
10
+ import { Box, GridRenderingContext, Viewport } from "../../src/types";
11
+ import { MockCanvasRenderingContext2D } from "../canvas.mock";
12
+ import { createEqualCF } from "../helpers";
13
+
14
+ MockCanvasRenderingContext2D.prototype.measureText = function (text: string) {
15
+ return { width: text.length };
16
+ };
17
+
18
+ function setCellContent(
19
+ model: Model,
20
+ xc: string,
21
+ content: string,
22
+ sheet: string = model["workbook"].visibleSheets[0]
23
+ ) {
24
+ const [col, row] = toCartesian(xc);
25
+ model.dispatch("UPDATE_CELL", { col, row, sheet, content });
26
+ }
27
+
28
+ function getBoxFromText(model: Model, text: string): Box {
29
+ const rendererPlugin = (model["handlers"].find(
30
+ (h) => h instanceof RendererPlugin
31
+ ) as RendererPlugin)!;
32
+ // @ts-ignore
33
+ return (rendererPlugin.boxes as Box[]).find((b) => b.text === text);
34
+ }
35
+
36
+ interface ContextObserver {
37
+ onSet?(key, val): void;
38
+ onGet?(key): void;
39
+ onFunctionCall?(fn: string, args: any[]): void;
40
+ }
41
+
42
+ class MockGridRenderingContext implements GridRenderingContext {
43
+ _context = document.createElement("canvas").getContext("2d");
44
+ ctx: CanvasRenderingContext2D;
45
+ viewport: Viewport;
46
+ dpr = 1;
47
+ thinLineWidth = 0.4;
48
+
49
+ constructor(model: Model, width: number, height: number, observer: ContextObserver) {
50
+ this.viewport = {
51
+ width,
52
+ height,
53
+ offsetX: 0,
54
+ offsetY: 0,
55
+ left: 0,
56
+ right: 0,
57
+ top: 0,
58
+ bottom: 0,
59
+ };
60
+ this.viewport = model.getters.adjustViewportZone(this.viewport);
61
+
62
+ const handler = {
63
+ get: (target, val) => {
64
+ if (val in (this._context as any).__proto__) {
65
+ return (...args) => {
66
+ if (observer.onFunctionCall) {
67
+ observer.onFunctionCall(val, args);
68
+ }
69
+ };
70
+ } else {
71
+ if (observer.onGet) {
72
+ observer.onGet(val);
73
+ }
74
+ }
75
+ return target[val];
76
+ },
77
+ set: (target, key, val) => {
78
+ if (observer.onSet) {
79
+ observer.onSet(key, val);
80
+ }
81
+ target[key] = val;
82
+ return true;
83
+ },
84
+ };
85
+ this.ctx = new Proxy({}, handler);
86
+ }
87
+ }
88
+
89
+ describe("renderer", () => {
90
+ test("snapshot for a simple grid rendering", () => {
91
+ const model = new Model();
92
+
93
+ model.dispatch("SET_VALUE", { xc: "A1", text: "1" });
94
+ const instructions: string[] = [];
95
+ let ctx = new MockGridRenderingContext(model, 1000, 1000, {
96
+ onSet: (key, value) => {
97
+ instructions.push(`context.${key}=${JSON.stringify(value)};`);
98
+ },
99
+ onGet: (key) => {
100
+ instructions.push(`GET:${key}`);
101
+ },
102
+ onFunctionCall: (key, args) => {
103
+ instructions.push(`context.${key}(${args.map((a) => JSON.stringify(a)).join(", ")})`);
104
+ },
105
+ });
106
+
107
+ model.drawGrid(ctx);
108
+ expect(instructions).toMatchSnapshot();
109
+ });
110
+
111
+ test("formulas evaluating to a string are properly aligned", () => {
112
+ const model = new Model();
113
+
114
+ model.dispatch("SET_VALUE", { xc: "A1", text: "1" });
115
+ model.dispatch("SET_VALUE", { xc: "A2", text: "=A1" });
116
+
117
+ let textAligns: string[] = [];
118
+ let ctx = new MockGridRenderingContext(model, 1000, 1000, {
119
+ onSet: (key, value) => {
120
+ if (key === "textAlign") {
121
+ textAligns.push(value);
122
+ }
123
+ },
124
+ });
125
+
126
+ model.drawGrid(ctx);
127
+ expect(textAligns).toEqual(["right", "right", "center"]); // center for headers
128
+
129
+ textAligns = [];
130
+ model.dispatch("SET_VALUE", { xc: "A1", text: "asdf" });
131
+ model.drawGrid(ctx);
132
+ expect(textAligns).toEqual(["left", "left", "center"]); // center for headers
133
+ });
134
+
135
+ test("fillstyle of cell will be rendered", () => {
136
+ const model = new Model({
137
+ sheets: [
138
+ {
139
+ colNumber: 1,
140
+ rowNumber: 3,
141
+ },
142
+ ],
143
+ });
144
+ model.dispatch("SET_FORMATTING", {
145
+ sheet: model["workbook"].visibleSheets[0],
146
+ target: [toZone("A1")],
147
+ style: { fillColor: "#DC6CDF" },
148
+ });
149
+
150
+ let fillStyle: any[] = [];
151
+ let fillStyleColor1Called = false;
152
+ let fillStyleColor2Called = false;
153
+ let ctx = new MockGridRenderingContext(model, 1000, 1000, {
154
+ onSet: (key, value) => {
155
+ if (key === "fillStyle" && value === "#DC6CDF") {
156
+ fillStyleColor1Called = true;
157
+ fillStyleColor2Called = false;
158
+ }
159
+ if (key === "fillStyle" && value === "#DC6CDE") {
160
+ fillStyleColor2Called = true;
161
+ fillStyleColor1Called = false;
162
+ }
163
+ },
164
+ onFunctionCall: (val, args) => {
165
+ if (val === "fillRect" && fillStyleColor1Called) {
166
+ fillStyle.push({ color: "#DC6CDF", x: args[0], y: args[1], w: args[2], h: args[3] });
167
+ fillStyleColor1Called = false;
168
+ fillStyleColor2Called = false;
169
+ }
170
+ if (val === "fillRect" && fillStyleColor2Called) {
171
+ fillStyle.push({ color: "#DC6CDE", x: args[0], y: args[1], w: args[2], h: args[3] });
172
+ fillStyleColor1Called = false;
173
+ fillStyleColor2Called = false;
174
+ }
175
+ },
176
+ });
177
+
178
+ model.drawGrid(ctx);
179
+ expect(fillStyle).toEqual([{ color: "#DC6CDF", h: 23, w: 96, x: 48, y: 26 }]);
180
+
181
+ fillStyle = [];
182
+ model.dispatch("SET_FORMATTING", {
183
+ sheet: model["workbook"].visibleSheets[0],
184
+ target: [toZone("A1")],
185
+ style: { fillColor: "#DC6CDE" },
186
+ });
187
+ model.drawGrid(ctx);
188
+ expect(fillStyle).toEqual([{ color: "#DC6CDE", h: 23, w: 96, x: 48, y: 26 }]);
189
+ });
190
+
191
+ test("fillstyle of merge will be rendered for all cells in merge", () => {
192
+ const model = new Model({
193
+ sheets: [
194
+ {
195
+ colNumber: 1,
196
+ rowNumber: 3,
197
+ },
198
+ ],
199
+ });
200
+ model.dispatch("SET_FORMATTING", {
201
+ sheet: model["workbook"].visibleSheets[0],
202
+ target: [toZone("A1")],
203
+ style: { fillColor: "#DC6CDF" },
204
+ });
205
+ model.dispatch("ADD_MERGE", {
206
+ sheet: model["workbook"].visibleSheets[0],
207
+ zone: toZone("A1:A3"),
208
+ });
209
+
210
+ let fillStyle: any[] = [];
211
+ let fillStyleColor1Called = false;
212
+ let fillStyleColor2Called = false;
213
+ let ctx = new MockGridRenderingContext(model, 1000, 1000, {
214
+ onSet: (key, value) => {
215
+ if (key === "fillStyle" && value === "#DC6CDF") {
216
+ fillStyleColor1Called = true;
217
+ fillStyleColor2Called = false;
218
+ }
219
+ if (key === "fillStyle" && value === "#DC6CDE") {
220
+ fillStyleColor2Called = true;
221
+ fillStyleColor1Called = false;
222
+ }
223
+ },
224
+ onFunctionCall: (val, args) => {
225
+ if (val === "fillRect" && fillStyleColor1Called) {
226
+ fillStyle.push({ color: "#DC6CDF", x: args[0], y: args[1], w: args[2], h: args[3] });
227
+ fillStyleColor1Called = false;
228
+ fillStyleColor2Called = false;
229
+ }
230
+ if (val === "fillRect" && fillStyleColor2Called) {
231
+ fillStyle.push({ color: "#DC6CDE", x: args[0], y: args[1], w: args[2], h: args[3] });
232
+ fillStyleColor1Called = false;
233
+ fillStyleColor2Called = false;
234
+ }
235
+ },
236
+ });
237
+
238
+ model.drawGrid(ctx);
239
+ expect(fillStyle).toEqual([{ color: "#DC6CDF", h: 3 * 23, w: 96, x: 48, y: 26 }]);
240
+
241
+ fillStyle = [];
242
+ model.dispatch("SET_FORMATTING", {
243
+ sheet: model["workbook"].visibleSheets[0],
244
+ target: [toZone("A1")],
245
+ style: { fillColor: "#DC6CDE" },
246
+ });
247
+ model.drawGrid(ctx);
248
+ expect(fillStyle).toEqual([{ color: "#DC6CDE", h: 3 * 23, w: 96, x: 48, y: 26 }]);
249
+ });
250
+
251
+ test("fillstyle of cell works with CF", () => {
252
+ const model = new Model({
253
+ sheets: [
254
+ {
255
+ colNumber: 1,
256
+ rowNumber: 3,
257
+ },
258
+ ],
259
+ });
260
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
261
+ cf: createEqualCF(["A1"], "1", { fillColor: "#DC6CDF" }, "1"),
262
+ sheet: model["workbook"].visibleSheets[0],
263
+ });
264
+
265
+ let fillStyle: any[] = [];
266
+ let fillStyleColor1Called = false;
267
+ let ctx = new MockGridRenderingContext(model, 1000, 1000, {
268
+ onSet: (key, value) => {
269
+ if (key === "fillStyle" && value === "#DC6CDF") {
270
+ fillStyleColor1Called = true;
271
+ }
272
+ },
273
+ onFunctionCall: (val, args) => {
274
+ if (val === "fillRect" && fillStyleColor1Called) {
275
+ fillStyle.push({ color: "#DC6CDF", x: args[0], y: args[1], w: args[2], h: args[3] });
276
+ fillStyleColor1Called = false;
277
+ }
278
+ },
279
+ });
280
+
281
+ model.drawGrid(ctx);
282
+ expect(fillStyle).toEqual([]);
283
+
284
+ fillStyle = [];
285
+ setCellContent(model, "A1", "1");
286
+ model.drawGrid(ctx);
287
+ expect(fillStyle).toEqual([{ color: "#DC6CDF", h: 23, w: 96, x: 48, y: 26 }]);
288
+ });
289
+
290
+ test("fillstyle of merge works with CF", () => {
291
+ const model = new Model({
292
+ sheets: [
293
+ {
294
+ colNumber: 1,
295
+ rowNumber: 3,
296
+ },
297
+ ],
298
+ });
299
+ model.dispatch("ADD_CONDITIONAL_FORMAT", {
300
+ cf: createEqualCF(["A1"], "1", { fillColor: "#DC6CDF" }, "1"),
301
+ sheet: model["workbook"].visibleSheets[0],
302
+ });
303
+ model.dispatch("ADD_MERGE", {
304
+ sheet: model["workbook"].visibleSheets[0],
305
+ zone: toZone("A1:A3"),
306
+ });
307
+ let fillStyle: any[] = [];
308
+ let fillStyleColor1Called = false;
309
+ let ctx = new MockGridRenderingContext(model, 1000, 1000, {
310
+ onSet: (key, value) => {
311
+ if (key === "fillStyle" && value === "#DC6CDF") {
312
+ fillStyleColor1Called = true;
313
+ }
314
+ },
315
+ onFunctionCall: (val, args) => {
316
+ if (val === "fillRect" && fillStyleColor1Called) {
317
+ fillStyle.push({ color: "#DC6CDF", x: args[0], y: args[1], w: args[2], h: args[3] });
318
+ fillStyleColor1Called = false;
319
+ }
320
+ },
321
+ });
322
+
323
+ model.drawGrid(ctx);
324
+ expect(fillStyle).toEqual([]);
325
+
326
+ fillStyle = [];
327
+ setCellContent(model, "A1", "1");
328
+ model.drawGrid(ctx);
329
+ expect(fillStyle).toEqual([{ color: "#DC6CDF", h: 23 * 3, w: 96, x: 48, y: 26 }]);
330
+ });
331
+
332
+ test("formulas in a merge, evaluating to a string are properly aligned", () => {
333
+ const model = new Model();
334
+
335
+ const sheet1 = model["workbook"].visibleSheets[0];
336
+ model.dispatch("ADD_MERGE", { sheet: sheet1, zone: toZone("A2:B2") });
337
+ model.dispatch("SET_VALUE", { xc: "A1", text: "1" });
338
+ model.dispatch("SET_VALUE", { xc: "A2", text: "=A1" });
339
+
340
+ let textAligns: string[] = [];
341
+
342
+ let ctx = new MockGridRenderingContext(model, 1000, 1000, {
343
+ onSet: (key, value) => {
344
+ if (key === "textAlign") {
345
+ textAligns.push(value);
346
+ }
347
+ },
348
+ });
349
+ model.drawGrid(ctx);
350
+
351
+ expect(textAligns).toEqual(["right", "right", "center"]); // center for headers
352
+
353
+ model.dispatch("SET_VALUE", { xc: "A1", text: "asdf" });
354
+
355
+ textAligns = [];
356
+ model.drawGrid(ctx);
357
+ expect(textAligns).toEqual(["left", "left", "center"]); // center for headers
358
+ });
359
+
360
+ test("formulas evaluating to a boolean are properly aligned", () => {
361
+ const model = new Model();
362
+
363
+ model.dispatch("SET_VALUE", { xc: "A1", text: "1" });
364
+ model.dispatch("SET_VALUE", { xc: "A2", text: "=A1" });
365
+
366
+ let textAligns: string[] = [];
367
+ let ctx = new MockGridRenderingContext(model, 1000, 1000, {
368
+ onSet: (key, value) => {
369
+ if (key === "textAlign") {
370
+ textAligns.push(value);
371
+ }
372
+ },
373
+ });
374
+ model.drawGrid(ctx);
375
+ expect(textAligns).toEqual(["right", "right", "center"]); // center for headers
376
+
377
+ textAligns = [];
378
+ model.dispatch("SET_VALUE", { xc: "A1", text: "true" });
379
+ model.drawGrid(ctx);
380
+ expect(textAligns).toEqual(["center", "center", "center"]); // center for headers
381
+ });
382
+
383
+ test("formulas in a merge, evaluating to a boolean are properly aligned", () => {
384
+ const model = new Model();
385
+ const sheet1 = model["workbook"].visibleSheets[0];
386
+
387
+ model.dispatch("ADD_MERGE", { sheet: sheet1, zone: toZone("A2:B2") });
388
+ model.dispatch("SET_VALUE", { xc: "A1", text: "1" });
389
+ model.dispatch("SET_VALUE", { xc: "A2", text: "=A1" });
390
+
391
+ let textAligns: string[] = [];
392
+
393
+ let ctx = new MockGridRenderingContext(model, 1000, 1000, {
394
+ onSet: (key, value) => {
395
+ if (key === "textAlign") {
396
+ textAligns.push(value);
397
+ }
398
+ },
399
+ });
400
+ model.drawGrid(ctx);
401
+
402
+ expect(textAligns).toEqual(["right", "right", "center"]); // center for headers
403
+
404
+ model.dispatch("SET_VALUE", { xc: "A1", text: "false" });
405
+
406
+ textAligns = [];
407
+ model.drawGrid(ctx);
408
+ expect(textAligns).toEqual(["center", "center", "center"]); // center for headers
409
+ });
410
+
411
+ test("errors are aligned to the center", () => {
412
+ const model = new Model();
413
+
414
+ model.dispatch("SET_VALUE", { xc: "A1", text: "=A1" });
415
+
416
+ let textAligns: string[] = [];
417
+
418
+ let ctx = new MockGridRenderingContext(model, 1000, 1000, {
419
+ onSet: (key, value) => {
420
+ if (key === "textAlign") {
421
+ textAligns.push(value);
422
+ }
423
+ },
424
+ });
425
+ model.drawGrid(ctx);
426
+
427
+ // 1 center for headers, 1 for cell content
428
+ expect(textAligns).toEqual(["center", "center"]);
429
+ });
430
+
431
+ test("dates are aligned to the right", () => {
432
+ const model = new Model();
433
+
434
+ model.dispatch("SET_VALUE", { xc: "A1", text: "03/23/2010" });
435
+
436
+ let textAligns: string[] = [];
437
+
438
+ let ctx = new MockGridRenderingContext(model, 1000, 1000, {
439
+ onSet: (key, value) => {
440
+ if (key === "textAlign") {
441
+ textAligns.push(value);
442
+ }
443
+ },
444
+ });
445
+ model.drawGrid(ctx);
446
+
447
+ // 1 center for headers, 1 for cell content
448
+ expect(textAligns).toEqual(["right", "center"]);
449
+ });
450
+
451
+ test("functions are aligned to the left", () => {
452
+ const model = new Model();
453
+
454
+ model.dispatch("SET_VALUE", { xc: "A1", text: "=SUM(1,2)" });
455
+ model.dispatch("SET_FORMULA_VISIBILITY", { show: true });
456
+ let textAligns: string[] = [];
457
+
458
+ let ctx = new MockGridRenderingContext(model, 1000, 1000, {
459
+ onSet: (key, value) => {
460
+ if (key === "textAlign") {
461
+ textAligns.push(value);
462
+ }
463
+ },
464
+ });
465
+ model.drawGrid(ctx);
466
+
467
+ // 1 center for headers, 1 for cell content
468
+ expect(textAligns).toEqual(["left", "center"]);
469
+ });
470
+
471
+ test("Overflowing left-aligned text is correctly clipped", () => {
472
+ const overflowingText = "I am a very long text";
473
+ let box: Box;
474
+ const model = new Model({
475
+ sheets: [
476
+ {
477
+ id: "sheet1",
478
+ colNumber: 3,
479
+ rowNumber: 1,
480
+ cols: { 1: { size: 5 } },
481
+ cells: { B1: { content: overflowingText, style: 1 } },
482
+ },
483
+ ],
484
+ styles: { 1: { align: "left" } },
485
+ });
486
+
487
+ let ctx = new MockGridRenderingContext(model, 1000, 1000, {});
488
+ model.drawGrid(ctx);
489
+
490
+ box = getBoxFromText(model, overflowingText);
491
+ // no clip
492
+ expect(box.clipRect).toBeNull();
493
+
494
+ // no clipping at the left
495
+ setCellContent(model, "A1", "Content at the left");
496
+ model.drawGrid(ctx);
497
+ box = getBoxFromText(model, overflowingText);
498
+ expect(box.clipRect).toBeNull();
499
+
500
+ // clipping at the right
501
+ setCellContent(model, "C1", "Content at the right");
502
+ model.drawGrid(ctx);
503
+ box = getBoxFromText(model, overflowingText);
504
+ expect(box.clipRect).toEqual([
505
+ HEADER_WIDTH + DEFAULT_CELL_WIDTH,
506
+ HEADER_HEIGHT,
507
+ 5,
508
+ DEFAULT_CELL_HEIGHT,
509
+ ]);
510
+ });
511
+
512
+ test("Overflowing left-aligned text is correctly clipped", () => {
513
+ const overflowingText = "I am a very long text";
514
+ let box: Box;
515
+ const model = new Model({
516
+ sheets: [
517
+ {
518
+ id: "sheet1",
519
+ colNumber: 3,
520
+ rowNumber: 1,
521
+ cols: { 1: { size: 5 } },
522
+ cells: { B1: { content: overflowingText, style: 1 } },
523
+ },
524
+ ],
525
+ styles: { 1: { align: "right" } },
526
+ });
527
+
528
+ let ctx = new MockGridRenderingContext(model, 1000, 1000, {});
529
+ model.drawGrid(ctx);
530
+
531
+ box = getBoxFromText(model, overflowingText);
532
+ // no clip
533
+ expect(box.clipRect).toBeNull();
534
+
535
+ // no clipping at the right
536
+ setCellContent(model, "C1", "Content at the left");
537
+ model.drawGrid(ctx);
538
+ box = getBoxFromText(model, overflowingText);
539
+ expect(box.clipRect).toBeNull();
540
+
541
+ // clipping at the left
542
+ setCellContent(model, "A1", "Content at the right");
543
+ model.drawGrid(ctx);
544
+ box = getBoxFromText(model, overflowingText);
545
+ expect(box.clipRect).toEqual([
546
+ HEADER_WIDTH + DEFAULT_CELL_WIDTH,
547
+ HEADER_HEIGHT,
548
+ 5,
549
+ DEFAULT_CELL_HEIGHT,
550
+ ]);
551
+ });
552
+
553
+ test("Overflowing centered text is clipped on both sides", () => {
554
+ const overflowingText = "I am a very long text";
555
+ let centeredBox: Box;
556
+ const model = new Model({
557
+ sheets: [
558
+ {
559
+ id: "sheet1",
560
+ colNumber: 3,
561
+ rowNumber: 1,
562
+ cols: { 1: { size: 5 } },
563
+ cells: { B1: { content: overflowingText, style: 1 } },
564
+ },
565
+ ],
566
+ styles: { 1: { align: "center" } },
567
+ });
568
+
569
+ let ctx = new MockGridRenderingContext(model, 1000, 1000, {});
570
+ model.drawGrid(ctx);
571
+
572
+ centeredBox = getBoxFromText(model, overflowingText);
573
+ // // spans from A1 to C1 <-> no clip
574
+ expect(centeredBox.clipRect).toBeNull();
575
+
576
+ setCellContent(model, "A1", "left");
577
+ model.drawGrid(ctx);
578
+
579
+ centeredBox = getBoxFromText(model, overflowingText);
580
+ expect(centeredBox.clipRect).toEqual([
581
+ HEADER_WIDTH + DEFAULT_CELL_WIDTH, // clipped to the left
582
+ HEADER_HEIGHT,
583
+ 5 + DEFAULT_CELL_WIDTH,
584
+ DEFAULT_CELL_HEIGHT,
585
+ ]);
586
+
587
+ setCellContent(model, "C1", "right");
588
+ model.drawGrid(ctx);
589
+
590
+ centeredBox = getBoxFromText(model, overflowingText);
591
+ expect(centeredBox.clipRect).toEqual([
592
+ HEADER_WIDTH + DEFAULT_CELL_WIDTH, //clipped to the left
593
+ HEADER_HEIGHT,
594
+ 5, // clipped to the right
595
+ DEFAULT_CELL_HEIGHT,
596
+ ]);
597
+ });
598
+ });