Bug 1874684 - Part 37: Fix unified compilation. r=allstarschh
[gecko.git] / layout / style / test / flexbox_layout_testcases.js
blobc8990877ab85e7acd6ffe8af46f84751e4b52e17
1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
2 /* vim: set ts=2 sw=2 sts=2 et: */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5  * License, v. 2.0. If a copy of the MPL was not distributed with this
6  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 /**
9  * For the purposes of this test, flex items are specified as a hash with a
10  * hash-entry for each CSS property that is to be set.  In these per-property
11  * entries, the key is the property-name, and the value can be either of the
12  * following:
13  *  (a) the property's specified value (which indicates that we don't need to
14  *      bother checking the computed value of this particular property)
15  *  ...OR...
16  *  (b) an array with 2-3 entries...
17  *        [specifiedValue, expectedComputedValue (, epsilon) ]
18  *      ...which indicates that the property's computed value should be
19  *      checked.  The array's first entry (for the specified value) may be
20  *      null; this means that no value should be explicitly specified for this
21  *      property. The second entry is the property's expected computed
22  *      value. The third (optional) entry is an epsilon value, which allows for
23  *      fuzzy equality when testing the computed value.
24  *
25  * To allow these testcases to be re-used in both horizontal and vertical
26  * flex containers, we specify "width"/"min-width"/etc. using the aliases
27  * "_main-size", "_min-main-size", etc.  The test code can map these
28  * placeholder names to their corresponding property-names using the maps
29  * defined below -- gRowPropertyMapping, gColumnPropertyMapping, etc.
30  *
31  * If the testcase needs to customize its flex container at all (e.g. by
32  * specifying a custom container-size), it can do so by including a hash
33  * called "container_properties", with propertyName:propertyValue mappings.
34  * (This hash can use aliased property-names like "_main-size" as well.)
35  */
37 // The standard main-size we'll use for our flex container when setting up
38 // the testcases defined below:
39 var gDefaultFlexContainerSize = "200px";
41 // Left-to-right versions of placeholder property-names used in
42 // testcases below:
43 var gRowPropertyMapping = {
44   "_main-size": "width",
45   "_min-main-size": "min-width",
46   "_max-main-size": "max-width",
47   "_border-main-start-width": "border-left-width",
48   "_border-main-end-width": "border-right-width",
49   "_padding-main-start": "padding-left",
50   "_padding-main-end": "padding-right",
51   "_margin-main-start": "margin-left",
52   "_margin-main-end": "margin-right",
55 // Right-to-left versions of placeholder property-names used in
56 // testcases below:
57 var gRowReversePropertyMapping = {
58   "_main-size": "width",
59   "_min-main-size": "min-width",
60   "_max-main-size": "max-width",
61   "_border-main-start-width": "border-right-width",
62   "_border-main-end-width": "border-left-width",
63   "_padding-main-start": "padding-right",
64   "_padding-main-end": "padding-left",
65   "_margin-main-start": "margin-right",
66   "_margin-main-end": "margin-left",
69 // Top-to-bottom versions of placeholder property-names used in
70 // testcases below:
71 var gColumnPropertyMapping = {
72   "_main-size": "height",
73   "_min-main-size": "min-height",
74   "_max-main-size": "max-height",
75   "_border-main-start-width": "border-top-width",
76   "_border-main-end-width": "border-bottom-width",
77   "_padding-main-start": "padding-top",
78   "_padding-main-end": "padding-bottom",
79   "_margin-main-start": "margin-top",
80   "_margin-main-end": "margin-bottom",
83 // Bottom-to-top versions of placeholder property-names used in
84 // testcases below:
85 var gColumnReversePropertyMapping = {
86   "_main-size": "height",
87   "_min-main-size": "min-height",
88   "_max-main-size": "max-height",
89   "_border-main-start-width": "border-bottom-width",
90   "_border-main-end-width": "border-top-width",
91   "_padding-main-start": "padding-bottom",
92   "_padding-main-end": "padding-top",
93   "_margin-main-start": "margin-bottom",
94   "_margin-main-end": "margin-top",
97 // The list of actual testcase definitions:
98 var gFlexboxTestcases = [
99   // No flex properties specified --> should just use 'width' for sizing
100   {
101     items: [
102       { "_main-size": ["40px", "40px"] },
103       { "_main-size": ["65px", "65px"] },
104     ],
105   },
106   // flex-basis is specified:
107   {
108     items: [
109       { "flex-basis": "50px", "_main-size": [null, "50px"] },
110       {
111         "flex-basis": "20px",
112         "_main-size": [null, "20px"],
113       },
114     ],
115   },
116   // flex-basis is *large* -- sum of flex-basis values is > flex container size:
117   // (w/ 0 flex-shrink so we don't shrink):
118   {
119     items: [
120       {
121         flex: "0 0 150px",
122         "_main-size": [null, "150px"],
123       },
124       {
125         flex: "0 0 90px",
126         "_main-size": [null, "90px"],
127       },
128     ],
129   },
130   // flex-basis is *large* -- each flex-basis value is > flex container size:
131   // (w/ 0 flex-shrink so we don't shrink):
132   {
133     items: [
134       {
135         flex: "0 0 250px",
136         "_main-size": [null, "250px"],
137       },
138       {
139         flex: "0 0 400px",
140         "_main-size": [null, "400px"],
141       },
142     ],
143   },
144   // flex-basis has percentage value:
145   {
146     items: [
147       {
148         "flex-basis": "30%",
149         "_main-size": [null, "60px"],
150       },
151       {
152         "flex-basis": "45%",
153         "_main-size": [null, "90px"],
154       },
155     ],
156   },
157   // flex-basis has calc(percentage) value:
158   {
159     items: [
160       {
161         "flex-basis": "calc(20%)",
162         "_main-size": [null, "40px"],
163       },
164       {
165         "flex-basis": "calc(80%)",
166         "_main-size": [null, "160px"],
167       },
168     ],
169   },
170   // flex-basis has calc(percentage +/- length) value:
171   {
172     items: [
173       {
174         "flex-basis": "calc(10px + 20%)",
175         "_main-size": [null, "50px"],
176       },
177       {
178         "flex-basis": "calc(60% - 1px)",
179         "_main-size": [null, "119px"],
180       },
181     ],
182   },
183   // flex-grow is specified:
184   {
185     items: [
186       {
187         flex: "1",
188         "_main-size": [null, "60px"],
189       },
190       {
191         flex: "2",
192         "_main-size": [null, "120px"],
193       },
194       {
195         flex: "0 20px",
196         "_main-size": [null, "20px"],
197       },
198     ],
199   },
200   // Same ratio as prev. testcase; making sure we handle float inaccuracy
201   {
202     items: [
203       {
204         flex: "100000",
205         "_main-size": [null, "60px"],
206       },
207       {
208         flex: "200000",
209         "_main-size": [null, "120px"],
210       },
211       {
212         flex: "0.000001 20px",
213         "_main-size": [null, "20px"],
214       },
215     ],
216   },
217   // Same ratio as prev. testcase, but with items cycled and w/
218   // "flex: none" & explicit size instead of "flex: 0 20px"
219   {
220     items: [
221       {
222         flex: "none",
223         "_main-size": ["20px", "20px"],
224       },
225       {
226         flex: "1",
227         "_main-size": [null, "60px"],
228       },
229       {
230         flex: "2",
231         "_main-size": [null, "120px"],
232       },
233     ],
234   },
236   // ...and now with flex-grow:[huge] to be sure we handle infinite float values
237   // gracefully.
238   {
239     items: [
240       {
241         flex: "9999999999999999999999999999999999999999999999999999999",
242         "_main-size": [null, "200px"],
243       },
244     ],
245   },
246   {
247     items: [
248       {
249         flex: "9999999999999999999999999999999999999999999999999999999",
250         "_main-size": [null, "50px"],
251       },
252       {
253         flex: "9999999999999999999999999999999999999999999999999999999",
254         "_main-size": [null, "50px"],
255       },
256       {
257         flex: "9999999999999999999999999999999999999999999999999999999",
258         "_main-size": [null, "50px"],
259       },
260       {
261         flex: "9999999999999999999999999999999999999999999999999999999",
262         "_main-size": [null, "50px"],
263       },
264     ],
265   },
266   {
267     items: [
268       {
269         flex: "99999999999999999999999999999999999",
270         "_main-size": [null, "50px"],
271       },
272       {
273         flex: "99999999999999999999999999999999999",
274         "_main-size": [null, "50px"],
275       },
276       {
277         flex: "99999999999999999999999999999999999",
278         "_main-size": [null, "50px"],
279       },
280       {
281         flex: "99999999999999999999999999999999999",
282         "_main-size": [null, "50px"],
283       },
284     ],
285   },
287   // And now, some testcases to check that we handle float accumulation error
288   // gracefully.
290   // First, a testcase with just a custom-sized huge container, to be sure we'll
291   // be able to handle content on that scale, in the subsequent more-complex
292   // testcases:
293   {
294     container_properties: {
295       "_main-size": "9000000px",
296     },
297     items: [
298       {
299         flex: "1",
300         "_main-size": [null, "9000000px"],
301       },
302     ],
303   },
304   // ...and now with two flex items dividing up that container's huge size:
305   {
306     container_properties: {
307       "_main-size": "9000000px",
308     },
309     items: [
310       {
311         flex: "2",
312         "_main-size": [null, "6000000px"],
313       },
314       {
315         flex: "1",
316         "_main-size": [null, "3000000px"],
317       },
318     ],
319   },
321   // OK, now to actually test accumulation error. Below, we have six flex items
322   // splitting up the container's size, with huge differences between flex
323   // weights.  For simplicity, I've set up the weights so that they sum exactly
324   // to the container's size in px. So 1 unit of flex *should* get you 1px.
325   //
326   // NOTE: The expected computed "_main-size" values for the flex items below
327   // appear to add up to more than their container's size, which would suggest
328   // that they overflow their container unnecessarily. But they don't actually
329   // overflow -- this discrepancy is simply because Gecko's code for reporting
330   // computed-sizes rounds to 6 significant figures (in particular, the method
331   // (nsTSubstring_CharT::AppendFloat() does this).  Internally, in app-units,
332   // the child frames' main-sizes add up exactly to the container's main-size,
333   // as you'd hope & expect.
334   {
335     container_properties: {
336       "_main-size": "9000000px",
337     },
338     items: [
339       {
340         flex: "3000000",
341         "_main-size": [null, "3000000px"],
342       },
343       {
344         flex: "1",
345         "_main-size": [null, "1px"],
346       },
347       {
348         flex: "1",
349         "_main-size": [null, "1px"],
350       },
351       {
352         flex: "2999999",
353         // NOTE: Expected value is off slightly, from float error when
354         // resolving flexible lengths & when generating computed value string:
355         "_main-size": [null, "3000000px"],
356       },
357       {
358         flex: "2999998",
359         // NOTE: Expected value is off slightly, from float error when
360         // resolving flexible lengths & when generating computed value string:
361         "_main-size": [null, "3000000px"],
362       },
363       {
364         flex: "1",
365         "_main-size": [null, "1px", 0.2],
366       },
367     ],
368   },
369   // Same flex items as previous testcase, but now reordered such that the items
370   // with tiny flex weights are all listed last:
371   {
372     container_properties: {
373       "_main-size": "9000000px",
374     },
375     items: [
376       {
377         flex: "3000000",
378         "_main-size": [null, "3000000px"],
379       },
380       {
381         flex: "2999999",
382         // NOTE: Expected value is off slightly, from float error when
383         // resolving flexible lengths & when generating computed value string:
384         "_main-size": [null, "3000000px"],
385       },
386       {
387         flex: "2999998",
388         // NOTE: Expected value is off slightly, from float error when
389         // resolving flexible lengths & when generating computed value string:
390         "_main-size": [null, "3000000px"],
391       },
392       {
393         flex: "1",
394         "_main-size": [null, "1px", 0.2],
395       },
396       {
397         flex: "1",
398         "_main-size": [null, "1px", 0.2],
399       },
400       {
401         flex: "1",
402         "_main-size": [null, "1px", 0.2],
403       },
404     ],
405   },
406   // Same flex items as previous testcase, but now reordered such that the items
407   // with tiny flex weights are all listed first:
408   {
409     container_properties: {
410       "_main-size": "9000000px",
411     },
412     items: [
413       {
414         flex: "1",
415         // NOTE: Expected value is off slightly, from float error when
416         // resolving flexible lengths:
417         "_main-size": [null, "1px", 0.2],
418       },
419       {
420         flex: "1",
421         // NOTE: Expected value is off slightly, from float error when
422         // resolving flexible lengths:
423         "_main-size": [null, "1px", 0.2],
424       },
425       {
426         flex: "1",
427         // NOTE: Expected value is off slightly, from float error when
428         // resolving flexible lengths:
429         "_main-size": [null, "1px", 0.2],
430       },
431       {
432         flex: "3000000",
433         "_main-size": [null, "3000000px"],
434       },
435       {
436         flex: "2999999",
437         // NOTE: Expected value is off slightly, from float error when
438         // resolving flexible lengths & when generating computed value string:
439         "_main-size": [null, "3000000px"],
440       },
441       {
442         flex: "2999998",
443         // NOTE: Expected value is off slightly, from float error when
444         // resolving flexible lengths & when generating computed value string:
445         "_main-size": [null, "3000000px"],
446       },
447     ],
448   },
450   // Trying "flex: auto" (== "1 1 auto") w/ a mix of flex-grow/flex-basis values
451   {
452     items: [
453       {
454         flex: "auto",
455         "_main-size": [null, "45px"],
456       },
457       {
458         flex: "2",
459         "_main-size": [null, "90px"],
460       },
461       {
462         flex: "20px 1 0",
463         "_main-size": [null, "65px"],
464       },
465     ],
466   },
467   // Same as previous, but with items cycled & different syntax
468   {
469     items: [
470       {
471         flex: "20px",
472         "_main-size": [null, "65px"],
473       },
474       {
475         flex: "1",
476         "_main-size": [null, "45px"],
477       },
478       {
479         flex: "2",
480         "_main-size": [null, "90px"],
481       },
482     ],
483   },
484   {
485     items: [
486       {
487         flex: "2",
488         "_main-size": [null, "100px"],
489         border: "0px dashed",
490         "_border-main-start-width": ["5px", "5px"],
491         "_border-main-end-width": ["15px", "15px"],
492         "_margin-main-start": ["22px", "22px"],
493         "_margin-main-end": ["8px", "8px"],
494       },
495       {
496         flex: "1",
497         "_main-size": [null, "50px"],
498         "_margin-main-start": ["auto", "0px"],
499         "_padding-main-end": ["auto", "0px"],
500       },
501     ],
502   },
503   // Test negative flexibility:
505   // Basic testcase: just 1 item (relying on initial "flex-shrink: 1") --
506   // should shrink to container size.
507   {
508     items: [{ "_main-size": ["400px", "200px"] }],
509   },
510   // ...and now with a "flex" specification and a different flex-shrink value:
511   {
512     items: [
513       {
514         flex: "4 2 250px",
515         "_main-size": [null, "200px"],
516       },
517     ],
518   },
519   // ...and now with multiple items, which all shrink proportionally (by 50%)
520   // to fit to the container, since they have the same (initial) flex-shrink val
521   {
522     items: [
523       { "_main-size": ["80px", "40px"] },
524       { "_main-size": ["40px", "20px"] },
525       { "_main-size": ["30px", "15px"] },
526       { "_main-size": ["250px", "125px"] },
527     ],
528   },
529   // ...and now with positive flexibility specified. (should have no effect, so
530   // everything still shrinks by the same proportion, since the flex-shrink
531   // values are all the same).
532   {
533     items: [
534       {
535         flex: "4 3 100px",
536         "_main-size": [null, "80px"],
537       },
538       {
539         flex: "5 3 50px",
540         "_main-size": [null, "40px"],
541       },
542       {
543         flex: "0 3 100px",
544         "_main-size": [null, "80px"],
545       },
546     ],
547   },
548   // ...and now with *different* flex-shrink values:
549   {
550     items: [
551       {
552         flex: "4 2 50px",
553         "_main-size": [null, "30px"],
554       },
555       {
556         flex: "5 3 50px",
557         "_main-size": [null, "20px"],
558       },
559       {
560         flex: "0 0 150px",
561         "_main-size": [null, "150px"],
562       },
563     ],
564   },
565   // Same ratio as prev. testcase; making sure we handle float inaccuracy
566   {
567     items: [
568       {
569         flex: "4 20000000 50px",
570         "_main-size": [null, "30px"],
571       },
572       {
573         flex: "5 30000000 50px",
574         "_main-size": [null, "20px"],
575       },
576       {
577         flex: "0 0.0000001 150px",
578         "_main-size": [null, "150px"],
579       },
580     ],
581   },
582   // Another "different flex-shrink values" testcase:
583   {
584     items: [
585       {
586         flex: "4 2 115px",
587         "_main-size": [null, "69px"],
588       },
589       {
590         flex: "5 1 150px",
591         "_main-size": [null, "120px"],
592       },
593       {
594         flex: "1 4 30px",
595         "_main-size": [null, "6px"],
596       },
597       {
598         flex: "1 0 5px",
599         "_main-size": [null, "5px"],
600       },
601     ],
602   },
604   // ...and now with min-size (clamping the effects of flex-shrink on one item):
605   {
606     items: [
607       {
608         flex: "4 5 75px",
609         "_min-main-size": "50px",
610         "_main-size": [null, "50px"],
611       },
612       {
613         flex: "5 5 100px",
614         "_main-size": [null, "62.5px"],
615       },
616       {
617         flex: "0 4 125px",
618         "_main-size": [null, "87.5px"],
619       },
620     ],
621   },
623   // Test a min-size that's much larger than initial preferred size, but small
624   // enough that our flexed size pushes us over it:
625   {
626     items: [
627       {
628         flex: "auto",
629         "_min-main-size": "110px",
630         "_main-size": ["50px", "125px"],
631       },
632       {
633         flex: "auto",
634         "_main-size": [null, "75px"],
635       },
636     ],
637   },
639   // Test a min-size that's much larger than initial preferred size, and is
640   // even larger than our positively-flexed size, so that we have to increase it
641   // (as a 'min violation') after we've flexed.
642   {
643     items: [
644       {
645         flex: "auto",
646         "_min-main-size": "150px",
647         "_main-size": ["50px", "150px"],
648       },
649       {
650         flex: "auto",
651         "_main-size": [null, "50px"],
652       },
653     ],
654   },
656   // Test min-size on multiple items simultaneously:
657   {
658     items: [
659       {
660         flex: "auto",
661         "_min-main-size": "20px",
662         "_main-size": [null, "20px"],
663       },
664       {
665         flex: "9 auto",
666         "_min-main-size": "150px",
667         "_main-size": ["50px", "180px"],
668       },
669     ],
670   },
671   {
672     items: [
673       {
674         flex: "1 1 0px",
675         "_min-main-size": "90px",
676         "_main-size": [null, "90px"],
677       },
678       {
679         flex: "1 1 0px",
680         "_min-main-size": "80px",
681         "_main-size": [null, "80px"],
682       },
683       {
684         flex: "1 1 40px",
685         "_main-size": [null, "30px"],
686       },
687     ],
688   },
690   // Test a case where _min-main-size will be violated on different items in
691   // successive iterations of the "resolve the flexible lengths" loop
692   {
693     items: [
694       {
695         flex: "1 2 100px",
696         "_min-main-size": "90px",
697         "_main-size": [null, "90px"],
698       },
699       {
700         flex: "1 1 100px",
701         "_min-main-size": "70px",
702         "_main-size": [null, "70px"],
703       },
704       {
705         flex: "1 1 100px",
706         "_main-size": [null, "40px"],
707       },
708     ],
709   },
711   // Test some cases that have a min-size violation on one item and a
712   // max-size violation on another:
714   // Here, both items initially grow to 100px. That violates both
715   // items' sizing constraints (it's smaller than the min-size and larger than
716   // the max-size), so we clamp both of them and sum the clamping-differences:
717   //
718   //   (130px - 100px) + (50px - 100px) = (30px) + (-50px) = -20px
719   //
720   // This sum is negative, so (per spec) we freeze the item that had its
721   // max-size violated (the second one) and restart the algorithm.  This time,
722   // all the available space (200px - 50px = 150px) goes to the not-yet-frozen
723   // first item, and that puts it above its min-size, so all is well.
724   {
725     items: [
726       {
727         flex: "auto",
728         "_min-main-size": "130px",
729         "_main-size": [null, "150px"],
730       },
731       {
732         flex: "auto",
733         "_max-main-size": "50px",
734         "_main-size": [null, "50px"],
735       },
736     ],
737   },
739   // As above, both items initially grow to 100px, and that violates both items'
740   // constraints. However, now the sum of the clamping differences is:
741   //
742   //   (130px - 100px) + (80px - 100px) = (30px) + (-20px) = 10px
743   //
744   // This sum is positive, so (per spec) we freeze the item that had its
745   // min-size violated (the first one) and restart the algorithm. This time,
746   // all the available space (200px - 130px = 70px) goes to the not-yet-frozen
747   // second item, and that puts it below its max-size, so all is well.
748   {
749     items: [
750       {
751         flex: "auto",
752         "_min-main-size": "130px",
753         "_main-size": [null, "130px"],
754       },
755       {
756         flex: "auto",
757         "_max-main-size": "80px",
758         "_main-size": [null, "70px"],
759       },
760     ],
761   },
763   // As above, both items initially grow to 100px, and that violates both items'
764   // constraints. So we clamp both items and sum the clamping differences to
765   // see what to do next.  The sum is:
766   //
767   //   (80px - 100px) + (120px - 100px) = (-20px) + (20px) = 0px
768   //
769   // Per spec, if the sum is 0, we're done -- we leave both items at their
770   // clamped sizes.
771   {
772     items: [
773       {
774         flex: "auto",
775         "_max-main-size": "80px",
776         "_main-size": [null, "80px"],
777       },
778       {
779         flex: "auto",
780         "_min-main-size": "120px",
781         "_main-size": [null, "120px"],
782       },
783     ],
784   },
786   // Test cases where flex-grow sums to less than 1:
787   // ===============================================
788   // This makes us treat the flexibilities like "fraction of free space"
789   // instead of weights, so that e.g. a single item with "flex-grow: 0.1"
790   // will only get 10% of the free space instead of all of the free space.
792   // Basic cases where flex-grow sum is less than 1:
793   {
794     items: [
795       {
796         flex: "0.1 100px",
797         "_main-size": [null, "110px"], // +10% of free space
798       },
799     ],
800   },
801   {
802     items: [
803       {
804         flex: "0.8 0px",
805         "_main-size": [null, "160px"], // +80% of free space
806       },
807     ],
808   },
810   // ... and now with two flex items:
811   {
812     items: [
813       {
814         flex: "0.4 70px",
815         "_main-size": [null, "110px"], // +40% of free space
816       },
817       {
818         flex: "0.2 30px",
819         "_main-size": [null, "50px"], // +20% of free space
820       },
821     ],
822   },
824   // ...and now with max-size modifying how much free space one item can take:
825   {
826     items: [
827       {
828         flex: "0.4 70px",
829         "_main-size": [null, "110px"], // +40% of free space
830       },
831       {
832         flex: "0.2 30px",
833         "_max-main-size": "35px",
834         "_main-size": [null, "35px"], // +20% free space, then clamped
835       },
836     ],
837   },
838   // ...and now with a max-size smaller than our flex-basis:
839   // (This makes us freeze the second item right away, before we compute
840   // the initial free space.)
841   {
842     items: [
843       {
844         flex: "0.4 70px",
845         "_main-size": [null, "118px"], // +40% of 200px-70px-10px
846       },
847       {
848         flex: "0.2 30px",
849         "_max-main-size": "10px",
850         "_main-size": [null, "10px"], // immediately frozen
851       },
852     ],
853   },
854   // ...and now with a max-size and a huge flex-basis, such that we initially
855   // have negative free space, which makes the "% of [original] free space"
856   // calculations a bit more subtle. We set the "original free space" after
857   // we've clamped the second item (the first time the free space is positive).
858   {
859     items: [
860       {
861         flex: "0.4 70px",
862         "_main-size": [null, "118px"], // +40% of free space _after freezing
863         // the other item_
864       },
865       {
866         flex: "0.2 150px",
867         "_max-main-size": "10px",
868         "_main-size": [null, "10px"], // clamped immediately
869       },
870     ],
871   },
873   // Now with min-size modifying how much free space our items take:
874   {
875     items: [
876       {
877         flex: "0.4 70px",
878         "_main-size": [null, "110px"], // +40% of free space
879       },
880       {
881         flex: "0.2 30px",
882         "_min-main-size": "70px",
883         "_main-size": [null, "70px"], // +20% free space, then clamped
884       },
885     ],
886   },
888   // ...and now with a large enough min-size that it prevents the other flex
889   // item from taking its full desired portion of the original free space:
890   {
891     items: [
892       {
893         flex: "0.4 70px",
894         "_main-size": [null, "80px"], // (Can't take my full +40% of
895         // free space due to other item's
896         // large min-size.)
897       },
898       {
899         flex: "0.2 30px",
900         "_min-main-size": "120px",
901         "_main-size": [null, "120px"], // +20% free space, then clamped
902       },
903     ],
904   },
905   // ...and now with a large-enough min-size that it pushes the other flex item
906   // to actually shrink a bit (with default "flex-shrink:1"):
907   {
908     items: [
909       {
910         flex: "0.3 30px",
911         "_main-size": [null, "20px"], // -10px, instead of desired +45px
912       },
913       {
914         flex: "0.2 20px",
915         "_min-main-size": "180px",
916         "_main-size": [null, "180px"], // +160px, instead of desired +30px
917       },
918     ],
919   },
921   // In this case, the items' flexibilities don't initially sum to < 1, but they
922   // do after we freeze the third item for violating its max-size.
923   {
924     items: [
925       {
926         flex: "0.3 30px",
927         "_main-size": [null, "75px"],
928         // 1st loop: desires (0.3 / 5) * 150px = 9px. Tentatively granted.
929         // 2nd loop: desires 0.3 * 150px = 45px. Tentatively granted.
930         // 3rd loop: desires 0.3 * 150px = 45px. Granted +45px.
931       },
932       {
933         flex: "0.2 20px",
934         "_max-main-size": "30px",
935         "_main-size": [null, "30px"],
936         // First loop: desires (0.2 / 5) * 150px = 6px. Tentatively granted.
937         // Second loop: desires 0.2 * 150px = 30px. Frozen at +10px.
938       },
939       {
940         flex: "4.5 0px",
941         "_max-main-size": "20px",
942         "_main-size": [null, "20px"],
943         // First loop: desires (4.5 / 5) * 150px = 135px. Frozen at +20px.
944       },
945     ],
946   },
948   // Make sure we calculate "original free space" correctly when one of our
949   // flex items will be clamped right away, due to max-size preventing it from
950   // growing at all:
951   {
952     // Here, the second flex item is effectively inflexible; it's
953     // immediately frozen at 40px since we're growing & this item's max size
954     // trivially prevents it from growing. This leaves us with an "original
955     // free space" of 60px. The first flex item takes half of that, due to
956     // its flex-grow value of 0.5.
957     items: [
958       {
959         flex: "0.5 100px",
960         "_main-size": [null, "130px"],
961       },
962       {
963         flex: "1 98px",
964         "_max-main-size": "40px",
965         "_main-size": [null, "40px"],
966       },
967     ],
968   },
969   {
970     // Same as previous example, but with a larger flex-basis on the second
971     // element (which shouldn't ultimately matter, because its max size clamps
972     // its size immediately anyway).
973     items: [
974       {
975         flex: "0.5 100px",
976         "_main-size": [null, "130px"],
977       },
978       {
979         flex: "1 101px",
980         "_max-main-size": "40px",
981         "_main-size": [null, "40px"],
982       },
983     ],
984   },
986   {
987     // Here, the third flex item is effectively inflexible; it's immediately
988     // frozen at 0px since we're growing & this item's max size trivially
989     // prevents it from growing. This leaves us with an "original free space" of
990     // 100px. The first flex item takes 40px, and the third takes 50px, due to
991     // their flex values of 0.4 and 0.5.
992     items: [
993       {
994         flex: "0.4 50px",
995         "_main-size": [null, "90px"],
996       },
997       {
998         flex: "0.5 50px",
999         "_main-size": [null, "100px"],
1000       },
1001       {
1002         flex: "0 90px",
1003         "_max-main-size": "0px",
1004         "_main-size": [null, "0px"],
1005       },
1006     ],
1007   },
1008   {
1009     // Same as previous example, but with slightly larger flex-grow values on
1010     // the first and second items, which sum to 1.0 and produce slightly larger
1011     // main sizes. This demonstrates that there's no discontinuity between the
1012     // "< 1.0 sum" to ">= 1.0 sum" behavior, in this situation at least.
1013     items: [
1014       {
1015         flex: "0.45 50px",
1016         "_main-size": [null, "95px"],
1017       },
1018       {
1019         flex: "0.55 50px",
1020         "_main-size": [null, "105px"],
1021       },
1022       {
1023         flex: "0 90px",
1024         "_max-main-size": "0px",
1025         "_main-size": [null, "0px"],
1026       },
1027     ],
1028   },
1030   // Test cases where flex-shrink sums to less than 1:
1031   // =================================================
1032   // This makes us treat the flexibilities more like "fraction of (negative)
1033   // free space" instead of weights, so that e.g. a single item with
1034   // "flex-shrink: 0.1" will only shrink by 10% of amount that it overflows
1035   // its container by.
1036   //
1037   // It gets a bit more complex when there are multiple flex items, because
1038   // flex-shrink is scaled by the flex-basis before it's used as a weight. But
1039   // even with that scaling, the general principal is that e.g. if the
1040   // flex-shrink values *sum* to 0.6, then the items will collectively only
1041   // shrink by 60% (and hence will still overflow).
1043   // Basic cases where flex-grow sum is less than 1:
1044   {
1045     items: [
1046       {
1047         flex: "0 0.1 300px",
1048         "_main-size": [null, "290px"], // +10% of (negative) free space
1049       },
1050     ],
1051   },
1052   {
1053     items: [
1054       {
1055         flex: "0 0.8 400px",
1056         "_main-size": [null, "240px"], // +80% of (negative) free space
1057       },
1058     ],
1059   },
1061   // ...now with two flex items, with the same flex-basis value:
1062   {
1063     items: [
1064       {
1065         flex: "0 0.4 150px",
1066         "_main-size": [null, "110px"], // +40% of (negative) free space
1067       },
1068       {
1069         flex: "0 0.2 150px",
1070         "_main-size": [null, "130px"], // +20% of (negative) free space
1071       },
1072     ],
1073   },
1075   // ...now with two flex items, with different flex-basis values (and hence
1076   // differently-scaled flex factors):
1077   {
1078     items: [
1079       {
1080         flex: "0 0.3 100px",
1081         "_main-size": [null, "76px"],
1082       },
1083       {
1084         flex: "0 0.1 200px",
1085         "_main-size": [null, "184px"],
1086       },
1087     ],
1088     // Notes:
1089     //  - Free space: -100px
1090     //  - Sum of flex-shrink factors: 0.3 + 0.1 = 0.4
1091     //  - Since that sum ^ is < 1, we'll only distribute that fraction of
1092     //    the free space. We'll distribute: -100px * 0.4 = -40px
1093     //
1094     //  - 1st item's scaled flex factor:  0.3 * 100px = 30
1095     //  - 2nd item's scaled flex factor:  0.1 * 200px = 20
1096     //  - 1st item's share of distributed free space: 30/(30+20) = 60%
1097     //  - 2nd item's share of distributed free space: 20/(30+20) = 40%
1098     //
1099     // SO:
1100     //  - 1st item gets 60% * -40px = -24px.  100px-24px = 76px
1101     //  - 2nd item gets 40% * -40px = -16px.  200px-16px = 184px
1102   },
1104   // ...now with min-size modifying how much one item can shrink:
1105   {
1106     items: [
1107       {
1108         flex: "0 0.3 100px",
1109         "_main-size": [null, "70px"],
1110       },
1111       {
1112         flex: "0 0.1 200px",
1113         "_min-main-size": "190px",
1114         "_main-size": [null, "190px"],
1115       },
1116     ],
1117     // Notes:
1118     //  - We proceed as in previous testcase, but clamp the second flex item
1119     //    at its min main size.
1120     //  - After that point, we have a total flex-shrink of = 0.3, so we
1121     //    distribute 0.3 * -100px = -30px to the remaining unfrozen flex
1122     //    items. Since there's only one unfrozen item left, it gets all of it.
1123   },
1125   // ...now with min-size larger than our flex-basis:
1126   // (This makes us freeze the second item right away, before we compute
1127   // the initial free space.)
1128   {
1129     items: [
1130       {
1131         flex: "0 0.3 100px",
1132         "_main-size": [null, "55px"], // +30% of 200px-100px-250px
1133       },
1134       {
1135         flex: "0 0.1 200px",
1136         "_min-main-size": "250px",
1137         "_main-size": [null, "250px"], // immediately frozen
1138       },
1139     ],
1140     // (Same as previous example, except the min-main-size prevents the
1141     // second item from shrinking at all)
1142   },
1144   // ...and now with a min-size and a small flex-basis, such that we initially
1145   // have positive free space, which makes the "% of [original] free space"
1146   // calculations a bit more subtle. We set the "original free space" after
1147   // we've clamped the second item (the first time the free space is negative).
1148   {
1149     items: [
1150       {
1151         flex: "0 0.3 100px",
1152         "_main-size": [null, "70px"],
1153       },
1154       {
1155         flex: "0 0.1 50px",
1156         "_min-main-size": "200px",
1157         "_main-size": [null, "200px"],
1158       },
1159     ],
1160   },
1162   // Now with max-size making an item shrink more than its flex-shrink value
1163   // calls for:
1164   {
1165     items: [
1166       {
1167         flex: "0 0.3 100px",
1168         "_main-size": [null, "70px"],
1169       },
1170       {
1171         flex: "0 0.1 200px",
1172         "_max-main-size": "150px",
1173         "_main-size": [null, "150px"],
1174       },
1175     ],
1176     // Notes:
1177     //  - We proceed as in an earlier testcase, but clamp the second flex item
1178     //    at its max main size.
1179     //  - After that point, we have a total flex-shrink of = 0.3, so we
1180     //    distribute 0.3 * -100px = -30px to the remaining unfrozen flex
1181     //    items. Since there's only one unfrozen item left, it gets all of it.
1182   },
1184   // ...and now with a small enough max-size that it prevents the other flex
1185   // item from taking its full desired portion of the (negative) original free
1186   // space:
1187   {
1188     items: [
1189       {
1190         flex: "0 0.3 100px",
1191         "_main-size": [null, "90px"],
1192       },
1193       {
1194         flex: "0 0.1 200px",
1195         "_max-main-size": "110px",
1196         "_main-size": [null, "110px"],
1197       },
1198     ],
1199     // Notes:
1200     //  - We proceed as in an earlier testcase, but clamp the second flex item
1201     //    at its max main size.
1202     //  - After that point, we have a total flex-shrink of 0.3, which would
1203     //    have us distribute 0.3 * -100px = -30px to the (one) remaining
1204     //    unfrozen flex item. But our remaining free space is only -10px at
1205     //    that point, so we distribute that instead.
1206   },
1208   // ...and now with a small enough max-size that it pushes the other flex item
1209   // to actually grow a bit (with custom "flex-grow: 1" for this testcase):
1210   {
1211     items: [
1212       {
1213         flex: "1 0.3 100px",
1214         "_main-size": [null, "120px"],
1215       },
1216       {
1217         flex: "1 0.1 200px",
1218         "_max-main-size": "80px",
1219         "_main-size": [null, "80px"],
1220       },
1221     ],
1222   },
1224   // In this case, the items' flexibilities don't initially sum to < 1, but they
1225   // do after we freeze the third item for violating its min-size.
1226   {
1227     items: [
1228       {
1229         flex: "0 0.3 100px",
1230         "_main-size": [null, "76px"],
1231       },
1232       {
1233         flex: "0 0.1 150px",
1234         "_main-size": [null, "138px"],
1235       },
1236       {
1237         flex: "0 0.8 10px",
1238         "_min-main-size": "40px",
1239         "_main-size": [null, "40px"],
1240       },
1241     ],
1242     // Notes:
1243     //  - We immediately freeze the 3rd item, since we're shrinking and its
1244     //    min size obviously prevents it from shrinking at all.  This leaves
1245     //    200px - 100px - 150px - 40px = -90px of "initial free space".
1246     //
1247     //  - Our remaining flexible items have a total flex-shrink of 0.4,
1248     //    so we can distribute a total of 0.4 * -90px = -36px
1249     //
1250     //  - We distribute that space using *scaled* flex factors:
1251     //    * 1st item's scaled flex factor:  0.3 * 100px = 30
1252     //    * 2nd item's scaled flex factor:  0.1 * 150px = 15
1253     //   ...which means...
1254     //    * 1st item's share of distributed free space: 30/(30+15) = 2/3
1255     //    * 2nd item's share of distributed free space: 15/(30+15) = 1/3
1256     //
1257     // SO:
1258     //  - 1st item gets 2/3 * -36px = -24px. 100px - 24px = 76px
1259     //  - 2nd item gets 1/3 * -36px = -12px. 150px - 12px = 138px
1260   },
1262   // In this case, the items' flexibilities sum to > 1, in part due to an item
1263   // that *can't actually shrink* due to its 0 flex-basis (which gives it a
1264   // "scaled flex factor" of 0). This prevents us from triggering the special
1265   // behavior for flexibilities that sum to less than 1, and as a result, the
1266   // first item ends up absorbing all of the free space.
1267   {
1268     items: [
1269       {
1270         flex: "0 .5 300px",
1271         "_main-size": [null, "200px"],
1272       },
1273       {
1274         flex: "0 5 0px",
1275         "_main-size": [null, "0px"],
1276       },
1277     ],
1278   },
1280   // This case is similar to the one above, but with a *barely* nonzero base
1281   // size for the second item. This should produce a result similar to the case
1282   // above. (In particular, we should first distribute a very small amount of
1283   // negative free space to the second item, getting it to approximately zero,
1284   // and distribute the bulk of the negative free space to the first item,
1285   // getting it to approximately 200px.)
1286   {
1287     items: [
1288       {
1289         flex: "0 .5 300px",
1290         "_main-size": [null, "200px"],
1291       },
1292       {
1293         flex: "0 1 0.01px",
1294         "_main-size": [null, "0px"],
1295       },
1296     ],
1297   },
1298   // This case is similar to the ones above, but now we've increased the
1299   // flex-shrink value on the second-item so that it claims enough of the
1300   // negative free space to go below its min-size (0px). So, it triggers a min
1301   // violation & is frozen. For the loop *after* the min violation, the sum of
1302   // the remaining flex items' flex-shrink values is less than 1, so we trigger
1303   // the special <1 behavior and only distribute half of the remaining
1304   // (negative) free space to the first item (instead of all of it).
1305   {
1306     items: [
1307       {
1308         flex: "0 .5 300px",
1309         "_main-size": [null, "250px"],
1310       },
1311       {
1312         flex: "0 5 0.01px",
1313         "_main-size": [null, "0px"],
1314       },
1315     ],
1316   },