What is the equivalent of toJSON in testing-library/react? - reactjs

In #testing-library/react-native I have a method provided by render called toJSON which I use to compare the resulting output
const { toJSON } = render(<HocText>simple string</HocText>);
const { toJSON: expectedToJSON } = render(<Text>simple string</Text>);
expect(toJSON()).toStrictEqual(expectedToJSON());
I am trying to find the equivalent of it in #testing-library/react.
I tried
const { asFragment } = render(<HocMyComponent text="should be as is" />);
const { asFragment: expectedAsFragment } = render(<span>should be as is</span>);
expect(asFragment()).toStrictEqual(expectedAsFragment());
But the result was a failure because it is missing my data.

asFragment was the correct one.
It as actually a bug on the test. But it was a bit hard to see so I had to do a serialization first.
type MyComponentProps = { text: string }
function MyComponent({ text }: MyComponentProps) {
return <span data-testid="my-element">{text}</span>
}
const MyComponentWithRef = forwardRef<HTMLSpanElement, MyComponentProps>(({ text }, ref) => <span data-testid="my-ref-element" ref={ref}>{text}</span>)
describe("hoc", () => {
it("simple component should work as expected", async () => {
render(<MyComponent text="bar" />);
expect(screen.getByTestId("my-element")).toHaveTextContent("bar");
})
it("should work with simple component", () => {
const serializer = new XMLSerializer();
const HocMyComponent = withHoc<MyComponentProps, MyComponentProps, HTMLSpanElement>(MyComponent);
const { asFragment } = render(<HocMyComponent text="should be as is" />);
expect(screen.getByTestId("my-element")).toHaveTextContent("should be as is");
const renderedValue = serializer.serializeToString(asFragment());
const { asFragment: expectedAsFragment } = render(<span data-testid="my-element">should be as is</span>);
expect(renderedValue).toStrictEqual(serializer.serializeToString(expectedAsFragment()));
});
But once I figured out what was wrong in the test. The resulting test is
it("should work with simple component", () => {
const HocMyComponent = withHoc<MyComponentProps, MyComponentProps, HTMLSpanElement>(MyComponent);
const { asFragment } = render(<HocMyComponent text="should be as is" />);
expect(screen.getByTestId("my-element")).toHaveTextContent("should be as is");
const { asFragment: expectedAsFragment } = render(<span data-testid="my-element">should be as is</span>);
expect(asFragment()).toStrictEqual(expectedAsFragment());
});

Related

Why does my React Context runs twice, and returns `undefined` first before the expect output?

I'm trying to use React context to decide whether to have a button or not, and the problem is my context always runs twice and returns the first value as undefined -- the second time the output is correct.
Context:
const defaultMyContextValue = null;
export const myContext = createContext<GenericResponseFromEndpoint | null>(
defaultMyContextValue
);
export const fetcher = async (
endpoint: 'an_endpoint',
id: string
) => {
const client = new Client().ns('abcdef');
try {
return await client.rpc(
endpoint,
{
id
},
{}
);
} catch (err) {
// log error
return undefined;
}
};
export const MyContextProvider: React.FC = ({children}) => {
const context = useAnotherContext();
const {id} = useMatch();
const fetchMyContext = useCallback(
(endpoint: 'an_endppoint', id: string) =>
fetcher(endpoint, id),
[id]
);
const {data = null} = useSWR(
context?.name && ['an_endpoint', id],
fetchMyContext
);
return <myContext.Provider value={data}>{children}</myContext.Provider>;
};
export const useMyContext = () => {
const context = useContext(myContext);
if (context === undefined) {
throw new Error('------------------------');
}
return context;
};
Conditional render:
export const contextElement: React.FC = () => {
const info = useMYContext()?.info;
if (!info) {
return null;
// console.log('null');
}
console.log('not null');
// when i run it, it always returns two output scenarios:
// undefined - undefined
// undefine - expected value
// the front-end works as expected
return (
<div className="some-class-name">
<Button
variant="primary"
data-testid="button-123"
onClick={() => window.open('_blank')}
>'Do Something',
})}
</Button>
</div>
);
};
Test: my tests failed and the error is the system couldn't find the element. If I set the style={display:none} instead of returning null in the above conditional rendering -- it passes
describe('contextElement', () => {
const renderSomething = () => render(<contextElement />);
afterEach(() => {
myMock.mockClear();
});
it('renders button', () => {
myMock.mockResolvedValue({
info: 'some info'
});
const {getByTestId} = renderSomething();
expect(getByTestId('button-123')).toBeTruthy();
});
it('does not render button', () => {
myMock.mockResolvedValue(undefined);
const {getByTestId} = renderSomething();
expect(getByTestId('button-123')).not.toBeTruthy();
});
});
It's a really long post I know. Thanks for making it till the end. And yes, I check and there's no strictMode in my set-up. Thank you.

Why is this testing in React not working?

I wanted to test that a count increases whenever I clicked on a button, but it seems not to work. Please Help! Here is the code...
describe('checkbtn', () => {
it('onClick', () => {
const { queryByTitle } = render(<Counter />);
const { queryByTitle } = render(<Counter />);
const btn = queryByTitle('button1');
const count = queryByTitle('count');
expect(count.innerHTML).toBe(count.innerHTML);
fireEvent.click(btn);
expect(count.innerHTML).toBe(count.innerHTML + 1);
})
})
First of all you expect some state to equal the same state + 1 here:
expect(count.innerHTML).toBe(count.innerHTML + 1);
It's the same as to write
const x = 2;
expect(x).toBe(x+2)
Second is that you try to add number to string which will result in not what you expect.
What you should do is to write explicit values in your test:
describe('checkbtn', () => {
it('onClick', () => {
const { queryByTitle } = render(<Counter />);
const btn = queryByTitle('button1');
const count = queryByTitle('count');
expect(count.innerHTML).toBe('1');
fireEvent.click(btn);
expect(count.innerHTML).toBe('2');
})
})

React + Jest: Test that setTimeOut didn't get called

I currently have a setTimeOut inside a useEffect, I was able to test it by using:
jest.useFakeTimers();
jest.advanceTimersByTime(5000);
And it works great. However, the setTimeout is getting triggered a few times as successMessage changes. How can I write a test that checks that setTimeOut is only called when successMessage is not an empty string?
I'm using jest and react-testing-library.
Here is my react code.
enum HELPER_MESSAGES {
SUCCESS = 'Congratulations you signed up!',
ERROR = 'Email address invalid',
}
export function EmailCapture (): ReactElement {
const [inputValue, setInputValue] = useState<string>('');
const [errorMessage, setErrorMessage] = useState<string>('');
const [successMessage, setSuccessMessage] = useState<string>('');
function handleOnClick (): void {
const EMAIL_REGEX: RegExp = /^[a-zA-Z0-9]+([-._][a-zA-Z0-9]+)*#([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*.)+\.+[a-zA-Z]{2,}$/;
const isInputValid: boolean = EMAIL_REGEX.test(inputValue) && inputValue !== '';
if (isInputValid) {
setSuccessMessage(HELPER_MESSAGES.SUCCESS);
} else {
setErrorMessage(HELPER_MESSAGES.ERROR);
}
}
useEffect((): () => void => {
const resetAfterSuccessTimer: NodeJS.Timeout = setTimeout((): void => {
setSuccessMessage('');
}, 5000);
return (): void => {
clearTimeout(resetAfterSuccessTimer);
};
}, [successMessage]);
function handleOnChange (e: ChangeEvent<HTMLInputElement>): void {
setInputValue(e.target.value);
setErrorMessage('');
setSuccessMessage('');
}
return (
<TextInput
onChange={handleOnChange}
error={errorMessage}
success={successMessage}
value={inputValue}
/>
);
}
I'm following TDD, so I'm looking to write a test that will make me write the following code:
useEffect((): () => void => {
let resetAfterSuccessTimer: NodeJS.Timeout;
if (successMessage) {
resetAfterSuccessTimer= setTimeout((): void => {
setSuccessMessage('');
setButtonText('Sign up!');
console.log('sexy');
}, 5000);
}
return (): void => {
clearTimeout(resetAfterSuccessTimer);
};
}, [successMessage]);
The sexy word should only get console logged once.
Figured it out based on their documentation: https://jestjs.io/docs/timer-mocks , you can just do the following:
expect(setTimeout).not.toHaveBeenCalled();
And when you want to check that it got called once, you can do:
expect(setTimeout).toHaveBeenCalledTimes(1);
Note: This is testing an implementation detail, so be aware of that.

Convert UseRef to React Hooks

I am trying to convert my component to react hooks.
class AppMenu extends Component<Props> {
menuRef = null;
initMenu() {
if (this.props.mode === 'horizontal') {
const menuRef = new MetisMenu('#menu-bar').on('shown.metisMenu', event => {
const menuClick = e => {
if (!event.target.contains(e.target)) {
menuRef.hide(event.detail.shownElement);
}
};
window.addEventListener('click', menuClick);
});
this.menuRef = menuRef;
} else {
this.menuRef = new MetisMenu('#menu-bar');
}
}
}
How do I convert this to hooks? Especially the menuRef part.
I did const menuRef = useRef(null);, but how do I convert the this.menuRef = new MetisMenu('#menu-bar'); to useRef? I tried menuRef.current = new MetisMenu('#menu-bar'); but it throws an error.
Am I doing this the incorrect way?
Thanks
Here is what you need to do:
function AppMenu(props: Props) {
const menuRef = useRef(null);
function initMenu() {
if (props.mode === "horizontal") {
menuRef.current = new MetisMenu("#menu-bar").on(
"shown.metisMenu",
(event) => {
const menuClick = (e) => {
if (!event.target.contains(e.target)) {
menuRef.hide(event.detail.shownElement);
}
};
window.addEventListener("click", menuClick);
}
);
} else {
menuRef.current = new MetisMenu("#menu-bar");
}
}
useEffect(() => {
initMenu();
}, []);
}

Jest mocking a function with hooks fails

I'm using nextJs and jest enzyme for testing of component. I have a component file like below, where I get items array from props and loop to it calling setRef inside useEffect.
[itemRefs, setItemRefs] = React.useState([]);
setRef = () => {
const refArr = [];
items.forEach(item => {
const setItemRef = RefItem => {
refArr.push(RefItem)
}
if(item && item.rendItem){
item.rendItem = item.rendItem.bind(null, setItemRef);
}
}
setItemRefs(refArr);
}
React.useEffect(() => {
setRef();
},[])
My test file is like below :
const items = [
{
original: 'mock-picture-link',
thumbnail: 'mock-thumb-link',
renderItem: jest.fn().mockReturnValue({ props: { children: {} } })
}
];
beforeEach(() => {
setItemRefs = jest.fn(),
jest.spyOn(React, 'useEffect').mockImplementationOnce(fn => fn());
})
it('mocks item references', ()=>{
jest.spyOn(React, 'useState').mockImplementationOnce(() => [items, setItemRefs]);
wrapper = shallow(<Component />).dive();
expect(setItemRefs).toHaveBeenCalledWith(items);
})
The test case fails with expected as items array BUT received a blank array. The console.log inside if(item && item.rendItem) works but console.log inside const setItemRef = RefItem => {... doesn't work I doubt the .bind is not getting mocked in jest.
Any help would be fine.

Resources