Enzyme: jest + React 15 - Passing array of objects as props results in an error

Created on 27 Apr 2016  Â·  5Comments  Â·  Source: enzymejs/enzyme

Hello,

See #315 for the start of this conversation.

I'm running into an error when I try to pass an array of objects to a tested, mounted component as a prop.

Here is the component:

import * as React from 'react';
import { ReferenceWindowEvents as LocalEvents, FieldMappings } from '../../utils/Constants';


interface PeopleProps {
    people: CSL.TypedPerson[]
    eventHandler: Function
    citationType: CSL.CitationType
}

export class People extends React.Component<PeopleProps,{}> {

    public fieldMaps: ABT.FieldMappings = FieldMappings;

    constructor(props) {
        super(props);
    }

    addPerson() {
        this.props.eventHandler(
            new CustomEvent(LocalEvents.ADD_PERSON)
        );
    }

    removePerson(e: InputEvent) {
        this.props.eventHandler(
            new CustomEvent(LocalEvents.REMOVE_PERSON, {
                detail: parseInt(e.target.dataset['num']),
            })
        );
    }

    onChange(e: InputEvent) {
        this.props.eventHandler(
            new CustomEvent(LocalEvents.PERSON_CHANGE, {
                detail: {
                    index: parseInt(e.target.dataset['num']),
                    field: e.target.dataset['namefield'],
                    value: e.target.value,
                }
            })
        );
    }

    render() {
        return (
            <div>
                <div className='row' style={{ display: 'flex', alignItems: 'center', }}>
                    <strong style={{ paddingRight: 5, }} children='Contributors'/>
                    <input
                        type='button'
                        id='add-person'
                        className='btn'
                        value='Add Another'
                        onClick={this.addPerson.bind(this)}/>
                </div>
                {this.props.people.map((person: CSL.TypedPerson, i: number) =>
                    <div key={`person-list-${i}`} style={{ display: 'flex', alignItems: 'center', }}>
                        <div>
                            <select
                                value={person.type}
                                data-num={i}
                                data-namefield='type'
                                onChange={this.onChange.bind(this)}>
                                { this.fieldMaps[this.props.citationType].people.map((p, j: number) =>
                                    <option key={`peopleSelect-${j}`} value={p.type} children={p.label} />
                                )}
                            </select>
                        </div>
                        <div style={{ flex: 1, padding: '0 5px', }} >
                            <input
                                type='text'
                                data-namefield='family'
                                data-num={i}
                                style={{ width: '100%', }}
                                placeholder='Lastname'
                                aria-label='Last Name'
                                id={`person-family-${i}`}
                                value={person.family}
                                onChange={this.onChange.bind(this)}
                                required={true} />
                        </div>
                        ,
                        <div style={{ flex: 1, padding: '0 5px', }} >
                            <input
                                type='text'
                                data-namefield='given'
                                style={{width: '100%'}}
                                placeholder='Firstname, Middleinitial'
                                aria-label='First Name, Middle Initial'
                                data-num={i}
                                id={`person-given-${i}`}
                                value={person.given}
                                onChange={this.onChange.bind(this)}
                                required={true} />
                        </div>
                        <div style={{ padding: '0 5px', }}>
                            <input
                            type='button'
                            className='btn'
                            data-num={i}
                            value='✖'
                            onClick={this.removePerson.bind(this)} />
                        </div>
                    </div>
                )}
            </div>
        )
    }
}

Here is the test file

jest.unmock('../People');

import * as React from 'react';
import { shallow, mount } from 'enzyme';
import * as sinon from 'sinon';
import { People } from '../People';

function setup(citationType: CSL.CitationType = 'article') {
    const eventHandler = sinon.spy();
    const component = shallow(
        <People citationType={citationType} eventHandler={eventHandler} people={[{type: 'author', family: 'adsf', given: 'asdfad'}]} />
    );
    return {
        eventHandler,
        component,
        addButton: component.find('#add-person')
    }
}

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

    it('should dispatch ADD_PERSON event when add button is clicked', () => {

        // this is where the error is thrown
        const { addButton, eventHandler } = setup();
// rest of file truncated, since it's not relevant

Here is the error I receive:

<People /> › it should dispatch ADD_PERSON event when add button is clicked
  - TypeError: Cannot read property 'people' of undefined
        at eval (lib/js/components/ReferenceWindow/People.tsx:9:3188)
        at Array.map (native)
        at People.render (lib/js/components/ReferenceWindow/People.tsx:9:2795)
        at ReactCompositeComponentMixin._renderValidatedComponentWithoutOwnerOrContext (node_modules/enzyme/node_modules/react/lib/ReactCompositeComponent.js:587:34)
        at ReactCompositeComponentMixin.mountComponent (node_modules/enzyme/node_modules/react/lib/ReactCompositeComponent.js:220:30)
        at wrapper [as mountComponent] (node_modules/enzyme/node_modules/react/lib/ReactPerf.js:66:21)
        at ReactShallowRenderer._render (node_modules/enzyme/node_modules/react/lib/ReactTestUtils.js:366:14)
        at _batchedRender (node_modules/enzyme/node_modules/react/lib/ReactTestUtils.js:348:12)
        at ReactDefaultBatchingStrategyTransaction.Mixin.perform (node_modules/enzyme/node_modules/react/lib/Transaction.js:136:20)
        at Object.ReactDefaultBatchingStrategy.batchedUpdates (node_modules/enzyme/node_modules/react/lib/ReactDefaultBatchingStrategy.js:62:19)
        at Object.batchedUpdates (node_modules/enzyme/node_modules/react/lib/ReactUpdates.js:94:20)
        at ReactShallowRenderer.render (node_modules/enzyme/node_modules/react/lib/ReactTestUtils.js:343:16)
        at render (node_modules/enzyme/build/react-compat.js:146:39)
        at new ShallowWrapper (node_modules/enzyme/build/ShallowWrapper.js:81:21)
        at Object.shallow (node_modules/enzyme/build/shallow.js:21:10)
        at setup (lib/js/components/ReferenceWindow/__tests__/People-test.tsx:10:30)
        at Object.eval (lib/js/components/ReferenceWindow/__tests__/People-test.tsx:19:18)
        at attemptSync (node_modules/jest-jasmine2/vendor/jasmine-2.3.4.js:1791:24)
        at QueueRunner.run (node_modules/jest-jasmine2/vendor/jasmine-2.3.4.js:1779:9)
        at QueueRunner.execute (node_modules/jest-jasmine2/vendor/jasmine-2.3.4.js:1764:10)
        at Spec.Env.queueRunnerFactory (node_modules/jest-jasmine2/vendor/jasmine-2.3.4.js:629:35)
        at Spec.execute (node_modules/jest-jasmine2/vendor/jasmine-2.3.4.js:355:10)
        at Object.fn (node_modules/jest-jasmine2/vendor/jasmine-2.3.4.js:2362:37)
        at attemptAsync (node_modules/jest-jasmine2/vendor/jasmine-2.3.4.js:1821:24)
        at QueueRunner.run (node_modules/jest-jasmine2/vendor/jasmine-2.3.4.js:1776:9)
        at QueueRunner.execute (node_modules/jest-jasmine2/vendor/jasmine-2.3.4.js:1764:10)
        at Env.queueRunnerFactory (node_modules/jest-jasmine2/vendor/jasmine-2.3.4.js:629:35)
        at Object.fn (node_modules/jest-jasmine2/vendor/jasmine-2.3.4.js:2347:13)
        at attemptAsync (node_modules/jest-jasmine2/vendor/jasmine-2.3.4.js:1821:24)
        at QueueRunner.run (node_modules/jest-jasmine2/vendor/jasmine-2.3.4.js:1776:9)
        at QueueRunner.execute (node_modules/jest-jasmine2/vendor/jasmine-2.3.4.js:1764:10)
        at Env.queueRunnerFactory (node_modules/jest-jasmine2/vendor/jasmine-2.3.4.js:629:35)
        at TreeProcessor.execute (node_modules/jest-jasmine2/vendor/jasmine-2.3.4.js:2211:7)
        at Env.execute (node_modules/jest-jasmine2/vendor/jasmine-2.3.4.js:680:17)
        at jasmine2 (node_modules/jest-jasmine2/src/index.js:194:7)
        at handle (node_modules/worker-farm/lib/child/index.js:41:8)
        at process.<anonymous> (node_modules/worker-farm/lib/child/index.js:47:3)
        at emitTwo (events.js:100:13)
        at process.emit (events.js:185:7)
        at handleMessage (internal/child_process.js:718:10)
        at Pipe.channel.onread (internal/child_process.js:444:11)

Here is my package.json

Note: I added everything below sinon in the unmockedModulePathPatterns in accordance to the recommendations in the enzyme docs.

{
  "name": "academic-bloggers-toolkit",
  "version": "3.0.0",
  "description": "A plugin extending the functionality of WordPress for Academic Blogging.",
  "main": "index.js",
  "scripts": {
    "test": "jest",
    "wp": "wpcmd() { docker exec $(docker ps -lq) /bin/bash -c \"sudo -u www-data wp $(echo $@)\"; };wpcmd "
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/dsifford/academic-bloggers-toolkit.git"
  },
  "author": "Derek P Sifford",
  "license": "GPL-3.0",
  "bugs": {
    "url": "https://github.com/dsifford/academic-bloggers-toolkit/issues"
  },
  "homepage": "https://github.com/dsifford/academic-bloggers-toolkit#readme",
  "devDependencies": {
    "babel-core": "^6.7.6",
    "babel-loader": "^6.2.4",
    "babel-preset-es2015": "^6.6.0",
    "babel-preset-react": "^6.5.0",
    "browser-sync": "^2.12.5",
    "coveralls": "^2.11.9",
    "del": "^2.2.0",
    "enzyme": "^2.2.0",
    "gulp": "^3.9.1",
    "gulp-autoprefixer": "^3.1.0",
    "gulp-clean-css": "^2.0.6",
    "gulp-sass": "^2.3.1",
    "gulp-uglify": "^1.5.3",
    "jest-cli": "^11.0.2",
    "ntypescript": "^1.201604262206.1",
    "react": "^15.0.1",
    "react-addons-test-utils": "^15.0.1",
    "react-dom": "^15.0.1",
    "sinon": "^1.17.3",
    "ts-loader": "^0.8.2",
    "typescript": "^1.9.0-dev.20160423",
    "webpack": "^1.13.0",
    "webpack-stream": "^3.2.0"
  },
  "jest": {
    "scriptPreprocessor": "jestPreprocessor.js",
    "testFileExtensions": [
      "ts",
      "tsx",
      "js"
    ],
    "moduleFileExtensions": [
      "js",
      "ts",
      "tsx",
      "json"
    ],
    "unmockedModulePathPatterns": [
        "react",
        "react-dom",
        "react-addons-test-utils",
        "fbjs",
        "enzyme",
        "sinon",
        "cheerio",
        "htmlparser2",
        "underscore",
        "lodash",
        "domhandler",
        "object.assign",
        "define-properties",
        "function-bind",
        "object-keys",
        "es-abstract",
        "object.values"
    ],
    "verbose": true,
    "collectCoverage": true
  },
  "dependencies": {}
}

If you can offer any help/tips, they would be much appreciated. Thank you very much in advance 😄

Final note: I suppose I should mention that everything works fine and as expected pass an empty array to <People /> as the people prop.

invalid

Most helpful comment

Update again: I'm embarrassed. Found a typo. Unmocking solved part 1 of the issue. Fixing the typo solved the other.

Closing the issue.

homer

All 5 comments

Is it possibly this.fieldMaps[this.props.citationType].people.map? Is this.fieldMaps being loaded correctly in your testing environment?

What do you get if you log out this.props at the top of your render method?

Standby, I'll give that a look now.

Of note, I tried jest.unmocking the fieldmapping object as well yesterday and that didn't seem to have any effect.

@Aweary Good observation...

The props are received just fine, however this.fieldMappings seems to only go one level deep...

Here is a look at what I get:

{ bill: { title: 'Bill', fields: [], people: [] },
  book: { title: 'Book', fields: [], people: [] },
  chapter: { title: 'Book Section', fields: [], people: [] },
  broadcast: { title: 'Broadcast', fields: [], people: [] },
  legal_case: { title: 'Case', fields: [], people: [] },
  'paper-conference': { title: 'Conference Proceeding', fields: [], people: [] },
  'entry-encyclopedia': { title: 'Encyclopedia Entry', fields: [], people: [] },
  motion_picture: { title: 'Film', fields: [], people: [] },
  speech: { title: 'Presentation', fields: [], people: [] },
  'article-journal': { title: 'Journal Article', fields: [], people: [] },
  'article-magazine': { title: 'Magazine Article', fields: [], people: [] },
  'article-newspaper': { title: 'Newspaper Article', fields: [], people: [] },
  patent: { title: 'Patent', fields: [], people: [] },
  report: { title: 'Report', fields: [], people: [] },
  legislation: { title: 'Statute', fields: [], people: [] },
  thesis: { title: 'Thesis', fields: [], people: [] },
  webpage: { title: 'Web Page', fields: [], people: [] } }

and here's a small snippet of how the first two fieldmaps should look:

export const FieldMappings: ABT.FieldMappings = {
    bill: {
        title: 'Bill',
        fields: [
            { value: 'title', label: 'Title', required: true, pattern: '.*', placeholder: '',  },
            { value: 'number', label: 'Bill Number', required: false, pattern: '[0-9]+', placeholder: '', },
            { value: 'page', label: 'Code Pages', required: false, pattern: '^[0-9]+-?[0-9]*$', placeholder: 'Number or Range of Numbers (100-200)', },
            { value: 'volume', label: 'Code Volume', required: false, pattern: '[0-9]+', placeholder: '', },
            { value: 'section', label: 'Section', required: false, pattern: '.*', placeholder: '', },
            { value: 'publisher', label: 'Legislative Body', required: false, pattern: '.*', placeholder: '', },
            { value: 'issued', label: 'Date', required: true, pattern: '[0-9]{4}(\/[0-9]{2})?(\/[0-9]{2})?(?!\/)$', placeholder: 'YYYY/MM/DD or YYYY/MM or YYYY', },
            { value: 'accessed', label: 'Date Accessed', required: false, pattern: '[0-9]{4}(\/[0-9]{2})?(\/[0-9]{2})?(?!\/)$', placeholder: 'YYYY/MM/DD or YYYY/MM or YYYY', },
        ],
        people: [
            { type: 'author', label: 'Sponsor', },
        ],
    },
    book: {
        title: 'Book',
        fields: [
            { value: 'title', label: 'Title', required: true, pattern: '.*', placeholder: '', },
            { value: 'collection-title', label: 'Series Title', required: false, pattern: '.*', placeholder: '', },
            { value: 'collection-number', label: 'Series Number', required: false, pattern: '[0-9]+', placeholder: '', },
            { value: 'number-of-pages', label: '# of Pages', required: false, pattern: '[0-9]+', placeholder: '', },
            { value: 'volume', label: 'Volume', required: false, pattern: '[0-9]+', placeholder: '', },
            { value: 'edition', label: 'Edition', required: false, pattern: '[0-9]+', placeholder: '', },
            { value: 'publisher', label: 'Publisher', required: true, pattern: '.*', placeholder: '', },
            { value: 'publisher-place', label: 'Publisher Location', required: false, pattern: '.*', placeholder: '', },
            { value: 'issued', label: 'Date', required: true, pattern: '[0-9]{4}(\/[0-9]{2})?(\/[0-9]{2})?(?!\/)$', placeholder: 'YYYY/MM/DD or YYYY/MM or YYYY', },
            { value: 'accessed', label: 'Date Accessed', required: false, pattern: '[0-9]{4}(\/[0-9]{2})?(\/[0-9]{2})?(?!\/)$', placeholder: 'YYYY/MM/DD or YYYY/MM or YYYY', },
        ],
        people: [
            { type: 'author', label: 'Author', },
            { type: 'editor', label: 'Editor', },
            { type: 'collection-editor', label: 'Series Editor', },
            { type: 'translator', label: 'Translator', },
        ],
    },

Thoughts?

Update: I unmocked the file that contained the fieldmaps and they're now being sent fully. However, the error still persists.

Update again: I'm embarrassed. Found a typo. Unmocking solved part 1 of the issue. Fixing the typo solved the other.

Closing the issue.

homer

Was this page helpful?
0 / 5 - 0 ratings

Related issues

timhonders picture timhonders  Â·  3Comments

abe903 picture abe903  Â·  3Comments

AdamYahid picture AdamYahid  Â·  3Comments

amcmillan01 picture amcmillan01  Â·  3Comments

mattkauffman23 picture mattkauffman23  Â·  3Comments