2020-08-01 03:43:15 +02:00
|
|
|
"use strict";
|
|
|
|
|
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.
|
2020-07-15 01:29:15 +02:00
|
|
|
set_global("$", global.make_zjquery());
|
2017-07-06 20:08:14 +02:00
|
|
|
|
2020-07-15 01:29:15 +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() {
|
2020-07-15 01:29:15 +02:00
|
|
|
$("#my-form").show();
|
2017-07-06 20:08:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Before we call show_my_form, we can assert that my-form is hidden:
|
2020-07-15 01:29:15 +02:00
|
|
|
assert(!$("#my-form").visible());
|
2017-07-06 20:08:14 +02:00
|
|
|
|
|
|
|
// Then calling show_my_form() should make it visible.
|
|
|
|
show_my_form();
|
2020-07-15 01:29:15 +02:00
|
|
|
assert($("#my-form").visible());
|
2017-07-06 20:08:14 +02:00
|
|
|
|
|
|
|
// Next, look at how several functions correctly simulate setting
|
|
|
|
// and getting for you.
|
2020-07-15 01:29:15 +02:00
|
|
|
const widget = $("#my-widget");
|
2017-07-06 20:08:14 +02:00
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
widget.attr("data-employee-id", 42);
|
|
|
|
assert.equal(widget.attr("data-employee-id"), 42);
|
|
|
|
assert.equal(widget.data("employee-id"), 42);
|
2020-01-03 17:10:59 +01:00
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
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-07-15 01:29:15 +02:00
|
|
|
widget.data("department-name", "hr");
|
|
|
|
assert.equal(widget.attr("data-department-name"), "hr");
|
|
|
|
assert.equal(widget.data("department-name"), "hr");
|
2020-05-16 08:16:35 +02:00
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
widget.html("<b>hello</b>");
|
|
|
|
assert.equal(widget.html(), "<b>hello</b>");
|
2017-07-06 20:08:14 +02:00
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
widget.prop("title", "My Widget");
|
|
|
|
assert.equal(widget.prop("title"), "My Widget");
|
2017-07-06 20:08:14 +02:00
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
widget.val("42");
|
|
|
|
assert.equal(widget.val(), "42");
|
2018-05-15 12:40:07 +02:00
|
|
|
});
|
2017-07-06 20:08:14 +02:00
|
|
|
|
2020-07-15 01:29:15 +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) {
|
2020-07-15 01:29:15 +02:00
|
|
|
$("#my-message").find(".emoji").attr("src", emoji_src);
|
2017-07-06 20:08:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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">');
|
2020-07-15 01:29:15 +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:
|
2020-07-15 01:29:15 +02:00
|
|
|
update_message_emoji("foo.png");
|
|
|
|
assert.equal(emoji.attr("src"), "foo.png");
|
2017-07-06 20:08:14 +02:00
|
|
|
|
2020-08-11 01:47:44 +02:00
|
|
|
// Sometimes you want to deliberately test paths that do not find an
|
2020-05-21 05:19:57 +02:00
|
|
|
// element. You can pass 'false' as the result for those cases.
|
2020-07-15 01:29:15 +02:00
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
const my_parents = $("#folder1,#folder4");
|
|
|
|
const elem = $("#folder555");
|
2017-07-08 14:49:09 +02:00
|
|
|
|
2020-07-15 01:29:15 +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
|
|
|
|
2020-07-15 01:29:15 +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-20 21:26:58 +02:00
|
|
|
$("#widget1").on("click", () => {
|
2017-07-06 20:08:14 +02:00
|
|
|
state.clicked = true;
|
|
|
|
});
|
|
|
|
|
2020-07-20 21:26:58 +02:00
|
|
|
$(".some-class").on("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.
|
2020-07-20 21:24:26 +02:00
|
|
|
$("#widget1").trigger("click");
|
2017-07-06 20:08:14 +02:00
|
|
|
assert.equal(state.clicked, true);
|
|
|
|
|
|
|
|
// and keydown
|
2020-07-20 21:24:26 +02:00
|
|
|
$(".some-class").trigger("keydown");
|
2017-07-06 20:08:14 +02:00
|
|
|
assert.equal(state.keydown, true);
|
2018-05-15 12:40:07 +02:00
|
|
|
});
|
2017-07-06 20:08:14 +02:00
|
|
|
|
2020-07-15 01:29:15 +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-15 01:29:15 +02:00
|
|
|
$("#my-parent").on("click", ".button-red", (e) => {
|
|
|
|
value = "red"; // just a dummy side effect
|
2018-07-18 13:39:53 +02:00
|
|
|
e.stopPropagation();
|
|
|
|
});
|
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
$("#my-parent").on("click", ".button-blue", (e) => {
|
|
|
|
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.
|
2020-07-15 01:29:15 +02: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 = {
|
2020-07-20 22:18:43 +02:00
|
|
|
stopPropagation() {},
|
2017-07-06 20:08:14 +02:00
|
|
|
};
|
|
|
|
|
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.
|
2020-07-15 01:29:15 +02:00
|
|
|
assert.equal(value, "red");
|
2018-07-18 13:39:53 +02:00
|
|
|
|
|
|
|
// Test we can have multiple click handlers in the parent.
|
2020-07-15 01:29:15 +02: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);
|
2020-07-15 01:29:15 +02:00
|
|
|
assert.equal(value, "blue");
|
2018-05-15 12:40:07 +02:00
|
|
|
});
|
2017-07-08 15:16:19 +02:00
|
|
|
|
2020-07-15 01:29:15 +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.
|
|
|
|
|
2020-07-15 01:29:15 +02: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());
|
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
obj2.addClass(".striped");
|
|
|
|
assert(obj2.hasClass(".striped"));
|
2018-05-15 12:40:07 +02:00
|
|
|
});
|
2018-04-12 14:45:06 +02:00
|
|
|
|
2020-07-15 01:29:15 +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:
|
|
|
|
|
2020-07-15 01:29:15 +02: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
|
|
|
});
|