I have a Modal component that uses a reusable <Modal> component using useQuery and useMutation:
const CCPAModal = ({ addNotification, closeModal }: Props): Node => {
const { data, loading: memberInfoLoading } = useQuery(getMemberInfo, { variables: { id: '1234' } } );
const [ updateMemberMutation, { loading: updateMemberLoading } ] = useMutation(updateMemberPrivacyPolicyAgreedAt, {
variables : { input: { id: data && data.user.member.id } },
onCompleted : (): void => closeModal(modalIds.ccpaModal),
onError : (err): void => addNotification(getFirstError(err), 'error'),
});
return (
<Modal size="md" id={modalIds.ccpaModal}>
{memberInfoLoading && <LoadingCircle />}
{!memberInfoLoading && (
<>
<Subtitle>
copycopycopy <Link alias="PrivacyPolicy">Privacy Policy</Link>.
</Subtitle>
<FlexRow justification="flex-end">
<Button
text="Accept"
disabled={updateMemberLoading}
onClick={updateMemberMutation}
/>
</FlexRow>
</>
)}
</Modal>
);
};
const mapDispatchToProps = { addNotification, closeModal };
export default connect(null, mapDispatchToProps)(CCPAModal);
My issue is that I'm not entirely sure in which direction to take my tests. I want to test the loading state and quite possibly test the data of the mocks, but googling and my lack of knowledge of testing in general is leaving me a bit confused. So far this is my test:
__CCPAModal.test.js
import React from 'react';
// import render from 'react-test-renderer';
import { MockedProvider } from '#apollo/react-testing';
import { act } from 'react-dom/test-utils';
import { getMemberInfo } from 'operations/queries/member';
import { CCPAModal } from './CCPAModal';
jest.mock('#project/ui-component');
const mock = {
request: {
query: getMemberInfo,
{ variables: { id: '1234' } }
},
};
describe('CCPAModal', () => {
it('mounts', () => {
const component = mount(
<MockedProvider
mocks={[]}
>
<CCPAModal />
</MockedProvider>
);
console.log(component.debug())
});
});
Which will log
<MockedProvider mocks={{...}} addTypename={true}>
<ApolloProvider client={{...}}>
<CCPAModal>
<Connect(t) size="md" id="CCPA Modal" />
</CCPAModal>
</ApolloProvider>
</MockedProvider>
Which I'm unsure where to proceed from here. I wanted to be able to test the loading state and the contents after it loaded, but it doesn't seem I can even get past <Connect(t) size="md" id="CCPA Modal" />.
Otherwise, my alternative solution is to just make snapshot tests.
Can anyone advise on how to test with Enzyme / useQuery / useMutation hooks? How do I access the child components after it loads? And for that matter, I'm unsure how to even test the loading state and data.
Related
I want to use react suspense.
The suspense behavior I want is to not show the fallback until a certain amount of time.
Using react with next works as intended. However, react alone flickers.
Why does this only work when using next ?
What's the difference?
After changing the react dom generation code, it worked as expected.
How can I do this in react-native as well?
Example
I made a simple todo app.
We made a 100ms delay to get the todos list with an asynchronous request.
recoil
import { selector, atom } from "recoil";
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
export const todosDelayState = selector({
key: "todosDelayState",
get: async ({ get }) => {
await sleep(100);
const todos = get(todosState);
return todos;
}
});
export const todosState = atom({
key: "todosState", // unique ID (with respect to other atoms/selectors)
default: [{ id: 0, text: "fasfasdf", done: false }] // default value (aka initial value)
});
Home
export default function Home({ accounts }) {
return (
<RecoilRoot>
<React.Suspense fallback={<span>Loading</span>}>
<Todo />
</React.Suspense>
</RecoilRoot>
);
}
Todo
import TodoForm from "./TodoForm";
import TodoList from "./TodoList";
import React from "react";
import { useRecoilState, useRecoilValue } from "recoil";
import { todosDelayState, todosState } from "./todos";
function Todo() {
const [_, setTodos] = useRecoilState(todosState);
const todos = useRecoilValue(todosDelayState);
const onToggle = (id, done) => {
const _todos = todos.map((todo) => {
if (todo.id === id) {
return { ...todo, done: !done };
} else {
return todo;
}
});
setTodos(_todos);
};
const onRemove = (id) => {
const _todos = todos.filter((todo) => {
return todo.id === id ? false : true;
});
setTodos(_todos);
};
const onInsert = (value) => {
const id = todos.length === 0 ? 1 : todos[todos.length - 1].id + 1;
const todo = {
id,
text: value,
done: false
};
const _todos = todos.concat([todo]);
setTodos(_todos);
};
return (
<React.Fragment>
<TodoForm onInsert={onInsert} />
<TodoList todos={todos} onToggle={onToggle} onRemove={onRemove} />
</React.Fragment>
);
}
export default Todo;
1. Sandbox with next, react 18, recoil
live demo
In case it behaves exactly as I expected.
2. Sandbox with react 18, recoil
live demo
~~blinks~~ --> nice work
After changing the react dom generation code, it worked as expected.
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import App from "./App";
// const rootElement = document.getElementById("root");
// ReactDOM.render(
// <StrictMode>
// <App />
// </StrictMode>,
// rootElement
// );
const container = document.getElementById("root");
const root = ReactDOM.createRoot(container);
root.render(<App />);
3. Expo with react-native, react, recoil
live demo solved live demo
~~blinks~~ -> work
Unlike react-dom, react-native could not find a code that directly affects it.
So, I made a custom hook, Suspense, and ErrorBoundary using the loadable of recoil.
export const useCustomRecoilValue = (state, initialValue) => {
const data = useRecoilValueLoadable(state)
const prevData = useRef(initialValue)
const _data = useMemo(() => {
if (data.state === 'hasValue') {
prevData.current = data.contents
return [data.contents, false, undefined]
} else if (data.state === 'hasError') {
return [prevData.current, false, data.contents]
} else {
return [prevData.current, true, undefined]
}
}, [data])
return _data
}
// const todos = useRecoilValue(todosDelayState)
const [todos, loading, error] = useCustomRecoilValue(todosDelayState, [])
// return (
// <VStack space={8} w="100%">
// <TodoForm onInsert={onInsert} />
// <TodoList todos={todos} onToggle={onToggle} onRemove={onRemove} />
// </VStack>
// )
return (
<VStack space={8} w="100%">
<TodoForm onInsert={onInsert} />
<ErrorBoundary error={error} fallback={<Box>Error</Box>}>
<Suspense
delay={250}
loading={loading}
fallback={<Box>Loading...</Box>}>
<TodoList todos={todos} onToggle={onToggle} onRemove={onRemove} />
</Suspense>
</ErrorBoundary>
</VStack>
)
I'm having a bit of a hard time understanding how to test my modal component. I'm using the react-native-modals package and #testing-library/react-native with Jest. My component is a modal that pops up when a GraphQL error is passed to it.
./ErrorMessage.js
import React from 'react';
import PropTypes from 'prop-types';
import { Dimensions, Text } from 'react-native';
import Modal, { ModalContent, ScaleAnimation } from 'react-native-modals';
import { theme } from '../styles/theme.styles';
const ModalError = ({ error, onClose }) => {
if (!error || !error.message) {
return (
<Modal visible={false}>
<Text />
</Modal>
);
}
return (
<Modal
visible
modalAnimation={
new ScaleAnimation({
initialValue: 0,
useNativeDriver: true,
})
}
onTouchOutside={onClose}
swipeDirection={['up', 'down', 'left', 'right']}
swipeThreshold={200}
onSwipeOut={onClose}
modalStyle={modalStyle}
overlayOpacity={0.7}
>
<ModalContent>
<Text testID="graphql-error">{error.message}</Text>
</ModalContent>
</Modal>
);
};
ModalError.defaultProps = {
error: {},
};
ModalError.propTypes = {
error: PropTypes.object,
onClose: PropTypes.func.isRequired,
};
export default ModalError;
const window = Dimensions.get('window');
const modalStyle = {
backgroundColor: theme.lightRed,
borderLeftWidth: 5,
borderLeftColor: theme.red,
width: window.width / 1.12,
};
My test is pretty simple so far. I just want to make sure it's rendering the modal. I'm not exactly sure what needs to be mocked out here or how to do it.
./__tests__/ErrorMessage.test.js
import React from 'react';
import { MockedProvider } from '#apollo/react-testing';
import { GraphQLError } from 'graphql';
import { render } from '#testing-library/react-native';
import Error from '../ErrorMessage';
jest.mock('react-native-modals', () => 'react-native-modals');
const error = new GraphQLError('This is a test error message.');
const handleOnCloseError = jest.fn();
describe('<ErrorMessage>', () => {
it('should render an ErrorMessage modal component', () => {
const { container } = render(
<MockedProvider>
<Error error={error} onClose={handleOnCloseError} />
</MockedProvider>
);
expect(container).toMatchSnapshot();
});
});
The error that I'm getting is...
TypeError: _reactNativeModals.ScaleAnimation is not a constructor
18 | visible
19 | modalAnimation={
> 20 | new ScaleAnimation({
| ^
21 | initialValue: 0,
22 | useNativeDriver: true,
23 | })
And the snapshot is only printing...
./__tests__/__snapshots__/ErrorMessage.test.js.snap
// Jest Snapshot v1,
exports[`<ErrorMessage> should render an ErrorMessage modal component 1`] = `
<View
collapsable={true}
pointerEvents="box-none"
style={
Object {
"flex": 1,
}
}
/>
`;
How can I get past this error and make a proper snapshot?
you can use this -> https://github.com/testing-library/jest-native
In react native component,
...
<Modal
testID="test-modal"
deviceWidth={deviceWidth}
deviceHeight={deviceHeight}
isVisible={isModalVisible}. // isModalVisible = useState(true or false)
onBackdropPress={toggleModal}
backdropOpacity={0.5}
>
...
In test component,
...
const test = getByTestId("test-modal");
expect(test).toHaveProp("visible", true); // test success !
...
// components/Example/index.tsx
import React, { useState } from 'react';
import { Pressable, Text } from 'react-native';
import Modal from 'react-native-modal';
const Example: React.FC = () => {
const [isPrivacyPolicyVisible, setIsPrivacyPolicyVisible] = useState(false);
return (
<>
<Pressable onPress={() => setIsPrivacyPolicyVisible(true)}>
<Text>Privacy Policy</Text>
</Pressable>
<Modal
accessibilityLabel="privacy-policy-modal"
isVisible={isPrivacyPolicyVisible}>
<Text>Content</Text>
</Modal>
</>
);
};
export default Example;
// components/Example/index.test.tsx
import React from 'react';
import { fireEvent, render, waitFor } from '#testing-library/react-native';
import { Example } from 'components';
describe('Example Component', () => {
it('should render privacy policy.', async () => {
// Arrange
const { queryByText, queryByA11yLabel } = render(<Example />);
const button = queryByText(/privacy policy/i);
const modal = queryByA11yLabel('privacy-policy-modal');
// Act and Assert
expect(button).toBeTruthy();
expect(modal).toBeTruthy();
expect(modal.props).toMatchObject({
visible: false,
});
});
it('should show privacy policy modal.', async () => {
// Arrange
const { queryByText, queryByA11yLabel } = render(<Example />);
const button = queryByText(/privacy policy/i);
const modal = queryByA11yLabel('privacy-policy-modal');
// Act
await waitFor(() => {
fireEvent.press(button);
});
// Assert
expect(modal.props).toMatchObject({
visible: true,
});
});
});
when you do jest.mock('react-native-modals', () => 'react-native-modals'); you're replacing the whole library with the string 'react-native-modals' thus when you use it in your component it fails. You need to return a full mocked implementation from your mock function (second argument to jest.mock). It's also possible auto-mocking may work for you which would be done by simply doing: jest.mock('react-native-modals');
Here's the docks for jest.mock() with some examples of the various ways to use it: https://jestjs.io/docs/en/jest-object#jestmockmodulename-factory-options.
I use my Logs component to map logs from an array of objects. My problem is, that using "useEffect" it makes my application very slow. Is there any possible way to rewrite it to class component?
my code:
import React, { useEffect } from "react";
import Log from "../logs/log";
import "../../scss/logs.scss";
const Logs = ({ logs, changeDetailState, getLogId, onClick, mountLogs }) => {
useEffect(() => {
mountLogs();
});
const logsmap = logs.map((log, i) => (
<Log
onClick={onClick}
getLogId={getLogId}
changeDetailState={changeDetailState}
key={i}
input={log.amount}
description={log.description}
id={i}
/>
));
return <div className="logs">{logsmap}</div>;
};
export default Logs;
You can transform it to a Class Component by doing the follow:
class Logs extends React.Component {
componentDidMount() {
const { mountLogs } = this.props;
mountLogs();
}
}
processLogsMap = () => {
const { logs } = this.props;
logs.map((log, i) => (
<Log
onClick={onClick}
getLogId={getLogId}
changeDetailState={changeDetailState}
key={i}
input={log.amount}
description={log.description}
id={i}
/>
));
}
render() {
return (
<>
<div className="logs">{processLogsMap()}</div>;
</>
)
}
export default Logs;
You are invoking mountLogs(), every time a property is changing.
If you want it to run just once upon mount, then use:
useEffect(() => {
mountLogs();
}, []);
If you want to run on specific values changes then you should use:
useEffect(() => {
mountLogs();
}, [logs, changeDetailState]);
For Running it once, it should be used as below:
import React, { useEffect } from "react";
import Log from "../logs/log";
import "../../scss/logs.scss";
const Logs = ({ logs, changeDetailState, getLogId, onClick, mountLogs }) => {
useEffect(() => {
mountLogs();
}, []);
const logsmap = logs.map((log, i) => (
<Log
onClick={onClick}
getLogId={getLogId}
changeDetailState={changeDetailState}
key={i}
input={log.amount}
description={log.description}
id={i}
/>
));
return <div className="logs">{logsmap}</div>;
};
export default Logs;
I've just written test file for my component, at the moment it's very rudimentary.. I'm quite inexperience in written test for frontend. I ran yarn test to this test file and it failed miserably..
Here is the message:
Unable to find an element with the text: Please review your billing details...
This is what I have so far for my test:
import React from 'react';
import { render, cleanup, waitForElement } from 'react-testing-library';
// React Router
import { MemoryRouter, Route } from "react-router";
import Show from './Show';
test('it shows the offer', async () => {
const { getByText } = render(
<MemoryRouter initialEntries={['/booking-requests/20-A1-C2/offer']}>
<Route
path="/booking-requests/:booking_request/offer"
render={props => (
<Show {...props} />
)}
/>
</MemoryRouter>
);
//displays the review prompt
await waitForElement(() => getByText('Please review your billing details, contract preview and Additions for your space. Once you’re happy, accept your offer'));
//displays the confirm button
await waitForElement(() => getByText('Confirm'));
});
and this is the component:
// #flow
import * as React from 'react';
import i18n from 'utils/i18n/i18n';
import { Btn } from '#appearhere/bloom';
import css from './Show.css';
import StepContainer from 'components/Layout/DynamicStepContainer/DynamicStepContainer';
const t = i18n.withPrefix('client.apps.offers.show');
const confirmOfferSteps = [
{
title: t('title'),
breadcrumb: t('breadcrumb'),
},
{
title: i18n.t('client.apps.offers.billing_information.title'),
breadcrumb: i18n.t('client.apps.offers.billing_information.breadcrumb'),
},
{
title: i18n.t('client.apps.offers.confirm_pay.title'),
breadcrumb: i18n.t('client.apps.offers.confirm_pay.breadcrumb'),
},
];
class Show extends React.Component<Props> {
steps = confirmOfferSteps;
renderCtaButton = (): React.Element<'Btn'> => {
const cta = t('cta');
return <Btn className={css.button} context='primary'>
{cta}
</Btn>
};
renderLeftContent = ({ isMobile }: { isMobile: boolean }): React.Element<'div'> => (
<div>
<p>{t('blurb')}</p>
{!isMobile && this.renderCtaButton()}
</div>
);
renderRightContent = () => {
return <div>Right content</div>;
};
render() {
const ctaButton = this.renderCtaButton();
return (
<StepContainer
steps={this.steps}
currentStep={1}
ctaButton={ctaButton}
leftContent={this.renderLeftContent}
rightContent={this.renderRightContent}
footer={ctaButton}
/>
);
}
}
export default Show;
what am I missing? Suggestions what else to add to my test file would be greatly appreciated!
As in the title, I'm trying to use React.lazy feature which works in my my other project. But not in this one, I don't know what I'm missing here. All works just fine, no errors, no warnings. But for some reason I don't see my bundle split in chunks.
Here's my implementation:
import React, { Component, Suspense } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { getApps } from '../../actions/apps';
import './_apps.scss';
const AppItemComponent = React.lazy(() => import('../AppItem'));
class Apps extends Component {
componentDidMount() {
const { getApps } = this.props;
getApps(3);
}
renderAppItem = () => {
const { apps } = this.props;
return apps && apps.map((item, i) => {
return (
<Suspense fallback={<div>loading...</div>} key={i}>
<AppItemComponent
index={i + 1}
item={item}
/>
</Suspense>
);
});
};
render() {
const { apps } = this.props;
return (
<div className="apps__section">
<div className="apps__container">
<div className="apps__header-bar">
<h3 className="apps__header-bar--title">Apps</h3>
<Link className="apps__header-bar--see-all link" to="/apps">{`see all (${apps.length})`}</Link>
</div>
{this.renderAppItem()}
</div>
</div>
);
}
}
const mapStateToProps = ({ apps }) => {
return { apps };
};
const mapDispatchToProps = dispatch => {
return {
getApps: quantity => dispatch(getApps(quantity)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Apps);
I'm doing this in react-create-app app and in react v16.6, react-dom v16.6.
What am I missing here?
I also have the same problem, then I have resolved this case without using Suspense and lazy(try code below), and I can see chunks file. However, after using this way, I try changing my code again with Suspense and lazy. It works!!! and I don't know why it does. Hope that it works for someone find solution for this case.
1 - create file asyncComponent
import React, { Component } from "react";
const asyncComponent = (importComponent) => {
return class extends Component {
state = {
component: null,
};
componentDidMount() {
importComponent().then((cmp) => {
this.setState({ component: cmp.default });
});
}
render() {
const C = this.state.component;
return C ? <C {...this.props} /> : null;
}
};
};
export default asyncComponent;
2 - and in App.js, example:
const AuthLazy = asyncComponent(() => import("./containers/Auth/Auth"));
//Route
<Route path="/auth" component={AuthLazy} />
Check that your component is not imported somewhere with regular import: import SomeComponent from './path_to_component/SomeComponent';
Check that component is not re-exported somewhere. (For example in index.js (index.ts) file: export * from './SomeComponent') If so, just remove re-export of this component.
Check that your export your component as default or use code like this:
const SomeComponent = React.lazy(() => import('./path_to_component/SomeComponent').then((module) => ({ default: module.SomeComponent })));