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

Method “type” is meant to be run on 1 node. n found instead #2549

Open
3 tasks done
totszwai opened this issue Nov 30, 2021 · 15 comments
Open
3 tasks done

Method “type” is meant to be run on 1 node. n found instead #2549

totszwai opened this issue Nov 30, 2021 · 15 comments

Comments

@totszwai
Copy link

totszwai commented Nov 30, 2021

Update

React 16 + enzyme-adapter-react-16 reduced use case sandbox here: #2549 (comment)

Current behavior

React 17, using all the latest packages.

I follow one of the enzyme unit test workaround to include Mui's ThemeProvider + styled-components's ThemeProvider

function UnitTestThemeWrapper (props: any) {
  // console.log(props.theme);
  return (
    <StyledEngineProvider injectFirst>
      <MuiThemeProvider theme={props.theme || LightTheme}>
        <ThemeProvider theme={props.theme || LightTheme}>
          <GlobalStyle />
          <CssBaseline />
          {props.children}
        </ThemeProvider>
      </MuiThemeProvider>
    </StyledEngineProvider>
  );
}

const shallowWithTheme = (node: ReactElement, options?: ShallowRendererProps): ShallowWrapper => {
  // The only way to get it to work, is to manually wraps the node ourselves.
  // return shallow(UnitTestThemeWrapper(node), options);

  // Note: For some reason the wrappingComponent from Enzyme is broken.
  // It would not properly calls the wrappingComponent when using shallow.
  // It throws a cryptic error message which has nothing to do with the issues:
  //    "Method “type” is meant to be run on 1 node. 3 found instead."
 
  options = options || {
    wrappingComponent: UnitTestThemeWrapper
  };
  return shallow(node, options); // <= this throw "Method type is meant to be run on 1 node. 3 found instead.
  // */
};

My UT only has the following:

describe('Blah', () => {
  let wrapper: ShallowWrapper | ReactWrapper;
  it('should render', () => {
    wrapper = shallow(<Menu open={false} anchorEl={dummy} />);
  });
});

How can I get it to work? When I try to dump the node, this is what it prints out:

  console.log
    { '$$typeof': Symbol(react.element),
      type: [Function: Menu],
      key: null,
      ref: null,
      props: { open: false, anchorEl: HTMLDivElement {} },
      _owner: null,
      _store: {} }

      at shallowWithTheme (src/themes/testUtils.tsx:30:11)

Expected behavior

Your environment

API

  • shallow

Version

    "@emotion/css": "^11.5.0",
    "@emotion/jest": "^11.6.0",
    "@emotion/react": "^11.6.0",
    "@emotion/styled": "^11.6.0",
    "@types/enzyme": "^3.10.10",
    "@types/enzyme-adapter-react-16": "^1.0.6",
    "@wojtekmaj/enzyme-adapter-react-17": "^0.6.5",
    "@mui/material": "^5.2.2",
    "@mui/styles": "^5.2.2",
    "enzyme": "^3.11.0",
    "enzyme-adapter-react-16": "^1.15.6",
    "enzyme-to-json": "^3.6.2",
    "identity-obj-proxy": "^3.0.0",
    "jest": "^26.6.0",
    "jest-emotion": "^11.0.0",
    "jest-enzyme": "^7.1.2",
    "jest-junit": "^12.2.0",
    "jest-styled-components": "^7.0.8",

Adapter

  • enzyme-adapter-react-16
  • others ("@wojtekmaj/enzyme-adapter-react-17": "^0.6.5",)
@ljharb
Copy link
Member

ljharb commented Nov 30, 2021

You're using an unofficial adapter, so I'm afraid there's not really any support I can offer. enzyme does not work with react 17 yet.

@ljharb ljharb closed this as completed Nov 30, 2021
@totszwai
Copy link
Author

totszwai commented Nov 30, 2021

@ljharb after 2 days of debugging, I've found the root cause. This is a bug in shallow.
The following wrappingComponent will fail, because the shallow traversal code expect only 1 node.

return (
    <StyledEngineProvider injectFirst>
      <MuiThemeProvider theme={theme}>
        <ThemeProvider theme={theme}>
          <GlobalStyle />
          <CssBaseline />
          {props.children}
        </ThemeProvider>
      </MuiThemeProvider>
    </StyledEngineProvider>
)

The following hack to workaround the shallow issue...

  const EnzymeWorkaround = (props: any) => (<><GlobalStyle /><CssBaseline />{props.children}</>);
  return (
    <StyledEngineProvider injectFirst>
      <MuiThemeProvider theme={props.theme}>
        <ThemeProvider theme={props.theme}>
          <EnzymeWorkaround>
            {props.children}
          </EnzymeWorkaround>
        </ThemeProvider>
      </MuiThemeProvider>
    </StyledEngineProvider>
  );

The code in shallow is seeing the following as "3 nodes".

          <GlobalStyle />
          <CssBaseline />
          {props.children}

The depth traversal code of shallow isn't reliable it seems...

@totszwai totszwai changed the title Method “type” is meant to be run on 1 node. 3 found instead Method “type” is meant to be run on 1 node. n found instead Nov 30, 2021
@ljharb
Copy link
Member

ljharb commented Nov 30, 2021

That’s not a bug, that is the design of “wrappingComponent” - it must only be a component that takes a single child.

Your hostile language is not appreciated.

@totszwai
Copy link
Author

totszwai commented Nov 30, 2021

it must only be a component that takes a single child.

That statement is not true? So if I created an additional wrapper that wraps everything together (now is only takes 1 child), it still throws the exact same error... It looks like shallow still drills down to the deepest child node of the inner React node(s).

export const BlahThemeProvider = (props) => {
  return (
    <StyledEngineProvider injectFirst>
      <MuiThemeProvider theme={props.theme}>
        <ThemeProvider theme={props.theme}>
          <GlobalStyle />
          <CssBaseline />
          {props.children}
        </ThemeProvider>
      </MuiThemeProvider>
    </StyledEngineProvider>
  );
};

Then in the wrappingComponent:

function UnitTestThemeWrapper (props: any) {
  const theme = props.theme || LightTheme;
  // console.log('theme? ', theme);
  return (
    <BlahThemeProvider theme={theme}>
      {props.children} // <== Takes only 1 now
    </BlahThemeProvider>
  );
}

const shallowWithTheme = (node: ReactElement, options?: ShallowRendererProps): ShallowWrapper => {
  options = options || {
    wrappingComponent: UnitTestThemeWrapper
  };
  return shallow(node, options);
};

@ljharb
Copy link
Member

ljharb commented Nov 30, 2021

Yes, you’re right it could be more precisely stated. The wrapping component must end up taking only one child, which your example still takes 3.

@totszwai
Copy link
Author

totszwai commented Nov 30, 2021

The wrapping component must end up taking only one child, which your example still takes 3.

@ljharb BTW, that statement is also not true because I've tried the following, and it will still fail.
I've tested the following by shifting the nodes upwards to where they could also be:

    <StyledEngineProvider injectFirst>
      <MuiThemeProvider theme={props.theme}>
        <GlobalStyle />
        <CssBaseline />
        <ThemeProvider theme={props.theme}>
          {props.children} // <== Takes only 1 now
        </ThemeProvider>
      </MuiThemeProvider>
    </StyledEngineProvider>

You get:

Method “type” is meant to be run on 1 node. 3 found instead.

Basically the wrappingComponent is fairly impractical/limiting as every single level must only take one single node and nothing else.

You should reopen this as a bug IMHO.

@ljharb
Copy link
Member

ljharb commented Nov 30, 2021

It is indeed limiting if you're using the very unidiomatic pattern of having a component that takes no children and sits somewhere just for side effects (CssBaseline and GlobalStyle, both of which should be HOCs or hooks).

If you can reproduce it with the react 16 adapter, I'll be happy to reopen it - enzyme must not be used with react 17 yet, because it does not support it, so bugs are expected.

@totszwai
Copy link
Author

totszwai commented Nov 30, 2021

@ljharb You can't just tell everyone who try to use Enzyme to just wrap everything at most 1 level only... I didn't create those CssBaseline nor GlobalStyle, thats how it works. That's how React works.

Here is the enzyme-adapter-react-16 sandbox, reduced test case link:
https://codesandbox.io/s/react-enzyme-16-shallow-bug-yvbz7

image

Below are the official documentations from various framework showcasing the valid use of "a component that takes no children".

CssBaseline from Material UI
https://mui.com/components/css-baseline

GlobalStyle from styled-components
https://styled-components.com/docs/api#createglobalstyle

Global from emotion
https://emotion.sh/docs/globals

@ljharb
Copy link
Member

ljharb commented Nov 30, 2021

Thanks; I'll reopen until I have time to dig further into it.

Yes, I'm aware that the pattern is used in a few places; that doesn't mean it's a good idea.

@ljharb ljharb reopened this Nov 30, 2021
@rmontesgar-chwy
Copy link

rmontesgar-chwy commented Jul 21, 2022

I found a similar case if you try to use RecoilRoot context (from https://recoiljs.org/), as it renders:

<RecoilRoot>
  <Batcher>
  {children}
</RecoilRoot>

where batcher is used internally to "The purpose of the Batcher is to observe when React batches end so that Recoil state changes can be batched."

This context is needed whenever you test component that uses any of its hooks.

I am writing it just as a heads up for people wanting to test testing a component that uses Recoil like:

const wrapper = shallow(<MyComponent />, {
  wrappingComponent: RecoilRoot,
});

that it is going to fail, so probably you should use mount instead.

@ljharb
Copy link
Member

ljharb commented Jul 21, 2022

@rmontesgar-chwy i'm assuming it renders <Batcher /> - but yes, wrappingComponent can't work there because it renders more than just its children.

@SupriyaPKalghatgi

This comment was marked as off-topic.

@ljharb

This comment was marked as off-topic.

@SupriyaPKalghatgi

This comment was marked as off-topic.

@ljharb

This comment was marked as off-topic.

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