Skip to content

Commit

Permalink
Merge pull request #1187 from geoadmin/feat-pb-1187-geolocation-test
Browse files Browse the repository at this point in the history
PB-1187: Add more geolocation test
  • Loading branch information
ismailsunni authored Jan 9, 2025
2 parents 15b3f9a + b346eaa commit 0a3b72e
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 79 deletions.
2 changes: 1 addition & 1 deletion src/modules/i18n/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const defaultLocal =
?.find((navigatorLang) => SUPPORTED_LANG.find((lang) => navigatorLang.startsWith(lang)))
?.split('-')[0] ?? SUPPORTED_LANG[0]
log.info(
`Default local set to ${defaultLocal}, navigator langagues`,
`Default local set to ${defaultLocal}, navigator languages`,
navigator.languages,
`supported language`,
SUPPORTED_LANG
Expand Down
61 changes: 27 additions & 34 deletions src/store/plugins/geolocation-management.plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { LV95, WGS84 } from '@/utils/coordinates/coordinateSystems'
import ErrorMessage from '@/utils/ErrorMessage.class'
import log from '@/utils/logging'
import { round } from '@/utils/numberUtils'
const { GeolocationPositionError } = window

const dispatcher = { dispatcher: 'geolocation-management.plugin' }

Expand Down Expand Up @@ -91,55 +92,47 @@ const handlePositionError = (error, store, state, options = {}) => {
const { reactivate = false } = options
log.error('Geolocation activation failed', error)
switch (error.code) {
case error.PERMISSION_DENIED:
case GeolocationPositionError.PERMISSION_DENIED:
store.dispatch('setGeolocationDenied', {
denied: true,
...dispatcher,
})
store.dispatch('addErrors', {
errors: [new ErrorMessage('geoloc_permission_denied', null)],
errors: [new ErrorMessage('geoloc_permission_denied')],
...dispatcher,
})
break
case error.TIMEOUT:
case GeolocationPositionError.TIMEOUT:
store.dispatch('setGeolocation', { active: false, ...dispatcher })
store.dispatch('addErrors', {
errors: [new ErrorMessage('geoloc_time_out', null)],
errors: [new ErrorMessage('geoloc_time_out')],
...dispatcher,
})
break
default:
if (IS_TESTING_WITH_CYPRESS && error.code === error.POSITION_UNAVAILABLE) {
// edge case for e2e testing, if we are testing with Cypress and we receive a POSITION_UNAVAILABLE
// we don't raise an error as it's "normal" in Electron to have this error raised (this API doesn't work
// on Electron embedded in Cypress : no Geolocation hardware detected, etc...)
// the position will be returned by a mocked up function by Cypress we can ignore this error
// we do nothing...
// It can happen that the position is not yet available so we retry the api call silently for the first
// 3 call
errorCount += IS_TESTING_WITH_CYPRESS ? 3 : 1
if (errorCount < 3) {
if (reactivate) {
activeGeolocation(store, state, { useInitial: false })
}
} else {
// It can happen that the position is not yet available so we retry the api call silently for the first
// 3 call
errorCount += 1
if (errorCount < 3) {
if (reactivate) {
activeGeolocation(store, state, { useInitial: false })
}
} else {
store.dispatch('addErrors', {
errors: [new ErrorMessage('geoloc_unknown', null)],
...dispatcher,
})
if (reactivate) {
// If after 3 retries we failed to re-activate, set the geolocation to false
// so that the user can manually retry the geolocation later on. This can
// mean that the device don't support geolocation so it doesn't make sense
// to retry for ever.
// In the case where we are in the watcher, this means that we had at least
// one successful location and that geolocation is supported by the device.
// So we let the watcher continue has he might recover itself later on, if
// not the error will kept showing and the user will have to manually stop
// geolocation.
store.dispatch('setGeolocation', { active: false, ...dispatcher })
}
store.dispatch('addErrors', {
errors: [new ErrorMessage('geoloc_unknown')],
...dispatcher,
})
if (reactivate) {
// If after 3 retries we failed to re-activate, set the geolocation to false
// so that the user can manually retry the geolocation later on. This can
// mean that the device don't support geolocation so it doesn't make sense
// to retry for ever.
// In the case where we are in the watcher, this means that we had at least
// one successful location and that geolocation is supported by the device.
// So we let the watcher continue has he might recover itself later on, if
// not the error will kept showing and the user will have to manually stop
// geolocation.
store.dispatch('setGeolocation', { active: false, ...dispatcher })
}
}
}
Expand Down
7 changes: 3 additions & 4 deletions src/utils/components/FeedbackPopup.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,15 @@ const warning = computed(() => {
flex-direction: column;
@include respond-below(phone) {
$top-margin: $header-height;
&.dev-disclaimer-present {
$top-margin: calc($header-height + $dev-disclaimer-height + 1rem);
}
top: $top-margin;
left: 50%;
right: unset;
transform: translate(-50%, 0%);
max-height: calc(100vh - $top-margin);
max-width: 100vw;
&.dev-disclaimer-present {
$top-margin: calc($header-height + $dev-disclaimer-height);
}
}
}
</style>
34 changes: 27 additions & 7 deletions tests/cypress/support/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -300,14 +300,34 @@ export function getDefaultFixturesAndIntercepts() {
/**
* Geolocation mockup
*
* @param {Cypress.AUTWindow} win A reference to the window object.
* @param {GeolocationCoordinates} coords The fake coordinates to pass along.
* @param {Cypress.AUTWindow} win - A reference to the window object.
* @param {Object} options - Configuration object for the mock geolocation.
* @param {number} [options.latitude=47] - The latitude to use for the mock position. Default is
* `47`
* @param {number} [options.longitude=7] - The longitude to use for the mock position. Default is
* `7`
* @param {number} [options.errorCode=null] - The error code to simulate, if any. Default is `null`
* @see https://github.com/cypress-io/cypress/issues/2671
*/
const mockGeolocation = (win, coords) => {
const handler = (callback) => callback({ coords })
cy.stub(win.navigator.geolocation, 'getCurrentPosition').callsFake(handler)
cy.stub(win.navigator.geolocation, 'watchPosition').callsFake(handler)
const mockGeolocation = (win, options) => {
const { latitude = 47, longitude = 7, errorCode = null } = options

if (errorCode) {
const error = { code: errorCode, message: 'Error' }

cy.stub(win.navigator.geolocation, 'getCurrentPosition').callsFake((_, errorCallback) => {
errorCallback(error)
})

cy.stub(win.navigator.geolocation, 'watchPosition').callsFake((_, errorCallback) => {
errorCallback(error)
})
} else {
const coords = { latitude, longitude }
const handler = (callback) => callback({ coords })
cy.stub(win.navigator.geolocation, 'getCurrentPosition').callsFake(handler)
cy.stub(win.navigator.geolocation, 'watchPosition').callsFake(handler)
}
}

/**
Expand All @@ -325,7 +345,7 @@ Cypress.Commands.add(
(
queryParams = {},
withHash = true,
geolocationMockupOptions = { latitude: 47, longitude: 7 },
geolocationMockupOptions = { latitude: 47, longitude: 7, errorCode: null },
fixturesAndIntercepts = {}
) => {
// Intercepts passed as parameters to "fixturesAndIntercepts" will overwrite the correspondent
Expand Down
132 changes: 99 additions & 33 deletions tests/cypress/tests-e2e/geolocation.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,30 @@
import proj4 from 'proj4'

import { DEFAULT_PROJECTION } from '@/config/map.config'
import { SWISS_ZOOM_LEVEL_1_25000_MAP } from '@/utils/coordinates/CoordinateSystem.class'
import { WGS84 } from '@/utils/coordinates/coordinateSystems'
const { GeolocationPositionError } = window

const geolocationButtonSelector = '[data-cy="geolocation-button"]'

function getGeolocationButtonAndClickIt() {
cy.get(geolocationButtonSelector).should('be.visible').click()
}

function testErrorMessage(message) {
// Check error in store
cy.readStoreValue('state.ui.errors').then((errors) => {
expect(errors).to.be.an('Set')
expect(errors.size).to.eq(1)

const error = errors.values().next().value
expect(error.msg).to.eq(message)
})
// Check error in UI
cy.get('[data-cy="error-window"]').should('be.visible')
cy.get('[data-cy="error-window-close"]').should('be.visible').click() // close the error window
}

// PB-701: TODO Those tests below are not working as expected, as the cypress-browser-permissions is not
// working and the geolocation is always allowed, this needs to be reworked and probably need to
// use another plugin.
Expand Down Expand Up @@ -49,54 +65,104 @@ describe('Geolocation cypress', () => {
},
},
() => {
// lon/lat to mock up the Geolocation API (see beforeEach)
const latitude = 47.5
const longitude = 6.8
// same position but in EPSG:2056 (default projection of the app)
const [x, y] = proj4(WGS84.epsg, DEFAULT_PROJECTION.epsg, [longitude, latitude])

beforeEach(() => {
cy.goToMapView({}, true, { latitude, longitude })
getGeolocationButtonAndClickIt()
})

it("Doesn't prompt the user if geolocation has previously been authorized", () => {
cy.goToMapView({}, true)
getGeolocationButtonAndClickIt()
cy.on('window:alert', () => {
throw new Error('Should not prompt for geolocation API permission again')
})
cy.readStoreValue('state.geolocation.active').should('be.true')
})

it('Uses the values given by the Geolocation API to feed the store', () => {
it('Uses the values given by the Geolocation API to feed the store and position the map to the new position and zoom level', () => {
const startingLatitude = 47
const startingLongitude = 7.5
const startingZoom = 12
// same position but in EPSG:2056 (default projection of the app)
const [x0, y0] = proj4(WGS84.epsg, DEFAULT_PROJECTION.epsg, [
startingLongitude,
startingLatitude,
])

const geoLatitude = 47.5
const geoLongitude = 6.8
// same position but in EPSG:2056 (default projection of the app)
const [geoX, geoY] = proj4(WGS84.epsg, DEFAULT_PROJECTION.epsg, [
geoLongitude,
geoLatitude,
])

cy.goToMapView(
{
center: proj4(WGS84.epsg, DEFAULT_PROJECTION.epsg, [
startingLongitude,
startingLatitude,
]).join(','),
z: startingZoom,
},
true,
{ latitude: geoLatitude, longitude: geoLongitude }
)

// check initial center and zoom
cy.readStoreValue('state.position.center').then((center) => {
expect(center).to.be.an('Array')
expect(center.length).to.eq(2)
expect(center[0]).to.approximately(x0, 0.1)
expect(center[1]).to.approximately(y0, 0.1)
})
cy.readStoreValue('state.position.zoom').then((zoom) => {
expect(zoom).to.eq(startingZoom)
})

getGeolocationButtonAndClickIt()
cy.readStoreValue('state.geolocation.position').then((position) => {
expect(position).to.be.an('Array')
expect(position.length).to.eq(2)
expect(position[0]).to.approximately(x, 0.1)
expect(position[1]).to.approximately(y, 0.1)
expect(position[0]).to.approximately(geoX, 0.1)
expect(position[1]).to.approximately(geoY, 0.1)
})
// check that the map has been centered on the geolocation and zoom is updated
cy.readStoreValue('state.position.center').then((center) => {
expect(center).to.be.an('Array')
expect(center.length).to.eq(2)
expect(center[0]).to.approximately(geoX, 0.1)
expect(center[1]).to.approximately(geoY, 0.1)
})
cy.readStoreValue('state.position.zoom').then((zoom) => {
expect(zoom).to.eq(SWISS_ZOOM_LEVEL_1_25000_MAP)
})
})
}
)
it('access from outside Switzerland shows an error message', () => {
// null island
cy.goToMapView({}, true, { latitude: 0, longitude: 0 })
getGeolocationButtonAndClickIt()
testErrorMessage('geoloc_out_of_bounds')

context(
'Test geolocation when geolocation is unauthorized',
{
env: {
browserPermissions: {
geolocation: 'block',
},
},
},
() => {
it('shows an alert telling the user geolocation is unauthorized when the geolocation button is clicked', () => {
cy.goToMapView()
// Java island
cy.goToMapView({}, true, { latitude: -7.71, longitude: 110.37 })
getGeolocationButtonAndClickIt()
cy.on('window:alert', (txt) => {
expect(txt).to.contains(
'The acquisition of the position failed because your browser settings does not allow it. Allow your browser /this website to use your location. Deactivate the "private" mode of your browser'
)
})
testErrorMessage('geoloc_out_of_bounds')
})
}
)

context('Test geolocation when geolocation is failed to be retrieved', () => {
it('shows an error telling the user geolocation is denied', () => {
cy.goToMapView({}, true, { errorCode: GeolocationPositionError.PERMISSION_DENIED })
getGeolocationButtonAndClickIt()
testErrorMessage('geoloc_permission_denied')
})

it('shows an alert telling the user geolocation is not able to be retrieved due to time out', () => {
cy.goToMapView({}, true, { errorCode: GeolocationPositionError.TIMEOUT })
getGeolocationButtonAndClickIt()
testErrorMessage('geoloc_time_out')
})
it('shows an alert telling the user geolocation is not available for other reason', () => {
cy.goToMapView({}, true, { errorCode: GeolocationPositionError.POSITION_UNAVAILABLE })
getGeolocationButtonAndClickIt()
testErrorMessage('geoloc_unknown')
})
})
})

0 comments on commit 0a3b72e

Please sign in to comment.