From 95c5f40ef86759d8a4b1411deba79ec4530429c6 Mon Sep 17 00:00:00 2001
From: KoloMl <thekolomoets@hotmail.com>
Date: Wed, 2 Apr 2025 05:00:53 +0400
Subject: [PATCH 1/9] Enclosing keyboard navigation test into describe block

I want to share the same context, but make a separate test for the
edge-case scenario.
---
 .../autocomplete/__tests__/keyboard.spec.ts   | 192 +++++++++---------
 1 file changed, 99 insertions(+), 93 deletions(-)

diff --git a/assets/js/autocomplete/__tests__/keyboard.spec.ts b/assets/js/autocomplete/__tests__/keyboard.spec.ts
index 11a3d446..86243430 100644
--- a/assets/js/autocomplete/__tests__/keyboard.spec.ts
+++ b/assets/js/autocomplete/__tests__/keyboard.spec.ts
@@ -1,114 +1,120 @@
-import { init } from './context';
+import { init, TestContext } from './context';
 
-it('supports navigation via keyboard', async () => {
-  const ctx = await init();
+describe('Autocomplete keyboard navigation', () => {
+  let ctx: TestContext;
 
-  await ctx.setInput('f');
+  beforeAll(async () => {
+    ctx = await init();
+  });
 
-  await ctx.keyDown('ArrowDown');
+  it('supports navigation via keyboard', async () => {
+    await ctx.setInput('f');
 
-  ctx.expectUi().toMatchInlineSnapshot(`
-    {
-      "input": "forest<>",
-      "suggestions": [
-        "👉 forest  3",
-        "force field  1",
-        "fog  1",
-        "flower  1",
-      ],
-    }
-  `);
+    await ctx.keyDown('ArrowDown');
 
-  await ctx.keyDown('ArrowDown');
+    ctx.expectUi().toMatchInlineSnapshot(`
+      {
+        "input": "forest<>",
+        "suggestions": [
+          "👉 forest  3",
+          "force field  1",
+          "fog  1",
+          "flower  1",
+        ],
+      }
+    `);
 
-  ctx.expectUi().toMatchInlineSnapshot(`
-    {
-      "input": "force field<>",
-      "suggestions": [
-        "forest  3",
-        "👉 force field  1",
-        "fog  1",
-        "flower  1",
-      ],
-    }
-  `);
+    await ctx.keyDown('ArrowDown');
 
-  await ctx.keyDown('ArrowDown', { ctrlKey: true });
+    ctx.expectUi().toMatchInlineSnapshot(`
+      {
+        "input": "force field<>",
+        "suggestions": [
+          "forest  3",
+          "👉 force field  1",
+          "fog  1",
+          "flower  1",
+        ],
+      }
+    `);
 
-  ctx.expectUi().toMatchInlineSnapshot(`
-    {
-      "input": "flower<>",
-      "suggestions": [
-        "forest  3",
-        "force field  1",
-        "fog  1",
-        "👉 flower  1",
-      ],
-    }
-  `);
+    await ctx.keyDown('ArrowDown', { ctrlKey: true });
 
-  await ctx.keyDown('ArrowUp', { ctrlKey: true });
+    ctx.expectUi().toMatchInlineSnapshot(`
+      {
+        "input": "flower<>",
+        "suggestions": [
+          "forest  3",
+          "force field  1",
+          "fog  1",
+          "👉 flower  1",
+        ],
+      }
+    `);
 
-  ctx.expectUi().toMatchInlineSnapshot(`
-    {
-      "input": "forest<>",
-      "suggestions": [
-        "👉 forest  3",
-        "force field  1",
-        "fog  1",
-        "flower  1",
-      ],
-    }
-  `);
+    await ctx.keyDown('ArrowUp', { ctrlKey: true });
 
-  await ctx.keyDown('Enter');
+    ctx.expectUi().toMatchInlineSnapshot(`
+      {
+        "input": "forest<>",
+        "suggestions": [
+          "👉 forest  3",
+          "force field  1",
+          "fog  1",
+          "flower  1",
+        ],
+      }
+    `);
 
-  ctx.expectUi().toMatchInlineSnapshot(`
-    {
-      "input": "forest<>",
-      "suggestions": [],
-    }
-  `);
+    await ctx.keyDown('Enter');
 
-  await ctx.setInput('forest, t<>, safe');
+    ctx.expectUi().toMatchInlineSnapshot(`
+      {
+        "input": "forest<>",
+        "suggestions": [],
+      }
+    `);
 
-  ctx.expectUi().toMatchInlineSnapshot(`
-    {
-      "input": "forest, t<>, safe",
-      "suggestions": [
-        "artist:test  1",
-      ],
-    }
-  `);
+    await ctx.setInput('forest, t<>, safe');
 
-  await ctx.keyDown('ArrowDown');
+    ctx.expectUi().toMatchInlineSnapshot(`
+      {
+        "input": "forest, t<>, safe",
+        "suggestions": [
+          "artist:test  1",
+        ],
+      }
+    `);
 
-  ctx.expectUi().toMatchInlineSnapshot(`
-    {
-      "input": "forest, artist:test<>, safe",
-      "suggestions": [
-        "👉 artist:test  1",
-      ],
-    }
-  `);
+    await ctx.keyDown('ArrowDown');
 
-  await ctx.keyDown('Escape');
+    ctx.expectUi().toMatchInlineSnapshot(`
+      {
+        "input": "forest, artist:test<>, safe",
+        "suggestions": [
+          "👉 artist:test  1",
+        ],
+      }
+    `);
 
-  ctx.expectUi().toMatchInlineSnapshot(`
-    {
-      "input": "forest, artist:test<>, safe",
-      "suggestions": [],
-    }
-  `);
+    await ctx.keyDown('Escape');
 
-  await ctx.setInput('forest, t<>, safe');
-  await ctx.keyDown('ArrowDown');
-  await ctx.keyDown('Enter');
+    ctx.expectUi().toMatchInlineSnapshot(`
+      {
+        "input": "forest, artist:test<>, safe",
+        "suggestions": [],
+      }
+    `);
 
-  ctx.expectUi().toMatchInlineSnapshot(`
-    {
-      "input": "forest, artist:test<>, safe",
-      "suggestions": [],
-    }
-  `);
+    await ctx.setInput('forest, t<>, safe');
+    await ctx.keyDown('ArrowDown');
+    await ctx.keyDown('Enter');
+
+    ctx.expectUi().toMatchInlineSnapshot(`
+      {
+        "input": "forest, artist:test<>, safe",
+        "suggestions": [],
+      }
+    `);
+  });
 });

From c77a93b45597fec4c9b62f5c19e92ac0daa79c6a Mon Sep 17 00:00:00 2001
From: KoloMl <thekolomoets@hotmail.com>
Date: Wed, 2 Apr 2025 05:06:18 +0400
Subject: [PATCH 2/9] Added the test case for the keydown event with empty code

---
 assets/js/autocomplete/__tests__/context.ts   |  2 +-
 .../autocomplete/__tests__/keyboard.spec.ts   | 26 +++++++++++++++++++
 2 files changed, 27 insertions(+), 1 deletion(-)

diff --git a/assets/js/autocomplete/__tests__/context.ts b/assets/js/autocomplete/__tests__/context.ts
index 89e949eb..1e37cc02 100644
--- a/assets/js/autocomplete/__tests__/context.ts
+++ b/assets/js/autocomplete/__tests__/context.ts
@@ -146,7 +146,7 @@ export class TestContext {
     await vi.runAllTimersAsync();
   }
 
-  async keyDown(code: string, params?: { ctrlKey?: boolean }) {
+  async keyDown(code: string, params?: { ctrlKey?: boolean; key?: string }) {
     fireEvent.keyDown(this.input, { code, ...(params ?? {}) });
     await vi.runAllTimersAsync();
   }
diff --git a/assets/js/autocomplete/__tests__/keyboard.spec.ts b/assets/js/autocomplete/__tests__/keyboard.spec.ts
index 86243430..3e135034 100644
--- a/assets/js/autocomplete/__tests__/keyboard.spec.ts
+++ b/assets/js/autocomplete/__tests__/keyboard.spec.ts
@@ -117,4 +117,30 @@ describe('Autocomplete keyboard navigation', () => {
       }
     `);
   });
+
+  it('should handle Enter key presses with empty code property', async () => {
+    await ctx.setInput('f');
+
+    ctx.expectUi().toMatchInlineSnapshot(`
+      {
+        "input": "f<>",
+        "suggestions": [
+          "forest  3",
+          "force field  1",
+          "fog  1",
+          "flower  1",
+        ],
+      }
+    `);
+
+    await ctx.keyDown('ArrowDown');
+    await ctx.keyDown('', { key: 'Enter' });
+
+    ctx.expectUi().toMatchInlineSnapshot(`
+      {
+        "input": "forest<>",
+        "suggestions": [],
+      }
+    `);
+  });
 });

From da81beec515d84c46a2d91d86aca471f3bb82bf5 Mon Sep 17 00:00:00 2001
From: KoloMl <thekolomoets@hotmail.com>
Date: Wed, 2 Apr 2025 05:09:14 +0400
Subject: [PATCH 3/9] Fixed `Enter` key presses not being handled properly on
 Android devices

---
 assets/js/autocomplete/index.ts | 20 ++++++++++++++------
 1 file changed, 14 insertions(+), 6 deletions(-)

diff --git a/assets/js/autocomplete/index.ts b/assets/js/autocomplete/index.ts
index 8bffa7d0..0acc4846 100644
--- a/assets/js/autocomplete/index.ts
+++ b/assets/js/autocomplete/index.ts
@@ -2,12 +2,12 @@ import { LocalAutocompleter } from '../utils/local-autocompleter';
 import * as history from './history';
 import { AutocompletableInput, TextInputElement } from './input';
 import {
-  SuggestionsPopupComponent,
-  Suggestions,
-  TagSuggestionComponent,
-  Suggestion,
   HistorySuggestionComponent,
   ItemSelectedEvent,
+  Suggestion,
+  Suggestions,
+  SuggestionsPopupComponent,
+  TagSuggestionComponent,
 } from '../utils/suggestions-view';
 import { prefixMatchParts } from '../utils/suggestions-model';
 import { $$ } from '../utils/dom';
@@ -243,13 +243,21 @@ class Autocomplete {
     if (!this.isActive() || this.input.element !== event.target) {
       return;
     }
-    if ((event.key === ',' || event.code === 'Enter') && this.input.type === 'single-tag') {
+
+    let keyCode = event.code;
+
+    // Chrome & Firefox on Android devices return empty code when "Enter" is pressed.
+    if (!keyCode && event.key === 'Enter') {
+      keyCode = event.key;
+    }
+
+    if ((event.key === ',' || keyCode === 'Enter') && this.input.type === 'single-tag') {
       // Comma/Enter mean the end of input for the current tag in single-tag mode.
       this.hidePopup(`The user accepted the existing input via key: '${event.key}', code: '${event.code}'`);
       return;
     }
 
-    switch (event.code) {
+    switch (keyCode) {
       case 'Enter': {
         const { selectedSuggestion } = this.popup;
         if (!selectedSuggestion) {

From 8f6f9a78493511600c43f7c6787da351e488083b Mon Sep 17 00:00:00 2001
From: KoloMl <thekolomoets@hotmail.com>
Date: Wed, 2 Apr 2025 17:11:10 +0400
Subject: [PATCH 4/9] Uninstalled `eslint-plugin-vitest`

Looks like package was renamed in NPM registry to
`@vitest/eslint-plugin`. This package is from same exact repository.
---
 assets/package-lock.json | 158 ---------------------------------------
 assets/package.json      |   1 -
 2 files changed, 159 deletions(-)

diff --git a/assets/package-lock.json b/assets/package-lock.json
index 52ea697b..87a739e1 100644
--- a/assets/package-lock.json
+++ b/assets/package-lock.json
@@ -22,7 +22,6 @@
         "@testing-library/jest-dom": "^6.6.3",
         "@vitest/coverage-v8": "^2.1.9",
         "eslint": "^9.16.0",
-        "eslint-plugin-vitest": "^0.5.4",
         "stylelint": "^16.11.0",
         "stylelint-config-standard": "^36.0.1",
         "typescript-eslint": "8.17.0",
@@ -2501,163 +2500,6 @@
         }
       }
     },
-    "node_modules/eslint-plugin-vitest": {
-      "version": "0.5.4",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-vitest/-/eslint-plugin-vitest-0.5.4.tgz",
-      "integrity": "sha512-um+odCkccAHU53WdKAw39MY61+1x990uXjSPguUCq3VcEHdqJrOb8OTMrbYlY6f9jAKx7x98kLVlIe3RJeJqoQ==",
-      "dev": true,
-      "dependencies": {
-        "@typescript-eslint/utils": "^7.7.1"
-      },
-      "engines": {
-        "node": "^18.0.0 || >= 20.0.0"
-      },
-      "peerDependencies": {
-        "eslint": "^8.57.0 || ^9.0.0",
-        "vitest": "*"
-      },
-      "peerDependenciesMeta": {
-        "@typescript-eslint/eslint-plugin": {
-          "optional": true
-        },
-        "vitest": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/eslint-plugin-vitest/node_modules/@typescript-eslint/scope-manager": {
-      "version": "7.18.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz",
-      "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==",
-      "dev": true,
-      "dependencies": {
-        "@typescript-eslint/types": "7.18.0",
-        "@typescript-eslint/visitor-keys": "7.18.0"
-      },
-      "engines": {
-        "node": "^18.18.0 || >=20.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/typescript-eslint"
-      }
-    },
-    "node_modules/eslint-plugin-vitest/node_modules/@typescript-eslint/types": {
-      "version": "7.18.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz",
-      "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==",
-      "dev": true,
-      "engines": {
-        "node": "^18.18.0 || >=20.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/typescript-eslint"
-      }
-    },
-    "node_modules/eslint-plugin-vitest/node_modules/@typescript-eslint/typescript-estree": {
-      "version": "7.18.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz",
-      "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==",
-      "dev": true,
-      "dependencies": {
-        "@typescript-eslint/types": "7.18.0",
-        "@typescript-eslint/visitor-keys": "7.18.0",
-        "debug": "^4.3.4",
-        "globby": "^11.1.0",
-        "is-glob": "^4.0.3",
-        "minimatch": "^9.0.4",
-        "semver": "^7.6.0",
-        "ts-api-utils": "^1.3.0"
-      },
-      "engines": {
-        "node": "^18.18.0 || >=20.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/typescript-eslint"
-      },
-      "peerDependenciesMeta": {
-        "typescript": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/eslint-plugin-vitest/node_modules/@typescript-eslint/utils": {
-      "version": "7.18.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz",
-      "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==",
-      "dev": true,
-      "dependencies": {
-        "@eslint-community/eslint-utils": "^4.4.0",
-        "@typescript-eslint/scope-manager": "7.18.0",
-        "@typescript-eslint/types": "7.18.0",
-        "@typescript-eslint/typescript-estree": "7.18.0"
-      },
-      "engines": {
-        "node": "^18.18.0 || >=20.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/typescript-eslint"
-      },
-      "peerDependencies": {
-        "eslint": "^8.56.0"
-      }
-    },
-    "node_modules/eslint-plugin-vitest/node_modules/@typescript-eslint/visitor-keys": {
-      "version": "7.18.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz",
-      "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==",
-      "dev": true,
-      "dependencies": {
-        "@typescript-eslint/types": "7.18.0",
-        "eslint-visitor-keys": "^3.4.3"
-      },
-      "engines": {
-        "node": "^18.18.0 || >=20.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/typescript-eslint"
-      }
-    },
-    "node_modules/eslint-plugin-vitest/node_modules/brace-expansion": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
-      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
-      "dev": true,
-      "dependencies": {
-        "balanced-match": "^1.0.0"
-      }
-    },
-    "node_modules/eslint-plugin-vitest/node_modules/eslint-visitor-keys": {
-      "version": "3.4.3",
-      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
-      "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
-      "dev": true,
-      "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-      },
-      "funding": {
-        "url": "https://opencollective.com/eslint"
-      }
-    },
-    "node_modules/eslint-plugin-vitest/node_modules/minimatch": {
-      "version": "9.0.5",
-      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
-      "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
-      "dev": true,
-      "dependencies": {
-        "brace-expansion": "^2.0.1"
-      },
-      "engines": {
-        "node": ">=16 || 14 >=14.17"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/isaacs"
-      }
-    },
     "node_modules/eslint-scope": {
       "version": "8.2.0",
       "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz",
diff --git a/assets/package.json b/assets/package.json
index a2cf1ae2..3d8a9d68 100644
--- a/assets/package.json
+++ b/assets/package.json
@@ -27,7 +27,6 @@
     "@testing-library/jest-dom": "^6.6.3",
     "@vitest/coverage-v8": "^2.1.9",
     "eslint": "^9.16.0",
-    "eslint-plugin-vitest": "^0.5.4",
     "stylelint": "^16.11.0",
     "stylelint-config-standard": "^36.0.1",
     "typescript-eslint": "8.17.0",

From 0a55ed93d24e7317cbf2a7f2768d4a96765eab12 Mon Sep 17 00:00:00 2001
From: KoloMl <thekolomoets@hotmail.com>
Date: Wed, 2 Apr 2025 17:17:15 +0400
Subject: [PATCH 5/9] Bumped `typescript-eslint` from 8.17.0 to 8.29.0

Resolves issue with conflicting dependencies when trying to install
`@vitest/eslint-plugin`:

npm error Could not resolve dependency:
npm error dev @vitest/eslint-plugin@"*" from the root project
npm error
npm error Conflicting peer dependency: @typescript-eslint/utils@8.29.0
npm error node_modules/@typescript-eslint/utils
npm error   peer @typescript-eslint/utils@"^8.24.0" from @vitest/eslint-plugin@1.1.38
npm error   node_modules/@vitest/eslint-plugin
npm error     dev @vitest/eslint-plugin@"*" from the root project
---
 assets/package-lock.json | 160 +++++++++++++++++----------------------
 assets/package.json      |   2 +-
 2 files changed, 70 insertions(+), 92 deletions(-)

diff --git a/assets/package-lock.json b/assets/package-lock.json
index 87a739e1..5c47b98e 100644
--- a/assets/package-lock.json
+++ b/assets/package-lock.json
@@ -24,7 +24,7 @@
         "eslint": "^9.16.0",
         "stylelint": "^16.11.0",
         "stylelint-config-standard": "^36.0.1",
-        "typescript-eslint": "8.17.0",
+        "typescript-eslint": "8.29.0",
         "vitest": "^2.1.9",
         "vitest-fetch-mock": "^0.4.2"
       }
@@ -1442,20 +1442,20 @@
       "integrity": "sha512-jxXqkf4sBn/WV9YsOlB5fFzWo9kGafMDF62VmVC1mFF367BuRn/2txr0ZaEchPsNIvyiLckMpxO6Xz3knpC6Nw=="
     },
     "node_modules/@typescript-eslint/eslint-plugin": {
-      "version": "8.17.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.17.0.tgz",
-      "integrity": "sha512-HU1KAdW3Tt8zQkdvNoIijfWDMvdSweFYm4hWh+KwhPstv+sCmWb89hCIP8msFm9N1R/ooh9honpSuvqKWlYy3w==",
+      "version": "8.29.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.0.tgz",
+      "integrity": "sha512-PAIpk/U7NIS6H7TEtN45SPGLQaHNgB7wSjsQV/8+KYokAb2T/gloOA/Bee2yd4/yKVhPKe5LlaUGhAZk5zmSaQ==",
       "dev": true,
       "dependencies": {
         "@eslint-community/regexpp": "^4.10.0",
-        "@typescript-eslint/scope-manager": "8.17.0",
-        "@typescript-eslint/type-utils": "8.17.0",
-        "@typescript-eslint/utils": "8.17.0",
-        "@typescript-eslint/visitor-keys": "8.17.0",
+        "@typescript-eslint/scope-manager": "8.29.0",
+        "@typescript-eslint/type-utils": "8.29.0",
+        "@typescript-eslint/utils": "8.29.0",
+        "@typescript-eslint/visitor-keys": "8.29.0",
         "graphemer": "^1.4.0",
         "ignore": "^5.3.1",
         "natural-compare": "^1.4.0",
-        "ts-api-utils": "^1.3.0"
+        "ts-api-utils": "^2.0.1"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1466,24 +1466,20 @@
       },
       "peerDependencies": {
         "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0",
-        "eslint": "^8.57.0 || ^9.0.0"
-      },
-      "peerDependenciesMeta": {
-        "typescript": {
-          "optional": true
-        }
+        "eslint": "^8.57.0 || ^9.0.0",
+        "typescript": ">=4.8.4 <5.9.0"
       }
     },
     "node_modules/@typescript-eslint/parser": {
-      "version": "8.17.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.17.0.tgz",
-      "integrity": "sha512-Drp39TXuUlD49F7ilHHCG7TTg8IkA+hxCuULdmzWYICxGXvDXmDmWEjJYZQYgf6l/TFfYNE167m7isnc3xlIEg==",
+      "version": "8.29.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.29.0.tgz",
+      "integrity": "sha512-8C0+jlNJOwQso2GapCVWWfW/rzaq7Lbme+vGUFKE31djwNncIpgXD7Cd4weEsDdkoZDjH0lwwr3QDQFuyrMg9g==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/scope-manager": "8.17.0",
-        "@typescript-eslint/types": "8.17.0",
-        "@typescript-eslint/typescript-estree": "8.17.0",
-        "@typescript-eslint/visitor-keys": "8.17.0",
+        "@typescript-eslint/scope-manager": "8.29.0",
+        "@typescript-eslint/types": "8.29.0",
+        "@typescript-eslint/typescript-estree": "8.29.0",
+        "@typescript-eslint/visitor-keys": "8.29.0",
         "debug": "^4.3.4"
       },
       "engines": {
@@ -1494,22 +1490,18 @@
         "url": "https://opencollective.com/typescript-eslint"
       },
       "peerDependencies": {
-        "eslint": "^8.57.0 || ^9.0.0"
-      },
-      "peerDependenciesMeta": {
-        "typescript": {
-          "optional": true
-        }
+        "eslint": "^8.57.0 || ^9.0.0",
+        "typescript": ">=4.8.4 <5.9.0"
       }
     },
     "node_modules/@typescript-eslint/scope-manager": {
-      "version": "8.17.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.17.0.tgz",
-      "integrity": "sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg==",
+      "version": "8.29.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.29.0.tgz",
+      "integrity": "sha512-aO1PVsq7Gm+tcghabUpzEnVSFMCU4/nYIgC2GOatJcllvWfnhrgW0ZEbnTxm36QsikmCN1K/6ZgM7fok2I7xNw==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "8.17.0",
-        "@typescript-eslint/visitor-keys": "8.17.0"
+        "@typescript-eslint/types": "8.29.0",
+        "@typescript-eslint/visitor-keys": "8.29.0"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1520,15 +1512,15 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils": {
-      "version": "8.17.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.17.0.tgz",
-      "integrity": "sha512-q38llWJYPd63rRnJ6wY/ZQqIzPrBCkPdpIsaCfkR3Q4t3p6sb422zougfad4TFW9+ElIFLVDzWGiGAfbb/v2qw==",
+      "version": "8.29.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.29.0.tgz",
+      "integrity": "sha512-ahaWQ42JAOx+NKEf5++WC/ua17q5l+j1GFrbbpVKzFL/tKVc0aYY8rVSYUpUvt2hUP1YBr7mwXzx+E/DfUWI9Q==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/typescript-estree": "8.17.0",
-        "@typescript-eslint/utils": "8.17.0",
+        "@typescript-eslint/typescript-estree": "8.29.0",
+        "@typescript-eslint/utils": "8.29.0",
         "debug": "^4.3.4",
-        "ts-api-utils": "^1.3.0"
+        "ts-api-utils": "^2.0.1"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1538,18 +1530,14 @@
         "url": "https://opencollective.com/typescript-eslint"
       },
       "peerDependencies": {
-        "eslint": "^8.57.0 || ^9.0.0"
-      },
-      "peerDependenciesMeta": {
-        "typescript": {
-          "optional": true
-        }
+        "eslint": "^8.57.0 || ^9.0.0",
+        "typescript": ">=4.8.4 <5.9.0"
       }
     },
     "node_modules/@typescript-eslint/types": {
-      "version": "8.17.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.17.0.tgz",
-      "integrity": "sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==",
+      "version": "8.29.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.0.tgz",
+      "integrity": "sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg==",
       "dev": true,
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1560,19 +1548,19 @@
       }
     },
     "node_modules/@typescript-eslint/typescript-estree": {
-      "version": "8.17.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.17.0.tgz",
-      "integrity": "sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==",
+      "version": "8.29.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.0.tgz",
+      "integrity": "sha512-yOfen3jE9ISZR/hHpU/bmNvTtBW1NjRbkSFdZOksL1N+ybPEE7UVGMwqvS6CP022Rp00Sb0tdiIkhSCe6NI8ow==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "8.17.0",
-        "@typescript-eslint/visitor-keys": "8.17.0",
+        "@typescript-eslint/types": "8.29.0",
+        "@typescript-eslint/visitor-keys": "8.29.0",
         "debug": "^4.3.4",
         "fast-glob": "^3.3.2",
         "is-glob": "^4.0.3",
         "minimatch": "^9.0.4",
         "semver": "^7.6.0",
-        "ts-api-utils": "^1.3.0"
+        "ts-api-utils": "^2.0.1"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1581,10 +1569,8 @@
         "type": "opencollective",
         "url": "https://opencollective.com/typescript-eslint"
       },
-      "peerDependenciesMeta": {
-        "typescript": {
-          "optional": true
-        }
+      "peerDependencies": {
+        "typescript": ">=4.8.4 <5.9.0"
       }
     },
     "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
@@ -1612,15 +1598,15 @@
       }
     },
     "node_modules/@typescript-eslint/utils": {
-      "version": "8.17.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.17.0.tgz",
-      "integrity": "sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w==",
+      "version": "8.29.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.29.0.tgz",
+      "integrity": "sha512-gX/A0Mz9Bskm8avSWFcK0gP7cZpbY4AIo6B0hWYFCaIsz750oaiWR4Jr2CI+PQhfW1CpcQr9OlfPS+kMFegjXA==",
       "dev": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.4.0",
-        "@typescript-eslint/scope-manager": "8.17.0",
-        "@typescript-eslint/types": "8.17.0",
-        "@typescript-eslint/typescript-estree": "8.17.0"
+        "@typescript-eslint/scope-manager": "8.29.0",
+        "@typescript-eslint/types": "8.29.0",
+        "@typescript-eslint/typescript-estree": "8.29.0"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1630,21 +1616,17 @@
         "url": "https://opencollective.com/typescript-eslint"
       },
       "peerDependencies": {
-        "eslint": "^8.57.0 || ^9.0.0"
-      },
-      "peerDependenciesMeta": {
-        "typescript": {
-          "optional": true
-        }
+        "eslint": "^8.57.0 || ^9.0.0",
+        "typescript": ">=4.8.4 <5.9.0"
       }
     },
     "node_modules/@typescript-eslint/visitor-keys": {
-      "version": "8.17.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz",
-      "integrity": "sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==",
+      "version": "8.29.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.0.tgz",
+      "integrity": "sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "8.17.0",
+        "@typescript-eslint/types": "8.29.0",
         "eslint-visitor-keys": "^4.2.0"
       },
       "engines": {
@@ -4700,15 +4682,15 @@
       }
     },
     "node_modules/ts-api-utils": {
-      "version": "1.4.3",
-      "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
-      "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==",
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
+      "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
       "dev": true,
       "engines": {
-        "node": ">=16"
+        "node": ">=18.12"
       },
       "peerDependencies": {
-        "typescript": ">=4.2.0"
+        "typescript": ">=4.8.4"
       }
     },
     "node_modules/type-check": {
@@ -4736,14 +4718,14 @@
       }
     },
     "node_modules/typescript-eslint": {
-      "version": "8.17.0",
-      "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.17.0.tgz",
-      "integrity": "sha512-409VXvFd/f1br1DCbuKNFqQpXICoTB+V51afcwG1pn1a3Cp92MqAUges3YjwEdQ0cMUoCIodjVDAYzyD8h3SYA==",
+      "version": "8.29.0",
+      "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.29.0.tgz",
+      "integrity": "sha512-ep9rVd9B4kQsZ7ZnWCVxUE/xDLUUUsRzE0poAeNu+4CkFErLfuvPt/qtm2EpnSyfvsR0S6QzDFSrPCFBwf64fg==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/eslint-plugin": "8.17.0",
-        "@typescript-eslint/parser": "8.17.0",
-        "@typescript-eslint/utils": "8.17.0"
+        "@typescript-eslint/eslint-plugin": "8.29.0",
+        "@typescript-eslint/parser": "8.29.0",
+        "@typescript-eslint/utils": "8.29.0"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -4753,12 +4735,8 @@
         "url": "https://opencollective.com/typescript-eslint"
       },
       "peerDependencies": {
-        "eslint": "^8.57.0 || ^9.0.0"
-      },
-      "peerDependenciesMeta": {
-        "typescript": {
-          "optional": true
-        }
+        "eslint": "^8.57.0 || ^9.0.0",
+        "typescript": ">=4.8.4 <5.9.0"
       }
     },
     "node_modules/undici-types": {
diff --git a/assets/package.json b/assets/package.json
index 3d8a9d68..5e4013dd 100644
--- a/assets/package.json
+++ b/assets/package.json
@@ -29,7 +29,7 @@
     "eslint": "^9.16.0",
     "stylelint": "^16.11.0",
     "stylelint-config-standard": "^36.0.1",
-    "typescript-eslint": "8.17.0",
+    "typescript-eslint": "8.29.0",
     "vitest": "^2.1.9",
     "vitest-fetch-mock": "^0.4.2"
   }

From 7216afa7838fc5da0ca439be3afe756517fac33a Mon Sep 17 00:00:00 2001
From: KoloMl <thekolomoets@hotmail.com>
Date: Wed, 2 Apr 2025 17:19:23 +0400
Subject: [PATCH 6/9] Installed `@vitest/eslint-plugin` 1.1.38

---
 assets/package-lock.json | 22 ++++++++++++++++++++++
 assets/package.json      |  1 +
 2 files changed, 23 insertions(+)

diff --git a/assets/package-lock.json b/assets/package-lock.json
index 5c47b98e..4e82d090 100644
--- a/assets/package-lock.json
+++ b/assets/package-lock.json
@@ -21,6 +21,7 @@
         "@testing-library/dom": "^10.4.0",
         "@testing-library/jest-dom": "^6.6.3",
         "@vitest/coverage-v8": "^2.1.9",
+        "@vitest/eslint-plugin": "^1.1.38",
         "eslint": "^9.16.0",
         "stylelint": "^16.11.0",
         "stylelint-config-standard": "^36.0.1",
@@ -1670,6 +1671,27 @@
         }
       }
     },
+    "node_modules/@vitest/eslint-plugin": {
+      "version": "1.1.38",
+      "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.1.38.tgz",
+      "integrity": "sha512-KcOTZyVz8RiM5HyriiDVrP1CyBGuhRxle+lBsmSs6NTJEO/8dKVAq+f5vQzHj1/Kc7bYXSDO6yBe62Zx0t5iaw==",
+      "dev": true,
+      "license": "MIT",
+      "peerDependencies": {
+        "@typescript-eslint/utils": "^8.24.0",
+        "eslint": ">= 8.57.0",
+        "typescript": ">= 5.0.0",
+        "vitest": "*"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        },
+        "vitest": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@vitest/expect": {
       "version": "2.1.9",
       "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz",
diff --git a/assets/package.json b/assets/package.json
index 5e4013dd..1477cd88 100644
--- a/assets/package.json
+++ b/assets/package.json
@@ -26,6 +26,7 @@
     "@testing-library/dom": "^10.4.0",
     "@testing-library/jest-dom": "^6.6.3",
     "@vitest/coverage-v8": "^2.1.9",
+    "@vitest/eslint-plugin": "^1.1.38",
     "eslint": "^9.16.0",
     "stylelint": "^16.11.0",
     "stylelint-config-standard": "^36.0.1",

From 5d76b5d9f11e3cea89a53bfb54473bc953565496 Mon Sep 17 00:00:00 2001
From: KoloMl <thekolomoets@hotmail.com>
Date: Wed, 2 Apr 2025 17:21:52 +0400
Subject: [PATCH 7/9] Replaced import from `eslint-plugin-vitest` to
 `@vitest/eslint-plugin`

---
 assets/eslint.config.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/assets/eslint.config.js b/assets/eslint.config.js
index 51cc1e26..c39019e8 100644
--- a/assets/eslint.config.js
+++ b/assets/eslint.config.js
@@ -1,5 +1,5 @@
 import tsEslint from 'typescript-eslint';
-import vitestPlugin from 'eslint-plugin-vitest';
+import vitestPlugin from '@vitest/eslint-plugin';
 import globals from 'globals';
 
 export default tsEslint.config(

From e68d7c3e1c9943edc76db00c3c07b4b201501c05 Mon Sep 17 00:00:00 2001
From: KoloMl <thekolomoets@hotmail.com>
Date: Wed, 2 Apr 2025 17:33:55 +0400
Subject: [PATCH 8/9] Moved `init()` directly to the `describe` block

As suggested by @MareStare
---
 assets/js/autocomplete/__tests__/keyboard.spec.ts | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/assets/js/autocomplete/__tests__/keyboard.spec.ts b/assets/js/autocomplete/__tests__/keyboard.spec.ts
index 3e135034..0eab9d02 100644
--- a/assets/js/autocomplete/__tests__/keyboard.spec.ts
+++ b/assets/js/autocomplete/__tests__/keyboard.spec.ts
@@ -1,11 +1,7 @@
 import { init, TestContext } from './context';
 
-describe('Autocomplete keyboard navigation', () => {
-  let ctx: TestContext;
-
-  beforeAll(async () => {
-    ctx = await init();
-  });
+describe('Autocomplete keyboard navigation', async () => {
+  const ctx: TestContext = await init();
 
   it('supports navigation via keyboard', async () => {
     await ctx.setInput('f');

From b69f7a3f17f1e8825cb6b3235307ecac6d1234ad Mon Sep 17 00:00:00 2001
From: KoloMl <thekolomoets@hotmail.com>
Date: Wed, 2 Apr 2025 18:20:48 +0400
Subject: [PATCH 9/9] Revert "Moved `init()` directly to the `describe` block"

This reverts commit e68d7c3e1c9943edc76db00c3c07b4b201501c05.
---
 assets/js/autocomplete/__tests__/keyboard.spec.ts | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/assets/js/autocomplete/__tests__/keyboard.spec.ts b/assets/js/autocomplete/__tests__/keyboard.spec.ts
index 0eab9d02..3e135034 100644
--- a/assets/js/autocomplete/__tests__/keyboard.spec.ts
+++ b/assets/js/autocomplete/__tests__/keyboard.spec.ts
@@ -1,7 +1,11 @@
 import { init, TestContext } from './context';
 
-describe('Autocomplete keyboard navigation', async () => {
-  const ctx: TestContext = await init();
+describe('Autocomplete keyboard navigation', () => {
+  let ctx: TestContext;
+
+  beforeAll(async () => {
+    ctx = await init();
+  });
 
   it('supports navigation via keyboard', async () => {
     await ctx.setInput('f');