Skip to content

Commit

Permalink
feat: deep search for parent with string support
Browse files Browse the repository at this point in the history
  • Loading branch information
Filipoliko committed Feb 10, 2025
1 parent d209b5f commit 126ae9f
Show file tree
Hide file tree
Showing 12 changed files with 321 additions and 137 deletions.
5 changes: 5 additions & 0 deletions .changeset/gold-colts-give.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"shallow-react-snapshot": minor
---

**Breaking Change!** Throw an error when unable to find React fiber or internal instance of the root component instead of just silently returning `null`. It seemed like an unrealistic scenario, but it can happen if there is no react component rendered into JSDOM. It is probably a user error, but it is better to inform the user about the issue than to let them wonder why the snapshot is empty.
5 changes: 5 additions & 0 deletions .changeset/pretty-fireants-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"shallow-react-snapshot": patch
---

RootReactComponent can be a string now! You can use `shallow(container, 'MyComponent')` and as long as the displayName, function name, or class name is matching, it will work. This might be more useful than it seems at first. Some implementations of HOC components extend your component instead of wrapping it. In this case the displayName might be the only way how to check the shallow representation of the component you actually want to test.
5 changes: 5 additions & 0 deletions .changeset/pretty-tips-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"shallow-react-snapshot": minor
---

Simplified the usage of shallow. **You can now write `shallow(container, MyComponent)` instead of `shallow(container.firstChild, MyComponent)`**. Both will still work, but it is always nice to write less code for the same value. This has been achieved by improving the internal logic of the shallow function to search also the children of the provided HTML element. This change should not break your tests, but some use-cases that were previously throwing an error might start working with this update.
5 changes: 5 additions & 0 deletions .changeset/tidy-timers-destroy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"shallow-react-snapshot": patch
---

Reorganized some repeating type definitinos
5 changes: 5 additions & 0 deletions .changeset/unlucky-seahorses-shave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"shallow-react-snapshot": patch
---

More informative error message when unable to find RootReactComponent
94 changes: 63 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,55 +28,87 @@ This library officially supports React 16 and newer.

## Usage

```typescript
function shallow(rootElement: Element | null, RootReactComponent: ReactComponent | string): ReactTestChild | null
```

- `rootElement` - The root element of the rendered component. Typically `container` from `render` function of React Testing Library.
- `RootReactComponent` - Most likely the component you are testing. It can be a React component or its name as a string.
- Returns snapshot friendly shallow structure of the component.

**Example**

```javascript
import { shallow } from "shallow-react-snapshot";
import { render } from "@testing-library/react";
function MyComponent() {
return (
<div>
<MyNestedComponent data-testid="nested">
<span>Text</span>
</MyNestedComponent>
</div>
<MyNestedComponent data-testid="nested">
<span>Text</span>
</MyNestedComponent>
);
}
function MyNestedComponent({ children, ...props }) {
return (
<div>
<div>
<div>
<div {...props}>{children || null}</div>
</div>
</div>
</div>
);
return <div {...props}>{children || null}</div>;
}
test("Render", () => {
test("Render MyComponent", () => {
const { container } = render(<MyComponent />);
expect(shallow(container.firstChild, MyComponent)).toMatchSnapshot();
// Typical use-case
expect(shallow(container, MyComponent)).toMatchSnapshot();
// You can also use the string representation of the component to get the same result
expect(shallow(container, "MyComponent")).toMatchSnapshot();
// Result:
// <MyNestedComponent
// data-testid="nested"
// >
// <span>
// Text
// </span>
// </MyNestedComponent>
});
```

This results in following jest snapshot.
### HOC Components

```
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Render 1`] = `
<div>
<MyNestedComponent
data-testid="nested"
>
<span>
Text
</span>
</MyNestedComponent>
</div>
`;
When you are testing components with higher-order components (HOC), you might run into some issues. The snapshot of the rendered component will contain the HOC component, which is probably not what you want to test. You want to test the original component without the HOC. With Enzyme shallow, this would not be possible, but with Shallow React Snapshot, you can easily achieve this.

```javascript
function withHOC(Component) {
return function HOC(props) {
return <Component {...props} />;
};
}
test("Render MyComponentWithHOC", () => {
const MyComponentWithHOC = withHOC(MyComponent);
const { container } = render(<MyComponentWithHOC />);
// This might not be what you want to test
expect(shallow(container, MyComponentWithHOC)).toMatchSnapshot(); // Wrong!
// Result:
// <MyComponent />
// You probably want to check the insides of the original component without HOC
// You can either pass the original component into shallow
expect(shallow(container, MyComponent)).toMatchSnapshot();
// Or you can use the string representation of the component if you don't have the reference to the original component
expect(shallow(container, "MyComponent")).toMatchSnapshot();
// Result:
// <MyNestedComponent
// data-testid="nested"
// >
// <span>
// Text
// </span>
// </MyNestedComponent>
});
```

## Contributing
Expand Down
1 change: 1 addition & 0 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"noExplicitAny": "off"
},
"complexity": {
"noBannedTypes": "off",
"noUselessFragments": "off"
}
}
Expand Down
8 changes: 8 additions & 0 deletions src/__tests__/__snapshots__/class.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Class component render HOC component with shallow render of the wrapped component 1`] = `
<div
id="MyComponent"
/>
`;

exports[`Class component render direct StrictMode wrapper around react component with a single child element 1`] = `
<StrictMode>
<MyComponent>
Expand Down Expand Up @@ -149,6 +155,8 @@ exports[`Class component render react component with portal 1`] = `
</MyComponent>
`;

exports[`Class component render react component with string component argument 1`] = `<MyComponent />`;

exports[`Class component render react component with with another react component and native component as a child 1`] = `
<MyComponent>
<div />
Expand Down
10 changes: 10 additions & 0 deletions src/__tests__/__snapshots__/functional.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Functional component render HOC component with shallow render of the wrapped component 1`] = `
<div
id="MyComponent"
/>
`;

exports[`Functional component render direct StrictMode wrapper around react component with a single child element 1`] = `
<StrictMode>
<MyComponent>
Expand Down Expand Up @@ -174,6 +180,8 @@ exports[`Functional component render react component with deeply nested multiple
</MyComponent>
`;

exports[`Functional component render react component with displayName and string component argument 1`] = `<MyComponent />`;

exports[`Functional component render react component with portal 1`] = `
<MyComponent>
<Portal>
Expand All @@ -184,6 +192,8 @@ exports[`Functional component render react component with portal 1`] = `
</MyComponent>
`;

exports[`Functional component render react component with string component argument 1`] = `<MyComponent />`;

exports[`Functional component render wrapped in React.forwardRef 1`] = `
<MyComponent>
Hello World One
Expand Down
Loading

0 comments on commit 126ae9f

Please sign in to comment.