Jest/React Testing Library test failing - react router link click not working as expected - jestjs

I have a failing test but can't work out why. I have react-router links which link to the URL structure: /classes/${weekday}.
Classes component then sets the activeWeekday in context by React Router location which is displayed by the Classes as {activeWeekday} Classes
Functionality works i nthe browser, but for some reason in my tests it's not updating the header so the test is failing.
TestingLibraryElementError: Unable to find an element with the text: /friday classes/i
Can anyone see why? I can't figure it out.
Thks so much in advance.
Update - Here is a codepen replicating the issue.
// Snapshot of the state passed to Classes via context
export const ClassesProvider = ({ children }: ClassesProviderProps) => {
const [activeWeekdayNumber, setActiveWeekdayNumber] = useState<number>(
new Date().getDay()
);
// Classes component
const Classes = () => {
const { activeWeekdayNumber, setActiveWeekdayNumber } = useContext(ClassesContext);
const location = useLocation();
useEffect(() => {
const day = location.pathname.replace("/classes/", "");
const dayIndex = daysOfWeekArray.indexOf(capitaliseFirstLetter(day));
if (dayIndex !== -1) {
setActiveWeekdayNumber(
daysOfWeekArray.indexOf(capitaliseFirstLetter(day))
);
}
}, [location, setActiveWeekdayNumber]);
return (
<>
<h2>{daysOfWeekArray[activeWeekdayNumber]} Classes</h2>
</>
);
};
// failing test - TestingLibraryElementError: Unable to find an element with the text: /friday classes/i
const setup = (value: ClassesContextType) =>
render(
<ClassesContext.Provider value={value}>
<MemoryRouter>
<Classes />
</MemoryRouter>
</ClassesContext.Provider>
);
test("displays the relevant heading when a day of the week link is clicked", () => {
const value = {
activeWeekdayNumber: 3, // wednesday
};
setup(value);
const link = screen.getByRole("link", { name: "Friday" });
fireEvent.click(link);
expect(screen.getByText(/friday classes/i)).toBeInTheDocument();
});
});
The menu list is a styled link:
<li>
<HorizontalMenuLink $active={weekdayNumber === 1} to="/classes/monday">
Monday
</HorizontalMenuLink>
</li>

Related

React Native Redux Component not rerendering on Redux State Change

I am working on a shopping basket component in React Native. The content and price of the basket get saved in the global redux store. When the user selects an item, an action gets dispatched to add the item to the basket and to update the total basket price.
The UI however does not get updated on this global state change.
My Reducer
const INITIAL_STATE = {
basket: [],
basketPrice: 0,
};
const mainReducer = (state = INITIAL_STATE, action) => {
const stateCpy = state;
switch (action.type) {
case 'SET_BASKET':
stateCpy.basket = action.payload
return stateCpy;
case 'ADD_TO_BASKET':
stateCpy.basket.push(action.payload)
return stateCpy
case 'REMOVE_FROM_BASKET':
let tempItems = stateCpy.basket
for (var x = 0; x < stateCpy.basket.length; x++) {
if (stateCpy.basket[x]._id === action.payload) {
tempItems.splice(x, 1)
break;
}
}
stateCpy.basket = tempItems
return stateCpy
case 'SET_BASKET_PRICE':
stateCpy.basketPrice = action.payload
console.log(stateCpy.basketPrice)
return stateCpy
default:
return state
}
};
export default mainReducer
My Actions
const setBasket = basket => ({
type: 'SET_BASKET',
payload: basket,
});
const addToBasket = item => ({
type: 'ADD_TO_BASKET',
payload: item,
});
const removeFromBasket = item_id => ({
type: 'REMOVE_FROM_BASKET',
payload: item_id,
});
const setBasketPrice = price => ({
type: 'SET_BASKET_PRICE',
payload: price,
});
export default actions = {
setBasket,
addToBasket,
removeFromBasket,
setBasketPrice
}
My UI Component
...
import { useSelector, useDispatch } from 'react-redux'
export const RestaurantView = ({ navigation, route }) => {
const basket = useSelector((state) => state.basket)
const basketPrice = useSelector((state) => state.basketPrice)
const dispatch = useDispatch()
...
function calcBasketPrice(){
let tempBasketPrice = 0
basket.forEach(element => {
tempBasketPrice += element.price
});
return tempBasketPrice
}
function addToBasket(item) {
dispatch(actions.setBasketPrice(calcBasketPrice() + item.price))
dispatch(actions.addToBasket(item))
}
return ( <View>
<ItemCard onPress={addToBasket}> </ItemCard>
<Text style={{ textAlign: "right", padding: 15, fontSize: 20 }}> {basketPrice}</Text>
</View>)
}
When logging the basketPrice to console in the reducer, it logs the correct, updated value on each press/dispatch but there no changes in the UI. When a local state change is made to force a rerender, it renders with the correct value from the global store.
Your stateCpy variable is actually not a copy, but just a reference to state - so your reducer is actually modifying the old redux state instead of creating a new one.
Since this is a very outdated style of Redux and in modern Redux createSlice reducers it is totally okay to modify state, I would recommend you not to fix this in legacy Redux, but to take a look at modern Redux - which is also less error-prone and about 1/4 of the code you would write with legacy Redux.
Rewriting your reducer is not a lot of work and in the long run you will really benefit from the (since 2019) new style.
Take a look at the official Redux tutorial

how to display element firstname and lastname ... from strore redux

find here my code whene I try to get firstName I get the issue “Cannot read properties of undefined” could some one give a solution or expalain what's the issue whith my code
let state = useSelector(state => {
// console.log('State: ', state.profile);
return state.profile;
});
const [profile, setProfile] = useState();
const [formState, setFormState] = useState({
isValid: false,
values: {},
touched: {},
errors: {}
});
useEffect(()=>{
console.log({accesstoken: token});
if(token)
dispatch(profileActions.getInfos({accesstoken: token}));
},[])
useEffect(()=>{
if (state)
setProfile(state)
console.log(profile.profile);
},[state, profile])
/// here the peace of code to display the profile.data.firstname
ListItemText primary="Date de creation" secondary{JSON.stringify(profile.data.firstName)} />
Did you console the variable state just before your useEffect.
I guess no need to use (useState & useEffect) you can directly use yout variable state.
try to add a console.log(state) before or after the useEffect and share it with as.

Pagination testing via cypress JS

I need to test that when I select some model car, as a result, I have only that model in all pages. So basically I do pagination testing. But I do something wrong that it does not moves to another page although selectors are correct. Please tell me what I am doing wrong.
findItem("AUDI")
});
async function findItem(value) {
async function findInPage(index) {
let found = false;
cy.get("li.page-item:not(.page-pre):not(.page-next)").as("pages");
await cy.get("#pages")
.its("length")
.then(async (len) => {
if (index >= len) {
return false;
} else {
await cy.get("#pages")
.eq(index)
.click();
await cy.get("table tr > td:nth-child(5) p")
.each(async ($itemNameEl, index) => {
const itemText = $itemNameEl.text().toUpperCase();
cy.log('item ', itemText);
if (itemText.includes(value)) {
found = true;
await cy.wrap($itemNameEl).eq(index);
//cy.get('.chakra-text.css-0').should('include', value)
cy.get('.css-1b4k5p > .chakra-text.css-0')
.each(($el) => {
expect($el.text().toUpperCase()).to.include(value)
})
return false;
}
})
.then(() => {
if (!found) {
findInPage(++index);
}
});
}
});
}
findInPage(0);
}
A simple example without aliases, async functions, etc, just using recursion and a single NEXT PAGE button (that I see you have in your screen shot) would look like this (tested and working on bootstrap pagination examples):
it('Some test', () => {
cy.visit('/')
const findInPage = () => {
cy.get('li:has(a.page-link:has(span:contains(»)))').then((el) => {
// do your test
if (el.hasClass('disabled')) {
// on last page, break out
return
}
cy.wrap(el).click()
findInPage()
})
}
findInPage()
});
How this works: Look for li element which represents a single pagination next-page button, in bootstrap case it has child a tag which has child span tag which contains an icon ». Once you reach last page, the button get's disabled by adding .disabled class to li tag which is checked for on every page. Using this it doesn't matter if you have 3 or 33 pages and if some numbers are hidden with ...
Reference: https://glebbahmutov.com/blog/cypress-recurse/

subscribing a http get call - why do I need to refresh my frontend page to get the data after it got altered?

So I have a web application built with angular on the frontend and node js on the backend. Im building an FAQ app where a user can add categories. See the photo below:
Those categories are stored in a pg database. When navigating to the page "Categories", I make a http call to my node js backend to get all categories from the database. My api looks like this:
app.get('/all-categories', async (req, res) => {
const result = await pool.query('select * from "Category"');
res.json(result.rows);
});
And following is my frontend, the categories.ts:
export class CategoriesComponent implements OnInit, OnDestroy {
categories: Category[] = [];
subscriptions = new Subscription();
constructor(private httpService: HttpService) { }
ngOnInit(): void {
const subscription = this.httpService.getAllCategories().subscribe(allCategories => {
this.categories = allCategories;
});
this.subscriptions.add(subscription);
}
ngOnDestroy(): void {
this.subscriptions.unsubscribe();
}
onDeleteButtonClick(categoryUUID: string) {
this.httpService.deleteCategoryByUUID(categoryUUID).pipe(
take(1)
).subscribe();
}
}
Now, whats the problem?
The problem is, when I navigate to the Categories page, all the categories are shown. But when I delete a category using the red delete button, I need to refresh the page in order to see the updated number of categories.
Why is this so? And what do I need to do to make it work?
I reckon to change the way of how you obtain the data from your service, you could subscribe to the observable in an asynchronous way with async pipe from the template, and when one category will be eliminated, you can update your observable.
on this way your .ts should be in this way:
export class CategoriesComponent implements OnInit {
categories$: Observable<Category[]> = [];
constructor(private httpService: HttpService) { }
ngOnInit(): void {
this.categories$ = this.httpService.getAllCategories();
}
async onDeleteButtonClick(categoryUUID: string) {
const deleteCategory = await this.httpService.deleteCategoryByUUID(categoryUUID).pipe(take(1))
.toPromise();
if(deleteCategory) {
this.categories$ = this.httpService.getAllCategories();
}
}
}
and your template something like this:
<div *ngIf="categories$ | async as categories">
<div *ngFor="let cat of categories">cat</div>
</div>
You can add tap operator in deleteCategoryByUUID, and filter categories:
this.categories = this.categories.filter(c => c !== c.uid);

Syncing React-Slick with Query Params

I'm using React-Slick to render <Report /> components in a carousel. I would like to sync each <Report />'s reportId with query params.
For example, a user would be able to see a specific report by going to myapp.com/reports?id=1 and it would take them to that specific "slide".
The problem I'm having is that the report data is being loaded before the slides are initialized. I can't find any good examples of react-slick's onInit or onReInit.
Instead of using onInit or onReInit, I just utilized the initialSlide setting and used componentDidUpdate().
componentDidUpdate = (prevProps, prevState) => {
const queryParams = qs.parse(this.props.location.search)
if (prevProps.reports !== this.props.reports) {
const sortedReports = this.sortReportsByDate(this.props.reports)
this.setSlideIndex(sortedReports.findIndex(i => i.id === parseInt(queryParams.reportId, 10)))
this.setQueryParams({
reportId: this.sortReportsByDate(this.props.reports)[this.state.slideIndex].id
})
}
if (prevState.slideIndex !== this.state.slideIndex) {
this.setQueryParams({
reportId: this.sortReportsByDate(this.props.reports)[this.state.slideIndex].id
})
}
}
And the settings:
const settings = {
...
initialSlide: reportsSortedByDate.findIndex(i => i.id === parseInt(queryParams.reportId, 10))
...
}
I hope someone finds this useful!

Resources