From eb70b1c43af9c8df63ddd702e45f67bf81562ea7 Mon Sep 17 00:00:00 2001 From: Michael Underwood Date: Thu, 4 Apr 2019 10:30:16 -0600 Subject: [PATCH 01/26] Add unit tests for 4/6 utility functions --- .../unit/utils/capitalizeFirstLetter.spec.js | 25 +++++ tests/unit/utils/collectionCleaner.spec.js | 91 +++++++++++++++++++ tests/unit/utils/debounce.spec.js | 82 +++++++++++++++++ tests/unit/utils/findRealParent.spec.js | 32 +++++++ 4 files changed, 230 insertions(+) create mode 100644 tests/unit/utils/capitalizeFirstLetter.spec.js create mode 100644 tests/unit/utils/collectionCleaner.spec.js create mode 100644 tests/unit/utils/debounce.spec.js create mode 100644 tests/unit/utils/findRealParent.spec.js diff --git a/tests/unit/utils/capitalizeFirstLetter.spec.js b/tests/unit/utils/capitalizeFirstLetter.spec.js new file mode 100644 index 00000000..3ceded16 --- /dev/null +++ b/tests/unit/utils/capitalizeFirstLetter.spec.js @@ -0,0 +1,25 @@ +import { capitalizeFirstLetter } from '@/utils/utils'; + +describe('utils: capitalizeFirstLetter', () => { + test.each([ + ['capitalize me', 'Capitalize me'], + ['toCaps', 'ToCaps'] + ])('it works with standard text', (input, expected) => { + expect(capitalizeFirstLetter(input)).toBe(expected); + }); + + test.each([ + ['Already capitalized'], + ['ALLCAPS'] + ])('it works when already capitalized', (value) => { + expect(capitalizeFirstLetter(value)).toBe(value); + }); + + test('it returns the empty string when given that', () => { + expect(capitalizeFirstLetter('')).toBe(''); + }); + + test('it returns undefined when given that', () => { + expect(capitalizeFirstLetter(undefined)).toBeUndefined(); + }); +}); diff --git a/tests/unit/utils/collectionCleaner.spec.js b/tests/unit/utils/collectionCleaner.spec.js new file mode 100644 index 00000000..00ac4e65 --- /dev/null +++ b/tests/unit/utils/collectionCleaner.spec.js @@ -0,0 +1,91 @@ +import { collectionCleaner } from '@/utils/utils'; + +describe('utils: collectionCleaner', () => { + test('it removes a key with a null value', () => { + const input = { removeMe: null }; + const output = collectionCleaner(input); + + expect(output).toBeDefined(); + expect(output.hasOwnProperty('removeMe')).toBeFalsy(); + }); + + test('it removes a key with an undefined value', () => { + const input = { removeMe: undefined }; + const output = collectionCleaner(input); + + expect(output).toBeDefined(); + expect(output.hasOwnProperty('removeMe')).toBeFalsy(); + }); + + test('it retains keys with the value false', () => { + const input = { keepMe: false }; + const output = collectionCleaner(input); + + expect(output).toBeDefined(); + expect(output.hasOwnProperty('keepMe')).toBeTruthy(); + }); + + test('it retains keys with the value 0', () => { + const input = { keepMe: 0 }; + const output = collectionCleaner(input); + + expect(output).toBeDefined(); + expect(output.hasOwnProperty('keepMe')).toBeTruthy(); + }); + + test('it retains keys with the empty string as a value', () => { + const input = { keepMe: '' }; + const output = collectionCleaner(input); + + expect(output).toBeDefined(); + expect(output.hasOwnProperty('keepMe')).toBeTruthy(); + }); + + test('it retains keys with the value NaN', () => { + const input = { keepMe: NaN }; + const output = collectionCleaner(input); + + expect(output).toBeDefined(); + expect(output.hasOwnProperty('keepMe')).toBeTruthy(); + }); + + test('it returns an empty object when given an undefined one', () => { + const output = collectionCleaner(); + + expect(output).toEqual({}); + }); + + test('it does not remove nested null properties', () => { + const expected = { a: { b: null } }; + const output = collectionCleaner(expected); + + expect(output).toEqual(expected); + }); + + test('it does not remove nested undefined properties', () => { + const expected = { a: { b: undefined } }; + const output = collectionCleaner(expected); + + expect(output).toEqual(expected); + }); + + test('it works as expected on a more complicated object', () => { + const expected = { + a: 1, + c: 'two', + e: { + f: undefined, + g: 3.14159 + } + }; + const input = { + ...expected, + b: undefined, + d: null + }; + + const output = collectionCleaner(input); + + expect(output).toEqual(expected); + }); +}); diff --git a/tests/unit/utils/debounce.spec.js b/tests/unit/utils/debounce.spec.js new file mode 100644 index 00000000..ff367cba --- /dev/null +++ b/tests/unit/utils/debounce.spec.js @@ -0,0 +1,82 @@ +import { debounce } from '@/utils/utils'; + +const sleep = (time) => new Promise(resolve => setTimeout(resolve, time)); +const debounceTime = 10; +const sleepTime = 15; + +describe('utils: debounce', () => { + test('it calls a function only once in a given window', async () => { + const fn = jest.fn(); + const debounced = debounce(fn, debounceTime); + + debounced(); + debounced(); + debounced(); + await sleep(sleepTime); + + expect(fn.mock.calls.length).toBe(1); + }); + + test('it allows multiple calls when outside the given window', async () => { + const fn = jest.fn(); + const debounced = debounce(fn, debounceTime); + + debounced(); + await sleep(sleepTime); + debounced(); + debounced(); + await sleep(sleepTime); + + expect(fn.mock.calls.length).toBe(2); + }); + + test('it passes arguments to the debounced function', async () => { + const fn = jest.fn(); + const debounced = debounce(fn, debounceTime); + + debounced(1, 'two', { three: 4 }); + await sleep(sleepTime); + + expect(fn.mock.calls.length).toBe(1); + + expect(fn.mock.calls[0][0]).toBe(1); + expect(fn.mock.calls[0][1]).toBe('two'); + expect(fn.mock.calls[0][2]).toEqual({ three: 4 }); + }); + + test('it only executes the final call in the time window, even with different arguments', async () => { + const fn = jest.fn(); + const debounced = debounce(fn, debounceTime); + + debounced('a'); + debounced('b'); + debounced('c'); + await sleep(sleepTime); + + expect(fn.mock.calls.length).toBe(1); + expect(fn.mock.calls[0][0]).toBe('c'); + }); + + test('it keeps the window open on subsequent calls, until it first expires', async () => { + const fn = jest.fn(); + const debounced = debounce(fn, debounceTime); + + // Call the function multiple times, each a little bit before the previous window + // would have expired. Even though this sequence will last 3.2 times the debounce + // window length plus the full sleep time, we expect only the final call to be + // actually executed. + debounced(1); + await sleep(debounceTime * 0.8); + debounced(2); + await sleep(debounceTime * 0.8); + debounced(3); + await sleep(debounceTime * 0.8); + debounced(4); + await sleep(debounceTime * 0.8); + debounced(5); + await sleep(sleepTime); + + expect(fn.mock.calls.length).toBe(1); + expect(fn.mock.calls[0][0]).toBe(5); + }); +}); diff --git a/tests/unit/utils/findRealParent.spec.js b/tests/unit/utils/findRealParent.spec.js new file mode 100644 index 00000000..4565e730 --- /dev/null +++ b/tests/unit/utils/findRealParent.spec.js @@ -0,0 +1,32 @@ +import { findRealParent } from '@/utils/utils'; + +describe('utils: findRealParent', () => { + test('it identifies when the given object is the parent', () => { + const realParent = { mapObject: 'exists' }; + + const foundParent = findRealParent(realParent); + + expect(foundParent).toEqual(realParent); + }); + + test('it finds a parent deeper in the hierarchy', () => { + const realParent = { mapObject: 'exists' }; + const descendent = { $parent: { $parent: realParent } }; + + const foundParent = findRealParent(descendent); + + expect(foundParent).toEqual(realParent); + }); + + test('it returns undefined when no real parent exists', () => { + const orphan = { $parent: { $parent: {} } }; + + const foundParent = findRealParent(orphan); + + expect(foundParent).toBeUndefined(); + }); + + test('it returns undefined when not given an object', () => { + expect(findRealParent()).toBeUndefined(); + }); +}); From 12eb81e32e1d224346e9603cb7761e75ac984f0d Mon Sep 17 00:00:00 2001 From: Michael Underwood Date: Thu, 4 Apr 2019 10:31:38 -0600 Subject: [PATCH 02/26] Add some checks to catch certain argument errors --- src/utils/utils.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/utils.js b/src/utils/utils.js index 157e54f6..673aa5af 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -16,6 +16,7 @@ export const debounce = (fn, time) => { }; export const capitalizeFirstLetter = (string) => { + if (!string || typeof string.charAt !== 'function') { return string; } return string.charAt(0).toUpperCase() + string.slice(1); }; @@ -78,7 +79,7 @@ export const optionsMerger = (props, instance) => { export const findRealParent = (firstVueParent) => { let found = false; - while (!found) { + while (firstVueParent && !found) { if (firstVueParent.mapObject === undefined) { firstVueParent = firstVueParent.$parent; } else { From dfc59d9407598a1eca5cc4769f7fb53b939a8658 Mon Sep 17 00:00:00 2001 From: Michael Underwood Date: Fri, 5 Apr 2019 08:04:10 -0600 Subject: [PATCH 03/26] Replace real-time sleep delays with mock waits --- tests/unit/utils/debounce.spec.js | 45 ++++++++++++++++++------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/tests/unit/utils/debounce.spec.js b/tests/unit/utils/debounce.spec.js index ff367cba..c3f8dcac 100644 --- a/tests/unit/utils/debounce.spec.js +++ b/tests/unit/utils/debounce.spec.js @@ -1,41 +1,48 @@ import { debounce } from '@/utils/utils'; -const sleep = (time) => new Promise(resolve => setTimeout(resolve, time)); -const debounceTime = 10; -const sleepTime = 15; - describe('utils: debounce', () => { - test('it calls a function only once in a given window', async () => { + const debounceTime = 1000; + const sleepTime = 1500; + + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + test('it calls a function only once in a given window', () => { const fn = jest.fn(); const debounced = debounce(fn, debounceTime); debounced(); debounced(); debounced(); - await sleep(sleepTime); + jest.advanceTimersByTime(sleepTime); expect(fn.mock.calls.length).toBe(1); }); - test('it allows multiple calls when outside the given window', async () => { + test('it allows multiple calls when outside the given window', () => { const fn = jest.fn(); const debounced = debounce(fn, debounceTime); debounced(); - await sleep(sleepTime); + jest.advanceTimersByTime(sleepTime); debounced(); debounced(); - await sleep(sleepTime); + jest.advanceTimersByTime(sleepTime); expect(fn.mock.calls.length).toBe(2); }); - test('it passes arguments to the debounced function', async () => { + test('it passes arguments to the debounced function', () => { const fn = jest.fn(); const debounced = debounce(fn, debounceTime); debounced(1, 'two', { three: 4 }); - await sleep(sleepTime); + jest.advanceTimersByTime(sleepTime); expect(fn.mock.calls.length).toBe(1); @@ -44,20 +51,20 @@ describe('utils: debounce', () => { expect(fn.mock.calls[0][2]).toEqual({ three: 4 }); }); - test('it only executes the final call in the time window, even with different arguments', async () => { + test('it only executes the final call in the time window, even with different arguments', () => { const fn = jest.fn(); const debounced = debounce(fn, debounceTime); debounced('a'); debounced('b'); debounced('c'); - await sleep(sleepTime); + jest.advanceTimersByTime(sleepTime); expect(fn.mock.calls.length).toBe(1); expect(fn.mock.calls[0][0]).toBe('c'); }); - test('it keeps the window open on subsequent calls, until it first expires', async () => { + test('it keeps the window open on subsequent calls, until it first expires', () => { const fn = jest.fn(); const debounced = debounce(fn, debounceTime); @@ -66,15 +73,15 @@ describe('utils: debounce', () => { // window length plus the full sleep time, we expect only the final call to be // actually executed. debounced(1); - await sleep(debounceTime * 0.8); + jest.advanceTimersByTime(debounceTime * 0.8); debounced(2); - await sleep(debounceTime * 0.8); + jest.advanceTimersByTime(debounceTime * 0.8); debounced(3); - await sleep(debounceTime * 0.8); + jest.advanceTimersByTime(debounceTime * 0.8); debounced(4); - await sleep(debounceTime * 0.8); + jest.advanceTimersByTime(debounceTime * 0.8); debounced(5); - await sleep(sleepTime); + jest.advanceTimersByTime(sleepTime); expect(fn.mock.calls.length).toBe(1); expect(fn.mock.calls[0][0]).toBe(5); From b89d41fa3863050bbf73e250ef86c2daf406294d Mon Sep 17 00:00:00 2001 From: Michael Underwood Date: Sun, 7 Apr 2019 13:38:09 -0600 Subject: [PATCH 04/26] WIP: Tests to investigate mounting inside LMap slot --- tests/unit/components/LControlZoom.spec.js | 116 +++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 tests/unit/components/LControlZoom.spec.js diff --git a/tests/unit/components/LControlZoom.spec.js b/tests/unit/components/LControlZoom.spec.js new file mode 100644 index 00000000..7b0e7d64 --- /dev/null +++ b/tests/unit/components/LControlZoom.spec.js @@ -0,0 +1,116 @@ +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import LControlZoom from '@/components/LControlZoom.vue'; +import LMap from '@/components/LMap.vue'; + +describe('component: LControlZoom', () => { + test('it can be mounted on its own', () => { + // This test fails because the component cannot be mounted without having a + // map object as its parent. + // Output: + // console.error node_modules/vue/dist/vue.runtime.common.dev.js:621 + // [Vue warn]: Error in mounted hook: "TypeError: Cannot read property '_zoom' of undefined" + // + // found in + // + // ---> + // + // + // console.error node_modules/vue/dist/vue.runtime.common.dev.js:1883 + // TypeError: Cannot read property '_zoom' of undefined + // at NewClass._updateDisabled (C:\code\Vue2Leaflet\node_modules\leaflet\src\control\Control.Zoom.js:109:29) + // at NewClass.onAdd (C:\code\Vue2Leaflet\node_modules\leaflet\src\control\Control.Zoom.js:48:8) + // at NewClass.addTo (C:\code\Vue2Leaflet\node_modules\leaflet\src\control\Control.js:70:42) + // at VueComponent.mounted (C:\code\Vue2Leaflet\src\components\LControlZoom.vue:377:20) + // at invokeWithErrorHandling (C:\code\Vue2Leaflet\node_modules\vue\dist\vue.runtime.common.dev.js:1850:57) + // at callHook (C:\code\Vue2Leaflet\node_modules\vue\dist\vue.runtime.common.dev.js:4178:7) + // at Object.insert (C:\code\Vue2Leaflet\node_modules\vue\dist\vue.runtime.common.dev.js:3126:7) + // at invokeInsertHook (C:\code\Vue2Leaflet\node_modules\vue\dist\vue.runtime.common.dev.js:6287:28) + // at VueComponent.patch [as __patch__] (C:\code\Vue2Leaflet\node_modules\vue\dist\vue.runtime.common.dev.js:6504:5) + // at VueComponent.Vue._update (C:\code\Vue2Leaflet\node_modules\vue\dist\vue.runtime.common.dev.js:3904:19) + // at VueComponent.updateComponent (C:\code\Vue2Leaflet\node_modules\vue\dist\vue.runtime.common.dev.js:4025:10) + // at Watcher.get (C:\code\Vue2Leaflet\node_modules\vue\dist\vue.runtime.common.dev.js:4426:25) + // at new Watcher (C:\code\Vue2Leaflet\node_modules\vue\dist\vue.runtime.common.dev.js:4415:12) + // at mountComponent (C:\code\Vue2Leaflet\node_modules\vue\dist\vue.runtime.common.dev.js:4032:3) + // at VueComponent.Object..Vue.$mount (C:\code\Vue2Leaflet\node_modules\vue\dist\vue.runtime.common.dev.js:8350:10) + // at mount (C:\code\Vue2Leaflet\node_modules\@vue\test-utils\dist\vue-test-utils.js:8649:21) + // at shallowMount (C:\code\Vue2Leaflet\node_modules\@vue\test-utils\dist\vue-test-utils.js:8677:10) + // at Object. (C:\code\Vue2Leaflet\tests\unit\components\LControlZoom.spec.js:8:21) + // at Object.asyncJestTest (C:\code\Vue2Leaflet\node_modules\jest-jasmine2\build\jasmineAsyncInstall.js:102:37) + // at resolve (C:\code\Vue2Leaflet\node_modules\jest-jasmine2\build\queueRunner.js:41:12) + // at new Promise () + // at mapper (C:\code\Vue2Leaflet\node_modules\jest-jasmine2\build\queueRunner.js:26:19) + // at promise.then (C:\code\Vue2Leaflet\node_modules\jest-jasmine2\build\queueRunner.js:71:41) + // at + // at process._tickCallback (internal/process/next_tick.js:189:7) + const wrapper = shallowMount(LControlZoom, { + localVue: createLocalVue(), + propsData: { + zoomInText: 'BIGGER' + }, + sync: false + }); + + expect(wrapper.html()).toContain('BIGGER'); + }); + + test('it can be mounted with LMap as its parent', () => { + // This test fails for the same reason as above, so presumably it doesn't make + // the parentComponent a parent of the mounted component until after it has + // tried to mount the component, which in this instance fails. + const wrapper = shallowMount(LControlZoom, { + localVue: createLocalVue(), + parentComponent: LMap, + propsData: { + zoomInText: 'BIGGER' + }, + sync: false + }); + + expect(wrapper.html()).toContain('BIGGER'); + }); + + test.only('it can be mounted inside an LMap slot', async () => { + // This test passes, but the method of changing props on the component under + // test by settings propsData on the options of the first element of the default + // slot of the mounted component's vm seems both tedious and brittle. + const localVue = createLocalVue(); + const mapWrapper = shallowMount(LMap, { + localVue, + slots: { + default: LControlZoom + }, + sync: false + }); + + // Manually set the propsData of the component inside the default slot of the map. + mapWrapper.vm.$slots.default[0].componentOptions.propsData.zoomInText = 'BIGGER'; + + // Allow the change in props to change in the rendered output. + await mapWrapper.vm.$nextTick(); + + expect(mapWrapper.html()).toContain('BIGGER'); + }); + + test.only('it can be rendered inside an LMap slot', async () => { + // This test passes, and allows arbitrary prop values to be set on component + // creation by using a render function to delay creation of the component under + // test until after the map to contain it has been mounted. + const componentWrapper = { + render (h) { + return h(LControlZoom, { props: { zoomInText: 'BIGGER' } }); + } + }; + const wrapper = shallowMount(LMap, { + localVue: createLocalVue(), + slots: { + default: componentWrapper + }, + sync: false + }); + + // Allow the props passed to the render function to become active. + await wrapper.vm.$nextTick(); + + expect(wrapper.html()).toContain('BIGGER'); + }); +}); From de8625a33b5e0b08fd27ed3e98c039710d3cca96 Mon Sep 17 00:00:00 2001 From: Michael Underwood Date: Sun, 7 Apr 2019 18:37:14 -0600 Subject: [PATCH 05/26] Create test helper for wrapping in LMaps. Test LControlZoom props. --- tests/test-helpers.js | 28 +++++ tests/unit/components/LControlZoom.spec.js | 121 +++------------------ 2 files changed, 45 insertions(+), 104 deletions(-) create mode 100644 tests/test-helpers.js diff --git a/tests/test-helpers.js b/tests/test-helpers.js new file mode 100644 index 00000000..7d2d56a4 --- /dev/null +++ b/tests/test-helpers.js @@ -0,0 +1,28 @@ +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import LMap from '@/components/LMap.vue'; + +const localVue = createLocalVue(); + +export async function wrapInMap (LeafletComponent, props) { + const componentWrapper = { + render (h) { + return h(LeafletComponent, { props }); + } + }; + const wrapper = shallowMount(LMap, { + localVue, + slots: { + default: componentWrapper + }, + sync: false // avoid warning, see + // Removing sync mode #1137 https://github.com/vuejs/vue-test-utils/issues/1137 + }); + // Allow the props passed to the render function to be employed. + await wrapper.vm.$nextTick(); + + return wrapper.find(LeafletComponent); +}; + +export function see (component, text) { + expect(component.html()).toContain(text); +} diff --git a/tests/unit/components/LControlZoom.spec.js b/tests/unit/components/LControlZoom.spec.js index 7b0e7d64..33b715e9 100644 --- a/tests/unit/components/LControlZoom.spec.js +++ b/tests/unit/components/LControlZoom.spec.js @@ -1,116 +1,29 @@ -import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { wrapInMap, see } from '../../test-helpers'; import LControlZoom from '@/components/LControlZoom.vue'; -import LMap from '@/components/LMap.vue'; describe('component: LControlZoom', () => { - test('it can be mounted on its own', () => { - // This test fails because the component cannot be mounted without having a - // map object as its parent. - // Output: - // console.error node_modules/vue/dist/vue.runtime.common.dev.js:621 - // [Vue warn]: Error in mounted hook: "TypeError: Cannot read property '_zoom' of undefined" - // - // found in - // - // ---> - // - // - // console.error node_modules/vue/dist/vue.runtime.common.dev.js:1883 - // TypeError: Cannot read property '_zoom' of undefined - // at NewClass._updateDisabled (C:\code\Vue2Leaflet\node_modules\leaflet\src\control\Control.Zoom.js:109:29) - // at NewClass.onAdd (C:\code\Vue2Leaflet\node_modules\leaflet\src\control\Control.Zoom.js:48:8) - // at NewClass.addTo (C:\code\Vue2Leaflet\node_modules\leaflet\src\control\Control.js:70:42) - // at VueComponent.mounted (C:\code\Vue2Leaflet\src\components\LControlZoom.vue:377:20) - // at invokeWithErrorHandling (C:\code\Vue2Leaflet\node_modules\vue\dist\vue.runtime.common.dev.js:1850:57) - // at callHook (C:\code\Vue2Leaflet\node_modules\vue\dist\vue.runtime.common.dev.js:4178:7) - // at Object.insert (C:\code\Vue2Leaflet\node_modules\vue\dist\vue.runtime.common.dev.js:3126:7) - // at invokeInsertHook (C:\code\Vue2Leaflet\node_modules\vue\dist\vue.runtime.common.dev.js:6287:28) - // at VueComponent.patch [as __patch__] (C:\code\Vue2Leaflet\node_modules\vue\dist\vue.runtime.common.dev.js:6504:5) - // at VueComponent.Vue._update (C:\code\Vue2Leaflet\node_modules\vue\dist\vue.runtime.common.dev.js:3904:19) - // at VueComponent.updateComponent (C:\code\Vue2Leaflet\node_modules\vue\dist\vue.runtime.common.dev.js:4025:10) - // at Watcher.get (C:\code\Vue2Leaflet\node_modules\vue\dist\vue.runtime.common.dev.js:4426:25) - // at new Watcher (C:\code\Vue2Leaflet\node_modules\vue\dist\vue.runtime.common.dev.js:4415:12) - // at mountComponent (C:\code\Vue2Leaflet\node_modules\vue\dist\vue.runtime.common.dev.js:4032:3) - // at VueComponent.Object..Vue.$mount (C:\code\Vue2Leaflet\node_modules\vue\dist\vue.runtime.common.dev.js:8350:10) - // at mount (C:\code\Vue2Leaflet\node_modules\@vue\test-utils\dist\vue-test-utils.js:8649:21) - // at shallowMount (C:\code\Vue2Leaflet\node_modules\@vue\test-utils\dist\vue-test-utils.js:8677:10) - // at Object. (C:\code\Vue2Leaflet\tests\unit\components\LControlZoom.spec.js:8:21) - // at Object.asyncJestTest (C:\code\Vue2Leaflet\node_modules\jest-jasmine2\build\jasmineAsyncInstall.js:102:37) - // at resolve (C:\code\Vue2Leaflet\node_modules\jest-jasmine2\build\queueRunner.js:41:12) - // at new Promise () - // at mapper (C:\code\Vue2Leaflet\node_modules\jest-jasmine2\build\queueRunner.js:26:19) - // at promise.then (C:\code\Vue2Leaflet\node_modules\jest-jasmine2\build\queueRunner.js:71:41) - // at - // at process._tickCallback (internal/process/next_tick.js:189:7) - const wrapper = shallowMount(LControlZoom, { - localVue: createLocalVue(), - propsData: { - zoomInText: 'BIGGER' - }, - sync: false - }); - - expect(wrapper.html()).toContain('BIGGER'); + test('it includes zoomInText in resulting html', async () => { + const wrapper = await wrapInMap(LControlZoom, { zoomInText: 'BIGGER' }); + see(wrapper, 'BIGGER'); }); - test('it can be mounted with LMap as its parent', () => { - // This test fails for the same reason as above, so presumably it doesn't make - // the parentComponent a parent of the mounted component until after it has - // tried to mount the component, which in this instance fails. - const wrapper = shallowMount(LControlZoom, { - localVue: createLocalVue(), - parentComponent: LMap, - propsData: { - zoomInText: 'BIGGER' - }, - sync: false - }); - - expect(wrapper.html()).toContain('BIGGER'); + test('it includes zoomOutText in resulting html', async () => { + const wrapper = await wrapInMap(LControlZoom, { zoomOutText: 'smaller' }); + see(wrapper, 'smaller'); }); - test.only('it can be mounted inside an LMap slot', async () => { - // This test passes, but the method of changing props on the component under - // test by settings propsData on the options of the first element of the default - // slot of the mounted component's vm seems both tedious and brittle. - const localVue = createLocalVue(); - const mapWrapper = shallowMount(LMap, { - localVue, - slots: { - default: LControlZoom - }, - sync: false - }); - - // Manually set the propsData of the component inside the default slot of the map. - mapWrapper.vm.$slots.default[0].componentOptions.propsData.zoomInText = 'BIGGER'; - - // Allow the change in props to change in the rendered output. - await mapWrapper.vm.$nextTick(); - - expect(mapWrapper.html()).toContain('BIGGER'); + test('it includes zoomInTitle in resulting html', async () => { + const wrapper = await wrapInMap(LControlZoom, { zoomInTitle: 'zoom in more' }); + see(wrapper, 'zoom in more'); }); - test.only('it can be rendered inside an LMap slot', async () => { - // This test passes, and allows arbitrary prop values to be set on component - // creation by using a render function to delay creation of the component under - // test until after the map to contain it has been mounted. - const componentWrapper = { - render (h) { - return h(LControlZoom, { props: { zoomInText: 'BIGGER' } }); - } - }; - const wrapper = shallowMount(LMap, { - localVue: createLocalVue(), - slots: { - default: componentWrapper - }, - sync: false - }); - - // Allow the props passed to the render function to become active. - await wrapper.vm.$nextTick(); + test('it includes zoomOutTitle in resulting html', async () => { + const wrapper = await wrapInMap(LControlZoom, { zoomOutTitle: 'zoom out a bit' }); + see(wrapper, 'zoom out a bit'); + }); - expect(wrapper.html()).toContain('BIGGER'); + test('it accepts and uses the control position option', async () => { + const wrapper = await wrapInMap(LControlZoom, { position: 'not-the-default' }); + expect(wrapper.props('position')).toBe('not-the-default'); }); }); From ef68c30409e02a540aa77ce6675435d794c6fe9e Mon Sep 17 00:00:00 2001 From: Michael Underwood Date: Mon, 22 Apr 2019 10:38:16 -0600 Subject: [PATCH 06/26] Add jest module mapper for tests directory --- jest.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/jest.config.js b/jest.config.js index 3443155c..32b81ae3 100644 --- a/jest.config.js +++ b/jest.config.js @@ -12,6 +12,7 @@ module.exports = { '^.+\\.jsx?$': 'babel-jest' }, moduleNameMapper: { + '^@/tests/(.*)$': '/tests/$1', '^@/(.*)$': '/src/$1' }, snapshotSerializers: ['jest-serializer-vue'], From eb785e9827999573d4b1d7dd72f365574770c63d Mon Sep 17 00:00:00 2001 From: Michael Underwood Date: Mon, 22 Apr 2019 10:51:53 -0600 Subject: [PATCH 07/26] Return both component and map wrappers when mounting --- tests/test-helpers.js | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/tests/test-helpers.js b/tests/test-helpers.js index 7d2d56a4..abfbd6ce 100644 --- a/tests/test-helpers.js +++ b/tests/test-helpers.js @@ -3,25 +3,37 @@ import LMap from '@/components/LMap.vue'; const localVue = createLocalVue(); -export async function wrapInMap (LeafletComponent, props) { - const componentWrapper = { - render (h) { - return h(LeafletComponent, { props }); +export function getWrapperWithMap (lComponent, propsData, mountOptions) { + const mapWrapper = shallowMount(LMap, { + localVue + }); + + const componentCreated = lComponent.created; + const componentToMount = { + ...lComponent, + created () { + // Ensure existing created hook still runs, if it exists. + if (typeof componentCreated === 'function') { + componentCreated.bind(this)(); + } + // trick from here https://github.com/vuejs/vue-test-utils/issues/560#issuecomment-461865315 + this.$parent = mapWrapper.vm; } }; - const wrapper = shallowMount(LMap, { + + const wrapper = shallowMount(componentToMount, { localVue, - slots: { - default: componentWrapper - }, - sync: false // avoid warning, see + propsData, + sync: false, // avoid warning, see // Removing sync mode #1137 https://github.com/vuejs/vue-test-utils/issues/1137 + ...mountOptions }); - // Allow the props passed to the render function to be employed. - await wrapper.vm.$nextTick(); - return wrapper.find(LeafletComponent); -}; + return { + wrapper, + mapWrapper + }; +} export function see (component, text) { expect(component.html()).toContain(text); From 4fc49bcab6cd53d23aad508b94bd039dcb967a23 Mon Sep 17 00:00:00 2001 From: Michael Underwood Date: Mon, 22 Apr 2019 11:10:16 -0600 Subject: [PATCH 08/26] Finish batch of passing LControlZoom tests --- tests/unit/components/LControlZoom.spec.js | 44 ++++++++++++++-------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/tests/unit/components/LControlZoom.spec.js b/tests/unit/components/LControlZoom.spec.js index 33b715e9..b48eef5d 100644 --- a/tests/unit/components/LControlZoom.spec.js +++ b/tests/unit/components/LControlZoom.spec.js @@ -1,29 +1,41 @@ -import { wrapInMap, see } from '../../test-helpers'; +import { getWrapperWithMap, see } from '@/tests/test-helpers'; import LControlZoom from '@/components/LControlZoom.vue'; describe('component: LControlZoom', () => { - test('it includes zoomInText in resulting html', async () => { - const wrapper = await wrapInMap(LControlZoom, { zoomInText: 'BIGGER' }); - see(wrapper, 'BIGGER'); + test('it has a mapObject', () => { + const { wrapper } = getWrapperWithMap(LControlZoom); + expect(wrapper.vm.mapObject).toBeDefined(); }); - test('it includes zoomOutText in resulting html', async () => { - const wrapper = await wrapInMap(LControlZoom, { zoomOutText: 'smaller' }); - see(wrapper, 'smaller'); + test('it accepts and uses the control position option', () => { + const { wrapper } = getWrapperWithMap(LControlZoom, { position: 'bottomleft' }); + expect(wrapper.vm.mapObject.getPosition()).toBe('bottomleft'); }); - test('it includes zoomInTitle in resulting html', async () => { - const wrapper = await wrapInMap(LControlZoom, { zoomInTitle: 'zoom in more' }); - see(wrapper, 'zoom in more'); + test('it updates the mapObject when position prop is changed', async () => { + const { wrapper } = getWrapperWithMap(LControlZoom, { position: 'bottomleft' }); + wrapper.setProps({ position: 'topright' }); + await wrapper.vm.$nextTick(); + expect(wrapper.vm.mapObject.getPosition()).toBe('topright'); }); - test('it includes zoomOutTitle in resulting html', async () => { - const wrapper = await wrapInMap(LControlZoom, { zoomOutTitle: 'zoom out a bit' }); - see(wrapper, 'zoom out a bit'); + test('it includes zoomInText in rendered html', async () => { + const { mapWrapper } = getWrapperWithMap(LControlZoom, { zoomInText: 'BIGGER' }); + see(mapWrapper, 'BIGGER'); }); - test('it accepts and uses the control position option', async () => { - const wrapper = await wrapInMap(LControlZoom, { position: 'not-the-default' }); - expect(wrapper.props('position')).toBe('not-the-default'); + test('it includes zoomOutText in rendered html', () => { + const { mapWrapper } = getWrapperWithMap(LControlZoom, { zoomOutText: 'smaller' }); + see(mapWrapper, 'smaller'); + }); + + test('it includes zoomInTitle in rendered html', () => { + const { mapWrapper } = getWrapperWithMap(LControlZoom, { zoomInTitle: 'zoom in more' }); + see(mapWrapper, 'zoom in more'); + }); + + test('it includes zoomOutTitle in rendered html', () => { + const { mapWrapper } = getWrapperWithMap(LControlZoom, { zoomOutTitle: 'zoom out a bit' }); + see(mapWrapper, 'zoom out a bit'); }); }); From d9e176ce4ed1c3f770cbdad377dec77c4a0bb399 Mon Sep 17 00:00:00 2001 From: Michael Underwood Date: Mon, 22 Apr 2019 11:51:44 -0600 Subject: [PATCH 09/26] Move LMap spec to component test dir. Use test helpers. --- tests/test-helpers.js | 13 +++++++--- tests/unit/{ => components}/LMap.spec.js | 33 +++++++----------------- 2 files changed, 20 insertions(+), 26 deletions(-) rename tests/unit/{ => components}/LMap.spec.js (86%) diff --git a/tests/test-helpers.js b/tests/test-helpers.js index abfbd6ce..fe1a1d71 100644 --- a/tests/test-helpers.js +++ b/tests/test-helpers.js @@ -3,10 +3,17 @@ import LMap from '@/components/LMap.vue'; const localVue = createLocalVue(); -export function getWrapperWithMap (lComponent, propsData, mountOptions) { - const mapWrapper = shallowMount(LMap, { - localVue +export function getMapWrapper (propsData) { + return shallowMount(LMap, { + localVue, + propsData, + sync: false // avoid warning, see + // Removing sync mode #1137 https://github.com/vuejs/vue-test-utils/issues/1137 }); +} + +export function getWrapperWithMap (lComponent, propsData, mountOptions) { + const mapWrapper = getMapWrapper(); const componentCreated = lComponent.created; const componentToMount = { diff --git a/tests/unit/LMap.spec.js b/tests/unit/components/LMap.spec.js similarity index 86% rename from tests/unit/LMap.spec.js rename to tests/unit/components/LMap.spec.js index 21b1800d..7959160d 100644 --- a/tests/unit/LMap.spec.js +++ b/tests/unit/components/LMap.spec.js @@ -1,23 +1,10 @@ -import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { getMapWrapper } from '@/tests/test-helpers'; import L from 'leaflet'; -import LMap from '@/components/LMap.vue'; - -const localVue = createLocalVue(); - -function getWrapper (propsData) { - const wrapper = shallowMount(LMap, { - localVue, - propsData, - sync: false // avoid warning, see - // Removing sync mode #1137 https://github.com/vuejs/vue-test-utils/issues/1137 - }); - return wrapper; -} describe('LMap.vue', () => { test('LMap.vue change prop center', async () => { const mockPanTo = jest.fn(); - const wrapper = getWrapper(); + const wrapper = getMapWrapper(); expect(wrapper.exists()).toBe(true); wrapper.vm.mapObject.panTo = mockPanTo; const newCenter = L.latLng([1, 1]); @@ -33,7 +20,7 @@ describe('LMap.vue', () => { test('LMap.vue center not change', () => { const mockPanTo = jest.fn(); const center = L.latLng([0, 0]); - const wrapper = getWrapper({ center: center }); + const wrapper = getMapWrapper({ center: center }); expect(wrapper.exists()).toBe(true); wrapper.vm.mapObject.panTo = mockPanTo; wrapper.vm.setCenter(center); @@ -44,7 +31,7 @@ describe('LMap.vue', () => { const center = [0, 0]; const mockPanTo = jest.fn(); - const wrapper = getWrapper({ center: [1, 1] }); + const wrapper = getMapWrapper({ center: [1, 1] }); wrapper.vm.mapObject.panTo = mockPanTo; wrapper.vm.setCenter(center); @@ -61,7 +48,7 @@ describe('LMap.vue', () => { const center2 = L.latLng([2, 2]); const center3 = L.latLng([3, 3]); - const wrapper = getWrapper({ center: initialCenter }); + const wrapper = getMapWrapper({ center: initialCenter }); wrapper.vm.setCenter(center1); wrapper.vm.setCenter(center2); wrapper.vm.setCenter(center3); @@ -76,7 +63,7 @@ describe('LMap.vue', () => { test('LMap.vue initial bounds', () => { const bounds = L.latLngBounds(L.latLng([1, 1]), L.latLng([2, 2])); - const wrapper = getWrapper({ bounds: bounds }); + const wrapper = getMapWrapper({ bounds: bounds }); expect(wrapper.vm.lastSetBounds).toEqual(bounds); }); @@ -85,7 +72,7 @@ describe('LMap.vue', () => { const newBounds = [[4, 4], [5, 5]]; const newBounds2 = L.latLngBounds(L.latLng([10, 10]), L.latLng([20, 20])); const mockFitBounds = jest.fn(); - const wrapper = getWrapper({ bounds: bounds }); + const wrapper = getMapWrapper({ bounds: bounds }); wrapper.vm.mapObject.fitBounds = mockFitBounds; wrapper.setProps({ bounds: newBounds }); await wrapper.vm.$nextTick(); @@ -102,7 +89,7 @@ describe('LMap.vue', () => { const bounds = L.latLngBounds(L.latLng([1, 1]), L.latLng([2, 2])); const sameBounds = L.latLngBounds(L.latLng([1, 1]), L.latLng([2, 2])); const mockFitBounds = jest.fn(); - const wrapper = getWrapper({ bounds: bounds }); + const wrapper = getMapWrapper({ bounds: bounds }); wrapper.vm.mapObject.fitBounds = mockFitBounds; wrapper.setProps({ bounds: sameBounds }); await wrapper.vm.$nextTick(); @@ -120,7 +107,7 @@ describe('LMap.vue', () => { paddingTopLeft: [3, 3] }; const mockFitBounds = jest.fn(); - const wrapper = getWrapper(); + const wrapper = getMapWrapper(); wrapper.vm.mapObject.fitBounds = mockFitBounds; wrapper.setProps({ padding: optionsPadding.padding }); wrapper.setProps({ paddingBottomRight: optionsPadding2.paddingBottomRight }); @@ -140,7 +127,7 @@ describe('LMap.vue', () => { }); test('LMap.vue no-blocking-animations options', async () => { - const wrapper = getWrapper({ + const wrapper = getMapWrapper({ noBlockingAnimations: true }); From c499af10556586adc05dca1098c7016cb0526f8f Mon Sep 17 00:00:00 2001 From: Michael Underwood Date: Mon, 22 Apr 2019 16:14:39 -0600 Subject: [PATCH 10/26] Move LMarker spec into component test directory --- tests/unit/{ => components}/LMarker.spec.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/unit/{ => components}/LMarker.spec.js (100%) diff --git a/tests/unit/LMarker.spec.js b/tests/unit/components/LMarker.spec.js similarity index 100% rename from tests/unit/LMarker.spec.js rename to tests/unit/components/LMarker.spec.js From e58a58d8610fd0c9ebc270acaab74ee2a8aac0dc Mon Sep 17 00:00:00 2001 From: Michael Underwood Date: Mon, 22 Apr 2019 16:16:51 -0600 Subject: [PATCH 11/26] Make test descriptions and styles more uniform --- tests/unit/components/LControlZoom.spec.js | 16 ++--- tests/unit/components/LMap.spec.js | 2 +- tests/unit/components/LMarker.spec.js | 81 +++++++++------------- 3 files changed, 41 insertions(+), 58 deletions(-) diff --git a/tests/unit/components/LControlZoom.spec.js b/tests/unit/components/LControlZoom.spec.js index b48eef5d..2330e69e 100644 --- a/tests/unit/components/LControlZoom.spec.js +++ b/tests/unit/components/LControlZoom.spec.js @@ -1,40 +1,40 @@ import { getWrapperWithMap, see } from '@/tests/test-helpers'; import LControlZoom from '@/components/LControlZoom.vue'; -describe('component: LControlZoom', () => { - test('it has a mapObject', () => { +describe('component: LControlZoom.vue', () => { + test('LControlZoom.vue has a mapObject', () => { const { wrapper } = getWrapperWithMap(LControlZoom); expect(wrapper.vm.mapObject).toBeDefined(); }); - test('it accepts and uses the control position option', () => { + test('LControlZoom.vue accepts and uses the control position option', () => { const { wrapper } = getWrapperWithMap(LControlZoom, { position: 'bottomleft' }); expect(wrapper.vm.mapObject.getPosition()).toBe('bottomleft'); }); - test('it updates the mapObject when position prop is changed', async () => { + test('LControlZoom.vue updates the mapObject when position prop is changed', async () => { const { wrapper } = getWrapperWithMap(LControlZoom, { position: 'bottomleft' }); wrapper.setProps({ position: 'topright' }); await wrapper.vm.$nextTick(); expect(wrapper.vm.mapObject.getPosition()).toBe('topright'); }); - test('it includes zoomInText in rendered html', async () => { + test('LControlZoom.vue includes zoomInText in rendered html', async () => { const { mapWrapper } = getWrapperWithMap(LControlZoom, { zoomInText: 'BIGGER' }); see(mapWrapper, 'BIGGER'); }); - test('it includes zoomOutText in rendered html', () => { + test('LControlZoom.vue includes zoomOutText in rendered html', () => { const { mapWrapper } = getWrapperWithMap(LControlZoom, { zoomOutText: 'smaller' }); see(mapWrapper, 'smaller'); }); - test('it includes zoomInTitle in rendered html', () => { + test('LControlZoom.vue includes zoomInTitle in rendered html', () => { const { mapWrapper } = getWrapperWithMap(LControlZoom, { zoomInTitle: 'zoom in more' }); see(mapWrapper, 'zoom in more'); }); - test('it includes zoomOutTitle in rendered html', () => { + test('LControlZoom.vue includes zoomOutTitle in rendered html', () => { const { mapWrapper } = getWrapperWithMap(LControlZoom, { zoomOutTitle: 'zoom out a bit' }); see(mapWrapper, 'zoom out a bit'); }); diff --git a/tests/unit/components/LMap.spec.js b/tests/unit/components/LMap.spec.js index 7959160d..8af03078 100644 --- a/tests/unit/components/LMap.spec.js +++ b/tests/unit/components/LMap.spec.js @@ -1,7 +1,7 @@ import { getMapWrapper } from '@/tests/test-helpers'; import L from 'leaflet'; -describe('LMap.vue', () => { +describe('component: LMap.vue', () => { test('LMap.vue change prop center', async () => { const mockPanTo = jest.fn(); const wrapper = getMapWrapper(); diff --git a/tests/unit/components/LMarker.spec.js b/tests/unit/components/LMarker.spec.js index 2f4e0058..9e4663a4 100644 --- a/tests/unit/components/LMarker.spec.js +++ b/tests/unit/components/LMarker.spec.js @@ -1,90 +1,73 @@ -import { createLocalVue, mount } from '@vue/test-utils'; -import L from 'leaflet'; +import { getWrapperWithMap } from '@/tests/test-helpers'; import LMarker from '@/components/LMarker.vue'; -import LMap from '@/components/LMap.vue'; +import L from 'leaflet'; -const localVue = createLocalVue(); +describe('component: LMarker.vue', () => { + test('LMarker.vue has a mapObject', () => { + const { wrapper } = getWrapperWithMap(LMarker, { + latLng: [0, 0] + }); -function getWrapperWithMap (component, propsData, mountOptions) { - const mapWrapper = mount(LMap, { - localVue + expect(wrapper.exists()).toBe(true); + expect(wrapper.vm.mapObject).toBeDefined(); }); - const wrapper = mount({ - ...component, - // trick from here https://github.com/vuejs/vue-test-utils/issues/560#issuecomment-461865315 - created () { - this.$parent = mapWrapper.vm; - } - }, { - localVue, - propsData, - sync: false, // avoid warning, see - // Removing sync mode #1137 https://github.com/vuejs/vue-test-utils/issues/1137 - ...mountOptions - }); - return { - wrapper, - mapWrapper - }; -} - -describe('LMarker.vue', () => { - test('LMarker.vue change prop latLng', async () => { + test('LMarker.vue updates the mapObject latLng when props change', async () => { const initLatlng = L.latLng([11, 22]); - const wrapperAndMap = getWrapperWithMap(LMarker, { + const { wrapper } = getWrapperWithMap(LMarker, { latLng: initLatlng }); - const wrapper = wrapperAndMap.wrapper; - expect(wrapper.exists()).toBe(true); - expect(wrapper.vm.mapObject.getLatLng().equals(initLatlng)).toBe(true); + + const markerObject = wrapper.vm.mapObject; + expect(markerObject.getLatLng().equals(initLatlng)).toBe(true); + const newLatLng = L.latLng([1, 1]); wrapper.setProps({ latLng: newLatLng }); await wrapper.vm.$nextTick(); - const curLatLng = wrapper.vm.mapObject.getLatLng(); - expect(curLatLng.equals(newLatLng)).toBe(true); + + expect(markerObject.getLatLng().equals(newLatLng)).toBe(true); }); - test('LMarker.vue default slot text', async () => { + test('LMarker.vue displays text from its default slot', async () => { const markerText = 'Hello from marker!'; - const wrapperAndMap = getWrapperWithMap(LMarker, { + const { wrapper } = getWrapperWithMap(LMarker, { latLng: [0, 0] }, { slots: { default: markerText } }); - const wrapper = wrapperAndMap.wrapper; - const mapWrapper = wrapperAndMap.mapWrapper; - expect(mapWrapper.text()).toContain('Leaflet'); - expect(wrapper.exists()).toBe(true); await wrapper.vm.$nextTick(); + expect(wrapper.text()).toEqual(markerText); }); - test('LMarker.vue draggable change', async () => { - const wrapperAndMap = getWrapperWithMap(LMarker, { - latLng: [0, 0] + test('LMarker.vue "draggable" prop toggles mapObject dragging option', async () => { + const { wrapper } = getWrapperWithMap(LMarker, { + latLng: [0, 0], + draggable: false }); - const wrapper = wrapperAndMap.wrapper; const markerObject = wrapper.vm.mapObject; + expect(markerObject.dragging.enabled()).toBeFalsy(); + wrapper.setProps({ draggable: true }); await wrapper.vm.$nextTick(); + expect(markerObject.dragging.enabled()).toBeTruthy(); + wrapper.setProps({ draggable: false }); await wrapper.vm.$nextTick(); + expect(markerObject.dragging.enabled()).toBeFalsy(); }); - test('LMarker.vue not change prop latLng to null', async () => { + test('LMarker.vue does not change the mapObject latLng value when set to null', async () => { const initLatlng = L.latLng([11, 22]); - const wrapperAndMap = getWrapperWithMap(LMarker, { + const { wrapper } = getWrapperWithMap(LMarker, { latLng: initLatlng }); - const wrapper = wrapperAndMap.wrapper; - expect(wrapper.exists()).toBe(true); - expect(wrapper.vm.mapObject.getLatLng().equals(initLatlng)).toBe(true); + wrapper.setProps({ latLng: null }); await wrapper.vm.$nextTick(); expect(wrapper.vm.mapObject.getLatLng().equals(initLatlng)).toBe(true); From dda44883f365f65512ad82ca73f0c5ee9115b230 Mon Sep 17 00:00:00 2001 From: Michael Underwood Date: Mon, 22 Apr 2019 20:08:03 -0600 Subject: [PATCH 12/26] Reorganise tests to more closely match Leaflet API docs --- tests/unit/components/LControlZoom.spec.js | 36 +++++++++++++--------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/tests/unit/components/LControlZoom.spec.js b/tests/unit/components/LControlZoom.spec.js index 2330e69e..85182c68 100644 --- a/tests/unit/components/LControlZoom.spec.js +++ b/tests/unit/components/LControlZoom.spec.js @@ -7,35 +7,41 @@ describe('component: LControlZoom.vue', () => { expect(wrapper.vm.mapObject).toBeDefined(); }); - test('LControlZoom.vue accepts and uses the control position option', () => { + // Leaflet Control options + + test('LControlZoom.vue accepts and uses the Leaflet position option', () => { const { wrapper } = getWrapperWithMap(LControlZoom, { position: 'bottomleft' }); expect(wrapper.vm.mapObject.getPosition()).toBe('bottomleft'); }); - test('LControlZoom.vue updates the mapObject when position prop is changed', async () => { - const { wrapper } = getWrapperWithMap(LControlZoom, { position: 'bottomleft' }); - wrapper.setProps({ position: 'topright' }); - await wrapper.vm.$nextTick(); - expect(wrapper.vm.mapObject.getPosition()).toBe('topright'); - }); + // Leaflet Control.Zoom options - test('LControlZoom.vue includes zoomInText in rendered html', async () => { + test('LControlZoom.vue includes "zoomInText" in rendered html', async () => { const { mapWrapper } = getWrapperWithMap(LControlZoom, { zoomInText: 'BIGGER' }); see(mapWrapper, 'BIGGER'); }); - test('LControlZoom.vue includes zoomOutText in rendered html', () => { - const { mapWrapper } = getWrapperWithMap(LControlZoom, { zoomOutText: 'smaller' }); - see(mapWrapper, 'smaller'); - }); - - test('LControlZoom.vue includes zoomInTitle in rendered html', () => { + test('LControlZoom.vue includes "zoomInTitle" in rendered html', () => { const { mapWrapper } = getWrapperWithMap(LControlZoom, { zoomInTitle: 'zoom in more' }); see(mapWrapper, 'zoom in more'); }); - test('LControlZoom.vue includes zoomOutTitle in rendered html', () => { + test('LControlZoom.vue includes "zoomOutText" in rendered html', () => { + const { mapWrapper } = getWrapperWithMap(LControlZoom, { zoomOutText: 'smaller' }); + see(mapWrapper, 'smaller'); + }); + + test('LControlZoom.vue includes "zoomOutTitle" in rendered html', () => { const { mapWrapper } = getWrapperWithMap(LControlZoom, { zoomOutTitle: 'zoom out a bit' }); see(mapWrapper, 'zoom out a bit'); }); + + // Leaflet Control set* methods + + test('LControlZoom.vue updates the mapObject when the "position" prop is set', async () => { + const { wrapper } = getWrapperWithMap(LControlZoom, { position: 'bottomleft' }); + wrapper.setProps({ position: 'topright' }); + await wrapper.vm.$nextTick(); + expect(wrapper.vm.mapObject.getPosition()).toBe('topright'); + }); }); From 236128368ac51dfa37592fdcdc4b49cd6581e080 Mon Sep 17 00:00:00 2001 From: Michael Underwood Date: Tue, 23 Apr 2019 14:16:13 -0600 Subject: [PATCH 13/26] Add tests for LControlAttribution and LControlScale --- .../components/LControlAttribution.spec.js | 43 +++++++++++++++++ tests/unit/components/LControlScale.spec.js | 47 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 tests/unit/components/LControlAttribution.spec.js create mode 100644 tests/unit/components/LControlScale.spec.js diff --git a/tests/unit/components/LControlAttribution.spec.js b/tests/unit/components/LControlAttribution.spec.js new file mode 100644 index 00000000..5166bbb2 --- /dev/null +++ b/tests/unit/components/LControlAttribution.spec.js @@ -0,0 +1,43 @@ +import { getWrapperWithMap, see } from '@/tests/test-helpers'; +import LControlAttribution from '@/components/LControlAttribution.vue'; + +describe('component: LControlAttribution.vue', () => { + test('LControlAttribution.vue has a mapObject', () => { + const { wrapper } = getWrapperWithMap(LControlAttribution); + expect(wrapper.vm.mapObject).toBeDefined(); + }); + + // Leaflet Control options + + test('LControlAttribution.vue uses the "position" Leaflet option', () => { + const { wrapper } = getWrapperWithMap(LControlAttribution, { position: 'bottomleft' }); + expect(wrapper.vm.mapObject.getPosition()).toBe('bottomleft'); + }); + + // Leaflet Control.Attribution options + + test('LControlAttribution.vue uses the "prefix" Leaflet option', () => { + const prefix = 'Custom Vue2Leaflet prefix'; + const { mapWrapper } = getWrapperWithMap(LControlAttribution, { prefix }); + see(mapWrapper, prefix); + }); + + // Leaflet Control set* methods + + test('LControlAttribution.vue updates the mapObject when the "position" prop is set', async () => { + const { wrapper } = await getWrapperWithMap(LControlAttribution, { position: 'bottomleft' }); + wrapper.setProps({ position: 'topright' }); + await wrapper.vm.$nextTick(); + expect(wrapper.vm.mapObject.getPosition()).toBe('topright'); + }); + + // Leaflet Control.Attribution set* methods + + test('LControlAttribution.vue updates the mapObject when the "prefix" prop is set', async () => { + const { wrapper, mapWrapper } = await getWrapperWithMap(LControlAttribution); + const prefix = 'something new'; + wrapper.setProps({ prefix }); + await wrapper.vm.$nextTick(); + see(mapWrapper, prefix); + }); +}); diff --git a/tests/unit/components/LControlScale.spec.js b/tests/unit/components/LControlScale.spec.js new file mode 100644 index 00000000..15dea995 --- /dev/null +++ b/tests/unit/components/LControlScale.spec.js @@ -0,0 +1,47 @@ +import { getWrapperWithMap } from '@/tests/test-helpers'; +import LControlScale from '@/components/LControlScale.vue'; + +describe('component: LControlScale.vue', () => { + test('LControlScale.vue has a mapObject', () => { + const { wrapper } = getWrapperWithMap(LControlScale); + expect(wrapper.vm.mapObject).toBeDefined(); + }); + + // Leaflet Control options + + test('LControlScale.vue uses the "position" Leaflet option', () => { + const { wrapper } = getWrapperWithMap(LControlScale, { position: 'bottomleft' }); + expect(wrapper.vm.mapObject.getPosition()).toBe('bottomleft'); + }); + + // Leaflet Control.Scale options + + test('LControlScale.vue uses the "maxWidth" Leaflet option', () => { + const { wrapper } = getWrapperWithMap(LControlScale, { maxWidth: 37 }); + expect(wrapper.vm.mapObject.options.maxWidth).toBe(37); + }); + + test('LControlScale.vue uses the "metric" Leaflet option', () => { + const { wrapper } = getWrapperWithMap(LControlScale, { metric: false }); + expect(wrapper.vm.mapObject.options.metric).toBeFalsy(); + }); + + test('LControlScale.vue uses the "imperial" Leaflet option', () => { + const { wrapper } = getWrapperWithMap(LControlScale, { imperial: false }); + expect(wrapper.vm.mapObject.options.imperial).toBeFalsy(); + }); + + test('LControlScale.vue uses the "updateWhenIdle" Leaflet option', () => { + const { wrapper } = getWrapperWithMap(LControlScale, { updateWhenIdle: true }); + expect(wrapper.vm.mapObject.options.updateWhenIdle).toBeTruthy(); + }); + + // Leaflet Control set* methods + + test('LControlScale.vue updates the mapObject when the "position" prop is set', async () => { + const { wrapper } = getWrapperWithMap(LControlScale, { position: 'bottomleft' }); + wrapper.setProps({ position: 'topright' }); + await wrapper.vm.$nextTick(); + expect(wrapper.vm.mapObject.getPosition()).toBe('topright'); + }); +}); From 6c3ef4a8da79a0f001b0f185450b484c14d14923 Mon Sep 17 00:00:00 2001 From: Michael Underwood Date: Tue, 23 Apr 2019 15:26:30 -0600 Subject: [PATCH 14/26] Update vue-test-utils requirement to beta 29 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5b84474a..996e8139 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "@babel/plugin-transform-runtime": "^7.3.4", "@babel/preset-env": "^7.3.1", "@babel/runtime": "^7.3.4", - "@vue/test-utils": "^1.0.0-beta.20", + "@vue/test-utils": "^1.0.0-beta.29", "babel-eslint": "^8.2.6", "babel-jest": "^24.1.0", "coveralls": "^3.0.3", From ae0c673aa9de2f348ded783d1e252463bfc3af13 Mon Sep 17 00:00:00 2001 From: Michael Underwood Date: Sat, 27 Apr 2019 12:09:55 -0600 Subject: [PATCH 15/26] Add tests for LControlLayers options Testing the addLayer and removeLayer methods is more involved, as this requires interaction with the parent map object and additional child layers of it. These tests are still TODO. --- tests/unit/components/LControlLayers.spec.js | 63 ++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 tests/unit/components/LControlLayers.spec.js diff --git a/tests/unit/components/LControlLayers.spec.js b/tests/unit/components/LControlLayers.spec.js new file mode 100644 index 00000000..aab15740 --- /dev/null +++ b/tests/unit/components/LControlLayers.spec.js @@ -0,0 +1,63 @@ +import { getWrapperWithMap } from '@/tests/test-helpers'; +import LControlLayers from '@/components/LControlLayers.vue'; + +describe('component: LControlLayers.vue', () => { + test('LControlLayers.vue has a mapObject', () => { + const { wrapper } = getWrapperWithMap(LControlLayers); + expect(wrapper.vm.mapObject).toBeDefined(); + }); + + // Leaflet Control options + + test('LControlLayers.vue uses the "position" Leaflet option', () => { + const { wrapper } = getWrapperWithMap(LControlLayers, { position: 'bottomleft' }); + expect(wrapper.vm.mapObject.getPosition()).toBe('bottomleft'); + }); + + // Leaflet Control.Layers options + + test('LControlLayers.vue uses the "collapsed" Leaflet option', () => { + const trueWrapper = getWrapperWithMap(LControlLayers, { collapsed: true }).wrapper; + const falseWrapper = getWrapperWithMap(LControlLayers, { collapsed: false }).wrapper; + expect(trueWrapper.vm.mapObject.options.collapsed).toBeTruthy(); + expect(falseWrapper.vm.mapObject.options.collapsed).toBeFalsy(); + }); + + test('LControlLayers.vue uses the "autoZIndex" Leaflet option', () => { + const trueWrapper = getWrapperWithMap(LControlLayers, { autoZIndex: true }).wrapper; + const falseWrapper = getWrapperWithMap(LControlLayers, { autoZIndex: false }).wrapper; + expect(trueWrapper.vm.mapObject.options.autoZIndex).toBeTruthy(); + expect(falseWrapper.vm.mapObject.options.autoZIndex).toBeFalsy(); + }); + + test('LControlLayers.vue uses the "hideSingleBase" Leaflet option', () => { + const trueWrapper = getWrapperWithMap(LControlLayers, { hideSingleBase: true }).wrapper; + const falseWrapper = getWrapperWithMap(LControlLayers, { hideSingleBase: false }).wrapper; + expect(trueWrapper.vm.mapObject.options.hideSingleBase).toBeTruthy(); + expect(falseWrapper.vm.mapObject.options.hideSingleBase).toBeFalsy(); + }); + + test('LControlLayers.vue uses the "sortLayers" Leaflet option', () => { + const trueWrapper = getWrapperWithMap(LControlLayers, { sortLayers: true }).wrapper; + const falseWrapper = getWrapperWithMap(LControlLayers, { sortLayers: false }).wrapper; + expect(trueWrapper.vm.mapObject.options.sortLayers).toBeTruthy(); + expect(falseWrapper.vm.mapObject.options.sortLayers).toBeFalsy(); + }); + + test('LControlLayers.vue uses the "sortFunction" Leaflet option', () => { + const sortFunction = jest.fn(); + const { wrapper } = getWrapperWithMap(LControlLayers, { sortFunction }); + expect(wrapper.vm.mapObject.options.sortFunction).toBe(sortFunction); + }); + + // Leaflet Control set* methods + + test('LControlLayers.vue updates the mapObject when the "position" prop is set', async () => { + const { wrapper } = await getWrapperWithMap(LControlLayers, { position: 'bottomleft' }); + wrapper.setProps({ position: 'topright' }); + await wrapper.vm.$nextTick(); + expect(wrapper.vm.mapObject.getPosition()).toBe('topright'); + }); + + // TODO: Test adding and removing layers +}); From e504bfb80ad7fbcb7044dde829ddd0029d1a49ab Mon Sep 17 00:00:00 2001 From: Michael Underwood Date: Wed, 29 May 2019 11:45:01 -0600 Subject: [PATCH 16/26] Add test setup to make Leaflet think JSDOM supports SVG --- jest.config.js | 3 +++ tests/setup.js | 13 +++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 tests/setup.js diff --git a/jest.config.js b/jest.config.js index 32b81ae3..86d3666d 100644 --- a/jest.config.js +++ b/jest.config.js @@ -5,6 +5,9 @@ module.exports = { coverageDirectory: 'coverage', restoreMocks: true, moduleFileExtensions: ['js', 'jsx', 'json', 'vue'], + setupFiles: [ + '/tests/setup.js' + ], transform: { '^.+\\.vue$': 'vue-jest', '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': diff --git a/tests/setup.js b/tests/setup.js new file mode 100644 index 00000000..bc126474 --- /dev/null +++ b/tests/setup.js @@ -0,0 +1,13 @@ +// Perform any pre-test setup required. + +// Trick Leaflet into thinking that JSDOM has SVG support. +// See: https://stackoverflow.com/a/54384719/607408 +const createElementNSOrig = global.document.createElementNS; +global.document.createElementNS = function (uri, name) { + const element = createElementNSOrig.apply(this, arguments); + if (uri === 'http://www.w3.org/2000/svg' && name === 'svg') { + element.createSVGRect = function () {}; + } + + return element; +}; From 8a4862c0129e3132c71d11bab159bbe89b44789e Mon Sep 17 00:00:00 2001 From: Michael Underwood Date: Wed, 29 May 2019 11:55:45 -0600 Subject: [PATCH 17/26] Prevent "error reading property 'lng' of null" on default --- src/components/LCircle.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/LCircle.vue b/src/components/LCircle.vue index 35cd7fcd..6178ebe4 100644 --- a/src/components/LCircle.vue +++ b/src/components/LCircle.vue @@ -15,7 +15,7 @@ export default { props: { latLng: { type: [Object, Array], - default: () => [] + default: () => [0, 0] } }, data () { From 5fc96e512fdf63cf0ef24267ee03e7c6ab9478e1 Mon Sep 17 00:00:00 2001 From: Michael Underwood Date: Wed, 29 May 2019 12:25:17 -0600 Subject: [PATCH 18/26] Add blank lines between arrange/act/assert steps --- .../components/LControlAttribution.spec.js | 5 +++++ tests/unit/components/LControlLayers.spec.js | 8 ++++++++ tests/unit/components/LControlScale.spec.js | 7 +++++++ tests/unit/components/LControlZoom.spec.js | 7 +++++++ tests/unit/components/LMap.spec.js | 18 ++++++++++++++++-- 5 files changed, 43 insertions(+), 2 deletions(-) diff --git a/tests/unit/components/LControlAttribution.spec.js b/tests/unit/components/LControlAttribution.spec.js index 5166bbb2..363f340f 100644 --- a/tests/unit/components/LControlAttribution.spec.js +++ b/tests/unit/components/LControlAttribution.spec.js @@ -4,6 +4,7 @@ import LControlAttribution from '@/components/LControlAttribution.vue'; describe('component: LControlAttribution.vue', () => { test('LControlAttribution.vue has a mapObject', () => { const { wrapper } = getWrapperWithMap(LControlAttribution); + expect(wrapper.vm.mapObject).toBeDefined(); }); @@ -11,6 +12,7 @@ describe('component: LControlAttribution.vue', () => { test('LControlAttribution.vue uses the "position" Leaflet option', () => { const { wrapper } = getWrapperWithMap(LControlAttribution, { position: 'bottomleft' }); + expect(wrapper.vm.mapObject.getPosition()).toBe('bottomleft'); }); @@ -19,6 +21,7 @@ describe('component: LControlAttribution.vue', () => { test('LControlAttribution.vue uses the "prefix" Leaflet option', () => { const prefix = 'Custom Vue2Leaflet prefix'; const { mapWrapper } = getWrapperWithMap(LControlAttribution, { prefix }); + see(mapWrapper, prefix); }); @@ -28,6 +31,7 @@ describe('component: LControlAttribution.vue', () => { const { wrapper } = await getWrapperWithMap(LControlAttribution, { position: 'bottomleft' }); wrapper.setProps({ position: 'topright' }); await wrapper.vm.$nextTick(); + expect(wrapper.vm.mapObject.getPosition()).toBe('topright'); }); @@ -38,6 +42,7 @@ describe('component: LControlAttribution.vue', () => { const prefix = 'something new'; wrapper.setProps({ prefix }); await wrapper.vm.$nextTick(); + see(mapWrapper, prefix); }); }); diff --git a/tests/unit/components/LControlLayers.spec.js b/tests/unit/components/LControlLayers.spec.js index aab15740..db91595c 100644 --- a/tests/unit/components/LControlLayers.spec.js +++ b/tests/unit/components/LControlLayers.spec.js @@ -4,6 +4,7 @@ import LControlLayers from '@/components/LControlLayers.vue'; describe('component: LControlLayers.vue', () => { test('LControlLayers.vue has a mapObject', () => { const { wrapper } = getWrapperWithMap(LControlLayers); + expect(wrapper.vm.mapObject).toBeDefined(); }); @@ -11,6 +12,7 @@ describe('component: LControlLayers.vue', () => { test('LControlLayers.vue uses the "position" Leaflet option', () => { const { wrapper } = getWrapperWithMap(LControlLayers, { position: 'bottomleft' }); + expect(wrapper.vm.mapObject.getPosition()).toBe('bottomleft'); }); @@ -19,6 +21,7 @@ describe('component: LControlLayers.vue', () => { test('LControlLayers.vue uses the "collapsed" Leaflet option', () => { const trueWrapper = getWrapperWithMap(LControlLayers, { collapsed: true }).wrapper; const falseWrapper = getWrapperWithMap(LControlLayers, { collapsed: false }).wrapper; + expect(trueWrapper.vm.mapObject.options.collapsed).toBeTruthy(); expect(falseWrapper.vm.mapObject.options.collapsed).toBeFalsy(); }); @@ -26,6 +29,7 @@ describe('component: LControlLayers.vue', () => { test('LControlLayers.vue uses the "autoZIndex" Leaflet option', () => { const trueWrapper = getWrapperWithMap(LControlLayers, { autoZIndex: true }).wrapper; const falseWrapper = getWrapperWithMap(LControlLayers, { autoZIndex: false }).wrapper; + expect(trueWrapper.vm.mapObject.options.autoZIndex).toBeTruthy(); expect(falseWrapper.vm.mapObject.options.autoZIndex).toBeFalsy(); }); @@ -33,6 +37,7 @@ describe('component: LControlLayers.vue', () => { test('LControlLayers.vue uses the "hideSingleBase" Leaflet option', () => { const trueWrapper = getWrapperWithMap(LControlLayers, { hideSingleBase: true }).wrapper; const falseWrapper = getWrapperWithMap(LControlLayers, { hideSingleBase: false }).wrapper; + expect(trueWrapper.vm.mapObject.options.hideSingleBase).toBeTruthy(); expect(falseWrapper.vm.mapObject.options.hideSingleBase).toBeFalsy(); }); @@ -40,6 +45,7 @@ describe('component: LControlLayers.vue', () => { test('LControlLayers.vue uses the "sortLayers" Leaflet option', () => { const trueWrapper = getWrapperWithMap(LControlLayers, { sortLayers: true }).wrapper; const falseWrapper = getWrapperWithMap(LControlLayers, { sortLayers: false }).wrapper; + expect(trueWrapper.vm.mapObject.options.sortLayers).toBeTruthy(); expect(falseWrapper.vm.mapObject.options.sortLayers).toBeFalsy(); }); @@ -47,6 +53,7 @@ describe('component: LControlLayers.vue', () => { test('LControlLayers.vue uses the "sortFunction" Leaflet option', () => { const sortFunction = jest.fn(); const { wrapper } = getWrapperWithMap(LControlLayers, { sortFunction }); + expect(wrapper.vm.mapObject.options.sortFunction).toBe(sortFunction); }); @@ -56,6 +63,7 @@ describe('component: LControlLayers.vue', () => { const { wrapper } = await getWrapperWithMap(LControlLayers, { position: 'bottomleft' }); wrapper.setProps({ position: 'topright' }); await wrapper.vm.$nextTick(); + expect(wrapper.vm.mapObject.getPosition()).toBe('topright'); }); diff --git a/tests/unit/components/LControlScale.spec.js b/tests/unit/components/LControlScale.spec.js index 15dea995..a6995605 100644 --- a/tests/unit/components/LControlScale.spec.js +++ b/tests/unit/components/LControlScale.spec.js @@ -4,6 +4,7 @@ import LControlScale from '@/components/LControlScale.vue'; describe('component: LControlScale.vue', () => { test('LControlScale.vue has a mapObject', () => { const { wrapper } = getWrapperWithMap(LControlScale); + expect(wrapper.vm.mapObject).toBeDefined(); }); @@ -11,6 +12,7 @@ describe('component: LControlScale.vue', () => { test('LControlScale.vue uses the "position" Leaflet option', () => { const { wrapper } = getWrapperWithMap(LControlScale, { position: 'bottomleft' }); + expect(wrapper.vm.mapObject.getPosition()).toBe('bottomleft'); }); @@ -18,21 +20,25 @@ describe('component: LControlScale.vue', () => { test('LControlScale.vue uses the "maxWidth" Leaflet option', () => { const { wrapper } = getWrapperWithMap(LControlScale, { maxWidth: 37 }); + expect(wrapper.vm.mapObject.options.maxWidth).toBe(37); }); test('LControlScale.vue uses the "metric" Leaflet option', () => { const { wrapper } = getWrapperWithMap(LControlScale, { metric: false }); + expect(wrapper.vm.mapObject.options.metric).toBeFalsy(); }); test('LControlScale.vue uses the "imperial" Leaflet option', () => { const { wrapper } = getWrapperWithMap(LControlScale, { imperial: false }); + expect(wrapper.vm.mapObject.options.imperial).toBeFalsy(); }); test('LControlScale.vue uses the "updateWhenIdle" Leaflet option', () => { const { wrapper } = getWrapperWithMap(LControlScale, { updateWhenIdle: true }); + expect(wrapper.vm.mapObject.options.updateWhenIdle).toBeTruthy(); }); @@ -42,6 +48,7 @@ describe('component: LControlScale.vue', () => { const { wrapper } = getWrapperWithMap(LControlScale, { position: 'bottomleft' }); wrapper.setProps({ position: 'topright' }); await wrapper.vm.$nextTick(); + expect(wrapper.vm.mapObject.getPosition()).toBe('topright'); }); }); diff --git a/tests/unit/components/LControlZoom.spec.js b/tests/unit/components/LControlZoom.spec.js index 85182c68..6e4fa6bf 100644 --- a/tests/unit/components/LControlZoom.spec.js +++ b/tests/unit/components/LControlZoom.spec.js @@ -4,6 +4,7 @@ import LControlZoom from '@/components/LControlZoom.vue'; describe('component: LControlZoom.vue', () => { test('LControlZoom.vue has a mapObject', () => { const { wrapper } = getWrapperWithMap(LControlZoom); + expect(wrapper.vm.mapObject).toBeDefined(); }); @@ -11,6 +12,7 @@ describe('component: LControlZoom.vue', () => { test('LControlZoom.vue accepts and uses the Leaflet position option', () => { const { wrapper } = getWrapperWithMap(LControlZoom, { position: 'bottomleft' }); + expect(wrapper.vm.mapObject.getPosition()).toBe('bottomleft'); }); @@ -18,21 +20,25 @@ describe('component: LControlZoom.vue', () => { test('LControlZoom.vue includes "zoomInText" in rendered html', async () => { const { mapWrapper } = getWrapperWithMap(LControlZoom, { zoomInText: 'BIGGER' }); + see(mapWrapper, 'BIGGER'); }); test('LControlZoom.vue includes "zoomInTitle" in rendered html', () => { const { mapWrapper } = getWrapperWithMap(LControlZoom, { zoomInTitle: 'zoom in more' }); + see(mapWrapper, 'zoom in more'); }); test('LControlZoom.vue includes "zoomOutText" in rendered html', () => { const { mapWrapper } = getWrapperWithMap(LControlZoom, { zoomOutText: 'smaller' }); + see(mapWrapper, 'smaller'); }); test('LControlZoom.vue includes "zoomOutTitle" in rendered html', () => { const { mapWrapper } = getWrapperWithMap(LControlZoom, { zoomOutTitle: 'zoom out a bit' }); + see(mapWrapper, 'zoom out a bit'); }); @@ -42,6 +48,7 @@ describe('component: LControlZoom.vue', () => { const { wrapper } = getWrapperWithMap(LControlZoom, { position: 'bottomleft' }); wrapper.setProps({ position: 'topright' }); await wrapper.vm.$nextTick(); + expect(wrapper.vm.mapObject.getPosition()).toBe('topright'); }); }); diff --git a/tests/unit/components/LMap.spec.js b/tests/unit/components/LMap.spec.js index 8af03078..27494050 100644 --- a/tests/unit/components/LMap.spec.js +++ b/tests/unit/components/LMap.spec.js @@ -5,13 +5,17 @@ describe('component: LMap.vue', () => { test('LMap.vue change prop center', async () => { const mockPanTo = jest.fn(); const wrapper = getMapWrapper(); + expect(wrapper.exists()).toBe(true); + wrapper.vm.mapObject.panTo = mockPanTo; const newCenter = L.latLng([1, 1]); wrapper.setProps({ center: newCenter }); await wrapper.vm.$nextTick(); + const center = wrapper.vm.lastSetCenter; const centerLatLng = L.latLng(center); + expect(centerLatLng.equals(newCenter)).toBe(true); expect(mockPanTo.mock.calls.length).toBe(1); expect(mockPanTo.mock.calls[0][0]).toBe(newCenter); @@ -21,17 +25,20 @@ describe('component: LMap.vue', () => { const mockPanTo = jest.fn(); const center = L.latLng([0, 0]); const wrapper = getMapWrapper({ center: center }); + expect(wrapper.exists()).toBe(true); + wrapper.vm.mapObject.panTo = mockPanTo; wrapper.vm.setCenter(center); + expect(mockPanTo.mock.calls.length).toBe(0); }); test('LMap.vue setCenter should work with [0, 0]', () => { const center = [0, 0]; const mockPanTo = jest.fn(); - const wrapper = getMapWrapper({ center: [1, 1] }); + wrapper.vm.mapObject.panTo = mockPanTo; wrapper.vm.setCenter(center); @@ -47,8 +54,8 @@ describe('component: LMap.vue', () => { const center1 = L.latLng([1, 1]); const center2 = L.latLng([2, 2]); const center3 = L.latLng([3, 3]); - const wrapper = getMapWrapper({ center: initialCenter }); + wrapper.vm.setCenter(center1); wrapper.vm.setCenter(center2); wrapper.vm.setCenter(center3); @@ -64,6 +71,7 @@ describe('component: LMap.vue', () => { test('LMap.vue initial bounds', () => { const bounds = L.latLngBounds(L.latLng([1, 1]), L.latLng([2, 2])); const wrapper = getMapWrapper({ bounds: bounds }); + expect(wrapper.vm.lastSetBounds).toEqual(bounds); }); @@ -74,10 +82,12 @@ describe('component: LMap.vue', () => { const mockFitBounds = jest.fn(); const wrapper = getMapWrapper({ bounds: bounds }); wrapper.vm.mapObject.fitBounds = mockFitBounds; + wrapper.setProps({ bounds: newBounds }); await wrapper.vm.$nextTick(); wrapper.setProps({ bounds: newBounds2 }); await wrapper.vm.$nextTick(); + expect(mockFitBounds.mock.calls.length).toBe(2); expect(mockFitBounds.mock.calls[0][0]).toEqual(L.latLngBounds(newBounds)); expect(mockFitBounds.mock.calls[0][1]).toEqual({}); @@ -91,8 +101,10 @@ describe('component: LMap.vue', () => { const mockFitBounds = jest.fn(); const wrapper = getMapWrapper({ bounds: bounds }); wrapper.vm.mapObject.fitBounds = mockFitBounds; + wrapper.setProps({ bounds: sameBounds }); await wrapper.vm.$nextTick(); + expect(mockFitBounds.mock.calls.length).toBe(0); }); @@ -109,6 +121,7 @@ describe('component: LMap.vue', () => { const mockFitBounds = jest.fn(); const wrapper = getMapWrapper(); wrapper.vm.mapObject.fitBounds = mockFitBounds; + wrapper.setProps({ padding: optionsPadding.padding }); wrapper.setProps({ paddingBottomRight: optionsPadding2.paddingBottomRight }); wrapper.setProps({ paddingTopLeft: optionsPadding2.paddingTopLeft }); @@ -119,6 +132,7 @@ describe('component: LMap.vue', () => { await wrapper.vm.$nextTick(); wrapper.setProps({ bounds: bounds2 }); await wrapper.vm.$nextTick(); + expect(mockFitBounds.mock.calls.length).toBe(2); expect(mockFitBounds.mock.calls[0][0]).toEqual(L.latLngBounds(bounds)); expect(mockFitBounds.mock.calls[0][1]).toEqual(optionsPadding); From bd4473cc075ee084190c81b0a8507fe48292b7ab Mon Sep 17 00:00:00 2001 From: Michael Underwood Date: Wed, 29 May 2019 12:27:29 -0600 Subject: [PATCH 19/26] Add tests for LCircle latLng and radius props --- tests/unit/components/LCircle.spec.js | 66 +++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 tests/unit/components/LCircle.spec.js diff --git a/tests/unit/components/LCircle.spec.js b/tests/unit/components/LCircle.spec.js new file mode 100644 index 00000000..d761f6ff --- /dev/null +++ b/tests/unit/components/LCircle.spec.js @@ -0,0 +1,66 @@ +import { getWrapperWithMap } from '@/tests/test-helpers'; +import LCircle from '@/components/LCircle.vue'; +import L from 'leaflet'; + +describe('component: LCircle.vue', () => { + test('LCircle.vue has a mapObject', () => { + const { wrapper } = getWrapperWithMap(LCircle); + + expect(wrapper.vm.mapObject).toBeDefined(); + }); + + // latLng property + + test('LCircle.vue accepts and uses latLng prop as an array', () => { + const latLng = [3.14, 2.79]; + const { wrapper } = getWrapperWithMap(LCircle, { latLng }); + + expect(wrapper.vm.mapObject.getLatLng()).toEqual(L.latLng(latLng)); + }); + + test('LCircle.vue accepts and uses latLng prop as a Leaflet object', () => { + const latLng = L.latLng([3.14, 2.79]); + const { wrapper } = getWrapperWithMap(LCircle, { latLng }); + + expect(wrapper.vm.mapObject.getLatLng()).toEqual(latLng); + }); + + test('LCircle.vue updates the mapObject latLng when prop is changed to an array', async () => { + const { wrapper } = getWrapperWithMap(LCircle, { latLng: [11, 22] }); + + const newLatLng = [1, 1]; + wrapper.setProps({ latLng: newLatLng }); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.getLatLng()).toEqual(L.latLng(newLatLng)); + }); + + test('LCircle.vue updates the mapObject latLng when prop is changed to an object', async () => { + const { wrapper } = getWrapperWithMap(LCircle, { latLng: [11, 22] }); + + const newLatLng = L.latLng([1, 1]); + wrapper.setProps({ latLng: newLatLng }); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.getLatLng()).toEqual(newLatLng); + }); + + // radius property + + test('LCircle.vue accepts and uses radius prop', () => { + const radius = 42; + const { wrapper } = getWrapperWithMap(LCircle, { radius }); + expect(wrapper.vm.mapObject.getRadius()).toBe(radius); + }); + + test('LCircle.vue updates the mapObject radius when prop changes', async () => { + const radius = 42; + const { wrapper } = getWrapperWithMap(LCircle, { radius }); + + const newRadius = 137; + wrapper.setProps({ radius: newRadius }); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.getRadius()).toBe(newRadius); + }); +}); From 64d61f7e4c196e7eb7c796c2e1f802ae417885d5 Mon Sep 17 00:00:00 2001 From: Michael Underwood Date: Wed, 29 May 2019 13:40:18 -0600 Subject: [PATCH 20/26] Add helper to test mixed-in path functionality --- tests/mixin-tests/path-tests.js | 65 +++++++++++++++++++++++++++ tests/unit/components/LCircle.spec.js | 3 ++ 2 files changed, 68 insertions(+) create mode 100644 tests/mixin-tests/path-tests.js diff --git a/tests/mixin-tests/path-tests.js b/tests/mixin-tests/path-tests.js new file mode 100644 index 00000000..1d6da665 --- /dev/null +++ b/tests/mixin-tests/path-tests.js @@ -0,0 +1,65 @@ +import { getWrapperWithMap } from '@/tests/test-helpers'; + +export const testPathFunctionality = (component, componentName = 'it') => { + // + // Stroke + + test(`${componentName} accepts and uses stroke prop when true`, () => { + const { wrapper } = getWrapperWithMap(component, { stroke: true }); + + expect(wrapper.vm.mapObject.options.stroke).toBeTruthy(); + }); + + test(`${componentName} accepts and uses stroke prop when false`, () => { + const { wrapper } = getWrapperWithMap(component, { stroke: false }); + + expect(wrapper.vm.mapObject.options.stroke).toBeFalsy(); + }); + + test(`${componentName} updates stroke when prop changes`, async () => { + const { wrapper } = getWrapperWithMap(component, { stroke: true }); + + wrapper.setProps({ stroke: false }); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.stroke).toBeFalsy(); + }); + + test(`${componentName} updates stroke using setStroke`, async () => { + const { wrapper } = getWrapperWithMap(component, { stroke: true }); + + wrapper.vm.setStroke(false); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.stroke).toBeFalsy(); + }); + + // Color + + test(`${componentName} accepts and uses color prop`, () => { + const color = '#314159'; + const { wrapper } = getWrapperWithMap(component, { color }); + + expect(wrapper.vm.mapObject.options.color).toEqual(color); + }); + + test(`${componentName} updates color when prop changes`, async () => { + const { wrapper } = getWrapperWithMap(component, { color: '#314159' }); + + const newColor = '#112233'; + wrapper.setProps({ color: newColor }); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.color).toEqual(newColor); + }); + + test(`${componentName} updates color using setColor`, async () => { + const { wrapper } = getWrapperWithMap(component, { color: '#314159' }); + + const newColor = '#112233'; + wrapper.vm.setColor(newColor); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.color).toEqual(newColor); + }); +}; diff --git a/tests/unit/components/LCircle.spec.js b/tests/unit/components/LCircle.spec.js index d761f6ff..ce15a9e1 100644 --- a/tests/unit/components/LCircle.spec.js +++ b/tests/unit/components/LCircle.spec.js @@ -1,4 +1,5 @@ import { getWrapperWithMap } from '@/tests/test-helpers'; +import { testPathFunctionality } from '@/tests/mixin-tests/path-tests'; import LCircle from '@/components/LCircle.vue'; import L from 'leaflet'; @@ -63,4 +64,6 @@ describe('component: LCircle.vue', () => { expect(wrapper.vm.mapObject.getRadius()).toBe(newRadius); }); + + testPathFunctionality(LCircle, 'LCircle.vue'); }); From 82ca3dbbcd46e97ed2df5b4bd4aa91d882a24e6c Mon Sep 17 00:00:00 2001 From: Michael Underwood Date: Wed, 29 May 2019 15:39:07 -0600 Subject: [PATCH 21/26] Add tests for remaining path mixin properties --- tests/mixin-tests/path-tests.js | 322 ++++++++++++++++++++++++++++++++ 1 file changed, 322 insertions(+) diff --git a/tests/mixin-tests/path-tests.js b/tests/mixin-tests/path-tests.js index 1d6da665..be135fbf 100644 --- a/tests/mixin-tests/path-tests.js +++ b/tests/mixin-tests/path-tests.js @@ -62,4 +62,326 @@ export const testPathFunctionality = (component, componentName = 'it') => { expect(wrapper.vm.mapObject.options.color).toEqual(newColor); }); + + // Weight + + test(`${componentName} accepts and uses weight prop`, () => { + const weight = 3; + const { wrapper } = getWrapperWithMap(component, { weight }); + + expect(wrapper.vm.mapObject.options.weight).toEqual(weight); + }); + + test(`${componentName} updates weight when prop changes`, async () => { + const { wrapper } = getWrapperWithMap(component, { weight: 1 }); + + const newWeight = 5; + wrapper.setProps({ weight: newWeight }); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.weight).toEqual(newWeight); + }); + + test(`${componentName} updates weight using setWeight`, async () => { + const { wrapper } = getWrapperWithMap(component, { weight: 2 }); + + const newWeight = 4; + wrapper.vm.setWeight(newWeight); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.weight).toEqual(newWeight); + }); + + // Opacity + + test(`${componentName} accepts and uses opacity prop`, () => { + const opacity = 0.3; + const { wrapper } = getWrapperWithMap(component, { opacity }); + + expect(wrapper.vm.mapObject.options.opacity).toEqual(opacity); + }); + + test(`${componentName} updates opacity when prop changes`, async () => { + const { wrapper } = getWrapperWithMap(component, { opacity: 0.1 }); + + const newOpacity = 0.5; + wrapper.setProps({ opacity: newOpacity }); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.opacity).toEqual(newOpacity); + }); + + test(`${componentName} updates opacity using setOpacity`, async () => { + const { wrapper } = getWrapperWithMap(component, { opacity: 0.2 }); + + const newOpacity = 0.4; + wrapper.vm.setOpacity(newOpacity); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.opacity).toEqual(newOpacity); + }); + + // LineCap + + test(`${componentName} accepts and uses lineCap prop`, () => { + const lineCap = 'round'; + const { wrapper } = getWrapperWithMap(component, { lineCap }); + + expect(wrapper.vm.mapObject.options.lineCap).toEqual(lineCap); + }); + + test(`${componentName} updates lineCap when prop changes`, async () => { + const { wrapper } = getWrapperWithMap(component, { lineCap: 'round' }); + + const newLineCap = 'square'; + wrapper.setProps({ lineCap: newLineCap }); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.lineCap).toEqual(newLineCap); + }); + + test(`${componentName} updates lineCap using setLineCap`, async () => { + const { wrapper } = getWrapperWithMap(component, { lineCap: 'square' }); + + const newLineCap = 'round'; + wrapper.vm.setLineCap(newLineCap); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.lineCap).toEqual(newLineCap); + }); + + // LineJoin + + test(`${componentName} accepts and uses lineJoin prop`, () => { + const lineJoin = 'miter'; + const { wrapper } = getWrapperWithMap(component, { lineJoin }); + + expect(wrapper.vm.mapObject.options.lineJoin).toEqual(lineJoin); + }); + + test(`${componentName} updates lineJoin when prop changes`, async () => { + const { wrapper } = getWrapperWithMap(component, { lineJoin: 'miter' }); + + const newLineJoin = 'bevel'; + wrapper.setProps({ lineJoin: newLineJoin }); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.lineJoin).toEqual(newLineJoin); + }); + + test(`${componentName} updates lineJoin using setLineJoin`, async () => { + const { wrapper } = getWrapperWithMap(component, { lineJoin: 'bevel' }); + + const newLineJoin = 'miter'; + wrapper.vm.setLineJoin(newLineJoin); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.lineJoin).toEqual(newLineJoin); + }); + + // DashArray + + test(`${componentName} accepts and uses dashArray prop`, () => { + const dashArray = '4 1'; + const { wrapper } = getWrapperWithMap(component, { dashArray }); + + expect(wrapper.vm.mapObject.options.dashArray).toEqual(dashArray); + }); + + test(`${componentName} updates dashArray when prop changes`, async () => { + const { wrapper } = getWrapperWithMap(component, { dashArray: '4 1' }); + + const newDashArray = '3 2 3'; + wrapper.setProps({ dashArray: newDashArray }); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.dashArray).toEqual(newDashArray); + }); + + test(`${componentName} updates dashArray using setDashArray`, async () => { + const { wrapper } = getWrapperWithMap(component, { dashArray: '3 2 3' }); + + const newDashArray = '4 1'; + wrapper.vm.setDashArray(newDashArray); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.dashArray).toEqual(newDashArray); + }); + + // DashOffset + + test(`${componentName} accepts and uses dashOffset prop`, () => { + const dashOffset = '1'; + const { wrapper } = getWrapperWithMap(component, { dashOffset }); + + expect(wrapper.vm.mapObject.options.dashOffset).toEqual(dashOffset); + }); + + test(`${componentName} updates dashOffset when prop changes`, async () => { + const { wrapper } = getWrapperWithMap(component, { dashOffset: '1' }); + + const newDashOffset = '3'; + wrapper.setProps({ dashOffset: newDashOffset }); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.dashOffset).toEqual(newDashOffset); + }); + + test(`${componentName} updates dashOffset using setDashOffset`, async () => { + const { wrapper } = getWrapperWithMap(component, { dashOffset: '3' }); + + const newDashOffset = '1'; + wrapper.vm.setDashOffset(newDashOffset); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.dashOffset).toEqual(newDashOffset); + }); + + // Fill + + test(`${componentName} accepts and uses fill prop when true`, () => { + const { wrapper } = getWrapperWithMap(component, { fill: true }); + + expect(wrapper.vm.mapObject.options.fill).toBeTruthy(); + }); + + test(`${componentName} accepts and uses fill prop when false`, () => { + const { wrapper } = getWrapperWithMap(component, { fill: false }); + + expect(wrapper.vm.mapObject.options.fill).toBeFalsy(); + }); + + test(`${componentName} updates fill when prop changes`, async () => { + const { wrapper } = getWrapperWithMap(component, { fill: true }); + + wrapper.setProps({ fill: false }); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.fill).toBeFalsy(); + }); + + test(`${componentName} updates fill using setFill`, async () => { + const { wrapper } = getWrapperWithMap(component, { fill: true }); + + wrapper.vm.setFill(false); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.fill).toBeFalsy(); + }); + + // FillColor + + test(`${componentName} accepts and uses fillColor prop`, () => { + const fillColor = '#abcdef'; + const { wrapper } = getWrapperWithMap(component, { fillColor }); + + expect(wrapper.vm.mapObject.options.fillColor).toEqual(fillColor); + }); + + test(`${componentName} updates fillColor when prop changes`, async () => { + const { wrapper } = getWrapperWithMap(component, { fillColor: '#abcdef' }); + + const newFillColor = '#987654'; + wrapper.setProps({ fillColor: newFillColor }); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.fillColor).toEqual(newFillColor); + }); + + test(`${componentName} updates fillColor using setFillColor`, async () => { + const { wrapper } = getWrapperWithMap(component, { fillColor: '#987654' }); + + const newFillColor = '#abcdef'; + wrapper.vm.setFillColor(newFillColor); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.fillColor).toEqual(newFillColor); + }); + + // FillOpacity + + test(`${componentName} accepts and uses fillOpacity prop`, () => { + const fillOpacity = 0.3; + const { wrapper } = getWrapperWithMap(component, { fillOpacity }); + + expect(wrapper.vm.mapObject.options.fillOpacity).toEqual(fillOpacity); + }); + + test(`${componentName} updates fillOpacity when prop changes`, async () => { + const { wrapper } = getWrapperWithMap(component, { fillOpacity: 0.1 }); + + const newFillOpacity = 0.5; + wrapper.setProps({ fillOpacity: newFillOpacity }); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.fillOpacity).toEqual(newFillOpacity); + }); + + test(`${componentName} updates fillOpacity using setFillOpacity`, async () => { + const { wrapper } = getWrapperWithMap(component, { fillOpacity: 0.2 }); + + const newFillOpacity = 0.4; + wrapper.vm.setFillOpacity(newFillOpacity); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.fillOpacity).toEqual(newFillOpacity); + }); + + // FillRule + + test(`${componentName} accepts and uses fillRule prop`, () => { + const fillRule = 'nonzero'; + const { wrapper } = getWrapperWithMap(component, { fillRule }); + + expect(wrapper.vm.mapObject.options.fillRule).toEqual(fillRule); + }); + + test(`${componentName} updates fillRule when prop changes`, async () => { + const { wrapper } = getWrapperWithMap(component, { fillRule: 'nonzero' }); + + const newFillRule = 'evenodd'; + wrapper.setProps({ fillRule: newFillRule }); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.fillRule).toEqual(newFillRule); + }); + + test(`${componentName} updates fillRule using setFillRule`, async () => { + const { wrapper } = getWrapperWithMap(component, { fillRule: 'evenodd' }); + + const newFillRule = 'nonzero'; + wrapper.vm.setFillRule(newFillRule); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.fillRule).toEqual(newFillRule); + }); + + // ClassName + + test(`${componentName} accepts and uses className prop`, () => { + const className = 'good'; + const { wrapper } = getWrapperWithMap(component, { className }); + + expect(wrapper.vm.mapObject.options.className).toEqual(className); + }); + + test(`${componentName} updates className when prop changes`, async () => { + const { wrapper } = getWrapperWithMap(component, { className: 'good' }); + + const newClassName = 'evil'; + wrapper.setProps({ className: newClassName }); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.className).toEqual(newClassName); + }); + + test(`${componentName} updates className using setClassName`, async () => { + const { wrapper } = getWrapperWithMap(component, { className: 'evil' }); + + const newClassName = 'good'; + wrapper.vm.setClassName(newClassName); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.className).toEqual(newClassName); + }); }; From 7d289cc4b9d11db34d50292e5ed9f08c60b4e2e4 Mon Sep 17 00:00:00 2001 From: Michael Underwood Date: Wed, 29 May 2019 15:40:33 -0600 Subject: [PATCH 22/26] Move radius test into test for Circle mixin and include Path there --- tests/mixin-tests/circle-tests.js | 32 +++++++++++++++++++++++++++ tests/unit/components/LCircle.spec.js | 27 +++++----------------- 2 files changed, 37 insertions(+), 22 deletions(-) create mode 100644 tests/mixin-tests/circle-tests.js diff --git a/tests/mixin-tests/circle-tests.js b/tests/mixin-tests/circle-tests.js new file mode 100644 index 00000000..87403b06 --- /dev/null +++ b/tests/mixin-tests/circle-tests.js @@ -0,0 +1,32 @@ +import { getWrapperWithMap } from '@/tests/test-helpers'; +import { testPathFunctionality } from './path-tests'; + +export const testCircleFunctionality = (component, componentName = 'it') => { + // The Circle mixin includes the Path mixin. + testPathFunctionality(component, componentName); + + // Radius + + test(`${componentName} has a getRadius method`, () => { + const { wrapper } = getWrapperWithMap(component); + + expect(typeof wrapper.vm.mapObject.getRadius).toEqual('function'); + }); + + test(`${componentName} accepts and uses radius prop`, () => { + const radius = 100; + const { wrapper } = getWrapperWithMap(component, { radius }); + + expect(wrapper.vm.mapObject.getRadius()).toEqual(radius); + }); + + test(`${componentName} updates radius when prop changes`, async () => { + const { wrapper } = getWrapperWithMap(component, { radius: 100 }); + + const newRadius = 2000; + wrapper.setProps({ radius: newRadius }); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.getRadius()).toEqual(newRadius); + }); +}; diff --git a/tests/unit/components/LCircle.spec.js b/tests/unit/components/LCircle.spec.js index ce15a9e1..98e9b50e 100644 --- a/tests/unit/components/LCircle.spec.js +++ b/tests/unit/components/LCircle.spec.js @@ -1,5 +1,5 @@ import { getWrapperWithMap } from '@/tests/test-helpers'; -import { testPathFunctionality } from '@/tests/mixin-tests/path-tests'; +import { testCircleFunctionality } from '@/tests/mixin-tests/circle-tests'; import LCircle from '@/components/LCircle.vue'; import L from 'leaflet'; @@ -10,6 +10,10 @@ describe('component: LCircle.vue', () => { expect(wrapper.vm.mapObject).toBeDefined(); }); + // Circle mixin + + testCircleFunctionality(LCircle, 'LCircle.vue'); + // latLng property test('LCircle.vue accepts and uses latLng prop as an array', () => { @@ -45,25 +49,4 @@ describe('component: LCircle.vue', () => { expect(wrapper.vm.mapObject.getLatLng()).toEqual(newLatLng); }); - - // radius property - - test('LCircle.vue accepts and uses radius prop', () => { - const radius = 42; - const { wrapper } = getWrapperWithMap(LCircle, { radius }); - expect(wrapper.vm.mapObject.getRadius()).toBe(radius); - }); - - test('LCircle.vue updates the mapObject radius when prop changes', async () => { - const radius = 42; - const { wrapper } = getWrapperWithMap(LCircle, { radius }); - - const newRadius = 137; - wrapper.setProps({ radius: newRadius }); - await wrapper.vm.$nextTick(); - - expect(wrapper.vm.mapObject.getRadius()).toBe(newRadius); - }); - - testPathFunctionality(LCircle, 'LCircle.vue'); }); From 9a6097558190813d70c5da4adcc6141ca04583bb Mon Sep 17 00:00:00 2001 From: Michael Underwood Date: Wed, 29 May 2019 16:10:44 -0600 Subject: [PATCH 23/26] Prevent "error reading property 'lng' of null" on default CircleMarker --- src/components/LCircleMarker.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/LCircleMarker.vue b/src/components/LCircleMarker.vue index 1a68084b..a3f34c89 100644 --- a/src/components/LCircleMarker.vue +++ b/src/components/LCircleMarker.vue @@ -15,7 +15,7 @@ export default { props: { latLng: { type: [Object, Array], - default: () => [] + default: () => [0, 0] }, pane: { type: String, From b84879cd0ec21e1f7c0dcf8b503560d56e66a6c4 Mon Sep 17 00:00:00 2001 From: Michael Underwood Date: Wed, 29 May 2019 16:19:54 -0600 Subject: [PATCH 24/26] Add tests for LCircleMarker component --- tests/unit/components/LCircleMarker.spec.js | 77 +++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 tests/unit/components/LCircleMarker.spec.js diff --git a/tests/unit/components/LCircleMarker.spec.js b/tests/unit/components/LCircleMarker.spec.js new file mode 100644 index 00000000..d876e4c6 --- /dev/null +++ b/tests/unit/components/LCircleMarker.spec.js @@ -0,0 +1,77 @@ +import { getWrapperWithMap } from '@/tests/test-helpers'; +import { testCircleFunctionality } from '@/tests/mixin-tests/circle-tests'; +import LCircleMarker from '@/components/LCircleMarker.vue'; +import L from 'leaflet'; + +describe('component: LCircleMarker.vue', () => { + test('LCircleMarker.vue has a mapObject', () => { + const { wrapper } = getWrapperWithMap(LCircleMarker); + + expect(wrapper.vm.mapObject).toBeDefined(); + }); + + // Circle mixin + + testCircleFunctionality(LCircleMarker, 'LCircleMarker.vue'); + + // latLng property + + test('LCircleMarker.vue accepts and uses latLng prop as an array', () => { + const latLng = [3.14, 2.79]; + const { wrapper } = getWrapperWithMap(LCircleMarker, { latLng }); + + expect(wrapper.vm.mapObject.getLatLng()).toEqual(L.latLng(latLng)); + }); + + test('LCircleMarker.vue accepts and uses latLng prop as a Leaflet object', () => { + const latLng = L.latLng([3.14, 2.79]); + const { wrapper } = getWrapperWithMap(LCircleMarker, { latLng }); + + expect(wrapper.vm.mapObject.getLatLng()).toEqual(latLng); + }); + + test('LCircleMarker.vue updates the mapObject latLng when prop is changed to an array', async () => { + const { wrapper } = getWrapperWithMap(LCircleMarker, { latLng: [11, 22] }); + + const newLatLng = [1, 1]; + wrapper.setProps({ latLng: newLatLng }); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.getLatLng()).toEqual(L.latLng(newLatLng)); + }); + + test('LCircleMarker.vue updates the mapObject latLng when prop is changed to an object', async () => { + const { wrapper } = getWrapperWithMap(LCircleMarker, { latLng: [11, 22] }); + + const newLatLng = L.latLng([1, 1]); + wrapper.setProps({ latLng: newLatLng }); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.getLatLng()).toEqual(newLatLng); + }); + + // Pane + + test('LCircleMarker.vue accepts and uses pane prop', () => { + const pane = 'overlayPane'; + const { wrapper } = getWrapperWithMap(LCircleMarker, { pane }); + + expect(wrapper.vm.mapObject.options.pane).toEqual(pane); + }); + + // Slot + + test('LCircleMarker.vue displays text from its default slot', async () => { + const markerText = 'O hai, Marker!'; + const { wrapper } = getWrapperWithMap(LCircleMarker, { + latLng: [0, 0] + }, { + slots: { + default: markerText + } + }); + await wrapper.vm.$nextTick(); + + expect(wrapper.text()).toEqual(markerText); + }); +}); From cc2c9f318c37aa58be59c62797bdb63d15b151e5 Mon Sep 17 00:00:00 2001 From: Michael Underwood Date: Wed, 29 May 2019 19:40:24 -0600 Subject: [PATCH 25/26] Add tests to ensure legitimate falsy values can be set --- tests/mixin-tests/path-tests.js | 64 ++++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 4 deletions(-) diff --git a/tests/mixin-tests/path-tests.js b/tests/mixin-tests/path-tests.js index be135fbf..1ab5b7f0 100644 --- a/tests/mixin-tests/path-tests.js +++ b/tests/mixin-tests/path-tests.js @@ -16,7 +16,7 @@ export const testPathFunctionality = (component, componentName = 'it') => { expect(wrapper.vm.mapObject.options.stroke).toBeFalsy(); }); - test(`${componentName} updates stroke when prop changes`, async () => { + test(`${componentName} updates stroke when prop changes to false`, async () => { const { wrapper } = getWrapperWithMap(component, { stroke: true }); wrapper.setProps({ stroke: false }); @@ -25,7 +25,16 @@ export const testPathFunctionality = (component, componentName = 'it') => { expect(wrapper.vm.mapObject.options.stroke).toBeFalsy(); }); - test(`${componentName} updates stroke using setStroke`, async () => { + test(`${componentName} updates stroke when prop changes to true`, async () => { + const { wrapper } = getWrapperWithMap(component, { stroke: false }); + + wrapper.setProps({ stroke: true }); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.stroke).toBeTruthy(); + }); + + test(`${componentName} updates stroke to false using setStroke`, async () => { const { wrapper } = getWrapperWithMap(component, { stroke: true }); wrapper.vm.setStroke(false); @@ -34,6 +43,15 @@ export const testPathFunctionality = (component, componentName = 'it') => { expect(wrapper.vm.mapObject.options.stroke).toBeFalsy(); }); + test(`${componentName} updates stroke to true using setStroke`, async () => { + const { wrapper } = getWrapperWithMap(component, { stroke: false }); + + wrapper.vm.setStroke(true); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.stroke).toBeTruthy(); + }); + // Color test(`${componentName} accepts and uses color prop`, () => { @@ -121,6 +139,16 @@ export const testPathFunctionality = (component, componentName = 'it') => { expect(wrapper.vm.mapObject.options.opacity).toEqual(newOpacity); }); + test(`${componentName} can set opacity to zero`, async () => { + const { wrapper } = getWrapperWithMap(component, { opacity: 0.2 }); + + const newOpacity = 0; + wrapper.vm.setOpacity(newOpacity); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.opacity).toEqual(newOpacity); + }); + // LineCap test(`${componentName} accepts and uses lineCap prop`, () => { @@ -251,7 +279,7 @@ export const testPathFunctionality = (component, componentName = 'it') => { expect(wrapper.vm.mapObject.options.fill).toBeFalsy(); }); - test(`${componentName} updates fill when prop changes`, async () => { + test(`${componentName} updates fill when prop changes to false`, async () => { const { wrapper } = getWrapperWithMap(component, { fill: true }); wrapper.setProps({ fill: false }); @@ -260,7 +288,16 @@ export const testPathFunctionality = (component, componentName = 'it') => { expect(wrapper.vm.mapObject.options.fill).toBeFalsy(); }); - test(`${componentName} updates fill using setFill`, async () => { + test(`${componentName} updates fill when prop changes to true`, async () => { + const { wrapper } = getWrapperWithMap(component, { fill: false }); + + wrapper.setProps({ fill: true }); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.fill).toBeTruthy(); + }); + + test(`${componentName} updates fill to false using setFill`, async () => { const { wrapper } = getWrapperWithMap(component, { fill: true }); wrapper.vm.setFill(false); @@ -269,6 +306,15 @@ export const testPathFunctionality = (component, componentName = 'it') => { expect(wrapper.vm.mapObject.options.fill).toBeFalsy(); }); + test(`${componentName} updates fill to true using setFill`, async () => { + const { wrapper } = getWrapperWithMap(component, { fill: false }); + + wrapper.vm.setFill(true); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.fill).toBeTruthy(); + }); + // FillColor test(`${componentName} accepts and uses fillColor prop`, () => { @@ -327,6 +373,16 @@ export const testPathFunctionality = (component, componentName = 'it') => { expect(wrapper.vm.mapObject.options.fillOpacity).toEqual(newFillOpacity); }); + test(`${componentName} can set fillOpacity to zero`, async () => { + const { wrapper } = getWrapperWithMap(component, { fillOpacity: 0.2 }); + + const newFillOpacity = 0; + wrapper.vm.setFillOpacity(newFillOpacity); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.fillOpacity).toEqual(newFillOpacity); + }); + // FillRule test(`${componentName} accepts and uses fillRule prop`, () => { From 88fc290dda53db6e6f5a3366155b1f5191046088 Mon Sep 17 00:00:00 2001 From: Michael Underwood Date: Wed, 29 May 2019 19:41:23 -0600 Subject: [PATCH 26/26] Add tests for LPolyline.vue and Polyline mixin --- tests/mixin-tests/polyline-tests.js | 113 ++++++++++++++++++++++++ tests/unit/components/LPolyline.spec.js | 34 +++++++ 2 files changed, 147 insertions(+) create mode 100644 tests/mixin-tests/polyline-tests.js create mode 100644 tests/unit/components/LPolyline.spec.js diff --git a/tests/mixin-tests/polyline-tests.js b/tests/mixin-tests/polyline-tests.js new file mode 100644 index 00000000..923401df --- /dev/null +++ b/tests/mixin-tests/polyline-tests.js @@ -0,0 +1,113 @@ +import { getWrapperWithMap } from '@/tests/test-helpers'; +import { testPathFunctionality } from './path-tests'; +import L from 'leaflet'; + +export const toLeafletLL = (latLngs) => latLngs.map(ll => L.latLng(ll)); + +export const testPolylineFunctionality = (component, componentName = 'it') => { + // The Polyline mixin includes the Path mixin. + testPathFunctionality(component, componentName); + + // SmoothFactor + + test(`${componentName} accepts and uses smoothFactor prop`, () => { + const smoothFactor = 1.5; + const { wrapper } = getWrapperWithMap(component, { smoothFactor }); + + expect(wrapper.vm.mapObject.options.smoothFactor).toEqual(smoothFactor); + }); + + test(`${componentName} updates smoothFactor when prop changes`, async () => { + const { wrapper } = getWrapperWithMap(component, { smoothFactor: 1.5 }); + + const newSmoothFactor = 2.2; + wrapper.setProps({ smoothFactor: newSmoothFactor }); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.smoothFactor).toEqual(newSmoothFactor); + }); + + test(`${componentName} updates smoothFactor using setSmoothFactor`, async () => { + const { wrapper } = getWrapperWithMap(component, { smoothFactor: 1.5 }); + + const newSmoothFactor = 2.2; + wrapper.vm.setSmoothFactor(newSmoothFactor); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.smoothFactor).toEqual(newSmoothFactor); + }); + + // NoClip + + test(`${componentName} accepts and uses noClip prop when true`, () => { + const { wrapper } = getWrapperWithMap(component, { noClip: true }); + + expect(wrapper.vm.mapObject.options.noClip).toBeTruthy(); + }); + + test(`${componentName} accepts and uses noClip prop when false`, () => { + const { wrapper } = getWrapperWithMap(component, { noClip: false }); + + expect(wrapper.vm.mapObject.options.noClip).toBeFalsy(); + }); + + test(`${componentName} updates noClip when prop changes to false`, async () => { + const { wrapper } = getWrapperWithMap(component, { noClip: true }); + + wrapper.setProps({ noClip: false }); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.noClip).toBeFalsy(); + }); + + test(`${componentName} updates noClip when prop changes to true`, async () => { + const { wrapper } = getWrapperWithMap(component, { noClip: false }); + + wrapper.setProps({ noClip: true }); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.noClip).toBeTruthy(); + }); + + test(`${componentName} updates noClip to false using setNoClip`, async () => { + const { wrapper } = getWrapperWithMap(component, { noClip: true }); + + wrapper.vm.setNoClip(false); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.noClip).toBeFalsy(); + }); + + test(`${componentName} updates noClip to true using setNoClip`, async () => { + const { wrapper } = getWrapperWithMap(component, { noClip: false }); + + wrapper.vm.setNoClip(true); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.options.noClip).toBeTruthy(); + }); + + // AddLatLng + + test(`${componentName} adds a point to an empty polyline`, async () => { + const { wrapper } = getWrapperWithMap(component); + + const newLatLng = [1, 2]; + wrapper.vm.addLatLng(newLatLng); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.getLatLngs()).toEqual(toLeafletLL([newLatLng])); + }); + + test(`${componentName} adds multiple points`, async () => { + const { wrapper } = getWrapperWithMap(component, { + + }); + + const newLatLngs = [[1, 2], [2, 3], [3, 4]]; + newLatLngs.forEach(ll => wrapper.vm.addLatLng(ll)); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.getLatLngs().length).toEqual(3); + }); +}; diff --git a/tests/unit/components/LPolyline.spec.js b/tests/unit/components/LPolyline.spec.js new file mode 100644 index 00000000..d04d0184 --- /dev/null +++ b/tests/unit/components/LPolyline.spec.js @@ -0,0 +1,34 @@ +import { getWrapperWithMap } from '@/tests/test-helpers'; +import { testPolylineFunctionality, toLeafletLL } from '@/tests/mixin-tests/polyline-tests'; +import LPolyline from '@/components/LPolyline.vue'; + +describe('component: LPolyline.vue', () => { + test('LPolyline.vue has a mapObject', () => { + const { wrapper } = getWrapperWithMap(LPolyline); + + expect(wrapper.vm.mapObject).toBeDefined(); + }); + + // Polyline mixin + + testPolylineFunctionality(LPolyline, 'LPolyline.vue'); + + // latLngs property + + test('LPolyline.vue accepts and uses latLngss prop', () => { + const latLngs = [[3.14, 2.79], [31.4, 27.9]]; + const { wrapper } = getWrapperWithMap(LPolyline, { latLngs }); + + expect(wrapper.vm.mapObject.getLatLngs()).toEqual(toLeafletLL(latLngs)); + }); + + test('LPolyline.vue updates the mapObject latLngs when prop is changed', async () => { + const { wrapper } = getWrapperWithMap(LPolyline, { latLng: [[11, 22], [22, 33]] }); + + const newLatLngs = [[1, 1], [3, 3]]; + wrapper.setProps({ latLngs: newLatLngs }); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.mapObject.getLatLngs()).toEqual(toLeafletLL(newLatLngs)); + }); +});