blob: 365de0c8677a27ec0eac144fd2bc2dcdda7dcb83 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. 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 'dart:typed_data';
import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'common.dart';
void main() {
internalBootstrapBrowserTest(() => testMain);
}
void testMain() {
setUpCanvasKitTest();
group('$fragmentUsingIntlSegmenter', () {
test('fragments text into words', () {
final Uint32List breaks = fragmentUsingIntlSegmenter(
'Hello world 你好世界',
IntlSegmenterGranularity.word,
);
expect(
breaks,
orderedEquals(<int>[0, 5, 6, 11, 12, 14, 16]),
);
});
test('fragments multi-line text into words', () {
final Uint32List breaks = fragmentUsingIntlSegmenter(
'Lorem ipsum\ndolor 你好世界 sit\namet',
IntlSegmenterGranularity.word,
);
expect(
breaks,
orderedEquals(<int>[
0, 5, 6, 11, 12, // "Lorem ipsum\n"
17, 18, 20, 22, 23, 26, 27, // "dolor 你好世界 sit\n"
31, // "amet"
]),
);
});
test('fragments text into grapheme clusters', () {
// The smiley emoji has a length of 2.
// The family emoji has a length of 11.
final Uint32List breaks = fragmentUsingIntlSegmenter(
'Lorem🙂ipsum👨‍👩‍👧‍👦',
IntlSegmenterGranularity.grapheme,
);
expect(
breaks,
orderedEquals(<int>[
0, 1, 2, 3, 4, 5, 7, // "Lorem🙂"
8, 9, 10, 11, 12, 23, // "ipsum👨‍👩‍👧‍👦"
]),
);
});
test('fragments multi-line text into grapheme clusters', () {
// The smiley emojis have a length of 2 each.
// The family emoji has a length of 11.
final Uint32List breaks = fragmentUsingIntlSegmenter(
'Lorem🙂\nipsum👨‍👩‍👧‍👦dolor\n😄',
IntlSegmenterGranularity.grapheme,
);
expect(
breaks,
orderedEquals(<int>[
0, 1, 2, 3, 4, 5, 7, 8, // "Lorem🙂\n"
9, 10, 11, 12, 13, 24, // "ipsum👨‍👩‍👧‍👦"
25, 26, 27, 28, 29, 30, 32, // "dolor😄\n"
]),
);
});
}, skip: !browserSupportsCanvaskitChromium);
group('$fragmentUsingV8LineBreaker', () {
const int kSoft = 0;
const int kHard = 1;
test('fragments text into soft and hard line breaks', () {
final Uint32List breaks = fragmentUsingV8LineBreaker(
'Lorem-ipsum 你好🙂\nDolor sit',
);
expect(
breaks,
orderedEquals(<int>[
0, kSoft,
6, kSoft, // "Lorem-"
12, kSoft, // "ipsum "
13, kSoft, // "你"
14, kSoft, // "好"
17, kHard, // "🙂\n"
23, kSoft, // "Dolor "
26, kSoft, // "sit"
]),
);
});
}, skip: !browserSupportsCanvaskitChromium);
group('segmentText', () {
setUp(() {
segmentationCache.clear();
});
tearDown(() {
segmentationCache.clear();
});
test('segments correctly', () {
const String text = 'Lorem-ipsum 你好🙂\nDolor sit';
final SegmentationResult segmentation = segmentText(text);
expect(
segmentation.words,
fragmentUsingIntlSegmenter(text, IntlSegmenterGranularity.word),
);
expect(
segmentation.graphemes,
fragmentUsingIntlSegmenter(text, IntlSegmenterGranularity.grapheme),
);
expect(
segmentation.breaks,
fragmentUsingV8LineBreaker(text),
);
});
test('caches segmentation results in LRU fashion', () {
const String text1 = 'hello';
segmentText(text1);
expect(segmentationCache.small.debugItemQueue, hasLength(1));
expect(segmentationCache.small[text1], isNotNull);
const String text2 = 'world';
segmentText(text2);
expect(segmentationCache.small.debugItemQueue, hasLength(2));
expect(segmentationCache.small[text2], isNotNull);
// "world" was segmented last, so it should be first, as in most recently used.
expect(segmentationCache.small.debugItemQueue.first.key, 'world');
expect(segmentationCache.small.debugItemQueue.last.key, 'hello');
});
test('puts segmentation results in the appropriate cache', () {
final String smallText = 'a' * (kSmallParagraphCacheSpec.maxTextLength - 1);
segmentText(smallText);
expect(segmentationCache.small.debugItemQueue, hasLength(1));
expect(segmentationCache.medium.debugItemQueue, hasLength(0));
expect(segmentationCache.large.debugItemQueue, hasLength(0));
expect(segmentationCache.small[smallText], isNotNull);
segmentationCache.clear();
final String mediumText = 'a' * (kMediumParagraphCacheSpec.maxTextLength - 1);
segmentText(mediumText);
expect(segmentationCache.small.debugItemQueue, hasLength(0));
expect(segmentationCache.medium.debugItemQueue, hasLength(1));
expect(segmentationCache.large.debugItemQueue, hasLength(0));
expect(segmentationCache.medium[mediumText], isNotNull);
segmentationCache.clear();
final String largeText = 'a' * (kLargeParagraphCacheSpec.maxTextLength - 1);
segmentText(largeText);
expect(segmentationCache.small.debugItemQueue, hasLength(0));
expect(segmentationCache.medium.debugItemQueue, hasLength(0));
expect(segmentationCache.large.debugItemQueue, hasLength(1));
expect(segmentationCache.large[largeText], isNotNull);
segmentationCache.clear();
// Should not cache extremely large texts.
final String tooLargeText = 'a' * (kLargeParagraphCacheSpec.maxTextLength + 1);
segmentText(tooLargeText);
expect(segmentationCache.small.debugItemQueue, hasLength(0));
expect(segmentationCache.medium.debugItemQueue, hasLength(0));
expect(segmentationCache.large.debugItemQueue, hasLength(0));
segmentationCache.clear();
});
test('has a limit on the number of entries', () {
testCacheCapacity(segmentationCache.small, kSmallParagraphCacheSpec);
testCacheCapacity(segmentationCache.medium, kMediumParagraphCacheSpec);
testCacheCapacity(segmentationCache.large, kLargeParagraphCacheSpec);
}, timeout: const Timeout.factor(4));
}, skip: !browserSupportsCanvaskitChromium);
}
void testCacheCapacity(
LruCache<String, SegmentationResult> cache,
SegmentationCacheSpec spec,
) {
// 1. Fill the cache.
for (int i = 0; i < spec.cacheSize; i++) {
final String text = _randomString(spec.maxTextLength);
segmentText(text);
// The segmented text should have been added to the cache.
// TODO(mdebbar): This may fail if the random string generator generates
// the same string twice.
expect(cache.debugItemQueue, hasLength(i + 1));
}
// 2. Make sure the cache is full.
expect(cache.length, spec.cacheSize);
// 3. Add more items to the cache.
for (int i = 0; i < 10; i++) {
final String text = _randomString(spec.maxTextLength);
segmentText(text);
// The cache size should remain the same.
expect(cache.debugItemQueue, hasLength(spec.cacheSize));
}
// 4. Clear the cache.
cache.clear();
}
int _seed = 0;
String _randomString(int length) {
const String allChars = ' 1234567890'
'abcdefghijklmnopqrstuvwxyz'
'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
final String text = '*' * length;
return text.replaceAllMapped(
'*',
// Passing a seed so the results are reproducible.
(_) => allChars[Random(_seed++).nextInt(allChars.length)],
);
}