tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

flexbox_layout_testcases.js (37396B)


      1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
      2 /* vim: set ts=2 sw=2 sts=2 et: */
      3 
      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/. */
      7 
      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 */
     36 
     37 // The standard main-size we'll use for our flex container when setting up
     38 // the testcases defined below:
     39 var gDefaultFlexContainerSize = "200px";
     40 
     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",
     53 };
     54 
     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",
     67 };
     68 
     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",
     81 };
     82 
     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",
     95 };
     96 
     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  },
    235 
    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  },
    286 
    287  // And now, some testcases to check that we handle float accumulation error
    288  // gracefully.
    289 
    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  },
    320 
    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  },
    449 
    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:
    504 
    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  },
    603 
    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  },
    622 
    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  },
    638 
    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  },
    655 
    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  },
    689 
    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  },
    710 
    711  // Test some cases that have a min-size violation on one item and a
    712  // max-size violation on another:
    713 
    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  },
    738 
    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  },
    762 
    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  },
    785 
    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.
    791 
    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  },
    809 
    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  },
    823 
    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  },
    872 
    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  },
    887 
    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  },
    920 
    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  },
    947 
    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  },
    985 
    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  },
   1029 
   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).
   1042 
   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  },
   1060 
   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  },
   1074 
   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  },
   1103 
   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  },
   1124 
   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  },
   1143 
   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  },
   1161 
   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  },
   1183 
   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  },
   1207 
   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  },
   1223 
   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  },
   1261 
   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  },
   1279 
   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  },
   1317 ];