test(feedback): reach the per-file coverage gate
Cover the reachable controller and control branches: each failure code's copy, dispose-during-flight, non-Error rejections, and non-conflict mutation failures. Two paths were unreachable rather than untested, so remove them instead: commit() cannot run after disposal because mutate() refuses admission first, and the mutation tail cannot reject because every queued operation settles as a result. Pass the recorded rating into the note save so the editor's render site proves it exists.
This commit is contained in:
@@ -60,16 +60,17 @@ export function FeedbackActions({ messageId, ensure, rate, clear, useFeedback, t
|
||||
void rate(messageId, next, item?.note).then(settle)
|
||||
}, [clear, item?.note, messageId, rate, rating, settle])
|
||||
|
||||
const onSaveNote = useCallback(() => {
|
||||
if (rating === undefined) return
|
||||
// The rating is a parameter because only the note editor's render site can
|
||||
// prove one is recorded; that removes an unreachable undefined guard here.
|
||||
const onSaveNote = useCallback((current: MessageFeedbackRating) => {
|
||||
const trimmed = draft.trim()
|
||||
setPending(true)
|
||||
setFailure(null)
|
||||
void rate(messageId, rating, trimmed.length === 0 ? undefined : trimmed).then((result) => {
|
||||
void rate(messageId, current, trimmed.length === 0 ? undefined : trimmed).then((result) => {
|
||||
settle(result)
|
||||
if (result.ok && alive.current) setNoteOpen(false)
|
||||
})
|
||||
}, [draft, messageId, rate, rating, settle])
|
||||
}, [draft, messageId, rate, settle])
|
||||
|
||||
const openNote = useCallback(() => {
|
||||
setDraft(item?.note ?? '')
|
||||
@@ -116,7 +117,7 @@ export function FeedbackActions({ messageId, ensure, rate, clear, useFeedback, t
|
||||
{item?.note === undefined ? t('note.open') : item.note}
|
||||
</button>
|
||||
)}
|
||||
{noteOpen && (
|
||||
{rating !== undefined && noteOpen && (
|
||||
<span className={css.noteEditor}>
|
||||
<textarea
|
||||
className={css.noteInput}
|
||||
@@ -126,7 +127,12 @@ export function FeedbackActions({ messageId, ensure, rate, clear, useFeedback, t
|
||||
rows={2}
|
||||
onChange={(event) => { setDraft(event.target.value) }}
|
||||
/>
|
||||
<button type="button" className={css.noteSave} disabled={pending} onClick={onSaveNote}>
|
||||
<button
|
||||
type="button"
|
||||
className={css.noteSave}
|
||||
disabled={pending}
|
||||
onClick={() => { onSaveNote(rating) }}
|
||||
>
|
||||
{t('note.save')}
|
||||
</button>
|
||||
<button type="button" className={css.noteCancel} onClick={() => { setNoteOpen(false) }}>
|
||||
|
||||
@@ -238,15 +238,20 @@ export class FeedbackController implements HostObservable<FeedbackView> {
|
||||
}
|
||||
}
|
||||
const result = this.operationTail.then(guarded, guarded)
|
||||
// Every queued operation settles carrier and business failures as a
|
||||
// FeedbackActionResult, so this controlled tail cannot reject.
|
||||
this.operationTail = result.then(() => undefined, () => undefined)
|
||||
// `guarded` settles every carrier and business failure as a
|
||||
// FeedbackActionResult and never rethrows, so this tail cannot reject and
|
||||
// needs no rejection handler.
|
||||
this.operationTail = result.then(() => undefined)
|
||||
return result
|
||||
}
|
||||
|
||||
/** Replace one message's entry, keeping every other entry's identity. */
|
||||
/**
|
||||
* Replace one message's entry, keeping every other entry's identity. Only a
|
||||
* `mutate` operation reaches this, and `mutate` refuses admission once the
|
||||
* controller is disposed, so no disposal guard belongs here; `publish` is
|
||||
* the single place that stops notifying after listeners are dropped.
|
||||
*/
|
||||
private commit(messageId: MessageId, item: MessageFeedbackItem | null): void {
|
||||
if (this.disposed) return
|
||||
const items = new Map(this.view.items)
|
||||
if (item === null) items.delete(messageId)
|
||||
else items.set(messageId, item)
|
||||
|
||||
Reference in New Issue
Block a user