Add shell emoji tests
This commit is contained in:
@@ -104,7 +104,7 @@ Run focused automated tests for the area you changed. Current test entry points:
|
|||||||
- `bash test/cli.sh` - CLI routing, command metadata, theme helpers, and safe dispatch coverage
|
- `bash test/cli.sh` - CLI routing, command metadata, theme helpers, and safe dispatch coverage
|
||||||
- `bash test/shell.sh` - all Omarchy shell tests under `test/shell/`
|
- `bash test/shell.sh` - all Omarchy shell tests under `test/shell/`
|
||||||
|
|
||||||
New Omarchy shell tests should live in `test/shell/*-test.sh` so `test/shell.sh` picks them up automatically.
|
New Omarchy shell tests should live in `test/shell/*-test.sh` so `test/shell.sh` picks them up automatically. Source `test/shell/base-test.sh` for shared root-path discovery, assertions, and Node test helpers.
|
||||||
|
|
||||||
For visual changes, such as omarchy-shell styling, desktop appearance, screenshots, or screen recording flows, verify with the running UI in addition to automated tests. Take and analyze screenshots with `omarchy capture screenshot fullscreen save`. For animation, transitions, capture, or screen recording behavior, make a short recording with `omarchy screenrecord --fullscreen`, stop it with `omarchy screenrecord --stop-recording`, and review the output before finishing.
|
For visual changes, such as omarchy-shell styling, desktop appearance, screenshots, or screen recording flows, verify with the running UI in addition to automated tests. Take and analyze screenshots with `omarchy capture screenshot fullscreen save`. For animation, transitions, capture, or screen recording behavior, make a short recording with `omarchy screenrecord --fullscreen`, stop it with `omarchy screenrecord --stop-recording`, and review the output before finishing.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
function parseEmojis(raw) {
|
||||||
|
try {
|
||||||
|
var data = JSON.parse(String(raw || ""))
|
||||||
|
return Array.isArray(data) ? data : []
|
||||||
|
} catch (e) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizedQuery(query) {
|
||||||
|
return String(query || "").trim().toLowerCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
function keywordText(item) {
|
||||||
|
return String((item && item.k) || "").toLowerCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterEmojis(emojis, query, limit) {
|
||||||
|
var values = Array.isArray(emojis) ? emojis : []
|
||||||
|
var needle = normalizedQuery(query)
|
||||||
|
var max = limit === undefined || limit === null ? 1000 : Number(limit)
|
||||||
|
if (isNaN(max)) max = 1000
|
||||||
|
max = Math.max(0, max)
|
||||||
|
if (max === 0) return []
|
||||||
|
|
||||||
|
var out = []
|
||||||
|
|
||||||
|
for (var i = 0; i < values.length; i++) {
|
||||||
|
var item = values[i]
|
||||||
|
if (!item || !item.e) continue
|
||||||
|
if (!needle || keywordText(item).indexOf(needle) >= 0) {
|
||||||
|
out.push(item)
|
||||||
|
if (out.length >= max) break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof module !== "undefined") {
|
||||||
|
module.exports = {
|
||||||
|
parseEmojis: parseEmojis,
|
||||||
|
normalizedQuery: normalizedQuery,
|
||||||
|
filterEmojis: filterEmojis
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import Quickshell.Io
|
|||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import qs.Commons
|
import qs.Commons
|
||||||
|
import "EmojiSearch.js" as EmojiSearch
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
@@ -65,26 +66,12 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function loadEmojis(raw) {
|
function loadEmojis(raw) {
|
||||||
try {
|
root.emojis = EmojiSearch.parseEmojis(raw)
|
||||||
var data = JSON.parse(raw)
|
|
||||||
root.emojis = data || []
|
|
||||||
} catch (e) {
|
|
||||||
console.warn("Failed to parse emojis.json:", e)
|
|
||||||
root.emojis = []
|
|
||||||
}
|
|
||||||
if (root.opened) root.rebuildDisplay()
|
if (root.opened) root.rebuildDisplay()
|
||||||
}
|
}
|
||||||
|
|
||||||
function rebuildDisplay() {
|
function rebuildDisplay() {
|
||||||
var query = root.filterText.trim().toLowerCase()
|
var out = EmojiSearch.filterEmojis(root.emojis, root.filterText, 1000)
|
||||||
var out = []
|
|
||||||
for (var i = 0; i < root.emojis.length; i++) {
|
|
||||||
var item = root.emojis[i]
|
|
||||||
if (!query || item.k.indexOf(query) >= 0) {
|
|
||||||
out.push(item)
|
|
||||||
if (out.length >= 1000) break // limit to keep it fast
|
|
||||||
}
|
|
||||||
}
|
|
||||||
root.filteredEmojis = out
|
root.filteredEmojis = out
|
||||||
|
|
||||||
displayModel.clear()
|
displayModel.clear()
|
||||||
|
|||||||
+5
-1
@@ -6,7 +6,11 @@ ROOT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
|||||||
TEST_DIR="$ROOT/test/shell"
|
TEST_DIR="$ROOT/test/shell"
|
||||||
|
|
||||||
shopt -s nullglob
|
shopt -s nullglob
|
||||||
tests=("$TEST_DIR"/*-test.sh)
|
tests=()
|
||||||
|
for test in "$TEST_DIR"/*-test.sh; do
|
||||||
|
[[ $(basename "$test") == "base-test.sh" ]] && continue
|
||||||
|
tests+=("$test")
|
||||||
|
done
|
||||||
shopt -u nullglob
|
shopt -u nullglob
|
||||||
|
|
||||||
if (( ${#tests[@]} == 0 )); then
|
if (( ${#tests[@]} == 0 )); then
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [[ ${BASH_SOURCE[0]} == "$0" ]]; then
|
||||||
|
echo "source test/shell/base-test.sh from a shell test; do not run it directly" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ROOT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)
|
||||||
|
SHELL_TEST_DIR="$ROOT/test/shell"
|
||||||
|
|
||||||
|
export ROOT
|
||||||
|
|
||||||
|
pass() {
|
||||||
|
printf 'ok - %s\n' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
local description="$1"
|
||||||
|
local detail="${2:-}"
|
||||||
|
|
||||||
|
[[ -n $detail ]] && printf '%s\n' "$detail" >&2
|
||||||
|
printf 'not ok - %s\n' "$description" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
require_command() {
|
||||||
|
local command="$1"
|
||||||
|
|
||||||
|
command -v "$command" >/dev/null || fail "required command is available: $command"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_node_test() {
|
||||||
|
require_command node
|
||||||
|
|
||||||
|
{
|
||||||
|
cat <<'JS_PRELUDE'
|
||||||
|
const path = require('path')
|
||||||
|
const root = process.env.ROOT
|
||||||
|
|
||||||
|
function fail(description, detail) {
|
||||||
|
if (detail) console.error(detail)
|
||||||
|
console.error(`not ok - ${description}`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
function pass(description) {
|
||||||
|
console.log(`ok - ${description}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function assert(condition, description, detail) {
|
||||||
|
if (!condition) fail(description, detail)
|
||||||
|
pass(description)
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertEqual(actual, expected, description) {
|
||||||
|
assert(
|
||||||
|
actual === expected,
|
||||||
|
description,
|
||||||
|
`expected: ${expected}\nactual: ${actual}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertDeepEqual(actual, expected, description) {
|
||||||
|
const actualJson = JSON.stringify(actual)
|
||||||
|
const expectedJson = JSON.stringify(expected)
|
||||||
|
assert(
|
||||||
|
actualJson === expectedJson,
|
||||||
|
description,
|
||||||
|
`expected: ${expectedJson}\nactual: ${actualJson}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function requireFromRoot(relativePath) {
|
||||||
|
return require(path.join(root, relativePath))
|
||||||
|
}
|
||||||
|
|
||||||
|
JS_PRELUDE
|
||||||
|
cat
|
||||||
|
} | node
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||||
|
|
||||||
|
run_node_test <<'JS'
|
||||||
|
const fs = require('fs')
|
||||||
|
const emojis = requireFromRoot('shell/plugins/emojis/EmojiSearch.js')
|
||||||
|
|
||||||
|
const raw = fs.readFileSync(path.join(root, 'shell/plugins/emojis/emojis.json'), 'utf8')
|
||||||
|
const data = emojis.parseEmojis(raw)
|
||||||
|
|
||||||
|
assert(data.length > 1000, 'emoji dataset parses')
|
||||||
|
assertDeepEqual(emojis.parseEmojis('{'), [], 'invalid emoji JSON parses as empty list')
|
||||||
|
assertDeepEqual(emojis.parseEmojis('{"e":"nope"}'), [], 'non-array emoji JSON parses as empty list')
|
||||||
|
|
||||||
|
const fixture = [
|
||||||
|
{ e: 'a', k: 'grinning face smile happy' },
|
||||||
|
{ e: 'b', k: 'face with tears of joy joy tears' },
|
||||||
|
{ e: 'c', k: 'flag: united states us america' }
|
||||||
|
]
|
||||||
|
|
||||||
|
assertDeepEqual(
|
||||||
|
emojis.filterEmojis(fixture, ' JOY ').map(item => item.e),
|
||||||
|
['b'],
|
||||||
|
'emoji filtering trims and lowercases query'
|
||||||
|
)
|
||||||
|
|
||||||
|
assertDeepEqual(
|
||||||
|
emojis.filterEmojis(fixture, '', 2).map(item => item.e),
|
||||||
|
['a', 'b'],
|
||||||
|
'emoji filtering honors result limit'
|
||||||
|
)
|
||||||
|
|
||||||
|
assertDeepEqual(
|
||||||
|
emojis.filterEmojis(fixture, '', 0),
|
||||||
|
[],
|
||||||
|
'emoji filtering supports zero result limit'
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEqual(
|
||||||
|
emojis.filterEmojis(data, 'face with tears')[0].e,
|
||||||
|
'\u{1F602}',
|
||||||
|
'emoji filtering finds face with tears of joy'
|
||||||
|
)
|
||||||
|
JS
|
||||||
@@ -2,26 +2,10 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
ROOT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)
|
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||||
export ROOT
|
|
||||||
|
|
||||||
node <<'JS'
|
run_node_test <<'JS'
|
||||||
const search = require(`${process.env.ROOT}/shell/plugins/launcher/LauncherSearch.js`)
|
const search = requireFromRoot('shell/plugins/launcher/LauncherSearch.js')
|
||||||
|
|
||||||
function fail(description, detail) {
|
|
||||||
if (detail) console.error(detail)
|
|
||||||
console.error(`not ok - ${description}`)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
function pass(description) {
|
|
||||||
console.log(`ok - ${description}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
function assert(condition, description, detail) {
|
|
||||||
if (!condition) fail(description, detail)
|
|
||||||
pass(description)
|
|
||||||
}
|
|
||||||
|
|
||||||
const entries = [
|
const entries = [
|
||||||
{
|
{
|
||||||
@@ -69,11 +53,7 @@ const entries = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const contactMatches = search.sortedEntries(entries, 'contact').map(row => search.entryName(row.entry))
|
const contactMatches = search.sortedEntries(entries, 'contact').map(row => search.entryName(row.entry))
|
||||||
assert(
|
assertDeepEqual(contactMatches, ['Google Contacts'], 'contact search only returns direct contact matches')
|
||||||
contactMatches.length === 1 && contactMatches[0] === 'Google Contacts',
|
|
||||||
'contact search only returns direct contact matches',
|
|
||||||
`matches: ${contactMatches.join(', ')}`
|
|
||||||
)
|
|
||||||
|
|
||||||
assert(
|
assert(
|
||||||
search.fuzzyScore(entries[1], 'contact') < 0,
|
search.fuzzyScore(entries[1], 'contact') < 0,
|
||||||
@@ -81,14 +61,8 @@ assert(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const acronymMatches = search.sortedEntries(entries, 'gc').map(row => search.entryName(row.entry))
|
const acronymMatches = search.sortedEntries(entries, 'gc').map(row => search.entryName(row.entry))
|
||||||
assert(
|
assertEqual(acronymMatches[0], 'Google Contacts', 'short acronym matching still works')
|
||||||
acronymMatches[0] === 'Google Contacts',
|
|
||||||
'short acronym matching still works'
|
|
||||||
)
|
|
||||||
|
|
||||||
const directMatches = search.sortedEntries(entries, 'obs').map(row => search.entryName(row.entry))
|
const directMatches = search.sortedEntries(entries, 'obs').map(row => search.entryName(row.entry))
|
||||||
assert(
|
assertEqual(directMatches[0], 'OBS Studio', 'direct app-name matching still works')
|
||||||
directMatches[0] === 'OBS Studio',
|
|
||||||
'direct app-name matching still works'
|
|
||||||
)
|
|
||||||
JS
|
JS
|
||||||
|
|||||||
Reference in New Issue
Block a user