Skip to content
This repository was archived by the owner on May 20, 2024. It is now read-only.
Open
17 changes: 17 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
dist: focal
language: node_js
node_js:
- "18"

# Specify branches that should be built
branches:
only:
- main
- scofield_map_update

# Define the script to run for tests
script:
- yarn --frozen-lockfile
- yarn test


2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@
"workbox-precaching": "^7.0.0"
},
"engines": {
"node": ">= 16",
"node": ">= 18",
"npm": ">= 6.13.4",
"yarn": ">= 1.21.1"
},
Expand Down
2 changes: 2 additions & 0 deletions some-file.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Trigger build
// Trigger build
6 changes: 6 additions & 0 deletions src/maps/components/KMarker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export default {
},
emits: [
'dragend',
'marker-clicked',
],
setup () {
const leafletMap = inject('leafletMap')
Expand Down Expand Up @@ -96,6 +97,11 @@ export default {
draggable: this.draggable,
}).addTo(this.leafletMap))

this.leafletMarker.on('click', event => {
// If user click the marker, it would enter localselectMarker array
this.$emit('marker-clicked', event)
})

if (this.opacity) {
this.leafletMarker.setOpacity(this.opacity)
}
Expand Down
134 changes: 115 additions & 19 deletions src/maps/components/StandardMap.spec.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,133 @@
import { flushPromises } from '@vue/test-utils'
import { flushPromises, mount } from '@vue/test-utils'
import { vi } from 'vitest'

import GroupMarker from '@/maps/components/GroupMarker.vue'
import KMap from '@/maps/components/KMap.vue'
import KMarker from '@/maps/components/KMarker.vue'

import { makeGroup } from '>/enrichedFactories'
import { mountWithDefaults } from '>/helpers'

import StandardMap from './StandardMap.vue'
import { groupMarker } from './markers'

const markers = [...Array(20).keys()].map(e => groupMarker(makeGroup()))
// Define a utility to create a mock marker
function createMockMarker (id) {
return {
id,
latLng: { lat: Math.random() * 100, lng: Math.random() * 100 },
popup: 'Test Marker ' + id,
color: 'blue',
fontIcon: 'marker-icon',
click_count: 0, // Initialize click_count here
}
}

// Custom equality checker for markers
function containsMarker (container, marker) {
return container.some(m => m.id === marker.id)
}

describe('StandardMap', () => {
beforeEach(() => { vi.resetModules() })
it('renders markers with popups', async () => {
const wrapper = await mountWithDefaults(StandardMap, {
propsData: {
markers,
let wrapper
let markers

beforeEach(() => {
vi.resetModules()
markers = [createMockMarker(1), createMockMarker(2), createMockMarker(3), createMockMarker(4)]
wrapper = mount(StandardMap, {
props: { markers },
global: {
stubs: { KMap }, // Stub KMap to isolate the component test
},
})
})

it('selects a marker and updates localSelectedMarkers', async () => {
const kMarker = wrapper.findAllComponents(KMarker).at(0)
await kMarker.vm.$emit('marker-clicked', { marker: markers[0] })
await flushPromises()
expect(wrapper.findAllComponents(KMap).length).toBe(1)
expect(wrapper.findAllComponents(KMarker).length).toBe(markers.length)
expect(wrapper.findAllComponents(GroupMarker).length).toBe(markers.length)

// add and remove some markers
expect(containsMarker(wrapper.vm.localSelectedMarkers, markers[0])).toBe(true)
expect(wrapper.emitted()['update:selectedMarkers'][0]).toEqual([[markers[0]]])
})

it('toggles the selection state on repeated clicks', async () => {
const kMarker = wrapper.findAllComponents(KMarker).at(0)
// Simulate clicking the marker three times
for (let i = 0; i < 3; i++) {
await wrapper.setProps({ markers: markers.filter((e, idx) => idx !== i) })
await kMarker.vm.$emit('marker-clicked', { marker: markers[0] })
await flushPromises()
expect(wrapper.findAllComponents(KMarker).length).toBe(markers.length - 1)
expect(wrapper.findAllComponents(GroupMarker).length).toBe(markers.length - 1)
}

// Check if the toggle works correctly (should end up selected since it's an odd number of clicks)
expect(containsMarker(wrapper.vm.localSelectedMarkers, markers[0])).toBe(true)
expect(wrapper.vm.localSelectedMarkers).toHaveLength(1)
expect(markers[0].click_count).toBe(3)
})

it('changes selected marker and verifies the update', async () => {
const kMarkers = wrapper.findAllComponents(KMarker)

// Select the first marker
await kMarkers.at(0).vm.$emit('marker-clicked', { marker: markers[0] })
await flushPromises()

// Select the second marker
await kMarkers.at(1).vm.$emit('marker-clicked', { marker: markers[1] })
await flushPromises()
expect(containsMarker(wrapper.vm.localSelectedMarkers, markers[0])).toBe(false)
expect(containsMarker(wrapper.vm.localSelectedMarkers, markers[1])).toBe(true)
expect(containsMarker(wrapper.vm.localSelectedMarkers, markers[2])).toBe(false)
expect(containsMarker(wrapper.vm.localSelectedMarkers, markers[3])).toBe(false)
expect(wrapper.vm.localSelectedMarkers).not.toContain(markers[0])
expect(wrapper.vm.localSelectedMarkers).not.toContain(markers[2])
expect(wrapper.vm.localSelectedMarkers).not.toContain(markers[3])
expect(wrapper.emitted()['update:selectedMarkers'][1]).toEqual([[markers[1]]])

// Select the third marker
await kMarkers.at(2).vm.$emit('marker-clicked', { marker: markers[2] })
await flushPromises()
expect(containsMarker(wrapper.vm.localSelectedMarkers, markers[0])).toBe(false)
expect(containsMarker(wrapper.vm.localSelectedMarkers, markers[1])).toBe(false)
expect(containsMarker(wrapper.vm.localSelectedMarkers, markers[2])).toBe(true)
expect(containsMarker(wrapper.vm.localSelectedMarkers, markers[3])).toBe(false)
expect(wrapper.vm.localSelectedMarkers).not.toContain(markers[0])
expect(wrapper.vm.localSelectedMarkers).not.toContain(markers[1])
expect(wrapper.vm.localSelectedMarkers).not.toContain(markers[3])
expect(wrapper.emitted()['update:selectedMarkers'][2]).toEqual([[markers[2]]])

// Select the fourth marker
await kMarkers.at(3).vm.$emit('marker-clicked', { marker: markers[1] })
await flushPromises()

expect(containsMarker(wrapper.vm.localSelectedMarkers, markers[0])).toBe(false)
expect(containsMarker(wrapper.vm.localSelectedMarkers, markers[1])).toBe(false)
expect(containsMarker(wrapper.vm.localSelectedMarkers, markers[2])).toBe(false)
expect(containsMarker(wrapper.vm.localSelectedMarkers, markers[3])).toBe(true)
expect(wrapper.vm.localSelectedMarkers).not.toContain(markers[0])
expect(wrapper.vm.localSelectedMarkers).not.toContain(markers[1])
expect(wrapper.vm.localSelectedMarkers).not.toContain(markers[2])
expect(wrapper.emitted()['update:selectedMarkers'][3]).toEqual([[markers[3]]])

// Select back to the first marker
await kMarkers.at(0).vm.$emit('marker-clicked', { marker: markers[0] })
await flushPromises()
expect(containsMarker(wrapper.vm.localSelectedMarkers, markers[0])).toBe(true)
expect(containsMarker(wrapper.vm.localSelectedMarkers, markers[1])).toBe(false)
expect(containsMarker(wrapper.vm.localSelectedMarkers, markers[2])).toBe(false)
expect(containsMarker(wrapper.vm.localSelectedMarkers, markers[3])).toBe(false)
expect(wrapper.vm.localSelectedMarkers).not.toContain(markers[1])
expect(wrapper.vm.localSelectedMarkers).not.toContain(markers[2])
expect(wrapper.vm.localSelectedMarkers).not.toContain(markers[3])
expect(wrapper.emitted()['update:selectedMarkers'][0]).toEqual([[markers[0]]])

// Select back to the fourth marker
await kMarkers.at(1).vm.$emit('marker-clicked', { marker: markers[1] })
await flushPromises()

expect(containsMarker(wrapper.vm.localSelectedMarkers, markers[0])).toBe(false)
expect(containsMarker(wrapper.vm.localSelectedMarkers, markers[1])).toBe(true)
expect(containsMarker(wrapper.vm.localSelectedMarkers, markers[2])).toBe(false)
expect(containsMarker(wrapper.vm.localSelectedMarkers, markers[3])).toBe(false)
expect(wrapper.vm.localSelectedMarkers).not.toContain(markers[0])
expect(wrapper.vm.localSelectedMarkers).not.toContain(markers[2])
expect(wrapper.vm.localSelectedMarkers).not.toContain(markers[3])
expect(wrapper.emitted()['update:selectedMarkers'][1]).toEqual([[markers[1]]])
})
})
83 changes: 79 additions & 4 deletions src/maps/components/StandardMap.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<template>
<KMap
ref="kmap"
v-bind="mapProps"
:show-attribution="showAttribution"
:padding-top-left="paddingTopLeft"
Expand All @@ -19,6 +20,7 @@
:popup="marker.popup"
:opacity="opacityFor(marker)"
@dragend="event => dragend(event, marker)"
@marker-clicked="event => handleMarkerClicked(event, marker)"
/>
<QMenu
ref="popover"
Expand All @@ -42,7 +44,8 @@ import KMap from './KMap.vue'
import KMarker from './KMarker.vue'

const SELECTED_OPACITY = 1
const UNSELECTED_OPACITY = 0.5
const UNSELECTED_OPACITY = 0.7
const i = 1

export default {
components: {
Expand Down Expand Up @@ -96,25 +99,31 @@ export default {
'map-move-end',
'marker-moved',
'map-click',
'update:selectedMarkers',
],
data () {
return {
lastZoom: 15,
popoverOffset: [0, 0],
popoverLatLng: null,
// Create the localSelect Marker, when we click the specific marker, it would push in localSelect marker
localSelectedMarkers: this.selectedMarkers || [],
}
},
computed: {
mapProps () {
console.log('the times called here: ', i)
if (this.forceZoom && this.forceCenter) {
// This is used on the full page map view, where the params come from the URL, so we need to ignore all the markers
console.log('here 1')
return {
zoom: this.forceZoom,
center: this.forceCenter,
}
}
if (this.forceBounds) {
// This is used when we emphasize nearby groups
console.log('here 2')
return {
bounds: this.forceBounds,
}
Expand Down Expand Up @@ -147,7 +156,15 @@ export default {
}
},
markersForBounds () {
return this.hasSelectedMarkers ? this.selectedMarkers : this.markers
if (this.hasSelectedMarkers) {
console.log('Selected markers:', this.selectedMarkers) // Log the selected markers
return this.selectedMarkers
}
else {
console.log('No selected markers, showing all markers.') // Inform when no markers are selected
return this.markers
}
// return this.hasSelectedMarkers ? this.selectedMarkers : this.markers
},
hasSelectedMarkers () {
return this.selectedMarkers && this.selectedMarkers.length > 0
Expand All @@ -157,6 +174,16 @@ export default {
zoom (val) {
if (Number.isInteger(val)) this.lastZoom = val
},
selectedMarkers: {
handler (newVal) {
this.localSelectedMarkers = newVal || []
},
immediate: true,
deep: true,
},
},
mounted () {
this.localSelectedMarkers = [...this.selectedMarkers]
},
methods: {
openContextMenu (event) {
Expand All @@ -172,16 +199,64 @@ export default {
dragend ({ target }, marker) {
this.$emit('marker-moved', target.getLatLng(), marker)
},
handleMarkerClicked ({ target }, marker) {
const isSelected = this.localSelectedMarkers.some(m => m.id === marker.id)
if (!isSelected) {
this.localSelectedMarkers = [marker]
marker.click_count = 1
this.centerAndZoom(marker.latLng, 15)
}
else {
// Marker is already selected, increment click count
marker.click_count = (marker.click_count || 0) + 1
if (marker.click_count % 2 === 0) {
// Even number of clicks: zoom out
this.fitBoundsToMarkers()
}
else {
// Odd number of clicks: zoom in
this.centerAndZoom(marker.latLng, 15)
}
}
this.$emit('update:selectedMarkers', this.localSelectedMarkers)
console.log('the local select marker is, ', this.localSelectedMarkers)
console.log('this.selectmarker is, ', this.localSelectedMarkers)
},

updateZoom (val) {
console.log('in zoom level, the select marker is', this.localSelectedMarkers)
if (Number.isInteger(val)) this.lastZoom = val
if (this.localSelectedMarkers.length === 1) {
this.centerAndZoom(this.localSelectedMarkers[0].latLng, val)
}
},
centerAndZoom (latLng, zoomLevel) {
// If user choose specific marker, the map would automatically centralize to the location
const mapInstance = this.$refs.kmap.leafletMap
if (mapInstance) {
mapInstance.setView(latLng, zoomLevel)
}
},
fitBoundsToMarkers () {
// This function can help the map always focus on the selected marker that user choose
const mapInstance = this.$refs.kmap.leafletMap
if (mapInstance && this.markers.length) {
const bounds = latLngBounds(this.markersForBounds.map(m => m.latLng)).pad(0.2)
mapInstance.fitBounds(bounds)
}
},

mapClick (event) {
this.closeContextMenu()
this.$emit('map-click', event)
},
opacityFor (marker) {
if (!this.hasSelectedMarkers) return SELECTED_OPACITY
return this.selectedMarkers.find(m => m.id === marker.id) ? SELECTED_OPACITY : UNSELECTED_OPACITY
// If user choose the specific marker, this marker would change to darker color
if (!marker || typeof marker.id === 'undefined') {
console.error('Invalid marker:', marker)
return UNSELECTED_OPACITY
}
return this.localSelectedMarkers.some(m => m.id === marker.id) ? SELECTED_OPACITY : UNSELECTED_OPACITY
},
},
}
Expand Down