summaryrefslogtreecommitdiffstats
path: root/dom/svg/test/MutationEventChecker.js
diff options
context:
space:
mode:
Diffstat (limited to 'dom/svg/test/MutationEventChecker.js')
-rw-r--r--dom/svg/test/MutationEventChecker.js278
1 files changed, 278 insertions, 0 deletions
diff --git a/dom/svg/test/MutationEventChecker.js b/dom/svg/test/MutationEventChecker.js
new file mode 100644
index 0000000000..860b8da567
--- /dev/null
+++ b/dom/svg/test/MutationEventChecker.js
@@ -0,0 +1,278 @@
+// Helper class to check DOM MutationEvents
+//
+// Usage:
+//
+// * Create a new event checker:
+// var eventChecker = new MutationEventChecker;
+// * Set the attribute to watch
+// eventChecker.watchAttr(<DOM element>, "<attribute name>");
+// * Set the events to expect (0..n)
+// eventChecker.expect("add", "modify");
+// OR
+// eventChecker.expect("add modify");
+// OR
+// eventChecker.expect(MutationEvent.ADDITION, MutationEvent.MODIFICATION);
+//
+// An empty string or empty set of arguments is also fine as a way of checking
+// that all expected events have been received and indicating no events are
+// expected from the following code, e.g.
+//
+// eventChecker.expect("");
+// // changes that are not expected to generate events
+// eventChecker.expect("modify");
+// // change that is expected to generate an event
+// ...
+//
+// * Either finish listening or set the next attribute to watch
+// eventChecker.finish();
+// eventChecker.watchAttr(element, "nextAttribute");
+//
+// In either case a check is performed that all expected events have been
+// received.
+//
+// * Event checking can be temporarily disabled with ignoreEvents(). The next
+// call to expect() will cause it to resume.
+
+function MutationEventChecker() {
+ this.expectedEvents = [];
+
+ this.watchAttr = function (element, attr) {
+ if (this.attr) {
+ this.finish();
+ }
+
+ this.expectedEvents = [];
+ this.element = element;
+ this.attr = attr;
+ this.oldValue = element.getAttribute(attr);
+ this.giveUp = false;
+ this.ignore = false;
+
+ this.element.addEventListener("DOMAttrModified", this._listener);
+ };
+
+ this.expect = function () {
+ if (this.giveUp) {
+ return;
+ }
+
+ ok(
+ !this.expectedEvents.length,
+ "Expecting new events for " +
+ this.attr +
+ " but the following previously expected events have still not been " +
+ "received: " +
+ this._stillExpecting()
+ );
+ if (this.expectedEvents.length) {
+ this.giveUp = true;
+ return;
+ }
+
+ this.ignore = false;
+
+ if (!arguments.length || (arguments.length == 1 && arguments[0] == "")) {
+ return;
+ }
+
+ // Turn arguments object into an array
+ var args = Array.prototype.slice.call(arguments);
+ // Check for whitespace separated keywords
+ if (
+ args.length == 1 &&
+ typeof args[0] === "string" &&
+ args[0].indexOf(" ") > 0
+ ) {
+ args = args[0].split(" ");
+ }
+ // Convert strings to event Ids
+ this.expectedEvents = args.map(this._argToEventId);
+ };
+
+ // Temporarily disable event checking
+ this.ignoreEvents = function () {
+ // Check all events have been received
+ ok(
+ this.giveUp || !this.expectedEvents.length,
+ "Going to ignore subsequent events on " +
+ this.attr +
+ " attribute, but we're still expecting the following events: " +
+ this._stillExpecting()
+ );
+
+ this.ignore = true;
+ };
+
+ this.finish = function () {
+ // Check all events have been received
+ ok(
+ this.giveUp || !this.expectedEvents.length,
+ "Finishing listening to " +
+ this.attr +
+ " attribute, but we're still expecting the following events: " +
+ this._stillExpecting()
+ );
+
+ this.element.removeEventListener("DOMAttrModified", this._listener);
+ this.attr = "";
+ };
+
+ this._receiveEvent = function (e) {
+ if (this.giveUp || this.ignore) {
+ this.oldValue = e.newValue;
+ return;
+ }
+
+ // Make sure we're expecting something at all
+ if (!this.expectedEvents.length) {
+ ok(
+ false,
+ "Unexpected " +
+ this._eventToName(e.attrChange) +
+ " event when none expected on " +
+ this.attr +
+ " attribute."
+ );
+ return;
+ }
+
+ var expectedEvent = this.expectedEvents.shift();
+
+ // Make sure we got the event we expected
+ if (e.attrChange != expectedEvent) {
+ ok(
+ false,
+ "Unexpected " +
+ this._eventToName(e.attrChange) +
+ " on " +
+ this.attr +
+ " attribute. Expected " +
+ this._eventToName(expectedEvent) +
+ " (followed by: " +
+ this._stillExpecting() +
+ ")"
+ );
+ // If we get events out of sequence, it doesn't make sense to do any
+ // further testing since we don't really know what to expect
+ this.giveUp = true;
+ return;
+ }
+
+ // Common param checking
+ is(
+ e.target,
+ this.element,
+ "Unexpected node for mutation event on " + this.attr + " attribute"
+ );
+ is(e.attrName, this.attr, "Unexpected attribute name for mutation event");
+
+ // Don't bother testing e.relatedNode since Attr nodes are on the way
+ // out anyway (but then, so are mutation events...)
+
+ // Event-specific checking
+ if (e.attrChange == MutationEvent.MODIFICATION) {
+ ok(
+ this.element.hasAttribute(this.attr),
+ "Attribute not set after modification"
+ );
+ is(
+ e.prevValue,
+ this.oldValue,
+ "Unexpected old value for modification to " + this.attr + " attribute"
+ );
+ isnot(
+ e.newValue,
+ this.oldValue,
+ "Unexpected new value for modification to " + this.attr + " attribute"
+ );
+ } else if (e.attrChange == MutationEvent.REMOVAL) {
+ ok(!this.element.hasAttribute(this.attr), "Attribute set after removal");
+ is(
+ e.prevValue,
+ this.oldValue,
+ "Unexpected old value for removal of " + this.attr + " attribute"
+ );
+ // DOM 3 Events doesn't say what value newValue will be for a removal
+ // event but generally empty strings are used for other events when an
+ // attribute isn't relevant
+ ok(
+ e.newValue === "",
+ "Unexpected new value for removal of " + this.attr + " attribute"
+ );
+ } else if (e.attrChange == MutationEvent.ADDITION) {
+ ok(
+ this.element.hasAttribute(this.attr),
+ "Attribute not set after addition"
+ );
+ // DOM 3 Events doesn't say what value prevValue will be for an addition
+ // event but generally empty strings are used for other events when an
+ // attribute isn't relevant
+ ok(
+ e.prevValue === "",
+ "Unexpected old value for addition of " + this.attr + " attribute"
+ );
+ ok(
+ typeof e.newValue == "string" && e.newValue !== "",
+ "Unexpected new value for addition of " + this.attr + " attribute"
+ );
+ } else {
+ ok(false, "Unexpected mutation event type: " + e.attrChange);
+ this.giveUp = true;
+ }
+ this.oldValue = e.newValue;
+ };
+ this._listener = this._receiveEvent.bind(this);
+
+ this._stillExpecting = function () {
+ if (!this.expectedEvents.length) {
+ return "(nothing)";
+ }
+ var eventNames = [];
+ for (var i = 0; i < this.expectedEvents.length; i++) {
+ eventNames.push(this._eventToName(this.expectedEvents[i]));
+ }
+ return eventNames.join(", ");
+ };
+
+ this._eventToName = function (evtId) {
+ switch (evtId) {
+ case MutationEvent.MODIFICATION:
+ return "modification";
+ case MutationEvent.ADDITION:
+ return "addition";
+ case MutationEvent.REMOVAL:
+ return "removal";
+ }
+ return "Unknown MutationEvent Type";
+ };
+
+ this._argToEventId = function (arg) {
+ if (typeof arg === "number") {
+ return arg;
+ }
+
+ if (typeof arg !== "string") {
+ ok(false, "Unexpected event type: " + arg);
+ return 0;
+ }
+
+ switch (arg.toLowerCase()) {
+ case "mod":
+ case "modify":
+ case "modification":
+ return MutationEvent.MODIFICATION;
+
+ case "add":
+ case "addition":
+ return MutationEvent.ADDITION;
+
+ case "removal":
+ case "remove":
+ return MutationEvent.REMOVAL;
+
+ default:
+ ok(false, "Unexpected event name: " + arg);
+ return 0;
+ }
+ };
+}