Skip to content

Commit

Permalink
fix(actions): implement usePublishCommentModeration
Browse files Browse the repository at this point in the history
  • Loading branch information
estebanabaroa committed Nov 9, 2024
1 parent de80441 commit 1a8a5c1
Show file tree
Hide file tree
Showing 8 changed files with 399 additions and 11 deletions.
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ useBlock({address?: string, cid?: string}): {blocked: boolean | undefined, block
usePublishComment(options: UsePublishCommentOptions): {index: number, ...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}
```
Expand Down Expand Up @@ -439,6 +440,44 @@ if (editedCommentState === 'failed') {
}
```

#### Create a comment moderation

```jsx
const publishCommentModerationOptions = {
commentCid: 'QmZVYzLChjKrYDVty6e5JokKffGDZivmEJz9318EYfp2ui',
subplebbitAddress: 'news.eth',
commentModeration: {locked: true},
onChallenge,
onChallengeVerification,
onError
}
const {state, error, publishCommentModeration} = usePublishCommentModeration(publishCommentModerationOptions)

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})

// if the comment has a succeeded, failed or pending edit, use the edited comment
if (editedComment) {
comment = editedComment
}

let editLabel
if (editedCommentState === 'succeeded') {
editLabel = {text: 'EDITED', color: 'green'}
}
if (editedCommentState === 'pending') {
editLabel = {text: 'PENDING EDIT', color: 'orange'}
}
if (editedCommentState === 'failed') {
editLabel = {text: 'FAILED EDIT', color: 'red'}
}
```

#### Delete a comment

```jsx
Expand Down
171 changes: 161 additions & 10 deletions src/hooks/accounts/accounts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ describe('accounts', () => {
let challengeVerificationCount = 0
const publishCommentEditOptions = {
subplebbitAddress,
locked: true,
spoiler: true,
onChallenge: (challenge: any, comment: any) => comment.publishChallengeAnswers(),
onChallengeVerification: () => challengeVerificationCount++,
}
Expand Down Expand Up @@ -945,7 +945,7 @@ describe('accounts', () => {
const commentEditOptions = {
subplebbitAddress: '12D3KooW...',
commentCid: 'Qm...',
locked: true,
spoiler: true,
onChallenge,
onChallengeVerification,
}
Expand Down Expand Up @@ -984,15 +984,15 @@ describe('accounts', () => {
test('account edits has comment edit', async () => {
await waitFor(() => rendered.result.current.accountEdits.length === 1)
expect(rendered.result.current.accountEdits.length).toBe(1)
expect(rendered.result.current.accountEdits[0].locked).toBe(true)
expect(rendered.result.current.accountEdits[0].spoiler).toBe(true)
expect(typeof rendered.result.current.accountEdits[0].timestamp).toBe('number')

// reset stores to force using the db
await testUtils.resetStores()
const rendered2 = renderHook<any, any>(() => useAccountEdits())
await waitFor(() => rendered2.result.current.accountEdits.length === 1)
expect(rendered2.result.current.accountEdits.length).toBe(1)
expect(rendered2.result.current.accountEdits[0].locked).toBe(true)
expect(rendered2.result.current.accountEdits[0].spoiler).toBe(true)
expect(typeof rendered2.result.current.accountEdits[0].timestamp).toBe('number')
})

Expand All @@ -1004,7 +1004,7 @@ describe('accounts', () => {
})
await waitFor(() => rendered2.result.current.editedComment)
expect(rendered2.result.current.editedComment).not.toBe(undefined)
expect(rendered2.result.current.pendingEdits.locked || rendered2.result.current.succeededEdits.locked).toBe(true)
expect(rendered2.result.current.pendingEdits.spoiler || rendered2.result.current.succeededEdits.spoiler).toBe(true)
expect(rendered2.result.current.state).toMatch(/pending|succeeded/)
})
})
Expand Down Expand Up @@ -2139,7 +2139,7 @@ describe('accounts', () => {
timestamp: commentEditTimestamp,
commentCid,
subplebbitAddress,
locked: true,
spoiler: true,
onChallenge: (challenge: any, comment: any) => comment.publishChallengeAnswers(),
onChallengeVerification: () => challengeVerificationCount++,
}
Expand All @@ -2151,6 +2151,74 @@ describe('accounts', () => {
await accountsActions.publishCommentEdit(publishCommentEditOptions)
})

// edit is pending because the comment from store doesn't yet have spoiler: true
await waitFor(() => rendered.result.current.editedComment.editedComment)
expect(rendered.result.current.comment.spoiler).toBe(undefined)
expect(rendered.result.current.editedComment.editedComment).not.toBe(undefined)
expect(rendered.result.current.editedComment.state).toBe('pending')
expect(rendered.result.current.editedComment.editedComment.spoiler).toBe(true)
expect(rendered.result.current.editedComment.pendingEdits.spoiler).toBe(true)
// there are no unecessary keys in editedCommentResult.[state]Edits
expect(Object.keys(rendered.result.current.editedComment.succeededEdits).length).toBe(0)
expect(Object.keys(rendered.result.current.editedComment.pendingEdits).length).toBe(1)
expect(Object.keys(rendered.result.current.editedComment.failedEdits).length).toBe(0)

// update comment with edited prop in store
const updatedComment = {...commentsStore.getState().comments[commentCid]}
updatedComment.spoiler = true
updatedComment.updatedAt = commentEditTimestamp + 1
commentsStore.setState(({comments}) => ({comments: {...comments, [commentCid]: updatedComment}}))

// wait for comment to become updated and to not be account comment (not have comment.index)
await waitFor(() => rendered.result.current.comment.spoiler === true && rendered.result.current.comment.index === undefined)
expect(rendered.result.current.comment.spoiler).toBe(true)
expect(rendered.result.current.comment.index).toBe(undefined)

// wait for edit to become succeeded
await waitFor(() => rendered.result.current.editedComment.state === 'succeeded')
expect(rendered.result.current.editedComment.editedComment).not.toBe(undefined)
expect(rendered.result.current.editedComment.state).toBe('succeeded')
expect(rendered.result.current.editedComment.editedComment.spoiler).toBe(true)
expect(rendered.result.current.editedComment.succeededEdits.spoiler).toBe(true)
// there are no unecessary keys in editedCommentResult.[state]Edits
expect(Object.keys(rendered.result.current.editedComment.succeededEdits).length).toBe(1)
expect(Object.keys(rendered.result.current.editedComment.pendingEdits).length).toBe(0)
expect(Object.keys(rendered.result.current.editedComment.failedEdits).length).toBe(0)
})

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)

rendered.rerender(commentCid)

// wait for useComment to load comment from store
await waitFor(() => rendered.result.current.comment?.cid && rendered.result.current.comment.index === undefined)
expect(rendered.result.current.comment?.cid).toBe(commentCid)
// comment isn't an account comment (doesn't have comment.index)
expect(rendered.result.current.comment.index).toBe(undefined)

// publish edit options
let challengeVerificationCount = 0
const commentModerationTimestamp = Math.ceil(Date.now() / 1000)
const publishCommentModerationOptions = {
timestamp: commentModerationTimestamp,
commentCid,
subplebbitAddress,
commentModeration: {locked: true},
onChallenge: (challenge: any, comment: any) => comment.publishChallengeAnswers(),
onChallengeVerification: () => challengeVerificationCount++,
}

// publish edit
expect(rendered.result.current.editedComment.editedComment).toBe(undefined)
expect(rendered.result.current.editedComment.state).toBe('unedited')
await act(async () => {
await accountsActions.publishCommentModeration(publishCommentModerationOptions)
})

// edit is pending because the comment from store doesn't yet have locked: true
await waitFor(() => rendered.result.current.editedComment.editedComment)
expect(rendered.result.current.comment.locked).toBe(undefined)
Expand All @@ -2166,7 +2234,7 @@ describe('accounts', () => {
// update comment with edited prop in store
const updatedComment = {...commentsStore.getState().comments[commentCid]}
updatedComment.locked = true
updatedComment.updatedAt = commentEditTimestamp + 1
updatedComment.updatedAt = commentModerationTimestamp + 1
commentsStore.setState(({comments}) => ({comments: {...comments, [commentCid]: updatedComment}}))

// wait for comment to become updated and to not be account comment (not have comment.index)
Expand Down Expand Up @@ -2207,7 +2275,7 @@ describe('accounts', () => {
timestamp: commentEditTimestamp,
commentCid,
subplebbitAddress,
locked: true,
spoiler: true,
onChallenge: (challenge: any, comment: any) => comment.publishChallengeAnswers(),
onChallengeVerification: () => challengeVerificationCount++,
}
Expand All @@ -2221,12 +2289,95 @@ describe('accounts', () => {

// edit failed (not pending) because is already 1 hour old
await waitFor(() => rendered.result.current.editedComment.editedComment)
expect(rendered.result.current.comment.locked).toBe(undefined)
expect(rendered.result.current.comment.spoiler).toBe(undefined)
// updatedAt is required to evaluate the status of a CommentEdit
await waitFor(() => rendered.result.current.comment.updatedAt)
expect(rendered.result.current.comment.updatedAt).toBeGreaterThan(commentEditTimestamp)
expect(rendered.result.current.editedComment.editedComment).not.toBe(undefined)
expect(rendered.result.current.editedComment.state).toBe('failed')
expect(rendered.result.current.editedComment.editedComment.spoiler).toBe(undefined)
expect(rendered.result.current.editedComment.failedEdits.spoiler).toBe(true)
// there are no unecessary keys in editedCommentResult.[state]Edits
expect(Object.keys(rendered.result.current.editedComment.succeededEdits).length).toBe(0)
expect(Object.keys(rendered.result.current.editedComment.pendingEdits).length).toBe(0)
expect(Object.keys(rendered.result.current.editedComment.failedEdits).length).toBe(1)

// edit becomes pending if comment.updatedAt is too old
let updatedComment = {...commentsStore.getState().comments[commentCid]}
// make updatedAt 1 hour ago but still newer than edit time
updatedComment.updatedAt = commentEditTimestamp + 1
commentsStore.setState(({comments}) => ({comments: {...comments, [commentCid]: updatedComment}}))

// edit is pending because the comment from store updatedAt is too old
await waitFor(() => rendered.result.current.editedComment?.state === 'pending')
expect(rendered.result.current.editedComment.editedComment).not.toBe(undefined)
expect(rendered.result.current.editedComment.state).toBe('pending')
expect(rendered.result.current.editedComment.editedComment.spoiler).toBe(true)
expect(rendered.result.current.editedComment.pendingEdits.spoiler).toBe(true)
// there are no unecessary keys in editedCommentResult.[state]Edits
expect(Object.keys(rendered.result.current.editedComment.succeededEdits).length).toBe(0)
expect(Object.keys(rendered.result.current.editedComment.pendingEdits).length).toBe(1)
expect(Object.keys(rendered.result.current.editedComment.failedEdits).length).toBe(0)

// add spoiler: true to comment
updatedComment = {...commentsStore.getState().comments[commentCid]}
// make updatedAt 1 hour ago but still newer than edit time
updatedComment.spoiler = true
commentsStore.setState(({comments}) => ({comments: {...comments, [commentCid]: updatedComment}}))

// edit is succeeded even if updatedAt is old because is newer than the edit
await waitFor(() => rendered.result.current.editedComment?.state === 'succeeded')
expect(rendered.result.current.editedComment.editedComment).not.toBe(undefined)
expect(rendered.result.current.editedComment.state).toBe('succeeded')
expect(rendered.result.current.editedComment.editedComment.spoiler).toBe(true)
expect(rendered.result.current.editedComment.succeededEdits.spoiler).toBe(true)
// there are no unecessary keys in editedCommentResult.[state]Edits
expect(Object.keys(rendered.result.current.editedComment.succeededEdits).length).toBe(1)
expect(Object.keys(rendered.result.current.editedComment.pendingEdits).length).toBe(0)
expect(Object.keys(rendered.result.current.editedComment.failedEdits).length).toBe(0)
})

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)

rendered.rerender(commentCid)

// wait for useComment to load comment from store
await waitFor(() => rendered.result.current.comment?.cid && rendered.result.current.comment.index === undefined)
expect(rendered.result.current.comment?.cid).toBe(commentCid)
// comment isn't an account comment (doesn't have comment.index)
expect(rendered.result.current.comment.index).toBe(undefined)

// publish edit options
let challengeVerificationCount = 0
const commentModerationTimestamp = Math.ceil(Date.now() / 1000) - 60 * 60 // 1 hour ago to make the edit not pending
const publishCommentModerationOptions = {
timestamp: commentModerationTimestamp,
commentCid,
subplebbitAddress,
commentModeration: {locked: true},
onChallenge: (challenge: any, comment: any) => comment.publishChallengeAnswers(),
onChallengeVerification: () => challengeVerificationCount++,
}

// publish edit
expect(rendered.result.current.editedComment.editedComment).toBe(undefined)
expect(rendered.result.current.editedComment.state).toBe('unedited')
await act(async () => {
await accountsActions.publishCommentModeration(publishCommentModerationOptions)
})

// edit failed (not pending) because is already 1 hour old
await waitFor(() => rendered.result.current.editedComment.editedComment)
expect(rendered.result.current.comment.locked).toBe(undefined)
// updatedAt is required to evaluate the status of a CommentModeration
await waitFor(() => rendered.result.current.comment.updatedAt)
expect(rendered.result.current.comment.updatedAt).toBeGreaterThan(commentModerationTimestamp)
expect(rendered.result.current.editedComment.editedComment).not.toBe(undefined)
expect(rendered.result.current.editedComment.state).toBe('failed')
expect(rendered.result.current.editedComment.editedComment.locked).toBe(undefined)
expect(rendered.result.current.editedComment.failedEdits.locked).toBe(true)
// there are no unecessary keys in editedCommentResult.[state]Edits
Expand All @@ -2237,7 +2388,7 @@ describe('accounts', () => {
// edit becomes pending if comment.updatedAt is too old
let updatedComment = {...commentsStore.getState().comments[commentCid]}
// make updatedAt 1 hour ago but still newer than edit time
updatedComment.updatedAt = commentEditTimestamp + 1
updatedComment.updatedAt = commentModerationTimestamp + 1
commentsStore.setState(({comments}) => ({comments: {...comments, [commentCid]: updatedComment}}))

// edit is pending because the comment from store updatedAt is too old
Expand Down
9 changes: 8 additions & 1 deletion src/hooks/accounts/accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,14 @@ export function useEditedComment(options?: UseEditedCommentOptions): UseEditedCo

// iterate over commentEdits and consolidate them into 1 propertyNameEdits object
const propertyNameEdits: any = {}
for (const commentEdit of commentEdits) {
for (let commentEdit of commentEdits) {
// TODO: commentEdit and commentModeration are now separate, but both still in accountEdits store
// merge them until we find a better design
if (commentEdit.commentModeration) {
commentEdit = {...commentEdit, ...commentEdit.commentModeration}
delete commentEdit.commentModeration
}

for (const propertyName in commentEdit) {
// not valid edited properties
if (commentEdit[propertyName] === undefined || nonEditPropertyNames.has(propertyName)) {
Expand Down
Loading

0 comments on commit 1a8a5c1

Please sign in to comment.