OLD | NEW |
---|---|
(Empty) | |
1 // global async_test, assert_equals | |
2 // | |
3 // This test generates a couple of scenarios (each a TestData) for | |
4 // sizing inline <svg> and has a simple JavaScript sizing | |
5 // implementation that handles the generated scenarios. It generates a | |
6 // DOM corresponding to the scenario and compares the laid out size to | |
7 // the calculated size. | |
8 // | |
9 // The tests loops through different combinations of: | |
10 // | |
11 // * width and height on <svg>, both through style and attributes | |
12 // | |
13 // * viewBox on <svg> (gives intrinsic ratio) | |
14 // | |
15 // * width and height on containing block of <object> | |
16 // | |
17 // All these contribute to the final size of the SVG in some way. | |
18 // | |
19 // The test focuses on the size of the CSS box generated by the SVG. | |
20 // The SVG is always empty by itself so no actual SVG are tested. | |
21 // | |
22 // Focus is also put on how the different attributes interact, little | |
23 // focus is put on variations within an attribute that doesn't affect | |
24 // the relationship to other attributes, i.e only px and % units are | |
25 // used since that covers the interactions. | |
26 // | |
27 // To debug a specific test append ?<test-id> to the URL. An <iframe> | |
28 // is generated with equivalent test and the source of the test is | |
29 // added to a <pre> element. | |
30 | |
31 (function() { | |
32 function parseLength(l) { | |
33 var match = /^([-+]?[0-9]+|[-+]?[0-9]*\.[0-9]+)(px|%)?$/.exec(l); | |
34 if (!match) | |
35 return null; | |
36 return new Length(Number(match[1]), match[2] ? match[2] : "px"); | |
37 } | |
38 | |
39 function parseViewBox(input) { | |
40 if (!input) | |
41 return null; | |
42 | |
43 var arr = input.split(' '); | |
44 return arr.map(function(a) { return parseInt(a); }); | |
45 } | |
46 | |
47 // Only px and % are used | |
48 function convertToPx(input, percentRef) { | |
49 if (input == null) | |
50 return null; | |
51 var length = parseLength(input); | |
52 if (length.amount == 0) | |
53 return 0; | |
54 if (!length.unit) | |
55 length.unit = "px"; | |
56 if (length.unit == "%" && percentRef === undefined) | |
57 return null; | |
58 return length.amount * { px: 1, | |
59 "%": percentRef/100}[length.unit]; | |
60 } | |
61 | |
62 function Length(amount, unit) { | |
63 this.amount = amount; | |
64 this.unit = unit; | |
65 } | |
66 | |
67 function describe(data) { | |
68 function dumpObject(obj) { | |
69 var r = ""; | |
70 for (var property in obj) { | |
71 if (obj.hasOwnProperty(property)) { | |
72 var value = obj[property]; | |
73 if (typeof value == 'string') | |
74 value = "'" + value + "'"; | |
75 else if (value == null) | |
76 value = "null"; | |
77 else if (typeof value == 'object') | |
78 { | |
79 if (value instanceof Array) | |
80 value = "[" + value + "]"; | |
81 else | |
82 value = "{" + dumpObject(value) + "}"; | |
83 } | |
84 | |
85 if (value != "null") | |
86 r += property + ": " + value + ", "; | |
87 } | |
88 } | |
89 return r; | |
90 } | |
91 var result = dumpObject(data); | |
92 if (result == "") | |
93 return "(initial values)"; | |
94 return result; | |
95 } | |
96 | |
97 function TestData(config) { | |
98 this.config = config; | |
99 this.name = describe(config); | |
100 this.style = {}; | |
101 if (config.svgWidthStyle) | |
102 this.style["width"] = config.svgWidthStyle; | |
103 else | |
104 this.mapPresentationalHintLength("width", config.svgWidthAttr); | |
105 | |
106 if (config.svgHeightStyle) | |
107 this.style["height"] = config.svgHeightStyle; | |
108 else | |
109 this.mapPresentationalHintLength("height", config.svgHeightAttr); | |
110 } | |
111 | |
112 TestData.prototype.mapPresentationalHintLength = | |
113 function(cssProperty, attr) { | |
114 if (attr) { | |
115 var l = parseLength(attr); | |
116 if (l) | |
117 this.style[cssProperty] = l.amount + l.unit; | |
118 } | |
119 }; | |
120 | |
121 TestData.prototype.computedWidthIsAuto = function() { | |
122 return !this.style["width"] || this.style["width"] == 'auto'; | |
123 }; | |
124 | |
125 TestData.prototype.computedHeightIsAuto = function() { | |
126 return !this.style["height"] || this.style["height"] == 'auto' || | |
127 (parseLength(this.style["height"]).unit == '%' && | |
128 this.containerComputedHeightIsAuto()); | |
129 }; | |
130 | |
131 TestData.prototype.containerComputedWidthIsAuto = function() { | |
132 return !this.config.containerWidthStyle || | |
133 this.config.containerWidthStyle == 'auto'; | |
134 }; | |
135 | |
136 TestData.prototype.containerComputedHeightIsAuto = function() { | |
137 return !this.config.containerHeightStyle || | |
138 this.config.containerHeightStyle == 'auto'; | |
139 }; | |
140 | |
141 TestData.prototype.intrinsicInformation = function() { | |
142 var w = convertToPx(this.config.svgWidthAttr) || 0; | |
143 var h = convertToPx(this.config.svgHeightAttr) || 0; | |
144 var r = 0; | |
145 if (w && h) { | |
146 r = w / h; | |
147 } else { | |
148 var vb = parseViewBox(this.config.svgViewBoxAttr); | |
149 if (vb) { | |
150 r = vb[2] / vb[3]; | |
151 } | |
152 if (r) { | |
153 if (!w && h) | |
154 w = h * r; | |
155 else if (!h && w) | |
156 h = w / r; | |
157 } | |
158 } | |
159 return { width: w, height: h, ratio: r }; | |
160 }; | |
161 | |
162 | |
163 TestData.prototype.computeInlineReplacedSize = function() { | |
164 var intrinsic = this.intrinsicInformation(); | |
165 var self = this; | |
166 | |
167 // http://www.w3.org/TR/CSS2/visudet.html#inline-replaced-height | |
168 function calculateUsedHeight() { | |
169 if (self.computedHeightIsAuto()) { | |
170 if (self.computedWidthIsAuto() && intrinsic.height) | |
171 return intrinsic.height; | |
172 if (intrinsic.ratio) | |
173 return calculateUsedWidth() / intrinsic.ratio; | |
174 if (intrinsic.height) | |
175 return intrinsic.height; | |
176 return 150; | |
177 } | |
178 | |
179 return convertToPx(self.style["height"], | |
180 convertToPx(self.config.containerHeightStyle, | |
181 self.outerHeight)); | |
182 } | |
183 | |
184 // http://www.w3.org/TR/CSS2/visudet.html#inline-replaced-width | |
185 function calculateUsedWidth() { | |
186 if (self.computedWidthIsAuto()) { | |
187 if (self.computedHeightIsAuto() && intrinsic.width) | |
188 return intrinsic.width; | |
189 if (!self.computedHeightIsAuto() && intrinsic.ratio) | |
190 return calculateUsedHeight() * intrinsic.ratio; | |
191 if (self.computedHeightIsAuto() && intrinsic.ratio) { | |
192 if (self.containerComputedWidthIsAuto()) { | |
193 // Note: While this is actually undefined in CSS | |
194 // 2.1, use the suggested value by examining the | |
195 // ancestor widths. | |
196 return self.outerWidth; | |
197 } else { | |
198 return convertToPx(self.config.containerWidthStyle, | |
199 self.outerWidth); | |
200 } | |
201 } | |
202 if (intrinsic.width) | |
203 return intrinsic.width; | |
204 return 300; | |
205 } | |
206 | |
207 if (self.containerComputedWidthIsAuto()) | |
208 return convertToPx(self.style["width"], self.outerWidth); | |
209 else | |
210 return convertToPx(self.style["width"], | |
211 convertToPx(self.config.containerWidthStyle, | |
212 self.outerWidth)); | |
213 } | |
214 return { width: calculateUsedWidth(), | |
215 height: calculateUsedHeight() }; | |
216 }; | |
217 | |
218 var testContainer = document.querySelector('#testContainer'); | |
219 TestData.prototype.outerWidth = testContainer.getBoundingClientRect().width; | |
220 TestData.prototype.outerHeight = testContainer.getBoundingClientRect().heigh t; | |
221 | |
222 window.TestData = TestData; | |
223 })(); | |
224 | |
225 function setupContainer(testData, svgElement, options) { | |
226 options = options || {}; | |
227 | |
228 var container = document.createElement("div"); | |
229 | |
230 container.id = "container"; | |
231 if (testData.config.containerWidthStyle) | |
232 container.style.width = testData.config.containerWidthStyle; | |
233 | |
234 if (testData.config.containerHeightStyle) | |
235 container.style.height = testData.config.containerHeightStyle; | |
236 | |
237 if (options.pretty) | |
238 container.appendChild(document.createTextNode("\n\t\t")); | |
239 container.appendChild(svgElement); | |
240 if (options.pretty) | |
241 container.appendChild(document.createTextNode("\n\t")); | |
242 | |
243 return container; | |
244 } | |
245 | |
246 function setupSVGElement(testData) { | |
247 var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg "); | |
248 svgElement.setAttribute("id", "test"); | |
249 if (testData.config.svgWidthStyle) | |
250 svgElement.style.width = testData.config.svgWidthStyle; | |
251 if (testData.config.svgHeightStyle) | |
252 svgElement.style.height = testData.config.svgHeightStyle; | |
253 if (testData.config.svgWidthAttr) | |
254 svgElement.setAttribute("width", testData.config.svgWidthAttr); | |
255 if (testData.config.svgHeightAttr) | |
256 svgElement.setAttribute("height", testData.config.svgHeightAttr); | |
257 if (testData.config.svgViewBoxAttr) | |
258 svgElement.setAttribute("viewBox", testData.config.svgViewBoxAttr); | |
259 | |
260 return svgElement; | |
261 } | |
262 | |
263 function buildDemo(testData) { | |
264 // Non-essential debugging tool | |
265 | |
266 var options = { pretty: true }; | |
267 var expectedRect = | |
268 testData.computeInlineReplacedSize(); | |
269 var container = | |
270 setupContainer(testData, setupSVGElement(testData), options); | |
271 | |
272 var root = document.createElement("html"); | |
273 var style = document.createElement("style"); | |
274 | |
275 style.textContent = "\n" + | |
276 "\tbody { margin: 0; font-family: sans-serif }\n" + | |
277 "\t#expected {\n" + | |
278 "\t\twidth: " + (expectedRect.width) + "px; height: " | |
279 + (expectedRect.height) + "px;\n" + | |
280 "\t\tborder: 10px solid lime; position: absolute;\n" + | |
281 "\t\tbackground-color: red }\n" + | |
282 "\t#testContainer { position: absolute;\n" + | |
283 "\t\ttop: 10px; left: 10px; width: 800px; height: 600px }\n" + | |
284 "\tsvg { background-color: green }\n" + | |
285 "\t.result { position: absolute; top: 0; right: 0;\n" + | |
286 "\t\tbackground-color: hsla(0,0%, 0%, 0.85); border-radius: 0.5em;\n" + | |
287 "\t\tpadding: 0.5em; border: 0.25em solid black }\n" + | |
288 "\t.pass { color: lime }\n" + | |
289 "\t.fail { color: red }\n"; | |
290 | |
291 root.appendChild(document.createTextNode("\n")); | |
292 root.appendChild(style); | |
293 root.appendChild(document.createTextNode("\n")); | |
294 | |
295 var script = document.createElement("script"); | |
296 script.textContent = "\n" + | |
297 "onload = function() {\n" + | |
298 "\tvar svgRect = \n" + | |
299 "\t\tdocument.querySelector('#test').getBoundingClientRect();\n" + | |
300 "\tpassed = (svgRect.width == " + expectedRect.width + " && " + | |
301 "svgRect.height == " + expectedRect.height + ");\n" + | |
302 "\tdocument.body.insertAdjacentHTML('beforeEnd',\n" + | |
303 "\t\t'<span class=\"result '+ (passed ? 'pass' : 'fail') " + | |
304 "+ '\">' + (passed ? 'Pass' : 'Fail') + '</span>');\n" + | |
305 "};\n"; | |
306 | |
307 root.appendChild(script); | |
308 root.appendChild(document.createTextNode("\n")); | |
309 | |
310 var expectedElement = document.createElement("div"); | |
311 expectedElement.id = "expected"; | |
312 root.appendChild(expectedElement); | |
313 root.appendChild(document.createTextNode("\n")); | |
314 | |
315 var testContainer = document.createElement("div"); | |
316 testContainer.id = "testContainer"; | |
317 testContainer.appendChild(document.createTextNode("\n\t")); | |
318 testContainer.appendChild(container); | |
319 testContainer.appendChild(document.createTextNode("\n")); | |
320 root.appendChild(testContainer); | |
321 root.appendChild(document.createTextNode("\n")); | |
322 | |
323 return "<!DOCTYPE html>\n" + root.outerHTML; | |
324 } | |
325 | |
326 function doCombinationTest(values, func) | |
327 { | |
328 // Recursively construct all possible combinations of values and | |
329 // send them to |func|. Example: | |
330 // | |
331 // values: [["X", ["a", "b"]], | |
332 // ["Y", ["c", "d"]]] | |
333 // | |
334 // generates the objects: | |
335 // | |
336 // 1: { "X": "a", "Y": "c" } | |
337 // 2: { "X": "a", "Y": "d" } | |
338 // 3: { "X": "b", "Y": "c" } | |
339 // 4: { "X": "b", "Y": "d" } | |
340 // | |
341 // and each will be sent to |func| with the corresponding prefixed | |
342 // id (modulo order). | |
343 | |
344 var combinationId = 1; | |
345 function doCombinationTestRecursive(slicedValues, config) { | |
346 if (slicedValues.length > 0) { | |
347 var configKey = slicedValues[0][0]; | |
348 slicedValues[0][1].forEach(function(configValue) { | |
349 var new_config = {}; | |
350 for (k in config) | |
351 new_config[k] = config[k]; | |
352 new_config[configKey] = configValue; | |
353 doCombinationTestRecursive(slicedValues.slice(1), new_config); | |
354 }); | |
355 } else { | |
356 func(config, combinationId++); | |
357 } | |
358 } | |
359 doCombinationTestRecursive(values, {}); | |
360 } | |
361 | |
362 var debugHint = function(id) { return "(append ?"+id+" to debug) " }; | |
363 var testSingleId; | |
364 if (window.location.search) { | |
365 testSingleId = window.location.search.substring(1); | |
366 debugHint = function(id) { return ""; }; | |
367 } | |
368 | |
369 doCombinationTest( | |
Erik Dahlström (inactive)
2014/04/15 09:37:43
shouldn't there be tests for explicit 'auto' and '
davve
2014/04/15 15:55:23
For 'auto', I'd rather only add combinations that
| |
370 [["containerWidthStyle", [null, "400px"]], | |
371 ["containerHeightStyle", [null, "400px"]], | |
372 ["svgViewBoxAttr", [ null, "0 0 100 200" ]], | |
373 ["svgWidthStyle", [ null, "100px", "50%" ]], | |
374 ["svgHeightStyle", [ null, "100px", "50%" ]], | |
375 ["svgWidthAttr", [ null, "200", "25%" ]], | |
376 ["svgHeightAttr", [ null, "200", "25%" ]]], | |
377 function(config, id) { | |
378 if (!testSingleId || testSingleId == id) { | |
379 var testData = new TestData(config); | |
380 | |
381 var expectedRect = | |
382 testData.computeInlineReplacedSize(); | |
383 var svgElement = setupSVGElement(testData); | |
384 var container = | |
385 setupContainer(testData, svgElement); | |
386 | |
387 var checkSize = function() { | |
388 var svgRect = | |
389 svgElement.getBoundingClientRect(); | |
390 | |
391 try { | |
392 assert_equals(svgRect.width, | |
393 expectedRect.width, | |
394 debugHint(id) + "Wrong width"); | |
395 assert_equals(svgRect.height, | |
396 expectedRect.height, | |
397 debugHint(id) + "Wrong height"); | |
398 } finally { | |
399 testContainer.removeChild(container); | |
400 if (testSingleId) | |
401 document.body.removeChild(testContainer); | |
402 } | |
403 }; | |
404 | |
405 testContainer.appendChild(container); | |
406 test(checkSize, testData.name); | |
407 } | |
408 | |
409 if (testSingleId == id) { | |
410 var pad = function(n, width, z) { | |
411 z = z || '0'; | |
412 n = n + ''; | |
413 return n.length >= width ? n : new Array(width - n.length + 1).j oin(z) + n; | |
414 }; | |
415 | |
416 var demo = buildDemo(testData); | |
417 var iframe = document.createElement('iframe'); | |
418 iframe.style.width = (Math.max(900, expectedRect.width)) + "px"; | |
419 iframe.style.height = (Math.max(400, expectedRect.height)) + "px"; | |
420 iframe.src = "data:text/html;charset=utf-8," + encodeURIComponent(de mo); | |
421 document.body.appendChild(iframe); | |
422 | |
423 document.body.insertAdjacentHTML( | |
424 'beforeEnd', | |
425 '<p><a href="data:application/octet-stream;charset=utf-8;base64, ' + | |
426 btoa(demo) + '" download="svg-in-object-test-' + pad(id, 3) + '.html">Download</a></p>'); | |
427 } | |
428 }); | |
OLD | NEW |