Electron Ipc render emit event to Vuex - vue.js

Hi guys I'm building an app with multiple windows,
My App is developed in Vue + Electron
The main feature I'm trying to accomplish is when the user for example wants to do some action in electron open popup for example, and next I want to send action to Vuex store to display message to user
So How can I call Vuex action from electron?
Code Sample:
Vue component:
import { remote, ipcRenderer } from 'electron';
ipcRenderer.send('openPopup', id);
Electron Code:
import { ipcMain, app } from 'electron';
ipcMain.on('openPopup', (event, args) => {
console.log('do some action');
// now how can I call vuex action from here
});
I tried calling it with:
this.$store
but its undefined

You could simply return the desired data back to the renderer process in the ipcMain.on event.
I would also recommend using ipcRenderer.invoke / ipcMain.handle (Electron Doc - ipcMain)
// Main process
ipcMain.handle('openPopup', async (event, ...args) => {
const result = await somePromise(...args)
return result
})
// Renderer process
async () => {
const result = await ipcRenderer.invoke('open-popup', id)
// this.$store.dispatch(....) etc etc
}

Related

Auto Refresh for Vuex

I would like to implement a auto refresh feature for my VueX store.
Everything the user refresh their browser, an actions in VueX store will be triggered to load the user profile from API call.
Is't possible to achieve that?
import apiService from "#/services/apiService";
import apiUrls from "#/services/apiUrls";
import { getToken } from "#/services/jwtService";
// Code to run actions when user refresh
getToken() !== null ? this.actions.getUserProfile() : "";
const state = {
userProfile: {},
};
const getters = {
userProfile: (state) => state.userProfile,
};
const actions = {
async getUserProfile({ commit }) {
console.log("here");
try {
let response = await apiService.get(apiUrls.PROFILE);
commit("setUserProfile", response.data.data);
} catch (error) {
console.log(error);
}
},
};
Thank you.
A user refresh means that the application will be re-executed. So basically main.js will be re-executed, App.vue re-created, etc.
That means just have to call your code in main.js or in a created lifecycle hook of any top-level component.
By top-level component I means any component which is created early in the app

Rendered hook / serverPrefetch Vue3 SSR

I've been trying to make a Vue3 app work with SSR and I'm stuck in the serverPrefetch part. I want to load some data in the server, save it in the store and pass it to the client but it looks like the rendered hook referred to here: https://ssr.vuejs.org/guide/data.html#final-state-injection is not called anymore (and therefore the server store is not updated after the data fetch). Do you know what the right way to do this with Vue3 is?
This is my server-entry.js
import { _createApp } from "./app";
export default async (context) => {
const { app, router, store } = _createApp();
router.push(context.url); // push router entry
await router.isReady();
context.rendered = () => {
// this is never executed!!!
context.state = store.state; // this gets embedded in the final HTML as window.__INITAL_STATE__
};
return app;
};

App has already been mounted. nothing show up when open page again

// main.js
const app = createApp(App);
app.provide('$axios', axios);
window.renderSomething = function() {
app.mount('#ticker-maintenance-host');
}
the application is developed with vuejs and blarzor. In blazor, will call it
#inject IJSRuntime JS;
#code{
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JS.InvokeVoidAsync("renderSomething");
}
}
}
#page "/dashboard/page1"
<div id="ticker-maintenance-host"></div>
first open this page1, all the content can show up. If I leave it and come back to this page, nothing show up. There is a warning saying
"[Vue warn]: App has already been mounted.
If you want to remount the same app, move your app creation logic into a factory function and create fresh app instances for each mount - e.g. `const createMyApp = () => createApp(App)`".
How can I fix this warning and make my content show up without refresh when coming back.
Thanks
You are mounting your App to the ID #ticker-maintenance-host
But all I can see in your HTML is an ID container.
Mount your app to the container, or add the ID #ticker-maintenance-host to your HTML.
#Edit:
You dont need to mount your view app on every render, just when the website is loaded initially.
Something like this straight out of the docs for Vue3 is how you register a vue app:
const { createApp, ref, computed } = Vue;
const app = createApp({
setup() {
const someValue = ref(10);
const someComputed = computed(() => someValue.value * 10);
return {
someValue,
someComputed
}
}
});
app.mount("#ticker-maintenance-host");
More reading here: Vue.createApp is not working but Is working with new Vue() method
I have fixed it by moving all the statements inside function block.
// main.js
window.renderSomething = function() {
const app = createApp(App);
app.provide('$axios', axios);
app.mount('#ticker-maintenance-host');
}

Enzyme integration testing: axios.get call not being executed in redux-saga

I am trying to setup tests for some an action creator that is triggering a redux saga.
My saga retrieves a word from a local flask server (will always return the same word) and then displays that word. This is not my real-life case but I tried to start with something easy...
My action creator and saga work as expected when I trigger them with a button in my react app (the word is retrieved from the server, stored in my redux store and the displayed with a selector in my react component), but I cannot get the test to succeed.
I would like to test only the redux part, not the actual rendered react component (not sure if that is part of my problem or not)
I use Enzyme for tests, my store is created correctly and can dispatch the action. I can also see that my saga is being called with the console logs:
My test code:
import { Store } from 'redux';
import { RootState } from '../root.reducer';
import { storeFactory } from '../../../test/testUtils';
import { getSecretWord } from './secret-word.actions';
describe('getSecretWord action creator', () => {
let store: Store<RootState>;
beforeEach(() => {
store = storeFactory();
});
test('add response word to state', () => {
const secretWord = 'party';
store.dispatch(getSecretWord());
const newState = store.getState();
console.log('new state: ' + newState.secretWord);
expect(newState.secretWord).toBe(secretWord);
});
});
and my saga function:
export function* getSecretWordSaga(action: getSecretWordAction): Generator<ForkEffect | CallEffect | PutEffect, void, unknown>
{
try {
console.log('getSecretWordSaga() saga started');
console.log('before axios query call:');
const response:any = yield call(api.get, '/api/word');
// const response = {data: { word: 'party'}, status:200}
console.log('axios query returned: ');
console.log(response);
yield put(setSecretWord(response.data.word));
console.log('getSecretWordSaga() saga finsshed');
} catch (err) {
console.log('error occured:');
console.log(err);
console.log('getSecretWordSaga() saga finsshed with errors');
}
}
export function* getSecretWordSagaStart(): Generator<
ForkEffect<never>,
void,
unknown
> {
yield takeLatest(SecretWordActionTypes.GET_SECRET_WORD, getSecretWordSaga);
}
The axios api is very basic and it includes two interceptors for logging purposes:
import axios from 'axios';
export const api = axios.create({
baseURL: 'http://localhost:5000',
responseType: 'json',
});
api.interceptors.request.use(request => {
console.log('Starting Request', JSON.stringify(request, null, 2))
return request
})
api.interceptors.response.use(response => {
console.log('Response:', JSON.stringify(response, null, 2))
return response
})
I can see in the logs (in "npm test") that I get log for the line "before axios query call:' and one console.log for the request interceptor (everything looks fine there), but no more logs afterwards (neither success nor error)
If I comment out the "yield call.." and hardcode the response (like in the commented out line below), my saga runs through the end and my test succeeds.
Why is the yield Call(api.get, '/api/word') not being executed (and I don't get any error message)?
The code is my opinion correct as it is running fine when executed in react. My flask server is obviously also running and I can see in the flask app than no call to the api are being made by the running tests.
I obviously plan to mock that api call but was also running into some problems there, that's why I first wanted to get the real api call working.
After trying many different ways for adding a timeout, setting the testing function to async and adding a setTimeout in a promise did work.
It's not ideal as I have to set the timeout to a specific value, but I could not figure out a better way to get it working.
test("add response word to state", async () => {
const secretWord = 'party';
store.dispatch(getSecretWord());
await new Promise(res => setTimeout(res, 1000));
const newState = store.getState();
console.log('new state: ' + newState.secretWord);
expect(newState.secretWord).toBe(secretWord);
});

Testing a function called on an object with Jest in React Native

EDIT
Current example,
it('CALLED THE canOpenURL FUNCTION', () => {
const wrapper = mount(<ResourceCardComponent {...mockProps} />);
const canOpenURLSpy = jest.spyOn(Linking, 'canOpenURL');
wrapper.find('TouchableOpacity').simulate('click');
expect(canOpenURLSpy).toHaveBeenCalled();
canOpenURLSpy.mockReset();
canOpenURLSpy.mockRestore();
});
Error
expect(jest.fn()).toHaveBeenCalled() Expected mock function to have
been called.
Problem
I am using Jest & Enzyme to test a class made with React Native. This class has a function inside of it that when fired off uses the Linking library to call canOpenUrl and openUrl. I can simulate the click event on the mounted component but I am having trouble knowing how much of this I can actually test.
My goal is to check if Linking.canOpenUrl ever fires off.
Exmaple
The function inside the component looks like this,
onPressLink() {
console.log('HEY THIS FUNCTION FIRED WOOT WOOT');
Linking.canOpenURL(this.props.url).then((supported) => {
if (supported) {
Linking.openURL(this.props.url);
}
});
}
I can simulate this firing off like this,
describe('onPressLink has been called!', () => {
it('It clicks the mock function onPressLink!', (done) => {
const wrapper = mount(<MyComponent {...mockProps} />);
const onPressLink = jest.fn();
const a = new onPressLink();
wrapper.find('TouchableOpacity').first().simulate('click');
expect(onPressLink).toHaveBeenCalled();
done();
});
});
Now that does work, but my goal is to use something like this,
expect(Linking.canOpenUrl).toHaveBeenCalled();
But I keep getting this error,
TypeError: Cannot read property '_isMockFunction' of undefined
Current code that is trying to check if this function is ever fired off. Which is inside the parent function that is clicked with the simulate method,
it('calls canOpenURL', () => {
const wrapper = mount(<MyComponent {...mockProps} />);
const canOpenURL = jest.spyOn(wrapper.instance, 'onPressLink');
wrapper.find('TouchableOpacity').simulate('click');
expect('Linking.canOpenUrl').toHaveBeenCalled();
});
Question
What is the proper way to check to see if Linking.canOpenURL is fired when its parent function is executed?
(Since Jest 19.0.0+)
You can spy on the Linking module methods using jest.spyOn().
(1) Tell jest to spy on the module method:
const spy = jest.spyOn(Linking, 'canOpenURL');
(2) After doing everything you need to test it, check the spy:
expect(spy).toHaveBeenCalled();
(3) Clean up and stop spying on the module method
spy.mockReset();
spy.mockRestore();
If you don't want the tests to use the actual implementation of the methods, you can fake them like this:
jest.spyOn(Linking, 'canOpenURL').mockImplementation(() => Promise.resolve());
Where the function passed to mockImplementation will be whatever you want the method to do when called.
Ref https://facebook.github.io/jest/docs/en/jest-object.html#jestspyonobject-methodname
When using the actual implementation of your module method, which is asynchronous, the promise might not have been resolved by the time you tested it. You need to make sure any promise is resolved in your method implementation before making any assertions on it.
One way to deal with this is using async/await, like so:
it('...', async () => {
// Wait for promise to resolve before moving on
await wrapper.instance().onPressLink();
// make your assertions
expect(...);
});
Another option is using expect().resolves, available since Jest 20.0.0, where you wait for some promise in the argument to expect() to resolve with a value before making an assertion on that value.
expect(somePromiseThatEventuallyResolvesWithValue).resolves.toBe(Value);
I've done in simplest way:
Steps to spy:
Make spy object for original function using jest
Call original function with / without argument(s)
Assert the function which should be called with valid argument(s)
Reset mock
Restore mock
Here is the sample example
DefaultBrowser.ts which is actual class.
import { Linking } from 'react-native';
export const openDefaultBrowser = async url => {
if (await Linking.canOpenURL(url)) {
Linking.openURL(url);
}
};
DefaultBrowser.test.ts which is test case class.
import { openDefaultBrowser } from '../DefaultBrowser';
import { Linking } from 'react-native';
describe('openDefaultBrowser with validate and open url', () => {
it('validate url', async () => {
const spy = jest.spyOn(Linking, 'canOpenURL');
openDefaultBrowser('https://www.google.com');
expect(spy).toBeCalledWith('https://www.google.com');
spy.mockReset();
spy.mockRestore();
});
it('open url', async () => {
const spy = jest.spyOn(Linking, 'openURL');
openDefaultBrowser('https://www.google.com');
expect(spy).toBeCalledWith('https://www.google.com');
spy.mockReset();
spy.mockRestore();
});
});
Hope this helps you.
it('open url', async () => {
jest.spyOn(Linking, 'canOpenURL')
const spy = jest.spyOn(Linking, 'openURL')
openURL(sitePath)
await waitFor(() => {
expect(spy).toHaveBeenCalledWith(sitePath)
})
spy.mockReset()
spy.mockRestore()
})