mirror of
https://github.com/Permissionless-Software-Foundation/psf-memo.git
synced 2026-09-21 16:52:01 -07:00
237 lines
7.4 KiB
JavaScript
237 lines
7.4 KiB
JavaScript
/*
|
|
Unit tests for the profile page controller.
|
|
|
|
The profile page loads a single address's posts from the MemoDb client and
|
|
fetches the follow state for the viewer. The like count returned by the API
|
|
must be preserved so the view can display it.
|
|
*/
|
|
|
|
'use strict'
|
|
|
|
const test = require('node:test')
|
|
const assert = require('node:assert/strict')
|
|
const ProfilePage = require('../../src/services/profile-page')
|
|
|
|
function makeMemoDb (postsByAddr, followState = {}, muteState = {}) {
|
|
return {
|
|
async getPostsByAddr (addr, { limit, offset }) {
|
|
return { posts: postsByAddr[addr] || [], pagination: { total: (postsByAddr[addr] || []).length } }
|
|
},
|
|
async getFollowState (followerAddr, followeeAddr) {
|
|
return followState[`${followerAddr}:${followeeAddr}`] || false
|
|
},
|
|
async getMuteState (muterAddr, muteeAddr) {
|
|
return muteState[`${muterAddr}:${muteeAddr}`] || false
|
|
}
|
|
}
|
|
}
|
|
|
|
function makeMemoFollow (profilePage) {
|
|
return {
|
|
async follow (addr) {
|
|
profilePage.followState = true
|
|
},
|
|
async unfollow (addr) {
|
|
profilePage.followState = false
|
|
}
|
|
}
|
|
}
|
|
|
|
test('load returns posts with like counts for the address', async () => {
|
|
const addr = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d'
|
|
const posts = [{ txid: 'a'.repeat(64), likeCount: 17 }]
|
|
const page = new ProfilePage({ memoDb: makeMemoDb({ [addr]: posts }), addr })
|
|
|
|
const result = await page.load()
|
|
|
|
assert.equal(result.posts[0].likeCount, 17)
|
|
})
|
|
|
|
test('getPost returns the like count for a loaded post', async () => {
|
|
const addr = 'bitcoincash:second'
|
|
const posts = [{ txid: 'b'.repeat(64), likeCount: 3 }]
|
|
const page = new ProfilePage({ memoDb: makeMemoDb({ [addr]: posts }), addr })
|
|
|
|
await page.load()
|
|
|
|
assert.equal(page.getPost('b'.repeat(64)).likeCount, 3)
|
|
})
|
|
|
|
test('load throws when no memo db client is provided', async () => {
|
|
const page = new ProfilePage({ addr: 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d' })
|
|
|
|
await assert.rejects(
|
|
() => page.load(),
|
|
/requires a memo db client/
|
|
)
|
|
})
|
|
|
|
test('load throws when no address is provided', async () => {
|
|
const page = new ProfilePage({ memoDb: makeMemoDb({}) })
|
|
|
|
await assert.rejects(
|
|
() => page.load(),
|
|
/requires an address/
|
|
)
|
|
})
|
|
|
|
test('load defaults limit to 50 and offset to 0', async () => {
|
|
const calls = []
|
|
const addr = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d'
|
|
const memoDb = {
|
|
async getPostsByAddr (a, params) {
|
|
calls.push({ a, params })
|
|
return { posts: [], pagination: {} }
|
|
},
|
|
async getFollowState () {
|
|
return false
|
|
}
|
|
}
|
|
const page = new ProfilePage({ memoDb, addr })
|
|
|
|
await page.load()
|
|
|
|
assert.deepEqual(calls, [{ a: addr, params: { limit: 50, offset: 0 } }])
|
|
})
|
|
|
|
test('load sets pagination to null when the API returns none', async () => {
|
|
const addr = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d'
|
|
const memoDb = {
|
|
async getPostsByAddr () {
|
|
return { posts: [], pagination: undefined }
|
|
},
|
|
async getFollowState () {
|
|
return false
|
|
}
|
|
}
|
|
const page = new ProfilePage({ memoDb, addr })
|
|
|
|
const result = await page.load()
|
|
|
|
assert.equal(result.pagination, null)
|
|
assert.equal(page.pagination, null)
|
|
})
|
|
|
|
test('load fetches follow state when a viewer address is provided', async () => {
|
|
const myAddr = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d'
|
|
const addr = 'bitcoincash:qr95sy3j9xwd2ap32xkykttr4cvcu7as4y0qverfuy'
|
|
const memoDb = makeMemoDb({}, { [`${myAddr}:${addr}`]: true })
|
|
const page = new ProfilePage({ memoDb, addr, myAddr })
|
|
|
|
const result = await page.load()
|
|
|
|
assert.equal(result.followState, true)
|
|
assert.equal(page.isFollowing(), true)
|
|
})
|
|
|
|
test('isOwnProfile returns true when viewer address matches profile address', () => {
|
|
const addr = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d'
|
|
const page = new ProfilePage({ memoDb: {}, addr, myAddr: addr })
|
|
|
|
assert.equal(page.isOwnProfile(), true)
|
|
assert.equal(page.canFollow(), false)
|
|
})
|
|
|
|
test('canFollow returns true when viewing another profile', () => {
|
|
const myAddr = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d'
|
|
const addr = 'bitcoincash:qr95sy3j9xwd2ap32xkykttr4cvcu7as4y0qverfuy'
|
|
const page = new ProfilePage({ memoDb: {}, addr, myAddr })
|
|
|
|
assert.equal(page.canFollow(), true)
|
|
})
|
|
|
|
test('follow delegates to the memo follow handler and updates state', async () => {
|
|
const myAddr = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d'
|
|
const addr = 'bitcoincash:qr95sy3j9xwd2ap32xkykttr4cvcu7as4y0qverfuy'
|
|
const memoDb = makeMemoDb({})
|
|
const page = new ProfilePage({ memoDb, addr, myAddr })
|
|
page.memoFollow = makeMemoFollow(page)
|
|
|
|
const result = await page.follow()
|
|
|
|
assert.equal(result.ok, true)
|
|
assert.equal(page.isFollowing(), true)
|
|
})
|
|
|
|
test('unfollow delegates to the memo follow handler and updates state', async () => {
|
|
const myAddr = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d'
|
|
const addr = 'bitcoincash:qr95sy3j9xwd2ap32xkykttr4cvcu7as4y0qverfuy'
|
|
const memoDb = makeMemoDb({})
|
|
const page = new ProfilePage({ memoDb, addr, myAddr })
|
|
page.memoFollow = makeMemoFollow(page)
|
|
page.followState = true
|
|
|
|
const result = await page.unfollow()
|
|
|
|
assert.equal(result.ok, true)
|
|
assert.equal(page.isFollowing(), false)
|
|
})
|
|
|
|
test('constructor preserves an injected memo follow handler', () => {
|
|
const memoFollow = { follow: async () => {}, unfollow: async () => {} }
|
|
const page = new ProfilePage({ memoFollow })
|
|
|
|
assert.equal(page.memoFollow, memoFollow)
|
|
})
|
|
|
|
test('follow throws when no memo follow handler is injected', async () => {
|
|
const myAddr = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d'
|
|
const addr = 'bitcoincash:qr95sy3j9xwd2ap32xkykttr4cvcu7as4y0qverfuy'
|
|
const page = new ProfilePage({ memoDb: makeMemoDb({}), addr, myAddr })
|
|
|
|
await assert.rejects(
|
|
() => page.follow(),
|
|
/requires a memo follow handler/
|
|
)
|
|
})
|
|
|
|
test('canLoadMore reflects pagination.hasMore', async () => {
|
|
const addr = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d'
|
|
const more = new ProfilePage({
|
|
memoDb: {
|
|
async getPostsByAddr () { return { posts: [], pagination: { hasMore: true } } },
|
|
async getFollowState () { return false },
|
|
async getMuteState () { return false }
|
|
},
|
|
addr
|
|
})
|
|
await more.load()
|
|
assert.equal(more.canLoadMore(), true)
|
|
|
|
const done = new ProfilePage({
|
|
memoDb: {
|
|
async getPostsByAddr () { return { posts: [], pagination: { hasMore: false } } },
|
|
async getFollowState () { return false },
|
|
async getMuteState () { return false }
|
|
},
|
|
addr
|
|
})
|
|
await done.load()
|
|
assert.equal(done.canLoadMore(), false)
|
|
})
|
|
|
|
test('canLoadMore returns false when pagination is null or missing hasMore', async () => {
|
|
const addr = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d'
|
|
const pageNull = new ProfilePage({
|
|
memoDb: {
|
|
async getPostsByAddr () { return { posts: [], pagination: null } },
|
|
async getFollowState () { return false },
|
|
async getMuteState () { return false }
|
|
},
|
|
addr
|
|
})
|
|
await pageNull.load()
|
|
assert.equal(pageNull.canLoadMore(), false)
|
|
|
|
const pageEmpty = new ProfilePage({
|
|
memoDb: {
|
|
async getPostsByAddr () { return { posts: [], pagination: { total: 0 } } },
|
|
async getFollowState () { return false },
|
|
async getMuteState () { return false }
|
|
},
|
|
addr
|
|
})
|
|
await pageEmpty.load()
|
|
assert.equal(pageEmpty.canLoadMore(), false)
|
|
})
|