Trying to use this.subscribe('users') of latest meteor, I would like to know as how to attach function or have callback function along with this
Previously we could have a call back function using then, how can we achieve this in latest angular meteor?
any kind of help or links are appreciated.
this.subscribe('users', () => {
return [foo, bar]; // publication arguments
}, {
onReady: (res) => {
// success callback
},
onError: (err) => {
// error callback
}
});
Related
I have following effect that uses the an observable source from a PlatformService provided by Angulars Dependency Injection.
public resume$ = createEffect(() => {
return this.platformService.resume().pipe(mapTo(PlatformActions.appResumed()));
});
constructor(private platformService: PlatformService) {
}
This works really well, but I do not really know how to test it elegantly. Usually we try to setup our TestingModule as unspecific as possible. e.G
describe('PlatformEffects', () => {
let effects: PlatformEffects;
let platformServiceSpy: SpyObj<PlatformService>;
beforeEach(() => {
platformServiceSpy = jasmine.createSpyObj({
resume: EMPTY
});
TestBed.configureTestingModule({
providers: [
PlatformEffects,
{
provide: PlatformService,
useValue: platformServiceSpy
}
]
});
effects = TestBed.inject(PlatformEffects);
});
it('should be created', () => {
expect(effects).toBeTruthy();
});
describe('resume$', () => {
it('dispatches appResumed action on resume event', (done) => {
// AAA: This test should setup the response from the platformService so the arrange setup is coupled in the test.
});
});
});
Sadly this does not seem to work as the effects are created in the constructor and therefore my event stream is always EMPTY and does not trigger the effect even if I overwrite the resume response in my spy.
Now I could set a default value in the beforeEach (e.G. of(undefined)) but then it is not really coupled in my tests and I cant use the AAA pattern.
On the other hand I could probably create a new Effects instance every time but that seems a bit overkill not?
Is there a better method? NgRx solved it really well with the actions stream and I wonder if there is a similar solution for effects using sources from DI.
For more info see, https://timdeschryver.dev/blog/testing-an-ngrx-project#effects
it('fetch$ dispatches a success action', () => {
// 🔦 The Effect Actions stream is created by instantiating a new `ActionsSubject`
const actions = new ActionsSubject();
const effects = new CustomersEffects(actions, newCustomerService());
// 🔦 Subscribe on the effect to catch emitted actions, which are used to assert the effect output
const result: Action[] = [];
effects.fetch$.subscribe((action) => {
result.push(action);
});
const action = customerPageActions.enter({ customerId: '3' });
actions.next(action);
expect(result).toEqual([
customersApiActions.fetchCustomerSuccess(
newCustomer({
id: action.customerId,
}),
),
]);
});
I tried a bit more and thought that the actions stream is probably just a subject and I could do the same for my own service method:
describe('PlatformEffects', () => {
let effects: PlatformEffects;
let platformServiceSpy: SpyObj<PlatformService>;
let resumeSubject: ReplaySubject<any>;
beforeEach(() => {
// create new subject that can be used as an event emitter
resumeSubject = new ReplaySubject<any>();
platformServiceSpy = jasmine.createSpyObj({
resume: resumeSubject.asObservable()
});
TestBed.configureTestingModule({
providers: [
PlatformEffects,
{
provide: PlatformService,
useValue: platformServiceSpy
}
]
});
effects = TestBed.inject(PlatformEffects);
});
it('should be created', () => {
expect(effects).toBeTruthy();
});
describe('resume$', () => {
it('dispatches appResumed action on resume event', (done) => {
// emit new test string in resume subject
resumeSubject.next('test');
effects.resume$.subscribe((res) => {
expect(res).toEqual(PlatformActions.appResumed());
done();
});
});
});
});
This works for this case but as soon as I try to do the same with a service method that returns a promise this solution does not work anymore.
I am using React with Meteor. I have a method inside React component that calls Meteor.method (which is run on client as well):
// index.js
loginWithGoogle() {
console.log('1')
Meteor.call('auth.loginWithGoogle', {}, (err, res)=>{
console.log('5');
if (err) {
console.log(err) // line 16:
} else {
console.log('success');
// console.log(res);
// console.log('logged in with google callback in react yay:)')
}
});
}
In client side Meteor I have method:
// auth.js
Meteor.methods({
async 'auth.loginWithGoogle'(){
console.log('2')
let err = await Meteor.loginWithGoogle()
console.log('3');
console.log(err)
if (err) {
console.log('-1')
throw new Error(err);
}
console.log('4');
// Meteor.loginWithGoogle({
// // options
// }, (err) => {
// console.log('3')
// if (err) {
// console.log(err)
// throw new Meteor.Error(err)
// } else {
// console.log('4')
// // successful login!
// }
// });
}
});
Note: Meteor.loginWithGoogle is provided by accounts-google package.
When testing I was able to navigate to google sign in page, sign in, and redirect back to my app) and logs are then printed.
Here the commented code is old approach. Notice, I have console.log calls with numbers, the numbers indicate the order in which I expect code to be executed. The old method does not work at all, console.log('5') runs earlier than (3 and 4), due to asynchronous execution. Rewriting with async/await gives this:
index.js:12 1
auth.js:4 2
auth.js:6 3
auth.js:7 undefined
auth.js:12 4
index.js:14 5
index.js:16 errorClass {isClientSafe: true, error: 404, reason: "Method 'auth.loginWithGoogle' not found", details: undefined, message: "Method 'auth.loginWithGoogle' not found [404]", …}
So, from logs I can see that code is executed as i expected.
Inside auth.js:7 I have err == undefined, but inside index.js (react part) it is errorClass.
How do we deal with async code in Meteor methods?
I got it. Why I was using Meteor.methods on client? I could just use javascript function like that:
const loginWithGoogle = async () => {
console.log('2')
let err = await Meteor.loginWithGoogle()
console.log('3');
console.log(err)
if (err) {
console.log('-1')
throw new Error(err);
}
console.log('4');
}
export {
loginWithGoogle
}
I just use async function, it returns Promise.
Inside React I use async syntax too:
async loginWithGoogle() {
let err = await loginWithGoogle()
if (err){
console.log(err)
}else {
console.log('success')
}
}
I'm trying to generate tests dynamically by looping over an array returned from an async call. I just cannot figure out how to do this - either using mocha or using jest. To illustrate using code, the following synchronous example works:
describe("Test using various frameworks", () => {
["mocha", "jest"].forEach(framework => {
it(`Should test using ${framework}`, () => {
expect(true).toBe(true);
});
});
});
However, if that array is fetched asynchronously, I cannot get the testing frameworks to wait until the array is fetched before trying to loop over it.
async function getFrameworks() {
//TODO: get it from some async source here
return ["mocha", "jest"];
}
describe("Test using various frameworks", () => {
var frameworks;
//before() instead of beforeAll() if using mocha
beforeAll(async ()=> {
frameworks = await getFrameworks();
});
frameworks.forEach(framework => {
it(`Should test using ${framework}`, () => {
expect(true).toBe(true);
});
});
});
This fails saying Cannot read property 'forEach' of undefined. I've tried all sort of combinations of using async/await and Promise and passing in a done callback but to no avail.
The closest I came to this was using Mocha's --delay flag, but that only solves part of the problem. What I really want to do in my actual use case is to run some async intialization in the before() or beforeAll() hooks which I then use to dynamically generate tests.
Any pointers on how to do this using either mocha or jest?
To answer my own question, I did not find a way to do this using Jest or Mocha, but I could accomplish it using tap - which I used through babel-tap.
import tap from "babel-tap";
async function getFrameworks() {
//TODO: get it from some async source here
return ["mocha", "jest"];
}
getFrameworks().then(frameworks => {
frameworks.forEach(framework => {
tap.test(`Should test using ${framework}`, (tester) => {
tester.ok("It works!");
});
});
});
You can do a lot more though. You can create nested scopes by further calling tester.test() for example. Also, since tap does not have the concept of before, after etc, (unless you use the Mocha-like DSL ), you can simply use imperative code to simulate the equivalent behavior.
Also, you can freely use async/await style calls inside tests.
Mocha has some support async tests. Specifically, your it() callback can be async. Here is how to rearrange your tests to "looping over an array returned from an async call":
const chai = require('chai');
const { expect } = require('chai');
function getMyTestData() {
return new Promise(resolve => setTimeout(() => resolve(['mocha', 'jest']), 1000));
}
describe('Test using various frameworks', () => {
it('Should test by looping over an array returned from an async call', async () => {
const myTestData = await getMyTestData();
for(let datum of myTestData) {
expect(datum.length).greaterThan(4, `Data '${datum}' not long enough`);
}
})
});
I want to use only React, React Redux, React Router and Redux Thunk.
I want to navigate to a dashboard page when a successful user creation action is dispatched. Here is my async action creator,
export function createUser() {
return dispatch => {
dispatch(creatingUser());
return axios.post('/api/users').then(() => {
// how to navigate to dashboard after this action is dispatched?
dispatch(createdUser());
});
};
}
Can you show me exactly where is the place I should naviage programmatically?
Initially looking, I would hope that "createdUser" returns a promise (like #idbehold asked previously)
in a nutshell, something like this.
// make sure your function createdUser returns a promise
function createdUser() {
return new Promise((resolve, reject) => {
//simulate some api request
setTimeout( () =>{
// api resolves. in the .then() do:
resolve()
}, 4000)
})
}
// the dispatch will forward resolution of the promise ahead into the
// .then.. then you can redirect.
dispatch(createdUser()).then( ()=> {
console.log("NAVIGATING AWAY")
//browserHistory.push('/some/path')
//assuming you are importing browserHistory
})
I hope I was helpful, if not :-( , perhaps I didn't fully understand what your need is/was. Let me know, and I'll try to help further.
I cannot update the UI immediately after subscribing the data from database, I have to click somewhere
Also if I use the router to go to another page, it does not work
#Component({
selector: 'foo-component',
template: `
{{foo}}
`
})
export class FooComponent extends MeteorComponent {
foo: string ;
constructor() {
super();
this.subscribe('messages', () => {
// I cannot change foo value immediately, I have to click somewhere
this.foo = 'foo';
// Also if I use the router to go to another page, it does not work
// this.router.navigate(['Home']);
});
}
}
How to solve this?
Note the new angular2-meteor version autoBind is set to true by default. So you probably won't meet this issue again.
But you still need use NgZone in Accounts.changePassword or other similar Accounts.foo() functions.
This problem is because that part of code run out of Angular 2 zone, you need run it inside of zone to update UI immediately or use router to go to another page.
Where do these problems usually happen?
Most time you don't do this. So when do you need this? Usually in callback of Meteor functions:
this.autorun(() => {
// here you need
});
this.subscribe('messages', () => {
// here you need
});
this.call('newMessage', (error, result) => {
// here you need
});
Accounts.changePassword(currentPassword, newPassword, error => {
// here you need
});
How to solve?
Take
this.call('newMessage', (error, result) => {
this.router.navigate(['Home']);
});
for example, you need change to:
import { NgZone } from '#angular/core';
constructor(private zone: NgZone) {}
this.call('newMessage', (error, result) => {
this.zone.run(() => {
this.router.navigate(['Home']);
});
});
Is there a clean way?
Luckily, Angular2-Meteor helps you do this dirty work.
Check Angular2-Meteor API document, there is an autoBind parameter for this.subscribe and this.autorun.
So now you don't need use this.zone.run, instead you can just add a true:
this.autorun(() => {
// your code
}, true);
this.subscribe('messages', () => {
// your code
}, true);