Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't seem to test with React 16, Jest, Enzyme 3.3 #52

Closed
kamranayub opened this issue Aug 27, 2018 · 10 comments
Closed

Can't seem to test with React 16, Jest, Enzyme 3.3 #52

kamranayub opened this issue Aug 27, 2018 · 10 comments

Comments

@kamranayub
Copy link

I've made a reproduction here; the IdleTimer works in the app but does not work when mounted during a test:

https://codesandbox.io/s/6vzxx83r0z

I get the error:

Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

Check the render method of `WrapperComponent`.

I'm specifically using the same enzyme and enzyme-adapter-react-16 versions listed in the package.json.

@kamranayub kamranayub changed the title Can't seem test with React 16, Jest, Ezyme 3.3 Can't seem to test with React 16, Jest, Ezyme 3.3 Aug 27, 2018
@kamranayub kamranayub changed the title Can't seem to test with React 16, Jest, Ezyme 3.3 Can't seem to test with React 16, Jest, Enzyme 3.3 Aug 27, 2018
@SupremeTechnopriest
Copy link
Owner

SupremeTechnopriest commented Aug 27, 2018

Hi @kamranayub, this error is not caused by IdleTImer. It's because you are not exporting your app component and the test suite cant find it. Ive updated your example here.

@SupremeTechnopriest
Copy link
Owner

SupremeTechnopriest commented Aug 27, 2018

Also, I would probably use IdleTimer as a standalone component and not as a wrapper component. <IdleTImer /> vs <IdleTimer>{{some content}}</IdleTimer>. This way your app state is separate from IdleTimer state.

@kamranayub
Copy link
Author

kamranayub commented Aug 27, 2018 via email

@kamranayub
Copy link
Author

kamranayub commented Aug 27, 2018

Ah hah! I figured it out. It was due to how react-idle-timer is exported as a module and how I described it in TypeScript.

This declaration file works as expected:

declare module "react-idle-timer" {
  import * as React from "react";

  class IdleTimer extends React.Component<IdleTimerProps> {
    /**
     * Restore initial state and restart timer
     */
    reset(): void;

    /**
     * Store remaining time and stop timer
     */
    pause(): void;

    /**
     * Resumes a paused timer
     */
    resume(): void;

    /**
     * Time remaining before idle (number of ms)
     */
    getRemainingTime(): number;

    /**
     * How much time has elapsed (timestamp)
     */
    getElapsedTime(): number;

    /**
     * Last time the user was active
     */
    getLastActiveTime(): number;

    /**
     * Returns wether or not the user is idle
     */
    isIdle(): boolean;
  }

  namespace IdleTimer {

  }

  interface IdleTimerProps {
    ref: (ref: IdleTimer) => any;

    /**
     * Activity Timeout in milliseconds default: 1200000
     */
    timeout?: number;

    /**
     * DOM events to listen to default: see [default events](https://github.com/SupremeTechnopriest/react-idle-timer#default-events)
     */
    events?: string[];

    /**
     * Element reference to bind activity listeners to default: document
     */
    element?: Node;

    /**
     * Start the timer on mount default: true
     */
    startOnMount?: boolean;

    /**
     * Bind events passively default: true
     */
    passive?: boolean;

    /**
     * Capture events default: true
     */
    capture?: boolean;

    /**
     * Function to call when user becomes active
     */
    onActive?: () => void;

    /**
     * Function to call when user is idle
     */
    onIdle?: () => void;
  }

  export = IdleTimer;
}

I may try to contribute a @types package for this.

@SupremeTechnopriest
Copy link
Owner

@kamranayub Please do! That would be great!

@kamranayub
Copy link
Author

OK, this was actually a little more involved.

I was having the issue I originally posted above in my CRA TS project when running Jest tests. I had no problems using the component in my application at runtime, though.

My tsconfig.test.json file has a different module format than the main application, due to some issues with react-scripts-ts:

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "module": "commonjs"
  }
}

For whatever reason, this module type causes a problem when trying to use the following import syntax:

import IdleTimer from "react-idle-timer";

While this works at runtime (webpack can import it properly), Jest cannot handle this and throws the error above (it's basically undefined).

The import has to look like this when running through tests:

import * as IdleTimer from "react-idle-timer";

This will work with the Jest tests.

So to handle both working at runtime and working during tests, I introduced a component prop on my wrapper component that uses whatever instance is passed so I can override it during tests. Super hacky but short of redoing how this package gets bundled, I don't know what to do to fix it.

These are the TS definitions I eventually ended up with:

declare module "react-idle-timer" {
  import * as React from "react";

  class IdleTimer extends React.Component<IdleTimerProps> {
    /**
     * Restore initial state and restart timer
     */
    reset(): void;

    /**
     * Store remaining time and stop timer
     */
    pause(): void;

    /**
     * Resumes a paused timer
     */
    resume(): void;

    /**
     * Time remaining before idle (number of ms)
     */
    getRemainingTime(): number;

    /**
     * How much time has elapsed (timestamp)
     */
    getElapsedTime(): number;

    /**
     * Last time the user was active
     */
    getLastActiveTime(): number;

    /**
     * Returns wether or not the user is idle
     */
    isIdle(): boolean;
  }

  export interface IdleTimerProps {
    ref: (ref: IdleTimer) => any;

    /**
     * Activity Timeout in milliseconds default: 1200000
     */
    timeout?: number;

    /**
     * DOM events to listen to default: see [default events](https://github.com/SupremeTechnopriest/react-idle-timer#default-events)
     */
    events?: string[];

    /**
     * Element reference to bind activity listeners to default: document
     */
    element?: Node;

    /**
     * Start the timer on mount default: true
     */
    startOnMount?: boolean;

    /**
     * Bind events passively default: true
     */
    passive?: boolean;

    /**
     * Capture events default: true
     */
    capture?: boolean;

    /**
     * Function to call when user becomes active
     */
    onActive?: () => void;

    /**
     * Function to call when user is idle
     */
    onIdle?: () => void;
  }

  export default IdleTimer;
}

The weird part is I can't repro this exactly in CodeSandbox. It apparently works in tests and at runtime with import * as IdleTimer syntax.

https://codesandbox.io/s/n0y232k4p0

🤷‍♂️

@SupremeTechnopriest
Copy link
Owner

Sounds like it's the way default exports are handled by typescript? I'm not too familiar with ts. I like it in concept but haven't really done anything with it. I see that react has the same style of import import * as React from "react"; where usually you can just import React from 'react'?

Glad you got something working... but not sure theres anything I can do on my end to help you clean it up. If it helps, I use Rollup to build the component. The main export is a cjs module and the module export is an es module. See here.

@kamranayub
Copy link
Author

kamranayub commented Aug 27, 2018 via email

@MQuy
Copy link

MQuy commented Aug 31, 2018

I had the same problem. It seems that typescript cannot pick module in package.json but webpack can.
You can check details here: microsoft/TypeScript#21423
I wonder that can we disable module in our package.json temporarity? @SupremeTechnopriest

@paigeflourin
Copy link

Hi @kamranayub ,

would happen to explain to me how to implement this? sorry i am new to this but i am creating an SPFX app and i need to check a user's idle time. i have forked your codesandbox and tried to implement the example in the readme but i can't make it work.

https://codesandbox.io/s/n0y232k4p0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants