text: zrequire('compose_state');
  zrequire('ui_util');
  zrequire('pm_conversations');
  zrequire('util');
  zrequire('Handlebars', 'handlebars');
  zrequire('templates');
  zrequire('typeahead_helper');
  zrequire('people');
  zrequire('user_groups');
  zrequire('stream_data');
  zrequire('user_pill');
  zrequire('compose_pm_pill');
  zrequire('composebox_typeahead');
  
  var ct = composebox_typeahead;
  var noop = function () {};
  
  var emoji_stadium = {
      emoji_name: 'stadium',
      emoji_url: 'TBD',
  };
  var emoji_tada = {
      emoji_name: 'tada',
      emoji_url: 'TBD',
  };
  var emoji_moneybag = {
      emoji_name: 'moneybag',
      emoji_url: 'TBD',
  };
  var emoji_japanese_post_office = {
      emoji_name: 'japanese_post_office',
      emoji_url: 'TBD',
  };
  var emoji_panda_face = {
      emoji_name: 'panda_face',
      emoji_url: 'TBD',
  };
  var emoji_see_no_evil = {
      emoji_name: 'see_no_evil',
      emoji_url: 'TBD',
  };
  var emoji_thumbs_up = {
      emoji_name: '+1',
      emoji_url: 'TBD',
  };
  
  var emoji_list = [emoji_tada, emoji_moneybag, emoji_stadium, emoji_japanese_post_office,
                    emoji_panda_face, emoji_see_no_evil, emoji_thumbs_up];
  var stream_list = ['Denmark', 'Sweden', 'The Netherlands'];
  var sweden_stream = {
      name: 'Sweden',
      description: 'Cold, mountains and home decor.',
      stream_id: 1,
      subscribed: true,
  };
  var denmark_stream = {
      name: 'Denmark',
      description: 'Vikings and boats, in a serene and cold weather.',
      stream_id: 2,
      subscribed: true,
  };
  var netherland_stream = {
      name: 'The Netherlands',
      description: 'The Netherlands, city of dream.',
      stream_id: 3,
  };
  
  set_global('$', global.make_zjquery());
  
  set_global('page_params', {});
  set_global('channel', {});
  set_global('compose', {
      finish: noop,
  });
  
  set_global('emoji', {
      active_realm_emojis: {},
      emojis: emoji_list,
  });
  set_global('pygments_data', {langs:
      {python: 0, javscript: 1, html: 2, css: 3},
  });
  
  global.compile_template('typeahead_list_item');
  
  stream_data.get_streams_for_settings_page = function () {
      return stream_list;
  };
  
  stream_data.subscribed_streams = function () {
      return stream_list;
  };
  
  var hamlet = {
      email: 'hamlet@zulip.com',
      user_id: 100,
      full_name: "King Hamlet",
  };
  
  var othello = {
      email: 'othello@zulip.com',
      user_id: 101,
      full_name: "Othello, the Moor of Venice",
  };
  var cordelia = {
      email: 'cordelia@zulip.com',
      user_id: 102,
      full_name: "Cordelia Lear",
  };
  var deactivated_user = {
      email: 'other@zulip.com',
      user_id: 103,
      full_name: "Deactivated User",
  };
  var lear = {
      email: 'lear@zulip.com',
      user_id: 104,
      full_name: "King Lear",
  };
  
  
  global.people.add_in_realm(hamlet);
  global.people.add_in_realm(othello);
  global.people.add_in_realm(cordelia);
  global.people.add_in_realm(lear);
  global.people.add(deactivated_user);
  
  var hamletcharacters = {
      name: "hamletcharacters",
      id: 1,
      description: "Characters of Hamlet",
      members: [],
  };
  
  var backend = {
      name: "Backend",
      id: 2,
      description: "Backend team",
      members: [],
  };
  
  global.user_groups.add(hamletcharacters);
  global.user_groups.add(backend);
  
  user_pill.get_user_ids = function () {
      return [];
  };
  
  (function test_add_topic() {
      ct.add_topic('Denmark', 'civil fears');
      ct.add_topic('devel', 'fading');
      ct.add_topic('denmark', 'acceptance');
      ct.add_topic('denmark', 'Acceptance');
      ct.add_topic('Denmark', 'With Twisted Metal');
  
      assert.deepEqual(
          ct.topics_seen_for('Denmark'),
          ['With Twisted Metal', 'acceptance', 'civil fears']
      );
  }());
  
  (function test_topics_seen_for() {
      // Test when the stream doesn't exist (there are no topics)
      assert.deepEqual(ct.topics_seen_for('non-existing-stream'), []);
  }());
  
  (function test_content_typeahead_selected() {
      var fake_this = {
          query: '',
          $element: {},
      };
      var caret_called1 = false;
      var caret_called2 = false;
      fake_this.$element.caret = function (arg1, arg2) {
          if (arguments.length === 0) {  // .caret() used in split_at_cursor
              caret_called1 = true;
              return fake_this.query.length;
          }
          // .caret() used in setTimeout
          assert.equal(arg1, arg2);
          caret_called2 = true;
      };
      var autosize_called = false;
      set_global('compose_ui', {
          autosize_textarea: function () {
              autosize_called = true;
          },
      });
      var set_timeout_called = false;
      global.patch_builtin('setTimeout', function (f, time) {
          f();
          assert.equal(time, 0);
          set_timeout_called = true;
      });
      set_global('document', 'document-stub');
  
      // emoji
      fake_this.completing = 'emoji';
      fake_this.query = ':octo';
      fake_this.token = 'octo';
      var item = {
          emoji_name: 'octopus',
      };
  
      var actual_value = ct.content_typeahead_selected.call(fake_this, item);
      var expected_value = ':octopus: ';
      assert.equal(actual_value, expected_value);
  
      fake_this.query = ' :octo';
      fake_this.token = 'octo';
      actual_value = ct.content_typeahead_selected.call(fake_this, item);
      expected_value = ' :octopus: ';
      assert.equal(actual_value, expected_value);
  
      fake_this.query = '{:octo';
      fake_this.token = 'octo';
      actual_value = ct.content_typeahead_selected.call(fake_this, item);
      expected_value = '{ :octopus: ';
      assert.equal(actual_value, expected_value);
  
      // mention
      fake_this.completing = 'mention';
      var document_stub_trigger1_called = false;
      $('document-stub').trigger = function (event, params) {
          assert.equal(event, 'usermention_completed.zulip');
          assert.deepEqual(params, { mentioned: othello });
          document_stub_trigger1_called = true;
      };
  
      fake_this.query = '@oth';
      fake_this.token = 'oth';
      actual_value = ct.content_typeahead_selected.call(fake_this, othello);
      expected_value = '@**Othello, the Moor of Venice** ';
      assert.equal(actual_value, expected_value);
  
      fake_this.query = '@**oth';
      fake_this.token = 'oth';
      actual_value = ct.content_typeahead_selected.call(fake_this, othello);
      expected_value = '@**Othello, the Moor of Venice** ';
      assert.equal(actual_value, expected_value);
  
      fake_this.query = '@*oth';
      fake_this.token = 'oth';
      actual_value = ct.content_typeahead_selected.call(fake_this, othello);
      expected_value = '@**Othello, the Moor of Venice** ';
      assert.equal(actual_value, expected_value);
  
      // user group mention
      var document_stub_group_trigger_called = false;
      $('document-stub').trigger = function (event, params) {
          assert.equal(event, 'usermention_completed.zulip');
          assert.deepEqual(params, { user_group: backend });
          document_stub_group_trigger_called = true;
      };
  
      fake_this.query = '@back';
      fake_this.token = 'back';
      actual_value = ct.content_typeahead_selected.call(fake_this, backend);
      expected_value = '@*Backend* ';
      assert.equal(actual_value, expected_value);
  
      fake_this.query = '@*back';
      fake_this.token = 'back';
      actual_value = ct.content_typeahead_selected.call(fake_this, backend);
      expected_value = '@*Backend* ';
      assert.equal(actual_value, expected_value);
  
      // stream
      fake_this.completing = 'stream';
      var document_stub_trigger2_called = false;
      $('document-stub').trigger = function (event, params) {
          assert.equal(event, 'streamname_completed.zulip');
          assert.deepEqual(params, { stream: sweden_stream });
          document_stub_trigger2_called = true;
      };
  
      fake_this.query = '#swed';
      fake_this.token = 'swed';
      actual_value = ct.content_typeahead_selected.call(fake_this, sweden_stream);
      expected_value = '#**Sweden** ';
      assert.equal(actual_value, expected_value);
  
      fake_this.query = '#**swed';
      fake_this.token = 'swed';
      actual_value = ct.content_typeahead_selected.call(fake_this, sweden_stream);
      expected_value = '#**Sweden** ';
      assert.equal(actual_value, expected_value);
  
      // syntax
      fake_this.completing = 'syntax';
  
      fake_this.query = '~~~p';
      fake_this.token = 'p';
      actual_value = ct.content_typeahead_selected.call(fake_this, 'python');
      expected_value = '~~~python\n\n~~~';
      assert.equal(actual_value, expected_value);
  
      fake_this.query = '```p';
      fake_this.token = 'p';
      actual_value = ct.content_typeahead_selected.call(fake_this, 'python');
      expected_value = '```python\n\n```';
      assert.equal(actual_value, expected_value);
  
      // Test special case to not close code blocks if there is text afterward
      fake_this.query = '```p\nsome existing code';
      fake_this.token = 'p';
      fake_this.$element.caret = function () {
          return 4; // Put cursor right after ```p
      };
      actual_value = ct.content_typeahead_selected.call(fake_this, 'python');
      expected_value = '```python\nsome existing code';
      assert.equal(actual_value, expected_value);
  
      fake_this.completing = 'something-else';
  
      fake_this.query = 'foo';
      actual_value = ct.content_typeahead_selected.call(fake_this, {});
      expected_value = fake_this.query;
      assert.equal(actual_value, expected_value);
  
      assert(caret_called1);
      assert(caret_called2);
      assert(autosize_called);
      assert(set_timeout_called);
      assert(document_stub_trigger1_called);
      assert(document_stub_group_trigger_called);
      assert(document_stub_trigger2_called);
  }());
  
  (function test_initialize() {
      var stream_typeahead_called = false;
      $('#stream').typeahead = function (options) {
          // options.source()
          //
          // We'll search through the streams in stream_list for the streams
          // typeahead.
          var actual_value = options.source();
          var expected_value = stream_list;
          assert.deepEqual(actual_value, expected_value);
  
          // options.highlighter()
          options.query = 'De';  // Beginning of "Denmark", one of the streams
                                 // provided in stream_list through .source().
          actual_value = options.highlighter('Denmark');
          expected_value = '<strong>Denmark</strong>';
          assert.equal(actual_value, expected_value);
  
          options.query = 'the n';
          actual_value = options.highlighter('The Netherlands');
          expected_value = '<strong>The Netherlands</strong>';
          assert.equal(actual_value, expected_value);
  
          // options.matcher()
          options.query = 'de';
          assert.equal(options.matcher('Denmark'), true);
          assert.equal(options.matcher('Sweden'), false);
  
          options.query = 'De';
          assert.equal(options.matcher('Denmark'), true);
          assert.equal(options.matcher('Sweden'), false);
  
          options.query = 'the ';
          assert.equal(options.matcher('The Netherlands'), true);
          assert.equal(options.matcher('Sweden'), false);
  
          stream_typeahead_called = true;
      };
  
      var subject_typeahead_called = false;
      $('#subject').typeahead = function (options) {
          // options.source()
          ct.add_topic('Sweden', 'furniture');
          ct.add_topic('Sweden', 'kronor');
          ct.add_topic('Sweden', 'ice');
          ct.add_topic('Sweden', 'more ice');
          ct.add_topic('Sweden', 'even more ice');
          ct.add_topic('Sweden', '<&>');
          var topics = ['<&>', 'even more ice', 'furniture', 'ice', 'kronor', 'more ice'];
          $('#stream').val('Sweden');
          var actual_value = options.source();
          // Topics should be sorted alphabetically, not by addition order.
          var expected_value = topics;
          assert.deepEqual(actual_value, expected_value);
  
          // options.highlighter()
          options.query = 'Kro';
          actual_value = options.highlighter('kronor');
          expected_value = '<strong>kronor</strong>';
          assert.equal(actual_value, expected_value);
  
          // Highlighted content should be escaped.
          options.query = '<';
          actual_value = options.highlighter('<&>');
          expected_value = '<strong>&lt;&amp;&gt;</strong>';
          assert.equal(actual_value, expected_value);
  
          options.query = 'even m';
          actual_value = options.highlighter('even more ice');
          expected_value = '<strong>even more ice</strong>';
          assert.equal(actual_value, expected_value);
  
          // options.sorter()
          //
          // Notice that alphabetical sorting isn't managed by this sorter,
          // it is a result of the topics already being sorted after adding
          // them with ct.add_topic().
          options.query = 'furniture';
          actual_value = options.sorter(['furniture']);
          expected_value = ['furniture'];
          assert.deepEqual(actual_value, expected_value);
  
          // A literal match at the beginning of an element puts it at the top.
          options.query = 'ice';
          actual_value = options.sorter(['even more ice', 'ice', 'more ice']);
          expected_value = ['ice', 'even more ice', 'more ice'];
          assert.deepEqual(actual_value, expected_value);
  
          // The sorter should return the query as the first element if there
          // isn't a topic with such name.
          // This only happens if typeahead is providing other suggestions.
          options.query = 'e';  // Letter present in "furniture" and "ice"
          actual_value = options.sorter(['furniture', 'ice']);
          expected_value = ['e', 'furniture', 'ice'];
          assert.deepEqual(actual_value, expected_value);
  
          // Don't make any suggestions if this query doesn't match any
          // existing topic.
          options.query = 'non-existing-topic';
          actual_value = options.sorter([]);
          expected_value = [];
          assert.deepEqual(actual_value, expected_value);
  
          subject_typeahead_called = true;
      };
  
      var pm_recipient_typeahead_called = false;
      $('#private_message_recipient').typeahead = function (options) {
          // This should match the users added at the beginning of this test file.
          var actual_value = options.source();
          var expected_value = [hamlet, othello, cordelia, lear];
          assert.deepEqual(actual_value, expected_value);
  
          // Even though the items passed to .highlighter() are the full
          // objects of the users matching the query, it only returns the
          // HTML string with the "User_name <email>" format, with the
          // corresponding parts in bold.
          options.query = 'oth';
          actual_value = options.highlighter(othello);
          expected_value = '<strong>Othello, the Moor of Venice</strong>&nbsp;&nbsp;\n<small class="autocomplete_secondary">othello@zulip.com</small>\n';
          assert.equal(actual_value, expected_value);
  
          options.query = 'Lear';
          actual_value = options.highlighter(cordelia);
          expected_value = '<strong>Cordelia Lear</strong>&nbsp;&nbsp;\n<small class="autocomplete_secondary">cordelia@zulip.com</small>\n';
          assert.equal(actual_value, expected_value);
  
          options.query = 'othello@zulip.com, co';
          actual_value = options.highlighter(cordelia);
          expected_value = '<strong>Cordelia Lear</strong>&nbsp;&nbsp;\n<small class="autocomplete_secondary">cordelia@zulip.com</small>\n';
          assert.equal(actual_value, expected_value);
  
          // options.matcher()
          options.query = 'el';  // Matches both "othELlo" and "cordELia"
          assert.equal(options.matcher(othello), true);
          assert.equal(options.matcher(cordelia), true);
  
          options.query = 'bender';  // Doesn't exist
          assert.equal(options.matcher(othello), false);
          assert.equal(options.matcher(cordelia), false);
  
          // Don't make suggestions if the last name only has whitespaces
          // (we're between typing names).
          options.query = 'othello@zulip.com,     ';
          assert.equal(options.matcher(othello), false);
          assert.equal(options.matcher(cordelia), false);
  
          // options.query = 'othello@zulip.com,, , cord';
          options.query = 'cord';
          assert.equal(options.matcher(othello), false);
          assert.equal(options.matcher(cordelia), true);
  
          // If the user is already in the list, typeahead doesn't include it
          // again.
          options.query = 'cordelia@zulip.com, cord';
          assert.equal(options.matcher(othello), false);
          assert.equal(options.matcher(cordelia), false);
  
          // options.sorter()
          //
          // The sorter's output has the items that match the query from the
          // beginning first, and then the rest of them in REVERSE order of
          // the input.
          options.query = 'othello';
          actual_value = options.sorter([othello]);
          expected_value = [othello];
          assert.deepEqual(actual_value, expected_value);
  
          // A literal match at the beginning of an element puts it at the top.
          options.query = 'co';  // Matches everything ("x@zulip.COm")
          actual_value = options.sorter([othello, deactivated_user, cordelia]);
          expected_value = [cordelia, deactivated_user, othello];
          assert.deepEqual(actual_value, expected_value);
  
          options.query = 'non-existing-user';
          actual_value = options.sorter([]);
          expected_value = [];
          assert.deepEqual(actual_value, expected_value);
  
          var event = {
              target: '#doesnotmatter',
          };
  
          var appended_name;
          compose_pm_pill.set_from_typeahead = function (item) {
              appended_name = item.full_name;
          };
  
          // options.updater()
          options.query = 'othello';
          options.updater(othello, event);
          assert.equal(appended_name, 'Othello, the Moor of Venice');
  
          options.query = 'othello@zulip.com, cor';
          actual_value = options.updater(cordelia, event);
          assert.equal(appended_name, 'Cordelia Lear');
  
          var click_event = { type: 'click', target: '#doesnotmatter' };
          options.query = 'othello';
          // Focus lost (caused by the click event in the typeahead list)
          $('#private_message_recipient').blur();
          actual_value = options.updater(othello, click_event);
          assert.equal(appended_name, 'Othello, the Moor of Venice');
  
          pm_recipient_typeahead_called = true;
      };
  
      var compose_textarea_typeahead_called = false;
      $('#compose-textarea').typeahead = function (options) {
          // options.source()
          //
          // For now we only test that compose_contents_begins_typeahead has been
          // properly set as the .source(). All its features are tested later on
          // in test_begins_typeahead().
          var fake_this = {
              $element: {},
          };
          var caret_called = false;
          fake_this.$element.caret = function () {
              caret_called = true;
              return 7;
          };
          fake_this.options = options;
          var actual_value = options.source.call(fake_this, 'test #s');
          var expected_value = stream_list;
          assert.deepEqual(actual_value, expected_value);
          assert(caret_called);
  
          // options.highlighter()
          //
          // Again, here we only verify that the highlighter has been set to
          // content_highlighter.
          fake_this = { completing: 'mention', token: 'othello' };
          actual_value = options.highlighter.call(fake_this, othello);
          expected_value = '<strong>Othello, the Moor of Venice</strong>&nbsp;&nbsp;\n<small class="autocomplete_secondary">othello@zulip.com</small>\n';
          assert.equal(actual_value, expected_value);
  
          fake_this = { completing: 'mention', token: 'hamletcharacters' };
          actual_value = options.highlighter.call(fake_this, hamletcharacters);
          expected_value = '<strong>hamletcharacters</strong>&nbsp;&nbsp;\n<small class="autocomplete_secondary">Characters of Hamlet</small>\n';
          assert.equal(actual_value, expected_value);
  
          // options.matcher()
          fake_this = { completing: 'emoji', token: 'ta' };
          assert.equal(options.matcher.call(fake_this, emoji_tada), true);
          assert.equal(options.matcher.call(fake_this, emoji_moneybag), false);
  
          fake_this = { completing: 'mention', token: 'Cord' };
          assert.equal(options.matcher.call(fake_this, cordelia), true);
          assert.equal(options.matcher.call(fake_this, othello), false);
  
          fake_this = { completing: 'mention', token: 'hamletchar' };
          assert.equal(options.matcher.call(fake_this, hamletcharacters), true);
          assert.equal(options.matcher.call(fake_this, backend), false);
  
          fake_this = { completing: 'stream', token: 'swed' };
          assert.equal(options.matcher.call(fake_this, sweden_stream), true);
          assert.equal(options.matcher.call(fake_this, denmark_stream), false);
  
          fake_this = { completing: 'syntax', token: 'py' };
          assert.equal(options.matcher.call(fake_this, 'python'), true);
          assert.equal(options.matcher.call(fake_this, 'javascript'), false);
  
          fake_this = { completing: 'non-existing-completion' };
          assert.equal(options.matcher.call(fake_this), undefined);
  
          // options.sorter()
          fake_this = { completing: 'emoji', token: 'ta' };
          actual_value = options.sorter.call(fake_this, [emoji_stadium, emoji_tada]);
          expected_value = [emoji_tada, emoji_stadium];
          assert.deepEqual(actual_value, expected_value);
  
          fake_this = { completing: 'mention', token: 'co' };
          actual_value = options.sorter.call(fake_this, [othello, cordelia]);
          expected_value = [cordelia, othello];
          assert.deepEqual(actual_value, expected_value);
  
          var hamburger = {
              email: 'coolham@zulip.com',
              user_id: 200,
              full_name: "Hamburger",
          };
          var hammer = {
              email: 'hammer@zulip.com',
              user_id: 202,
              full_name: "Apollo",
          };
          var zeus = {
              email: 'zeus@hamel.com',
              user_id: 201,
              full_name: "Zeus",
          };
  
          fake_this = { completing: 'mention', token: 'ham' };
          actual_value = options.sorter.call(fake_this, [hamletcharacters, hamburger]);
          expected_value = [hamburger, hamletcharacters];
          assert.deepEqual(actual_value, expected_value);
  
          fake_this = { completing: 'mention', token: 'ham' };
          actual_value = options.sorter.call(fake_this, [hamletcharacters, hamlet]);
          expected_value = [hamletcharacters, hamlet];
          assert.deepEqual(actual_value, expected_value);
  
          fake_this = { completing: 'mention', token: 'ham' };
          actual_value = options.sorter.call(fake_this, [hamletcharacters, backend]);
          expected_value = [hamletcharacters, backend];
          assert.deepEqual(actual_value, expected_value);
  
          fake_this = { completing: 'mention', token: 'ham' };
          actual_value = options.sorter.call(fake_this, [hamletcharacters, zeus]);
          expected_value = [hamletcharacters, zeus];
          assert.deepEqual(actual_value, expected_value);
  
          fake_this = { completing: 'mention', token: 'ham' };
          actual_value = options.sorter.call(fake_this, [hamletcharacters, hammer]);
          expected_value = [hamletcharacters, hammer];
          assert.deepEqual(actual_value, expected_value);
  
          fake_this = { completing: 'stream', token: 'de' };
          actual_value = options.sorter.call(fake_this, [sweden_stream, denmark_stream]);
          expected_value = [denmark_stream, sweden_stream];
          assert.deepEqual(actual_value, expected_value);
  
          // Matches in the descriptions affect the order as well.
          // Testing "co" for "cold", in both streams' description. It's at the
          // beginning of Sweden's description, so that one should go first.
          fake_this = { completing: 'stream', token: 'co' };
          actual_value = options.sorter.call(fake_this, [denmark_stream, sweden_stream]);
          expected_value = [sweden_stream, denmark_stream];
          assert.deepEqual(actual_value, expected_value);
  
          fake_this = { completing: 'syntax', token: 'ap' };
          actual_value = options.sorter.call(fake_this, ['abap', 'applescript']);
          expected_value = ['applescript', 'abap'];
          assert.deepEqual(actual_value, expected_value);
  
          var serbia_stream = {
              name: 'Serbia',
              description: 'Snow and cold',
              stream_id: 3,
              subscribed: false,
          };
          // Subscribed stream is active
          stream_data.is_active = function () {
              return false;
          };
          fake_this = { completing: 'stream', token: 's' };
          actual_value = options.sorter.call(fake_this, [sweden_stream, serbia_stream]);
          expected_value = [sweden_stream, serbia_stream];
          assert.deepEqual(actual_value, expected_value);
          // Subscribed stream is inactive
          stream_data.is_active = function () {
              return true;
          };
          actual_value = options.sorter.call(fake_this, [sweden_stream, serbia_stream]);
          expected_value = [sweden_stream, serbia_stream];
          assert.deepEqual(actual_value, expected_value);
  
          fake_this = { completing: 'stream', token: 'ser' };
          actual_value = options.sorter.call(fake_this, [denmark_stream, serbia_stream]);
          expected_value = [serbia_stream, denmark_stream];
          assert.deepEqual(actual_value, expected_value);
  
          fake_this = { completing: 'non-existing-completion' };
          assert.equal(options.sorter.call(fake_this), undefined);
  
          compose_textarea_typeahead_called = true;
      };
  
      var pm_recipient_blur_called = false;
      var old_pm_recipient_blur = $('#private_message_recipient').blur;
      $('#private_message_recipient').blur = function (handler) {
          if (handler) {  // The blur handler is being set.
              this.val('othello@zulip.com, ');
              handler.call(this);
              var actual_value = this.val();
              var expected_value = 'othello@zulip.com';
              assert.equal(actual_value, expected_value);
          } else {  // The element is simply losing the focus.
              old_pm_recipient_blur();
          }
          pm_recipient_blur_called = true;
      };
  
      page_params.enter_sends = false;  // We manually specify it the first
                                        // time because the click_func
                                        // doesn't exist yet.
      $("#stream").select(noop);
      $("#subject").select(noop);
      $("#private_message_recipient").select(noop);
  
      ct.initialize();
  
      // handle_keydown()
      var event = {
          keyCode: 13,
          target: {
              id: 'stream',
          },
          preventDefault: noop,
      };
  
      $('#subject').data = function () {
          return { typeahead: { shown: true }};
      };
      $('form#send_message_form').keydown(event);
  
      var stub_typeahead_hidden = function () {
          return { typeahead: { shown: false }};
      };
      $('#subject').data = stub_typeahead_hidden;
      $('#stream').data = stub_typeahead_hidden;
      $('#private_message_recipient').data = stub_typeahead_hidden;
      $('#compose-textarea').data = stub_typeahead_hidden;
      $('form#send_message_form').keydown(event);
  
      event.keyCode = undefined;
      event.which = 9;
      event.shiftKey = false;
      event.target.id = 'subject';
      $('form#send_message_form').keydown(event);
      event.target.id = 'compose-textarea';
      $('form#send_message_form').keydown(event);
      event.target.id = 'some_non_existing_id';
      $('form#send_message_form').keydown(event);
  
  
      // Setup jquery functions used in compose_textarea enter
      // handler.
      var range_length = 0;
      $('#compose-textarea').range = function () {
          return {
              length: range_length,
              range: noop,
              start: 0,
              end: 0 + range_length,
          };
      };
      $('#compose-textarea').caret = noop;
  
      event.keyCode = 13;
      event.target.id = 'subject';
      $('form#send_message_form').keydown(event);
      event.target.id = 'compose-textarea';
      page_params.enter_sends = false;
      event.metaKey = true;
      var compose_finish_called = false;
      compose.finish = function () {
          compose_finish_called = true;
      };
  
      $('form#send_message_form').keydown(event);
      assert(compose_finish_called);
      event.metaKey = false;
      event.ctrlKey = true;
      $('form#send_message_form').keydown(event);
      page_params.enter_sends = true;
      event.ctrlKey = false;
      event.altKey = true;
      $('form#send_message_form').keydown(event);
  
      // Cover case where there's a least one character there.
      range_length = 2;
      $('form#send_message_form').keydown(event);
  
      event.altKey = false;
      event.metaKey = true;
      $('form#send_message_form').keydown(event);
      event.target.id = 'private_message_recipient';
      $('form#send_message_form').keydown(event);
  
      event.keyCode = 42;
      $('form#send_message_form').keydown(event);
  
      // handle_keyup()
      event = {
          keyCode: 13,
          target: {
              id: 'stream',
          },
          preventDefault: noop,
      };
      // We execute .keydown() in order to make nextFocus !== false
      $('#subject').data = function () {
          return { typeahead: { shown: true }};
      };
      $('form#send_message_form').keydown(event);
      $('form#send_message_form').keyup(event);
      event.keyCode = undefined;
      event.which = 9;
      event.shiftKey = false;
      $('form#send_message_form').keyup(event);
      event.keyCode = 42;
      $('form#send_message_form').keyup(event);
  
      // select_on_focus()
      var focus_handler_called = false;
      var stream_one_called = false;
      $('#stream').focus = function (f) {
          // This .one() function emulates the possible infinite recursion that
          // in_handler tries to avoid.
          $('#stream').one = function (event, handler) {
              handler({ preventDefault: noop });
              f();  // This time in_handler will already be true.
              stream_one_called = true;
          };
          f();  // Here in_handler is false.
          focus_handler_called = true;
      };
  
      $("#compose-send-button").fadeOut = noop;
      $("#compose-send-button").fadeIn = noop;
      var channel_post_called = false;
      global.channel.post = function (params) {
          assert.equal(params.url, '/json/users/me/enter-sends');
          assert.equal(params.idempotent, true);
          assert.deepEqual(params.data, {enter_sends: page_params.enter_sends});
  
          channel_post_called = true;
      };
      $('#enter_sends').is = function () { return false; };
      $('#enter_sends').click();
  
      // Now we re-run both .initialize() and the click handler, this time
      // with enter_sends: page_params.enter_sends being true
      $('#enter_sends').is = function () { return true; };
      $('#enter_sends').click();
      ct.initialize();
  
      // Now let's make sure that all the stub functions have been called
      // during the initialization.
      assert(stream_typeahead_called);
      assert(subject_typeahead_called);
      assert(pm_recipient_typeahead_called);
      assert(pm_recipient_blur_called);
      assert(channel_post_called);
      assert(compose_textarea_typeahead_called);
      assert(focus_handler_called);
      assert(stream_one_called);
  }());
  
  (function test_begins_typeahead() {
  
      var begin_typehead_this = {options: {completions: {
          emoji: true, mention: true, stream: true, syntax: true}}};
  
      function assert_typeahead_equals(input, rest, reference) {
          // Usage:
          // assert_typeahead_equals('#some', reference); => '#some|'
          // assert_typeahead_equals('#some', 'thing', reference) => '#some|thing'
          // In the above examples, '|' serves as the cursor.
          if (reference === undefined) {
              reference = rest;
              rest = '';
          }
          // Stub out split_at_cursor that uses $(':focus')
          ct.split_at_cursor = function () {
              return [input, rest];
          };
          var returned = ct.compose_content_begins_typeahead.call(
              begin_typehead_this, input
          );
          assert.deepEqual(returned, reference);
      }
  
      var all_items = [
          {
              special_item_text: 'all (Notify everyone)',
              email: 'all',
              pm_recipient_count: Infinity,
              full_name: 'all',
          },
          {
              special_item_text: 'everyone (Notify everyone)',
              email: 'everyone',
              pm_recipient_count: Infinity,
              full_name: 'everyone',
          },
      ];
  
      var people_with_all = global.people.get_realm_persons().concat(all_items);
      var all_mentions = people_with_all.concat(global.user_groups.get_realm_user_groups());
      var lang_list = Object.keys(pygments_data.langs);
  
      assert_typeahead_equals("test", false);
      assert_typeahead_equals("test one two", false);
      assert_typeahead_equals("*", false);
      assert_typeahead_equals("* ", false);
      assert_typeahead_equals(" *", false);
      assert_typeahead_equals("test *", false);
  
      // Make sure that the last token is the one we read.
      assert_typeahead_equals("~~~ @zulip", all_mentions);
      assert_typeahead_equals("@zulip :ta", emoji_list);
      assert_typeahead_equals(":tada: #foo", stream_list);
      assert_typeahead_equals("#foo\n~~~py", lang_list);
  
      assert_typeahead_equals("@", false);
      assert_typeahead_equals(" @", false);
      assert_typeahead_equals("test @**o", all_mentions);
      assert_typeahead_equals("test @*o", all_mentions);
      assert_typeahead_equals("test @*h", all_mentions);
      assert_typeahead_equals("test @", false);
      assert_typeahead_equals("test no@o", false);
      assert_typeahead_equals("@ ", false);
      assert_typeahead_equals("@* ", false);
      assert_typeahead_equals("@** ", false);
      assert_typeahead_equals("test\n@i", all_mentions);
      assert_typeahead_equals("test\n @l", all_mentions);
      assert_typeahead_equals("@zuli", all_mentions);
      assert_typeahead_equals("@ zuli", false);
      assert_typeahead_equals(" @zuli", all_mentions);
      assert_typeahead_equals("test @o", all_mentions);
      assert_typeahead_equals("test @z", all_mentions);
  
      assert_typeahead_equals(":", false);
      assert_typeahead_equals(": ", false);
      assert_typeahead_equals(" :", false);
      assert_typeahead_equals(":)", false);
      assert_typeahead_equals(":4", false);
      assert_typeahead_equals("test :-P", false);
      assert_typeahead_equals("hi emoji :", false);
      assert_typeahead_equals("hi emoj:i", false);
      assert_typeahead_equals("hi emoji :D", false);
      assert_typeahead_equals("hi emoji :t", emoji_list);
      assert_typeahead_equals("hi emoji :ta", emoji_list);
      assert_typeahead_equals("hi emoji :da", emoji_list);
      assert_typeahead_equals("hi emoji :da_", emoji_list);
      assert_typeahead_equals("hi emoji :da ", emoji_list);
      assert_typeahead_equals("hi emoji\n:da", emoji_list);
      assert_typeahead_equals("hi emoji\n :ra", emoji_list);
      assert_typeahead_equals(":+", emoji_list);
      assert_typeahead_equals(":la", emoji_list);
      assert_typeahead_equals(" :lee", emoji_list);
      assert_typeahead_equals("hi :see no", emoji_list);
      assert_typeahead_equals("hi :japanese post of", emoji_list);
  
      assert_typeahead_equals("#", false);
      assert_typeahead_equals("# ", false);
      assert_typeahead_equals(" #", false);
      assert_typeahead_equals("# s", false);
      assert_typeahead_equals("test #", false);
      assert_typeahead_equals("test # a", false);
      assert_typeahead_equals("test no#o", false);
      assert_typeahead_equals("#s", stream_list);
      assert_typeahead_equals(" #s", stream_list);
      assert_typeahead_equals("test #D", stream_list);
      assert_typeahead_equals("test #**v", stream_list);
  
      assert_typeahead_equals("```", false);
      assert_typeahead_equals("``` ", false);
      assert_typeahead_equals(" ```", false);
      assert_typeahead_equals("test ```", false);
      assert_typeahead_equals("test ``` py", false);
      assert_typeahead_equals("test ```a", false);
      assert_typeahead_equals("test\n```", false);
      assert_typeahead_equals("``c", false);
      assert_typeahead_equals("```b", lang_list);
      assert_typeahead_equals("``` d", lang_list);
      assert_typeahead_equals("test\n``` p", lang_list);
      assert_typeahead_equals("test\n```  p", lang_list);
      assert_typeahead_equals("~~~", false);
      assert_typeahead_equals("~~~ ", false);
      assert_typeahead_equals(" ~~~", false);
      assert_typeahead_equals(" ~~~ g", false);
      assert_typeahead_equals("test ~~~", false);
      assert_typeahead_equals("test ~~~p", false);
      assert_typeahead_equals("test\n~~~", false);
      assert_typeahead_equals("~~~e", lang_list);
      assert_typeahead_equals("~~~ f", lang_list);
      assert_typeahead_equals("test\n~~~ p", lang_list);
      assert_typeahead_equals("test\n~~~  p", lang_list);
  
      // Following tests place the cursor before the second string
      assert_typeahead_equals("#test", "ing", false);
      assert_typeahead_equals("@test", "ing", false);
      assert_typeahead_equals(":test", "ing", false);
      assert_typeahead_equals("```test", "ing", false);
      assert_typeahead_equals("~~~test", "ing", false);
      var terminal_symbols = ',.;?!()[] "\'\n\t';
      terminal_symbols.split().forEach(symbol => {
          assert_typeahead_equals("#test", symbol, stream_list);
          assert_typeahead_equals("@test", symbol, all_mentions);
          assert_typeahead_equals(":test", symbol, emoji_list);
          assert_typeahead_equals("```test", symbol, lang_list);
          assert_typeahead_equals("~~~test", symbol, lang_list);
      });
  }());
  
  (function test_tokenizing() {
      assert.equal(ct.tokenize_compose_str("foo bar"), "");
      assert.equal(ct.tokenize_compose_str("foo#@:bar"), "");
      assert.equal(ct.tokenize_compose_str("foo bar [#alic"), "#alic");
      assert.equal(ct.tokenize_compose_str("#foo @bar [#alic"), "#alic");
      assert.equal(ct.tokenize_compose_str("foo bar #alic"), "#alic");
      assert.equal(ct.tokenize_compose_str("foo bar @alic"), "@alic");
      assert.equal(ct.tokenize_compose_str("foo bar :smil"), ":smil");
      assert.equal(ct.tokenize_compose_str(":smil"), ":smil");
      assert.equal(ct.tokenize_compose_str("foo @alice sm"), "@alice sm");
      assert.equal(ct.tokenize_compose_str("foo ```p"), "");
      assert.equal(ct.tokenize_compose_str("``` py"), "``` py");
      assert.equal(ct.tokenize_compose_str("foo``bar ~~~ py"), "");
      assert.equal(ct.tokenize_compose_str("foo ~~~why = why_not\n~~~"), "~~~");
  
      // The following cases are kinda judgment calls...
      assert.equal(ct.tokenize_compose_str(
          "foo @toomanycharactersisridiculoustocomplete"), "");
      assert.equal(ct.tokenize_compose_str("foo #streams@foo"), "#streams@foo");
  }());
  
  (function test_content_highlighter() {
      var fake_this = { completing: 'emoji' };
      var emoji = { emoji_name: 'person shrugging', emoji_url: '¯\_(ツ)_/¯' };
      var th_render_typeahead_item_called = false;
      typeahead_helper.render_emoji = function (item) {
          assert.deepEqual(item, emoji);
          th_render_typeahead_item_called = true;
      };
      ct.content_highlighter.call(fake_this, emoji);
  
      fake_this = { completing: 'mention' };
      var th_render_person_called = false;
      typeahead_helper.render_person = function (person) {
          assert.deepEqual(person, othello);
          th_render_person_called = true;
      };
      ct.content_highlighter.call(fake_this, othello);
  
      var th_render_user_group_called = false;
      typeahead_helper.render_user_group = function (user_group) {
          assert.deepEqual(user_group, backend);
          th_render_user_group_called = true;
      };
      ct.content_highlighter.call(fake_this, backend);
  
      fake_this = { completing: 'stream' };
      var th_render_stream_called = false;
      typeahead_helper.render_stream = function (stream) {
          assert.deepEqual(stream, denmark_stream);
          th_render_stream_called = true;
      };
      ct.content_highlighter.call(fake_this, denmark_stream);
  
      fake_this = { completing: 'syntax' };
      th_render_typeahead_item_called = false;
      typeahead_helper.render_typeahead_item = function (item) {
          assert.deepEqual(item, { primary: 'py' });
          th_render_typeahead_item_called = true;
      };
      ct.content_highlighter.call(fake_this, 'py');
  
      fake_this = { completing: 'something-else' };
      assert(!ct.content_highlighter.call(fake_this));
  
      // Verify that all stub functions have been called.
      assert(th_render_typeahead_item_called);
      assert(th_render_person_called);
      assert(th_render_user_group_called);
      assert(th_render_stream_called);
      assert(th_render_typeahead_item_called);
  }());
  
  (function test_typeahead_results() {
      var all_items = [
          {
              special_item_text: 'all (Notify everyone)',
              email: 'all',
              pm_recipient_count: Infinity,
              full_name: 'all',
          },
          {
              special_item_text: 'everyone (Notify everyone)',
              email: 'everyone',
              pm_recipient_count: Infinity,
              full_name: 'everyone',
          },
      ];
      var people_with_all = global.people.get_realm_persons().concat(all_items);
      var all_mentions = people_with_all.concat(global.user_groups.get_realm_user_groups());
      var stream_list = [denmark_stream, sweden_stream, netherland_stream];
  
      function compose_typeahead_results(completing, items, token) {
          // items -> emoji array, token -> simulates text in input
          var matcher = ct.compose_content_matcher.bind({completing: completing, token: token});
          var sorter = ct.compose_matches_sorter.bind({completing: completing, token: token});
          var matches = [];
          _.each(items, function (item) {
              if (matcher(item)) {
                  matches.push(item);
              }
          });
          var sorted_matches = sorter(matches);
          return sorted_matches;
      }
  
      function assert_emoji_matches(input, expected) {
          var returned = compose_typeahead_results('emoji', emoji_list, input);
          assert.deepEqual(returned, expected);
      }
      function assert_mentions_matches(input, expected) {
          var returned = compose_typeahead_results('mention', all_mentions, input);
          assert.deepEqual(returned, expected);
      }
      function assert_stream_matches(input, expected) {
          var returned = compose_typeahead_results('stream', stream_list, input);
          assert.deepEqual(returned, expected);
      }
  
      assert_emoji_matches('da',[{emoji_name: "tada", emoji_url: "TBD"},
          {emoji_name: "panda_face", emoji_url: "TBD"}]);
      assert_emoji_matches('da_', []);
      assert_emoji_matches('da ', []);
      assert_emoji_matches('panda ', [{emoji_name: "panda_face", emoji_url: "TBD"}]);
      assert_emoji_matches('panda_', [{emoji_name: "panda_face", emoji_url: "TBD"}]);
      assert_emoji_matches('japanese_post_', [{emoji_name: "japanese_post_office", emoji_url: "TBD"}]);
      assert_emoji_matches('japanese post ', [{emoji_name: "japanese_post_office", emoji_url: "TBD"}]);
      assert_emoji_matches('notaemoji', []);
      // Autocomplete user mentions by user name.
      assert_mentions_matches('cordelia', [cordelia]);
      assert_mentions_matches('cordelia le', [cordelia]);
      assert_mentions_matches('cordelia le ', []);
      assert_mentions_matches('King ', [hamlet, lear]);
      assert_mentions_matches('King H', [hamlet]);
      assert_mentions_matches('King L', [lear]);
      assert_mentions_matches('delia lear', []);
      // Autocomplete user group mentions by group name.
      assert_mentions_matches('hamletchar', [hamletcharacters]);
      // Autocomplete user group mentions by group descriptions.
      assert_mentions_matches('characters ', [hamletcharacters]);
      assert_mentions_matches('characters of ', [hamletcharacters]);
      assert_mentions_matches('characters o ', []);
      assert_mentions_matches('haracters of hamlet', []);
      assert_mentions_matches('of hamlet', []);
      // Autocomplete stream by stream name or stream description.
      assert_stream_matches('den', [denmark_stream, sweden_stream]);
      assert_stream_matches('denmark', [denmark_stream]);
      assert_stream_matches('denmark ', []);
      assert_stream_matches('den ', []);
      assert_stream_matches('cold', [sweden_stream, denmark_stream]);
      assert_stream_matches('the ', [netherland_stream]);
      assert_stream_matches('city', [netherland_stream]);
  }());
  