Skip to content
Merged
86 changes: 86 additions & 0 deletions cypress/component/FlatmapServerTest.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import MultiFlatmapVuer from '../../src/components/MultiFlatmapVuer.vue';
const FLATMAP_API = 'https://mapcore-demo.org/current/flatmap/v3/';

describe('MultiFlatmapVuer Error Handling', () => {

it('should handle 500 Internal Server Error', () => {
cy.intercept('GET', FLATMAP_API, {
statusCode: 500,
body: 'Internal Server Error',
}).as('serverError');

cy.mount(MultiFlatmapVuer, {
props: {
flatmapAPI: FLATMAP_API,
initial: 'Rat',
availableSpecies: {
Rat: {
taxo: 'NCBITaxon:10114',
iconClass: 'mapicon-icon_rat',
},
},
},
});

cy.wait('@serverError');

cy.get('.flatmap-error').should('exist');
cy.contains('MultiFlatmap Error!').should('be.visible');
cy.contains('unexpected error').should('be.visible');
cy.contains('try again later').should('be.visible');
});

it('handles 500 with valid JSON body (would bypass catch)', () => {
cy.intercept('GET', '**/flatmap/v3/', {
statusCode: 500,
headers: { 'content-type': 'application/json' },
body: [], // valid JSON array -> response.json() resolves
}).as('serverErrorJson');

cy.mount(MultiFlatmapVuer, {
props: {
flatmapAPI: FLATMAP_API,
initial: 'Rat',
availableSpecies: { Rat: { taxo: 'NCBITaxon:10114' } },
},
});

cy.wait('@serverErrorJson');
cy.get('.flatmap-error').should('exist');
});

it('should handle 404 endpoint error', () => {
cy.intercept('GET', FLATMAP_API, {
statusCode: 404,
body: { status_code: 404 },
}).as('notFoundError');

cy.mount(MultiFlatmapVuer, {
props: {
flatmapAPI: FLATMAP_API,
initial: 'Rat',
},
});

cy.wait('@notFoundError');

cy.contains('flatmap API endpoint is incorrect').should('be.visible');
});

it('should handle network failure', () => {
cy.intercept('GET', FLATMAP_API, {
forceNetworkError: true,
}).as('networkError');

cy.mount(MultiFlatmapVuer, {
props: {
flatmapAPI: FLATMAP_API,
initial: 'Rat',
},
});

cy.wait('@networkError');

cy.get('.flatmap-error').should('exist');
});
});
2 changes: 2 additions & 0 deletions src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
DynamicLegends: typeof import('./components/legends/DynamicLegends.vue')['default']
ElAutocomplete: typeof import('element-plus/es')['ElAutocomplete']
ElButton: typeof import('element-plus/es')['ElButton']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
Expand All @@ -24,6 +25,7 @@ declare module 'vue' {
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRow: typeof import('element-plus/es')['ElRow']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
FlatmapError: typeof import('./components/FlatmapError.vue')['default']
FlatmapVuer: typeof import('./components/FlatmapVuer.vue')['default']
LegendItem: typeof import('./components/legends/LegendItem.vue')['default']
Expand Down
40 changes: 37 additions & 3 deletions src/components/MultiFlatmapVuer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,35 @@ export default {
if (this.requireInitialisation) {
//It has not been initialised yet
this.requireInitialisation = false
fetch(this.flatmapAPI)
.then((response) => response.json())
const controller = new AbortController();
const signal = controller.signal;
const timeoutId = setTimeout(() => controller.abort(), 5000);

fetch(this.flatmapAPI, {signal})
.then((response) => {
if (!response.ok) {
// HTTP-level errors
if (response.status === 404) {
this.multiflatmapError = {};
this.multiflatmapError['title'] = 'MultiFlatmap Error!';
this.multiflatmapError['messages'] = [
`Sorry, the component could not be loaded because the specified
flatmap API endpoint is incorrect. Please check the endpoint URL
or contact support if the problem persists.`
];
this.initialised = true;
resolve();
this.resolveList.forEach((other) => other());

return Promise.reject({ handled: true });
}
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json()
})
.then((data) => {
if (data.status_code === 404) {
// Application-level 404 in a 200 response
if (data && data.status_code === 404) {
console.error('Flatmap API endpoint is incorrect', data);
this.multiflatmapError = {};
this.multiflatmapError['title'] = 'MultiFlatmap Error!';
Expand All @@ -178,6 +203,11 @@ export default {
flatmap API endpoint is incorrect. Please check the endpoint URL
or contact support if the problem persists.`
];

this.initialised = true;
resolve();
this.resolveList.forEach((other) => other());
return;
}
//Check each key in the provided availableSpecies against the one
Object.keys(this.availableSpecies).forEach((key) => {
Expand Down Expand Up @@ -237,6 +267,7 @@ export default {
})
})
.catch((error) => {
if (error && error.handled) return;
console.error('Error fetching flatmap:', error)
this.initialised = true;
this.multiflatmapError = {};
Expand All @@ -251,6 +282,9 @@ export default {
other()
})
})
.finally(() => {
clearTimeout(timeoutId);
});
} else if (this.initialised) {
//resolve as it has been initialised
resolve()
Expand Down