mirror of
https://github.com/phranck/TUIkit.git
synced 2026-05-21 09:50:35 +00:00
ac5e63eb84
- Add ButtonRole (.cancel, .destructive) with SwiftUI-conformant API - Alert renders buttons horizontally, sorted by role (cancel left) - Alert max width capped at 60 characters for readability - ESC key dismisses alerts via AlertPresentationModifier handler - FocusManager: Left/Right arrows navigate within sections (like Up/Down) - Move completed plans to done/ directory - Update example pages with minor adjustments
173 lines
5.1 KiB
Swift
173 lines
5.1 KiB
Swift
// 🖥️ TUIKit — Terminal UI Kit for Swift
|
||
// ModalPresentationModifierTests.swift
|
||
//
|
||
// Created by LAYERED.work
|
||
// License: MIT dimming, centering, and arbitrary content.
|
||
//
|
||
|
||
import Testing
|
||
|
||
@testable import TUIkit
|
||
|
||
@MainActor
|
||
@Suite("ModalPresentationModifier Tests")
|
||
struct ModalPresentationModifierTests {
|
||
|
||
/// Helper to create a RenderContext with default test settings.
|
||
private func testContext() -> RenderContext {
|
||
RenderContext(
|
||
availableWidth: 80,
|
||
availableHeight: 24,
|
||
tuiContext: TUIContext()
|
||
)
|
||
}
|
||
|
||
/// Helper to render a view to a FrameBuffer.
|
||
private func render<V: View>(_ view: V) -> FrameBuffer {
|
||
renderToBuffer(view, context: testContext())
|
||
}
|
||
|
||
@Test("Modal not presented shows only base content")
|
||
func notPresentedShowsBase() {
|
||
let isPresented = Binding.constant(false)
|
||
let view = Text("Base Content")
|
||
.modal(isPresented: isPresented) {
|
||
Text("Modal Content")
|
||
}
|
||
|
||
let buffer = render(view)
|
||
let content = buffer.lines.joined(separator: "\n").stripped
|
||
|
||
#expect(content.contains("Base Content"))
|
||
#expect(!content.contains("Modal Content"))
|
||
}
|
||
|
||
@Test("Modal presented shows dimmed base with modal overlay")
|
||
func presentedShowsModal() {
|
||
let isPresented = Binding.constant(true)
|
||
let view = VStack {
|
||
Text("Base Content")
|
||
Text("More base text")
|
||
}
|
||
.modal(isPresented: isPresented) {
|
||
Text("Modal Content")
|
||
}
|
||
|
||
let buffer = render(view)
|
||
|
||
// Modal content should be present
|
||
let stripped = buffer.lines.joined(separator: "\n").stripped
|
||
#expect(stripped.contains("Modal Content"))
|
||
|
||
// Should have ANSI codes (from dimmed base and compositing)
|
||
let rawContent = buffer.lines.joined(separator: "\n")
|
||
#expect(rawContent.contains("\u{1B}[")) // Contains ANSI codes
|
||
}
|
||
|
||
@Test("Modal accepts arbitrary view content")
|
||
func arbitraryContent() {
|
||
let isPresented = Binding.constant(true)
|
||
let view = Text("Base")
|
||
.modal(isPresented: isPresented) {
|
||
VStack {
|
||
Text("Line 1")
|
||
Text("Line 2")
|
||
Text("Line 3")
|
||
}
|
||
}
|
||
|
||
let buffer = render(view)
|
||
let content = buffer.lines.joined(separator: "\n").stripped
|
||
|
||
#expect(content.contains("Line 1"))
|
||
#expect(content.contains("Line 2"))
|
||
#expect(content.contains("Line 3"))
|
||
}
|
||
|
||
@Test("Modal works with Dialog view")
|
||
func modalWithDialog() {
|
||
let isPresented = Binding.constant(true)
|
||
let view = Text("Base")
|
||
.modal(isPresented: isPresented) {
|
||
Dialog(title: "Settings") {
|
||
Text("Option 1")
|
||
Text("Option 2")
|
||
}
|
||
}
|
||
|
||
let buffer = render(view)
|
||
let content = buffer.lines.joined(separator: "\n").stripped
|
||
|
||
#expect(content.contains("Settings"))
|
||
#expect(content.contains("Option 1"))
|
||
#expect(content.contains("Option 2"))
|
||
}
|
||
|
||
@Test("Modal works with Alert view")
|
||
func modalWithAlert() {
|
||
let isPresented = Binding.constant(true)
|
||
let view = Text("Base")
|
||
.modal(isPresented: isPresented) {
|
||
Alert(title: "Warning", message: "Sure?") {
|
||
Button("Yes") { }
|
||
}
|
||
}
|
||
|
||
let buffer = render(view)
|
||
let content = buffer.lines.joined(separator: "\n").stripped
|
||
|
||
#expect(content.contains("Warning"))
|
||
#expect(content.contains("Sure?"))
|
||
#expect(content.contains("Yes"))
|
||
}
|
||
|
||
@Test("Toggle isPresented switches between states")
|
||
func togglePresentation() {
|
||
@State var showModal = false
|
||
|
||
let view1 = Text("Content")
|
||
.modal(isPresented: $showModal) {
|
||
Text("Modal")
|
||
}
|
||
|
||
let buffer1 = render(view1)
|
||
let content1 = buffer1.lines.joined(separator: "\n").stripped
|
||
|
||
// Initially not shown
|
||
#expect(!content1.contains("Modal"))
|
||
|
||
// Toggle to show
|
||
showModal = true
|
||
let view2 = Text("Content")
|
||
.modal(isPresented: $showModal) {
|
||
Text("Modal")
|
||
}
|
||
|
||
let buffer2 = render(view2)
|
||
let content2 = buffer2.lines.joined(separator: "\n").stripped
|
||
|
||
// Now shown
|
||
#expect(content2.contains("Modal"))
|
||
}
|
||
|
||
@Test("Modal centers content over base")
|
||
func centersContent() {
|
||
let isPresented = Binding.constant(true)
|
||
let view = VStack {
|
||
Text("Wide base content that spans multiple characters")
|
||
Text("Another line of wide content here")
|
||
}
|
||
.modal(isPresented: isPresented) {
|
||
Text("Small")
|
||
}
|
||
|
||
let buffer = render(view)
|
||
|
||
// Modal should be rendered (non-empty, contains both base and modal)
|
||
#expect(!buffer.isEmpty)
|
||
let content = buffer.lines.joined(separator: "\n").stripped
|
||
#expect(content.contains("Wide base content"))
|
||
#expect(content.contains("Small"))
|
||
}
|
||
}
|