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 ];