snapshot of https://github.com/zulip/zulip.git @ 48c69911c7dd4e03b20552eb3bd77ad8151c1357 (2026-09-15)
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
This commit is contained in:
325
web/tests/components.test.cjs
Normal file
325
web/tests/components.test.cjs
Normal file
@@ -0,0 +1,325 @@
|
||||
"use strict";
|
||||
|
||||
const assert = require("node:assert/strict");
|
||||
|
||||
const {$t} = require("./lib/i18n.cjs");
|
||||
const {mock_esm, mock_jquery, zrequire} = require("./lib/namespace.cjs");
|
||||
const {run_test, noop} = require("./lib/test.cjs");
|
||||
const blueslip = require("./lib/zblueslip.cjs");
|
||||
|
||||
const ui_util = mock_esm("../src/ui_util");
|
||||
|
||||
let env;
|
||||
|
||||
function make_tab(i) {
|
||||
const $self = {};
|
||||
|
||||
assert.equal(env.tabs.length, i);
|
||||
|
||||
$self.stub = true;
|
||||
$self.class = [];
|
||||
$self.visible = true;
|
||||
|
||||
$self.addClass = (c) => {
|
||||
$self.class += " " + c;
|
||||
const tokens = $self.class.trim().split(/ +/);
|
||||
$self.class = [...new Set(tokens)].join(" ");
|
||||
};
|
||||
|
||||
$self.removeClass = (c) => {
|
||||
const tokens = $self.class.trim().split(/ +/);
|
||||
$self.class = tokens.filter((token) => token !== c).join(" ");
|
||||
};
|
||||
|
||||
$self.hasClass = (c) => {
|
||||
const tokens = $self.class.trim().split(/ +/);
|
||||
return tokens.includes(c);
|
||||
};
|
||||
|
||||
$self.css = (prop) => {
|
||||
assert.equal(prop, "display");
|
||||
if ($self.visible) {
|
||||
return "";
|
||||
}
|
||||
return "none";
|
||||
};
|
||||
|
||||
$self.attr = (name) => {
|
||||
assert.equal(name, "data-tab-id");
|
||||
return i;
|
||||
};
|
||||
|
||||
$self.text = (text) => {
|
||||
assert.equal(
|
||||
text,
|
||||
[
|
||||
"translated: Keyboard shortcuts",
|
||||
"translated: Message formatting",
|
||||
"translated: Search filters",
|
||||
][i],
|
||||
);
|
||||
};
|
||||
|
||||
$self.trigger = (type) => {
|
||||
if (type === "focus") {
|
||||
env.focused_tab = i;
|
||||
} else if (type === "click") {
|
||||
env.click_f.call(env.tabs[i]);
|
||||
}
|
||||
};
|
||||
|
||||
env.tabs.push($self);
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
const ind_tab = (function () {
|
||||
return {
|
||||
stub: true,
|
||||
|
||||
on(name, f) {
|
||||
if (name === "click") {
|
||||
env.click_f = f;
|
||||
} else if (name === "keydown") {
|
||||
env.keydown_f = f;
|
||||
}
|
||||
},
|
||||
|
||||
off(name) {
|
||||
if (name === "click") {
|
||||
env.click_f = undefined;
|
||||
}
|
||||
},
|
||||
|
||||
removeClass(c) {
|
||||
for (const $tab of env.tabs) {
|
||||
$tab.removeClass(c);
|
||||
}
|
||||
},
|
||||
|
||||
eq: (idx) => env.tabs[idx],
|
||||
};
|
||||
})();
|
||||
|
||||
function make_switcher() {
|
||||
return {
|
||||
stub: true,
|
||||
|
||||
children: [],
|
||||
|
||||
classList: new Set(),
|
||||
|
||||
append(child) {
|
||||
this.children.push(child);
|
||||
},
|
||||
|
||||
addClass(c) {
|
||||
this.classList.add(c);
|
||||
this.addedClass = c;
|
||||
},
|
||||
|
||||
find(sel) {
|
||||
switch (sel) {
|
||||
case ".ind-tab":
|
||||
return ind_tab;
|
||||
/* istanbul ignore next */
|
||||
default:
|
||||
throw new Error("unknown selector: " + sel);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
mock_jquery((sel) => {
|
||||
if (sel.stub) {
|
||||
// The component often redundantly re-wraps objects.
|
||||
return sel;
|
||||
}
|
||||
|
||||
switch (sel) {
|
||||
case "<div>":
|
||||
return {
|
||||
addClass(className) {
|
||||
if (className === "tab-switcher") {
|
||||
return env.switcher;
|
||||
}
|
||||
|
||||
assert.equal(className, "ind-tab");
|
||||
return {
|
||||
attr(attributes) {
|
||||
const tab_id = attributes["data-tab-id"];
|
||||
assert.deepEqual(
|
||||
attributes,
|
||||
[
|
||||
{
|
||||
"data-tab-key": "keyboard-shortcuts",
|
||||
"data-tab-id": 0,
|
||||
tabindex: 0,
|
||||
},
|
||||
{
|
||||
"data-tab-key": "message-formatting",
|
||||
"data-tab-id": 1,
|
||||
tabindex: 0,
|
||||
},
|
||||
{
|
||||
"data-tab-key": "search-operators",
|
||||
"data-tab-id": 2,
|
||||
tabindex: 0,
|
||||
},
|
||||
][tab_id],
|
||||
);
|
||||
return make_tab(tab_id);
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
/* istanbul ignore next */
|
||||
default:
|
||||
throw new Error("unknown selector: " + sel);
|
||||
}
|
||||
});
|
||||
|
||||
const components = zrequire("components");
|
||||
|
||||
const LEFT_KEY = {key: "ArrowLeft", preventDefault: noop, stopPropagation: noop};
|
||||
const RIGHT_KEY = {key: "ArrowRight", preventDefault: noop, stopPropagation: noop};
|
||||
const ENTER_KEY = {key: "Enter", preventDefault: noop, stopPropagation: noop};
|
||||
|
||||
run_test("basics", () => {
|
||||
env = {
|
||||
keydown_f: undefined,
|
||||
click_f: undefined,
|
||||
tabs: [],
|
||||
focused_tab: undefined,
|
||||
switcher: make_switcher(),
|
||||
};
|
||||
|
||||
let callback_args;
|
||||
let callback_value;
|
||||
|
||||
let widget = null;
|
||||
widget = components.toggle({
|
||||
selected: 0,
|
||||
values: [
|
||||
{label: $t({defaultMessage: "Keyboard shortcuts"}), key: "keyboard-shortcuts"},
|
||||
{label: $t({defaultMessage: "Message formatting"}), key: "message-formatting"},
|
||||
{label: $t({defaultMessage: "Search filters"}), key: "search-operators"},
|
||||
],
|
||||
html_class: "stream_sorter_toggle",
|
||||
callback(name, key) {
|
||||
assert.equal(callback_args, undefined);
|
||||
callback_args = [name, key];
|
||||
|
||||
// The subs code tries to get a widget value in the middle of a
|
||||
// callback, which can lead to obscure bugs.
|
||||
if (widget) {
|
||||
callback_value = widget.value();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(widget.get(), env.switcher);
|
||||
|
||||
assert.deepEqual(env.switcher.children, env.tabs);
|
||||
|
||||
assert.equal(env.switcher.addedClass, "stream_sorter_toggle");
|
||||
|
||||
assert.equal(env.focused_tab, 0);
|
||||
assert.equal(env.tabs[0].class, "first selected");
|
||||
assert.equal(env.tabs[1].class, "middle");
|
||||
assert.equal(env.tabs[2].class, "last");
|
||||
assert.deepEqual(callback_args, ["translated: Keyboard shortcuts", "keyboard-shortcuts"]);
|
||||
assert.equal(widget.value(), "translated: Keyboard shortcuts");
|
||||
assert.equal(widget.key(), "keyboard-shortcuts");
|
||||
|
||||
callback_args = undefined;
|
||||
|
||||
widget.goto("message-formatting");
|
||||
assert.equal(env.focused_tab, 1);
|
||||
assert.equal(env.tabs[0].class, "first");
|
||||
assert.equal(env.tabs[1].class, "middle selected");
|
||||
assert.equal(env.tabs[2].class, "last");
|
||||
assert.deepEqual(callback_args, ["translated: Message formatting", "message-formatting"]);
|
||||
assert.equal(widget.value(), "translated: Message formatting");
|
||||
assert.equal(widget.key(), "message-formatting");
|
||||
|
||||
// Go to same tab twice and make sure we get callback.
|
||||
callback_args = undefined;
|
||||
widget.goto("message-formatting");
|
||||
assert.deepEqual(callback_args, ["translated: Message formatting", "message-formatting"]);
|
||||
|
||||
callback_args = undefined;
|
||||
env.keydown_f.call(env.tabs[env.focused_tab], RIGHT_KEY);
|
||||
assert.equal(env.focused_tab, 2);
|
||||
assert.equal(env.tabs[0].class, "first");
|
||||
assert.equal(env.tabs[1].class, "middle");
|
||||
assert.equal(env.tabs[2].class, "last selected");
|
||||
assert.deepEqual(callback_args, ["translated: Search filters", "search-operators"]);
|
||||
assert.equal(widget.value(), "translated: Search filters");
|
||||
assert.equal(widget.value(), callback_value);
|
||||
|
||||
// try to crash the key handler
|
||||
env.keydown_f.call(env.tabs[env.focused_tab], RIGHT_KEY);
|
||||
assert.equal(widget.value(), "translated: Search filters");
|
||||
|
||||
callback_args = undefined;
|
||||
|
||||
env.keydown_f.call(env.tabs[env.focused_tab], LEFT_KEY);
|
||||
assert.equal(widget.value(), "translated: Message formatting");
|
||||
|
||||
callback_args = undefined;
|
||||
|
||||
env.keydown_f.call(env.tabs[env.focused_tab], LEFT_KEY);
|
||||
assert.equal(widget.value(), "translated: Keyboard shortcuts");
|
||||
|
||||
// try to crash the key handler
|
||||
env.keydown_f.call(env.tabs[env.focused_tab], LEFT_KEY);
|
||||
assert.equal(widget.value(), "translated: Keyboard shortcuts");
|
||||
|
||||
callback_args = undefined;
|
||||
widget.disable_tab("message-formatting");
|
||||
|
||||
env.keydown_f.call(env.tabs[env.focused_tab], RIGHT_KEY);
|
||||
assert.equal(widget.value(), "translated: Search filters");
|
||||
|
||||
callback_args = undefined;
|
||||
|
||||
env.keydown_f.call(env.tabs[env.focused_tab], LEFT_KEY);
|
||||
assert.equal(widget.value(), "translated: Keyboard shortcuts");
|
||||
|
||||
ui_util.convert_enter_to_click = () => {
|
||||
env.tabs[2].trigger("click");
|
||||
};
|
||||
callback_args = undefined;
|
||||
env.keydown_f.call(env.tabs[2], ENTER_KEY);
|
||||
assert.equal(widget.value(), "translated: Search filters");
|
||||
|
||||
widget.enable_tab("message-formatting");
|
||||
|
||||
callback_args = undefined;
|
||||
env.tabs[1].visible = false;
|
||||
env.keydown_f.call(env.tabs[env.focused_tab], LEFT_KEY);
|
||||
assert.equal(widget.value(), "translated: Keyboard shortcuts");
|
||||
|
||||
env.tabs[1].visible = true;
|
||||
|
||||
callback_args = undefined;
|
||||
|
||||
env.click_f.call(env.tabs[1]);
|
||||
assert.equal(widget.value(), "translated: Message formatting");
|
||||
|
||||
callback_args = undefined;
|
||||
widget.disable_tab("search-operators");
|
||||
assert.equal(env.tabs[2].hasClass("disabled"), true);
|
||||
assert.equal(env.tabs[2].class, "last disabled");
|
||||
|
||||
widget.goto("keyboard-shortcuts");
|
||||
assert.equal(env.focused_tab, 0);
|
||||
widget.goto("search-operators");
|
||||
assert.equal(env.focused_tab, 0);
|
||||
|
||||
blueslip.expect("warn", "Incorrect tab name given.", 3);
|
||||
widget.disable_tab("incorrect-tab");
|
||||
widget.enable_tab("incorrect-tab");
|
||||
widget.goto("incorrect-tab");
|
||||
});
|
||||
Reference in New Issue
Block a user