diff --git a/src/utils/tracked-controls.js b/src/utils/tracked-controls.js index 1a0126a869c..2c43a462e02 100644 --- a/src/utils/tracked-controls.js +++ b/src/utils/tracked-controls.js @@ -27,7 +27,15 @@ export function checkControllerPresentAndSetup (component, idPrefix, queryObject // Update controller presence. if (isPresent) { component.addEventListeners(); + // On reconnect, injectTrackedControls calls setAttribute('tracked-controls', ...) + // with unchanged data so update/updateController is skipped. + // tracked-controls' own controllersupdated listener would call updateController, + // but it was registered after the specific controller component's listener + // (e.g. meta-touch-controls) so it fires too late. + // Explicitly call updateController before emitting controllerconnected. + var trackedControls = el.components['tracked-controls']; component.injectTrackedControls(controller); + if (trackedControls) { trackedControls.updateController(); } el.emit('controllerconnected', {name: component.name, component: component}); } else { component.removeEventListeners(); diff --git a/tests/utils/tracked-controls.test.js b/tests/utils/tracked-controls.test.js index a96583973be..235db934eee 100644 --- a/tests/utils/tracked-controls.test.js +++ b/tests/utils/tracked-controls.test.js @@ -1,6 +1,59 @@ -/* global assert, sinon, suite, test */ +/* global assert, sinon, setup, suite, test */ +import { entityFactory } from '../helpers.js'; import * as trackedControlsUtils from 'utils/tracked-controls.js'; +suite('checkControllerPresentAndSetup', function () { + var el; + var system; + + setup(function (done) { + el = entityFactory(); + setTimeout(() => { + el.sceneEl.addEventListener('loaded', function () { + system = el.sceneEl.systems['tracked-controls']; + el.setAttribute('meta-touch-controls', {hand: 'left', model: false}); + done(); + }); + }); + }); + + test('tracked-controls.controller is set when controllerconnected fires on reconnect', function (done) { + var controller = { + handedness: 'left', + profiles: ['oculus-touch-v3'] + }; + + // Simulate the xrSession inputsourceschange event by setting + // system.controllers and emitting controllersupdated, which is what + // the tracked-controls system does in onInputSourcesChange. + + // First connection. + system.controllers = [controller]; + el.sceneEl.emit('controllersupdated', undefined, false); + var updateControllerSpy = sinon.spy(el.components['tracked-controls'], 'updateController'); + + assert.ok(el.components['tracked-controls'].controller, 'controller set on first connect'); + + // Disconnect. + system.controllers = []; + el.sceneEl.emit('controllersupdated', undefined, false); + assert.notOk(el.components['tracked-controls'].controller, 'controller cleared on disconnect'); + + // Reconnect. injectTrackedControls calls setAttribute('tracked-controls', ...) + // with the same data as the first connection, so update/updateController would be + // skipped without the explicit updateController call in checkControllerPresentAndSetup. + system.controllers = [controller]; + el.addEventListener('controllerconnected', function () { + assert.ok(updateControllerSpy.called, + 'updateController must be called before controllerconnected fires'); + assert.ok(el.components['tracked-controls'].controller, + 'controller must be set when controllerconnected fires on reconnect'); + done(); + }); + el.sceneEl.emit('controllersupdated', undefined, false); + }); +}); + suite('onButtonEvent', function () { test('reemit button event based on mappings', function () { var mockedComponent = {