diff --git a/pkgs/characters/.travis.yml b/pkgs/characters/.travis.yml index 8f0ecf66..afa42c9f 100644 --- a/pkgs/characters/.travis.yml +++ b/pkgs/characters/.travis.yml @@ -1,14 +1,38 @@ language: dart dart: - dev -- stable -# Only building master means that we don't run two builds for each pull request. -dart_task: -- test: --platform vm,chrome -- dartanalyzer -- dartfmt + +jobs: + include: + - stage: analyze_and_format + name: "Analyze lib/ (no experiment flag)" + dart: dev + os: linux + script: dartanalyzer --fatal-warnings --fatal-infos lib/ + - stage: analyze_and_format + name: "Analyze (with experiment flag)" + dart: dev + os: linux + script: dartanalyzer --enable-experiment=non-nullable --fatal-warnings --fatal-infos . + - stage: analyze_and_format + name: "Format" + dart: dev + os: linux + script: dartfmt -n --set-exit-if-changed . + - stage: test + name: "Vm Tests" + dart: dev + os: linux + script: pub run --enable-experiment=non-nullable test -p vm + - stage: test + name: "Web Tests" + dart: dev + os: linux + script: pub run --enable-experiment=non-nullable test -p chrome + branches: - only: [master] + only: [master, null_safety] + cache: directories: - $HOME/.pub-cache diff --git a/pkgs/characters/CHANGELOG.md b/pkgs/characters/CHANGELOG.md index b8a7dce6..458b8c84 100644 --- a/pkgs/characters/CHANGELOG.md +++ b/pkgs/characters/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.1.0-nullsafety + +* Make package null safe. + ## 1.0.0 * Core APIs deemed stable; package version set to 1.0.0. diff --git a/pkgs/characters/analysis_options.yaml b/pkgs/characters/analysis_options.yaml index 3bad72be..d34d1f0d 100644 --- a/pkgs/characters/analysis_options.yaml +++ b/pkgs/characters/analysis_options.yaml @@ -5,3 +5,5 @@ analyzer: annotate_overrides: ignore prefer_single_quotes: ignore use_function_type_syntax_for_parameters: ignore + enable-experiment: + - non-nullable diff --git a/pkgs/characters/lib/src/characters.dart b/pkgs/characters/lib/src/characters.dart index 676716b5..70b40ebe 100644 --- a/pkgs/characters/lib/src/characters.dart +++ b/pkgs/characters/lib/src/characters.dart @@ -47,14 +47,14 @@ abstract class Characters implements Iterable { /// as well as controlling the iteration in more detail. CharacterRange get iteratorAtEnd; - /// Whether [Character] is an element of this sequence of - /// characters. + /// Whether [other] is an element of this sequence of + /// others. /// - /// Returns false if [Character] is not a string containing + /// Returns false if [other] is not a string containing /// a single character, /// because then it is not a single element of this [Iterable] /// of characters. - bool contains(Object Character); + bool contains(Object? other); /// Whether this sequence of characters contains [other] /// as a subsequence. @@ -79,13 +79,14 @@ abstract class Characters implements Iterable { /// Returns a [CharacterRange] containing the first occurrence of /// [characters] in this string. /// Returns `null` if there is no such occurrence. - CharacterRange /*?*/ findFirst(Characters characters); + CharacterRange? findFirst(Characters characters); /// Finds the last occurrence of [characters]. /// /// Returns a [CharacterRange] containing the last occurrence of - /// [characters]. Returns `null` if there is no such occurrence, - CharacterRange /*?*/ findLast(Characters characters); + /// [characters]. + /// Returns `null` if there is no such occurrence, + CharacterRange? findLast(Characters characters); /// Eagerly selects a subset of the characters. /// @@ -132,7 +133,7 @@ abstract class Characters implements Iterable { /// of characters. Characters skipWhile(bool Function(String) test); - /// Eagerly selects a leading sequnce of characters. + /// Eagerly selects a leading sequence of characters. /// /// Checks each character, from first to last, against [test], /// until one is found whwere [test] returns `false`. @@ -143,7 +144,7 @@ abstract class Characters implements Iterable { /// is returned. Characters takeWhile(bool Function(String) test); - /// Eagerly selects a leading sequnce of characters. + /// Eagerly selects a leading sequence of characters. /// /// Checks each character, from last to first, against [test], /// until one is found whwere [test] returns `false`. @@ -679,7 +680,7 @@ abstract class CharacterRange implements Iterator { /// /// Returns `null` if there are no occurrences of [pattern] /// in the current range. - CharacterRange /*?*/ replaceAll(Characters pattern, Characters replacement); + CharacterRange? replaceAll(Characters pattern, Characters replacement); /// Splits the current range of characters at each occurrence of [pattern]. /// @@ -737,7 +738,7 @@ abstract class CharacterRange implements Iterator { /// /// Returns `null` if there are no occurrences of [pattern] /// in the current range. - CharacterRange /*?*/ replaceFirst(Characters pattern, Characters replacement); + CharacterRange? replaceFirst(Characters pattern, Characters replacement); /// Whether the current range starts with [characters]. /// diff --git a/pkgs/characters/lib/src/characters_impl.dart b/pkgs/characters/lib/src/characters_impl.dart index 4f474c86..dde34d5c 100644 --- a/pkgs/characters/lib/src/characters_impl.dart +++ b/pkgs/characters/lib/src/characters_impl.dart @@ -69,7 +69,7 @@ class StringCharacters extends Iterable implements Characters { @override Iterable whereType() { - Iterable self = this; + Iterable self = this; if (self is Iterable) { return self.map((x) => x); } @@ -83,7 +83,7 @@ class StringCharacters extends Iterable implements Characters { } @override - String lastWhere(bool test(String element), {String orElse()}) { + String lastWhere(bool test(String element), {String orElse()?}) { int cursor = string.length; var brk = BackBreaks(string, cursor, 0, stateEoTNoBreak); int next = 0; @@ -114,7 +114,7 @@ class StringCharacters extends Iterable implements Characters { } @override - bool contains(Object other) { + bool contains(Object? other) { if (other is String) { if (other.isEmpty) return false; int next = Breaks(other, 0, other.length, stateSoTNoBreak).nextBreak(); @@ -375,14 +375,14 @@ class StringCharacters extends Iterable implements Characters { String toString() => string; @override - CharacterRange findFirst(Characters characters) { + CharacterRange? findFirst(Characters characters) { var range = _rangeAll; if (range.collapseToFirst(characters)) return range; return null; } @override - CharacterRange findLast(Characters characters) { + CharacterRange? findLast(Characters characters) { var range = _rangeAll; if (range.collapseToLast(characters)) return range; return null; @@ -408,7 +408,7 @@ class StringCharacterRange implements CharacterRange { /// The [current] value is created lazily and cached to avoid repeated /// or unnecessary string allocation. - String _currentCache; + String? _currentCache; StringCharacterRange(String string) : this._(string, 0, 0); StringCharacterRange._(this._string, this._start, this._end); @@ -778,8 +778,7 @@ class StringCharacterRange implements CharacterRange { } @override - CharacterRange /*?*/ replaceFirst( - Characters pattern, Characters replacement) { + CharacterRange? replaceFirst(Characters pattern, Characters replacement) { String patternString = pattern.string; String replacementString = replacement.string; String replaced; @@ -799,7 +798,7 @@ class StringCharacterRange implements CharacterRange { } @override - CharacterRange /*?*/ replaceAll(Characters pattern, Characters replacement) { + CharacterRange? replaceAll(Characters pattern, Characters replacement) { var patternString = pattern.string; var replacementString = replacement.string; if (patternString.isEmpty) { @@ -811,7 +810,7 @@ class StringCharacterRange implements CharacterRange { if (_start == _end) return null; int start = 0; int cursor = _start; - StringBuffer buffer; + StringBuffer? buffer; while ((cursor = _indexOf(_string, patternString, cursor, _end)) >= 0) { (buffer ??= StringBuffer()) ..write(_string.substring(start, cursor)) diff --git a/pkgs/characters/pubspec.yaml b/pkgs/characters/pubspec.yaml index 8863486e..c0b1dc46 100644 --- a/pkgs/characters/pubspec.yaml +++ b/pkgs/characters/pubspec.yaml @@ -1,10 +1,96 @@ name: characters -version: 1.0.0 +version: 1.1.0-nullsafety description: String replacement with operations that are Unicode/grapheme cluster aware. homepage: https://www.github.com/dart-lang/characters environment: - sdk: ">=2.6.0 <3.0.0" + # This must remain a tight constraint (only allow dev versions) until nnbd is + # stable. + sdk: '>=2.9.0-18.0 <2.9.0' + dev_dependencies: test: "^1.6.0" pedantic: ^1.9.0 + +dependency_overrides: + async: + git: + url: git://github.com/dart-lang/async.git + ref: null_safety + boolean_selector: + git: + url: git://github.com/dart-lang/boolean_selector.git + ref: null_safety + charcode: + git: + url: git://github.com/dart-lang/charcode.git + ref: null_safety + collection: + git: git://github.com/dart-lang/collection.git + js: + git: + url: git://github.com/dart-lang/sdk.git + path: pkg/js + matcher: + git: + url: git://github.com/dart-lang/matcher.git + ref: null_safety + meta: + git: + url: git://github.com/dart-lang/sdk.git + path: pkg/meta + path: + git: + url: git://github.com/dart-lang/path.git + ref: null_safety + pedantic: + git: + url: git://github.com/dart-lang/pedantic.git + ref: null_safety + pool: + git: + url: git://github.com/dart-lang/pool.git + ref: null_safety + source_maps: + git: + url: git://github.com/dart-lang/source_maps.git + ref: null_safety + source_map_stack_trace: + git: + url: git://github.com/dart-lang/source_map_stack_trace.git + ref: null_safety + source_span: + git: + url: git://github.com/dart-lang/source_span.git + ref: null_safety + stack_trace: + git: + url: git://github.com/dart-lang/stack_trace.git + ref: null_safety + stream_channel: + git: + url: git://github.com/dart-lang/stream_channel.git + ref: null_safety + string_scanner: + git: + url: git://github.com/dart-lang/string_scanner.git + ref: null_safety + term_glyph: + git: + url: git://github.com/dart-lang/term_glyph.git + ref: null_safety + test_api: + git: + url: git://github.com/dart-lang/test.git + ref: null_safety + path: pkgs/test_api + test_core: + git: + url: git://github.com/dart-lang/test.git + ref: null_safety + path: pkgs/test_core + test: + git: + url: git://github.com/dart-lang/test.git + ref: null_safety + path: pkgs/test diff --git a/pkgs/characters/test/characters_test.dart b/pkgs/characters/test/characters_test.dart index f34e6ecf..d7f057b1 100644 --- a/pkgs/characters/test/characters_test.dart +++ b/pkgs/characters/test/characters_test.dart @@ -1,668 +1,13 @@ -// Copyright (c) 2019, 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. - -import "dart:math"; - -import "package:test/test.dart"; - -import "package:characters/characters.dart"; - -import "src/unicode_tests.dart"; -import "src/unicode_grapheme_tests.dart"; -import "src/various_tests.dart"; - -Random random; - -void main([List args]) { - // Ensure random seed is part of every test failure message, - // and that it can be reapplied for testing. - var seed = (args != null && args.isNotEmpty) - ? int.parse(args[0]) - : Random().nextInt(0x3FFFFFFF); - random = Random(seed); - group("[Random Seed: $seed]", tests); - group("characters", () { - test("operations", () { - var flag = "\u{1F1E9}\u{1F1F0}"; // Regional Indicators "DK". - var string = "Hi $flag!"; - expect(string.length, 8); - var cs = gc(string); - expect(cs.length, 5); - expect(cs.toList(), ["H", "i", " ", flag, "!"]); - expect(cs.skip(2).toString(), " $flag!"); - expect(cs.skipLast(2).toString(), "Hi "); - expect(cs.take(2).toString(), "Hi"); - expect(cs.takeLast(2).toString(), "$flag!"); - - expect(cs.contains("\u{1F1E9}"), false); - expect(cs.contains(flag), true); - expect(cs.contains("$flag!"), false); - expect(cs.containsAll(gc("$flag!")), true); - - expect(cs.takeWhile((x) => x != " ").toString(), "Hi"); - expect(cs.takeLastWhile((x) => x != " ").toString(), "$flag!"); - expect(cs.skipWhile((x) => x != " ").toString(), " $flag!"); - expect(cs.skipLastWhile((x) => x != " ").toString(), "Hi "); - - expect(cs.findFirst(gc("")).moveBack(), false); - expect(cs.findFirst(gc(flag)).current, flag); - expect(cs.findLast(gc(flag)).current, flag); - expect(cs.iterator.moveNext(), true); - expect(cs.iterator.moveBack(), false); - expect((cs.iterator..moveNext()).current, "H"); - expect(cs.iteratorAtEnd.moveNext(), false); - expect(cs.iteratorAtEnd.moveBack(), true); - expect((cs.iteratorAtEnd..moveBack()).current, "!"); - }); - - testParts(gc("a"), gc("b"), gc("c"), gc("d"), gc("e")); - - // Composite pictogram example, from https://en.wikipedia.org/wiki/Zero-width_joiner. - var flag = "\u{1f3f3}"; // U+1F3F3, Flag, waving. Category Pictogram. - var white = "\ufe0f"; // U+FE0F, Variant selector 16. Category Extend. - var zwj = "\u200d"; // U+200D, ZWJ - var rainbow = "\u{1f308}"; // U+1F308, Rainbow. Category Pictogram - - testParts(gc("$flag$white$zwj$rainbow"), gc("$flag$white"), gc("$rainbow"), - gc("$flag$zwj$rainbow"), gc("!")); - }); -} - -void tests() { - test("empty", () { - expectGC(gc(""), []); - }); - group("gc-ASCII", () { - for (var text in [ - "", - "A", - "123456abcdefab", - ]) { - test('"$text"', () { - expectGC(gc(text), charsOf(text)); - }); - } - test("CR+NL", () { - expectGC(gc("a\r\nb"), ["a", "\r\n", "b"]); - expectGC(gc("a\n\rb"), ["a", "\n", "\r", "b"]); - }); - }); - group("Non-ASCII single-code point", () { - for (var text in [ - "à la mode", - "rødgrød-æble-ål", - ]) { - test('"$text"', () { - expectGC(gc(text), charsOf(text)); - }); - } - }); - group("Combining marks", () { - var text = "a\u0300 la mode"; - test('"$text"', () { - expectGC(gc(text), ["a\u0300", " ", "l", "a", " ", "m", "o", "d", "e"]); - }); - var text2 = "æble-a\u030Al"; - test('"$text2"', () { - expectGC(gc(text2), ["æ", "b", "l", "e", "-", "a\u030A", "l"]); - }); - }); - - group("Regional Indicators", () { - test('"🇦🇩🇰🇾🇪🇸"', () { - // Andorra, Cayman Islands, Spain. - expectGC(gc("🇦🇩🇰🇾🇪🇸"), ["🇦🇩", "🇰🇾", "🇪🇸"]); - }); - test('"X🇦🇩🇰🇾🇪🇸"', () { - // Other, Andorra, Cayman Islands, Spain. - expectGC(gc("X🇦🇩🇰🇾🇪🇸"), ["X", "🇦🇩", "🇰🇾", "🇪🇸"]); - }); - test('"🇩🇰🇾🇪🇸"', () { - // Denmark, Yemen, unmatched S. - expectGC(gc("🇩🇰🇾🇪🇸"), ["🇩🇰", "🇾🇪", "🇸"]); - }); - test('"X🇩🇰🇾🇪🇸"', () { - // Other, Denmark, Yemen, unmatched S. - expectGC(gc("X🇩🇰🇾🇪🇸"), ["X", "🇩🇰", "🇾🇪", "🇸"]); - }); - }); - - group("Hangul", () { - // Individual characters found on Wikipedia. Not expected to make sense. - test('"읍쌍된밟"', () { - expectGC(gc("읍쌍된밟"), ["읍", "쌍", "된", "밟"]); - }); - }); - - group("Unicode test", () { - for (var gcs in splitTests) { - test("[${testDescription(gcs)}]", () { - expectGC(gc(gcs.join()), gcs); - }); - } - }); - - group("Emoji test", () { - for (var gcs in emojis) { - test("[${testDescription(gcs)}]", () { - expectGC(gc(gcs.join()), gcs); - }); - } - }); - - group("Zalgo test", () { - for (var gcs in zalgo) { - test("[${testDescription(gcs)}]", () { - expectGC(gc(gcs.join()), gcs); - }); - } - }); -} - -// Converts text with no multi-code-point grapheme clusters into -// list of grapheme clusters. -List charsOf(String text) => - text.runes.map((r) => String.fromCharCode(r)).toList(); - -void expectGC(Characters actual, List expected) { - var text = expected.join(); - - // Iterable operations. - expect(actual.string, text); - expect(actual.toString(), text); - expect(actual.toList(), expected); - expect(actual.length, expected.length); - if (expected.isNotEmpty) { - expect(actual.first, expected.first); - expect(actual.last, expected.last); - } else { - expect(() => actual.first, throwsStateError); - expect(() => actual.last, throwsStateError); - } - if (expected.length == 1) { - expect(actual.single, expected.single); - } else { - expect(() => actual.single, throwsStateError); - } - expect(actual.isEmpty, expected.isEmpty); - expect(actual.isNotEmpty, expected.isNotEmpty); - expect(actual.contains(""), false); - for (var char in expected) { - expect(actual.contains(char), true); - } - for (int i = 1; i < expected.length; i++) { - expect(actual.contains(expected[i - 1] + expected[i]), false); - } - expect(actual.skip(1).toList(), expected.skip(1).toList()); - expect(actual.take(1).toList(), expected.take(1).toList()); - expect(actual.skip(1).toString(), expected.skip(1).join()); - expect(actual.take(1).toString(), expected.take(1).join()); - - if (expected.isNotEmpty) { - expect(actual.skipLast(1).toList(), - expected.take(expected.length - 1).toList()); - expect(actual.takeLast(1).toList(), - expected.skip(expected.length - 1).toList()); - expect(actual.skipLast(1).toString(), - expected.take(expected.length - 1).join()); - expect(actual.takeLast(1).toString(), - expected.skip(expected.length - 1).join()); - } - bool isEven(String s) => s.length.isEven; - - expect( - actual.skipWhile(isEven).toList(), expected.skipWhile(isEven).toList()); - expect( - actual.takeWhile(isEven).toList(), expected.takeWhile(isEven).toList()); - expect( - actual.skipWhile(isEven).toString(), expected.skipWhile(isEven).join()); - expect( - actual.takeWhile(isEven).toString(), expected.takeWhile(isEven).join()); - - expect(actual.skipLastWhile(isEven).toString(), - expected.toList().reversed.skipWhile(isEven).toList().reversed.join()); - expect(actual.takeLastWhile(isEven).toString(), - expected.toList().reversed.takeWhile(isEven).toList().reversed.join()); - - expect(actual.where(isEven).toString(), expected.where(isEven).join()); - - expect((actual + actual).toString(), actual.string + actual.string); - - // Iteration. - var it = actual.iterator; - expect(it.isEmpty, true); - for (var i = 0; i < expected.length; i++) { - expect(it.moveNext(), true); - expect(it.current, expected[i]); - - expect(actual.elementAt(i), expected[i]); - expect(actual.skip(i).first, expected[i]); - } - expect(it.moveNext(), false); - for (var i = expected.length - 1; i >= 0; i--) { - expect(it.moveBack(), true); - expect(it.current, expected[i]); - } - expect(it.moveBack(), false); - expect(it.isEmpty, true); - - // GraphemeClusters operations. - expect(actual.toUpperCase().string, text.toUpperCase()); - expect(actual.toLowerCase().string, text.toLowerCase()); - - expect(actual.string, text); - - expect(actual.containsAll(gc("")), true); - expect(actual.containsAll(actual), true); - if (expected.isNotEmpty) { - int steps = min(5, expected.length); - for (int s = 0; s <= steps; s++) { - int i = expected.length * s ~/ steps; - expect(actual.startsWith(gc(expected.sublist(0, i).join())), true); - expect(actual.endsWith(gc(expected.sublist(i).join())), true); - for (int t = s + 1; t <= steps; t++) { - int j = expected.length * t ~/ steps; - var slice = expected.sublist(i, j).join(); - var gcs = gc(slice); - expect(actual.containsAll(gcs), true); - } - } - } - - { - // Random walk back and forth. - var it = actual.iterator; - int pos = -1; - if (random.nextBool()) { - pos = expected.length; - it = actual.iteratorAtEnd; - } - int steps = 5 + random.nextInt(expected.length * 2 + 1); - bool lastMove = false; - while (true) { - bool back = false; - if (pos < 0) { - expect(lastMove, false); - expect(it.isEmpty, true); - } else if (pos >= expected.length) { - expect(lastMove, false); - expect(it.isEmpty, true); - back = true; - } else { - expect(lastMove, true); - expect(it.current, expected[pos]); - back = random.nextBool(); - } - if (--steps < 0) break; - if (back) { - lastMove = it.moveBack(); - pos -= 1; - } else { - lastMove = it.moveNext(); - pos += 1; - } - } - } -} - -Characters gc(String string) => Characters(string); - -void testParts( - Characters a, Characters b, Characters c, Characters d, Characters e) { - var cs = gc("$a$b$c$d$e"); - test("$cs", () { - var it = cs.iterator; - expect(it.isEmpty, true); - expect(it.isNotEmpty, false); - expect(it.current, ""); - - // moveNext(). - expect(it.moveNext(), true); - expect(it.isEmpty, false); - expect(it.current, "$a"); - expect(it.moveNext(), true); - expect(it.isEmpty, false); - expect(it.current, "$b"); - expect(it.moveNext(), true); - expect(it.isEmpty, false); - expect(it.current, "$c"); - expect(it.moveNext(), true); - expect(it.isEmpty, false); - expect(it.current, "$d"); - expect(it.moveNext(), true); - expect(it.isEmpty, false); - expect(it.current, "$e"); - expect(it.moveNext(), false); - expect(it.isEmpty, true); - expect(it.current, ""); - - // moveBack(). - expect(it.moveBack(), true); - expect(it.isEmpty, false); - expect(it.current, "$e"); - expect(it.moveBack(), true); - expect(it.isEmpty, false); - expect(it.current, "$d"); - expect(it.moveBack(), true); - expect(it.isEmpty, false); - expect(it.current, "$c"); - expect(it.moveBack(), true); - expect(it.isEmpty, false); - expect(it.current, "$b"); - expect(it.moveBack(), true); - expect(it.isEmpty, false); - expect(it.current, "$a"); - expect(it.moveBack(), false); - expect(it.isEmpty, true); - expect(it.current, ""); - - // moveNext(int). - expect(it.moveTo(c), true); - expect(it.current, "$c"); - expect(it.moveTo(b), false); - expect(it.moveTo(c), false); - expect(it.current, "$c"); - expect(it.moveTo(d), true); - expect(it.current, "$d"); - - // moveBack(c). - expect(it.moveBackTo(c), true); - expect(it.current, "$c"); - expect(it.moveBackTo(d), false); - expect(it.moveBackTo(c), false); - expect(it.moveBackTo(a), true); - expect(it.current, "$a"); - - // moveNext(n) - expect(it.moveBack(), false); - - expect(it.moveNext(2), true); - expect(it.current, "$a$b"); - expect(it.moveNext(4), false); - expect(it.current, "$c$d$e"); - expect(it.moveNext(0), true); - expect(it.current, ""); - expect(it.moveNext(1), false); - expect(it.current, ""); - - // moveBack(n). - expect(it.moveBack(2), true); - expect(it.current, "$d$e"); - expect(it.moveBack(1), true); - expect(it.current, "$c"); - expect(it.moveBack(3), false); - expect(it.current, "$a$b"); - expect(it.moveBack(), false); - - // moveFirst. - it.expandAll(); - expect(it.current, "$a$b$c$d$e"); - expect(it.collapseToFirst(b), true); - expect(it.current, "$b"); - it.expandAll(); - expect(it.current, "$b$c$d$e"); - expect(it.collapseToFirst(a), false); - expect(it.current, "$b$c$d$e"); - - // moveBackTo - it.expandBackAll(); - expect(it.current, "$a$b$c$d$e"); - expect(it.collapseToLast(c), true); - expect(it.current, "$c"); - - // includeNext/includePrevious - expect(it.expandTo(e), true); - expect(it.current, "$c$d$e"); - expect(it.expandTo(e), false); - expect(it.expandBackTo(b), true); - expect(it.current, "$b$c$d$e"); - expect(it.expandBackTo(b), false); - expect(it.current, "$b$c$d$e"); - expect(it.collapseToFirst(c), true); - expect(it.current, "$c"); - - // includeUntilNext/expandBackUntil - expect(it.expandBackUntil(a), true); - expect(it.current, "$b$c"); - expect(it.expandBackUntil(a), true); - expect(it.current, "$b$c"); - expect(it.expandUntil(e), true); - expect(it.current, "$b$c$d"); - expect(it.expandUntil(e), true); - expect(it.current, "$b$c$d"); - - // dropFirst/dropLast - expect(it.dropFirst(), true); - expect(it.current, "$c$d"); - expect(it.dropLast(), true); - expect(it.current, "$c"); - it.expandBackAll(); - it.expandAll(); - expect(it.current, "$a$b$c$d$e"); - expect(it.dropTo(b), true); - expect(it.current, "$c$d$e"); - expect(it.dropBackTo(d), true); - expect(it.current, "$c"); - - it.expandBackAll(); - it.expandAll(); - expect(it.current, "$a$b$c$d$e"); - - expect(it.dropUntil(b), true); - expect(it.current, "$b$c$d$e"); - expect(it.dropBackUntil(d), true); - expect(it.current, "$b$c$d"); - - it.dropWhile((x) => x == b.string); - expect(it.current, "$c$d"); - it.expandBackAll(); - expect(it.current, "$a$b$c$d"); - it.dropBackWhile((x) => x != b.string); - expect(it.current, "$a$b"); - it.dropBackWhile((x) => false); - expect(it.current, "$a$b"); - - // include..While - it.expandWhile((x) => false); - expect(it.current, "$a$b"); - it.expandWhile((x) => x != e.string); - expect(it.current, "$a$b$c$d"); - expect(it.collapseToFirst(c), true); - expect(it.current, "$c"); - it.expandBackWhile((x) => false); - expect(it.current, "$c"); - it.expandBackWhile((x) => x != a.string); - expect(it.current, "$b$c"); - - var cs2 = cs.replaceAll(c, gc("")); - var cs3 = cs.replaceFirst(c, gc("")); - var cs4 = cs.findFirst(c).replaceRange(gc("")).source; - var cse = gc("$a$b$d$e"); - expect(cs2, cse); - expect(cs3, cse); - expect(cs4, cse); - var cs5 = cs4.replaceAll(a, c); - expect(cs5, gc("$c$b$d$e")); - var cs6 = cs5.replaceAll(gc(""), a); - expect(cs6, gc("$a$c$a$b$a$d$a$e$a")); - var cs7 = cs6.replaceFirst(b, a); - expect(cs7, gc("$a$c$a$a$a$d$a$e$a")); - var cs8 = cs7.replaceFirst(e, a); - expect(cs8, gc("$a$c$a$a$a$d$a$a$a")); - var cs9 = cs8.replaceAll(a + a, b); - expect(cs9, gc("$a$c$b$a$d$b$a")); - it = cs9.iterator; - it.moveTo(b + a); - expect("$b$a", it.current); - it.expandTo(b + a); - expect("$b$a$d$b$a", it.current); - var cs10 = it.replaceAll(b + a, e + e); - expect(cs10.currentCharacters, e + e + d + e + e); - expect(cs10.source, gc("$a$c$e$e$d$e$e")); - var cs11 = it.replaceRange(e); - expect(cs11.currentCharacters, e); - expect(cs11.source, gc("$a$c$e")); - - var cs12 = gc("$a$b$a"); - expect(cs12.split(b), [a, a]); - expect(cs12.split(a), [gc(""), b, gc("")]); - expect(cs12.split(a, 2), [gc(""), gc("$b$a")]); - - expect(cs12.split(gc("")), [a, b, a]); - expect(cs12.split(gc(""), 2), [a, gc("$b$a")]); - - expect(gc("").split(gc("")), [gc("")]); - - var cs13 = gc("$b$a$b$a$b$a"); - expect(cs13.split(b), [gc(""), a, a, a]); - expect(cs13.split(b, 1), [cs13]); - expect(cs13.split(b, 2), [gc(""), gc("$a$b$a$b$a")]); - expect(cs13.split(b, 3), [gc(""), a, gc("$a$b$a")]); - expect(cs13.split(b, 4), [gc(""), a, a, a]); - expect(cs13.split(b, 5), [gc(""), a, a, a]); - expect(cs13.split(b, 9999), [gc(""), a, a, a]); - expect(cs13.split(b, 0), [gc(""), a, a, a]); - expect(cs13.split(b, -1), [gc(""), a, a, a]); - expect(cs13.split(b, -9999), [gc(""), a, a, a]); - - it = cs13.iterator..expandAll(); - expect(it.current, "$b$a$b$a$b$a"); - it.dropFirst(); - it.dropLast(); - expect(it.current, "$a$b$a$b"); - expect(it.split(a).map((range) => range.current), ["", "$b", "$b"]); - expect(it.split(a, 2).map((range) => range.current), ["", "$b$a$b"]); - // Each split is after an *a*. - bool first = true; - for (var range in it.split(a)) { - if (range.isEmpty) { - // First range is empty. - expect(first, true); - first = false; - continue; - } - // Later ranges are "b" that come after "a". - expect(range.current, "$b"); - range.moveBack(); - expect(range.current, "$a"); - } - - expect(it.split(gc("")).map((range) => range.current), - ["$a", "$b", "$a", "$b"]); - - expect(gc("").iterator.split(gc("")).map((range) => range.current), [""]); - - expect(cs.startsWith(gc("")), true); - expect(cs.startsWith(a), true); - expect(cs.startsWith(a + b), true); - expect(cs.startsWith(gc("$a$b$c")), true); - expect(cs.startsWith(gc("$a$b$c$d")), true); - expect(cs.startsWith(gc("$a$b$c$d$e")), true); - expect(cs.startsWith(b), false); - expect(cs.startsWith(c), false); - expect(cs.startsWith(d), false); - expect(cs.startsWith(e), false); - - expect(cs.endsWith(gc("")), true); - expect(cs.endsWith(e), true); - expect(cs.endsWith(d + e), true); - expect(cs.endsWith(gc("$c$d$e")), true); - expect(cs.endsWith(gc("$b$c$d$e")), true); - expect(cs.endsWith(gc("$a$b$c$d$e")), true); - expect(cs.endsWith(d), false); - expect(cs.endsWith(c), false); - expect(cs.endsWith(b), false); - expect(cs.endsWith(a), false); - - it = cs.findFirst(b + c); - expect(it.startsWith(gc("")), true); - expect(it.startsWith(b), true); - expect(it.startsWith(b + c), true); - expect(it.startsWith(a + b + c), false); - expect(it.startsWith(b + c + d), false); - expect(it.startsWith(a), false); - - expect(it.endsWith(gc("")), true); - expect(it.endsWith(c), true); - expect(it.endsWith(b + c), true); - expect(it.endsWith(a + b + c), false); - expect(it.endsWith(b + c + d), false); - expect(it.endsWith(d), false); - - it.collapseToFirst(c); - expect(it.isPrecededBy(gc("")), true); - expect(it.isPrecededBy(b), true); - expect(it.isPrecededBy(a + b), true); - expect(it.isPrecededBy(a + b + c), false); - expect(it.isPrecededBy(a), false); - - expect(it.isFollowedBy(gc("")), true); - expect(it.isFollowedBy(d), true); - expect(it.isFollowedBy(d + e), true); - expect(it.isFollowedBy(c + d + e), false); - expect(it.isFollowedBy(e), false); - }); - test("replace methods", () { - // Unicode grapheme breaking character classes, - // represented by their first value. - - var pattern = gc("\t"); // A non-combining entry to be replaced. - var non = gc(""); - - var c = otr + cr + pattern + lf + pic + pattern + zwj + pic + otr; - var r = c.replaceAll(pattern, non); - expect(r, otr + cr + lf + pic + zwj + pic + otr); - var ci = c.iterator..moveNextAll(); - var ri = ci.replaceAll(pattern, non); - expect(ri.currentCharacters, otr + cr + lf + pic + zwj + pic + otr); - ci.dropFirst(); - ci.dropLast(); - expect(ci.currentCharacters, cr + pattern + lf + pic + pattern + zwj + pic); - expect(ci.currentCharacters.length, 7); - ri = ci.replaceAll(pattern, non); - expect(ri.currentCharacters, cr + lf + pic + zwj + pic); - expect(ri.currentCharacters.length, 2); - ci.dropFirst(); - ci.dropLast(5); - expect(ci.currentCharacters, pattern); - ri = ci.replaceAll(pattern, non); - expect(ri.currentCharacters, cr + lf); - ci.moveNext(2); - ci.moveNext(1); - expect(ci.currentCharacters, pattern); - ri = ci.replaceAll(pattern, non); - expect(ri.currentCharacters, pic + zwj + pic); - - c = otr + pic + ext + pattern + pic + ext + otr; - expect(c.length, 5); - ci = c.iterator..moveTo(pattern); - expect(ci.currentCharacters, pattern); - ri = ci.replaceAll(pattern, zwj); - expect(ri.currentCharacters, pic + ext + zwj + pic + ext); - - c = reg + pattern + reg + reg; - ci = c.iterator..moveTo(pattern); - ri = ci.replaceRange(non); - expect(ri.currentCharacters, reg + reg); - expect(ri.moveNext(), true); - expect(ri.currentCharacters, reg); - }); -} - -/// Sample characters from each breaking algorithm category. -final Characters ctl = gc("\x00"); // Control, NUL. -final Characters cr = gc("\r"); // Carriage Return, CR. -final Characters lf = gc("\n"); // Newline, NL. -final Characters otr = gc(" "); // Other, Space. -final Characters ext = gc("\u0300"); // Extend, Combining Grave Accent. -final Characters spc = gc("\u0903"); // Spacing Mark, Devanagari Sign Visarga. -final Characters pre = gc("\u0600"); // Prepend, Arabic Number Sign. -final Characters zwj = gc("\u200d"); // Zero-Width Joiner. -final Characters pic = gc("\u00a9"); // Extended Pictographic, Copyright. -final Characters reg = gc("\u{1f1e6}"); // Regional Identifier "a". -final Characters hanl = gc("\u1100"); // Hangul L, Choseong Kiyeok. -final Characters hanv = gc("\u1160"); // Hangul V, Jungseong Filler. -final Characters hant = gc("\u11a8"); // Hangul T, Jongseong Kiyeok. -final Characters hanlv = gc("\uac00"); // Hangul LV, Syllable Ga. -final Characters hanlvt = gc("\uac01"); // Hangul LVT, Syllable Gag. +// Copyright (c) 2020, 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. + +// Unsound entry point. Use until all dependencies are sound. +// Then move `characters_test.dart` back out from `sound_tests/`. + +// @dart=2.8 +import 'sound_tests/characters_test.dart' as sound; + +void main() { + sound.main(); +} diff --git a/pkgs/characters/test/sound_tests/characters_test.dart b/pkgs/characters/test/sound_tests/characters_test.dart new file mode 100644 index 00000000..5fe41fe4 --- /dev/null +++ b/pkgs/characters/test/sound_tests/characters_test.dart @@ -0,0 +1,668 @@ +// Copyright (c) 2019, 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. + +import "dart:math"; + +import "package:test/test.dart"; + +import "package:characters/characters.dart"; + +import "../src/unicode_tests.dart"; +import "../src/unicode_grapheme_tests.dart"; +import "../src/various_tests.dart"; + +late Random random; + +void main([List? args]) { + // Ensure random seed is part of every test failure message, + // and that it can be reapplied for testing. + var seed = (args != null && args.isNotEmpty) + ? int.parse(args[0]) + : Random().nextInt(0x3FFFFFFF); + random = Random(seed); + group("[Random Seed: $seed]", tests); + group("characters", () { + test("operations", () { + var flag = "\u{1F1E9}\u{1F1F0}"; // Regional Indicators "DK". + var string = "Hi $flag!"; + expect(string.length, 8); + var cs = gc(string); + expect(cs.length, 5); + expect(cs.toList(), ["H", "i", " ", flag, "!"]); + expect(cs.skip(2).toString(), " $flag!"); + expect(cs.skipLast(2).toString(), "Hi "); + expect(cs.take(2).toString(), "Hi"); + expect(cs.takeLast(2).toString(), "$flag!"); + + expect(cs.contains("\u{1F1E9}"), false); + expect(cs.contains(flag), true); + expect(cs.contains("$flag!"), false); + expect(cs.containsAll(gc("$flag!")), true); + + expect(cs.takeWhile((x) => x != " ").toString(), "Hi"); + expect(cs.takeLastWhile((x) => x != " ").toString(), "$flag!"); + expect(cs.skipWhile((x) => x != " ").toString(), " $flag!"); + expect(cs.skipLastWhile((x) => x != " ").toString(), "Hi "); + + expect(cs.findFirst(gc(""))!.moveBack(), false); + expect(cs.findFirst(gc(flag))!.current, flag); + expect(cs.findLast(gc(flag))!.current, flag); + expect(cs.iterator.moveNext(), true); + expect(cs.iterator.moveBack(), false); + expect((cs.iterator..moveNext()).current, "H"); + expect(cs.iteratorAtEnd.moveNext(), false); + expect(cs.iteratorAtEnd.moveBack(), true); + expect((cs.iteratorAtEnd..moveBack()).current, "!"); + }); + + testParts(gc("a"), gc("b"), gc("c"), gc("d"), gc("e")); + + // Composite pictogram example, from https://en.wikipedia.org/wiki/Zero-width_joiner. + var flag = "\u{1f3f3}"; // U+1F3F3, Flag, waving. Category Pictogram. + var white = "\ufe0f"; // U+FE0F, Variant selector 16. Category Extend. + var zwj = "\u200d"; // U+200D, ZWJ + var rainbow = "\u{1f308}"; // U+1F308, Rainbow. Category Pictogram + + testParts(gc("$flag$white$zwj$rainbow"), gc("$flag$white"), gc("$rainbow"), + gc("$flag$zwj$rainbow"), gc("!")); + }); +} + +void tests() { + test("empty", () { + expectGC(gc(""), []); + }); + group("gc-ASCII", () { + for (var text in [ + "", + "A", + "123456abcdefab", + ]) { + test('"$text"', () { + expectGC(gc(text), charsOf(text)); + }); + } + test("CR+NL", () { + expectGC(gc("a\r\nb"), ["a", "\r\n", "b"]); + expectGC(gc("a\n\rb"), ["a", "\n", "\r", "b"]); + }); + }); + group("Non-ASCII single-code point", () { + for (var text in [ + "à la mode", + "rødgrød-æble-ål", + ]) { + test('"$text"', () { + expectGC(gc(text), charsOf(text)); + }); + } + }); + group("Combining marks", () { + var text = "a\u0300 la mode"; + test('"$text"', () { + expectGC(gc(text), ["a\u0300", " ", "l", "a", " ", "m", "o", "d", "e"]); + }); + var text2 = "æble-a\u030Al"; + test('"$text2"', () { + expectGC(gc(text2), ["æ", "b", "l", "e", "-", "a\u030A", "l"]); + }); + }); + + group("Regional Indicators", () { + test('"🇦🇩🇰🇾🇪🇸"', () { + // Andorra, Cayman Islands, Spain. + expectGC(gc("🇦🇩🇰🇾🇪🇸"), ["🇦🇩", "🇰🇾", "🇪🇸"]); + }); + test('"X🇦🇩🇰🇾🇪🇸"', () { + // Other, Andorra, Cayman Islands, Spain. + expectGC(gc("X🇦🇩🇰🇾🇪🇸"), ["X", "🇦🇩", "🇰🇾", "🇪🇸"]); + }); + test('"🇩🇰🇾🇪🇸"', () { + // Denmark, Yemen, unmatched S. + expectGC(gc("🇩🇰🇾🇪🇸"), ["🇩🇰", "🇾🇪", "🇸"]); + }); + test('"X🇩🇰🇾🇪🇸"', () { + // Other, Denmark, Yemen, unmatched S. + expectGC(gc("X🇩🇰🇾🇪🇸"), ["X", "🇩🇰", "🇾🇪", "🇸"]); + }); + }); + + group("Hangul", () { + // Individual characters found on Wikipedia. Not expected to make sense. + test('"읍쌍된밟"', () { + expectGC(gc("읍쌍된밟"), ["읍", "쌍", "된", "밟"]); + }); + }); + + group("Unicode test", () { + for (var gcs in splitTests) { + test("[${testDescription(gcs)}]", () { + expectGC(gc(gcs.join()), gcs); + }); + } + }); + + group("Emoji test", () { + for (var gcs in emojis) { + test("[${testDescription(gcs)}]", () { + expectGC(gc(gcs.join()), gcs); + }); + } + }); + + group("Zalgo test", () { + for (var gcs in zalgo) { + test("[${testDescription(gcs)}]", () { + expectGC(gc(gcs.join()), gcs); + }); + } + }); +} + +// Converts text with no multi-code-point grapheme clusters into +// list of grapheme clusters. +List charsOf(String text) => + text.runes.map((r) => String.fromCharCode(r)).toList(); + +void expectGC(Characters actual, List expected) { + var text = expected.join(); + + // Iterable operations. + expect(actual.string, text); + expect(actual.toString(), text); + expect(actual.toList(), expected); + expect(actual.length, expected.length); + if (expected.isNotEmpty) { + expect(actual.first, expected.first); + expect(actual.last, expected.last); + } else { + expect(() => actual.first, throwsStateError); + expect(() => actual.last, throwsStateError); + } + if (expected.length == 1) { + expect(actual.single, expected.single); + } else { + expect(() => actual.single, throwsStateError); + } + expect(actual.isEmpty, expected.isEmpty); + expect(actual.isNotEmpty, expected.isNotEmpty); + expect(actual.contains(""), false); + for (var char in expected) { + expect(actual.contains(char), true); + } + for (int i = 1; i < expected.length; i++) { + expect(actual.contains(expected[i - 1] + expected[i]), false); + } + expect(actual.skip(1).toList(), expected.skip(1).toList()); + expect(actual.take(1).toList(), expected.take(1).toList()); + expect(actual.skip(1).toString(), expected.skip(1).join()); + expect(actual.take(1).toString(), expected.take(1).join()); + + if (expected.isNotEmpty) { + expect(actual.skipLast(1).toList(), + expected.take(expected.length - 1).toList()); + expect(actual.takeLast(1).toList(), + expected.skip(expected.length - 1).toList()); + expect(actual.skipLast(1).toString(), + expected.take(expected.length - 1).join()); + expect(actual.takeLast(1).toString(), + expected.skip(expected.length - 1).join()); + } + bool isEven(String s) => s.length.isEven; + + expect( + actual.skipWhile(isEven).toList(), expected.skipWhile(isEven).toList()); + expect( + actual.takeWhile(isEven).toList(), expected.takeWhile(isEven).toList()); + expect( + actual.skipWhile(isEven).toString(), expected.skipWhile(isEven).join()); + expect( + actual.takeWhile(isEven).toString(), expected.takeWhile(isEven).join()); + + expect(actual.skipLastWhile(isEven).toString(), + expected.toList().reversed.skipWhile(isEven).toList().reversed.join()); + expect(actual.takeLastWhile(isEven).toString(), + expected.toList().reversed.takeWhile(isEven).toList().reversed.join()); + + expect(actual.where(isEven).toString(), expected.where(isEven).join()); + + expect((actual + actual).toString(), actual.string + actual.string); + + // Iteration. + var it = actual.iterator; + expect(it.isEmpty, true); + for (var i = 0; i < expected.length; i++) { + expect(it.moveNext(), true); + expect(it.current, expected[i]); + + expect(actual.elementAt(i), expected[i]); + expect(actual.skip(i).first, expected[i]); + } + expect(it.moveNext(), false); + for (var i = expected.length - 1; i >= 0; i--) { + expect(it.moveBack(), true); + expect(it.current, expected[i]); + } + expect(it.moveBack(), false); + expect(it.isEmpty, true); + + // GraphemeClusters operations. + expect(actual.toUpperCase().string, text.toUpperCase()); + expect(actual.toLowerCase().string, text.toLowerCase()); + + expect(actual.string, text); + + expect(actual.containsAll(gc("")), true); + expect(actual.containsAll(actual), true); + if (expected.isNotEmpty) { + int steps = min(5, expected.length); + for (int s = 0; s <= steps; s++) { + int i = expected.length * s ~/ steps; + expect(actual.startsWith(gc(expected.sublist(0, i).join())), true); + expect(actual.endsWith(gc(expected.sublist(i).join())), true); + for (int t = s + 1; t <= steps; t++) { + int j = expected.length * t ~/ steps; + var slice = expected.sublist(i, j).join(); + var gcs = gc(slice); + expect(actual.containsAll(gcs), true); + } + } + } + + { + // Random walk back and forth. + var it = actual.iterator; + int pos = -1; + if (random.nextBool()) { + pos = expected.length; + it = actual.iteratorAtEnd; + } + int steps = 5 + random.nextInt(expected.length * 2 + 1); + bool lastMove = false; + while (true) { + bool back = false; + if (pos < 0) { + expect(lastMove, false); + expect(it.isEmpty, true); + } else if (pos >= expected.length) { + expect(lastMove, false); + expect(it.isEmpty, true); + back = true; + } else { + expect(lastMove, true); + expect(it.current, expected[pos]); + back = random.nextBool(); + } + if (--steps < 0) break; + if (back) { + lastMove = it.moveBack(); + pos -= 1; + } else { + lastMove = it.moveNext(); + pos += 1; + } + } + } +} + +Characters gc(String string) => Characters(string); + +void testParts( + Characters a, Characters b, Characters c, Characters d, Characters e) { + var cs = gc("$a$b$c$d$e"); + test("$cs", () { + var it = cs.iterator; + expect(it.isEmpty, true); + expect(it.isNotEmpty, false); + expect(it.current, ""); + + // moveNext(). + expect(it.moveNext(), true); + expect(it.isEmpty, false); + expect(it.current, "$a"); + expect(it.moveNext(), true); + expect(it.isEmpty, false); + expect(it.current, "$b"); + expect(it.moveNext(), true); + expect(it.isEmpty, false); + expect(it.current, "$c"); + expect(it.moveNext(), true); + expect(it.isEmpty, false); + expect(it.current, "$d"); + expect(it.moveNext(), true); + expect(it.isEmpty, false); + expect(it.current, "$e"); + expect(it.moveNext(), false); + expect(it.isEmpty, true); + expect(it.current, ""); + + // moveBack(). + expect(it.moveBack(), true); + expect(it.isEmpty, false); + expect(it.current, "$e"); + expect(it.moveBack(), true); + expect(it.isEmpty, false); + expect(it.current, "$d"); + expect(it.moveBack(), true); + expect(it.isEmpty, false); + expect(it.current, "$c"); + expect(it.moveBack(), true); + expect(it.isEmpty, false); + expect(it.current, "$b"); + expect(it.moveBack(), true); + expect(it.isEmpty, false); + expect(it.current, "$a"); + expect(it.moveBack(), false); + expect(it.isEmpty, true); + expect(it.current, ""); + + // moveNext(int). + expect(it.moveTo(c), true); + expect(it.current, "$c"); + expect(it.moveTo(b), false); + expect(it.moveTo(c), false); + expect(it.current, "$c"); + expect(it.moveTo(d), true); + expect(it.current, "$d"); + + // moveBack(c). + expect(it.moveBackTo(c), true); + expect(it.current, "$c"); + expect(it.moveBackTo(d), false); + expect(it.moveBackTo(c), false); + expect(it.moveBackTo(a), true); + expect(it.current, "$a"); + + // moveNext(n) + expect(it.moveBack(), false); + + expect(it.moveNext(2), true); + expect(it.current, "$a$b"); + expect(it.moveNext(4), false); + expect(it.current, "$c$d$e"); + expect(it.moveNext(0), true); + expect(it.current, ""); + expect(it.moveNext(1), false); + expect(it.current, ""); + + // moveBack(n). + expect(it.moveBack(2), true); + expect(it.current, "$d$e"); + expect(it.moveBack(1), true); + expect(it.current, "$c"); + expect(it.moveBack(3), false); + expect(it.current, "$a$b"); + expect(it.moveBack(), false); + + // moveFirst. + it.expandAll(); + expect(it.current, "$a$b$c$d$e"); + expect(it.collapseToFirst(b), true); + expect(it.current, "$b"); + it.expandAll(); + expect(it.current, "$b$c$d$e"); + expect(it.collapseToFirst(a), false); + expect(it.current, "$b$c$d$e"); + + // moveBackTo + it.expandBackAll(); + expect(it.current, "$a$b$c$d$e"); + expect(it.collapseToLast(c), true); + expect(it.current, "$c"); + + // includeNext/includePrevious + expect(it.expandTo(e), true); + expect(it.current, "$c$d$e"); + expect(it.expandTo(e), false); + expect(it.expandBackTo(b), true); + expect(it.current, "$b$c$d$e"); + expect(it.expandBackTo(b), false); + expect(it.current, "$b$c$d$e"); + expect(it.collapseToFirst(c), true); + expect(it.current, "$c"); + + // includeUntilNext/expandBackUntil + expect(it.expandBackUntil(a), true); + expect(it.current, "$b$c"); + expect(it.expandBackUntil(a), true); + expect(it.current, "$b$c"); + expect(it.expandUntil(e), true); + expect(it.current, "$b$c$d"); + expect(it.expandUntil(e), true); + expect(it.current, "$b$c$d"); + + // dropFirst/dropLast + expect(it.dropFirst(), true); + expect(it.current, "$c$d"); + expect(it.dropLast(), true); + expect(it.current, "$c"); + it.expandBackAll(); + it.expandAll(); + expect(it.current, "$a$b$c$d$e"); + expect(it.dropTo(b), true); + expect(it.current, "$c$d$e"); + expect(it.dropBackTo(d), true); + expect(it.current, "$c"); + + it.expandBackAll(); + it.expandAll(); + expect(it.current, "$a$b$c$d$e"); + + expect(it.dropUntil(b), true); + expect(it.current, "$b$c$d$e"); + expect(it.dropBackUntil(d), true); + expect(it.current, "$b$c$d"); + + it.dropWhile((x) => x == b.string); + expect(it.current, "$c$d"); + it.expandBackAll(); + expect(it.current, "$a$b$c$d"); + it.dropBackWhile((x) => x != b.string); + expect(it.current, "$a$b"); + it.dropBackWhile((x) => false); + expect(it.current, "$a$b"); + + // include..While + it.expandWhile((x) => false); + expect(it.current, "$a$b"); + it.expandWhile((x) => x != e.string); + expect(it.current, "$a$b$c$d"); + expect(it.collapseToFirst(c), true); + expect(it.current, "$c"); + it.expandBackWhile((x) => false); + expect(it.current, "$c"); + it.expandBackWhile((x) => x != a.string); + expect(it.current, "$b$c"); + + var cs2 = cs.replaceAll(c, gc("")); + var cs3 = cs.replaceFirst(c, gc("")); + var cs4 = cs.findFirst(c)!.replaceRange(gc("")).source; + var cse = gc("$a$b$d$e"); + expect(cs2, cse); + expect(cs3, cse); + expect(cs4, cse); + var cs5 = cs4.replaceAll(a, c); + expect(cs5, gc("$c$b$d$e")); + var cs6 = cs5.replaceAll(gc(""), a); + expect(cs6, gc("$a$c$a$b$a$d$a$e$a")); + var cs7 = cs6.replaceFirst(b, a); + expect(cs7, gc("$a$c$a$a$a$d$a$e$a")); + var cs8 = cs7.replaceFirst(e, a); + expect(cs8, gc("$a$c$a$a$a$d$a$a$a")); + var cs9 = cs8.replaceAll(a + a, b); + expect(cs9, gc("$a$c$b$a$d$b$a")); + it = cs9.iterator; + it.moveTo(b + a); + expect("$b$a", it.current); + it.expandTo(b + a); + expect("$b$a$d$b$a", it.current); + var cs10 = it.replaceAll(b + a, e + e)!; + expect(cs10.currentCharacters, e + e + d + e + e); + expect(cs10.source, gc("$a$c$e$e$d$e$e")); + var cs11 = it.replaceRange(e); + expect(cs11.currentCharacters, e); + expect(cs11.source, gc("$a$c$e")); + + var cs12 = gc("$a$b$a"); + expect(cs12.split(b), [a, a]); + expect(cs12.split(a), [gc(""), b, gc("")]); + expect(cs12.split(a, 2), [gc(""), gc("$b$a")]); + + expect(cs12.split(gc("")), [a, b, a]); + expect(cs12.split(gc(""), 2), [a, gc("$b$a")]); + + expect(gc("").split(gc("")), [gc("")]); + + var cs13 = gc("$b$a$b$a$b$a"); + expect(cs13.split(b), [gc(""), a, a, a]); + expect(cs13.split(b, 1), [cs13]); + expect(cs13.split(b, 2), [gc(""), gc("$a$b$a$b$a")]); + expect(cs13.split(b, 3), [gc(""), a, gc("$a$b$a")]); + expect(cs13.split(b, 4), [gc(""), a, a, a]); + expect(cs13.split(b, 5), [gc(""), a, a, a]); + expect(cs13.split(b, 9999), [gc(""), a, a, a]); + expect(cs13.split(b, 0), [gc(""), a, a, a]); + expect(cs13.split(b, -1), [gc(""), a, a, a]); + expect(cs13.split(b, -9999), [gc(""), a, a, a]); + + it = cs13.iterator..expandAll(); + expect(it.current, "$b$a$b$a$b$a"); + it.dropFirst(); + it.dropLast(); + expect(it.current, "$a$b$a$b"); + expect(it.split(a).map((range) => range.current), ["", "$b", "$b"]); + expect(it.split(a, 2).map((range) => range.current), ["", "$b$a$b"]); + // Each split is after an *a*. + bool first = true; + for (var range in it.split(a)) { + if (range.isEmpty) { + // First range is empty. + expect(first, true); + first = false; + continue; + } + // Later ranges are "b" that come after "a". + expect(range.current, "$b"); + range.moveBack(); + expect(range.current, "$a"); + } + + expect(it.split(gc("")).map((range) => range.current), + ["$a", "$b", "$a", "$b"]); + + expect(gc("").iterator.split(gc("")).map((range) => range.current), [""]); + + expect(cs.startsWith(gc("")), true); + expect(cs.startsWith(a), true); + expect(cs.startsWith(a + b), true); + expect(cs.startsWith(gc("$a$b$c")), true); + expect(cs.startsWith(gc("$a$b$c$d")), true); + expect(cs.startsWith(gc("$a$b$c$d$e")), true); + expect(cs.startsWith(b), false); + expect(cs.startsWith(c), false); + expect(cs.startsWith(d), false); + expect(cs.startsWith(e), false); + + expect(cs.endsWith(gc("")), true); + expect(cs.endsWith(e), true); + expect(cs.endsWith(d + e), true); + expect(cs.endsWith(gc("$c$d$e")), true); + expect(cs.endsWith(gc("$b$c$d$e")), true); + expect(cs.endsWith(gc("$a$b$c$d$e")), true); + expect(cs.endsWith(d), false); + expect(cs.endsWith(c), false); + expect(cs.endsWith(b), false); + expect(cs.endsWith(a), false); + + it = cs.findFirst(b + c)!; + expect(it.startsWith(gc("")), true); + expect(it.startsWith(b), true); + expect(it.startsWith(b + c), true); + expect(it.startsWith(a + b + c), false); + expect(it.startsWith(b + c + d), false); + expect(it.startsWith(a), false); + + expect(it.endsWith(gc("")), true); + expect(it.endsWith(c), true); + expect(it.endsWith(b + c), true); + expect(it.endsWith(a + b + c), false); + expect(it.endsWith(b + c + d), false); + expect(it.endsWith(d), false); + + it.collapseToFirst(c); + expect(it.isPrecededBy(gc("")), true); + expect(it.isPrecededBy(b), true); + expect(it.isPrecededBy(a + b), true); + expect(it.isPrecededBy(a + b + c), false); + expect(it.isPrecededBy(a), false); + + expect(it.isFollowedBy(gc("")), true); + expect(it.isFollowedBy(d), true); + expect(it.isFollowedBy(d + e), true); + expect(it.isFollowedBy(c + d + e), false); + expect(it.isFollowedBy(e), false); + }); + test("replace methods", () { + // Unicode grapheme breaking character classes, + // represented by their first value. + + var pattern = gc("\t"); // A non-combining entry to be replaced. + var non = gc(""); + + var c = otr + cr + pattern + lf + pic + pattern + zwj + pic + otr; + var r = c.replaceAll(pattern, non); + expect(r, otr + cr + lf + pic + zwj + pic + otr); + var ci = c.iterator..moveNextAll(); + var ri = ci.replaceAll(pattern, non)!; + expect(ri.currentCharacters, otr + cr + lf + pic + zwj + pic + otr); + ci.dropFirst(); + ci.dropLast(); + expect(ci.currentCharacters, cr + pattern + lf + pic + pattern + zwj + pic); + expect(ci.currentCharacters.length, 7); + ri = ci.replaceAll(pattern, non)!; + expect(ri.currentCharacters, cr + lf + pic + zwj + pic); + expect(ri.currentCharacters.length, 2); + ci.dropFirst(); + ci.dropLast(5); + expect(ci.currentCharacters, pattern); + ri = ci.replaceAll(pattern, non)!; + expect(ri.currentCharacters, cr + lf); + ci.moveNext(2); + ci.moveNext(1); + expect(ci.currentCharacters, pattern); + ri = ci.replaceAll(pattern, non)!; + expect(ri.currentCharacters, pic + zwj + pic); + + c = otr + pic + ext + pattern + pic + ext + otr; + expect(c.length, 5); + ci = c.iterator..moveTo(pattern); + expect(ci.currentCharacters, pattern); + ri = ci.replaceAll(pattern, zwj)!; + expect(ri.currentCharacters, pic + ext + zwj + pic + ext); + + c = reg + pattern + reg + reg; + ci = c.iterator..moveTo(pattern); + ri = ci.replaceRange(non); + expect(ri.currentCharacters, reg + reg); + expect(ri.moveNext(), true); + expect(ri.currentCharacters, reg); + }); +} + +/// Sample characters from each breaking algorithm category. +final Characters ctl = gc("\x00"); // Control, NUL. +final Characters cr = gc("\r"); // Carriage Return, CR. +final Characters lf = gc("\n"); // Newline, NL. +final Characters otr = gc(" "); // Other, Space. +final Characters ext = gc("\u0300"); // Extend, Combining Grave Accent. +final Characters spc = gc("\u0903"); // Spacing Mark, Devanagari Sign Visarga. +final Characters pre = gc("\u0600"); // Prepend, Arabic Number Sign. +final Characters zwj = gc("\u200d"); // Zero-Width Joiner. +final Characters pic = gc("\u00a9"); // Extended Pictographic, Copyright. +final Characters reg = gc("\u{1f1e6}"); // Regional Identifier "a". +final Characters hanl = gc("\u1100"); // Hangul L, Choseong Kiyeok. +final Characters hanv = gc("\u1160"); // Hangul V, Jungseong Filler. +final Characters hant = gc("\u11a8"); // Hangul T, Jongseong Kiyeok. +final Characters hanlv = gc("\uac00"); // Hangul LV, Syllable Ga. +final Characters hanlvt = gc("\uac01"); // Hangul LVT, Syllable Gag. diff --git a/pkgs/characters/test/src/unicode_tests.dart b/pkgs/characters/test/src/unicode_tests.dart index 740c9664..23bc1ca7 100644 --- a/pkgs/characters/test/src/unicode_tests.dart +++ b/pkgs/characters/test/src/unicode_tests.dart @@ -16,7 +16,7 @@ String testDescription(List expected) { " ÷"; } -var categoryName = List(16) +var categoryName = List.filled(16, "") ..[categoryOther] = "Other" ..[categoryCR] = "CR" ..[categoryLF] = "LF"