diff --git a/test/functions_aggregate_test.dart b/test/functions_aggregate_test.dart new file mode 100644 index 0000000..feb45b3 --- /dev/null +++ b/test/functions_aggregate_test.dart @@ -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); + }); + }); +} diff --git a/test/functions_math_test.dart b/test/functions_math_test.dart new file mode 100644 index 0000000..ed16fea --- /dev/null +++ b/test/functions_math_test.dart @@ -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()); + } + }); + }); +} diff --git a/test/functions_stats_test.dart b/test/functions_stats_test.dart new file mode 100644 index 0000000..6ed26f0 --- /dev/null +++ b/test/functions_stats_test.dart @@ -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 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)); + }); + }); +}