Files
notifications-admin/tests/javascripts/support/helpers.js
Tom Byers 6854361375 Expand event helpers
Extends triggerEvent, allowing the creation of
different types of event, and to change the data
on its object. Also fakes the positional data
browsers add to the event object.

Also adds helpers for simulating:
- all the events for a mouse click
- the events invovled in moving the selection in a radio group
2019-08-07 10:23:09 +01:00

241 lines
6.2 KiB
JavaScript

const triggerEvent = (el, evtType, options) => {
const eventInit = {
bubbles: true,
cancelable: true
};
let setPositionData = () => {
const browserUI = {
leftFrameBorder: 0,
topHeight: 100
};
const cursorOffset = {
x: 5,
y: 5
};
const elBoundingBox = el.getBoundingClientRect();
if (!eventInit.clientX) { eventInit.clientX = elBoundingBox.left + cursorOffset.x; }
if (!eventInit.clientY) { eventInit.clientY = elBoundingBox.top + cursorOffset.y; }
if (!eventInit.pageX) { eventInit.pageX = elBoundingBox.left + cursorOffset.x; }
if (!eventInit.pageY) { eventInit.pageY = elBoundingBox.top + cursorOffset.y; }
if (!eventInit.screenX) { eventInit.screenX = eventInit.clientX + browserUI.leftFrameBorder; }
if (!eventInit.screenY) { eventInit.screenY = eventInit.clientY + browserUI.topHeight; }
if (!eventInit.offsetX) { eventInit.offsetX = cursorOffset.x; }
if (!eventInit.offsetY) { eventInit.offsetY = cursorOffset.y; }
};
let Instance;
// mixin any specified event properties with the defaults
if (options && ('eventInit' in options)) {
Object.assign(eventInit, options.eventInit);
}
// use event interface if specified
if (options && ('interface' in options)) {
Instance = options.interface;
} else {
// otherwise, derive from the event type
switch (evtType) {
case 'click':
// click events are part of the MouseEvent interface
Instance = window.MouseEvent;
break;
case 'mousedown':
Instance = window.MouseEvent;
break;
case 'mouseup':
Instance = window.MouseEvent;
break;
case 'keydown':
Instance = window.KeyboardEvent;
break;
case 'keyup':
Instance = window.KeyboardEvent;
break;
default:
Instance = Event;
}
}
if (evtType === 'click') {
// hack for click events to simulate details of pointer interaction
setPositionData();
}
const evt = new Instance(evtType, eventInit);
el.dispatchEvent(evt);
};
function clickElementWithMouse (el) {
triggerEvent(el, 'mousedown');
triggerEvent(el, 'mouseup');
triggerEvent(el, 'click');
};
function moveSelectionToRadio (el, options) {
// movement within a radio group with arrow keys fires no keyboard events
// click event fired from option radio being activated
triggerEvent(el, 'click', {
eventInit: { pageX: 0 }
});
};
function activateRadioWithSpace (el) {
// simulate events for space key press to confirm selection
// event for space key press
triggerEvent(el, 'keydown', {
eventInit: { which: 32 }
});
// click event fired from option radio being activated
triggerEvent(el, 'click', {
eventInit: { pageX: 0 }
});
};
class ElementQuery {
constructor (el) {
this.el = el;
}
get nodeName () {
return this.el.nodeName.toLowerCase();
}
get firstTextNodeValue () {
const textNodes = Array.from(this.el.childNodes).filter(el => el.nodeType === 3);
return textNodes.length ? textNodes[0].nodeValue : undefined;
};
// returns the elements attributes as an object
hasAttributesSetTo (mappings) {
if (!this.el.hasAttributes()) { return false; }
const keys = Object.keys(mappings);
let matches = 0;
keys.forEach(key => {
if (this.el.hasAttribute(key) && (this.el.attributes[key].value === mappings[key])) {
matches++;
}
});
return matches === keys.length;
}
hasClass (classToken) {
return Array.from(this.el.classList).includes(classToken);
}
is (state) {
const test = `_is${state.charAt(0).toUpperCase()}${state.slice(1)}`;
if (ElementQuery.prototype.hasOwnProperty(test)) {
return this[test]();
}
}
// looks for a sibling before the el that matches the supplied test function
// the test function gets sent each sibling, wrapped in an Element instance
getPreviousSibling (test) {
let node = this.el.previousElementSibling;
let el;
while(node) {
el = element(node);
if (test(el)) {
return node;
}
node = node.previousElementSibling;
}
return null;
}
_isHidden () {
const display = window.getComputedStyle(this.el).getPropertyValue('display');
return display === 'none';
}
};
class WindowMock {
constructor (jest) {
this._defaults = {
height: window.innerHeight,
width: window.innerWidth
};
this._spies = {
document: {}
};
this._jest = jest;
}
setHeightTo (height) {
// mock DOM calls for window height
window.innerHeight = height;
// remove calls to document.documentElement.clientHeight when jQuery is gone. It's called to support older browsers like IE8
this._spies.document.clientHeight = this._jest.spyOn(document.documentElement, 'clientHeight', 'get').mockImplementation(() => height);
}
setWidthTo (width) {
// mock DOM calls for window width
window.innerWidth = width;
// remove calls to document.documentElement.clientWidth when jQuery is gone. It's called to support older browsers like IE8
this._spies.document.clientWidth = this._jest.spyOn(document.documentElement, 'clientWidth', 'get').mockImplementation(() => height);
}
resizeTo (dimensions) {
this.setHeightTo(dimensions.height);
this.setWidthTo(dimensions.width);
triggerEvent(window, 'resize');
}
scrollBy (scrollPosition) {
document.documentElement.scrollTop = scrollPosition;
triggerEvent(window, 'scroll');
}
reset () {
window.innerHeight = this._defaults.height;
window.innerWidth = this._defaults.width;
document.documentElement.scrollTop = 0;
// reset all spies
Object.keys(this._spies).forEach(key => {
const objectSpies = this._spies[key];
Object.keys(objectSpies).forEach(key => objectSpies[key].mockClear());
});
}
}
// function to ask certain questions of a DOM Element
const element = function (el) {
return new ElementQuery(el);
};
exports.triggerEvent = triggerEvent;
exports.clickElementWithMouse = clickElementWithMouse;
exports.moveSelectionToRadio = moveSelectionToRadio;
exports.activateRadioWithSpace = activateRadioWithSpace;
exports.element = element;
exports.WindowMock = WindowMock;