mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 13:33:42 +08:00
d921cf6559
Render tool menus from the catalog and update navigation, guides, and tests
115 lines
6.0 KiB
JavaScript
115 lines
6.0 KiB
JavaScript
import assert from "node:assert/strict"
|
|
import { test } from "node:test"
|
|
import { readFileSync } from "node:fs"
|
|
import { SettingsSearchIndex, searchSettings, toolSearchEntries, ToggleSearch } from "../web/js/toggle-search.js"
|
|
import { SETTINGS_SECTIONS } from "../web/js/settings.js"
|
|
|
|
const response = (page, rows, title = page) => ({ ok: true, status: 200, json: async () => ({ page, title, rows }) })
|
|
const catalogTools = JSON.parse(readFileSync(new URL("../web/data/catalog.json", import.meta.url), "utf8")).tools
|
|
|
|
test("global search reads every current settings section and nested page, then routes to its owner", async () => {
|
|
const pages = new Map()
|
|
for (const section of SETTINGS_SECTIONS) for (const page of section.pages) {
|
|
if (["ui_layout", "favorites"].includes(page)) continue
|
|
pages.set(page, response(page, [{ label: `Saved ${page} setting`, value: "Off", available: true }]))
|
|
}
|
|
pages.set("conditional", response("conditional", [{ label: "CEM options", page: "conditional/cem" }], "Conditional modes"))
|
|
pages.set("conditional/cem", response("conditional/cem", [{ label: "Remember manual choice", reason: "Saved choice", unit: "drives" }], "CEM"))
|
|
const requested = []
|
|
let state
|
|
const index = new SettingsSearchIndex({
|
|
fetcher: async (url, options) => {
|
|
assert.equal(options.credentials, "same-origin")
|
|
assert.equal(options.cache, "no-store")
|
|
requested.push(decodeURIComponent(url.split("/").at(-1)))
|
|
const page = pages.get(decodeURIComponent(url.split("/").at(-1)))
|
|
assert.ok(page, `unexpected settings page ${url}`)
|
|
return page
|
|
},
|
|
publish: (update) => { state = update },
|
|
})
|
|
await index.load()
|
|
assert.equal(state.status, "ready")
|
|
assert.deepEqual(new Set(requested), new Set(pages.keys()))
|
|
assert.deepEqual(searchSettings(state.entries, "manual choice").map(({ page, label, section }) => ({ page, label, section })),
|
|
[{ page: "conditional/cem", label: "Remember manual choice", section: "Longitudinal (Speed & Following)" }])
|
|
assert.equal(searchSettings(state.entries, "saved drives")[0].page, "conditional/cem")
|
|
assert.equal(searchSettings(state.entries, "torque")[0].page, "torque")
|
|
const opened = []
|
|
ToggleSearch.methods.choose.call({ state: { query: "manual", open: true }, openPage: (hit) => opened.push(hit.page) },
|
|
searchSettings(state.entries, "manual")[0])
|
|
assert.deepEqual(opened, ["conditional/cem"])
|
|
index.stop()
|
|
assert.deepEqual(state.entries, [])
|
|
})
|
|
|
|
test("search revokes cached labels on unauthorized response and ignores late reads", async () => {
|
|
let finish
|
|
const late = new Promise((resolve) => { finish = resolve })
|
|
const published = []
|
|
let unauthorized = 0
|
|
const index = new SettingsSearchIndex({
|
|
fetcher: (url) => url.endsWith("/aol") ? Promise.resolve({ ok: false, status: 401 }) : late,
|
|
publish: (update) => published.push(update),
|
|
unauthorized: () => { unauthorized++ },
|
|
})
|
|
const loading = index.load()
|
|
finish(response("lane", [{ label: "Private setting" }]))
|
|
await loading
|
|
assert.equal(unauthorized, 1)
|
|
assert.equal(index.status, "idle")
|
|
assert.deepEqual(index.entries, [])
|
|
assert.deepEqual(published.at(-1).entries, [])
|
|
})
|
|
|
|
test("keyboard selection and Escape are bounded to visible results", () => {
|
|
const chosen = []
|
|
let focused = false
|
|
const context = { state: { query: "lane", open: true, active: 0 }, showResults: true,
|
|
$nextTick: fn => fn(), $refs: { toggle: { getClientRects: () => [1], focus: () => { focused = true } } },
|
|
results: [{ page: "lane" }, { page: "lane_change" }], choose(hit) { chosen.push(hit.page) } }
|
|
const event = (key) => ({ key, prevented: false, preventDefault() { this.prevented = true } })
|
|
ToggleSearch.methods.onKey.call(context, event("ArrowDown"))
|
|
assert.equal(context.state.active, 1)
|
|
ToggleSearch.methods.onKey.call(context, event("Enter"))
|
|
assert.deepEqual(chosen, ["lane_change"])
|
|
ToggleSearch.methods.onKey.call(context, event("Escape"))
|
|
assert.equal(context.state.query, "")
|
|
assert.equal(context.state.open, false)
|
|
assert.equal(focused, true, "Escape restores focus to the compact search button")
|
|
})
|
|
|
|
test("global search finds visible tool tiles by title and description", () => {
|
|
const tools = [{ name: "Navigation & Maps", path: "/navigation", description: "Plan routes and download maps" },
|
|
{ name: "Model Manager", path: "/manage_models", description: "Manage models" }]
|
|
const entries = toolSearchEntries(tools)
|
|
assert.equal(searchSettings(entries, "navigation and maps")[0].path, "/navigation")
|
|
assert.equal(searchSettings(entries, "navigation & maps")[0].path, "/navigation")
|
|
assert.equal(searchSettings(entries, "download maps")[0].path, "/navigation")
|
|
assert.deepEqual(searchSettings(entries, "model manager").map(hit => hit.path), ["/manage_models"])
|
|
assert.ok(entries.every(hit => hit.kind === "Tool"))
|
|
assert.equal(entries[0].context, "")
|
|
})
|
|
|
|
test("nested camera tools appear as direct results with a useful location", () => {
|
|
const entries = toolSearchEntries(catalogTools.filter(tool => tool.path === "/cameras"))
|
|
const hits = searchSettings(entries, "V-ASM")
|
|
assert.equal(hits[0].path, "/cameras/vasm")
|
|
assert.equal(hits[0].kind, "Tool")
|
|
assert.equal(hits[0].context, "Cameras & Monitoring")
|
|
assert.equal(hits[1].path, "/cameras")
|
|
})
|
|
|
|
test("tool search walks every depth and indexes other tool sections", () => {
|
|
const tools = catalogTools.filter(tool => ["/logs", "/navigation"].includes(tool.path))
|
|
const entries = toolSearchEntries(tools)
|
|
assert.equal(searchSettings(entries, "crash reports")[0].path, "/logs/crashes")
|
|
assert.equal(searchSettings(entries, "offline maps")[0].path, "/navigation/maps")
|
|
assert.equal(searchSettings(entries, "destination")[0].label, "Destination")
|
|
const nested = toolSearchEntries([{ name: "Parent", path: "/parent", children: [
|
|
{ name: "Child", path: "/parent/child", children: [{ name: "Grandchild", path: "/parent/child/grandchild" }] },
|
|
] }])
|
|
assert.deepEqual(nested.map(hit => hit.path), ["/parent", "/parent/child", "/parent/child/grandchild"])
|
|
assert.equal(nested[2].context, "Parent · Child")
|
|
})
|