test_flex_items.html (12084B)
1 <!doctype html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> 6 <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> 7 <style> 8 .container { 9 display: flex; 10 background-color: grey; 11 font: 14px sans-serif; 12 height: 50px; 13 } 14 #flex-sanity { 15 /* This just needs to be large enough so that no shrinking is required. */ 16 width: 1600px; 17 } 18 .clamped-huge-item { 19 flex: 1 1 1650px; /* This needs to be bigger than #flex-sanity, to test 20 the scenario it's aiming to test (early clamping of a 21 flex item which, if left unclamped, would've reversed 22 the direction of flexing & made everything shrink). */ 23 max-width: 10px; 24 } 25 26 .base { align-self: baseline; } 27 .lastbase { align-self: last baseline; } 28 29 .offset { margin-top: 10px; 30 margin-bottom: 3px; } 31 32 .lime { background: lime; } 33 .yellow { background: yellow; } 34 .orange { background: orange; } 35 .pink { background: pink; } 36 .tan { background: tan; } 37 .white { background: white; } 38 39 .crossMinMax { min-height: 40px; 40 max-height: 120px; } 41 42 .mainMinMax { min-width: 120px; 43 max-width: 500px; } 44 45 .flexGrow { flex-grow: 1; } 46 .spacer150 { width: 150px; 47 box-sizing: border-box; 48 height: 10px; 49 border: 1px solid teal; } 50 51 </style> 52 53 <script> 54 "use strict"; 55 56 SimpleTest.waitForExplicitFinish(); 57 58 const TEXT_NODE = Node.TEXT_NODE; 59 60 function testItemMatchesExpectedValues(item, values, index) { 61 if (typeof(values.node) != "undefined") { 62 is(item.node, values.node, "Item index " + index + " has expected node."); 63 } 64 65 if (typeof(values.mainBaseSize) != "undefined") { 66 is(item.mainBaseSize, values.mainBaseSize, "Item index " + index + " has expected mainBaseSize."); 67 } 68 69 if (typeof(values.mainDeltaSize) != "undefined") { 70 is(item.mainDeltaSize, values.mainDeltaSize, "Item index " + index + " has expected mainDeltaSize."); 71 } 72 73 if (typeof(values.mainMinSize) != "undefined") { 74 is(item.mainMinSize, values.mainMinSize, "Item index " + index + " has expected mainMinSize."); 75 } 76 77 if (typeof(values.mainMaxSize) != "undefined") { 78 is(item.mainMaxSize, values.mainMaxSize, "Item index " + index + " has expected mainMaxSize."); 79 } else { 80 // If we didn't specify an expected mainMaxSize, then it's implied 81 // that the max main-size property (max-width/max-height) is at its 82 // default "none" value, which our FlexItem API represents as +infinity. 83 is(item.mainMaxSize, Number.POSITIVE_INFINITY, 84 "Item index " + index + " has expected (default) mainMaxSize."); 85 } 86 87 if (typeof(values.crossMinSize) != "undefined") { 88 is(item.crossMinSize, values.crossMinSize, "Item index " + index + " has expected crossMinSize."); 89 } 90 91 if (typeof(values.crossMaxSize) != "undefined") { 92 is(item.crossMaxSize, values.crossMaxSize, "Item index " + index + " has expected crossMaxSize."); 93 } else { 94 // As above for mainMaxSize, no-expected-value implies we expect +infinity. 95 is(item.crossMaxSize, Number.POSITIVE_INFINITY, 96 "Item index " + index + " has expected (default) crossMaxSize."); 97 } 98 } 99 100 // Test for items in "flex-sanity" flex container: 101 function testFlexSanity() { 102 let container = document.getElementById("flex-sanity"); 103 let flex = container.getAsFlexContainer(); 104 let lines = flex.getLines(); 105 is(lines.length, 1, "Container should have expected number of lines."); 106 107 let line = lines[0]; 108 let containerHeight = container.getBoundingClientRect().height; 109 is(line.crossSize, containerHeight, 110 "Line crossSize should equal the height of the container."); 111 112 // We can't compare baselines precisely, so we'll just confirm that they 113 // appear somewhere within the elements that determine them. 114 // (This assumes the first rect is baseline-aligned.) 115 let firstRect = container.firstElementChild.getBoundingClientRect(); 116 ok(line.firstBaselineOffset > firstRect.top && 117 line.firstBaselineOffset < firstRect.bottom, 118 "Line firstBaselineOffset should land somewhere within the element " + 119 "that determines it."); 120 121 // For last baseline, it's measured from the bottom, so we have to compare 122 // against the element bounds subtracted from the container height. 123 // We use the first node which uses last-baseline ("lb") alignment to 124 // provide the rect: 125 let lbElem = document.querySelector(".lastbase"); 126 let lbElemBoundingRect = lbElem.getBoundingClientRect(); 127 ok(line.lastBaselineOffset > containerHeight - lbElemBoundingRect.bottom && 128 line.lastBaselineOffset < containerHeight - lbElemBoundingRect.top, 129 "Line lastBaselineOffset should land somewhere within the element" + 130 "that determines it."); 131 132 let expectedValues = [ 133 { crossMinSize: 0 }, 134 { mainBaseSize: 100, 135 mainDeltaSize: 0 }, 136 { crossMinSize: 40, 137 crossMaxSize: 120, 138 mainDeltaSize: 0 }, 139 { mainMinSize: 120, 140 mainMaxSize: 500, 141 mainDeltaSize: 0 }, 142 { mainMinSize: 120, 143 mainMaxSize: 500, 144 mainBaseSize: 150, 145 mainDeltaSize: 0 }, 146 { mainBaseSize: 10, 147 mainMaxSize: 5, 148 mainDeltaSize: 0 }, 149 { mainBaseSize: 10, 150 mainMinSize: 15, 151 mainDeltaSize: 0 }, 152 { mainBaseSize: 50, 153 mainMaxSize: 10 }, 154 { mainBaseSize: 1650, 155 mainMaxSize: 10, 156 mainDeltaSize: 0 }, 157 { mainDeltaSize: 0 }, 158 { /* final item is anonymous flex item */ }, 159 ]; 160 161 let items = line.getItems(); 162 is(items.length, expectedValues.length, 163 "Line should have expected number of items."); 164 is(items.length, container.children.length + 1, 165 "Line should have as many items as the flex container has child elems, " + 166 "plus 1 for anonymous flex item"); 167 168 for (let i = 0; i < items.length; ++i) { 169 let item = items[i]; 170 let values = expectedValues[i]; 171 // set the expected node to the node we're currently iterating over, 172 // except for: 173 // - the display:contents element (whose item is its first child) 174 // - the final item (which is an anonymous flex item around text) 175 if (i < container.children.length) { 176 let curElem = container.children[i]; 177 values.node = (curElem.style.display == "contents" 178 ? curElem.firstElementChild 179 : curElem); 180 } else { 181 is(container.lastChild.nodeType, TEXT_NODE, 182 "container's last child should be a text node"); 183 values.node = container.lastChild; 184 } 185 testItemMatchesExpectedValues(item, values, i); 186 } 187 188 // Check that the delta size of the first item is (roughly) equal to the 189 // actual size minus the base size. 190 isfuzzy(items[0].mainDeltaSize, firstRect.width - items[0].mainBaseSize, 1e-4, 191 "flex-grow item should have expected mainDeltaSize."); 192 } 193 194 // Test for items in "flex-growing" flex container: 195 function testFlexGrowing() { 196 let expectedValues = [ 197 { mainBaseSize: 10, 198 mainDeltaSize: 10, 199 mainMinSize: 35 }, 200 { mainBaseSize: 20, 201 mainDeltaSize: 5, 202 mainMinSize: 28 }, 203 { mainBaseSize: 30, 204 mainDeltaSize: 7 }, 205 { mainBaseSize: 0, 206 mainDeltaSize: 48, 207 mainMaxSize: 20 }, 208 ]; 209 210 let container = document.getElementById("flex-growing"); 211 let items = container.getAsFlexContainer().getLines()[0].getItems(); 212 is(items.length, container.children.length, 213 "Line should have as many items as the flex container has child elems"); 214 215 for (let i = 0; i < items.length; ++i) { 216 let item = items[i]; 217 let values = expectedValues[i]; 218 testItemMatchesExpectedValues(item, values, i); 219 } 220 } 221 222 function runTests() { 223 testFlexSanity(); 224 testFlexGrowing(); 225 SimpleTest.finish(); 226 } 227 </script> 228 </head> 229 230 <body onLoad="runTests();"> 231 <!-- First flex container to be tested: "flex-sanity". 232 We test a few general things, e.g.: 233 - accuracy of reported baselines. 234 - accuracy of reported flex base size, min/max sizes, & trivial deltas. 235 - flex items formation for display:contents subtrees & text nodes. 236 --> 237 <div id="flex-sanity" class="container"> 238 <div class="lime base flexGrow">one line (first)</div> 239 <div class="yellow lastbase" style="width: 100px">one line (last)</div> 240 <div class="orange offset lastbase crossMinMax">two<br/>lines and offset (last)</div> 241 <div class="pink offset base mainMinMax">offset (first)</div> 242 <!-- Inflexible item w/ content-derived flex base size, which has min/max 243 but doesn't violate them: --> 244 <div class="tan mainMinMax"> 245 <div class="spacer150"></div> 246 </div> 247 <!-- Inflexible item that is trivially clamped to smaller max-width: --> 248 <div style="flex: 0 0 10px; max-width: 5px"></div> 249 <!-- Inflexible item that is trivially clamped to larger min-width: --> 250 <div style="flex: 0 0 10px; min-width: 15px"></div> 251 <!-- Item that wants to grow but is trivially clamped to max-width 252 below base size: --> 253 <div style="flex: 1 1 50px; max-width: 10px"></div> 254 <div class="clamped-huge-item"></div> 255 <div style="display:contents"> 256 <div class="white">replaced</div> 257 </div> 258 anon item for text 259 </div> 260 261 <!-- Second flex container to be tested, with items that grow by specific 262 predictable amounts. This ends up triggering 4 passes of the main 263 flexbox layout algorithm loop - and note that for each item, we only 264 report (via 'mainDeltaSize') the delta that it wanted in the *last pass 265 of the loop before that item was frozen*. 266 267 Here's what goes on in each pass (and the tentative deltas) 268 * PASS 1 269 - Available space = 120 - 10 - 20 - 30 - 0 = 60px. 270 - Sum of flex values = 1+1+2+16 = 20. So 1 "share" is 60px/20 = 3px. 271 - Deltas (space distributed): +3px, +3px, +6px, +48px 272 VIOLATIONS: 273 item0 is now 10+3 = 13px, which under its min 274 item1 is now 20+3 = 23px, which under its min 275 item3 is now 0+48 = 48px, which over its max 276 - the magnitude of max-violations (how far we're out of bounds) exceeds 277 magnitude of min-violations, so we prioritize the max-violations. 278 - So we freeze item3 at its max-width, 20px, leaving its final "desired" 279 mainDeltaSize at +48px from this pass. 280 281 * PASS 2 282 - Available space = 120 - 10 - 20 - 30 - 20 = 40px. 283 - Sum of flex values = 1+1+2 = 4. So 1 "share" is 40px/4 = 10px. 284 - Deltas (space distributed): +10px, +10px, +20px, +0px. 285 VIOLATIONS: 286 item0 is now 10+10 = 20px, which is under its min 287 - So we freeze item0 at its min-width, 35px, leaving its final "desired" 288 mainDeltaSize at +10px from this pass. 289 290 * PASS 3 291 - Available space = 120 - 35 - 20 - 30 - 20 = 15px. 292 - Sum of flex values = 1+2 = 3. So 1 "share" is 15px/3 = 5px. 293 - Deltas (space distributed): +0px, +5px, +10px, +0px. 294 VIOLATIONS: 295 item1 is now 20+5 = 25px, which is under its min 296 - So we freeze item1 at its min-width, 28px, leaving its final "desired" 297 mainDeltaSize at +5px from this pass. 298 299 * PASS 4 300 - Available space = 120 - 35 - 28 - 30 - 20 = 7px. 301 - Sum of flex values = 2. So 1 "share" is 7px/2 = 3.5px 302 - Deltas (space distributed): +0px, +0px, +7px, +0px. 303 VIOLATIONS: 304 None! (So, we'll be done after this pass!) 305 - So we freeze item2 (the only unfrozen item) at its flexed size, 37px, 306 and set its final "desired" mainDeltaSize to +7px from this pass. 307 - And we're done! 308 --> 309 <div id="flex-growing" class="container" style="width: 120px"> 310 <div style="flex: 1 10px; min-width: 35px"></div> 311 <div style="flex: 1 20px; min-width: 28px"></div> 312 <div style="flex: 2 30px; min-width: 0"></div> 313 <div style="flex: 16 0px; max-width: 20px"></div> 314 </div> 315 </body> 316 </html>