I am writing my first test case but facing following error:
jest.doMock is not a function
May be I am not doing hook mock in right way.
I am sharing my code, please go through this and let me know if you guys found anything wrong with code.
useCustomHook.js
export const useCustomHook = () => {
return {
val: "abcd"
};
}
App.js
import "./styles.css";
import { useCustomHook } from "./useCustomHook";
export default function App() {
const { val } = useCustomHook();
return (
<div className="App" data-testid="hi">
{val}
</div>
);
}
demoTest.spec.js
import { render } from "#testing-library/react";
import "#testing-library/jest-dom/extend-expect";
import App from "../App";
jest.doMock("../useCustomHook", () => {
return {
useCustomHook: () => {
return { val: "mg" };
}
};
});
describe("demo test describe", () => {
it("demo it", async () => {
const { getByTestId } = render(<App />);
const n = await getByTestId("hi");
expect(n).toHaveTextContent("mg");
});
});
Related
I am using PrimeReact's toast component, whose API looks like this:
function App() {
const toast = useRef(null);
useEffect(() => {
toast.current.show({
severity: 'info',
detail: 'Hellope'
});
});
return (
<div className='App'>
<Toast ref={toast} />
</div>
);
}
I would now like to call toast.current.show() from a non-React context. In particular, I have an http() utility function through which all HTTP calls are made. Whenever one fails, I would like to show a toast. What are clean/idiomatic ways to achieve this?
Initialize the toast on the window object.
useLayoutEffect(() => {
window.PrimeToast = toast.current || {};
}, []);
On your fetch or axios handler, use the above object on your error handler
const fakeUrl = "https://api.afakeurl.com/hello";
fetch(fakeUrl)
.then((res) => res.data)
.catch((err) => {
console.error("error fetching request", err);
if (window.PrimeToast) {
window.PrimeToast.show({
severity: "error",
summary: "Error calling https",
detail: "hello"
});
}
});
Updated Sandbox
Reference:
https://www.primefaces.org/primereact/toast/
I would create a toast context that would allow showing toasts
toast-context.js
import "primereact/resources/themes/lara-light-indigo/theme.css";
import "primereact/resources/primereact.css";
import { Toast } from "primereact/toast";
import { createContext, useContext, useRef } from "react";
// create context
const ToastContext = createContext(undefined);
// wrap context provider to add functionality
export const ToastContextProvider = ({ children }) => {
const toastRef = useRef(null);
const showToast = (options) => {
if (!toastRef.current) return;
toastRef.current.show(options);
};
return (
<ToastContext.Provider value={{ showToast }}>
<Toast ref={toastRef} />
<div>{children}</div>
</ToastContext.Provider>
);
};
export const useToastContext = () => {
const context = useContext(ToastContext);
if (!context) {
throw new Error(
"useToastContext have to be used within ToastContextProvider"
);
}
return context;
};
index.js
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
import { ToastContextProvider } from "./toast-context";
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(
<StrictMode>
<ToastContextProvider>
<App />
</ToastContextProvider>
</StrictMode>
);
App.js
import { useToastContext } from "./toast-context";
export default function App() {
// use context to get the showToast function
const { showToast } = useToastContext();
const handleClick = () => {
http(showToast);
};
return (
<div className="App">
<button onClick={handleClick}>show toast</button>
</div>
);
}
// pass showToast callback to your http function
function http(showToast) {
showToast({
severity: "success",
summary: "Success Message",
detail: "Order submitted"
});
}
Codesanbox example: https://codesandbox.io/s/beautiful-cray-rzrfne?file=/src/App.js
Here is one solution I have been experimenting with, although I have the impression it isn't very idiomatic. I suppose one could look at it as a "micro-frontend" responsible exclusively for showing toasts.
import ReactDOM from 'react-dom/client';
import { RefObject, useRef } from 'react';
import { Toast, ToastMessage } from 'primereact/toast';
class NotificationService {
private toast?: RefObject<Toast>;
constructor() {
const toastAppRoot = document.createElement('div');
document.body.append(toastAppRoot);
const ToastApp = () => {
this.toast = useRef<Toast>(null);
return <Toast ref={this.toast} />;
};
ReactDOM.createRoot(toastAppRoot).render(<ToastApp />);
}
showToast(message: ToastMessage) {
this.toast!.current!.show(message);
}
}
export const notificationService = new NotificationService();
The simplicity of its usage is what's really nice of an approach like this. Import the service, call its method. It used to be that simple.
Can someone help me cover this test case, I am not able to figure out how to cover this inline function
Note: DropdownField is a wrapper component and contains the actual which is imported from
import { Field } from "redux-form";
dropdown input inside
I have tried to call mockfunction and jest.fn() but nothing works, Any help will be appreciated because I am totally blank at the moment. Thanks in advance to all the wonderful devs
import React from "react";
import DropdownField from "components/FormFields/DropdownField";
import get from "lodash/get";
const AddressLookup = props => {
const {
change,
formValues,
fetchAddressLookup,
postalCodeOptions,
type = "delivery",
placeholder = "type_to_search",
field
} = props;
const selectedDeliveryMethod = get(formValues, "delivery_method", {});
return (
<DropdownField
placeholder={placeholder}
options={postalCodeOptions}
{...selectedDeliveryMethod.fields.zip_code}
isSearchable={true}
field={field}
onInputChange={value => {
if (value.length >= 2) fetchAddressLookup({ q: value });
}}
onChange={({ value }) => {
const [city, state, zipCode] = value.split("-");
change(field, value);
change(`${type}_state`, state);
change(`${type}_city`, city);
change(`${type}_zip_code`, zipCode);
}}
/>
);
};
export default AddressLookup;
I have tried this approach but It failed to cover. First test case covers the UI part only as you can see it is matching to snapshot. In second test cases I removed some code and commented some because nothing works
import * as React from 'react';
import { render, fireEvent, wait } from '#testing-library/react';
import { IntlProvider } from 'react-intl';
import { Provider } from 'react-redux';
import { reduxForm } from 'redux-form';
import configureStore from 'redux-mock-store';
import messages from '__fixtures__/messages.json';
import AddressLookup from '../index';
const DecoratedAddressLookup = reduxForm({ form: 'testForm' })(AddressLookup);
const testProps = {
change: jest.fn(),
fetchAddressLookup: jest.fn(),
postalCodeOptions: [
{
name: 'abc-abcd-1234',
value: 'abc-abcd-1234',
},
],
formValues: {
delivery_method: {
fields: {
zip_code: 'BD-BDBD-1234',
},
},
},
field: 'zip_code',
};
describe('<AddressLookup />', () => {
let testStore;
let addressField;
const mockStore = configureStore([]);
const store = mockStore({});
const spy = jest.fn();
beforeAll(() => {
testStore = store;
});
const renderComponent = () => {
return render(
<Provider store={testStore}>
<IntlProvider locale='en' messages={messages}>
<DecoratedAddressLookup
{...testProps}
onInputChange={spy}
onChange={spy}
/>
</IntlProvider>
</Provider>
);
};
it('should render and match the snapshot', () => {
const {
getByTestId,
container: { firstChild },
} = renderComponent();
addressField = getByTestId('zip_code');
expect(firstChild).toMatchSnapshot();
});
it('should type a value', async () => {
addressField = addressField.querySelector('input');
// expect(addressField).toBeTruthy();
// console.log('addressField', addressField);
// const input = screen.getByTestId('add-word-input');
fireEvent.change(addressField, { target: { value: 'abc-abcd-1234' } });
expect(addressField).toHaveValue('abc-abcd-1234');
// expect(testProps.change).toBeCalled();
await wait(() => {
expect(spy).toHaveBeenCalledTimes(1);
});
});
});
I'm having trouble testing a page that has Context and useEffect using Jest and Testing-library, can you help me?
REPO: https://github.com/jefferson1104/padawan
My Context: src/context/personContext.tsx
import { createContext, ReactNode, useState } from 'react'
import { useRouter } from 'next/router'
import { api } from '../services/api'
type PersonData = {
name?: string
avatar?: string
}
type PersonProviderProps = {
children: ReactNode
}
type PersonContextData = {
person: PersonData
loading: boolean
handlePerson: () => void
}
export const PersonContext = createContext({} as PersonContextData)
export function PersonProvider({ children }: PersonProviderProps) {
const [person, setPerson] = useState<PersonData>({})
const [loading, setLoading] = useState(false)
const router = useRouter()
function checkAvatar(name: string): string {
return name === 'Darth Vader'
? '/img/darth-vader.png'
: '/img/luke-skywalker.png'
}
async function handlePerson() {
setLoading(true)
const promise1 = api.get('/1')
const promise2 = api.get('/4')
Promise.race([promise1, promise2]).then(function (values) {
const data = {
name: values.data.name,
avatar: checkAvatar(values.data.name)
}
setPerson(data)
setLoading(false)
router.push('/battlefield')
})
}
return (
<PersonContext.Provider value={{ person, handlePerson, loading }}>
{children}
</PersonContext.Provider>
)
}
My Page: src/pages/battlefield.tsx
import { useContext, useEffect } from 'react'
import { useRouter } from 'next/router'
import { PersonContext } from '../context/personContext'
import Person from '../components/Person'
const Battlefield = () => {
const { person } = useContext(PersonContext)
const router = useRouter()
useEffect(() => {
if (!person.name) {
router.push('/')
}
})
return <Person />
}
export default Battlefield
My Test: src/tests/pages/Battlefield.spec.tsx
import { render, screen } from '#testing-library/react'
import { PersonContext } from '../../context/personContext'
import Battlefield from '../../pages'
jest.mock('../../components/Person', () => {
return {
__esModule: true,
default: function mock() {
return <div data-test-id="person" />
}
}
})
describe('Battlefield page', () => {
it('renders correctly', () => {
const mockPerson = { name: 'Darth Vader', avatar: 'darth-vader.png' }
const mockHandlePerson = jest.fn()
const mockLoading = false
render(
<PersonContext.Provider
value={{
person: mockPerson,
handlePerson: mockHandlePerson,
loading: mockLoading
}}
>
<Battlefield />
</PersonContext.Provider>
)
expect(screen.getByTestId('person')).toBeInTheDocument()
})
})
PRINSCREEN ERROR
enter image description here
I found a solution:
The error was happening because the path where I call the Battlefield page didn't have the absolute path.
I have this problem, can anyone help me?
TypeError: customers.map is not a function.
I've always used it that way and I've never had any problems.
Its about data integration.
Basically is that, please anyone can help me?
import React, { useState, useEffect } from "react";
import { List, Card } from "antd";
import { data } from "../../../mocks/customers";
import { DeleteCustomerButton } from "#components/atoms/DeleteCustomerButton";
import { CustomersEditButton } from "#components/atoms/CustomersEditButton";
import { useContext } from "../../../contexts/context";
const { Meta } = Card;
const CustomersCardList: React.FC = () => {
const customers: any = useContext();
return (
<div>
{customers.map((customer, key) => { })}</div>)
}
//context.tsx
import * as React from 'react';
import axios from 'axios';
export const AccountContext = React.createContext({});
export const useContext = () => React.useContext(AccountContext);
interface AccounterContextProviderProps {
value: any
};
export const AccounterContextProvider: React.FC<AccounterContextProviderProps> = ({ children, value }) => {
const [customers, setCustomers] = React.useState<any>([]);
React.useEffect(() => {
const getCustomers = async () => {
const result = await axios.get("http://localhost:3333/customers");
setCustomers(result.data);
}
getCustomers();
}, []);
console.log(customers);
return (
<AccountContext.Provider value={{ ...value, customers }}>
{children}
</AccountContext.Provider>
)
};
Any can be anything not only array, so it will not have a map method. Use const customers:any[] = useContext() instead
I'm trying to use the useSnack hook from notistack library but I keep getting this error
TypeError: Cannot destructure property 'enqueueSnackbar' of 'Object(...)(...)' as it is undefined.
Here is the code:
import React, { useContext, useEffect } from "react";
import AlertContext from "../context/alert/alertContext";
import { SnackbarProvider, useSnackbar } from "notistack";
const Alerts = (props) => {
const alertContext = useContext(AlertContext);
// This line below is where the error seems to be
const { enqueueSnackbar } = useSnackbar();
useEffect(() => {
alertContext.msg !== "" &&
enqueueSnackbar(alertContext.msg, {
variant: alertContext.type,
});
}, [alertContext]);
return <SnackbarProvider maxSnack={4}>{props.children}</SnackbarProvider>;
};
export default Alerts;
useSnackbar hook accessible anywhere down the tree from SnackbarProvider.
So you cannot use it in the same component as SnackbarProvier.
import AlertContext from "../context/alert/alertContext";
import { SnackbarProvider } from "notistack";
const Alerts = (props) => {
const alertContext = useContext(AlertContext);
const providerRef = React.useRef();
useEffect(() => {
alertContext.msg !== "" &&
providerRef.current.enqueueSnackbar(alertContext.msg, {
variant: alertContext.type,
});
}, [alertContext]);
return <SnackbarProvider ref={providerRef} maxSnack={4}>
{props.children}
</SnackbarProvider>;
};
export default Alerts;
Wrap you index file with SnapBar provider:
index.js
import { SnackbarProvider } from "notistack";
const Index = () => (
<SnackbarProvider maxSnack={1} preventDuplicate>
index
</SnackbarProvider>
)
export default Index
jsx file
import { useSnackbar } from "notistack";
const Logs = () => {
const { enqueueSnackbar } = useSnackbar();
const handler = () => {
enqueueSnackbar(`Successful.`, { variant: "success" });
};
return <span onClick={handler}>"Logs loading"</span>;
};
export default Logs;