128 lines
4.4 KiB
Dart
128 lines
4.4 KiB
Dart
|
|
import 'dart:math' as math;
|
||
|
|
|
||
|
|
import 'package:flutter_test/flutter_test.dart';
|
||
|
|
import 'package:halkyon_learn_flutter/src/feature/kanji/data/stroke_repository.dart';
|
||
|
|
import 'package:halkyon_learn_flutter/src/feature/kanji/model/character_strokes.dart';
|
||
|
|
import 'package:halkyon_learn_flutter/src/feature/kanji/model/stroke_check.dart';
|
||
|
|
import 'package:halkyon_learn_flutter/src/feature/study/model/rating.dart';
|
||
|
|
|
||
|
|
/// Characters whose strokes the checks are tried on: kanji of few and many
|
||
|
|
/// strokes, parallel ones whose order matters, crossing ones, and kana.
|
||
|
|
const _characters = ['三', '十', '日', '木', '食', '語', 'あ', 'ア'];
|
||
|
|
|
||
|
|
void main() {
|
||
|
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||
|
|
late Map<String, List<List<StrokePoint>>> strokes;
|
||
|
|
|
||
|
|
setUpAll(() async {
|
||
|
|
final repository = StrokeRepository();
|
||
|
|
strokes = {
|
||
|
|
for (final character in _characters)
|
||
|
|
character: (await repository.strokesOf(character))!.polylines(),
|
||
|
|
};
|
||
|
|
});
|
||
|
|
|
||
|
|
/// [points] moved by [dx], [dy].
|
||
|
|
List<StrokePoint> moved(List<StrokePoint> points, double dx, double dy) => [
|
||
|
|
for (final (:x, :y) in points) (x: x + dx, y: y + dy),
|
||
|
|
];
|
||
|
|
|
||
|
|
/// [points] grown by [factor] around their middle.
|
||
|
|
List<StrokePoint> grown(List<StrokePoint> points, double factor) {
|
||
|
|
final cx = points.map((p) => p.x).reduce((a, b) => a + b) / points.length;
|
||
|
|
final cy = points.map((p) => p.y).reduce((a, b) => a + b) / points.length;
|
||
|
|
return [
|
||
|
|
for (final (:x, :y) in points)
|
||
|
|
(x: cx + (x - cx) * factor, y: cy + (y - cy) * factor),
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
/// [points] as a hand wavers: off the line by up to [amplitude], twice
|
||
|
|
/// along it.
|
||
|
|
List<StrokePoint> wavering(List<StrokePoint> points, double amplitude) => [
|
||
|
|
for (final (i, (:x, :y)) in points.indexed)
|
||
|
|
(
|
||
|
|
x: x + amplitude * math.sin(i / points.length * 4 * math.pi),
|
||
|
|
y: y + amplitude * math.cos(i / points.length * 4 * math.pi),
|
||
|
|
),
|
||
|
|
];
|
||
|
|
|
||
|
|
test('a stroke written as it should be counts, also moved a little, '
|
||
|
|
'wavering, or a size larger or smaller', () {
|
||
|
|
for (final MapEntry(key: character, value: lines) in strokes.entries) {
|
||
|
|
for (final (i, line) in lines.indexed) {
|
||
|
|
for (final (how, written) in [
|
||
|
|
('as drawn', line),
|
||
|
|
('moved', moved(line, 5, -5)),
|
||
|
|
('wavering', wavering(line, 1.5)),
|
||
|
|
('larger', grown(line, 1.2)),
|
||
|
|
('smaller', grown(line, 0.85)),
|
||
|
|
]) {
|
||
|
|
expect(
|
||
|
|
checkStroke(written, lines, i),
|
||
|
|
StrokeVerdict.right,
|
||
|
|
reason: '$character, stroke ${i + 1}, $how',
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
test('a stroke written the other way round does not count, but for a '
|
||
|
|
'dot, which goes no way to speak of', () {
|
||
|
|
for (final MapEntry(key: character, value: lines) in strokes.entries) {
|
||
|
|
for (final (i, line) in lines.indexed) {
|
||
|
|
if (lengthOf(line) < 12) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
expect(
|
||
|
|
checkStroke(line.reversed.toList(), lines, i),
|
||
|
|
isNot(StrokeVerdict.right),
|
||
|
|
reason: '$character, stroke ${i + 1}',
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
test('a later stroke written before its turn is out of order', () {
|
||
|
|
for (final MapEntry(key: character, value: lines) in strokes.entries) {
|
||
|
|
for (var i = 0; i < lines.length; i++) {
|
||
|
|
for (var j = i + 1; j < lines.length; j++) {
|
||
|
|
expect(
|
||
|
|
checkStroke(lines[j], lines, i),
|
||
|
|
StrokeVerdict.wrongOrder,
|
||
|
|
reason: '$character: stroke ${j + 1} for ${i + 1}',
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
test('a stroke nowhere near any to come is wrong', () {
|
||
|
|
final lines = strokes['十']!;
|
||
|
|
// A diagonal through the box.
|
||
|
|
final slash = [
|
||
|
|
for (var t = 0.0; t <= 1; t += 0.02) (x: 10 + 80 * t, y: 10 + 80 * t),
|
||
|
|
];
|
||
|
|
expect(checkStroke(slash, lines, 0), StrokeVerdict.wrong);
|
||
|
|
expect(checkStroke(const [(x: 50, y: 50)], lines, 0), StrokeVerdict.wrong);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('the rating suggested: none wrong Good, one or two Hard, more '
|
||
|
|
'Again', () {
|
||
|
|
expect(
|
||
|
|
[
|
||
|
|
for (final mistakes in [0, 1, 2, 3, 7]) suggestedRating(mistakes),
|
||
|
|
],
|
||
|
|
[Rating.good, Rating.hard, Rating.hard, Rating.again, Rating.again],
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('resampled: evenly along the line, the ends kept', () {
|
||
|
|
final points = resampled(const [(x: 0, y: 0), (x: 10, y: 0)], 11);
|
||
|
|
expect(points, hasLength(11));
|
||
|
|
expect(points.first, (x: 0.0, y: 0.0));
|
||
|
|
expect(points[5].x, closeTo(5, 1e-9));
|
||
|
|
expect(points.last, (x: 10.0, y: 0.0));
|
||
|
|
});
|
||
|
|
}
|