Files
TUIkit/Tests/TUIkitTests/KeyEventTests.swift
T
phranck 80553547c1 Feat: List/Table focus bar, F-keys support, StatusBar defaults
List & Table:
- Replace focus indicator with full-width background bar (focusBackground)
- Change selection indicator from symbol to subtle background color
- Add Footer generic parameter and ViewBuilder initializers to List
- Environment values now propagate to child views

Keyboard:
- Add F1-F12 function key support (VT and SS3 escape sequences)
- Add Key.space case (previously parsed as .character(" "))
- Add Shortcut.f1 through Shortcut.f12

StatusBar:
- Change default style from .compact to .bordered
- Disable showAppearanceItem and showThemeItem by default
- Split StatusBarTests into 4 focused test files

Palette:
- Add focusBackground color for focus indication
2026-02-08 00:35:12 +01:00

311 lines
8.1 KiB
Swift

// 🖥️ TUIKit — Terminal UI Kit for Swift
// KeyEventTests.swift
//
// Created by LAYERED.work
// License: MIT
import Testing
@testable import TUIkit
// MARK: - Key Enum Tests
@MainActor
@Suite("Key Enum Tests")
struct KeyEnumTests {
@Test("Key.from creates character key")
func keyFromCharacter() {
let key = Key.from("a")
#expect(key == .character("a"))
}
@Test("Key is Hashable")
func keyHashable() {
let set: Set<Key> = [.enter, .tab, .escape, .enter]
#expect(set.count == 3) // .enter deduped
}
@Test("Character keys with different characters are not equal")
func characterKeysInequality() {
#expect(Key.character("a") != Key.character("b"))
}
@Test("Same character keys are equal")
func characterKeysEquality() {
let key1 = Key.character("x")
let key2 = Key.character("x")
#expect(key1 == key2)
}
@Test("Special keys are distinct")
func specialKeysDistinct() {
let keys: [Key] = [.escape, .enter, .tab, .backspace, .delete,
.up, .down, .left, .right,
.home, .end, .pageUp, .pageDown]
let set = Set(keys)
#expect(set.count == 13)
}
}
// MARK: - KeyEvent Creation Tests
@MainActor
@Suite("KeyEvent Creation Tests")
struct KeyEventCreationTests {
@Test("KeyEvent character init detects uppercase as shift")
func characterInitShift() {
let upper = KeyEvent(character: "A")
#expect(upper.shift == true)
#expect(upper.key == .character("A"))
let lower = KeyEvent(character: "a")
#expect(lower.shift == false)
#expect(lower.key == .character("a"))
}
@Test("KeyEvent character init with non-letter")
func characterInitNonLetter() {
let event = KeyEvent(character: "5")
#expect(event.key == .character("5"))
#expect(event.shift == false)
}
@Test("KeyEvent is Equatable")
func equatable() {
let eventA = KeyEvent(key: .enter, ctrl: false, alt: false, shift: false)
let eventB = KeyEvent(key: .enter)
#expect(eventA == eventB)
let eventC = KeyEvent(key: .enter, ctrl: true)
#expect(eventA != eventC)
}
}
// MARK: - KeyEvent Parsing Tests
@MainActor
@Suite("KeyEvent Parse Tests")
struct KeyEventParseTests {
@Test("Parse empty bytes returns nil")
func parseEmpty() {
#expect(KeyEvent.parse([]) == nil)
}
@Test("Parse escape byte")
func parseEscape() {
let event = KeyEvent.parse([0x1B])
#expect(event?.key == .escape)
}
@Test("Parse enter (carriage return)")
func parseEnter() {
let event = KeyEvent.parse([0x0D])
#expect(event?.key == .enter)
}
@Test("Parse enter (line feed)")
func parseLineFeed() {
let event = KeyEvent.parse([0x0A])
#expect(event?.key == .enter)
}
@Test("Parse tab")
func parseTab() {
let event = KeyEvent.parse([0x09])
#expect(event?.key == .tab)
}
@Test("Parse backspace (0x7F)")
func parseBackspaceDelete() {
let event = KeyEvent.parse([0x7F])
#expect(event?.key == .backspace)
}
@Test("Parse backspace (0x08)")
func parseBackspace() {
let event = KeyEvent.parse([0x08])
#expect(event?.key == .backspace)
}
@Test("Parse printable character")
func parsePrintable() {
let event = KeyEvent.parse([0x41]) // 'A'
#expect(event?.key == .character("A"))
#expect(event?.shift == true)
}
@Test("Parse lowercase character")
func parseLowercase() {
let event = KeyEvent.parse([0x61]) // 'a'
#expect(event?.key == .character("a"))
#expect(event?.shift == false)
}
@Test("Parse space character")
func parseSpace() {
let event = KeyEvent.parse([0x20])
#expect(event?.key == .space)
}
@Test("Parse Ctrl+A")
func parseCtrlA() {
let event = KeyEvent.parse([0x01])
#expect(event?.key == .character("a"))
#expect(event?.ctrl == true)
}
@Test("Parse Ctrl+C")
func parseCtrlC() {
let event = KeyEvent.parse([0x03])
#expect(event?.key == .character("c"))
#expect(event?.ctrl == true)
}
@Test("Parse Ctrl+Z")
func parseCtrlZ() {
let event = KeyEvent.parse([0x1A])
#expect(event?.key == .character("z"))
#expect(event?.ctrl == true)
}
// MARK: Arrow Keys (CSI Sequences)
@Test("Parse arrow up (ESC [ A)")
func parseArrowUp() {
let event = KeyEvent.parse([0x1B, 0x5B, 0x41])
#expect(event?.key == .up)
}
@Test("Parse arrow down (ESC [ B)")
func parseArrowDown() {
let event = KeyEvent.parse([0x1B, 0x5B, 0x42])
#expect(event?.key == .down)
}
@Test("Parse arrow right (ESC [ C)")
func parseArrowRight() {
let event = KeyEvent.parse([0x1B, 0x5B, 0x43])
#expect(event?.key == .right)
}
@Test("Parse arrow left (ESC [ D)")
func parseArrowLeft() {
let event = KeyEvent.parse([0x1B, 0x5B, 0x44])
#expect(event?.key == .left)
}
// MARK: Navigation Keys
@Test("Parse home (ESC [ H)")
func parseHome() {
let event = KeyEvent.parse([0x1B, 0x5B, 0x48])
#expect(event?.key == .home)
}
@Test("Parse end (ESC [ F)")
func parseEnd() {
let event = KeyEvent.parse([0x1B, 0x5B, 0x46])
#expect(event?.key == .end)
}
// MARK: Extended Keys (ESC [ n ~)
@Test("Parse home via extended (ESC [ 1 ~)")
func parseHomeExtended() {
let event = KeyEvent.parse([0x1B, 0x5B, 0x31, 0x7E])
#expect(event?.key == .home)
}
@Test("Parse delete (ESC [ 3 ~)")
func parseDelete() {
let event = KeyEvent.parse([0x1B, 0x5B, 0x33, 0x7E])
#expect(event?.key == .delete)
}
@Test("Parse end via extended (ESC [ 4 ~)")
func parseEndExtended() {
let event = KeyEvent.parse([0x1B, 0x5B, 0x34, 0x7E])
#expect(event?.key == .end)
}
@Test("Parse page up (ESC [ 5 ~)")
func parsePageUp() {
let event = KeyEvent.parse([0x1B, 0x5B, 0x35, 0x7E])
#expect(event?.key == .pageUp)
}
@Test("Parse page down (ESC [ 6 ~)")
func parsePageDown() {
let event = KeyEvent.parse([0x1B, 0x5B, 0x36, 0x7E])
#expect(event?.key == .pageDown)
}
@Test("Parse insert returns nil (ESC [ 2 ~)")
func parseInsertReturnsNil() {
let event = KeyEvent.parse([0x1B, 0x5B, 0x32, 0x7E])
#expect(event == nil)
}
// MARK: Alt+Key
@Test("Parse Alt+a")
func parseAltA() {
let event = KeyEvent.parse([0x1B, 0x61]) // ESC + 'a'
#expect(event?.key == .character("a"))
#expect(event?.alt == true)
}
@Test("Parse Alt+Enter")
func parseAltEnter() {
let event = KeyEvent.parse([0x1B, 0x0D]) // ESC + CR
#expect(event?.key == .enter)
#expect(event?.alt == true)
}
// MARK: UTF-8
@Test("Parse UTF-8 multi-byte character")
func parseUtf8() {
// "ü" = 0xC3 0xBC
let event = KeyEvent.parse([0xC3, 0xBC])
#expect(event?.key == .character("ü"))
}
@Test("Parse emoji UTF-8")
func parseEmoji() {
// "😀" = 0xF0 0x9F 0x98 0x80
let event = KeyEvent.parse([0xF0, 0x9F, 0x98, 0x80])
#expect(event?.key == .character("😀"))
}
// MARK: Edge Cases
@Test("Parse incomplete CSI returns nil")
func parseIncompleteCSI() {
let event = KeyEvent.parse([0x1B, 0x5B])
#expect(event == nil)
}
@Test("Parse Shift+Tab (CSI Z) returns tab with shift")
func parseShiftTab() {
let event = KeyEvent.parse([0x1B, 0x5B, 0x5A]) // ESC [ Z
#expect(event?.key == .tab)
#expect(event?.shift == true)
}
@Test("Parse unknown CSI final byte returns nil")
func parseUnknownCSI() {
let event = KeyEvent.parse([0x1B, 0x5B, 0x58]) // ESC [ X (not a valid sequence)
#expect(event == nil)
}
@Test("Parse bare escape sequence returns escape")
func parseBareEscape() {
let event = KeyEvent.parse([0x1B])
#expect(event?.key == .escape)
}
}