pill_typeahead: Separate user-only typeahead to specialized function.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg 2024-06-06 15:43:21 -07:00 committed by Tim Abbott
parent 512f4d1476
commit e6bfaed782
5 changed files with 151 additions and 13 deletions

View File

@ -56,7 +56,7 @@ function set_up_pill_typeahead({
user_group: true, user_group: true,
user: true, user: true,
}; };
pill_typeahead.set_up($pill_container.find(".input"), pill_widget, opts); pill_typeahead.set_up_combined($pill_container.find(".input"), pill_widget, opts);
} }
export function create({ export function create({

View File

@ -117,15 +117,14 @@ export function initialize_custom_user_type_fields(
const update_func = () => pill_update_handler(field, pills); const update_func = () => pill_update_handler(field, pills);
const opts = { const opts = {
update_func, update_func,
user: true,
exclude_bots: true, exclude_bots: true,
}; };
pill_typeahead.set_up($input, pills, opts); pill_typeahead.set_up_user($input, pills, opts);
pills.onPillRemove(() => { pills.onPillRemove(() => {
pill_update_handler(field, pills); pill_update_handler(field, pills);
}); });
} else { } else {
pill_typeahead.set_up($input, pills, {user: true, exclude_bots: true}); pill_typeahead.set_up_user($input, pills, {exclude_bots: true});
} }
} }
user_pills.set(field.id, pills); user_pills.set(field.id, pills);

View File

@ -12,7 +12,7 @@ import type {CombinedPillContainer} from "./typeahead_helper";
import * as user_group_pill from "./user_group_pill"; import * as user_group_pill from "./user_group_pill";
import type {UserGroupPillData} from "./user_group_pill"; import type {UserGroupPillData} from "./user_group_pill";
import * as user_pill from "./user_pill"; import * as user_pill from "./user_pill";
import type {UserPillData} from "./user_pill"; import type {UserPillData, UserPillWidget} from "./user_pill";
function person_matcher(query: string, item: UserPillData): boolean { function person_matcher(query: string, item: UserPillData): boolean {
return ( return (
@ -27,7 +27,52 @@ function group_matcher(query: string, item: UserGroupPillData): boolean {
type TypeaheadItem = UserGroupPillData | StreamPillData | UserPillData; type TypeaheadItem = UserGroupPillData | StreamPillData | UserPillData;
export function set_up( export function set_up_user(
$input: JQuery,
pills: UserPillWidget,
opts: {
exclude_bots?: boolean;
update_func?: () => void;
},
): void {
const exclude_bots = opts.exclude_bots;
const bootstrap_typeahead_input: TypeaheadInputElement = {
$element: $input,
type: "contenteditable",
};
new Typeahead(bootstrap_typeahead_input, {
items: 5,
dropup: true,
source(_query: string): UserPillData[] {
return user_pill.typeahead_source(pills, exclude_bots);
},
highlighter_html(item: UserPillData, _query: string): string {
return typeahead_helper.render_person(item);
},
matcher(item: UserPillData, query: string): boolean {
query = query.toLowerCase();
query = query.replaceAll("\u00A0", " ");
return person_matcher(query, item);
},
sorter(matches: UserPillData[], query: string): UserPillData[] {
const users = matches.filter((match) => people.is_known_user_id(match.user.user_id));
return typeahead_helper.sort_recipients({users, query}).map((item) => {
assert(item.type === "user");
return item;
});
},
updater(item: UserPillData, _query: string): undefined {
if (people.is_known_user_id(item.user.user_id)) {
user_pill.append_user(item.user, pills);
}
$input.trigger("focus");
opts.update_func?.();
},
stopAdvance: true,
});
}
export function set_up_combined(
$input: JQuery, $input: JQuery,
pills: CombinedPillContainer, pills: CombinedPillContainer,
opts: { opts: {

View File

@ -132,7 +132,7 @@ export function has_unconverted_data(pill_widget: UserPillWidget): boolean {
} }
export function typeahead_source( export function typeahead_source(
pill_widget: CombinedPillContainer, pill_widget: UserPillWidget | CombinedPillContainer,
exclude_bots?: boolean, exclude_bots?: boolean,
): UserPillData[] { ): UserPillData[] {
const users = exclude_bots ? people.get_realm_active_human_users() : people.get_realm_users(); const users = exclude_bots ? people.get_realm_active_human_users() : people.get_realm_users();
@ -148,7 +148,7 @@ export function filter_taken_users(
return items; return items;
} }
export function append_user(user: User, pills: CombinedPillContainer): void { export function append_user(user: User, pills: UserPillWidget | CombinedPillContainer): void {
if (user) { if (user) {
append_person({ append_person({
pill_widget: pills, pill_widget: pills,

View File

@ -33,8 +33,9 @@ function override_typeahead_helper(override_rewire) {
override_rewire(typeahead_helper, "sort_streams", () => { override_rewire(typeahead_helper, "sort_streams", () => {
sort_streams_called = true; sort_streams_called = true;
}); });
override_rewire(typeahead_helper, "sort_recipients", () => { override_rewire(typeahead_helper, "sort_recipients", ({users}) => {
sort_recipients_called = true; sort_recipients_called = true;
return users;
}); });
} }
@ -130,7 +131,100 @@ for (const sub of subs) {
stream_data.add_sub(sub); stream_data.add_sub(sub);
} }
run_test("set_up", ({mock_template, override, override_rewire}) => { run_test("set_up_user", ({mock_template, override, override_rewire}) => {
override_rewire(typeahead_helper, "render_person", () => $fake_rendered_person);
override_rewire(typeahead_helper, "sort_recipients", ({users}) => {
sort_recipients_called = true;
return users;
});
mock_template("input_pill.hbs", true, (data, html) => {
assert.equal(typeof data.display_value, "string");
assert.equal(typeof data.has_image, "boolean");
return html;
});
let input_pill_typeahead_called = false;
const $fake_input = $.create(".input");
$fake_input.before = noop;
const $container = $.create(".pill-container");
$container.find = () => $fake_input;
const $pill_widget = input_pill.create({
$container,
create_item_from_text: noop,
get_text_from_item: noop,
});
let update_func_called = false;
function update_func() {
update_func_called = true;
}
override(bootstrap_typeahead, "Typeahead", (input_element, config) => {
assert.equal(input_element.$element, $fake_input);
assert.equal(config.items, 5);
assert.ok(config.dropup);
assert.ok(config.stopAdvance);
assert.equal(typeof config.source, "function");
assert.equal(typeof config.highlighter_html, "function");
assert.equal(typeof config.matcher, "function");
assert.equal(typeof config.sorter, "function");
assert.equal(typeof config.updater, "function");
// test queries
const person_query = "me";
(function test_highlighter() {
assert.equal(config.highlighter_html(me_item, person_query), $fake_rendered_person);
})();
(function test_matcher() {
let result;
result = config.matcher(me_item, person_query);
assert.ok(result);
result = config.matcher(jill_item, person_query);
assert.ok(!result);
})();
(function test_sorter() {
sort_recipients_called = false;
config.sorter([me_item], person_query);
assert.ok(sort_recipients_called);
})();
(function test_source() {
let expected_result = [];
let actual_result = [];
const result = config.source(person_query);
actual_result = result.map((item) => item.user_id);
expected_result = [...expected_result, ...person_items];
expected_result = expected_result.map((item) => item.user_id);
assert.deepEqual(actual_result, expected_result);
})();
(function test_updater() {
function number_of_pills() {
const pills = $pill_widget.items();
return pills.length;
}
assert.equal(number_of_pills(), 0);
config.updater(me_item, person_query);
assert.equal(number_of_pills(), 1);
assert.ok(update_func_called);
})();
// input_pill_typeahead_called is set true if
// no exception occurs in pill_typeahead.set_up_user.
input_pill_typeahead_called = true;
});
pill_typeahead.set_up_user($fake_input, $pill_widget, {update_func});
assert.ok(input_pill_typeahead_called);
});
run_test("set_up_combined", ({mock_template, override, override_rewire}) => {
override_typeahead_helper(override_rewire); override_typeahead_helper(override_rewire);
mock_template("input_pill.hbs", true, (data, html) => { mock_template("input_pill.hbs", true, (data, html) => {
assert.equal(typeof data.display_value, "string"); assert.equal(typeof data.display_value, "string");
@ -328,12 +422,12 @@ run_test("set_up", ({mock_template, override, override_rewire}) => {
})(); })();
// input_pill_typeahead_called is set true if // input_pill_typeahead_called is set true if
// no exception occurs in pill_typeahead.set_up. // no exception occurs in pill_typeahead.set_up_combined.
input_pill_typeahead_called = true; input_pill_typeahead_called = true;
}); });
function test_pill_typeahead(opts) { function test_pill_typeahead(opts) {
pill_typeahead.set_up($fake_input, $pill_widget, opts); pill_typeahead.set_up_combined($fake_input, $pill_widget, opts);
assert.ok(input_pill_typeahead_called); assert.ok(input_pill_typeahead_called);
} }
@ -365,6 +459,6 @@ run_test("set_up", ({mock_template, override, override_rewire}) => {
opts = {}; opts = {};
input_pill_typeahead_called = false; input_pill_typeahead_called = false;
blueslip.expect("error", "Unspecified possible item types"); blueslip.expect("error", "Unspecified possible item types");
pill_typeahead.set_up($fake_input, $pill_widget, {}); pill_typeahead.set_up_combined($fake_input, $pill_widget, {});
assert.ok(!input_pill_typeahead_called); assert.ok(!input_pill_typeahead_called);
}); });