Szelek
Szelek

Reputation: 2837

TypeError: _mapboxGl.default.Map is not a constructor

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

Upvotes: 6

Views: 3876

Answers (2)

user13333032
user13333032

Reputation:

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()
  })
})

Upvotes: 0

Morlo Mbakop
Morlo Mbakop

Reputation: 3756

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(),
}));

Upvotes: 2

Related Questions