openSlackChannelFromTask.test.ts4.1 KBView on GitHub /**
* The one definition of "open this Slack task's channel".
*
* Two things about it are easy to get wrong and impossible to see afterwards:
*
* 1. WHICH composer the draft lands in. There are two Slack composers, fed by two different
* mechanisms — the per-deal timeline one takes a ProseMirror doc, the unibox's
* `ChannelThreadView` takes raw mrkdwn through `pending-channel-draft`. This path opens the
* unibox one, so seeding the deal's put the message somewhere the user was never sent and
* left the channel that DID open with an empty box. Nothing errors; the draft is just gone.
* 2. Whether a SENT message is seeded again. Sending completes the task but leaves the text on
* `taskActionData`, so a done task re-seeded an already-posted message — one Enter from a
* duplicate post.
*/
import { openSlackChannelFromTask } from '@/modules/userTasks/utils/open-slack-channel-from-task';
import { seedChannelDraft } from '@/modules/inbox/store/pending-channel-draft';
import { seedComposerDoc } from '@/modules/conversations/components/timeline/composer/useComposerDraft';
import { useCedarStore } from '@/modules/store';
jest.mock('@/modules/inbox/store/pending-channel-draft', () => ({
seedChannelDraft: jest.fn(),
}));
// The OTHER Slack composer — the per-deal timeline one. Mocked purely so the test below can
// assert it is never reached; the two are chosen between by which surface actually opens.
jest.mock('@/modules/conversations/components/timeline/composer/useComposerDraft', () => ({
seedComposerDoc: jest.fn(),
requestComposerOpen: jest.fn(),
}));
const TARGET = {
conversationId: 'conv-1',
channelId: 'C0BNUT9RD8X',
channelName: 'cedar-payrollintegrations',
workspaceId: 'T08AHSLJBTL',
message: 'Recap of our call…',
};
beforeEach(() => {
jest.clearAllMocks();
useCedarStore.getState().setSlackChannelHint(null);
useCedarStore.getState().setSelectedArtifact(null);
});
describe('openSlackChannelFromTask', () => {
it('seeds the UNIBOX channel composer, keyed by the container the view opens', () => {
openSlackChannelFromTask(TARGET);
expect(seedChannelDraft).toHaveBeenCalledWith('C0BNUT9RD8X', 'Recap of our call…');
});
it('opens the channel through the display artifact, as an email task opens its thread', () => {
openSlackChannelFromTask(TARGET);
expect(useCedarStore.getState().getDisplayArtifact()).toMatchObject({
kind: 'slack_thread',
id: 'C0BNUT9RD8X',
});
expect(useCedarStore.getState().isChannelChatOpen).toBe(true);
});
it('leaves the workspace behind — the channel cannot be READ without it', () => {
// `inbox.slackChannelMessages` is keyed by workspace AND channel, so a hint carrying only the
// channel id renders a header over an empty thread.
openSlackChannelFromTask(TARGET);
expect(useCedarStore.getState().slackChannelHint).toEqual({
channelId: 'C0BNUT9RD8X',
workspaceId: 'T08AHSLJBTL',
channelName: 'cedar-payrollintegrations',
conversationId: 'conv-1',
});
});
it('does not re-seed a message that is already on Slack', () => {
openSlackChannelFromTask({ ...TARGET, alreadySent: true });
expect(seedChannelDraft).not.toHaveBeenCalled();
// It still OPENS — a sent Slack task's channel is exactly where you want to land.
expect(useCedarStore.getState().getDisplayArtifact()).toMatchObject({ kind: 'slack_thread' });
});
it('opens a channel the task drafted nothing for, without seeding an empty draft', () => {
openSlackChannelFromTask({ ...TARGET, message: undefined });
expect(seedChannelDraft).not.toHaveBeenCalled();
expect(useCedarStore.getState().isChannelChatOpen).toBe(true);
});
it('never seeds the per-deal timeline composer instead', () => {
// The regression this file exists for. Both composers accept a Slack draft and neither
// errors on the wrong one, so seeding the deal's silently produced a channel with an empty
// box and a draft sitting in a surface the click never navigated to.
openSlackChannelFromTask(TARGET);
expect(seedComposerDoc).not.toHaveBeenCalled();
});
});