2017-07-06 20:08:14 +02:00
|
|
|
/*
|
|
|
|
|
|
|
|
This test module actually tests our test code, particularly zjquery, and
|
|
|
|
it is intended to demonstrate how to use zjquery (as well as, of course, verify
|
2019-04-11 21:12:07 +02:00
|
|
|
that it works as advertised). This test module is a good place to learn how to
|
|
|
|
stub out functions from jQuery.
|
2017-07-06 20:08:14 +02:00
|
|
|
|
|
|
|
What is zjquery?
|
|
|
|
|
|
|
|
The zjquery test module behaves like jQuery at a very surface level, and it
|
|
|
|
allows you to test code that uses actual jQuery without pulling in all the
|
|
|
|
complexity of jQuery. It also allows you to mostly simulate DOM for the
|
|
|
|
purposes of unit testing, so that your tests focus on component interactions
|
|
|
|
that aren't super tightly coupled to building the DOM. The tests also run
|
2019-04-11 21:12:07 +02:00
|
|
|
faster! Inorder to keep zjquery light, it only has stubs for the most commonly
|
|
|
|
used functions of jQuery. This means that it is possible that you may need to
|
|
|
|
stub out additional functions manually in the relevant test module.
|
2017-07-06 20:08:14 +02:00
|
|
|
|
|
|
|
The code we are testing lives here:
|
|
|
|
|
|
|
|
https://github.com/zulip/zulip/blob/master/frontend_tests/zjsunit/zjquery.js
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
// The first thing we do to use zjquery is patch our global namespace
|
|
|
|
// with zjquery as follows. This call gives us our own instance of a
|
|
|
|
// zjquery stub variable. Like with real jQuery, the '$' function will
|
|
|
|
// be the gateway to a bigger API.
|
|
|
|
set_global('$', global.make_zjquery());
|
|
|
|
|
2018-05-15 12:40:07 +02:00
|
|
|
run_test('basics', () => {
|
2017-07-06 20:08:14 +02:00
|
|
|
// Let's create a sample piece of code to test:
|
|
|
|
|
|
|
|
function show_my_form() {
|
|
|
|
$('#my-form').show();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Before we call show_my_form, we can assert that my-form is hidden:
|
|
|
|
assert(!$('#my-form').visible());
|
|
|
|
|
|
|
|
// Then calling show_my_form() should make it visible.
|
|
|
|
show_my_form();
|
|
|
|
assert($('#my-form').visible());
|
|
|
|
|
|
|
|
// Next, look at how several functions correctly simulate setting
|
|
|
|
// and getting for you.
|
2019-11-02 00:06:25 +01:00
|
|
|
const widget = $('#my-widget');
|
2017-07-06 20:08:14 +02:00
|
|
|
|
|
|
|
widget.attr('data-employee-id', 42);
|
|
|
|
assert.equal(widget.attr('data-employee-id'), 42);
|
2020-01-03 17:10:59 +01:00
|
|
|
assert.equal(widget.data('employee-id'), 42);
|
|
|
|
|
|
|
|
widget.data('department-id', 77);
|
|
|
|
assert.equal(widget.attr('data-department-id'), 77);
|
|
|
|
assert.equal(widget.data('department-id'), 77);
|
2017-07-06 20:08:14 +02:00
|
|
|
|
2020-05-16 08:16:35 +02:00
|
|
|
widget.data('department-name', 'hr');
|
|
|
|
assert.equal(widget.attr('data-department-name'), 'hr');
|
|
|
|
assert.equal(widget.data('department-name'), 'hr');
|
|
|
|
|
2017-07-06 20:08:14 +02:00
|
|
|
widget.html('<b>hello</b>');
|
|
|
|
assert.equal(widget.html(), '<b>hello</b>');
|
|
|
|
|
|
|
|
widget.prop('title', 'My Widget');
|
|
|
|
assert.equal(widget.prop('title'), 'My Widget');
|
|
|
|
|
|
|
|
widget.val('42');
|
|
|
|
assert.equal(widget.val(), '42');
|
2018-05-15 12:40:07 +02:00
|
|
|
});
|
2017-07-06 20:08:14 +02:00
|
|
|
|
2018-05-15 12:40:07 +02:00
|
|
|
run_test('finding_related_objects', () => {
|
2017-07-06 20:08:14 +02:00
|
|
|
// Let's say you have a function like the following:
|
|
|
|
function update_message_emoji(emoji_src) {
|
|
|
|
$('#my-message').find('.emoji').attr('src', emoji_src);
|
|
|
|
}
|
|
|
|
|
|
|
|
// This would explode:
|
|
|
|
// update_message_emoji('foo.png');
|
|
|
|
|
|
|
|
// The error would be:
|
|
|
|
// Error: Cannot find .emoji in #my-message
|
|
|
|
|
|
|
|
// But you can set up your tests to simulate DOM relationships.
|
|
|
|
//
|
2017-07-08 14:31:18 +02:00
|
|
|
// We will use set_find_results(), which is a special zjquery helper.
|
2019-11-02 00:06:25 +01:00
|
|
|
const emoji = $('<div class="emoji">');
|
2017-07-08 14:31:18 +02:00
|
|
|
$('#my-message').set_find_results('.emoji', emoji);
|
2017-07-06 20:08:14 +02:00
|
|
|
|
|
|
|
// And then calling the function produces the desired effect:
|
|
|
|
update_message_emoji('foo.png');
|
|
|
|
assert.equal(emoji.attr('src'), 'foo.png');
|
|
|
|
|
2020-05-21 05:19:57 +02:00
|
|
|
// Sometimes you want to deliberatly test paths that do not find an
|
|
|
|
// element. You can pass 'false' as the result for those cases.
|
|
|
|
emoji.set_find_results('.random', false);
|
|
|
|
assert.equal(emoji.find('.random').length, 0);
|
2017-07-08 14:49:09 +02:00
|
|
|
/*
|
|
|
|
An important thing to understand is that zjquery doesn't truly
|
|
|
|
simulate DOM. The way you make relationships work in zjquery
|
|
|
|
is that you explicitly set up what your functions want to return.
|
|
|
|
|
|
|
|
Here is another example.
|
|
|
|
*/
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const my_parents = $('#folder1,#folder4');
|
|
|
|
const elem = $('#folder555');
|
2017-07-08 14:49:09 +02:00
|
|
|
|
|
|
|
elem.set_parents_result('.folder', my_parents);
|
|
|
|
elem.parents('.folder').addClass('active');
|
|
|
|
assert(my_parents.hasClass('active'));
|
|
|
|
|
2018-05-15 12:40:07 +02:00
|
|
|
});
|
2017-07-06 20:08:14 +02:00
|
|
|
|
2018-05-15 12:40:07 +02:00
|
|
|
run_test('clicks', () => {
|
2017-07-06 20:08:14 +02:00
|
|
|
// We can support basic handlers like click and keydown.
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const state = {};
|
2017-07-06 20:08:14 +02:00
|
|
|
|
|
|
|
function set_up_click_handlers() {
|
2020-07-02 01:45:54 +02:00
|
|
|
$('#widget1').click(() => {
|
2017-07-06 20:08:14 +02:00
|
|
|
state.clicked = true;
|
|
|
|
});
|
|
|
|
|
2020-07-02 01:45:54 +02:00
|
|
|
$('.some-class').keydown(() => {
|
2017-07-06 20:08:14 +02:00
|
|
|
state.keydown = true;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Setting up the click handlers doesn't change state right away.
|
|
|
|
set_up_click_handlers();
|
|
|
|
assert(!state.clicked);
|
|
|
|
assert(!state.keydown);
|
|
|
|
|
|
|
|
// But we can simulate clicks.
|
|
|
|
$('#widget1').click();
|
|
|
|
assert.equal(state.clicked, true);
|
|
|
|
|
|
|
|
// and keydown
|
|
|
|
$('.some-class').keydown();
|
|
|
|
assert.equal(state.keydown, true);
|
|
|
|
|
2018-05-15 12:40:07 +02:00
|
|
|
});
|
2017-07-06 20:08:14 +02:00
|
|
|
|
2018-05-15 12:40:07 +02:00
|
|
|
run_test('events', () => {
|
2017-07-06 20:08:14 +02:00
|
|
|
// Zulip's codebase uses jQuery's event API heavily with anonymous
|
|
|
|
// functions that are hard for naive test code to cover. zjquery
|
|
|
|
// will come to our rescue.
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
let value;
|
2017-07-06 20:08:14 +02:00
|
|
|
|
|
|
|
function initialize_handler() {
|
2020-07-02 01:45:54 +02:00
|
|
|
$('#my-parent').on('click', '.button-red', (e) => {
|
2018-07-18 13:39:53 +02:00
|
|
|
value = 'red'; // just a dummy side effect
|
|
|
|
e.stopPropagation();
|
|
|
|
});
|
|
|
|
|
2020-07-02 01:45:54 +02:00
|
|
|
$('#my-parent').on('click', '.button-blue', (e) => {
|
2018-07-18 13:39:53 +02:00
|
|
|
value = 'blue';
|
2017-07-06 20:08:14 +02:00
|
|
|
e.stopPropagation();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calling initialize_handler() doesn't immediately do much of interest.
|
|
|
|
initialize_handler();
|
|
|
|
assert.equal(value, undefined);
|
|
|
|
|
|
|
|
// We want to call the inner function, so first let's get it using the
|
|
|
|
// get_on_handler() helper from zjquery.
|
2019-11-02 00:06:25 +01:00
|
|
|
const red_handler_func = $('#my-parent').get_on_handler('click', '.button-red');
|
2017-07-06 20:08:14 +02:00
|
|
|
|
|
|
|
// Set up a stub event so that stopPropagation doesn't explode on us.
|
2019-11-02 00:06:25 +01:00
|
|
|
const stub_event = {
|
2017-07-06 20:08:14 +02:00
|
|
|
stopPropagation: function () {},
|
|
|
|
};
|
|
|
|
|
2020-03-28 01:25:56 +01:00
|
|
|
// Now call the handler.
|
2018-07-18 13:39:53 +02:00
|
|
|
red_handler_func(stub_event);
|
2017-07-06 20:08:14 +02:00
|
|
|
|
|
|
|
// And verify it did what it was supposed to do.
|
2018-07-18 13:39:53 +02:00
|
|
|
assert.equal(value, 'red');
|
|
|
|
|
|
|
|
// Test we can have multiple click handlers in the parent.
|
2019-11-02 00:06:25 +01:00
|
|
|
const blue_handler_func = $('#my-parent').get_on_handler('click', '.button-blue');
|
2018-07-18 13:39:53 +02:00
|
|
|
blue_handler_func(stub_event);
|
|
|
|
assert.equal(value, 'blue');
|
2018-05-15 12:40:07 +02:00
|
|
|
});
|
2017-07-08 15:16:19 +02:00
|
|
|
|
2018-05-15 12:40:07 +02:00
|
|
|
run_test('create', () => {
|
2017-07-08 15:16:19 +02:00
|
|
|
// You can create jQuery objects that aren't tied to any particular
|
|
|
|
// selector, and which just have a name.
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const obj1 = $.create('the table holding employees');
|
|
|
|
const obj2 = $.create('the collection of rows in the table');
|
2017-07-08 15:16:19 +02:00
|
|
|
|
|
|
|
obj1.show();
|
|
|
|
assert(obj1.visible());
|
|
|
|
|
|
|
|
obj2.addClass('.striped');
|
|
|
|
assert(obj2.hasClass('.striped'));
|
2018-05-15 12:40:07 +02:00
|
|
|
});
|
2018-04-12 14:45:06 +02:00
|
|
|
|
2018-05-15 12:40:07 +02:00
|
|
|
run_test('extensions', () => {
|
2018-04-12 14:45:06 +02:00
|
|
|
// You can extend $.fn so that all subsequent objects
|
|
|
|
// we create get a new function.
|
|
|
|
|
|
|
|
$.fn.area = function () {
|
|
|
|
return this.width() * this.height();
|
|
|
|
};
|
|
|
|
|
|
|
|
// Before we use area, though, let's illustrate that
|
|
|
|
// the predominant Zulip testing style is to stub objects
|
|
|
|
// using direct syntax:
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const rect = $.create('rectangle');
|
2020-07-02 01:41:40 +02:00
|
|
|
rect.width = () => 5;
|
|
|
|
rect.height = () => 7;
|
2018-04-12 14:45:06 +02:00
|
|
|
|
|
|
|
assert.equal(rect.width(), 5);
|
|
|
|
assert.equal(rect.height(), 7);
|
|
|
|
|
|
|
|
// But we also have area available from general extension.
|
|
|
|
assert.equal(rect.area(), 35);
|
2018-05-15 12:40:07 +02:00
|
|
|
});
|