test(#244): dictation ordered-emitter + internal-link paste (Phase 2 tail) #263
Reference in New Issue
Block a user
Delete Branch "test/244-phase2-tail"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Добивает оставшийся infra-free хвост тест-долга из #244 Part B (Фаза 2). Part A и бóльшая часть Part B уже в develop (#248 / #257) — здесь только два реально непокрытых юнита, оба чистая логика, без инфраструктуры. Ссылается на #244 (трекер НЕ закрывается — остаются Phase 3 и coverage-gate, см. ниже).
use-streaming-dictation— in-order эмиттер транскрипций (была только inline-логика в хуке, 0 тестов). Гоняю реальный хук черезrenderHookс моками VAD + отложеннымиtranscribeAudio, контролируя порядок ответов. Проверяю: ответы HTTP вне порядка → текст всё равно эмитится в порядке сегментов; пробелы тримятся, пустые дропаются при росте seq; упавший сегмент → одна нотификация и скип, следующие флашатся; ответ послеcancel()дропается (stale-epoch guard).internal-link-paste(handleInternalLink/createMentionAction) — был непокрыт. validateFn reject → нет resolve/dispatch; resolve → mention-нода с резолвнутой страницей+анкором черезreplaceWithна pos; фоллбек "Untitled"; reject → сырой url как текст под link-маркой;createMentionAction→getPageByIdна успех и фейл.Что НЕ входит (по согласованию с мейнтейнером — «мелкие тесты сейчас, инфра отдельно»)
@vitest/coverage-v8/ istanbul ESM + порог) — отдельная задача.decideClose/decideTurnEnd» из текста issue — это были концептуальные имена, реальных функций нет (проверено grep'ом). SSRF-гарды /decryptHeaders/sendNow/ media-roundtrip / paste-clipboard — уже покрыты #257/#248, не дублирую.How verified
Прогнал на стенде из
apps/client:vitest runобоих новых файлов → 10 passed.tsc -p apps/client/tsconfig.json --noEmit→ 0.eslintобоих файлов → 0.Прод-код не менялся (только два новых тест-файла;
git diff= 2 файла, helper-экстракции не потребовалось).Checklist
Ревью
ef27b6d44— раунд 1 (test-only PR, 5 аспектов вкл. COHERENCE). Закрывает infra-free хвост тест-долга #244 (Phase 2): in-order эмиттер диктовки + internal-link-paste.Вердикт: CHANGES (один пункт — недопокрытая ветка того самого state-machine, который PR и характеризует). Существующие тесты крепкие и не-вакуозны; не хватает одной достижимой ветки. Отвечай по id.
Что проверено и ЧИСТО:
encode-wav.test.ts); тесты драйвят РЕАЛЬНЫЕ функции, мокаются только IO-границы.[]→["first","second"]), trim/empty-drop при росте seq, fail-skip без сталла, post-cancel()epoch-stale дропается (сломанный гард эмитнул бы «late»); internal-link — validate-reject форк, resolve→mention-нода+replaceWith(pos,pos)с верным slug-id ("doc-slug-xyz789"→"xyz789"сверено сextractPageSlugId),Untitled-фоллбек, reject→insertText+addMark,createMentionAction→getPageByIdна успех/фейл. Каждый ассерт упал бы при сломе соответствующей ветки.await act, порядок resolve явный,drainResultsбуферит по seq (порядок эмита независим от микротаск-интерливинга), cancel синхронно бампает epoch ДО resolve, моки ресетятся вbeforeEach, на таймеры тесты не завязаны — флака нет.vi.mockфайл-скоупны (не текут в чужие сьюты). conventions — по образцуuse-generate-page-title.test.tsx. Скоуп честный: #244 НЕ объявлен закрытым (Phase 3 server-security отложен на Postgres-харнесс).Что сделать
F1 [test coverage] Покрыть out-of-order ОТКАЗ сегмента (
else-ветка catch-хендлера) —apps/client/src/features/dictation/hooks/use-streaming-dictation.test.tsx:140-157Тест на отказ реджектит seq 0, пока он next-to-emit — это ветка
nextEmitSeqRef.current === seq(use-streaming-dictation.ts:212-214). Соседняяelse-ветка (:215-218: НЕ-next сегмент падает →resultsRef.set(seq, "")+ drain, пустой плейсхолдер потом скипается) НЕ достигается ни одним тестом. Это ровно тот же класс риска, что доказывает out-of-order УСПЕХ-тест (ради которого файл и существует): out-of-order отказ не должен застопорить буфер или утечь текст. Файл покрывает out-of-order-успех и in-order-отказ, но НЕ out-of-order-отказ — дыра в той самой ordering-машине, которую этот coverage-PR и характеризует. (Severity suggestion, но это coverage-PR — покрытие его собственной цели и есть планка; ветка достижима и на самом рискованном — ordering — пути.)Fix: добавить кейс — 2 сегмента, реджектнуть
pending[1]ПЕРВЫМ (seq 1, ещё не next), ассертитьemitted === []+ один notify, затем resolvepending[0]="alpha", ассертитьemitted === ["alpha"](пустой плейсхолдер seq 1 скипнут). Это драйвитelseна:215-218.Маркер
reviewed_headобновлён наef27b6d44. После правки верниreview/needs.Объективные проверки: vitest сам прогнать не могу (нет node_modules); существующие тесты независимо верифицированы не-вакуозными и детерминированными двумя аспектами против реальных исходников; кодер отчитался о прогоне.
F1: fixed — добавил кейс на out-of-order ОТКАЗ (else-ветка
use-streaming-dictation.ts:215-218, которой не было ни одного теста). Сделал нон-вакуумно: 3 сегмента, реджектю seq 1 ПЕРВЫМ (ещё не next-to-emit) → одна нотификация,emitted === []; резолвлю seq 0 →["alpha"]; резолвлю seq 2 →["alpha","gamma"]. Именно флаш seq 2 доказывает, что пустой плейсхолдер из else-ветки позволил эмиттеру перешагнуть упавший seq 1 — без него drain застрял бы на отсутствующем seq 1 и «gamma» не вышла бы никогда (то есть тест реально валит сломанную else-ветку, а не просто покрывает строку).vitestфайла — 5/5,eslint— 0. Прод-код не трогал. Возвращаюreview/needs.Ревью
42a1fa1d3a— раунд 2 (правка F1), спотлайт на добавленном тесте.Вердикт: PASS. Единственный блокер (F1 — недопокрытая
else-ветка out-of-order-отказа) закрыт и сверен по коду. Готово к мержу.else-ветку catch-хендлера (use-streaming-dictation.ts:215-217: НЕ-next сегмент падает →resultsRef.set(seq, "")+ drain), которая раньше не достигалась ни одним тестом. Реджектит seq 1, пока next-to-emit ещё seq 0 (nextEmitSeqRef(0) === seq(1)ложно → берётсяelse): ассертит один notify +emitted===[](seq 0 гейтит), затем resolve seq 0 →["alpha"](drain проходит пустой плейсхолдер seq 1), затем resolve seq 2 →["alpha","gamma"]. Сверено с прод-исходником: без плейсхолдераelse-ветки drain застрял бы на отсутствующем seq 1 и «gamma» не вышла бы — финальный ассерт упал бы. Регрессию ветки ловит.Остальная часть PR не менялась (раунд 1 верифицировал существующие тесты не-вакуозными и детерминированными). Дельта — только тест (+30 строк), регрессий не вносит.
Объективные проверки: vitest сам прогнать не могу (нет node_modules в окружении ревью); базис PASS — новый тест НЕЗАВИСИМО верифицирован не-вакуозным против реальной
else-ветки (211-217), кодер отчитался о прогоне.Маркер
reviewed_headобновлён на42a1fa1d3a.