Ngrx store dispatch is not accepting string as arguments - javascript

I'm taking a test in which I should write code a in such a way that all unit test case gets passed.
Case 1:
it('should dispatch action when dispatchAction is called', async() => {
// you need to spy on store's 'dispatch' method
store = TestBed.get(Store);
spyOn(store, 'dispatch').and.callThrough();
// if you call function dispatchAction with 'movies' paramter. expect store to dispatch action='movies'
component.dispatchAction('movies');
fixture.detectChanges();
expect(store.dispatch).toHaveBeenCalledWith('movies');
});
My code:
dispatchAction($event: string) {
this.store.dispatch({type: 'movie'});
}
But the spec is getting failed throwing the below error
Expected spy dispatch to have been called with [ 'movies' ] but actual calls were [ Object({ type: 'movies' }) ].
Reducer,
export function news (state = initialState, action: Action) {
switch (action.type) {
case LOAD_SECTION_NEWS: {
return {
newsList: mockNewsList,
filter: action.type
};
}
case FILTER_SUBSECTION: {
return {
newsList: mockNewsList,
filter: action.payload
};
}
default:
return state;
}
}
export const getNewsList = (state: any) => {
return state;
};
export const getFilter = (state: any) => {
return state;
};
Action
export class NewsActions {
static LOAD_SECTION_NEWS = '[News] LOAD_SECTION_NEWS';
static FILTER_SUBSECTION = '[News] FILTER_SUBSECTION';
LoadSectionNews(list: News[]): Action {
return {
type: '',
payload: ''
};
}
FilterSubsection(subsection: string) {
return {
type: '',
payload: ''
};
}
}
How do I modify, the reducer in such a way that the unit test case get passed.
This Ngrx is out of syllabus and I've no idea. Please help.

The error reported is about .toHaveBeenCalledWith('movies'); from your test case. The expectation is the word movies to have been used as argument, and this is incorrect.
When you call this.store.dispatch({type: 'movies'}); in your controller, it is passing the object {type: 'movies'} as argument.
as your test is expecting only the word movie, it fails
change your expectation to
expect(store.dispatch).toHaveBeenCalledWith({type: 'movies'});
that will fix your test
Good luck with your studies

var data = 'movies';
this.store.dispatch(data as any)
var data = 'movies';
this.store.dispatch(data as any)
You can achieve the result by casting the string to any

Related

JHipster and React: How to call function after calling asynchronous reducer action?

Use Case: I have a react application generated with JHipster. I need to get data from API, map to form contract, and then submit the form.
Problem: JHipster generated reducer code doesn't return a promise, so how do I know when a reducer action is finished? How can I call a function after getting an entity to update state?
Getting Entity and Returning the ICrudGetAction:
export interface IPayload<T> { // redux-action-type.ts (JHipster Code from node_modules)
type: string;
payload: AxiosPromise<T>;
meta?: any;
}
export type IPayloadResult<T> = ((dispatch: any) => IPayload<T> | Promise<IPayload<T>>);
export type ICrudGetAction<T> = (id: string | number) => IPayload<T> | ((dispatch: any) => IPayload<T>);
// Start of my reducer code
export default (state: AdState = initialState, action): AdState => {
switch (action.type) {
case REQUEST(ACTION_TYPES.FETCH_MYENTITY_LIST):
return {
...state,
errorMessage: null,
updateSuccess: false,
loading: true
};
case FAILURE(ACTION_TYPES.FETCH_MYENTITY_LIST):
return {
...state,
loading: false,
updating: false,
updateSuccess: false,
errorMessage: action.payload
};
case SUCCESS(ACTION_TYPES.FETCH_MYENTITY_LIST): {
const links = parseHeaderForLinks(action.payload.headers.link);
return {
...state,
loading: false,
links,
entities: loadMoreDataWhenScrolled(state.entities, action.payload.data, links),
totalItems: parseInt(action.payload.headers['x-total-count'], 10)
};
}
case ACTION_TYPES.RESET:
return {
...initialState
};
default:
return state;
}
};
export const getEntity: ICrudGetAction<IMyEntity> = id => {
const requestUrl = `${apiUrl}/${id}`;
return {
type: ACTION_TYPES.FETCH_ENTITY,
payload: axios.get<IMyEntity>(requestUrl)
};
};
I try to do this and it works but gives me compile errors:
this.props.getEntity(this.props.match.params.id).then((response) => {
// Map to form attributes and call setState
});
I get error message:
TS2339: Property 'then' does not exist on type 'IPayload | ((dispatch: any) => IPayload)'.
Property 'then' does not exist on type 'IPayload'.
This makes sense because we are not returning a promise. but how can I update the code to return a promise such that I don't break all the things that were autogenerated while also keeping the redux store updated?

Flow not detecting if-block handling potentially undefined (maybe) value. Why?

I am seeing a behavior in Flow that I don't understand. The function that I'm trying to type is a redux reducer. The problem that I'm seeing seems to stem from the fact that the action object provided to the reducer has a payload that is marked with the "maybe" operator: ?. That type looks like this essentially:
type Action = {
type: 'foo',
payload?: { ... }
}
In the reducer I tried to provide an if-gate to handle the case when payload is not defined. In theory, it seems this should work. The simplest case works (see here):
type Foo = {
type: 'foo',
payload?: {
foo: 'foo'
}
};
type Bar = {
type: 'bar',
payload?: {
bar: 'bar',
}
};
const reducer = (state: {} = {}, action: Foo | Bar) => {
switch (action.type) {
case 'foo': {
if (!action.payload) {
return state;
}
return action.payload.foo;
}
case 'bar': {
if (!action.payload) {
return state;
}
return action.payload.bar;
}
default:
return state;
}
}
However, in my actual reducer, which is a bit more involved, I'm unable to do away with the errors. My reducer looks something like this:
const camelize = (x) => x;
const getSearchTypeFromPath = (x) => x;
type A = {
type: '##router/LOCATION_CHANGE',
payload?: {
pathname: string,
query: {},
}
}
type B = {
type: 'action.foo',
payload?: {
mode: string,
params: {}
}
}
const byMode = (
state: {} = {},
action: A | B,
) => {
switch (action.type) {
case '##router/LOCATION_CHANGE': {
if (!action.payload) {
return state;
}
const modeKey: string = camelize(getSearchTypeFromPath(action.payload.pathname));
return {
...state,
[modeKey]: action.payload.query,
};
}
case 'action.foo': {
if (!action.payload) {
return state;
}
const modeKey: string = camelize(action.payload.mode);
return {
...state,
[modeKey]: action.payload.params,
}
}
default:
return state;
}
};
This code, unlike the simplified case above, produces some flow errors: see here:
33: [modeKey]: action.payload.query,
^ Cannot get `action.payload.query` because property `query` is missing in undefined [1].
References:
6: payload?: { ^ [1]
44: [modeKey]: action.payload.params,
^ Cannot get `action.payload.params` because property `params` is missing in undefined [1].
References:
14: payload?: { ^ [1]
In terms of the logic gate handling the possibly undefined payload they are I think the same. So why the error? It's also interesting that there is no error for the first properties that are referenced in each case block: i.e. pathname and mode. Finally, one other thing I noticed is that if I remove the helper functions, which in my example here have just been turned into dummy functions, then I don't get the errors. See here.
Is there anyone out there that can explain what's going on here. I can't make sense of it yet.
The issue is the call to camelize.
Flow resets all type refinements after a function call where that function call could potentially mutate a value. In your example, action is a parameter, so it's plausible that something like this happens:
function camelize(x) {
someAction.payload = undefined;
}
// Now your code crashes
byMode({}, someAction);
Generally the solution is to extract out the thing being tested into a const:
const payload = action.payload;
if (payload === undefined) return;
// OK to use 'payload' after function calls now
See also https://flow.org/en/docs/lang/refinements/#toc-refinement-invalidations or e.g. https://github.com/facebook/flow/issues/5393
type A = {
type: '##router/LOCATION_CHANGE',
payload: {
pathname: string,
query: {},
}
}
type B = {
type: 'action.foo',
payload: {
mode: string,
params: {}
}
}
Don't know the purpose of ? but by removing this will remove your errors.
The only way I have been able to get this to work is to add some ternary checks for each of the references to those properties even after the logic gate like this:
const camelize = (x) => x;
const getSearchTypeFromPath = (x) => x;
type A = {
type: '##router/LOCATION_CHANGE',
payload?: {
pathname: string,
query: {},
}
}
type B = {
type: 'action.foo',
payload?: {
mode: string,
params: {}
}
}
const byMode = (
state: {} = {},
action: A | B,
) => {
switch (action.type) {
case '##router/LOCATION_CHANGE': {
if (!action.payload) {
throw new Error('Ooops');
}
const modeKey: string = camelize(getSearchTypeFromPath(action.payload ? action.payload.pathname : ''));
return {
...state,
[modeKey]: action.payload ? action.payload.query: {},
};
}
case 'action.foo': {
if (!action.payload) {
return state;
}
const modeKey: string = camelize(action.payload.mode || '');
return {
...state,
[modeKey]: action.payload ? action.payload.params : {},
}
}
default:
return state;
}
};
See here for example.
I don't understand why this works even though the code in question wouldn't be reached if action.payload is undefined. The code required to please flow in this case seems less than ideal as it requires checks that don't seem necessary.

Flow vs TypeScript action creator (Redux)

I'm trying to figure out how to implement type annotation for Redux with Flow.
With TypeScript:
const PLAY = 'PLAY';
const RUN = 'RUN';
class PlayAction {
readonly type = PLAY;
constructor(public payload: string) {}
}
class RunAction {
readonly type = RUN;
constructor(public payload: boolean) {}
}
type Actions = PlayAction | RunAction;
function dummyReducer(state: any, action: Actions) {
switch(action.type) {
case PLAY: {
const typeTest = action.payload; // type: string
break;
}
case RUN: {
const typetest = action.payload; // type: boolean
break;
}
}
}
In flow documentation, I found this example: Redux with Flow.
// #flow
type State = { +value: boolean };
type FooAction = { type: "FOO", foo: boolean };
type BarAction = { type: "BAR", bar: boolean };
type Action = FooAction | BarAction;
function reducer(state: State, action: Action): State {
switch (action.type) {
case "FOO": return { ...state, value: action.foo };
case "BAR": return { ...state, value: action.bar };
default:
(action: empty);
return state;
}
}
The big problems with Flow approach:
1) use in 2 places strings, so it bad practice for miss spelling mistake.
2) is not maintainable to change the string in 2 places every time I want to change the action type value.
How can I solve this problem with Flow? Any ideas?
I found the some answer, but it don't work pretty much,
and again you can't work with class action creators. :/
type _ExtractReturn<B, F: (...args: any[]) => B> = B;
export type ExtractReturn<F> = _ExtractReturn<*, F>;
Action file //
export const SET_NAME = 'SET_NAME';
export const SET_AGE = 'SET_AGE';
const setName = (name: string) => {
return {type: SET_NAME, payload: name}
}
const setAge = (age: number) => {
return {type: SET_AGE, payload: age}
}
export type Actions =
ExtractReturn<typeof setName> |
ExtractReturn<typeof setAge>
credit: Shane Osbourne
Redux & Flow-type

Flow union type refinement defeated by filter

The example can be found # flowtype.org/try. Here I would expect the type refinement in the conditional to work in both examples, while it only works in the simpler one. When I introduce Array.filter the refinement does not take effect. Is this a bug in Flow or mis-usage on my part?
/* #flow */
export type Action =
{| type: 'ACTION1', payload: string |}
| {| type: 'ACTION2', payload: number |}
| {| type: 'ACTION3' |}
const things = (state: Array<number> = [], action: Action): Array<number> => {
if (action.type === 'ACTION2') {
return state.filter((thing) => { return thing !== action.payload })
} else {
return state
}
}
things([1, 5], { type: 'ACTION2', payload: 5 })
const add = (state: number = 0, action: Action): number => {
if (action.type === 'ACTION2') {
return state + action.payload
} else {
return state
}
}
add(0, { type: 'ACTION2', payload: 5 })
generates the following errors:
10: return state.filter((thing) => { return thing !== action.payload })
^ property `payload`. Property not found in
6: | {| type: 'ACTION3' |} ^ object type
This is simply a matter of Flow aggressively invalidating type refinements. Flow does not know what filter is going to do with the callback you pass. Maybe it's going to save it and call it later. Flow also doesn't realize that nothing else reassigns action. As far as it's concerned, action might be reassigned to {type: 'ACTION3'} by the time the callback is called. Pulling the payload out into a const solves the issue:
const payload = action.payload;
return state.filter((thing) => { return thing !== payload })

Flowtype not detecting type of property inside object

I am hitting the following error
90: var b = action.data;
^^^^ property `data`. Property not found in
90: var b = action.data;
^^^^^^ object type
This is inside a function that receives action as an argument like this:
export default (state: SecurityGroupState = { groups: null, editingIPRange: null }, action: Action) => {
The type Action is imported using import type like so:
import type { Action } from "../../actions";
And it is declared as so:
export type Action = {
type: string,
data: Object,
} | {
type: string,
error: Object,
};
The code that is triggering the initial error is the following:
switch (action.type) {
case GET:
if (action.error) {
console.error(action.error);
break;
}
var a = action.data; // no error here
const groupsCopy2 = _.map(state.groups, () => {
var b = action.data;
});
}
So in the var a = ... line, Flow is OK with action.data, but inside the map lambda it doesn't seem to know that action: Action can have a data key.
Flow is pessimistic about refinements, it considers that every function call could modify action.data. As for a fix, you can use a const binding
const data = action.data
const groupsCopy2 = _.map(state.groups, () => {
var b = data;
});

Categories

Resources