Acting on an element in onRender doesn't work - marionette

When I try to act on some HTML elements in the onRender method (or in a item:rendered callback), it fails.
Example:
Bars.EditGallery = Backbone.Marionette.ItemView.extend
template: 'bars/edit_gallery'
className: 'edit-gallery'
onRender: ->
# If I just write #$('select').chosen(), it doesn't work
# despite the jQuery object contains what I expect.
# To get it working, I have to write:
callback = -> #$('select').chosen()
setTimeout(callback, 0)
It's the same with others actions, like giving the focus to a field.
How do you deal with that? The trick with setTimeout works but it is not very elegant.

I've seen this happen when the templates used for rendering are loaded asynchronously. I thought I a pull request had fixed this in a recent release. What version of Marionette are you using?
But it looks like you're using JST, anyways, so that shouldn't be the problem. Is there anything else in your setup that is causing the render to happen asynchronously?
It seems likely that there is some asynchronous issue happening, though. Since using setTimeout fixes the problem, that makes me think the rendering is not completing before the onRender method is called.
Also - it can be hard tell if the jQuery selector is actually returning the object you want, right away. If you're using console.log to check the selector, this may be giving false results. console.log is itself asynchronous (in most browsers, anyways... not sure about all) which means the request to log the item gets queued up at the end of the event loop. It's likely that the DOM element is available by the time the logging happens.
FWIW: I use onRender for this exact purpose on a regular basis, and I've never had to use setTimeout to make it work. So my assumption is something funny going on with the rendering process, related to async stuff.

This problem is caused by Chosen, as mentioned in this issue.

Related

Cypress Assertion that a button does not exist [duplicate]

I want to be able to click on a check box and test that an element is no longer in the DOM in Cypress. Can someone suggest how you do it?
// This is the Test when the checkbox is clicked and the element is there
cy.get('[type="checkbox"]').click();
cy.get('.check-box-sub-text').contains('Some text in this div.')
I want to do the opposite of the test above.
So when I click it again the div with the class check-box-sub-text should not be in the DOM.
Well this seems to work, so it tells me I have some more to learn about .should()
cy.get('.check-box-sub-text').should('not.exist');
You can also search for a text which is not supposed to exist:
cy.contains('test_invite_member#gmail.com').should('not.exist')
Here you have the result in Cypress: 0 matched elements
Reference: Docs - Assertions, Existence
Use .should('not.exist') to assert that an element does not exist in the DOM.
Do not use not.visible assertion. It would falsely pass in < 6.0, but properly fail now:
// for element that was removed from the DOM
// assertions below pass in < 6.0, but properly fail in 6.0+
.should('not.be.visible')
.should('not.contain', 'Text')
Migration Docs here: Migrating-to-Cypress-6-0
Cypress 6.x+ Migration
According to cypress docs on Existence
The very popular attempt which is a bit naive will work until it doesn't and then you'll have to rewrite it again... and again...
// retry until loading spinner no longer exists
cy.get('#loading').should('not.exist')
This doesn't really work for the title problem which is what most people will be looking for.
This works for the case that it is being removed. but in the case that you want it to never exist... It will retry until it goes away.
However, if you want to test that the element never exists in our case.
Yes lol. This is what you really want unless you want to just have your headache again another day.
// Goes through all the like elements, and says this object doesn't exist ever
cy.get(`img[src]`)
.then(($imageSection) => {
$imageSection.map((x, i) => { expect($imageSection[x].getAttribute('src')).to.not.equal(`${Cypress.config().baseUrl}/assets/images/imageName.jpg`) });
})
cy.get('[data-e2e="create-entity-field-relation-contact-name"]').should('not.exist');
might lead to some false results, as some error messages get hidden. It might be better to use
.should('not.visible');
in that case.
Here's what worked for me:
cy.get('[data-cy=parent]').should('not.have.descendants', 'img')
I check that some <div data-cy="parent"> has no images inside.
Regarding original question, you can set data-cy="something, i.e. child" attribute on inner nodes and use this assertion:
cy.get('[data-cy=parent]').should('not.have.descendants', '[data-cy=child]')
You can use get and contains together to differentiate HTML elements as well.
<button type='button'>Text 1</button>
<button type='button'>Text 2</button>
Let's say you have 2 buttons with different texts and you want to check if the first button doesn't exist then you can use;
cy.get('button').contains('Text 1').should('not.exist')
Could be done also using jQuery mode in cypress:
assert(Cypress.$('.check-box-sub-text').length==0)
I closed an element and checked should('not.exist') but the assertion failed as it existed in the DOM. It just that it is not visible anymore.
In such cases, should('not.visible') worked for me. I have just started using cypress. A lot to learn.
No try-catch flow in cypress
In java-selenium, we usually add the NoSuchElementException and do our cases. if UI is not displaying element for some Role based access cases.
You can also query for the matched elements inside the body or inside the element's parent container, and then do some assertions on its length:
cy.get("body").find(".check-box-sub-text").should("have.length", 0);
In case anyone comes across this, I was having the issue that neither .should('not.exist') nor .should('have.length', 0) worked - even worse: If the element I was querying was actually there right from the get-go, both asserts still returned true.
In my case this lead to the very strange situation that these three assertions, executed right after each other, were true, even though asserts 1+2 and 3 contradict each other:
cy.get('[data-cy="foobar"]').should('not.exist')
cy.get('[data-cy="foobar"]').should('have.length', 0)
cy.get('[data-cy="foobar"]').should('have.text', 'Foobar')
After extensive testing, I found out that this was simply a race condition problem. I was waiting on a backend call to finish before running the above 3 lines. Like so:
cy.wait('#someBackendCall')
cy.get('[data-cy="foobar"]').should('not.exist')
However once the backend called finished Cypress immediately ran the first two assertions and both were still true, because the DOM hadn't yet caught up rerendering based on the backend-data.
I added an explicit wait on an element that I knew was gonna be there in any case, so my code now looks something like this:
cy.wait('#someBackendCall')
cy.get('[data-cy="some-element"]').should('contain', 'I am always here after loading')
cy.get('[data-cy="foobar"]').should('not.exist')
You can also use below code
expect(opportunitynametext.include("Addon")).to.be.false
or
should('be.not.be.visible')
or
should('have.attr','minlength','2')
Voted element is correct but I highly recommend not to using anti-pattern saving you from a lot of headaches. Why? Yes, because;
Your application may use dynamic classes or ID's that change
Your selectors break from development changes to CSS styles or JS behavior
Luckily, it is possible to avoid both of these problems.
Don't target elements based on CSS attributes such as: id, class, tag
Don't target elements that may change their textContent
Add data-* attributes to make it easier to target elements
Example:
<button id="main" name="submission" role="button" data-cy="submit">Submit</button>
And if you want to be more specific and want to indentify more than one selector, it is always good to use .shouldchainer.
Example:
cy.get("ul").should(($li) => {
expect($li).to.be.visible
expect($li).to.contain("[data-cy=attribute-name]")
expect($li).to.not.contain("text or another selector")
})
If there is no element, we can use simple line like:
cy.get('[type="checkbox"]').should('not.exist')
In my case, Cypress was so fast, that simple .should('not.be.visible') was passing the test and after that, loader appears and test failed.
I've manage to success with this:
cy.get('.loader__wrapper')
.should('be.visible')
cy.get('.loader__wrapper', { timeout: 10000 })
.should('not.be.visible')
Also nice to set the timeout on 10 seconds when your application loads more than 4s.
I would use :
cy.get('.check-box-sub-text').should('not.be.visible');
This is safer than
cy.get('.check-box-sub-text').should('not.exist');
( The element can be present in the DOM but not visible with display: none or opacity: 0 )

How can I get an immutable version of an object from store.getState()?

I'm using react-redux for a project I'm working on. I noticed that when I grab an object from my store and edit it, the object in state changes without me dispatching the change (but doesn't trigger a re-render on the components attached to that reducer's object). How can I stop state from changing without a dispatch?
For example if I do:
export function changeNeonGreenColourValue(colour) {
return (dispatch) => {
var neonColours = store.getState().colours.neon;
neonColours.green = colour;
dispatch(push('./home'));
};
}
And then in the layoutComponent I log:
console.log(this.props.state.colours.neon.green)
The output is still whatever I passed into changeNeonGreenColourValue() as "colour" but the page doesn't re-render to show that change. I know to get the page to re-render, all I have to do is dispatch the appropriate reducer case but I don't want the state object being altered at all unless I have an appropriate dispatch.
Apparently the 'standard' deep copying technique for solving this is to do a JSON stringifying and parse as so: const copiedObj = JSON.parse(JSON.stringify(sourceObj)); Unfortunately if you use this on large objects that will need parsing frequently you're going to have performance issues in your app as I do, if anyone has any suggestions for this I welcome them.
edit: so both jQuery and Loadash have their own implementations of deep cloning that are supposed to be better performance-wise:
https://lodash.com/docs/#cloneDeep
I personally implemented loadash to resolve my issue and it worked fine with little to no performance issues. I highly recommend it over JSON.stringify.

Race condition with Capybara value set

I have faced an issue while using .set(#{value}) to fill the text field in registering form, e.g: the phone number i wanna put in is 506307 then it ended up with 063075.
The work-around i have been made is executing Javascript block like
execute_script("document.querySelector('#{selector}').value = '#{value}'")
However, using the same scripts applying for Webmobile based on React.JS, the scripts above just send the text but didn't send the onChange event, which cause another element cannot be selected/clicked -> made the test failed.
I came up with another approach is to use the send_keys #{value} to trigger the key-pressed event that would make browser think there was a key-pressed event happen for that form, but it ended up with race-condition like set(#{value}) as i mentioned.
The another work-around is using What is the best way to trigger onchange event in react js , but i tend to use the native Capybara actions before making that tricky Javascript.
So, is there any other way to interact / fill the form field which won't cause that Race condition issue ?
Thanks everybody in advance.
Note: Any "solution" suggested that is purely the use of execute_script to run some JS is a terrible idea since it completely bypasses the concept of testing what a user can do and can basically make your test worthless.
The root cause of the issue here is the JS behavior attached to the input not being able to handle the key events fast enough. The proper fix would be to fix the JS, however if that's not possible there's a few things you can try
First you can try changing the clear method being used by set
element.set('506307', clear: :backspace)
or
element.set('506307', clear: :none)
If that doesn't change anything then try clicking on the input, followed by a short sleep before setting the content
element.click
sleep 0.25
element.set('506307')
If none of those work around the issue we need to know exactly what JS behavior you have attached to the input and/or what events that JS behavior is listening to.

What is the best place to put onEnter, onExit callback functionality?

I am creating my first project that uses ui-router.
My project has about 10 views, each with their own controller and state. I am trying to modularise/encapsulate/decouple as best as possible but I am having trouble working out where to put the onExit and onEnter state callbacks.
The first option is to put it in app.js which is currently defining all of my states, however I feel that this would not be a good place as it could cause this file to blow up and become hard to read as more states are introduced and the logic gets more complex.
The second option I looked into was to put it into a controller (I have one for each state), however from researching it doesn't seem to be best practice to do this.
The third option is to create a service that is resolved, however with this option I would end up with either a giant service full of state change functions for each of the states (not decoupled) or an additional service per state which would contain the state change functionality, and I worry that would increase project complexity.
What is the standard way to achieve this?
Are there any other options that I am missing?
Our strategy for this has been to disregard the onEnter and onExit on the state object, because as you are discovering, they feel like they are in the wrong place in terms of separation of concerns (app.js).
For onEnter: we handle setup in an activate() function in each controller, which we manually execute inside the controller. This happens to also match the callback that will get executed in Angular 2.0, which was not an accident ;).
function activate() {
// your setup code here
}
// execute it. this line can be removed in Angular 2.0
activate();
For onExit: We rarely need an exit callback, but when we do, we listen for the $scope $destroy event.
$scope.$on("$destroy", function() {
if (timer) {
$timeout.cancel(timer);
}
});

Using Promises in AngularJS Views

Mark Dalgleish wrote a nice little article about how to use promises in AngularJS views.
Some people asked questions about this in the comments, but Mark didn't answer them (yet). Because I'm asking me the same question, I will ask on StackOverflow instead to get an answer:
If you use promises in views, how do I handle "loading"/"waiting" indication, because they are async? Does a promise have something like a "resolved" or "withinRequest" property?
How do I handle errors? Normally they would arise in the second callback, but if I use a promise directly in the view I don't handle this case. Is there another way?
Thank you.
EDIT: as of angular v1.2 the resolution of promise in views is not activated by default.
The automatic resolution of promises in a view looks like a handy tool at first but it has number of limitations that need to be understood and evaluated carefully. The biggest issue with this approach is that it is AngularJS who will add callbacks to a promise and we've got little control over it.
Answering your questions:
1) As indicated, it is ultimately AngularJS who will add a success / error callbacks so we don't have much control here. What you could do is to wrap the original promise into a custom one that would track resolution. But this kind of deft the whole purpose of saving few keystrokes. And no, there is no things like 'resolved'. In short - there is no universal mechanism for tracking progress that would work for all promises. If your promises are $http-based you might use interceptors or pendingRequests property to track request in progress.
2) You can't. Once again, it is AngularJS that adds a handler inside the $parse service and it looks like this: promise.then(function(val) { promise.$$v = val; }); (see code here). You can see that only a success callback are added so all the failures are going to be silently ignored.
Those are not the only limitations of the automatic promise resolution in the view. The other problem is that promises returned by a function won't be resolved correctly. For example, if you would rewrite an example like so:
myModule.controller('HelloCtrl', function($scope, HelloWorld) {
$scope.messages = function() {
return HelloWorld.getMessages();
}
});
and try to use the following markup:
<li ng-repeat="message in messages()"></li>
things would work as expected, which might come as a surprise.
In short: while the automatic resolution of promises might seem like a handy shortcut it has number of limitations and non-obvious behaviors. Evaluate those carefully and decide if saving few keystrokes are worth it.

Resources