From 9b8bea411e4e744739b06b11ba65639422afc328 Mon Sep 17 00:00:00 2001 From: Robert Wagner Date: Tue, 15 Aug 2017 23:11:27 -0400 Subject: [PATCH] Remove jquery, fix all tests --- .../nav-items-container/component.js | 4 +- .../nav-trigger-container/component.js | 6 +- addon/mixins/debounced-response.js | 19 ++++ addon/mixins/responds-to-scroll.js | 30 ++++++ addon/services/ember-3d-nav.js | 20 ++-- addon/utils.js | 38 ++++++++ ember-cli-build.js | 4 +- package.json | 4 +- tests/acceptance/ember-3d-nav-test.js | 94 ++++++++++--------- tests/unit/mixins/debounced-response-test.js | 12 +++ tests/unit/mixins/responds-to-scroll-test.js | 12 +++ yarn.lock | 76 +++++++++++++-- 12 files changed, 251 insertions(+), 68 deletions(-) create mode 100644 addon/mixins/debounced-response.js create mode 100644 addon/mixins/responds-to-scroll.js create mode 100644 addon/utils.js create mode 100644 tests/unit/mixins/debounced-response-test.js create mode 100644 tests/unit/mixins/responds-to-scroll-test.js diff --git a/addon/components/nav-items-container/component.js b/addon/components/nav-items-container/component.js index f7171f2..6b38b52 100644 --- a/addon/components/nav-items-container/component.js +++ b/addon/components/nav-items-container/component.js @@ -1,4 +1,3 @@ -import $ from 'jquery'; import Component from '@ember/component'; import { inject as service } from '@ember/service'; import layout from './template'; @@ -7,9 +6,8 @@ export default Component.extend({ navService: service('ember-3d-nav'), layout, tagName: 'grid', - classNameBindings: [''], didInsertElement() { - $(window).on('resize', () => { + window.addEventListener('resize', () => { window.requestAnimationFrame(this.get('navService').updateSelectedNav); }); } diff --git a/addon/components/nav-trigger-container/component.js b/addon/components/nav-trigger-container/component.js index 5a20f39..1d1119f 100644 --- a/addon/components/nav-trigger-container/component.js +++ b/addon/components/nav-trigger-container/component.js @@ -1,12 +1,12 @@ /* eslint-disable ship-shape/no-on-calls-in-components */ -import $ from 'jquery'; import Component from '@ember/component'; import { on } from '@ember/object/evented'; +import { getScrollTop } from '../../utils'; import { inject as service } from '@ember/service'; import { run } from '@ember/runloop'; import Headroom from 'headroom'; import layout from './template'; -import RespondsToScroll from 'ember-responds-to/mixins/responds-to-scroll'; +import RespondsToScroll from '../../mixins/responds-to-scroll'; export default Component.extend(RespondsToScroll, { navService: service('ember-3d-nav'), @@ -17,7 +17,7 @@ export default Component.extend(RespondsToScroll, { useHeadroom: false, onScroll: on('scroll', function() { if (this.get('isFixed')) { - const scrollPosition = $(window).scrollTop(); + const scrollPosition = getScrollTop(); if (scrollPosition > 0) { this.set('isFixedAndScrolled', true); diff --git a/addon/mixins/debounced-response.js b/addon/mixins/debounced-response.js new file mode 100644 index 0000000..179b54b --- /dev/null +++ b/addon/mixins/debounced-response.js @@ -0,0 +1,19 @@ +import Ember from 'ember'; + +export default Ember.Mixin.create({ + debounce(handler) { + return (...args) => { + if (!this.isScheduled) { + this.isScheduled = true; + + window.requestAnimationFrame(() => { + this.isScheduled = false; + + if (this.get('isDestroyed')) return; + + Ember.run(this, handler, ...args); + }); + } + }; + } +}); diff --git a/addon/mixins/responds-to-scroll.js b/addon/mixins/responds-to-scroll.js new file mode 100644 index 0000000..6989526 --- /dev/null +++ b/addon/mixins/responds-to-scroll.js @@ -0,0 +1,30 @@ +import Ember from 'ember'; +import DebouncedResponse from './debounced-response'; + +function noop() { } + +// Debounces browser event, triggers 'scroll' event and calls 'scroll' handler. +export default Ember.Mixin.create( + Ember.Evented, + DebouncedResponse, + { + + scroll: noop, + + didInsertElement: function() { + this._super(...arguments); + + this.scrollHandler = this.debounce((...args) => { + this.trigger('scroll', ...args); + this.scroll(...args); + }); + + window.addEventListener('scroll', this.scrollHandler); + }, + + willDestroyElement: function() { + this._super(...arguments); + + window.removeEventListener('scroll', this.scrollHandler); + } + }); diff --git a/addon/services/ember-3d-nav.js b/addon/services/ember-3d-nav.js index dfbeb7e..6d1648a 100644 --- a/addon/services/ember-3d-nav.js +++ b/addon/services/ember-3d-nav.js @@ -1,6 +1,6 @@ -import $ from 'jquery'; import { computed } from '@ember/object'; import { getOwner } from '@ember/application'; +import { oneTimeTransitionEvent } from '../utils'; import Service from '@ember/service'; export default Service.extend({ @@ -13,8 +13,9 @@ export default Service.extend({ selectedIndex: 0, toggle3dBlock() { const addOrRemove = this.get('navIsVisible'); - - $('.main').toggleClass('nav-is-visible', addOrRemove).one('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend', () => { + const main = document.querySelectorAll('.main')[0]; + main.classList.toggle('nav-is-visible', addOrRemove); + oneTimeTransitionEvent(main, () => { // fix marker position when opening the menu (after a window resize) if (addOrRemove) { this.updateSelectedNav(); @@ -28,17 +29,14 @@ export default Service.extend({ * @private */ updateSelectedNav(type) { - const selectedItem = $('.is-selected'); - - const leftPosition = selectedItem.offset().left; + const selectedItem = document.querySelectorAll('.is-selected')[0]; + const leftPosition = selectedItem.getBoundingClientRect().left + document.body.scrollLeft; + const marker = document.querySelectorAll('.nav-marker')[0]; - const marker = $('.nav-marker'); + marker.style.left = `${leftPosition}px`; - marker.css({ - 'left': leftPosition - }); if (type === 'close') { - marker.one('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend', () => { + oneTimeTransitionEvent(marker, () => { this.set('navIsVisible', false); this.toggle3dBlock(); }); diff --git a/addon/utils.js b/addon/utils.js new file mode 100644 index 0000000..f9f7381 --- /dev/null +++ b/addon/utils.js @@ -0,0 +1,38 @@ +function whichTransitionEvent() { + let t; + const el = document.createElement('fakeelement'); + + const transitions = { + 'transition': 'transitionend', + 'OTransition': 'oTransitionEnd', + 'MozTransition': 'transitionend', + 'WebkitTransition': 'webkitTransitionEnd' + }; + + for (t in transitions) { + if (el.style[t] !== undefined) { + return transitions[t]; + } + } +} + +const getScrollTop = function() { + return (window.pageYOffset !== undefined) ? + window.pageYOffset : + (document.documentElement || document.body.parentNode || document.body).scrollTop; +}; + +const oneTimeTransitionEvent = function(element, callback) { + const transitionEvent = whichTransitionEvent(); + const customFunction = (e) => { + e.target.removeEventListener(transitionEvent, customFunction); + return callback(e); + }; + + element.addEventListener(transitionEvent, customFunction); +}; + +export { + getScrollTop, + oneTimeTransitionEvent +}; diff --git a/ember-cli-build.js b/ember-cli-build.js index 12276d5..74f1592 100644 --- a/ember-cli-build.js +++ b/ember-cli-build.js @@ -7,8 +7,8 @@ const shim = require('@html-next/flexi-layouts/lib/pod-templates-shim'); shim(EmberAddon); module.exports = function(defaults) { - const app = new EmberAddon(defaults, { - // Add options here + let app = new EmberAddon(defaults, { + vendorFiles: { 'jquery.js': null } }); /* diff --git a/package.json b/package.json index 5ae6f02..541d2b6 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,6 @@ "ember-cli-htmlbars": "^2.0.1", "ember-cli-sass": "^7.0.0", "ember-headroom": "^1.1.1", - "ember-responds-to": "1.4.0", "ember-truth-helpers": "1.3.0", "flexi": "^2.0.1" }, @@ -56,6 +55,9 @@ "ember-disable-prototype-extensions": "^1.1.2", "ember-export-application-global": "^2.0.0", "ember-load-initializers": "^1.0.0", + "ember-maybe-import-regenerator": "^0.1.6", + "ember-native-dom-event-dispatcher": "^0.6.3", + "ember-native-dom-helpers": "^0.5.3", "ember-resolver": "^4.0.0", "ember-source": "~2.15.0-beta.1", "eslint-plugin-ember": "^4.0.0", diff --git a/tests/acceptance/ember-3d-nav-test.js b/tests/acceptance/ember-3d-nav-test.js index a3650b5..ce45c53 100644 --- a/tests/acceptance/ember-3d-nav-test.js +++ b/tests/acceptance/ember-3d-nav-test.js @@ -1,58 +1,68 @@ -import Ember from 'ember'; +import { click, find, findAll, scrollTo, visit } from 'ember-native-dom-helpers'; +import { getScrollTop } from 'ember-3d-nav/utils'; import { test } from 'qunit'; import moduleForAcceptance from '../helpers/module-for-acceptance'; -const { $, run } = Ember; +import { run } from '@ember/runloop'; moduleForAcceptance('Nav menu behavior'); -test('clicking menu button opens nav', function(assert) { +test('clicking menu button opens nav', async function(assert) { assert.expect(2); - visit('/'); - andThen(function() { - assert.equal(find('.ember-3d-nav-container').hasClass('nav-is-visible'), false, 'nav-is-visible class not applied initially'); - }); - click('.nav-trigger'); - andThen(function() { - assert.equal(find('.ember-3d-nav-container').hasClass('nav-is-visible'), true, 'nav-is-visible class applied after clicking menu button'); - }); + await visit('/'); + + assert.equal(find('.ember-3d-nav-container').classList.contains('nav-is-visible'), false, + 'nav-is-visible class not applied initially'); + + await click(find('.nav-trigger')); + + assert.equal(find('.ember-3d-nav-container').classList.contains('nav-is-visible'), true, + 'nav-is-visible class applied after clicking menu button'); }); -test('clicking an option selects it and closes the menu', function(assert) { +test('clicking an option selects it and closes the menu', async function(assert) { assert.expect(4); - visit('/'); - andThen(function() { - assert.equal(find('.ember-3d-nav-container').hasClass('nav-is-visible'), false, 'nav-is-visible class not applied initially'); - }); - click('.nav-trigger'); - andThen(function() { - assert.equal(find('.ember-3d-nav-container').hasClass('nav-is-visible'), true, 'nav-is-visible class applied after clicking menu button'); - }); - run.scheduleOnce('afterRender', this, function() { - click('centered:nth-of-type(2)'); - andThen(function() { - assert.equal(find('centered:nth-of-type(2)').hasClass('is-selected'), true, 'nav item is selected'); - assert.equal(find('.ember-3d-nav-container').hasClass('nav-is-visible'), false, 'nav-is-visible class removed after clicking nav item'); - }); - }); + const done = assert.async(); + + await visit('/'); + + assert.equal(find('.ember-3d-nav-container').classList.contains('nav-is-visible'), false, + 'nav-is-visible class not applied initially'); + + await click(find('.nav-trigger')); + + assert.equal(find('.ember-3d-nav-container').classList.contains('nav-is-visible'), true, + 'nav-is-visible class applied after clicking menu button'); + + await click(findAll('centered')[2]); + + assert.equal(findAll('centered')[2].classList.contains('is-selected'), true, + 'nav item is selected'); + + run.later(function() { + assert.equal(find('.ember-3d-nav-container').classList.contains('nav-is-visible'), false, + 'nav-is-visible class removed after clicking nav item'); + done(); + }, 1500); + }); -test('scrolling applies isFixedAndScrolled', function(assert) { +test('scrolling applies isFixedAndScrolled', async function(assert) { assert.expect(5); - visit('/'); - andThen(function() { - assert.equal($(window).scrollTop(), 0, 'window scroll is 0'); - $(window).scrollTop(50); - assert.equal($(window).scrollTop(), 50, 'window scroll is 50'); - run.later(this, function() { - assert.equal(find('.nav-trigger-container').hasClass('is-fixed-and-scrolled'), true, 'is-fixed-and-scrolled applied'); - $(window).scrollTop(0); - assert.equal($(window).scrollTop(), 0, 'window scroll is 0'); - run.later(this, function() { - assert.equal(find('.nav-trigger-container').hasClass('is-fixed-and-scrolled'), false, 'is-fixed-and-scrolled removed'); - }, 100); - }, 100); - }); + await visit('/'); + assert.equal(getScrollTop(), 0, 'window scroll is 0'); + + await scrollTo(document.body, 0, 50); + assert.equal(getScrollTop(), 50, 'window scroll is 50'); + + assert.equal(find('.nav-trigger-container').classList.contains('is-fixed-and-scrolled'), true, + 'is-fixed-and-scrolled applied'); + + await scrollTo(document.body, 0, 0); + assert.equal(getScrollTop(), 0, 'window scroll is 0'); + + assert.equal(find('.nav-trigger-container').classList.contains('is-fixed-and-scrolled'), false, + 'is-fixed-and-scrolled removed'); }); diff --git a/tests/unit/mixins/debounced-response-test.js b/tests/unit/mixins/debounced-response-test.js new file mode 100644 index 0000000..8ba668f --- /dev/null +++ b/tests/unit/mixins/debounced-response-test.js @@ -0,0 +1,12 @@ +import Ember from 'ember'; +import DebouncedResponseMixin from 'ember-3d-nav/mixins/debounced-response'; +import { module, test } from 'qunit'; + +module('Unit | Mixin | debounced response'); + +// Replace this with your real tests. +test('it works', function(assert) { + let DebouncedResponseObject = Ember.Object.extend(DebouncedResponseMixin); + let subject = DebouncedResponseObject.create(); + assert.ok(subject); +}); diff --git a/tests/unit/mixins/responds-to-scroll-test.js b/tests/unit/mixins/responds-to-scroll-test.js new file mode 100644 index 0000000..9b810f5 --- /dev/null +++ b/tests/unit/mixins/responds-to-scroll-test.js @@ -0,0 +1,12 @@ +import Ember from 'ember'; +import RespondsToScrollMixin from 'ember-3d-nav/mixins/responds-to-scroll'; +import { module, test } from 'qunit'; + +module('Unit | Mixin | responds to scroll'); + +// Replace this with your real tests. +test('it works', function(assert) { + let RespondsToScrollObject = Ember.Object.extend(RespondsToScrollMixin); + let subject = RespondsToScrollObject.create(); + assert.ok(subject); +}); diff --git a/yarn.lock b/yarn.lock index ea9ad25..36dbbbe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -202,6 +202,12 @@ amd-name-resolver@0.0.6: dependencies: ensure-posix-path "^1.0.1" +amd-name-resolver@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/amd-name-resolver/-/amd-name-resolver-0.0.7.tgz#814301adfe8a2f109f6e84d5e935196efb669615" + dependencies: + ensure-posix-path "^1.0.1" + amdefine@>=0.0.4: version "1.0.1" resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" @@ -647,6 +653,12 @@ babel-plugin-ember-modules-api-polyfill@^1.4.1: dependencies: ember-rfc176-data "^0.2.0" +babel-plugin-ember-modules-api-polyfill@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/babel-plugin-ember-modules-api-polyfill/-/babel-plugin-ember-modules-api-polyfill-1.5.1.tgz#244b4a716a2ee4c839554c77102145d9bee4a4a4" + dependencies: + ember-rfc176-data "^0.2.0" + babel-plugin-eval@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/babel-plugin-eval/-/babel-plugin-eval-1.0.1.tgz#a2faed25ce6be69ade4bfec263f70169195950da" @@ -1196,6 +1208,21 @@ broccoli-babel-transpiler@^6.0.0: rsvp "^3.5.0" workerpool "^2.2.1" +broccoli-babel-transpiler@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/broccoli-babel-transpiler/-/broccoli-babel-transpiler-6.1.2.tgz#26019c045b5ea3e44cfef62821302f9bd483cabd" + dependencies: + babel-core "^6.14.0" + broccoli-funnel "^1.0.0" + broccoli-merge-trees "^1.0.0" + broccoli-persistent-filter "^1.4.0" + clone "^2.0.0" + hash-for-dep "^1.0.2" + heimdalljs-logger "^0.1.7" + json-stable-stringify "^1.0.0" + rsvp "^3.5.0" + workerpool "^2.2.1" + broccoli-brocfile-loader@^0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/broccoli-brocfile-loader/-/broccoli-brocfile-loader-0.18.0.tgz#2e86021c805c34ffc8d29a2fb721cf273e819e4b" @@ -2138,6 +2165,23 @@ ember-cli-babel@^6.0.0, ember-cli-babel@^6.0.0-beta.7, ember-cli-babel@^6.3.0, e clone "^2.0.0" ember-cli-version-checker "^2.0.0" +ember-cli-babel@^6.0.0-beta.4, ember-cli-babel@^6.4.1: + version "6.8.1" + resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-6.8.1.tgz#695f94c57a9375c2a0e219306a41105d6b937991" + dependencies: + amd-name-resolver "0.0.7" + babel-plugin-debug-macros "^0.1.11" + babel-plugin-ember-modules-api-polyfill "^1.5.1" + babel-plugin-transform-es2015-modules-amd "^6.24.0" + babel-polyfill "^6.16.0" + babel-preset-env "^1.5.1" + broccoli-babel-transpiler "^6.1.2" + broccoli-debug "^0.6.2" + broccoli-funnel "^1.0.0" + broccoli-source "^1.1.0" + clone "^2.0.0" + ember-cli-version-checker "^2.0.0" + ember-cli-broccoli-sane-watcher@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/ember-cli-broccoli-sane-watcher/-/ember-cli-broccoli-sane-watcher-2.0.4.tgz#f43f42f75b7509c212fb926cd9aea86ae19264c6" @@ -2483,6 +2527,28 @@ ember-load-initializers@^1.0.0: dependencies: ember-cli-babel "^6.0.0-beta.7" +ember-maybe-import-regenerator@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/ember-maybe-import-regenerator/-/ember-maybe-import-regenerator-0.1.6.tgz#35d41828afa6d6a59bc0da3ce47f34c573d776ca" + dependencies: + broccoli-funnel "^1.0.1" + broccoli-merge-trees "^1.0.0" + ember-cli-babel "^6.0.0-beta.4" + regenerator-runtime "^0.9.5" + +ember-native-dom-event-dispatcher@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/ember-native-dom-event-dispatcher/-/ember-native-dom-event-dispatcher-0.6.3.tgz#3b2f10dcf82f9aaa4dd211a704ac511fd8714720" + dependencies: + ember-cli-babel "^6.4.1" + +ember-native-dom-helpers@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/ember-native-dom-helpers/-/ember-native-dom-helpers-0.5.3.tgz#276f2172b3018dc3f98202bd0a52b2b43bb0f0e7" + dependencies: + broccoli-funnel "^1.1.0" + ember-cli-babel "^6.6.0" + ember-qunit@^2.1.3: version "2.2.0" resolved "https://registry.yarnpkg.com/ember-qunit/-/ember-qunit-2.2.0.tgz#3cdf400031c93a38de781a7304819738753b7f99" @@ -2501,12 +2567,6 @@ ember-resolver@^4.0.0: ember-cli-version-checker "1.3.1" resolve "^1.3.3" -ember-responds-to@1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/ember-responds-to/-/ember-responds-to-1.4.0.tgz#1be1b5e3339f996cf3ce24a7ab263af0d04abe20" - dependencies: - ember-cli-babel "^5.1.6" - ember-rfc176-data@^0.2.0, ember-rfc176-data@^0.2.2: version "0.2.6" resolved "https://registry.yarnpkg.com/ember-rfc176-data/-/ember-rfc176-data-0.2.6.tgz#ff9981d117a885a7344813685a9f3bf8257d1016" @@ -5041,6 +5101,10 @@ regenerator-runtime@^0.10.0: version "0.10.5" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658" +regenerator-runtime@^0.9.5: + version "0.9.6" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.9.6.tgz#d33eb95d0d2001a4be39659707c51b0cb71ce029" + regenerator-transform@0.9.11: version "0.9.11" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.9.11.tgz#3a7d067520cb7b7176769eb5ff868691befe1283"