Testing an epic that consumes delay and from operators - redux-observable

I have this epic, need help for testing, the epic calls to async method (from operator) and it uses the delay operator
export const getAll$ = (
action$: any,
state: { value: TStore },
{ stock, Error }: TDependencies
) => {
return action$.pipe(
ofType(getAll.toString()),
map(({ meta: { failures} }) => ({
failures,
})),
switchMap(({ failures }) => {
return merge(
of(getAllStart()),
// call api service stock.getAll, this is async
from(stock.getAll()).pipe(
// wait a 1 second, not matter if stock.getAll finish early
delay(OPERATION_DELAY),
switchMap((response: TGetAllResponse) => {
if(response instanceof Error) {
return throwError(response);
}
return of(
getAllSuccess()
);
}),
catchError((error) => {
return failures === 0 ?
of(getAllFailure({ error }))
:
of(getAll({
failures: failures + 1,
})).pipe(
delay(FAILURE_RETRY_TIME),
);
}),
// "cancel" this epic if getAllAbort action is emitted
takeUntil(
action$.ofType(
getAllAbort.toString()
)
)
)
)
})
)
};
Here is my test
import { ActionsObservable } from 'redux-observable';
// some mocking
const state = { value: {} };
const dependencies = {
api: {},
stock: {
getAll() {
return [];
}
},
Error: class Error {},
};
describe('stock epic', () => {
it('should return all products', (done) => {
// start epic getAll$
getAll$(ActionsObservable.of(getAll()), state, dependencies).subscribe((action) => {
console.log(action); // { type: 'STOCK/GET_ALL_START' }
done();
});
});
});
If you see console.log(action); only returns { type: 'STOCK/GET_ALL_START' }
If I use .toArray() test never ends
I am posting lorem impsun because stack overflow complains about my question
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin ac blandit leo. Phasellus non turpis eu mi finibus pharetra nec vel ante. Phasellus non feugiat lorem, nec ultricies quam. In porttitor bibendum facilisis. Donec euismod imperdiet tincidunt. Mauris enim ante, suscipit iaculis mi et, convallis fermentum ante. Vestibulum eget purus pharetra, finibus velit in, porta metus. Vivamus interdum lobortis elit, dignissim tempor sem. Nam ultricies, odio sed tempus convallis, nibh lectus maximus tortor, non sollicitudin enim ex sit amet justo. Nam vel lacus feugiat lorem venenatis interdum.

After several hours, I found the solution, if your epic uses from operator you cannot test it, you need to mock up this operator, in my case I just map this operator to the of operator, with jest this is easy, create in your root project folder, a folder named __mocks__ and create a js file called rxjs.js inside it, and put this code inside
const rxjs = jest.requireActual('rxjs');
rxjs.from = rxjs.of;
module.exports = rxjs;
Then for testing your need to use the class TestScheduler, it is available in rxjs/testing as export const
The code will would like this
import { ActionsObservable } from 'redux-observable';
import { TestScheduler } from 'rxjs/testing';
// your epics to be tested
import epics from './stock';
// actions that you epic will emit
import {
getAll,
getAllStart,
getAllSuccess,
} from '../actions/stock';
// mock the state param (if your epic use it)
import { initialState } from '../reducers/stock';
const state = {
value: {
stock: initialState,
},
};
// mock your dependencies param
const dependencies = {
api: {},
stock: {
getAll() { // in my real code, this is a promise, here is just a normal function, perfect to be use with the of operator
return [];
},
},
Error: class Error {},
};
describe('stock epic', () => {
it('should return all products', (done) => {
// this is just copy-paste
const testScheduler = new TestScheduler((actual, expected) => {
expect(actual).toStrictEqual(expected);
});
testScheduler.run(({ cold, hot, expectObservable, expectSubscriptions, flush }) => {
expectObservable(
epics.getAll$(
ActionsObservable.of(getAll()), // transform action to action$
state, // pass mocked state
dependencies // pass mocked dependencies
)
).toBe(
// my epic has a delay(1000) call
'a 999ms (b|)', {
a: getAllStart(),
b: getAllSuccess()
}
);
done();
});
});
});

Related

"Unexpected digit after hash token" requiring IFC file in React Native

I'm simply trying to perform a require(./file.ifc) into my application so I can run it with threejs and expo-gl. Although when I require it, I get this error:
error: SyntaxError: /Users/niltonsf/Desktop/Project/codes.nosync/prjs12/src/test2.ifc: Unexpected digit after hash token. (28:0)
26 |
27 | DATA;
> 28 | #1= IFCORGANIZATION($,'Autodesk Revit 2021 (ESP)',$,$,$);
| ^
29 | #5= IFCAPPLICATION(#1,'2021','Autodesk Revit 2021 (ESP)','Revit');
30 | #6= IFCCARTESIANPOINT((0.,0.,0.));
How I'm attempting to load it:
import { IFCLoader } from 'three/examples/jsm/loaders/IFCLoader';
import { Asset } from 'expo-asset';
export function Main() {
const getUrl = async () => {
const filePath = require('./test2.ifc') as any;
const asset = Asset.fromModule(filePath);
};
My metro.config.js with I had to insert .ifc so I don't get an error when requiring it:
const { getDefaultConfig } = require('metro-config');
module.exports = (async () => {
const {
resolver: { sourceExts, assetExts },
} = await getDefaultConfig();
return {
transformer: {
babelTransformerPath: require.resolve('react-native-svg-transformer'),
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true,
},
}),
},
resolver: {
assetExts: assetExts.filter((ext) => ext !== 'svg'),
sourceExts: [...sourceExts, 'svg', 'ifc', 'dwg', 'dxf'],
},
};
})();

Perform PUT/PATCH request with #tanstack/vue-query

Currently I'm using vue-query by tanstack in my vue 3 app with the following code structure.
I have the following code in my todo/edit.vue
<template>
<v-container>
<v-row cols="12">
<v-text-field label="Name" v-model="formData.title"></v-text-field>
</v-row>
<v-row cols="12">
<v-text-field label="Price" v-model="formData.price"></v-text-field>
</v-row>
<v-row cols="12">
<v-col md="6" offset="6">
<v-btn color="success" #click="submit">Submit</v-btn>
</v-col>
</v-row>
</v-container>
</template>
<script setup lang="ts">
import { useQuery, useMutation, useQueryClient } from "#tanstack/vue-query"
import { fetchProduct, update} from "../services/product"
import { reactive } from "vue"
import { useRoute, useRouter } from "vue-router"
import type { IProduct } from "#/interface"
const route = useRoute()
let formData = reactive<IProduct>({
id: "",
title: "",
price: "",
category: "",
email: "",
})
const {
isLoading,
isError,
data: loadedData,
error,
refetch,
} = useQuery({
queryKey: ["getOneProduct", route.params.id],
queryFn: fetchProduct,
select: (data: any) => (formData = data),
})
const mutation = useMutation({
mutationFn: ({id, data}) => {
return update(id, data)
},
onSuccess: () => {
alert("success")
},
})
const submit = () => {
mutation.mutate({id:formData.id, data:formData})
}
</script>
My services/product.ts contains
import { API } from "../util/API"
import type { IProduct } from "../interface/IProduct"
export interface IProductsResponse {
products?: Array<IProduct>
total?: number
skip?: number
limit?: number
}
export const fetchProducts = async (): Promise<IProductsResponse> => {
return await API.get<IProductsResponse, any>(`/products?limit=10&select=id,title,price,category`)
}
export const fetchProduct = async (param: any): Promise<IProduct> => {
const id = param.queryKey[1]
const response = await API.get<IProduct, any>(`products/${id}`)
return response
// return await API.get<IProduct, any>(`/products/`)
}
export const update = async (id: any, data: any): Promise<IProduct> => {
return API.put(`/products/${id}`, data)
}
With the above code setup I'm getting the following errors:
Property 'id' does not exist on type 'void'.
Property 'data' does not exist on type 'void'.
Refereeing to the line:
mutationFn: ({id, data}) => {
return update(id, data)
},
And error message
Argument of type '{ id: string | number; data: { id: string | number; title: string; price: string | number; category: string; email: string; }; }' is not assignable to parameter of type 'void'.
Refereeing to line:
mutation.mutate({id:formData.id, data:formData})
What would be the correct way to perform PUT/PATCH operation with "#tanstack/vue-query" ?

Testing AsyncStorage with Redux Thunk

When I try to test if a Cat is added correctly to the Async Storage I am receiving null. How can I solve this? In the app is working correctly. I am using React Native, React-Native-Async-Storage, Redux Thunk and Redux Persist.
REDUCER
export const INITIAL_STATE: CatsState = {
cats: [],
selectedCat: null
}
const catsReducer = (state = INITIAL_STATE, action: CatsAction): CatsState => {
switch (action.type) {
case CatsActionTypes.SET_CATS:
return { ...state, cats: action.payload }
case CatsActionTypes.SELECT_CAT:
return { ...state, selectedCat: action.payload }
default:
return state
}
}
ACTION
export const addCat = ({ cat }: AddCatData): ThunkAction<void, RootState, null, CatsAction> => {
return async (dispatch: Dispatch<CatsAction>) => {
try {
const response = await AsyncStorage.getItem(STORAGE_KEYS.cats)
const cats: Cat[] = response ? JSON.parse(response) : []
cats.push(cat)
await AsyncStorage.setItem(STORAGE_KEYS.cats, JSON.stringify(cats))
dispatch({ type: CatsActionTypes.SET_CATS, payload: cats })
} catch (error) {
console.log(error)
}
}
}
TEST
beforeEach(async () => await AsyncStorage.clear())
describe('add cat', () => {
it('persist cat correctly into local storage', async () => {
const cat: Cat = {
id: '1',
name: 'Cat',
breed: 'Breed',
age: 1,
gender: 'male',
weight: 5,
size: 'Big',
color: 'Brown',
mood: 'Angry',
description: 'Cat description',
friendly: 3,
liked: true
}
addCat({ cat })
const response = await AsyncStorage.getItem(STORAGE_KEYS.cats)
const cats: Cat[] = JSON.parse(response)
const expectedArray = { cats: [cat] }
expect(cats).toStrictEqual(expectedArray)
})
})
Thank you
Use official documentation react-native-async-storage package: https://react-native-async-storage.github.io/async-storage/docs/advanced/jest
UPDATED:
For you case you need do several steps:
#1 Mock your data
const CAT_MOCK = {
id: '1',
name: 'Cat',
}
#2 Mock Storage
const STORAGE_MOCK = {
[STORAGE_KEYS.cats]: [CAT_MOCK]
}
#3 Mock library for your case
jest.mock('#react-native-async-storage', () => ({
getItem: jest.fn((item) => {
return new Promise((resolve, reject) => {
resolve(STORAGE_MOCK[item]);
});
}),
}));

How to display all the products when clicking on "selectAll" and also before selecting any filter?

**I'm creating a webshop with Vue & Firebase. The filter works but I want it to show all the products before choosing a specific filter. Here are the links to filters, in addition, I have a link to show all products: **
<v-list-item link>
<v-list-item-content #click="selectAll()">
<v-list-item-title>All Categories</v-list-item-title>
</v-list-item>
<v-list-item link>
<v-list-item-content #click="selectElectronics()">
<v-list-item-title>Electronics & Lights</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item link>
<v-list-item-content #click="selectKitchen()">
<v-list-item-title>Kitchen</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item link>
<v-list-item-content #click="selectHobbies()">
<v-list-item-title>Hobbies & Free-time</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-col
sm="5"
md="6"
v-for="item in filterCategoryItems"
:key="item.name"
>
**What should be changed in the computed to make it work? It worked before with the commented line but not anymore I don't know why
Error in render: "TypeError: Cannot read property 'includes' of undefined"
**
export default {
data() {
return {
categoryChoice: "",
};
},
methods: {
selectAll() {
this.categoryChoice = "";
},
selectElectronics() {
this.categoryChoice = "electronics";
},
selectKitchen() {
this.categoryChoice = "kitchen";
},
selectHobbies() {
this.categoryChoice = "hobbies";
},
computed: {
filterCategoryItems() {
return this.menuItems.filter(
filterItem => filterItem.category == this.categoryChoice
// (filterItem) => filterItem.category.includes(this.categoryChoice)
);
},
menuItems() {
return this.$store.getters.getMenuItems;
},
basket() {
return this.$store.getters.getBasketItems;
},
favItems() {
return this.$store.getters.getFavItems;
},
},
};
Your script is not build up correctly. If you fix indenting you might have noticed.
Also data() should return an Object:
<script>
export default {
data() {
return {
categoryChoice: ""
};
},
methods: {
selectAll() {
this.categoryChoice = "";
},
selectElectronics() {
this.categoryChoice = "electronics";
},
selectKitchen() {
this.categoryChoice = "kitchen";
},
selectHobbies() {
this.categoryChoice = "hobbies";
}
},
computed: {
filterCategoryItems() {
return this.menuItems.filter(
(filterItem) => filterItem.category == this.categoryChoice
// (filterItem) => filterItem.category.includes(this.categoryChoice)
);
},
menuItems() {
return this.$store.getters.getMenuItems;
},
basket() {
return this.$store.getters.getBasketItems;
},
favItems() {
return this.$store.getters.getFavItems;
}
}
};
</script>

React Testing Library / Jest Update not wrapped in act(...)

I've been getting this error message regarding act while testing.
Warning: An update to EmployeesDashboard inside a test was not
wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
in EmployeesDashboard (at EmployeesDashboard.test.tsx:69)"
I can't figure out why, since it seems I've already wrapped everything in act... What in here should I also be wrapping in act? Any help would be greatly appreciated since I'm fairly new to testing.
import React from 'react';
import { mocked } from 'ts-jest/utils';
import { act } from 'react-dom/test-utils';
import { EmployeesDashboard } from '../EmployeesDashboard';
import {
render,
wait,
waitForElementToBeRemoved,
} from '#testing-library/react';
import { Employee } from '#neurocann-ui/types';
import { getAll } from '#neurocann-ui/api';
jest.mock('#neurocann-ui/api');
const fakeEmployees: Employee[] = [
{
email: 'joni#gmail.com',
phone: '720-555-555',
mailingAddress: {
type: 'home',
streetOne: '390 Blake St',
streetTwo: 'Apt 11',
city: 'Denver',
state: 'CO',
zip: '90203',
},
name: 'Joni Baez',
permissions: ['Employee'],
hireDate: new Date('2020-09-19T05:13:12.923Z'),
},
{
email: 'joni22#gmail.com',
phone: '720-555-555',
mailingAddress: {
type: 'home',
streetOne: '390 Blake St',
streetTwo: 'Apt 11',
city: 'Denver',
state: 'CO',
zip: '90203',
},
name: 'Joni Baez',
permissions: ['Employee'],
hireDate: new Date('2020-09-19T05:13:12.923Z'),
},
];
mocked(getAll).mockImplementation(
(): Promise<any> => {
return Promise.resolve({ data: fakeEmployees });
},
);
describe('EmployeesDashboard', () => {
it('renders progress loader', () => {
act(() => {
render(<EmployeesDashboard />)
});
const employeesDashboard = document.getElementById(
'simple-circular-progress',
);
expect(employeesDashboard).toBeInTheDocument();
expect(employeesDashboard).toMatchSnapshot();
});
it('renders a table', async () => {
await act(() => { render(<EmployeesDashboard />) });
const table = document.getElementById('employees-table');
expect(table).toBeInTheDocument();
});
});
It usually means that you need to wait for something else to show up before expecting the EmployeesDashboard to be in the document. Wait for something else first, maybe the page's header or title. Try:
it('renders progress loader', async () => {
const {findByText} = render(<EmployeesDashboard />)
await findByText("something else");
const employeesDashboard = document.getElementById(
'simple-circular-progress',
);
expect(employeesDashboard).toBeInTheDocument();
expect(employeesDashboard).toMatchSnapshot();
});
Try adding a timer to your mock.
Instead of:
mocked(getAll).mockImplementation(
(): Promise<any> => {
return Promise.resolve({ data: fakeEmployees });
},
);
Try:
mocked(getAll).mockImplementation(() => {
return new Promise(resolve => {
setTimeout(() => resolve(123), 0);
});
});