Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React testing with Jest and Enzyme @react-google-maps/api returns TypeError: Cannot read property 'maps' of undefined

I am trying to test a component with @react-google-maps/api package. I am getting error: TypeError: Cannot read property 'maps' of undefined.

My component:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchPersonId, updatePersonSettings, pushPersonMessage } from '../../actions/person';
import TemplatePage from '../templates/TemplatePage';
import Card from '../partials/Card';
import Msg from '../partials/Msg';
import { GoogleMap, Marker } from '@react-google-maps/api';
import markerPosition from'../../img/marker-position.png';
import PlacesAutocomplete, { geocodeByAddress, getLatLng } from 'react-places-autocomplete';
import PropTypes from 'prop-types';
import { DEFAULT_LAT, DEFAULT_LNG, DEFAULT_ZOOM } from '../../config/';


export class Settings extends Component {

  state = {
    lat: DEFAULT_LAT,
    lng: DEFAULT_LNG,
    zoom: DEFAULT_ZOOM,
    address: '',
    formSubmitted: false
  }

  componentDidMount () {
    const { lat, lng, zoom } = this.props.auth;
    this.setState({
      lat: lat !== undefined && lat !== null ? lat : DEFAULT_LAT,
      lng: lng !== undefined && lng !== null ? lng : DEFAULT_LNG,
      zoom: zoom !== undefined && zoom !== null ? zoom : DEFAULT_ZOOM
    });
    this.drawMarker();
  }

  handleOnSubmit = e => {
    e.preventDefault();
    const settings = {
      zoom: this.state.zoom,
      lat: this.state.lat,
      lng: this.state.lng
    }
    this.props.updatePersonSettings({ id: this.props.auth.person_id, settings })
  }

  handleChangeZoom = event => {
    this.setState({ zoom: parseInt(event.target.value )});
  }

  handleChangeAddress = (address) => {
    this.setState({ address });
  }

  handleSelect = (address) => {
    geocodeByAddress(address)
      .then(results => 
          getLatLng(results[0])
          .then(function(result) {
            this.setState({
              lat: result.lat,
              lng: result.lng,
            })
            this.drawMarker()
        }.bind(this))
      )
      .catch(error => console.error('Error', error));
  };

  handleMapClick = e => {
    this.setState({
      lat: e.latLng.lat(),
      lng: e.latLng.lng(),
    });
    this.drawMarker();
  }

  handleMapZoom = (zoom) => {
    console.log(zoom)
  }

  drawMarker = () => {
      return <Marker
      position={{
        lat: parseFloat(this.state.lat),
        lng: parseFloat(this.state.lng)
      }}
      icon={
        new window.google.maps.MarkerImage(
          markerPosition,
          null, /* size is determined at runtime */
          null, /* origin is 0,0 */
          null, /* anchor is bottom center of the scaled image */
          new window.google.maps.Size(48, 48)
        )
      }
    >
    </Marker>
  }

  get msg() {
    if(this.props.person !== '') {
      return <Msg msg={this.props.person} />
    }
    return null;
  }

  render() {

    const { status } = this.props.person;
    const { lat, lng, zoom, address, formSubmitted } = this.state;

    return (
      <TemplatePage>

        { this.msg }
        <Card title='Settings' padding='large'>

          <form className="form" onSubmit={this.handleOnSubmit}>
            <div className="form-group">
              <label htmlFor="position">Default map position</label>
              <div className="google-map google-map__settings">

                  <GoogleMap
                    center={{ lat, lng }}
                    zoom={ zoom }
                    onClick={ e => this.handleMapClick(e) }
                    onZoomChanged={(e) => {
                      console.log('zoom changed')
                    }}
                  >
                    {this.drawMarker()}
                    <div className="map-constraints-container" />
                  </GoogleMap>
              </div>
            </div>
            <div className="form-group">
              <div className="map-constraints-slider"> 
                <label htmlFor="range">Default map zoom: {zoom}</label>
                <input 
                  type="range" 
                  id="zoom" 
                  value={ zoom } 
                  name="zoom" 
                  min="1" 
                  max="18" 
                  onChange={ this.handleChangeZoom }
                />
              </div>
            </div>

            <div className="form-group">
              <PlacesAutocomplete
                value={address}
                onChange={ this.handleChangeAddress }
                onSelect={ this.handleSelect }
              >
            {({ getInputProps, suggestions, getSuggestionItemProps, loading }) => (
              <div>
                <input
                  {...getInputProps({
                    placeholder: 'Search places...',
                    className: 'location-search-input',
                  })}
                />
                <div className="autocomplete-dropdown-container">
                  {loading && <div>Loading...</div>}
                  {suggestions.map(suggestion => {
                    const className = suggestion.active
                      ? 'suggestion-item--active'
                      : 'suggestion-item';
                    // inline style for demonstration purpose
                    const style = suggestion.active
                      ? { backgroundColor: '#fafafa', cursor: 'pointer' }
                      : { backgroundColor: '#ffffff', cursor: 'pointer' };
                    return (
                      <div
                        {...getSuggestionItemProps(suggestion, {
                          className,
                          style,
                        })}
                      >
                        <span>{suggestion.description}</span>
                      </div>
                    );
                  })}
                </div>
              </div>
            )}
            </PlacesAutocomplete>
            </div>

            <div className="form-group">
              <input 
                type="submit" 
                value="Update settings" 
                className="btn btn--primary card__footer--btn-left"
                disabled={ formSubmitted && status === 'fetching' ? 'disabled' : null }
              />
            </div>
            </form>
        </Card>
      </TemplatePage>
    )
  }

}


Settings.defaultProps = {
  auth: {},
  person: {},
  fetchPersonId: () => Promise.resolve(),
  updatePersonsettings: () => Promise.resolve(),
  pushPersonMessage: () => Promise.resolve()
}


Settings.propTypes = {
  auth: PropTypes.object,
  person: PropTypes.object,
  fetchPersonId: PropTypes.func,
  updatePersonsettings: PropTypes.func,
  pushPersonMessage: PropTypes.func
};

export default connect(
  ({ auth, person }) => ({ auth, person }),
  { fetchPersonId, updatePersonSettings, pushPersonMessage }
)(Settings);

My test:

import React from 'react';
import { shallow } from 'enzyme';
import { Settings } from '../../../components/pages/Settings';

test('should render settings page', () => {
  const wrapper = shallow(<Settings />);
  expect(wrapper).toMatchSnapshot();
});

I read that in order to resolve such issues it is best to mock up the package. In some other component I am using `` package, which I managed to mock like so:

const zxcvbn = require.requireActual('zxcvbn');

export default (password = 'test') => {
  return zxcvbn(password);
}

How would I mock up the @react-google-maps/api package and get rid of the error? Is this a good approach (mocking the package)? Or can this be resolved any other way? How would I test if the map or marker rendered at all?

like image 337
xyz83242 Avatar asked Oct 28 '25 22:10

xyz83242


1 Answers

I know this is an old question. But the way I manage this is by mocking the google object. This is only mocks what I'm calling so you'd need to add any google constants and methods you call.

As for testing if it gets rendered, that's all buried in the google maps javascript api blob. You could add spys to ensure the appropriate functions are called. I don't think it'd be possible to verify google maps is actually rendering anything though.

import ReactDOM from 'react-dom';

//ReactDOM.createPortal = jest.fn(node => node);

jest.mock('@react-google-maps/api', () => {
    const React = require('React');
    return {
        withGoogleMap: (Component) => Component,
        withScriptjs: (Component) => Component,
        Polyline: (props) => <div />,
        Marker: (props) => <div />,
        GoogleMap: (props) => (<div><div className="mock-google-maps" />{props.children}</div>),
    };
});

global.google = {
    maps: {
        LatLngBounds: () => ({
            extend: () => { },
        }),
        MapTypeId: {
            ROADMAP: 'rdmap',
            SATELLITE: 'stllte'
        },
        ControlPosition: {
            BOTTOM_CENTER: 'BC',
            BOTTOM_LEFT: 'BL',
            BOTTOM_RIGHT: 'BR',
            LEFT_BOTTOM: 'LB',
            LEFT_CENTER: 'LC',
            LEFT_TOP: 'LT',
            RIGHT_BOTTOM: 'RB',
            RIGHT_CENTER: 'RC',
            RIGHT_TOP: 'RT',
            TOP_CENTER: 'TC',
            TOP_LEFT: 'TL',
            TOP_RIGHT: 'TR',            
        },
        Size: function (w, h) {},
        Data: class {
            setStyle() {}
            addListener() {}
            setMap() {}
        }
    }
};
like image 167
cliffb Avatar answered Oct 31 '25 13:10

cliffb



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!