From c7951b344e2dfe6730de782848e051af6a79567d Mon Sep 17 00:00:00 2001 From: plebeius Date: Wed, 11 Mar 2026 14:22:31 +0800 Subject: [PATCH 1/2] refactor(api): rename subplebbit APIs to community Closes #22 --- README.md | 818 +++++++++-------- docs/TODO.md | 2 +- docs/algorithms.md | 56 +- docs/clients.md | 407 ++++----- docs/mock-content.md | 4 +- docs/schema.md | 30 +- docs/testing.md | 2 +- scripts/coverage-gaps.mjs | 34 +- src/hooks/accounts/accounts.test.ts | 552 ++++++------ src/hooks/accounts/accounts.ts | 172 ++-- src/hooks/accounts/index.ts | 2 +- src/hooks/accounts/utils.test.ts | 14 +- src/hooks/accounts/utils.ts | 2 +- src/hooks/actions/actions.test.ts | 174 ++-- src/hooks/actions/actions.ts | 96 +- src/hooks/actions/index.ts | 2 +- src/hooks/authors/author-avatars.ts | 2 +- src/hooks/authors/authors.test.ts | 12 +- src/hooks/authors/authors.ts | 2 +- src/hooks/authors/index.ts | 2 +- src/hooks/authors/utils.ts | 58 +- src/hooks/comments.test.ts | 52 +- src/hooks/comments.ts | 26 +- ...ubplebbits.test.ts => communities.test.ts} | 360 ++++---- src/hooks/communities.ts | 360 ++++++++ src/hooks/feeds/feeds.test.ts | 716 +++++++-------- src/hooks/feeds/feeds.ts | 75 +- src/hooks/feeds/index.ts | 2 +- src/hooks/replies.test.ts | 116 +-- src/hooks/states.test.ts | 174 ++-- src/hooks/states.ts | 141 ++- src/hooks/subplebbits.ts | 362 -------- src/index.ts | 62 +- src/lib/chain/chain.test.ts | 324 ++++--- src/lib/chain/index.ts | 6 +- src/lib/community-address.test.ts | 43 + ...lebbit-address.ts => community-address.ts} | 10 +- src/lib/debug-utils.ts | 8 +- .../localforage-lru/localforage-lru.test.ts | 192 ++-- src/lib/localforage-lru/localforage-lru.ts | 178 ++-- .../plebbit-js/fixtures/markdown-example.ts | 4 +- .../plebbit-js-mock-content.donttest.ts | 80 +- src/lib/plebbit-js/plebbit-js-mock-content.ts | 314 +++---- src/lib/plebbit-js/plebbit-js-mock.test.ts | 26 +- src/lib/plebbit-js/plebbit-js-mock.ts | 210 ++--- src/lib/subplebbit-address.test.ts | 43 - src/lib/test-utils.ts | 16 +- src/lib/utils/index.ts | 6 +- src/lib/utils/utils.test.ts | 6 +- src/lib/utils/utils.ts | 58 +- src/lib/validator.ts | 117 ++- src/stores/accounts/account-generator.ts | 8 +- .../accounts-actions-internal.test.ts | 94 +- .../accounts/accounts-actions-internal.ts | 44 +- src/stores/accounts/accounts-actions.test.ts | 90 +- src/stores/accounts/accounts-actions.ts | 156 ++-- src/stores/accounts/accounts-database.test.ts | 48 +- src/stores/accounts/accounts-database.ts | 2 +- src/stores/accounts/accounts-store.test.ts | 4 +- src/stores/accounts/index.ts | 6 +- src/stores/accounts/utils.test.ts | 120 +-- src/stores/accounts/utils.ts | 42 +- .../authors-comments-store.test.ts | 98 +- .../authors-comments-store.ts | 22 +- src/stores/authors-comments/index.ts | 6 +- src/stores/comments/index.ts | 6 +- .../communities-pages-store.test.ts | 834 +++++++++++++++++ .../communities-pages-store.ts | 402 +++++++++ src/stores/communities-pages/index.ts | 3 + .../communities/communities-store.test.ts | 257 ++++++ src/stores/communities/communities-store.ts | 354 ++++++++ src/stores/communities/index.ts | 3 + src/stores/feeds/feed-sorter.test.ts | 360 ++++---- src/stores/feeds/feeds-store.test.ts | 268 +++--- src/stores/feeds/feeds-store.ts | 320 ++++--- src/stores/feeds/index.ts | 6 +- src/stores/feeds/utils.test.ts | 284 +++--- src/stores/feeds/utils.ts | 271 +++--- src/stores/replies-pages/index.ts | 6 +- .../replies-pages/replies-pages-store.test.ts | 54 +- .../replies-pages/replies-pages-store.ts | 8 +- src/stores/replies-pages/utils.test.ts | 4 +- src/stores/replies/index.ts | 6 +- src/stores/replies/replies-store.test.ts | 34 +- src/stores/replies/replies-store.ts | 4 +- src/stores/replies/utils.test.ts | 70 +- src/stores/replies/utils.ts | 14 +- src/stores/subplebbits-pages/index.ts | 3 - .../subplebbits-pages-store.test.ts | 838 ------------------ .../subplebbits-pages-store.ts | 402 --------- src/stores/subplebbits/index.ts | 3 - .../subplebbits/subplebbits-store.test.ts | 257 ------ src/stores/subplebbits/subplebbits-store.ts | 354 -------- src/types.ts | 130 +-- test/browser-e2e/accounts.test.js | 242 +++-- ...ubplebbits.test.js => communities.test.js} | 38 +- test/browser-e2e/feeds.test.js | 22 +- .../plebbit-js-mock-content.test.js | 64 +- test/browser-plebbit-js-mock/accounts.test.js | 4 +- ...ubplebbits.test.js => communities.test.js} | 34 +- test/browser-plebbit-js-mock/feeds.test.js | 50 +- test/test-server/index.js | 30 +- 102 files changed, 6688 insertions(+), 6621 deletions(-) rename src/hooks/{subplebbits.test.ts => communities.test.ts} (53%) create mode 100644 src/hooks/communities.ts delete mode 100644 src/hooks/subplebbits.ts create mode 100644 src/lib/community-address.test.ts rename src/lib/{subplebbit-address.ts => community-address.ts} (69%) delete mode 100644 src/lib/subplebbit-address.test.ts create mode 100644 src/stores/communities-pages/communities-pages-store.test.ts create mode 100644 src/stores/communities-pages/communities-pages-store.ts create mode 100644 src/stores/communities-pages/index.ts create mode 100644 src/stores/communities/communities-store.test.ts create mode 100644 src/stores/communities/communities-store.ts create mode 100644 src/stores/communities/index.ts delete mode 100644 src/stores/subplebbits-pages/index.ts delete mode 100644 src/stores/subplebbits-pages/subplebbits-pages-store.test.ts delete mode 100644 src/stores/subplebbits-pages/subplebbits-pages-store.ts delete mode 100644 src/stores/subplebbits/index.ts delete mode 100644 src/stores/subplebbits/subplebbits-store.test.ts delete mode 100644 src/stores/subplebbits/subplebbits-store.ts rename test/browser-e2e/{subplebbits.test.js => communities.test.js} (84%) rename test/browser-plebbit-js-mock/{subplebbits.test.js => communities.test.js} (54%) diff --git a/README.md b/README.md index 6987306f..572abeb7 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ React hooks for the Bitsocial protocol. Build decentralized, serverless social a This package is currently consumed directly from [`bitsocialnet/bitsocial-react-hooks`](https://github.com/bitsocialnet/bitsocial-react-hooks) and is used by [5chan](https://github.com/bitsocialnet/5chan) and other Bitsocial clients. -> **Note:** This repo is a temporary [Bitsocial](https://github.com/bitsocialnet) fork of [plebbit/plebbit-react-hooks](https://github.com/plebbit/plebbit-react-hooks) for AI-aided development. Bug fixes, new features, and improvements made here will be merged upstream when the original maintainer is ready. The codebase still uses legacy naming (`plebbit`, `subplebbit`, etc.) pending upstream rebranding of the protocol layer. +> **Note:** This repo is a temporary [Bitsocial](https://github.com/bitsocialnet) fork of [plebbit/plebbit-react-hooks](https://github.com/plebbit/plebbit-react-hooks) for AI-aided development. Bug fixes, new features, and improvements made here will be merged upstream when the original maintainer is ready. The codebase still uses legacy naming (`plebbit`, `plebbit-js`, etc.) pending upstream rebranding of the protocol layer. ## Installation @@ -32,7 +32,7 @@ Use a pinned commit hash (or tag) so installs are reproducible. - [Hooks](#hooks) - [Accounts Hooks](#accounts-hooks) - [Comments Hooks](#comments-hooks) - - [Subplebbits Hooks](#subplebbits-hooks) + - [Communities Hooks](#communities-hooks) - [Authors Hooks](#authors-hooks) - [Feeds Hooks](#feeds-hooks) - [Actions Hooks](#actions-hooks) @@ -48,7 +48,7 @@ Use a pinned commit hash (or tag) so installs are reproducible. - [Get a comment](#get-a-comment) - [Get author avatar](#get-author-avatar) - [Get author profile page](#get-author-profile-page) - - [Get a subplebbit](#get-a-subplebbit) + - [Get a community](#get-a-community) - [Create a post or comment using callbacks](#create-a-post-or-comment-using-callbacks) - [Create a post or comment using hooks](#create-a-post-or-comment-using-hooks) - [Create a post or comment anonymously (without account.signer or account.author)](#create-a-post-or-comment-anonymously-without-accountsigner-or-accountauthor) @@ -56,7 +56,7 @@ Use a pinned commit hash (or tag) so installs are reproducible. - [Create a comment edit](#create-a-comment-edit) - [Create a comment moderation](#create-a-comment-moderation) - [Delete a comment](#delete-a-comment) - - [Subscribe to a subplebbit](#subscribe-to-a-subplebbit) + - [Subscribe to a community](#subscribe-to-a-community) - [Get feed](#get-feed) - [Get mod queue (pending approval)](#get-mod-queue-pending-approval) - [Approve a pending approval comment](#approve-a-pending-approval-comment) @@ -65,15 +65,15 @@ Use a pinned commit hash (or tag) so installs are reproducible. - [Get your own comments and votes](#get-your-own-comments-and-votes) - [Determine if a comment is your own](#determine-if-a-comment-is-your-own) - [Get account notifications](#get-account-notifications) - - [Block an address (author, subplebbit or multisub)](#block-an-address-author-subplebbit-or-multisub) + - [Block an address (author, community or multisub)](#block-an-address-author-community-or-multisub) - [Block a cid (hide a comment)](#block-a-cid-hide-a-comment) - - [(Desktop only) Create a subplebbit](#desktop-only-create-a-subplebbit) - - [(Desktop only) List the subplebbits you created](#desktop-only-list-the-subplebbits-you-created) - - [(Desktop only) Edit your subplebbit settings](#desktop-only-edit-your-subplebbit-settings) + - [(Desktop only) Create a community](#desktop-only-create-a-community) + - [(Desktop only) List the communities you created](#desktop-only-list-the-communities-you-created) + - [(Desktop only) Edit your community settings](#desktop-only-edit-your-community-settings) - [Export and import account](#export-and-import-account) - [View the status of a comment edit](#view-the-status-of-a-comment-edit) - [View the status of a specific comment edit property](#view-the-status-of-a-specific-comment-edit-property) - - [List all comment and subplebbit edits the account has performed](#list-all-comment-and-subplebbit-edits-the-account-has-performed) + - [List all comment and community edits the account has performed](#list-all-comment-and-community-edits-the-account-has-performed) - [Get replies to a post (nested or flat)](#get-replies-to-a-post-nested-or-flat) - [Get a shortCid or shortAddress (plebbit-js)](#get-a-shortcid-or-shortaddress-plebbit-js) - [Get a shortCid or shortAddress (hooks)](#get-a-shortcid-or-shortaddress-hooks) @@ -94,6 +94,7 @@ Use a pinned commit hash (or tag) so installs are reproducible. ### Hooks #### Accounts Hooks + ``` useAccount(): Account | undefined useAccountComment({commentIndex: string}): Comment // get a pending published comment by its index @@ -101,11 +102,13 @@ useAccountComments({filter: AccountPublicationsFilter}): {accountComments: Comme useAccountVotes({filter: AccountPublicationsFilter}): {accountVotes: Vote[]} // export or display list of own votes useAccountVote({commentCid: string}): Vote // know if you already voted on some comment useAccountEdits({filer: AccountPublicationsFilter}): {accountEdits: AccountEdit[]} -useAccountSubplebbits(): {accountSubplebbits: {[subplebbitAddress: string]: AccountSubplebbit}, onlyIfCached?: boolean} +useAccountCommunities(): {accountCommunities: {[communityAddress: string]: AccountCommunity}, onlyIfCached?: boolean} useAccounts(): Account[] useNotifications(): {notifications: Notification[], markAsRead: Function} ``` + #### Comments Hooks + ``` useComment({commentCid: string, onlyIfCached?: boolean}): Comment useReplies({comment: Comment, sortType?: string, flat?: boolean, repliesPerPage?: number, filter?: CommentsFilter, accountComments?: {newerThan: number, append?: boolean}}): {replies: Comment[], hasMore: boolean, loadMore: function, reset: function, updatedReplies: Comment[], bufferedReplies: Comment[]} @@ -113,14 +116,18 @@ useComments({commentCids: string[], onlyIfCached?: boolean}): {comments: Comment useEditedComment({comment: Comment}): {editedComment: Comment | undefined} useValidateComment({comment: Comment, validateReplies?: boolean}): {valid: boolean} ``` -#### Subplebbits Hooks + +#### Communities Hooks + ``` -useSubplebbit({subplebbitAddress: string, onlyIfCached?: boolean}): Subplebbit -useSubplebbits({subplebbitAddresses: string[], onlyIfCached?: boolean}): {subplebbits: Subplebbits[]} -useSubplebbitStats({subplebbitAddress: string, onlyIfCached?: boolean}): SubplebbitStats -useResolvedSubplebbitAddress({subplebbitAddress: string, cache: boolean}): {resolvedAddress: string | undefined} // use {cache: false} when checking the user's own subplebbit address +useCommunity({communityAddress: string, onlyIfCached?: boolean}): Community +useCommunities({communityAddresses: string[], onlyIfCached?: boolean}): {communities: Communities[]} +useCommunityStats({communityAddress: string, onlyIfCached?: boolean}): CommunityStats +useResolvedCommunityAddress({communityAddress: string, cache: boolean}): {resolvedAddress: string | undefined} // use {cache: false} when checking the user's own community address ``` + #### Authors Hooks + ``` useAuthor({authorAddress: string, commentCid: string}): {author: Author | undefined} useAuthorAddress({comment: Comment}): {authorAddress: string | undefined, shortAuthorAddress: string | undefined, authorAddressChanged: boolean} @@ -129,33 +136,44 @@ useResolvedAuthorAddress({author?: Author, cache?: boolean}): {resolvedAddress: useAuthorAvatar({author?: Author}): {imageUrl: string | undefined} setAuthorAvatarsWhitelistedTokenAddresses(tokenAddresses: string[]) ``` + #### Feeds Hooks + ``` -useFeed({subplebbitAddresses: string[], sortType?: string, postsPerPage?: number, filter?: CommentsFilter, newerThan?: number, accountComments?: {newerThan: number, append?: boolean}, modQueue: ['pendingApproval']}): {feed: Comment[], loadMore: function, hasMore: boolean, reset: function, updatedFeed: Comment[], bufferedFeed: Comment[], subplebbitAddressesWithNewerPosts: string[]} +useFeed({communityAddresses: string[], sortType?: string, postsPerPage?: number, filter?: CommentsFilter, newerThan?: number, accountComments?: {newerThan: number, append?: boolean}, modQueue: ['pendingApproval']}): {feed: Comment[], loadMore: function, hasMore: boolean, reset: function, updatedFeed: Comment[], bufferedFeed: Comment[], communityAddressesWithNewerPosts: string[]} useBufferedFeeds({feedsOptions: UseFeedOptions[]}) // preload or buffer feeds in the background, so they load faster when you call `useFeed` ``` -`useFeed().reset()` clears the current feed and refreshes the latest subplebbit snapshots before rebuilding it. + +`useFeed().reset()` clears the current feed and refreshes the latest community snapshots before rebuilding it. + #### Actions Hooks + ``` -useSubscribe({subplebbitAddress: string}): {subscribed: boolean | undefined, subscribe: Function, unsubscribe: Function} +useSubscribe({communityAddress: string}): {subscribed: boolean | undefined, subscribe: Function, unsubscribe: Function} useBlock({address?: string, cid?: string}): {blocked: boolean | undefined, block: Function, unblock: Function} usePublishComment(options: UsePublishCommentOptions): {index: number, abandonPublish: () => Promise, ...UsePublishCommentResult} usePublishVote(options: UsePublishVoteOptions): UsePublishVoteResult usePublishCommentEdit(options: UsePublishCommentEditOptions): UsePublishCommentEditResult usePublishCommentModeration(options: UsePublishCommentModerationOptions): UsePublishCommentModerationResult -usePublishSubplebbitEdit(options: UsePublishSubplebbitEditOptions): UsePublishSubplebbitEditResult -useCreateSubplebbit(options: CreateSubplebbitOptions): {createdSubplebbit: Subplebbit | undefined, createSubplebbit: Function} +usePublishCommunityEdit(options: UsePublishCommunityEditOptions): UsePublishCommunityEditResult +useCreateCommunity(options: CreateCommunityOptions): {createdCommunity: Community | undefined, createCommunity: Function} ``` + #### States Hooks + ``` -useClientsStates({comment?: Comment, subplebbit?: Subplebbit}): {states, peers} -useSubplebbitsStates({subplebbitAddresses: string[]}): {states, peers} +useClientsStates({comment?: Comment, community?: Community}): {states, peers} +useCommunitiesStates({communityAddresses: string[]}): {states, peers} ``` + #### Plebbit RPC Hooks + ``` usePlebbitRpcSettings(): {plebbitRpcSettings: {plebbitOptions, challenges}, setPlebbitRpcSettings: Function} ``` + #### Actions with no hooks implementations yet + ``` createAccount(account: Account) deleteAccount(accountName: string) @@ -164,14 +182,16 @@ setActiveAccount(accountName: string) setAccountsOrder(accountNames: string[]) importAccount(serializedAccount: string) exportAccount(accountName: string): string // don't allow undefined to prevent catastrophic bugs -deleteSubplebbit(subplebbitAddress: string, accountName?: string) +deleteCommunity(communityAddress: string, accountName?: string) deleteComment(commentCidOrAccountCommentIndex: string | number, accountName?: string): Promise ``` + #### Utility functions + ``` setPlebbitJs(PlebbitJs) // set which plebbit-js version to use, e.g. to mock content for frontend dev or to use the node version in Electron deleteDatabases() // delete all databases, including all caches and accounts data -deleteCaches() // delete the cached comments, cached subplebbits and cached pages only, no accounts data +deleteCaches() // delete the cached comments, cached communities and cached pages only, no accounts data ``` ## Recipes @@ -179,60 +199,65 @@ deleteCaches() // delete the cached comments, cached subplebbits and cached page #### Getting started ```jsx -import {useComment, useAccount} from '@bitsocialnet/bitsocial-react-hooks' +import { useComment, useAccount } from "@bitsocialnet/bitsocial-react-hooks"; -const account = useAccount() -const comment = useComment({commentCid}) +const account = useAccount(); +const comment = useComment({ commentCid }); ``` #### Get the active account, if none exist in browser database, a default account is generated ```jsx -const account = useAccount() +const account = useAccount(); ``` #### Create accounts and change active account ```jsx -import {useAccount, useAccounts, createAccount, setActiveAccount} from '@bitsocialnet/bitsocial-react-hooks' +import { + useAccount, + useAccounts, + createAccount, + setActiveAccount, +} from "@bitsocialnet/bitsocial-react-hooks"; -const account = useAccount() -const {accounts} = useAccounts() +const account = useAccount(); +const { accounts } = useAccounts(); // on first render -console.log(accounts.length) // 1 -console.log(account.name) // 'Account 1' +console.log(accounts.length); // 1 +console.log(account.name); // 'Account 1' -await createAccount() // create 'Account 2' -await createAccount() // create 'Account 3' -await setActiveAccount('Account 3') +await createAccount(); // create 'Account 2' +await createAccount(); // create 'Account 3' +await setActiveAccount("Account 3"); // on render after updates -console.log(accounts.length) // 3 -console.log(account.name) // 'Account 3' +console.log(accounts.length); // 3 +console.log(account.name); // 'Account 3' // you are now publishing from 'Account 3' because it is the active one -const {publishComment} = usePublishComment(publishCommentOptions) -await publishComment() +const { publishComment } = usePublishComment(publishCommentOptions); +await publishComment(); ``` #### Get a post ```jsx -const post = useComment({commentCid}) +const post = useComment({ commentCid }); // post.author.address should not be used directly, it needs to be verified asynchronously using useAuthorAddress -const {authorAddress, shortAuthorAddress} = useAuthorAddress({comment: post}) +const { authorAddress, shortAuthorAddress } = useAuthorAddress({ comment: post }); // exception: when linking to an author profile page, /u/${comment.author.address}/c/${comment.cid} should be used, not useAuthorAddress({comment}).authorAddress // use many times in a page without affecting performance -const post = useComment({commentCid, onlyIfCached: true}) +const post = useComment({ commentCid, onlyIfCached: true }); // post.replies are not validated, to show replies -const {replies, hasMore, loadMore} = useReplies({comment: post}) +const { replies, hasMore, loadMore } = useReplies({ comment: post }); // to show a preloaded reply without rerenders, validate manually -const {valid} = useValidateComment({comment: post.replies.pages.best.comments[0]}) +const { valid } = useValidateComment({ comment: post.replies.pages.best.comments[0] }); if (valid === false) { // don't show this reply, it's malicious } @@ -242,45 +267,47 @@ if (valid === false) { #### Get a comment ```jsx -const comment = useComment({commentCid}) -const {comments} = useComments({commentCids: [commentCid1, commentCid2, commentCid3]}) +const comment = useComment({ commentCid }); +const { comments } = useComments({ commentCids: [commentCid1, commentCid2, commentCid3] }); // content -console.log(comment.content || comment.link || comment.title) +console.log(comment.content || comment.link || comment.title); // comment.author.address should not be used directly, it needs to be verified asynchronously using useAuthorAddress -const {authorAddress, shortAuthorAddress} = useAuthorAddress({comment}) +const { authorAddress, shortAuthorAddress } = useAuthorAddress({ comment }); // exception: when linking to an author profile page, /u/${comment.author.address}/c/${comment.cid} should be used, not useAuthorAddress({comment}).authorAddress // use without affecting performance -const {comments} = useComments({commentCids, onlyIfCached: true}) +const { comments } = useComments({ commentCids, onlyIfCached: true }); ``` #### Get author avatar ```jsx -const comment = useComment({commentCid}) +const comment = useComment({ commentCid }); // get the nft avatar image url of the comment author -const {imageUrl, state, error, chainProvider, metadataUrl} = useAuthorAvatar({author: comment.author}) +const { imageUrl, state, error, chainProvider, metadataUrl } = useAuthorAvatar({ + author: comment.author, +}); // result -if (state === 'succeeded') { - console.log('Succeeded getting avatar image URL', imageUrl) +if (state === "succeeded") { + console.log("Succeeded getting avatar image URL", imageUrl); } -if (state === 'failed') { - console.log('Failed getting avatar image URL', error.message) +if (state === "failed") { + console.log("Failed getting avatar image URL", error.message); } // pending -if (state === 'fetching-owner') { - console.log('Fetching NFT owner address from chain provider', chainProvider.urls) +if (state === "fetching-owner") { + console.log("Fetching NFT owner address from chain provider", chainProvider.urls); } -if (state === 'fetching-uri') { - console.log('Fetching NFT URI from chain provider URL', chainProvider.urls) +if (state === "fetching-uri") { + console.log("Fetching NFT URI from chain provider URL", chainProvider.urls); } -if (state === 'fetching-metadata') { - console.log('Fetching NFT URI from', metadataUrl) +if (state === "fetching-metadata") { + console.log("Fetching NFT URI from", metadataUrl); } ``` @@ -289,66 +316,78 @@ if (state === 'fetching-metadata') { ```jsx // NOTE: you must have a comment cid from the author to load his profile page // e.g. the page url would be /#/u//c/ -const authorResult = useAuthor({commentCid, authorAddress}) -const {imageUrl} = useAuthorAvatar({author: authorResult.author}) -const {authorComments, lastCommentCid, hasMore, loadMore} = useAuthorComments({commentCid, authorAddress}) +const authorResult = useAuthor({ commentCid, authorAddress }); +const { imageUrl } = useAuthorAvatar({ author: authorResult.author }); +const { authorComments, lastCommentCid, hasMore, loadMore } = useAuthorComments({ + commentCid, + authorAddress, +}); // result -if (authorResult.state === 'succeeded') { - console.log('Succeeded getting author', authorResult.author) +if (authorResult.state === "succeeded") { + console.log("Succeeded getting author", authorResult.author); } -if (state === 'failed') { - console.log('Failed getting author', authorResult.error.message) +if (state === "failed") { + console.log("Failed getting author", authorResult.error.message); } // listing the author comments with infinite scroll -import {Virtuoso} from 'react-virtuoso' +import { Virtuoso } from "react-virtuoso"; } + itemContent={(index, comment) => } useWindowScroll={true} - components={{Footer: hasMore ? () => : undefined}} + components={{ Footer: hasMore ? () => : undefined }} endReached={loadMore} - increaseViewportBy={{bottom: 600, top: 600}} -/> + increaseViewportBy={{ bottom: 600, top: 600 }} +/>; // it is recommended to always redirect the user to the last known comment cid // in case they want to share the url with someone, the author's comments // will load faster when using the last comment cid -import {useParams} from 'react-router-dom' -const params = useParams() +import { useParams } from "react-router-dom"; +const params = useParams(); useEffect(() => { if (lastCommentCid && params.comentCid !== lastCommentCid) { history.push(`/u/${params.authorAddress}/c/${lastCommentCid}`); } -}, [lastCommentCid]) +}, [lastCommentCid]); // search an author's comments const createSearchFilter = (searchTerm) => ({ filter: (comment) => comment.title?.includes(searchTerm) || comment.content?.includes(searchTerm), - key: `includes-${searchTerm}` // required key to cache the filter -}) -const filter = createSearchFilter('bitcoin') -const {authorComments, lastCommentCid, hasMore, loadMore} = useAuthorComments({commentCid, authorAddress, filter}) + key: `includes-${searchTerm}`, // required key to cache the filter +}); +const filter = createSearchFilter("bitcoin"); +const { authorComments, lastCommentCid, hasMore, loadMore } = useAuthorComments({ + commentCid, + authorAddress, + filter, +}); ``` -#### Get a subplebbit +#### Get a community ```jsx -const subplebbit = useSubplebbit({subplebbitAddress}) -const subplebbitStats = useSubplebbitStats({subplebbitAddress}) -const {subplebbits} = useSubplebbits({subplebbitAddresses: [subplebbitAddress, subplebbitAddress2, subplebbitAddress3]}) +const community = useCommunity({ communityAddress }); +const communityStats = useCommunityStats({ communityAddress }); +const { communities } = useCommunities({ + communityAddresses: [communityAddress, communityAddress2, communityAddress3], +}); // use without affecting performance -const {subplebbits} = useSubplebbits({subplebbitAddresses: [subplebbitAddress, subplebbitAddress2, subplebbitAddress3], onlyIfCached: true}) +const { communities } = useCommunities({ + communityAddresses: [communityAddress, communityAddress2, communityAddress3], + onlyIfCached: true, +}); -// subplebbit.posts are not validated, to show posts -const {feed, hasMore, loadMore} = useFeed({subplebbitAddresses: [subplebbitAddress]}) +// community.posts are not validated, to show posts +const { feed, hasMore, loadMore } = useFeed({ communityAddresses: [communityAddress] }); // to show a preloaded post without rerenders, validate manually -const {valid} = useValidateComment({comment: subplebbit.posts.pages.topAll.comments[0]}) +const { valid } = useValidateComment({ comment: community.posts.pages.topAll.comments[0] }); if (valid === false) { // don't show this post, it's malicious } @@ -376,7 +415,7 @@ const onChallenge = async (challenges: Challenge[], comment: Comment) => { const onChallengeVerification = (challengeVerification, comment) => { // if the challengeVerification fails, a new challenge request will be sent automatically // to break the loop, the user must decline to send a challenge answer - // if the subplebbit owner sends more than 1 challenge for the same challenge request, subsequents will be ignored + // if the community owner sends more than 1 challenge for the same challenge request, subsequents will be ignored if (challengeVerification.challengeSuccess === true) { console.log('challenge success', {publishedCid: challengeVerification.publication.cid}) } @@ -390,7 +429,7 @@ const onError = (error, comment) => console.error(error) const publishCommentOptions = { content: 'hello', title: 'hello', - subplebbitAddress: '12D3KooW...', + communityAddress: '12D3KooW...', onChallenge, onChallengeVerification, onError @@ -413,7 +452,7 @@ if (index !== undefined) { // on the "pending" comment page, you can get the pending comment by doing // const accountComment = useAccountComment({commentIndex: index}) // after accountComment.cid gets defined, it means the comment was published successfully - // it is recommended to immediately redirect to `/p/${accountComment.subplebbitAddress}/c/${useAccountComment.cid}` + // it is recommended to immediately redirect to `/p/${accountComment.communityAddress}/c/${useAccountComment.cid}` } // if the user closes the challenge modal and wants to cancel publishing: @@ -425,7 +464,7 @@ await abandonPublish() const publishReplyOptions = { content: 'hello', parentCid: 'Qm...', // the cid of the comment to reply to - subplebbitAddress: '12D3KooW...', + communityAddress: '12D3KooW...', onChallenge, onChallengeVerification, onError @@ -442,12 +481,21 @@ await publishComment() ```jsx const publishCommentOptions = { - content: 'hello', - title: 'hello', - subplebbitAddress: '12D3KooW...', -} - -const {index, state, publishComment, challenge, challengeVerification, publishChallengeAnswers, abandonPublish, error} = usePublishComment(publishCommentOptions) + content: "hello", + title: "hello", + communityAddress: "12D3KooW...", +}; + +const { + index, + state, + publishComment, + challenge, + challengeVerification, + publishChallengeAnswers, + abandonPublish, + error, +} = usePublishComment(publishCommentOptions); if (challenge) { // display challenges to user and call publishChallengeAnswers(challengeAnswers) @@ -464,113 +512,109 @@ if (error) { // if the user closes your challenge modal: if (challenge && challengeModalClosedByUser) { - await abandonPublish() + await abandonPublish(); } // after publishComment is called, the account comment index gets defined // it is recommended to immediately redirect the user to a page displaying // the user's comment with a "pending" label if (index !== undefined) { - history.push(`/profile/c/${index}`) + history.push(`/profile/c/${index}`); // on the "pending" comment page, you can get the pending comment by doing // const accountComment = useAccountComment({commentIndex: index}) // after accountComment.cid gets defined, it means the comment was published successfully - // it is recommended to immediately redirect to `/p/${accountComment.subplebbitAddress}/c/${useAccountComment.cid}` + // it is recommended to immediately redirect to `/p/${accountComment.communityAddress}/c/${useAccountComment.cid}` } // create post -await publishComment() +await publishComment(); ``` #### Create a post or comment anonymously (without account.signer or account.author) ```jsx -const account = useAccount() -const signer = await account.plebbit.createSigner() +const account = useAccount(); +const signer = await account.plebbit.createSigner(); const publishCommentOptions = { - content: 'hello', - title: 'hello', - subplebbitAddress: '12D3KooW...', + content: "hello", + title: "hello", + communityAddress: "12D3KooW...", // use a newly generated author address (optional) signer, // use a different display name (optional) author: { - displayName: 'Esteban', - address: signer.address - } -} + displayName: "Esteban", + address: signer.address, + }, +}; -const {publishComment} = usePublishComment(publishCommentOptions) -await publishComment() +const { publishComment } = usePublishComment(publishCommentOptions); +await publishComment(); ``` #### Create a vote ```jsx -const commentCid = 'QmZVYzLChjKrYDVty6e5JokKffGDZivmEJz9318EYfp2ui' +const commentCid = "QmZVYzLChjKrYDVty6e5JokKffGDZivmEJz9318EYfp2ui"; const publishVoteOptions = { commentCid, vote: 1, - subplebbitAddress: 'news.eth', + communityAddress: "news.eth", onChallenge, onChallengeVerification, - onError -} -const {state, error, publishVote} = usePublishVote(publishVoteOptions) + onError, +}; +const { state, error, publishVote } = usePublishVote(publishVoteOptions); -await publishVote() -console.log(state) -console.log(error) +await publishVote(); +console.log(state); +console.log(error); // display the user's vote -const {vote} = useAccountVote({commentCid}) - -if (vote === 1) - console.log('user voted 1') -if (vote === -1) - console.log('user voted -1') -if (vote === 0) - console.log('user voted 0') -if (vote === undefined) - console.log(`user didn't vote yet`) +const { vote } = useAccountVote({ commentCid }); + +if (vote === 1) console.log("user voted 1"); +if (vote === -1) console.log("user voted -1"); +if (vote === 0) console.log("user voted 0"); +if (vote === undefined) console.log(`user didn't vote yet`); ``` #### Create a comment edit ```jsx const publishCommentEditOptions = { - commentCid: 'QmZVYzLChjKrYDVty6e5JokKffGDZivmEJz9318EYfp2ui', - content: 'edited content', - subplebbitAddress: 'news.eth', + commentCid: "QmZVYzLChjKrYDVty6e5JokKffGDZivmEJz9318EYfp2ui", + content: "edited content", + communityAddress: "news.eth", onChallenge, onChallengeVerification, - onError -} -const {state, error, publishCommentEdit} = usePublishCommentEdit(publishCommentEditOptions) + onError, +}; +const { state, error, publishCommentEdit } = usePublishCommentEdit(publishCommentEditOptions); -await publishCommentEdit() -console.log(state) -console.log(error) +await publishCommentEdit(); +console.log(state); +console.log(error); // view the status of a comment edit instantly -let comment = useComment({commentCid: publishCommentEditOptions.commentCid}) -const {state: editedCommentState, editedComment} = useEditedComment({comment}) +let comment = useComment({ commentCid: publishCommentEditOptions.commentCid }); +const { state: editedCommentState, editedComment } = useEditedComment({ comment }); // if the comment has a succeeded, failed or pending edit, use the edited comment if (editedComment) { - comment = editedComment + comment = editedComment; } -let editLabel -if (editedCommentState === 'succeeded') { - editLabel = {text: 'EDITED', color: 'green'} +let editLabel; +if (editedCommentState === "succeeded") { + editLabel = { text: "EDITED", color: "green" }; } -if (editedCommentState === 'pending') { - editLabel = {text: 'PENDING EDIT', color: 'orange'} +if (editedCommentState === "pending") { + editLabel = { text: "PENDING EDIT", color: "orange" }; } -if (editedCommentState === 'failed') { - editLabel = {text: 'FAILED EDIT', color: 'red'} +if (editedCommentState === "failed") { + editLabel = { text: "FAILED EDIT", color: "red" }; } ``` @@ -578,37 +622,39 @@ if (editedCommentState === 'failed') { ```jsx const publishCommentModerationOptions = { - commentCid: 'QmZVYzLChjKrYDVty6e5JokKffGDZivmEJz9318EYfp2ui', - subplebbitAddress: 'news.eth', - commentModeration: {locked: true}, + commentCid: "QmZVYzLChjKrYDVty6e5JokKffGDZivmEJz9318EYfp2ui", + communityAddress: "news.eth", + commentModeration: { locked: true }, onChallenge, onChallengeVerification, - onError -} -const {state, error, publishCommentModeration} = usePublishCommentModeration(publishCommentModerationOptions) + onError, +}; +const { state, error, publishCommentModeration } = usePublishCommentModeration( + publishCommentModerationOptions, +); -await publishCommentModeration() -console.log(state) -console.log(error) +await publishCommentModeration(); +console.log(state); +console.log(error); // view the status of a comment moderation instantly -let comment = useComment({commentCid: publishCommentModerationOptions.commentCid}) -const {state: editedCommentState, editedComment} = useEditedComment({comment}) +let comment = useComment({ commentCid: publishCommentModerationOptions.commentCid }); +const { state: editedCommentState, editedComment } = useEditedComment({ comment }); // if the comment has a succeeded, failed or pending edit, use the edited comment if (editedComment) { - comment = editedComment + comment = editedComment; } -let editLabel -if (editedCommentState === 'succeeded') { - editLabel = {text: 'EDITED', color: 'green'} +let editLabel; +if (editedCommentState === "succeeded") { + editLabel = { text: "EDITED", color: "green" }; } -if (editedCommentState === 'pending') { - editLabel = {text: 'PENDING EDIT', color: 'orange'} +if (editedCommentState === "pending") { + editLabel = { text: "PENDING EDIT", color: "orange" }; } -if (editedCommentState === 'failed') { - editLabel = {text: 'FAILED EDIT', color: 'red'} +if (editedCommentState === "failed") { + editLabel = { text: "FAILED EDIT", color: "red" }; } ``` @@ -620,28 +666,28 @@ This only removes local account history entries; it does not delete already-publ **1. Abandon a pending publish** — if you just published and want to cancel before it propagates: ```jsx -const {publishComment, abandonPublish} = usePublishComment(publishCommentOptions) +const { publishComment, abandonPublish } = usePublishComment(publishCommentOptions); -await publishComment() +await publishComment(); // User changes mind — abandon the pending comment -await abandonPublish() +await abandonPublish(); // Hook state returns to ready; the comment is removed from accountComments ``` **2. Delete by index or CID** — remove any of your comments (pending or published): ```jsx -import {deleteComment, useAccountComments} from '@bitsocialnet/bitsocial-react-hooks' +import { deleteComment, useAccountComments } from "@bitsocialnet/bitsocial-react-hooks"; // By account comment index (from usePublishComment or useAccountComment) -const {index, publishComment} = usePublishComment(publishCommentOptions) -await publishComment() -await deleteComment(index) +const { index, publishComment } = usePublishComment(publishCommentOptions); +await publishComment(); +await deleteComment(index); // By comment CID (from useAccountComments or useAccountComment) -const {accountComments} = useAccountComments() -const accountComment = accountComments[0] -await deleteComment(accountComment.cid) +const { accountComments } = useAccountComments(); +const accountComment = accountComments[0]; +await deleteComment(accountComment.cid); ``` > **Note:** `accountComment.index` can change after deletions. If you delete a comment, indices of comments after it may shift. Prefer using `commentCid` when you need a stable identifier, or re-fetch `accountComments` after deletions. @@ -649,44 +695,47 @@ await deleteComment(accountComment.cid) **Common cleanup pattern (remove failed UI clutter):** ```jsx -import {deleteComment, useAccountComments} from '@bitsocialnet/bitsocial-react-hooks' +import { deleteComment, useAccountComments } from "@bitsocialnet/bitsocial-react-hooks"; -const {accountComments} = useAccountComments() -const failedComments = accountComments.filter((comment) => comment.state === 'failed') +const { accountComments } = useAccountComments(); +const failedComments = accountComments.filter((comment) => comment.state === "failed"); for (const failedComment of failedComments) { // failed pending comments may not have a cid yet, so fallback to index - await deleteComment(failedComment.cid || failedComment.index) + await deleteComment(failedComment.cid || failedComment.index); } ``` -#### Subscribe to a subplebbit +#### Subscribe to a community ```jsx -let subplebbitAddress = 'news.eth' -subplebbitAddress = '12D3KooWANwdyPERMQaCgiMnTT1t3Lr4XLFbK1z4ptFVhW2ozg1z' -subplebbitAddress = 'tech.eth' -const {subscribed, subscribe, unsubscribe} = useSubscribe({subplebbitAddress}) -await subscribe() -console.log(subscribed) // true +let communityAddress = "news.eth"; +communityAddress = "12D3KooWANwdyPERMQaCgiMnTT1t3Lr4XLFbK1z4ptFVhW2ozg1z"; +communityAddress = "tech.eth"; +const { subscribed, subscribe, unsubscribe } = useSubscribe({ communityAddress }); +await subscribe(); +console.log(subscribed); // true // view subscriptions -const account = useAccount() -console.log(account.subscriptions) // ['news.eth', '12D3KooWANwdyPERMQaCgiMnTT1t3Lr4XLFbK1z4ptFVhW2ozg1z', 'tech.eth'] +const account = useAccount(); +console.log(account.subscriptions); // ['news.eth', '12D3KooWANwdyPERMQaCgiMnTT1t3Lr4XLFbK1z4ptFVhW2ozg1z', 'tech.eth'] // unsubscribe -await unsubscribe() +await unsubscribe(); // get a feed of subscriptions -const {feed, hasMore, loadMore} = useFeed({subplebbitAddresses: account.subscriptions, sortType: 'topAll'}) -console.log(feed) +const { feed, hasMore, loadMore } = useFeed({ + communityAddresses: account.subscriptions, + sortType: "topAll", +}); +console.log(feed); ``` #### Get feed ```jsx import {Virtuoso} from 'react-virtuoso' -const {feed, hasMore, loadMore} = useFeed({subplebbitAddresses: ['memes.eth', '12D3KooW...', '12D3KooW...'], sortType: 'topAll'}) +const {feed, hasMore, loadMore} = useFeed({communityAddresses: ['memes.eth', '12D3KooW...', '12D3KooW...'], sortType: 'topAll'}) ({ key: `includes-${searchTerm}` // required key to cache the filter }) const filter = createSearchFilter('bitcoin') -const {feed, hasMore, loadMore} = useFeed({subplebbitAddresses, filter}) +const {feed, hasMore, loadMore} = useFeed({communityAddresses, filter}) // image only feed const filter = { filter: (comment) => getCommentLinkMediaType(comment?.link) === 'image', key: 'image-only' // required key to cache the filter } -const {feed, hasMore, loadMore} = useFeed({subplebbitAddresses, filter}) +const {feed, hasMore, loadMore} = useFeed({communityAddresses, filter}) ``` #### Get mod queue (pending approval) @@ -728,7 +777,7 @@ const {feed, hasMore, loadMore} = useFeed({subplebbitAddresses, filter}) ```jsx import {Virtuoso} from 'react-virtuoso' const {feed, hasMore, loadMore} = useFeed({ - subplebbitAddresses: ['memes.eth', '12D3KooW...', '12D3KooW...'], + communityAddresses: ['memes.eth', '12D3KooW...', '12D3KooW...'], modQueue: ['pendingApproval'] }) @@ -748,18 +797,20 @@ Comments automatically drop out of this feed once they are no longer returned by ```jsx const publishCommentModerationOptions = { - commentCid: 'QmZVYzLChjKrYDVty6e5JokKffGDZivmEJz9318EYfp2ui', - subplebbitAddress: 'news.eth', - commentModeration: {approved: true}, + commentCid: "QmZVYzLChjKrYDVty6e5JokKffGDZivmEJz9318EYfp2ui", + communityAddress: "news.eth", + commentModeration: { approved: true }, onChallenge, onChallengeVerification, - onError -} -const {state, error, publishCommentModeration} = usePublishCommentModeration(publishCommentModerationOptions) - -await publishCommentModeration() -console.log(state) -console.log(error) + onError, +}; +const { state, error, publishCommentModeration } = usePublishCommentModeration( + publishCommentModerationOptions, +); + +await publishCommentModeration(); +console.log(state); +console.log(error); ``` #### Edit an account @@ -779,7 +830,7 @@ await setAccount(editedAccount) // check if the user has set their .eth or .bso author name properly, use {cache: false} or it won't update const author = {...account.author, address: 'username.bso'} // or 'username.eth' // authorAddress should equal to account.signer.address -const {resolvedAddress, state, error, chainProvider} = useResolvedAuthorAddress({author, cache: false}) +const {resolvedAddress, state, error, chainProvider} = useResolvedAuthorAddress({author, cache: false}) // result if (state === 'succeeded') { @@ -800,91 +851,105 @@ if (state === 'resolving') { > Note: deleting account is unrecoverable, warn the user to export/backup his account before deleting ```jsx -import {deleteAccount} from '@bitsocialnet/bitsocial-react-hooks' +import { deleteAccount } from "@bitsocialnet/bitsocial-react-hooks"; // delete active account -await deleteAccount() +await deleteAccount(); // delete account by name -await deleteAccount('Account 2') +await deleteAccount("Account 2"); ``` #### Get your own comments and votes ```jsx // all my own comments -const {accountComments} = useAccountComments() +const { accountComments } = useAccountComments(); for (const accountComment of accountComments) { // it is recommended to show a label in the UI if accountComment.state is 'pending' or 'failed' - console.log('comment', accountComment.index, 'is status', accountComment.state) + console.log("comment", accountComment.index, "is status", accountComment.state); } // note: accountComment.index can change after deletions; prefer commentCid for stable identifiers // all my own votes -const {accountVotes} = useAccountVotes() +const { accountVotes } = useAccountVotes(); // my own comments in memes.eth -const subplebbitAddress = 'memes.eth' -const filter = useCallback((comment) => comment.subplebbitAddress === subplebbitAddress, [subplebbitAddress]) // important to use useCallback or the same function or will cause rerenders -const myCommentsInMemesEth = useAccountComments({filter}) +const communityAddress = "memes.eth"; +const filter = useCallback( + (comment) => comment.communityAddress === communityAddress, + [communityAddress], +); // important to use useCallback or the same function or will cause rerenders +const myCommentsInMemesEth = useAccountComments({ filter }); // my own posts in memes.eth -const filter = useCallback((comment) => comment.subplebbitAddress === subplebbitAddress && !comment.parentCid, [subplebbitAddress]) -const myPostsInMemesEth = useAccountComments({filter}) +const filter = useCallback( + (comment) => comment.communityAddress === communityAddress && !comment.parentCid, + [communityAddress], +); +const myPostsInMemesEth = useAccountComments({ filter }); // my own replies in a post with cid 'Qm...' -const postCid = 'Qm...' -const filter = useCallback((comment) => comment.postCid === postCid, [postCid]) -const myCommentsInSomePost = useAccountComments({filter}) +const postCid = "Qm..."; +const filter = useCallback((comment) => comment.postCid === postCid, [postCid]); +const myCommentsInSomePost = useAccountComments({ filter }); // my own replies to a comment with cid 'Qm...' -const parentCommentCid = 'Qm...' -const filter = useCallback((comment) => comment.parentCid === parentCommentCid, [parentCommentCid]) -const myRepliesToSomeComment = useAccountComments({filter}) +const parentCommentCid = "Qm..."; +const filter = useCallback((comment) => comment.parentCid === parentCommentCid, [parentCommentCid]); +const myRepliesToSomeComment = useAccountComments({ filter }); // know if you upvoted a comment already with cid 'Qm...' -const {vote} = useAccountVote({commentCid: 'Qm...'}) -console.log(vote) // 1, -1 or 0 +const { vote } = useAccountVote({ commentCid: "Qm..." }); +console.log(vote); // 1, -1 or 0 // my own pending posts in a feed -const {feed} = useFeed({subplebbitAddresses: [subplebbitAddress], accountComments: {newerThan: Infinity, append: false}}) +const { feed } = useFeed({ + communityAddresses: [communityAddress], + accountComments: { newerThan: Infinity, append: false }, +}); // my own pending replies in a replies feed -const {replies} = useReplies({comment: post, accountComments: {newerThan: Infinity, append: false}}) +const { replies } = useReplies({ + comment: post, + accountComments: { newerThan: Infinity, append: false }, +}); ``` #### Determine if a comment is your own ```jsx -const account = useAccount() -const comment = useComment({commentCid}) -const isMyOwnComment = account?.author.address === comment?.author.address +const account = useAccount(); +const comment = useComment({ commentCid }); +const isMyOwnComment = account?.author.address === comment?.author.address; ``` #### Get account notifications ```jsx -const {notifications, markAsRead} = useNotifications() +const { notifications, markAsRead } = useNotifications(); for (const notification of notifications) { - console.log(notification) + console.log(notification); } -await markAsRead() +await markAsRead(); -const johnsNotifications = useNotifications({accountName: 'John'}) +const johnsNotifications = useNotifications({ accountName: "John" }); for (const notification of johnsNotifications.notifications) { - console.log(notification) + console.log(notification); } -await johnsNotifications.markAsRead() +await johnsNotifications.markAsRead(); // get the unread notification counts for all accounts -const {accounts} = useAccounts() -const accountsUnreadNotificationsCounts = accounts?.map(account => account.unreadNotificationCount) +const { accounts } = useAccounts(); +const accountsUnreadNotificationsCounts = accounts?.map( + (account) => account.unreadNotificationCount, +); ``` -#### Block an address (author, subplebbit or multisub) +#### Block an address (author, community or multisub) ```jsx -const address: 'subplebbit-address.eth' // or 'author-address.eth' or '12D3KooW...' +const address: 'community-address.eth' // or 'author-address.eth' or '12D3KooW...' const {blocked, unblock, block} = useBlock({address}) if (blocked) { @@ -904,101 +969,102 @@ unblock() #### Block a cid (hide a comment) ```jsx -const {blocked, unblock, block} = useBlock({cid: 'Qm...'}) +const { blocked, unblock, block } = useBlock({ cid: "Qm..." }); if (blocked) { - console.log(`'${cid}' is blocked`) -} -else { - console.log(`'${cid}' is not blocked`) + console.log(`'${cid}' is blocked`); +} else { + console.log(`'${cid}' is not blocked`); } // to block -block() +block(); // to unblock -unblock() +unblock(); ``` -#### (Desktop only) Create a subplebbit +#### (Desktop only) Create a community ```jsx -const createSubplebbitOptions = {title: 'My subplebbit title'} -const {createdSubplebbit, createSubplebbit} = useCreateSubplebbit(createSubplebbitOptions) -await createSubplebbit() - -// it is recommended to redirect to `p/${createdSubplebbit.address}` after creation -if (createdSubplebbit?.address) { - console.log('created subplebbit with title', createdSubplebbit.title) - history.push(`/p/${createdSubplebbit.address}`) +const createCommunityOptions = { title: "My community title" }; +const { createdCommunity, createCommunity } = useCreateCommunity(createCommunityOptions); +await createCommunity(); + +// it is recommended to redirect to `p/${createdCommunity.address}` after creation +if (createdCommunity?.address) { + console.log("created community with title", createdCommunity.title); + history.push(`/p/${createdCommunity.address}`); } -// after the subplebbit is created, fetch it using -const {accountSubplebbits} = useAccountSubplebbits() -const accountSubplebbitAddresses = Object.keys(accountSubplebbits) -const subplebbits = useSubplebbits({subplebbitAddresses: accountSubplebbitAddresses}) +// after the community is created, fetch it using +const { accountCommunities } = useAccountCommunities(); +const accountCommunityAddresses = Object.keys(accountCommunities); +const communities = useCommunities({ communityAddresses: accountCommunityAddresses }); // or -const _subplebbit = useSubplebbit({subplebbitAddress: createdSubplebbit.address}) +const _community = useCommunity({ communityAddress: createdCommunity.address }); ``` -#### (Desktop only) List the subplebbits you created +#### (Desktop only) List the communities you created ```jsx -const {accountSubplebbits} = useAccountSubplebbits() -const ownerSubplebbitAddresses = Object.keys(accountSubplebbits).map(subplebbitAddress => accountSubplebbits[subplebbitAddress].role === 'owner') -const subplebbits = useSubplebbits({subplebbitAddresses: ownerSubplebbitAddresses}) +const { accountCommunities } = useAccountCommunities(); +const ownerCommunityAddresses = Object.keys(accountCommunities).map( + (communityAddress) => accountCommunities[communityAddress].role === "owner", +); +const communities = useCommunities({ communityAddresses: ownerCommunityAddresses }); ``` -#### (Desktop only) Edit your subplebbit settings +#### (Desktop only) Edit your community settings ```jsx -const onChallenge = async (challenges: Challenge[], subplebbitEdit: SubplebbitEdit) => { +const onChallenge = async (challenges: Challenge[], communityEdit: CommunityEdit) => { let challengeAnswers: string[] try { challengeAnswers = await getChallengeAnswersFromUser(challenges) } catch (e) {} if (challengeAnswers) { - await subplebbitEdit.publishChallengeAnswers(challengeAnswers) + await communityEdit.publishChallengeAnswers(challengeAnswers) } } -const onChallengeVerification = (challengeVerification, subplebbitEdit) => { +const onChallengeVerification = (challengeVerification, communityEdit) => { console.log('challenge verified', challengeVerification) } -const onError = (error, subplebbitEdit) => console.error(error) +const onError = (error, communityEdit) => console.error(error) -// add ENS to your subplebbit -const editSubplebbitOptions = { - subplebbitAddress: '12D3KooWANwdyPERMQaCgiMnTT1t3Lr4XLFbK1z4ptFVhW2ozg1z', // the previous address before changing it - address: 'your-subplebbit-address.eth', // the new address to change to - onChallenge, +// add ENS to your community +const editCommunityOptions = { + communityAddress: '12D3KooWANwdyPERMQaCgiMnTT1t3Lr4XLFbK1z4ptFVhW2ozg1z', // the previous address before changing it + address: 'your-community-address.eth', // the new address to change to + onChallenge, onChallengeVerification, onError } -await publishSubplebbitEdit() +await publishCommunityEdit() -// edit other subplebbit settings -const editSubplebbitOptions = { - subplebbitAddress: 'your-subplebbit-address.eth', // the address of the subplebbit to change - title: 'Your title', +// edit other community settings +const editCommunityOptions = { + communityAddress: 'your-community-address.eth', // the address of the community to change + title: 'Your title', description: 'Your description', - onChallenge, + onChallenge, onChallengeVerification, onError } -const {publishSubplebbitEdit} = usePublishSubplebbitEdit(editSubplebbitOptions) -await publishSubplebbitEdit() +const {publishCommunityEdit} = usePublishCommunityEdit(editCommunityOptions) +await publishCommunityEdit() // verify if ENS was set correctly, use {cache: false} or it won't update -const {resolvedAddress} = useResolvedSubplebbitAddress({subplebbitAddress: 'your-subplebbit-address.eth', cache: false}) +const {resolvedAddress} = useResolvedCommunityAddress({communityAddress: 'your-community-address.eth', cache: false}) // result if (state === 'succeeded') { console.log('Succeeded resolving address', resolvedAddress) - console.log('ENS set correctly', resolvedAddress === subplebbit.signer.address) + console.log('ENS set correctly', resolvedAddress === community.signer.address) } if (state === 'failed') { console.log('Failed resolving address', error.message) @@ -1013,152 +1079,182 @@ if (state === 'resolving') { #### Export and import account ```jsx -import {exportAccount, importAccount, setActiveAccount, setAccountsOrder} from '@bitsocialnet/bitsocial-react-hooks' +import { + exportAccount, + importAccount, + setActiveAccount, + setAccountsOrder, +} from "@bitsocialnet/bitsocial-react-hooks"; // get active account 'Account 1' -const activeAccount = useAccount() +const activeAccount = useAccount(); // export active account, tell user to copy or download this json -const activeAccountJson = await exportAccount() +const activeAccountJson = await exportAccount(); // import account -await importAccount(activeAccountJson) +await importAccount(activeAccountJson); // get imported account 'Account 1 2' (' 2' gets added to account.name if account.name already exists) -const importedAccount = useAccount('Account 1 2') +const importedAccount = useAccount("Account 1 2"); // make imported account active account -await setActiveAccount('Account 1 2') +await setActiveAccount("Account 1 2"); // reorder the accounts list -await setAccountsOrder(['Account 1 2', 'Account 1']) +await setAccountsOrder(["Account 1 2", "Account 1"]); ``` #### View the status of a comment edit ```jsx -let comment = useComment({commentCid}) -const {state: editedCommentState, editedComment} = useEditedComment({comment}) +let comment = useComment({ commentCid }); +const { state: editedCommentState, editedComment } = useEditedComment({ comment }); // if the comment has a succeeded, failed or pending edit, use the edited comment if (editedComment) { - comment = editedComment + comment = editedComment; } -let editLabel -if (editedCommentState === 'succeeded') { - editLabel = {text: 'EDITED', color: 'green'} +let editLabel; +if (editedCommentState === "succeeded") { + editLabel = { text: "EDITED", color: "green" }; } -if (editedCommentState === 'pending') { - editLabel = {text: 'PENDING EDIT', color: 'orange'} +if (editedCommentState === "pending") { + editLabel = { text: "PENDING EDIT", color: "orange" }; } -if (editedCommentState === 'failed') { - editLabel = {text: 'FAILED EDIT', color: 'red'} +if (editedCommentState === "failed") { + editLabel = { text: "FAILED EDIT", color: "red" }; } ``` #### View the status of a specific comment edit property ```jsx -const comment = useComment({commentCid}) -const editedComment = useEditedComment({comment}) +const comment = useComment({ commentCid }); +const editedComment = useEditedComment({ comment }); if (editedComment.failedEdits.removed !== undefined) { - console.log('failed editing comment.removed property') + console.log("failed editing comment.removed property"); } if (editedComment.succeededEdits.removed !== undefined) { - console.log('succeeded editing comment.removed property') + console.log("succeeded editing comment.removed property"); } if (editedCommentResult.pendingEdits.removed !== undefined) { - console.log('pending editing comment.removed property') + console.log("pending editing comment.removed property"); } // view the full comment with all edited properties (both succeeded and pending) -console.log(editedComment.editedComment) -console.log(editedComment.editedComment.commentModeration?.removed) +console.log(editedComment.editedComment); +console.log(editedComment.editedComment.commentModeration?.removed); // view the state of all edits of the comment -console.log(editedComment.state) // 'unedited' | 'succeeded' | 'pending' | 'failed' +console.log(editedComment.state); // 'unedited' | 'succeeded' | 'pending' | 'failed' ``` Moderation fields are mirrored on both the legacy top-level keys like `comment.removed` and the nested `comment.commentModeration.removed` shape. -#### List all comment and subplebbit edits the account has performed +#### List all comment and community edits the account has performed ```jsx -const {accountEdits} = useAccountEdits() +const { accountEdits } = useAccountEdits(); for (const accountEdit of accountEdits) { - console.log(accountEdit) + console.log(accountEdit); } -console.log(`there's ${accountEdits.length} account edits`) +console.log(`there's ${accountEdits.length} account edits`); // get only the account edits of a specific comment -const commentCid = 'Qm...' -const filter = useCallback((edit) => edit.commentCid === commentCid, [commentCid]) // important to use useMemo or the same function or will cause rerenders -const {accountEdits} = useAccountEdits({filter}) - -// only get account edits in a specific subplebbit -const subplebbitAddress = 'news.eth' -const filter = useCallback((edit) => edit.subplebbitAddress === subplebbitAddress, [subplebbitAddress]) -const {accountEdits} = useAccountEdits({filter}) +const commentCid = "Qm..."; +const filter = useCallback((edit) => edit.commentCid === commentCid, [commentCid]); // important to use useMemo or the same function or will cause rerenders +const { accountEdits } = useAccountEdits({ filter }); + +// only get account edits in a specific community +const communityAddress = "news.eth"; +const filter = useCallback( + (edit) => edit.communityAddress === communityAddress, + [communityAddress], +); +const { accountEdits } = useAccountEdits({ filter }); ``` #### Get replies to a post (nested or flat) ```jsx -import {useReplies, useComment, useAccountComment} from '@bitsocialnet/bitsocial-react-hooks' +import { useReplies, useComment, useAccountComment } from "@bitsocialnet/bitsocial-react-hooks"; // NOTE: recommended to use the same replies options for all depths, or will load slower -const useRepliesOptions = {sortType: 'best', flat: false, repliesPerPage: 20, accountComments: {newerThan: Infinity, append: false}} - -const Reply = ({reply, updatedReply}) => { - const {replies, updatedReplies, bufferedReplies, hasMore, loadMore} = useReplies({...useRepliesOptions, comment: reply}) +const useRepliesOptions = { + sortType: "best", + flat: false, + repliesPerPage: 20, + accountComments: { newerThan: Infinity, append: false }, +}; + +const Reply = ({ reply, updatedReply }) => { + const { replies, updatedReplies, bufferedReplies, hasMore, loadMore } = useReplies({ + ...useRepliesOptions, + comment: reply, + }); // updatedReply updates values in real time, reply does not - const score = (updatedReply?.upvoteCount || 0) - (updatedReply?.downvoteCount || 0) + const score = (updatedReply?.upvoteCount || 0) - (updatedReply?.downvoteCount || 0); // bufferedReplies updates in real time, can show new replies count in real time - const moreReplies = hasMore && bufferedReplies?.length !== 0 ? `(${bufferedReplies.length} more replies)` : '' + const moreReplies = + hasMore && bufferedReplies?.length !== 0 ? `(${bufferedReplies.length} more replies)` : ""; // publishing states exist only on account comment - const accountReply = useAccountComment({commentIndex: reply.index}) - const state = accountReply?.state - const publishingStateString = useStateString(accountReply) + const accountReply = useAccountComment({ commentIndex: reply.index }); + const state = accountReply?.state; + const publishingStateString = useStateString(accountReply); return (
-
{score} {reply.author.address} {reply.timestamp} {moreReplies}
- {state === 'pending' &&
PENDING ({publishingStateString})
} - {state === 'failed' &&
FAILED
} +
+ {score} {reply.author.address} {reply.timestamp} {moreReplies} +
+ {state === "pending" &&
PENDING ({publishingStateString})
} + {state === "failed" &&
FAILED
}
{reply.content}
-
- {replies.map((reply, index) => )} +
+ {replies.map((reply, index) => ( + + ))}
- ) -} - -const comment = useComment({commentCid}) -const {replies, updatedReplies, hasMore, loadMore} = useReplies({...useRepliesOptions, comment}) -const repliesComponents = replies.map((reply, index) => ) + ); +}; + +const comment = useComment({ commentCid }); +const { replies, updatedReplies, hasMore, loadMore } = useReplies({ + ...useRepliesOptions, + comment, +}); +const repliesComponents = replies.map((reply, index) => ( + +)); ``` #### Get a shortCid or shortAddress (plebbit-js) ```jsx // NOTE: not possible to do from bitsocial-react-hooks, needs plebbit-js -import {getShortAddress, getShortCid} from '@plebbit/plebbit-js' +import { getShortAddress, getShortCid } from "@plebbit/plebbit-js"; -const shortParentCid = getShortAddress(comment.parentCid) -const shortAddress = getShortCid(address) +const shortParentCid = getShortAddress(comment.parentCid); +const shortAddress = getShortCid(address); ``` #### Get a shortCid or shortAddress (hooks) ```jsx -import {useShortAddress, useShortCid} from '@bitsocialnet/bitsocial-react-hooks' +import { useShortAddress, useShortCid } from "@bitsocialnet/bitsocial-react-hooks"; -const shortParentCid = useShortCid(comment.parentCid) -const shortAddress = useShortAddress(address) +const shortParentCid = useShortCid(comment.parentCid); +const shortAddress = useShortAddress(address); ``` #### useBufferedFeeds with concurrency @@ -1166,15 +1262,15 @@ const shortAddress = useShortAddress(address) ```jsx const useBufferedFeedsWithConcurrency = ({feedOptions}) => { - const subplebbits = useSubplebbits() + const communities = useCommunities() return useBufferedFeeds({feedsOptions}) } const feedOptions = [ - {subplebbitAddresses: ['news.eth', 'crypto.eth'], sortType: 'new'}, - {subplebbitAddresses: ['memes.eth'], sortType: 'topWeek'}, - {subplebbitAddresses: ['12D3KooW...', '12D3KooW...', '12D3KooW...', '12D3KooW...'], sortType: 'hot'}, + {communityAddresses: ['news.eth', 'crypto.eth'], sortType: 'new'}, + {communityAddresses: ['memes.eth'], sortType: 'topWeek'}, + {communityAddresses: ['12D3KooW...', '12D3KooW...', '12D3KooW...', '12D3KooW...'], sortType: 'hot'}, ... ] diff --git a/docs/TODO.md b/docs/TODO.md index dd6c103b..0f87b53e 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -6,7 +6,7 @@ - useAuthorAvatar check if ENS has an avatar set up - implement something like useCommentLinkTagName(commentLink?: string): 'a' | 'img' | 'video' | 'audio' to indicate how to display links (only do it after special embeds like twitter, youtube, etc are implemented) - add nft.timetamp caching and validation, if you see them same nft signature twice, only use the latest timestamp -- make comment and subplebbit state succeeded even if fetching an update if the content was found once +- make comment and community state succeeded even if fetching an update if the content was found once - add 'pending' and 'failed' to accountVotes and accountEdits state - implement multiple gateways in nft fetching, or possibly using the best gateway using gateway stats - implement multiple chain providers in ens resolving, or possibly using the best provider using provider stats diff --git a/docs/algorithms.md b/docs/algorithms.md index 75000b84..80e74ad1 100644 --- a/docs/algorithms.md +++ b/docs/algorithms.md @@ -2,19 +2,19 @@ #### Account notifications and own comment updates -On startup, and every time a comment is created, it is added to the AccountsComments store and database. On the comment challengeverification event, the comment CID is received from the subplebbit owner, and we can start listening to comment update events, and update the store and database every time. Sometimes the user closes the page and the challengeverification event is never received, so every time a comment, subplebbit or subplebbit page is fetched, we awkwardly check to see if it has one of our own comment with a missing CID, and update it if found. +On startup, and every time a comment is created, it is added to the AccountsComments store and database. On the comment challengeverification event, the comment CID is received from the community owner, and we can start listening to comment update events, and update the store and database every time. Sometimes the user closes the page and the challengeverification event is never received, so every time a comment, community or community page is fetched, we awkwardly check to see if it has one of our own comment with a missing CID, and update it if found. AccountsCommentsReplies are found on the comment update events and are stored in a last rencently used database and have the field "markedAsRead" once read. `useAccountNotifications` uses the AccountsCommentsReplies to compile the read/unread notifications. TODO: add notifications for upvotes e.g. "Your comment has 10 upvotes". #### Feed pages and infinite scrolling -A "feed" is a combination of a list of subplebbits to fetch, a sort type (hot/top/new/etc) and an account (for its IPFS settings). After using `useFeed(useFeedOptions)`, a feed with those options is added to the feedsStore. After a feed is added to store, its subplebbits are fetched, then the first page of the subplebbit.posts `Pages` are fetched (if needed, usually the 'hot' sort is included with `plebbit.getSubplebbit()`). Each feed has a `pageNumber` which gets incremented on `loadMore` (used by infinite scrolling). Each feed has a list of `SubplebbitsPostsInfo` which keep track of `SubplebbitPostsInfo.bufferedPostCount` for each combination of subplebbit and sort type. When `SubplebbitPostsInfo.bufferedPostCount` gets below 50, the next page for the subplebbit and sort type is fetched. +A "feed" is a combination of a list of communities to fetch, a sort type (hot/top/new/etc) and an account (for its IPFS settings). After using `useFeed(useFeedOptions)`, a feed with those options is added to the feedsStore. After a feed is added to store, its communities are fetched, then the first page of the community.posts `Pages` are fetched (if needed, usually the 'hot' sort is included with `plebbit.getCommunity()`). Each feed has a `pageNumber` which gets incremented on `loadMore` (used by infinite scrolling). Each feed has a list of `CommunitiesPostsInfo` which keep track of `CommunityPostsInfo.bufferedPostCount` for each combination of community and sort type. When `CommunityPostsInfo.bufferedPostCount` gets below 50, the next page for the community and sort type is fetched. When a new post page is received from IPFS, the `feedsStore.bufferedFeeds` are recalculated, but the `feedsStore.loadedFeeds` (which are displayed to the user) are not, new posts fetched will only be displayed to the user the next time he calls `loadMore`. If we detect that a `loadedFeed` is stale, we can prompt the user to load more posts, like Reddit/Facebook/Twitter do. Post pages are cached in IndexedDb for a short time, in case the user reloads the app. -When a subplebbit updates, the buffered feeds are emptied of that subplebbit's posts, and the first page is immediately fetched to try to refill it. TODO: If an updated comment already in `loadedFeeds` is fetched by a new subplebbit page, it should replace the old comment with the new one with updated votes/replies. +When a community updates, the buffered feeds are emptied of that community's posts, and the first page is immediately fetched to try to refill it. TODO: If an updated comment already in `loadedFeeds` is fetched by a new community page, it should replace the old comment with the new one with updated votes/replies. #### Feeds stores @@ -22,45 +22,45 @@ When a subplebbit updates, the buffered feeds are emptied of that subplebbit's p feedsStore { feedsOptions: FeedsOptions bufferedFeeds: Feeds - bufferedPostsCounts: {[subplebbitAddress+sortType: string]: number} + bufferedPostsCounts: {[communityAddress+sortType: string]: number} loadedFeeds: Feeds feedsHaveMore: {[feedName: string]: boolean} // actions addFeedToStore: (feedName: string, ...feedOptions: FeedOptions) => void incrementFeedPageNumber: (feedName: string) => void - // recalculate all feeds using new subplebbits.post.pages, subplebbitsPagesStore and page numbers + // recalculate all feeds using new communities.post.pages, communitiesPagesStore and page numbers updateFeeds: () => void } -subplebbitsStore { - subplebbits: Subplebbits +communitiesStore { + communities: Communities // actions - addSubplebbitToStore: (subplebbitAddress: string) => void + addCommunityToStore: (communityAddress: string) => void } -subplebbitsPagesStore { - subplebbitsPages +communitiesPagesStore { + communitiesPages // actions - // a subplebbit instance only knows its first page CID, so take the first page CID as an argument - // and scroll through every subplebbit next page in the store until you find the last page, then add it - addNextSubplebbitPageToStore: (subplebbitFirstPageCid: string) => void + // a community instance only knows its first page CID, so take the first page CID as an argument + // and scroll through every community next page in the store until you find the last page, then add it + addNextCommunityPageToStore: (communityFirstPageCid: string) => void } ``` #### Flow of adding a new feed -1. user calls useFeed(subplebbitAddresses, sortType) and feed gets added to feeds store -2. feed subplebbits are added to subplebbitsStore +1. user calls useFeed(communityAddresses, sortType) and feed gets added to feeds store +2. feed communities are added to communitiesStore -- in parallel: 3. each feed subplebbit+sortType subscribes to its subplebbit.posts.pages and firstPageCids (subplebbit.posts.pageCids[sortType]) value changing (a subplebbit update) 4. on each subplebbit.posts.pages and firstPageCids change, updateFeeds and bufferedFeedsSubplebbitsPostCounts -- in parallel: 3. each feed subplebbit subscribes to its bufferedFeedsSubplebbitsPostCounts value changing 4. on each bufferedFeedsSubplebbitsPostCounts change, if the bufferedFeedsSubplebbitsPostCounts is below threshold for the subplebbit, add the next subplebbit+sortType page to the subplebbitsPagesStore -- in parallel: 3. each feed subscribes to subplebbitsPagesStore changing - - on each subplebbitsPagesStore change, if any new pages are relevant to the feed: 5. the feed's buffered feeds is rebuilt and bufferedFeedsSubplebbitsPostCounts updated 6. if the loaded feeds is missing posts and buffered feeds has them, rebuild the loaded feeds +- in parallel: 3. each feed community+sortType subscribes to its community.posts.pages and firstPageCids (community.posts.pageCids[sortType]) value changing (a community update) 4. on each community.posts.pages and firstPageCids change, updateFeeds and bufferedFeedsCommunitiesPostCounts +- in parallel: 3. each feed community subscribes to its bufferedFeedsCommunitiesPostCounts value changing 4. on each bufferedFeedsCommunitiesPostCounts change, if the bufferedFeedsCommunitiesPostCounts is below threshold for the community, add the next community+sortType page to the communitiesPagesStore +- in parallel: 3. each feed subscribes to communitiesPagesStore changing + - on each communitiesPagesStore change, if any new pages are relevant to the feed: 5. the feed's buffered feeds is rebuilt and bufferedFeedsCommunitiesPostCounts updated 6. if the loaded feeds is missing posts and buffered feeds has them, rebuild the loaded feeds - in parallel: 3. each feed subscribes to accountsStore changing 4. on each accounts change, like a blockedAddress added for example, updateFeeds -3. update feeds to rebuild the feeds using the already preloaded subplebbits and pages if any +3. update feeds to rebuild the feeds using the already preloaded communities and pages if any #### Flow of incrementing a feed's page -1. the feeds store gets updated with the new page number and loadedFeeds, bufferedFeeds and bufferedFeedsSubplebbitsPostCounts are partially recalculated and updated +1. the feeds store gets updated with the new page number and loadedFeeds, bufferedFeeds and bufferedFeedsCommunitiesPostCounts are partially recalculated and updated #### Replies stores @@ -68,25 +68,25 @@ Similar to feeds store, but with a some differences: - each reply depth needs its own feed, and all the nested feeds must be added at the same time with addFeedsToStore or all depths won't render simultaneously - because nested feeds are added in bulk, not possible to set a custom feedStoreName, must use feedOptionsToFeedName -- feeds store take a subplebbit addresses argument and add the subplebbit to subplebbits store, adding all nested replies to comments store and subscribing to updates would not scale, so instead useReplies takes a comment argument, which is passed to addFeedToStoreOrUpdateComment +- feeds store take a community addresses argument and add the community to communities store, adding all nested replies to comments store and subscribing to updates would not scale, so instead useReplies takes a comment argument, which is passed to addFeedToStoreOrUpdateComment - every time the comment changes, addFeedToStoreOrUpdateComment is called, which calls addFeedsToStore to add all new nested feeds simultaneously - validating replies has a few 100ms delay, but this looks ugly in the ui, so show replies instantly with getRepliesFirstPageSkipValidation, and after validation remove invalid replies, can be turned off with validateOptimistically: false - nested replies don't automatically stream new replies until repliesPerPage is reached, because that would displace the ui, loadMore must be called manually is feedOptions.streamPage is false #### Accounts settings persistance, export, import and caching -All accounts settings, accounts comments and accounts votes are stored permanently in the various IndexedDb databases. Import from file and export to file are possible but not yet implemented. Ephemeral data like random subplebbits, comments and feeds are stored in last recently used IndexedDb databases, and eventually erased. +All accounts settings, accounts comments and accounts votes are stored permanently in the various IndexedDb databases. Import from file and export to file are possible but not yet implemented. Ephemeral data like random communities, comments and feeds are stored in last recently used IndexedDb databases, and eventually erased. #### Editing account.plebbitOptions and replacing the account.plebbit instance -Not implemented, but the easiest method would be to force a page reload, which will reset setting up all the comments and subplebbit listeners. +Not implemented, but the easiest method would be to force a page reload, which will reset setting up all the comments and community listeners. #### Author comments algorithm 1. Start with an author.address and a comment.cid, fetch the comment.cid and validate the comment.author.address - in parallel: 2. Fetch the previous comment using comment.author.previousCommentCid and validate the comment.author.address 3. Continue this process until comment.author.previousCommentCid is undefined (no more comments) -- in parallel: 2. If one of the author comments receives an update with comment.author.subplebbit.lastCommentCid, fetch the lastCommentCid and validate the lastComment.author.address 3. If the lastComment is more recent than the original comment, replace the original comment with the lastComment and start fetching lastComment.author.previousCommentCid 4. Continue this process until comment.author.previousCommentCid is undefined (no more comments) +- in parallel: 2. If one of the author comments receives an update with comment.author.community.lastCommentCid, fetch the lastCommentCid and validate the lastComment.author.address 3. If the lastComment is more recent than the original comment, replace the original comment with the lastComment and start fetching lastComment.author.previousCommentCid 4. Continue this process until comment.author.previousCommentCid is undefined (no more comments) #### Flow of adding authorComments to authorsCommentsStore @@ -97,7 +97,7 @@ Not implemented, but the easiest method would be to force a page reload, which w - in parallel: 5. the fetched nextCommentCidToFetch comment gets added to bufferedCommentCids and filtered loadedComments - in parallel: 5. nextCommentCidToFetch gets set to comment.author.previousCommentCid 6. go back to step 3 -- in parallel: 5. if the updated comment has comment.author.subplebbit.lastCommentCid, add the lastCommentCid to commentsStore\* 6. go back to step 4 +- in parallel: 5. if the updated comment has comment.author.community.lastCommentCid, add the lastCommentCid to commentsStore\* 6. go back to step 4 - in parallel: 5. if the updated comment was a lastCommentCid, and its comment.timestamp is newer than current lastCommentCid comment.timestamp, and newer than all bufferedCommentCids comment.timestamp, set lastCommentCid as comment.cid 6. comment gets added to bufferedCommentCids and filtered loadedComments 7. it is recommended to redirect the user to `/#/u//` so if they share the link they share the most recent commentCid --- @@ -120,13 +120,13 @@ When a comment updates, the buffered feeds are emptied of that comment's replies repliesStore { feedsOptions: FeedsOptions bufferedFeeds: Feeds - bufferedPostsCounts: {[subplebbitAddress+sortType: string]: number} + bufferedPostsCounts: {[communityAddress+sortType: string]: number} loadedFeeds: Feeds feedsHaveMore: {[feedName: string]: boolean} // actions addFeedToStore: (feedName: string, ...feedOptions: FeedOptions) => void incrementFeedPageNumber: (feedName: string) => void - // recalculate all feeds using new subplebbits.post.pages, subplebbitsPagesStore and page numbers + // recalculate all feeds using new communities.post.pages, communitiesPagesStore and page numbers updateFeeds: () => void } commentsStore { diff --git a/docs/clients.md b/docs/clients.md index 1b943044..72d79d37 100644 --- a/docs/clients.md +++ b/docs/clients.md @@ -16,8 +16,8 @@ const getClientHost = (clientUrl) => { return clientHosts[clientUrl]; }; -const useStateString = (commentOrSubplebbit) => { - const { states } = useClientsStates({ comment: commentOrSubplebbit }); +const useStateString = (commentOrCommunity) => { + const { states } = useClientsStates({ comment: commentOrCommunity }); return useMemo(() => { let stateString = ""; for (const state in states) { @@ -42,19 +42,19 @@ const useStateString = (commentOrSubplebbit) => { stateString += `${formattedState} from ${clientHosts.join(", ")}`; } - // fallback to comment or subplebbit state when possible - if (!stateString && commentOrSubplebbit?.state !== "succeeded") { + // fallback to comment or community state when possible + if (!stateString && commentOrCommunity?.state !== "succeeded") { if ( - commentOrSubplebbit?.publishingState && - commentOrSubplebbit?.publishingState !== "stopped" && - commentOrSubplebbit?.publishingState !== "succeeded" + commentOrCommunity?.publishingState && + commentOrCommunity?.publishingState !== "stopped" && + commentOrCommunity?.publishingState !== "succeeded" ) { - stateString = commentOrSubplebbit.publishingState; + stateString = commentOrCommunity.publishingState; } else if ( - commentOrSubplebbit?.updatingState !== "stopped" && - commentOrSubplebbit?.updatingState !== "succeeded" + commentOrCommunity?.updatingState !== "stopped" && + commentOrCommunity?.updatingState !== "succeeded" ) { - stateString = commentOrSubplebbit.updatingState; + stateString = commentOrCommunity.updatingState; } if (stateString) { stateString = stateString @@ -71,26 +71,26 @@ const useStateString = (commentOrSubplebbit) => { // if string is empty, return undefined instead return stateString === "" ? undefined : stateString; - }, [states, commentOrSubplebbit]); + }, [states, commentOrCommunity]); }; export default useStateString; ``` -#### Get subplebbit with state string +#### Get community with state string ```js -const subplebbit = useSubplebbit({ subplebbitAddress }); -const stateString = useStateString(subplebbit); +const community = useCommunity({ communityAddress }); +const stateString = useStateString(community); const errorString = useMemo(() => { - if (subplebbit?.state === "failed") { - let errorString = "Failed fetching subplebbit"; - if (subplebbit.error) { - errorString += `: ${subplebbit.error.toString().slice(0, 300)}`; + if (community?.state === "failed") { + let errorString = "Failed fetching community"; + if (community.error) { + errorString += `: ${community.error.toString().slice(0, 300)}`; } return errorString; } -}, [subplebbit?.state]); +}, [community?.state]); if (stateString) { console.log(stateString); @@ -129,7 +129,7 @@ if (errorString) { ```js import { useMemo } from "react"; -import { useSubplebbit, useSubplebbitsStates } from "@bitsocialnet/bitsocial-react-hooks"; +import { useCommunity, useCommunitiesStates } from "@bitsocialnet/bitsocial-react-hooks"; const clientHosts = {}; const getClientHost = (clientUrl) => { @@ -143,17 +143,17 @@ const getClientHost = (clientUrl) => { return clientHosts[clientUrl]; }; -const useFeedStateString = (subplebbitAddresses) => { - // single subplebbit feed state string - const subplebbitAddress = subplebbitAddresses?.length === 1 ? subplebbitAddresses[0] : undefined; - const subplebbit = useSubplebbit({ subplebbitAddress }); - const singleSubplebbitFeedStateString = useStateString(subplebbit); +const useFeedStateString = (communityAddresses) => { + // single community feed state string + const communityAddress = communityAddresses?.length === 1 ? communityAddresses[0] : undefined; + const community = useCommunity({ communityAddress }); + const singleCommunityFeedStateString = useStateString(community); - // multiple subplebbit feed state string - const { states } = useSubplebbitsStates({ subplebbitAddresses }); + // multiple community feed state string + const { states } = useCommunitiesStates({ communityAddresses }); - const multipleSubplebbitsFeedStateString = useMemo(() => { - if (subplebbitAddress) { + const multipleCommunitiesFeedStateString = useMemo(() => { + if (communityAddress) { return; } @@ -161,9 +161,9 @@ const useFeedStateString = (subplebbitAddresses) => { let stateString = ""; if (states["resolving-address"]) { - const { subplebbitAddresses, clientUrls } = states["resolving-address"]; - if (subplebbitAddresses.length && clientUrls.length) { - stateString += `resolving ${subplebbitAddresses.length} ${subplebbitAddresses.length === 1 ? "address" : "addresses"} from ${clientUrls + const { communityAddresses, clientUrls } = states["resolving-address"]; + if (communityAddresses.length && clientUrls.length) { + stateString += `resolving ${communityAddresses.length} ${communityAddresses.length === 1 ? "address" : "addresses"} from ${clientUrls .map(getClientHost) .join(", ")}`; } @@ -171,19 +171,19 @@ const useFeedStateString = (subplebbitAddresses) => { // find all page client and sub addresses const pagesStatesClientHosts = new Set(); - const pagesStatesSubplebbitAddresses = new Set(); + const pagesStatesCommunityAddresses = new Set(); for (const state in states) { if (state.match("page")) { states[state].clientUrls.forEach((clientUrl) => pagesStatesClientHosts.add(getClientHost(clientUrl)), ); - states[state].subplebbitAddresses.forEach((subplebbitAddress) => - pagesStatesSubplebbitAddresses.add(subplebbitAddress), + states[state].communityAddresses.forEach((communityAddress) => + pagesStatesCommunityAddresses.add(communityAddress), ); } } - if (states["fetching-ipns"] || states["fetching-ipfs"] || pagesStatesSubplebbitAddresses.size) { + if (states["fetching-ipns"] || states["fetching-ipfs"] || pagesStatesCommunityAddresses.size) { // separate 2 different states using ', ' if (stateString) { stateString += ", "; @@ -201,19 +201,19 @@ const useFeedStateString = (subplebbitAddresses) => { if (clientHosts.size) { stateString += "fetching "; if (states["fetching-ipns"]) { - stateString += `${states["fetching-ipns"].subplebbitAddresses.length} IPNS`; + stateString += `${states["fetching-ipns"].communityAddresses.length} IPNS`; } if (states["fetching-ipfs"]) { if (states["fetching-ipns"]) { stateString += ", "; } - stateString += `${states["fetching-ipfs"].subplebbitAddresses.length} IPFS`; + stateString += `${states["fetching-ipfs"].communityAddresses.length} IPFS`; } - if (pagesStatesSubplebbitAddresses.size) { + if (pagesStatesCommunityAddresses.size) { if (states["fetching-ipns"] || states["fetching-ipfs"]) { stateString += ", "; } - stateString += `${pagesStatesSubplebbitAddresses.size} ${pagesStatesSubplebbitAddresses.size === 1 ? "page" : "pages"}`; + stateString += `${pagesStatesCommunityAddresses.size} ${pagesStatesCommunityAddresses.size === 1 ? "page" : "pages"}`; } stateString += ` from ${[...clientHosts].join(", ")}`; } @@ -224,12 +224,12 @@ const useFeedStateString = (subplebbitAddresses) => { // if string is empty, return undefined instead return stateString === "" ? undefined : stateString; - }, [states, subplebbitAddress]); + }, [states, communityAddress]); - if (singleSubplebbitFeedStateString) { - return singleSubplebbitFeedStateString; + if (singleCommunityFeedStateString) { + return singleCommunityFeedStateString; } - return multipleSubplebbitsFeedStateString; + return multipleCommunitiesFeedStateString; }; export default useFeedStateString; @@ -238,22 +238,22 @@ export default useFeedStateString; #### Get feed with single sub with state string ```js -const subplebbitAddress = "memes.eth"; +const communityAddress = "memes.eth"; const { feed, hasMore, loadMore } = useFeed({ - subplebbitAddresses: [subplebbitAddress], + communityAddresses: [communityAddress], sortType: "topAll", }); -const subplebbit = useSubplebbit({ subplebbitAddress }); -const stateString = useFeedStateString(subplebbitAddresses); +const community = useCommunity({ communityAddress }); +const stateString = useFeedStateString(communityAddresses); const errorString = useMemo(() => { - if (subplebbit?.state === "failed") { - let errorString = "Failed fetching subplebbit"; - if (subplebbit.error) { - errorString += `: ${subplebbit.error.toString().slice(0, 300)}`; + if (community?.state === "failed") { + let errorString = "Failed fetching community"; + if (community.error) { + errorString += `: ${community.error.toString().slice(0, 300)}`; } return errorString; } -}, [subplebbit?.state]); +}, [community?.state]); if (stateString) { console.log(stateString); @@ -266,25 +266,25 @@ if (errorString) { #### Get feed with multiple subs with state string ```js -const subplebbitAddresses = ["memes.eth", "12D3KooW...", "12D3KooW..."]; -const { feed, hasMore, loadMore } = useFeed({ subplebbitAddresses, sortType: "topAll" }); -const { subplebbits } = useSubplebbits({ subplebbitAddresses }); -const stateString = useFeedStateString(subplebbitAddresses); +const communityAddresses = ["memes.eth", "12D3KooW...", "12D3KooW..."]; +const { feed, hasMore, loadMore } = useFeed({ communityAddresses, sortType: "topAll" }); +const { communities } = useCommunities({ communityAddresses }); +const stateString = useFeedStateString(communityAddresses); const errorString = useMemo(() => { - // only show error string if all subplebbits updating state are failed - for (const subplebbit of subplebbits) { - if (subplebbit.updatingState !== "failed") { + // only show error string if all communities updating state are failed + for (const community of communities) { + if (community.updatingState !== "failed") { return; } } // only show the first error found because not possible to show all of them - for (const subplebbit of subplebbits) { - if (subplebbit.error) { - return `Failed fetching subplebbit: ${subplebbit.error.toString().slice(0, 300)}`; + for (const community of communities) { + if (community.error) { + return `Failed fetching community: ${community.error.toString().slice(0, 300)}`; } } -}, [subplebbits]); +}, [communities]); if (stateString) { console.log(stateString); @@ -328,22 +328,22 @@ for (const ipfsClientUrl in comment.clients.ipfsClients) { } ``` -#### Get a subplebbit clients states +#### Get a community clients states ```js -const subplebbit = useSubplebbit({ subplebbitAddress }); +const community = useCommunity({ communityAddress }); -// resolving subplebbit address from chain providers -for (const chainProviderUrl in subplebbit.clients.chainProviders) { - const chainProvider = subplebbit.clients.chainProviders[chainProviderUrl]; +// resolving community address from chain providers +for (const chainProviderUrl in community.clients.chainProviders) { + const chainProvider = community.clients.chainProviders[chainProviderUrl]; if (chainProvider.state === "resolving-address") { - console.log(`Resolving subplebbit address from ${chainProviderUrl}`); + console.log(`Resolving community address from ${chainProviderUrl}`); } } // fetching from gateways -for (const ipfsGatewayUrl in subplebbit.clients.ipfsGateways) { - const ipfsGateway = subplebbit.clients.ipfsGateways[ipfsGatewayUrl]; +for (const ipfsGatewayUrl in community.clients.ipfsGateways) { + const ipfsGateway = community.clients.ipfsGateways[ipfsGatewayUrl]; if (ipfsGateway.state === "fetching-ipns") { console.log(`Fetching IPNS from ${ipfsGatewayUrl}`); } @@ -351,13 +351,13 @@ for (const ipfsGatewayUrl in subplebbit.clients.ipfsGateways) { console.log(`Fetching page IPFS from ${ipfsGatewayUrl}`); } if (ipfsGateway.state === "succeeded") { - console.log(`Fetched subplebbit from ${ipfsGatewayUrl}`); + console.log(`Fetched community from ${ipfsGatewayUrl}`); } } // fetching from ipfs clients -for (const ipfsClientUrl in subplebbit.clients.ipfsClients) { - const ipfsClient = subplebbit.clients.ipfsClients[ipfsClientUrl]; +for (const ipfsClientUrl in community.clients.ipfsClients) { + const ipfsClient = community.clients.ipfsClients[ipfsClientUrl]; if (ipfsClient.state === "fetching-ipns") { console.log(`Fetching IPNS from ${ipfsClient.peers.length} peers`); } @@ -368,7 +368,7 @@ for (const ipfsClientUrl in subplebbit.clients.ipfsClients) { console.log(`Fetching page IPFS from ${ipfsClient.peers.length} peers`); } if (ipfsClient.state === "succeeded") { - console.log(`Fetched subplebbit from ${ipfsClient.peers.length} peers`); + console.log(`Fetched community from ${ipfsClient.peers.length} peers`); } } ``` @@ -379,7 +379,7 @@ for (const ipfsClientUrl in subplebbit.clients.ipfsClients) { const publishCommentOptions = { content: "hello", title: "hello", - subplebbitAddress: "12D3KooW...", + communityAddress: "12D3KooW...", onChallenge, onChallengeVerification, onError, @@ -390,19 +390,19 @@ const { clients, publishComment } = usePublishComment(publishCommentOptions); // start publishing await publishComment(); -// resolving subplebbit address from chain providers +// resolving community address from chain providers for (const chainProviderUrl in clients.chainProviders) { const chainProvider = clients.chainProviders[chainProviderUrl]; if (chainProvider.state === "resolving-address") { - console.log(`Resolving subplebbit address from ${chainProviderUrl}`); + console.log(`Resolving community address from ${chainProviderUrl}`); } } // fetching from gateways for (const ipfsGatewayUrl in clients.ipfsGateways) { const ipfsGateway = clients.ipfsGateways[ipfsGatewayUrl]; - if (ipfsGateway.state === "fetching-subplebbit-ipns") { - console.log(`Fetching subplebbit IPNS from ${ipfsGatewayUrl}`); + if (ipfsGateway.state === "fetching-community-ipns") { + console.log(`Fetching community IPNS from ${ipfsGatewayUrl}`); } } @@ -429,11 +429,11 @@ for (const pubsubClientUrl in clients.pubsubClients) { // fetching and publishing from ipfs client for (const ipfsClientUrl in clients.ipfsClients) { const ipfsClient = clients.ipfsClients[ipfsClientUrl]; - if (ipfsClient.state === "fetching-subplebbit-ipns") { - console.log(`Fetching subplebbit IPNS from ${ipfsClient.peers.length} peers`); + if (ipfsClient.state === "fetching-community-ipns") { + console.log(`Fetching community IPNS from ${ipfsClient.peers.length} peers`); } - if (ipfsClient.state === "fetching-subplebbit-ipfs") { - console.log(`Fetching subplebbit IPFS from ${ipfsClient.peers.length} peers`); + if (ipfsClient.state === "fetching-community-ipfs") { + console.log(`Fetching community IPFS from ${ipfsClient.peers.length} peers`); } if (ipfsClient.state === "publishing-challenge-request") { console.log(`Publishing challenge request to ${ipfsClient.peers.length} peers`); @@ -487,102 +487,93 @@ for (const ipfsGatewayUrl in ipfsGateways) { console.log("Session succeeded IPNS averate time:", ipfsGateway.sessionSucceededIpnsAverageTime); console.log("Session succeeded IPNS median time:", ipfsGateway.sessionSucceededIpnsMedianTime); - for (const subplebbitAddress in ipfsGateway.subplebbits) { - const subplebbit = ipfsGateway.subplebbits[subplebbitAddress]; - console.log("Subplebbit:", subplebbitAddress); + for (const communityAddress in ipfsGateway.communities) { + const community = ipfsGateway.communities[communityAddress]; + console.log("Community:", communityAddress); - console.log("Succeeded subplebbit updates:", subplebbit.succeededSubplebbitUpdateCount); - console.log("Failed subplebbit updates:", subplebbit.failedSubplebbitUpdateCount); + console.log("Succeeded community updates:", community.succeededCommunityUpdateCount); + console.log("Failed community updates:", community.failedCommunityUpdateCount); console.log( - "Succeeded subplebbit updates average time:", - subplebbit.succeededSubplebbitUpdateAverageTime, + "Succeeded community updates average time:", + community.succeededCommunityUpdateAverageTime, ); console.log( - "Succeeded subplebbit updates median time:", - subplebbit.succeededSubplebbitUpdateMedianTime, + "Succeeded community updates median time:", + community.succeededCommunityUpdateMedianTime, ); console.log( - "Session succeeded subplebbit updates:", - subplebbit.sessionSucceededSubplebbitUpdateCount, + "Session succeeded community updates:", + community.sessionSucceededCommunityUpdateCount, ); + console.log("Session failed community updates:", community.sessionFailedCommunityUpdateCount); console.log( - "Session failed subplebbit updates:", - subplebbit.sessionFailedSubplebbitUpdateCount, + "Session succeeded community updates average time:", + community.sessionSucceededCommunityUpdateAverageTime, ); console.log( - "Session succeeded subplebbit updates average time:", - subplebbit.sessionSucceededSubplebbitUpdateAverageTime, - ); - console.log( - "Session succeeded subplebbit updates median time:", - subplebbit.sessionSucceededSubplebbitUpdateMedianTime, + "Session succeeded community updates median time:", + community.sessionSucceededCommunityUpdateMedianTime, ); - console.log("Succeeded subplebbit pages:", subplebbit.succeededSubplebbitPageCount); - console.log("Failed subplebbit pages:", subplebbit.failedSubplebbitPageCount); + console.log("Succeeded community pages:", community.succeededCommunityPageCount); + console.log("Failed community pages:", community.failedCommunityPageCount); console.log( - "Succeeded subplebbit pages average time:", - subplebbit.succeededSubplebbitPageAverageTime, + "Succeeded community pages average time:", + community.succeededCommunityPageAverageTime, ); console.log( - "Succeeded subplebbit pages median time:", - subplebbit.succeededSubplebbitPageMedianTime, + "Succeeded community pages median time:", + community.succeededCommunityPageMedianTime, ); + console.log("Session succeeded community pages:", community.sessionSucceededCommunityPageCount); + console.log("Session failed community pages:", community.sessionFailedCommunityPageCount); console.log( - "Session succeeded subplebbit pages:", - subplebbit.sessionSucceededSubplebbitPageCount, - ); - console.log("Session failed subplebbit pages:", subplebbit.sessionFailedSubplebbitPageCount); - console.log( - "Session succeeded subplebbit pages average time:", - subplebbit.sessionSucceededSubplebbitPageAverageTime, + "Session succeeded community pages average time:", + community.sessionSucceededCommunityPageAverageTime, ); console.log( - "Session succeeded subplebbit pages median time:", - subplebbit.sessionSucceededSubplebbitPageMedianTime, + "Session succeeded community pages median time:", + community.sessionSucceededCommunityPageMedianTime, ); - console.log("Succeeded comments:", subplebbit.succeededCommentCount); - console.log("Failed comments:", subplebbit.failedCommentCount); - console.log("Succeeded comments average time:", subplebbit.succeededCommentAverageTime); - console.log("Succeeded comments median time:", subplebbit.succeededCommentMedianTime); + console.log("Succeeded comments:", community.succeededCommentCount); + console.log("Failed comments:", community.failedCommentCount); + console.log("Succeeded comments average time:", community.succeededCommentAverageTime); + console.log("Succeeded comments median time:", community.succeededCommentMedianTime); - console.log("Session succeeded comments:", subplebbit.sessionSucceededCommentCount); - console.log("Session failed comments:", subplebbit.sessionFailedCommentCount); + console.log("Session succeeded comments:", community.sessionSucceededCommentCount); + console.log("Session failed comments:", community.sessionFailedCommentCount); console.log( "Session succeeded comments average time:", - subplebbit.sessionSucceededCommentAverageTime, + community.sessionSucceededCommentAverageTime, ); console.log( "Session succeeded comments median time:", - subplebbit.sessionSucceededCommentMedianTime, + community.sessionSucceededCommentMedianTime, ); - console.log("Succeeded comment updates:", subplebbit.succeededCommentUpdateCount); - console.log("Failed comment updates:", subplebbit.failedCommentUpdateCount); + console.log("Succeeded comment updates:", community.succeededCommentUpdateCount); + console.log("Failed comment updates:", community.failedCommentUpdateCount); console.log( "Succeeded comment updates average time:", - subplebbit.succeededCommentUpdateAverageTime, + community.succeededCommentUpdateAverageTime, ); console.log( "Succeeded comment updates median time:", - subplebbit.succeededCommentUpdateMedianTime, + community.succeededCommentUpdateMedianTime, ); - console.log( - "Session succeeded comment updates:", - subplebbit.sessionSucceededCommentUpdateCount, - ); - console.log("Session failed comment updates:", subplebbit.sessionFailedCommentUpdateCount); + console.log("Session succeeded comment updates:", community.sessionSucceededCommentUpdateCount); + console.log("Session failed comment updates:", community.sessionFailedCommentUpdateCount); console.log( "Session succeeded comment updates average time:", - subplebbit.sessionSucceededCommentUpdateAverageTime, + community.sessionSucceededCommentUpdateAverageTime, ); console.log( "Session succeeded comment updates median time:", - subplebbit.sessionSucceededCommentUpdateMedianTime, + community.sessionSucceededCommentUpdateMedianTime, ); } } @@ -622,102 +613,93 @@ for (const ipfsClientUrl in ipfsClients) { console.log("Session succeeded IPNS averate time:", ipfsClient.sessionSucceededIpnsAverageTime); console.log("Session succeeded IPNS median time:", ipfsClient.sessionSucceededIpnsMedianTime); - for (const subplebbitAddress in ipfsClient.subplebbits) { - const subplebbit = ipfsClient.subplebbits[subplebbitAddress]; - console.log("Subplebbit:", subplebbitAddress); + for (const communityAddress in ipfsClient.communities) { + const community = ipfsClient.communities[communityAddress]; + console.log("Community:", communityAddress); - console.log("Succeeded subplebbit updates:", subplebbit.succeededSubplebbitUpdateCount); - console.log("Failed subplebbit updates:", subplebbit.failedSubplebbitUpdateCount); + console.log("Succeeded community updates:", community.succeededCommunityUpdateCount); + console.log("Failed community updates:", community.failedCommunityUpdateCount); console.log( - "Succeeded subplebbit updates average time:", - subplebbit.succeededSubplebbitUpdateAverageTime, + "Succeeded community updates average time:", + community.succeededCommunityUpdateAverageTime, ); console.log( - "Succeeded subplebbit updates median time:", - subplebbit.succeededSubplebbitUpdateMedianTime, + "Succeeded community updates median time:", + community.succeededCommunityUpdateMedianTime, ); console.log( - "Session succeeded subplebbit updates:", - subplebbit.sessionSucceededSubplebbitUpdateCount, - ); - console.log( - "Session failed subplebbit updates:", - subplebbit.sessionFailedSubplebbitUpdateCount, + "Session succeeded community updates:", + community.sessionSucceededCommunityUpdateCount, ); + console.log("Session failed community updates:", community.sessionFailedCommunityUpdateCount); console.log( - "Session succeeded subplebbit updates average time:", - subplebbit.sessionSucceededSubplebbitUpdateAverageTime, + "Session succeeded community updates average time:", + community.sessionSucceededCommunityUpdateAverageTime, ); console.log( - "Session succeeded subplebbit updates median time:", - subplebbit.sessionSucceededSubplebbitUpdateMedianTime, + "Session succeeded community updates median time:", + community.sessionSucceededCommunityUpdateMedianTime, ); - console.log("Succeeded subplebbit pages:", subplebbit.succeededSubplebbitPageCount); - console.log("Failed subplebbit pages:", subplebbit.failedSubplebbitPageCount); + console.log("Succeeded community pages:", community.succeededCommunityPageCount); + console.log("Failed community pages:", community.failedCommunityPageCount); console.log( - "Succeeded subplebbit pages average time:", - subplebbit.succeededSubplebbitPageAverageTime, + "Succeeded community pages average time:", + community.succeededCommunityPageAverageTime, ); console.log( - "Succeeded subplebbit pages median time:", - subplebbit.succeededSubplebbitPageMedianTime, + "Succeeded community pages median time:", + community.succeededCommunityPageMedianTime, ); + console.log("Session succeeded community pages:", community.sessionSucceededCommunityPageCount); + console.log("Session failed community pages:", community.sessionFailedCommunityPageCount); console.log( - "Session succeeded subplebbit pages:", - subplebbit.sessionSucceededSubplebbitPageCount, - ); - console.log("Session failed subplebbit pages:", subplebbit.sessionFailedSubplebbitPageCount); - console.log( - "Session succeeded subplebbit pages average time:", - subplebbit.sessionSucceededSubplebbitPageAverageTime, + "Session succeeded community pages average time:", + community.sessionSucceededCommunityPageAverageTime, ); console.log( - "Session succeeded subplebbit pages median time:", - subplebbit.sessionSucceededSubplebbitPageMedianTime, + "Session succeeded community pages median time:", + community.sessionSucceededCommunityPageMedianTime, ); - console.log("Succeeded comments:", subplebbit.succeededCommentCount); - console.log("Failed comments:", subplebbit.failedCommentCount); - console.log("Succeeded comments average time:", subplebbit.succeededCommentAverageTime); - console.log("Succeeded comments median time:", subplebbit.succeededCommentMedianTime); + console.log("Succeeded comments:", community.succeededCommentCount); + console.log("Failed comments:", community.failedCommentCount); + console.log("Succeeded comments average time:", community.succeededCommentAverageTime); + console.log("Succeeded comments median time:", community.succeededCommentMedianTime); - console.log("Session succeeded comments:", subplebbit.sessionSucceededCommentCount); - console.log("Session failed comments:", subplebbit.sessionFailedCommentCount); + console.log("Session succeeded comments:", community.sessionSucceededCommentCount); + console.log("Session failed comments:", community.sessionFailedCommentCount); console.log( "Session succeeded comments average time:", - subplebbit.sessionSucceededCommentAverageTime, + community.sessionSucceededCommentAverageTime, ); console.log( "Session succeeded comments median time:", - subplebbit.sessionSucceededCommentMedianTime, + community.sessionSucceededCommentMedianTime, ); - console.log("Succeeded comment updates:", subplebbit.succeededCommentUpdateCount); - console.log("Failed comment updates:", subplebbit.failedCommentUpdateCount); + console.log("Succeeded comment updates:", community.succeededCommentUpdateCount); + console.log("Failed comment updates:", community.failedCommentUpdateCount); console.log( "Succeeded comment updates average time:", - subplebbit.succeededCommentUpdateAverageTime, + community.succeededCommentUpdateAverageTime, ); console.log( "Succeeded comment updates median time:", - subplebbit.succeededCommentUpdateMedianTime, + community.succeededCommentUpdateMedianTime, ); - console.log( - "Session succeeded comment updates:", - subplebbit.sessionSucceededCommentUpdateCount, - ); - console.log("Session failed comment updates:", subplebbit.sessionFailedCommentUpdateCount); + console.log("Session succeeded comment updates:", community.sessionSucceededCommentUpdateCount); + console.log("Session failed comment updates:", community.sessionFailedCommentUpdateCount); console.log( "Session succeeded comment updates average time:", - subplebbit.sessionSucceededCommentUpdateAverageTime, + community.sessionSucceededCommentUpdateAverageTime, ); console.log( "Session succeeded comment updates median time:", - subplebbit.sessionSucceededCommentUpdateMedianTime, + community.sessionSucceededCommentUpdateMedianTime, ); } } @@ -802,73 +784,70 @@ for (const pubsubClientUrl in pubsubClients) { pubsubClient.sessionSucceededChallengeAnswerMessageMedianTime, ); - for (const subplebbitAddress in pubsubClient.subplebbits) { - const subplebbit = pubsubClient.subplebbits[subplebbitAddress]; - console.log("Subplebbit:", subplebbitAddress); + for (const communityAddress in pubsubClient.communities) { + const community = pubsubClient.communities[communityAddress]; + console.log("Community:", communityAddress); console.log( "Succeeded challenge request messages:", - subplebbit.succeededChallengeRequestMessageCount, - ); - console.log( - "Failed challenge request messages:", - subplebbit.failedChallengeRequestMessageCount, + community.succeededChallengeRequestMessageCount, ); + console.log("Failed challenge request messages:", community.failedChallengeRequestMessageCount); console.log( "Succeeded challenge request messages average time:", - subplebbit.succeededChallengeRequestMessageAverageTime, + community.succeededChallengeRequestMessageAverageTime, ); console.log( "Succeeded challenge request messages median time:", - subplebbit.succeededChallengeRequestMessageMedianTime, + community.succeededChallengeRequestMessageMedianTime, ); console.log( "Succeeded challenge answer messages:", - subplebbit.succeededChallengeAnswerMessageCount, + community.succeededChallengeAnswerMessageCount, ); - console.log("Failed challenge answer messages:", subplebbit.failedChallengeAnswerMessageCount); + console.log("Failed challenge answer messages:", community.failedChallengeAnswerMessageCount); console.log( "Succeeded challenge answer messages average time:", - subplebbit.succeededChallengeAnswerMessageAverageTime, + community.succeededChallengeAnswerMessageAverageTime, ); console.log( "Succeeded challenge answer messages median time:", - subplebbit.succeededChallengeAnswerMessageMedianTime, + community.succeededChallengeAnswerMessageMedianTime, ); console.log( "Session succeeded challenge request messages:", - subplebbit.sessionSucceededChallengeRequestMessageCount, + community.sessionSucceededChallengeRequestMessageCount, ); console.log( "Session failed challenge request messages:", - subplebbit.sessionFailedChallengeRequestMessageCount, + community.sessionFailedChallengeRequestMessageCount, ); console.log( "Session succeeded challenge request messages average time:", - subplebbit.sessionSucceededChallengeRequestMessageAverageTime, + community.sessionSucceededChallengeRequestMessageAverageTime, ); console.log( "Session succeeded challenge request messages median time:", - subplebbit.sessionSucceededChallengeRequestMessageMedianTime, + community.sessionSucceededChallengeRequestMessageMedianTime, ); console.log( "Session succeeded challenge answer messages:", - subplebbit.sessionSucceededChallengeAnswerMessageCount, + community.sessionSucceededChallengeAnswerMessageCount, ); console.log( "Session failed challenge answer messages:", - subplebbit.sessionFailedChallengeAnswerMessageCount, + community.sessionFailedChallengeAnswerMessageCount, ); console.log( "Session succeeded challenge answer messages average time:", - subplebbit.sessionSucceededChallengeAnswerMessageAverageTime, + community.sessionSucceededChallengeAnswerMessageAverageTime, ); console.log( "Session succeeded challenge answer messages median time:", - subplebbit.sessionSucceededChallengeAnswerMessageMedianTime, + community.sessionSucceededChallengeAnswerMessageMedianTime, ); } } diff --git a/docs/mock-content.md b/docs/mock-content.md index 3c5bedd1..eead4b7b 100644 --- a/docs/mock-content.md +++ b/docs/mock-content.md @@ -20,7 +20,7 @@ import { useFeed } from "@bitsocialnet/bitsocial-react-hooks"; function App() { const { feed, hasMore, loadMore } = useFeed({ - subplebbitAddresses: ["news.eth"], + communityAddresses: ["news.eth"], sortType: "new", }); console.log({ feed }); @@ -44,6 +44,6 @@ import { deleteCaches, deleteDatabases } from "@bitsocialnet/bitsocial-react-hoo // delete all databases, including all caches and accounts data await deleteDatabases(); -// delete the cached comments, cached subplebbits and cached pages only, no accounts data +// delete the cached comments, cached communities and cached pages only, no accounts data await deleteCaches(); ``` diff --git a/docs/schema.md b/docs/schema.md index 5147ba65..3ec94192 100644 --- a/docs/schema.md +++ b/docs/schema.md @@ -11,11 +11,11 @@ Account { plebbit: Plebbit, plebbitOptions: PlebbitOptions, // subscriptions to show in feed - subscriptions: string[], // subplebbit subscriptions + subscriptions: string[], // community subscriptions multisubSubscriptions: string[], authorSubscriptions: string[], // notifications turned on for addresses/cids - notifyingSubplebbits: {[address: string]: boolean} + notifyingCommunities: {[address: string]: boolean} notifyingMultisubs: {[address: string]: boolean} notifyingAuthors: {[address: string]: boolean} notifyingComments: {[commentCid: string]: boolean} @@ -25,7 +25,7 @@ Account { savedComments: string[], // save a list of comments for later karma: Karma unreadNotificationCount: number - subplebbits: {[subplebbitAddress: string]: AccountSubplebbit} // the subplebbits moderated or created by the user + communities: {[communityAddress: string]: AccountCommunity} // the communities moderated or created by the user } Karma { replyUpvoteCount @@ -38,7 +38,7 @@ Karma { downvoteCount score } -AccountSubplebbit { // the subplebbits moderated or created by the user +AccountCommunity { // the communities moderated or created by the user role: Role } AccountComment extends Comment { @@ -53,8 +53,8 @@ UseAccountsCommentsOptions { accountName?: string filter: UseAccountCommentsFilter } -UseAccountCommentsFilter { // only get your own account's comments/votes on a certain subplebbit, thread, etc useful for certain UI pages - subplebbitAddresses?: string[] +UseAccountCommentsFilter { // only get your own account's comments/votes on a certain community, thread, etc useful for certain UI pages + communityAddresses?: string[] postCids?: string[] commentCids?: string[] parentCommentCids?: string[] @@ -99,14 +99,14 @@ Challenge { AccountsVotes (each database named accountVotes-[accountId]) { [commentCid: string]: AccountVote } - Subplebbits { - [subplebbitAddress: string]: Subplebbit // last recently used database, delete oldest data + Communities { + [communityAddress: string]: Community // last recently used database, delete oldest data } Comments { [commentCid: string]: Comment // last recently used database, delete oldest data, different from AccountsComments that never expire } - SubplebbitPages { - [pageCid: string]: SubplebbitPage // last recently used database, delete oldest data + CommunityPages { + [pageCid: string]: CommunityPage // last recently used database, delete oldest data } ``` @@ -132,16 +132,16 @@ commentsStore (store in indexeddb last recently used) { // internal addCommentToStore(commentCid) } -subplebbitsStore (store in indexeddb last recently used) { - subplebbits: {[subplebbitAddress: string]: Subplebbit} +communitiesStore (store in indexeddb last recently used) { + communities: {[communityAddress: string]: Community} // internal - addSubplebbitToStore(subplebbitAddress) + addCommunityToStore(communityAddress) } -feedsStore (no persistant storage, can be rebuilt from Subplebbits and SubplebbitPages databases) { +feedsStore (no persistant storage, can be rebuilt from Communities and CommunityPages databases) { bufferedFeeds: {[feedName: string]: Comment[]} loadedFeeds: {[feedName: string]: Comment[]} feedsHaveMore: {[feedName: string]: boolean} // internal - addFeedToStore(feedName, subplebbitAddresses, sortType, account) + addFeedToStore(feedName, communityAddresses, sortType, account) } ``` diff --git a/docs/testing.md b/docs/testing.md index 486cfabc..cb3bc889 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -25,7 +25,7 @@ DEBUG=bitsocial-react-hooks:hooks:accounts DEBUG_DEPTH=6 yarn test feeds - Create a `.env` file and add `CHROME_BIN=/usr/bin/chromium` (replace the path with your chrome path) - In a new terminal run `yarn build:watch` to compile typescript - In a new terminal run `yarn webpack:watch` to compile the browser tests -- In a new terminal run `yarn test:server` to start an ipfs node and the test subplebbits +- In a new terminal run `yarn test:server` to start an ipfs node and the test communities #### E2E tests diff --git a/scripts/coverage-gaps.mjs b/scripts/coverage-gaps.mjs index 1f6f0c32..5a2ef317 100644 --- a/scripts/coverage-gaps.mjs +++ b/scripts/coverage-gaps.mjs @@ -2,32 +2,30 @@ /** * Reads coverage-final.json and reports uncovered statements, functions, and branches * for files matching: feed-sorter, feeds-store, feeds/utils, replies-pages-store, - * replies-store, replies/utils, subplebbits-pages, subplebbits-store + * replies-store, replies/utils, communitys-pages, communitys-store */ -import { readFileSync } from 'fs'; -import { fileURLToPath } from 'url'; -import { dirname, join } from 'path'; +import { readFileSync } from "fs"; +import { fileURLToPath } from "url"; +import { dirname, join } from "path"; const __dirname = dirname(fileURLToPath(import.meta.url)); -const coverageDir = process.env.COVERAGE_DIR || 'coverage-live3'; +const coverageDir = process.env.COVERAGE_DIR || "coverage-live3"; const coveragePath = join(__dirname, `../src/${coverageDir}/coverage-final.json`); const MATCH_KEYS = [ - 'feed-sorter', - 'feeds-store', - 'feeds/utils', - 'replies-pages-store', - 'replies-store', - 'replies/utils', - 'subplebbits-pages', - 'subplebbits-store', + "feed-sorter", + "feeds-store", + "feeds/utils", + "replies-pages-store", + "replies-store", + "replies/utils", + "communitys-pages", + "communitys-store", ]; -const data = JSON.parse(readFileSync(coveragePath, 'utf8')); -const paths = Object.keys(data).filter((p) => - MATCH_KEYS.some((k) => p.includes(k)) -); +const data = JSON.parse(readFileSync(coveragePath, "utf8")); +const paths = Object.keys(data).filter((p) => MATCH_KEYS.some((k) => p.includes(k))); const report = {}; @@ -61,7 +59,7 @@ for (const path of paths) { } } - const shortPath = path.replace(/^.*\/src\//, 'src/'); + const shortPath = path.replace(/^.*\/src\//, "src/"); report[shortPath] = { uncoveredStmtLines: [...new Set(stmtLines)], uncoveredFuncLines: [...new Set(funcLines)], diff --git a/src/hooks/accounts/accounts.test.ts b/src/hooks/accounts/accounts.test.ts index 1d88fdea..17d9cc91 100644 --- a/src/hooks/accounts/accounts.test.ts +++ b/src/hooks/accounts/accounts.test.ts @@ -10,12 +10,12 @@ import { useAccountVote, useAccountEdits, useEditedComment, - useAccountSubplebbits, + useAccountCommunities, UseAccountCommentsOptions, useComment, useNotifications, useFeed, - useSubplebbit, + useCommunity, usePubsubSubscribe, setPlebbitJs, } from "../.."; @@ -24,7 +24,7 @@ import * as accountsActions from "../../stores/accounts/accounts-actions"; import PlebbitJsMock, { Plebbit, Comment, - Subplebbit, + Community, Pages, resetPlebbitJsMock, debugPlebbitJsMock, @@ -197,20 +197,20 @@ describe("accounts", () => { const rendered = renderHook(() => usePubsubSubscribe({ accountName: "NonExistentAccount", - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", }), ); expect(rendered.result.current.state).toBe("initializing"); }); test("usePubsubSubscribe", async () => { - const rendered = renderHook((subplebbitAddress) => { - const result = usePubsubSubscribe({ subplebbitAddress }); + const rendered = renderHook((communityAddress) => { + const result = usePubsubSubscribe({ communityAddress }); return { result }; }); const waitFor = testUtils.createWaitFor(rendered); - rendered.rerender("subplebbit-address.eth"); + rendered.rerender("community-address.eth"); await waitFor(() => rendered.result.current.result.state === "succeeded"); expect(rendered.result.current.result.state).toBe("succeeded"); }); @@ -222,7 +222,7 @@ describe("accounts", () => { }; try { const rendered = renderHook(() => - usePubsubSubscribe({ subplebbitAddress: "error-sub.eth" }), + usePubsubSubscribe({ communityAddress: "error-sub.eth" }), ); const waitFor = testUtils.createWaitFor(rendered); @@ -241,7 +241,7 @@ describe("accounts", () => { }; try { const rendered = renderHook(() => - usePubsubSubscribe({ subplebbitAddress: "unsub-error.eth" }), + usePubsubSubscribe({ communityAddress: "unsub-error.eth" }), ); const waitFor = testUtils.createWaitFor(rendered); @@ -435,23 +435,23 @@ describe("accounts", () => { // test.todo(`fail to edit account.signer.address that doesn't match signer private key`) describe("account comments, account votes, account edits in database", () => { - const subplebbitAddress = "subplebbit address"; + const communityAddress = "community address"; beforeEach(async () => { let challengeVerificationCount = 0; const publishCommentEditOptions = { - subplebbitAddress, + communityAddress, spoiler: true, onChallenge: (challenge: any, comment: any) => comment.publishChallengeAnswers(), onChallengeVerification: () => challengeVerificationCount++, }; const publishCommentOptions = { - subplebbitAddress, + communityAddress, onChallenge: (challenge: any, comment: any) => comment.publishChallengeAnswers(), onChallengeVerification: () => challengeVerificationCount++, }; const publishVoteOptions = { - subplebbitAddress, + communityAddress, vote: 1, onChallenge: (challenge: any, comment: any) => comment.publishChallengeAnswers(), onChallengeVerification: () => challengeVerificationCount++, @@ -552,7 +552,7 @@ describe("accounts", () => { expect(typeof rendered.result.current.account?.id).toBe("string"); expect(rendered.result.current.account?.id).not.toBe(exported?.account.id); // account.plebbit has been initialized - expect(typeof rendered.result.current.account?.plebbit?.getSubplebbit).toBe("function"); + expect(typeof rendered.result.current.account?.plebbit?.getCommunity).toBe("function"); // has account comments, votes, edits await waitFor(() => rendered.result.current.accountComments?.[0]?.content === "content 1"); @@ -588,7 +588,7 @@ describe("accounts", () => { expect(typeof rendered2.result.current.account.id).toBe("string"); expect(rendered2.result.current.account.id).not.toBe(exported?.account.id); // account.plebbit has been initialized - expect(typeof rendered2.result.current.account.plebbit?.getSubplebbit).toBe("function"); + expect(typeof rendered2.result.current.account.plebbit?.getCommunity).toBe("function"); // has account comments, votes, edits await waitFor2( @@ -655,7 +655,7 @@ describe("accounts", () => { }); describe("deleteComment", () => { - const subplebbitAddress = "12D3KooW... deleteComment.test"; + const communityAddress = "12D3KooW... deleteComment.test"; test("useAccountComments with filter logs when accountComments and options present", async () => { const filter = (c: AccountComment) => !!c.content; @@ -679,7 +679,7 @@ describe("accounts", () => { ...existing, { timestamp: recentTimestamp, - subplebbitAddress: "test.eth", + communityAddress: "test.eth", content: "pending", index: existing.length, }, @@ -710,7 +710,7 @@ describe("accounts", () => { test(`deleteComment(index) removes pending comment, reindexes list, and persists after store reset`, async () => { const publishCommentOptions = { - subplebbitAddress, + communityAddress, parentCid: "Qm...", content: "pending to delete", onChallenge: () => {}, @@ -739,7 +739,7 @@ describe("accounts", () => { test(`deleteComment(cid) removes succeeded comment and reindexes/mapping remains correct`, async () => { const publishCommentOptions = { - subplebbitAddress, + communityAddress, onChallenge: (challenge: any, comment: any) => comment.publishChallengeAnswers(["4"]), }; let cvCount = 0; @@ -780,7 +780,7 @@ describe("accounts", () => { test(`deleting one entry while another pending publish exists does not cause wrong comment mutation from later publish callbacks`, async () => { let cvCount = 0; const publishCommentOptions = { - subplebbitAddress, + communityAddress, onChallenge: (challenge: any, comment: any) => comment.publishChallengeAnswers(["4"]), onChallengeVerification: () => cvCount++, }; @@ -946,53 +946,51 @@ describe("accounts", () => { expect(rendered.result.current.account.name).toBe("Account 1"); }); - test(`subscribe and unsubscribe to subplebbit`, async () => { - const subplebbitAddress = "tosubscribeto.eth"; - const subplebbitAddress2 = "tosubscribeto2.eth"; + test(`subscribe and unsubscribe to community`, async () => { + const communityAddress = "tosubscribeto.eth"; + const communityAddress2 = "tosubscribeto2.eth"; // subscribe to 1 sub await act(async () => { - await rendered.result.current.subscribe(subplebbitAddress); + await rendered.result.current.subscribe(communityAddress); }); await waitFor(() => rendered.result.current.account.subscriptions.length === 1); - expect(rendered.result.current.account.subscriptions).toEqual([subplebbitAddress]); + expect(rendered.result.current.account.subscriptions).toEqual([communityAddress]); // fail subscribing twice await act(async () => { - await expect(() => rendered.result.current.subscribe(subplebbitAddress)).rejects.toThrow(); + await expect(() => rendered.result.current.subscribe(communityAddress)).rejects.toThrow(); }); // unsubscribe await act(async () => { - await rendered.result.current.unsubscribe(subplebbitAddress); + await rendered.result.current.unsubscribe(communityAddress); }); await waitFor(() => rendered.result.current.account.subscriptions.length === 0); expect(rendered.result.current.account.subscriptions).toEqual([]); // fail unsubscribing twice await act(async () => { - await expect(() => - rendered.result.current.unsubscribe(subplebbitAddress), - ).rejects.toThrow(); + await expect(() => rendered.result.current.unsubscribe(communityAddress)).rejects.toThrow(); }); // subscribe to 2 subs await act(async () => { - await rendered.result.current.subscribe(subplebbitAddress); - await rendered.result.current.subscribe(subplebbitAddress2); + await rendered.result.current.subscribe(communityAddress); + await rendered.result.current.subscribe(communityAddress2); }); await waitFor(() => rendered.result.current.account.subscriptions.length === 2); expect(rendered.result.current.account.subscriptions).toEqual([ - subplebbitAddress, - subplebbitAddress2, + communityAddress, + communityAddress2, ]); // unsubscribe with 2 subs await act(async () => { - await rendered.result.current.unsubscribe(subplebbitAddress); + await rendered.result.current.unsubscribe(communityAddress); }); await waitFor(() => rendered.result.current.account.subscriptions.length === 1); - expect(rendered.result.current.account.subscriptions).toEqual([subplebbitAddress2]); + expect(rendered.result.current.account.subscriptions).toEqual([communityAddress2]); // reset stores to force using the db await testUtils.resetStores(); @@ -1001,11 +999,11 @@ describe("accounts", () => { const rendered2 = renderHook(() => useAccount()); const waitFor2 = testUtils.createWaitFor(rendered2); await waitFor2(() => rendered2.result.current.subscriptions.length === 1); - expect(rendered2.result.current.subscriptions).toEqual([subplebbitAddress2]); + expect(rendered2.result.current.subscriptions).toEqual([communityAddress2]); }); test(`block and unblock to address`, async () => { - const address1 = "subplebbit.eth"; + const address1 = "community.eth"; const address2 = "author.eth"; // block address 1 @@ -1106,7 +1104,7 @@ describe("accounts", () => { test("publish comment", async () => { const publishCommentOptions = { - subplebbitAddress: "12D3KooW...", + communityAddress: "12D3KooW...", parentCid: "Qm...", content: "some content", onChallenge, @@ -1199,7 +1197,7 @@ describe("accounts", () => { test("publish vote", async () => { const publishVoteOptions = { - subplebbitAddress: "12D3KooW...", + communityAddress: "12D3KooW...", commentCid: "Qm...", vote: 1, onChallenge, @@ -1251,7 +1249,7 @@ describe("accounts", () => { test("publish comment edit", async () => { const commentEditOptions = { - subplebbitAddress: "12D3KooW...", + communityAddress: "12D3KooW...", commentCid: "Qm...", spoiler: true, onChallenge, @@ -1331,7 +1329,7 @@ describe("accounts", () => { }); }); - describe(`create subplebbit edit`, () => { + describe(`create community edit`, () => { beforeAll(async () => { await render(); }); @@ -1342,22 +1340,22 @@ describe("accounts", () => { const onChallenge = vi.fn(); const onChallengeVerification = vi.fn(); - test("publish subplebbit edit", async () => { - const subplebbitAddress = "12D3KooW..."; - const publishSubplebbitEditOptions = { + test("publish community edit", async () => { + const communityAddress = "12D3KooW..."; + const publishCommunityEditOptions = { title: "edited title", onChallenge, onChallengeVerification, }; await act(async () => { - await rendered.result.current.publishSubplebbitEdit( - subplebbitAddress, - publishSubplebbitEditOptions, + await rendered.result.current.publishCommunityEdit( + communityAddress, + publishCommunityEditOptions, ); }); }); - let subplebbitEdit: any; + let communityEdit: any; test("onChallenge gets called", async () => { // onChallenge gets call backed once @@ -1366,22 +1364,22 @@ describe("accounts", () => { // onChallenge arguments are [challenge, comment] const challenge = onChallenge.mock.calls[0][0]; - subplebbitEdit = onChallenge.mock.calls[0][1]; + communityEdit = onChallenge.mock.calls[0][1]; expect(challenge.type).toBe("CHALLENGE"); expect(challenge.challenges[0]).toEqual({ challenge: "2+2=?", type: "text" }); - expect(typeof subplebbitEdit.publishChallengeAnswers).toBe("function"); + expect(typeof communityEdit.publishChallengeAnswers).toBe("function"); }); test("onChallengeVerification gets called", async () => { // publish challenge answer and wait for verification - subplebbitEdit.publishChallengeAnswers(["4"]); + communityEdit.publishChallengeAnswers(["4"]); await waitFor(() => onChallengeVerification.mock.calls.length === 1); expect(onChallengeVerification.mock.calls.length).toBe(1); const challengeVerification = onChallengeVerification.mock.calls[0][0]; - const subplebbitEditVerified = onChallengeVerification.mock.calls[0][1]; + const communityEditVerified = onChallengeVerification.mock.calls[0][1]; expect(challengeVerification.type).toBe("CHALLENGEVERIFICATION"); - expect(subplebbitEditVerified.constructor.name).toBe("SubplebbitEdit"); + expect(communityEditVerified.constructor.name).toBe("CommunityEdit"); }); }); }); @@ -1427,37 +1425,37 @@ describe("accounts", () => { content: "content 1", parentCid: "parent comment cid 1", postCid: "post cid 1", - subplebbitAddress: "subplebbit address 1", + communityAddress: "community address 1", }); await rendered.result.current.publishComment({ ...publishOptions, title: "title 2", content: "content 2", - subplebbitAddress: "subplebbit address 1", + communityAddress: "community address 1", }); await rendered.result.current.publishComment({ ...publishOptions, title: "title 3", content: "content 3", - subplebbitAddress: "subplebbit address 2", + communityAddress: "community address 2", }); await rendered.result.current.publishVote({ ...publishOptions, vote: 1, commentCid: "comment cid 1", - subplebbitAddress: "subplebbit address 1", + communityAddress: "community address 1", }); await rendered.result.current.publishVote({ ...publishOptions, vote: 1, commentCid: "comment cid 2", - subplebbitAddress: "subplebbit address 1", + communityAddress: "community address 1", }); await rendered.result.current.publishVote({ ...publishOptions, vote: 1, commentCid: "comment cid 3", - subplebbitAddress: "subplebbit address 2", + communityAddress: "community address 2", }); }); }); @@ -1493,7 +1491,7 @@ describe("accounts", () => { ...(s.accountsComments?.[accountId] || []), { timestamp: Math.floor(Date.now() / 1000), - subplebbitAddress: "test.eth", + communityAddress: "test.eth", content: "failed", index: failedIndex, error: Error("publish failed"), @@ -1561,9 +1559,9 @@ describe("accounts", () => { expect(rendered.result.current.accountComments.length).toBe(3); expect(rendered.result.current.accountComments[1].depth).toBe(0); expect(rendered.result.current.accountComments[2].depth).toBe(0); - expect(rendered.result.current.accountComments[0].shortSubplebbitAddress).not.toBe(undefined); - expect(rendered.result.current.accountComments[1].shortSubplebbitAddress).not.toBe(undefined); - expect(rendered.result.current.accountComments[2].shortSubplebbitAddress).not.toBe(undefined); + expect(rendered.result.current.accountComments[0].shortCommunityAddress).not.toBe(undefined); + expect(rendered.result.current.accountComments[1].shortCommunityAddress).not.toBe(undefined); + expect(rendered.result.current.accountComments[2].shortCommunityAddress).not.toBe(undefined); expect(rendered.result.current.accountComments[0].author.shortAddress).not.toBe(undefined); expect(rendered.result.current.accountComments[1].author.shortAddress).not.toBe(undefined); expect(rendered.result.current.accountComments[2].author.shortAddress).not.toBe(undefined); @@ -1734,7 +1732,7 @@ describe("accounts", () => { const rendered = renderHook((props?) => { const { feed } = useFeed({ - subplebbitAddresses: props?.subplebbitAddresses, + communityAddresses: props?.communityAddresses, sortType: "new", }); const { accountComments } = useAccountComments(); @@ -1748,20 +1746,20 @@ describe("accounts", () => { // get feed page with our timestamp and author address in it const accountCommentTimestamp = rendered.result.current.accountComments[0].timestamp; const accountCommentAuthor = rendered.result.current.accountComments[0].author; - const accountCommentSubplebbitAddress = - rendered.result.current.accountComments[0].subplebbitAddress; + const accountCommentCommunityAddress = + rendered.result.current.accountComments[0].communityAddress; Pages.prototype.getPage = async () => ({ comments: [ { cid: "cid from feed", timestamp: accountCommentTimestamp, author: accountCommentAuthor, - subplebbitAddress: accountCommentSubplebbitAddress, + communityAddress: accountCommentCommunityAddress, }, ], nextCid: undefined, }); - rendered.rerender({ subplebbitAddresses: [accountCommentSubplebbitAddress] }); + rendered.rerender({ communityAddresses: [accountCommentCommunityAddress] }); // wait for feed to load await waitFor(() => rendered.result.current.feed?.length > 0); @@ -1882,13 +1880,13 @@ describe("accounts", () => { ...publishOptions, title: "account 2 title 1", content: "account 2 content 1", - subplebbitAddress: "account 2 subplebbit address 1", + communityAddress: "account 2 community address 1", }); await rendered.result.current.publishVote({ ...publishOptions, vote: 1, commentCid: "account 2 comment cid 1", - subplebbitAddress: "account 2 subplebbit address 1", + communityAddress: "account 2 community address 1", }); }); expect(rendered.result.current.accountComments.length).toBe(1); @@ -1937,19 +1935,19 @@ describe("accounts", () => { expect(rendered.result.current.accountComments[0].parentCid).toBe("parent comment cid 1"); }); - test(`get account posts in a subplebbit`, () => { + test(`get account posts in a community`, () => { rendered.rerender({ filter: (comment: AccountComment) => - comment.subplebbitAddress === "subplebbit address 1" && !comment.parentCid, + comment.communityAddress === "community address 1" && !comment.parentCid, }); expect(rendered.result.current.accountComments.length).toBe(1); expect(rendered.result.current.accountVotes.length).toBe(2); expect(rendered.result.current.accountComments[0].parentCid).toBe(undefined); }); - test(`get account posts and comments in a subplebbit`, () => { + test(`get account posts and comments in a community`, () => { rendered.rerender({ - filter: (comment: AccountComment) => comment.subplebbitAddress === "subplebbit address 1", + filter: (comment: AccountComment) => comment.communityAddress === "community address 1", }); expect(rendered.result.current.accountComments.length).toBe(2); expect(rendered.result.current.accountVotes.length).toBe(2); @@ -2015,7 +2013,7 @@ describe("accounts", () => { content: "content 1", parentCid: "parent comment cid 1", postCid: "post cid 1", - subplebbitAddress: "subplebbit address 1", + communityAddress: "community address 1", // @ts-ignore onChallenge: (challenge, comment) => comment.publishChallengeAnswers(), onChallengeVerification: () => {}, @@ -2041,16 +2039,16 @@ describe("accounts", () => { // update the comment with replies to see get notifications comment.depth = 0; const depth = comment.depth + 1; - const subplebbitAddress = comment.subplebbitAddress; + const communityAddress = comment.communityAddress; const parentCid = comment.cid; comment.replies = { pages: { topAll: { nextCid: undefined, comments: [ - { cid: "reply cid 1", timestamp: 1, depth, subplebbitAddress, parentCid }, - { cid: "reply cid 2", timestamp: 2, depth, subplebbitAddress, parentCid }, - { cid: "reply cid 3", timestamp: 3, depth, subplebbitAddress, parentCid }, + { cid: "reply cid 1", timestamp: 1, depth, communityAddress, parentCid }, + { cid: "reply cid 2", timestamp: 2, depth, communityAddress, parentCid }, + { cid: "reply cid 3", timestamp: 3, depth, communityAddress, parentCid }, ], }, }, @@ -2093,8 +2091,8 @@ describe("accounts", () => { // update the comment with one unread reply and one read reply comment.depth = 0; const depth = comment.depth + 1; - comment.subplebbitAddress = "blocked subplebbit address"; - const subplebbitAddress = comment.subplebbitAddress; + comment.communityAddress = "blocked community address"; + const communityAddress = comment.communityAddress; const parentCid = comment.cid; comment.replies = { pages: { @@ -2105,7 +2103,7 @@ describe("accounts", () => { cid: "reply cid 3", timestamp: 3, depth, - subplebbitAddress, + communityAddress, parentCid, postCid: "blocked post cid", }, @@ -2113,7 +2111,7 @@ describe("accounts", () => { cid: "reply cid 4", timestamp: 4, depth, - subplebbitAddress, + communityAddress, parentCid, author: { address: "blocked author address" }, }, @@ -2141,7 +2139,7 @@ describe("accounts", () => { // block addresses await act(async () => { - await accountsActions.blockAddress("blocked subplebbit address"); + await accountsActions.blockAddress("blocked community address"); await accountsActions.blockAddress("blocked author address"); }); await waitFor(() => rendered.result.current.notifications.length === 2); @@ -2152,7 +2150,7 @@ describe("accounts", () => { // unblock addresses await act(async () => { - await accountsActions.unblockAddress("blocked subplebbit address"); + await accountsActions.unblockAddress("blocked community address"); await accountsActions.unblockAddress("blocked author address"); }); await waitFor(() => rendered.result.current.notifications.length === 4); @@ -2211,28 +2209,28 @@ describe("accounts", () => { // roles tests depend on race conditions as part of the test // so not possible to make them deterministic, add a retry // the hooks don't have the race condition, only the tests do - describe("useAccountSubplebbits", { retry: 20 }, () => { - test("useAccountSubplebbits returns initializing when no account", () => { + describe("useAccountCommunities", { retry: 20 }, () => { + test("useAccountCommunities returns initializing when no account", () => { const rendered = renderHook(() => - useAccountSubplebbits({ accountName: "NonExistentAccount" }), + useAccountCommunities({ accountName: "NonExistentAccount" }), ); expect(rendered.result.current.state).toBe("initializing"); }); - test("useAccountSubplebbits with account that has no subplebbits", async () => { + test("useAccountCommunities with account that has no communities", async () => { const rendered = renderHook(() => { const account = useAccount(); const { setAccount } = accountsActions; - const { accountSubplebbits, state } = useAccountSubplebbits(); - return { account, setAccount, accountSubplebbits, state }; + const { accountCommunities, state } = useAccountCommunities(); + return { account, setAccount, accountCommunities, state }; }); const waitFor = testUtils.createWaitFor(rendered); await waitFor(() => rendered.result.current.account.name); const { account, setAccount } = rendered.result.current; await act(async () => { - await setAccount({ ...account, subplebbits: {} }); + await setAccount({ ...account, communities: {} }); }); - await waitFor(() => rendered.result.current.accountSubplebbits !== undefined); + await waitFor(() => rendered.result.current.accountCommunities !== undefined); expect(rendered.result.current.state).toBe("succeeded"); }); @@ -2252,117 +2250,111 @@ describe("accounts", () => { beforeEach(async () => { rendered = renderHook(() => { - const { accountSubplebbits } = useAccountSubplebbits(); + const { accountCommunities } = useAccountCommunities(); const account = useAccount(); const { setAccount } = accountsActions; - return { accountSubplebbits, setAccount, account }; + return { accountCommunities, setAccount, account }; }); waitFor = testUtils.createWaitFor(rendered); await waitFor(() => rendered.result.current.account.name); const { account, setAccount } = rendered.result.current; const role = { role: "moderator" }; - const subplebbits = { "subplebbit address 1": { role } }; + const communities = { "community address 1": { role } }; await act(async () => { - await setAccount({ ...account, subplebbits }); + await setAccount({ ...account, communities }); }); }); - test("returns owner subplebbits", async () => { - await waitFor( - () => rendered.result.current.accountSubplebbits["list subplebbit address 1"], - ); + test("returns owner communities", async () => { + await waitFor(() => rendered.result.current.accountCommunities["list community address 1"]); expect( - rendered.result.current.accountSubplebbits["list subplebbit address 1"].role.role, + rendered.result.current.accountCommunities["list community address 1"].role.role, ).toBe("owner"); expect( - rendered.result.current.accountSubplebbits["list subplebbit address 2"].role.role, + rendered.result.current.accountCommunities["list community address 2"].role.role, ).toBe("owner"); }); - test("not yet fetched accounts subplebbits have address", async () => { - await waitFor( - () => rendered.result.current.accountSubplebbits["list subplebbit address 1"], + test("not yet fetched accounts communities have address", async () => { + await waitFor(() => rendered.result.current.accountCommunities["list community address 1"]); + expect(rendered.result.current.accountCommunities["list community address 1"].address).toBe( + "list community address 1", + ); + expect(rendered.result.current.accountCommunities["list community address 2"].address).toBe( + "list community address 2", ); - expect( - rendered.result.current.accountSubplebbits["list subplebbit address 1"].address, - ).toBe("list subplebbit address 1"); - expect( - rendered.result.current.accountSubplebbits["list subplebbit address 2"].address, - ).toBe("list subplebbit address 2"); }); - test("returns moderator subplebbits after setting them", async () => { + test("returns moderator communities after setting them", async () => { await waitFor( () => - rendered.result.current.accountSubplebbits["subplebbit address 1"].role.role === + rendered.result.current.accountCommunities["community address 1"].role.role === "moderator", ); - expect(rendered.result.current.accountSubplebbits["subplebbit address 1"].role.role).toBe( + expect(rendered.result.current.accountCommunities["community address 1"].role.role).toBe( "moderator", ); - await waitFor( - () => rendered.result.current.accountSubplebbits["list subplebbit address 1"], - ); + await waitFor(() => rendered.result.current.accountCommunities["list community address 1"]); expect( - rendered.result.current.accountSubplebbits["list subplebbit address 1"].role.role, + rendered.result.current.accountCommunities["list community address 1"].role.role, ).toBe("owner"); expect( - rendered.result.current.accountSubplebbits["list subplebbit address 2"].role.role, + rendered.result.current.accountCommunities["list community address 2"].role.role, ).toBe("owner"); }); - test("dedupes equivalent .eth and .bso account subplebbits under the canonical .bso key", async () => { + test("dedupes equivalent .eth and .bso account communities under the canonical .bso key", async () => { const { account, setAccount } = rendered.result.current; const role = { role: "moderator" }; await act(async () => { await setAccount({ ...account, - subplebbits: { + communities: { "music-posting.eth": { role }, "music-posting.bso": { role }, }, }); }); - await waitFor(() => rendered.result.current.accountSubplebbits["music-posting.bso"]); - const accountSubplebbitKeys = Object.keys(rendered.result.current.accountSubplebbits); - expect(accountSubplebbitKeys).toContain("music-posting.bso"); - expect(accountSubplebbitKeys).not.toContain("music-posting.eth"); - expect(rendered.result.current.accountSubplebbits["music-posting.bso"].address).toBe( + await waitFor(() => rendered.result.current.accountCommunities["music-posting.bso"]); + const accountCommunityKeys = Object.keys(rendered.result.current.accountCommunities); + expect(accountCommunityKeys).toContain("music-posting.bso"); + expect(accountCommunityKeys).not.toContain("music-posting.eth"); + expect(rendered.result.current.accountCommunities["music-posting.bso"].address).toBe( "music-posting.bso", ); }); - test("remove subplebbit role to account.subplebbits[subplebbitAddress].role after encountering it removed a subplebbit", async () => { - await waitFor(() => rendered.result.current.accountSubplebbits["subplebbit address 1"]); - expect(rendered.result.current.accountSubplebbits["subplebbit address 1"].role.role).toBe( + test("remove community role to account.communities[communityAddress].role after encountering it removed a community", async () => { + await waitFor(() => rendered.result.current.accountCommunities["community address 1"]); + expect(rendered.result.current.accountCommunities["community address 1"].role.role).toBe( "moderator", ); - // subplebbit address 1 doesn't have account.author.address as role, so it gets removed from accountSubplebbits + // community address 1 doesn't have account.author.address as role, so it gets removed from accountCommunities // after a render - await waitFor(() => !rendered.result.current.accountSubplebbits["subplebbit address 1"]); - expect(rendered.result.current.accountSubplebbits["subplebbit address 1"]).toBe(undefined); + await waitFor(() => !rendered.result.current.accountCommunities["community address 1"]); + expect(rendered.result.current.accountCommunities["community address 1"]).toBe(undefined); }); }); - test("add subplebbit role to account.subplebbits[subplebbitAddress].role after encountering it in a subplebbit", async () => { + test("add community role to account.communities[communityAddress].role after encountering it in a community", async () => { // don't use the same setup or test doesnt work - // mock the roles on a new subplebbit + // mock the roles on a new community const moderatorAuthorAddress = "author address"; - const moderatingSubplebbitAddress = "moderating subplebbit address"; - const rolesToGet = Subplebbit.prototype.rolesToGet; - Subplebbit.prototype.rolesToGet = () => ({ + const moderatingCommunityAddress = "moderating community address"; + const rolesToGet = Community.prototype.rolesToGet; + Community.prototype.rolesToGet = () => ({ [moderatorAuthorAddress]: { role: "moderator" }, }); - const rendered = renderHook((subplebbitAddress) => { - const { accountSubplebbits } = useAccountSubplebbits(); + const rendered = renderHook((communityAddress) => { + const { accountCommunities } = useAccountCommunities(); const account = useAccount(); const { setAccount } = accountsActions; - const subplebbit = useSubplebbit({ subplebbitAddress }); - return { accountSubplebbits, setAccount, account }; + const community = useCommunity({ communityAddress }); + return { accountCommunities, setAccount, account }; }); const waitFor = testUtils.createWaitFor(rendered); await waitFor(() => rendered.result.current.account); @@ -2377,31 +2369,31 @@ describe("accounts", () => { await waitFor( () => rendered.result.current.account.author.address === moderatorAuthorAddress, ); - // account subplebbits are not yet added, will be added after we fetch the sub - expect(rendered.result.current.accountSubplebbits[moderatingSubplebbitAddress]).toBe( + // account communities are not yet added, will be added after we fetch the sub + expect(rendered.result.current.accountCommunities[moderatingCommunityAddress]).toBe( undefined, ); - expect(rendered.result.current.account.subplebbits[moderatingSubplebbitAddress]).toBe( + expect(rendered.result.current.account.communities[moderatingCommunityAddress]).toBe( undefined, ); - // fetch the moderating subplebbit from the moderator account - rendered.rerender(moderatingSubplebbitAddress); - await waitFor(() => rendered.result.current.accountSubplebbits[moderatingSubplebbitAddress]); - expect( - rendered.result.current.accountSubplebbits[moderatingSubplebbitAddress].role.role, - ).toBe("moderator"); - await waitFor(() => rendered.result.current.account.subplebbits[moderatingSubplebbitAddress]); + // fetch the moderating community from the moderator account + rendered.rerender(moderatingCommunityAddress); + await waitFor(() => rendered.result.current.accountCommunities[moderatingCommunityAddress]); + expect(rendered.result.current.accountCommunities[moderatingCommunityAddress].role.role).toBe( + "moderator", + ); + await waitFor(() => rendered.result.current.account.communities[moderatingCommunityAddress]); expect( - rendered.result.current.account.subplebbits[moderatingSubplebbitAddress].role.role, + rendered.result.current.account.communities[moderatingCommunityAddress].role.role, ).toBe("moderator"); // unmock the roles - Subplebbit.prototype.rolesToGet = rolesToGet; + Community.prototype.rolesToGet = rolesToGet; }); }); - describe("create owner subplebbit", () => { + describe("create owner community", () => { let rendered: any; let waitFor: Function; @@ -2410,11 +2402,11 @@ describe("accounts", () => { }); beforeEach(async () => { - rendered = renderHook((subplebbitAddress?: string) => { + rendered = renderHook((communityAddress?: string) => { const account = useAccount(); - const { accountSubplebbits } = useAccountSubplebbits(); - const subplebbit = useSubplebbit({ subplebbitAddress }); - return { account, subplebbit, accountSubplebbits, ...accountsActions }; + const { accountCommunities } = useAccountCommunities(); + const community = useCommunity({ communityAddress }); + return { account, community, accountCommunities, ...accountsActions }; }); waitFor = testUtils.createWaitFor(rendered); await waitFor(() => rendered.result.current.account); @@ -2424,38 +2416,38 @@ describe("accounts", () => { resetPlebbitJsMock(); }); - test("create owner subplebbit and edit it", async () => { - const createdSubplebbitAddress = "created subplebbit address"; - let subplebbit: any; + test("create owner community and edit it", async () => { + const createdCommunityAddress = "created community address"; + let community: any; await act(async () => { - subplebbit = await rendered.result.current.createSubplebbit(); + community = await rendered.result.current.createCommunity(); }); - expect(subplebbit?.address).toBe(createdSubplebbitAddress); + expect(community?.address).toBe(createdCommunityAddress); - // wait for subplebbit to be added to account subplebbits (optional chaining for React 19 batching) + // wait for community to be added to account communities (optional chaining for React 19 batching) await waitFor( () => - rendered.result.current.accountSubplebbits[createdSubplebbitAddress]?.role?.role === + rendered.result.current.accountCommunities[createdCommunityAddress]?.role?.role === "owner", ); await act(async () => {}); - expect(rendered.result.current.accountSubplebbits[createdSubplebbitAddress]?.role?.role).toBe( + expect(rendered.result.current.accountCommunities[createdCommunityAddress]?.role?.role).toBe( "owner", ); - // can useSubplebbit - rendered.rerender(createdSubplebbitAddress); - await waitFor(() => rendered.result.current.subplebbit); - expect(rendered.result.current.subplebbit.address).toBe(createdSubplebbitAddress); + // can useCommunity + rendered.rerender(createdCommunityAddress); + await waitFor(() => rendered.result.current.community); + expect(rendered.result.current.community.address).toBe(createdCommunityAddress); // TODO: figure out why next line unreliable in CI - // expect(rendered.result.current.subplebbit.title).toBe(undefined) + // expect(rendered.result.current.community.title).toBe(undefined) - // publishSubplebbitEdit + // publishCommunityEdit const editedTitle = "edited title"; const onChallenge = vi.fn(); const onChallengeVerification = vi.fn(); await act(async () => { - await rendered.result.current.publishSubplebbitEdit(createdSubplebbitAddress, { + await rendered.result.current.publishCommunityEdit(createdCommunityAddress, { title: editedTitle, onChallenge, onChallengeVerification, @@ -2467,15 +2459,15 @@ describe("accounts", () => { expect(onChallengeVerification).toBeCalledTimes(1); expect(onChallengeVerification.mock.calls[0][0].challengeSuccess).toBe(true); - // useSubplebbit is edited - await waitFor(() => rendered.result.current.subplebbit.title === editedTitle); - expect(rendered.result.current.subplebbit.address).toBe(createdSubplebbitAddress); - expect(rendered.result.current.subplebbit.title).toBe(editedTitle); + // useCommunity is edited + await waitFor(() => rendered.result.current.community.title === editedTitle); + expect(rendered.result.current.community.address).toBe(createdCommunityAddress); + expect(rendered.result.current.community.title).toBe(editedTitle); // edit address const editedAddress = "edited.eth"; await act(async () => { - await rendered.result.current.publishSubplebbitEdit(createdSubplebbitAddress, { + await rendered.result.current.publishCommunityEdit(createdCommunityAddress, { address: editedAddress, title: editedTitle, onChallenge, @@ -2483,111 +2475,111 @@ describe("accounts", () => { }); }); - // useSubplebbit(previousAddress) address is edited - await waitFor(() => rendered.result.current.subplebbit.address === editedAddress); - expect(rendered.result.current.subplebbit.address).toBe(editedAddress); - expect(rendered.result.current.subplebbit.title).toBe(editedTitle); + // useCommunity(previousAddress) address is edited + await waitFor(() => rendered.result.current.community.address === editedAddress); + expect(rendered.result.current.community.address).toBe(editedAddress); + expect(rendered.result.current.community.title).toBe(editedTitle); - // useSubplebbit(currentAddress) address is edited + // useCommunity(currentAddress) address is edited rendered.rerender("doesnt exist"); - await waitFor(() => rendered.result.current.subplebbit.address === "doesnt exist"); - expect(rendered.result.current.subplebbit.address).toBe("doesnt exist"); + await waitFor(() => rendered.result.current.community.address === "doesnt exist"); + expect(rendered.result.current.community.address).toBe("doesnt exist"); rendered.rerender(editedAddress); - await waitFor(() => rendered.result.current.subplebbit.address === editedAddress); - expect(rendered.result.current.subplebbit.address).toBe(editedAddress); - expect(rendered.result.current.subplebbit.title).toBe(editedTitle); + await waitFor(() => rendered.result.current.community.address === editedAddress); + expect(rendered.result.current.community.address).toBe(editedAddress); + expect(rendered.result.current.community.title).toBe(editedTitle); }); - test("create owner subplebbit and delete it", async () => { - const createdSubplebbitAddress = "created subplebbit address"; - let subplebbit: any; + test("create owner community and delete it", async () => { + const createdCommunityAddress = "created community address"; + let community: any; await act(async () => { - subplebbit = await rendered.result.current.createSubplebbit(); + community = await rendered.result.current.createCommunity(); }); - expect(subplebbit?.address).toBe(createdSubplebbitAddress); + expect(community?.address).toBe(createdCommunityAddress); - // can useSubplebbit - rendered.rerender(createdSubplebbitAddress); - await waitFor(() => rendered.result.current.subplebbit); - expect(rendered.result.current.subplebbit.address).toBe(createdSubplebbitAddress); - expect(rendered.result.current.subplebbit.title).toBe(undefined); + // can useCommunity + rendered.rerender(createdCommunityAddress); + await waitFor(() => rendered.result.current.community); + expect(rendered.result.current.community.address).toBe(createdCommunityAddress); + expect(rendered.result.current.community.title).toBe(undefined); // delete it await act(async () => { - await rendered.result.current.deleteSubplebbit(createdSubplebbitAddress); + await rendered.result.current.deleteCommunity(createdCommunityAddress); }); - // useSubplebbit is edited - await waitFor(() => rendered.result.current.subplebbit.address === undefined); - expect(rendered.result.current.subplebbit.address).toBe(undefined); + // useCommunity is edited + await waitFor(() => rendered.result.current.community.address === undefined); + expect(rendered.result.current.community.address).toBe(undefined); }); - test("create and edit owner subplebbit useSubplebbit persists after reload", async () => { - const createdSubplebbitAddress = "created subplebbit address"; - const createdSubplebbitTitle = "created subplebbit title"; - let subplebbit: any; + test("create and edit owner community useCommunity persists after reload", async () => { + const createdCommunityAddress = "created community address"; + const createdCommunityTitle = "created community title"; + let community: any; await act(async () => { - subplebbit = await rendered.result.current.createSubplebbit({ - title: createdSubplebbitTitle, + community = await rendered.result.current.createCommunity({ + title: createdCommunityTitle, }); }); - expect(subplebbit?.address).toBe(createdSubplebbitAddress); + expect(community?.address).toBe(createdCommunityAddress); - // can useSubplebbit - rendered.rerender(createdSubplebbitAddress); - await waitFor(() => rendered.result.current.subplebbit); - expect(rendered.result.current.subplebbit.address).toBe(createdSubplebbitAddress); - expect(rendered.result.current.subplebbit.title).toBe(createdSubplebbitTitle); + // can useCommunity + rendered.rerender(createdCommunityAddress); + await waitFor(() => rendered.result.current.community); + expect(rendered.result.current.community.address).toBe(createdCommunityAddress); + expect(rendered.result.current.community.title).toBe(createdCommunityTitle); // render again with new context and store await testUtils.resetStores(); - rendered = renderHook((subplebbitAddress?: string) => { - const subplebbit = useSubplebbit({ subplebbitAddress }); - return { subplebbit, ...accountsActions }; + rendered = renderHook((communityAddress?: string) => { + const community = useCommunity({ communityAddress }); + return { community, ...accountsActions }; }); - expect(rendered.result.current.subplebbit.address).toBe(undefined); + expect(rendered.result.current.community.address).toBe(undefined); - // can useSubplebbit after reload - rendered.rerender(createdSubplebbitAddress); - await waitFor(() => rendered.result.current.subplebbit.address); - expect(rendered.result.current.subplebbit.address).toBe(createdSubplebbitAddress); - expect(rendered.result.current.subplebbit.title).toBe(createdSubplebbitTitle); + // can useCommunity after reload + rendered.rerender(createdCommunityAddress); + await waitFor(() => rendered.result.current.community.address); + expect(rendered.result.current.community.address).toBe(createdCommunityAddress); + expect(rendered.result.current.community.title).toBe(createdCommunityTitle); - // publishSubplebbitEdit + // publishCommunityEdit const editedTitle = "edited title"; const onChallenge = vi.fn(); const onChallengeVerification = vi.fn(); await act(async () => { - await rendered.result.current.publishSubplebbitEdit(createdSubplebbitAddress, { + await rendered.result.current.publishCommunityEdit(createdCommunityAddress, { title: editedTitle, onChallenge, onChallengeVerification, }); }); - // useSubplebbit is edited - await waitFor(() => rendered.result.current.subplebbit.title === editedTitle); - expect(rendered.result.current.subplebbit.address).toBe(createdSubplebbitAddress); - expect(rendered.result.current.subplebbit.title).toBe(editedTitle); + // useCommunity is edited + await waitFor(() => rendered.result.current.community.title === editedTitle); + expect(rendered.result.current.community.address).toBe(createdCommunityAddress); + expect(rendered.result.current.community.title).toBe(editedTitle); // render again with new context and store await testUtils.resetStores(); - rendered = renderHook((subplebbitAddress?: string) => { - const subplebbit = useSubplebbit({ subplebbitAddress }); - return { subplebbit, ...accountsActions }; + rendered = renderHook((communityAddress?: string) => { + const community = useCommunity({ communityAddress }); + return { community, ...accountsActions }; }); - expect(rendered.result.current.subplebbit.address).toBe(undefined); + expect(rendered.result.current.community.address).toBe(undefined); - // can useSubplebbit after reload - rendered.rerender(createdSubplebbitAddress); - await waitFor(() => rendered.result.current.subplebbit.title === editedTitle); - expect(rendered.result.current.subplebbit.address).toBe(createdSubplebbitAddress); - expect(rendered.result.current.subplebbit.title).toBe(editedTitle); + // can useCommunity after reload + rendered.rerender(createdCommunityAddress); + await waitFor(() => rendered.result.current.community.title === editedTitle); + expect(rendered.result.current.community.address).toBe(createdCommunityAddress); + expect(rendered.result.current.community.title).toBe(editedTitle); // edit address const editedAddress = "edited.eth"; await act(async () => { - await rendered.result.current.publishSubplebbitEdit(createdSubplebbitAddress, { + await rendered.result.current.publishCommunityEdit(createdCommunityAddress, { address: editedAddress, title: editedTitle, onChallenge, @@ -2597,31 +2589,31 @@ describe("accounts", () => { // render again with new context and store await testUtils.resetStores(); - rendered = renderHook((subplebbitAddress?: string) => { - const subplebbit = useSubplebbit({ subplebbitAddress }); - return { subplebbit, ...accountsActions }; + rendered = renderHook((communityAddress?: string) => { + const community = useCommunity({ communityAddress }); + return { community, ...accountsActions }; }); - expect(rendered.result.current.subplebbit.address).toBe(undefined); + expect(rendered.result.current.community.address).toBe(undefined); - // useSubplebbit(previousAddress) address is edited - rendered.rerender(createdSubplebbitAddress); - await waitFor(() => rendered.result.current.subplebbit.address === editedAddress); - expect(rendered.result.current.subplebbit.address).toBe(editedAddress); - expect(rendered.result.current.subplebbit.title).toBe(editedTitle); + // useCommunity(previousAddress) address is edited + rendered.rerender(createdCommunityAddress); + await waitFor(() => rendered.result.current.community.address === editedAddress); + expect(rendered.result.current.community.address).toBe(editedAddress); + expect(rendered.result.current.community.title).toBe(editedTitle); - // useSubplebbit(currentAddress) address is edited + // useCommunity(currentAddress) address is edited rendered.rerender(`doesnt exist`); await waitFor( () => - rendered.result.current.subplebbit.address === undefined || - rendered.result.current.subplebbit.address === "doesnt exist", + rendered.result.current.community.address === undefined || + rendered.result.current.community.address === "doesnt exist", ); - expect([undefined, "doesnt exist"]).toContain(rendered.result.current.subplebbit.address); + expect([undefined, "doesnt exist"]).toContain(rendered.result.current.community.address); rendered.rerender(editedAddress); - await waitFor(() => rendered.result.current.subplebbit.address === editedAddress); - expect(rendered.result.current.subplebbit.address).toBe(editedAddress); - expect(rendered.result.current.subplebbit.title).toBe(editedTitle); + await waitFor(() => rendered.result.current.community.address === editedAddress); + expect(rendered.result.current.community.address).toBe(editedAddress); + expect(rendered.result.current.community.title).toBe(editedTitle); }); }); @@ -2645,7 +2637,7 @@ describe("accounts", () => { title: "title " + String(number), content: "content " + String(number), parentCid: "parent comment cid " + String(number), - subplebbitAddress: "subplebbit address", + communityAddress: "community address", // @ts-ignore onChallenge: (challenge, comment) => { publishedComments.push(comment); @@ -2691,8 +2683,8 @@ describe("accounts", () => { test("edited comment succeeded", async () => { const commentCid = rendered.result.current.accountComments[0].cid; expect(commentCid).not.toBe(undefined); - const subplebbitAddress = rendered.result.current.accountComments[0].subplebbitAddress; - expect(subplebbitAddress).not.toBe(undefined); + const communityAddress = rendered.result.current.accountComments[0].communityAddress; + expect(communityAddress).not.toBe(undefined); rendered.rerender(commentCid); @@ -2712,7 +2704,7 @@ describe("accounts", () => { const publishCommentEditOptions = { timestamp: commentEditTimestamp, commentCid, - subplebbitAddress, + communityAddress, spoiler: true, onChallenge: (challenge: any, comment: any) => comment.publishChallengeAnswers(), onChallengeVerification: () => challengeVerificationCount++, @@ -2773,7 +2765,7 @@ describe("accounts", () => { test("useEditedComment pending when propertyNameEdit is too recent to evaluate", async () => { const commentCid = rendered.result.current.accountComments[0].cid; - const subplebbitAddress = rendered.result.current.accountComments[0].subplebbitAddress; + const communityAddress = rendered.result.current.accountComments[0].communityAddress; const now = Math.round(Date.now() / 1000); const editTimestamp = now - 60 * 10; @@ -2785,7 +2777,7 @@ describe("accounts", () => { cid: commentCid, spoiler: undefined, updatedAt: now - 60 * 5, - subplebbitAddress, + communityAddress, }, }, })); @@ -2814,8 +2806,8 @@ describe("accounts", () => { test("comment moderation succeeded", async () => { const commentCid = rendered.result.current.accountComments[0].cid; expect(commentCid).not.toBe(undefined); - const subplebbitAddress = rendered.result.current.accountComments[0].subplebbitAddress; - expect(subplebbitAddress).not.toBe(undefined); + const communityAddress = rendered.result.current.accountComments[0].communityAddress; + expect(communityAddress).not.toBe(undefined); rendered.rerender(commentCid); @@ -2835,7 +2827,7 @@ describe("accounts", () => { const publishCommentModerationOptions = { timestamp: commentModerationTimestamp, commentCid, - subplebbitAddress, + communityAddress, commentModeration: { locked: true }, onChallenge: (challenge: any, comment: any) => comment.publishChallengeAnswers(), onChallengeVerification: () => challengeVerificationCount++, @@ -2892,8 +2884,8 @@ describe("accounts", () => { test("edited comment failed", async () => { const commentCid = rendered.result.current.accountComments[0].cid; expect(commentCid).not.toBe(undefined); - const subplebbitAddress = rendered.result.current.accountComments[0].subplebbitAddress; - expect(subplebbitAddress).not.toBe(undefined); + const communityAddress = rendered.result.current.accountComments[0].communityAddress; + expect(communityAddress).not.toBe(undefined); rendered.rerender(commentCid); @@ -2913,7 +2905,7 @@ describe("accounts", () => { const publishCommentEditOptions = { timestamp: commentEditTimestamp, commentCid, - subplebbitAddress, + communityAddress, spoiler: true, onChallenge: (challenge: any, comment: any) => comment.publishChallengeAnswers(), onChallengeVerification: () => challengeVerificationCount++, @@ -2983,8 +2975,8 @@ describe("accounts", () => { test("comment moderation failed", async () => { const commentCid = rendered.result.current.accountComments[0].cid; expect(commentCid).not.toBe(undefined); - const subplebbitAddress = rendered.result.current.accountComments[0].subplebbitAddress; - expect(subplebbitAddress).not.toBe(undefined); + const communityAddress = rendered.result.current.accountComments[0].communityAddress; + expect(communityAddress).not.toBe(undefined); rendered.rerender(commentCid); @@ -3004,7 +2996,7 @@ describe("accounts", () => { const publishCommentModerationOptions = { timestamp: commentModerationTimestamp, commentCid, - subplebbitAddress, + communityAddress, commentModeration: { locked: true }, onChallenge: (challenge: any, comment: any) => comment.publishChallengeAnswers(), onChallengeVerification: () => challengeVerificationCount++, @@ -3074,8 +3066,8 @@ describe("accounts", () => { test("purge comment moderation succeeded", async () => { const commentCid = rendered.result.current.accountComments[0].cid; expect(commentCid).not.toBe(undefined); - const subplebbitAddress = rendered.result.current.accountComments[0].subplebbitAddress; - expect(subplebbitAddress).not.toBe(undefined); + const communityAddress = rendered.result.current.accountComments[0].communityAddress; + expect(communityAddress).not.toBe(undefined); rendered.rerender(commentCid); @@ -3094,7 +3086,7 @@ describe("accounts", () => { const publishCommentModerationOptions = { timestamp: commentModerationTimestamp, commentCid, - subplebbitAddress, + communityAddress, commentModeration: { purged: true }, onChallenge: (challenge: any, comment: any) => comment.publishChallengeAnswers(), onChallengeVerification: () => challengeVerificationCount++, @@ -3156,8 +3148,8 @@ describe("accounts", () => { test("purge comment moderation failed", async () => { const commentCid = rendered.result.current.accountComments[0].cid; expect(commentCid).not.toBe(undefined); - const subplebbitAddress = rendered.result.current.accountComments[0].subplebbitAddress; - expect(subplebbitAddress).not.toBe(undefined); + const communityAddress = rendered.result.current.accountComments[0].communityAddress; + expect(communityAddress).not.toBe(undefined); rendered.rerender(commentCid); @@ -3176,7 +3168,7 @@ describe("accounts", () => { const publishCommentModerationOptions = { timestamp: commentModerationTimestamp, commentCid, - subplebbitAddress, + communityAddress, commentModeration: { purged: true }, onChallenge: (challenge: any, comment: any) => comment.publishChallengeAnswers(), onChallengeVerification: () => challengeVerificationCount++, @@ -3268,7 +3260,7 @@ describe("accounts", () => { ...state, accountsComments: { ...state.accountsComments, - [accountId]: [{ content: "x", subplebbitAddress: "s.eth", timestamp: 1, index: 0 }], + [accountId]: [{ content: "x", communityAddress: "s.eth", timestamp: 1, index: 0 }], }, }; }); @@ -3336,7 +3328,7 @@ describe("accounts", () => { title: "t", content: "c", parentCid: "p", - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", onChallenge: (ch: any, c: any) => c.publishChallengeAnswers(), onChallengeVerification: () => {}, }); diff --git a/src/hooks/accounts/accounts.ts b/src/hooks/accounts/accounts.ts index 1d797db0..15344175 100644 --- a/src/hooks/accounts/accounts.ts +++ b/src/hooks/accounts/accounts.ts @@ -4,7 +4,7 @@ import useAccountsStore from "../../stores/accounts"; import Logger from "@plebbit/plebbit-logger"; const log = Logger("bitsocial-react-hooks:accounts:hooks"); import assert from "assert"; -import { useListSubplebbits, useSubplebbits } from "../subplebbits"; +import { useListCommunities, useCommunities } from "../communities"; import type { AccountComment, AccountComments, @@ -13,8 +13,8 @@ import type { AccountVote, AccountsComments, AccountsCommentsReplies, - UseAccountSubplebbitsOptions, - UseAccountSubplebbitsResult, + UseAccountCommunitiesOptions, + UseAccountCommunitiesResult, UseAccountVoteOptions, UseAccountVoteResult, UseAccountVotesOptions, @@ -40,10 +40,10 @@ import { useCalculatedNotifications, } from "./utils"; import { - getCanonicalSubplebbitAddress, - getEquivalentSubplebbitAddressGroupKey, - pickPreferredEquivalentSubplebbitAddress, -} from "../../lib/subplebbit-address"; + getCanonicalCommunityAddress, + getEquivalentCommunityAddressGroupKey, + pickPreferredEquivalentCommunityAddress, +} from "../../lib/community-address"; import { addCommentModeration } from "../../lib/utils/comment-moderation"; import useInterval from "../utils/use-interval"; @@ -124,137 +124,137 @@ export function useAccounts() { } /** - * Returns all subplebbits where the account is a creator or moderator + * Returns all communities where the account is a creator or moderator */ -export function useAccountSubplebbits( - options?: UseAccountSubplebbitsOptions, -): UseAccountSubplebbitsResult { +export function useAccountCommunities( + options?: UseAccountCommunitiesOptions, +): UseAccountCommunitiesResult { assert( !options || typeof options === "object", - `useAccountSubplebbits options argument '${options}' not an object`, + `useAccountCommunities options argument '${options}' not an object`, ); const opts = options ?? {}; const { accountName, onlyIfCached } = opts; const accountId = useAccountId(accountName); const accountIdKey = accountId || ""; - const accountsStoreAccountSubplebbits = useAccountsStore( - (state) => state.accounts[accountIdKey]?.subplebbits, + const accountsStoreAccountCommunities = useAccountsStore( + (state) => state.accounts[accountIdKey]?.communities, ); - // get all unique account subplebbit addresses - const ownerSubplebbitAddresses = useListSubplebbits(); - const groupedSubplebbitAddresses = useMemo(() => { - const accountSubplebbitAddresses = []; - if (accountsStoreAccountSubplebbits) { - for (const subplebbitAddress in accountsStoreAccountSubplebbits) { - accountSubplebbitAddresses.push(subplebbitAddress); + // get all unique account community addresses + const ownerCommunityAddresses = useListCommunities(); + const groupedCommunityAddresses = useMemo(() => { + const accountCommunityAddresses = []; + if (accountsStoreAccountCommunities) { + for (const communityAddress in accountsStoreAccountCommunities) { + accountCommunityAddresses.push(communityAddress); } } - const allSubplebbitAddresses = [ - ...new Set([...ownerSubplebbitAddresses, ...accountSubplebbitAddresses]), + const allCommunityAddresses = [ + ...new Set([...ownerCommunityAddresses, ...accountCommunityAddresses]), ].sort(); const groupedAddresses = new Map(); - for (const subplebbitAddress of allSubplebbitAddresses) { - const groupKey = getEquivalentSubplebbitAddressGroupKey(subplebbitAddress); + for (const communityAddress of allCommunityAddresses) { + const groupKey = getEquivalentCommunityAddressGroupKey(communityAddress); const addresses = groupedAddresses.get(groupKey); if (addresses) { - addresses.push(subplebbitAddress); + addresses.push(communityAddress); } else { - groupedAddresses.set(groupKey, [subplebbitAddress]); + groupedAddresses.set(groupKey, [communityAddress]); } } return [...groupedAddresses.entries()].map(([groupKey, addresses]) => ({ groupKey, addresses, - preferredAddress: pickPreferredEquivalentSubplebbitAddress(addresses), + preferredAddress: pickPreferredEquivalentCommunityAddress(addresses), })); - }, [accountsStoreAccountSubplebbits, ownerSubplebbitAddresses]); - const uniqueSubplebbitAddresses = useMemo( - () => groupedSubplebbitAddresses.map(({ preferredAddress }) => preferredAddress), - [groupedSubplebbitAddresses], + }, [accountsStoreAccountCommunities, ownerCommunityAddresses]); + const uniqueCommunityAddresses = useMemo( + () => groupedCommunityAddresses.map(({ preferredAddress }) => preferredAddress), + [groupedCommunityAddresses], ); - // fetch all subplebbit data - const { subplebbits: subplebbitsArray } = useSubplebbits({ - subplebbitAddresses: uniqueSubplebbitAddresses, + // fetch all community data + const { communities: communitiesArray } = useCommunities({ + communityAddresses: uniqueCommunityAddresses, accountName, onlyIfCached, }); const canonicalAddressByGroupKey = useMemo(() => { const canonicalAddresses: { [groupKey: string]: string } = {}; - for (const [i, { groupKey, preferredAddress }] of groupedSubplebbitAddresses.entries()) { - const fetchedAddress = subplebbitsArray[i]?.address; - canonicalAddresses[groupKey] = getCanonicalSubplebbitAddress( + for (const [i, { groupKey, preferredAddress }] of groupedCommunityAddresses.entries()) { + const fetchedAddress = communitiesArray[i]?.address; + canonicalAddresses[groupKey] = getCanonicalCommunityAddress( fetchedAddress || preferredAddress, ); } return canonicalAddresses; - }, [groupedSubplebbitAddresses, subplebbitsArray]); - const subplebbits: any = useMemo(() => { - const subplebbits: any = {}; - for (const [i, subplebbit] of subplebbitsArray.entries()) { - const { groupKey, preferredAddress } = groupedSubplebbitAddresses[i]; + }, [groupedCommunityAddresses, communitiesArray]); + const communities: any = useMemo(() => { + const communities: any = {}; + for (const [i, community] of communitiesArray.entries()) { + const { groupKey, preferredAddress } = groupedCommunityAddresses[i]; const canonicalAddress = canonicalAddressByGroupKey[groupKey] || - getCanonicalSubplebbitAddress(subplebbit?.address || preferredAddress); - subplebbits[canonicalAddress] = { - ...subplebbits[canonicalAddress], - ...subplebbit, - // make sure the canonical address is defined even if the subplebbit hasn't fetched yet + getCanonicalCommunityAddress(community?.address || preferredAddress); + communities[canonicalAddress] = { + ...communities[canonicalAddress], + ...community, + // make sure the canonical address is defined even if the community hasn't fetched yet address: canonicalAddress, }; } - return subplebbits; - }, [subplebbitsArray, groupedSubplebbitAddresses, canonicalAddressByGroupKey]); - - // merged subplebbit data with account.subplebbits data - const accountSubplebbits: any = useMemo(() => { - const accountSubplebbits: any = { ...subplebbits }; - if (accountsStoreAccountSubplebbits) { - for (const subplebbitAddress in accountsStoreAccountSubplebbits) { - const groupKey = getEquivalentSubplebbitAddressGroupKey(subplebbitAddress); + return communities; + }, [communitiesArray, groupedCommunityAddresses, canonicalAddressByGroupKey]); + + // merged community data with account.communities data + const accountCommunities: any = useMemo(() => { + const accountCommunities: any = { ...communities }; + if (accountsStoreAccountCommunities) { + for (const communityAddress in accountsStoreAccountCommunities) { + const groupKey = getEquivalentCommunityAddressGroupKey(communityAddress); const canonicalAddress = - canonicalAddressByGroupKey[groupKey] || getCanonicalSubplebbitAddress(subplebbitAddress); - accountSubplebbits[canonicalAddress] = { - ...accountSubplebbits[canonicalAddress], - ...accountsStoreAccountSubplebbits[subplebbitAddress], + canonicalAddressByGroupKey[groupKey] || getCanonicalCommunityAddress(communityAddress); + accountCommunities[canonicalAddress] = { + ...accountCommunities[canonicalAddress], + ...accountsStoreAccountCommunities[communityAddress], address: canonicalAddress, }; } } - // add plebbit.subplebbits data - for (const subplebbitAddress of ownerSubplebbitAddresses) { - const groupKey = getEquivalentSubplebbitAddressGroupKey(subplebbitAddress); + // add plebbit.communities data + for (const communityAddress of ownerCommunityAddresses) { + const groupKey = getEquivalentCommunityAddressGroupKey(communityAddress); const canonicalAddress = - canonicalAddressByGroupKey[groupKey] || getCanonicalSubplebbitAddress(subplebbitAddress); - accountSubplebbits[canonicalAddress] = { - ...accountSubplebbits[canonicalAddress], + canonicalAddressByGroupKey[groupKey] || getCanonicalCommunityAddress(communityAddress); + accountCommunities[canonicalAddress] = { + ...accountCommunities[canonicalAddress], address: canonicalAddress, role: { role: "owner" }, }; } - return accountSubplebbits; + return accountCommunities; }, [ - accountsStoreAccountSubplebbits, - ownerSubplebbitAddresses, - subplebbits, + accountsStoreAccountCommunities, + ownerCommunityAddresses, + communities, canonicalAddressByGroupKey, ]); if (accountId) { - log("useAccountSubplebbits", { accountSubplebbits }); + log("useAccountCommunities", { accountCommunities }); } const state = accountId ? "succeeded" : "initializing"; return useMemo( () => ({ - accountSubplebbits, + accountCommunities, state, error: undefined, errors: [], }), - [accountSubplebbits, state], + [accountCommunities, state], ); } @@ -427,8 +427,8 @@ export function useAccountComment(options?: UseAccountCommentOptions): UseAccoun } /** - * Returns the own user's votes stored locally, even those not yet published by the subplebbit owner. - * Check UseAccountCommentsOptions type in types.tsx to filter them, e.g. filter = {subplebbitAddresses: ['memes.eth']}. + * Returns the own user's votes stored locally, even those not yet published by the community owner. + * Check UseAccountCommentsOptions type in types.tsx to filter them, e.g. filter = {communityAddresses: ['memes.eth']}. */ export function useAccountVotes(options?: UseAccountVotesOptions): UseAccountVotesResult { assert( @@ -508,7 +508,7 @@ export function useAccountVote(options?: UseAccountVoteOptions): UseAccountVoteR } /** - * Returns all the comment and subplebbit edits published by an account. + * Returns all the comment and community edits published by an account. */ export function useAccountEdits(options?: UseAccountEditsOptions): UseAccountEditsResult { assert( @@ -596,7 +596,7 @@ export function useEditedComment(options?: UseEditedCommentOptions): UseEditedCo "author", "signer", "commentCid", - "subplebbitAddress", + "communityAddress", "timestamp", ]); @@ -741,7 +741,7 @@ export function useEditedComment(options?: UseEditedCommentOptions): UseEditedCo * by subscribing to the pubsub right away. * * @param accountName - The nickname of the account, e.g. 'Account 1'. - * @param subplebbitAddress - The subplebbit address to subscribe to, e.g. 'news.eth'. + * @param communityAddress - The community address to subscribe to, e.g. 'news.eth'. */ export function usePubsubSubscribe(options?: UsePubsubSubscribeOptions): UsePubsubSubscribeResult { assert( @@ -749,7 +749,7 @@ export function usePubsubSubscribe(options?: UsePubsubSubscribeOptions): UsePubs `usePubsubSubscribe options argument '${options}' not an object`, ); const opts = options ?? {}; - const { accountName, subplebbitAddress } = opts; + const { accountName, communityAddress } = opts; const accountId = useAccountId(accountName); const accountIdKey = accountId || ""; const account = useAccountsStore((state) => state.accounts[accountIdKey]); @@ -757,30 +757,30 @@ export function usePubsubSubscribe(options?: UsePubsubSubscribeOptions): UsePubs const [errors, setErrors] = useState([]); useEffect(() => { - if (!account?.plebbit || !subplebbitAddress) { + if (!account?.plebbit || !communityAddress) { return; } setState("subscribing"); account.plebbit - .pubsubSubscribe(subplebbitAddress) + .pubsubSubscribe(communityAddress) .then(() => setState("succeeded")) .catch((error: any) => { setErrors([...errors, error]); setState("failed"); - log.error("usePubsubSubscribe plebbit.pubsubSubscribe error", { subplebbitAddress, error }); + log.error("usePubsubSubscribe plebbit.pubsubSubscribe error", { communityAddress, error }); }); // unsub on component unmount return function () { - account.plebbit.pubsubUnsubscribe(subplebbitAddress).catch((error: any) => { + account.plebbit.pubsubUnsubscribe(communityAddress).catch((error: any) => { setErrors([...errors, error]); log.error("usePubsubSubscribe plebbit.pubsubUnsubscribe error", { - subplebbitAddress, + communityAddress, error, }); }); }; - }, [account?.plebbit, subplebbitAddress]); + }, [account?.plebbit, communityAddress]); return useMemo( () => ({ diff --git a/src/hooks/accounts/index.ts b/src/hooks/accounts/index.ts index cd504a73..974a8c33 100644 --- a/src/hooks/accounts/index.ts +++ b/src/hooks/accounts/index.ts @@ -1 +1 @@ -export * from './accounts' +export * from "./accounts"; diff --git a/src/hooks/accounts/utils.test.ts b/src/hooks/accounts/utils.test.ts index 4fea583b..a0a94113 100644 --- a/src/hooks/accounts/utils.test.ts +++ b/src/hooks/accounts/utils.test.ts @@ -36,35 +36,35 @@ describe("accounts utils", () => { const accountCommentsReplies = { reply1: { cid: "reply1", - subplebbitAddress: "blocked.eth", + communityAddress: "blocked.eth", parentCid: "p1", postCid: "post1", timestamp: 1, }, reply2: { cid: "blocked-cid", - subplebbitAddress: "ok.eth", + communityAddress: "ok.eth", parentCid: "p2", postCid: "post2", timestamp: 2, }, reply3: { cid: "reply3", - subplebbitAddress: "ok.eth", + communityAddress: "ok.eth", parentCid: "blocked-cid", postCid: "post3", timestamp: 3, }, reply4: { cid: "reply4", - subplebbitAddress: "ok.eth", + communityAddress: "ok.eth", parentCid: "p4", postCid: "blocked-cid", timestamp: 4, }, reply5: { cid: "reply5", - subplebbitAddress: "ok.eth", + communityAddress: "ok.eth", parentCid: "p5", postCid: "post5", author: { address: "blocked.eth" }, @@ -72,7 +72,7 @@ describe("accounts utils", () => { }, reply6: { cid: "reply6", - subplebbitAddress: "ok.eth", + communityAddress: "ok.eth", parentCid: "p6", postCid: "post6", timestamp: 6, @@ -102,7 +102,7 @@ describe("accounts utils", () => { id1: { reply1: { cid: "reply1", - subplebbitAddress: "s1", + communityAddress: "s1", parentCid: "p1", postCid: "post1", timestamp: 1, diff --git a/src/hooks/accounts/utils.ts b/src/hooks/accounts/utils.ts index 47d8ba48..835f9335 100644 --- a/src/hooks/accounts/utils.ts +++ b/src/hooks/accounts/utils.ts @@ -127,7 +127,7 @@ const getReplyNotificationsFromAccountCommentsReplies = ( for (const replyCid in accountCommentsReplies) { const reply = accountCommentsReplies[replyCid]; if ( - accountBlockedAddresses?.[reply.subplebbitAddress] || + accountBlockedAddresses?.[reply.communityAddress] || accountBlockedAddresses?.[reply.author?.address] ) { continue; diff --git a/src/hooks/actions/actions.test.ts b/src/hooks/actions/actions.test.ts index c97598ac..b214cb1d 100644 --- a/src/hooks/actions/actions.test.ts +++ b/src/hooks/actions/actions.test.ts @@ -5,11 +5,11 @@ import { usePublishComment, usePublishCommentEdit, usePublishCommentModeration, - usePublishSubplebbitEdit, + usePublishCommunityEdit, usePublishVote, useBlock, useAccount, - useCreateSubplebbit, + useCreateCommunity, setPlebbitJs, useAccountVote, useAccountComments, @@ -24,9 +24,9 @@ import PlebbitJsMock, { Comment, CommentEdit, CommentModeration, - SubplebbitEdit, + CommunityEdit, Vote, - Subplebbit, + Community, Pages, resetPlebbitJsMock, debugPlebbitJsMock, @@ -122,9 +122,9 @@ describe("actions", () => { await testUtils.resetDatabasesAndStores(); }); - test(`subscribe and unsubscribe to subplebbit`, async () => { - const subplebbitAddress = "tosubscribeto.eth"; - const subplebbitAddress2 = "tosubscribeto2.eth"; + test(`subscribe and unsubscribe to community`, async () => { + const communityAddress = "tosubscribeto.eth"; + const communityAddress2 = "tosubscribeto2.eth"; expect(rendered.result.current[0].state).toBe("initializing"); expect(rendered.result.current[0].subscribed).toBe(undefined); @@ -132,7 +132,7 @@ describe("actions", () => { expect(typeof rendered.result.current[0].unsubscribe).toBe("function"); // get the default value - rendered.rerender([{ subplebbitAddress }]); + rendered.rerender([{ communityAddress }]); await waitFor(() => typeof rendered.result.current[0].subscribed === "boolean"); expect(rendered.result.current[0].state).toBe("ready"); expect(rendered.result.current[0].subscribed).toBe(false); @@ -166,7 +166,7 @@ describe("actions", () => { expect(rendered.result.current[0].errors.length).toBe(2); // subscribe to 2 subs - rendered.rerender([{ subplebbitAddress }, { subplebbitAddress: subplebbitAddress2 }]); + rendered.rerender([{ communityAddress }, { communityAddress: communityAddress2 }]); await waitFor(() => rendered.result.current[0].state === "ready"); await waitFor(() => rendered.result.current[1].state === "ready"); expect(rendered.result.current[0].state).toBe("ready"); @@ -196,7 +196,7 @@ describe("actions", () => { // subscribing persists in database after store reset const rendered2 = renderHook(() => - useSubscribe({ subplebbitAddress: subplebbitAddress2 }), + useSubscribe({ communityAddress: communityAddress2 }), ); const waitFor2 = testUtils.createWaitFor(rendered2); await waitFor2(() => rendered2.result.current.state === "ready"); @@ -206,7 +206,7 @@ describe("actions", () => { test("useSubscribe onError callback when subscribe fails", async () => { const onError = vi.fn(); - rendered.rerender([{ subplebbitAddress: "tosubscribeto.eth", onError }]); + rendered.rerender([{ communityAddress: "tosubscribeto.eth", onError }]); await waitFor(() => typeof rendered.result.current[0].subscribed === "boolean"); await act(async () => { @@ -223,7 +223,7 @@ describe("actions", () => { test("useSubscribe onError callback when unsubscribe fails", async () => { const onError = vi.fn(); - rendered.rerender([{ subplebbitAddress: "tosubscribeto.eth", onError }]); + rendered.rerender([{ communityAddress: "tosubscribeto.eth", onError }]); await waitFor(() => typeof rendered.result.current[0].subscribed === "boolean"); await act(async () => { await rendered.result.current[0].subscribe(); @@ -263,7 +263,7 @@ describe("actions", () => { }).toThrow(/can't useBlock with both/); }); - test(`block and unblock two addresses (subplebbit addresses)`, async () => { + test(`block and unblock two addresses (community addresses)`, async () => { const address = "address.eth"; const address2 = "address2.eth"; @@ -458,13 +458,13 @@ describe("actions", () => { }); }); - describe("useCreateSubplebbit", () => { + describe("useCreateCommunity", () => { let rendered: any, waitFor: Function; beforeEach(async () => { - rendered = renderHook((useCreateSubplebbitOptions = []) => { - const result1 = useCreateSubplebbit(useCreateSubplebbitOptions[0]); - const result2 = useCreateSubplebbit(useCreateSubplebbitOptions[1]); + rendered = renderHook((useCreateCommunityOptions = []) => { + const result1 = useCreateCommunity(useCreateCommunityOptions[0]); + const result2 = useCreateCommunity(useCreateCommunityOptions[1]); return [result1, result2]; }); waitFor = testUtils.createWaitFor(rendered); @@ -474,10 +474,10 @@ describe("actions", () => { await testUtils.resetDatabasesAndStores(); }); - test(`can create subplebbit`, async () => { + test(`can create community`, async () => { expect(rendered.result.current[0].state).toBe("initializing"); - expect(rendered.result.current[0].createdSubplebbit).toBe(undefined); - expect(typeof rendered.result.current[0].createSubplebbit).toBe("function"); + expect(rendered.result.current[0].createdCommunity).toBe(undefined); + expect(typeof rendered.result.current[0].createCommunity).toBe("function"); const options1 = { title: "title", @@ -487,28 +487,28 @@ describe("actions", () => { rendered.rerender([options1]); await waitFor(() => rendered.result.current[0].state === "ready"); expect(rendered.result.current[0].state).toBe("ready"); - expect(rendered.result.current[0].createdSubplebbit).toBe(undefined); + expect(rendered.result.current[0].createdCommunity).toBe(undefined); - // create subplebbit + // create community await act(async () => { - await rendered.result.current[0].createSubplebbit(); + await rendered.result.current[0].createCommunity(); }); - await waitFor(() => rendered.result.current[0].createdSubplebbit); + await waitFor(() => rendered.result.current[0].createdCommunity); expect(rendered.result.current[0].state).toBe("succeeded"); - expect(rendered.result.current[0].createdSubplebbit?.title).toBe(options1.title); + expect(rendered.result.current[0].createdCommunity?.title).toBe(options1.title); - // useCreateSubplebbit 2 with same option not created + // useCreateCommunity 2 with same option not created rendered.rerender([options1, options1]); await waitFor(() => rendered.result.current[1].state === "ready"); expect(rendered.result.current[1].state).toBe("ready"); - expect(rendered.result.current[1].createdSubplebbit).toBe(undefined); + expect(rendered.result.current[1].createdCommunity).toBe(undefined); }); test(`can error`, async () => { // mock the comment publish to error out - const createSubplebbit = Plebbit.prototype.createSubplebbit; - Plebbit.prototype.createSubplebbit = async () => { - throw Error("create subplebbit error"); + const createCommunity = Plebbit.prototype.createCommunity; + Plebbit.prototype.createCommunity = async () => { + throw Error("create community error"); }; const options1 = { @@ -519,27 +519,27 @@ describe("actions", () => { rendered.rerender([options1]); await waitFor(() => rendered.result.current[0].state === "ready"); expect(rendered.result.current[0].state).toBe("ready"); - expect(rendered.result.current[0].createdSubplebbit).toBe(undefined); + expect(rendered.result.current[0].createdCommunity).toBe(undefined); - // create subplebbit + // create community await act(async () => { - await rendered.result.current[0].createSubplebbit(); + await rendered.result.current[0].createCommunity(); }); // wait for error await waitFor(() => rendered.result.current[0].error); - expect(rendered.result.current[0].error.message).toBe("create subplebbit error"); - expect(rendered.result.current[0].createdSubplebbit).toBe(undefined); + expect(rendered.result.current[0].error.message).toBe("create community error"); + expect(rendered.result.current[0].createdCommunity).toBe(undefined); expect(rendered.result.current[0].state).toBe("failed"); expect(rendered.result.current[0].errors.length).toBe(1); // restore mock - Plebbit.prototype.createSubplebbit = createSubplebbit; + Plebbit.prototype.createCommunity = createCommunity; }); - test("useCreateSubplebbit onError callback when create fails", async () => { - const createSubplebbit = Plebbit.prototype.createSubplebbit; - Plebbit.prototype.createSubplebbit = async () => { - throw Error("create subplebbit error"); + test("useCreateCommunity onError callback when create fails", async () => { + const createCommunity = Plebbit.prototype.createCommunity; + Plebbit.prototype.createCommunity = async () => { + throw Error("create community error"); }; const onError = vi.fn(); @@ -547,12 +547,12 @@ describe("actions", () => { await waitFor(() => rendered.result.current[0].state === "ready"); await act(async () => { - await rendered.result.current[0].createSubplebbit(); + await rendered.result.current[0].createCommunity(); }); await waitFor(() => rendered.result.current[0].error); expect(onError).toHaveBeenCalledWith(expect.any(Error)); - Plebbit.prototype.createSubplebbit = createSubplebbit; + Plebbit.prototype.createCommunity = createCommunity; }); }); @@ -574,7 +574,7 @@ describe("actions", () => { test(`publishChallengeAnswers throws when challenge not yet received`, async () => { const publishCommentOptions = { - subplebbitAddress: "12D3KooW... acions.test", + communityAddress: "12D3KooW... acions.test", parentCid: "Qm... acions.test", content: "content", }; @@ -590,7 +590,7 @@ describe("actions", () => { const onChallenge = vi.fn(); const onChallengeVerification = vi.fn(); const publishCommentOptions = { - subplebbitAddress: "12D3KooW... acions.test", + communityAddress: "12D3KooW... acions.test", parentCid: "Qm... acions.test", content: "some content acions.test", onChallenge, @@ -649,7 +649,7 @@ describe("actions", () => { comment.publishChallengeAnswers(), ); const publishCommentOptions = { - subplebbitAddress: "12D3KooW... acions.test", + communityAddress: "12D3KooW... acions.test", parentCid: "Qm... acions.test", content: "no onChallengeVerification test", onChallenge, @@ -673,7 +673,7 @@ describe("actions", () => { test(`abandon during waiting-challenge-answers removes pending local comment and returns hook state to ready`, async () => { const publishCommentOptions = { - subplebbitAddress: "12D3KooW... acions.test", + communityAddress: "12D3KooW... acions.test", parentCid: "Qm... acions.test", content: "abandon test content", }; @@ -737,7 +737,7 @@ describe("actions", () => { let challengeCalls = 0; const publishCommentOptions = { - subplebbitAddress: "12D3KooW... actions.test early abandon", + communityAddress: "12D3KooW... actions.test early abandon", parentCid: "Qm... actions.test early abandon", content: "abandon onChallenge test content", onChallenge: async () => { @@ -789,7 +789,7 @@ describe("actions", () => { })); const publishCommentOptions = { - subplebbitAddress: "12D3KooW... actions.test", + communityAddress: "12D3KooW... actions.test", parentCid: "Qm... actions.test", content: "abandon before reject test", }; @@ -830,7 +830,7 @@ describe("actions", () => { })); const publishCommentOptions = { - subplebbitAddress: "12D3KooW... actions.test", + communityAddress: "12D3KooW... actions.test", parentCid: "Qm... actions.test", content: "catch test", onError, @@ -866,7 +866,7 @@ describe("actions", () => { })); const publishCommentOptions = { - subplebbitAddress: "12D3KooW... actions.test", + communityAddress: "12D3KooW... actions.test", parentCid: "Qm... actions.test", content: "catch no onError test", }; @@ -890,7 +890,7 @@ describe("actions", () => { test(`abandon is idempotent-safe (second call no-ops or fails predictably, does not corrupt state)`, async () => { const publishCommentOptions = { - subplebbitAddress: "12D3KooW... acions.test", + communityAddress: "12D3KooW... acions.test", parentCid: "Qm... acions.test", content: "idempotent abandon test", }; @@ -924,7 +924,7 @@ describe("actions", () => { const onChallenge = vi.fn(); const onChallengeVerification = vi.fn(); const publishCommentOptions = { - subplebbitAddress: "12D3KooW... acions.test", + communityAddress: "12D3KooW... acions.test", parentCid: "Qm... acions.test", title: "some title acions.test", link: "some link acions.test", @@ -989,7 +989,7 @@ describe("actions", () => { const onError = vi.fn(); const publishCommentOptions = { - subplebbitAddress: "12D3KooW... acions.test", + communityAddress: "12D3KooW... acions.test", parentCid: "Qm... acions.test", content: "some content acions.test", onError, @@ -1042,7 +1042,7 @@ describe("actions", () => { const onChallenge = vi.fn(); const onChallengeVerification = vi.fn(); const publishCommentEditOptions = { - subplebbitAddress: "12D3KooW... acions.test", + communityAddress: "12D3KooW... acions.test", commentCid: "Qm... acions.test", spoiler: true, onChallenge, @@ -1104,7 +1104,7 @@ describe("actions", () => { const onError = vi.fn(); const publishCommentEditOptions = { - subplebbitAddress: "12D3KooW... acions.test", + communityAddress: "12D3KooW... acions.test", commentCid: "Qm... acions.test", spoiler: true, onError, @@ -1151,7 +1151,7 @@ describe("actions", () => { const onError = vi.fn(); rendered.rerender({ - subplebbitAddress: "12D3KooW... acions.test", + communityAddress: "12D3KooW... acions.test", commentCid: "Qm... acions.test", spoiler: true, onError, @@ -1196,7 +1196,7 @@ describe("actions", () => { const onChallenge = vi.fn(); const onChallengeVerification = vi.fn(); const publishCommentModerationOptions = { - subplebbitAddress: "12D3KooW... acions.test", + communityAddress: "12D3KooW... acions.test", commentCid: "Qm... acions.test", commentModeration: { locked: true }, onChallenge, @@ -1252,7 +1252,7 @@ describe("actions", () => { const onChallenge = vi.fn(); const onChallengeVerification = vi.fn(); const publishCommentModerationOptions = { - subplebbitAddress: "12D3KooW... acions.test", + communityAddress: "12D3KooW... acions.test", commentCid: "Qm... acions.test", commentModeration: { purged: true }, onChallenge, @@ -1314,7 +1314,7 @@ describe("actions", () => { const onError = vi.fn(); const publishCommentModerationOptions = { - subplebbitAddress: "12D3KooW... acions.test", + communityAddress: "12D3KooW... acions.test", commentCid: "Qm... acions.test", commentModeration: { locked: true }, onError, @@ -1360,7 +1360,7 @@ describe("actions", () => { const onError = vi.fn(); rendered.rerender({ - subplebbitAddress: "12D3KooW... acions.test", + communityAddress: "12D3KooW... acions.test", commentCid: "Qm... acions.test", commentModeration: { locked: true }, onError, @@ -1386,12 +1386,12 @@ describe("actions", () => { }); // retry usePublish because publishing state is flaky - describe("usePublishSubplebbitEdit", { retry: 3 }, () => { + describe("usePublishCommunityEdit", { retry: 3 }, () => { let rendered: any, waitFor: Function; beforeEach(async () => { rendered = renderHook((options) => { - const result = usePublishSubplebbitEdit(options); + const result = usePublishCommunityEdit(options); return result; }); waitFor = testUtils.createWaitFor(rendered); @@ -1401,16 +1401,16 @@ describe("actions", () => { await testUtils.resetDatabasesAndStores(); }); - test(`can publish subplebbit edit`, async () => { + test(`can publish community edit`, async () => { const onChallenge = vi.fn(); const onChallengeVerification = vi.fn(); - const publishSubplebbitEditOptions = { - subplebbitAddress: "12D3KooW... acions.test", + const publishCommunityEditOptions = { + communityAddress: "12D3KooW... acions.test", title: "new title", onChallenge, onChallengeVerification, }; - rendered.rerender(publishSubplebbitEditOptions); + rendered.rerender(publishCommunityEditOptions); // wait for ready await waitFor(() => rendered.result.current.state === "ready"); @@ -1418,7 +1418,7 @@ describe("actions", () => { // publish await act(async () => { - await rendered.result.current.publishSubplebbitEdit(); + await rendered.result.current.publishCommunityEdit(); }); await waitFor(() => rendered.result.current.state === "publishing-challenge-request"); @@ -1457,20 +1457,20 @@ describe("actions", () => { }); test(`can error`, async () => { - // mock the subplebbit edit publish to error out - const subplebbitEditPublish = SubplebbitEdit.prototype.publish; - SubplebbitEdit.prototype.publish = async function () { + // mock the community edit publish to error out + const communityEditPublish = CommunityEdit.prototype.publish; + CommunityEdit.prototype.publish = async function () { this.emit("error", Error("emit error")); throw Error("publish error"); }; const onError = vi.fn(); - const publishSubplebbitEditOptions = { - subplebbitAddress: "12D3KooW... acions.test", + const publishCommunityEditOptions = { + communityAddress: "12D3KooW... acions.test", title: "new title", onError, }; - rendered.rerender(publishSubplebbitEditOptions); + rendered.rerender(publishCommunityEditOptions); // wait for ready await waitFor(() => rendered.result.current.state === "ready"); @@ -1479,7 +1479,7 @@ describe("actions", () => { // publish await act(async () => { - await rendered.result.current.publishSubplebbitEdit(); + await rendered.result.current.publishCommunityEdit(); }); // wait for error @@ -1494,31 +1494,31 @@ describe("actions", () => { expect(onError.mock.calls[1][0].message).toBe("publish error"); // restore mock - SubplebbitEdit.prototype.publish = subplebbitEditPublish; + CommunityEdit.prototype.publish = communityEditPublish; }); - test("usePublishSubplebbitEdit hook catch and onError when store throws", async () => { - const original = useAccountsStore.getState().accountsActions.publishSubplebbitEdit; + test("usePublishCommunityEdit hook catch and onError when store throws", async () => { + const original = useAccountsStore.getState().accountsActions.publishCommunityEdit; useAccountsStore.setState((state: any) => ({ ...state, accountsActions: { ...state.accountsActions, - publishSubplebbitEdit: async () => { - throw Error("store publishSubplebbitEdit error"); + publishCommunityEdit: async () => { + throw Error("store publishCommunityEdit error"); }, }, })); const onError = vi.fn(); rendered.rerender({ - subplebbitAddress: "12D3KooW... acions.test", + communityAddress: "12D3KooW... acions.test", title: "new title", onError, }); await waitFor(() => rendered.result.current.state === "ready"); await act(async () => { - await rendered.result.current.publishSubplebbitEdit(); + await rendered.result.current.publishCommunityEdit(); }); expect(rendered.result.current.errors.length).toBe(1); @@ -1529,7 +1529,7 @@ describe("actions", () => { ...state, accountsActions: { ...state.accountsActions, - publishSubplebbitEdit: original, + publishCommunityEdit: original, }, })); }); @@ -1554,7 +1554,7 @@ describe("actions", () => { test(`publishChallengeAnswers throws when challenge not yet received`, async () => { const publishVoteOptions = { - subplebbitAddress: "12D3KooW... acions.test", + communityAddress: "12D3KooW... acions.test", commentCid: "Qm... acions.test", vote: 1, }; @@ -1570,7 +1570,7 @@ describe("actions", () => { const onChallenge = vi.fn(); const onChallengeVerification = vi.fn(); const publishVoteOptions = { - subplebbitAddress: "12D3KooW... acions.test", + communityAddress: "12D3KooW... acions.test", commentCid: "Qm... acions.test", vote: 1, onChallenge, @@ -1635,7 +1635,7 @@ describe("actions", () => { const onError = vi.fn(); const publishVoteOptions = { - subplebbitAddress: "12D3KooW... acions.test", + communityAddress: "12D3KooW... acions.test", commentCid: "Qm... acions.test", vote: 1, onError, @@ -1680,7 +1680,7 @@ describe("actions", () => { const testRendered = renderHook(() => usePublishVote({ - subplebbitAddress: "12D3KooW... acions.test", + communityAddress: "12D3KooW... acions.test", commentCid: "Qm... acions.test", vote: 1, }), @@ -1701,7 +1701,7 @@ describe("actions", () => { test("publishVote with no onChallenge/onChallengeVerification completes successfully", async () => { const publishVoteOptions = { - subplebbitAddress: "12D3KooW... acions.test", + communityAddress: "12D3KooW... acions.test", commentCid: "Qm... acions.test", vote: 1, }; diff --git a/src/hooks/actions/actions.ts b/src/hooks/actions/actions.ts index 8126f74c..21fc53e3 100644 --- a/src/hooks/actions/actions.ts +++ b/src/hooks/actions/actions.ts @@ -48,24 +48,24 @@ import type { UsePublishCommentResult, UseBlockOptions, UseBlockResult, - UseCreateSubplebbitOptions, - UseCreateSubplebbitResult, + UseCreateCommunityOptions, + UseCreateCommunityResult, UsePublishVoteOptions, UsePublishVoteResult, UsePublishCommentEditOptions, UsePublishCommentEditResult, UsePublishCommentModerationOptions, UsePublishCommentModerationResult, - UsePublishSubplebbitEditOptions, - UsePublishSubplebbitEditResult, + UsePublishCommunityEditOptions, + UsePublishCommunityEditResult, Challenge, ChallengeVerification, Comment, CommentEdit, CommentModeration, - SubplebbitEdit, + CommunityEdit, Vote, - Subplebbit, + Community, } from "../../types"; type PublishChallengeAnswers = (challengeAnswers: string[]) => Promise; @@ -80,22 +80,22 @@ export function useSubscribe(options?: UseSubscribeOptions): UseSubscribeResult !options || typeof options === "object", `useSubscribe options argument '${options}' not an object`, ); - const { subplebbitAddress, accountName, onError } = options || {}; + const { communityAddress, accountName, onError } = options || {}; const account = useAccount({ accountName }); const accountsActions = useAccountsStore((state) => state.accountsActions); const [errors, setErrors] = useState([]); let state = "initializing"; let subscribed: boolean | undefined; - // before the account and subplebbitAddress is defined, nothing can happen - if (account && subplebbitAddress) { + // before the account and communityAddress is defined, nothing can happen + if (account && communityAddress) { state = "ready"; - subscribed = Boolean(account.subscriptions?.includes(subplebbitAddress)); + subscribed = Boolean(account.subscriptions?.includes(communityAddress)); } const subscribe = async () => { try { - await accountsActions.subscribe(subplebbitAddress, accountName); + await accountsActions.subscribe(communityAddress, accountName); } catch (e: any) { setErrors((errors) => [...errors, e]); onError?.(e); @@ -104,7 +104,7 @@ export function useSubscribe(options?: UseSubscribeOptions): UseSubscribeResult const unsubscribe = async () => { try { - await accountsActions.unsubscribe(subplebbitAddress, accountName); + await accountsActions.unsubscribe(communityAddress, accountName); } catch (e: any) { setErrors((errors) => [...errors, e]); onError?.(e); @@ -120,7 +120,7 @@ export function useSubscribe(options?: UseSubscribeOptions): UseSubscribeResult error: errors[errors.length - 1], errors, }), - [state, subscribed, errors, subplebbitAddress, accountName], + [state, subscribed, errors, communityAddress, accountName], ); } @@ -574,14 +574,14 @@ export function usePublishCommentModeration( ); } -export function usePublishSubplebbitEdit( - options?: UsePublishSubplebbitEditOptions, -): UsePublishSubplebbitEditResult { +export function usePublishCommunityEdit( + options?: UsePublishCommunityEditOptions, +): UsePublishCommunityEditResult { assert( !options || typeof options === "object", - `usePublishSubplebbitEdit options argument '${options}' not an object`, + `usePublishCommunityEdit options argument '${options}' not an object`, ); - const { accountName, subplebbitAddress, ...publishSubplebbitEditOptions } = options || {}; + const { accountName, communityAddress, ...publishCommunityEditOptions } = options || {}; const accountsActions = useAccountsStore((state) => state.accountsActions); const accountId = useAccountId(accountName); const [errors, setErrors] = useState([]); @@ -592,49 +592,49 @@ export function usePublishSubplebbitEdit( let initialState = "initializing"; // before the accountId and options is defined, nothing can happen - if (accountId && subplebbitAddress) { + if (accountId && communityAddress) { initialState = "ready"; } // define onError if not defined - const originalOnError = publishSubplebbitEditOptions.onError; + const originalOnError = publishCommunityEditOptions.onError; const onError = async (error: Error) => { setErrors((errors) => [...errors, error]); originalOnError?.(error); }; - publishSubplebbitEditOptions.onError = onError; + publishCommunityEditOptions.onError = onError; // define onChallenge if not defined - const originalOnChallenge = publishSubplebbitEditOptions.onChallenge; - const onChallenge = async (challenge: Challenge, subplebbitEdit: SubplebbitEdit) => { + const originalOnChallenge = publishCommunityEditOptions.onChallenge; + const onChallenge = async (challenge: Challenge, communityEdit: CommunityEdit) => { // cannot set a function directly with setState - setPublishChallengeAnswers(() => subplebbitEdit?.publishChallengeAnswers.bind(subplebbitEdit)); + setPublishChallengeAnswers(() => communityEdit?.publishChallengeAnswers.bind(communityEdit)); setChallenge(challenge); - originalOnChallenge?.(challenge, subplebbitEdit); + originalOnChallenge?.(challenge, communityEdit); }; - publishSubplebbitEditOptions.onChallenge = onChallenge; + publishCommunityEditOptions.onChallenge = onChallenge; // define onChallengeVerification if not defined - const originalOnChallengeVerification = publishSubplebbitEditOptions.onChallengeVerification; + const originalOnChallengeVerification = publishCommunityEditOptions.onChallengeVerification; const onChallengeVerification = async ( challengeVerification: ChallengeVerification, - subplebbitEdit: SubplebbitEdit, + communityEdit: CommunityEdit, ) => { setChallengeVerification(challengeVerification); - originalOnChallengeVerification?.(challengeVerification, subplebbitEdit); + originalOnChallengeVerification?.(challengeVerification, communityEdit); }; - publishSubplebbitEditOptions.onChallengeVerification = onChallengeVerification; + publishCommunityEditOptions.onChallengeVerification = onChallengeVerification; // change state on publishing state change - publishSubplebbitEditOptions.onPublishingStateChange = (publishingState: string) => { + publishCommunityEditOptions.onPublishingStateChange = (publishingState: string) => { setPublishingState(publishingState); }; - const publishSubplebbitEdit = async () => { + const publishCommunityEdit = async () => { try { - await accountsActions.publishSubplebbitEdit( - subplebbitAddress, - publishSubplebbitEditOptions, + await accountsActions.publishCommunityEdit( + communityAddress, + publishCommunityEditOptions, accountName, ); } catch (e: any) { @@ -647,7 +647,7 @@ export function usePublishSubplebbitEdit( () => ({ challenge, challengeVerification, - publishSubplebbitEdit, + publishCommunityEdit, publishChallengeAnswers: publishChallengeAnswers || publishChallengeAnswersNotReady, state: publishingState || initialState, error: errors[errors.length - 1], @@ -666,19 +666,17 @@ export function usePublishSubplebbitEdit( ); } -export function useCreateSubplebbit( - options?: UseCreateSubplebbitOptions, -): UseCreateSubplebbitResult { +export function useCreateCommunity(options?: UseCreateCommunityOptions): UseCreateCommunityResult { assert( !options || typeof options === "object", - `useCreateSubplebbit options argument '${options}' not an object`, + `useCreateCommunity options argument '${options}' not an object`, ); - const { accountName, onError, ...createSubplebbitOptions } = options || {}; + const { accountName, onError, ...createCommunityOptions } = options || {}; const accountId = useAccountId(accountName); const accountsActions = useAccountsStore((state) => state.accountsActions); const [errors, setErrors] = useState([]); const [state, setState] = useState(); - const [createdSubplebbit, setCreatedSubplebbit] = useState(); + const [createdCommunity, setCreatedCommunity] = useState(); let initialState = "initializing"; // before the accountId and options is defined, nothing can happen @@ -686,14 +684,14 @@ export function useCreateSubplebbit( initialState = "ready"; } - const createSubplebbit = async () => { + const createCommunity = async () => { try { setState("creating"); - const createdSubplebbit = await accountsActions.createSubplebbit( - createSubplebbitOptions, + const createdCommunity = await accountsActions.createCommunity( + createCommunityOptions, accountName, ); - setCreatedSubplebbit(createdSubplebbit); + setCreatedCommunity(createdCommunity); setState("succeeded"); } catch (e: any) { setErrors((errors) => [...errors, e]); @@ -704,12 +702,12 @@ export function useCreateSubplebbit( return useMemo( () => ({ - createdSubplebbit, - createSubplebbit, + createdCommunity, + createCommunity, state: state || initialState, error: errors[errors.length - 1], errors, }), - [state, errors, createdSubplebbit, options, accountName], + [state, errors, createdCommunity, options, accountName], ); } diff --git a/src/hooks/actions/index.ts b/src/hooks/actions/index.ts index 9fef387d..8ff5a89a 100644 --- a/src/hooks/actions/index.ts +++ b/src/hooks/actions/index.ts @@ -1 +1 @@ -export * from './actions' +export * from "./actions"; diff --git a/src/hooks/authors/author-avatars.ts b/src/hooks/authors/author-avatars.ts index a2a8abce..ef642679 100644 --- a/src/hooks/authors/author-avatars.ts +++ b/src/hooks/authors/author-avatars.ts @@ -182,7 +182,7 @@ setAuthorAvatarsWhitelistedTokenAddresses(defaultWhitelistedTokenAddresses); // export function useAuthorAvatarIsWhitelisted(nft?: Nft) { // TODO: make a list that a dao can vote it, get the list from plebbit.getDefaults() - // TODO: make subplebbit owners able to whitelist their own nfts in their subplebbits + // TODO: make community owners able to whitelist their own nfts in their communities // TODO: make each user able to whitelist/blacklist any nft they want for their own client // TODO: make hook to list which default nfts are whitelisted to display to the user diff --git a/src/hooks/authors/authors.test.ts b/src/hooks/authors/authors.test.ts index 9b1d496c..4753c4ff 100644 --- a/src/hooks/authors/authors.test.ts +++ b/src/hooks/authors/authors.test.ts @@ -57,7 +57,7 @@ const comment = { signature: "+ixn9hY2nBlzRwLEGGE5+JgbnuRAAZQxkv4Kz9wM6as3sA0tA8PuOyCHe29rcNl9gOzLtCmYCARQOqHpmA05CQ", signedPropertyNames: [ - "subplebbitAddress", + "communityAddress", "author", "timestamp", "content", @@ -67,7 +67,7 @@ const comment = { ], type: "ed25519", }, - subplebbitAddress: "12D3KooWG3XbzoVyAE6Y9vHZKF64Yuuu4TjdgQKedk14iYmTEPWu", + communityAddress: "12D3KooWG3XbzoVyAE6Y9vHZKF64Yuuu4TjdgQKedk14iYmTEPWu", timestamp: 1686133292, title: "test", }; @@ -499,7 +499,7 @@ describe("authors", () => { author: { address: "author.eth", previousCommentCid: getAuthorPreviousCommentCid(), - subplebbit: { + community: { lastCommentCid: "last comment cid", }, }, @@ -828,7 +828,7 @@ describe("authors", () => { const waitFor = testUtils.createWaitFor(rendered, { timeout }); expect(rendered.result.current.resolvedAddress).toBe(undefined); - rendered.rerender({ address: "subplebbit.eth" }); + rendered.rerender({ address: "community.eth" }); await waitFor(() => typeof rendered.result.current.resolvedAddress === "string"); expect(rendered.result.current.resolvedAddress).toBe("resolved author address"); }); @@ -909,7 +909,7 @@ describe("authors", () => { useResolvedAuthorAddress({ author: opts, cache: false }), ); const waitFor = testUtils.createWaitFor(rendered, { timeout: 22000 }); - rendered.rerender({ address: "subplebbit.eth" }); + rendered.rerender({ address: "community.eth" }); await waitFor(() => typeof rendered.result.current.resolvedAddress === "string"); expect(rendered.result.current.resolvedAddress).toBe("resolved author address"); }); @@ -917,7 +917,7 @@ describe("authors", () => { test("useResolvedAuthorAddress resets when author/account cleared", { timeout }, async () => { const rendered = renderHook((opts) => useResolvedAuthorAddress({ author: opts })); const waitFor = testUtils.createWaitFor(rendered, { timeout: 20000 }); - rendered.rerender({ address: "subplebbit.eth" } as any); + rendered.rerender({ address: "community.eth" } as any); await waitFor(() => typeof rendered.result.current.resolvedAddress === "string"); rendered.rerender(undefined); await waitFor(() => rendered.result.current.resolvedAddress === undefined); diff --git a/src/hooks/authors/authors.ts b/src/hooks/authors/authors.ts index e50ae25b..01e16975 100644 --- a/src/hooks/authors/authors.ts +++ b/src/hooks/authors/authors.ts @@ -28,7 +28,7 @@ import { useComment, useComments } from "../comments"; import { useAuthorCommentsName, usePlebbitAddress } from "./utils"; import useAuthorsCommentsStore from "../../stores/authors-comments"; import PlebbitJs from "../../lib/plebbit-js"; -import { normalizeEthAliasDomain } from "../../lib/subplebbit-address"; +import { normalizeEthAliasDomain } from "../../lib/community-address"; import QuickLRU from "quick-lru"; export { setAuthorAvatarsWhitelistedTokenAddresses } from "./author-avatars"; diff --git a/src/hooks/authors/index.ts b/src/hooks/authors/index.ts index bf9c1e65..f9adb07f 100644 --- a/src/hooks/authors/index.ts +++ b/src/hooks/authors/index.ts @@ -1 +1 @@ -export * from './authors' +export * from "./authors"; diff --git a/src/hooks/authors/utils.ts b/src/hooks/authors/utils.ts index a6963591..4c0fc97e 100644 --- a/src/hooks/authors/utils.ts +++ b/src/hooks/authors/utils.ts @@ -1,28 +1,40 @@ -import {useEffect, useState, useMemo} from 'react' -import {Comment, CommentsFilter} from '../../types' -import {useComments} from '../comments' -import utils from '../../lib/utils' -import PeerId from 'peer-id' -import {fromString as uint8ArrayFromString} from 'uint8arrays/from-string' -import {toString as uint8ArrayToString} from 'uint8arrays/to-string' -import {create as createMultihash} from 'multiformats/hashes/digest' -import assert from 'assert' +import { useEffect, useState, useMemo } from "react"; +import { Comment, CommentsFilter } from "../../types"; +import { useComments } from "../comments"; +import utils from "../../lib/utils"; +import PeerId from "peer-id"; +import { fromString as uint8ArrayFromString } from "uint8arrays/from-string"; +import { toString as uint8ArrayToString } from "uint8arrays/to-string"; +import { create as createMultihash } from "multiformats/hashes/digest"; +import assert from "assert"; -export const useAuthorCommentsName = (accountId?: string, authorAddress?: string, filter?: CommentsFilter | undefined) => { - return useMemo(() => accountId + '-' + authorAddress + '-' + filter?.key, [accountId, authorAddress, filter?.key]) -} +export const useAuthorCommentsName = ( + accountId?: string, + authorAddress?: string, + filter?: CommentsFilter | undefined, +) => { + return useMemo( + () => accountId + "-" + authorAddress + "-" + filter?.key, + [accountId, authorAddress, filter?.key], + ); +}; -const protobufPublicKeyPrefix = new Uint8Array([8, 1, 18, 32]) -const multihashIdentityCode = 0 +const protobufPublicKeyPrefix = new Uint8Array([8, 1, 18, 32]); +const multihashIdentityCode = 0; const getPlebbitAddressFromPublicKey = (publicKeyBase64: string) => { - const publicKeyBuffer = uint8ArrayFromString(publicKeyBase64, 'base64') - const publicKeyBufferWithPrefix = new Uint8Array(protobufPublicKeyPrefix.length + publicKeyBuffer.length) - publicKeyBufferWithPrefix.set(protobufPublicKeyPrefix, 0) - publicKeyBufferWithPrefix.set(publicKeyBuffer, protobufPublicKeyPrefix.length) - const multihash = createMultihash(multihashIdentityCode, publicKeyBufferWithPrefix).bytes - return uint8ArrayToString(multihash, 'base58btc') -} + const publicKeyBuffer = uint8ArrayFromString(publicKeyBase64, "base64"); + const publicKeyBufferWithPrefix = new Uint8Array( + protobufPublicKeyPrefix.length + publicKeyBuffer.length, + ); + publicKeyBufferWithPrefix.set(protobufPublicKeyPrefix, 0); + publicKeyBufferWithPrefix.set(publicKeyBuffer, protobufPublicKeyPrefix.length); + const multihash = createMultihash(multihashIdentityCode, publicKeyBufferWithPrefix).bytes; + return uint8ArrayToString(multihash, "base58btc"); +}; export const usePlebbitAddress = (publicKeyBase64?: string) => { - return useMemo(() => (publicKeyBase64 ? getPlebbitAddressFromPublicKey(publicKeyBase64) : undefined), [publicKeyBase64]) -} + return useMemo( + () => (publicKeyBase64 ? getPlebbitAddressFromPublicKey(publicKeyBase64) : undefined), + [publicKeyBase64], + ); +}; diff --git a/src/hooks/comments.test.ts b/src/hooks/comments.test.ts index cfeede50..58d7c576 100644 --- a/src/hooks/comments.test.ts +++ b/src/hooks/comments.test.ts @@ -4,7 +4,7 @@ import { useComment, useComments, useValidateComment, setPlebbitJs } from ".."; import { getCommentFreshness, preferFresher } from "./comments"; import * as accountsHooks from "./accounts"; import commentsStore from "../stores/comments"; -import subplebbitsPagesStore from "../stores/subplebbits-pages"; +import communitiesPagesStore from "../stores/communities-pages"; import accountsStore from "../stores/accounts"; import PlebbitJsMock, { Plebbit, Comment, Pages } from "../lib/plebbit-js/plebbit-js-mock"; import repliesPagesStore from "../stores/replies-pages"; @@ -260,17 +260,17 @@ describe("comments", () => { vi.mocked(accountsHooks.useAccount).mockRestore(); }); - test("useComment when subplebbitsPagesComment and repliesPagesComment are absent (branches 88, 94)", async () => { + test("useComment when communitiesPagesComment and repliesPagesComment are absent (branches 88, 94)", async () => { const rendered = renderHook((commentCid) => useComment({ commentCid })); const waitFor = testUtils.createWaitFor(rendered); rendered.rerender("comment cid 1"); await waitFor(() => typeof rendered.result.current.cid === "string"); expect(rendered.result.current.cid).toBe("comment cid 1"); - // No subplebbitsPagesStore or repliesPagesStore entries - uses commentFromStore only + // No communitiesPagesStore or repliesPagesStore entries - uses commentFromStore only expect(commentsStore.getState().comments["comment cid 1"]).toBeDefined(); }); - test("get comment from subplebbit pages", async () => { + test("get comment from community pages", async () => { // on first render, the account is undefined because it's not yet loaded from database const rendered = renderHook((commentCid) => useComment({ commentCid })); const waitFor = testUtils.createWaitFor(rendered); @@ -281,24 +281,24 @@ describe("comments", () => { expect(rendered.result.current.cid).toBe("comment cid 1"); expect(rendered.result.current.replyCount).toBe(undefined); - // mock getting a subplebbit page with an updated comment - const subplebbitsPagesComment = { + // mock getting a community page with an updated comment + const communitiesPagesComment = { ...rendered.result.current, replyCount: 100, updatedAt: Math.round(Date.now() / 1000) + 60, // 1 minute in the future to make sure it's more recent }; act(() => { - subplebbitsPagesStore.setState((_state: any) => ({ - comments: { "comment cid 1": subplebbitsPagesComment }, + communitiesPagesStore.setState((_state: any) => ({ + comments: { "comment cid 1": communitiesPagesComment }, })); }); - // using the subplebbit page comment + // using the community page comment await waitFor(() => rendered.result.current.replyCount === 100); expect(rendered.result.current.replyCount).toBe(100); }); - test("get comments from subplebbit pages", async () => { + test("get comments from community pages", async () => { const rendered = renderHook((commentCids) => useComments({ commentCids })); const waitFor = testUtils.createWaitFor(rendered); expect(rendered.result.current.comments).toEqual([]); @@ -316,19 +316,19 @@ describe("comments", () => { expect(rendered.result.current.comments[2].cid).toBe("comment cid 3"); expect(rendered.result.current.comments[1].replyCount).toBe(undefined); - // mock getting a subplebbit page with an updated comment - const subplebbitsPagesComment = { + // mock getting a community page with an updated comment + const communitiesPagesComment = { ...rendered.result.current.comments[1], replyCount: 100, updatedAt: Math.round(Date.now() / 1000) + 60, }; act(() => { - subplebbitsPagesStore.setState((_state: any) => ({ - comments: { "comment cid 2": subplebbitsPagesComment }, + communitiesPagesStore.setState((_state: any) => ({ + comments: { "comment cid 2": communitiesPagesComment }, })); }); - // using the subplebbit page comment + // using the community page comment await waitFor(() => rendered.result.current.comments[1].replyCount === 100); expect(rendered.result.current.comments[1].replyCount).toBe(100); }); @@ -343,10 +343,10 @@ describe("comments", () => { cid: pendingCommentCid, timestamp: pendingTimestamp, replyCount: 42, - subplebbitAddress: "subplebbit address 1", + communityAddress: "community address 1", }; act(() => { - subplebbitsPagesStore.setState((_state: any) => ({ + communitiesPagesStore.setState((_state: any) => ({ comments: { [pendingCommentCid]: pendingComment }, })); }); @@ -366,7 +366,7 @@ describe("comments", () => { cid, timestamp: 100, content: "from account", - subplebbitAddress: "sub", + communityAddress: "sub", }; act(() => { accountsStore.setState((s: any) => ({ @@ -395,7 +395,7 @@ describe("comments", () => { const freshComment = { cid: freshCid, timestamp: freshTimestamp, - subplebbitAddress: "sub", + communityAddress: "sub", }; act(() => { commentsStore.setState((s: any) => ({ @@ -433,7 +433,7 @@ describe("comments", () => { updatedAt: undefined, }; act(() => { - subplebbitsPagesStore.setState((_state: any) => ({ + communitiesPagesStore.setState((_state: any) => ({ comments: { "comment cid 1": pageStoreComment }, })); }); @@ -607,8 +607,8 @@ describe("comments", () => { [cid2]: { cid: cid2, timestamp: 2 } as Comment, }, })); - // Only cid1 has subplebbitsPages data; cid2 has no candidate - subplebbitsPagesStore.setState((s) => ({ + // Only cid1 has communitiesPages data; cid2 has no candidate + communitiesPagesStore.setState((s) => ({ comments: { ...s.comments, [cid1]: { cid: cid1, timestamp: 10 } as Comment, @@ -622,7 +622,7 @@ describe("comments", () => { expect(rendered.result.current.comments[1].timestamp).toBe(2); }); - test("preferFresher merges subplebbitsPagesComments when fresher", async () => { + test("preferFresher merges communitiesPagesComments when fresher", async () => { setPlebbitJs(PlebbitJsMock); await testUtils.resetDatabasesAndStores(); @@ -635,7 +635,7 @@ describe("comments", () => { [cid2]: { cid: cid2, timestamp: 2 } as Comment, }, })); - subplebbitsPagesStore.setState((s) => ({ + communitiesPagesStore.setState((s) => ({ comments: { ...s.comments, [cid1]: { cid: cid1, timestamp: 10 } as Comment, @@ -667,7 +667,7 @@ describe("comments", () => { expect(rendered.result.current.state).toBe("initializing"); // the first render is always true, to avoid rerenders when true - const comment = { cid: "comment cid 1", subplebbitAddress: "subplebbit address 1" }; + const comment = { cid: "comment cid 1", communityAddress: "community address 1" }; rendered.rerender({ comment }); expect(rendered.result.current.valid).toBe(true); expect(rendered.result.current.state).toBe("initializing"); // valid true but still initializing @@ -730,7 +730,7 @@ describe("comments", () => { expect(rendered.result.current.state).toBe("initializing"); // the first render is always true, to avoid rerenders when true - const comment = { cid: "comment cid 1", subplebbitAddress: "subplebbit address 1" }; + const comment = { cid: "comment cid 1", communityAddress: "community address 1" }; rendered.rerender({ comment }); expect(rendered.result.current.valid).toBe(true); expect(rendered.result.current.state).toBe("initializing"); diff --git a/src/hooks/comments.ts b/src/hooks/comments.ts index ebd8b327..ba92dfd2 100644 --- a/src/hooks/comments.ts +++ b/src/hooks/comments.ts @@ -20,7 +20,7 @@ import { addCommentModeration, addCommentModerationToComments, } from "../lib/utils/comment-moderation"; -import useSubplebbitsPagesStore from "../stores/subplebbits-pages"; +import useCommunitiesPagesStore from "../stores/communities-pages"; import useRepliesPagesStore from "../stores/replies-pages"; import shallow from "zustand/shallow"; @@ -52,7 +52,7 @@ export function useComment(options?: UseCommentOptions): UseCommentResult { const account = useAccount({ accountName }); const commentFromStore = useCommentsStore((state: any) => state.comments[commentCid || ""]); const addCommentToStore = useCommentsStore((state: any) => state.addCommentToStore); - const subplebbitsPagesComment = useSubplebbitsPagesStore( + const communitiesPagesComment = useCommunitiesPagesStore( (state: any) => state.comments[commentCid || ""], ); const repliesPagesComment = useRepliesPagesStore( @@ -86,8 +86,8 @@ export function useComment(options?: UseCommentOptions): UseCommentResult { let comment = commentFromStore; - if (commentCid && subplebbitsPagesComment) { - comment = preferFresher(comment, subplebbitsPagesComment); + if (commentCid && communitiesPagesComment) { + comment = preferFresher(comment, communitiesPagesComment); } if (commentCid && repliesPagesComment) { comment = preferFresher(comment, repliesPagesComment); @@ -132,7 +132,7 @@ export function useComment(options?: UseCommentOptions): UseCommentResult { replyCount, state, commentFromStore, - subplebbitsPagesComment, + communitiesPagesComment, repliesPagesComment, accountComment, commentsStore: useCommentsStore.getState().comments, @@ -169,7 +169,7 @@ export function useComments(options?: UseCommentsOptions): UseCommentsResult { (state: any) => commentCids.map((commentCid) => state.comments[commentCid || ""]), shallow, ); - const subplebbitsPagesComments: (Comment | undefined)[] = useSubplebbitsPagesStore( + const communitiesPagesComments: (Comment | undefined)[] = useCommunitiesPagesStore( (state: any) => commentCids.map((commentCid) => state.comments[commentCid || ""]), shallow, ); @@ -201,15 +201,15 @@ export function useComments(options?: UseCommentsOptions): UseCommentsResult { }); } - // if comment from subplebbit pages exists and is fresher (or current missing), use it instead + // if comment from community pages exists and is fresher (or current missing), use it instead const comments = useMemo(() => { const result = [...commentsStoreComments]; for (const i in result) { - const candidate = subplebbitsPagesComments[i]; + const candidate = communitiesPagesComments[i]; if (candidate) result[i] = preferFresher(result[i], candidate); } return result; - }, [commentsStoreComments, subplebbitsPagesComments]); + }, [commentsStoreComments, communitiesPagesComments]); const normalizedComments = useMemo(() => addCommentModerationToComments(comments), [comments]); // succeed if no comments are undefined @@ -242,10 +242,10 @@ export function useValidateComment(options?: UseValidateCommentOptions): UseVali setValidated(undefined); return; } - // don't automatically block subplebbit because what subplebbit it comes from - // a malicious subplebbit could try to block other subplebbits, etc - const blockSubplebbit = false; - commentIsValid(comment, { validateReplies, blockSubplebbit }, account.plebbit).then( + // don't automatically block community because what community it comes from + // a malicious community could try to block other communities, etc + const blockCommunity = false; + commentIsValid(comment, { validateReplies, blockCommunity }, account.plebbit).then( (validated) => setValidated(validated), ); }, [comment, validateReplies, account?.plebbit]); diff --git a/src/hooks/subplebbits.test.ts b/src/hooks/communities.test.ts similarity index 53% rename from src/hooks/subplebbits.test.ts rename to src/hooks/communities.test.ts index 73917b34..5d2e57ff 100644 --- a/src/hooks/subplebbits.test.ts +++ b/src/hooks/communities.test.ts @@ -1,20 +1,20 @@ import { act } from "@testing-library/react"; import testUtils, { renderHook } from "../lib/test-utils"; import { - useSubplebbit, - useSubplebbitStats, - useSubplebbits, + useCommunity, + useCommunityStats, + useCommunities, setPlebbitJs, - useResolvedSubplebbitAddress, + useResolvedCommunityAddress, } from ".."; import * as accountsHooks from "./accounts"; -import subplebbitStore from "../stores/subplebbits"; -import subplebbitsPagesStore from "../stores/subplebbits-pages"; -import { useListSubplebbits, resolveSubplebbitAddress } from "./subplebbits"; -import PlebbitJsMock, { Plebbit, Subplebbit } from "../lib/plebbit-js/plebbit-js-mock"; +import communityStore from "../stores/communities"; +import communitiesPagesStore from "../stores/communities-pages"; +import { useListCommunities, resolveCommunityAddress } from "./communities"; +import PlebbitJsMock, { Plebbit, Community } from "../lib/plebbit-js/plebbit-js-mock"; import * as chain from "../lib/chain"; -describe("subplebbits", () => { +describe("communities", () => { beforeAll(async () => { // set plebbit-js mock and reset dbs setPlebbitJs(PlebbitJsMock); @@ -29,179 +29,179 @@ describe("subplebbits", () => { await testUtils.resetDatabasesAndStores(); }); - describe("no subplebbits in database", () => { + describe("no communities in database", () => { afterEach(async () => { await testUtils.resetDatabasesAndStores(); }); - test("get subplebbits one at a time", async () => { - const rendered = renderHook((subplebbitAddress) => - useSubplebbit({ subplebbitAddress }), + test("get communities one at a time", async () => { + const rendered = renderHook((communityAddress) => + useCommunity({ communityAddress }), ); const waitFor = testUtils.createWaitFor(rendered); expect(rendered.result.current.address).toBe(undefined); - rendered.rerender("subplebbit address 1"); + rendered.rerender("community address 1"); await waitFor(() => typeof rendered.result.current.title === "string"); expect(typeof rendered.result.current.fetchedAt).toBe("number"); - expect(rendered.result.current.address).toBe("subplebbit address 1"); - expect(rendered.result.current.title).toBe("subplebbit address 1 title"); - // wait for subplebbit.on('update') to fetch the updated description + expect(rendered.result.current.address).toBe("community address 1"); + expect(rendered.result.current.title).toBe("community address 1 title"); + // wait for community.on('update') to fetch the updated description await waitFor(() => typeof rendered.result.current.description === "string"); - expect(rendered.result.current.description).toBe("subplebbit address 1 description updated"); + expect(rendered.result.current.description).toBe("community address 1 description updated"); - rendered.rerender("subplebbit address 2"); + rendered.rerender("community address 2"); await waitFor(() => typeof rendered.result.current.title === "string"); - expect(rendered.result.current.address).toBe("subplebbit address 2"); - expect(rendered.result.current.title).toBe("subplebbit address 2 title"); - // wait for subplebbit.on('update') to fetch the updated description + expect(rendered.result.current.address).toBe("community address 2"); + expect(rendered.result.current.title).toBe("community address 2 title"); + // wait for community.on('update') to fetch the updated description await waitFor(() => typeof rendered.result.current.description === "string"); - expect(rendered.result.current.description).toBe("subplebbit address 2 description updated"); + expect(rendered.result.current.description).toBe("community address 2 description updated"); // get sub 1 again, no need to wait for any updates - rendered.rerender("subplebbit address 1"); - expect(rendered.result.current.address).toBe("subplebbit address 1"); - expect(rendered.result.current.description).toBe("subplebbit address 1 description updated"); + rendered.rerender("community address 1"); + expect(rendered.result.current.address).toBe("community address 1"); + expect(rendered.result.current.description).toBe("community address 1 description updated"); - // make sure subplebbits are still in database - const simulateUpdateEvent = Subplebbit.prototype.simulateUpdateEvent; + // make sure communities are still in database + const simulateUpdateEvent = Community.prototype.simulateUpdateEvent; // don't simulate 'update' event during this test to see if the updates were saved to database - let throwOnSubplebbitUpdateEvent = false; - Subplebbit.prototype.simulateUpdateEvent = () => { - if (throwOnSubplebbitUpdateEvent) { + let throwOnCommunityUpdateEvent = false; + Community.prototype.simulateUpdateEvent = () => { + if (throwOnCommunityUpdateEvent) { throw Error( - "no subplebbit update events should be emitted when subplebbit already in store", + "no community update events should be emitted when community already in store", ); } }; - // subplebbitsPagesStore has preloaded subplebbit comments + // communitiesPagesStore has preloaded community comments expect(rendered.result.current.posts.pages.hot.comments.length).toBeGreaterThan(0); - const subplebbitsPagesStoreComments = subplebbitsPagesStore.getState().comments; + const communitiesPagesStoreComments = communitiesPagesStore.getState().comments; for (const comment of rendered.result.current.posts.pages.hot.comments) { expect(typeof comment.cid).toBe("string"); - expect(subplebbitsPagesStoreComments[comment.cid].cid).toBe(comment.cid); + expect(communitiesPagesStoreComments[comment.cid].cid).toBe(comment.cid); } // reset stores to force using the db - expect(subplebbitStore.getState().subplebbits).not.toEqual({}); + expect(communityStore.getState().communities).not.toEqual({}); await testUtils.resetStores(); - expect(subplebbitStore.getState().subplebbits).toEqual({}); - expect(subplebbitsPagesStore.getState().comments).toEqual({}); + expect(communityStore.getState().communities).toEqual({}); + expect(communitiesPagesStore.getState().comments).toEqual({}); // on first render, the account is undefined because it's not yet loaded from database - const rendered2 = renderHook((subplebbitAddress) => - useSubplebbit({ subplebbitAddress }), + const rendered2 = renderHook((communityAddress) => + useCommunity({ communityAddress }), ); expect(rendered2.result.current.address).toBe(undefined); - rendered2.rerender("subplebbit address 1"); + rendered2.rerender("community address 1"); // wait to get account loaded - await waitFor(() => rendered2.result.current.address === "subplebbit address 1"); + await waitFor(() => rendered2.result.current.address === "community address 1"); expect(typeof rendered2.result.current.fetchedAt).toBe("number"); - expect(rendered2.result.current.address).toBe("subplebbit address 1"); - expect(rendered2.result.current.title).toBe("subplebbit address 1 title"); - expect(rendered2.result.current.description).toBe("subplebbit address 1 description updated"); - - rendered2.rerender("subplebbit address 2"); - // wait for addSubplebbitToStore action - await waitFor(() => rendered2.result.current.address === "subplebbit address 2"); - - expect(rendered2.result.current.address).toBe("subplebbit address 2"); - expect(rendered2.result.current.title).toBe("subplebbit address 2 title"); - expect(rendered2.result.current.description).toBe("subplebbit address 2 description updated"); - - // get subplebbit 1 again from store, should not trigger any subplebbit updates - throwOnSubplebbitUpdateEvent = true; - rendered2.rerender("subplebbit address 1"); - expect(rendered2.result.current.address).toBe("subplebbit address 1"); - expect(rendered2.result.current.title).toBe("subplebbit address 1 title"); - expect(rendered2.result.current.description).toBe("subplebbit address 1 description updated"); - - // subplebbitsPagesStore has preloaded subplebbit comments + expect(rendered2.result.current.address).toBe("community address 1"); + expect(rendered2.result.current.title).toBe("community address 1 title"); + expect(rendered2.result.current.description).toBe("community address 1 description updated"); + + rendered2.rerender("community address 2"); + // wait for addCommunityToStore action + await waitFor(() => rendered2.result.current.address === "community address 2"); + + expect(rendered2.result.current.address).toBe("community address 2"); + expect(rendered2.result.current.title).toBe("community address 2 title"); + expect(rendered2.result.current.description).toBe("community address 2 description updated"); + + // get community 1 again from store, should not trigger any community updates + throwOnCommunityUpdateEvent = true; + rendered2.rerender("community address 1"); + expect(rendered2.result.current.address).toBe("community address 1"); + expect(rendered2.result.current.title).toBe("community address 1 title"); + expect(rendered2.result.current.description).toBe("community address 1 description updated"); + + // communitiesPagesStore has preloaded community comments expect(rendered2.result.current.posts.pages.hot.comments.length).toBeGreaterThan(0); - const subplebbitsPagesStoreComments2 = subplebbitsPagesStore.getState().comments; + const communitiesPagesStoreComments2 = communitiesPagesStore.getState().comments; for (const comment of rendered2.result.current.posts.pages.hot.comments) { expect(typeof comment.cid).toBe("string"); - expect(subplebbitsPagesStoreComments2[comment.cid].cid).toBe(comment.cid); + expect(communitiesPagesStoreComments2[comment.cid].cid).toBe(comment.cid); } // restore mock - Subplebbit.prototype.simulateUpdateEvent = simulateUpdateEvent; + Community.prototype.simulateUpdateEvent = simulateUpdateEvent; }); test(`onlyIfCached: true doesn't add to store`, async () => { let rendered; - rendered = renderHook((options: any) => useSubplebbit(options)); + rendered = renderHook((options: any) => useCommunity(options)); testUtils.createWaitFor(rendered); - rendered.rerender({ subplebbitAddress: "subplebbit address 1", onlyIfCached: true }); + rendered.rerender({ communityAddress: "community address 1", onlyIfCached: true }); // TODO: find better way to wait await new Promise((r) => setTimeout(r, 20)); - // subplebbit not added to store - expect(subplebbitStore.getState().subplebbits).toEqual({}); + // community not added to store + expect(communityStore.getState().communities).toEqual({}); - rendered = renderHook((options: any) => useSubplebbits(options)); + rendered = renderHook((options: any) => useCommunities(options)); testUtils.createWaitFor(rendered); rendered.rerender({ - subplebbitAddresses: ["subplebbit address 1", "subplebbit address 2"], + communityAddresses: ["community address 1", "community address 2"], onlyIfCached: true, }); - expect(rendered.result.current.subplebbits.length).toBe(2); + expect(rendered.result.current.communities.length).toBe(2); // TODO: find better way to wait await new Promise((r) => setTimeout(r, 20)); - // subplebbit not added to store - expect(subplebbitStore.getState().subplebbits).toEqual({}); + // community not added to store + expect(communityStore.getState().communities).toEqual({}); }); - test("get multiple subplebbits at once", async () => { - const rendered = renderHook((subplebbitAddresses) => - useSubplebbits({ subplebbitAddresses }), + test("get multiple communities at once", async () => { + const rendered = renderHook((communityAddresses) => + useCommunities({ communityAddresses }), ); const waitFor = testUtils.createWaitFor(rendered); - expect(rendered.result.current.subplebbits).toEqual([]); - rendered.rerender(["subplebbit address 1", "subplebbit address 2", "subplebbit address 3"]); - expect(rendered.result.current.subplebbits).toEqual([undefined, undefined, undefined]); + expect(rendered.result.current.communities).toEqual([]); + rendered.rerender(["community address 1", "community address 2", "community address 3"]); + expect(rendered.result.current.communities).toEqual([undefined, undefined, undefined]); await waitFor( () => - typeof rendered.result.current.subplebbits[0].address === "string" && - typeof rendered.result.current.subplebbits[1].address === "string" && - typeof rendered.result.current.subplebbits[2].address === "string", + typeof rendered.result.current.communities[0].address === "string" && + typeof rendered.result.current.communities[1].address === "string" && + typeof rendered.result.current.communities[2].address === "string", ); - expect(rendered.result.current.subplebbits[0].address).toBe("subplebbit address 1"); - expect(rendered.result.current.subplebbits[1].address).toBe("subplebbit address 2"); - expect(rendered.result.current.subplebbits[2].address).toBe("subplebbit address 3"); + expect(rendered.result.current.communities[0].address).toBe("community address 1"); + expect(rendered.result.current.communities[1].address).toBe("community address 2"); + expect(rendered.result.current.communities[2].address).toBe("community address 3"); await waitFor( () => - typeof rendered.result.current.subplebbits[0].description === "string" && - typeof rendered.result.current.subplebbits[1].description === "string" && - typeof rendered.result.current.subplebbits[2].description === "string", + typeof rendered.result.current.communities[0].description === "string" && + typeof rendered.result.current.communities[1].description === "string" && + typeof rendered.result.current.communities[2].description === "string", ); - expect(rendered.result.current.subplebbits[0].description).toBe( - "subplebbit address 1 description updated", + expect(rendered.result.current.communities[0].description).toBe( + "community address 1 description updated", ); - expect(rendered.result.current.subplebbits[1].description).toBe( - "subplebbit address 2 description updated", + expect(rendered.result.current.communities[1].description).toBe( + "community address 2 description updated", ); - expect(rendered.result.current.subplebbits[2].description).toBe( - "subplebbit address 3 description updated", + expect(rendered.result.current.communities[2].description).toBe( + "community address 3 description updated", ); }); test("has updating state", async () => { - const rendered = renderHook((subplebbitAddress) => - useSubplebbit({ subplebbitAddress }), + const rendered = renderHook((communityAddress) => + useCommunity({ communityAddress }), ); const waitFor = testUtils.createWaitFor(rendered); - rendered.rerender("subplebbit address"); + rendered.rerender("community address"); await waitFor( () => @@ -214,23 +214,23 @@ describe("subplebbits", () => { }); test("has error events", async () => { - // mock update to save subplebbit instance - const subplebbitUpdate = Subplebbit.prototype.update; - const updatingSubplebbits: any = []; - Subplebbit.prototype.update = function () { - updatingSubplebbits.push(this); - return subplebbitUpdate.bind(this)(); + // mock update to save community instance + const communityUpdate = Community.prototype.update; + const updatingCommunities: any = []; + Community.prototype.update = function () { + updatingCommunities.push(this); + return communityUpdate.bind(this)(); }; - const rendered = renderHook((subplebbitAddress) => - useSubplebbit({ subplebbitAddress }), + const rendered = renderHook((communityAddress) => + useCommunity({ communityAddress }), ); const waitFor = testUtils.createWaitFor(rendered); - rendered.rerender("subplebbit address"); + rendered.rerender("community address"); // emit error event - await waitFor(() => updatingSubplebbits.length > 0); - updatingSubplebbits[0].emit("error", Error("error 1")); + await waitFor(() => updatingCommunities.length > 0); + updatingCommunities[0].emit("error", Error("error 1")); // first error await waitFor(() => rendered.result.current.error.message === "error 1"); @@ -239,7 +239,7 @@ describe("subplebbits", () => { expect(rendered.result.current.errors.length).toBe(1); // second error - updatingSubplebbits[0].emit("error", Error("error 2")); + updatingCommunities[0].emit("error", Error("error 2")); await waitFor(() => rendered.result.current.error.message === "error 2"); expect(rendered.result.current.error.message).toBe("error 2"); expect(rendered.result.current.errors[0].message).toBe("error 1"); @@ -247,68 +247,68 @@ describe("subplebbits", () => { expect(rendered.result.current.errors.length).toBe(2); // restore mock - Subplebbit.prototype.update = subplebbitUpdate; + Community.prototype.update = communityUpdate; }); - test("plebbit.createSubplebbit throws adds useSubplebbit().error", async () => { - // mock update to save subplebbit instance - const createSubplebbit = Plebbit.prototype.createSubplebbit; - Plebbit.prototype.createSubplebbit = async function () { - throw Error("plebbit.createSubplebbit error"); + test("plebbit.createCommunity throws adds useCommunity().error", async () => { + // mock update to save community instance + const createCommunity = Plebbit.prototype.createCommunity; + Plebbit.prototype.createCommunity = async function () { + throw Error("plebbit.createCommunity error"); }; - const rendered = renderHook((subplebbitAddress) => - useSubplebbit({ subplebbitAddress }), + const rendered = renderHook((communityAddress) => + useCommunity({ communityAddress }), ); const waitFor = testUtils.createWaitFor(rendered); - rendered.rerender("subplebbit address"); + rendered.rerender("community address"); - // plebbit.createSubplebbit error + // plebbit.createCommunity error await waitFor( - () => rendered.result.current.error.message === "plebbit.createSubplebbit error", + () => rendered.result.current.error.message === "plebbit.createCommunity error", ); - expect(rendered.result.current.error.message).toBe("plebbit.createSubplebbit error"); - expect(rendered.result.current.errors[0].message).toBe("plebbit.createSubplebbit error"); + expect(rendered.result.current.error.message).toBe("plebbit.createCommunity error"); + expect(rendered.result.current.errors[0].message).toBe("plebbit.createCommunity error"); expect(rendered.result.current.errors.length).toBe(1); // restore mock - Plebbit.prototype.createSubplebbit = createSubplebbit; + Plebbit.prototype.createCommunity = createCommunity; }); }); - test("useListSubplebbits", async () => { - const rendered = renderHook(() => useListSubplebbits()); + test("useListCommunities", async () => { + const rendered = renderHook(() => useListCommunities()); const waitFor = testUtils.createWaitFor(rendered); await waitFor(() => rendered.result.current.length > 0); expect(rendered.result.current).toEqual([ - "list subplebbit address 1", - "list subplebbit address 2", + "list community address 1", + "list community address 2", ]); }); - test("useSubplebbits with subplebbitAddresses undefined returns empty (branch 171)", async () => { - const rendered = renderHook(() => useSubplebbits({ subplebbitAddresses: undefined })); + test("useCommunities with communityAddresses undefined returns empty (branch 171)", async () => { + const rendered = renderHook(() => useCommunities({ communityAddresses: undefined })); await act(async () => {}); - expect(rendered.result.current.subplebbits).toEqual([]); + expect(rendered.result.current.communities).toEqual([]); }); - test("useSubplebbits effect returns early when account is undefined (branch 180)", async () => { + test("useCommunities effect returns early when account is undefined (branch 180)", async () => { vi.spyOn(accountsHooks, "useAccount").mockReturnValue(undefined as any); const rendered = renderHook(() => - useSubplebbits({ subplebbitAddresses: ["subplebbit address 1"] }), + useCommunities({ communityAddresses: ["community address 1"] }), ); await act(async () => {}); - expect(rendered.result.current.subplebbits).toEqual([undefined]); + expect(rendered.result.current.communities).toEqual([undefined]); vi.mocked(accountsHooks.useAccount).mockRestore(); }); - test("useListSubplebbits hits log and setState when arrays differ (lines 225, 228)", async () => { + test("useListCommunities hits log and setState when arrays differ (lines 225, 228)", async () => { vi.useFakeTimers(); try { - const subplebbits = ["addr-a", "addr-b"]; - const mockAccount = { plebbit: { subplebbits } }; + const communities = ["addr-a", "addr-b"]; + const mockAccount = { plebbit: { communities } }; vi.spyOn(accountsHooks, "useAccount").mockReturnValue(mockAccount as any); - const rendered = renderHook(() => useListSubplebbits()); + const rendered = renderHook(() => useListCommunities()); await act(async () => {}); vi.advanceTimersByTime(1100); await act(async () => {}); @@ -319,10 +319,10 @@ describe("subplebbits", () => { } }); - test("useListSubplebbits no-change branch when arrays match (line 227)", async () => { + test("useListCommunities no-change branch when arrays match (line 227)", async () => { vi.useFakeTimers(); try { - const rendered = renderHook(() => useListSubplebbits()); + const rendered = renderHook(() => useListCommunities()); await act(async () => {}); vi.advanceTimersByTime(2500); await act(async () => {}); @@ -335,11 +335,11 @@ describe("subplebbits", () => { } }); - test("useListSubplebbits treats missing plebbit.subplebbits as empty", async () => { + test("useListCommunities treats missing plebbit.communities as empty", async () => { vi.useFakeTimers(); try { vi.spyOn(accountsHooks, "useAccount").mockReturnValue({ plebbit: {} } as any); - const rendered = renderHook(() => useListSubplebbits()); + const rendered = renderHook(() => useListCommunities()); await act(async () => {}); vi.advanceTimersByTime(1100); await act(async () => {}); @@ -350,41 +350,41 @@ describe("subplebbits", () => { } }); - test("useSubplebbits addSubplebbitToStore catch logs error (stmt 189)", async () => { - const origAdd = subplebbitStore.getState().addSubplebbitToStore; - subplebbitStore.setState({ - addSubplebbitToStore: () => Promise.reject(new Error("addSubplebbit failed")), + test("useCommunities addCommunityToStore catch logs error (stmt 189)", async () => { + const origAdd = communityStore.getState().addCommunityToStore; + communityStore.setState({ + addCommunityToStore: () => Promise.reject(new Error("addCommunity failed")), }); const logSpy = vi.spyOn(console, "error").mockImplementation(() => {}); renderHook(() => - useSubplebbits({ subplebbitAddresses: ["new-addr-1", "new-addr-2"] }), + useCommunities({ communityAddresses: ["new-addr-1", "new-addr-2"] }), ); await new Promise((r) => setTimeout(r, 100)); - subplebbitStore.setState({ addSubplebbitToStore: origAdd }); + communityStore.setState({ addCommunityToStore: origAdd }); logSpy.mockRestore(); }); - test("useSubplebbitStats with no options (branch 88)", async () => { - const rendered = renderHook(() => useSubplebbitStats()); + test("useCommunityStats with no options (branch 88)", async () => { + const rendered = renderHook(() => useCommunityStats()); await act(async () => {}); expect(rendered.result.current.state).toBe("fetching-ipfs"); }); - test("useSubplebbitStats", async () => { + test("useCommunityStats", async () => { const rendered = renderHook(() => - useSubplebbitStats({ subplebbitAddress: "address 1" }), + useCommunityStats({ communityAddress: "address 1" }), ); const waitFor = testUtils.createWaitFor(rendered); await waitFor(() => rendered.result.current.hourActiveUserCount); expect(rendered.result.current.hourActiveUserCount).toBe(1); }); - test("useSubplebbitStats fetchCid error logs (stmt 110)", async () => { + test("useCommunityStats fetchCid error logs (stmt 110)", async () => { const origFetch = Plebbit.prototype.fetchCid; (Plebbit.prototype as any).fetchCid = () => Promise.reject(new Error("fetchCid failed")); const logSpy = vi.spyOn(console, "error").mockImplementation(() => {}); const rendered = renderHook(() => - useSubplebbitStats({ subplebbitAddress: "subplebbit address 1" }), + useCommunityStats({ communityAddress: "community address 1" }), ); await testUtils .createWaitFor(rendered)(() => true, { timeout: 2000 }) @@ -393,13 +393,13 @@ describe("subplebbits", () => { logSpy.mockRestore(); }); - describe("useResolvedSubplebbitAddress", () => { + describe("useResolvedCommunityAddress", () => { const timeout = 60000; // skip because uses internet and not deterministic - test.skip("useResolvedSubplebbitAddress", { timeout }, async () => { - const rendered = renderHook((subplebbitAddress) => - useResolvedSubplebbitAddress({ subplebbitAddress }), + test.skip("useResolvedCommunityAddress", { timeout }, async () => { + const rendered = renderHook((communityAddress) => + useResolvedCommunityAddress({ communityAddress }), ); const waitFor = testUtils.createWaitFor(rendered, { timeout }); expect(rendered.result.current.resolvedAddress).toBe(undefined); @@ -412,8 +412,8 @@ describe("subplebbits", () => { }); test("unsupported crypto domain", { timeout }, async () => { - const rendered = renderHook((subplebbitAddress) => - useResolvedSubplebbitAddress({ subplebbitAddress }), + const rendered = renderHook((communityAddress) => + useResolvedCommunityAddress({ communityAddress }), ); const waitFor = testUtils.createWaitFor(rendered); expect(rendered.result.current.resolvedAddress).toBe(undefined); @@ -424,8 +424,8 @@ describe("subplebbits", () => { }); test("not a crypto domain", { timeout }, async () => { - const rendered = renderHook((subplebbitAddress) => - useResolvedSubplebbitAddress({ subplebbitAddress }), + const rendered = renderHook((communityAddress) => + useResolvedCommunityAddress({ communityAddress }), ); const waitFor = testUtils.createWaitFor(rendered); expect(rendered.result.current.resolvedAddress).toBe(undefined); @@ -435,17 +435,17 @@ describe("subplebbits", () => { expect(rendered.result.current.error.message).toBe("not a crypto domain"); }); - test("reset when subplebbitAddress undefined", async () => { + test("reset when communityAddress undefined", async () => { vi.useFakeTimers(); const resolveSpy = vi.spyOn(chain, "resolveEnsTxtRecord").mockResolvedValue("resolved-addr"); const rendered = renderHook((opts: any) => - useResolvedSubplebbitAddress({ subplebbitAddress: opts?.subplebbitAddress }), + useResolvedCommunityAddress({ communityAddress: opts?.communityAddress }), ); - rendered.rerender({ subplebbitAddress: "test.eth" }); + rendered.rerender({ communityAddress: "test.eth" }); vi.advanceTimersByTime(2000); await act(async () => {}); expect(rendered.result.current.resolvedAddress || rendered.result.current.state).toBeTruthy(); - rendered.rerender({ subplebbitAddress: undefined }); + rendered.rerender({ communityAddress: undefined }); vi.advanceTimersByTime(2000); await act(async () => {}); expect(rendered.result.current.resolvedAddress).toBe(undefined); @@ -460,7 +460,7 @@ describe("subplebbits", () => { .spyOn(chain, "resolveEnsTxtRecord") .mockResolvedValue("resolved-cid-123"); const rendered = renderHook(() => - useResolvedSubplebbitAddress({ subplebbitAddress: "test.eth" }), + useResolvedCommunityAddress({ communityAddress: "test.eth" }), ); vi.advanceTimersByTime(2000); await act(async () => {}); @@ -476,7 +476,7 @@ describe("subplebbits", () => { .spyOn(chain, "resolveEnsTxtRecord") .mockRejectedValue(new Error("name not registered")); const rendered = renderHook(() => - useResolvedSubplebbitAddress({ subplebbitAddress: "nonexistent.eth" }), + useResolvedCommunityAddress({ communityAddress: "nonexistent.eth" }), ); vi.advanceTimersByTime(2000); await act(async () => {}); @@ -491,7 +491,7 @@ describe("subplebbits", () => { const resolvedAddr = "same-resolved-addr"; const resolveSpy = vi.spyOn(chain, "resolveEnsTxtRecord").mockResolvedValue(resolvedAddr); const rendered = renderHook(() => - useResolvedSubplebbitAddress({ subplebbitAddress: "test.eth", cache: false }), + useResolvedCommunityAddress({ communityAddress: "test.eth", cache: false }), ); vi.advanceTimersByTime(2000); await act(async () => {}); @@ -504,15 +504,15 @@ describe("subplebbits", () => { vi.useRealTimers(); }); - test("useResolvedSubplebbitAddress reset branch clears state when subplebbitAddress cleared", async () => { + test("useResolvedCommunityAddress reset branch clears state when communityAddress cleared", async () => { vi.useFakeTimers(); const resolveSpy = vi.spyOn(chain, "resolveEnsTxtRecord").mockResolvedValue("resolved"); - const rendered = renderHook((opts: any) => useResolvedSubplebbitAddress(opts)); - rendered.rerender({ subplebbitAddress: "test.eth" }); + const rendered = renderHook((opts: any) => useResolvedCommunityAddress(opts)); + rendered.rerender({ communityAddress: "test.eth" }); vi.advanceTimersByTime(2000); await act(async () => {}); expect(rendered.result.current.resolvedAddress).toBe("resolved"); - rendered.rerender({ subplebbitAddress: undefined }); + rendered.rerender({ communityAddress: undefined }); vi.advanceTimersByTime(2000); await act(async () => {}); expect(rendered.result.current.resolvedAddress).toBe(undefined); @@ -521,18 +521,18 @@ describe("subplebbits", () => { vi.useRealTimers(); }); - test("useResolvedSubplebbitAddress reset clears errors when subplebbitAddress cleared (line 289)", async () => { + test("useResolvedCommunityAddress reset clears errors when communityAddress cleared (line 289)", async () => { vi.useFakeTimers(); const resolveSpy = vi .spyOn(chain, "resolveEnsTxtRecord") .mockRejectedValue(new Error("name not registered")); - const rendered = renderHook((opts: any) => useResolvedSubplebbitAddress(opts)); - rendered.rerender({ subplebbitAddress: "nonexistent.eth" }); + const rendered = renderHook((opts: any) => useResolvedCommunityAddress(opts)); + rendered.rerender({ communityAddress: "nonexistent.eth" }); vi.advanceTimersByTime(2000); await act(async () => {}); expect(rendered.result.current.resolvedAddress).toBe(undefined); expect(rendered.result.current.errors.length).toBeGreaterThan(0); - rendered.rerender({ subplebbitAddress: undefined }); + rendered.rerender({ communityAddress: undefined }); vi.advanceTimersByTime(2000); await act(async () => {}); expect(rendered.result.current.resolvedAddress).toBe(undefined); @@ -543,9 +543,9 @@ describe("subplebbits", () => { }); }); - test("resolveSubplebbitAddress throw for non-.eth", async () => { - await expect(resolveSubplebbitAddress("plebbit.com", {})).rejects.toThrow( - "resolveSubplebbitAddress invalid subplebbitAddress", + test("resolveCommunityAddress throw for non-.eth", async () => { + await expect(resolveCommunityAddress("plebbit.com", {})).rejects.toThrow( + "resolveCommunityAddress invalid communityAddress", ); }); }); diff --git a/src/hooks/communities.ts b/src/hooks/communities.ts new file mode 100644 index 00000000..ab6272cf --- /dev/null +++ b/src/hooks/communities.ts @@ -0,0 +1,360 @@ +import { useEffect, useState, useMemo } from "react"; +import { useAccount } from "./accounts"; +import validator from "../lib/validator"; +import Logger from "@plebbit/plebbit-logger"; +const log = Logger("bitsocial-react-hooks:communities:hooks"); +import assert from "assert"; +import { + Community, + CommunityStats, + ChainProviders, + UseResolvedCommunityAddressOptions, + UseResolvedCommunityAddressResult, + UseCommunityOptions, + UseCommunityResult, + UseCommunitiesOptions, + UseCommunitiesResult, + UseCommunityStatsOptions, + UseCommunityStatsResult, +} from "../types"; +import useInterval from "./utils/use-interval"; +import createStore from "zustand"; +import { resolveEnsTxtRecord } from "../lib/chain"; +import useCommunitiesStore from "../stores/communities"; +import shallow from "zustand/shallow"; + +/** + * @param communityAddress - The address of the community, e.g. 'memes.eth', '12D3KooW...', etc + * @param acountName - The nickname of the account, e.g. 'Account 1'. If no accountName is provided, use + * the active account. + */ +export function useCommunity(options?: UseCommunityOptions): UseCommunityResult { + assert( + !options || typeof options === "object", + `useCommunity options argument '${options}' not an object`, + ); + const { communityAddress, accountName, onlyIfCached } = options ?? {}; + const account = useAccount({ accountName }); + const community = useCommunitiesStore((state: any) => state.communities[communityAddress || ""]); + const addCommunityToStore = useCommunitiesStore((state: any) => state.addCommunityToStore); + const errors = useCommunitiesStore((state: any) => state.errors[communityAddress || ""]); + + useEffect(() => { + if (!communityAddress || !account) { + return; + } + validator.validateUseCommunityArguments(communityAddress, account); + if (!community && !onlyIfCached) { + // if community isn't already in store, add it + addCommunityToStore(communityAddress, account).catch((error: unknown) => + log.error("useCommunity addCommunityToStore error", { communityAddress, error }), + ); + } + }, [communityAddress, account?.id]); + + if (account && communityAddress) { + log("useCommunity", { communityAddress, community, account }); + } + + let state = community?.updatingState || "initializing"; + // force succeeded even if the community is fecthing a new update + if (community?.updatedAt) { + state = "succeeded"; + } + + return useMemo( + () => ({ + ...community, + state, + error: errors?.[errors.length - 1], + errors: errors || [], + }), + [community, communityAddress, errors], + ); +} + +/** + * @param communityAddress - The address of the community, e.g. 'memes.eth', '12D3KooW...', etc + * @param acountName - The nickname of the account, e.g. 'Account 1'. If no accountName is provided, use + * the active account. + */ +export function useCommunityStats(options?: UseCommunityStatsOptions): UseCommunityStatsResult { + assert( + !options || typeof options === "object", + `useCommunityStats options argument '${options}' not an object`, + ); + const { communityAddress, accountName, onlyIfCached } = options ?? {}; + const account = useAccount({ accountName }); + const community = useCommunity({ communityAddress, onlyIfCached }); + const communityStatsCid = community?.statsCid; + const communityStats = useCommunitiesStatsStore( + (state: CommunitiesStatsState) => state.communitiesStats[communityAddress || ""], + ); + const setCommunityStats = useCommunitiesStatsStore( + (state: CommunitiesStatsState) => state.setCommunityStats, + ); + + useEffect(() => { + if (!communityAddress || !communityStatsCid || !account) { + return; + } + (async () => { + let fetchedCid; + try { + fetchedCid = await account.plebbit.fetchCid({ cid: communityStatsCid }); + fetchedCid = JSON.parse(fetchedCid); + setCommunityStats(communityAddress, fetchedCid); + } catch (error) { + log.error("useCommunityStats plebbit.fetchCid error", { + communityAddress, + communityStatsCid, + community, + fetchedCid, + error, + }); + } + })(); + }, [communityStatsCid, account?.id, communityAddress, setCommunityStats]); + + if (account && communityStatsCid) { + log("useCommunityStats", { + communityAddress, + communityStatsCid, + communityStats, + community, + account, + }); + } + + const state = communityStats ? "succeeded" : "fetching-ipfs"; + + return useMemo( + () => ({ + ...communityStats, + state, + error: undefined, + errors: [], + }), + [communityStats, communityStatsCid, communityAddress], + ); +} + +type CommunitiesStatsState = { + communitiesStats: { [communityAddress: string]: CommunityStats }; + setCommunityStats: Function; +}; + +const useCommunitiesStatsStore = createStore((setState: Function) => ({ + communitiesStats: {}, + setCommunityStats: (communityAddress: string, communityStats: CommunityStats) => + setState((state: CommunitiesStatsState) => ({ + communitiesStats: { ...state.communitiesStats, [communityAddress]: communityStats }, + })), +})); + +/** + * @param communityAddresses - The addresses of the communities, e.g. ['memes.eth', '12D3KooWA...'] + * @param acountName - The nickname of the account, e.g. 'Account 1'. If no accountName is provided, use + * the active account. + */ +export function useCommunities(options?: UseCommunitiesOptions): UseCommunitiesResult { + assert( + !options || typeof options === "object", + `useCommunities options argument '${options}' not an object`, + ); + const { communityAddresses = [], accountName, onlyIfCached } = options ?? {}; + const addrs = communityAddresses ?? []; + const account = useAccount({ accountName }); + const communities: (Community | undefined)[] = useCommunitiesStore( + (state: any) => addrs.map((communityAddress) => state.communities[communityAddress || ""]), + shallow, + ); + const addCommunityToStore = useCommunitiesStore((state: any) => state.addCommunityToStore); + + useEffect(() => { + if (!addrs.length || !account) { + return; + } + validator.validateUseCommunitiesArguments(addrs, account); + if (onlyIfCached) { + return; + } + const uniqueCommunityAddresses = new Set(addrs); + for (const communityAddress of uniqueCommunityAddresses) { + addCommunityToStore(communityAddress, account).catch((error: unknown) => + log.error("useCommunities addCommunityToStore error", { communityAddress, error }), + ); + } + }, [addrs.toString(), account?.id]); + + if (account && addrs.length) { + log("useCommunities", { communityAddresses: addrs, communities, account }); + } + + // succeed if no communities are undefined + const state = communities.indexOf(undefined) === -1 ? "succeeded" : "fetching-ipns"; + + return useMemo( + () => ({ + communities, + state, + error: undefined, + errors: [], + }), + [communities, addrs.toString()], + ); +} + +// TODO: plebbit.listCommunities() has been removed, rename this and use event communitieschanged instead of polling +/** + * Returns all the owner communities created by plebbit-js by calling plebbit.listCommunities() + */ +export function useListCommunities() { + const account = useAccount(); + const [communityAddresses, setCommunityAddresses] = useState([]); + + const delay = 1000; + const immediate = true; + useInterval( + () => { + const plebbit = account?.plebbit; + if (!plebbit) return; + const newAddrs = Array.isArray(plebbit.communities) ? plebbit.communities : []; + if (newAddrs.toString() !== communityAddresses.toString()) { + log("useListCommunities", { communityAddresses }); + setCommunityAddresses(newAddrs); + } + }, + delay, + immediate, + ); + + return communityAddresses; +} + +/** + * @param communityAddress - The community address to resolve to a public key, e.g. 'news.eth' resolves to '12D3KooW...'. + * @param acountName - The nickname of the account, e.g. 'Account 1'. If no accountName is provided, use + * the active account. + */ +// NOTE: useResolvedCommunityAddress tests are skipped, if changes are made they must be tested manually +export function useResolvedCommunityAddress( + options?: UseResolvedCommunityAddressOptions, +): UseResolvedCommunityAddressResult { + assert( + !options || typeof options === "object", + `useResolvedCommunityAddress options argument '${options}' not an object`, + ); + let { communityAddress, accountName, cache } = options ?? {}; + + // cache by default + if (typeof cache !== "boolean") { + cache = true; + } + + // poll every 15 seconds, about the duration of an eth block + let interval = 15000; + // no point in polling often if caching is on + if (cache) { + interval = 1000 * 60 * 60 * 25; + } + + const account = useAccount({ accountName }); + // possible to use account.plebbit instead of account.plebbitOptions + const chainProviders = account?.plebbitOptions?.chainProviders; + const [resolvedAddress, setResolvedAddress] = useState(); + const [errors, setErrors] = useState([]); + const [state, setState] = useState(); + + let initialState = "initializing"; + // before those defined, nothing can happen + if (options && account && communityAddress) { + initialState = "ready"; + } + + useInterval( + () => { + if (!account || !communityAddress) { + setResolvedAddress(undefined); + setState(undefined); + setErrors((prevErrors) => (prevErrors.length ? [] : prevErrors)); + return; + } + + // address isn't a crypto domain, can't be resolved + if (!communityAddress?.includes(".")) { + if (state !== "failed") { + setErrors([Error("not a crypto domain")]); + setState("failed"); + setResolvedAddress(undefined); + } + return; + } + + // only support resolving '.eth' for now + if (!communityAddress?.endsWith(".eth")) { + if (state !== "failed") { + setErrors([Error("crypto domain type unsupported")]); + setState("failed"); + setResolvedAddress(undefined); + } + return; + } + + (async () => { + try { + setState("resolving"); + const res = await resolveCommunityAddress(communityAddress, chainProviders); + setState("succeeded"); + if (res !== resolvedAddress) { + setResolvedAddress(res); + } + } catch (error: any) { + setErrors([...errors, error]); + setState("failed"); + setResolvedAddress(undefined); + log.error("useResolvedCommunityAddress resolveCommunityAddress error", { + communityAddress, + chainProviders, + error, + }); + } + })(); + }, + interval, + true, + [communityAddress, chainProviders], + ); + + // only support ENS at the moment + const chainProvider = chainProviders?.["eth"]; + + // log('useResolvedCommunityAddress', {communityAddress, state, errors, resolvedAddress, chainProviders}) + return { + resolvedAddress, + chainProvider, + state: state || initialState, + error: errors[errors.length - 1], + errors, + }; +} + +// NOTE: resolveCommunityAddress tests are skipped, if changes are made they must be tested manually +export const resolveCommunityAddress = async ( + communityAddress: string, + chainProviders: ChainProviders, +) => { + let resolvedCommunityAddress; + if (communityAddress.endsWith(".eth")) { + resolvedCommunityAddress = await resolveEnsTxtRecord( + communityAddress, + "community-address", + "eth", + chainProviders?.["eth"]?.urls?.[0], + chainProviders?.["eth"]?.chainId, + ); + } else { + throw Error(`resolveCommunityAddress invalid communityAddress '${communityAddress}'`); + } + return resolvedCommunityAddress; +}; diff --git a/src/hooks/feeds/feeds.test.ts b/src/hooks/feeds/feeds.test.ts index 90056441..c2bda369 100644 --- a/src/hooks/feeds/feeds.test.ts +++ b/src/hooks/feeds/feeds.test.ts @@ -1,23 +1,23 @@ import { act } from "@testing-library/react"; import testUtils, { renderHook } from "../../lib/test-utils"; import { Comment } from "../../types"; -import { useFeed, useBufferedFeeds, useAccount, useSubplebbit, setPlebbitJs } from "../.."; +import { useFeed, useBufferedFeeds, useAccount, useCommunity, setPlebbitJs } from "../.."; import * as accountsActions from "../../stores/accounts/accounts-actions"; import { getCommentCidsToAccountsComments } from "../../stores/accounts/utils"; import localForageLru from "../../lib/localforage-lru"; import localForage from "localforage"; import feedsStore, { defaultPostsPerPage as postsPerPage } from "../../stores/feeds"; -import subplebbitsStore from "../../stores/subplebbits"; -import subplebbitsPagesStore from "../../stores/subplebbits-pages"; +import communitiesStore from "../../stores/communities"; +import communitiesPagesStore from "../../stores/communities-pages"; import accountsStore from "../../stores/accounts"; import PlebbitJsMock, { Plebbit, - Subplebbit, + Community, Pages, simulateLoadingTime, } from "../../lib/plebbit-js/plebbit-js-mock"; -const plebbitJsMockSubplebbitPageLength = 100; +const plebbitJsMockCommunityPageLength = 100; describe("feeds", () => { beforeAll(async () => { @@ -75,7 +75,7 @@ describe("feeds", () => { }, })); - rendered.rerender({ subplebbitAddresses: ["subplebbit address 1"] }); + rendered.rerender({ communityAddresses: ["community address 1"] }); await new Promise((r) => setTimeout(r, 150)); expect(rendered.result.current.feed).toEqual([]); @@ -89,16 +89,16 @@ describe("feeds", () => { } }); - test("useFeed hasMore false when subplebbitAddresses empty", async () => { + test("useFeed hasMore false when communityAddresses empty", async () => { rendered.rerender({}); expect(rendered.result.current.hasMore).toBe(false); - rendered.rerender({ subplebbitAddresses: [] }); + rendered.rerender({ communityAddresses: [] }); expect(rendered.result.current.hasMore).toBe(false); }); test("loadMore init guard throws when not initialized", async () => { rendered.rerender({ - subplebbitAddresses: ["subplebbit address 1"], + communityAddresses: ["community address 1"], accountName: "nonexistent-account-xyz", }); await act(async () => { @@ -110,7 +110,7 @@ describe("feeds", () => { test("reset init guard throws when not initialized", async () => { rendered.rerender({ - subplebbitAddresses: ["subplebbit address 1"], + communityAddresses: ["community address 1"], accountName: "nonexistent-account-xyz", }); await act(async () => { @@ -122,13 +122,13 @@ describe("feeds", () => { test("not yet loaded feed hasMore true", async () => { expect(rendered.result.current.hasMore).toBe(false); - rendered.rerender({ subplebbitAddresses: ["subplebbit address 1"] }); + rendered.rerender({ communityAddresses: ["community address 1"] }); expect(rendered.result.current.hasMore).toBe(true); }); - test("get feed page 1 with 1 subplebbit sorted by default (hot)", async () => { + test("get feed page 1 with 1 community sorted by default (hot)", async () => { // get feed with 1 sub - rendered.rerender({ subplebbitAddresses: ["subplebbit address 1"] }); + rendered.rerender({ communityAddresses: ["community address 1"] }); // initial state expect(typeof rendered.result.current.hasMore).toBe("boolean"); expect(typeof rendered.result.current.loadMore).toBe("function"); @@ -142,32 +142,32 @@ describe("feeds", () => { // NOTE: the 'hot' sort type uses timestamps and bugs out with timestamp '1-100' so this is why we get cid 1 // with low upvote count first expect(rendered.result.current.feed[0].cid).toBe( - "subplebbit address 1 page cid hot comment cid 100", + "community address 1 page cid hot comment cid 100", ); expect(rendered.result.current.feed.length).toBe(postsPerPage); expect(rendered.result.current.bufferedFeed.length).toBe( - plebbitJsMockSubplebbitPageLength - postsPerPage, + plebbitJsMockCommunityPageLength - postsPerPage, ); // reset stores to force using the db await testUtils.resetStores(); - // get feed again from database, only wait for 1 render because subplebbit is stored in db + // get feed again from database, only wait for 1 render because community is stored in db const rendered2 = renderHook(() => - useFeed({ subplebbitAddresses: ["subplebbit address 1"] }), + useFeed({ communityAddresses: ["community address 1"] }), ); expect(rendered2.result.current.feed).toEqual([]); - // only wait for 1 render because subplebbit is stored in db + // only wait for 1 render because community is stored in db await waitFor(() => rendered2.result.current.feed[0].cid); expect(rendered2.result.current.feed[0].cid).toBe( - "subplebbit address 1 page cid hot comment cid 100", + "community address 1 page cid hot comment cid 100", ); expect(rendered2.result.current.feed.length).toBe(postsPerPage); }); test("useFeed mirrors moderation flags into commentModeration", async () => { - rendered.rerender({ subplebbitAddresses: ["subplebbit address 1"] }); + rendered.rerender({ communityAddresses: ["community address 1"] }); await waitFor(() => rendered.result.current.feed.length > 0); const [feedName] = Object.keys(feedsStore.getState().loadedFeeds); @@ -192,12 +192,12 @@ describe("feeds", () => { Date.now = () => now; // get feed with 1 sub - rendered.rerender({ subplebbitAddresses: ["subplebbit address 1"] }); + rendered.rerender({ communityAddresses: ["community address 1"] }); // wait for posts to be added, should get full first page await waitFor(() => rendered.result.current.feed.length > 0); expect(rendered.result.current.feed[0].cid).toBe( - "subplebbit address 1 page cid hot comment cid 100", + "community address 1 page cid hot comment cid 100", ); expect(rendered.result.current.feed.length).toBe(postsPerPage); @@ -205,15 +205,15 @@ describe("feeds", () => { Date.now = () => now + 61 * 60 * 1000; // mock sub update to never update - const update = Subplebbit.prototype.update; - Subplebbit.prototype.update = async function () {}; + const update = Community.prototype.update; + Community.prototype.update = async function () {}; // reset stores to force using the db await testUtils.resetStores(); - // get feed again from database, only wait for 1 render because subplebbit is stored in db + // get feed again from database, only wait for 1 render because community is stored in db const rendered2 = renderHook(() => - useFeed({ subplebbitAddresses: ["subplebbit address 1"] }), + useFeed({ communityAddresses: ["community address 1"] }), ); // no way to wait other than just time since result is that there's no result @@ -227,13 +227,13 @@ describe("feeds", () => { // restore mock Date.now = DateNow; - Subplebbit.prototype.update = update; + Community.prototype.update = update; }); test("get feed with custom posts per page", async () => { const customPostsPerPage = 10; rendered.rerender({ - subplebbitAddresses: ["subplebbit address 1"], + communityAddresses: ["community address 1"], postsPerPage: customPostsPerPage, }); @@ -264,7 +264,7 @@ describe("feeds", () => { page.comments.push({ timestamp: now - page.comments.length, // 1 post per second cid: cid + " comment cid " + (page.comments.length + 1), - subplebbitAddress: this.subplebbit.address, + communityAddress: this.community.address, }); } return page; @@ -272,7 +272,7 @@ describe("feeds", () => { const newerThan = 5; // newer than x seconds rendered.rerender({ - subplebbitAddresses: ["subplebbit address 1"], + communityAddresses: ["community address 1"], sortType: "new", // sort by new so the feed uses getPage newerThan, }); @@ -303,26 +303,26 @@ describe("feeds", () => { timestamp: now, lastReplyTimestamp: undefined, cid: "newer cid 1", - subplebbitAddress: this.subplebbit.address, + communityAddress: this.community.address, }, { timestamp: 1, lastReplyTimestamp: now, cid: "newer cid 2", - subplebbitAddress: this.subplebbit.address, + communityAddress: this.community.address, }, // should not be newer { timestamp: 1, lastReplyTimestamp: undefined, cid: "older cid 1", - subplebbitAddress: this.subplebbit.address, + communityAddress: this.community.address, }, { timestamp: 1, lastReplyTimestamp: 1, cid: "older cid 2", - subplebbitAddress: this.subplebbit.address, + communityAddress: this.community.address, }, ], }; @@ -330,7 +330,7 @@ describe("feeds", () => { const newerThan = 5; // newer than x seconds rendered.rerender({ - subplebbitAddresses: ["subplebbit address 1"], + communityAddresses: ["community address 1"], sortType: "active", newerThan, }); @@ -350,42 +350,42 @@ describe("feeds", () => { test("newerThan sets correct sortType", async () => { rendered.rerender({ - subplebbitAddresses: ["subplebbit address 1"], + communityAddresses: ["community address 1"], sortType: "topAll", newerThan: 60 * 60 * 24, }); expect(Object.keys(feedsStore.getState().feedsOptions).join(" ")).toMatch("topDay"); rendered.rerender({ - subplebbitAddresses: ["subplebbit address 1"], + communityAddresses: ["community address 1"], sortType: "topAll", newerThan: 60 * 60 * 24 * 7, }); expect(Object.keys(feedsStore.getState().feedsOptions).join(" ")).toMatch("topWeek"); rendered.rerender({ - subplebbitAddresses: ["subplebbit address 1"], + communityAddresses: ["community address 1"], sortType: "topAll", newerThan: 60 * 60 * 24 * 30, }); expect(Object.keys(feedsStore.getState().feedsOptions).join(" ")).toMatch("topMonth"); rendered.rerender({ - subplebbitAddresses: ["subplebbit address 1"], + communityAddresses: ["community address 1"], sortType: "topAll", newerThan: 60 * 60 * 24 * 365, }); expect(Object.keys(feedsStore.getState().feedsOptions).join(" ")).toMatch("topYear"); rendered.rerender({ - subplebbitAddresses: ["subplebbit address 1"], + communityAddresses: ["community address 1"], sortType: "controversialAll", newerThan: 60 * 60 * 24, }); expect(Object.keys(feedsStore.getState().feedsOptions).join(" ")).toMatch("controversialDay"); rendered.rerender({ - subplebbitAddresses: ["subplebbit address 1"], + communityAddresses: ["community address 1"], sortType: "controversialAll", newerThan: 60 * 60 * 24 * 7, }); @@ -394,7 +394,7 @@ describe("feeds", () => { ); rendered.rerender({ - subplebbitAddresses: ["subplebbit address 1"], + communityAddresses: ["community address 1"], sortType: "controversialAll", newerThan: 60 * 60 * 24 * 30, }); @@ -403,7 +403,7 @@ describe("feeds", () => { ); rendered.rerender({ - subplebbitAddresses: ["subplebbit address 1"], + communityAddresses: ["community address 1"], sortType: "controversialAll", newerThan: 60 * 60 * 24 * 365, }); @@ -412,69 +412,69 @@ describe("feeds", () => { ); rendered.rerender({ - subplebbitAddresses: ["subplebbit address 1"], + communityAddresses: ["community address 1"], sortType: "topAll", newerThan: 60 * 60 * 24 * 400, }); expect(Object.keys(feedsStore.getState().feedsOptions).join(" ")).toMatch("topAll"); }); - test("change subplebbit addresses and sort type", async () => { - rendered.rerender({ subplebbitAddresses: ["subplebbit address 1"], sortType: "hot" }); - await waitFor(() => !!rendered.result.current.feed[0].cid.match(/subplebbit address 1/)); - expect(rendered.result.current.feed[0].cid).toMatch(/subplebbit address 1/); + test("change community addresses and sort type", async () => { + rendered.rerender({ communityAddresses: ["community address 1"], sortType: "hot" }); + await waitFor(() => !!rendered.result.current.feed[0].cid.match(/community address 1/)); + expect(rendered.result.current.feed[0].cid).toMatch(/community address 1/); expect(rendered.result.current.feed.length).toBe(postsPerPage); - // change subplebbit addresses + // change community addresses rendered.rerender({ - subplebbitAddresses: ["subplebbit address 2", "subplebbit address 3"], + communityAddresses: ["community address 2", "community address 3"], sortType: "hot", }); - await waitFor(() => !!rendered.result.current.feed[0].cid.match(/subplebbit address (2|3)/)); + await waitFor(() => !!rendered.result.current.feed[0].cid.match(/community address (2|3)/)); - expect(rendered.result.current.feed[0].cid).toMatch(/subplebbit address (2|3)/); + expect(rendered.result.current.feed[0].cid).toMatch(/community address (2|3)/); // the 'hot' sort type should give timestamp 100 with the current mock expect(rendered.result.current.feed[0].timestamp).toBe(100); expect(rendered.result.current.feed.length).toBe(postsPerPage); // change sort type rendered.rerender({ - subplebbitAddresses: ["subplebbit address 2", "subplebbit address 3"], + communityAddresses: ["community address 2", "community address 3"], sortType: "new", }); - await waitFor(() => !!rendered.result.current.feed[0].cid.match(/subplebbit address (2|3)/)); + await waitFor(() => !!rendered.result.current.feed[0].cid.match(/community address (2|3)/)); - expect(rendered.result.current.feed[0].cid).toMatch(/subplebbit address (2|3)/); + expect(rendered.result.current.feed[0].cid).toMatch(/community address (2|3)/); // the 'new' sort type should give timestamp higher than 99 with the current mock expect(rendered.result.current.feed[0].timestamp).toBeGreaterThan(99); expect(rendered.result.current.feed.length).toBe(postsPerPage); - // change subplebbit addresses and sort type + // change community addresses and sort type rendered.rerender({ - subplebbitAddresses: ["subplebbit address 4", "subplebbit address 5"], + communityAddresses: ["community address 4", "community address 5"], sortType: "topAll", }); - await waitFor(() => !!rendered.result.current.feed[0].cid.match(/subplebbit address (4|5)/)); + await waitFor(() => !!rendered.result.current.feed[0].cid.match(/community address (4|5)/)); - expect(rendered.result.current.feed[0].cid).toMatch(/subplebbit address (4|5)/); + expect(rendered.result.current.feed[0].cid).toMatch(/community address (4|5)/); expect(rendered.result.current.feed.length).toBe(postsPerPage); // change sort type active rendered.rerender({ - subplebbitAddresses: ["subplebbit address 2", "subplebbit address 3"], + communityAddresses: ["community address 2", "community address 3"], sortType: "active", }); - await waitFor(() => !!rendered.result.current.feed[0].cid.match(/subplebbit address (2|3)/)); + await waitFor(() => !!rendered.result.current.feed[0].cid.match(/community address (2|3)/)); - expect(rendered.result.current.feed[0].cid).toMatch(/subplebbit address (2|3)/); + expect(rendered.result.current.feed[0].cid).toMatch(/community address (2|3)/); // the 'new' sort type should give timestamp higher than 99 with the current mock expect(rendered.result.current.feed[0].timestamp).toBeGreaterThan(99); expect(rendered.result.current.feed.length).toBe(postsPerPage); }); - test("get feed with 1 subplebbit and scroll to multiple pages", async () => { + test("get feed with 1 community and scroll to multiple pages", async () => { // get feed with 1 sub - rendered.rerender({ subplebbitAddresses: ["subplebbit address 1"] }); + rendered.rerender({ communityAddresses: ["community address 1"] }); // wait for posts to be added, should get full first page await waitFor(() => rendered.result.current.feed.length > 0); @@ -493,7 +493,7 @@ describe("feeds", () => { } }); - test("get feed with 1 subplebbit sorted by new and scroll to multiple pages", async () => { + test("get feed with 1 community sorted by new and scroll to multiple pages", async () => { let getPageCalledTimes = 0; const getPage = Pages.prototype.getPage; Pages.prototype.getPage = async function (options: { cid: string }) { @@ -501,7 +501,7 @@ describe("feeds", () => { // without the extra simulated load time the hooks will fetch multiple pages in advance instead of just 1 await simulateLoadingTime(); const page: any = { - nextCid: this.subplebbit.address + " next page cid " + (getPageCalledTimes + 1), + nextCid: this.community.address + " next page cid " + (getPageCalledTimes + 1), comments: [], }; const postCount = 100; @@ -511,7 +511,7 @@ describe("feeds", () => { page.comments.push({ timestamp: commentStartIndex + index, cid: cid + " comment cid " + (commentStartIndex + index), - subplebbitAddress: this.subplebbit.address, + communityAddress: this.community.address, }); } getPageCalledTimes++; @@ -519,30 +519,30 @@ describe("feeds", () => { }; // get feed with 1 sub sorted by new page 1 - rendered.rerender({ subplebbitAddresses: ["subplebbit address 1"], sortType: "new" }); + rendered.rerender({ communityAddresses: ["community address 1"], sortType: "new" }); await waitFor(() => rendered.result.current.feed?.length >= postsPerPage); expect(rendered.result.current.feed[0].timestamp).toBe(100); expect(rendered.result.current.feed[1].timestamp).toBe(99); expect(rendered.result.current.feed[2].timestamp).toBe(98); expect(rendered.result.current.feed[0].cid).toBe( - "subplebbit address 1 page cid new comment cid 100", + "community address 1 page cid new comment cid 100", ); expect(rendered.result.current.feed[1].cid).toBe( - "subplebbit address 1 page cid new comment cid 99", + "community address 1 page cid new comment cid 99", ); expect(rendered.result.current.feed[2].cid).toBe( - "subplebbit address 1 page cid new comment cid 98", + "community address 1 page cid new comment cid 98", ); - // at this point the buffered feed has gotten 1 subplebbit page + // at this point the buffered feed has gotten 1 community page expect(getPageCalledTimes).toBe(1); // get page 2 await scrollOnePage(); expect(rendered.result.current.feed[postsPerPage].timestamp).toBe(75); expect(rendered.result.current.feed[postsPerPage].cid).toBe( - "subplebbit address 1 page cid new comment cid 75", + "community address 1 page cid new comment cid 75", ); // ad this point the buffered feed is length 50, we can wait for getPage to be called again @@ -558,14 +558,14 @@ describe("feeds", () => { ).toBe(200); expect( rendered.result.current.feed[rendered.result.current.feed.length - postsPerPage].cid, - ).toBe("subplebbit address 1 next page cid 1 comment cid 200"); + ).toBe("community address 1 next page cid 1 comment cid 200"); await scrollOnePage(); expect( rendered.result.current.feed[rendered.result.current.feed.length - postsPerPage].timestamp, ).toBe(175); expect( rendered.result.current.feed[rendered.result.current.feed.length - postsPerPage].cid, - ).toBe("subplebbit address 1 next page cid 1 comment cid 175"); + ).toBe("community address 1 next page cid 1 comment cid 175"); // scroll 2 more times to get to buffered feeds length 50 and trigger a new buffer refill await scrollOnePage(); @@ -581,24 +581,24 @@ describe("feeds", () => { ).toBe(300); expect( rendered.result.current.feed[rendered.result.current.feed.length - postsPerPage].cid, - ).toBe("subplebbit address 1 next page cid 2 comment cid 300"); + ).toBe("community address 1 next page cid 2 comment cid 300"); await scrollOnePage(); expect( rendered.result.current.feed[rendered.result.current.feed.length - postsPerPage].timestamp, ).toBe(275); expect( rendered.result.current.feed[rendered.result.current.feed.length - postsPerPage].cid, - ).toBe("subplebbit address 1 next page cid 2 comment cid 275"); + ).toBe("community address 1 next page cid 2 comment cid 275"); // restore mock Pages.prototype.getPage = getPage; }); - test("get multiple subplebbits sorted by new and scroll to multiple pages", async () => { + test("get multiple communities sorted by new and scroll to multiple pages", async () => { const getPageCalledTimes = { - "subplebbit address 1": 0, - "subplebbit address 2": 0, - "subplebbit address 3": 0, + "community address 1": 0, + "community address 2": 0, + "community address 3": 0, }; const getPage = Pages.prototype.getPage; Pages.prototype.getPage = async function (options: { cid: string }) { @@ -609,34 +609,30 @@ describe("feeds", () => { const page: any = { // @ts-ignore nextCid: - this.subplebbit.address + + this.community.address + " next page cid " + - (getPageCalledTimes[this.subplebbit.address] + 1), + (getPageCalledTimes[this.community.address] + 1), comments: [], }; const postCount = 100; let index = 0; // @ts-ignore - let commentStartIndex = getPageCalledTimes[this.subplebbit.address] * postCount; + let commentStartIndex = getPageCalledTimes[this.community.address] * postCount; while (index++ < postCount) { page.comments.push({ timestamp: commentStartIndex + index, cid: cid + " comment cid " + (commentStartIndex + index), - subplebbitAddress: this.subplebbit.address, + communityAddress: this.community.address, }); } // @ts-ignore - getPageCalledTimes[this.subplebbit.address]++; + getPageCalledTimes[this.community.address]++; return page; }; // get feed with 3 sub sorted by new page 1 rendered.rerender({ - subplebbitAddresses: [ - "subplebbit address 1", - "subplebbit address 2", - "subplebbit address 3", - ], + communityAddresses: ["community address 1", "community address 2", "community address 3"], sortType: "new", }); await waitFor(() => rendered.result.current.feed?.length >= postsPerPage); @@ -647,26 +643,26 @@ describe("feeds", () => { expect(rendered.result.current.feed[1].timestamp).toBe(100); expect(rendered.result.current.feed[2].timestamp).toBe(100); expect(rendered.result.current.feed[0].cid).toBe( - "subplebbit address 1 page cid new comment cid 100", + "community address 1 page cid new comment cid 100", ); expect(rendered.result.current.feed[1].cid).toBe( - "subplebbit address 2 page cid new comment cid 100", + "community address 2 page cid new comment cid 100", ); expect(rendered.result.current.feed[2].cid).toBe( - "subplebbit address 3 page cid new comment cid 100", + "community address 3 page cid new comment cid 100", ); // at this point the buffered feed has gotten page 1 from all subs await waitFor( () => - getPageCalledTimes["subplebbit address 1"] === 1 && - getPageCalledTimes["subplebbit address 2"] === 1 && - getPageCalledTimes["subplebbit address 3"] === 1, + getPageCalledTimes["community address 1"] === 1 && + getPageCalledTimes["community address 2"] === 1 && + getPageCalledTimes["community address 3"] === 1, ); - expect(getPageCalledTimes["subplebbit address 1"]).toBe(1); - expect(getPageCalledTimes["subplebbit address 2"]).toBe(1); - expect(getPageCalledTimes["subplebbit address 3"]).toBe(1); + expect(getPageCalledTimes["community address 1"]).toBe(1); + expect(getPageCalledTimes["community address 2"]).toBe(1); + expect(getPageCalledTimes["community address 3"]).toBe(1); // get page 2, the first posts of page 2 await scrollOnePage(); @@ -679,10 +675,10 @@ describe("feeds", () => { ).toBe(92); expect( rendered.result.current.feed[rendered.result.current.feed.length - postsPerPage].cid, - ).toBe("subplebbit address 2 page cid new comment cid 92"); + ).toBe("community address 2 page cid new comment cid 92"); expect( rendered.result.current.feed[rendered.result.current.feed.length - postsPerPage + 1].cid, - ).toBe("subplebbit address 3 page cid new comment cid 92"); + ).toBe("community address 3 page cid new comment cid 92"); // scroll until the next buffered feed that needs to be refilled await scrollOnePage(); @@ -693,13 +689,13 @@ describe("feeds", () => { // at this point the buffered feed has gotten page 2 from all subs await waitFor( () => - getPageCalledTimes["subplebbit address 1"] === 2 && - getPageCalledTimes["subplebbit address 2"] === 2 && - getPageCalledTimes["subplebbit address 3"] === 2, + getPageCalledTimes["community address 1"] === 2 && + getPageCalledTimes["community address 2"] === 2 && + getPageCalledTimes["community address 3"] === 2, ); - expect(getPageCalledTimes["subplebbit address 1"]).toBe(2); - expect(getPageCalledTimes["subplebbit address 2"]).toBe(2); - expect(getPageCalledTimes["subplebbit address 3"]).toBe(2); + expect(getPageCalledTimes["community address 1"]).toBe(2); + expect(getPageCalledTimes["community address 2"]).toBe(2); + expect(getPageCalledTimes["community address 3"]).toBe(2); // get next page, the first posts should all be cids 200 from the buffered feed await scrollOnePage(); @@ -716,19 +712,19 @@ describe("feeds", () => { ).toBe(200); expect( rendered.result.current.feed[rendered.result.current.feed.length - postsPerPage].cid, - ).toBe("subplebbit address 1 next page cid 1 comment cid 200"); + ).toBe("community address 1 next page cid 1 comment cid 200"); expect( rendered.result.current.feed[rendered.result.current.feed.length - postsPerPage + 1].cid, - ).toBe("subplebbit address 2 next page cid 1 comment cid 200"); + ).toBe("community address 2 next page cid 1 comment cid 200"); expect( rendered.result.current.feed[rendered.result.current.feed.length - postsPerPage + 2].cid, - ).toBe("subplebbit address 3 next page cid 1 comment cid 200"); + ).toBe("community address 3 next page cid 1 comment cid 200"); // restore mock Pages.prototype.getPage = getPage; }); - test("get multiple subplebbits with filter and scroll to multiple pages", async () => { + test("get multiple communities with filter and scroll to multiple pages", async () => { // filter only comment cids that contain a '5' const cidMatch5 = (comment: Comment) => !!comment.cid.match("5"); const filter = { @@ -736,11 +732,7 @@ describe("feeds", () => { key: "cid-match-5", }; rendered.rerender({ - subplebbitAddresses: [ - "subplebbit address 1", - "subplebbit address 2", - "subplebbit address 3", - ], + communityAddresses: ["community address 1", "community address 2", "community address 3"], filter, }); await waitFor(() => rendered.result.current.feed?.length >= postsPerPage); @@ -748,13 +740,13 @@ describe("feeds", () => { expect(rendered.result.current.feed.length).toBe(postsPerPage); expect(rendered.result.current.updatedFeed.length).toBe(rendered.result.current.feed.length); expect(rendered.result.current.feed[0].cid).toBe( - "subplebbit address 1 page cid hot comment cid 95", + "community address 1 page cid hot comment cid 95", ); expect(rendered.result.current.feed[1].cid).toBe( - "subplebbit address 2 page cid hot comment cid 95", + "community address 2 page cid hot comment cid 95", ); expect(rendered.result.current.feed[2].cid).toBe( - "subplebbit address 3 page cid hot comment cid 95", + "community address 3 page cid hot comment cid 95", ); // scroll until the next buffered feed that needs to be refilled @@ -775,11 +767,7 @@ describe("feeds", () => { key: "cid-match-5 (2)", }; rendered.rerender({ - subplebbitAddresses: [ - "subplebbit address 1", - "subplebbit address 2", - "subplebbit address 3", - ], + communityAddresses: ["community address 1", "community address 2", "community address 3"], filter: filter2, }); expect(Object.keys(feedsStore.getState().feedsOptions).length).toBe(2); @@ -790,24 +778,20 @@ describe("feeds", () => { key: "cid-match-5", }; rendered.rerender({ - subplebbitAddresses: [ - "subplebbit address 1", - "subplebbit address 2", - "subplebbit address 3", - ], + communityAddresses: ["community address 1", "community address 2", "community address 3"], filter: filter3, }); expect(Object.keys(feedsStore.getState().feedsOptions).length).toBe(2); // still using the cached filter with key 'cid-match-5' expect(rendered.result.current.feed[0].cid).toBe( - "subplebbit address 1 page cid hot comment cid 95", + "community address 1 page cid hot comment cid 95", ); expect(rendered.result.current.feed[1].cid).toBe( - "subplebbit address 2 page cid hot comment cid 95", + "community address 2 page cid hot comment cid 95", ); expect(rendered.result.current.feed[2].cid).toBe( - "subplebbit address 3 page cid hot comment cid 95", + "community address 3 page cid hot comment cid 95", ); }); @@ -818,11 +802,7 @@ describe("feeds", () => { }); rendered.rerender({ - subplebbitAddresses: [ - "subplebbit address 1", - "subplebbit address 2", - "subplebbit address 3", - ], + communityAddresses: ["community address 1", "community address 2", "community address 3"], filter: createCidMatchFilter("13"), }); await waitFor(() => rendered.result.current.feed?.length > 0); @@ -832,11 +812,7 @@ describe("feeds", () => { expect(Object.keys(feedsStore.getState().feedsOptions).length).toBe(1); rendered.rerender({ - subplebbitAddresses: [ - "subplebbit address 1", - "subplebbit address 2", - "subplebbit address 3", - ], + communityAddresses: ["community address 1", "community address 2", "community address 3"], filter: createCidMatchFilter("14"), }); await waitFor(() => rendered.result.current.feed?.length > 0); @@ -847,7 +823,7 @@ describe("feeds", () => { }); test("reset feed", async () => { - rendered.rerender({ subplebbitAddresses: ["subplebbit address 1"] }); + rendered.rerender({ communityAddresses: ["community address 1"] }); await waitFor(() => rendered.result.current.feed?.length === postsPerPage); expect(rendered.result.current.feed.length).toBe(postsPerPage); expect(rendered.result.current.updatedFeed.length).toBe(rendered.result.current.feed.length); @@ -861,13 +837,13 @@ describe("feeds", () => { expect(rendered.result.current.updatedFeed.length).toBe(rendered.result.current.feed.length); }); - test("get feed page 1 and 2 with multiple subplebbits sorted by topAll", async () => { + test("get feed page 1 and 2 with multiple communities sorted by topAll", async () => { // use buffered feeds to be able to wait until the buffered feeds have updated before loading page 2 rendered = renderHook((props: any) => { const feed = useFeed(props); const { bufferedFeeds } = useBufferedFeeds({ feedsOptions: [ - { subplebbitAddresses: props?.subplebbitAddresses, sortType: props?.sortType }, + { communityAddresses: props?.communityAddresses, sortType: props?.sortType }, ], accountName: props?.accountName, }); @@ -876,11 +852,7 @@ describe("feeds", () => { // get feed with 1 sub rendered.rerender({ - subplebbitAddresses: [ - "subplebbit address 1", - "subplebbit address 2", - "subplebbit address 3", - ], + communityAddresses: ["community address 1", "community address 2", "community address 3"], sortType: "topAll", }); // initial state @@ -895,13 +867,13 @@ describe("feeds", () => { await waitFor(() => rendered.result.current.feed.length > 0); expect(rendered.result.current.feed.length).toBe(postsPerPage); expect(rendered.result.current.feed[0].cid).toBe( - "subplebbit address 1 page cid topAll comment cid 100", + "community address 1 page cid topAll comment cid 100", ); expect(rendered.result.current.feed[1].cid).toBe( - "subplebbit address 2 page cid topAll comment cid 100", + "community address 2 page cid topAll comment cid 100", ); expect(rendered.result.current.feed[2].cid).toBe( - "subplebbit address 3 page cid topAll comment cid 100", + "community address 3 page cid topAll comment cid 100", ); expect(rendered.result.current.feed[0].upvoteCount).toBe(100); expect(rendered.result.current.feed[1].upvoteCount).toBe(100); @@ -912,21 +884,21 @@ describe("feeds", () => { await waitFor(() => { bufferedFeedString = JSON.stringify(rendered.result.current.bufferedFeed); return Boolean( - bufferedFeedString.match("subplebbit address 2") && - bufferedFeedString.match("subplebbit address 3"), + bufferedFeedString.match("community address 2") && + bufferedFeedString.match("community address 3"), ); }); - expect(bufferedFeedString).toMatch("subplebbit address 2"); - expect(bufferedFeedString).toMatch("subplebbit address 3"); + expect(bufferedFeedString).toMatch("community address 2"); + expect(bufferedFeedString).toMatch("community address 3"); // the second page first posts should be sub 2 and 3 with the highest upvotes await scrollOnePage(); expect(rendered.result.current.feed[postsPerPage].cid).toMatch( - /subplebbit address (2|3) page cid topAll comment cid 92/, + /community address (2|3) page cid topAll comment cid 92/, ); expect(rendered.result.current.feed[postsPerPage + 1].cid).toMatch( - /subplebbit address (2|3) page cid topAll comment cid 92/, + /community address (2|3) page cid topAll comment cid 92/, ); expect(rendered.result.current.feed[postsPerPage].upvoteCount).toBeGreaterThan(91); expect(rendered.result.current.feed[postsPerPage + 1].upvoteCount).toBeGreaterThan(91); @@ -951,7 +923,7 @@ describe("feeds", () => { const rendered = renderHook(() => useBufferedFeeds({ - feedsOptions: [{ subplebbitAddresses: ["subplebbit address 1"], sortType: "new" }], + feedsOptions: [{ communityAddresses: ["community address 1"], sortType: "new" }], }), ); await new Promise((r) => setTimeout(r, 150)); @@ -972,26 +944,26 @@ describe("feeds", () => { useBufferedFeeds({ feedsOptions: [ { - subplebbitAddresses: [ - "subplebbit address 1", - "subplebbit address 2", - "subplebbit address 3", + communityAddresses: [ + "community address 1", + "community address 2", + "community address 3", ], sortType: "new", }, { - subplebbitAddresses: [ - "subplebbit address 4", - "subplebbit address 5", - "subplebbit address 6", + communityAddresses: [ + "community address 4", + "community address 5", + "community address 6", ], sortType: "topAll", }, { - subplebbitAddresses: [ - "subplebbit address 7", - "subplebbit address 8", - "subplebbit address 9", + communityAddresses: [ + "community address 7", + "community address 8", + "community address 9", ], }, ], @@ -1031,7 +1003,7 @@ describe("feeds", () => { }); rendered.rerender({ - subplebbitAddresses: ["subplebbit address 1"], + communityAddresses: ["community address 1"], sortType: "new", accountName: "custom name", }); @@ -1051,7 +1023,7 @@ describe("feeds", () => { test("get feed and change active account", async () => { const newActiveAccountName = "new active account"; rendered = renderHook((props: any) => { - const feed = useFeed(props || { subplebbitAddresses: [] }); + const feed = useFeed(props || { communityAddresses: [] }); const account = useAccount(); const [bufferedFeed] = useBufferedFeeds( props @@ -1060,7 +1032,7 @@ describe("feeds", () => { ).bufferedFeeds; return { ...feed, ...accountsActions, account, bufferedFeed }; }); - rendered.rerender({ subplebbitAddresses: ["subplebbit address 1"], sortType: "new" }); + rendered.rerender({ communityAddresses: ["community address 1"], sortType: "new" }); // wait for posts to be added, should get full first page await waitFor(() => rendered.result.current.feed.length > 0); @@ -1095,11 +1067,7 @@ describe("feeds", () => { const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); expect(() => { rendered.rerender({ - subplebbitAddresses: [ - "subplebbit address 1", - "subplebbit address 2", - "subplebbit address 3", - ], + communityAddresses: ["community address 1", "community address 2", "community address 3"], sortType: `doesnt exist`, }); }).toThrow(`useFeed sortType argument 'doesnt exist' invalid`); @@ -1112,26 +1080,26 @@ describe("feeds", () => { useBufferedFeeds({ feedsOptions: [ { - subplebbitAddresses: [ - "subplebbit address 1", - "subplebbit address 2", - "subplebbit address 3", + communityAddresses: [ + "community address 1", + "community address 2", + "community address 3", ], sortType: "new", }, { - subplebbitAddresses: [ - "subplebbit address 4", - "subplebbit address 5", - "subplebbit address 6", + communityAddresses: [ + "community address 4", + "community address 5", + "community address 6", ], sortType: `doesnt exist`, }, { - subplebbitAddresses: [ - "subplebbit address 7", - "subplebbit address 8", - "subplebbit address 9", + communityAddresses: [ + "community address 7", + "community address 8", + "community address 9", ], }, ], @@ -1158,7 +1126,7 @@ describe("feeds", () => { page.comments.push({ timestamp: index, cid: cid + " comment cid " + index, - subplebbitAddress: this.subplebbit.address, + communityAddress: this.community.address, }); } return page; @@ -1169,8 +1137,8 @@ describe("feeds", () => { Pages.prototype.getPage = getPage; }); - test(`1 subplebbit, scroll to end of feed, hasMore becomes false`, async () => { - rendered.rerender({ subplebbitAddresses: ["subplebbit address 1"], sortType: "new" }); + test(`1 community, scroll to end of feed, hasMore becomes false`, async () => { + rendered.rerender({ communityAddresses: ["community address 1"], sortType: "new" }); // hasMore should be true before the feed is loaded expect(rendered.result.current.hasMore).toBe(true); expect(typeof rendered.result.current.loadMore).toBe("function"); @@ -1216,13 +1184,9 @@ describe("feeds", () => { ); }); - test(`multiple subplebbits, scroll to end of feed, hasMore becomes false`, async () => { + test(`multiple communities, scroll to end of feed, hasMore becomes false`, async () => { rendered.rerender({ - subplebbitAddresses: [ - "subplebbit address 1", - "subplebbit address 2", - "subplebbit address 3", - ], + communityAddresses: ["community address 1", "community address 2", "community address 3"], sortType: "new", }); // hasMore should be true before the feed is loaded @@ -1278,7 +1242,7 @@ describe("feeds", () => { }); test(`don't increment page number if loaded feed hasn't increased yet`, async () => { - rendered.rerender({ subplebbitAddresses: ["subplebbit address 1"] }); + rendered.rerender({ communityAddresses: ["community address 1"] }); await waitFor(() => rendered.result.current.feed.length > 0); // increment page manually because loadMore can't work that fast @@ -1314,7 +1278,7 @@ describe("feeds", () => { // it can get called with a next cid to fetch the second page if (!cid.match("next")) { throw Error( - `subplebbit.getPage() was called with argument '${cid}', should not get called at all on first page of sort type 'hot'`, + `community.getPage() was called with argument '${cid}', should not get called at all on first page of sort type 'hot'`, ); } return { nextCid: undefined, comments: [] }; @@ -1325,19 +1289,19 @@ describe("feeds", () => { Pages.prototype.getPage = getPage; }); - test(`get feed sorted by hot, don't call subplebbit.getPage() because already included in IPNS record`, async () => { - rendered.rerender({ subplebbitAddresses: ["subplebbit address 1"], sortType: "hot" }); + test(`get feed sorted by hot, don't call community.getPage() because already included in IPNS record`, async () => { + rendered.rerender({ communityAddresses: ["community address 1"], sortType: "hot" }); await waitFor(() => rendered.result.current.feed?.length >= postsPerPage); expect(rendered.result.current.feed?.length).toBe(postsPerPage); }); }); - test(`subplebbit updates while we are scrolling`, async () => { - const update = Subplebbit.prototype.update; - // mock the update method to be able to have access to the updating subplebbit instances - const subplebbits: any = []; - Subplebbit.prototype.update = function () { - subplebbits.push(this); + test(`community updates while we are scrolling`, async () => { + const update = Community.prototype.update; + // mock the update method to be able to have access to the updating community instances + const communities: any = []; + Community.prototype.update = function () { + communities.push(this); return update.bind(this)(); }; @@ -1345,7 +1309,7 @@ describe("feeds", () => { const feed = useFeed(props); const { bufferedFeeds } = useBufferedFeeds({ feedsOptions: [ - { subplebbitAddresses: props?.subplebbitAddresses, sortType: props?.sortType }, + { communityAddresses: props?.communityAddresses, sortType: props?.sortType }, ], accountName: props?.accountName, }); @@ -1354,24 +1318,24 @@ describe("feeds", () => { waitFor = testUtils.createWaitFor(rendered); // get feed with 1 sub - rendered.rerender({ subplebbitAddresses: ["subplebbit address 1"], sortType: "topAll" }); + rendered.rerender({ communityAddresses: ["community address 1"], sortType: "topAll" }); await waitFor(() => rendered.result.current.feed.length > 0); // the first page of loaded and buffered feeds should have laoded expect(rendered.result.current.feed.length).toBe(postsPerPage); expect(rendered.result.current.bufferedFeed.length).toBeGreaterThan(postsPerPage); - // at this point only one subplebbit should have updated a single time - expect(subplebbits.length).toBe(1); - const [subplebbit] = subplebbits; + // at this point only one community should have updated a single time + expect(communities.length).toBe(1); + const [community] = communities; act(() => { - // update the page cids and send a subplebbit update event and wait for buffered feeds to change - subplebbits[0].posts.pageCids = { + // update the page cids and send a community update event and wait for buffered feeds to change + communities[0].posts.pageCids = { hot: "updated page cid hot", topAll: "updated page cid topAll", new: "updated page cid new", }; - subplebbit.emit("update", subplebbit); + community.emit("update", community); }); // wait for the buffered feed to empty (because of the update), then to refill with updated page @@ -1385,7 +1349,7 @@ describe("feeds", () => { "updated page cid topAll comment cid 100", ); - Subplebbit.prototype.update = update; + Community.prototype.update = update; }); describe("getPage only gets called once per pageCid", () => { @@ -1396,7 +1360,7 @@ describe("feeds", () => { Pages.prototype.getPage = async function (options: { cid: string }) { const cid = options?.cid; if (usedPageCids[cid]) { - throw Error(`subplebbit.getPage() already called with argument '${cid}'`); + throw Error(`community.getPage() already called with argument '${cid}'`); } usedPageCids[cid] = true; return getPage.bind(this)(options); @@ -1408,7 +1372,7 @@ describe("feeds", () => { }); test(`store page pages in database`, async () => { - rendered.rerender({ subplebbitAddresses: ["subplebbit address 1"], sortType: "new" }); + rendered.rerender({ communityAddresses: ["community address 1"], sortType: "new" }); await waitFor(() => rendered.result.current.feed?.length >= postsPerPage); expect(rendered.result.current.feed?.length).toBe(postsPerPage); @@ -1418,7 +1382,7 @@ describe("feeds", () => { // render with a fresh empty store to test database persistance const rendered2 = renderHook(() => - useFeed({ subplebbitAddresses: ["subplebbit address 1"], sortType: "new" }), + useFeed({ communityAddresses: ["community address 1"], sortType: "new" }), ); await waitFor(() => rendered2.result.current.feed?.length >= postsPerPage); expect(rendered2.result.current.feed?.length).toBe(postsPerPage); @@ -1426,14 +1390,14 @@ describe("feeds", () => { }); test(`feed doesn't contain blocked addresses`, async () => { - const expectFeedToHaveSubplebbitAddresses = (feed: any[], subplebbitAddresses: string[]) => { - for (const subplebbitAddress of subplebbitAddresses) { - // feed posts are missing a subplebbitAddress expected in `subplebbitAddresses` argument - const feedSubplebbitAddresses = feed.map((feedPost) => feedPost.subplebbitAddress); - expect(feedSubplebbitAddresses).toContain(subplebbitAddress); - // feed posts contain a subplebbitAddress not expected in `subplebbitAddresses` argument + const expectFeedToHaveCommunityAddresses = (feed: any[], communityAddresses: string[]) => { + for (const communityAddress of communityAddresses) { + // feed posts are missing a communityAddress expected in `communityAddresses` argument + const feedCommunityAddresses = feed.map((feedPost) => feedPost.communityAddress); + expect(feedCommunityAddresses).toContain(communityAddress); + // feed posts contain a communityAddress not expected in `communityAddresses` argument for (const feedPost of feed) { - expect(subplebbitAddresses).toContain(feedPost.subplebbitAddress); + expect(communityAddresses).toContain(feedPost.communityAddress); } } return true; @@ -1451,7 +1415,7 @@ describe("feeds", () => { const rendered = renderHook((props: any) => { const [bufferedFeed] = useBufferedFeeds({ - feedsOptions: [{ subplebbitAddresses: props?.subplebbitAddresses, sortType: "new" }], + feedsOptions: [{ communityAddresses: props?.communityAddresses, sortType: "new" }], }).bufferedFeeds; const { blockAddress, unblockAddress } = accountsActions; const account = useAccount(); @@ -1460,51 +1424,51 @@ describe("feeds", () => { const waitFor = testUtils.createWaitFor(rendered); await waitFor(() => typeof rendered.result.current.blockAddress === "function"); - const blockedSubplebbitAddress = "blocked.eth"; - const unblockedSubplebbitAddress = "unblocked-address.eth"; - const blockedAuthorAddress = `${blockedSubplebbitAddress} page cid new author address 1`; + const blockedCommunityAddress = "blocked.eth"; + const unblockedCommunityAddress = "unblocked-address.eth"; + const blockedAuthorAddress = `${blockedCommunityAddress} page cid new author address 1`; // render feed before blocking rendered.rerender({ - subplebbitAddresses: [unblockedSubplebbitAddress, blockedSubplebbitAddress], + communityAddresses: [unblockedCommunityAddress, blockedCommunityAddress], }); // wait until feed contains both blocked and unblocked addresses await waitFor(() => rendered.result.current.bufferedFeed.length > 0); - expectFeedToHaveSubplebbitAddresses(rendered.result.current.bufferedFeed, [ - blockedSubplebbitAddress, - unblockedSubplebbitAddress, + expectFeedToHaveCommunityAddresses(rendered.result.current.bufferedFeed, [ + blockedCommunityAddress, + unblockedCommunityAddress, ]); - // block subplebbit address + // block community address await act(async () => { - await rendered.result.current.blockAddress(blockedSubplebbitAddress); + await rendered.result.current.blockAddress(blockedCommunityAddress); }); await waitFor( () => Object.keys(rendered.result.current.account.blockedAddresses).length === 1 && - expectFeedToHaveSubplebbitAddresses(rendered.result.current.bufferedFeed, [ - unblockedSubplebbitAddress, + expectFeedToHaveCommunityAddresses(rendered.result.current.bufferedFeed, [ + unblockedCommunityAddress, ]), ); - expectFeedToHaveSubplebbitAddresses(rendered.result.current.bufferedFeed, [ - unblockedSubplebbitAddress, + expectFeedToHaveCommunityAddresses(rendered.result.current.bufferedFeed, [ + unblockedCommunityAddress, ]); - // unblock subplebbit address + // unblock community address await act(async () => { - await rendered.result.current.unblockAddress(blockedSubplebbitAddress); + await rendered.result.current.unblockAddress(blockedCommunityAddress); }); await waitFor( () => Object.keys(rendered.result.current.account.blockedAddresses).length === 0 && - expectFeedToHaveSubplebbitAddresses(rendered.result.current.bufferedFeed, [ - blockedSubplebbitAddress, - unblockedSubplebbitAddress, + expectFeedToHaveCommunityAddresses(rendered.result.current.bufferedFeed, [ + blockedCommunityAddress, + unblockedCommunityAddress, ]), ); - expectFeedToHaveSubplebbitAddresses(rendered.result.current.bufferedFeed, [ - blockedSubplebbitAddress, - unblockedSubplebbitAddress, + expectFeedToHaveCommunityAddresses(rendered.result.current.bufferedFeed, [ + blockedCommunityAddress, + unblockedCommunityAddress, ]); // feed has blocked author address before blocking @@ -1544,7 +1508,7 @@ describe("feeds", () => { const rendered = renderHook((props: any) => { const [bufferedFeed] = useBufferedFeeds({ - feedsOptions: [{ subplebbitAddresses: props?.subplebbitAddresses, sortType: "new" }], + feedsOptions: [{ communityAddresses: props?.communityAddresses, sortType: "new" }], }).bufferedFeeds; const { blockCid, unblockCid } = accountsActions; const account = useAccount(); @@ -1554,7 +1518,7 @@ describe("feeds", () => { await waitFor(() => typeof rendered.result.current.blockCid === "function"); // render feed before blocking - rendered.rerender({ subplebbitAddresses: ["subplebbit address 1"] }); + rendered.rerender({ communityAddresses: ["community address 1"] }); // wait until feed contains both blocked and unblocked addresses await waitFor(() => rendered.result.current.bufferedFeed.length > 0); const blockedCid = rendered.result.current.bufferedFeed[0].cid; @@ -1582,7 +1546,7 @@ describe("feeds", () => { // it seems preferable to causing unnecessary rerenders every time an unused block event occurs // cause another feed update to fix the edge case - rendered.rerender({ subplebbitAddresses: ["subplebbit address 1", "subplebbit address 2"] }); + rendered.rerender({ communityAddresses: ["community address 1", "community address 2"] }); await waitFor( () => @@ -1593,11 +1557,11 @@ describe("feeds", () => { expectFeedToHaveCid(rendered.result.current.bufferedFeed, blockedCid); }); - test(`empty subplebbit.posts hasMore is false`, async () => { - const update = Subplebbit.prototype.update; + test(`empty community.posts hasMore is false`, async () => { + const update = Community.prototype.update; const updatedAt = Math.floor(Date.now() / 1000); const emptyPosts: any = { pages: {}, pageCids: {} }; - Subplebbit.prototype.update = async function () { + Community.prototype.update = async function () { await simulateLoadingTime(); this.updatedAt = updatedAt; this.posts = emptyPosts; @@ -1606,24 +1570,24 @@ describe("feeds", () => { rendered = renderHook((props: any) => { const feed = useFeed(props); - const subplebbit = useSubplebbit({ subplebbitAddress: props?.subplebbitAddresses?.[0] }); - return { feed, subplebbit }; + const community = useCommunity({ communityAddress: props?.communityAddresses?.[0] }); + return { feed, community }; }); waitFor = testUtils.createWaitFor(rendered); // get feed with 1 sub with no posts - rendered.rerender({ subplebbitAddresses: ["subplebbit address 1"] }); + rendered.rerender({ communityAddresses: ["community address 1"] }); expect(rendered.result.current.feed.hasMore).toBe(true); await waitFor(() => rendered.result.current.feed.hasMore === false); expect(rendered.result.current.feed.hasMore).toBe(false); expect(rendered.result.current.feed.feed.length).toBe(0); - Subplebbit.prototype.update = update; + Community.prototype.update = update; }); test("posts.pages has 1 post with no next cid, hasMore false", async () => { - const update = Subplebbit.prototype.update; + const update = Community.prototype.update; const updatedAt = Math.floor(Date.now() / 1000); const postsWithNoNextCid: any = { pages: { @@ -1632,7 +1596,7 @@ describe("feeds", () => { { timestamp: 1, cid: "comment cid 1", - subplebbitAddress: "subplebbit address 1", + communityAddress: "community address 1", updatedAt: 1, upvoteCount: 1, }, @@ -1641,7 +1605,7 @@ describe("feeds", () => { }, pageCids: {}, }; - Subplebbit.prototype.update = async function () { + Community.prototype.update = async function () { await simulateLoadingTime(); this.updatedAt = updatedAt; this.posts = postsWithNoNextCid; @@ -1655,7 +1619,7 @@ describe("feeds", () => { waitFor = testUtils.createWaitFor(rendered); // get feed with 1 sub with no posts - rendered.rerender({ subplebbitAddresses: ["subplebbit address 1"] }); + rendered.rerender({ communityAddresses: ["community address 1"] }); expect(rendered.result.current.feed.hasMore).toBe(true); expect(rendered.result.current.feed.feed.length).toBe(0); @@ -1663,15 +1627,15 @@ describe("feeds", () => { expect(rendered.result.current.feed.hasMore).toBe(false); expect(rendered.result.current.feed.feed.length).toBe(1); - Subplebbit.prototype.update = update; + Community.prototype.update = update; }); - test(`subplebbitAddressesWithNewerPosts and reset`, async () => { - const update = Subplebbit.prototype.update; - // mock the update method to be able to have access to the updating subplebbit instances - const subplebbits: any = []; - Subplebbit.prototype.update = function () { - subplebbits.push(this); + test(`communityAddressesWithNewerPosts and reset`, async () => { + const update = Community.prototype.update; + // mock the update method to be able to have access to the updating community instances + const communities: any = []; + Community.prototype.update = function () { + communities.push(this); return update.bind(this)(); }; @@ -1679,7 +1643,7 @@ describe("feeds", () => { const feed = useFeed(props); const { bufferedFeeds } = useBufferedFeeds({ feedsOptions: [ - { subplebbitAddresses: props?.subplebbitAddresses, sortType: props?.sortType }, + { communityAddresses: props?.communityAddresses, sortType: props?.sortType }, ], accountName: props?.accountName, }); @@ -1689,7 +1653,7 @@ describe("feeds", () => { // get feed with 1 sub rendered.rerender({ - subplebbitAddresses: ["subplebbit address 1", "subplebbit address 2"], + communityAddresses: ["community address 1", "community address 2"], sortType: "new", }); await waitFor(() => rendered.result.current.feed.length > 0); @@ -1701,36 +1665,36 @@ describe("feeds", () => { // the first page of loaded and buffered feeds should have laoded expect(rendered.result.current.feed.length).toBe(postsPerPage * 2); expect(rendered.result.current.bufferedFeed.length).toBeGreaterThan(postsPerPage * 2); - expect(rendered.result.current.subplebbitAddressesWithNewerPosts).toEqual([]); - expect(subplebbits.length).toBe(2); + expect(rendered.result.current.communityAddressesWithNewerPosts).toEqual([]); + expect(communities.length).toBe(2); act(() => { // update the subs - subplebbits[0].posts.pageCids = { + communities[0].posts.pageCids = { new: "updated page cid new", }; - subplebbits[0].emit("update", subplebbits[0]); - subplebbits[1].posts.pageCids = { + communities[0].emit("update", communities[0]); + communities[1].posts.pageCids = { new: "updated page cid new", }; - subplebbits[1].emit("update", subplebbits[1]); + communities[1].emit("update", communities[1]); }); - await waitFor(() => rendered.result.current.subplebbitAddressesWithNewerPosts.length === 2); - expect(rendered.result.current.subplebbitAddressesWithNewerPosts).toEqual([ - "subplebbit address 1", - "subplebbit address 2", + await waitFor(() => rendered.result.current.communityAddressesWithNewerPosts.length === 2); + expect(rendered.result.current.communityAddressesWithNewerPosts).toEqual([ + "community address 1", + "community address 2", ]); await act(async () => { await rendered.result.current.reset(); }); - await waitFor(() => rendered.result.current.subplebbitAddressesWithNewerPosts.length === 0); + await waitFor(() => rendered.result.current.communityAddressesWithNewerPosts.length === 0); expect(rendered.result.current.bufferedFeed.length).toBeGreaterThan(postsPerPage); - expect(rendered.result.current.subplebbitAddressesWithNewerPosts).toEqual([]); + expect(rendered.result.current.communityAddressesWithNewerPosts).toEqual([]); - Subplebbit.prototype.update = update; + Community.prototype.update = update; }); test("updated feeds is updated, loaded feeds is not", async () => { @@ -1739,7 +1703,7 @@ describe("feeds", () => { { timestamp: 1, cid: "comment cid 1", - subplebbitAddress: "subplebbit address 1", + communityAddress: "community address 1", updatedAt: 1, upvoteCount: 1, }, @@ -1751,7 +1715,7 @@ describe("feeds", () => { { timestamp: 1, cid: "comment cid 1", - subplebbitAddress: "subplebbit address 1", + communityAddress: "community address 1", updatedAt: 1, upvoteCount: 2, }, @@ -1762,7 +1726,7 @@ describe("feeds", () => { { timestamp: 1, cid: "comment cid 1", - subplebbitAddress: "subplebbit address 1", + communityAddress: "community address 1", updatedAt: 2, upvoteCount: 2, }, @@ -1773,14 +1737,14 @@ describe("feeds", () => { { timestamp: 100, cid: "comment cid 2", - subplebbitAddress: "subplebbit address 1", + communityAddress: "community address 1", updatedAt: 100, upvoteCount: 100, }, { timestamp: 1, cid: "comment cid 1", - subplebbitAddress: "subplebbit address 1", + communityAddress: "community address 1", updatedAt: 3, upvoteCount: 3, }, @@ -1788,10 +1752,10 @@ describe("feeds", () => { }; const pages = [page1, page2, page3, page4]; - const simulateUpdateEvent = Subplebbit.prototype.simulateUpdateEvent; - let subplebbit; - Subplebbit.prototype.simulateUpdateEvent = async function () { - subplebbit = this; + const simulateUpdateEvent = Community.prototype.simulateUpdateEvent; + let community; + Community.prototype.simulateUpdateEvent = async function () { + community = this; this.posts.pages = { hot: pages.shift() }; this.posts.pageCids = {}; this.updatedAt = this.updatedAt ? this.updatedAt + 1 : 1; @@ -1800,10 +1764,10 @@ describe("feeds", () => { this.emit("updatingstatechange", "succeeded"); }; - const subplebbitAddresses = ["subplebbit address 1"]; - rendered.rerender({ subplebbitAddresses }); + const communityAddresses = ["community address 1"]; + rendered.rerender({ communityAddresses }); - // first subplebbit update + // first community update await waitFor(() => rendered.result.current.feed.length === 1); expect(pages.length).toBe(3); expect(rendered.result.current.feed.length).toBe(1); @@ -1816,21 +1780,21 @@ describe("feeds", () => { expect(rendered.result.current.updatedFeed[0].upvoteCount).toBe(1); expect(rendered.result.current.hasMore).toBe(false); - // second subplebbit update (updatedAt doesn't change, so shouldn't update) - subplebbit.simulateUpdateEvent(); + // second community update (updatedAt doesn't change, so shouldn't update) + community.simulateUpdateEvent(); await waitFor( () => - subplebbitsStore.getState().subplebbits["subplebbit address 1"].posts.pages.hot - .comments[0].upvoteCount === 2, + communitiesStore.getState().communities["community address 1"].posts.pages.hot.comments[0] + .upvoteCount === 2, ); expect(pages.length).toBe(2); - // subplebbit in store updated, but the updatedAt didn't change so no change in useFeed().updatedFeed + // community in store updated, but the updatedAt didn't change so no change in useFeed().updatedFeed expect( - subplebbitsStore.getState().subplebbits["subplebbit address 1"].posts.pages.hot.comments[0] + communitiesStore.getState().communities["community address 1"].posts.pages.hot.comments[0] .updatedAt, ).toBe(1); expect( - subplebbitsStore.getState().subplebbits["subplebbit address 1"].posts.pages.hot.comments[0] + communitiesStore.getState().communities["community address 1"].posts.pages.hot.comments[0] .upvoteCount, ).toBe(2); expect(rendered.result.current.feed.length).toBe(1); @@ -1843,8 +1807,8 @@ describe("feeds", () => { expect(rendered.result.current.updatedFeed[0].upvoteCount).toBe(1); expect(rendered.result.current.hasMore).toBe(false); - // third subplebbit update (updatedAt doesn't change, so shouldn't update) - subplebbit.simulateUpdateEvent(); + // third community update (updatedAt doesn't change, so shouldn't update) + community.simulateUpdateEvent(); await waitFor(() => rendered.result.current.updatedFeed[0].updatedAt === 2); expect(pages.length).toBe(1); expect(rendered.result.current.feed.length).toBe(1); @@ -1857,8 +1821,8 @@ describe("feeds", () => { expect(rendered.result.current.updatedFeed[0].upvoteCount).toBe(2); expect(rendered.result.current.hasMore).toBe(false); - // fourth subplebbit update - subplebbit.simulateUpdateEvent(); + // fourth community update + community.simulateUpdateEvent(); await waitFor(() => rendered.result.current.updatedFeed[0].updatedAt === 3); expect(pages.length).toBe(0); expect(rendered.result.current.feed.length).toBe(2); @@ -1873,12 +1837,12 @@ describe("feeds", () => { expect(rendered.result.current.feed[1].cid).toBe("comment cid 2"); expect(rendered.result.current.updatedFeed[1].cid).toBe("comment cid 2"); - Subplebbit.prototype.simulateUpdateEvent = simulateUpdateEvent; + Community.prototype.simulateUpdateEvent = simulateUpdateEvent; }); test("no pageCids, no page.nextCid, use any preloaded page sort", async () => { - const update = Subplebbit.prototype.update; - Subplebbit.prototype.update = async function () { + const update = Community.prototype.update; + Community.prototype.update = async function () { this.updatedAt = Math.floor(Date.now() / 1000); const hotPageCid = this.address + " page cid hot"; this.posts.pages.hot = this.posts.pageToGet(hotPageCid); @@ -1891,31 +1855,31 @@ describe("feeds", () => { this.emit("updatingstatechange", "succeeded"); }; - rendered.rerender({ subplebbitAddresses: ["subplebbit address 1"], sortType: "new" }); + rendered.rerender({ communityAddresses: ["community address 1"], sortType: "new" }); await waitFor(() => rendered.result.current.feed.length > 0); expect(rendered.result.current.feed[0].cid).toBe( - "subplebbit address 1 page cid hot comment cid 100", + "community address 1 page cid hot comment cid 100", ); expect(rendered.result.current.feed.length).toBe(postsPerPage); rendered.rerender({ - subplebbitAddresses: ["subplebbit address 1"], + communityAddresses: ["community address 1"], sortType: "controversialAll", }); await waitFor(() => rendered.result.current.feed.length > 0); expect(rendered.result.current.feed[0].cid).toBe( - "subplebbit address 1 page cid hot comment cid 9", + "community address 1 page cid hot comment cid 9", ); expect(rendered.result.current.feed.length).toBe(postsPerPage); // restore mock - Subplebbit.prototype.update = update; + Community.prototype.update = update; }); describe("accountComments", () => { const sortType = "hot"; - const subplebbitAddress = "subplebbit address 1"; - const subplebbitAddresses = [subplebbitAddress]; + const communityAddress = "community address 1"; + const communityAddresses = [communityAddress]; const accountPostCid1 = "account post cid 1"; const authorAddress = "accountcomments.eth"; const author = { address: authorAddress }; @@ -1932,7 +1896,7 @@ describe("feeds", () => { { // no cid, no updatedAt, is pending timestamp: yearAgo - 2, // very old reply - subplebbitAddress, + communityAddress, // depth: 0, test no depth index: 0, author, @@ -1940,7 +1904,7 @@ describe("feeds", () => { { // no cid, no updatedAt, is pending timestamp: yearAgo - 1, // very old reply - subplebbitAddress: "wrong subplebbit address", + communityAddress: "wrong community address", depth: 0, index: 1, author, @@ -1948,7 +1912,7 @@ describe("feeds", () => { { // no cid, no updatedAt, is pending timestamp: yearAgo, // very old reply - subplebbitAddress, + communityAddress, // is reply, should not appear in feed parentCid: accountPostCid1, postCid: accountPostCid1, @@ -1958,7 +1922,7 @@ describe("feeds", () => { }, { timestamp: now - 2, - subplebbitAddress, + communityAddress, cid: accountPostCid1, // cid received, not pending, but not published by sub owner yet updatedAt: now, depth: 0, @@ -1967,7 +1931,7 @@ describe("feeds", () => { }, { timestamp: now - 1, - subplebbitAddress: "wrong subplebbit address", + communityAddress: "wrong community address", cid: "account post cid 2", // cid received, not pending, but not published by sub owner yet updatedAt: now, depth: 0, @@ -1976,7 +1940,7 @@ describe("feeds", () => { }, { timestamp: now, - subplebbitAddress, + communityAddress, cid: "account reply cid 1", // cid received, not pending, but not published by sub owner yet parentCid: accountPostCid1, postCid: accountPostCid1, @@ -1998,7 +1962,7 @@ describe("feeds", () => { test("prepend changes to append and publish", async () => { // default (prepend) + newerThan Infinity rendered.rerender({ - subplebbitAddresses, + communityAddresses, sortType, accountComments: { newerThan: Infinity }, }); @@ -2013,13 +1977,13 @@ describe("feeds", () => { expect(rendered.result.current.feed.length).toBe(postsPerPage + 2); expect(rendered.result.current.feed[0].cid).toBe(accountPostCid1); expect(rendered.result.current.feed[0].author.address).toBe(authorAddress); - expect(rendered.result.current.feed[0].subplebbitAddress).toBe(subplebbitAddress); + expect(rendered.result.current.feed[0].communityAddress).toBe(communityAddress); expect(rendered.result.current.feed[1].cid).toBe(undefined); expect(rendered.result.current.feed[1].author.address).toBe(authorAddress); - expect(rendered.result.current.feed[1].subplebbitAddress).toBe(subplebbitAddress); + expect(rendered.result.current.feed[1].communityAddress).toBe(communityAddress); expect(rendered.result.current.feed[2].cid).not.toBe(undefined); expect(rendered.result.current.feed[2].author.address).not.toBe(authorAddress); - expect(rendered.result.current.feed[2].subplebbitAddress).toBe(subplebbitAddress); + expect(rendered.result.current.feed[2].communityAddress).toBe(communityAddress); // prepend order should be newest first expect(rendered.result.current.feed[0].timestamp).toBeGreaterThan( rendered.result.current.feed[1].timestamp, @@ -2027,7 +1991,7 @@ describe("feeds", () => { // newerThan 1h rendered.rerender({ - subplebbitAddresses, + communityAddresses, sortType, accountComments: { newerThan: 60 * 60 }, }); @@ -2037,14 +2001,14 @@ describe("feeds", () => { expect(rendered.result.current.feed.length).toBe(postsPerPage + 1); expect(rendered.result.current.feed[0].cid).toBe(accountPostCid1); expect(rendered.result.current.feed[0].author.address).toBe(authorAddress); - expect(rendered.result.current.feed[0].subplebbitAddress).toBe(subplebbitAddress); + expect(rendered.result.current.feed[0].communityAddress).toBe(communityAddress); expect(rendered.result.current.feed[1].cid).not.toBe(undefined); expect(rendered.result.current.feed[1].author.address).not.toBe(authorAddress); - expect(rendered.result.current.feed[1].subplebbitAddress).toBe(subplebbitAddress); + expect(rendered.result.current.feed[1].communityAddress).toBe(communityAddress); // append + newerThan Infinity rendered.rerender({ - subplebbitAddresses, + communityAddresses, sortType, accountComments: { append: true, newerThan: Infinity }, }); @@ -2060,8 +2024,8 @@ describe("feeds", () => { rendered.result.current.feed[rendered.result.current.feed.length - 1].author.address, ).toBe(authorAddress); expect( - rendered.result.current.feed[rendered.result.current.feed.length - 1].subplebbitAddress, - ).toBe(subplebbitAddress); + rendered.result.current.feed[rendered.result.current.feed.length - 1].communityAddress, + ).toBe(communityAddress); expect(rendered.result.current.feed[rendered.result.current.feed.length - 2].cid).toBe( undefined, ); @@ -2069,8 +2033,8 @@ describe("feeds", () => { rendered.result.current.feed[rendered.result.current.feed.length - 2].author.address, ).toBe(authorAddress); expect( - rendered.result.current.feed[rendered.result.current.feed.length - 2].subplebbitAddress, - ).toBe(subplebbitAddress); + rendered.result.current.feed[rendered.result.current.feed.length - 2].communityAddress, + ).toBe(communityAddress); expect(rendered.result.current.feed[rendered.result.current.feed.length - 3].cid).not.toBe( undefined, ); @@ -2078,8 +2042,8 @@ describe("feeds", () => { rendered.result.current.feed[rendered.result.current.feed.length - 3].author.address, ).not.toBe(authorAddress); expect( - rendered.result.current.feed[rendered.result.current.feed.length - 3].subplebbitAddress, - ).toBe(subplebbitAddress); + rendered.result.current.feed[rendered.result.current.feed.length - 3].communityAddress, + ).toBe(communityAddress); // append: true order should be newest last expect( @@ -2091,7 +2055,7 @@ describe("feeds", () => { // publishing a post automatically adds to feed await act(async () => { await accountsActions.publishComment({ - subplebbitAddress, + communityAddress, content: "added to feed", onChallenge: () => {}, onChallengeVerification: () => {}, @@ -2121,7 +2085,7 @@ describe("feeds", () => { }; rendered.rerender({ - subplebbitAddresses, + communityAddresses, sortType, postsPerPage, accountComments: { newerThan: Infinity }, @@ -2141,7 +2105,7 @@ describe("feeds", () => { const content = "published content"; await act(async () => { await accountsActions.publishComment({ - subplebbitAddress, + communityAddress, content, onChallenge: () => {}, onChallengeVerification: () => {}, @@ -2177,7 +2141,7 @@ describe("feeds", () => { test("deleted local account post/reindex disappears from feed accountComments injection immediately", async () => { rendered.rerender({ - subplebbitAddresses, + communityAddresses, sortType, accountComments: { newerThan: Infinity }, }); @@ -2224,12 +2188,12 @@ describe("feeds", () => { }); test("modQueue pendingApproval", async () => { - const subplebbitAddresses = [ - "subplebbit address 1", - "subplebbit address 2", - "subplebbit address 3", + const communityAddresses = [ + "community address 1", + "community address 2", + "community address 3", ]; - rendered.rerender({ subplebbitAddresses, modQueue: ["pendingApproval"] }); + rendered.rerender({ communityAddresses, modQueue: ["pendingApproval"] }); await waitFor(() => rendered.result.current.feed.length > 0); expect(rendered.result.current.feed.length).toBe(postsPerPage); @@ -2241,23 +2205,23 @@ describe("feeds", () => { }); test("modQueue drops approved posts after the page stops returning them", async () => { - const subplebbitAddresses = ["subplebbit address 1"]; - rendered.rerender({ subplebbitAddresses, modQueue: ["pendingApproval"] }); + const communityAddresses = ["community address 1"]; + rendered.rerender({ communityAddresses, modQueue: ["pendingApproval"] }); await waitFor(() => rendered.result.current.feed.length > 0); const removedCid = rendered.result.current.feed[0].cid; - const pageCid = Object.keys(subplebbitsPagesStore.getState().subplebbitsPages).find((cid) => + const pageCid = Object.keys(communitiesPagesStore.getState().communitiesPages).find((cid) => cid.includes("pendingApproval"), ); expect(pageCid).toBeDefined(); await act(async () => { - subplebbitsPagesStore.setState((state: any) => { - const page = state.subplebbitsPages[pageCid as string]; + communitiesPagesStore.setState((state: any) => { + const page = state.communitiesPages[pageCid as string]; return { ...state, - subplebbitsPages: { - ...state.subplebbitsPages, + communitiesPages: { + ...state.communitiesPages, [pageCid as string]: { ...page, comments: page.comments.filter((comment: Comment) => comment.cid !== removedCid), @@ -2278,19 +2242,19 @@ describe("feeds", () => { }); test("modQueue drops posts that stay on the page but lose pendingApproval", async () => { - const subplebbitAddresses = ["subplebbit address 1"]; - rendered.rerender({ subplebbitAddresses, modQueue: ["pendingApproval"] }); + const communityAddresses = ["community address 1"]; + rendered.rerender({ communityAddresses, modQueue: ["pendingApproval"] }); await waitFor(() => rendered.result.current.feed.length > 0); const removedCid = rendered.result.current.feed[0].cid; - const pageCid = Object.keys(subplebbitsPagesStore.getState().subplebbitsPages).find((cid) => + const pageCid = Object.keys(communitiesPagesStore.getState().communitiesPages).find((cid) => cid.includes("pendingApproval"), ); expect(pageCid).toBeDefined(); await act(async () => { - subplebbitsPagesStore.setState((state: any) => { - const page = state.subplebbitsPages[pageCid as string]; + communitiesPagesStore.setState((state: any) => { + const page = state.communitiesPages[pageCid as string]; const nextComments = page.comments.map((comment: Comment) => comment.cid === removedCid ? { ...comment, pendingApproval: undefined } : comment, ); @@ -2303,8 +2267,8 @@ describe("feeds", () => { pendingApproval: undefined, }, }, - subplebbitsPages: { - ...state.subplebbitsPages, + communitiesPages: { + ...state.communitiesPages, [pageCid as string]: { ...page, comments: nextComments, @@ -2324,21 +2288,21 @@ describe("feeds", () => { ).toBe(false); }); - test("modQueue reset refreshes the latest subplebbit snapshot before rebuilding", async () => { - const getSubplebbit = Plebbit.prototype.getSubplebbit; + test("modQueue reset refreshes the latest community snapshot before rebuilding", async () => { + const getCommunity = Plebbit.prototype.getCommunity; let hidePendingApprovalPage = false; - Plebbit.prototype.getSubplebbit = async function (options: { address: string }) { - const subplebbit = await getSubplebbit.call(this, options); + Plebbit.prototype.getCommunity = async function (options: { address: string }) { + const community = await getCommunity.call(this, options); if (hidePendingApprovalPage) { - subplebbit.modQueue.pageCids = {}; + community.modQueue.pageCids = {}; } - return subplebbit; + return community; }; try { rendered.rerender({ - subplebbitAddresses: ["subplebbit address 1"], + communityAddresses: ["community address 1"], modQueue: ["pendingApproval"], }); @@ -2354,13 +2318,13 @@ describe("feeds", () => { await waitFor( () => - !subplebbitsStore.getState().subplebbits["subplebbit address 1"]?.modQueue?.pageCids + !communitiesStore.getState().communities["community address 1"]?.modQueue?.pageCids ?.pendingApproval, ); await waitFor(() => rendered.result.current.feed.length === 0); expect(rendered.result.current.feed).toEqual([]); } finally { - Plebbit.prototype.getSubplebbit = getSubplebbit; + Plebbit.prototype.getCommunity = getCommunity; } }); @@ -2369,12 +2333,12 @@ describe("feeds", () => { // TODO: not implemented // at the moment a comment already inside a loaded feed will ignore all updates from future pages - // test.todo(`if an updated subplebbit page gives a comment already in a loaded feed, replace it with the newest version with updated votes/replies`) + // test.todo(`if an updated community page gives a comment already in a loaded feed, replace it with the newest version with updated votes/replies`) // TODO: not implemented // test.todo(`don't let a malicious sub owner display older posts in top hour/day/week/month/year`) // already implemented but no tests for it because difficult to test - // test.todo(`subplebbits finish loading with 0 posts, hasMore becomes false, but only after finished loading`) + // test.todo(`communities finish loading with 0 posts, hasMore becomes false, but only after finished loading`) }); }); diff --git a/src/hooks/feeds/feeds.ts b/src/hooks/feeds/feeds.ts index 840ebc33..2dc144fa 100644 --- a/src/hooks/feeds/feeds.ts +++ b/src/hooks/feeds/feeds.ts @@ -18,7 +18,7 @@ import { addCommentModerationToComments } from "../../lib/utils/comment-moderati import shallow from "zustand/shallow"; /** - * @param subplebbitAddresses - The addresses of the subplebbits, e.g. ['memes.eth', '12D3KooW...'] + * @param communityAddresses - The addresses of the communities, e.g. ['memes.eth', '12D3KooW...'] * @param sortType - The sorting algo for the feed: 'hot' | 'new' | 'active' | 'topHour' | 'topDay' | 'topWeek' | 'topMonth' | 'topYear' | 'topAll' | 'controversialHour' | 'controversialDay' | 'controversialWeek' | 'controversialMonth' | 'controversialYear' | 'controversialAll' * @param acountName - The nickname of the account, e.g. 'Account 1'. If no accountName is provided, use * the active account. @@ -29,7 +29,7 @@ export function useFeed(options?: UseFeedOptions): UseFeedResult { `useFeed options argument '${options}' not an object`, ); let { - subplebbitAddresses, + communityAddresses, sortType, accountName, postsPerPage, @@ -41,7 +41,7 @@ export function useFeed(options?: UseFeedOptions): UseFeedResult { sortType = getSortType(sortType, newerThan); validator.validateUseFeedArguments( - subplebbitAddresses, + communityAddresses, sortType, accountName, postsPerPage, @@ -53,11 +53,11 @@ export function useFeed(options?: UseFeedOptions): UseFeedResult { const addFeedToStore = useFeedsStore((state) => state.addFeedToStore); const incrementFeedPageNumber = useFeedsStore((state) => state.incrementFeedPageNumber); const resetFeed = useFeedsStore((state) => state.resetFeed); - const uniqueSubplebbitAddresses = useUniqueSorted(subplebbitAddresses); + const uniqueCommunityAddresses = useUniqueSorted(communityAddresses); const feedName = useFeedName( account?.id, sortType, - uniqueSubplebbitAddresses, + uniqueCommunityAddresses, postsPerPage, filter, newerThan, @@ -65,19 +65,19 @@ export function useFeed(options?: UseFeedOptions): UseFeedResult { modQueue, ); const [errors, setErrors] = useState([]); - const subplebbitAddressesWithNewerPosts = useFeedsStore( - (state) => state.feedsSubplebbitAddressesWithNewerPosts[feedName], + const communityAddressesWithNewerPosts = useFeedsStore( + (state) => state.feedsCommunityAddressesWithNewerPosts[feedName], ); // add feed to store useEffect(() => { - if (!uniqueSubplebbitAddresses?.length || !account) { + if (!uniqueCommunityAddresses?.length || !account) { return; } const isBufferedFeed = false; addFeedToStore( feedName, - uniqueSubplebbitAddresses, + uniqueCommunityAddresses, sortType, account, isBufferedFeed, @@ -97,13 +97,13 @@ export function useFeed(options?: UseFeedOptions): UseFeedResult { if (!feedName || typeof hasMore !== "boolean") { hasMore = true; } - if (!subplebbitAddresses?.length) { + if (!communityAddresses?.length) { hasMore = false; } const loadMore = async () => { try { - if (!uniqueSubplebbitAddresses || !account) { + if (!uniqueCommunityAddresses || !account) { throw Error("useFeed cannot load more feed not initalized yet"); } incrementFeedPageNumber(feedName); @@ -116,7 +116,7 @@ export function useFeed(options?: UseFeedOptions): UseFeedResult { const reset = async () => { try { - if (!uniqueSubplebbitAddresses || !account) { + if (!uniqueCommunityAddresses || !account) { throw Error("useFeed cannot reset feed not initalized yet"); } await resetFeed(feedName); @@ -127,11 +127,11 @@ export function useFeed(options?: UseFeedOptions): UseFeedResult { } }; - if (account && subplebbitAddresses?.length) { + if (account && communityAddresses?.length) { log("useFeed", { feedLength: feed?.length || 0, hasMore, - subplebbitAddresses, + communityAddresses, sortType, account, feedsStoreOptions: useFeedsStore.getState().feedsOptions, @@ -156,7 +156,7 @@ export function useFeed(options?: UseFeedOptions): UseFeedResult { bufferedFeed: normalizedBufferedFeed, updatedFeed: normalizedUpdatedFeed, hasMore, - subplebbitAddressesWithNewerPosts: subplebbitAddressesWithNewerPosts || [], + communityAddressesWithNewerPosts: communityAddressesWithNewerPosts || [], loadMore, reset, state, @@ -170,7 +170,7 @@ export function useFeed(options?: UseFeedOptions): UseFeedResult { feedName, hasMore, errors, - subplebbitAddressesWithNewerPosts, + communityAddressesWithNewerPosts, ], ); } @@ -196,27 +196,27 @@ export function useBufferedFeeds(options?: UseBufferedFeedsOptions): UseBuffered // do a bunch of calculations to get feedsOptionsFlattened and feedNames const feedsOpts = feedsOptions; - const { subplebbitAddressesArrays, sortTypes, postsPerPages, filters, newerThans } = + const { communityAddressesArrays, sortTypes, postsPerPages, filters, newerThans } = useMemo(() => { - const subplebbitAddressesArrays = []; + const communityAddressesArrays = []; const sortTypes = []; const postsPerPages = []; const filters = []; const newerThans = []; for (const feedOptions of feedsOpts) { - subplebbitAddressesArrays.push(feedOptions.subplebbitAddresses ?? []); + communityAddressesArrays.push(feedOptions.communityAddresses ?? []); sortTypes.push(getSortType(feedOptions.sortType, feedOptions.newerThan)); postsPerPages.push(feedOptions.postsPerPage); filters.push(feedOptions.filter); newerThans.push(feedOptions.newerThan); } - return { subplebbitAddressesArrays, sortTypes, postsPerPages, filters, newerThans }; + return { communityAddressesArrays, sortTypes, postsPerPages, filters, newerThans }; }, [feedsOpts]); - const uniqueSubplebbitAddressesArrays = useUniqueSortedArrays(subplebbitAddressesArrays); + const uniqueCommunityAddressesArrays = useUniqueSortedArrays(communityAddressesArrays); const feedNames = useFeedNames( account?.id, sortTypes, - uniqueSubplebbitAddressesArrays, + uniqueCommunityAddressesArrays, postsPerPages, filters, newerThans, @@ -232,25 +232,20 @@ export function useBufferedFeeds(options?: UseBufferedFeedsOptions): UseBuffered // add feed to store useEffect(() => { - for (const [i] of uniqueSubplebbitAddressesArrays.entries()) { + for (const [i] of uniqueCommunityAddressesArrays.entries()) { const sortType = sortTypes[i] ?? "hot"; - const uniqueSubplebbitAddresses = uniqueSubplebbitAddressesArrays[i]; + const uniqueCommunityAddresses = uniqueCommunityAddressesArrays[i]; validator.validateFeedSortType(sortType); const feedName = feedNames[i]; - if (!uniqueSubplebbitAddresses || !account) { + if (!uniqueCommunityAddresses || !account) { return; } const fkey = feedName ?? ""; if (!bufferedFeeds[fkey]) { const isBufferedFeed = true; - addFeedToStore( - feedName, - uniqueSubplebbitAddresses, - sortType, - account, - isBufferedFeed, - ).catch((error: unknown) => - log.error("useBufferedFeeds addFeedToStore error", { feedName, error }), + addFeedToStore(feedName, uniqueCommunityAddresses, sortType, account, isBufferedFeed).catch( + (error: unknown) => + log.error("useBufferedFeeds addFeedToStore error", { feedName, error }), ); } } @@ -291,7 +286,7 @@ export function useBufferedFeeds(options?: UseBufferedFeedsOptions): UseBuffered } /** - * Util to find unique and sorted subplebbit addresses for multiple feed options + * Util to find unique and sorted community addresses for multiple feed options */ function useUniqueSortedArrays(stringsArrays?: string[][]) { return useMemo(() => { @@ -316,7 +311,7 @@ function useUniqueSorted(stringsArray?: string[]) { function useFeedName( accountId: string, sortType: string, - uniqueSubplebbitAddresses: string[], + uniqueCommunityAddresses: string[], postsPerPage?: number, filter?: CommentsFilter, newerThan?: number, @@ -332,7 +327,7 @@ function useFeedName( "-" + sortType + "-" + - uniqueSubplebbitAddresses + + uniqueCommunityAddresses + "-" + postsPerPage + "-" + @@ -349,7 +344,7 @@ function useFeedName( }, [ accountId, sortType, - uniqueSubplebbitAddresses, + uniqueCommunityAddresses, postsPerPage, filterKey, newerThan, @@ -362,7 +357,7 @@ function useFeedName( function useFeedNames( accountId: string, sortTypes: (string | undefined)[], - uniqueSubplebbitAddressesArrays: string[][], + uniqueCommunityAddressesArrays: string[][], postsPerPages: (number | undefined)[], filters: (CommentsFilter | undefined)[], newerThans: (number | undefined)[], @@ -375,7 +370,7 @@ function useFeedNames( "-" + (sortTypes[i] ?? "hot") + "-" + - uniqueSubplebbitAddressesArrays[i] + + uniqueCommunityAddressesArrays[i] + "-" + postsPerPages[i] + "-" + @@ -385,7 +380,7 @@ function useFeedNames( ); } return feedNames; - }, [accountId, sortTypes, uniqueSubplebbitAddressesArrays, postsPerPages, filters, newerThans]); + }, [accountId, sortTypes, uniqueCommunityAddressesArrays, postsPerPages, filters, newerThans]); } const NEWER_THAN_LIMITS = [ diff --git a/src/hooks/feeds/index.ts b/src/hooks/feeds/index.ts index 4634cb16..2ab8f864 100644 --- a/src/hooks/feeds/index.ts +++ b/src/hooks/feeds/index.ts @@ -1 +1 @@ -export * from './feeds' +export * from "./feeds"; diff --git a/src/hooks/replies.test.ts b/src/hooks/replies.test.ts index f601d2a4..c31816ef 100644 --- a/src/hooks/replies.test.ts +++ b/src/hooks/replies.test.ts @@ -196,7 +196,7 @@ describe("replies", () => { cid: "comment cid 1", depth: 0, postCid: "p", - subplebbitAddress: "sub", + communityAddress: "sub", replies: { pages: {} }, }; const { result } = renderHook(() => useReplies({ comment, validateOptimistically: false })); @@ -209,7 +209,7 @@ describe("replies", () => { cid: "flat-cid", depth: 0, postCid: "p", - subplebbitAddress: "sub", + communityAddress: "sub", replies: { pages: {} }, }; rendered.rerender({ comment, flat: true, flatDepth: 5 }); @@ -251,22 +251,26 @@ describe("replies", () => { cid: "comment cid 1", depth: 0, postCid: "p", - subplebbitAddress: "sub", + communityAddress: "sub", replies: { pages: {} }, }; const rendered = renderHook(() => useReplies({ comment })); - await act(async () => {}); - const feedNames = Object.keys(repliesStore.getState().feedsOptions); - const feedName = feedNames.find((fn) => fn.includes(comment.cid)); - if (feedName) { + const waitFor = testUtils.createWaitFor(rendered); + await waitFor(() => Object.keys(repliesStore.getState().feedsOptions).length > 0); + const feedName = Object.keys(repliesStore.getState().feedsOptions).find((fn) => + fn.includes(comment.cid), + ); + expect(feedName).toBeDefined(); + + await act(async () => { repliesStore.setState((s: any) => ({ ...s, - feedsHaveMore: { ...s.feedsHaveMore, [feedName]: false }, + feedsHaveMore: { ...s.feedsHaveMore, [feedName!]: false }, })); - rendered.rerender({ comment }); - await act(async () => {}); - expect(rendered.result.current.hasMore).toBe(false); - } + }); + + await waitFor(() => rendered.result.current.hasMore === false); + expect(rendered.result.current.hasMore).toBe(false); }); test("useReplies sortType defaults to best when not provided (branch 31)", async () => { @@ -280,7 +284,7 @@ describe("replies", () => { cid: "comment cid 1", depth: 0, postCid: "p", - subplebbitAddress: "sub", + communityAddress: "sub", replies: { pages: {} }, }; comment.replies.pages.best = Pages.prototype.pageToGet.apply({ comment }, [ @@ -408,7 +412,7 @@ describe("replies", () => { const commentAbc = createMockComment({ cid: "comment cid abc", - subplebbitAddress: "subplebbit address 1", + communityAddress: "community address 1", }); rendered.rerender({ comment: commentAbc, sortType: "new" }); await waitFor(() => rendered.result.current.replies.length === repliesPerPage); @@ -437,7 +441,7 @@ describe("replies", () => { const commentXyz = createMockComment({ cid: "comment cid xyz", - subplebbitAddress: "subplebbit address 1", + communityAddress: "community address 1", }); rendered.rerender({ comment: commentXyz, sortType: "old" }); await waitFor( @@ -562,7 +566,7 @@ describe("replies", () => { { timestamp: 1, cid: this.cid + " topAll reply cid 1", - subplebbitAddress: this.subplebbitAddress, + communityAddress: this.communityAddress, upvoteCount: 1, downvoteCount: 10, author: { address: this.cid + " topAll author address" }, @@ -613,7 +617,7 @@ describe("replies", () => { page.comments.push({ timestamp: page.comments.length + 1, cid: cid + " comment cid " + (page.comments.length + 1), - subplebbitAddress: this.comment.subplebbitAddress, + communityAddress: this.comment.communityAddress, }); } return page; @@ -747,7 +751,7 @@ describe("replies", () => { pageToGet = Pages.prototype.pageToGet; Pages.prototype.pageToGet = function (pageCid) { void (pageCid.match(/\b(best|newFlat|new|oldFlat|old|topAll)\b/)?.[1] || "best"); - const subplebbitAddress = this.subplebbit?.address || this.comment?.subplebbitAddress; + const communityAddress = this.community?.address || this.comment?.communityAddress; const depth = (this.comment.depth || 0) + 1; const page: any = { comments: [] }; const count = 35; @@ -756,7 +760,7 @@ describe("replies", () => { page.comments.push({ timestamp: index, cid: pageCid + " comment cid " + index, - subplebbitAddress, + communityAddress, upvoteCount: index, downvoteCount: 10, author: { address: pageCid + " author address " + index }, @@ -880,10 +884,10 @@ describe("replies", () => { Pages.prototype.pageToGet = function (pageCid) { const pageCidSortType = pageCid.match(/\b(best|newFlat|new|oldFlat|old|topAll)\b/)?.[1] || "best"; - const subplebbitAddress = this.subplebbit?.address || this.comment?.subplebbitAddress; + const communityAddress = this.community?.address || this.comment?.communityAddress; const depth = (this.comment.depth || 0) + 1; const page: any = { - nextCid: subplebbitAddress + " " + pageCid + " - next page cid", + nextCid: communityAddress + " " + pageCid + " - next page cid", comments: [], }; const count = 35; @@ -892,7 +896,7 @@ describe("replies", () => { page.comments.push({ timestamp: index, cid: pageCid + " comment cid " + index, - subplebbitAddress, + communityAddress, upvoteCount: index, downvoteCount: 10, author: { @@ -907,7 +911,7 @@ describe("replies", () => { { timestamp: index + 10, cid: pageCid + " comment cid " + index + " nested 1", - subplebbitAddress, + communityAddress, upvoteCount: index, downvoteCount: 10, author: { @@ -922,7 +926,7 @@ describe("replies", () => { { timestamp: index + 20, cid: pageCid + " comment cid " + index + " nested 2", - subplebbitAddress, + communityAddress, upvoteCount: index, downvoteCount: 10, author: { @@ -1151,14 +1155,14 @@ describe("replies", () => { test("replies.pages has 1 reply with no next cid, hasMore false", async () => { const simulateUpdateEvent = Comment.prototype.simulateUpdateEvent; Comment.prototype.simulateUpdateEvent = async function () { - this.subplebbitAddress = "subplebbit address"; + this.communityAddress = "community address"; this.replies.pages = { best: { comments: [ { timestamp: 1, cid: "reply cid 1", - subplebbitAddress: "subplebbit address", + communityAddress: "community address", updatedAt: 1, upvoteCount: 1, }, @@ -1189,7 +1193,7 @@ describe("replies", () => { { timestamp: 1, cid: "reply cid 1", - subplebbitAddress: "subplebbit address", + communityAddress: "community address", updatedAt: 1, upvoteCount: 1, }, @@ -1201,7 +1205,7 @@ describe("replies", () => { { timestamp: 1, cid: "reply cid 1", - subplebbitAddress: "subplebbit address", + communityAddress: "community address", updatedAt: 1, upvoteCount: 2, }, @@ -1212,7 +1216,7 @@ describe("replies", () => { { timestamp: 1, cid: "reply cid 1", - subplebbitAddress: "subplebbit address", + communityAddress: "community address", updatedAt: 2, upvoteCount: 2, }, @@ -1223,14 +1227,14 @@ describe("replies", () => { { timestamp: 100, cid: "reply cid 2", - subplebbitAddress: "subplebbit address", + communityAddress: "community address", updatedAt: 100, upvoteCount: 100, }, { timestamp: 1, cid: "reply cid 1", - subplebbitAddress: "subplebbit address", + communityAddress: "community address", updatedAt: 3, upvoteCount: 3, }, @@ -1243,7 +1247,7 @@ describe("replies", () => { Comment.prototype.simulateUpdateEvent = async function () { // eslint-disable-next-line @typescript-eslint/no-this-alias -- test mock needs to capture comment reference comment = this; - this.subplebbitAddress = "subplebbit address"; + this.communityAddress = "community address"; this.replies.pages = { best: pages.shift() }; this.replies.pageCids = {}; this.updatedAt = this.updatedAt ? this.updatedAt + 1 : 1; @@ -1341,7 +1345,7 @@ describe("replies", () => { } const pageCidSortType = pageCid.match(/\b(best|newFlat|new|oldFlat|old|topAll)\b/)?.[1] || "best"; - const subplebbitAddress = this.subplebbit?.address || this.comment?.subplebbitAddress; + const communityAddress = this.community?.address || this.comment?.communityAddress; const depth = (this.comment.depth || 0) + 1; const page: any = { comments: [], @@ -1353,7 +1357,7 @@ describe("replies", () => { page.comments.push({ timestamp: index, cid: pageCid + " comment cid " + index, - subplebbitAddress, + communityAddress, upvoteCount: index, downvoteCount: 10, author: { @@ -1368,7 +1372,7 @@ describe("replies", () => { { timestamp: index + 10, cid: pageCid + " comment cid " + index + " nested 1", - subplebbitAddress, + communityAddress, upvoteCount: index, downvoteCount: 10, author: { @@ -1383,7 +1387,7 @@ describe("replies", () => { { timestamp: index + 20, cid: pageCid + " comment cid " + index + " nested 2", - subplebbitAddress, + communityAddress, upvoteCount: index, downvoteCount: 10, author: { @@ -1662,7 +1666,7 @@ describe("replies", () => { { timestamp: 1, cid: this.cid + " - reply cid 1", - subplebbitAddress: this.subplebbitAddress, + communityAddress: this.communityAddress, updatedAt: this.updatedAt, depth: 1, replies: { @@ -1672,7 +1676,7 @@ describe("replies", () => { { timestamp: 2, cid: this.cid + " - reply cid 1 - nested 1", - subplebbitAddress: this.subplebbitAddress, + communityAddress: this.communityAddress, updatedAt: this.updatedAt, depth: 2, }, @@ -1695,7 +1699,7 @@ describe("replies", () => { this.replies.pages.best.comments[0].replies.pages.best.comments.push({ timestamp: 3, cid: this.cid + " - reply cid 1 - nested 2", - subplebbitAddress: this.subplebbitAddress, + communityAddress: this.communityAddress, updatedAt: this.updatedAt, depth: 2, }); @@ -1759,7 +1763,7 @@ describe("replies", () => { { timestamp: 1, cid: this.cid + " - reply cid 1", - subplebbitAddress: this.subplebbitAddress, + communityAddress: this.communityAddress, updatedAt: this.updatedAt, depth: 1, replies: { pages: {} }, @@ -1778,7 +1782,7 @@ describe("replies", () => { { timestamp: 2, cid: this.cid + " - reply cid 1 - nested 1", - subplebbitAddress: this.subplebbitAddress, + communityAddress: this.communityAddress, updatedAt: this.updatedAt, depth: 2, }, @@ -1857,7 +1861,7 @@ describe("replies", () => { } const pageCidSortType = pageCid.match(/\b(best|newFlat|new|oldFlat|old|topAll)\b/)?.[1] || "best"; - const subplebbitAddress = this.subplebbit?.address || this.comment?.subplebbitAddress; + const communityAddress = this.community?.address || this.comment?.communityAddress; const depth = (this.comment?.depth || 0) + 1; const postCid = this.comment?.postCid || this.comment?.cid; const page: any = { @@ -1871,7 +1875,7 @@ describe("replies", () => { timestamp: index, cid: pageCid + " comment cid " + index, postCid, - subplebbitAddress, + communityAddress, upvoteCount: index, downvoteCount: 10, author: { @@ -1887,7 +1891,7 @@ describe("replies", () => { timestamp: index + 10, cid: pageCid + " comment cid " + index + " nested 1", postCid, - subplebbitAddress, + communityAddress, upvoteCount: index, downvoteCount: 10, author: { @@ -1903,7 +1907,7 @@ describe("replies", () => { timestamp: index + 20, cid: pageCid + " comment cid " + index + " nested 2", postCid, - subplebbitAddress, + communityAddress, upvoteCount: index, downvoteCount: 10, author: { @@ -2241,7 +2245,7 @@ describe("replies", () => { // publishing a reply automatically adds to replies feed await act(async () => { await accountsActions.publishComment({ - subplebbitAddress: "subplebbit address", + communityAddress: "community address", parentCid: postCid, postCid, content: "added to feed", @@ -2249,7 +2253,7 @@ describe("replies", () => { onChallengeVerification: () => {}, }); await accountsActions.publishComment({ - subplebbitAddress: "subplebbit address", + communityAddress: "community address", parentCid: reply1Depth2Cid, postCid, content: "added to feed 2", @@ -2386,7 +2390,7 @@ describe("replies", () => { // publishing a reply automatically adds to replies feed await act(async () => { await accountsActions.publishComment({ - subplebbitAddress: "subplebbit address", + communityAddress: "community address", parentCid: postCid, postCid, content: "added to feed", @@ -2394,7 +2398,7 @@ describe("replies", () => { onChallengeVerification: () => {}, }); await accountsActions.publishComment({ - subplebbitAddress: "subplebbit address", + communityAddress: "community address", parentCid: reply1Depth2Cid, postCid, content: "added to feed 2", @@ -2509,7 +2513,7 @@ describe("replies", () => { const content = "published content"; await act(async () => { await accountsActions.publishComment({ - subplebbitAddress: "subplebbit address", + communityAddress: "community address", parentCid: postCid, postCid, content, @@ -2583,7 +2587,7 @@ describe("replies", () => { const content = "published content"; await act(async () => { await accountsActions.publishComment({ - subplebbitAddress: "subplebbit address", + communityAddress: "community address", parentCid: postCid, postCid, content, @@ -2645,7 +2649,7 @@ describe("replies", () => { const content2 = "published content 2"; await act(async () => { await accountsActions.publishComment({ - subplebbitAddress: "subplebbit address", + communityAddress: "community address", parentCid: reply1Depth2Cid, postCid, content: content2, @@ -2793,7 +2797,7 @@ describe("replies", () => { const content = "published content"; await act(async () => { await accountsActions.publishComment({ - subplebbitAddress: "subplebbit address", + communityAddress: "community address", parentCid: postCid, postCid, content, @@ -2841,7 +2845,7 @@ describe("replies", () => { const content2 = "published content 2"; await act(async () => { await accountsActions.publishComment({ - subplebbitAddress: "subplebbit address", + communityAddress: "community address", parentCid: reply1Depth2Cid, postCid, content: content2, @@ -2911,7 +2915,7 @@ describe("replies", () => { const content = "published content"; await act(async () => { await accountsActions.publishComment({ - subplebbitAddress: "subplebbit address", + communityAddress: "community address", parentCid: postCid, postCid, content, @@ -2959,7 +2963,7 @@ describe("replies", () => { const content2 = "published content 2"; await act(async () => { await accountsActions.publishComment({ - subplebbitAddress: "subplebbit address", + communityAddress: "community address", parentCid: reply1Depth2Cid, postCid, content: content2, diff --git a/src/hooks/states.test.ts b/src/hooks/states.test.ts index 0dcce4de..34e94604 100644 --- a/src/hooks/states.test.ts +++ b/src/hooks/states.test.ts @@ -2,12 +2,12 @@ import { act } from "@testing-library/react"; import testUtils, { renderHook } from "../lib/test-utils"; import { useComment, - useSubplebbit, + useCommunity, useFeed, useReplies, setPlebbitJs, useClientsStates, - useSubplebbitsStates, + useCommunitiesStates, useAccountComment, usePublishComment, useAccount, @@ -60,7 +60,7 @@ class Client extends EventEmitter { state = "stopped"; } -const mockCommentSubplebbitAddress = "subplebbit address 1"; +const mockCommentCommunityAddress = "community address 1"; class Comment extends EventEmitter { clients: any = { ipfsGateways: { @@ -95,12 +95,12 @@ class Comment extends EventEmitter { timestamp?: number; updatedAt?: number; replies: Pages; - subplebbitAddress: string; + communityAddress: string; constructor(createCommentOptions: any) { super(); this.cid = createCommentOptions.cid; - this.subplebbitAddress = mockCommentSubplebbitAddress; - this.replies = new Pages(this.subplebbitAddress); + this.communityAddress = mockCommentCommunityAddress; + this.replies = new Pages(this.communityAddress); } async update() { @@ -152,7 +152,7 @@ class Comment extends EventEmitter { ); await simulateLoadingTime(20); - // set states resolving subplebbit address + // set states resolving community address changeClientsStates( this.clients, "ipfsGateways", @@ -179,7 +179,7 @@ class Comment extends EventEmitter { ); await simulateLoadingTime(20); - // set states stop resolving subplebbit address + // set states stop resolving community address changeClientsStates( this.clients, "chainProviders", @@ -193,9 +193,9 @@ class Comment extends EventEmitter { this.replies.pages = { new: { comments: [ - { cid: `${this.cid} reply cid 1`, subplebbitAddress: this.subplebbitAddress }, - { cid: `${this.cid} reply cid 2`, subplebbitAddress: this.subplebbitAddress }, - { cid: `${this.cid} reply cid 3`, subplebbitAddress: this.subplebbitAddress }, + { cid: `${this.cid} reply cid 1`, communityAddress: this.communityAddress }, + { cid: `${this.cid} reply cid 2`, communityAddress: this.communityAddress }, + { cid: `${this.cid} reply cid 3`, communityAddress: this.communityAddress }, ], nextCid: `${this.cid} next replies page cid -`, }, @@ -208,7 +208,7 @@ class Comment extends EventEmitter { (async () => { await simulateLoadingTime(20); - // set states resolving subplebbit address + // set states resolving community address changeClientsStates( this.clients, "chainProviders", @@ -378,9 +378,9 @@ class Pages { }; pages: any = {}; pageCids = {}; - subplebbitAddress: string; - constructor(subplebbitAddress) { - this.subplebbitAddress = subplebbitAddress; + communityAddress: string; + constructor(communityAddress) { + this.communityAddress = communityAddress; } async getPage(options: { cid: string }) { const cid = options?.cid; @@ -431,16 +431,16 @@ class Pages { return { comments: [ - { cid: `${cid} cid 1`, subplebbitAddress: this.subplebbitAddress }, - { cid: `${cid} cid 2`, subplebbitAddress: this.subplebbitAddress }, - { cid: `${cid} cid 3`, subplebbitAddress: this.subplebbitAddress }, + { cid: `${cid} cid 1`, communityAddress: this.communityAddress }, + { cid: `${cid} cid 2`, communityAddress: this.communityAddress }, + { cid: `${cid} cid 3`, communityAddress: this.communityAddress }, ], }; } async validatePage(_page: any) {} } -class Subplebbit extends EventEmitter { +class Community extends EventEmitter { clients: any = { ipfsGateways: { [ipfsGatewayUrl1]: new Client(), @@ -482,7 +482,7 @@ class Subplebbit extends EventEmitter { async update() { (async () => { - // set states resolving subplebbit address + // set states resolving community address this.updatingState = "resolving-address"; this.emit("updatingstatechange", "resolving-address"); changeClientsStates( @@ -501,7 +501,7 @@ class Subplebbit extends EventEmitter { "stopped", ); - // set states fetching subplebbit ipns + // set states fetching community ipns this.updatingState = "fetching-ipns"; this.emit("updatingstatechange", "fetching-ipns"); changeClientsStates( @@ -524,7 +524,7 @@ class Subplebbit extends EventEmitter { ); await simulateLoadingTime(100); - // set states stop fetching subplebbit ipns + // set states stop fetching community ipns this.updatingState = "succeeded"; this.emit("updatingstatechange", "succeeded"); changeClientsStates( @@ -547,14 +547,14 @@ class Subplebbit extends EventEmitter { ); await simulateLoadingTime(100); - // fetched subplebbit ipns + // fetched community ipns this.updatedAt = updatedAt; this.posts.pages = { new: { comments: [ - { cid: `${this.address} cid 1`, subplebbitAddress: this.address }, - { cid: `${this.address} cid 2`, subplebbitAddress: this.address }, - { cid: `${this.address} cid 3`, subplebbitAddress: this.address }, + { cid: `${this.address} cid 1`, communityAddress: this.address }, + { cid: `${this.address} cid 2`, communityAddress: this.address }, + { cid: `${this.address} cid 3`, communityAddress: this.address }, ], nextCid: `${this.address} next page cid -`, }, @@ -565,7 +565,7 @@ class Subplebbit extends EventEmitter { } const createComment = Plebbit.prototype.createComment; -const createSubplebbit = Plebbit.prototype.createSubplebbit; +const createCommunity = Plebbit.prototype.createCommunity; describe("states", () => { beforeAll(async () => { @@ -578,16 +578,16 @@ describe("states", () => { const comment: any = new Comment({ cid }); return comment; }; - Plebbit.prototype.createSubplebbit = async ({ address }: any) => { - const subplebbit: any = new Subplebbit({ address }); - return subplebbit; + Plebbit.prototype.createCommunity = async ({ address }: any) => { + const community: any = new Community({ address }); + return community; }; testUtils.silenceReactWarnings(); }); afterAll(() => { // restore Plebbit.prototype.createComment = createComment; - Plebbit.prototype.createSubplebbit = createSubplebbit; + Plebbit.prototype.createCommunity = createCommunity; testUtils.restoreAll(); }); afterEach(async () => { @@ -616,16 +616,16 @@ describe("states", () => { ); }); - test("useClientsStates asserts invalid subplebbit type (branch 28)", () => { - expect(() => renderHook(() => useClientsStates({ subplebbit: 123 as any }))).toThrow( - /subplebbit argument.*not an object/, + test("useClientsStates asserts invalid community type (branch 28)", () => { + expect(() => renderHook(() => useClientsStates({ community: 123 as any }))).toThrow( + /community argument.*not an object/, ); }); - test("useClientsStates asserts comment and subplebbit both defined (branch 28)", () => { + test("useClientsStates asserts comment and community both defined (branch 28)", () => { expect(() => renderHook(() => - useClientsStates({ comment: { cid: "c" } as any, subplebbit: { address: "a" } as any }), + useClientsStates({ comment: { cid: "c" } as any, community: { address: "a" } as any }), ), ).toThrow(/cannot be defined at the same time/); }); @@ -766,24 +766,24 @@ describe("states", () => { expect(rendered.result.current.states).toEqual({}); }); - test("fetch subplebbit", async () => { - const rendered = renderHook((subplebbitAddress: string) => { - const subplebbit = useSubplebbit({ subplebbitAddress }); + test("fetch community", async () => { + const rendered = renderHook((communityAddress: string) => { + const community = useCommunity({ communityAddress }); const { feed, loadMore } = useFeed({ - subplebbitAddresses: subplebbitAddress ? [subplebbitAddress] : [], + communityAddresses: communityAddress ? [communityAddress] : [], sortType: "new", }); - const { states } = useClientsStates({ subplebbit }); - return { subplebbit, states, feed, loadMore }; + const { states } = useClientsStates({ community }); + return { community, states, feed, loadMore }; }); const waitFor = testUtils.createWaitFor(rendered); - expect(rendered.result.current.subplebbit.address).toBe(undefined); + expect(rendered.result.current.community.address).toBe(undefined); expect(rendered.result.current.states).toEqual({}); // initial state - rendered.rerender("subplebbit address 1"); - await waitFor(() => typeof rendered.result.current.subplebbit.address === "string"); - expect(rendered.result.current.subplebbit.address).toBe("subplebbit address 1"); + rendered.rerender("community address 1"); + await waitFor(() => typeof rendered.result.current.community.address === "string"); + expect(rendered.result.current.community.address).toBe("community address 1"); // states start fetching comment ipfs await waitFor(() => rendered.result.current.states["resolving-address"].length >= 2); @@ -791,7 +791,7 @@ describe("states", () => { "resolving-address": [ethChainProviderUrl1, ethChainProviderUrl2], }); - // states start fetching subplebbit ipns + // states start fetching community ipns await waitFor(() => rendered.result.current.states["fetching-ipns"].length >= 6); expect(rendered.result.current.states).toEqual({ "fetching-ipns": [ @@ -804,16 +804,16 @@ describe("states", () => { ], }); - // wait for subplebbit update - await waitFor(() => typeof rendered.result.current.subplebbit.updatedAt === "number"); - expect(rendered.result.current.subplebbit.updatedAt).toBe(updatedAt); + // wait for community update + await waitFor(() => typeof rendered.result.current.community.updatedAt === "number"); + expect(rendered.result.current.community.updatedAt).toBe(updatedAt); expect(rendered.result.current.states).toEqual({}); // wait for first page await waitFor(() => rendered.result.current.feed.length === 3); expect(rendered.result.current.feed.length).toEqual(3); - // states start fetching subplebbit page + // states start fetching community page await waitFor(() => rendered.result.current.states["fetching-ipfs-page-new"].length >= 6); expect(rendered.result.current.states).toEqual({ "fetching-ipfs-page-new": [ @@ -836,7 +836,7 @@ describe("states", () => { const onChallenge = (challenge: any, comment: Comment) => comment.publishChallengeAnswers(); const onChallengeVerification = vi.fn(); const publishCommentOptions = { - subplebbitAddress: "12D3KooW... states.test", + communityAddress: "12D3KooW... states.test", title: "some title states.test", content: "some content states.test", onChallenge, @@ -860,7 +860,7 @@ describe("states", () => { await rendered.result.current.publishComment(); }); - // wait for resolving subplebbit address (React 19 may batch past intermediate states) + // wait for resolving community address (React 19 may batch past intermediate states) await waitFor( () => rendered.result.current.states["resolving-address"]?.length >= 2 || @@ -958,46 +958,46 @@ describe("states", () => { }); }); - describe("useSubplebbitsStates", () => { + describe("useCommunitiesStates", () => { afterEach(async () => { await testUtils.resetDatabasesAndStores(); }); - test("useSubplebbitsStates asserts invalid options type", () => { - expect(() => renderHook(() => useSubplebbitsStates(123 as any))).toThrow(/not an object/); + test("useCommunitiesStates asserts invalid options type", () => { + expect(() => renderHook(() => useCommunitiesStates(123 as any))).toThrow(/not an object/); }); - test("useSubplebbitsStates with no options (branch 28)", () => { - const rendered = renderHook(() => useSubplebbitsStates()); + test("useCommunitiesStates with no options (branch 28)", () => { + const rendered = renderHook(() => useCommunitiesStates()); expect(rendered.result.current.states).toEqual({}); }); - test("useSubplebbitsStates with subplebbitAddresses undefined (branch 149)", () => { - const rendered = renderHook(() => useSubplebbitsStates({ subplebbitAddresses: undefined })); + test("useCommunitiesStates with communityAddresses undefined (branch 149)", () => { + const rendered = renderHook(() => useCommunitiesStates({ communityAddresses: undefined })); expect(rendered.result.current.states).toEqual({}); }); - test("useSubplebbitsStates asserts subplebbitAddresses not array (branch 144)", () => { + test("useCommunitiesStates asserts communityAddresses not array (branch 144)", () => { expect(() => - renderHook(() => useSubplebbitsStates({ subplebbitAddresses: "not-array" as any })), - ).toThrow(/subplebbitAddresses.*not an array/); + renderHook(() => useCommunitiesStates({ communityAddresses: "not-array" as any })), + ).toThrow(/communityAddresses.*not an array/); }); - test("useSubplebbitsStates asserts subplebbitAddress not string (branch 149)", () => { + test("useCommunitiesStates asserts communityAddress not string (branch 149)", () => { expect(() => - renderHook(() => useSubplebbitsStates({ subplebbitAddresses: ["valid", 123 as any] })), - ).toThrow(/subplebbitAddress.*not a string/); + renderHook(() => useCommunitiesStates({ communityAddresses: ["valid", 123 as any] })), + ).toThrow(/communityAddress.*not a string/); }); test("fetch feed", { retry: 5 }, async () => { - const subplebbitAddresses = [ - "subplebbit address 1", - "subplebbit address 2", - "subplebbit address 3", + const communityAddresses = [ + "community address 1", + "community address 2", + "community address 3", ]; const rendered = testUtils.renderHookWithHistory(() => { - const { states } = useSubplebbitsStates({ subplebbitAddresses }); - const { feed, loadMore } = useFeed({ subplebbitAddresses, sortType: "new" }); + const { states } = useCommunitiesStates({ communityAddresses }); + const { feed, loadMore } = useFeed({ communityAddresses, sortType: "new" }); return { states, feed, loadMore }; }); const waitFor = testUtils.createWaitFor(rendered); @@ -1010,13 +1010,13 @@ describe("states", () => { // states contained resolving address (React 19 may batch past this intermediate state) let _resolvingAddress = false; for (const result of rendered.result.all) { - if (result.states["resolving-address"]?.subplebbitAddresses.length === 3) { + if (result.states["resolving-address"]?.communityAddresses.length === 3) { expect(result.states).toEqual({ "resolving-address": { - subplebbitAddresses: [ - "subplebbit address 1", - "subplebbit address 2", - "subplebbit address 3", + communityAddresses: [ + "community address 1", + "community address 2", + "community address 3", ], clientUrls: ["https://ethchainprovider1.com", "https://ethchainprovider2.com"], }, @@ -1030,13 +1030,13 @@ describe("states", () => { // states contained fetching ipns (React 19 may batch past this intermediate state) let _fetchingIpns = false; for (const result of rendered.result.all) { - if (result.states["fetching-ipns"]?.subplebbitAddresses.length === 3) { + if (result.states["fetching-ipns"]?.communityAddresses.length === 3) { expect(result.states).toEqual({ "fetching-ipns": { - subplebbitAddresses: [ - "subplebbit address 1", - "subplebbit address 2", - "subplebbit address 3", + communityAddresses: [ + "community address 1", + "community address 2", + "community address 3", ], clientUrls: [ "https://ipfsgateway1.com", @@ -1061,13 +1061,13 @@ describe("states", () => { // states contained fetching ipfs page new (React 19 may batch past this intermediate state) let _fetchingIpfsPageNew = false; for (const result of rendered.result.all) { - if (result.states["fetching-ipfs-page-new"]?.subplebbitAddresses.length === 3) { + if (result.states["fetching-ipfs-page-new"]?.communityAddresses.length === 3) { expect(result.states).toEqual({ "fetching-ipfs-page-new": { - subplebbitAddresses: [ - "subplebbit address 1", - "subplebbit address 2", - "subplebbit address 3", + communityAddresses: [ + "community address 1", + "community address 2", + "community address 3", ], clientUrls: [ "https://ipfsgateway1.com", diff --git a/src/hooks/states.ts b/src/hooks/states.ts index e43949cf..a0cd640a 100644 --- a/src/hooks/states.ts +++ b/src/hooks/states.ts @@ -5,18 +5,18 @@ import assert from "assert"; import { UseClientsStatesOptions, UseClientsStatesResult, - UseSubplebbitsStatesOptions, - UseSubplebbitsStatesResult, + UseCommunitiesStatesOptions, + UseCommunitiesStatesResult, } from "../types"; -import { useSubplebbits } from "./subplebbits"; -import { subplebbitPostsCacheExpired } from "../lib/utils"; +import { useCommunities } from "./communities"; +import { communityPostsCacheExpired } from "../lib/utils"; // TODO: implement getting peers const peers = {}; /** * @param comment - The comment to get the states from - * @param subplebbit - The subplebbit to get the states from + * @param community - The community to get the states from * @param acountName - The nickname of the account, e.g. 'Account 1'. If no accountName is provided, use * the active account. */ @@ -25,33 +25,33 @@ export function useClientsStates(options?: UseClientsStatesOptions): UseClientsS options == null || typeof options === "object", `useClientsStates options argument '${options}' not an object`, ); - const { comment, subplebbit } = options ?? {}; + const { comment, community } = options ?? {}; assert( comment == null || typeof comment === "object", `useClientsStates options.comment argument '${comment}' not an object`, ); assert( - subplebbit == null || typeof subplebbit === "object", - `useClientsStates options.subplebbit argument '${subplebbit}' not an object`, + community == null || typeof community === "object", + `useClientsStates options.community argument '${community}' not an object`, ); assert( - !(comment && subplebbit), - `useClientsStates options.comment and options.subplebbit arguments cannot be defined at the same time`, + !(comment && community), + `useClientsStates options.comment and options.community arguments cannot be defined at the same time`, ); - const commentOrSubplebbit = comment || subplebbit; + const commentOrCommunity = comment || community; const states = useMemo(() => { const states: { [state: string]: string[] } = {}; // if comment is newer than 5 minutes, don't show updating state so user knows it finished - if (commentOrSubplebbit?.cid && commentOrSubplebbit.timestamp + 5 * 60 > Date.now() / 1000) { + if (commentOrCommunity?.cid && commentOrCommunity.timestamp + 5 * 60 > Date.now() / 1000) { return states; } - if (!commentOrSubplebbit?.clients) { + if (!commentOrCommunity?.clients) { return states; } - const clients = commentOrSubplebbit?.clients; + const clients = commentOrCommunity?.clients; const addState = (state: string | undefined, clientUrl: string) => { if (!state || state === "stopped") { @@ -64,7 +64,7 @@ export function useClientsStates(options?: UseClientsStatesOptions): UseClientsS }; // dont show state if the data is already fetched - if (!commentOrSubplebbit?.updatedAt || subplebbitPostsCacheExpired(commentOrSubplebbit)) { + if (!commentOrCommunity?.updatedAt || communityPostsCacheExpired(commentOrCommunity)) { for (const clientUrl in clients?.ipfsGateways) { addState(clients.ipfsGateways[clientUrl]?.state, clientUrl); } @@ -87,8 +87,8 @@ export function useClientsStates(options?: UseClientsStatesOptions): UseClientsS } } - // find subplebbit pages and comment replies pages states - const pages = commentOrSubplebbit?.posts || commentOrSubplebbit?.replies; + // find community pages and comment replies pages states + const pages = commentOrCommunity?.posts || commentOrCommunity?.replies; if (pages) { for (const clientType in pages.clients) { for (const sortType in pages.clients[clientType]) { @@ -108,14 +108,14 @@ export function useClientsStates(options?: UseClientsStatesOptions): UseClientsS } log("useClientsStates", { - subplebbitAddress: commentOrSubplebbit?.address, - commentCid: commentOrSubplebbit?.cid, + communityAddress: commentOrCommunity?.address, + commentCid: commentOrCommunity?.cid, states, - commentOrSubplebbit, + commentOrCommunity, }); return states; - }, [commentOrSubplebbit]); + }, [commentOrCommunity]); return useMemo( () => ({ @@ -130,85 +130,85 @@ export function useClientsStates(options?: UseClientsStatesOptions): UseClientsS } /** - * @param subplebbitAddresses - The subplebbit addresses to get the states from + * @param communityAddresses - The community addresses to get the states from * @param acountName - The nickname of the account, e.g. 'Account 1'. If no accountName is provided, use * the active account. */ -export function useSubplebbitsStates( - options?: UseSubplebbitsStatesOptions, -): UseSubplebbitsStatesResult { +export function useCommunitiesStates( + options?: UseCommunitiesStatesOptions, +): UseCommunitiesStatesResult { assert( options == null || typeof options === "object", - `useSubplebbitsStates options argument '${options}' not an object`, + `useCommunitiesStates options argument '${options}' not an object`, ); - const { subplebbitAddresses } = options ?? {}; + const { communityAddresses } = options ?? {}; assert( - subplebbitAddresses == null || Array.isArray(subplebbitAddresses), - `useSubplebbitsStates subplebbitAddresses '${subplebbitAddresses}' not an array`, + communityAddresses == null || Array.isArray(communityAddresses), + `useCommunitiesStates communityAddresses '${communityAddresses}' not an array`, ); - for (const subplebbitAddress of subplebbitAddresses ?? []) { + for (const communityAddress of communityAddresses ?? []) { assert( - typeof subplebbitAddress === "string", - `useSubplebbitsStates subplebbitAddresses '${subplebbitAddresses}' subplebbitAddress '${subplebbitAddress}' not a string`, + typeof communityAddress === "string", + `useCommunitiesStates communityAddresses '${communityAddresses}' communityAddress '${communityAddress}' not a string`, ); } - const { subplebbits } = useSubplebbits({ subplebbitAddresses }); + const { communities } = useCommunities({ communityAddresses }); const states = useMemo(() => { const states: { - [state: string]: { subplebbitAddresses: Set; clientUrls: Set }; + [state: string]: { communityAddresses: Set; clientUrls: Set }; } = {}; - for (const subplebbit of subplebbits) { - if (!subplebbit?.updatingState) { + for (const community of communities) { + if (!community?.updatingState) { continue; } - // dont show subplebbit state if data is already fetched + // dont show community state if data is already fetched if ( - (!subplebbit.updatedAt || subplebbitPostsCacheExpired(subplebbit)) && - subplebbit?.updatingState !== "stopped" && - subplebbit?.updatingState !== "succeeded" + (!community.updatedAt || communityPostsCacheExpired(community)) && + community?.updatingState !== "stopped" && + community?.updatingState !== "succeeded" ) { - if (!states[subplebbit.updatingState]) { - states[subplebbit.updatingState] = { - subplebbitAddresses: new Set(), + if (!states[community.updatingState]) { + states[community.updatingState] = { + communityAddresses: new Set(), clientUrls: new Set(), }; } - states[subplebbit.updatingState].subplebbitAddresses.add(subplebbit.address); + states[community.updatingState].communityAddresses.add(community.address); // find client urls - for (const clientType in subplebbit.clients) { + for (const clientType in community.clients) { if (clientType === "chainProviders") { - for (const chainTicker in subplebbit.clients.chainProviders) { - for (const clientUrl in subplebbit.clients.chainProviders[chainTicker]) { - const state = subplebbit.clients.chainProviders[chainTicker][clientUrl].state; - // TODO: client states should always be the same as subplebbit.updatingState + for (const chainTicker in community.clients.chainProviders) { + for (const clientUrl in community.clients.chainProviders[chainTicker]) { + const state = community.clients.chainProviders[chainTicker][clientUrl].state; + // TODO: client states should always be the same as community.updatingState // but possibly because of a plebbit-js bug they are sometimes not - if (state !== "stopped" && state === subplebbit.updatingState) { - states[subplebbit.updatingState].clientUrls.add(clientUrl); + if (state !== "stopped" && state === community.updatingState) { + states[community.updatingState].clientUrls.add(clientUrl); } } } } else { - for (const clientUrl in subplebbit.clients[clientType]) { - const state = subplebbit.clients[clientType][clientUrl].state; - // TODO: client states should always be the same as subplebbit.updatingState + for (const clientUrl in community.clients[clientType]) { + const state = community.clients[clientType][clientUrl].state; + // TODO: client states should always be the same as community.updatingState // but possibly because of a plebbit-js bug they are sometimes not - if (state !== "stopped" && state === subplebbit.updatingState) { - states[subplebbit.updatingState].clientUrls.add(clientUrl); + if (state !== "stopped" && state === community.updatingState) { + states[community.updatingState].clientUrls.add(clientUrl); } } } } } - // find subplebbit pages states and client urls + // find community pages states and client urls const pagesClientsUrls: { [state: string]: string[] } = {}; - for (const clientType in subplebbit?.posts?.clients) { - for (const sortType in subplebbit.posts.clients[clientType]) { - for (const clientUrl in subplebbit.posts.clients[clientType][sortType]) { - let state = subplebbit.posts.clients[clientType][sortType][clientUrl].state; + for (const clientType in community?.posts?.clients) { + for (const sortType in community.posts.clients[clientType]) { + for (const clientUrl in community.posts.clients[clientType][sortType]) { + let state = community.posts.clients[clientType][sortType][clientUrl].state; if (state !== "stopped") { state += `-page-${sortType}`; if (!pagesClientsUrls[state]) { @@ -219,12 +219,12 @@ export function useSubplebbitsStates( } } } - // add subplebbitAddresses and clientUrls + // add communityAddresses and clientUrls for (const pagesState in pagesClientsUrls) { if (!states[pagesState]) { - states[pagesState] = { subplebbitAddresses: new Set(), clientUrls: new Set() }; + states[pagesState] = { communityAddresses: new Set(), clientUrls: new Set() }; } - states[pagesState].subplebbitAddresses.add(subplebbit.address); + states[pagesState].communityAddresses.add(community.address); pagesClientsUrls[pagesState].forEach((clientUrl: string) => states[pagesState].clientUrls.add(clientUrl), ); @@ -232,23 +232,22 @@ export function useSubplebbitsStates( } // convert sets to arrays - const _states: { [state: string]: { subplebbitAddresses: string[]; clientUrls: string[] } } = - {}; + const _states: { [state: string]: { communityAddresses: string[]; clientUrls: string[] } } = {}; for (const state in states) { _states[state] = { - subplebbitAddresses: [...states[state].subplebbitAddresses], + communityAddresses: [...states[state].communityAddresses], clientUrls: [...states[state].clientUrls], }; } - log("useSubplebbitsStates", { - subplebbitAddresses, + log("useCommunitiesStates", { + communityAddresses, states: _states, - subplebbits, + communities, }); return _states; - }, [subplebbits]); + }, [communities]); return useMemo( () => ({ diff --git a/src/hooks/subplebbits.ts b/src/hooks/subplebbits.ts deleted file mode 100644 index 2799c08f..00000000 --- a/src/hooks/subplebbits.ts +++ /dev/null @@ -1,362 +0,0 @@ -import { useEffect, useState, useMemo } from "react"; -import { useAccount } from "./accounts"; -import validator from "../lib/validator"; -import Logger from "@plebbit/plebbit-logger"; -const log = Logger("bitsocial-react-hooks:subplebbits:hooks"); -import assert from "assert"; -import { - Subplebbit, - SubplebbitStats, - ChainProviders, - UseResolvedSubplebbitAddressOptions, - UseResolvedSubplebbitAddressResult, - UseSubplebbitOptions, - UseSubplebbitResult, - UseSubplebbitsOptions, - UseSubplebbitsResult, - UseSubplebbitStatsOptions, - UseSubplebbitStatsResult, -} from "../types"; -import useInterval from "./utils/use-interval"; -import createStore from "zustand"; -import { resolveEnsTxtRecord } from "../lib/chain"; -import useSubplebbitsStore from "../stores/subplebbits"; -import shallow from "zustand/shallow"; - -/** - * @param subplebbitAddress - The address of the subplebbit, e.g. 'memes.eth', '12D3KooW...', etc - * @param acountName - The nickname of the account, e.g. 'Account 1'. If no accountName is provided, use - * the active account. - */ -export function useSubplebbit(options?: UseSubplebbitOptions): UseSubplebbitResult { - assert( - !options || typeof options === "object", - `useSubplebbit options argument '${options}' not an object`, - ); - const { subplebbitAddress, accountName, onlyIfCached } = options ?? {}; - const account = useAccount({ accountName }); - const subplebbit = useSubplebbitsStore( - (state: any) => state.subplebbits[subplebbitAddress || ""], - ); - const addSubplebbitToStore = useSubplebbitsStore((state: any) => state.addSubplebbitToStore); - const errors = useSubplebbitsStore((state: any) => state.errors[subplebbitAddress || ""]); - - useEffect(() => { - if (!subplebbitAddress || !account) { - return; - } - validator.validateUseSubplebbitArguments(subplebbitAddress, account); - if (!subplebbit && !onlyIfCached) { - // if subplebbit isn't already in store, add it - addSubplebbitToStore(subplebbitAddress, account).catch((error: unknown) => - log.error("useSubplebbit addSubplebbitToStore error", { subplebbitAddress, error }), - ); - } - }, [subplebbitAddress, account?.id]); - - if (account && subplebbitAddress) { - log("useSubplebbit", { subplebbitAddress, subplebbit, account }); - } - - let state = subplebbit?.updatingState || "initializing"; - // force succeeded even if the subplebbit is fecthing a new update - if (subplebbit?.updatedAt) { - state = "succeeded"; - } - - return useMemo( - () => ({ - ...subplebbit, - state, - error: errors?.[errors.length - 1], - errors: errors || [], - }), - [subplebbit, subplebbitAddress, errors], - ); -} - -/** - * @param subplebbitAddress - The address of the subplebbit, e.g. 'memes.eth', '12D3KooW...', etc - * @param acountName - The nickname of the account, e.g. 'Account 1'. If no accountName is provided, use - * the active account. - */ -export function useSubplebbitStats(options?: UseSubplebbitStatsOptions): UseSubplebbitStatsResult { - assert( - !options || typeof options === "object", - `useSubplebbitStats options argument '${options}' not an object`, - ); - const { subplebbitAddress, accountName, onlyIfCached } = options ?? {}; - const account = useAccount({ accountName }); - const subplebbit = useSubplebbit({ subplebbitAddress, onlyIfCached }); - const subplebbitStatsCid = subplebbit?.statsCid; - const subplebbitStats = useSubplebbitsStatsStore( - (state: SubplebbitsStatsState) => state.subplebbitsStats[subplebbitAddress || ""], - ); - const setSubplebbitStats = useSubplebbitsStatsStore( - (state: SubplebbitsStatsState) => state.setSubplebbitStats, - ); - - useEffect(() => { - if (!subplebbitAddress || !subplebbitStatsCid || !account) { - return; - } - (async () => { - let fetchedCid; - try { - fetchedCid = await account.plebbit.fetchCid({ cid: subplebbitStatsCid }); - fetchedCid = JSON.parse(fetchedCid); - setSubplebbitStats(subplebbitAddress, fetchedCid); - } catch (error) { - log.error("useSubplebbitStats plebbit.fetchCid error", { - subplebbitAddress, - subplebbitStatsCid, - subplebbit, - fetchedCid, - error, - }); - } - })(); - }, [subplebbitStatsCid, account?.id, subplebbitAddress, setSubplebbitStats]); - - if (account && subplebbitStatsCid) { - log("useSubplebbitStats", { - subplebbitAddress, - subplebbitStatsCid, - subplebbitStats, - subplebbit, - account, - }); - } - - const state = subplebbitStats ? "succeeded" : "fetching-ipfs"; - - return useMemo( - () => ({ - ...subplebbitStats, - state, - error: undefined, - errors: [], - }), - [subplebbitStats, subplebbitStatsCid, subplebbitAddress], - ); -} - -type SubplebbitsStatsState = { - subplebbitsStats: { [subplebbitAddress: string]: SubplebbitStats }; - setSubplebbitStats: Function; -}; - -const useSubplebbitsStatsStore = createStore((setState: Function) => ({ - subplebbitsStats: {}, - setSubplebbitStats: (subplebbitAddress: string, subplebbitStats: SubplebbitStats) => - setState((state: SubplebbitsStatsState) => ({ - subplebbitsStats: { ...state.subplebbitsStats, [subplebbitAddress]: subplebbitStats }, - })), -})); - -/** - * @param subplebbitAddresses - The addresses of the subplebbits, e.g. ['memes.eth', '12D3KooWA...'] - * @param acountName - The nickname of the account, e.g. 'Account 1'. If no accountName is provided, use - * the active account. - */ -export function useSubplebbits(options?: UseSubplebbitsOptions): UseSubplebbitsResult { - assert( - !options || typeof options === "object", - `useSubplebbits options argument '${options}' not an object`, - ); - const { subplebbitAddresses = [], accountName, onlyIfCached } = options ?? {}; - const addrs = subplebbitAddresses ?? []; - const account = useAccount({ accountName }); - const subplebbits: (Subplebbit | undefined)[] = useSubplebbitsStore( - (state: any) => addrs.map((subplebbitAddress) => state.subplebbits[subplebbitAddress || ""]), - shallow, - ); - const addSubplebbitToStore = useSubplebbitsStore((state: any) => state.addSubplebbitToStore); - - useEffect(() => { - if (!addrs.length || !account) { - return; - } - validator.validateUseSubplebbitsArguments(addrs, account); - if (onlyIfCached) { - return; - } - const uniqueSubplebbitAddresses = new Set(addrs); - for (const subplebbitAddress of uniqueSubplebbitAddresses) { - addSubplebbitToStore(subplebbitAddress, account).catch((error: unknown) => - log.error("useSubplebbits addSubplebbitToStore error", { subplebbitAddress, error }), - ); - } - }, [addrs.toString(), account?.id]); - - if (account && addrs.length) { - log("useSubplebbits", { subplebbitAddresses: addrs, subplebbits, account }); - } - - // succeed if no subplebbits are undefined - const state = subplebbits.indexOf(undefined) === -1 ? "succeeded" : "fetching-ipns"; - - return useMemo( - () => ({ - subplebbits, - state, - error: undefined, - errors: [], - }), - [subplebbits, addrs.toString()], - ); -} - -// TODO: plebbit.listSubplebbits() has been removed, rename this and use event subplebbitschanged instead of polling -/** - * Returns all the owner subplebbits created by plebbit-js by calling plebbit.listSubplebbits() - */ -export function useListSubplebbits() { - const account = useAccount(); - const [subplebbitAddresses, setSubplebbitAddresses] = useState([]); - - const delay = 1000; - const immediate = true; - useInterval( - () => { - const plebbit = account?.plebbit; - if (!plebbit) return; - const newAddrs = Array.isArray(plebbit.subplebbits) ? plebbit.subplebbits : []; - if (newAddrs.toString() !== subplebbitAddresses.toString()) { - log("useListSubplebbits", { subplebbitAddresses }); - setSubplebbitAddresses(newAddrs); - } - }, - delay, - immediate, - ); - - return subplebbitAddresses; -} - -/** - * @param subplebbitAddress - The subplebbit address to resolve to a public key, e.g. 'news.eth' resolves to '12D3KooW...'. - * @param acountName - The nickname of the account, e.g. 'Account 1'. If no accountName is provided, use - * the active account. - */ -// NOTE: useResolvedSubplebbitAddress tests are skipped, if changes are made they must be tested manually -export function useResolvedSubplebbitAddress( - options?: UseResolvedSubplebbitAddressOptions, -): UseResolvedSubplebbitAddressResult { - assert( - !options || typeof options === "object", - `useResolvedSubplebbitAddress options argument '${options}' not an object`, - ); - let { subplebbitAddress, accountName, cache } = options ?? {}; - - // cache by default - if (typeof cache !== "boolean") { - cache = true; - } - - // poll every 15 seconds, about the duration of an eth block - let interval = 15000; - // no point in polling often if caching is on - if (cache) { - interval = 1000 * 60 * 60 * 25; - } - - const account = useAccount({ accountName }); - // possible to use account.plebbit instead of account.plebbitOptions - const chainProviders = account?.plebbitOptions?.chainProviders; - const [resolvedAddress, setResolvedAddress] = useState(); - const [errors, setErrors] = useState([]); - const [state, setState] = useState(); - - let initialState = "initializing"; - // before those defined, nothing can happen - if (options && account && subplebbitAddress) { - initialState = "ready"; - } - - useInterval( - () => { - if (!account || !subplebbitAddress) { - setResolvedAddress(undefined); - setState(undefined); - setErrors((prevErrors) => (prevErrors.length ? [] : prevErrors)); - return; - } - - // address isn't a crypto domain, can't be resolved - if (!subplebbitAddress?.includes(".")) { - if (state !== "failed") { - setErrors([Error("not a crypto domain")]); - setState("failed"); - setResolvedAddress(undefined); - } - return; - } - - // only support resolving '.eth' for now - if (!subplebbitAddress?.endsWith(".eth")) { - if (state !== "failed") { - setErrors([Error("crypto domain type unsupported")]); - setState("failed"); - setResolvedAddress(undefined); - } - return; - } - - (async () => { - try { - setState("resolving"); - const res = await resolveSubplebbitAddress(subplebbitAddress, chainProviders); - setState("succeeded"); - if (res !== resolvedAddress) { - setResolvedAddress(res); - } - } catch (error: any) { - setErrors([...errors, error]); - setState("failed"); - setResolvedAddress(undefined); - log.error("useResolvedSubplebbitAddress resolveSubplebbitAddress error", { - subplebbitAddress, - chainProviders, - error, - }); - } - })(); - }, - interval, - true, - [subplebbitAddress, chainProviders], - ); - - // only support ENS at the moment - const chainProvider = chainProviders?.["eth"]; - - // log('useResolvedSubplebbitAddress', {subplebbitAddress, state, errors, resolvedAddress, chainProviders}) - return { - resolvedAddress, - chainProvider, - state: state || initialState, - error: errors[errors.length - 1], - errors, - }; -} - -// NOTE: resolveSubplebbitAddress tests are skipped, if changes are made they must be tested manually -export const resolveSubplebbitAddress = async ( - subplebbitAddress: string, - chainProviders: ChainProviders, -) => { - let resolvedSubplebbitAddress; - if (subplebbitAddress.endsWith(".eth")) { - resolvedSubplebbitAddress = await resolveEnsTxtRecord( - subplebbitAddress, - "subplebbit-address", - "eth", - chainProviders?.["eth"]?.urls?.[0], - chainProviders?.["eth"]?.chainId, - ); - } else { - throw Error(`resolveSubplebbitAddress invalid subplebbitAddress '${subplebbitAddress}'`); - } - return resolvedSubplebbitAddress; -}; diff --git a/src/index.ts b/src/index.ts index 1dc47d22..46135b07 100644 --- a/src/index.ts +++ b/src/index.ts @@ -22,7 +22,7 @@ import { useAccountEdits, useEditedComment, useNotifications, - useAccountSubplebbits, + useAccountCommunities, usePubsubSubscribe, } from "./hooks/accounts"; @@ -32,13 +32,13 @@ import { useComment, useComments, useValidateComment } from "./hooks/comments"; // replies import { useReplies } from "./hooks/replies"; -// subplebbits +// communities import { - useSubplebbit, - useSubplebbits, - useSubplebbitStats, - useResolvedSubplebbitAddress, -} from "./hooks/subplebbits"; + useCommunity, + useCommunities, + useCommunityStats, + useResolvedCommunityAddress, +} from "./hooks/communities"; // feeds import { useFeed, useBufferedFeeds } from "./hooks/feeds"; @@ -60,10 +60,10 @@ import { useBlock, usePublishComment, usePublishVote, - useCreateSubplebbit, + useCreateCommunity, usePublishCommentEdit, usePublishCommentModeration, - usePublishSubplebbitEdit, + usePublishCommunityEdit, } from "./hooks/actions"; // actions that don't have their own hooks yet @@ -76,11 +76,11 @@ import { setAccountsOrder, importAccount, exportAccount, - deleteSubplebbit, + deleteCommunity, } from "./stores/accounts/accounts-actions"; // states -import { useClientsStates, useSubplebbitsStates } from "./hooks/states"; +import { useClientsStates, useCommunitiesStates } from "./hooks/states"; // plebbit-rpc import { usePlebbitRpcSettings } from "./hooks/plebbit-rpc"; @@ -112,7 +112,7 @@ export { useAccountVotes, useAccountVote, useAccountEdits, - useAccountSubplebbits, + useAccountCommunities, useNotifications, usePubsubSubscribe, // comments @@ -122,11 +122,11 @@ export { useValidateComment, // replies useReplies, - // subplebbits - useSubplebbit, - useSubplebbits, - useSubplebbitStats, - useResolvedSubplebbitAddress, + // communities + useCommunity, + useCommunities, + useCommunityStats, + useResolvedCommunityAddress, // authors useAuthor, useAuthorComments, @@ -145,8 +145,8 @@ export { usePublishVote, usePublishCommentEdit, usePublishCommentModeration, - usePublishSubplebbitEdit, - useCreateSubplebbit, + usePublishCommunityEdit, + useCreateCommunity, // actions that don't have their own hooks yet createAccount, deleteAccount, @@ -156,10 +156,10 @@ export { setAccountsOrder, importAccount, exportAccount, - deleteSubplebbit, + deleteCommunity, // states useClientsStates, - useSubplebbitsStates, + useCommunitiesStates, // plebbit-rpc usePlebbitRpcSettings, // chain @@ -186,7 +186,7 @@ const hooks = { useAccountVotes, useAccountVote, useAccountEdits, - useAccountSubplebbits, + useAccountCommunities, useNotifications, usePubsubSubscribe, // comments @@ -196,11 +196,11 @@ const hooks = { useValidateComment, // replies useReplies, - // subplebbits - useSubplebbit, - useSubplebbits, - useSubplebbitStats, - useResolvedSubplebbitAddress, + // communities + useCommunity, + useCommunities, + useCommunityStats, + useResolvedCommunityAddress, // authors useAuthor, useAuthorComments, @@ -219,8 +219,8 @@ const hooks = { usePublishVote, usePublishCommentEdit, usePublishCommentModeration, - usePublishSubplebbitEdit, - useCreateSubplebbit, + usePublishCommunityEdit, + useCreateCommunity, // actions that don't have their own hooks yet createAccount, deleteAccount, @@ -230,10 +230,10 @@ const hooks = { setAccountsOrder, importAccount, exportAccount, - deleteSubplebbit, + deleteCommunity, // states useClientsStates, - useSubplebbitsStates, + useCommunitiesStates, // plebbit-rpc usePlebbitRpcSettings, // chain diff --git a/src/lib/chain/chain.test.ts b/src/lib/chain/chain.test.ts index b74ed860..233b37d9 100644 --- a/src/lib/chain/chain.test.ts +++ b/src/lib/chain/chain.test.ts @@ -1,4 +1,4 @@ -import {getNftImageUrl, validateEthWalletViem, getWalletMessageToSign} from '.' +import { getNftImageUrl, validateEthWalletViem, getWalletMessageToSign } from "."; import { getEthWalletFromPlebbitPrivateKey, getSolWalletFromPlebbitPrivateKey, @@ -6,199 +6,237 @@ import { getSolPrivateKeyFromPlebbitPrivateKey, validateEthWallet, validateSolWallet, -} from '../..' +} from "../.."; const avatarNft1 = { - chainTicker: 'eth', - address: '0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d', // the contract address of the nft + chainTicker: "eth", + address: "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d", // the contract address of the nft id: 100, // the nft number 100 in the colletion -} +}; const avatarNft2 = { - chainTicker: 'matic', - address: '0xf6d8e606c862143556b342149a7fe0558c220375', // the contract address of the nft + chainTicker: "matic", + address: "0xf6d8e606c862143556b342149a7fe0558c220375", // the contract address of the nft id: 100, // the nft number 100 in the colletion -} +}; -const ipfsGatewayUrl = 'https://cloudflare-ipfs.com' +const ipfsGatewayUrl = "https://cloudflare-ipfs.com"; const chainProviders = { eth: { // default should not use a url, but rather ethers.js default provider - urls: ['ethers.js'], + urls: ["ethers.js"], chainId: 1, }, avax: { - urls: ['https://api.avax.network/ext/bc/C/rpc'], + urls: ["https://api.avax.network/ext/bc/C/rpc"], chainId: 43114, }, matic: { - urls: ['https://polygon-rpc.com'], + urls: ["https://polygon-rpc.com"], chainId: 137, }, -} +}; -const plebbitPrivateKey = 'mV8GRU5TGScen7UYZOuNQQ1CKe2G46DCc60moM1yLF4' -const authorAddress = 'authoraddress.eth' -const walletTimestamp = 1740000000 +const plebbitPrivateKey = "mV8GRU5TGScen7UYZOuNQQ1CKe2G46DCc60moM1yLF4"; +const authorAddress = "authoraddress.eth"; +const walletTimestamp = 1740000000; -describe('chain', () => { - describe('nft', () => { - const timeout = 30000 +describe("chain", () => { + describe("nft", () => { + const timeout = 30000; // skip because uses internet and not deterministic // also cache and pending is difficult to test without console logging it - test.skip('getNftImageUrl (cache and pending)', {timeout}, async () => { + test.skip("getNftImageUrl (cache and pending)", { timeout }, async () => { // const url = await getNftImageUrl(avatarNft1, ipfsGatewayUrl, chainProviders) // console.log(url) // const cachedUrl = await getNftImageUrl(avatarNft1, ipfsGatewayUrl, chainProviders) // console.log(cachedUrl) // const res = await Promise.all([getNftImageUrl(avatarNft2, ipfsGatewayUrl, chainProviders), getNftImageUrl(avatarNft2, ipfsGatewayUrl, chainProviders)]) // console.log(res) - }) - }) + }); + }); - describe('eth wallet', () => { - let wallet, privateKey + describe("eth wallet", () => { + let wallet, privateKey; beforeAll(async () => { - privateKey = await getEthPrivateKeyFromPlebbitPrivateKey(plebbitPrivateKey) - const dateNow = Date.now - Date.now = () => walletTimestamp * 1000 - wallet = await getEthWalletFromPlebbitPrivateKey(plebbitPrivateKey, authorAddress) - Date.now = dateNow - }) - - test('getWalletMessageToSign', () => { - const string = getWalletMessageToSign(authorAddress, walletTimestamp) - const json = JSON.parse(string) - expect(json.domainSeparator).toBe('plebbit-author-wallet') - expect(json.authorAddress).toBe(authorAddress) - expect(json.authorAddress).not.toBe(undefined) - expect(json.timestamp).toBe(walletTimestamp) - expect(json.timestamp).not.toBe(undefined) - expect(string).toBe(JSON.stringify(json)) - }) - - test('getEthWalletFromPlebbitPrivateKey', async () => { - expect(wallet.timestamp).toBe(walletTimestamp) - expect(wallet.address).toBe('0x37BC48124fDf985DC3983E2e8414606D4a996ED7') - expect(wallet.privateKey).toBe(undefined) - expect(privateKey).toBe('0x995f06454e5319271e9fb51864eb8d410d4229ed86e3a0c273ad26a0cd722c5e') - expect(wallet.signature.type).toBe('eip191') + privateKey = await getEthPrivateKeyFromPlebbitPrivateKey(plebbitPrivateKey); + const dateNow = Date.now; + Date.now = () => walletTimestamp * 1000; + wallet = await getEthWalletFromPlebbitPrivateKey(plebbitPrivateKey, authorAddress); + Date.now = dateNow; + }); + + test("getWalletMessageToSign", () => { + const string = getWalletMessageToSign(authorAddress, walletTimestamp); + const json = JSON.parse(string); + expect(json.domainSeparator).toBe("plebbit-author-wallet"); + expect(json.authorAddress).toBe(authorAddress); + expect(json.authorAddress).not.toBe(undefined); + expect(json.timestamp).toBe(walletTimestamp); + expect(json.timestamp).not.toBe(undefined); + expect(string).toBe(JSON.stringify(json)); + }); + + test("getEthWalletFromPlebbitPrivateKey", async () => { + expect(wallet.timestamp).toBe(walletTimestamp); + expect(wallet.address).toBe("0x37BC48124fDf985DC3983E2e8414606D4a996ED7"); + expect(wallet.privateKey).toBe(undefined); + expect(privateKey).toBe("0x995f06454e5319271e9fb51864eb8d410d4229ed86e3a0c273ad26a0cd722c5e"); + expect(wallet.signature.type).toBe("eip191"); expect(wallet.signature.signature).toBe( - '0xea38d758767f746fa73761a0e5c60a810ee6762ab2d6fd0d9d3390d9e5e60f304c91aebb91f4f7337321cebbfbbf8206ee4ec92909f136ba5ff546322434a90c1b' - ) - }) + "0xea38d758767f746fa73761a0e5c60a810ee6762ab2d6fd0d9d3390d9e5e60f304c91aebb91f4f7337321cebbfbbf8206ee4ec92909f136ba5ff546322434a90c1b", + ); + }); - test('validateEthWallet', async () => { + test("validateEthWallet", async () => { // good signature - await validateEthWallet(wallet, authorAddress) + await validateEthWallet(wallet, authorAddress); // make sure viem also works - await validateEthWalletViem(wallet, authorAddress) + await validateEthWalletViem(wallet, authorAddress); // bad signatures - await expect(validateEthWallet({...wallet, timestamp: wallet.timestamp + 1}, authorAddress)).rejects.toThrow('wallet address does not equal signature address') - await expect(validateEthWallet(wallet, 'invalidauthoraddress.eth')).rejects.toThrow('wallet address does not equal signature address') - await expect(validateEthWallet({...wallet, timestamp: undefined}, authorAddress)).rejects.toThrow( - `validateEthWallet invalid wallet.timestamp 'undefined' not a number` - ) - await expect(validateEthWallet({...wallet, signature: undefined}, authorAddress)).rejects.toThrow(`validateEthWallet invalid wallet.signature 'undefined'`) - await expect(validateEthWallet({...wallet, signature: {type: 'eip191'}}, authorAddress)).rejects.toThrow( - `validateEthWallet invalid wallet.signature.signature 'undefined'` - ) - await expect(validateEthWallet({...wallet, signature: {}}, authorAddress)).rejects.toThrow(`validateEthWallet invalid wallet.signature.signature 'undefined'`) - await expect(validateEthWallet({...wallet, address: undefined}, authorAddress)).rejects.toThrow(`validateEthWallet invalid wallet.address 'undefined'`) - await expect(validateEthWallet({...wallet, address: '0x0000000000000000000000000000000000000000'}, authorAddress)).rejects.toThrow( - 'wallet address does not equal signature address' - ) - }) - - test('validateEthWallet fixture wallet', async () => { - const authorAddress = '12D3KooWRLHxva6Mrt2fxuL4hMeGJCs8erHAAoXCzPGLsdLpdvrF' + await expect( + validateEthWallet({ ...wallet, timestamp: wallet.timestamp + 1 }, authorAddress), + ).rejects.toThrow("wallet address does not equal signature address"); + await expect(validateEthWallet(wallet, "invalidauthoraddress.eth")).rejects.toThrow( + "wallet address does not equal signature address", + ); + await expect( + validateEthWallet({ ...wallet, timestamp: undefined }, authorAddress), + ).rejects.toThrow(`validateEthWallet invalid wallet.timestamp 'undefined' not a number`); + await expect( + validateEthWallet({ ...wallet, signature: undefined }, authorAddress), + ).rejects.toThrow(`validateEthWallet invalid wallet.signature 'undefined'`); + await expect( + validateEthWallet({ ...wallet, signature: { type: "eip191" } }, authorAddress), + ).rejects.toThrow(`validateEthWallet invalid wallet.signature.signature 'undefined'`); + await expect(validateEthWallet({ ...wallet, signature: {} }, authorAddress)).rejects.toThrow( + `validateEthWallet invalid wallet.signature.signature 'undefined'`, + ); + await expect( + validateEthWallet({ ...wallet, address: undefined }, authorAddress), + ).rejects.toThrow(`validateEthWallet invalid wallet.address 'undefined'`); + await expect( + validateEthWallet( + { ...wallet, address: "0x0000000000000000000000000000000000000000" }, + authorAddress, + ), + ).rejects.toThrow("wallet address does not equal signature address"); + }); + + test("validateEthWallet fixture wallet", async () => { + const authorAddress = "12D3KooWRLHxva6Mrt2fxuL4hMeGJCs8erHAAoXCzPGLsdLpdvrF"; const wallet = { - address: '0x172bb210Ebf51882b63d59609A7BC5c70ce84311', + address: "0x172bb210Ebf51882b63d59609A7BC5c70ce84311", timestamp: 1758422293, signature: { - signature: '0x0d2a091975bcaa4895eb532a74bdef7060db7980ec7bed47812a3e26d5138ea712b890151c117d5e28739b40303b186dc58483065e7390238bd9902e88dbd1071c', - type: 'eip191', + signature: + "0x0d2a091975bcaa4895eb532a74bdef7060db7980ec7bed47812a3e26d5138ea712b890151c117d5e28739b40303b186dc58483065e7390238bd9902e88dbd1071c", + type: "eip191", }, - } - await validateEthWallet(wallet, authorAddress) - await validateEthWalletViem(wallet, authorAddress) - }) - - test('fixture wallet 2', async () => { - const plebbitPrivateKey = 'Q2dsIzBWgHZuof0Aq1KhtMhmW2z5gM8NYY0NL+daBcI' - const authorAddress = '12D3KooWNzFJQ7CCcSCZNg7925WWHMzqVS4qe663PfQ3uBNCHZQb' + }; + await validateEthWallet(wallet, authorAddress); + await validateEthWalletViem(wallet, authorAddress); + }); + + test("fixture wallet 2", async () => { + const plebbitPrivateKey = "Q2dsIzBWgHZuof0Aq1KhtMhmW2z5gM8NYY0NL+daBcI"; + const authorAddress = "12D3KooWNzFJQ7CCcSCZNg7925WWHMzqVS4qe663PfQ3uBNCHZQb"; const wallet = { - address: '0x9097084f571AF3BFcc64E4dcA33FB3223071E4aB', + address: "0x9097084f571AF3BFcc64E4dcA33FB3223071E4aB", timestamp: 1759958639, signature: { - signature: '0x1212ac6953a8d5e5adfc6ec9964042d9b3fc4241a72bfabbccef26cd74d35be5029485b2c1187c3423f05acf7f49694dfa00480c5a203ba08c9773b4517054e71b', - type: 'eip191', + signature: + "0x1212ac6953a8d5e5adfc6ec9964042d9b3fc4241a72bfabbccef26cd74d35be5029485b2c1187c3423f05acf7f49694dfa00480c5a203ba08c9773b4517054e71b", + type: "eip191", }, - } - - const dateNow = Date.now - Date.now = () => wallet.timestamp * 1000 - const generatedWallet = await getEthWalletFromPlebbitPrivateKey(plebbitPrivateKey, authorAddress) - Date.now = dateNow - - expect(wallet.address).toBe(generatedWallet.address) - expect(wallet.timestamp).toBe(generatedWallet.timestamp) - expect(wallet.signature.signature).toBe(generatedWallet.signature.signature) - - await validateEthWallet(wallet, authorAddress) - await validateEthWalletViem(wallet, authorAddress) - }) - }) - - describe('sol wallet', () => { - let wallet, privateKey + }; + + const dateNow = Date.now; + Date.now = () => wallet.timestamp * 1000; + const generatedWallet = await getEthWalletFromPlebbitPrivateKey( + plebbitPrivateKey, + authorAddress, + ); + Date.now = dateNow; + + expect(wallet.address).toBe(generatedWallet.address); + expect(wallet.timestamp).toBe(generatedWallet.timestamp); + expect(wallet.signature.signature).toBe(generatedWallet.signature.signature); + + await validateEthWallet(wallet, authorAddress); + await validateEthWalletViem(wallet, authorAddress); + }); + }); + + describe("sol wallet", () => { + let wallet, privateKey; beforeAll(async () => { - privateKey = await getSolPrivateKeyFromPlebbitPrivateKey(plebbitPrivateKey) - const dateNow = Date.now - Date.now = () => walletTimestamp * 1000 - wallet = await getSolWalletFromPlebbitPrivateKey(plebbitPrivateKey, authorAddress) - Date.now = dateNow - }) - - test('getSolWalletFromPlebbitPrivateKey', async () => { - expect(wallet.timestamp).toBe(walletTimestamp) - expect(wallet.address).toBe('AzAfDLMxbptaq5Ppy4BK5aEsEzvTYNFAub5ffewbSdn9') - expect(wallet.privateKey).toBe(undefined) - expect(privateKey).toBe('44rJnvSKZwF6qMrc49MVe4KqcugR8zc8B4i1yo9iXrvKsf6FAFB7x1dSNdbAqqga4xvpU7VmnKRkwyvQWxrcBmGV') - expect(wallet.signature.type).toBe('sol') - expect(wallet.signature.signature).toBe('4A5VKfweqJMxj3mrFXDEgfxtQBJDYEgfg5BNKKaa7Aiq65ACC7rokBQXoRfBwERKRGQZryw8ZYrr9vuxnG8tnVnB') - }) - - test('validateSolWallet', async () => { + privateKey = await getSolPrivateKeyFromPlebbitPrivateKey(plebbitPrivateKey); + const dateNow = Date.now; + Date.now = () => walletTimestamp * 1000; + wallet = await getSolWalletFromPlebbitPrivateKey(plebbitPrivateKey, authorAddress); + Date.now = dateNow; + }); + + test("getSolWalletFromPlebbitPrivateKey", async () => { + expect(wallet.timestamp).toBe(walletTimestamp); + expect(wallet.address).toBe("AzAfDLMxbptaq5Ppy4BK5aEsEzvTYNFAub5ffewbSdn9"); + expect(wallet.privateKey).toBe(undefined); + expect(privateKey).toBe( + "44rJnvSKZwF6qMrc49MVe4KqcugR8zc8B4i1yo9iXrvKsf6FAFB7x1dSNdbAqqga4xvpU7VmnKRkwyvQWxrcBmGV", + ); + expect(wallet.signature.type).toBe("sol"); + expect(wallet.signature.signature).toBe( + "4A5VKfweqJMxj3mrFXDEgfxtQBJDYEgfg5BNKKaa7Aiq65ACC7rokBQXoRfBwERKRGQZryw8ZYrr9vuxnG8tnVnB", + ); + }); + + test("validateSolWallet", async () => { // good signature - await validateSolWallet(wallet, authorAddress) + await validateSolWallet(wallet, authorAddress); // bad signatures - await expect(validateSolWallet({...wallet, timestamp: wallet.timestamp + 1}, authorAddress)).rejects.toThrow('signature invalid') - await expect(validateSolWallet(wallet, 'invalidauthoraddress.eth')).rejects.toThrow('signature invalid') - await expect(validateSolWallet({...wallet, timestamp: undefined}, authorAddress)).rejects.toThrow( - `validateSolWallet invalid wallet.timestamp 'undefined' not a number` - ) - await expect(validateSolWallet({...wallet, signature: undefined}, authorAddress)).rejects.toThrow(`validateSolWallet invalid wallet.signature 'undefined'`) - await expect(validateSolWallet({...wallet, signature: {}}, authorAddress)).rejects.toThrow(`validateSolWallet invalid wallet.signature.signature 'undefined'`) - await expect(validateSolWallet({...wallet, address: undefined}, authorAddress)).rejects.toThrow(`validateSolWallet invalid wallet.address 'undefined'`) - await expect(validateSolWallet({...wallet, address: '11111111111111111111111111111111'}, authorAddress)).rejects.toThrow('signature invalid') - }) - - test('validateSolWallet fixture wallet', async () => { - const authorAddress = '12D3KooWRLHxva6Mrt2fxuL4hMeGJCs8erHAAoXCzPGLsdLpdvrF' + await expect( + validateSolWallet({ ...wallet, timestamp: wallet.timestamp + 1 }, authorAddress), + ).rejects.toThrow("signature invalid"); + await expect(validateSolWallet(wallet, "invalidauthoraddress.eth")).rejects.toThrow( + "signature invalid", + ); + await expect( + validateSolWallet({ ...wallet, timestamp: undefined }, authorAddress), + ).rejects.toThrow(`validateSolWallet invalid wallet.timestamp 'undefined' not a number`); + await expect( + validateSolWallet({ ...wallet, signature: undefined }, authorAddress), + ).rejects.toThrow(`validateSolWallet invalid wallet.signature 'undefined'`); + await expect(validateSolWallet({ ...wallet, signature: {} }, authorAddress)).rejects.toThrow( + `validateSolWallet invalid wallet.signature.signature 'undefined'`, + ); + await expect( + validateSolWallet({ ...wallet, address: undefined }, authorAddress), + ).rejects.toThrow(`validateSolWallet invalid wallet.address 'undefined'`); + await expect( + validateSolWallet( + { ...wallet, address: "11111111111111111111111111111111" }, + authorAddress, + ), + ).rejects.toThrow("signature invalid"); + }); + + test("validateSolWallet fixture wallet", async () => { + const authorAddress = "12D3KooWRLHxva6Mrt2fxuL4hMeGJCs8erHAAoXCzPGLsdLpdvrF"; const wallet = { - address: 'GWvoBSWefymBZ1pe4ktvXQnJAXEX97Sj2nuKVeBvjz8K', + address: "GWvoBSWefymBZ1pe4ktvXQnJAXEX97Sj2nuKVeBvjz8K", timestamp: 1758422293, signature: { - signature: 'duY6oPos8RdH31EKaE86g4Lh5oqTL22tVHTG1kTEW8F4eHLG3ynFrP7xVvDm4pFCevKczbLcik8VmH6yZ8mgfx8', - type: 'sol', + signature: + "duY6oPos8RdH31EKaE86g4Lh5oqTL22tVHTG1kTEW8F4eHLG3ynFrP7xVvDm4pFCevKczbLcik8VmH6yZ8mgfx8", + type: "sol", }, - } - await validateSolWallet(wallet, authorAddress) - }) - }) -}) + }; + await validateSolWallet(wallet, authorAddress); + }); + }); +}); diff --git a/src/lib/chain/index.ts b/src/lib/chain/index.ts index 2bf5d5a2..badc6c7a 100644 --- a/src/lib/chain/index.ts +++ b/src/lib/chain/index.ts @@ -1,3 +1,3 @@ -import chain from './chain' -export * from './chain' -export default chain +import chain from "./chain"; +export * from "./chain"; +export default chain; diff --git a/src/lib/community-address.test.ts b/src/lib/community-address.test.ts new file mode 100644 index 00000000..34e3faa0 --- /dev/null +++ b/src/lib/community-address.test.ts @@ -0,0 +1,43 @@ +import { + areEquivalentCommunityAddresses, + getCanonicalCommunityAddress, + getEquivalentCommunityAddressGroupKey, + normalizeEthAliasDomain, + pickPreferredEquivalentCommunityAddress, +} from "./community-address"; + +describe("community-address", () => { + test("treats .eth and .bso aliases as equivalent", () => { + expect(areEquivalentCommunityAddresses("music-posting.eth", "music-posting.bso")).toBe(true); + expect(areEquivalentCommunityAddresses("music-posting.bso", "music-posting.eth")).toBe(true); + }); + + test("matches aliases case-insensitively", () => { + expect(areEquivalentCommunityAddresses("Music-Posting.ETH", "music-posting.bso")).toBe(true); + }); + + test("does not treat different names as equivalent", () => { + expect(areEquivalentCommunityAddresses("music-posting.eth", "other-posting.bso")).toBe(false); + }); + + test("normalizes .bso aliases to .eth", () => { + expect(normalizeEthAliasDomain("music-posting.bso")).toBe("music-posting.eth"); + expect(normalizeEthAliasDomain("music-posting.eth")).toBe("music-posting.eth"); + }); + + test("canonicalizes .eth aliases to .bso for public keys", () => { + expect(getCanonicalCommunityAddress("music-posting.eth")).toBe("music-posting.bso"); + expect(getCanonicalCommunityAddress("music-posting.bso")).toBe("music-posting.bso"); + }); + + test("uses the same group key for equivalent aliases", () => { + expect(getEquivalentCommunityAddressGroupKey("music-posting.eth")).toBe("music-posting.bso"); + expect(getEquivalentCommunityAddressGroupKey("music-posting.bso")).toBe("music-posting.bso"); + }); + + test("prefers the .bso variant when equivalent aliases are present", () => { + expect( + pickPreferredEquivalentCommunityAddress(["music-posting.eth", "music-posting.bso"]), + ).toBe("music-posting.bso"); + }); +}); diff --git a/src/lib/subplebbit-address.ts b/src/lib/community-address.ts similarity index 69% rename from src/lib/subplebbit-address.ts rename to src/lib/community-address.ts index 8795014f..74a682a1 100644 --- a/src/lib/subplebbit-address.ts +++ b/src/lib/community-address.ts @@ -6,18 +6,18 @@ const isEthAliasDomain = (address: string) => { export const normalizeEthAliasDomain = (address: string) => address.endsWith(".bso") ? address.slice(0, -4) + ".eth" : address; -export const getCanonicalSubplebbitAddress = (address: string) => +export const getCanonicalCommunityAddress = (address: string) => address.toLowerCase().endsWith(".eth") ? address.slice(0, -4) + ".bso" : address; -export const getEquivalentSubplebbitAddressGroupKey = (address: string) => { +export const getEquivalentCommunityAddressGroupKey = (address: string) => { const lower = address.toLowerCase(); - return isEthAliasDomain(lower) ? getCanonicalSubplebbitAddress(lower) : address; + return isEthAliasDomain(lower) ? getCanonicalCommunityAddress(lower) : address; }; -export const pickPreferredEquivalentSubplebbitAddress = (addresses: string[]) => +export const pickPreferredEquivalentCommunityAddress = (addresses: string[]) => addresses.find((address) => address.toLowerCase().endsWith(".bso")) || addresses[0]; -export const areEquivalentSubplebbitAddresses = (addressA?: string, addressB?: string) => { +export const areEquivalentCommunityAddresses = (addressA?: string, addressB?: string) => { if (addressA === addressB) { return true; } diff --git a/src/lib/debug-utils.ts b/src/lib/debug-utils.ts index e24daa32..c93e168d 100644 --- a/src/lib/debug-utils.ts +++ b/src/lib/debug-utils.ts @@ -5,16 +5,16 @@ const deleteDatabases = () => Promise.all([ localForage.createInstance({ name: "plebbitReactHooks-accountsMetadata" }).clear(), localForage.createInstance({ name: "plebbitReactHooks-accounts" }).clear(), - localForageLru.createInstance({ name: "plebbitReactHooks-subplebbits" }).clear(), + localForageLru.createInstance({ name: "plebbitReactHooks-communities" }).clear(), localForageLru.createInstance({ name: "plebbitReactHooks-comments" }).clear(), - localForageLru.createInstance({ name: "plebbitReactHooks-subplebbitsPages" }).clear(), + localForageLru.createInstance({ name: "plebbitReactHooks-communitiesPages" }).clear(), ]); const deleteCaches = () => Promise.all([ - localForageLru.createInstance({ name: "plebbitReactHooks-subplebbits" }).clear(), + localForageLru.createInstance({ name: "plebbitReactHooks-communities" }).clear(), localForageLru.createInstance({ name: "plebbitReactHooks-comments" }).clear(), - localForageLru.createInstance({ name: "plebbitReactHooks-subplebbitsPages" }).clear(), + localForageLru.createInstance({ name: "plebbitReactHooks-communitiesPages" }).clear(), ]); const debugUtils = { diff --git a/src/lib/localforage-lru/localforage-lru.test.ts b/src/lib/localforage-lru/localforage-lru.test.ts index f59d4799..e9b1d0e6 100644 --- a/src/lib/localforage-lru/localforage-lru.test.ts +++ b/src/lib/localforage-lru/localforage-lru.test.ts @@ -1,139 +1,139 @@ -import localForageLru, {instances} from './localforage-lru' +import localForageLru, { instances } from "./localforage-lru"; -let dbCount = 0 -const getNewTestDbName = () => `testDb${++dbCount}` +let dbCount = 0; +const getNewTestDbName = () => `testDb${++dbCount}`; -describe('localForageLru', () => { - test('get last recently used', async () => { - const name = getNewTestDbName() - const testDatabase = localForageLru.createInstance({name, size: 4}) +describe("localForageLru", () => { + test("get last recently used", async () => { + const name = getNewTestDbName(); + const testDatabase = localForageLru.createInstance({ name, size: 4 }); - await testDatabase.setItem('one', 1) - await testDatabase.setItem('two', 2) - await testDatabase.setItem('three', 3) - await testDatabase.setItem('four', 4) + await testDatabase.setItem("one", 1); + await testDatabase.setItem("two", 2); + await testDatabase.setItem("three", 3); + await testDatabase.setItem("four", 4); // access 1 and 2 last to make them last recently used - await testDatabase.getItem('three') - await testDatabase.getItem('four') - await testDatabase.getItem('one') - await testDatabase.getItem('two') + await testDatabase.getItem("three"); + await testDatabase.getItem("four"); + await testDatabase.getItem("one"); + await testDatabase.getItem("two"); // erase 3 and 4 by adding 2 more items over than size limit 4 - await testDatabase.setItem('five', 5) - await testDatabase.setItem('six', 6) - expect(await testDatabase.getItem('one')).toBe(1) - expect(await testDatabase.getItem('two')).toBe(2) - expect(await testDatabase.getItem('three')).toBe(undefined) - expect(await testDatabase.getItem('four')).toBe(undefined) - expect(await testDatabase.getItem('five')).toBe(5) - expect(await testDatabase.getItem('six')).toBe(6) + await testDatabase.setItem("five", 5); + await testDatabase.setItem("six", 6); + expect(await testDatabase.getItem("one")).toBe(1); + expect(await testDatabase.getItem("two")).toBe(2); + expect(await testDatabase.getItem("three")).toBe(undefined); + expect(await testDatabase.getItem("four")).toBe(undefined); + expect(await testDatabase.getItem("five")).toBe(5); + expect(await testDatabase.getItem("six")).toBe(6); // .keys() is implemented - expect(await testDatabase.keys()).toEqual(['five', 'six', 'one', 'two']) + expect(await testDatabase.keys()).toEqual(["five", "six", "one", "two"]); // .entries() is implemented expect(await testDatabase.entries()).toEqual([ - ['five', 5], - ['six', 6], - ['one', 1], - ['two', 2], - ]) + ["five", 5], + ["six", 6], + ["one", 1], + ["two", 2], + ]); // .clear() is implemented - await testDatabase.clear() - expect(await testDatabase.entries()).toEqual([]) - expect(await testDatabase.keys()).toEqual([]) - expect(await testDatabase.getItem('one')).toBe(undefined) - expect(await testDatabase.getItem('two')).toBe(undefined) - }) + await testDatabase.clear(); + expect(await testDatabase.entries()).toEqual([]); + expect(await testDatabase.keys()).toEqual([]); + expect(await testDatabase.getItem("one")).toBe(undefined); + expect(await testDatabase.getItem("two")).toBe(undefined); + }); - test('reinstantiate', async () => { - const name = getNewTestDbName() - let testDatabase = localForageLru.createInstance({name, size: 4}) + test("reinstantiate", async () => { + const name = getNewTestDbName(); + let testDatabase = localForageLru.createInstance({ name, size: 4 }); - await testDatabase.setItem('one', 1) - await testDatabase.setItem('two', 2) + await testDatabase.setItem("one", 1); + await testDatabase.setItem("two", 2); // reinstantiate - delete instances[name] - testDatabase = localForageLru.createInstance({name, size: 4}) + delete instances[name]; + testDatabase = localForageLru.createInstance({ name, size: 4 }); - await testDatabase.setItem('three', 3) - await testDatabase.setItem('four', 4) - expect(await testDatabase.getItem('one')).toBe(1) - expect(await testDatabase.getItem('two')).toBe(2) - }) + await testDatabase.setItem("three", 3); + await testDatabase.setItem("four", 4); + expect(await testDatabase.getItem("one")).toBe(1); + expect(await testDatabase.getItem("two")).toBe(2); + }); - test('reinstantiate and overfill', async () => { - const name = getNewTestDbName() - let testDatabase = localForageLru.createInstance({name, size: 4}) + test("reinstantiate and overfill", async () => { + const name = getNewTestDbName(); + let testDatabase = localForageLru.createInstance({ name, size: 4 }); - await testDatabase.setItem('one', 1) - await testDatabase.setItem('two', 2) + await testDatabase.setItem("one", 1); + await testDatabase.setItem("two", 2); // reinstantiate - delete instances[name] - testDatabase = localForageLru.createInstance({name, size: 4}) + delete instances[name]; + testDatabase = localForageLru.createInstance({ name, size: 4 }); - await testDatabase.setItem('three', 3) - await testDatabase.setItem('four', 4) - await testDatabase.setItem('five', 5) + await testDatabase.setItem("three", 3); + await testDatabase.setItem("four", 4); + await testDatabase.setItem("five", 5); // need to add 2 more, because the max size is actually not just 4, it can be up to 4*2-1 - await testDatabase.setItem('six', 6) - await testDatabase.setItem('seven', 7) - expect(await testDatabase.getItem('one')).toBe(1) // since the 1 is gotten first, the 2 gets deleted, even if the 2 was set last - expect(await testDatabase.getItem('two')).toBe(undefined) - }) + await testDatabase.setItem("six", 6); + await testDatabase.setItem("seven", 7); + expect(await testDatabase.getItem("one")).toBe(1); // since the 1 is gotten first, the 2 gets deleted, even if the 2 was set last + expect(await testDatabase.getItem("two")).toBe(undefined); + }); - test('reinstantiate twice', async () => { - const name = getNewTestDbName() - let testDatabase = localForageLru.createInstance({name, size: 100}) + test("reinstantiate twice", async () => { + const name = getNewTestDbName(); + let testDatabase = localForageLru.createInstance({ name, size: 100 }); - await testDatabase.setItem('a', 1) + await testDatabase.setItem("a", 1); // reinstantiate - delete instances[name] - testDatabase = localForageLru.createInstance({name, size: 100}) + delete instances[name]; + testDatabase = localForageLru.createInstance({ name, size: 100 }); - await testDatabase.setItem('a', 2) - await testDatabase.setItem('b', 2) + await testDatabase.setItem("a", 2); + await testDatabase.setItem("b", 2); // reinstantiate - delete instances[name] - testDatabase = localForageLru.createInstance({name, size: 100}) + delete instances[name]; + testDatabase = localForageLru.createInstance({ name, size: 100 }); - expect(await testDatabase.getItem('a')).toBe(2) - expect(await testDatabase.getItem('b')).toBe(2) - }) + expect(await testDatabase.getItem("a")).toBe(2); + expect(await testDatabase.getItem("b")).toBe(2); + }); - test('reinstantiate twice and overfill', async () => { - const name = getNewTestDbName() - let testDatabase = localForageLru.createInstance({name, size: 4}) + test("reinstantiate twice and overfill", async () => { + const name = getNewTestDbName(); + let testDatabase = localForageLru.createInstance({ name, size: 4 }); - await testDatabase.setItem('a', 1) - await testDatabase.setItem('b', 1) - await testDatabase.setItem('c', 1) - await testDatabase.setItem('d', 1) + await testDatabase.setItem("a", 1); + await testDatabase.setItem("b", 1); + await testDatabase.setItem("c", 1); + await testDatabase.setItem("d", 1); // reinstantiate - delete instances[name] - testDatabase = localForageLru.createInstance({name, size: 4}) + delete instances[name]; + testDatabase = localForageLru.createInstance({ name, size: 4 }); - await testDatabase.setItem('a', 2) - await testDatabase.setItem('b', 2) - await testDatabase.setItem('e', 2) + await testDatabase.setItem("a", 2); + await testDatabase.setItem("b", 2); + await testDatabase.setItem("e", 2); // reinstantiate - delete instances[name] - testDatabase = localForageLru.createInstance({name, size: 4}) + delete instances[name]; + testDatabase = localForageLru.createInstance({ name, size: 4 }); - expect(await testDatabase.getItem('a')).toBe(2) - expect(await testDatabase.getItem('b')).toBe(2) - expect(await testDatabase.getItem('d')).toBe(1) + expect(await testDatabase.getItem("a")).toBe(2); + expect(await testDatabase.getItem("b")).toBe(2); + expect(await testDatabase.getItem("d")).toBe(1); // reinstantiate - delete instances[name] - testDatabase = localForageLru.createInstance({name, size: 4}) - }) -}) + delete instances[name]; + testDatabase = localForageLru.createInstance({ name, size: 4 }); + }); +}); diff --git a/src/lib/localforage-lru/localforage-lru.ts b/src/lib/localforage-lru/localforage-lru.ts index 04772ecf..a82a1b6c 100644 --- a/src/lib/localforage-lru/localforage-lru.ts +++ b/src/lib/localforage-lru/localforage-lru.ts @@ -1,155 +1,177 @@ -import localForage from 'localforage' +import localForage from "localforage"; function createLocalForageInstance(localForageLruOptions: any): any { - if (typeof localForageLruOptions?.size !== 'number') { - throw Error(`LocalForageLru.createInstance localForageLruOptions.size '${localForageLruOptions?.size}' not a number`) + if (typeof localForageLruOptions?.size !== "number") { + throw Error( + `LocalForageLru.createInstance localForageLruOptions.size '${localForageLruOptions?.size}' not a number`, + ); } - const localForageOptions = {...localForageLruOptions} - delete localForageOptions.size + const localForageOptions = { ...localForageLruOptions }; + delete localForageOptions.size; let database1: any, database2: any, databaseSize: number, - initialized = false + initialized = false; const initializationPromize = new Promise(async (resolve) => { const localForage1 = localForage.createInstance({ ...localForageOptions, name: localForageLruOptions.name, - }) + }); const localForage2 = localForage.createInstance({ ...localForageOptions, - name: localForageLruOptions.name + '2', - }) - const [localForage1Size, localForage2Size] = await Promise.all([localForage1.length(), localForage2.length()]) + name: localForageLruOptions.name + "2", + }); + const [localForage1Size, localForage2Size] = await Promise.all([ + localForage1.length(), + localForage2.length(), + ]); // largest db is always active db, unless is max size, because max sized db is always inactive - if ((localForage1Size >= localForage2Size && localForage1Size !== localForageLruOptions.size) || localForage2Size === localForageLruOptions.size) { - database2 = localForage2 - database1 = localForage1 - databaseSize = localForage1Size + if ( + (localForage1Size >= localForage2Size && localForage1Size !== localForageLruOptions.size) || + localForage2Size === localForageLruOptions.size + ) { + database2 = localForage2; + database1 = localForage1; + databaseSize = localForage1Size; } else { - database2 = localForage1 - database1 = localForage2 - databaseSize = localForage2Size + database2 = localForage1; + database1 = localForage2; + databaseSize = localForage2Size; } - initialized = true - resolve(undefined) - }) + initialized = true; + resolve(undefined); + }); return { getItem: async function (key: string) { - await initialization() - const [value, value2] = await Promise.all([database1.getItem(key), database2.getItem(key)]) + await initialization(); + const [value, value2] = await Promise.all([database1.getItem(key), database2.getItem(key)]); - let returnValue = value - if (returnValue !== null && returnValue !== undefined) return returnValue + let returnValue = value; + if (returnValue !== null && returnValue !== undefined) return returnValue; if ((returnValue = value2) !== null && (returnValue = value2) !== undefined) { - await updateDatabases(key, returnValue) - return returnValue + await updateDatabases(key, returnValue); + return returnValue; } }, setItem: async function (key: string, value: any) { - await initialization() - const databaseValue = await database1.getItem(key) + await initialization(); + const databaseValue = await database1.getItem(key); if (databaseValue !== null && databaseValue !== undefined) { try { - await database1.setItem(key, value) + await database1.setItem(key, value); } catch (error: any) { - console.error('localforageLru.setItem setItem error', {error, errorMessage: error?.message, key, value}) - throw error + console.error("localforageLru.setItem setItem error", { + error, + errorMessage: error?.message, + key, + value, + }); + throw error; } } else { - await updateDatabases(key, value) + await updateDatabases(key, value); } }, removeItem: async function (key: string) { - await initialization() - await Promise.all([database1.removeItem(key), database2.removeItem(key)]) + await initialization(); + await Promise.all([database1.removeItem(key), database2.removeItem(key)]); }, clear: async function () { - await initialization() - await Promise.all([database1.clear(), database2.clear()]) + await initialization(); + await Promise.all([database1.clear(), database2.clear()]); }, key: async function (keyIndex: number) { - throw Error('not implemented') + throw Error("not implemented"); }, // don't use for init react state, use entries() instead keys: async function () { - await initialization() - const [keys1, keys2] = await Promise.all([database1.keys(), database2.keys()]) - return [...new Set([...keys1, ...keys2])] + await initialization(); + const [keys1, keys2] = await Promise.all([database1.keys(), database2.keys()]); + return [...new Set([...keys1, ...keys2])]; }, // useful to init a react state on load entries: async function () { - await initialization() - const [keys1, keys2] = await Promise.all([database1.keys(), database2.keys()]) - const keys = [...new Set([...keys1, ...keys2])] - const entries: any[] = [] + await initialization(); + const [keys1, keys2] = await Promise.all([database1.keys(), database2.keys()]); + const keys = [...new Set([...keys1, ...keys2])]; + const entries: any[] = []; const getItem = async (key: string) => { - const [value, value2] = await Promise.all([database1.getItem(key), database2.getItem(key)]) + const [value, value2] = await Promise.all([database1.getItem(key), database2.getItem(key)]); if (value !== null && value !== undefined) { - return value + return value; } - return value2 - } + return value2; + }; await Promise.all( keys.map((key, i) => getItem(key).then((value) => { - entries[i] = [key, value] - }) - ) - ) - return entries + entries[i] = [key, value]; + }), + ), + ); + return entries; }, length: async function () { - throw Error('not implemented') + throw Error("not implemented"); }, - } + }; async function updateDatabases(key: string, value: any) { try { - await database1.setItem(key, value) + await database1.setItem(key, value); } catch (error: any) { - console.error('localforageLru updateDatabases setItem error', {error, errorMessage: error?.message, key, value}) + console.error("localforageLru updateDatabases setItem error", { + error, + errorMessage: error?.message, + key, + value, + }); // ignore this error, don't know why it happens - if (error?.message?.includes?.('unit storage quota has been exceeded')) { - return + if (error?.message?.includes?.("unit storage quota has been exceeded")) { + return; } - throw error + throw error; } - databaseSize++ + databaseSize++; if (databaseSize >= localForageLruOptions.size) { - databaseSize = 0 - const database1Temp = database1 - const database2Temp = database2 - database2 = database1Temp - database1 = database2Temp - await database1.clear() + databaseSize = 0; + const database1Temp = database1; + const database2Temp = database2; + database2 = database1Temp; + database1 = database2Temp; + await database1.clear(); } } async function initialization() { if (initialized) { - return + return; } - return initializationPromize + return initializationPromize; } } -export const instances: any = {} +export const instances: any = {}; const createInstance = (localForageLruOptions: any) => { - if (typeof localForageLruOptions?.name !== 'string') { - throw Error(`LocalForageLru.createInstance localForageLruOptions.name '${localForageLruOptions?.name}' not a string`) + if (typeof localForageLruOptions?.name !== "string") { + throw Error( + `LocalForageLru.createInstance localForageLruOptions.name '${localForageLruOptions?.name}' not a string`, + ); } if (instances[localForageLruOptions.name]) { if (localForageLruOptions.size) { - throw Error(`LocalForageLru.createInstance with name '${localForageLruOptions.name}' already created, remove localForageLruOptions.size, size cannot be changed`) + throw Error( + `LocalForageLru.createInstance with name '${localForageLruOptions.name}' already created, remove localForageLruOptions.size, size cannot be changed`, + ); } - return instances[localForageLruOptions.name] + return instances[localForageLruOptions.name]; } - instances[localForageLruOptions.name] = createLocalForageInstance(localForageLruOptions) - return instances[localForageLruOptions.name] -} + instances[localForageLruOptions.name] = createLocalForageInstance(localForageLruOptions); + return instances[localForageLruOptions.name]; +}; -export default {createInstance} +export default { createInstance }; diff --git a/src/lib/plebbit-js/fixtures/markdown-example.ts b/src/lib/plebbit-js/fixtures/markdown-example.ts index e50d5603..0c12b9e5 100644 --- a/src/lib/plebbit-js/fixtures/markdown-example.ts +++ b/src/lib/plebbit-js/fixtures/markdown-example.ts @@ -275,6 +275,6 @@ _http://danlec_@.1 style=background-image:url(data:image/png;base64,iVBORw0KGgoA [ ](https://a.de?p=[[/data-x=. style=background-color:#000000;z-index:999;width:100%;position:fixed;top:0;left:0;right:0;bottom:0; data-y=.]]) [ ](http://a?p=[[/onclick=alert(0) .]]) [a](javascript:new%20Function\`al\\ert\\\`1\\\`\`;) -` +`; -export default markdownExample +export default markdownExample; diff --git a/src/lib/plebbit-js/plebbit-js-mock-content.donttest.ts b/src/lib/plebbit-js/plebbit-js-mock-content.donttest.ts index e2403494..7c927c5c 100644 --- a/src/lib/plebbit-js/plebbit-js-mock-content.donttest.ts +++ b/src/lib/plebbit-js/plebbit-js-mock-content.donttest.ts @@ -13,9 +13,9 @@ import { act } from "@testing-library/react"; import testUtils, { renderHook } from "../../lib/test-utils"; import { useComment, - useSubplebbit, + useCommunity, useFeed, - useAccountSubplebbits, + useAccountCommunities, useAccount, useReplies, setPlebbitJs, @@ -67,16 +67,16 @@ describe.skip("PlebbitJsMockContent", () => { async () => { const plebbit = await PlebbitJsMockContent(); const address = "news.eth"; - const subplebbit: any = await plebbit.createSubplebbit({ address }); - console.log(subplebbit); - subplebbit.update().catch(console.error); + const community: any = await plebbit.createCommunity({ address }); + console.log(community); + community.update().catch(console.error); await new Promise((r) => - subplebbit.on("update", async () => { - console.log(subplebbit); - console.log(subplebbit.posts.pages); - // subplebbit.removeAllListeners() + community.on("update", async () => { + console.log(community); + console.log(community.posts.pages); + // community.removeAllListeners() try { - // const page = await subplebbit.posts.getPage({cid: subplebbit.posts.pageCids.new}) + // const page = await community.posts.getPage({cid: community.posts.pageCids.new}) // console.log(page) // const comment = page.comments[0] // console.log({comment}) @@ -237,10 +237,8 @@ describe("mock content", () => { expect(typeof rendered2.result.current.upvoteCount).toBe("number"); }); - test("use subplebbits", async () => { - const rendered = renderHook((subplebbitAddress) => - useSubplebbit({ subplebbitAddress }), - ); + test("use communities", async () => { + const rendered = renderHook((communityAddress) => useCommunity({ communityAddress })); const waitFor = testUtils.createWaitFor(rendered, { timeout }); rendered.rerender("anything2.eth"); @@ -279,8 +277,8 @@ describe("mock content", () => { // test getting from db await testUtils.resetStores(); - const rendered2 = renderHook((subplebbitAddress) => - useSubplebbit({ subplebbitAddress }), + const rendered2 = renderHook((communityAddress) => + useCommunity({ communityAddress }), ); rendered2.rerender("anything2.eth"); @@ -293,8 +291,8 @@ describe("mock content", () => { }); test("use feed new", async () => { - const rendered = renderHook((subplebbitAddresses) => - useFeed({ subplebbitAddresses, sortType: "new" }), + const rendered = renderHook((communityAddresses) => + useFeed({ communityAddresses, sortType: "new" }), ); const waitFor = testUtils.createWaitFor(rendered, { timeout }); @@ -325,9 +323,7 @@ describe("mock content", () => { }); test("use feed hot", async () => { - const rendered = renderHook((subplebbitAddresses) => - useFeed({ subplebbitAddresses }), - ); + const rendered = renderHook((communityAddresses) => useFeed({ communityAddresses })); const waitFor = testUtils.createWaitFor(rendered, { timeout }); const scrollOnePage = async () => { @@ -374,7 +370,7 @@ describe("mock content", () => { onChallengeVerificationCalled = true; }; await accountsActions.publishComment({ - subplebbitAddress: "news.eth", + communityAddress: "news.eth", content: "content", title: "title", onChallenge, @@ -387,7 +383,7 @@ describe("mock content", () => { console.log("publishing vote"); onChallengeVerificationCalled = false; await accountsActions.publishVote({ - subplebbitAddress: "news.eth", + communityAddress: "news.eth", vote: 1, commentCid: "some cid...", onChallenge, @@ -398,44 +394,44 @@ describe("mock content", () => { expect(onChallengeVerificationCalled).toBe(true); }); - test("use account subplebbits", async () => { + test("use account communities", async () => { const rendered = renderHook(() => { const account = useAccount(); - const { createSubplebbit } = accountsActions; - const accountSubplebbits = useAccountSubplebbits(); - return { createSubplebbit, accountSubplebbits, account }; + const { createCommunity } = accountsActions; + const accountCommunities = useAccountCommunities(); + return { createCommunity, accountCommunities, account }; }); const waitFor = testUtils.createWaitFor(rendered, { timeout }); await waitFor( - () => typeof rendered.result.current.account?.plebbit?.createSubplebbit === "function", + () => typeof rendered.result.current.account?.plebbit?.createCommunity === "function", ); - expect(typeof rendered.result.current.account?.plebbit?.createSubplebbit).toBe("function"); + expect(typeof rendered.result.current.account?.plebbit?.createCommunity).toBe("function"); - console.log("creating subplebbit"); - const subplebbit = await rendered.result.current.createSubplebbit({ + console.log("creating community"); + const community = await rendered.result.current.createCommunity({ title: "title", description: "description", }); - console.log({ subplebbit }); - expect(subplebbit.title).toBe("title"); + console.log({ community }); + expect(community.title).toBe("title"); - // wait for account subplebbits + // wait for account communities await waitFor( () => - JSON.stringify(rendered.result.current?.accountSubplebbits?.accountSubplebbits) !== "{}", + JSON.stringify(rendered.result.current?.accountCommunities?.accountCommunities) !== "{}", ); expect( - JSON.stringify(rendered.result.current?.accountSubplebbits?.accountSubplebbits), + JSON.stringify(rendered.result.current?.accountCommunities?.accountCommunities), ).not.toBe("{}"); - console.log(rendered.result.current?.accountSubplebbits); + console.log(rendered.result.current?.accountCommunities); - // NOTE: this test won't change accountSubplebbits state, need to use publishSubplebbitEdit for that - console.log("editing subplebbit"); - await subplebbit.edit({ + // NOTE: this test won't change accountCommunities state, need to use publishCommunityEdit for that + console.log("editing community"); + await community.edit({ address: "name.eth", }); - console.log({ subplebbit }); - expect(subplebbit.address).toBe("name.eth"); + console.log({ community }); + expect(community.address).toBe("name.eth"); }); test("use comment replies", async () => { diff --git a/src/lib/plebbit-js/plebbit-js-mock-content.ts b/src/lib/plebbit-js/plebbit-js-mock-content.ts index 1465d0bb..0767ec66 100644 --- a/src/lib/plebbit-js/plebbit-js-mock-content.ts +++ b/src/lib/plebbit-js/plebbit-js-mock-content.ts @@ -60,14 +60,14 @@ const mediaLinks = [ "https://upload.wikimedia.org/wikipedia/en/b/bf/Dave_Niehaus_Winning_Call_1995_AL_Division_Series.ogg", ]; -const subplebbitTitles = [ +const communityTitles = [ "The Ethereum investment community", "Cryptography news and discussions", "Memes", "🤡", ]; -const subplebbitDescriptions = [ +const communityDescriptions = [ "Welcome to /r/EthTrader, a 100% community driven sub. Here you can discuss Ethereum news, memes, investing, trading, miscellaneous market-related subjects and other relevant technology.", "Cryptography is the art of creating mathematical assurances for who can do what with data, including but not limited to encryption of messages such that only the key-holder can read it. Cryptography lives at an intersection of math and computer science. This subreddit covers the theory and practice of modern and *strong* cryptography, and it is a technical subreddit focused on the algorithms and implementations of cryptography.", "Memes", @@ -500,178 +500,178 @@ const getReplyContent = async (getReplyContentOptions: any, seed: string) => { return replyContent; }; -const getSubplebbitContent = async (seed: string) => { - const subplebbitNumberSeed = SeedIncrementer(await getNumberHash(seed)); - const subplebbit: any = { - pubsubTopic: await seedToCid(subplebbitNumberSeed.seed), +const getCommunityContent = async (seed: string) => { + const communityNumberSeed = SeedIncrementer(await getNumberHash(seed)); + const community: any = { + pubsubTopic: await seedToCid(communityNumberSeed.seed), }; - const hasChallengeTypes = await getArrayItem([true, false], subplebbitNumberSeed.increment()); + const hasChallengeTypes = await getArrayItem([true, false], communityNumberSeed.increment()); if (hasChallengeTypes) { - subplebbit.challengeTypes = ["image"]; + community.challengeTypes = ["image"]; } - const hasRoles = await getArrayItem([true, false], subplebbitNumberSeed.increment()); + const hasRoles = await getArrayItem([true, false], communityNumberSeed.increment()); if (hasRoles) { - subplebbit.roles = { - [await getAuthorAddress(subplebbitNumberSeed.increment())]: { role: "owner" }, - [await getAuthorAddress(subplebbitNumberSeed.increment())]: { role: "admin" }, - [await getAuthorAddress(subplebbitNumberSeed.increment())]: { role: "moderator" }, - [await getAuthorAddress(subplebbitNumberSeed.increment())]: { role: "moderator" }, - [await getAuthorAddress(subplebbitNumberSeed.increment())]: { role: "moderator" }, - [await getAuthorAddress(subplebbitNumberSeed.increment())]: { role: "moderator" }, - [await getAuthorAddress(subplebbitNumberSeed.increment())]: { role: "moderator" }, - [await getAuthorAddress(subplebbitNumberSeed.increment())]: { role: "moderator" }, + community.roles = { + [await getAuthorAddress(communityNumberSeed.increment())]: { role: "owner" }, + [await getAuthorAddress(communityNumberSeed.increment())]: { role: "admin" }, + [await getAuthorAddress(communityNumberSeed.increment())]: { role: "moderator" }, + [await getAuthorAddress(communityNumberSeed.increment())]: { role: "moderator" }, + [await getAuthorAddress(communityNumberSeed.increment())]: { role: "moderator" }, + [await getAuthorAddress(communityNumberSeed.increment())]: { role: "moderator" }, + [await getAuthorAddress(communityNumberSeed.increment())]: { role: "moderator" }, + [await getAuthorAddress(communityNumberSeed.increment())]: { role: "moderator" }, }; } const title = await getArrayItem( - [undefined, ...subplebbitTitles], - subplebbitNumberSeed.increment(), + [undefined, ...communityTitles], + communityNumberSeed.increment(), ); if (title) { - subplebbit.title = title; + community.title = title; } const description = await getArrayItem( - [undefined, ...subplebbitDescriptions], - subplebbitNumberSeed.increment(), + [undefined, ...communityDescriptions], + communityNumberSeed.increment(), ); if (description) { - subplebbit.description = description; + community.description = description; } - const hasPostFlairs = await getArrayItem([true, false], subplebbitNumberSeed.increment()); + const hasPostFlairs = await getArrayItem([true, false], communityNumberSeed.increment()); if (hasPostFlairs) { - subplebbit.flairs = { post: postFlairs }; + community.flairs = { post: postFlairs }; } - const hasAuthorFlairs = await getArrayItem([true, false], subplebbitNumberSeed.increment()); + const hasAuthorFlairs = await getArrayItem([true, false], communityNumberSeed.increment()); if (hasAuthorFlairs) { - subplebbit.flairs = { post: subplebbit.flairs?.post, author: authorFlairs }; + community.flairs = { post: community.flairs?.post, author: authorFlairs }; } - const hasSuggested = await getArrayItem([true, false], subplebbitNumberSeed.increment()); + const hasSuggested = await getArrayItem([true, false], communityNumberSeed.increment()); if (hasSuggested) { - subplebbit.suggested = { - primaryColor: (await getArrayItem(postFlairs, subplebbitNumberSeed.increment())) + community.suggested = { + primaryColor: (await getArrayItem(postFlairs, communityNumberSeed.increment())) .backgroundColor, - secondaryColor: (await getArrayItem(postFlairs, subplebbitNumberSeed.increment())) + secondaryColor: (await getArrayItem(postFlairs, communityNumberSeed.increment())) .backgroundColor, avatarUrl: await getArrayItem( - [undefined, await getImageUrl(subplebbitNumberSeed.increment())], - subplebbitNumberSeed.increment(), + [undefined, await getImageUrl(communityNumberSeed.increment())], + communityNumberSeed.increment(), ), bannerUrl: await getArrayItem( - [undefined, await getImageUrl(subplebbitNumberSeed.increment())], - subplebbitNumberSeed.increment(), + [undefined, await getImageUrl(communityNumberSeed.increment())], + communityNumberSeed.increment(), ), backgroundUrl: await getArrayItem( - [undefined, await getImageUrl(subplebbitNumberSeed.increment())], - subplebbitNumberSeed.increment(), + [undefined, await getImageUrl(communityNumberSeed.increment())], + communityNumberSeed.increment(), ), language: await getArrayItem( [undefined, undefined, "en", "en", "es", "ru"], - subplebbitNumberSeed.increment(), + communityNumberSeed.increment(), ), }; } - const hasFeatures = await getArrayItem([true, false], subplebbitNumberSeed.increment()); + const hasFeatures = await getArrayItem([true, false], communityNumberSeed.increment()); if (hasFeatures) { - subplebbit.features = { + community.features = { noVideos: await getArrayItem( [undefined, undefined, true, false], - subplebbitNumberSeed.increment(), + communityNumberSeed.increment(), ), noSpoilers: await getArrayItem( [undefined, undefined, true, false], - subplebbitNumberSeed.increment(), + communityNumberSeed.increment(), ), noImages: await getArrayItem( [undefined, undefined, true, false], - subplebbitNumberSeed.increment(), + communityNumberSeed.increment(), ), noVideoReplies: await getArrayItem( [undefined, undefined, true, false], - subplebbitNumberSeed.increment(), + communityNumberSeed.increment(), ), noSpoilerReplies: await getArrayItem( [undefined, undefined, true, false], - subplebbitNumberSeed.increment(), + communityNumberSeed.increment(), ), noImageReplies: await getArrayItem( [undefined, undefined, true, false], - subplebbitNumberSeed.increment(), + communityNumberSeed.increment(), ), noPolls: await getArrayItem( [undefined, undefined, true, false], - subplebbitNumberSeed.increment(), + communityNumberSeed.increment(), ), noCrossposts: await getArrayItem( [undefined, undefined, true, false], - subplebbitNumberSeed.increment(), + communityNumberSeed.increment(), ), noUpvotes: await getArrayItem( [undefined, undefined, true, false], - subplebbitNumberSeed.increment(), + communityNumberSeed.increment(), ), noDownvotes: await getArrayItem( [undefined, undefined, true, false], - subplebbitNumberSeed.increment(), + communityNumberSeed.increment(), ), noAuthors: await getArrayItem( [undefined, undefined, true, false], - subplebbitNumberSeed.increment(), + communityNumberSeed.increment(), ), anonymousAuthors: await getArrayItem( [undefined, undefined, true, false], - subplebbitNumberSeed.increment(), + communityNumberSeed.increment(), ), noNestedReplies: await getArrayItem( [undefined, undefined, true, false], - subplebbitNumberSeed.increment(), + communityNumberSeed.increment(), ), safeForWork: await getArrayItem( [undefined, undefined, true, false], - subplebbitNumberSeed.increment(), + communityNumberSeed.increment(), ), authorFlairs: await getArrayItem( [undefined, undefined, true, false], - subplebbitNumberSeed.increment(), + communityNumberSeed.increment(), ), requireAuthorFlairs: await getArrayItem( [undefined, undefined, true, false], - subplebbitNumberSeed.increment(), + communityNumberSeed.increment(), ), postFlairs: await getArrayItem( [undefined, undefined, true, false], - subplebbitNumberSeed.increment(), + communityNumberSeed.increment(), ), requirePostFlairs: await getArrayItem( [undefined, undefined, true, false], - subplebbitNumberSeed.increment(), + communityNumberSeed.increment(), ), noMarkdownImages: await getArrayItem( [undefined, undefined, true, false], - subplebbitNumberSeed.increment(), + communityNumberSeed.increment(), ), noMarkdownVideos: await getArrayItem( [undefined, undefined, true, false], - subplebbitNumberSeed.increment(), + communityNumberSeed.increment(), ), markdownImageReplies: await getArrayItem( [undefined, undefined, true, false], - subplebbitNumberSeed.increment(), + communityNumberSeed.increment(), ), markdownVideoReplies: await getArrayItem( [undefined, undefined, true, false], - subplebbitNumberSeed.increment(), + communityNumberSeed.increment(), ), }; } - const hasRules = await getArrayItem([true, false], subplebbitNumberSeed.increment()); + const hasRules = await getArrayItem([true, false], communityNumberSeed.increment()); if (hasRules) { - subplebbit.rules = [ + community.rules = [ "no spam", "be nice", "Do not link to CNN.", @@ -680,23 +680,23 @@ const getSubplebbitContent = async (seed: string) => { ]; } - const isOnline = await getArrayItem([true, false], subplebbitNumberSeed.increment()); + const isOnline = await getArrayItem([true, false], communityNumberSeed.increment()); if (isOnline) { // updated in last 1h - subplebbit.updatedAt = + community.updatedAt = Math.round(Date.now() / 1000) - - (await getNumberBetween(1, 60 * 60, subplebbitNumberSeed.increment())); + (await getNumberBetween(1, 60 * 60, communityNumberSeed.increment())); } else { // updated in last month - subplebbit.updatedAt = + community.updatedAt = Math.round(Date.now() / 1000) - - (await getNumberBetween(60 * 60, 60 * 60 * 24 * 30, subplebbitNumberSeed.increment())); + (await getNumberBetween(60 * 60, 60 * 60 * 24 * 30, communityNumberSeed.increment())); } - subplebbit.createdAt = - subplebbit.updatedAt - - (await getNumberBetween(1, 60 * 60 * 24 * 3000, subplebbitNumberSeed.increment())); + community.createdAt = + community.updatedAt - + (await getNumberBetween(1, 60 * 60 * 24 * 3000, communityNumberSeed.increment())); - return subplebbit; + return community; }; // for debugging slow bulk reply generation @@ -765,7 +765,7 @@ const getCommentUpdateContent = async (comment: any) => { commentUpdateSeedNumber.increment(), ), ...replyContent, - subplebbitAddress: comment.subplebbitAddress || "memes.eth", + communityAddress: comment.communityAddress || "memes.eth", }; if (replyCids.has(reply.cid)) { console.error(`mock content error: duplicate reply cid '${reply.cid}'`); @@ -874,8 +874,8 @@ const getCommentUpdateContent = async (comment: any) => { }; const pageCommentCids: any = new Set(); -const getCommentsPage = async (pageCid: string, subplebbitOrComment: any) => { - const subplebbitAddress = subplebbitOrComment.address || subplebbitOrComment.subplebbitAddress; +const getCommentsPage = async (pageCid: string, communityOrComment: any) => { + const communityAddress = communityOrComment.address || communityOrComment.communityAddress; const commentsPageSeedNumber = SeedIncrementer(await getNumberHash(pageCid)); const page: any = { nextCid: await seedToCid(commentsPageSeedNumber.increment()), @@ -893,7 +893,7 @@ const getCommentsPage = async (pageCid: string, subplebbitOrComment: any) => { pageCommentCids.add(cid); // comment = {...comment, ...(await getPostContent(comment.cid)), ...(await getCommentUpdateContent(comment))} const comment: any = await plebbit.getComment({ cid }); - comment.subplebbitAddress = subplebbitAddress; + comment.communityAddress = communityAddress; const commentUpdateContent: any = await getCommentUpdateContent(comment); for (const prop in commentUpdateContent) { comment[prop] = commentUpdateContent[prop]; @@ -903,8 +903,8 @@ const getCommentsPage = async (pageCid: string, subplebbitOrComment: any) => { return page; }; -// array of subplebbits probably created by the user -const createdSubplebbits: any = {}; +// array of communities probably created by the user +const createdCommunities: any = {}; class Plebbit extends EventEmitter { async createSigner() { @@ -916,48 +916,48 @@ class Plebbit extends EventEmitter { async resolveAuthorAddress(options: { address: string }) {} - async createSubplebbit(createSubplebbitOptions: any) { + async createCommunity(createCommunityOptions: any) { // if the only argument is {address}, the user didn't create the sub, it's a fetched sub - if (createSubplebbitOptions?.address && Object.keys(createSubplebbitOptions).length === 1) { - return new Subplebbit(createSubplebbitOptions); + if (createCommunityOptions?.address && Object.keys(createCommunityOptions).length === 1) { + return new Community(createCommunityOptions); } const signer = await this.createSigner(); - const subplebbit = new Subplebbit({ signer, ...createSubplebbitOptions }); + const community = new Community({ signer, ...createCommunityOptions }); - // keep a list of subplebbits the user probably created himself to use with plebbit.subplebbits - if (!createSubplebbitOptions?.address) { - createdSubplebbits[subplebbit.address || ""] = subplebbit; + // keep a list of communities the user probably created himself to use with plebbit.communities + if (!createCommunityOptions?.address) { + createdCommunities[community.address || ""] = community; } - return subplebbit; + return community; } - async getSubplebbit(options: { address: string }) { + async getCommunity(options: { address: string }) { const address = options?.address; - const createSubplebbitOptions = { address }; - const subplebbit: any = new Subplebbit(createSubplebbitOptions); + const createCommunityOptions = { address }; + const community: any = new Community(createCommunityOptions); const hotPageCid = await seedToCid(await getNumberHash(address + "hotpagecid")); - subplebbit.posts.pages.hot = await getCommentsPage(hotPageCid, subplebbit); - subplebbit.posts.pageCids = { + community.posts.pages.hot = await getCommentsPage(hotPageCid, community); + community.posts.pageCids = { hot: await seedToCid(await getNumberHash(address + "hotpagecid2")), topAll: await seedToCid(await getNumberHash(address + "topallpagecid")), new: await seedToCid(await getNumberHash(address + "newpagecid")), active: await seedToCid(await getNumberHash(address + "activepagecid")), }; - const subplebbitContent = await getSubplebbitContent(address); + const communityContent = await getCommunityContent(address); // add extra props - for (const prop in subplebbitContent) { - subplebbit[prop] = subplebbitContent[prop]; + for (const prop in communityContent) { + community[prop] = communityContent[prop]; } - return subplebbit; + return community; } - // TODO: implement event subplebbitschange - get subplebbits() { - const subplebbitAddresses = Object.keys(createdSubplebbits); - return subplebbitAddresses; + // TODO: implement event communitieschange + get communities() { + const communityAddresses = Object.keys(createdCommunities); + return communityAddresses; } async createComment(createCommentOptions: any) { @@ -979,7 +979,7 @@ class Plebbit extends EventEmitter { const createCommentOptions = { cid, timestamp: await getNumberBetween(NOW - DAY * 30, NOW, commentSeedNumber.increment()), - subplebbitAddress: "memes.eth", + communityAddress: "memes.eth", ...commentContent, }; const comment = new Comment(createCommentOptions); @@ -999,8 +999,8 @@ class Plebbit extends EventEmitter { return new CommentEdit(publishCommentEditOptions); } - async createSubplebbitEdit() { - return new SubplebbitEdit(); + async createCommunityEdit() { + return new CommunityEdit(); } async fetchCid(options: { cid: string }) { @@ -1024,8 +1024,8 @@ class Plebbit extends EventEmitter { throw Error(`plebbit.fetchCid not implemented in mock content for cid '${cid}'`); } - async pubsubSubscribe(subplebbitAddress: string) {} - async pubsubUnsubscribe(subplebbitAddress: string) {} + async pubsubSubscribe(communityAddress: string) {} + async pubsubUnsubscribe(communityAddress: string) {} clients = { plebbitRpcClients: { @@ -1059,12 +1059,12 @@ class PlebbitRpcClient extends EventEmitter { class Pages { pageCids: any = {}; pages: any = {}; - subplebbit: any; + community: any; comment: any; constructor(pagesOptions?: any) { - Object.defineProperty(this, "subplebbit", { - value: pagesOptions?.subplebbit, + Object.defineProperty(this, "community", { + value: pagesOptions?.community, enumerable: false, }); Object.defineProperty(this, "comment", { value: pagesOptions?.comment, enumerable: false }); @@ -1074,13 +1074,13 @@ class Pages { const cid = options?.cid; // need to wait twice otherwise react renders too fast and fetches too many pages in advance await simulateLoadingTime(); - return getCommentsPage(cid, this.subplebbit || this.comment); + return getCommentsPage(cid, this.community || this.comment); } async validatePage(page: any) {} } -class Subplebbit extends EventEmitter { +class Community extends EventEmitter { address: string | undefined; title: string | undefined; description: string | undefined; @@ -1098,39 +1098,39 @@ class Subplebbit extends EventEmitter { signer: any | undefined; shortAddress: string | undefined; statsCid: string | undefined; - _getSubplebbitOnFirstUpdate = false; + _getCommunityOnFirstUpdate = false; updatingState: string | undefined; - constructor(createSubplebbitOptions?: any) { + constructor(createCommunityOptions?: any) { super(); - this.address = createSubplebbitOptions?.address; - this.pubsubTopic = createSubplebbitOptions?.pubsubTopic; - this.createdAt = createSubplebbitOptions?.createdAt; - this.updatedAt = createSubplebbitOptions?.updatedAt; - this.challengeTypes = createSubplebbitOptions?.challengeTypes; - this.roles = createSubplebbitOptions?.roles; - this.flairs = createSubplebbitOptions?.flairs; - this.suggested = createSubplebbitOptions?.suggested; - this.features = createSubplebbitOptions?.features; - this.rules = createSubplebbitOptions?.rules; - this.title = createSubplebbitOptions?.title; - this.description = createSubplebbitOptions?.description; + this.address = createCommunityOptions?.address; + this.pubsubTopic = createCommunityOptions?.pubsubTopic; + this.createdAt = createCommunityOptions?.createdAt; + this.updatedAt = createCommunityOptions?.updatedAt; + this.challengeTypes = createCommunityOptions?.challengeTypes; + this.roles = createCommunityOptions?.roles; + this.flairs = createCommunityOptions?.flairs; + this.suggested = createCommunityOptions?.suggested; + this.features = createCommunityOptions?.features; + this.rules = createCommunityOptions?.rules; + this.title = createCommunityOptions?.title; + this.description = createCommunityOptions?.description; this.statsCid = "statscid"; - for (const prop in createSubplebbitOptions) { - if (createSubplebbitOptions[prop]) { + for (const prop in createCommunityOptions) { + if (createCommunityOptions[prop]) { // @ts-ignore - this[prop] = createSubplebbitOptions[prop]; + this[prop] = createCommunityOptions[prop]; } } - this.posts = new Pages({ subplebbit: this }); + this.posts = new Pages({ community: this }); - // add subplebbit.posts from createSubplebbitOptions - if (createSubplebbitOptions?.posts?.pages) { - this.posts.pages = createSubplebbitOptions?.posts?.pages; + // add community.posts from createCommunityOptions + if (createCommunityOptions?.posts?.pages) { + this.posts.pages = createCommunityOptions?.posts?.pages; } - if (createSubplebbitOptions?.posts?.pageCids) { - this.posts.pageCids = createSubplebbitOptions?.posts?.pageCids; + if (createCommunityOptions?.posts?.pageCids) { + this.posts.pageCids = createCommunityOptions?.posts?.pageCids; } if (!this.address && this.signer?.address) { @@ -1144,28 +1144,28 @@ class Subplebbit extends EventEmitter { // @ts-ignore this.updating = false; - // if the only argument is {address}, it means the first update should use getSubplebbit() - if (createSubplebbitOptions?.address && Object.keys(createSubplebbitOptions).length === 1) { - this._getSubplebbitOnFirstUpdate = true; + // if the only argument is {address}, it means the first update should use getCommunity() + if (createCommunityOptions?.address && Object.keys(createCommunityOptions).length === 1) { + this._getCommunityOnFirstUpdate = true; } } - async edit(editSubplebbitOptions: any) { + async edit(editCommunityOptions: any) { assert( - editSubplebbitOptions && typeof editSubplebbitOptions === "object", - `invalid editSubplebbitOptions '${editSubplebbitOptions}'`, + editCommunityOptions && typeof editCommunityOptions === "object", + `invalid editCommunityOptions '${editCommunityOptions}'`, ); - for (const prop in editSubplebbitOptions) { - if (editSubplebbitOptions[prop]) { + for (const prop in editCommunityOptions) { + if (editCommunityOptions[prop]) { // @ts-ignore - this[prop] = editSubplebbitOptions[prop]; + this[prop] = editCommunityOptions[prop]; } } } async update() { if (!this.address) { - throw Error(`can't update without subplebbit.address`); + throw Error(`can't update without community.address`); } // don't update twice // @ts-ignore @@ -1183,23 +1183,23 @@ class Subplebbit extends EventEmitter { async delete() { if (this.address) { - delete createdSubplebbits[this.address]; + delete createdCommunities[this.address]; } } simulateUpdateEvent() { - if (this._getSubplebbitOnFirstUpdate) { - return this.simulateGetSubplebbitOnFirstUpdateEvent(); + if (this._getCommunityOnFirstUpdate) { + return this.simulateGetCommunityOnFirstUpdateEvent(); } this.emit("update", this); } - async simulateGetSubplebbitOnFirstUpdateEvent() { - this._getSubplebbitOnFirstUpdate = false; + async simulateGetCommunityOnFirstUpdateEvent() { + this._getCommunityOnFirstUpdate = false; // @ts-ignore - const subplebbit = await new Plebbit().getSubplebbit({ address: this.address }); - const props = JSON.parse(JSON.stringify(subplebbit)); + const community = await new Plebbit().getCommunity({ address: this.address }); + const props = JSON.parse(JSON.stringify(community)); for (const prop in props) { if (prop.startsWith("_")) { continue; @@ -1207,7 +1207,7 @@ class Subplebbit extends EventEmitter { // @ts-ignore this[prop] = props[prop]; } - this.posts.getPage = subplebbit.posts.getPage; + this.posts.getPage = community.posts.getPage; this.updatingState = "succeeded"; this.emit("update", this); this.emit("updatingstatechange", "succeeded"); @@ -1304,8 +1304,8 @@ class Comment extends Publication { shortCid: string | undefined; _getCommentOnFirstUpdate = false; updatingState: string | undefined; - subplebbitAddress: string | undefined; - shortSubplebbitAddress: string | undefined; + communityAddress: string | undefined; + shortCommunityAddress: string | undefined; constructor(createCommentOptions?: any) { super(); @@ -1347,8 +1347,8 @@ class Comment extends Publication { this._getCommentOnFirstUpdate = true; } - if (this.subplebbitAddress) { - this.shortSubplebbitAddress = this.subplebbitAddress.substring(0, 12); + if (this.communityAddress) { + this.shortCommunityAddress = this.communityAddress.substring(0, 12); } } @@ -1426,7 +1426,7 @@ class CommentEdit extends Publication { } } -class SubplebbitEdit extends Publication {} +class CommunityEdit extends Publication {} const createPlebbit: any = async (...args: any) => { return new Plebbit(...args); diff --git a/src/lib/plebbit-js/plebbit-js-mock.test.ts b/src/lib/plebbit-js/plebbit-js-mock.test.ts index 2eb5249f..383079a6 100644 --- a/src/lib/plebbit-js/plebbit-js-mock.test.ts +++ b/src/lib/plebbit-js/plebbit-js-mock.test.ts @@ -39,31 +39,31 @@ describe("PlebbitJsMock", () => { expect(onUpdatingstatechange.mock.results[2].value).toEqual("succeeded"); }); - test("Subplebbit.state and Subplebbit.updatingState", async () => { + test("Community.state and Community.updatingState", async () => { const plebbit = await PlebbitJsMock(); - const subplebbit = await plebbit.createSubplebbit({ address: "subplebbit address" }); + const community = await plebbit.createCommunity({ address: "community address" }); // initial state is stopped - expect(subplebbit.state).toBe("stopped"); - expect(subplebbit.updatingState).toBe("stopped"); + expect(community.state).toBe("stopped"); + expect(community.updatingState).toBe("stopped"); - const onStatechange = vi.fn(() => subplebbit.state); - const onUpdatingstatechange = vi.fn(() => subplebbit.updatingState); + const onStatechange = vi.fn(() => community.state); + const onUpdatingstatechange = vi.fn(() => community.updatingState); - subplebbit.on("statechange", onStatechange); - subplebbit.on("updatingstatechange", onUpdatingstatechange); + community.on("statechange", onStatechange); + community.on("updatingstatechange", onUpdatingstatechange); // wait for succeeded twice (2 updates) let succeededCount = 0; const succeededPromise = new Promise((resolve) => - subplebbit.on("updatingstatechange", (state: string) => { + community.on("updatingstatechange", (state: string) => { state === "succeeded" && ++succeededCount === 2 && resolve(state); }), ); // start updating state - await subplebbit.update(); - expect(subplebbit.state).toBe("updating"); + await community.update(); + expect(community.state).toBe("updating"); await succeededPromise; expect(onStatechange.mock.calls[0]).toEqual(["updating"]); @@ -84,7 +84,7 @@ describe("PlebbitJsMock", () => { const plebbit = await PlebbitJsMock(); const comment = await plebbit.createComment({ content: "content", - subplebbitAddresses: "subplebbit address", + communityAddresses: "community address", }); // initial state is stopped @@ -130,7 +130,7 @@ describe("PlebbitJsMock", () => { const plebbit = await PlebbitJsMock(); const comment = await plebbit.createComment({ content: "content", - subplebbitAddress: "subplebbit address", + communityAddress: "community address", }); expect(comment.state).toBe("stopped"); diff --git a/src/lib/plebbit-js/plebbit-js-mock.ts b/src/lib/plebbit-js/plebbit-js-mock.ts index b309c3ff..216aac05 100644 --- a/src/lib/plebbit-js/plebbit-js-mock.ts +++ b/src/lib/plebbit-js/plebbit-js-mock.ts @@ -3,18 +3,18 @@ import EventEmitter from "events"; const loadingTime = 10; export const simulateLoadingTime = () => new Promise((r) => setTimeout(r, loadingTime)); -// keep a list of created and edited owner subplebbits -// to reinitialize them with plebbit.createSubplebbit() -let createdOwnerSubplebbits: any = {}; -let editedOwnerSubplebbits: any = {}; +// keep a list of created and edited owner communities +// to reinitialize them with plebbit.createCommunity() +let createdOwnerCommunities: any = {}; +let editedOwnerCommunities: any = {}; // reset the plebbit-js global state in between tests export const resetPlebbitJsMock = () => { - createdOwnerSubplebbits = {}; - editedOwnerSubplebbits = {}; + createdOwnerCommunities = {}; + editedOwnerCommunities = {}; }; export const debugPlebbitJsMock = () => { - console.log({ createdOwnerSubplebbits, editedOwnerSubplebbits }); + console.log({ createdOwnerCommunities, editedOwnerCommunities }); }; export class Plebbit extends EventEmitter { @@ -29,77 +29,77 @@ export class Plebbit extends EventEmitter { }; } - async createSubplebbit(createSubplebbitOptions: any) { - if (!createSubplebbitOptions) { - createSubplebbitOptions = {}; + async createCommunity(createCommunityOptions: any) { + if (!createCommunityOptions) { + createCommunityOptions = {}; } - // no address provided so probably a user creating an owner subplebbit + // no address provided so probably a user creating an owner community if ( - !createSubplebbitOptions.address && - !createdOwnerSubplebbits[createSubplebbitOptions.address] + !createCommunityOptions.address && + !createdOwnerCommunities[createCommunityOptions.address] ) { - createSubplebbitOptions = { - ...createSubplebbitOptions, - address: "created subplebbit address", + createCommunityOptions = { + ...createCommunityOptions, + address: "created community address", }; - // createdSubplebbitAddresses.push('created subplebbit address') - createdOwnerSubplebbits[createSubplebbitOptions.address] = { ...createSubplebbitOptions }; + // createdCommunityAddresses.push('created community address') + createdOwnerCommunities[createCommunityOptions.address] = { ...createCommunityOptions }; } - // only address provided, so could be a previously created owner subplebbit + // only address provided, so could be a previously created owner community // add props from previously created sub else if ( - createdOwnerSubplebbits[createSubplebbitOptions.address] && - JSON.stringify(Object.keys(createSubplebbitOptions)) === '["address"]' + createdOwnerCommunities[createCommunityOptions.address] && + JSON.stringify(Object.keys(createCommunityOptions)) === '["address"]' ) { - for (const prop in createdOwnerSubplebbits[createSubplebbitOptions.address]) { - if (createdOwnerSubplebbits[createSubplebbitOptions.address][prop]) { - createSubplebbitOptions[prop] = - createdOwnerSubplebbits[createSubplebbitOptions.address][prop]; + for (const prop in createdOwnerCommunities[createCommunityOptions.address]) { + if (createdOwnerCommunities[createCommunityOptions.address][prop]) { + createCommunityOptions[prop] = + createdOwnerCommunities[createCommunityOptions.address][prop]; } } } - // add edited props if owner subplebbit was edited in the past - if (editedOwnerSubplebbits[createSubplebbitOptions.address]) { - for (const prop in editedOwnerSubplebbits[createSubplebbitOptions.address]) { - if (editedOwnerSubplebbits[createSubplebbitOptions.address][prop]) { - createSubplebbitOptions[prop] = - editedOwnerSubplebbits[createSubplebbitOptions.address][prop]; + // add edited props if owner community was edited in the past + if (editedOwnerCommunities[createCommunityOptions.address]) { + for (const prop in editedOwnerCommunities[createCommunityOptions.address]) { + if (editedOwnerCommunities[createCommunityOptions.address][prop]) { + createCommunityOptions[prop] = + editedOwnerCommunities[createCommunityOptions.address][prop]; } } } - return new Subplebbit(createSubplebbitOptions); + return new Community(createCommunityOptions); } - async getSubplebbit(options: { address: string }) { + async getCommunity(options: { address: string }) { const address = options?.address; await simulateLoadingTime(); - const createSubplebbitOptions = { address }; - const subplebbit: any = new Subplebbit(createSubplebbitOptions); - subplebbit.title = subplebbit.address + " title"; - const hotPageCid = subplebbit.address + " page cid hot"; - subplebbit.posts.pages.hot = subplebbit.posts.pageToGet(hotPageCid); - subplebbit.posts.pageCids = { + const createCommunityOptions = { address }; + const community: any = new Community(createCommunityOptions); + community.title = community.address + " title"; + const hotPageCid = community.address + " page cid hot"; + community.posts.pages.hot = community.posts.pageToGet(hotPageCid); + community.posts.pageCids = { hot: hotPageCid, - topAll: subplebbit.address + " page cid topAll", - new: subplebbit.address + " page cid new", - active: subplebbit.address + " page cid active", + topAll: community.address + " page cid topAll", + new: community.address + " page cid new", + active: community.address + " page cid active", }; - subplebbit.modQueue.pageCids = { - pendingApproval: subplebbit.address + " page cid pendingApproval", + community.modQueue.pageCids = { + pendingApproval: community.address + " page cid pendingApproval", }; - return subplebbit; + return community; } - // TODO: implement event subplebbitschange - get subplebbits() { + // TODO: implement event communitieschange + get communities() { return [ ...new Set([ - "list subplebbit address 1", - "list subplebbit address 2", - ...Object.keys(createdOwnerSubplebbits), + "list community address 1", + "list community address 2", + ...Object.keys(createdOwnerCommunities), ]), ]; } @@ -141,8 +141,8 @@ export class Plebbit extends EventEmitter { return new CommentModeration(createCommentModerationOptions); } - async createSubplebbitEdit(createSubplebbitEditOptions: any) { - return new SubplebbitEdit(createSubplebbitEditOptions); + async createCommunityEdit(createCommunityEditOptions: any) { + return new CommunityEdit(createCommunityEditOptions); } async fetchCid(options: { cid: string }) { @@ -153,8 +153,8 @@ export class Plebbit extends EventEmitter { throw Error(`plebbit.fetchCid not implemented in plebbit-js mock for cid '${cid}'`); } - async pubsubSubscribe(subplebbitAddress: string) {} - async pubsubUnsubscribe(subplebbitAddress: string) {} + async pubsubSubscribe(communityAddress: string) {} + async pubsubUnsubscribe(communityAddress: string) {} clients = { plebbitRpcClients: { @@ -188,12 +188,12 @@ class PlebbitRpcClient extends EventEmitter { export class Pages { pageCids: any = {}; pages: any = {}; - subplebbit: any; + community: any; comment: any; constructor(pagesOptions?: any) { - Object.defineProperty(this, "subplebbit", { - value: pagesOptions?.subplebbit, + Object.defineProperty(this, "community", { + value: pagesOptions?.community, enumerable: false, }); Object.defineProperty(this, "comment", { value: pagesOptions?.comment, enumerable: false }); @@ -210,10 +210,10 @@ export class Pages { // mock this method to get pages with different content, or use to getPage without simulated loading time pageToGet(pageCid: string) { - const subplebbitAddress = this.subplebbit?.address || this.comment?.subplebbitAddress; + const communityAddress = this.community?.address || this.comment?.communityAddress; const isPendingApprovalPage = pageCid.includes("pendingApproval"); const page: any = { - nextCid: subplebbitAddress + " " + pageCid + " - next page cid", + nextCid: communityAddress + " " + pageCid + " - next page cid", comments: [], }; const postCount = 100; @@ -222,7 +222,7 @@ export class Pages { const comment: any = { timestamp: index, cid: pageCid + " comment cid " + index, - subplebbitAddress, + communityAddress, upvoteCount: index, downvoteCount: 10, author: { @@ -239,7 +239,7 @@ export class Pages { } } -export class Subplebbit extends EventEmitter { +export class Community extends EventEmitter { updateCalledTimes = 0; updating = false; firstUpdate = true; @@ -253,33 +253,33 @@ export class Subplebbit extends EventEmitter { state: string; updatingState: string; - constructor(createSubplebbitOptions?: any) { + constructor(createCommunityOptions?: any) { super(); - this.address = createSubplebbitOptions?.address; - this.title = createSubplebbitOptions?.title; - this.description = createSubplebbitOptions?.description; + this.address = createCommunityOptions?.address; + this.title = createCommunityOptions?.title; + this.description = createCommunityOptions?.description; this.statsCid = "statscid"; this.state = "stopped"; this.updatingState = "stopped"; - this.updatedAt = createSubplebbitOptions?.updatedAt; + this.updatedAt = createCommunityOptions?.updatedAt; - this.posts = new Pages({ subplebbit: this }); - // add subplebbit.posts from createSubplebbitOptions - if (createSubplebbitOptions?.posts?.pages) { - this.posts.pages = createSubplebbitOptions?.posts?.pages; + this.posts = new Pages({ community: this }); + // add community.posts from createCommunityOptions + if (createCommunityOptions?.posts?.pages) { + this.posts.pages = createCommunityOptions?.posts?.pages; } - if (createSubplebbitOptions?.posts?.pageCids) { - this.posts.pageCids = createSubplebbitOptions?.posts?.pageCids; + if (createCommunityOptions?.posts?.pageCids) { + this.posts.pageCids = createCommunityOptions?.posts?.pageCids; } - this.modQueue = new Pages({ subplebbit: this }); - // add subplebbit.modQueue from createSubplebbitOptions - if (createSubplebbitOptions?.modQueue?.pageCids) { - this.modQueue.pageCids = createSubplebbitOptions?.modQueue?.pageCids; + this.modQueue = new Pages({ community: this }); + // add community.modQueue from createCommunityOptions + if (createCommunityOptions?.modQueue?.pageCids) { + this.modQueue.pageCids = createCommunityOptions?.modQueue?.pageCids; } // only trigger a first update if argument is only ({address}) - if (!createSubplebbitOptions?.address || Object.keys(createSubplebbitOptions).length !== 1) { + if (!createCommunityOptions?.address || Object.keys(createCommunityOptions).length !== 1) { this.firstUpdate = false; } } @@ -288,11 +288,11 @@ export class Subplebbit extends EventEmitter { this.updateCalledTimes++; if (this.updateCalledTimes > 1) { throw Error( - "with the current hooks, subplebbit.update() should be called maximum 1 times, this number might change if the hooks change and is only there to catch bugs, the real comment.update() can be called infinite times", + "with the current hooks, community.update() should be called maximum 1 times, this number might change if the hooks change and is only there to catch bugs, the real comment.update() can be called infinite times", ); } if (!this.address) { - throw Error(`can't update without subplebbit.address`); + throw Error(`can't update without community.address`); } // don't update twice if (this.updating) { @@ -312,8 +312,8 @@ export class Subplebbit extends EventEmitter { async delete() { if (this.address) { - delete createdOwnerSubplebbits[this.address]; - delete editedOwnerSubplebbits[this.address]; + delete createdOwnerCommunities[this.address]; + delete editedOwnerCommunities[this.address]; } } @@ -332,7 +332,7 @@ export class Subplebbit extends EventEmitter { this.emit("updatingstatechange", "succeeded"); } - // the first update event adds all the field from getSubplebbit + // the first update event adds all the field from getCommunity async simulateFirstUpdateEvent() { this.firstUpdate = false; this.updatedAt = Math.floor(Date.now() / 1000); @@ -373,49 +373,49 @@ export class Subplebbit extends EventEmitter { return {}; } - async edit(editSubplebbitOptions: any) { + async edit(editCommunityOptions: any) { if (!this.address || typeof this.address !== "string") { - throw Error(`can't subplebbit.edit with no subplebbit.address`); + throw Error(`can't community.edit with no community.address`); } const previousAddress = this.address; - // do subplebbit.edit - for (const prop in editSubplebbitOptions) { - if (editSubplebbitOptions[prop]) { + // do community.edit + for (const prop in editCommunityOptions) { + if (editCommunityOptions[prop]) { // @ts-ignore - this[prop] = editSubplebbitOptions[prop]; + this[prop] = editCommunityOptions[prop]; } } - // keep a list of edited subplebbits to reinitialize - // them with plebbit.createSubplebbit() - editedOwnerSubplebbits[this.address] = { + // keep a list of edited communities to reinitialize + // them with plebbit.createCommunity() + editedOwnerCommunities[this.address] = { address: this.address, title: this.title, description: this.description, }; - // handle change of subplebbit.address - if (editSubplebbitOptions.address) { - // apply address change to editedOwnerSubplebbits - editedOwnerSubplebbits[previousAddress] = { + // handle change of community.address + if (editCommunityOptions.address) { + // apply address change to editedOwnerCommunities + editedOwnerCommunities[previousAddress] = { address: this.address, title: this.title, description: this.description, }; - delete editedOwnerSubplebbits[previousAddress]; + delete editedOwnerCommunities[previousAddress]; - // apply address change to createdOwnerSubplebbits - createdOwnerSubplebbits[this.address] = { - ...createdOwnerSubplebbits[previousAddress], + // apply address change to createdOwnerCommunities + createdOwnerCommunities[this.address] = { + ...createdOwnerCommunities[previousAddress], address: this.address, }; - delete createdOwnerSubplebbits[previousAddress]; + delete createdOwnerCommunities[previousAddress]; } } } // make roles enumarable so it acts like a regular prop -Object.defineProperty(Subplebbit.prototype, "roles", { enumerable: true }); +Object.defineProperty(Community.prototype, "roles", { enumerable: true }); let challengeRequestCount = 0; let challengeAnswerCount = 0; @@ -502,7 +502,7 @@ export class Comment extends Publication { parentCid: string | undefined; replies: any; updatedAt: number | undefined; - subplebbitAddress: string | undefined; + communityAddress: string | undefined; state: string; updatingState: string; publishingState: string; @@ -516,7 +516,7 @@ export class Comment extends Publication { this.author = createCommentOptions?.author; this.timestamp = createCommentOptions?.timestamp; this.parentCid = createCommentOptions?.parentCid; - this.subplebbitAddress = createCommentOptions?.subplebbitAddress; + this.communityAddress = createCommentOptions?.communityAddress; this.state = "stopped"; this.updatingState = "stopped"; this.publishingState = "stopped"; @@ -583,7 +583,7 @@ export class Comment extends Publication { this.author = commentIpfs.author; this.timestamp = commentIpfs.timestamp; this.parentCid = commentIpfs.parentCid; - this.subplebbitAddress = commentIpfs.subplebbitAddress; + this.communityAddress = commentIpfs.communityAddress; // simulate the ipns update this.updatingState = "fetching-update-ipns"; @@ -601,7 +601,7 @@ export class CommentEdit extends Publication {} export class CommentModeration extends Publication {} -export class SubplebbitEdit extends Publication {} +export class CommunityEdit extends Publication {} const createPlebbit: any = async (...args: any) => { return new Plebbit(...args); diff --git a/src/lib/subplebbit-address.test.ts b/src/lib/subplebbit-address.test.ts deleted file mode 100644 index afa404fb..00000000 --- a/src/lib/subplebbit-address.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { - areEquivalentSubplebbitAddresses, - getCanonicalSubplebbitAddress, - getEquivalentSubplebbitAddressGroupKey, - normalizeEthAliasDomain, - pickPreferredEquivalentSubplebbitAddress, -} from "./subplebbit-address"; - -describe("subplebbit-address", () => { - test("treats .eth and .bso aliases as equivalent", () => { - expect(areEquivalentSubplebbitAddresses("music-posting.eth", "music-posting.bso")).toBe(true); - expect(areEquivalentSubplebbitAddresses("music-posting.bso", "music-posting.eth")).toBe(true); - }); - - test("matches aliases case-insensitively", () => { - expect(areEquivalentSubplebbitAddresses("Music-Posting.ETH", "music-posting.bso")).toBe(true); - }); - - test("does not treat different names as equivalent", () => { - expect(areEquivalentSubplebbitAddresses("music-posting.eth", "other-posting.bso")).toBe(false); - }); - - test("normalizes .bso aliases to .eth", () => { - expect(normalizeEthAliasDomain("music-posting.bso")).toBe("music-posting.eth"); - expect(normalizeEthAliasDomain("music-posting.eth")).toBe("music-posting.eth"); - }); - - test("canonicalizes .eth aliases to .bso for public keys", () => { - expect(getCanonicalSubplebbitAddress("music-posting.eth")).toBe("music-posting.bso"); - expect(getCanonicalSubplebbitAddress("music-posting.bso")).toBe("music-posting.bso"); - }); - - test("uses the same group key for equivalent aliases", () => { - expect(getEquivalentSubplebbitAddressGroupKey("music-posting.eth")).toBe("music-posting.bso"); - expect(getEquivalentSubplebbitAddressGroupKey("music-posting.bso")).toBe("music-posting.bso"); - }); - - test("prefers the .bso variant when equivalent aliases are present", () => { - expect( - pickPreferredEquivalentSubplebbitAddress(["music-posting.eth", "music-posting.bso"]), - ).toBe("music-posting.bso"); - }); -}); diff --git a/src/lib/test-utils.ts b/src/lib/test-utils.ts index ec2a37c2..e54f19a0 100644 --- a/src/lib/test-utils.ts +++ b/src/lib/test-utils.ts @@ -1,13 +1,13 @@ import { render, act as tlAct } from "@testing-library/react"; import React from "react"; import { resetCommentsStore, resetCommentsDatabaseAndStore } from "../stores/comments"; -import { resetSubplebbitsStore, resetSubplebbitsDatabaseAndStore } from "../stores/subplebbits"; +import { resetCommunitiesStore, resetCommunitiesDatabaseAndStore } from "../stores/communities"; import { resetAccountsStore, resetAccountsDatabaseAndStore } from "../stores/accounts"; import { resetFeedsStore, resetFeedsDatabaseAndStore } from "../stores/feeds"; import { - resetSubplebbitsPagesStore, - resetSubplebbitsPagesDatabaseAndStore, -} from "../stores/subplebbits-pages"; + resetCommunitiesPagesStore, + resetCommunitiesPagesDatabaseAndStore, +} from "../stores/communities-pages"; import { resetAuthorsCommentsStore, resetAuthorsCommentsDatabaseAndStore, @@ -155,9 +155,9 @@ const resetStores = async () => { await resetRepliesPagesStore(); await resetRepliesStore(); await resetAuthorsCommentsStore(); - await resetSubplebbitsPagesStore(); + await resetCommunitiesPagesStore(); await resetFeedsStore(); - await resetSubplebbitsStore(); + await resetCommunitiesStore(); await resetCommentsStore(); // always accounts last because it has async initialization await resetAccountsStore(); @@ -167,9 +167,9 @@ const resetDatabasesAndStores = async () => { await resetRepliesPagesDatabaseAndStore(); await resetRepliesDatabaseAndStore(); await resetAuthorsCommentsDatabaseAndStore(); - await resetSubplebbitsPagesDatabaseAndStore(); + await resetCommunitiesPagesDatabaseAndStore(); await resetFeedsDatabaseAndStore(); - await resetSubplebbitsDatabaseAndStore(); + await resetCommunitiesDatabaseAndStore(); await resetCommentsDatabaseAndStore(); // always accounts last because it has async initialization await resetAccountsDatabaseAndStore(); diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index d0af9b03..a7175054 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -1,3 +1,3 @@ -import utils from './utils' -export * from './utils' -export default utils +import utils from "./utils"; +export * from "./utils"; +export default utils; diff --git a/src/lib/utils/utils.test.ts b/src/lib/utils/utils.test.ts index fede7d60..4f67354b 100644 --- a/src/lib/utils/utils.test.ts +++ b/src/lib/utils/utils.test.ts @@ -2,11 +2,11 @@ import utils from "./utils"; describe("utils", () => { describe("repliesAreValid", () => { - test("accepts reply pages when comment and reply subplebbit addresses are .eth/.bso aliases", async () => { + test("accepts reply pages when comment and reply community addresses are .eth/.bso aliases", async () => { const comment = { cid: "comment-1", depth: 0, - subplebbitAddress: "music-posting.bso", + communityAddress: "music-posting.bso", replies: { pages: { new: { @@ -15,7 +15,7 @@ describe("utils", () => { cid: "reply-1", depth: 1, parentCid: "comment-1", - subplebbitAddress: "music-posting.eth", + communityAddress: "music-posting.eth", }, ], }, diff --git a/src/lib/utils/utils.ts b/src/lib/utils/utils.ts index 83ce1bc1..2b45ed9a 100644 --- a/src/lib/utils/utils.ts +++ b/src/lib/utils/utils.ts @@ -3,7 +3,7 @@ import QuickLru from "quick-lru"; import Logger from "@plebbit/plebbit-logger"; import PlebbitJs from "../plebbit-js"; import { Comment } from "../../types"; -import { areEquivalentSubplebbitAddresses } from "../subplebbit-address"; +import { areEquivalentCommunityAddresses } from "../community-address"; const log = Logger("bitsocial-react-hooks:utils"); const merge = (...args: any) => { @@ -278,19 +278,19 @@ const pageClientsOnStateChange = (clients: any, onStateChange: Function) => { } }; -export const subplebbitPostsCacheExpired = (subplebbit: any) => { - // NOTE: fetchedAt is undefined on owner subplebbits - if (!subplebbit?.fetchedAt) { +export const communityPostsCacheExpired = (community: any) => { + // NOTE: fetchedAt is undefined on owner communities + if (!community?.fetchedAt) { false; } - // if subplebbit cache is older than 1 hour, its subplebbit.posts are considered expired + // if community cache is older than 1 hour, its community.posts are considered expired const oneHourAgo = Date.now() / 1000 - 60 * 60; - return oneHourAgo > subplebbit.fetchedAt; + return oneHourAgo > community.fetchedAt; }; export const removeInvalidComments = async ( comments: Comment[], - { validateReplies, blockSubplebbit }: any, + { validateReplies, blockCommunity }: any, plebbit: any, ) => { if (!comments.length) { @@ -298,37 +298,37 @@ export const removeInvalidComments = async ( } const isValid = await Promise.all( comments.map((comment) => - commentIsValid(comment, { validateReplies, blockSubplebbit }, plebbit), + commentIsValid(comment, { validateReplies, blockCommunity }, plebbit), ), ); const validComments = comments.filter((_, i) => isValid[i]); return validComments; }; -const subplebbitsWithInvalidComments: { [subplebbitAddress: string]: boolean } = {}; +const communitiesWithInvalidComments: { [communityAddress: string]: boolean } = {}; export const commentIsValid = async ( comment: Comment, - { validateReplies, blockSubplebbit }: any = {}, + { validateReplies, blockCommunity }: any = {}, plebbit: any, ) => { validateReplies = Boolean(validateReplies); - if (blockSubplebbit === undefined || blockSubplebbit === null) { - blockSubplebbit = true; + if (blockCommunity === undefined || blockCommunity === null) { + blockCommunity = true; } if (!comment) { return false; } - if (subplebbitsWithInvalidComments[comment.subplebbitAddress]) { + if (communitiesWithInvalidComments[comment.communityAddress]) { console.log( - `subplebbit '${comment.subplebbitAddress}' had an invalid comment, invalidate all its future comments to avoid wasting resources`, + `community '${comment.communityAddress}' had an invalid comment, invalidate all its future comments to avoid wasting resources`, ); return false; } try { await plebbit.validateComment(comment, { validateReplies }); } catch (e) { - if (blockSubplebbit) { - subplebbitsWithInvalidComments[comment.subplebbitAddress] = true; + if (blockCommunity) { + communitiesWithInvalidComments[comment.communityAddress] = true; } console.log("invalid comment", { comment, error: e }); return false; @@ -338,19 +338,19 @@ export const commentIsValid = async ( const repliesAreValid = async ( comment: Comment, - { validateReplies, blockSubplebbit }: any = {}, + { validateReplies, blockCommunity }: any = {}, plebbit: any, ) => { validateReplies = Boolean(validateReplies); - if (blockSubplebbit === undefined || blockSubplebbit === null) { - blockSubplebbit = true; + if (blockCommunity === undefined || blockCommunity === null) { + blockCommunity = true; } if (!comment) { return false; } - if (subplebbitsWithInvalidComments[comment.subplebbitAddress]) { + if (communitiesWithInvalidComments[comment.communityAddress]) { console.log( - `subplebbit '${comment.subplebbitAddress}' had an invalid comment, invalidate all its future comments to avoid wasting resources`, + `community '${comment.communityAddress}' had an invalid comment, invalidate all its future comments to avoid wasting resources`, ); return false; } @@ -360,17 +360,17 @@ const repliesAreValid = async ( // manual validation for (const reply of replies) { if ( - !areEquivalentSubplebbitAddresses(reply.subplebbitAddress, comment.subplebbitAddress) || + !areEquivalentCommunityAddresses(reply.communityAddress, comment.communityAddress) || reply.depth !== comment.depth + 1 || reply.parentCid !== comment.cid ) { - if (blockSubplebbit) { - subplebbitsWithInvalidComments[comment.subplebbitAddress] = true; + if (blockCommunity) { + communitiesWithInvalidComments[comment.communityAddress] = true; } console.log("invalid comment", { comment: reply, error: - "!areEquivalentSubplebbitAddresses(reply.subplebbitAddress, comment.subplebbitAddress) || reply.depth !== comment.depth + 1 || reply.parentCid !== comment.cid", + "!areEquivalentCommunityAddresses(reply.communityAddress, comment.communityAddress) || reply.depth !== comment.depth + 1 || reply.parentCid !== comment.cid", }); return false; } @@ -379,12 +379,12 @@ const repliesAreValid = async ( // signature verification try { const promises = replies.map((reply) => - commentIsValid(reply, { validateReplies: false, blockSubplebbit: true }, plebbit), + commentIsValid(reply, { validateReplies: false, blockCommunity: true }, plebbit), ); await Promise.all(promises); } catch (e: any) { - if (blockSubplebbit) { - subplebbitsWithInvalidComments[comment.subplebbitAddress] = true; + if (blockCommunity) { + communitiesWithInvalidComments[comment.communityAddress] = true; } console.log("invalid comment", { comment, error: e }); return false; @@ -404,7 +404,7 @@ const utils = { retryInfinityMaxTimeout: 1000 * 60 * 60 * 24, clientsOnStateChange, pageClientsOnStateChange, - subplebbitPostsCacheExpired, + communityPostsCacheExpired, commentIsValid, removeInvalidComments, repliesAreValid, diff --git a/src/lib/validator.ts b/src/lib/validator.ts index 5a90672c..3211baad 100644 --- a/src/lib/validator.ts +++ b/src/lib/validator.ts @@ -42,8 +42,8 @@ const validateAccountsActionsPublishCommentArguments = ({ "publishComment publishCommentOptions.onError not a function", ); assert( - typeof publishCommentOptions.subplebbitAddress === "string", - "publishComment publishCommentOptions.subplebbitAddress not a string", + typeof publishCommentOptions.communityAddress === "string", + "publishComment publishCommentOptions.communityAddress not a string", ); assert( !publishCommentOptions.parentCid || typeof publishCommentOptions.parentCid === "string", @@ -112,8 +112,8 @@ const validateAccountsActionsPublishVoteArguments = ({ "publishVote publishVoteOptions.onError not a function", ); assert( - typeof publishVoteOptions.subplebbitAddress === "string", - "publishVote publishVoteOptions.subplebbitAddress not a string", + typeof publishVoteOptions.communityAddress === "string", + "publishVote publishVoteOptions.communityAddress not a string", ); assert( typeof publishVoteOptions.commentCid === "string", @@ -162,8 +162,8 @@ const validateAccountsActionsPublishCommentEditArguments = ({ "publishCommentEditOptions publishCommentEditOptions.onError not a function", ); assert( - typeof publishCommentEditOptions.subplebbitAddress === "string", - "publishCommentEdit publishCommentEditOptions.subplebbitAddress not a string", + typeof publishCommentEditOptions.communityAddress === "string", + "publishCommentEdit publishCommentEditOptions.communityAddress not a string", ); assert( typeof publishCommentEditOptions.commentCid === "string", @@ -207,8 +207,8 @@ const validateAccountsActionsPublishCommentModerationArguments = ({ "publishCommentModerationOptions publishCommentModerationOptions.onError not a function", ); assert( - typeof publishCommentModerationOptions.subplebbitAddress === "string", - "publishCommentModeration publishCommentModerationOptions.subplebbitAddress not a string", + typeof publishCommentModerationOptions.communityAddress === "string", + "publishCommentModeration publishCommentModerationOptions.communityAddress not a string", ); assert( typeof publishCommentModerationOptions.commentCid === "string", @@ -226,50 +226,47 @@ const validateAccountsActionsPublishCommentModerationArguments = ({ ); }; -const validateAccountsActionsPublishSubplebbitEditArguments = ({ - subplebbitAddress, - publishSubplebbitEditOptions, +const validateAccountsActionsPublishCommunityEditArguments = ({ + communityAddress, + publishCommunityEditOptions, accountName, account, }: any) => { assert( !accountName || typeof accountName === "string", - `publishSubplebbitEdit accountName '${accountName}' not a string`, + `publishCommunityEdit accountName '${accountName}' not a string`, ); - assert(accountName !== "", `publishSubplebbitEdit accountName argument is empty string`); + assert(accountName !== "", `publishCommunityEdit accountName argument is empty string`); assert( !accountName || account, - `publishSubplebbitEdit no account with name '${accountName}' in accountsStore`, + `publishCommunityEdit no account with name '${accountName}' in accountsStore`, ); assert( - publishSubplebbitEditOptions && typeof publishSubplebbitEditOptions === "object", - "publishSubplebbitEdit publishSubplebbitEditOptions not an object", + publishCommunityEditOptions && typeof publishCommunityEditOptions === "object", + "publishCommunityEdit publishCommunityEditOptions not an object", ); assert( - typeof publishSubplebbitEditOptions.onChallenge === "function", - "publishSubplebbitEdit publishSubplebbitEditOptions.onChallenge not a function", + typeof publishCommunityEditOptions.onChallenge === "function", + "publishCommunityEdit publishCommunityEditOptions.onChallenge not a function", ); assert( - typeof publishSubplebbitEditOptions.onChallengeVerification === "function", - "publishSubplebbitEdit publishSubplebbitEditOptions.onChallengeVerification not a function", + typeof publishCommunityEditOptions.onChallengeVerification === "function", + "publishCommunityEdit publishCommunityEditOptions.onChallengeVerification not a function", ); assert( - !publishSubplebbitEditOptions.onError || - typeof publishSubplebbitEditOptions.onError === "function", - "publishSubplebbitEdit publishSubplebbitEditOptions.onError not a function", + !publishCommunityEditOptions.onError || + typeof publishCommunityEditOptions.onError === "function", + "publishCommunityEdit publishCommunityEditOptions.onError not a function", ); + assert(communityAddress !== "", `publishCommunityEdit communityAddress argument is empty string`); assert( - subplebbitAddress !== "", - `publishSubplebbitEdit subplebbitAddress argument is empty string`, + typeof communityAddress === "string", + "publishCommunityEdit communityAddress not a string", ); assert( - typeof subplebbitAddress === "string", - "publishSubplebbitEdit subplebbitAddress not a string", - ); - assert( - !publishSubplebbitEditOptions.timestamp || - typeof publishSubplebbitEditOptions.timestamp === "number", - "publishSubplebbitEdit publishSubplebbitEditOptions.timestamp is not a number", + !publishCommunityEditOptions.timestamp || + typeof publishCommunityEditOptions.timestamp === "number", + "publishCommunityEdit publishCommunityEditOptions.timestamp is not a number", ); }; @@ -383,31 +380,31 @@ const validateUseCommentsArguments = (commentCids: any, account: any) => { ); }; -const validateUseSubplebbitArguments = (subplebbitAddress: any, account: any) => { +const validateUseCommunityArguments = (communityAddress: any, account: any) => { assert( - typeof subplebbitAddress === "string", - `useSubplebbit subplebbitAddress '${subplebbitAddress}' not a string`, + typeof communityAddress === "string", + `useCommunity communityAddress '${communityAddress}' not a string`, ); assert( account?.plebbit && typeof account?.plebbit === "object", - `useSubplebbit account.plebbit '${account?.plebbit}' not an object`, + `useCommunity account.plebbit '${account?.plebbit}' not an object`, ); }; -const validateUseSubplebbitsArguments = (subplebbitAddresses: any, account: any) => { +const validateUseCommunitiesArguments = (communityAddresses: any, account: any) => { assert( - Array.isArray(subplebbitAddresses), - `useSubplebbit subplebbitAddresses '${toString(subplebbitAddresses)}' not an array`, + Array.isArray(communityAddresses), + `useCommunity communityAddresses '${toString(communityAddresses)}' not an array`, ); - for (const subplebbitAddress of subplebbitAddresses) { + for (const communityAddress of communityAddresses) { assert( - typeof subplebbitAddress === "string", - `useSubplebbits subplebbitAddresses '${toString(subplebbitAddresses)}' subplebbitAddress '${toString(subplebbitAddress)}' not a string`, + typeof communityAddress === "string", + `useCommunities communityAddresses '${toString(communityAddresses)}' communityAddress '${toString(communityAddress)}' not a string`, ); } assert( account?.plebbit && typeof account?.plebbit === "object", - `useSubplebbit account.plebbit '${account?.plebbit}' not an object`, + `useCommunity account.plebbit '${account?.plebbit}' not an object`, ); }; @@ -432,7 +429,7 @@ const validateFeedSortType = (sortType: any) => { assert(feedSortTypes.has(sortType), `invalid feed sort type '${sortType}'`); }; const validateUseFeedArguments = ( - subplebbitAddresses?: any, + communityAddresses?: any, sortType?: any, accountName?: any, postsPerPage?: any, @@ -440,15 +437,15 @@ const validateUseFeedArguments = ( newerThan?: any, accountComments?: any, ) => { - if (subplebbitAddresses) { + if (communityAddresses) { assert( - Array.isArray(subplebbitAddresses), - `useFeed subplebbitAddresses argument '${toString(subplebbitAddresses)}' not an array`, + Array.isArray(communityAddresses), + `useFeed communityAddresses argument '${toString(communityAddresses)}' not an array`, ); - for (const subplebbitAddress of subplebbitAddresses) { + for (const communityAddress of communityAddresses) { assert( - typeof subplebbitAddress === "string", - `useFeed subplebbitAddresses argument '${toString(subplebbitAddresses)}' subplebbitAddress '${toString(subplebbitAddress)}' not a string`, + typeof communityAddress === "string", + `useFeed communityAddresses argument '${toString(communityAddresses)}' communityAddress '${toString(communityAddress)}' not a string`, ); } } @@ -494,16 +491,16 @@ const validateUseBufferedFeedsArguments = (feedsOptions?: any, accountName?: any Array.isArray(feedsOptions), `useBufferedFeeds feedsOptions argument '${toString(feedsOptions)}' not an array`, ); - for (const { subplebbitAddresses, sortType, postsPerPage, filter, newerThan } of feedsOptions) { - if (subplebbitAddresses) { + for (const { communityAddresses, sortType, postsPerPage, filter, newerThan } of feedsOptions) { + if (communityAddresses) { assert( - Array.isArray(subplebbitAddresses), - `useBufferedFeeds feedOptions.subplebbitAddresses argument '${toString(subplebbitAddresses)}' not an array`, + Array.isArray(communityAddresses), + `useBufferedFeeds feedOptions.communityAddresses argument '${toString(communityAddresses)}' not an array`, ); - for (const subplebbitAddress of subplebbitAddresses) { + for (const communityAddress of communityAddresses) { assert( - typeof subplebbitAddress === "string", - `useBufferedFeeds feedOptions.subplebbitAddresses argument '${toString(subplebbitAddresses)}' subplebbitAddress '${toString(subplebbitAddress)}' not a string`, + typeof communityAddress === "string", + `useBufferedFeeds feedOptions.communityAddresses argument '${toString(communityAddresses)}' communityAddress '${toString(communityAddress)}' not a string`, ); } } @@ -619,7 +616,7 @@ const validator = { validateAccountsActionsPublishCommentArguments, validateAccountsActionsPublishCommentEditArguments, validateAccountsActionsPublishCommentModerationArguments, - validateAccountsActionsPublishSubplebbitEditArguments, + validateAccountsActionsPublishCommunityEditArguments, validateAccountsActionsPublishVoteArguments, validateAccountsActionsExportAccountArguments, validateAccountsActionsSetAccountsOrderArguments, @@ -630,8 +627,8 @@ const validator = { validateAccountsDatabaseAccountNames, validateUseCommentArguments, validateUseCommentsArguments, - validateUseSubplebbitArguments, - validateUseSubplebbitsArguments, + validateUseCommunityArguments, + validateUseCommunitiesArguments, validateFeedSortType, validateUseFeedArguments, validateUseBufferedFeedsArguments, diff --git a/src/stores/accounts/account-generator.ts b/src/stores/accounts/account-generator.ts index f23c1f27..df95b1e6 100644 --- a/src/stores/accounts/account-generator.ts +++ b/src/stores/accounts/account-generator.ts @@ -3,7 +3,7 @@ import validator from "../../lib/validator"; import chain from "../../lib/chain"; import { v4 as uuid } from "uuid"; import accountsDatabase from "./accounts-database"; -import { Accounts, AccountSubplebbit, ChainProviders } from "../../types"; +import { Accounts, AccountCommunity, ChainProviders } from "../../types"; import Logger from "@plebbit/plebbit-logger"; const log = Logger("bitsocial-react-hooks:accounts:stores"); @@ -102,8 +102,8 @@ const generateDefaultAccount = async () => { const accountName = await getNextAvailableDefaultAccountName(); - // subplebbits where the account has a role, like moderator, admin, owner, etc. - const subplebbits: { [subplebbitAddress: string]: AccountSubplebbit } = {}; + // communities where the account has a role, like moderator, admin, owner, etc. + const communities: { [communityAddress: string]: AccountCommunity } = {}; const account = { id: uuid(), @@ -116,7 +116,7 @@ const generateDefaultAccount = async () => { subscriptions: [], blockedAddresses: {}, blockedCids: {}, - subplebbits, + communities, mediaIpfsGatewayUrl: defaultMediaIpfsGatewayUrl, }; return account; diff --git a/src/stores/accounts/accounts-actions-internal.test.ts b/src/stores/accounts/accounts-actions-internal.test.ts index a4ec884b..5fbce15a 100644 --- a/src/stores/accounts/accounts-actions-internal.test.ts +++ b/src/stores/accounts/accounts-actions-internal.test.ts @@ -38,7 +38,7 @@ describe("accounts-actions-internal", () => { const plainComment = { cid: "plain-cid", author: { address: account.author.address }, - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", depth: 0, }; await accountsDatabase.addAccountComment(account.id, { @@ -267,14 +267,14 @@ describe("accounts-actions-internal", () => { const validReply = { cid: "reply-p1", author: { address: "r1" }, - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", depth: 1, parentCid: "cid-undefined-comments", }; const comment = new Comment({ cid: "cid-undefined-comments", author: { address: account.author.address }, - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", depth: 0, }); (comment as any).replies = { @@ -311,7 +311,7 @@ describe("accounts-actions-internal", () => { const updatedComment = { cid: "cid-undefined-comments", - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", depth: 0, author: { address: account.author.address }, replies: { @@ -339,14 +339,14 @@ describe("accounts-actions-internal", () => { const validReply = { cid: "reply-p1", author: { address: "r1" }, - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", depth: 1, parentCid: "cid-null-page", }; const comment = new Comment({ cid: "cid-null-page", author: { address: account.author.address }, - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", depth: 0, }); (comment as any).replies = { @@ -381,7 +381,7 @@ describe("accounts-actions-internal", () => { const updatedComment = { cid: "cid-null-page", - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", depth: 0, author: { address: account.author.address }, replies: { @@ -443,7 +443,7 @@ describe("accounts-actions-internal", () => { const comment = new Comment({ cid: "cid-no-pages", author: { address: account.author.address }, - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", depth: 0, }); (comment as any).replies = {}; @@ -486,13 +486,13 @@ describe("accounts-actions-internal", () => { const comment = new Comment({ cid: "cid-replies", author: { address: account.author.address }, - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", depth: 0, }); const validReply = { cid: "reply-1", author: { address: "r1" }, - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", depth: 1, parentCid: "cid-replies", }; @@ -531,7 +531,7 @@ describe("accounts-actions-internal", () => { const updatedComment = { ...comment, cid: "cid-replies", - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", depth: 0, replies: { pages: { @@ -557,7 +557,7 @@ describe("accounts-actions-internal", () => { const comment = new Comment({ cid: "cid-replies", author: { address: account.author.address }, - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", depth: 0, }); (comment as any).replies = { @@ -567,7 +567,7 @@ describe("accounts-actions-internal", () => { { cid: "reply-1", author: { address: "r1" }, - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", depth: 1, parentCid: "cid-replies", }, @@ -601,7 +601,7 @@ describe("accounts-actions-internal", () => { const updatedComment = { cid: "cid-replies", - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", depth: 0, author: { address: account.author.address }, replies: { @@ -611,7 +611,7 @@ describe("accounts-actions-internal", () => { { cid: "reply-1", author: { address: "r1" }, - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", depth: 1, parentCid: "cid-replies", }, @@ -637,13 +637,13 @@ describe("accounts-actions-internal", () => { const comment = new Comment({ cid: "cid-mixed-pages", author: { address: account.author.address }, - subplebbitAddress: subAddr, + communityAddress: subAddr, depth: 0, }); const validReply = { cid: "reply-from-p1", author: { address: "r1" }, - subplebbitAddress: subAddr, + communityAddress: subAddr, depth: 1, parentCid: "cid-mixed-pages", }; @@ -682,7 +682,7 @@ describe("accounts-actions-internal", () => { const updatedComment = { cid: "cid-mixed-pages", - subplebbitAddress: subAddr, + communityAddress: subAddr, depth: 0, author: { address: account.author.address }, replies: { @@ -707,7 +707,7 @@ describe("accounts-actions-internal", () => { const comment = new Comment({ cid: "cid-marked", author: { address: account.author.address }, - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", }); (comment as any).replies = { pages: { @@ -1094,54 +1094,54 @@ describe("accounts-actions-internal", () => { }); }); - describe("addSubplebbitRoleToAccountsSubplebbits", () => { + describe("addCommunityRoleToAccountsCommunities", () => { beforeEach(async () => { await testUtils.resetDatabasesAndStores(); }); - test("no subplebbit: returns early", async () => { - await accountsActionsInternal.addSubplebbitRoleToAccountsSubplebbits(null as any); - await accountsActionsInternal.addSubplebbitRoleToAccountsSubplebbits(undefined as any); + test("no community: returns early", async () => { + await accountsActionsInternal.addCommunityRoleToAccountsCommunities(null as any); + await accountsActionsInternal.addCommunityRoleToAccountsCommunities(undefined as any); }); - test("subplebbit with no roles property: treats as no roles (branch 337)", async () => { + test("community with no roles property: treats as no roles (branch 337)", async () => { const account = Object.values(accountsStore.getState().accounts)[0]; - const subplebbit = { address: "sub.eth" } as any; + const community = { address: "sub.eth" } as any; accountsStore.setState((s) => ({ accounts: { ...s.accounts, [account.id]: { ...account, - subplebbits: { "sub.eth": { role: { role: "admin" } } }, + communities: { "sub.eth": { role: { role: "admin" } } }, }, }, })); - await accountsActionsInternal.addSubplebbitRoleToAccountsSubplebbits(subplebbit); + await accountsActionsInternal.addCommunityRoleToAccountsCommunities(community); const acc = accountsStore.getState().accounts[account.id]; - expect(acc?.subplebbits?.["sub.eth"]).toBeUndefined(); + expect(acc?.communities?.["sub.eth"]).toBeUndefined(); }); - test("subplebbit with no roles, account has no subplebbit: no-op (branch 340 false)", async () => { + test("community with no roles, account has no community: no-op (branch 340 false)", async () => { const account = Object.values(accountsStore.getState().accounts)[0]; - const subplebbit = { address: "other.eth" } as any; + const community = { address: "other.eth" } as any; accountsStore.setState((s) => ({ accounts: { ...s.accounts, - [account.id]: { ...account, subplebbits: {} }, + [account.id]: { ...account, communities: {} }, }, })); - await accountsActionsInternal.addSubplebbitRoleToAccountsSubplebbits(subplebbit); + await accountsActionsInternal.addCommunityRoleToAccountsCommunities(community); const acc = accountsStore.getState().accounts[account.id]; - expect(acc?.subplebbits?.["other.eth"]).toBeUndefined(); + expect(acc?.communities?.["other.eth"]).toBeUndefined(); }); test("no-change: account already has role, no add/remove", async () => { const account = Object.values(accountsStore.getState().accounts)[0]; - const subplebbit = { + const community = { address: "sub.eth", roles: { [account.author.address]: { role: "admin" } }, }; @@ -1150,39 +1150,39 @@ describe("accounts-actions-internal", () => { ...s.accounts, [account.id]: { ...account, - subplebbits: { "sub.eth": { role: { role: "admin" } } }, + communities: { "sub.eth": { role: { role: "admin" } } }, }, }, })); - await accountsActionsInternal.addSubplebbitRoleToAccountsSubplebbits(subplebbit as any); + await accountsActionsInternal.addCommunityRoleToAccountsCommunities(community as any); const acc = accountsStore.getState().accounts[account.id]; - expect(acc?.subplebbits?.["sub.eth"]).toBeDefined(); + expect(acc?.communities?.["sub.eth"]).toBeDefined(); }); - test("add: adds role when subplebbit has role for account", async () => { + test("add: adds role when community has role for account", async () => { const account = Object.values(accountsStore.getState().accounts)[0]; - const subplebbit = { + const community = { address: "new-sub.eth", roles: { [account.author.address]: { role: "moderator" } }, }; accountsStore.setState((s) => ({ accounts: { ...s.accounts, - [account.id]: { ...account, subplebbits: {} }, + [account.id]: { ...account, communities: {} }, }, })); - await accountsActionsInternal.addSubplebbitRoleToAccountsSubplebbits(subplebbit as any); + await accountsActionsInternal.addCommunityRoleToAccountsCommunities(community as any); const acc = accountsStore.getState().accounts[account.id]; - expect(acc?.subplebbits?.["new-sub.eth"]).toEqual({ role: { role: "moderator" } }); + expect(acc?.communities?.["new-sub.eth"]).toEqual({ role: { role: "moderator" } }); }); - test("remove: removes role when subplebbit no longer has role", async () => { + test("remove: removes role when community no longer has role", async () => { const account = Object.values(accountsStore.getState().accounts)[0]; - const subplebbit = { + const community = { address: "old-sub.eth", roles: {}, }; @@ -1191,15 +1191,15 @@ describe("accounts-actions-internal", () => { ...s.accounts, [account.id]: { ...account, - subplebbits: { "old-sub.eth": { role: { role: "admin" } } }, + communities: { "old-sub.eth": { role: { role: "admin" } } }, }, }, })); - await accountsActionsInternal.addSubplebbitRoleToAccountsSubplebbits(subplebbit as any); + await accountsActionsInternal.addCommunityRoleToAccountsCommunities(community as any); const acc = accountsStore.getState().accounts[account.id]; - expect(acc?.subplebbits?.["old-sub.eth"]).toBeUndefined(); + expect(acc?.communities?.["old-sub.eth"]).toBeUndefined(); }); }); }); diff --git a/src/stores/accounts/accounts-actions-internal.ts b/src/stores/accounts/accounts-actions-internal.ts index c0c5f5a9..b95f0502 100644 --- a/src/stores/accounts/accounts-actions-internal.ts +++ b/src/stores/accounts/accounts-actions-internal.ts @@ -12,7 +12,7 @@ import { Comment, AccountsComments, AccountCommentsReplies, - Subplebbit, + Community, } from "../../types"; import utils from "../../lib/utils"; @@ -116,7 +116,7 @@ export const startUpdatingAccountCommentOnCommentUpdateEvents = async ( const hasReplies = replyCount > 0; const repliesAreValid = await utils.repliesAreValid( updatedComment, - { validateReplies: false, blockSubplebbit: true }, + { validateReplies: false, blockCommunity: true }, account.plebbit, ); @@ -318,30 +318,30 @@ export const markNotificationsAsRead = async (account: Account) => { }); }; -// internal accounts action: if a subplebbit has a role with an account's address -// add it to the account.subplebbits database -export const addSubplebbitRoleToAccountsSubplebbits = async (subplebbit: Subplebbit) => { - if (!subplebbit) { +// internal accounts action: if a community has a role with an account's address +// add it to the account.communities database +export const addCommunityRoleToAccountsCommunities = async (community: Community) => { + if (!community) { return; } const { accounts } = accountsStore.getState(); assert(accounts, `can't use accountsStore.accountActions before initialized`); - // find subplebbit roles to add and remove - const getRole = (subplebbit: any, authorAddress: string) => - subplebbit.roles && subplebbit.roles[authorAddress]; - const getChange = (accounts: any, subplebbit: any) => { + // find community roles to add and remove + const getRole = (community: any, authorAddress: string) => + community.roles && community.roles[authorAddress]; + const getChange = (accounts: any, community: any) => { const toAdd: string[] = []; const toRemove: string[] = []; for (const accountId in accounts) { const account = accounts[accountId]; - const role = getRole(subplebbit, account.author.address); + const role = getRole(community, account.author.address); if (!role) { - if (account.subplebbits[subplebbit.address]) { + if (account.communities[community.address]) { toRemove.push(accountId); } } else { - if (!account.subplebbits[subplebbit.address]) { + if (!account.communities[community.address]) { toAdd.push(accountId); } } @@ -349,35 +349,35 @@ export const addSubplebbitRoleToAccountsSubplebbits = async (subplebbit: Subpleb return { toAdd, toRemove, hasChange: toAdd.length !== 0 || toRemove.length !== 0 }; }; - const { hasChange } = getChange(accounts, subplebbit); + const { hasChange } = getChange(accounts, community); if (!hasChange) { return; } accountsStore.setState(({ accounts }) => { - const { toAdd, toRemove, hasChange } = getChange(accounts, subplebbit); + const { toAdd, toRemove, hasChange } = getChange(accounts, community); const nextAccounts = { ...accounts }; // edit databases and build next accounts (toAdd implies role exists from getChange) for (const accountId of toAdd) { const account = { ...nextAccounts[accountId] }; - const role = subplebbit.roles![account.author.address]; - account.subplebbits = { - ...account.subplebbits, - [subplebbit.address]: { role }, + const role = community.roles![account.author.address]; + account.communities = { + ...account.communities, + [community.address]: { role }, }; nextAccounts[accountId] = account; accountsDatabase.addAccount(account); } for (const accountId of toRemove) { const account = { ...nextAccounts[accountId] }; - account.subplebbits = { ...account.subplebbits }; - delete account.subplebbits[subplebbit.address]; + account.communities = { ...account.communities }; + delete account.communities[community.address]; nextAccounts[accountId] = account; accountsDatabase.addAccount(account); } - log("accountsActions.addSubplebbitRoleToAccountsSubplebbits", { subplebbit, toAdd, toRemove }); + log("accountsActions.addCommunityRoleToAccountsCommunities", { community, toAdd, toRemove }); return { accounts: nextAccounts }; }); }; diff --git a/src/stores/accounts/accounts-actions.test.ts b/src/stores/accounts/accounts-actions.test.ts index 62a780f8..5a48d58c 100644 --- a/src/stores/accounts/accounts-actions.test.ts +++ b/src/stores/accounts/accounts-actions.test.ts @@ -110,8 +110,8 @@ function createRetryPlebbitMock() { } return m; } - async createSubplebbitEdit(opts: any) { - const e = await baseInstance.createSubplebbitEdit(opts); + async createCommunityEdit(opts: any) { + const e = await baseInstance.createCommunityEdit(opts); const orig = e.simulateChallengeVerificationEvent?.bind(e); if (orig) { let first = true; @@ -303,7 +303,7 @@ describe("accounts-actions", () => { }); const opts = { - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", content: "from named account", onChallenge: (ch: any, c: any) => c.publishChallengeAnswers(), onChallengeVerification: () => {}, @@ -326,7 +326,7 @@ describe("accounts-actions", () => { }); const opts = { - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", commentCid: "comment cid", vote: 1, onChallenge: (ch: any, v: any) => v.publishChallengeAnswers(), @@ -349,7 +349,7 @@ describe("accounts-actions", () => { }); const opts = { - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", commentCid: "comment cid", spoiler: true, onChallenge: (ch: any, e: any) => e.publishChallengeAnswers(), @@ -373,7 +373,7 @@ describe("accounts-actions", () => { }); const opts = { - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", commentCid: "comment cid", commentModeration: { locked: true }, onChallenge: (ch: any, m: any) => m.publishChallengeAnswers(), @@ -390,7 +390,7 @@ describe("accounts-actions", () => { expect(mods.length).toBeGreaterThan(0); }); - test("publishSubplebbitEdit with accountName uses named account", async () => { + test("publishCommunityEdit with accountName uses named account", async () => { await act(async () => { await accountsActions.createAccount(); await accountsActions.createAccount("SubEditAccount"); @@ -403,12 +403,12 @@ describe("accounts-actions", () => { }; await act(async () => { - await accountsActions.publishSubplebbitEdit("remote-sub.eth", opts, "SubEditAccount"); + await accountsActions.publishCommunityEdit("remote-sub.eth", opts, "SubEditAccount"); }); // no throw = success }); - test("createSubplebbit with accountName uses named account", async () => { + test("createCommunity with accountName uses named account", async () => { await act(async () => { await accountsActions.createAccount(); await accountsActions.createAccount("CreateSubAccount"); @@ -416,7 +416,7 @@ describe("accounts-actions", () => { let sub: any; await act(async () => { - sub = await accountsActions.createSubplebbit({ title: "My sub" }, "CreateSubAccount"); + sub = await accountsActions.createCommunity({ title: "My sub" }, "CreateSubAccount"); }); expect(sub?.address).toBeDefined(); }); @@ -451,7 +451,7 @@ describe("accounts-actions", () => { expect(accountNamesToAccountIds["Second 2"]).toBeDefined(); }); - test("deleteSubplebbit with accountName uses named account", async () => { + test("deleteCommunity with accountName uses named account", async () => { await act(async () => { await accountsActions.createAccount(); await accountsActions.createAccount("DelSubAccount"); @@ -459,10 +459,10 @@ describe("accounts-actions", () => { let sub: any; await act(async () => { - sub = await accountsActions.createSubplebbit({ title: "To delete" }, "DelSubAccount"); + sub = await accountsActions.createCommunity({ title: "To delete" }, "DelSubAccount"); }); await act(async () => { - await accountsActions.deleteSubplebbit(sub.address, "DelSubAccount"); + await accountsActions.deleteCommunity(sub.address, "DelSubAccount"); }); // no throw = success }); @@ -476,7 +476,7 @@ describe("accounts-actions", () => { await act(async () => { await accountsActions.publishComment( { - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", content: "to delete by name", onChallenge: (ch: any, c: any) => c.publishChallengeAnswers(), onChallengeVerification: () => {}, @@ -521,7 +521,7 @@ describe("accounts-actions", () => { await act(async () => { await accountsActions.publishComment( { - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", content: "to delete by cid", onChallenge: (ch: any, c: any) => c.publishChallengeAnswers(), onChallengeVerification: () => {}, @@ -547,19 +547,19 @@ describe("accounts-actions", () => { expect(after.length).toBe(0); }); - test("publishSubplebbitEdit asserts when address differs from subplebbitAddress", async () => { + test("publishCommunityEdit asserts when address differs from communityAddress", async () => { await act(async () => { await accountsActions.createAccount(); }); await expect( - accountsActions.publishSubplebbitEdit("remote-sub.eth", { + accountsActions.publishCommunityEdit("remote-sub.eth", { address: "other-sub.eth", title: "edited", onChallenge: (ch: any, e: any) => e.publishChallengeAnswers(), onChallengeVerification: () => {}, }), - ).rejects.toThrow("can't edit address of a remote subplebbit"); + ).rejects.toThrow("can't edit address of a remote community"); }); test("setAccount with author.address change updates only the eth wallet when using plebbit signer", async () => { @@ -650,7 +650,7 @@ describe("accounts-actions", () => { test("publishComment retries on challenge failure", async () => { const opts = { - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", content: "retry test", onChallenge: (ch: any, c: any) => c.publishChallengeAnswers(["4"]), onChallengeVerification: () => {}, @@ -670,7 +670,7 @@ describe("accounts-actions", () => { test("publishVote retries on challenge failure", async () => { const opts = { - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", commentCid: "cid", vote: 1, onChallenge: (ch: any, v: any) => v.publishChallengeAnswers(["4"]), @@ -689,7 +689,7 @@ describe("accounts-actions", () => { test("publishCommentEdit retries on challenge failure", async () => { const opts = { - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", commentCid: "cid", spoiler: true, onChallenge: (ch: any, e: any) => e.publishChallengeAnswers(["4"]), @@ -709,7 +709,7 @@ describe("accounts-actions", () => { test("publishCommentModeration retries on challenge failure", async () => { const opts = { - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", commentCid: "cid", commentModeration: { locked: true }, onChallenge: (ch: any, m: any) => m.publishChallengeAnswers(["4"]), @@ -727,7 +727,7 @@ describe("accounts-actions", () => { expect(mods.length).toBeGreaterThan(0); }); - test("publishSubplebbitEdit retries on challenge failure", async () => { + test("publishCommunityEdit retries on challenge failure", async () => { const opts = { title: "edited", onChallenge: (ch: any, e: any) => e.publishChallengeAnswers(["4"]), @@ -735,7 +735,7 @@ describe("accounts-actions", () => { }; await act(async () => { - await accountsActions.publishSubplebbitEdit("remote-sub.eth", opts); + await accountsActions.publishCommunityEdit("remote-sub.eth", opts); }); await new Promise((r) => setTimeout(r, 200)); @@ -764,7 +764,7 @@ describe("accounts-actions", () => { // publish a comment await act(async () => { await accountsActions.publishComment({ - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", content: "to delete", onChallenge: (ch: any, c: any) => c.publishChallengeAnswers(), onChallengeVerification: () => {}, @@ -814,7 +814,7 @@ describe("accounts-actions", () => { await act(async () => { await accountsActions.publishComment({ - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", content: "no-stop", onChallenge: (ch: any, c: any) => c.publishChallengeAnswers(), onChallengeVerification: () => {}, @@ -846,7 +846,7 @@ describe("accounts-actions", () => { }); const publishPromise = accountsActions.publishComment({ - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", content: "to-abandon", onChallenge: (ch: any, c: any) => c.publishChallengeAnswers(), onChallengeVerification: () => {}, @@ -878,7 +878,7 @@ describe("accounts-actions", () => { await act(async () => { await accountsActions.publishComment({ - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", content: "err-test", onChallenge: (ch: any, c: any) => c.publishChallengeAnswers(), onChallengeVerification: () => {}, @@ -908,7 +908,7 @@ describe("accounts-actions", () => { const onError = vi.fn(); const publishPromise = accountsActions.publishComment({ - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", content: "err-no-state", onChallenge: (ch: any, c: any) => c.publishChallengeAnswers(), onChallengeVerification: () => {}, @@ -943,7 +943,7 @@ describe("accounts-actions", () => { const onError = vi.fn(); await act(async () => { await accountsActions.publishComment({ - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", content: "err-cb", onChallenge: (ch: any, c: any) => c.publishChallengeAnswers(), onChallengeVerification: () => {}, @@ -959,7 +959,7 @@ describe("accounts-actions", () => { const onPublishingStateChange = vi.fn(); await act(async () => { await accountsActions.publishComment({ - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", content: "state-change", onChallenge: (ch: any, c: any) => c.publishChallengeAnswers(), onChallengeVerification: () => {}, @@ -982,7 +982,7 @@ describe("accounts-actions", () => { const onPublishingStateChange = vi.fn(); await act(async () => { await accountsActions.publishComment({ - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", content: "with link", link: "https://example.com/image.png", onChallenge: (ch: any, c: any) => c.publishChallengeAnswers(), @@ -1013,7 +1013,7 @@ describe("accounts-actions", () => { await act(async () => { await accountsActions.publishComment({ - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", content: "chain", onChallenge: (ch: any, c: any) => c.publishChallengeAnswers(), onChallengeVerification: () => {}, @@ -1050,7 +1050,7 @@ describe("accounts-actions", () => { await act(async () => { await accountsActions.publishComment({ - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", content: "no-state-pub", onChallenge: (ch: any, c: any) => c.publishChallengeAnswers(), onChallengeVerification: () => {}, @@ -1085,7 +1085,7 @@ describe("accounts-actions", () => { await act(async () => { await accountsActions.publishComment({ - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", content: "no-state", onChallenge: (ch: any, c: any) => c.publishChallengeAnswers(), onChallengeVerification: () => {}, @@ -1111,7 +1111,7 @@ describe("accounts-actions", () => { await act(async () => { await accountsActions.publishComment({ - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", content: "ipfs", onChallenge: (ch: any, c: any) => c.publishChallengeAnswers(), onChallengeVerification: () => {}, @@ -1136,7 +1136,7 @@ describe("accounts-actions", () => { const onError = vi.fn(); await act(async () => { await accountsActions.publishComment({ - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", content: "fail", onChallenge: (ch: any, c: any) => c.publishChallengeAnswers(), onChallengeVerification: () => {}, @@ -1156,7 +1156,7 @@ describe("accounts-actions", () => { await act(async () => { await accountsActions.publishComment({ - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", content: "cid", onChallenge: (ch: any, c: any) => c.publishChallengeAnswers(), onChallengeVerification: () => {}, @@ -1170,7 +1170,7 @@ describe("accounts-actions", () => { test("importAccount startUpdatingAccountCommentOnCommentUpdateEvents error: catch runs", async () => { await act(async () => { await accountsActions.publishComment({ - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", content: "for-import", onChallenge: (ch: any, c: any) => c.publishChallengeAnswers(), onChallengeVerification: () => {}, @@ -1207,7 +1207,7 @@ describe("accounts-actions", () => { const onError = vi.fn(); await act(async () => { await accountsActions.publishCommentEdit({ - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", commentCid: "cid", spoiler: true, onChallenge: (ch: any, e: any) => e.publishChallengeAnswers(["4"]), @@ -1232,7 +1232,7 @@ describe("accounts-actions", () => { const onError = vi.fn(); await act(async () => { await accountsActions.publishCommentModeration({ - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", commentCid: "cid", commentModeration: { locked: true }, onChallenge: (ch: any, m: any) => m.publishChallengeAnswers(["4"]), @@ -1245,10 +1245,10 @@ describe("accounts-actions", () => { expect(onError).toHaveBeenCalled(); }); - test("publishSubplebbitEdit publish throws: onError called", async () => { + test("publishCommunityEdit publish throws: onError called", async () => { const account = Object.values(accountsStore.getState().accounts)[0]; - const origCreate = account.plebbit.createSubplebbitEdit.bind(account.plebbit); - vi.spyOn(account.plebbit, "createSubplebbitEdit").mockImplementation(async (opts: any) => { + const origCreate = account.plebbit.createCommunityEdit.bind(account.plebbit); + vi.spyOn(account.plebbit, "createCommunityEdit").mockImplementation(async (opts: any) => { const e = await origCreate(opts); vi.spyOn(e, "publish").mockRejectedValueOnce(new Error("publish failed")); return e; @@ -1256,7 +1256,7 @@ describe("accounts-actions", () => { const onError = vi.fn(); await act(async () => { - await accountsActions.publishSubplebbitEdit("remote-sub.eth", { + await accountsActions.publishCommunityEdit("remote-sub.eth", { title: "edited", onChallenge: (ch: any, e: any) => e.publishChallengeAnswers(["4"]), onChallengeVerification: () => {}, diff --git a/src/stores/accounts/accounts-actions.ts b/src/stores/accounts/accounts-actions.ts index d56317bf..acb87a6a 100644 --- a/src/stores/accounts/accounts-actions.ts +++ b/src/stores/accounts/accounts-actions.ts @@ -1,7 +1,7 @@ // public accounts actions that are called by the user import accountsStore, { listeners } from "./accounts-store"; -import subplebbitsStore from "../subplebbits"; +import communitiesStore from "../communities"; import accountsDatabase from "./accounts-database"; import accountGenerator from "./account-generator"; import Logger from "@plebbit/plebbit-logger"; @@ -18,14 +18,14 @@ import { PublishVoteOptions, PublishCommentEditOptions, PublishCommentModerationOptions, - PublishSubplebbitEditOptions, - CreateSubplebbitOptions, - Subplebbits, + PublishCommunityEditOptions, + CreateCommunityOptions, + Communities, AccountComment, } from "../../types"; import * as accountsActionsInternal from "./accounts-actions-internal"; import { - getAccountSubplebbits, + getAccountCommunities, getCommentCidsToAccountsComments, fetchCommentLinkDimensions, getAccountCommentDepth, @@ -209,11 +209,11 @@ export const setAccount = async (account: Account) => { `cannot set account with account.id '${account.id}' id does not exist in database`, ); - // if author.address has changed, add new subplebbit roles of author.address found in subplebbits store + // if author.address has changed, add new community roles of author.address found in communities store // TODO: add test to check if roles get added if (account.author.address !== accounts[account.id].author.address) { - const subplebbits = getAccountSubplebbits(account, subplebbitsStore.getState().subplebbits); - account = { ...account, subplebbits }; + const communities = getAccountCommunities(account, communitiesStore.getState().communities); + account = { ...account, communities }; // wallet.signature changes if author.address changes if (account.author.wallets?.eth) { @@ -283,11 +283,11 @@ export const importAccount = async (serializedAccount: string) => { `accountsActions.importAccount failed JSON.stringify json serializedAccount '${serializedAccount}'`, ); - // add subplebbit roles already in subplebbits store to imported account + // add community roles already in communities store to imported account // TODO: add test to check if roles get added - const subplebbits = getAccountSubplebbits( + const communities = getAccountCommunities( imported.account, - subplebbitsStore.getState().subplebbits, + communitiesStore.getState().communities, ); // if imported.account.name already exists, add ' 2', don't overwrite @@ -302,7 +302,7 @@ export const importAccount = async (serializedAccount: string) => { const newAccount = { ...generatedAccount, ...imported.account, - subplebbits, + communities, id: generatedAccount.id, }; @@ -399,11 +399,11 @@ export const exportAccount = async (accountName?: string) => { return exportedAccountJson; }; -export const subscribe = async (subplebbitAddress: string, accountName?: string) => { +export const subscribe = async (communityAddress: string, accountName?: string) => { const { accounts, accountNamesToAccountIds, activeAccountId } = accountsStore.getState(); assert( - subplebbitAddress && typeof subplebbitAddress === "string", - `accountsActions.subscribe invalid subplebbitAddress '${subplebbitAddress}'`, + communityAddress && typeof communityAddress === "string", + `accountsActions.subscribe invalid communityAddress '${communityAddress}'`, ); assert( accounts && accountNamesToAccountIds && activeAccountId, @@ -420,24 +420,24 @@ export const subscribe = async (subplebbitAddress: string, accountName?: string) ); let subscriptions: string[] = account.subscriptions || []; - if (subscriptions.includes(subplebbitAddress)) { - throw Error(`account '${account.id}' already subscribed to '${subplebbitAddress}'`); + if (subscriptions.includes(communityAddress)) { + throw Error(`account '${account.id}' already subscribed to '${communityAddress}'`); } - subscriptions = [...subscriptions, subplebbitAddress]; + subscriptions = [...subscriptions, communityAddress]; const updatedAccount: Account = { ...account, subscriptions }; // update account in db async for instant feedback speed accountsDatabase.addAccount(updatedAccount); const updatedAccounts = { ...accounts, [updatedAccount.id]: updatedAccount }; - log("accountsActions.subscribe", { account: updatedAccount, accountName, subplebbitAddress }); + log("accountsActions.subscribe", { account: updatedAccount, accountName, communityAddress }); accountsStore.setState({ accounts: updatedAccounts }); }; -export const unsubscribe = async (subplebbitAddress: string, accountName?: string) => { +export const unsubscribe = async (communityAddress: string, accountName?: string) => { const { accounts, accountNamesToAccountIds, activeAccountId } = accountsStore.getState(); assert( - subplebbitAddress && typeof subplebbitAddress === "string", - `accountsActions.unsubscribe invalid subplebbitAddress '${subplebbitAddress}'`, + communityAddress && typeof communityAddress === "string", + `accountsActions.unsubscribe invalid communityAddress '${communityAddress}'`, ); assert( accounts && accountNamesToAccountIds && activeAccountId, @@ -454,17 +454,17 @@ export const unsubscribe = async (subplebbitAddress: string, accountName?: strin ); let subscriptions: string[] = account.subscriptions || []; - if (!subscriptions.includes(subplebbitAddress)) { - throw Error(`account '${account.id}' already unsubscribed from '${subplebbitAddress}'`); + if (!subscriptions.includes(communityAddress)) { + throw Error(`account '${account.id}' already unsubscribed from '${communityAddress}'`); } - // remove subplebbitAddress - subscriptions = subscriptions.filter((address) => address !== subplebbitAddress); + // remove communityAddress + subscriptions = subscriptions.filter((address) => address !== communityAddress); const updatedAccount: Account = { ...account, subscriptions }; // update account in db async for instant feedback speed accountsDatabase.addAccount(updatedAccount); const updatedAccounts = { ...accounts, [updatedAccount.id]: updatedAccount }; - log("accountsActions.unsubscribe", { account: updatedAccount, accountName, subplebbitAddress }); + log("accountsActions.unsubscribe", { account: updatedAccount, accountName, communityAddress }); accountsStore.setState({ accounts: updatedAccounts }); }; @@ -1156,9 +1156,9 @@ export const publishCommentModeration = async ( }); }; -export const publishSubplebbitEdit = async ( - subplebbitAddress: string, - publishSubplebbitEditOptions: PublishSubplebbitEditOptions, +export const publishCommunityEdit = async ( + communityAddress: string, + publishCommunityEditOptions: PublishCommunityEditOptions, accountName?: string, ) => { const { accounts, accountNamesToAccountIds, activeAccountId } = accountsStore.getState(); @@ -1171,91 +1171,91 @@ export const publishSubplebbitEdit = async ( const accountId = accountNamesToAccountIds[accountName]; account = accounts[accountId]; } - validator.validateAccountsActionsPublishSubplebbitEditArguments({ - subplebbitAddress, - publishSubplebbitEditOptions, + validator.validateAccountsActionsPublishCommunityEditArguments({ + communityAddress, + publishCommunityEditOptions, accountName, account, }); - const subplebbitEditOptions = { ...publishSubplebbitEditOptions }; - delete subplebbitEditOptions.onChallenge; - delete subplebbitEditOptions.onChallengeVerification; - delete subplebbitEditOptions.onError; - delete subplebbitEditOptions.onPublishingStateChange; + const communityEditOptions = { ...publishCommunityEditOptions }; + delete communityEditOptions.onChallenge; + delete communityEditOptions.onChallengeVerification; + delete communityEditOptions.onError; + delete communityEditOptions.onPublishingStateChange; - // account is the owner of the subplebbit and can edit it locally, no need to publish - const localSubplebbitAddresses = account.plebbit.subplebbits; - if (localSubplebbitAddresses.includes(subplebbitAddress)) { - await subplebbitsStore + // account is the owner of the community and can edit it locally, no need to publish + const localCommunityAddresses = account.plebbit.communities; + if (localCommunityAddresses.includes(communityAddress)) { + await communitiesStore .getState() - .editSubplebbit(subplebbitAddress, subplebbitEditOptions, account); - // create fake success challenge verification for consistent behavior with remote subplebbit edit - publishSubplebbitEditOptions.onChallengeVerification({ challengeSuccess: true }); - publishSubplebbitEditOptions.onPublishingStateChange?.("succeeded"); + .editCommunity(communityAddress, communityEditOptions, account); + // create fake success challenge verification for consistent behavior with remote community edit + publishCommunityEditOptions.onChallengeVerification({ challengeSuccess: true }); + publishCommunityEditOptions.onPublishingStateChange?.("succeeded"); return; } assert( - !publishSubplebbitEditOptions.address || - publishSubplebbitEditOptions.address === subplebbitAddress, - `accountsActions.publishSubplebbitEdit can't edit address of a remote subplebbit`, + !publishCommunityEditOptions.address || + publishCommunityEditOptions.address === communityAddress, + `accountsActions.publishCommunityEdit can't edit address of a remote community`, ); - let createSubplebbitEditOptions: any = { + let createCommunityEditOptions: any = { timestamp: Math.floor(Date.now() / 1000), author: account.author, signer: account.signer, - // not possible to edit subplebbit.address over pubsub, only locally - subplebbitAddress, - subplebbitEdit: subplebbitEditOptions, + // not possible to edit community.address over pubsub, only locally + communityAddress, + communityEdit: communityEditOptions, }; - let subplebbitEdit = await account.plebbit.createSubplebbitEdit(createSubplebbitEditOptions); + let communityEdit = await account.plebbit.createCommunityEdit(createCommunityEditOptions); let lastChallenge: Challenge | undefined; const publishAndRetryFailedChallengeVerification = async () => { - subplebbitEdit.once("challenge", async (challenge: Challenge) => { + communityEdit.once("challenge", async (challenge: Challenge) => { lastChallenge = challenge; - publishSubplebbitEditOptions.onChallenge(challenge, subplebbitEdit); + publishCommunityEditOptions.onChallenge(challenge, communityEdit); }); - subplebbitEdit.once( + communityEdit.once( "challengeverification", async (challengeVerification: ChallengeVerification) => { - publishSubplebbitEditOptions.onChallengeVerification(challengeVerification, subplebbitEdit); + publishCommunityEditOptions.onChallengeVerification(challengeVerification, communityEdit); if (!challengeVerification.challengeSuccess && lastChallenge) { // publish again automatically on fail - createSubplebbitEditOptions = { - ...createSubplebbitEditOptions, + createCommunityEditOptions = { + ...createCommunityEditOptions, timestamp: Math.floor(Date.now() / 1000), }; - subplebbitEdit = await account.plebbit.createSubplebbitEdit(createSubplebbitEditOptions); + communityEdit = await account.plebbit.createCommunityEdit(createCommunityEditOptions); lastChallenge = undefined; publishAndRetryFailedChallengeVerification(); } }, ); - subplebbitEdit.on("error", (error: Error) => - publishSubplebbitEditOptions.onError?.(error, subplebbitEdit), + communityEdit.on("error", (error: Error) => + publishCommunityEditOptions.onError?.(error, communityEdit), ); // TODO: add publishingState to account edits - subplebbitEdit.on("publishingstatechange", (publishingState: string) => - publishSubplebbitEditOptions.onPublishingStateChange?.(publishingState), + communityEdit.on("publishingstatechange", (publishingState: string) => + publishCommunityEditOptions.onPublishingStateChange?.(publishingState), ); - listeners.push(subplebbitEdit); + listeners.push(communityEdit); try { // publish will resolve after the challenge request // if it fails before, like failing to resolve ENS, we can emit the error - await subplebbitEdit.publish(); + await communityEdit.publish(); } catch (error) { - publishSubplebbitEditOptions.onError?.(error, subplebbitEdit); + publishCommunityEditOptions.onError?.(error, communityEdit); } }; publishAndRetryFailedChallengeVerification(); - log("accountsActions.publishSubplebbitEdit", { createSubplebbitEditOptions }); + log("accountsActions.publishCommunityEdit", { createCommunityEditOptions }); }; -export const createSubplebbit = async ( - createSubplebbitOptions: CreateSubplebbitOptions, +export const createCommunity = async ( + createCommunityOptions: CreateCommunityOptions, accountName?: string, ) => { const { accounts, accountNamesToAccountIds, activeAccountId } = accountsStore.getState(); @@ -1269,14 +1269,14 @@ export const createSubplebbit = async ( account = accounts[accountId]; } - const subplebbit = await subplebbitsStore + const community = await communitiesStore .getState() - .createSubplebbit(createSubplebbitOptions, account); - log("accountsActions.createSubplebbit", { createSubplebbitOptions, subplebbit }); - return subplebbit; + .createCommunity(createCommunityOptions, account); + log("accountsActions.createCommunity", { createCommunityOptions, community }); + return community; }; -export const deleteSubplebbit = async (subplebbitAddress: string, accountName?: string) => { +export const deleteCommunity = async (communityAddress: string, accountName?: string) => { const { accounts, accountNamesToAccountIds, activeAccountId } = accountsStore.getState(); assert( accounts && accountNamesToAccountIds && activeAccountId, @@ -1288,6 +1288,6 @@ export const deleteSubplebbit = async (subplebbitAddress: string, accountName?: account = accounts[accountId]; } - await subplebbitsStore.getState().deleteSubplebbit(subplebbitAddress, account); - log("accountsActions.deleteSubplebbit", { subplebbitAddress }); + await communitiesStore.getState().deleteCommunity(communityAddress, account); + log("accountsActions.deleteCommunity", { communityAddress }); }; diff --git a/src/stores/accounts/accounts-database.test.ts b/src/stores/accounts/accounts-database.test.ts index 8d22abda..7e97c4a1 100644 --- a/src/stores/accounts/accounts-database.test.ts +++ b/src/stores/accounts/accounts-database.test.ts @@ -26,7 +26,7 @@ describe("accounts-database", () => { subscriptions: [], blockedAddresses: {}, blockedCids: {}, - subplebbits: {}, + communities: {}, mediaIpfsGatewayUrl: "https://ipfs.io", ...overrides, }); @@ -482,14 +482,14 @@ describe("accounts-database", () => { await accountsDatabase.addAccountComment(acc.id, { cid: "cc1", content: "c", - subplebbitAddress: "s", + communityAddress: "s", timestamp: 1, author: { address: "a" }, } as any); await accountsDatabase.addAccountVote(acc.id, { commentCid: "cid1", vote: 1, - subplebbitAddress: "sub", + communityAddress: "sub", } as any); await accountsDatabase.removeAccount(acc); const accountIds = await accountsDatabase.accountsMetadataDatabase.getItem("accountIds"); @@ -510,7 +510,7 @@ describe("accounts-database", () => { await accountsDatabase.addAccountVote(acc.id, { commentCid: "cid1", vote: 1, - subplebbitAddress: "sub", + communityAddress: "sub", onChallenge: () => {}, onChallengeVerification: () => {}, } as any); @@ -540,12 +540,12 @@ describe("accounts-database", () => { await accountsDatabase.addAccountVote(acc.id, { commentCid: "v1", vote: 1, - subplebbitAddress: "s", + communityAddress: "s", } as any); await accountsDatabase.addAccountVote(acc.id, { commentCid: "v2", vote: -1, - subplebbitAddress: "s", + communityAddress: "s", } as any); const votes = await accountsDatabase.getAccountVotes(acc.id); expect(votes["v1"]).toBeDefined(); @@ -558,7 +558,7 @@ describe("accounts-database", () => { await accountsDatabase.addAccountEdit(acc.id, { commentCid: "cid1", content: "edited", - subplebbitAddress: "sub", + communityAddress: "sub", onChallenge: () => {}, } as any); const edits = await accountsDatabase.getAccountEdits(acc.id); @@ -599,7 +599,7 @@ describe("accounts-database", () => { await accountsDatabase.addAccountComment(acc.id, { cid: "ec1", content: "hello", - subplebbitAddress: "s", + communityAddress: "s", timestamp: 1, author: { address: "a" }, } as any); @@ -617,14 +617,14 @@ describe("accounts-database", () => { await accountsDatabase.addAccountComment(acc.id, { cid: "cid1", content: "hello", - subplebbitAddress: "sub", + communityAddress: "sub", timestamp: 1, author: { address: "addr" }, } as any); await accountsDatabase.addAccountComment(acc.id, { cid: "cid2", content: "world", - subplebbitAddress: "sub", + communityAddress: "sub", timestamp: 2, author: { address: "addr" }, } as any); @@ -640,7 +640,7 @@ describe("accounts-database", () => { await accountsDatabase.addAccountComment(acc.id, { cid: "c1", content: "a", - subplebbitAddress: "s", + communityAddress: "s", timestamp: 1, author: { address: "a" }, } as any); @@ -650,7 +650,7 @@ describe("accounts-database", () => { { cid: "c2", content: "b", - subplebbitAddress: "s", + communityAddress: "s", timestamp: 1, author: { address: "a" }, } as any, @@ -665,7 +665,7 @@ describe("accounts-database", () => { await accountsDatabase.addAccountComment(acc.id, { cid: "c1", content: "original", - subplebbitAddress: "s", + communityAddress: "s", timestamp: 1, author: { address: "a" }, } as any); @@ -674,7 +674,7 @@ describe("accounts-database", () => { { cid: "c1", content: "edited", - subplebbitAddress: "s", + communityAddress: "s", timestamp: 1, author: { address: "a" }, } as any, @@ -698,7 +698,7 @@ describe("accounts-database", () => { await accountsDatabase.addAccountComment(acc1.id, { cid: "c1", content: "a", - subplebbitAddress: "s", + communityAddress: "s", timestamp: 1, author: { address: "a" }, } as any); @@ -721,7 +721,7 @@ describe("accounts-database", () => { await accountsDatabase.addAccountComment(acc.id, { cid: "c1", content: "a", - subplebbitAddress: "s", + communityAddress: "s", timestamp: 1, author: { address: "a" }, } as any); @@ -736,14 +736,14 @@ describe("accounts-database", () => { await accountsDatabase.addAccountComment(acc.id, { cid: "c1", content: "a", - subplebbitAddress: "s", + communityAddress: "s", timestamp: 1, author: { address: "a" }, } as any); await accountsDatabase.addAccountComment(acc.id, { cid: "c2", content: "b", - subplebbitAddress: "s", + communityAddress: "s", timestamp: 2, author: { address: "a" }, } as any); @@ -761,7 +761,7 @@ describe("accounts-database", () => { await accountsDatabase.addAccountCommentReply(acc.id, { cid: "reply-cid", commentCid: "parent", - subplebbitAddress: "sub", + communityAddress: "sub", } as any); const replies = await accountsDatabase.getAccountCommentsReplies(acc.id); expect(replies["reply-cid"]).toBeDefined(); @@ -775,7 +775,7 @@ describe("accounts-database", () => { await accountsDatabase.addAccountCommentReply(acc1.id, { cid: "r1", commentCid: "p", - subplebbitAddress: "s", + communityAddress: "s", } as any); const replies = await accountsDatabase.getAccountsCommentsReplies([acc1.id, acc2.id]); expect(replies[acc1.id]["r1"]).toBeDefined(); @@ -824,7 +824,7 @@ describe("accounts-database", () => { await accountsDatabase.addAccountVote(acc.id, { commentCid: "vc1", vote: 1, - subplebbitAddress: "s", + communityAddress: "s", } as any); const votes = await accountsDatabase.getAccountsVotes([acc.id]); expect(votes[acc.id]["vc1"]).toBeDefined(); @@ -836,7 +836,7 @@ describe("accounts-database", () => { await accountsDatabase.addAccountEdit(acc.id, { commentCid: "ec1", content: "edit", - subplebbitAddress: "s", + communityAddress: "s", } as any); const edits = await accountsDatabase.getAccountsEdits([acc.id]); expect(edits[acc.id]["ec1"]).toBeDefined(); @@ -849,12 +849,12 @@ describe("accounts-database", () => { await accountsDatabase.addAccountEdit(acc.id, { commentCid: "same-cid", content: "edit1", - subplebbitAddress: "s", + communityAddress: "s", } as any); await accountsDatabase.addAccountEdit(acc.id, { commentCid: "same-cid", content: "edit2", - subplebbitAddress: "s", + communityAddress: "s", } as any); const edits = await accountsDatabase.getAccountEdits(acc.id); expect(edits["same-cid"]).toHaveLength(2); diff --git a/src/stores/accounts/accounts-database.ts b/src/stores/accounts/accounts-database.ts index b461b4f3..05f9e28d 100644 --- a/src/stores/accounts/accounts-database.ts +++ b/src/stores/accounts/accounts-database.ts @@ -556,7 +556,7 @@ const getAccountEdits = async (accountId: string) => { } const editsArray = await Promise.all(promises); for (const edit of editsArray) { - // TODO: must change this logic for subplebbit edits + // TODO: must change this logic for community edits if (!edits[edit?.commentCid]) { edits[edit?.commentCid] = []; } diff --git a/src/stores/accounts/accounts-store.test.ts b/src/stores/accounts/accounts-store.test.ts index 70d20c80..a93817b4 100644 --- a/src/stores/accounts/accounts-store.test.ts +++ b/src/stores/accounts/accounts-store.test.ts @@ -68,7 +68,7 @@ describe("accounts-store", () => { ).toBe("function"); expect(typeof state.accountsActionsInternal?.addCidToAccountComment).toBe("function"); expect(typeof state.accountsActionsInternal?.markNotificationsAsRead).toBe("function"); - expect(typeof state.accountsActionsInternal?.addSubplebbitRoleToAccountsSubplebbits).toBe( + expect(typeof state.accountsActionsInternal?.addCommunityRoleToAccountsCommunities).toBe( "function", ); }); @@ -78,7 +78,7 @@ describe("accounts-store", () => { test("initializeAccountsStore catch when startUpdatingAccountCommentOnCommentUpdateEvents rejects", async () => { await testUtils.resetDatabasesAndStores(); await accountsStore.getState().accountsActions.publishComment({ - subplebbitAddress: "sub.eth", + communityAddress: "sub.eth", content: "for-init-err", onChallenge: (ch: any, c: any) => c.publishChallengeAnswers(), onChallengeVerification: () => {}, diff --git a/src/stores/accounts/index.ts b/src/stores/accounts/index.ts index 6e22aec4..c4a08e01 100644 --- a/src/stores/accounts/index.ts +++ b/src/stores/accounts/index.ts @@ -1,3 +1,3 @@ -import accountsStore from './accounts-store' -export * from './accounts-store' -export default accountsStore +import accountsStore from "./accounts-store"; +export * from "./accounts-store"; +export default accountsStore; diff --git a/src/stores/accounts/utils.test.ts b/src/stores/accounts/utils.test.ts index e8e8267e..edfd66da 100644 --- a/src/stores/accounts/utils.test.ts +++ b/src/stores/accounts/utils.test.ts @@ -2,7 +2,7 @@ import utils from "./utils"; import { Role } from "../../types"; import commentsStore from "../comments"; import repliesPagesStore from "../replies-pages"; -import subplebbitsPagesStore from "../subplebbits-pages"; +import communitiesPagesStore from "../communities-pages"; import PlebbitJsModule, { setPlebbitJs, restorePlebbitJs } from "../../lib/plebbit-js"; import PlebbitJsMock from "../../lib/plebbit-js/plebbit-js-mock"; @@ -11,123 +11,123 @@ describe("accountsStore utils", () => { const adminRole: Role = { role: "admin" }; const moderatorRole: Role = { role: "moderator" }; - describe("getAccountSubplebbits", () => { + describe("getAccountCommunities", () => { test("empty", async () => { const account = { author }; - const subplebbits = {}; - const accountSubplebbits = utils.getAccountSubplebbits(account, subplebbits); - expect(accountSubplebbits).toEqual({}); + const communities = {}; + const accountCommunities = utils.getAccountCommunities(account, communities); + expect(accountCommunities).toEqual({}); }); - test("previous account subplebbits, no new account subplebbits", async () => { - const previousAccountSubplebbits = { - subplebbitAddress1: { + test("previous account communities, no new account communities", async () => { + const previousAccountCommunities = { + communityAddress1: { role: adminRole, }, }; - const account = { author, subplebbits: previousAccountSubplebbits }; - const subplebbits = {}; - const accountSubplebbits = utils.getAccountSubplebbits(account, subplebbits); - expect(accountSubplebbits).toEqual(previousAccountSubplebbits); + const account = { author, communities: previousAccountCommunities }; + const communities = {}; + const accountCommunities = utils.getAccountCommunities(account, communities); + expect(accountCommunities).toEqual(previousAccountCommunities); }); - test("subplebbit with roles for other addresses skips author (branch 22)", async () => { + test("community with roles for other addresses skips author (branch 22)", async () => { const account = { author }; - const subplebbits = { - subplebbitAddress1: { + const communities = { + communityAddress1: { roles: { "other-address": moderatorRole, }, }, - subplebbitAddress2: { + communityAddress2: { roles: {}, }, }; - const accountSubplebbits = utils.getAccountSubplebbits(account, subplebbits); - expect(accountSubplebbits).toEqual({}); + const accountCommunities = utils.getAccountCommunities(account, communities); + expect(accountCommunities).toEqual({}); }); - test("no previous account subplebbits, new account subplebbits", async () => { + test("no previous account communities, new account communities", async () => { const account = { author }; - const subplebbits = { - subplebbitAddress1: { + const communities = { + communityAddress1: { roles: { [author.address]: moderatorRole, }, }, - subplebbitAddress2: { + communityAddress2: { roles: { [author.address]: adminRole, }, }, }; - const accountSubplebbits = utils.getAccountSubplebbits(account, subplebbits); - const expectedAccountSubplebbits = { - subplebbitAddress1: { + const accountCommunities = utils.getAccountCommunities(account, communities); + const expectedAccountCommunities = { + communityAddress1: { role: moderatorRole, }, - subplebbitAddress2: { + communityAddress2: { role: adminRole, }, }; - expect(accountSubplebbits).toEqual(expectedAccountSubplebbits); + expect(accountCommunities).toEqual(expectedAccountCommunities); }); - test("previous account subplebbits, new account subplebbits", async () => { - const previousAccountSubplebbits = { - subplebbitAddress1: { + test("previous account communities, new account communities", async () => { + const previousAccountCommunities = { + communityAddress1: { role: adminRole, }, }; - const account = { author, subplebbits: previousAccountSubplebbits }; - const subplebbits = { - subplebbitAddress2: { + const account = { author, communities: previousAccountCommunities }; + const communities = { + communityAddress2: { roles: { [author.address]: adminRole, }, }, }; - const accountSubplebbits = utils.getAccountSubplebbits(account, subplebbits); - const expectedAccountSubplebbits = { - subplebbitAddress1: { + const accountCommunities = utils.getAccountCommunities(account, communities); + const expectedAccountCommunities = { + communityAddress1: { role: adminRole, }, - subplebbitAddress2: { + communityAddress2: { role: adminRole, }, }; - expect(accountSubplebbits).toEqual(expectedAccountSubplebbits); + expect(accountCommunities).toEqual(expectedAccountCommunities); }); - test("previous account subplebbits, new account subplebbit overwrites previous", async () => { - const previousAccountSubplebbits = { - subplebbitAddress1: { + test("previous account communities, new account community overwrites previous", async () => { + const previousAccountCommunities = { + communityAddress1: { role: adminRole, }, }; - const account = { author, subplebbits: previousAccountSubplebbits }; - const subplebbits = { - subplebbitAddress1: { + const account = { author, communities: previousAccountCommunities }; + const communities = { + communityAddress1: { roles: { [author.address]: moderatorRole, }, }, - subplebbitAddress2: { + communityAddress2: { roles: { [author.address]: adminRole, }, }, }; - const accountSubplebbits = utils.getAccountSubplebbits(account, subplebbits); - const expectedAccountSubplebbits = { - subplebbitAddress1: { + const accountCommunities = utils.getAccountCommunities(account, communities); + const expectedAccountCommunities = { + communityAddress1: { role: moderatorRole, }, - subplebbitAddress2: { + communityAddress2: { role: adminRole, }, }; - expect(accountSubplebbits).toEqual(expectedAccountSubplebbits); + expect(accountCommunities).toEqual(expectedAccountCommunities); }); }); @@ -439,14 +439,14 @@ describe("accountsStore utils", () => { }); } }); - test("returns parent depth + 1 when parent in subplebbitsPagesStore", () => { - subplebbitsPagesStore.setState((s: any) => ({ + test("returns parent depth + 1 when parent in communitiesPagesStore", () => { + communitiesPagesStore.setState((s: any) => ({ comments: { ...s.comments, parentCid3: { cid: "parentCid3", depth: 0 } }, })); try { expect(utils.getAccountCommentDepth({ parentCid: "parentCid3" } as any)).toBe(1); } finally { - subplebbitsPagesStore.setState((s: any) => { + communitiesPagesStore.setState((s: any) => { const { parentCid3, ...rest } = s.comments; return { comments: rest }; }); @@ -455,7 +455,7 @@ describe("accountsStore utils", () => { test("returns undefined when parent not found (missing-parent fallback)", () => { commentsStore.setState((s: any) => ({ comments: s.comments })); repliesPagesStore.setState((s: any) => ({ comments: s.comments })); - subplebbitsPagesStore.setState((s: any) => ({ comments: s.comments })); + communitiesPagesStore.setState((s: any) => ({ comments: s.comments })); const result = utils.getAccountCommentDepth({ parentCid: "nonexistent" } as any); expect(result).toBeUndefined(); }); @@ -465,13 +465,13 @@ describe("accountsStore utils", () => { beforeAll(() => setPlebbitJs(PlebbitJsMock)); afterAll(() => restorePlebbitJs()); - test("adds shortSubplebbitAddress and author.shortAddress on success", () => { + test("adds shortCommunityAddress and author.shortAddress on success", () => { const comment = { - subplebbitAddress: "eip155:0x1234567890abcdef", + communityAddress: "eip155:0x1234567890abcdef", author: { address: "eip155:0xfedcba0987654321" }, }; const result = utils.addShortAddressesToAccountComment(comment as any); - expect(result.shortSubplebbitAddress).toBeDefined(); + expect(result.shortCommunityAddress).toBeDefined(); expect(result.author.shortAddress).toBeDefined(); expect(result).not.toBe(comment); }); @@ -482,13 +482,13 @@ describe("accountsStore utils", () => { }; try { const comment = { - subplebbitAddress: "eip155:0x1234567890abcdef", + communityAddress: "eip155:0x1234567890abcdef", author: { address: "eip155:0xfedcba0987654321" }, }; const result = utils.addShortAddressesToAccountComment(comment as any); expect(result).toBeDefined(); - expect(result.subplebbitAddress).toBe("eip155:0x1234567890abcdef"); - expect(result.shortSubplebbitAddress).toBeUndefined(); + expect(result.communityAddress).toBe("eip155:0x1234567890abcdef"); + expect(result.shortCommunityAddress).toBeUndefined(); } finally { PlebbitJsModule.Plebbit.getShortAddress = orig; } diff --git a/src/stores/accounts/utils.ts b/src/stores/accounts/utils.ts index 2d44baee..20cb38ac 100644 --- a/src/stores/accounts/utils.ts +++ b/src/stores/accounts/utils.ts @@ -1,7 +1,7 @@ import { Account, Role, - Subplebbits, + Communities, AccountComment, AccountsComments, CommentCidsToAccountsComments, @@ -12,37 +12,37 @@ import Logger from "@plebbit/plebbit-logger"; const log = Logger("bitsocial-react-hooks:accounts:stores"); import commentsStore from "../comments"; import repliesPagesStore from "../replies-pages"; -import subplebbitsPagesStore from "../subplebbits-pages"; +import communitiesPagesStore from "../communities-pages"; import PlebbitJs from "../../lib/plebbit-js"; -const getAuthorAddressRolesFromSubplebbits = (authorAddress: string, subplebbits: Subplebbits) => { - const roles: { [subplebbitAddress: string]: Role } = {}; - for (const subplebbitAddress in subplebbits) { - const role = subplebbits[subplebbitAddress]?.roles?.[authorAddress]; +const getAuthorAddressRolesFromCommunities = (authorAddress: string, communities: Communities) => { + const roles: { [communityAddress: string]: Role } = {}; + for (const communityAddress in communities) { + const role = communities[communityAddress]?.roles?.[authorAddress]; if (role) { - roles[subplebbitAddress] = role; + roles[communityAddress] = role; } } return roles; }; -export const getAccountSubplebbits = (account: Account, subplebbits: Subplebbits) => { +export const getAccountCommunities = (account: Account, communities: Communities) => { assert( account?.author?.address && typeof account?.author?.address === "string", - `accountsStore utils getAccountSubplebbits invalid account.author.address '${account?.author?.address}'`, + `accountsStore utils getAccountCommunities invalid account.author.address '${account?.author?.address}'`, ); assert( - subplebbits && typeof subplebbits === "object", - `accountsStore utils getAccountSubplebbits invalid subplebbits '${subplebbits}'`, + communities && typeof communities === "object", + `accountsStore utils getAccountCommunities invalid communities '${communities}'`, ); - const roles = getAuthorAddressRolesFromSubplebbits(account.author.address, subplebbits); - const accountSubplebbits = { ...account.subplebbits }; - for (const subplebbitAddress in roles) { - accountSubplebbits[subplebbitAddress] = { ...accountSubplebbits[subplebbitAddress] }; - accountSubplebbits[subplebbitAddress].role = roles[subplebbitAddress]; + const roles = getAuthorAddressRolesFromCommunities(account.author.address, communities); + const accountCommunities = { ...account.communities }; + for (const communityAddress in roles) { + accountCommunities[communityAddress] = { ...accountCommunities[communityAddress] }; + accountCommunities[communityAddress].role = roles[communityAddress]; } - return accountSubplebbits; + return accountCommunities; }; export const getCommentCidsToAccountsComments = (accountsComments: AccountsComments) => { @@ -239,7 +239,7 @@ export const getAccountCommentDepth = (comment: Comment) => { if (typeof parentCommentDepth === "number") { return parentCommentDepth + 1; } - parentCommentDepth = subplebbitsPagesStore.getState().comments[comment.parentCid]?.depth; + parentCommentDepth = communitiesPagesStore.getState().comments[comment.parentCid]?.depth; if (typeof parentCommentDepth === "number") { return parentCommentDepth + 1; } @@ -250,8 +250,8 @@ export const getAccountCommentDepth = (comment: Comment) => { export const addShortAddressesToAccountComment = (comment: Comment) => { comment = { ...comment }; try { - comment.shortSubplebbitAddress = PlebbitJs.Plebbit.getShortAddress({ - address: comment.subplebbitAddress, + comment.shortCommunityAddress = PlebbitJs.Plebbit.getShortAddress({ + address: comment.communityAddress, }); } catch (e) {} try { @@ -264,7 +264,7 @@ export const addShortAddressesToAccountComment = (comment: Comment) => { }; const utils = { - getAccountSubplebbits, + getAccountCommunities, getCommentCidsToAccountsComments, fetchCommentLinkDimensions, getInitAccountCommentsToUpdate, diff --git a/src/stores/authors-comments/authors-comments-store.test.ts b/src/stores/authors-comments/authors-comments-store.test.ts index 23adfcd8..45059b3f 100644 --- a/src/stores/authors-comments/authors-comments-store.test.ts +++ b/src/stores/authors-comments/authors-comments-store.test.ts @@ -242,10 +242,10 @@ describe("authors comments store", () => { if (commentCid === "comment cid") { authorCommentIndex = totalAuthorCommentCount; } - if (commentCid === "subplebbit last comment cid") { + if (commentCid === "community last comment cid") { authorCommentIndex = totalAuthorCommentCountFromLastCommentCid; } - if (commentCid === "subplebbit last comment cid 2") { + if (commentCid === "community last comment cid 2") { authorCommentIndex = totalAuthorCommentCountFromLastCommentCid2; } @@ -263,8 +263,8 @@ describe("authors comments store", () => { // add a last comment cid to comments after the 80th comment if (totalAuthorCommentCount - authorCommentIndex > 80) { - comment.author.subplebbit = { lastCommentCid: "subplebbit last comment cid" }; - comment.subplebbitAddress = "subplebbit address"; + comment.author.community = { lastCommentCid: "community last comment cid" }; + comment.communityAddress = "community address"; } // add parent cid to some of the comments for filters @@ -272,8 +272,8 @@ describe("authors comments store", () => { comment.parentCid = "parent cid"; } - // if comment is 'subplebbit last comment cid' - if (commentCid === "subplebbit last comment cid") { + // if comment is 'community last comment cid' + if (commentCid === "community last comment cid") { // timestamp of last comment cid must be newer than all comment.timestamp = firstTimestamp + totalAuthorCommentCount + totalAuthorCommentCountFromLastCommentCid; @@ -281,7 +281,7 @@ describe("authors comments store", () => { comment.author.previousCommentCid = `previous from last comment cid ${authorCommentIndex - 1}`; } - // if comment is previous from 'subplebbit last comment cid' + // if comment is previous from 'community last comment cid' if (commentCid.includes("previous from last comment cid")) { comment.timestamp = firstTimestamp + totalAuthorCommentCount + authorCommentIndex; comment.author.previousCommentCid = `previous from last comment cid ${authorCommentIndex - 1}`; @@ -292,8 +292,8 @@ describe("authors comments store", () => { } } - // if comment is 'subplebbit last comment cid 2' - if (commentCid === "subplebbit last comment cid 2") { + // if comment is 'community last comment cid 2' + if (commentCid === "community last comment cid 2") { // timestamp of last comment cid must be newer than all comment.timestamp = firstTimestamp + @@ -304,14 +304,14 @@ describe("authors comments store", () => { comment.author.previousCommentCid = `previous 2 from last comment cid ${authorCommentIndex - 1}`; } - // if comment is previous from 'subplebbit last comment cid 2' + // if comment is previous from 'community last comment cid 2' if (commentCid.includes("previous 2 from last comment cid")) { comment.timestamp = firstTimestamp + totalAuthorCommentCount + authorCommentIndex; comment.author.previousCommentCid = `previous 2 from last comment cid ${authorCommentIndex - 1}`; - // no more comments from last comment cid, go back to first 'subplebbit last comment cid' + // no more comments from last comment cid, go back to first 'community last comment cid' if (authorCommentIndex === 1) { - comment.author.previousCommentCid = "subplebbit last comment cid"; + comment.author.previousCommentCid = "community last comment cid"; } } @@ -394,11 +394,10 @@ describe("authors comments store", () => { // wait for last comment cid await waitFor( - () => - rendered.result.current.lastCommentCids[authorAddress] === "subplebbit last comment cid", + () => rendered.result.current.lastCommentCids[authorAddress] === "community last comment cid", ); expect(rendered.result.current.lastCommentCids[authorAddress]).toBe( - "subplebbit last comment cid", + "community last comment cid", ); // last comment of loaded comments is from 'comment cid' previous comments because already loaded feeds can't change @@ -409,14 +408,14 @@ describe("authors comments store", () => { ).toBe("previous comment cid 56"); // first comment of next page is from last cid because buffered comments get reordered by most recent as they are fetched - expect(bufferedComments[2 * commentsPerPage].cid).toBe("subplebbit last comment cid"); + expect(bufferedComments[2 * commentsPerPage].cid).toBe("community last comment cid"); expect(bufferedComments[2 * commentsPerPage + 1].cid).toBe("previous from last comment cid 39"); // discover older lastCommentCid, should do nothing because not new commentsStore.setState((state: any) => { const commentCid = "previous comment cid 100"; const comment = { ...state.comments[commentCid] }; - comment.author.subplebbit = { lastCommentCid: "previous comment cid 3" }; + comment.author.community = { lastCommentCid: "previous comment cid 3" }; return { comments: { ...state.comments, [commentCid]: comment } }; }); @@ -495,7 +494,7 @@ describe("authors comments store", () => { // fetched all author comments, no next comment to fetch expect(rendered.result.current.nextCommentCidsToFetch[authorAddress]).toBe(undefined); - // no more comments from 'subplebbit last comment cid' + // no more comments from 'community last comment cid' bufferedComments = getBufferedComments(rendered, authorCommentsName, authorAddress); expect(bufferedComments[4 * commentsPerPage - 1].cid).toBe("previous comment cid 46"); expect( @@ -508,17 +507,17 @@ describe("authors comments store", () => { commentsStore.setState((state: any) => { const commentCid = "comment cid"; const comment = { ...state.comments[commentCid] }; - comment.author.subplebbit = { lastCommentCid: "subplebbit last comment cid 2" }; + comment.author.community = { lastCommentCid: "community last comment cid 2" }; return { comments: { ...state.comments, [commentCid]: comment } }; }); // wait for last comment cid and next comment cid to fetch await waitFor( () => - rendered.result.current.lastCommentCids[authorAddress] === "subplebbit last comment cid 2", + rendered.result.current.lastCommentCids[authorAddress] === "community last comment cid 2", ); expect(rendered.result.current.lastCommentCids[authorAddress]).toBe( - "subplebbit last comment cid 2", + "community last comment cid 2", ); // React 19 batching may produce off-by-one; accept 8 or 9 expect(["previous 2 from last comment cid 8", "previous 2 from last comment cid 9"]).toContain( @@ -676,24 +675,24 @@ describe("authors comments store", () => { emptyFilterCommentCount, ); - // add another author comments with subplebbit filter (0 matching) - const subplebbitFilterName = authorAddress + "-subplebbit-filter"; - const subplebbitFilter = { - filter: (comment: Comment) => comment.subplebbitAddress === `doesn't exist`, - key: "subplebbit-filter", + // add another author comments with community filter (0 matching) + const communityFilterName = authorAddress + "-community-filter"; + const communityFilter = { + filter: (comment: Comment) => comment.communityAddress === `doesn't exist`, + key: "community-filter", }; act(() => { rendered.result.current.addAuthorCommentsToStore( - subplebbitFilterName, + communityFilterName, authorAddress, commentCid, - subplebbitFilter, + communityFilter, account, ); }); // give some time to load comments await new Promise((r) => setTimeout(r, 100)); - expect(rendered.result.current.loadedComments[subplebbitFilterName].length).toBe(0); + expect(rendered.result.current.loadedComments[communityFilterName].length).toBe(0); // add another author comments with different address const differentAuthorAddress = "different-" + authorAddress; @@ -723,7 +722,7 @@ describe("authors comments store", () => { commentsStore.setState((state: any) => { const commentCid = "different author comment cid 20"; const comment = { ...state.comments[commentCid] }; - comment.author.subplebbit = { + comment.author.community = { lastCommentCid: "different author comment cid " + differentAuthorTotalCommentCountFromLastCid, }; @@ -787,7 +786,7 @@ describe("authors comments store", () => { // 1/3 of comments are replies parentCid: authorCommentIndex % 3 === 0 ? "parent cid" : undefined, // split comments between 2 subs - subplebbitAddress: authorCommentIndex % 2 === 0 ? "subplebbit1.eth" : "subplebbit2.eth", + communityAddress: authorCommentIndex % 2 === 0 ? "community1.eth" : "community2.eth", }; return comment; }; @@ -799,16 +798,15 @@ describe("authors comments store", () => { filter: (comment: Comment) => !!comment.parentCid, key: "reply-filter", }; - const postAndSubplebbitFilter = { - filter: (comment: any) => - !comment.parentCid && comment.subplebbitAddress === "subplebbit2.eth", - key: "post-subplebbit2.eth-filter", + const postAndCommunityFilter = { + filter: (comment: any) => !comment.parentCid && comment.communityAddress === "community2.eth", + key: "post-community2.eth-filter", }; const author1Name = `${author1}-name`; const author2Name = `${author2}-name`; const author3Name = `${author3}-name`; const author1ReplyFilterName = `${author1}-reply-filter-name`; - const author1PostAndSubplebbitFilterName = `${author1}-post-and-subplebbit-filter-name`; + const author1PostAndCommunityFilterName = `${author1}-post-and-community-filter-name`; const author1TotalCommentCount = 100; const author2TotalCommentCount = 70; const author3TotalCommentCount = 50; @@ -863,8 +861,8 @@ describe("authors comments store", () => { ...baseComment, author: { ...(baseComment.author || { address: authorAddress }), - subplebbit: { - ...(baseComment.author?.subplebbit || {}), + community: { + ...(baseComment.author?.community || {}), lastCommentCid: getAccountCommentCid( startCid, totalAuthorCommentCount + totalAuthorCommentCountFromLastCommentCid, @@ -881,10 +879,10 @@ describe("authors comments store", () => { if (filter === replyFilter) { commentCount = Math.ceil(commentCount / 3); } - if (filter === postAndSubplebbitFilter) { + if (filter === postAndCommunityFilter) { // 2/3 of comments are posts commentCount = Math.ceil((commentCount / 3) * 2); - // 1/2 of subplebbits are filtered + // 1/2 of communities are filtered commentCount = Math.ceil(commentCount / 2); } await scrollPagesToComment(rendered, authorCommentsName, commentCount, waitFor); @@ -925,10 +923,10 @@ describe("authors comments store", () => { author1TotalCommentCountFromLastCommentCid, ), test( - author1PostAndSubplebbitFilterName, + author1PostAndCommunityFilterName, author1, author1StartCid, - postAndSubplebbitFilter, + postAndCommunityFilter, author1TotalCommentCount, author1TotalCommentCountFromLastCommentCid, ), @@ -946,7 +944,7 @@ describe("authors comments store", () => { expect(rendered.result.current.loadedComments[author1ReplyFilterName].length).toBe( Math.ceil((author1TotalCommentCount + author1TotalCommentCountFromLastCommentCid) / 3), ); - expect(rendered.result.current.loadedComments[author1PostAndSubplebbitFilterName].length).toBe( + expect(rendered.result.current.loadedComments[author1PostAndCommunityFilterName].length).toBe( (((author1TotalCommentCount + author1TotalCommentCountFromLastCommentCid) / 3) * 2) / 2, ); @@ -1079,7 +1077,7 @@ describe("authors comments store", () => { }, async () => { const createComment = Plebbit.prototype.createComment; - const failingLastCid = "subplebbit-last-fail"; + const failingLastCid = "community-last-fail"; Plebbit.prototype.createComment = async function (opts: any) { if (opts?.cid === failingLastCid) { throw new Error("sub last comment fetch failed"); @@ -1089,7 +1087,7 @@ describe("authors comments store", () => { (comment as any).author = { address: authorAddress, previousCommentCid: "prev 1", - subplebbit: { lastCommentCid: failingLastCid }, + community: { lastCommentCid: failingLastCid }, }; (comment as any).timestamp = 1000; } @@ -1162,7 +1160,7 @@ describe("authors comments store", () => { (comment as any).author = { address: authorAddress, previousCommentCid: "prev 1", - subplebbit: { lastCommentCid: orphanCid }, + community: { lastCommentCid: orphanCid }, }; (comment as any).timestamp = 1000; } @@ -1218,7 +1216,7 @@ describe("authors comments store", () => { (comment as any).author = { address: authorAddress, previousCommentCid: "prev 1", - subplebbit: { lastCommentCid: lowTsCid }, + community: { lastCommentCid: lowTsCid }, }; (comment as any).timestamp = 1000; } @@ -1226,7 +1224,7 @@ describe("authors comments store", () => { (comment as any).author = { address: authorAddress, previousCommentCid: undefined, - subplebbit: { lastCommentCid: midTsLastCid }, + community: { lastCommentCid: midTsLastCid }, }; (comment as any).timestamp = 100; } @@ -1308,7 +1306,7 @@ describe("authors comments store", () => { (comment as any).author = { address: authorAddress, previousCommentCid: "prev 1", - subplebbit: { lastCommentCid: wrongAuthorCid }, + community: { lastCommentCid: wrongAuthorCid }, }; (comment as any).timestamp = 1000; } @@ -1355,7 +1353,7 @@ describe("authors comments store", () => { (comment as any).author = { address: authorAddress, previousCommentCid: "prev 1", - subplebbit: { lastCommentCid: leafLastCid }, + community: { lastCommentCid: leafLastCid }, }; (comment as any).timestamp = 1000; } diff --git a/src/stores/authors-comments/authors-comments-store.ts b/src/stores/authors-comments/authors-comments-store.ts index 662218ed..e050bf68 100644 --- a/src/stores/authors-comments/authors-comments-store.ts +++ b/src/stores/authors-comments/authors-comments-store.ts @@ -356,7 +356,7 @@ const fetchCommentOnShouldFetchOrNextCidChange = // if commentStore changed, update loadedComments, bufferedCommentCids, shouldFetchNextComment and nextCommentCidsToFetch let previousComments = new QuickLru({ maxSize: 10000 }); let authorCommentCidsFetching: { [commentCid: string]: boolean } = {}; -let subplebbitLastCommentCidsFetching: { [commentCid: string]: boolean } = {}; +let communityLastCommentCidsFetching: { [commentCid: string]: boolean } = {}; const updateCommentsOnCommentsChange = (options: AuthorCommentsOptions, commentCid: string) => (state: CommentsState) => { // not a next cid, do nothing @@ -396,23 +396,23 @@ const updateCommentsOnCommentsChange = // one of the comment changed, must update loaded comments updateLoadedComments(); - // the changed comment might have a new author.subplebbit.lastCommentCid, try to fetch it - const subplebbitLastCommentCid = comment.author?.subplebbit?.lastCommentCid; - if (subplebbitLastCommentCid) { + // the changed comment might have a new author.community.lastCommentCid, try to fetch it + const communityLastCommentCid = comment.author?.community?.lastCommentCid; + if (communityLastCommentCid) { // when last comment has fetched, update lastCommentCid - if (!subplebbitLastCommentCidsFetching[subplebbitLastCommentCid]) { - subplebbitLastCommentCidsFetching[subplebbitLastCommentCid] = true; + if (!communityLastCommentCidsFetching[communityLastCommentCid]) { + communityLastCommentCidsFetching[communityLastCommentCid] = true; commentsStore.subscribe( - setLastCommentCidOnCommentsChange(options, subplebbitLastCommentCid), + setLastCommentCidOnCommentsChange(options, communityLastCommentCid), ); } // start fetching lastCommentCid const account = accountsStore.getState().accounts[options.accountId]; - state.addCommentToStore(subplebbitLastCommentCid, account).catch((error: unknown) => + state.addCommentToStore(communityLastCommentCid, account).catch((error: unknown) => log.error("authorsCommentsStore updateCommentsOnCommentsChange addCommentToStore error", { error, - subplebbitLastCommentCid, + communityLastCommentCid, account, }), ); @@ -423,7 +423,7 @@ let previousLastComments = new QuickLru({ maxSize: 10000 }); const setLastCommentCidOnCommentsChange = (options: AuthorCommentsOptions, commentCid: string) => (state: CommentsState) => { // not a last cid candidate, do nothing - if (!subplebbitLastCommentCidsFetching[commentCid]) { + if (!communityLastCommentCidsFetching[commentCid]) { return; } const { comments } = state; @@ -512,7 +512,7 @@ const setLastCommentCidOnCommentsChange = const originalState = authorsCommentsStore.getState(); // async function because some stores have async init export const resetAuthorsCommentsStore = async () => { - subplebbitLastCommentCidsFetching = {}; + communityLastCommentCidsFetching = {}; previousComments = new QuickLru({ maxSize: 10000 }); authorCommentCidsFetching = {}; previousLastComments = new QuickLru({ maxSize: 10000 }); diff --git a/src/stores/authors-comments/index.ts b/src/stores/authors-comments/index.ts index 6eeaf76f..b9ec0dc8 100644 --- a/src/stores/authors-comments/index.ts +++ b/src/stores/authors-comments/index.ts @@ -1,3 +1,3 @@ -import authorsCommentsStore from './authors-comments-store' -export * from './authors-comments-store' -export default authorsCommentsStore +import authorsCommentsStore from "./authors-comments-store"; +export * from "./authors-comments-store"; +export default authorsCommentsStore; diff --git a/src/stores/comments/index.ts b/src/stores/comments/index.ts index 0b8e70fd..8fecb685 100644 --- a/src/stores/comments/index.ts +++ b/src/stores/comments/index.ts @@ -1,3 +1,3 @@ -import commentsStore from './comments-store' -export * from './comments-store' -export default commentsStore +import commentsStore from "./comments-store"; +export * from "./comments-store"; +export default commentsStore; diff --git a/src/stores/communities-pages/communities-pages-store.test.ts b/src/stores/communities-pages/communities-pages-store.test.ts new file mode 100644 index 00000000..0ccd96d1 --- /dev/null +++ b/src/stores/communities-pages/communities-pages-store.test.ts @@ -0,0 +1,834 @@ +import { act, waitFor as tlWaitFor } from "@testing-library/react"; +import testUtils, { renderHook } from "../../lib/test-utils"; +import useCommunitiesPagesStore, { + resetCommunitiesPagesDatabaseAndStore, + resetCommunitiesPagesStore, + getCommunityFirstPageCid, + getCommentFreshness, + log, +} from "./communities-pages-store"; +import { CommunityPage } from "../../types"; +import communitiesStore from "../communities"; +import accountsStore from "../accounts"; +import localForageLru from "../../lib/localforage-lru"; + +const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); + +class MockPages { + communityAddress: string; + pageCids: { [pageCid: string]: string }; + pages: { [sortType: string]: CommunityPage }; + constructor({ communityAddress }: any) { + this.communityAddress = communityAddress; + this.pageCids = { + new: `${communityAddress} new page cid`, + }; + const hotPageCid = `${communityAddress} hot page cid`; + this.pages = { + hot: { + nextCid: hotPageCid + " - next page cid", + comments: this.getPageMockComments(hotPageCid), + }, + }; + } + + async getPage(options: { cid: string }) { + const cid = options?.cid; + await sleep(200); + const page: CommunityPage = { + nextCid: cid + " - next page cid", + comments: this.getPageMockComments(cid), + }; + return page; + } + + getPageMockComments(pageCid: string) { + const commentCount = 100; + let index = 0; + const comments: any[] = []; + while (index++ < commentCount) { + comments.push({ + timestamp: index, + cid: pageCid + " comment cid " + index, + communityAddress: this.communityAddress, + updatedAt: index, + }); + } + return comments; + } +} + +class MockCommunity { + address: string; + posts: MockPages; + constructor({ address }: any) { + this.address = address; + this.posts = new MockPages({ communityAddress: address }); + } + removeAllListeners() {} +} + +const mockAccount: any = { + id: "mock account id", + plebbit: { + createCommunity: async ({ address }: any) => new MockCommunity({ address }), + }, +}; + +describe("communities pages store", () => { + beforeAll(() => { + testUtils.silenceReactWarnings(); + }); + afterAll(async () => { + testUtils.restoreAll(); + }); + + let rendered: any, waitFor: any; + beforeEach(async () => { + rendered = renderHook(() => useCommunitiesPagesStore()); + waitFor = testUtils.createWaitFor(rendered); + }); + + afterEach(async () => { + await resetCommunitiesPagesDatabaseAndStore(); + }); + + test("addNextCommunityPageToStore returns early when no communityFirstPageCid", async () => { + const communityWithoutPosts = { + address: "no-posts-address", + posts: {}, + }; + const sortType = "new"; + + await rendered.result.current.addNextCommunityPageToStore( + communityWithoutPosts, + sortType, + mockAccount, + ); + + expect(Object.keys(rendered.result.current.communitiesPages).length).toBe(0); + }); + + test("addCommunityPageCommentsToStore returns early when no new comments", () => { + const communityWithExistingComments = { + address: "existing-comments-addr", + posts: { + pages: { + hot: { + comments: [ + { + cid: "existing-hot-cid", + timestamp: 100, + updatedAt: 100, + communityAddress: "existing-comments-addr", + }, + ], + }, + }, + }, + }; + + act(() => { + rendered.result.current.addCommunityPageCommentsToStore(communityWithExistingComments); + }); + expect(rendered.result.current.comments["existing-hot-cid"]).toBeDefined(); + + act(() => { + rendered.result.current.addCommunityPageCommentsToStore(communityWithExistingComments); + }); + expect(rendered.result.current.comments["existing-hot-cid"]).toBeDefined(); + }); + + test("addCommunityPageCommentsToStore returns early when no community.posts.pages", () => { + const communityWithoutPages = { + address: "no-pages", + posts: {}, + }; + + act(() => { + rendered.result.current.addCommunityPageCommentsToStore(communityWithoutPages); + }); + + expect(Object.keys(rendered.result.current.comments).length).toBe(0); + }); + + test("initial store", async () => { + expect(rendered.result.current.communitiesPages).toEqual({}); + expect(typeof rendered.result.current.addNextCommunityPageToStore).toBe("function"); + expect(typeof rendered.result.current.invalidateCommunityPages).toBe("function"); + }); + + test("invalidateCommunityPages returns early when no communityFirstPageCid", async () => { + const communityWithoutPosts = { + address: "no-pages-address", + posts: {}, + }; + + await act(async () => { + await rendered.result.current.invalidateCommunityPages(communityWithoutPosts, "new"); + }); + + expect(rendered.result.current.communitiesPages).toEqual({}); + }); + + test("resetCommunitiesPagesDatabaseAndStore clears database and store", async () => { + act(() => { + rendered.result.current.addCommunityPageCommentsToStore({ + address: "db-reset-addr", + posts: { + pages: { + hot: { + comments: [{ cid: "db-c1", timestamp: 1, communityAddress: "s1" }], + }, + }, + }, + }); + }); + await waitFor(() => rendered.result.current.comments["db-c1"]); + await resetCommunitiesPagesDatabaseAndStore(); + const state = useCommunitiesPagesStore.getState(); + expect(state.comments["db-c1"]).toBeUndefined(); + expect(state.communitiesPages).toEqual({}); + }); + + test("resetCommunitiesPagesStore after addNextCommunityPageToStore clears listeners and state", async () => { + const mockCommunity = await mockAccount.plebbit.createCommunity({ + address: "reset-listener-sub", + }); + const sortType = "new"; + const firstPageCid = mockCommunity.posts.pageCids[sortType]; + + act(() => { + rendered.result.current.addNextCommunityPageToStore(mockCommunity, sortType, mockAccount); + }); + await waitFor( + () => rendered.result.current.communitiesPages[firstPageCid]?.comments?.length === 100, + ); + + await resetCommunitiesPagesStore(); + const state = useCommunitiesPagesStore.getState(); + expect(state.communitiesPages).toEqual({}); + expect(state.comments).toEqual({}); + }); + + test("resetCommunitiesPagesStore clears store state", async () => { + act(() => { + rendered.result.current.addCommunityPageCommentsToStore({ + address: "tmp", + posts: { + pages: { + hot: { + comments: [{ cid: "c1", timestamp: 1, communityAddress: "s1" }], + }, + }, + }, + }); + }); + expect(rendered.result.current.comments["c1"]).toBeDefined(); + await resetCommunitiesPagesStore(); + const state = useCommunitiesPagesStore.getState(); + expect(state.comments["c1"]).toBeUndefined(); + expect(state.communitiesPages).toEqual({}); + }); + + test("invalidateCommunityPages clears stored page chains for posts", async () => { + const firstPageCid = "invalidate-posts-page-1"; + const secondPageCid = "invalidate-posts-page-2"; + const db = localForageLru.createInstance({ name: "plebbitReactHooks-communitiesPages" }); + + await db.setItem(firstPageCid, { + nextCid: secondPageCid, + comments: [{ cid: `${firstPageCid}-comment`, communityAddress: "invalidate-posts" }], + }); + await db.setItem(secondPageCid, { + comments: [{ cid: `${secondPageCid}-comment`, communityAddress: "invalidate-posts" }], + }); + useCommunitiesPagesStore.setState({ + communitiesPages: { + [firstPageCid]: { + nextCid: secondPageCid, + comments: [{ cid: `${firstPageCid}-comment`, communityAddress: "invalidate-posts" }], + }, + [secondPageCid]: { + comments: [{ cid: `${secondPageCid}-comment`, communityAddress: "invalidate-posts" }], + }, + }, + }); + + await rendered.result.current.invalidateCommunityPages( + { + address: "invalidate-posts", + posts: { pageCids: { new: firstPageCid } }, + }, + "new", + ); + + expect(useCommunitiesPagesStore.getState().communitiesPages[firstPageCid]).toBeUndefined(); + expect(useCommunitiesPagesStore.getState().communitiesPages[secondPageCid]).toBeUndefined(); + expect(await db.getItem(firstPageCid)).toBeUndefined(); + expect(await db.getItem(secondPageCid)).toBeUndefined(); + }); + + test("invalidateCommunityPages returns early when no first page cid", async () => { + useCommunitiesPagesStore.setState({ + communitiesPages: { + existing: { + comments: [{ cid: "existing-comment", communityAddress: "existing-sub" }], + }, + }, + }); + + await rendered.result.current.invalidateCommunityPages( + { + address: "no-pages", + posts: {}, + }, + "new", + ); + + expect(useCommunitiesPagesStore.getState().communitiesPages.existing).toBeDefined(); + }); + + test("getCommentFreshness returns 0 when comment undefined (branch 26)", () => { + expect(getCommentFreshness(undefined)).toBe(0); + }); + + test("getCommunityFirstPageCid throws when sortType empty", () => { + expect(() => + getCommunityFirstPageCid({ address: "addr", posts: {} } as any, "", "posts"), + ).toThrow(); + }); + + test("getCommunityFirstPageCid throws when sortType undefined (branch 313)", () => { + expect(() => + getCommunityFirstPageCid({ address: "addr", posts: {} } as any, undefined as any, "posts"), + ).toThrow("sortType"); + }); + + test("getCommunityFirstPageCid returns nextCid when pages preloaded", () => { + const community = { + address: "addr", + posts: { + pages: { + hot: { + nextCid: "first-page-cid", + comments: [{ cid: "c1" }], + }, + }, + }, + }; + expect(getCommunityFirstPageCid(community as any, "hot", "posts")).toBe("first-page-cid"); + }); + + test("getCommunityFirstPageCid returns pageCids when no preloaded pages", () => { + const community = { + address: "addr", + posts: { + pageCids: { hot: "page-cid-from-pageCids" }, + }, + }; + expect(getCommunityFirstPageCid(community as any, "hot", "posts")).toBe( + "page-cid-from-pageCids", + ); + }); + + test("getCommunityFirstPageCid defaults pageType to posts", () => { + const community = { + address: "addr", + posts: { + pageCids: { hot: "default-posts-page-cid" }, + }, + }; + expect(getCommunityFirstPageCid(community as any, "hot")).toBe("default-posts-page-cid"); + }); + + test("fetchPage returns cached page when in database", async () => { + const mockCommunity = await mockAccount.plebbit.createCommunity({ + address: "community address 1", + }); + const sortType = "new"; + const firstPageCid = mockCommunity.posts.pageCids[sortType]; + const cachedPage = { + nextCid: firstPageCid + " - next", + comments: [{ cid: "cached-1", communityAddress: "community address 1" }], + }; + const db = localForageLru.createInstance({ name: "plebbitReactHooks-communitiesPages" }); + await db.setItem(firstPageCid, cachedPage); + + act(() => { + rendered.result.current.addNextCommunityPageToStore(mockCommunity, sortType, mockAccount); + }); + + await waitFor( + () => + rendered.result.current.communitiesPages[firstPageCid]?.nextCid === + firstPageCid + " - next", + ); + expect(rendered.result.current.communitiesPages[firstPageCid].comments).toHaveLength(1); + }); + + test("invalidateCommunityPages removes loaded page chain from store and database", async () => { + const mockCommunity = await mockAccount.plebbit.createCommunity({ + address: "invalidate-pages-community", + }); + const sortType = "new"; + const firstPageCid = mockCommunity.posts.pageCids[sortType]; + const db = localForageLru.createInstance({ name: "plebbitReactHooks-communitiesPages" }); + + await act(async () => { + await rendered.result.current.addNextCommunityPageToStore( + mockCommunity, + sortType, + mockAccount, + ); + }); + await waitFor(() => rendered.result.current.communitiesPages[firstPageCid]?.nextCid); + + const secondPageCid = rendered.result.current.communitiesPages[firstPageCid].nextCid; + await act(async () => { + await rendered.result.current.addNextCommunityPageToStore( + mockCommunity, + sortType, + mockAccount, + ); + }); + await waitFor(() => rendered.result.current.communitiesPages[secondPageCid]?.comments?.length); + + expect(await db.getItem(firstPageCid)).toBeDefined(); + expect(await db.getItem(secondPageCid)).toBeDefined(); + + await act(async () => { + await rendered.result.current.invalidateCommunityPages(mockCommunity, sortType); + }); + + expect(useCommunitiesPagesStore.getState().communitiesPages[firstPageCid]).toBeUndefined(); + expect(useCommunitiesPagesStore.getState().communitiesPages[secondPageCid]).toBeUndefined(); + expect(await db.getItem(firstPageCid)).toBeUndefined(); + expect(await db.getItem(secondPageCid)).toBeUndefined(); + }); + + test("fetchPage onError logs when getPage rejects", async () => { + const mockCommunity = await mockAccount.plebbit.createCommunity({ + address: "community address 1", + }); + const sortType = "new"; + const firstPageCid = mockCommunity.posts.pageCids[sortType]; + const getPageOrig = MockPages.prototype.getPage; + MockPages.prototype.getPage = async () => { + throw new Error("getPage failed"); + }; + + const utils = await import("../../lib/utils"); + const retryOrig = utils.default.retryInfinity; + (utils.default as any).retryInfinity = async (fn: () => Promise, opts?: any) => { + try { + return await fn(); + } catch (e) { + opts?.onError?.(e); + throw e; + } + }; + + const logSpy = vi.spyOn(log, "error").mockImplementation(() => {}); + try { + await act(async () => { + try { + await rendered.result.current.addNextCommunityPageToStore( + mockCommunity, + sortType, + mockAccount, + ); + } catch { + // expected + } + }); + + expect(logSpy).toHaveBeenCalledWith( + expect.stringContaining("failed community.posts.getPage"), + expect.any(Error), + ); + } finally { + logSpy.mockRestore(); + (utils.default as any).retryInfinity = retryOrig; + MockPages.prototype.getPage = getPageOrig; + } + }); + + test("addCidToAccountComment error is logged when it rejects", async () => { + const addCidSpy = vi.fn().mockRejectedValue(new Error("addCid failed")); + const accountsGetState = accountsStore.getState; + (accountsStore as any).getState = () => ({ + ...accountsGetState(), + accountsActionsInternal: { addCidToAccountComment: addCidSpy }, + }); + + const mockCommunity = await mockAccount.plebbit.createCommunity({ + address: "community address 1", + }); + const sortType = "new"; + const logSpy = vi.spyOn(log, "error").mockImplementation(() => {}); + + act(() => { + rendered.result.current.addNextCommunityPageToStore(mockCommunity, sortType, mockAccount); + }); + + await waitFor(() => Object.keys(rendered.result.current.communitiesPages).length > 0); + await new Promise((r) => setTimeout(r, 100)); + + expect(logSpy).toHaveBeenCalledWith( + "communitiesPagesStore.addNextCommunityPageToStore addCidToAccountComment error", + expect.objectContaining({ comment: expect.anything(), error: expect.any(Error) }), + ); + + logSpy.mockRestore(); + (accountsStore as any).getState = accountsGetState; + }); + + test("onCommunityPostsClientsStateChange returns empty object when community missing", async () => { + let capturedCb: ((...args: any[]) => void) | null = null; + const utilsMod = await import("../../lib/utils"); + const origPageClients = utilsMod.default.pageClientsOnStateChange; + utilsMod.default.pageClientsOnStateChange = (_clients: any, cb: any) => { + capturedCb = cb; + }; + + const mockCommunity = await mockAccount.plebbit.createCommunity({ + address: "client-state-sub-addr", + }); + const sortType = "new"; + + await act(async () => { + await rendered.result.current.addNextCommunityPageToStore( + mockCommunity, + sortType, + mockAccount, + ); + }); + + await waitFor(() => Object.keys(rendered.result.current.communitiesPages).length > 0); + expect(capturedCb).toBeTruthy(); + + communitiesStore.setState({ communities: {} }); + capturedCb!("state", "type", "sort", "url"); + expect(communitiesStore.getState().communities).toEqual({}); + + utilsMod.default.pageClientsOnStateChange = origPageClients; + }); + + test("onCommunityPostsClientsStateChange updates client state when community exists", async () => { + let capturedCb: ((...args: any[]) => void) | null = null; + const utilsMod = await import("../../lib/utils"); + const origPageClients = utilsMod.default.pageClientsOnStateChange; + utilsMod.default.pageClientsOnStateChange = (_clients: any, cb: any) => { + capturedCb = cb; + }; + + const mockCommunity = await mockAccount.plebbit.createCommunity({ + address: "client-state-live-sub-addr", + }); + communitiesStore.setState({ + communities: { + [mockCommunity.address]: { + address: mockCommunity.address, + posts: { clients: {} }, + }, + }, + }); + + await act(async () => { + await rendered.result.current.addNextCommunityPageToStore(mockCommunity, "new", mockAccount); + }); + + expect(capturedCb).toBeTruthy(); + capturedCb!("fetching", "ipfs", "new", "http://client.example"); + expect( + communitiesStore.getState().communities[mockCommunity.address].posts.clients.ipfs.new[ + "http://client.example" + ], + ).toEqual({ + state: "fetching", + }); + + utilsMod.default.pageClientsOnStateChange = origPageClients; + }); + + test("add next pages from community.posts.pageCids", async () => { + const mockCommunity = await mockAccount.plebbit.createCommunity({ + address: "community address 1", + }); + // in the mock, sortType 'new' is only on community.pageCids + const sortType = "new"; + const communityAddress1FirstPageCid = mockCommunity.posts.pageCids[sortType]; + + act(() => { + rendered.result.current.addNextCommunityPageToStore(mockCommunity, sortType, mockAccount); + }); + + // wait for first page to be defined + await waitFor( + () => + rendered.result.current.communitiesPages[communityAddress1FirstPageCid].nextCid === + communityAddress1FirstPageCid + " - next page cid", + ); + expect(rendered.result.current.communitiesPages[communityAddress1FirstPageCid].nextCid).toBe( + communityAddress1FirstPageCid + " - next page cid", + ); + expect( + rendered.result.current.communitiesPages[communityAddress1FirstPageCid].comments.length, + ).toBe(100); + + // comments are individually stored in comments store + const firstCommentCid = + rendered.result.current.communitiesPages[communityAddress1FirstPageCid].comments[0].cid; + expect(rendered.result.current.comments[firstCommentCid].cid).toBe(firstCommentCid); + expect(Object.keys(rendered.result.current.comments).length).toBe(100); + + act(() => { + rendered.result.current.addNextCommunityPageToStore(mockCommunity, sortType, mockAccount); + }); + + // wait for second page to be defined + const communityAddress1SecondPageCid = `${communityAddress1FirstPageCid} - next page cid`; + await waitFor( + () => + rendered.result.current.communitiesPages[communityAddress1SecondPageCid].nextCid === + communityAddress1SecondPageCid + " - next page cid", + ); + expect(rendered.result.current.communitiesPages[communityAddress1SecondPageCid].nextCid).toBe( + communityAddress1SecondPageCid + " - next page cid", + ); + expect( + rendered.result.current.communitiesPages[communityAddress1SecondPageCid].comments.length, + ).toBe(100); + + // no more pages + const getPage = MockPages.prototype.getPage; + MockPages.prototype.getPage = async (options) => ({ comments: [], nextCid: undefined }); + + act(() => { + rendered.result.current.addNextCommunityPageToStore(mockCommunity, sortType, mockAccount); + }); + + // wait for third page to be defined + const communityAddress1ThirdPageCid = `${communityAddress1SecondPageCid} - next page cid`; + await waitFor( + () => + rendered.result.current.communitiesPages[communityAddress1ThirdPageCid].nextCid === + undefined, + ); + expect(rendered.result.current.communitiesPages[communityAddress1ThirdPageCid].nextCid).toBe( + undefined, + ); + expect( + rendered.result.current.communitiesPages[communityAddress1ThirdPageCid].comments.length, + ).toBe(0); + + // adding a next page when no more pages does nothing + const previousCommunityPagesFetchedCount = Object.keys( + rendered.result.current.communitiesPages, + ).length; + act(() => { + rendered.result.current.addNextCommunityPageToStore(mockCommunity, sortType, mockAccount); + }); + await expect( + tlWaitFor(() => { + if ( + !( + Object.keys(rendered.result.current.communitiesPages).length > + previousCommunityPagesFetchedCount + ) + ) + throw new Error("condition not met"); + }), + ).rejects.toThrow(); + expect(Object.keys(rendered.result.current.communitiesPages).length).toBe( + previousCommunityPagesFetchedCount, + ); + + // restore mock + MockPages.prototype.getPage = getPage; + }); + + test("add next pages from community.posts.pages", async () => { + const mockCommunity = await mockAccount.plebbit.createCommunity({ + address: "community address 1", + }); + // in the mock, sortType 'hot' is only on community.pages + const sortType = "hot"; + const communityAddress1FirstPageCid = mockCommunity.posts.pages[sortType].nextCid; + + act(() => { + rendered.result.current.addNextCommunityPageToStore(mockCommunity, sortType, mockAccount); + }); + + // wait for first page to be defined + await waitFor( + () => + rendered.result.current.communitiesPages[communityAddress1FirstPageCid].nextCid === + communityAddress1FirstPageCid + " - next page cid", + ); + expect(rendered.result.current.communitiesPages[communityAddress1FirstPageCid].nextCid).toBe( + communityAddress1FirstPageCid + " - next page cid", + ); + expect( + rendered.result.current.communitiesPages[communityAddress1FirstPageCid].comments.length, + ).toBe(100); + + act(() => { + rendered.result.current.addNextCommunityPageToStore(mockCommunity, sortType, mockAccount); + }); + + // wait for second page to be defined + const communityAddress1SecondPageCid = `${communityAddress1FirstPageCid} - next page cid`; + await waitFor( + () => + rendered.result.current.communitiesPages[communityAddress1SecondPageCid].nextCid === + communityAddress1SecondPageCid + " - next page cid", + ); + expect(rendered.result.current.communitiesPages[communityAddress1SecondPageCid].nextCid).toBe( + communityAddress1SecondPageCid + " - next page cid", + ); + expect( + rendered.result.current.communitiesPages[communityAddress1SecondPageCid].comments.length, + ).toBe(100); + + // no more pages + const getPage = MockPages.prototype.getPage; + MockPages.prototype.getPage = async (options) => ({ comments: [], nextCid: undefined }); + + act(() => { + rendered.result.current.addNextCommunityPageToStore(mockCommunity, sortType, mockAccount); + }); + + // wait for third page to be defined + const communityAddress1ThirdPageCid = `${communityAddress1SecondPageCid} - next page cid`; + await waitFor( + () => + rendered.result.current.communitiesPages[communityAddress1ThirdPageCid].nextCid === + undefined, + ); + expect(rendered.result.current.communitiesPages[communityAddress1ThirdPageCid].nextCid).toBe( + undefined, + ); + expect( + rendered.result.current.communitiesPages[communityAddress1ThirdPageCid].comments.length, + ).toBe(0); + + // adding a next page when no more pages does nothing + const previousCommunityPagesFetchedCount = Object.keys( + rendered.result.current.communitiesPages, + ).length; + act(() => { + rendered.result.current.addNextCommunityPageToStore(mockCommunity, sortType, mockAccount); + }); + await expect( + tlWaitFor(() => { + if ( + !( + Object.keys(rendered.result.current.communitiesPages).length > + previousCommunityPagesFetchedCount + ) + ) + throw new Error("condition not met"); + }), + ).rejects.toThrow(); + expect(Object.keys(rendered.result.current.communitiesPages).length).toBe( + previousCommunityPagesFetchedCount, + ); + + // restore mock + MockPages.prototype.getPage = getPage; + }); + + test("page comments without updatedAt are still indexed on first insert", async () => { + const mockCommunity = await mockAccount.plebbit.createCommunity({ + address: "community address 1", + }); + const sortType = "new"; + const firstPageCid = mockCommunity.posts.pageCids[sortType]; + const commentCid = firstPageCid + " comment-no-updated-at"; + const getPageOriginal = MockPages.prototype.getPage; + MockPages.prototype.getPage = async (options) => { + const cid = options?.cid; + await sleep(200); + return { + nextCid: cid + " - next page cid", + comments: [ + { + cid: commentCid, + timestamp: 100, + communityAddress: "community address 1", + // no updatedAt - should still be indexed via max(updatedAt??0, timestamp, 0) + }, + ], + }; + }; + act(() => { + rendered.result.current.addNextCommunityPageToStore(mockCommunity, sortType, mockAccount); + }); + await waitFor(() => { + expect(rendered.result.current.comments[commentCid]).toBeDefined(); + }); + expect(rendered.result.current.comments[commentCid].cid).toBe(commentCid); + expect(rendered.result.current.comments[commentCid].timestamp).toBe(100); + expect(rendered.result.current.comments[commentCid].updatedAt).toBeUndefined(); + MockPages.prototype.getPage = getPageOriginal; + }); + + test("existing fresher indexed comment is not overwritten by older/empty-freshness page data", async () => { + const mockCommunity = await mockAccount.plebbit.createCommunity({ + address: "community address 2", + }); + const sortType = "new"; + const firstPageCid = mockCommunity.posts.pageCids[sortType]; + const secondPageCid = firstPageCid + " - next page cid"; + const sharedCommentCid = "shared-comment-fresher-wins"; + const getPageOriginal = MockPages.prototype.getPage; + MockPages.prototype.getPage = async (options) => { + const cid = options?.cid; + await sleep(200); + if (cid === firstPageCid) { + return { + nextCid: secondPageCid, + comments: [ + { + cid: sharedCommentCid, + timestamp: 50, + updatedAt: 100, + communityAddress: "community address 2", + }, + ], + }; + } + if (cid === secondPageCid) { + return { + nextCid: secondPageCid + " - next page cid", + comments: [ + { + cid: sharedCommentCid, + timestamp: 50, + updatedAt: 10, + communityAddress: "community address 2", + }, + ], + }; + } + return { nextCid: undefined, comments: [] }; + }; + act(() => { + rendered.result.current.addNextCommunityPageToStore(mockCommunity, sortType, mockAccount); + }); + await waitFor(() => { + expect(rendered.result.current.comments[sharedCommentCid]).toBeDefined(); + }); + expect(rendered.result.current.comments[sharedCommentCid].updatedAt).toBe(100); + act(() => { + rendered.result.current.addNextCommunityPageToStore(mockCommunity, sortType, mockAccount); + }); + await waitFor(() => { + expect(rendered.result.current.communitiesPages[secondPageCid]).toBeDefined(); + }); + expect(rendered.result.current.comments[sharedCommentCid].updatedAt).toBe(100); + MockPages.prototype.getPage = getPageOriginal; + }); +}); diff --git a/src/stores/communities-pages/communities-pages-store.ts b/src/stores/communities-pages/communities-pages-store.ts new file mode 100644 index 00000000..0a8c2a6a --- /dev/null +++ b/src/stores/communities-pages/communities-pages-store.ts @@ -0,0 +1,402 @@ +import utils from "../../lib/utils"; +import Logger from "@plebbit/plebbit-logger"; +// include communities pages store with feeds for debugging +export const log = Logger("bitsocial-react-hooks:feeds:stores"); +import { + Community, + CommunityPage, + CommunitiesPages, + Account, + Comment, + Comments, +} from "../../types"; +import accountsStore from "../accounts"; +import communitiesStore, { CommunitiesState } from "../communities"; +import localForageLru from "../../lib/localforage-lru"; +import createStore from "zustand"; +import assert from "assert"; + +const communitiesPagesDatabase = localForageLru.createInstance({ + name: "plebbitReactHooks-communitiesPages", + size: 500, +}); + +/** Freshness for comparison: max(updatedAt, timestamp, 0). Used to decide add vs replace per CID. Exported for coverage. */ +export const getCommentFreshness = (comment: Comment | undefined): number => + Math.max(comment?.updatedAt ?? 0, comment?.timestamp ?? 0, 0); + +// reset all event listeners in between tests +const listeners: any = []; + +type CommunitiesPagesState = { + communitiesPages: CommunitiesPages; + comments: Comments; + addNextCommunityPageToStore: Function; + invalidateCommunityPages: Function; + addCommunityPageCommentsToStore: Function; +}; + +const communitiesPagesStore = createStore( + (setState: Function, getState: Function) => ({ + // TODO: eventually clear old pages and comments from memory + communitiesPages: {}, + comments: {}, + + addNextCommunityPageToStore: async ( + community: Community, + sortType: string, + account: Account, + modQueue?: string[], + ) => { + assert( + community?.address && typeof community?.address === "string", + `communitiesPagesStore.addNextCommunityPageToStore community '${community}' invalid`, + ); + assert( + sortType && typeof sortType === "string", + `communitiesPagesStore.addNextCommunityPageToStore sortType '${sortType}' invalid`, + ); + assert( + typeof account?.plebbit?.createCommunity === "function", + `communitiesPagesStore.addNextCommunityPageToStore account '${account}' invalid`, + ); + assert( + !modQueue || Array.isArray(modQueue), + `communitiesPagesStore.addNextCommunityPageToStore modQueue '${modQueue}' invalid`, + ); + + let pageType = "posts"; + if (modQueue?.[0]) { + // TODO: allow multiple modQueue at once, fow now only use first in array + // TODO: fix 'sortType' is not accurate variable name when pageType is 'modQueue' + sortType = modQueue[0]; + pageType = "modQueue"; + } + + // check the preloaded posts on community.posts.pages first, then the community.posts.pageCids + const communityFirstPageCid = getCommunityFirstPageCid(community, sortType, pageType); + if (!communityFirstPageCid) { + log( + `communitiesPagesStore.addNextCommunityPageToStore community '${community?.address}' sortType '${sortType}' no communityFirstPageCid`, + ); + return; + } + + // all communities pages in store + const { communitiesPages } = getState(); + // only specific pages of the community+sortType + const communityPages = getCommunityPages(community, sortType, communitiesPages, pageType); + + // if no pages exist yet, add the first page + let pageCidToAdd: string; + if (!communityPages.length) { + pageCidToAdd = communityFirstPageCid; + } else { + const nextCid = communityPages[communityPages.length - 1]?.nextCid; + // if last nextCid is undefined, reached end of pages + if (!nextCid) { + log.trace("communitiesPagesStore.addNextCommunityPageToStore no more pages", { + communityAddress: community.address, + sortType, + account, + }); + return; + } + + pageCidToAdd = nextCid; + } + + // page is already added or pending + if (communitiesPages[pageCidToAdd] || fetchPagePending[account.id + pageCidToAdd]) { + return; + } + + fetchPagePending[account.id + pageCidToAdd] = true; + let page: CommunityPage; + try { + page = await fetchPage(pageCidToAdd, community.address, account, pageType); + log.trace("communitiesPagesStore.addNextCommunityPageToStore community.posts.getPage", { + pageCid: pageCidToAdd, + communityAddress: community.address, + account, + }); + } catch (e) { + throw e; + } finally { + fetchPagePending[account.id + pageCidToAdd] = false; + } + + // find new comments in the page + const flattenedComments = utils.flattenCommentsPages(page); + const { comments } = getState(); + let hasNewComments = false; + const newComments: Comments = {}; + for (const comment of flattenedComments) { + const existing = comments[comment.cid]; + if ( + comment.cid && + (!existing || getCommentFreshness(comment) > getCommentFreshness(existing)) + ) { + // don't clone the comment to save memory, comments remain a pointer to the page object + newComments[comment.cid] = comment; + hasNewComments = true; + } + } + + setState(({ communitiesPages, comments }: any) => { + const newState: any = { communitiesPages: { ...communitiesPages, [pageCidToAdd]: page } }; + if (hasNewComments) { + newState.comments = { ...comments, ...newComments }; + } + return newState; + }); + log("communitiesPagesStore.addNextCommunityPageToStore", { + pageCid: pageCidToAdd, + communityAddress: community.address, + sortType, + page, + account, + }); + + // when publishing a comment, you don't yet know its CID + // so when a new comment is fetched, check to see if it's your own + // comment, and if yes, add the CID to your account comments database + for (const comment of flattenedComments) { + accountsStore + .getState() + .accountsActionsInternal.addCidToAccountComment(comment) + .catch((error: unknown) => + log.error( + "communitiesPagesStore.addNextCommunityPageToStore addCidToAccountComment error", + { comment, error }, + ), + ); + } + }, + + invalidateCommunityPages: async ( + community: Community, + sortType: string, + modQueue?: string[], + ) => { + assert( + community?.address && typeof community?.address === "string", + `communitiesPagesStore.invalidateCommunityPages community '${community}' invalid`, + ); + assert( + sortType && typeof sortType === "string", + `communitiesPagesStore.invalidateCommunityPages sortType '${sortType}' invalid`, + ); + assert( + !modQueue || Array.isArray(modQueue), + `communitiesPagesStore.invalidateCommunityPages modQueue '${modQueue}' invalid`, + ); + + let pageType = "posts"; + if (modQueue?.[0]) { + // TODO: allow multiple modQueue at once, for now only use first in array + // TODO: fix 'sortType' is not accurate variable name when pageType is 'modQueue' + sortType = modQueue[0]; + pageType = "modQueue"; + } + + const firstPageCid = getCommunityFirstPageCid(community, sortType, pageType); + if (!firstPageCid) { + return; + } + + const { communitiesPages } = getState(); + const pageCidsToInvalidate = new Set([firstPageCid]); + let nextPageCid = communitiesPages[firstPageCid]?.nextCid; + while (nextPageCid) { + pageCidsToInvalidate.add(nextPageCid); + nextPageCid = communitiesPages[nextPageCid]?.nextCid; + } + + await Promise.all( + [...pageCidsToInvalidate].map((pageCid) => communitiesPagesDatabase.removeItem(pageCid)), + ); + + setState(({ communitiesPages }: any) => { + const nextCommunitiesPages = { ...communitiesPages }; + for (const pageCid of pageCidsToInvalidate) { + delete nextCommunitiesPages[pageCid]; + } + return { communitiesPages: nextCommunitiesPages }; + }); + }, + + // communities contain preloaded pages, those page comments must be added separately + addCommunityPageCommentsToStore: (community: Community) => { + if (!community.posts?.pages) { + return; + } + + // find new comments in the page + const flattenedComments = utils.flattenCommentsPages(community.posts.pages); + const { comments } = getState(); + let hasNewComments = false; + const newComments: Comments = {}; + for (const comment of flattenedComments) { + const existing = comments[comment.cid]; + if ( + comment.cid && + (!existing || getCommentFreshness(comment) > getCommentFreshness(existing)) + ) { + // don't clone the comment to save memory, comments remain a pointer to the page object + newComments[comment.cid] = comment; + hasNewComments = true; + } + } + + if (!hasNewComments) { + return; + } + + setState(({ comments }: any) => { + return { comments: { ...comments, ...newComments } }; + }); + log("communitiesPagesStore.addCommunityPageCommentsToStore", { community, newComments }); + }, + }), +); + +// set clients states on communities store so the frontend can display it, dont persist in db because a reload cancels updating +const onCommunityPostsClientsStateChange = + (communityAddress: string) => + (clientState: string, clientType: string, sortType: string, clientUrl: string) => { + communitiesStore.setState((state: CommunitiesState) => { + // make sure not undefined, sometimes happens in e2e tests + if (!state.communities[communityAddress]) { + return {}; + } + const client = { state: clientState }; + const community = { ...state.communities[communityAddress] }; + community.posts = { ...community.posts }; + community.posts.clients = { ...community.posts.clients }; + community.posts.clients[clientType] = { ...community.posts.clients[clientType] }; + community.posts.clients[clientType][sortType] = { + ...community.posts.clients[clientType][sortType], + }; + community.posts.clients[clientType][sortType][clientUrl] = client; + return { communities: { ...state.communities, [community.address]: community } }; + }); + }; + +const fetchPageCommunities: { [communityAddress: string]: any } = {}; // cache plebbit.createCommunities because sometimes it's slow +let fetchPagePending: { [key: string]: boolean } = {}; +const fetchPage = async ( + pageCid: string, + communityAddress: string, + account: Account, + pageType: string, +) => { + // community page is cached + const cachedCommunityPage = await communitiesPagesDatabase.getItem(pageCid); + if (cachedCommunityPage) { + return cachedCommunityPage; + } + if (!fetchPageCommunities[communityAddress]) { + fetchPageCommunities[communityAddress] = await account.plebbit.createCommunity({ + address: communityAddress, + }); + listeners.push(fetchPageCommunities[communityAddress]); + + // set clients states on communities store so the frontend can display it + utils.pageClientsOnStateChange( + fetchPageCommunities[communityAddress][pageType]?.clients, + onCommunityPostsClientsStateChange(communityAddress), + ); + } + + const onError = (error: any) => + log.error( + `communitiesPagesStore community '${communityAddress}' failed community.posts.getPage page cid '${pageCid}':`, + error, + ); + const fetchedCommunityPage = await utils.retryInfinity( + () => fetchPageCommunities[communityAddress][pageType].getPage({ cid: pageCid }), + { onError }, + ); + await communitiesPagesDatabase.setItem(pageCid, utils.clone(fetchedCommunityPage)); + return fetchedCommunityPage; +}; + +/** + * Util function to get all pages in the store for a + * specific community+sortType using `CommunityPage.nextCid` + */ +export const getCommunityPages = ( + community: Community, + sortType: string, + communitiesPages: CommunitiesPages, + pageType: string, +) => { + assert( + communitiesPages && typeof communitiesPages === "object", + `getCommunityPages communitiesPages '${communitiesPages}' invalid`, + ); + const pages: CommunityPage[] = []; + const firstPageCid = getCommunityFirstPageCid(community, sortType, pageType); + // community has no pages + // TODO: if a loaded community doesn't have a first page, it's unclear what we should do + // should we try to use another sort type by default, like 'hot', or should we just ignore it? + // 'return pages' to ignore it for now + if (!firstPageCid) { + return pages; + } + const firstPage = communitiesPages[firstPageCid]; + if (!firstPage) { + return pages; + } + pages.push(firstPage); + while (true) { + const nextCid = pages[pages.length - 1]?.nextCid; + const communityPage = nextCid && communitiesPages[nextCid]; + if (!communityPage) { + return pages; + } + pages.push(communityPage); + } +}; + +export const getCommunityFirstPageCid = ( + community: Community, + sortType: string, + pageType = "posts", +) => { + assert(community?.address, `getCommunityFirstPageCid community '${community}' invalid`); + assert( + sortType && typeof sortType === "string", + `getCommunityFirstPageCid sortType '${sortType}' invalid`, + ); + // community has preloaded posts for sort type + if (community[pageType]?.pages?.[sortType]?.comments) { + return community[pageType]?.pages?.[sortType]?.nextCid; + } + return community[pageType]?.pageCids?.[sortType]; + + // TODO: if a loaded community doesn't have a first page, it's unclear what we should do + // should we try to use another sort type by default, like 'hot', or should we just ignore it? +}; + +// reset store in between tests +const originalState = communitiesPagesStore.getState(); +// async function because some stores have async init +export const resetCommunitiesPagesStore = async () => { + fetchPagePending = {}; + // remove all event listeners + listeners.forEach((listener: any) => listener.removeAllListeners()); + // destroy all component subscriptions to the store + communitiesPagesStore.destroy(); + // restore original state + communitiesPagesStore.setState(originalState); +}; + +// reset database and store in between tests +export const resetCommunitiesPagesDatabaseAndStore = async () => { + await localForageLru.createInstance({ name: "plebbitReactHooks-communitiesPages" }).clear(); + await resetCommunitiesPagesStore(); +}; + +export default communitiesPagesStore; diff --git a/src/stores/communities-pages/index.ts b/src/stores/communities-pages/index.ts new file mode 100644 index 00000000..0335a1fb --- /dev/null +++ b/src/stores/communities-pages/index.ts @@ -0,0 +1,3 @@ +import communitiesPagesStore from "./communities-pages-store"; +export * from "./communities-pages-store"; +export default communitiesPagesStore; diff --git a/src/stores/communities/communities-store.test.ts b/src/stores/communities/communities-store.test.ts new file mode 100644 index 00000000..56e7d13a --- /dev/null +++ b/src/stores/communities/communities-store.test.ts @@ -0,0 +1,257 @@ +import { act } from "@testing-library/react"; +import testUtils, { renderHook } from "../../lib/test-utils"; +import communitiesStore, { resetCommunitiesDatabaseAndStore } from "./communities-store"; +import localForageLru from "../../lib/localforage-lru"; +import { setPlebbitJs } from "../.."; +import PlebbitJsMock from "../../lib/plebbit-js/plebbit-js-mock"; +import accountsStore from "../accounts"; +import communitiesPagesStore from "../communities-pages"; + +let mockAccount: any; + +describe("communities store", () => { + beforeAll(async () => { + setPlebbitJs(PlebbitJsMock); + testUtils.silenceReactWarnings(); + const plebbit = await PlebbitJsMock(); + mockAccount = { id: "mock-account-id", plebbit }; + }); + afterAll(() => { + testUtils.restoreAll(); + }); + + afterEach(async () => { + await resetCommunitiesDatabaseAndStore(); + }); + + test("initial store", () => { + const { result } = renderHook(() => communitiesStore.getState()); + expect(result.current.communities).toEqual({}); + expect(result.current.errors).toEqual({}); + expect(typeof result.current.addCommunityToStore).toBe("function"); + }); + + test("addCommunityToStore adds community from plebbit", async () => { + const address = "community address 1"; + + await act(async () => { + await communitiesStore.getState().addCommunityToStore(address, mockAccount); + }); + + expect(communitiesStore.getState().communities[address]).toBeDefined(); + expect(communitiesStore.getState().communities[address].address).toBe(address); + }); + + test("cached community create failure logs to console", async () => { + const address = "cached-fail-address"; + const db = localForageLru.createInstance({ name: "plebbitReactHooks-communities" }); + await db.setItem(address, { address, invalid: "data" }); + + const createCommunityOriginal = mockAccount.plebbit.createCommunity; + mockAccount.plebbit.createCommunity = vi.fn().mockRejectedValue(new Error("create failed")); + const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + + await act(async () => { + try { + await communitiesStore.getState().addCommunityToStore(address, mockAccount); + } catch { + // expected to throw + } + }); + + expect(consoleSpy).toHaveBeenCalledWith( + "failed plebbit.createCommunity(cachedCommunity)", + expect.objectContaining({ + cachedCommunity: expect.any(Object), + error: expect.any(Error), + }), + ); + consoleSpy.mockRestore(); + mockAccount.plebbit.createCommunity = createCommunityOriginal; + }); + + test("missing-community state guard returns empty object in client updater", async () => { + const address = "client-update-address"; + let storedCb: ((...args: any[]) => void) | null = null; + + const utils = await import("../../lib/utils"); + const origClientsOnStateChange = utils.default.clientsOnStateChange; + (utils.default as any).clientsOnStateChange = (_clients: any, cb: any) => { + storedCb = () => cb("state", "type", "url"); + }; + + await act(async () => { + await communitiesStore.getState().addCommunityToStore(address, mockAccount); + }); + + expect(storedCb).toBeTruthy(); + communitiesStore.setState({ communities: {} }); + + storedCb!(); + + expect(communitiesStore.getState().communities).toEqual({}); + + (utils.default as any).clientsOnStateChange = origClientsOnStateChange; + }); + + test("community.update catch logs when update rejects", async () => { + const address = "update-reject-address"; + const plebbit = await PlebbitJsMock(); + const community = await plebbit.createCommunity({ address }); + const updateSpy = vi + .spyOn(community, "update") + .mockRejectedValueOnce(new Error("update failed")); + + const createCommunityOrig = mockAccount.plebbit.createCommunity; + mockAccount.plebbit.createCommunity = vi.fn().mockResolvedValue(community); + + await act(async () => { + await communitiesStore.getState().addCommunityToStore(address, mockAccount); + }); + + await new Promise((r) => setTimeout(r, 100)); + + mockAccount.plebbit.createCommunity = createCommunityOrig; + updateSpy.mockRestore(); + }); + + test("addCommunityToStore sets errors and throws when createCommunity rejects", async () => { + const address = "create-reject-address"; + const createOrig = mockAccount.plebbit.createCommunity; + mockAccount.plebbit.createCommunity = vi.fn().mockRejectedValue(new Error("create failed")); + + await act(async () => { + try { + await communitiesStore.getState().addCommunityToStore(address, mockAccount); + } catch (e) { + expect((e as Error).message).toBe("create failed"); + } + }); + + expect(communitiesStore.getState().errors[address]).toHaveLength(1); + expect(communitiesStore.getState().errors[address][0].message).toBe("create failed"); + mockAccount.plebbit.createCommunity = createOrig; + }); + + test("addCommunityToStore throws generic Error when community is undefined without thrown error", async () => { + const address = "resolve-undefined-address"; + const createOrig = mockAccount.plebbit.createCommunity; + mockAccount.plebbit.createCommunity = vi.fn().mockResolvedValue(undefined); + + await act(async () => { + try { + await communitiesStore.getState().addCommunityToStore(address, mockAccount); + } catch (e) { + expect((e as Error).message).toContain("failed getting community"); + } + }); + + mockAccount.plebbit.createCommunity = createOrig; + }); + + test("community update event calls addCommunityRoleToAccountsCommunities and addCommunityPageCommentsToStore", async () => { + const address = "update-event-address"; + const plebbit = await PlebbitJsMock(); + const community = await plebbit.createCommunity({ address }); + const addRoleSpy = vi.fn().mockResolvedValue(undefined); + const addCommentsSpy = vi.fn(); + const accountsGetState = accountsStore.getState; + (accountsStore as any).getState = () => ({ + ...accountsGetState(), + accountsActionsInternal: { addCommunityRoleToAccountsCommunities: addRoleSpy }, + }); + const pagesGetState = communitiesPagesStore.getState; + (communitiesPagesStore as any).getState = () => ({ + ...pagesGetState(), + addCommunityPageCommentsToStore: addCommentsSpy, + }); + + const createOrig = mockAccount.plebbit.createCommunity; + mockAccount.plebbit.createCommunity = vi.fn().mockResolvedValue(community); + + await act(async () => { + await communitiesStore.getState().addCommunityToStore(address, mockAccount); + }); + + community.emit("update", community); + await new Promise((r) => setTimeout(r, 50)); + + expect(addRoleSpy).toHaveBeenCalledWith(expect.objectContaining({ address })); + expect(addCommentsSpy).toHaveBeenCalledWith(expect.objectContaining({ address })); + + (accountsStore as any).getState = accountsGetState; + (communitiesPagesStore as any).getState = pagesGetState; + mockAccount.plebbit.createCommunity = createOrig; + }); + + test("createCommunity with no signer asserts address must be undefined", async () => { + const plebbit = await PlebbitJsMock(); + const community = await plebbit.createCommunity({ address: "new-sub-address" }); + const createOrig = mockAccount.plebbit.createCommunity; + mockAccount.plebbit.createCommunity = vi.fn().mockResolvedValue(community); + + await act(async () => { + await communitiesStore.getState().createCommunity({}, mockAccount); + }); + + expect(mockAccount.plebbit.createCommunity).toHaveBeenCalledWith({}); + mockAccount.plebbit.createCommunity = createOrig; + }); + + test("createCommunity with address but no signer throws (branch 251)", async () => { + await expect( + communitiesStore.getState().createCommunity({ address: "addr-no-signer" }, mockAccount), + ).rejects.toThrow("createCommunityOptions.address 'addr-no-signer' must be undefined"); + }); + + test("clientsOnStateChange with chainTicker branch", async () => { + const address = "chain-ticker-address"; + let storedCb: ((...args: any[]) => void) | null = null; + + const utils = await import("../../lib/utils"); + const origClientsOnStateChange = utils.default.clientsOnStateChange; + (utils.default as any).clientsOnStateChange = (_clients: any, cb: any) => { + storedCb = () => cb("state", "type", "url", "ETH"); + }; + + await act(async () => { + await communitiesStore.getState().addCommunityToStore(address, mockAccount); + }); + + expect(storedCb).toBeTruthy(); + communitiesStore.setState((state: any) => ({ + communities: { + ...state.communities, + [address]: { + ...state.communities[address], + clients: { type: {} }, + }, + }, + })); + storedCb!(); + expect(communitiesStore.getState().communities[address]?.clients?.type?.ETH).toBeDefined(); + + (utils.default as any).clientsOnStateChange = origClientsOnStateChange; + }); + + test("clientsOnStateChange returns {} when community missing and chainTicker provided", async () => { + const address = "chain-missing-address"; + let storedCb: ((...args: any[]) => void) | null = null; + + const utils = await import("../../lib/utils"); + const origClientsOnStateChange = utils.default.clientsOnStateChange; + (utils.default as any).clientsOnStateChange = (_clients: any, cb: any) => { + storedCb = () => cb("state", "type", "url", "ETH"); + }; + + await act(async () => { + await communitiesStore.getState().addCommunityToStore(address, mockAccount); + }); + expect(storedCb).toBeTruthy(); + communitiesStore.setState({ communities: {} }); + storedCb!(); + expect(communitiesStore.getState().communities).toEqual({}); + + (utils.default as any).clientsOnStateChange = origClientsOnStateChange; + }); +}); diff --git a/src/stores/communities/communities-store.ts b/src/stores/communities/communities-store.ts new file mode 100644 index 00000000..818d2cfe --- /dev/null +++ b/src/stores/communities/communities-store.ts @@ -0,0 +1,354 @@ +import assert from "assert"; +import localForageLru from "../../lib/localforage-lru"; +const communitiesDatabase = localForageLru.createInstance({ + name: "plebbitReactHooks-communities", + size: 500, +}); +import Logger from "@plebbit/plebbit-logger"; +const log = Logger("bitsocial-react-hooks:communities:stores"); +import { Community, Communities, Account, CreateCommunityOptions } from "../../types"; +import utils from "../../lib/utils"; +import createStore from "zustand"; +import accountsStore from "../accounts"; +import communitiesPagesStore from "../communities-pages"; + +let plebbitGetCommunityPending: { [key: string]: boolean } = {}; + +// reset all event listeners in between tests +const listeners: any = []; + +export type CommunitiesState = { + communities: Communities; + errors: { [communityAddress: string]: Error[] }; + addCommunityToStore: Function; + refreshCommunity: Function; + editCommunity: Function; + createCommunity: Function; + deleteCommunity: Function; +}; + +const communitiesStore = createStore( + (setState: Function, getState: Function) => ({ + communities: {}, + errors: {}, + + async addCommunityToStore(communityAddress: string, account: Account) { + assert( + communityAddress !== "" && typeof communityAddress === "string", + `communitiesStore.addCommunityToStore invalid communityAddress argument '${communityAddress}'`, + ); + assert( + typeof account?.plebbit?.getCommunity === "function", + `communitiesStore.addCommunityToStore invalid account argument '${account}'`, + ); + + // community is in store already, do nothing + const { communities } = getState(); + let community: Community | undefined = communities[communityAddress]; + if (community || plebbitGetCommunityPending[communityAddress + account.id]) { + return; + } + + // start trying to get community + plebbitGetCommunityPending[communityAddress + account.id] = true; + let errorGettingCommunity: any; + + // try to find community in owner communities + if (account.plebbit.communities.includes(communityAddress)) { + community = await account.plebbit.createCommunity({ address: communityAddress }); + } + + // try to find community in database + let fetchedAt: number | undefined; + if (!community) { + const communityData: any = await communitiesDatabase.getItem(communityAddress); + if (communityData) { + fetchedAt = communityData.fetchedAt; + delete communityData.fetchedAt; // not part of plebbit-js schema + try { + community = await account.plebbit.createCommunity(communityData); + } catch (e) { + fetchedAt = undefined; + // need to log this always or it could silently fail in production and cache never be used + console.error("failed plebbit.createCommunity(cachedCommunity)", { + cachedCommunity: communityData, + error: e, + }); + } + } + if (community) { + // add page comments to communitiesPagesStore so they can be used in useComment + communitiesPagesStore.getState().addCommunityPageCommentsToStore(community); + } + } + + // community not in database, try to fetch from plebbit-js + if (!community) { + try { + community = await account.plebbit.createCommunity({ address: communityAddress }); + } catch (e) { + errorGettingCommunity = e; + } + } + + // finished trying to get community + plebbitGetCommunityPending[communityAddress + account.id] = false; + + // failure getting community + if (!community) { + if (errorGettingCommunity) { + setState((state: CommunitiesState) => { + let communityErrors = state.errors[communityAddress] || []; + communityErrors = [...communityErrors, errorGettingCommunity]; + return { ...state, errors: { ...state.errors, [communityAddress]: communityErrors } }; + }); + } + + throw ( + errorGettingCommunity || + Error( + `communitiesStore.addCommunityToStore failed getting community '${communityAddress}'`, + ) + ); + } + + // success getting community + const firstCommunityState = utils.clone({ ...community, fetchedAt }); + await communitiesDatabase.setItem(communityAddress, firstCommunityState); + log("communitiesStore.addCommunityToStore", { communityAddress, community, account }); + setState((state: any) => ({ + communities: { ...state.communities, [communityAddress]: firstCommunityState }, + })); + + // the community has published new posts + community.on("update", async (updatedCommunity: Community) => { + updatedCommunity = utils.clone(updatedCommunity); + + // add fetchedAt to be able to expire the cache + // NOTE: fetchedAt is undefined on owner communities because never stale + updatedCommunity.fetchedAt = Math.floor(Date.now() / 1000); + + await communitiesDatabase.setItem(communityAddress, updatedCommunity); + log("communitiesStore community update", { + communityAddress, + updatedCommunity, + account, + }); + setState((state: any) => ({ + communities: { ...state.communities, [communityAddress]: updatedCommunity }, + })); + + // if a community has a role with an account's address add it to the account.communities + accountsStore + .getState() + .accountsActionsInternal.addCommunityRoleToAccountsCommunities(updatedCommunity); + + // add page comments to communitiesPagesStore so they can be used in useComment + communitiesPagesStore.getState().addCommunityPageCommentsToStore(updatedCommunity); + }); + + community.on("updatingstatechange", (updatingState: string) => { + setState((state: CommunitiesState) => ({ + communities: { + ...state.communities, + [communityAddress]: { ...state.communities[communityAddress], updatingState }, + }, + })); + }); + + community.on("error", (error: Error) => { + setState((state: CommunitiesState) => { + let communityErrors = state.errors[communityAddress] || []; + communityErrors = [...communityErrors, error]; + return { ...state, errors: { ...state.errors, [communityAddress]: communityErrors } }; + }); + }); + + // set clients on community so the frontend can display it, dont persist in db because a reload cancels updating + utils.clientsOnStateChange( + community?.clients, + (clientState: string, clientType: string, clientUrl: string, chainTicker?: string) => { + setState((state: CommunitiesState) => { + // make sure not undefined, sometimes happens in e2e tests + if (!state.communities[communityAddress]) { + return {}; + } + const clients = { ...state.communities[communityAddress]?.clients }; + const client = { state: clientState }; + if (chainTicker) { + const chainProviders = { ...clients[clientType][chainTicker], [clientUrl]: client }; + clients[clientType] = { ...clients[clientType], [chainTicker]: chainProviders }; + } else { + clients[clientType] = { ...clients[clientType], [clientUrl]: client }; + } + return { + communities: { + ...state.communities, + [communityAddress]: { ...state.communities[communityAddress], clients }, + }, + }; + }); + }, + ); + + listeners.push(community); + community + .update() + .catch((error: unknown) => log.trace("community.update error", { community, error })); + }, + + async refreshCommunity(communityAddress: string, account: Account) { + assert( + communityAddress !== "" && typeof communityAddress === "string", + `communitiesStore.refreshCommunity invalid communityAddress argument '${communityAddress}'`, + ); + assert( + typeof account?.plebbit?.getCommunity === "function", + `communitiesStore.refreshCommunity invalid account argument '${account}'`, + ); + + const refreshedCommunity = utils.clone( + await account.plebbit.getCommunity({ address: communityAddress }), + ); + refreshedCommunity.fetchedAt = Math.floor(Date.now() / 1000); + + await communitiesDatabase.setItem(communityAddress, refreshedCommunity); + log("communitiesStore.refreshCommunity", { + communityAddress, + refreshedCommunity, + account, + }); + setState((state: any) => ({ + communities: { ...state.communities, [communityAddress]: refreshedCommunity }, + })); + + communitiesPagesStore.getState().addCommunityPageCommentsToStore(refreshedCommunity); + + return refreshedCommunity; + }, + + // user is the owner of the community and can edit it locally + async editCommunity(communityAddress: string, communityEditOptions: any, account: Account) { + assert( + communityAddress !== "" && typeof communityAddress === "string", + `communitiesStore.editCommunity invalid communityAddress argument '${communityAddress}'`, + ); + assert( + communityEditOptions && typeof communityEditOptions === "object", + `communitiesStore.editCommunity invalid communityEditOptions argument '${communityEditOptions}'`, + ); + assert( + typeof account?.plebbit?.createCommunity === "function", + `communitiesStore.editCommunity invalid account argument '${account}'`, + ); + + // if not added to store first, community.update() is never called + await getState().addCommunityToStore(communityAddress, account); + + // `communityAddress` is different from `communityEditOptions.address` when editing the community address + const community = await account.plebbit.createCommunity({ address: communityAddress }); + + // could fix some test issues + community.on("error", console.log); + + await community.edit(communityEditOptions); + + const updatedCommunity = utils.clone(community); + // edit db of both old and new community address to not break the UI + await communitiesDatabase.setItem(communityAddress, updatedCommunity); + await communitiesDatabase.setItem(community.address, updatedCommunity); + log("communitiesStore.editCommunity", { + communityAddress, + communityEditOptions, + community, + account, + }); + setState((state: any) => ({ + communities: { + ...state.communities, + // edit react state of both old and new community address to not break the UI + [communityAddress]: updatedCommunity, + [community.address]: updatedCommunity, + }, + })); + }, + + // internal action called by accountsActions.createCommunity + async createCommunity(createCommunityOptions: CreateCommunityOptions, account: Account) { + assert( + !createCommunityOptions || typeof createCommunityOptions === "object", + `communitiesStore.createCommunity invalid createCommunityOptions argument '${createCommunityOptions}'`, + ); + if (!createCommunityOptions?.signer) { + assert( + !createCommunityOptions?.address, + `communitiesStore.createCommunity createCommunityOptions.address '${createCommunityOptions?.address}' must be undefined to create a community`, + ); + } + assert( + typeof account?.plebbit?.createCommunity === "function", + `communitiesStore.createCommunity invalid account argument '${account}'`, + ); + + const community = await account.plebbit.createCommunity(createCommunityOptions); + + // could fix some test issues + community.on("error", console.log); + + // if not added to store first, community.update() is never called + await getState().addCommunityToStore(community.address, account); + + await communitiesDatabase.setItem(community.address, utils.clone(community)); + log("communitiesStore.createCommunity", { createCommunityOptions, community, account }); + setState((state: any) => ({ + communities: { ...state.communities, [community.address]: utils.clone(community) }, + })); + return community; + }, + + // internal action called by accountsActions.deleteCommunity + async deleteCommunity(communityAddress: string, account: Account) { + assert( + communityAddress && typeof communityAddress === "string", + `communitiesStore.deleteCommunity invalid communityAddress argument '${communityAddress}'`, + ); + assert( + typeof account?.plebbit?.createCommunity === "function", + `communitiesStore.deleteCommunity invalid account argument '${account}'`, + ); + + const community = await account.plebbit.createCommunity({ address: communityAddress }); + + // could fix some test issues + community.on("error", console.log); + + await community.delete(); + await communitiesDatabase.removeItem(communityAddress); + log("communitiesStore.deleteCommunity", { communityAddress, community, account }); + setState((state: any) => ({ + communities: { ...state.communities, [communityAddress]: undefined }, + })); + }, + }), +); + +// reset store in between tests +const originalState = communitiesStore.getState(); +// async function because some stores have async init +export const resetCommunitiesStore = async () => { + plebbitGetCommunityPending = {}; + // remove all event listeners + listeners.forEach((listener: any) => listener.removeAllListeners()); + // destroy all component subscriptions to the store + communitiesStore.destroy(); + // restore original state + communitiesStore.setState(originalState); +}; + +// reset database and store in between tests +export const resetCommunitiesDatabaseAndStore = async () => { + await localForageLru.createInstance({ name: "plebbitReactHooks-communities" }).clear(); + await resetCommunitiesStore(); +}; + +export default communitiesStore; diff --git a/src/stores/communities/index.ts b/src/stores/communities/index.ts new file mode 100644 index 00000000..91413d76 --- /dev/null +++ b/src/stores/communities/index.ts @@ -0,0 +1,3 @@ +import communitiesStore from "./communities-store"; +export * from "./communities-store"; +export default communitiesStore; diff --git a/src/stores/feeds/feed-sorter.test.ts b/src/stores/feeds/feed-sorter.test.ts index 32998220..058be0ca 100644 --- a/src/stores/feeds/feed-sorter.test.ts +++ b/src/stores/feeds/feed-sorter.test.ts @@ -5,47 +5,47 @@ const approximateDay = 100000; const day = (day: number) => timestamp + approximateDay * day; const feed: any[] = [ - { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, subplebbitAddress: "sub1" }, - { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, subplebbitAddress: "sub1" }, - { timestamp: day(0), upvoteCount: 10001, downvoteCount: 1000, subplebbitAddress: "sub1" }, - { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, subplebbitAddress: "sub1" }, - { timestamp: day(3), upvoteCount: 100, downvoteCount: 10, subplebbitAddress: "sub1" }, - { timestamp: day(2), upvoteCount: 100, downvoteCount: 10, subplebbitAddress: "sub1" }, - { timestamp: day(1), upvoteCount: 100, downvoteCount: 10, subplebbitAddress: "sub1" }, - { timestamp: day(0), upvoteCount: 100, downvoteCount: 100, subplebbitAddress: "sub1" }, - { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, subplebbitAddress: "sub2" }, - { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, subplebbitAddress: "sub2" }, - { timestamp: day(0), upvoteCount: 10000, downvoteCount: 1000, subplebbitAddress: "sub2" }, - { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, subplebbitAddress: "sub2" }, - { timestamp: day(3), upvoteCount: 100, downvoteCount: 10, subplebbitAddress: "sub2" }, - { timestamp: day(2), upvoteCount: 100, downvoteCount: 10, subplebbitAddress: "sub3" }, + { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, communityAddress: "sub1" }, + { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, communityAddress: "sub1" }, + { timestamp: day(0), upvoteCount: 10001, downvoteCount: 1000, communityAddress: "sub1" }, + { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, communityAddress: "sub1" }, + { timestamp: day(3), upvoteCount: 100, downvoteCount: 10, communityAddress: "sub1" }, + { timestamp: day(2), upvoteCount: 100, downvoteCount: 10, communityAddress: "sub1" }, + { timestamp: day(1), upvoteCount: 100, downvoteCount: 10, communityAddress: "sub1" }, + { timestamp: day(0), upvoteCount: 100, downvoteCount: 100, communityAddress: "sub1" }, + { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, communityAddress: "sub2" }, + { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, communityAddress: "sub2" }, + { timestamp: day(0), upvoteCount: 10000, downvoteCount: 1000, communityAddress: "sub2" }, + { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, communityAddress: "sub2" }, + { timestamp: day(3), upvoteCount: 100, downvoteCount: 10, communityAddress: "sub2" }, + { timestamp: day(2), upvoteCount: 100, downvoteCount: 10, communityAddress: "sub3" }, { timestamp: day(1), lastReplyTimestamp: day(4) + 1, upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub3", + communityAddress: "sub3", }, { timestamp: day(0), lastReplyTimestamp: day(4) + 2, upvoteCount: 100, downvoteCount: 100, - subplebbitAddress: "sub3", + communityAddress: "sub3", }, // pinned posts should be on top { timestamp: day(1), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", pinned: true, }, { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, - subplebbitAddress: "sub1", + communityAddress: "sub1", pinned: true, }, ]; @@ -61,7 +61,7 @@ describe("feedSorter", () => { timestamp: day(1), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", pinned: true, cid: "16", }, @@ -69,7 +69,7 @@ describe("feedSorter", () => { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, - subplebbitAddress: "sub1", + communityAddress: "sub1", pinned: true, cid: "17", }, @@ -77,63 +77,63 @@ describe("feedSorter", () => { timestamp: day(0), upvoteCount: 10001, downvoteCount: 1000, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "2", }, { timestamp: day(0), upvoteCount: 10000, downvoteCount: 1000, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "10", }, { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "1", }, { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "9", }, { timestamp: day(3), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "4", }, { timestamp: day(3), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "12", }, { timestamp: day(2), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "5", }, { timestamp: day(2), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub3", + communityAddress: "sub3", cid: "13", }, { timestamp: day(1), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "6", }, { @@ -141,42 +141,42 @@ describe("feedSorter", () => { lastReplyTimestamp: day(4) + 1, upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub3", + communityAddress: "sub3", cid: "14", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "0", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "3", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "8", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "11", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 100, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "7", }, { @@ -184,7 +184,7 @@ describe("feedSorter", () => { lastReplyTimestamp: day(4) + 2, upvoteCount: 100, downvoteCount: 100, - subplebbitAddress: "sub3", + communityAddress: "sub3", cid: "15", }, ]); @@ -197,7 +197,7 @@ describe("feedSorter", () => { timestamp: day(1), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", pinned: true, cid: "16", }, @@ -205,7 +205,7 @@ describe("feedSorter", () => { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, - subplebbitAddress: "sub1", + communityAddress: "sub1", pinned: true, cid: "17", }, @@ -213,7 +213,7 @@ describe("feedSorter", () => { timestamp: day(0), upvoteCount: 100, downvoteCount: 100, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "7", }, { @@ -221,56 +221,56 @@ describe("feedSorter", () => { lastReplyTimestamp: day(4) + 2, upvoteCount: 100, downvoteCount: 100, - subplebbitAddress: "sub3", + communityAddress: "sub3", cid: "15", }, { timestamp: day(0), upvoteCount: 10000, downvoteCount: 1000, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "10", }, { timestamp: day(0), upvoteCount: 10001, downvoteCount: 1000, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "2", }, { timestamp: day(3), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "4", }, { timestamp: day(3), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "12", }, { timestamp: day(2), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "5", }, { timestamp: day(2), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub3", + communityAddress: "sub3", cid: "13", }, { timestamp: day(1), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "6", }, { @@ -278,49 +278,49 @@ describe("feedSorter", () => { lastReplyTimestamp: day(4) + 1, upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub3", + communityAddress: "sub3", cid: "14", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "0", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "3", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "8", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "11", }, { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "1", }, { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "9", }, ]); @@ -333,7 +333,7 @@ describe("feedSorter", () => { timestamp: day(1), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", pinned: true, cid: "16", }, @@ -341,7 +341,7 @@ describe("feedSorter", () => { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, - subplebbitAddress: "sub1", + communityAddress: "sub1", pinned: true, cid: "17", }, @@ -349,35 +349,35 @@ describe("feedSorter", () => { timestamp: day(3), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "4", }, { timestamp: day(3), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "12", }, { timestamp: day(2), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "5", }, { timestamp: day(2), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub3", + communityAddress: "sub3", cid: "13", }, { timestamp: day(1), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "6", }, { @@ -385,70 +385,70 @@ describe("feedSorter", () => { lastReplyTimestamp: day(4) + 1, upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub3", + communityAddress: "sub3", cid: "14", }, { timestamp: day(0), upvoteCount: 10001, downvoteCount: 1000, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "2", }, { timestamp: day(0), upvoteCount: 10000, downvoteCount: 1000, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "10", }, { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "1", }, { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "9", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "0", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "3", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "8", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "11", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 100, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "7", }, { @@ -456,7 +456,7 @@ describe("feedSorter", () => { lastReplyTimestamp: day(4) + 2, upvoteCount: 100, downvoteCount: 100, - subplebbitAddress: "sub3", + communityAddress: "sub3", cid: "15", }, ]); @@ -469,7 +469,7 @@ describe("feedSorter", () => { timestamp: day(1), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", pinned: true, cid: "16", }, @@ -477,7 +477,7 @@ describe("feedSorter", () => { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, - subplebbitAddress: "sub1", + communityAddress: "sub1", pinned: true, cid: "17", }, @@ -485,35 +485,35 @@ describe("feedSorter", () => { timestamp: day(3), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "4", }, { timestamp: day(3), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "12", }, { timestamp: day(2), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "5", }, { timestamp: day(2), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub3", + communityAddress: "sub3", cid: "13", }, { timestamp: day(1), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "6", }, { @@ -521,70 +521,70 @@ describe("feedSorter", () => { lastReplyTimestamp: day(4) + 1, upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub3", + communityAddress: "sub3", cid: "14", }, { timestamp: day(0), upvoteCount: 10001, downvoteCount: 1000, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "2", }, { timestamp: day(0), upvoteCount: 10000, downvoteCount: 1000, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "10", }, { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "1", }, { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "9", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "0", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "3", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 100, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "7", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "8", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "11", }, { @@ -592,7 +592,7 @@ describe("feedSorter", () => { lastReplyTimestamp: day(4) + 2, upvoteCount: 100, downvoteCount: 100, - subplebbitAddress: "sub3", + communityAddress: "sub3", cid: "15", }, ]); @@ -605,7 +605,7 @@ describe("feedSorter", () => { timestamp: day(1), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", pinned: true, cid: "16", }, @@ -613,7 +613,7 @@ describe("feedSorter", () => { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, - subplebbitAddress: "sub1", + communityAddress: "sub1", pinned: true, cid: "17", }, @@ -621,35 +621,35 @@ describe("feedSorter", () => { timestamp: day(3), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "4", }, { timestamp: day(3), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "12", }, { timestamp: day(2), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "5", }, { timestamp: day(2), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub3", + communityAddress: "sub3", cid: "13", }, { timestamp: day(1), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "6", }, { @@ -657,70 +657,70 @@ describe("feedSorter", () => { lastReplyTimestamp: day(4) + 1, upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub3", + communityAddress: "sub3", cid: "14", }, { timestamp: day(0), upvoteCount: 10001, downvoteCount: 1000, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "2", }, { timestamp: day(0), upvoteCount: 10000, downvoteCount: 1000, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "10", }, { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "1", }, { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "9", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "0", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "3", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 100, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "7", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "8", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "11", }, { @@ -728,7 +728,7 @@ describe("feedSorter", () => { lastReplyTimestamp: day(4) + 2, upvoteCount: 100, downvoteCount: 100, - subplebbitAddress: "sub3", + communityAddress: "sub3", cid: "15", }, ]); @@ -741,7 +741,7 @@ describe("feedSorter", () => { timestamp: day(1), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", pinned: true, cid: "16", }, @@ -749,7 +749,7 @@ describe("feedSorter", () => { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, - subplebbitAddress: "sub1", + communityAddress: "sub1", pinned: true, cid: "17", }, @@ -758,7 +758,7 @@ describe("feedSorter", () => { lastReplyTimestamp: day(4) + 2, upvoteCount: 100, downvoteCount: 100, - subplebbitAddress: "sub3", + communityAddress: "sub3", cid: "15", }, { @@ -766,105 +766,105 @@ describe("feedSorter", () => { lastReplyTimestamp: day(4) + 1, upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub3", + communityAddress: "sub3", cid: "14", }, { timestamp: day(3), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "4", }, { timestamp: day(3), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "12", }, { timestamp: day(2), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "5", }, { timestamp: day(2), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub3", + communityAddress: "sub3", cid: "13", }, { timestamp: day(1), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "6", }, { timestamp: day(0), upvoteCount: 10001, downvoteCount: 1000, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "2", }, { timestamp: day(0), upvoteCount: 10000, downvoteCount: 1000, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "10", }, { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "1", }, { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "9", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "0", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "3", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 100, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "7", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "8", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "11", }, ]); @@ -877,7 +877,7 @@ describe("feedSorter", () => { timestamp: day(1), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", pinned: true, cid: "16", }, @@ -885,7 +885,7 @@ describe("feedSorter", () => { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, - subplebbitAddress: "sub1", + communityAddress: "sub1", pinned: true, cid: "17", }, @@ -893,63 +893,63 @@ describe("feedSorter", () => { timestamp: day(0), upvoteCount: 10001, downvoteCount: 1000, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "2", }, { timestamp: day(0), upvoteCount: 10000, downvoteCount: 1000, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "10", }, { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "1", }, { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "9", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "0", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "3", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 100, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "7", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "8", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "11", }, { @@ -957,14 +957,14 @@ describe("feedSorter", () => { lastReplyTimestamp: day(4) + 2, upvoteCount: 100, downvoteCount: 100, - subplebbitAddress: "sub3", + communityAddress: "sub3", cid: "15", }, { timestamp: day(1), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "6", }, { @@ -972,35 +972,35 @@ describe("feedSorter", () => { lastReplyTimestamp: day(4) + 1, upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub3", + communityAddress: "sub3", cid: "14", }, { timestamp: day(2), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "5", }, { timestamp: day(2), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub3", + communityAddress: "sub3", cid: "13", }, { timestamp: day(3), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "4", }, { timestamp: day(3), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "12", }, ]); @@ -1013,7 +1013,7 @@ describe("feedSorter", () => { timestamp: day(1), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", pinned: true, cid: "16", }, @@ -1021,7 +1021,7 @@ describe("feedSorter", () => { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, - subplebbitAddress: "sub1", + communityAddress: "sub1", pinned: true, cid: "17", }, @@ -1029,63 +1029,63 @@ describe("feedSorter", () => { timestamp: day(0), upvoteCount: 10001, downvoteCount: 1000, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "2", }, { timestamp: day(0), upvoteCount: 10000, downvoteCount: 1000, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "10", }, { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "1", }, { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "9", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "0", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "3", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 100, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "7", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "8", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "11", }, { @@ -1093,14 +1093,14 @@ describe("feedSorter", () => { lastReplyTimestamp: day(4) + 2, upvoteCount: 100, downvoteCount: 100, - subplebbitAddress: "sub3", + communityAddress: "sub3", cid: "15", }, { timestamp: day(1), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "6", }, { @@ -1108,35 +1108,35 @@ describe("feedSorter", () => { lastReplyTimestamp: day(4) + 1, upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub3", + communityAddress: "sub3", cid: "14", }, { timestamp: day(2), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "5", }, { timestamp: day(2), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub3", + communityAddress: "sub3", cid: "13", }, { timestamp: day(3), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "4", }, { timestamp: day(3), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "12", }, ]); @@ -1307,7 +1307,7 @@ describe("feedSorter", () => { timestamp: day(1), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", pinned: true, cid: "16", }, @@ -1315,7 +1315,7 @@ describe("feedSorter", () => { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, - subplebbitAddress: "sub1", + communityAddress: "sub1", pinned: true, cid: "17", }, @@ -1323,63 +1323,63 @@ describe("feedSorter", () => { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "1", }, { timestamp: day(0), upvoteCount: 1000, downvoteCount: 1, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "9", }, { timestamp: day(0), upvoteCount: 10001, downvoteCount: 1000, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "2", }, { timestamp: day(0), upvoteCount: 10000, downvoteCount: 1000, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "10", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "0", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "3", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "8", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "11", }, { timestamp: day(1), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "6", }, { @@ -1387,42 +1387,42 @@ describe("feedSorter", () => { lastReplyTimestamp: day(4) + 1, upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub3", + communityAddress: "sub3", cid: "14", }, { timestamp: day(2), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "5", }, { timestamp: day(2), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub3", + communityAddress: "sub3", cid: "13", }, { timestamp: day(3), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "4", }, { timestamp: day(3), upvoteCount: 100, downvoteCount: 10, - subplebbitAddress: "sub2", + communityAddress: "sub2", cid: "12", }, { timestamp: day(0), upvoteCount: 100, downvoteCount: 100, - subplebbitAddress: "sub1", + communityAddress: "sub1", cid: "7", }, { @@ -1430,7 +1430,7 @@ describe("feedSorter", () => { lastReplyTimestamp: day(4) + 2, upvoteCount: 100, downvoteCount: 100, - subplebbitAddress: "sub3", + communityAddress: "sub3", cid: "15", }, ]); diff --git a/src/stores/feeds/feeds-store.test.ts b/src/stores/feeds/feeds-store.test.ts index dd2ff90f..084212ed 100644 --- a/src/stores/feeds/feeds-store.test.ts +++ b/src/stores/feeds/feeds-store.test.ts @@ -1,32 +1,32 @@ import { act, waitFor as tlWaitFor } from "@testing-library/react"; import testUtils, { renderHook } from "../../lib/test-utils"; import useFeedsStore, { defaultPostsPerPage as postsPerPage } from "./feeds-store"; -import { SubplebbitPage } from "../../types"; -import subplebbitsStore from "../subplebbits"; -import subplebbitsPagesStore from "../subplebbits-pages"; +import { CommunityPage } from "../../types"; +import communitiesStore from "../communities"; +import communitiesPagesStore from "../communities-pages"; import EventEmitter from "events"; import accountsStore from "../accounts"; import { setPlebbitJs } from "../.."; import PlebbitJsMock from "../../lib/plebbit-js/plebbit-js-mock"; -const subplebbitGetPageCommentCount = 100; +const communityGetPageCommentCount = 100; const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); class MockPages { - subplebbitAddress: string; + communityAddress: string; pageCids: { [pageCid: string]: string }; - constructor({ subplebbitAddress }: any) { - this.subplebbitAddress = subplebbitAddress; + constructor({ communityAddress }: any) { + this.communityAddress = communityAddress; this.pageCids = { - new: `${subplebbitAddress} new page cid`, + new: `${communityAddress} new page cid`, }; } async getPage(options: { cid: string }) { const cid = options?.cid; await sleep(200); - const page: SubplebbitPage = { + const page: CommunityPage = { nextCid: cid + " - next page cid", comments: this.getPageMockComments(cid), }; @@ -38,24 +38,24 @@ class MockPages { getPageMockComments(pageCid: string) { let index = 0; const comments: any[] = []; - while (index++ < subplebbitGetPageCommentCount) { + while (index++ < communityGetPageCommentCount) { comments.push({ timestamp: index, cid: pageCid + " comment cid " + index, - subplebbitAddress: this.subplebbitAddress, + communityAddress: this.communityAddress, }); } return comments; } } -class MockSubplebbit extends EventEmitter { +class MockCommunity extends EventEmitter { address: string; posts: MockPages; constructor({ address }: any) { super(); this.address = address; - this.posts = new MockPages({ subplebbitAddress: address }); + this.posts = new MockPages({ communityAddress: address }); } async update() {} } @@ -63,10 +63,10 @@ class MockSubplebbit extends EventEmitter { const mockAccount: any = { id: "mock account id", plebbit: { - createSubplebbit: async ({ address }: any) => new MockSubplebbit({ address }), - getSubplebbit: async (options: { address: string }) => - new MockSubplebbit({ address: options?.address }), - subplebbits: [], + createCommunity: async ({ address }: any) => new MockCommunity({ address }), + getCommunity: async (options: { address: string }) => + new MockCommunity({ address: options?.address }), + communities: [], async validateComment(comment: any) {}, }, blockedAddresses: {}, @@ -112,7 +112,7 @@ describe("feeds store", () => { test("initial store", async () => { expect(rendered.result.current.feedsOptions).toEqual({}); expect(rendered.result.current.bufferedFeeds).toEqual({}); - expect(rendered.result.current.bufferedFeedsSubplebbitsPostCounts).toEqual({}); + expect(rendered.result.current.bufferedFeedsCommunitiesPostCounts).toEqual({}); expect(rendered.result.current.loadedFeeds).toEqual({}); expect(rendered.result.current.updatedFeeds).toEqual({}); expect(typeof rendered.result.current.addFeedToStore).toBe("function"); @@ -121,45 +121,45 @@ describe("feeds store", () => { }); test("add feed, increment page, block address", async () => { - const subplebbitAddresses = ["subplebbit address 1"]; + const communityAddresses = ["community address 1"]; const sortType = "new"; - const feedName = JSON.stringify([mockAccount?.id, sortType, subplebbitAddresses]); + const feedName = JSON.stringify([mockAccount?.id, sortType, communityAddresses]); act(() => { - rendered.result.current.addFeedToStore(feedName, subplebbitAddresses, sortType, mockAccount); + rendered.result.current.addFeedToStore(feedName, communityAddresses, sortType, mockAccount); }); // wait for feed to be added await waitFor(() => rendered.result.current.feedsOptions[feedName]); expect(rendered.result.current.feedsOptions[feedName].pageNumber).toBe(1); expect(rendered.result.current.feedsOptions[feedName].sortType).toBe(sortType); - expect(rendered.result.current.feedsOptions[feedName].subplebbitAddresses).toEqual( - subplebbitAddresses, + expect(rendered.result.current.feedsOptions[feedName].communityAddresses).toEqual( + communityAddresses, ); // wait for feed to load await waitFor(() => rendered.result.current.loadedFeeds[feedName].length > 0); - // subplebbit was added to subplebbits store - expect(subplebbitsStore.getState().subplebbits[subplebbitAddresses[0]]).not.toBe(undefined); + // community was added to communities store + expect(communitiesStore.getState().communities[communityAddresses[0]]).not.toBe(undefined); // feeds become defined expect(rendered.result.current.bufferedFeeds[feedName]).not.toBe(undefined); expect(rendered.result.current.loadedFeeds[feedName]).not.toBe(undefined); expect(rendered.result.current.updatedFeeds[feedName].length).toBe( rendered.result.current.loadedFeeds[feedName].length, ); - expect(rendered.result.current.bufferedFeedsSubplebbitsPostCounts[feedName]).not.toBe( + expect(rendered.result.current.bufferedFeedsCommunitiesPostCounts[feedName]).not.toBe( undefined, ); expect(rendered.result.current.feedsHaveMore[feedName]).toBe(true); - // subplebbits pages fetch 1 page - expect(Object.keys(subplebbitsPagesStore.getState().subplebbitsPages).length).toBe(1); + // communities pages fetch 1 page + expect(Object.keys(communitiesPagesStore.getState().communitiesPages).length).toBe(1); // buffered feed has 1 page expect(rendered.result.current.bufferedFeeds[feedName].length).toBe( - subplebbitGetPageCommentCount - postsPerPage, + communityGetPageCommentCount - postsPerPage, ); expect( - rendered.result.current.bufferedFeedsSubplebbitsPostCounts[feedName][subplebbitAddresses[0]], - ).toBe(subplebbitGetPageCommentCount - postsPerPage); + rendered.result.current.bufferedFeedsCommunitiesPostCounts[feedName][communityAddresses[0]], + ).toBe(communityGetPageCommentCount - postsPerPage); expect(rendered.result.current.feedsHaveMore[feedName]).toBe(true); // loaded feed has 1 page expect(rendered.result.current.loadedFeeds[feedName].length).toBe(postsPerPage); @@ -178,38 +178,38 @@ describe("feeds store", () => { expect(rendered.result.current.feedsOptions[feedName].pageNumber).toBe(2); // feed options are unchanged expect(rendered.result.current.feedsOptions[feedName].sortType).toBe(sortType); - expect(rendered.result.current.feedsOptions[feedName].subplebbitAddresses).toEqual( - subplebbitAddresses, + expect(rendered.result.current.feedsOptions[feedName].communityAddresses).toEqual( + communityAddresses, ); // loaded feed has correct post counts expect(rendered.result.current.loadedFeeds[feedName].length).toBe(postsPerPage * 2); // buffered feed has 1 page less - const bufferedFeedPostCount = subplebbitGetPageCommentCount - postsPerPage * 2; + const bufferedFeedPostCount = communityGetPageCommentCount - postsPerPage * 2; expect(rendered.result.current.bufferedFeeds[feedName].length).toBe(bufferedFeedPostCount); expect( - rendered.result.current.bufferedFeedsSubplebbitsPostCounts[feedName][subplebbitAddresses[0]], + rendered.result.current.bufferedFeedsCommunitiesPostCounts[feedName][communityAddresses[0]], ).toBe(bufferedFeedPostCount); expect(rendered.result.current.feedsHaveMore[feedName]).toBe(true); - // bufferedFeedsSubplebbitsPostCounts now too low (50), wait for buffered feeds to fetch next page + // bufferedFeedsCommunitiesPostCounts now too low (50), wait for buffered feeds to fetch next page await waitFor( () => rendered.result.current.bufferedFeeds[feedName].length > bufferedFeedPostCount, ); expect(rendered.result.current.bufferedFeeds[feedName].length).toBe( - bufferedFeedPostCount + subplebbitGetPageCommentCount, + bufferedFeedPostCount + communityGetPageCommentCount, ); expect( - rendered.result.current.bufferedFeedsSubplebbitsPostCounts[feedName][subplebbitAddresses[0]], - ).toBe(bufferedFeedPostCount + subplebbitGetPageCommentCount); + rendered.result.current.bufferedFeedsCommunitiesPostCounts[feedName][communityAddresses[0]], + ).toBe(bufferedFeedPostCount + communityGetPageCommentCount); expect(rendered.result.current.feedsHaveMore[feedName]).toBe(true); - // save subplebbits pages count to make sure they don't change - const subplebbitsPagesCount = Object.keys( - subplebbitsPagesStore.getState().subplebbitsPages, + // save communities pages count to make sure they don't change + const communitiesPagesCount = Object.keys( + communitiesPagesStore.getState().communitiesPages, ).length; - // account blocks the subplebbit address - const newMockAccount = { ...mockAccount, blockedAddresses: { [subplebbitAddresses[0]]: true } }; + // account blocks the community address + const newMockAccount = { ...mockAccount, blockedAddresses: { [communityAddresses[0]]: true } }; // @ts-ignore accountsStore.getState = () => ({ accounts: { [mockAccount.id]: newMockAccount }, @@ -223,36 +223,36 @@ describe("feeds store", () => { await waitFor(() => rendered.result.current.bufferedFeeds[feedName].length === 0); expect(rendered.result.current.bufferedFeeds[feedName].length).toBe(0); expect( - rendered.result.current.bufferedFeedsSubplebbitsPostCounts[feedName][subplebbitAddresses[0]], + rendered.result.current.bufferedFeedsCommunitiesPostCounts[feedName][communityAddresses[0]], ).toBe(0); expect(rendered.result.current.feedsHaveMore[feedName]).toBe(false); // loaded feed is unaffected expect(rendered.result.current.loadedFeeds[feedName].length).toBe(postsPerPage * 2); - // make sure no more subplebbits pages get added for the blocked address + // make sure no more communities pages get added for the blocked address await expect( tlWaitFor(() => { if ( !( - Object.keys(subplebbitsPagesStore.getState().subplebbitsPages).length > - subplebbitsPagesCount + Object.keys(communitiesPagesStore.getState().communitiesPages).length > + communitiesPagesCount ) ) throw new Error("condition not met"); }), ).rejects.toThrow(); - expect(Object.keys(subplebbitsPagesStore.getState().subplebbitsPages).length).toBe( - subplebbitsPagesCount, + expect(Object.keys(communitiesPagesStore.getState().communitiesPages).length).toBe( + communitiesPagesCount, ); }); test("addFeedToStore accepts isBufferedFeed null (branch 110)", async () => { const feedName = "null-buffered-feed"; - const subplebbitAddresses = ["subplebbit address 1"]; + const communityAddresses = ["community address 1"]; act(() => { rendered.result.current.addFeedToStore( feedName, - subplebbitAddresses, + communityAddresses, "new", mockAccount, null as any, @@ -264,28 +264,28 @@ describe("feeds store", () => { test("duplicate feed add returns early without overwriting", async () => { const feedName = "duplicate-feed"; - const subplebbitAddresses = ["subplebbit address 1"]; + const communityAddresses = ["community address 1"]; const sortType = "new"; act(() => { - rendered.result.current.addFeedToStore(feedName, subplebbitAddresses, sortType, mockAccount); + rendered.result.current.addFeedToStore(feedName, communityAddresses, sortType, mockAccount); }); await waitFor(() => rendered.result.current.feedsOptions[feedName]); const optsBefore = rendered.result.current.feedsOptions[feedName]; act(() => { - rendered.result.current.addFeedToStore(feedName, subplebbitAddresses, sortType, mockAccount); + rendered.result.current.addFeedToStore(feedName, communityAddresses, sortType, mockAccount); }); expect(rendered.result.current.feedsOptions[feedName]).toBe(optsBefore); }); test("incrementFeedPageNumber before loaded throws", async () => { const feedName = "early-increment-feed"; - const subplebbitAddresses = ["subplebbit address 1"]; + const communityAddresses = ["community address 1"]; const sortType = "new"; act(() => { - rendered.result.current.addFeedToStore(feedName, subplebbitAddresses, sortType, mockAccount); + rendered.result.current.addFeedToStore(feedName, communityAddresses, sortType, mockAccount); }); await waitFor(() => rendered.result.current.feedsOptions[feedName]); @@ -299,16 +299,16 @@ describe("feeds store", () => { test("initializeFeedsStore early return on second addFeedToStore", async () => { const feed1 = "init-feed-1"; const feed2 = "init-feed-2"; - const subplebbitAddresses = ["subplebbit address 1"]; + const communityAddresses = ["community address 1"]; const sortType = "new"; act(() => { - rendered.result.current.addFeedToStore(feed1, subplebbitAddresses, sortType, mockAccount); + rendered.result.current.addFeedToStore(feed1, communityAddresses, sortType, mockAccount); }); await waitFor(() => rendered.result.current.feedsOptions[feed1]); act(() => { - rendered.result.current.addFeedToStore(feed2, subplebbitAddresses, sortType, mockAccount); + rendered.result.current.addFeedToStore(feed2, communityAddresses, sortType, mockAccount); }); await waitFor(() => rendered.result.current.feedsOptions[feed2]); expect(rendered.result.current.feedsOptions[feed1]).toBeDefined(); @@ -316,12 +316,12 @@ describe("feeds store", () => { }); test("updateFeedsOnAccountsBlockedAddressesChange returns when blocked address not in feeds", async () => { - const subplebbitAddresses = ["subplebbit address 1"]; - const feedName = JSON.stringify([mockAccount?.id, "new", subplebbitAddresses]); + const communityAddresses = ["community address 1"]; + const feedName = JSON.stringify([mockAccount?.id, "new", communityAddresses]); const otherAddress = "other-sub-not-in-feed"; act(() => { - rendered.result.current.addFeedToStore(feedName, subplebbitAddresses, "new", mockAccount); + rendered.result.current.addFeedToStore(feedName, communityAddresses, "new", mockAccount); }); await waitFor(() => rendered.result.current.loadedFeeds[feedName]?.length > 0); @@ -340,15 +340,15 @@ describe("feeds store", () => { await new Promise((r) => setTimeout(r, 150)); }); - test("addSubplebbitsPagesOnLowBufferedFeeds skips blocked subplebbit", async () => { - const sub1 = "subplebbit address 1"; - const sub2 = "subplebbit address 2"; - const subplebbitAddresses = [sub1, sub2]; + test("addCommunitiesPagesOnLowBufferedFeeds skips blocked community", async () => { + const sub1 = "community address 1"; + const sub2 = "community address 2"; + const communityAddresses = [sub1, sub2]; const sortType = "new"; - const feedName = JSON.stringify([mockAccount?.id, sortType, subplebbitAddresses]); + const feedName = JSON.stringify([mockAccount?.id, sortType, communityAddresses]); act(() => { - rendered.result.current.addFeedToStore(feedName, subplebbitAddresses, sortType, mockAccount); + rendered.result.current.addFeedToStore(feedName, communityAddresses, sortType, mockAccount); }); await waitFor(() => rendered.result.current.loadedFeeds[feedName]?.length > 0); @@ -367,46 +367,46 @@ describe("feeds store", () => { await new Promise((r) => setTimeout(r, 200)); }); - test("addSubplebbitsPagesOnLowBufferedFeeds skips cache-expired subplebbit", async () => { - const subplebbitAddresses = ["subplebbit address 1"]; - const feedName = JSON.stringify([mockAccount?.id, "new", subplebbitAddresses]); + test("addCommunitiesPagesOnLowBufferedFeeds skips cache-expired community", async () => { + const communityAddresses = ["community address 1"]; + const feedName = JSON.stringify([mockAccount?.id, "new", communityAddresses]); act(() => { - rendered.result.current.addFeedToStore(feedName, subplebbitAddresses, "new", mockAccount); + rendered.result.current.addFeedToStore(feedName, communityAddresses, "new", mockAccount); }); await waitFor(() => rendered.result.current.loadedFeeds[feedName]?.length > 0); - const sub = subplebbitsStore.getState().subplebbits[subplebbitAddresses[0]]; - subplebbitsStore.setState((state: any) => ({ - subplebbits: { - ...state.subplebbits, - [subplebbitAddresses[0]]: { ...sub, fetchedAt: 0 }, + const sub = communitiesStore.getState().communities[communityAddresses[0]]; + communitiesStore.setState((state: any) => ({ + communities: { + ...state.communities, + [communityAddresses[0]]: { ...sub, fetchedAt: 0 }, }, })); act(() => rendered.result.current.incrementFeedPageNumber(feedName)); await new Promise((r) => setTimeout(r, 300)); - subplebbitsStore.setState((state: any) => ({ - subplebbits: { - ...state.subplebbits, - [subplebbitAddresses[0]]: sub, + communitiesStore.setState((state: any) => ({ + communities: { + ...state.communities, + [communityAddresses[0]]: sub, }, })); }); test("updateFeedsOnAccountsBlockedAddressesChange calls updateFeeds when blocked address is in feed", async () => { - const subplebbitAddresses = ["subplebbit address 1"]; - const feedName = JSON.stringify([mockAccount?.id, "new", subplebbitAddresses]); + const communityAddresses = ["community address 1"]; + const feedName = JSON.stringify([mockAccount?.id, "new", communityAddresses]); act(() => { - rendered.result.current.addFeedToStore(feedName, subplebbitAddresses, "new", mockAccount); + rendered.result.current.addFeedToStore(feedName, communityAddresses, "new", mockAccount); }); await waitFor(() => rendered.result.current.loadedFeeds[feedName]?.length > 0); const blockedAccount = { ...mockAccount, - blockedAddresses: { [subplebbitAddresses[0]]: true }, + blockedAddresses: { [communityAddresses[0]]: true }, }; (accountsStore as any).getState = () => ({ accounts: { [mockAccount.id]: blockedAccount }, @@ -420,12 +420,12 @@ describe("feeds store", () => { expect(rendered.result.current.bufferedFeeds[feedName].length).toBe(0); }); - test("addSubplebbitsToSubplebbitsStore catch when addSubplebbitToStore rejects", async () => { - const rejectAddress = "subplebbit-reject-address"; - const addOrig = subplebbitsStore.getState().addSubplebbitToStore; - subplebbitsStore.setState((s: any) => ({ + test("addCommunitiesToCommunitiesStore catch when addCommunityToStore rejects", async () => { + const rejectAddress = "community-reject-address"; + const addOrig = communitiesStore.getState().addCommunityToStore; + communitiesStore.setState((s: any) => ({ ...s, - addSubplebbitToStore: () => Promise.reject(new Error("add failed")), + addCommunityToStore: () => Promise.reject(new Error("add failed")), })); act(() => { @@ -442,33 +442,33 @@ describe("feeds store", () => { JSON.stringify([mockAccount?.id, "new", [rejectAddress]]) ], ).toBeDefined(); - subplebbitsStore.setState((s: any) => ({ ...s, addSubplebbitToStore: addOrig })); + communitiesStore.setState((s: any) => ({ ...s, addCommunityToStore: addOrig })); }); - test("updateFeedsOnFeedsSubplebbitsChange returns when subplebbit added is not in any feed", async () => { - const subplebbitAddresses = ["subplebbit address 1"]; - const feedName = JSON.stringify([mockAccount?.id, "new", subplebbitAddresses]); + test("updateFeedsOnFeedsCommunitiesChange returns when community added is not in any feed", async () => { + const communityAddresses = ["community address 1"]; + const feedName = JSON.stringify([mockAccount?.id, "new", communityAddresses]); act(() => { - rendered.result.current.addFeedToStore(feedName, subplebbitAddresses, "new", mockAccount); + rendered.result.current.addFeedToStore(feedName, communityAddresses, "new", mockAccount); }); await waitFor(() => rendered.result.current.loadedFeeds[feedName]?.length > 0); - const otherAddress = "subplebbit-not-in-feed"; + const otherAddress = "community-not-in-feed"; await act(async () => { - await subplebbitsStore.getState().addSubplebbitToStore(otherAddress, mockAccount); + await communitiesStore.getState().addCommunityToStore(otherAddress, mockAccount); }); await new Promise((r) => setTimeout(r, 150)); }); test("resetFeed resets page to 1 and clears loaded/updated", async () => { - const subplebbitAddresses = ["subplebbit address reset-feed"]; + const communityAddresses = ["community address reset-feed"]; const sortType = "new"; - const feedName = JSON.stringify([mockAccount?.id, sortType, subplebbitAddresses]); - const getSubplebbitSpy = vi.spyOn(mockAccount.plebbit, "getSubplebbit"); + const feedName = JSON.stringify([mockAccount?.id, sortType, communityAddresses]); + const getCommunitySpy = vi.spyOn(mockAccount.plebbit, "getCommunity"); act(() => { - rendered.result.current.addFeedToStore(feedName, subplebbitAddresses, sortType, mockAccount); + rendered.result.current.addFeedToStore(feedName, communityAddresses, sortType, mockAccount); }); await waitFor(() => rendered.result.current.loadedFeeds[feedName]?.length >= postsPerPage); @@ -481,16 +481,16 @@ describe("feeds store", () => { expect(rendered.result.current.feedsOptions[feedName].pageNumber).toBe(1); expect(rendered.result.current.loadedFeeds[feedName]).toEqual([]); expect(rendered.result.current.updatedFeeds[feedName]).toEqual([]); - expect(getSubplebbitSpy).toHaveBeenCalledWith({ address: subplebbitAddresses[0] }); - getSubplebbitSpy.mockRestore(); + expect(getCommunitySpy).toHaveBeenCalledWith({ address: communityAddresses[0] }); + getCommunitySpy.mockRestore(); }); test("updateFeedsOnAccountsBlockedCidsChange calls updateFeeds when blocked cid is in feed", async () => { - const subplebbitAddresses = ["subplebbit address 1"]; - const feedName = JSON.stringify([mockAccount?.id, "new", subplebbitAddresses]); + const communityAddresses = ["community address 1"]; + const feedName = JSON.stringify([mockAccount?.id, "new", communityAddresses]); act(() => { - rendered.result.current.addFeedToStore(feedName, subplebbitAddresses, "new", mockAccount); + rendered.result.current.addFeedToStore(feedName, communityAddresses, "new", mockAccount); }); await waitFor(() => rendered.result.current.loadedFeeds[feedName]?.length > 0); @@ -511,12 +511,12 @@ describe("feeds store", () => { }); test("updateFeedsOnAccountsBlockedAddressesChange returns when blocked address is not in feed", async () => { - const subplebbitAddresses = ["subplebbit address 1"]; - const feedName = JSON.stringify([mockAccount?.id, "new", subplebbitAddresses]); + const communityAddresses = ["community address 1"]; + const feedName = JSON.stringify([mockAccount?.id, "new", communityAddresses]); const originalBlockedAddresses = mockAccount.blockedAddresses; act(() => { - rendered.result.current.addFeedToStore(feedName, subplebbitAddresses, "new", mockAccount); + rendered.result.current.addFeedToStore(feedName, communityAddresses, "new", mockAccount); }); await waitFor(() => rendered.result.current.loadedFeeds[feedName]?.length > 0); @@ -531,21 +531,21 @@ describe("feeds store", () => { mockAccount.blockedAddresses = originalBlockedAddresses; }); - test("resetFeed logs and continues when refreshSubplebbit rejects", async () => { - const subplebbitAddresses = ["subplebbit address refresh-fail"]; + test("resetFeed logs and continues when refreshCommunity rejects", async () => { + const communityAddresses = ["community address refresh-fail"]; const sortType = "new"; - const feedName = JSON.stringify([mockAccount?.id, sortType, subplebbitAddresses]); - const refreshSubplebbit = subplebbitsStore.getState().refreshSubplebbit; + const feedName = JSON.stringify([mockAccount?.id, sortType, communityAddresses]); + const refreshCommunity = communitiesStore.getState().refreshCommunity; act(() => { - rendered.result.current.addFeedToStore(feedName, subplebbitAddresses, sortType, mockAccount); + rendered.result.current.addFeedToStore(feedName, communityAddresses, sortType, mockAccount); }); await waitFor(() => rendered.result.current.loadedFeeds[feedName]?.length >= postsPerPage); const refreshSpy = vi.fn().mockRejectedValue(new Error("refresh failed")); - subplebbitsStore.setState((state: any) => ({ + communitiesStore.setState((state: any) => ({ ...state, - refreshSubplebbit: refreshSpy, + refreshCommunity: refreshSpy, })); await act(async () => { @@ -553,25 +553,25 @@ describe("feeds store", () => { }); expect(refreshSpy).toHaveBeenCalledWith( - subplebbitAddresses[0], + communityAddresses[0], expect.objectContaining({ id: mockAccount.id }), ); - subplebbitsStore.setState((state: any) => ({ + communitiesStore.setState((state: any) => ({ ...state, - refreshSubplebbit, + refreshCommunity, })); }); test("addFeedToStore with isBufferedFeed true sets pageNumber 0", async () => { - const subplebbitAddresses = ["subplebbit address buffered"]; + const communityAddresses = ["community address buffered"]; const sortType = "new"; - const feedName = JSON.stringify([mockAccount?.id, sortType, subplebbitAddresses]); + const feedName = JSON.stringify([mockAccount?.id, sortType, communityAddresses]); act(() => { rendered.result.current.addFeedToStore( feedName, - subplebbitAddresses, + communityAddresses, sortType, mockAccount, true, // isBufferedFeed @@ -581,21 +581,21 @@ describe("feeds store", () => { expect(rendered.result.current.feedsOptions[feedName].pageNumber).toBe(0); }); - test("addNextSubplebbitPageToStore catch logs error when fetch throws", async () => { - const subplebbitAddresses = ["subplebbit address addNext-throws"]; + test("addNextCommunityPageToStore catch logs error when fetch throws", async () => { + const communityAddresses = ["community address addNext-throws"]; const sortType = "new"; - const feedName = JSON.stringify([mockAccount?.id, sortType, subplebbitAddresses]); - const addNextOriginal = subplebbitsPagesStore.getState().addNextSubplebbitPageToStore; + const feedName = JSON.stringify([mockAccount?.id, sortType, communityAddresses]); + const addNextOriginal = communitiesPagesStore.getState().addNextCommunityPageToStore; act(() => { - rendered.result.current.addFeedToStore(feedName, subplebbitAddresses, sortType, mockAccount); + rendered.result.current.addFeedToStore(feedName, communityAddresses, sortType, mockAccount); }); const longWaitFor = testUtils.createWaitFor(rendered, { timeout: 10000 }); await longWaitFor(() => rendered.result.current.loadedFeeds[feedName]?.length >= postsPerPage); - subplebbitsPagesStore.setState((state: any) => ({ + communitiesPagesStore.setState((state: any) => ({ ...state, - addNextSubplebbitPageToStore: async () => { + addNextCommunityPageToStore: async () => { throw new Error("fetch failed"); }, })); @@ -603,15 +603,15 @@ describe("feeds store", () => { act(() => rendered.result.current.incrementFeedPageNumber(feedName)); await waitFor(() => { const count = - rendered.result.current.bufferedFeedsSubplebbitsPostCounts[feedName]?.[ - subplebbitAddresses[0] + rendered.result.current.bufferedFeedsCommunitiesPostCounts[feedName]?.[ + communityAddresses[0] ]; return count !== undefined && count <= 50; }); - subplebbitsPagesStore.setState((state: any) => ({ + communitiesPagesStore.setState((state: any) => ({ ...state, - addNextSubplebbitPageToStore: addNextOriginal, + addNextCommunityPageToStore: addNextOriginal, })); }); }); diff --git a/src/stores/feeds/feeds-store.ts b/src/stores/feeds/feeds-store.ts index 00f47206..56a47a8e 100644 --- a/src/stores/feeds/feeds-store.ts +++ b/src/stores/feeds/feeds-store.ts @@ -4,28 +4,28 @@ const log = Logger("bitsocial-react-hooks:feeds:stores"); import { Feed, Feeds, - Subplebbit, - Subplebbits, + Community, + Communities, Account, FeedsOptions, - SubplebbitPage, - FeedsSubplebbitsPostCounts, + CommunityPage, + FeedsCommunitiesPostCounts, CommentsFilter, FeedOptionsAccountComments, Comment, } from "../../types"; import createStore from "zustand"; import localForageLru from "../../lib/localforage-lru"; -import { subplebbitPostsCacheExpired } from "../../lib/utils"; +import { communityPostsCacheExpired } from "../../lib/utils"; import accountsStore from "../accounts"; -import subplebbitsStore from "../subplebbits"; -import subplebbitsPagesStore from "../subplebbits-pages"; +import communitiesStore from "../communities"; +import communitiesPagesStore from "../communities-pages"; import { - getFeedsSubplebbitsFirstPageCids, + getFeedsCommunitiesFirstPageCids, getLoadedFeeds, getUpdatedFeeds, getBufferedFeedsWithoutLoadedFeeds, - getFeedsSubplebbitsPostCounts, + getFeedsCommunitiesPostCounts, getFeedsHaveMore, getAccountsBlockedAddresses, feedsHaveChangedBlockedAddresses, @@ -33,12 +33,12 @@ import { getAccountsBlockedCids, feedsHaveChangedBlockedCids, accountsBlockedCidsChanged, - feedsSubplebbitsChanged, - getFeedsSubplebbits, - getFeedsSubplebbitsLoadedCount, - getFeedsSubplebbitsPostsPagesFirstUpdatedAts, + feedsCommunitiesChanged, + getFeedsCommunities, + getFeedsCommunitiesLoadedCount, + getFeedsCommunitiesPostsPagesFirstUpdatedAts, getFilteredSortedFeeds, - getFeedsSubplebbitAddressesWithNewerPosts, + getFeedsCommunityAddressesWithNewerPosts, } from "./utils"; // reddit loads approximately 25 posts per page @@ -46,16 +46,16 @@ import { export const defaultPostsPerPage = 25; // keep large buffer because fetching cids is slow -const subplebbitPostsLeftBeforeNextPage = 50; +const communityPostsLeftBeforeNextPage = 50; type FeedsState = { feedsOptions: FeedsOptions; bufferedFeeds: Feeds; loadedFeeds: Feeds; updatedFeeds: Feeds; - bufferedFeedsSubplebbitsPostCounts: FeedsSubplebbitsPostCounts; + bufferedFeedsCommunitiesPostCounts: FeedsCommunitiesPostCounts; feedsHaveMore: { [feedName: string]: boolean }; - feedsSubplebbitAddressesWithNewerPosts: { [feedName: string]: string[] }; + feedsCommunityAddressesWithNewerPosts: { [feedName: string]: string[] }; addFeedToStore: Function; incrementFeedPageNumber: Function; resetFeed: Function; @@ -71,13 +71,13 @@ const feedsStore = createStore((setState: Function, getState: Functi bufferedFeeds: {}, loadedFeeds: {}, updatedFeeds: {}, - bufferedFeedsSubplebbitsPostCounts: {}, + bufferedFeedsCommunitiesPostCounts: {}, feedsHaveMore: {}, - feedsSubplebbitAddressesWithNewerPosts: {}, + feedsCommunityAddressesWithNewerPosts: {}, async addFeedToStore( feedName: string, - subplebbitAddresses: string[], + communityAddresses: string[], sortType: string, account: Account, isBufferedFeed?: boolean, @@ -95,15 +95,15 @@ const feedsStore = createStore((setState: Function, getState: Functi `feedsStore.addFeedToStore feedName '${feedName}' invalid`, ); assert( - Array.isArray(subplebbitAddresses), - `addFeedToStore.addFeedToStore subplebbitAddresses '${subplebbitAddresses}' invalid`, + Array.isArray(communityAddresses), + `addFeedToStore.addFeedToStore communityAddresses '${communityAddresses}' invalid`, ); assert( sortType && typeof sortType === "string", `addFeedToStore.addFeedToStore sortType '${sortType}' invalid`, ); assert( - typeof account?.plebbit?.getSubplebbit === "function", + typeof account?.plebbit?.getCommunity === "function", `addFeedToStore.addFeedToStore account '${account}' invalid`, ); assert( @@ -146,7 +146,7 @@ const feedsStore = createStore((setState: Function, getState: Functi } // to add a buffered feed, add a feed with pageNumber 0 const feedOptions = { - subplebbitAddresses, + communityAddresses, sortType, accountId: account.id, pageNumber: isBufferedFeed === true ? 0 : 1, @@ -162,10 +162,10 @@ const feedsStore = createStore((setState: Function, getState: Functi feedsOptions: { ...feedsOptions, [feedName]: feedOptions }, })); - addSubplebbitsToSubplebbitsStore(subplebbitAddresses, account); + addCommunitiesToCommunitiesStore(communityAddresses, account); - // update feeds right away to use the already loaded subplebbits and pages - // if no new subplebbits are added by the feed, like for a sort type change, + // update feeds right away to use the already loaded communities and pages + // if no new communities are added by the feed, like for a sort type change, // a feed update will never be triggered, so must be triggered it manually updateFeeds(); }, @@ -208,7 +208,7 @@ const feedsStore = createStore((setState: Function, getState: Functi ); log("feedsActions.resetFeed", { feedName }); - const { modQueue, sortType, subplebbitAddresses, accountId } = feedsOptions[feedName]; + const { modQueue, sortType, communityAddresses, accountId } = feedsOptions[feedName]; const account = accountsStore.getState().accounts[accountId]; assert( account, @@ -228,29 +228,27 @@ const feedsStore = createStore((setState: Function, getState: Functi }); if (modQueue?.[0]) { - const { subplebbits } = subplebbitsStore.getState(); - const { invalidateSubplebbitPages } = subplebbitsPagesStore.getState(); - const loadedSubplebbits = subplebbitAddresses - .map((subplebbitAddress: string) => subplebbits[subplebbitAddress]) - .filter((subplebbit: Subplebbit | undefined): subplebbit is Subplebbit => - Boolean(subplebbit), - ); + const { communities } = communitiesStore.getState(); + const { invalidateCommunityPages } = communitiesPagesStore.getState(); + const loadedCommunities = communityAddresses + .map((communityAddress: string) => communities[communityAddress]) + .filter((community: Community | undefined): community is Community => Boolean(community)); await Promise.all( - loadedSubplebbits.map((subplebbit: Subplebbit) => - invalidateSubplebbitPages(subplebbit, sortType, modQueue), + loadedCommunities.map((community: Community) => + invalidateCommunityPages(community, sortType, modQueue), ), ); } await Promise.all( - subplebbitAddresses.map((subplebbitAddress: string) => - subplebbitsStore + communityAddresses.map((communityAddress: string) => + communitiesStore .getState() - .refreshSubplebbit(subplebbitAddress, account) + .refreshCommunity(communityAddress, account) .catch((error: unknown) => - log.error("feedsStore.resetFeed refreshSubplebbit error", { + log.error("feedsStore.resetFeed refreshCommunity error", { feedName, - subplebbitAddress, + communityAddress, error, }), ), @@ -260,7 +258,7 @@ const feedsStore = createStore((setState: Function, getState: Functi updateFeeds(); }, - // recalculate all feeds using new subplebbits.post.pages, subplebbitsPagesStore and page numbers + // recalculate all feeds using new communities.post.pages, communitiesPagesStore and page numbers updateFeeds() { if (updateFeedsPending) { return; @@ -274,17 +272,17 @@ const feedsStore = createStore((setState: Function, getState: Functi // get state from all stores const previousState = getState(); const { feedsOptions } = previousState; - const { subplebbits } = subplebbitsStore.getState(); - const { subplebbitsPages } = subplebbitsPagesStore.getState(); + const { communities } = communitiesStore.getState(); + const { communitiesPages } = communitiesPagesStore.getState(); const { accounts } = accountsStore.getState(); // calculate new feeds const filteredSortedFeeds = getFilteredSortedFeeds( feedsOptions, - subplebbits, - subplebbitsPages, + communities, + communitiesPages, accounts, - subplebbitsPagesStore.getState().comments, + communitiesPagesStore.getState().comments, ); const bufferedFeedsWithoutPreviousLoadedFeeds = getBufferedFeedsWithoutLoadedFeeds( filteredSortedFeeds, @@ -302,21 +300,21 @@ const feedsStore = createStore((setState: Function, getState: Functi bufferedFeedsWithoutPreviousLoadedFeeds, loadedFeeds, ); - const bufferedFeedsSubplebbitsPostCounts = getFeedsSubplebbitsPostCounts( + const bufferedFeedsCommunitiesPostCounts = getFeedsCommunitiesPostCounts( feedsOptions, bufferedFeeds, ); const feedsHaveMore = getFeedsHaveMore( feedsOptions, bufferedFeeds, - subplebbits, - subplebbitsPages, + communities, + communitiesPages, accounts, ); - const feedsSubplebbitAddressesWithNewerPosts = getFeedsSubplebbitAddressesWithNewerPosts( + const feedsCommunityAddressesWithNewerPosts = getFeedsCommunityAddressesWithNewerPosts( filteredSortedFeeds, loadedFeeds, - previousState.feedsSubplebbitAddressesWithNewerPosts, + previousState.feedsCommunityAddressesWithNewerPosts, ); const updatedFeeds = await getUpdatedFeeds( feedsOptions, @@ -331,20 +329,20 @@ const feedsStore = createStore((setState: Function, getState: Functi bufferedFeeds, loadedFeeds, updatedFeeds, - bufferedFeedsSubplebbitsPostCounts, + bufferedFeedsCommunitiesPostCounts, feedsHaveMore, - feedsSubplebbitAddressesWithNewerPosts, + feedsCommunityAddressesWithNewerPosts, })); log.trace("feedsStore.updateFeeds", { feedsOptions, bufferedFeeds, loadedFeeds, updatedFeeds, - bufferedFeedsSubplebbitsPostCounts, + bufferedFeedsCommunitiesPostCounts, feedsHaveMore, - subplebbits, - subplebbitsPages, - feedsSubplebbitAddressesWithNewerPosts, + communities, + communitiesPages, + feedsCommunityAddressesWithNewerPosts, }); // TODO: if updateFeeds was called while updateFeedsPending = true, maybe we should recall updateFeeds here @@ -358,13 +356,13 @@ const initializeFeedsStore = async () => { if (feedsStoreInitialized) { return; } - // TODO: optimize subscriptions e.g. updateFeedsOnFeedsSubplebbitsChange(subplebbits) - // subscribe to subplebbits store changes - subplebbitsStore.subscribe(updateFeedsOnFeedsSubplebbitsChange); - // subscribe to bufferedFeedsSubplebbitsPostCounts change - feedsStore.subscribe(addSubplebbitsPagesOnLowBufferedFeedsSubplebbitsPostCounts); - // subscribe to subplebbits pages store changes - subplebbitsPagesStore.subscribe(updateFeedsOnFeedsSubplebbitsPagesChange); + // TODO: optimize subscriptions e.g. updateFeedsOnFeedsCommunitiesChange(communities) + // subscribe to communities store changes + communitiesStore.subscribe(updateFeedsOnFeedsCommunitiesChange); + // subscribe to bufferedFeedsCommunitiesPostCounts change + feedsStore.subscribe(addCommunitiesPagesOnLowBufferedFeedsCommunitiesPostCounts); + // subscribe to communities pages store changes + communitiesPagesStore.subscribe(updateFeedsOnFeedsCommunitiesPagesChange); // subscribe to accounts store change (for blocked addresses) accountsStore.subscribe(updateFeedsOnAccountsBlockedAddressesChange); // subscribe to accounts store change (for blocked cids) @@ -456,95 +454,95 @@ const updateFeedsOnAccountsBlockedCidsChange = (accountsStoreState: any) => { updateFeeds(); }; -let previousSubplebbitsPages: { [pageCid: string]: SubplebbitPage } = {}; -const updateFeedsOnFeedsSubplebbitsPagesChange = (subplebbitsPagesStoreState: any) => { - const { subplebbitsPages } = subplebbitsPagesStoreState; +let previousCommunitiesPages: { [pageCid: string]: CommunityPage } = {}; +const updateFeedsOnFeedsCommunitiesPagesChange = (communitiesPagesStoreState: any) => { + const { communitiesPages } = communitiesPagesStoreState; // no changes, do nothing - if (subplebbitsPages === previousSubplebbitsPages) { + if (communitiesPages === previousCommunitiesPages) { return; } - previousSubplebbitsPages = subplebbitsPages; + previousCommunitiesPages = communitiesPages; - // currently only the feeds use subplebbitsPagesStore, so any change must - // trigger a feed update, if in the future another hook uses the subplebbitsPagesStore - // we should check if the subplebbits pages changed are actually used by the feeds before + // currently only the feeds use communitiesPagesStore, so any change must + // trigger a feed update, if in the future another hook uses the communitiesPagesStore + // we should check if the communities pages changed are actually used by the feeds before // triggering an update feedsStore.getState().updateFeeds(); }; -let previousBufferedFeedsSubplebbitsPostCountsPageCids: string[] = []; -let previousBufferedFeedsSubplebbits = new Map(); -let previousBufferedFeedsSubplebbitsPostCounts: FeedsSubplebbitsPostCounts = {}; -const addSubplebbitsPagesOnLowBufferedFeedsSubplebbitsPostCounts = (feedsStoreState: any) => { - const { bufferedFeedsSubplebbitsPostCounts, feedsOptions } = feedsStore.getState(); - const { subplebbits } = subplebbitsStore.getState(); - - // if feeds subplebbits have changed, we must try adding them even if buffered posts counts haven't changed - const bufferedFeedsSubplebbits = getFeedsSubplebbits(feedsOptions, subplebbits); - const _feedsSubplebbitsChanged = feedsSubplebbitsChanged( - previousBufferedFeedsSubplebbits, - bufferedFeedsSubplebbits, +let previousBufferedFeedsCommunitiesPostCountsPageCids: string[] = []; +let previousBufferedFeedsCommunities = new Map(); +let previousBufferedFeedsCommunitiesPostCounts: FeedsCommunitiesPostCounts = {}; +const addCommunitiesPagesOnLowBufferedFeedsCommunitiesPostCounts = (feedsStoreState: any) => { + const { bufferedFeedsCommunitiesPostCounts, feedsOptions } = feedsStore.getState(); + const { communities } = communitiesStore.getState(); + + // if feeds communities have changed, we must try adding them even if buffered posts counts haven't changed + const bufferedFeedsCommunities = getFeedsCommunities(feedsOptions, communities); + const _feedsCommunitiesChanged = feedsCommunitiesChanged( + previousBufferedFeedsCommunities, + bufferedFeedsCommunities, ); - const bufferedFeedsSubplebbitsPostCountsChanged = - previousBufferedFeedsSubplebbitsPostCounts !== bufferedFeedsSubplebbitsPostCounts; + const bufferedFeedsCommunitiesPostCountsChanged = + previousBufferedFeedsCommunitiesPostCounts !== bufferedFeedsCommunitiesPostCounts; - // if feeds subplebbits havent changed and buffered posts counts also havent changed, do nothing - if (!_feedsSubplebbitsChanged && !bufferedFeedsSubplebbitsPostCountsChanged) { + // if feeds communities havent changed and buffered posts counts also havent changed, do nothing + if (!_feedsCommunitiesChanged && !bufferedFeedsCommunitiesPostCountsChanged) { return; } - previousBufferedFeedsSubplebbits = bufferedFeedsSubplebbits; - previousBufferedFeedsSubplebbitsPostCounts = bufferedFeedsSubplebbitsPostCounts; - - // in case feeds subplebbit changed, but the first page cids haven't - const bufferedFeedsSubplebbitsPostCountsPageCids = - getFeedsSubplebbitsFirstPageCids(bufferedFeedsSubplebbits); - const bufferedFeedsSubplebbitsPostCountsPageCidsChanged = - bufferedFeedsSubplebbitsPostCountsPageCids.toString() !== - previousBufferedFeedsSubplebbitsPostCountsPageCids.toString(); + previousBufferedFeedsCommunities = bufferedFeedsCommunities; + previousBufferedFeedsCommunitiesPostCounts = bufferedFeedsCommunitiesPostCounts; + + // in case feeds community changed, but the first page cids haven't + const bufferedFeedsCommunitiesPostCountsPageCids = + getFeedsCommunitiesFirstPageCids(bufferedFeedsCommunities); + const bufferedFeedsCommunitiesPostCountsPageCidsChanged = + bufferedFeedsCommunitiesPostCountsPageCids.toString() !== + previousBufferedFeedsCommunitiesPostCountsPageCids.toString(); if ( - !bufferedFeedsSubplebbitsPostCountsPageCidsChanged && - !bufferedFeedsSubplebbitsPostCountsChanged + !bufferedFeedsCommunitiesPostCountsPageCidsChanged && + !bufferedFeedsCommunitiesPostCountsChanged ) { return; } - previousBufferedFeedsSubplebbitsPostCountsPageCids = bufferedFeedsSubplebbitsPostCountsPageCids; + previousBufferedFeedsCommunitiesPostCountsPageCids = bufferedFeedsCommunitiesPostCountsPageCids; - const { addNextSubplebbitPageToStore } = subplebbitsPagesStore.getState(); + const { addNextCommunityPageToStore } = communitiesPagesStore.getState(); const { accounts } = accountsStore.getState(); - // bufferedFeedsSubplebbitsPostCounts have changed, check if any of them are low - for (const feedName in bufferedFeedsSubplebbitsPostCounts) { + // bufferedFeedsCommunitiesPostCounts have changed, check if any of them are low + for (const feedName in bufferedFeedsCommunitiesPostCounts) { const account = accounts[feedsOptions[feedName].accountId]; - const subplebbitsPostCounts = bufferedFeedsSubplebbitsPostCounts[feedName]; + const communitiesPostCounts = bufferedFeedsCommunitiesPostCounts[feedName]; const { sortType, modQueue } = feedsOptions[feedName]; - for (const subplebbitAddress in subplebbitsPostCounts) { - // don't fetch more pages if subplebbit address is blocked - if (account?.blockedAddresses[subplebbitAddress]) { + for (const communityAddress in communitiesPostCounts) { + // don't fetch more pages if community address is blocked + if (account?.blockedAddresses[communityAddress]) { continue; } - // subplebbit hasn't loaded yet - if (!subplebbits[subplebbitAddress]) { + // community hasn't loaded yet + if (!communities[communityAddress]) { continue; } - // if subplebbit posts cache is expired, don't use, wait for next subplebbit update - if (subplebbitPostsCacheExpired(subplebbits[subplebbitAddress])) { + // if community posts cache is expired, don't use, wait for next community update + if (communityPostsCacheExpired(communities[communityAddress])) { continue; } - // subplebbit post count is low, fetch next subplebbit page - if (subplebbitsPostCounts[subplebbitAddress] <= subplebbitPostsLeftBeforeNextPage) { - addNextSubplebbitPageToStore( - subplebbits[subplebbitAddress], + // community post count is low, fetch next community page + if (communitiesPostCounts[communityAddress] <= communityPostsLeftBeforeNextPage) { + addNextCommunityPageToStore( + communities[communityAddress], sortType, account, modQueue, ).catch((error: unknown) => - log.error("feedsStore subplebbitsActions.addNextSubplebbitPageToStore error", { - subplebbitAddress, - subplebbit: subplebbits[subplebbitAddress], + log.error("feedsStore communitiesActions.addNextCommunityPageToStore error", { + communityAddress, + community: communities[communityAddress], sortType, error, }), @@ -554,50 +552,50 @@ const addSubplebbitsPagesOnLowBufferedFeedsSubplebbitsPostCounts = (feedsStoreSt } }; -let previousFeedsSubplebbitsFirstPageCids: string[] = []; -let previousFeedsSubplebbits: Map = new Map(); -let previousFeedsSubplebbitsLoadedCount = 0; -let previousFeedsSubplebbitsPostsPagesFirstUpdatedAts = ""; -const updateFeedsOnFeedsSubplebbitsChange = (subplebbitsStoreState: any) => { - const { subplebbits } = subplebbitsStoreState; +let previousFeedsCommunitiesFirstPageCids: string[] = []; +let previousFeedsCommunities: Map = new Map(); +let previousFeedsCommunitiesLoadedCount = 0; +let previousFeedsCommunitiesPostsPagesFirstUpdatedAts = ""; +const updateFeedsOnFeedsCommunitiesChange = (communitiesStoreState: any) => { + const { communities } = communitiesStoreState; const { feedsOptions, updateFeeds } = feedsStore.getState(); - // feeds subplebbits haven't changed, do nothing - const feedsSubplebbits = getFeedsSubplebbits(feedsOptions, subplebbits); - if (!feedsSubplebbitsChanged(previousFeedsSubplebbits, feedsSubplebbits)) { + // feeds communities haven't changed, do nothing + const feedsCommunities = getFeedsCommunities(feedsOptions, communities); + if (!feedsCommunitiesChanged(previousFeedsCommunities, feedsCommunities)) { return; } - previousFeedsSubplebbits = feedsSubplebbits; + previousFeedsCommunities = feedsCommunities; - // decide if feeds subplebbits have changed by looking at all feeds subplebbits page cids - // (in case that a subplebbit changed, but its first page cid didn't) - const feedsSubplebbitsFirstPageCids = getFeedsSubplebbitsFirstPageCids(feedsSubplebbits); + // decide if feeds communities have changed by looking at all feeds communities page cids + // (in case that a community changed, but its first page cid didn't) + const feedsCommunitiesFirstPageCids = getFeedsCommunitiesFirstPageCids(feedsCommunities); // first page cids haven't changed, do nothing if ( - feedsSubplebbitsFirstPageCids.toString() === previousFeedsSubplebbitsFirstPageCids.toString() + feedsCommunitiesFirstPageCids.toString() === previousFeedsCommunitiesFirstPageCids.toString() ) { - // if no new feed subplebbits have loaded, do nothing + // if no new feed communities have loaded, do nothing // in case a sub loads with no first page cid and first pages cids don't change, need to trigger hasMore update - const feedsSubplebbitsLoadedCount = getFeedsSubplebbitsLoadedCount(feedsSubplebbits); - if (feedsSubplebbitsLoadedCount === previousFeedsSubplebbitsLoadedCount) { - // if subplebbit.posts.pages haven't changed, do nothing - const feedsSubplebbitsPostsPagesFirstUpdatedAts = - getFeedsSubplebbitsPostsPagesFirstUpdatedAts(feedsSubplebbits); + const feedsCommunitiesLoadedCount = getFeedsCommunitiesLoadedCount(feedsCommunities); + if (feedsCommunitiesLoadedCount === previousFeedsCommunitiesLoadedCount) { + // if community.posts.pages haven't changed, do nothing + const feedsCommunitiesPostsPagesFirstUpdatedAts = + getFeedsCommunitiesPostsPagesFirstUpdatedAts(feedsCommunities); if ( - feedsSubplebbitsPostsPagesFirstUpdatedAts === - previousFeedsSubplebbitsPostsPagesFirstUpdatedAts + feedsCommunitiesPostsPagesFirstUpdatedAts === + previousFeedsCommunitiesPostsPagesFirstUpdatedAts ) { return; } - previousFeedsSubplebbitsPostsPagesFirstUpdatedAts = feedsSubplebbitsPostsPagesFirstUpdatedAts; + previousFeedsCommunitiesPostsPagesFirstUpdatedAts = feedsCommunitiesPostsPagesFirstUpdatedAts; } - previousFeedsSubplebbitsLoadedCount = feedsSubplebbitsLoadedCount; + previousFeedsCommunitiesLoadedCount = feedsCommunitiesLoadedCount; } - // feeds subplebbits have changed, update feeds - previousFeedsSubplebbitsFirstPageCids = feedsSubplebbitsFirstPageCids; + // feeds communities have changed, update feeds + previousFeedsCommunitiesFirstPageCids = feedsCommunitiesFirstPageCids; updateFeeds(); }; @@ -628,12 +626,12 @@ const updateFeedsOnAccountsCommentsChange = (accountsStoreState: any) => { feedsStore.getState().updateFeeds(); }; -const addSubplebbitsToSubplebbitsStore = (subplebbitAddresses: string[], account: Account) => { - const addSubplebbitToStore = subplebbitsStore.getState().addSubplebbitToStore; - for (const subplebbitAddress of subplebbitAddresses) { - addSubplebbitToStore(subplebbitAddress, account).catch((error: unknown) => - log.error("feedsStore subplebbitsActions.addSubplebbitToStore error", { - subplebbitAddress, +const addCommunitiesToCommunitiesStore = (communityAddresses: string[], account: Account) => { + const addCommunityToStore = communitiesStore.getState().addCommunityToStore; + for (const communityAddress of communityAddresses) { + addCommunityToStore(communityAddress, account).catch((error: unknown) => + log.error("feedsStore communitiesActions.addCommunityToStore error", { + communityAddress, error, }), ); @@ -644,18 +642,18 @@ const addSubplebbitsToSubplebbitsStore = (subplebbitAddresses: string[], account const originalState = feedsStore.getState(); // async function because some stores have async init export const resetFeedsStore = async () => { - previousBufferedFeedsSubplebbitsPostCounts = {}; - previousBufferedFeedsSubplebbitsPostCountsPageCids = []; - previousBufferedFeedsSubplebbits = new Map(); + previousBufferedFeedsCommunitiesPostCounts = {}; + previousBufferedFeedsCommunitiesPostCountsPageCids = []; + previousBufferedFeedsCommunities = new Map(); previousBlockedAddresses = []; previousAccountsBlockedAddresses = []; previousBlockedCids = []; previousAccountsBlockedCids = []; - previousFeedsSubplebbitsFirstPageCids = []; - previousFeedsSubplebbits = new Map(); - previousFeedsSubplebbitsLoadedCount = 0; - previousFeedsSubplebbitsPostsPagesFirstUpdatedAts = ""; - previousSubplebbitsPages = {}; + previousFeedsCommunitiesFirstPageCids = []; + previousFeedsCommunities = new Map(); + previousFeedsCommunitiesLoadedCount = 0; + previousFeedsCommunitiesPostsPagesFirstUpdatedAts = ""; + previousCommunitiesPages = {}; previousAccountsCommentsCount = 0; previousAccountsCommentsCids = ""; updateFeedsPending = false; @@ -668,7 +666,7 @@ export const resetFeedsStore = async () => { // reset database and store in between tests export const resetFeedsDatabaseAndStore = async () => { - await localForageLru.createInstance({ name: "plebbitReactHooks-subplebbitsPages" }).clear(); + await localForageLru.createInstance({ name: "plebbitReactHooks-communitiesPages" }).clear(); await resetFeedsStore(); }; diff --git a/src/stores/feeds/index.ts b/src/stores/feeds/index.ts index 4f1a0c76..558688aa 100644 --- a/src/stores/feeds/index.ts +++ b/src/stores/feeds/index.ts @@ -1,3 +1,3 @@ -import feedsStore from './feeds-store' -export * from './feeds-store' -export default feedsStore +import feedsStore from "./feeds-store"; +export * from "./feeds-store"; +export default feedsStore; diff --git a/src/stores/feeds/utils.test.ts b/src/stores/feeds/utils.test.ts index b0ecc558..eed0ca26 100644 --- a/src/stores/feeds/utils.test.ts +++ b/src/stores/feeds/utils.test.ts @@ -4,8 +4,8 @@ import { addAccountsComments, getBufferedFeedsWithoutLoadedFeeds, getUpdatedFeeds, - getFeedsSubplebbitsFirstPageCids, - getFeedsSubplebbits, + getFeedsCommunitiesFirstPageCids, + getFeedsCommunities, getAccountsBlockedAddresses, accountsBlockedAddressesChanged, accountsBlockedCidsChanged, @@ -48,14 +48,14 @@ describe("feeds utils", () => { }); }); - test("uses preloaded posts when subplebbit.posts.pages[sortType].comments exists", () => { + test("uses preloaded posts when community.posts.pages[sortType].comments exists", () => { const feedName = "feed1"; const preloadedComment = { cid: "preloaded-cid", - subplebbitAddress: "sub1", + communityAddress: "sub1", timestamp: 100, }; - const subplebbits = { + const communities = { sub1: { address: "sub1", updatedAt: 1, @@ -66,103 +66,103 @@ describe("feeds utils", () => { }; const feedsOptions = { [feedName]: { - subplebbitAddresses: ["sub1"], + communityAddresses: ["sub1"], sortType: "new", accountId: mockAccountId, }, }; - const feeds = getFilteredSortedFeeds(feedsOptions, subplebbits, {}, makeMockAccounts()); + const feeds = getFilteredSortedFeeds(feedsOptions, communities, {}, makeMockAccounts()); expect(feeds[feedName]).toContainEqual(expect.objectContaining({ cid: "preloaded-cid" })); }); - test("returns undefined preloaded when pageCids present (hasPageCids), uses subplebbitsPages", () => { + test("returns undefined preloaded when pageCids present (hasPageCids), uses communitiesPages", () => { const pageCid = "page-cid-1"; - const subplebbits = { + const communities = { sub1: { address: "sub1", updatedAt: 1, posts: { pageCids: { new: pageCid }, - pages: { new: { comments: [{ cid: "c1", subplebbitAddress: "sub1" }] } }, + pages: { new: { comments: [{ cid: "c1", communityAddress: "sub1" }] } }, }, }, }; - const subplebbitsPages = { + const communitiesPages = { [pageCid]: { - comments: [{ cid: "c1", subplebbitAddress: "sub1", timestamp: 1 }], + comments: [{ cid: "c1", communityAddress: "sub1", timestamp: 1 }], nextCid: undefined, }, }; const feedsOptions = { feed1: { - subplebbitAddresses: ["sub1"], + communityAddresses: ["sub1"], sortType: "new", accountId: mockAccountId, }, }; const feeds = getFilteredSortedFeeds( feedsOptions, - subplebbits, - subplebbitsPages, + communities, + communitiesPages, makeMockAccounts(), ); expect(feeds.feed1).toBeDefined(); expect(feeds.feed1).toContainEqual( - expect.objectContaining({ cid: "c1", subplebbitAddress: "sub1" }), + expect.objectContaining({ cid: "c1", communityAddress: "sub1" }), ); }); - test("skips cache-expired subplebbit when navigator.onLine", () => { + test("skips cache-expired community when navigator.onLine", () => { const origOnLine = Object.getOwnPropertyDescriptor(navigator, "onLine"); Object.defineProperty(navigator, "onLine", { value: true, configurable: true }); - const subplebbits = { + const communities = { sub1: { address: "sub1", updatedAt: 1, fetchedAt: 0, posts: { - pages: { new: { comments: [{ cid: "c1", subplebbitAddress: "sub1", timestamp: 1 }] } }, + pages: { new: { comments: [{ cid: "c1", communityAddress: "sub1", timestamp: 1 }] } }, }, }, }; const feedsOptions = { feed1: { - subplebbitAddresses: ["sub1"], + communityAddresses: ["sub1"], sortType: "new", accountId: mockAccountId, }, }; - const feeds = getFilteredSortedFeeds(feedsOptions, subplebbits, {}, makeMockAccounts()); + const feeds = getFilteredSortedFeeds(feedsOptions, communities, {}, makeMockAccounts()); expect(feeds.feed1).toEqual([]); if (origOnLine) Object.defineProperty(navigator, "onLine", origOnLine); }); - test("skips subplebbit that has not loaded yet", () => { + test("skips community that has not loaded yet", () => { const feedsOptions = { feed1: { - subplebbitAddresses: ["sub1", "sub2"], + communityAddresses: ["sub1", "sub2"], sortType: "new", accountId: mockAccountId, }, }; - const subplebbits = { + const communities = { sub1: { address: "sub1", updatedAt: 1, posts: { - pages: { new: { comments: [{ cid: "c1", subplebbitAddress: "sub1", timestamp: 1 }] } }, + pages: { new: { comments: [{ cid: "c1", communityAddress: "sub1", timestamp: 1 }] } }, }, }, }; - const feeds = getFilteredSortedFeeds(feedsOptions, subplebbits, {}, makeMockAccounts()); + const feeds = getFilteredSortedFeeds(feedsOptions, communities, {}, makeMockAccounts()); expect(feeds.feed1).toContainEqual( - expect.objectContaining({ cid: "c1", subplebbitAddress: "sub1" }), + expect.objectContaining({ cid: "c1", communityAddress: "sub1" }), ); }); - test("breaks subplebbitPages loop when post has wrong subplebbitAddress", () => { + test("breaks communityPages loop when post has wrong communityAddress", () => { const pageCid = "page-cid-break"; - const subplebbits = { + const communities = { sub1: { address: "sub1", updatedAt: 1, @@ -172,34 +172,34 @@ describe("feeds utils", () => { }, }, }; - const subplebbitsPages = { + const communitiesPages = { [pageCid]: { comments: [ - { cid: "c1", subplebbitAddress: "sub1", timestamp: 1 }, - { cid: "c2", subplebbitAddress: "wrong-sub", timestamp: 2 }, + { cid: "c1", communityAddress: "sub1", timestamp: 1 }, + { cid: "c2", communityAddress: "wrong-sub", timestamp: 2 }, ], nextCid: undefined, }, }; const feedsOptions = { feed1: { - subplebbitAddresses: ["sub1"], + communityAddresses: ["sub1"], sortType: "new", accountId: mockAccountId, }, }; const feeds = getFilteredSortedFeeds( feedsOptions, - subplebbits, - subplebbitsPages, + communities, + communitiesPages, makeMockAccounts(), ); expect(feeds.feed1).toHaveLength(1); expect(feeds.feed1[0].cid).toBe("c1"); }); - test("breaks preloaded loop when post has wrong subplebbitAddress", () => { - const subplebbits = { + test("breaks preloaded loop when post has wrong communityAddress", () => { + const communities = { sub1: { address: "sub1", updatedAt: 1, @@ -207,8 +207,8 @@ describe("feeds utils", () => { pages: { new: { comments: [ - { cid: "c1", subplebbitAddress: "sub1", timestamp: 1 }, - { cid: "c2", subplebbitAddress: "other-sub", timestamp: 2 }, + { cid: "c1", communityAddress: "sub1", timestamp: 1 }, + { cid: "c2", communityAddress: "other-sub", timestamp: 2 }, ], }, }, @@ -217,18 +217,18 @@ describe("feeds utils", () => { }; const feedsOptions = { feed1: { - subplebbitAddresses: ["sub1"], + communityAddresses: ["sub1"], sortType: "new", accountId: mockAccountId, }, }; - const feeds = getFilteredSortedFeeds(feedsOptions, subplebbits, {}, makeMockAccounts()); + const feeds = getFilteredSortedFeeds(feedsOptions, communities, {}, makeMockAccounts()); expect(feeds.feed1).toHaveLength(1); expect(feeds.feed1[0].cid).toBe("c1"); }); test("modQueue excludes page comments that no longer have pendingApproval", () => { - const subplebbits = { + const communities = { sub1: { address: "sub1", updatedAt: 1, @@ -238,17 +238,17 @@ describe("feeds utils", () => { }, }, }; - const subplebbitsPages = { + const communitiesPages = { "mod-queue-page-cid": { comments: [ { cid: "public-cid", - subplebbitAddress: "sub1", + communityAddress: "sub1", timestamp: 1, }, { cid: "pending-cid", - subplebbitAddress: "sub1", + communityAddress: "sub1", timestamp: 2, pendingApproval: true, }, @@ -258,7 +258,7 @@ describe("feeds utils", () => { }; const feedsOptions = { feed1: { - subplebbitAddresses: ["sub1"], + communityAddresses: ["sub1"], sortType: "new", accountId: mockAccountId, modQueue: ["pendingApproval"], @@ -267,16 +267,16 @@ describe("feeds utils", () => { const feeds = getFilteredSortedFeeds( feedsOptions, - subplebbits, - subplebbitsPages, + communities, + communitiesPages, makeMockAccounts(), ); expect(feeds.feed1.map((post: any) => post.cid)).toEqual(["pending-cid"]); }); - test("keeps posts when requested subplebbitAddress is a .bso alias of the post .eth address", () => { - const subplebbits = { + test("keeps posts when requested communityAddress is a .bso alias of the post .eth address", () => { + const communities = { "music-posting.bso": { address: "music-posting.bso", updatedAt: 1, @@ -284,8 +284,8 @@ describe("feeds utils", () => { pages: { new: { comments: [ - { cid: "c1", subplebbitAddress: "music-posting.eth", timestamp: 1 }, - { cid: "c2", subplebbitAddress: "music-posting.eth", timestamp: 2 }, + { cid: "c1", communityAddress: "music-posting.eth", timestamp: 1 }, + { cid: "c2", communityAddress: "music-posting.eth", timestamp: 2 }, ], }, }, @@ -294,18 +294,18 @@ describe("feeds utils", () => { }; const feedsOptions = { feed1: { - subplebbitAddresses: ["music-posting.bso"], + communityAddresses: ["music-posting.bso"], sortType: "new", accountId: mockAccountId, }, }; - const feeds = getFilteredSortedFeeds(feedsOptions, subplebbits, {}, makeMockAccounts()); + const feeds = getFilteredSortedFeeds(feedsOptions, communities, {}, makeMockAccounts()); expect(feeds.feed1.map((post: any) => post.cid)).toEqual(["c2", "c1"]); }); - test("skips post when subplebbit address is blocked", () => { + test("skips post when community address is blocked", () => { const blockedSubAddr = "blocked-sub-addr"; - const subplebbits = { + const communities = { [blockedSubAddr]: { address: blockedSubAddr, updatedAt: 1, @@ -313,8 +313,8 @@ describe("feeds utils", () => { pages: { new: { comments: [ - { cid: "c1", subplebbitAddress: blockedSubAddr, timestamp: 1 }, - { cid: "c2", subplebbitAddress: blockedSubAddr, timestamp: 2 }, + { cid: "c1", communityAddress: blockedSubAddr, timestamp: 1 }, + { cid: "c2", communityAddress: blockedSubAddr, timestamp: 2 }, ], }, }, @@ -323,7 +323,7 @@ describe("feeds utils", () => { }; const feedsOptions = { feed1: { - subplebbitAddresses: [blockedSubAddr], + communityAddresses: [blockedSubAddr], sortType: "new", accountId: mockAccountId, }, @@ -331,13 +331,13 @@ describe("feeds utils", () => { const accounts = makeMockAccounts({ blockedAddresses: { [blockedSubAddr]: true }, }); - const feeds = getFilteredSortedFeeds(feedsOptions, subplebbits, {}, accounts); + const feeds = getFilteredSortedFeeds(feedsOptions, communities, {}, accounts); expect(feeds.feed1).toHaveLength(0); }); test("skips post when author address is blocked", () => { const blockedAuthorAddr = "blocked-author-addr"; - const subplebbits = { + const communities = { sub1: { address: "sub1", updatedAt: 1, @@ -347,11 +347,11 @@ describe("feeds utils", () => { comments: [ { cid: "c1", - subplebbitAddress: "sub1", + communityAddress: "sub1", timestamp: 1, author: { address: blockedAuthorAddr }, }, - { cid: "c2", subplebbitAddress: "sub1", timestamp: 2 }, + { cid: "c2", communityAddress: "sub1", timestamp: 2 }, ], }, }, @@ -360,7 +360,7 @@ describe("feeds utils", () => { }; const feedsOptions = { feed1: { - subplebbitAddresses: ["sub1"], + communityAddresses: ["sub1"], sortType: "new", accountId: mockAccountId, }, @@ -368,13 +368,13 @@ describe("feeds utils", () => { const accounts = makeMockAccounts({ blockedAddresses: { [blockedAuthorAddr]: true }, }); - const feeds = getFilteredSortedFeeds(feedsOptions, subplebbits, {}, accounts); + const feeds = getFilteredSortedFeeds(feedsOptions, communities, {}, accounts); expect(feeds.feed1.map((p: any) => p.cid)).not.toContain("c1"); expect(feeds.feed1.map((p: any) => p.cid)).toContain("c2"); }); test("filter function excludes posts when filter returns false", () => { - const subplebbits = { + const communities = { sub1: { address: "sub1", updatedAt: 1, @@ -382,8 +382,8 @@ describe("feeds utils", () => { pages: { new: { comments: [ - { cid: "c1", subplebbitAddress: "sub1", timestamp: 1 }, - { cid: "c2", subplebbitAddress: "sub1", timestamp: 2 }, + { cid: "c1", communityAddress: "sub1", timestamp: 1 }, + { cid: "c2", communityAddress: "sub1", timestamp: 2 }, ], }, }, @@ -392,19 +392,19 @@ describe("feeds utils", () => { }; const feedsOptions = { feed1: { - subplebbitAddresses: ["sub1"], + communityAddresses: ["sub1"], sortType: "new", accountId: mockAccountId, filter: { filter: (p: any) => p.cid !== "c2", key: "exclude-c2" }, }, }; - const feeds = getFilteredSortedFeeds(feedsOptions, subplebbits, {}, makeMockAccounts()); + const feeds = getFilteredSortedFeeds(feedsOptions, communities, {}, makeMockAccounts()); expect(feeds.feed1).toHaveLength(1); expect(feeds.feed1[0].cid).toBe("c1"); }); - test("skips pinned post when feed has multiple subplebbits", () => { - const subplebbits = { + test("skips pinned post when feed has multiple communities", () => { + const communities = { sub1: { address: "sub1", updatedAt: 1, @@ -412,8 +412,8 @@ describe("feeds utils", () => { pages: { new: { comments: [ - { cid: "c1", subplebbitAddress: "sub1", timestamp: 1, pinned: true }, - { cid: "c2", subplebbitAddress: "sub1", timestamp: 2 }, + { cid: "c1", communityAddress: "sub1", timestamp: 1, pinned: true }, + { cid: "c2", communityAddress: "sub1", timestamp: 2 }, ], }, }, @@ -425,7 +425,7 @@ describe("feeds utils", () => { posts: { pages: { new: { - comments: [{ cid: "c3", subplebbitAddress: "sub2", timestamp: 3 }], + comments: [{ cid: "c3", communityAddress: "sub2", timestamp: 3 }], }, }, }, @@ -433,19 +433,19 @@ describe("feeds utils", () => { }; const feedsOptions = { feed1: { - subplebbitAddresses: ["sub1", "sub2"], + communityAddresses: ["sub1", "sub2"], sortType: "new", accountId: mockAccountId, }, }; - const feeds = getFilteredSortedFeeds(feedsOptions, subplebbits, {}, makeMockAccounts()); + const feeds = getFilteredSortedFeeds(feedsOptions, communities, {}, makeMockAccounts()); expect(feeds.feed1.map((p: any) => p.cid)).not.toContain("c1"); expect(feeds.feed1.map((p: any) => p.cid)).toContain("c2"); expect(feeds.feed1.map((p: any) => p.cid)).toContain("c3"); }); test("getPreloadedPosts returns undefined when hasPageCids but no preloaded for sortType", () => { - const subplebbits = { + const communities = { sub1: { address: "sub1", updatedAt: 1, @@ -455,32 +455,32 @@ describe("feeds utils", () => { }, }, }; - const subplebbitsPages = { + const communitiesPages = { "page-cid-1": { - comments: [{ cid: "c1", subplebbitAddress: "sub1", timestamp: 1 }], + comments: [{ cid: "c1", communityAddress: "sub1", timestamp: 1 }], nextCid: undefined, }, }; const feedsOptions = { feed1: { - subplebbitAddresses: ["sub1"], + communityAddresses: ["sub1"], sortType: "new", accountId: mockAccountId, }, }; const feeds = getFilteredSortedFeeds( feedsOptions, - subplebbits, - subplebbitsPages, + communities, + communitiesPages, makeMockAccounts(), ); expect(feeds.feed1).toContainEqual( - expect.objectContaining({ cid: "c1", subplebbitAddress: "sub1" }), + expect.objectContaining({ cid: "c1", communityAddress: "sub1" }), ); }); test("getPreloadedPosts returns undefined when pages exist but pages[0].comments is empty", () => { - const subplebbits = { + const communities = { sub1: { address: "sub1", updatedAt: 1, @@ -493,22 +493,22 @@ describe("feeds utils", () => { }; const feedsOptions = { feed1: { - subplebbitAddresses: ["sub1"], + communityAddresses: ["sub1"], sortType: "new", accountId: mockAccountId, }, }; - const feeds = getFilteredSortedFeeds(feedsOptions, subplebbits, {}, makeMockAccounts()); + const feeds = getFilteredSortedFeeds(feedsOptions, communities, {}, makeMockAccounts()); expect(feeds.feed1).toEqual([]); }); test("fallback to any page when no pageCids, no nextCids, single preloaded page", () => { const feedComment = { cid: "fallback-cid", - subplebbitAddress: "sub1", + communityAddress: "sub1", timestamp: 1, }; - const subplebbits = { + const communities = { sub1: { address: "sub1", updatedAt: 1, @@ -521,12 +521,12 @@ describe("feeds utils", () => { }; const feedsOptions = { feed1: { - subplebbitAddresses: ["sub1"], + communityAddresses: ["sub1"], sortType: "new", accountId: mockAccountId, }, }; - const feeds = getFilteredSortedFeeds(feedsOptions, subplebbits, {}, makeMockAccounts()); + const feeds = getFilteredSortedFeeds(feedsOptions, communities, {}, makeMockAccounts()); expect(feeds.feed1).toContainEqual(expect.objectContaining({ cid: "fallback-cid" })); }); }); @@ -547,7 +547,7 @@ describe("feeds utils", () => { const recentTs = Math.floor(Date.now() / 1000) - 100; const feedsOptions = { [feedName]: { - subplebbitAddresses: ["sub1"], + communityAddresses: ["sub1"], accountId: mockAccountId, accountComments: { newerThan: 3600, append: false }, }, @@ -555,7 +555,7 @@ describe("feeds utils", () => { const existingPost = { cid: "already-cid", index: 1, - subplebbitAddress: "sub1", + communityAddress: "sub1", timestamp: recentTs, }; (accountsStore as any).getState = () => ({ @@ -573,7 +573,7 @@ describe("feeds utils", () => { const recentTs = Math.floor(Date.now() / 1000) - 100; const feedsOptions = { [feedName]: { - subplebbitAddresses: ["sub1"], + communityAddresses: ["sub1"], accountId: mockAccountId, accountComments: { newerThan: 3600, append: false }, }, @@ -581,13 +581,13 @@ describe("feeds utils", () => { const loadedPostWithOldIndex = { cid: "same-cid", index: 1, - subplebbitAddress: "sub1", + communityAddress: "sub1", timestamp: recentTs, }; const freshAccountPost = { cid: "same-cid", index: 2, - subplebbitAddress: "sub1", + communityAddress: "sub1", timestamp: recentTs, }; // @ts-ignore @@ -610,7 +610,7 @@ describe("feeds utils", () => { const recentTs = Math.floor(Date.now() / 1000) - 100; const feedsOptions = { [feedName]: { - subplebbitAddresses: ["sub1"], + communityAddresses: ["sub1"], accountId: mockAccountId, accountComments: { newerThan: 3600, append: true }, }, @@ -618,13 +618,13 @@ describe("feeds utils", () => { const existingPost = { cid: "existing-cid", index: undefined, - subplebbitAddress: "sub1", + communityAddress: "sub1", timestamp: recentTs - 1000, }; const accountPost = { cid: "append-cid", index: 1, - subplebbitAddress: "sub1", + communityAddress: "sub1", timestamp: recentTs, }; (accountsStore as any).getState = () => ({ @@ -643,20 +643,20 @@ describe("feeds utils", () => { const recentTs = Math.floor(Date.now() / 1000) - 100; const feedsOptions = { [feedName]: { - subplebbitAddresses: ["sub1"], + communityAddresses: ["sub1"], accountId: mockAccountId, accountComments: { newerThan: 3600, append: false }, }, }; const pendingPost = { index: 1, - subplebbitAddress: "sub1", + communityAddress: "sub1", timestamp: recentTs, }; const accountPostWithCid = { cid: "new-cid", index: 1, - subplebbitAddress: "sub1", + communityAddress: "sub1", timestamp: recentTs, }; // @ts-ignore @@ -676,8 +676,8 @@ describe("feeds utils", () => { }); describe("blocked addresses/cids no-change false branches", () => { - test("feedsHaveChangedBlockedAddresses returns true when changed address is in feed subplebbits", () => { - const feedsOptions = { feeds1: { subplebbitAddresses: ["blocked-x"] } }; + test("feedsHaveChangedBlockedAddresses returns true when changed address is in feed communities", () => { + const feedsOptions = { feeds1: { communityAddresses: ["blocked-x"] } }; const bufferedFeeds = { feeds1: [] }; const blockedAddresses = ["blocked-x"]; const previousBlockedAddresses: string[] = []; @@ -691,9 +691,9 @@ describe("feeds utils", () => { }); test("feedsHaveChangedBlockedAddresses returns false when changed blocked not in feeds", () => { - const feedsOptions = { feeds1: { subplebbitAddresses: ["sub-a"] } }; + const feedsOptions = { feeds1: { communityAddresses: ["sub-a"] } }; const bufferedFeeds = { - feeds1: [{ cid: "c1", subplebbitAddress: "sub-a", author: { address: "addr-a" } }], + feeds1: [{ cid: "c1", communityAddress: "sub-a", author: { address: "addr-a" } }], }; const blockedAddresses = ["blocked-x"]; const previousBlockedAddresses: string[] = []; @@ -707,9 +707,9 @@ describe("feeds utils", () => { }); test("feedsHaveChangedBlockedCids returns false when changed blocked cids not in feeds", () => { - const feedsOptions = { feeds1: { subplebbitAddresses: ["sub-a"] } }; + const feedsOptions = { feeds1: { communityAddresses: ["sub-a"] } }; const bufferedFeeds = { - feeds1: [{ cid: "c1", subplebbitAddress: "sub-a" }], + feeds1: [{ cid: "c1", communityAddress: "sub-a" }], }; const blockedCids = ["blocked-cid-x"]; const previousBlockedCids: string[] = []; @@ -723,9 +723,9 @@ describe("feeds utils", () => { }); test("feedsHaveChangedBlockedCids returns true when cid in feed is blocked", () => { - const feedsOptions = { feeds1: { subplebbitAddresses: ["sub-a"] } }; + const feedsOptions = { feeds1: { communityAddresses: ["sub-a"] } }; const bufferedFeeds = { - feeds1: [{ cid: "blocked-cid-x", subplebbitAddress: "sub-a" }], + feeds1: [{ cid: "blocked-cid-x", communityAddress: "sub-a" }], }; const blockedCids = ["blocked-cid-x"]; const previousBlockedCids: string[] = []; @@ -751,7 +751,7 @@ describe("feeds utils", () => { test("returns loadedFeeds when no missing posts and no account comments change", async () => { const feedsOptions = { feed1: { - subplebbitAddresses: ["sub1"], + communityAddresses: ["sub1"], sortType: "new", accountId: mockAccountId, pageNumber: 1, @@ -759,10 +759,10 @@ describe("feeds utils", () => { }, }; const loadedFeeds = { - feed1: [{ cid: "c1", subplebbitAddress: "sub1", timestamp: 100, index: 1 }], + feed1: [{ cid: "c1", communityAddress: "sub1", timestamp: 100, index: 1 }], }; const filteredSortedFeeds = { - feed1: [{ cid: "c1", subplebbitAddress: "sub1", timestamp: 100, index: 1 }], + feed1: [{ cid: "c1", communityAddress: "sub1", timestamp: 100, index: 1 }], }; const bufferedFeeds = { feed1: [] }; const accounts = makeMockAccounts(); @@ -779,7 +779,7 @@ describe("feeds utils", () => { test("adds missing posts from buffered feed", async () => { const feedsOptions = { feed1: { - subplebbitAddresses: ["sub1"], + communityAddresses: ["sub1"], sortType: "new", accountId: mockAccountId, pageNumber: 2, @@ -788,11 +788,11 @@ describe("feeds utils", () => { }; const loadedFeeds = { feed1: [] }; const bufferedPosts = [ - { cid: "c1", subplebbitAddress: "sub1", timestamp: 1 }, - { cid: "c2", subplebbitAddress: "sub1", timestamp: 2 }, - { cid: "c3", subplebbitAddress: "sub1", timestamp: 3 }, - { cid: "c4", subplebbitAddress: "sub1", timestamp: 4 }, - { cid: "c5", subplebbitAddress: "sub1", timestamp: 5 }, + { cid: "c1", communityAddress: "sub1", timestamp: 1 }, + { cid: "c2", communityAddress: "sub1", timestamp: 2 }, + { cid: "c3", communityAddress: "sub1", timestamp: 3 }, + { cid: "c4", communityAddress: "sub1", timestamp: 4 }, + { cid: "c5", communityAddress: "sub1", timestamp: 5 }, ]; const filteredSortedFeeds = { feed1: bufferedPosts }; const bufferedFeeds = { feed1: bufferedPosts }; @@ -811,7 +811,7 @@ describe("feeds utils", () => { const feedName = "feed1"; const feedsOptions = { [feedName]: { - subplebbitAddresses: ["sub1"], + communityAddresses: ["sub1"], sortType: "new", accountId: mockAccountId, pageNumber: 2, @@ -823,14 +823,14 @@ describe("feeds utils", () => { [feedName]: [ { cid: "keep-cid", - subplebbitAddress: "sub1", + communityAddress: "sub1", timestamp: 1, updatedAt: 1, pendingApproval: true, }, { cid: "remove-cid", - subplebbitAddress: "sub1", + communityAddress: "sub1", timestamp: 2, updatedAt: 2, pendingApproval: true, @@ -841,14 +841,14 @@ describe("feeds utils", () => { [feedName]: [ { cid: "keep-cid", - subplebbitAddress: "sub1", + communityAddress: "sub1", timestamp: 1, updatedAt: 10, pendingApproval: true, }, { cid: "new-cid", - subplebbitAddress: "sub1", + communityAddress: "sub1", timestamp: 3, updatedAt: 3, pendingApproval: true, @@ -883,14 +883,14 @@ describe("feeds utils", () => { validateComment: () => Promise.resolve(true), }; const accounts = makeMockAccounts({ plebbit }); - const loadedFeed = [{ cid: "r1", subplebbitAddress: "sub1", timestamp: 100, updatedAt: 100 }]; + const loadedFeed = [{ cid: "r1", communityAddress: "sub1", timestamp: 100, updatedAt: 100 }]; const loadedFeeds = { [feedName]: loadedFeed }; const updatedFeeds = { [feedName]: [loadedFeed[0]], }; const newerPost = { cid: "r1", - subplebbitAddress: "sub1", + communityAddress: "sub1", timestamp: 100, updatedAt: 200, }; @@ -907,15 +907,15 @@ describe("feeds utils", () => { }); describe("getFeedsHaveMore", () => { - test("continues when subplebbit address is blocked", () => { + test("continues when community address is blocked", () => { const feedsOptions = { feed1: { - subplebbitAddresses: ["blocked-sub", "sub2"], + communityAddresses: ["blocked-sub", "sub2"], sortType: "new", accountId: mockAccountId, }, }; - const subplebbits = { + const communities = { "blocked-sub": { address: "blocked-sub", updatedAt: 1, posts: {} }, sub2: { address: "sub2", @@ -923,48 +923,48 @@ describe("feeds utils", () => { posts: { pageCids: { new: "pc1" }, pages: {} }, }, }; - const subplebbitsPages = { + const communitiesPages = { pc1: { comments: [], nextCid: undefined }, }; const accounts = makeMockAccounts({ blockedAddresses: { "blocked-sub": true }, }); - const result = getFeedsHaveMore(feedsOptions, {}, subplebbits, subplebbitsPages, accounts); + const result = getFeedsHaveMore(feedsOptions, {}, communities, communitiesPages, accounts); expect(result.feed1).toBeDefined(); }); test("uses modQueue when modQueue option present", () => { const feedsOptions = { feed1: { - subplebbitAddresses: ["sub1"], + communityAddresses: ["sub1"], sortType: "new", accountId: mockAccountId, modQueue: ["approved"], }, }; - const subplebbits = { + const communities = { sub1: { address: "sub1", updatedAt: 1, modQueue: { pageCids: { approved: "mq1" }, pages: {} }, }, }; - const subplebbitsPages = { mq1: { comments: [], nextCid: undefined } }; + const communitiesPages = { mq1: { comments: [], nextCid: undefined } }; const result = getFeedsHaveMore( feedsOptions, {}, - subplebbits, - subplebbitsPages, + communities, + communitiesPages, makeMockAccounts(), ); expect(result.feed1).toBe(false); }); }); - describe("getFeedsSubplebbitsFirstPageCids", () => { + describe("getFeedsCommunitiesFirstPageCids", () => { test("includes pageCids from posts and modQueue", () => { - const feedsOptions = { f1: { subplebbitAddresses: ["s1"] } }; - const subplebbits = new Map([ + const feedsOptions = { f1: { communityAddresses: ["s1"] } }; + const communities = new Map([ [ "s1", { @@ -977,7 +977,7 @@ describe("feeds utils", () => { }, ], ]); - const cids = getFeedsSubplebbitsFirstPageCids(subplebbits); + const cids = getFeedsCommunitiesFirstPageCids(communities); expect(cids).toContain("page-cid-1"); expect(cids).toContain("next-cid-1"); expect(cids).toContain("mod-cid-1"); @@ -1030,9 +1030,9 @@ describe("feeds utils", () => { describe("feedsHaveChangedBlockedAddresses author address", () => { test("returns true when post author address is in changed blocked", () => { - const feedsOptions = { f1: { subplebbitAddresses: ["sub-a"] } }; + const feedsOptions = { f1: { communityAddresses: ["sub-a"] } }; const bufferedFeeds = { - f1: [{ cid: "c1", subplebbitAddress: "sub-a", author: { address: "blocked-author" } }], + f1: [{ cid: "c1", communityAddress: "sub-a", author: { address: "blocked-author" } }], }; const result = feedsHaveChangedBlockedAddresses( feedsOptions, diff --git a/src/stores/feeds/utils.ts b/src/stores/feeds/utils.ts index 2f9e7023..21bd9d49 100644 --- a/src/stores/feeds/utils.ts +++ b/src/stores/feeds/utils.ts @@ -5,23 +5,19 @@ import { Feeds, FeedOptions, FeedsOptions, - Subplebbit, - Subplebbits, + Community, + Communities, Account, Accounts, - SubplebbitPage, - SubplebbitsPages, - FeedsSubplebbitsPostCounts, + CommunityPage, + CommunitiesPages, + FeedsCommunitiesPostCounts, } from "../../types"; -import { getSubplebbitPages, getSubplebbitFirstPageCid } from "../subplebbits-pages"; +import { getCommunityPages, getCommunityFirstPageCid } from "../communities-pages"; import accountsStore from "../accounts"; import feedSorter from "./feed-sorter"; -import { - subplebbitPostsCacheExpired, - commentIsValid, - removeInvalidComments, -} from "../../lib/utils"; -import { areEquivalentSubplebbitAddresses } from "../../lib/subplebbit-address"; +import { communityPostsCacheExpired, commentIsValid, removeInvalidComments } from "../../lib/utils"; +import { areEquivalentCommunityAddresses } from "../../lib/community-address"; import Logger from "@plebbit/plebbit-logger"; const log = Logger("bitsocial-react-hooks:feeds:stores"); @@ -41,12 +37,12 @@ const commentMatchesModQueue = (comment: Comment | undefined, modQueue?: string[ const getFeedPost = ( post: Comment, - subplebbitAddress: string, + communityAddress: string, modQueue?: string[], freshestComments?: { [commentCid: string]: Comment }, ) => { const freshestComment = post.cid ? freshestComments?.[post.cid] : undefined; - if (!areEquivalentSubplebbitAddresses(post.subplebbitAddress, subplebbitAddress)) { + if (!areEquivalentCommunityAddresses(post.communityAddress, communityAddress)) { return; } if (!commentMatchesModQueue(post, modQueue)) { @@ -58,7 +54,7 @@ const getFeedPost = ( if (!commentMatchesModQueue(freshestComment, modQueue)) { return; } - if (!areEquivalentSubplebbitAddresses(freshestComment.subplebbitAddress, subplebbitAddress)) { + if (!areEquivalentCommunityAddresses(freshestComment.communityAddress, communityAddress)) { return; } return freshestComment; @@ -107,19 +103,19 @@ const reconcileLoadedModQueueFeed = ( }; /** - * Calculate the feeds from all the loaded subplebbit pages, filter and sort them + * Calculate the feeds from all the loaded community pages, filter and sort them */ export const getFilteredSortedFeeds = ( feedsOptions: FeedsOptions, - subplebbits: Subplebbits, - subplebbitsPages: SubplebbitsPages, + communities: Communities, + communitiesPages: CommunitiesPages, accounts: Accounts, freshestComments?: { [commentCid: string]: Comment }, ) => { // calculate each feed let feeds: Feeds = {}; for (const feedName in feedsOptions) { - let { subplebbitAddresses, sortType, accountId, filter, newerThan, modQueue } = + let { communityAddresses, sortType, accountId, filter, newerThan, modQueue } = feedsOptions[feedName]; const newerThanTimestamp = newerThan ? Math.floor(Date.now() / 1000) - newerThan : undefined; @@ -134,47 +130,47 @@ export const getFilteredSortedFeeds = ( const bufferedFeedPosts = []; // add each comment from each page, do not filter at this stage, filter after sorting - for (const subplebbitAddress of subplebbitAddresses) { - // subplebbit hasn't loaded yet - if (!subplebbits[subplebbitAddress]) { + for (const communityAddress of communityAddresses) { + // community hasn't loaded yet + if (!communities[communityAddress]) { continue; } - // if cache is expired and has internet access, don't use, wait for next subplebbit update - if (subplebbitPostsCacheExpired(subplebbits[subplebbitAddress]) && window.navigator.onLine) { + // if cache is expired and has internet access, don't use, wait for next community update + if (communityPostsCacheExpired(communities[communityAddress]) && window.navigator.onLine) { continue; } - // use subplebbit preloaded posts if any - const preloadedPosts = getPreloadedPosts(subplebbits[subplebbitAddress], sortType); + // use community preloaded posts if any + const preloadedPosts = getPreloadedPosts(communities[communityAddress], sortType); if (preloadedPosts) { for (const post of preloadedPosts) { - // posts are manually validated, could have fake subplebbitAddress - if (!areEquivalentSubplebbitAddresses(post.subplebbitAddress, subplebbitAddress)) { + // posts are manually validated, could have fake communityAddress + if (!areEquivalentCommunityAddresses(post.communityAddress, communityAddress)) { break; } - const nextPost = getFeedPost(post, subplebbitAddress, modQueue, freshestComments); + const nextPost = getFeedPost(post, communityAddress, modQueue, freshestComments); if (nextPost) { bufferedFeedPosts.push(nextPost); } } } - // add all posts from subplebbit pages - const subplebbitPages = getSubplebbitPages( - subplebbits[subplebbitAddress], + // add all posts from community pages + const communityPages = getCommunityPages( + communities[communityAddress], sortType, - subplebbitsPages, + communitiesPages, pageType, ); - for (const subplebbitPage of subplebbitPages) { - if (subplebbitPage?.comments) { - for (const post of subplebbitPage.comments) { - // posts are manually validated, could have fake subplebbitAddress - if (!areEquivalentSubplebbitAddresses(post.subplebbitAddress, subplebbitAddress)) { + for (const communityPage of communityPages) { + if (communityPage?.comments) { + for (const post of communityPage.comments) { + // posts are manually validated, could have fake communityAddress + if (!areEquivalentCommunityAddresses(post.communityAddress, communityAddress)) { break; } - const nextPost = getFeedPost(post, subplebbitAddress, modQueue, freshestComments); + const nextPost = getFeedPost(post, communityAddress, modQueue, freshestComments); if (nextPost) { bufferedFeedPosts.push(nextPost); } @@ -192,7 +188,7 @@ export const getFilteredSortedFeeds = ( for (const post of sortedBufferedFeedPosts) { // address is blocked if ( - accounts[accountId]?.blockedAddresses[post.subplebbitAddress] || + accounts[accountId]?.blockedAddresses[post.communityAddress] || (post.author?.address && accounts[accountId]?.blockedAddresses[post.author.address]) ) { continue; @@ -205,7 +201,7 @@ export const getFilteredSortedFeeds = ( // if a feed has more than 1 sub, don't include pinned posts // TODO: add test to check if pinned are filtered - if (post.pinned && subplebbitAddresses.length > 1) { + if (post.pinned && communityAddresses.length > 1) { continue; } @@ -235,16 +231,16 @@ export const getFilteredSortedFeeds = ( return feeds; }; -const getPreloadedPosts = (subplebbit: Subplebbit, sortType: string) => { - let preloadedPosts = subplebbit.posts?.pages?.[sortType]?.comments; +const getPreloadedPosts = (community: Community, sortType: string) => { + let preloadedPosts = community.posts?.pages?.[sortType]?.comments; if (preloadedPosts) { return preloadedPosts; } - const hasPageCids = Object.keys(subplebbit.posts?.pageCids || {}).length !== 0; + const hasPageCids = Object.keys(community.posts?.pageCids || {}).length !== 0; if (hasPageCids) { return; } - const pages: any[] = Object.values(subplebbit.posts?.pages || {}); + const pages: any[] = Object.values(community.posts?.pages || {}); if (!pages.length) { return; } @@ -334,7 +330,7 @@ export const addAccountsComments = (feedsOptions: FeedsOptions, loadedFeeds: Fee const { accountId, accountComments: accountCommentsOptions, - subplebbitAddresses, + communityAddresses, } = feedsOptions[feedName]; const { newerThan, append } = accountCommentsOptions || {}; if (!newerThan) { @@ -344,7 +340,7 @@ export const addAccountsComments = (feedsOptions: FeedsOptions, loadedFeeds: Fee newerThan === Infinity ? 0 : Math.floor(Date.now() / 1000) - newerThan; const isNewerThan = (post: Comment) => post.timestamp > newerThanTimestamp; - const subplebbitAddressesSet = new Set(subplebbitAddresses); + const communityAddressesSet = new Set(communityAddresses); const accountComments = accountsComments[accountId] || []; const accountPosts = accountComments.filter((comment) => { // is a reply, not a post @@ -354,7 +350,7 @@ export const addAccountsComments = (feedsOptions: FeedsOptions, loadedFeeds: Fee if (!isNewerThan(comment)) { return false; } - return subplebbitAddressesSet.has(comment.subplebbitAddress); + return communityAddressesSet.has(comment.communityAddress); }); const validAccountIndices = new Set(accountPosts.map((p) => p.index)); const accountCidToPost = new Map(); @@ -525,63 +521,62 @@ export const getUpdatedFeeds = async ( return newUpdatedFeeds; }; -// find with subplebbits have posts newer (or ranked higher) than the loaded feeds +// find with communities have posts newer (or ranked higher) than the loaded feeds // can be used to display "new posts in x, y, z subs" alert, like on twitter -export const getFeedsSubplebbitAddressesWithNewerPosts = ( +export const getFeedsCommunityAddressesWithNewerPosts = ( filteredSortedFeeds: Feeds, loadedFeeds: Feeds, - previousFeedsSubplebbitAddressesWithNewerPosts: { [feedName: string]: string[] }, + previousFeedsCommunityAddressesWithNewerPosts: { [feedName: string]: string[] }, ) => { - const feedsSubplebbitAddressesWithNewerPosts: { [feedName: string]: string[] } = {}; + const feedsCommunityAddressesWithNewerPosts: { [feedName: string]: string[] } = {}; for (const feedName in loadedFeeds) { const loadedFeed = loadedFeeds[feedName]; const cidsInLoadedFeed = new Set(); for (const post of loadedFeed) { cidsInLoadedFeed.add(post.cid); } - const subplebbitAddressesWithNewerPostsSet = new Set(); + const communityAddressesWithNewerPostsSet = new Set(); for (const [i, post] of filteredSortedFeeds[feedName].entries()) { if (i >= loadedFeed.length) { break; } // if any post in filteredSortedFeeds ranks higher than the loaded feed count, it's a newer post if (!cidsInLoadedFeed.has(post.cid)) { - subplebbitAddressesWithNewerPostsSet.add(post.subplebbitAddress); + communityAddressesWithNewerPostsSet.add(post.communityAddress); } } - const subplebbitAddressesWithNewerPosts = [...subplebbitAddressesWithNewerPostsSet]; + const communityAddressesWithNewerPosts = [...communityAddressesWithNewerPostsSet]; // don't update the array if the data is the same to avoid rerenders - const previousSubplebbitAddressesWithNewerPosts = - previousFeedsSubplebbitAddressesWithNewerPosts[feedName] || []; + const previousCommunityAddressesWithNewerPosts = + previousFeedsCommunityAddressesWithNewerPosts[feedName] || []; if ( - subplebbitAddressesWithNewerPosts.length === - previousSubplebbitAddressesWithNewerPosts.length && - subplebbitAddressesWithNewerPosts.toString() === - previousSubplebbitAddressesWithNewerPosts.toString() + communityAddressesWithNewerPosts.length === previousCommunityAddressesWithNewerPosts.length && + communityAddressesWithNewerPosts.toString() === + previousCommunityAddressesWithNewerPosts.toString() ) { - feedsSubplebbitAddressesWithNewerPosts[feedName] = - previousFeedsSubplebbitAddressesWithNewerPosts[feedName]; + feedsCommunityAddressesWithNewerPosts[feedName] = + previousFeedsCommunityAddressesWithNewerPosts[feedName]; } else { - feedsSubplebbitAddressesWithNewerPosts[feedName] = subplebbitAddressesWithNewerPosts; + feedsCommunityAddressesWithNewerPosts[feedName] = communityAddressesWithNewerPosts; } } - return feedsSubplebbitAddressesWithNewerPosts; + return feedsCommunityAddressesWithNewerPosts; }; -// find how many posts are left in each subplebbits in a buffereds feeds -export const getFeedsSubplebbitsPostCounts = (feedsOptions: FeedsOptions, feeds: Feeds) => { - const feedsSubplebbitsPostCounts: FeedsSubplebbitsPostCounts = {}; +// find how many posts are left in each communities in a buffereds feeds +export const getFeedsCommunitiesPostCounts = (feedsOptions: FeedsOptions, feeds: Feeds) => { + const feedsCommunitiesPostCounts: FeedsCommunitiesPostCounts = {}; for (const feedName in feedsOptions) { - feedsSubplebbitsPostCounts[feedName] = {}; - for (const subplebbitAddress of feedsOptions[feedName].subplebbitAddresses) { - feedsSubplebbitsPostCounts[feedName][subplebbitAddress] = 0; + feedsCommunitiesPostCounts[feedName] = {}; + for (const communityAddress of feedsOptions[feedName].communityAddresses) { + feedsCommunitiesPostCounts[feedName][communityAddress] = 0; } for (const comment of feeds[feedName] || []) { - feedsSubplebbitsPostCounts[feedName][comment.subplebbitAddress]++; + feedsCommunitiesPostCounts[feedName][comment.communityAddress]++; } } - return feedsSubplebbitsPostCounts; + return feedsCommunitiesPostCounts; }; /** @@ -590,8 +585,8 @@ export const getFeedsSubplebbitsPostCounts = (feedsOptions: FeedsOptions, feeds: export const getFeedsHaveMore = ( feedsOptions: FeedsOptions, bufferedFeeds: Feeds, - subplebbits: Subplebbits, - subplebbitsPages: SubplebbitsPages, + communities: Communities, + communitiesPages: CommunitiesPages, accounts: Accounts, ) => { const feedsHaveMore: { [feedName: string]: boolean } = {}; @@ -602,7 +597,7 @@ export const getFeedsHaveMore = ( continue feedsLoop; } - let { subplebbitAddresses, sortType, accountId, modQueue } = feedsOptions[feedName]; + let { communityAddresses, sortType, accountId, modQueue } = feedsOptions[feedName]; let pageType = "posts"; if (modQueue?.[0]) { @@ -611,33 +606,33 @@ export const getFeedsHaveMore = ( pageType = "modQueue"; } - subplebbitAddressesLoop: for (const subplebbitAddress of subplebbitAddresses) { + communityAddressesLoop: for (const communityAddress of communityAddresses) { // don't consider the sub if the address is blocked - if (accounts[accountId]?.blockedAddresses[subplebbitAddress]) { - continue subplebbitAddressesLoop; + if (accounts[accountId]?.blockedAddresses[communityAddress]) { + continue communityAddressesLoop; } - const subplebbit = subplebbits[subplebbitAddress]; - // if at least 1 subplebbit hasn't loaded yet, then the feed still has more - if (!subplebbit?.updatedAt) { + const community = communities[communityAddress]; + // if at least 1 community hasn't loaded yet, then the feed still has more + if (!community?.updatedAt) { feedsHaveMore[feedName] = true; continue feedsLoop; } - // if at least 1 subplebbit has posts cache expired, then the feed still has more - if (subplebbitPostsCacheExpired(subplebbit)) { + // if at least 1 community has posts cache expired, then the feed still has more + if (communityPostsCacheExpired(community)) { feedsHaveMore[feedName] = true; continue feedsLoop; } - const firstPageCid = getSubplebbitFirstPageCid(subplebbit, sortType, pageType); - // TODO: if a loaded subplebbit doesn't have a first page, it's unclear what we should do + const firstPageCid = getCommunityFirstPageCid(community, sortType, pageType); + // TODO: if a loaded community doesn't have a first page, it's unclear what we should do // should we try to use another sort type by default, like 'hot', or should we just ignore it? // 'continue' to ignore it for now if (!firstPageCid) { - continue subplebbitAddressesLoop; + continue communityAddressesLoop; } - const pages = getSubplebbitPages(subplebbit, sortType, subplebbitsPages, pageType); + const pages = getCommunityPages(community, sortType, communitiesPages, pageType); // if first page isn't loaded yet, then the feed still has more if (!pages.length) { feedsHaveMore[feedName] = true; @@ -650,111 +645,109 @@ export const getFeedsHaveMore = ( } } - // if buffered feeds are empty and no last page of any subplebbit has a next page, then has more is false + // if buffered feeds are empty and no last page of any community has a next page, then has more is false feedsHaveMore[feedName] = false; } return feedsHaveMore; }; -// get all subplebbits pages cids of all feeds, use to check if a subplebbitsStore change should trigger updateFeeds -export const getFeedsSubplebbits = (feedsOptions: FeedsOptions, subplebbits: Subplebbits) => { - // find all feeds subplebbits - const feedsSubplebbitAddresses = new Set(); +// get all communities pages cids of all feeds, use to check if a communitiesStore change should trigger updateFeeds +export const getFeedsCommunities = (feedsOptions: FeedsOptions, communities: Communities) => { + // find all feeds communities + const feedsCommunityAddresses = new Set(); Object.keys(feedsOptions).forEach((i) => - feedsOptions[i].subplebbitAddresses.forEach((a) => feedsSubplebbitAddresses.add(a)), + feedsOptions[i].communityAddresses.forEach((a) => feedsCommunityAddresses.add(a)), ); // use map for performance increase when checking size - const feedsSubplebbits = new Map(); - for (const subplebbitAddress of feedsSubplebbitAddresses) { - feedsSubplebbits.set(subplebbitAddress, subplebbits[subplebbitAddress]); + const feedsCommunities = new Map(); + for (const communityAddress of feedsCommunityAddresses) { + feedsCommunities.set(communityAddress, communities[communityAddress]); } - return feedsSubplebbits; + return feedsCommunities; }; -export const feedsSubplebbitsChanged = ( - previousFeedsSubplebbits: Map, - feedsSubplebbits: Map, +export const feedsCommunitiesChanged = ( + previousFeedsCommunities: Map, + feedsCommunities: Map, ) => { - if (previousFeedsSubplebbits.size !== feedsSubplebbits.size) { + if (previousFeedsCommunities.size !== feedsCommunities.size) { return true; } - for (let subplebbitAddress of previousFeedsSubplebbits.keys()) { + for (let communityAddress of previousFeedsCommunities.keys()) { // check if the object is still the same - if ( - previousFeedsSubplebbits.get(subplebbitAddress) !== feedsSubplebbits.get(subplebbitAddress) - ) { + if (previousFeedsCommunities.get(communityAddress) !== feedsCommunities.get(communityAddress)) { return true; } } return false; }; -// get all subplebbits pages cids of all feeds, use to check if a subplebbitsStore change should trigger updateFeeds -export const getFeedsSubplebbitsFirstPageCids = ( - feedsSubplebbits: Map, +// get all communities pages cids of all feeds, use to check if a communitiesStore change should trigger updateFeeds +export const getFeedsCommunitiesFirstPageCids = ( + feedsCommunities: Map, ): string[] => { - // find all the feeds subplebbits first page cids - const feedsSubplebbitsFirstPageCids = new Set(); - for (const subplebbit of feedsSubplebbits.values()) { - if (!subplebbit?.posts && !subplebbit?.modQueue) { + // find all the feeds communities first page cids + const feedsCommunitiesFirstPageCids = new Set(); + for (const community of feedsCommunities.values()) { + if (!community?.posts && !community?.modQueue) { continue; } // check pages - if (subplebbit.posts?.pages) { - for (const page of Object.values(subplebbit.posts.pages)) { + if (community.posts?.pages) { + for (const page of Object.values(community.posts.pages)) { if (page?.nextCid) { - feedsSubplebbitsFirstPageCids.add(page?.nextCid); + feedsCommunitiesFirstPageCids.add(page?.nextCid); } } } // check pageCids - if (subplebbit.posts?.pageCids) { - for (const pageCid of Object.values(subplebbit.posts.pageCids)) { + if (community.posts?.pageCids) { + for (const pageCid of Object.values(community.posts.pageCids)) { if (pageCid) { - feedsSubplebbitsFirstPageCids.add(pageCid); + feedsCommunitiesFirstPageCids.add(pageCid); } } } // TODO: would be more performant to only check modQueue if there's a feedOptions with modQueue - if (subplebbit.modQueue?.pageCids) { - for (const pageCid of Object.values(subplebbit.modQueue.pageCids)) { + if (community.modQueue?.pageCids) { + for (const pageCid of Object.values(community.modQueue.pageCids)) { if (pageCid) { - feedsSubplebbitsFirstPageCids.add(pageCid); + feedsCommunitiesFirstPageCids.add(pageCid); } } } } - return [...feedsSubplebbitsFirstPageCids].sort(); + return [...feedsCommunitiesFirstPageCids].sort(); }; -// get all subplebbits posts pages first post updatedAts, use to check if a subplebbitsStore change should trigger updateFeeds -export const getFeedsSubplebbitsPostsPagesFirstUpdatedAts = ( - feedsSubplebbits: Map, +// get all communities posts pages first post updatedAts, use to check if a communitiesStore change should trigger updateFeeds +export const getFeedsCommunitiesPostsPagesFirstUpdatedAts = ( + feedsCommunities: Map, ): string => { - let feedsSubplebbitsPostsPagesFirstUpdatedAts = ""; - for (const subplebbit of feedsSubplebbits.values()) { - for (const page of Object.values(subplebbit?.posts?.pages || {})) { + let feedsCommunitiesPostsPagesFirstUpdatedAts = ""; + for (const community of feedsCommunities.values()) { + for (const page of Object.values(community?.posts?.pages || {})) { if (page?.comments?.[0]?.updatedAt) { - feedsSubplebbitsPostsPagesFirstUpdatedAts += + feedsCommunitiesPostsPagesFirstUpdatedAts += page.comments[0].cid + page.comments[0].updatedAt; } } } - return feedsSubplebbitsPostsPagesFirstUpdatedAts; + return feedsCommunitiesPostsPagesFirstUpdatedAts; }; -// get number of feeds subplebbit that are loaded -export const getFeedsSubplebbitsLoadedCount = ( - feedsSubplebbits: Map, +// get number of feeds community that are loaded +export const getFeedsCommunitiesLoadedCount = ( + feedsCommunities: Map, ): number => { let count = 0; - for (const subplebbit of feedsSubplebbits.values()) { - if (subplebbit?.updatedAt) { + for (const community of feedsCommunities.values()) { + if (community?.updatedAt) { count++; } } @@ -801,13 +794,13 @@ export const feedsHaveChangedBlockedAddresses = ( .concat(previousBlockedAddresses.filter((x) => !blockedAddresses.includes(x))); // if changed blocked addresses arent used in the feeds, do nothing - const feedsSubplebbitAddresses = new Set(); + const feedsCommunityAddresses = new Set(); Object.keys(feedsOptions).forEach((i) => - feedsOptions[i].subplebbitAddresses.forEach((a) => feedsSubplebbitAddresses.add(a)), + feedsOptions[i].communityAddresses.forEach((a) => feedsCommunityAddresses.add(a)), ); for (const address of changedBlockedAddresses) { // a changed address is used in the feed, update feeds - if (feedsSubplebbitAddresses.has(address)) { + if (feedsCommunityAddresses.has(address)) { return true; } } diff --git a/src/stores/replies-pages/index.ts b/src/stores/replies-pages/index.ts index e9a327d0..b295d5e0 100644 --- a/src/stores/replies-pages/index.ts +++ b/src/stores/replies-pages/index.ts @@ -1,3 +1,3 @@ -import repliesPagesStore from './replies-pages-store' -export * from './replies-pages-store' -export default repliesPagesStore +import repliesPagesStore from "./replies-pages-store"; +export * from "./replies-pages-store"; +export default repliesPagesStore; diff --git a/src/stores/replies-pages/replies-pages-store.test.ts b/src/stores/replies-pages/replies-pages-store.test.ts index 30756210..b74fdae6 100644 --- a/src/stores/replies-pages/replies-pages-store.test.ts +++ b/src/stores/replies-pages/replies-pages-store.test.ts @@ -14,15 +14,15 @@ import localForageLru from "../../lib/localforage-lru"; const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); class MockPages { - subplebbitAddress: string; + communityAddress: string; pageCids: { [pageCid: string]: string }; pages: { [sortType: string]: RepliesPage }; - constructor({ subplebbitAddress }: any) { - this.subplebbitAddress = subplebbitAddress; + constructor({ communityAddress }: any) { + this.communityAddress = communityAddress; this.pageCids = { - new: `${subplebbitAddress} new page cid`, + new: `${communityAddress} new page cid`, }; - const bestPageCid = `${subplebbitAddress} best page cid`; + const bestPageCid = `${communityAddress} best page cid`; this.pages = { best: { nextCid: bestPageCid + " - next page cid", @@ -49,7 +49,7 @@ class MockPages { comments.push({ timestamp: index, cid: pageCid + " comment cid " + index, - subplebbitAddress: this.subplebbitAddress, + communityAddress: this.communityAddress, updatedAt: index, }); } @@ -59,25 +59,25 @@ class MockPages { async validatePage(page: any) {} } -class MockSubplebbit extends EventEmitter { +class MockCommunity extends EventEmitter { address: string; posts: MockPages; constructor({ address }: any) { super(); this.address = address; - this.posts = new MockPages({ subplebbitAddress: address }); + this.posts = new MockPages({ communityAddress: address }); } } class MockComment extends EventEmitter { cid: string; - subplebbitAddress: string; + communityAddress: string; replies: MockPages; constructor({ cid }: any) { super(); this.cid = cid; - this.subplebbitAddress = `${cid} subplebbit address`; - this.replies = new MockPages({ subplebbitAddress: this.subplebbitAddress }); + this.communityAddress = `${cid} community address`; + this.replies = new MockPages({ communityAddress: this.communityAddress }); } async update() {} } @@ -85,7 +85,7 @@ class MockComment extends EventEmitter { const mockAccount: any = { id: "mock account id", plebbit: { - createSubplebbit: async ({ address }: any) => new MockSubplebbit({ address }), + createCommunity: async ({ address }: any) => new MockCommunity({ address }), createComment: async ({ cid }: any) => new MockComment({ cid }), }, }; @@ -120,7 +120,7 @@ describe("replies pages store", () => { replies: { pages: { best: { - comments: [{ cid: "db-c1", timestamp: 1, subplebbitAddress: "s1" }], + comments: [{ cid: "db-c1", timestamp: 1, communityAddress: "s1" }], }, }, }, @@ -152,7 +152,7 @@ describe("replies pages store", () => { }); test("addRepliesPageCommentsToStore skips when existing fresher (branch 116, 175)", async () => { - const staleComment = { cid: "stale-c1", timestamp: 1, subplebbitAddress: "s1" }; + const staleComment = { cid: "stale-c1", timestamp: 1, communityAddress: "s1" }; act(() => { rendered.result.current.addRepliesPageCommentsToStore({ cid: "tmp", @@ -168,7 +168,7 @@ describe("replies pages store", () => { replies: { pages: { best: { - comments: [{ cid: "stale-c1", timestamp: 1, subplebbitAddress: "s1" }], + comments: [{ cid: "stale-c1", timestamp: 1, communityAddress: "s1" }], }, }, }, @@ -184,7 +184,7 @@ describe("replies pages store", () => { replies: { pages: { best: { - comments: [{ cid: "c1", timestamp: 1, subplebbitAddress: "s1" }], + comments: [{ cid: "c1", timestamp: 1, communityAddress: "s1" }], }, }, }, @@ -222,7 +222,7 @@ describe("replies pages store", () => { cid: firstCommentCid, timestamp: 0, updatedAt: 0, - subplebbitAddress: (this as any).subplebbitAddress, + communityAddress: (this as any).communityAddress, }, ], }; @@ -410,7 +410,7 @@ describe("replies pages store", () => { const commentWithoutUpdatedAt = { cid: "no-updated-at-cid", timestamp: 5, - subplebbitAddress: "test-sub", + communityAddress: "test-sub", }; const comment = { cid: "parent-cid", @@ -437,7 +437,7 @@ describe("replies pages store", () => { test("addNextRepliesPageToStore returns early when no repliesFirstPageCid", async () => { const commentWithoutReplies = { cid: "no-replies-cid", - subplebbitAddress: "sub1", + communityAddress: "sub1", replies: {}, }; const sortType = "new"; @@ -458,8 +458,8 @@ describe("replies pages store", () => { pages: { best: { comments: [ - { cid: "c1", timestamp: 1, subplebbitAddress: "sub1" }, - { timestamp: 2, subplebbitAddress: "sub1" }, + { cid: "c1", timestamp: 1, communityAddress: "sub1" }, + { timestamp: 2, communityAddress: "sub1" }, ], }, }, @@ -495,8 +495,8 @@ describe("replies pages store", () => { return { nextCid: options?.cid + " - next page cid", comments: [ - { cid: "valid-cid", timestamp: 1, subplebbitAddress: mockComment.subplebbitAddress }, - { timestamp: 2, subplebbitAddress: mockComment.subplebbitAddress }, + { cid: "valid-cid", timestamp: 1, communityAddress: mockComment.communityAddress }, + { timestamp: 2, communityAddress: mockComment.communityAddress }, ], }; }; @@ -522,7 +522,7 @@ describe("replies pages store", () => { cid: "existing-cid", timestamp: 100, updatedAt: 100, - subplebbitAddress: "sub1", + communityAddress: "sub1", }, ], }, @@ -547,7 +547,7 @@ describe("replies pages store", () => { const firstPageCid = mockComment.replies.pageCids[sortType]; const cachedPage = { nextCid: firstPageCid + " - next", - comments: [{ cid: "cached-1", subplebbitAddress: mockComment.subplebbitAddress }], + comments: [{ cid: "cached-1", communityAddress: mockComment.communityAddress }], }; const db = localForageLru.createInstance({ name: "plebbitReactHooks-repliesPages" }); await db.setItem(firstPageCid, cachedPage); @@ -667,7 +667,7 @@ describe("replies pages store", () => { cid: "shared-cid", timestamp: 100, updatedAt: 100, - subplebbitAddress: "test-sub", + communityAddress: "test-sub", }; const commentWithFresher = { cid: "parent-1", @@ -691,7 +691,7 @@ describe("replies pages store", () => { const olderComment = { cid: "shared-cid", timestamp: 1, - subplebbitAddress: "test-sub", + communityAddress: "test-sub", }; const commentWithOlder = { cid: "parent-2", diff --git a/src/stores/replies-pages/replies-pages-store.ts b/src/stores/replies-pages/replies-pages-store.ts index 2160ebae..fab80943 100644 --- a/src/stores/replies-pages/replies-pages-store.ts +++ b/src/stores/replies-pages/replies-pages-store.ts @@ -41,7 +41,7 @@ const repliesPagesStore = createStore( `repliesPagesStore.addNextRepliesPageToStore sortType '${sortType}' invalid`, ); assert( - typeof account?.plebbit?.createSubplebbit === "function", + typeof account?.plebbit?.createCommunity === "function", `repliesPagesStore.addNextRepliesPageToStore account '${account}' invalid`, ); @@ -94,7 +94,7 @@ const repliesPagesStore = createStore( pageCid: pageCidToAdd, page, commentCid: comment.cid, - subplebbitAddress: comment.subplebbitAddress, + communityAddress: comment.communityAddress, account, }); } catch (e) { @@ -227,12 +227,12 @@ const fetchPage = async (pageCid: string, comment: Comment, account: Account) => fetchPageComments[comment.cid] = await account.plebbit.createComment({ cid: comment.cid, postCid: comment.postCid, - subplebbitAddress: comment.subplebbitAddress, + communityAddress: comment.communityAddress, depth: comment.depth, }); listeners.push(fetchPageComments[comment.cid]); - // set clients states on subplebbits store so the frontend can display it + // set clients states on communities store so the frontend can display it utils.pageClientsOnStateChange( fetchPageComments[comment.cid].replies?.clients, onCommentRepliesClientsStateChange(comment.cid), diff --git a/src/stores/replies-pages/utils.test.ts b/src/stores/replies-pages/utils.test.ts index ae74da4a..4e4026b3 100644 --- a/src/stores/replies-pages/utils.test.ts +++ b/src/stores/replies-pages/utils.test.ts @@ -43,7 +43,7 @@ describe("replies-pages utils", () => { { cid: "reply-1", depth: 1, - subplebbitAddress: "sub1", + communityAddress: "sub1", replies: { pages: { best: { comments: [{ cid: "nested-1", depth: 2 }] }, @@ -62,7 +62,7 @@ describe("replies-pages utils", () => { test("addChildrenRepliesFeedsToAddToStore uses getSortTypeFromPage fallback when no nested comments", () => { const page = { - comments: [{ cid: "reply-1", depth: 1, subplebbitAddress: "sub1" }], + comments: [{ cid: "reply-1", depth: 1, communityAddress: "sub1" }], }; const comment = { cid: "parent-cid", depth: 0 }; diff --git a/src/stores/replies/index.ts b/src/stores/replies/index.ts index 27cfa947..6b293eea 100644 --- a/src/stores/replies/index.ts +++ b/src/stores/replies/index.ts @@ -1,3 +1,3 @@ -import repliesStore from './replies-store' -export * from './replies-store' -export default repliesStore +import repliesStore from "./replies-store"; +export * from "./replies-store"; +export default repliesStore; diff --git a/src/stores/replies/replies-store.test.ts b/src/stores/replies/replies-store.test.ts index b69f0aea..322949dc 100644 --- a/src/stores/replies/replies-store.test.ts +++ b/src/stores/replies/replies-store.test.ts @@ -18,12 +18,12 @@ const getPageCommentCount = 100; const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); class MockPages { - subplebbitAddress: string; + communityAddress: string; pageCids: { [pageCid: string]: string }; - constructor({ subplebbitAddress }: any) { - this.subplebbitAddress = subplebbitAddress; + constructor({ communityAddress }: any) { + this.communityAddress = communityAddress; this.pageCids = { - new: `${subplebbitAddress} new page cid`, + new: `${communityAddress} new page cid`, }; } @@ -46,20 +46,20 @@ class MockPages { comments.push({ timestamp: index, cid: pageCid + " comment cid " + index, - subplebbitAddress: this.subplebbitAddress, + communityAddress: this.communityAddress, }); } return comments; } } -class MockSubplebbit extends EventEmitter { +class MockCommunity extends EventEmitter { address: string; posts: MockPages; constructor({ address }: any) { super(); this.address = address; - this.posts = new MockPages({ subplebbitAddress: address }); + this.posts = new MockPages({ communityAddress: address }); } async update() {} } @@ -68,13 +68,13 @@ class MockComment extends EventEmitter { cid: string; replies: MockPages; postCid: string; - subplebbitAddress: string; + communityAddress: string; constructor({ cid }: any) { super(); this.cid = cid; this.postCid = "post cid"; - this.subplebbitAddress = `${cid} subplebbit address`; - this.replies = new MockPages({ subplebbitAddress: this.subplebbitAddress }); + this.communityAddress = `${cid} community address`; + this.replies = new MockPages({ communityAddress: this.communityAddress }); } async update() {} } @@ -82,11 +82,11 @@ class MockComment extends EventEmitter { const mockAccount: any = { id: "mock account id", plebbit: { - createSubplebbit: async ({ address }: any) => new MockSubplebbit({ address }), + createCommunity: async ({ address }: any) => new MockCommunity({ address }), createComment: async ({ cid }: any) => new MockComment({ cid }), - getSubplebbit: async (options: { address: string }) => - new MockSubplebbit({ address: options?.address }), - subplebbits: [], + getCommunity: async (options: { address: string }) => + new MockCommunity({ address: options?.address }), + communities: [], async validateComment(comment: any) {}, }, blockedAddresses: {}, @@ -335,13 +335,13 @@ describe("replies store", () => { const commentCid = "skip-validation-cid"; const comment = { cid: commentCid, - subplebbitAddress: subAddr, + communityAddress: subAddr, replies: { pages: { new: { comments: [ - { cid: "r1", subplebbitAddress: subAddr, timestamp: 1 }, - { cid: "r2", subplebbitAddress: subAddr, timestamp: 2 }, + { cid: "r1", communityAddress: subAddr, timestamp: 1 }, + { cid: "r2", communityAddress: subAddr, timestamp: 2 }, ], nextCid: "next-page-cid", }, diff --git a/src/stores/replies/replies-store.ts b/src/stores/replies/replies-store.ts index b61444bb..9ab2a2fa 100644 --- a/src/stores/replies/replies-store.ts +++ b/src/stores/replies/replies-store.ts @@ -140,7 +140,7 @@ const repliesStore = createStore((setState: Function, getState: Fu ); const account = accountsStore.getState().accounts[feedOptions.accountId]; assert( - typeof account?.plebbit?.getSubplebbit === "function", + typeof account?.plebbit?.getCommunity === "function", `repliesStore.addFeedToStoreOrUpdateComment feedOptions.accountId '${feedOptions.accountId}' invalid`, ); assert( @@ -432,7 +432,7 @@ const addRepliesPagesOnLowBufferedFeedsReplyCounts = (repliesStoreState: any) => let sortType = feedsOptions[feedName].sortType; const commentCid = feedsOptions[feedName].commentCid; - // TODO: maybe skip if comment subplebbit address, comment cid or comment author is blocked? + // TODO: maybe skip if comment community address, comment cid or comment author is blocked? // comment hasn't loaded yet if (!comments[commentCid]) { diff --git a/src/stores/replies/utils.test.ts b/src/stores/replies/utils.test.ts index 973b3b11..ba809879 100644 --- a/src/stores/replies/utils.test.ts +++ b/src/stores/replies/utils.test.ts @@ -18,13 +18,13 @@ describe("replies utils", () => { const feedName = "feed1"; const preloadedReply = { cid: "preloaded-reply-cid", - subplebbitAddress: "sub1", + communityAddress: "sub1", timestamp: 100, }; const comments = { comment1: { cid: "comment1", - subplebbitAddress: "sub1", + communityAddress: "sub1", updatedAt: 1, replies: { pages: { new: { comments: [preloadedReply] } }, @@ -53,7 +53,7 @@ describe("replies utils", () => { const comments = { comment1: { cid: "comment1", - subplebbitAddress: "sub1", + communityAddress: "sub1", depth: 0, updatedAt: 1, replies: { @@ -74,7 +74,7 @@ describe("replies utils", () => { comments, { "page-cid-1": { - comments: [{ cid: "r1", subplebbitAddress: "sub1" }], + comments: [{ cid: "r1", communityAddress: "sub1" }], nextCid: undefined, }, }, @@ -86,13 +86,13 @@ describe("replies utils", () => { test("uses fallback when depth > 0 and hasPageCids (no early return)", () => { const reply = { cid: "depth1-fallback", - subplebbitAddress: "sub1", + communityAddress: "sub1", timestamp: 1, }; const comments = { comment1: { cid: "comment1", - subplebbitAddress: "sub1", + communityAddress: "sub1", depth: 1, updatedAt: 1, replies: { @@ -119,12 +119,12 @@ describe("replies utils", () => { expect(feeds.feed1).toContainEqual(expect.objectContaining({ cid: "depth1-fallback" })); }); - test("breaks repliesPages loop when reply has wrong subplebbitAddress", () => { + test("breaks repliesPages loop when reply has wrong communityAddress", () => { const pageCid = "page-cid-break"; const comments = { comment1: { cid: "comment1", - subplebbitAddress: "sub1", + communityAddress: "sub1", updatedAt: 1, replies: { pageCids: { new: pageCid }, @@ -135,8 +135,8 @@ describe("replies utils", () => { const repliesPages = { [pageCid]: { comments: [ - { cid: "r1", subplebbitAddress: "sub1", timestamp: 1 }, - { cid: "r2", subplebbitAddress: "wrong-sub", timestamp: 2 }, + { cid: "r1", communityAddress: "sub1", timestamp: 1 }, + { cid: "r2", communityAddress: "wrong-sub", timestamp: 2 }, ], nextCid: undefined, }, @@ -155,18 +155,18 @@ describe("replies utils", () => { expect(feeds.feed1[0].cid).toBe("r1"); }); - test("breaks preloaded loop when reply has wrong subplebbitAddress", () => { + test("breaks preloaded loop when reply has wrong communityAddress", () => { const comments = { comment1: { cid: "comment1", - subplebbitAddress: "sub1", + communityAddress: "sub1", updatedAt: 1, replies: { pages: { new: { comments: [ - { cid: "r1", subplebbitAddress: "sub1", timestamp: 1 }, - { cid: "r2", subplebbitAddress: "other-sub", timestamp: 2 }, + { cid: "r1", communityAddress: "sub1", timestamp: 1 }, + { cid: "r2", communityAddress: "other-sub", timestamp: 2 }, ], }, }, @@ -190,18 +190,18 @@ describe("replies utils", () => { expect(feeds.feed1[0].cid).toBe("r1"); }); - test("keeps replies when comment and reply subplebbit addresses use .eth/.bso aliases", () => { + test("keeps replies when comment and reply community addresses use .eth/.bso aliases", () => { const comments = { comment1: { cid: "comment1", - subplebbitAddress: "music-posting.bso", + communityAddress: "music-posting.bso", updatedAt: 1, replies: { pages: { new: { comments: [ - { cid: "r1", subplebbitAddress: "music-posting.eth", timestamp: 1 }, - { cid: "r2", subplebbitAddress: "music-posting.eth", timestamp: 2 }, + { cid: "r1", communityAddress: "music-posting.eth", timestamp: 1 }, + { cid: "r2", communityAddress: "music-posting.eth", timestamp: 2 }, ], }, }, @@ -227,13 +227,13 @@ describe("replies utils", () => { test("fallback to any page when no pageCids, no nextCids, single preloaded page", () => { const reply = { cid: "fallback-reply", - subplebbitAddress: "sub1", + communityAddress: "sub1", timestamp: 1, }; const comments = { comment1: { cid: "comment1", - subplebbitAddress: "sub1", + communityAddress: "sub1", updatedAt: 1, replies: { pages: { @@ -289,14 +289,14 @@ describe("replies utils", () => { const loadedReply = { cid: "same-cid", index: 1, - subplebbitAddress: "sub1", + communityAddress: "sub1", timestamp: recentTs, }; const freshAccountReply = { cid: "same-cid", index: 2, parentCid: "c1", - subplebbitAddress: "sub1", + communityAddress: "sub1", timestamp: recentTs, }; (accountsStore as any).getState = () => ({ @@ -323,14 +323,14 @@ describe("replies utils", () => { const existingReply = { cid: "existing-cid", parentCid: "c1", - subplebbitAddress: "sub1", + communityAddress: "sub1", timestamp: recentTs - 1000, }; const accountReply = { cid: "append-cid", index: 1, parentCid: "c1", - subplebbitAddress: "sub1", + communityAddress: "sub1", timestamp: recentTs, }; (accountsStore as any).getState = () => ({ @@ -360,7 +360,7 @@ describe("replies utils", () => { cid: "child-cid", index: 1, parentCid: "parent-cid", - subplebbitAddress: "sub1", + communityAddress: "sub1", timestamp: recentTs, }; (accountsStore as any).getState = () => ({ @@ -391,7 +391,7 @@ describe("replies utils", () => { index: 1, postCid: "p1", depth: 1, - subplebbitAddress: "sub1", + communityAddress: "sub1", timestamp: recentTs, }; (accountsStore as any).getState = () => ({ @@ -418,14 +418,14 @@ describe("replies utils", () => { const pendingReply = { index: 1, parentCid: "c1", - subplebbitAddress: "sub1", + communityAddress: "sub1", timestamp: recentTs, }; const accountReplyWithCid = { cid: "new-cid", index: 1, parentCid: "c1", - subplebbitAddress: "sub1", + communityAddress: "sub1", timestamp: recentTs, }; (accountsStore as any).getState = () => ({ @@ -542,7 +542,7 @@ describe("replies utils", () => { }, }; const loadedFeeds = { - feed1: [{ cid: "r1", subplebbitAddress: "sub1", timestamp: 100 }], + feed1: [{ cid: "r1", communityAddress: "sub1", timestamp: 100 }], }; const bufferedFeeds = { feed1: [] }; const accounts = { [mockAccountId]: { plebbit: {} } }; @@ -563,9 +563,9 @@ describe("replies utils", () => { }; const loadedFeeds = { feed1: [] }; const bufferedReplies = [ - { cid: "r1", subplebbitAddress: "sub1", timestamp: 1 }, - { cid: "r2", subplebbitAddress: "sub1", timestamp: 2 }, - { cid: "r3", subplebbitAddress: "sub1", timestamp: 3 }, + { cid: "r1", communityAddress: "sub1", timestamp: 1 }, + { cid: "r2", communityAddress: "sub1", timestamp: 2 }, + { cid: "r3", communityAddress: "sub1", timestamp: 3 }, ]; const bufferedFeeds = { feed1: bufferedReplies }; const accounts = { [mockAccountId]: { plebbit: {} } }; @@ -589,7 +589,7 @@ describe("replies utils", () => { const feedsOptions = { [feedName]: { commentCid: "c1", accountId: mockAccountId }, }; - const loadedFeed = [{ cid: "r1", subplebbitAddress: "sub1", timestamp: 100, updatedAt: 100 }]; + const loadedFeed = [{ cid: "r1", communityAddress: "sub1", timestamp: 100, updatedAt: 100 }]; const loadedFeeds = { [feedName]: loadedFeed }; const updatedFeeds: Record = {}; const filteredSortedFeeds = { [feedName]: loadedFeed }; @@ -612,7 +612,7 @@ describe("replies utils", () => { }; const loadedReply = { cid: "r1", - subplebbitAddress: "sub1", + communityAddress: "sub1", timestamp: 100, updatedAt: 100, }; @@ -662,7 +662,7 @@ describe("replies utils", () => { }; const loadedReply = { cid: "r1", - subplebbitAddress: "sub1", + communityAddress: "sub1", timestamp: 100, updatedAt: 100, }; diff --git a/src/stores/replies/utils.ts b/src/stores/replies/utils.ts index 60132d8f..a50835d7 100644 --- a/src/stores/replies/utils.ts +++ b/src/stores/replies/utils.ts @@ -15,7 +15,7 @@ import { getRepliesPages, getRepliesFirstPageCid } from "../replies-pages"; import repliesSorter from "../feeds/feed-sorter"; import accountsStore from "../accounts"; import { flattenCommentsPages, commentIsValid, removeInvalidComments } from "../../lib/utils"; -import { areEquivalentSubplebbitAddresses } from "../../lib/subplebbit-address"; +import { areEquivalentCommunityAddresses } from "../../lib/community-address"; import Logger from "@plebbit/plebbit-logger"; const log = Logger("bitsocial-react-hooks:replies:stores"); @@ -45,10 +45,8 @@ export const getFilteredSortedFeeds = ( const preloadedReplies = getPreloadedReplies(comment, sortType); if (preloadedReplies) { for (const reply of preloadedReplies) { - // replies are manually validated, could have fake subplebbitAddress - if ( - !areEquivalentSubplebbitAddresses(reply.subplebbitAddress, comment.subplebbitAddress) - ) { + // replies are manually validated, could have fake communityAddress + if (!areEquivalentCommunityAddresses(reply.communityAddress, comment.communityAddress)) { break; } bufferedFeedReplies.push(reply); @@ -60,9 +58,9 @@ export const getFilteredSortedFeeds = ( for (const repliesPage of _repliesPages) { if (repliesPage?.comments) { for (const reply of repliesPage.comments) { - // replies are manually validated, could have fake subplebbitAddress + // replies are manually validated, could have fake communityAddress if ( - !areEquivalentSubplebbitAddresses(reply.subplebbitAddress, comment.subplebbitAddress) + !areEquivalentCommunityAddresses(reply.communityAddress, comment.communityAddress) ) { break; } @@ -82,7 +80,7 @@ export const getFilteredSortedFeeds = ( // filter the feed const filteredSortedBufferedFeedReplies = []; for (const reply of sortedBufferedFeedReplies) { - // TODO: maybe skip if comment subplebbit address, comment cid or comment author is blocked? + // TODO: maybe skip if comment community address, comment cid or comment author is blocked? // feedOptions filter function if (filter && !filter.filter(reply)) { diff --git a/src/stores/subplebbits-pages/index.ts b/src/stores/subplebbits-pages/index.ts deleted file mode 100644 index ae1d9ee6..00000000 --- a/src/stores/subplebbits-pages/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import subplebbitsPagesStore from './subplebbits-pages-store' -export * from './subplebbits-pages-store' -export default subplebbitsPagesStore diff --git a/src/stores/subplebbits-pages/subplebbits-pages-store.test.ts b/src/stores/subplebbits-pages/subplebbits-pages-store.test.ts deleted file mode 100644 index 3648d5d4..00000000 --- a/src/stores/subplebbits-pages/subplebbits-pages-store.test.ts +++ /dev/null @@ -1,838 +0,0 @@ -import { act, waitFor as tlWaitFor } from "@testing-library/react"; -import testUtils, { renderHook } from "../../lib/test-utils"; -import useSubplebbitsPagesStore, { - resetSubplebbitsPagesDatabaseAndStore, - resetSubplebbitsPagesStore, - getSubplebbitFirstPageCid, - getCommentFreshness, - log, -} from "./subplebbits-pages-store"; -import { SubplebbitPage } from "../../types"; -import subplebbitsStore from "../subplebbits"; -import accountsStore from "../accounts"; -import localForageLru from "../../lib/localforage-lru"; - -const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); - -class MockPages { - subplebbitAddress: string; - pageCids: { [pageCid: string]: string }; - pages: { [sortType: string]: SubplebbitPage }; - constructor({ subplebbitAddress }: any) { - this.subplebbitAddress = subplebbitAddress; - this.pageCids = { - new: `${subplebbitAddress} new page cid`, - }; - const hotPageCid = `${subplebbitAddress} hot page cid`; - this.pages = { - hot: { - nextCid: hotPageCid + " - next page cid", - comments: this.getPageMockComments(hotPageCid), - }, - }; - } - - async getPage(options: { cid: string }) { - const cid = options?.cid; - await sleep(200); - const page: SubplebbitPage = { - nextCid: cid + " - next page cid", - comments: this.getPageMockComments(cid), - }; - return page; - } - - getPageMockComments(pageCid: string) { - const commentCount = 100; - let index = 0; - const comments: any[] = []; - while (index++ < commentCount) { - comments.push({ - timestamp: index, - cid: pageCid + " comment cid " + index, - subplebbitAddress: this.subplebbitAddress, - updatedAt: index, - }); - } - return comments; - } -} - -class MockSubplebbit { - address: string; - posts: MockPages; - constructor({ address }: any) { - this.address = address; - this.posts = new MockPages({ subplebbitAddress: address }); - } - removeAllListeners() {} -} - -const mockAccount: any = { - id: "mock account id", - plebbit: { - createSubplebbit: async ({ address }: any) => new MockSubplebbit({ address }), - }, -}; - -describe("subplebbits pages store", () => { - beforeAll(() => { - testUtils.silenceReactWarnings(); - }); - afterAll(async () => { - testUtils.restoreAll(); - }); - - let rendered: any, waitFor: any; - beforeEach(async () => { - rendered = renderHook(() => useSubplebbitsPagesStore()); - waitFor = testUtils.createWaitFor(rendered); - }); - - afterEach(async () => { - await resetSubplebbitsPagesDatabaseAndStore(); - }); - - test("addNextSubplebbitPageToStore returns early when no subplebbitFirstPageCid", async () => { - const subplebbitWithoutPosts = { - address: "no-posts-address", - posts: {}, - }; - const sortType = "new"; - - await rendered.result.current.addNextSubplebbitPageToStore( - subplebbitWithoutPosts, - sortType, - mockAccount, - ); - - expect(Object.keys(rendered.result.current.subplebbitsPages).length).toBe(0); - }); - - test("addSubplebbitPageCommentsToStore returns early when no new comments", () => { - const subplebbitWithExistingComments = { - address: "existing-comments-addr", - posts: { - pages: { - hot: { - comments: [ - { - cid: "existing-hot-cid", - timestamp: 100, - updatedAt: 100, - subplebbitAddress: "existing-comments-addr", - }, - ], - }, - }, - }, - }; - - act(() => { - rendered.result.current.addSubplebbitPageCommentsToStore(subplebbitWithExistingComments); - }); - expect(rendered.result.current.comments["existing-hot-cid"]).toBeDefined(); - - act(() => { - rendered.result.current.addSubplebbitPageCommentsToStore(subplebbitWithExistingComments); - }); - expect(rendered.result.current.comments["existing-hot-cid"]).toBeDefined(); - }); - - test("addSubplebbitPageCommentsToStore returns early when no subplebbit.posts.pages", () => { - const subplebbitWithoutPages = { - address: "no-pages", - posts: {}, - }; - - act(() => { - rendered.result.current.addSubplebbitPageCommentsToStore(subplebbitWithoutPages); - }); - - expect(Object.keys(rendered.result.current.comments).length).toBe(0); - }); - - test("initial store", async () => { - expect(rendered.result.current.subplebbitsPages).toEqual({}); - expect(typeof rendered.result.current.addNextSubplebbitPageToStore).toBe("function"); - expect(typeof rendered.result.current.invalidateSubplebbitPages).toBe("function"); - }); - - test("invalidateSubplebbitPages returns early when no subplebbitFirstPageCid", async () => { - const subplebbitWithoutPosts = { - address: "no-pages-address", - posts: {}, - }; - - await act(async () => { - await rendered.result.current.invalidateSubplebbitPages(subplebbitWithoutPosts, "new"); - }); - - expect(rendered.result.current.subplebbitsPages).toEqual({}); - }); - - test("resetSubplebbitsPagesDatabaseAndStore clears database and store", async () => { - act(() => { - rendered.result.current.addSubplebbitPageCommentsToStore({ - address: "db-reset-addr", - posts: { - pages: { - hot: { - comments: [{ cid: "db-c1", timestamp: 1, subplebbitAddress: "s1" }], - }, - }, - }, - }); - }); - await waitFor(() => rendered.result.current.comments["db-c1"]); - await resetSubplebbitsPagesDatabaseAndStore(); - const state = useSubplebbitsPagesStore.getState(); - expect(state.comments["db-c1"]).toBeUndefined(); - expect(state.subplebbitsPages).toEqual({}); - }); - - test("resetSubplebbitsPagesStore after addNextSubplebbitPageToStore clears listeners and state", async () => { - const mockSubplebbit = await mockAccount.plebbit.createSubplebbit({ - address: "reset-listener-sub", - }); - const sortType = "new"; - const firstPageCid = mockSubplebbit.posts.pageCids[sortType]; - - act(() => { - rendered.result.current.addNextSubplebbitPageToStore(mockSubplebbit, sortType, mockAccount); - }); - await waitFor( - () => rendered.result.current.subplebbitsPages[firstPageCid]?.comments?.length === 100, - ); - - await resetSubplebbitsPagesStore(); - const state = useSubplebbitsPagesStore.getState(); - expect(state.subplebbitsPages).toEqual({}); - expect(state.comments).toEqual({}); - }); - - test("resetSubplebbitsPagesStore clears store state", async () => { - act(() => { - rendered.result.current.addSubplebbitPageCommentsToStore({ - address: "tmp", - posts: { - pages: { - hot: { - comments: [{ cid: "c1", timestamp: 1, subplebbitAddress: "s1" }], - }, - }, - }, - }); - }); - expect(rendered.result.current.comments["c1"]).toBeDefined(); - await resetSubplebbitsPagesStore(); - const state = useSubplebbitsPagesStore.getState(); - expect(state.comments["c1"]).toBeUndefined(); - expect(state.subplebbitsPages).toEqual({}); - }); - - test("invalidateSubplebbitPages clears stored page chains for posts", async () => { - const firstPageCid = "invalidate-posts-page-1"; - const secondPageCid = "invalidate-posts-page-2"; - const db = localForageLru.createInstance({ name: "plebbitReactHooks-subplebbitsPages" }); - - await db.setItem(firstPageCid, { - nextCid: secondPageCid, - comments: [{ cid: `${firstPageCid}-comment`, subplebbitAddress: "invalidate-posts" }], - }); - await db.setItem(secondPageCid, { - comments: [{ cid: `${secondPageCid}-comment`, subplebbitAddress: "invalidate-posts" }], - }); - useSubplebbitsPagesStore.setState({ - subplebbitsPages: { - [firstPageCid]: { - nextCid: secondPageCid, - comments: [{ cid: `${firstPageCid}-comment`, subplebbitAddress: "invalidate-posts" }], - }, - [secondPageCid]: { - comments: [{ cid: `${secondPageCid}-comment`, subplebbitAddress: "invalidate-posts" }], - }, - }, - }); - - await rendered.result.current.invalidateSubplebbitPages( - { - address: "invalidate-posts", - posts: { pageCids: { new: firstPageCid } }, - }, - "new", - ); - - expect(useSubplebbitsPagesStore.getState().subplebbitsPages[firstPageCid]).toBeUndefined(); - expect(useSubplebbitsPagesStore.getState().subplebbitsPages[secondPageCid]).toBeUndefined(); - expect(await db.getItem(firstPageCid)).toBeUndefined(); - expect(await db.getItem(secondPageCid)).toBeUndefined(); - }); - - test("invalidateSubplebbitPages returns early when no first page cid", async () => { - useSubplebbitsPagesStore.setState({ - subplebbitsPages: { - existing: { - comments: [{ cid: "existing-comment", subplebbitAddress: "existing-sub" }], - }, - }, - }); - - await rendered.result.current.invalidateSubplebbitPages( - { - address: "no-pages", - posts: {}, - }, - "new", - ); - - expect(useSubplebbitsPagesStore.getState().subplebbitsPages.existing).toBeDefined(); - }); - - test("getCommentFreshness returns 0 when comment undefined (branch 26)", () => { - expect(getCommentFreshness(undefined)).toBe(0); - }); - - test("getSubplebbitFirstPageCid throws when sortType empty", () => { - expect(() => - getSubplebbitFirstPageCid({ address: "addr", posts: {} } as any, "", "posts"), - ).toThrow(); - }); - - test("getSubplebbitFirstPageCid throws when sortType undefined (branch 313)", () => { - expect(() => - getSubplebbitFirstPageCid({ address: "addr", posts: {} } as any, undefined as any, "posts"), - ).toThrow("sortType"); - }); - - test("getSubplebbitFirstPageCid returns nextCid when pages preloaded", () => { - const subplebbit = { - address: "addr", - posts: { - pages: { - hot: { - nextCid: "first-page-cid", - comments: [{ cid: "c1" }], - }, - }, - }, - }; - expect(getSubplebbitFirstPageCid(subplebbit as any, "hot", "posts")).toBe("first-page-cid"); - }); - - test("getSubplebbitFirstPageCid returns pageCids when no preloaded pages", () => { - const subplebbit = { - address: "addr", - posts: { - pageCids: { hot: "page-cid-from-pageCids" }, - }, - }; - expect(getSubplebbitFirstPageCid(subplebbit as any, "hot", "posts")).toBe( - "page-cid-from-pageCids", - ); - }); - - test("getSubplebbitFirstPageCid defaults pageType to posts", () => { - const subplebbit = { - address: "addr", - posts: { - pageCids: { hot: "default-posts-page-cid" }, - }, - }; - expect(getSubplebbitFirstPageCid(subplebbit as any, "hot")).toBe("default-posts-page-cid"); - }); - - test("fetchPage returns cached page when in database", async () => { - const mockSubplebbit = await mockAccount.plebbit.createSubplebbit({ - address: "subplebbit address 1", - }); - const sortType = "new"; - const firstPageCid = mockSubplebbit.posts.pageCids[sortType]; - const cachedPage = { - nextCid: firstPageCid + " - next", - comments: [{ cid: "cached-1", subplebbitAddress: "subplebbit address 1" }], - }; - const db = localForageLru.createInstance({ name: "plebbitReactHooks-subplebbitsPages" }); - await db.setItem(firstPageCid, cachedPage); - - act(() => { - rendered.result.current.addNextSubplebbitPageToStore(mockSubplebbit, sortType, mockAccount); - }); - - await waitFor( - () => - rendered.result.current.subplebbitsPages[firstPageCid]?.nextCid === - firstPageCid + " - next", - ); - expect(rendered.result.current.subplebbitsPages[firstPageCid].comments).toHaveLength(1); - }); - - test("invalidateSubplebbitPages removes loaded page chain from store and database", async () => { - const mockSubplebbit = await mockAccount.plebbit.createSubplebbit({ - address: "invalidate-pages-subplebbit", - }); - const sortType = "new"; - const firstPageCid = mockSubplebbit.posts.pageCids[sortType]; - const db = localForageLru.createInstance({ name: "plebbitReactHooks-subplebbitsPages" }); - - await act(async () => { - await rendered.result.current.addNextSubplebbitPageToStore( - mockSubplebbit, - sortType, - mockAccount, - ); - }); - await waitFor(() => rendered.result.current.subplebbitsPages[firstPageCid]?.nextCid); - - const secondPageCid = rendered.result.current.subplebbitsPages[firstPageCid].nextCid; - await act(async () => { - await rendered.result.current.addNextSubplebbitPageToStore( - mockSubplebbit, - sortType, - mockAccount, - ); - }); - await waitFor(() => rendered.result.current.subplebbitsPages[secondPageCid]?.comments?.length); - - expect(await db.getItem(firstPageCid)).toBeDefined(); - expect(await db.getItem(secondPageCid)).toBeDefined(); - - await act(async () => { - await rendered.result.current.invalidateSubplebbitPages(mockSubplebbit, sortType); - }); - - expect(useSubplebbitsPagesStore.getState().subplebbitsPages[firstPageCid]).toBeUndefined(); - expect(useSubplebbitsPagesStore.getState().subplebbitsPages[secondPageCid]).toBeUndefined(); - expect(await db.getItem(firstPageCid)).toBeUndefined(); - expect(await db.getItem(secondPageCid)).toBeUndefined(); - }); - - test("fetchPage onError logs when getPage rejects", async () => { - const mockSubplebbit = await mockAccount.plebbit.createSubplebbit({ - address: "subplebbit address 1", - }); - const sortType = "new"; - const firstPageCid = mockSubplebbit.posts.pageCids[sortType]; - const getPageOrig = MockPages.prototype.getPage; - MockPages.prototype.getPage = async () => { - throw new Error("getPage failed"); - }; - - const utils = await import("../../lib/utils"); - const retryOrig = utils.default.retryInfinity; - (utils.default as any).retryInfinity = async (fn: () => Promise, opts?: any) => { - try { - return await fn(); - } catch (e) { - opts?.onError?.(e); - throw e; - } - }; - - const logSpy = vi.spyOn(log, "error").mockImplementation(() => {}); - try { - await act(async () => { - try { - await rendered.result.current.addNextSubplebbitPageToStore( - mockSubplebbit, - sortType, - mockAccount, - ); - } catch { - // expected - } - }); - - expect(logSpy).toHaveBeenCalledWith( - expect.stringContaining("failed subplebbit.posts.getPage"), - expect.any(Error), - ); - } finally { - logSpy.mockRestore(); - (utils.default as any).retryInfinity = retryOrig; - MockPages.prototype.getPage = getPageOrig; - } - }); - - test("addCidToAccountComment error is logged when it rejects", async () => { - const addCidSpy = vi.fn().mockRejectedValue(new Error("addCid failed")); - const accountsGetState = accountsStore.getState; - (accountsStore as any).getState = () => ({ - ...accountsGetState(), - accountsActionsInternal: { addCidToAccountComment: addCidSpy }, - }); - - const mockSubplebbit = await mockAccount.plebbit.createSubplebbit({ - address: "subplebbit address 1", - }); - const sortType = "new"; - const logSpy = vi.spyOn(log, "error").mockImplementation(() => {}); - - act(() => { - rendered.result.current.addNextSubplebbitPageToStore(mockSubplebbit, sortType, mockAccount); - }); - - await waitFor(() => Object.keys(rendered.result.current.subplebbitsPages).length > 0); - await new Promise((r) => setTimeout(r, 100)); - - expect(logSpy).toHaveBeenCalledWith( - "subplebbitsPagesStore.addNextSubplebbitPageToStore addCidToAccountComment error", - expect.objectContaining({ comment: expect.anything(), error: expect.any(Error) }), - ); - - logSpy.mockRestore(); - (accountsStore as any).getState = accountsGetState; - }); - - test("onSubplebbitPostsClientsStateChange returns empty object when subplebbit missing", async () => { - let capturedCb: ((...args: any[]) => void) | null = null; - const utilsMod = await import("../../lib/utils"); - const origPageClients = utilsMod.default.pageClientsOnStateChange; - utilsMod.default.pageClientsOnStateChange = (_clients: any, cb: any) => { - capturedCb = cb; - }; - - const mockSubplebbit = await mockAccount.plebbit.createSubplebbit({ - address: "client-state-sub-addr", - }); - const sortType = "new"; - - await act(async () => { - await rendered.result.current.addNextSubplebbitPageToStore( - mockSubplebbit, - sortType, - mockAccount, - ); - }); - - await waitFor(() => Object.keys(rendered.result.current.subplebbitsPages).length > 0); - expect(capturedCb).toBeTruthy(); - - subplebbitsStore.setState({ subplebbits: {} }); - capturedCb!("state", "type", "sort", "url"); - expect(subplebbitsStore.getState().subplebbits).toEqual({}); - - utilsMod.default.pageClientsOnStateChange = origPageClients; - }); - - test("onSubplebbitPostsClientsStateChange updates client state when subplebbit exists", async () => { - let capturedCb: ((...args: any[]) => void) | null = null; - const utilsMod = await import("../../lib/utils"); - const origPageClients = utilsMod.default.pageClientsOnStateChange; - utilsMod.default.pageClientsOnStateChange = (_clients: any, cb: any) => { - capturedCb = cb; - }; - - const mockSubplebbit = await mockAccount.plebbit.createSubplebbit({ - address: "client-state-live-sub-addr", - }); - subplebbitsStore.setState({ - subplebbits: { - [mockSubplebbit.address]: { - address: mockSubplebbit.address, - posts: { clients: {} }, - }, - }, - }); - - await act(async () => { - await rendered.result.current.addNextSubplebbitPageToStore( - mockSubplebbit, - "new", - mockAccount, - ); - }); - - expect(capturedCb).toBeTruthy(); - capturedCb!("fetching", "ipfs", "new", "http://client.example"); - expect( - subplebbitsStore.getState().subplebbits[mockSubplebbit.address].posts.clients.ipfs.new[ - "http://client.example" - ], - ).toEqual({ - state: "fetching", - }); - - utilsMod.default.pageClientsOnStateChange = origPageClients; - }); - - test("add next pages from subplebbit.posts.pageCids", async () => { - const mockSubplebbit = await mockAccount.plebbit.createSubplebbit({ - address: "subplebbit address 1", - }); - // in the mock, sortType 'new' is only on subplebbit.pageCids - const sortType = "new"; - const subplebbitAddress1FirstPageCid = mockSubplebbit.posts.pageCids[sortType]; - - act(() => { - rendered.result.current.addNextSubplebbitPageToStore(mockSubplebbit, sortType, mockAccount); - }); - - // wait for first page to be defined - await waitFor( - () => - rendered.result.current.subplebbitsPages[subplebbitAddress1FirstPageCid].nextCid === - subplebbitAddress1FirstPageCid + " - next page cid", - ); - expect(rendered.result.current.subplebbitsPages[subplebbitAddress1FirstPageCid].nextCid).toBe( - subplebbitAddress1FirstPageCid + " - next page cid", - ); - expect( - rendered.result.current.subplebbitsPages[subplebbitAddress1FirstPageCid].comments.length, - ).toBe(100); - - // comments are individually stored in comments store - const firstCommentCid = - rendered.result.current.subplebbitsPages[subplebbitAddress1FirstPageCid].comments[0].cid; - expect(rendered.result.current.comments[firstCommentCid].cid).toBe(firstCommentCid); - expect(Object.keys(rendered.result.current.comments).length).toBe(100); - - act(() => { - rendered.result.current.addNextSubplebbitPageToStore(mockSubplebbit, sortType, mockAccount); - }); - - // wait for second page to be defined - const subplebbitAddress1SecondPageCid = `${subplebbitAddress1FirstPageCid} - next page cid`; - await waitFor( - () => - rendered.result.current.subplebbitsPages[subplebbitAddress1SecondPageCid].nextCid === - subplebbitAddress1SecondPageCid + " - next page cid", - ); - expect(rendered.result.current.subplebbitsPages[subplebbitAddress1SecondPageCid].nextCid).toBe( - subplebbitAddress1SecondPageCid + " - next page cid", - ); - expect( - rendered.result.current.subplebbitsPages[subplebbitAddress1SecondPageCid].comments.length, - ).toBe(100); - - // no more pages - const getPage = MockPages.prototype.getPage; - MockPages.prototype.getPage = async (options) => ({ comments: [], nextCid: undefined }); - - act(() => { - rendered.result.current.addNextSubplebbitPageToStore(mockSubplebbit, sortType, mockAccount); - }); - - // wait for third page to be defined - const subplebbitAddress1ThirdPageCid = `${subplebbitAddress1SecondPageCid} - next page cid`; - await waitFor( - () => - rendered.result.current.subplebbitsPages[subplebbitAddress1ThirdPageCid].nextCid === - undefined, - ); - expect(rendered.result.current.subplebbitsPages[subplebbitAddress1ThirdPageCid].nextCid).toBe( - undefined, - ); - expect( - rendered.result.current.subplebbitsPages[subplebbitAddress1ThirdPageCid].comments.length, - ).toBe(0); - - // adding a next page when no more pages does nothing - const previousSubplebbitPagesFetchedCount = Object.keys( - rendered.result.current.subplebbitsPages, - ).length; - act(() => { - rendered.result.current.addNextSubplebbitPageToStore(mockSubplebbit, sortType, mockAccount); - }); - await expect( - tlWaitFor(() => { - if ( - !( - Object.keys(rendered.result.current.subplebbitsPages).length > - previousSubplebbitPagesFetchedCount - ) - ) - throw new Error("condition not met"); - }), - ).rejects.toThrow(); - expect(Object.keys(rendered.result.current.subplebbitsPages).length).toBe( - previousSubplebbitPagesFetchedCount, - ); - - // restore mock - MockPages.prototype.getPage = getPage; - }); - - test("add next pages from subplebbit.posts.pages", async () => { - const mockSubplebbit = await mockAccount.plebbit.createSubplebbit({ - address: "subplebbit address 1", - }); - // in the mock, sortType 'hot' is only on subplebbit.pages - const sortType = "hot"; - const subplebbitAddress1FirstPageCid = mockSubplebbit.posts.pages[sortType].nextCid; - - act(() => { - rendered.result.current.addNextSubplebbitPageToStore(mockSubplebbit, sortType, mockAccount); - }); - - // wait for first page to be defined - await waitFor( - () => - rendered.result.current.subplebbitsPages[subplebbitAddress1FirstPageCid].nextCid === - subplebbitAddress1FirstPageCid + " - next page cid", - ); - expect(rendered.result.current.subplebbitsPages[subplebbitAddress1FirstPageCid].nextCid).toBe( - subplebbitAddress1FirstPageCid + " - next page cid", - ); - expect( - rendered.result.current.subplebbitsPages[subplebbitAddress1FirstPageCid].comments.length, - ).toBe(100); - - act(() => { - rendered.result.current.addNextSubplebbitPageToStore(mockSubplebbit, sortType, mockAccount); - }); - - // wait for second page to be defined - const subplebbitAddress1SecondPageCid = `${subplebbitAddress1FirstPageCid} - next page cid`; - await waitFor( - () => - rendered.result.current.subplebbitsPages[subplebbitAddress1SecondPageCid].nextCid === - subplebbitAddress1SecondPageCid + " - next page cid", - ); - expect(rendered.result.current.subplebbitsPages[subplebbitAddress1SecondPageCid].nextCid).toBe( - subplebbitAddress1SecondPageCid + " - next page cid", - ); - expect( - rendered.result.current.subplebbitsPages[subplebbitAddress1SecondPageCid].comments.length, - ).toBe(100); - - // no more pages - const getPage = MockPages.prototype.getPage; - MockPages.prototype.getPage = async (options) => ({ comments: [], nextCid: undefined }); - - act(() => { - rendered.result.current.addNextSubplebbitPageToStore(mockSubplebbit, sortType, mockAccount); - }); - - // wait for third page to be defined - const subplebbitAddress1ThirdPageCid = `${subplebbitAddress1SecondPageCid} - next page cid`; - await waitFor( - () => - rendered.result.current.subplebbitsPages[subplebbitAddress1ThirdPageCid].nextCid === - undefined, - ); - expect(rendered.result.current.subplebbitsPages[subplebbitAddress1ThirdPageCid].nextCid).toBe( - undefined, - ); - expect( - rendered.result.current.subplebbitsPages[subplebbitAddress1ThirdPageCid].comments.length, - ).toBe(0); - - // adding a next page when no more pages does nothing - const previousSubplebbitPagesFetchedCount = Object.keys( - rendered.result.current.subplebbitsPages, - ).length; - act(() => { - rendered.result.current.addNextSubplebbitPageToStore(mockSubplebbit, sortType, mockAccount); - }); - await expect( - tlWaitFor(() => { - if ( - !( - Object.keys(rendered.result.current.subplebbitsPages).length > - previousSubplebbitPagesFetchedCount - ) - ) - throw new Error("condition not met"); - }), - ).rejects.toThrow(); - expect(Object.keys(rendered.result.current.subplebbitsPages).length).toBe( - previousSubplebbitPagesFetchedCount, - ); - - // restore mock - MockPages.prototype.getPage = getPage; - }); - - test("page comments without updatedAt are still indexed on first insert", async () => { - const mockSubplebbit = await mockAccount.plebbit.createSubplebbit({ - address: "subplebbit address 1", - }); - const sortType = "new"; - const firstPageCid = mockSubplebbit.posts.pageCids[sortType]; - const commentCid = firstPageCid + " comment-no-updated-at"; - const getPageOriginal = MockPages.prototype.getPage; - MockPages.prototype.getPage = async (options) => { - const cid = options?.cid; - await sleep(200); - return { - nextCid: cid + " - next page cid", - comments: [ - { - cid: commentCid, - timestamp: 100, - subplebbitAddress: "subplebbit address 1", - // no updatedAt - should still be indexed via max(updatedAt??0, timestamp, 0) - }, - ], - }; - }; - act(() => { - rendered.result.current.addNextSubplebbitPageToStore(mockSubplebbit, sortType, mockAccount); - }); - await waitFor(() => { - expect(rendered.result.current.comments[commentCid]).toBeDefined(); - }); - expect(rendered.result.current.comments[commentCid].cid).toBe(commentCid); - expect(rendered.result.current.comments[commentCid].timestamp).toBe(100); - expect(rendered.result.current.comments[commentCid].updatedAt).toBeUndefined(); - MockPages.prototype.getPage = getPageOriginal; - }); - - test("existing fresher indexed comment is not overwritten by older/empty-freshness page data", async () => { - const mockSubplebbit = await mockAccount.plebbit.createSubplebbit({ - address: "subplebbit address 2", - }); - const sortType = "new"; - const firstPageCid = mockSubplebbit.posts.pageCids[sortType]; - const secondPageCid = firstPageCid + " - next page cid"; - const sharedCommentCid = "shared-comment-fresher-wins"; - const getPageOriginal = MockPages.prototype.getPage; - MockPages.prototype.getPage = async (options) => { - const cid = options?.cid; - await sleep(200); - if (cid === firstPageCid) { - return { - nextCid: secondPageCid, - comments: [ - { - cid: sharedCommentCid, - timestamp: 50, - updatedAt: 100, - subplebbitAddress: "subplebbit address 2", - }, - ], - }; - } - if (cid === secondPageCid) { - return { - nextCid: secondPageCid + " - next page cid", - comments: [ - { - cid: sharedCommentCid, - timestamp: 50, - updatedAt: 10, - subplebbitAddress: "subplebbit address 2", - }, - ], - }; - } - return { nextCid: undefined, comments: [] }; - }; - act(() => { - rendered.result.current.addNextSubplebbitPageToStore(mockSubplebbit, sortType, mockAccount); - }); - await waitFor(() => { - expect(rendered.result.current.comments[sharedCommentCid]).toBeDefined(); - }); - expect(rendered.result.current.comments[sharedCommentCid].updatedAt).toBe(100); - act(() => { - rendered.result.current.addNextSubplebbitPageToStore(mockSubplebbit, sortType, mockAccount); - }); - await waitFor(() => { - expect(rendered.result.current.subplebbitsPages[secondPageCid]).toBeDefined(); - }); - expect(rendered.result.current.comments[sharedCommentCid].updatedAt).toBe(100); - MockPages.prototype.getPage = getPageOriginal; - }); -}); diff --git a/src/stores/subplebbits-pages/subplebbits-pages-store.ts b/src/stores/subplebbits-pages/subplebbits-pages-store.ts deleted file mode 100644 index 041c46f5..00000000 --- a/src/stores/subplebbits-pages/subplebbits-pages-store.ts +++ /dev/null @@ -1,402 +0,0 @@ -import utils from "../../lib/utils"; -import Logger from "@plebbit/plebbit-logger"; -// include subplebbits pages store with feeds for debugging -export const log = Logger("bitsocial-react-hooks:feeds:stores"); -import { - Subplebbit, - SubplebbitPage, - SubplebbitsPages, - Account, - Comment, - Comments, -} from "../../types"; -import accountsStore from "../accounts"; -import subplebbitsStore, { SubplebbitsState } from "../subplebbits"; -import localForageLru from "../../lib/localforage-lru"; -import createStore from "zustand"; -import assert from "assert"; - -const subplebbitsPagesDatabase = localForageLru.createInstance({ - name: "plebbitReactHooks-subplebbitsPages", - size: 500, -}); - -/** Freshness for comparison: max(updatedAt, timestamp, 0). Used to decide add vs replace per CID. Exported for coverage. */ -export const getCommentFreshness = (comment: Comment | undefined): number => - Math.max(comment?.updatedAt ?? 0, comment?.timestamp ?? 0, 0); - -// reset all event listeners in between tests -const listeners: any = []; - -type SubplebbitsPagesState = { - subplebbitsPages: SubplebbitsPages; - comments: Comments; - addNextSubplebbitPageToStore: Function; - invalidateSubplebbitPages: Function; - addSubplebbitPageCommentsToStore: Function; -}; - -const subplebbitsPagesStore = createStore( - (setState: Function, getState: Function) => ({ - // TODO: eventually clear old pages and comments from memory - subplebbitsPages: {}, - comments: {}, - - addNextSubplebbitPageToStore: async ( - subplebbit: Subplebbit, - sortType: string, - account: Account, - modQueue?: string[], - ) => { - assert( - subplebbit?.address && typeof subplebbit?.address === "string", - `subplebbitsPagesStore.addNextSubplebbitPageToStore subplebbit '${subplebbit}' invalid`, - ); - assert( - sortType && typeof sortType === "string", - `subplebbitsPagesStore.addNextSubplebbitPageToStore sortType '${sortType}' invalid`, - ); - assert( - typeof account?.plebbit?.createSubplebbit === "function", - `subplebbitsPagesStore.addNextSubplebbitPageToStore account '${account}' invalid`, - ); - assert( - !modQueue || Array.isArray(modQueue), - `subplebbitsPagesStore.addNextSubplebbitPageToStore modQueue '${modQueue}' invalid`, - ); - - let pageType = "posts"; - if (modQueue?.[0]) { - // TODO: allow multiple modQueue at once, fow now only use first in array - // TODO: fix 'sortType' is not accurate variable name when pageType is 'modQueue' - sortType = modQueue[0]; - pageType = "modQueue"; - } - - // check the preloaded posts on subplebbit.posts.pages first, then the subplebbit.posts.pageCids - const subplebbitFirstPageCid = getSubplebbitFirstPageCid(subplebbit, sortType, pageType); - if (!subplebbitFirstPageCid) { - log( - `subplebbitsPagesStore.addNextSubplebbitPageToStore subplebbit '${subplebbit?.address}' sortType '${sortType}' no subplebbitFirstPageCid`, - ); - return; - } - - // all subplebbits pages in store - const { subplebbitsPages } = getState(); - // only specific pages of the subplebbit+sortType - const subplebbitPages = getSubplebbitPages(subplebbit, sortType, subplebbitsPages, pageType); - - // if no pages exist yet, add the first page - let pageCidToAdd: string; - if (!subplebbitPages.length) { - pageCidToAdd = subplebbitFirstPageCid; - } else { - const nextCid = subplebbitPages[subplebbitPages.length - 1]?.nextCid; - // if last nextCid is undefined, reached end of pages - if (!nextCid) { - log.trace("subplebbitsPagesStore.addNextSubplebbitPageToStore no more pages", { - subplebbitAddress: subplebbit.address, - sortType, - account, - }); - return; - } - - pageCidToAdd = nextCid; - } - - // page is already added or pending - if (subplebbitsPages[pageCidToAdd] || fetchPagePending[account.id + pageCidToAdd]) { - return; - } - - fetchPagePending[account.id + pageCidToAdd] = true; - let page: SubplebbitPage; - try { - page = await fetchPage(pageCidToAdd, subplebbit.address, account, pageType); - log.trace("subplebbitsPagesStore.addNextSubplebbitPageToStore subplebbit.posts.getPage", { - pageCid: pageCidToAdd, - subplebbitAddress: subplebbit.address, - account, - }); - } catch (e) { - throw e; - } finally { - fetchPagePending[account.id + pageCidToAdd] = false; - } - - // find new comments in the page - const flattenedComments = utils.flattenCommentsPages(page); - const { comments } = getState(); - let hasNewComments = false; - const newComments: Comments = {}; - for (const comment of flattenedComments) { - const existing = comments[comment.cid]; - if ( - comment.cid && - (!existing || getCommentFreshness(comment) > getCommentFreshness(existing)) - ) { - // don't clone the comment to save memory, comments remain a pointer to the page object - newComments[comment.cid] = comment; - hasNewComments = true; - } - } - - setState(({ subplebbitsPages, comments }: any) => { - const newState: any = { subplebbitsPages: { ...subplebbitsPages, [pageCidToAdd]: page } }; - if (hasNewComments) { - newState.comments = { ...comments, ...newComments }; - } - return newState; - }); - log("subplebbitsPagesStore.addNextSubplebbitPageToStore", { - pageCid: pageCidToAdd, - subplebbitAddress: subplebbit.address, - sortType, - page, - account, - }); - - // when publishing a comment, you don't yet know its CID - // so when a new comment is fetched, check to see if it's your own - // comment, and if yes, add the CID to your account comments database - for (const comment of flattenedComments) { - accountsStore - .getState() - .accountsActionsInternal.addCidToAccountComment(comment) - .catch((error: unknown) => - log.error( - "subplebbitsPagesStore.addNextSubplebbitPageToStore addCidToAccountComment error", - { comment, error }, - ), - ); - } - }, - - invalidateSubplebbitPages: async ( - subplebbit: Subplebbit, - sortType: string, - modQueue?: string[], - ) => { - assert( - subplebbit?.address && typeof subplebbit?.address === "string", - `subplebbitsPagesStore.invalidateSubplebbitPages subplebbit '${subplebbit}' invalid`, - ); - assert( - sortType && typeof sortType === "string", - `subplebbitsPagesStore.invalidateSubplebbitPages sortType '${sortType}' invalid`, - ); - assert( - !modQueue || Array.isArray(modQueue), - `subplebbitsPagesStore.invalidateSubplebbitPages modQueue '${modQueue}' invalid`, - ); - - let pageType = "posts"; - if (modQueue?.[0]) { - // TODO: allow multiple modQueue at once, for now only use first in array - // TODO: fix 'sortType' is not accurate variable name when pageType is 'modQueue' - sortType = modQueue[0]; - pageType = "modQueue"; - } - - const firstPageCid = getSubplebbitFirstPageCid(subplebbit, sortType, pageType); - if (!firstPageCid) { - return; - } - - const { subplebbitsPages } = getState(); - const pageCidsToInvalidate = new Set([firstPageCid]); - let nextPageCid = subplebbitsPages[firstPageCid]?.nextCid; - while (nextPageCid) { - pageCidsToInvalidate.add(nextPageCid); - nextPageCid = subplebbitsPages[nextPageCid]?.nextCid; - } - - await Promise.all( - [...pageCidsToInvalidate].map((pageCid) => subplebbitsPagesDatabase.removeItem(pageCid)), - ); - - setState(({ subplebbitsPages }: any) => { - const nextSubplebbitsPages = { ...subplebbitsPages }; - for (const pageCid of pageCidsToInvalidate) { - delete nextSubplebbitsPages[pageCid]; - } - return { subplebbitsPages: nextSubplebbitsPages }; - }); - }, - - // subplebbits contain preloaded pages, those page comments must be added separately - addSubplebbitPageCommentsToStore: (subplebbit: Subplebbit) => { - if (!subplebbit.posts?.pages) { - return; - } - - // find new comments in the page - const flattenedComments = utils.flattenCommentsPages(subplebbit.posts.pages); - const { comments } = getState(); - let hasNewComments = false; - const newComments: Comments = {}; - for (const comment of flattenedComments) { - const existing = comments[comment.cid]; - if ( - comment.cid && - (!existing || getCommentFreshness(comment) > getCommentFreshness(existing)) - ) { - // don't clone the comment to save memory, comments remain a pointer to the page object - newComments[comment.cid] = comment; - hasNewComments = true; - } - } - - if (!hasNewComments) { - return; - } - - setState(({ comments }: any) => { - return { comments: { ...comments, ...newComments } }; - }); - log("subplebbitsPagesStore.addSubplebbitPageCommentsToStore", { subplebbit, newComments }); - }, - }), -); - -// set clients states on subplebbits store so the frontend can display it, dont persist in db because a reload cancels updating -const onSubplebbitPostsClientsStateChange = - (subplebbitAddress: string) => - (clientState: string, clientType: string, sortType: string, clientUrl: string) => { - subplebbitsStore.setState((state: SubplebbitsState) => { - // make sure not undefined, sometimes happens in e2e tests - if (!state.subplebbits[subplebbitAddress]) { - return {}; - } - const client = { state: clientState }; - const subplebbit = { ...state.subplebbits[subplebbitAddress] }; - subplebbit.posts = { ...subplebbit.posts }; - subplebbit.posts.clients = { ...subplebbit.posts.clients }; - subplebbit.posts.clients[clientType] = { ...subplebbit.posts.clients[clientType] }; - subplebbit.posts.clients[clientType][sortType] = { - ...subplebbit.posts.clients[clientType][sortType], - }; - subplebbit.posts.clients[clientType][sortType][clientUrl] = client; - return { subplebbits: { ...state.subplebbits, [subplebbit.address]: subplebbit } }; - }); - }; - -const fetchPageSubplebbits: { [subplebbitAddress: string]: any } = {}; // cache plebbit.createSubplebbits because sometimes it's slow -let fetchPagePending: { [key: string]: boolean } = {}; -const fetchPage = async ( - pageCid: string, - subplebbitAddress: string, - account: Account, - pageType: string, -) => { - // subplebbit page is cached - const cachedSubplebbitPage = await subplebbitsPagesDatabase.getItem(pageCid); - if (cachedSubplebbitPage) { - return cachedSubplebbitPage; - } - if (!fetchPageSubplebbits[subplebbitAddress]) { - fetchPageSubplebbits[subplebbitAddress] = await account.plebbit.createSubplebbit({ - address: subplebbitAddress, - }); - listeners.push(fetchPageSubplebbits[subplebbitAddress]); - - // set clients states on subplebbits store so the frontend can display it - utils.pageClientsOnStateChange( - fetchPageSubplebbits[subplebbitAddress][pageType]?.clients, - onSubplebbitPostsClientsStateChange(subplebbitAddress), - ); - } - - const onError = (error: any) => - log.error( - `subplebbitsPagesStore subplebbit '${subplebbitAddress}' failed subplebbit.posts.getPage page cid '${pageCid}':`, - error, - ); - const fetchedSubplebbitPage = await utils.retryInfinity( - () => fetchPageSubplebbits[subplebbitAddress][pageType].getPage({ cid: pageCid }), - { onError }, - ); - await subplebbitsPagesDatabase.setItem(pageCid, utils.clone(fetchedSubplebbitPage)); - return fetchedSubplebbitPage; -}; - -/** - * Util function to get all pages in the store for a - * specific subplebbit+sortType using `SubplebbitPage.nextCid` - */ -export const getSubplebbitPages = ( - subplebbit: Subplebbit, - sortType: string, - subplebbitsPages: SubplebbitsPages, - pageType: string, -) => { - assert( - subplebbitsPages && typeof subplebbitsPages === "object", - `getSubplebbitPages subplebbitsPages '${subplebbitsPages}' invalid`, - ); - const pages: SubplebbitPage[] = []; - const firstPageCid = getSubplebbitFirstPageCid(subplebbit, sortType, pageType); - // subplebbit has no pages - // TODO: if a loaded subplebbit doesn't have a first page, it's unclear what we should do - // should we try to use another sort type by default, like 'hot', or should we just ignore it? - // 'return pages' to ignore it for now - if (!firstPageCid) { - return pages; - } - const firstPage = subplebbitsPages[firstPageCid]; - if (!firstPage) { - return pages; - } - pages.push(firstPage); - while (true) { - const nextCid = pages[pages.length - 1]?.nextCid; - const subplebbitPage = nextCid && subplebbitsPages[nextCid]; - if (!subplebbitPage) { - return pages; - } - pages.push(subplebbitPage); - } -}; - -export const getSubplebbitFirstPageCid = ( - subplebbit: Subplebbit, - sortType: string, - pageType = "posts", -) => { - assert(subplebbit?.address, `getSubplebbitFirstPageCid subplebbit '${subplebbit}' invalid`); - assert( - sortType && typeof sortType === "string", - `getSubplebbitFirstPageCid sortType '${sortType}' invalid`, - ); - // subplebbit has preloaded posts for sort type - if (subplebbit[pageType]?.pages?.[sortType]?.comments) { - return subplebbit[pageType]?.pages?.[sortType]?.nextCid; - } - return subplebbit[pageType]?.pageCids?.[sortType]; - - // TODO: if a loaded subplebbit doesn't have a first page, it's unclear what we should do - // should we try to use another sort type by default, like 'hot', or should we just ignore it? -}; - -// reset store in between tests -const originalState = subplebbitsPagesStore.getState(); -// async function because some stores have async init -export const resetSubplebbitsPagesStore = async () => { - fetchPagePending = {}; - // remove all event listeners - listeners.forEach((listener: any) => listener.removeAllListeners()); - // destroy all component subscriptions to the store - subplebbitsPagesStore.destroy(); - // restore original state - subplebbitsPagesStore.setState(originalState); -}; - -// reset database and store in between tests -export const resetSubplebbitsPagesDatabaseAndStore = async () => { - await localForageLru.createInstance({ name: "plebbitReactHooks-subplebbitsPages" }).clear(); - await resetSubplebbitsPagesStore(); -}; - -export default subplebbitsPagesStore; diff --git a/src/stores/subplebbits/index.ts b/src/stores/subplebbits/index.ts deleted file mode 100644 index 583614b1..00000000 --- a/src/stores/subplebbits/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import subplebbitsStore from './subplebbits-store' -export * from './subplebbits-store' -export default subplebbitsStore diff --git a/src/stores/subplebbits/subplebbits-store.test.ts b/src/stores/subplebbits/subplebbits-store.test.ts deleted file mode 100644 index 1d0b82ee..00000000 --- a/src/stores/subplebbits/subplebbits-store.test.ts +++ /dev/null @@ -1,257 +0,0 @@ -import { act } from "@testing-library/react"; -import testUtils, { renderHook } from "../../lib/test-utils"; -import subplebbitsStore, { resetSubplebbitsDatabaseAndStore } from "./subplebbits-store"; -import localForageLru from "../../lib/localforage-lru"; -import { setPlebbitJs } from "../.."; -import PlebbitJsMock from "../../lib/plebbit-js/plebbit-js-mock"; -import accountsStore from "../accounts"; -import subplebbitsPagesStore from "../subplebbits-pages"; - -let mockAccount: any; - -describe("subplebbits store", () => { - beforeAll(async () => { - setPlebbitJs(PlebbitJsMock); - testUtils.silenceReactWarnings(); - const plebbit = await PlebbitJsMock(); - mockAccount = { id: "mock-account-id", plebbit }; - }); - afterAll(() => { - testUtils.restoreAll(); - }); - - afterEach(async () => { - await resetSubplebbitsDatabaseAndStore(); - }); - - test("initial store", () => { - const { result } = renderHook(() => subplebbitsStore.getState()); - expect(result.current.subplebbits).toEqual({}); - expect(result.current.errors).toEqual({}); - expect(typeof result.current.addSubplebbitToStore).toBe("function"); - }); - - test("addSubplebbitToStore adds subplebbit from plebbit", async () => { - const address = "subplebbit address 1"; - - await act(async () => { - await subplebbitsStore.getState().addSubplebbitToStore(address, mockAccount); - }); - - expect(subplebbitsStore.getState().subplebbits[address]).toBeDefined(); - expect(subplebbitsStore.getState().subplebbits[address].address).toBe(address); - }); - - test("cached subplebbit create failure logs to console", async () => { - const address = "cached-fail-address"; - const db = localForageLru.createInstance({ name: "plebbitReactHooks-subplebbits" }); - await db.setItem(address, { address, invalid: "data" }); - - const createSubplebbitOriginal = mockAccount.plebbit.createSubplebbit; - mockAccount.plebbit.createSubplebbit = vi.fn().mockRejectedValue(new Error("create failed")); - const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); - - await act(async () => { - try { - await subplebbitsStore.getState().addSubplebbitToStore(address, mockAccount); - } catch { - // expected to throw - } - }); - - expect(consoleSpy).toHaveBeenCalledWith( - "failed plebbit.createSubplebbit(cachedSubplebbit)", - expect.objectContaining({ - cachedSubplebbit: expect.any(Object), - error: expect.any(Error), - }), - ); - consoleSpy.mockRestore(); - mockAccount.plebbit.createSubplebbit = createSubplebbitOriginal; - }); - - test("missing-subplebbit state guard returns empty object in client updater", async () => { - const address = "client-update-address"; - let storedCb: ((...args: any[]) => void) | null = null; - - const utils = await import("../../lib/utils"); - const origClientsOnStateChange = utils.default.clientsOnStateChange; - (utils.default as any).clientsOnStateChange = (_clients: any, cb: any) => { - storedCb = () => cb("state", "type", "url"); - }; - - await act(async () => { - await subplebbitsStore.getState().addSubplebbitToStore(address, mockAccount); - }); - - expect(storedCb).toBeTruthy(); - subplebbitsStore.setState({ subplebbits: {} }); - - storedCb!(); - - expect(subplebbitsStore.getState().subplebbits).toEqual({}); - - (utils.default as any).clientsOnStateChange = origClientsOnStateChange; - }); - - test("subplebbit.update catch logs when update rejects", async () => { - const address = "update-reject-address"; - const plebbit = await PlebbitJsMock(); - const subplebbit = await plebbit.createSubplebbit({ address }); - const updateSpy = vi - .spyOn(subplebbit, "update") - .mockRejectedValueOnce(new Error("update failed")); - - const createSubplebbitOrig = mockAccount.plebbit.createSubplebbit; - mockAccount.plebbit.createSubplebbit = vi.fn().mockResolvedValue(subplebbit); - - await act(async () => { - await subplebbitsStore.getState().addSubplebbitToStore(address, mockAccount); - }); - - await new Promise((r) => setTimeout(r, 100)); - - mockAccount.plebbit.createSubplebbit = createSubplebbitOrig; - updateSpy.mockRestore(); - }); - - test("addSubplebbitToStore sets errors and throws when createSubplebbit rejects", async () => { - const address = "create-reject-address"; - const createOrig = mockAccount.plebbit.createSubplebbit; - mockAccount.plebbit.createSubplebbit = vi.fn().mockRejectedValue(new Error("create failed")); - - await act(async () => { - try { - await subplebbitsStore.getState().addSubplebbitToStore(address, mockAccount); - } catch (e) { - expect((e as Error).message).toBe("create failed"); - } - }); - - expect(subplebbitsStore.getState().errors[address]).toHaveLength(1); - expect(subplebbitsStore.getState().errors[address][0].message).toBe("create failed"); - mockAccount.plebbit.createSubplebbit = createOrig; - }); - - test("addSubplebbitToStore throws generic Error when subplebbit is undefined without thrown error", async () => { - const address = "resolve-undefined-address"; - const createOrig = mockAccount.plebbit.createSubplebbit; - mockAccount.plebbit.createSubplebbit = vi.fn().mockResolvedValue(undefined); - - await act(async () => { - try { - await subplebbitsStore.getState().addSubplebbitToStore(address, mockAccount); - } catch (e) { - expect((e as Error).message).toContain("failed getting subplebbit"); - } - }); - - mockAccount.plebbit.createSubplebbit = createOrig; - }); - - test("subplebbit update event calls addSubplebbitRoleToAccountsSubplebbits and addSubplebbitPageCommentsToStore", async () => { - const address = "update-event-address"; - const plebbit = await PlebbitJsMock(); - const subplebbit = await plebbit.createSubplebbit({ address }); - const addRoleSpy = vi.fn().mockResolvedValue(undefined); - const addCommentsSpy = vi.fn(); - const accountsGetState = accountsStore.getState; - (accountsStore as any).getState = () => ({ - ...accountsGetState(), - accountsActionsInternal: { addSubplebbitRoleToAccountsSubplebbits: addRoleSpy }, - }); - const pagesGetState = subplebbitsPagesStore.getState; - (subplebbitsPagesStore as any).getState = () => ({ - ...pagesGetState(), - addSubplebbitPageCommentsToStore: addCommentsSpy, - }); - - const createOrig = mockAccount.plebbit.createSubplebbit; - mockAccount.plebbit.createSubplebbit = vi.fn().mockResolvedValue(subplebbit); - - await act(async () => { - await subplebbitsStore.getState().addSubplebbitToStore(address, mockAccount); - }); - - subplebbit.emit("update", subplebbit); - await new Promise((r) => setTimeout(r, 50)); - - expect(addRoleSpy).toHaveBeenCalledWith(expect.objectContaining({ address })); - expect(addCommentsSpy).toHaveBeenCalledWith(expect.objectContaining({ address })); - - (accountsStore as any).getState = accountsGetState; - (subplebbitsPagesStore as any).getState = pagesGetState; - mockAccount.plebbit.createSubplebbit = createOrig; - }); - - test("createSubplebbit with no signer asserts address must be undefined", async () => { - const plebbit = await PlebbitJsMock(); - const subplebbit = await plebbit.createSubplebbit({ address: "new-sub-address" }); - const createOrig = mockAccount.plebbit.createSubplebbit; - mockAccount.plebbit.createSubplebbit = vi.fn().mockResolvedValue(subplebbit); - - await act(async () => { - await subplebbitsStore.getState().createSubplebbit({}, mockAccount); - }); - - expect(mockAccount.plebbit.createSubplebbit).toHaveBeenCalledWith({}); - mockAccount.plebbit.createSubplebbit = createOrig; - }); - - test("createSubplebbit with address but no signer throws (branch 251)", async () => { - await expect( - subplebbitsStore.getState().createSubplebbit({ address: "addr-no-signer" }, mockAccount), - ).rejects.toThrow("createSubplebbitOptions.address 'addr-no-signer' must be undefined"); - }); - - test("clientsOnStateChange with chainTicker branch", async () => { - const address = "chain-ticker-address"; - let storedCb: ((...args: any[]) => void) | null = null; - - const utils = await import("../../lib/utils"); - const origClientsOnStateChange = utils.default.clientsOnStateChange; - (utils.default as any).clientsOnStateChange = (_clients: any, cb: any) => { - storedCb = () => cb("state", "type", "url", "ETH"); - }; - - await act(async () => { - await subplebbitsStore.getState().addSubplebbitToStore(address, mockAccount); - }); - - expect(storedCb).toBeTruthy(); - subplebbitsStore.setState((state: any) => ({ - subplebbits: { - ...state.subplebbits, - [address]: { - ...state.subplebbits[address], - clients: { type: {} }, - }, - }, - })); - storedCb!(); - expect(subplebbitsStore.getState().subplebbits[address]?.clients?.type?.ETH).toBeDefined(); - - (utils.default as any).clientsOnStateChange = origClientsOnStateChange; - }); - - test("clientsOnStateChange returns {} when subplebbit missing and chainTicker provided", async () => { - const address = "chain-missing-address"; - let storedCb: ((...args: any[]) => void) | null = null; - - const utils = await import("../../lib/utils"); - const origClientsOnStateChange = utils.default.clientsOnStateChange; - (utils.default as any).clientsOnStateChange = (_clients: any, cb: any) => { - storedCb = () => cb("state", "type", "url", "ETH"); - }; - - await act(async () => { - await subplebbitsStore.getState().addSubplebbitToStore(address, mockAccount); - }); - expect(storedCb).toBeTruthy(); - subplebbitsStore.setState({ subplebbits: {} }); - storedCb!(); - expect(subplebbitsStore.getState().subplebbits).toEqual({}); - - (utils.default as any).clientsOnStateChange = origClientsOnStateChange; - }); -}); diff --git a/src/stores/subplebbits/subplebbits-store.ts b/src/stores/subplebbits/subplebbits-store.ts deleted file mode 100644 index b1c46533..00000000 --- a/src/stores/subplebbits/subplebbits-store.ts +++ /dev/null @@ -1,354 +0,0 @@ -import assert from "assert"; -import localForageLru from "../../lib/localforage-lru"; -const subplebbitsDatabase = localForageLru.createInstance({ - name: "plebbitReactHooks-subplebbits", - size: 500, -}); -import Logger from "@plebbit/plebbit-logger"; -const log = Logger("bitsocial-react-hooks:subplebbits:stores"); -import { Subplebbit, Subplebbits, Account, CreateSubplebbitOptions } from "../../types"; -import utils from "../../lib/utils"; -import createStore from "zustand"; -import accountsStore from "../accounts"; -import subplebbitsPagesStore from "../subplebbits-pages"; - -let plebbitGetSubplebbitPending: { [key: string]: boolean } = {}; - -// reset all event listeners in between tests -const listeners: any = []; - -export type SubplebbitsState = { - subplebbits: Subplebbits; - errors: { [subplebbitAddress: string]: Error[] }; - addSubplebbitToStore: Function; - refreshSubplebbit: Function; - editSubplebbit: Function; - createSubplebbit: Function; - deleteSubplebbit: Function; -}; - -const subplebbitsStore = createStore( - (setState: Function, getState: Function) => ({ - subplebbits: {}, - errors: {}, - - async addSubplebbitToStore(subplebbitAddress: string, account: Account) { - assert( - subplebbitAddress !== "" && typeof subplebbitAddress === "string", - `subplebbitsStore.addSubplebbitToStore invalid subplebbitAddress argument '${subplebbitAddress}'`, - ); - assert( - typeof account?.plebbit?.getSubplebbit === "function", - `subplebbitsStore.addSubplebbitToStore invalid account argument '${account}'`, - ); - - // subplebbit is in store already, do nothing - const { subplebbits } = getState(); - let subplebbit: Subplebbit | undefined = subplebbits[subplebbitAddress]; - if (subplebbit || plebbitGetSubplebbitPending[subplebbitAddress + account.id]) { - return; - } - - // start trying to get subplebbit - plebbitGetSubplebbitPending[subplebbitAddress + account.id] = true; - let errorGettingSubplebbit: any; - - // try to find subplebbit in owner subplebbits - if (account.plebbit.subplebbits.includes(subplebbitAddress)) { - subplebbit = await account.plebbit.createSubplebbit({ address: subplebbitAddress }); - } - - // try to find subplebbit in database - let fetchedAt: number | undefined; - if (!subplebbit) { - const subplebbitData: any = await subplebbitsDatabase.getItem(subplebbitAddress); - if (subplebbitData) { - fetchedAt = subplebbitData.fetchedAt; - delete subplebbitData.fetchedAt; // not part of plebbit-js schema - try { - subplebbit = await account.plebbit.createSubplebbit(subplebbitData); - } catch (e) { - fetchedAt = undefined; - // need to log this always or it could silently fail in production and cache never be used - console.error("failed plebbit.createSubplebbit(cachedSubplebbit)", { - cachedSubplebbit: subplebbitData, - error: e, - }); - } - } - if (subplebbit) { - // add page comments to subplebbitsPagesStore so they can be used in useComment - subplebbitsPagesStore.getState().addSubplebbitPageCommentsToStore(subplebbit); - } - } - - // subplebbit not in database, try to fetch from plebbit-js - if (!subplebbit) { - try { - subplebbit = await account.plebbit.createSubplebbit({ address: subplebbitAddress }); - } catch (e) { - errorGettingSubplebbit = e; - } - } - - // finished trying to get subplebbit - plebbitGetSubplebbitPending[subplebbitAddress + account.id] = false; - - // failure getting subplebbit - if (!subplebbit) { - if (errorGettingSubplebbit) { - setState((state: SubplebbitsState) => { - let subplebbitErrors = state.errors[subplebbitAddress] || []; - subplebbitErrors = [...subplebbitErrors, errorGettingSubplebbit]; - return { ...state, errors: { ...state.errors, [subplebbitAddress]: subplebbitErrors } }; - }); - } - - throw ( - errorGettingSubplebbit || - Error( - `subplebbitsStore.addSubplebbitToStore failed getting subplebbit '${subplebbitAddress}'`, - ) - ); - } - - // success getting subplebbit - const firstSubplebbitState = utils.clone({ ...subplebbit, fetchedAt }); - await subplebbitsDatabase.setItem(subplebbitAddress, firstSubplebbitState); - log("subplebbitsStore.addSubplebbitToStore", { subplebbitAddress, subplebbit, account }); - setState((state: any) => ({ - subplebbits: { ...state.subplebbits, [subplebbitAddress]: firstSubplebbitState }, - })); - - // the subplebbit has published new posts - subplebbit.on("update", async (updatedSubplebbit: Subplebbit) => { - updatedSubplebbit = utils.clone(updatedSubplebbit); - - // add fetchedAt to be able to expire the cache - // NOTE: fetchedAt is undefined on owner subplebbits because never stale - updatedSubplebbit.fetchedAt = Math.floor(Date.now() / 1000); - - await subplebbitsDatabase.setItem(subplebbitAddress, updatedSubplebbit); - log("subplebbitsStore subplebbit update", { - subplebbitAddress, - updatedSubplebbit, - account, - }); - setState((state: any) => ({ - subplebbits: { ...state.subplebbits, [subplebbitAddress]: updatedSubplebbit }, - })); - - // if a subplebbit has a role with an account's address add it to the account.subplebbits - accountsStore - .getState() - .accountsActionsInternal.addSubplebbitRoleToAccountsSubplebbits(updatedSubplebbit); - - // add page comments to subplebbitsPagesStore so they can be used in useComment - subplebbitsPagesStore.getState().addSubplebbitPageCommentsToStore(updatedSubplebbit); - }); - - subplebbit.on("updatingstatechange", (updatingState: string) => { - setState((state: SubplebbitsState) => ({ - subplebbits: { - ...state.subplebbits, - [subplebbitAddress]: { ...state.subplebbits[subplebbitAddress], updatingState }, - }, - })); - }); - - subplebbit.on("error", (error: Error) => { - setState((state: SubplebbitsState) => { - let subplebbitErrors = state.errors[subplebbitAddress] || []; - subplebbitErrors = [...subplebbitErrors, error]; - return { ...state, errors: { ...state.errors, [subplebbitAddress]: subplebbitErrors } }; - }); - }); - - // set clients on subplebbit so the frontend can display it, dont persist in db because a reload cancels updating - utils.clientsOnStateChange( - subplebbit?.clients, - (clientState: string, clientType: string, clientUrl: string, chainTicker?: string) => { - setState((state: SubplebbitsState) => { - // make sure not undefined, sometimes happens in e2e tests - if (!state.subplebbits[subplebbitAddress]) { - return {}; - } - const clients = { ...state.subplebbits[subplebbitAddress]?.clients }; - const client = { state: clientState }; - if (chainTicker) { - const chainProviders = { ...clients[clientType][chainTicker], [clientUrl]: client }; - clients[clientType] = { ...clients[clientType], [chainTicker]: chainProviders }; - } else { - clients[clientType] = { ...clients[clientType], [clientUrl]: client }; - } - return { - subplebbits: { - ...state.subplebbits, - [subplebbitAddress]: { ...state.subplebbits[subplebbitAddress], clients }, - }, - }; - }); - }, - ); - - listeners.push(subplebbit); - subplebbit - .update() - .catch((error: unknown) => log.trace("subplebbit.update error", { subplebbit, error })); - }, - - async refreshSubplebbit(subplebbitAddress: string, account: Account) { - assert( - subplebbitAddress !== "" && typeof subplebbitAddress === "string", - `subplebbitsStore.refreshSubplebbit invalid subplebbitAddress argument '${subplebbitAddress}'`, - ); - assert( - typeof account?.plebbit?.getSubplebbit === "function", - `subplebbitsStore.refreshSubplebbit invalid account argument '${account}'`, - ); - - const refreshedSubplebbit = utils.clone( - await account.plebbit.getSubplebbit({ address: subplebbitAddress }), - ); - refreshedSubplebbit.fetchedAt = Math.floor(Date.now() / 1000); - - await subplebbitsDatabase.setItem(subplebbitAddress, refreshedSubplebbit); - log("subplebbitsStore.refreshSubplebbit", { - subplebbitAddress, - refreshedSubplebbit, - account, - }); - setState((state: any) => ({ - subplebbits: { ...state.subplebbits, [subplebbitAddress]: refreshedSubplebbit }, - })); - - subplebbitsPagesStore.getState().addSubplebbitPageCommentsToStore(refreshedSubplebbit); - - return refreshedSubplebbit; - }, - - // user is the owner of the subplebbit and can edit it locally - async editSubplebbit(subplebbitAddress: string, subplebbitEditOptions: any, account: Account) { - assert( - subplebbitAddress !== "" && typeof subplebbitAddress === "string", - `subplebbitsStore.editSubplebbit invalid subplebbitAddress argument '${subplebbitAddress}'`, - ); - assert( - subplebbitEditOptions && typeof subplebbitEditOptions === "object", - `subplebbitsStore.editSubplebbit invalid subplebbitEditOptions argument '${subplebbitEditOptions}'`, - ); - assert( - typeof account?.plebbit?.createSubplebbit === "function", - `subplebbitsStore.editSubplebbit invalid account argument '${account}'`, - ); - - // if not added to store first, subplebbit.update() is never called - await getState().addSubplebbitToStore(subplebbitAddress, account); - - // `subplebbitAddress` is different from `subplebbitEditOptions.address` when editing the subplebbit address - const subplebbit = await account.plebbit.createSubplebbit({ address: subplebbitAddress }); - - // could fix some test issues - subplebbit.on("error", console.log); - - await subplebbit.edit(subplebbitEditOptions); - - const updatedSubplebbit = utils.clone(subplebbit); - // edit db of both old and new subplebbit address to not break the UI - await subplebbitsDatabase.setItem(subplebbitAddress, updatedSubplebbit); - await subplebbitsDatabase.setItem(subplebbit.address, updatedSubplebbit); - log("subplebbitsStore.editSubplebbit", { - subplebbitAddress, - subplebbitEditOptions, - subplebbit, - account, - }); - setState((state: any) => ({ - subplebbits: { - ...state.subplebbits, - // edit react state of both old and new subplebbit address to not break the UI - [subplebbitAddress]: updatedSubplebbit, - [subplebbit.address]: updatedSubplebbit, - }, - })); - }, - - // internal action called by accountsActions.createSubplebbit - async createSubplebbit(createSubplebbitOptions: CreateSubplebbitOptions, account: Account) { - assert( - !createSubplebbitOptions || typeof createSubplebbitOptions === "object", - `subplebbitsStore.createSubplebbit invalid createSubplebbitOptions argument '${createSubplebbitOptions}'`, - ); - if (!createSubplebbitOptions?.signer) { - assert( - !createSubplebbitOptions?.address, - `subplebbitsStore.createSubplebbit createSubplebbitOptions.address '${createSubplebbitOptions?.address}' must be undefined to create a subplebbit`, - ); - } - assert( - typeof account?.plebbit?.createSubplebbit === "function", - `subplebbitsStore.createSubplebbit invalid account argument '${account}'`, - ); - - const subplebbit = await account.plebbit.createSubplebbit(createSubplebbitOptions); - - // could fix some test issues - subplebbit.on("error", console.log); - - // if not added to store first, subplebbit.update() is never called - await getState().addSubplebbitToStore(subplebbit.address, account); - - await subplebbitsDatabase.setItem(subplebbit.address, utils.clone(subplebbit)); - log("subplebbitsStore.createSubplebbit", { createSubplebbitOptions, subplebbit, account }); - setState((state: any) => ({ - subplebbits: { ...state.subplebbits, [subplebbit.address]: utils.clone(subplebbit) }, - })); - return subplebbit; - }, - - // internal action called by accountsActions.deleteSubplebbit - async deleteSubplebbit(subplebbitAddress: string, account: Account) { - assert( - subplebbitAddress && typeof subplebbitAddress === "string", - `subplebbitsStore.deleteSubplebbit invalid subplebbitAddress argument '${subplebbitAddress}'`, - ); - assert( - typeof account?.plebbit?.createSubplebbit === "function", - `subplebbitsStore.deleteSubplebbit invalid account argument '${account}'`, - ); - - const subplebbit = await account.plebbit.createSubplebbit({ address: subplebbitAddress }); - - // could fix some test issues - subplebbit.on("error", console.log); - - await subplebbit.delete(); - await subplebbitsDatabase.removeItem(subplebbitAddress); - log("subplebbitsStore.deleteSubplebbit", { subplebbitAddress, subplebbit, account }); - setState((state: any) => ({ - subplebbits: { ...state.subplebbits, [subplebbitAddress]: undefined }, - })); - }, - }), -); - -// reset store in between tests -const originalState = subplebbitsStore.getState(); -// async function because some stores have async init -export const resetSubplebbitsStore = async () => { - plebbitGetSubplebbitPending = {}; - // remove all event listeners - listeners.forEach((listener: any) => listener.removeAllListeners()); - // destroy all component subscriptions to the store - subplebbitsStore.destroy(); - // restore original state - subplebbitsStore.setState(originalState); -}; - -// reset database and store in between tests -export const resetSubplebbitsDatabaseAndStore = async () => { - await localForageLru.createInstance({ name: "plebbitReactHooks-subplebbits" }).clear(); - await resetSubplebbitsStore(); -}; - -export default subplebbitsStore; diff --git a/src/types.ts b/src/types.ts index f8b05839..88af9d68 100644 --- a/src/types.ts +++ b/src/types.ts @@ -71,17 +71,17 @@ export interface UseNotificationsResult extends Result { markAsRead(): Promise; } -// useAccountSubplebbits(options): result -export interface UseAccountSubplebbitsOptions extends Options { +// useAccountCommunities(options): result +export interface UseAccountCommunitiesOptions extends Options { onlyIfCached?: boolean; } -export interface UseAccountSubplebbitsResult extends Result { - accountSubplebbits: AccountSubplebbit[]; +export interface UseAccountCommunitiesResult extends Result { + accountCommunities: AccountCommunity[]; } // usePubsubSubscribe(options): result export interface UsePubsubSubscribeOptions extends Options { - subplebbitAddress?: string; + communityAddress?: string; } export interface UsePubsubSubscribeResult extends Result {} @@ -150,42 +150,42 @@ export interface UseEditedCommentResult extends Result { // state: 'initializing' | 'unedited' | 'succeeded' | 'pending' | 'failed' } -// useSubplebbit(options): result -export interface UseSubplebbitOptions extends Options { - subplebbitAddress?: string; +// useCommunity(options): result +export interface UseCommunityOptions extends Options { + communityAddress?: string; onlyIfCached?: boolean; } -export interface UseSubplebbitResult extends Result, Subplebbit {} +export interface UseCommunityResult extends Result, Community {} -// useSubplebbits(options): result -export interface UseSubplebbitsOptions extends Options { - subplebbitAddresses?: string[]; +// useCommunities(options): result +export interface UseCommunitiesOptions extends Options { + communityAddresses?: string[]; onlyIfCached?: boolean; } -export interface UseSubplebbitsResult extends Result { - subplebbits: (Subplebbit | undefined)[]; +export interface UseCommunitiesResult extends Result { + communities: (Community | undefined)[]; } -// useSubplebbitStats(options): result -export interface UseSubplebbitStatsOptions extends Options { - subplebbitAddress?: string; +// useCommunityStats(options): result +export interface UseCommunityStatsOptions extends Options { + communityAddress?: string; onlyIfCached?: boolean; } -export interface UseSubplebbitStatsResult extends Result, SubplebbitStats {} +export interface UseCommunityStatsResult extends Result, CommunityStats {} -// useResolvedSubplebbitAddress(options): result -export interface UseResolvedSubplebbitAddressOptions extends Options { - subplebbitAddress: string | undefined; +// useResolvedCommunityAddress(options): result +export interface UseResolvedCommunityAddressOptions extends Options { + communityAddress: string | undefined; cache?: boolean; } -export interface UseResolvedSubplebbitAddressResult extends Result { +export interface UseResolvedCommunityAddressResult extends Result { resolvedAddress: string | undefined; chainProvider: ChainProvider | undefined; } // useFeed(options): result export interface UseFeedOptions extends Options { - subplebbitAddresses: string[]; + communityAddresses: string[]; sortType?: string; postsPerPage?: number; newerThan?: number; @@ -197,7 +197,7 @@ export interface UseFeedResult extends Result { feed: Comment[]; hasMore: boolean; loadMore(): Promise; - subplebbitAddressesWithNewerPosts: string[]; + communityAddressesWithNewerPosts: string[]; reset(): Promise; } @@ -388,9 +388,9 @@ export interface UsePublishCommentModerationResult extends Result { publishChallengeAnswers(challengeAnswers: string[]): Promise; } -// usePublishSubplebbitEdit(options): result -export interface UsePublishSubplebbitEditOptions extends Options { - subplebbitAddress?: string; +// usePublishCommunityEdit(options): result +export interface UsePublishCommunityEditOptions extends Options { + communityAddress?: string; onChallenge?(challenge: Challenge, comment?: Comment): Promise; onChallengeVerification?( challengeVerification: ChallengeVerification, @@ -398,34 +398,34 @@ export interface UsePublishSubplebbitEditOptions extends Options { ): Promise; [publishOption: string]: any; } -export interface UsePublishSubplebbitEditResult extends Result { +export interface UsePublishCommunityEditResult extends Result { challenge: Challenge | undefined; challengeVerification: ChallengeVerification | undefined; - publishSubplebbitEdit(): Promise; + publishCommunityEdit(): Promise; publishChallengeAnswers(challengeAnswers: string[]): Promise; } -// useCreateSubplebbit(options): result -export interface UseCreateSubplebbitOptions extends Options { - [createSubplebbitOption: string]: any; +// useCreateCommunity(options): result +export interface UseCreateCommunityOptions extends Options { + [createCommunityOption: string]: any; } -export interface UseCreateSubplebbitResult extends Result { - createdSubplebbit: Subplebbit | undefined; - createSubplebbit(): Promise; +export interface UseCreateCommunityResult extends Result { + createdCommunity: Community | undefined; + createCommunity(): Promise; } -// useDeleteSubplebbit(options): result -// export interface UseDeleteSubplebbitOptions extends Options { -// subplebbitAddress?: string +// useDeleteCommunity(options): result +// export interface UseDeleteCommunityOptions extends Options { +// communityAddress?: string // } -// export interface UseDeleteSubplebbitResult extends Result { -// deletedSubplebbit: Subplebbit | undefined -// deleteSubplebbit(): Promise +// export interface UseDeleteCommunityResult extends Result { +// deletedCommunity: Community | undefined +// deleteCommunity(): Promise // } // useSubscribe(options): result export interface UseSubscribeOptions extends Options { - subplebbitAddress?: string; + communityAddress?: string; multisubAddress?: string; authorAddress?: string; } @@ -448,12 +448,12 @@ export interface UseBlockResult extends Result { // useNotify(options): result // export interface UseNotifyOptions extends Options { -// subplebbitAddress?: string +// communityAddress?: string // multisubAddress?: string // authorAddress?: string // commentCid?: string // } -// export interface UseNotifySubplebbitResult extends Result { +// export interface UseNotifyCommunityResult extends Result { // notifying: boolean | undefined // notify(): Promise // unnotify(): Promise @@ -492,7 +492,7 @@ export interface UseBlockResult extends Result { export interface UseClientsStatesOptions extends Options { comment?: Comment; - subplebbit?: Subplebbit; + community?: Community; } type ClientUrls = string[]; type Peer = string; @@ -501,11 +501,11 @@ export interface UseClientsStatesResult extends Result { peers: { [clientUrl: string]: Peer[] }; } -export interface UseSubplebbitsStatesOptions extends Options { - subplebbitAddresses?: string[]; +export interface UseCommunitiesStatesOptions extends Options { + communityAddresses?: string[]; } -export interface UseSubplebbitsStatesResult extends Result { - states: { [state: string]: { subplebbitAddresses: string[]; clientUrls: string[] } }; +export interface UseCommunitiesStatesResult extends Result { + states: { [state: string]: { communityAddresses: string[]; clientUrls: string[] } }; peers: { [clientUrl: string]: Peer[] }; } @@ -527,28 +527,28 @@ export type PublishCommentOptions = { [key: string]: any }; export type PublishVoteOptions = { [key: string]: any }; export type PublishCommentEditOptions = { [key: string]: any }; export type PublishCommentModerationOptions = { [key: string]: any }; -export type PublishSubplebbitEditOptions = { [key: string]: any }; +export type PublishCommunityEditOptions = { [key: string]: any }; export type Challenge = { [key: string]: any }; export type ChallengeVerification = { [key: string]: any }; export type CreateCommentOptions = { [key: string]: any }; -export type CreateSubplebbitOptions = { [key: string]: any }; +export type CreateCommunityOptions = { [key: string]: any }; export type CreateVoteOptions = { [key: string]: any }; export type Comment = { [key: string]: any }; export type Vote = { [key: string]: any }; export type CommentEdit = { [key: string]: any }; export type CommentModeration = { [key: string]: any }; -export type SubplebbitEdit = { [key: string]: any }; -export type Subplebbit = { [key: string]: any }; -export type SubplebbitStats = { [key: string]: any }; +export type CommunityEdit = { [key: string]: any }; +export type Community = { [key: string]: any }; +export type CommunityStats = { [key: string]: any }; export type Notification = { [key: string]: any }; export type Nft = { [key: string]: any }; export type Author = { [key: string]: any }; export type Wallet = { [key: string]: any }; /** - * Subplebbits and comments store + * Communities and comments store */ -export type Subplebbits = { [subplebbitAddress: string]: Subplebbit }; +export type Communities = { [communityAddress: string]: Community }; export type Comments = { [commentCid: string]: Comment }; /** @@ -574,7 +574,7 @@ export type AccountsNotifications = { [accountId: string]: Notification[] }; export type Role = { role: "owner" | "admin" | "moderator"; }; -export type AccountSubplebbit = { +export type AccountCommunity = { role: Role; }; export type AccountsVotes = { [accountId: string]: AccountVotes }; @@ -584,9 +584,9 @@ export type AccountVote = { [publishOption: string]: any; }; export type AccountsEdits = { [accountId: string]: AccountEdits }; -export type AccountEdits = { [commentCidOrSubplebbitAddress: string]: AccountEdit[] }; +export type AccountEdits = { [commentCidOrCommunityAddress: string]: AccountEdit[] }; export type AccountEdit = { - // has all the publish options like commentCid, vote, timestamp, etc (both comment edits and subplebbit edits) + // has all the publish options like commentCid, vote, timestamp, etc (both comment edits and community edits) [publishOption: string]: any; }; export type AccountPublicationsFilter = ( @@ -599,7 +599,7 @@ export type AccountPublicationsFilter = ( export type Feed = Comment[]; export type Feeds = { [feedName: string]: Feed }; export type FeedOptions = { - subplebbitAddresses: string[]; + communityAddresses: string[]; sortType: string; accountId: string; pageNumber: number; @@ -614,13 +614,13 @@ export type FeedOptionsAccountComments = { append?: boolean; // default to prepend, set append: true to append instead }; export type FeedsOptions = { [feedName: string]: FeedOptions }; -export type FeedSubplebbitsPostCounts = { [subplebbitAddress: string]: number }; -export type FeedsSubplebbitsPostCounts = { [feedName: string]: FeedSubplebbitsPostCounts }; -export type SubplebbitPage = { +export type FeedCommunitiesPostCounts = { [communityAddress: string]: number }; +export type FeedsCommunitiesPostCounts = { [feedName: string]: FeedCommunitiesPostCounts }; +export type CommunityPage = { nextCid?: string; comments: Comment[]; }; -export type SubplebbitsPages = { [pageCid: string]: SubplebbitPage }; +export type CommunitiesPages = { [pageCid: string]: CommunityPage }; export type CommentsFilter = { filter(comment: Comment): Boolean; key: string; @@ -645,7 +645,7 @@ export type RepliesFeedOptions = { streamPage?: boolean; // by default, replies with depth > 1 won't continuously fill the page until repliesPerPage is reached, to not displace the UI }; export type RepliesFeedsOptions = { [feedName: string]: RepliesFeedOptions }; -export type RepliesPage = SubplebbitPage; +export type RepliesPage = CommunityPage; export type RepliesPages = { [pageCid: string]: RepliesPage }; /** diff --git a/test/browser-e2e/accounts.test.js b/test/browser-e2e/accounts.test.js index f58028f5..969e82bf 100644 --- a/test/browser-e2e/accounts.test.js +++ b/test/browser-e2e/accounts.test.js @@ -8,18 +8,18 @@ import { useNotifications, useComment, useReplies, - useAccountSubplebbits, - useSubplebbit, + useAccountCommunities, + useCommunity, useFeed, } from "../../dist"; import debugUtils from "../../dist/lib/debug-utils"; import * as accountsActions from "../../dist/stores/accounts/accounts-actions"; -import subplebbitsStore from "../../dist/stores/subplebbits"; +import communitiesStore from "../../dist/stores/communities"; import testUtils from "../../dist/lib/test-utils"; import { offlineIpfs, pubsubIpfs, plebbitRpc } from "../test-server/config"; import signers from "../fixtures/signers"; -const subplebbitAddress = signers[0].address; +const communityAddress = signers[0].address; const adminRoleSigner = signers[1]; const isBase64 = (testString) => @@ -107,20 +107,20 @@ for (const plebbitOptionsType in plebbitOptionsTypes) { }); if (plebbitOptionsType !== "plebbit rpc client") { - console.log(`${plebbitOptionsType} can't create subplebbit, skipping`); + console.log(`${plebbitOptionsType} can't create community, skipping`); } else { - describe(`create subplebbit (${plebbitOptionsType})`, () => { + describe(`create community (${plebbitOptionsType})`, () => { let rendered, waitFor; beforeAll(async () => { - rendered = renderHook((subplebbitAddress) => { + rendered = renderHook((communityAddress) => { const account = useAccount(); - const { accountSubplebbits } = useAccountSubplebbits(); - const subplebbit = useSubplebbit({ subplebbitAddress }); - const subplebbitAddresses = subplebbitAddress ? [subplebbitAddress] : undefined; - const modQueue = useFeed({ subplebbitAddresses, modQueue: ["pendingApproval"] }); - const feed = useFeed({ subplebbitAddresses }); - return { account, accountSubplebbits, subplebbit, modQueue, feed, ...accountsActions }; + const { accountCommunities } = useAccountCommunities(); + const community = useCommunity({ communityAddress }); + const communityAddresses = communityAddress ? [communityAddress] : undefined; + const modQueue = useFeed({ communityAddresses, modQueue: ["pendingApproval"] }); + const feed = useFeed({ communityAddresses }); + return { account, accountCommunities, community, modQueue, feed, ...accountsActions }; }); rendered.detach(); waitFor = testUtils.createWaitFor(rendered, { timeout }); @@ -145,65 +145,63 @@ for (const plebbitOptionsType in plebbitOptionsTypes) { console.log("after set account"); }); - it("create and edit a subplebbit", async () => { - console.log("before create subplebbit"); - const createdSubplebbitTitle = "my title"; - let subplebbit; + it("create and edit a community", async () => { + console.log("before create community"); + const createdCommunityTitle = "my title"; + let community; await act(async () => { - subplebbit = await rendered.result.current.createSubplebbit({ - title: createdSubplebbitTitle, + community = await rendered.result.current.createCommunity({ + title: createdCommunityTitle, }); }); - console.log("after create subplebbit", subplebbit.address); - const createdSubplebbitAddress = subplebbit?.address; - expect(typeof createdSubplebbitAddress).to.equal("string"); - expect(subplebbit.title).to.equal(createdSubplebbitTitle); - - console.log("before used subplebbit"); - // can useSubplebbit - rendered.rerender(createdSubplebbitAddress); - await waitFor(() => rendered.result.current.subplebbit.title === createdSubplebbitTitle); - expect(rendered.result.current.subplebbit.address).to.equal(createdSubplebbitAddress); - expect(rendered.result.current.subplebbit.title).to.equal(createdSubplebbitTitle); - console.log("after used subplebbit"); - - // wait for subplebbit to be added to account subplebbits - console.log("before subplebbit added to account subplebbits"); + console.log("after create community", community.address); + const createdCommunityAddress = community?.address; + expect(typeof createdCommunityAddress).to.equal("string"); + expect(community.title).to.equal(createdCommunityTitle); + + console.log("before used community"); + // can useCommunity + rendered.rerender(createdCommunityAddress); + await waitFor(() => rendered.result.current.community.title === createdCommunityTitle); + expect(rendered.result.current.community.address).to.equal(createdCommunityAddress); + expect(rendered.result.current.community.title).to.equal(createdCommunityTitle); + console.log("after used community"); + + // wait for community to be added to account communities + console.log("before community added to account communities"); await waitFor( () => - rendered.result.current.accountSubplebbits[createdSubplebbitAddress].role.role === + rendered.result.current.accountCommunities[createdCommunityAddress].role.role === "owner", ); expect( - rendered.result.current.accountSubplebbits[createdSubplebbitAddress].role.role, + rendered.result.current.accountCommunities[createdCommunityAddress].role.role, ).to.equal("owner"); - console.log("after subplebbit added to account subplebbits"); + console.log("after community added to account communities"); - console.log("before edit subplebbit address"); - // publishSubplebbitEdit address - const editedSubplebbitAddress = "my-sub.eth"; + console.log("before edit community address"); + // publishCommunityEdit address + const editedCommunityAddress = "my-sub.eth"; let onChallenge = () => {}; const onChallengeVerificationCalls = []; let onChallengeVerification = (...args) => onChallengeVerificationCalls.push([...args]); await act(async () => { - await rendered.result.current.publishSubplebbitEdit(createdSubplebbitAddress, { - address: editedSubplebbitAddress, + await rendered.result.current.publishCommunityEdit(createdCommunityAddress, { + address: editedCommunityAddress, onChallenge, onChallengeVerification, }); }); - console.log("after edit subplebbit address"); + console.log("after edit community address"); - console.log("before use subplebbit"); - // change useSubplebbit address - rendered.rerender(editedSubplebbitAddress); - await waitFor( - () => rendered.result.current.subplebbit.address === editedSubplebbitAddress, - ); - expect(rendered.result.current.subplebbit.address).to.equal(editedSubplebbitAddress); - expect(rendered.result.current.subplebbit.title).to.equal(createdSubplebbitTitle); - console.log("after use subplebbit"); + console.log("before use community"); + // change useCommunity address + rendered.rerender(editedCommunityAddress); + await waitFor(() => rendered.result.current.community.address === editedCommunityAddress); + expect(rendered.result.current.community.address).to.equal(editedCommunityAddress); + expect(rendered.result.current.community.title).to.equal(createdCommunityTitle); + console.log("after use community"); console.log("before onChallengeVerification"); // onChallengeVerification should be called with success even if the sub is edited locally @@ -212,27 +210,25 @@ for (const plebbitOptionsType in plebbitOptionsTypes) { expect(onChallengeVerificationCalls[0][0].challengeSuccess).to.equal(true); console.log("after onChallengeVerification"); - console.log("before edit subplebbit title"); - // publishSubplebbitEdit title and description - const editedSubplebbitTitle = "edited title"; - const editedSubplebbitDescription = "edited description"; + console.log("before edit community title"); + // publishCommunityEdit title and description + const editedCommunityTitle = "edited title"; + const editedCommunityDescription = "edited description"; await act(async () => { - await rendered.result.current.publishSubplebbitEdit(editedSubplebbitAddress, { - title: editedSubplebbitTitle, - description: editedSubplebbitDescription, + await rendered.result.current.publishCommunityEdit(editedCommunityAddress, { + title: editedCommunityTitle, + description: editedCommunityDescription, onChallenge, onChallengeVerification, }); }); - console.log("after edit subplebbit title"); + console.log("after edit community title"); - console.log("before subplebbit change"); + console.log("before community change"); // wait for change - await waitFor( - () => rendered.result.current.subplebbit.address === editedSubplebbitAddress, - ); - expect(rendered.result.current.subplebbit.address).to.equal(editedSubplebbitAddress); - console.log("after subplebbit change"); + await waitFor(() => rendered.result.current.community.address === editedCommunityAddress); + expect(rendered.result.current.community.address).to.equal(editedCommunityAddress); + console.log("after community change"); console.log("before onChallengeVerification"); // onChallengeVerification should be called with success even if the sub is edited locally @@ -241,27 +237,27 @@ for (const plebbitOptionsType in plebbitOptionsTypes) { expect(onChallengeVerificationCalls[1][0].challengeSuccess).to.equal(true); console.log("after onChallengeVerification"); - // delete subplebbit - console.log("before deleteSubplebbit"); + // delete community + console.log("before deleteCommunity"); await act(async () => { - await rendered.result.current.deleteSubplebbit(editedSubplebbitAddress); + await rendered.result.current.deleteCommunity(editedCommunityAddress); }); - await waitFor(() => rendered.result.current.subplebbit?.updatedAt === undefined); - expect(rendered.result.current.subplebbit?.updatedAt).to.equal(undefined); + await waitFor(() => rendered.result.current.community?.updatedAt === undefined); + expect(rendered.result.current.community?.updatedAt).to.equal(undefined); await waitFor( () => - rendered.result.current.accountSubplebbits[editedSubplebbitAddress]?.updatedAt === + rendered.result.current.accountCommunities[editedCommunityAddress]?.updatedAt === undefined, ); - console.log("after deleteSubplebbit"); + console.log("after deleteCommunity"); }); - it("create pending approval subplebbit, publish and approve", async () => { - const title = "pending approval subplebbit"; - console.log("before create subplebbit"); - let subplebbit; + it("create pending approval community, publish and approve", async () => { + const title = "pending approval community"; + console.log("before create community"); + let community; await act(async () => { - subplebbit = await rendered.result.current.createSubplebbit({ + community = await rendered.result.current.createCommunity({ title, settings: { challenges: [ @@ -273,27 +269,27 @@ for (const plebbitOptionsType in plebbitOptionsTypes) { ], }, }); - await subplebbit.start(); + await community.start(); - // flaky if not waiting after subplebbit.start() + // flaky if not waiting after community.start() await new Promise((r) => setTimeout(r, 1000)); }); - console.log("after create subplebbit", subplebbit.address); - expect(typeof subplebbit.address).to.equal("string"); - expect(subplebbit.title).to.equal(title); - expect(subplebbit.challenges[0].description.includes("math")).to.equal(true); - expect(subplebbit.challenges[0].pendingApproval).to.equal(true); - - console.log("before used subplebbit"); - // can useSubplebbit - rendered.rerender(subplebbit.address); - await waitFor(() => rendered.result.current.subplebbit.title === title); - expect(rendered.result.current.subplebbit.title).to.equal(title); + console.log("after create community", community.address); + expect(typeof community.address).to.equal("string"); + expect(community.title).to.equal(title); + expect(community.challenges[0].description.includes("math")).to.equal(true); + expect(community.challenges[0].pendingApproval).to.equal(true); + + console.log("before used community"); + // can useCommunity + rendered.rerender(community.address); + await waitFor(() => rendered.result.current.community.title === title); + expect(rendered.result.current.community.title).to.equal(title); expect( - rendered.result.current.subplebbit.challenges[0].description.includes("math"), + rendered.result.current.community.challenges[0].description.includes("math"), ).to.equal(true); - expect(rendered.result.current.subplebbit.challenges[0].pendingApproval).to.equal(true); - console.log("after used subplebbit"); + expect(rendered.result.current.community.challenges[0].pendingApproval).to.equal(true); + console.log("after used community"); let challenge, comment, challengeVerification; const logChallenge = (str, obj) => { @@ -315,7 +311,7 @@ for (const plebbitOptionsType in plebbitOptionsTypes) { // publish wrong challenge answer, verification should be success false let publishCommentOptions = { - subplebbitAddress: subplebbit.address, + communityAddress: community.address, title: "some title", content: "some content", onChallenge, @@ -388,17 +384,17 @@ for (const plebbitOptionsType in plebbitOptionsTypes) { // approve pending approval comment expect(rendered.result.current.feed.feed.length).to.equal(0); expect(typeof rendered.result.current.account.author.address).to.equal("string"); - await subplebbit.edit({ + await community.edit({ roles: { [rendered.result.current.account.author.address]: { role: "moderator" } }, }); - expect(subplebbit.roles[rendered.result.current.account.author.address].role).to.equal( + expect(community.roles[rendered.result.current.account.author.address].role).to.equal( "moderator", ); await act(async () => { console.log("before publishCommentModeration"); await rendered.result.current.publishCommentModeration({ - subplebbitAddress: subplebbit.address, + communityAddress: community.address, commentCid: pendingApprovalCommentCid, commentModeration: { approved: true }, onChallenge, @@ -419,14 +415,14 @@ for (const plebbitOptionsType in plebbitOptionsTypes) { }); } - describe(`publish subplebbit edit (${plebbitOptionsType})`, () => { + describe(`publish community edit (${plebbitOptionsType})`, () => { let rendered, waitFor; beforeAll(async () => { - rendered = renderHook((subplebbitAddress) => { + rendered = renderHook((communityAddress) => { const account = useAccount(); - const subplebbit = useSubplebbit({ subplebbitAddress }); - return { account, subplebbit, ...accountsActions }; + const community = useCommunity({ communityAddress }); + return { account, community, ...accountsActions }; }); rendered.detach(); waitFor = testUtils.createWaitFor(rendered, { timeout }); @@ -447,7 +443,7 @@ for (const plebbitOptionsType in plebbitOptionsTypes) { const account = { ...rendered.result.current.account, plebbitOptions, - // the 'admin' role signer of subplebbitAddress + // the 'admin' role signer of communityAddress signer: { type: "ed25519", privateKey: adminRoleSigner.privateKey, @@ -464,34 +460,34 @@ for (const plebbitOptionsType in plebbitOptionsTypes) { console.log("after set account"); }); - it("publish subplebbit edit", async () => { - console.log("before used subplebbit"); - rendered.rerender(subplebbitAddress); - await waitFor(() => rendered.result.current.subplebbit.address === subplebbitAddress); + it("publish community edit", async () => { + console.log("before used community"); + rendered.rerender(communityAddress); + await waitFor(() => rendered.result.current.community.address === communityAddress); await waitFor( - () => rendered.result.current.subplebbit.roles[adminRoleSigner.address].role === "admin", + () => rendered.result.current.community.roles[adminRoleSigner.address].role === "admin", ); - expect(rendered.result.current.subplebbit.address).to.equal(subplebbitAddress); - expect(rendered.result.current.subplebbit.roles[adminRoleSigner.address].role).to.equal( + expect(rendered.result.current.community.address).to.equal(communityAddress); + expect(rendered.result.current.community.roles[adminRoleSigner.address].role).to.equal( "admin", ); - console.log("after used subplebbit"); + console.log("after used community"); - // publish subplebbit edit - const onChallenge = (challenge, subplebbitEdit) => - subplebbitEdit.publishChallengeAnswers(["2"]); + // publish community edit + const onChallenge = (challenge, communityEdit) => + communityEdit.publishChallengeAnswers(["2"]); const onChallengeVerificationCalls = []; const onChallengeVerification = (...args) => onChallengeVerificationCalls.push([...args]); const editedTitle = `edited title ${Math.random()}`; - console.log("before plebbit.publishSubplebbitEdit()"); + console.log("before plebbit.publishCommunityEdit()"); await act(async () => { - await rendered.result.current.publishSubplebbitEdit(subplebbitAddress, { + await rendered.result.current.publishCommunityEdit(communityAddress, { title: editedTitle, onChallenge, onChallengeVerification, }); }); - console.log("after plebbit.publishSubplebbitEdit()"); + console.log("after plebbit.publishCommunityEdit()"); console.log("before onChallengeVerification"); await waitFor(() => onChallengeVerificationCalls.length >= 1); @@ -500,9 +496,9 @@ for (const plebbitOptionsType in plebbitOptionsTypes) { console.log(onChallengeVerificationCalls[0][0]); console.log("after onChallengeVerification"); - await waitFor(() => rendered.result.current.subplebbit.title === editedTitle); - expect(rendered.result.current.subplebbit.title).to.equal(editedTitle); - console.log(rendered.result.current.subplebbit.title); + await waitFor(() => rendered.result.current.community.title === editedTitle); + expect(rendered.result.current.community.title).to.equal(editedTitle); + console.log(rendered.result.current.community.title); }); }); @@ -573,7 +569,7 @@ for (const plebbitOptionsType in plebbitOptionsTypes) { it(`publish comment (${plebbitOptionsType})`, async () => { const publishCommentOptions = { - subplebbitAddress, + communityAddress, title: "some title", content: "some content", onChallenge, @@ -645,7 +641,7 @@ for (const plebbitOptionsType in plebbitOptionsTypes) { } replyChallengeVerification = challengeVerification; }; - // wait for the parent comment to be indexed by the subplebbit before publishing a reply + // wait for the parent comment to be indexed by the community before publishing a reply console.log(`publish reply: publishedCid=${publishedCid}, typeof=${typeof publishedCid}`); rendered.rerender(publishedCid); await waitFor(() => typeof rendered.result.current.comment?.updatedAt === "number"); @@ -664,7 +660,7 @@ for (const plebbitOptionsType in plebbitOptionsTypes) { console.log("parent comment indexed, publishing reply"); const publishCommentOptions = { - subplebbitAddress, + communityAddress, parentCid: publishedCid, postCid: publishedCid, content: "some content", diff --git a/test/browser-e2e/subplebbits.test.js b/test/browser-e2e/communities.test.js similarity index 84% rename from test/browser-e2e/subplebbits.test.js rename to test/browser-e2e/communities.test.js index b561c7d9..f25b52ff 100644 --- a/test/browser-e2e/subplebbits.test.js +++ b/test/browser-e2e/communities.test.js @@ -1,12 +1,12 @@ import { assertTestServerDidntCrash } from "../test-server/monitor-test-server"; import { act } from "@testing-library/react"; import { renderHook } from "../test-utils"; -import { useAccount, useSubplebbit, useAccountVotes, useComment } from "../../dist"; +import { useAccount, useCommunity, useAccountVotes, useComment } from "../../dist"; import debugUtils from "../../dist/lib/debug-utils"; import * as accountsActions from "../../dist/stores/accounts/accounts-actions"; import testUtils from "../../dist/lib/test-utils"; import signers from "../fixtures/signers"; -const subplebbitAddress = signers[0].address; +const communityAddress = signers[0].address; import { offlineIpfs, pubsubIpfs, plebbitRpc } from "../test-server/config"; // large value for manual debugging @@ -42,9 +42,9 @@ const plebbitOptionsTypes = { }; for (const plebbitOptionsType in plebbitOptionsTypes) { - describe(`subplebbits (${plebbitOptionsType})`, () => { + describe(`communities (${plebbitOptionsType})`, () => { beforeAll(async () => { - console.log(`before subplebbits tests (${plebbitOptionsType})`); + console.log(`before communities tests (${plebbitOptionsType})`); testUtils.silenceReactWarnings(); // reset before or init accounts sometimes fails await testUtils.resetDatabasesAndStores(); @@ -61,16 +61,16 @@ for (const plebbitOptionsType in plebbitOptionsTypes) { await assertTestServerDidntCrash(); }); - describe(`no subplebbits in database (${plebbitOptionsType})`, () => { + describe(`no communities in database (${plebbitOptionsType})`, () => { let rendered, waitFor, commentCid; beforeAll(async () => { - rendered = renderHook(({ subplebbitAddress, commentCid } = {}) => { + rendered = renderHook(({ communityAddress, commentCid } = {}) => { const account = useAccount(); - const subplebbit = useSubplebbit({ subplebbitAddress }); + const community = useCommunity({ communityAddress }); const { accountVotes } = useAccountVotes(); const comment = useComment({ commentCid }); - return { account, subplebbit, comment, accountVotes, ...accountsActions }; + return { account, community, comment, accountVotes, ...accountsActions }; }); rendered.detach(); waitFor = testUtils.createWaitFor(rendered, { timeout }); @@ -95,17 +95,17 @@ for (const plebbitOptionsType in plebbitOptionsTypes) { console.log("after set account"); }); - it(`get subplebbits one at a time (${plebbitOptionsType})`, async () => { - rendered.rerender({ subplebbitAddress }); - await waitFor(() => typeof rendered.result.current.subplebbit.updatedAt === "number"); - expect(typeof rendered.result.current.subplebbit.updatedAt).to.equal("number"); - expect(rendered.result.current.subplebbit.address).to.equal(subplebbitAddress); - await waitFor(() => rendered.result.current.subplebbit.posts.pages.hot.comments[0]); - expect(typeof rendered.result.current.subplebbit.posts.pages.hot.comments[0].cid).to.equal( + it(`get communities one at a time (${plebbitOptionsType})`, async () => { + rendered.rerender({ communityAddress }); + await waitFor(() => typeof rendered.result.current.community.updatedAt === "number"); + expect(typeof rendered.result.current.community.updatedAt).to.equal("number"); + expect(rendered.result.current.community.address).to.equal(communityAddress); + await waitFor(() => rendered.result.current.community.posts.pages.hot.comments[0]); + expect(typeof rendered.result.current.community.posts.pages.hot.comments[0].cid).to.equal( "string", ); - commentCid = rendered.result.current.subplebbit.posts.pages.hot.comments[0].cid; - expect(rendered.result.current.subplebbit.state).to.equal("succeeded"); + commentCid = rendered.result.current.community.posts.pages.hot.comments[0].cid; + expect(rendered.result.current.community.state).to.equal("succeeded"); console.log("comment cid", commentCid); }); @@ -127,7 +127,7 @@ for (const plebbitOptionsType in plebbitOptionsTypes) { it(`publish vote (${plebbitOptionsType})`, async () => { const publishVoteOptions = { - subplebbitAddress, + communityAddress, commentCid: commentCid, vote: 1, onChallenge, @@ -173,7 +173,7 @@ for (const plebbitOptionsType in plebbitOptionsTypes) { it(`get comment with updated vote count (${plebbitOptionsType})`, async () => { console.log("before getting comment"); - rendered.rerender({ subplebbitAddress, commentCid }); + rendered.rerender({ communityAddress, commentCid }); await waitFor(() => typeof rendered.result.current.comment.cid === "string"); console.log("after getting comment"); diff --git a/test/browser-e2e/feeds.test.js b/test/browser-e2e/feeds.test.js index 645594aa..e1531ef9 100644 --- a/test/browser-e2e/feeds.test.js +++ b/test/browser-e2e/feeds.test.js @@ -13,7 +13,7 @@ import * as accountsActions from "../../dist/stores/accounts/accounts-actions"; import testUtils from "../../dist/lib/test-utils"; import { offlineIpfs, pubsubIpfs, plebbitRpc } from "../test-server/config"; import signers from "../fixtures/signers"; -const subplebbitAddress = signers[0].address; +const communityAddress = signers[0].address; const isBase64 = (testString) => /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}))?$/gm.test(testString); @@ -107,21 +107,21 @@ for (const plebbitOptionsType in plebbitOptionsTypes) { it("change sort type", async () => { console.log(`starting feeds tests (${plebbitOptionsType})`); - rendered.rerender({ subplebbitAddresses: [subplebbitAddress], sortType: "hot" }); + rendered.rerender({ communityAddresses: [communityAddress], sortType: "hot" }); await waitFor(() => !!rendered.result.current.feed[0].cid); - expect(rendered.result.current.feed[0].subplebbitAddress).to.equal(subplebbitAddress); + expect(rendered.result.current.feed[0].communityAddress).to.equal(communityAddress); console.log("after first render"); // reset - rendered.rerender({ subplebbitAddresses: [] }); + rendered.rerender({ communityAddresses: [] }); await waitFor(() => rendered.result.current.feed.length === 0); expect(rendered.result.current.feed.length).to.equal(0); console.log("after second render"); // change sort type - rendered.rerender({ subplebbitAddresses: [subplebbitAddress], sortType: "new" }); + rendered.rerender({ communityAddresses: [communityAddress], sortType: "new" }); await waitFor(() => !!rendered.result.current.feed[0].cid); - expect(rendered.result.current.feed[0].subplebbitAddress).to.equal(subplebbitAddress); + expect(rendered.result.current.feed[0].communityAddress).to.equal(communityAddress); }); it("validate comments", async () => { @@ -133,9 +133,9 @@ for (const plebbitOptionsType in plebbitOptionsTypes) { // utility directly instead — the hook is a thin wrapper around it. const { commentIsValid } = await import("../../dist/lib/utils/utils"); - rendered.rerender({ subplebbitAddresses: [subplebbitAddress], sortType: "hot" }); + rendered.rerender({ communityAddresses: [communityAddress], sortType: "hot" }); await waitFor(() => !!rendered.result.current.feed[0]?.cid); - expect(rendered.result.current.feed[0].subplebbitAddress).to.equal(subplebbitAddress); + expect(rendered.result.current.feed[0].communityAddress).to.equal(communityAddress); const comment = rendered.result.current.feed[0]; const plebbit = rendered.result.current.account.plebbit; console.log("after first render"); @@ -148,7 +148,7 @@ for (const plebbitOptionsType in plebbitOptionsTypes) { invalidComment.raw.comment.signature.signature = "corrupted"; const invalidResult = await commentIsValid( invalidComment, - { validateReplies: true, blockSubplebbit: false }, + { validateReplies: true, blockCommunity: false }, plebbit, ); expect(invalidResult).to.equal(false); @@ -157,7 +157,7 @@ for (const plebbitOptionsType in plebbitOptionsTypes) { // validate valid comment const validResult = await commentIsValid( comment, - { validateReplies: true, blockSubplebbit: false }, + { validateReplies: true, blockCommunity: false }, plebbit, ); expect(validResult).to.equal(true); @@ -166,7 +166,7 @@ for (const plebbitOptionsType in plebbitOptionsTypes) { // validate valid comment without replies const validWithoutRepliesResult = await commentIsValid( comment, - { validateReplies: false, blockSubplebbit: false }, + { validateReplies: false, blockCommunity: false }, plebbit, ); expect(validWithoutRepliesResult).to.equal(true); diff --git a/test/browser-plebbit-js-mock-content/plebbit-js-mock-content.test.js b/test/browser-plebbit-js-mock-content/plebbit-js-mock-content.test.js index cea3be16..1845f65b 100644 --- a/test/browser-plebbit-js-mock-content/plebbit-js-mock-content.test.js +++ b/test/browser-plebbit-js-mock-content/plebbit-js-mock-content.test.js @@ -6,9 +6,9 @@ window.process.env.REACT_APP_PLEBBIT_REACT_HOOKS_MOCK_CONTENT_LOADING_TIME = "10 import { useComment, - useSubplebbit, + useCommunity, useFeed, - useAccountSubplebbits, + useAccountCommunities, useAccount, setPlebbitJs, } from "../../dist"; @@ -82,15 +82,15 @@ describe("mock content", () => { const rendered2 = renderHook((commentCid) => useComment({ commentCid })); rendered2.rerender("QmXxWyFRBUReRNzyJueFLFh84Mtj7ycbySktRQ5ffZLVa3"); - await waitFor(() => typeof rendered2.result.current.subplebbitAddress === "string"); + await waitFor(() => typeof rendered2.result.current.communityAddress === "string"); console.log(rendered2.result.current); - expect(typeof rendered2.result.current.subplebbitAddress).to.equal("string"); + expect(typeof rendered2.result.current.communityAddress).to.equal("string"); expect(typeof rendered2.result.current.timestamp).to.equal("number"); expect(typeof rendered2.result.current.upvoteCount).to.equal("number"); }); - it("use subplebbits", async () => { - const rendered = renderHook((subplebbitAddress) => useSubplebbit({ subplebbitAddress })); + it("use communities", async () => { + const rendered = renderHook((communityAddress) => useCommunity({ communityAddress })); const waitFor = testUtils.createWaitFor(rendered, { timeout }); rendered.rerender("anything2.eth"); @@ -129,7 +129,7 @@ describe("mock content", () => { // test getting from db await testUtils.resetStores(); - const rendered2 = renderHook((subplebbitAddress) => useSubplebbit({ subplebbitAddress })); + const rendered2 = renderHook((communityAddress) => useCommunity({ communityAddress })); rendered2.rerender("anything2"); await waitFor(() => typeof rendered2.result.current.updatedAt === "number"); @@ -143,8 +143,8 @@ describe("mock content", () => { }); it("use feed hot", async () => { - const rendered = renderHook((subplebbitAddresses) => - useFeed({ subplebbitAddresses, sortType: "hot" }), + const rendered = renderHook((communityAddresses) => + useFeed({ communityAddresses, sortType: "hot" }), ); const waitFor = testUtils.createWaitFor(rendered, { timeout }); @@ -171,8 +171,8 @@ describe("mock content", () => { }); it("use feed new", async () => { - const rendered = renderHook((subplebbitAddresses) => - useFeed({ subplebbitAddresses, sortType: "new" }), + const rendered = renderHook((communityAddresses) => + useFeed({ communityAddresses, sortType: "new" }), ); const waitFor = testUtils.createWaitFor(rendered, { timeout }); @@ -216,7 +216,7 @@ describe("mock content", () => { onChallengeVerificationCalled = true; }; await accountsActions.publishComment({ - subplebbitAddress: "news.eth", + communityAddress: "news.eth", content: "content", title: "title", onChallenge, @@ -229,7 +229,7 @@ describe("mock content", () => { console.log("publishing vote"); onChallengeVerificationCalled = false; await accountsActions.publishVote({ - subplebbitAddress: "news.eth", + communityAddress: "news.eth", vote: 1, commentCid: "some cid...", onChallenge, @@ -240,43 +240,43 @@ describe("mock content", () => { expect(onChallengeVerificationCalled).to.equal(true); }); - it("use account subplebbits", async () => { + it("use account communities", async () => { const rendered = renderHook(() => { const account = useAccount(); - const { createSubplebbit } = accountsActions; - const accountSubplebbits = useAccountSubplebbits(); - return { createSubplebbit, accountSubplebbits, account }; + const { createCommunity } = accountsActions; + const accountCommunities = useAccountCommunities(); + return { createCommunity, accountCommunities, account }; }); const waitFor = testUtils.createWaitFor(rendered, { timeout }); await waitFor( - () => typeof rendered.result.current.account?.plebbit?.createSubplebbit === "function", + () => typeof rendered.result.current.account?.plebbit?.createCommunity === "function", ); - expect(typeof rendered.result.current.account?.plebbit?.createSubplebbit).to.equal("function"); + expect(typeof rendered.result.current.account?.plebbit?.createCommunity).to.equal("function"); - console.log("creating subplebbit"); - const subplebbit = await rendered.result.current.createSubplebbit({ + console.log("creating community"); + const community = await rendered.result.current.createCommunity({ title: "title", description: "description", }); - console.log({ subplebbit }); - expect(subplebbit.title).to.equal("title"); + console.log({ community }); + expect(community.title).to.equal("title"); - // wait for account subplebbits + // wait for account communities await waitFor( () => - JSON.stringify(rendered.result.current?.accountSubplebbits?.accountSubplebbits) !== "{}", + JSON.stringify(rendered.result.current?.accountCommunities?.accountCommunities) !== "{}", ); expect( - JSON.stringify(rendered.result.current?.accountSubplebbits?.accountSubplebbits), + JSON.stringify(rendered.result.current?.accountCommunities?.accountCommunities), ).not.to.equal("{}"); - console.log(rendered.result.current?.accountSubplebbits); + console.log(rendered.result.current?.accountCommunities); - // NOTE: this test won't change accountSubplebbits state, need to use publishSubplebbitEdit for that - console.log("editing subplebbit"); - await subplebbit.edit({ + // NOTE: this test won't change accountCommunities state, need to use publishCommunityEdit for that + console.log("editing community"); + await community.edit({ address: "name.eth", }); - console.log({ subplebbit }); - expect(subplebbit.address).to.equal("name.eth"); + console.log({ community }); + expect(community.address).to.equal("name.eth"); }); }); diff --git a/test/browser-plebbit-js-mock/accounts.test.js b/test/browser-plebbit-js-mock/accounts.test.js index 71f64b0a..79260404 100644 --- a/test/browser-plebbit-js-mock/accounts.test.js +++ b/test/browser-plebbit-js-mock/accounts.test.js @@ -89,7 +89,7 @@ describe("accounts (plebbit-js mock)", () => { it("publish comment", async () => { const publishCommentOptions = { - subplebbitAddress: "12D3KooW...", + communityAddress: "12D3KooW...", parentCid: "Qm...", content: "some content", onChallenge, @@ -146,7 +146,7 @@ describe("accounts (plebbit-js mock)", () => { it("publish vote", async () => { const publishVoteOptions = { - subplebbitAddress: "12D3KooW...", + communityAddress: "12D3KooW...", commentCid: "Qm...", vote: 1, onChallenge, diff --git a/test/browser-plebbit-js-mock/subplebbits.test.js b/test/browser-plebbit-js-mock/communities.test.js similarity index 54% rename from test/browser-plebbit-js-mock/subplebbits.test.js rename to test/browser-plebbit-js-mock/communities.test.js index f06a73c2..764030b8 100644 --- a/test/browser-plebbit-js-mock/subplebbits.test.js +++ b/test/browser-plebbit-js-mock/communities.test.js @@ -1,6 +1,6 @@ import { act } from "@testing-library/react"; import { renderHook } from "../test-utils"; -import { useSubplebbit, setPlebbitJs, restorePlebbitJs } from "../../dist"; +import { useCommunity, setPlebbitJs, restorePlebbitJs } from "../../dist"; import debugUtils from "../../dist/lib/debug-utils"; import testUtils from "../../dist/lib/test-utils"; import PlebbitJsMock from "../../dist/lib/plebbit-js/plebbit-js-mock"; @@ -9,9 +9,9 @@ setPlebbitJs(PlebbitJsMock); const timeout = 10000; -describe("subplebbits (plebbit-js mock)", () => { +describe("communities (plebbit-js mock)", () => { beforeAll(async () => { - console.log("before subplebbits tests"); + console.log("before communities tests"); testUtils.silenceReactWarnings(); // reset before or init accounts sometimes fails await testUtils.resetDatabasesAndStores(); @@ -22,31 +22,31 @@ describe("subplebbits (plebbit-js mock)", () => { console.log("after reset stores"); }); - describe("no subplebbits in database", () => { - it("get subplebbits one at a time", async () => { - console.log("starting subplebbits tests"); - const rendered = renderHook((subplebbitAddress) => useSubplebbit({ subplebbitAddress })); + describe("no communities in database", () => { + it("get communities one at a time", async () => { + console.log("starting communities tests"); + const rendered = renderHook((communityAddress) => useCommunity({ communityAddress })); const waitFor = testUtils.createWaitFor(rendered, { timeout }); expect(rendered.result.current?.updatedAt).to.equal(undefined); - rendered.rerender("subplebbit address 1"); + rendered.rerender("community address 1"); await waitFor(() => typeof rendered.result.current.title === "string"); - expect(rendered.result.current.address).to.equal("subplebbit address 1"); - expect(rendered.result.current.title).to.equal("subplebbit address 1 title"); - // wait for subplebbit.on('update') to fetch the updated description + expect(rendered.result.current.address).to.equal("community address 1"); + expect(rendered.result.current.title).to.equal("community address 1 title"); + // wait for community.on('update') to fetch the updated description await waitFor(() => typeof rendered.result.current.description === "string"); expect(rendered.result.current.description).to.equal( - "subplebbit address 1 description updated", + "community address 1 description updated", ); - rendered.rerender("subplebbit address 2"); + rendered.rerender("community address 2"); await waitFor(() => typeof rendered.result.current.title === "string"); - expect(rendered.result.current.address).to.equal("subplebbit address 2"); - expect(rendered.result.current.title).to.equal("subplebbit address 2 title"); - // wait for subplebbit.on('update') to fetch the updated description + expect(rendered.result.current.address).to.equal("community address 2"); + expect(rendered.result.current.title).to.equal("community address 2 title"); + // wait for community.on('update') to fetch the updated description await waitFor(() => typeof rendered.result.current.description === "string"); expect(rendered.result.current.description).to.equal( - "subplebbit address 2 description updated", + "community address 2 description updated", ); }); }); diff --git a/test/browser-plebbit-js-mock/feeds.test.js b/test/browser-plebbit-js-mock/feeds.test.js index cf8878b4..ab3c8f93 100644 --- a/test/browser-plebbit-js-mock/feeds.test.js +++ b/test/browser-plebbit-js-mock/feeds.test.js @@ -48,9 +48,9 @@ describe("feeds (plebbit-js mock)", () => { expect(typeof rendered.result.current.loadMore).to.equal("function"); }); - it("get feed page 1 with 1 subplebbit sorted by default (hot)", async () => { + it("get feed page 1 with 1 community sorted by default (hot)", async () => { // get feed with 1 sub - rendered.rerender({ subplebbitAddresses: ["subplebbit address 1"] }); + rendered.rerender({ communityAddresses: ["community address 1"] }); // initial state expect(typeof rendered.result.current.hasMore).to.equal("boolean"); expect(typeof rendered.result.current.loadMore).to.equal("function"); @@ -64,52 +64,52 @@ describe("feeds (plebbit-js mock)", () => { // NOTE: the 'hot' sort type uses timestamps and bugs out with timestamp '100-1' so this is why we get cid 100 // with low upvote count first expect(rendered.result.current.feed[0].cid).to.equal( - "subplebbit address 1 page cid hot comment cid 100", + "community address 1 page cid hot comment cid 100", ); expect(rendered.result.current.feed.length).to.equal(postsPerPage); }); - it("change subplebbit addresses and sort type", async () => { - rendered.rerender({ subplebbitAddresses: ["subplebbit address 1"], sortType: "hot" }); - await waitFor(() => !!rendered.result.current.feed[0].cid.match(/subplebbit address 1/)); - expect(rendered.result.current.feed[0].cid).to.match(/subplebbit address 1/); + it("change community addresses and sort type", async () => { + rendered.rerender({ communityAddresses: ["community address 1"], sortType: "hot" }); + await waitFor(() => !!rendered.result.current.feed[0].cid.match(/community address 1/)); + expect(rendered.result.current.feed[0].cid).to.match(/community address 1/); expect(rendered.result.current.feed.length).to.equal(postsPerPage); - // change subplebbit addresses + // change community addresses rendered.rerender({ - subplebbitAddresses: ["subplebbit address 2", "subplebbit address 3"], + communityAddresses: ["community address 2", "community address 3"], sortType: "hot", }); - await waitFor(() => !!rendered.result.current.feed[0].cid.match(/subplebbit address (2|3)/)); - expect(rendered.result.current.feed[0].cid).to.match(/subplebbit address (2|3)/); + await waitFor(() => !!rendered.result.current.feed[0].cid.match(/community address (2|3)/)); + expect(rendered.result.current.feed[0].cid).to.match(/community address (2|3)/); // the 'hot' sort type should give timestamp 1 with the current mock expect(rendered.result.current.feed[0].timestamp).to.equal(100); expect(rendered.result.current.feed.length).to.equal(postsPerPage); // change sort type rendered.rerender({ - subplebbitAddresses: ["subplebbit address 2", "subplebbit address 3"], + communityAddresses: ["community address 2", "community address 3"], sortType: "new", }); - await waitFor(() => !!rendered.result.current.feed[0].cid.match(/subplebbit address (2|3)/)); - expect(rendered.result.current.feed[0].cid).to.match(/subplebbit address (2|3)/); + await waitFor(() => !!rendered.result.current.feed[0].cid.match(/community address (2|3)/)); + expect(rendered.result.current.feed[0].cid).to.match(/community address (2|3)/); // the 'new' sort type should give timestamp higher than 99 with the current mock expect(rendered.result.current.feed[0].timestamp).to.be.greaterThan(99); expect(rendered.result.current.feed.length).to.equal(postsPerPage); - // change subplebbit addresses and sort type + // change community addresses and sort type rendered.rerender({ - subplebbitAddresses: ["subplebbit address 4", "subplebbit address 5"], + communityAddresses: ["community address 4", "community address 5"], sortType: "topAll", }); - await waitFor(() => !!rendered.result.current.feed[0].cid.match(/subplebbit address (4|5)/)); - expect(rendered.result.current.feed[0].cid).to.match(/subplebbit address (4|5)/); + await waitFor(() => !!rendered.result.current.feed[0].cid.match(/community address (4|5)/)); + expect(rendered.result.current.feed[0].cid).to.match(/community address (4|5)/); expect(rendered.result.current.feed.length).to.equal(postsPerPage); }); - it("get feed with 1 subplebbit and scroll to multiple pages", async () => { + it("get feed with 1 community and scroll to multiple pages", async () => { // get feed with 1 sub - rendered.rerender({ subplebbitAddresses: ["subplebbit address 1"] }); + rendered.rerender({ communityAddresses: ["community address 1"] }); // wait for posts to be added, should get full first page await waitFor(() => rendered.result.current.feed.length > 0); @@ -126,12 +126,12 @@ describe("feeds (plebbit-js mock)", () => { }); // use this as stress test - it.skip("(long stress test) get feed with 25 subplebbit and scroll to 1000 pages", async () => { - const subplebbitAddresses = []; - while (subplebbitAddresses.length < 25) { - subplebbitAddresses.push(`subplebbit address ${subplebbitAddresses.length + 1}`); + it.skip("(long stress test) get feed with 25 community and scroll to 1000 pages", async () => { + const communityAddresses = []; + while (communityAddresses.length < 25) { + communityAddresses.push(`community address ${communityAddresses.length + 1}`); } - rendered.rerender({ subplebbitAddresses }); + rendered.rerender({ communityAddresses }); // wait for posts to be added, should get full first page await waitFor(() => rendered.result.current.feed.length > 0); diff --git a/test/test-server/index.js b/test/test-server/index.js index f889e44a..b805385c 100644 --- a/test/test-server/index.js +++ b/test/test-server/index.js @@ -8,9 +8,9 @@ import { directory as getTmpFolderPath } from "tempy"; import http from "http"; const plebbitDataPath = getTmpFolderPath(); -// set up a subplebbit for testing +// set up a community for testing (async () => { - // always use the same private key and subplebbit address when testing + // always use the same private key and community address when testing const privateKey = signers[0].privateKey; const adminRoleAddress = signers[1].address; @@ -41,33 +41,33 @@ const plebbitDataPath = getTmpFolderPath(); }); const signer = await plebbit.createSigner({ privateKey, type: "ed25519" }); - console.log(`creating subplebbit with address '${signer.address}'...`); - const subplebbit = await plebbit.createSubplebbit({ + console.log(`creating community with address '${signer.address}'...`); + const community = await plebbit.createCommunity({ signer: signer, }); - subplebbit.on("challengerequest", console.log); - subplebbit.on("challengeanswer", console.log); - await subplebbit.edit({ + community.on("challengerequest", console.log); + community.on("challengeanswer", console.log); + await community.edit({ settings: { challenges: [{ name: "question", options: { question: "1+1=?", answer: "2" } }], }, roles: { [adminRoleAddress]: { role: "admin" } }, }); - console.log("subplebbit created"); + console.log("community created"); - // tests can cause subplebbit errors, e.g. changing name to wrong .eth - subplebbit.on("error", console.log); + // tests can cause community errors, e.g. changing name to wrong .eth + community.on("error", console.log); - console.log("starting subplebbit..."); - await subplebbit.start(); - subplebbit.once("update", async () => { - console.log(`subplebbit started with address ${signer.address}`); + console.log("starting community..."); + await community.start(); + community.once("update", async () => { + console.log(`community started with address ${signer.address}`); console.log("publish test comment"); const comment = await plebbit2.createComment({ title: "comment title", content: "comment content", - subplebbitAddress: signer.address, + communityAddress: signer.address, signer, author: { address: signer.address }, }); From 6c83bcda2964665f507ab1d54b88a4863bf6a266 Mon Sep 17 00:00:00 2001 From: tomcasaburi Date: Wed, 11 Mar 2026 19:25:27 +0800 Subject: [PATCH 2/2] chore: add tracked PR workflow skills --- .codex/skills/make-closed-issue/SKILL.md | 187 +++++++++++++++++ .codex/skills/review-and-merge-pr/SKILL.md | 196 ++++++++++++++++++ .../review-and-merge-pr/agents/openai.yaml | 4 + .cursor/skills/make-closed-issue/SKILL.md | 187 +++++++++++++++++ .cursor/skills/review-and-merge-pr/SKILL.md | 196 ++++++++++++++++++ .../review-and-merge-pr/agents/openai.yaml | 4 + .gitignore | 16 ++ AGENTS.md | 6 + 8 files changed, 796 insertions(+) create mode 100644 .codex/skills/make-closed-issue/SKILL.md create mode 100644 .codex/skills/review-and-merge-pr/SKILL.md create mode 100644 .codex/skills/review-and-merge-pr/agents/openai.yaml create mode 100644 .cursor/skills/make-closed-issue/SKILL.md create mode 100644 .cursor/skills/review-and-merge-pr/SKILL.md create mode 100644 .cursor/skills/review-and-merge-pr/agents/openai.yaml diff --git a/.codex/skills/make-closed-issue/SKILL.md b/.codex/skills/make-closed-issue/SKILL.md new file mode 100644 index 00000000..47982ff9 --- /dev/null +++ b/.codex/skills/make-closed-issue/SKILL.md @@ -0,0 +1,187 @@ +--- +name: make-closed-issue +description: Create a GitHub issue from recent changes, commit only relevant diffs on a short-lived task branch, push that branch, and open a PR into master that will close the issue on merge. Use when the user says "make closed issue", "close issue", or wants to create a tracked, already-resolved GitHub issue for completed work. +--- + +# Make Closed Issue + +Creates a GitHub issue, commits relevant changes on a review branch, pushes the branch, and opens a PR into `master` that closes the issue when merged. + +## Inputs + +- What changed and why (from prior conversation context) +- Uncommitted or staged git changes in the working tree + +## Workflow + +### 1. Determine label(s) + +Ask the user using AskQuestion (multi-select): + +| Option | When | +|--------|------| +| `bug` | Bug fix | +| `enhancement` | New feature | +| `bug` + `enhancement` | New feature that also fixes a bug | +| `documentation` | README, AGENTS.md, docs-only changes | + +### 2. Resolve the current GitHub assignee + +Before creating or editing any issue assignee, determine the current contributor's GitHub username from the authenticated `gh` session. +If `gh` is not signed in or cannot resolve the login, stop and ask the contributor for their GitHub username before proceeding. + +```bash +GH_LOGIN=$(gh api user --jq '.login' 2>/dev/null || true) + +if [ -z "$GH_LOGIN" ]; then + echo "GitHub username could not be determined from gh auth. Ask the contributor for their GitHub username before proceeding." + exit 1 +fi +``` + +### 3. Ensure branch workflow is reviewable + +- If already on a short-lived task branch such as `feature/*`, `fix/*`, `docs/*`, or `chore/*`, stay on it. +- If on `master`, create a task branch before staging or committing. +- Do **not** commit the work directly on `master` when PR review bots are expected. + +Suggested naming: + +- `feature/short-slug` +- `fix/short-slug` +- `docs/short-slug` +- `chore/short-slug` + +Example: + +```bash +git switch -c fix/reply-editor-stuck +``` + +### 4. Review diffs for relevance + +```bash +git status +git diff +git diff --cached +``` + +Identify which files relate to the work done in this conversation. Only relevant changes get committed. Unrelated files must be excluded from staging. + +**Important**: `git add -p` and `git add -i` are not available (interactive mode unsupported). If a file has mixed relevant/irrelevant changes, include the entire file and note the caveat to the user. + +### 5. Generate issue title and description + +From the conversation context: + +- **Title**: Short, present-tense, describes the **problem** (not the solution). Use backticks for UI elements, code, or literal strings. +- **Description**: 2-3 sentences about the problem. Use backticks for code references and literal strings. Write as if the issue hasn't been fixed yet. + +### 6. Create the issue + +```bash +gh issue create \ + --repo bitsocialnet/bitsocial-react-hooks \ + --title "ISSUE_TITLE" \ + --body "ISSUE_DESCRIPTION" \ + --label "LABEL1,LABEL2" \ + --assignee "$GH_LOGIN" +``` + +Capture the issue number from the output. + +### 7. Commit relevant changes + +Stage only the relevant files: + +```bash +git add file1.ts file2.tsx ... +``` + +Commit using Conventional Commits with scope: + +```bash +git commit -m "$(cat <<'EOF' +type(scope): concise title + +Optional 1-sentence description only if the title isn't self-explanatory. +EOF +)" +``` + +- **Types**: `fix`, `feat`, `perf`, `refactor`, `docs`, `chore` +- **Scope**: area of the codebase (e.g. `accounts`, `comments`, `replies`, `stores`) +- Prefer title-only commits - skip description when the title is exhaustive + +### 8. Push branch and open PR + +Push the current task branch to origin and open a PR into `master`. + +Use `Closes #ISSUE_NUMBER` in the PR body so the issue closes automatically when the PR is merged. + +```bash +COMMIT_HASH=$(git rev-parse HEAD) +BRANCH_NAME=$(git branch --show-current) +git push -u origin "$BRANCH_NAME" + +gh pr create \ + --repo bitsocialnet/bitsocial-react-hooks \ + --base master \ + --head "$BRANCH_NAME" \ + --title "PR_TITLE" \ + --body "$(cat < --repo bitsocialnet/bitsocial-react-hooks --json number,title,url,headRefName,baseRefName,isDraft,reviewDecision,mergeStateStatus +``` + +### 2. Gather all review signals before changing code + +Read the PR state, checks, issue comments, review summaries, and inline review comments before deciding what to change. +Do not merge based only on the top-level review verdict. + +Useful commands: + +```bash +gh pr view --repo bitsocialnet/bitsocial-react-hooks --json number,title,url,headRefName,baseRefName,isDraft,reviewDecision,mergeStateStatus +gh pr checks +gh api "repos/bitsocialnet/bitsocial-react-hooks/issues//comments?per_page=100" +gh api "repos/bitsocialnet/bitsocial-react-hooks/pulls//reviews?per_page=100" +gh api "repos/bitsocialnet/bitsocial-react-hooks/pulls//comments?per_page=100" +``` + +Focus on comments from: + +- Cursor Bugbot +- CodeRabbit +- human reviewers +- failing CI checks + +### 3. Triage findings instead of blindly applying them + +Sort feedback into these buckets: + +- `must-fix`: correctness bugs, broken behavior, crashes, security issues, test failures, reproducible regressions +- `should-fix`: clear maintainability or edge-case issues with concrete evidence +- `decline`: false positives, stale comments, duplicate findings, speculative style-only suggestions, or feedback already addressed in newer commits + +Rules: + +- Never merge with unresolved `must-fix` findings. +- Do not accept a bot finding without reading the relevant code and diff. +- If a finding is ambiguous but high-risk, ask the user before merging. +- If a comment is wrong or stale, explain why in the PR rather than silently ignoring it. + +### 4. Work on the PR branch and keep the PR updated + +Switch to the PR branch if needed, apply the valid fixes, and push new commits to the same branch. +Do not open a replacement PR unless the user explicitly asks for that. + +Useful commands: + +```bash +git switch +git fetch origin +git status --short --branch +git add +git commit -m "fix(scope): address review feedback" +git push +``` + +After code changes, follow repo verification rules from `AGENTS.md`: + +- run `yarn build` +- run `yarn test` after adding or changing tests +- run `yarn prettier` before finishing + +### 5. Report back on the PR before merging + +Summarize what was fixed and what was declined. +Use `gh pr comment` for a concise PR update when the branch changed because of review feedback. + +Example: + +```bash +gh pr comment --repo bitsocialnet/bitsocial-react-hooks --body "Addressed the valid review findings in the latest commit. Remaining bot comments are stale or not applicable for the reasons checked locally." +``` + +### 6. Merge only when the PR is actually ready + +Merge only if all of these are true: + +- the PR is not draft +- required checks are passing +- the branch is mergeable into `master` +- no unresolved `must-fix` reviewer findings remain +- the latest code was verified locally after the last review-driven change + +Preferred merge command: + +```bash +gh pr merge --repo bitsocialnet/bitsocial-react-hooks --squash --delete-branch +``` + +### 7. Finalize linked issues to match `make-closed-issue` + +After merge, inspect the PR's linked closing issues. +For every linked issue, bring it into the same final state expected from `make-closed-issue`: + +- closed +- assigned to the current GitHub user +- added to the bitsocialnet project if missing +- project status `Done` + +Before editing issue assignees, determine the current contributor's GitHub username from the authenticated `gh` session. +If `gh` is not signed in or cannot resolve the login, stop and ask the contributor for their GitHub username before proceeding. +If the PR has no linked issue, explicitly tell the user that there was no associated issue to finalize. + +Useful commands: + +```bash +GH_LOGIN=$(gh api user --jq '.login' 2>/dev/null || true) + +if [ -z "$GH_LOGIN" ]; then + echo "GitHub username could not be determined from gh auth. Ask the contributor for their GitHub username before proceeding." + exit 1 +fi + +ISSUE_NUMBERS=$(gh pr view --repo bitsocialnet/bitsocial-react-hooks --json closingIssuesReferences --jq '.closingIssuesReferences[].number') + +if [ -n "$ISSUE_NUMBERS" ]; then + FIELD_JSON=$(gh project field-list 1 --owner bitsocialnet --format json) + STATUS_FIELD_ID=$(echo "$FIELD_JSON" | jq -r '.fields[] | select(.name=="Status") | .id') + DONE_OPTION_ID=$(echo "$FIELD_JSON" | jq -r '.fields[] | select(.name=="Status") | .options[] | select(.name=="Done") | .id') + + for ISSUE_NUMBER in $ISSUE_NUMBERS; do + ISSUE_STATE=$(gh issue view "$ISSUE_NUMBER" --repo bitsocialnet/bitsocial-react-hooks --json state --jq '.state') + if [ "$ISSUE_STATE" != "CLOSED" ]; then + gh issue close "$ISSUE_NUMBER" --repo bitsocialnet/bitsocial-react-hooks + fi + + if ! gh issue view "$ISSUE_NUMBER" --repo bitsocialnet/bitsocial-react-hooks --json assignees --jq '.assignees[].login' | grep -qx "$GH_LOGIN"; then + gh issue edit "$ISSUE_NUMBER" --repo bitsocialnet/bitsocial-react-hooks --add-assignee "$GH_LOGIN" + fi + + ITEM_ID=$(gh project item-list 1 --owner bitsocialnet --limit 1000 --format json --jq ".items[] | select(.content.number == $ISSUE_NUMBER) | .id" | head -n1) + if [ -z "$ITEM_ID" ]; then + ITEM_JSON=$(gh project item-add 1 --owner bitsocialnet --url "https://github.com/bitsocialnet/bitsocial-react-hooks/issues/$ISSUE_NUMBER" --format json) + ITEM_ID=$(echo "$ITEM_JSON" | jq -r '.id') + fi + + gh project item-edit --id "$ITEM_ID" --project-id PVT_kwDODohK7M4BM4wg --field-id "$STATUS_FIELD_ID" --single-select-option-id "$DONE_OPTION_ID" + done +fi +``` + +### 8. Clean up local state after merge + +After the PR is merged: + +```bash +git switch master +git pull --ff-only +git branch -D 2>/dev/null || true +git branch -D "pr/" 2>/dev/null || true +``` + +If the PR branch lived in a dedicated worktree, remove that worktree after leaving it: + +```bash +git worktree list +git worktree remove /path/to/worktree +``` + +### 9. Report the outcome + +Tell the user: + +- which findings were fixed +- which findings were declined and why +- which verification commands ran +- whether the PR was merged +- whether linked issues were confirmed closed +- whether linked issues were assigned to the current GitHub user +- whether linked project items were confirmed `Done` +- whether the feature branch, local `pr/` alias, and any worktree were cleaned up diff --git a/.codex/skills/review-and-merge-pr/agents/openai.yaml b/.codex/skills/review-and-merge-pr/agents/openai.yaml new file mode 100644 index 00000000..b6f83e84 --- /dev/null +++ b/.codex/skills/review-and-merge-pr/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Review and Merge PR" + short_description: "Check PR feedback, fix valid findings, and merge when ready" + default_prompt: "Review the current PR, address valid bot or human feedback, and merge it when ready." diff --git a/.cursor/skills/make-closed-issue/SKILL.md b/.cursor/skills/make-closed-issue/SKILL.md new file mode 100644 index 00000000..47982ff9 --- /dev/null +++ b/.cursor/skills/make-closed-issue/SKILL.md @@ -0,0 +1,187 @@ +--- +name: make-closed-issue +description: Create a GitHub issue from recent changes, commit only relevant diffs on a short-lived task branch, push that branch, and open a PR into master that will close the issue on merge. Use when the user says "make closed issue", "close issue", or wants to create a tracked, already-resolved GitHub issue for completed work. +--- + +# Make Closed Issue + +Creates a GitHub issue, commits relevant changes on a review branch, pushes the branch, and opens a PR into `master` that closes the issue when merged. + +## Inputs + +- What changed and why (from prior conversation context) +- Uncommitted or staged git changes in the working tree + +## Workflow + +### 1. Determine label(s) + +Ask the user using AskQuestion (multi-select): + +| Option | When | +|--------|------| +| `bug` | Bug fix | +| `enhancement` | New feature | +| `bug` + `enhancement` | New feature that also fixes a bug | +| `documentation` | README, AGENTS.md, docs-only changes | + +### 2. Resolve the current GitHub assignee + +Before creating or editing any issue assignee, determine the current contributor's GitHub username from the authenticated `gh` session. +If `gh` is not signed in or cannot resolve the login, stop and ask the contributor for their GitHub username before proceeding. + +```bash +GH_LOGIN=$(gh api user --jq '.login' 2>/dev/null || true) + +if [ -z "$GH_LOGIN" ]; then + echo "GitHub username could not be determined from gh auth. Ask the contributor for their GitHub username before proceeding." + exit 1 +fi +``` + +### 3. Ensure branch workflow is reviewable + +- If already on a short-lived task branch such as `feature/*`, `fix/*`, `docs/*`, or `chore/*`, stay on it. +- If on `master`, create a task branch before staging or committing. +- Do **not** commit the work directly on `master` when PR review bots are expected. + +Suggested naming: + +- `feature/short-slug` +- `fix/short-slug` +- `docs/short-slug` +- `chore/short-slug` + +Example: + +```bash +git switch -c fix/reply-editor-stuck +``` + +### 4. Review diffs for relevance + +```bash +git status +git diff +git diff --cached +``` + +Identify which files relate to the work done in this conversation. Only relevant changes get committed. Unrelated files must be excluded from staging. + +**Important**: `git add -p` and `git add -i` are not available (interactive mode unsupported). If a file has mixed relevant/irrelevant changes, include the entire file and note the caveat to the user. + +### 5. Generate issue title and description + +From the conversation context: + +- **Title**: Short, present-tense, describes the **problem** (not the solution). Use backticks for UI elements, code, or literal strings. +- **Description**: 2-3 sentences about the problem. Use backticks for code references and literal strings. Write as if the issue hasn't been fixed yet. + +### 6. Create the issue + +```bash +gh issue create \ + --repo bitsocialnet/bitsocial-react-hooks \ + --title "ISSUE_TITLE" \ + --body "ISSUE_DESCRIPTION" \ + --label "LABEL1,LABEL2" \ + --assignee "$GH_LOGIN" +``` + +Capture the issue number from the output. + +### 7. Commit relevant changes + +Stage only the relevant files: + +```bash +git add file1.ts file2.tsx ... +``` + +Commit using Conventional Commits with scope: + +```bash +git commit -m "$(cat <<'EOF' +type(scope): concise title + +Optional 1-sentence description only if the title isn't self-explanatory. +EOF +)" +``` + +- **Types**: `fix`, `feat`, `perf`, `refactor`, `docs`, `chore` +- **Scope**: area of the codebase (e.g. `accounts`, `comments`, `replies`, `stores`) +- Prefer title-only commits - skip description when the title is exhaustive + +### 8. Push branch and open PR + +Push the current task branch to origin and open a PR into `master`. + +Use `Closes #ISSUE_NUMBER` in the PR body so the issue closes automatically when the PR is merged. + +```bash +COMMIT_HASH=$(git rev-parse HEAD) +BRANCH_NAME=$(git branch --show-current) +git push -u origin "$BRANCH_NAME" + +gh pr create \ + --repo bitsocialnet/bitsocial-react-hooks \ + --base master \ + --head "$BRANCH_NAME" \ + --title "PR_TITLE" \ + --body "$(cat < --repo bitsocialnet/bitsocial-react-hooks --json number,title,url,headRefName,baseRefName,isDraft,reviewDecision,mergeStateStatus +``` + +### 2. Gather all review signals before changing code + +Read the PR state, checks, issue comments, review summaries, and inline review comments before deciding what to change. +Do not merge based only on the top-level review verdict. + +Useful commands: + +```bash +gh pr view --repo bitsocialnet/bitsocial-react-hooks --json number,title,url,headRefName,baseRefName,isDraft,reviewDecision,mergeStateStatus +gh pr checks +gh api "repos/bitsocialnet/bitsocial-react-hooks/issues//comments?per_page=100" +gh api "repos/bitsocialnet/bitsocial-react-hooks/pulls//reviews?per_page=100" +gh api "repos/bitsocialnet/bitsocial-react-hooks/pulls//comments?per_page=100" +``` + +Focus on comments from: + +- Cursor Bugbot +- CodeRabbit +- human reviewers +- failing CI checks + +### 3. Triage findings instead of blindly applying them + +Sort feedback into these buckets: + +- `must-fix`: correctness bugs, broken behavior, crashes, security issues, test failures, reproducible regressions +- `should-fix`: clear maintainability or edge-case issues with concrete evidence +- `decline`: false positives, stale comments, duplicate findings, speculative style-only suggestions, or feedback already addressed in newer commits + +Rules: + +- Never merge with unresolved `must-fix` findings. +- Do not accept a bot finding without reading the relevant code and diff. +- If a finding is ambiguous but high-risk, ask the user before merging. +- If a comment is wrong or stale, explain why in the PR rather than silently ignoring it. + +### 4. Work on the PR branch and keep the PR updated + +Switch to the PR branch if needed, apply the valid fixes, and push new commits to the same branch. +Do not open a replacement PR unless the user explicitly asks for that. + +Useful commands: + +```bash +git switch +git fetch origin +git status --short --branch +git add +git commit -m "fix(scope): address review feedback" +git push +``` + +After code changes, follow repo verification rules from `AGENTS.md`: + +- run `yarn build` +- run `yarn test` after adding or changing tests +- run `yarn prettier` before finishing + +### 5. Report back on the PR before merging + +Summarize what was fixed and what was declined. +Use `gh pr comment` for a concise PR update when the branch changed because of review feedback. + +Example: + +```bash +gh pr comment --repo bitsocialnet/bitsocial-react-hooks --body "Addressed the valid review findings in the latest commit. Remaining bot comments are stale or not applicable for the reasons checked locally." +``` + +### 6. Merge only when the PR is actually ready + +Merge only if all of these are true: + +- the PR is not draft +- required checks are passing +- the branch is mergeable into `master` +- no unresolved `must-fix` reviewer findings remain +- the latest code was verified locally after the last review-driven change + +Preferred merge command: + +```bash +gh pr merge --repo bitsocialnet/bitsocial-react-hooks --squash --delete-branch +``` + +### 7. Finalize linked issues to match `make-closed-issue` + +After merge, inspect the PR's linked closing issues. +For every linked issue, bring it into the same final state expected from `make-closed-issue`: + +- closed +- assigned to the current GitHub user +- added to the bitsocialnet project if missing +- project status `Done` + +Before editing issue assignees, determine the current contributor's GitHub username from the authenticated `gh` session. +If `gh` is not signed in or cannot resolve the login, stop and ask the contributor for their GitHub username before proceeding. +If the PR has no linked issue, explicitly tell the user that there was no associated issue to finalize. + +Useful commands: + +```bash +GH_LOGIN=$(gh api user --jq '.login' 2>/dev/null || true) + +if [ -z "$GH_LOGIN" ]; then + echo "GitHub username could not be determined from gh auth. Ask the contributor for their GitHub username before proceeding." + exit 1 +fi + +ISSUE_NUMBERS=$(gh pr view --repo bitsocialnet/bitsocial-react-hooks --json closingIssuesReferences --jq '.closingIssuesReferences[].number') + +if [ -n "$ISSUE_NUMBERS" ]; then + FIELD_JSON=$(gh project field-list 1 --owner bitsocialnet --format json) + STATUS_FIELD_ID=$(echo "$FIELD_JSON" | jq -r '.fields[] | select(.name=="Status") | .id') + DONE_OPTION_ID=$(echo "$FIELD_JSON" | jq -r '.fields[] | select(.name=="Status") | .options[] | select(.name=="Done") | .id') + + for ISSUE_NUMBER in $ISSUE_NUMBERS; do + ISSUE_STATE=$(gh issue view "$ISSUE_NUMBER" --repo bitsocialnet/bitsocial-react-hooks --json state --jq '.state') + if [ "$ISSUE_STATE" != "CLOSED" ]; then + gh issue close "$ISSUE_NUMBER" --repo bitsocialnet/bitsocial-react-hooks + fi + + if ! gh issue view "$ISSUE_NUMBER" --repo bitsocialnet/bitsocial-react-hooks --json assignees --jq '.assignees[].login' | grep -qx "$GH_LOGIN"; then + gh issue edit "$ISSUE_NUMBER" --repo bitsocialnet/bitsocial-react-hooks --add-assignee "$GH_LOGIN" + fi + + ITEM_ID=$(gh project item-list 1 --owner bitsocialnet --limit 1000 --format json --jq ".items[] | select(.content.number == $ISSUE_NUMBER) | .id" | head -n1) + if [ -z "$ITEM_ID" ]; then + ITEM_JSON=$(gh project item-add 1 --owner bitsocialnet --url "https://github.com/bitsocialnet/bitsocial-react-hooks/issues/$ISSUE_NUMBER" --format json) + ITEM_ID=$(echo "$ITEM_JSON" | jq -r '.id') + fi + + gh project item-edit --id "$ITEM_ID" --project-id PVT_kwDODohK7M4BM4wg --field-id "$STATUS_FIELD_ID" --single-select-option-id "$DONE_OPTION_ID" + done +fi +``` + +### 8. Clean up local state after merge + +After the PR is merged: + +```bash +git switch master +git pull --ff-only +git branch -D 2>/dev/null || true +git branch -D "pr/" 2>/dev/null || true +``` + +If the PR branch lived in a dedicated worktree, remove that worktree after leaving it: + +```bash +git worktree list +git worktree remove /path/to/worktree +``` + +### 9. Report the outcome + +Tell the user: + +- which findings were fixed +- which findings were declined and why +- which verification commands ran +- whether the PR was merged +- whether linked issues were confirmed closed +- whether linked issues were assigned to the current GitHub user +- whether linked project items were confirmed `Done` +- whether the feature branch, local `pr/` alias, and any worktree were cleaned up diff --git a/.cursor/skills/review-and-merge-pr/agents/openai.yaml b/.cursor/skills/review-and-merge-pr/agents/openai.yaml new file mode 100644 index 00000000..b6f83e84 --- /dev/null +++ b/.cursor/skills/review-and-merge-pr/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Review and Merge PR" + short_description: "Check PR feedback, fix valid findings, and merge when ready" + default_prompt: "Review the current PR, address valid bot or human feedback, and merge it when ready." diff --git a/.gitignore b/.gitignore index b8cdcb22..6f25a4f4 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,22 @@ yarn-error.log # agent files .cursor .codex +!.cursor/ +!.cursor/skills/ +!.cursor/skills/make-closed-issue/ +!.cursor/skills/make-closed-issue/SKILL.md +!.cursor/skills/review-and-merge-pr/ +!.cursor/skills/review-and-merge-pr/SKILL.md +!.cursor/skills/review-and-merge-pr/agents/ +!.cursor/skills/review-and-merge-pr/agents/openai.yaml +!.codex/ +!.codex/skills/ +!.codex/skills/make-closed-issue/ +!.codex/skills/make-closed-issue/SKILL.md +!.codex/skills/review-and-merge-pr/ +!.codex/skills/review-and-merge-pr/SKILL.md +!.codex/skills/review-and-merge-pr/agents/ +!.codex/skills/review-and-merge-pr/agents/openai.yaml # generated browser test screenshots test/**/__screenshots__/ diff --git a/AGENTS.md b/AGENTS.md index 65dc9b74..c7271d37 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -36,6 +36,7 @@ This repo is a temporary fork of [plebbit/plebbit-react-hooks](https://github.co | Tests added or changed (`src/**/*.test.ts`, `test/`) | Run `yarn test` to verify | | GitHub operation needed | Use `gh` CLI, not GitHub MCP | | User asks for commit/issue phrasing | Use `docs/agent-playbooks/commit-issue-format.md` | +| Repo AI workflow files changed (`.codex/**`, `.cursor/**`) | Keep the Codex and Cursor copies aligned when they represent the same workflow | | Surprising/ambiguous repo behavior encountered | Alert developer and, once confirmed, document in `docs/agent-playbooks/known-surprises.md` | ## Stack @@ -116,6 +117,11 @@ src/ - Do not use browser MCP servers. - If many MCP tools are present in context, warn user and suggest disabling unused MCPs. +### AI Tooling Rules + +- Treat `.codex/` and `.cursor/` as repo-managed contributor tooling, not private scratch space. +- Keep equivalent workflow files aligned across both toolchains when both directories contain the same skill, hook, or agent. + ### Security and Boundaries - Never commit secrets or API keys.