зеркало из https://github.com/mozilla/gecko-dev.git
282 строки
7.8 KiB
JavaScript
282 строки
7.8 KiB
JavaScript
// 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 == 0,
|
|
"Expecting new events for " +
|
|
this.attr +
|
|
" but the following previously expected events have still not been " +
|
|
"received: " +
|
|
this._stillExpecting()
|
|
);
|
|
if (this.expectedEvents.length != 0) {
|
|
this.giveUp = true;
|
|
return;
|
|
}
|
|
|
|
this.ignore = false;
|
|
|
|
if (
|
|
arguments.length == 0 ||
|
|
(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 == 0,
|
|
"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 == 0,
|
|
"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 == 0) {
|
|
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 == 0) {
|
|
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;
|
|
}
|
|
};
|
|
}
|