From 1492be0dbad2b3b2d9f778d36d85934c5e668070 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 19:25:17 -0300 Subject: [PATCH 01/41] chore(b-img-lazy): switch IntersectionObserver to use private `v-b-visible` directive --- src/components/image/img-lazy.js | 88 ++++++++++++++++++++------------ 1 file changed, 55 insertions(+), 33 deletions(-) diff --git a/src/components/image/img-lazy.js b/src/components/image/img-lazy.js index d458d5ce855..8820b5a3038 100644 --- a/src/components/image/img-lazy.js +++ b/src/components/image/img-lazy.js @@ -3,9 +3,11 @@ import { BImg } from './img' import { getComponentConfig } from '../../utils/config' import { getBCR, eventOn, eventOff } from '../../utils/dom' import { hasIntersectionObserverSupport } from '../../utils/env' +import { VBVisible } from '../../directives/visible' const NAME = 'BImgLazy' +// TODO: if we assume user has IntersectionObserver, then these can be removed const THROTTLE = 100 const EVENT_OPTIONS = { passive: true, capture: false } @@ -84,6 +86,7 @@ export const props = { type: [Number, String], default: 360 }, + // TODO: if we assume user has IntersectionObserver, then this can be removed throttle: { type: [Number, String], default: THROTTLE @@ -93,12 +96,15 @@ export const props = { // @vue/component export const BImgLazy = /*#__PURE__*/ Vue.extend({ name: NAME, + directives: { + bVisible: VBVisible + }, props, data() { return { isShown: false, - scrollTimeout: null, - observer: null + // TODO: if we assume user has IntersectionObserver, then this can be removed + scrollTimeout: null } }, computed: { @@ -119,6 +125,7 @@ export const BImgLazy = /*#__PURE__*/ Vue.extend({ show(newVal, oldVal) { if (newVal !== oldVal) { this.isShown = newVal + // TODO: if we assume user has IntersectionObserver, then this can be removed if (!newVal) { // Make sure listeners are re-enabled if img is force set to blank this.setListeners(true) @@ -136,6 +143,7 @@ export const BImgLazy = /*#__PURE__*/ Vue.extend({ this.isShown = this.show }, mounted() { + // TODO: if we assume user has IntersectionObserver, then this can be removed if (this.isShown) { this.setListeners(false) } else { @@ -143,56 +151,50 @@ export const BImgLazy = /*#__PURE__*/ Vue.extend({ } }, activated() /* istanbul ignore next */ { + // TODO: if we assume user has IntersectionObserver, then this can be removed if (!this.isShown) { this.setListeners(true) } }, deactivated() /* istanbul ignore next */ { + // TODO: if we assume user has IntersectionObserver, then this can be removed this.setListeners(false) }, beforeDestroy() { + // TODO: if we assume user has IntersectionObserver, then this can be removed this.setListeners(false) }, methods: { + // TODO: if we assume user has IntersectionObserver, then this can be removed setListeners(on) { - if (this.scrollTimeout) { - clearTimeout(this.scrollTimeout) - this.scrollTimeout = null - } - /* istanbul ignore next: JSDOM doen't support IntersectionObserver */ - if (this.observer) { - this.observer.unobserve(this.$el) - this.observer.disconnect() - this.observer = null - } - const winEvts = ['scroll', 'resize', 'orientationchange'] - winEvts.forEach(evt => eventOff(window, evt, this.onScroll, EVENT_OPTIONS)) - eventOff(this.$el, 'load', this.checkView, EVENT_OPTIONS) - eventOff(document, 'transitionend', this.onScroll, EVENT_OPTIONS) - if (on) { - /* istanbul ignore if: JSDOM doen't support IntersectionObserver */ - if (hasIntersectionObserverSupport) { - this.observer = new IntersectionObserver(this.doShow, { - root: null, // viewport - rootMargin: `${parseInt(this.offset, 10) || 0}px`, - threshold: 0 // percent intersection - }) - this.observer.observe(this.$el) - } else { - // Fallback to scroll/etc events - winEvts.forEach(evt => eventOn(window, evt, this.onScroll, EVENT_OPTIONS)) - eventOn(this.$el, 'load', this.checkView, EVENT_OPTIONS) - eventOn(document, 'transitionend', this.onScroll, EVENT_OPTIONS) + if (!hasIntersectionObserverSupport) { + // We only instantiate these events if the client + // doesn't have `InteresctionObserver` support + if (this.scrollTimeout) { + clearTimeout(this.scrollTimeout) + this.scrollTimeout = null + } + const events = on => { + const winEvts = ['scroll', 'resize', 'orientationchange'] + const method = on ? eventOn : eventOff + winEvts.forEach(evt => method(window, evt, this.onScroll, EVENT_OPTIONS)) + method(this.$el, 'load', this.checkView, EVENT_OPTIONS) + method(document, 'transitionend', this.onScroll, EVENT_OPTIONS) + } + events(false) + if (on) { + events(true) } } }, - doShow(entries) { - if (entries && (entries[0].isIntersecting || entries[0].intersectionRatio > 0.0)) { + doShow(isVisble) { + if (isVisible && !this.isShown) { this.isShown = true this.setListeners(false) } }, checkView() { + // TODO: if we assume user has IntersectionObserver, then this can be removed // check bounding box + offset to see if we should show /* istanbul ignore next: should rarely occur */ if (this.isShown) { @@ -211,10 +213,11 @@ export const BImgLazy = /*#__PURE__*/ Vue.extend({ const box = getBCR(this.$el) if (box.right >= view.l && box.bottom >= view.t && box.left <= view.r && box.top <= view.b) { // image is in view (or about to be in view) - this.doShow([{ isIntersecting: true }]) + this.doShow(true) } }, onScroll() { + // TODO: if we assume user has IntersectionObserver, then this can be removed /* istanbul ignore if: should rarely occur */ if (this.isShown) { this.setListeners(false) @@ -225,7 +228,26 @@ export const BImgLazy = /*#__PURE__*/ Vue.extend({ } }, render(h) { + const directives = [] + if (!this.isShown) { + // We only add the visible directive if we are not shown + directives.push({ + // Visible directive will silently do nothing if + // `IntersectionObserver` is not supported + name: 'b-visible', + // Value expects a callback (passed on arg of visible = true/false) + value: this.doShow, + modifiers: { + // Root margin from viewport + [`${parseInt(this.offset, 10) || 0}`]: true, + // Once the image is shown, stop observing + once: true + } + }) + } + return h(BImg, { + directives, props: { // Computed value props src: this.computedSrc, From 5816d439b5f29db558b557b333c5d634ad32b024 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 19:30:31 -0300 Subject: [PATCH 02/41] Update img-lazy.js --- src/components/image/img-lazy.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/image/img-lazy.js b/src/components/image/img-lazy.js index 8820b5a3038..65ef79f8b94 100644 --- a/src/components/image/img-lazy.js +++ b/src/components/image/img-lazy.js @@ -187,9 +187,10 @@ export const BImgLazy = /*#__PURE__*/ Vue.extend({ } } }, - doShow(isVisble) { - if (isVisible && !this.isShown) { + doShow(visible) { + if (visible && !this.isShown) { this.isShown = true + // TODO: if we assume user has IntersectionObserver, then this can be removed this.setListeners(false) } }, From 439d2df455343229bcffa80f08c2933710644acb Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 20:15:52 -0300 Subject: [PATCH 03/41] remove scroll event fallback --- src/components/image/img-lazy.js | 104 +++++-------------------------- 1 file changed, 16 insertions(+), 88 deletions(-) diff --git a/src/components/image/img-lazy.js b/src/components/image/img-lazy.js index 4eca2613142..aa78e41cd17 100644 --- a/src/components/image/img-lazy.js +++ b/src/components/image/img-lazy.js @@ -83,13 +83,10 @@ export const props = { default: false }, offset: { + // distance away from viewport 9jn pixels) before being + // considered "visible" type: [Number, String], default: 360 - }, - // TODO: if we assume user has IntersectionObserver, then this can be removed - throttle: { - type: [Number, String], - default: THROTTLE } } @@ -102,9 +99,7 @@ export const BImgLazy = /*#__PURE__*/ Vue.extend({ props, data() { return { - isShown: false, - // TODO: if we assume user has IntersectionObserver, then this can be removed - scrollTimeout: null + isShown: false } }, computed: { @@ -124,68 +119,34 @@ export const BImgLazy = /*#__PURE__*/ Vue.extend({ watch: { show(newVal, oldVal) { if (newVal !== oldVal) { - this.isShown = newVal - // TODO: if we assume user has IntersectionObserver, then this can be removed - if (!newVal) { - // Make sure listeners are re-enabled if img is force set to blank - this.setListeners(true) + // If IntersectionObserver support is not available, image is always shown + const visible = hasIntersectionObserverSupport ? newVal : true + this.isShown = visible + if (visible !== newVal) { + // Ensure the show prop is synced (when no IntersectionObserver) + this.$nextTick(this.updateShowProp) } } }, isShown(newVal, oldVal) { if (newVal !== oldVal) { // Update synched show prop - this.$emit('update:show', newVal) + this.updateShowProp() } } }, created() { this.isShown = this.show }, - mounted() { - // TODO: if we assume user has IntersectionObserver, then this can be removed - if (this.isShown) { - this.setListeners(false) - } else { - this.setListeners(true) - } - }, - activated() /* istanbul ignore next */ { - // TODO: if we assume user has IntersectionObserver, then this can be removed - if (!this.isShown) { - this.setListeners(true) + beforeMount() { + if (!hasIntersectionObserverSupport) { + // If IntersectionObserver support is not available, image is always shown + this.isShown = true } }, - deactivated() /* istanbul ignore next */ { - // TODO: if we assume user has IntersectionObserver, then this can be removed - this.setListeners(false) - }, - beforeDestroy() { - // TODO: if we assume user has IntersectionObserver, then this can be removed - this.setListeners(false) - }, methods: { - // TODO: if we assume user has IntersectionObserver, then this can be removed - setListeners(on) { - if (!hasIntersectionObserverSupport) { - // We only instantiate these events if the client - // doesn't have `InteresctionObserver` support - if (this.scrollTimeout) { - clearTimeout(this.scrollTimeout) - this.scrollTimeout = null - } - const events = on => { - const winEvts = ['scroll', 'resize', 'orientationchange'] - const method = on ? eventOn : eventOff - winEvts.forEach(evt => method(window, evt, this.onScroll, EVENT_OPTIONS)) - method(this.$el, 'load', this.checkView, EVENT_OPTIONS) - method(document, 'transitionend', this.onScroll, EVENT_OPTIONS) - } - events(false) - if (on) { - events(true) - } - } + updateShowProp() { + this.$emit('update:show', this.isShown) }, doShow(visible) { if (visible && !this.isShown) { @@ -193,39 +154,6 @@ export const BImgLazy = /*#__PURE__*/ Vue.extend({ // TODO: if we assume user has IntersectionObserver, then this can be removed this.setListeners(false) } - }, - checkView() { - // TODO: if we assume user has IntersectionObserver, then this can be removed - // check bounding box + offset to see if we should show - /* istanbul ignore next: should rarely occur */ - if (this.isShown) { - this.setListeners(false) - return - } - const offset = parseInt(this.offset, 10) || 0 - const docElement = document.documentElement - const view = { - l: 0 - offset, - t: 0 - offset, - b: docElement.clientHeight + offset, - r: docElement.clientWidth + offset - } - // JSDOM Doesn't support BCR, but we fake it in the tests - const box = getBCR(this.$el) - if (box.right >= view.l && box.bottom >= view.t && box.left <= view.r && box.top <= view.b) { - // image is in view (or about to be in view) - this.doShow(true) - } - }, - onScroll() { - // TODO: if we assume user has IntersectionObserver, then this can be removed - /* istanbul ignore if: should rarely occur */ - if (this.isShown) { - this.setListeners(false) - } else { - clearTimeout(this.scrollTimeout) - this.scrollTimeout = setTimeout(this.checkView, parseInt(this.throttle, 10) || THROTTLE) - } } }, render(h) { From c1b1bae0bc73edca56d9c850897f710849e30cef Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 20:20:09 -0300 Subject: [PATCH 04/41] Update img-lazy.js --- src/components/image/img-lazy.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/components/image/img-lazy.js b/src/components/image/img-lazy.js index aa78e41cd17..b4f11d7405b 100644 --- a/src/components/image/img-lazy.js +++ b/src/components/image/img-lazy.js @@ -1,16 +1,11 @@ import Vue from '../../utils/vue' import { BImg } from './img' import { getComponentConfig } from '../../utils/config' -import { getBCR, eventOn, eventOff } from '../../utils/dom' import { hasIntersectionObserverSupport } from '../../utils/env' import { VBVisible } from '../../directives/visible' const NAME = 'BImgLazy' -// TODO: if we assume user has IntersectionObserver, then these can be removed -const THROTTLE = 100 -const EVENT_OPTIONS = { passive: true, capture: false } - export const props = { src: { type: String, From 3d74dac07bb8b67cfcbf50226670fde6c728a6a1 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 20:40:57 -0300 Subject: [PATCH 05/41] Update visible.js --- src/directives/visible.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/directives/visible.js b/src/directives/visible.js index 8bc6e355a4d..132ea838f1d 100644 --- a/src/directives/visible.js +++ b/src/directives/visible.js @@ -83,8 +83,8 @@ class VisibilityObserver { }) } catch { // No IntersectionObserver support, so just stop trying to observe - this.donOnce = true - this.observer = null + this.doneOnce = true + this.observer = undefined this.callback(null) return } From cc89a14c28f3d34d38ee6038ef5dfcc7cca53a45 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 20:44:57 -0300 Subject: [PATCH 06/41] Update img-lazy.js --- src/components/image/img-lazy.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/image/img-lazy.js b/src/components/image/img-lazy.js index b4f11d7405b..ffb1ac78bb9 100644 --- a/src/components/image/img-lazy.js +++ b/src/components/image/img-lazy.js @@ -135,7 +135,7 @@ export const BImgLazy = /*#__PURE__*/ Vue.extend({ }, beforeMount() { if (!hasIntersectionObserverSupport) { - // If IntersectionObserver support is not available, image is always shown + // If IntersectionObserver is not available, image is always shown this.isShown = true } }, @@ -144,10 +144,10 @@ export const BImgLazy = /*#__PURE__*/ Vue.extend({ this.$emit('update:show', this.isShown) }, doShow(visible) { - if (visible && !this.isShown) { + // If 'IntersectionObserver` is not supported, the callback + // will be called with `null` rather than `true` or `false` + if ((visible || visible === null) && !this.isShown) { this.isShown = true - // TODO: if we assume user has IntersectionObserver, then this can be removed - this.setListeners(false) } } }, From 0167f1db28280e60ffc79284266c21eb5c5c0eb7 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 20:48:38 -0300 Subject: [PATCH 07/41] Update img-lazy.spec.js --- src/components/image/img-lazy.spec.js | 121 +++++++++----------------- 1 file changed, 40 insertions(+), 81 deletions(-) diff --git a/src/components/image/img-lazy.spec.js b/src/components/image/img-lazy.spec.js index ed5cfea2189..d911d2535f4 100644 --- a/src/components/image/img-lazy.spec.js +++ b/src/components/image/img-lazy.spec.js @@ -5,8 +5,34 @@ import { BImgLazy } from './img-lazy' const src = 'https://picsum.photos/1024/400/?image=41' describe('img-lazy', () => { + beforeEach(() => { + // IntersectionObserver not supported by JSDOM + // So we mock up just the basics + global.IntersectionObserver = class IntersectionObserver { + constructor(handler, opts) { + this.handler = handler + } + observe() { + return null + } + unobserve() { + return null + } + disconnect() { + return null + } + } + }) + + afterEach(() => { + try { + delete global.IntersectionObserver + } catch {} + }) + it('has root element "img"', async () => { const wrapper = mount(BImgLazy, { + attachToDocument: true, propsData: { src: src } @@ -18,6 +44,7 @@ describe('img-lazy', () => { it('is initially shown show prop is set', async () => { const wrapper = mount(BImgLazy, { + attachToDocument: true, propsData: { src: src, show: true @@ -33,6 +60,7 @@ describe('img-lazy', () => { it('shows when show prop is set', async () => { const wrapper = mount(BImgLazy, { + attachToDocument: true, propsData: { src: src, show: false @@ -43,99 +71,30 @@ describe('img-lazy', () => { expect(wrapper.attributes('src')).toBeDefined() expect(wrapper.attributes('src')).toContain('data:image/svg+xml;charset=UTF-8') + // Our directive instance with test "fake" observer + let observer = wrapper.element.__bv__visibility_observer + expect(observer).toBeDefined() + wrapper.setProps({ show: true }) await waitNT(wrapper.vm) expect(wrapper.attributes('src')).toBe(src) + // Our directive instance should be gone + observer = wrapper.element.__bv__visibility_observer + expect(observer).not.toBeDefined() + wrapper.setProps({ show: false }) await waitNT(wrapper.vm) expect(wrapper.attributes('src')).toContain('data:image/svg+xml;charset=UTF-8') - wrapper.destroy() - }) - - // These tests are wrapped in a new describe to limit the scope of the getBCR Mock - describe('scroll events', () => { - const origGetBCR = Element.prototype.getBoundingClientRect - - jest.useFakeTimers() - - afterEach(() => { - // Restore prototype - Element.prototype.getBoundingClientRect = origGetBCR - }) - - it('triggers check on resize event event', async () => { - const src = 'https://picsum.photos/1024/400/?image=41' - - // Fake getBCR initially "off screen" - Element.prototype.getBoundingClientRect = jest.fn(() => ({ - width: 24, - height: 24, - top: 10000, - left: 10000, - bottom: -10000, - right: -10000 - })) + // Our directive instance should be back + observer = wrapper.element.__bv__visibility_observer + expect(observer).toBeDefined() - const wrapper = mount(BImgLazy, { - attachToDocument: true, - propsData: { - src: src, - offset: 500 - } - }) - expect(wrapper.is('img')).toBe(true) - - expect(wrapper.attributes('src')).toBeDefined() - expect(wrapper.attributes('src')).toContain('data:image/svg+xml;charset=UTF-8') - - expect(wrapper.vm.scrollTimeout).toBe(null) - - // Fake getBCR "in view" - Element.prototype.getBoundingClientRect = jest.fn(() => ({ - width: 24, - height: 24, - top: 0, - left: 0, - bottom: 0, - right: 0 - })) - - window.dispatchEvent(new UIEvent('resize')) - - await wrapper.vm.$nextTick() - await wrapper.vm.$nextTick() - - expect(wrapper.vm.scrollTimeout).not.toBe(null) - - // Since JSDOM doesnt support getBCR, we fake it by setting - // the data prop to shown - // wrapper.setData({ - // isShown: true - // }) - - // Advance the setTimeout - jest.runOnlyPendingTimers() - - await wrapper.vm.$nextTick() - await wrapper.vm.$nextTick() - - expect(wrapper.vm.scrollTimeout).toBe(null) - - expect(wrapper.attributes('src')).toContain(src) - - window.dispatchEvent(new UIEvent('resize')) - - expect(wrapper.vm.scrollTimeout).toBe(null) - - wrapper.destroy() - - Element.prototype.getBoundingClientRect = origGetBCR - }) + wrapper.destroy() }) }) From c901cfc4b4d9f15e6521fe3edf361ca0ca99fb01 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 20:51:25 -0300 Subject: [PATCH 08/41] Update img-lazy.spec.js --- src/components/image/img-lazy.spec.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/image/img-lazy.spec.js b/src/components/image/img-lazy.spec.js index d911d2535f4..b790a00c5f5 100644 --- a/src/components/image/img-lazy.spec.js +++ b/src/components/image/img-lazy.spec.js @@ -10,14 +10,19 @@ describe('img-lazy', () => { // So we mock up just the basics global.IntersectionObserver = class IntersectionObserver { constructor(handler, opts) { + // We store a copy of handler so we can call it + // during tests this.handler = handler } + observe() { return null } + unobserve() { return null } + disconnect() { return null } @@ -29,7 +34,7 @@ describe('img-lazy', () => { delete global.IntersectionObserver } catch {} }) - + it('has root element "img"', async () => { const wrapper = mount(BImgLazy, { attachToDocument: true, From 48d5236ce1ea29eba45b83ce0a63dc61c0ee5238 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 20:53:01 -0300 Subject: [PATCH 09/41] Update img-lazy.spec.js --- src/components/image/img-lazy.spec.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/image/img-lazy.spec.js b/src/components/image/img-lazy.spec.js index b790a00c5f5..ada46c01477 100644 --- a/src/components/image/img-lazy.spec.js +++ b/src/components/image/img-lazy.spec.js @@ -5,10 +5,10 @@ import { BImgLazy } from './img-lazy' const src = 'https://picsum.photos/1024/400/?image=41' describe('img-lazy', () => { - beforeEach(() => { + beforeAll(() => { // IntersectionObserver not supported by JSDOM // So we mock up just the basics - global.IntersectionObserver = class IntersectionObserver { + window.IntersectionObserver = class IntersectionObserver { constructor(handler, opts) { // We store a copy of handler so we can call it // during tests @@ -29,9 +29,9 @@ describe('img-lazy', () => { } }) - afterEach(() => { + afterAll(() => { try { - delete global.IntersectionObserver + window global.IntersectionObserver } catch {} }) From dc234aaeab5b176b7a5c3256b4d33e0cb5aab51f Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 20:54:55 -0300 Subject: [PATCH 10/41] Update img-lazy.spec.js --- src/components/image/img-lazy.spec.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/image/img-lazy.spec.js b/src/components/image/img-lazy.spec.js index ada46c01477..5f21fa6f1ae 100644 --- a/src/components/image/img-lazy.spec.js +++ b/src/components/image/img-lazy.spec.js @@ -31,7 +31,8 @@ describe('img-lazy', () => { afterAll(() => { try { - window global.IntersectionObserver + window.IntersectionObserver = undefined + delete window.IntersectionObserver } catch {} }) From 16352892f5f4c3945cee6e7c760cfca28963eebb Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 21:08:54 -0300 Subject: [PATCH 11/41] Update img-lazy.spec.js --- src/components/image/img-lazy.spec.js | 33 ++++++++++++++++++--------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/components/image/img-lazy.spec.js b/src/components/image/img-lazy.spec.js index 5f21fa6f1ae..b97149b0405 100644 --- a/src/components/image/img-lazy.spec.js +++ b/src/components/image/img-lazy.spec.js @@ -1,18 +1,22 @@ import { mount } from '@vue/test-utils' -import { waitNT } from '../../../tests/utils' +import { waitNT, waitRAF } from '../../../tests/utils' import { BImgLazy } from './img-lazy' const src = 'https://picsum.photos/1024/400/?image=41' describe('img-lazy', () => { + const windowIntersectionObserver = window.IntersectionObserver + // Mock callback netry + // const mockEntry = { isIntersecting: true, intersectionRatio: 1 } + beforeAll(() => { // IntersectionObserver not supported by JSDOM // So we mock up just the basics window.IntersectionObserver = class IntersectionObserver { - constructor(handler, opts) { - // We store a copy of handler so we can call it - // during tests - this.handler = handler + constructor(calback, opts) { + // We store a copy of handler so + // we can call it during tests + this.callback = calback } observe() { @@ -30,10 +34,7 @@ describe('img-lazy', () => { }) afterAll(() => { - try { - window.IntersectionObserver = undefined - delete window.IntersectionObserver - } catch {} + window.IntersectionObserver = windowIntersectionObserver }) it('has root element "img"', async () => { @@ -72,19 +73,27 @@ describe('img-lazy', () => { show: false } }) + expect(wrapper.is('img')).toBe(true) - expect(wrapper.attributes('src')).toBeDefined() - expect(wrapper.attributes('src')).toContain('data:image/svg+xml;charset=UTF-8') + await waitNT(wrapper.vm) + await waitRAF() + await waitNT(wrapper.vm) // Our directive instance with test "fake" observer let observer = wrapper.element.__bv__visibility_observer expect(observer).toBeDefined() + expect(wrapper.attributes('src')).toBeDefined() + expect(wrapper.attributes('src')).toContain('data:image/svg+xml;charset=UTF-8') + wrapper.setProps({ show: true }) await waitNT(wrapper.vm) + await waitRAF() + await waitNT(wrapper.vm) + expect(wrapper.attributes('src')).toBe(src) // Our directive instance should be gone @@ -95,6 +104,8 @@ describe('img-lazy', () => { show: false }) await waitNT(wrapper.vm) + await waitRAF() + await waitNT(wrapper.vm) expect(wrapper.attributes('src')).toContain('data:image/svg+xml;charset=UTF-8') // Our directive instance should be back From 644051be31db5129de4d03fe8504f261ffbde376 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 21:14:49 -0300 Subject: [PATCH 12/41] Update img-lazy.spec.js --- src/components/image/img-lazy.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/image/img-lazy.spec.js b/src/components/image/img-lazy.spec.js index b97149b0405..a811f4284d0 100644 --- a/src/components/image/img-lazy.spec.js +++ b/src/components/image/img-lazy.spec.js @@ -12,7 +12,7 @@ describe('img-lazy', () => { beforeAll(() => { // IntersectionObserver not supported by JSDOM // So we mock up just the basics - window.IntersectionObserver = class IntersectionObserver { + window.IntersectionObserver = class mockIntersectionObserver { constructor(calback, opts) { // We store a copy of handler so // we can call it during tests From 77a5b11d6059b5f2e7ad13c41a4d14c67fd6ad61 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 21:16:59 -0300 Subject: [PATCH 13/41] Update img-lazy.js --- src/components/image/img-lazy.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/image/img-lazy.js b/src/components/image/img-lazy.js index ffb1ac78bb9..401d85fb5da 100644 --- a/src/components/image/img-lazy.js +++ b/src/components/image/img-lazy.js @@ -133,7 +133,7 @@ export const BImgLazy = /*#__PURE__*/ Vue.extend({ created() { this.isShown = this.show }, - beforeMount() { + mounted() { if (!hasIntersectionObserverSupport) { // If IntersectionObserver is not available, image is always shown this.isShown = true @@ -159,7 +159,7 @@ export const BImgLazy = /*#__PURE__*/ Vue.extend({ // Visible directive will silently do nothing if // `IntersectionObserver` is not supported name: 'b-visible', - // Value expects a callback (passed on arg of visible = true/false) + // Value expects a callback (passed one arg of visible = true/false) value: this.doShow, modifiers: { // Root margin from viewport From 6a183e1cf47dd9b229fafaacacce5fd940402760 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 21:23:15 -0300 Subject: [PATCH 14/41] Update img-lazy.spec.js --- src/components/image/img-lazy.spec.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/components/image/img-lazy.spec.js b/src/components/image/img-lazy.spec.js index a811f4284d0..cf2d20757ff 100644 --- a/src/components/image/img-lazy.spec.js +++ b/src/components/image/img-lazy.spec.js @@ -6,7 +6,9 @@ const src = 'https://picsum.photos/1024/400/?image=41' describe('img-lazy', () => { const windowIntersectionObserver = window.IntersectionObserver - // Mock callback netry + const windowIntersectionObserverEntry = window.IntersectionObserverEntry + + // Mock callback entry // const mockEntry = { isIntersecting: true, intersectionRatio: 1 } beforeAll(() => { @@ -31,10 +33,19 @@ describe('img-lazy', () => { return null } } + + window.IntersectionObserverEntry = class mockIntersectionObserverEntry { + constructor() {} + + get intersectionRatio() { + return 1 + } + } }) afterAll(() => { window.IntersectionObserver = windowIntersectionObserver + window.IntersectionObserverEntry = windowIntersectionObserverEntry }) it('has root element "img"', async () => { From 55a64212cc9cd751420ddd319cc5cb9ed8da0c18 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 21:30:20 -0300 Subject: [PATCH 15/41] Update visible.js --- src/directives/visible.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/directives/visible.js b/src/directives/visible.js index 132ea838f1d..6e7dad0edc1 100644 --- a/src/directives/visible.js +++ b/src/directives/visible.js @@ -93,7 +93,11 @@ class VisibilityObserver { /* istanbul ignore next: IntersectionObserver not supported in JSDOM */ vnode.context.$nextTick(() => { requestAF(() => { - this.observer.observe(this.el) + // placed in an `if` just in case we were + // destroyed before this requestAnimationFrame runs + if (this.observer) { + this.observer.observe(this.el) + } }) }) } From c89a816b58d5e40d312883e3694a383d3720efaf Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 21:32:57 -0300 Subject: [PATCH 16/41] Update card-img-lazy.spec.js --- src/components/card/card-img-lazy.spec.js | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/components/card/card-img-lazy.spec.js b/src/components/card/card-img-lazy.spec.js index 747cef50de5..2890192b15c 100644 --- a/src/components/card/card-img-lazy.spec.js +++ b/src/components/card/card-img-lazy.spec.js @@ -11,17 +11,7 @@ describe('card-image', () => { } }) expect(wrapper.is('img')).toBe(true) - }) - - it('default has data src attribute', async () => { - const wrapper = mount(BCardImgLazy, { - context: { - props: { - src: 'https://picsum.photos/600/300/?image=25' - } - } - }) - expect(wrapper.attributes('src')).toContain('data:image/svg+xml') + expect(wrapper.attributes('src')).toBeDefined() }) it('default does not have alt attribute', async () => { From 683074c8449c10f2a65d526d81f76bed2869993b Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 21:37:04 -0300 Subject: [PATCH 17/41] Update img-lazy.spec.js --- src/components/image/img-lazy.spec.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/image/img-lazy.spec.js b/src/components/image/img-lazy.spec.js index cf2d20757ff..6e533f5e9a5 100644 --- a/src/components/image/img-lazy.spec.js +++ b/src/components/image/img-lazy.spec.js @@ -35,10 +35,12 @@ describe('img-lazy', () => { } window.IntersectionObserverEntry = class mockIntersectionObserverEntry { - constructor() {} + constructor() { + this._foo = 1 + } get intersectionRatio() { - return 1 + return this._foo } } }) From f3b931f754e25f3188f436c0dad6fe73b07ead52 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 21:41:48 -0300 Subject: [PATCH 18/41] Update img-lazy.spec.js --- src/components/image/img-lazy.spec.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/image/img-lazy.spec.js b/src/components/image/img-lazy.spec.js index 6e533f5e9a5..405d596bbd0 100644 --- a/src/components/image/img-lazy.spec.js +++ b/src/components/image/img-lazy.spec.js @@ -92,8 +92,11 @@ describe('img-lazy', () => { await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) + await waitRAF() + + expect(wrapper.vm.isShown).toBe(false) - // Our directive instance with test "fake" observer + // Our directive instance should exist let observer = wrapper.element.__bv__visibility_observer expect(observer).toBeDefined() @@ -106,6 +109,7 @@ describe('img-lazy', () => { await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) + await waitRAF() expect(wrapper.attributes('src')).toBe(src) @@ -119,6 +123,8 @@ describe('img-lazy', () => { await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) + await waitRAF() + expect(wrapper.attributes('src')).toContain('data:image/svg+xml;charset=UTF-8') // Our directive instance should be back From 26c40c757ea020c5a55684b230bd70a68b749085 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 21:57:05 -0300 Subject: [PATCH 19/41] Update card-img-lazy.spec.js --- src/components/card/card-img-lazy.spec.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/card/card-img-lazy.spec.js b/src/components/card/card-img-lazy.spec.js index 2890192b15c..6eed7706406 100644 --- a/src/components/card/card-img-lazy.spec.js +++ b/src/components/card/card-img-lazy.spec.js @@ -33,10 +33,14 @@ describe('card-image', () => { } } }) - expect(wrapper.attributes('width')).toBeDefined() - expect(wrapper.attributes('width')).toBe('1') - expect(wrapper.attributes('height')).toBeDefined() - expect(wrapper.attributes('height')).toBe('1') + expect(wrapper.attributes('width')).not.toBeDefined() + expect(wrapper.attributes('height')).not.toBeDefined() + // Without IntersectionObserver support, the main image is shown + // and the value of the width and height props are used (null in this case) + // expect(wrapper.attributes('width')).toBeDefined() + // expect(wrapper.attributes('width')).toBe('1') + // expect(wrapper.attributes('height')).toBeDefined() + // expect(wrapper.attributes('height')).toBe('1') }) it('default has class "card-img"', async () => { From 91d784c53de1c6ecf173e6691b2426b1eb36483a Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 22:09:07 -0300 Subject: [PATCH 20/41] Update visible.js --- src/directives/visible.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/directives/visible.js b/src/directives/visible.js index 6e7dad0edc1..7b7ce6beffc 100644 --- a/src/directives/visible.js +++ b/src/directives/visible.js @@ -90,7 +90,6 @@ class VisibilityObserver { } // Start observing in a `$nextTick()` (to allow DOM to complete rendering) - /* istanbul ignore next: IntersectionObserver not supported in JSDOM */ vnode.context.$nextTick(() => { requestAF(() => { // placed in an `if` just in case we were @@ -102,7 +101,7 @@ class VisibilityObserver { }) } - handler(entries) /* istanbul ignore next: IntersectionObserver not supported in JSDOM */ { + handler(entries) { const entry = entries ? entries[0] : {} const isIntersecting = Boolean(entry.isIntersecting || entry.intersectionRatio > 0.0) if (isIntersecting !== this.visible) { @@ -158,7 +157,7 @@ const bind = (el, { value, modifiers }, vnode) => { } // When the directive options may have been updated (or element) -const update = (el, { value, oldValue, modifiers }, vnode) => { +const componentUpdated = (el, { value, oldValue, modifiers }, vnode) => { // Compare value/oldValue and modifiers to see if anything has changed // and if so, destroy old observer and create new observer /* istanbul ignore next */ @@ -181,6 +180,6 @@ const unbind = el => { // Export the directive export const VBVisible = { bind, - update, + componentUpdated, unbind } From b8ed86835fb70dce86434850d93fe48237eb81d3 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 22:21:06 -0300 Subject: [PATCH 21/41] Update img-lazy.spec.js --- src/components/image/img-lazy.spec.js | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/components/image/img-lazy.spec.js b/src/components/image/img-lazy.spec.js index 405d596bbd0..6b156439d14 100644 --- a/src/components/image/img-lazy.spec.js +++ b/src/components/image/img-lazy.spec.js @@ -1,5 +1,6 @@ import { mount } from '@vue/test-utils' import { waitNT, waitRAF } from '../../../tests/utils' +import { hasIntersectionObserverSupport } from '../../utils/env' import { BImgLazy } from './img-lazy' const src = 'https://picsum.photos/1024/400/?image=41' @@ -15,23 +16,22 @@ describe('img-lazy', () => { // IntersectionObserver not supported by JSDOM // So we mock up just the basics window.IntersectionObserver = class mockIntersectionObserver { - constructor(calback, opts) { - // We store a copy of handler so + constructor(callback, opts) { + // We store a copy of callback so // we can call it during tests - this.callback = calback + this._callback = callback } - observe() { - return null + // Getter for stored callback for testing + get callback() { + return this._callback } - unobserve() { - return null - } + observe() {} - disconnect() { - return null - } + unobserve() {} + + disconnect() {} } window.IntersectionObserverEntry = class mockIntersectionObserverEntry { @@ -79,6 +79,8 @@ describe('img-lazy', () => { }) it('shows when show prop is set', async () => { + expect(hasIntersectionObserverSupport).toBeTruthy() + const wrapper = mount(BImgLazy, { attachToDocument: true, propsData: { From 41a22b25c3b3aa95a7028c70f658749ab4c99c4a Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 22:25:59 -0300 Subject: [PATCH 22/41] Update img-lazy.spec.js --- src/components/image/img-lazy.spec.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/image/img-lazy.spec.js b/src/components/image/img-lazy.spec.js index 6b156439d14..a11b6a60397 100644 --- a/src/components/image/img-lazy.spec.js +++ b/src/components/image/img-lazy.spec.js @@ -1,6 +1,5 @@ import { mount } from '@vue/test-utils' import { waitNT, waitRAF } from '../../../tests/utils' -import { hasIntersectionObserverSupport } from '../../utils/env' import { BImgLazy } from './img-lazy' const src = 'https://picsum.photos/1024/400/?image=41' @@ -79,6 +78,14 @@ describe('img-lazy', () => { }) it('shows when show prop is set', async () => { + const hasIntersectionObserverSupport = + 'IntersectionObserver' in window && + 'IntersectionObserverEntry' in window && + // Edge 15 and UC Browser lack support for `isIntersecting` + // but we an use intersectionRatio > 0 instead + // 'isIntersecting' in window.IntersectionObserverEntry.prototype && + 'intersectionRatio' in window.IntersectionObserverEntry.prototype + expect(hasIntersectionObserverSupport).toBeTruthy() const wrapper = mount(BImgLazy, { From 77aac7d332cde4ae3d3bb01ebc7f955439612fe8 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 22:31:22 -0300 Subject: [PATCH 23/41] Update img-lazy.js --- src/components/image/img-lazy.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/image/img-lazy.js b/src/components/image/img-lazy.js index 401d85fb5da..9ab808b1a13 100644 --- a/src/components/image/img-lazy.js +++ b/src/components/image/img-lazy.js @@ -94,7 +94,9 @@ export const BImgLazy = /*#__PURE__*/ Vue.extend({ props, data() { return { - isShown: false + isShown: false, + // DEBUG: For test debugging + hasIntersectionObserverSupport: hasIntersectionObserverSupport } }, computed: { From a29f9f76f51b715059aba36181626f908c56688e Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 22:32:16 -0300 Subject: [PATCH 24/41] Update img-lazy.spec.js --- src/components/image/img-lazy.spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/image/img-lazy.spec.js b/src/components/image/img-lazy.spec.js index a11b6a60397..64d881a2018 100644 --- a/src/components/image/img-lazy.spec.js +++ b/src/components/image/img-lazy.spec.js @@ -97,6 +97,7 @@ describe('img-lazy', () => { }) expect(wrapper.is('img')).toBe(true) + expect(wrapper.vm.hasIntersectionObserverSupport).toBeTruthy() await waitNT(wrapper.vm) await waitRAF() From 4514848dcda0a36168768848e558a3aad4bd25ba Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 22:37:54 -0300 Subject: [PATCH 25/41] Update img-lazy.spec.js --- src/components/image/img-lazy.spec.js | 72 +++++++++++++-------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/src/components/image/img-lazy.spec.js b/src/components/image/img-lazy.spec.js index 64d881a2018..ff8c66c2273 100644 --- a/src/components/image/img-lazy.spec.js +++ b/src/components/image/img-lazy.spec.js @@ -4,51 +4,51 @@ import { BImgLazy } from './img-lazy' const src = 'https://picsum.photos/1024/400/?image=41' -describe('img-lazy', () => { - const windowIntersectionObserver = window.IntersectionObserver - const windowIntersectionObserverEntry = window.IntersectionObserverEntry - - // Mock callback entry - // const mockEntry = { isIntersecting: true, intersectionRatio: 1 } - - beforeAll(() => { - // IntersectionObserver not supported by JSDOM - // So we mock up just the basics - window.IntersectionObserver = class mockIntersectionObserver { - constructor(callback, opts) { - // We store a copy of callback so - // we can call it during tests - this._callback = callback - } +const windowIntersectionObserver = window.IntersectionObserver +const windowIntersectionObserverEntry = window.IntersectionObserverEntry + +// Mock callback entry +// const mockEntry = { isIntersecting: true, intersectionRatio: 1 } + +beforeAll(() => { + // IntersectionObserver not supported by JSDOM + // So we mock up just the basics + window.IntersectionObserver = class mockIntersectionObserver { + constructor(callback, opts) { + // We store a copy of callback so + // we can call it during tests + this._callback = callback + } - // Getter for stored callback for testing - get callback() { - return this._callback - } + // Getter for stored callback for testing + get callback() { + return this._callback + } - observe() {} + observe() {} - unobserve() {} + unobserve() {} - disconnect() {} - } + disconnect() {} + } - window.IntersectionObserverEntry = class mockIntersectionObserverEntry { - constructor() { - this._foo = 1 - } + window.IntersectionObserverEntry = class mockIntersectionObserverEntry { + constructor() { + this._foo = 1 + } - get intersectionRatio() { - return this._foo - } + get intersectionRatio() { + return this._foo } - }) + } +}) - afterAll(() => { - window.IntersectionObserver = windowIntersectionObserver - window.IntersectionObserverEntry = windowIntersectionObserverEntry - }) +afterAll(() => { + window.IntersectionObserver = windowIntersectionObserver + window.IntersectionObserverEntry = windowIntersectionObserverEntry +}) +describe('img-lazy', () => { it('has root element "img"', async () => { const wrapper = mount(BImgLazy, { attachToDocument: true, From 620a829bb2e67f230f26161fc4613c7a0c1616fb Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 22:41:07 -0300 Subject: [PATCH 26/41] Update img-lazy.spec.js --- src/components/image/img-lazy.spec.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/image/img-lazy.spec.js b/src/components/image/img-lazy.spec.js index ff8c66c2273..649db0d436b 100644 --- a/src/components/image/img-lazy.spec.js +++ b/src/components/image/img-lazy.spec.js @@ -1,8 +1,5 @@ import { mount } from '@vue/test-utils' import { waitNT, waitRAF } from '../../../tests/utils' -import { BImgLazy } from './img-lazy' - -const src = 'https://picsum.photos/1024/400/?image=41' const windowIntersectionObserver = window.IntersectionObserver const windowIntersectionObserverEntry = window.IntersectionObserverEntry @@ -48,6 +45,10 @@ afterAll(() => { window.IntersectionObserverEntry = windowIntersectionObserverEntry }) +import { BImgLazy } from './img-lazy' + +const src = 'https://picsum.photos/1024/400/?image=41' + describe('img-lazy', () => { it('has root element "img"', async () => { const wrapper = mount(BImgLazy, { From fd1dc8422dd1e67e70b05f262b60f8c7382dbf2a Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 22:44:13 -0300 Subject: [PATCH 27/41] Update img-lazy.spec.js --- src/components/image/img-lazy.spec.js | 58 +++++++++++---------------- 1 file changed, 24 insertions(+), 34 deletions(-) diff --git a/src/components/image/img-lazy.spec.js b/src/components/image/img-lazy.spec.js index 649db0d436b..941ed08d9a1 100644 --- a/src/components/image/img-lazy.spec.js +++ b/src/components/image/img-lazy.spec.js @@ -1,5 +1,8 @@ import { mount } from '@vue/test-utils' import { waitNT, waitRAF } from '../../../tests/utils' +import { BImgLazy } from './img-lazy' + +const src = 'https://picsum.photos/1024/400/?image=41' const windowIntersectionObserver = window.IntersectionObserver const windowIntersectionObserverEntry = window.IntersectionObserverEntry @@ -7,47 +10,34 @@ const windowIntersectionObserverEntry = window.IntersectionObserverEntry // Mock callback entry // const mockEntry = { isIntersecting: true, intersectionRatio: 1 } -beforeAll(() => { - // IntersectionObserver not supported by JSDOM - // So we mock up just the basics - window.IntersectionObserver = class mockIntersectionObserver { - constructor(callback, opts) { - // We store a copy of callback so - // we can call it during tests - this._callback = callback - } - - // Getter for stored callback for testing - get callback() { - return this._callback - } - - observe() {} - - unobserve() {} +window.IntersectionObserver = class mockIntersectionObserver { + constructor(callback, opts) { + // We store a copy of callback so + // we can call it during tests + this._callback = callback + } - disconnect() {} + // Getter for stored callback for testing + get callback() { + return this._callback } - window.IntersectionObserverEntry = class mockIntersectionObserverEntry { - constructor() { - this._foo = 1 - } + observe() {} - get intersectionRatio() { - return this._foo - } - } -}) + unobserve() {} -afterAll(() => { - window.IntersectionObserver = windowIntersectionObserver - window.IntersectionObserverEntry = windowIntersectionObserverEntry -}) + disconnect() {} +} -import { BImgLazy } from './img-lazy' +window.IntersectionObserverEntry = class mockIntersectionObserverEntry { + constructor() { + this._foo = 1 + } -const src = 'https://picsum.photos/1024/400/?image=41' + get intersectionRatio() { + return this._foo + } +} describe('img-lazy', () => { it('has root element "img"', async () => { From ffefa38a678d3b245bc590ca6db070bb6190e91a Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 22:50:24 -0300 Subject: [PATCH 28/41] Update img-lazy.spec.js --- src/components/image/img-lazy.spec.js | 65 +++++---------------------- 1 file changed, 10 insertions(+), 55 deletions(-) diff --git a/src/components/image/img-lazy.spec.js b/src/components/image/img-lazy.spec.js index 941ed08d9a1..d521ace3d11 100644 --- a/src/components/image/img-lazy.spec.js +++ b/src/components/image/img-lazy.spec.js @@ -4,41 +4,6 @@ import { BImgLazy } from './img-lazy' const src = 'https://picsum.photos/1024/400/?image=41' -const windowIntersectionObserver = window.IntersectionObserver -const windowIntersectionObserverEntry = window.IntersectionObserverEntry - -// Mock callback entry -// const mockEntry = { isIntersecting: true, intersectionRatio: 1 } - -window.IntersectionObserver = class mockIntersectionObserver { - constructor(callback, opts) { - // We store a copy of callback so - // we can call it during tests - this._callback = callback - } - - // Getter for stored callback for testing - get callback() { - return this._callback - } - - observe() {} - - unobserve() {} - - disconnect() {} -} - -window.IntersectionObserverEntry = class mockIntersectionObserverEntry { - constructor() { - this._foo = 1 - } - - get intersectionRatio() { - return this._foo - } -} - describe('img-lazy', () => { it('has root element "img"', async () => { const wrapper = mount(BImgLazy, { @@ -68,17 +33,7 @@ describe('img-lazy', () => { wrapper.destroy() }) - it('shows when show prop is set', async () => { - const hasIntersectionObserverSupport = - 'IntersectionObserver' in window && - 'IntersectionObserverEntry' in window && - // Edge 15 and UC Browser lack support for `isIntersecting` - // but we an use intersectionRatio > 0 instead - // 'isIntersecting' in window.IntersectionObserverEntry.prototype && - 'intersectionRatio' in window.IntersectionObserverEntry.prototype - - expect(hasIntersectionObserverSupport).toBeTruthy() - + it('shows when IntersectionObserver not supported', async () => { const wrapper = mount(BImgLazy, { attachToDocument: true, propsData: { @@ -88,21 +43,20 @@ describe('img-lazy', () => { }) expect(wrapper.is('img')).toBe(true) - expect(wrapper.vm.hasIntersectionObserverSupport).toBeTruthy() await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() - expect(wrapper.vm.isShown).toBe(false) + expect(wrapper.vm.isShown).toBe(true) - // Our directive instance should exist + // Our directive instance should not exist let observer = wrapper.element.__bv__visibility_observer - expect(observer).toBeDefined() + expect(observer).not.toBeDefined() expect(wrapper.attributes('src')).toBeDefined() - expect(wrapper.attributes('src')).toContain('data:image/svg+xml;charset=UTF-8') + expect(wrapper.attributes('src')).toContain(src) wrapper.setProps({ show: true @@ -113,8 +67,9 @@ describe('img-lazy', () => { await waitRAF() expect(wrapper.attributes('src')).toBe(src) + expect(wrapper.vm.isShown).toBe(true) - // Our directive instance should be gone + // Our directive instance should not exist observer = wrapper.element.__bv__visibility_observer expect(observer).not.toBeDefined() @@ -126,11 +81,11 @@ describe('img-lazy', () => { await waitNT(wrapper.vm) await waitRAF() - expect(wrapper.attributes('src')).toContain('data:image/svg+xml;charset=UTF-8') + expect(wrapper.attributes('src')).toContain(src) - // Our directive instance should be back + // Our directive instance should not exist observer = wrapper.element.__bv__visibility_observer - expect(observer).toBeDefined() + expect(observer).not.toBeDefined() wrapper.destroy() }) From 7d1691ae31e3719dda39ae0c2860cddc1355a852 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 22:50:48 -0300 Subject: [PATCH 29/41] Update img-lazy.js --- src/components/image/img-lazy.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/image/img-lazy.js b/src/components/image/img-lazy.js index 9ab808b1a13..401d85fb5da 100644 --- a/src/components/image/img-lazy.js +++ b/src/components/image/img-lazy.js @@ -94,9 +94,7 @@ export const BImgLazy = /*#__PURE__*/ Vue.extend({ props, data() { return { - isShown: false, - // DEBUG: For test debugging - hasIntersectionObserverSupport: hasIntersectionObserverSupport + isShown: false } }, computed: { From 2ff128b6a89ce19fc83263997d4ffc542e52e7f6 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 22:58:40 -0300 Subject: [PATCH 30/41] Update img-lazy.spec.js --- src/components/image/img-lazy.spec.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/image/img-lazy.spec.js b/src/components/image/img-lazy.spec.js index d521ace3d11..7bf2832b759 100644 --- a/src/components/image/img-lazy.spec.js +++ b/src/components/image/img-lazy.spec.js @@ -48,6 +48,8 @@ describe('img-lazy', () => { await waitRAF() await waitNT(wrapper.vm) await waitRAF() + await waitNT(wrapper.vm) + await waitRAF() expect(wrapper.vm.isShown).toBe(true) From 8de93c05dbc4ada6eed6a41d72b71afec5a7da6a Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 23:14:12 -0300 Subject: [PATCH 31/41] Update img-lazy.js --- src/components/image/img-lazy.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/components/image/img-lazy.js b/src/components/image/img-lazy.js index 401d85fb5da..cff630259c9 100644 --- a/src/components/image/img-lazy.js +++ b/src/components/image/img-lazy.js @@ -94,7 +94,7 @@ export const BImgLazy = /*#__PURE__*/ Vue.extend({ props, data() { return { - isShown: false + isShown: this.show } }, computed: { @@ -130,14 +130,9 @@ export const BImgLazy = /*#__PURE__*/ Vue.extend({ } } }, - created() { - this.isShown = this.show - }, mounted() { - if (!hasIntersectionObserverSupport) { - // If IntersectionObserver is not available, image is always shown - this.isShown = true - } + // If IntersectionObserver is not available, image is always shown + this.isShown = hasIntersectionObserverSupport ? this.show : true }, methods: { updateShowProp() { From 50861f9d28a69ae4165dd72e5d15e824c6fb6a65 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 23:16:33 -0300 Subject: [PATCH 32/41] Update img-lazy.spec.js --- src/components/image/img-lazy.spec.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/components/image/img-lazy.spec.js b/src/components/image/img-lazy.spec.js index 7bf2832b759..3f6a8c0accb 100644 --- a/src/components/image/img-lazy.spec.js +++ b/src/components/image/img-lazy.spec.js @@ -44,6 +44,30 @@ describe('img-lazy', () => { expect(wrapper.is('img')).toBe(true) + await waitNT(wrapper.vm) + await waitRAF() + await waitNT(wrapper.vm) + await waitRAF() + await waitNT(wrapper.vm) + await waitRAF() + await waitNT(wrapper.vm) + await waitRAF() + await waitNT(wrapper.vm) + await waitRAF() + await waitNT(wrapper.vm) + await waitRAF() + await waitNT(wrapper.vm) + await waitRAF() + await waitNT(wrapper.vm) + await waitRAF() + await waitNT(wrapper.vm) + await waitRAF() + await waitNT(wrapper.vm) + await waitRAF() + await waitNT(wrapper.vm) + await waitRAF() + await waitNT(wrapper.vm) + await waitRAF() await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) From 100e1157e3c5f0ea7c495623b0ca44d723028734 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 23:29:40 -0300 Subject: [PATCH 33/41] Update img-lazy.spec.js --- src/components/image/img-lazy.spec.js | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/src/components/image/img-lazy.spec.js b/src/components/image/img-lazy.spec.js index 3f6a8c0accb..b45d44731eb 100644 --- a/src/components/image/img-lazy.spec.js +++ b/src/components/image/img-lazy.spec.js @@ -44,30 +44,6 @@ describe('img-lazy', () => { expect(wrapper.is('img')).toBe(true) - await waitNT(wrapper.vm) - await waitRAF() - await waitNT(wrapper.vm) - await waitRAF() - await waitNT(wrapper.vm) - await waitRAF() - await waitNT(wrapper.vm) - await waitRAF() - await waitNT(wrapper.vm) - await waitRAF() - await waitNT(wrapper.vm) - await waitRAF() - await waitNT(wrapper.vm) - await waitRAF() - await waitNT(wrapper.vm) - await waitRAF() - await waitNT(wrapper.vm) - await waitRAF() - await waitNT(wrapper.vm) - await waitRAF() - await waitNT(wrapper.vm) - await waitRAF() - await waitNT(wrapper.vm) - await waitRAF() await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) @@ -79,7 +55,7 @@ describe('img-lazy', () => { // Our directive instance should not exist let observer = wrapper.element.__bv__visibility_observer - expect(observer).not.toBeDefined() + // expect(observer).not.toBeDefined() expect(wrapper.attributes('src')).toBeDefined() expect(wrapper.attributes('src')).toContain(src) From 24b1e301d43619c67c79a73ad3a371bab1befc9c Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 23:34:47 -0300 Subject: [PATCH 34/41] Update visible.js --- src/directives/visible.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/directives/visible.js b/src/directives/visible.js index 7b7ce6beffc..e81e229e978 100644 --- a/src/directives/visible.js +++ b/src/directives/visible.js @@ -180,6 +180,7 @@ const unbind = el => { // Export the directive export const VBVisible = { bind, + updated: componentUpdated, componentUpdated, unbind } From 71a8b874dc0a39e31bb021007091272d0a638885 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 23:38:11 -0300 Subject: [PATCH 35/41] Update visible.js --- src/directives/visible.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/directives/visible.js b/src/directives/visible.js index e81e229e978..7b7ce6beffc 100644 --- a/src/directives/visible.js +++ b/src/directives/visible.js @@ -180,7 +180,6 @@ const unbind = el => { // Export the directive export const VBVisible = { bind, - updated: componentUpdated, componentUpdated, unbind } From 44df4deb7eaaeb7a5bceb93328bd42df13133819 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 23:40:53 -0300 Subject: [PATCH 36/41] Update img-lazy.spec.js --- src/components/image/img-lazy.spec.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/components/image/img-lazy.spec.js b/src/components/image/img-lazy.spec.js index b45d44731eb..48b7d4e68c2 100644 --- a/src/components/image/img-lazy.spec.js +++ b/src/components/image/img-lazy.spec.js @@ -53,9 +53,11 @@ describe('img-lazy', () => { expect(wrapper.vm.isShown).toBe(true) + // It appears that vue-test-utils does not run unbind when the directive is + // removed from the element. Only when the component is destroyed... unlike Vue // Our directive instance should not exist - let observer = wrapper.element.__bv__visibility_observer - // expect(observer).not.toBeDefined() + let observer = wrapper.vm.$el.__bv__visibility_observer + expect(observer).not.toBeDefined() expect(wrapper.attributes('src')).toBeDefined() expect(wrapper.attributes('src')).toContain(src) @@ -72,8 +74,8 @@ describe('img-lazy', () => { expect(wrapper.vm.isShown).toBe(true) // Our directive instance should not exist - observer = wrapper.element.__bv__visibility_observer - expect(observer).not.toBeDefined() + // observer = wrapper.element.__bv__visibility_observer + // expect(observer).not.toBeDefined() wrapper.setProps({ show: false @@ -86,8 +88,8 @@ describe('img-lazy', () => { expect(wrapper.attributes('src')).toContain(src) // Our directive instance should not exist - observer = wrapper.element.__bv__visibility_observer - expect(observer).not.toBeDefined() + // observer = wrapper.element.__bv__visibility_observer + // expect(observer).not.toBeDefined() wrapper.destroy() }) From 300be3fdd71d2e205c684999f65fd5d5ba8ba994 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 23:44:04 -0300 Subject: [PATCH 37/41] Update img-lazy.spec.js --- src/components/image/img-lazy.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/image/img-lazy.spec.js b/src/components/image/img-lazy.spec.js index 48b7d4e68c2..abb6d92be6f 100644 --- a/src/components/image/img-lazy.spec.js +++ b/src/components/image/img-lazy.spec.js @@ -56,8 +56,8 @@ describe('img-lazy', () => { // It appears that vue-test-utils does not run unbind when the directive is // removed from the element. Only when the component is destroyed... unlike Vue // Our directive instance should not exist - let observer = wrapper.vm.$el.__bv__visibility_observer - expect(observer).not.toBeDefined() + // let observer = wrapper.element.__bv__visibility_observer + // expect(observer).not.toBeDefined() expect(wrapper.attributes('src')).toBeDefined() expect(wrapper.attributes('src')).toContain(src) From a523ae4a2f1cff48d86578b49347e336204be1ab Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 29 Aug 2019 23:48:19 -0300 Subject: [PATCH 38/41] Update visible.js --- src/directives/visible.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/directives/visible.js b/src/directives/visible.js index 7b7ce6beffc..b72a1b61394 100644 --- a/src/directives/visible.js +++ b/src/directives/visible.js @@ -90,6 +90,7 @@ class VisibilityObserver { } // Start observing in a `$nextTick()` (to allow DOM to complete rendering) + /* istanbul ignore next: IntersectionObserver not supported in JSDOM */ vnode.context.$nextTick(() => { requestAF(() => { // placed in an `if` just in case we were @@ -101,7 +102,7 @@ class VisibilityObserver { }) } - handler(entries) { + handler(entries) /* istanbul ignore next: IntersectionObserver not supported in JSDOM */ { const entry = entries ? entries[0] : {} const isIntersecting = Boolean(entry.isIntersecting || entry.intersectionRatio > 0.0) if (isIntersecting !== this.visible) { From c53b1fae9ba67b23ded6fa5b81632d3dbaf3ed7b Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Fri, 30 Aug 2019 00:02:48 -0300 Subject: [PATCH 39/41] Update README.md --- src/components/image/README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/image/README.md b/src/components/image/README.md index b3486b3af7f..32bececc2a9 100644 --- a/src/components/image/README.md +++ b/src/components/image/README.md @@ -209,9 +209,8 @@ The default `blank-color` is `transparent`. Lazy loading images uses [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) -if supported by the browser (or polyfill), otherwise it uses the document `scroll`, `resize`, and -`transitionend` events to determine if the image is in view in order to trigger the loading of the -final image. Scrolling of other elements is not monitored, and will not trigger image loading. +if supported by the browser (or via a polyfill) to detect with the image should be shown. If +`IntersectionObserver` support is _not detected_, then the image will _always_ be shown. ### Usage From 496dabf0bd42388e6c66f63df689292e916a262f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacob=20M=C3=BCller?= Date: Fri, 30 Aug 2019 07:57:57 +0200 Subject: [PATCH 40/41] Update visible.js --- src/directives/visible.js | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/src/directives/visible.js b/src/directives/visible.js index b72a1b61394..8ef04b63d76 100644 --- a/src/directives/visible.js +++ b/src/directives/visible.js @@ -36,7 +36,7 @@ import { requestAF } from '../utils/dom' import { isFunction } from '../utils/inspect' import { keys } from '../utils/object' -const PROP_NAME = '__bv__visibility_observer' +const OBSERVER_PROP_NAME = '__bv__visibility_observer' class VisibilityObserver { constructor(el, options, vnode) { @@ -52,19 +52,14 @@ class VisibilityObserver { } createObserver(vnode) { + // Remove any previous observer if (this.observer) { - // Remove any previous observer /* istanbul ignore next */ this.stop() } - if (this.doneOnce) { - // Should only be called once - /* istanbul ignore next */ - return - } - - if (!isFunction(this.callback)) { + // Should only be called once and `callback` prop should be a function + if (this.doneOnce || !isFunction(this.callback)) { /* istanbul ignore next */ return } @@ -93,8 +88,8 @@ class VisibilityObserver { /* istanbul ignore next: IntersectionObserver not supported in JSDOM */ vnode.context.$nextTick(() => { requestAF(() => { - // placed in an `if` just in case we were - // destroyed before this requestAnimationFrame runs + // Placed in an `if` just in case we were destroyed before + // this `requestAnimationFrame` runs if (this.observer) { this.observer.observe(this.el) } @@ -126,11 +121,11 @@ class VisibilityObserver { } const destroy = el => { - const observer = el[PROP_NAME] + const observer = el[OBSERVER_PROP_NAME] if (observer && observer.stop) { observer.stop() } - delete el[PROP_NAME] + delete el[OBSERVER_PROP_NAME] } const bind = (el, { value, modifiers }, vnode) => { @@ -152,9 +147,9 @@ const bind = (el, { value, modifiers }, vnode) => { // Destroy any previous observer destroy(el) // Create new observer - el[PROP_NAME] = new VisibilityObserver(el, options, vnode) + el[OBSERVER_PROP_NAME] = new VisibilityObserver(el, options, vnode) // Store the current modifiers on the object (cloned) - el[PROP_NAME]._prevModifiers = { ...modifiers } + el[OBSERVER_PROP_NAME]._prevModifiers = { ...modifiers } } // When the directive options may have been updated (or element) @@ -164,8 +159,8 @@ const componentUpdated = (el, { value, oldValue, modifiers }, vnode) => { /* istanbul ignore next */ if ( value !== oldValue || - !el[PROP_NAME] || - !looseEqual(modifiers, el[PROP_NAME]._prevModifiers) + !el[OBSERVER_PROP_NAME] || + !looseEqual(modifiers, el[OBSERVER_PROP_NAME]._prevModifiers) ) { // Re-bind on element bind(el, { value, modifiers }, vnode) From c0f3242e5388f19720a9898b5e9a260faf758641 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacob=20M=C3=BCller?= Date: Fri, 30 Aug 2019 08:02:42 +0200 Subject: [PATCH 41/41] Update img-lazy.js --- src/components/image/img-lazy.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/image/img-lazy.js b/src/components/image/img-lazy.js index cff630259c9..66681da6fc5 100644 --- a/src/components/image/img-lazy.js +++ b/src/components/image/img-lazy.js @@ -1,8 +1,8 @@ import Vue from '../../utils/vue' -import { BImg } from './img' import { getComponentConfig } from '../../utils/config' import { hasIntersectionObserverSupport } from '../../utils/env' import { VBVisible } from '../../directives/visible' +import { BImg } from './img' const NAME = 'BImgLazy' @@ -78,7 +78,7 @@ export const props = { default: false }, offset: { - // distance away from viewport 9jn pixels) before being + // Distance away from viewport (in pixels) before being // considered "visible" type: [Number, String], default: 360 @@ -139,7 +139,7 @@ export const BImgLazy = /*#__PURE__*/ Vue.extend({ this.$emit('update:show', this.isShown) }, doShow(visible) { - // If 'IntersectionObserver` is not supported, the callback + // If IntersectionObserver is not supported, the callback // will be called with `null` rather than `true` or `false` if ((visible || visible === null) && !this.isShown) { this.isShown = true @@ -152,9 +152,9 @@ export const BImgLazy = /*#__PURE__*/ Vue.extend({ // We only add the visible directive if we are not shown directives.push({ // Visible directive will silently do nothing if - // `IntersectionObserver` is not supported + // IntersectionObserver is not supported name: 'b-visible', - // Value expects a callback (passed one arg of visible = true/false) + // Value expects a callback (passed one arg of `visible` = `true` or `false`) value: this.doShow, modifiers: { // Root margin from viewport @@ -173,7 +173,7 @@ export const BImgLazy = /*#__PURE__*/ Vue.extend({ blank: this.computedBlank, width: this.computedWidth, height: this.computedHeight, - // Passthough props + // Passthrough props alt: this.alt, blankColor: this.blankColor, fluid: this.fluid,