mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-16 14:32:34 +00:00
Bring apps/desktop and ui-tui to a clean state for typecheck, eslint,
and prettier:
- Run prettier across both trees (printWidth/wrap drift; prettier is not
CI-enforced for these JS projects, so main had accumulated drift).
- Apply eslint --fix for padding-line-between-statements and perfectionist
import/export sorting.
- Manual fixes for non-auto-fixable rules:
- remove unused node:net import in electron/main.cjs (uses Electron net)
- replace inline `typeof import(...)` annotations with top-level
`import type * as EnvModule` in two ui-tui test files
- scoped eslint-disable no-control-regex on intentional sentinel/ANSI
regexes (mathUnicode.ts, text.ts)
- resolve react-hooks/exhaustive-deps per-case: correct swapped/missing
deps, collapse redundant session.* members, and justified disables on
settings mount-only data-load effects to preserve run-once behavior
No behavior changes; test pass/fail counts are unchanged from the main
baseline.
109 lines
3.8 KiB
TypeScript
109 lines
3.8 KiB
TypeScript
import { afterEach, describe, expect, it } from 'vitest'
|
|
|
|
import {
|
|
$composerAttachments,
|
|
addComposerAttachment,
|
|
clearSessionDraft,
|
|
type ComposerAttachment,
|
|
removeComposerAttachment,
|
|
SESSION_DRAFTS_STORAGE_KEY,
|
|
stashSessionDraft,
|
|
takeSessionDraft,
|
|
updateComposerAttachment
|
|
} from './composer'
|
|
|
|
function attachment(overrides: Partial<ComposerAttachment> & Pick<ComposerAttachment, 'id'>): ComposerAttachment {
|
|
return { kind: 'file', label: 'doc.pdf', ...overrides }
|
|
}
|
|
|
|
describe('updateComposerAttachment', () => {
|
|
afterEach(() => {
|
|
$composerAttachments.set([])
|
|
})
|
|
|
|
it('replaces an existing attachment in place', () => {
|
|
addComposerAttachment(attachment({ id: 'file:a', uploadState: 'uploading' }))
|
|
|
|
const updated = updateComposerAttachment(attachment({ id: 'file:a', attachedSessionId: 'sess-1' }))
|
|
|
|
expect(updated).toBe(true)
|
|
const current = $composerAttachments.get()
|
|
expect(current).toHaveLength(1)
|
|
expect(current[0]?.attachedSessionId).toBe('sess-1')
|
|
expect(current[0]?.uploadState).toBeUndefined()
|
|
})
|
|
|
|
it('does NOT resurrect an attachment the user removed mid-upload', () => {
|
|
// Drop → eager upload starts → user removes the chip → upload resolves.
|
|
// The late success must not re-add the removed attachment.
|
|
addComposerAttachment(attachment({ id: 'file:a', uploadState: 'uploading' }))
|
|
removeComposerAttachment('file:a')
|
|
|
|
const updated = updateComposerAttachment(attachment({ id: 'file:a', attachedSessionId: 'sess-1' }))
|
|
|
|
expect(updated).toBe(false)
|
|
expect($composerAttachments.get()).toHaveLength(0)
|
|
})
|
|
})
|
|
|
|
describe('session drafts', () => {
|
|
afterEach(() => {
|
|
for (const scope of ['session-a', 'session-b', null]) {
|
|
clearSessionDraft(scope)
|
|
}
|
|
|
|
window.localStorage.clear()
|
|
})
|
|
|
|
it('keeps drafts isolated per session scope', () => {
|
|
stashSessionDraft('session-a', 'draft a', [])
|
|
stashSessionDraft('session-b', 'draft b', [attachment({ id: 'image:b', kind: 'image' })])
|
|
|
|
expect(takeSessionDraft('session-a')).toEqual({ attachments: [], text: 'draft a' })
|
|
expect(takeSessionDraft('session-b').text).toBe('draft b')
|
|
expect(takeSessionDraft('session-b').attachments.map(a => a.id)).toEqual(['image:b'])
|
|
})
|
|
|
|
it('scopes the unsaved new-session draft separately from real sessions', () => {
|
|
stashSessionDraft(null, 'new chat draft', [])
|
|
stashSessionDraft('session-a', 'session draft', [])
|
|
|
|
expect(takeSessionDraft(null).text).toBe('new chat draft')
|
|
expect(takeSessionDraft(undefined).text).toBe('new chat draft')
|
|
expect(takeSessionDraft('session-a').text).toBe('session draft')
|
|
})
|
|
|
|
it('persists draft text (not attachments) to localStorage', () => {
|
|
stashSessionDraft('session-a', 'survives reload', [attachment({ id: 'file:a' })])
|
|
|
|
const persisted = JSON.parse(window.localStorage.getItem(SESSION_DRAFTS_STORAGE_KEY) ?? '{}') as Record<
|
|
string,
|
|
string
|
|
>
|
|
|
|
expect(persisted['session-a']).toBe('survives reload')
|
|
})
|
|
|
|
it('evicts empty drafts instead of leaving stale entries behind', () => {
|
|
stashSessionDraft('session-a', 'saved', [])
|
|
stashSessionDraft('session-a', ' ', [])
|
|
|
|
expect(takeSessionDraft('session-a')).toEqual({ attachments: [], text: '' })
|
|
})
|
|
|
|
it('clears a stashed draft after an accepted submit', () => {
|
|
stashSessionDraft('session-a', 'sent prompt', [attachment({ id: 'file:a' })])
|
|
clearSessionDraft('session-a')
|
|
|
|
expect(takeSessionDraft('session-a')).toEqual({ attachments: [], text: '' })
|
|
})
|
|
|
|
it('returns clones so callers cannot mutate the stash', () => {
|
|
stashSessionDraft('session-a', 'draft', [attachment({ id: 'file:a' })])
|
|
|
|
const taken = takeSessionDraft('session-a')
|
|
taken.attachments[0]!.label = 'mutated'
|
|
|
|
expect(takeSessionDraft('session-a').attachments[0]?.label).toBe('doc.pdf')
|
|
})
|
|
})
|