Skip to content

Commit 53781d6

Browse files
[AttributedText] - Make constructor agnostic to placeholder order in placeholder map (Resolves #2786) (#2787)
1 parent f4c5bbe commit 53781d6

File tree

2 files changed

+47
-3
lines changed

2 files changed

+47
-3
lines changed

attributed_text/lib/src/attributed_text.dart

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,14 @@ class AttributedText {
5151
AttributedSpans? spans,
5252
Map<int, Object>? placeholders,
5353
]) : _text = text ?? "",
54-
spans = spans ?? AttributedSpans(),
55-
placeholders = placeholders ?? <int, Object>{} {
54+
spans = spans ?? AttributedSpans() {
55+
// Sort the placeholders map so that we can always assume the entries
56+
// iterate in the placeholder content order. This knowledge is relied upon
57+
// in multiple places in this class.
58+
this.placeholders = Map.fromEntries(
59+
placeholders?.entries.sorted((a, b) => a.key - b.key) ?? [],
60+
);
61+
5662
assert(() {
5763
// ^ Run this in an assert with a callback so that the validation doesn't run in
5864
// production and cost processor cycles.
@@ -70,6 +76,7 @@ class AttributedText {
7076
final buffer = StringBuffer();
7177
int start = 0;
7278
int insertedPlaceholders = 0;
79+
7380
for (final entry in this.placeholders.entries) {
7481
final textSegment = _text.substring(start - insertedPlaceholders, entry.key - insertedPlaceholders);
7582
buffer.write(textSegment);
@@ -160,8 +167,11 @@ class AttributedText {
160167
/// Placeholders that represent non-text content, e.g., inline images, that
161168
/// should appear in the rendered text.
162169
///
170+
/// The entries in this map are in content-order, e.g., the entry for a placeholder
171+
/// at index 3 comes before an entry whose placeholder is at index 5.
172+
///
163173
/// In terms of [length], each placeholder is treated as a single character.
164-
final Map<int, Object> placeholders;
174+
late final Map<int, Object> placeholders;
165175

166176
/// Returns the `length` of this [AttributedText], which includes the length
167177
/// of the plain text `String`, and the number of [placeholders].

attributed_text/test/attributed_text_placeholders_test.dart

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,40 @@ void main() {
2121
throwsA(isA<AssertionError>()),
2222
);
2323
});
24+
25+
test("is order agnostic", () {
26+
// Ensure that constructors don't below up when the placeholder
27+
// entry order isn't the same as content order, and also ensure that
28+
// equality doesn't care about the map order.
29+
30+
final order1 = AttributedText("Hello, World!", null, {
31+
3: const _FakePlaceholder("first"),
32+
6: const _FakePlaceholder("second"),
33+
9: const _FakePlaceholder("third"),
34+
});
35+
36+
final order2 = AttributedText("Hello, World!", null, {
37+
6: const _FakePlaceholder("second"),
38+
3: const _FakePlaceholder("first"),
39+
9: const _FakePlaceholder("third"),
40+
});
41+
42+
final order3 = AttributedText("Hello, World!", null, {
43+
6: const _FakePlaceholder("second"),
44+
9: const _FakePlaceholder("third"),
45+
3: const _FakePlaceholder("first"),
46+
});
47+
48+
final order4 = AttributedText("Hello, World!", null, {
49+
9: const _FakePlaceholder("third"),
50+
6: const _FakePlaceholder("second"),
51+
3: const _FakePlaceholder("first"),
52+
});
53+
54+
expect(order1, equals(order2));
55+
expect(order1, equals(order3));
56+
expect(order1, equals(order4));
57+
});
2458
});
2559

2660
group("length >", () {

0 commit comments

Comments
 (0)