/
githubmirror
/
zulip
Обзор
Документация
Войти
/
githubmirror
/
zulip
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
web/tests/input_pill.test.cjs
740 строк
20 KB
Anders Kaseorg
web: Use named import for jQuery.
29 июл 2026, 02:35
29 июл 2026, 02:35
52a10e8
Код
Авторство
О чём код?
"use strict"; const assert = require("node:assert/strict"); const {mock_esm, set_global, zrequire} = require("./lib/namespace.cjs"); const {run_test, noop} = require("./lib/test.cjs"); const {$} = require("./lib/zjquery.cjs"); set_global("document", {}); class ClipboardEvent {} set_global("ClipboardEvent", ClipboardEvent); mock_esm("../src/ui_util", { place_caret_at_end: noop, }); set_global("getSelection", () => ({ anchorOffset: 0, })); const input_pill = zrequire("input_pill"); function pill_html(value) { const opts = { display_value: value, }; return require("../templates/input_pill.hbs")(opts); } run_test("basics", ({mock_template}) => { mock_template("input_pill.hbs", true, (data, html) => { assert.equal(data.display_value, "JavaScript"); return html; }); const $pill_input = $.create("pill_input"); const $container = $.create("container"); $container.set_find_results(".input", $pill_input); const widget = input_pill.create({ $container, create_item_from_text: noop, get_text_from_item: noop, get_display_value_from_item: (item) => item.language, }); // type for a pill can be any string but it needs to be // defined while creating any pill. const item = { language: "JavaScript", type: "language", }; let inserted_before; const expected_html = pill_html("JavaScript"); $pill_input[0].before = (element) => { inserted_before = true; assert.equal(element.innerHTML, expected_html); }; widget.appendValidatedData(item); assert.ok(!widget.is_pending()); assert.ok(inserted_before); assert.deepEqual(widget.items(), [item]); }); function set_up() { const items = { blue: { color_name: "BLUE", description: "color of the sky", type: "color", }, red: { color_name: "RED", type: "color", description: "color of stop signs", }, yellow: { color_name: "YELLOW", type: "color", description: "color of bananas", }, }; const $pill_input = $.create("pill_input"); $pill_input[0].before = noop; const create_item_from_text = (text) => items[text]; const $container = $.create("container"); $container.set_find_results(".input", $pill_input); const config = { $container, create_item_from_text, get_text_from_item: (item) => item.color_name, get_display_value_from_item: (item) => item.color_name, }; return { config, $pill_input, items, $container, }; } run_test("copy from pill", ({mock_template}) => { mock_template("input_pill.hbs", true, (data, html) => { assert.ok(["BLUE", "RED"].includes(data.display_value)); return html; }); const info = set_up(); const config = info.config; const $container = info.$container; const widget = input_pill.create(config); widget.appendValue("blue,red"); const copy_handler = $container.get_on_handler("copy", ".pill"); let copied_text; const pill_stub = $(pill_html("RED"))[0]; const originalEvent = new ClipboardEvent(); originalEvent.clipboardData = { setData(format, text) { assert.equal(format, "text/plain"); copied_text = text; }, }; const e = { originalEvent, preventDefault: noop, }; copy_handler.call(pill_stub, e); assert.equal(copied_text, "RED"); }); run_test("paste to input", ({mock_template}) => { mock_template("input_pill.hbs", true, (_data, html) => html); const info = set_up(); const config = info.config; const $container = info.$container; const items = info.items; const widget = input_pill.create(config); const paste_handler = $container.get_on_handler("paste", ".input"); const paste_text = "blue,yellow"; const originalEvent = new ClipboardEvent(); originalEvent.clipboardData = { getData(format) { assert.equal(format, "text/plain"); return paste_text; }, }; const e = { originalEvent, preventDefault: noop, }; document.execCommand = (cmd, _, text) => { assert.equal(cmd, "insertText"); $container.find(".input").text(text); }; paste_handler(e); assert.deepEqual(widget.items(), [items.blue, items.yellow]); let entered = false; widget.createPillonPaste(() => { entered = true; }); paste_handler(e); assert.ok(entered); }); run_test("arrows on pills", ({mock_template}) => { mock_template("input_pill.hbs", true, (_data, html) => html); const info = set_up(); const config = info.config; const $container = info.$container; const widget = input_pill.create(config); widget.appendValue("blue,red"); const key_handler = $container.get_on_handler("keydown", ".pill"); function test_key(c) { key_handler({ key: c, }); } const $prev_pill_stub = $.create("prev-pill-stub"); const $next_pill_stub = $.create("next-pill-stub"); const $pill_stub = $.create("pill-stub"); $pill_stub.set_prev($prev_pill_stub); $pill_stub.set_next($next_pill_stub); $container.set_find_results(".pill:focus", $pill_stub); // We use the same stub to test both arrows, since we don't // actually cause any real state changes here. We stub out // the only interaction, which is to move the focus. test_key("ArrowLeft"); assert.ok($prev_pill_stub.is(":focus")); test_key("ArrowRight"); assert.ok($next_pill_stub.is(":focus")); }); run_test("left arrow on input", ({mock_template}) => { mock_template("input_pill.hbs", true, (data, html) => { assert.equal(typeof data.display_value, "string"); return html; }); const info = set_up(); const config = info.config; const $container = info.$container; const widget = input_pill.create(config); widget.appendValue("blue,red"); const key_handler = $container.get_on_handler("keydown", ".input"); const $pill_stub = $.create("pill-stub"); $container.set_find_results(".pill", $pill_stub); key_handler({ key: "ArrowLeft", }); assert.ok($pill_stub.is(":focus")); }); run_test("comma", ({mock_template}) => { mock_template("input_pill.hbs", true, (data, html) => { assert.equal(typeof data.display_value, "string"); return html; }); const info = set_up(); const config = info.config; const items = info.items; const $pill_input = info.$pill_input; const $container = info.$container; const widget = input_pill.create(config); widget.appendValue("blue,red"); assert.deepEqual(widget.items(), [items.blue, items.red]); const key_handler = $container.get_on_handler("keydown", ".input"); $pill_input.text(" yel"); key_handler({ key: ",", preventDefault: noop, }); assert.deepEqual(widget.items(), [items.blue, items.red]); $pill_input.text(" yellow"); key_handler({ key: ",", preventDefault: noop, }); assert.deepEqual(widget.items(), [items.blue, items.red, items.yellow]); }); run_test("Enter key with text", ({mock_template}) => { mock_template("input_pill.hbs", true, (data, html) => { assert.equal(typeof data.display_value, "string"); return html; }); const info = set_up(); const config = info.config; const items = info.items; const $container = info.$container; const widget = input_pill.create(config); widget.appendValue("blue,red"); assert.deepEqual(widget.items(), [items.blue, items.red]); const key_handler = $container.get_on_handler("keydown", ".input"); key_handler.call( { textContent: " yellow ", }, { key: "Enter", preventDefault: noop, stopPropagation: noop, }, ); assert.deepEqual(widget.items(), [items.blue, items.red, items.yellow]); }); run_test("insert_remove", ({mock_template}) => { mock_template("input_pill.hbs", true, (data, html) => { assert.equal(typeof data.display_value, "string"); assert.ok(html.startsWith, "<div class='pill'"); return html; }); const info = set_up(); const config = info.config; const $pill_input = info.$pill_input; const items = info.items; const $container = info.$container; const inserted_html = []; $pill_input[0].before = (element) => { inserted_html.push(element.innerHTML); }; const widget = input_pill.create(config); let created; let removed; widget.onPillCreate(() => { created = true; }); widget.onPillRemove(() => { removed = true; }); widget.appendValue("blue,chartreuse,red,yellow,mauve"); assert.ok(created); assert.ok(!removed); assert.deepEqual(inserted_html, [pill_html("BLUE"), pill_html("RED"), pill_html("YELLOW")]); assert.deepEqual(widget.items(), [items.blue, items.red, items.yellow]); assert.equal($pill_input.text(), "chartreuse, mauve"); assert.equal(widget.is_pending(), true); widget.clear_text(); assert.equal($pill_input.text(), ""); assert.equal(widget.is_pending(), false); let color_removed; function set_colored_removed_func(color) { return () => { color_removed = color; }; } let color_focused; function handle_event(color) { return () => { color_focused = color; }; } const pills = widget._get_pills_for_testing(); for (const pill of pills) { pill.$element[0].remove = set_colored_removed_func(pill.item.color_name); pill.$element.on("focus", handle_event(pill.item.color_name)); } let key_handler = $container.get_on_handler("keydown", ".input"); key_handler.call( { textContent: "", }, { key: "Backspace", preventDefault: noop, }, ); // The first backspace focuses the pill, the second removes it. assert.ok(!removed); assert.equal(color_focused, "YELLOW"); const yellow_pill = pills.find((pill) => pill.item.color_name === "YELLOW"); $container.set_find_results(".pill:focus", yellow_pill.$element); const $prev_pill_stub = $("<prev-stub>"); yellow_pill.$element.set_prev($prev_pill_stub); yellow_pill.$element.set_next($("<next-stub>")); key_handler = $container.get_on_handler("keydown", ".pill"); key_handler.call( {}, { key: "Backspace", preventDefault: noop, }, ); assert.ok(removed); assert.equal(color_removed, "YELLOW"); assert.ok($prev_pill_stub.is(":focus")); color_removed = undefined; $prev_pill_stub.trigger("blur"); assert.deepEqual(widget.items(), [items.blue, items.red]); const $focus_pill_stub = $(pill_html("RED")); $focus_pill_stub.set_prev($prev_pill_stub); $focus_pill_stub.set_next($("<next-stub>")); $container.set_find_results(".pill:focus", $focus_pill_stub); const red_pill = pills.find((pill) => pill.item.color_name === "RED"); // Disabled pill should not be removed. red_pill.disabled = true; key_handler = $container.get_on_handler("keydown", ".pill"); key_handler({ key: "Backspace", preventDefault: noop, }); assert.equal(color_removed, undefined); assert.ok($prev_pill_stub.is(":focus")); // We should be able to remove the pill after marking it as not // disabled. red_pill.disabled = false; $prev_pill_stub.trigger("blur"); assert.deepEqual(widget.items(), [items.blue, items.red]); key_handler({ key: "Backspace", preventDefault: noop, }); assert.equal(color_removed, "RED"); assert.ok($prev_pill_stub.is(":focus")); }); run_test("exit button on pill", ({mock_template}) => { mock_template("input_pill.hbs", true, (data, html) => { assert.equal(typeof data.display_value, "string"); assert.ok(html.startsWith, "<div class='pill'"); return html; }); const info = set_up(); const config = info.config; const items = info.items; const $container = info.$container; const widget = input_pill.create(config); widget.appendValue("blue,red"); const pills = widget._get_pills_for_testing(); for (const pill of pills) { pill.$element[0].remove = noop; } const $curr_pill_stub = { [0]: $(pill_html("BLUE"))[0], length: 1, }; const exit_button_stub = { to_$: () => ({ closest(sel) { assert.equal(sel, ".pill"); return $curr_pill_stub; }, }), }; const e = { stopPropagation: noop, }; const exit_click_handler = $container.get_on_handler("click", ".exit"); exit_click_handler.call(exit_button_stub, e); assert.deepEqual(widget.items(), [items.red]); }); run_test("misc things", () => { const info = set_up(); const $container = info.$container; const $pill_input = info.$pill_input; input_pill.create(info.config); // animation const animation_end_handler = $container.get_on_handler("animationend", ".input"); let shake_class_removed = false; const input_stub = { to_$: () => ({ removeClass(cls) { assert.equal(cls, "shake"); shake_class_removed = true; }, }), }; animation_end_handler.call(input_stub); assert.ok(shake_class_removed); // click on container const container_click_handler = $container.get_on_handler("click"); const $stub = $.create("the-pill-container"); $stub.set_find_results(".input", $pill_input); $stub.set_matches(".pill-container", true); const this_ = { to_$: () => $stub, }; container_click_handler.call(this_, {target: this_}); }); run_test("appendValue/clear", ({mock_template}) => { mock_template("input_pill.hbs", true, (data, html) => { assert.equal(typeof data.display_value, "string"); assert.ok(html.startsWith, "<div class='pill'"); return html; }); const $pill_input = $.create("pill_input"); const $container = $.create("container"); $container.set_find_results(".input", $pill_input); const config = { $container, create_item_from_text: (s) => ({type: "color", color_name: s}), get_text_from_item: /* istanbul ignore next */ (s) => s.color_name, get_display_value_from_item: (s) => s.color_name, }; $pill_input[0].before = noop; const widget = input_pill.create(config); // First test some early-exit code. widget.appendValue(""); assert.deepEqual(widget._get_pills_for_testing(), []); // Now set up real data. widget.appendValue("red,yellow,blue"); const pills = widget._get_pills_for_testing(); const removed_colors = []; for (const pill of pills) { pill.$element[0].remove = () => { removed_colors.push(pill.item.color_name); }; } widget.clear(); // Note that we remove colors in the reverse order that we inserted. assert.deepEqual(removed_colors, ["blue", "yellow", "red"]); assert.equal($pill_input[0].textContent, ""); }); run_test("getCurrentText", ({mock_template}) => { mock_template("input_pill.hbs", true, (data, html) => { assert.equal(typeof data.display_value, "string"); return html; }); const info = set_up(); const config = info.config; const items = info.items; const $pill_input = info.$pill_input; const $container = info.$container; const widget = input_pill.create(config); widget.appendValue("blue,red"); assert.deepEqual(widget.items(), [items.blue, items.red]); $pill_input.text("yellow"); assert.equal(widget.getCurrentText(), "yellow"); const key_handler = $container.get_on_handler("keydown", ".input"); key_handler({ key: " ", preventDefault: noop, }); key_handler({ key: ",", preventDefault: noop, }); assert.deepEqual(widget.items(), [items.blue, items.red, items.yellow]); }); run_test("onTextInputHook", () => { const info = set_up(); const config = info.config; const widget = input_pill.create(config); const $container = info.$container; const $pill_input = info.$pill_input; let hookCalled = false; let currentText = "re"; const onTextInputHook = () => { hookCalled = true; // Test that the hook always gets the correct updated text. assert.equal(widget.getCurrentText(), currentText); }; widget.onTextInputHook(onTextInputHook); const input_handler = $container.get_on_handler("input", ".input"); $pill_input.text(currentText); input_handler(); currentText += "d"; $pill_input.text(currentText); input_handler(); assert.ok(hookCalled); }); run_test("getPillByPredicate", ({mock_template}) => { mock_template("input_pill.hbs", true, (data, html) => { assert.equal(typeof data.display_value, "string"); return html; }); const info = set_up(); const widget = input_pill.create(info.config); widget.appendValue("blue,red,yellow"); // Test finding pill by color name const found_blue_pill = widget.getPillByPredicate((item) => item.color_name === "BLUE"); assert.ok(found_blue_pill); assert.equal(found_blue_pill.item.color_name, "BLUE"); assert.equal(found_blue_pill.item.description, "color of the sky"); const found_red_pill = widget.getPillByPredicate((item) => item.color_name === "RED"); assert.ok(found_red_pill); assert.equal(found_red_pill.item.color_name, "RED"); assert.equal(found_red_pill.item.description, "color of stop signs"); // Test with non-existent item returns undefined const nonexistent_pill = widget.getPillByPredicate((item) => item.color_name === "GREEN"); assert.equal(nonexistent_pill, undefined); // Test finding pill by different property (description) const found_by_description = widget.getPillByPredicate( (item) => item.description === "color of the sky", ); assert.ok(found_by_description); assert.equal(found_by_description.item.color_name, "BLUE"); }); run_test("updatePill", ({mock_template}) => { mock_template("input_pill.hbs", true, (data, html) => { assert.equal(typeof data.display_value, "string"); return html; }); const info = set_up(); const widget = input_pill.create(info.config); widget.appendValue("blue,red"); const pills = widget._get_pills_for_testing(); assert.equal(pills.length, 2); const blue_pill = pills[0]; assert.equal(blue_pill.item.color_name, "BLUE"); assert.equal(blue_pill.item.description, "color of the sky"); // Update the blue pill with new data const updated_blue_data = { color_name: "DARK BLUE", description: "color of the deep ocean", type: "color", }; let element_replaced = false; blue_pill.$element[0].replaceWith = (new_element) => { element_replaced = true; assert.equal(new_element.innerHTML, pill_html("DARK BLUE")); }; widget.updatePill(blue_pill.$element[0], updated_blue_data); // Verify the pill element was replaced with new HTML assert.ok(element_replaced); // Verify that pill.$element was updated by updatePill assert.equal(blue_pill.$element.html(), pill_html("DARK BLUE")); // Verify the pill's internal data was updated assert.equal(blue_pill.item.color_name, "DARK BLUE"); assert.equal(blue_pill.item.description, "color of the deep ocean"); // Verify other pill is unchanged const red_pill = pills[1]; assert.equal(red_pill.item.color_name, "RED"); assert.equal(red_pill.item.description, "color of stop signs"); // Test updating non-existent pill element - should be a no-op const fake_element = {}; widget.updatePill(fake_element, updated_blue_data); // Blue pill data should remain unchanged assert.equal(blue_pill.item.color_name, "DARK BLUE"); assert.equal(blue_pill.item.description, "color of the deep ocean"); });