diff --git a/test/unit/memo-action-helpers.js b/test/unit/memo-action-helpers.js index 0db0983..b4d3f77 100644 --- a/test/unit/memo-action-helpers.js +++ b/test/unit/memo-action-helpers.js @@ -18,17 +18,21 @@ const { fakeWallet } = require('../helpers/fake-wallet') // storeKey - the action's store dependency key ('feed' or 'profiles') // storeFactory - () => a fresh store // assertStoreEmpty - (store, wallet) => asserts the store was not updated +// assertBroadcastMsg - (broadcast, value) => asserts the broadcast message +// byteBased - true when the slice counts bytes (registers multi-byte tests) +// extraArgs - extra arguments passed to the broadcast method after the value function registerMemoActionTests (cfg) { - const { Action, method, MAX, lengthCode, validationCode, label, storeKey, storeFactory, assertStoreEmpty } = cfg + const { Action, method, MAX, lengthCode, validationCode, label, storeKey, storeFactory, assertStoreEmpty, assertBroadcastMsg, byteBased = false, extraArgs = [] } = cfg + const checkBroadcastMsg = assertBroadcastMsg || ((broadcast, value) => assert.equal(broadcast.msg, value)) test(`${label} at the maximum length (${MAX}) is accepted`, async () => { const wallet = fakeWallet() const action = new Action({ wallet }) const value = 'x'.repeat(MAX) - const txid = await action[method](value) + const txid = await action[method](value, ...extraArgs) assert.equal(txid, 'fake-txid') - assert.equal(wallet.broadcasts[0].msg, value) + checkBroadcastMsg(wallet.broadcasts[0], value) }) test(`${label} over the limit (${MAX + 1}) throws a length error and broadcasts nothing`, async () => { @@ -37,7 +41,7 @@ function registerMemoActionTests (cfg) { const action = new Action({ wallet, [storeKey]: store }) await assert.rejects( - action[method]('y'.repeat(MAX + 1)), + action[method]('y'.repeat(MAX + 1), ...extraArgs), (err) => err.code === lengthCode ) assert.equal(wallet.broadcasts.length, 0) @@ -50,12 +54,54 @@ function registerMemoActionTests (cfg) { const action = new Action({ wallet }) await assert.rejects( - action[method](invalid), + action[method](invalid, ...extraArgs), (err) => err.code === validationCode ) assert.equal(wallet.broadcasts.length, 0) } }) + + test(`${label} that is empty throws a validation error and broadcasts nothing`, async () => { + const wallet = fakeWallet() + const store = storeFactory() + const action = new Action({ wallet, [storeKey]: store }) + + await assert.rejects( + action[method]('', ...extraArgs), + (err) => err.code === validationCode + ) + assert.equal(wallet.broadcasts.length, 0) + assertStoreEmpty(store, wallet) + }) + + if (byteBased) { + test(`${label} with multi-byte characters at the byte limit is accepted`, async () => { + const wallet = fakeWallet() + const action = new Action({ wallet }) + + // 'é' encodes to 2 UTF-8 bytes, so floor(MAX/2) characters reach the limit. + const count = Math.floor(MAX / 2) + const value = 'é'.repeat(count) + assert.equal(Buffer.byteLength(value, 'utf8'), count * 2) + const txid = await action[method](value, ...extraArgs) + assert.equal(txid, 'fake-txid') + }) + + test(`${label} with multi-byte characters that exceed the byte limit throws a length error`, async () => { + const wallet = fakeWallet() + const action = new Action({ wallet }) + + const count = Math.floor(MAX / 2) + 1 + const value = 'é'.repeat(count) + assert.ok(Buffer.byteLength(value, 'utf8') > MAX) + + await assert.rejects( + action[method](value, ...extraArgs), + (err) => err.code === lengthCode + ) + assert.equal(wallet.broadcasts.length, 0) + }) + } } module.exports = { registerMemoActionTests } diff --git a/test/unit/memo-post.test.js b/test/unit/memo-post.test.js index b04f92d..148b382 100644 --- a/test/unit/memo-post.test.js +++ b/test/unit/memo-post.test.js @@ -59,19 +59,6 @@ test('posting a valid memo broadcasts an OP_RETURN with the Memo post prefix and assert.equal(feed.posts[0].address, wallet.walletInfo.cashAddress) }) -test('posting an empty memo throws a validation error and broadcasts nothing', async () => { - const wallet = fakeWallet() - const feed = fakeFeed() - const memoPost = new MemoPost({ wallet, feed }) - - await assert.rejects( - memoPost.post(''), - (err) => err.code === 'memo_validation' - ) - assert.equal(wallet.broadcasts.length, 0) - assert.equal(feed.posts.length, 0) -}) - test('posting without a wallet reports a missing-wallet error', async () => { const memoPost = new MemoPost({}) await assert.rejects( diff --git a/test/unit/memo-reply.test.js b/test/unit/memo-reply.test.js index 3f3f9c4..319e7fc 100644 --- a/test/unit/memo-reply.test.js +++ b/test/unit/memo-reply.test.js @@ -16,24 +16,11 @@ const test = require('node:test') const assert = require('node:assert/strict') const MemoReply = require('../../src/services/memo-reply') +const { fakeWallet } = require('../helpers/fake-wallet') +const { registerMemoActionTests } = require('./memo-action-helpers') const PARENT_TXID = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' -// A fake wallet that records every broadcast attempt. -function fakeWallet (cashAddress = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d', utxos = [{ txid: 'utxo1' }]) { - const broadcasts = [] - const wallet = { - walletInfo: { cashAddress }, - getUtxos: async () => utxos, - sendOpReturn: async (msg, prefix) => { - broadcasts.push({ msg, prefix }) - return 'fake-txid' - } - } - wallet.broadcasts = broadcasts - return wallet -} - // A fake thread that records replies added to the thread view. function fakeThread (rootTxid = PARENT_TXID) { const replies = [] @@ -55,6 +42,21 @@ function decodeParentTxid (raw) { return Buffer.from(raw.slice(0, 32)).toString('hex') } +registerMemoActionTests({ + Action: MemoReply, + method: 'reply', + MAX: 184, + lengthCode: 'reply_length', + validationCode: 'reply_validation', + label: 'a reply', + storeKey: 'thread', + storeFactory: fakeThread, + assertStoreEmpty: (thread) => assert.equal(thread.replies.length, 0), + assertBroadcastMsg: (broadcast, value) => assert.equal(decodeReplyText(broadcast.msg), value), + byteBased: true, + extraArgs: [PARENT_TXID] +}) + test('MEMO_REPLY_PREFIX is the Memo reply action 0x6d03', () => { assert.equal(MemoReply.MEMO_REPLY_PREFIX, '6d03') }) @@ -81,81 +83,6 @@ test('replying with a valid message broadcasts an OP_RETURN with the Memo reply assert.equal(thread.replies[0].parentTxid, PARENT_TXID) }) -test('replying at the maximum byte length (184) is accepted', async () => { - const wallet = fakeWallet() - const memoReply = new MemoReply({ wallet }) - - const msg = 'x'.repeat(184) - const txid = await memoReply.reply(msg, PARENT_TXID) - assert.equal(txid, 'fake-txid') - assert.equal(decodeReplyText(wallet.broadcasts[0].msg), msg) -}) - -test('replying with a multi-byte character at the byte limit is accepted', async () => { - const wallet = fakeWallet() - const memoReply = new MemoReply({ wallet }) - - // 92 'é' characters encode to 184 UTF-8 bytes. - const msg = 'é'.repeat(92) - assert.equal(Buffer.byteLength(msg, 'utf8'), 184) - const txid = await memoReply.reply(msg, PARENT_TXID) - assert.equal(txid, 'fake-txid') -}) - -test('replying with an empty message throws a validation error and broadcasts nothing', async () => { - const wallet = fakeWallet() - const thread = fakeThread() - const memoReply = new MemoReply({ wallet, thread }) - - await assert.rejects( - memoReply.reply('', PARENT_TXID), - (err) => err.code === 'reply_validation' - ) - assert.equal(wallet.broadcasts.length, 0) - assert.equal(thread.replies.length, 0) -}) - -test('replying with a whitespace-only or non-string message throws a validation error and broadcasts nothing', async () => { - for (const invalid of [' ', 42]) { - const wallet = fakeWallet() - const memoReply = new MemoReply({ wallet }) - - await assert.rejects( - memoReply.reply(invalid, PARENT_TXID), - (err) => err.code === 'reply_validation' - ) - assert.equal(wallet.broadcasts.length, 0) - } -}) - -test('replying with an over-long message (185 bytes) throws a length error and broadcasts nothing', async () => { - const wallet = fakeWallet() - const thread = fakeThread() - const memoReply = new MemoReply({ wallet, thread }) - - await assert.rejects( - memoReply.reply('y'.repeat(185), PARENT_TXID), - (err) => err.code === 'reply_length' - ) - assert.equal(wallet.broadcasts.length, 0) - assert.equal(thread.replies.length, 0) -}) - -test('replying with a multi-byte character that exceeds the byte limit throws a length error', async () => { - const wallet = fakeWallet() - const memoReply = new MemoReply({ wallet }) - - // 93 'é' characters encode to 186 UTF-8 bytes, exceeding the 184-byte limit. - const msg = 'é'.repeat(93) - assert.ok(Buffer.byteLength(msg, 'utf8') > 184) - - await assert.rejects( - memoReply.reply(msg, PARENT_TXID), - (err) => err.code === 'reply_length' - ) - assert.equal(wallet.broadcasts.length, 0) -}) - test('replying without a wallet reports a missing-wallet error', async () => { const memoReply = new MemoReply({}) await assert.rejects( diff --git a/test/unit/memo-set-name.test.js b/test/unit/memo-set-name.test.js index 623ff17..c902e70 100644 --- a/test/unit/memo-set-name.test.js +++ b/test/unit/memo-set-name.test.js @@ -28,7 +28,8 @@ registerMemoActionTests({ label: 'setting a name', storeKey: 'profiles', storeFactory: fakeProfiles, - assertStoreEmpty: (profiles, wallet) => assert.equal(profiles.getName(wallet.walletInfo.cashAddress), null) + assertStoreEmpty: (profiles, wallet) => assert.equal(profiles.getName(wallet.walletInfo.cashAddress), null), + byteBased: true }) test('MEMO_SET_NAME_PREFIX is the Memo set-name action 0x6d01', () => { assert.equal(MemoSetName.MEMO_SET_NAME_PREFIX, '6d01') @@ -51,31 +52,6 @@ test('setting a valid name broadcasts an OP_RETURN with the Memo set-name prefix assert.equal(profiles.getName(wallet.walletInfo.cashAddress), 'trout') }) -test('setting a name at the maximum byte length with multi-byte characters is accepted', async () => { - const wallet = fakeWallet() - const memoSetName = new MemoSetName({ wallet }) - - // 38 'é' characters are 76 bytes in UTF-8. - const name = 'é'.repeat(38) - assert.equal(Buffer.byteLength(name, 'utf8'), 76) - const txid = await memoSetName.setName(name) - assert.equal(txid, 'fake-txid') -}) - -test('setting an over-long name in bytes (78) throws a length error even when char count is lower', async () => { - const wallet = fakeWallet() - const memoSetName = new MemoSetName({ wallet }) - - // 40 'é' characters are 80 bytes, exceeding the 77-byte limit. - const name = 'é'.repeat(40) - assert.ok(Buffer.byteLength(name, 'utf8') > 77) - await assert.rejects( - memoSetName.setName(name), - (err) => err.code === 'name_length' - ) - assert.equal(wallet.broadcasts.length, 0) -}) - test('setting an empty name throws a validation error and broadcasts nothing', async () => { const wallet = fakeWallet() const profiles = fakeProfiles() diff --git a/test/unit/new-post.test.js b/test/unit/new-post.test.js index f1944ab..dd4a0dd 100644 --- a/test/unit/new-post.test.js +++ b/test/unit/new-post.test.js @@ -18,7 +18,7 @@ const assert = require('node:assert/strict') const MemoPost = require('../../src/services/memo-post') const NewPostPage = require('../../src/services/new-post') const { fakeWallet } = require('../helpers/fake-wallet') -const { registerPageControllerTests } = require('./page-controller-helpers') +const { registerPageControllerTests, registerPageSubmitTests } = require('./page-controller-helpers') const { buildPage } = require('./page-build-helpers') const MAX = MemoPost.MAX_MEMO_CHARS // 217 @@ -70,54 +70,19 @@ test('the character counter reaches zero at the memo limit', () => { assert.equal(page.remainingCount(), 0) }) -test('posting a valid memo broadcasts the Memo post prefix and navigates to the feed', async () => { - const { wallet, store, page, navigations } = build() - page.setInput('hello memo') - - const result = await page.submit() - - assert.equal(result.ok, true) - // The page returns to an idle (not posting) state after success. - assert.equal(page.posting, false) - // Broadcast happened with the Memo post prefix and the exact message. - assert.equal(wallet.broadcasts.length, 1) - assert.equal(wallet.broadcasts[0].prefix, '6d02') - assert.equal(wallet.broadcasts[0].msg, 'hello memo') - // Navigated to the recent feed after posting. - assert.deepEqual(navigations, ['/posts/recent']) - // The feed reflects the new post from this address. - assert.equal(store.posts.length, 1) - assert.equal(store.posts[0].text, 'hello memo') -}) - -test('posting an empty memo is rejected with a validation error and nothing is broadcast', async () => { - const { wallet, store, page, navigations } = build() - page.setInput('') - - const result = await page.submit() - - assert.equal(result.ok, false) - assert.equal(result.error, 'memo_validation') - assert.equal(page.submitError, 'memo_validation') - assert.equal(page.posting, false) - assert.equal(wallet.broadcasts.length, 0) - assert.equal(store.posts.length, 0) - assert.deepEqual(navigations, []) -}) - -test('posting an over-long memo is rejected with a length error and nothing is broadcast', async () => { - const { wallet, store, page, navigations } = build() - page.setInput('y'.repeat(MAX + 1)) - - const result = await page.submit() - - assert.equal(result.ok, false) - assert.equal(result.error, 'memo_length') - assert.equal(page.submitError, 'memo_length') - assert.equal(page.posting, false) - assert.equal(wallet.broadcasts.length, 0) - assert.equal(store.posts.length, 0) - assert.deepEqual(navigations, []) +registerPageSubmitTests({ + buildPage: build, + verb: 'posting', + label: 'memo', + busyFlag: 'posting', + prefix: '6d02', + validationCode: 'memo_validation', + lengthCode: 'memo_length', + MAX, + successPath: '/posts/recent', + assertBroadcastMsg: (broadcast) => assert.equal(broadcast.msg, 'hello memo'), + assertStore: (store) => assert.equal(store.posts[0].text, 'hello memo'), + assertStoreEmpty: (store) => assert.equal(store.posts.length, 0) }) test('the new post page starts idle (not posting)', () => { diff --git a/test/unit/page-build-helpers.js b/test/unit/page-build-helpers.js index 402b571..ea48d03 100644 --- a/test/unit/page-build-helpers.js +++ b/test/unit/page-build-helpers.js @@ -11,14 +11,16 @@ const { fakeWallet } = require('../helpers/fake-wallet') // actionKey - the page's action dependency key ('memoPost' or 'memoSetName') // storeKey - the action's store dependency key ('feed' or 'profiles') // storeFactory - () => a fresh store -function buildPage ({ Page, Action, actionKey, storeKey, storeFactory }) { +// pageDeps - extra dependencies passed to the page constructor +function buildPage ({ Page, Action, actionKey, storeKey, storeFactory, pageDeps = {} }) { const wallet = fakeWallet() const store = storeFactory() const action = new Action({ wallet, [storeKey]: store }) const navigations = [] const page = new Page({ [actionKey]: action, - navigate: (path) => navigations.push(path) + navigate: (path) => navigations.push(path), + ...pageDeps }) return { wallet, store, action, page, navigations } } diff --git a/test/unit/page-controller-helpers.js b/test/unit/page-controller-helpers.js index 795b16b..3ec30c2 100644 --- a/test/unit/page-controller-helpers.js +++ b/test/unit/page-controller-helpers.js @@ -63,4 +63,69 @@ function registerPageControllerTests (cfg) { }) } -module.exports = { registerPageControllerTests } +// Register the page-submit tests shared by the New Post and Reply Thread pages. +// Both pages extend PageController and submit a single action, so the valid, +// empty, and over-long submit behaviors are identical; only the page-specific +// pieces differ. `cfg` supplies: +// buildPage - () => ({ wallet, store, page, navigations }) +// verb - the action verb for test names ('posting' or 'submitting') +// label - the noun for test names ('memo' or 'reply') +// busyFlag - the page's in-flight flag name ('posting' or 'replying') +// prefix - the broadcast prefix ('6d02' or '6d03') +// validationCode - the validation error code +// lengthCode - the length error code +// MAX - the length limit +// successPath - the navigation path on success, or null for no navigation +// assertBroadcastMsg - (broadcast) => asserts the broadcast message (optional) +// assertStore - (store, wallet) => asserts the store reflects the value +// assertStoreEmpty - (store, wallet) => asserts the store was not updated +function registerPageSubmitTests (cfg) { + const { buildPage, verb, label, busyFlag, prefix, validationCode, lengthCode, MAX, successPath, assertBroadcastMsg, assertStore, assertStoreEmpty } = cfg + + test(`${verb} a valid ${label} broadcasts the ${prefix} prefix and reflects it`, async () => { + const { wallet, store, page, navigations } = buildPage() + page.setInput('hello memo') + + const result = await page.submit() + + assert.equal(result.ok, true) + assert.equal(page[busyFlag], false) + assert.equal(wallet.broadcasts.length, 1) + assert.equal(wallet.broadcasts[0].prefix, prefix) + if (assertBroadcastMsg) assertBroadcastMsg(wallet.broadcasts[0]) + assert.deepEqual(navigations, successPath ? [successPath] : []) + assertStore(store, wallet) + }) + + test(`${verb} an empty ${label} is rejected with a validation error and nothing is broadcast`, async () => { + const { wallet, store, page, navigations } = buildPage() + page.setInput('') + + const result = await page.submit() + + assert.equal(result.ok, false) + assert.equal(result.error, validationCode) + assert.equal(page.submitError, validationCode) + assert.equal(page[busyFlag], false) + assert.equal(wallet.broadcasts.length, 0) + assertStoreEmpty(store, wallet) + assert.deepEqual(navigations, []) + }) + + test(`${verb} an over-long ${label} is rejected with a length error and nothing is broadcast`, async () => { + const { wallet, store, page, navigations } = buildPage() + page.setInput('y'.repeat(MAX + 1)) + + const result = await page.submit() + + assert.equal(result.ok, false) + assert.equal(result.error, lengthCode) + assert.equal(page.submitError, lengthCode) + assert.equal(page[busyFlag], false) + assert.equal(wallet.broadcasts.length, 0) + assertStoreEmpty(store, wallet) + assert.deepEqual(navigations, []) + }) +} + +module.exports = { registerPageControllerTests, registerPageSubmitTests } diff --git a/test/unit/reply-thread-page.test.js b/test/unit/reply-thread-page.test.js index bccd662..2e1f828 100644 --- a/test/unit/reply-thread-page.test.js +++ b/test/unit/reply-thread-page.test.js @@ -16,42 +16,30 @@ const assert = require('node:assert/strict') const MemoReply = require('../../src/services/memo-reply') const ReplyThreadPage = require('../../src/services/reply-thread-page') +const { registerPageControllerTests, registerPageSubmitTests } = require('./page-controller-helpers') +const { buildPage } = require('./page-build-helpers') const MAX = MemoReply.MAX_REPLY_BYTES // 184 const PARENT_TXID = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' -function fakeWallet (cashAddress = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d') { - const broadcasts = [] - const wallet = { - walletInfo: { cashAddress }, - utxos: [{ txid: 'utxo-fee' }], - getUtxos: async function () { return this.utxos }, - sendOpReturn: async function (msg, prefix) { - this.broadcasts.push({ msg, prefix }) - if (this.failWith) throw new Error(this.failWith) - return 'reply-txid' - } - } - wallet.broadcasts = broadcasts - return wallet -} - function fakeThread (rootTxid = PARENT_TXID) { const replies = [] return { rootTxid, replies, addReply: (r) => replies.push(r) } } -function build (deps = {}) { - const wallet = deps.wallet || fakeWallet() - const thread = deps.thread || fakeThread() - const memoReply = new MemoReply({ wallet, thread }) - const navigations = [] - const page = new ReplyThreadPage({ - memoReply, - parentTxid: deps.parentTxid || PARENT_TXID, - navigate: (path) => navigations.push(path) +function build () { + return buildPage({ + Page: ReplyThreadPage, + Action: MemoReply, + actionKey: 'memoReply', + storeKey: 'thread', + storeFactory: fakeThread, + pageDeps: { parentTxid: PARENT_TXID } }) - return { wallet, thread, memoReply, page, navigations } +} + +function buildBarePage (navigations) { + return new ReplyThreadPage({ navigate: (p) => navigations.push(p) }) } test('REPLY_THREAD_PATH constant', () => { @@ -82,50 +70,21 @@ test('the byte counter reaches zero at the reply byte limit', () => { assert.equal(page.remainingCount(), 0) }) -test('submitting a valid reply broadcasts the Memo reply prefix and reflects it in the thread', async () => { - const { wallet, thread, page, navigations } = build() - page.setInput('hello memo') - - const result = await page.submit() - - assert.equal(result.ok, true) - assert.equal(page.replying, false) - assert.equal(wallet.broadcasts.length, 1) - assert.equal(wallet.broadcasts[0].prefix, '6d03') - assert.deepEqual(navigations, []) - assert.equal(thread.replies.length, 1) - assert.equal(thread.replies[0].text, 'hello memo') - assert.equal(thread.replies[0].parentTxid, PARENT_TXID) -}) - -test('submitting an empty reply is rejected with a validation error and nothing is broadcast', async () => { - const { wallet, thread, page, navigations } = build() - page.setInput('') - - const result = await page.submit() - - assert.equal(result.ok, false) - assert.equal(result.error, 'reply_validation') - assert.equal(page.submitError, 'reply_validation') - assert.equal(page.replying, false) - assert.equal(wallet.broadcasts.length, 0) - assert.equal(thread.replies.length, 0) - assert.deepEqual(navigations, []) -}) - -test('submitting an over-long reply is rejected with a length error and nothing is broadcast', async () => { - const { wallet, thread, page, navigations } = build() - page.setInput('y'.repeat(MAX + 1)) - - const result = await page.submit() - - assert.equal(result.ok, false) - assert.equal(result.error, 'reply_length') - assert.equal(page.submitError, 'reply_length') - assert.equal(page.replying, false) - assert.equal(wallet.broadcasts.length, 0) - assert.equal(thread.replies.length, 0) - assert.deepEqual(navigations, []) +registerPageSubmitTests({ + buildPage: build, + verb: 'submitting', + label: 'reply', + busyFlag: 'replying', + prefix: '6d03', + validationCode: 'reply_validation', + lengthCode: 'reply_length', + MAX, + successPath: null, + assertStore: (store) => { + assert.equal(store.replies[0].text, 'hello memo') + assert.equal(store.replies[0].parentTxid, PARENT_TXID) + }, + assertStoreEmpty: (store) => assert.equal(store.replies.length, 0) }) test('the reply page starts idle (not replying)', () => { @@ -133,72 +92,22 @@ test('the reply page starts idle (not replying)', () => { assert.equal(page.replying, false) }) -test('replying is true while a submit is in flight and false once it settles', async () => { - const wallet = fakeWallet() - const thread = fakeThread() - - let resolveSend - wallet.sendOpReturn = async () => new Promise((resolve) => { resolveSend = resolve }) - const page = new ReplyThreadPage({ - memoReply: new MemoReply({ wallet, thread }), - parentTxid: PARENT_TXID, - navigate: () => {} - }) - page.setInput('hello memo') - - assert.equal(page.replying, false) - const pending = page.submit() - assert.equal(page.replying, true) - - await new Promise((resolve) => setImmediate(resolve)) - assert.equal(typeof resolveSend, 'function') - resolveSend('in-flight-txid') - await pending - assert.equal(page.replying, false) -}) - -test('submitting without a memo reply handler reports an error and does not navigate', async () => { - const navigations = [] - const page = new ReplyThreadPage({ navigate: (p) => navigations.push(p) }) - page.setInput('hello') - - const result = await page.submit() - - assert.equal(result.ok, false) - assert.deepEqual(navigations, []) -}) - -test('a failed broadcast surfaces the real error and does not navigate', async () => { - const wallet = fakeWallet() - const thread = fakeThread() - wallet.failWith = 'BCH UTXO list is empty' - const navigations = [] - const page = new ReplyThreadPage({ - memoReply: new MemoReply({ wallet, thread }), - parentTxid: PARENT_TXID, - navigate: (p) => navigations.push(p) - }) - page.setInput('hello memo') - - const result = await page.submit() - - assert.equal(result.ok, false) - assert.equal(page.submitError, 'broadcast') - assert.match(page.broadcastError, /BCH UTXO list is empty/) - assert.equal(wallet.broadcasts.length, 1) - assert.equal(wallet.broadcasts[0].prefix, '6d03') - assert.deepEqual(navigations, []) +registerPageControllerTests({ + buildPage: build, + buildBarePage, + busyFlag: 'replying', + prefix: '6d03' }) test('replying to a nested reply uses the selected parent txid', async () => { const nestedTxid = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' - const { thread, page } = build() + const { store, page } = build() page.setParent(nestedTxid) page.setInput('hello nested') const result = await page.submit() assert.equal(result.ok, true) - assert.equal(thread.replies[0].parentTxid, nestedTxid) - assert.equal(thread.replies[0].text, 'hello nested') + assert.equal(store.replies[0].parentTxid, nestedTxid) + assert.equal(store.replies[0].text, 'hello nested') })