MM-52265: Boards ESLint improvements (#23218)

* eslint changes

* sync max-lines with channels rule

* eslint fixes

* remove dupe rules
Этот коммит содержится в:
Caleb Roseland
2023-05-03 09:32:51 -05:00
коммит произвёл GitHub
родитель b5ce8ac741
Коммит b43a74808d
298 изменённых файлов: 1890 добавлений и 628 удалений

Просмотреть файл

@@ -11,7 +11,8 @@
"formatjs", "formatjs",
"unused-imports", "unused-imports",
"no-relative-import-paths", "no-relative-import-paths",
"import-newlines" "import-newlines",
"eslint-comments"
], ],
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"settings": { "settings": {
@@ -27,21 +28,101 @@
"line", "line",
" Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n See LICENSE.txt for license information." " Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n See LICENSE.txt for license information."
], ],
"max-lines": "off", "array-bracket-spacing": [
"no-unused-expressions": 0,
"babel/no-unused-expressions": [
2, 2,
{ "never"
"allowShortCircuit": true
}
], ],
"eol-last": [ "array-callback-return": 2,
"arrow-body-style": 0,
"arrow-parens": [
2, 2,
"always" "always"
], ],
"import/no-unresolved": 2, "arrow-spacing": [
"import/order": [
2, 2,
{
"before": true,
"after": true
}
],
"block-scoped-var": 2,
"brace-style": [
2,
"1tbs",
{
"allowSingleLine": false
}
],
"capitalized-comments": 0,
"class-methods-use-this": 0,
"comma-dangle": [
2,
"always-multiline"
],
"comma-spacing": [
2,
{
"before": false,
"after": true
}
],
"comma-style": [
2,
"last"
],
"complexity": [
0,
10
],
"computed-property-spacing": [
2,
"never"
],
"consistent-return": 2,
"consistent-this": [
2,
"self"
],
"constructor-super": 2,
"curly": [
2,
"all"
],
"dot-location": [
2,
"property"
],
"dot-notation": 2,
"eqeqeq": [
2,
"smart"
],
"func-call-spacing": [
2,
"never"
],
"func-name-matching": 0,
"func-names": 2,
"func-style": [
2,
"declaration",
{
"allowArrowFunctions": true
}
],
"generator-star-spacing": [
2,
{
"before": false,
"after": true
}
],
"global-require": 2,
"guard-for-in": 2,
"id-blacklist": 0,
"import/no-unresolved": 0, // ts handles this better
"import/order": [
"error",
{ {
"newlines-between": "always-and-inside-groups", "newlines-between": "always-and-inside-groups",
"groups": [ "groups": [
@@ -56,27 +137,413 @@
] ]
} }
], ],
"indent": 0, // ts handles this
"jsx-quotes": [
2,
"prefer-single"
],
"key-spacing": [
2,
{
"beforeColon": false,
"afterColon": true,
"mode": "strict"
}
],
"keyword-spacing": [
2,
{
"before": true,
"after": true,
"overrides": {}
}
],
"line-comment-position": 0,
"linebreak-style": 2,
"lines-around-comment": [
2,
{
"beforeBlockComment": true,
"beforeLineComment": true,
"allowBlockStart": true,
"allowBlockEnd": true
}
],
"max-lines": [
2,
{
"max": 800,
"skipBlankLines": true,
"skipComments": true
}
],
"max-statements-per-line": [
2,
{
"max": 1
}
],
"multiline-ternary": [
1,
"never"
],
"new-cap": 2,
"new-parens": 2,
"padding-line-between-statements": [
2,
{
"blankLine": "always",
"prev": "*",
"next": "return"
}
],
"newline-per-chained-call": 0,
"no-alert": 2,
"no-array-constructor": 2,
"no-await-in-loop": 2,
"no-caller": 2,
"no-case-declarations": 2,
"no-class-assign": 2,
"no-compare-neg-zero": 2,
"no-cond-assign": [
2,
"except-parens"
],
"no-confusing-arrow": 2,
"no-console": 2,
"no-const-assign": 2,
"no-constant-condition": 2,
"no-debugger": 2,
"no-div-regex": 2,
"no-dupe-args": 2,
"no-dupe-class-members": 2,
"no-dupe-keys": 2,
"no-duplicate-case": 2,
"no-duplicate-imports": [
2,
{
"includeExports": true
}
],
"no-else-return": 2,
"no-empty": 2,
"no-empty-function": 2,
"no-empty-pattern": 2,
"no-eval": 2,
"no-ex-assign": 2,
"no-extend-native": 2,
"no-extra-bind": 2,
"no-extra-label": 2,
"no-extra-parens": 0,
"no-extra-semi": 2,
"no-fallthrough": 2,
"no-floating-decimal": 2,
"no-func-assign": 2,
"no-global-assign": 2,
"no-implicit-coercion": 2,
"no-implicit-globals": 0,
"no-implied-eval": 2,
"no-inner-declarations": 0,
"no-invalid-regexp": 2,
"no-irregular-whitespace": 2,
"no-iterator": 2,
"no-labels": 2,
"no-lone-blocks": 2,
"no-lonely-if": 2,
"no-loop-func": 2,
"no-magic-numbers": [
0,
{
"ignore": [
-1,
0,
1,
2
],
"enforceConst": true,
"detectObjects": true
}
],
"no-mixed-operators": [
2,
{
"allowSamePrecedence": false
}
],
"no-mixed-spaces-and-tabs": 2,
"no-multi-assign": 2,
"no-multi-spaces": [
2,
{
"exceptions": {
"Property": false
}
}
],
"no-multi-str": 0,
"no-multiple-empty-lines": [
2,
{
"max": 1
}
],
"no-native-reassign": 2,
"no-negated-condition": 2,
"no-nested-ternary": 2,
"no-new": 2,
"no-new-func": 2,
"no-new-object": 2,
"no-new-symbol": 2,
"no-new-wrappers": 2,
"no-octal-escape": 2,
"no-param-reassign": 2,
"no-process-env": 2,
"no-process-exit": 2,
"no-proto": 2,
"no-redeclare": 2,
"no-return-assign": [
2,
"always"
],
"no-return-await": 2,
"no-script-url": 2,
"no-self-assign": [
2,
{
"props": true
}
],
"no-self-compare": 2,
"no-sequences": 2,
"no-shadow-restricted-names": 2,
"no-spaced-func": 2,
"no-tabs": 0,
"no-template-curly-in-string": 2,
"no-ternary": 0,
"no-this-before-super": 2,
"no-throw-literal": 2,
"no-trailing-spaces": [
2,
{
"skipBlankLines": false
}
],
"no-undef-init": 2,
"no-undefined": 0, "no-undefined": 0,
"react/jsx-filename-extension": 0, "no-underscore-dangle": 2,
"no-unexpected-multiline": 2,
"no-unmodified-loop-condition": 2,
"no-unneeded-ternary": [
2,
{
"defaultAssignment": false
}
],
"no-unreachable": 2,
"no-unsafe-finally": 2,
"no-unsafe-negation": 2,
"no-unused-expressions": 2,
"no-unused-vars": [
2,
{
"vars": "all",
"args": "after-used"
}
],
"no-use-before-define": 0,
"no-useless-computed-key": 2,
"no-useless-concat": 2,
"no-useless-constructor": 2,
"no-useless-escape": 2,
"no-useless-rename": 2,
"no-useless-return": 2,
"no-var": 0,
"no-void": 2,
"no-warning-comments": 1,
"no-whitespace-before-property": 2,
"no-with": 2,
"object-curly-newline": 0,
"object-curly-spacing": [
2,
"never"
],
"object-property-newline": [
2,
{
"allowMultiplePropertiesPerLine": true
}
],
"object-shorthand": [
2,
"always"
],
"one-var": [
2,
"never"
],
"one-var-declaration-per-line": 0,
"operator-assignment": [
2,
"always"
],
"operator-linebreak": [
2,
"after"
],
"padded-blocks": [
2,
"never"
],
"prefer-arrow-callback": 2,
"prefer-const": 2,
"prefer-destructuring": 0,
"prefer-numeric-literals": 2,
"prefer-promise-reject-errors": 2,
"prefer-rest-params": 2,
"prefer-spread": 2,
"prefer-template": 0,
"quote-props": [
2,
"as-needed"
],
"quotes": [
2,
"single",
"avoid-escape"
],
"radix": 2,
"react/display-name": [
0,
{
"ignoreTranspilerName": false
}
],
"react/forbid-component-props": 0,
"react/forbid-elements": [
2,
{
"forbid": [
"embed"
]
}
],
"react/jsx-boolean-value": [
2,
"always"
],
"react/jsx-closing-bracket-location": [
2,
{
"location": "tag-aligned"
}
],
"react/jsx-curly-spacing": [
2,
"never"
],
"react/jsx-equals-spacing": [
2,
"never"
],
"react/jsx-filename-extension": 2,
"react/jsx-first-prop-new-line": [
2,
"multiline"
],
"react/jsx-handler-names": 0,
"react/jsx-indent": [
2,
4
],
"react/jsx-indent-props": [
2,
4
],
"react/jsx-key": 2,
"react/jsx-max-props-per-line": [
2,
{
"maximum": 1
}
],
"react/jsx-no-bind": 0,
"react/jsx-no-comment-textnodes": 2,
"react/jsx-no-duplicate-props": [
2,
{
"ignoreCase": false
}
],
"react/jsx-no-literals": 2,
"react/jsx-no-target-blank": 2,
"react/jsx-no-undef": 2,
"react/jsx-pascal-case": 2,
"react/jsx-tag-spacing": [
2,
{
"closingSlash": "never",
"beforeSelfClosing": "never",
"afterOpening": "never"
}
],
"react/jsx-uses-react": 2,
"react/jsx-uses-vars": 2,
"react/jsx-wrap-multilines": 2,
"react/no-array-index-key": 1,
"react/no-children-prop": 2,
"react/no-danger": 0,
"react/no-danger-with-children": 2,
"react/no-deprecated": 1,
"react/no-did-mount-set-state": 2,
"react/no-did-update-set-state": 2,
"react/no-direct-mutation-state": 2,
"react/no-find-dom-node": 1,
"react/no-is-mounted": 2,
"react/no-multi-comp": [
2,
{
"ignoreStateless": true
}
],
"react/no-render-return-value": 2,
"react/no-set-state": 0,
"react/no-string-refs": 0,
"react/no-unescaped-entities": 2,
"react/no-unknown-property": 2,
"react/no-unused-prop-types": [
1,
{
"skipShapeProps": true
}
],
"react/prefer-es6-class": 2,
"react/prefer-stateless-function": 2,
"react/prop-types": [ "react/prop-types": [
2, 2,
{ {
"ignore": [ "ignore": [
"location", "location",
"history", "history",
"component" "component",
"className"
] ]
} }
], ],
"react/no-string-refs": 2, "react/require-default-props": 0,
"no-only-tests/no-only-tests": [ "react/require-optimization": 1,
2, "react/require-render-return": 2,
{ "react/self-closing-comp": 2,
"focus": [ "react/sort-comp": 0,
"only", "react/style-prop-object": [
"skip" 2,
] {
} "allow": [
"FormattedNumber",
"FormattedDuration",
"FormattedRelativeTime",
"Timestamp"
]
}
], ],
"max-nested-callbacks": [ "max-nested-callbacks": [
2, 2,
@@ -95,11 +562,84 @@
2, 2,
3 3
], ],
"object-curly-spacing": [ "formatjs/no-multiple-whitespaces": 2,
"require-yield": 2,
"rest-spread-spacing": [
2, 2,
"never" "never"
], ],
"formatjs/no-multiple-whitespaces": 2 "semi": [
2,
"always"
],
"semi-spacing": [
2,
{
"before": false,
"after": true
}
],
"sort-imports": [
2,
{
"ignoreDeclarationSort": true
}
],
"sort-keys": 0,
"space-before-blocks": [
2,
"always"
],
"space-before-function-paren": [
2,
{
"anonymous": "never",
"named": "never",
"asyncArrow": "always"
}
],
"space-in-parens": [
2,
"never"
],
"space-infix-ops": 2,
"space-unary-ops": [
2,
{
"words": true,
"nonwords": false
}
],
"symbol-description": 2,
"template-curly-spacing": [
2,
"never"
],
"valid-typeof": [
2,
{
"requireStringLiterals": false
}
],
"vars-on-top": 0,
"wrap-iife": [
2,
"outside"
],
"wrap-regex": 2,
"yoda": [
2,
"never",
{
"exceptRange": false,
"onlyEquality": false
}
],
"eol-last": [
2,
"always"
],
"eslint-comments/no-unused-disable": 2
}, },
"overrides": [ "overrides": [
{ {
@@ -206,7 +746,9 @@
"global-require": 0, "global-require": 0,
"new-cap": 0, "new-cap": 0,
"prefer-arrow-callback": 0, "prefer-arrow-callback": 0,
"no-import-assign": 0 "no-import-assign": 0,
"no-console": 0,
"max-lines": 0
} }
} }
] ]

Просмотреть файл

@@ -5,63 +5,63 @@
const config = { const config = {
transform: { transform: {
"^.+\\.(t|j)sx?$": ["@swc/jest"] '^.+\\.(t|j)sx?$': ['@swc/jest'],
}, },
moduleFileExtensions: [ moduleFileExtensions: [
"ts", 'ts',
"tsx", 'tsx',
"js", 'js',
"jsx", 'jsx',
"json", 'json',
"node" 'node',
], ],
extensionsToTreatAsEsm: ['.ts', '.tsx'], extensionsToTreatAsEsm: ['.ts', '.tsx'],
transformIgnorePatterns: [ transformIgnorePatterns: [
"/nanoevents/", '/nanoevents/',
"node_modules/(?!react-native|react-router|react-day-picker)" 'node_modules/(?!react-native|react-router|react-day-picker)',
], ],
testEnvironment: "jsdom", testEnvironment: 'jsdom',
collectCoverage: true, collectCoverage: true,
collectCoverageFrom: [ collectCoverageFrom: [
"src/**/*.{ts,tsx,js,jsx}", 'src/**/*.{ts,tsx,js,jsx}',
"!src/test/**" '!src/test/**',
], ],
testPathIgnorePatterns: [ testPathIgnorePatterns: [
"/node_modules/", '/node_modules/',
], ],
clearMocks: true, clearMocks: true,
coverageReporters: [ coverageReporters: [
"lcov", 'lcov',
"text-summary" 'text-summary',
], ],
moduleNameMapper: { moduleNameMapper: {
"^.+\\.(scss|css)$": "<rootDir>/src/test/style_mock.json", '^.+\\.(scss|css)$': '<rootDir>/src/test/style_mock.json',
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js", '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/__mocks__/fileMock.js',
"\\.(scss|css)$": "<rootDir>/__mocks__/styleMock.js", '\\.(scss|css)$': '<rootDir>/__mocks__/styleMock.js',
"^bundle-loader\\?lazy\\!(.*)$": "$1", '^bundle-loader\\?lazy\\!(.*)$': '$1',
"^src(.*)$": "<rootDir>/src$1", '^src(.*)$': '<rootDir>/src$1',
"^i18n(.*)$": "<rootDir>/i18n$1", '^i18n(.*)$': '<rootDir>/i18n$1',
"^static(.*)$": "<rootDir>/static$1", '^static(.*)$': '<rootDir>/static$1',
"^moment(.*)$": "<rootDir>/../node_modules/moment$1", '^moment(.*)$': '<rootDir>/../node_modules/moment$1',
}, },
moduleDirectories: [ moduleDirectories: [
"src", 'src',
"node_modules", 'node_modules',
], ],
reporters: [ reporters: [
"default", 'default',
"jest-junit" 'jest-junit',
], ],
setupFiles: [ setupFiles: [
"jest-canvas-mock" 'jest-canvas-mock',
], ],
setupFilesAfterEnv: [ setupFilesAfterEnv: [
"<rootDir>/src/test/setup.tsx" '<rootDir>/src/test/setup.tsx',
], ],
testTimeout: 60000, testTimeout: 60000,
testEnvironmentOptions: { testEnvironmentOptions: {
url: "http://localhost:8065" url: 'http://localhost:8065',
} },
}; };
module.exports = config; module.exports = config;

Просмотреть файл

@@ -19,5 +19,6 @@ module.exports = function loader(source) {
newSource.push(line); newSource.push(line);
} }
}); });
return newSource.join('\n'); return newSource.join('\n');
}; };

Просмотреть файл

@@ -106,6 +106,8 @@
"css-loader": "6.7.1", "css-loader": "6.7.1",
"eslint-import-resolver-webpack": "0.13.2", "eslint-import-resolver-webpack": "0.13.2",
"eslint-plugin-babel": "^5.3.1", "eslint-plugin-babel": "^5.3.1",
"eslint-plugin-eslint-comments": "3.2.0",
"eslint-plugin-formatjs": "4.9.0",
"eslint-plugin-header": "3.1.1", "eslint-plugin-header": "3.1.1",
"eslint-plugin-import": "2.25.4", "eslint-plugin-import": "2.25.4",
"eslint-plugin-import-newlines": "1.3.1", "eslint-plugin-import-newlines": "1.3.1",

Просмотреть файл

@@ -14,8 +14,8 @@ import FlashMessages from './components/flashMessages'
import NewVersionBanner from './components/newVersionBanner' import NewVersionBanner from './components/newVersionBanner'
import {Utils} from './utils' import {Utils} from './utils'
import {fetchMe, getMe} from './store/users' import {fetchMe, getMe} from './store/users'
import {getLanguage, fetchLanguage} from './store/language' import {fetchLanguage, getLanguage} from './store/language'
import {useAppSelector, useAppDispatch} from './store/hooks' import {useAppDispatch, useAppSelector} from './store/hooks'
import {fetchClientConfig} from './store/clientConfig' import {fetchClientConfig} from './store/clientConfig'
import FocalboardRouter from './router' import FocalboardRouter from './router'

Просмотреть файл

@@ -21,8 +21,8 @@ class Archiver {
private static exportArchive(prom: Promise<Response>): void { private static exportArchive(prom: Promise<Response>): void {
// TODO: don't download whole archive before presenting SaveAs dialog. // TODO: don't download whole archive before presenting SaveAs dialog.
prom.then((response) => { prom.then((response) => {
response.blob(). response.blob()
then((blob) => { .then((blob) => {
const link = document.createElement('a') const link = document.createElement('a')
link.style.display = 'none' link.style.display = 'none'

Просмотреть файл

@@ -8,6 +8,7 @@ class BlockIcons {
randomIcon(): string { randomIcon(): string {
const index = Math.floor(Math.random() * randomEmojiList.length) const index = Math.floor(Math.random() * randomEmojiList.length)
const icon = randomEmojiList[index] const icon = randomEmojiList[index]
return icon return icon
} }
} }

Просмотреть файл

@@ -2,7 +2,7 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {TestBlockFactory} from 'src/test/testBlockFactory' import {TestBlockFactory} from 'src/test/testBlockFactory'
import {createPatchesFromBlocks, createBlock} from './block' import {createBlock, createPatchesFromBlocks} from './block'
describe('block tests', () => { describe('block tests', () => {
const board = TestBlockFactory.createBoard() const board = TestBlockFactory.createBoard()

Просмотреть файл

@@ -53,6 +53,7 @@ interface FileInfo {
function createBlock(block?: Block): Block { function createBlock(block?: Block): Block {
const now = Date.now() const now = Date.now()
return { return {
id: block?.id || Utils.createGuid(Utils.blockTypeToIDType(block?.type)), id: block?.id || Utils.createGuid(Utils.blockTypeToIDType(block?.type)),
schema: 1, schema: 1,

Просмотреть файл

@@ -3,10 +3,10 @@
import {TestBlockFactory} from 'src/test/testBlockFactory' import {TestBlockFactory} from 'src/test/testBlockFactory'
import { import {
createPatchesFromBoards,
createBoard,
IPropertyTemplate, IPropertyTemplate,
createPatchesFromBoardsAndBlocks createBoard,
createPatchesFromBoards,
createPatchesFromBoardsAndBlocks,
} from './board' } from './board'
import {createBlock} from './block' import {createBlock} from './block'

Просмотреть файл

@@ -3,7 +3,7 @@
import difference from 'lodash/difference' import difference from 'lodash/difference'
import {Utils, IDType} from 'src/utils' import {IDType, Utils} from 'src/utils'
import {Block, BlockPatch, createPatchesFromBlocks} from './block' import {Block, BlockPatch, createPatchesFromBlocks} from './block'
import {Card} from './card' import {Card} from './card'
@@ -54,7 +54,7 @@ type BoardPatch = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
updatedProperties?: Record<string, any> updatedProperties?: Record<string, any>
deletedProperties?: string[] deletedProperties?: string[]
// eslint-disable-next-line @typescript-eslint/no-explicit-any
updatedCardProperties?: IPropertyTemplate[] updatedCardProperties?: IPropertyTemplate[]
deletedCardProperties?: string[] deletedCardProperties?: string[]
} }

Просмотреть файл

@@ -27,6 +27,7 @@ function createCard(block?: Block): Card {
} }
} }
} }
return { return {
...createBlock(block), ...createBlock(block),
type: 'card', type: 'card',

Просмотреть файл

@@ -21,9 +21,11 @@ function createFilterGroup(o?: FilterGroup): FilterGroup {
if (isAFilterGroupInstance(p)) { if (isAFilterGroupInstance(p)) {
return createFilterGroup(p) return createFilterGroup(p)
} }
return createFilterClause(p) return createFilterClause(p)
}) })
} }
return { return {
operation: o?.operation || 'and', operation: o?.operation || 'and',
filters, filters,

Просмотреть файл

@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {Card} from './blocks/card' import {Card} from './blocks/card'
import {IPropertyTemplate, IPropertyOption, BoardGroup} from './blocks/board' import {BoardGroup, IPropertyOption, IPropertyTemplate} from './blocks/board'
function groupCardsByOptions(cards: Card[], optionIds: string[], groupByProperty?: IPropertyTemplate): BoardGroup[] { function groupCardsByOptions(cards: Card[], optionIds: string[], groupByProperty?: IPropertyTemplate): BoardGroup[] {
const groups = [] const groups = []
@@ -22,6 +22,7 @@ function groupCardsByOptions(cards: Card[], optionIds: string[], groupByProperty
// Empty group // Empty group
const emptyGroupCards = cards.filter((card) => { const emptyGroupCards = cards.filter((card) => {
const groupByOptionId = card.fields.properties[groupByProperty?.id || ''] const groupByOptionId = card.fields.properties[groupByProperty?.id || '']
return !groupByOptionId || !groupByProperty?.options.find((option) => option.id === groupByOptionId) return !groupByOptionId || !groupByProperty?.options.find((option) => option.id === groupByOptionId)
}) })
const group: BoardGroup = { const group: BoardGroup = {
@@ -31,15 +32,16 @@ function groupCardsByOptions(cards: Card[], optionIds: string[], groupByProperty
groups.push(group) groups.push(group)
} }
} }
return groups return groups
} }
function getOptionGroups(cards: Card[], visibleOptionIds: string[], hiddenOptionIds: string[], groupByProperty?: IPropertyTemplate): {visible: BoardGroup[], hidden: BoardGroup[]} { function getOptionGroups(cards: Card[], visibleOptionIds: string[], hiddenOptionIds: string[], groupByProperty?: IPropertyTemplate): {visible: BoardGroup[], hidden: BoardGroup[]} {
let unassignedOptionIds: string[] = [] let unassignedOptionIds: string[] = []
if (groupByProperty) { if (groupByProperty) {
unassignedOptionIds = groupByProperty.options. unassignedOptionIds = groupByProperty.options
filter((o: IPropertyOption) => !visibleOptionIds.includes(o.id) && !hiddenOptionIds.includes(o.id)). .filter((o: IPropertyOption) => !visibleOptionIds.includes(o.id) && !hiddenOptionIds.includes(o.id))
map((o: IPropertyOption) => o.id) .map((o: IPropertyOption) => o.id)
} }
const allVisibleOptionIds = [...visibleOptionIds, ...unassignedOptionIds] const allVisibleOptionIds = [...visibleOptionIds, ...unassignedOptionIds]
@@ -50,6 +52,7 @@ function getOptionGroups(cards: Card[], visibleOptionIds: string[], hiddenOption
const visibleGroups = groupCardsByOptions(cards, allVisibleOptionIds, groupByProperty) const visibleGroups = groupCardsByOptions(cards, allVisibleOptionIds, groupByProperty)
const hiddenGroups = groupCardsByOptions(cards, hiddenOptionIds, groupByProperty) const hiddenGroups = groupCardsByOptions(cards, hiddenOptionIds, groupByProperty)
return {visible: visibleGroups, hidden: hiddenGroups} return {visible: visibleGroups, hidden: hiddenGroups}
} }
export function getVisibleAndHiddenGroups(cards: Card[], visibleOptionIds: string[], hiddenOptionIds: string[], groupByProperty?: IPropertyTemplate): {visible: BoardGroup[], hidden: BoardGroup[]} { export function getVisibleAndHiddenGroups(cards: Card[], visibleOptionIds: string[], hiddenOptionIds: string[], groupByProperty?: IPropertyTemplate): {visible: BoardGroup[], hidden: BoardGroup[]} {
@@ -70,6 +73,7 @@ function getPersonGroups(cards: Card[], groupByProperty: IPropertyTemplate, hidd
} }
const curGroup = unique[key] ?? [] const curGroup = unique[key] ?? []
return {...unique, [key]: [...curGroup, item]} return {...unique, [key]: [...curGroup, item]}
}, {}) }, {})

Просмотреть файл

@@ -27,6 +27,7 @@ class CardFilter {
} }
} }
} }
return dateProperty return dateProperty
} }
@@ -51,6 +52,7 @@ class CardFilter {
return true return true
} }
} }
return false return false
} }
Utils.assert(filterGroup.operation === 'and') Utils.assert(filterGroup.operation === 'and')
@@ -63,6 +65,7 @@ class CardFilter {
return false return false
} }
} }
return true return true
} }
@@ -95,12 +98,14 @@ class CardFilter {
if (filter.values?.length < 1) { if (filter.values?.length < 1) {
break break
} // No values = ignore clause (always met) } // No values = ignore clause (always met)
return (filter.values.find((cValue) => (Array.isArray(value) ? value.includes(cValue) : cValue === value)) !== undefined) return (filter.values.find((cValue) => (Array.isArray(value) ? value.includes(cValue) : cValue === value)) !== undefined)
} }
case 'notIncludes': { case 'notIncludes': {
if (filter.values?.length < 1) { if (filter.values?.length < 1) {
break break
} // No values = ignore clause (always met) } // No values = ignore clause (always met)
return (filter.values.find((cValue) => (Array.isArray(value) ? value.includes(cValue) : cValue === value)) === undefined) return (filter.values.find((cValue) => (Array.isArray(value) ? value.includes(cValue) : cValue === value)) === undefined)
} }
case 'isEmpty': { case 'isEmpty': {
@@ -128,50 +133,59 @@ class CardFilter {
if (dateValue.from) { if (dateValue.from) {
return dateValue.from > (numericFilter - halfDay) && dateValue.from < (numericFilter + halfDay) return dateValue.from > (numericFilter - halfDay) && dateValue.from < (numericFilter + halfDay)
} }
return false return false
} }
if (dateValue.from && dateValue.to) { if (dateValue.from && dateValue.to) {
return dateValue.from <= numericFilter && dateValue.to >= numericFilter return dateValue.from <= numericFilter && dateValue.to >= numericFilter
} }
return dateValue.from === numericFilter return dateValue.from === numericFilter
} }
return filter.values[0]?.toLowerCase() === value return filter.values[0]?.toLowerCase() === value
} }
case 'contains': { case 'contains': {
if (filter.values.length === 0) { if (filter.values.length === 0) {
return true return true
} }
return (value as string || '').includes(filter.values[0]?.toLowerCase()) return (value as string || '').includes(filter.values[0]?.toLowerCase())
} }
case 'notContains': { case 'notContains': {
if (filter.values.length === 0) { if (filter.values.length === 0) {
return true return true
} }
return !(value as string || '').includes(filter.values[0]?.toLowerCase()) return !(value as string || '').includes(filter.values[0]?.toLowerCase())
} }
case 'startsWith': { case 'startsWith': {
if (filter.values.length === 0) { if (filter.values.length === 0) {
return true return true
} }
return (value as string || '').startsWith(filter.values[0]?.toLowerCase()) return (value as string || '').startsWith(filter.values[0]?.toLowerCase())
} }
case 'notStartsWith': { case 'notStartsWith': {
if (filter.values.length === 0) { if (filter.values.length === 0) {
return true return true
} }
return !(value as string || '').startsWith(filter.values[0]?.toLowerCase()) return !(value as string || '').startsWith(filter.values[0]?.toLowerCase())
} }
case 'endsWith': { case 'endsWith': {
if (filter.values.length === 0) { if (filter.values.length === 0) {
return true return true
} }
return (value as string || '').endsWith(filter.values[0]?.toLowerCase()) return (value as string || '').endsWith(filter.values[0]?.toLowerCase())
} }
case 'notEndsWith': { case 'notEndsWith': {
if (filter.values.length === 0) { if (filter.values.length === 0) {
return true return true
} }
return !(value as string || '').endsWith(filter.values[0]?.toLowerCase()) return !(value as string || '').endsWith(filter.values[0]?.toLowerCase())
} }
case 'isBefore': { case 'isBefore': {
@@ -187,11 +201,13 @@ class CardFilter {
if (dateValue.from) { if (dateValue.from) {
return dateValue.from < (numericFilter - halfDay) return dateValue.from < (numericFilter - halfDay)
} }
return false return false
} }
return dateValue.from ? dateValue.from < numericFilter : false return dateValue.from ? dateValue.from < numericFilter : false
} }
return false return false
} }
case 'isAfter': { case 'isAfter': {
@@ -207,14 +223,17 @@ class CardFilter {
if (dateValue.from) { if (dateValue.from) {
return dateValue.from > (numericFilter + halfDay) return dateValue.from > (numericFilter + halfDay)
} }
return false return false
} }
if (dateValue.to) { if (dateValue.to) {
return dateValue.to > numericFilter return dateValue.to > numericFilter
} }
return dateValue.from ? dateValue.from > numericFilter : false return dateValue.from ? dateValue.from > numericFilter : false
} }
return false return false
} }
@@ -222,6 +241,7 @@ class CardFilter {
Utils.assertFailure(`Invalid filter condition ${filter.condition}`) Utils.assertFailure(`Invalid filter condition ${filter.condition}`)
} }
} }
return true return true
} }
@@ -243,6 +263,7 @@ class CardFilter {
if (property.value) { if (property.value) {
result[property.id] = property.value result[property.id] = property.value
} }
return result return result
} }
@@ -254,6 +275,7 @@ class CardFilter {
result[property.id] = property.value result[property.id] = property.value
} }
}) })
return result return result
} }
@@ -261,6 +283,7 @@ class CardFilter {
const template = templates.find((o) => o.id === filterClause.propertyId) const template = templates.find((o) => o.id === filterClause.propertyId)
if (!template) { if (!template) {
Utils.assertFailure(`propertyThatMeetsFilterClause. Cannot find template: ${filterClause.propertyId}`) Utils.assertFailure(`propertyThatMeetsFilterClause. Cannot find template: ${filterClause.propertyId}`)
return {id: filterClause.propertyId} return {id: filterClause.propertyId}
} }
@@ -273,6 +296,7 @@ class CardFilter {
if (filterClause.values.length < 1) { if (filterClause.values.length < 1) {
return {id: filterClause.propertyId} return {id: filterClause.propertyId}
} }
return {id: filterClause.propertyId, value: filterClause.values[0]} return {id: filterClause.propertyId, value: filterClause.values[0]}
} }
case 'notIncludes': { case 'notIncludes': {
@@ -285,8 +309,10 @@ class CardFilter {
if (template.type === 'select') { if (template.type === 'select') {
if (template.options.length > 0) { if (template.options.length > 0) {
const option = template.options[0] const option = template.options[0]
return {id: filterClause.propertyId, value: option.id} return {id: filterClause.propertyId, value: option.id}
} }
return {id: filterClause.propertyId} return {id: filterClause.propertyId}
} }

Просмотреть файл

@@ -4,7 +4,6 @@
import React, {ReactElement, ReactNode} from 'react' import React, {ReactElement, ReactNode} from 'react'
import {render, screen, waitFor} from '@testing-library/react' import {render, screen, waitFor} from '@testing-library/react'
import {mocked} from 'jest-mock' import {mocked} from 'jest-mock'
import userEvent from '@testing-library/user-event' import userEvent from '@testing-library/user-event'
@@ -114,6 +113,5 @@ describe('components/addContentMenuItem', () => {
) )
expect(console.error).toBeCalledWith(expect.stringContaining('addContentMenu, unknown content type: unknown')) expect(console.error).toBeCalledWith(expect.stringContaining('addContentMenu, unknown content type: unknown'))
expect(container).toMatchSnapshot() expect(container).toMatchSnapshot()
}) })
}) })

Просмотреть файл

@@ -4,7 +4,7 @@
import React from 'react' import React from 'react'
import {useIntl} from 'react-intl' import {useIntl} from 'react-intl'
import {BlockTypes, Block} from 'src/blocks/block' import {Block, BlockTypes} from 'src/blocks/block'
import {Card} from 'src/blocks/card' import {Card} from 'src/blocks/card'
import mutator from 'src/mutator' import mutator from 'src/mutator'
import octoClient from 'src/octoClient' import octoClient from 'src/octoClient'
@@ -27,6 +27,7 @@ const AddContentMenuItem = (props: Props): JSX.Element => {
const handler = contentRegistry.getHandler(type) const handler = contentRegistry.getHandler(type)
if (!handler) { if (!handler) {
Utils.logError(`addContentMenu, unknown content type: ${type}`) Utils.logError(`addContentMenu, unknown content type: ${type}`)
return <></> return <></>
} }

Просмотреть файл

@@ -6,7 +6,6 @@ import {fireEvent, render, screen} from '@testing-library/react'
import userEvent from '@testing-library/user-event' import userEvent from '@testing-library/user-event'
import {mocked} from 'jest-mock' import {mocked} from 'jest-mock'
import mutator from 'src/mutator' import mutator from 'src/mutator'

Просмотреть файл

@@ -4,17 +4,17 @@
import React from 'react' import React from 'react'
import {Provider as ReduxProvider} from 'react-redux' import {Provider as ReduxProvider} from 'react-redux'
import { import {
act,
fireEvent,
render, render,
screen, screen,
fireEvent,
act
} from '@testing-library/react' } from '@testing-library/react'
import { import {
mockDOM, mockDOM,
wrapDNDIntl,
mockStateStore, mockStateStore,
setup setup,
wrapDNDIntl,
} from 'src/testUtils' } from 'src/testUtils'
import {TestBlockFactory} from 'src/test/testBlockFactory' import {TestBlockFactory} from 'src/test/testBlockFactory'
@@ -146,7 +146,7 @@ describe('components/blocksEditor/blockContent', () => {
test('should call onSave on hit enter in the input', async () => { test('should call onSave on hit enter in the input', async () => {
const onSave = jest.fn() const onSave = jest.fn()
const {user} = setup(wrapDNDIntl( const {user} = setup(wrapDNDIntl(
<ReduxProvider store={store}> <ReduxProvider store={store}>
<BlockContent <BlockContent

Просмотреть файл

@@ -61,6 +61,7 @@ function BlockContent(props: Props) {
const updatedBlock = await onSave(b) const updatedBlock = await onSave(b)
props.setEditing(null) props.setEditing(null)
props.setAfterBlock(updatedBlock) props.setAfterBlock(updatedBlock)
return updatedBlock return updatedBlock
}} }}
id={block.id} id={block.id}
@@ -73,6 +74,7 @@ function BlockContent(props: Props) {
const contentType = registry.get(block.contentType) const contentType = registry.get(block.contentType)
if (contentType && contentType.Display) { if (contentType && contentType.Display) {
const DisplayContent = contentType.Display const DisplayContent = contentType.Display
return ( return (
<div <div
ref={drop} ref={drop}
@@ -118,6 +120,7 @@ function BlockContent(props: Props) {
</div> </div>
) )
} }
return null return null
} }

Просмотреть файл

@@ -2,7 +2,7 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react' import React from 'react'
import {render, screen, fireEvent} from '@testing-library/react' import {fireEvent, render, screen} from '@testing-library/react'
import {mocked} from 'jest-mock' import {mocked} from 'jest-mock'
import octoClient from 'src/octoClient' import octoClient from 'src/octoClient'

Просмотреть файл

@@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {useRef, useEffect, useState} from 'react' import React, {useEffect, useRef, useState} from 'react'
import {BlockInputProps, ContentType} from 'src/components/blocksEditor/blocks/types' import {BlockInputProps, ContentType} from 'src/components/blocksEditor/blocks/types'
import octoClient from 'src/octoClient' import octoClient from 'src/octoClient'

Просмотреть файл

@@ -2,7 +2,7 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react' import React from 'react'
import {render, screen, fireEvent} from '@testing-library/react' import {fireEvent, render, screen} from '@testing-library/react'
import CheckboxBlock from '.' import CheckboxBlock from '.'

Просмотреть файл

@@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {useRef, useEffect} from 'react' import React, {useEffect, useRef} from 'react'
import {marked} from 'marked' import {marked} from 'marked'
import {BlockInputProps, ContentType} from 'src/components/blocksEditor/blocks/types' import {BlockInputProps, ContentType} from 'src/components/blocksEditor/blocks/types'
@@ -23,6 +23,7 @@ const Checkbox: ContentType<ValueType> = {
Display: (props: BlockInputProps<ValueType>) => { Display: (props: BlockInputProps<ValueType>) => {
const renderer = new marked.Renderer() const renderer = new marked.Renderer()
const html = marked(props.value.value || '', {renderer, breaks: true}) const html = marked(props.value.value || '', {renderer, breaks: true})
return ( return (
<div className='CheckboxView'> <div className='CheckboxView'>
<input <input
@@ -46,6 +47,7 @@ const Checkbox: ContentType<ValueType> = {
useEffect(() => { useEffect(() => {
ref.current?.focus() ref.current?.focus()
}, []) }, [])
return ( return (
<div className='Checkbox'> <div className='Checkbox'>
<input <input

Просмотреть файл

@@ -18,6 +18,7 @@ const Divider: ContentType = {
useEffect(() => { useEffect(() => {
props.onSave(props.value) props.onSave(props.value)
}, []) }, [])
return null return null
}, },
} }

Просмотреть файл

@@ -2,7 +2,7 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react' import React from 'react'
import {render, screen, fireEvent} from '@testing-library/react' import {fireEvent, render, screen} from '@testing-library/react'
import H1Block from '.' import H1Block from '.'

Просмотреть файл

@@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {useRef, useEffect} from 'react' import React, {useEffect, useRef} from 'react'
import {marked} from 'marked' import {marked} from 'marked'
import {BlockInputProps, ContentType} from 'src/components/blocksEditor/blocks/types' import {BlockInputProps, ContentType} from 'src/components/blocksEditor/blocks/types'
@@ -17,6 +17,7 @@ const H1: ContentType = {
Display: (props: BlockInputProps) => { Display: (props: BlockInputProps) => {
const renderer = new marked.Renderer() const renderer = new marked.Renderer()
const html = marked('# ' + props.value, {renderer, breaks: true}) const html = marked('# ' + props.value, {renderer, breaks: true})
return ( return (
<div <div
dangerouslySetInnerHTML={{__html: html.trim()}} dangerouslySetInnerHTML={{__html: html.trim()}}
@@ -28,6 +29,7 @@ const H1: ContentType = {
useEffect(() => { useEffect(() => {
ref.current?.focus() ref.current?.focus()
}, []) }, [])
return ( return (
<input <input
ref={ref} ref={ref}

Просмотреть файл

@@ -2,7 +2,7 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react' import React from 'react'
import {render, screen, fireEvent} from '@testing-library/react' import {fireEvent, render, screen} from '@testing-library/react'
import H2Block from '.' import H2Block from '.'

Просмотреть файл

@@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {useRef, useEffect} from 'react' import React, {useEffect, useRef} from 'react'
import {marked} from 'marked' import {marked} from 'marked'
import {BlockInputProps, ContentType} from 'src/components/blocksEditor/blocks/types' import {BlockInputProps, ContentType} from 'src/components/blocksEditor/blocks/types'
@@ -17,6 +17,7 @@ const H2: ContentType = {
Display: (props: BlockInputProps) => { Display: (props: BlockInputProps) => {
const renderer = new marked.Renderer() const renderer = new marked.Renderer()
const html = marked('## ' + props.value, {renderer, breaks: true}) const html = marked('## ' + props.value, {renderer, breaks: true})
return ( return (
<div <div
dangerouslySetInnerHTML={{__html: html.trim()}} dangerouslySetInnerHTML={{__html: html.trim()}}
@@ -28,6 +29,7 @@ const H2: ContentType = {
useEffect(() => { useEffect(() => {
ref.current?.focus() ref.current?.focus()
}, []) }, [])
return ( return (
<input <input
ref={ref} ref={ref}

Просмотреть файл

@@ -2,7 +2,7 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react' import React from 'react'
import {render, screen, fireEvent} from '@testing-library/react' import {fireEvent, render, screen} from '@testing-library/react'
import H3Block from '.' import H3Block from '.'

Просмотреть файл

@@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {useRef, useEffect} from 'react' import React, {useEffect, useRef} from 'react'
import {marked} from 'marked' import {marked} from 'marked'
import {BlockInputProps, ContentType} from 'src/components/blocksEditor/blocks/types' import {BlockInputProps, ContentType} from 'src/components/blocksEditor/blocks/types'
@@ -17,6 +17,7 @@ const H3: ContentType = {
Display: (props: BlockInputProps) => { Display: (props: BlockInputProps) => {
const renderer = new marked.Renderer() const renderer = new marked.Renderer()
const html = marked('### ' + props.value, {renderer, breaks: true}) const html = marked('### ' + props.value, {renderer, breaks: true})
return ( return (
<div <div
dangerouslySetInnerHTML={{__html: html.trim()}} dangerouslySetInnerHTML={{__html: html.trim()}}
@@ -28,6 +29,7 @@ const H3: ContentType = {
useEffect(() => { useEffect(() => {
ref.current?.focus() ref.current?.focus()
}, []) }, [])
return ( return (
<input <input
ref={ref} ref={ref}

Просмотреть файл

@@ -2,7 +2,7 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react' import React from 'react'
import {render, screen, fireEvent} from '@testing-library/react' import {fireEvent, render, screen} from '@testing-library/react'
import {mocked} from 'jest-mock' import {mocked} from 'jest-mock'
import octoClient from 'src/octoClient' import octoClient from 'src/octoClient'

Просмотреть файл

@@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {useRef, useEffect, useState} from 'react' import React, {useEffect, useRef, useState} from 'react'
import {BlockInputProps, ContentType} from 'src/components/blocksEditor/blocks/types' import {BlockInputProps, ContentType} from 'src/components/blocksEditor/blocks/types'
import octoClient from 'src/octoClient' import octoClient from 'src/octoClient'
@@ -44,6 +44,7 @@ const Image: ContentType<FileInfo> = {
/> />
) )
} }
return null return null
}, },
Input: (props: BlockInputProps<FileInfo>) => { Input: (props: BlockInputProps<FileInfo>) => {

Просмотреть файл

@@ -45,6 +45,7 @@ export function isSubPrefix(text: string): boolean {
return true return true
} }
} }
return false return false
} }
@@ -58,6 +59,7 @@ export function getBySlashCommandPrefix(slashCommandPrefix: string): ContentType
return ct return ct
} }
} }
return null return null
} }

Просмотреть файл

@@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {useRef, useEffect} from 'react' import React, {useEffect, useRef} from 'react'
import {BlockInputProps, ContentType} from 'src/components/blocksEditor/blocks/types' import {BlockInputProps, ContentType} from 'src/components/blocksEditor/blocks/types'
@@ -20,6 +20,7 @@ const ListItem: ContentType = {
useEffect(() => { useEffect(() => {
ref.current?.focus() ref.current?.focus()
}, []) }, [])
return ( return (
<ul> <ul>
<li> <li>

Просмотреть файл

@@ -2,7 +2,7 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react' import React from 'react'
import {render, screen, fireEvent} from '@testing-library/react' import {fireEvent, render, screen} from '@testing-library/react'
import ListItemBlock from '.' import ListItemBlock from '.'

Просмотреть файл

@@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {useRef, useEffect} from 'react' import React, {useEffect, useRef} from 'react'
import {marked} from 'marked' import {marked} from 'marked'
import {BlockInputProps, ContentType} from 'src/components/blocksEditor/blocks/types' import {BlockInputProps, ContentType} from 'src/components/blocksEditor/blocks/types'
@@ -15,6 +15,7 @@ const Quote: ContentType = {
Display: (props: BlockInputProps) => { Display: (props: BlockInputProps) => {
const renderer = new marked.Renderer() const renderer = new marked.Renderer()
const html = marked('> ' + props.value, {renderer, breaks: true}) const html = marked('> ' + props.value, {renderer, breaks: true})
return ( return (
<div <div
className='Quote' className='Quote'
@@ -30,6 +31,7 @@ const Quote: ContentType = {
useEffect(() => { useEffect(() => {
ref.current?.focus() ref.current?.focus()
}, []) }, [])
return ( return (
<blockquote <blockquote
className='Quote' className='Quote'

Просмотреть файл

@@ -2,7 +2,7 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react' import React from 'react'
import {render, screen, fireEvent} from '@testing-library/react' import {fireEvent, render, screen} from '@testing-library/react'
import QuoteBlock from '.' import QuoteBlock from '.'

Просмотреть файл

@@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {useRef, useEffect} from 'react' import React, {useEffect, useRef} from 'react'
import {BlockInputProps, ContentType} from 'src/components/blocksEditor/blocks/types' import {BlockInputProps, ContentType} from 'src/components/blocksEditor/blocks/types'
import {Utils} from 'src/utils' import {Utils} from 'src/utils'
@@ -16,6 +16,7 @@ const Text: ContentType = {
editable: true, editable: true,
Display: (props: BlockInputProps) => { Display: (props: BlockInputProps) => {
const html: string = Utils.htmlFromMarkdown(props.value || '') const html: string = Utils.htmlFromMarkdown(props.value || '')
return ( return (
<div <div
dangerouslySetInnerHTML={{__html: html}} dangerouslySetInnerHTML={{__html: html}}
@@ -28,6 +29,7 @@ const Text: ContentType = {
useEffect(() => { useEffect(() => {
ref.current?.focus() ref.current?.focus()
}, []) }, [])
return ( return (
<input <input
ref={ref} ref={ref}

Просмотреть файл

@@ -18,6 +18,7 @@ const TextContent: ContentType = {
editable: true, editable: true,
Display: (props: BlockInputProps) => { Display: (props: BlockInputProps) => {
const html: string = Utils.htmlFromMarkdown(props.value || '') const html: string = Utils.htmlFromMarkdown(props.value || '')
return ( return (
<div <div
dangerouslySetInnerHTML={{__html: html}} dangerouslySetInnerHTML={{__html: html}}

Просмотреть файл

@@ -3,9 +3,9 @@
import React from 'react' import React from 'react'
import {Provider as ReduxProvider} from 'react-redux' import {Provider as ReduxProvider} from 'react-redux'
import {render, act} from '@testing-library/react' import {act, render} from '@testing-library/react'
import {mockDOM, wrapDNDIntl, mockStateStore} from 'src/testUtils' import {mockDOM, mockStateStore, wrapDNDIntl} from 'src/testUtils'
import {TestBlockFactory} from 'src/test/testBlockFactory' import {TestBlockFactory} from 'src/test/testBlockFactory'
import TextBlock from '.' import TextBlock from '.'

Просмотреть файл

@@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {useRef, useEffect, useState} from 'react' import React, {useEffect, useRef, useState} from 'react'
import {BlockInputProps, ContentType} from 'src/components/blocksEditor/blocks/types' import {BlockInputProps, ContentType} from 'src/components/blocksEditor/blocks/types'
import octoClient from 'src/octoClient' import octoClient from 'src/octoClient'
@@ -49,6 +49,7 @@ const Video: ContentType<FileInfo> = {
</video> </video>
) )
} }
return null return null
}, },
Input: (props: BlockInputProps<FileInfo>) => { Input: (props: BlockInputProps<FileInfo>) => {

Просмотреть файл

@@ -2,7 +2,7 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react' import React from 'react'
import {render, screen, fireEvent} from '@testing-library/react' import {fireEvent, render, screen} from '@testing-library/react'
import {mocked} from 'jest-mock' import {mocked} from 'jest-mock'
import octoClient from 'src/octoClient' import octoClient from 'src/octoClient'

Просмотреть файл

@@ -4,21 +4,20 @@
import React from 'react' import React from 'react'
import {Provider as ReduxProvider} from 'react-redux' import {Provider as ReduxProvider} from 'react-redux'
import { import {
act,
fireEvent,
render, render,
screen, screen,
fireEvent,
act
} from '@testing-library/react' } from '@testing-library/react'
import { import {
mockDOM, mockDOM,
wrapDNDIntl,
mockStateStore, mockStateStore,
setup setup,
wrapDNDIntl,
} from 'src/testUtils' } from 'src/testUtils'
import {TestBlockFactory} from 'src/test/testBlockFactory' import {TestBlockFactory} from 'src/test/testBlockFactory'
import {BlockData} from './blocks/types' import {BlockData} from './blocks/types'
import BlocksEditor from './blocksEditor' import BlocksEditor from './blocksEditor'
@@ -120,7 +119,6 @@ describe('components/blocksEditor/blocksEditor', () => {
await user.keyboard('{Enter}') await user.keyboard('{Enter}')
}) })
expect(onBlockCreated).toBeCalledWith(expect.objectContaining({value: 'test'})) expect(onBlockCreated).toBeCalledWith(expect.objectContaining({value: 'test'}))
}) })

Просмотреть файл

@@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {useState, useMemo} from 'react' import React, {useMemo, useState} from 'react'
import {DndProvider} from 'react-dnd' import {DndProvider} from 'react-dnd'
import {HTML5Backend} from 'react-dnd-html5-backend' import {HTML5Backend} from 'react-dnd-html5-backend'
@@ -22,6 +22,7 @@ function BlocksEditor(props: Props) {
const [editing, setEditing] = useState<BlockData|null>(null) const [editing, setEditing] = useState<BlockData|null>(null)
const [afterBlock, setAfterBlock] = useState<BlockData|null>(null) const [afterBlock, setAfterBlock] = useState<BlockData|null>(null)
const contentOrder = useMemo(() => props.blocks.filter((b) => b.id).map((b) => b.id!), [props.blocks]) const contentOrder = useMemo(() => props.blocks.filter((b) => b.id).map((b) => b.id!), [props.blocks])
return ( return (
<div <div
className='BlocksEditor' className='BlocksEditor'
@@ -34,6 +35,7 @@ function BlocksEditor(props: Props) {
setEditing(afterBlock) setEditing(afterBlock)
} }
setAfterBlock(null) setAfterBlock(null)
return return
} }
let prevBlock = null let prevBlock = null
@@ -97,6 +99,7 @@ function BlocksEditor(props: Props) {
const newBlock = await props.onBlockModified(b) const newBlock = await props.onBlockModified(b)
setNextType(registry.get(b.contentType).nextType || '') setNextType(registry.get(b.contentType).nextType || '')
setAfterBlock(newBlock) setAfterBlock(newBlock)
return newBlock return newBlock
}} }}
onMove={props.onBlockMoved} onMove={props.onBlockMoved}
@@ -109,6 +112,7 @@ function BlocksEditor(props: Props) {
const newBlock = await props.onBlockCreated(b, afterBlock) const newBlock = await props.onBlockCreated(b, afterBlock)
setNextType(registry.get(b.contentType).nextType || '') setNextType(registry.get(b.contentType).nextType || '')
setAfterBlock(newBlock) setAfterBlock(newBlock)
return newBlock return newBlock
}} }}
/>)} />)}

Просмотреть файл

@@ -3,13 +3,13 @@
import React from 'react' import React from 'react'
import {Provider as ReduxProvider} from 'react-redux' import {Provider as ReduxProvider} from 'react-redux'
import {render, screen, act} from '@testing-library/react' import {act, render, screen} from '@testing-library/react'
import { import {
mockDOM, mockDOM,
wrapDNDIntl,
mockStateStore, mockStateStore,
setup setup,
wrapDNDIntl,
} from 'src/testUtils' } from 'src/testUtils'
import {TestBlockFactory} from 'src/test/testBlockFactory' import {TestBlockFactory} from 'src/test/testBlockFactory'

Просмотреть файл

@@ -1,9 +1,9 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {useState, useEffect} from 'react' import React, {useEffect, useState} from 'react'
import * as contentBlocks from './blocks/' import * as contentBlocks from './blocks/'
import {ContentType, BlockData} from './blocks/types' import {BlockData, ContentType} from './blocks/types'
import RootInput from './rootInput' import RootInput from './rootInput'
import './editor.scss' import './editor.scss'

Просмотреть файл

@@ -2,7 +2,7 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react' import React from 'react'
import {render, screen, fireEvent} from '@testing-library/react' import {fireEvent, render, screen} from '@testing-library/react'
import RootInput from './rootInput' import RootInput from './rootInput'

Просмотреть файл

@@ -4,19 +4,18 @@
import React from 'react' import React from 'react'
import {Provider as ReduxProvider} from 'react-redux' import {Provider as ReduxProvider} from 'react-redux'
import { import {
act,
fireEvent,
render, render,
screen, screen,
act,
fireEvent
} from '@testing-library/react' } from '@testing-library/react'
import {mocked} from 'jest-mock' import {mocked} from 'jest-mock'
import userEvent from '@testing-library/user-event' import userEvent from '@testing-library/user-event'
import octoClient from 'src/octoClient' import octoClient from 'src/octoClient'
import {mockStateStore} from 'src/testUtils' import {mockStateStore, wrapIntl} from 'src/testUtils'
import {createBoard} from 'src/blocks/board' import {createBoard} from 'src/blocks/board'
import {wrapIntl} from 'src/testUtils'
import BoardSelector from './boardSelector' import BoardSelector from './boardSelector'
@@ -97,7 +96,7 @@ describe('components/boardSelector', () => {
expect(container).toMatchSnapshot() expect(container).toMatchSnapshot()
}) })
it("escape button should unmount the component", () => { it('escape button should unmount the component', () => {
mockedOctoClient.searchLinkableBoards.mockResolvedValueOnce([]) mockedOctoClient.searchLinkableBoards.mockResolvedValueOnce([])
const store = mockStateStore([], state) const store = mockStateStore([], state)
@@ -114,10 +113,10 @@ describe('components/boardSelector', () => {
expect(store.dispatch).toHaveBeenCalledTimes(0) expect(store.dispatch).toHaveBeenCalledTimes(0)
fireEvent.keyDown(getByText(/Link boards/i), { fireEvent.keyDown(getByText(/Link boards/i), {
key: "Escape", key: 'Escape',
code: "Escape", code: 'Escape',
keyCode: 27, keyCode: 27,
charCode: 27 charCode: 27,
}) })
expect(store.dispatch).toHaveBeenCalledTimes(2) expect(store.dispatch).toHaveBeenCalledTimes(2)

Просмотреть файл

@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {useState, useMemo, useCallback} from 'react' import React, {useCallback, useMemo, useState} from 'react'
import {IntlProvider, useIntl, FormattedMessage} from 'react-intl' import {FormattedMessage, IntlProvider, useIntl} from 'react-intl'
import debounce from 'lodash/debounce' import debounce from 'lodash/debounce'
import {SuiteWindow} from 'src/types/index' import {SuiteWindow} from 'src/types/index'
@@ -13,10 +13,10 @@ import {useWebsockets} from 'src/hooks/websockets'
import octoClient from 'src/octoClient' import octoClient from 'src/octoClient'
import mutator from 'src/mutator' import mutator from 'src/mutator'
import {getCurrentTeamId, getAllTeams, Team} from 'src/store/teams' import {Team, getAllTeams, getCurrentTeamId} from 'src/store/teams'
import {createBoard, Board} from 'src/blocks/board' import {Board, createBoard} from 'src/blocks/board'
import {useAppSelector, useAppDispatch} from 'src/store/hooks' import {useAppDispatch, useAppSelector} from 'src/store/hooks'
import {EmptySearch, EmptyResults} from 'src/components/searchDialog/searchDialog' import {EmptyResults, EmptySearch} from 'src/components/searchDialog/searchDialog'
import ConfirmationDialog from 'src/components/confirmationDialogBox' import ConfirmationDialog from 'src/components/confirmationDialogBox'
import Dialog from 'src/components/dialog' import Dialog from 'src/components/dialog'
import SearchIcon from 'src/widgets/icons/search' import SearchIcon from 'src/widgets/icons/search'
@@ -67,7 +67,7 @@ const BoardSelector = () => {
let updated = false let updated = false
results.forEach((board, idx) => { results.forEach((board, idx) => {
for (const newBoard of boards) { for (const newBoard of boards) {
if (newBoard.id == board.id) { if (newBoard.id === board.id) {
newResults[idx] = newBoard newResults[idx] = newBoard
updated = true updated = true
} }
@@ -95,6 +95,7 @@ const BoardSelector = () => {
const linkBoard = async (board: Board, confirmed?: boolean): Promise<void> => { const linkBoard = async (board: Board, confirmed?: boolean): Promise<void> => {
if (!confirmed) { if (!confirmed) {
setShowLinkBoardConfirmation(board) setShowLinkBoardConfirmation(board)
return return
} }
const newBoard = createBoard({...board, channelId: currentChannel}) const newBoard = createBoard({...board, channelId: currentChannel})
@@ -117,7 +118,7 @@ const BoardSelector = () => {
} }
let confirmationSubText let confirmationSubText
if (showLinkBoardConfirmation?.channelId !== '') { if (showLinkBoardConfirmation?.channelId) {
confirmationSubText = intl.formatMessage({ confirmationSubText = intl.formatMessage({
id: 'boardSelector.confirm-link-board-subtext-with-other-channel', id: 'boardSelector.confirm-link-board-subtext-with-other-channel',
defaultMessage: 'When you link "{boardName}" to the channel, all members of the channel (existing and new) will be able to edit it. This excludes members who are guests.{lineBreak} This board is currently linked to another channel. It will be unlinked if you choose to link it here.', defaultMessage: 'When you link "{boardName}" to the channel, all members of the channel (existing and new) will be able to edit it. This excludes members who are guests.{lineBreak} This board is currently linked to another channel. It will be unlinked if you choose to link it here.',
@@ -138,7 +139,7 @@ const BoardSelector = () => {
} }
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => { const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key == 'Escape') { if (event.key === 'Escape') {
closeDialog() closeDialog()
} }
} }
@@ -197,13 +198,15 @@ const BoardSelector = () => {
<div className='searchResults'> <div className='searchResults'>
{/*When there are results to show*/} {/*When there are results to show*/}
{searchQuery && results.length > 0 && {searchQuery && results.length > 0 &&
results.map((result) => (<BoardSelectorItem results.map((result) => (
key={result.id} <BoardSelectorItem
item={result} key={result.id}
linkBoard={linkBoard} item={result}
unlinkBoard={unlinkBoard} linkBoard={linkBoard}
currentChannel={currentChannel} unlinkBoard={unlinkBoard}
/>))} currentChannel={currentChannel}
/>
))}
{/*when user searched for something and there were no results*/} {/*when user searched for something and there were no results*/}
{emptyResult && <EmptyResults query={searchQuery}/>} {emptyResult && <EmptyResults query={searchQuery}/>}

Просмотреть файл

@@ -14,7 +14,7 @@ import BoardSelectorItem from './boardSelectorItem'
describe('components/boardSelectorItem', () => { describe('components/boardSelectorItem', () => {
it('renders board without title', async () => { it('renders board without title', async () => {
const board = createBoard() const board = createBoard()
board.title = "" board.title = ''
const {container} = render(wrapIntl( const {container} = render(wrapIntl(
<BoardSelectorItem <BoardSelectorItem
@@ -29,7 +29,7 @@ describe('components/boardSelectorItem', () => {
it('renders linked board', async () => { it('renders linked board', async () => {
const board = createBoard() const board = createBoard()
board.title = "Test title" board.title = 'Test title'
const {container} = render(wrapIntl( const {container} = render(wrapIntl(
<BoardSelectorItem <BoardSelectorItem
@@ -44,7 +44,7 @@ describe('components/boardSelectorItem', () => {
it('renders not linked board', async () => { it('renders not linked board', async () => {
const board = createBoard() const board = createBoard()
board.title = "Test title" board.title = 'Test title'
const {container} = render(wrapIntl( const {container} = render(wrapIntl(
<BoardSelectorItem <BoardSelectorItem

Просмотреть файл

@@ -2,7 +2,7 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react' import React from 'react'
import {useIntl, FormattedMessage} from 'react-intl' import {FormattedMessage, useIntl} from 'react-intl'
import {Board} from 'src/blocks/board' import {Board} from 'src/blocks/board'
import Button from 'src/widgets/buttons/button' import Button from 'src/widgets/buttons/button'
@@ -22,6 +22,7 @@ const BoardSelectorItem = (props: Props) => {
const intl = useIntl() const intl = useIntl()
const untitledBoardTitle = intl.formatMessage({id: 'ViewTitle.untitled-board', defaultMessage: 'Untitled board'}) const untitledBoardTitle = intl.formatMessage({id: 'ViewTitle.untitled-board', defaultMessage: 'Untitled board'})
const resultTitle = item.title || untitledBoardTitle const resultTitle = item.title || untitledBoardTitle
return ( return (
<div className='BoardSelectorItem'> <div className='BoardSelectorItem'>
<div className='BoardSelectorItem-info'> <div className='BoardSelectorItem-info'>

Просмотреть файл

@@ -5,7 +5,7 @@ import {
render, render,
screen, screen,
waitFor, waitFor,
within within,
} from '@testing-library/react' } from '@testing-library/react'
import userEvent from '@testing-library/user-event' import userEvent from '@testing-library/user-event'
import React from 'react' import React from 'react'
@@ -20,7 +20,7 @@ import {MemoryRouter, Router} from 'react-router-dom'
import Mutator from 'src/mutator' import Mutator from 'src/mutator'
import {Team} from 'src/store/teams' import {Team} from 'src/store/teams'
import {createBoard, Board} from 'src/blocks/board' import {Board, createBoard} from 'src/blocks/board'
import {IUser} from 'src/user' import {IUser} from 'src/user'
import {mockDOM, mockStateStore, wrapDNDIntl} from 'src/testUtils' import {mockDOM, mockStateStore, wrapDNDIntl} from 'src/testUtils'
@@ -288,7 +288,7 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
const useTemplateButton = screen.getByText('Use this template').parentElement const useTemplateButton = screen.getByText('Use this template').parentElement
expect(useTemplateButton).not.toBeNull() expect(useTemplateButton).not.toBeNull()
await userEvent.click(useTemplateButton!) await userEvent.click(useTemplateButton!)
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1)) await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1))
@@ -313,11 +313,10 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
expect(divBoardToSelect).not.toBeNull() expect(divBoardToSelect).not.toBeNull()
await userEvent.click(divBoardToSelect!) await userEvent.click(divBoardToSelect!)
const useTemplateButton = screen.getByText('Use this template').parentElement const useTemplateButton = screen.getByText('Use this template').parentElement
expect(useTemplateButton).not.toBeNull() expect(useTemplateButton).not.toBeNull()
await userEvent.click(useTemplateButton!) await userEvent.click(useTemplateButton!)
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1)) await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1))
@@ -339,12 +338,12 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
expect(divBoardToSelect).not.toBeNull() expect(divBoardToSelect).not.toBeNull()
await userEvent.click(divBoardToSelect!) await userEvent.click(divBoardToSelect!)
const useTemplateButton = screen.getByText('Use this template').parentElement const useTemplateButton = screen.getByText('Use this template').parentElement
expect(useTemplateButton).not.toBeNull() expect(useTemplateButton).not.toBeNull()
await userEvent.click(useTemplateButton!) await userEvent.click(useTemplateButton!)
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1)) await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1))
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledWith(team1.id, expect.anything(), expect.anything(), expect.anything(), 'global-1', team1.id)) await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledWith(team1.id, expect.anything(), expect.anything(), expect.anything(), 'global-1', team1.id))
await waitFor(() => expect(mockedTelemetry.trackEvent).toBeCalledWith('boards', 'createBoardViaTemplate', {boardTemplateId: 'template_id_global'})) await waitFor(() => expect(mockedTelemetry.trackEvent).toBeCalledWith('boards', 'createBoardViaTemplate', {boardTemplateId: 'template_id_global'}))
@@ -367,9 +366,9 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
const useTemplateButton = screen.getByText('Use this template').parentElement const useTemplateButton = screen.getByText('Use this template').parentElement
expect(useTemplateButton).not.toBeNull() expect(useTemplateButton).not.toBeNull()
await userEvent.click(useTemplateButton!) await userEvent.click(useTemplateButton!)
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1)) await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1))
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledWith(team1.id, expect.anything(), expect.anything(), expect.anything(), '2', team1.id)) await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledWith(team1.id, expect.anything(), expect.anything(), expect.anything(), '2', team1.id))
await waitFor(() => expect(mockedTelemetry.trackEvent).toBeCalledWith('boards', 'createBoardViaTemplate', {boardTemplateId: 'template_id_2'})) await waitFor(() => expect(mockedTelemetry.trackEvent).toBeCalledWith('boards', 'createBoardViaTemplate', {boardTemplateId: 'template_id_2'}))

Просмотреть файл

@@ -1,10 +1,10 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, { import React, {
useEffect,
useState,
useCallback, useCallback,
useMemo useEffect,
useMemo,
useState,
} from 'react' } from 'react'
import {FormattedMessage, useIntl} from 'react-intl' import {FormattedMessage, useIntl} from 'react-intl'
import {useHistory, useRouteMatch} from 'react-router-dom' import {useHistory, useRouteMatch} from 'react-router-dom'
@@ -18,8 +18,8 @@ import CloseIcon from 'src/widgets/icons/close'
import Button from 'src/widgets/buttons/button' import Button from 'src/widgets/buttons/button'
import octoClient from 'src/octoClient' import octoClient from 'src/octoClient'
import mutator from 'src/mutator' import mutator from 'src/mutator'
import {getTemplates, getCurrentBoardId} from 'src/store/boards' import {getCurrentBoardId, getTemplates} from 'src/store/boards'
import {getCurrentTeam, Team} from 'src/store/teams' import {Team, getCurrentTeam} from 'src/store/teams'
import {fetchGlobalTemplates, getGlobalTemplates} from 'src/store/globalTemplates' import {fetchGlobalTemplates, getGlobalTemplates} from 'src/store/globalTemplates'
import {useAppDispatch, useAppSelector} from 'src/store/hooks' import {useAppDispatch, useAppSelector} from 'src/store/hooks'
import TelemetryClient, {TelemetryActions, TelemetryCategory} from 'src/telemetry/telemetryClient' import TelemetryClient, {TelemetryActions, TelemetryCategory} from 'src/telemetry/telemetryClient'

Просмотреть файл

@@ -1,17 +1,17 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import { import {
render,
within,
act, act,
waitFor render,
waitFor,
within,
} from '@testing-library/react' } from '@testing-library/react'
import userEvent from '@testing-library/user-event' import userEvent from '@testing-library/user-event'
import React from 'react' import React from 'react'
import {MockStoreEnhanced} from 'redux-mock-store' import {MockStoreEnhanced} from 'redux-mock-store'
import {Provider as ReduxProvider} from 'react-redux' import {Provider as ReduxProvider} from 'react-redux'
import {Board, MemberRole, IPropertyTemplate} from 'src/blocks/board' import {Board, IPropertyTemplate, MemberRole} from 'src/blocks/board'
import {mockStateStore, wrapDNDIntl} from 'src/testUtils' import {mockStateStore, wrapDNDIntl} from 'src/testUtils'
import {IUser} from 'src/user' import {IUser} from 'src/user'

Просмотреть файл

@@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {useEffect, useState, useMemo} from 'react' import React, {useEffect, useMemo, useState} from 'react'
import {Board} from 'src/blocks/board' import {Board} from 'src/blocks/board'
import {Card} from 'src/blocks/card' import {Card} from 'src/blocks/card'
@@ -45,6 +45,7 @@ const BoardTemplateSelectorPreview = (props: Props) => {
} }
}) })
} }
return () => { return () => {
isSubscribed = false isSubscribed = false
} }
@@ -62,6 +63,7 @@ const BoardTemplateSelectorPreview = (props: Props) => {
if (!activeView) { if (!activeView) {
return {visible: [], hidden: []} return {visible: [], hidden: []}
} }
return getVisibleAndHiddenGroups(activeTemplateCards, activeView.fields.visibleOptionIds, activeView?.fields.hiddenOptionIds, groupByProperty) return getVisibleAndHiddenGroups(activeTemplateCards, activeView.fields.visibleOptionIds, activeView?.fields.hiddenOptionIds, groupByProperty)
}, [activeTemplateCards, activeView, groupByProperty]) }, [activeTemplateCards, activeView, groupByProperty])

Просмотреть файл

@@ -19,7 +19,7 @@ import AddIcon from 'src/widgets/icons/add'
import BoardSwitcherDialog from 'src/components/boardsSwitcherDialog/boardSwitcherDialog' import BoardSwitcherDialog from 'src/components/boardsSwitcherDialog/boardSwitcherDialog'
import {Utils} from 'src/utils' import {Utils} from 'src/utils'
import {Constants} from 'src/constants' import {Constants} from 'src/constants'
import {TOUR_SIDEBAR, SidebarTourSteps} from 'src/components/onboardingTour' import {SidebarTourSteps, TOUR_SIDEBAR} from 'src/components/onboardingTour'
import IconButton from 'src/widgets/buttons/iconButton' import IconButton from 'src/widgets/buttons/iconButton'
import SearchForBoardsTourStep from 'src/components/onboardingTour/searchForBoards/searchForBoards' import SearchForBoardsTourStep from 'src/components/onboardingTour/searchForBoards/searchForBoards'

Просмотреть файл

@@ -9,7 +9,7 @@ import {Provider as ReduxProvider} from 'react-redux'
import {render} from '@testing-library/react' import {render} from '@testing-library/react'
import {createMemoryHistory, History} from 'history' import {History, createMemoryHistory} from 'history'
import {Router} from 'react-router-dom' import {Router} from 'react-router-dom'

Просмотреть файл

@@ -1,12 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, { import React, {
MutableRefObject,
ReactNode, ReactNode,
useRef,
createRef, createRef,
useState,
useEffect, useEffect,
MutableRefObject useRef,
useState,
} from 'react' } from 'react'
import './boardSwitcherDialog.scss' import './boardSwitcherDialog.scss'
@@ -19,7 +19,7 @@ import SearchDialog from 'src/components/searchDialog/searchDialog'
import Globe from 'src/widgets/icons/globe' import Globe from 'src/widgets/icons/globe'
import LockOutline from 'src/widgets/icons/lockOutline' import LockOutline from 'src/widgets/icons/lockOutline'
import {useAppSelector} from 'src/store/hooks' import {useAppSelector} from 'src/store/hooks'
import {getAllTeams, getCurrentTeam, Team} from 'src/store/teams' import {Team, getAllTeams, getCurrentTeam} from 'src/store/teams'
import {getMe} from 'src/store/users' import {getMe} from 'src/store/users'
import {Utils} from 'src/utils' import {Utils} from 'src/utils'
import {BoardTypeOpen, BoardTypePrivate} from 'src/blocks/board' import {BoardTypeOpen, BoardTypePrivate} from 'src/blocks/board'
@@ -75,6 +75,7 @@ const BoardSwitcherDialog = (props: Props): JSX.Element => {
const untitledBoardTitle = intl.formatMessage({id: 'ViewTitle.untitled-board', defaultMessage: 'Untitled board'}) const untitledBoardTitle = intl.formatMessage({id: 'ViewTitle.untitled-board', defaultMessage: 'Untitled board'})
refs.current = items.map((_, i) => refs.current[i] ?? createRef()) refs.current = items.map((_, i) => refs.current[i] ?? createRef())
setRefs(refs) setRefs(refs)
return items.map((item, i) => { return items.map((item, i) => {
const resultTitle = item.title || untitledBoardTitle const resultTitle = item.title || untitledBoardTitle
const teamTitle = teamsById[item.teamId].title const teamTitle = teamsById[item.teamId].title
@@ -83,6 +84,7 @@ const BoardSwitcherDialog = (props: Props): JSX.Element => {
...prevIDs, ...prevIDs,
[i]: [item.teamId, item.id], [i]: [item.teamId, item.id],
})) }))
return ( return (
<div <div
key={item.id} key={item.id}

Просмотреть файл

@@ -130,6 +130,7 @@ describe('components/boardsUnfurl/BoardsUnfurl', () => {
}) })
const board = {...createBoard(), title: 'test board'} const board = {...createBoard(), title: 'test board'}
// mockedOctoClient.getBoard.mockResolvedValueOnce(board) // mockedOctoClient.getBoard.mockResolvedValueOnce(board)
const component = ( const component = (

Просмотреть файл

@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {useState, useEffect} from 'react' import React, {useEffect, useState} from 'react'
import {IntlProvider, FormattedMessage, useIntl} from 'react-intl' import {FormattedMessage, IntlProvider, useIntl} from 'react-intl'
import WithWebSockets from 'src/components/withWebSockets' import WithWebSockets from 'src/components/withWebSockets'
import {useWebsockets} from 'src/hooks/websockets' import {useWebsockets} from 'src/hooks/websockets'
@@ -10,7 +10,7 @@ import {getLanguage} from 'src/store/language'
import {useAppSelector} from 'src/store/hooks' import {useAppSelector} from 'src/store/hooks'
import {getCurrentTeamId} from 'src/store/teams' import {getCurrentTeamId} from 'src/store/teams'
import {WSClient, MMWebSocketClient} from 'src/wsclient' import {MMWebSocketClient, WSClient} from 'src/wsclient'
import manifest from 'src/manifest' import manifest from 'src/manifest'
import {getMessages} from 'src/i18n' import {getMessages} from 'src/i18n'
@@ -86,6 +86,7 @@ export const BoardsUnfurl = (props: Props): JSX.Element => {
const [firstCard] = cards as Card[] const [firstCard] = cards as Card[]
if (!firstCard || !fetchedBoard || firstCard.type !== 'card') { if (!firstCard || !fetchedBoard || firstCard.type !== 'card') {
setLoading(false) setLoading(false)
return null return null
} }
setCard(firstCard) setCard(firstCard)
@@ -102,12 +103,14 @@ export const BoardsUnfurl = (props: Props): JSX.Element => {
const [firstContentBlock] = contentBlock const [firstContentBlock] = contentBlock
if (!firstContentBlock) { if (!firstContentBlock) {
setLoading(false) setLoading(false)
return null return null
} }
setContent(firstContentBlock) setContent(firstContentBlock)
} }
setLoading(false) setLoading(false)
return null return null
} }
fetchData() fetchData()

Просмотреть файл

@@ -44,8 +44,8 @@ function fixTimestampToMinutesAccuracy(timestamp: number) {
} }
function cardsWithValue(cards: readonly Card[], property: IPropertyTemplate): Card[] { function cardsWithValue(cards: readonly Card[], property: IPropertyTemplate): Card[] {
return cards. return cards
filter((card) => Boolean(getCardProperty(card, property))) .filter((card) => Boolean(getCardProperty(card, property)))
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -66,6 +66,7 @@ function percentEmpty(cards: readonly Card[], property: IPropertyTemplate): stri
if (cards.length === 0) { if (cards.length === 0) {
return '' return ''
} }
return String((((cards.length - cardsWithValue(cards, property).length) / cards.length) * 100).toFixed(0)) + '%' return String((((cards.length - cardsWithValue(cards, property).length) / cards.length) * 100).toFixed(0)) + '%'
} }
@@ -73,6 +74,7 @@ function percentNotEmpty(cards: readonly Card[], property: IPropertyTemplate): s
if (cards.length === 0) { if (cards.length === 0) {
return '' return ''
} }
return String(((cardsWithValue(cards, property).length / cards.length) * 100).toFixed(0)) + '%' return String(((cardsWithValue(cards, property).length / cards.length) * 100).toFixed(0)) + '%'
} }
@@ -80,8 +82,8 @@ function countValueHelper(cards: readonly Card[], property: IPropertyTemplate):
let values = 0 let values = 0
if (property.type === 'multiSelect') { if (property.type === 'multiSelect') {
cardsWithValue(cards, property). cardsWithValue(cards, property)
forEach((card) => { .forEach((card) => {
values += (getCardProperty(card, property) as string[]).length values += (getCardProperty(card, property) as string[]).length
}) })
} else { } else {
@@ -140,8 +142,8 @@ function countUniqueValue(cards: readonly Card[], property: IPropertyTemplate):
function sum(cards: readonly Card[], property: IPropertyTemplate): string { function sum(cards: readonly Card[], property: IPropertyTemplate): string {
let result = 0 let result = 0
cardsWithValue(cards, property). cardsWithValue(cards, property)
forEach((card) => { .forEach((card) => {
result += parseFloat(getCardProperty(card, property) as string) result += parseFloat(getCardProperty(card, property) as string)
}) })
@@ -156,12 +158,13 @@ function average(cards: readonly Card[], property: IPropertyTemplate): string {
const result = parseFloat(sum(cards, property)) const result = parseFloat(sum(cards, property))
const avg = result / numCards const avg = result / numCards
return String(Utils.roundTo(avg, ROUNDED_DECIMAL_PLACES)) return String(Utils.roundTo(avg, ROUNDED_DECIMAL_PLACES))
} }
function median(cards: readonly Card[], property: IPropertyTemplate): string { function median(cards: readonly Card[], property: IPropertyTemplate): string {
const sorted = cardsWithValue(cards, property). const sorted = cardsWithValue(cards, property)
sort((a, b) => { .sort((a, b) => {
if (!getCardProperty(a, property)) { if (!getCardProperty(a, property)) {
return 1 return 1
} }
@@ -231,6 +234,7 @@ function earliest(cards: readonly Card[], property: IPropertyTemplate, intl: Int
return '' return ''
} }
const date = new Date(result) const date = new Date(result)
return property.type === 'date' ? Utils.displayDate(date, intl) : Utils.displayDateTime(date, intl) return property.type === 'date' ? Utils.displayDate(date, intl) : Utils.displayDateTime(date, intl)
} }
@@ -242,6 +246,7 @@ function earliestEpoch(cards: readonly Card[], property: IPropertyTemplate): num
result = Math.min(result, timestamp) result = Math.min(result, timestamp)
} }
}) })
return result return result
} }
@@ -251,6 +256,7 @@ function latest(cards: readonly Card[], property: IPropertyTemplate, intl: IntlS
return '' return ''
} }
const date = new Date(result) const date = new Date(result)
return property.type === 'date' ? Utils.displayDate(date, intl) : Utils.displayDateTime(date, intl) return property.type === 'date' ? Utils.displayDate(date, intl) : Utils.displayDateTime(date, intl)
} }
@@ -262,6 +268,7 @@ function latestEpoch(cards: readonly Card[], property: IPropertyTemplate): numbe
result = Math.max(result, timestamp) result = Math.max(result, timestamp)
} }
}) })
return result return result
} }
@@ -276,10 +283,12 @@ function getTimestampsFromPropertyValue(value: number | string | string[]): numb
} catch { } catch {
return [] return []
} }
return [property.from, property.to].flatMap((e) => { return [property.from, property.to].flatMap((e) => {
return e ? [e] : [] return e ? [e] : []
}) })
} }
return [] return []
} }
@@ -292,6 +301,7 @@ function dateRange(cards: readonly Card[], property: IPropertyTemplate, intl: In
if (resultLatest === Number.NEGATIVE_INFINITY) { if (resultLatest === Number.NEGATIVE_INFINITY) {
return '' return ''
} }
return moment.duration(resultLatest - resultEarliest, 'milliseconds').locale(intl.locale.toLowerCase()).humanize() return moment.duration(resultLatest - resultEarliest, 'milliseconds').locale(intl.locale.toLowerCase()).humanize()
} }

Просмотреть файл

@@ -2,10 +2,9 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react' import React from 'react'
import Select, {components, DropdownIndicatorProps, StylesConfig} from 'react-select' import Select, {DropdownIndicatorProps, StylesConfig, components} from 'react-select'
import {IntlShape, useIntl} from 'react-intl'
import {useIntl, IntlShape} from 'react-intl'
import {getSelectBaseStyle} from 'src/theme' import {getSelectBaseStyle} from 'src/theme'
import ChevronUp from 'src/widgets/icons/chevronUp' import ChevronUp from 'src/widgets/icons/chevronUp'

Просмотреть файл

@@ -5,7 +5,7 @@ import {render} from '@testing-library/react'
import {Provider as ReduxProvider} from 'react-redux' import {Provider as ReduxProvider} from 'react-redux'
import {TestBlockFactory} from 'src/test/testBlockFactory' import {TestBlockFactory} from 'src/test/testBlockFactory'
import {wrapIntl, mockStateStore} from 'src/testUtils' import {mockStateStore, wrapIntl} from 'src/testUtils'
import {IPropertyTemplate} from 'src/blocks/board' import {IPropertyTemplate} from 'src/blocks/board'
import CalendarView from './fullCalendar' import CalendarView from './fullCalendar'

Просмотреть файл

@@ -5,10 +5,10 @@ import React, {useCallback, useMemo, useState} from 'react'
import {useIntl} from 'react-intl' import {useIntl} from 'react-intl'
import FullCalendar, { import FullCalendar, {
DayCellContentArg,
EventChangeArg, EventChangeArg,
EventInput,
EventContentArg, EventContentArg,
DayCellContentArg EventInput,
} from '@fullcalendar/react' } from '@fullcalendar/react'
import interactionPlugin from '@fullcalendar/interaction' import interactionPlugin from '@fullcalendar/interaction'
@@ -60,6 +60,7 @@ function createDatePropertyFromCalendarDates(start: Date, end: Date): DateProper
if (dateTo !== dateFrom) { if (dateTo !== dateFrom) {
dateProperty.to = dateTo dateProperty.to = dateTo
} }
return dateProperty return dateProperty
} }
@@ -69,6 +70,7 @@ function createDatePropertyFromCalendarDate(start: Date): DateProperty {
const dateFrom = start.getTime() - timeZoneOffset(start.getTime()) const dateFrom = start.getTime() - timeZoneOffset(start.getTime())
const dateProperty: DateProperty = {from: dateFrom} const dateProperty: DateProperty = {from: dateFrom}
return dateProperty return dateProperty
} }
@@ -97,6 +99,7 @@ const CalendarFullView = (props: Props): JSX.Element|null => {
if (readonly || !dateDisplayProperty || propsRegistry.get(dateDisplayProperty.type).isReadOnly) { if (readonly || !dateDisplayProperty || propsRegistry.get(dateDisplayProperty.type).isReadOnly) {
return false return false
} }
return true return true
}, [readonly, dateDisplayProperty]) }, [readonly, dateDisplayProperty])
@@ -118,6 +121,7 @@ const CalendarFullView = (props: Props): JSX.Element|null => {
//full calendar end date is exclusive, so increment by 1 day. //full calendar end date is exclusive, so increment by 1 day.
dateTo.setDate(dateTo.getDate() + 1) dateTo.setDate(dateTo.getDate() + 1)
} }
return [{ return [{
id: card.id, id: card.id,
title: card.title, title: card.title,

Просмотреть файл

@@ -64,6 +64,7 @@ const calculateBadges = (contents: ContentsType, comments: CommentBlock[]): Badg
updateCounters(content) updateCounters(content)
} }
} }
return { return {
description: text > 0, description: text > 0,
comments: comments.length, comments: comments.length,
@@ -84,6 +85,7 @@ const CardBadges = (props: Props) => {
} }
const intl = useIntl() const intl = useIntl()
const {checkboxes} = badges const {checkboxes} = badges
return ( return (
<div className={`CardBadges ${className || ''}`}> <div className={`CardBadges ${className || ''}`}>
{badges.description && {badges.description &&

Просмотреть файл

@@ -1,14 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, { import React, {
Fragment,
useCallback, useCallback,
useEffect, useEffect,
useMemo,
useRef, useRef,
useState, useState,
Fragment,
useMemo
} from 'react' } from 'react'
import {FormattedMessage, useIntl, IntlShape} from 'react-intl' import {FormattedMessage, IntlShape, useIntl} from 'react-intl'
import {BlockIcons} from 'src/blockIcons' import {BlockIcons} from 'src/blockIcons'
import {Card} from 'src/blocks/card' import {Card} from 'src/blocks/card'
@@ -30,7 +30,7 @@ import TelemetryClient, {TelemetryActions, TelemetryCategory} from 'src/telemetr
import BlockIconSelector from 'src/components/blockIconSelector' import BlockIconSelector from 'src/components/blockIconSelector'
import {useAppDispatch, useAppSelector} from 'src/store/hooks' import {useAppDispatch, useAppSelector} from 'src/store/hooks'
import {updateCards, setCurrent as setCurrentCard} from 'src/store/cards' import {setCurrent as setCurrentCard, updateCards} from 'src/store/cards'
import {updateContents} from 'src/store/contents' import {updateContents} from 'src/store/contents'
import {Permission} from 'src/constants' import {Permission} from 'src/constants'
import {useHasCurrentBoardPermissions} from 'src/hooks/permissions' import {useHasCurrentBoardPermissions} from 'src/hooks/permissions'
@@ -102,6 +102,7 @@ async function addBlockNewEditor(card: Card, intl: IntlShape, title: string, fie
const newBlock = await mutator.insertBlock(block.boardId, block, description, afterRedo, beforeUndo) const newBlock = await mutator.insertBlock(block.boardId, block, description, afterRedo, beforeUndo)
dispatch(updateContents([newBlock])) dispatch(updateContents([newBlock]))
return newBlock return newBlock
} }
@@ -144,7 +145,7 @@ const CardDetail = (props: Props): JSX.Element|null => {
useEffect(() => { useEffect(() => {
return () => { return () => {
saveTitleRef.current && saveTitleRef.current() saveTitleRef.current?.()
} }
}, []) }, [])
@@ -339,6 +340,7 @@ const CardDetail = (props: Props): JSX.Element|null => {
} else { } else {
newBlock = await addBlockNewEditor(card, intl, block.value, {}, block.contentType, afterBlock?.id, dispatch) newBlock = await addBlockNewEditor(card, intl, block.value, {}, block.contentType, afterBlock?.id, dispatch)
} }
return {...block, id: newBlock.id} return {...block, id: newBlock.id}
}} }}
onBlockModified={async (block: any): Promise<BlockData<any>|null> => { onBlockModified={async (block: any): Promise<BlockData<any>|null> => {
@@ -351,6 +353,7 @@ const CardDetail = (props: Props): JSX.Element|null => {
const description = intl.formatMessage({id: 'ContentBlock.DeleteAction', defaultMessage: 'delete'}) const description = intl.formatMessage({id: 'ContentBlock.DeleteAction', defaultMessage: 'delete'})
mutator.deleteBlock(originalContentBlock, description) mutator.deleteBlock(originalContentBlock, description)
return null return null
} }
const newBlock = { const newBlock = {
@@ -363,6 +366,7 @@ const CardDetail = (props: Props): JSX.Element|null => {
newBlock.fields = {...newBlock.fields, value: block.value.checked} newBlock.fields = {...newBlock.fields, value: block.value.checked}
} }
mutator.updateBlock(card.boardId, newBlock, originalContentBlock, intl.formatMessage({id: 'ContentBlock.editCardText', defaultMessage: 'edit card text'})) mutator.updateBlock(card.boardId, newBlock, originalContentBlock, intl.formatMessage({id: 'ContentBlock.editCardText', defaultMessage: 'edit card text'}))
return block return block
}} }}
onBlockMoved={async (block: BlockData, beforeBlock: BlockData|null, afterBlock: BlockData|null): Promise<void> => { onBlockMoved={async (block: BlockData, beforeBlock: BlockData|null, afterBlock: BlockData|null): Promise<void> => {
@@ -372,6 +376,7 @@ const CardDetail = (props: Props): JSX.Element|null => {
let sourceWhere: 'after'|'before' let sourceWhere: 'after'|'before'
if (idx === -1) { if (idx === -1) {
Utils.logError('Unable to find the block id in the order of the current block') Utils.logError('Unable to find the block id in the order of the current block')
return return
} }
if (idx === 0) { if (idx === 0) {
@@ -383,6 +388,7 @@ const CardDetail = (props: Props): JSX.Element|null => {
} }
if (afterBlock && afterBlock.id) { if (afterBlock && afterBlock.id) {
await mutator.moveContentBlock(block.id, afterBlock.id, 'after', sourceBlockId, sourceWhere, intl.formatMessage({id: 'ContentBlock.moveBlock', defaultMessage: 'move card content'})) await mutator.moveContentBlock(block.id, afterBlock.id, 'after', sourceBlockId, sourceWhere, intl.formatMessage({id: 'ContentBlock.moveBlock', defaultMessage: 'move card content'}))
return return
} }
if (beforeBlock && beforeBlock.id) { if (beforeBlock && beforeBlock.id) {

Просмотреть файл

@@ -9,7 +9,7 @@ import {act} from 'react-dom/test-utils'
import {TestBlockFactory} from 'src/test/testBlockFactory' import {TestBlockFactory} from 'src/test/testBlockFactory'
import {mockDOM, wrapDNDIntl, mockStateStore} from 'src/testUtils' import {mockDOM, mockStateStore, wrapDNDIntl} from 'src/testUtils'
import CardDetailContents from './cardDetailContents' import CardDetailContents from './cardDetailContents'
import {CardDetailProvider} from './cardDetailContext' import {CardDetailProvider} from './cardDetailContext'

Просмотреть файл

@@ -1,9 +1,9 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react' import React from 'react'
import {useIntl, IntlShape} from 'react-intl' import {IntlShape, useIntl} from 'react-intl'
import {IContentBlockWithCords, ContentBlock as ContentBlockType} from 'src/blocks/contentBlock' import {ContentBlock as ContentBlockType, IContentBlockWithCords} from 'src/blocks/contentBlock'
import {Card} from 'src/blocks/card' import {Card} from 'src/blocks/card'
import {createTextBlock} from 'src/blocks/textBlock' import {createTextBlock} from 'src/blocks/textBlock'
import {Block} from 'src/blocks/block' import {Block} from 'src/blocks/block'
@@ -182,6 +182,7 @@ const CardDetailContents = (props: Props) => {
</div> </div>
) )
} }
return ( return (
<div className='octo-content CardDetailContents'> <div className='octo-content CardDetailContents'>
<div className='octo-block'> <div className='octo-block'>

Просмотреть файл

@@ -6,7 +6,7 @@ import {Provider as ReduxProvider} from 'react-redux'
import userEvent from '@testing-library/user-event' import userEvent from '@testing-library/user-event'
import {wrapIntl, mockStateStore, setup} from 'src/testUtils' import {mockStateStore, setup, wrapIntl} from 'src/testUtils'
import {TestBlockFactory} from 'src/test/testBlockFactory' import {TestBlockFactory} from 'src/test/testBlockFactory'

Просмотреть файл

@@ -17,6 +17,7 @@ function addContentMenu(intl: IntlShape, type: BlockTypes): JSX.Element {
const handler = contentRegistry.getHandler(type) const handler = contentRegistry.getHandler(type)
if (!handler) { if (!handler) {
Utils.logError(`addContentMenu, unknown content type: ${type}`) Utils.logError(`addContentMenu, unknown content type: ${type}`)
return <></> return <></>
} }
const cardDetail = useCardDetailContext() const cardDetail = useCardDetailContext()
@@ -39,6 +40,7 @@ function addContentMenu(intl: IntlShape, type: BlockTypes): JSX.Element {
const CardDetailContentsMenu = () => { const CardDetailContentsMenu = () => {
const intl = useIntl() const intl = useIntl()
return ( return (
<div className='CardDetailContentsMenu content add-content'> <div className='CardDetailContentsMenu content add-content'>
<MenuWrapper> <MenuWrapper>

Просмотреть файл

@@ -1,13 +1,13 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, { import React, {
createContext,
ReactElement, ReactElement,
ReactNode, ReactNode,
createContext,
useCallback,
useContext, useContext,
useMemo, useMemo,
useState, useState,
useCallback
} from 'react' } from 'react'
import {useIntl} from 'react-intl' import {useIntl} from 'react-intl'
@@ -37,6 +37,7 @@ export function useCardDetailContext(): CardDetailContextType {
if (!cardDetailContext) { if (!cardDetailContext) {
throw new Error('CardDetailContext is not available!') throw new Error('CardDetailContext is not available!')
} }
return cardDetailContext return cardDetailContext
} }

Просмотреть файл

@@ -3,10 +3,10 @@
import React from 'react' import React from 'react'
import { import {
act,
fireEvent,
render, render,
screen, screen,
act,
fireEvent
} from '@testing-library/react' } from '@testing-library/react'
import userEvent from '@testing-library/user-event' import userEvent from '@testing-library/user-event'
import {mocked} from 'jest-mock' import {mocked} from 'jest-mock'
@@ -206,7 +206,7 @@ describe('components/cardDetail/CardDetailProperties', () => {
await act(() => userEvent.click(menuElement)) await act(() => userEvent.click(menuElement))
const numberType = screen.getByRole('button', {name: /number/i}) const numberType = screen.getByRole('button', {name: /number/i})
await act( () => userEvent.click(numberType)) await act(() => userEvent.click(numberType))
expect(mockedMutator.insertPropertyTemplate).toHaveBeenCalledTimes(1) expect(mockedMutator.insertPropertyTemplate).toHaveBeenCalledTimes(1)

Просмотреть файл

@@ -63,6 +63,7 @@ const CardDetailProperties = (props: Props) => {
// if only the name has changed, set the property without warning // if only the name has changed, set the property without warning
if (affectsNumOfCards === '0' || oldType === newType) { if (affectsNumOfCards === '0' || oldType === newType) {
mutator.changePropertyTypeAndName(board, cards, propertyTemplate, newType.type, newName) mutator.changePropertyTypeAndName(board, cards, propertyTemplate, newType.type, newName)
return return
} }

Просмотреть файл

@@ -8,7 +8,7 @@ import moment from 'moment'
import {mocked} from 'jest-mock' import {mocked} from 'jest-mock'
import {wrapIntl, mockStateStore} from 'src/testUtils' import {mockStateStore, wrapIntl} from 'src/testUtils'
import {TestBlockFactory} from 'src/test/testBlockFactory' import {TestBlockFactory} from 'src/test/testBlockFactory'

Просмотреть файл

@@ -95,6 +95,7 @@ const CommentsList = (props: Props) => {
// Only modify _own_ comments, EXCEPT for Admins, which can delete _any_ comment // Only modify _own_ comments, EXCEPT for Admins, which can delete _any_ comment
// NOTE: editing comments will exist in the future (in addition to deleting) // NOTE: editing comments will exist in the future (in addition to deleting)
const canDeleteComment: boolean = canDeleteOthersComments || me?.id === comment.modifiedBy const canDeleteComment: boolean = canDeleteOthersComments || me?.id === comment.modifiedBy
return ( return (
<Comment <Comment
key={comment.id} key={comment.id}

Просмотреть файл

@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {useEffect, useCallback} from 'react' import {useCallback, useEffect} from 'react'
import {useIntl} from 'react-intl' import {useIntl} from 'react-intl'
import {ImageBlock, createImageBlock} from 'src/blocks/imageBlock' import {ImageBlock, createImageBlock} from 'src/blocks/imageBlock'
@@ -77,6 +77,7 @@ export default function useImagePaste(boardId: string, cardId: string, contentOr
useEffect(() => { useEffect(() => {
document.addEventListener('paste', onPaste) document.addEventListener('paste', onPaste)
document.addEventListener('drop', onDrop) document.addEventListener('drop', onDrop)
return () => { return () => {
document.removeEventListener('paste', onPaste) document.removeEventListener('paste', onPaste)
document.removeEventListener('drop', onDrop) document.removeEventListener('drop', onDrop)

Просмотреть файл

@@ -93,7 +93,6 @@ describe('components/cardDialog', () => {
jest.clearAllMocks() jest.clearAllMocks()
}) })
test('should match snapshot', async () => { test('should match snapshot', async () => {
const {container} = render(wrapDNDIntl( const {container} = render(wrapDNDIntl(
<ReduxProvider store={store}> <ReduxProvider store={store}>
<CardDialog <CardDialog
@@ -324,7 +323,7 @@ describe('components/cardDialog', () => {
/> />
</ReduxProvider>, </ReduxProvider>,
)) ))
expect(container).toMatchSnapshot() expect(container).toMatchSnapshot()
}) })

Просмотреть файл

@@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {useState, useCallback} from 'react' import React, {useCallback, useState} from 'react'
import {FormattedMessage, useIntl} from 'react-intl' import {FormattedMessage, useIntl} from 'react-intl'
import {Board} from 'src/blocks/board' import {Board} from 'src/blocks/board'
@@ -67,6 +67,7 @@ const CardDialog = (props: Props): JSX.Element => {
const makeTemplateClicked = async () => { const makeTemplateClicked = async () => {
if (!card) { if (!card) {
Utils.assertFailure('card') Utils.assertFailure('card')
return return
} }
@@ -89,6 +90,7 @@ const CardDialog = (props: Props): JSX.Element => {
const handleDeleteCard = async () => { const handleDeleteCard = async () => {
if (!card) { if (!card) {
Utils.assertFailure() Utils.assertFailure()
return return
} }
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DeleteCard, {board: props.board.id, view: props.activeView.id, card: card.id}) TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DeleteCard, {board: props.board.id, view: props.activeView.id, card: card.id})
@@ -111,6 +113,7 @@ const CardDialog = (props: Props): JSX.Element => {
// so adding des // so adding des
if (card?.title === '' && card?.fields.contentOrder.length === 0) { if (card?.title === '' && card?.fields.contentOrder.length === 0) {
handleDeleteCard() handleDeleteCard()
return return
} }
@@ -266,6 +269,7 @@ const CardDialog = (props: Props): JSX.Element => {
if (!isTemplate && !card?.limited) { if (!isTemplate && !card?.limited) {
return (<>{attachBtn()}{following ? unfollowBtn : followBtn}</>) return (<>{attachBtn()}{following ? unfollowBtn : followBtn}</>)
} }
return (<>{attachBtn()}</>) return (<>{attachBtn()}</>)
} }

Просмотреть файл

@@ -1,19 +1,19 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {useCallback, useEffect, useState} from 'react' import React, {useCallback, useEffect, useState} from 'react'
import {useIntl, FormattedMessage} from 'react-intl' import {FormattedMessage, useIntl} from 'react-intl'
import AlertIcon from 'src/widgets/icons/alert' import AlertIcon from 'src/widgets/icons/alert'
import {useAppSelector, useAppDispatch} from 'src/store/hooks' import {useAppDispatch, useAppSelector} from 'src/store/hooks'
import {IUser, UserConfigPatch} from 'src/user' import {IUser, UserConfigPatch} from 'src/user'
import { import {
getCardHiddenWarningSnoozeUntil,
getCardLimitSnoozeUntil,
getMe, getMe,
patchProps, patchProps,
getCardLimitSnoozeUntil,
getCardHiddenWarningSnoozeUntil
} from 'src/store/users' } from 'src/store/users'
import {getCurrentBoardHiddenCardsCount, getCardHiddenWarning} from 'src/store/cards' import {getCardHiddenWarning, getCurrentBoardHiddenCardsCount} from 'src/store/cards'
import TelemetryClient, {TelemetryActions, TelemetryCategory} from 'src/telemetry/telemetryClient' import TelemetryClient, {TelemetryActions, TelemetryCategory} from 'src/telemetry/telemetryClient'
import CheckIcon from 'src/widgets/icons/check' import CheckIcon from 'src/widgets/icons/check'
import NotificationBox from 'src/widgets/notificationBox/notificationBox' import NotificationBox from 'src/widgets/notificationBox/notificationBox'
@@ -103,10 +103,12 @@ const CardLimitNotification = (props: Props) => {
useEffect(() => { useEffect(() => {
if (!show) { if (!show) {
const interval = setInterval(() => setTime(Date.now()), checkSnoozeInterval) const interval = setInterval(() => setTime(Date.now()), checkSnoozeInterval)
return () => { return () => {
clearInterval(interval) clearInterval(interval)
} }
} }
return () => null return () => null
}, [show]) }, [show])

Просмотреть файл

@@ -5,7 +5,7 @@ import {
fireEvent, fireEvent,
render, render,
screen, screen,
within within,
} from '@testing-library/react' } from '@testing-library/react'
import userEvent from '@testing-library/user-event' import userEvent from '@testing-library/user-event'
import React from 'react' import React from 'react'
@@ -375,7 +375,7 @@ describe('components/centerPanel', () => {
)) ))
const cardElement = screen.getByRole('textbox', {name: 'card1'}) const cardElement = screen.getByRole('textbox', {name: 'card1'})
expect(cardElement).not.toBeNull() expect(cardElement).not.toBeNull()
fireEvent.click(cardElement, {shiftKey: true}) fireEvent.click(cardElement, {shiftKey: true})
expect(container).toMatchSnapshot() expect(container).toMatchSnapshot()
//delete //delete

Просмотреть файл

@@ -1,11 +1,11 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
/* eslint-disable max-lines */
import React, { import React, {
useState,
useCallback, useCallback,
useEffect, useEffect,
useMemo useMemo,
useState,
} from 'react' } from 'react'
import {useIntl} from 'react-intl' import {useIntl} from 'react-intl'
import {useHotkeys} from 'react-hotkeys-hook' import {useHotkeys} from 'react-hotkeys-hook'
@@ -15,32 +15,32 @@ import {ClientConfig} from 'src/config/clientConfig'
import {Block} from 'src/blocks/block' import {Block} from 'src/blocks/block'
import {BlockIcons} from 'src/blockIcons' import {BlockIcons} from 'src/blockIcons'
import {Card, createCard} from 'src/blocks/card' import {Card, createCard} from 'src/blocks/card'
import {Board, IPropertyTemplate, BoardGroup} from 'src/blocks/board' import {Board, BoardGroup, IPropertyTemplate} from 'src/blocks/board'
import {BoardView} from 'src/blocks/boardView' import {BoardView} from 'src/blocks/boardView'
import {CardFilter} from 'src/cardFilter' import {CardFilter} from 'src/cardFilter'
import mutator from 'src/mutator' import mutator from 'src/mutator'
import {Utils} from 'src/utils' import {Utils} from 'src/utils'
import {UserSettings} from 'src/userSettings' import {UserSettings} from 'src/userSettings'
import { import {
getCurrentCard,
addCard as addCardAction, addCard as addCardAction,
addTemplate as addTemplateAction, addTemplate as addTemplateAction,
showCardHiddenWarning getCurrentCard,
showCardHiddenWarning,
} from 'src/store/cards' } from 'src/store/cards'
import {getCardLimitTimestamp} from 'src/store/limits' import {getCardLimitTimestamp} from 'src/store/limits'
import {updateView} from 'src/store/views' import {updateView} from 'src/store/views'
import {getVisibleAndHiddenGroups} from 'src/boardUtils' import {getVisibleAndHiddenGroups} from 'src/boardUtils'
import TelemetryClient, {TelemetryCategory, TelemetryActions} from 'src/telemetry/telemetryClient' import TelemetryClient, {TelemetryActions, TelemetryCategory} from 'src/telemetry/telemetryClient'
import {getClientConfig} from 'src/store/clientConfig' import {getClientConfig} from 'src/store/clientConfig'
import './centerPanel.scss' import './centerPanel.scss'
import {useAppSelector, useAppDispatch} from 'src/store/hooks' import {useAppDispatch, useAppSelector} from 'src/store/hooks'
import { import {
getMe,
getBoardUsers, getBoardUsers,
getMe,
getOnboardingTourCategory, getOnboardingTourCategory,
getOnboardingTourStarted, getOnboardingTourStarted,
getOnboardingTourStep, getOnboardingTourStep,
@@ -72,7 +72,7 @@ import {
BoardTourSteps, BoardTourSteps,
FINISHED, FINISHED,
TOUR_BOARD, TOUR_BOARD,
TOUR_CARD TOUR_CARD,
} from './onboardingTour' } from './onboardingTour'
import ShareBoardTourStep from './onboardingTour/shareBoard/shareBoard' import ShareBoardTourStep from './onboardingTour/shareBoard/shareBoard'
@@ -393,6 +393,7 @@ const CenterPanel = (props: Props) => {
defaultMessage: 'No {propertyName}', defaultMessage: 'No {propertyName}',
}, {propertyName: groupByProperty?.name}) }, {propertyName: groupByProperty?.name})
} }
return intl.formatMessage({id: 'centerPanel.unknown-user', defaultMessage: 'Unknown user'}) return intl.formatMessage({id: 'centerPanel.unknown-user', defaultMessage: 'Unknown user'})
} }
@@ -408,6 +409,7 @@ const CenterPanel = (props: Props) => {
}) })
} }
} }
return {visible: vg, hidden: hg} return {visible: vg, hidden: hg}
}, [cards, activeView.fields.visibleOptionIds, activeView.fields.hiddenOptionIds, groupByProperty, boardUsers]) }, [cards, activeView.fields.visibleOptionIds, activeView.fields.hiddenOptionIds, groupByProperty, boardUsers])

Просмотреть файл

@@ -1,9 +1,9 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {useState, useRef} from 'react' import React, {useRef, useState} from 'react'
import Select from 'react-select' import Select from 'react-select'
import {useIntl, FormattedMessage} from 'react-intl' import {FormattedMessage, useIntl} from 'react-intl'
import {MemberRole} from 'src/blocks/board' import {MemberRole} from 'src/blocks/board'

Просмотреть файл

@@ -18,6 +18,7 @@ const ArchivedFile = (props: Props): JSX.Element => {
const fileExtension = useCallback(() => { const fileExtension = useCallback(() => {
let extension = props.fileInfo.extension let extension = props.fileInfo.extension
extension = extension?.startsWith('.') ? extension?.substring(1) : extension extension = extension?.startsWith('.') ? extension?.substring(1) : extension
return extension?.toUpperCase() return extension?.toUpperCase()
}, [props.fileInfo.extension]) }, [props.fileInfo.extension])

Просмотреть файл

@@ -48,6 +48,7 @@ const AttachmentElement = (props: Props): JSX.Element|null => {
name: block.title, name: block.title,
extension: block.title.split('.').slice(0, -1).join('.'), extension: block.title.split('.').slice(0, -1).join('.'),
}) })
return return
} }
const attachmentInfo = await octoClient.getFileInfo(block.boardId, block.fields.fileId) const attachmentInfo = await octoClient.getFileInfo(block.boardId, block.fields.fileId)
@@ -65,8 +66,10 @@ const AttachmentElement = (props: Props): JSX.Element|null => {
if (fName.length > 18) { if (fName.length > 18) {
let result = fName.slice(0, 15) let result = fName.slice(0, 15)
result += '...' result += '...'
return result return result
} }
return fName return fName
} }
setFileName(generateFileName(fileInfo.name)) setFileName(generateFileName(fileInfo.name))

Просмотреть файл

@@ -6,7 +6,7 @@ import {
fireEvent, fireEvent,
render, render,
screen, screen,
waitFor waitFor,
} from '@testing-library/react' } from '@testing-library/react'
import {mocked} from 'jest-mock' import {mocked} from 'jest-mock'
import userEvent from '@testing-library/user-event' import userEvent from '@testing-library/user-event'
@@ -174,7 +174,7 @@ describe('components/content/checkboxElement', () => {
)) ))
const input = screen.getByRole('textbox') const input = screen.getByRole('textbox')
// should delete if title is empty // should delete if title is empty
await userEvent.type(input, '{Escape}') await userEvent.type(input, '{Escape}')
expect(deleteElement).toHaveBeenCalledTimes(1) expect(deleteElement).toHaveBeenCalledTimes(1)
await userEvent.type(input, '{Enter}') await userEvent.type(input, '{Enter}')

Просмотреть файл

@@ -68,6 +68,7 @@ const CheckboxElement = (props: Props) => {
const {lastAddedBlock} = cardDetail const {lastAddedBlock} = cardDetail
if (title === '' && block.id === lastAddedBlock.id && lastAddedBlock.autoAdded && props.onDeleteElement) { if (title === '' && block.id === lastAddedBlock.id && lastAddedBlock.autoAdded && props.onDeleteElement) {
props.onDeleteElement() props.onDeleteElement()
return return
} }
@@ -77,6 +78,7 @@ const CheckboxElement = (props: Props) => {
// Wait for the change to happen // Wait for the change to happen
setTimeout(props.onAddElement, 100) setTimeout(props.onAddElement, 100)
} }
return return
} }

Просмотреть файл

@@ -2,7 +2,6 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {ReactElement, ReactNode} from 'react' import React, {ReactElement, ReactNode} from 'react'
import {render} from '@testing-library/react' import {render} from '@testing-library/react'
import {wrapIntl} from 'src/testUtils' import {wrapIntl} from 'src/testUtils'
@@ -53,7 +52,7 @@ describe('components/content/contentElement', () => {
}) })
it('should return null for unknown type', () => { it('should return null for unknown type', () => {
jest.spyOn(console, 'error').mockImplementation() jest.spyOn(console, 'error').mockImplementation()
const block: ContentBlock = {...contentBlock, type: 'unknown'} const block: ContentBlock = {...contentBlock, type: 'unknown'}
const {container} = render(wrap( const {container} = render(wrap(

Просмотреть файл

@@ -30,6 +30,7 @@ export default function ContentElement(props: Props): JSX.Element|null {
const handler = contentRegistry.getHandler(block.type) const handler = contentRegistry.getHandler(block.type)
if (!handler) { if (!handler) {
Utils.logError(`ContentElement, unknown content type: ${block.type}`) Utils.logError(`ContentElement, unknown content type: ${block.type}`)
return null return null
} }

Просмотреть файл

@@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
/* eslint-disable react/require-optimization */
import {IntlShape} from 'react-intl' import {IntlShape} from 'react-intl'
import {BlockTypes} from 'src/blocks/block' import {BlockTypes} from 'src/blocks/block'
@@ -25,6 +25,7 @@ class ContentRegistry {
registerContentType(entry: ContentHandler) { registerContentType(entry: ContentHandler) {
if (this.isContentType(entry.type)) { if (this.isContentType(entry.type)) {
Utils.logError(`registerContentType, already registered type: ${entry.type}`) Utils.logError(`registerContentType, already registered type: ${entry.type}`)
return return
} }
this.registry.set(entry.type, entry) this.registry.set(entry.type, entry)

Просмотреть файл

@@ -2,15 +2,14 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react' import React from 'react'
import {render, act} from '@testing-library/react' import {act, render} from '@testing-library/react'
import {Provider as ReduxProvider} from 'react-redux' import {Provider as ReduxProvider} from 'react-redux'
import {mocked} from 'jest-mock' import {mocked} from 'jest-mock'
import {TextBlock} from 'src/blocks/textBlock' import {TextBlock} from 'src/blocks/textBlock'
import {mockDOM, wrapDNDIntl, mockStateStore} from 'src/testUtils' import {mockDOM, mockStateStore, wrapDNDIntl} from 'src/testUtils'
import {Utils} from 'src/utils' import {Utils} from 'src/utils'

Просмотреть файл

@@ -3,7 +3,7 @@
import {act, render, screen} from '@testing-library/react' import {act, render, screen} from '@testing-library/react'
import React, {ReactNode, ReactElement} from 'react' import React, {ReactElement, ReactNode} from 'react'
import {mocked} from 'jest-mock' import {mocked} from 'jest-mock'
import {Provider as ReduxProvider} from 'react-redux' import {Provider as ReduxProvider} from 'react-redux'
@@ -11,7 +11,7 @@ import userEvent from '@testing-library/user-event'
import {Utils} from 'src/utils' import {Utils} from 'src/utils'
import {TestBlockFactory} from 'src/test/testBlockFactory' import {TestBlockFactory} from 'src/test/testBlockFactory'
import {mockDOM, wrapDNDIntl, mockStateStore} from 'src/testUtils' import {mockDOM, mockStateStore, wrapDNDIntl} from 'src/testUtils'
import mutator from 'src/mutator' import mutator from 'src/mutator'

Просмотреть файл

@@ -6,13 +6,12 @@ import {act, render, screen} from '@testing-library/react'
import userEvent from '@testing-library/user-event' import userEvent from '@testing-library/user-event'
import {mockStateStore} from 'src/testUtils' import {mockStateStore, wrapIntl} from 'src/testUtils'
import {wrapIntl} from 'src/testUtils'
import CreateBoardFromTemplate from './createBoardFromTemplate' import CreateBoardFromTemplate from './createBoardFromTemplate'
jest.mock('src/hooks/useGetAllTemplates', () => ({ jest.mock('src/hooks/useGetAllTemplates', () => ({
useGetAllTemplates: () => [{id: 'id', title: 'title', description: 'description', icon: '🍔'}] useGetAllTemplates: () => [{id: 'id', title: 'title', description: 'description', icon: '🍔'}],
})) }))
describe('components/createBoardFromTemplate', () => { describe('components/createBoardFromTemplate', () => {
@@ -26,7 +25,7 @@ describe('components/createBoardFromTemplate', () => {
const store = mockStateStore([], state) const store = mockStateStore([], state)
const setCanCreate = jest.fn const setCanCreate = jest.fn
const setAction = jest.fn const setAction = jest.fn
const newBoardInfoIcon = (<i className="icon-information-outline" />) const newBoardInfoIcon = (<i className='icon-information-outline'/>)
const {container} = render(wrapIntl( const {container} = render(wrapIntl(
<ReduxProvider store={store}> <ReduxProvider store={store}>
@@ -41,11 +40,11 @@ describe('components/createBoardFromTemplate', () => {
expect(container).toMatchSnapshot() expect(container).toMatchSnapshot()
}) })
it('clicking checkbox toggles the templates selector', async () => { it.only('clicking checkbox toggles the templates selector', async () => {
const store = mockStateStore([], state) const store = mockStateStore([], state)
const setCanCreate = jest.fn const setCanCreate = jest.fn
const setAction = jest.fn const setAction = jest.fn
const newBoardInfoIcon = (<i className="icon-information-outline" />) const newBoardInfoIcon = (<i className='icon-information-outline'/>)
render(wrapIntl( render(wrapIntl(
<ReduxProvider store={store}> <ReduxProvider store={store}>

Просмотреть файл

@@ -4,19 +4,19 @@ import React, {
useCallback, useCallback,
useEffect, useEffect,
useRef, useRef,
useState useState,
} from 'react' } from 'react'
import {createIntl, createIntlCache, IntlProvider} from 'react-intl' import {IntlProvider, createIntl, createIntlCache} from 'react-intl'
import Select from 'react-select/async' import Select from 'react-select/async'
import { import {
components,
FormatOptionLabelMeta, FormatOptionLabelMeta,
GroupBase, GroupBase,
PlaceholderProps PlaceholderProps,
SingleValue,
components,
} from 'react-select' } from 'react-select'
import {SingleValue} from 'react-select'
import {CSSObject} from '@emotion/serialize' import {CSSObject} from '@emotion/serialize'
@@ -49,7 +49,7 @@ const TEMPLATE_DESCRIPTION_LENGTH = 70
const cache = createIntlCache() const cache = createIntlCache()
const intl = createIntl({ const intl = createIntl({
locale: getCurrentLanguage(), locale: getCurrentLanguage(),
messages: getMessages(getCurrentLanguage()) messages: getMessages(getCurrentLanguage()),
}, cache) }, cache)
const {ValueContainer, Placeholder} = components const {ValueContainer, Placeholder} = components
@@ -60,7 +60,7 @@ const CustomValueContainer = ({children, ...props}: any) => {
{props.selectProps.placeholder} {props.selectProps.placeholder}
</Placeholder> </Placeholder>
{React.Children.map(children, (child) => {React.Children.map(children, (child) =>
child && child.type !== Placeholder ? child : null (child && child.type !== Placeholder ? child : null)
)} )}
</ValueContainer> </ValueContainer>
) )
@@ -78,7 +78,6 @@ const CreateBoardFromTemplate = (props: Props) => {
const templateIdRef = useRef('') const templateIdRef = useRef('')
templateIdRef.current = selectedBoardTemplateId templateIdRef.current = selectedBoardTemplateId
const showNewBoardTemplateSelector = async () => { const showNewBoardTemplateSelector = async () => {
setAddBoard((prev: boolean) => !prev) setAddBoard((prev: boolean) => !prev)
} }
@@ -86,14 +85,14 @@ const CreateBoardFromTemplate = (props: Props) => {
// CreateBoardFromTemplate // CreateBoardFromTemplate
const addBoardToChannel = async (channelId: string, teamId: string) => { const addBoardToChannel = async (channelId: string, teamId: string) => {
if (!addBoardRef.current || !templateIdRef.current) { if (!addBoardRef.current || !templateIdRef.current) {
return return undefined
} }
const ACTION_DESCRIPTION = 'board created from channel' const ACTION_DESCRIPTION = 'board created from channel'
const LINKED_CHANNEL = 'linked channel' const LINKED_CHANNEL = 'linked channel'
const asTemplate = false const asTemplate = false
let boardsAndBlocks = undefined let boardsAndBlocks
if (templateIdRef.current === EMPTY_BOARD) { if (templateIdRef.current === EMPTY_BOARD) {
boardsAndBlocks = await mutator.addEmptyBoard(teamId, intl) boardsAndBlocks = await mutator.addEmptyBoard(teamId, intl)
@@ -102,7 +101,8 @@ const CreateBoardFromTemplate = (props: Props) => {
} }
const board = boardsAndBlocks.boards[0] const board = boardsAndBlocks.boards[0]
await mutator.updateBoard({...board, channelId: channelId}, board, LINKED_CHANNEL) await mutator.updateBoard({...board, channelId}, board, LINKED_CHANNEL)
return board return board
} }
@@ -127,6 +127,7 @@ const CreateBoardFromTemplate = (props: Props) => {
if (wordBreakingIndex === -1) { if (wordBreakingIndex === -1) {
return str return str
} }
return `${str.substring(0, (len + wordBreakingIndex))}` return `${str.substring(0, (len + wordBreakingIndex))}`
} }
@@ -143,6 +144,7 @@ const CreateBoardFromTemplate = (props: Props) => {
// do not show the description for the selected option so the input only show the icon and title of the template // do not show the description for the selected option so the input only show the icon and title of the template
const selectedOption = id === optionLabel.selectValue[0]?.id const selectedOption = id === optionLabel.selectValue[0]?.id
return ( return (
<div key={id}> <div key={id}>
<span className={`${cssPrefix}__icon`}> <span className={`${cssPrefix}__icon`}>
@@ -176,8 +178,9 @@ const CreateBoardFromTemplate = (props: Props) => {
templates.push(emptyBoard) templates.push(emptyBoard)
if (value !== '') { if (value !== '') {
templates = templates.filter(template => template.title.toLowerCase().includes(value.toLowerCase())) templates = templates.filter((template) => template.title.toLowerCase().includes(value.toLowerCase()))
} }
return templates return templates
}, [allTemplates]) }, [allTemplates])
@@ -202,10 +205,11 @@ const CreateBoardFromTemplate = (props: Props) => {
}), }),
valueContainer: (baseStyles: CSSObject): CSSObject => ({ valueContainer: (baseStyles: CSSObject): CSSObject => ({
...baseStyles, ...baseStyles,
overflow: 'visible' overflow: 'visible',
}), }),
placeholder: (baseStyles: CSSObject, state: PlaceholderProps<ReactSelectItem, false, GroupBase<ReactSelectItem>>): CSSObject => { placeholder: (baseStyles: CSSObject, state: PlaceholderProps<ReactSelectItem, false, GroupBase<ReactSelectItem>>): CSSObject => {
const modifyPlaceholder = state.selectProps.menuIsOpen || (!state.selectProps.menuIsOpen && state.hasValue) const modifyPlaceholder = state.selectProps.menuIsOpen || (!state.selectProps.menuIsOpen && state.hasValue)
return { return {
...baseStyles, ...baseStyles,
position: 'absolute', position: 'absolute',
@@ -257,12 +261,13 @@ const CreateBoardFromTemplate = (props: Props) => {
const IntlCreateBoardFromTemplate = (props: Props) => { const IntlCreateBoardFromTemplate = (props: Props) => {
const language = useAppSelector<string>(getLanguage) const language = useAppSelector<string>(getLanguage)
return ( return (
<IntlProvider <IntlProvider
locale={language.split(/[_]/)[0]} locale={language.split(/[_]/)[0]}
messages={getMessages(language)} messages={getMessages(language)}
> >
<CreateBoardFromTemplate {...props}/> <CreateBoardFromTemplate {...props}/>
</IntlProvider> </IntlProvider>
) )
} }

Просмотреть файл

@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {useState, KeyboardEvent} from 'react' import React, {KeyboardEvent, useState} from 'react'
import {useIntl} from 'react-intl' import {useIntl} from 'react-intl'
@@ -49,6 +49,7 @@ const CreateCategory = (props: Props): JSX.Element => {
const onCreate = async (categoryName: string) => { const onCreate = async (categoryName: string) => {
if (!me) { if (!me) {
Utils.logError('me not initialized') Utils.logError('me not initialized')
return return
} }

Просмотреть файл

@@ -38,8 +38,10 @@ export default class ErrorBoundary extends React.Component<Props, State> {
render(): React.ReactNode { render(): React.ReactNode {
if (this.state.hasError) { if (this.state.hasError) {
this.handleError() this.handleError()
return <span>{this.msg}</span> return <span>{this.msg}</span>
} }
return this.props.children return this.props.children
} }
} }

Просмотреть файл

@@ -4,17 +4,12 @@
import React from 'react' import React from 'react'
import { import {
render,
act, act,
fireEvent,
render,
screen, screen,
fireEvent
} from '@testing-library/react' } from '@testing-library/react'
import {wrapIntl} from 'src/testUtils' import {wrapIntl} from 'src/testUtils'
import FlashMessages, {sendFlashMessage} from './flashMessages' import FlashMessages, {sendFlashMessage} from './flashMessages'

Просмотреть файл

@@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {useState, useEffect} from 'react' import React, {useEffect, useState} from 'react'
import {createNanoEvents} from 'nanoevents' import {createNanoEvents} from 'nanoevents'
import './flashMessages.scss' import './flashMessages.scss'
@@ -37,6 +37,7 @@ const FlashMessages = (props: Props) => {
setMessage(newMessage) setMessage(newMessage)
} }
}) })
return () => { return () => {
isSubscribed = false isSubscribed = false
} }

Просмотреть файл

@@ -2,7 +2,7 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react' import React from 'react'
import {render, screen, fireEvent} from '@testing-library/react' import {fireEvent, render, screen} from '@testing-library/react'
import {Provider as ReduxProvider} from 'react-redux' import {Provider as ReduxProvider} from 'react-redux'
@@ -10,7 +10,7 @@ import userEvent from '@testing-library/user-event'
import {mocked} from 'jest-mock' import {mocked} from 'jest-mock'
import {wrapDNDIntl, mockStateStore, blocksById} from 'src/testUtils' import {blocksById, mockStateStore, wrapDNDIntl} from 'src/testUtils'
import {TestBlockFactory} from 'src/test/testBlockFactory' import {TestBlockFactory} from 'src/test/testBlockFactory'

Просмотреть файл

@@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {useMemo, useCallback} from 'react' import React, {useCallback, useMemo} from 'react'
import {FormattedMessage} from 'react-intl' import {FormattedMessage} from 'react-intl'
import {Constants, Permission} from 'src/constants' import {Constants, Permission} from 'src/constants'

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше