TypeError: _mapboxGl.default.Map is not a constructor
Asked Answered
D

2

6

Trying to write a unit test for react.js component. The component implements a map rendered with mapbox API. But unfortunately i bumped into a series of problems:

The firs one was: TypeError: window.URL.createObjectURL is not a function enter image description here

I have solved this thanks to this: https://github.com/mapbox/mapbox-gl-js/issues/3436, by adding this code:

jest.mock('mapbox-gl/dist/mapbox-gl', () => ({
    Map: () => ({})
}))

Then the secound one was: ReferenceError: shallow is not defined enter image description here

To solve this problem in the base of this: ReferenceError on enzyme import,

1) npm istall enzyme,

2) add the line:

import { shallow, render, mount } from 'enzyme'

The third problem was: import Adapter from 'enzyme-adapter-react-15' enter image description here

Thanks to this article: Could not find declaration file for enzyme-adapter-react-16?, the next step was adding this code:

import Adapter from 'enzyme-adapter-react-16'
import enzyme from 'enzyme'

enzyme.configure({ adapter: new Adapter() })

And now finally a have: TypeError: _mapboxGl.default.Map is not a constructor enter image description here

And now, unfortunately, I couldn't find a meaningful solution on the web.

Did anyone have a similar problem?

Why unit testing the mapbox API is so hard?

Maybe i'm doing it completely wrong and the whole solution is to the trash? if so, can anyone suggest an alternative?

Below the whole test code:

import React, { Component } from 'react'
import { shallow, render, mount } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import enzyme from 'enzyme'

enzyme.configure({ adapter: new Adapter() })

import Map from '../Map'

jest.mock('mapbox-gl/dist/mapbox-gl', () => ({
    Map: () => ({})
}))

describe('<Map />', ()=>{

    let mapWrapper
    let mapInstance

    const map = (disableLifecycleMethods = false)=>shallow(<Map />,{disableLifecycleMethods})

    beforeEach(()=>{
        mapWrapper = map()
        mapInstance = mapWrapper.instance()
    })

    afterEach(() => {
        mapWrapper = undefined;
        mapInstance = undefined;
    })

    it('renders without crashing', () => {
        expect(map().exists()).toBe(true);
    })
})

Below the tested component code:

import React, { Component } from 'react'
import mapboxgl from 'mapbox-gl'

//Mechanics
import {importContours} from './utilities/importContours'
import {addData} from './utilities/addData'
import {setLegend} from './utilities/setLegend'

//Components
import Searchbar from '../search/Searchbar'
import Tabbar from '../tabbar/Tabbar'
import Legend from '../legend/Legend'
//import Popup from '../popup/Popup'

class Map extends Component {

    map

    constructor(){
        super()
        this.state = {
            active: null,
            fetchData: null,
            mapType: 0,
            searchedPhrase: ''
        }
    }

    componentDidUpdate() {
        this.setMapLayer()          
    }

    componentDidMount() {

        mapboxgl.accessToken = process.env.REACT_APP_MAPBOX_TOKEN
        this.map = new mapboxgl.Map({
            container: 'Map',
            style: 'mapbox://styles/mapbox/streets-v9',
            center: [16.145136, 51.919437],
            maxZoom: 13,
            minZoom: 3,
            zoom: 5.7,
        })

        this.map.once('load', () => {}) 
    }

    setMapLayer(){

        if (!this.map.loaded() || this.state.searchedPhrase === '') return

        var contours = importContours(this.state.mapType)
        var contoursWithData = addData(contours, this.state.mapType, this.state.searchedPhrase)
        contoursWithData.then((data)=>{
            var mpSource = this.map.getSource("contours")

            if (typeof mpSource === 'undefined') 
                this.map.addSource('contours', { type: 'geojson', data })
            else 
                this.map.getSource("contours").setData(data)

            var mpLayer = this.map.getLayer("contours")

            if (typeof mpLayer === 'undefined') {
                this.map.addLayer({
                    id: 'contours',
                    type: 'fill',
                    source: 'contours',
                    layout: {},
                    paint: {
                        'fill-opacity': [
                            'case',
                            ['boolean', ['feature-state', 'hover'], false],
                            0.8,
                            0.4
                        ]
                    }
                }, 'country-label-lg')

                this.map.addLayer({
                    id: 'state-borders',
                    type: 'line',
                    source: 'contours',
                    layout: {},
                    paint: {
                        'line-color': '#c44cc0',
                        'line-width': 0.01
                    }
                })
            }

            var hoveredStateId = null

            // When the user moves their mouse over the state-fill layer, we'll update the
            // feature state for the feature under the mouse.
            this.map.on('mousemove', 'contours', (e) => {
                if (e.features.length > 0) {
                    if (hoveredStateId) {
                        this.map.setFeatureState(
                            { source: 'contours', id: hoveredStateId },
                            { hover: false }
                        )
                    }

                    hoveredStateId = e.features[0].id
                    this.map.setFeatureState(
                        { source: 'contours', id: hoveredStateId },
                        { hover: true }
                    )
                }
            })

            // When the mouse leaves the state-fill layer, update the feature state of the
            // previously hovered feature.
            this.map.on('mouseleave', 'contours', () => {
                if (hoveredStateId) {
                    this.map.setFeatureState(
                        { source: 'contours', id: hoveredStateId },
                        { hover: false }
                    )
                }
                hoveredStateId = null
            }) 

            // When the user click their mouse over the layer, we'll update the
            this.map.on('click', 'contours', (e) => {

                var popupHTML = `<Popover 
                    style = { zIndex: 2, position: 'absolute' }
                    anchorOrigin={{ vertical: 'center',horizontal: 'center'}}
                    transformOrigin={{vertical: 'center',horizontal: 'center'}}
                >
                    ${e.features[0].id}
                </Popover>`

                if (e.features.length > 0) {
                    new mapboxgl.Popup(
                        {style:"zIndex: 2"},
                        {closeButton: false, closeOnClick: true}
                        )
                    .setLngLat(e.lngLat)
                    .setHTML(popupHTML)
                    .addTo(this.map);
                }
            })

            this.setState({
                active: setLegend(data)
            })

            //Set fill
            if(this.state.active == null) return 

            const { property, stops } = this.state.active

            this.map.setPaintProperty('contours', 'fill-color', {
              property,
              stops
            })
        })
    }

    handleChange = (newMapType) => {

        if (this.state.mapType === newMapType) return

        const { searchedPhrase } = this.state

        if (typeof searchedPhrase === 'undefined')return

        this.setState({mapType:newMapType})
    }

    handleSearch = (newSearchPhrase) => {

        if (typeof newSearchPhrase === 'undefined') return

        this.setState({searchedPhrase:newSearchPhrase.toUpperCase()})    
    }

    render(){
        return (
            <div id="Map">
                <Searchbar click={this.handleSearch.bind(this)}/>
                <Tabbar click={this.handleChange.bind(this)}/>
                <Legend active={this.state.active}/>
            </div>
        )
    }
}

export default Map
Dyspeptic answered 31/12, 2019 at 10:28 Comment(0)
C
2

Your can add the code below to your test entry file for me it was src/setupTests.ts

jest.mock('mapbox-gl/dist/mapbox-gl', () => ({
  GeolocateControl: jest.fn(),
  Map: jest.fn(() => ({
    addControl: jest.fn(),
    on: jest.fn(),
    remove: jest.fn(),
  })),
  NavigationControl: jest.fn(),
}));
Carbide answered 1/7, 2020 at 16:47 Comment(0)
E
0

Thanks to Morlo's answer, I succeeded in fixing directly in my test file:

import Map from '@/components/modules/Home/Map/Map'

jest.mock('mapbox-gl/dist/mapbox-gl', () => ({
  Map: jest.fn(),
  Marker: jest.fn().mockReturnValue({
    setLngLat: jest.fn().mockReturnValue({
      setPopup: jest.fn().mockReturnValue({
        addTo: jest.fn().mockReturnValue({})
      })
    })
  }),
  Popup: jest.fn().mockReturnValue({
    setHTML: jest.fn().mockReturnValue({ on: jest.fn() })
  })
}))

describe('Map', () => {
  it('should match snapshot', () => {
    // When
    const wrapper = shallowMount(Map)

    // Then
    expect(wrapper).toMatchSnapshot()
  })
})
Extradite answered 26/12, 2020 at 15:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.