name:
src/test/js/testutils/SpreadsheetTestRunner.ts
-rw-r--r--
4876
1import { assert } from "chai";
2import { it, describe } from "../testutils/TestUtils";
3import { F7Exception } from "../../../main/js/errors/F7Exception";
4import { Executor } from "../../../main/js/execution/Executor";
5import { Complex } from "../../../main/js/models/common/Types";
6import { CellObject } from "../../../main/js/spreadsheet/CellObject";
7import { excelDataTypeFromActualType } from "../../../main/js/spreadsheet/ExcelDataType";
8import { NamedRange } from "../../../main/js/spreadsheet/NamedRange";
9import { Sheet } from "../../../main/js/spreadsheet/Sheet";
10import { Spreadsheet } from "../../../main/js/spreadsheet/Spreadsheet";
11import { Converters } from "../../../main/js/utils/Converters";
12import { isNull } from "../../../main/js/common/utils/Types";
13
14export class MoreMath {
15 static maxUInt32 = 4294967296;
16
17 // Generate a random UInt32 between 0 and `uint32::max`.
18 static randomUInt32(): number {
19 return Math.floor(Math.random() * this.maxUInt32);
20 }
21}
22
23/**
24 * Runner class lets us build tests dynamically by adding individual cells,
25 * without a ton of boiler-plating.
26 */
27export class SpreadsheetTestRunner {
28 private sheets: { [key: string]: Sheet } = {};
29 private expectedGridValues: { [index: string]: { [index: string]: Complex } } = {};
30 private expectedEmptyKeys: { [index: string]: Array<string> } = {};
31 private expectedEmptyKeysButComputed: { [index: string]: Array<string> } = {};
32 private namedRanges: { [index: string]: NamedRange } = {};
33
34 addCell(sheet: string, a1Key: string, value: string): SpreadsheetTestRunner {
35 if (!(sheet in this.sheets)) {
36 this.sheets[sheet] = new Sheet({ id: `s${MoreMath.randomUInt32()}`, name: sheet });
37 }
38 const parsedType = value.startsWith("=") ? "f" : null;
39 const parsedValue = value.startsWith("=") ? value.replace("=", "") : value;
40 const cell: CellObject = {
41 t: excelDataTypeFromActualType(value),
42 };
43 if (parsedType) {
44 cell.f = parsedValue;
45 } else {
46 cell.v = parsedValue;
47 }
48 this.sheets[sheet].setCell(a1Key, cell);
49 return this;
50 }
51
52 addNamedRange(name: string, ref: string): SpreadsheetTestRunner {
53 this.namedRanges[name] = {
54 name,
55 ref,
56 };
57 return this;
58 }
59
60 addExpectedValue(gridName: string, a1Key: string, value: Complex): SpreadsheetTestRunner {
61 if (!(gridName in this.expectedGridValues)) {
62 this.expectedGridValues[gridName] = {};
63 }
64 this.expectedGridValues[gridName][a1Key] = value;
65 return this;
66 }
67
68 addExpectedEmptyValue(gridName: string, a1Key: string): SpreadsheetTestRunner {
69 if (!(gridName in this.expectedEmptyKeys)) {
70 this.expectedEmptyKeys[gridName] = [];
71 }
72 this.expectedEmptyKeys[gridName].push(a1Key);
73 return this;
74 }
75
76 addExpectedEmptyComputedValue(gridName: string, a1Key: string): SpreadsheetTestRunner {
77 if (!(gridName in this.expectedEmptyKeysButComputed)) {
78 this.expectedEmptyKeysButComputed[gridName] = [];
79 }
80 this.expectedEmptyKeysButComputed[gridName].push(a1Key);
81 return this;
82 }
83
84 /**
85 * Run the tests in several steps:
86 * 1) Build sheet executor and run it.
87 * 2) For each expected entry, expect result.
88 * 3) For each empty value, ensure it's empty.
89 * 4) For each computed empty value, ensure it's empty.
90 */
91 public run() {
92 // 1) Build sheet executor and run it.
93 const executor = new Executor(new Spreadsheet(this.namedRanges, this.sheets));
94 executor.execute();
95
96 // 2) For each expected entry, expect result.
97 for (const gridName in this.expectedGridValues) {
98 // Doing "OR-empty-logic" to get the IDE to stop complaining about it.
99 for (const a1key in this.expectedGridValues[gridName] || {}) {
100 const cell = executor.getCell(gridName, a1key);
101 const expectedValue = this.expectedGridValues[gridName][a1key];
102 if (expectedValue instanceof F7Exception) {
103 const eName = Converters.castAsF7Exception(cell.v).name;
104 assert.deepEqual(
105 eName,
106 Converters.castAsF7Exception(expectedValue).name,
107 JSON.stringify(eName)
108 );
109 } else {
110 assert.deepEqual(cell.v, expectedValue as Complex);
111 }
112 }
113 }
114
115 // 3) For each empty value, ensure it's empty.
116 for (const sheet in this.expectedEmptyKeys) {
117 for (const a1key of this.expectedEmptyKeys[sheet]) {
118 const foundCell = executor.getCell(sheet, a1key);
119 if (isNull(foundCell)) {
120 assert.isNull(foundCell);
121 } else {
122 assert.fail(`${JSON.stringify(foundCell)} is not empty.`);
123 }
124 }
125 }
126 // 4) For each expected empty computed value, ensure it's empty.
127 for (const sheet in this.expectedEmptyKeys) {
128 for (const a1key of this.expectedEmptyKeys[sheet]) {
129 assert.isNull(executor.getCell(sheet, a1key));
130 }
131 }
132 }
133}