I have a master/detail page that I am trying to test. There is a list of things, which, when you click renders the route for the thing.
The following test finds the thing to click, but doesn't seem to click it.
#exists = (selector) ->
!!find(selector).length
...
test "things expand to show a detail view", 1, ->
visit("/").then(->
click(".things li:contains('Example Thing')")
).then ->
ok(#exists("h2")) # a tag in the detail view
...
<ul class='things'>
{{#each controller}}
<li>{{#linkTo 'thing' this}}{{title}}{{/linkTo}}</li>
{{/each}}
</ul>
{{outlet}}
...
<div class='thing'>
<h2>{{title}}</h2>
</div>
This raises with: Failure/Error: Element h2 not found. Does anybody know what I am missing/how to get this test to pass?
I am using teaspoon (formerly known as teabag) in the browser as a test runner, if that makes a difference. Should I be able to see the interaction happening as the test clicks around?
You shouldn't click the <li> element, but the <a> element instead.
The linkTo helper in handlebars generate an <a> tag in the DOM.
Something like click(".things li:contains('Example Thing') a ") should works.
Related
I have multiple tabs in my page which link to some internal routes. When an user clicks on a tab ,the route has to be rendered and also the tab has to be highlighted but once in a few clicks the highlighting does not shift to the new tab and the old tab remains highlighted but the route of the clicked tab gets rendered.
The highlighting is done by an action inside the li tag and the #link-to is nested inside the li tag.
In my investigation till now what I have found is that the when this happens the click event is not registered. I get a bunch of mouse events but no click event. Seems like the click event is eaten up.
<ul class="nav nav-tabs">
{{# each tab in tabBars}}
<li {{action 'someAction'}}>{{#link-to tab.link}}{{/link-to}}</li>
{{/each}}
</ul>
The action should be triggered all times when a tab is click and the new tab should be highlighted.
Try to do something like this instead:
{{#link-to tab.link tagName='li'}}click me{{/link-to}}
(and omit the <li> tag).
Then you can check for the active class to highlight the li element.
The {{link-to}} helper renders a link which will get an additional class="active" if the link matches the current route. You can then style this by CSS.
See https://guides.emberjs.com/v1.10.0/templates/links/ and search for "active".
Other options
Since the {{link-to}} will trigger the tab.link route's beforeModel hook, perhaps this is the place you might want to set view-related properties to be "data-downed" to the <li>. I would personally reserve this hook for business logic.
When I absolutely need to fire an action before transitioning (usually, this is when I want to fire something like a tracking event), I use the invokeAction closure action which comes with the ember-link-action addon although it looks like might only support 1.13.13
{{link-to invokeAction=(action "someAction")}}
Best practice
For the action in the <li> element you generally want to avoid setting actions on non-interactive elements since it creates an accessibility concern for users of assistive technology (see no-invalid-interactive).
However, this doesn't mean you should turn the <li> element into an interactive element by doing something link <li role="button"> as this violates more a11y concerns (see no-nested-interactive).
I found a solution which does not involve actions:
<ul class="nav nav-tabs">
{{#each tab in tabs}}
{{#link-to tab.link id=tab.id tagName='li' }}<a>{{tab.title}}</a>{{/link-to}}
{{/each}}
</ul>
link-to masquerading as li and having a dummy anchor inside it.
When writing Acceptance tests, does anyone happen to know how one would select the LAST element in a list? I am testing the act of adding a new record to a list and would like to edit/delete the newly created record in my test.
I've tried to select the newly created record like this....
let newRecordLink = find('div.parent-list .list-group-item').last();
click(newRecordLink);
Which sorta works. It pulls the last element, but it busts out of my test and suddenly I'm on the actual page of the app I'm trying to test instead of remain within the test container.
Here is the relevant HBS code:
<div class="list-group parent-list">
{{#each model.owners as |item|}}
{{#link-to 'client.members.edit-owner' item.id class="list-group-item" id=item.id}}
<h4 class="list-group-item-heading">
{{item.firstName}} {{item.lastName}}
</h4>
{{/link-to}}
{{/each}}
</div>
You can use :last selector (https://api.jquery.com/last-selector/)
click('div.parent-list .list-group-item:last')
if it is an ember array u can use .get('lastObject')
I was struggling with this too, but I got it to work by using :last-of-type instead of :last
Template:
{{#each document in documents}}
<div class="col-md-6" {{action "selectDocument" document}}>{{view Ember.Checkbox checked=document.isSelected}} {{document.name}}</div>
{{/each}}
Controller:
App.IndexDocumentsController = Ember.ArrayController.extend({
actions: {
selectDocument: function(document){
document.set('isSelected', !document.get('isSelected'));
}
}
});
When I click on the div, the checkbox toggles 'checked' property. But when I click on the ckeckbox - nothing happens. What can be the reason?
UPDATED
Link to jsbin: http://emberjs.jsbin.com/nuvocumuteto/1/edit?html,css,js,output
The issue is that when you click on the checkbox 2 things happen.
the checkbox toggles the isActive property, then
the selectRow action is ran which again toggles the isActive property
So the isActive property ends up staying in the same state that it was.
In your case I would get rid of the action, wrap the checkbox in a <label> and set the label to display: block.
The template would look like
<script type="text/x-handlebars" data-template-name="index">
<ul>
{{#each item in model}}
<li {{bind-attr class="item.isActive:active"}}><label>{{input type="checkbox" checked=item.isActive}}{{item.name}}</label></li>
{{/each}}
</ul>
</script>
and the css would look like
label {
display: block;
}
you would then be able to get rid of the selectRow action completely because clicking on the label will trigger the checkbox.
You can see a working bin here: http://emberjs.jsbin.com/nuvocumuteto/3/edit
I would argue that you are not following "The Ember Way" in two different ways here.
First, Ember.Checkbox is an internal Ember class (http://emberjs.com/api/classes/Ember.Checkbox.html). The recommended way to render a checkbox is to use the Handlebars input helpers (http://emberjs.com/guides/templates/input-helpers/#toc_checkboxes). This is just wrapping Ember.Checkbox anyway.
Second, if you want to update the value of isSelected, the "Ember Way" is to use two-way data bindings. Your code uses one-way data-binding when it reads document.isSelected and then tries to manually re-create the the data-binding in the other direction when the user clicks by manually writing a selectDocument action and calling it from an {{action}}.
Instead, simply bind the Ember Handlebars Input Helper directly to your value like this:
{{#each document in documents}}
<div class="col-md-6">{{input type="checkbox" checked=document.isSelected}} {{document.name}}</div>
{{/each}}
Say I have a few links in my navigation:
...
{{#linkTo 'projects.trending' tagName="li"}}
<a href="#" {{bindAttr href="view.href"}}> Trending projects</a>
{{/linkTo}}
{{#linkTo 'projects.all' tagName="li"}}
<a href="#" {{bindAttr href="view.href"}}> All projects</a>
{{/linkTo}}
...
When I click one of these links, it will fetch data via Ajax and go to that route. But sometimes it takes a while to fetch the data, so I'd like to show a spinner. I currently already have a global spinner at the top of the page. It would be cool to show a spinner next to the navigation link that was clicked, so the user sees what page is currently loading.
What would be the best/easiest approach to implement this?
You could put a span next to each navigation link that's hidden by default, and contains the spinner. Then onClick, show the span. Hide all of them after ajax is done.
(Note: I am using Ember version 1.0.0-rc.3)
I'm trying to catch the 'click' of a {{linkTo}} using a view, so that I can do additional stuff (basically scroll the list of users in the sidebar) besides merely loading the new template. Me being relatively new to this (but having read the documentation!), I thought the following would just work:
"users" template:
{{#each user in users}}
{{#view App.ClickView}}
{{#linkTo user user}}{{ user.name }}{{/linkTo}}
{{/view}}
{{/each}}
the view code:
App.ClickView = Ember.View.extend({
click: function(evt) {
// do stuff
}
});
and for context, the layout template:
<div id='sidebar'>
{{#each user in users}}
{{#linkTo user user}}{{ user.name }}{{/linkTo}}
{{/each}}
</div>
<div id='main'>
{{ outlet }}
</div>
Referring back to the users template, you can see that each {{linkTo}} is contained within a view. I'm expecting for a click on that {{linkTo}} to therefore bubble up to, and caught by the view (App.ClickView). Unfortunately, it doesn't. It seems like the click is somehow not being bubbled up to the view when it's happens on a {{linkTo}}... What should I do?
Note #1:
If I replace the {{linkTo}} (only in div#main! I don't intend to replace the ones in div#sidebar) with an <a> element, it works, and the click gets caught by the view. However, I'm not so sure that i want to go down this route (I'd have to replicate the functionality of the {{linkTo}}!). And I'm thinking that there ought to be a better way to do this. Is there?
Note #2:
*Note that I'm also aware that i can put my intended "do stuff" code in renderTemplate() of the UserRoute, but the problem with that is that the effect will happen for every link to that route (including the ones in the sidebar - which is not what I want). I want the scroll to only trigger for specific {{linkTo}}s - specifically the {{linkTo}}s in div#main.
I would suggest using an anchor (<a>) tag with {{action ... target="view"}} in it instead of linkTo, apply your conditional logic in the view, and then if appropriate, re-send to the controller (this.get('controller').send(actionName), let it bubble to the router, and do a transitionTo in a router event handler.