Created
April 2, 2026 03:47
-
-
Save recca0120/57f2e12d7e21a6f79af31e9ed0982edb to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { segments as s } from '@code-quest/summoner/test'; | |
| import { render, screen } from '@testing-library/react'; | |
| import userEvent from '@testing-library/user-event'; | |
| import { useEffect, useRef } from 'react'; | |
| import { describe, expect, it } from 'vitest'; | |
| import { createFakeClaude } from '../../test/fake-claude'; | |
| import { useChannelCompose } from '../channel'; | |
| import { ChannelProvider } from '../channel/ChannelContext'; | |
| import { PluginProvider } from '../PluginContext'; | |
| import { SessionProvider } from '../SessionContext'; | |
| import { SocketProvider } from '../SocketContext'; | |
| import { TabProvider } from '../TabContext'; | |
| /** Renders a test harness that exposes compose context via UI */ | |
| function ComposeTestUI() { | |
| const compose = useChannelCompose(); | |
| const textareaRef = useRef<HTMLTextAreaElement>(null); | |
| useEffect(() => { | |
| compose.registerFocus((pos?: number) => { | |
| const el = textareaRef.current; | |
| if (!el) return; | |
| if (pos !== undefined) { | |
| setTimeout(() => { | |
| el.focus(); | |
| el.setSelectionRange(pos, pos); | |
| }, 0); | |
| } else { | |
| el.focus(); | |
| } | |
| }); | |
| }, [compose.registerFocus]); | |
| return ( | |
| <div> | |
| <textarea | |
| ref={textareaRef} | |
| value={compose.value} | |
| onChange={(e) => { | |
| const pos = e.target.selectionStart; | |
| compose.updateValue(e.target.value, pos); | |
| }} | |
| placeholder="compose" | |
| /> | |
| <span data-testid="hasText">{String(compose.hasText)}</span> | |
| <span data-testid="slashFilter">{compose.slashFilter ?? 'null'}</span> | |
| <span data-testid="hasTextBeforeSlash">{String(compose.hasTextBeforeSlash)}</span> | |
| <button type="button" onClick={compose.submit}> | |
| Submit | |
| </button> | |
| <button type="button" onClick={compose.focusTextarea}> | |
| Focus | |
| </button> | |
| <button type="button" onClick={compose.mentionFile}> | |
| Mention | |
| </button> | |
| <button type="button" onClick={() => compose.addAttachments([new File([''], 'test.txt')])}> | |
| Attach | |
| </button> | |
| <button type="button" onClick={() => compose.insertSlashCommand('/compact ')}> | |
| Insert | |
| </button> | |
| <button type="button" onClick={() => compose.executeSlashCommand('/compact')}> | |
| Execute | |
| </button> | |
| <button type="button" onClick={compose.closeSlash}> | |
| CloseSlash | |
| </button> | |
| {compose.attachedFiles.map((f, i) => ( | |
| <span key={f.name}> | |
| {f.name} | |
| <button type="button" onClick={() => compose.removeAttachment(i)}> | |
| Remove | |
| </button> | |
| </span> | |
| ))} | |
| </div> | |
| ); | |
| } | |
| async function setup() { | |
| const claude = createFakeClaude(); | |
| const channelId = await claude.initialize(s.init('sess-1')); | |
| const result = render( | |
| <SocketProvider socket={claude.socket}> | |
| <SessionProvider> | |
| <PluginProvider> | |
| <TabProvider> | |
| <ChannelProvider channelId={channelId}> | |
| <ComposeTestUI /> | |
| </ChannelProvider> | |
| </TabProvider> | |
| </PluginProvider> | |
| </SessionProvider> | |
| </SocketProvider>, | |
| ); | |
| return { claude, ...result }; | |
| } | |
| describe('ChannelComposeProvider', () => { | |
| it('provides initial empty state', async () => { | |
| await setup(); | |
| expect(screen.getByPlaceholderText('compose')).toHaveValue(''); | |
| expect(screen.getByTestId('hasText')).toHaveTextContent('false'); | |
| expect(screen.getByTestId('slashFilter')).toHaveTextContent('null'); | |
| }); | |
| it('hasText reflects textarea value', async () => { | |
| await setup(); | |
| await userEvent.type(screen.getByPlaceholderText('compose'), 'hello'); | |
| expect(screen.getByTestId('hasText')).toHaveTextContent('true'); | |
| }); | |
| it('submit clears value after sending', async () => { | |
| await setup(); | |
| await userEvent.type(screen.getByPlaceholderText('compose'), 'hello'); | |
| await userEvent.click(screen.getByText('Submit')); | |
| expect(screen.getByPlaceholderText('compose')).toHaveValue(''); | |
| }); | |
| it('submit does nothing when value is empty', async () => { | |
| await setup(); | |
| const textarea = screen.getByPlaceholderText('compose'); | |
| expect(textarea).toHaveValue(''); | |
| await userEvent.click(screen.getByText('Submit')); | |
| // Still empty, no error thrown | |
| expect(textarea).toHaveValue(''); | |
| }); | |
| it('focusTextarea focuses the textarea', async () => { | |
| await setup(); | |
| await userEvent.click(screen.getByText('Focus')); | |
| expect(screen.getByPlaceholderText('compose')).toHaveFocus(); | |
| }); | |
| it('mentionFile inserts @ at cursor position', async () => { | |
| await setup(); | |
| await userEvent.type(screen.getByPlaceholderText('compose'), 'hello '); | |
| await userEvent.click(screen.getByText('Mention')); | |
| expect(screen.getByPlaceholderText('compose')).toHaveValue('hello @'); | |
| }); | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment