diff --git a/webapp/src/components/__snapshots__/contentBlock.test.tsx.snap b/webapp/src/components/__snapshots__/contentBlock.test.tsx.snap
new file mode 100644
index 000000000..fda7fe913
--- /dev/null
+++ b/webapp/src/components/__snapshots__/contentBlock.test.tsx.snap
@@ -0,0 +1,569 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`components/contentBlock return commentBlock and click on menuwrapper 1`] = `
+
+`;
+
+exports[`components/contentBlock should match snapshot with commentBlock 1`] = `
+
+`;
+
+exports[`components/contentBlock should match snapshot with commentBlock readonly 1`] = `
+
+`;
+
+exports[`components/contentBlock should match snapshot with dividerBlock 1`] = `
+
+`;
+
+exports[`components/contentBlock should match snapshot with imageBlock 1`] = `
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`components/contentBlock should match snapshot with textBlock 1`] = `
+
+`;
diff --git a/webapp/src/components/contentBlock.test.tsx b/webapp/src/components/contentBlock.test.tsx
new file mode 100644
index 000000000..6a33cc87b
--- /dev/null
+++ b/webapp/src/components/contentBlock.test.tsx
@@ -0,0 +1,282 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import '@testing-library/jest-dom'
+import {act, render, screen} from '@testing-library/react'
+
+import React from 'react'
+import {mocked} from 'ts-jest/utils'
+
+import userEvent from '@testing-library/user-event'
+
+import {Utils} from '../utils'
+import {TestBlockFactory} from '../test/testBlockFactory'
+import {mockDOM, wrapDNDIntl} from '../testUtils'
+
+import mutator from '../mutator'
+
+import octoClient from '../octoClient'
+
+import ContentBlock from './contentBlock'
+import {CardDetailContext, CardDetailContextType} from './cardDetail/cardDetailContext'
+
+jest.mock('../mutator')
+jest.mock('../utils')
+jest.mock('../octoClient')
+
+beforeAll(mockDOM)
+
+describe('components/contentBlock', () => {
+ const mockedMutator = mocked(mutator, true)
+ const mockedUtils = mocked(Utils, true)
+ const mockedOcto = mocked(octoClient, true)
+
+ mockedUtils.createGuid.mockReturnValue('test-id')
+ mockedOcto.getFileAsDataUrl.mockResolvedValue('test.jpg')
+
+ const board = TestBlockFactory.createBoard()
+ board.fields.cardProperties = []
+ board.id = 'board-id'
+ board.rootId = board.id
+ const boardView = TestBlockFactory.createBoardView(board)
+ boardView.id = board.id
+ const card = TestBlockFactory.createCard(board)
+ card.id = board.id
+ card.createdBy = 'user-id-1'
+ const textBlock = TestBlockFactory.createText(card)
+ textBlock.id = 'textBlock-id'
+ const dividerBlock = TestBlockFactory.createDivider(card)
+ dividerBlock.id = 'dividerBlock-id'
+ const imageBlock = TestBlockFactory.createImage(card)
+ imageBlock.fields.fileId = 'test.jpg'
+ imageBlock.id = 'imageBlock-id'
+ const commentBlock = TestBlockFactory.createComment(card)
+ commentBlock.id = 'commentBlock-id'
+
+ card.fields.contentOrder = [textBlock.id, dividerBlock.id, commentBlock.id]
+ const cardDetailContextValue = (autoAdded: boolean): CardDetailContextType => ({
+ card,
+ lastAddedBlock: {
+ id: textBlock.id,
+ autoAdded,
+ },
+ deleteBlock: jest.fn(),
+ addBlock: jest.fn(),
+ })
+
+ beforeEach(jest.clearAllMocks)
+
+ test('should match snapshot with textBlock', async () => {
+ let container
+ await act(async () => {
+ const result = render(wrapDNDIntl(
+
+
+ ,
+ ))
+ container = result.container
+ })
+ expect(container).toMatchSnapshot()
+ })
+
+ test('should match snapshot with dividerBlock', async () => {
+ let container
+ await act(async () => {
+ const result = render(wrapDNDIntl(
+
+
+ ,
+ ))
+ container = result.container
+ })
+ expect(container).toMatchSnapshot()
+ })
+
+ test('should match snapshot with commentBlock', async () => {
+ let container
+ await act(async () => {
+ const result = render(wrapDNDIntl(
+
+
+ ,
+ ))
+ container = result.container
+ })
+ expect(container).toMatchSnapshot()
+ })
+
+ test('should match snapshot with imageBlock', async () => {
+ let container
+ await act(async () => {
+ const result = render(wrapDNDIntl(
+
+
+ ,
+ ))
+ container = result.container
+ })
+ expect(container).toMatchSnapshot()
+ })
+
+ test('should match snapshot with commentBlock readonly', async () => {
+ let container
+ await act(async () => {
+ const result = render(wrapDNDIntl(
+
+
+ ,
+ ))
+ container = result.container
+ })
+ expect(container).toMatchSnapshot()
+ })
+
+ test('return commentBlock and click on menuwrapper', async () => {
+ let container
+ await act(async () => {
+ const result = render(wrapDNDIntl(
+
+
+ ,
+ ))
+ container = result.container
+ })
+ const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})
+ userEvent.click(buttonElement)
+
+ expect(container).toMatchSnapshot()
+ })
+
+ test('return commentBlock and click move up', async () => {
+ await act(async () => {
+ render(wrapDNDIntl(
+
+
+ ,
+ ))
+ })
+ const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})
+ userEvent.click(buttonElement)
+ const buttonMoveUp = screen.getByRole('button', {name: 'Move up'})
+ userEvent.click(buttonMoveUp)
+ expect(mockedUtils.arrayMove).toBeCalledTimes(1)
+ expect(mockedMutator.changeCardContentOrder).toBeCalledTimes(1)
+ })
+
+ test('return commentBlock and click move down', async () => {
+ await act(async () => {
+ render(wrapDNDIntl(
+
+
+ ,
+ ))
+ })
+ const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})
+ userEvent.click(buttonElement)
+ const buttonMoveUp = screen.getByRole('button', {name: 'Move down'})
+ userEvent.click(buttonMoveUp)
+ expect(mockedUtils.arrayMove).toBeCalledTimes(1)
+ expect(mockedMutator.changeCardContentOrder).toBeCalledTimes(1)
+ })
+
+ test('return commentBlock and click delete', async () => {
+ await act(async () => {
+ render(wrapDNDIntl(
+
+
+ ,
+ ))
+ })
+ const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})
+ userEvent.click(buttonElement)
+ const buttonMoveUp = screen.getByRole('button', {name: 'Delete'})
+ userEvent.click(buttonMoveUp)
+ expect(mockedMutator.performAsUndoGroup).toBeCalledTimes(1)
+ })
+
+ test('return commentBlock and click delete with another contentOrder', async () => {
+ card.fields.contentOrder = [[textBlock.id], [dividerBlock.id], [commentBlock.id]]
+ await act(async () => {
+ render(wrapDNDIntl(
+
+
+ ,
+ ))
+ })
+ const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})
+ userEvent.click(buttonElement)
+ const buttonMoveUp = screen.getByRole('button', {name: 'Delete'})
+ userEvent.click(buttonMoveUp)
+ expect(mockedMutator.performAsUndoGroup).toBeCalledTimes(1)
+ })
+})