Git Product home page Git Product logo

react-magnetic-di's Issues

Mocking not functional with Jest + storybook + storyshots

Hi, I'm trying to use react-magnetic-di in a project created using https://github.com/facebook/create-react-app along with Jest and storybook snapshots.

For some reason the mocking does not appear to work. Apologies in advance if I have misunderstood something, I do not understand the whole development stack very well.

Is the Babel plugin critical for the functioning of magnetic-di, or is it optional? It seems create-react-app out of the box does not allow custom Babel plugins.

In any case, I tried ejecting from create-react-app and created babel.config.json with:

{
  "plugins": [["react-magnetic-di/babel-plugin", {"forceEnable": true}]]
}

Here's my test case:

import {di, DiProvider} from 'react-magnetic-di';
import React from 'react';

const useBla = () => 'real';

const useBlaMock = di.mock(useBla, () => 'mocked');

const Bla: React.FC = () => {
  di(useBla);
  const value = useBla();
  return <div>{value}</div>;
};

storiesOf('DiTest', module).add('bla', () => (
  <DiProvider use={[useBlaMock]}>
    <Bla />
  </DiProvider>
));
// ^^^^^^^^^^^ test case

The snapshot output is:

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Storyshots DiTest bla 1`] = `
<div>
  real
</div>
`;

So the mocked return value of mocked was not used.

Eslint errors on files without di

Seems like this code breaks eslint on the JSX tag:

// @flow strict-local

import React from 'react';
import { shallow } from 'enzyme';

import type { Props } from './types';
import CommitWarningNotification from './view';

test('Should render commit warning notifications', () => {
    const props: Props = {
        show: true,
        successfulCommitCount: 8,
        failedCommitCount: 7,
        onClose: () => {},
    };
    const tree = shallow(<CommitWarningNotification {...props} />);
    expect(tree).toMatchSnapshot();
});
Cannot read property 'name' of undefined Occurred while linting ....

Probably due to missing check on no-extraneous JSXOpeningElement:exit for diIdentifier presence

unable to `di` in production mode

Hi, thank you for this amazing work!

I want to do dependency injection on production mode (as for mocking data, for now), and i have added these lines in my .babelrc

plugins: [
    ["react-magnetic-di/babel-plugin", { "forceEnable": true }],
  ],

the problem was in production mode, di was not applied. And if it helps, i cannot import things from react-magnetic-di even after i put those line above in my .babelrc.

is there something i did miss?

Thank you.

Add option to strip code on production build

Currently, even on prod builds, we have variable assignment and one function call (a pass through). We can optionally strip the entire di(...) so the performance cost in prod is actually zero.

Is it possible to update an injectable, i.e. make it dynamic?

Hi, I encountered a specific scenario in which I need to make a prop provided by the mocked hook dynamic, here is the code:

  passedProps: SelectRoomPageUIProps & { selectResort: ResortComparisonDropdownProps },
) => {
  const [selectedPropertyId, setSelectedPropertyId] = React.useState('2');
  const [selectedCardId, setRoomRateCardId] = React.useState('2');

  const onChange = useCallback(
    (id: any) => {
      setSelectedPropertyId(id);
    },
    [setSelectedPropertyId],
  );

  const useResortControllerDi = injectable(useResortController, () => ({
    apolloResult: {
      loading: false,
    } as ApolloResult,
    uiProps: {
      ...passedProps.selectResort,
      selectedPropertyId,
      onChange: onChange,
    },
  }));

  return (
    <DiProvider use={[useResortControllerDi]}>
      <SelectRoomPageUI
        {...passedProps}
      />
    </DiProvider>
  );
};

As you can notice the value of selectedPropertyId is dynamic, the reason I ended up needing to mock the hook is because there's a component nested under SelectRoomPageUI that consumes from the hook directly(the idea was to avoid prop drilling), thanks to your library I fixed it and the story now renders the list of values being mocked properly on the nested component, but when I trigger the onChange method it does update and if I use useEffect to watch the selectedPropertyId value it reflects it properly on the console but the value returned by useResortControllerDi never updates.

If I'm doing this wrong please feel free to correct me, and thanks again for making this library it saved me a lot of time of r&d.

missing React hooks exemption for exhaustive-inject

The rule contains a few react hooks - useState, useContext, useReducer - but not any other.

https://github.com/albertogasparin/react-magnetic-di/blob/master/src/eslint/rules/exhaustive-inject.js#L16

Is it a feature or a bug? I can understand if someone wants to override useEffect, but am a little concerned about useRef
In fact useState is overridden the most.

However adding any exemptions to the rule may cause friction to the testing process as something you might expect to be overridden will not be.

Is it possible to use di() in parent components only?

Hi there, loving this package, thank you! I have a question - am I right in assuming this sentence here means that you can make the di() call in a parent component and it will replace all usages in child components?

In the example above we replace all Modal and useQuery dependencies across all components in the tree with the custom versions.

I'm trying to create a wrapper component for storybook so that all the di code can be contained in the story itself, and am testing this in a wrapper component, but am thinking I might've misinterpreted the sentence above. For example, this is my storybook for the component.

// Header.stories.jsx
import { useState } from "react";
import { DiProvider, injectable } from "react-magnetic-di";
import Header from "./Header";

const useStateDi = injectable(useState, () => ["hello", () => null]);

export default {
  title: "My Components/Header",
  component: Header,
};

const Template = () => (
  <DiProvider use={[useStateDi]}>
    <Header />
  </DiProvider>
);

export const Default = Template.bind({});

And then, in my actual component, if I'm looking to replace useState for example, can I call di(useState) in a component that wraps the actual useState usage?

import { useState } from "react";
import { di } from "react-magnetic-di";

const ChildComponent = () => {
  const [title, _setTitle] = useState("Hi there");
  return <h2>{title}</h2>;
};

const Header = () => {
  di(useState);
  return <ChildComponent />;
};

export default Header;

Issues with CRA and TypeScript

Hi Alberto,

thanks for the project, looks very cool!
I was trying it out on a React app bootstrapped with the TypeScript template and when I import the library like so:

import { di } from "react-magnetic-di/macro";

I get: Cannot find module 'react-magnetic-di/macro' or its corresponding type declarations.

I guess it makes sense since they seem to be missing when you import it like that.

Anyway, using the library with TS doesn't seems to be working properly for me.

For example if you include the doc example for testing with RTL it complains about missing required children prop (as per your types):

const { container } = render(<MyComponent />, {
    wrapper: (p) => <DiProvider use={[ModalOpenMock, useQueryMock]} {...p} />,
  });

Should be:

const { container } = render(<MyComponent />, {
    wrapper: (p: {children?: React.ReactNode}) => <DiProvider use={[ModalOpenMock, useQueryMock]}  children={p.children} />,
  });

It's just a documentation issue but will probably trip other users.

I wasn't able to run it in the end unfortunately but maybe because of the first error above that results in Warning: Seems like you are using react-magnetic-di without Babel plugin... if I import without /macro and Cannot find module 'react-magnetic-di/macro' from '/Users/XXXX/repos/repo/src/components/AdminMessages' if I add //@ts-ignore to it.

Thanks!

Unbound Magnetic from React

Magnetic is mostly used in a preconfigured testing environment with so many things expected to be "global". Having this in mind what would one think about adding alternative way to configure di - via global configuration, not ContextApi

That will allow using di for plain functions, not components/hooks. One can do (and doing) this today by testing such functions as hooks, which is not the correct way.

All required is to wire a configuration variable to https://github.com/albertogasparin/react-magnetic-di/blob/master/src/react/consumer.js#L14

Track used and unused mocks

There is a general problem with mocking - it can be only added.

  • Let's imagine you have a component doing something
  • You create a test for it...
    • and test fails
  • You add a mock
    • and test passes
  • Time goes by, component got refactored a few time
    • but the no longer required mock is still here

โ€“ A short story "How I met code erosion"


Desired goal: be notified about missing mocks as well as no longer required ones.

Proposal: implement tracking of injectable usage:

  • verify that every injected got di-ed.
    • Will reduce wtf-factor of missed di(if eslint by any reason is not working). Still happening once-twice a week in my team.
  • verify that some injected functions(only) got executed
    • is totally a jest responsibility, but magnetic should guide which injectables expected.toHaveBeenCalled and which are not.
    • not all injectables should be executed, there are cased for di-ing something on environment level (intl), or some tests where stuff is excepted not to be called.

I am not sure that Magnetic can do what is required due to the need of integration with the test runner, or at least with stubs (aka jest.fn). Thus desired goal might be achievable only by building an abstraction on top of Magnetic.

Deciding what should be build inside Magnetic and what should be outside - is the task for this issue.

DiProvider prop-types does not support forwardRef replacement

Issue

The DiProvider prop-types use definition prevents forward refs from being used, and thus produces a warning when using the provided withDi utility. This issue is more problematic in environments that treat any console warnings as errors.

Steps to reproduce the issue

  1. Create a component that injects a dependency where its value utilises a forward ref, particularly from the provided withDi utility
| src/app/examples.js

export const DefaultApp = withDi(App, [injectable(Foo, DefaultFoo)]);
| src/foo/examples.js

// DefaultFoo will produce the warning
export const DefaultFoo = withDi(Foo, [
  injectable(Container, ({ children }) => <div>injected {children}</div>)
]);

// or: export const DefaultFoo = withDi(Foo);

Reproducible example here: react-magnetic-di-sandbox

What's the expected result?

  • The component is injected correctly, with no prop-types warnings

What's the actual result?

  • The component is injected correctly, with a prop-type warning:

Warning: Failed prop type: Invalid prop use[0] of type object supplied to DiProvider, expected function.
at DiProvider (https://yh3sc.csb.app/node_modules/react-magnetic-di/lib/esm/react/provider.js:29:23)
at withDi(App)

Enzyme `wrappingComponent` doesn't work

This recommended form for enzyme doesn't actually work for some reason:

  const container = mount(<MyComponent />, {
    wrappingComponent: DiProvider,
    wrappingComponentProps: { use: [ModalOpen, useQuery] },
  });

No errors, the context is just not provided.

Works okay if you do this though:

const container = mount(
  <DiProvider use={[ModalOpen, useQuery]}>
    <MyComponent />
  </DiProvider>
);

Babel plugin cannot be found

The babel plugin 'cannot be found' at the moment if using react-magnetic-di/babel in babel config.

Workaround: reference the full babel plugin path:

  // ... other stuff like presets
  plugins: [
    // ... other plugins
    'react-magnetic-di/lib/cjs/babel',
  ],

Add support for `react-test-renderer`

Dependency injection doesn't work properly when a component is being rendered via react-test-renderer:

import React, { useState } from 'react';
import { create } from 'react-test-renderer';
import { DiProvider, injectable } from 'react-magnetic-di';

import { Input, useTheme } from '../input';

const useThemeDi = injectable(useTheme, () => {
  return useState({ color: '#B00' });
});

describe('Input', () => {
    it('should render with theme', () => {
      const tree = create(
        <DiProvider use={[useThemeDi]}>
          <Input />
        </DiProvider>
      ).toJSON();
      expect(tree).toMatchInlineSnapshot(`
        <input
          placeholder="Type..."
          style={
            {
              "border": "1px solid #777", // <-- Wrong, should be #B00
            }
          }
        />
      `);
    });
  });
});

Supposedly due to how react-test-renderer's create works, the di consumer does not get a value from a Provider in

const { getDependencies = (v) => v } = Context._currentValue || {};
and ends up using a default implementation from
getDependencies(deps) {
return deps;
},

Replacing const { getDependencies = (v) => v } = Context._currentValue || {} with const { getDependencies = (v) => v } = useContext(Context) works, but breaks the ability to use di in both functional and class components.

Would you consider a major version release to remove the hack in di function and providing di and useDi separately?

Error: "Invalid hook call. Hooks can only be called inside of the body of a function component."

I'm seeing the following error whenever I use <DiProvider /> inside jest:

Invalid hook call. Hooks can only be called inside of the body of a function component.

Screen Shot 2021-11-16 at 5 02 59 PM

Screen Shot 2021-11-16 at 5 04 52 PM

I thought I was doing something wrong but I tried this by building an MVP:

// jest spec
// SomeTest.spec.js
import { useState } from 'react'
import { DiProvider, di, injectable } from 'react-magnetic-di'
import { render } from '@testing-library/react'
import '@testing-library/jest-dom'

function useSomeHook() {
  return useState('dummy')
}

function Testing() {
  di(useSomeHook)
  const [dummy] = useSomeHook()
  return <div>{dummy}</div>
}

const useSomeHookDi = injectable(useSomeHook, () => ['testing'])
function WithDep() {
  return (
    <DiProvider use={[useSomeHookDi]}>
      <Testing />
    </DiProvider>
  )
}

test('loads and displays input text', async () => {
  const { getByText } = render(<WithDep />)
  expect(() => getByText('testing')).not.toThrow()
})

eslint rule "react-magnetic-di/no-extraneous" unnecessarily injects Components provided via HOC

In this example Component is injected via HOC, but react-magnetic-di/no-extraneous eslint rule considers it to be injectable via DI

export const withNestingOptimisation = <Props: { children?: Node, ... }, Ref>(
    Component: AbstractComponent<Props, Ref>,
) => {
    const WithNestingOptimisation = forwardRef<Props, Ref>(
        ({ children = null, ...rest }: Props, ref) => {
            // "Component" here should not be DI'ed
            di(Component, useShouldNestedElementRender);

            return true ? (
                <Component {...rest} ref={ref}>
                    {children}
                </Component>
            ) : (
                children
            );
        },
    );

    return WithNestingOptimisation;
};

Is storybook 7 supported?

I have a sneaky suspicion that I might be doing something wrong, but I copy/pasted the examples on the readme and shooting console.log(debug(ModalOpenDi)); seems to always come back with undefined. I am using react native and storybook 7, so I wonder if maybe my setup is not supported somehow?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.