Issue testing RTK Query: The preloadedState argument passed to createStore has unexpected type of "array" - redux-toolkit

I'm learning RTK Query.
So, in a test, I have the following error:
The preloadedState argument passed to createStore has unexpected type of "array". Expected argument to be an object with the following keys: "queries", "mutations", "provided", "subscriptions", "config"
This is my test:
test("Can use preloadedState", () => {
const initialPosts = [
{
id: 1,
body: "Lorem ipsum",
},
];
// wrap component with custom render function
renderWithProviders(<GenericList />, {
preloadedState: {
postsSlice: initialPosts,
},
});
const loremIpsum = screen.getByText(/lorem ipsum/i);
expect(loremIpsum).toBeInTheDocument();
});
I have followed this tutorial https://redux.js.org/usage/writing-tests#preparing-initial-test-state.
This is my test-utils.js file:
import React from 'react'
import { render } from '#testing-library/react'
import { Provider } from 'react-redux'
import { setupStore } from '../../store/index'
import { setupListeners } from '#reduxjs/toolkit/dist/query'
export function renderWithProviders(
ui,
{
preloadedState = {},
// Automatically create a store instance if no store was passed in
store = setupStore(preloadedState),
...renderOptions
} = {}
) {
setupListeners(store.dispatch);
function Wrapper({ children }) {
return <Provider store={store}>{children}</Provider>
}
return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) }
}
This is my store:
import { configureStore } from "#reduxjs/toolkit";
import { postsSlice } from "../features/postsSlice";
export const setupStore = preloadedState => {
return configureStore({
reducer: {
[postsSlice.reducerPath]: postsSlice.reducer,
},
preloadedState,
middleware: getDefaultMiddleware =>
getDefaultMiddleware({
immutableCheck: false,
serializableCheck: false,
}).concat(postsSlice.middleware),
})
}
And finally this is the postsSlice
import { createApi, fetchBaseQuery } from "#reduxjs/toolkit/query/react";
export const postsSlice = createApi({
// Reducer Path it's name shown on Redux Tab
reducerPath: "postsSlice",
baseQuery: fetchBaseQuery({
baseUrl: process.env.REACT_APP_BACKEND_URL,
}),
// With tag type we can invalidate cache
tagTypes: ['posts'],
endpoints: (builder) => ({
getPosts: builder.query({
query: () => "/posts"
})
})
});
export const { useGetPostsQuery } = postsSlice;

You cannot just make up some random contents for your initialState, it has to be exactly the structure of your Redux state. And for RTK Query, that is a very complex internal structure that you should probably not mock (it could change in another version!).
Honestly, to your last question, this is a step backwards - if you want to test RTK Query, test it with a normal Redux store and mock the api.
All you were missing was to wait in your test until the result was rendered.
Faking internal data structures means that your test will just test a very small part of what actually happens.

Related

ngrx jasmine-marbles test resulting the "Received" part returning '?' question mark

I am having an issue with the following tech-stack:
Angular v8,
ionic Angular v5,
ngrx v8,
jasmine-marbles v0.8.3.
I am writing a unit test for "ngrx", in particular the "effects" part.
Following is my code snippet:
import { TestBed } from '#angular/core/testing';
import { provideMockActions } from '#ngrx/effects/testing';
import { Observable } from 'rxjs';
import { InformationEffects } from './information.effects';
import { HttpClientTestingModule, HttpTestingController } from '#angular/common/http/testing';
import { Storage } from '#ionic/storage';
import { DataService } from 'src/app/shared/services/data.service';
import { RouterTestingModule } from '#angular/router/testing';
import { cold, hot } from 'jasmine-marbles';
import { MockStore, provideMockStore } from '#ngrx/store/testing';
import {
InformationRequested,
InformationSuccess,
} from './information.actions';
describe('Information Effects', () => {
let information = {} as any;
const initialState = { information: information};
let actions$: Observable<any>;
let effects: InformationEffects;
let store: MockStore<any>;
let dataService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule, RouterTestingModule],
providers: [
{
provide: Storage,
},
InformationEffects,
MockStore,
provideMockStore({ initialState }),
provideMockActions(() => actions$),
{
provide: DataService,
useValue: jasmine.createSpyObj('DataService', ['getInformation'])
}
]
});
effects = TestBed.get(InformationEffects);
store = TestBed.get(MockStore);
dataService = TestBed.get(DataService);
});
it('should be created', () => {
expect(effects).toBeTruthy();
});
describe('INFORMATION_REQUESTED', () => {
it('should return an InformationSucess action, with the user, on success', () => {
let language = {} as any;
const action = new InformationRequested(language);
const outcome = new InformationSuccess(information);
actions$ = hot('-a-', { a: action });
const response = cold('-a|', { a: information });
const expected = cold('--b', { b: outcome });
dataService.getInformation.and.returnValue(response);
expect(effects.informationAction$).toBeObservable(expected);
});
});
});
When I run the test using "npm test", it failed at the "Received" part showing "?".
It says:
Expected: --b,
Received: --?,
Expected:
[{"frame":20,"notification":{"kind":"N","value":{"payload":{},"type":"[Information]
INFORMATION Success"},"hasValue":true}}]
Received:
[{"frame":20,"notification":{"kind":"N","value":{"payload":{},"type":"[Information]
INFORMATION Success"},"hasValue":true}}],
Please refer below figure:
enter image description here
I had searched the internet for this question mark in the "Received:" section, to no avail. I had also researched on each jasmine-marbles syntaxes, as well the (hot & cold) observable, to understand why & how to use it. Still no solution to get rid of the '?' question mark to have the unit-test being "Success". As such please help me.

reference error when I try to get a value from other slice redux toolkit?

I am trying to import a value form other slice that has some user information, any idea why I am getting this nasty error ? I read it is normal to request data from other slices, the error seem to be like the slice cannot find the store... below is my code structure, my store is at the top of my app, does this getState function works in a component only and not in slice to other slice .
import React from 'react';
import ReactDOM from 'react-dom';
import { HashRouter } from 'react-router-dom';
import App from './App';
import './index.css';
// Redux Tool Kit
import { store } from './app/store';
import { Provider } from 'react-redux';
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
import {
RootState,
store
} from './store';
import {
createSlice,
PayloadAction
} from '#reduxjs/toolkit';
export interface miscState {
dayNumber: true,
dayOfWeek: false,
};
export const miscSlice = createSlice({
name: 'misc',
initialState,
reducers: {
setDisplayDay: (state, action: PayloadAction < {
bool: boolean;type: string
} > ) => {
const {
user,
uid
} = store.getState().global.currentUser;
const setDisplay = async() => {
const docRef = doc(db, colDynamic(user)[0], uid);
await updateDoc(docRef, {
[action.payload.type]: action.payload.bool,
});
};
},
},
});
// Values
export const MiscCurrentState = (state: RootState) => state.misc;
// Action creators are generated for each case reducer function
export const {
setDisplayDay
} = miscSlice.actions;
export default miscSlice.reducer;
import { configureStore } from '#reduxjs/toolkit';
// Global
import globalReducer from './globalSlice';
// Misc
import miscReducer from './miscSlice';
export const store = configureStore({
reducer: {
global: globalReducer,
misc: miscReducer,
},
});
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>;
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch;
In Redux, you are not allowed to access the store from within a reducer. A reducer has to be a pure function that only reads the variables passed into it - so all information you have is the slice's own state and the action being dispatched. You are not allowed to read a global variable, have any kind of side effect or read from the global Redux store to get the data of another slice.

Unable to parse class method decorator in a Babel plugin

I'm writing a Babel plugin that manipulates the AST node related to a specific decorator. I'm traversing the AST but for some reason, my plugin doesn't detect the method decorator - node.decorators is always null when the visitor visits a node.
This is the plugin:
import { ClassMethod, Decorator } from '#babel/types';
import { get } from 'lodash';
import { NodePath, PluginObj } from '#babel/core';
const providerArgumentsTransformer = (): PluginObj => ({
visitor: {
ClassMethod({ node, parent }: NodePath<ClassMethod>) {
const decorator = getProviderDecorator(node.decorators); // <- node.decorators is always null
if (getDecoratorName(decorator) === 'Provides') {
console.log('Success');
}
},
},
});
function getProviderDecorator(decorators: Array<Decorator> | undefined | null): Decorator | undefined {
return decorators?.find((decorator) => get(decorator, 'expression.callee.name') === 'Provides');
}
function getDecoratorName(decorator?: Decorator): string | undefined {
return get(decorator, 'expression.callee.name');
}
export default providerArgumentsTransformer;
I'm testing the decorator as follows:
import { PluginObj } from '#babel/core';
import * as babel from '#babel/core';
import providerArgumentsTransformer from './providerArgumentsTransformer';
const code = `class MainGraph {
Provides(clazz, propertyKey, descriptor) { }
#Provides()
someString(stringProvider) {
return stringProvider.theString;
}
}`;
describe('Provider Arguments Transformer', () => {
const uut: PluginObj = providerArgumentsTransformer();
it('Exposes transformer', () => {
babel.transformSync(code, {
plugins: [
['#babel/plugin-proposal-decorators', { legacy: true }],
['#babel/plugin-proposal-class-properties', { legacy: true }],
[uut, { legacy: true }],
],
configFile: false,
});
});
});
I wonder if the issue is related to how babel.transformSync is used or perhaps the visitor is not configured properly.
Turns out the decorators were missing because #babel/plugin-proposal-decorators clears the decorators when it traverses the AST.
In order to visit the node before #babel/plugin-proposal-decorators I had to modify my visitor a bit. This approach should probably be optimized by visiting ClassBody or ClassExpression instead of Program.
const providerArgumentsTransformer: PluginObj = {
visitor: {
Program(path: NodePath<Program>) {
path.traverse(internalVisitor);
},
},
};
const internalVisitor = {
ClassMethod: {
enter({ node }: NodePath<ClassMethod>) {
// node.decorators are not null anymore
},
},
};

Using VUEX necessary with NODE.js REST Backend

I´m not very experienced with Frontend/Backend Architecture, but i created a simple REST Backend with NODE.js and want to build up a Frontend based on Vue.js and Framework7.
So do you recommend using VUEX there? Or how do you deal with the sessions or the different requests you sending to the Backend?
Thanks a lot!
You don't have to use Vuex, but I'd suggest using Vuex. Here's an example using Vuex and rest api.
In store/actions.js
import {
fetchSomething,
} from '../api/index.js';
export const actions = {
getSomething({ commit }) {
fetchSomething().then((something) => {
commit('UPATED_SOMETHING', something);
});
},
}
In api/index.js
export const fetchSomething = () => {
const url = 'Some endpoint';
return new Promise((resolve) => {
axios.get(url).then((res) => {
const data = res.data;
resolve(data);
}).catch((err) => {
console.log(err);
})
})
}
In store/mutations.js
export const mutations = {
UPATED_SOMETHING(state, data) {
state.something = data;
},
}
In store/index.js
import { getters } from './getters'
import { actions } from './actions'
import { mutations } from './mutations'
// initial state
const state = {
something: null,
}
export default {
state,
getters,
actions,
mutations,
}
In store/getters.js
export const getters = {
getSomething: state => {
return state.something;
},
}

How to use node-simple-schema reactively?

Given that there is not much examples about this, I am following the docs as best as I can, but the validation is not reactive.
I declare a schema :
import { Tracker } from 'meteor/tracker';
import SimpleSchema from 'simpl-schema';
export const modelSchema = new SimpleSchema({
foo: {
type: String,
custom() {
setTimeout(() => {
this.addValidationErrors([{ name: 'foo', type: 'notUnique' }]);
}, 100); // simulate async
return false;
}
}
}, {
tracker: Tracker
});
then I use this schema in my component :
export default class InventoryItemForm extends TrackerReact(Component) {
constructor(props) {
super(props);
this.validation = modelSchema.newContext();
this.state = {
isValid: this.validation.isValid()
};
}
...
render() {
...
const errors = this.validation._validationErrors;
return (
...
)
}
}
So, whenever I try to validate foo, the asynchronous' custom function is called, and the proper addValidationErrors function is called, but the component is never re-rendered when this.validation.isValid() is supposed to be false.
What am I missing?
There are actually two errors in your code. Firstly this.addValidationErrors cannot be used asynchronously inside custom validation, as it does not refer to the correct validation context. Secondly, TrackerReact only registers reactive data sources (such as .isValid) inside the render function, so it's not sufficient to only access _validationErrors in it. Thus to get it working you need to use a named validation context, and call isValid in the render function (or some other function called by it) like this:
in the validation
custom() {
setTimeout(() => {
modelSchema.namedContext().addValidationErrors([
{ name: 'foo', type: 'notUnique' }
]);
}, 100);
}
the component
export default class InventoryItemForm extends TrackerReact(Component) {
constructor(props) {
super(props);
this.validation = modelSchema.namedContext();
}
render() {
let errors = [];
if (!this.validation.isValid()) {
errors = this.validation._validationErrors;
}
return (
...
)
}
}
See more about asynchronous validation here.