@@ -139,6 +139,72 @@ void main() {
139
139
expect (_RenderTest ().publicNameForSlot (slot), slot.toString ());
140
140
});
141
141
142
+ testWidgets ('key reparenting' , (WidgetTester tester) async {
143
+ const Widget widget1 = SizedBox (key: ValueKey <String >('smol' ), height: 10 , width: 10 );
144
+ const Widget widget2 = SizedBox (key: ValueKey <String >('big' ), height: 100 , width: 100 );
145
+ const Widget nullWidget = SizedBox (key: ValueKey <String >('null' ), height: 50 , width: 50 );
146
+
147
+ await tester.pumpWidget (buildWidget (topLeft: widget1, bottomRight: widget2, nullSlot: nullWidget));
148
+ final _RenderDiagonal renderObject = tester.renderObject (find.byType (_Diagonal ));
149
+ expect (renderObject._topLeft! .size, const Size (10 , 10 ));
150
+ expect (renderObject._bottomRight! .size, const Size (100 , 100 ));
151
+ expect (renderObject._nullSlot! .size, const Size (50 , 50 ));
152
+
153
+ final Element widget1Element = tester.element (find.byWidget (widget1));
154
+ final Element widget2Element = tester.element (find.byWidget (widget2));
155
+ final Element nullWidgetElement = tester.element (find.byWidget (nullWidget));
156
+
157
+ // Swapping 1 and 2.
158
+ await tester.pumpWidget (buildWidget (topLeft: widget2, bottomRight: widget1, nullSlot: nullWidget));
159
+ expect (renderObject._topLeft! .size, const Size (100 , 100 ));
160
+ expect (renderObject._bottomRight! .size, const Size (10 , 10 ));
161
+ expect (renderObject._nullSlot! .size, const Size (50 , 50 ));
162
+ expect (widget1Element, same (tester.element (find.byWidget (widget1))));
163
+ expect (widget2Element, same (tester.element (find.byWidget (widget2))));
164
+ expect (nullWidgetElement, same (tester.element (find.byWidget (nullWidget))));
165
+
166
+ // Shifting slots
167
+ await tester.pumpWidget (buildWidget (topLeft: nullWidget, bottomRight: widget2, nullSlot: widget1));
168
+ expect (renderObject._topLeft! .size, const Size (50 , 50 ));
169
+ expect (renderObject._bottomRight! .size, const Size (100 , 100 ));
170
+ expect (renderObject._nullSlot! .size, const Size (10 , 10 ));
171
+ expect (widget1Element, same (tester.element (find.byWidget (widget1))));
172
+ expect (widget2Element, same (tester.element (find.byWidget (widget2))));
173
+ expect (nullWidgetElement, same (tester.element (find.byWidget (nullWidget))));
174
+
175
+ // Moving + Deleting.
176
+ await tester.pumpWidget (buildWidget (bottomRight: widget2));
177
+ expect (renderObject._topLeft, null );
178
+ expect (renderObject._bottomRight! .size, const Size (100 , 100 ));
179
+ expect (renderObject._nullSlot, null );
180
+ expect (widget1Element.debugIsDefunct, isTrue);
181
+ expect (nullWidgetElement.debugIsDefunct, isTrue);
182
+ expect (widget2Element, same (tester.element (find.byWidget (widget2))));
183
+
184
+ // Moving.
185
+ await tester.pumpWidget (buildWidget (topLeft: widget2));
186
+ expect (renderObject._topLeft! .size, const Size (100 , 100 ));
187
+ expect (renderObject._bottomRight, null );
188
+ expect (widget2Element, same (tester.element (find.byWidget (widget2))));
189
+ });
190
+
191
+ testWidgets ('duplicated key error message' , (WidgetTester tester) async {
192
+ const Widget widget1 = SizedBox (key: ValueKey <String >('widget 1' ), height: 10 , width: 10 );
193
+ const Widget widget2 = SizedBox (key: ValueKey <String >('widget 1' ), height: 100 , width: 100 );
194
+ const Widget widget3 = SizedBox (key: ValueKey <String >('widget 1' ), height: 50 , width: 50 );
195
+
196
+ await tester.pumpWidget (buildWidget (topLeft: widget1, bottomRight: widget2, nullSlot: widget3));
197
+
198
+ expect ((tester.takeException () as FlutterError ).toString (), equalsIgnoringHashCodes (
199
+ 'Multiple widgets used the same key in _Diagonal.\n '
200
+ "The key [<'widget 1'>] was used by multiple widgets. The parents of those widgets were:\n "
201
+ " - SizedBox-[<'widget 1'>](width: 50.0, height: 50.0, renderObject: RenderConstrainedBox#00000 NEEDS-LAYOUT NEEDS-PAINT)\n "
202
+ " - SizedBox-[<'widget 1'>](width: 10.0, height: 10.0, renderObject: RenderConstrainedBox#00000 NEEDS-LAYOUT NEEDS-PAINT)\n "
203
+ " - SizedBox-[<'widget 1'>](width: 100.0, height: 100.0, renderObject: RenderConstrainedBox#a4685 NEEDS-LAYOUT NEEDS-PAINT)\n "
204
+ 'A key can only be specified on one widget at a time in the same parent widget.'
205
+ ));
206
+ });
207
+
142
208
testWidgets ('debugDescribeChildren' , (WidgetTester tester) async {
143
209
await tester.pumpWidget (buildWidget (
144
210
topLeft: const SizedBox (
@@ -178,14 +244,15 @@ _RenderDiagonal#00000 relayoutBoundary=up1
178
244
});
179
245
}
180
246
181
- Widget buildWidget ({Widget ? topLeft, Widget ? bottomRight}) {
247
+ Widget buildWidget ({Widget ? topLeft, Widget ? bottomRight, Widget ? nullSlot }) {
182
248
return Directionality (
183
249
textDirection: TextDirection .ltr,
184
250
child: Align (
185
251
alignment: Alignment .topLeft,
186
252
child: _Diagonal (
187
253
topLeft: topLeft,
188
254
bottomRight: bottomRight,
255
+ nullSlot: nullSlot,
189
256
),
190
257
),
191
258
);
@@ -196,21 +263,25 @@ enum _DiagonalSlot {
196
263
bottomRight,
197
264
}
198
265
199
- class _Diagonal extends RenderObjectWidget with SlottedMultiChildRenderObjectWidgetMixin <_DiagonalSlot > {
266
+ class _Diagonal extends RenderObjectWidget with SlottedMultiChildRenderObjectWidgetMixin <_DiagonalSlot ? > {
200
267
const _Diagonal ({
201
268
this .topLeft,
202
269
this .bottomRight,
270
+ this .nullSlot,
203
271
});
204
272
205
273
final Widget ? topLeft;
206
274
final Widget ? bottomRight;
275
+ final Widget ? nullSlot;
207
276
208
277
@override
209
- Iterable <_DiagonalSlot > get slots => _DiagonalSlot . values;
278
+ Iterable <_DiagonalSlot ? > get slots => < _DiagonalSlot ? > [ null , ... _DiagonalSlot . values] ;
210
279
211
280
@override
212
- Widget ? childForSlot (_DiagonalSlot slot) {
281
+ Widget ? childForSlot (_DiagonalSlot ? slot) {
213
282
switch (slot) {
283
+ case null :
284
+ return nullSlot;
214
285
case _DiagonalSlot .topLeft:
215
286
return topLeft;
216
287
case _DiagonalSlot .bottomRight:
@@ -219,16 +290,17 @@ class _Diagonal extends RenderObjectWidget with SlottedMultiChildRenderObjectWid
219
290
}
220
291
221
292
@override
222
- SlottedContainerRenderObjectMixin <_DiagonalSlot > createRenderObject (
293
+ SlottedContainerRenderObjectMixin <_DiagonalSlot ? > createRenderObject (
223
294
BuildContext context,
224
295
) {
225
296
return _RenderDiagonal ();
226
297
}
227
298
}
228
299
229
- class _RenderDiagonal extends RenderBox with SlottedContainerRenderObjectMixin <_DiagonalSlot > {
300
+ class _RenderDiagonal extends RenderBox with SlottedContainerRenderObjectMixin <_DiagonalSlot ? > {
230
301
RenderBox ? get _topLeft => childForSlot (_DiagonalSlot .topLeft);
231
302
RenderBox ? get _bottomRight => childForSlot (_DiagonalSlot .bottomRight);
303
+ RenderBox ? get _nullSlot => childForSlot (null );
232
304
233
305
@override
234
306
void performLayout () {
@@ -251,9 +323,17 @@ class _RenderDiagonal extends RenderBox with SlottedContainerRenderObjectMixin<_
251
323
bottomRightSize = _bottomRight! .size;
252
324
}
253
325
326
+ Size nullSlotSize = Size .zero;
327
+ final RenderBox ? nullSlot = _nullSlot;
328
+ if (nullSlot != null ) {
329
+ nullSlot.layout (childConstraints, parentUsesSize: true );
330
+ _positionChild (nullSlot, Offset .zero);
331
+ nullSlotSize = nullSlot.size;
332
+ }
333
+
254
334
size = constraints.constrain (Size (
255
- topLeftSize.width + bottomRightSize.width,
256
- topLeftSize.height + bottomRightSize.height,
335
+ topLeftSize.width + bottomRightSize.width + nullSlotSize.width ,
336
+ topLeftSize.height + bottomRightSize.height + nullSlotSize.height ,
257
337
));
258
338
}
259
339
0 commit comments