hotkeyTargets.test.ts2.7 KBView on GitHub
/**
 * Which driver a keyboard target goes to.
 *
 * The failure this guards is silent and destructive-looking: `x`, `u`, `i`, `h` and `#` all
 * resolve one target set, and handing a chat row to the Gmail driver issues a modify for a
 * thread id Gmail never had. The lookup alone cannot decide it — selection and hover state
 * outlive a feed replacement, so "the feed does not know this id" happens in normal use.
 */
import { splitHotkeyTargets, isChannelSelectionId } from '@/modules/inbox/lib/hotkey-targets';
import type { InboxItem } from '@/modules/inbox/types';

const item = (id: string, channel: InboxItem['channel']): InboxItem =>
  ({ id, channel }) as InboxItem;

/** A feed holding one email row and one LinkedIn row. */
const feed = new Map<string, InboxItem>([
  ['18ab2f9c1d', item('18ab2f9c1d', 'email')],
  ['li:chat-1', item('li:chat-1', 'linkedin')],
]);
const lookup = (id: string) => feed.get(id);

describe('splitHotkeyTargets', () => {
  it('sends a resolved email row to the email half', () => {
    expect(splitHotkeyTargets(['18ab2f9c1d'], lookup)).toMatchObject({
      emailIds: ['18ab2f9c1d'],
      channelItems: [],
    });
  });

  it('sends a resolved chat row to the channel half', () => {
    const out = splitHotkeyTargets(['li:chat-1'], lookup);
    expect(out.emailIds).toEqual([]);
    expect(out.channelItems.map((i) => i.id)).toEqual(['li:chat-1']);
  });

  it.each(['li:gone', 'wa:gone', 'slack:ws:chan'])(
    'never hands an unresolved %s to Gmail',
    (id) => {
      // The row left the loaded feed but the selection survived. Dropping it is the only
      // safe answer: there is no item to act on, and Gmail has no such thread.
      expect(splitHotkeyTargets([id], lookup)).toMatchObject({
        ids: [id],
        emailIds: [],
        channelItems: [],
      });
    },
  );

  it('still treats an unresolved BARE id as email', () => {
    // The non-unibox case: nothing is in the feed and every id is a Gmail thread id.
    expect(splitHotkeyTargets(['18ab2f9c1d', 'ffee00'], () => undefined)).toMatchObject({
      emailIds: ['18ab2f9c1d', 'ffee00'],
      channelItems: [],
    });
  });

  it('keeps `ids` as the full set in list order, whatever each one turned out to be', () => {
    const out = splitHotkeyTargets(['18ab2f9c1d', 'li:gone', 'li:chat-1'], lookup);
    expect(out.ids).toEqual(['18ab2f9c1d', 'li:gone', 'li:chat-1']);
  });
});

describe('isChannelSelectionId', () => {
  it.each(['li:chat-1', 'wa:chat-2', 'slack:ws:chan:1700.1'])('is true for %s', (id) => {
    expect(isChannelSelectionId(id)).toBe(true);
  });

  it.each(['18ab2f9c1d', 'ffee00', 'lithium', 'wander'])('is false for %s', (id) => {
    expect(isChannelSelectionId(id)).toBe(false);
  });
});