Effector: how to reset all domain stores before each test? - effector

I want to reset all domain stores before each test case. Is there some way to do it with Effector?

There is no such API in effector. You can create separate event and subscribe every store to it:
const resetForm = createEvent()
formDomain.onCreateStore(store => store.reset(resetForm))
But in general you shouldn't manually reset stores in tests.
Prefer Fork API usage instead
https://effector.dev/docs/api/effector/fork - docs
https://dev.to/effector/the-best-part-of-effector-4c27 - article
Example:
test('stuff', async () => {
// create new forked scope, which is completly independent
const scope = fork({
// apply modifications like initial store values in this scope
values: [[$myStore, "value"], [$myOtherStore, 0]], // changed value in $myStore specifically for this scope
handlers: [[myFx, mockHandler)]] // changed effect handler to mock one for this scope
});
// launching event or effect, which triggers the logic we want to test
// we doing it just in our forked scope
await allSettled(startEvent, {
scope,
params: // params of startEvent
})
// check states of stores in this scope after all calculations ended
expect(scope.getState($myStore)).toEqual(...)
})

Related

How to unregister middleware in Telegraf?

When I add bot.hears(...), it registers middleware for handling matching text messages. But now it will handle those messages even if they are sent any time, even if not expected.
So if I am creating a stateful service, I would like to listen to particular messages only at appropriate time.
How can I unregister middleware, so that it does not hear any more previously handled messages?
I turned out I was looking for Scenes. How to use them is described on Github.
I'll just post a slightly modified code from the links above:
const { Telegraf, Scenes, session } = require('telegraf')
const contactDataWizard = new Scenes.WizardScene(
'CONTACT_DATA_WIZARD_SCENE_ID', // first argument is Scene_ID, same as for BaseScene
(ctx) => {
ctx.reply('Please enter guest\'s first name', Markup.removeKeyboard());
ctx.wizard.state.contactData = {};
return ctx.wizard.next();
},
(ctx) => {
// validation example
if (ctx.message.text.length < 2) {
ctx.reply('Please enter real name');
return;
}
ctx.wizard.state.contactData.firstName = ctx.message.text;
ctx.reply('And last name...');
return ctx.wizard.next();
},
);
const stage = new Scenes.Stage();
stage.register(contactDataWizard);
bot.use(session());
bot.use(stage.middleware());
But I still don't know how to generally implement it, so I need to find it out in the Scenes code of Telegraf.

Mongoose Methods not stored in Session?

I'd like to use a Method defined in the Mongoose Model after saving the retrieved Object to a Session. Its not working though. Is it normal that these methods get lost after storing it to the session?
Calling Method from Mongoose Model works fine:
Puppies.findOne({_id:123}).then(puppy => puppy.bark()) // WOOF WOOF
Storing Model in Session and calling method fails:
// First Request
Puppies.findOne({_id:123}).then(puppy => {
req.session.puppy = puppy
})
// Second Request somewhere else in the app
app.use(function(req,res,next){
req.session.puppy.bark() // req.session.puppy.bark is not a function
})
I've got the exact issue, but I believe what happens is that when you're storing the variable in session, it's being toObject()'d, causing it to become a simple JavaScript object, instead of remaining as an instance of Model. I've used Model.hydrate as a means of recreating this Model instance.
app.use(function(req,res,next){
let puppyModel = mongoose.model("puppy");
let puppy = puppyModel.hydrate(req.session.puppy);
puppy.bark() // Awooo
});
This essentially is creating a new Model and then filling it with all the relevant information so it acts a clone.
Because it is needing all the relevant information to make an update (including _id if you have it), I believe you may need to extend the toObject function to return getters/virtuals:
puppySchema.set('toObject', { getters: true, virtuals: true });
Else, when it attempts to save, and it's missing the _id field, it won't be able to save it.
I do hope someone else can provide a nicer method of doing this and/or explain why when storing it it has to be converted to an object and can't remain as an instance of Model.
I think what Ciaran Blewitt said was correct. Finally worked around it by just using mongoose statics:
puppy.model.js
schema.statics.bark(puppy) {
console.log(puppy.sound)
}
Storing Model in Session and getting desired effect via static:
// First Request, storing Puppy in Session
Puppy.findOne({_id:123}).then(puppy => {
req.session.puppy = puppy
})
// Second Request somewhere else in the app
app.use(function(req,res,next){
Puppy.bark(req.session.puppy) // WOOF WOOF
})

RxJs: How to conditionally chain observable of BehaviorSubject?

I've got an observable data service (UserService) that returns the currently logged in user. I followed this tutorial - https://coryrylan.com/blog/angular-observable-data-services, which describes using a BehaviorSubject to return a default currentUser immediately, then emit the real currentUser once it's loaded or altered. The service is basically like this...
private _currentUser: BehaviorSubject<User> = new BehaviorSubject(new User());
public currentUser: Observable<User> = this._currentUser.asObservable();
constructor(private http: Http) {}
loadUser() { // app.component onInit and login component call this
return this.http.get('someapi.com/getcurrentuser')
.map(response => <User>this.extractData(response))
.do(
(user) => {
this.dataStore.currentUser = user;
this._currentUser.next(Object.assign(new User(), this.dataStore).currentUser);
},
(error) => this.handleError(error)
)
.catch(error -> this.handleError(error));
}
I'm having problems whenever a user hits F5 to reload the entire spa. When a consuming component subscribes to the currentUser on the UserService, it immediately receives a default user while the UserService waits for an api call to receive the actual user. The moment that api call finishes, the real user is emitted by UserService and all the subscribers get the real user. The first value emitted by the BehaviorSubject, however, is the default value and it always has an id of "undefined", so we can't make our next api call yet. In fact, when the real user comes through and I CAN make a valid call using the user.id, the chained subscription never happens and I don't get the values out of the response.
I know I'm doing something stupid, but I haven't figured out exactly what yet. I just stumbled across concatMap, but I'm not sure how to use it. While I pursue that, I'd like to know why the below code doesn't work. I particularly want to know why the subscribe never fires, even after the real user comes in, just to help my newbie understanding of Observables.
this.userService.currentUser
.flatMap((user) => {
this.user = user;
// Need to NOT call this if the user does not have an id!!!
this.someOtherService.getSomethingElse(user.id); // user.id is always undefined the first time
})
.subscribe((somethingElse) => {
// This never gets called, even after the real user is emitted by the UserService
// and I see the getSomethingElse() call above get executed with a valid user.id
this.somethingElse = somethingElse;
});
If you want to ignore user instances that do not have an id, use the filter operator:
import 'rxjs/add/operator/filter';
this.userService.currentUser
.filter((user) => Boolean(user.id))
.flatMap((user) => {
this.user = user;
this.someOtherService.getSomethingElse(user.id);
})
.subscribe((somethingElse) => {
this.somethingElse = somethingElse;
});
Regarding "why the subscribe never fires", it's likely due to an error arising from the undefined id. You only pass a next function to subscribe, so any errors will be unhandled. And if an error occurs, the observable will terminate and will unsubscribe any subscribers - as that is how observables behave - so any subsequent users with defined id properties will not be received.

using bluebird promises with express to make API calls

I'm trying to get different chunks of data from a trello API using bluebird promises library. In my express router I'm using middleware isLoggedIn, and getBoards, which body looks something like:
trello.get("/1/members/me/boards") // resolves with array of board objects
.each((board) => {
// do some async stuff like saving board to db or other api calls, based on retrieved board
.catch(err => console.error('ERR: fetching boards error - ${err.message}'))
})
The question is: I want to redirect (res.redirect('/')) only when all boards were retrieved and saved. How can I do that? Where should I place xres.redirect('/') expression?
I think you need something like:
var Promise = require('bluebird');
var promises = [];
trello.get("/1/members/me/boards") // resolves with array of board objects
.each((board) => {
//
promises.push( /*some promisified async call that return a promise, saving data in db or whatever asynchronous action. The important bit is that this operation must return a Promise. */ );
});
//So now we have an array of promises. The async calls are getting done, but it will take time, so we work with the promises:
Promise.all(promises).catch(console.log).then( function(results){
/*This will fire only when all the promises are fullfiled. results is an array with the result of every async call to trello. */
res.redirect('/'); //now we are safe to redirect, all data is saved
} );
EDIT:
Actually, you can avoid some boilerplate code using map instead of each:
trello.get("/1/members/me/boards") // resolves with array of board objects
.map((board) => {
return somePromisifiedSaveToDbFunction(board);
}).all(promises).catch(console.log).then( function(results){
res.redirect('/');
} );

Durandal KO binding when data is fetched in activate

A parameter governs what data is to be displayed. The parameter is retrieved from activationData in the activate method of the view model and used in a call to a Web Api method. Data is returned, and added to the view model like this
define(['durandal/app', 'knockout', 'moment'],
function (app, config, ko, moment) {
var vm = {
app: app
};
vm.activate = function (activationData) {
vm.ChecklistInstanceId = activationData.ChecklistInstanceId;
$.ajax({
url: "api/ChecklistInstance/" + vm.ChecklistInstanceId,
headers: { Authorization: "Session " + app.SessionToken() }
}).done(function (data) {
$.extend(vm, ko.mapping.fromJS(data));
});
};
return vm;
});
Inspecting the viewmodel immediately after it is extended reveals that it is decorated with observables exactly as expected. For example, vm.Caption() exists and returns the string I expect, and vm.Section() is an appropriately populated observable array, and so on down a fairly elaborate object graph.
The problem is the binding phase has already occurred, and at that time the view model lacks all the observables to which I'm trying to bind.
Two possible strategies suggest themselves:
obtain the parameter earlier
re-bind
I don't know how to do either of those things. Can anyone tell me how to re-organise my code to allow binding to parametrically fetched data?
A third possibility occurred to me:
define(['durandal/app', 'knockout', 'moment'],
function (app, config, ko, moment) {
var vm = {
app: app,
Caption: ko.observable(),
Section: ko.observableArray()
};
vm.activate = function (activationData) {
vm.ChecklistInstanceId = activationData.ChecklistInstanceId;
$.ajax({
url: "api/ChecklistInstance/" + vm.ChecklistInstanceId,
headers: { Authorization: "Session " + app.SessionToken() }
}).done(function (data) {
var foo = ko.mapping.fromJS(data);
vm.Caption(foo.Caption());
vm.Section(foo.Section());
});
};
return vm;
});
This works because all the observables exist in the binding phase. This might seem surprising given that I describe only the root of a potentially deep object graph, but the fact that the observable array is empty causes the binding phase to exit without a hitch.
Later in the activate handler, values are added to the observable array after ko.mapping has its way with the data, and binding succeeds.
I have a sense of dèja vu from this: it is eerily reminiscent of problems solved using forward declarations in TurboPascal back in the eighties. La plus ça change...
In order to work on a fully-constructed view, you will need to move your logic to either the attached handler or the compositionComplete handler. As you said, at the activate stage, the DOM isn't yet fully constructed. You can read about these lifecycle callbacks here.
Typically, what we do is pass the activationData in through the activate handler, store the activationData locally (if your viewModel is instance-based, then on a property in the constructor), and then reference that activationData in the attached or the compositionComplete handler.
You can fetch the data in the activate handler, and then store the data locally. But that's all you should do there. Reserve view-related logic for later in the cycle. In this case, you may need to return a promise from activate, and then resolve upon receiving your data. You can read about it here.
UPDATE
Take a look at this post, and the conversation there.