mirror of
				https://github.com/zulip/zulip.git
				synced 2025-10-30 19:43:47 +00:00 
			
		
		
		
	settings: Remove "User groups" panel from settings overlay.
The "User groups" panel is now removed from settings overlay and we instead use new "#groups" UI. This commit also makes some changes to tests to ensure coverage for pill_typeahead.js which was previously done by settings_user_group_legacy.test.js. We have still not got complete coverage on user_pill.ts as we have removed settings_user_group_legacy.test.js, but we just add the file to EXEMPT_FILS list for now and will handle it in future. Fixes #28012.
This commit is contained in:
		| @@ -215,7 +215,6 @@ EXEMPT_FILES = make_set( | ||||
|         "web/src/settings_streams.js", | ||||
|         "web/src/settings_toggle.js", | ||||
|         "web/src/settings_ui.ts", | ||||
|         "web/src/settings_user_groups_legacy.js", | ||||
|         "web/src/settings_user_topics.js", | ||||
|         "web/src/settings_users.js", | ||||
|         "web/src/setup.ts", | ||||
| @@ -269,6 +268,7 @@ EXEMPT_FILES = make_set( | ||||
|         "web/src/user_group_popover.js", | ||||
|         "web/src/user_group_ui_updates.js", | ||||
|         "web/src/user_groups.ts", | ||||
|         "web/src/user_pill.ts", | ||||
|         "web/src/user_profile.js", | ||||
|         "web/src/user_settings.ts", | ||||
|         "web/src/user_sort.ts", | ||||
|   | ||||
| @@ -77,7 +77,6 @@ function insert_tip_box() { | ||||
|     $(".organization-box") | ||||
|         .find(".settings-section") | ||||
|         .not("#emoji-settings") | ||||
|         .not("#user-groups-admin") | ||||
|         .not("#organization-auth-settings") | ||||
|         .not("#admin-bot-list") | ||||
|         .not("#admin-invites-list") | ||||
| @@ -116,7 +115,6 @@ export function build_page() { | ||||
|         realm_inline_url_embed_preview: page_params.realm_inline_url_embed_preview, | ||||
|         server_inline_url_embed_preview: page_params.server_inline_url_embed_preview, | ||||
|         realm_authentication_methods: page_params.realm_authentication_methods, | ||||
|         realm_user_group_edit_policy: page_params.realm_user_group_edit_policy, | ||||
|         realm_name_changes_disabled: page_params.realm_name_changes_disabled, | ||||
|         realm_email_changes_disabled: page_params.realm_email_changes_disabled, | ||||
|         realm_avatar_changes_disabled: page_params.realm_avatar_changes_disabled, | ||||
| @@ -174,7 +172,6 @@ export function build_page() { | ||||
|         can_create_multiuse_invite: settings_data.user_can_create_multiuse_invite(), | ||||
|         can_invite_users_by_email: settings_data.user_can_invite_users_by_email(), | ||||
|         realm_invite_required: page_params.realm_invite_required, | ||||
|         can_edit_user_groups: settings_data.user_can_edit_user_groups(), | ||||
|         policy_values: settings_config.common_policy_values, | ||||
|         realm_delete_own_message_policy: page_params.realm_delete_own_message_policy, | ||||
|         DELETE_OWN_MESSAGE_POLICY_ADMINS_ONLY: | ||||
|   | ||||
| @@ -62,7 +62,6 @@ import * as settings_profile_fields from "./settings_profile_fields"; | ||||
| import * as settings_realm_domains from "./settings_realm_domains"; | ||||
| import * as settings_realm_user_settings_defaults from "./settings_realm_user_settings_defaults"; | ||||
| import * as settings_streams from "./settings_streams"; | ||||
| import * as settings_user_groups_legacy from "./settings_user_groups_legacy"; | ||||
| import * as settings_users from "./settings_users"; | ||||
| import * as sidebar_ui from "./sidebar_ui"; | ||||
| import * as starred_messages from "./starred_messages"; | ||||
| @@ -882,7 +881,6 @@ export function dispatch_normal_event(event) { | ||||
|                     blueslip.error("Unexpected event type user_group/" + event.op); | ||||
|                     break; | ||||
|             } | ||||
|             settings_user_groups_legacy.reload(); | ||||
|             break; | ||||
|  | ||||
|         case "user_status": | ||||
|   | ||||
| @@ -15,7 +15,6 @@ import * as settings_playgrounds from "./settings_playgrounds"; | ||||
| import * as settings_profile_fields from "./settings_profile_fields"; | ||||
| import * as settings_realm_user_settings_defaults from "./settings_realm_user_settings_defaults"; | ||||
| import * as settings_streams from "./settings_streams"; | ||||
| import * as settings_user_groups_legacy from "./settings_user_groups_legacy"; | ||||
| import * as settings_user_topics from "./settings_user_topics"; | ||||
| import * as settings_users from "./settings_users"; | ||||
|  | ||||
| @@ -72,7 +71,6 @@ export function initialize() { | ||||
|     load_func_dict.set("linkifier-settings", settings_linkifiers.set_up); | ||||
|     load_func_dict.set("playground-settings", settings_playgrounds.set_up); | ||||
|     load_func_dict.set("invites-list-admin", settings_invites.set_up); | ||||
|     load_func_dict.set("user-groups-admin", settings_user_groups_legacy.set_up); | ||||
|     load_func_dict.set("profile-field-settings", settings_profile_fields.set_up); | ||||
|     load_func_dict.set("data-exports-admin", settings_exports.set_up); | ||||
|     load_func_dict.set( | ||||
| @@ -112,7 +110,6 @@ export function reset_sections() { | ||||
|     settings_org.reset(); | ||||
|     settings_profile_fields.reset(); | ||||
|     settings_streams.reset(); | ||||
|     settings_user_groups_legacy.reset(); | ||||
|     settings_user_topics.reset(); | ||||
|     settings_muted_users.reset(); | ||||
|     alert_words_ui.reset(); | ||||
|   | ||||
| @@ -1,407 +0,0 @@ | ||||
| import $ from "jquery"; | ||||
| import _ from "lodash"; | ||||
|  | ||||
| import render_confirm_delete_user from "../templates/confirm_dialog/confirm_delete_user.hbs"; | ||||
| import render_add_user_group_modal from "../templates/settings/add_user_group_modal.hbs"; | ||||
| import render_admin_user_group_list from "../templates/settings/admin_user_group_list.hbs"; | ||||
|  | ||||
| import * as channel from "./channel"; | ||||
| import * as confirm_dialog from "./confirm_dialog"; | ||||
| import * as dialog_widget from "./dialog_widget"; | ||||
| import {$t, $t_html} from "./i18n"; | ||||
| import * as keydown_util from "./keydown_util"; | ||||
| import {page_params} from "./page_params"; | ||||
| import * as people from "./people"; | ||||
| import * as pill_typeahead from "./pill_typeahead"; | ||||
| import * as settings_data from "./settings_data"; | ||||
| import * as ui_report from "./ui_report"; | ||||
| import * as user_groups from "./user_groups"; | ||||
| import * as user_pill from "./user_pill"; | ||||
|  | ||||
| const meta = { | ||||
|     loaded: false, | ||||
| }; | ||||
|  | ||||
| export function reset() { | ||||
|     meta.loaded = false; | ||||
| } | ||||
|  | ||||
| export function reload() { | ||||
|     if (!meta.loaded) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const $user_groups_section = $("#user-groups").expectOne(); | ||||
|     $user_groups_section.empty(); | ||||
|     populate_user_groups(); | ||||
| } | ||||
|  | ||||
| export function can_edit(group_id) { | ||||
|     if (!settings_data.user_can_edit_user_groups()) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     // Admins and moderators are allowed to edit user groups even if they | ||||
|     // are not a member of that user group. Members can edit user groups | ||||
|     // only if they belong to that group. | ||||
|     if (page_params.is_admin || page_params.is_moderator) { | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     return user_groups.is_direct_member_of(people.my_current_user_id(), group_id); | ||||
| } | ||||
|  | ||||
| export function populate_user_groups() { | ||||
|     const $user_groups_section = $("#user-groups").expectOne(); | ||||
|     const user_groups_array = user_groups.get_realm_user_groups(); | ||||
|  | ||||
|     for (const data of user_groups_array) { | ||||
|         $user_groups_section.append( | ||||
|             render_admin_user_group_list({ | ||||
|                 user_group: { | ||||
|                     name: data.name, | ||||
|                     id: data.id, | ||||
|                     description: data.description, | ||||
|                 }, | ||||
|             }), | ||||
|         ); | ||||
|  | ||||
|         const pill_config = { | ||||
|             show_user_status_emoji: false, | ||||
|         }; | ||||
|  | ||||
|         const $pill_container = $(`.pill-container[data-group-pills="${CSS.escape(data.id)}"]`); | ||||
|         const pills = user_pill.create_pills($pill_container, pill_config); | ||||
|  | ||||
|         function get_pill_user_ids() { | ||||
|             return user_pill.get_user_ids(pills); | ||||
|         } | ||||
|  | ||||
|         const $userg = $(`div.user-group[id="${CSS.escape(data.id)}"]`); | ||||
|         for (const user_id of data.members) { | ||||
|             const user = people.get_by_user_id(user_id); | ||||
|             user_pill.append_user(user, pills); | ||||
|         } | ||||
|  | ||||
|         function update_membership(group_id) { | ||||
|             if (can_edit(group_id)) { | ||||
|                 return; | ||||
|             } | ||||
|             $userg.find(".name").attr("contenteditable", "false"); | ||||
|             $userg.find(".description").attr("contenteditable", "false"); | ||||
|             $userg.addClass("ntm"); | ||||
|             $pill_container.find(".input").attr("contenteditable", "false"); | ||||
|             $pill_container.find(".input").css("display", "none"); | ||||
|             $pill_container.addClass("not-editable"); | ||||
|             $pill_container.off("keydown", ".pill"); | ||||
|             $pill_container.off("keydown", ".input"); | ||||
|             $pill_container.off("click"); | ||||
|             $pill_container.on("click", (e) => { | ||||
|                 e.stopPropagation(); | ||||
|             }); | ||||
|             $pill_container.find(".pill").on("mouseenter", () => { | ||||
|                 $pill_container.find(".pill").find(".exit").css("opacity", "0.5"); | ||||
|             }); | ||||
|         } | ||||
|         update_membership(data.id); | ||||
|  | ||||
|         function is_user_group_changed() { | ||||
|             const draft_group = get_pill_user_ids(); | ||||
|             const group_data = user_groups.get_user_group_from_id(data.id); | ||||
|             const original_group = [...group_data.members]; | ||||
|             const same_groups = _.isEqual(_.sortBy(draft_group), _.sortBy(original_group)); | ||||
|             const description = $(`#user-groups #${CSS.escape(data.id)} .description`) | ||||
|                 .text() | ||||
|                 .trim(); | ||||
|             const name = $(`#user-groups #${CSS.escape(data.id)} .name`) | ||||
|                 .text() | ||||
|                 .trim(); | ||||
|             const $user_group_status = $(`#user-groups #${CSS.escape(data.id)} .user-group-status`); | ||||
|  | ||||
|             if ($user_group_status.is(":visible")) { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             if ( | ||||
|                 group_data.description === description && | ||||
|                 group_data.name === name && | ||||
|                 (!draft_group.length || same_groups) | ||||
|             ) { | ||||
|                 return false; | ||||
|             } | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         function update_cancel_button() { | ||||
|             if (!can_edit(data.id)) { | ||||
|                 return; | ||||
|             } | ||||
|             const $cancel_button = $( | ||||
|                 `#user-groups #${CSS.escape(data.id)} .save-status.btn-danger`, | ||||
|             ); | ||||
|             const $saved_button = $(`#user-groups #${CSS.escape(data.id)} .save-status.sea-green`); | ||||
|             const $save_instructions = $(`#user-groups #${CSS.escape(data.id)} .save-instructions`); | ||||
|  | ||||
|             if (is_user_group_changed() && !$cancel_button.is(":visible")) { | ||||
|                 $saved_button.fadeOut(0); | ||||
|                 $cancel_button.css({display: "inline-block", opacity: "0"}).fadeTo(400, 1); | ||||
|                 $save_instructions.css({display: "block", opacity: "0"}).fadeTo(400, 1); | ||||
|             } else if (!is_user_group_changed() && $cancel_button.is(":visible")) { | ||||
|                 $cancel_button.fadeOut(); | ||||
|                 $save_instructions.fadeOut(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         function show_saved_button() { | ||||
|             const $cancel_button = $( | ||||
|                 `#user-groups #${CSS.escape(data.id)} .save-status.btn-danger`, | ||||
|             ); | ||||
|             const $saved_button = $(`#user-groups #${CSS.escape(data.id)} .save-status.sea-green`); | ||||
|             const $save_instructions = $(`#user-groups #${CSS.escape(data.id)} .save-instructions`); | ||||
|             if (!$saved_button.is(":visible")) { | ||||
|                 $cancel_button.fadeOut(0); | ||||
|                 $save_instructions.fadeOut(0); | ||||
|                 $saved_button | ||||
|                     .css({display: "inline-block", opacity: "0"}) | ||||
|                     .fadeTo(400, 1) | ||||
|                     .delay(2000) | ||||
|                     .fadeTo(400, 0); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         function save_members() { | ||||
|             const draft_group = get_pill_user_ids(); | ||||
|             const group_data = user_groups.get_user_group_from_id(data.id); | ||||
|             const original_group = [...group_data.members]; | ||||
|             const same_groups = _.isEqual(_.sortBy(draft_group), _.sortBy(original_group)); | ||||
|             if (!draft_group.length || same_groups) { | ||||
|                 return; | ||||
|             } | ||||
|             const added = _.difference(draft_group, original_group); | ||||
|             const removed = _.difference(original_group, draft_group); | ||||
|             channel.post({ | ||||
|                 url: "/json/user_groups/" + data.id + "/members", | ||||
|                 data: { | ||||
|                     add: JSON.stringify(added), | ||||
|                     delete: JSON.stringify(removed), | ||||
|                 }, | ||||
|                 success() { | ||||
|                     setTimeout(show_saved_button, 200); | ||||
|                 }, | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         function save_name_desc() { | ||||
|             const $user_group_status = $(`#user-groups #${CSS.escape(data.id)} .user-group-status`); | ||||
|             const group_data = user_groups.get_user_group_from_id(data.id); | ||||
|             const description = $(`#user-groups #${CSS.escape(data.id)} .description`) | ||||
|                 .text() | ||||
|                 .trim(); | ||||
|             const name = $(`#user-groups #${CSS.escape(data.id)} .name`) | ||||
|                 .text() | ||||
|                 .trim(); | ||||
|  | ||||
|             if (group_data.description === description && group_data.name === name) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             channel.patch({ | ||||
|                 url: "/json/user_groups/" + data.id, | ||||
|                 data: { | ||||
|                     name, | ||||
|                     description, | ||||
|                 }, | ||||
|                 success() { | ||||
|                     $user_group_status.hide(); | ||||
|                     setTimeout(show_saved_button, 200); | ||||
|                 }, | ||||
|                 error(xhr) { | ||||
|                     ui_report.error($t_html({defaultMessage: "Failed"}), xhr, $user_group_status); | ||||
|                     update_cancel_button(); | ||||
|                     $(`#user-groups #${CSS.escape(data.id)} .name`).text(group_data.name); | ||||
|                     $(`#user-groups #${CSS.escape(data.id)} .description`).text( | ||||
|                         group_data.description, | ||||
|                     ); | ||||
|                 }, | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         function do_not_blur(except_class, event) { | ||||
|             // Event generated from or inside the typeahead. | ||||
|             if ($(event.relatedTarget).closest(".typeahead").length) { | ||||
|                 return true; | ||||
|             } | ||||
|  | ||||
|             if ($(event.relatedTarget).closest(`#user-groups #${CSS.escape(data.id)}`).length) { | ||||
|                 return [".pill-container", ".name", ".description", ".input", ".delete"].some( | ||||
|                     (class_name) => | ||||
|                         class_name !== except_class && | ||||
|                         $(event.relatedTarget).closest(class_name).length, | ||||
|                 ); | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         function auto_save(class_name, event) { | ||||
|             if (!can_edit(data.id)) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             if (do_not_blur(class_name, event)) { | ||||
|                 return; | ||||
|             } | ||||
|             if ( | ||||
|                 $(event.relatedTarget).closest(`#user-groups #${CSS.escape(data.id)}`) && | ||||
|                 $(event.relatedTarget).closest(".save-status.btn-danger").length | ||||
|             ) { | ||||
|                 reload(); | ||||
|                 return; | ||||
|             } | ||||
|             save_name_desc(); | ||||
|             save_members(); | ||||
|         } | ||||
|  | ||||
|         $(`#user-groups #${CSS.escape(data.id)}`).on("blur", ".input", (event) => { | ||||
|             auto_save(".input", event); | ||||
|         }); | ||||
|  | ||||
|         $(`#user-groups #${CSS.escape(data.id)}`).on("blur", ".name", (event) => { | ||||
|             auto_save(".name", event); | ||||
|         }); | ||||
|         $(`#user-groups #${CSS.escape(data.id)}`).on("input", ".name", () => { | ||||
|             update_cancel_button(); | ||||
|         }); | ||||
|  | ||||
|         $(`#user-groups #${CSS.escape(data.id)}`).on("blur", ".description", (event) => { | ||||
|             auto_save(".description", event); | ||||
|         }); | ||||
|         $(`#user-groups #${CSS.escape(data.id)}`).on("input", ".description", () => { | ||||
|             update_cancel_button(); | ||||
|         }); | ||||
|  | ||||
|         const $input = $pill_container.children(".input"); | ||||
|         if (can_edit(data.id)) { | ||||
|             const opts = {update_func: update_cancel_button, user: true}; | ||||
|             pill_typeahead.set_up($input, pills, opts); | ||||
|         } | ||||
|  | ||||
|         if (can_edit(data.id)) { | ||||
|             pills.onPillRemove(() => { | ||||
|                 // onPillRemove is fired before the pill is removed from | ||||
|                 // the DOM. | ||||
|                 update_cancel_button(); | ||||
|                 setTimeout(() => { | ||||
|                     $input.trigger("focus"); | ||||
|                 }, 100); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| export function add_user_group() { | ||||
|     const $user_group_status = $("#dialog_error"); | ||||
|  | ||||
|     const group = { | ||||
|         members: JSON.stringify([people.my_current_user_id()]), | ||||
|     }; | ||||
|  | ||||
|     for (const obj of $("#add-user-group-form").serializeArray()) { | ||||
|         if (obj.value.trim() === "") { | ||||
|             continue; | ||||
|         } | ||||
|         group[obj.name] = obj.value; | ||||
|     } | ||||
|  | ||||
|     channel.post({ | ||||
|         url: "/json/user_groups/create", | ||||
|         data: group, | ||||
|         success() { | ||||
|             $user_group_status.hide(); | ||||
|             ui_report.success($t_html({defaultMessage: "User group added!"}), $user_group_status); | ||||
|             dialog_widget.close(); | ||||
|         }, | ||||
|         error(xhr) { | ||||
|             $user_group_status.hide(); | ||||
|             ui_report.error($t_html({defaultMessage: "Failed"}), xhr, $user_group_status); | ||||
|         }, | ||||
|     }); | ||||
| } | ||||
|  | ||||
| function show_add_user_group_modal() { | ||||
|     const html_body = render_add_user_group_modal(); | ||||
|  | ||||
|     function add_user_group_post_render() { | ||||
|         const $add_user_group_input_element = $("#user_group_name"); | ||||
|         const $add_user_group_submit_button = $("#add-user-group-modal .dialog_submit_button"); | ||||
|         $add_user_group_submit_button.prop("disabled", true); | ||||
|  | ||||
|         $add_user_group_input_element.on("input", () => { | ||||
|             $add_user_group_submit_button.prop( | ||||
|                 "disabled", | ||||
|                 $add_user_group_input_element.val() === "", | ||||
|             ); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     dialog_widget.launch({ | ||||
|         html_heading: $t_html({defaultMessage: "Add new user group"}), | ||||
|         html_body, | ||||
|         html_submit_button: $t_html({defaultMessage: "Save"}), | ||||
|         help_link: "/help/user-groups", | ||||
|         form_id: "add-user-group-form", | ||||
|         id: "add-user-group-modal", | ||||
|         on_click: add_user_group, | ||||
|         on_shown: () => $("#user_group_name").trigger("focus"), | ||||
|         post_render: add_user_group_post_render, | ||||
|     }); | ||||
| } | ||||
|  | ||||
| export function set_up() { | ||||
|     meta.loaded = true; | ||||
|     populate_user_groups(); | ||||
|  | ||||
|     $("#show-add-user-group-modal").on("click", (e) => { | ||||
|         e.preventDefault(); | ||||
|         e.stopPropagation(); | ||||
|         show_add_user_group_modal(); | ||||
|     }); | ||||
|  | ||||
|     $("#user-groups").on("click", ".delete", function () { | ||||
|         const group_id = Number.parseInt($(this).parents(".user-group").attr("id"), 10); | ||||
|         if (!can_edit(group_id)) { | ||||
|             return; | ||||
|         } | ||||
|         const user_group = user_groups.get_user_group_from_id(group_id); | ||||
|         const $btn = $(this); | ||||
|  | ||||
|         function delete_user_group() { | ||||
|             channel.del({ | ||||
|                 url: "/json/user_groups/" + group_id, | ||||
|                 data: { | ||||
|                     id: group_id, | ||||
|                 }, | ||||
|                 error() { | ||||
|                     $btn.text($t({defaultMessage: "Failed!"})); | ||||
|                 }, | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         const html_body = render_confirm_delete_user({ | ||||
|             group_name: user_group.name, | ||||
|         }); | ||||
|  | ||||
|         const user_group_name = user_group.name; | ||||
|  | ||||
|         confirm_dialog.launch({ | ||||
|             html_heading: $t_html({defaultMessage: "Delete {user_group_name}?"}, {user_group_name}), | ||||
|             html_body, | ||||
|             on_click: delete_user_group, | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     $("#user-groups").on("keypress", ".user-group h4 > span", (e) => { | ||||
|         if (keydown_util.is_enter_event(e)) { | ||||
|             e.preventDefault(); | ||||
|         } | ||||
|     }); | ||||
| } | ||||
| @@ -651,7 +651,6 @@ | ||||
|     .clear_search_button:focus, | ||||
|     .clear_search_button:active, | ||||
|     .clear_search_button:disabled:hover, | ||||
|     #user-groups .save-instructions, | ||||
|     .close { | ||||
|         color: hsl(236deg 33% 80%); | ||||
|     } | ||||
|   | ||||
| @@ -687,19 +687,6 @@ input[type="checkbox"] { | ||||
|     text-align: right; | ||||
| } | ||||
|  | ||||
| #show-add-user-group-modal { | ||||
|     margin-bottom: 10px; | ||||
| } | ||||
|  | ||||
| #add-user-group-form { | ||||
|     margin: 0; | ||||
|  | ||||
|     /* This 14px is the border and padding of the input element */ | ||||
|     #user_group_description { | ||||
|         width: calc(100% - 14px); | ||||
|     } | ||||
| } | ||||
|  | ||||
| .add-new-export-box { | ||||
|     margin: 10px 0; | ||||
| } | ||||
| @@ -1220,102 +1207,6 @@ $option_title_width: 180px; | ||||
|     margin-right: 5px; | ||||
| } | ||||
|  | ||||
| #user-groups { | ||||
|     .user-group { | ||||
|         margin-bottom: 20px; | ||||
|         padding: 10px; | ||||
|         border-radius: 5px; | ||||
|  | ||||
|         & h4 { | ||||
|             font-weight: normal; | ||||
|             margin: 0; | ||||
|             display: flex; | ||||
|             align-items: center; | ||||
|             justify-content: left; | ||||
|         } | ||||
|  | ||||
|         & span[contenteditable] { | ||||
|             display: inline-block; | ||||
|             word-break: break-all; | ||||
|  | ||||
|             &:empty::before { | ||||
|                 opacity: 0.5; | ||||
|                 display: inline-block; | ||||
|                 content: attr(data-placeholder); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         & span[contenteditable]:focus, | ||||
|         span[contenteditable="true"]:hover { | ||||
|             border-bottom: 1px solid hsl(0deg 0% 80%); | ||||
|             margin-bottom: -1px; | ||||
|             outline: none; | ||||
|         } | ||||
|  | ||||
|         .pill-container .input[contenteditable]:empty::after { | ||||
|             content: attr(data-placeholder); | ||||
|             opacity: 0.5; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     .user-group-status { | ||||
|         margin-bottom: 10px; | ||||
|     } | ||||
|  | ||||
|     & p { | ||||
|         line-height: 2; | ||||
|         margin: 0; | ||||
|     } | ||||
|  | ||||
|     .spacer { | ||||
|         margin: 0 2px; | ||||
|     } | ||||
|  | ||||
|     .subscribers, | ||||
|     .user-group h4 > .name { | ||||
|         font-weight: bold; | ||||
|     } | ||||
|  | ||||
|     .ntm { | ||||
|         cursor: not-allowed; | ||||
|  | ||||
|         & h4 > .button { | ||||
|             cursor: not-allowed; | ||||
|             display: none; | ||||
|  | ||||
|             &:hover { | ||||
|                 border-color: hsl(4deg 56% 82%); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     .save-status { | ||||
|         background-color: transparent; | ||||
|         padding: 2px 5px; | ||||
|         border-radius: 4px; | ||||
|         margin-left: 10px; | ||||
|         border-style: solid; | ||||
|         border-width: 1px; | ||||
|         display: none; | ||||
|         opacity: 0; | ||||
|     } | ||||
|  | ||||
|     .checkmark { | ||||
|         height: 12px; | ||||
|     } | ||||
|  | ||||
|     .delete { | ||||
|         margin-left: auto; | ||||
|     } | ||||
|  | ||||
|     .save-instructions { | ||||
|         display: none; | ||||
|         opacity: 0; | ||||
|         color: hsl(0deg 0% 20%); | ||||
|         font-size: 0.9em; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /* -- new settings overlay -- */ | ||||
| #settings_page { | ||||
|     height: 95vh; | ||||
|   | ||||
| @@ -29,8 +29,6 @@ | ||||
|  | ||||
| {{> invites_list_admin }} | ||||
|  | ||||
| {{> user_groups_admin }} | ||||
|  | ||||
| {{> profile_field_settings_admin }} | ||||
|  | ||||
| {{> data_exports_admin }} | ||||
|   | ||||
| @@ -1,27 +0,0 @@ | ||||
| {{#with user_group}} | ||||
| <div class="user-group white-box" id="{{id}}"> | ||||
|     <div class="alert user-group-status"></div> | ||||
|     <h4> | ||||
|         <span class="name" data-placeholder="{{t 'Name' }}" contenteditable="true" spellcheck="false">{{name}}</span> | ||||
|         <span class="spacer">—</span> | ||||
|         <span class="description" data-placeholder="{{t 'Description' }}" contenteditable="true">{{description}}</span> | ||||
|         <button class="button save-status sea-green small"> | ||||
|             <img class="checkmark" src="../../images/checkbox-green.svg" /> | ||||
|             {{t 'Saved' }} | ||||
|         </button> | ||||
|         <button class="button save-status btn-danger small"> | ||||
|             <i class="fa fa-undo" aria-label="{{t 'Delete' }}" title="{{t 'Delete' }}"></i> | ||||
|         </button> | ||||
|         <button class="button rounded small delete btn-danger"> | ||||
|             <i class="fa fa-trash-o" aria-label="{{t 'Delete' }}" title="{{t 'Delete' }}"></i> | ||||
|         </button> | ||||
|     </h4> | ||||
|     <p class="subscribers">{{t 'Subscribers' }}</p> | ||||
|     <div class="pill-container" data-group-pills="{{id}}"> | ||||
|         <div class="input" contenteditable="true" data-placeholder="{{t 'Add member…' }}"></div> | ||||
|     </div> | ||||
|     <p class="save-instructions"> | ||||
|         {{t "Click outside the input box to save. We'll automatically notify anyone that was added or removed."}} | ||||
|     </p> | ||||
| </div> | ||||
| {{/with}} | ||||
| @@ -1,28 +0,0 @@ | ||||
| <div id="user-groups-admin" class="settings-section" data-name="user-groups-admin"> | ||||
|     <div class="user-group-setting-tip-container {{#unless (or is_admin (not can_edit_user_groups))}}hide{{/unless}}"> | ||||
|         {{#if (eq realm_user_group_edit_policy policy_values.by_members.code) }} | ||||
|         <div class="tip">{{t 'This organization is configured so that administrators, moderators and group members can modify user groups.' }}</div> | ||||
|         {{else if (eq realm_user_group_edit_policy policy_values.by_full_members.code) }} | ||||
|         <div class="tip">{{t 'This organization is configured so that administrators, moderators and full members belonging to the group can modify user groups.' }}</div> | ||||
|         {{else if (eq realm_user_group_edit_policy policy_values.by_moderators_only.code) }} | ||||
|         <div class="tip">{{t 'This organization is configured so that administrators and moderators can modify user groups.' }}</div> | ||||
|         {{else}} | ||||
|         <div class="tip">{{t 'This organization is configured so that only administrators can modify user groups.' }}</div> | ||||
|         {{/if}} | ||||
|     </div> | ||||
|     {{#unless is_guest}} | ||||
|         <p> | ||||
|             {{#tr}} | ||||
|                 User groups allow you to <z-link>mention</z-link> multiple users at once. When you mention a user group, everyone in the group is notified as if they were individually mentioned. | ||||
|                 {{#*inline "z-link"}}<a href="/help/mention-a-user-or-group" target="_blank" rel="noopener noreferrer">{{> @partial-block}}</a>{{/inline}} | ||||
|             {{/tr}} | ||||
|         </p> | ||||
|         {{#if can_edit_user_groups}} | ||||
|             <button id="show-add-user-group-modal" class="button rounded sea-green"> | ||||
|                 {{t 'Add a new user group' }} | ||||
|             </button> | ||||
|         {{/if}} | ||||
|     {{/unless}} | ||||
|  | ||||
|     <div id="user-groups" class="new-style"></div> | ||||
| </div> | ||||
| @@ -86,12 +86,6 @@ | ||||
|                         <i class="locked fa fa-lock tippy-zulip-tooltip" {{#if is_admin}}style="display: none;"{{/if}} data-tippy-content="{{t 'Only organization administrators can edit these settings.' }}"></i> | ||||
|                     </li> | ||||
|                     {{#unless is_guest}} | ||||
|                     <li tabindex="0" data-section="user-groups-admin"> | ||||
|                         <i class="icon fa fa-group" aria-hidden="true"></i> | ||||
|                         <div class="text">{{t "User groups" }}</div> | ||||
|                     </li> | ||||
|                     {{/unless}} | ||||
|                     {{#unless is_guest}} | ||||
|                     <li tabindex="0" data-section="user-list-admin"> | ||||
|                         <i class="icon fa fa-user" aria-hidden="true"></i> | ||||
|                         <div class="text">{{t "Users" }}</div> | ||||
|   | ||||
| @@ -63,7 +63,6 @@ const settings_realm_user_settings_defaults = mock_esm( | ||||
| ); | ||||
| const settings_realm_domains = mock_esm("../src/settings_realm_domains"); | ||||
| const settings_streams = mock_esm("../src/settings_streams"); | ||||
| const settings_user_groups_legacy = mock_esm("../src/settings_user_groups_legacy"); | ||||
| const settings_users = mock_esm("../src/settings_users"); | ||||
| const sidebar_ui = mock_esm("../src/sidebar_ui"); | ||||
| const stream_data = mock_esm("../src/stream_data"); | ||||
| @@ -171,7 +170,6 @@ run_test("attachments", ({override}) => { | ||||
|  | ||||
| run_test("user groups", ({override}) => { | ||||
|     let event = event_fixtures.user_group__add; | ||||
|     override(settings_user_groups_legacy, "reload", noop); | ||||
|     { | ||||
|         const stub = make_stub(); | ||||
|         const user_group_settings_ui_stub = make_stub(); | ||||
| @@ -1165,7 +1163,7 @@ run_test("realm_export", ({override}) => { | ||||
|     assert.equal(args.exports, event.exports); | ||||
| }); | ||||
|  | ||||
| run_test("server_event_dispatch_op_errors", ({override}) => { | ||||
| run_test("server_event_dispatch_op_errors", () => { | ||||
|     blueslip.expect("error", "Unexpected event type subscription/other"); | ||||
|     server_events_dispatch.dispatch_normal_event({type: "subscription", op: "other"}); | ||||
|     blueslip.expect("error", "Unexpected event type reaction/other"); | ||||
| @@ -1190,7 +1188,6 @@ run_test("server_event_dispatch_op_errors", ({override}) => { | ||||
|         sender: {user_id: 5}, | ||||
|         op: "other", | ||||
|     }); | ||||
|     override(settings_user_groups_legacy, "reload", noop); | ||||
|     blueslip.expect("error", "Unexpected event type user_group/other"); | ||||
|     server_events_dispatch.dispatch_normal_event({type: "user_group", op: "other"}); | ||||
| }); | ||||
|   | ||||
| @@ -124,6 +124,11 @@ run_test("set_up", ({mock_template}) => { | ||||
|         get_text_from_item: noop, | ||||
|     }); | ||||
|  | ||||
|     let update_func_called = false; | ||||
|     function update_func() { | ||||
|         update_func_called = true; | ||||
|     } | ||||
|  | ||||
|     let opts = {}; | ||||
|     $fake_input.typeahead = (config) => { | ||||
|         assert.equal(config.items, 5); | ||||
| @@ -297,6 +302,8 @@ run_test("set_up", ({mock_template}) => { | ||||
|                 assert.equal(number_of_pills(), 2); | ||||
|                 config.updater.call(fake_group_this, testers); | ||||
|                 assert.equal(number_of_pills(), 3); | ||||
|  | ||||
|                 assert.ok(update_func_called); | ||||
|             } | ||||
|         })(); | ||||
|  | ||||
| @@ -324,7 +331,7 @@ run_test("set_up", ({mock_template}) => { | ||||
|         {user_group: true, stream: true}, | ||||
|         {user_group: true, user: true}, | ||||
|         {user: true, stream: true}, | ||||
|         {user_group: true, stream: true, user: true}, | ||||
|         {user_group: true, stream: true, user: true, update_func}, | ||||
|     ]; | ||||
|  | ||||
|     for (const config of all_possible_opts) { | ||||
|   | ||||
| @@ -1,854 +0,0 @@ | ||||
| "use strict"; | ||||
|  | ||||
| const {strict: assert} = require("assert"); | ||||
|  | ||||
| const {$t} = require("./lib/i18n"); | ||||
| const {mock_esm, set_global, zrequire} = require("./lib/namespace"); | ||||
| const {run_test} = require("./lib/test"); | ||||
| const blueslip = require("./lib/zblueslip"); | ||||
| const $ = require("./lib/zjquery"); | ||||
| const {page_params} = require("./lib/zpage_params"); | ||||
|  | ||||
| const noop = () => {}; | ||||
|  | ||||
| const pills = { | ||||
|     pill: {}, | ||||
| }; | ||||
|  | ||||
| let create_item_handler; | ||||
|  | ||||
| const channel = mock_esm("../src/channel"); | ||||
| const confirm_dialog = mock_esm("../src/confirm_dialog"); | ||||
| const dialog_widget = mock_esm("../src/dialog_widget"); | ||||
| const input_pill = mock_esm("../src/input_pill"); | ||||
| const settings_data = mock_esm("../src/settings_data"); | ||||
| const typeahead_helper = mock_esm("../src/typeahead_helper"); | ||||
| const user_groups = mock_esm("../src/user_groups", { | ||||
|     get_user_group_from_id: noop, | ||||
|     remove: noop, | ||||
|     add: noop, | ||||
| }); | ||||
| const ui_report = mock_esm("../src/ui_report"); | ||||
|  | ||||
| const people = zrequire("people"); | ||||
| const settings_user_groups_legacy = zrequire("settings_user_groups_legacy"); | ||||
| const user_pill = zrequire("user_pill"); | ||||
|  | ||||
| function reset_test_setup($pill_container_stub) { | ||||
|     function input_pill_stub(opts) { | ||||
|         assert.equal(opts.$container, $pill_container_stub); | ||||
|         create_item_handler = opts.create_item_from_text; | ||||
|         assert.ok(create_item_handler); | ||||
|         return pills; | ||||
|     } | ||||
|     input_pill.create = input_pill_stub; | ||||
| } | ||||
|  | ||||
| function test_ui(label, f) { | ||||
|     // The sloppy_$ flag lets us reuse setup from prior tests. | ||||
|     run_test(label, f, {sloppy_$: true}); | ||||
| } | ||||
|  | ||||
| test_ui("can_edit", ({override}) => { | ||||
|     override(settings_data, "user_can_edit_user_groups", () => false); | ||||
|     assert.ok(!settings_user_groups_legacy.can_edit(1)); | ||||
|  | ||||
|     override(settings_data, "user_can_edit_user_groups", () => true); | ||||
|     user_groups.is_direct_member_of = (user_id, group_id) => { | ||||
|         assert.equal(group_id, 1); | ||||
|         assert.equal(user_id, undefined); | ||||
|         return false; | ||||
|     }; | ||||
|     assert.ok(!settings_user_groups_legacy.can_edit(1)); | ||||
|  | ||||
|     page_params.is_admin = true; | ||||
|     assert.ok(settings_user_groups_legacy.can_edit(1)); | ||||
|  | ||||
|     page_params.is_admin = false; | ||||
|     page_params.is_moderator = true; | ||||
|     assert.ok(settings_user_groups_legacy.can_edit(1)); | ||||
|  | ||||
|     page_params.is_admin = false; | ||||
|     page_params.is_moderator = false; | ||||
|     user_groups.is_direct_member_of = (user_id, group_id) => { | ||||
|         assert.equal(group_id, 1); | ||||
|         assert.equal(user_id, undefined); | ||||
|         return true; | ||||
|     }; | ||||
|     assert.ok(settings_user_groups_legacy.can_edit(1)); | ||||
| }); | ||||
|  | ||||
| const user_group_selector = `#user-groups #${CSS.escape(1)}`; | ||||
| const cancel_selector = `#user-groups #${CSS.escape(1)} .save-status.btn-danger`; | ||||
| const saved_selector = `#user-groups #${CSS.escape(1)} .save-status.sea-green`; | ||||
| const name_selector = `#user-groups #${CSS.escape(1)} .name`; | ||||
| const description_selector = `#user-groups #${CSS.escape(1)} .description`; | ||||
| const instructions_selector = `#user-groups #${CSS.escape(1)} .save-instructions`; | ||||
|  | ||||
| test_ui("populate_user_groups", ({mock_template, override, override_rewire}) => { | ||||
|     override(settings_data, "user_can_edit_user_groups", () => true); | ||||
|  | ||||
|     const realm_user_group = { | ||||
|         id: 1, | ||||
|         name: "Mobile", | ||||
|         description: "All mobile people", | ||||
|         members: new Set([2, 4]), | ||||
|     }; | ||||
|     const iago = { | ||||
|         email: "iago@zulip.com", | ||||
|         user_id: 2, | ||||
|         full_name: "Iago", | ||||
|     }; | ||||
|     const alice = { | ||||
|         email: "alice@example.com", | ||||
|         user_id: 31, | ||||
|         full_name: "Alice", | ||||
|     }; | ||||
|     const bob = { | ||||
|         email: "bob@example.com", | ||||
|         user_id: 32, | ||||
|         full_name: "Bob", | ||||
|     }; | ||||
|  | ||||
|     people.add_active_user(iago); | ||||
|     people.add_active_user(alice); | ||||
|     people.add_active_user(bob); | ||||
|  | ||||
|     override_rewire(people, "get_realm_users", () => [iago, alice, bob]); | ||||
|  | ||||
|     user_groups.get_realm_user_groups = () => [realm_user_group]; | ||||
|  | ||||
|     let templates_render_called = false; | ||||
|     const $fake_rendered_temp = $.create("fake_admin_user_group_list_template_rendered"); | ||||
|     mock_template("settings/admin_user_group_list.hbs", false, (args) => { | ||||
|         assert.equal(args.user_group.id, 1); | ||||
|         assert.equal(args.user_group.name, "Mobile"); | ||||
|         assert.equal(args.user_group.description, "All mobile people"); | ||||
|         templates_render_called = true; | ||||
|         return $fake_rendered_temp; | ||||
|     }); | ||||
|  | ||||
|     let user_groups_list_append_called = false; | ||||
|     $("#user-groups").append = (rendered_temp) => { | ||||
|         assert.equal(rendered_temp, $fake_rendered_temp); | ||||
|         user_groups_list_append_called = true; | ||||
|     }; | ||||
|  | ||||
|     let get_by_user_id_called = false; | ||||
|     override_rewire(people, "get_by_user_id", (user_id) => { | ||||
|         if (user_id === iago.user_id) { | ||||
|             return iago; | ||||
|         } | ||||
|         assert.equal(user_id, 4); | ||||
|         blueslip.expect("warn", "Undefined user in function append_user"); | ||||
|         get_by_user_id_called = true; | ||||
|         return undefined; | ||||
|     }); | ||||
|     override_rewire( | ||||
|         people, | ||||
|         "is_known_user", | ||||
|         () => people.get_by_user_id !== undefined && people.get_by_user_id !== noop, | ||||
|     ); | ||||
|  | ||||
|     page_params.is_admin = true; | ||||
|  | ||||
|     const all_pills = new Map(); | ||||
|  | ||||
|     const $pill_container_stub = $(`.pill-container[data-group-pills="${CSS.escape(1)}"]`); | ||||
|     pills.appendValidatedData = (item) => { | ||||
|         const id = item.user_id; | ||||
|         assert.ok(!all_pills.has(id)); | ||||
|         all_pills.set(id, item); | ||||
|     }; | ||||
|     pills.items = () => [...all_pills.values()]; | ||||
|  | ||||
|     let text_cleared; | ||||
|     pills.clear_text = () => { | ||||
|         text_cleared = true; | ||||
|     }; | ||||
|  | ||||
|     const $input_field_stub = $.create("fake-input-field"); | ||||
|     $pill_container_stub.children = () => $input_field_stub; | ||||
|  | ||||
|     let input_typeahead_called = false; | ||||
|     $input_field_stub.typeahead = (config) => { | ||||
|         assert.equal(config.items, 5); | ||||
|         assert.ok(config.fixed); | ||||
|         assert.ok(config.dropup); | ||||
|         assert.ok(config.stopAdvance); | ||||
|         assert.equal(typeof config.source, "function"); | ||||
|         assert.equal(typeof config.highlighter, "function"); | ||||
|         assert.equal(typeof config.matcher, "function"); | ||||
|         assert.equal(typeof config.sorter, "function"); | ||||
|         assert.equal(typeof config.updater, "function"); | ||||
|  | ||||
|         (function test_highlighter() { | ||||
|             const $fake_person = $.create("fake-person"); | ||||
|             typeahead_helper.render_person = () => $fake_person; | ||||
|             assert.equal(config.highlighter(), $fake_person); | ||||
|         })(); | ||||
|  | ||||
|         const fake_context = { | ||||
|             query: "ali", | ||||
|         }; | ||||
|  | ||||
|         const fake_context_for_email = { | ||||
|             query: "am", | ||||
|         }; | ||||
|  | ||||
|         (function test_source() { | ||||
|             const result = config.source.call(fake_context, iago); | ||||
|             const emails = result.map((user) => user.email).sort(); | ||||
|             assert.deepEqual(emails, [alice.email, bob.email]); | ||||
|         })(); | ||||
|  | ||||
|         (function test_matcher() { | ||||
|             /* Here the query doesn't begin with an '@' because typeahead is triggered | ||||
|             by the '@' sign and thus removed in the query. */ | ||||
|             let result = config.matcher.call(fake_context, iago); | ||||
|             assert.ok(!result); | ||||
|  | ||||
|             result = config.matcher.call(fake_context, alice); | ||||
|             assert.ok(result); | ||||
|  | ||||
|             bob.delivery_email = null; | ||||
|             result = config.matcher.call(fake_context_for_email, bob); | ||||
|             assert.ok(!result); | ||||
|  | ||||
|             bob.delivery_email = "bob-delivery@example.com"; | ||||
|             result = config.matcher.call(fake_context_for_email, bob); | ||||
|             assert.ok(result); | ||||
|         })(); | ||||
|  | ||||
|         (function test_sorter() { | ||||
|             let sort_recipients_typeahead_called = false; | ||||
|             typeahead_helper.sort_recipients = function () { | ||||
|                 sort_recipients_typeahead_called = true; | ||||
|             }; | ||||
|             config.sorter.call(fake_context, []); | ||||
|             assert.ok(sort_recipients_typeahead_called); | ||||
|         })(); | ||||
|  | ||||
|         (function test_updater() { | ||||
|             $input_field_stub.text("@ali"); | ||||
|             user_groups.get_user_group_from_id = () => realm_user_group; | ||||
|  | ||||
|             let saved_fade_out_called = false; | ||||
|             let cancel_fade_to_called = false; | ||||
|             let instructions_fade_to_called = false; | ||||
|             $(saved_selector).fadeOut = () => { | ||||
|                 saved_fade_out_called = true; | ||||
|             }; | ||||
|             $(cancel_selector).css = (data) => { | ||||
|                 assert.equal(typeof data, "object"); | ||||
|                 assert.equal(data.display, "inline-block"); | ||||
|                 assert.equal(data.opacity, "0"); | ||||
|                 return $(cancel_selector); | ||||
|             }; | ||||
|             $(cancel_selector).fadeTo = () => { | ||||
|                 cancel_fade_to_called = true; | ||||
|             }; | ||||
|             $(instructions_selector).css = (data) => { | ||||
|                 assert.equal(typeof data, "object"); | ||||
|                 assert.equal(data.display, "block"); | ||||
|                 assert.equal(data.opacity, "0"); | ||||
|                 return $(instructions_selector); | ||||
|             }; | ||||
|             $(instructions_selector).fadeTo = () => { | ||||
|                 instructions_fade_to_called = true; | ||||
|             }; | ||||
|  | ||||
|             text_cleared = false; | ||||
|             config.updater(alice); | ||||
|             // update_cancel_button is called. | ||||
|             assert.ok(saved_fade_out_called); | ||||
|             assert.ok(cancel_fade_to_called); | ||||
|             assert.ok(instructions_fade_to_called); | ||||
|             assert.equal(text_cleared, true); | ||||
|         })(); | ||||
|         input_typeahead_called = true; | ||||
|     }; | ||||
|  | ||||
|     let get_by_email_called = false; | ||||
|     override_rewire(people, "get_by_email", (user_email) => { | ||||
|         get_by_email_called = true; | ||||
|         switch (user_email) { | ||||
|             case iago.email: | ||||
|                 return iago; | ||||
|             case bob.email: | ||||
|                 return bob; | ||||
|             /* istanbul ignore next */ | ||||
|             default: | ||||
|                 throw new Error("Expected user email to be of Iago or Bob here."); | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     function test_create_item(handler) { | ||||
|         (function test_rejection_path() { | ||||
|             const item = handler(iago.email, pills.items()); | ||||
|             assert.ok(get_by_email_called); | ||||
|             assert.equal(item, undefined); | ||||
|         })(); | ||||
|  | ||||
|         (function test_success_path() { | ||||
|             get_by_email_called = false; | ||||
|             const res = handler(bob.email, pills.items()); | ||||
|             assert.ok(get_by_email_called); | ||||
|             assert.equal(typeof res, "object"); | ||||
|             assert.equal(res.user_id, bob.user_id); | ||||
|             assert.equal(res.display_value, bob.full_name); | ||||
|         })(); | ||||
|  | ||||
|         (function test_deactivated_pill() { | ||||
|             people.deactivate(bob); | ||||
|             get_by_email_called = false; | ||||
|             const res = handler(bob.email, pills.items()); | ||||
|             assert.ok(get_by_email_called); | ||||
|             assert.equal(typeof res, "object"); | ||||
|             assert.equal(res.user_id, bob.user_id); | ||||
|             assert.equal(res.display_value, bob.full_name); | ||||
|             assert.ok(res.deactivated); | ||||
|             people.add_active_user(bob); | ||||
|         })(); | ||||
|     } | ||||
|  | ||||
|     pills.onPillRemove = (handler) => { | ||||
|         set_global("setTimeout", (func) => { | ||||
|             func(); | ||||
|         }); | ||||
|         realm_user_group.members = new Set([2, 31]); | ||||
|         handler(); | ||||
|     }; | ||||
|  | ||||
|     reset_test_setup($pill_container_stub); | ||||
|     settings_user_groups_legacy.set_up(); | ||||
|     assert.ok(templates_render_called); | ||||
|     assert.ok(user_groups_list_append_called); | ||||
|     assert.ok(get_by_user_id_called); | ||||
|     assert.ok(input_typeahead_called); | ||||
|     test_create_item(create_item_handler); | ||||
|  | ||||
|     // Tests for settings_user_groups_legacy.set_up workflow. | ||||
|     assert.equal(typeof $("#user-groups").get_on_handler("click", ".delete"), "function"); | ||||
|     assert.equal( | ||||
|         typeof $("#user-groups").get_on_handler("keypress", ".user-group h4 > span"), | ||||
|         "function", | ||||
|     ); | ||||
| }); | ||||
| test_ui("with_external_user", ({disallow_rewire, override_rewire, mock_template}) => { | ||||
|     const realm_user_group = { | ||||
|         id: 1, | ||||
|         name: "Mobile", | ||||
|         description: "All mobile people", | ||||
|         members: new Set([2, 4]), | ||||
|     }; | ||||
|  | ||||
|     user_groups.get_realm_user_groups = () => [realm_user_group]; | ||||
|  | ||||
|     // These are already tested, so we skip them | ||||
|     disallow_rewire(people, "get_realm_users"); | ||||
|  | ||||
|     mock_template( | ||||
|         "settings/admin_user_group_list.hbs", | ||||
|         false, | ||||
|         () => "settings/admin_user_group_list.hbs", | ||||
|     ); | ||||
|  | ||||
|     override_rewire(people, "get_by_user_id", () => "user stub"); | ||||
|  | ||||
|     override_rewire(user_pill, "append_person", noop); | ||||
|  | ||||
|     let can_edit_called = 0; | ||||
|     override_rewire(settings_user_groups_legacy, "can_edit", () => { | ||||
|         can_edit_called += 1; | ||||
|         return false; | ||||
|     }); | ||||
|  | ||||
|     // Reset zjquery to test stuff with user who cannot edit | ||||
|     $.clear_all_elements(); | ||||
|  | ||||
|     let user_group_find_called = 0; | ||||
|     const $user_group_stub = $(`div.user-group[id="${CSS.escape(1)}"]`); | ||||
|     const $name_field_stub = $.create("fake-name-field"); | ||||
|     const $description_field_stub = $.create("fake-description-field"); | ||||
|     const $input_stub = $.create("fake-input"); | ||||
|     $user_group_stub.find = (elem) => { | ||||
|         user_group_find_called += 1; | ||||
|         switch (elem) { | ||||
|             case ".name": | ||||
|                 return $name_field_stub; | ||||
|             case ".description": | ||||
|                 return $description_field_stub; | ||||
|             /* istanbul ignore next */ | ||||
|             default: | ||||
|                 throw new Error(`Unknown element ${elem}`); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     const $pill_container_stub = $(`.pill-container[data-group-pills="${CSS.escape(1)}"]`); | ||||
|     const $pill_stub = $.create("fake-pill"); | ||||
|     let pill_container_find_called = 0; | ||||
|     $pill_container_stub.find = (elem) => { | ||||
|         pill_container_find_called += 1; | ||||
|         switch (elem) { | ||||
|             case ".input": | ||||
|                 return $input_stub; | ||||
|             case ".pill": | ||||
|                 return $pill_stub; | ||||
|             /* istanbul ignore next */ | ||||
|             default: | ||||
|                 throw new Error(`Unknown element ${elem}`); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     $input_stub.css = (property, val) => { | ||||
|         assert.equal(property, "display"); | ||||
|         assert.equal(val, "none"); | ||||
|     }; | ||||
|  | ||||
|     // Test the 'off' handlers on the pill-container | ||||
|     const turned_off = {}; | ||||
|     $pill_container_stub.off = (event_name, sel = "whole") => { | ||||
|         turned_off[event_name + "/" + sel] = true; | ||||
|     }; | ||||
|  | ||||
|     const $exit_button = $.create("fake-pill-exit"); | ||||
|     $pill_stub.set_find_results(".exit", $exit_button); | ||||
|     let exit_button_called = false; | ||||
|     $exit_button.css = (property, value) => { | ||||
|         exit_button_called = true; | ||||
|         assert.equal(property, "opacity"); | ||||
|         assert.equal(value, "0.5"); | ||||
|     }; | ||||
|  | ||||
|     // We return [] because these are already tested, so we skip them | ||||
|     $pill_container_stub.children = () => []; | ||||
|  | ||||
|     $("#user-groups").append = noop; | ||||
|  | ||||
|     reset_test_setup($pill_container_stub); | ||||
|  | ||||
|     settings_user_groups_legacy.set_up(); | ||||
|  | ||||
|     let set_parents_result_called = 0; | ||||
|     let set_attributes_called = 0; | ||||
|  | ||||
|     // Test different handlers with an external user | ||||
|     const delete_handler = $("#user-groups").get_on_handler("click", ".delete"); | ||||
|     const $fake_delete = $.create("fk-#user-groups.delete_btn"); | ||||
|     $fake_delete.set_parents_result(".user-group", $(".user-group")); | ||||
|     set_parents_result_called += 1; | ||||
|     $(".user-group").attr("id", "1"); | ||||
|     set_attributes_called += 1; | ||||
|  | ||||
|     const name_update_handler = $(user_group_selector).get_on_handler("input", ".name"); | ||||
|  | ||||
|     const des_update_handler = $(user_group_selector).get_on_handler("input", ".description"); | ||||
|  | ||||
|     const member_change_handler = $(user_group_selector).get_on_handler("blur", ".input"); | ||||
|  | ||||
|     const name_change_handler = $(user_group_selector).get_on_handler("blur", ".name"); | ||||
|  | ||||
|     const des_change_handler = $(user_group_selector).get_on_handler("blur", ".description"); | ||||
|  | ||||
|     const event = { | ||||
|         stopPropagation: noop, | ||||
|     }; | ||||
|     const pill_mouseenter_handler = $pill_stub.get_on_handler("mouseenter"); | ||||
|     const pill_click_handler = $pill_container_stub.get_on_handler("click"); | ||||
|     pill_mouseenter_handler(event); | ||||
|     pill_click_handler(event); | ||||
|     assert.equal(delete_handler.call($fake_delete), undefined); | ||||
|     assert.equal(name_update_handler(), undefined); | ||||
|     assert.equal(des_update_handler(), undefined); | ||||
|     assert.equal(member_change_handler(), undefined); | ||||
|     assert.equal(name_change_handler(), undefined); | ||||
|     assert.equal(des_change_handler(), undefined); | ||||
|     assert.equal(set_parents_result_called, 1); | ||||
|     assert.equal(set_attributes_called, 1); | ||||
|     assert.equal(can_edit_called, 9); | ||||
|     assert.ok(exit_button_called); | ||||
|     assert.equal(user_group_find_called, 2); | ||||
|     assert.equal(pill_container_find_called, 4); | ||||
|     assert.equal(turned_off["keydown/.pill"], true); | ||||
|     assert.equal(turned_off["keydown/.input"], true); | ||||
|     assert.equal(turned_off["click/whole"], true); | ||||
| }); | ||||
|  | ||||
| test_ui("reload", ({override_rewire}) => { | ||||
|     $("#user-groups").html("Some text"); | ||||
|     let populate_user_groups_called = false; | ||||
|     override_rewire(settings_user_groups_legacy, "populate_user_groups", () => { | ||||
|         populate_user_groups_called = true; | ||||
|     }); | ||||
|     settings_user_groups_legacy.reload(); | ||||
|     assert.ok(populate_user_groups_called); | ||||
|     assert.equal($("#user-groups").html(), ""); | ||||
| }); | ||||
|  | ||||
| test_ui("reset", () => { | ||||
|     settings_user_groups_legacy.reset(); | ||||
|     const result = settings_user_groups_legacy.reload(); | ||||
|     assert.equal(result, undefined); | ||||
| }); | ||||
|  | ||||
| test_ui("on_events", ({mock_template, override, override_rewire}) => { | ||||
|     override(settings_data, "user_can_edit_user_groups", () => true); | ||||
|     mock_template("confirm_dialog/confirm_delete_user.hbs", false, (data) => { | ||||
|         assert.deepEqual(data, { | ||||
|             group_name: "Mobile", | ||||
|         }); | ||||
|         return "stub"; | ||||
|     }); | ||||
|  | ||||
|     page_params.is_admin = true; | ||||
|  | ||||
|     (function test_admin_user_group_form_submit_triggered() { | ||||
|         const handler = settings_user_groups_legacy.add_user_group; | ||||
|         const event = { | ||||
|             stopPropagation: noop, | ||||
|             preventDefault: noop, | ||||
|         }; | ||||
|         const $fake_this = $.create("#add-user-group-form"); | ||||
|         const fake_object_array = [ | ||||
|             { | ||||
|                 name: "fake-name", | ||||
|                 value: "", | ||||
|             }, | ||||
|             { | ||||
|                 name: "fake-name", | ||||
|                 value: "fake-value", | ||||
|             }, | ||||
|         ]; | ||||
|         $fake_this.serializeArray = () => fake_object_array; | ||||
|         channel.post = (opts) => { | ||||
|             const data = { | ||||
|                 members: "[null]", | ||||
|             }; | ||||
|             data[fake_object_array[1].name] = fake_object_array[1].value; | ||||
|             assert.equal(opts.url, "/json/user_groups/create"); | ||||
|             assert.deepEqual(opts.data, data); | ||||
|  | ||||
|             (function test_post_success() { | ||||
|                 $("#dialog_error").show(); | ||||
|                 $("#add-user-group-form input[type='text']").val("fake-content"); | ||||
|                 ui_report.success = (text, ele) => { | ||||
|                     assert.equal(text, "translated HTML: User group added!"); | ||||
|                     assert.equal(ele, $("#dialog_error")); | ||||
|                 }; | ||||
|                 dialog_widget.close = () => {}; | ||||
|  | ||||
|                 opts.success(); | ||||
|  | ||||
|                 assert.ok(!$("#dialog_error").visible()); | ||||
|             })(); | ||||
|             (function test_post_error() { | ||||
|                 $("#dialog_error").show(); | ||||
|                 ui_report.error = (error_msg, error_obj, ele) => { | ||||
|                     assert.equal(error_msg, "translated HTML: Failed"); | ||||
|                     assert.deepEqual(error_obj, {responseJson: {msg: "fake-msg"}}); | ||||
|                     assert.equal(ele, $("#dialog_error")); | ||||
|                 }; | ||||
|                 opts.error({responseJson: {msg: "fake-msg"}}); | ||||
|  | ||||
|                 assert.ok(!$("#dialog_error").visible()); | ||||
|             })(); | ||||
|         }; | ||||
|  | ||||
|         handler(event); | ||||
|     })(); | ||||
|  | ||||
|     (function test_user_groups_delete_click_triggered() { | ||||
|         const handler = $("#user-groups").get_on_handler("click", ".delete"); | ||||
|         const $fake_this = $.create("fake-#user-groups.delete_btn"); | ||||
|         $fake_this.set_parents_result(".user-group", $(".user-group")); | ||||
|         $(".user-group").attr("id", "1"); | ||||
|  | ||||
|         channel.del = (opts) => { | ||||
|             const data = { | ||||
|                 id: 1, | ||||
|             }; | ||||
|             assert.equal(opts.url, "/json/user_groups/1"); | ||||
|             assert.deepEqual(opts.data, data); | ||||
|  | ||||
|             $fake_this.text($t({defaultMessage: "fake-text"})); | ||||
|             opts.error(); | ||||
|             assert.equal($fake_this.text(), "translated: Failed!"); | ||||
|         }; | ||||
|  | ||||
|         confirm_dialog.launch = (conf) => { | ||||
|             conf.on_click(); | ||||
|         }; | ||||
|  | ||||
|         handler.call($fake_this); | ||||
|     })(); | ||||
|  | ||||
|     (function test_user_groups_keypress_enter_triggered() { | ||||
|         const handler = $("#user-groups").get_on_handler("keypress", ".user-group h4 > span"); | ||||
|         let default_action_for_enter_stopped = false; | ||||
|         const event = { | ||||
|             key: "Enter", | ||||
|             preventDefault() { | ||||
|                 default_action_for_enter_stopped = true; | ||||
|             }, | ||||
|         }; | ||||
|         handler(event); | ||||
|         assert.ok(default_action_for_enter_stopped); | ||||
|     })(); | ||||
|  | ||||
|     (function test_do_not_blur() { | ||||
|         const blur_event_classes = [".name", ".description", ".input"]; | ||||
|         let api_endpoint_called = false; | ||||
|         /* istanbul ignore next */ | ||||
|         channel.post = () => { | ||||
|             api_endpoint_called = true; | ||||
|         }; | ||||
|         channel.patch = noop; | ||||
|         const $fake_this = $.create("fake-#user-groups_do_not_blur"); | ||||
|         const event = { | ||||
|             // FIXME: event.relatedTarget should not be a jQuery object | ||||
|             relatedTarget: $fake_this, | ||||
|         }; | ||||
|  | ||||
|         // Any of the blur_exceptions trigger blur event. | ||||
|         for (const class_name of blur_event_classes) { | ||||
|             const handler = $(user_group_selector).get_on_handler("blur", class_name); | ||||
|  | ||||
|             for (const blur_exception of [ | ||||
|                 ".pill-container", | ||||
|                 ".name", | ||||
|                 ".description", | ||||
|                 ".input", | ||||
|                 ".delete", | ||||
|             ]) { | ||||
|                 if (blur_exception === class_name) { | ||||
|                     continue; | ||||
|                 } | ||||
|                 api_endpoint_called = false; | ||||
|                 $fake_this.closest = (class_name) => { | ||||
|                     if (class_name === blur_exception || class_name === user_group_selector) { | ||||
|                         return [1]; | ||||
|                     } | ||||
|                     return []; | ||||
|                 }; | ||||
|                 handler.call($fake_this, event); | ||||
|                 assert.ok(!api_endpoint_called); | ||||
|             } | ||||
|  | ||||
|             api_endpoint_called = false; | ||||
|             $fake_this.closest = (class_name) => { | ||||
|                 assert.equal(class_name, ".typeahead"); | ||||
|                 return [1]; | ||||
|             }; | ||||
|             handler.call($fake_this, event); | ||||
|             assert.ok(!api_endpoint_called); | ||||
|  | ||||
|             // Cancel button triggers blur event. | ||||
|             let settings_user_groups_legacy_reload_called = false; | ||||
|             override_rewire(settings_user_groups_legacy, "reload", () => { | ||||
|                 settings_user_groups_legacy_reload_called = true; | ||||
|             }); | ||||
|             api_endpoint_called = false; | ||||
|             $fake_this.closest = (class_name) => { | ||||
|                 if ( | ||||
|                     class_name === ".save-status.btn-danger" || | ||||
|                     class_name === user_group_selector | ||||
|                 ) { | ||||
|                     return [1]; | ||||
|                 } | ||||
|                 return []; | ||||
|             }; | ||||
|             handler.call($fake_this, event); | ||||
|             assert.ok(!api_endpoint_called); | ||||
|             assert.ok(settings_user_groups_legacy_reload_called); | ||||
|         } | ||||
|     })(); | ||||
|  | ||||
|     (function test_update_cancel_button() { | ||||
|         const handler_name = $(user_group_selector).get_on_handler("input", ".name"); | ||||
|         const handler_desc = $(user_group_selector).get_on_handler("input", ".description"); | ||||
|         const $sib_des = $(description_selector); | ||||
|         const $sib_name = $(name_selector); | ||||
|         $sib_name.text($t({defaultMessage: "mobile"})); | ||||
|         $sib_des.text($t({defaultMessage: "All mobile members"})); | ||||
|  | ||||
|         const group_data = { | ||||
|             name: "translated: mobile", | ||||
|             description: "translated: All mobile members", | ||||
|             members: new Set([2, 31]), | ||||
|         }; | ||||
|         user_groups.get_user_group_from_id = () => group_data; | ||||
|  | ||||
|         let cancel_fade_out_called = false; | ||||
|         let instructions_fade_out_called = false; | ||||
|         $(cancel_selector).show(); | ||||
|         $(cancel_selector).fadeOut = () => { | ||||
|             cancel_fade_out_called = true; | ||||
|         }; | ||||
|         $(instructions_selector).fadeOut = () => { | ||||
|             instructions_fade_out_called = true; | ||||
|         }; | ||||
|  | ||||
|         // Cancel button removed if user group if user group has no changes. | ||||
|         const $fake_this = $.create("fake-#update_cancel_button"); | ||||
|         handler_name.call($fake_this); | ||||
|         assert.ok(cancel_fade_out_called); | ||||
|         assert.ok(instructions_fade_out_called); | ||||
|  | ||||
|         // Check if cancel button removed if user group error is showing. | ||||
|         $(user_group_selector + " .user-group-status").show(); | ||||
|         cancel_fade_out_called = false; | ||||
|         instructions_fade_out_called = false; | ||||
|         handler_name.call($fake_this); | ||||
|         assert.ok(cancel_fade_out_called); | ||||
|         assert.ok(instructions_fade_out_called); | ||||
|  | ||||
|         // Check for handler_desc to achieve 100% coverage. | ||||
|         cancel_fade_out_called = false; | ||||
|         instructions_fade_out_called = false; | ||||
|         handler_desc.call($fake_this); | ||||
|         assert.ok(cancel_fade_out_called); | ||||
|         assert.ok(instructions_fade_out_called); | ||||
|     })(); | ||||
|  | ||||
|     (function test_user_groups_save_group_changes_triggered() { | ||||
|         const handler_name = $(user_group_selector).get_on_handler("blur", ".name"); | ||||
|         const handler_desc = $(user_group_selector).get_on_handler("blur", ".description"); | ||||
|         const $sib_des = $(description_selector); | ||||
|         const $sib_name = $(name_selector); | ||||
|         $sib_name.text($t({defaultMessage: "mobile"})); | ||||
|         $sib_des.text($t({defaultMessage: "All mobile members"})); | ||||
|  | ||||
|         const group_data = {members: new Set([2, 31])}; | ||||
|         user_groups.get_user_group_from_id = () => group_data; | ||||
|         let api_endpoint_called = false; | ||||
|         let cancel_fade_out_called = false; | ||||
|         let saved_fade_to_called = false; | ||||
|         let instructions_fade_out_called = false; | ||||
|         $(instructions_selector).fadeOut = () => { | ||||
|             instructions_fade_out_called = true; | ||||
|         }; | ||||
|         $(cancel_selector).fadeOut = () => { | ||||
|             cancel_fade_out_called = true; | ||||
|         }; | ||||
|         $(saved_selector).css = (data) => { | ||||
|             assert.equal(typeof data, "object"); | ||||
|             assert.equal(data.display, "inline-block"); | ||||
|             assert.equal(data.opacity, "0"); | ||||
|             return $(saved_selector); | ||||
|         }; | ||||
|         $(saved_selector).fadeTo = () => { | ||||
|             saved_fade_to_called = true; | ||||
|             return $(saved_selector); | ||||
|         }; | ||||
|  | ||||
|         channel.patch = (opts) => { | ||||
|             assert.equal(opts.url, "/json/user_groups/1"); | ||||
|             assert.equal(opts.data.name, "translated: mobile"); | ||||
|             assert.equal(opts.data.description, "translated: All mobile members"); | ||||
|             api_endpoint_called = true; | ||||
|             (function test_post_success() { | ||||
|                 set_global("setTimeout", (func) => { | ||||
|                     func(); | ||||
|                 }); | ||||
|                 opts.success(); | ||||
|                 assert.ok(cancel_fade_out_called); | ||||
|                 assert.ok(instructions_fade_out_called); | ||||
|                 assert.ok(saved_fade_to_called); | ||||
|             })(); | ||||
|             (function test_post_error() { | ||||
|                 const $user_group_error = $(user_group_selector + " .user-group-status"); | ||||
|                 $user_group_error.show(); | ||||
|                 ui_report.error = (error_msg, error_obj, ele) => { | ||||
|                     assert.equal(error_msg, "translated HTML: Failed"); | ||||
|                     assert.deepEqual(error_obj, {responseJson: {msg: "fake-msg"}}); | ||||
|                     assert.equal(ele, $user_group_error); | ||||
|                 }; | ||||
|                 opts.error({responseJson: {msg: "fake-msg"}}); | ||||
|  | ||||
|                 assert.ok($user_group_error.visible()); | ||||
|             })(); | ||||
|         }; | ||||
|  | ||||
|         const $fake_this = $.create("fake-#user-groups_blur_name"); | ||||
|         $fake_this.closest = () => []; | ||||
|         $fake_this.set_parents_result(user_group_selector, $(user_group_selector)); | ||||
|         const event = { | ||||
|             // FIXME: event.relatedTarget should not be a jQuery object | ||||
|             relatedTarget: $fake_this, | ||||
|         }; | ||||
|  | ||||
|         api_endpoint_called = false; | ||||
|         handler_name.call($fake_this, event); | ||||
|         assert.ok(api_endpoint_called); | ||||
|  | ||||
|         // Check API endpoint isn't called if name and desc haven't changed. | ||||
|         group_data.name = "translated: mobile"; | ||||
|         group_data.description = "translated: All mobile members"; | ||||
|         api_endpoint_called = false; | ||||
|         handler_name.call($fake_this, event); | ||||
|         assert.ok(!api_endpoint_called); | ||||
|  | ||||
|         // Check for handler_desc to achieve 100% coverage. | ||||
|         api_endpoint_called = false; | ||||
|         handler_desc.call($fake_this, event); | ||||
|         assert.ok(!api_endpoint_called); | ||||
|     })(); | ||||
|  | ||||
|     (function test_user_groups_save_member_changes_triggered() { | ||||
|         const handler = $(user_group_selector).get_on_handler("blur", ".input"); | ||||
|         const realm_user_group = { | ||||
|             id: 1, | ||||
|             name: "Mobile", | ||||
|             description: "All mobile people", | ||||
|             members: new Set([2, 4]), | ||||
|         }; | ||||
|  | ||||
|         user_groups.get_user_group_from_id = (id) => { | ||||
|             assert.equal(id, 1); | ||||
|             return realm_user_group; | ||||
|         }; | ||||
|  | ||||
|         let cancel_fade_out_called = false; | ||||
|         let saved_fade_to_called = false; | ||||
|         let instructions_fade_out_called = false; | ||||
|         $(instructions_selector).fadeOut = () => { | ||||
|             instructions_fade_out_called = true; | ||||
|         }; | ||||
|         $(cancel_selector).fadeOut = () => { | ||||
|             cancel_fade_out_called = true; | ||||
|         }; | ||||
|         $(saved_selector).css = () => $(saved_selector); | ||||
|         $(saved_selector).fadeTo = () => { | ||||
|             saved_fade_to_called = true; | ||||
|             return $(saved_selector); | ||||
|         }; | ||||
|  | ||||
|         let api_endpoint_called = false; | ||||
|         channel.post = (opts) => { | ||||
|             assert.equal(opts.url, "/json/user_groups/1/members"); | ||||
|             assert.equal(opts.data.add, "[31]"); | ||||
|             assert.equal(opts.data.delete, "[4]"); | ||||
|             api_endpoint_called = true; | ||||
|  | ||||
|             (function test_post_success() { | ||||
|                 opts.success(); | ||||
|                 assert.ok(cancel_fade_out_called); | ||||
|                 assert.ok(instructions_fade_out_called); | ||||
|                 assert.ok(saved_fade_to_called); | ||||
|             })(); | ||||
|         }; | ||||
|  | ||||
|         const $fake_this = $.create("fake-#user-groups_blur_input"); | ||||
|         $fake_this.set_parents_result(user_group_selector, $(user_group_selector)); | ||||
|         $fake_this.closest = () => []; | ||||
|         const event = { | ||||
|             // FIXME: event.relatedTarget should not be a jQuery object | ||||
|             relatedTarget: $fake_this, | ||||
|         }; | ||||
|  | ||||
|         api_endpoint_called = false; | ||||
|         handler.call($fake_this, event); | ||||
|         assert.ok(api_endpoint_called); | ||||
|     })(); | ||||
| }); | ||||
| @@ -4,6 +4,7 @@ const {strict: assert} = require("assert"); | ||||
|  | ||||
| const {zrequire} = require("./lib/namespace"); | ||||
| const {run_test} = require("./lib/test"); | ||||
| const blueslip = require("./lib/zblueslip"); | ||||
| const {page_params} = require("./lib/zpage_params"); | ||||
|  | ||||
| const people = zrequire("people"); | ||||
| @@ -102,6 +103,9 @@ test("append", () => { | ||||
|  | ||||
|     assert.ok(appended); | ||||
|     assert.ok(cleared); | ||||
|  | ||||
|     blueslip.expect("warn", "Undefined user in function append_user"); | ||||
|     user_pill.append_user(undefined, pill_widget); | ||||
| }); | ||||
|  | ||||
| test("get_items", () => { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user