SymbolPicker: one well at rest, a 6x6 curated grid in a popover (leading well = the level default, clearing the key), and an optional search over the OS's full symbol inventory read from CoreGlyphs metadata. Geometry font-derived off StyleEditorLayout's base, grid enlarged by a deliberate 1.3x. Wired inline with the rename field in the board info popover through the StyleCommand funnel; the Title header retires. Curated set and the search's standing vs 03's full-browser refusal await ratification.
88 lines
3.8 KiB
Swift
88 lines
3.8 KiB
Swift
import Testing
|
|
@testable import Kanban
|
|
|
|
/// **The symbol picker's pure seams** (03-board-ui.md § Styling ▸ Controls, the general-purpose
|
|
/// picker `SymbolPicker.swift` builds beside the style editor's own curated grid): the curated
|
|
/// default set, the full-catalog loader and its cache, and the search filter's AND semantics.
|
|
/// SwiftUI rendering — the well grid, the popover's arrow-key navigation — is deliberately untested,
|
|
/// exactly as `StyleEditor.swift`'s own wells are.
|
|
@Suite("SymbolPicker ▸ the curated default set")
|
|
struct SymbolPickerCatalogDefaultSetTests {
|
|
|
|
@Test("Exactly 36 entries, all unique")
|
|
func shape() {
|
|
#expect(SymbolPickerCatalog.defaultSet.count == 36)
|
|
#expect(Set(SymbolPickerCatalog.defaultSet).count == SymbolPickerCatalog.defaultSet.count)
|
|
}
|
|
|
|
@Test("Every entry is one this system can actually draw — pins the list against typos")
|
|
func everyNameResolves() {
|
|
let missing = SymbolPickerCatalog.defaultSet.filter { !ItemSymbol.exists($0) }
|
|
#expect(missing.isEmpty, "unknown SF Symbol names: \(missing)")
|
|
#expect(SymbolPickerCatalog.available.count == SymbolPickerCatalog.defaultSet.count)
|
|
}
|
|
}
|
|
|
|
@Suite("SymbolPicker ▸ filter")
|
|
struct SymbolPickerFilterTests {
|
|
|
|
@Test("An empty or whitespace-only query returns the input unchanged")
|
|
func emptyQueryIsANoOp() {
|
|
let symbols = ["star", "flag", "heart"]
|
|
#expect(SymbolPickerCatalog.filter("", in: symbols) == symbols)
|
|
#expect(SymbolPickerCatalog.filter(" ", in: symbols) == symbols)
|
|
#expect(SymbolPickerCatalog.filter("\t\n", in: symbols) == symbols)
|
|
}
|
|
|
|
@Test("A single token matches case-insensitively, as a substring")
|
|
func singleTokenSubstring() {
|
|
let symbols = ["star", "star.fill", "flag", "flag.checkered"]
|
|
#expect(SymbolPickerCatalog.filter("star", in: symbols) == ["star", "star.fill"])
|
|
#expect(SymbolPickerCatalog.filter("STAR", in: symbols) == ["star", "star.fill"])
|
|
#expect(SymbolPickerCatalog.filter("Fla", in: symbols) == ["flag", "flag.checkered"])
|
|
}
|
|
|
|
@Test("Multiple tokens are an AND — every token must appear somewhere in the name")
|
|
func multiTokenIsAnAnd() {
|
|
let symbols = ["wrench.and.screwdriver", "screwdriver", "wrench"]
|
|
#expect(SymbolPickerCatalog.filter("wrench screw", in: symbols) == ["wrench.and.screwdriver"])
|
|
#expect(SymbolPickerCatalog.filter("screw wrench", in: symbols) == ["wrench.and.screwdriver"])
|
|
}
|
|
|
|
@Test("Input order is preserved")
|
|
func orderPreserved() {
|
|
let symbols = ["zebra.star", "apple.star", "mango.star"]
|
|
#expect(SymbolPickerCatalog.filter("star", in: symbols) == symbols)
|
|
}
|
|
|
|
@Test("No match returns an empty array")
|
|
func noMatchIsEmpty() {
|
|
#expect(SymbolPickerCatalog.filter("xyzzy-nonexistent", in: ["star", "flag"]).isEmpty)
|
|
}
|
|
}
|
|
|
|
@Suite("SymbolPicker ▸ the full catalog")
|
|
struct SymbolPickerFullCatalogTests {
|
|
|
|
@Test("The default path returns a sorted, unique, non-empty list containing well-known names")
|
|
func defaultPathLoads() {
|
|
let catalog = SymbolPickerCatalog.fullCatalog()
|
|
#expect(!catalog.isEmpty)
|
|
#expect(catalog == catalog.sorted())
|
|
#expect(Set(catalog).count == catalog.count)
|
|
#expect(catalog.contains("star"))
|
|
#expect(catalog.contains("folder"))
|
|
}
|
|
|
|
@Test("Two calls against the default path return identical results — the cache is coherent")
|
|
func cacheIsCoherent() {
|
|
#expect(SymbolPickerCatalog.fullCatalog() == SymbolPickerCatalog.fullCatalog())
|
|
}
|
|
|
|
@Test("A nonexistent bundle path falls back to the merged curated set, sorted and unique")
|
|
func nonexistentPathFallsBack() {
|
|
let expected = Set(SymbolPickerCatalog.defaultSet + CuratedSymbols.all).sorted()
|
|
#expect(SymbolPickerCatalog.fullCatalog(bundlePath: "/nonexistent") == expected)
|
|
}
|
|
}
|