| // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| // Check that JSStringImpl behaves correctly as a subtype of String. | |
| import 'dart:js_interop'; | |
| import 'package:expect/expect.dart'; | |
| // We run many tests in three configurations: | |
| // 1) Test should ensure receivers for all [String] operations will be | |
| // `JSStringImpl`. | |
| // 2) Test should ensure arguments to all [String] operations | |
| // will be `JSStringImpl`. | |
| // 3) Test should ensure both receivers and arguments | |
| // for all [String] operations will be `JSStringImpl`. | |
| enum TestMode { | |
| jsStringImplReceiver, | |
| jsStringImplArgument, | |
| jsStringImplReceiverAndArguments, | |
| } | |
| enum Position { jsStringImplReceiver, jsStringImplArgument } | |
| bool useJSStringImpl(Position pos, TestMode mode) => | |
| (pos == Position.jsStringImplReceiver && | |
| (mode == TestMode.jsStringImplReceiver || | |
| mode == TestMode.jsStringImplReceiverAndArguments)) || | |
| (pos == Position.jsStringImplArgument && | |
| (mode == TestMode.jsStringImplArgument || | |
| mode == TestMode.jsStringImplReceiverAndArguments)); | |
| // Round trip through `dart:js_interop` to get a `JSString` backed `String`. | |
| String jsStringImpl(String s) => s.toJS.toDart; | |
| String getStr(String s, Position pos, TestMode mode) => | |
| useJSStringImpl(pos, mode) ? jsStringImpl(s) : s; | |
| void testStringLength(int length, String str) { | |
| Expect.equals(length, str.length); | |
| (length == 0 ? Expect.isTrue : Expect.isFalse)(str.isEmpty); | |
| (length != 0 ? Expect.isTrue : Expect.isFalse)(str.isNotEmpty); | |
| } | |
| void testLength(TestMode mode) { | |
| String str = getStr('', Position.jsStringImplReceiver, mode); | |
| for (var i = 0; i < 5; i++) { | |
| testStringLength(i, str); | |
| str += getStr(' ', Position.jsStringImplArgument, mode); | |
| } | |
| } | |
| void testOutOfRange() { | |
| String a = jsStringImpl('Hello'); | |
| bool exception_caught = false; | |
| try { | |
| var c = a[20]; // Throw exception. | |
| } on RangeError catch (e) { | |
| exception_caught = true; | |
| } | |
| Expect.isTrue(exception_caught); | |
| } | |
| void testIndex() { | |
| String str = jsStringImpl('string'); | |
| List<String> chars = ['s', 't', 'r', 'i', 'n', 'g']; | |
| assert(str.length == chars.length); | |
| for (int i = 0; i < str.length; i++) { | |
| Expect.isTrue(str[i] is String); | |
| testStringLength(1, str[i]); | |
| Expect.equals(str[i], chars[i]); | |
| } | |
| } | |
| void testCodeUnitAt() { | |
| String str = jsStringImpl('string'); | |
| List<int> codes = [115, 116, 114, 105, 110, 103]; | |
| for (int i = 0; i < str.length; i++) { | |
| Expect.isTrue(str.codeUnitAt(i) is int); | |
| Expect.equals(str.codeUnitAt(i), codes[i]); | |
| } | |
| } | |
| void testConcat(TestMode mode) { | |
| var a = getStr('One', Position.jsStringImplReceiver, mode); | |
| var b = getStr('Four', Position.jsStringImplArgument, mode); | |
| var c = a + b; | |
| testStringLength(7, c); | |
| Expect.equals('OneFour', c); | |
| } | |
| void testEquals(TestMode mode) { | |
| String r(String s) => getStr(s, Position.jsStringImplReceiver, mode); | |
| String a(String s) => getStr(s, Position.jsStringImplArgument, mode); | |
| final rstr = r('str'); | |
| final astr = a('str'); | |
| Expect.equals(rstr, astr); | |
| Expect.equals(rstr, a('s') + 't' + 'r'); | |
| Expect.equals(r('s') + 't' + 'r', a('str')); | |
| Expect.isFalse(rstr == a('s')); | |
| Expect.isFalse(r('s') == astr); | |
| Expect.isFalse(r('') == a('s')); | |
| Expect.equals(r(''), a('')); | |
| } | |
| void testEndsWith(TestMode mode) { | |
| String r(String s) => getStr(s, Position.jsStringImplReceiver, mode); | |
| String a(String s) => getStr(s, Position.jsStringImplArgument, mode); | |
| final rstr = r('str'); | |
| Expect.isTrue(rstr.endsWith(a('str'))); | |
| Expect.isFalse(rstr.endsWith(a('t'))); | |
| final rEmptyStr = r(''); | |
| Expect.isTrue(rEmptyStr.endsWith(r(''))); | |
| Expect.isFalse(rEmptyStr.endsWith(r('s'))); | |
| } | |
| void testStartsWith(TestMode mode) { | |
| String r(String s) => getStr(s, Position.jsStringImplReceiver, mode); | |
| String a(String s) => getStr(s, Position.jsStringImplArgument, mode); | |
| final rstr = r('str'); | |
| Expect.isTrue(rstr.startsWith(a('str'))); | |
| Expect.isFalse(rstr.startsWith(a('stri'))); | |
| final rEmptyStr = r(''); | |
| Expect.isTrue(rEmptyStr.startsWith(a(''))); | |
| Expect.isFalse(rEmptyStr.startsWith(a('s'))); | |
| final rstrstr = r('strstr'); | |
| final as = a('s'); | |
| Expect.isFalse(rstrstr.startsWith(as, 1)); | |
| Expect.isTrue(rstrstr.startsWith(as, 3)); | |
| final ast = a('st'); | |
| Expect.isFalse(rstrstr.startsWith(ast, 1)); | |
| Expect.isTrue(rstrstr.startsWith(ast, 3)); | |
| final astr = a('str'); | |
| Expect.isFalse(rstrstr.startsWith(astr, 1)); | |
| Expect.isTrue(rstrstr.startsWith(astr, 3)); | |
| final aEmptyStr = a(''); | |
| Expect.isTrue(rstr.startsWith(aEmptyStr, 0)); | |
| Expect.throws(() => rstr.startsWith(aEmptyStr, -1)); | |
| Expect.throws(() => rstr.startsWith(aEmptyStr, 4)); | |
| final regexp = RegExp('s(?:tr?)?'); | |
| Expect.isTrue(rstr.startsWith(regexp)); | |
| Expect.isFalse(rstrstr.startsWith(regexp, 1)); | |
| Expect.isFalse(rstrstr.startsWith(regexp, 2)); | |
| Expect.isTrue(rstrstr.startsWith(regexp, 3)); | |
| Expect.isFalse(rstrstr.startsWith(regexp, 4)); | |
| } | |
| void testIndexOf(TestMode mode) { | |
| String r(String s) => getStr(s, Position.jsStringImplReceiver, mode); | |
| String a(String s) => getStr(s, Position.jsStringImplArgument, mode); | |
| final rstr = r('str'); | |
| final rEmptyStr = r(''); | |
| final aEmptyStr = a(''); | |
| Expect.equals(0, rstr.indexOf(aEmptyStr, 0)); | |
| Expect.equals(0, rEmptyStr.indexOf(aEmptyStr, 0)); | |
| Expect.equals(-1, rEmptyStr.indexOf(a('a'), 0)); | |
| Expect.equals(1, rstr.indexOf(a('t'), 0)); | |
| Expect.equals(-1, rstr.indexOf(a('string'), 0)); | |
| final rstrstr = r('strstr'); | |
| final astr = a('str'); | |
| Expect.equals(0, rstrstr.indexOf(astr, 0)); | |
| Expect.equals(3, rstrstr.indexOf(astr, 1)); | |
| Expect.equals(3, rstrstr.indexOf(astr, 2)); | |
| Expect.equals(3, rstrstr.indexOf(astr, 3)); | |
| for (int i = 0; i < 5; i++) { | |
| if (i > rstr.length) { | |
| Expect.throws(() => rstr.indexOf(aEmptyStr, i)); | |
| } else { | |
| int result = rstr.indexOf(aEmptyStr, i); | |
| Expect.equals(i, result); | |
| } | |
| } | |
| final banana = a('banana'); | |
| var re = RegExp('an?'); | |
| Expect.equals(1, banana.indexOf(re)); | |
| Expect.equals(1, banana.indexOf(re, 0)); | |
| Expect.equals(1, banana.indexOf(re, 1)); | |
| Expect.equals(3, banana.indexOf(re, 2)); | |
| Expect.equals(3, banana.indexOf(re, 3)); | |
| Expect.equals(5, banana.indexOf(re, 4)); | |
| Expect.equals(5, banana.indexOf(re, 5)); | |
| Expect.equals(-1, banana.indexOf(re, 6)); | |
| Expect.throws(() => banana.indexOf(re, -1)); | |
| Expect.throws(() => banana.indexOf(re, 7)); | |
| re = RegExp('x?'); | |
| for (int i = 0; i <= rstr.length; i++) { | |
| Expect.equals(i, rstr.indexOf(re, i)); | |
| } | |
| } | |
| void testLastIndexOf(TestMode mode) { | |
| String r(String s) => getStr(s, Position.jsStringImplReceiver, mode); | |
| String a(String s) => getStr(s, Position.jsStringImplArgument, mode); | |
| final rstr = r('str'); | |
| final rEmptyStr = r(''); | |
| final aEmptyStr = a(''); | |
| Expect.equals(2, rstr.lastIndexOf(aEmptyStr, 2)); | |
| Expect.equals(0, rEmptyStr.lastIndexOf(aEmptyStr, 0)); | |
| Expect.equals(-1, rEmptyStr.lastIndexOf(a('a'), 0)); | |
| final astr = a('str'); | |
| final astring = a('string'); | |
| final at = a('t'); | |
| Expect.equals(1, rstr.lastIndexOf(at)); | |
| Expect.equals(0, rstr.lastIndexOf(astr, 2)); | |
| Expect.equals(2, rstr.lastIndexOf(a('r'), 2)); | |
| Expect.equals(-1, rstr.lastIndexOf(astring, 2)); | |
| final rstrstr = r('strstr'); | |
| Expect.equals(4, rstrstr.lastIndexOf(at, 5)); | |
| Expect.equals(3, rstrstr.lastIndexOf(astr, 5)); | |
| Expect.throws(() { | |
| rstr.lastIndexOf(astring, 5); | |
| }); | |
| Expect.equals(4, rstrstr.lastIndexOf(at, 5)); | |
| Expect.equals(3, rstrstr.lastIndexOf(astr, 5)); | |
| Expect.equals(5, rstrstr.lastIndexOf(a('r'))); | |
| Expect.equals(5, rstrstr.lastIndexOf(a('r'), null)); | |
| for (int i = 0; i < 5; i++) { | |
| if (i > rstr.length) { | |
| Expect.throws(() => rstr.indexOf(aEmptyStr, i)); | |
| } else { | |
| int result = rstr.lastIndexOf(aEmptyStr, i); | |
| Expect.equals(i, result); | |
| } | |
| } | |
| final banana = r('banana'); | |
| var re = RegExp('an?'); | |
| Expect.equals(5, banana.lastIndexOf(re)); | |
| Expect.equals(5, banana.lastIndexOf(re, 6)); | |
| Expect.equals(5, banana.lastIndexOf(re, 5)); | |
| Expect.equals(3, banana.lastIndexOf(re, 4)); | |
| Expect.equals(3, banana.lastIndexOf(re, 3)); | |
| Expect.equals(1, banana.lastIndexOf(re, 2)); | |
| Expect.equals(1, banana.lastIndexOf(re, 1)); | |
| Expect.equals(-1, banana.lastIndexOf(re, 0)); | |
| Expect.throws(() => banana.lastIndexOf(re, -1)); | |
| Expect.throws(() => banana.lastIndexOf(re, 7)); | |
| re = RegExp('x?'); | |
| for (int i = 0; i <= rstr.length; i++) { | |
| Expect.equals(i, rstr.indexOf(re, i)); | |
| } | |
| } | |
| void testHashCode() { | |
| for (final str in ['', 'foobar', 'hello world !']) { | |
| String a = str; | |
| String b = jsStringImpl(str); | |
| Expect.equals(a, b); | |
| Expect.equals(a.hashCode, b.hashCode); | |
| } | |
| } | |
| void testContains(TestMode mode) { | |
| String r(String s) => getStr(s, Position.jsStringImplReceiver, mode); | |
| String a(String s) => getStr(s, Position.jsStringImplArgument, mode); | |
| final rstr = r('str'); | |
| Expect.isTrue(rstr.contains(a('s'), 0)); | |
| Expect.isTrue(rstr.contains(a('st'), 0)); | |
| Expect.isTrue(rstr.contains(a('str'), 0)); | |
| Expect.isTrue(rstr.contains(a('t'), 0)); | |
| Expect.isTrue(rstr.contains(a('r'), 0)); | |
| Expect.isTrue(rstr.contains(a('tr'), 0)); | |
| Expect.isFalse(rstr.contains(a('sr'), 0)); | |
| Expect.isFalse(rstr.contains(a('string'), 0)); | |
| Expect.isTrue(rstr.contains(a(''), 0)); | |
| Expect.isTrue(r('').contains(a(''), 0)); | |
| Expect.isFalse(r('').contains(a('s'), 0)); | |
| } | |
| void testReplaceAll(TestMode mode) { | |
| String r(String s) => getStr(s, Position.jsStringImplReceiver, mode); | |
| String a(String s) => getStr(s, Position.jsStringImplArgument, mode); | |
| final aFrom = a('from'); | |
| final aTo = a('to'); | |
| final aEmptyStr = a(''); | |
| Expect.equals('AtoBtoCDtoE', r('AfromBfromCDfromE').replaceAll(aFrom, aTo)); | |
| Expect.equals('toABtoCDtoE', r('fromABfromCDfromE').replaceAll(aFrom, aTo)); | |
| Expect.equals( | |
| 'toABtoCDtoEto', | |
| r('fromABfromCDfromEfrom').replaceAll(aFrom, aTo), | |
| ); | |
| Expect.equals('ABC', r('ABC').replaceAll(aFrom, aTo)); | |
| Expect.equals('', r('').replaceAll(aFrom, aTo)); | |
| Expect.equals('fro', r('fro').replaceAll(aFrom, aTo)); | |
| Expect.equals('to', r('from').replaceAll(aFrom, aTo)); | |
| Expect.equals('to', r('to').replaceAll(aFrom, aTo)); | |
| Expect.equals('', r('from').replaceAll(aFrom, aEmptyStr)); | |
| Expect.equals('AB', r('AfromB').replaceAll(aFrom, aEmptyStr)); | |
| Expect.equals('to', r('').replaceAll(aEmptyStr, aTo)); | |
| Expect.equals('toAtoBtoCto', r('ABC').replaceAll(aEmptyStr, aTo)); | |
| Expect.equals('aXXcaXXdae', r('abcabdae').replaceAll(RegExp('b'), a('XX'))); | |
| Expect.equals( | |
| 'aXXcaXXdae', | |
| r('abcabdae').replaceAll(RegExpWrap('b'), a('XX')), | |
| ); | |
| } | |
| void testReplaceAllMapped(TestMode mode) { | |
| String r(String s) => getStr(s, Position.jsStringImplReceiver, mode); | |
| String a(String s) => getStr(s, Position.jsStringImplArgument, mode); | |
| String mark(Match m) => a('[${m[0]}]'); | |
| Expect.equals('a[b]ca[b]dae', r('abcabdae').replaceAllMapped(a('b'), mark)); | |
| Expect.equals('abcabdae', r('abcabdae').replaceAllMapped(a('f'), mark)); | |
| Expect.equals('', r('').replaceAllMapped(a('from'), mark)); | |
| Expect.equals('bcbde', r('abcabdae').replaceAllMapped(a('a'), (m) => a(''))); | |
| Expect.equals('[]', r('').replaceAllMapped(a(''), mark)); | |
| Expect.equals('[]A[]B[]C[]', r('ABC').replaceAllMapped(a(''), mark)); | |
| Expect.equals( | |
| 'aXXcaXXdae', | |
| r('abcabdae').replaceAllMapped(RegExp('b'), (_) => a('XX')), | |
| ); | |
| Expect.equals( | |
| 'aXXcaXXdae', | |
| r('abcabdae').replaceAllMapped(RegExpWrap('b'), (_) => a('XX')), | |
| ); | |
| } | |
| void testCompareTo(TestMode mode) { | |
| String r(String s) => getStr(s, Position.jsStringImplReceiver, mode); | |
| String a(String s) => getStr(s, Position.jsStringImplArgument, mode); | |
| Expect.equals(0, r('').compareTo(a(''))); | |
| Expect.equals(0, r('str').compareTo(a('str'))); | |
| Expect.equals(-1, r('str').compareTo(a('string'))); | |
| Expect.equals(1, r('string').compareTo(a('str'))); | |
| Expect.equals(1, r('string').compareTo(a(''))); | |
| Expect.equals(-1, r('').compareTo(a('string'))); | |
| } | |
| void testCharCodes() { | |
| void test(String str) { | |
| final list = str.codeUnits; | |
| Expect.equals(str.length, list.length); | |
| for (int i = 0; i < str.length; i++) { | |
| Expect.equals(str.codeUnitAt(i), list[i]); | |
| } | |
| } | |
| test(jsStringImpl('abc')); | |
| test(jsStringImpl('')); | |
| test(jsStringImpl(' ')); | |
| } | |
| void testRepeat() { | |
| List<String> testStrings = [ | |
| '', | |
| '\x00', | |
| 'a', | |
| 'ab', | |
| '\x80', | |
| '\xff', | |
| '\u2028', | |
| 'abcdef\u2028', | |
| '\u{10002}', | |
| 'abcdef\u{10002}', | |
| ].map(jsStringImpl).toList(); | |
| List<int> counts = [0, 1, 2, 10]; | |
| void testRepeat(String str, int repeat) { | |
| String expect; | |
| if (repeat <= 0) { | |
| expect = ''; | |
| } else if (repeat == 1) { | |
| expect = str; | |
| } else { | |
| StringBuffer buf = StringBuffer(); | |
| for (int i = 0; i < repeat; i++) { | |
| buf.write(str); | |
| } | |
| expect = buf.toString(); | |
| } | |
| String actual = str * repeat; | |
| Expect.equals(expect, actual, '$str#${str.length} * $repeat'); | |
| } | |
| for (String str in testStrings) { | |
| for (int repeat in counts) { | |
| testRepeat(str, repeat); | |
| } | |
| } | |
| } | |
| void testPadLeft(TestMode mode) { | |
| String r(String s) => getStr(s, Position.jsStringImplReceiver, mode); | |
| String a(String s) => getStr(s, Position.jsStringImplArgument, mode); | |
| Expect.equals(' 1', r('1').padLeft(5, a(' '))); | |
| Expect.equals(' \u{10002}', r('\u{10002}').padLeft(5, a(' '))); | |
| Expect.equals('', r('').padLeft(0, a('a'))); | |
| Expect.equals('a', r('').padLeft(1, a('a'))); | |
| Expect.equals('', r('').padLeft(-2, a('a'))); | |
| Expect.equals('xyzxyzxyzxyzxyz', r('').padLeft(5, a('xyz'))); | |
| Expect.equals('xyzxyzxyzxyza', r('a').padLeft(5, a('xyz'))); | |
| Expect.equals( | |
| '\u{10002}\u{10002}\u{10002}aa', | |
| r('aa').padLeft(5, a('\u{10002}')), | |
| ); | |
| } | |
| void testPadRight(TestMode mode) { | |
| String r(String s) => getStr(s, Position.jsStringImplReceiver, mode); | |
| String a(String s) => getStr(s, Position.jsStringImplArgument, mode); | |
| Expect.equals('1 ', r('1').padRight(5, a(' '))); | |
| Expect.equals('\u{10002} ', r('\u{10002}').padRight(5, a(' '))); | |
| Expect.equals('', r('').padRight(0, a('a'))); | |
| Expect.equals('a', r('').padRight(1, a('a'))); | |
| Expect.equals('', r('').padRight(-2, a('a'))); | |
| Expect.equals('xyzxyzxyzxyzxyz', r('').padRight(5, a('xyz'))); | |
| Expect.equals('axyzxyzxyzxyz', r('a').padRight(5, a('xyz'))); | |
| Expect.equals( | |
| 'aa\u{10002}\u{10002}\u{10002}', | |
| r('aa').padRight(5, a('\u{10002}')), | |
| ); | |
| Expect.equals('a', r('a').padRight(10, a(''))); | |
| } | |
| // Characters with Whitespace property (Unicode 6.3). | |
| // 0009..000D ; White_Space # Cc <control-0009>..<control-000D> | |
| // 0020 ; White_Space # Zs SPACE | |
| // 0085 ; White_Space # Cc <control-0085> | |
| // 00A0 ; White_Space # Zs NO-BREAK SPACE | |
| // 1680 ; White_Space # Zs OGHAM SPACE MARK | |
| // 2000..200A ; White_Space # Zs EN QUAD..HAIR SPACE | |
| // 2028 ; White_Space # Zl LINE SEPARATOR | |
| // 2029 ; White_Space # Zp PARAGRAPH SEPARATOR | |
| // 202F ; White_Space # Zs NARROW NO-BREAK SPACE | |
| // 205F ; White_Space # Zs MEDIUM MATHEMATICAL SPACE | |
| // 3000 ; White_Space # Zs IDEOGRAPHIC SPACE | |
| // And BOM: | |
| // FEFF ; Byte order mark. | |
| const whitespace = const [ | |
| 0x09, | |
| 0x0A, | |
| 0x0B, | |
| 0x0C, | |
| 0x0D, | |
| 0x20, | |
| 0x85, | |
| 0xA0, | |
| 0x1680, | |
| 0x2000, | |
| 0x2001, | |
| 0x2002, | |
| 0x2003, | |
| 0x2004, | |
| 0x2005, | |
| 0x2006, | |
| 0x2007, | |
| 0x2008, | |
| 0x2009, | |
| 0x200A, | |
| 0x2028, | |
| 0x2029, | |
| 0x202F, | |
| 0x205F, | |
| 0x3000, | |
| 0xFEFF, | |
| ]; | |
| void testTrimTest() { | |
| String s(String a) => jsStringImpl(a); | |
| Expect.equals('', s(' ').trim()); | |
| Expect.equals('', s(' ').trim()); | |
| Expect.equals('left', s(' left').trim()); | |
| Expect.equals('right', s('right ').trim()); | |
| Expect.equals('', ' \t \n \r '.trim()); | |
| for (final ws in whitespace) { | |
| Expect.equals('', s(String.fromCharCode(ws)).trim()); | |
| } | |
| } | |
| void testTrimLeftTest() { | |
| String s(String a) => jsStringImpl(a); | |
| Expect.equals('', s(' ').trimLeft()); | |
| Expect.equals('', s(' ').trimLeft()); | |
| Expect.equals('left', s(' left').trimLeft()); | |
| Expect.equals('right ', s('right ').trimLeft()); | |
| Expect.equals('', ' \t \n \r '.trimLeft()); | |
| for (final ws in whitespace) { | |
| Expect.equals('', s(String.fromCharCode(ws)).trimLeft()); | |
| } | |
| } | |
| void testTrimRightTest() { | |
| String s(String a) => jsStringImpl(a); | |
| Expect.equals('', s(' ').trimRight()); | |
| Expect.equals('', s(' ').trimRight()); | |
| Expect.equals(' left', s(' left').trimRight()); | |
| Expect.equals('right', s('right ').trimRight()); | |
| Expect.equals('', ' \t \n \r '.trimRight()); | |
| for (final ws in whitespace) { | |
| Expect.equals('', s(String.fromCharCode(ws)).trimRight()); | |
| } | |
| } | |
| void testMatch(TestMode mode) { | |
| String r(String s) => getStr(s, Position.jsStringImplReceiver, mode); | |
| String a(String s) => getStr(s, Position.jsStringImplArgument, mode); | |
| String astr = a('this is a string with hello here and hello there'); | |
| // No match: | |
| { | |
| final helloPattern = r('with (hello)'); | |
| final matches = helloPattern.allMatches(astr); | |
| Expect.isFalse(matches.iterator.moveNext()); | |
| } | |
| int check(String helloPattern, Iterable<Match> matches, String expected) { | |
| int count = 0; | |
| int start = 0; | |
| for (final match in matches) { | |
| Expect.equals(astr.indexOf(expected, start), match.start); | |
| Expect.equals( | |
| astr.indexOf(expected, start) + helloPattern.length, | |
| match.end, | |
| ); | |
| Expect.equals(helloPattern, match.pattern); | |
| Expect.equals(astr, match.input); | |
| Expect.equals(helloPattern, match[0]); | |
| Expect.equals(0, match.groupCount); | |
| count++; | |
| start = match.end; | |
| } | |
| return count; | |
| } | |
| // One match: | |
| { | |
| String helloPattern = r('with hello'); | |
| Iterable<Match> matches = helloPattern.allMatches(astr); | |
| final count = check(helloPattern, matches, a('with')); | |
| Expect.equals(1, count); | |
| } | |
| // Two matches: | |
| { | |
| String helloPattern = r('hello'); | |
| Iterable<Match> matches = helloPattern.allMatches(astr); | |
| final count = check(helloPattern, matches, a('hello')); | |
| Expect.equals(2, count); | |
| } | |
| // Empty pattern: | |
| { | |
| String pattern = r(''); | |
| Iterable<Match> matches = pattern.allMatches(astr); | |
| Expect.isTrue(matches.iterator.moveNext()); | |
| } | |
| // Empty string: | |
| { | |
| String pattern = r('foo'); | |
| String str = a(''); | |
| Iterable<Match> matches = pattern.allMatches(str); | |
| Expect.isFalse(matches.iterator.moveNext()); | |
| } | |
| // Empty pattern and string: | |
| { | |
| String pattern = r(''); | |
| String str = a(''); | |
| Iterable<Match> matches = pattern.allMatches(str); | |
| Expect.isTrue(matches.iterator.moveNext()); | |
| } | |
| // Match as prefix: | |
| { | |
| String pattern = r('an'); | |
| String str = a('banana'); | |
| Expect.isNull(pattern.matchAsPrefix(str)); | |
| Expect.isNull(pattern.matchAsPrefix(str, 0)); | |
| var m = pattern.matchAsPrefix(str, 1)!; | |
| Expect.equals('an', m[0]); | |
| Expect.equals(1, m.start); | |
| Expect.isNull(pattern.matchAsPrefix(str, 2)); | |
| m = pattern.matchAsPrefix(str, 3)!; | |
| Expect.equals('an', m[0]); | |
| Expect.equals(3, m.start); | |
| Expect.isNull(pattern.matchAsPrefix(str, 4)); | |
| Expect.isNull(pattern.matchAsPrefix(str, 5)); | |
| Expect.isNull(pattern.matchAsPrefix(str, 6)); | |
| Expect.throws(() => pattern.matchAsPrefix(str, -1)); | |
| Expect.throws(() => pattern.matchAsPrefix(str, 7)); | |
| } | |
| // Start: | |
| { | |
| String p = r('ass'); | |
| String s = a('assassin'); | |
| Expect.equals(2, p.allMatches(s).length); | |
| Expect.equals(2, p.allMatches(s, 0).length); | |
| Expect.equals(1, p.allMatches(s, 1).length); | |
| Expect.equals(0, p.allMatches(s, 4).length); | |
| Expect.equals(0, p.allMatches(s, s.length).length); | |
| Expect.throws(() => p.allMatches(s, -1)); | |
| Expect.throws(() => p.allMatches(s, s.length + 1)); | |
| } | |
| } | |
| void testSubstring() { | |
| String s(String a) => jsStringImpl(a); | |
| final emptyStr = s(''); | |
| Expect.equals(emptyStr.substring(0), ''); | |
| Expect.throwsRangeError(() => emptyStr.substring(1)); | |
| Expect.throwsRangeError(() => emptyStr.substring(-1)); | |
| final abcStr = s('abc'); | |
| Expect.equals(abcStr.substring(0), 'abc'); | |
| Expect.equals(abcStr.substring(1), 'bc'); | |
| Expect.equals(abcStr.substring(2), 'c'); | |
| Expect.equals(abcStr.substring(3), ''); | |
| Expect.throwsRangeError(() => abcStr.substring(4)); | |
| Expect.throwsRangeError(() => abcStr.substring(-1)); | |
| // Test that providing null goes to the end. | |
| Expect.equals(emptyStr.substring(0, null), ''); | |
| Expect.throwsRangeError(() => emptyStr.substring(1, null)); | |
| Expect.throwsRangeError(() => emptyStr.substring(-1, null)); | |
| Expect.equals(abcStr.substring(0, null), 'abc'); | |
| Expect.equals(abcStr.substring(1, null), 'bc'); | |
| Expect.equals(abcStr.substring(2, null), 'c'); | |
| Expect.equals(abcStr.substring(3, null), ''); | |
| Expect.throwsRangeError(() => abcStr.substring(4, null)); | |
| Expect.throwsRangeError(() => abcStr.substring(-1, null)); | |
| } | |
| void testSplitString(TestMode mode) { | |
| String r(String s) => getStr(s, Position.jsStringImplReceiver, mode); | |
| String a(String s) => getStr(s, Position.jsStringImplArgument, mode); | |
| Expect.listEquals(['a', 'b', 'c'], r('a b c').split(a(' '))); | |
| Expect.listEquals(['a', 'b', 'c'], r('adbdc').split(a('d'))); | |
| Expect.listEquals(['abc'], r('abc').split(a(' '))); | |
| Expect.listEquals(['a', 'b', 'c'], a('abc').split(a(''))); | |
| } | |
| void testSplit(TestMode mode, Pattern p(String s)) { | |
| String r(String s) => getStr(s, Position.jsStringImplReceiver, mode); | |
| Expect.listEquals(['a', 'b', 'c'], r('a b c').split(p(' '))); | |
| Expect.listEquals(['a', 'b', 'c'], r('adbdc').split(p(r'[dz]'))); | |
| } | |
| void testSplitRegExp(TestMode mode) => testSplit(mode, (s) => RegExp(s)); | |
| void testSplitUserPattern(TestMode mode) => | |
| testSplit(mode, (s) => RegExpWrap(s)); | |
| void testCase() { | |
| String s(String a) => jsStringImpl(a); | |
| Expect.equals('ABC', s('aBc').toUpperCase()); | |
| Expect.equals('abc', s('AbC').toLowerCase()); | |
| } | |
| void testReplace(TestMode mode) { | |
| String r(String s) => getStr(s, Position.jsStringImplReceiver, mode); | |
| String a(String s) => getStr(s, Position.jsStringImplArgument, mode); | |
| // Test replaceFirst | |
| Expect.equals( | |
| 'AtoBtoCDtoE', | |
| r('AfromBtoCDtoE').replaceFirst(a('from'), a('to')), | |
| ); | |
| Expect.equals( | |
| 'toABtoCDtoE', | |
| r('fromABtoCDtoE').replaceFirst(a('from'), a('to')), | |
| ); | |
| Expect.equals( | |
| 'toABtoCDtoEto', | |
| r('fromABtoCDtoEto').replaceFirst(a('from'), a('to')), | |
| ); | |
| Expect.equals('ABC', r('ABC').replaceFirst(a('from'), a('to'))); | |
| Expect.equals('', r('').replaceFirst(a('from'), a('to'))); | |
| Expect.equals( | |
| 'foo-AAA-foo-bar', | |
| r('foo-bar-foo-bar').replaceFirst(a('bar'), a('AAA'), 4), | |
| ); | |
| Expect.equals( | |
| 'foo-bar-foo-bar', | |
| r('foo-bar-foo-bar').replaceFirst(RegExp(r'^foo'), a(''), 8), | |
| ); | |
| Expect.throwsRangeError(() => r('hello').replaceFirst(a('h'), a('X'), -1)); | |
| Expect.throwsRangeError(() => r('hello').replaceFirst(a('h'), a('X'), 6)); | |
| // Test replaceFirstMapped. | |
| Expect.equals( | |
| 'AtoBtoCDtoE', | |
| r('AfromBtoCDtoE').replaceFirstMapped(a('from'), (_) => a('to')), | |
| ); | |
| Expect.equals('ABC', r('ABC').replaceFirstMapped(a('from'), (_) => a('to'))); | |
| Expect.equals('', r('').replaceFirstMapped(a('from'), (_) => a('to'))); | |
| Expect.equals( | |
| 'foo-AAA-foo-bar', | |
| r('foo-bar-foo-bar').replaceFirstMapped(a('bar'), (_) => a('AAA'), 4), | |
| ); | |
| Expect.equals( | |
| 'foo-bar-foo-bar', | |
| r('foo-bar-foo-bar').replaceFirstMapped(RegExp(r'^foo'), (_) => a(''), 8), | |
| ); | |
| Expect.throwsRangeError( | |
| () => r('hello').replaceFirstMapped(a('h'), (_) => a('X'), -1), | |
| ); | |
| Expect.throwsRangeError( | |
| () => r('hello').replaceFirstMapped(a('h'), (_) => a('X'), 6), | |
| ); | |
| Expect.equals( | |
| 'foo-BAR-foo-bar', | |
| r('foo-bar-foo-bar') | |
| .replaceFirstMapped(a('bar'), (v) => a(v[0]!.toUpperCase())), | |
| ); | |
| for (final string in [r(''), r('x'), r('foo'), r('x\u2000z')]) { | |
| for (final replacement in [a(''), a('foo'), a(string)]) { | |
| for (int start = 0; start <= string.length; start++) { | |
| var expect; | |
| for (int end = start; end <= string.length; end++) { | |
| expect = | |
| string.substring(0, start) + replacement + string.substring(end); | |
| Expect.equals( | |
| expect, | |
| string.replaceRange(start, end, replacement), | |
| "'$string'[$start:$end]='$replacement'", | |
| ); | |
| } | |
| // Reuse expect from 'end == string.length' case when omitting end. | |
| Expect.equals( | |
| expect, | |
| string.replaceRange(start, null, replacement), | |
| "'$string'[$start:]='$replacement'", | |
| ); | |
| } | |
| } | |
| Expect.throws(() => string.replaceRange(-1, 0, 'x')); | |
| Expect.throws(() => string.replaceRange(0, string.length + 1, 'x')); | |
| } | |
| } | |
| void testSplitMapJoin(TestMode mode) { | |
| String r(String s) => getStr(s, Position.jsStringImplReceiver, mode); | |
| String a(String s) => getStr(s, Position.jsStringImplArgument, mode); | |
| String mark(Match m) => a('[${m[0]}]'); | |
| String rest(String s) => a('<${s}>'); | |
| Expect.equals( | |
| '<a>[b]<ca>[b]<dae>', | |
| r('abcabdae').splitMapJoin(a('b'), onMatch: mark, onNonMatch: rest), | |
| ); | |
| Expect.equals( | |
| '<abcabdae>', | |
| r('abcabdae').splitMapJoin(a('f'), onMatch: mark, onNonMatch: rest), | |
| ); | |
| Expect.equals( | |
| '<>', | |
| r('').splitMapJoin(a('from'), onMatch: mark, onNonMatch: rest), | |
| ); | |
| Expect.equals( | |
| '<>[]<>', | |
| r('').splitMapJoin(a(''), onMatch: mark, onNonMatch: rest), | |
| ); | |
| Expect.equals( | |
| '<>[]<A>[]<B>[]<C>[]<>', | |
| r('ABC').splitMapJoin(a(''), onMatch: mark, onNonMatch: rest), | |
| ); | |
| Expect.equals( | |
| '[a]bc[a]bd[a]e', | |
| r('abcabdae').splitMapJoin(a('a'), onMatch: mark), | |
| ); | |
| Expect.equals( | |
| '<>a<bc>a<bd>a<e>', | |
| r('abcabdae').splitMapJoin(a('a'), onNonMatch: rest), | |
| ); | |
| } | |
| void main() { | |
| for (final mode in [ | |
| TestMode.jsStringImplReceiver, | |
| TestMode.jsStringImplArgument, | |
| TestMode.jsStringImplReceiverAndArguments, | |
| ]) { | |
| testLength(mode); | |
| testConcat(mode); | |
| testEquals(mode); | |
| testEndsWith(mode); | |
| testStartsWith(mode); | |
| testIndexOf(mode); | |
| testLastIndexOf(mode); | |
| testContains(mode); | |
| testReplaceAll(mode); | |
| testReplaceAllMapped(mode); | |
| testCompareTo(mode); | |
| testPadLeft(mode); | |
| testPadRight(mode); | |
| testMatch(mode); | |
| testSplitString(mode); | |
| testSplitRegExp(mode); | |
| testSplitUserPattern(mode); | |
| testReplace(mode); | |
| testSplitMapJoin(mode); | |
| } | |
| testOutOfRange(); | |
| testIndex(); | |
| testCodeUnitAt(); | |
| testHashCode(); | |
| testCharCodes(); | |
| testRepeat(); | |
| testTrimTest(); | |
| testTrimLeftTest(); | |
| testTrimRightTest(); | |
| testSubstring(); | |
| testCase(); | |
| } | |
| // A Pattern implementation with the same capabilities as a RegExp, but not | |
| // directly recognizable as a RegExp. | |
| class RegExpWrap implements Pattern { | |
| final RegExp regexp; | |
| RegExpWrap(String source) : regexp = RegExp(source); | |
| Iterable<Match> allMatches(String string, [int start = 0]) => | |
| regexp.allMatches(string, start); | |
| Match? matchAsPrefix(String string, [int start = 0]) => | |
| regexp.matchAsPrefix(string, start); | |
| String toString() => 'Wrap(/${regexp.pattern}/)'; | |
| } |