When I tried to image, image is broken
BASE_URL I want to fetch is composed like this.
{
"renderings": [
{
"_id": "image_file"(string type)
},
...more _id
}
This is a similar topic to the question I posted a few days ago.
But I see the problem seems to arise while implementing the detail page.
// App.tsx
function App() {
return (
<BrowserRouter>
<Route exact path="/">
<Gallery />
</Route>
<Route path="/detail">
<Detail />
</Route>
</BrowserRouter>
);
}
export default App;
// Gallery.tsx
function Gallery() {
const [gallery, setGallery] = useState<any>();
const getGallery = async () => {
const json: GalleriesProps = await (await fetch(BASE_URL)).json();
setGallery(json.renderings);
};
useEffect(() => {
getGallery();
}, []);
return (
<ImgContainer>
<Link to="/detail">
{gallery &&
gallery.map((x: any) => (
<img key={x._id} alt="gallery_logo" src={x._id} />
))}
</Link>
</ImgContainer>
);
}
export default Gallery;
// Detail.tsx
function Detail() {
const [galleryDetail, setGalleryDetail] = useState<any>();
const [clicked, setClicked] = useState(false);
console.log(galleryDetail);
useEffect(() => {
async function fetchGallery() {
const response = await fetch(BASE_URL);
const result: GalleriesProps = await response.json();
setGalleryDetail(result.renderings[0]._id);
}
fetchGallery();
}, []);
const prevPage = () => {
if (clicked) return;
setClicked(true);
};
const nextPage = () => {
if (clicked) return;
setClicked(true);
};
return (
<>
<Container>
<Button onClick={prevPage}>
<FontAwesomeIcon icon={faArrowAltCircleLeft} />
</Button>
<ImageDetail>
{galleryDetail && <img src={galleryDetail} />}
</ImageDetail>
<Button onClick={nextPage}>
<FontAwesomeIcon icon={faArrowAltCircleRight} />
</Button>
</Container>
</>
);
}
export default Detail;
When I click on an image, I go to the detail page, and then I have to implement that image to show,
For example,
If I click the image of the cat.jpeg file, I have to go to the detail page and change the size of cat.jpeg and show the rest as it is,
but I can't quite figure it out.
I have a component that looks like:
import React, { useRef } from 'react'
import Modal from '#example-component-library/modal'
import ModalHeader from '#example-component-library/modal/header'
import ModalFooter from '#example-component-library/modal/footer'
const ExampleModal = () => {
const headerRef = useRef(null)
...
return (
<Modal
headerRef={headerRef}
isOpen={showModal}
header={
<ModalHeader closeModal={handleCloseModal} headerRef={headerRef} content="Modal Header"/>
}
footer={<ModalFooter closeModal={handleCloseModal} />}
>
Modal body stuff
</Modal>
)
}
Then I have a test:
it('renders as expected', () => {
const wrapper = mount(
<TestWrapper>
<ExampleModal />
</TestWrapper>
)
expect(wrapper.exists()).toBe(true)
})
})
and then I have an error
TypeError: Cannot read property 'style' of null
28 |
29 | it('renders as expected', () => {
> 30 | const wrapper = mount(
| ^
31 | <TestWrapper>
32 | <ExampleModal />
33 | </TestWrapper>
If I change ExampleModal prop header to:
<Modal header={<>HEADER</>} ...>
The test works without issues - so I believe it has something to do with the headerRef I've tried jest.spyOn and a few other solutions - however I always get the same error.
Modal Component Markup
const ModalHeader = ({ headerRef, content, ...}) => (
<div>
<h5 tabIndex={-1} ref={headerRef}> {content}</h5>
...
</div>
)
const Modal = ({ id, isOpen, header, headerRef, children, ...}) => {
useEffect(() => {
const selectorId = `#${id}`
const selectedElement: HTMLElement = document.querySelector(selectorId)
// set focus to the header when modal is opened
if (isOpen && headerRef.current) {
headerRef.current.focus()
// React Ref wasn't working for this case
selectedElement.style.right = '0px'
document.body.style.overflow = 'hidden'
setPostAnimationState(true)
}
if (!isOpen && document.querySelector(selectorId)) {
// React Ref wasn't working for this case
document.body.style.overflow = 'auto'
selectedElement.style.right = '-768px'
setTimeout(() => {
setPostAnimationState(false)
}, 400)
}
}, [isOpen, headerRef, id])
return (
<div>
...
{!!header && header}
<div className="modal-content" ref={!header ? headerRef : null}>
{children}
</div>
...
</div>
)
}
It actually had nothing to do with ref, it had to do with the document.body. So here is the test I have that works now:
it('renders as expected', () => {
const wrapper = mount(
<TestWrapper>
<ExampleModal />
</TestWrapper>,
{ attachTo: document.body }
)
expect(wrapper.exists()).toBe(true)
})
})
Having troubles displaying JSX returned from method.
Error I get is Method “text” is meant to be run on 1 node. 0 found instead.
When I run wrapper.debug() no JSX elements from the condition exists. (I can hit the console.log in that method though)
Thank you in advance!!
component.js
function component(props) {
const [message, setMessage] = useState();
const [open, setOpen] = useState(false);
useEffect(() => {
fetch.then((results)=>(setMessage(fetchedMessage));
});
const displayMessage = () => {
if(condition1) {
return(
<div class="container">
<div id='user'>
Hello user, {message}
{console.log("test if I am hitting inside this statement")}
</div>
</div>
)
};
if(condition2) {
return(
<div class="container">
<div id='admin'>Hello admin, {message}</div>
<button id='modal-button' onClick={() => {setOpen(true}}> Open </button>
</div>
)
};
return null;
};
return (
<>
<div>{message ? displayMessage() : null}</div>
<Modal open={open} />
</>
);
};
component.spec.js
describe('Describe test', () => {
function createMockStore() {
return configureMockStore()({ mockedStore: {some values}})
};
it('displays correct message for condition1', () => {
const mockFetchedData = { message: "Mocked message" };
const mockStore = createMockStore(condition1);
const mockFetchData = jest.fn(() => new Promise((resolve) => resolve({ data: mockFetchedData })));
const wrapper = await mount(
<Provider store={mockStore}>
<Component {...props} />
</Provider>
).find('Component');
expect(wrapper.find('#user').text().toEqual("Mocked message"))); //Failing here. Error is: Method “text” is meant to be run on 1 node. 0 found instead.
});
};
What would be the way to test a component that relies on the initial state for conditional rendering ?
For example showLessFlag is dependent on state, and testing state in react-testing-library is counter productive.
so how would i test this condition in the CommentList component
{showLessFlag === true ? (
// will show most recent comments below
showMoreComments()
) : (
<Fragment>
{/* filter based on first comment, this shows by default */}
{filterComments.map((comment, i) => (
<div key={i} className="comment">
<CommentListContainer ref={ref} comment={comment} openModal={openModal} handleCloseModal={handleCloseModal} isBold={isBold} handleClickOpen={handleClickOpen} {...props} />
</div>
))}
</Fragment>
)}
Should it be test like the following
it("should check more comments", () => {
const { getByTestId } = render(<CommentList {...props} />);
const commentList = getByTestId("comment-show-more");
expect(commentList).toBeNull();
});
But im getting this error because of the conditional rendering
TestingLibraryElementError: Unable to find an element by:
[data-testid="comment-show-more"]
CommentList.tsx
import React, { Fragment, useState, Ref } from "react";
import Grid from "#material-ui/core/Grid";
import OurSecondaryButton from "../../../common/OurSecondaryButton";
import CommentListContainer from "../commentListContainer/commentListContainer";
function CommentList(props: any, ref: Ref<HTMLDivElement>) {
const [showMore, setShowMore] = useState<Number>(2);
const [openModal, setOpenModal] = useState(false);
const [showLessFlag, setShowLessFlag] = useState<Boolean>(false);
const the_comments = props.comments.length;
const inc = showMore as any;
const min = Math.min(2, the_comments - inc);
const showComments = (e) => {
e.preventDefault();
if (inc + 2 && inc <= the_comments) {
setShowMore(inc + 2);
setShowLessFlag(true);
} else {
setShowMore(the_comments);
}
};
const handleClickOpen = () => {
setOpenModal(true);
};
const handleCloseModal = () => {
setOpenModal(false);
};
const showLessComments = (e) => {
e.preventDefault();
setShowMore(2);
setShowLessFlag(false);
};
const isBold = (comment) => {
return comment.userId === props.userId ? 800 : 400;
};
// show comments by recent, and have the latest comment at the bottom, with the previous one just before it.
const filterComments = props.comments
.slice(0)
.sort((a, b) => {
const date1 = new Date(a.createdAt) as any;
const date2 = new Date(b.createdAt) as any;
return date2 - date1;
})
.slice(0, inc)
.reverse();
const showMoreComments = () => {
return filterComments.map((comment, i) => (
<div data-testid="comment-show-more" key={i} className="comment">
<CommentListContainer ref={ref} comment={comment} openModal={openModal} handleCloseModal={handleCloseModal} isBold={isBold} handleClickOpen={handleClickOpen} {...props} />
</div>
));
};
return (
<Grid data-testid="comment-list-div">
<Fragment>
<div style={{ margin: "30px 0px" }}>
{props.comments.length > 2 ? (
<Fragment>
{min !== -1 && min !== -2 ? (
<Fragment>
{min !== 0 ? (
<OurSecondaryButton onClick={(e) => showComments(e)} component="span" color="secondary">
View {min !== -1 && min !== -2 ? min : 0} More Comments
</OurSecondaryButton>
) : (
<OurSecondaryButton onClick={(e) => showLessComments(e)} component="span" color="secondary">
Show Less Comments
</OurSecondaryButton>
)}
</Fragment>
) : (
<OurSecondaryButton onClick={(e) => showLessComments(e)} component="span" color="secondary">
Show Less Comments
</OurSecondaryButton>
)}
</Fragment>
) : null}
</div>
</Fragment>
{showLessFlag === true ? (
// will show most recent comments below
showMoreComments()
) : (
<Fragment>
{/* filter based on first comment */}
{filterComments.map((comment, i) => (
<div key={i} className="comment">
<CommentListContainer ref={ref} comment={comment} openModal={openModal} handleCloseModal={handleCloseModal} isBold={isBold} handleClickOpen={handleClickOpen} {...props} />
</div>
))}
</Fragment>
)}
</Grid>
);
}
export default React.forwardRef(CommentList) as React.RefForwardingComponent<HTMLDivElement, any>;
CommentList.test.tsx
import "#testing-library/jest-dom";
import React, { Ref } from "react";
import CommentList from "./CommentList";
import { render, getByText, queryByText, getAllByTestId } from "#testing-library/react";
const props = {
user: {},
postId: null,
userId: null,
currentUser: {},
ref: {
current: undefined,
},
comments: [
{
author: { username: "barnowl", gravatar: "https://api.adorable.io/avatars/400/bf1eed82fbe37add91cb4192e4d14de6.png", bio: null },
comment_body: "fsfsfsfsfs",
createdAt: "2020-05-27T14:32:01.682Z",
gifUrl: "",
id: 520,
postId: 28,
updatedAt: "2020-05-27T14:32:01.682Z",
userId: 9,
},
{
author: { username: "barnowl", gravatar: "https://api.adorable.io/avatars/400/bf1eed82fbe37add91cb4192e4d14de6.png", bio: null },
comment_body: "fsfsfsfsfs",
createdAt: "2020-05-27T14:32:01.682Z",
gifUrl: "",
id: 519,
postId: 27,
updatedAt: "2020-05-27T14:32:01.682Z",
userId: 10,
},
],
deleteComment: jest.fn(),
};
describe("Should render <CommentList/>", () => {
it("should render <CommentList/>", () => {
const commentList = render(<CommentList {...props} />);
expect(commentList).toBeTruthy();
});
it("should render first comment", () => {
const { getByTestId } = render(<CommentList {...props} />);
const commentList = getByTestId("comment-list-div");
expect(commentList.firstChild).toBeTruthy();
});
it("should render second child", () => {
const { getByTestId } = render(<CommentList {...props} />);
const commentList = getByTestId("comment-list-div");
expect(commentList.lastChild).toBeTruthy();
});
it("should check comments", () => {
const rtl = render(<CommentList {...props} />);
expect(rtl.getByTestId("comment-list-div")).toBeTruthy();
expect(rtl.getByTestId("comment-list-div")).toBeTruthy();
expect(rtl.getByTestId("comment-list-div").querySelectorAll(".comment").length).toEqual(2);
});
it("should match snapshot", () => {
const rtl = render(<CommentList {...props} />);
expect(rtl).toMatchSnapshot();
});
it("should check more comments", () => {
const { getByTestId } = render(<CommentList {...props} />);
const commentList = getByTestId("comment-show-more");
expect(commentList).toBeNull();
});
});
Any getBy* query in react-testing-library will throw an error if no match is found. If you want to test/assert the absence of an element then you want to use any of the queryBy* queries, they return null if no match is found.
Queries
it("should check more comments", () => {
const { queryByTestId } = render(<CommentList {...props} />);
const commentList = queryByTestId("comment-show-more");
expect(commentList).toBeNull();
});
To better answer this question, being that i have more experience with using react testing library now.
When we go about testing for conditions, we need to trigger the action that makes the change to the state.
For example in this situation
We have a condition like showLessFlag
{showLessFlag === true ? (
// will show most recent comments below
showMoreComments()
) : (
<Fragment>
{/* filter based on first comment, this shows by default */}
{filterComments.map((comment, i) => (
<div key={i} className="comment">
<CommentListContainer ref={ref} comment={comment} openModal={openModal} handleCloseModal={handleCloseModal} isBold={isBold} handleClickOpen={handleClickOpen} {...props} />
</div>
))}
</Fragment>
)}
In order to properly test this, we need to trigger the event that will change showLessFlag to false.
So we can do something like
<OurSecondaryButton
onClick={(e) => showLessComments(e)}
data-testid="_test-show-less"
component="span"
color="secondary"
>
Show Less Comments
</OurSecondaryButton>
test
it("should trigger showLessComments ", () => {
const { getByTestId } = render(<CommentList {...props} />);
const showLessButton = getByTestId("__test-show-less");
fireEvent.click(showLessButton);
expect(...) // whatever to be called, check for the existence of a div tag, or whatever you want
});
Testing for conditions improves code coverage :)
Using Reac.memo to wrap my functional component, and it can run smoothly, but the eslint always reminded me two errors:
error Component definition is missing display name react/display-name
error 'time' is missing in props validation react/prop-types
Here is my code:
type Data = {
time: number;
};
const Child: React.FC<Data> = React.memo(({ time }) => {
console.log('child render...');
const newTime: string = useMemo(() => {
return changeTime(time);
}, [time]);
return (
<>
<p>Time is {newTime}</p>
{/* <p>Random is: {children}</p> */}
</>
);
});
My whole code:
import React, { useState, useMemo } from 'react';
const Father = () => {
const [time, setTime] = useState(0);
const [random, setRandom] = useState(0);
return (
<>
<button type="button" onClick={() => setTime(new Date().getTime())}>
getCurrTime
</button>
<button type="button" onClick={() => setRandom(Math.random())}>
getCurrRandom
</button>
<Child time={time} />
</>
);
};
function changeTime(time: number): string {
console.log('changeTime excuted...');
return new Date(time).toISOString();
}
type Data = {
time: number;
};
const Child: React.FC<Data> = React.memo(({ time }) => {
console.log('child render...');
const newTime: string = useMemo(() => {
return changeTime(time);
}, [time]);
return (
<>
<p>Time is {newTime}</p>
{/* <p>Random is: {children}</p> */}
</>
);
});
export default Father;
It's because you have eslint config which requries you to add displayName and propTypes
Do something like
const Child: React.FC<Data> = React.memo(({ time }) => {
console.log('child render...');
const newTime: string = useMemo(() => {
return changeTime(time);
}, [time]);
return (
<>
<p>Time is {newTime}</p>
{/* <p>Random is: {children}</p> */}
</>
);
});
Child.propTypes = {
time: PropTypes.isRequired
}
Child.displayName = 'Child';
If you are working with React and TypeScript, you can turn off the react/prop-types rule.
This is because TypeScript interfaces/props are good enough to replace React's prop types.