fix(webapp): fix map & user-teaser (#9484)

This commit is contained in:
Ulf Gebhardt 2026-04-03 20:39:04 +02:00 committed by GitHub
parent 9fa22ee3eb
commit 4245d2cbd4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 216 additions and 229 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 86 KiB

View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="33" viewBox="0 0 20 33">
<ellipse cx="10" cy="27" rx="9" ry="5" fill="#c4c4c4" opacity="0.3"/>
<path d="M19.25,10.4a13.0663,13.0663,0,0,1-1.4607,5.2235,41.5281,41.5281,0,0,1-3.2459,5.5483c-1.1829,1.7369-2.3662,3.2784-3.2541,4.3859-.4438.5536-.8135.9984-1.0721,1.3046-.0844.1-.157.1852-.2164.2545-.06-.07-.1325-.1564-.2173-.2578-.2587-.3088-.6284-.7571-1.0723-1.3147-.8879-1.1154-2.0714-2.6664-3.2543-4.41a42.2677,42.2677,0,0,1-3.2463-5.5535A12.978,12.978,0,0,1,.75,10.4,9.4659,9.4659,0,0,1,10,.75,9.4659,9.4659,0,0,1,19.25,10.4Z"/>
<path d="M13.55,10A3.55,3.55,0,1,1,10,6.45,3.5484,3.5484,0,0,1,13.55,10Z" fill="#fff"/>
</svg>

After

Width:  |  Height:  |  Size: 692 B

View File

@ -140,7 +140,7 @@ hr {
transition-delay: 0;
transition: opacity 80ms ease-out;
@media(hover: none) {
@media(pointer: coarse) {
pointer-events: all;
}
}

View File

@ -48,8 +48,10 @@ export default {
if (typeof document === 'undefined') return
if (isOpen) {
document.body.classList.add('dropdown-open')
this.addOverlayClickHandler()
} else {
document.body.classList.remove('dropdown-open')
this.removeOverlayClickHandler()
}
},
},
@ -57,6 +59,7 @@ export default {
beforeDestroy() {
clearTimeout(mouseEnterTimer)
clearTimeout(mouseLeaveTimer)
this.removeOverlayClickHandler()
if (this.isPopoverOpen) {
this.isPopoverOpen = false
if (typeof document !== 'undefined') {
@ -116,6 +119,28 @@ export default {
clearTimeout(mouseEnterTimer)
clearTimeout(mouseLeaveTimer)
},
addOverlayClickHandler() {
this.overlayClickHandler = (e) => {
// Allow clicks inside the popover content (rendered under <body> by v-tooltip)
if (e.target.closest('.tooltip-inner, .popover-inner')) return
// Allow clicks on the trigger itself
if (this.$el.contains(e.target)) return
e.stopPropagation()
e.preventDefault()
this.isPopoverOpen = false
}
// Capture phase fires before any other handler
setTimeout(() => {
if (this.isPopoverOpen && this.overlayClickHandler) {
document.addEventListener('click', this.overlayClickHandler, true)
}
}, 0)
},
removeOverlayClickHandler() {
if (!this.overlayClickHandler) return
document.removeEventListener('click', this.overlayClickHandler, true)
this.overlayClickHandler = null
},
},
}
</script>

View File

@ -5,6 +5,12 @@ import Vuex from 'vuex'
const localVue = global.localVue
window.matchMedia = jest.fn().mockImplementation(() => ({
matches: false,
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
}))
// Mock Math.random, used in Dropdown
Object.assign(Math, {
random: () => 0,
@ -12,11 +18,6 @@ Object.assign(Math, {
const waitForPopover = async () => await new Promise((resolve) => setTimeout(resolve, 1000))
let mockIsTouchDevice
jest.mock('../utils/isTouchDevice', () => ({
isTouchDevice: jest.fn(() => mockIsTouchDevice),
}))
const userTilda = {
name: 'Tilda Swinton',
slug: 'tilda-swinton',
@ -65,7 +66,11 @@ describe('UserTeaser', () => {
user = userTilda,
withPopoverEnabled = true,
}) => {
mockIsTouchDevice = onTouchScreen
window.matchMedia.mockImplementation(() => ({
matches: onTouchScreen,
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
}))
const store = new Vuex.Store({
getters: {

View File

@ -2,6 +2,16 @@ import { render, waitFor, fireEvent } from '@testing-library/vue'
import { RouterLinkStub } from '@vue/test-utils'
import UserTeaserHelper from './UserTeaserHelper.vue'
let mockMatchMediaMatches = false
const mockMatchMediaListeners = []
window.matchMedia = jest.fn().mockImplementation(() => ({
get matches() {
return mockMatchMediaMatches
},
addEventListener: jest.fn((event, handler) => mockMatchMediaListeners.push(handler)),
removeEventListener: jest.fn(),
}))
const localVue = global.localVue
const userLink = {
@ -9,12 +19,6 @@ const userLink = {
params: { slug: 'slug', id: 'id' },
}
let mockIsTouchDevice
jest.mock('../utils/isTouchDevice', () => ({
isTouchDevice: jest.fn(() => mockIsTouchDevice),
}))
describe('UserTeaserHelper', () => {
const Wrapper = ({
withLinkToProfile = true,
@ -22,7 +26,7 @@ describe('UserTeaserHelper', () => {
withPopoverEnabled = true,
hoverDelay = 500,
}) => {
mockIsTouchDevice = onTouchScreen
mockMatchMediaMatches = onTouchScreen
return render(UserTeaserHelper, {
localVue,

View File

@ -20,10 +20,11 @@
</template>
<script>
import { isTouchDevice } from '../utils/isTouchDevice'
import touchDevice from '~/mixins/touchDevice'
export default {
name: 'UserTeaserHelper',
mixins: [touchDevice],
props: {
userLink: { type: Object, default: null },
linkToProfile: { type: Boolean, default: true },
@ -36,11 +37,6 @@ export default {
isHovering: false,
}
},
computed: {
isTouchDevice() {
return isTouchDevice()
},
},
methods: {
handleMouseEnter() {
if (!this.showPopover) return

View File

@ -2,6 +2,15 @@ import { render } from '@testing-library/vue'
import { RouterLinkStub } from '@vue/test-utils'
import UserTeaserPopover from './UserTeaserPopover.vue'
const mockMatchMedia = (matches = false) => {
window.matchMedia = jest.fn().mockImplementation(() => ({
matches,
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
}))
}
mockMatchMedia(false)
const localVue = global.localVue
const user = {
@ -51,10 +60,7 @@ describe('UserTeaserPopover', () => {
onTouchScreen = false,
userData = user,
}) => {
const mockIsTouchDevice = onTouchScreen
jest.mock('../utils/isTouchDevice', () => ({
isTouchDevice: jest.fn(() => mockIsTouchDevice),
}))
mockMatchMedia(onTouchScreen)
return render(UserTeaserPopover, {
localVue,
propsData: {

View File

@ -44,11 +44,12 @@
import { OsButton, OsNumber } from '@ocelot-social/ui'
import Badges from '~/components/Badges.vue'
import LocationInfo from '~/components/LocationInfo/LocationInfo.vue'
import { isTouchDevice } from '~/components/utils/isTouchDevice'
import touchDevice from '~/mixins/touchDevice'
import { userTeaserQuery } from '~/graphql/User.js'
export default {
name: 'UserTeaserPopover',
mixins: [touchDevice],
components: {
Badges,
LocationInfo,
@ -60,9 +61,6 @@ export default {
userLink: { type: Object },
},
computed: {
isTouchDevice() {
return isTouchDevice()
},
user() {
return (this.User && this.User[0]) ?? null
},

View File

@ -412,7 +412,19 @@ exports[`UserTeaserPopover given a touch device shows button when userLink is pr
</li>
</ul>
<!---->
<a
class="link os-button inline-flex items-center justify-center [white-space-collapse:collapse] relative appearance-none font-semibold tracking-[0.05em] transition-[color,background-color] duration-[80ms] ease-[cubic-bezier(0.25,0.46,0.45,0.94)] cursor-pointer select-none border-[0.8px] border-solid focus:outline-1 disabled:pointer-events-none disabled:cursor-default focus:outline-dashed focus:outline-[var(--color-primary)] shadow-[inset_0_0_0_1px_rgba(0,0,0,0.05)] disabled:bg-[var(--color-disabled)] disabled:text-[var(--color-disabled-contrast)] disabled:border-[var(--color-disabled)] disabled:shadow-[inset_0_0_0_1px_transparent] disabled:hover:bg-[var(--color-disabled)] disabled:hover:text-[var(--color-disabled-contrast)] disabled:hover:border-[var(--color-disabled)] disabled:active:bg-[var(--color-disabled)] disabled:active:text-[var(--color-disabled-contrast)] disabled:active:border-[var(--color-disabled)] h-[36px] min-w-[36px] px-[16px] py-0 text-[15px] leading-[normal] rounded-[5px] align-middle bg-[var(--color-primary)] text-[var(--color-primary-contrast)] border-[var(--color-primary)] hover:bg-[var(--color-primary-hover)] hover:border-[var(--color-primary-hover)] hover:text-[var(--color-primary-contrast)] active:bg-[var(--color-primary-active)] active:border-[var(--color-primary-active)] active:text-[var(--color-primary-contrast)] link"
data-appearance="filled"
data-variant="primary"
>
<span
class="inline-flex items-center"
>
user-teaser.popover.open-profile
</span>
</a>
</div>
</div>
`;

View File

@ -0,0 +1,23 @@
export default {
data() {
const pointerQuery =
typeof window !== 'undefined' ? window.matchMedia('(pointer: coarse)') : null
return {
isTouchDevice: pointerQuery ? pointerQuery.matches : false,
pointerQuery,
}
},
mounted() {
if (this.pointerQuery) {
this.onPointerChange = (e) => {
this.isTouchDevice = e.matches
}
this.pointerQuery.addEventListener('change', this.onPointerChange)
}
},
beforeDestroy() {
if (this.pointerQuery && this.onPointerChange) {
this.pointerQuery.removeEventListener('change', this.onPointerChange)
}
},
}

View File

@ -6,7 +6,12 @@ import Map from './map'
import MapboxGeocoder from '@mapbox/mapbox-gl-geocoder'
jest.mock('@mapbox/mapbox-gl-geocoder', () => {
return jest.fn().mockImplementation(jest.fn())
return jest.fn().mockImplementation(() => {
const mockParent = global.document.createElement('div')
const container = global.document.createElement('div')
mockParent.appendChild(container)
return { container, clear: jest.fn() }
})
})
jest.mock('mapbox-gl', () => {
@ -79,9 +84,9 @@ const mapMock = {
getContainer: mapGetContainerMock,
queryRenderedFeatures: mapQueryRenderedFeaturesMock,
getStyle: mapGetStyleMock,
getCanvas: jest.fn(() => ({
getCanvas: jest.fn().mockReturnValue({
style: { cursor: '' },
})),
}),
}
const stubs = {
@ -574,18 +579,6 @@ describe('map', () => {
expect(links[1].getAttribute('href')).toBe('/groups/g1/journalism')
})
it('clears pending leave timeout', () => {
jest.useFakeTimers()
wrapper.vm.popupOnLeaveTimeoutId = setTimeout(() => {}, 3000)
mapQueryRenderedFeaturesMock.mockReturnValueOnce(features)
onEventMocks.mouseenter({
point: { x: 100, y: 200 },
lngLat: { lng: 10.0, lat: 53.55 },
})
expect(wrapper.vm.popupOnLeaveTimeoutId).toBeNull()
jest.useRealTimers()
})
it('removes existing popup before showing new one', () => {
mapboxgl.__popupInstance.isOpen.mockReturnValueOnce(true)
mapQueryRenderedFeaturesMock.mockReturnValueOnce(features)
@ -621,20 +614,10 @@ describe('map', () => {
})
describe('mouseleave event', () => {
it('sets timeout to remove popup when open', () => {
jest.useFakeTimers()
mapboxgl.__popupInstance.isOpen.mockReturnValueOnce(true)
it('resets cursor style', () => {
mapMock.getCanvas().style.cursor = 'pointer'
onEventMocks.mouseleave()
expect(wrapper.vm.popupOnLeaveTimeoutId).toBeTruthy()
jest.advanceTimersByTime(3000)
expect(mapboxgl.__popupInstance.remove).toHaveBeenCalled()
jest.useRealTimers()
})
it('does nothing when popup is not open', () => {
mapboxgl.__popupInstance.isOpen.mockReturnValueOnce(false)
onEventMocks.mouseleave()
expect(wrapper.vm.popupOnLeaveTimeoutId).toBeFalsy()
expect(mapMock.getCanvas().style.cursor).toBe('')
})
})
@ -1057,10 +1040,13 @@ describe('map', () => {
})
describe('beforeDestroy', () => {
it('removes resize listener', () => {
it('removes resize listeners', () => {
wrapper.vm.onMapLoad({ map: mapMock })
const spy = jest.spyOn(window, 'removeEventListener')
const geocoderHandler = wrapper.vm.geocoderCollapseHandler
wrapper.destroy()
expect(spy).toHaveBeenCalledWith('resize', wrapper.vm.updateMapPosition)
expect(spy).toHaveBeenCalledWith('resize', geocoderHandler)
spy.mockRestore()
})
})

View File

@ -33,17 +33,19 @@
</button>
<div
id="map-legend-content"
v-show="legendOpen || !isMobile"
:class="{ 'map-legend-content--hidden': isMobile && !legendOpen }"
class="map-legend-content"
role="region"
:aria-label="$t('map.legend.title')"
>
<div v-for="type in markers.types" :key="type.id" class="map-legend-item">
<img
:alt="$t('map.legend.' + type.id)"
:src="'/img/mapbox/marker-icons/' + type.icon.legendName"
width="15"
/>
<span :style="{ color: type.color }">
<os-icon
:icon="icons.mapPinFilled"
size="xl"
:aria-label="$t('map.legend.' + type.id)"
/>
</span>
{{ $t('map.legend.' + type.id) }}
</div>
</div>
@ -61,6 +63,8 @@ import mapboxgl from 'mapbox-gl'
import MapboxGeocoder from '@mapbox/mapbox-gl-geocoder'
import '@mapbox/mapbox-gl-geocoder/dist/mapbox-gl-geocoder.css'
import { mapGetters } from 'vuex'
import { OsIcon } from '@ocelot-social/ui'
import { iconRegistry } from '~/utils/iconRegistry'
import { profileUserQuery } from '~/graphql/User'
import { mapQuery } from '~/graphql/MapQuery'
import mobile from '~/mixins/mobile'
@ -73,6 +77,7 @@ export default {
mixins: [mobile(maxMobileWidth)],
components: {
Empty,
OsIcon,
},
head() {
return {
@ -96,33 +101,33 @@ export default {
types: [
{
id: 'theUser',
color: '#f79640',
icon: {
id: 'marker-orange',
legendName: 'mapbox-marker-icon-orange.svg',
mapName: 'mapbox-marker-icon-20px-orange.png',
},
},
{
id: 'user',
color: '#33c377',
icon: {
id: 'marker-green',
legendName: 'mapbox-marker-icon-green.svg',
mapName: 'mapbox-marker-icon-20px-green.png',
},
},
{
id: 'group',
color: '#f84d4d',
icon: {
id: 'marker-red',
legendName: 'mapbox-marker-icon-red.svg',
mapName: 'mapbox-marker-icon-20px-red.png',
},
},
{
id: 'event',
color: '#7753eb',
icon: {
id: 'marker-purple',
legendName: 'mapbox-marker-icon-purple.svg',
mapName: 'mapbox-marker-icon-20px-purple.png',
},
},
@ -133,10 +138,12 @@ export default {
isSourceAndLayerAdded: false,
isFlyToCenter: false,
popup: null,
popupOnLeaveTimeoutId: null,
},
}
},
created() {
this.icons = iconRegistry
},
async mounted() {
this.updateMapPosition()
window.addEventListener('resize', this.updateMapPosition)
@ -149,6 +156,9 @@ export default {
},
beforeDestroy() {
window.removeEventListener('resize', this.updateMapPosition)
if (this.geocoderCollapseHandler) {
window.removeEventListener('resize', this.geocoderCollapseHandler)
}
},
computed: {
...mapGetters({
@ -212,6 +222,21 @@ export default {
},
},
methods: {
addGeocoder() {
this.geocoder = new MapboxGeocoder({
accessToken: this.$env.MAPBOX_TOKEN,
mapboxgl: this.mapboxgl,
marker: false,
collapsed: this.geocoderCollapsed,
})
this.map.addControl(this.geocoder, 'top-right')
// Ensure geocoder stays at the top of the control group
const container = this.geocoder.container
const parent = container.parentNode
if (parent && parent.firstChild !== container) {
parent.insertBefore(container, parent.firstChild)
}
},
updateMapPosition() {
const navbar = document.getElementById('navbar')
const footer = document.getElementById('footer')
@ -239,15 +264,17 @@ export default {
})
// add search field for locations
this.map.addControl(
new MapboxGeocoder({
accessToken: this.$env.MAPBOX_TOKEN,
mapboxgl: this.mapboxgl,
marker: false,
collapsed: window.innerWidth <= 810,
}),
'top-right',
)
this.geocoderCollapsed = window.innerWidth <= 810
this.addGeocoder()
this.geocoderCollapseHandler = () => {
const shouldCollapse = window.innerWidth <= 810
if (shouldCollapse !== this.geocoderCollapsed) {
this.geocoderCollapsed = shouldCollapse
this.map.removeControl(this.geocoder)
this.addGeocoder()
}
}
window.addEventListener('resize', this.geocoderCollapseHandler)
// add style switcher control
let closePopoverHandler = null
@ -335,10 +362,6 @@ export default {
// show popup for given features at coordinates
const showPopup = (features, lngLat) => {
if (this.popupOnLeaveTimeoutId) {
clearTimeout(this.popupOnLeaveTimeoutId)
this.popupOnLeaveTimeoutId = null
}
if (this.markers.popup.isOpen()) {
this.markers.popup.remove()
}
@ -441,12 +464,7 @@ export default {
})
this.map.on('mouseleave', 'markers', () => {
if (this.markers.popup.isOpen()) {
this.popupOnLeaveTimeoutId = setTimeout(() => {
this.map.getCanvas().style.cursor = ''
this.markers.popup.remove()
}, 3000)
}
this.map.getCanvas().style.cursor = ''
})
// Mobile: show popup on click/tap
@ -722,6 +740,8 @@ export default {
flex-direction: column;
overflow: hidden;
z-index: 1;
font-family: $font-family-text;
font-size: $font-size-base;
}
.mgl-map-wrapper {
@ -733,10 +753,22 @@ export default {
overflow: hidden;
}
.mapboxgl-ctrl-attrib.mapboxgl-compact {
display: flex;
align-items: center;
min-height: 24px;
padding: 0;
}
.mapboxgl-ctrl-attrib-button {
position: static;
}
.mapboxgl-popup-content {
max-height: 40vh;
overflow: hidden;
padding: 10px;
border-radius: $border-radius-x-large;
}
.map-popup-container {
@ -747,14 +779,14 @@ export default {
}
.mapboxgl-popup-close-button {
font-size: 1.2rem;
font-size: $font-size-large;
padding: 2px 6px;
z-index: 1;
}
.map-popup-header {
font-weight: bold;
font-size: 1.1em;
font-size: $font-size-large;
margin-bottom: 4px;
padding-right: 16px;
flex-shrink: 0;
@ -804,7 +836,7 @@ export default {
height: 29px;
min-width: 29px;
background-color: white;
border-radius: 4px;
border-radius: $border-radius-x-large;
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
overflow: hidden;
@ -832,9 +864,9 @@ export default {
left: 10px;
background: rgba(255, 255, 255, 0.75);
backdrop-filter: blur(4px);
border-radius: 4px;
border-radius: $border-radius-x-large;
z-index: 1;
font-size: 0.8rem;
font-size: $font-size-base;
color: $color-neutral-10;
}
@ -845,11 +877,13 @@ export default {
border: none;
background: rgba(0, 0, 0, 0.05);
color: $color-neutral-10;
border-radius: 4px;
border-radius: $border-radius-x-large;
cursor: pointer;
font-size: 0.8rem;
font-size: $font-size-base;
text-align: left;
order: 1;
align-items: center;
justify-content: space-between;
&:hover,
&:active {
@ -858,8 +892,8 @@ export default {
}
.map-legend-arrow {
float: right;
font-size: 0.7rem;
font-size: $font-size-small;
line-height: 1;
}
.map-legend-content {
@ -879,22 +913,34 @@ export default {
}
.map-legend-toggle {
display: block;
display: flex;
}
.map-legend-content {
order: 0;
}
.map-legend-content--hidden {
visibility: hidden;
height: 0;
padding: 0 8px;
overflow: hidden;
}
.map-legend--open .map-legend-content {
border-bottom: 1px solid #eee;
}
.map-legend--open .map-legend-toggle {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
}
.map-style-switcher {
position: relative;
background: white;
border-radius: 4px;
border-radius: $border-radius-x-large;
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
}
@ -922,7 +968,7 @@ export default {
right: 100%;
margin-right: 6px;
background: white;
border-radius: 4px;
border-radius: $border-radius-x-large;
box-shadow: 0 0 4px rgba(0, 0, 0, 0.3);
white-space: nowrap;
overflow: hidden;
@ -939,7 +985,7 @@ export default {
border: none;
background: none;
cursor: pointer;
font-size: 0.8rem;
font-size: $font-size-base;
text-align: left;
&:not(:last-child) {

View File

@ -1,16 +0,0 @@
<!-- Create a custom map style: https://studio.mapbox.com -->
<svg id="marker" data-name="marker" xmlns="http://www.w3.org/2000/svg" width="20" height="48" viewBox="0 0 20 48">
<g id="mapbox-marker-icon">
<g id="icon">
<ellipse id="shadow" cx="10" cy="27" rx="9" ry="5" fill="#c4c4c4" opacity="0.3" style="isolation: isolate"/>
<g id="mask" opacity="0.3">
<g id="group">
<path id="shadow-2" data-name="shadow" fill="#bfbfbf" d="M10,32c5,0,9-2.2,9-5s-4-5-9-5-9,2.2-9,5S5,32,10,32Z" fill-rule="evenodd"/>
</g>
</g>
<path id="color" fill="#4264fb" stroke="#314ccd" stroke-width="0.5" d="M19.25,10.4a13.0663,13.0663,0,0,1-1.4607,5.2235,41.5281,41.5281,0,0,1-3.2459,5.5483c-1.1829,1.7369-2.3662,3.2784-3.2541,4.3859-.4438.5536-.8135.9984-1.0721,1.3046-.0844.1-.157.1852-.2164.2545-.06-.07-.1325-.1564-.2173-.2578-.2587-.3088-.6284-.7571-1.0723-1.3147-.8879-1.1154-2.0714-2.6664-3.2543-4.41a42.2677,42.2677,0,0,1-3.2463-5.5535A12.978,12.978,0,0,1,.75,10.4,9.4659,9.4659,0,0,1,10,.75,9.4659,9.4659,0,0,1,19.25,10.4Z"/>
<path id="circle" fill="#fff" stroke="#314ccd" stroke-width="0.5" d="M13.55,10A3.55,3.55,0,1,1,10,6.45,3.5484,3.5484,0,0,1,13.55,10Z"/>
</g>
</g>
<rect width="20" height="48" fill="none"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,16 +0,0 @@
<!-- Create a custom map style: https://studio.mapbox.com -->
<svg id="marker" data-name="marker" xmlns="http://www.w3.org/2000/svg" width="20" height="48" viewBox="0 0 20 48">
<g id="mapbox-marker-icon">
<g id="icon">
<ellipse id="shadow" cx="10" cy="27" rx="9" ry="5" fill="#c4c4c4" opacity="0.3" style="isolation: isolate"/>
<g id="mask" opacity="0.3">
<g id="group">
<path id="shadow-2" data-name="shadow" fill="#bfbfbf" d="M10,32c5,0,9-2.2,9-5s-4-5-9-5-9,2.2-9,5S5,32,10,32Z" fill-rule="evenodd"/>
</g>
</g>
<path id="color" fill="#5b7897" stroke="#23374d" stroke-width="0.5" d="M19.25,10.4a13.0663,13.0663,0,0,1-1.4607,5.2235,41.5281,41.5281,0,0,1-3.2459,5.5483c-1.1829,1.7369-2.3662,3.2784-3.2541,4.3859-.4438.5536-.8135.9984-1.0721,1.3046-.0844.1-.157.1852-.2164.2545-.06-.07-.1325-.1564-.2173-.2578-.2587-.3088-.6284-.7571-1.0723-1.3147-.8879-1.1154-2.0714-2.6664-3.2543-4.41a42.2677,42.2677,0,0,1-3.2463-5.5535A12.978,12.978,0,0,1,.75,10.4,9.4659,9.4659,0,0,1,10,.75,9.4659,9.4659,0,0,1,19.25,10.4Z"/>
<path id="circle" fill="#fff" stroke="#23374d" stroke-width="0.5" d="M13.55,10A3.55,3.55,0,1,1,10,6.45,3.5484,3.5484,0,0,1,13.55,10Z"/>
</g>
</g>
<rect width="20" height="48" fill="none"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,16 +0,0 @@
<!-- Create a custom map style: https://studio.mapbox.com -->
<svg id="marker" data-name="marker" xmlns="http://www.w3.org/2000/svg" width="20" height="48" viewBox="0 0 20 48">
<g id="mapbox-marker-icon">
<g id="icon">
<ellipse id="shadow" cx="10" cy="27" rx="9" ry="5" fill="#c4c4c4" opacity="0.3" style="isolation: isolate"/>
<g id="mask" opacity="0.3">
<g id="group">
<path id="shadow-2" data-name="shadow" fill="#bfbfbf" d="M10,32c5,0,9-2.2,9-5s-4-5-9-5-9,2.2-9,5S5,32,10,32Z" fill-rule="evenodd"/>
</g>
</g>
<path id="color" fill="#33c377" stroke="#269561" stroke-width="0.5" d="M19.25,10.4a13.0663,13.0663,0,0,1-1.4607,5.2235,41.5281,41.5281,0,0,1-3.2459,5.5483c-1.1829,1.7369-2.3662,3.2784-3.2541,4.3859-.4438.5536-.8135.9984-1.0721,1.3046-.0844.1-.157.1852-.2164.2545-.06-.07-.1325-.1564-.2173-.2578-.2587-.3088-.6284-.7571-1.0723-1.3147-.8879-1.1154-2.0714-2.6664-3.2543-4.41a42.2677,42.2677,0,0,1-3.2463-5.5535A12.978,12.978,0,0,1,.75,10.4,9.4659,9.4659,0,0,1,10,.75,9.4659,9.4659,0,0,1,19.25,10.4Z"/>
<path id="circle" fill="#fff" stroke="#269561" stroke-width="0.5" d="M13.55,10A3.55,3.55,0,1,1,10,6.45,3.5484,3.5484,0,0,1,13.55,10Z"/>
</g>
</g>
<rect width="20" height="48" fill="none"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,16 +0,0 @@
<!-- Create a custom map style: https://studio.mapbox.com -->
<svg id="marker" data-name="marker" xmlns="http://www.w3.org/2000/svg" width="20" height="48" viewBox="0 0 20 48">
<g id="mapbox-marker-icon">
<g id="icon">
<ellipse id="shadow" cx="10" cy="27" rx="9" ry="5" fill="#c4c4c4" opacity="0.3" style="isolation: isolate"/>
<g id="mask" opacity="0.3">
<g id="group">
<path id="shadow-2" data-name="shadow" fill="#bfbfbf" d="M10,32c5,0,9-2.2,9-5s-4-5-9-5-9,2.2-9,5S5,32,10,32Z" fill-rule="evenodd"/>
</g>
</g>
<path id="color" fill="#f79640" stroke="#ba7334" stroke-width="0.5" d="M19.25,10.4a13.0663,13.0663,0,0,1-1.4607,5.2235,41.5281,41.5281,0,0,1-3.2459,5.5483c-1.1829,1.7369-2.3662,3.2784-3.2541,4.3859-.4438.5536-.8135.9984-1.0721,1.3046-.0844.1-.157.1852-.2164.2545-.06-.07-.1325-.1564-.2173-.2578-.2587-.3088-.6284-.7571-1.0723-1.3147-.8879-1.1154-2.0714-2.6664-3.2543-4.41a42.2677,42.2677,0,0,1-3.2463-5.5535A12.978,12.978,0,0,1,.75,10.4,9.4659,9.4659,0,0,1,10,.75,9.4659,9.4659,0,0,1,19.25,10.4Z"/>
<path id="circle" fill="#fff" stroke="#ba7334" stroke-width="0.5" d="M13.55,10A3.55,3.55,0,1,1,10,6.45,3.5484,3.5484,0,0,1,13.55,10Z"/>
</g>
</g>
<rect width="20" height="48" fill="none"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,16 +0,0 @@
<!-- Create a custom map style: https://studio.mapbox.com -->
<svg id="marker" data-name="marker" xmlns="http://www.w3.org/2000/svg" width="20" height="48" viewBox="0 0 20 48">
<g id="mapbox-marker-icon">
<g id="icon">
<ellipse id="shadow" cx="10" cy="27" rx="9" ry="5" fill="#c4c4c4" opacity="0.3" style="isolation: isolate"/>
<g id="mask" opacity="0.3">
<g id="group">
<path id="shadow-2" data-name="shadow" fill="#bfbfbf" d="M10,32c5,0,9-2.2,9-5s-4-5-9-5-9,2.2-9,5S5,32,10,32Z" fill-rule="evenodd"/>
</g>
</g>
<path id="color" fill="#ee4e8b" stroke="#b43b71" stroke-width="0.5" d="M19.25,10.4a13.0663,13.0663,0,0,1-1.4607,5.2235,41.5281,41.5281,0,0,1-3.2459,5.5483c-1.1829,1.7369-2.3662,3.2784-3.2541,4.3859-.4438.5536-.8135.9984-1.0721,1.3046-.0844.1-.157.1852-.2164.2545-.06-.07-.1325-.1564-.2173-.2578-.2587-.3088-.6284-.7571-1.0723-1.3147-.8879-1.1154-2.0714-2.6664-3.2543-4.41a42.2677,42.2677,0,0,1-3.2463-5.5535A12.978,12.978,0,0,1,.75,10.4,9.4659,9.4659,0,0,1,10,.75,9.4659,9.4659,0,0,1,19.25,10.4Z"/>
<path id="circle" fill="#fff" stroke="#b43b71" stroke-width="0.5" d="M13.55,10A3.55,3.55,0,1,1,10,6.45,3.5484,3.5484,0,0,1,13.55,10Z"/>
</g>
</g>
<rect width="20" height="48" fill="none"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,16 +0,0 @@
<!-- Create a custom map style: https://studio.mapbox.com -->
<svg id="marker" data-name="marker" xmlns="http://www.w3.org/2000/svg" width="20" height="48" viewBox="0 0 20 48">
<g id="mapbox-marker-icon">
<g id="icon">
<ellipse id="shadow" cx="10" cy="27" rx="9" ry="5" fill="#c4c4c4" opacity="0.3" style="isolation: isolate"/>
<g id="mask" opacity="0.3">
<g id="group">
<path id="shadow-2" data-name="shadow" fill="#bfbfbf" d="M10,32c5,0,9-2.2,9-5s-4-5-9-5-9,2.2-9,5S5,32,10,32Z" fill-rule="evenodd"/>
</g>
</g>
<path id="color" fill="#7753eb" stroke="#5a3fc0" stroke-width="0.5" d="M19.25,10.4a13.0663,13.0663,0,0,1-1.4607,5.2235,41.5281,41.5281,0,0,1-3.2459,5.5483c-1.1829,1.7369-2.3662,3.2784-3.2541,4.3859-.4438.5536-.8135.9984-1.0721,1.3046-.0844.1-.157.1852-.2164.2545-.06-.07-.1325-.1564-.2173-.2578-.2587-.3088-.6284-.7571-1.0723-1.3147-.8879-1.1154-2.0714-2.6664-3.2543-4.41a42.2677,42.2677,0,0,1-3.2463-5.5535A12.978,12.978,0,0,1,.75,10.4,9.4659,9.4659,0,0,1,10,.75,9.4659,9.4659,0,0,1,19.25,10.4Z"/>
<path id="circle" fill="#fff" stroke="#5a3fc0" stroke-width="0.5" d="M13.55,10A3.55,3.55,0,1,1,10,6.45,3.5484,3.5484,0,0,1,13.55,10Z"/>
</g>
</g>
<rect width="20" height="48" fill="none"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,16 +0,0 @@
<!-- Create a custom map style: https://studio.mapbox.com -->
<svg id="marker" data-name="marker" xmlns="http://www.w3.org/2000/svg" width="20" height="48" viewBox="0 0 20 48">
<g id="mapbox-marker-icon">
<g id="icon">
<ellipse id="shadow" cx="10" cy="27" rx="9" ry="5" fill="#c4c4c4" opacity="0.3" style="isolation: isolate"/>
<g id="mask" opacity="0.3">
<g id="group">
<path id="shadow-2" data-name="shadow" fill="#bfbfbf" d="M10,32c5,0,9-2.2,9-5s-4-5-9-5-9,2.2-9,5S5,32,10,32Z" fill-rule="evenodd"/>
</g>
</g>
<path id="color" fill="#f84d4d" stroke="#951212" stroke-width="0.5" d="M19.25,10.4a13.0663,13.0663,0,0,1-1.4607,5.2235,41.5281,41.5281,0,0,1-3.2459,5.5483c-1.1829,1.7369-2.3662,3.2784-3.2541,4.3859-.4438.5536-.8135.9984-1.0721,1.3046-.0844.1-.157.1852-.2164.2545-.06-.07-.1325-.1564-.2173-.2578-.2587-.3088-.6284-.7571-1.0723-1.3147-.8879-1.1154-2.0714-2.6664-3.2543-4.41a42.2677,42.2677,0,0,1-3.2463-5.5535A12.978,12.978,0,0,1,.75,10.4,9.4659,9.4659,0,0,1,10,.75,9.4659,9.4659,0,0,1,19.25,10.4Z"/>
<path id="circle" fill="#fff" stroke="#951212" stroke-width="0.5" d="M13.55,10A3.55,3.55,0,1,1,10,6.45,3.5484,3.5484,0,0,1,13.55,10Z"/>
</g>
</g>
<rect width="20" height="48" fill="none"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,16 +0,0 @@
<!-- Create a custom map style: https://studio.mapbox.com -->
<svg id="marker" data-name="marker" xmlns="http://www.w3.org/2000/svg" width="20" height="48" viewBox="0 0 20 48">
<g id="mapbox-marker-icon">
<g id="icon">
<ellipse id="shadow" cx="10" cy="27" rx="9" ry="5" fill="#c4c4c4" opacity="0.3" style="isolation: isolate"/>
<g id="mask" opacity="0.3">
<g id="group">
<path id="shadow-2" data-name="shadow" fill="#bfbfbf" d="M10,32c5,0,9-2.2,9-5s-4-5-9-5-9,2.2-9,5S5,32,10,32Z" fill-rule="evenodd"/>
</g>
</g>
<path id="color" fill="#d9d838" stroke="#a4a62d" stroke-width="0.5" d="M19.25,10.4a13.0663,13.0663,0,0,1-1.4607,5.2235,41.5281,41.5281,0,0,1-3.2459,5.5483c-1.1829,1.7369-2.3662,3.2784-3.2541,4.3859-.4438.5536-.8135.9984-1.0721,1.3046-.0844.1-.157.1852-.2164.2545-.06-.07-.1325-.1564-.2173-.2578-.2587-.3088-.6284-.7571-1.0723-1.3147-.8879-1.1154-2.0714-2.6664-3.2543-4.41a42.2677,42.2677,0,0,1-3.2463-5.5535A12.978,12.978,0,0,1,.75,10.4,9.4659,9.4659,0,0,1,10,.75,9.4659,9.4659,0,0,1,19.25,10.4Z"/>
<path id="circle" fill="#fff" stroke="#a4a62d" stroke-width="0.5" d="M13.55,10A3.55,3.55,0,1,1,10,6.45,3.5484,3.5484,0,0,1,13.55,10Z"/>
</g>
</g>
<rect width="20" height="48" fill="none"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,3 +1,4 @@
// Global mock for matchMedia (used by touchDevice mixin, drag detection, etc.)
import Vue from 'vue'
import { createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
@ -6,6 +7,14 @@ import Filters from '~/plugins/vue-filters'
import InfiniteLoading from '~/plugins/vue-infinite-loading'
import Directives from '~/plugins/vue-directives'
import VueObserveVisibility from '~/plugins/vue-observe-visibility'
window.matchMedia =
window.matchMedia ||
jest.fn().mockImplementation((query) => ({
matches: query === '(pointer: fine)' || query === '(min-width: 640px)',
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
}))
require('intersection-observer')
// Fail tests on Vue warnings