Skip to content

Commit 35b159e

Browse files
authored
feat: Now supports the mocking of legacy Class based components
Support class components
2 parents ec34b6c + c3ddac9 commit 35b159e

File tree

6 files changed

+87
-33
lines changed

6 files changed

+87
-33
lines changed

package-lock.json

Lines changed: 11 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@
4848
"typescript": "^4.5.4"
4949
},
5050
"peerDependencies": {
51+
"@testing-library/react": "*",
5152
"react": "*",
52-
"react-dom": "*",
53-
"@testing-library/react": "*"
53+
"react-dom": "*"
5454
},
5555
"jest": {
5656
"testEnvironment": "jest-environment-jsdom",
@@ -64,7 +64,9 @@
6464
]
6565
},
6666
"release": {
67-
"branches": ["master"]
67+
"branches": [
68+
"master"
69+
]
6870
},
6971
"bugs": {
7072
"url": "https://github.com/sourceallies/rtl-mock-component/issues"

src/class-components.spec.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import React from 'react';
2+
import { render } from '@testing-library/react';
3+
import { setupMockComponent, getByMockComponent } from './index';
4+
import MockedClassComponent from './test-components/MockedClassComponent';
5+
6+
jest.mock('./test-components/MockedClassComponent');
7+
8+
const TestedComponent = ({value}: {value: string}) => (
9+
<div>
10+
<MockedClassComponent value={value}/>
11+
</div>
12+
);
13+
14+
beforeEach(() => {
15+
setupMockComponent(MockedClassComponent);
16+
});
17+
18+
it('should render a div', () => {
19+
render(<TestedComponent value='val' />);
20+
21+
// @ts-ignore
22+
expect(getByMockComponent(MockedClassComponent).tagName).toEqual('DIV');
23+
});

src/happy-path.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { buildQueries, getByRole, render, screen, within } from '@testing-library/react';
1+
import { render, screen } from '@testing-library/react';
22
import React, { useEffect, useState } from 'react';
33
import { setupMockComponent, getByMockComponent } from './index';
44
import { findAllByMockComponent, findByMockComponent, getAllByMockComponent, queryAllByMockComponent, queryByMockComponent } from './queries';

src/setupMockComponent.tsx

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import React, { ReactHTML, RefCallback, useEffect, useRef } from "react";
1+
import React, { Component, ReactHTML, RefCallback, useEffect, useRef } from "react";
22
import { FC } from "react";
33

4-
export type MockedComponent<PropType> = jest.MockedFunction<FC<PropType>>;
4+
export type MockedComponent<PropType> = jest.MockedFunction<FC<PropType>> | jest.Mock<Component<PropType>>;
55

66
export type MockedElement<PropType = {}> = HTMLElement & {
77
props: PropType,
@@ -10,7 +10,9 @@ export type MockedElement<PropType = {}> = HTMLElement & {
1010

1111
export const mockElementTestId = 'rtl-mock-element';
1212

13-
function ensureIsMock<PropType>(mockedComponent: FC<PropType>) {
13+
type FunctionOrClassComponent<PropType> = FC<PropType> | (new (props: PropType) => Component<PropType>);
14+
15+
function ensureIsMock<PropType>(mockedComponent: FunctionOrClassComponent<PropType>) {
1416
if (!jest.isMockFunction(mockedComponent)) {
1517
throw new Error(`${mockedComponent.name} cannot be setup because it is not a Jest mock. Call "jest.mock('path/to/component')" first`);
1618
}
@@ -23,10 +25,8 @@ export interface MockComponentOptions {
2325
element?: keyof ReactHTML;
2426
}
2527

26-
export function setupMockComponent<PropType>(mockedComponent: FC<PropType>, options?: MockComponentOptions) {
27-
ensureIsMock(mockedComponent);
28-
29-
const mockImplementation: FC<PropType> = (props) => {
28+
function createMockComponent<PropType>(mockedComponent: MockedComponent<PropType>, options?: MockComponentOptions): FC<PropType> {
29+
const MockImplementation: FC<PropType> = (props) => {
3030
const mockedElmentRef = useRef<MockedElement<PropType> | null>(null);
3131
useEffect(() => {
3232
if (mockedElmentRef.current) {
@@ -37,7 +37,7 @@ export function setupMockComponent<PropType>(mockedComponent: FC<PropType>, opti
3737
const ref: RefCallback<HTMLDivElement> = (el) => {
3838
mockedElmentRef.current = el as unknown as MockedElement<PropType>;
3939
if (mockedElmentRef.current) {
40-
mockedElmentRef.current.component = comp;
40+
mockedElmentRef.current.component = mockedComponent; //TODO: fix me
4141
}
4242
};
4343

@@ -47,7 +47,29 @@ export function setupMockComponent<PropType>(mockedComponent: FC<PropType>, opti
4747
'data-testid': mockElementTestId,
4848
}, props.children);
4949
};
50+
return MockImplementation;
51+
}
5052

51-
const comp = mockedComponent as MockedComponent<PropType>;
52-
comp.mockImplementation(mockImplementation);
53-
}
53+
export function setupMockComponent<PropType>(mockedComponent: FunctionOrClassComponent<PropType>, options?: MockComponentOptions) {
54+
ensureIsMock(mockedComponent);
55+
const MockImplementation = createMockComponent(mockedComponent as MockedComponent<PropType>, options);
56+
if (isClassComponent(mockedComponent)) {
57+
setupClassComponent(MockImplementation, mockedComponent as jest.Mock<Component<PropType>>);
58+
} else {
59+
const mockFunctionComponent = mockedComponent as jest.MockedFunction<FC<PropType>>;
60+
mockFunctionComponent.mockImplementation(MockImplementation);
61+
}
62+
}
63+
64+
function setupClassComponent<PropType>(MockImplementation: FC<PropType>, mockedComponent: jest.Mock<Component<PropType>>) {
65+
class MockClassComponentWrapper extends Component<PropType> {
66+
render() {
67+
return <MockImplementation {...this.props} />;
68+
}
69+
}
70+
mockedComponent.mockImplementation((props: PropType) => new MockClassComponentWrapper(props));
71+
}
72+
73+
function isClassComponent<PropType>(mockedComponent: FunctionOrClassComponent<PropType>) {
74+
return 'isReactComponent' in mockedComponent.prototype;
75+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Component } from "react";
2+
3+
export interface MockedClassProps {
4+
value?: string;
5+
}
6+
7+
class MockedClassComponent extends Component<MockedClassProps> {
8+
constructor(props: MockedClassProps) {
9+
super(props);
10+
throw new Error('MockedClassComponent was rendered');
11+
}
12+
};
13+
14+
export default MockedClassComponent;

0 commit comments

Comments
 (0)