From 8a169852bb6735e6aa36cb9a264b5a164438e010 Mon Sep 17 00:00:00 2001 From: Max Engisch Date: Sat, 23 Aug 2025 21:21:21 +0200 Subject: [PATCH 01/15] test: add tests for uncovered code --- src/components/LRectangle.vue | 5 ++++- tests/unit/components/LRectangle.test.ts | 27 ++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/components/LRectangle.vue b/src/components/LRectangle.vue index 939b7f1..aeca2e7 100644 --- a/src/components/LRectangle.vue +++ b/src/components/LRectangle.vue @@ -42,9 +42,12 @@ function useRectangle() { const { options, methods } = setupRectangle(props, leafletObject, emit) onMounted(async () => { + if (!props.bounds && !props.latLngs) { + throw new Error("Specify bounds or LatLngs for a valid rectangle.") + } const bounds = props.bounds ? new LatLngBounds(props.bounds) - : new LatLngBounds(props.latLngs || []) + : new LatLngBounds(props.latLngs!) leafletObject.value = markRaw(new Rectangle(bounds, options)) const { listeners } = remapEvents(useAttrs()) diff --git a/tests/unit/components/LRectangle.test.ts b/tests/unit/components/LRectangle.test.ts index 4dedde8..ce2839b 100644 --- a/tests/unit/components/LRectangle.test.ts +++ b/tests/unit/components/LRectangle.test.ts @@ -1,4 +1,4 @@ -import { flushPromises, shallowMount, type VueWrapper } from '@vue/test-utils' +import { config, flushPromises, shallowMount, type VueWrapper } from '@vue/test-utils' import { describe, expect, it } from 'vitest' import LRectangle from '../../../src/components/LRectangle.vue' import { AddLayerInjection, RemoveLayerInjection } from '../../../src/types/injectionKeys' @@ -76,7 +76,7 @@ describe('LRectangle.vue', () => { testAddLayer(createWrapper) }) -const testCorrectInitialisation = (getWrapper: () => Promise>) => { +const testCorrectInitialisation = (getWrapper: (props?) => Promise>) => { it('creates a Leaflet rectangle with correct options', async () => { const wrapper = await getWrapper() const obj = wrapper.vm.leafletObject as Rectangle @@ -91,4 +91,27 @@ const testCorrectInitialisation = (getWrapper: () => Promise>) = ] ]) }) + it('creates a Leaflet rectangle with undefined latLngs', async () => { + let caughtError: unknown = null + config.global.config.errorHandler = (err) => { + caughtError = err + } + await getWrapper({ latLngs: undefined }) + expect(caughtError).toBeInstanceOf(Error) + expect((caughtError as Error).message).toMatch("Specify bounds or LatLngs for a valid rectangle.") + + }) + it('creates a Leaflet rectangle with bounds', async () => { + const wrapper = await getWrapper({ + bounds: [ + [46.234787, -1.509485], + [46.342596, -1.190568] + ] + }) + const obj = wrapper.vm.leafletObject as Rectangle + + expect(obj.getBounds()).toStrictEqual( + new LatLngBounds([46.234787, -1.509485], [46.342596, -1.190568]) + ) + }) } From efd36db0e426e4c8ef15adaffe32282505fd2862 Mon Sep 17 00:00:00 2001 From: Max Engisch Date: Sat, 23 Aug 2025 21:21:59 +0200 Subject: [PATCH 02/15] test: remove unreachable code from coverage and mark as deprecated. --- src/functions/polygon.ts | 6 ++++++ src/functions/polyline.ts | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/src/functions/polygon.ts b/src/functions/polygon.ts index 509f5f1..1f5728f 100644 --- a/src/functions/polygon.ts +++ b/src/functions/polygon.ts @@ -43,9 +43,15 @@ export const setupPolygon = ( const methods = { ...polylineMethods, + /* v8 ignore start - unreachable code, marked for removal */ + /** + * TODO remove unused code + * @deprecated unreachable code ? + */ toGeoJSON(precision: number | false) { return leafletRef.value?.toGeoJSON(precision) } + /* v8 ignore stop */ } return { options, methods } diff --git a/src/functions/polyline.ts b/src/functions/polyline.ts index c766d13..b24e753 100644 --- a/src/functions/polyline.ts +++ b/src/functions/polyline.ts @@ -57,9 +57,15 @@ export const setupPolyline = ( setNoClip(noClip: boolean) { leafletRef.value?.setStyle({ noClip } as PolylineOptions) // TYPES remove cast }, + /* v8 ignore start - unreachable code, marked for removal */ + /** + * TODO remove unused code + * @deprecated unreachable code ? + */ addLatLng(latLng: LatLngExpression) { leafletRef.value?.addLatLng(latLng) } + /* v8 ignore stop */ } return { options, methods } From c130f3a4bcdfe92c570a3ee08cf3e13576420cdc Mon Sep 17 00:00:00 2001 From: Max Engisch Date: Sat, 23 Aug 2025 22:01:49 +0200 Subject: [PATCH 03/15] test: add tests for Tooltip (onUnmount and slot) and Popup (onUnmount and init with latLng) --- src/components/LPopup.vue | 2 +- src/components/LTooltip.vue | 2 +- tests/unit/components/LPopup.test.ts | 13 ++++++-- tests/unit/components/LTooltip.test.ts | 42 ++++++++++++++++++++++++-- tests/unit/components/helper/tests.ts | 24 ++++++++++++++- 5 files changed, 75 insertions(+), 8 deletions(-) diff --git a/src/components/LPopup.vue b/src/components/LPopup.vue index 5c01562..14afabf 100644 --- a/src/components/LPopup.vue +++ b/src/components/LPopup.vue @@ -55,7 +55,7 @@ function usePopup() { propsBinder(methods, leafletObject.value, props) const { listeners } = remapEvents(useAttrs()) leafletObject.value.on(listeners) - leafletObject.value.setContent(props.content || root.value || '') + leafletObject.value.setContent(props.content || root.value!) bindPopup(leafletObject.value) nextTick(() => emit('ready', leafletObject.value!)) }) diff --git a/src/components/LTooltip.vue b/src/components/LTooltip.vue index 50edfaf..c462a12 100644 --- a/src/components/LTooltip.vue +++ b/src/components/LTooltip.vue @@ -50,7 +50,7 @@ function useTooltip() { propsBinder(methods, leafletObject.value, props) const { listeners } = remapEvents(useAttrs()) leafletObject.value.on(listeners) - leafletObject.value.setContent(props.content || root.value || '') + leafletObject.value.setContent(props.content || root.value!) bindTooltip(leafletObject.value) nextTick(() => emit('ready', leafletObject.value!)) }) diff --git a/tests/unit/components/LPopup.test.ts b/tests/unit/components/LPopup.test.ts index b388c11..65d8fde 100644 --- a/tests/unit/components/LPopup.test.ts +++ b/tests/unit/components/LPopup.test.ts @@ -11,6 +11,8 @@ import { import { testEmitsReady } from './helper/emitTests' import { mockBindPopup, mockUnbindPopup } from './helper/injectionsTests' import { mergeReactiveProps } from './helper/props' +import { testUnbindPopupOnUnmount } from './helper/tests' +import { PopupProps } from '../../../src/functions/popup' const popupProps = mergeReactiveProps(popperProps, { latLng: new LatLng(44.5, 11.5) @@ -37,17 +39,24 @@ describe('LPopup.vue', () => { testEmitsReady(createWrapper) testComponentPropBindings(createWrapper, 'LPopup') testPropsBindingToLeaflet(createWrapper, popupProps) - // TEST testRemoveLayerOnUnmount(createWrapper) + testUnbindPopupOnUnmount(createWrapper) testCorrectInitialisation(createWrapper) // TEST bindPopup }) -const testCorrectInitialisation = (getWrapper: () => Promise>) => { +const testCorrectInitialisation = (getWrapper: (props?: PopupProps) => Promise>) => { it('creates a Leaflet popup with correct options', async () => { const wrapper = await getWrapper() const obj = wrapper.vm.leafletObject as Popup expect(obj).toBeDefined() }) + it('creates a Leaflet popup with latLng', async () => { + const wrapper = await getWrapper({ latLng: [45, 5] }) + const obj = wrapper.vm.leafletObject as Popup + + expect(obj).toBeDefined() + expect(obj.getLatLng()).toStrictEqual(new LatLng(45, 5)) + }) } diff --git a/tests/unit/components/LTooltip.test.ts b/tests/unit/components/LTooltip.test.ts index 84038d0..add58f3 100644 --- a/tests/unit/components/LTooltip.test.ts +++ b/tests/unit/components/LTooltip.test.ts @@ -1,4 +1,4 @@ -import { flushPromises, shallowMount, type VueWrapper } from '@vue/test-utils' +import { flushPromises, mount, shallowMount, type VueWrapper } from '@vue/test-utils' import { describe, expect, it } from 'vitest' import LTooltip from '../../../src/components/LTooltip.vue' import { BindTooltipInjection, UnbindTooltipInjection } from '../../../src/types/injectionKeys' @@ -11,6 +11,8 @@ import { testEmitsReady } from './helper/emitTests' import { mockBindTooltip, mockUnbindTooltip } from './helper/injectionsTests' import { Tooltip } from 'leaflet' import { mergeReactiveProps } from './helper/props' +import { testUnbindTooltipOnUnmount } from './helper/tests' +import { TooltipProps } from '../../../src/functions/tooltip' const tooltipProps = mergeReactiveProps(popperProps, {}) @@ -32,16 +34,39 @@ const createWrapper = async (props = {}) => { return wrapper } +const createWrapperWithSlot = async (props = {}) => { + const wrapper = mount(LTooltip, { + propsData: { + ...props + }, + global: { + provide: { + [BindTooltipInjection as symbol]: mockBindTooltip, + [UnbindTooltipInjection as symbol]: mockUnbindTooltip + } + }, + slots: { + default: 'Something' + } + }) + console.log(wrapper.html()) + + await flushPromises() + return wrapper +} + describe('LTooltip.vue', () => { testEmitsReady(createWrapper) testComponentPropBindings(createWrapper, 'LTooltip') testPropsBindingToLeaflet(createWrapper, tooltipProps) - // TEST testRemoveLayerOnUnmount(createWrapper) + testUnbindTooltipOnUnmount(createWrapper) testCorrectInitialisation(createWrapper) }) -const testCorrectInitialisation = (getWrapper: () => Promise>) => { +const testCorrectInitialisation = ( + getWrapper: (props?: TooltipProps) => Promise> +) => { it('creates a Leaflet tooltip with correct options', async () => { const wrapper = await getWrapper() const obj = wrapper.vm.leafletObject as Tooltip @@ -49,4 +74,15 @@ const testCorrectInitialisation = (getWrapper: () => Promise>) = expect(obj).toBeDefined() expect(obj.getContent()).toBe('This is a tooltip.') }) + it('creates a Leaflet tooltip with correct options', async () => { + const wrapper = await createWrapperWithSlot() + const obj = wrapper.vm.leafletObject as Tooltip + + expect(obj).toBeDefined() + expect((obj.getContent() as HTMLDivElement).outerHTML).toBe( + '
Something
' + ) + }) } diff --git a/tests/unit/components/helper/tests.ts b/tests/unit/components/helper/tests.ts index ec970ef..cf7bcea 100644 --- a/tests/unit/components/helper/tests.ts +++ b/tests/unit/components/helper/tests.ts @@ -1,6 +1,6 @@ import { beforeEach, expect, it, vi } from 'vitest' import { VueWrapper } from '@vue/test-utils' -import { mockRemoveLayer } from './injectionsTests' +import { mockRemoveLayer, mockUnbindPopup, mockUnbindTooltip } from './injectionsTests' export const testRemoveOnUnmount = (getWrapper: () => Promise>) => { it('removes the leafletObject on unmount', async () => { @@ -22,3 +22,25 @@ export const testRemoveLayerOnUnmount = (getWrapper: () => Promise Promise>) => { + beforeEach(() => { + mockUnbindTooltip.mockReset() + }) + it('unbinds the tooltip on unmount', async () => { + const wrapper = await getWrapper() + wrapper.unmount() + expect(mockUnbindTooltip).toHaveBeenCalledOnce() + }) +} + +export const testUnbindPopupOnUnmount = (getWrapper: () => Promise>) => { + beforeEach(() => { + mockUnbindPopup.mockReset() + }) + it('unbinds the popup on unmount', async () => { + const wrapper = await getWrapper() + wrapper.unmount() + expect(mockUnbindPopup).toHaveBeenCalledOnce() + }) +} From af7a983728f256f879f3302f835c1c80f0c60340 Mon Sep 17 00:00:00 2001 From: Max Engisch Date: Sun, 24 Aug 2025 20:38:18 +0200 Subject: [PATCH 04/15] chore(tests): ignore experimental feature with v8 coverage --- src/utils.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/utils.ts b/src/utils.ts index ace632b..7300dc5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -63,6 +63,7 @@ export const isFunction = (x: unknown) => typeof x === 'function' export const propsBinder = (methods: Readonly, leafletElement: PropertyMap, props: Readonly) => { //propsBinderScope.run(() => { for (const key in props) { + /* v8 ignore next */ if (vueLeafletConfig.experimental.skipUndefinedProps && props[key] === undefined) continue const setMethodName = 'set' + capitalizeFirstLetter(key) const setterMethod = methods[setMethodName] @@ -137,6 +138,7 @@ export const remapEvents = (contextAttrs: Record): ListenersAnd // TODO It seems like Icon.Default is now IconDefault in leaflet v2 export const resetWebpackIcon = async (Icon) => { //export const resetWebpackIcon = async (Icon: typeof IconDefault) => { + /* v8 ignore next */ if (!vueLeafletConfig.experimental.useResetWebpackIcon) return const modules = await Promise.all([ import('leaflet/dist/images/marker-icon-2x.png'), From 3ab050832a940c8d6d515a4ca9f9c8eca296a066 Mon Sep 17 00:00:00 2001 From: Max Engisch Date: Sun, 24 Aug 2025 21:19:52 +0200 Subject: [PATCH 05/15] test: add tests with LMap for controls. --- tests/unit/components/LControl.test.ts | 10 ++++++ tests/unit/components/LControlLayers.test.ts | 35 ++++++++++++++++++-- tests/unit/components/LMap.test.ts | 9 ++--- 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/tests/unit/components/LControl.test.ts b/tests/unit/components/LControl.test.ts index 50ab3ee..7daec97 100644 --- a/tests/unit/components/LControl.test.ts +++ b/tests/unit/components/LControl.test.ts @@ -12,6 +12,7 @@ import { import { testEmitsReady } from './helper/emitTests' import { mockRegisterControl, testControlRegistration } from './helper/injectionsTests' import { mergeReactiveProps } from './helper/props' +import { createWrapper as createMapWrapper } from './LMap.test' export const controlAbstractProps = mergeReactiveProps(componentProps, { position: 'bottomleft' @@ -23,6 +24,7 @@ const createWrapper = async (props = {}) => { const wrapper = shallowMount(LControl, { propsData: { position: 'topright', + disableScrollPropagation: true, ...props }, global: { @@ -54,4 +56,12 @@ const testCorrectInitialisation = (getWrapper: () => Promise>) = expect(obj).toBeDefined() expect(obj.options.position).toBe('topright') }) + + // TODO move from unit tests + it('creates a Leaflet control with Map', async () => { + const wrapper = await createMapWrapper({}, { default: LControl }) + const lControl = wrapper.findComponent(LControl) + expect(lControl.vm.leafletObject).toBeDefined() + expect((lControl.vm.leafletObject as Control)._map).toBeDefined() + }) } diff --git a/tests/unit/components/LControlLayers.test.ts b/tests/unit/components/LControlLayers.test.ts index e8f25ed..5f9d2d2 100644 --- a/tests/unit/components/LControlLayers.test.ts +++ b/tests/unit/components/LControlLayers.test.ts @@ -1,5 +1,6 @@ -import { flushPromises, shallowMount } from '@vue/test-utils' -import { describe } from 'vitest' +import { flushPromises, shallowMount, type VueWrapper } from '@vue/test-utils' +import { h } from 'vue' +import { describe, expect, it } from 'vitest' import { RegisterLayerControlInjection } from '../../../src/types/injectionKeys' import { testRemoveOnUnmount } from './helper/tests' import { testComponentPropBindings, testPropsBindingToLeaflet } from './helper/propsBindingTests' @@ -8,6 +9,9 @@ import LControlLayers from '../../../src/components/LControlLayers.vue' import { mockRegisterLayerControl, testControlLayerRegistration } from './helper/injectionsTests' import { controlAbstractProps } from './LControl.test' import { mergeReactiveProps } from './helper/props' +import { Control } from 'leaflet' +import { createWrapper as createMapWrapper } from './LMap.test' +import LTileLayer from '../../../src/components/LTileLayer.vue' const controlLayersProps = mergeReactiveProps(controlAbstractProps, {}) @@ -34,5 +38,32 @@ describe('LControlLayers.vue', () => { testPropsBindingToLeaflet(createWrapper, controlLayersProps) testRemoveOnUnmount(createWrapper) + testCorrectInitialisation(createWrapper) testControlLayerRegistration(createWrapper) }) + +const testCorrectInitialisation = (getWrapper: () => Promise>) => { + it('creates a Leaflet control layers with correct options', async () => { + const wrapper = await getWrapper() + const obj = wrapper.vm.leafletObject as Control.Layers + + expect(obj).toBeDefined() + expect(obj.options.position).toBe('topright') + }) + + // TODO move from unit tests + it.each(['base', 'overlay'])( + 'creates a Leaflet control layers with Map and a TileLayer', + async (layerType) => { + const wrapper = await createMapWrapper( + {}, + { default: [h(LTileLayer, { layerType: layerType, url: '' }), LControlLayers] } + ) + const lControlLayers = wrapper.findComponent(LControlLayers) + expect(lControlLayers.vm.leafletObject).toBeDefined() + expect((lControlLayers.vm.leafletObject as Control.Layers)._map).toBeDefined() + const lTileLayer = wrapper.findComponent(LTileLayer) + expect(lTileLayer.vm.leafletObject).toBeDefined() + } + ) +} diff --git a/tests/unit/components/LMap.test.ts b/tests/unit/components/LMap.test.ts index bd6e701..c571ac8 100644 --- a/tests/unit/components/LMap.test.ts +++ b/tests/unit/components/LMap.test.ts @@ -1,4 +1,4 @@ -import { flushPromises, shallowMount, type VueWrapper } from '@vue/test-utils' +import { flushPromises, mount, type VueWrapper } from '@vue/test-utils' import { describe, expect, it, vi } from 'vitest' import { componentProps, @@ -49,8 +49,8 @@ class ResizeObserver { global.ResizeObserver = ResizeObserver -const createWrapper = async (props = {}) => { - const wrapper = shallowMount(LMap, { +export const createWrapper = async (props = {}, slots = {}) => { + const wrapper = mount(LMap, { propsData: { width: '300px', height: '300px', @@ -58,7 +58,8 @@ const createWrapper = async (props = {}) => { zoom: 8, noBlockingAnimations: true, ...props - } + }, + slots: slots }) await flushPromises() await vi.waitFor(() => expect(wrapper.emitted('ready')).toBeTruthy()) From 34bde6de21c750028b147ec0f69765eed790ac9e Mon Sep 17 00:00:00 2001 From: Max Engisch Date: Sun, 24 Aug 2025 21:41:53 +0200 Subject: [PATCH 06/15] test: add tests for geoJson, GridLayer and LayerGroup --- src/functions/geoJSON.ts | 10 ++ src/functions/gridLayer.ts | 6 + tests/unit/components/LGeoJson.test.ts | 2 +- tests/unit/components/LLayerGroup.test.ts | 15 +- tests/unit/components/geo_replace.json | 196 +--------------------- 5 files changed, 31 insertions(+), 198 deletions(-) diff --git a/src/functions/geoJSON.ts b/src/functions/geoJSON.ts index 8299962..d91a8a8 100644 --- a/src/functions/geoJSON.ts +++ b/src/functions/geoJSON.ts @@ -53,12 +53,22 @@ export const setupGeoJSON = ( setOptionsStyle(newVal: PathOptions | StyleFunction) { leafletRef.value?.setStyle(newVal) }, + /* v8 ignore start - unreachable code, marked for removal */ + /** + * TODO remove unused code + * @deprecated unreachable code ? + */ getGeoJSONData() { return leafletRef.value?.toGeoJSON() }, + /** + * TODO remove unused code + * @deprecated unreachable code ? + */ getBounds() { return leafletRef.value?.getBounds() }, + /* v8 ignore end */ } return { options, methods } diff --git a/src/functions/gridLayer.ts b/src/functions/gridLayer.ts index 62b78a6..1fd14d3 100644 --- a/src/functions/gridLayer.ts +++ b/src/functions/gridLayer.ts @@ -84,9 +84,15 @@ export const setupGridLayer = ( const methods = { ...layerMethods, + /* v8 ignore start - unreachable code, marked for removal */ + /** + * TODO remove unused code + * @deprecated unreachable code ? + */ setTileComponent() { leafletRef.value?.redraw() }, + /* v8 ignore end */ } onUnmounted(() => { diff --git a/tests/unit/components/LGeoJson.test.ts b/tests/unit/components/LGeoJson.test.ts index 79a3aa8..7775f1a 100644 --- a/tests/unit/components/LGeoJson.test.ts +++ b/tests/unit/components/LGeoJson.test.ts @@ -9,7 +9,7 @@ import { GeoJSON, Layer } from 'leaflet' import LGeoJson from '../../../src/components/LGeoJson.vue' import { layerGroupProps } from './LLayerGroup.test' import geoJson from './geo.json' -import geoJsonReplace from './geo.json' +import geoJsonReplace from './geo_replace.json' import { mergeReactiveProps } from './helper/props' const geoJsonProps = mergeReactiveProps(layerGroupProps, { diff --git a/tests/unit/components/LLayerGroup.test.ts b/tests/unit/components/LLayerGroup.test.ts index bbff717..3fc1a62 100644 --- a/tests/unit/components/LLayerGroup.test.ts +++ b/tests/unit/components/LLayerGroup.test.ts @@ -1,6 +1,8 @@ -import { flushPromises, shallowMount, type VueWrapper } from '@vue/test-utils' +import { flushPromises, mount, type VueWrapper } from '@vue/test-utils' +import { h } from 'vue' import { describe, expect, it } from 'vitest' import LLayerGroup from '../../../src/components/LLayerGroup.vue' +import LTileLayer from '../../../src/components/LTileLayer.vue' import { AddLayerInjection, RemoveLayerInjection } from '../../../src/types/injectionKeys' import { testRemoveLayerOnUnmount } from './helper/tests' import { @@ -15,11 +17,12 @@ import { mergeReactiveProps } from './helper/props' export const layerGroupProps = mergeReactiveProps(layerProps, {}) -const createWrapper = async (props = {}) => { - const wrapper = shallowMount(LLayerGroup, { +const createWrapper = async (props = {}, slots = {}) => { + const wrapper = mount(LLayerGroup, { propsData: { ...props }, + slots: slots, global: { provide: { [AddLayerInjection as symbol]: mockAddLayer, @@ -47,6 +50,12 @@ const testCorrectInitialisation = (getWrapper: () => Promise>) = const wrapper = await getWrapper() const obj = wrapper.vm.leafletObject as LayerGroup + expect(obj).toBeDefined() + }) + it('creates a Leaflet layer group with layers', async () => { + const wrapper = await createWrapper({}, { default: h(LTileLayer, { url: '' }) }) + const obj = wrapper.vm.leafletObject as LayerGroup + expect(obj).toBeDefined() }) } diff --git a/tests/unit/components/geo_replace.json b/tests/unit/components/geo_replace.json index 87b90b2..8d68b15 100644 --- a/tests/unit/components/geo_replace.json +++ b/tests/unit/components/geo_replace.json @@ -24,200 +24,8 @@ 48.01883 ], [ - 0.41823, - 48.02207 - ], - [ - 0.41459, - 48.02236 - ], - [ - 0.41076, - 48.02087 - ], - [ - 0.40949, - 48.02228 - ], - [ - 0.40216, - 48.02477 - ], - [ - 0.40479, - 48.02662 - ], - [ - 0.40592, - 48.02866 - ], - [ - 0.41303, - 48.03584 - ], - [ - 0.42177, - 48.03701 - ], - [ - 0.42959, - 48.03786 - ], - [ - 0.43702, - 48.0342 - ], - [ - 0.44029, - 48.03295 - ], - [ - 0.44277, - 48.0311 - ], - [ - 0.44867, - 48.0276 - ], - [ - 0.45273, - 48.02469 - ], - [ - 0.45567, - 48.02699 - ], - [ - 0.45994, - 48.02917 - ], - [ - 0.46319, - 48.03008 - ], - [ - 0.46924, - 48.03341 - ], - [ - 0.47123, - 48.03219 - ], - [ - 0.4719, - 48.03017 - ], - [ - 0.47339, - 48.02993 - ], - [ - 0.47736, - 48.03134 - ], - [ - 0.47834, - 48.03019 - ], - [ - 0.47339, - 48.02597 - ], - [ - 0.47408, - 48.02553 - ], - [ - 0.47528, - 48.02213 - ], - [ - 0.47224, - 48.02092 - ], - [ - 0.47167, - 48.01798 - ], - [ - 0.46869, - 48.0151 - ], - [ - 0.4684, - 48.01366 - ], - [ - 0.47026, - 48.01225 - ], - [ - 0.46474, - 48.01099 - ], - [ - 0.46433, - 48.00946 - ], - [ - 0.46206, - 48.00734 - ], - [ - 0.45934, - 48.00838 - ], - [ - 0.45282, - 48.00724 - ], - [ - 0.44904, - 48.0057 - ], - [ - 0.44444, - 48.00311 - ], - [ - 0.44327, - 48.00434 - ], - [ - 0.44292, - 48.00623 - ], - [ - 0.4396, - 48.00915 - ], - [ - 0.44375, - 48.01132 - ], - [ - 0.44032, - 48.01541 - ], - [ - 0.43843, - 48.01556 - ], - [ - 0.43633, - 48.01476 - ], - [ - 0.42873, - 48.01342 - ], - [ - 0.42745, - 48.01345 - ], - [ - 0.42183, - 48.01156 + 0.42068, + 48.01531 ] ] ] From dd6a9c6b8d5075e3331dac4ca5af8a116f62d1dc Mon Sep 17 00:00:00 2001 From: Max Engisch Date: Sun, 24 Aug 2025 22:06:20 +0200 Subject: [PATCH 07/15] test: add tests for Tooltip and Popup --- tests/unit/components/LMarker.test.ts | 7 ++++--- tests/unit/components/LPopup.test.ts | 15 ++++++++++--- tests/unit/components/LTooltip.test.ts | 10 ++++++++- tests/unit/components/helper/tests.ts | 29 +++++++++++++++++++++++++- 4 files changed, 53 insertions(+), 8 deletions(-) diff --git a/tests/unit/components/LMarker.test.ts b/tests/unit/components/LMarker.test.ts index fe2893f..6fbe028 100644 --- a/tests/unit/components/LMarker.test.ts +++ b/tests/unit/components/LMarker.test.ts @@ -1,4 +1,4 @@ -import { flushPromises, shallowMount, type VueWrapper } from '@vue/test-utils' +import { flushPromises, mount, type VueWrapper } from '@vue/test-utils' import { describe, expect, it } from 'vitest' import LMarker from '../../../src/components/LMarker.vue' import { AddLayerInjection, RemoveLayerInjection } from '../../../src/types/injectionKeys' @@ -26,12 +26,13 @@ const markerProps = mergeReactiveProps(layerProps, { } }) -const createWrapper = async (props = {}) => { - const wrapper = shallowMount(LMarker, { +export const createWrapper = async (props = {}, slots = {}) => { + const wrapper = mount(LMarker, { propsData: { latLng: [44.48865, 11.3317], ...props }, + slots: slots, global: { provide: { [AddLayerInjection as symbol]: mockAddLayer, diff --git a/tests/unit/components/LPopup.test.ts b/tests/unit/components/LPopup.test.ts index 65d8fde..3f5e375 100644 --- a/tests/unit/components/LPopup.test.ts +++ b/tests/unit/components/LPopup.test.ts @@ -11,8 +11,9 @@ import { import { testEmitsReady } from './helper/emitTests' import { mockBindPopup, mockUnbindPopup } from './helper/injectionsTests' import { mergeReactiveProps } from './helper/props' -import { testUnbindPopupOnUnmount } from './helper/tests' +import { testBindPopup, testUnbindPopupOnUnmount } from './helper/tests' import { PopupProps } from '../../../src/functions/popup' +import { createWrapper as createMarkerWrapper } from './LMarker.test' const popupProps = mergeReactiveProps(popperProps, { latLng: new LatLng(44.5, 11.5) @@ -42,10 +43,12 @@ describe('LPopup.vue', () => { testUnbindPopupOnUnmount(createWrapper) testCorrectInitialisation(createWrapper) - // TEST bindPopup + testBindPopup(createWrapper) }) -const testCorrectInitialisation = (getWrapper: (props?: PopupProps) => Promise>) => { +const testCorrectInitialisation = ( + getWrapper: (props?: PopupProps) => Promise> +) => { it('creates a Leaflet popup with correct options', async () => { const wrapper = await getWrapper() const obj = wrapper.vm.leafletObject as Popup @@ -59,4 +62,10 @@ const testCorrectInitialisation = (getWrapper: (props?: PopupProps) => Promise { + const wrapper = await createMarkerWrapper({}, { default: LPopup }) + expect(wrapper.vm.leafletObject).toBeDefined() + const lPopup = wrapper.findComponent(LPopup) + expect(lPopup.vm.leafletObject).toBeDefined() + }) } diff --git a/tests/unit/components/LTooltip.test.ts b/tests/unit/components/LTooltip.test.ts index add58f3..09783ab 100644 --- a/tests/unit/components/LTooltip.test.ts +++ b/tests/unit/components/LTooltip.test.ts @@ -11,8 +11,9 @@ import { testEmitsReady } from './helper/emitTests' import { mockBindTooltip, mockUnbindTooltip } from './helper/injectionsTests' import { Tooltip } from 'leaflet' import { mergeReactiveProps } from './helper/props' -import { testUnbindTooltipOnUnmount } from './helper/tests' +import { testBindTooltip, testUnbindTooltipOnUnmount } from './helper/tests' import { TooltipProps } from '../../../src/functions/tooltip' +import { createWrapper as createMarkerWrapper } from './LMarker.test' const tooltipProps = mergeReactiveProps(popperProps, {}) @@ -62,6 +63,7 @@ describe('LTooltip.vue', () => { testUnbindTooltipOnUnmount(createWrapper) testCorrectInitialisation(createWrapper) + testBindTooltip(createWrapper) }) const testCorrectInitialisation = ( @@ -85,4 +87,10 @@ const testCorrectInitialisation = ( ' -->Something' ) }) + it('creates a Leaflet layer with a tooltip', async () => { + const wrapper = await createMarkerWrapper({}, { default: LTooltip }) + expect(wrapper.vm.leafletObject).toBeDefined() + const lTooltip = wrapper.findComponent(LTooltip) + expect(lTooltip.vm.leafletObject).toBeDefined() + }) } diff --git a/tests/unit/components/helper/tests.ts b/tests/unit/components/helper/tests.ts index cf7bcea..be6bae6 100644 --- a/tests/unit/components/helper/tests.ts +++ b/tests/unit/components/helper/tests.ts @@ -1,6 +1,12 @@ import { beforeEach, expect, it, vi } from 'vitest' import { VueWrapper } from '@vue/test-utils' -import { mockRemoveLayer, mockUnbindPopup, mockUnbindTooltip } from './injectionsTests' +import { + mockBindPopup, + mockBindTooltip, + mockRemoveLayer, + mockUnbindPopup, + mockUnbindTooltip +} from './injectionsTests' export const testRemoveOnUnmount = (getWrapper: () => Promise>) => { it('removes the leafletObject on unmount', async () => { @@ -23,6 +29,17 @@ export const testRemoveLayerOnUnmount = (getWrapper: () => Promise Promise>) => { + beforeEach(() => { + mockBindTooltip.mockReset() + }) + it('binds the tooltip on mount', async () => { + const wrapper = await getWrapper() + wrapper.unmount() + expect(mockBindTooltip).toHaveBeenCalledOnce() + }) +} + export const testUnbindTooltipOnUnmount = (getWrapper: () => Promise>) => { beforeEach(() => { mockUnbindTooltip.mockReset() @@ -34,6 +51,16 @@ export const testUnbindTooltipOnUnmount = (getWrapper: () => Promise Promise>) => { + beforeEach(() => { + mockBindPopup.mockReset() + }) + it('binds the popup on mount', async () => { + await getWrapper() + expect(mockBindPopup).toHaveBeenCalledOnce() + }) +} + export const testUnbindPopupOnUnmount = (getWrapper: () => Promise>) => { beforeEach(() => { mockUnbindPopup.mockReset() From 087ee26ac44b439361bfac87bb82f18d86954056 Mon Sep 17 00:00:00 2001 From: Max Engisch Date: Sun, 24 Aug 2025 22:19:13 +0200 Subject: [PATCH 08/15] test: update tests for imageOverlay and add deprecations --- src/functions/imageOverlay.ts | 12 +++++++++++- tests/unit/components/LImageOverlay.test.ts | 9 +++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/functions/imageOverlay.ts b/src/functions/imageOverlay.ts index 1cb6ce2..9adabb2 100644 --- a/src/functions/imageOverlay.ts +++ b/src/functions/imageOverlay.ts @@ -107,37 +107,47 @@ export const setupImageOverlay = ( setBounds(bounds: LatLngBounds) { return leafletRef.value?.setBounds(bounds) }, + /* v8 ignore start - unreachable code, marked for removal */ /** * Get the bounds that this ImageOverlay covers + * TODO remove unused code + * @deprecated unreachable code ? */ getBounds(): LatLngBounds | undefined { return leafletRef.value?.getBounds() }, /** * Returns the instance of HTMLImageElement used by this overlay. + * TODO remove unused code + * @deprecated unreachable code ? */ getElement(): HTMLElement | undefined { return leafletRef.value?.getElement() }, /** * Brings the layer to the top of all overlays. + * TODO remove unused code + * @deprecated unreachable code ? */ bringToFront() { return leafletRef.value?.bringToFront() }, /** * Brings the layer to the bottom of all overlays. + * TODO remove unused code + * @deprecated unreachable code ? */ bringToBack() { return leafletRef.value?.bringToBack() }, + /* v8 ignore end */ /** * Changes the zIndex of the image overlay. * @param {number} zIndex */ setZIndex(zIndex: number) { return leafletRef.value?.setZIndex(zIndex) - }, + } } return { options, methods } diff --git a/tests/unit/components/LImageOverlay.test.ts b/tests/unit/components/LImageOverlay.test.ts index 2261abb..229a4e4 100644 --- a/tests/unit/components/LImageOverlay.test.ts +++ b/tests/unit/components/LImageOverlay.test.ts @@ -20,13 +20,18 @@ export const imageOverlayAbstractProps = mergeReactiveProps(layerProps, { }) const imageOverlayProps = mergeReactiveProps(imageOverlayAbstractProps, { - url: 'https://www.printablee.com/postpic/2011/06/blank-100-square-grid-paper_405041.jpg' + url: 'replace.jpg', + expecting: { + url: (leafletObject: ImageOverlay) => { + expect(leafletObject._url).toBe(imageOverlayProps.url) + } + } }) const createWrapper = async (props = {}) => { const wrapper = shallowMount(LImageOverlay, { propsData: { - url: 'https://www.printablee.com/postpic/2011/06/blank-100-square-grid-paper_405041.jpg', + url: 'some.jpg', bounds: [ [0, 0], [100, 100] From 49007bd34132ba029bd45eff03565502856e75c9 Mon Sep 17 00:00:00 2001 From: Max Engisch Date: Mon, 25 Aug 2025 12:07:53 +0200 Subject: [PATCH 09/15] test: add multiple reactive props values and update map tests. --- src/components/LMap.vue | 16 +-- src/functions/map.ts | 6 +- tests/unit/components/LControlLayers.test.ts | 2 +- tests/unit/components/LMap.test.ts | 98 +++++++++++++++++-- tests/unit/components/helper/geo.ts | 16 +++ tests/unit/components/helper/props.ts | 8 +- .../components/helper/propsBindingTests.ts | 21 ++-- vitest.config.ts | 1 + 8 files changed, 129 insertions(+), 39 deletions(-) create mode 100644 tests/unit/components/helper/geo.ts diff --git a/src/components/LMap.vue b/src/components/LMap.vue index 68a15d0..74391d1 100644 --- a/src/components/LMap.vue +++ b/src/components/LMap.vue @@ -145,15 +145,9 @@ function useOptions() { const fitBoundsOptions = computed((): FitBoundsOptions => { const result: FitBoundsOptions = zoomPanOptions.value - if (props.padding) { - result.padding = props.padding - } - if (props.paddingTopLeft) { - result.paddingTopLeft = props.paddingTopLeft - } - if (props.paddingBottomRight) { - result.paddingBottomRight = props.paddingBottomRight - } + result.padding = props.padding ? props.padding : undefined + result.paddingTopLeft = props.paddingTopLeft ? props.paddingTopLeft : undefined + result.paddingBottomRight = props.paddingBottomRight ? props.paddingBottomRight : undefined return result }) return { zoomPanOptions, fitBoundsOptions } @@ -245,13 +239,13 @@ function useMethods() { const boundsChanged = !oldBounds.equals(newBounds, 0) // set maxMargin to 0 - check exact equals if (boundsChanged) { lastSetBounds.value = newBounds - leafletObject.value!.fitBounds(newBounds) + fitBounds(newBounds) } } const lastSetCenter = ref() function setCenter(center: [number, number]) { - if (center == null) { + if (!center) { return } const newCenter = new LatLng(...center) diff --git a/src/functions/map.ts b/src/functions/map.ts index 815abaf..9164663 100644 --- a/src/functions/map.ts +++ b/src/functions/map.ts @@ -43,17 +43,17 @@ export interface MapProps extends ComponentProps { */ maxZoom?: number /** - * The paddingBottomRight of the map + * The paddingBottomRight of the map. Applies only when LMap.fitBounds is called. * @reactive native */ paddingBottomRight?: PointExpression /** - * The paddingTopLeft of the map + * The paddingTopLeft of the map. Applies only when LMap.fitBounds is called. * @reactive native */ paddingTopLeft?: PointExpression /** - * The padding of the map + * The padding of the map. Applies only when LMap.fitBounds is called. * @reactive native */ padding?: PointExpression diff --git a/tests/unit/components/LControlLayers.test.ts b/tests/unit/components/LControlLayers.test.ts index 5f9d2d2..9769846 100644 --- a/tests/unit/components/LControlLayers.test.ts +++ b/tests/unit/components/LControlLayers.test.ts @@ -57,7 +57,7 @@ const testCorrectInitialisation = (getWrapper: () => Promise>) = async (layerType) => { const wrapper = await createMapWrapper( {}, - { default: [h(LTileLayer, { layerType: layerType, url: '' }), LControlLayers] } + { default: [h(LTileLayer, { layerType: layerType, url: '' }), LControlLayers, h(LTileLayer, { layerType: layerType, url: '' })] } ) const lControlLayers = wrapper.findComponent(LControlLayers) expect(lControlLayers.vm.leafletObject).toBeDefined() diff --git a/tests/unit/components/LMap.test.ts b/tests/unit/components/LMap.test.ts index c571ac8..124da1c 100644 --- a/tests/unit/components/LMap.test.ts +++ b/tests/unit/components/LMap.test.ts @@ -6,23 +6,37 @@ import { testPropsBindingToLeaflet } from './helper/propsBindingTests' import { testEmitsReady } from './helper/emitTests' -import { LatLng, LatLngBounds, Map } from 'leaflet' +import { CRS, LatLngBounds, LatLngExpression, Map } from 'leaflet' import LMap from '../../../src/components/LMap.vue' import { mergeReactiveProps } from './helper/props' +import 'leaflet/dist/leaflet.css' +import { expectBoundsToBeClose } from './helper/geo' const mapProps = mergeReactiveProps(componentProps, { width: '400px', height: '400px', - center: [44.5, 10.5], - // TEST bounds: new LatLngBounds([44.5, 10.5], [47.5, 11.5]), + center: { + values: [undefined, null, [44.5, 10.5]] + }, + bounds: { + values: [ + [ + [-5, 5], + [5, -5] + ] as LatLngExpression[], + undefined, + null, + [null, undefined] as LatLngExpression[] + ] + }, maxBounds: new LatLngBounds([44.5, 10.5], [47.5, 11.5]), zoom: 10, minZoom: 3, maxZoom: 15, - // TEST paddingBottomRight: [20, 20], - // TEST paddingTopLeft: [20, 20], - // TEST padding: [20, 20], - // TEST crs: CRS.Simple, + paddingBottomRight: [100, 100], + paddingTopLeft: [100, 100], + padding: [75, 75], + crs: CRS.Simple, expecting: { width: (leafletObject: Map) => { expect(leafletObject.getContainer().style.width).toBe('400px') @@ -30,13 +44,29 @@ const mapProps = mergeReactiveProps(componentProps, { height: (leafletObject: Map) => { expect(leafletObject.getContainer().style.height).toBe('400px') }, - center: (leafletObject: Map) => { - expect(leafletObject.getCenter()).toEqual(new LatLng(44.5, 10.5)) + center: (leafletObject: Map, iteration: number) => { + const [lat, lng] = iteration >= 2 ? mapProps.center.values[iteration] : [45, 10] + expect(leafletObject.getCenter().lat).toBeCloseTo(lat) + expect(leafletObject.getCenter().lng).toBeCloseTo(lng) + }, + bounds: (leafletObject: Map) => { + const lBounds = leafletObject.getBounds() + const expectedBounds = new LatLngBounds(mapProps.bounds.values[0]) + expectBoundsToBeClose(lBounds, expectedBounds, [2, 2, 1, 1]) }, maxBounds: (leafletObject: Map) => { expect(leafletObject.options.maxBounds).toStrictEqual( new LatLngBounds([44.5, 10.5], [47.5, 11.5]) ) + }, + paddingBottomRight: (leafletObject: Map, i, wrapper) => { + expect(wrapper.vm.$props.paddingBottomRight).toStrictEqual(mapProps.paddingBottomRight) + }, + paddingTopLeft: (leafletObject: Map, i, wrapper) => { + expect(wrapper.vm.$props.paddingTopLeft).toStrictEqual(mapProps.paddingTopLeft) + }, + padding: (leafletObject: Map, i, wrapper) => { + expect(wrapper.vm.$props.padding).toStrictEqual(mapProps.padding) } } }) @@ -49,6 +79,15 @@ class ResizeObserver { global.ResizeObserver = ResizeObserver +Object.defineProperty(HTMLElement.prototype, 'clientWidth', { + configurable: true, + value: 300 +}) +Object.defineProperty(HTMLElement.prototype, 'clientHeight', { + configurable: true, + value: 300 +}) + export const createWrapper = async (props = {}, slots = {}) => { const wrapper = mount(LMap, { propsData: { @@ -57,9 +96,13 @@ export const createWrapper = async (props = {}, slots = {}) => { center: [45, 10], zoom: 8, noBlockingAnimations: true, + options: { + zoomSnap: 0 + }, ...props }, - slots: slots + slots: slots, + attachTo: document.body }) await flushPromises() await vi.waitFor(() => expect(wrapper.emitted('ready')).toBeTruthy()) @@ -72,6 +115,7 @@ describe('LMap.vue', () => { testPropsBindingToLeaflet(createWrapper, mapProps) testCorrectInitialisation(createWrapper) + testFitBounds(createWrapper) }) const testCorrectInitialisation = (getWrapper: () => Promise>) => { @@ -84,3 +128,37 @@ const testCorrectInitialisation = (getWrapper: () => Promise>) = expect(obj.options).toBeDefined() }) } + +const testFitBounds = (getWrapper: () => Promise>) => { + it('fits the map bounds with different paddings', async () => { + const wrapper = await getWrapper() + const obj = wrapper.vm.leafletObject as Map + // paddingBottomRight + await wrapper.setProps({ + paddingBottomRight: mapProps.paddingBottomRight, + paddingTopLeft: undefined, + padding: undefined + }) + wrapper.vm.fitBounds(mapProps.bounds.values[0]) + let expectedBounds = new LatLngBounds([-10, -5], [5, 10]) + expectBoundsToBeClose(obj.getBounds(), expectedBounds, [1, 0, 1, 1]) + // paddingTopLeft + await wrapper.setProps({ + paddingBottomRight: undefined, + paddingTopLeft: mapProps.paddingTopLeft, + padding: undefined + }) + wrapper.vm.fitBounds(mapProps.bounds.values[0]) + expectedBounds = new LatLngBounds([-5, -10], [10, 5]) + expectBoundsToBeClose(obj.getBounds(), expectedBounds, 1) + // padding + await wrapper.setProps({ + paddingBottomRight: undefined, + paddingTopLeft: undefined, + padding: mapProps.padding + }) + wrapper.vm.fitBounds(mapProps.bounds.values[0]) + expectedBounds = new LatLngBounds([-10, -10], [10, 10]) + expectBoundsToBeClose(obj.getBounds(), expectedBounds, [0, 1, 1, 1]) + }) +} diff --git a/tests/unit/components/helper/geo.ts b/tests/unit/components/helper/geo.ts new file mode 100644 index 0000000..a80b40a --- /dev/null +++ b/tests/unit/components/helper/geo.ts @@ -0,0 +1,16 @@ +import { LatLngBounds } from 'leaflet' +import { expect } from 'vitest' + +export function expectBoundsToBeClose( + first: LatLngBounds, + second: LatLngBounds, + numDigits: number | number[] +) { + if (!Array.isArray(numDigits)) { + numDigits = [numDigits, numDigits, numDigits, numDigits] + } + expect(first.getNorth()).toBeCloseTo(second.getNorth(), numDigits[0]) + expect(first.getSouth()).toBeCloseTo(second.getSouth(), numDigits[1]) + expect(first.getWest()).toBeCloseTo(second.getWest(), numDigits[2]) + expect(first.getEast()).toBeCloseTo(second.getEast(), numDigits[3]) +} diff --git a/tests/unit/components/helper/props.ts b/tests/unit/components/helper/props.ts index 56c4cc2..029013e 100644 --- a/tests/unit/components/helper/props.ts +++ b/tests/unit/components/helper/props.ts @@ -1,4 +1,5 @@ import { InterfaceDeclaration, Project, SyntaxKind } from 'ts-morph' +import { type VueWrapper } from '@vue/test-utils' const project = new Project({ tsConfigFilePath: 'tsconfig.json' @@ -67,14 +68,11 @@ function collectReactivePropCount(interfaceDecl: InterfaceDeclaration) { } type ReactiveProps = { - expecting?: Record void> + expecting?: Record void> [key: string]: any } -export function mergeReactiveProps< - P extends ReactiveProps, - C extends ReactiveProps ->( +export function mergeReactiveProps

( parentProps: P, childProps: C ): P & C { diff --git a/tests/unit/components/helper/propsBindingTests.ts b/tests/unit/components/helper/propsBindingTests.ts index 8a82d81..48466e8 100644 --- a/tests/unit/components/helper/propsBindingTests.ts +++ b/tests/unit/components/helper/propsBindingTests.ts @@ -28,18 +28,21 @@ export function testPropsBindingToLeaflet( async (propName, newValue) => { const wrapper = await getWrapper() const leafletObject = wrapper.vm.leafletObject + const values = Array.isArray(newValue?.values) ? newValue.values : [newValue] - await wrapper.setProps({ [propName]: newValue }) - await flushPromises() + for (const [i, value] of values.entries()) { + await wrapper.setProps({ [propName]: value }) + await flushPromises() - if (updatedProps['expecting']?.[propName]) { - updatedProps['expecting']?.[propName](leafletObject) - return + if (updatedProps['expecting']?.[propName]) { + updatedProps['expecting']?.[propName](leafletObject, i, wrapper) + continue + } + const getter = 'get' + capitalizeFirstLetter(propName) + if (isFunction(leafletObject[getter])) + expect(leafletObject[getter]()).toStrictEqual(newValue) + else expect(leafletObject.options[propName]).toBe(newValue) } - const getter = 'get' + capitalizeFirstLetter(propName) - if (isFunction(leafletObject[getter])) - expect(leafletObject[getter]()).toStrictEqual(newValue) - else expect(leafletObject.options[propName]).toBe(newValue) } ) } diff --git a/vitest.config.ts b/vitest.config.ts index 2f8eb9f..142c78f 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -10,6 +10,7 @@ export default mergeConfig( exclude: [...configDefaults.exclude, 'e2e/**', 'node_modules'], root: fileURLToPath(new URL('./', import.meta.url)), coverage: { + provider: 'v8', include: ['src/**'] } } From 42a0ff5c96733bf6d4b75b960861b18f0c830936 Mon Sep 17 00:00:00 2001 From: Max Engisch Date: Mon, 25 Aug 2025 16:24:03 +0200 Subject: [PATCH 10/15] refactor(tests): move exported functions and variables to an additional file to reduce the number of tests. --- tests/unit/components/LCircle.test.ts | 2 +- tests/unit/components/LCircleMarker.test.ts | 13 +--- tests/unit/components/LControl.test.ts | 16 ++--- .../components/LControlAttribution.test.ts | 2 +- tests/unit/components/LControlLayers.test.ts | 13 +++- tests/unit/components/LControlScale.test.ts | 2 +- tests/unit/components/LControlZoom.test.ts | 2 +- tests/unit/components/LFeatureGroup.test.ts | 2 +- tests/unit/components/LGeoJson.test.ts | 2 +- tests/unit/components/LGridLayer.test.ts | 13 +--- tests/unit/components/LImageOverlay.test.ts | 16 ++--- tests/unit/components/LLayerGroup.test.ts | 10 +-- tests/unit/components/LMap.test.ts | 71 +++++-------------- tests/unit/components/LMarker.test.ts | 38 +++------- tests/unit/components/LPolygon.test.ts | 19 +---- tests/unit/components/LPolyline.test.ts | 22 +----- tests/unit/components/LPopup.test.ts | 2 +- tests/unit/components/LRectangle.test.ts | 2 +- tests/unit/components/LSVGOverlay.test.ts | 2 +- tests/unit/components/LTileLayer.test.ts | 12 +--- tests/unit/components/LTooltip.test.ts | 2 +- tests/unit/components/LVideoOverlay.test.ts | 2 +- tests/unit/components/LWmsTileLayer.test.ts | 2 +- .../unit/components/wrapper/LCircleMarker.ts | 8 +++ tests/unit/components/wrapper/LControl.ts | 6 ++ tests/unit/components/wrapper/LGridLayer.ts | 7 ++ .../unit/components/wrapper/LImageOverlay.ts | 9 +++ tests/unit/components/wrapper/LLayerGroup.ts | 4 ++ tests/unit/components/wrapper/LMap.ts | 41 +++++++++++ tests/unit/components/wrapper/LMarker.ts | 23 ++++++ .../unit/components/wrapper/LPolygonProps.ts | 20 ++++++ tests/unit/components/wrapper/LPolyline.ts | 22 ++++++ tests/unit/components/wrapper/LTileLayer.ts | 13 ++++ 33 files changed, 222 insertions(+), 198 deletions(-) create mode 100644 tests/unit/components/wrapper/LCircleMarker.ts create mode 100644 tests/unit/components/wrapper/LControl.ts create mode 100644 tests/unit/components/wrapper/LGridLayer.ts create mode 100644 tests/unit/components/wrapper/LImageOverlay.ts create mode 100644 tests/unit/components/wrapper/LLayerGroup.ts create mode 100644 tests/unit/components/wrapper/LMap.ts create mode 100644 tests/unit/components/wrapper/LMarker.ts create mode 100644 tests/unit/components/wrapper/LPolygonProps.ts create mode 100644 tests/unit/components/wrapper/LPolyline.ts create mode 100644 tests/unit/components/wrapper/LTileLayer.ts diff --git a/tests/unit/components/LCircle.test.ts b/tests/unit/components/LCircle.test.ts index b581895..6508b30 100644 --- a/tests/unit/components/LCircle.test.ts +++ b/tests/unit/components/LCircle.test.ts @@ -7,8 +7,8 @@ import { testRemoveLayerOnUnmount } from './helper/tests' import { testComponentPropBindings, testPropsBindingToLeaflet } from './helper/propsBindingTests' import { testEmitsReady } from './helper/emitTests' import { mockAddLayer, mockRemoveLayer, testAddLayer } from './helper/injectionsTests' -import { circleMarkerProps } from './LCircleMarker.test' import { mergeReactiveProps } from './helper/props' +import { circleMarkerProps } from './wrapper/LCircleMarker' const circleProps = mergeReactiveProps(circleMarkerProps, { radius: 10000 diff --git a/tests/unit/components/LCircleMarker.test.ts b/tests/unit/components/LCircleMarker.test.ts index 2effc3f..4a95cb5 100644 --- a/tests/unit/components/LCircleMarker.test.ts +++ b/tests/unit/components/LCircleMarker.test.ts @@ -4,19 +4,10 @@ import LCircleMarker from '../../../src/components/LCircleMarker.vue' import { AddLayerInjection, RemoveLayerInjection } from '../../../src/types/injectionKeys' import { Circle, LatLng } from 'leaflet' import { testRemoveLayerOnUnmount } from './helper/tests' -import { - pathProps, - testComponentPropBindings, - testPropsBindingToLeaflet -} from './helper/propsBindingTests' +import { testComponentPropBindings, testPropsBindingToLeaflet } from './helper/propsBindingTests' import { testEmitsReady } from './helper/emitTests' import { mockAddLayer, mockRemoveLayer, testAddLayer } from './helper/injectionsTests' -import { mergeReactiveProps } from './helper/props' - -export const circleMarkerProps = mergeReactiveProps(pathProps, { - radius: 15, - latLng: new LatLng(44.5, 11.5) -}) +import { circleMarkerProps } from './wrapper/LCircleMarker' const createWrapper = async (props = {}) => { const wrapper = shallowMount(LCircleMarker, { diff --git a/tests/unit/components/LControl.test.ts b/tests/unit/components/LControl.test.ts index 7daec97..2b6bee4 100644 --- a/tests/unit/components/LControl.test.ts +++ b/tests/unit/components/LControl.test.ts @@ -4,21 +4,14 @@ import LControl from '../../../src/components/LControl.vue' import { RegisterControlInjection } from '../../../src/types/injectionKeys' import { Control } from 'leaflet' import { testRemoveOnUnmount } from './helper/tests' -import { - componentProps, - testComponentPropBindings, - testPropsBindingToLeaflet -} from './helper/propsBindingTests' +import { testComponentPropBindings, testPropsBindingToLeaflet } from './helper/propsBindingTests' import { testEmitsReady } from './helper/emitTests' import { mockRegisterControl, testControlRegistration } from './helper/injectionsTests' import { mergeReactiveProps } from './helper/props' -import { createWrapper as createMapWrapper } from './LMap.test' +import { createMapWrapper } from './wrapper/LMap' +import { controlAbstractProps } from './wrapper/LControl' -export const controlAbstractProps = mergeReactiveProps(componentProps, { - position: 'bottomleft' -}) - -export const controlProps = mergeReactiveProps(controlAbstractProps, {}) +const controlProps = mergeReactiveProps(controlAbstractProps, {}) const createWrapper = async (props = {}) => { const wrapper = shallowMount(LControl, { @@ -62,6 +55,7 @@ const testCorrectInitialisation = (getWrapper: () => Promise>) = const wrapper = await createMapWrapper({}, { default: LControl }) const lControl = wrapper.findComponent(LControl) expect(lControl.vm.leafletObject).toBeDefined() + // @ts-expect-error _map is private so not in the types expect((lControl.vm.leafletObject as Control)._map).toBeDefined() }) } diff --git a/tests/unit/components/LControlAttribution.test.ts b/tests/unit/components/LControlAttribution.test.ts index 6bd22d2..1767894 100644 --- a/tests/unit/components/LControlAttribution.test.ts +++ b/tests/unit/components/LControlAttribution.test.ts @@ -7,8 +7,8 @@ import { testRemoveOnUnmount } from './helper/tests' import { testComponentPropBindings, testPropsBindingToLeaflet } from './helper/propsBindingTests' import { testEmitsReady } from './helper/emitTests' import { mockRegisterControl, testControlRegistration } from './helper/injectionsTests' -import { controlAbstractProps } from './LControl.test' import { mergeReactiveProps } from './helper/props' +import { controlAbstractProps } from './wrapper/LControl' const controlAttributionProps = mergeReactiveProps(controlAbstractProps, { prefix: 'new prefix' diff --git a/tests/unit/components/LControlLayers.test.ts b/tests/unit/components/LControlLayers.test.ts index 9769846..7d310ce 100644 --- a/tests/unit/components/LControlLayers.test.ts +++ b/tests/unit/components/LControlLayers.test.ts @@ -7,11 +7,11 @@ import { testComponentPropBindings, testPropsBindingToLeaflet } from './helper/p import { testEmitsReady } from './helper/emitTests' import LControlLayers from '../../../src/components/LControlLayers.vue' import { mockRegisterLayerControl, testControlLayerRegistration } from './helper/injectionsTests' -import { controlAbstractProps } from './LControl.test' import { mergeReactiveProps } from './helper/props' import { Control } from 'leaflet' -import { createWrapper as createMapWrapper } from './LMap.test' import LTileLayer from '../../../src/components/LTileLayer.vue' +import { createMapWrapper } from './wrapper/LMap' +import { controlAbstractProps } from './wrapper/LControl' const controlLayersProps = mergeReactiveProps(controlAbstractProps, {}) @@ -57,10 +57,17 @@ const testCorrectInitialisation = (getWrapper: () => Promise>) = async (layerType) => { const wrapper = await createMapWrapper( {}, - { default: [h(LTileLayer, { layerType: layerType, url: '' }), LControlLayers, h(LTileLayer, { layerType: layerType, url: '' })] } + { + default: [ + h(LTileLayer, { layerType: layerType, url: '' }), + LControlLayers, + h(LTileLayer, { layerType: layerType, url: '' }) + ] + } ) const lControlLayers = wrapper.findComponent(LControlLayers) expect(lControlLayers.vm.leafletObject).toBeDefined() + // @ts-expect-error _map is private so not in the types expect((lControlLayers.vm.leafletObject as Control.Layers)._map).toBeDefined() const lTileLayer = wrapper.findComponent(LTileLayer) expect(lTileLayer.vm.leafletObject).toBeDefined() diff --git a/tests/unit/components/LControlScale.test.ts b/tests/unit/components/LControlScale.test.ts index 1199061..d21b59c 100644 --- a/tests/unit/components/LControlScale.test.ts +++ b/tests/unit/components/LControlScale.test.ts @@ -7,8 +7,8 @@ import { testRemoveOnUnmount } from './helper/tests' import { testComponentPropBindings, testPropsBindingToLeaflet } from './helper/propsBindingTests' import { testEmitsReady } from './helper/emitTests' import { mockRegisterControl, testControlRegistration } from './helper/injectionsTests' -import { controlAbstractProps } from './LControl.test' import { mergeReactiveProps } from './helper/props' +import { controlAbstractProps } from './wrapper/LControl' const controlScaleProps = mergeReactiveProps(controlAbstractProps, {}) diff --git a/tests/unit/components/LControlZoom.test.ts b/tests/unit/components/LControlZoom.test.ts index e318af5..b62b9b6 100644 --- a/tests/unit/components/LControlZoom.test.ts +++ b/tests/unit/components/LControlZoom.test.ts @@ -7,8 +7,8 @@ import { testRemoveOnUnmount } from './helper/tests' import { testComponentPropBindings, testPropsBindingToLeaflet } from './helper/propsBindingTests' import { testEmitsReady } from './helper/emitTests' import { mockRegisterControl, testControlRegistration } from './helper/injectionsTests' -import { controlAbstractProps } from './LControl.test' import { mergeReactiveProps } from './helper/props' +import { controlAbstractProps } from './wrapper/LControl' const controlZoomProps = mergeReactiveProps(controlAbstractProps, {}) diff --git a/tests/unit/components/LFeatureGroup.test.ts b/tests/unit/components/LFeatureGroup.test.ts index dc31827..3002486 100644 --- a/tests/unit/components/LFeatureGroup.test.ts +++ b/tests/unit/components/LFeatureGroup.test.ts @@ -7,8 +7,8 @@ import { testComponentPropBindings, testPropsBindingToLeaflet } from './helper/p import { testEmitsReady } from './helper/emitTests' import { mockAddLayer, mockRemoveLayer, testAddLayer } from './helper/injectionsTests' import { FeatureGroup } from 'leaflet' -import { layerGroupProps } from './LLayerGroup.test' import { mergeReactiveProps } from './helper/props' +import { layerGroupProps } from './wrapper/LLayerGroup' const featureGroupProps = mergeReactiveProps(layerGroupProps, {}) diff --git a/tests/unit/components/LGeoJson.test.ts b/tests/unit/components/LGeoJson.test.ts index 7775f1a..6ec2961 100644 --- a/tests/unit/components/LGeoJson.test.ts +++ b/tests/unit/components/LGeoJson.test.ts @@ -7,10 +7,10 @@ import { testEmitsReady } from './helper/emitTests' import { mockAddLayer, mockRemoveLayer, testAddLayer } from './helper/injectionsTests' import { GeoJSON, Layer } from 'leaflet' import LGeoJson from '../../../src/components/LGeoJson.vue' -import { layerGroupProps } from './LLayerGroup.test' import geoJson from './geo.json' import geoJsonReplace from './geo_replace.json' import { mergeReactiveProps } from './helper/props' +import { layerGroupProps } from './wrapper/LLayerGroup' const geoJsonProps = mergeReactiveProps(layerGroupProps, { geojson: geoJsonReplace, diff --git a/tests/unit/components/LGridLayer.test.ts b/tests/unit/components/LGridLayer.test.ts index c16f41d..f5c7a4f 100644 --- a/tests/unit/components/LGridLayer.test.ts +++ b/tests/unit/components/LGridLayer.test.ts @@ -3,21 +3,12 @@ import { h } from 'vue' import { describe, expect, it } from 'vitest' import { AddLayerInjection, RemoveLayerInjection } from '../../../src/types/injectionKeys' import { testRemoveLayerOnUnmount } from './helper/tests' -import { - layerProps, - testComponentPropBindings, - testPropsBindingToLeaflet -} from './helper/propsBindingTests' +import { testComponentPropBindings, testPropsBindingToLeaflet } from './helper/propsBindingTests' import { testEmitsReady } from './helper/emitTests' import { mockAddLayer, mockRemoveLayer, testAddLayer } from './helper/injectionsTests' import { GridLayer } from 'leaflet' import LGridLayer from '../../../src/components/LGridLayer.vue' -import { mergeReactiveProps } from './helper/props' - -export const gridLayerProps = mergeReactiveProps(layerProps, { - opacity: 0.5, - zIndex: 50 -}) +import { gridLayerProps } from './wrapper/LGridLayer' const createWrapper = async (props = {}) => { const wrapper = shallowMount(LGridLayer, { diff --git a/tests/unit/components/LImageOverlay.test.ts b/tests/unit/components/LImageOverlay.test.ts index 229a4e4..5fe0032 100644 --- a/tests/unit/components/LImageOverlay.test.ts +++ b/tests/unit/components/LImageOverlay.test.ts @@ -2,27 +2,19 @@ import { flushPromises, shallowMount, type VueWrapper } from '@vue/test-utils' import { describe, expect, it } from 'vitest' import { AddLayerInjection, RemoveLayerInjection } from '../../../src/types/injectionKeys' import { testRemoveLayerOnUnmount } from './helper/tests' -import { - layerProps, - testComponentPropBindings, - testPropsBindingToLeaflet -} from './helper/propsBindingTests' +import { testComponentPropBindings, testPropsBindingToLeaflet } from './helper/propsBindingTests' import { testEmitsReady } from './helper/emitTests' import { mockAddLayer, mockRemoveLayer, testAddLayer } from './helper/injectionsTests' -import { ImageOverlay, LatLngBounds, type LatLngBoundsLiteral } from 'leaflet' +import { ImageOverlay, type LatLngBoundsLiteral } from 'leaflet' import LImageOverlay from '../../../src/components/LImageOverlay.vue' import { mergeReactiveProps } from './helper/props' - -export const imageOverlayAbstractProps = mergeReactiveProps(layerProps, { - opacity: 0.5, - zIndex: 50, - bounds: new LatLngBounds([0, 0], [50, 50]) -}) +import { imageOverlayAbstractProps } from './wrapper/LImageOverlay' const imageOverlayProps = mergeReactiveProps(imageOverlayAbstractProps, { url: 'replace.jpg', expecting: { url: (leafletObject: ImageOverlay) => { + // @ts-expect-error _url is private so not in the types expect(leafletObject._url).toBe(imageOverlayProps.url) } } diff --git a/tests/unit/components/LLayerGroup.test.ts b/tests/unit/components/LLayerGroup.test.ts index 3fc1a62..e245c12 100644 --- a/tests/unit/components/LLayerGroup.test.ts +++ b/tests/unit/components/LLayerGroup.test.ts @@ -5,17 +5,11 @@ import LLayerGroup from '../../../src/components/LLayerGroup.vue' import LTileLayer from '../../../src/components/LTileLayer.vue' import { AddLayerInjection, RemoveLayerInjection } from '../../../src/types/injectionKeys' import { testRemoveLayerOnUnmount } from './helper/tests' -import { - layerProps, - testComponentPropBindings, - testPropsBindingToLeaflet -} from './helper/propsBindingTests' +import { testComponentPropBindings, testPropsBindingToLeaflet } from './helper/propsBindingTests' import { testEmitsReady } from './helper/emitTests' import { mockAddLayer, mockRemoveLayer, testAddLayer } from './helper/injectionsTests' import { LayerGroup } from 'leaflet' -import { mergeReactiveProps } from './helper/props' - -export const layerGroupProps = mergeReactiveProps(layerProps, {}) +import { layerGroupProps } from './wrapper/LLayerGroup' const createWrapper = async (props = {}, slots = {}) => { const wrapper = mount(LLayerGroup, { diff --git a/tests/unit/components/LMap.test.ts b/tests/unit/components/LMap.test.ts index 124da1c..62e2ac6 100644 --- a/tests/unit/components/LMap.test.ts +++ b/tests/unit/components/LMap.test.ts @@ -1,5 +1,5 @@ -import { flushPromises, mount, type VueWrapper } from '@vue/test-utils' -import { describe, expect, it, vi } from 'vitest' +import { flushPromises, type VueWrapper } from '@vue/test-utils' +import { describe, expect, it } from 'vitest' import { componentProps, testComponentPropBindings, @@ -7,10 +7,11 @@ import { } from './helper/propsBindingTests' import { testEmitsReady } from './helper/emitTests' import { CRS, LatLngBounds, LatLngExpression, Map } from 'leaflet' -import LMap from '../../../src/components/LMap.vue' import { mergeReactiveProps } from './helper/props' import 'leaflet/dist/leaflet.css' import { expectBoundsToBeClose } from './helper/geo' +import { createMapWrapper } from './wrapper/LMap' +import { MapProps } from '../../../src/functions/map' const mapProps = mergeReactiveProps(componentProps, { width: '400px', @@ -59,63 +60,29 @@ const mapProps = mergeReactiveProps(componentProps, { new LatLngBounds([44.5, 10.5], [47.5, 11.5]) ) }, - paddingBottomRight: (leafletObject: Map, i, wrapper) => { - expect(wrapper.vm.$props.paddingBottomRight).toStrictEqual(mapProps.paddingBottomRight) + paddingBottomRight: (_m: Map, _i, wrapper) => { + expect((wrapper.vm.$props as MapProps).paddingBottomRight).toStrictEqual( + mapProps.paddingBottomRight + ) }, - paddingTopLeft: (leafletObject: Map, i, wrapper) => { - expect(wrapper.vm.$props.paddingTopLeft).toStrictEqual(mapProps.paddingTopLeft) + paddingTopLeft: (_m: Map, _, wrapper) => { + expect((wrapper.vm.$props as MapProps).paddingTopLeft).toStrictEqual( + mapProps.paddingTopLeft + ) }, - padding: (leafletObject: Map, i, wrapper) => { - expect(wrapper.vm.$props.padding).toStrictEqual(mapProps.padding) + padding: (_m: Map, _i, wrapper) => { + expect((wrapper.vm.$props as MapProps).padding).toStrictEqual(mapProps.padding) } } }) -class ResizeObserver { - observe() {} - unobserve() {} - disconnect() {} -} - -global.ResizeObserver = ResizeObserver - -Object.defineProperty(HTMLElement.prototype, 'clientWidth', { - configurable: true, - value: 300 -}) -Object.defineProperty(HTMLElement.prototype, 'clientHeight', { - configurable: true, - value: 300 -}) - -export const createWrapper = async (props = {}, slots = {}) => { - const wrapper = mount(LMap, { - propsData: { - width: '300px', - height: '300px', - center: [45, 10], - zoom: 8, - noBlockingAnimations: true, - options: { - zoomSnap: 0 - }, - ...props - }, - slots: slots, - attachTo: document.body - }) - await flushPromises() - await vi.waitFor(() => expect(wrapper.emitted('ready')).toBeTruthy()) - return wrapper -} - describe('LMap.vue', () => { - testEmitsReady(createWrapper) - testComponentPropBindings(createWrapper, 'LMap') - testPropsBindingToLeaflet(createWrapper, mapProps) + testEmitsReady(createMapWrapper) + testComponentPropBindings(createMapWrapper, 'LMap') + testPropsBindingToLeaflet(createMapWrapper, mapProps) - testCorrectInitialisation(createWrapper) - testFitBounds(createWrapper) + testCorrectInitialisation(createMapWrapper) + testFitBounds(createMapWrapper) }) const testCorrectInitialisation = (getWrapper: () => Promise>) => { diff --git a/tests/unit/components/LMarker.test.ts b/tests/unit/components/LMarker.test.ts index 6fbe028..523607f 100644 --- a/tests/unit/components/LMarker.test.ts +++ b/tests/unit/components/LMarker.test.ts @@ -1,7 +1,5 @@ -import { flushPromises, mount, type VueWrapper } from '@vue/test-utils' +import { type VueWrapper } from '@vue/test-utils' import { describe, expect, it } from 'vitest' -import LMarker from '../../../src/components/LMarker.vue' -import { AddLayerInjection, RemoveLayerInjection } from '../../../src/types/injectionKeys' import { Circle, LatLng, Marker } from 'leaflet' import { testRemoveLayerOnUnmount } from './helper/tests' import { @@ -10,8 +8,9 @@ import { testPropsBindingToLeaflet } from './helper/propsBindingTests' import { testEmitsReady } from './helper/emitTests' -import { mockAddLayer, mockRemoveLayer, testAddLayer } from './helper/injectionsTests' +import { testAddLayer } from './helper/injectionsTests' import { mergeReactiveProps } from './helper/props' +import { createMarkerWrapper } from './wrapper/LMarker' const markerProps = mergeReactiveProps(layerProps, { // TEST draggable: true, @@ -26,33 +25,14 @@ const markerProps = mergeReactiveProps(layerProps, { } }) -export const createWrapper = async (props = {}, slots = {}) => { - const wrapper = mount(LMarker, { - propsData: { - latLng: [44.48865, 11.3317], - ...props - }, - slots: slots, - global: { - provide: { - [AddLayerInjection as symbol]: mockAddLayer, - [RemoveLayerInjection as symbol]: mockRemoveLayer - } - } - }) - - await flushPromises() - return wrapper -} - describe('LMarker.vue', () => { - testEmitsReady(createWrapper) - testComponentPropBindings(createWrapper, 'LMarker') - testPropsBindingToLeaflet(createWrapper, markerProps) - testRemoveLayerOnUnmount(createWrapper) + testEmitsReady(createMarkerWrapper) + testComponentPropBindings(createMarkerWrapper, 'LMarker') + testPropsBindingToLeaflet(createMarkerWrapper, markerProps) + testRemoveLayerOnUnmount(createMarkerWrapper) - testCorrectInitialisation(createWrapper) - testAddLayer(createWrapper) + testCorrectInitialisation(createMarkerWrapper) + testAddLayer(createMarkerWrapper) }) const testCorrectInitialisation = (getWrapper: () => Promise>) => { diff --git a/tests/unit/components/LPolygon.test.ts b/tests/unit/components/LPolygon.test.ts index 276525b..7cbefed 100644 --- a/tests/unit/components/LPolygon.test.ts +++ b/tests/unit/components/LPolygon.test.ts @@ -7,24 +7,7 @@ import { testRemoveLayerOnUnmount } from './helper/tests' import { testComponentPropBindings, testPropsBindingToLeaflet } from './helper/propsBindingTests' import { testEmitsReady } from './helper/emitTests' import { mockAddLayer, mockRemoveLayer, testAddLayer } from './helper/injectionsTests' -import { polylineProps } from './LPolyline.test' -import { mergeReactiveProps } from './helper/props' - -export const polygonProps = mergeReactiveProps(polylineProps, { - latLngs: [ - [26.774, -81.19], - [19.466, -67.118], - [33.321, -65.757], - [26.774, -81.19] - ], - expecting: { - latLngs: (leafletObject: Polygon) => { - expect(leafletObject.getLatLngs()).toStrictEqual([ - polygonProps.latLngs.map(([lat, lng]) => new LatLng(lat, lng)).slice(0, -1) - ]) - } - } -}) +import { polygonProps } from './wrapper/LPolygonProps' const createWrapper = async (props = {}) => { const wrapper = shallowMount(LPolygon, { diff --git a/tests/unit/components/LPolyline.test.ts b/tests/unit/components/LPolyline.test.ts index da16678..88dfc7d 100644 --- a/tests/unit/components/LPolyline.test.ts +++ b/tests/unit/components/LPolyline.test.ts @@ -4,28 +4,10 @@ import LPolyline from '../../../src/components/LPolyline.vue' import { AddLayerInjection, RemoveLayerInjection } from '../../../src/types/injectionKeys' import { LatLng, Polyline } from 'leaflet' import { testRemoveLayerOnUnmount } from './helper/tests' -import { pathProps, testComponentPropBindings, testPropsBindingToLeaflet } from './helper/propsBindingTests' +import { testComponentPropBindings, testPropsBindingToLeaflet } from './helper/propsBindingTests' import { testEmitsReady } from './helper/emitTests' import { mockAddLayer, mockRemoveLayer, testAddLayer } from './helper/injectionsTests' -import { mergeReactiveProps } from './helper/props' - -export const polylineProps = mergeReactiveProps(pathProps, { - smoothFactor: 0.9, - noClip: true, - latLngs: [ - [47.4, -1.51], - [47.34, -1.3], - [47.24, -1.2], - [47.23, -1.36] - ], - expecting: { - latLngs: (leafletObject: Polyline) => { - expect(leafletObject.getLatLngs()).toStrictEqual( - polylineProps.latLngs.map(([lat, lng]) => new LatLng(lat, lng)) - ) - } - } -}) +import { polylineProps } from './wrapper/LPolyline' const createWrapper = async (props = {}) => { const wrapper = shallowMount(LPolyline, { diff --git a/tests/unit/components/LPopup.test.ts b/tests/unit/components/LPopup.test.ts index 3f5e375..91cb8c5 100644 --- a/tests/unit/components/LPopup.test.ts +++ b/tests/unit/components/LPopup.test.ts @@ -13,7 +13,7 @@ import { mockBindPopup, mockUnbindPopup } from './helper/injectionsTests' import { mergeReactiveProps } from './helper/props' import { testBindPopup, testUnbindPopupOnUnmount } from './helper/tests' import { PopupProps } from '../../../src/functions/popup' -import { createWrapper as createMarkerWrapper } from './LMarker.test' +import { createMarkerWrapper } from './wrapper/LMarker' const popupProps = mergeReactiveProps(popperProps, { latLng: new LatLng(44.5, 11.5) diff --git a/tests/unit/components/LRectangle.test.ts b/tests/unit/components/LRectangle.test.ts index ce2839b..55677e8 100644 --- a/tests/unit/components/LRectangle.test.ts +++ b/tests/unit/components/LRectangle.test.ts @@ -7,8 +7,8 @@ import { testRemoveLayerOnUnmount } from './helper/tests' import { testComponentPropBindings, testPropsBindingToLeaflet } from './helper/propsBindingTests' import { testEmitsReady } from './helper/emitTests' import { mockAddLayer, mockRemoveLayer, testAddLayer } from './helper/injectionsTests' -import { polygonProps } from './LPolygon.test' import { mergeReactiveProps } from './helper/props' +import { polygonProps } from './wrapper/LPolygonProps' const rectangleProps = mergeReactiveProps(polygonProps, { bounds: [ diff --git a/tests/unit/components/LSVGOverlay.test.ts b/tests/unit/components/LSVGOverlay.test.ts index bbe65ee..e533d71 100644 --- a/tests/unit/components/LSVGOverlay.test.ts +++ b/tests/unit/components/LSVGOverlay.test.ts @@ -7,8 +7,8 @@ import { testEmitsReady } from './helper/emitTests' import { mockAddLayer, mockRemoveLayer, testAddLayer } from './helper/injectionsTests' import { type LatLngBoundsLiteral, SVGOverlay } from 'leaflet' import LSVGOverlay from '../../../src/components/LSVGOverlay.vue' -import { imageOverlayAbstractProps } from './LImageOverlay.test' import { mergeReactiveProps } from './helper/props' +import { imageOverlayAbstractProps } from './wrapper/LImageOverlay' const svgOverlayProps = mergeReactiveProps(imageOverlayAbstractProps, {}) diff --git a/tests/unit/components/LTileLayer.test.ts b/tests/unit/components/LTileLayer.test.ts index a83105e..00eb745 100644 --- a/tests/unit/components/LTileLayer.test.ts +++ b/tests/unit/components/LTileLayer.test.ts @@ -7,17 +7,7 @@ import { testEmitsReady } from './helper/emitTests' import { mockAddLayer, mockRemoveLayer, testAddLayer } from './helper/injectionsTests' import { TileLayer } from 'leaflet' import LTileLayer from '../../../src/components/LTileLayer.vue' -import { mergeReactiveProps } from './helper/props' -import { gridLayerProps } from './LGridLayer.test' - -export const tileLayerProps = mergeReactiveProps(gridLayerProps, { - url: 'https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png', - expecting: { - url: (l: TileLayer & { _url: string }) => { - expect(l._url).toBe(tileLayerProps.url) - } - } -}) +import { tileLayerProps } from './wrapper/LTileLayer' const createWrapper = async (props = {}) => { const wrapper = shallowMount(LTileLayer, { diff --git a/tests/unit/components/LTooltip.test.ts b/tests/unit/components/LTooltip.test.ts index 09783ab..49a2ceb 100644 --- a/tests/unit/components/LTooltip.test.ts +++ b/tests/unit/components/LTooltip.test.ts @@ -13,7 +13,7 @@ import { Tooltip } from 'leaflet' import { mergeReactiveProps } from './helper/props' import { testBindTooltip, testUnbindTooltipOnUnmount } from './helper/tests' import { TooltipProps } from '../../../src/functions/tooltip' -import { createWrapper as createMarkerWrapper } from './LMarker.test' +import { createMarkerWrapper } from './wrapper/LMarker' const tooltipProps = mergeReactiveProps(popperProps, {}) diff --git a/tests/unit/components/LVideoOverlay.test.ts b/tests/unit/components/LVideoOverlay.test.ts index defb61c..9fc51d2 100644 --- a/tests/unit/components/LVideoOverlay.test.ts +++ b/tests/unit/components/LVideoOverlay.test.ts @@ -7,8 +7,8 @@ import { testEmitsReady } from './helper/emitTests' import { mockAddLayer, mockRemoveLayer, testAddLayer } from './helper/injectionsTests' import { type LatLngBoundsLiteral, VideoOverlay } from 'leaflet' import LVideoOverlay from '../../../src/components/LVideoOverlay.vue' -import { imageOverlayAbstractProps } from './LImageOverlay.test' import { mergeReactiveProps } from './helper/props' +import { imageOverlayAbstractProps } from './wrapper/LImageOverlay' const videoOverlayProps = mergeReactiveProps(imageOverlayAbstractProps, { video: 'https://www.mapbox.com/bites/00188/patricia_nasa.webm' diff --git a/tests/unit/components/LWmsTileLayer.test.ts b/tests/unit/components/LWmsTileLayer.test.ts index d1f5fc7..b9eac71 100644 --- a/tests/unit/components/LWmsTileLayer.test.ts +++ b/tests/unit/components/LWmsTileLayer.test.ts @@ -7,8 +7,8 @@ import { testEmitsReady } from './helper/emitTests' import { mockAddLayer, mockRemoveLayer, testAddLayer } from './helper/injectionsTests' import { TileLayer } from 'leaflet' import LWmsTileLayer from '../../../src/components/LWmsTileLayer.vue' -import { tileLayerProps } from './LTileLayer.test' import { mergeReactiveProps } from './helper/props' +import { tileLayerProps } from './wrapper/LTileLayer' const wmsTileLayerProps = mergeReactiveProps(tileLayerProps, { url: 'https://ows.terrestris.de/osm/service?', diff --git a/tests/unit/components/wrapper/LCircleMarker.ts b/tests/unit/components/wrapper/LCircleMarker.ts new file mode 100644 index 0000000..dc41bd1 --- /dev/null +++ b/tests/unit/components/wrapper/LCircleMarker.ts @@ -0,0 +1,8 @@ +import { mergeReactiveProps } from '../helper/props' +import { pathProps } from '../helper/propsBindingTests' +import { LatLng } from 'leaflet' + +export const circleMarkerProps = mergeReactiveProps(pathProps, { + radius: 15, + latLng: new LatLng(44.5, 11.5) +}) diff --git a/tests/unit/components/wrapper/LControl.ts b/tests/unit/components/wrapper/LControl.ts new file mode 100644 index 0000000..20218e6 --- /dev/null +++ b/tests/unit/components/wrapper/LControl.ts @@ -0,0 +1,6 @@ +import { mergeReactiveProps } from '../helper/props' +import { componentProps } from '../helper/propsBindingTests' + +export const controlAbstractProps = mergeReactiveProps(componentProps, { + position: 'bottomleft' +}) diff --git a/tests/unit/components/wrapper/LGridLayer.ts b/tests/unit/components/wrapper/LGridLayer.ts new file mode 100644 index 0000000..05f003a --- /dev/null +++ b/tests/unit/components/wrapper/LGridLayer.ts @@ -0,0 +1,7 @@ +import { mergeReactiveProps } from '../helper/props' +import { layerProps } from '../helper/propsBindingTests' + +export const gridLayerProps = mergeReactiveProps(layerProps, { + opacity: 0.5, + zIndex: 50 +}) diff --git a/tests/unit/components/wrapper/LImageOverlay.ts b/tests/unit/components/wrapper/LImageOverlay.ts new file mode 100644 index 0000000..e93d687 --- /dev/null +++ b/tests/unit/components/wrapper/LImageOverlay.ts @@ -0,0 +1,9 @@ +import { mergeReactiveProps } from '../helper/props' +import { layerProps } from '../helper/propsBindingTests' +import { LatLngBounds } from 'leaflet' + +export const imageOverlayAbstractProps = mergeReactiveProps(layerProps, { + opacity: 0.5, + zIndex: 50, + bounds: new LatLngBounds([0, 0], [50, 50]) +}) diff --git a/tests/unit/components/wrapper/LLayerGroup.ts b/tests/unit/components/wrapper/LLayerGroup.ts new file mode 100644 index 0000000..265affb --- /dev/null +++ b/tests/unit/components/wrapper/LLayerGroup.ts @@ -0,0 +1,4 @@ +import { mergeReactiveProps } from '../helper/props' +import { layerProps } from '../helper/propsBindingTests' + +export const layerGroupProps = mergeReactiveProps(layerProps, {}) diff --git a/tests/unit/components/wrapper/LMap.ts b/tests/unit/components/wrapper/LMap.ts new file mode 100644 index 0000000..bb22f06 --- /dev/null +++ b/tests/unit/components/wrapper/LMap.ts @@ -0,0 +1,41 @@ +import { flushPromises, mount } from '@vue/test-utils' +import { expect, vi } from 'vitest' +import LMap from '../../../../src/components/LMap.vue' + +class ResizeObserver { + observe() {} + unobserve() {} + disconnect() {} +} + +global.ResizeObserver = ResizeObserver + +Object.defineProperty(HTMLElement.prototype, 'clientWidth', { + configurable: true, + value: 300 +}) +Object.defineProperty(HTMLElement.prototype, 'clientHeight', { + configurable: true, + value: 300 +}) + +export const createMapWrapper = async (props = {}, slots = {}) => { + const wrapper = mount(LMap, { + propsData: { + width: '300px', + height: '300px', + center: [45, 10], + zoom: 8, + noBlockingAnimations: true, + options: { + zoomSnap: 0 + }, + ...props + }, + slots: slots, + attachTo: document.body + }) + await flushPromises() + await vi.waitFor(() => expect(wrapper.emitted('ready')).toBeTruthy()) + return wrapper +} diff --git a/tests/unit/components/wrapper/LMarker.ts b/tests/unit/components/wrapper/LMarker.ts new file mode 100644 index 0000000..a571ac8 --- /dev/null +++ b/tests/unit/components/wrapper/LMarker.ts @@ -0,0 +1,23 @@ +import { flushPromises, mount } from '@vue/test-utils' +import { AddLayerInjection, RemoveLayerInjection } from '../../../../src/types/injectionKeys' +import { mockAddLayer, mockRemoveLayer } from '../helper/injectionsTests' +import LMarker from '../../../../src/components/LMarker.vue' + +export const createMarkerWrapper = async (props = {}, slots = {}) => { + const wrapper = mount(LMarker, { + propsData: { + latLng: [44.48865, 11.3317], + ...props + }, + slots: slots, + global: { + provide: { + [AddLayerInjection as symbol]: mockAddLayer, + [RemoveLayerInjection as symbol]: mockRemoveLayer + } + } + }) + + await flushPromises() + return wrapper +} diff --git a/tests/unit/components/wrapper/LPolygonProps.ts b/tests/unit/components/wrapper/LPolygonProps.ts new file mode 100644 index 0000000..6c42875 --- /dev/null +++ b/tests/unit/components/wrapper/LPolygonProps.ts @@ -0,0 +1,20 @@ +import { mergeReactiveProps } from '../helper/props' +import { LatLng, Polygon } from 'leaflet' +import { expect } from 'vitest' +import { polylineProps } from './LPolyline' + +export const polygonProps = mergeReactiveProps(polylineProps, { + latLngs: [ + [26.774, -81.19], + [19.466, -67.118], + [33.321, -65.757], + [26.774, -81.19] + ], + expecting: { + latLngs: (leafletObject: Polygon) => { + expect(leafletObject.getLatLngs()).toStrictEqual([ + polygonProps.latLngs.map(([lat, lng]) => new LatLng(lat, lng)).slice(0, -1) + ]) + } + } +}) diff --git a/tests/unit/components/wrapper/LPolyline.ts b/tests/unit/components/wrapper/LPolyline.ts new file mode 100644 index 0000000..4411fd9 --- /dev/null +++ b/tests/unit/components/wrapper/LPolyline.ts @@ -0,0 +1,22 @@ +import { mergeReactiveProps } from '../helper/props' +import { pathProps } from '../helper/propsBindingTests' +import { LatLng, Polyline } from 'leaflet' +import { expect } from 'vitest' + +export const polylineProps = mergeReactiveProps(pathProps, { + smoothFactor: 0.9, + noClip: true, + latLngs: [ + [47.4, -1.51], + [47.34, -1.3], + [47.24, -1.2], + [47.23, -1.36] + ], + expecting: { + latLngs: (leafletObject: Polyline) => { + expect(leafletObject.getLatLngs()).toStrictEqual( + polylineProps.latLngs.map(([lat, lng]) => new LatLng(lat, lng)) + ) + } + } +}) diff --git a/tests/unit/components/wrapper/LTileLayer.ts b/tests/unit/components/wrapper/LTileLayer.ts new file mode 100644 index 0000000..5d5a925 --- /dev/null +++ b/tests/unit/components/wrapper/LTileLayer.ts @@ -0,0 +1,13 @@ +import { mergeReactiveProps } from '../helper/props' +import { gridLayerProps } from './LGridLayer' +import { TileLayer } from 'leaflet' +import { expect } from 'vitest' + +export const tileLayerProps = mergeReactiveProps(gridLayerProps, { + url: 'https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png', + expecting: { + url: (l: TileLayer & { _url: string }) => { + expect(l._url).toBe(tileLayerProps.url) + } + } +}) From 8c8c9f92971e22d6df97610bc1e08b8821ae8e7f Mon Sep 17 00:00:00 2001 From: Max Engisch Date: Tue, 26 Aug 2025 10:25:00 +0200 Subject: [PATCH 11/15] refactor(tests): improve propsBinder method and tests to avoid unnecessary console logs. These console logs were only produced in vitest and not in a normal application. Every component can now check which components were not bound. --- src/utils.ts | 7 +++++-- tests/unit/components/helper/propsBindingTests.ts | 7 ++++--- tests/unit/utils/propsBinder.test.ts | 6 ++---- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 7300dc5..826ff38 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -59,9 +59,11 @@ export const isFunction = (x: unknown) => typeof x === 'function' * @param methods * @param leafletElement * @param props the relevant Vue component props + * @return array of unbounded prop keys */ export const propsBinder = (methods: Readonly, leafletElement: PropertyMap, props: Readonly) => { //propsBinderScope.run(() => { + const unboundedProps: string[] = [] for (const key in props) { /* v8 ignore next */ if (vueLeafletConfig.experimental.skipUndefinedProps && props[key] === undefined) continue @@ -83,12 +85,13 @@ export const propsBinder = (methods: Readonly, leafletElement: Prop leafletElement[setMethodName](newVal) } ) - } else if (key !== 'options' && import.meta.env.vitest) { - console.warn(`No setter for '${key}'`) + } else if (key !== 'options') { + unboundedProps.push(key) } } //}) // propsBinderScope.log('Active watchers:', devScope.effects.length) + return unboundedProps } export const propsToLeafletOptions = ( diff --git a/tests/unit/components/helper/propsBindingTests.ts b/tests/unit/components/helper/propsBindingTests.ts index 48466e8..46f4008 100644 --- a/tests/unit/components/helper/propsBindingTests.ts +++ b/tests/unit/components/helper/propsBindingTests.ts @@ -2,6 +2,7 @@ import { flushPromises, VueWrapper } from '@vue/test-utils' import { expect, it, vi } from 'vitest' import getReactivePropCount, { mergeReactiveProps } from './props' import { capitalizeFirstLetter, isFunction } from '../../../../src/utils' +import * as utils from '../../../../src/utils' import { Layer } from 'leaflet' import { mockAddLayer, mockRemoveLayer } from './injectionsTests' @@ -11,10 +12,10 @@ export function testComponentPropBindings( ) { const { initOnly, reactiveNative } = getReactivePropCount(componentName) it('registers watch for each prop with matching setter', async () => { - const consoleWarnMock = vi.spyOn(console, 'warn').mockImplementation(() => {}) + const propsBinderSpy = vi.spyOn(utils, 'propsBinder') await getWrapper() - expect(consoleWarnMock).toHaveBeenCalledTimes(initOnly + reactiveNative) - consoleWarnMock.mockRestore() + expect(propsBinderSpy).toHaveBeenCalledOnce() + expect((propsBinderSpy.mock.results[0].value.length)).toBe(initOnly + reactiveNative) }) } diff --git a/tests/unit/utils/propsBinder.test.ts b/tests/unit/utils/propsBinder.test.ts index a560360..d572ca7 100644 --- a/tests/unit/utils/propsBinder.test.ts +++ b/tests/unit/utils/propsBinder.test.ts @@ -40,7 +40,6 @@ describe('propsBinder', () => { }) it('should not bind props if no matching setMethod exists', async () => { - const consoleWarnMock = vi.spyOn(console, 'warn').mockImplementation(() => {}) const methods = {} const leafletElement = { setOpacity: vi.fn(), @@ -48,13 +47,12 @@ describe('propsBinder', () => { const props = reactive({ weight: 3, }) - - propsBinder(methods, leafletElement, props) + const result = propsBinder(methods, leafletElement, props) props.weight = 5 await nextTick() expect(leafletElement.setOpacity).not.toHaveBeenCalled() - expect(consoleWarnMock).toHaveBeenCalledOnce() + expect(result).toStrictEqual(["weight"]) }) }) From 97ec98b9073efa6db847abad0d3a4f97179252cc Mon Sep 17 00:00:00 2001 From: Max Engisch Date: Tue, 26 Aug 2025 11:13:30 +0200 Subject: [PATCH 12/15] chore(tests): exclude types from coverage --- src/types/interfaces/IControlDefinition.ts | 2 +- src/types/interfaces/ILayerDefinition.ts | 2 +- src/types/interfaces/index.ts | 4 ++-- vitest.config.ts | 3 ++- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/types/interfaces/IControlDefinition.ts b/src/types/interfaces/IControlDefinition.ts index d70e481..0428d0f 100644 --- a/src/types/interfaces/IControlDefinition.ts +++ b/src/types/interfaces/IControlDefinition.ts @@ -1,4 +1,4 @@ -import { Control } from 'leaflet' +import type { Control } from 'leaflet' import type { ILayerDefinition } from '@/types/interfaces/ILayerDefinition.ts' export interface IControlDefinition { diff --git a/src/types/interfaces/ILayerDefinition.ts b/src/types/interfaces/ILayerDefinition.ts index e555576..06fe906 100644 --- a/src/types/interfaces/ILayerDefinition.ts +++ b/src/types/interfaces/ILayerDefinition.ts @@ -1,5 +1,5 @@ import type { LayerType } from '../enums/LayerType' -import { type Layer } from 'leaflet' +import type { Layer } from 'leaflet' export interface ILayerDefinition { name?: string diff --git a/src/types/interfaces/index.ts b/src/types/interfaces/index.ts index b3a9f31..18d4bfa 100644 --- a/src/types/interfaces/index.ts +++ b/src/types/interfaces/index.ts @@ -1,2 +1,2 @@ -export * from "./IControlDefinition"; -export * from "./ILayerDefinition"; +export * from './IControlDefinition' +export * from './ILayerDefinition' diff --git a/vitest.config.ts b/vitest.config.ts index 142c78f..ae3947d 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -11,7 +11,8 @@ export default mergeConfig( root: fileURLToPath(new URL('./', import.meta.url)), coverage: { provider: 'v8', - include: ['src/**'] + include: ['src/**'], + exclude: ['src/types/**'], } } }) From 14f29175bab9057eed697c20747dadcafa45a98c Mon Sep 17 00:00:00 2001 From: Max Engisch Date: Tue, 26 Aug 2025 11:41:39 +0200 Subject: [PATCH 13/15] test: add remaining tests --- src/components/LMarker.vue | 13 ++-- src/functions/layer.ts | 4 +- tests/unit/components/LMap.test.ts | 72 ++++++++++++++++++- tests/unit/components/LTooltip.test.ts | 4 +- .../components/helper/propsBindingTests.ts | 8 ++- 5 files changed, 84 insertions(+), 17 deletions(-) diff --git a/src/components/LMarker.vue b/src/components/LMarker.vue index da9b379..a407c91 100644 --- a/src/components/LMarker.vue +++ b/src/components/LMarker.vue @@ -7,7 +7,7 @@ import { provide, ref, useAttrs, - useSlots, + useSlots } from 'vue' import { assertInject, @@ -15,13 +15,13 @@ import { cancelDebounces, isFunction, propsBinder, - remapEvents, + remapEvents } from '@/utils' import { AddLayerInjection, CanSetParentHtmlInjection, SetIconInjection, - SetParentHtmlInjection, + SetParentHtmlInjection } from '@/types/injectionKeys' import { DivIcon, Icon, type LeafletEventHandlerFnMap, Marker } from 'leaflet' import { debounce } from 'ts-debounce' @@ -30,7 +30,7 @@ import { type MarkerProps, markerPropsDefaults, setupMarker, - shouldBlankIcon, + shouldBlankIcon } from '@/functions/marker' /** @@ -67,12 +67,13 @@ function useMarker() { function useEvents() { const { listeners } = remapEvents(useAttrs()) const eventHandlers: LeafletEventHandlerFnMap = { - move: debounce(methods.latLngSync), + move: debounce(methods.latLngSync) } return { listeners, eventHandlers } } onMounted(async () => { + // TODO this causes in vitest: [Vue warn]: Slot "default" invoked outside of the render function: this will not track dependencies used in the slot. Invoke the slot function inside the render function instead. if (shouldBlankIcon(useSlots())) { options.icon = new DivIcon({ className: '' }) } @@ -84,7 +85,7 @@ function useMarker() { addLayer({ ...props, ...methods, - leafletObject: leafletObject.value, + leafletObject: leafletObject.value }) ready.value = true nextTick(() => emit('ready', leafletObject.value!)) diff --git a/src/functions/layer.ts b/src/functions/layer.ts index abc6f19..3711f2c 100644 --- a/src/functions/layer.ts +++ b/src/functions/layer.ts @@ -68,9 +68,9 @@ export const setupLayer = ( function updateVisibleProp(value: boolean) { emit('update:visible', value) } - const addThisLayer = () => addLayer({ leafletObject: leafletRef.value, updateVisibleProp }) + const addThisLayer = () => addLayer({ leafletObject: leafletRef.value, updateVisibleProp, layerType: props.layerType }) const removeThisLayer = () => - removeLayer({ leafletObject: leafletRef.value, updateVisibleProp }) + removeLayer({ leafletObject: leafletRef.value, updateVisibleProp, layerType: props.layerType }) const methods = { ...componentMethods, diff --git a/tests/unit/components/LMap.test.ts b/tests/unit/components/LMap.test.ts index 62e2ac6..4c5aea3 100644 --- a/tests/unit/components/LMap.test.ts +++ b/tests/unit/components/LMap.test.ts @@ -1,5 +1,6 @@ -import { flushPromises, type VueWrapper } from '@vue/test-utils' -import { describe, expect, it } from 'vitest' +import { type VueWrapper } from '@vue/test-utils' +import { describe, expect, it, vi } from 'vitest' +import { h } from 'vue' import { componentProps, testComponentPropBindings, @@ -12,6 +13,9 @@ import 'leaflet/dist/leaflet.css' import { expectBoundsToBeClose } from './helper/geo' import { createMapWrapper } from './wrapper/LMap' import { MapProps } from '../../../src/functions/map' +import { nextTick } from 'vue' +import LTileLayer from '../../../src/components/LTileLayer.vue' +import LControlLayers from '../../../src/components/LControlLayers.vue' const mapProps = mergeReactiveProps(componentProps, { width: '400px', @@ -83,16 +87,20 @@ describe('LMap.vue', () => { testCorrectInitialisation(createMapWrapper) testFitBounds(createMapWrapper) + testBeforeMapMount(createMapWrapper) + testRemoveLayer(createMapWrapper) + testRemoveMapOnUmount(createMapWrapper) }) const testCorrectInitialisation = (getWrapper: () => Promise>) => { it('creates a Leaflet map with correct options', async () => { const wrapper = await getWrapper() - await flushPromises() const obj = wrapper.vm.leafletObject as Map expect(obj).toBeDefined() expect(obj.options).toBeDefined() + const mapElement = wrapper.find('.leaflet-container') + expect(mapElement.exists()).toBe(true) }) } @@ -129,3 +137,61 @@ const testFitBounds = (getWrapper: () => Promise>) => { expectBoundsToBeClose(obj.getBounds(), expectedBounds, [0, 1, 1, 1]) }) } + +const testBeforeMapMount = (getWrapper: (props: MapProps) => Promise>) => { + it('it calls beforeMapMount', async () => { + const mockFn = vi.fn() + await getWrapper({ beforeMapMount: mockFn }) + expect(mockFn).toHaveBeenCalledOnce() + }) + it('it catches the error in beforeMapMount', async () => { + const consoleErrorMock = vi.spyOn(console, 'error').mockImplementation(() => {}) + const mockFn = vi.fn(() => { + throw new Error('Error in BeforeMapMount') + }) + await getWrapper({ beforeMapMount: mockFn }) + expect(mockFn).toThrow() + expect(consoleErrorMock).toHaveBeenCalledExactlyOnceWith( + 'The following error occurred running the provided beforeMapMount hook Error: Error in BeforeMapMount' + ) + }) +} + +const testRemoveLayer = (getWrapper: () => Promise>) => { + it('removes the layer from the map on unmount with ControlLayers', async () => { + const wrapper = await createMapWrapper( + {}, + { default: [LControlLayers, h(LTileLayer, { url: '', layerType: 'base' })] } + ) + wrapper.unmount() + }) + it('removes the layer from the map on unmount without ControlLayers', async () => { + const wrapper = await createMapWrapper( + {}, + { default: h(LTileLayer, { url: '', layerType: 'base' }) } + ) + wrapper.unmount() + }) +} + +const testRemoveMapOnUmount = (getWrapper: () => Promise>) => { + it('removes the map on unmount', async () => { + const wrapper = await getWrapper() + + wrapper.unmount() + await nextTick() + const leafletObject = wrapper.vm.leafletObject as Map + // @ts-expect-error _mapPane is private so not in the types + expect(leafletObject._mapPane).toBe(undefined) + }) + it('should not respond to events after removal', async () => { + const wrapper = await getWrapper() + const leafletObject = wrapper.vm.leafletObject as Map + const spy = vi.fn() + leafletObject.on('click', spy) + + wrapper.unmount() + leafletObject.fire('click') + expect(spy).not.toHaveBeenCalled() + }) +} diff --git a/tests/unit/components/LTooltip.test.ts b/tests/unit/components/LTooltip.test.ts index 49a2ceb..b58f9e2 100644 --- a/tests/unit/components/LTooltip.test.ts +++ b/tests/unit/components/LTooltip.test.ts @@ -50,8 +50,6 @@ const createWrapperWithSlot = async (props = {}) => { default: 'Something' } }) - console.log(wrapper.html()) - await flushPromises() return wrapper } @@ -87,7 +85,7 @@ const testCorrectInitialisation = ( ' -->Something' ) }) - it('creates a Leaflet layer with a tooltip', async () => { + it('creates a Leaflet layer (marker) with a tooltip', async () => { const wrapper = await createMarkerWrapper({}, { default: LTooltip }) expect(wrapper.vm.leafletObject).toBeDefined() const lTooltip = wrapper.findComponent(LTooltip) diff --git a/tests/unit/components/helper/propsBindingTests.ts b/tests/unit/components/helper/propsBindingTests.ts index 46f4008..b4b63b1 100644 --- a/tests/unit/components/helper/propsBindingTests.ts +++ b/tests/unit/components/helper/propsBindingTests.ts @@ -54,7 +54,9 @@ export const layerProps = mergeReactiveProps(componentProps, { attribution: 'new attribution', name: 'name', layerType: 'overlay', - visible: false, + visible: { + values: [false, true] + }, expecting: { name: (_leafletObject: Layer) => { expect(mockRemoveLayer).toHaveBeenCalledOnce() @@ -64,9 +66,9 @@ export const layerProps = mergeReactiveProps(componentProps, { expect(mockRemoveLayer).toHaveBeenCalledOnce() expect(mockAddLayer).toHaveBeenCalledTimes(2) }, - visible: (_leafletObject: Layer) => { + visible: (_leafletObject: Layer, iteration) => { expect(mockRemoveLayer).toHaveBeenCalledOnce() - expect(mockAddLayer).toHaveBeenCalledOnce() + expect(mockAddLayer).toHaveBeenCalledTimes(iteration + 1) } } }) From 5fceebb0d735e4bea25a4575d6b5d8dab8ec85b0 Mon Sep 17 00:00:00 2001 From: Max Engisch Date: Tue, 26 Aug 2025 12:54:47 +0200 Subject: [PATCH 14/15] test: add tests for LIcon --- tests/unit/components/LIcon.test.ts | 32 ++++++++++++------- .../unit/components/helper/injectionsTests.ts | 2 +- tests/unit/components/helper/props.ts | 1 + .../components/helper/propsBindingTests.ts | 12 +++++-- 4 files changed, 31 insertions(+), 16 deletions(-) diff --git a/tests/unit/components/LIcon.test.ts b/tests/unit/components/LIcon.test.ts index 6780b93..357ca5f 100644 --- a/tests/unit/components/LIcon.test.ts +++ b/tests/unit/components/LIcon.test.ts @@ -1,5 +1,5 @@ import { flushPromises, shallowMount, type VueWrapper } from '@vue/test-utils' -import { describe, expect, it } from 'vitest' +import { describe, expect, it, vi } from 'vitest' import { CanSetParentHtmlInjection, SetIconInjection, @@ -16,14 +16,26 @@ import { mockSetParentHtmlInjection, testSetIcon } from './helper/injectionsTests' -import { Icon } from 'leaflet' import LIcon from '../../../src/components/LIcon.vue' import { mergeReactiveProps } from './helper/props' // TODO incomplete testing const iconProps = mergeReactiveProps(componentProps, { - // TODO add props + iconUrl: 'replace.icon', + iconRetinaUrl: 'replaceRetina.icon', + iconSize: [20, 20], + iconAnchor: [10, 10], + popupAnchor: [10, 10], + tooltipAnchor: [10, 10], + shadowUrl: 'replace.shadow', + shadowRetinaUrl: 'replaceRetina.shadow', + shadowAnchor: [10, 10], + bgPos: [10, 10], + className: 'classes', + customCheck: async (wrapper: VueWrapper) => { + await vi.waitFor(() => expect(mockSetIconInjection).toHaveBeenCalledTimes(2)) + } }) const createWrapper = async (props = {}) => { @@ -39,29 +51,25 @@ const createWrapper = async (props = {}) => { } } }) - await flushPromises() + await vi.waitFor(() => expect(mockSetIconInjection).toHaveBeenCalledOnce()) return wrapper } describe('LIcon.vue', () => { - // TEST testEmitsReady(createWrapper) testComponentPropBindings(createWrapper, 'LIcon') testPropsBindingToLeaflet(createWrapper, iconProps) // TEST testRemoveLayerOnUnmount(createWrapper) // TEST testCorrectInitialisation(createWrapper) - // TEST testCanSetParentHtml(createWrapper) - // TEST testSetParentHtml(createWrapper) testSetIcon(createWrapper) + testSwapHtml(createWrapper) }) -const testCorrectInitialisation = (getWrapper: () => Promise>) => { +const testSwapHtml = (getWrapper: () => Promise>) => { it('creates a Leaflet icon with correct options', async () => { const wrapper = await getWrapper() - await flushPromises() - const obj = wrapper.vm.leafletObject as Icon - - expect(obj).toBeDefined() + wrapper.vm.root.innerHTML = '

SOme new content
' + await vi.waitFor(() => expect(mockSetParentHtmlInjection).toHaveBeenCalledOnce()) }) } diff --git a/tests/unit/components/helper/injectionsTests.ts b/tests/unit/components/helper/injectionsTests.ts index e002428..84861c0 100644 --- a/tests/unit/components/helper/injectionsTests.ts +++ b/tests/unit/components/helper/injectionsTests.ts @@ -6,7 +6,7 @@ export const mockRegisterControl = vi.fn() export const mockRegisterLayerControl = vi.fn() export const mockAddLayer = vi.fn() export const mockRemoveLayer = vi.fn() -export const mockCanSetParentHtmlInjection = vi.fn() +export const mockCanSetParentHtmlInjection = vi.fn(() => true) export const mockSetParentHtmlInjection = vi.fn() export const mockSetIconInjection = vi.fn() export const mockBindPopup = vi.fn() diff --git a/tests/unit/components/helper/props.ts b/tests/unit/components/helper/props.ts index 029013e..d59bc60 100644 --- a/tests/unit/components/helper/props.ts +++ b/tests/unit/components/helper/props.ts @@ -69,6 +69,7 @@ function collectReactivePropCount(interfaceDecl: InterfaceDeclaration) { type ReactiveProps = { expecting?: Record void> + customCheck?: (wrapper: VueWrapper) => Promise [key: string]: any } diff --git a/tests/unit/components/helper/propsBindingTests.ts b/tests/unit/components/helper/propsBindingTests.ts index b4b63b1..4ec168a 100644 --- a/tests/unit/components/helper/propsBindingTests.ts +++ b/tests/unit/components/helper/propsBindingTests.ts @@ -1,8 +1,8 @@ import { flushPromises, VueWrapper } from '@vue/test-utils' import { expect, it, vi } from 'vitest' import getReactivePropCount, { mergeReactiveProps } from './props' -import { capitalizeFirstLetter, isFunction } from '../../../../src/utils' import * as utils from '../../../../src/utils' +import { capitalizeFirstLetter, isFunction } from '../../../../src/utils' import { Layer } from 'leaflet' import { mockAddLayer, mockRemoveLayer } from './injectionsTests' @@ -15,7 +15,7 @@ export function testComponentPropBindings( const propsBinderSpy = vi.spyOn(utils, 'propsBinder') await getWrapper() expect(propsBinderSpy).toHaveBeenCalledOnce() - expect((propsBinderSpy.mock.results[0].value.length)).toBe(initOnly + reactiveNative) + expect(propsBinderSpy.mock.results[0].value.length).toBe(initOnly + reactiveNative) }) } @@ -23,7 +23,9 @@ export function testPropsBindingToLeaflet( getWrapper: (initialProps?: Record) => Promise>, updatedProps: Record ) { - const entries = Object.entries(updatedProps).filter(([key]) => key !== 'expecting') + const entries = Object.entries(updatedProps).filter( + ([key]) => key !== 'expecting' && key != 'customCheck' + ) it.each(entries)( 'updates Leaflet object when prop "%s" changes', async (propName, newValue) => { @@ -35,6 +37,10 @@ export function testPropsBindingToLeaflet( await wrapper.setProps({ [propName]: value }) await flushPromises() + if (updatedProps['customCheck']) { + await updatedProps['customCheck'](wrapper) + continue + } if (updatedProps['expecting']?.[propName]) { updatedProps['expecting']?.[propName](leafletObject, i, wrapper) continue From ed0b401b4fb54d97729834ba177d44adb26b2b66 Mon Sep 17 00:00:00 2001 From: Max Engisch Date: Tue, 26 Aug 2025 13:33:59 +0200 Subject: [PATCH 15/15] test: add export tests --- src/lib.ts | 2 +- tests/exports/README.MD | 23 +++++++++++++++++++++++ tests/exports/lib.test.ts | 31 +++++++++++++++++++++++++++++++ vitest.config.ts | 6 +++--- 4 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 tests/exports/README.MD create mode 100644 tests/exports/lib.test.ts diff --git a/src/lib.ts b/src/lib.ts index 07635cb..cb64ac1 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -1 +1 @@ -export * from './components'; +export * from './components' diff --git a/tests/exports/README.MD b/tests/exports/README.MD new file mode 100644 index 0000000..880f7ca --- /dev/null +++ b/tests/exports/README.MD @@ -0,0 +1,23 @@ +### 📦 Export Integrity Tests + +This folder contains tests that validate the structure and accessibility of public exports — such as those defined in +`lib.ts` or `components/index.ts`. These tests ensure that all components, utilities, and modules are properly exposed +and can be imported without error. + +> ⚠️ **Important note on coverage:** +> Code coverage tools may report artificially high coverage when running export tests. This happens because files are +> marked as "covered" simply by being imported — even if none of their internal logic is executed. As a result, components +> may appear to have 100% coverage despite not being functionally tested. + +### ✅ For accurate coverage reporting + +To ensure meaningful coverage metrics, run your **unit and integration tests separately** and **exclude export-only +tests** from coverage analysis. You can do this by updating your `vitest.config.ts` like so: + +```ts +coverage: { + exclude: ['tests/exports/**'] +} +``` + +This keeps your coverage report focused on actual logic execution, not just import validation. \ No newline at end of file diff --git a/tests/exports/lib.test.ts b/tests/exports/lib.test.ts new file mode 100644 index 0000000..1623969 --- /dev/null +++ b/tests/exports/lib.test.ts @@ -0,0 +1,31 @@ +import * as lib from '../../src/lib' +import { describe, expect, it } from 'vitest' + +describe('lib', () => { + it('should export the components', () => { + expect(lib.LCircle).toBeDefined() + expect(lib.LCircleMarker).toBeDefined() + expect(lib.LControl).toBeDefined() + expect(lib.LControlAttribution).toBeDefined() + expect(lib.LControlLayers).toBeDefined() + expect(lib.LControlScale).toBeDefined() + expect(lib.LControlZoom).toBeDefined() + expect(lib.LFeatureGroup).toBeDefined() + expect(lib.LGeoJson).toBeDefined() + expect(lib.LGridLayer).toBeDefined() + expect(lib.LIcon).toBeDefined() + expect(lib.LImageOverlay).toBeDefined() + expect(lib.LLayerGroup).toBeDefined() + expect(lib.LMap).toBeDefined() + expect(lib.LMarker).toBeDefined() + expect(lib.LPolygon).toBeDefined() + expect(lib.LPolyline).toBeDefined() + expect(lib.LPopup).toBeDefined() + expect(lib.LRectangle).toBeDefined() + expect(lib.LSVGOverlay).toBeDefined() + expect(lib.LTileLayer).toBeDefined() + expect(lib.LTooltip).toBeDefined() + expect(lib.LVideoOverlay).toBeDefined() + expect(lib.LWmsTileLayer).toBeDefined() + }) +}) diff --git a/vitest.config.ts b/vitest.config.ts index ae3947d..152e61c 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,5 +1,5 @@ import { fileURLToPath } from 'node:url' -import { mergeConfig, defineConfig, configDefaults } from 'vitest/config' +import { configDefaults, defineConfig, mergeConfig } from 'vitest/config' import viteConfig from './vite.config' export default mergeConfig( @@ -7,12 +7,12 @@ export default mergeConfig( defineConfig({ test: { environment: 'jsdom', - exclude: [...configDefaults.exclude, 'e2e/**', 'node_modules'], + exclude: [...configDefaults.exclude, 'e2e/**', 'node_modules' /*, 'tests/exports/**' */], root: fileURLToPath(new URL('./', import.meta.url)), coverage: { provider: 'v8', include: ['src/**'], - exclude: ['src/types/**'], + exclude: ['src/types/**', 'tests/exports/**'] } } })