59 lines
2.8 KiB
TypeScript
59 lines
2.8 KiB
TypeScript
import assert from 'node:assert/strict'
|
|
import test from 'node:test'
|
|
import { createSpreadsheet } from '../src/facade/spreadsheet'
|
|
|
|
test('Spreadsheet evaluates unit formulas and aliases deterministically', () => {
|
|
const sheet = createSpreadsheet('sheet', 'Parameters')
|
|
sheet.setCell('A1', '6 mm', { alias: 'Width' })
|
|
sheet.setCell('A2', '=Width * 2', { alias: 'Length' })
|
|
sheet.setCell('B1', '=max(A1, 4 mm)')
|
|
const evaluation = sheet.evaluate()
|
|
assert.equal(evaluation.errors.length, 0)
|
|
assert.deepEqual(evaluation.cells.A1.value, { value: 6, dimension: 'length' })
|
|
assert.deepEqual(evaluation.cells.A2.value, { value: 12, dimension: 'length' })
|
|
assert.deepEqual(evaluation.cells.B1.value, { value: 6, dimension: 'length' })
|
|
assert.deepEqual(evaluation.dependencies.map(({ source, target, reference }) => `${source}:${target}:${reference}`).sort(), ['A2:A1:Width', 'B1:A1:A1'])
|
|
})
|
|
|
|
test('Spreadsheet rejects duplicate aliases and reports dependency cycles', () => {
|
|
const sheet = createSpreadsheet()
|
|
sheet.setCell('A1', 1, { alias: 'Count' })
|
|
assert.throws(() => sheet.setCell('A2', 2, { alias: 'Count' }), /already used/)
|
|
sheet.setCell('B1', '=C1 + 1')
|
|
sheet.setCell('C1', '=B1 + 1')
|
|
const evaluation = sheet.evaluate()
|
|
assert.equal(evaluation.cycles.length > 0, true)
|
|
assert.equal(evaluation.cells.B1.error?.code, 'DEPENDENCY_CYCLE')
|
|
assert.equal(evaluation.cells.C1.error?.code, 'DEPENDENCY_CYCLE')
|
|
})
|
|
|
|
test('Spreadsheet preserves binding metadata and exports escaped CSV', () => {
|
|
const sheet = createSpreadsheet()
|
|
sheet.setCell('$A$1', '6 mm')
|
|
sheet.setAlias('A1', 'Width')
|
|
sheet.setCell('B1', '=Width * 2')
|
|
const binding = sheet.bindProperty('Width', 'pad', 'Length')
|
|
assert.deepEqual(binding, { alias: 'Width', objectId: 'pad', propertyName: 'Length' })
|
|
assert.deepEqual(sheet.snapshot().bindings, [binding])
|
|
assert.match(sheet.exportCsv(), /^6 length,12 length$/)
|
|
assert.throws(() => sheet.setCell('1A', 1), /Invalid spreadsheet cell address/)
|
|
assert.throws(() => sheet.bindProperty('Missing', 'pad', 'Length'), /does not exist/)
|
|
})
|
|
|
|
test('Spreadsheet preserves styles, merged ranges, named ranges and address shifts', () => {
|
|
const sheet = createSpreadsheet()
|
|
sheet.setCell('A1', 1)
|
|
sheet.setCell('A2', '=A1 + 1')
|
|
sheet.setStyle('A1:B2', { background: '#e6f4f1', bold: true, numberFormat: '0.00' })
|
|
sheet.mergeCells('B1:A2')
|
|
sheet.setNamedRange('Inputs', 'A1:A2')
|
|
sheet.insertRows(2)
|
|
sheet.insertColumns(2)
|
|
const snapshot = sheet.snapshot()
|
|
assert.equal(snapshot.cells.find((cell) => cell.address === 'A1')?.style?.bold, true)
|
|
assert.deepEqual(snapshot.namedRanges, [{ name: 'Inputs', range: 'A1:A3' }])
|
|
assert.deepEqual(snapshot.mergedRanges, ['A1:C3'])
|
|
assert.equal(snapshot.cells.find((cell) => cell.address === 'A3')?.input, '=A1 + 1')
|
|
assert.throws(() => sheet.setStyle('A1', { color: 'red' }), /invalid color/)
|
|
})
|