Token导航 LogoToken导航

Agent Skill · 前端设计

tui-testing(推测试)

用于辅助测试设计、自动化测试、用例整理和回归验证。它适合让 Agent 编写单元测试、端到端测试、测试计划或根据失败日志定位问题。使用时需要确认项目测试框架、运行命令和夹具数据,避免为了通过测试而改坏真实逻辑;涉及浏览器或外部服务时,应区分本地模拟、测试环境和生产环境。

tui-testing 是前端设计类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为只读;许可证信息还不明确,第三方扫描已通过,按安装量在前端设计类的 10,216 个 Skill 里排第 3,930。

总安装 1,058周安装 45GitHub Stars 24下载 371核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
unknown

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:tui-testing(推测试)
来源仓库:https://github.com/colonyops/hive
仓库路径:skills/tui-testing
安装命令:
npx skills add https://github.com/colonyops/hive --skill tui-testing
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/colonyops/hive --skill tui-testing

这个 Skill 是做什么的

前端设计类 Skill 关注页面结构、视觉规范和组件写法,输出通常是可运行的界面代码。

对测试进行分层 - 逻辑单元、行为组件、视觉效果 测试可观察的行为 - 不是实现细节 使用黄金文件进行视觉回归测试 为测试中的异步操作创建同步助手 测试边界 - 空、单个、满、溢出 设置实际尺寸 - 测试前始终调用 SetSize 使用测试实用程序 - StripANSI、KeyPress 助手、数据构建器 当多个键执行相同操作时测试两个键绑定 当外部工具不可用时优雅地跳过 将集成测试重点放在关键工作流程上,而不是每个组合上 每周安装量 45 存储库 群落/蜂巢 GitHub 之星 24 第一次看到 3天前 安全审计 Gen Agent Trust Hub 通行证 套接字通行证 斯尼克通行证

来源仓库内路径:skills/tui-testing

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/colonyops/hive --skill tui-testing;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它标注为只读,先用只读任务试一次,确认行为符合预期再扩大使用范围。

常见问题

6 条

tui-testing 怎么安装?

在终端执行 npx skills add https://github.com/colonyops/hive --skill tui-testing。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

tui-testing 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

tui-testing 需要什么权限?

权限级别是只读。该 Skill 主要提供规则、说明或参考内容,本身偏只读;真正读写文件、联网或执行命令仍取决于宿主 Agent 的任务。

tui-testing 是免费的吗?

公开资料没有标注明确许可证,收费和商用限制都无法确认。正式使用前请到来源仓库核对授权说明。

tui-testing 用的人多吗?

公开数据是累计安装 1,058 次,近一周安装 45 次,下载 371 次,GitHub 24 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

tui-testing 安全吗?

第三方扫描结果为通过,但这只覆盖已知的静态检查项,不能替代自己读一遍源码和安装命令。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 第三方扫描已通过,常见的高风险行为没有命中。
  • 标注为只读权限,试用阶段不容易误改本地文件。

先想清楚的地方

  • 许可证没有明确标注,能不能商用、能不能再分发都不确定。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理前端设计类任务、又不想每次都手写提示词的人。
  • 只想先试试效果、不希望它改动本地文件的人。

不太适合

  • 需要明确授权才能用到公司项目里的场景。
  • 需要厂商级技术支持和 SLA 的生产系统。

前端设计类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
frontend-design

前端设计 · Codex、Claude

8,823,360126,072只读MIT通过
vercel-composition-patterns

Vercel 组合模式 · Codex、Claude

3,858,81625,893需要联网MIT通过
vercel-react-native-skills

Vercel React native skills 前端 · Codex、Claude

2,468,66425,878只读MIT通过
adapt

adapt 搜索 · Codex、Claude

2,027,04023,125操作浏览器未明确通过
bolder

bolder 分析 · Codex、Claude

2,004,28823,130只读未明确通过
tui-testing

推测试 · 本页

1,05824只读未明确通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

TUI Testing Best Practices

Comprehensive testing strategies for Bubbletea v2 applications, based on the hive diff viewer implementation.

Testing Strategy Overview

Use a layered approach with different test types for different concerns:

Unit Tests          → Pure logic, state transformations
Component Tests     → Update/View behavior with synthetic messages
Golden File Tests   → Visual regression testing of rendered output
Integration Tests   → End-to-end workflows with teatest

Test Organization

File Structure

Match test files to implementation files:

internal/tui/diff/
├── diffviewer.go
├── diffviewer_test.go           # Component behavior tests
├── diffviewer_editor_test.go    # Feature-specific tests
├── filetree.go
├── filetree_test.go
├── lineparse.go
├── lineparse_test.go            # Pure function tests
├── model.go
├── model_test.go
└── testdata/                    # Golden files
    ├── TestFileTreeView_Empty.golden
    ├── TestFileTreeView_SingleFile.golden
    └── TestDiffViewerView_NormalMode.golden

Naming convention:

  • <component>_test.go - Main component tests
  • <component>_<feature>_test.go - Feature-specific tests
  • Test<Component><Method>_<Scenario> - Test function names
  • Test<Component><Method>_<Scenario>.golden - Golden file names

Unit Testing Pure Functions

Parse/Transform Logic

For functions that transform data without UI state:

func TestParseDiffLines_SimpleDiff(t *testing.T) {
    diff := `--- a/file.go
+++ b/file.go
@@ -1,3 +1,4 @@
 package main
 func main() {
+	fmt.Println("hello")
 }`

    lines, err := ParseDiffLines(diff)
    require.NoError(t, err)
    require.Len(t, lines, 7) // 2 headers + 1 hunk + 4 content lines

    // Test specific line properties
    assert.Equal(t, LineTypeFileHeader, lines[0].Type)
    assert.Equal(t, "--- a/file.go", lines[0].Content)

    assert.Equal(t, LineTypeAdd, lines[5].Type)
    assert.Equal(t, "\tfmt.Println(\"hello\")", lines[5].Content)
    assert.Equal(t, 0, lines[5].OldLineNum) // Not in old file
    assert.Equal(t, 3, lines[5].NewLineNum)
}

Key principles:

  • Use require.* for preconditions that must pass
  • Use assert.* for actual test conditions
  • Test edge cases (empty, single item, boundaries)
  • Test error conditions

Edge Cases to Cover

func TestParseDiffLines_EmptyDiff(t *testing.T) {
    lines, err := ParseDiffLines("")
    require.NoError(t, err)
    assert.Empty(t, lines)
}

func TestParseDiffLines_MultipleHunks(t *testing.T) {
    // Test line number tracking across hunks
}

func TestParseDiffLines_WithDeletions(t *testing.T) {
    // Test that deleted lines have NewLineNum = 0
}

Component Testing

Testing Update Logic

Test state transitions directly:

func TestDiffViewerScrollDown(t *testing.T) {
    file := &gitdiff.File{
        // ... file with 10 lines ...
    }

    m := NewDiffViewer(file)
    loadFileSync(&m, file)  // Helper for async loading
    m.SetSize(80, 8) // Height 8 = 3 header + 5 content

    // Initial position
    assert.Equal(t, 0, m.offset)
    assert.Equal(t, 0, m.cursorLine)

    // Move cursor down
    m, _ = m.Update(tea.KeyPressMsg(tea.Key{Code: 'j'}))
    assert.Equal(t, 1, m.cursorLine)
    assert.Equal(t, 0, m.offset) // Viewport doesn't scroll yet

    // Move to bottom of viewport (line 4)
    for i := 0; i < 3; i++ {
        m, _ = m.Update(tea.KeyPressMsg(tea.Key{Code: 'j'}))
    }
    assert.Equal(t, 4, m.cursorLine)
    assert.Equal(t, 0, m.offset)

    // One more scroll triggers viewport scroll
    m, _ = m.Update(tea.KeyPressMsg(tea.Key{Code: 'j'}))
    assert.Equal(t, 5, m.cursorLine)
    assert.Equal(t, 1, m.offset) // Viewport scrolled down
}

Test Helper Pattern

For async operations, create sync helpers:

// loadFileSync executes async loading synchronously for tests
func loadFileSync(m *DiffViewerModel, file *gitdiff.File) {
    cmd := m.SetFile(file)
    if cmd != nil {
        // Execute command to get message
        msg := cmd()
        // Apply message
        *m, _ = m.Update(msg)
    }
}

This lets tests control timing without dealing with async complexity.

Navigation Testing Pattern

func TestFileTreeNavigationDown(t *testing.T) {
    files := []*gitdiff.File{
        {NewName: "file1.go"},
        {NewName: "file2.go"},
        {NewName: "file3.go"},
    }

    m := NewFileTree(files, &config.Config{})
    assert.Equal(t, 0, m.selected)

    // Test down with 'j'
    m, _ = m.Update(tea.KeyPressMsg(tea.Key{Code: 'j'}))
    assert.Equal(t, 1, m.selected)

    // Test down with arrow key
    m, _ = m.Update(tea.KeyPressMsg(tea.Key{Code: tea.KeyDown}))
    assert.Equal(t, 2, m.selected)

    // Test boundary - can't go past last
    m, _ = m.Update(tea.KeyPressMsg(tea.Key{Code: 'j'}))
    assert.Equal(t, 2, m.selected) // Still at last item
}

Test both keybindings when multiple keys do the same thing (vim-style).

Golden File Testing

When to Use Golden Files

Golden files are ideal for:

  • Visual regression testing - Catch unintended rendering changes
  • Complex rendering logic - Easier than manual string building
  • Layout verification - Ensure components render correctly at different sizes

Basic Golden File Test

func TestFileTreeView_SingleFile(t *testing.T) {
    files := []*gitdiff.File{
        {NewName: "main.go"},
    }

    cfg := &config.Config{
        TUI: config.TUIConfig{},
    }

    m := NewFileTree(files, cfg)
    m.SetSize(40, 10)

    output := m.View()

    // Strip ANSI for readable golden files
    golden.RequireEqual(t, []byte(tuitest.StripANSI(output)))
}

Golden file (testdata/TestFileTreeView_SingleFile.golden):

 main.go

Selection and Highlighting Tests

For visual modes with highlighting:

func TestDiffViewerView_SingleLineSelection(t *testing.T) {
    file := createTestFile()

    m := NewDiffViewer(file)
    loadFileSync(&m, file)
    m.SetSize(80, 15)

    // Enter visual mode
    m, _ = m.Update(tea.KeyPressMsg(tea.Key{Code: 'v'}))
    assert.True(t, m.selectionMode)

    output := m.View()

    // Keep ANSI codes to verify highlighting
    golden.RequireEqual(t, []byte(output))
}

Decision point: Keep ANSI codes for highlighting tests, strip for layout tests.

Testing Multiple Scenarios

Use table-driven pattern with golden files:

func TestFileTreeView_Icons(t *testing.T) {
    tests := []struct {
        name      string
        iconStyle IconStyle
    }{
        {"ASCII", IconStyleASCII},
        {"NerdFonts", IconStyleNerdFonts},
    }

    files := []*gitdiff.File{
        {NewName: "main.go"},
        {NewName: "README.md"},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            m := NewFileTree(files, &config.Config{})
            m.iconStyle = tt.iconStyle
            m.SetSize(40, 10)

            output := m.View()
            golden.RequireEqual(t, []byte(tuitest.StripANSI(output)))
        })
    }
}

This generates:

  • testdata/TestFileTreeView_Icons/ASCII.golden
  • testdata/TestFileTreeView_Icons/NerdFonts.golden

Updating Golden Files

# Update all golden files
go test ./... -update

# Update specific test
go test ./internal/tui/diff -run TestFileTreeView_SingleFile -update

Test Utilities

Standard Test Helpers

Create shared utilities in pkg/tuitest:

// StripANSI removes escape codes and trailing whitespace
func StripANSI(s string) string {
    s = ansi.Strip(s)
    lines := strings.Split(s, "\n")
    var result []string
    for _, line := range lines {
        trimmed := strings.TrimRight(line, " ")
        result = append(result, trimmed)
    }
    return strings.TrimRight(strings.Join(result, "\n"), "\n")
}

// Helper functions for creating messages
func KeyPress(key rune) tea.Msg {
    return tea.KeyPressMsg(tea.Key{Code: key})
}

func KeyDown() tea.Msg {
    return tea.KeyPressMsg(tea.Key{Code: tea.KeyDown})
}

func WindowSize(w, h int) tea.WindowSizeMsg {
    return tea.WindowSizeMsg{Width: w, Height: h}
}

Test Data Builders

For complex test data:

func createTestFile() *gitdiff.File {
    return &gitdiff.File{
        OldName: "test.go",
        NewName: "test.go",
        TextFragments: []*gitdiff.TextFragment{
            {
                OldPosition: 1,
                OldLines:    3,
                NewPosition: 1,
                NewLines:    3,
                Lines: []gitdiff.Line{
                    {Op: gitdiff.OpContext, Line: "package main\n"},
                    {Op: gitdiff.OpDelete, Line: "old line\n"},
                    {Op: gitdiff.OpAdd, Line: "new line\n"},
                },
            },
        },
    }
}

func createMultiHunkFile() *gitdiff.File {
    // ... builder for multi-hunk scenarios
}

Testing Async Operations

Pattern: Synchronous Execution in Tests

func TestDiffViewerAsyncLoading(t *testing.T) {
    file := createLargeFile()
    m := NewDiffViewer(file)

    // SetFile returns a command
    cmd := m.SetFile(file)
    require.NotNil(t, cmd)

    // Execute synchronously
    msg := cmd()
    m, _ = m.Update(msg)

    // Verify content loaded
    assert.NotEmpty(t, m.content)
    assert.False(t, m.loading)
}

Testing Loading States

func TestDiffViewerLoadingState(t *testing.T) {
    m := NewDiffViewer(nil)

    // Before loading
    assert.False(t, m.loading)
    assert.Empty(t, m.content)

    // Initiate load (but don't execute command)
    file := createTestFile()
    cmd := m.SetFile(file)
    assert.NotNil(t, cmd)
    // Note: loading state is set when command executes,
    // not when it's created

    // After load completes
    msg := cmd()
    m, _ = m.Update(msg)
    assert.False(t, m.loading)
    assert.NotEmpty(t, m.content)
}

Testing External Tool Integration

Delta/Syntax Highlighting

func TestDeltaIntegration(t *testing.T) {
    // Skip if delta not available
    if err := CheckDeltaAvailable(); err != nil {
        t.Skip("delta not available")
    }

    diff := "--- a/file.go\n+++ b/file.go\n@@ -1 +1 @@\n-old\n+new\n"

    // Test with delta enabled
    highlighted, _ := ApplyDelta(diff)
    assert.NotEqual(t, diff, highlighted)
    assert.Contains(t, highlighted, "\x1b[") // Contains ANSI codes

    // Test without delta
    plain, _ := generateDiffContent(nil, false)
    assert.NotContains(t, plain, "\x1b[")
}

Mock External Dependencies

For tests that shouldn't depend on external tools:

func TestDiffViewerWithoutDelta(t *testing.T) {
    // Force delta unavailable
    m := NewDiffViewer(createTestFile())
    m.deltaAvailable = false

    cmd := m.SetFile(createTestFile())
    msg := cmd()
    m, _ = m.Update(msg)

    // Should still work, just without highlighting
    assert.NotEmpty(t, m.content)
}

Editor Integration Testing

Testing Editor Launch

func TestOpenInEditor(t *testing.T) {
    // Set test editor
    oldEditor := os.Getenv("EDITOR")
    defer os.Setenv("EDITOR", oldEditor)
    os.Setenv("EDITOR", "echo")

    m := NewDiffViewer(createTestFile())
    loadFileSync(&m, createTestFile())

    // Get line number to open
    lineNum := 5

    // Open editor command
    cmd := m.openInEditor("/tmp/test.go", lineNum)
    msg := cmd()

    // Should receive editor finished message
    if finishMsg, ok := msg.(editorFinishedMsg); ok {
        assert.NoError(t, finishMsg.err)
    }
}

Note: Use echo or similar non-interactive command for testing.

Component Boundary Testing

File Tree State

func TestFileTreeCollapse(t *testing.T) {
    files := []*gitdiff.File{
        {NewName: "src/main.go"},
        {NewName: "src/util.go"},
    }

    m := NewFileTree(files, &config.Config{})
    m.SetSize(40, 20)

    // Should start hierarchical and expanded
    assert.True(t, m.hierarchical)
    assert.NotEmpty(t, m.tree)
    assert.False(t, m.tree[0].Collapsed)

    // Collapse first directory
    m, _ = m.Update(tea.KeyPressMsg(tea.Key{Code: tea.KeyLeft}))

    // Directory should be collapsed
    assert.True(t, m.tree[0].Collapsed)

    // Selection should stay valid
    assert.GreaterOrEqual(t, m.selected, 0)
    assert.Less(t, m.selected, len(m.tree))
}

Integration Testing Patterns

End-to-End Workflows

func TestDiffReviewWorkflow(t *testing.T) {
    files := []*gitdiff.File{
        {NewName: "file1.go"},
        {NewName: "file2.go"},
    }

    m := New(files, &config.Config{})
    m.SetSize(120, 40)

    // 1. Start in file tree
    assert.Equal(t, FocusFileTree, m.focused)

    // 2. Navigate to second file
    m, _ = m.Update(tea.KeyPressMsg(tea.Key{Code: 'j'}))
    assert.Equal(t, 1, m.fileTree.selected)

    // 3. Switch to diff viewer
    m, _ = m.Update(tea.KeyPressMsg(tea.Key{Code: tea.KeyTab}))
    assert.Equal(t, FocusDiffViewer, m.focused)

    // 4. Scroll in diff viewer
    m, _ = m.Update(tea.KeyPressMsg(tea.Key{Code: 'j'}))
    assert.Equal(t, 1, m.diffViewer.cursorLine)

    // 5. Open help
    m, _ = m.Update(tea.KeyPressMsg(tea.Key{Code: '?'}))
    assert.True(t, m.showHelp)

    // 6. Close help
    m, _ = m.Update(tea.KeyPressMsg(tea.Key{Code: '?'}))
    assert.False(t, m.showHelp)
}

Common Testing Pitfalls

❌ Don't Test Implementation Details

// BAD: Testing internal state that could change
func TestDiffViewerInternals(t *testing.T) {
    m := NewDiffViewer(file)
    assert.NotNil(t, m.cache) // Implementation detail!
}

// GOOD: Test observable behavior
func TestDiffViewerCaching(t *testing.T) {
    m := NewDiffViewer(file)

    // First load
    cmd1 := m.SetFile(file)
    msg1 := cmd1()
    m, _ = m.Update(msg1)
    content1 := m.content

    // Second load of same file
    cmd2 := m.SetFile(file)
    msg2 := cmd2()
    m, _ = m.Update(msg2)
    content2 := m.content

    // Should get same content (implying cache worked)
    assert.Equal(t, content1, content2)
}

❌ Don't Ignore Dimensions

// BAD: Testing without setting size
func TestScrolling(t *testing.T) {
    m := NewDiffViewer(file)
    // m.height is 0, viewport calculations will break!
    m, _ = m.Update(tea.KeyPressMsg(tea.Key{Code: 'j'}))
}

// GOOD: Always set size before testing
func TestScrolling(t *testing.T) {
    m := NewDiffViewer(file)
    m.SetSize(80, 40) // Realistic dimensions
    m, _ = m.Update(tea.KeyPressMsg(tea.Key{Code: 'j'}))
}

❌ Don't Skip Boundaries

// GOOD: Test edge cases
func TestScrollBoundaries(t *testing.T) {
    // Test scroll up at top
    m.offset = 0
    m, _ = m.Update(tea.KeyPressMsg(tea.Key{Code: 'k'}))
    assert.Equal(t, 0, m.offset) // Shouldn't go negative

    // Test scroll down at bottom
    m.offset = len(m.lines) - m.contentHeight()
    m, _ = m.Update(tea.KeyPressMsg(tea.Key{Code: 'j'}))
    assert.Equal(t, len(m.lines)-m.contentHeight(), m.offset)
}

Test Coverage Goals

Aim for:

  • Unit tests: 100% for pure functions (parsers, transformers)
  • Component tests: 80%+ for Update logic (state transitions, navigation)
  • Golden files: Key scenarios for each component (normal, edge cases, modes)
  • Integration tests: Critical workflows only (don't test every combination)

Running Tests

# All tests
mise run test

# Watch specific tasks
mise watch test

# Specific package
go test ./internal/tui/diff

# Specific test
go test ./internal/tui/diff -run TestDiffViewerScrollDown

# With coverage
mise run coverage

# Update golden files
go test ./... -update

# Verbose output
go test ./internal/tui/diff -v

Summary

  1. Layer your tests - Unit for logic, component for behavior, golden for visuals
  2. Test observable behavior - Not implementation details
  3. Use golden files for visual regression testing
  4. Create sync helpers for async operations in tests
  5. Test boundaries - Empty, single, full, overflows
  6. Set realistic dimensions - Always call SetSize before testing
  7. Use test utilities - StripANSI, KeyPress helpers, data builders
  8. Test both keybindings when multiple keys do the same thing
  9. Skip gracefully when external tools unavailable
  10. Focus integration tests on critical workflows, not every combination

平台分布

Codex

35.54%
按下载量换算132

Claude

29.89%
按下载量换算111

Cursor

20.08%
按下载量换算74

Gemini CLI

9.25%
按下载量换算34

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

只读

该 Skill 主要提供规则、说明或参考内容,本身偏只读;真正读写文件、联网或执行命令仍取决于宿主 Agent 的任务。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。目前只有一个公开来源,建议同时核对源仓库。

继续浏览同类 Skills