diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 404ab4892..76ff3a16a 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -6150,6 +6150,16 @@ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", "dev": true }, + "isomorphic-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", + "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", + "dev": true, + "requires": { + "node-fetch": "^2.6.1", + "whatwg-fetch": "^3.4.1" + } + }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -8265,6 +8275,12 @@ "tslib": "^1.10.0" } }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "dev": true + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -12492,6 +12508,12 @@ "iconv-lite": "0.4.24" } }, + "whatwg-fetch": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.5.0.tgz", + "integrity": "sha512-jXkLtsR42xhXg7akoDKvKWE40eJeI+2KZqcp2h3NsOrRnDvtWX36KcKl30dy+hxECivdk2BVUHVNrPtoMBUx6A==", + "dev": true + }, "whatwg-mimetype": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", diff --git a/webapp/package.json b/webapp/package.json index 13be9f065..52b37e5c3 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -27,9 +27,11 @@ "transform": { "^.+\\.tsx?$": "ts-jest" }, - "collectCoverage": true, - "collectCoverageFrom": ["src/**/*.{ts,tsx,js,jsx}"] - }, + "collectCoverage": true, + "collectCoverageFrom": [ + "src/**/*.{ts,tsx,js,jsx}" + ] + }, "devDependencies": { "@formatjs/cli": "^2.13.2", "@formatjs/ts-transformer": "^2.11.3", @@ -60,6 +62,7 @@ "eslint-plugin-react": "7.20.6", "file-loader": "^6.1.0", "html-webpack-plugin": "^4.5.0", + "isomorphic-fetch": "^3.0.0", "jest": "^26.5.3", "sass": "^1.27.0", "sass-loader": "^10.0.2", diff --git a/webapp/src/octoClient.test.ts b/webapp/src/octoClient.test.ts new file mode 100644 index 000000000..543db21a6 --- /dev/null +++ b/webapp/src/octoClient.test.ts @@ -0,0 +1,91 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// Disable console log +console.log = jest.fn() + +import {IBlock} from './blocks/block' +import {MutableBoard} from './blocks/board' +import octoClient from './octoClient' +import 'isomorphic-fetch' + +const fetchMock = jest.fn(async () => { + const response = new Response() + return response +}) + +global.fetch = fetchMock + +beforeEach(() => { + fetchMock.mockReset() +}) + +test('OctoClient: get blocks', async () => { + const blocks = createBoards() + + fetchMock.mockReturnValueOnce(jsonResponse(JSON.stringify(blocks))) + let boards = await octoClient.getBlocksWithType('board') + expect(boards.length).toBe(blocks.length) + + fetchMock.mockReturnValueOnce(jsonResponse(JSON.stringify(blocks))) + boards = await octoClient.getSubtree() + expect(boards.length).toBe(blocks.length) + + fetchMock.mockReturnValueOnce(jsonResponse(JSON.stringify(blocks))) + boards = await octoClient.exportFullArchive() + expect(boards.length).toBe(blocks.length) + + fetchMock.mockReturnValueOnce(jsonResponse(JSON.stringify(blocks))) + const parentId = 'id1' + boards = await octoClient.getBlocksWithParent(parentId) + expect(boards.length).toBe(blocks.length) + + fetchMock.mockReturnValueOnce(jsonResponse(JSON.stringify(blocks))) + boards = await octoClient.getBlocksWithParent(parentId, 'board') + expect(boards.length).toBe(blocks.length) +}) + +test('OctoClient: insert blocks', async () => { + const blocks = createBoards() + + await octoClient.insertBlocks(blocks) + + expect(fetchMock).toBeCalledTimes(1) + expect(fetchMock).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + method: 'POST', + body: JSON.stringify(blocks), + })) +}) + +test('OctoClient: importFullArchive', async () => { + const blocks = createBoards() + + await octoClient.importFullArchive(blocks) + + expect(fetchMock).toBeCalledTimes(1) + expect(fetchMock).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + method: 'POST', + body: JSON.stringify(blocks), + })) +}) + +async function jsonResponse(json: string) { + const response = new Response(json) + return response +} + +function createBoards(): IBlock[] { + const blocks = [] + + for (let i = 0; i < 5; i++) { + const board = new MutableBoard() + board.id = `board${i + 1}` + blocks.push(board) + } + + return blocks +} diff --git a/webapp/src/undoManager.test.ts b/webapp/src/undoManager.test.ts index 7ff252663..87536a842 100644 --- a/webapp/src/undoManager.test.ts +++ b/webapp/src/undoManager.test.ts @@ -2,7 +2,6 @@ // See LICENSE.txt for license information. import undoManager from './undomanager' -import {Utils} from './utils' test('Basic undo/redo', async () => { expect(undoManager.canUndo).toBe(false) diff --git a/webapp/src/viewModel/cardTree.test.ts b/webapp/src/viewModel/cardTree.test.ts new file mode 100644 index 000000000..16925c224 --- /dev/null +++ b/webapp/src/viewModel/cardTree.test.ts @@ -0,0 +1,132 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// Disable console log +console.log = jest.fn() + +import 'isomorphic-fetch' +import {IBlock} from '../blocks/block' +import {Card, MutableCard} from '../blocks/card' +import {MutableCommentBlock} from '../blocks/commentBlock' +import {DividerBlock, MutableDividerBlock} from '../blocks/dividerBlock' +import {ImageBlock, MutableImageBlock} from '../blocks/imageBlock' +import {MutableTextBlock, TextBlock} from '../blocks/textBlock' + +import {MutableCardTree} from './cardTree' + +const fetchMock = jest.fn(async () => { + const response = new Response() + return response +}) + +global.fetch = fetchMock + +beforeEach(() => { + fetchMock.mockReset() +}) + +test('CardTree: sync', async () => { + const blocks: IBlock[] = [] + const card = createCard() + expect(card.id).not.toBeNull() + blocks.push(card) + const comment = createComment(card) + blocks.push(comment) + const text = createText(card) + blocks.push(text) + const image = createImage(card) + blocks.push(image) + const divider = createDivider(card) + blocks.push(divider) + + fetchMock.mockReturnValueOnce(jsonResponse(JSON.stringify(blocks))) + const cardTree = new MutableCardTree(card.id) + await cardTree.sync() + + expect(fetchMock).toBeCalledTimes(1) + expect(cardTree.card).toEqual(card) + expect(cardTree.comments).toEqual([comment]) + expect(cardTree.contents).toEqual([text, image, divider]) + + // Incremental update + const blocks2: IBlock[] = [] + const comment2 = createComment(card) + blocks2.push(comment2) + const text2 = createText(card) + blocks2.push(text2) + const image2 = createImage(card) + blocks2.push(image2) + const divider2 = createDivider(card) + blocks2.push(divider2) + + expect(cardTree.incrementalUpdate(blocks2)).toBe(true) + expect(cardTree.comments).toEqual([comment, comment2]) + expect(cardTree.contents).toEqual([text, image, divider, text2, image2, divider2]) + + // Incremental update: No change + const blocks3: IBlock[] = [] + const comment3 = createComment(card) + comment3.parentId = 'another parent' + blocks3.push(comment3) + expect(cardTree.incrementalUpdate(blocks3)).toBe(false) + + // Copy + const cardTree2 = cardTree.mutableCopy() + expect(cardTree2).toEqual(cardTree) + expect(cardTree2.card).toEqual(cardTree.card) +}) + +async function jsonResponse(json: string) { + const response = new Response(json) + return response +} + +function createCard(): Card { + const card = new MutableCard() + card.parentId = 'parent' + card.rootId = 'root' + card.title = 'title' + card.icon = 'i' + card.properties.property1 = 'value1' + + return card +} + +function createComment(card: Card): MutableCommentBlock { + const block = new MutableCommentBlock() + block.parentId = card.id + block.rootId = card.rootId + block.title = 'title' + + return block +} + +function createText(card: Card): TextBlock { + const block = new MutableTextBlock() + block.parentId = card.id + block.rootId = card.rootId + block.title = 'title' + block.order = 100 + + return block +} + +function createImage(card: Card): ImageBlock { + const block = new MutableImageBlock() + block.parentId = card.id + block.rootId = card.rootId + block.url = 'url' + block.order = 100 + + return block +} + +function createDivider(card: Card): DividerBlock { + const block = new MutableDividerBlock() + block.parentId = card.id + block.rootId = card.rootId + block.title = 'title' + block.order = 100 + + return block +}