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",
"unused-imports",
"no-relative-import-paths",
"import-newlines"
"import-newlines",
"eslint-comments"
],
"parser": "@typescript-eslint/parser",
"settings": {
@@ -27,21 +28,101 @@
"line",
" Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n See LICENSE.txt for license information."
],
"max-lines": "off",
"no-unused-expressions": 0,
"babel/no-unused-expressions": [
"array-bracket-spacing": [
2,
{
"allowShortCircuit": true
}
"never"
],
"eol-last": [
"array-callback-return": 2,
"arrow-body-style": 0,
"arrow-parens": [
2,
"always"
],
"import/no-unresolved": 2,
"import/order": [
"arrow-spacing": [
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",
"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,
"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": [
2,
{
"ignore": [
"location",
"history",
"component"
"component",
"className"
]
}
],
"react/no-string-refs": 2,
"no-only-tests/no-only-tests": [
2,
{
"focus": [
"only",
"skip"
]
}
"react/require-default-props": 0,
"react/require-optimization": 1,
"react/require-render-return": 2,
"react/self-closing-comp": 2,
"react/sort-comp": 0,
"react/style-prop-object": [
2,
{
"allow": [
"FormattedNumber",
"FormattedDuration",
"FormattedRelativeTime",
"Timestamp"
]
}
],
"max-nested-callbacks": [
2,
@@ -95,11 +562,84 @@
2,
3
],
"object-curly-spacing": [
"formatjs/no-multiple-whitespaces": 2,
"require-yield": 2,
"rest-spread-spacing": [
2,
"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": [
{
@@ -206,7 +746,9 @@
"global-require": 0,
"new-cap": 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 = {
transform: {
"^.+\\.(t|j)sx?$": ["@swc/jest"]
'^.+\\.(t|j)sx?$': ['@swc/jest'],
},
moduleFileExtensions: [
"ts",
"tsx",
"js",
"jsx",
"json",
"node"
'ts',
'tsx',
'js',
'jsx',
'json',
'node',
],
extensionsToTreatAsEsm: ['.ts', '.tsx'],
transformIgnorePatterns: [
"/nanoevents/",
"node_modules/(?!react-native|react-router|react-day-picker)"
'/nanoevents/',
'node_modules/(?!react-native|react-router|react-day-picker)',
],
testEnvironment: "jsdom",
testEnvironment: 'jsdom',
collectCoverage: true,
collectCoverageFrom: [
"src/**/*.{ts,tsx,js,jsx}",
"!src/test/**"
'src/**/*.{ts,tsx,js,jsx}',
'!src/test/**',
],
testPathIgnorePatterns: [
"/node_modules/",
'/node_modules/',
],
clearMocks: true,
coverageReporters: [
"lcov",
"text-summary"
'lcov',
'text-summary',
],
moduleNameMapper: {
"^.+\\.(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",
"\\.(scss|css)$": "<rootDir>/__mocks__/styleMock.js",
"^bundle-loader\\?lazy\\!(.*)$": "$1",
"^src(.*)$": "<rootDir>/src$1",
"^i18n(.*)$": "<rootDir>/i18n$1",
"^static(.*)$": "<rootDir>/static$1",
"^moment(.*)$": "<rootDir>/../node_modules/moment$1",
'^.+\\.(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',
'\\.(scss|css)$': '<rootDir>/__mocks__/styleMock.js',
'^bundle-loader\\?lazy\\!(.*)$': '$1',
'^src(.*)$': '<rootDir>/src$1',
'^i18n(.*)$': '<rootDir>/i18n$1',
'^static(.*)$': '<rootDir>/static$1',
'^moment(.*)$': '<rootDir>/../node_modules/moment$1',
},
moduleDirectories: [
"src",
"node_modules",
'src',
'node_modules',
],
reporters: [
"default",
"jest-junit"
'default',
'jest-junit',
],
setupFiles: [
"jest-canvas-mock"
'jest-canvas-mock',
],
setupFilesAfterEnv: [
"<rootDir>/src/test/setup.tsx"
'<rootDir>/src/test/setup.tsx',
],
testTimeout: 60000,
testEnvironmentOptions: {
url: "http://localhost:8065"
}
url: 'http://localhost:8065',
},
};
module.exports = config;

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

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

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

@@ -106,6 +106,8 @@
"css-loader": "6.7.1",
"eslint-import-resolver-webpack": "0.13.2",
"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-import": "2.25.4",
"eslint-plugin-import-newlines": "1.3.1",

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -4,7 +4,6 @@
import React, {ReactElement, ReactNode} from 'react'
import {render, screen, waitFor} from '@testing-library/react'
import {mocked} from 'jest-mock'
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(container).toMatchSnapshot()
})
})

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

@@ -4,7 +4,7 @@
import React from 'react'
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 mutator from 'src/mutator'
import octoClient from 'src/octoClient'
@@ -27,6 +27,7 @@ const AddContentMenuItem = (props: Props): JSX.Element => {
const handler = contentRegistry.getHandler(type)
if (!handler) {
Utils.logError(`addContentMenu, unknown content type: ${type}`)
return <></>
}

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

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

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

@@ -4,17 +4,17 @@
import React from 'react'
import {Provider as ReduxProvider} from 'react-redux'
import {
act,
fireEvent,
render,
screen,
fireEvent,
act
} from '@testing-library/react'
import {
mockDOM,
wrapDNDIntl,
mockStateStore,
setup
setup,
wrapDNDIntl,
} from 'src/testUtils'
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 () => {
const onSave = jest.fn()
const {user} = setup(wrapDNDIntl(
<ReduxProvider store={store}>
<BlockContent

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

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

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

@@ -2,7 +2,7 @@
// See LICENSE.txt for license information.
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 octoClient from 'src/octoClient'

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

@@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// 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 octoClient from 'src/octoClient'

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -2,7 +2,7 @@
// See LICENSE.txt for license information.
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 octoClient from 'src/octoClient'

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

@@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// 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 octoClient from 'src/octoClient'
@@ -44,6 +44,7 @@ const Image: ContentType<FileInfo> = {
/>
)
}
return null
},
Input: (props: BlockInputProps<FileInfo>) => {

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

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

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

@@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// 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'
@@ -20,6 +20,7 @@ const ListItem: ContentType = {
useEffect(() => {
ref.current?.focus()
}, [])
return (
<ul>
<li>

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

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

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

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

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

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

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

@@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// 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 {Utils} from 'src/utils'
@@ -16,6 +16,7 @@ const Text: ContentType = {
editable: true,
Display: (props: BlockInputProps) => {
const html: string = Utils.htmlFromMarkdown(props.value || '')
return (
<div
dangerouslySetInnerHTML={{__html: html}}
@@ -28,6 +29,7 @@ const Text: ContentType = {
useEffect(() => {
ref.current?.focus()
}, [])
return (
<input
ref={ref}

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

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

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

@@ -3,9 +3,9 @@
import React from 'react'
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 TextBlock from '.'

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

@@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// 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 octoClient from 'src/octoClient'
@@ -49,6 +49,7 @@ const Video: ContentType<FileInfo> = {
</video>
)
}
return null
},
Input: (props: BlockInputProps<FileInfo>) => {

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

@@ -2,7 +2,7 @@
// See LICENSE.txt for license information.
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 octoClient from 'src/octoClient'

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -5,7 +5,7 @@ import {
render,
screen,
waitFor,
within
within,
} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import React from 'react'
@@ -20,7 +20,7 @@ import {MemoryRouter, Router} from 'react-router-dom'
import Mutator from 'src/mutator'
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 {mockDOM, mockStateStore, wrapDNDIntl} from 'src/testUtils'
@@ -288,7 +288,7 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
const useTemplateButton = screen.getByText('Use this template').parentElement
expect(useTemplateButton).not.toBeNull()
await userEvent.click(useTemplateButton!)
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1))
@@ -313,11 +313,10 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
expect(divBoardToSelect).not.toBeNull()
await userEvent.click(divBoardToSelect!)
const useTemplateButton = screen.getByText('Use this template').parentElement
expect(useTemplateButton).not.toBeNull()
await userEvent.click(useTemplateButton!)
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1))
@@ -339,12 +338,12 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
expect(divBoardToSelect).not.toBeNull()
await userEvent.click(divBoardToSelect!)
const useTemplateButton = screen.getByText('Use this template').parentElement
expect(useTemplateButton).not.toBeNull()
await userEvent.click(useTemplateButton!)
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(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
expect(useTemplateButton).not.toBeNull()
await userEvent.click(useTemplateButton!)
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(mockedTelemetry.trackEvent).toBeCalledWith('boards', 'createBoardViaTemplate', {boardTemplateId: 'template_id_2'}))

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

@@ -1,10 +1,10 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {
useEffect,
useState,
useCallback,
useMemo
useEffect,
useMemo,
useState,
} from 'react'
import {FormattedMessage, useIntl} from 'react-intl'
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 octoClient from 'src/octoClient'
import mutator from 'src/mutator'
import {getTemplates, getCurrentBoardId} from 'src/store/boards'
import {getCurrentTeam, Team} from 'src/store/teams'
import {getCurrentBoardId, getTemplates} from 'src/store/boards'
import {Team, getCurrentTeam} from 'src/store/teams'
import {fetchGlobalTemplates, getGlobalTemplates} from 'src/store/globalTemplates'
import {useAppDispatch, useAppSelector} from 'src/store/hooks'
import TelemetryClient, {TelemetryActions, TelemetryCategory} from 'src/telemetry/telemetryClient'

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

@@ -1,17 +1,17 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {
render,
within,
act,
waitFor
render,
waitFor,
within,
} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import React from 'react'
import {MockStoreEnhanced} from 'redux-mock-store'
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 {IUser} from 'src/user'

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

@@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// 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 {Card} from 'src/blocks/card'
@@ -45,6 +45,7 @@ const BoardTemplateSelectorPreview = (props: Props) => {
}
})
}
return () => {
isSubscribed = false
}
@@ -62,6 +63,7 @@ const BoardTemplateSelectorPreview = (props: Props) => {
if (!activeView) {
return {visible: [], hidden: []}
}
return getVisibleAndHiddenGroups(activeTemplateCards, activeView.fields.visibleOptionIds, activeView?.fields.hiddenOptionIds, groupByProperty)
}, [activeTemplateCards, activeView, groupByProperty])

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

@@ -19,7 +19,7 @@ import AddIcon from 'src/widgets/icons/add'
import BoardSwitcherDialog from 'src/components/boardsSwitcherDialog/boardSwitcherDialog'
import {Utils} from 'src/utils'
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 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 {createMemoryHistory, History} from 'history'
import {History, createMemoryHistory} from 'history'
import {Router} from 'react-router-dom'

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

@@ -1,12 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {
MutableRefObject,
ReactNode,
useRef,
createRef,
useState,
useEffect,
MutableRefObject
useRef,
useState,
} from 'react'
import './boardSwitcherDialog.scss'
@@ -19,7 +19,7 @@ import SearchDialog from 'src/components/searchDialog/searchDialog'
import Globe from 'src/widgets/icons/globe'
import LockOutline from 'src/widgets/icons/lockOutline'
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 {Utils} from 'src/utils'
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'})
refs.current = items.map((_, i) => refs.current[i] ?? createRef())
setRefs(refs)
return items.map((item, i) => {
const resultTitle = item.title || untitledBoardTitle
const teamTitle = teamsById[item.teamId].title
@@ -83,6 +84,7 @@ const BoardSwitcherDialog = (props: Props): JSX.Element => {
...prevIDs,
[i]: [item.teamId, item.id],
}))
return (
<div
key={item.id}

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

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

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

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

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

@@ -44,8 +44,8 @@ function fixTimestampToMinutesAccuracy(timestamp: number) {
}
function cardsWithValue(cards: readonly Card[], property: IPropertyTemplate): Card[] {
return cards.
filter((card) => Boolean(getCardProperty(card, property)))
return cards
.filter((card) => Boolean(getCardProperty(card, property)))
}
// 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) {
return ''
}
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) {
return ''
}
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
if (property.type === 'multiSelect') {
cardsWithValue(cards, property).
forEach((card) => {
cardsWithValue(cards, property)
.forEach((card) => {
values += (getCardProperty(card, property) as string[]).length
})
} else {
@@ -140,8 +142,8 @@ function countUniqueValue(cards: readonly Card[], property: IPropertyTemplate):
function sum(cards: readonly Card[], property: IPropertyTemplate): string {
let result = 0
cardsWithValue(cards, property).
forEach((card) => {
cardsWithValue(cards, property)
.forEach((card) => {
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 avg = result / numCards
return String(Utils.roundTo(avg, ROUNDED_DECIMAL_PLACES))
}
function median(cards: readonly Card[], property: IPropertyTemplate): string {
const sorted = cardsWithValue(cards, property).
sort((a, b) => {
const sorted = cardsWithValue(cards, property)
.sort((a, b) => {
if (!getCardProperty(a, property)) {
return 1
}
@@ -231,6 +234,7 @@ function earliest(cards: readonly Card[], property: IPropertyTemplate, intl: Int
return ''
}
const date = new Date(result)
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)
}
})
return result
}
@@ -251,6 +256,7 @@ function latest(cards: readonly Card[], property: IPropertyTemplate, intl: IntlS
return ''
}
const date = new Date(result)
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)
}
})
return result
}
@@ -276,10 +283,12 @@ function getTimestampsFromPropertyValue(value: number | string | string[]): numb
} catch {
return []
}
return [property.from, property.to].flatMap((e) => {
return e ? [e] : []
})
}
return []
}
@@ -292,6 +301,7 @@ function dateRange(cards: readonly Card[], property: IPropertyTemplate, intl: In
if (resultLatest === Number.NEGATIVE_INFINITY) {
return ''
}
return moment.duration(resultLatest - resultEarliest, 'milliseconds').locale(intl.locale.toLowerCase()).humanize()
}

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

@@ -2,10 +2,9 @@
// See LICENSE.txt for license information.
import React from 'react'
import Select, {components, DropdownIndicatorProps, StylesConfig} from 'react-select'
import Select, {DropdownIndicatorProps, StylesConfig, components} from 'react-select'
import {useIntl, IntlShape} from 'react-intl'
import {IntlShape, useIntl} from 'react-intl'
import {getSelectBaseStyle} from 'src/theme'
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 {TestBlockFactory} from 'src/test/testBlockFactory'
import {wrapIntl, mockStateStore} from 'src/testUtils'
import {mockStateStore, wrapIntl} from 'src/testUtils'
import {IPropertyTemplate} from 'src/blocks/board'
import CalendarView from './fullCalendar'

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

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

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

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

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

@@ -1,14 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {
Fragment,
useCallback,
useEffect,
useMemo,
useRef,
useState,
Fragment,
useMemo
} from 'react'
import {FormattedMessage, useIntl, IntlShape} from 'react-intl'
import {FormattedMessage, IntlShape, useIntl} from 'react-intl'
import {BlockIcons} from 'src/blockIcons'
import {Card} from 'src/blocks/card'
@@ -30,7 +30,7 @@ import TelemetryClient, {TelemetryActions, TelemetryCategory} from 'src/telemetr
import BlockIconSelector from 'src/components/blockIconSelector'
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 {Permission} from 'src/constants'
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)
dispatch(updateContents([newBlock]))
return newBlock
}
@@ -144,7 +145,7 @@ const CardDetail = (props: Props): JSX.Element|null => {
useEffect(() => {
return () => {
saveTitleRef.current && saveTitleRef.current()
saveTitleRef.current?.()
}
}, [])
@@ -339,6 +340,7 @@ const CardDetail = (props: Props): JSX.Element|null => {
} else {
newBlock = await addBlockNewEditor(card, intl, block.value, {}, block.contentType, afterBlock?.id, dispatch)
}
return {...block, id: newBlock.id}
}}
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'})
mutator.deleteBlock(originalContentBlock, description)
return null
}
const newBlock = {
@@ -363,6 +366,7 @@ const CardDetail = (props: Props): JSX.Element|null => {
newBlock.fields = {...newBlock.fields, value: block.value.checked}
}
mutator.updateBlock(card.boardId, newBlock, originalContentBlock, intl.formatMessage({id: 'ContentBlock.editCardText', defaultMessage: 'edit card text'}))
return block
}}
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'
if (idx === -1) {
Utils.logError('Unable to find the block id in the order of the current block')
return
}
if (idx === 0) {
@@ -383,6 +388,7 @@ const CardDetail = (props: Props): JSX.Element|null => {
}
if (afterBlock && afterBlock.id) {
await mutator.moveContentBlock(block.id, afterBlock.id, 'after', sourceBlockId, sourceWhere, intl.formatMessage({id: 'ContentBlock.moveBlock', defaultMessage: 'move card content'}))
return
}
if (beforeBlock && beforeBlock.id) {

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

@@ -9,7 +9,7 @@ import {act} from 'react-dom/test-utils'
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 {CardDetailProvider} from './cardDetailContext'

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

@@ -1,9 +1,9 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
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 {createTextBlock} from 'src/blocks/textBlock'
import {Block} from 'src/blocks/block'
@@ -182,6 +182,7 @@ const CardDetailContents = (props: Props) => {
</div>
)
}
return (
<div className='octo-content CardDetailContents'>
<div className='octo-block'>

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

@@ -6,7 +6,7 @@ import {Provider as ReduxProvider} from 'react-redux'
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'

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

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

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

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

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

@@ -3,10 +3,10 @@
import React from 'react'
import {
act,
fireEvent,
render,
screen,
act,
fireEvent
} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import {mocked} from 'jest-mock'
@@ -206,7 +206,7 @@ describe('components/cardDetail/CardDetailProperties', () => {
await act(() => userEvent.click(menuElement))
const numberType = screen.getByRole('button', {name: /number/i})
await act( () => userEvent.click(numberType))
await act(() => userEvent.click(numberType))
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 (affectsNumOfCards === '0' || oldType === newType) {
mutator.changePropertyTypeAndName(board, cards, propertyTemplate, newType.type, newName)
return
}

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

@@ -8,7 +8,7 @@ import moment from 'moment'
import {mocked} from 'jest-mock'
import {wrapIntl, mockStateStore} from 'src/testUtils'
import {mockStateStore, wrapIntl} from 'src/testUtils'
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
// NOTE: editing comments will exist in the future (in addition to deleting)
const canDeleteComment: boolean = canDeleteOthersComments || me?.id === comment.modifiedBy
return (
<Comment
key={comment.id}

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

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

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

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

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

@@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// 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 {Board} from 'src/blocks/board'
@@ -67,6 +67,7 @@ const CardDialog = (props: Props): JSX.Element => {
const makeTemplateClicked = async () => {
if (!card) {
Utils.assertFailure('card')
return
}
@@ -89,6 +90,7 @@ const CardDialog = (props: Props): JSX.Element => {
const handleDeleteCard = async () => {
if (!card) {
Utils.assertFailure()
return
}
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
if (card?.title === '' && card?.fields.contentOrder.length === 0) {
handleDeleteCard()
return
}
@@ -266,6 +269,7 @@ const CardDialog = (props: Props): JSX.Element => {
if (!isTemplate && !card?.limited) {
return (<>{attachBtn()}{following ? unfollowBtn : followBtn}</>)
}
return (<>{attachBtn()}</>)
}

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

@@ -1,19 +1,19 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
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 {useAppSelector, useAppDispatch} from 'src/store/hooks'
import {useAppDispatch, useAppSelector} from 'src/store/hooks'
import {IUser, UserConfigPatch} from 'src/user'
import {
getCardHiddenWarningSnoozeUntil,
getCardLimitSnoozeUntil,
getMe,
patchProps,
getCardLimitSnoozeUntil,
getCardHiddenWarningSnoozeUntil
} 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 CheckIcon from 'src/widgets/icons/check'
import NotificationBox from 'src/widgets/notificationBox/notificationBox'
@@ -103,10 +103,12 @@ const CardLimitNotification = (props: Props) => {
useEffect(() => {
if (!show) {
const interval = setInterval(() => setTime(Date.now()), checkSnoozeInterval)
return () => {
clearInterval(interval)
}
}
return () => null
}, [show])

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

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

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

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

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

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

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

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

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

@@ -48,6 +48,7 @@ const AttachmentElement = (props: Props): JSX.Element|null => {
name: block.title,
extension: block.title.split('.').slice(0, -1).join('.'),
})
return
}
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) {
let result = fName.slice(0, 15)
result += '...'
return result
}
return fName
}
setFileName(generateFileName(fileInfo.name))

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

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

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

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

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

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

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

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

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

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

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

@@ -2,15 +2,14 @@
// See LICENSE.txt for license information.
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 {mocked} from 'jest-mock'
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'

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

@@ -3,7 +3,7 @@
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 {Provider as ReduxProvider} from 'react-redux'
@@ -11,7 +11,7 @@ import userEvent from '@testing-library/user-event'
import {Utils} from 'src/utils'
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'

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -2,7 +2,7 @@
// See LICENSE.txt for license information.
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'
@@ -10,7 +10,7 @@ import userEvent from '@testing-library/user-event'
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'

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

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

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