Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
- Manifest: dom/svg/test/mochitest.toml
<!--
-->
<head>
<title>Tests specific to SVGTransformList</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="matrixUtils.js"></script>
<script type="text/javascript" src="MutationEventChecker.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="display:none;">
onload="this.pauseAnimations();">
<g id="g"/>
</svg>
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
<![CDATA[
SimpleTest.waitForExplicitFinish();
/*
This file runs a series of SVGTransformList specific tests. Generic SVGXxxList
tests can be found in test_SVGxxxList.xhtml. Anything that can be generalized
to other list types belongs there.
*/
function main() {
var g = $("g");
var tests =
[ testConsolidateMatrix,
testConsolidateMatrixOneElem,
testConsolidateMatrixZeroElem,
testCreateSVGTransformFromMatrix,
testReadOnly,
testOrphan,
testFailedSet,
testMutationEvents,
];
for (var i = 0; i < tests.length; i++) {
tests[i](g);
}
SimpleTest.finish();
}
function testConsolidateMatrix(g) {
// This is the example from SVG 1.1 section 7.5
g.setAttribute("transform",
"translate(50 90) rotate(-45) translate(130 160)");
var list = g.transform.baseVal;
is(list.numberOfItems, 3, "Unexpected length of unconsolidated list");
// Sanity check -- take ref to first item in list and validate it
var first_item = list.getItem(0);
is(first_item.type, SVGTransform.SVG_TRANSFORM_TRANSLATE,
"Unexpected type of first item in list");
cmpMatrix(first_item.matrix, [1, 0, 0, 1, 50, 90],
"Unexpected value for first item in list");
// Consolidate
var consolidated = list.consolidate();
is(list.numberOfItems, 1, "Unexpected length of consolidated list");
ok(consolidated === list.getItem(0),
"Consolidate return value should be first item in list, not a copy");
is(consolidated.type, SVGTransform.SVG_TRANSFORM_MATRIX,
"Consolidated transform not of type matrix");
const angle = -Math.PI / 4;
roughCmpMatrix(consolidated.matrix,
[Math.cos(angle), Math.sin(angle),
-Math.sin(angle), Math.cos(angle),
130 * Math.cos(angle) - 160 * Math.sin(angle) + 50,
160 * Math.cos(angle) + 130 * Math.sin(angle) + 90],
"Unexpected result after consolidating matrices");
// Check ref to first item in list
// a) should not have changed
is(first_item.type, SVGTransform.SVG_TRANSFORM_TRANSLATE,
"Unexpected type of cached first item in list after consolidating");
cmpMatrix(first_item.matrix, [1, 0, 0, 1, 50, 90],
"Unexpected value for cached first item in list after consolidating");
// b) should still be usable
first_item.setScale(2, 3);
is(first_item.type, SVGTransform.SVG_TRANSFORM_SCALE,
"Cached first item in list not usable after consolidating");
// Check consolidated is live
// a) Changes to 'consolidated' affect list
consolidated.setSkewX(45);
is(list.getItem(0).type, SVGTransform.SVG_TRANSFORM_SKEWX,
"Changing return value from consolidate doesn't affect list");
// b) Changes to list affect 'consolidated'
list.getItem(0).setRotate(90, 0, 0);
is(consolidated.type, SVGTransform.SVG_TRANSFORM_ROTATE,
"Changing list doesn't affect return value from consolidate");
}
function testConsolidateMatrixOneElem(g) {
// Check that even if we only have one item in the list it becomes a matrix
// transform (as per the spec)
g.setAttribute("transform", "translate(50 90)");
var list = g.transform.baseVal;
is(list.numberOfItems, 1, "Unexpected length of unconsolidated list");
var first_item = list.getItem(0);
is(first_item.type, SVGTransform.SVG_TRANSFORM_TRANSLATE,
"Unexpected type of first item in list");
cmpMatrix(first_item.matrix, [1, 0, 0, 1, 50, 90],
"Unexpected value for first item in list");
// Consolidate
var consolidated = list.consolidate();
is(list.numberOfItems, 1, "Unexpected length of consolidated list");
ok(consolidated === list.getItem(0),
"Consolidate return value should be first item in list, not a copy");
is(consolidated.type, SVGTransform.SVG_TRANSFORM_MATRIX,
"Consolidated transform not of type matrix");
cmpMatrix(consolidated.matrix, [1, 0, 0, 1, 50, 90],
"Unexpected consolidated matrix value");
}
function testConsolidateMatrixZeroElem(g) {
// Check that zero items returns null
g.setAttribute("transform", "");
var list = g.transform.baseVal;
is(list.numberOfItems, 0, "Unexpected length of unconsolidated list");
var consolidated = list.consolidate();
ok(consolidated === null,
"consolidate() should return null for a zero-length transform list");
}
function testCreateSVGTransformFromMatrix(g) {
var m = createMatrix(1, 2, 3, 4, 5, 6);
// "Creates an SVGTransform object which is initialized to transform of type
// SVG_TRANSFORM_MATRIX and whose values are the given matrix. The values from
// the parameter matrix are copied, the matrix parameter is not adopted as
// SVGTransform::matrix."
var list = g.transform.baseVal;
list.clear();
var t = list.createSVGTransformFromMatrix(m);
// Check that list hasn't changed
is(list.numberOfItems, 0,
"Transform list changed after calling createSVGTransformFromMatrix");
// Check return value
is(t.type, SVGTransform.SVG_TRANSFORM_MATRIX,
"Returned transform not of type matrix");
cmpMatrix(t.matrix, [1, 2, 3, 4, 5, 6],
"Unexpected returned matrix value");
// Check values are copied
ok(t.matrix != m, "Matrix should be copied not adopted");
m.a = 2;
is(t.matrix.a, 1,
"Changing source matrix should not affect newly created transform");
// null should give us an identity matrix
t = list.createSVGTransformFromMatrix(null);
cmpMatrix(t.matrix, [1, 0, 0, 1, 0, 0],
"Unexpected returned matrix value");
// Try passing in bad values ("undefined" etc.)
let exception = null;
try {
t = list.createSVGTransformFromMatrix("undefined");
} catch (e) { exception = e; }
ok(exception,
"Failed to throw for string input to createSVGTransformFromMatrix");
exception = null;
try {
t = list.createSVGTransformFromMatrix(SVGMatrix(t));
} catch (e) { exception = e; }
ok(exception,
"Failed to throw for bad input to createSVGTransformFromMatrix");
exception = null;
}
function testReadOnly(g) {
// Just some data to work with
g.setAttribute("transform", "translate(50 90)");
// baseVal / animVal are readonly attributes
// Create another (empty) transform list
var otherg = document.createElementNS(SVG_NS, "g");
g.parentNode.appendChild(otherg);
is(g.transform.baseVal.numberOfItems, 1,
"Unexpected number of items in transform list before attempting to set");
is(otherg.transform.baseVal.numberOfItems, 0,
"Unexpected number of items in source transform list before attempting to"
+ " set");
// Attempt to set the base value and check nothing changes
g.transform.baseVal = otherg.transform.baseVal;
is(g.transform.baseVal.numberOfItems, 1,
"baseVal should be read-only but its value has changed");
is(otherg.transform.baseVal.numberOfItems, 0,
"baseVal changed after attempting to use it set another value");
// Read-only SVGTransformList:
// Standard list methods are covered in test_SVGxxxList.xhtml so here we
// just add tests for SVGTransformList-specific methods
var roList = g.transform.animVal;
// consolidate()
var threw = false;
try {
roList.consolidate();
} catch (e) {
is(e.name, "NoModificationAllowedError",
"Got unexpected exception " + e +
", expected NoModificationAllowedError");
is(e.code, DOMException.NO_MODIFICATION_ALLOWED_ERR,
"Got unexpected exception " + e +
", expected NO_MODIFICATION_ALLOWED_ERR");
threw = true;
}
ok(threw,
"Failed to throw exception when calling consolidate on read-only list");
// Read-only SVGTransform:
// read-only attributes are tested in test_transform.xhtml. Here we are
// concerned with methods that throw because this *object* is read-only
// (since it belongs to a read-only transform list)
var roTransform = roList.getItem(0);
// setMatrix
threw = false;
try {
var m = createMatrix(1, 2, 3, 4, 5, 6);
roTransform.setMatrix(m);
} catch (e) {
is(e.name, "NoModificationAllowedError",
"Got unexpected exception " + e +
", expected NoModificationAllowedError");
is(e.code, DOMException.NO_MODIFICATION_ALLOWED_ERR,
"Got unexpected exception " + e +
", expected NO_MODIFICATION_ALLOWED_ERR");
threw = true;
}
ok(threw, "Failed to throw exception when calling setMatrix on read-only"
+ " transform");
// setTranslate
threw = false;
try {
roTransform.setTranslate(2, 3);
} catch (e) {
threw = true;
}
ok(threw, "Failed to throw when calling setTranslate on read-only"
+ " transform");
// setScale
threw = false;
try {
roTransform.setScale(2, 3);
} catch (e) {
threw = true;
}
ok(threw, "Failed to throw when calling setScale on read-only transform");
// setRotate
threw = false;
try {
roTransform.setRotate(1, 2, 3);
} catch (e) {
threw = true;
}
ok(threw, "Failed to throw when calling setRotate on read-only transform");
// setSkewX
threw = false;
try {
roTransform.setSkewX(2);
} catch (e) {
threw = true;
}
ok(threw, "Failed to throw when calling setSkewX on read-only transform");
// setSkewY
threw = false;
try {
roTransform.setSkewY(2);
} catch (e) {
threw = true;
}
ok(threw, "Failed to throw when calling setSkewY on read-only transform");
// Read-only SVGMatrix
var roMatrix = roTransform.matrix;
threw = false;
try {
roMatrix.a = 1;
} catch (e) {
is(e.name, "NoModificationAllowedError",
"Got unexpected exception " + e +
", expected NoModificationAllowedError");
is(e.code, DOMException.NO_MODIFICATION_ALLOWED_ERR,
"Got unexpected exception " + e +
", expected NO_MODIFICATION_ALLOWED_ERR");
threw = true;
}
ok(threw, "Failed to throw exception when modifying read-only matrix");
}
function testOrphan(g) {
// Although this isn't defined, if a read-only object becomes orphaned
// (detached from it's parent), then presumably it should become editable
// again.
// As with the read-only test set a value to test with
g.setAttribute("transform", "translate(50 90)");
var roList = g.transform.animVal;
var roTransform = roList.getItem(0);
var roMatrix = roTransform.matrix;
// Orphan transform list contents by re-setting transform attribute
g.setAttribute("transform", "");
// Transform should now be editable
var exception = null;
try {
roTransform.setTranslate(5, 3);
} catch (e) {
exception = e;
}
ok(exception === null,
"Unexpected exception " + exception + " modifying orphaned transform");
// So should matrix
exception = null;
try {
roMatrix.a = 1;
} catch (e) {
exception = e;
}
ok(exception === null,
"Unexpected exception " + exception + " modifying orphaned matrix");
}
function testFailedSet(g) {
// Check that a parse failure results in the attribute being empty
// Set initial value
g.setAttribute("transform", "translate(50 90)");
var list = g.transform.baseVal;
is(list.numberOfItems, 1, "Unexpected initial length of list");
// Attempt to set bad value
g.setAttribute("transform", "translate(40 50) scale(a)");
is(list.numberOfItems, 0,
"Transform list should be empty after setting bad value");
is(g.transform.animVal.numberOfItems, 0,
"Animated transform list should also be empty after setting bad value");
}
function testMutationEvents(g) {
// Check mutation events
// Set initial value
g.setAttribute("transform", "translate(50 90)");
var list = g.transform.baseVal;
is(list.numberOfItems, 1, "Unexpected initial length of list");
var eventChecker = new MutationEventChecker;
eventChecker.watchAttr(g, "transform");
// consolidate
//
// Consolidate happens to generate two modification events in our
// implementation--it's not ideal but it's better than none
eventChecker.expect("modify modify modify");
g.setAttribute("transform", "translate(10 10) translate(10 10)");
list.consolidate();
// In the following, each of the operations is performed twice but only one
// mutation event is expected. This is to check that redundant mutation
// events are not sent.
// transform.setMatrix
eventChecker.expect("modify");
var mx = $("svg").createSVGMatrix();
list[0].setMatrix(mx);
list[0].setMatrix(mx);
[
{a: 1, m11: 2},
{a: Infinity},
{b: 0, m12: -1},
{c: Infinity, m21: -Infinity},
{d: 0, m22: NaN},
{e: 1, m41: 1.00000001},
{f: 0, m42: Number.MIN_VALUE},
].forEach(dict => {
let exception = null;
try {
list[0].setMatrix(dict);
} catch (e) {
exception = e;
}
ok(exception,
"Failed to throw for invalid input to setMatrix");
});
// transform.setTranslate
eventChecker.expect("modify");
list[0].setTranslate(10, 10);
list[0].setTranslate(10, 10);
// transform.setScale
eventChecker.expect("modify");
list[0].setScale(2, 2);
list[0].setScale(2, 2);
// transform.setRotate
eventChecker.expect("modify");
list[0].setRotate(45, 1, 2);
list[0].setRotate(45, 1, 2);
// transform.setSkewX
eventChecker.expect("modify");
list[0].setSkewX(45);
list[0].setSkewX(45);
// transform.setSkewY
eventChecker.expect("modify");
list[0].setSkewY(25);
list[0].setSkewY(25);
// transform.matrix
eventChecker.expect("modify modify");
list[0].matrix.a = 1;
list[0].matrix.a = 1;
list[0].matrix.e = 5;
list[0].matrix.e = 5;
// setAttribute interaction
eventChecker.expect("modify");
list[0].setMatrix(mx);
eventChecker.expect("");
g.setAttribute("transform", "matrix(1, 0, 0, 1, 0, 0)");
list[0].setMatrix(mx);
// Attribute removal
eventChecker.expect("remove");
g.removeAttribute("transform");
// Non-existent attribute removal
eventChecker.expect("");
g.removeAttribute("transform");
g.removeAttributeNS(null, "transform");
eventChecker.finish();
}
window.addEventListener("load",
() => SpecialPowers.pushPrefEnv({"set": [["dom.mutation_events.enabled", true]]}, main));
]]>
</script>
</pre>
</body>
</html>