Extract optimistic reply shaping into testable module

Move the optimistic reply object construction out of the ReplyThreadForm
React component into a testable service module with unit tests, keeping
the component a thin UI adapter. Behavior is preserved; all unit,
property, and acceptance tests pass.

By refactorer.
This commit is contained in:
Chris Troutner
2026-08-26 04:40:45 -07:00
parent 6e8dcaf783
commit de24180c4d
3 changed files with 90 additions and 5 deletions
@@ -15,6 +15,7 @@ import { Form, Button } from 'react-bootstrap'
import MemoReply from '../../services/memo-reply'
import ReplyThreadPage from '../../services/reply-thread-page'
import { byteLength } from '../../services/utf8'
import { buildOptimisticReply } from '../../services/optimistic-reply'
function ReplyThreadForm ({ parentTxid, rootPost, wallet, profiles, onOptimisticReply }) {
const maxBytes = MemoReply.MAX_REPLY_BYTES
@@ -42,16 +43,14 @@ function ReplyThreadForm ({ parentTxid, rootPost, wallet, profiles, onOptimistic
if (typeof onOptimisticReply === 'function') {
const cashAddress = wallet?.walletInfo?.cashAddress
const displayName = profiles?.[cashAddress]?.name || null
onOptimisticReply({
onOptimisticReply(buildOptimisticReply({
txid: result.txid,
addr: cashAddress,
text: input,
seen: Date.now(),
blockHeight: rootPost?.blockHeight,
replyCount: 0,
replies: [],
profile: displayName ? { name: displayName } : undefined
})
displayName
}))
}
} else {
if (result.error === 'reply_length') {
+20
View File
@@ -0,0 +1,20 @@
'use strict'
// Build the optimistic reply object used by the reply form for immediate
// thread rendering before the thread is refreshed from the network. This is a
// pure data-shaping function so the reply object shape is unit-testable and
// the React form stays a thin adapter.
function buildOptimisticReply ({ txid, addr, text, seen, blockHeight, displayName }) {
return {
txid,
addr,
text,
seen,
blockHeight,
replyCount: 0,
replies: [],
profile: displayName ? { name: displayName } : undefined
}
}
module.exports = { buildOptimisticReply }
+66
View File
@@ -0,0 +1,66 @@
/*
Unit tests for the optimistic reply object builder
(src/services/optimistic-reply.js).
The reply form renders an optimistic reply immediately after a successful
broadcast, before the thread is refreshed from the network. This module
shapes that reply object so the shape is covered by unit tests and the
React form stays a thin adapter.
*/
'use strict'
const test = require('node:test')
const assert = require('node:assert/strict')
const { buildOptimisticReply } = require('../../src/services/optimistic-reply')
const TXID = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
const ADDR = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d'
test('builds a reply with a zero reply count and no child replies', () => {
const reply = buildOptimisticReply({
txid: TXID,
addr: ADDR,
text: 'hello',
seen: 1234,
blockHeight: 800000,
displayName: null
})
assert.equal(reply.txid, TXID)
assert.equal(reply.addr, ADDR)
assert.equal(reply.text, 'hello')
assert.equal(reply.seen, 1234)
assert.equal(reply.blockHeight, 800000)
assert.equal(reply.replyCount, 0)
assert.deepEqual(reply.replies, [])
assert.equal(reply.profile, undefined)
})
test('attaches a profile when a display name is present', () => {
const reply = buildOptimisticReply({
txid: TXID,
addr: ADDR,
text: 'hello',
seen: 1234,
blockHeight: undefined,
displayName: 'Trout'
})
assert.deepEqual(reply.profile, { name: 'Trout' })
assert.equal(reply.blockHeight, undefined)
})
test('preserves an absent block height', () => {
const reply = buildOptimisticReply({
txid: TXID,
addr: ADDR,
text: 'hello',
seen: 1234,
blockHeight: null,
displayName: null
})
assert.equal(reply.blockHeight, null)
})