/* globals Power0 */
/* eslint no-console: ["error", { allow: ["log", "info", "warn", "error"] }] */
/**
* Imports
*/
/**
* A class that can be used to perform automated user interface tests.
*
* @example
* // Create the UITest object
* const test = new UITest({
* timeScale: 2
* })
*
* // Add an action to the test case
* test.tap(button, {eventType: 'click'})
*
* // Start the test case
* test.start()
*
* @class
* @see {@link https://www.iwm-tuebingen.de/iwmbrowser/lib/pixi/uitest.html|DocTest}
*/
export default class UITest {
/**
* Creates an instance of an UITest.
*
* In the background, the class UITest uses the Greensock TimelineMax class. The opts object is passed directly to the TimelineMax class, so it can use any key that uses the TimelineMax class.
*
* @constructor
* @param {object} [opts] - An options object to specify the behaviour of the test case.
* @param {number} [opts.timeScale=1] - The speed at which the test should run, see https://greensock.com/docs/TimelineMax/timeScale().
* @param {string} [opts.eventType=auto] - The type of events which should be used. Possible values: pointer, touch, mouse, auto. If set to auto, the eventType is set depending on the support of the browser used.
* @param {boolean} [opts.debug=false] - If set to true, multiple informations will be print to the console.
* @param {number} [opts.defaultInterval] - The interval used when no action is specified for an action.
*/
constructor(opts = {}) {
this.opts = Object.assign(
{},
{
timeScale: 1,
eventType: 'auto',
debug: false,
defaultInterval: null
},
opts
)
// timeline
//--------------------
this._timeline = new TimelineMax(
Object.assign(
{},
{
paused: true
},
this.opts
)
)
this._timeline.timeScale(this.opts.timeScale)
// eventType
//--------------------
if (this.opts.eventType === 'auto') {
if (window.PointerEvent) {
this.opts.eventType = 'pointer'
} else if ('ontouchstart' in window) {
this.opts.eventType = 'touch'
} else {
this.opts.eventType = 'mouse'
}
}
if (this.opts.debug) {
console.log(`Event type: ${this.opts.eventType}`)
}
this._timelinePositions = [0]
this._actions = 0
// setup
//-----------------
this.setup()
}
/**
* Generates the required structure.
*
* @private
* @return {UITest} A reference to the UITest for chaining.
*/
setup() {
return this
}
/**
* Gets the Greensock TimelineMax object, used in the background of UITest.
*
* @member {TimelineMax}
*/
get timeline() {
return this._timeline
}
/**
* Starts the test case and executes the corresponding statements in the specified order.
*
* @return {UITest} A reference to the UITest for chaining.
*/
start() {
this._timeline.play()
return this
}
/**
* Stops the test case and stops executing any further instructions.
*
* @return {UITest} A reference to the UITest for chaining.
*/
stop() {
this._timeline.pause()
return this
}
/**
* Clears all instructions of the test case.
*
* @return {UITest} A reference to the UITest for chaining.
*/
clear() {
this._timeline.clear()
return this
}
/**
* Restarts the test case.
*
* @return {UITest} A reference to the UITest for chaining.
*/
restart() {
this._timeline.restart()
return this
}
/**
* Executes a tap event (pointerdown, pointerup) on a specific element.
*
* @param {HTMLElement|string} element - The HTML element on which the event is to be executed, e.g. button, document, h2, canvas, etc. or an selector string. If a selector has been specified, it is evaluated immediately before the event is called using the querySelector method.
* @param {number[]|object|PIXI.DisplayObject} [position=The center of the element.] - The local position of the event in the context of the specified HTML element. If no position is specified, the center of the HTML element is used. The position can be specified as an array of numbers, as an object with the two properties x and y, or as a PIXI.Display object.
* @param {number} [timelinePosition=One second after the last action.] - The position in seconds when the event should be triggered, see shttps://greensock.com/docs/TimelineMax/addCallback().
* @param {object} [opts] - An options object to specify the behaviour of the action.
* @param {function} [opts.onStart] - A function that runs after the first event is fired. Will not be fired if only one event is running (for example, a click event). Receives the fired event object as the first parameter. The test case (UITest) is bound to this.
* @param {function} [opts.onComplete] - A function that runs after the second event is fired. Always fired, even if only one event is running (for example, a click event). Receives the fired event object as the first parameter. The test case (UITest) is bound to this.
* @param {string[]} [opts.eventTypes=['pointerdown', 'pointerup']] - The event types to use. If no types are specified, the event types specified in the UITest constructor are used (or auto if not specified).
* @param {string} [opts.eventType] - If you want the tap method to fire only one event (for example, a click event), you can specify the opts.eventType parameter. If eventType is not null, the parameter opts.eventTypes is ignored.
* @param {Window|Frame} [opts.context=window] - The context within which the optionally specified element selector should be executed.
* @param {boolean} [opts.bubbles=true] - The Event property bubbles indicates whether the event bubbles up through the DOM or not.
* @param {boolean} [opts.cancelable=true] - Events' cancelable property indicates if the event can be canceled, and therefore prevented as if the event never happened. If the event is not cancelable, then its cancelable property will be false and the event listener cannot stop the event from occurring.
*/
tap(element, position, timelinePosition, opts = {}) {
// arguments
//--------------------
;[position, timelinePosition, opts] = this.reorderArguments(arguments)
this._timelinePositions.push(timelinePosition)
// debug
//--------------------
if (this.opts.debug)
console.log('tap params', {
element,
position,
timelinePosition,
opts
})
// opts
//--------------------
opts = Object.assign(
{},
{
onStart: null,
onComplete: null,
eventTypes: this.resolveEvents(['down', 'up']),
eventType: null,
context: window,
bubbles: true,
cancelable: true
},
opts
)
if (opts.eventType) {
opts.eventTypes = opts.eventType
}
opts.eventTypes = Array.isArray(opts.eventTypes)
? opts.eventTypes
: [opts.eventTypes]
// timeline
//--------------------
this._timeline.addCallback(
position => {
// element
//--------------------
const elem = Util.extractElement(opts.context, element)
// position
//--------------------
if (position === null) {
const rect = elem.getBoundingClientRect()
position = [rect.width / 2, rect.height / 2]
}
// coords
//--------------------
const coords = Util.extractPosition(position)
if (this.opts.debug) console.log('local coords', coords)
// eventTypes
//--------------------
if (opts.eventTypes.length === 1) {
opts.eventTypes.unshift(null)
}
// event opts
//--------------------
const eventOpts = {
bubbles: opts.bubbles,
cancelable: opts.cancelable
}
if (opts.eventTypes[0]) {
// create and dispatch event
//--------------------
const eventStart = Event.create(
elem,
coords,
opts.eventTypes[0],
eventOpts
)
if (this.opts.debug)
console.log('dispatch event', eventStart)
elem.dispatchEvent(eventStart)
// onStart
//--------------------
if (opts.onStart) {
opts.onStart.call(this, eventStart)
}
}
// create and dispatch event
//--------------------
const eventComplete = Event.create(
elem,
coords,
opts.eventTypes[1],
eventOpts
)
if (this.opts.debug)
console.log('dispatch event', eventComplete)
elem.dispatchEvent(eventComplete)
// onComplete
//--------------------
if (opts.onComplete) {
opts.onComplete.call(this, eventComplete)
}
},
timelinePosition,
[position]
)
this._actions++
return this
}
/**
* Executes a pan event (pointerdown, pointermove, pointerup) on a specific element.
*
* @param {HTMLElement|string} element - The HTML element on which the event is to be executed, e.g. button, document, h2, canvas, etc. or an selector string. If a selector has been specified, it is evaluated immediately before the event is called using the querySelector method.
* @param {number[]|object|PIXI.DisplayObject} [position=The center of the element.] - The local position of the event in the context of the specified HTML element. If no position is specified, the center of the HTML element is used. The position can be specified as an array of numbers, as an object with the two properties x and y, or as a PIXI.Display object.
* @param {number} [timelinePosition=One second after the last action.] - The position in seconds when the event should be triggered, see shttps://greensock.com/docs/TimelineMax/addCallback().
* @param {object} [opts] - An options object to specify the behaviour of the action.
* @param {function} [opts.onStart] - A function that runs after the first event is fired. Receives the fired event object as the first parameter. The test case (UITest) is bound to this.
* @param {function} [opts.onUpdate] - A function that runs after each execution of the second event. Receives the fired event object as the first parameter. The test case (UITest) is bound to this.
* @param {function} [opts.onComplete] - A function that runs after the third event is fired. Receives the fired event object as the first parameter. The test case (UITest) is bound to this.
* @param {number[]|object|PIXI.DisplayObject} [opts.to={x: 0, y: 0}] - The target of the pan process. The position can be specified as an array of numbers, as an object with the two properties x and y, or as a PIXI.Display object.
* @param {number} [opts.duration=1] - The duration of the pan animation in seconds, see https://greensock.com/docs/TweenLite/duration().
* @param {Ease} [opts.ease=Power0.easeNone] - The easing of the pan animation, see https://greensock.com/docs/Easing.
* @param {string[]} [opts.eventTypes=['pointerdown', 'pointermove', 'pointerup']] - The event types to use. If no types are specified, the event types specified in the UITest constructor are used (or auto if not specified).
* @param {Window|Frame} [opts.context=window] - The context within which the optionally specified element selector should be executed.
* @param {boolean} [opts.bubbles=true] - The Event property bubbles indicates whether the event bubbles up through the DOM or not.
* @param {boolean} [opts.cancelable=true] - Events' cancelable property indicates if the event can be canceled, and therefore prevented as if the event never happened. If the event is not cancelable, then its cancelable property will be false and the event listener cannot stop the event from occurring.
*/
pan(element, position, timelinePosition, opts = {}) {
// arguments
//--------------------
;[position, timelinePosition, opts] = this.reorderArguments(arguments)
this._timelinePositions.push(timelinePosition)
// debug
//--------------------
if (this.opts.debug)
console.log('tap params', {
element,
position,
timelinePosition,
opts
})
// opts
//--------------------
opts = Object.assign(
{},
{
onStart: null,
onUpdate: null,
onComplete: null,
to: { x: 0, y: 0 },
duration: 1,
ease: Power0.easeNone,
eventTypes: this.resolveEvents(['down', 'move', 'up']),
context: window,
bubbles: true,
cancelable: true
},
opts
)
// timeline
//--------------------
this._timeline.addCallback(
position => {
// element
//--------------------
const elem = Util.extractElement(opts.context, element)
// coords
//--------------------
const from = Util.extractPosition(position)
// event opts
//--------------------
const eventOpts = {
bubbles: opts.bubbles,
cancelable: opts.cancelable
}
const gsOpts = {
ease: opts.ease,
onStart: () => {
// create and dispatch event
//--------------------
const event = Event.create(
elem,
from,
opts.eventTypes[0],
eventOpts
)
if (this.opts.debug)
console.log('dispatch event', event)
elem.dispatchEvent(event)
// onStart
//--------------------
if (opts.onStart) {
opts.onStart.call(this, event)
}
},
onUpdate: () => {
// create and dispatch event
//--------------------
const event = Event.create(
elem,
from,
opts.eventTypes[1],
eventOpts
)
if (this.opts.debug)
console.log('dispatch event', event)
elem.dispatchEvent(event)
// onUpdate
//--------------------
if (opts.onUpdate) {
opts.onUpdate.call(this, event)
}
},
onComplete: () => {
// create and dispatch event
//--------------------
const event = Event.create(
elem,
from,
opts.eventTypes[2],
eventOpts
)
if (this.opts.debug)
console.log('dispatch event', event)
elem.dispatchEvent(event)
// onComplete
//--------------------
if (opts.onComplete) {
opts.onComplete.call(this, event)
}
}
}
// to
//--------------------
const object = Util.extractTo(opts)
Object.assign(gsOpts, object)
// drag animation
//--------------------
TweenLite.to(from, opts.duration, gsOpts)
},
timelinePosition,
[position]
)
this._actions++
return this
}
/**
* Executes a pinch event (pointerdown, pointermove, pointerup) on a specific element with two "fingers" simultaneously.
*
* @param {HTMLElement|string} element - The HTML element on which the event is to be executed, e.g. button, document, h2, canvas, etc. or an selector string. If a selector has been specified, it is evaluated immediately before the event is called using the querySelector method.
* @param {number[]|object|PIXI.DisplayObject} [position=The center of the element.] - The local position of the event in the context of the specified HTML element. If no position is specified, the center of the HTML element is used. The position can be specified as an array of numbers, as an object with the two properties x and y, or as a PIXI.Display object.
* @param {number} [timelinePosition=One second after the last action.] - The position in seconds when the event should be triggered, see shttps://greensock.com/docs/TimelineMax/addCallback().
* @param {object} [opts] - An options object to specify the behaviour of the action.
* @param {function} [opts.onStart] - A function that runs after the first events are fired. Receives the fired event object as the first parameter. The test case (UITest) is bound to this.
* @param {function} [opts.onUpdate] - A function that runs after each execution of the second events. Receives the fired event object as the first parameter. The test case (UITest) is bound to this.
* @param {function} [opts.onComplete] - A function that runs after the third events are fired. Receives the fired event object as the first parameter. The test case (UITest) is bound to this.
* @param {boolean} [opts.doubleCallbacks=false] - The callbacks onStart, onUpdate and onComplete will be fired only for one finger. If set to true, the events will be fired for both fingers.
* @param {number} [opts.distance=100] - The distance in pixels, how far the two "fingers" should move apart. If to or bezier specified, distance is ignored.
* @param {number[][]|object[]|PIXI.DisplayObject[]} [opts.to] - The targets of the pinch process. The position must be an array with two entries. An entry can be specified as an array of numbers, as an object with the two properties x and y, or as a PIXI.Display object. If bezier is specified, to is ignored.
* @param {number[][]|object[]|PIXI.DisplayObject[]} [opts.bezier] - The targets of the pinch process. The position must be an array with two entries. An entry may be an array of positions or a bezier object (https://greensock.com/docs/Plugins/BezierPlugin). A position in the array or the values array of the bezier object can be specified as an array of numbers, as an object with the two properties x and y, or as a PIXI.Display object. If bezier is specified, to is ignored.
* @param {number} [opts.duration=1] - The duration of the pan animation in seconds, see https://greensock.com/docs/TweenLite/duration().
* @param {Ease} [opts.ease=Power0.easeNone] - The easing of the pan animation, see https://greensock.com/docs/Easing.
* @param {string[]} [opts.eventTypes=['pointerdown', 'pointermove', 'pointerup']] - The event types to use. If no types are specified, the event types specified in the UITest constructor are used (or auto if not specified).
* @param {Window|Frame} [opts.context=window] - The context within which the optionally specified element selector should be executed.
* @param {boolean} [opts.bubbles=true] - The Event property bubbles indicates whether the event bubbles up through the DOM or not.
* @param {boolean} [opts.cancelable=true] - Events' cancelable property indicates if the event can be canceled, and therefore prevented as if the event never happened. If the event is not cancelable, then its cancelable property will be false and the event listener cannot stop the event from occurring.
*/
pinch(element, position, timelinePosition, opts = {}) {
// arguments
//--------------------
;[position, timelinePosition, opts] = this.reorderArguments(arguments)
this._timelinePositions.push(timelinePosition)
// debug
//--------------------
if (this.opts.debug)
console.log('tap params', {
element,
position,
timelinePosition,
opts
})
// opts
//--------------------
opts = Object.assign(
{},
{
onStart: null,
onUpdate: null,
onComplete: null,
doubleCallbacks: false,
duration: 1,
distance: 100,
to: null,
bezier: null,
ease: Power0.easeNone,
eventTypes: this.resolveEvents(['down', 'move', 'up']),
context: window,
bubbles: true,
cancelable: true
},
opts
)
// timeline
//--------------------
this._timeline.addCallback(
position => {
// element
//--------------------
const elem = Util.extractElement(opts.context, element)
// from
//--------------------
let from1 = null
let from2 = null
if (Array.isArray(position) && !Util.isNumber(position[0])) {
from1 = Util.extractPosition(position[0])
from2 = Util.extractPosition(position[1])
} else {
from1 = Util.extractPosition(position)
from2 = { x: from1.x, y: from1.y }
}
// to
//--------------------
let gsOpts1 = {}
let gsOpts2 = {}
if (opts.to || opts.bezier) {
;[gsOpts1, gsOpts2] = Util.extractMultiTo(opts)
} else {
const distance = opts.distance != null ? opts.distance : 100
gsOpts1.x = from1.x - distance / 2
gsOpts1.y = from1.y
gsOpts2.x = from2.x + distance / 2
gsOpts2.y = from2.y
}
// pointers
//--------------------
const pointers = new Map()
pointers.set(0, { element: from1, gsOpts: gsOpts1 })
pointers.set(1, { element: from2, gsOpts: gsOpts2 })
// loop
//--------------------
pointers.forEach((value, key) => {
// from
//--------------------
const from = value.element
// event opts
//--------------------
const eventOpts = {
bubbles: opts.bubbles,
cancelable: opts.cancelable,
pointerId: key,
isPrimary: key === 0
}
const gsOpts = {
ease: opts.ease,
onStart: () => {
// create and dispatch event
//--------------------
const event = Event.create(
elem,
from,
opts.eventTypes[0],
eventOpts
)
if (this.opts.debug)
console.log('dispatch event', event)
elem.dispatchEvent(event)
// onStart
//--------------------
if (
opts.onStart &&
(opts.doubleCallbacks || key === 0)
) {
opts.onStart.call(this, event)
}
},
onUpdate: () => {
// create and dispatch event
//--------------------
const event = Event.create(
elem,
from,
opts.eventTypes[1],
eventOpts
)
if (this.opts.debug)
console.log('dispatch event', event)
elem.dispatchEvent(event)
// onUpdate
//--------------------
if (
opts.onUpdate &&
(opts.doubleCallbacks || key === 0)
) {
opts.onUpdate.call(this, event)
}
},
onComplete: () => {
// create and dispatch event
//--------------------
const event = Event.create(
elem,
from,
opts.eventTypes[2],
eventOpts
)
if (this.opts.debug)
console.log('dispatch event', event)
elem.dispatchEvent(event)
// onComplete
//--------------------
if (
opts.onComplete &&
(opts.doubleCallbacks || key === 0)
) {
opts.onComplete.call(this, event)
}
}
}
// to
//--------------------
Object.assign(gsOpts, value.gsOpts)
// drag animation
//--------------------
TweenLite.to(from, opts.duration, gsOpts)
})
},
timelinePosition,
[position]
)
this._actions++
return this
}
// /**
// * Adds a tap event to the timeline.
// *
// * @return {UITest} A reference to the uitest for chaining.
// */
// rotate() {
// return this
// }
// /**
// * Adds a tap event to the timeline.
// *
// * @return {UITest} A reference to the uitest for chaining.
// */
// swipe() {
// return this
// }
// /**
// * Adds a tap event to the timeline.
// *
// * @return {UITest} A reference to the uitest for chaining.
// */
// press() {
// return this
// }
// /**
// * Adds a tap event to the timeline.
// *
// * @return {UITest} A reference to the uitest for chaining.
// */
// event() {
// return this
// }
/**
* Sorts the parameters so that the second, third, and fourth parameters can be optional (and possibly slip forward).
*
* @private
* @param {arguments} params - The arguments which were passed to the function.
* @returns {array} - Returns an array of the position, the timelinePosition and the opts object.
*/
reorderArguments(params) {
// first parameter
//--------------------
const element = params[0]
// other parameter
//--------------------
let position = null
let timelinePosition = null
let opts = null
// second parameter
//--------------------
if (Util.isNumber(params[1])) {
timelinePosition = params[1]
} else if (
Util.isObject(params[1]) &&
!Util.isPixiDisplayObject(params[1]) &&
(params[1].x == null || params[1].y == null)
) {
opts = params[1]
} else if (params[1] != null) {
position = params[1]
}
// third parameter
//--------------------
if (Util.isNumber(params[2])) {
timelinePosition = params[2]
} else if (Util.isObject(params[2])) {
opts = params[2]
}
// fourth parameter
//--------------------
if (Util.isObject(params[3])) {
opts = params[3]
}
// defaults
//--------------------
if (position === null) {
// will later be filled...
}
if (timelinePosition === null) {
if (this.opts.defaultInterval === null && this._actions > 1) {
throw new Error(
'No execution time was specified for this action, and a default interval was not set in the class constructor!'
)
}
timelinePosition =
Math.max(...this._timelinePositions) +
(this.opts.defaultInterval || 1)
}
if (opts === null) {
opts = {}
}
return [position, timelinePosition, opts]
}
/**
* Converts event type shortcuts to real event names.
*
* @private
* @param {string[]} events - An array of event types.
*/
resolveEvents(events) {
const data = []
if (this.opts.eventType === 'pointer') {
events.forEach(it => {
if (it === 'down') {
data.push('pointerdown')
} else if (it === 'move') {
data.push('pointermove')
} else if (it === 'up') {
data.push('pointerup')
}
})
} else if (this.opts.eventType === 'touch') {
events.forEach(it => {
if (it === 'down') {
data.push('touchstart')
} else if (it === 'move') {
data.push('touchmove')
} else if (it === 'up') {
data.push('touchend')
}
})
} else {
events.forEach(it => {
if (it === 'down') {
data.push('mousedown')
} else if (it === 'move') {
data.push('mousemove')
} else if (it === 'up') {
data.push('mouseup')
}
})
}
return data
}
}
/**
* Helper class.
*
* @example
* // Checks if a thing is a number.
* const num = Util.isNumber(20)
*
* @private
* @ignore
* @class
*/
class Util {
/**
* Resolves the element from a specific context.
*
* @static
* @param {Window|Frame} context - The context within which the optionally specified element selector should be executed.
* @return {HTMLElement|string} element - The HTML element on which the event is to be executed, e.g. button, document, h2, canvas, etc. or an selector string. If a selector has been specified, it is evaluated immediately before the event is called using the querySelector method.
*/
static extractElement(context, element) {
const cont = Util.isFrame(context)
? context.contentDocument
: context.document
const elem = Util.isString(element)
? cont.querySelector(element)
: element
return elem
}
/**
* Extracts the position of the second parameter.
*
* @static
* @param {object} object - Something were the coords should be extracted.
* @return {object} - Returns an object with the keys x and y.
*/
static extractPosition(object) {
// event coords
//--------------------
const position = { x: 0, y: 0 }
// get the position
//--------------------
if (!object) {
position.x = 0
position.y = 0
} else if (typeof object.getBounds === 'function') {
const bounds = object.getBounds()
position.x = bounds.x + bounds.width / 2
position.y = bounds.y + bounds.height / 2
} else if (Array.isArray(object)) {
position.x = object[0]
position.y = object[1]
} else if (object.x != null && object.y != null) {
position.x = object.x
position.y = object.y
}
return position
}
/**
* Extracts the to or bezier key.
*
* @static
* @param {object} opts - An options object where to or bezier should be extracted.
* @return {object} - Returns an object with the to or bezier keys.
*/
static extractTo(opts) {
const object = {}
if (opts.bezier) {
let bezier = null
if (Array.isArray(opts.bezier)) {
bezier = {
values: opts.bezier.map(it => Util.extractPosition(it)),
type: 'thru'
}
} else {
opts.bezier.values = opts.bezier.values.map(it =>
Util.extractPosition(it)
)
bezier = opts.bezier
}
object.bezier = bezier
} else {
const to = Util.extractPosition(opts.to)
object.x = to.x
object.y = to.y
}
return object
}
/**
* Extracts multiple to or bezier keys.
*
* @static
* @param {object} opts - An options object where to or bezier should be extracted.
* @return {object[]} - Returns an array of objects with the keys x and y.
*/
static extractMultiTo(opts) {
const objects = []
if (opts.bezier) {
opts.bezier.forEach(it => {
let bezier = null
if (Array.isArray(it)) {
bezier = {
values: it.map(it => Util.extractPosition(it)),
type: 'thru'
}
} else {
it.values = it.values.map(it => Util.extractPosition(it))
bezier = it
}
objects.push({
bezier
})
})
} else {
opts.to.forEach(it => {
const to = Util.extractPosition(it)
objects.push({
x: to.x,
y: to.y
})
})
}
return objects
}
/**
* Checks if a thing is a string.
*
* @static
* @param {object} object - The object to test for.
* @return {boolean} - true if the thing is a string, otherwise false.
*/
static isString(object) {
return typeof object === 'string'
}
/**
* Checks if a thing is a number.
*
* @static
* @param {object} object - The object to test for.
* @return {boolean} - true if the thing is a number, otherwise false.
*/
static isNumber(object) {
return typeof object === 'number'
}
/**
* Checks if a thing is an object.
*
* @static
* @param {object} object - The object to test for.
* @return {boolean} - true if the thing is an object, otherwise false.
*/
static isObject(object) {
return typeof object === 'object' && !Array.isArray(object)
}
/**
* Checks if a thing is an PIXI.DisplayObject.
*
* @static
* @param {object} object - The object to test for.
* @return {boolean} - true if the thing is a PIXI.DisplayObject, otherwise false.
*/
static isPixiDisplayObject(object) {
return (
typeof object.getBounds === 'function' &&
typeof object.renderWebGL === 'function' &&
typeof object.setTransform === 'function'
)
}
/**
* Checks if a thing is a frame.
*
* @static
* @param {object} object - The object to test for.
* @return {boolean} - true if the thing is a frame, otherwise false.
*/
static isFrame(object) {
return object.contentDocument != null
}
}
/**
* Event helper class.
*
* @example
* // Creates an event object.
* const event = Event.create(h2, {x: 5, y: 10}, 'touchstart')
*
* @private
* @ignore
* @class
*/
class Event {
/**
* Creates an event object.
*
* @static
* @param {HTMLElement} target - The element on which the event should be executed.
* @param {object} position - The local position of the event in relation to the target. The object must have the keys x and y.
* @param {string} type - The type of the event, see https://developer.mozilla.org/de/docs/Web/Events
* @param {object} opts - An options object. Every paramter of the event object can be overridden, see e.g. https://developer.mozilla.org/de/docs/Web/API/MouseEvent for all the properties.
*/
static create(
target,
position = { x: 0, y: 0 },
type = 'pointerup',
opts = {}
) {
const rect =
typeof target.getBoundingClientRect === 'function'
? target.getBoundingClientRect()
: { x: 0, y: 0 }
// EventInit
const eventOpts = {
bubbles: true,
cancelable: true,
composed: false
}
// UIEventInit
const uiEventOpts = {
detail: 0,
view: window
}
// MouseEvent
const mouseEventOpts = {
screenX: window.screenX + (target.offsetLeft || 0) + position.x,
screenY: window.screenY + (target.offsetTop || 0) + position.y,
clientX: rect.x + position.x,
clientY: rect.y + position.y,
ctrlKey: false,
shiftKey: false,
altKey: false,
metaKey: false,
button: 0,
buttons: 1,
relatedTarget: null,
region: null
}
// TouchEvent
const touchEventOpts = {
touches: [],
targetTouches: [],
changedTouches: [],
ctrlKey: false,
shiftKey: false,
altKey: false,
metaKey: false
}
// PointerEvent
const pointerEventOpts = {
pointerId: 0,
width: 1,
height: 1,
pressure: 0,
tangentialPressure: 0,
tiltX: 0,
tiltY: 0,
twist: 0,
pointerType: 'touch',
isPrimary: true
}
if (type.startsWith('pointer')) {
return new PointerEvent(
type,
Object.assign(
{},
eventOpts,
uiEventOpts,
mouseEventOpts,
pointerEventOpts,
opts
)
)
} else if (type.startsWith('touch')) {
return new TouchEvent(
type,
Object.assign({}, eventOpts, uiEventOpts, touchEventOpts, opts)
)
} else {
return new MouseEvent(
type,
Object.assign({}, eventOpts, uiEventOpts, mouseEventOpts, opts)
)
}
}
}