How to create promise based action (instead of sendAction) in ember? - ember.js

I have a use case where the action should get the value from another component, based on that I need to do some actions.
Initially, I used sendAction (instead of promiseAction()) to do some actions. But closeDataModal() runs immediately after finishing the sendAction. I want the first function to finish up and then run the second one.
saveAction() {
promiseAction()
closeDataModal() -> Run after resolving the promiseAction
}

Use an async function so that you can await an async operation like an async action.
async saveAction() {
await promiseAction()
closeDataModal() -> will run after resolving the promiseAction
}
If you want to use the result of promiseAction then:
async saveAction() {
let result = await promiseAction()
closeDataModal(result) -> will run after resolving the promiseAction
}
As mentioned in the comments, this will not work with sendAction which is deprecated.
See https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Statements/async_function for more infos on how async/await works and how error handling is done.

Related

How can I wait for a future to finish during a test if it wasn't called from the test directly?

I'm trying to write a test for a method that makes a call to an API using Dio. The Dio response has been mocked using http_mock_adapter. My problem is that I need to wait for the API call to finish before continuing with the test, and I can't simply use await since the method I'm testing isn't asynchronous. Is there a way to wait for a future that wasn't called from the test?
Below is an example of what I'm talking about:
String apiResult = 'foo';
void methodToTest(){
apiCall().then((value) => apiResult = value);
}
test('methodToTest works', () {
expect(apiResult, equals('foo'));
methodToTest();
// I need to wait for apiCall to finish here.
expect(apiResult, equals('bar'));
});
Previously, I have been able to use Future.delayed(Duration.zero) when I have had situations like this, but it has always seemed like a workaround, and now it doesn't work.
the method I'm testing isn't asynchronous
Congratulations, your tests found a bug.
this is your method after fixing the bug:
Future<void> methodToTest() async {
apiResult = await apiCall();
}

element is not rendering properly with *ngIf async pipe in unit test spec

What I'm trying to do is get the reference of a button in order to simulate a click event, my main problem is that the button is not rendering cause the component is not detecting the change of a list that should be populated by that time.
Here is my unit test code :
it('should add more cases when load more cases button is clicked', () => {
spyOn(component, 'loadMoreCases');
component.cases$.subscribe((cases) => {
fixture.detectChanges();
let buttons = fixture.debugElement.nativeElement.querySelectorAll('[nz-button]') as HTMLButtonElement[];
buttons[1].click();
expect(component.loadMoreCases).toBeCalled;
});
component.ngAfterViewInit();
fixture.detectChanges();
});
here is the *ngIf part in the HTML:
enter image description here
here is the service that is called after view init in the main component:
ngAfterViewInit(): void {
this.caseService
.getPaginatedCases(1, 15)
.pipe(take(1))
.subscribe((cases) => {
this.cases$.next(cases.list);
this.amountOfCases = cases.total;
});
}
I just want to know how to tell angular that the cases have some mock data already and inform the component to rerender in order to be able to access the button that it's in the *ngIf, I had been trying with everything, fakeAsync() using the tick function, async using done function, but nothing had worked.
How are you providing the data? Mocking the service and then returning them should be enough for it to work:
const getPaginatedCases = spyOn(caseService, "getPaginatedCasees");
getPaginatedCases.and.returnValue(of(mockCases));
Of course, it's important that this is run before ngOnInit() (you have the initializer in ngAfterViewInit(), but I think it belongs in init – doesn't matter for the sake of this answer anyway) is called, so fixture.detectChanges() can only be called after this has run. Don't forget that you might have a fixture.detectChanges() in the beforeEach() that is messing you up.

Ember 3.0 acceptance redirect test hangs forever

I have a simple acceptance test written in the modern RFC 268 format for Ember 3.0.
The test is for a page where, if the user is unauthenticated, the URL immediately redirects to /login.
...
module('Acceptance | index', function (hooks) {
setupApplicationTest(hooks);
test('visiting / logged out', async function(assert) {
assert.expect(1);
// Test hangs here forever.
await visit('/');
assert.equal(currentURL(), '/login');
});
});
This test worked great using the older format with moduleForAcceptance.
Unfortunately, this test hangs forever in Ember 3.0. Am I missing something here? Is there a better way to test a redirect?
There are no errors in the console, and the addition of some console.log statements show that the await is where the test hangs.
I found the reason why this was failing. I have an Ember mixin that I use to enhance all of my routes. The mixin checks for whether or not a user is authenticated, and redirects to /login as needed.
export default Mixin.create({
session: service(),
beforeModel() {
this._super(...arguments);
return new EmberPromise((resolve) => {
if (authenticated) {
resolve();
return;
}
this.transitionTo('login');
});
}
});
You'll notice that I am not resolving if authenticated is falsey. That worked fine with my app and the test syntax in 2.18.
The docs say the following regarding that hook I am overriding in my mixin.
returns Any | Promise
if the value returned from this hook is a promise, the transition will pause until the transition resolves. Otherwise, non-promise return values are not utilized in any way.
To me, the bit about "non-promise return values" implies that I should be able to do what I'm doing. Especially considering this worked in 2.18, but I wonder if this was one of those "wow, how did that ever work in the first place" scenarios. Clearly this syntax isn't working in 3.0. since the transition pauses forever when testing.
The answer for me was to ensure I always resolve/reject something. In this case, I had to add an explicit reject() so that the promise chain doesn't hang.
export default Mixin.create({
session: service(),
beforeModel() {
this._super(...arguments);
return new EmberPromise((resolve) => {
if (authenticated) {
resolve();
return;
}
this.transitionTo('login');
reject();
});
}
});
My test was fine. It was the mixin that needed updating in order to work properly with Ember 3.0 and the latest testing syntax.
The issue was not what your beforeModel hook resolves if user is authenticated but that your Promise does not resolve at all. You don't have to return a Promise in beforeModel hook but if you return one it will block the transition until the Promise is resolved. Since it's not clear how ember should react if another transition is called while current transition is blocked (not resolved/rejected promise), resolving or rejecting is correct behavior. Please have in mind that in a Promise return does not have any other meaning than ending your execution. It does not resolve or reject your Promise.
Another reason visit() could hang is that it waits for timers like Ember.run.later() to resolve, causing a non-obvious block somewhere in the application.
AlphaGit on github summarized the issue with an example, saying:
Most of the actions that Ember.testing executes (like visit) will
append to a promise that gets executed action after action in the
right order. In order to pass to the next action, Ember.testing makes
sure that there is nothing pending, so that the step can be considered
complete and move forward.
Along with the things that are tested for, pending AJAX requests are
verified, and also scheduled timers. These timers may arise from, you
guessed it, Ember.run.later calls. If for any reason you would have in
your code periodic Ember.run.later methods (so that one is always
waiting to be excuted), it's likely that you'll face this issue.
I've faced it myself in a similar scenario: My server returns a OAuth
access token with 100 hours until expired, so ember-simpleAuth
registers a call close to the expiration time with Ember.run.later to
refresh the token. This will, however, prevent the test from moving
along. My specific situations has been fixed in further versions but
any similar behavior will reproduce the issue (which is likely a
conclusion of the current design of Ember.testing).
Here are a couple other examples of users running into similar issues:
https://stackoverflow.com/a/58526993/3257984
https://stackoverflow.com/a/27887807/3257984

Catch app 'ready' event outside the app

I need to run some code after ember application got initialized. I don't want to invoke this code from App.ready to avoid tight coupling. It would be nice to have something like this:
App.on 'ready, -> console.log('do stuff')
But it won't work since Em.Application object is not subscribable and ready isn't really an event despite that docs said so
A simple way you can achieve this would be to extend your Application class with the Ember.Evented mixin:
App = Ember.Application.createWithMixins(Ember.Evented, {
ready: function() {
console.log('App ready');
this.trigger('appReady');
}
});
And you hook inside the ready event inside your app and trigger your custom event using this.trigger(...)
At this point you can use .on(...) to be notified when the event is triggered.
App.on('appReady', function() {
console.log('App already ready');
});
Example demo.
Hope it helps.
An other possibility may be to invoke your code from the resolve callback of the application.
App.then(function(app) {
console.log("App is resolved, so it's ready");
});
example stolen from #intuitivepixel ;) http://jsbin.com/AdOVala/66/edit
Edit/Note:
App.then() has been deprecated, see http://emberjs.com/deprecations/v1.x/#toc_code-then-code-on-ember-application:
As part of the Ember.DeferredMixin deprecation, using .then on an
Ember.Application instance itself has been deprecated.
You can use the ready hook or initializers to defer/advance readiness instead.

Handling several event listeners

Update: here's a fiddle of my problem. The tests pass once, and fail the next time:
http://jsfiddle.net/samselikoff/hhk6u/4/
The problem is departments has events.on("userSet:company"), so both variables respond to the event.
This is a general question about unit testing. In my app, a certain event is fired, and several other pieces of my app listen for this event. I'd like to unit test each piece separately, since they are performing different functions; but to do this, I have to fire off the event in each test.
This causes problems, since the first test must fire off the event, triggering the listeners in the other tests. How can I keep my tests atomic while still testing multiple event listeners?
(I am using QUnit, but I think this is a more general unit-testing question).
Answer:
Jeferson is correct. One easy way to solve this, is to use events.once instead of events.on. This way you clean up your events from each test.
All your calls to async methods should be tested using "asyncTest" methods and making sure you wrap your calls in other functions that calls QUnit.start() when the assertions data are ready to be collected and analyzed.
I updated your JSFiddle with working code: http://jsfiddle.net/hhk6u/8/
The new code is:
QUnit.config.autostart = false;
QUnit.config.testTimeOut = 1000;
asyncTest('Some test that needs companies.', function() {
function getCompanies() {
var companies = new Companies();
ok(1);
start();
}
setTimeout(getCompanies, 500);
});
asyncTest('Some other async test that triggers a listener in companies.', function() {
var companies = new Companies();
events.trigger("userSet:company", { name: "Acme", id: 1 });
stop();
events.on('fetched:departments', function(response) {
console.log(response);
deepEqual(response, [1, 2, 3]);
start();
});
});
See my answer in this other question for more details:
Test fails then succeeds
Hope this helps you!