- hypr/apps.lua - hypr/autostart.lua - hypr/envs.lua - hypr/hyprland.lua - hypr/hyprsunset.conf - hypr/input.lua - hypr/looknfeel.lua - hypr/omasettings.lua - hypr/xdph.conf - omarchy/branding/about.txt - omarchy/branding/screensaver.txt - omarchy/extensions/omarchy-menu.jsonc - omarchy/hooks/battery-low.d/play-warning-sound.sample - omarchy/hooks/font-set.d/show-font-notification.sample - omarchy/hooks/post-boot.d/weather.sample - omarchy/hooks/post-update.d/install-voxtype.hook - omarchy/hooks/post-update.d/setup-agent.hook - omarchy/hooks/post-update.d/setup-fingerprint.hook - omarchy/hooks/post-update.d/show-update-notification.sample - omarchy/hooks/pre-refresh-pacman.d/add-custom-repo.sample - omarchy/hooks/theme-set.d/show-theme-notification.sample - omarchy/shell.json - omarchy/shell.toml - omarchy/theme.name - omarchy/themes/azure-glow/README.md - omarchy/themes/azure-glow/alacritty.toml - omarchy/themes/azure-glow/btop.theme - omarchy/themes/azure-glow/hyprland.conf - omarchy/themes/azure-glow/hyprlock.conf - omarchy/themes/azure-glow/icons.theme - … 269 more
130 lines
7.0 KiB
JavaScript
130 lines
7.0 KiB
JavaScript
#!/usr/bin/env node
|
|
// Tests for folder parsing, filtering and payload assignment.
|
|
//
|
|
// node tests/folders.test.js
|
|
|
|
const fs = require("fs")
|
|
const path = require("path")
|
|
const panelSrc = fs.readFileSync(path.join(__dirname, "..", "Panel.qml"), "utf8")
|
|
const Model = {}
|
|
new Function("exports", fs.readFileSync(path.join(__dirname, "..", "BitwardenModel.js"), "utf8")
|
|
.replace(/^\.pragma library\s*$/m, "") + `
|
|
exports.parseFolders = parseFolders
|
|
exports.folderName = folderName
|
|
exports.filterItems = filterItems
|
|
exports.parseItems = parseItems
|
|
exports.parseItemDetail = parseItemDetail
|
|
exports.buildCreatePayload = buildCreatePayload
|
|
exports.buildEditPayload = buildEditPayload
|
|
exports.listFoldersCommand = listFoldersCommand
|
|
exports.createFolderCommand = createFolderCommand
|
|
exports.folderPayload = folderPayload
|
|
exports.folderEnvVar = folderEnvVar
|
|
`)(Model)
|
|
|
|
let pass = 0
|
|
const failures = []
|
|
const check = (l, ok, d) => ok ? pass++ : failures.push(`${l}\n ${d}`)
|
|
|
|
// --- parsing ---
|
|
const folders = Model.parseFolders(JSON.stringify([
|
|
{ id: "b", name: "Work", object: "folder" },
|
|
{ id: null, name: "No Folder", object: "folder" },
|
|
{ id: "a", name: "apps", object: "folder" },
|
|
]))
|
|
check("folders are sorted case-insensitively",
|
|
folders.map(f => f.name).join(",") === "apps,Work", folders.map(f => f.name).join(","))
|
|
check("bw's null-id 'No Folder' entry is dropped (the panel has its own control)",
|
|
folders.length === 2 && folders.every(f => f.id), JSON.stringify(folders))
|
|
check("malformed folder JSON yields an empty list",
|
|
Model.parseFolders("{{").length === 0 && Model.parseFolders("").length === 0, "expected []")
|
|
check("folderName resolves a known id", Model.folderName(folders, "b") === "Work", Model.folderName(folders, "b"))
|
|
check("folderName is empty for an unknown or absent id",
|
|
Model.folderName(folders, "zzz") === "" && Model.folderName(folders, null) === "", "expected empty")
|
|
|
|
// --- items carry folderId ---
|
|
const items = Model.parseItems(JSON.stringify([
|
|
{ id: "1", name: "In folder", type: 1, folderId: "a", login: {} },
|
|
{ id: "2", name: "Unfiled", type: 1, folderId: null, login: {} },
|
|
{ id: "3", name: "Other folder", type: 1, folderId: "b", login: {} },
|
|
]))
|
|
check("parseItems exposes folderId",
|
|
items.find(i => i.id === "1").folderId === "a" && items.find(i => i.id === "2").folderId === null,
|
|
JSON.stringify(items.map(i => [i.id, i.folderId])))
|
|
check("parseItemDetail exposes folderId",
|
|
Model.parseItemDetail(JSON.stringify({ id: "1", name: "x", type: 1, folderId: "a", login: {} })).folderId === "a",
|
|
"expected 'a'")
|
|
|
|
// --- filtering ---
|
|
const ids = f => Model.filterItems(items, "", "all", "all", f).map(i => i.id).sort().join(",")
|
|
check('folder "all" returns everything', ids("all") === "1,2,3", ids("all"))
|
|
check('folder "none" returns only unfiled items', ids("none") === "2", ids("none"))
|
|
check("a folder id returns only that folder", ids("a") === "1", ids("a"))
|
|
// Existing callers pass four arguments; folders must not break them.
|
|
check("omitting the folder argument behaves as 'all'",
|
|
Model.filterItems(items, "", "all", "all").map(i => i.id).sort().join(",") === "1,2,3",
|
|
"regression: existing four-arg calls changed behaviour")
|
|
check("folder filter composes with search",
|
|
Model.filterItems(items, "unfiled", "all", "all", "none").map(i => i.id).join(",") === "2",
|
|
"expected only item 2")
|
|
|
|
// --- payloads ---
|
|
check("create assigns the chosen folder",
|
|
Model.buildCreatePayload(1, "n", "u", "p", "", "", "", false, null, "a").folderId === "a", "expected 'a'")
|
|
for (const v of ["", null, "all", "none", undefined]) {
|
|
check(`create maps ${JSON.stringify(v)} to no folder`,
|
|
Model.buildCreatePayload(1, "n", "u", "p", "", "", "", false, null, v).folderId === null,
|
|
JSON.stringify(Model.buildCreatePayload(1, "n", "u", "p", "", "", "", false, null, v).folderId))
|
|
}
|
|
// Editing must be able to clear an assignment, not only set one.
|
|
const existing = { rawObject: { id: "1", name: "x", type: 1, folderId: "a", login: {} } }
|
|
check("edit can move an item to another folder",
|
|
Model.buildEditPayload(existing, "x", "", "", "", "", "", false, null, "b").folderId === "b", "expected 'b'")
|
|
check("edit can clear an existing folder assignment",
|
|
Model.buildEditPayload(existing, "x", "", "", "", "", "", false, null, "").folderId === null, "expected null")
|
|
|
|
// --- commands ---
|
|
// The session goes in BW_SESSION, never argv -- /proc/<pid>/cmdline is
|
|
// world-readable and the token unlocks the vault.
|
|
check("list folders carries no session on the command line and caps output",
|
|
Model.listFoldersCommand().join(" ").includes("bw list folders")
|
|
&& Model.listFoldersCommand().join(" ").includes("head -c")
|
|
&& !Model.listFoldersCommand().join(" ").includes("--session"),
|
|
Model.listFoldersCommand().join(" "))
|
|
const folderName = "it's \"private\""
|
|
check("folder names are serialized for the private environment payload",
|
|
JSON.parse(Model.folderPayload(folderName)).name === folderName,
|
|
Model.folderPayload(folderName))
|
|
check("folder names never enter the command line",
|
|
Model.createFolderCommand().join(" ").includes('"$' + Model.folderEnvVar() + '"')
|
|
&& !Model.createFolderCommand().join(" ").includes(folderName),
|
|
Model.createFolderCommand().join(" "))
|
|
check("the folder writer receives its payload through the private environment binding",
|
|
/function folderEnv\(\)[\s\S]{0,180}Model\.folderEnvVar\(\)[\s\S]{0,180}Model\.folderPayload\(newFolderName\)/.test(panelSrc)
|
|
&& /id:\s*createFolderProc[\s\S]{0,100}environment:\s*root\.folderEnv\(\)/.test(panelSrc),
|
|
"createFolderProc is not bound to folderEnv()")
|
|
|
|
// --- the collapsed filter buttons ---
|
|
// Each button names its own keyboard shortcut in its tooltip, and the key that
|
|
// actually opens the drawer lives in runShortcut(). Two places, so they can
|
|
// disagree -- and a tooltip promising a key that does nothing is worse than no
|
|
// tooltip. Pin them to each other.
|
|
const filterButtons = [...panelSrc.matchAll(
|
|
/VaultFilterButton\s*\{[\s\S]*?group:\s*"([a-z]+)"[\s\S]*?shortcut:\s*"([a-z])"/g)]
|
|
.map(m => ({ group: m[1], shortcut: m[2] }))
|
|
check("all three vault filter buttons are declared",
|
|
filterButtons.length === 3, JSON.stringify(filterButtons))
|
|
for (const { group, shortcut } of filterButtons) {
|
|
const dispatch = new RegExp(`case "${shortcut}":\\s*toggleFilterGroup\\("${group}"\\)`)
|
|
check(`the "${shortcut}" in the ${group} filter tooltip is the key that opens it`,
|
|
dispatch.test(panelSrc), `no runShortcut case pairing "${shortcut}" with "${group}"`)
|
|
}
|
|
// The label is the vault value alone now, so the group name survives only in
|
|
// the tooltip -- which is the one place still telling you what you are looking at.
|
|
check("each filter button still names its group somewhere the user can reach",
|
|
/tooltipText: Model\.plainLabel\(name \+ " filter \(" \+ shortcut \+ "\): " \+ value\)/.test(panelSrc),
|
|
"the filter tooltip no longer carries the group name and value")
|
|
|
|
console.log(`${pass} passed, ${failures.length} failed`)
|
|
if (failures.length) { console.error("\nFAILURES:\n " + failures.join("\n ")); process.exit(1) }
|