• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

iddan / react-spreadsheet / 6066679176

03 Sep 2023 08:50PM UTC coverage: 80.439% (+0.2%) from 80.232%
6066679176

push

github

iddan
v0.8.4

414 of 563 branches covered (0.0%)

Branch coverage included in aggregate %.

980 of 1170 relevant lines covered (83.76%)

36.44 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

68.24
/src/engine.ts
1
import FormulaParser, { FormulaError, Value } from "fast-formula-parser";
11✔
2
import { getReferences } from "./formula";
11✔
3
import * as Matrix from "./matrix";
11✔
4
import * as Formula from "./formula";
11✔
5
import { Point } from "./point";
6
import { PointGraph } from "./point-graph";
11✔
7
import { PointSet } from "./point-set";
11✔
8
import { CellBase, CreateFormulaParser } from "./types";
9

10
export class Model<Cell extends CellBase> {
11✔
11
  readonly data!: Matrix.Matrix<Cell>;
12
  readonly evaluatedData!: Matrix.Matrix<Cell>;
13
  readonly referenceGraph!: PointGraph;
14
  readonly createFormulaParser!: CreateFormulaParser;
15

16
  constructor(
17
    createFormulaParser: CreateFormulaParser,
18
    data: Matrix.Matrix<Cell>,
19
    referenceGraph?: PointGraph,
20
    evaluatedData?: Matrix.Matrix<Cell>
21
  ) {
22
    this.createFormulaParser = createFormulaParser;
53✔
23
    this.data = data;
53✔
24
    this.referenceGraph = referenceGraph || createReferenceGraph(data);
53✔
25
    this.evaluatedData =
53✔
26
      evaluatedData ||
100✔
27
      createEvaluatedData(
28
        data,
29
        this.referenceGraph,
30
        this.createFormulaParser(data)
31
      );
32
  }
33
}
34

35
export function updateCellValue<Cell extends CellBase>(
11✔
36
  model: Model<Cell>,
37
  point: Point,
38
  cell: Cell
39
): Model<Cell> {
40
  const nextData = Matrix.set(point, cell, model.data);
6✔
41
  const nextReferenceGraph = Formula.isFormulaValue(cell.value)
6✔
42
    ? updateReferenceGraph(model.referenceGraph, point, cell, nextData)
6✔
43
    : model.referenceGraph;
44

45
  const formulaParser = model.createFormulaParser(nextData);
6✔
46
  const nextEvaluatedData = evaluateCell(
6✔
47
    model.evaluatedData,
48
    nextData,
49
    nextReferenceGraph,
50
    point,
51
    cell,
52
    formulaParser
53
  );
54
  return new Model(
6✔
55
    model.createFormulaParser,
56
    nextData,
57
    nextReferenceGraph,
58
    nextEvaluatedData
59
  );
60
}
61

62
function updateReferenceGraph(
63
  referenceGraph: PointGraph,
64
  point: Point,
65
  cell: CellBase<string>,
66
  data: Matrix.Matrix<CellBase>
67
): PointGraph {
68
  const references = getReferences(
3✔
69
    Formula.extractFormula(cell.value),
70
    point,
71
    data
72
  );
73
  const nextReferenceGraph = referenceGraph.set(point, references);
3✔
74
  return nextReferenceGraph;
3✔
75
}
76

77
function evaluateCell<Cell extends CellBase>(
78
  prevEvaluatedData: Matrix.Matrix<Cell>,
79
  data: Matrix.Matrix<Cell>,
80
  referenceGraph: PointGraph,
81
  point: Point,
82
  cell: Cell,
83
  formulaParser: FormulaParser
84
): Matrix.Matrix<Cell> {
85
  if (referenceGraph.hasCircularDependency(point)) {
6✔
86
    let visited = PointSet.from([point]);
1✔
87
    let nextEvaluatedData = Matrix.set(
1✔
88
      point,
89
      { ...cell, value: FormulaError.REF },
90
      prevEvaluatedData
91
    );
92
    for (const referrer of referenceGraph.getBackwardsRecursive(point)) {
1✔
93
      if (visited.has(referrer)) {
1✔
94
        break;
1✔
95
      }
96
      visited = visited.add(referrer);
×
97
      const referrerCell = Matrix.get(referrer, data);
×
98
      if (!referrerCell) {
×
99
        continue;
×
100
      }
101
      nextEvaluatedData = Matrix.set(
×
102
        referrer,
103
        { ...referrerCell, value: FormulaError.REF },
104
        nextEvaluatedData
105
      );
106
    }
107
    return nextEvaluatedData;
1✔
108
  }
109

110
  let nextEvaluatedData = prevEvaluatedData;
5✔
111

112
  const evaluatedValue = Formula.isFormulaValue(cell.value)
5✔
113
    ? getFormulaComputedValue(cell.value, point, formulaParser)
5✔
114
    : cell.value;
115

116
  const evaluatedCell = { ...cell, value: evaluatedValue };
5✔
117

118
  nextEvaluatedData = Matrix.set(point, evaluatedCell, nextEvaluatedData);
5✔
119

120
  // for every formula cell that references the cell re-evaluate (recursive)
121
  for (const referrer of referenceGraph.getBackwardsRecursive(point)) {
5✔
122
    const referrerCell = Matrix.get(referrer, data);
×
123
    if (!referrerCell) {
×
124
      continue;
×
125
    }
126
    const evaluatedValue = Formula.isFormulaValue(referrerCell.value)
×
127
      ? getFormulaComputedValue(referrerCell.value, point, formulaParser)
×
128
      : referrerCell.value;
129
    const evaluatedCell = { ...referrerCell, value: evaluatedValue };
×
130
    nextEvaluatedData = Matrix.set(referrer, evaluatedCell, nextEvaluatedData);
×
131
  }
132

133
  return nextEvaluatedData;
5✔
134
}
135

136
/**
137
 *
138
 * @param data - the spreadsheet data
139
 * @returns the spreadsheet reference graph
140
 */
141
export function createReferenceGraph(
11✔
142
  data: Matrix.Matrix<CellBase>
143
): PointGraph {
144
  const entries: Array<[Point, PointSet]> = [];
47✔
145
  for (const [point, cell] of Matrix.entries(data)) {
47✔
146
    if (cell && Formula.isFormulaValue(cell.value)) {
508!
147
      const references = getReferences(
×
148
        Formula.extractFormula(cell.value),
149
        point,
150
        data
151
      );
152
      entries.push([point, references]);
×
153
    }
154
  }
155
  return PointGraph.from(entries);
47✔
156
}
157

158
export function createEvaluatedData<Cell extends CellBase>(
11✔
159
  data: Matrix.Matrix<Cell>,
160
  referenceGraph: PointGraph,
161
  formulaParser: FormulaParser
162
): Matrix.Matrix<Cell> {
163
  let evaluatedData = data;
47✔
164

165
  // Iterate over the points in the reference graph, starting from the leaves
166
  for (const point of referenceGraph.traverseBFS()) {
47✔
167
    // Get the cell at the current point in the data Matrix
168
    const cell = Matrix.get(point, data);
×
169
    if (!cell) {
×
170
      continue;
×
171
    }
172

173
    // If the cell is a formula cell, evaluate it
174
    if (Formula.isFormulaValue(cell.value)) {
×
175
      const evaluatedValue = getFormulaComputedValue(
×
176
        cell.value,
177
        point,
178
        formulaParser
179
      );
180
      evaluatedData = Matrix.set(
×
181
        point,
182
        { ...cell, value: evaluatedValue },
183
        evaluatedData
184
      );
185
    }
186
  }
187

188
  return evaluatedData;
47✔
189
}
190

191
/** Get the computed value of a formula cell */
192
export function getFormulaComputedValue(
11✔
193
  value: string,
194
  point: Point,
195
  formulaParser: FormulaParser
196
): Value {
197
  const formula = Formula.extractFormula(value);
4✔
198
  try {
4✔
199
    return Formula.evaluate(formula, point, formulaParser);
4✔
200
  } catch (e) {
201
    return FormulaError.REF;
×
202
  }
203
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc