From 173f28a09d4b88e91fe1c2db16f2aab9f171a627 Mon Sep 17 00:00:00 2001 From: Daniel Calviño Sánchez Date: Fri, 20 Oct 2017 17:11:59 +0200 Subject: Add unit tests for the navigation bar slide gesture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The slide gesture is enabled or disabled depending on the width of the browser window. In order to easily control that width the karma-viewport plugin is now used in the unit tests. Signed-off-by: Daniel Calviño Sánchez --- core/js/tests/specs/coreSpec.js | 104 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) (limited to 'core/js') diff --git a/core/js/tests/specs/coreSpec.js b/core/js/tests/specs/coreSpec.js index 9848fb46ffc..0a26a44d599 100644 --- a/core/js/tests/specs/coreSpec.js +++ b/core/js/tests/specs/coreSpec.js @@ -1112,4 +1112,108 @@ describe('Core base tests', function() { expect(OC._ajaxConnectionLostHandler.calls.count()).toBe(1); }); }); + describe('Snapper', function() { + var snapConstructorStub; + var snapperStub; + var clock; + + beforeEach(function() { + snapConstructorStub = sinon.stub(window, 'Snap'); + + snapperStub = {}; + snapperStub.enable = sinon.stub(); + snapperStub.disable = sinon.stub(); + snapperStub.close = sinon.stub(); + + snapConstructorStub.returns(snapperStub); + + clock = sinon.useFakeTimers(); + + // _.now could have been set to Date.now before Sinon replaced it + // with a fake version, so _.now must be stubbed to ensure that the + // fake Date.now will be called instead of the original one. + _.now = sinon.stub(_, 'now').callsFake(function() { + return new Date().getTime(); + }); + + $('#testArea').append('
The navigation bar
Content
'); + }); + afterEach(function() { + snapConstructorStub.restore(); + + clock.restore(); + + _.now.restore(); + + // Remove the event handler for the resize event added to the window + // due to calling window.initCore() when there is an #app-navigation + // element. + $(window).off('resize'); + + viewport.reset(); + }); + + it('is enabled on a narrow screen', function() { + viewport.set(480); + + window.initCore(); + + expect(snapConstructorStub.calledOnce).toBe(true); + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.called).toBe(false); + }); + it('is disabled on a wide screen', function() { + viewport.set(1280); + + window.initCore(); + + expect(snapConstructorStub.calledOnce).toBe(true); + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + }); + it('is enabled when resizing to a narrow screen', function() { + viewport.set(1280); + + window.initCore(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + + viewport.set(480); + + // Setting the viewport width does not automatically trigger a + // resize. + $(window).resize(); + + // The resize handler is debounced to be executed a few milliseconds + // after the resize event. + clock.tick(1000); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.calledOnce).toBe(true); + }); + it('is disabled when resizing to a wide screen', function() { + viewport.set(480); + + window.initCore(); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.called).toBe(false); + expect(snapperStub.close.called).toBe(false); + + viewport.set(1280); + + // Setting the viewport width does not automatically trigger a + // resize. + $(window).resize(); + + // The resize handler is debounced to be executed a few milliseconds + // after the resize event. + clock.tick(1000); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.calledOnce).toBe(true); + }); + }); }); -- cgit v1.2.3 From a5db0d2825424f505f9139af6d439108b9e07782 Mon Sep 17 00:00:00 2001 From: Daniel Calviño Sánchez Date: Mon, 23 Oct 2017 01:01:01 +0200 Subject: Make possible for apps to disallow the navigation bar slide gesture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On narrow screens a slide gesture can be used to open or close the navigation bar. However that gesture could conflict at times with the gestures used by certain apps (for example, if the right sidebar is open the user may expect to close it by dragging it to the right, but that could open the navigation bar instead depending on how the events are handled). This commit makes possible for apps to disallow and allow again that slide gesture. In any case, note that applications can only disallow the gesture, but they can not enable it. That is, they can prevent the gesture from being used on narrow screens, but they can not make the gesture work on wide screens; they are always limited by the base rules set by the core. Signed-off-by: Daniel Calviño Sánchez --- core/js/js.js | 36 ++++- core/js/tests/specs/coreSpec.js | 318 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 353 insertions(+), 1 deletion(-) (limited to 'core/js') diff --git a/core/js/js.js b/core/js/js.js index 9af80676d5e..c9427bf533d 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -1612,12 +1612,46 @@ function initCore() { snapper.close(); }); + var navigationBarSlideGestureEnabled = false; + var navigationBarSlideGestureAllowed = true; + var navigationBarSlideGestureEnablePending = false; + + OC.allowNavigationBarSlideGesture = function() { + navigationBarSlideGestureAllowed = true; + + if (navigationBarSlideGestureEnablePending) { + snapper.enable(); + + navigationBarSlideGestureEnabled = true; + navigationBarSlideGestureEnablePending = false; + } + }; + + OC.disallowNavigationBarSlideGesture = function() { + navigationBarSlideGestureAllowed = false; + + if (navigationBarSlideGestureEnabled) { + snapper.disable(); + + navigationBarSlideGestureEnabled = false; + navigationBarSlideGestureEnablePending = true; + } + }; + var toggleSnapperOnSize = function() { if($(window).width() > 768) { snapper.close(); snapper.disable(); - } else { + + navigationBarSlideGestureEnabled = false; + navigationBarSlideGestureEnablePending = false; + } else if (navigationBarSlideGestureAllowed) { snapper.enable(); + + navigationBarSlideGestureEnabled = true; + navigationBarSlideGestureEnablePending = false; + } else { + navigationBarSlideGestureEnablePending = true; } }; diff --git a/core/js/tests/specs/coreSpec.js b/core/js/tests/specs/coreSpec.js index 0a26a44d599..d2bb2a504e9 100644 --- a/core/js/tests/specs/coreSpec.js +++ b/core/js/tests/specs/coreSpec.js @@ -1162,6 +1162,90 @@ describe('Core base tests', function() { expect(snapperStub.enable.calledOnce).toBe(true); expect(snapperStub.disable.called).toBe(false); }); + it('is disabled when disallowing the gesture on a narrow screen', function() { + viewport.set(480); + + window.initCore(); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.called).toBe(false); + expect(snapperStub.close.called).toBe(false); + + OC.disallowNavigationBarSlideGesture(); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.called).toBe(false); + }); + it('is not disabled again when disallowing the gesture twice on a narrow screen', function() { + viewport.set(480); + + window.initCore(); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.called).toBe(false); + expect(snapperStub.close.called).toBe(false); + + OC.disallowNavigationBarSlideGesture(); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.called).toBe(false); + + OC.disallowNavigationBarSlideGesture(); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.called).toBe(false); + }); + it('is enabled when allowing the gesture after disallowing it on a narrow screen', function() { + viewport.set(480); + + window.initCore(); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.called).toBe(false); + expect(snapperStub.close.called).toBe(false); + + OC.disallowNavigationBarSlideGesture(); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.called).toBe(false); + + OC.allowNavigationBarSlideGesture(); + + expect(snapperStub.enable.calledTwice).toBe(true); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.called).toBe(false); + }); + it('is not enabled again when allowing the gesture twice after disallowing it on a narrow screen', function() { + viewport.set(480); + + window.initCore(); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.called).toBe(false); + expect(snapperStub.close.called).toBe(false); + + OC.disallowNavigationBarSlideGesture(); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.called).toBe(false); + + OC.allowNavigationBarSlideGesture(); + + expect(snapperStub.enable.calledTwice).toBe(true); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.called).toBe(false); + + OC.allowNavigationBarSlideGesture(); + + expect(snapperStub.enable.calledTwice).toBe(true); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.called).toBe(false); + }); it('is disabled on a wide screen', function() { viewport.set(1280); @@ -1171,6 +1255,42 @@ describe('Core base tests', function() { expect(snapperStub.enable.called).toBe(false); expect(snapperStub.disable.calledOnce).toBe(true); }); + it('is not disabled again when disallowing the gesture on a wide screen', function() { + viewport.set(1280); + + window.initCore(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.calledOnce).toBe(true); + + OC.disallowNavigationBarSlideGesture(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.calledOnce).toBe(true); + }); + it('is not enabled when allowing the gesture after disallowing it on a wide screen', function() { + viewport.set(1280); + + window.initCore(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.calledOnce).toBe(true); + + OC.disallowNavigationBarSlideGesture(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.calledOnce).toBe(true); + + OC.allowNavigationBarSlideGesture(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.calledOnce).toBe(true); + }); it('is enabled when resizing to a narrow screen', function() { viewport.set(1280); @@ -1192,6 +1312,130 @@ describe('Core base tests', function() { expect(snapperStub.enable.calledOnce).toBe(true); expect(snapperStub.disable.calledOnce).toBe(true); }); + it('is not enabled when resizing to a narrow screen after disallowing the gesture', function() { + viewport.set(1280); + + window.initCore(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + + OC.disallowNavigationBarSlideGesture(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + + viewport.set(480); + + // Setting the viewport width does not automatically trigger a + // resize. + $(window).resize(); + + // The resize handler is debounced to be executed a few milliseconds + // after the resize event. + clock.tick(1000); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + }); + it('is enabled when resizing to a narrow screen after disallowing the gesture and allowing it', function() { + viewport.set(1280); + + window.initCore(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + + OC.disallowNavigationBarSlideGesture(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + + OC.allowNavigationBarSlideGesture(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + + viewport.set(480); + + // Setting the viewport width does not automatically trigger a + // resize. + $(window).resize(); + + // The resize handler is debounced to be executed a few milliseconds + // after the resize event. + clock.tick(1000); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.calledOnce).toBe(true); + }); + it('is enabled when allowing the gesture after disallowing it and resizing to a narrow screen', function() { + viewport.set(1280); + + window.initCore(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + + OC.disallowNavigationBarSlideGesture(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + + viewport.set(480); + + // Setting the viewport width does not automatically trigger a + // resize. + $(window).resize(); + + // The resize handler is debounced to be executed a few milliseconds + // after the resize event. + clock.tick(1000); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + + OC.allowNavigationBarSlideGesture(); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.calledOnce).toBe(true); + }); + it('is disabled when disallowing the gesture after disallowing it, resizing to a narrow screen and allowing it', function() { + viewport.set(1280); + + window.initCore(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + + OC.disallowNavigationBarSlideGesture(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + + viewport.set(480); + + // Setting the viewport width does not automatically trigger a + // resize. + $(window).resize(); + + // The resize handler is debounced to be executed a few milliseconds + // after the resize event. + clock.tick(1000); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + + OC.allowNavigationBarSlideGesture(); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.calledOnce).toBe(true); + + OC.disallowNavigationBarSlideGesture(); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.calledTwice).toBe(true); + }); it('is disabled when resizing to a wide screen', function() { viewport.set(480); @@ -1215,5 +1459,79 @@ describe('Core base tests', function() { expect(snapperStub.disable.calledOnce).toBe(true); expect(snapperStub.close.calledOnce).toBe(true); }); + it('is not disabled again when disallowing the gesture after resizing to a wide screen', function() { + viewport.set(480); + + window.initCore(); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.called).toBe(false); + expect(snapperStub.close.called).toBe(false); + + viewport.set(1280); + + // Setting the viewport width does not automatically trigger a + // resize. + $(window).resize(); + + // The resize handler is debounced to be executed a few milliseconds + // after the resize event. + clock.tick(1000); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.calledOnce).toBe(true); + + OC.disallowNavigationBarSlideGesture(); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.calledOnce).toBe(true); + }); + it('is not enabled when allowing the gesture after disallowing it, resizing to a narrow screen and resizing to a wide screen', function() { + viewport.set(1280); + + window.initCore(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.calledOnce).toBe(true); + + OC.disallowNavigationBarSlideGesture(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.calledOnce).toBe(true); + + viewport.set(480); + + // Setting the viewport width does not automatically trigger a + // resize. + $(window).resize(); + + // The resize handler is debounced to be executed a few milliseconds + // after the resize event. + clock.tick(1000); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.calledOnce).toBe(true); + + viewport.set(1280); + + $(window).resize(); + + clock.tick(1000); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledTwice).toBe(true); + expect(snapperStub.close.calledTwice).toBe(true); + + OC.allowNavigationBarSlideGesture(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledTwice).toBe(true); + expect(snapperStub.close.calledTwice).toBe(true); + }); }); }); -- cgit v1.2.3 From de4028336aca79ab243fc0d1ad3a904d4cc72299 Mon Sep 17 00:00:00 2001 From: Daniel Calviño Sánchez Date: Thu, 14 Dec 2017 18:12:12 +0100 Subject: Force the drag to end when the navigation bar Snap is disabled by an app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a Snap was disabled it stopped listening to the events, but if a drag gesture was being performed it was kept as active. Thus, when the Snap was enabled again move events were handled as if the Snap had never been disabled, causing the gesture handling to continue where it was left. When the Snap for the navigation bar is disabled by an app it could be as a result of a different gesture being recognized by the app (for example, a vertical swipe) once both gestures have started. In that case when the other gesture ends and the Snap is enabled again any pointer movement will cause the navigation bar to slide until an "up" event is triggered again (obviously not the desired behaviour). Due to all this now when the Snap for the navigation bar is disabled by an app the current drag gesture for the navigation bar is ended. Note that this was added as a parameter to "Snap.disable()" instead of done unconditionally to keep back-compatibility with the previous behaviour (probably not really needed as it is unlikely that any app is using the Snap library relying on that behaviour... but just in case). Signed-off-by: Daniel Calviño Sánchez --- core/js/js.js | 3 ++- core/js/tests/specs/coreSpec.js | 5 +++++ core/vendor/core.js | 6 +++++- core/vendor/snapjs/dist/latest/snap.js | 6 +++++- 4 files changed, 17 insertions(+), 3 deletions(-) (limited to 'core/js') diff --git a/core/js/js.js b/core/js/js.js index c9427bf533d..a9180663405 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -1631,7 +1631,8 @@ function initCore() { navigationBarSlideGestureAllowed = false; if (navigationBarSlideGestureEnabled) { - snapper.disable(); + var endCurrentDrag = true; + snapper.disable(endCurrentDrag); navigationBarSlideGestureEnabled = false; navigationBarSlideGestureEnablePending = true; diff --git a/core/js/tests/specs/coreSpec.js b/core/js/tests/specs/coreSpec.js index d2bb2a504e9..b6c617303cf 100644 --- a/core/js/tests/specs/coreSpec.js +++ b/core/js/tests/specs/coreSpec.js @@ -1175,6 +1175,7 @@ describe('Core base tests', function() { expect(snapperStub.enable.calledOnce).toBe(true); expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.disable.alwaysCalledWithExactly(true)).toBe(true); expect(snapperStub.close.called).toBe(false); }); it('is not disabled again when disallowing the gesture twice on a narrow screen', function() { @@ -1190,6 +1191,7 @@ describe('Core base tests', function() { expect(snapperStub.enable.calledOnce).toBe(true); expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.disable.alwaysCalledWithExactly(true)).toBe(true); expect(snapperStub.close.called).toBe(false); OC.disallowNavigationBarSlideGesture(); @@ -1211,6 +1213,7 @@ describe('Core base tests', function() { expect(snapperStub.enable.calledOnce).toBe(true); expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.disable.alwaysCalledWithExactly(true)).toBe(true); expect(snapperStub.close.called).toBe(false); OC.allowNavigationBarSlideGesture(); @@ -1232,6 +1235,7 @@ describe('Core base tests', function() { expect(snapperStub.enable.calledOnce).toBe(true); expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.disable.alwaysCalledWithExactly(true)).toBe(true); expect(snapperStub.close.called).toBe(false); OC.allowNavigationBarSlideGesture(); @@ -1435,6 +1439,7 @@ describe('Core base tests', function() { expect(snapperStub.enable.calledOnce).toBe(true); expect(snapperStub.disable.calledTwice).toBe(true); + expect(snapperStub.disable.getCall(1).calledWithExactly(true)).toBe(true); }); it('is disabled when resizing to a wide screen', function() { viewport.set(480); diff --git a/core/vendor/core.js b/core/vendor/core.js index bda270892ad..e573ef10642 100644 --- a/core/vendor/core.js +++ b/core/vendor/core.js @@ -6704,9 +6704,13 @@ dav.Client.prototype = { /** * Disables Snap.js events + * @param {Boolean} endCurrentDrag Whether to end the current drag (if any) or not. */ - disable: function() { + disable: function(endCurrentDrag) { utils.dispatchEvent('disable'); + if (endCurrentDrag) { + this.action.drag.endDrag(); + } this.action.drag.stopListening(); }, diff --git a/core/vendor/snapjs/dist/latest/snap.js b/core/vendor/snapjs/dist/latest/snap.js index a0274138de0..7ae088d0aea 100644 --- a/core/vendor/snapjs/dist/latest/snap.js +++ b/core/vendor/snapjs/dist/latest/snap.js @@ -744,9 +744,13 @@ /** * Disables Snap.js events + * @param {Boolean} endCurrentDrag Whether to end the current drag (if any) or not. */ - disable: function() { + disable: function(endCurrentDrag) { utils.dispatchEvent('disable'); + if (endCurrentDrag) { + this.action.drag.endDrag(); + } this.action.drag.stopListening(); }, -- cgit v1.2.3