next-i18next, next export & 404 on non-generated pages (with fallback true/blocking) - vercel

Having followed the documentation on i18next/next-i18next to configure i18n and then the instructions on this locize blog post on how to export static sites with next export, I am able to export localised versions of each page.
The problem is that pages that have not been statically generated return a 404, despite setting fallback: true in the getStaticPaths return object. The page works on my localhost but not when deployed with Vercel.
Code:
const ArticlePage: NextPageWithLayout<Props> = ({ article }: Props) => {
const { i18n, t } = useTranslation('page/news/article')
const router = useRouter()
if (router.isFallback) return <div>Loading...</div>
return <div>Article</div>
}
export const getStaticPaths: GetStaticPaths = async () => {
let paths: { params: { aid: string; locale: TLocale } }[] = []
try {
const response = await api.get(`/articles?per_page=9999`)
const articles = response.data.data as IArticle[]
articles.forEach((a) => {
getI18nPaths().forEach((p) => {
paths.push({
params: {
aid: a.base_64_id,
locale: p.params.locale,
},
})
})
})
return {
paths,
fallback: true,
}
} catch (error) {
return {
paths,
fallback: true,
}
}
}
export const getStaticProps: GetStaticProps = async ({ locale, params }) => {
try {
const article = await api.get(`/articles/${params?.aid}`)
return {
props: {
...(await serverSideTranslations(locale || 'en', [
'footer',
'header',
'misc',
'page/news/index',
'page/news/article',
])),
article: article.data as IArticle,
},
}
} catch (error) {
return {
notFound: true,
}
}
}
ArticlePage.getLayout = function getLayout(page: ReactElement) {
return <Layout>{page}</Layout>
}
export default ArticlePage
"i18next": "22.4.9",
"next-i18next": "^13.1.5",
"react-i18next": "^12.1.5",
There is a warning in the console react-i18next:: You will need to pass in an i18next instance by using initReactI18next when entering an non-generated page (alongside the not-found error of course). An issue raised about this warning is interesting but I could not find an answer to my issue within: https://github.com/i18next/next-i18next/issues/1917.
Attempts to fix:
adding revalidate: 10 to the return object of getStaticProps
using fallback: 'blocking'
trying a few different variants of localePath in next-i18next.config including the recommendation featured here: https://github.com/i18next/next-i18next#vercel-and-netlify
adding react: { useSuspense: false } to next-i18next.config
combinations of the above

Related

How to mock vue composable functions with jest

I'm using vue2 with composition Api, vuex and apollo client to request a graphql API and I have problems when mocking composable functions with jest
// store-service.ts
export function apolloQueryService(): {
// do some graphql stuff
return { result, loading, error };
}
// store-module.ts
import { apolloQueryService } from 'store-service'
export StoreModule {
state: ()=> ({
result: {}
}),
actions: {
fetchData({commit}) {
const { result, loading, error } = apolloQueryService()
commit('setState', result);
}
},
mutations: {
setState(state, result): {
state.result = result
}
}
}
The Test:
// store-module.spec.ts
import { StoreModule } from store-module.ts
const store = StoreModule
describe('store-module.ts', () => {
beforeEach(() => {
jest.mock('store-service', () => ({
apolloQueryService: jest.fn().mockReturnValue({
result: { value: 'foo' }, loading: false, error: {}
})
}))
})
test('action', async ()=> {
const commit = jest.fn();
await store.actions.fetchData({ commit });
expect(commit).toHaveBeenCalledWith('setData', { value: 'foo' });
})
}
The test fails, because the commit gets called with ('setData', { value: undefined }) which is the result from the original apolloQueryService. My Mock doesn't seem to work. Am I doing something wrong? Appreciate any help, thanks!
Try this :
// store-module.spec.ts
import { StoreModule } from store-module.ts
// first mock the module. use the absolute path to store-service.ts from the project root
jest.mock('store-service');
// then you import the mocked module.
import { apolloQueryService } from 'store-service';
// finally, you add the mock return values for the mock module
apolloQueryService.mockReturnValue({
result: { value: 'foo' }, loading: false, error: {}
});
/* if the import order above creates a problem for you,
you can extract the first step (jest.mock) to an external setup file.
You should do this if you are supposed to mock it in all tests anyway.
https://jestjs.io/docs/configuration#setupfiles-array */
const store = StoreModule
describe('store-module.ts', () => {
test('action', async ()=> {
const commit = jest.fn();
await store.actions.fetchData({ commit });
expect(commit).toHaveBeenCalledWith('setData', { value: 'foo' });
})
}

Conditionally execute a graphql mutation after a query is fetched

Scenario
When a user is authenticated (isAuthenticated booelan ref):
Check if a user has preferences by a graphql call to the backend (useViewerQuery)
If there are no preferences for the user set the default (useSetPreferenceDefaultMutation)
Problem
Both the query and the mutation work correctly in the graphql Playground and in the Vue app. They have been generated with the graphql codegenerator which uses useQuery and useMutation in the background.
The issue we're having is that we can't define the correct order. Sometimes useSetPreferenceDefaultMutation is executed before useViewerQuery. This resets the user's settings to the defaults and it not the desired behavior.
Also, on a page refresh all is working correctly. However, when closing an reopening the page it always calls useSetPreferenceDefaultMutation.
Code
export default defineComponent({
setup() {
const {
result: queryResult,
loading: queryLoading,
error: queryError,
} = useViewerQuery(() => ({
enabled: isAuthenticated.value,
}))
const {
mutate: setDefaultPreferences,
loading: mutationLoading,
error: mutationError,
called: mutationCalled,
} = useSetPreferenceDefaultMutation({
variables: {
language: 'en-us',
darkMode: false,
},
})
onMounted(() => {
watchEffect(() => {
if (
isAuthenticated.value &&
!queryLoading.value &&
!queryResult.value?.viewer?.preference &&
!mutationCalled.value
) {
void setDefaultPreferences()
}
})
})
return {
isAuthenticated,
loading: queryLoading || mutationLoading,
error: queryError || mutationError,
}
},
})
Failed efforts
We opened an issue here and here to have extra options on useQuery or useMutation which could help in our scenario but no luck.
Use fetch option with sync or post on watchEffect
Use watch instead of watchEffect
Thanks to comment from #xadm it's fixed now by using the onResult event hook on the query, so it will execute the mutation afterwards.
onResult(handler): Event hook called when a new result is available.
export default defineComponent({
setup(_, { root }) {
const {
loading: queryLoading,
error: queryError,
onResult: onQueryResult,
} = useViewerQuery(() => ({
enabled: isAuthenticated.value,
}))
const {
mutate: setDefaultPreferences,
loading: mutationLoading,
error: mutationError,
} = useSetPreferenceDefaultMutation({
variables: {
language: 'en-us',
darkMode: false,
},
})
onQueryResult((result) => {
if (!result.data.viewer.preference) {
void setDefaultPreferences()
}
})
return {
isAuthenticated,
loading: queryLoading || mutationLoading,
error: queryError || mutationError,
}
},
})

How to solve Avoided redundant navigation to current location error in vue?

I am new in vue and i got the error after user logged in and redirect to another route.
Basically i am a PHP developer and i use laravel with vue. Please help me to solve this error.
Uncaught (in promise) Error: Avoided redundant navigation to current location: "/admin".
Here is the screenshot too
Vue Code
methods: {
loginUser() {
var data = {
email: this.userData.email,
password: this.userData.password
};
this.app.req.post("api/auth/authenticate", data).then(res => {
const token = res.data.token;
sessionStorage.setItem("chatbot_token", token);
this.$router.push("/admin");
});
}
}
Vue Routes
const routes = [
{
path: "/admin",
component: Navbar,
name: "navbar",
meta: {
authGuard: true
},
children: [
{
path: "",
component: Dashboard,
name: "dashboard"
},
{
path: "users",
component: Users,
name: "user"
}
]
},
{
path: "/login",
component: Login,
name: "login"
}
];
const router = new VueRouter({
routes,
mode: "history"
});
router.beforeEach((to, from, next) => {
const loggedInUserDetail = !!sessionStorage.getItem("chatbot_token");
if (to.matched.some(m => m.meta.authGuard) && !loggedInUserDetail)
next({ name: "login" });
else next();
});
As I remember well, you can use catch clause after this.$router.push. Then it will look like:
this.$router.push("/admin").catch(()=>{});
This allows you to only avoid the error displaying, because browser thinks the exception was handled.
I don't think suppressing all errors from router is good practice, I made just picks of certain errors, like this:
router.push(route).catch(err => {
// Ignore the vuex err regarding navigating to the page they are already on.
if (
err.name !== 'NavigationDuplicated' &&
!err.message.includes('Avoided redundant navigation to current location')
) {
// But print any other errors to the console
logError(err);
}
});
Maybe this is happening because your are trying to route to the existing $route.matched.path.
For original-poster
You may want to prevent the error by preventing a route to the same path:
if (this.$route.path != '/admin') {
this.$router.push("/admin");
}
Generic solutions
You could create a method to check for this if you are sending dynamic routes, using one of several options
Easy: Ignore the error
Hard: Compare the $route.matched against the desired route
1. Ignore the error
You can catch the NavigationDuplicated exception and ignore it.
pushRouteTo(route) {
try {
this.$router.push(route);
} catch (error) {
if (!(error instanceof NavigationDuplicated)) {
throw error;
}
}
}
Although this is much simpler, it bothers me because it generates an exception.
2. Compare the $route.matched against the desired route
You can compare the $route.matched against the desired route
pushRouteTo(route) {
// if sending path:
if (typeof(route) == "string") {
if (this.$route.path != route) {
this.$router.push(route);
}
} else { // if sending a {name: '', ...}
if (this.$route.name == route.name) {
if ('params' in route) {
let routesMatched = true;
for (key in this.$route.params) {
const value = this.$route.params[key];
if (value == null || value == undefined) {
if (key in route.params) {
if (route.params[key] != undefined && route.params[key] != null) {
routesMatched = false;
break;
}
}
} else {
if (key in route.params) {
if (routes.params[key] != value) {
routesMatched = false;
break
}
} else {
routesMatched = false;
break
}
}
if (!routesMatched) {
this.$router.push(route);
}
}
} else {
if (Object.keys(this.$route.params).length != 0) {
this.$router.push(route);
}
}
}
}
}
This is obviously a lot longer but doesn't throw an error. Choose your poison.
Runnable demo
You can try both implementations in this demo:
const App = {
methods: {
goToPageCatchException(route) {
try {
this.$router.push(route)
} catch (error) {
if (!(error instanceof NavigationDuplicated)) {
throw error;
}
}
},
goToPageMatch(route) {
if (typeof(route) == "string") {
if (this.$route.path != route) {
this.$router.push(route);
}
} else { // if sending a {name: '', ...}
if (this.$route.name == route.name) {
if ('params' in route) {
let routesMatched = true;
for (key in this.$route.params) {
const value = this.$route.params[key];
if (value == null || value == undefined) {
if (key in route.params) {
if (route.params[key] != undefined && route.params[key] != null) {
routesMatched = false;
break;
}
}
} else {
if (key in route.params) {
if (routes.params[key] != value) {
routesMatched = false;
break
}
} else {
routesMatched = false;
break
}
}
if (!routesMatched) {
this.$router.push(route);
}
}
} else {
if (Object.keys(this.$route.params).length != 0) {
this.$router.push(route);
}
}
} else {
this.$router.push(route);
}
}
},
},
template: `
<div>
<nav class="navbar bg-light">
Catch Exception
Match Route
</nav>
<router-view></router-view>
</div>`
}
const Page1 = {template: `
<div class="container">
<h1>Catch Exception</h1>
<p>We used a try/catch to get here</p>
</div>`
}
const Page2 = {template: `
<div class="container">
<h1>Match Route</h1>
<p>We used a route match to get here</p>
</div>`
}
const routes = [
{ name: 'page1', path: '/', component: Page1 },
{ name: 'page2', path: '/page2', component: Page2 },
]
const router = new VueRouter({
routes
})
new Vue({
router,
render: h => h(App)
}).$mount('#app')
<link href="https://cdn.jsdelivr.net/npm/bootstrap#4.5.3/dist/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.12/vue.min.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app"></div>
Just to make this complete you can also compare the from and to route fullPaths and compare them against each other which seems to me a simple, valid and reusable solution.
Here is an example in a component method:
move(params){
// get comparable fullPaths
let from = this.$route.fullPath
let to = this.$router.resolve(params).route.fullPath
if(from === to) { 
// handle any error due the redundant navigation here
// or handle any other param modification and route afterwards
return
}
// route as expected
this.$router.push(params)
}
If you wanna use that you just put your route params in it like this.move({ name: 'something' }). This is the easiest way to handle the duplicate route without running into try catch syntax. And also you can have that method exported in Vue.prorotype.$move = ... which will work across the whole application.
I found the solution by adding the following code to router.js:
import router from 'vue-router';
const originalPush = router.prototype.push
router.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => err)
}
Provide a Typescript solution
The idea is to overwrite the router.push function. You can handle (ignore) the error in one place, instead of writing catch everywhere to handle it.
This is the function to overwrite
export declare class VueRouter {
// ...
push(
location: RawLocation,
onComplete?: Function,
onAbort?: ErrorHandler
): void
}
Here is the code
// in router/index.ts
import Vue from 'vue';
import VueRouter, { RawLocation, Route } from 'vue-router';
Vue.use(VueRouter);
const originalPush = VueRouter.prototype.push;
VueRouter.prototype.push = function push(location: RawLocation): Promise<Route> {
return new Promise((resolve, reject) => {
originalPush.call(this, location, () => {
// on complete
resolve(this.currentRoute);
}, (error) => {
// on abort
// only ignore NavigationDuplicated error
if (error.name === 'NavigationDuplicated') {
resolve(this.currentRoute);
} else {
reject(error);
}
});
});
};
// your router configs ...
you can define and use a function like this:
routerPush(path) {
if (this.$route.path !== path) {
this.$router.push(path);
}
}
It means - you want to navigate to a route that looks the same as the current one and Vue doesn’t want to trigger everything again.
methods: {
loginUser() {
var data = {
email: this.userData.email,
password: this.userData.password
};
this.app.req.post("api/auth/authenticate", data).then(res => {
const token = res.data.token;
sessionStorage.setItem("chatbot_token", token);
// Here you conditioally navigate to admin page to prevent error raised
if(this.$route.name !== "/admin") {
this.$router.push("/admin");
}
});
}
}
Clean solution ;)
Move the code to an util function. So you can replace all this.$router.push with it.
import router from '../router'; // path to the file where you defined
// your router
const goToRoute = (path) =>
if(router.currentRoute.fullPath !== path) router.push(path);
export {
goToRoute
}
In addition to the above mentioned solutions: a convenient way is to put this snippet in main.js to make it a global function
Vue.mixin({
/**
* Avoids redundand error when navigating to already active page
*/
routerPush(route) {
this.$router.push(route).catch((error) => {
if(error.name != "NavigationDuplicated") {
throw error;
}
})
},
})
Now you can call in any component:
this.routerPush('/myRoute')
Global solution
In router/index.js, after initialization
// init or import router..
/* ... your routes ... */
// error handler
const onError = (e) => {
// avoid NavigationDuplicated
if (e.name !== 'NavigationDuplicated') throw e
}
// keep original function
const _push = router.__proto__.push
// then override it
router.__proto__.push = function push (...args) {
try {
const op = _push.call(this, ...args)
if (op instanceof Promise) op.catch(onError)
return op
} catch (e) {
onError(e)
}
}
I have added the code below in the main.js file of my project and the error disappeared.
import Router from 'vue-router'
const routerPush = Router.prototype.push
Router.prototype.push = function push(location) {
return routerPush.call(this, location).catch(error => error)
};
I have experienced the same issue and when I looked for a solution I found .catch(() => {}) which is actually telling the browser that we have handled the error please don’t print the error in dev tools :) Hehehe Nice hack! but ignoring an error is not a solution I think. So what I did, I created a utility function that takes two parameters router, path, and compares it with the current route's path if both are the same it means we already on that route and it ignore the route change. So simple :)
Here is the code.
export function checkCurrentRouteAndRedirect(router, path) {
const {
currentRoute: { path: curPath }
} = router;
if (curPath !== path) router.push({ path });
}
checkCurrentRouteAndRedirect(this.$router, "/dashboard-1");
checkCurrentRouteAndRedirect(this.$router, "/dashboard-2");
I had this problem and i solve it like that.
by adding that in my router file
import Router from 'vue-router'
Vue.use(Router)
const originalPush = Router.prototype.push
Router.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => err)
}
Short solution:
if (this.$router.currentRoute.name !== 'routeName1') {
this.$router.push({
name: 'routeName2',
})
}
Late to the party, but trying to add my 10 cents. Most of the answers- including the one which is accepted are trying to hide the exception without finding the root cause which causes the error. I did have the same issue in our project and had a feeling it's something to do with Vue/ Vue router. In the end I managed to prove myself wrong and it was due to a code segment we had in App.vue to replace the route in addition to the similar logic like you in the index.ts.
this.$router.replace({ name: 'Login' })
So try to do a search and find if you are having any code which calls $router.replace OR $router.push for the route you are worried about- "/admin". Simply your code must be calling the route more than once, not the Vue magically trying to call it more than once.
I guess this answer comes in super late. Instead of catching the error I looked for a way to prevent the error the come up. Therefore I've enhanced the router by an additional function called pushSave. Probably this can be done via navGuards as well
VueRouter.prototype.pushSave = function(routeObject) {
let isSameRoute = true
if (this.currentRoute.name === routeObject.name && routeObject.params) {
for (const key in routeObject.params) {
if (
!this.currentRoute.params[key] ||
this.currentRoute.params[key] !== routeObject.params[key]
) {
isSameRoute = false
}
}
} else {
isSameRoute = false
}
if (!isSameRoute) {
this.push(routeObject)
}
}
const router = new VueRouter({
routes
})
As you've probably realized this will only work if you provide a routeObject like
this.$router.pushSave({ name: 'MyRoute', params: { id: 1 } })
So you might need to enhance it to work for strings aswell
For components <router-link>
You can create a new component instead of <router-link>.
Component name: <to-link>
<template>
<router-link :to="to" :event="to === $route.path || loading ? '' : 'click'" :class="classNames">
<slot></slot>
</router-link>
</template>
<script>
export default {
name: 'ToLink',
props: {
to: {
type: [String, Number],
default: ''
},
classNames: {
type: String,
default: ''
},
loading: {
type: Boolean,
default: false
}
}
}
</script>
For bind:
import ToLink from '#/components/to-link'
Vue.component('to-link', ToLink)
For $router.push() need create global method.
toHttpParams(obj) {
let str = ''
for (const key in obj) {
if (str !== '') {
str += '&'
}
str += key + '=' + encodeURIComponent(obj[key])
}
return str
},
async routerPush(path, queryParams = undefined, params = undefined, locale = true) {
let fullPath = this.$route.fullPath
if (!path) {
path = this.$route.path
}
if (!params) {
params = this.$route.params
}
if (queryParams && typeof queryParams === 'object' && Object.entries(queryParams).length) {
path = path + '?' + this.toHttpParams(queryParams)
}
if (path !== fullPath) {
const route = { path, params, query: queryParams }
await this.$router.push(route)
}
}
I don't understand why they no longer handle this case internally, but this is what I have implemented in our app.
If you are already on the fullPath then dont bother pushing / replacing
const origPush = Router.prototype.push;
Router.prototype.push = function(to) {
const match = this.matcher.match(to);
// console.log(match.fullPath, match)
if (match.fullPath !== this.currentRoute.fullPath) {
origPush.call(this, to);
} else {
// console.log('Already at route', match.fullPath);
}
}
const origReplace = Router.prototype.replace;
Router.prototype.replace = function(to) {
const match = this.matcher.match(to);
// console.log(match.fullPath, match)
if (match.fullPath !== this.currentRoute.fullPath) {
origReplace.call(this, to);
} else {
// console.log('Already at route', match.fullPath);
}
}
you can add a random query parameter to push object like this:
this.$router.push({path : "/admin", query : { time : Date.now()} });

How to correctly test effects in ngrx 4?

There are plenty of tutorials how to test effects in ngrx 3.
However, I've found only 1 or 2 for ngrx4 (where they removed the classical approach via EffectsTestingModule ), e.g. the official tutorial
However, in my case their approach doesn't work.
effects.spec.ts (under src/modules/list/store/list in the link below)
describe('addItem$', () => {
it('should return LoadItemsSuccess action for each item', async() => {
const item = makeItem(Faker.random.word);
actions = hot('--a-', { a: new AddItem({ item })});
const expected = cold('--b', { b: new AddUpdateItemSuccess({ item }) });
// comparing marbles
expect(effects.addItem$).toBeObservable(expected);
});
})
effects.ts (under src/modules/list/store/list in the link below)
...
#Effect() addItem$ = this._actions$
.ofType(ADD_ITEM)
.map<AddItem, {item: Item}>(action => {
return action.payload
})
.mergeMap<{item: Item}, Observable<Item>>(payload => {
return Observable.fromPromise(this._listService.add(payload.item))
})
.map<any, AddUpdateItemSuccess>(item => {
return new AddUpdateItemSuccess({
item,
})
});
...
Error
should return LoadItemsSuccess action for each item
Expected $.length = 0 to equal 1.
Expected $[0] = undefined to equal Object({ frame: 20, notification: Notification({ kind: 'N', value: AddUpdateItemSuccess({ payload: Object({ item: Object({ title: Function }) }), type: 'ADD_UPDATE_ITEM_SUCCESS' }), error: undefined, hasValue: true }) }).
at compare (webpack:///node_modules/jasmine-marbles/index.js:82:0 <- karma-test-shim.js:159059:33)
at Object.<anonymous> (webpack:///src/modules/list/store/list/effects.spec.ts:58:31 <- karma-test-shim.js:131230:42)
at step (karma-test-shim.js:131170:23)
NOTE: the effects use a service which involves writing to PouchDB. However, the issue doesn't seem related to that
and also the effects work in the running app.
The full code is a Ionic 3 app and be found here (just clone, npm i and npm run test)
UPDATE:
With ReplaySubject it works, but not with hot/cold marbles
const item = makeItem(Faker.random.word);
actions = new ReplaySubject(1) // = Observable + Observer, 1 = buffer size
actions.next(new AddItem({ item }));
effects.addItem$.subscribe(result => {
expect(result).toEqual(new AddUpdateItemSuccess({ item }));
});
My question was answered by #phillipzada at the Github issue I posted.
For anyone checking this out later, I report here the answer:
Looks like this is a RxJS issue when using promises using marbles. https://stackoverflow.com/a/46313743/4148561
I did manage to do a bit of a hack which should work, however, you will need to put a separate test the service is being called unless you can update the service to return an observable instead of a promise.
Essentially what I did was extract the Observable.fromPromise call into its own "internal function" which we can mock to simulate a call to the service, then it looks from there.
This way you can test the internal function _addItem without using marbles.
Effect
import 'rxjs/add/observable/fromPromise';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';
import { Injectable } from '#angular/core';
import { Actions, Effect } from '#ngrx/effects';
import { Action } from '#ngrx/store';
import { Observable } from 'rxjs/Observable';
export const ADD_ITEM = 'Add Item';
export const ADD_UPDATE_ITEM_SUCCESS = 'Add Item Success';
export class AddItem implements Action {
type: string = ADD_ITEM;
constructor(public payload: { item: any }) { }
}
export class AddUpdateItemSuccess implements Action {
type: string = ADD_UPDATE_ITEM_SUCCESS;
constructor(public payload: { item: any }) { }
}
export class Item {
}
export class ListingService {
add(item: Item) {
return new Promise((resolve, reject) => { resolve(item); });
}
}
#Injectable()
export class SutEffect {
_addItem(payload: { item: Item }) {
return Observable.fromPromise(this._listService.add(payload.item));
}
#Effect() addItem$ = this._actions$
.ofType<AddItem>(ADD_ITEM)
.map(action => action.payload)
.mergeMap<{ item: Item }, Observable<Item>>(payload => {
return this._addItem(payload).map(item => new AddUpdateItemSuccess({
item,
}));
});
constructor(
private _actions$: Actions,
private _listService: ListingService) {
}
}
Spec
import { cold, hot, getTestScheduler } from 'jasmine-marbles';
import { async, TestBed } from '#angular/core/testing';
import { Actions } from '#ngrx/effects';
import { Store, StoreModule } from '#ngrx/store';
import { getTestActions, TestActions } from 'app/tests/sut.helpers';
import { AddItem, AddUpdateItemSuccess, ListingService, SutEffect } from './sut.effect';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
describe('Effect Tests', () => {
let store: Store<any>;
let storeSpy: jasmine.Spy;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
StoreModule.forRoot({})
],
providers: [
SutEffect,
{
provide: ListingService,
useValue: jasmine.createSpyObj('ListingService', ['add'])
},
{
provide: Actions,
useFactory: getTestActions
}
]
});
store = TestBed.get(Store);
storeSpy = spyOn(store, 'dispatch').and.callThrough();
storeSpy = spyOn(store, 'select').and.callThrough();
}));
function setup() {
return {
effects: TestBed.get(SutEffect) as SutEffect,
listingService: TestBed.get(ListingService) as jasmine.SpyObj<ListingService>,
actions$: TestBed.get(Actions) as TestActions
};
}
fdescribe('addItem$', () => {
it('should return LoadItemsSuccess action for each item', async () => {
const { effects, listingService, actions$ } = setup();
const action = new AddItem({ item: 'test' });
const completion = new AddUpdateItemSuccess({ item: 'test' });
// mock this function which we can test later on, due to the promise issue
spyOn(effects, '_addItem').and.returnValue(Observable.of('test'));
actions$.stream = hot('-a|', { a: action });
const expected = cold('-b|', { b: completion });
expect(effects.addItem$).toBeObservable(expected);
expect(effects._addItem).toHaveBeenCalled();
});
})
})
Helpers
import { Actions } from '#ngrx/effects';
import { Observable } from 'rxjs/Observable';
import { empty } from 'rxjs/observable/empty';
export class TestActions extends Actions {
constructor() {
super(empty());
}
set stream(source: Observable<any>) {
this.source = source;
}
}
export function getTestActions() {
return new TestActions();
}

Realm "observer.next create #[native code]" exception

I am trying to fetch data with apollo and then write it to realm. I have created a js file that I know works, because it has worked before. But, when I try to write to a particular model I get an error message. More details as follows:
Code (Not entire code) LocationQuery.js:
const realm = new Realm({ schema: [testBuilding1], schemaVersion: 1 });
let buildingTypeArray = [];
const temp = [];
class LocationQuery extends Component {
static get propTypes() {
return {
data: React.PropTypes.shape({
loading: React.PropTypes.bool,
error: React.PropTypes.object,
sites: React.PropTypes.array,
}).isRequired,
};
}
render() {
if (this.props.data.loading) {
return (null);
}
if (this.props.data.error) {
return (<Text>An unexpected error occurred</Text>);
}
if (this.props.data.sites) {
this.props.data.sites.map((value) => {
buildingTypeArray.push(value.locations);
});
buildingTypeArray.forEach((locationValues) => {
realm.write(() => {
realm.create('testBuilding1', {
building: '273',
});
});
});
}
return null;
}
}
const locationQueryCall = gql`
query locationQueryCall($id: String!){
sites(id: $id){
locations {
building
type
}
}
}`;
const ViewWithData = graphql(locationQueryCall, {
options: props => ({
variables: {
id: 'SCH1',
},
}),
})(LocationQuery);
export default connect(mapStateToProp)(ViewWithData);
The error I get is a big red screen that read:
console.error: "Error in observe.next.... blah blah blah"
The Model I am using:
export const testBuilding1 = {
name: 'testBuilding1',
properties: {
building: 'string',
},
};
The weird thing is that the code works when I use this model:
export const locationScene = {
name: 'locationScene',
properties: {
building: 'string',
},
};
I am calling LocationQuery.js in another piece of code passing it through at render.
Thank you in advance for the help!