lib/src/pathrule.dart ports internal/pathrule of datekeys-go at c531e93 (spec §29.5, §29.5.1, §29.6): NFD with the canonical ordering and Hangul, the case folding, the simple lowercase, Default_Ignorable with the whitelist of R4, the best-fit projections of R6c, every rule of a path (R2 to R6c and R10), the tree (R7 with its key and the two paths it names, and R9), and the texts of the comment and the declared author, with the texts of Go. canonicalTables is pathrule.Canonical, and a test recomputes tablesDigest from the lists. Go reads a string as bytes, and so does this port: the functions whose name ends in Utf8 take the bytes of a Go string, where a byte that is not valid UTF-8 is the rune U+FFFD, and the limits count bytes; the others take a String as utf8Bytes writes it. Each rule returns its violation, as in Go, and only the public functions throw. tool/pathrule_go_vectors.go runs in an export of datekeys-go, since internal/pathrule cannot be imported from outside its tree, and writes test/vectors/pathrule_vectors.json and its Dart copy: the cases of the tests of Go and of datekeys-ts, 1300 strings and 350 trees drawn from a fixed seed (marks, Hangul, ignorables, emoji, best-fit look-alikes, device names, 8.3 aliases, limits, texts and invalid UTF-8), the cases of R9, code points, and for each plane the SHA-256 of one line per code point of each function. Every code point of every plane gives the results of Go: planes 0, 1 and 14 also on Node.js. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>v0.11
parent
b4927829eb
commit
03c4333ca8
@ -0,0 +1,95 @@
|
||||
// The exhaustive comparison of the rules of paths and texts with Go: for each
|
||||
// code point of a plane, one line per function, hashed with SHA-256, against
|
||||
// the digests of test/vectors/pathrule_vectors.json, which
|
||||
// tool/pathrule_go_vectors.go computes with the same lines.
|
||||
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:datekeys/src/bytes.dart';
|
||||
import 'package:datekeys/src/pathrule.dart';
|
||||
import 'package:datekeys/src/sha256.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
/// Lines of ASCII added to a SHA-256 through a buffer of 64 KiB.
|
||||
final class LineHash {
|
||||
final _hash = Sha256();
|
||||
final _buffer = Uint8List(1 << 16);
|
||||
var _length = 0;
|
||||
|
||||
void add(String line) {
|
||||
if (_length + line.length > _buffer.length) _flush();
|
||||
for (var i = 0; i < line.length; i++) {
|
||||
final c = line.codeUnitAt(i);
|
||||
if (c >= 0x80) throw ArgumentError.value(line, 'line', 'not ASCII');
|
||||
_buffer[_length++] = c;
|
||||
}
|
||||
}
|
||||
|
||||
void _flush() {
|
||||
_hash.add(_buffer, 0, _length);
|
||||
_length = 0;
|
||||
}
|
||||
|
||||
String finish() {
|
||||
_flush();
|
||||
return toHex(_hash.finish());
|
||||
}
|
||||
}
|
||||
|
||||
String _hexUpper(int r) => r.toRadixString(16).toUpperCase();
|
||||
|
||||
String _outcome(void Function() f) {
|
||||
try {
|
||||
f();
|
||||
return 'ok';
|
||||
} on PathRuleException catch (e) {
|
||||
return e.message;
|
||||
}
|
||||
}
|
||||
|
||||
/// The planes whose code points pathrule_test.dart checks, also compiled to
|
||||
/// JavaScript: the BMP, the SMP and plane 14, of the tags and of the
|
||||
/// variation selectors. pathrule_vm_test.dart checks the others.
|
||||
const sharedPlanes = [0, 1, 14];
|
||||
|
||||
/// The names of the digests of a plane, in the order of the lines.
|
||||
const planeFunctions = [
|
||||
'props',
|
||||
'nfd',
|
||||
'fold',
|
||||
'key',
|
||||
'path',
|
||||
'comment',
|
||||
'author',
|
||||
];
|
||||
|
||||
/// Checks every code point of [plane] against [digests], the entry of the
|
||||
/// plane in the vectors: for each code point r, its string is its UTF-8, or
|
||||
/// U+FFFD for a surrogate, as Go's string(rune(r)) writes it.
|
||||
void checkPlane(int plane, Map<String, Object?> digests) {
|
||||
final replacement = Uint8List.fromList([0xef, 0xbf, 0xbd]);
|
||||
final h = [for (final _ in planeFunctions) LineHash()];
|
||||
for (var r = plane << 16; r <= (plane << 16 | 0xffff); r++) {
|
||||
final s = r >= 0xd800 && r <= 0xdfff
|
||||
? replacement
|
||||
: utf8Bytes(String.fromCharCode(r));
|
||||
final x = _hexUpper(r);
|
||||
h[0].add(
|
||||
'$x ${isAssigned(r)} ${isDefaultIgnorable(r)} ${_hexUpper(lower(r))}\n',
|
||||
);
|
||||
h[1].add('$x ${toHex(nfdUtf8(s))}\n');
|
||||
h[2].add('$x ${toHex(foldUtf8(s))}\n');
|
||||
h[3].add('$x ${toHex(pathKeyUtf8(s))}\n');
|
||||
h[4].add('$x ${_outcome(() => checkPathUtf8(s))}\n');
|
||||
h[5].add('$x ${_outcome(() => checkCommentUtf8(s))}\n');
|
||||
h[6].add('$x ${_outcome(() => checkAuthorUtf8(s))}\n');
|
||||
}
|
||||
expect(digests['plane'], plane);
|
||||
for (var i = 0; i < planeFunctions.length; i++) {
|
||||
expect(
|
||||
h[i].finish(),
|
||||
digests[planeFunctions[i]],
|
||||
reason: 'plane $plane, ${planeFunctions[i]}',
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,293 @@
|
||||
// The rules of the paths and texts of a format 3 head (spec §29.5, §29.6)
|
||||
// against test/vectors/pathrule_vectors.json, whose expected values the Go
|
||||
// reference computed (tool/pathrule_go_vectors.go): the cases of the tests
|
||||
// of internal/pathrule and of datekeys-ts, and strings and trees drawn from a
|
||||
// fixed seed, bytes that are not valid UTF-8 among them. The vectors come
|
||||
// from a Dart constant, so that these tests also run compiled to
|
||||
// JavaScript, where a String counts UTF-16 code units and the integers are
|
||||
// doubles. Every code point of three planes is checked here, those of the
|
||||
// others and the shared vectors of testdata/ in pathrule_vm_test.dart.
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:datekeys/src/bytes.dart';
|
||||
import 'package:datekeys/src/pathrule.dart';
|
||||
import 'package:datekeys/src/pathrule_tables.dart';
|
||||
import 'package:datekeys/src/sha256.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'pathrule_planes.dart';
|
||||
import 'vectors/pathrule_vectors.g.dart';
|
||||
|
||||
final Map<String, Object?> vectors =
|
||||
jsonDecode(pathruleVectorsJson) as Map<String, Object?>;
|
||||
|
||||
List<Map<String, Object?>> cases(String name) =>
|
||||
(vectors[name]! as List).cast<Map<String, Object?>>();
|
||||
|
||||
String cp(List<int> runes) => String.fromCharCodes(runes);
|
||||
|
||||
/// "ok", or the message of the [PathRuleException] that [f] throws, as the
|
||||
/// vectors write the result of Go.
|
||||
String outcome(void Function() f) {
|
||||
try {
|
||||
f();
|
||||
return 'ok';
|
||||
} on PathRuleException catch (e) {
|
||||
return e.message;
|
||||
}
|
||||
}
|
||||
|
||||
/// The [PathRuleException] that [f] throws.
|
||||
PathRuleException violation(void Function() f) {
|
||||
try {
|
||||
f();
|
||||
} on PathRuleException catch (e) {
|
||||
return e;
|
||||
}
|
||||
throw StateError('no violation');
|
||||
}
|
||||
|
||||
/// A case of the vectors for a failure message: its name, or its bytes.
|
||||
String label(Map<String, Object?> c) => c['name'] as String? ?? 'in ${c['in']}';
|
||||
|
||||
void main() {
|
||||
group('the tables', () {
|
||||
test('match their digest, recomputed from the lists as Go does', () {
|
||||
final digest = toHex(sha256(utf8Bytes(canonicalTables())));
|
||||
expect(digest, tablesDigest);
|
||||
expect(vectors['tables_digest'], tablesDigest);
|
||||
expect(unicodeVersion, vectors['unicode_version']);
|
||||
expect(sources, hasLength(vectors['sources']));
|
||||
expect(bestFitTables, hasLength(15));
|
||||
});
|
||||
|
||||
test('the limits are those of Go', () {
|
||||
expect(vectors['limits'], {
|
||||
'max_author_len': maxAuthorLen,
|
||||
'max_comment_len': maxCommentLen,
|
||||
'max_implicit_dirs': maxImplicitDirs,
|
||||
'max_path_len': maxPathLen,
|
||||
'max_segment_len': maxSegmentLen,
|
||||
'max_segment_utf16': maxSegmentUtf16,
|
||||
'max_segments': maxSegments,
|
||||
});
|
||||
});
|
||||
|
||||
test('Assigned, DefaultIgnorable and Lower of code points', () {
|
||||
final points = (vectors['code_points']! as List).cast<List<Object?>>();
|
||||
expect(points, hasLength(greaterThan(1000)));
|
||||
for (final p in points) {
|
||||
final r = p[0]! as int;
|
||||
final name = codePointName(r);
|
||||
expect(isAssigned(r), p[1] == 1, reason: '$name assigned');
|
||||
expect(isDefaultIgnorable(r), p[2] == 1, reason: '$name ignorable');
|
||||
expect(lower(r), p[3], reason: '$name lower');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
group('strings', () {
|
||||
test('CheckPath, CheckComment, CheckAuthor, NFD and Key of Go', () {
|
||||
final failures = <String>[];
|
||||
void check(String what, Object? got, Object? want, String c) {
|
||||
if (got != want) failures.add('$c: $what $got, want $want');
|
||||
}
|
||||
|
||||
final all = cases('strings');
|
||||
expect(all, hasLength(greaterThan(1400)));
|
||||
for (final c in all) {
|
||||
final input = fromHex(c['in']! as String);
|
||||
final name = label(c);
|
||||
final comment = c['comment'];
|
||||
check('path', outcome(() => checkPathUtf8(input)), c['path'], name);
|
||||
check('comment', outcome(() => checkCommentUtf8(input)), comment, name);
|
||||
check(
|
||||
'author',
|
||||
outcome(() => checkAuthorUtf8(input)),
|
||||
c['author'] ?? comment,
|
||||
name,
|
||||
);
|
||||
check('nfd', toHex(nfdUtf8(input)), c['nfd'], name);
|
||||
check('key', toHex(pathKeyUtf8(input)), c['key'], name);
|
||||
}
|
||||
expect(failures, isEmpty, reason: failures.take(20).join('\n'));
|
||||
});
|
||||
|
||||
test('a String is its UTF-8, a lone surrogate the bytes of Go', () {
|
||||
// Every valid UTF-8 input, as a String, gives the same results; and a
|
||||
// lone surrogate is the three bytes of its code point, which Go reads
|
||||
// as three U+FFFD.
|
||||
var strings = 0;
|
||||
for (final c in cases('strings')) {
|
||||
final s = decodeUtf8(fromHex(c['in']! as String));
|
||||
if (s == null) continue;
|
||||
strings++;
|
||||
final name = label(c);
|
||||
expect(outcome(() => checkPath(s)), c['path'], reason: name);
|
||||
expect(outcome(() => checkComment(s)), c['comment'], reason: name);
|
||||
expect(
|
||||
outcome(() => checkAuthor(s)),
|
||||
c['author'] ?? c['comment'],
|
||||
reason: name,
|
||||
);
|
||||
expect(utf8Bytes(nfd(s)), fromHex(c['nfd']! as String), reason: name);
|
||||
expect(
|
||||
utf8Bytes(pathKey(s)),
|
||||
fromHex(c['key']! as String),
|
||||
reason: name,
|
||||
);
|
||||
}
|
||||
expect(strings, greaterThan(1000));
|
||||
final surrogate = cases('strings')
|
||||
.singleWhere((c) => c['name'] == 'a surrogate in UTF-8 bytes');
|
||||
expect(surrogate['in'], 'eda080');
|
||||
final lone = cp([0xd800]);
|
||||
expect(outcome(() => checkPath(lone)), surrogate['path']);
|
||||
expect(outcome(() => checkComment(lone)), surrogate['comment']);
|
||||
expect(utf8Bytes(nfd(lone)), fromHex(surrogate['nfd']! as String));
|
||||
expect(utf8Bytes(pathKey(lone)), fromHex(surrogate['key']! as String));
|
||||
});
|
||||
|
||||
test('Fold maps each code point alone', () {
|
||||
// The planes of pathrule_vm_test.dart check Fold code point by code
|
||||
// point against Go; on a string it is their concatenation.
|
||||
for (final c in cases('strings')) {
|
||||
final input = fromHex(c['in']! as String);
|
||||
final each = <int>[];
|
||||
for (final r in decodeUtf8(nfdUtf8(input))!.runes) {
|
||||
each.addAll(foldUtf8(utf8Bytes(cp([r]))));
|
||||
}
|
||||
expect(foldUtf8(nfdUtf8(input)), each, reason: label(c));
|
||||
}
|
||||
});
|
||||
|
||||
test('the named cases of Go, with their exact texts', () {
|
||||
final named = {
|
||||
for (final c in cases('strings'))
|
||||
if (c['name'] != null) c['name']! as String: c,
|
||||
};
|
||||
expect(named, hasLength(greaterThan(140)));
|
||||
String path(String name) => named[name]!['path']! as String;
|
||||
// A few of them as the reader of a head sees them.
|
||||
expect(outcome(() => checkPath('a${cp([9])}b')), path('TAB'));
|
||||
expect(path('TAB'), 'R4: segment 1: control U+0009');
|
||||
expect(
|
||||
outcome(() => checkPath('CON.txt')),
|
||||
'R6: segment 1: CON is a reserved device name',
|
||||
);
|
||||
expect(path('CON.txt'), 'R6: segment 1: CON is a reserved device name');
|
||||
expect(
|
||||
path('.datekeys-x'),
|
||||
'R10: the first segment starts with ".datekeys-"',
|
||||
);
|
||||
expect(outcome(() => checkPath('.datekeys-x')), path('.datekeys-x'));
|
||||
expect(
|
||||
outcome(() => checkPath(cp([0x3000, 0x61]))),
|
||||
path('U+3000 first'),
|
||||
);
|
||||
expect(
|
||||
outcome(() => checkPath(cp(List.filled(127, 0x390)))),
|
||||
path('127 times U+0390: 381 UTF-16 units after NFD'),
|
||||
);
|
||||
expect(
|
||||
outcome(() => checkAuthor(' Ana')),
|
||||
named['a leading space in an author']!['author'],
|
||||
);
|
||||
});
|
||||
|
||||
test('a violation carries its rule and detail apart', () {
|
||||
final e = violation(() => checkPath('a/b${cp([0x202e])}'));
|
||||
expect(e.rule, 'R4');
|
||||
expect(e.detail, 'segment 2: invisible U+202E');
|
||||
expect(e.message, 'R4: segment 2: invisible U+202E');
|
||||
expect(e.toString(), e.message);
|
||||
expect(e.paths, (0, 0));
|
||||
final t = violation(() => checkComment('ok\na${cp([0xfe0f])}'));
|
||||
expect(t.rule, 'text');
|
||||
expect(
|
||||
t.detail,
|
||||
'line 2: U+FE0F is not part of an emoji variation sequence',
|
||||
);
|
||||
});
|
||||
|
||||
test('limits count UTF-8 bytes, not UTF-16 code units', () {
|
||||
// 85 emoji are 170 UTF-16 code units and 340 bytes: R3 counts bytes.
|
||||
final emoji = cp(List.filled(85, 0x1f600));
|
||||
expect(emoji.length, 170);
|
||||
expect(
|
||||
outcome(() => checkPath(emoji)),
|
||||
'R3: segment 1: 340 bytes, more than 255',
|
||||
);
|
||||
// R6b counts code points: an astral letter is one, not two.
|
||||
final astral = '${cp([0x10400])}BCDEF~1';
|
||||
expect(astral.length, 9);
|
||||
expect(
|
||||
outcome(() => checkPath(astral)),
|
||||
'R6b: segment 1: the segment has the form of an 8.3 alias',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('every code point of planes 0, 1 and 14 gives the results of Go', () {
|
||||
final planes = cases('planes');
|
||||
for (final plane in sharedPlanes) {
|
||||
checkPlane(plane, planes[plane]);
|
||||
}
|
||||
});
|
||||
|
||||
group('trees', () {
|
||||
test('CheckTree of Go, with the two paths of R7', () {
|
||||
final all = cases('trees');
|
||||
expect(all, hasLength(greaterThan(350)));
|
||||
var r7 = 0;
|
||||
for (final c in all) {
|
||||
final paths = [
|
||||
for (final p in (c['in'] as List?) ?? const <Object?>[])
|
||||
fromHex(p! as String),
|
||||
];
|
||||
final name = label2(c);
|
||||
final want = c['result']! as String;
|
||||
expect(outcome(() => checkTreeUtf8(paths)), want, reason: name);
|
||||
final pair = (c['paths']! as List).cast<int>();
|
||||
if (want != 'ok') {
|
||||
final e = violation(() => checkTreeUtf8(paths));
|
||||
expect(e.paths, (pair[0], pair[1]), reason: name);
|
||||
if (e.rule == 'R7') r7++;
|
||||
} else {
|
||||
expect(pair, [0, 0], reason: name);
|
||||
}
|
||||
final strings = [for (final p in paths) decodeUtf8(p)];
|
||||
if (strings.every((s) => s != null)) {
|
||||
expect(
|
||||
outcome(() => checkTree([for (final s in strings) s!])),
|
||||
want,
|
||||
reason: name,
|
||||
);
|
||||
}
|
||||
}
|
||||
expect(r7, greaterThan(50));
|
||||
});
|
||||
|
||||
test('R9 counts the distinct folders of every path', () {
|
||||
for (final c in cases('folders')) {
|
||||
final prefix = c['prefix']! as String;
|
||||
final suffix = c['suffix']! as String;
|
||||
final digits = c['digits']! as int;
|
||||
final paths = [
|
||||
for (var i = 0; i < (c['count']! as int); i++)
|
||||
'$prefix${i.toString().padLeft(digits, '0')}$suffix',
|
||||
];
|
||||
expect(
|
||||
outcome(() => checkTree(paths)),
|
||||
c['result'],
|
||||
reason: c['name']! as String,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// A tree of the vectors for a failure message: its name, or its paths.
|
||||
String label2(Map<String, Object?> c) =>
|
||||
c['name'] as String? ?? 'paths ${c['in']}';
|
||||
@ -0,0 +1,134 @@
|
||||
// The rules of paths and texts against files: the copy of
|
||||
// test/vectors/pathrule_vectors.json that pathrule_test.dart reads, a Dart
|
||||
// constant for the tests compiled to JavaScript, is the JSON file byte for
|
||||
// byte; the shared vectors of testdata/, paths.json and path_fold.json of
|
||||
// datekeys-go, give their results; and every code point of every plane
|
||||
// gives the results of Go, compared through the SHA-256 of one line per
|
||||
// code point that tool/pathrule_go_vectors.go computes with the reference:
|
||||
// here the planes that pathrule_test.dart leaves out.
|
||||
@TestOn('vm')
|
||||
library;
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:datekeys/src/bytes.dart';
|
||||
import 'package:datekeys/src/pathrule.dart';
|
||||
import 'package:datekeys/src/pathrule_tables.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'pathrule_planes.dart';
|
||||
import 'vectors/pathrule_vectors.g.dart';
|
||||
|
||||
Map<String, Object?> readJson(String path) =>
|
||||
jsonDecode(File(path).readAsStringSync()) as Map<String, Object?>;
|
||||
|
||||
String outcome(void Function() f) {
|
||||
try {
|
||||
f();
|
||||
return 'ok';
|
||||
} on PathRuleException catch (e) {
|
||||
return e.message;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
test('pathrule_vectors.g.dart holds pathrule_vectors.json', () {
|
||||
final file = File('test/vectors/pathrule_vectors.json').readAsStringSync();
|
||||
expect(pathruleVectorsJson, file);
|
||||
});
|
||||
|
||||
group('testdata', () {
|
||||
final paths = readJson('testdata/vectors/paths.json');
|
||||
final fold = readJson('testdata/vectors/path_fold.json');
|
||||
|
||||
test('name the tables of this library', () {
|
||||
for (final f in [paths, fold]) {
|
||||
expect(f['unicode_version'], unicodeVersion);
|
||||
expect(f['tables_digest'], tablesDigest);
|
||||
}
|
||||
});
|
||||
|
||||
test('paths.json: the rules of one entry', () {
|
||||
final cases = (paths['paths']! as List).cast<Map<String, Object?>>();
|
||||
expect(cases, hasLength(greaterThan(80)));
|
||||
for (final c in cases) {
|
||||
expect(
|
||||
outcome(() => checkPath(c['path']! as String)),
|
||||
c['result'],
|
||||
reason: c['name'] as String?,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('paths.json: the paths of a head, from layer 3 to layer 4', () {
|
||||
// A head checks R1 and R8 in its third layer (ERR_NON_CANONICAL_CBOR),
|
||||
// comparing UTF-8 bytes, never UTF-16 code units; then each entry,
|
||||
// with R2 to R10, and R7 and R9 over the tree (ERR_HEAD_INVALID).
|
||||
// The decoder of the head, in stage 4c, does this with the CBOR; here
|
||||
// it is done with the rules of pathrule.dart.
|
||||
final trees = (paths['trees']! as List).cast<Map<String, Object?>>();
|
||||
expect(trees, hasLength(greaterThan(10)));
|
||||
var utf16Differs = false;
|
||||
for (final c in trees) {
|
||||
final name = c['name']! as String;
|
||||
final list = (c['paths']! as List).cast<String>();
|
||||
var result = 'ok';
|
||||
var detail = '';
|
||||
for (var i = 0; i < list.length && result == 'ok'; i++) {
|
||||
final n = utf8Bytes(list[i]).length;
|
||||
if (n < 1 ||
|
||||
n > maxPathLen ||
|
||||
(i > 0 &&
|
||||
compareBytes(utf8Bytes(list[i - 1]), utf8Bytes(list[i])) >=
|
||||
0)) {
|
||||
result = 'ERR_NON_CANONICAL_CBOR';
|
||||
}
|
||||
if (i > 0 && list[i - 1].compareTo(list[i]) >= 0 && result == 'ok') {
|
||||
utf16Differs = true;
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < list.length && result == 'ok'; i++) {
|
||||
final o = outcome(() => checkPath(list[i]));
|
||||
if (o != 'ok') {
|
||||
result = 'ERR_HEAD_INVALID';
|
||||
detail = 'file ${i + 1}: $o';
|
||||
}
|
||||
}
|
||||
if (result == 'ok') {
|
||||
final o = outcome(() => checkTree(list));
|
||||
if (o != 'ok') {
|
||||
result = 'ERR_HEAD_INVALID';
|
||||
detail = o;
|
||||
}
|
||||
}
|
||||
expect(result, c['result'], reason: name);
|
||||
expect(detail, c['detail'] ?? '', reason: name);
|
||||
}
|
||||
// U+FF5E before U+1F600 is in UTF-8 order, not in UTF-16 order.
|
||||
expect(utf16Differs, isTrue);
|
||||
});
|
||||
|
||||
test('path_fold.json: the NFD and the key of R7', () {
|
||||
final keys = (fold['keys']! as List).cast<Map<String, Object?>>();
|
||||
expect(keys, hasLength(greaterThan(20)));
|
||||
for (final c in keys) {
|
||||
final s = c['segment']! as String;
|
||||
expect(nfd(s), c['nfd'], reason: c['name'] as String?);
|
||||
expect(pathKey(s), c['key'], reason: c['name'] as String?);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('every code point of the other planes gives the results of Go', () {
|
||||
// Planes 0, 1 and 14 run in pathrule_test.dart, also compiled to
|
||||
// JavaScript.
|
||||
final vectors = jsonDecode(pathruleVectorsJson) as Map<String, Object?>;
|
||||
final planes = (vectors['planes']! as List).cast<Map<String, Object?>>();
|
||||
expect(planes, hasLength(17));
|
||||
for (final p in planes) {
|
||||
final plane = p['plane']! as int;
|
||||
if (!sharedPlanes.contains(plane)) checkPlane(plane, p);
|
||||
}
|
||||
});
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue