Repository: exadel-inc/esl
Pull request #395 (2021-08-17 15:55:24)

Feat/add arr css class utils

github-actions[bot]: ****CLA Assistant Lite bot**** All contributors have signed the CLA  ✍️ 

Patch:
diff --git a/.eslintrc.json b/.eslintrc.json
index c8c84dfc4..3152ede05 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -7,7 +7,11 @@
   "extends": [
     "eslint:recommended",
     "plugin:import/typescript",
-    "plugin:@typescript-eslint/recommended"
+    "plugin:@typescript-eslint/recommended",
+    "./eslint/codestyle.eslintrc.yml",
+    "./eslint/coderules.eslintrc.yml",
+    "./eslint/import.eslintrc.yml",
+    "./eslint/sonarjs.eslintrc.yml"
   ],
   "parser": "@typescript-eslint/parser",
   "parserOptions": {
@@ -22,7 +26,6 @@
     "eslint-plugin-tsdoc"
   ],
   "rules": {
-    "comma-dangle": "off",
     "complexity": "warn",
     "constructor-super": "warn",
     "curly": ["warn", "multi-line"],
@@ -42,17 +45,14 @@
     "no-empty": "warn",
     "no-eval": "off",
     "no-fallthrough": "off",
-    "no-invalid-this": "off",
     "no-mixed-spaces-and-tabs": "error",
     "no-multiple-empty-lines": "off",
     "no-new-wrappers": "warn",
-    "no-shadow": ["off"],
     "no-throw-literal": "warn",
     "no-trailing-spaces": "warn",
     "no-undef-init": "warn",
     "no-underscore-dangle": "off",
     "no-unsafe-finally": "warn",
-    "no-unused-expressions": "off",
     "no-labels": "warn",
     "no-var": "error",
     "object-shorthand": "warn",
@@ -60,85 +60,14 @@
     "prefer-arrow/prefer-arrow-functions": "off",
     "prefer-const": "error",
     "radix": "warn",
-    "semi": ["error", "always"],
     "semi-style": ["error", "last"],
-    "space-before-function-paren": [
-      "warn",
-      {
-        "anonymous": "always",
-        "named": "never",
-        "asyncArrow": "always"
-      }
-    ],
     "spaced-comment": "warn",
     "use-isnan": "error",
     "valid-typeof": "error",
+    "space-in-parens": ["warn", "never"],
 
     "tsdoc/syntax": "warn",
 
-    "@typescript-eslint/consistent-type-imports": ["error", { "prefer": "type-imports" }],
-    "@typescript-eslint/object-curly-spacing": ["warn",  "never"],
-    "@typescript-eslint/explicit-function-return-type": "off",
-
-    "@typescript-eslint/adjacent-overload-signatures": "warn",
-    "@typescript-eslint/array-type": "warn",
-    "@typescript-eslint/ban-types": "warn",
-    "@typescript-eslint/ban-ts-comment": "warn",
-    "@typescript-eslint/no-inferrable-types": "off",
-    "@typescript-eslint/member-ordering": "off",
-    "@typescript-eslint/no-this-alias": "error",
-    "@typescript-eslint/no-empty-function": "off",
-    "@typescript-eslint/no-empty-interface": "off",
-    "@typescript-eslint/prefer-as-const": "error",
-    "@typescript-eslint/no-extra-semi": "error",
-    "@typescript-eslint/no-explicit-any": "off",
-    "@typescript-eslint/no-misused-new": "warn",
-    "@typescript-eslint/no-namespace": "error",
-    "@typescript-eslint/no-parameter-properties": "off",
-    "@typescript-eslint/no-unused-vars": "off",
-    "@typescript-eslint/no-use-before-define": "off",
-    "@typescript-eslint/no-var-requires": "error",
-    "@typescript-eslint/no-extra-non-null-assertion": "error",
-    "@typescript-eslint/no-non-null-assertion": "off",
-    "@typescript-eslint/no-non-null-asserted-optional-chain": "error",
-    "@typescript-eslint/prefer-for-of": "off",
-    "@typescript-eslint/prefer-function-type": "warn",
-    "@typescript-eslint/prefer-namespace-keyword": "warn",
-    "@typescript-eslint/quotes": ["error", "single"],
-    "@typescript-eslint/semi": ["error"],
-    "@typescript-eslint/triple-slash-reference": "warn",
-    "@typescript-eslint/unified-signatures": "warn",
-    "@typescript-eslint/explicit-module-boundary-types": "off",
-    "@typescript-eslint/no-for-in-array": "error",
-    "@typescript-eslint/no-unsafe-assignment": "off",
-    "@typescript-eslint/no-unnecessary-type-assertion": "warn",
-    "@typescript-eslint/no-shadow": "warn",
-
-    "import/order": ["warn", {"groups": ["builtin", "external", "parent", "sibling", "index", "object", "type"]}],
-    "import/export": "warn",
-    "import/named": "error",
-    "import/no-default-export": "warn",
-    "import/no-self-import": "error",
-
-    "sonarjs/cognitive-complexity": "error",
-    "sonarjs/max-switch-cases": ["error", 8],
-    "sonarjs/no-collapsible-if": "error",
-    "sonarjs/no-duplicated-branches": "error",
-    "sonarjs/no-gratuitous-expressions": "warn",
-    "sonarjs/no-identical-functions": "warn",
-    "sonarjs/no-inverted-boolean-check": "warn",
-    "sonarjs/no-nested-switch": "error",
-    "sonarjs/no-redundant-boolean": "error",
-    "sonarjs/no-redundant-jump": "error",
-    "sonarjs/no-same-line-conditional": "error",
-    "sonarjs/no-small-switch": "warn",
-    "sonarjs/no-unused-collection": "error",
-    "sonarjs/no-useless-catch": "error",
-    "sonarjs/prefer-single-boolean-return": "error",
-    "sonarjs/prefer-while": "error",
-    "sonarjs/prefer-object-literal": "warn",
-    "sonarjs/prefer-immediate-return": "warn",
-
     "editorconfig/charset": "warn",
     "editorconfig/eol-last": "warn",
     "editorconfig/no-trailing-spaces": "warn"
diff --git a/eslint/coderules.eslintrc.yml b/eslint/coderules.eslintrc.yml
new file mode 100644
index 000000000..9a1707a32
--- /dev/null
+++ b/eslint/coderules.eslintrc.yml
@@ -0,0 +1,282 @@
+rules:
+  # === Compulsory practices ===
+
+  # Enforces consistent usage of type imports
+  '@typescript-eslint/consistent-type-imports':
+    - error
+    - prefer: type-imports
+
+  # Disallow the use of custom TypeScript modules and namespaces
+  '@typescript-eslint/no-namespace': error
+
+  # Disallows using a non-null assertion after an optional chain expression
+  '@typescript-eslint/no-non-null-asserted-optional-chain': error
+
+  # Disallows invocation of require()
+  '@typescript-eslint/no-require-imports': error
+
+  # Disallow aliasing this
+  '@typescript-eslint/no-this-alias': error
+
+  # Flags unnecessary equality comparisons against boolean literals
+  '@typescript-eslint/no-unnecessary-boolean-literal-compare': error
+
+  # Disallows the use of require statements except in import statements
+  '@typescript-eslint/no-var-requires': error
+
+  # Prefer usage of as const over literal type
+  '@typescript-eslint/prefer-as-const': error
+
+  # Disallow iterating over an array with a for-in loop
+  '@typescript-eslint/no-for-in-array': error
+
+  # Disallow extra non-null assertion
+  '@typescript-eslint/no-extra-non-null-assertion': error
+
+  # Enforce default parameters to be last
+  'default-param-last': off
+  '@typescript-eslint/default-param-last': error
+
+  # === Warnings ===
+
+  # Require that member overloads be consecutive
+  '@typescript-eslint/adjacent-overload-signatures': warn
+
+  # Requires using either T[] or Array<T> for arrays
+  '@typescript-eslint/array-type': warn
+
+  # Disallows awaiting a value that is not a Thenable
+
+  '@typescript-eslint/await-thenable': warn
+
+  # Bans @ts-<directive> comments from being used or requires descriptions after directive
+  '@typescript-eslint/ban-ts-comment': warn
+
+  # Bans specific types from being used
+  '@typescript-eslint/ban-types': warn
+
+  # Enforce valid definition of new and constructor
+  '@typescript-eslint/no-misused-new': warn
+
+  # Warns when a namespace qualifier is unnecessary
+  '@typescript-eslint/no-unnecessary-qualifier': warn
+
+  # Use function types instead of interfaces with call signatures
+  '@typescript-eslint/prefer-function-type': warn
+
+  # Sets preference level for triple slash directives versus ES6-style import declarations
+  '@typescript-eslint/triple-slash-reference': warn
+
+  # Warns for any two overloads that could be unified into one by using a union or an optional/rest parameter
+  '@typescript-eslint/unified-signatures': warn
+
+  # Warns if a type assertion does not change the type of an expression
+  '@typescript-eslint/no-unnecessary-type-assertion': warn
+
+  # === Disabled Rules ===
+
+  # Disallow the delete operator with computed key expressions
+  '@typescript-eslint/no-dynamic-delete': off
+
+  # Disallow the use of parameter properties in class constructors
+  '@typescript-eslint/no-parameter-properties': off
+
+  # Disallow the use of type aliases
+  '@typescript-eslint/no-type-alias': off
+
+  # Prefer a ‘for-of’ loop over a standard ‘for’ loop if the index is only used to access the array being iterated
+  '@typescript-eslint/prefer-for-of': off
+
+  # When adding two variables, operands must both be of type number or of type string
+  '@typescript-eslint/restrict-plus-operands': off
+
+  # Restricts the types allowed in boolean expressions
+  '@typescript-eslint/strict-boolean-expressions': off
+
+  # Disallow the declaration of empty interfaces
+  '@typescript-eslint/no-empty-interface': off
+
+  # Disallow empty functions
+  'no-empty-function': off
+  '@typescript-eslint/no-empty-function': off
+
+  # Disallow usage of the any type
+  '@typescript-eslint/no-explicit-any': off
+
+  # Disallows non-null assertions using the ! postfix operator
+  '@typescript-eslint/no-non-null-assertion': off
+
+  # Ensures that literals on classes are exposed in a consistent style
+  '@typescript-eslint/class-literal-property-style': off
+
+  # Enforce or disallow the use of the record type
+  '@typescript-eslint/consistent-indexed-object-style': off
+
+  # Consistent with type definition either interface or type
+  '@typescript-eslint/consistent-type-definitions': off
+
+  # Require explicit return types on functions and class methods
+  '@typescript-eslint/explicit-function-return-type': off
+
+  # Require explicit accessibility modifiers on class properties and methods
+  '@typescript-eslint/explicit-member-accessibility': off
+
+  # Require a consistent member declaration order
+  'member-ordering': off
+  '@typescript-eslint/member-ordering': off
+
+  # Enforces using a particular method signature syntax
+  '@typescript-eslint/method-signature-style': off
+
+  # Requires that .toString() is only called on objects which provide useful information when stringified
+  '@typescript-eslint/no-base-to-string': off
+
+  # Disallow non-null assertion in locations that may be confusing
+  '@typescript-eslint/no-confusing-non-null-assertion': off
+
+  # Requires expressions of type void to appear in statement position
+  '@typescript-eslint/no-confusing-void-expression': off
+
+  # Forbids the use of classes as namespaces
+  '@typescript-eslint/no-extraneous-class': off
+
+  # Requires Promise-like values to be handled appropriately
+  # TODO: enable
+  '@typescript-eslint/no-floating-promises': off
+
+  # Disallow usage of the implicit any type in catch clauses
+  # TODO: enable
+  '@typescript-eslint/no-implicit-any-catch': off
+
+  # Disallows explicit type declarations for variables or parameters initialized to a number, string, or boolean
+  # TODO: review
+  '@typescript-eslint/no-inferrable-types': off
+
+  # Disallows usage of void type outside of generic or return types
+  # TODO: review
+  '@typescript-eslint/no-invalid-void-type': off
+
+  # Avoid using promises in places not designed to handle them
+  # TODO: review
+  '@typescript-eslint/no-misused-promises': off
+
+  # Enforces that type arguments will not be used if not required
+  # TODO: review
+  '@typescript-eslint/no-unnecessary-type-arguments': off
+
+  # Disallows unnecessary constraints on generic types
+  # TODO: review
+  '@typescript-eslint/no-unnecessary-type-constraint': off
+
+  # Disallows calling an function with an any type value
+  # TODO: enable
+  '@typescript-eslint/no-unsafe-argument': off
+
+  # Disallows assigning any to variables and properties
+  '@typescript-eslint/no-unsafe-assignment': off
+
+  # Disallows calling an any type value
+  # TODO: enable
+  '@typescript-eslint/no-unsafe-call': off
+
+  # Disallows member access on any typed variables
+  # TODO: enable
+  '@typescript-eslint/no-unsafe-member-access': off
+
+  # Disallows returning any from a function
+  # TODO: enable
+  '@typescript-eslint/no-unsafe-return': off
+
+  # Prefers a non-null assertion over explicit type cast when possible
+  # TODO: review
+  '@typescript-eslint/non-nullable-type-assertion-style': off
+
+  # Prefer initializing each enums member value
+  # TODO: review
+  '@typescript-eslint/prefer-enum-initializers': off
+
+  # Require that all enum members be literal values to prevent unintended enum member name shadow issues
+  # TODO: review
+  '@typescript-eslint/prefer-literal-enum-member': off
+
+  # Enforce the usage of the nullish coalescing operator instead of logical chaining
+  # TODO: review
+  '@typescript-eslint/prefer-nullish-coalescing': off
+
+  # Prefer using concise optional chain expressions instead of chained logical ands
+  # TODO: review
+  '@typescript-eslint/prefer-optional-chain': off
+
+  # Requires that private members are marked as readonly if they're never modified outside of the constructor
+  # TODO: review
+  '@typescript-eslint/prefer-readonly': off
+
+  # Requires that function parameters are typed as readonly to prevent accidental mutation of inputs
+  # TODO: review
+  '@typescript-eslint/prefer-readonly-parameter-types': off
+
+  # Prefer using type parameter when calling Array#reduce instead of casting
+  # TODO: review
+  '@typescript-eslint/prefer-reduce-type-parameter': off
+
+  # Enforce that RegExp#exec is used instead of String#match if no global flag is provided
+  # TODO: review
+  '@typescript-eslint/prefer-regexp-exec': off
+
+  # Enforce that this is used when only this type is returned
+  '@typescript-eslint/prefer-return-this-type': off
+
+  # Enforce the use of String#startsWith and String#endsWith instead of other equivalent methods of checking substrings
+  # TODO: review
+  '@typescript-eslint/prefer-string-starts-ends-with': off
+
+  # Recommends using @ts-expect-error over @ts-ignore
+  # TODO: review
+  '@typescript-eslint/prefer-ts-expect-error': off
+
+  # Requires any function or method that returns a Promise to be marked async
+  # TODO: review
+  '@typescript-eslint/promise-function-async': off
+
+  # Requires Array#sort calls to always provide a compareFunction
+  # TODO: review
+  '@typescript-eslint/require-array-sort-compare': off
+
+  # Enforce template literal expressions to be of string type
+  # TODO: review
+  '@typescript-eslint/restrict-template-expressions': off
+
+  # Enforces that members of a type union/intersection are sorted alphabetically
+  # TODO: review
+  '@typescript-eslint/sort-type-union-intersection-members': off
+
+  # Exhaustiveness checking in switch with union type
+  # TODO: review
+  '@typescript-eslint/switch-exhaustiveness-check': off
+
+  # Requires type annotations to exist
+  # TODO: review
+  '@typescript-eslint/typedef': off
+
+  # Enforces unbound methods are called with their expected scope
+  # TODO: review
+  '@typescript-eslint/unbound-method': off
+
+  # Require explicit return and argument types on exported functions' and classes' public class methods
+  '@typescript-eslint/explicit-module-boundary-types': off
+
+  # Prevents conditionals where the type is always truthy or always falsy
+  '@typescript-eslint/no-unnecessary-condition': off
+
+  # Enforce includes method over indexOf method
+  '@typescript-eslint/prefer-includes': off
+
+  # Disallow generic Array constructors
+  'no-array-constructor': off
+  '@typescript-eslint/no-array-constructor': off
+
+  # Disallow the use of variables before they are defined
+  'no-use-before-define': off
+  '@typescript-eslint/no-use-before-define': off
+
+
diff --git a/eslint/codestyle.eslintrc.yml b/eslint/codestyle.eslintrc.yml
new file mode 100644
index 000000000..d3b270122
--- /dev/null
+++ b/eslint/codestyle.eslintrc.yml
@@ -0,0 +1,173 @@
+rules:
+  # ==== Spacing | Code style ====
+
+  # Enforce consistent brace style for blocks
+  'brace-style': off
+  '@typescript-eslint/brace-style': warn
+
+  # Enforces consistent spacing before and after commas
+  'comma-dangle': off
+  '@typescript-eslint/comma-spacing': warn
+
+  # Require or disallow trailing comma
+  'comma-spacing': off
+  '@typescript-eslint/comma-dangle': off
+
+  # Enforce dot notation whenever possible
+  'dot-notation': off
+  '@typescript-eslint/dot-notation': off
+
+  # Require consistent spacing around type annotations
+  '@typescript-eslint/type-annotation-spacing': warn
+
+  # Enforces consistent usage of type assertions
+  '@typescript-eslint/consistent-type-assertions': warn
+
+  # Require a semicolon member delimiter style for interfaces and type literals
+  '@typescript-eslint/member-delimiter-style':
+    - warn
+    - multiline:
+        delimiter: 'semi'
+        requireLast: true
+      singleline:
+        delimiter: 'comma'
+        requireLast: false
+
+  # Require the use of the namespace keyword instead of the module keyword to declare custom TypeScript modules
+  '@typescript-eslint/prefer-namespace-keyword': warn
+
+  # Enforces naming conventions for everything across a codebase
+  # TODO: enable
+  '@typescript-eslint/naming-convention': off
+
+  # Require or disallow spacing between function identifiers and their invocations
+  'func-call-spacing': off
+  '@typescript-eslint/func-call-spacing':
+    - warn
+    - never
+
+  # Enforce consistent indentation
+  'indent': off
+  '@typescript-eslint/indent':
+    - warn
+    - 2
+    - ignoredNodes: [FunctionExpression, FunctionDeclaration]
+      SwitchCase: 1
+      ObjectExpression: 1
+      ImportDeclaration: 1
+      flatTernaryExpressions: false
+
+  # Require or disallow initialization in variable declarations
+  'init-declarations': off
+  '@typescript-eslint/init-declarations': off
+
+  # Enforce consistent spacing before and after keywords
+  'keyword-spacing': off
+  '@typescript-eslint/keyword-spacing': warn
+
+  # Require or disallow an empty line between class members
+  'lines-between-class-members': off
+  '@typescript-eslint/lines-between-class-members': off
+
+  # Disallow duplicate class members
+  'no-dupe-class-members': off
+  '@typescript-eslint/no-dupe-class-members': warn
+
+  # Disallow duplicate imports
+  'no-duplicate-imports': off
+  '@typescript-eslint/no-duplicate-imports': warn
+
+  # Disallow unnecessary parentheses
+  'no-extra-parens': off
+  '@typescript-eslint/no-extra-parens': off
+
+  # Disallow unnecessary semicolons
+  'no-extra-semi': off
+  '@typescript-eslint/no-extra-semi': warn
+
+  # Disallow the use of eval()-like methods
+  'no-implied-eval': off
+  '@typescript-eslint/no-implied-eval': warn
+
+  # Disallow this keywords outside of classes or class-like objects
+  'no-invalid-this': off
+  '@typescript-eslint/no-invalid-this': off
+
+  # Disallow function declarations that contain unsafe references inside loop statements
+  'no-loop-func': off
+  '@typescript-eslint/no-loop-func': warn
+
+  # Disallow literal numbers that lose precision
+  'no-loss-of-precision': off
+  '@typescript-eslint/no-loss-of-precision': warn
+
+  # Disallow magic numbers
+  # TODO: enable
+  'no-magic-numbers': off
+  '@typescript-eslint/no-magic-numbers': off
+
+  # Disallow variable redeclaration
+  'no-redeclare': off
+  '@typescript-eslint/no-redeclare': warn
+
+  # Disallow variable declarations from shadowing variables declared in the outer scope
+  'no-shadow': off
+  '@typescript-eslint/no-shadow': warn
+
+  # Disallow throwing literals as exceptions
+  'no-throw-literal': off
+  '@typescript-eslint/no-throw-literal': warn
+
+  # Disallow unused expressions
+  'no-unused-expressions': off
+  '@typescript-eslint/no-unused-expressions': off
+
+  # Disallow unused variables
+  'no-unused-vars': off
+  '@typescript-eslint/no-unused-vars':
+    - warn
+    - vars: all
+      args: none
+
+  # Disallow unnecessary constructors
+  # TODO: enable
+  'no-useless-constructor': off
+  '@typescript-eslint/no-useless-constructor': off
+
+  # Enforce the consistent use of either backticks, double, or single quotes
+  'quotes': off
+  '@typescript-eslint/quotes':
+    - error
+    - single
+
+  # Disallow async functions which have no await expression
+  'require-await': off
+  '@typescript-eslint/require-await': warn
+
+  # Enforces consistent returning of awaited values
+  'return-await': off
+  '@typescript-eslint/return-await': warn
+
+  # Require or disallow semicolons instead of ASI
+  'semi': off
+  '@typescript-eslint/semi': error
+
+  # Enforces consistent spacing before function parenthesis
+  'space-before-function-paren': off
+  '@typescript-eslint/space-before-function-paren':
+    - warn
+    - anonymous: always
+      named: never
+      asyncArrow: always
+
+  # This rule is aimed at ensuring there are spaces around infix operators.
+  'space-infix-ops': off
+  '@typescript-eslint/space-infix-ops': warn
+
+  # Enforce consistent spacing inside braces
+  'object-curly-spacing': off
+  '@typescript-eslint/object-curly-spacing':
+    - warn
+    - never
+
+
diff --git a/eslint/import.eslintrc.yml b/eslint/import.eslintrc.yml
new file mode 100644
index 000000000..c680b8139
--- /dev/null
+++ b/eslint/import.eslintrc.yml
@@ -0,0 +1,24 @@
+rules:
+  # Enforce a convention in module import order
+  'import/order':
+    - warn
+    - groups:
+      - builtin
+      - external
+      - parent
+      - sibling
+      - index
+      - object
+      - type
+
+  # Reports funny business with exports, like repeated exports of names or defaults.
+  'import/export': warn
+
+  # Verifies that all named imports are part of the set of named exports in the referenced module.
+  'import/named': error
+
+  # Prohibit default exports
+  'import/no-default-export': warn
+
+  # Forbid a module from importing itself
+  'import/no-self-import': error
diff --git a/eslint/sonarjs.eslintrc.yml b/eslint/sonarjs.eslintrc.yml
new file mode 100644
index 000000000..e45516819
--- /dev/null
+++ b/eslint/sonarjs.eslintrc.yml
@@ -0,0 +1,60 @@
+rules:
+  # Cognitive Complexity is a measure of how hard the control flow of a function is to understand.
+  # Functions with high Cognitive Complexity will be difficult to maintain.
+  'sonarjs/cognitive-complexity': error
+
+  # Limits maximum of switch cases before suggest a map
+  'sonarjs/max-switch-cases':
+    - error
+    - 6
+
+  # Merging collapsible if statements increases the code's readability.
+  'sonarjs/no-collapsible-if': error
+
+  # Having all branches in a switch or if chain with the same implementation is an error.
+  'sonarjs/no-duplicated-branches': error
+
+  # If a boolean expression doesn’t change the evaluation of the condition,
+  # then it is entirely unnecessary, and can be removed.
+  'sonarjs/no-gratuitous-expressions': warn
+
+  # Disallows identical functions
+  'sonarjs/no-identical-functions': warn
+
+  # It is needlessly complex to invert the result of a boolean comparison.
+  # The opposite comparison should be made instead.
+  'sonarjs/no-inverted-boolean-check': warn
+
+  # Disallows nested switch structures as they are difficult to understand
+  'sonarjs/no-nested-switch': error
+
+  # Redundant Boolean literals should be removed from expressions to improve readability.
+  'sonarjs/no-redundant-boolean': error
+
+  # Disallows redundant jump statements, such as return, break and continue
+  'sonarjs/no-redundant-jump': error
+
+  # Code is clearest when each statement has its own line.
+  'sonarjs/no-same-line-conditional': error
+
+  # For just one or two cases however, the code will be more readable with if statements.
+  'sonarjs/no-small-switch': warn
+
+  # This rule raises an issue when no methods are called on a collection other than those that add or remove values.
+  'sonarjs/no-unused-collection': error
+
+  # Disallows empty catch statements
+  'sonarjs/no-useless-catch': error
+
+  # Return of boolean literal statements wrapped into if-then-else ones should be simplified.
+  'sonarjs/prefer-single-boolean-return': error
+
+  # When only the condition expression is defined in a for loop, and the initialization and
+  # increment expressions are missing, a while loop should be used instead to increase readability.
+  'sonarjs/prefer-while': error
+
+  # Prefer creating a literal instead of fill the object "key by key"
+  'sonarjs/prefer-object-literal': warn
+
+  # Declaring a variable only to immediately return or throw it is a bad practice.
+  'sonarjs/prefer-immediate-return': warn
diff --git a/package-lock.json b/package-lock.json
index cf5e2b822..75203f029 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2255,13 +2255,13 @@
       "integrity": "sha512-bvBETO6kkqq32Yuk5CI3k0V/UCOTzhsqImJsboABsnCUA8BzawvnrgaRe51wqXQPqZkTwLFkRvhS7iSvsgp4pg=="
     },
     "@typescript-eslint/eslint-plugin": {
-      "version": "4.29.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.29.1.tgz",
-      "integrity": "sha512-AHqIU+SqZZgBEiWOrtN94ldR3ZUABV5dUG94j8Nms9rQnHFc8fvDOue/58K4CFz6r8OtDDc35Pw9NQPWo0Ayrw==",
+      "version": "4.29.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.29.2.tgz",
+      "integrity": "sha512-x4EMgn4BTfVd9+Z+r+6rmWxoAzBaapt4QFqE+d8L8sUtYZYLDTK6VG/y/SMMWA5t1/BVU5Kf+20rX4PtWzUYZg==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/experimental-utils": "4.29.1",
-        "@typescript-eslint/scope-manager": "4.29.1",
+        "@typescript-eslint/experimental-utils": "4.29.2",
+        "@typescript-eslint/scope-manager": "4.29.2",
         "debug": "^4.3.1",
         "functional-red-black-tree": "^1.0.1",
         "regexpp": "^3.1.0",
@@ -2287,28 +2287,28 @@
       }
     },
     "@typescript-eslint/experimental-utils": {
-      "version": "4.29.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.29.1.tgz",
-      "integrity": "sha512-kl6QG6qpzZthfd2bzPNSJB2YcZpNOrP6r9jueXupcZHnL74WiuSjaft7WSu17J9+ae9zTlk0KJMXPUj0daBxMw==",
+      "version": "4.29.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.29.2.tgz",
+      "integrity": "sha512-P6mn4pqObhftBBPAv4GQtEK7Yos1fz/MlpT7+YjH9fTxZcALbiiPKuSIfYP/j13CeOjfq8/fr9Thr2glM9ub7A==",
       "dev": true,
       "requires": {
         "@types/json-schema": "^7.0.7",
-        "@typescript-eslint/scope-manager": "4.29.1",
-        "@typescript-eslint/types": "4.29.1",
-        "@typescript-eslint/typescript-estree": "4.29.1",
+        "@typescript-eslint/scope-manager": "4.29.2",
+        "@typescript-eslint/types": "4.29.2",
+        "@typescript-eslint/typescript-estree": "4.29.2",
         "eslint-scope": "^5.1.1",
         "eslint-utils": "^3.0.0"
       }
     },
     "@typescript-eslint/parser": {
-      "version": "4.29.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.29.1.tgz",
-      "integrity": "sha512-3fL5iN20hzX3Q4OkG7QEPFjZV2qsVGiDhEwwh+EkmE/w7oteiOvUNzmpu5eSwGJX/anCryONltJ3WDmAzAoCMg==",
+      "version": "4.29.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.29.2.tgz",
+      "integrity": "sha512-WQ6BPf+lNuwteUuyk1jD/aHKqMQ9jrdCn7Gxt9vvBnzbpj7aWEf+aZsJ1zvTjx5zFxGCt000lsbD9tQPEL8u6g==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/scope-manager": "4.29.1",
-        "@typescript-eslint/types": "4.29.1",
-        "@typescript-eslint/typescript-estree": "4.29.1",
+        "@typescript-eslint/scope-manager": "4.29.2",
+        "@typescript-eslint/types": "4.29.2",
+        "@typescript-eslint/typescript-estree": "4.29.2",
         "debug": "^4.3.1"
       },
       "dependencies": {
@@ -2330,29 +2330,29 @@
       }
     },
     "@typescript-eslint/scope-manager": {
-      "version": "4.29.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.29.1.tgz",
-      "integrity": "sha512-Hzv/uZOa9zrD/W5mftZa54Jd5Fed3tL6b4HeaOpwVSabJK8CJ+2MkDasnX/XK4rqP5ZTWngK1ZDeCi6EnxPQ7A==",
+      "version": "4.29.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.29.2.tgz",
+      "integrity": "sha512-mfHmvlQxmfkU8D55CkZO2sQOueTxLqGvzV+mG6S/6fIunDiD2ouwsAoiYCZYDDK73QCibYjIZmGhpvKwAB5BOA==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/types": "4.29.1",
-        "@typescript-eslint/visitor-keys": "4.29.1"
+        "@typescript-eslint/types": "4.29.2",
+        "@typescript-eslint/visitor-keys": "4.29.2"
       }
     },
     "@typescript-eslint/types": {
-      "version": "4.29.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.29.1.tgz",
-      "integrity": "sha512-Jj2yu78IRfw4nlaLtKjVaGaxh/6FhofmQ/j8v3NXmAiKafbIqtAPnKYrf0sbGjKdj0hS316J8WhnGnErbJ4RCA==",
+      "version": "4.29.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.29.2.tgz",
+      "integrity": "sha512-K6ApnEXId+WTGxqnda8z4LhNMa/pZmbTFkDxEBLQAbhLZL50DjeY0VIDCml/0Y3FlcbqXZrABqrcKxq+n0LwzQ==",
       "dev": true
     },
     "@typescript-eslint/typescript-estree": {
-      "version": "4.29.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.29.1.tgz",
-      "integrity": "sha512-lIkkrR9E4lwZkzPiRDNq0xdC3f2iVCUjw/7WPJ4S2Sl6C3nRWkeE1YXCQ0+KsiaQRbpY16jNaokdWnm9aUIsfw==",
+      "version": "4.29.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.29.2.tgz",
+      "integrity": "sha512-TJ0/hEnYxapYn9SGn3dCnETO0r+MjaxtlWZ2xU+EvytF0g4CqTpZL48SqSNn2hXsPolnewF30pdzR9a5Lj3DNg==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/types": "4.29.1",
-        "@typescript-eslint/visitor-keys": "4.29.1",
+        "@typescript-eslint/types": "4.29.2",
+        "@typescript-eslint/visitor-keys": "4.29.2",
         "debug": "^4.3.1",
         "globby": "^11.0.3",
         "is-glob": "^4.0.1",
@@ -2378,12 +2378,12 @@
       }
     },
     "@typescript-eslint/visitor-keys": {
-      "version": "4.29.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.29.1.tgz",
-      "integrity": "sha512-zLqtjMoXvgdZY/PG6gqA73V8BjqPs4af1v2kiiETBObp+uC6gRYnJLmJHxC0QyUrrHDLJPIWNYxoBV3wbcRlag==",
+      "version": "4.29.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.29.2.tgz",
+      "integrity": "sha512-bDgJLQ86oWHJoZ1ai4TZdgXzJxsea3Ee9u9wsTAvjChdj2WLcVsgWYAPeY7RQMn16tKrlQaBnpKv7KBfs4EQag==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/types": "4.29.1",
+        "@typescript-eslint/types": "4.29.2",
         "eslint-visitor-keys": "^2.0.0"
       }
     },
@@ -2561,9 +2561,9 @@
       }
     },
     "@webpack-cli/serve": {
-      "version": "1.5.1",
-      "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.5.1.tgz",
-      "integrity": "sha512-4vSVUiOPJLmr45S8rMGy7WDvpWxfFxfP/Qx/cxZFCfvoypTYpPPL1X8VIZMe0WTA+Jr7blUxwUSEZNkjoMTgSw==",
+      "version": "1.5.2",
+      "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.5.2.tgz",
+      "integrity": "sha512-vgJ5OLWadI8aKjDlOH3rb+dYyPd2GTZuQC/Tihjct6F9GpXGZINo3Y/IVuZVTM1eDQB+/AOsjPUWH/WySDaXvw==",
       "dev": true
     },
     "@xtuc/ieee754": {
@@ -8415,28 +8415,6 @@
         "picomatch": "^2.2.3"
       },
       "dependencies": {
-        "@jest/types": {
-          "version": "27.0.6",
-          "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz",
-          "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==",
-          "dev": true,
-          "requires": {
-            "@types/istanbul-lib-coverage": "^2.0.0",
-            "@types/istanbul-reports": "^3.0.0",
-            "@types/node": "*",
-            "@types/yargs": "^16.0.0",
-            "chalk": "^4.0.0"
-          }
-        },
-        "@types/yargs": {
-          "version": "16.0.4",
-          "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz",
-          "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==",
-          "dev": true,
-          "requires": {
-            "@types/yargs-parser": "*"
-          }
-        },
         "picomatch": {
           "version": "2.3.0",
           "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
@@ -9095,12 +9073,6 @@
       "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=",
       "dev": true
     },
-    "lodash.toarray": {
-      "version": "4.4.0",
-      "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz",
-      "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=",
-      "dev": true
-    },
     "lodash.truncate": {
       "version": "4.4.2",
       "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz",
@@ -9267,9 +9239,9 @@
       }
     },
     "marked": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/marked/-/marked-2.1.0.tgz",
-      "integrity": "sha512-KSdOTs1I/+hrQrFB+e7O/r+AorwdD4XzAi9caoRrbbkRLUS6txr1MZCZQhimm1N5Pg7A/WazC9VrYgqnwGiXMg==",
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/marked/-/marked-3.0.0.tgz",
+      "integrity": "sha512-IF2MYfFafPsLIhzLTu63secRBwOmIY+vwS+ei6qg8F+bTS+MxH6ONYRmuseGdZqF44qvoi3nP/rlpClBdgLbiQ==",
       "dev": true
     },
     "marked-terminal": {
@@ -9643,12 +9615,12 @@
       }
     },
     "node-emoji": {
-      "version": "1.10.0",
-      "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz",
-      "integrity": "sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw==",
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz",
+      "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==",
       "dev": true,
       "requires": {
-        "lodash.toarray": "^4.4.0"
+        "lodash": "^4.17.21"
       }
     },
     "node-fetch": {
@@ -13286,9 +13258,9 @@
       }
     },
     "rechoir": {
-      "version": "0.7.0",
-      "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.0.tgz",
-      "integrity": "sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q==",
+      "version": "0.7.1",
+      "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz",
+      "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==",
       "dev": true,
       "requires": {
         "resolve": "^1.9.0"
@@ -13663,9 +13635,9 @@
       }
     },
     "semantic-release": {
-      "version": "17.4.4",
-      "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-17.4.4.tgz",
-      "integrity": "sha512-fQIA0lw2Sy/9+TcoM/BxyzKCSwdUd8EPRwGoOuBLgxKigPCY6kaKs8TOsgUVy6QrlTYwni2yzbMb5Q2107P9eA==",
+      "version": "17.4.5",
+      "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-17.4.5.tgz",
+      "integrity": "sha512-s9JqU46nK1TQla37+kA7aSoNWDCkQq9/T5K2QX/z7bfI+zW3o1Pg/2nlZuZffnnROnt5sT0ysE49yhedINTtTw==",
       "dev": true,
       "requires": {
         "@semantic-release/commit-analyzer": "^8.0.0",
@@ -13685,7 +13657,7 @@
         "hook-std": "^2.0.0",
         "hosted-git-info": "^4.0.0",
         "lodash": "^4.17.21",
-        "marked": "^2.0.0",
+        "marked": "^3.0.0",
         "marked-terminal": "^4.1.1",
         "micromatch": "^4.0.2",
         "p-each-series": "^2.1.0",
@@ -13699,9 +13671,9 @@
       },
       "dependencies": {
         "debug": {
-          "version": "4.3.1",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
-          "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+          "version": "4.3.2",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
+          "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
           "dev": true,
           "requires": {
             "ms": "2.1.2"
@@ -15161,32 +15133,19 @@
       "dev": true
     },
     "ts-jest": {
-      "version": "27.0.4",
-      "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-27.0.4.tgz",
-      "integrity": "sha512-c4E1ECy9Xz2WGfTMyHbSaArlIva7Wi2p43QOMmCqjSSjHP06KXv+aT+eSY+yZMuqsMi3k7pyGsGj2q5oSl5WfQ==",
+      "version": "27.0.5",
+      "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-27.0.5.tgz",
+      "integrity": "sha512-lIJApzfTaSSbtlksfFNHkWOzLJuuSm4faFAfo5kvzOiRAuoN4/eKxVJ2zEAho8aecE04qX6K1pAzfH5QHL1/8w==",
       "dev": true,
       "requires": {
         "bs-logger": "0.x",
-        "buffer-from": "1.x",
         "fast-json-stable-stringify": "2.x",
         "jest-util": "^27.0.0",
         "json5": "2.x",
         "lodash": "4.x",
         "make-error": "1.x",
-        "mkdirp": "1.x",
         "semver": "7.x",
         "yargs-parser": "20.x"
-      },
-      "dependencies": {
-        "json5": {
-          "version": "2.2.0",
-          "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
-          "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==",
-          "dev": true,
-          "requires": {
-            "minimist": "^1.2.5"
-          }
-        }
       }
     },
     "ts-loader": {
@@ -15578,15 +15537,15 @@
       }
     },
     "webpack-cli": {
-      "version": "4.7.2",
-      "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.7.2.tgz",
-      "integrity": "sha512-mEoLmnmOIZQNiRl0ebnjzQ74Hk0iKS5SiEEnpq3dRezoyR3yPaeQZCMCe+db4524pj1Pd5ghZXjT41KLzIhSLw==",
+      "version": "4.8.0",
+      "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.8.0.tgz",
+      "integrity": "sha512-+iBSWsX16uVna5aAYN6/wjhJy1q/GKk4KjKvfg90/6hykCTSgozbfz5iRgDTSJt/LgSbYxdBX3KBHeobIs+ZEw==",
       "dev": true,
       "requires": {
         "@discoveryjs/json-ext": "^0.5.0",
         "@webpack-cli/configtest": "^1.0.4",
         "@webpack-cli/info": "^1.3.0",
-        "@webpack-cli/serve": "^1.5.1",
+        "@webpack-cli/serve": "^1.5.2",
         "colorette": "^1.2.1",
         "commander": "^7.0.0",
         "execa": "^5.0.0",
diff --git a/package.json b/package.json
index a66cd79d3..4e5683cf7 100644
--- a/package.json
+++ b/package.json
@@ -84,8 +84,8 @@
     "@semantic-release/release-notes-generator": "^9.0.3",
     "@types/jest": "^27.0.1",
     "@types/smoothscroll-polyfill": "^0.3.1",
-    "@typescript-eslint/eslint-plugin": "^4.29.1",
-    "@typescript-eslint/parser": "^4.29.1",
+    "@typescript-eslint/eslint-plugin": "^4.29.2",
+    "@typescript-eslint/parser": "^4.29.2",
     "@webcomponents/custom-elements": "1.4.3",
     "@webcomponents/webcomponents-platform": "1.0.1",
     "browser-sync": "^2.27.5",
@@ -109,14 +109,14 @@
     "postcss": "^8.3.6",
     "promise-polyfill": "^8.2.0",
     "rimraf": "^3.0.2",
-    "semantic-release": "^17.4.4",
+    "semantic-release": "^17.4.5",
     "smoothscroll-polyfill": "^0.4.4",
     "stylelint": "^13.13.1",
-    "ts-jest": "^27.0.4",
+    "ts-jest": "^27.0.5",
     "ts-loader": "^9.2.5",
     "typescript": "4.3.5",
     "webpack": "^5.50.0",
-    "webpack-cli": "^4.7.2"
+    "webpack-cli": "^4.8.0"
   },
   "browserslist": [
     "last 3 versions",
diff --git a/pages/views/basic/esl-media.njk b/pages/views/basic/esl-media.njk
index 0eedff2ed..ce5572b05 100644
--- a/pages/views/basic/esl-media.njk
+++ b/pages/views/basic/esl-media.njk
@@ -22,7 +22,7 @@ tags: basic
     </div>
     <div class="demo col-md-6 order-md-1">
       <div class="img-container img-container-16-9 img-container-loaded" style="border: solid black 1px;">
-        <esl-media group="demo-media" controls></esl-media>
+        <esl-media group="demo-media" controls tabindex="0"></esl-media>
       </div>
       <test-media-controls target="[data-demo='0'] esl-media"></test-media-controls>
     </div>
@@ -44,7 +44,8 @@ tags: basic
                    load-cls-declined="hide-video"
                    ready-class="video-ready"
                    ready-class-target="::parent"
-                   controls>
+                   controls
+                   tabindex="0">
         </esl-media>
       </div>
     </div>
@@ -66,7 +67,8 @@ tags: basic
                    load-cls-target="::parent(section)"
                    load-cls-accepted="show-video"
                    load-cls-declined="hide-video"
-                   controls>
+                   controls
+                   tabindex="0">
         </esl-media>
       </div>
     </div>
@@ -84,7 +86,8 @@ tags: basic
       <hr/>
       <div class="img-container img-container-16-9 img-container-loaded">
         <esl-media media-type="iframe"
-                   media-src="https://player.vimeo.com/video/1084537">
+                   media-src="https://player.vimeo.com/video/1084537"
+                   tabindex="0">
         </esl-media>
       </div>
     </div>
@@ -101,7 +104,8 @@ tags: basic
       <esl-media group="demo-media"
                  controls
                  media-type="audio"
-                 media-src="https://html5book.ru/examples/media/track.mp3">
+                 media-src="https://html5book.ru/examples/media/track.mp3"
+                 tabindex="0">
       </esl-media>
     </div>
     <div class="row btn-group-centered">
diff --git a/src/modules/draft/esl-carousel/core/view/esl-multi-carousel-view.ts b/src/modules/draft/esl-carousel/core/view/esl-multi-carousel-view.ts
index 1abdf30c4..b8cc3cf15 100644
--- a/src/modules/draft/esl-carousel/core/view/esl-multi-carousel-view.ts
+++ b/src/modules/draft/esl-carousel/core/view/esl-multi-carousel-view.ts
@@ -3,6 +3,7 @@ import {ESLCarouselView, ESLCarouselViewRegistry} from './esl-carousel-view';
 import type {ESLCarousel} from '../esl-carousel';
 
 class ESLMultiCarouselView extends ESLCarouselView {
+
   constructor(carousel: ESLCarousel) {
     super(carousel);
   }
diff --git a/src/modules/esl-footnotes/core/esl-footnotes-data.ts b/src/modules/esl-footnotes/core/esl-footnotes-data.ts
index 54d71a015..4b0ef4027 100644
--- a/src/modules/esl-footnotes/core/esl-footnotes-data.ts
+++ b/src/modules/esl-footnotes/core/esl-footnotes-data.ts
@@ -24,9 +24,16 @@ export function compileFootnotesGroupedList(notes: ESLNote[]): FootnotesItem[] {
   convertNotesToFootnotesList(notes).forEach(({index, text}) => {
     map.set(text, map.has(text) ? `${map.get(text)}, ${index}` : index);
   });
-  return Array.from(map)
-    .reduce(
-      (list,[text, index]) => [...list, {index, text}],
-      []
-    );
+  const groupedList: FootnotesItem[] = [];
+  map.forEach((index, text) => groupedList.push({index, text}));
+  return groupedList;
+}
+
+/* Sort notes list */
+export function sortFootnotes(notes: ESLNote[]): ESLNote[] {
+  return notes.sort((note1: ESLNote, note2: ESLNote) => {
+    const rect1 = note1.getBoundingClientRect();
+    const rect2 = note2.getBoundingClientRect();
+    return rect1.top - rect2.top || rect1.left - rect2.left;
+  });
 }
diff --git a/src/modules/esl-footnotes/core/esl-footnotes.ts b/src/modules/esl-footnotes/core/esl-footnotes.ts
index a9ca8109e..9fbbe83a1 100644
--- a/src/modules/esl-footnotes/core/esl-footnotes.ts
+++ b/src/modules/esl-footnotes/core/esl-footnotes.ts
@@ -4,7 +4,8 @@ import {memoize} from '../../esl-utils/decorators/memoize';
 import {ESLBaseElement, attr} from '../../esl-base-element/core';
 import {TraversingQuery} from '../../esl-traversing-query/core';
 import {EventUtils} from '../../esl-utils/dom/events';
-import {compileFootnotesGroupedList, compileFootnotesNongroupedList} from './esl-footnotes-data';
+import {sequentialUID} from '../../esl-utils/misc/uid';
+import {compileFootnotesGroupedList, compileFootnotesNongroupedList, sortFootnotes} from './esl-footnotes-data';
 
 import type {ESLNote} from './esl-note';
 import type {FootnotesItem} from './esl-footnotes-data';
@@ -28,11 +29,17 @@ export class ESLFootnotes extends ESLBaseElement {
   }
 
   protected get footnotesList(): FootnotesItem[] {
+    this.reindex();
     return this.grouping !== 'enable'
       ? compileFootnotesNongroupedList(this._notes)
       : compileFootnotesGroupedList(this._notes);
   }
 
+  public reindex() {
+    this._notes = sortFootnotes(this._notes);
+    this._notes.forEach((note, index) => note.setIndex(index + 1));
+  }
+
   protected connectedCallback() {
     super.connectedCallback();
 
@@ -62,7 +69,9 @@ export class ESLFootnotes extends ESLBaseElement {
   }
 
   public linkNote(note: ESLNote) {
-    const index = this._notes.push(note);
+    if (this._notes.includes(note)) return;
+    this._notes.push(note);
+    const index = +sequentialUID(ESLFootnotes.is, '');
     note.link(this, index);
   }
 
diff --git a/src/modules/esl-footnotes/core/esl-note.ts b/src/modules/esl-footnotes/core/esl-note.ts
index 7f4d2a636..c42338c52 100644
--- a/src/modules/esl-footnotes/core/esl-note.ts
+++ b/src/modules/esl-footnotes/core/esl-note.ts
@@ -22,6 +22,9 @@ export class ESLNote extends ESLBaseElement {
   /** Tooltip state marker */
   @boolAttr() public tooltipShown: boolean;
 
+  /** Tooltip content */
+  @attr() public html: string;
+
   /** Click event tracking media query. Default: `all` */
   @attr({defaultValue: 'all'}) public trackClick: string;
   /** Hover event tracking media query. Default: `all` */
@@ -29,7 +32,6 @@ export class ESLNote extends ESLBaseElement {
 
   protected _$footnotes: ESLFootnotes | null;
   protected _index: number;
-  protected _innerHTML: string;
 
   /** Marker to allow track hover */
   public get allowHover() {
@@ -48,23 +50,21 @@ export class ESLNote extends ESLBaseElement {
     return this._index;
   }
 
-  get html() {
-    return this._innerHTML;
-  }
-
   @ready
   protected connectedCallback() {
-    this._innerHTML = this.innerHTML;
+    if (!this.html) {
+      this.html = this.innerHTML;
+    }
     super.connectedCallback();
     this.bindEvents();
     this._sendResponseToFootnote();
   }
 
-  @ready
   protected disconnectedCallback() {
     super.disconnectedCallback();
     this.unbindEvents();
     this._$footnotes?.unlinkNote(this);
+    this.restore();
   }
 
   protected attributeChangedCallback(attrName: string, oldVal: string, newVal: string) {
@@ -100,17 +100,25 @@ export class ESLNote extends ESLBaseElement {
   public link(footnotes: ESLFootnotes, index: number) {
     this.linked = true;
     this._$footnotes = footnotes;
-    this._index = index;
-    this.innerHTML = `${index}`;
+    this.setIndex(index);
     this.tabIndex = 0;
   }
 
   public unlink() {
+    this.restore();
+    this._sendResponseToFootnote();
+  }
+
+  public setIndex(index: number) {
+    this._index = index;
+    this.innerHTML = `${index}`;
+  }
+
+  protected restore() {
     this.linked = false;
     this._$footnotes = null;
     this.innerHTML = this.html;
     this.tabIndex = -1;
-    this._sendResponseToFootnote();
   }
 
   /** Merge params to pass to the toggleable */
diff --git a/src/modules/esl-forms/esl-select/core/esl-select-dropdown.ts b/src/modules/esl-forms/esl-select/core/esl-select-dropdown.ts
index 682250171..44bb4b7b0 100644
--- a/src/modules/esl-forms/esl-select/core/esl-select-dropdown.ts
+++ b/src/modules/esl-forms/esl-select/core/esl-select-dropdown.ts
@@ -68,7 +68,7 @@ export class ESLSelectDropdown extends ESLToggleable {
 
     super.onShow(params);
     const focusable = this.querySelector('[tabindex]') as HTMLElement;
-    focusable && focusable.focus( {preventScroll: true} );
+    focusable && focusable.focus({preventScroll: true});
     this.updatePosition();
   }
   protected onHide(params: ToggleableActionParams) {
diff --git a/src/modules/esl-media-query/core/conditions/media-query-base.ts b/src/modules/esl-media-query/core/conditions/media-query-base.ts
index 38c1eb730..770f9e018 100644
--- a/src/modules/esl-media-query/core/conditions/media-query-base.ts
+++ b/src/modules/esl-media-query/core/conditions/media-query-base.ts
@@ -26,9 +26,13 @@ class MediaQueryConstCondition implements IMediaQueryCondition {
   public addListener(cb: VoidFunction) {}
   public removeListener(cb: VoidFunction) {}
 
-  public optimize() { return this; }
+  public optimize() {
+    return this;
+  }
 
-  public toString() { return this._matches ? 'all' : 'not all'; }
+  public toString() {
+    return this._matches ? 'all' : 'not all';
+  }
 
   /** Compare const media condition with the passed query instance or string */
   public eq(val: IMediaQueryCondition | string): boolean {
diff --git a/src/modules/esl-media-query/core/conditions/media-query-containers.ts b/src/modules/esl-media-query/core/conditions/media-query-containers.ts
index 3e7f6517f..e0222cefa 100644
--- a/src/modules/esl-media-query/core/conditions/media-query-containers.ts
+++ b/src/modules/esl-media-query/core/conditions/media-query-containers.ts
@@ -29,10 +29,14 @@ class MediaQueryContainer extends Observable<(matches: boolean) => void> impleme
     this.items.forEach((item) => item.removeListener(this._onChildChange));
   }
 
-  public get matches() { return false; }
+  public get matches() {
+    return false;
+  }
 
   /** Exclude const conditions. Unwrap empty or trivial (with one item) containers */
-  public optimize(): IMediaQueryCondition { return this; }
+  public optimize(): IMediaQueryCondition {
+    return this;
+  }
 
   /** Handle query change and dispatch it on top level in case result value is changed */
   protected _onChildChange() {
diff --git a/src/modules/esl-media/core/esl-media-provider.ts b/src/modules/esl-media/core/esl-media-provider.ts
index 45ffc1bfa..173a30f62 100644
--- a/src/modules/esl-media/core/esl-media-provider.ts
+++ b/src/modules/esl-media/core/esl-media-provider.ts
@@ -125,7 +125,7 @@ export abstract class BaseProvider {
    * If the player is PAUSED then it starts playing otherwise it pause playing
    */
   protected toggle() {
-    if (this.state === PlayerStates.PAUSED) {
+    if ([PlayerStates.PAUSED, PlayerStates.UNSTARTED, PlayerStates.VIDEO_CUED].includes(this.state)) {
       return this.play();
     } else {
       return this.pause();
diff --git a/src/modules/esl-media/core/esl-media.ts b/src/modules/esl-media/core/esl-media.ts
index 8ba7f0363..b3ccefe66 100644
--- a/src/modules/esl-media/core/esl-media.ts
+++ b/src/modules/esl-media/core/esl-media.ts
@@ -10,6 +10,7 @@ import {parseAspectRatio} from '../../esl-utils/misc/format';
 import {ESLMediaQuery} from '../../esl-media-query/core';
 import {TraversingQuery} from '../../esl-traversing-query/core';
 
+import {SPACE, PAUSE} from '../../esl-utils/dom/keys';
 import {getIObserver} from './esl-media-iobserver';
 import {PlayerStates} from './esl-media-provider';
 import {ESLMediaProviderRegistry} from './esl-media-registry';
@@ -123,28 +124,14 @@ export class ESLMedia extends ESLBaseElement {
       this.setAttribute('role', 'application');
     }
     this.innerHTML += '<!-- Inner Content, do not modify it manually -->';
-    ESLMediaProviderRegistry.instance.addListener(this._onRegistryStateChange);
-    if (this.conditionQuery) {
-      this.conditionQuery.addListener(this.deferredReinitialize);
-    }
-    if (this.fillModeEnabled) {
-      window.addEventListener('resize', this.deferredResize);
-    }
-    window.addEventListener('esl:refresh', this._onRefresh);
+    this.bindEvents();
     this.attachViewportConstraint();
     this.deferredReinitialize();
   }
 
   protected disconnectedCallback() {
     super.disconnectedCallback();
-    ESLMediaProviderRegistry.instance.removeListener(this._onRegistryStateChange);
-    if (this.conditionQuery) {
-      this.conditionQuery.removeListener(this.deferredReinitialize);
-    }
-    if (this.fillModeEnabled) {
-      window.removeEventListener('resize', this.deferredResize);
-    }
-    window.removeEventListener('esl:refresh', this._onRefresh);
+    this.unbindEvents();
     this.detachViewportConstraint();
     this._provider && this._provider.unbind();
   }
@@ -175,6 +162,29 @@ export class ESLMedia extends ESLBaseElement {
     }
   }
 
+  protected bindEvents() {
+    ESLMediaProviderRegistry.instance.addListener(this._onRegistryStateChange);
+    if (this.conditionQuery) {
+      this.conditionQuery.addListener(this.deferredReinitialize);
+    }
+    if (this.fillModeEnabled) {
+      window.addEventListener('resize', this.deferredResize);
+    }
+    window.addEventListener('esl:refresh', this._onRefresh);
+    this.addEventListener('keydown', this._onKeydown);
+  }
+  protected unbindEvents() {
+    ESLMediaProviderRegistry.instance.removeListener(this._onRegistryStateChange);
+    if (this.conditionQuery) {
+      this.conditionQuery.removeListener(this.deferredReinitialize);
+    }
+    if (this.fillModeEnabled) {
+      window.removeEventListener('resize', this.deferredResize);
+    }
+    window.removeEventListener('esl:refresh', this._onRefresh);
+    this.removeEventListener('keydown', this._onKeydown);
+  }
+
   public canActivate() {
     if (this.disabled) return false;
     if (this.conditionQuery) return this.conditionQuery.matches;
@@ -242,8 +252,8 @@ export class ESLMedia extends ESLBaseElement {
     return this._provider && this._provider.safeToggle();
   }
 
-  /** @override */
-  public focus(): void {
+  /** Focus inner player **/
+  public focusPlayer(): void {
     this._provider && this._provider.focus();
   }
 
@@ -321,6 +331,16 @@ export class ESLMedia extends ESLBaseElement {
     }
   }
 
+  @bind
+  protected _onKeydown(e: KeyboardEvent) {
+    if (e.target !== this) return;
+    if ([SPACE, PAUSE].includes(e.key)) {
+      e.preventDefault();
+      e.stopPropagation();
+      this.toggle();
+    }
+  }
+
   /** Update ready class state */
   protected updateReadyClass() {
     const target = TraversingQuery.first(this.readyClassTarget, this) as HTMLElement;
diff --git a/src/modules/esl-media/providers/youtube-provider.ts b/src/modules/esl-media/providers/youtube-provider.ts
index 816407a4c..9664f859c 100644
--- a/src/modules/esl-media/providers/youtube-provider.ts
+++ b/src/modules/esl-media/providers/youtube-provider.ts
@@ -103,7 +103,7 @@ export class YouTubeProvider extends BaseProvider {
   protected onPlayerReady() {
     console.debug('[ESL]: Media Youtube Player ready ', this);
     this._el = this._api.getIframe();
-      if (this.config.muted) {
+    if (this.config.muted) {
       this._api.mute();
     }
     this.component._onReady();
@@ -188,7 +188,7 @@ export class YouTubeProvider extends BaseProvider {
 // typings
 declare global {
   interface YT extends Promise<void> {
-    Player: YT.Player,
+    Player: YT.Player;
   }
 
   interface Window {
diff --git a/src/modules/esl-popup/core/esl-popup-position.ts b/src/modules/esl-popup/core/esl-popup-position.ts
index e85dfc789..66e06f1de 100644
--- a/src/modules/esl-popup/core/esl-popup-position.ts
+++ b/src/modules/esl-popup/core/esl-popup-position.ts
@@ -23,7 +23,7 @@ export interface IntersectionRatioRect {
 export interface PopupPositionConfig {
   position: PositionType;
   behavior: string;
-  intersectionRatio: IntersectionRatioRect,
+  intersectionRatio: IntersectionRatioRect;
   element: DOMRect;
   inner: Rect;
   outer: Rect;
diff --git a/src/modules/esl-popup/core/esl-popup.ts b/src/modules/esl-popup/core/esl-popup.ts
index 70f402022..4c0284ce9 100644
--- a/src/modules/esl-popup/core/esl-popup.ts
+++ b/src/modules/esl-popup/core/esl-popup.ts
@@ -148,7 +148,7 @@ export class ESLPopup extends ESLToggleable {
 
     const options = {
       rootMargin: '0px',
-      threshold: [...Array(9).keys()].map((x) => x/8)
+      threshold: [...Array(9).keys()].map((x) => x / 8)
     } as IntersectionObserverInit;
 
     const observer = new IntersectionObserver(this.onActivatorIntersection, options);
diff --git a/src/modules/esl-toggleable/core/esl-toggleable.ts b/src/modules/esl-toggleable/core/esl-toggleable.ts
index 1c9419e84..b5cba4eed 100644
--- a/src/modules/esl-toggleable/core/esl-toggleable.ts
+++ b/src/modules/esl-toggleable/core/esl-toggleable.ts
@@ -192,7 +192,7 @@ export class ESLToggleable extends ESLBaseElement {
   /** Actual hide task to execute by toggleable task manger ({@link DelayedTask} out of the box) */
   protected hideTask(params: ToggleableActionParams) {
     if (!params.force && !this.open) return;
-    if (!params.silent && !this.$$fire('before:hide',{detail: {params}})) return;
+    if (!params.silent && !this.$$fire('before:hide', {detail: {params}})) return;
     this.open = false;
     this.onHide(params);
     if (!params.silent) this.$$fire('hide', {detail: {params}, cancelable: false});
@@ -234,7 +234,7 @@ export class ESLToggleable extends ESLBaseElement {
     return activators.get(this);
   }
   public set activator(el: HTMLElement | null | undefined) {
-    el ? activators.set(this, el): activators.delete(this);
+    el ? activators.set(this, el) : activators.delete(this);
   }
 
   /** Returns the element to apply a11y attributes */
diff --git a/src/modules/esl-utils/dom/class.ts b/src/modules/esl-utils/dom/class.ts
index faae3f5bd..882461501 100644
--- a/src/modules/esl-utils/dom/class.ts
+++ b/src/modules/esl-utils/dom/class.ts
@@ -1,3 +1,5 @@
+import {wrap} from '../misc/array';
+
 /** Describe locker elements collection per class name */
 type Locks = Map<string, Set<HTMLElement>>;
 /** Store locks for key element classes*/
@@ -66,32 +68,34 @@ export abstract class CSSClassUtils {
    * Add all classes from the class token string to the element.
    * @see CSSClassUtils
    * */
-  public static add(el: HTMLElement, cls: string | null | undefined, locker?: HTMLElement) {
-    CSSClassUtils.splitTokens(cls).forEach((className) => add(el, className, locker));
+  public static add(els: HTMLElement | HTMLElement[], cls: string | null | undefined, locker?: HTMLElement) {
+    const tokens = CSSClassUtils.splitTokens(cls);
+    wrap(els).forEach((el) => tokens.forEach((className) => add(el, className, locker)));
   }
 
   /**
    * Remove all classes from the class token string to the element.
    * @see CSSClassUtils
    * */
-  public static remove(el: HTMLElement, cls: string | null | undefined, locker?: HTMLElement) {
-    CSSClassUtils.splitTokens(cls).forEach((className) => remove(el, className, locker));
+  public static remove(els: HTMLElement | HTMLElement[], cls: string | null | undefined, locker?: HTMLElement) {
+    const tokens = CSSClassUtils.splitTokens(cls);
+    wrap(els).forEach((el) => tokens.forEach((className) => remove(el, className, locker)));
   }
 
   /**
    * Toggle all classes from the class token string on the element to the passed state.
    * @see CSSClassUtils
    * */
-  public static toggle(el: HTMLElement, cls: string | null | undefined, state: boolean, locker?: HTMLElement) {
-    (state ? CSSClassUtils.add : CSSClassUtils.remove)(el, cls, locker);
+  public static toggle(els: HTMLElement | HTMLElement[], cls: string | null | undefined, state: boolean, locker?: HTMLElement) {
+    (state ? CSSClassUtils.add : CSSClassUtils.remove)(els, cls, locker);
   }
 
   /** Remove all lockers for the element or passed element className */
-  public static unlock(el: HTMLElement, className?: string) {
+  public static unlock(els: HTMLElement | HTMLElement[], className?: string) {
     if (className) {
-      lockStore.get(el)?.delete(className);
+      wrap(els).forEach((el) => lockStore.get(el)?.delete(className));
     } else {
-      lockStore.delete(el);
+      wrap(els).forEach((el) => lockStore.delete(el));
     }
   }
 }
diff --git a/src/modules/esl-utils/dom/element.ts b/src/modules/esl-utils/dom/element.ts
index 42aa3b3d3..50ddf85f8 100644
--- a/src/modules/esl-utils/dom/element.ts
+++ b/src/modules/esl-utils/dom/element.ts
@@ -13,7 +13,7 @@ export function getDocumentElement(element: Element | Window): Element {
  * @param element - element for which to get the name
  */
 export function getNodeName(element?: Node | Window): string {
-  return element && !(element instanceof Window)? (element.nodeName).toLowerCase() : '';
+  return element && !(element instanceof Window) ? (element.nodeName).toLowerCase() : '';
 }
 
 /**
diff --git a/src/modules/esl-utils/dom/focus.ts b/src/modules/esl-utils/dom/focus.ts
index 8c4cf595c..4b2f8a482 100644
--- a/src/modules/esl-utils/dom/focus.ts
+++ b/src/modules/esl-utils/dom/focus.ts
@@ -5,7 +5,7 @@ import {TAB} from './keys';
  * Passive (should be called inside keyboard event handler)
  */
 export const handleFocusChain = (e: KeyboardEvent, first: HTMLElement, last: HTMLElement) => {
-  if (e.key !== TAB ) return;
+  if (e.key !== TAB) return;
   if (last && e.target === first && e.shiftKey) {
     last.focus();
     e.preventDefault();
diff --git a/src/modules/esl-utils/dom/rect.ts b/src/modules/esl-utils/dom/rect.ts
index 5c92ab611..0b6d48cbd 100644
--- a/src/modules/esl-utils/dom/rect.ts
+++ b/src/modules/esl-utils/dom/rect.ts
@@ -15,14 +15,14 @@ export class Rect {
    * The static method creates a new Rect instance from a rect-like object.
    * @param rect - rect-like object
    * */
-  public static from(rect: {x?: number; left?: number; y?: number; top?: number; width?: number; height?: number} = {}): Rect {
+  public static from(rect: {x?: number, left?: number, y?: number, top?: number, width?: number, height?: number} = {}): Rect {
     return new this(rect.x || rect.left, rect.y || rect.top, rect.width, rect.height);
   }
 
   public constructor(x?: number, y?: number, width?: number, height?: number) {
     this.x = x || 0;
     this.y = y || 0;
-    this.width= width || 0;
+    this.width = width || 0;
     this.height = height || 0;
   }
 
diff --git a/src/modules/esl-utils/dom/rtl.ts b/src/modules/esl-utils/dom/rtl.ts
index 56a33e67a..baf97e688 100644
--- a/src/modules/esl-utils/dom/rtl.ts
+++ b/src/modules/esl-utils/dom/rtl.ts
@@ -27,7 +27,7 @@ export abstract class RTLUtils {
   }
 
   static normalizeScrollLeft(el: HTMLElement, value: number | null = null, isRtl: boolean = RTLUtils.isRtl(el)): number {
-    value = (value === null) ? el.scrollLeft: value;
+    value = (value === null) ? el.scrollLeft : value;
     switch (isRtl ? RTLUtils.scrollType : '') {
       case 'negative':
         return el.scrollWidth - el.clientWidth + value;
diff --git a/src/modules/esl-utils/misc/format.ts b/src/modules/esl-utils/misc/format.ts
index 433aab4c4..3793a64f1 100644
--- a/src/modules/esl-utils/misc/format.ts
+++ b/src/modules/esl-utils/misc/format.ts
@@ -42,6 +42,7 @@ export function parseAspectRatio(str: string): number {
 /** Evaluate passed string or returns `defaultValue` */
 export function evaluate(str: string, defaultValue?: any): any {
   try {
+    // eslint-disable-next-line @typescript-eslint/no-implied-eval
     return str ? (new Function(`return ${str}`))() : defaultValue;
   } catch (e) {
     console.warn('Cannot parse value ', str, e);
diff --git a/src/modules/esl-utils/misc/memoize.ts b/src/modules/esl-utils/misc/memoize.ts
index 8a7e1e1da..d694cd799 100644
--- a/src/modules/esl-utils/misc/memoize.ts
+++ b/src/modules/esl-utils/misc/memoize.ts
@@ -3,9 +3,9 @@ import type {AnyToAnyFnSignature} from './functions';
 /** Memoized function type */
 export type MemoizedFn<T extends AnyToAnyFnSignature> = T & {
   /** Cache instance */
-  cache: Map<null | string, ReturnType<T>>,
+  cache: Map<null | string, ReturnType<T>>;
   /** Clear memoization cache */
-  clear: () => void
+  clear: () => void;
 };
 
 /**
diff --git a/src/polyfills/list/dom.tokenList.toggle.ts b/src/polyfills/list/dom.tokenList.toggle.ts
index 772fc780e..2f058fdc2 100644
--- a/src/polyfills/list/dom.tokenList.toggle.ts
+++ b/src/polyfills/list/dom.tokenList.toggle.ts
@@ -8,6 +8,7 @@
   const testTokenList = window.document.createElement('div').classList;
 
   if (!DOMTokenListProto.toggle) return;
+  // eslint-disable-next-line
   if (testTokenList.toggle('a', false) === false) return;
 
   DOMTokenListProto.toggle = function toggle(val: string, force?: boolean) {


Test patch:
diff --git a/src/modules/esl-utils/dom/test/class.test.ts b/src/modules/esl-utils/dom/test/class.test.ts
index b34bc7ad8..debe6fcdf 100644
--- a/src/modules/esl-utils/dom/test/class.test.ts
+++ b/src/modules/esl-utils/dom/test/class.test.ts
@@ -122,3 +122,41 @@ describe('CSSClassUtils tests:', () => {
     });
   });
 });
+
+describe('multiple targets', () => {
+  const els = [
+    document.createElement('div'),
+    document.createElement('div'),
+    document.createElement('div')
+  ];
+
+  beforeEach(() => els.forEach((el) => el.className = ''));
+  test('add', () => {
+    CSSClassUtils.add(els, 'a b');
+    for (const el of els) {
+      expect(el.classList.contains('a')).toBeTruthy();
+      expect(el.classList.contains('b')).toBeTruthy();
+    }
+    CSSClassUtils.add(els, '!a !b');
+    for (const el of els) {
+      expect(el.classList.contains('a')).toBeFalsy();
+      expect(el.classList.contains('b')).toBeFalsy();
+    }
+  });
+  test('remove', () => {
+    CSSClassUtils.remove(els, '!a !b');
+    for (const el of els) {
+      expect(el.classList.contains('a')).toBeTruthy();
+      expect(el.classList.contains('b')).toBeTruthy();
+    }
+    CSSClassUtils.remove(els, 'a b');
+    for (const el of els) {
+      expect(el.classList.contains('a')).toBeFalsy();
+      expect(el.classList.contains('b')).toBeFalsy();
+    }
+  });
+  test('trivial',  () => {
+    expect(() => CSSClassUtils.add([], 'a')).not.toThrowError();
+    expect(() => CSSClassUtils.remove([], 'a')).not.toThrowError();
+  });
+});
\ No newline at end of file