Skip to content
1 change: 1 addition & 0 deletions apps/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@tailwindcss/vite": "^4.1.11",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"fastest-levenshtein": "^1.0.16",
"lucide-react": "^0.541.0",
"next-themes": "^0.4.6",
"ramda": "^0.31.3",
Expand Down
93 changes: 93 additions & 0 deletions apps/frontend/src/__tests__/text-insertion.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { describe, it, expect } from "vitest"
import type { ValkeyCommand } from "@/types/valkey-commands"
import { insertCommandIntoText, extractCommandFromText } from "@/utils/text-insertion"

describe("text-insertion", () => {
describe("extractCommandFromText", () => {
it("should extract command from simple text", () => {
const result = extractCommandFromText("GET", 3)
expect(result).toBe("GET")
})

it("should extract partial command", () => {
const result = extractCommandFromText("GE", 2)
expect(result).toBe("GE")
})

it("should extract command with arguments", () => {
const result = extractCommandFromText("GET mykey", 3)
expect(result).toBe("GET")
})

it("should extract command from multi-line text", () => {
const result = extractCommandFromText("SET key1 value1\nGET", 19)
expect(result).toBe("GET")
})

it("should handle whitespace before command", () => {
const result = extractCommandFromText(" GET", 5)
expect(result).toBe("GET")
})

it("should return empty string for empty text", () => {
const result = extractCommandFromText("", 0)
expect(result).toBe("")
})
})

describe("insertCommandIntoText", () => {
const getCommand: ValkeyCommand = {
name: "GET",
syntax: "GET key",
category: "string",
description: "Get the value of a key",
parameters: [{ name: "key", type: "key", required: true, placeholder: "key" }],
tier: "read",
}

const pingCommand: ValkeyCommand = {
name: "PING",
syntax: "PING",
category: "connection",
description: "Ping the server",
parameters: [],
tier: "read",
}

it("should insert command without placeholder", () => {
const result = insertCommandIntoText("G", 1, getCommand)
expect(result.newText).toBe("GET")
expect(result.newCursorPosition).toBe(3)
})

it("should preserve existing arguments", () => {
const result = insertCommandIntoText("G mykey", 1, getCommand)
expect(result.newText).toBe("GET mykey")
expect(result.newCursorPosition).toBe(9)
})

it("should insert command without parameters", () => {
const result = insertCommandIntoText("PIN", 3, pingCommand)
expect(result.newText).toBe("PING")
expect(result.newCursorPosition).toBe(4)
})

it("should handle multi-line text", () => {
const result = insertCommandIntoText("SET key1 value1\nG", 17, getCommand)
expect(result.newText).toBe("SET key1 value1\nGET")
expect(result.newCursorPosition).toBe(19)
})

it("should handle whitespace before command", () => {
const result = insertCommandIntoText(" G", 3, getCommand)
expect(result.newText).toBe(" GET")
expect(result.newCursorPosition).toBe(5)
})

it("should replace partial command on same line", () => {
const result = insertCommandIntoText("GE", 2, getCommand)
expect(result.newText).toBe("GET")
expect(result.newCursorPosition).toBe(3)
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest"
import { renderHook } from "@testing-library/react"
import { Provider } from "react-redux"
import { CONNECTED, CONNECTING, DISCONNECTED, ERROR } from "@common/src/constants"
import useIsConnected from "./useIsConnected"
import useIsConnected from "../hooks/useIsConnected"
import { setupTestStore } from "@/test/utils/test-utils"
import { mockConnectionState } from "@/test/utils/mocks"
import { standaloneConnectFulfilled } from "@/state/valkey-features/connection/connectionSlice"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { renderHook } from "@testing-library/react"
import { Provider } from "react-redux"
import { BrowserRouter } from "react-router"
import { CONNECTED, CONNECTING, ERROR } from "@common/src/constants"
import { useValkeyConnectionNavigation } from "./useValkeyConnectionNavigation"
import { useValkeyConnectionNavigation } from "../hooks/useValkeyConnectionNavigation"
import { setupTestStore } from "@/test/utils/test-utils"
import { mockConnectionState } from "@/test/utils/mocks"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { renderHook } from "@testing-library/react"
import { Provider } from "react-redux"
import { BrowserRouter } from "react-router"
import { CONNECTED, CONNECTING, ERROR } from "@common/src/constants"
import { useWebSocketNavigation } from "./useWebSocketNavigation"
import { useWebSocketNavigation } from "../hooks/useWebSocketNavigation"
import { setupTestStore } from "@/test/utils/test-utils"
import { mockWebSocketState } from "@/test/utils/mocks"

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { describe, it, expect } from "vitest"
import {
getCommands,
searchCommands,
matchCommands
} from "../utils/valkey-command-matching"

describe("Valkey Command Filtering", () => {
describe("getCommands", () => {
it("should return only read and mutating commands when adminMode is false", () => {
const commands = getCommands({ adminMode: false })

commands.forEach((command) => {
expect(["read", "mutating"]).toContain(command.tier)
})
})

it("should return all commands including admin when adminMode is true", () => {
const allCommands = getCommands({ adminMode: true })
const nonAdminCommands = getCommands({ adminMode: false })

expect(allCommands.length).toBeGreaterThan(nonAdminCommands.length)

const hasAdminCommands = allCommands.some((c) => c.tier === "admin")
expect(hasAdminCommands).toBe(true)
})

it("should default to adminMode false when no options provided", () => {
const commands = getCommands()

commands.forEach((command) => {
expect(["read", "mutating"]).toContain(command.tier)
})
})
})

describe("searchCommands", () => {
it("should not return admin commands when adminMode is false", () => {
const results = searchCommands("FLUSH", { adminMode: false })

results.forEach((result) => {
expect(result.command.tier).not.toBe("admin")
})
})

it("should return admin commands when adminMode is true", () => {
const results = searchCommands("FLUSH", { adminMode: true })

const hasFlushDb = results.some((r) => r.command.name === "FLUSHDB")
const hasFlushAll = results.some((r) => r.command.name === "FLUSHALL")

expect(hasFlushDb || hasFlushAll).toBe(true)
})

it("should respect maxResults parameter", () => {
const results = searchCommands("S", { maxResults: 5 })

expect(results.length).toBeLessThanOrEqual(5)
})

it("should return empty array for empty query", () => {
const results = searchCommands("", { adminMode: true })

expect(results).toEqual([])
})
})

describe("matchCommands", () => {
it("should filter by adminMode parameter", () => {
const withoutAdmin = matchCommands("CONFIG", 10, false)
const withAdmin = matchCommands("CONFIG", 10, true)

expect(withAdmin.length).toBeGreaterThanOrEqual(withoutAdmin.length)
})

it("should prioritize prefix matches", () => {
const results = matchCommands("GET", 10, false)

if (results.length > 0) {
expect(results[0].command.name).toBe("GET")
expect(results[0].matchType).toBe("prefix")
}
})

it("should return contains matches", () => {
const results = matchCommands("SCAN", 10, false)

const hasScan = results.some((r) => r.command.name === "SCAN")
const hasHscan = results.some((r) => r.command.name === "HSCAN")
const hasSscan = results.some((r) => r.command.name === "SSCAN")
const hasZscan = results.some((r) => r.command.name === "ZSCAN")

expect(hasScan || hasHscan || hasSscan || hasZscan).toBe(true)
})
})
})
67 changes: 67 additions & 0 deletions apps/frontend/src/__tests__/valkey-command-matching.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { describe, it, expect } from "vitest"
import { matchCommands } from "../utils/valkey-command-matching"

describe("Valkey Command Matching", () => {
it("should return empty array for empty query", () => {
const results = matchCommands("")
expect(results).toEqual([])
})

it("should return empty array for whitespace query", () => {
const results = matchCommands(" ")
expect(results).toEqual([])
})

it("should find exact prefix matches", () => {
const results = matchCommands("GET")
expect(results.length).toBeGreaterThan(0)
expect(results[0].command.name).toBe("GET")
expect(results[0].matchType).toBe("prefix")
})

it("should prioritize prefix matches over contains matches", () => {
const results = matchCommands("SET")
const setCommand = results.find((r) => r.command.name === "SET")
const hsetCommand = results.find((r) => r.command.name === "HSET")

expect(setCommand).toBeDefined()
expect(hsetCommand).toBeDefined()

if (setCommand && hsetCommand) {
const setIndex = results.indexOf(setCommand)
const hsetIndex = results.indexOf(hsetCommand)
expect(setIndex).toBeLessThan(hsetIndex)
}
})

it("should limit results to maxResults parameter", () => {
const results = matchCommands("S", 5)
expect(results.length).toBeLessThanOrEqual(5)
})

it("should find contains matches", () => {
const results = matchCommands("PUSH")
expect(results.length).toBeGreaterThan(0)

const lpushResult = results.find((r) => r.command.name === "LPUSH")
const rpushResult = results.find((r) => r.command.name === "RPUSH")

expect(lpushResult).toBeDefined()
expect(rpushResult).toBeDefined()
expect(lpushResult?.matchType).toBe("contains")
expect(rpushResult?.matchType).toBe("contains")
})

it("should handle case insensitive matching", () => {
const results = matchCommands("get")
expect(results.length).toBeGreaterThan(0)
expect(results[0].command.name).toBe("GET")
})

it("should include highlight ranges for matches", () => {
const results = matchCommands("GET")
expect(results[0].highlightRanges).toBeDefined()
expect(results[0].highlightRanges.length).toBeGreaterThan(0)
expect(results[0].highlightRanges[0]).toEqual([0, 3])
})
})
Loading