Review and harden topic actions: kill mutation survivors

- Add hardening tests for topic-post-page, profiles, topic-feed-page, and
  topic-query to kill mutation survivors.
- Refresh mutation manifests and Gherkin acceptance-mutation stamps.
- Add topic-actions review summary.

By architect.
This commit is contained in:
Chris Troutner
2026-08-28 13:13:01 -07:00
parent d6f56780be
commit 5183359143
18 changed files with 91 additions and 5 deletions
@@ -1,3 +1,7 @@
# acceptance-mutation-manifest-begin
# {"version":1,"tested_at":"2026-08-28T20:08:33.247012122Z","feature_name":"Topic Follow","feature_path":"/home/trout/work/psf-memo/.worktrees/architect/psf-memo-client/specs/topic-follow.feature","background_hash":"e1d5f81f1ed083ac6934c429ca3cb4a0f8d4dac44c2eaa45c0960920bde2c017","implementation_hash":"unknown","scenarios":[]}
# acceptance-mutation-manifest-end
# Scenarios: Topic Follow - 1, Topic Follow - 2, Topic Follow - 3, Topic Follow - 4
#
# The topic feed page has a Follow/Unfollow button that broadcasts a Memo
+4
View File
@@ -1,3 +1,7 @@
# acceptance-mutation-manifest-begin
# {"version":1,"tested_at":"2026-08-28T20:08:37.622959822Z","feature_name":"Topic Post","feature_path":"/home/trout/work/psf-memo/.worktrees/architect/psf-memo-client/specs/topic-post.feature","background_hash":"e1d5f81f1ed083ac6934c429ca3cb4a0f8d4dac44c2eaa45c0960920bde2c017","implementation_hash":"unknown","scenarios":[]}
# acceptance-mutation-manifest-end
# Scenarios: Topic Post - 1, Topic Post - 2, Topic Post - 3, Topic Post - 4
#
# The topic feed page has a composer that broadcasts a Memo topic message
+1 -1
View File
@@ -148,5 +148,5 @@ class MemoDb {
export default MemoDb
// mutate4javascript-manifest-begin
// {"version":1,"tested_at":"2026-08-28T15:36:17.287Z","module_hash":"77d15bc46bc63e2ffbd5fa7387b08a74db8e967482626f4f52211ceb60bd0633","functions":[{"id":"func/MemoDb.constructor","name":"MemoDb.constructor","line":9,"end_line":11,"hash":"188825ae5983840d4e1a4df182966ef012297c336ea3b763c87ea1c384654279"},{"id":"func/MemoDb.getRecentProfiles","name":"MemoDb.getRecentProfiles","line":13,"end_line":15,"hash":"cb819c72881b70f719f964c607f64be70b40377297c738897eee495bcf6dff4d"},{"id":"func/MemoDb.getRecentPosts","name":"MemoDb.getRecentPosts","line":17,"end_line":19,"hash":"7c0fd29c2fdc67a27a05a190e2e4294a698a1fe5223d8017ca82c4f890c2d094"},{"id":"func/MemoDb.getProfile","name":"MemoDb.getProfile","line":21,"end_line":23,"hash":"6a250c7205799b40dbe97b8f17ddaee6eace8cb435b0561d9ff8f5eac93d2b20"},{"id":"func/MemoDb.getProfilePic","name":"MemoDb.getProfilePic","line":25,"end_line":27,"hash":"d61dd5aebce32ef28b69ad56f2f7108d6b5f333a4d94cd83f05f01a09fa8f1d7"},{"id":"func/MemoDb.getName","name":"MemoDb.getName","line":29,"end_line":31,"hash":"3f841735b93180c6aeeb14a563bb6d4eb0ca5d395fb960267c8896b06c1e85f3"},{"id":"func/MemoDb.getFollowState","name":"MemoDb.getFollowState","line":33,"end_line":49,"hash":"d62eb85c742b40f2d319230c6ef59e21738063d31bccf1f37ebdc52c20bac994"},{"id":"func/MemoDb.getTopics","name":"MemoDb.getTopics","line":51,"end_line":53,"hash":"8d392b8a4b1bef405a9872c2564c51d9343df7b1c628d016258ee1c165f713ef"},{"id":"func/MemoDb.getTopicPosts","name":"MemoDb.getTopicPosts","line":55,"end_line":57,"hash":"4cc1e13334b5b4a2b72942cbc5f3cce8493254628f7d51da33d7be9c60ef9ff7"},{"id":"func/MemoDb.getRecent","name":"MemoDb.getRecent","line":60,"end_line":71,"hash":"1565fd17dced45fbf1b6d6fd6b8003521129bbd88046ff52a4eb61c17862530d"},{"id":"func/MemoDb.getPage","name":"MemoDb.getPage","line":74,"end_line":85,"hash":"986ec43d7699d642b25a9f4627aa5420cc685a247c3caa4671797778bc84e3b5"},{"id":"func/MemoDb.getLevelResource","name":"MemoDb.getLevelResource","line":88,"end_line":101,"hash":"c2fb7338918b1e69aa8a14f65ae687532c843f0641f04074947731691001e4ea"},{"id":"func/MemoDb.getPostsByAddr","name":"MemoDb.getPostsByAddr","line":103,"end_line":105,"hash":"45bbfef12cd8e35da1d71299153b2e54304664d5e3191c497f105ba17b134431"},{"id":"func/MemoDb.getPostThread","name":"MemoDb.getPostThread","line":107,"end_line":118,"hash":"ed5ef157b457f8d984ff6a9488a4dccf7a37b0ae65adb40005df90508426661f"}]}
// {"version":1,"tested_at":"2026-08-28T18:23:08.799Z","module_hash":"c55696ecbf8f9c776dc025b308cefadf604950952002bc3a2f4a021f9c73085b","functions":[{"id":"func/MemoDb.constructor","name":"MemoDb.constructor","line":9,"end_line":11,"hash":"188825ae5983840d4e1a4df182966ef012297c336ea3b763c87ea1c384654279"},{"id":"func/MemoDb.getRecentProfiles","name":"MemoDb.getRecentProfiles","line":13,"end_line":15,"hash":"cb819c72881b70f719f964c607f64be70b40377297c738897eee495bcf6dff4d"},{"id":"func/MemoDb.getRecentPosts","name":"MemoDb.getRecentPosts","line":17,"end_line":19,"hash":"7c0fd29c2fdc67a27a05a190e2e4294a698a1fe5223d8017ca82c4f890c2d094"},{"id":"func/MemoDb.getProfile","name":"MemoDb.getProfile","line":21,"end_line":23,"hash":"6a250c7205799b40dbe97b8f17ddaee6eace8cb435b0561d9ff8f5eac93d2b20"},{"id":"func/MemoDb.getProfilePic","name":"MemoDb.getProfilePic","line":25,"end_line":27,"hash":"d61dd5aebce32ef28b69ad56f2f7108d6b5f333a4d94cd83f05f01a09fa8f1d7"},{"id":"func/MemoDb.getName","name":"MemoDb.getName","line":29,"end_line":31,"hash":"3f841735b93180c6aeeb14a563bb6d4eb0ca5d395fb960267c8896b06c1e85f3"},{"id":"func/MemoDb.getFollowState","name":"MemoDb.getFollowState","line":33,"end_line":49,"hash":"d62eb85c742b40f2d319230c6ef59e21738063d31bccf1f37ebdc52c20bac994"},{"id":"func/MemoDb.getTopics","name":"MemoDb.getTopics","line":51,"end_line":53,"hash":"8d392b8a4b1bef405a9872c2564c51d9343df7b1c628d016258ee1c165f713ef"},{"id":"func/MemoDb.getTopicPosts","name":"MemoDb.getTopicPosts","line":55,"end_line":57,"hash":"4cc1e13334b5b4a2b72942cbc5f3cce8493254628f7d51da33d7be9c60ef9ff7"},{"id":"func/MemoDb.getTopicFollowState","name":"MemoDb.getTopicFollowState","line":59,"end_line":72,"hash":"4f056870b5dee08dfffd7484f9d94cf1721297ffa4ea629dcc4208a8217b4d24"},{"id":"func/MemoDb.getTopicFollowers","name":"MemoDb.getTopicFollowers","line":74,"end_line":84,"hash":"65c9eff7e2afb949cc46b64f2091eed006d9b6800ca5aee6c7e8793ae13e3e94"},{"id":"func/MemoDb.getRecent","name":"MemoDb.getRecent","line":87,"end_line":98,"hash":"1565fd17dced45fbf1b6d6fd6b8003521129bbd88046ff52a4eb61c17862530d"},{"id":"func/MemoDb.getPage","name":"MemoDb.getPage","line":101,"end_line":112,"hash":"986ec43d7699d642b25a9f4627aa5420cc685a247c3caa4671797778bc84e3b5"},{"id":"func/MemoDb.getLevelResource","name":"MemoDb.getLevelResource","line":115,"end_line":128,"hash":"c2fb7338918b1e69aa8a14f65ae687532c843f0641f04074947731691001e4ea"},{"id":"func/MemoDb.getPostsByAddr","name":"MemoDb.getPostsByAddr","line":130,"end_line":132,"hash":"45bbfef12cd8e35da1d71299153b2e54304664d5e3191c497f105ba17b134431"},{"id":"func/MemoDb.getPostThread","name":"MemoDb.getPostThread","line":134,"end_line":145,"hash":"ed5ef157b457f8d984ff6a9488a4dccf7a37b0ae65adb40005df90508426661f"}]}
// mutate4javascript-manifest-end
@@ -67,3 +67,7 @@ MemoTopicFollow.MEMO_TOPIC_FOLLOW_PREFIX = MEMO_TOPIC_FOLLOW_PREFIX
MemoTopicFollow.MEMO_TOPIC_UNFOLLOW_PREFIX = MEMO_TOPIC_UNFOLLOW_PREFIX
module.exports = MemoTopicFollow
// mutate4javascript-manifest-begin
// {"version":1,"tested_at":"2026-08-28T18:19:01.010Z","module_hash":"f6fd04a72273df34505b97c8f790bba83a2d379d8325267f7904e3826749c686","functions":[{"id":"func/MemoTopicFollow.constructor","name":"MemoTopicFollow.constructor","line":21,"end_line":24,"hash":"dd4ee8221c4d849937d901f6383138aefe615763747cc6dab059db9291843b72"},{"id":"func/MemoTopicFollow.validate","name":"MemoTopicFollow.validate","line":26,"end_line":32,"hash":"e8392594d65c9a31e79a52afcdf92a0ddf111296cf24fa108b16d236405c61d4"},{"id":"func/MemoTopicFollow.follow","name":"MemoTopicFollow.follow","line":34,"end_line":36,"hash":"3a066e2f12efb2ec54f642e11d1eefd4e55d733589fe8266d9de8b3c6651265f"},{"id":"func/MemoTopicFollow.unfollow","name":"MemoTopicFollow.unfollow","line":38,"end_line":40,"hash":"cc714d188e5ba49eee63e0dda84a4ebc205af4ee783c346b647f5ac6645d90a2"},{"id":"func/MemoTopicFollow._broadcastAction","name":"MemoTopicFollow._broadcastAction","line":42,"end_line":56,"hash":"d191f210d5505f9bbb29c74fd02afe91f037a6e9c13a9514a4203a40cd28ff5f"},{"id":"func/MemoTopicFollow.reflect","name":"MemoTopicFollow.reflect","line":58,"end_line":63,"hash":"8f8ef1f3a088f66698f261e25e9b76243355bb02ec50334a37c799e3348b99ba"}]}
// mutate4javascript-manifest-end
@@ -83,3 +83,7 @@ MemoTopicPost.MEMO_TOPIC_MESSAGE_PREFIX = MEMO_TOPIC_MESSAGE_PREFIX
MemoTopicPost.MAX_TOPIC_MESSAGE_BYTES = MAX_TOPIC_MESSAGE_BYTES
module.exports = MemoTopicPost
// mutate4javascript-manifest-begin
// {"version":1,"tested_at":"2026-08-28T18:18:11.190Z","module_hash":"c73b1395983accf085921554a84c8ef9ad8014ba8d8ddfa794e051ae4a487f04","functions":[{"id":"func/MemoTopicPost.constructor","name":"MemoTopicPost.constructor","line":34,"end_line":38,"hash":"34483e7579cbde3babf17cc43f64bc6535537b6391169a75cb51025029283289"},{"id":"func/MemoTopicPost.isTooLong","name":"MemoTopicPost.isTooLong","line":42,"end_line":44,"hash":"a8d16816cf0aeda1be759f796aee75e9cc5d89ccbcc288b5d2551917121be27a"},{"id":"func/MemoTopicPost.remainingBytes","name":"MemoTopicPost.remainingBytes","line":46,"end_line":48,"hash":"11a07f6539fa6b74feabfdd258f83ede4556213951aa85266a959871ff616df1"},{"id":"func/MemoTopicPost.post","name":"MemoTopicPost.post","line":51,"end_line":67,"hash":"dec3508e4969efd3edfefbc0752ca4608bac9521fbda62cf2ce5d7bf0a5696c7"},{"id":"func/MemoTopicPost.reflect","name":"MemoTopicPost.reflect","line":70,"end_line":79,"hash":"8a136fe98fa6752b2247b9bc0064b9860cfa34e518abaa33e50e95833120d529"}]}
// mutate4javascript-manifest-end
+1 -1
View File
@@ -85,5 +85,5 @@ class Profiles {
module.exports = Profiles
// mutate4javascript-manifest-begin
// {"version":1,"tested_at":"2026-08-28T18:00:08.907Z","module_hash":"1cec5125fb228d43d47e4ec171eb43c492f2bd773ca315748df78a16071e2802","functions":[{"id":"func/Profiles.constructor","name":"Profiles.constructor","line":13,"end_line":19,"hash":"247804207ec0220b6e6507f6beb6f86023a7c08b50737a52a0dbb99d262993d3"},{"id":"func/Profiles.setName","name":"Profiles.setName","line":21,"end_line":24,"hash":"5a36c6e237798608de0bedd8744b75000c0eec5c0a6a64c870a25bcdf20aed21"},{"id":"func/Profiles.getName","name":"Profiles.getName","line":26,"end_line":29,"hash":"2fcb9d84687ea0f3b24f3e34c0a72eda55a4e869b87e08a7f4551321a4166198"},{"id":"func/Profiles.setBio","name":"Profiles.setBio","line":31,"end_line":34,"hash":"3825665ead1ba9bb217694c45a17bb2e11c78ffa053e5e90111a4b9208183b56"},{"id":"func/Profiles.getBio","name":"Profiles.getBio","line":36,"end_line":39,"hash":"2e9708249db4a0e4fa642cbe52e6216144ec91283281c61dee2ff3cc3d8f1572"},{"id":"func/Profiles.setAvatarUrl","name":"Profiles.setAvatarUrl","line":41,"end_line":44,"hash":"e4ccb0164fee4ab6cc3a1290fb4bf85e4a79f2227ca72872b1f10bc616926fd9"},{"id":"func/Profiles.getAvatarUrl","name":"Profiles.getAvatarUrl","line":46,"end_line":49,"hash":"0aa58dc15fd519e553136d04d1954e18084f79a6cd9349dc89b1136cf0a22a2b"},{"id":"func/Profiles.setFollowState","name":"Profiles.setFollowState","line":52,"end_line":54,"hash":"805cafb46052f91bfa0a123842c25ff8ee0f7f4a7e661f51d289b0487738dbd4"},{"id":"func/Profiles.getFollowState","name":"Profiles.getFollowState","line":56,"end_line":58,"hash":"48fdf5a632825946c391146628cd6f51b4eb0169c7a6d6b89c795ff554e62b07"},{"id":"func/Profiles.setTopicFollowState","name":"Profiles.setTopicFollowState","line":61,"end_line":63,"hash":"54ab980e42018d64fbacee7d80347d530791b7ba21d8fac46f5ed85f33903f98"},{"id":"func/Profiles.getTopicFollowState","name":"Profiles.getTopicFollowState","line":65,"end_line":67,"hash":"90ce88799dda5477e20ce4de17404dace55abf1d2f2c1c87671e94fc2f270205"},{"id":"func/Profiles._setMapState","name":"Profiles._setMapState","line":70,"end_line":76,"hash":"13d2cad84be961b89482970f55f107d93fa1bdc10636dc64844c6a9263e88583"},{"id":"func/Profiles._getMapState","name":"Profiles._getMapState","line":79,"end_line":82,"hash":"c1c8966a797533614d35c7a13deb8f757572127ebcbde1d234c7d966346d89a8"}]}
// {"version":1,"tested_at":"2026-08-28T18:21:58.102Z","module_hash":"1cec5125fb228d43d47e4ec171eb43c492f2bd773ca315748df78a16071e2802","functions":[{"id":"func/Profiles.constructor","name":"Profiles.constructor","line":13,"end_line":19,"hash":"247804207ec0220b6e6507f6beb6f86023a7c08b50737a52a0dbb99d262993d3"},{"id":"func/Profiles.setName","name":"Profiles.setName","line":21,"end_line":24,"hash":"5a36c6e237798608de0bedd8744b75000c0eec5c0a6a64c870a25bcdf20aed21"},{"id":"func/Profiles.getName","name":"Profiles.getName","line":26,"end_line":29,"hash":"2fcb9d84687ea0f3b24f3e34c0a72eda55a4e869b87e08a7f4551321a4166198"},{"id":"func/Profiles.setBio","name":"Profiles.setBio","line":31,"end_line":34,"hash":"3825665ead1ba9bb217694c45a17bb2e11c78ffa053e5e90111a4b9208183b56"},{"id":"func/Profiles.getBio","name":"Profiles.getBio","line":36,"end_line":39,"hash":"2e9708249db4a0e4fa642cbe52e6216144ec91283281c61dee2ff3cc3d8f1572"},{"id":"func/Profiles.setAvatarUrl","name":"Profiles.setAvatarUrl","line":41,"end_line":44,"hash":"e4ccb0164fee4ab6cc3a1290fb4bf85e4a79f2227ca72872b1f10bc616926fd9"},{"id":"func/Profiles.getAvatarUrl","name":"Profiles.getAvatarUrl","line":46,"end_line":49,"hash":"0aa58dc15fd519e553136d04d1954e18084f79a6cd9349dc89b1136cf0a22a2b"},{"id":"func/Profiles.setFollowState","name":"Profiles.setFollowState","line":52,"end_line":54,"hash":"805cafb46052f91bfa0a123842c25ff8ee0f7f4a7e661f51d289b0487738dbd4"},{"id":"func/Profiles.getFollowState","name":"Profiles.getFollowState","line":56,"end_line":58,"hash":"48fdf5a632825946c391146628cd6f51b4eb0169c7a6d6b89c795ff554e62b07"},{"id":"func/Profiles.setTopicFollowState","name":"Profiles.setTopicFollowState","line":61,"end_line":63,"hash":"54ab980e42018d64fbacee7d80347d530791b7ba21d8fac46f5ed85f33903f98"},{"id":"func/Profiles.getTopicFollowState","name":"Profiles.getTopicFollowState","line":65,"end_line":67,"hash":"90ce88799dda5477e20ce4de17404dace55abf1d2f2c1c87671e94fc2f270205"},{"id":"func/Profiles._setMapState","name":"Profiles._setMapState","line":70,"end_line":76,"hash":"13d2cad84be961b89482970f55f107d93fa1bdc10636dc64844c6a9263e88583"},{"id":"func/Profiles._getMapState","name":"Profiles._getMapState","line":79,"end_line":82,"hash":"c1c8966a797533614d35c7a13deb8f757572127ebcbde1d234c7d966346d89a8"}]}
// mutate4javascript-manifest-end
@@ -90,5 +90,5 @@ TopicFeedPage.topicFeedPath = function (room) {
module.exports = TopicFeedPage
// mutate4javascript-manifest-begin
// {"version":1,"tested_at":"2026-08-28T15:35:01.465Z","module_hash":"d6433e58dfe8e7d0095074424340477c0abcfdcac7f611e2267ed04232936a1d","functions":[{"id":"func/TopicFeedPage.constructor","name":"TopicFeedPage.constructor","line":10,"end_line":15,"hash":"5d839555bdd88de97e47d68ef4dd2bad3a657ec4f9ce93991d332902bb4c371e"},{"id":"func/TopicFeedPage.load","name":"TopicFeedPage.load","line":17,"end_line":30,"hash":"ddf4ed21448879fa579225491125e343ebc129ef08445da811d98e79c6c29042"},{"id":"func/TopicFeedPage.getPost","name":"TopicFeedPage.getPost","line":32,"end_line":34,"hash":"1a6ae1a02b0f79b5b62a2b2324a5f1edbd743bec004fe73cfef7970bead56885"}]}
// {"version":1,"tested_at":"2026-08-28T18:24:48.504Z","module_hash":"236ec55785bb69ac64c4a610ef4a5f8a8b03fae7b717d79c14c7fd2e755775e6","functions":[{"id":"func/TopicFeedPage.constructor","name":"TopicFeedPage.constructor","line":10,"end_line":19,"hash":"e6ee69c9caa13fcc05c7739fc185767f906c907a3031cf2c02abaab9220daca5"},{"id":"func/TopicFeedPage.load","name":"TopicFeedPage.load","line":21,"end_line":36,"hash":"ef85636d06f2b1eb6afd19b276bd8b9728ee35122d4424b3faf468de4a0e23a4"},{"id":"func/TopicFeedPage._loadFollowState","name":"TopicFeedPage._loadFollowState","line":38,"end_line":43,"hash":"d1984bb94b4f2e7c98c39524fc2fd0080dc5cff10e775a17c68856c2e3a2c249"},{"id":"func/TopicFeedPage._loadFollowers","name":"TopicFeedPage._loadFollowers","line":45,"end_line":47,"hash":"8b19d3623b4a872f4abe879ceda7e4018edf60efff03d037fe795b6558222b37"},{"id":"func/TopicFeedPage.canFollow","name":"TopicFeedPage.canFollow","line":49,"end_line":51,"hash":"2f82c2096bf6f542b60cf20d82cd70f366652fcce3a38ff14e82164d5ee86cad"},{"id":"func/TopicFeedPage.isFollowing","name":"TopicFeedPage.isFollowing","line":53,"end_line":55,"hash":"9fc0470db7ffea2da96d2dbbdceff55562e1f7e84377fbb0e8b60d53cc723b40"},{"id":"func/TopicFeedPage.follow","name":"TopicFeedPage.follow","line":57,"end_line":59,"hash":"7674b789a9d3c48e0f7a6e553613bac99b2f38449fcdd17faa3c6d7cd2773bdd"},{"id":"func/TopicFeedPage.unfollow","name":"TopicFeedPage.unfollow","line":61,"end_line":63,"hash":"69d278b09da1f284be6adacb7264f9c06ac71c42f8e8f1baf9e34d5b24a891fb"},{"id":"func/TopicFeedPage._setFollowState","name":"TopicFeedPage._setFollowState","line":65,"end_line":79,"hash":"698f22cdfc2c088446c732cf01864d8859b299e066c199eb542bc965413f48b4"},{"id":"func/TopicFeedPage.getPost","name":"TopicFeedPage.getPost","line":81,"end_line":83,"hash":"1a6ae1a02b0f79b5b62a2b2324a5f1edbd743bec004fe73cfef7970bead56885"}]}
// mutate4javascript-manifest-end
@@ -50,3 +50,7 @@ class TopicPostPage extends PageController {
TopicPostPage.MAX_TOPIC_MESSAGE_BYTES = MemoTopicPost.MAX_TOPIC_MESSAGE_BYTES
module.exports = TopicPostPage
// mutate4javascript-manifest-begin
// {"version":1,"tested_at":"2026-08-28T18:20:31.541Z","module_hash":"34b7d1b6936611010fd664764742b2d395db9b9c23699dc7ae0e938230d74847","functions":[{"id":"func/TopicPostPage.constructor","name":"TopicPostPage.constructor","line":21,"end_line":26,"hash":"a93d04b51599074b2e2aa3c85dda465e77fec20b43c73c76c3e153e98bdc1366"},{"id":"func/TopicPostPage.remainingCount","name":"TopicPostPage.remainingCount","line":29,"end_line":34,"hash":"e289a1a02748206cfb0967677fcff1ab92f2ceede8c6727d9b203538298e92e2"},{"id":"func/TopicPostPage._setBusy","name":"TopicPostPage._setBusy","line":37,"end_line":39,"hash":"4cc342945de59ccc4284e845c45fbd4c78508638c30fda621892d5554da2c985"},{"id":"func/TopicPostPage._perform","name":"TopicPostPage._perform","line":42,"end_line":47,"hash":"5900159e55ddb681034e06db3ec42e2e00d80cf99148f2bc99e15c766794fd7a"}]}
// mutate4javascript-manifest-end
@@ -168,6 +168,27 @@ test('setTopicFollowState with no room does nothing', () => {
assert.equal(profiles.getTopicFollowState(selfAddr, ''), false)
})
test('_setMapState stores nothing when either input is missing', () => {
const profiles = new Profiles()
// Missing self address, present room.
profiles.setTopicFollowState('', 'bitcoin', true)
// Present self address, missing room.
profiles.setTopicFollowState('bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d', '', true)
assert.equal(profiles.topicFollowing.size, 0)
})
test('_getMapState ignores stored state when either input is missing', () => {
const profiles = new Profiles()
// Manually store under a missing room to prove the read guard alone reports
// not-following instead of leaking the stored value.
profiles.topicFollowing.set('bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d', new Map([['', true]]))
assert.equal(profiles.getTopicFollowState('bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d', ''), false)
})
test('topic follow state storage is independent per self address', () => {
const profiles = new Profiles()
const selfAddr = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d'
@@ -119,6 +119,13 @@ test('load throws when no room is provided', async () => {
)
})
test('starts not following and with no followers', () => {
const page = new TopicFeedPage({ room: 'bitcoin' })
assert.equal(page.followState, false)
assert.deepEqual(page.followers, [])
})
test('getPost returns a loaded post by txid', async () => {
const posts = [{ txid: 'a'.repeat(64), text: 'hello bitcoin' }]
const page = new TopicFeedPage({ memoDb: makeMemoDb(posts, {}), room: 'bitcoin' })
@@ -96,3 +96,8 @@ test('remainingCount requires a memo topic post handler', () => {
test('exposes the combined byte limit', () => {
assert.equal(TopicPostPage.MAX_TOPIC_MESSAGE_BYTES, 214)
})
test('starts not busy', () => {
const page = new TopicPostPage({})
assert.equal(page.postingTopic, false)
})
@@ -1,3 +1,7 @@
# acceptance-mutation-manifest-begin
# {"version":1,"tested_at":"2026-08-28T20:09:23.829689449Z","feature_name":"Topic Follow Read","feature_path":"/home/trout/work/psf-memo/.worktrees/architect/psf-memo-db/specs/topic-follow-read.feature","background_hash":"bf41600ace5aaae19b01881936ae5ec595b53abbb9829b5f7927f43a830ded38","implementation_hash":"unknown","scenarios":[]}
# acceptance-mutation-manifest-end
# Scenarios: Topic Follow Read - 1, Topic Follow Read - 2
#
# The indexer stores topic follows in the rooms store keyed `${room}:${addr}`
+1 -1
View File
@@ -115,5 +115,5 @@ class TopicQuery {
export default TopicQuery
// mutate4javascript-manifest-begin
// {"version":1,"tested_at":"2026-08-28T14:46:39.922Z","module_hash":"515aa0e8cfdc7d596b270d5c4b82e2f33efa7f074ea406b6850007402d69f2e2","functions":[{"id":"func/TopicQuery.constructor","name":"TopicQuery.constructor","line":14,"end_line":29,"hash":"1c1e10e3ee613b0cd141a941849ce28a99eac688142629c7781ded6e5e15c938"},{"id":"func/TopicQuery.roomFromKey","name":"TopicQuery.roomFromKey","line":31,"end_line":34,"hash":"4175916ac2bb8f9f102c70750f336ed980db2a30a1f8f35645534303408aaece"},{"id":"func/TopicQuery.txidFromKey","name":"TopicQuery.txidFromKey","line":36,"end_line":39,"hash":"4f5f5c456f1e74a60acff54d1d5e2d982be53886c2a77c6eff3293133efa9c87"},{"id":"func/TopicQuery.listTopics","name":"TopicQuery.listTopics","line":41,"end_line":57,"hash":"9c524ef62e22d7a434768dc431c360f9c81b0c2a321b162a614f7f7e1e011e08"},{"id":"func/TopicQuery.getTopicPostTxids","name":"TopicQuery.getTopicPostTxids","line":59,"end_line":77,"hash":"4e0fccf874560249152d04d13879c24fbae0271bf7835716789dfafcff7dbd04"}]}
// {"version":1,"tested_at":"2026-08-28T19:16:59.454Z","module_hash":"7b5597f76aed02abe591e652087cb1c560dd17a1154a62299f50f60303aa6e87","functions":[{"id":"func/TopicQuery.constructor","name":"TopicQuery.constructor","line":14,"end_line":32,"hash":"08f209a40b4ffc2962e5f399e78936005f3286ec37123a043850b12e0c20833b"},{"id":"func/TopicQuery.roomFromKey","name":"TopicQuery.roomFromKey","line":34,"end_line":37,"hash":"4175916ac2bb8f9f102c70750f336ed980db2a30a1f8f35645534303408aaece"},{"id":"func/TopicQuery.txidFromKey","name":"TopicQuery.txidFromKey","line":39,"end_line":42,"hash":"4f5f5c456f1e74a60acff54d1d5e2d982be53886c2a77c6eff3293133efa9c87"},{"id":"func/TopicQuery.listTopics","name":"TopicQuery.listTopics","line":44,"end_line":60,"hash":"9c524ef62e22d7a434768dc431c360f9c81b0c2a321b162a614f7f7e1e011e08"},{"id":"func/TopicQuery.getTopicPostTxids","name":"TopicQuery.getTopicPostTxids","line":62,"end_line":80,"hash":"4e0fccf874560249152d04d13879c24fbae0271bf7835716789dfafcff7dbd04"},{"id":"func/TopicQuery.isFollowingRoom","name":"TopicQuery.isFollowingRoom","line":83,"end_line":92,"hash":"a0cf48cc6f4842adf65830b41d536cd6a078c7adf6b837e57259491157e2e97d"},{"id":"func/TopicQuery.listRoomFollowers","name":"TopicQuery.listRoomFollowers","line":95,"end_line":106,"hash":"351d71881e8b0953366056f223a6f06326e8ecd946277abc372ae5771c0b0f08"},{"id":"func/TopicQuery.followAddrFromValue","name":"TopicQuery.followAddrFromValue","line":108,"end_line":112,"hash":"2ce68c35f539e1fd7694774059b4b2afd096014c99a87c7538f3cbb70e26c3c7"}]}
// mutate4javascript-manifest-end
@@ -146,5 +146,5 @@ class TopicsRESTControllerLib {
export default TopicsRESTControllerLib
// mutate4javascript-manifest-begin
// {"version":1,"tested_at":"2026-08-28T15:26:54.488Z","module_hash":"64ac0a8aa7c2e0b575a89161ceb19c59e6618448eaf767ef5bbccc3ce174afaa","functions":[{"id":"func/TopicsRESTControllerLib.constructor","name":"TopicsRESTControllerLib.constructor","line":8,"end_line":21,"hash":"63d2092a2bd7946dd11bdf0c4ff102e31c47a0ed6ad56a0652f26e1760d525f9"},{"id":"func/TopicsRESTControllerLib.handleError","name":"TopicsRESTControllerLib.handleError","line":23,"end_line":30,"hash":"b9ba0c7b9752ac2fda3cf058a983a8b6708715e2e4dc882903ab5ef369956e03"},{"id":"func/TopicsRESTControllerLib.getTopics","name":"TopicsRESTControllerLib.getTopics","line":47,"end_line":53,"hash":"b89a696bcd1d601ef86754974dbf181baf2ece7d592674ba4fd87887725d6fa8"},{"id":"func/TopicsRESTControllerLib.getTopicPosts","name":"TopicsRESTControllerLib.getTopicPosts","line":80,"end_line":88,"hash":"8ad5f9b1749968c3fbfea95683bb388a5d99eda0b8fe7a4dce3447d6d47e6dba"}]}
// {"version":1,"tested_at":"2026-08-28T19:53:31.127Z","module_hash":"4c3f3e9152899ed272cb583e558c4522c0f54c37a0ae728f3ec6bc40248a3fdc","functions":[{"id":"func/TopicsRESTControllerLib.constructor","name":"TopicsRESTControllerLib.constructor","line":8,"end_line":23,"hash":"bacd230f3777dda39ec134a69a9a7c1801cb3cd09c0cc86659df324540053028"},{"id":"func/TopicsRESTControllerLib.handleError","name":"TopicsRESTControllerLib.handleError","line":25,"end_line":32,"hash":"b9ba0c7b9752ac2fda3cf058a983a8b6708715e2e4dc882903ab5ef369956e03"},{"id":"func/TopicsRESTControllerLib.getTopics","name":"TopicsRESTControllerLib.getTopics","line":49,"end_line":55,"hash":"b89a696bcd1d601ef86754974dbf181baf2ece7d592674ba4fd87887725d6fa8"},{"id":"func/TopicsRESTControllerLib.getTopicPosts","name":"TopicsRESTControllerLib.getTopicPosts","line":82,"end_line":90,"hash":"8ad5f9b1749968c3fbfea95683bb388a5d99eda0b8fe7a4dce3447d6d47e6dba"},{"id":"func/TopicsRESTControllerLib.getTopicFollowState","name":"TopicsRESTControllerLib.getTopicFollowState","line":110,"end_line":118,"hash":"6d72fecc2bf46276cd00d39cc1c19ed6647beed1f2f3ba187a2c085ed4308611"},{"id":"func/TopicsRESTControllerLib.getTopicFollowers","name":"TopicsRESTControllerLib.getTopicFollowers","line":136,"end_line":143,"hash":"2bcd22520d962c008a3255f5cf6cf536fdeb738d036d3c6b6d61d5d5cf040615"}]}
// mutate4javascript-manifest-end
+4
View File
@@ -89,3 +89,7 @@ class UseCases {
}
export default UseCases
// mutate4javascript-manifest-begin
// {"version":1,"tested_at":"2026-08-28T20:07:25.668Z","module_hash":"4056d0f29e6828f95ab07f34186b3cef9d93f244c3d6914aa52463abdc224f32","functions":[{"id":"func/UseCases.constructor","name":"UseCases.constructor","line":18,"end_line":38,"hash":"bf14b8bc15f8a9ad966bb4d2a2de92072be3ec695af1aef5604495f5cf681e34"},{"id":"func/UseCases.start","name":"UseCases.start","line":40,"end_line":88,"hash":"f6fbf584c0e6786294a352fdae4e7a01fef1d24d31ca5202a33f5ed5e1772020"}]}
// mutate4javascript-manifest-end
@@ -24,3 +24,7 @@ class ListTopicFollowers extends ListUseCase {
}
export default ListTopicFollowers
// mutate4javascript-manifest-begin
// {"version":1,"tested_at":"2026-08-28T20:05:30.757Z","module_hash":"c2df7f5656fb718fe66f800585bef77d926c25b780925c4bafcb05a4888c7bb3","functions":[{"id":"func/ListTopicFollowers.constructor","name":"ListTopicFollowers.constructor","line":10,"end_line":12,"hash":"032be6dff5472c86a00dbfece93708dcfb41f0d5fa3d5aa9a08c3e97ca5a922e"},{"id":"func/ListTopicFollowers.execute","name":"ListTopicFollowers.execute","line":14,"end_line":23,"hash":"744248c669d8dbf5ced565d5a3328c9273ebac5f5db44bb5e947e7d26d4177a6"}]}
// mutate4javascript-manifest-end
@@ -27,3 +27,7 @@ class TopicFollowState extends ListUseCase {
}
export default TopicFollowState
// mutate4javascript-manifest-begin
// {"version":1,"tested_at":"2026-08-28T19:53:30.917Z","module_hash":"48b427de8b141c64275851761259c085142d7137817712cebaa6d42eea27a366","functions":[{"id":"func/TopicFollowState.constructor","name":"TopicFollowState.constructor","line":10,"end_line":12,"hash":"028e260fea5ad97395eaa65be5eaea9aa3134ae1a37c530b96b66df6e864fb0b"},{"id":"func/TopicFollowState.execute","name":"TopicFollowState.execute","line":14,"end_line":26,"hash":"7474d322e757cdb2ab2a03c65f17456c33196811171b478dd813fbc020d8e4bf"}]}
// mutate4javascript-manifest-end
@@ -312,4 +312,21 @@ describe('#TopicQuery', () => {
assert.deepEqual(result, [])
})
})
describe('#followAddrFromValue', () => {
it('should return the addr from the value when present', () => {
const result = uut.followAddrFromValue({ addr: 'addr-a' }, 'bitcoin:addr-a')
assert.equal(result, 'addr-a')
})
it('should fall back to the last key segment when the value has no addr', () => {
const result = uut.followAddrFromValue({ room: 'bitcoin', type: 'follow' }, 'bitcoin:addr-a')
assert.equal(result, 'addr-a')
})
it('should return null when the key has no address segment', () => {
const result = uut.followAddrFromValue({ room: 'lone', type: 'follow' }, 'lone')
assert.equal(result, null)
})
})
})