| Index: client/tests/client/samples/total/total_tests.dart
|
| diff --git a/client/tests/client/samples/total/total_tests.dart b/client/tests/client/samples/total/total_tests.dart
|
| index d0ff36f83fdef077cae915ed4b2e387ab249360b..da3f13b78c1ca544fba4c2fc49f6f3a624ad790e 100644
|
| --- a/client/tests/client/samples/total/total_tests.dart
|
| +++ b/client/tests/client/samples/total/total_tests.dart
|
| @@ -10,8 +10,442 @@
|
| #import('../../../../testing/unittest/unittest.dart');
|
| #import('../../../../view/view.dart');
|
|
|
| -#source('TotalTests.dart');
|
| -
|
| main() {
|
| - TotalTests.main();
|
| + test('DateUtils', () {
|
| + _isDate('1/1');
|
| + _isDate('12/1');
|
| + _isDate('11/31');
|
| + _isDate('01/1');
|
| + _isDate('1/01');
|
| + _isDate('01/01');
|
| + _isDate('1-1');
|
| + _isDate('12-1');
|
| + _isDate('11-31');
|
| + _isDate('01-1');
|
| + _isDate('1-01');
|
| + _isDate('01-01');
|
| + _isDate('1/1/1970');
|
| + _isDate('01/1/1970');
|
| + _isDate('1/01/1970');
|
| + _isDate('1/1/70');
|
| + _isDate('1/01/1970');
|
| + _isNotDate("1/1A");
|
| + _isNotDate("11/222");
|
| + _isNotDate("11/22/");
|
| + _checkDate('7/7/2011', 40731.0);
|
| + _checkDate('7/7/11', 40731.0);
|
| + _checkDate('7/07/2011', 40731.0);
|
| + _checkDate('07/07/2011', 40731.0);
|
| + _checkDate('07/7/2011', 40731.0);
|
| + _checkDate('7-7-2011', 40731.0);
|
| + _checkDate('7-7-11', 40731.0);
|
| + _checkDate('7-07-2011', 40731.0);
|
| + _checkDate('07-07-2011', 40731.0);
|
| + _checkDate('07-7-2011', 40731.0);
|
| +
|
| + // Test MM-DD-<current year>
|
| + double date = DateUtils.parseDate('7/7');
|
| + double years = (date - 40731.0) / 365.0;
|
| + // Assert days/365 is close to an integer -- this will be true for > 100 years
|
| + Expect.isTrue(years - years.floor() < 0.1, "7/7");
|
| + });
|
| +
|
| + test('HtmlUtils', () {
|
| + Expect.equals("", HtmlUtils.quoteHtml(""));
|
| + Expect.equals("<", HtmlUtils.quoteHtml("<"));
|
| + Expect.equals("<<<<", HtmlUtils.quoteHtml("<<<<"));
|
| + Expect.equals("Hello<World", HtmlUtils.quoteHtml("Hello<World"));
|
| + Expect.equals("Hello<<World", HtmlUtils.quoteHtml("Hello<<World"));
|
| + Expect.equals("Hello<W<orld", HtmlUtils.quoteHtml("Hello<W<orld"));
|
| + Expect.equals("<Hello<World", HtmlUtils.quoteHtml("<Hello<World"));
|
| + Expect.equals("<<Hello<World<", HtmlUtils.quoteHtml("<<Hello<World<"));
|
| + Expect.equals("<<Hello<W<orld<", HtmlUtils.quoteHtml("<<Hello<W<orld<"));
|
| +
|
| + Document doc = window.document;
|
| + Element body = doc.body;
|
| + DivElement div = doc.createElement('div');
|
| + body.nodes.add(div);
|
| +
|
| + HtmlUtils.setIntegerProperty(div, "left", 100, "px");
|
| + CSSStyleDeclaration computedStyle = window.getComputedStyle(div, "");
|
| + String valueAsString = computedStyle.getPropertyValue("left");
|
| + // FIXME: Test fails, with valueAsString == "auto". However, setIntegerProperty
|
| + // works when tested in practice, so there is something wrong with recovering
|
| + // the value.
|
| + // Expect.equals("100", valueAsString);
|
| +
|
| + div.remove();
|
| + });
|
| +
|
| + test('StringUtils', () {
|
| + _assertNumeric("0");
|
| + _assertNumeric(".0");
|
| + _assertNumeric("0.0");
|
| + _assertNumeric(".1");
|
| + _assertNumeric("1.");
|
| + _assertNumeric("1.0");
|
| + _assertNumeric("1e17");
|
| + _assertNumeric("1.e17");
|
| + _assertNumeric(".1e17");
|
| + _assertNumeric("1e+17");
|
| + _assertNumeric("1.e-17");
|
| + _assertNumeric(".1e+17");
|
| + _assertNumeric("12345.17");
|
| + _assertNumeric("2.3993e-37");
|
| + _assertNumeric("-12345.17");
|
| + _assertNumeric("+12345.17");
|
| + _assertNumeric("-12345.17e2");
|
| + _assertNumeric("+12345.17e2");
|
| + _assertNumeric("-12345.17e-2");
|
| + _assertNumeric("+12345.17e+2");
|
| + _assertNumeric("-12345.17e+2");
|
| + _assertNumeric("+12345.17e-2");
|
| + _assertNonNumeric("");
|
| + _assertNonNumeric("ABC");
|
| + _assertNonNumeric("++12345.17");
|
| + _assertNonNumeric("12345.17.2");
|
| + _assertNonNumeric("-12345.17e");
|
| + _assertNonNumeric("-12345.17e-");
|
| + _assertNonNumeric("-12345.17e+");
|
| + _assertNonNumeric("-12345.17e2.5");
|
| + _assertNonNumeric("++12345.17A");
|
| + _assertNonNumeric("++12345.17");
|
| + _assertNonNumeric("++12345.17e");
|
| + _assertNonNumeric("++12345.17");
|
| + _assertNonNumeric("--12345.17A");
|
| + _assertNonNumeric("--12345.17");
|
| + _assertNonNumeric("--12345.17e");
|
| + _assertNonNumeric("--12345.17");
|
| + _assertNonNumeric("e17");
|
| + _assertNonNumeric("E17");
|
| + _assertNonNumeric(".e17");
|
| + _assertNonNumeric(".E17");
|
| + _assertNonNumeric("-e17");
|
| + _assertNonNumeric("-E17");
|
| + _assertNonNumeric("+.e17");
|
| + _assertNonNumeric("+.E17");
|
| +
|
| + Expect.equals("A", StringUtils.columnString(1));
|
| + Expect.equals("Z", StringUtils.columnString(26));
|
| + Expect.equals("AA", StringUtils.columnString(27));
|
| + Expect.equals("AZ", StringUtils.columnString(52));
|
| + Expect.equals("BA", StringUtils.columnString(53));
|
| + Expect.equals("BZ", StringUtils.columnString(78));
|
| + Expect.equals("ZA", StringUtils.columnString(677));
|
| + Expect.equals("ZZ", StringUtils.columnString(702));
|
| + Expect.equals("AAA", StringUtils.columnString(703));
|
| + Expect.equals("ZZZ", StringUtils.columnString(18278));
|
| + Expect.equals("AAAA", StringUtils.columnString(18279));
|
| +
|
| + Expect.equals(-1, StringUtils.compare("a", "b"));
|
| + Expect.equals(1, StringUtils.compare("b", "a"));
|
| + Expect.equals(0, StringUtils.compare("a", "a"));
|
| + Expect.equals(-1, StringUtils.compare("ab", "abb"));
|
| + Expect.equals(1, StringUtils.compare("abb", "ab"));
|
| + // Note: StringUtils.compare sorts empty string before non-empty strings,
|
| + // but the comparator used to sort spreadhseet cells deals with this case
|
| + // internally and never calls StringUtils.compare with an empty string argument
|
| + Expect.isTrue(StringUtils.compare("", "a") < 0);
|
| + Expect.isTrue(StringUtils.compare("a", "") > 0);
|
| + });
|
| +
|
| + test('Scanner', () {
|
| + String input =
|
| + "ROUND(R1C0 * ((R1C1 / 1234.0000) / (1 - POWER(1 + (R1C1 / 1234.5000), -12 * R1C2))), 2)";
|
| + Scanner scanner =
|
| + new Scanner.preserveWhitespace(input, new CellLocation(null, new RowCol(0, 0)));
|
| + List<Token> tokens = scanner.scan();
|
| +
|
| + List<String> expected = <String>[
|
| + "ROUND", "(", "R1C0", " ", "*", " ", "(", "(", "R1C1", " ", "/", " ",
|
| + "1234", ")", " ", "/", " ", "(", "1", " ", "-", " ", "POWER", "(", "1",
|
| + " ", "+", " ", "(", "R1C1", " ", "/", " ", "1234.5", ")", ",", " ", "-",
|
| + "12", " ", "*", " ", "R1C2", ")", ")", ")", ",", " ", "2", ")", ];
|
| +
|
| + int index = 0;
|
| + tokens.forEach((Token token) {
|
| + Expect.equals(expected[index++], token.toString());
|
| + });
|
| +
|
| + validate(String input) {
|
| + Scanner scanner =
|
| + new Scanner.preserveWhitespace(input, new CellLocation(null, new RowCol(1, 1)));
|
| + StringBuffer sb = new StringBuffer();
|
| + while (true) {
|
| + Token token = scanner.nextToken();
|
| + if (token == null) {
|
| + break;
|
| + }
|
| + sb.add(token.toString());
|
| + }
|
| +
|
| + Expect.equals(input, sb.toString());
|
| + }
|
| +
|
| + validate("10 < 12");
|
| + validate("10 > 12");
|
| + validate("10 = 12");
|
| + validate("10 <> 12");
|
| + validate("10 <= 12");
|
| + validate("10 >= 12");
|
| + validate("FALSE");
|
| + validate("TRUE");
|
| + validate("ROUND(R1C0*((R1C1/1200)/(1-POWER(1+(R1C1/1200),-12*R1C2))),2)");
|
| + validate("R1C1 + R2C2");
|
| + validate("ThisIsAFunction999()");
|
| + validate("LOG10(1000)");
|
| + validate("R1C2 R1C2 R1C2 R1C2");
|
| + validate("R[1]C2 R[-1]C2 R[1]C-2\tR[-1]C-2");
|
| + validate("R1C[2]R1C[2]R1C[-2]R1C[-2]");
|
| + validate("R[1]C[2]R[-1]C[2]R[1]C[-2]R[-1]C[-2]");
|
| + validate("R111C222 R111C222 R111C222 R111C2");
|
| + validate("R[111]C222:R[-111]C222, R[111]C222:R[-111]C222");
|
| + validate("R111C[222]R111C[222]R111C[-222]R111C[-222]");
|
| + validate("(R[111]C[222],R[-111]C[222]:R[111]C[-222], R[-111]C[-222])");
|
| + validate("R1C0");
|
| + validate("R[-1]C + 1");
|
| + // Problem: R[-1]C-R[-1]C[2] : the C- is really C[0]- but looks like C
|
| + // followed by a (degenerate) number
|
| + validate("R[-1]C[1] - R[-1]C[2]");
|
| + validate("RC[-1]* R1C1 / 1200");
|
| + validate("R1C3 -RC[-1]");
|
| + });
|
| +
|
| + test('Parser', () {
|
| + String input = "ROUND(R1C0*((R1C1/1200)/(1-POWER(1+(R1C1/1200),-12*R1C2))),2)";
|
| + Scanner scanner = new Scanner(input, new CellLocation(null, new RowCol(1, 1)));
|
| + Parser parser = new Parser(scanner);
|
| + List<Token> tokens = parser.parse();
|
| +
|
| + List<String> expected = [
|
| + "R1C0", "R1C1", "1200", "/", "1", "1",
|
| + "R1C1", "1200", "/", "+", "0", "12", "-",
|
| + "R1C2", "*", "2" /* nargs */, "POWER", "-", "/", "*", "2",
|
| + "2" /* nargs */, "ROUND"];
|
| +
|
| + int index = 0;
|
| + tokens.forEach((Token token) {
|
| + Expect.equals(expected[index++], token.toString());
|
| + });
|
| + });
|
| +
|
| + test('Functions', () {
|
| + validate(String formula, double expected) {
|
| + try {
|
| + CellLocation location = new CellLocation(null, new RowCol(0, 0));
|
| + Value value = new StringFormula(formula, location).calculate();
|
| + double actual = value.asDouble(null);
|
| + Expect.approxEquals(expected, actual, 0.00001, "Error evaluating formula: ${formula}");
|
| + } catch (var error) {
|
| + Expect.fail("Error evaluating formula: ${formula}, error=${error}");
|
| + }
|
| + }
|
| +
|
| + validate("2<5", 1.0);
|
| + validate("2>5", 0.0);
|
| + validate("2=5", 0.0);
|
| + validate("5=5", 1.0);
|
| + validate("2<>5", 1.0);
|
| + validate("5<>5", 0.0);
|
| + validate("2<=1", 0.0);
|
| + validate("2<=2", 1.0);
|
| + validate("2<=3", 1.0);
|
| + validate("2>=1", 1.0);
|
| + validate("2>=2", 1.0);
|
| + validate("2>=3", 0.0);
|
| + validate("FALSE", 0.0);
|
| + validate("TRUE", 1.0);
|
| + validate("FALSE()", 0.0);
|
| + validate("TRUE()", 1.0);
|
| + validate("NOT(FALSE)", 1.0);
|
| + validate("NOT(TRUE)", 0.0);
|
| + validate("NOT(FALSE())", 1.0);
|
| + validate("NOT(TRUE())", 0.0);
|
| + validate("AND(FALSE)", 0.0);
|
| + validate("AND(TRUE)", 1.0);
|
| + validate("AND(FALSE,FALSE)", 0.0);
|
| + validate("AND(FALSE,TRUE)", 0.0);
|
| + validate("AND(TRUE,FALSE)", 0.0);
|
| + validate("AND(TRUE,TRUE)", 1.0);
|
| + validate("OR(FALSE,FALSE)", 0.0);
|
| + validate("OR(FALSE,TRUE)", 1.0);
|
| + validate("OR(TRUE,FALSE)", 1.0);
|
| + validate("OR(TRUE,TRUE)", 1.0);
|
| + validate("IF(TRUE,3,4)", 3.0);
|
| + validate("IF(FALSE,3,4)", 4.0);
|
| + validate("IF(TRUE,3)", 3.0);
|
| + validate("IF(FALSE,3)", 0.0);
|
| + validate("IF(NOT(FALSE()),3,4)", 3.0);
|
| + validate("IF(NOT(TRUE()),3,4)", 4.0);
|
| + // Unary functions
|
| + validate("5-2", 3.0);
|
| + validate("5--2", 7.0);
|
| + validate("ABS(3)", 3.0);
|
| + validate("ABS(-3)", 3.0);
|
| + validate("COS(1)", 0.540302306);
|
| + validate("DEGREES(3)", 171.887339);
|
| + validate("EVEN(1.5)", 2.0);
|
| + validate("EVEN(3)", 4.0);
|
| + validate("EVEN(2)", 2.0);
|
| + validate("EVEN(-1)", -2.0);
|
| + validate("EVEN(-2)", -2.0);
|
| + validate("EXP(0)", 1.0);
|
| + validate("EXP(1)", 2.71828183);
|
| + validate("EXP(-1)", 0.367879441);
|
| + validate("FACT(0)", 1.0);
|
| + validate("FACT(1)", 1.0);
|
| + validate("FACT(2)", 2.0);
|
| + validate("FACT(3)", 6.0);
|
| + validate("FACT(4)", 24.0);
|
| + validate("FACTDOUBLE(5)", 15.0);
|
| + validate("FACTDOUBLE(6)", 48.0);
|
| + validate("FACTDOUBLE(10)", 3840.0);
|
| + validate("FACTDOUBLE(0)", 1.0);
|
| + validate("INT(8.9)", 8.0);
|
| + validate("INT(-8.9)", -9.0);
|
| + validate("LN(1)", 0.0);
|
| + validate("LN(2)", 0.693147181);
|
| + validate("LOG10(1)", 0.0);
|
| + validate("LOG10(10)", 1.0);
|
| + validate("LOG10(20)", 1.30103);
|
| + validate("ODD(1.5)", 3.0);
|
| + validate("ODD(3)", 3.0);
|
| + validate("ODD(2)", 3.0);
|
| + validate("ODD(-1)", -1.0);
|
| + validate("ODD(-2)", -3.0);
|
| + validate("RADIANS(180)", 3.14159265);
|
| + validate("SIGN(-3)", -1.0);
|
| + validate("SIGN(0)", 0.0);
|
| + validate("SIGN(3)", 1.0);
|
| + validate("SIN(1)", 0.841470985);
|
| + validate("SQRT(2)", 1.41421356);
|
| + validate("TANH(0)", 0.0);
|
| + validate("TANH(LN((1 + SQRT(5))/2))", Math.sqrt(5.0) / 5.0);
|
| +
|
| + // Binary functions
|
| + validate("COMBIN(7,3)", 35.0);
|
| + validate("COMBIN(7,4)", 35.0);
|
| + validate("LOG(1, 10)", 0.0);
|
| + validate("LOG(10, 10)", 1.0);
|
| + validate("LOG(20, 10)", 1.30103);
|
| + validate("MOD(3, 2)", 1.0);
|
| + validate("MOD(-3, 2)", 1.0);
|
| + validate("MOD(3, -2)", -1.0);
|
| + validate("MOD(-3, -2)", -1.0);
|
| + validate("QUOTIENT(5,2)", 2.0);
|
| + // validate("QUOTIENT(4.5,3.1)", 1.0);
|
| + validate("QUOTIENT(-10,3)", -3.0);
|
| + validate("ROUND(SQRT(2), 2)", 1.41);
|
| + validate("ROUND(SQRT(2), 1)", 1.4);
|
| + validate("POWER(SQRT(2), 2)", 2.0);
|
| + validate("TRUNC(8.567)", 8.0);
|
| + validate("TRUNC(8.567, 1)", 8.5);
|
| + validate("TRUNC(8.567, 2)", 8.56);
|
| + validate("TRUNC(-8.567)", -8.0);
|
| + validate("TRUNC(-8.567, 1)", -8.5);
|
| + validate("TRUNC(-8.567, 2)", -8.56);
|
| +
|
| + // Date/Time functions
|
| + validate("HOUR(40730.6789)", 16.0);
|
| + validate("MINUTE(40730.6789)", 17.0);
|
| + validate("SECOND(40730.6789)", 36.0);
|
| + validate("YEAR(DATE(2011,7,4))", 2011.0);
|
| + validate("MONTH(DATE(2011,7,4))", 7.0);
|
| + validate("DAY(DATE(2011,7,4))", 4.0);
|
| +
|
| + // N-ary functions
|
| + validate("GCD(5,2)", 1.0);
|
| + validate("GCD(24,36)", 12.0);
|
| + validate("GCD(24,36,4)", 4.0);
|
| + validate("GCD(7,1)", 1.0);
|
| + validate("GCD(5,0)", 5.0);
|
| + validate("LCM(5,2)", 10.0);
|
| + validate("LCM(24,36)", 72.0);
|
| + validate("LCM(24,36,5)", 360.0);
|
| + validate("MULTINOMIAL(2, 3, 4)", 1260.0);
|
| + validate("SERIESSUM(PI()/4,0,2,1,-1/FACT(2),1/FACT(4),-1/FACT(6))", 0.707103);
|
| + });
|
| +
|
| + test('Mortgage', () {
|
| + Reader reader = new SYLKReader();
|
| + List<String> data = reader.makeExample("mortgage");
|
| + Spreadsheet spreadsheet = new Spreadsheet();
|
| + reader.loadSpreadsheet(spreadsheet, data);
|
| + Expect.approxEquals(383.05, spreadsheet.getDoubleValue(new RowCol(99, 2)), 0.05);
|
| + Expect.approxEquals(36977.28, spreadsheet.getDoubleValue(new RowCol(100, 2)), 0.05);
|
| + Expect.approxEquals(6976.86, spreadsheet.getDoubleValue(new RowCol(100, 3)), 0.05);
|
| + Expect.approxEquals(30000.42, spreadsheet.getDoubleValue(new RowCol(100, 4)), 0.05);
|
| + });
|
| +
|
| + test('Vlookup', () {
|
| + Spreadsheet spreadsheet = new Spreadsheet();
|
| + spreadsheet.setCellFromContentString(new RowCol(1, 1), "4.14");
|
| + spreadsheet.setCellFromContentString(new RowCol(2, 1), "4.19");
|
| + spreadsheet.setCellFromContentString(new RowCol(3, 1), "5.17");
|
| + spreadsheet.setCellFromContentString(new RowCol(4, 1), "5.77");
|
| + spreadsheet.setCellFromContentString(new RowCol(5, 1), "6.39");
|
| + spreadsheet.setCellFromContentString(new RowCol(1, 2), "1");
|
| + spreadsheet.setCellFromContentString(new RowCol(2, 2), "2");
|
| + spreadsheet.setCellFromContentString(new RowCol(3, 2), "3");
|
| + spreadsheet.setCellFromContentString(new RowCol(4, 2), "4");
|
| + spreadsheet.setCellFromContentString(new RowCol(5, 2), "5");
|
| +
|
| + // Test exact match: 5.77 ==> 4
|
| + spreadsheet.setCellFromContentString(new RowCol(6, 1), "=VLOOKUP(5.77,A1:B5,2,0)");
|
| + Expect.equals(4.0, spreadsheet.getDoubleValue(new RowCol(6, 1)));
|
| +
|
| + // Test approximate match: 5.177 < 5.3 < 5.77 ==> 3
|
| + spreadsheet.setCellFromContentString(new RowCol(6, 1), "=VLOOKUP(5.3,A1:B5,2,1)");
|
| + Expect.equals(3.0, spreadsheet.getDoubleValue(new RowCol(6, 1)));
|
| +
|
| + // Omit 'range_lookup' flag, behavior is the same as "true"
|
| + spreadsheet.setCellFromContentString(new RowCol(6, 1), "=VLOOKUP(5.3,A1:B5,2)");
|
| + Expect.equals(3.0, spreadsheet.getDoubleValue(new RowCol(6, 1)));
|
| + });
|
| +
|
| + test('Hlookup', () {
|
| + Spreadsheet spreadsheet = new Spreadsheet();
|
| + spreadsheet.setCellFromContentString(new RowCol(1, 1), "4.14");
|
| + spreadsheet.setCellFromContentString(new RowCol(1, 2), "4.19");
|
| + spreadsheet.setCellFromContentString(new RowCol(1, 3), "5.17");
|
| + spreadsheet.setCellFromContentString(new RowCol(1, 4), "5.77");
|
| + spreadsheet.setCellFromContentString(new RowCol(1, 5), "6.39");
|
| + spreadsheet.setCellFromContentString(new RowCol(2, 1), "1");
|
| + spreadsheet.setCellFromContentString(new RowCol(2, 2), "2");
|
| + spreadsheet.setCellFromContentString(new RowCol(2, 3), "3");
|
| + spreadsheet.setCellFromContentString(new RowCol(2, 4), "4");
|
| + spreadsheet.setCellFromContentString(new RowCol(2, 5), "5");
|
| +
|
| + // Test exact match: 5.77 ==> 4
|
| + spreadsheet.setCellFromContentString(new RowCol(1, 6), "=HLOOKUP(5.77,A1:E2,2,0)");
|
| + Expect.equals(4.0, spreadsheet.getDoubleValue(new RowCol(1, 6)));
|
| +
|
| + // Test approximate match: 5.177 < 5.3 < 5.77 ==> 3
|
| + spreadsheet.setCellFromContentString(new RowCol(1, 6), "=HLOOKUP(5.3,A1:E2,2,1)");
|
| + Expect.equals(3.0, spreadsheet.getDoubleValue(new RowCol(1, 6)));
|
| +
|
| + // Omit 'range_lookup' flag, behavior is the same as "true"
|
| + spreadsheet.setCellFromContentString(new RowCol(6, 1), "=HLOOKUP(5.3,A1:E2,2)");
|
| + Expect.equals(3.0, spreadsheet.getDoubleValue(new RowCol(6, 1)));
|
| + });
|
| +}
|
| +
|
| +_checkDate(String date, num value) {
|
| + Expect.equals(value.toString(), DateUtils.parseDate(date).toString());
|
| +}
|
| +
|
| +_isDate(String date) {
|
| + Expect.isTrue(DateUtils.isDate(date), "DateUtils.isDate(${date})");
|
| +}
|
| +
|
| +_isNotDate(String date) {
|
| + Expect.isFalse(DateUtils.isDate(date), "DateUtils.isDate(${date})");
|
| +}
|
| +
|
| +_assertNumeric(String s) {
|
| + Expect.isTrue(StringUtils.isNumeric(s), 'StringUtils.isNumeric("{$s}")');
|
| +}
|
| +
|
| +_assertNonNumeric(String s) {
|
| + Expect.isFalse(StringUtils.isNumeric(s), '!StringUtils.isNumeric("${s}")');
|
| }
|
|
|