mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
test: repair behavioral coverage and remove export inventory (#33246)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
@@ -30,12 +30,14 @@ const fruit = {
|
||||
|
||||
let tree: ReactTestRenderer
|
||||
|
||||
function render(prompt: AskPrompt, onAnswer = vi.fn(async () => true)): typeof onAnswer {
|
||||
function render(
|
||||
prompt: AskPrompt,
|
||||
onAnswer = vi.fn(async () => true),
|
||||
onCancel = vi.fn(async () => true)
|
||||
): typeof onAnswer {
|
||||
vi.useFakeTimers()
|
||||
act(() => {
|
||||
tree = create(
|
||||
createElement(MobileNativeChatAsk, { prompt, onAnswer, onCancel: async () => true })
|
||||
)
|
||||
tree = create(createElement(MobileNativeChatAsk, { prompt, onAnswer, onCancel }))
|
||||
})
|
||||
return onAnswer
|
||||
}
|
||||
@@ -45,6 +47,15 @@ function press(label: string): void {
|
||||
act(() => row.props.onPress())
|
||||
}
|
||||
|
||||
async function submit(): Promise<void> {
|
||||
const button = tree.root.find(
|
||||
(node) =>
|
||||
isHost(node, 'Pressable') && node.findAll((n) => n.props.children === 'Submit').length > 0
|
||||
)
|
||||
expect(button.props.disabled).toBe(false)
|
||||
await act(async () => button.props.onPress())
|
||||
}
|
||||
|
||||
async function passAutoAdvanceBeat(): Promise<void> {
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(NATIVE_CHAT_QUESTION_AUTO_ADVANCE_MS)
|
||||
@@ -84,6 +95,12 @@ describe('MobileNativeChatAsk single-select auto-advance', () => {
|
||||
await passAutoAdvanceBeat()
|
||||
|
||||
expect(onAnswer).not.toHaveBeenCalled()
|
||||
const input = tree.root.find((node) => isHost(node, 'TextInput'))
|
||||
act(() => input.props.onChangeText(' custom answer '))
|
||||
await passAutoAdvanceBeat()
|
||||
expect(onAnswer).not.toHaveBeenCalled()
|
||||
await submit()
|
||||
expect(onAnswer).toHaveBeenCalledExactlyOnceWith([{ indices: [], other: 'custom answer' }])
|
||||
})
|
||||
|
||||
it('waits for Submit on a multi-select question', async () => {
|
||||
@@ -93,6 +110,8 @@ describe('MobileNativeChatAsk single-select auto-advance', () => {
|
||||
await passAutoAdvanceBeat()
|
||||
|
||||
expect(onAnswer).not.toHaveBeenCalled()
|
||||
await submit()
|
||||
expect(onAnswer).toHaveBeenCalledExactlyOnceWith([{ indices: [0] }])
|
||||
})
|
||||
|
||||
it('holds its choices while an answer is being delivered', async () => {
|
||||
@@ -113,7 +132,12 @@ describe('MobileNativeChatAsk single-select auto-advance', () => {
|
||||
})
|
||||
|
||||
it('sends nothing when the question is cancelled before the pick moves on', async () => {
|
||||
const onAnswer = render({ questions: [indent] })
|
||||
const onCancel = vi.fn(async () => true)
|
||||
const onAnswer = render(
|
||||
{ questions: [indent] },
|
||||
vi.fn(async () => true),
|
||||
onCancel
|
||||
)
|
||||
|
||||
press('Spaces')
|
||||
const cancel = tree.root.find(
|
||||
@@ -121,6 +145,7 @@ describe('MobileNativeChatAsk single-select auto-advance', () => {
|
||||
isHost(node, 'Pressable') && node.findAll((n) => n.props.children === 'Cancel').length > 0
|
||||
)
|
||||
await act(async () => cancel.props.onPress())
|
||||
expect(onCancel).toHaveBeenCalledExactlyOnceWith()
|
||||
await passAutoAdvanceBeat()
|
||||
|
||||
expect(onAnswer).not.toHaveBeenCalled()
|
||||
|
||||
@@ -40,6 +40,7 @@ type Tick = {
|
||||
structured?: boolean
|
||||
activeTabId?: string | null
|
||||
setTabView?: (tabId: string, view: 'terminal') => void
|
||||
attachImage?: (source: 'library' | 'camera') => Promise<void>
|
||||
}
|
||||
|
||||
function overlayElement(tick: Tick): ReturnType<typeof createElement> {
|
||||
@@ -77,7 +78,14 @@ function overlayElement(tick: Tick): ReturnType<typeof createElement> {
|
||||
return createElement(MobileNativeChatOverlay, {
|
||||
controller,
|
||||
activeSessionTabId: tick.activeTabId,
|
||||
images: {} as never,
|
||||
onOpenFile: vi.fn(),
|
||||
images: {
|
||||
attachments: [],
|
||||
isAttaching: false,
|
||||
attachImage: tick.attachImage ?? vi.fn(async () => {}),
|
||||
removeAttachment: vi.fn(),
|
||||
sendNativeChat: vi.fn(async () => true)
|
||||
},
|
||||
onMicPress: vi.fn(),
|
||||
micActive: false,
|
||||
dictationMode: 'toggle',
|
||||
@@ -277,10 +285,17 @@ describe('MobileNativeChatOverlay attach button', () => {
|
||||
)
|
||||
|
||||
it.each([true, false])('offers attach only when the agent takes images (%s)', async (accepts) => {
|
||||
const attachImage = vi.fn(async () => {})
|
||||
await act(async () => {
|
||||
renderer = create(overlayElement({ acceptsImages: accepts }))
|
||||
renderer = create(overlayElement({ acceptsImages: accepts, attachImage }))
|
||||
})
|
||||
const view = renderer!.root.find((node) => node.type === 'ChatView')
|
||||
expect(typeof view.props.onAttachImage === 'function').toBe(accepts)
|
||||
if (accepts) {
|
||||
await act(async () => view.props.onAttachImage())
|
||||
expect(attachImage).toHaveBeenCalledExactlyOnceWith('library')
|
||||
} else {
|
||||
expect(attachImage).not.toHaveBeenCalled()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,7 +3,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { markRpcDeliveryUnknown } from '../transport/rpc-delivery-ambiguity'
|
||||
import type { RpcResponse } from '../transport/types'
|
||||
import type { RpcClient } from '../transport/rpc-client'
|
||||
import { buildAskAnswerKeys, type AskPrompt } from '../../../src/shared/native-chat-ask'
|
||||
import type { AskPrompt } from '../../../src/shared/native-chat-ask'
|
||||
import {
|
||||
visible,
|
||||
handleRef,
|
||||
@@ -103,7 +103,6 @@ describe('prompt cards through the production controller, send contract and view
|
||||
questions: [{ question: 'Answer?', options: [{ label: 'A' }], multiSelect: false }]
|
||||
}
|
||||
const selections = [{ indices: [], other: 'custom answer' }]
|
||||
const groups = buildAskAnswerKeys(prompt, selections)
|
||||
vi.mocked(client.sendRequest).mockResolvedValue(response(true, 'legacy'))
|
||||
await render({
|
||||
tab: {
|
||||
@@ -129,9 +128,9 @@ describe('prompt cards through the production controller, send contract and view
|
||||
.mock.calls.filter(([method]) => method === 'terminal.send')
|
||||
.map(([, params]) => params)
|
||||
expect(writes).toEqual(
|
||||
groups.map((group) =>
|
||||
['2', 'custom answer', '\r'].map((text) =>
|
||||
expect.objectContaining({
|
||||
text: 'raw' in group ? group.raw : group.text,
|
||||
text,
|
||||
enter: false,
|
||||
requireWriteSettlement: true
|
||||
})
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { AgentJournalRenderItem } from '../../../src/shared/agent-session-journal-types'
|
||||
import {
|
||||
projectStructuredQuestion,
|
||||
type StructuredQuestionItem
|
||||
@@ -11,6 +10,8 @@ function groupedPrompt(): StructuredQuestionItem {
|
||||
return {
|
||||
itemId: 'item-1',
|
||||
revision: 1,
|
||||
sequence: 1,
|
||||
observedAt: 1,
|
||||
body: {
|
||||
kind: 'question',
|
||||
question: '2 grouped questions from Claude',
|
||||
@@ -31,16 +32,16 @@ function groupedPrompt(): StructuredQuestionItem {
|
||||
freeTextQuestionId: 'q2'
|
||||
}
|
||||
],
|
||||
resolution: { state: 'pending' }
|
||||
resolution: { state: 'pending', selectedOptionId: null, resolvedBy: null, resolvedAt: null }
|
||||
}
|
||||
} as unknown as AgentJournalRenderItem as StructuredQuestionItem
|
||||
}
|
||||
}
|
||||
|
||||
describe('structured question projection for grouped Claude prompts', () => {
|
||||
it('renders an answerable question instead of the empty placeholder card', () => {
|
||||
const projected = projectStructuredQuestion(groupedPrompt())
|
||||
|
||||
expect(projected?.question).not.toBe('2 grouped questions from Claude')
|
||||
expect(projected?.question).toBe('Which database? (1 of 2)')
|
||||
expect(projected?.options).toEqual(['Postgres'])
|
||||
expect(projected?.optionDescriptions).toEqual(['Durable server'])
|
||||
expect(projected?.optionTokens.filter(Boolean)).toHaveLength(1)
|
||||
|
||||
@@ -63,6 +63,7 @@ describe('useMobileStructuredNativeChatSendBridge', () => {
|
||||
await expect(sendWithOutcome('/review')).resolves.toBe(outcome)
|
||||
|
||||
expect(sendStructured).toHaveBeenCalledWith('/review')
|
||||
expect(clearDraftForSend).toHaveBeenCalledExactlyOnceWith(ORIGIN, '/review')
|
||||
expect(restoreRejectedDraft).not.toHaveBeenCalled()
|
||||
}
|
||||
)
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
EMPTY_STRUCTURED_AGENT_SESSION,
|
||||
type StructuredAgentSessionState
|
||||
} from '../../../src/shared/structured-agent-session-reducer'
|
||||
import { encodeAgentSessionQuestionAnswers } from '../../../src/shared/agent-session-question-answer'
|
||||
import { formatQuestionFreeTextAnswer } from './mobile-native-chat-question'
|
||||
import {
|
||||
projectStructuredQuestion,
|
||||
@@ -352,7 +351,12 @@ describe('useMobileStructuredPromptResponses', () => {
|
||||
expect(sentFields(sent)).toEqual({
|
||||
itemId: 'item-g',
|
||||
expectedRevision: 1,
|
||||
...(supported ? { answers } : { optionId: encodeAgentSessionQuestionAnswers(answers) })
|
||||
...(supported
|
||||
? { answers }
|
||||
: {
|
||||
optionId:
|
||||
'question-group:[{"questionId":"q1","optionIds":["q1:choice-1"]},{"questionId":"q2","optionIds":["q2:choice-1"]}]'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { createElement } from 'react'
|
||||
import { act, create, type ReactTestRenderer } from 'react-test-renderer'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { AgentSessionQueuedMessage } from '../../../src/shared/agent-session-wire'
|
||||
import type {
|
||||
AgentSessionQueuedMessage,
|
||||
AgentSessionQueuedMessageEditHoldResult
|
||||
} from '../../../src/shared/agent-session-wire'
|
||||
import { agentSessionSendBodyFingerprint } from '../../../src/shared/structured-agent-session-send-mutation'
|
||||
import { queuedMessageWithEdit } from '../../../src/shared/queued-message-edit'
|
||||
import type { RpcClient } from '../transport/rpc-client'
|
||||
@@ -27,6 +30,20 @@ function withText(body: AgentSessionQueuedMessage['body'], text: string) {
|
||||
return edited.body
|
||||
}
|
||||
|
||||
function editHoldReply(params: unknown): RpcResponse {
|
||||
const result: AgentSessionQueuedMessageEditHoldResult =
|
||||
fieldsOf(params).action === 'release'
|
||||
? { status: 'released' }
|
||||
: { status: 'held', fingerprint: 'fp', leaseDurationMs: 120_000, remainingMs: 120_000 }
|
||||
return ok(result)
|
||||
}
|
||||
|
||||
function readOrHoldReply(method: string, params: unknown): RpcResponse {
|
||||
return method === 'agentSession.queuedMessageEditHold'
|
||||
? editHoldReply(params)
|
||||
: ok({ models: [], current: {} })
|
||||
}
|
||||
|
||||
vi.mock('@react-native-async-storage/async-storage', () => ({
|
||||
default: { getItem: async () => null, setItem: async () => {}, removeItem: async () => {} }
|
||||
}))
|
||||
@@ -89,12 +106,10 @@ describe('mobile in-place queued Edit', () => {
|
||||
}
|
||||
async function mount(message: AgentSessionQueuedMessage, editSupport: boolean | null = true) {
|
||||
vi.clearAllMocks()
|
||||
sendRequest.mockImplementation(async (method) =>
|
||||
method === 'agentSession.queuedMessageEditHold'
|
||||
? ok({ status: 'held', fingerprint: 'fp', leaseDurationMs: 120_000, remainingMs: 120_000 })
|
||||
: method === 'agentSession.queuedMessageUpdate'
|
||||
? mutationOk({ status: 'updated', messageId: message.messageId, fingerprint: 'new' })
|
||||
: ok({ models: [], current: {} })
|
||||
sendRequest.mockImplementation(async (method, params) =>
|
||||
method === 'agentSession.queuedMessageUpdate'
|
||||
? mutationOk({ status: 'updated', messageId: message.messageId, fingerprint: 'new' })
|
||||
: readOrHoldReply(method, params)
|
||||
)
|
||||
await act(async () => {
|
||||
renderer = create(createElement(Harness, { editSupport }))
|
||||
@@ -125,7 +140,7 @@ describe('mobile in-place queued Edit', () => {
|
||||
expect(hook!.queued.edit).toEqual(expect.any(Function))
|
||||
})
|
||||
|
||||
it('holds and renews a card while typing, saves its text without replacing images, and cancels in place', async () => {
|
||||
it('holds and renews a card while typing, sends only its edited text, and cancels in place', async () => {
|
||||
const message = queuedDraft({
|
||||
messageId: 'image-card',
|
||||
position: 7,
|
||||
@@ -169,7 +184,6 @@ describe('mobile in-place queued Edit', () => {
|
||||
const body = withText(message.body, 'edited')
|
||||
act(() => listener?.(batchEvent([{ ...message, body }])))
|
||||
expect(hook!.queued.cards).toMatchObject([{ messageId: 'image-card', text: 'edited' }])
|
||||
expect(body.blocks).toContainEqual({ type: 'image-ref', path: '/images/snapshot.png' })
|
||||
expect(hook!.queued.editor).toBeUndefined()
|
||||
expect(fieldsOf(holdCalls().at(-1)?.[1])).toMatchObject({
|
||||
action: 'release',
|
||||
@@ -191,9 +205,9 @@ describe('mobile in-place queued Edit', () => {
|
||||
const message = queuedDraft({ messageId: 'draft-1' })
|
||||
await mount(message)
|
||||
const newer = withText(message.body, 'edited elsewhere')
|
||||
sendRequest.mockImplementation(async (method) => {
|
||||
sendRequest.mockImplementation(async (method, params) => {
|
||||
if (method !== 'agentSession.queuedMessageUpdate') {
|
||||
return ok({ status: 'held' })
|
||||
return readOrHoldReply(method, params)
|
||||
}
|
||||
listener?.(batchEvent([{ ...message, body: newer }]))
|
||||
return mutationOk({ status: 'changed', messageId: 'draft-1' })
|
||||
@@ -209,12 +223,12 @@ describe('mobile in-place queued Edit', () => {
|
||||
expect(hook!.queued.editor).toMatchObject({ messageId: 'draft-1', text: 'edited elsewhere' })
|
||||
act(() => hook!.queued.editor!.change('saved text'))
|
||||
const body = withText(message.body, 'saved text')
|
||||
sendRequest.mockImplementation(async (method) => {
|
||||
sendRequest.mockImplementation(async (method, params) => {
|
||||
if (method === 'agentSession.queuedMessageUpdate') {
|
||||
listener?.(batchEvent([{ ...message, body }]))
|
||||
throw markRpcDeliveryUnknown(new Error('reply lost'))
|
||||
}
|
||||
return ok({ status: 'held' })
|
||||
return readOrHoldReply(method, params)
|
||||
})
|
||||
await act(async () => {
|
||||
await hook!.queued.editor!.save()
|
||||
@@ -250,12 +264,12 @@ describe('mobile in-place queued Edit', () => {
|
||||
const message = queuedDraft({ messageId: 'deferred-card' })
|
||||
await mount(message)
|
||||
let answer: ((response: RpcResponse) => void) | undefined
|
||||
sendRequest.mockImplementation((method) =>
|
||||
sendRequest.mockImplementation((method, params) =>
|
||||
method === 'agentSession.queuedMessageUpdate'
|
||||
? new Promise<RpcResponse>((resolve) => {
|
||||
answer = resolve
|
||||
})
|
||||
: Promise.resolve(ok({ status: 'held' }))
|
||||
: Promise.resolve(readOrHoldReply(method, params))
|
||||
)
|
||||
act(() => hook!.queued.editor!.change('edited in place'))
|
||||
let save: Promise<boolean> | undefined
|
||||
@@ -286,12 +300,12 @@ describe('mobile in-place queued Edit', () => {
|
||||
nextMessage.messageId
|
||||
])
|
||||
let answer: ((response: RpcResponse) => void) | undefined
|
||||
sendRequest.mockImplementation((method) =>
|
||||
sendRequest.mockImplementation((method, params) =>
|
||||
method === 'agentSession.queuedMessageUpdate'
|
||||
? new Promise<RpcResponse>((resolve) => {
|
||||
answer = resolve
|
||||
})
|
||||
: Promise.resolve(ok({ status: 'held' }))
|
||||
: Promise.resolve(readOrHoldReply(method, params))
|
||||
)
|
||||
act(() => hook!.queued.editor!.change('submitted edit'))
|
||||
let save: Promise<boolean> | undefined
|
||||
@@ -327,13 +341,13 @@ describe('mobile in-place queued Edit', () => {
|
||||
it('keeps a refused Save in the editor and closes only when the published card is gone', async () => {
|
||||
const message = queuedDraft({ messageId: 'refused-card' })
|
||||
await mount(message)
|
||||
sendRequest.mockImplementation(async (method) =>
|
||||
sendRequest.mockImplementation(async (method, params) =>
|
||||
method === 'agentSession.queuedMessageUpdate'
|
||||
? ok({
|
||||
ok: false,
|
||||
refusal: { code: 'agent_session_operation_invalid', message: 'Could not edit.' }
|
||||
})
|
||||
: ok({ status: 'held' })
|
||||
: readOrHoldReply(method, params)
|
||||
)
|
||||
act(() => hook!.queued.editor!.change('keep this text'))
|
||||
await act(async () => {
|
||||
@@ -361,7 +375,9 @@ describe('mobile in-place queued Edit', () => {
|
||||
expect(hook!.queued.editor?.text).toBe(text)
|
||||
const reconnectedRequest = vi.fn<RpcClient['sendRequest']>(async (method, params) =>
|
||||
method === 'agentSession.queuedMessageEditHold'
|
||||
? ok({ status: fieldsOf(params).action === 'renew' ? 'expired' : 'held' })
|
||||
? fieldsOf(params).action === 'renew'
|
||||
? ok({ status: 'expired' })
|
||||
: editHoldReply(params)
|
||||
: ok({ models: [], current: {} })
|
||||
)
|
||||
await act(async () =>
|
||||
@@ -404,7 +420,7 @@ describe('mobile in-place queued Edit', () => {
|
||||
answerHold = resolve
|
||||
})
|
||||
: method === 'agentSession.queuedMessageEditHold'
|
||||
? Promise.resolve(ok({ status: 'held' }))
|
||||
? Promise.resolve(readOrHoldReply(method, params))
|
||||
: Promise.resolve(
|
||||
mutationOk({ status: 'updated', messageId: message.messageId, fingerprint: 'new' })
|
||||
)
|
||||
@@ -418,7 +434,7 @@ describe('mobile in-place queued Edit', () => {
|
||||
})
|
||||
expect(hook!.queued.editor).toBeUndefined()
|
||||
expect(answerHold).toBeDefined()
|
||||
await act(async () => answerHold?.(ok({ status: 'held' })))
|
||||
await act(async () => answerHold?.(editHoldReply({ action: 'acquire' })))
|
||||
expect(append).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -49,9 +49,12 @@ describe("the phone's Stop press read as Stopping", () => {
|
||||
tracked = press.track(() => new Promise<boolean>((_, reject) => (fail = reject)))
|
||||
})
|
||||
|
||||
expect(latest?.pressed).toBe(true)
|
||||
const failure = new Error('socket closed')
|
||||
await act(async () => {
|
||||
fail(new Error('socket closed'))
|
||||
await tracked.catch(() => undefined)
|
||||
const rejected = expect(tracked).rejects.toBe(failure)
|
||||
fail(failure)
|
||||
await rejected
|
||||
})
|
||||
|
||||
expect(latest?.pressed).toBe(false)
|
||||
|
||||
+9
-1
@@ -52,13 +52,21 @@ const bindingRecovery = {
|
||||
}
|
||||
|
||||
describe('workspace session terminal binding replay', () => {
|
||||
it('retains a restorable legacy tab binding when neither snapshot has a layout', () => {
|
||||
it('retains only restorable legacy tab bindings when neither snapshot has a layout', () => {
|
||||
const prior = session('runtime-pty')
|
||||
const incoming = session(null)
|
||||
|
||||
preserveMissingWorkspaceSessionTerminalBindings(incoming, prior, bindingRecovery as never)
|
||||
|
||||
expect(incoming.tabsByWorktree.worktree[0]!.ptyId).toBe('runtime-pty')
|
||||
|
||||
const unavailable = session(null)
|
||||
preserveMissingWorkspaceSessionTerminalBindings(unavailable, prior, {
|
||||
...bindingRecovery,
|
||||
getTerminalLayoutLeafIds: () => new Set<string>(),
|
||||
isRestorablePtyBinding: () => false
|
||||
})
|
||||
expect(unavailable.tabsByWorktree.worktree[0]!.ptyId).toBeNull()
|
||||
})
|
||||
|
||||
it('does not retain a tab binding whose prior leaf was removed from the layout', () => {
|
||||
|
||||
@@ -272,7 +272,7 @@ describe('worktree meta alias projection', () => {
|
||||
* now-dangling aliases and `migrateLegacyWorktreeMetadata` re-mints the identical identity key
|
||||
* from the `instanceId` the locator row still carries. Nothing is lost at any step.
|
||||
*/
|
||||
it('loses no row on a build that has no rebuild at all', () => {
|
||||
it('preserves rows through metadata normalization before current Store reimport', () => {
|
||||
const fixture = buildFixture()
|
||||
const dataFile = tempDataFile()
|
||||
writeFixture(dataFile, fixture.state)
|
||||
|
||||
@@ -1,27 +1,12 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
classifyTabEntryQuery,
|
||||
getTabEntryOptions,
|
||||
openTabEntryWithOperations,
|
||||
TAB_ENTRY_ABSOLUTE_PATH_REMOTE_BLOCKED_MESSAGE,
|
||||
validateNewTabEntryAbsolutePath,
|
||||
validateNewTabEntryRelativePath,
|
||||
type TabEntryOperations
|
||||
} from './tab-create-entry-action'
|
||||
import * as classifier from './tab-create-entry-classifier'
|
||||
|
||||
const readyFiles = (files: string[]) => ({ files, loading: false, loadError: null })
|
||||
|
||||
it('keeps the pure classifier available through the tab action module', () => {
|
||||
expect(classifyTabEntryQuery).toBe(classifier.classifyTabEntryQuery)
|
||||
expect(getTabEntryOptions).toBe(classifier.getTabEntryOptions)
|
||||
expect(TAB_ENTRY_ABSOLUTE_PATH_REMOTE_BLOCKED_MESSAGE).toBe(
|
||||
classifier.TAB_ENTRY_ABSOLUTE_PATH_REMOTE_BLOCKED_MESSAGE
|
||||
)
|
||||
expect(validateNewTabEntryAbsolutePath).toBe(classifier.validateNewTabEntryAbsolutePath)
|
||||
expect(validateNewTabEntryRelativePath).toBe(classifier.validateNewTabEntryRelativePath)
|
||||
})
|
||||
|
||||
describe('openTabEntryWithOperations', () => {
|
||||
function makeOperations(overrides: Partial<TabEntryOperations> = {}): TabEntryOperations {
|
||||
return {
|
||||
|
||||
+8
@@ -157,5 +157,13 @@ describe('tab entry options over the real runtime listing', () => {
|
||||
await drainMicrotasks()
|
||||
|
||||
expect(blockedIds(latestState(states))).not.toContain('loading')
|
||||
expect(getTabEntryOptions('packages/app/package.json', latestState(states), 4)).toContainEqual({
|
||||
id: 'existing-file:packages/app/package.json',
|
||||
classification: {
|
||||
kind: 'existing-file',
|
||||
matchKind: 'exact-path',
|
||||
relativePath: 'packages/app/package.json'
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -28,7 +28,7 @@ describe('clipboard image limits', () => {
|
||||
).toThrow('Clipboard image is too large')
|
||||
})
|
||||
|
||||
it('rejects invalid or oversized image dimensions before pixel allocation', () => {
|
||||
it('rejects invalid or oversized image dimensions', () => {
|
||||
expect(() =>
|
||||
assertClipboardImageDimensionsWithinLimit({
|
||||
height: 1,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import * as utf8ByteLimits from './utf8-byte-limits'
|
||||
import {
|
||||
CLIPBOARD_TEXT_MEASURE_YIELD_CODE_UNITS,
|
||||
CLIPBOARD_TEXT_TOO_LARGE_ERROR,
|
||||
@@ -52,7 +53,7 @@ describe('clipboard text limits', () => {
|
||||
expect(yieldToEventLoop).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('uses the default 256k code-unit yield cadence for accepted large clipboard text', async () => {
|
||||
it('yields twice across two default clipboard measurement batches', async () => {
|
||||
const yieldToEventLoop = vi.fn(async () => {})
|
||||
|
||||
const measurement = await measureClipboardTextByteLengthWithYield(
|
||||
@@ -80,7 +81,7 @@ describe('clipboard text limits', () => {
|
||||
})
|
||||
|
||||
it('rejects text whose string length already exceeds the byte limit without scanning', () => {
|
||||
const codePointAt = vi.spyOn(String.prototype, 'codePointAt')
|
||||
const measureBytes = vi.spyOn(utf8ByteLimits, 'measureUtf8ByteLength')
|
||||
|
||||
expect(isClipboardTextByteLengthOverLimit('x'.repeat(6), 5)).toBe(true)
|
||||
expect(() => assertClipboardTextWithinLimit('x'.repeat(6), { maxBytes: 5 })).toThrow(
|
||||
@@ -89,7 +90,7 @@ describe('clipboard text limits', () => {
|
||||
expect(() => assertClipboardTextWriteWithinLimit('x'.repeat(6), { maxBytes: 5 })).toThrow(
|
||||
CLIPBOARD_TEXT_WRITE_TOO_LARGE_ERROR
|
||||
)
|
||||
expect(codePointAt).not.toHaveBeenCalled()
|
||||
expect(measureBytes).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('lets each clipboard consumer override the shared default byte limits', async () => {
|
||||
@@ -129,7 +130,7 @@ describe('clipboard text limits', () => {
|
||||
).rejects.toThrow(CLIPBOARD_TEXT_TOO_LARGE_ERROR)
|
||||
await expect(
|
||||
assertClipboardTextWriteWithinLimitWithYield('copied-secret-token-value', { maxBytes: 4 })
|
||||
).rejects.toThrow(CLIPBOARD_TEXT_WRITE_TOO_LARGE_ERROR)
|
||||
).rejects.toEqual(new Error(CLIPBOARD_TEXT_WRITE_TOO_LARGE_ERROR))
|
||||
|
||||
try {
|
||||
await assertClipboardTextWithinLimitWithYield('secret-token-value', { maxBytes: 4 })
|
||||
|
||||
Reference in New Issue
Block a user