1(function() {
2
3function insertAfter(nodeToAdd, referenceNode)
4{
5    if (referenceNode == document.body) {
6        document.body.appendChild(nodeToAdd);
7        return;
8    }
9
10    if (referenceNode.nextSibling)
11        referenceNode.parentNode.insertBefore(nodeToAdd, referenceNode.nextSibling);
12    else
13        referenceNode.parentNode.appendChild(nodeToAdd);
14}
15
16function positionedAncestor(node)
17{
18    var ancestor = node.parentNode;
19    while (getComputedStyle(ancestor).position == 'static')
20        ancestor = ancestor.parentNode;
21    return ancestor;
22}
23
24function checkSubtreeExpectedValues(parent, failures)
25{
26    var checkedLayout = checkExpectedValues(parent, failures);
27    Array.prototype.forEach.call(parent.childNodes, function(node) {
28        checkedLayout |= checkSubtreeExpectedValues(node, failures);
29    });
30    return checkedLayout;
31}
32
33function checkAttribute(output, node, attribute)
34{
35    var result = node.getAttribute && node.getAttribute(attribute);
36    output.checked |= !!result;
37    return result;
38}
39
40function checkExpectedValues(node, failures)
41{
42    var output = { checked: false };
43    var expectedWidth = checkAttribute(output, node, "data-expected-width");
44    if (expectedWidth) {
45        if (isNaN(expectedWidth) || Math.abs(node.offsetWidth - expectedWidth) >= 1)
46            failures.push("Expected " + expectedWidth + " for width, but got " + node.offsetWidth + ". ");
47    }
48
49    var expectedHeight = checkAttribute(output, node, "data-expected-height");
50    if (expectedHeight) {
51        if (isNaN(expectedHeight) || Math.abs(node.offsetHeight - expectedHeight) >= 1)
52            failures.push("Expected " + expectedHeight + " for height, but got " + node.offsetHeight + ". ");
53    }
54
55    var expectedOffset = checkAttribute(output, node, "data-offset-x");
56    if (expectedOffset) {
57        if (isNaN(expectedOffset) || Math.abs(node.offsetLeft - expectedOffset) >= 1)
58            failures.push("Expected " + expectedOffset + " for offsetLeft, but got " + node.offsetLeft + ". ");
59    }
60
61    var expectedOffset = checkAttribute(output, node, "data-offset-y");
62    if (expectedOffset) {
63        if (isNaN(expectedOffset) || Math.abs(node.offsetTop - expectedOffset) >= 1)
64            failures.push("Expected " + expectedOffset + " for offsetTop, but got " + node.offsetTop + ". ");
65    }
66
67    var expectedOffset = checkAttribute(output, node, "data-positioned-offset-x");
68    if (expectedOffset) {
69        var actualOffset = node.getBoundingClientRect().left - positionedAncestor(node).getBoundingClientRect().left;
70        if (isNaN(expectedOffset) || Math.abs(actualOffset - expectedOffset) >= 1)
71            failures.push("Expected " + expectedOffset + " for getBoundingClientRect().left offset, but got " + actualOffset + ". ");
72    }
73
74    var expectedOffset = checkAttribute(output, node, "data-positioned-offset-y");
75    if (expectedOffset) {
76        var actualOffset = node.getBoundingClientRect().top - positionedAncestor(node).getBoundingClientRect().top;
77        if (isNaN(expectedOffset) || Math.abs(actualOffset - expectedOffset) >= 1)
78            failures.push("Expected " + expectedOffset + " for getBoundingClientRect().top offset, but got " + actualOffset + ". ");
79    }
80
81    var expectedWidth = checkAttribute(output, node, "data-expected-client-width");
82    if (expectedWidth) {
83        if (isNaN(expectedWidth) || Math.abs(node.clientWidth - expectedWidth) >= 1)
84            failures.push("Expected " + expectedWidth + " for clientWidth, but got " + node.clientWidth + ". ");
85    }
86
87    var expectedHeight = checkAttribute(output, node, "data-expected-client-height");
88    if (expectedHeight) {
89        if (isNaN(expectedHeight) || Math.abs(node.clientHeight - expectedHeight) >= 1)
90            failures.push("Expected " + expectedHeight + " for clientHeight, but got " + node.clientHeight + ". ");
91    }
92
93    var expectedWidth = checkAttribute(output, node, "data-expected-scroll-width");
94    if (expectedWidth) {
95        if (isNaN(expectedWidth) || Math.abs(node.scrollWidth - expectedWidth) >= 1)
96            failures.push("Expected " + expectedWidth + " for scrollWidth, but got " + node.scrollWidth + ". ");
97    }
98
99    var expectedHeight = checkAttribute(output, node, "data-expected-scroll-height");
100    if (expectedHeight) {
101        if (isNaN(expectedHeight) || Math.abs(node.scrollHeight - expectedHeight) >= 1)
102            failures.push("Expected " + expectedHeight + " for scrollHeight, but got " + node.scrollHeight + ". ");
103    }
104
105    var expectedOffset = checkAttribute(output, node, "data-total-x");
106    if (expectedOffset) {
107        var totalLeft = node.clientLeft + node.offsetLeft;
108        if (isNaN(expectedOffset) || Math.abs(totalLeft - expectedOffset) >= 1)
109            failures.push("Expected " + expectedOffset + " for clientLeft+offsetLeft, but got " + totalLeft + ", clientLeft: " + node.clientLeft + ", offsetLeft: " + node.offsetLeft + ". ");
110    }
111
112    var expectedOffset = checkAttribute(output, node, "data-total-y");
113    if (expectedOffset) {
114        var totalTop = node.clientTop + node.offsetTop;
115        if (isNaN(expectedOffset) || Math.abs(totalTop - expectedOffset) >= 1)
116            failures.push("Expected " + expectedOffset + " for clientTop+offsetTop, but got " + totalTop + ", clientTop: " + node.clientTop + ", + offsetTop: " + node.offsetTop + ". ");
117    }
118
119    var expectedDisplay = checkAttribute(output, node, "data-expected-display");
120    if (expectedDisplay) {
121        var actualDisplay = getComputedStyle(node).display;
122        if (actualDisplay != expectedDisplay)
123            failures.push("Expected " + expectedDisplay + " for display, but got " + actualDisplay + ". ");
124    }
125
126    var expectedPaddingTop = checkAttribute(output, node, "data-expected-padding-top");
127    if (expectedPaddingTop) {
128        var actualPaddingTop = getComputedStyle(node).paddingTop;
129        // Trim the unit "px" from the output.
130        actualPaddingTop = actualPaddingTop.substring(0, actualPaddingTop.length - 2);
131        if (actualPaddingTop != expectedPaddingTop)
132            failures.push("Expected " + expectedPaddingTop + " for padding-top, but got " + actualPaddingTop + ". ");
133    }
134
135    var expectedPaddingBottom = checkAttribute(output, node, "data-expected-padding-bottom");
136    if (expectedPaddingBottom) {
137        var actualPaddingBottom = getComputedStyle(node).paddingBottom;
138        // Trim the unit "px" from the output.
139        actualPaddingBottom = actualPaddingBottom.substring(0, actualPaddingBottom.length - 2);
140        if (actualPaddingBottom != expectedPaddingBottom)
141            failures.push("Expected " + expectedPaddingBottom + " for padding-bottom, but got " + actualPaddingBottom + ". ");
142    }
143
144    var expectedPaddingLeft = checkAttribute(output, node, "data-expected-padding-left");
145    if (expectedPaddingLeft) {
146        var actualPaddingLeft = getComputedStyle(node).paddingLeft;
147        // Trim the unit "px" from the output.
148        actualPaddingLeft = actualPaddingLeft.substring(0, actualPaddingLeft.length - 2);
149        if (actualPaddingLeft != expectedPaddingLeft)
150            failures.push("Expected " + expectedPaddingLeft + " for padding-left, but got " + actualPaddingLeft + ". ");
151    }
152
153    var expectedPaddingRight = checkAttribute(output, node, "data-expected-padding-right");
154    if (expectedPaddingRight) {
155        var actualPaddingRight = getComputedStyle(node).paddingRight;
156        // Trim the unit "px" from the output.
157        actualPaddingRight = actualPaddingRight.substring(0, actualPaddingRight.length - 2);
158        if (actualPaddingRight != expectedPaddingRight)
159            failures.push("Expected " + expectedPaddingRight + " for padding-right, but got " + actualPaddingRight + ". ");
160    }
161
162    var expectedMarginTop = checkAttribute(output, node, "data-expected-margin-top");
163    if (expectedMarginTop) {
164        var actualMarginTop = getComputedStyle(node).marginTop;
165        // Trim the unit "px" from the output.
166        actualMarginTop = actualMarginTop.substring(0, actualMarginTop.length - 2);
167        if (actualMarginTop != expectedMarginTop)
168            failures.push("Expected " + expectedMarginTop + " for margin-top, but got " + actualMarginTop + ". ");
169    }
170
171    var expectedMarginBottom = checkAttribute(output, node, "data-expected-margin-bottom");
172    if (expectedMarginBottom) {
173        var actualMarginBottom = getComputedStyle(node).marginBottom;
174        // Trim the unit "px" from the output.
175        actualMarginBottom = actualMarginBottom.substring(0, actualMarginBottom.length - 2);
176        if (actualMarginBottom != expectedMarginBottom)
177            failures.push("Expected " + expectedMarginBottom + " for margin-bottom, but got " + actualMarginBottom + ". ");
178    }
179
180    var expectedMarginLeft = checkAttribute(output, node, "data-expected-margin-left");
181    if (expectedMarginLeft) {
182        var actualMarginLeft = getComputedStyle(node).marginLeft;
183        // Trim the unit "px" from the output.
184        actualMarginLeft = actualMarginLeft.substring(0, actualMarginLeft.length - 2);
185        if (actualMarginLeft != expectedMarginLeft)
186            failures.push("Expected " + expectedMarginLeft + " for margin-left, but got " + actualMarginLeft + ". ");
187    }
188
189    var expectedMarginRight = checkAttribute(output, node, "data-expected-margin-right");
190    if (expectedMarginRight) {
191        var actualMarginRight = getComputedStyle(node).marginRight;
192        // Trim the unit "px" from the output.
193        actualMarginRight = actualMarginRight.substring(0, actualMarginRight.length - 2);
194        if (actualMarginRight != expectedMarginRight)
195            failures.push("Expected " + expectedMarginRight + " for margin-right, but got " + actualMarginRight + ". ");
196    }
197
198    return output.checked;
199}
200
201window.checkLayout = function(selectorList, outputContainer)
202{
203    var result = true;
204    if (!selectorList) {
205        document.body.appendChild(document.createTextNode("You must provide a CSS selector of nodes to check."));
206        return;
207    }
208    var nodes = document.querySelectorAll(selectorList);
209    nodes = Array.prototype.slice.call(nodes);
210    nodes.reverse();
211    var checkedLayout = false;
212    Array.prototype.forEach.call(nodes, function(node) {
213        var failures = [];
214        checkedLayout |= checkExpectedValues(node.parentNode, failures);
215        checkedLayout |= checkSubtreeExpectedValues(node, failures);
216
217        var container = node.parentNode.className == 'container' ? node.parentNode : node;
218
219        var pre = document.createElement('pre');
220        if (failures.length) {
221            pre.className = 'FAIL';
222            result = false;
223        }
224        pre.appendChild(document.createTextNode(failures.length ? "FAIL:\n" + failures.join('\n') + '\n\n' + container.outerHTML : "PASS"));
225
226        var referenceNode = container;
227        if (outputContainer) {
228            if (!outputContainer.lastChild) {
229                // Inserting a text node so we have something to insertAfter.
230                outputContainer.textContent = " ";
231            }
232            referenceNode = outputContainer.lastChild;
233        }
234        insertAfter(pre, referenceNode);
235    });
236
237    if (!checkedLayout) {
238        document.body.appendChild(document.createTextNode("FAIL: No valid data-* attributes found in selector list : " + selectorList));
239        return false;
240    }
241
242    return result;
243}
244
245})();
246