Code that I'm trying to test:
$(".toggle_item").on("change", function() {
console.log("change triggered")
item_name = $(this).data("name");
value = $(this).prop("checked");
if (Item.isValid(item_name) && cartModule.isUnique(item_name)) {
cartModule.toggleItem(item_name, value);
}
})
Jasmine spec:
describe("changing toggle item", function() {
beforeEach(function() {
console.log("in spec")
affix(".toggle_item[data-name='ladder']")
spyOn(cartModule, "isUnique")
spyOn(Item, "isValid")
$(".toggle_item").trigger("change")
})
it("should check item for validity & cart for uniqueness", function() {
expect(Item.isValid).toHaveBeenCalledWith("ladder")
expect(cartModule.isUnique).toHaveBeenCalledWith("ladder")
})
})
The console log output indicates that the trigger did not fire. Log output:
> "in spec"
FWIW:
- I have tried using both
affix
and loadFixtures
- I have tried using both
$(".toggle_item").on("change"...
and $(".toggle_item").change...
Gem list:
jasmine (2.7.0)
jasmine-core (2.8.0, 2.7.0)
jasmine-jquery-rails (2.0.3)
jasmine-rails (0.14.1)
** Updated answer: 2018-02-03**
The fundamental issue here is that your code is running too early, before your fixtures have been set up. This is different to loading your code before all of your tests. You need to be able to control when your code runs after it has been loaded.
This is more to do with how you write your code so that it can be tested, and is largely independent of the tools and libraries that are actually being used for testing.
Here is an example:
// There is no way to test this expression - it runs as soon as it is loaded
1 + 2
// This is similar to the line above - it tries to add the event listener as soon
// as the line of code is encountered. If you haven't set up fixtures before this line is run
// then you can't test it.
$('.toggle_item').on('change', ...);
// Here is an example of code that is testable
// It is testable because we have separated out the loading of the code
// from the **invocation** of the code.
function add(){
// i.e. This expression will only run when `add()` is invoked.
return 1 + 2;
}
// Here is an example that adds a bit more control over when the event listeners are added.
$(function(){
// This expression runs when `$.ready` is fired (aka `DOMContentLoaded`)
// If you can delay `$.ready` until your fixtures are set up then this will work.
$('.toggle_item').on('change', ...);
})
The explanation in your linked answer isn't quite right. $('.selector').click(...)
is just an alias or short-hand for $('.selector').on('click', ...)
so changing to that won't make any difference - they are functionally exactly the same.
The reason that answer actually works in that case is because the event listener is being added in the $.ready()
event instead of immediately as soon as the code is loaded.
It's the difference between running this:
console.log('1 before');
console.log('2 middle');
console.log('3 after');
Result:
> 1 before
> 2 middle
> 3 after
And running this:
console.log('1 before');
$(function(){
console.log('2 middle');
});
console.log('3 after');
Result:
> 1 before
> 3 after
> 2 middle
Another tool that might help you here is delegated event listeners.
Bascially, you add a single event listener to an element further up the DOM tree which listens for events from particular child selectors.
e.g.:
$(function(){
$(document).on('change', '.toggle_item', function(){
});
})
The benefit of this is that the event listener can be added early, event before .toggle_item
exists, but the listeners will be fired once the elements have been added.
There are some caveats to using event delegates (e.g. when using event.preventDefault()
or event.stopPropagation()
) but they are still a very useful tool.
Original answer
In your code under test are you able to control/delay when the event listener is added?
I suspect that your code is trying to add the event listener to .toggle_item
before the affix(".toggle_item[data-name='ladder']")
statement is being run in your tests.
console.log('Adding listener')
$(".toggle_item").on("change", function() {
console.log("change triggered")
...
});
You'll know it's out of order if you get:
> "Adding listener"
> "in spec"
Instead of what we want:
> "in spec"
> "Adding listener"