Skip to content

Commit 1b139bd

Browse files
committed
test: cover font char slot + invalid framerate; run prepare_submit
Made-with: Cursor
1 parent b11b5bd commit 1b139bd

6 files changed

Lines changed: 147 additions & 18 deletions

File tree

lib/src/animation/content/fill_content.dart

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import 'dart:ui';
2-
32
import 'package:vector_math/vector_math_64.dart';
4-
53
import '../../l.dart';
64
import '../../lottie_drawable.dart';
75
import '../../lottie_property.dart';

lib/src/composition.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,7 @@ class LottieComposition {
204204

205205
/// Color slots from the root `"slots"` object in the Lottie JSON.
206206
/// Populated during parsing; immutable from outside after [parseJsonBytes].
207-
Map<String, Color> get colorSlots =>
208-
Map.unmodifiable(_parameters.colorSlots);
207+
Map<String, Color> get colorSlots => Map.unmodifiable(_parameters.colorSlots);
209208

210209
Marker? getMarker(String markerName) {
211210
for (var i = 0; i < markers.length; i++) {

lib/src/parser/lottie_composition_parser.dart

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import 'dart:ui';
2-
32
import '../composition.dart';
43
import '../lottie_image_asset.dart';
54
import '../model/animatable/animatable_color_value.dart';
@@ -284,8 +283,10 @@ class LottieCompositionParser {
284283

285284
static final JsonReaderOptions _slotPropNames = JsonReaderOptions.of(['p']);
286285

287-
static final JsonReaderOptions _slotInnerNames =
288-
JsonReaderOptions.of(['a', 'k']);
286+
static final JsonReaderOptions _slotInnerNames = JsonReaderOptions.of([
287+
'a',
288+
'k',
289+
]);
289290

290291
static void _parseSlots(
291292
JsonReader reader,
@@ -384,10 +385,7 @@ class LottieCompositionParser {
384385
}
385386
}
386387

387-
static void _walkColorValues(
388-
ContentModel shape,
389-
Map<String, Color> slots,
390-
) {
388+
static void _walkColorValues(ContentModel shape, Map<String, Color> slots) {
391389
if (shape is ShapeFill) {
392390
_maybeOverrideColor(shape.color, slots);
393391
} else if (shape is ShapeStroke) {
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
{
2+
"v": "5.4.4",
3+
"fr": 30,
4+
"ip": 0,
5+
"op": 60,
6+
"w": 100,
7+
"h": 100,
8+
"layers": [
9+
{
10+
"ddd": 0,
11+
"ind": 1,
12+
"ty": 4,
13+
"nm": "Filler Layer",
14+
"sr": 1,
15+
"ks": {
16+
"o": { "a": 0, "k": 100, "ix": 11 },
17+
"r": { "a": 0, "k": 0, "ix": 10 },
18+
"p": { "a": 0, "k": [50, 50, 0], "ix": 2 },
19+
"a": { "a": 0, "k": [0, 0, 0], "ix": 1 },
20+
"s": { "a": 0, "k": [100, 100, 100], "ix": 6 }
21+
},
22+
"ao": 0,
23+
"shapes": [],
24+
"ip": 0,
25+
"op": 60,
26+
"st": 0,
27+
"bm": 0
28+
}
29+
],
30+
"chars": [
31+
{
32+
"ch": "A",
33+
"size": 12,
34+
"w": 10,
35+
"style": "Regular",
36+
"fFamily": "Test",
37+
"data": {
38+
"shapes": [
39+
{
40+
"ty": "gr",
41+
"it": [
42+
{
43+
"ty": "fl",
44+
"c": {
45+
"a": 0,
46+
"k": [0, 1, 0, 1],
47+
"sid": "primary"
48+
},
49+
"o": { "a": 0, "k": 100, "ix": 5 },
50+
"r": 1,
51+
"nm": "Char Fill",
52+
"hd": false
53+
},
54+
{
55+
"ty": "tr",
56+
"p": { "a": 0, "k": [0, 0], "ix": 2 },
57+
"a": { "a": 0, "k": [0, 0], "ix": 1 },
58+
"s": { "a": 0, "k": [100, 100], "ix": 3 },
59+
"r": { "a": 0, "k": 0, "ix": 6 },
60+
"o": { "a": 0, "k": 100, "ix": 7 },
61+
"sk": { "a": 0, "k": 0, "ix": 4 },
62+
"sa": { "a": 0, "k": 0, "ix": 5 },
63+
"nm": "Transform"
64+
}
65+
],
66+
"nm": "Char Group"
67+
}
68+
]
69+
}
70+
}
71+
],
72+
"slots": {
73+
"primary": {
74+
"p": { "a": 0, "k": [1, 0, 0, 1] }
75+
}
76+
}
77+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"v": "5.4.4",
3+
"fr": 0,
4+
"ip": 0,
5+
"op": 60,
6+
"w": 100,
7+
"h": 100,
8+
"layers": []
9+
}

test/slots_test.dart

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import 'dart:io';
22
import 'dart:ui';
3-
43
import 'package:flutter_test/flutter_test.dart';
54
import 'package:lottie/lottie.dart';
65
import 'package:lottie/src/model/content/content_model.dart';
@@ -14,15 +13,21 @@ void main() {
1413
expect(composition.colorSlots['primary'], const Color(0xFFFF0000));
1514
final fills = _collectFills(composition);
1615
expect(fills, hasLength(1));
17-
expect(fills.single.color?.keyframes.single.startValue, const Color(0xFFFF0000));
16+
expect(
17+
fills.single.color?.keyframes.single.startValue,
18+
const Color(0xFFFF0000),
19+
);
1820
expect(fills.single.color?.slotId, isNull);
1921
});
2022

2123
test('empty slot definition does not crash', () async {
2224
final composition = await _loadComposition('empty_slot.json');
2325
expect(composition.colorSlots, isEmpty);
2426
final fills = _collectFills(composition);
25-
expect(fills.single.color?.keyframes.single.startValue, const Color(0xFF0000FF));
27+
expect(
28+
fills.single.color?.keyframes.single.startValue,
29+
const Color(0xFF0000FF),
30+
);
2631
});
2732

2833
test('sid without matching slot keeps encoded color', () async {
@@ -42,8 +47,14 @@ void main() {
4247
expect(composition.colorSlots['secondary'], const Color(0xFF00FF00));
4348
final fills = _collectFills(composition);
4449
expect(fills, hasLength(2));
45-
expect(fills[0].color?.keyframes.single.startValue, const Color(0xFFFF0000));
46-
expect(fills[1].color?.keyframes.single.startValue, const Color(0xFF00FF00));
50+
expect(
51+
fills[0].color?.keyframes.single.startValue,
52+
const Color(0xFFFF0000),
53+
);
54+
expect(
55+
fills[1].color?.keyframes.single.startValue,
56+
const Color(0xFF00FF00),
57+
);
4758
expect(fills[0].color?.slotId, isNull);
4859
expect(fills[1].color?.slotId, isNull);
4960
});
@@ -85,7 +96,10 @@ void main() {
8596
for (final layer in precompLayers!) {
8697
_collectFillsFromShapes(layer.shapes, fills);
8798
}
88-
expect(fills.single.color?.keyframes.single.startValue, const Color(0xFFFF0000));
99+
expect(
100+
fills.single.color?.keyframes.single.startValue,
101+
const Color(0xFFFF0000),
102+
);
89103
expect(fills.single.color?.slotId, isNull);
90104
});
91105

@@ -95,9 +109,43 @@ void main() {
95109
expect(composition.colorSlots.containsKey('stringSlot'), isFalse);
96110
expect(composition.colorSlots.containsKey('badInner'), isFalse);
97111
final fills = _collectFills(composition);
98-
expect(fills.single.color?.keyframes.single.startValue, const Color(0xFFFF0000));
112+
expect(
113+
fills.single.color?.keyframes.single.startValue,
114+
const Color(0xFFFF0000),
115+
);
99116
expect(fills.single.color?.slotId, isNull);
100117
});
118+
119+
test('color slot resolves inside font character shapes', () async {
120+
final composition = await _loadComposition('font_char_slot.json');
121+
expect(composition.colorSlots['primary'], const Color(0xFFFF0000));
122+
expect(composition.characters, isNotEmpty);
123+
final fills = <ShapeFill>[];
124+
for (final char in composition.characters.values) {
125+
for (final group in char.shapes) {
126+
_collectFillsFromShapes(group.items, fills);
127+
}
128+
}
129+
expect(fills, hasLength(1));
130+
expect(
131+
fills.single.color?.keyframes.single.startValue,
132+
const Color(0xFFFF0000),
133+
);
134+
expect(fills.single.color?.slotId, isNull);
135+
});
136+
137+
test('invalid framerate triggers assertion with message', () async {
138+
await expectLater(
139+
_loadComposition('invalid_framerate.json'),
140+
throwsA(
141+
isA<AssertionError>().having(
142+
(e) => e.message?.toString() ?? '',
143+
'message',
144+
contains('invalid framerate'),
145+
),
146+
),
147+
);
148+
});
101149
}
102150

103151
Future<LottieComposition> _loadComposition(String fileName) async {

0 commit comments

Comments
 (0)