1 // global async_test, assert_equals
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.
9 // The tests loops through different combinations of:
11 // * width and height on <svg>, both through style and attributes
13 // * viewBox on <svg> (gives intrinsic ratio)
15 // * width and height on containing block of <object>
17 // All these contribute to the final size of the SVG in some way.
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.
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.
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.
32 function parseLength(l
) {
33 var match
= /^([-+]?[0-9]+|[-+]?[0-9]*\.[0-9]+)(px|%)?$/.exec(l
);
36 return new Length(Number(match
[1]), match
[2] ? match
[2] : "px");
39 function parseViewBox(input
) {
43 var arr
= input
.split(' ');
44 return arr
.map(function(a
) { return parseInt(a
); });
47 // Only px and % are used
48 function convertToPx(input
, percentRef
) {
51 var length
= parseLength(input
);
52 if (length
.amount
== 0)
56 if (length
.unit
== "%" && percentRef
=== undefined)
58 return length
.amount
* { px
: 1,
59 "%": percentRef
/100}[length
.unit
];
62 function Length(amount
, unit
) {
67 function describe(data
) {
68 function dumpObject(obj
) {
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)
77 else if (typeof value
== 'object')
79 if (value
instanceof Array
)
80 value
= "[" + value
+ "]";
82 value
= "{" + dumpObject(value
) + "}";
86 r
+= property
+ ": " + value
+ ", ";
91 var result
= dumpObject(data
);
93 return "(initial values)";
97 function TestData(config
) {
99 this.name
= describe(config
);
101 if (config
.svgWidthStyle
)
102 this.style
["width"] = config
.svgWidthStyle
;
104 this.mapPresentationalHintLength("width", config
.svgWidthAttr
);
106 if (config
.svgHeightStyle
)
107 this.style
["height"] = config
.svgHeightStyle
;
109 this.mapPresentationalHintLength("height", config
.svgHeightAttr
);
112 TestData
.prototype.mapPresentationalHintLength
=
113 function(cssProperty
, attr
) {
115 var l
= parseLength(attr
);
117 this.style
[cssProperty
] = l
.amount
+ l
.unit
;
121 TestData
.prototype.computedWidthIsAuto = function() {
122 return !this.style
["width"] || this.style
["width"] == 'auto';
125 TestData
.prototype.computedHeightIsAuto = function() {
126 return !this.style
["height"] || this.style
["height"] == 'auto' ||
127 (parseLength(this.style
["height"]).unit
== '%' &&
128 this.containerComputedHeightIsAuto());
131 TestData
.prototype.containerComputedWidthIsAuto = function() {
132 return !this.config
.containerWidthStyle
||
133 this.config
.containerWidthStyle
== 'auto';
136 TestData
.prototype.containerComputedHeightIsAuto = function() {
137 return !this.config
.containerHeightStyle
||
138 this.config
.containerHeightStyle
== 'auto';
141 TestData
.prototype.intrinsicInformation = function() {
142 var w
= convertToPx(this.config
.svgWidthAttr
) || 0;
143 var h
= convertToPx(this.config
.svgHeightAttr
) || 0;
148 var vb
= parseViewBox(this.config
.svgViewBoxAttr
);
159 return { width
: w
, height
: h
, ratio
: r
};
163 TestData
.prototype.computeInlineReplacedSize = function() {
164 var intrinsic
= this.intrinsicInformation();
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
;
173 return calculateUsedWidth() / intrinsic
.ratio
;
174 if (intrinsic
.height
)
175 return intrinsic
.height
;
179 return convertToPx(self
.style
["height"],
180 convertToPx(self
.config
.containerHeightStyle
,
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
196 return self
.outerWidth
;
198 return convertToPx(self
.config
.containerWidthStyle
,
203 return intrinsic
.width
;
207 if (self
.containerComputedWidthIsAuto())
208 return convertToPx(self
.style
["width"], self
.outerWidth
);
210 return convertToPx(self
.style
["width"],
211 convertToPx(self
.config
.containerWidthStyle
,
214 return { width
: calculateUsedWidth(),
215 height
: calculateUsedHeight() };
218 var testContainer
= document
.querySelector('#testContainer');
219 TestData
.prototype.outerWidth
= testContainer
.getBoundingClientRect().width
;
220 TestData
.prototype.outerHeight
= testContainer
.getBoundingClientRect().height
;
222 window
.TestData
= TestData
;
225 function setupContainer(testData
, svgElement
, options
) {
226 options
= options
|| {};
228 var container
= document
.createElement("div");
230 container
.id
= "container";
231 if (testData
.config
.containerWidthStyle
)
232 container
.style
.width
= testData
.config
.containerWidthStyle
;
234 if (testData
.config
.containerHeightStyle
)
235 container
.style
.height
= testData
.config
.containerHeightStyle
;
238 container
.appendChild(document
.createTextNode("\n\t\t"));
239 container
.appendChild(svgElement
);
241 container
.appendChild(document
.createTextNode("\n\t"));
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
);
263 function buildDemo(testData
) {
264 // Non-essential debugging tool
266 var options
= { pretty
: true };
268 testData
.computeInlineReplacedSize();
270 setupContainer(testData
, setupSVGElement(testData
), options
);
272 var root
= document
.createElement("html");
273 var style
= document
.createElement("style");
275 style
.textContent
= "\n" +
276 "\tbody { margin: 0; font-family: sans-serif }\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";
291 root
.appendChild(document
.createTextNode("\n"));
292 root
.appendChild(style
);
293 root
.appendChild(document
.createTextNode("\n"));
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" +
307 root
.appendChild(script
);
308 root
.appendChild(document
.createTextNode("\n"));
310 var expectedElement
= document
.createElement("div");
311 expectedElement
.id
= "expected";
312 root
.appendChild(expectedElement
);
313 root
.appendChild(document
.createTextNode("\n"));
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"));
323 return "<!DOCTYPE html>\n" + root
.outerHTML
;
326 function doCombinationTest(values
, func
)
328 // Recursively construct all possible combinations of values and
329 // send them to |func|. Example:
331 // values: [["X", ["a", "b"]],
332 // ["Y", ["c", "d"]]]
334 // generates the objects:
336 // 1: { "X": "a", "Y": "c" }
337 // 2: { "X": "a", "Y": "d" }
338 // 3: { "X": "b", "Y": "c" }
339 // 4: { "X": "b", "Y": "d" }
341 // and each will be sent to |func| with the corresponding prefixed
342 // id (modulo order).
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
) {
351 new_config
[k
] = config
[k
];
352 new_config
[configKey
] = configValue
;
353 doCombinationTestRecursive(slicedValues
.slice(1), new_config
);
356 func(config
, combinationId
++);
359 doCombinationTestRecursive(values
, {});
362 var debugHint = function(id
) { return "(append ?"+id
+" to debug) " };
364 if (window
.location
.search
) {
365 testSingleId
= window
.location
.search
.substring(1);
366 debugHint = function(id
) { return ""; };
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
);
382 testData
.computeInlineReplacedSize();
383 var svgElement
= setupSVGElement(testData
);
385 setupContainer(testData
, svgElement
);
387 var checkSize = function() {
389 svgElement
.getBoundingClientRect();
392 assert_equals(svgRect
.width
,
394 debugHint(id
) + "Wrong width");
395 assert_equals(svgRect
.height
,
397 debugHint(id
) + "Wrong height");
399 testContainer
.removeChild(container
);
401 document
.body
.removeChild(testContainer
);
405 testContainer
.appendChild(container
);
406 test(checkSize
, testData
.name
);
409 if (testSingleId
== id
) {
410 var pad = function(n
, width
, z
) {
413 return n
.length
>= width
? n
: new Array(width
- n
.length
+ 1).join(z
) + n
;
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(demo
);
421 document
.body
.appendChild(iframe
);
423 document
.body
.insertAdjacentHTML(
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>');