WIP components (as built)
This commit is contained in:
109 files changed
+6509
-3
No files matched your search
@@ -0,0 +1,201 @@
|
||||
package mujica
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
)
|
||||
|
||||
var apiKeys = []APIKey{{ID: "k1", Name: "Deploy bot", Prefix: "mk_live_9f2c", Created: time.Date(2026, 9, 1, 0, 0, 0, 0, time.UTC)}}
|
||||
|
||||
func TestApiKeyManagerCreateRevealOnceAndRevoke(t *testing.T) {
|
||||
logs := accountLogs(t)
|
||||
const secret = "mk_live_9f2c_SECRET_DO_NOT_LOG"
|
||||
var s ApiKeyManagerState
|
||||
var created, revoked []string
|
||||
tt := accountTester(700, 600, func(c *ui.Context) {
|
||||
r := ApiKeyManager(c, &s, apiKeys, ApiKeyManagerOptions{})
|
||||
if name, ok := r.Create(); ok {
|
||||
created = append(created, name)
|
||||
}
|
||||
if id, ok := r.Revoked(); ok {
|
||||
revoked = append(revoked, id)
|
||||
}
|
||||
})
|
||||
if !tt.HasText("mk_live_9f2c••••••••••••") || !tt.HasText("Created 2026-09-01 · last used never") {
|
||||
t.Fatalf("masked list: %q", tt.Texts())
|
||||
}
|
||||
_ = tt.Click("Create key")
|
||||
tt.Type("CI")
|
||||
tt.Key(0, ui.KeyEnter)
|
||||
tt.Key(0, ui.KeyEnter)
|
||||
if len(created) != 1 || created[0] != "CI" {
|
||||
t.Fatalf("created %v", created)
|
||||
}
|
||||
s.Reveal(secret)
|
||||
tt.Frame()
|
||||
if s.secret != secret || !tt.HasText("Copy key") {
|
||||
t.Fatal("new key not shown")
|
||||
}
|
||||
if err := tt.Click("Copy key"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if tt.Clipboard() != secret {
|
||||
t.Fatal("copy did not take the key")
|
||||
}
|
||||
_ = tt.Click("I've saved it")
|
||||
if tt.HasText("Copy key") || s.secret != "" {
|
||||
t.Fatal("key still shown or kept after dismissal")
|
||||
}
|
||||
if strings.Contains(logs.String(), secret) {
|
||||
t.Fatal("the key reached the log")
|
||||
}
|
||||
_ = tt.Click("Revoke Deploy bot")
|
||||
if !tt.Focused("Cancel") {
|
||||
t.Fatal("revoke confirmation not focused on Cancel")
|
||||
}
|
||||
tt.Key(0, ui.KeyTab)
|
||||
tt.Key(0, ui.KeyEnter)
|
||||
if len(revoked) != 1 || revoked[0] != "k1" {
|
||||
t.Fatalf("revoked %v", revoked)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApiKeyManagerFailAndDisabled(t *testing.T) {
|
||||
var s ApiKeyManagerState
|
||||
disabled, made := false, 0
|
||||
tt := accountTester(700, 600, func(c *ui.Context) {
|
||||
if _, ok := ApiKeyManager(c, &s, nil, ApiKeyManagerOptions{Disabled: disabled}).Create(); ok {
|
||||
made++
|
||||
}
|
||||
})
|
||||
if !tt.HasText("No API keys") {
|
||||
t.Fatal("no empty state")
|
||||
}
|
||||
_ = tt.Click("Create key")
|
||||
tt.Type("x")
|
||||
tt.Key(0, ui.KeyEnter)
|
||||
tt.Key(0, ui.KeyEscape)
|
||||
if made != 1 || !s.pending {
|
||||
t.Fatal("escape left a pending create")
|
||||
}
|
||||
s.Fail("Key limit reached.")
|
||||
tt.Frame()
|
||||
if !tt.HasText("Key limit reached.") {
|
||||
t.Fatalf("failure: %q", tt.Texts())
|
||||
}
|
||||
tt.Key(0, ui.KeyEscape)
|
||||
disabled = true
|
||||
tt.Frame()
|
||||
_ = tt.Click("Create key")
|
||||
if tt.HasText("New API key") {
|
||||
t.Fatal("disabled manager opened the dialog")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApiKeyManagerSnapshot(t *testing.T) {
|
||||
snapshot(t, "api_key_manager", 560, 220, func(c *ui.Context) {
|
||||
s := ui.Local(c.Root(), "s", func() ApiKeyManagerState { return ApiKeyManagerState{} })
|
||||
ui.Column(c).Padding(16).Children(func() { ApiKeyManager(c, s, apiKeys, ApiKeyManagerOptions{}) })
|
||||
})
|
||||
}
|
||||
|
||||
func TestApiKeyManagerDisablingClosesEveryDialog(t *testing.T) {
|
||||
for _, phase := range []string{"name", "pending", "secret", "revoke"} {
|
||||
t.Run(phase, func(t *testing.T) {
|
||||
var s ApiKeyManagerState
|
||||
disabled := false
|
||||
tt := accountTester(700, 600, func(c *ui.Context) {
|
||||
ui.Column(c).Disabled(disabled).Children(func() { ApiKeyManager(c, &s, apiKeys, ApiKeyManagerOptions{}) })
|
||||
})
|
||||
if phase == "revoke" {
|
||||
if err := tt.Click("Revoke Deploy bot"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
if err := tt.Click("Create key"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if phase != "name" {
|
||||
tt.Type("CI")
|
||||
tt.Key(0, ui.KeyEnter)
|
||||
}
|
||||
if phase == "secret" {
|
||||
s.Reveal("one-time-test-secret")
|
||||
tt.Frame()
|
||||
}
|
||||
}
|
||||
disabled = true
|
||||
tt.Frame()
|
||||
if tt.HasText("New API key") || tt.HasText("Copy your new key") || tt.HasText("Revoke Deploy bot?") || s.secret != "" {
|
||||
t.Fatal("disabled manager retains an active dialog or secret")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestApiKeyManagerRemovedKeyCancelsConfirmation(t *testing.T) {
|
||||
var s ApiKeyManagerState
|
||||
keys := apiKeys
|
||||
tt := accountTester(700, 600, func(c *ui.Context) { ApiKeyManager(c, &s, keys, ApiKeyManagerOptions{}) })
|
||||
if err := tt.Click("Revoke Deploy bot"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
keys = nil
|
||||
tt.Frame()
|
||||
if s.revoking != "" || tt.HasText("Apps using this key stop working at once.") {
|
||||
t.Fatal("removed key keeps confirmation open")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApiKeyManagerLargeDataBuildsVisibleRows(t *testing.T) {
|
||||
items := make([]APIKey, 100000)
|
||||
for i := range items {
|
||||
items[i] = APIKey{ID: fmt.Sprint(i), Name: fmt.Sprintf("Key %d", i), Prefix: "public"}
|
||||
}
|
||||
var s ApiKeyManagerState
|
||||
tt := accountTester(600, 500, func(c *ui.Context) { ApiKeyManager(c, &s, items, ApiKeyManagerOptions{}) })
|
||||
if n := len(tt.Texts()); n > 100 {
|
||||
t.Fatalf("built %d text elements for a bounded viewport", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApiKeyManagerFailureSurvivesDisabledRequest(t *testing.T) {
|
||||
for _, whileDisabled := range []bool{false, true} {
|
||||
t.Run(fmt.Sprint(whileDisabled), func(t *testing.T) {
|
||||
var s ApiKeyManagerState
|
||||
disabled, requests := false, 0
|
||||
tt := accountTester(700, 600, func(c *ui.Context) {
|
||||
if _, ok := ApiKeyManager(c, &s, nil, ApiKeyManagerOptions{Disabled: disabled}).Create(); ok {
|
||||
requests++
|
||||
}
|
||||
})
|
||||
if err := tt.Click("Create key"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tt.Type("CI")
|
||||
tt.Key(0, ui.KeyEnter)
|
||||
disabled = true
|
||||
tt.Frame()
|
||||
if whileDisabled {
|
||||
s.Fail("Create failed")
|
||||
tt.Frame()
|
||||
}
|
||||
disabled = false
|
||||
if !whileDisabled {
|
||||
s.Fail("Create failed")
|
||||
}
|
||||
tt.Frame()
|
||||
if !tt.HasText("Create failed") || s.name != "CI" || s.pending {
|
||||
t.Fatal("disabled request lost its failure or draft")
|
||||
}
|
||||
tt.Key(0, ui.KeyEnter)
|
||||
if requests != 2 || !s.pending {
|
||||
t.Fatal("failed request cannot be retried")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user