test(functions): add math, aggregate and stats tests

This commit is contained in:
dualfroz committed 2026-08-28 11:17:00 +02:00
1 parent e0d5adb91c
commit c31cea00f7
3 files changed
+255

No files matched your search

+97
View File
@@ -0,0 +1,97 @@
import 'package:cellar/cellar.dart';
import 'package:test/test.dart';
double num(CellValue value) => (value as NumberValue).value;
Sheet withGrid() {
final sheet = Sheet();
final rows = [
['North', 'A', '100'],
['South', 'A', '150'],
['North', 'B', '200'],
['South', 'B', '50'],
['North', 'A', '75'],
];
for (var r = 0; r < rows.length; r++) {
sheet.setValue('A${r + 1}', rows[r][0]);
sheet.setValue('B${r + 1}', rows[r][1]);
sheet.setValue('C${r + 1}', rows[r][2]);
}
return sheet;
}
void main() {
group('aggregate functions', () {
test('SUM SUMSQ PRODUCT MIN MAX', () {
final sheet = Sheet();
sheet.setValue('A1', '2');
sheet.setValue('A2', '3');
sheet.setValue('A3', '4');
sheet.setValue('B1', '=SUM(A1:A3)');
sheet.setValue('B2', '=SUMSQ(A1:A3)');
sheet.setValue('B3', '=PRODUCT(A1:A3)');
sheet.setValue('B4', '=MIN(A1:A3)');
sheet.setValue('B5', '=MAX(A1:A3)');
expect(num(sheet.valueOf('B1')), 9);
expect(num(sheet.valueOf('B2')), 29);
expect(num(sheet.valueOf('B3')), 24);
expect(num(sheet.valueOf('B4')), 2);
expect(num(sheet.valueOf('B5')), 4);
});
test('SUMPRODUCT multiplies element wise', () {
final sheet = Sheet();
sheet.setValue('A1', '1');
sheet.setValue('A2', '2');
sheet.setValue('A3', '3');
sheet.setValue('B1', '4');
sheet.setValue('B2', '5');
sheet.setValue('B3', '6');
sheet.setValue('C1', '=SUMPRODUCT(A1:A3, B1:B3)');
expect(num(sheet.valueOf('C1')), 32);
});
test('SUMIF with a comparison criterion and a separate sum range', () {
final sheet = withGrid();
sheet.setValue('E1', '=SUMIF(C1:C5, ">100")');
sheet.setValue('E2', '=SUMIF(A1:A5, "North", C1:C5)');
expect(num(sheet.valueOf('E1')), 350);
expect(num(sheet.valueOf('E2')), 375);
});
test('COUNTIF and AVERAGEIF', () {
final sheet = withGrid();
sheet.setValue('E1', '=COUNTIF(A1:A5, "North")');
sheet.setValue('E2', '=AVERAGEIF(A1:A5, "South", C1:C5)');
expect(num(sheet.valueOf('E1')), 3);
expect(num(sheet.valueOf('E2')), 100);
});
test('COUNTIF with a wildcard', () {
final sheet = Sheet();
sheet.setValue('A1', 'apple');
sheet.setValue('A2', 'apricot');
sheet.setValue('A3', 'banana');
sheet.setValue('B1', '=COUNTIF(A1:A3, "ap*")');
expect(num(sheet.valueOf('B1')), 2);
});
test('SUMIFS AVERAGEIFS COUNTIFS with two conditions', () {
final sheet = withGrid();
sheet.setValue('E1', '=SUMIFS(C1:C5, A1:A5, "North", B1:B5, "A")');
sheet.setValue('E2', '=COUNTIFS(A1:A5, "North", B1:B5, "A")');
sheet.setValue('E3', '=AVERAGEIFS(C1:C5, A1:A5, "North", B1:B5, "A")');
expect(num(sheet.valueOf('E1')), 175);
expect(num(sheet.valueOf('E2')), 2);
expect(num(sheet.valueOf('E3')), 87.5);
});
test('MAXIFS and MINIFS', () {
final sheet = withGrid();
sheet.setValue('E1', '=MAXIFS(C1:C5, A1:A5, "North")');
sheet.setValue('E2', '=MINIFS(C1:C5, A1:A5, "North")');
expect(num(sheet.valueOf('E1')), 200);
expect(num(sheet.valueOf('E2')), 75);
});
});
}
+80
View File
@@ -0,0 +1,80 @@
import 'package:cellar/cellar.dart';
import 'package:test/test.dart';
CellValue ev(String formula) {
final sheet = Sheet();
sheet.setValue('Z1', formula);
return sheet.valueOf('Z1');
}
double num(CellValue value) => (value as NumberValue).value;
void main() {
group('math functions', () {
test('rounding family', () {
expect(num(ev('=ROUND(3.14159, 2)')), 3.14);
expect(num(ev('=ROUNDUP(3.141, 2)')), 3.15);
expect(num(ev('=ROUNDDOWN(3.149, 2)')), 3.14);
expect(num(ev('=ROUNDUP(-3.141, 2)')), -3.15);
expect(num(ev('=MROUND(17, 5)')), 15);
expect(num(ev('=TRUNC(9.99)')), 9);
expect(num(ev('=INT(-1.1)')), -2);
});
test('modulo and quotient', () {
expect(num(ev('=MOD(10, 3)')), 1);
expect(num(ev('=MOD(-10, 3)')), 2);
expect(num(ev('=QUOTIENT(17, 5)')), 3);
expect(ev('=QUOTIENT(1, 0)'), const ErrorValue(ErrorKind.divZero));
});
test('powers, roots and logs', () {
expect(num(ev('=POWER(2, 10)')), 1024);
expect(num(ev('=SQRT(144)')), 12);
expect(num(ev('=EXP(0)')), 1);
expect(num(ev('=LN(EXP(3))')), closeTo(3, 1e-9));
expect(num(ev('=LOG10(1000)')), closeTo(3, 1e-9));
expect(num(ev('=LOG(8, 2)')), closeTo(3, 1e-9));
expect(ev('=SQRT(-1)'), const ErrorValue(ErrorKind.numError));
expect(ev('=LN(0)'), const ErrorValue(ErrorKind.numError));
});
test('sign, even, odd', () {
expect(num(ev('=SIGN(-7)')), -1);
expect(num(ev('=EVEN(3)')), 4);
expect(num(ev('=EVEN(-1)')), -2);
expect(num(ev('=ODD(2)')), 3);
expect(num(ev('=ODD(-4)')), -5);
});
test('floor and ceil with significance', () {
expect(num(ev('=FLOOR(7.8)')), 7);
expect(num(ev('=FLOOR(23, 5)')), 20);
expect(num(ev('=CEILING(23, 5)')), 25);
});
test('gcd, lcm, factorial, combinations', () {
expect(num(ev('=GCD(24, 36)')), 12);
expect(num(ev('=LCM(4, 6)')), 12);
expect(num(ev('=FACT(5)')), 120);
expect(num(ev('=COMBIN(5, 2)')), 10);
expect(ev('=FACT(-1)'), const ErrorValue(ErrorKind.numError));
});
test('trigonometry and conversions', () {
expect(num(ev('=SIN(0)')), 0);
expect(num(ev('=COS(0)')), 1);
expect(num(ev('=DEGREES(PI())')), closeTo(180, 1e-9));
expect(num(ev('=RADIANS(180)')), closeTo(3.14159265, 1e-6));
expect(num(ev('=ATAN2(1, 1)')), closeTo(0.785398, 1e-5));
});
test('RANDBETWEEN stays within bounds', () {
for (var i = 0; i < 50; i++) {
final value = num(ev('=RANDBETWEEN(3, 7)'));
expect(value, inInclusiveRange(3, 7));
expect(value, value.roundToDouble());
}
});
});
}
+78
View File
@@ -0,0 +1,78 @@
import 'package:cellar/cellar.dart';
import 'package:test/test.dart';
double num(CellValue value) => (value as NumberValue).value;
Sheet withColumn(List<String> values) {
final sheet = Sheet();
for (var i = 0; i < values.length; i++) {
sheet.setValue('A${i + 1}', values[i]);
}
return sheet;
}
void main() {
group('statistics functions', () {
test('MEDIAN', () {
final odd = withColumn(['3', '1', '2']);
odd.setValue('B1', '=MEDIAN(A1:A3)');
expect(num(odd.valueOf('B1')), 2);
final even = withColumn(['1', '2', '3', '4']);
even.setValue('B1', '=MEDIAN(A1:A4)');
expect(num(even.valueOf('B1')), 2.5);
});
test('MODE finds the most frequent value', () {
final sheet = withColumn(['2', '4', '4', '7', '4', '2']);
sheet.setValue('B1', '=MODE(A1:A6)');
expect(num(sheet.valueOf('B1')), 4);
});
test('COUNT COUNTA COUNTBLANK', () {
final sheet = Sheet();
sheet.setValue('A1', '10');
sheet.setValue('A2', 'text');
sheet.setValue('A4', '5');
sheet.setValue('C1', '=COUNT(A1:A4)');
sheet.setValue('C2', '=COUNTA(A1:A4)');
sheet.setValue('C3', '=COUNTBLANK(A1:A4)');
expect(num(sheet.valueOf('C1')), 2);
expect(num(sheet.valueOf('C2')), 3);
expect(num(sheet.valueOf('C3')), 1);
});
test('variance and standard deviation', () {
final sheet = withColumn(['2', '4', '4', '4', '5', '5', '7', '9']);
sheet.setValue('B1', '=VARP(A1:A8)');
sheet.setValue('B2', '=STDEVP(A1:A8)');
sheet.setValue('B3', '=VAR(A1:A8)');
expect(num(sheet.valueOf('B1')), closeTo(4, 1e-9));
expect(num(sheet.valueOf('B2')), closeTo(2, 1e-9));
expect(num(sheet.valueOf('B3')), closeTo(4.5714286, 1e-6));
});
test('LARGE SMALL RANK', () {
final sheet = withColumn(['5', '1', '9', '3']);
sheet.setValue('B1', '=LARGE(A1:A4, 1)');
sheet.setValue('B2', '=SMALL(A1:A4, 2)');
sheet.setValue('B3', '=RANK(9, A1:A4)');
sheet.setValue('B4', '=RANK(1, A1:A4, 1)');
expect(num(sheet.valueOf('B1')), 9);
expect(num(sheet.valueOf('B2')), 3);
expect(num(sheet.valueOf('B3')), 1);
expect(num(sheet.valueOf('B4')), 1);
});
test('PERCENTILE interpolates', () {
final sheet = withColumn(['1', '2', '3', '4']);
sheet.setValue('B1', '=PERCENTILE(A1:A4, 0.5)');
expect(num(sheet.valueOf('B1')), 2.5);
});
test('AVERAGE of an empty selection is #DIV/0!', () {
final sheet = Sheet();
sheet.setValue('B1', '=AVERAGE(A1:A3)');
expect(sheet.valueOf('B1'), const ErrorValue(ErrorKind.divZero));
});
});
}