diff --git a/test/functions_logic_test.dart b/test/functions_logic_test.dart new file mode 100644 index 0000000..c567610 --- /dev/null +++ b/test/functions_logic_test.dart @@ -0,0 +1,79 @@ +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('logical functions', () { + test('IF chooses a branch', () { + expect(ev('=IF(2>1, "yes", "no")'), const TextValue('yes')); + expect(ev('=IF(2<1, "yes", "no")'), const TextValue('no')); + }); + + test('IFS picks the first true condition', () { + expect(ev('=IFS(FALSE, 1, TRUE, 2, TRUE, 3)'), const NumberValue(2)); + expect(ev('=IFS(FALSE, 1, FALSE, 2)'), const ErrorValue(ErrorKind.na)); + }); + + test('SWITCH matches a value with a default', () { + expect(ev('=SWITCH(2, 1, "a", 2, "b", "z")'), const TextValue('b')); + expect(ev('=SWITCH(9, 1, "a", 2, "b", "z")'), const TextValue('z')); + expect(ev('=SWITCH(9, 1, "a")'), const ErrorValue(ErrorKind.na)); + }); + + test('AND OR NOT XOR', () { + expect(ev('=AND(TRUE, 1=1, 2>1)'), const BooleanValue(true)); + expect(ev('=OR(FALSE, 1=2, 3>1)'), const BooleanValue(true)); + expect(ev('=NOT(1=1)'), const BooleanValue(false)); + expect(ev('=XOR(TRUE, TRUE, TRUE)'), const BooleanValue(true)); + expect(ev('=XOR(TRUE, TRUE)'), const BooleanValue(false)); + }); + + test('IFERROR and IFNA', () { + expect(num(ev('=IFERROR(1/0, 42)')), 42); + expect(num(ev('=IFERROR(5, 42)')), 5); + expect(num(ev('=IFNA(NA(), 7)')), 7); + expect(ev('=IFNA(1/0, 7)'), const ErrorValue(ErrorKind.divZero)); + }); + }); + + group('info functions', () { + test('type predicates', () { + expect(ev('=ISNUMBER(5)'), const BooleanValue(true)); + expect(ev('=ISTEXT("x")'), const BooleanValue(true)); + expect(ev('=ISLOGICAL(TRUE)'), const BooleanValue(true)); + expect(ev('=ISNUMBER("x")'), const BooleanValue(false)); + }); + + test('error predicates', () { + expect(ev('=ISERROR(1/0)'), const BooleanValue(true)); + expect(ev('=ISERR(1/0)'), const BooleanValue(true)); + expect(ev('=ISERR(NA())'), const BooleanValue(false)); + expect(ev('=ISNA(NA())'), const BooleanValue(true)); + expect(ev('=ISNA(1/0)'), const BooleanValue(false)); + }); + + test('ISBLANK reflects an empty cell', () { + final sheet = Sheet(); + sheet.setValue('B1', '=ISBLANK(A1)'); + sheet.setValue('B2', '=ISBLANK(A2)'); + sheet.setValue('A2', 'x'); + expect(sheet.valueOf('B1'), const BooleanValue(true)); + expect(sheet.valueOf('B2'), const BooleanValue(false)); + }); + + test('N and TYPE', () { + expect(num(ev('=N(TRUE)')), 1); + expect(num(ev('=N("text")')), 0); + expect(num(ev('=TYPE(5)')), 1); + expect(num(ev('=TYPE("x")')), 2); + expect(num(ev('=TYPE(1/0)')), 16); + }); + }); +} diff --git a/test/functions_lookup_test.dart b/test/functions_lookup_test.dart new file mode 100644 index 0000000..404fcf6 --- /dev/null +++ b/test/functions_lookup_test.dart @@ -0,0 +1,93 @@ +import 'package:cellar/cellar.dart'; +import 'package:test/test.dart'; + +double num(CellValue value) => (value as NumberValue).value; + +Sheet lookupSheet() { + final sheet = Sheet(); + final rows = [ + ['apple', '1', 'red'], + ['banana', '2', 'yellow'], + ['cherry', '3', 'red'], + ]; + 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('lookup functions', () { + test('VLOOKUP exact match returns another column', () { + final sheet = lookupSheet(); + sheet.setValue('E1', '=VLOOKUP("banana", A1:C3, 3, FALSE)'); + expect(sheet.valueOf('E1'), const TextValue('yellow')); + }); + + test('VLOOKUP approximate on sorted numbers', () { + final sheet = Sheet(); + sheet.setValue('A1', '0'); + sheet.setValue('B1', 'low'); + sheet.setValue('A2', '10'); + sheet.setValue('B2', 'mid'); + sheet.setValue('A3', '20'); + sheet.setValue('B3', 'high'); + sheet.setValue('D1', '=VLOOKUP(15, A1:B3, 2)'); + expect(sheet.valueOf('D1'), const TextValue('mid')); + }); + + test('VLOOKUP missing exact key is #N/A', () { + final sheet = lookupSheet(); + sheet.setValue('E1', '=VLOOKUP("mango", A1:C3, 2, FALSE)'); + expect(sheet.valueOf('E1'), const ErrorValue(ErrorKind.na)); + }); + + test('HLOOKUP across a row', () { + final sheet = Sheet(); + sheet.setValue('A1', 'Q1'); + sheet.setValue('B1', 'Q2'); + sheet.setValue('C1', 'Q3'); + sheet.setValue('A2', '10'); + sheet.setValue('B2', '20'); + sheet.setValue('C2', '30'); + sheet.setValue('E1', '=HLOOKUP("Q2", A1:C2, 2, FALSE)'); + expect(num(sheet.valueOf('E1')), 20); + }); + + test('MATCH exact and approximate', () { + final sheet = lookupSheet(); + sheet.setValue('E1', '=MATCH("cherry", A1:A3, 0)'); + expect(num(sheet.valueOf('E1')), 3); + final numbers = Sheet(); + numbers.setValue('A1', '10'); + numbers.setValue('A2', '20'); + numbers.setValue('A3', '30'); + numbers.setValue('B1', '=MATCH(25, A1:A3, 1)'); + expect(num(numbers.valueOf('B1')), 2); + }); + + test('INDEX and INDEX/MATCH', () { + final sheet = lookupSheet(); + sheet.setValue('E1', '=INDEX(A1:C3, 2, 3)'); + sheet.setValue('E2', '=INDEX(B1:B3, MATCH("cherry", A1:A3, 0))'); + expect(sheet.valueOf('E1'), const TextValue('yellow')); + expect(num(sheet.valueOf('E2')), 3); + }); + + test('CHOOSE ROWS COLUMNS ROW COLUMN', () { + final sheet = lookupSheet(); + sheet.setValue('E1', '=CHOOSE(2, "a", "b", "c")'); + sheet.setValue('E2', '=ROWS(A1:C3)'); + sheet.setValue('E3', '=COLUMNS(A1:C3)'); + sheet.setValue('E4', '=ROW(B5)'); + sheet.setValue('E5', '=COLUMN(D2)'); + expect(sheet.valueOf('E1'), const TextValue('b')); + expect(num(sheet.valueOf('E2')), 3); + expect(num(sheet.valueOf('E3')), 3); + expect(num(sheet.valueOf('E4')), 5); + expect(num(sheet.valueOf('E5')), 4); + }); + }); +} diff --git a/test/functions_text_test.dart b/test/functions_text_test.dart new file mode 100644 index 0000000..d169635 --- /dev/null +++ b/test/functions_text_test.dart @@ -0,0 +1,72 @@ +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('text functions', () { + test('slicing', () { + expect(ev('=LEFT("spreadsheet", 6)'), const TextValue('spread')); + expect(ev('=RIGHT("spreadsheet", 5)'), const TextValue('sheet')); + expect(ev('=MID("spreadsheet", 7, 5)'), const TextValue('sheet')); + expect(num(ev('=LEN("hello")')), 5); + }); + + test('case and proper', () { + expect(ev('=UPPER("cellar")'), const TextValue('CELLAR')); + expect(ev('=LOWER("CELLAR")'), const TextValue('cellar')); + expect(ev('=PROPER("john the FIRST")'), const TextValue('John The First')); + expect(ev('=TRIM(" a b ")'), const TextValue('a b')); + }); + + test('concat and textjoin', () { + expect(ev('=CONCAT("foo", "bar")'), const TextValue('foobar')); + expect(ev('=TEXTJOIN("-", TRUE, "a", "", "b")'), const TextValue('a-b')); + expect(ev('=TEXTJOIN("-", FALSE, "a", "", "b")'), const TextValue('a--b')); + }); + + test('substitute and replace', () { + expect(ev('=SUBSTITUTE("a-b-c", "-", "+")'), const TextValue('a+b+c')); + expect(ev('=SUBSTITUTE("a-b-c", "-", "+", 2)'), const TextValue('a-b+c')); + expect(ev('=REPLACE("abcdef", 2, 3, "XY")'), const TextValue('aXYef')); + }); + + test('find and search', () { + expect(num(ev('=FIND("c", "abcabc")')), 3); + expect(num(ev('=SEARCH("C", "abcabc")')), 3); + expect(ev('=FIND("z", "abc")'), const ErrorValue(ErrorKind.value)); + }); + + test('char and code', () { + expect(ev('=CHAR(65)'), const TextValue('A')); + expect(num(ev('=CODE("A")')), 65); + }); + + test('value and numbervalue', () { + expect(num(ev('=VALUE("42.5")')), 42.5); + expect(num(ev('=NUMBERVALUE("1.234,50", ",", ".")')), 1234.5); + }); + + test('TEXT formats a number', () { + expect(ev('=TEXT(1234.5, "#,##0.00")'), const TextValue('1,234.50')); + expect(ev('=TEXT(0.25, "0.0%")'), const TextValue('25.0%')); + }); + + test('regex helpers', () { + expect(ev('=REGEXMATCH("abc123", "[0-9]+")'), const BooleanValue(true)); + expect(ev('=REGEXEXTRACT("order 42 done", "([0-9]+)")'), const TextValue('42')); + expect(ev('=REGEXREPLACE("a1b2c3", "[0-9]", "#")'), const TextValue('a#b#c#')); + }); + + test('REPT and EXACT', () { + expect(ev('=REPT("ab", 3)'), const TextValue('ababab')); + expect(ev('=EXACT("Abc", "abc")'), const BooleanValue(false)); + }); + }); +}