super-hands
Version:
All-in-one natural hand controller interaction component for A-Frame.
425 lines (421 loc) • 17.3 kB
JavaScript
/* global assert, setup, suite, test */
const helpers = require('../helpers')
const entityFactory = helpers.entityFactory
suite('super-hands GlobalEventHandler integration', function () {
setup(function (done) {
this.target1 = entityFactory()
this.target1.id = 'target1'
this.target2 = document.createElement('a-entity')
this.target2.id = 'target2'
this.target1.parentNode.appendChild(this.target2)
this.hand1 = helpers.controllerFactory({
'super-hands': ''
});
// add reaction components to ensure no conflicts
[this.target1, this.target2].forEach(t => {
['grabbable', 'hoverable', 'draggable', 'droppable', 'stretchable']
.forEach(c => t.setAttribute(c, ''))
})
this.hand2 = helpers.controllerFactory({
'vive-controls': 'hand: left',
'super-hands': ''
}, true)
this.target1.sceneEl.addEventListener('loaded', () => {
this.sh1 = this.hand1.components['super-hands']
this.sh2 = this.hand2.components['super-hands']
done()
})
})
test('mouseover', function (done) {
this.target1.onmouseover = e => {
assert.typeOf(e, 'MouseEvent')
assert.strictEqual(e.target, this.target1)
assert.strictEqual(e.relatedTarget, this.hand1)
done()
}
this.sh1.onHit({ detail: { el: this.target1 } })
})
test('mouseout', function (done) {
this.sh1.onHit({ detail: { el: this.target1 } })
this.target1.onmouseout = e => {
assert.typeOf(e, 'MouseEvent')
assert.strictEqual(e.target, this.target1)
assert.strictEqual(e.relatedTarget, this.hand1)
done()
}
helpers.simCollisionEnd(this.hand1, this.target1)
})
test('dragenter - carried', function (done) {
this.target1.ondragenter = e => {
assert.typeOf(e, 'MouseEvent')
assert.strictEqual(e.target, this.target1)
assert.strictEqual(e.relatedTarget, this.target2)
done()
}
this.sh1.onHit({ detail: { el: this.target1 } })
this.sh1.onDragDropStartButton()
this.sh1.onHit({ detail: { el: this.target2 } })
})
test('dragenter - hovered', function (done) {
this.target2.ondragenter = e => {
assert.typeOf(e, 'MouseEvent')
assert.strictEqual(e.target, this.target2)
assert.strictEqual(e.relatedTarget, this.target1)
done()
}
this.sh1.onHit({ detail: { el: this.target1 } })
this.sh1.onDragDropStartButton()
this.sh1.onHit({ detail: { el: this.target2 } })
})
test('dragleave by move - carried', function (done) {
this.sh1.onHit({ detail: { el: this.target1 } })
this.sh1.onDragDropStartButton()
this.sh1.onHit({ detail: { el: this.target2 } })
this.target1.ondragleave = e => {
assert.typeOf(e, 'MouseEvent')
assert.strictEqual(e.target, this.target1)
assert.strictEqual(e.relatedTarget, this.target2)
done()
}
helpers.simCollisionEnd(this.hand1, this.target2)
})
test('dragleave by move- hovered', function (done) {
this.sh1.onHit({ detail: { el: this.target1 } })
this.sh1.onDragDropStartButton()
this.sh1.onHit({ detail: { el: this.target2 } })
this.target2.ondragleave = e => {
assert.typeOf(e, 'MouseEvent')
assert.strictEqual(e.target, this.target2)
assert.strictEqual(e.relatedTarget, this.target1)
done()
}
helpers.simCollisionEnd(this.hand1, this.target2)
})
test('dragleave by drop - carried', function (done) {
this.sh1.onHit({ detail: { el: this.target1 } })
this.sh1.onDragDropStartButton()
this.sh1.onHit({ detail: { el: this.target2 } })
this.target1.ondragleave = e => {
assert.typeOf(e, 'MouseEvent')
assert.strictEqual(e.target, this.target1)
assert.strictEqual(e.relatedTarget, this.target2)
done()
}
this.sh1.onDragDropEndButton()
})
test('dragleave by drop - hovered', function (done) {
this.sh1.onHit({ detail: { el: this.target1 } })
this.sh1.onDragDropStartButton()
this.sh1.onHit({ detail: { el: this.target2 } })
this.target2.ondragleave = e => {
assert.typeOf(e, 'MouseEvent')
assert.strictEqual(e.target, this.target2)
assert.strictEqual(e.relatedTarget, this.target1)
done()
}
this.sh1.onDragDropEndButton()
})
test('dragstart', function (done) {
this.target1.ondragstart = e => {
assert.typeOf(e, 'MouseEvent')
assert.strictEqual(e.target, this.target1)
assert.strictEqual(e.relatedTarget, this.hand1)
done()
}
this.sh1.onHit({ detail: { el: this.target1 } })
this.sh1.onDragDropStartButton()
})
test('dragend', function (done) {
this.sh1.onHit({ detail: { el: this.target1 } })
this.sh1.onDragDropStartButton()
this.target1.ondragend = e => {
assert.typeOf(e, 'MouseEvent')
assert.strictEqual(e.target, this.target1)
assert.strictEqual(e.relatedTarget, this.hand1)
done()
}
this.sh1.onDragDropEndButton()
})
test('drop - target', function (done) {
this.target2.ondrop = e => {
assert.typeOf(e, 'MouseEvent')
assert.strictEqual(e.target, this.target2)
assert.strictEqual(e.relatedTarget, this.target1)
done()
}
this.sh1.onHit({ detail: { el: this.target1 } })
this.sh1.onDragDropStartButton()
this.sh1.onHit({ detail: { el: this.target2 } })
this.sh1.onDragDropEndButton()
})
test('drop - carried', function (done) {
this.target1.ondrop = e => {
assert.typeOf(e, 'MouseEvent')
assert.strictEqual(e.target, this.target1)
assert.strictEqual(e.relatedTarget, this.target2)
done()
}
this.sh1.onHit({ detail: { el: this.target1 } })
this.sh1.onDragDropStartButton()
this.sh1.onHit({ detail: { el: this.target2 } })
this.sh1.onDragDropEndButton()
})
test('mousedown', function (done) {
this.target1.onmousedown = e => {
assert.typeOf(e, 'MouseEvent')
assert.strictEqual(e.target, this.target1)
assert.strictEqual(e.relatedTarget, this.hand1)
done()
}
this.sh1.onHit({ detail: { el: this.target1 } })
this.sh1.onGrabStartButton()
})
test('mouseup', function (done) {
this.sh1.onHit({ detail: { el: this.target1 } })
this.sh1.onGrabStartButton()
this.target1.onmouseup = e => {
assert.typeOf(e, 'MouseEvent')
assert.strictEqual(e.target, this.target1)
assert.strictEqual(e.relatedTarget, this.hand1)
done()
}
this.sh1.onGrabEndButton()
})
test('mouseup different target than mousedown', function () {
const spy1 = this.sinon.spy()
const spy2 = this.sinon.spy()
this.sh1.onHit({ detail: { el: this.target1 } })
this.sh1.onGrabStartButton()
this.sh1.onHit({ detail: { el: this.target2 } })
helpers.simCollisionEnd(this.hand1, this.target1)
this.target1.onmouseup = spy1
this.target2.onmouseup = spy2
this.sh1.onGrabEndButton()
assert.isFalse(spy1.called, 'no event on old target')
assert.isTrue(spy2.called, 'event on new target')
assert.isTrue(spy2.calledWithMatch({ relatedTarget: this.hand1 }), 'event object')
})
test('click', function (done) {
this.sh1.onHit({ detail: { el: this.target1 } })
this.sh1.onGrabStartButton()
this.target1.onclick = e => {
assert.typeOf(e, 'MouseEvent')
assert.strictEqual(e.target, this.target1)
assert.strictEqual(e.relatedTarget, this.hand1)
done()
}
this.sh1.onGrabEndButton()
})
test('click does not fire if target is lost', function () {
const clickSpy = this.sinon.spy()
this.sh1.onHit({ detail: { el: this.target1 } })
this.sh1.onGrabStartButton()
this.target1.onclick = clickSpy
helpers.simCollisionEnd(this.hand1, this.target1)
this.sh1.onGrabEndButton()
assert.isFalse(clickSpy.called)
})
// need to add state tracking for mousedown-ed element
test('click only on target previously mousedown-ed', function () {
const spy1 = this.sinon.spy()
const spy2 = this.sinon.spy()
this.sh1.onHit({ detail: { el: this.target1 } })
this.sh1.onGrabStartButton()
this.sh1.onHit({ detail: { el: this.target2 } })
this.target1.onclick = spy1
this.target2.onclick = spy2
this.sh1.onGrabEndButton()
assert.isTrue(spy1.called, 'original target')
assert.isFalse(spy2.called, 'new target')
})
test('click state tracking clears on release', function () {
this.sh1.onHit({ detail: { el: this.target1 } })
this.sh1.onGrabStartButton()
assert.isTrue(this.sh1.gehClicking.has(this.target1))
this.sh1.onGrabEndButton()
assert.strictEqual(this.sh1.gehClicking.size, 0)
})
})
suite('super-hands GlobalEventHandler multiple targets', function () {
setup(function (done) {
this.target1 = entityFactory()
this.target1.id = 'target1'
this.target2 = document.createElement('a-entity')
this.target2.id = 'target2'
this.target1.parentNode.appendChild(this.target2)
this.target3 = document.createElement('a-entity')
this.target3.id = 'target3'
this.target1.parentNode.appendChild(this.target3)
this.target4 = document.createElement('a-entity')
this.target4.id = 'target4'
this.target1.parentNode.appendChild(this.target4)
this.hand1 = helpers.controllerFactory({
'super-hands': ''
})
this.hand2 = helpers.controllerFactory({
'vive-controls': 'hand: left',
'super-hands': ''
}, true)
this.hand1.parentNode.addEventListener('loaded', () => {
this.sh1 = this.hand1.components['super-hands']
this.sh2 = this.hand2.components['super-hands']
done()
})
})
test('mouseover all', function () {
const t1Spy = this.sinon.spy()
const t2Spy = this.sinon.spy()
const t3Spy = this.sinon.spy()
this.target1.addEventListener('mouseover', t1Spy)
this.target2.addEventListener('mouseover', t2Spy)
this.target3.addEventListener('mouseover', t3Spy)
this.sh1.onHit({ detail: { el: this.target1 } })
this.sh1.onHit({ detail: { el: this.target2 } })
this.sh1.onHit({ detail: { el: this.target3 } })
assert.isTrue(t1Spy.called, 'target 1')
assert.isTrue(t2Spy.called, 'target 2')
assert.isTrue(t3Spy.called, 'target 3')
})
test('mousedown all', function () {
const t1Spy = this.sinon.spy()
const t2Spy = this.sinon.spy()
const t3Spy = this.sinon.spy()
this.target1.addEventListener('mousedown', t1Spy)
this.target2.addEventListener('mousedown', t2Spy)
this.target3.addEventListener('mousedown', t3Spy)
this.sh1.onHit({ detail: { el: this.target1 } })
this.sh1.onHit({ detail: { el: this.target2 } })
this.sh1.onGrabStartButton()
this.sh1.onHit({ detail: { el: this.target3 } })
assert.isTrue(t1Spy.called, 'target 1')
assert.isTrue(t2Spy.called, 'target 2')
assert.isFalse(t3Spy.called, 'target 3 - after grab')
})
test('mouseup all', function () {
const t1Spy = this.sinon.spy()
const t2Spy = this.sinon.spy()
const t3Spy = this.sinon.spy()
this.target1.onmouseup = t1Spy
this.target2.onmouseup = t2Spy
this.target3.onmouseup = t3Spy
this.sh1.onGrabStartButton()
this.sh1.onHit({ detail: { el: this.target1 } })
this.sh1.onHit({ detail: { el: this.target2 } })
this.sh1.onHit({ detail: { el: this.target3 } })
this.sh1.onGrabEndButton()
assert.isTrue(t1Spy.called, 'target 1')
assert.isTrue(t2Spy.called, 'target 2')
assert.isTrue(t3Spy.called, 'target 3')
})
test('click all', function () {
const t1Spy = this.sinon.spy()
const t2Spy = this.sinon.spy()
const t3Spy = this.sinon.spy()
this.target1.onclick = t1Spy
this.target2.onclick = t2Spy
this.target3.onclick = t3Spy
this.sh1.onHit({ detail: { el: this.target1 } })
this.sh1.onHit({ detail: { el: this.target2 } })
this.sh1.onHit({ detail: { el: this.target3 } })
this.sh1.onGrabStartButton()
this.sh1.onGrabEndButton()
assert.isTrue(t1Spy.called, 'target 1')
assert.isTrue(t2Spy.called, 'target 2')
assert.isTrue(t3Spy.called, 'target 3')
})
// consider making multiple entities draggable
test('dragstart all', function () {
const t1Spy = this.sinon.spy()
const t2Spy = this.sinon.spy()
const t3Spy = this.sinon.spy()
this.target1.ondragstart = t1Spy
this.target2.ondragstart = t2Spy
this.target3.ondragstart = t3Spy
this.sh1.onHit({ detail: { el: this.target1 } })
this.sh1.onHit({ detail: { el: this.target2 } })
this.sh1.onHit({ detail: { el: this.target3 } })
this.sh1.onDragDropStartButton()
assert.isTrue(t1Spy.called, 'target 1')
assert.isTrue(t2Spy.called, 'target 2')
assert.isTrue(t3Spy.called, 'target 3')
})
test('dragenter all', function () {
const t1Spy = this.sinon.spy()
const t2Spy = this.sinon.spy()
const t3Spy = this.sinon.spy()
const t4Spy = this.sinon.spy()
this.target1.addEventListener('dragenter', t1Spy)
this.target2.addEventListener('dragenter', t2Spy)
this.target3.addEventListener('dragenter', t3Spy)
this.target4.addEventListener('dragenter', t4Spy)
this.sh1.onHit({ detail: { el: this.target1 } })
this.sh1.onHit({ detail: { el: this.target4 } })
this.sh1.onDragDropStartButton()
this.sh1.onHit({ detail: { el: this.target2 } })
this.sh1.onHit({ detail: { el: this.target3 } })
assert.isTrue(t1Spy.calledWithMatch({ relatedTarget: this.target2 }), 'carried 1 - target 2')
assert.isTrue(t1Spy.calledWithMatch({ relatedTarget: this.target3 }), 'carried 1 - target 3')
assert.isTrue(t4Spy.calledWithMatch({ relatedTarget: this.target2 }), 'carried 4 - target 2')
assert.isTrue(t4Spy.calledWithMatch({ relatedTarget: this.target3 }), 'carried 4 - target 3')
assert.isFalse(t1Spy.calledWithMatch({ relatedTarget: this.target1 }), 'carried 1 not on self')
assert.isFalse(t1Spy.calledWithMatch({ relatedTarget: this.target4 }), 'carried 1 not on carried 4')
assert.isTrue(t2Spy.calledWithMatch({ relatedTarget: this.target1 }), 'target 2 - carried 1')
assert.isTrue(t3Spy.calledWithMatch({ relatedTarget: this.target1 }), 'target 3 - carried 1')
assert.isTrue(t2Spy.calledWithMatch({ relatedTarget: this.target4 }), 'target 2 - carried 4')
assert.isTrue(t3Spy.calledWithMatch({ relatedTarget: this.target4 }), 'target 3 - carried 4')
})
test('drop all', function () {
const t1Spy = this.sinon.spy()
const t2Spy = this.sinon.spy()
const t3Spy = this.sinon.spy()
const t4Spy = this.sinon.spy()
this.target1.ondrop = t1Spy
this.target2.ondrop = t2Spy
this.target3.ondrop = t3Spy
this.target4.ondrop = t4Spy
this.sh1.onHit({ detail: { el: this.target1 } })
this.sh1.onHit({ detail: { el: this.target4 } })
this.sh1.onDragDropStartButton()
this.sh1.onHit({ detail: { el: this.target2 } })
this.sh1.onHit({ detail: { el: this.target3 } })
this.sh1.onDragDropEndButton()
assert.isTrue(t1Spy.called, 'target 1 drop')
assert.isTrue(t2Spy.called, 'target 2 drop')
assert.isTrue(t3Spy.called, 'target 3 drop')
assert.isTrue(t4Spy.called, 'target 4 drop')
assert.isTrue(t4Spy.called, 'target 4 drop')
assert.isTrue(t1Spy.calledWithMatch({ relatedTarget: this.target2 }), 'dropper1 t2')
assert.isTrue(t1Spy.calledWithMatch({ relatedTarget: this.target3 }), 'dropper1 t3')
assert.isTrue(t4Spy.calledWithMatch({ relatedTarget: this.target2 }), 'dropper4 t2')
assert.isTrue(t4Spy.calledWithMatch({ relatedTarget: this.target3 }), 'dropper4 t3')
assert.isTrue(t2Spy.calledWithMatch({ relatedTarget: this.target1 }), 'dropee2 t1')
assert.isTrue(t3Spy.calledWithMatch({ relatedTarget: this.target1 }), 'dropee3 t1')
assert.isTrue(t2Spy.calledWithMatch({ relatedTarget: this.target4 }), 'dropee2 t4')
assert.isTrue(t3Spy.calledWithMatch({ relatedTarget: this.target4 }), 'dropee3 t4')
assert.isFalse(t1Spy.calledWithMatch({ relatedTarget: this.target1 }), 'dropper1 self')
assert.isFalse(t1Spy.calledWithMatch({ relatedTarget: this.target4 }), 'dropper1 other dropper')
})
test('dragleave all on drop', function () {
const t1Spy = this.sinon.spy()
const t2Spy = this.sinon.spy()
const t3Spy = this.sinon.spy()
this.target1.ondragleave = t1Spy
this.target2.ondragleave = t2Spy
this.target3.ondragleave = t3Spy
this.target4.ondragleave = t3Spy
this.sh1.onHit({ detail: { el: this.target1 } })
this.sh1.onHit({ detail: { el: this.target4 } })
this.sh1.onDragDropStartButton()
this.sh1.onHit({ detail: { el: this.target2 } })
this.sh1.onHit({ detail: { el: this.target3 } })
this.sh1.onDragDropEndButton()
assert.isTrue(t2Spy.called, 'target 2 left')
assert.isTrue(t3Spy.called, 'target 3 left')
assert.isTrue(t2Spy.calledWithMatch({ relatedTarget: this.target1 }), 'target 2 w/ related1')
assert.isTrue(t3Spy.calledWithMatch({ relatedTarget: this.target1 }), 'target 3 w/ related1')
assert.isTrue(t2Spy.calledWithMatch({ relatedTarget: this.target4 }), 'target 2 w/ related4')
assert.isTrue(t3Spy.calledWithMatch({ relatedTarget: this.target4 }), 'target 3 w/ related4')
assert.isFalse(t1Spy.calledWithMatch({ relatedTarget: this.target1 }), 'carried not triggered')
assert.isFalse(t1Spy.calledWithMatch({ relatedTarget: this.target4 }), 'carried not w/ other carried')
})
})