summaryrefslogtreecommitdiffstats
path: root/dom/flex/test/chrome/test_flex_items.html
blob: 0bed577ae74a4afa1524946a905c9cef383b2042 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<style>
  .container {
    display: flex;
    background-color: grey;
    font: 14px sans-serif;
    height: 50px;
  }
  #flex-sanity {
    /* This just needs to be large enough so that no shrinking is required. */
    width: 1600px;
  }
  .clamped-huge-item {
    flex: 1 1 1650px; /* This needs to be bigger than #flex-sanity, to test
                         the scenario it's aiming to test (early clamping of a
                         flex item which, if left unclamped, would've reversed
                         the direction of flexing & made everything shrink). */
    max-width: 10px;
  }

  .base        { align-self: baseline; }
  .lastbase    { align-self: last baseline; }

  .offset      { margin-top: 10px;
                 margin-bottom: 3px; }

  .lime        { background: lime;   }
  .yellow      { background: yellow; }
  .orange      { background: orange; }
  .pink        { background: pink;   }
  .tan         { background: tan;    }
  .white       { background: white;  }

  .crossMinMax { min-height: 40px;
                 max-height: 120px; }

  .mainMinMax  { min-width: 120px;
                 max-width: 500px; }

  .flexGrow    { flex-grow: 1; }
  .spacer150   { width: 150px;
                 box-sizing: border-box;
                 height: 10px;
                 border: 1px solid teal; }

</style>

<script>
"use strict";

SimpleTest.waitForExplicitFinish();

const TEXT_NODE = Node.TEXT_NODE;

function testItemMatchesExpectedValues(item, values, index) {
  if (typeof(values.node) != "undefined") {
    is(item.node, values.node, "Item index " + index + " has expected node.");
  }

  if (typeof(values.mainBaseSize) != "undefined") {
    is(item.mainBaseSize, values.mainBaseSize, "Item index " + index + " has expected mainBaseSize.");
  }

  if (typeof(values.mainDeltaSize) != "undefined") {
    is(item.mainDeltaSize, values.mainDeltaSize, "Item index " + index + " has expected mainDeltaSize.");
  }

  if (typeof(values.mainMinSize) != "undefined") {
    is(item.mainMinSize, values.mainMinSize, "Item index " + index + " has expected mainMinSize.");
  }

  if (typeof(values.mainMaxSize) != "undefined") {
    is(item.mainMaxSize, values.mainMaxSize, "Item index " + index + " has expected mainMaxSize.");
  } else {
    // If we didn't specify an expected mainMaxSize, then it's implied
    // that the max main-size property (max-width/max-height) is at its
    // default "none" value, which our FlexItem API represents as +infinity.
    is(item.mainMaxSize, Number.POSITIVE_INFINITY,
       "Item index " + index + " has expected (default) mainMaxSize.");
  }

  if (typeof(values.crossMinSize) != "undefined") {
    is(item.crossMinSize, values.crossMinSize, "Item index " + index + " has expected crossMinSize.");
  }

  if (typeof(values.crossMaxSize) != "undefined") {
    is(item.crossMaxSize, values.crossMaxSize, "Item index " + index + " has expected crossMaxSize.");
  } else {
    // As above for mainMaxSize, no-expected-value implies we expect +infinity.
    is(item.crossMaxSize, Number.POSITIVE_INFINITY,
       "Item index " + index + " has expected (default) crossMaxSize.");
  }
}

// Test for items in "flex-sanity" flex container:
function testFlexSanity() {
  let container = document.getElementById("flex-sanity");
  let flex = container.getAsFlexContainer();
  let lines = flex.getLines();
  is(lines.length, 1, "Container should have expected number of lines.");

  let line = lines[0];
  let containerHeight = container.getBoundingClientRect().height;
  is(line.crossSize, containerHeight,
     "Line crossSize should equal the height of the container.");

  // We can't compare baselines precisely, so we'll just confirm that they
  // appear somewhere within the elements that determine them.
  // (This assumes the first rect is baseline-aligned.)
  let firstRect = container.firstElementChild.getBoundingClientRect();
  ok(line.firstBaselineOffset > firstRect.top &&
     line.firstBaselineOffset < firstRect.bottom,
     "Line firstBaselineOffset should land somewhere within the element " +
     "that determines it.");

  // For last baseline, it's measured from the bottom, so we have to compare
  // against the element bounds subtracted from the container height.
  // We use the first node which uses last-baseline ("lb") alignment to
  // provide the rect:
  let lbElem = document.querySelector(".lastbase");
  let lbElemBoundingRect = lbElem.getBoundingClientRect();
  ok(line.lastBaselineOffset > containerHeight - lbElemBoundingRect.bottom &&
     line.lastBaselineOffset < containerHeight - lbElemBoundingRect.top,
     "Line lastBaselineOffset should land somewhere within the element" +
     "that determines it.");

  let expectedValues = [
    { crossMinSize: 0 },
    { mainBaseSize: 100,
      mainDeltaSize: 0 },
    { crossMinSize: 40,
      crossMaxSize: 120,
      mainDeltaSize: 0 },
    { mainMinSize: 120,
      mainMaxSize: 500,
      mainDeltaSize: 0 },
    { mainMinSize: 120,
      mainMaxSize: 500,
      mainBaseSize: 150,
      mainDeltaSize: 0 },
    { mainBaseSize:  10,
      mainMaxSize:   5,
      mainDeltaSize: 0 },
    { mainBaseSize: 10,
      mainMinSize:  15,
      mainDeltaSize: 0 },
    { mainBaseSize: 50,
      mainMaxSize: 10 },
    { mainBaseSize: 1650,
      mainMaxSize: 10,
      mainDeltaSize: 0 },
    { mainDeltaSize: 0 },
    { /* final item is anonymous flex item */ },
  ];

  let items = line.getItems();
  is(items.length, expectedValues.length,
     "Line should have expected number of items.");
  is(items.length, container.children.length + 1,
     "Line should have as many items as the flex container has child elems, " +
     "plus 1 for anonymous flex item");

  for (let i = 0; i < items.length; ++i) {
    let item = items[i];
    let values = expectedValues[i];
    // set the expected node to the node we're currently iterating over,
    // except for:
    // - the display:contents element (whose item is its first child)
    // - the final item (which is an anonymous flex item around text)
    if (i < container.children.length) {
      let curElem = container.children[i];
      values.node = (curElem.style.display == "contents"
                     ? curElem.firstElementChild
                     : curElem);
    } else {
      is(container.lastChild.nodeType, TEXT_NODE,
         "container's last child should be a text node");
      values.node = container.lastChild;
    }
    testItemMatchesExpectedValues(item, values, i);
  }

  // Check that the delta size of the first item is (roughly) equal to the
  // actual size minus the base size.
  isfuzzy(items[0].mainDeltaSize, firstRect.width - items[0].mainBaseSize, 1e-4,
          "flex-grow item should have expected mainDeltaSize.");
}

// Test for items in "flex-growing" flex container:
function testFlexGrowing() {
  let expectedValues = [
    { mainBaseSize:  10,
      mainDeltaSize: 10,
      mainMinSize:   35 },
    { mainBaseSize:  20,
      mainDeltaSize: 5,
      mainMinSize:   28 },
    { mainBaseSize:  30,
      mainDeltaSize: 7  },
    { mainBaseSize:  0,
      mainDeltaSize: 48,
      mainMaxSize:   20 },
  ];

  let container = document.getElementById("flex-growing");
  let items = container.getAsFlexContainer().getLines()[0].getItems();
  is(items.length, container.children.length,
     "Line should have as many items as the flex container has child elems");

  for (let i = 0; i < items.length; ++i) {
    let item = items[i];
    let values = expectedValues[i];
    testItemMatchesExpectedValues(item, values, i);
  }
}

function runTests() {
  testFlexSanity();
  testFlexGrowing();
  SimpleTest.finish();
}
</script>
</head>

<body onLoad="runTests();">
  <!-- First flex container to be tested: "flex-sanity".
       We test a few general things, e.g.:
       - accuracy of reported baselines.
       - accuracy of reported flex base size, min/max sizes, & trivial deltas.
       - flex items formation for display:contents subtrees & text nodes.
   -->
  <div id="flex-sanity" class="container">
    <div class="lime base flexGrow">one line (first)</div>
    <div class="yellow lastbase" style="width: 100px">one line (last)</div>
    <div class="orange offset lastbase crossMinMax">two<br/>lines and offset (last)</div>
    <div class="pink offset base mainMinMax">offset (first)</div>
    <!-- Inflexible item w/ content-derived flex base size, which has min/max
         but doesn't violate them: -->
    <div class="tan mainMinMax">
      <div class="spacer150"></div>
    </div>
    <!-- Inflexible item that is trivially clamped to smaller max-width: -->
    <div style="flex: 0 0 10px; max-width: 5px"></div>
    <!-- Inflexible item that is trivially clamped to larger min-width: -->
    <div style="flex: 0 0 10px; min-width: 15px"></div>
    <!-- Item that wants to grow but is trivially clamped to max-width
         below base size: -->
    <div style="flex: 1 1 50px; max-width: 10px"></div>
    <div class="clamped-huge-item"></div>
    <div style="display:contents">
      <div class="white">replaced</div>
    </div>
    anon item for text
  </div>

  <!-- Second flex container to be tested, with items that grow by specific
       predictable amounts. This ends up triggering 4 passes of the main
       flexbox layout algorithm loop - and note that for each item, we only
       report (via 'mainDeltaSize') the delta that it wanted in the *last pass
       of the loop before that item was frozen*.

       Here's what goes on in each pass (and the tentative deltas)
     * PASS 1
        - Available space = 120 - 10 - 20 - 30 - 0 = 60px.
        - Sum of flex values = 1+1+2+16 = 20. So 1 "share" is 60px/20 = 3px.
        - Deltas (space distributed): +3px, +3px, +6px, +48px
          VIOLATIONS:
            item0 is now 10+3 = 13px, which under its min
            item1 is now 20+3 = 23px, which under its min
            item3 is now 0+48 = 48px, which over its max
        - the magnitude of max-violations (how far we're out of bounds) exceeds
          magnitude of min-violations, so we prioritize the max-violations.
        - So we freeze item3 at its max-width, 20px, leaving its final "desired"
          mainDeltaSize at +48px from this pass.

     * PASS 2
        - Available space = 120 - 10 - 20 - 30 - 20 = 40px.
        - Sum of flex values = 1+1+2 = 4. So 1 "share" is 40px/4 = 10px.
        - Deltas (space distributed): +10px, +10px, +20px, +0px.
          VIOLATIONS:
            item0 is now 10+10 = 20px, which is under its min
        - So we freeze item0 at its min-width, 35px, leaving its final "desired"
          mainDeltaSize at +10px from this pass.

     * PASS 3
        - Available space = 120 - 35 - 20 - 30 - 20 = 15px.
        - Sum of flex values = 1+2 = 3. So 1 "share" is 15px/3 = 5px.
        - Deltas (space distributed): +0px, +5px, +10px, +0px.
          VIOLATIONS:
            item1 is now 20+5 = 25px, which is under its min
        - So we freeze item1 at its min-width, 28px, leaving its final "desired"
          mainDeltaSize at +5px from this pass.

     * PASS 4
        - Available space = 120 - 35 - 28 - 30 - 20 = 7px.
        - Sum of flex values = 2. So 1 "share" is 7px/2 = 3.5px
        - Deltas (space distributed): +0px, +0px, +7px, +0px.
          VIOLATIONS:
            None! (So, we'll be done after this pass!)
        - So we freeze item2 (the only unfrozen item) at its flexed size, 37px,
          and set its final "desired" mainDeltaSize to +7px from this pass.
        - And we're done!
       -->
  <div id="flex-growing" class="container" style="width: 120px">
    <div style="flex: 1 10px; min-width: 35px"></div>
    <div style="flex: 1 20px; min-width: 28px"></div>
    <div style="flex: 2 30px; min-width: 0"></div>
    <div style="flex: 16 0px; max-width: 20px"></div>
  </div>
</body>
</html>