Add feedback components 106-130; inert scope, focus return, two-step deploy
This commit is contained in:
87 files changed
+2492
-147
No files matched your search
@@ -0,0 +1,269 @@
|
||||
package feedback_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/feedback"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/input"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
||||
)
|
||||
|
||||
func TestCountUpStepsOrJumps(t *testing.T) {
|
||||
for _, reduce := range []bool{false, true} {
|
||||
clk := newClock()
|
||||
v := 0
|
||||
tt := view(400, 100, clk, reduce, func(c *ui.Context) {
|
||||
feedback.CountUp(c, v, feedback.CountUpOptions{Slot: pixel.Ink})
|
||||
})
|
||||
v = 800
|
||||
wait(tt, clk, 0)
|
||||
r := find(t, tt, "800")
|
||||
mid := slots(t, tt.Image(), r)
|
||||
wait(tt, clk, time.Second)
|
||||
end := slots(t, tt.Image(), r)
|
||||
if counted := !equal(mid, end); counted == reduce {
|
||||
t.Errorf("reduce %v: counted in steps %v", reduce, counted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPulseStopsUnderReduceMotion(t *testing.T) {
|
||||
for _, reduce := range []bool{false, true} {
|
||||
clk := newClock()
|
||||
tt := view(300, 200, clk, reduce, func(c *ui.Context) {
|
||||
feedback.Pulse(c, feedback.PulseOptions{}, func() {
|
||||
ui.Box(c).Size(20, 20).Label("dot")
|
||||
}).Label("ring")
|
||||
})
|
||||
if moved := moves(t, tt, clk, "ring", 200*time.Millisecond); moved == reduce {
|
||||
t.Errorf("reduce %v: the ring moved %v", reduce, moved)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLevelUpShowsOnlyOnARise(t *testing.T) {
|
||||
clk := newClock()
|
||||
lv := 4
|
||||
tt := view(500, 200, clk, false, func(c *ui.Context) {
|
||||
feedback.LevelUp(c, lv, feedback.LevelUpOptions{})
|
||||
})
|
||||
lv = 3
|
||||
tt.Frame()
|
||||
if _, ok := tt.Find("LEVEL UP! LV 3"); ok {
|
||||
t.Fatal("LEVEL UP showed without a rise")
|
||||
}
|
||||
lv = 5
|
||||
tt.Frame()
|
||||
r := find(t, tt, "LEVEL UP! LV 5")
|
||||
if count(slots(t, tt.Image(), r), pixel.Accent) != 0 {
|
||||
t.Fatal("the banner did not start off its box")
|
||||
}
|
||||
wait(tt, clk, time.Second)
|
||||
if count(slots(t, tt.Image(), r), pixel.Accent) == 0 {
|
||||
t.Fatal("the banner did not slide in")
|
||||
}
|
||||
wait(tt, clk, 3*time.Second)
|
||||
if _, ok := tt.Find("LEVEL UP! LV 5"); ok {
|
||||
t.Fatal("the banner stayed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAchievementComesAndGoes(t *testing.T) {
|
||||
clk := newClock()
|
||||
cue := 0
|
||||
tt := view(500, 200, clk, true, func(c *ui.Context) {
|
||||
feedback.Achievement(c, "Slime Tamer", feedback.AchievementOptions{Cue: cue})
|
||||
})
|
||||
if _, ok := tt.Find("ACHIEVEMENT UNLOCKED Slime Tamer"); ok {
|
||||
t.Fatal("the card showed before its cue")
|
||||
}
|
||||
cue++
|
||||
tt.Frame()
|
||||
find(t, tt, "ACHIEVEMENT UNLOCKED Slime Tamer")
|
||||
wait(tt, clk, 3*time.Second)
|
||||
find(t, tt, "ACHIEVEMENT UNLOCKED Slime Tamer")
|
||||
wait(tt, clk, 2*time.Second)
|
||||
if _, ok := tt.Find("ACHIEVEMENT UNLOCKED Slime Tamer"); ok {
|
||||
t.Fatal("the card stayed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestComboCounterPopsOnARise(t *testing.T) {
|
||||
clk := newClock()
|
||||
n := 0
|
||||
tt := view(400, 200, clk, false, func(c *ui.Context) {
|
||||
feedback.ComboCounter(c, n, feedback.ComboCounterOptions{})
|
||||
})
|
||||
if _, ok := tt.Find("×0 COMBO!"); ok {
|
||||
t.Fatal("a zero combo showed")
|
||||
}
|
||||
n = 12
|
||||
tt.Frame()
|
||||
r := find(t, tt, "×12 COMBO!")
|
||||
popped := count(slots(t, tt.Image(), r), pixel.Warn)
|
||||
wait(tt, clk, 500*time.Millisecond)
|
||||
if rest := count(slots(t, tt.Image(), r), pixel.Warn); rest >= popped {
|
||||
t.Fatalf("no pop: %d Warn dots, then %d", popped, rest)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveIndicatorStates(t *testing.T) {
|
||||
saving := false
|
||||
tt := view(300, 100, newClock(), false, func(c *ui.Context) {
|
||||
feedback.SaveIndicator(c, saving, feedback.SaveIndicatorOptions{})
|
||||
})
|
||||
if _, ok := tt.Find("SAVED"); ok {
|
||||
t.Fatal("SAVED before any save")
|
||||
}
|
||||
saving = true
|
||||
tt.Frame()
|
||||
find(t, tt, "SAVING…")
|
||||
saving = false
|
||||
tt.Frame()
|
||||
find(t, tt, "SAVED")
|
||||
}
|
||||
|
||||
func TestBusyOverlayBlocksInputAndGivesFocusBack(t *testing.T) {
|
||||
busy, clicks, raw := false, 0, true
|
||||
tt := view(400, 200, newClock(), false, func(c *ui.Context) {
|
||||
input.Button(c, "top", input.ButtonOptions{})
|
||||
feedback.BusyOverlay(c, busy, feedback.BusyOverlayOptions{}, func() {
|
||||
if input.Button(c, "SAVE", input.ButtonOptions{}).Clicked {
|
||||
clicks++
|
||||
}
|
||||
// Plain boxes, not library controls.
|
||||
if ui.Box(c).Size(40, 20).Label("raw").Clicked() {
|
||||
clicks++
|
||||
}
|
||||
if raw && ui.Box(c).Size(40, 20).Label("rawkey").Focusable().Shortcut(0, ui.KeyEnter) {
|
||||
clicks++
|
||||
}
|
||||
})
|
||||
ui.Box(c).Size(4, 4).Label("elsewhere")
|
||||
input.Button(c, "after", input.ButtonOptions{})
|
||||
})
|
||||
// Focused counts ancestors; a sibling tells the two apart.
|
||||
own := func() bool { return tt.Focused("SAVE") && !tt.Focused("elsewhere") }
|
||||
click(t, tt, "rawkey")
|
||||
busy = true
|
||||
tt.Frame()
|
||||
tt.Key(0, ui.KeyEnter)
|
||||
if clicks != 0 || tt.Focused("rawkey") && !tt.Focused("elsewhere") {
|
||||
t.Fatal("a plain focusable inside kept the focus or its keys")
|
||||
}
|
||||
busy, raw = false, false
|
||||
tt.Frame()
|
||||
click(t, tt, "SAVE")
|
||||
busy = true
|
||||
tt.Frame()
|
||||
tt.Key(0, ui.KeyEnter)
|
||||
// Focus waits on the veil; Tab goes on from it.
|
||||
tt.Key(0, ui.KeyTab)
|
||||
if !tt.Focused("after") || tt.Focused("elsewhere") {
|
||||
t.Fatal("Tab did not pass over the busy content")
|
||||
}
|
||||
// Presses land on the veil, which keeps the focus.
|
||||
click(t, tt, "SAVE")
|
||||
click(t, tt, "raw")
|
||||
if clicks != 1 || own() {
|
||||
t.Fatalf("busy content took input: %d clicks, focus %v", clicks, own())
|
||||
}
|
||||
busy = false
|
||||
tt.Frame()
|
||||
if !own() {
|
||||
t.Fatal("the focus did not come back")
|
||||
}
|
||||
tt.Key(0, ui.KeyEnter)
|
||||
if clicks != 2 {
|
||||
t.Fatal("the content stayed blocked")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBusyContentIsOneTabStop(t *testing.T) {
|
||||
tt := view(400, 200, newClock(), false, func(c *ui.Context) {
|
||||
input.Button(c, "before", input.ButtonOptions{})
|
||||
feedback.BusyOverlay(c, true, feedback.BusyOverlayOptions{}, func() {
|
||||
ui.Box(c).Size(40, 20).Label("raw").Focusable()
|
||||
input.Button(c, "SAVE", input.ButtonOptions{})
|
||||
})
|
||||
input.Button(c, "after", input.ButtonOptions{})
|
||||
})
|
||||
click(t, tt, "before")
|
||||
tt.Key(0, ui.KeyTab)
|
||||
// FocusMark rings the veil two dots outside it.
|
||||
v := find(t, tt, "PLEASE WAIT")
|
||||
if count(slots(t, tt.Image(), ui.Rect{X: v.X, Y: v.Y + v.H + 2, W: v.W, H: 2}), pixel.Accent) == 0 {
|
||||
t.Fatal("the focused veil showed no focus")
|
||||
}
|
||||
tt.Key(0, ui.KeyTab)
|
||||
if !tt.Focused("after") || tt.Focused("before") {
|
||||
t.Fatal("two Tabs did not cross the busy content")
|
||||
}
|
||||
tt.Key(ui.Shift, ui.KeyTab)
|
||||
tt.Key(ui.Shift, ui.KeyTab)
|
||||
if !tt.Focused("before") || tt.Focused("after") {
|
||||
t.Fatal("two Shift+Tabs did not cross back")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadyGoCountsAndReportsDone(t *testing.T) {
|
||||
clk := newClock()
|
||||
cue, done := 0, 0
|
||||
tt := view(400, 300, clk, true, func(c *ui.Context) {
|
||||
if feedback.ReadyGo(c, feedback.ReadyGoOptions{Cue: cue}).Done {
|
||||
done++
|
||||
}
|
||||
})
|
||||
cue++
|
||||
tt.Frame()
|
||||
for _, want := range []string{"3", "2", "1", "GO!"} {
|
||||
find(t, tt, want)
|
||||
wait(tt, clk, 500*time.Millisecond)
|
||||
wait(tt, clk, 500*time.Millisecond)
|
||||
}
|
||||
if _, ok := tt.Find("GO!"); ok || done != 1 {
|
||||
t.Fatalf("GO! stayed, or Done came %d times", done)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadyGoReportsDoneAfterAJump(t *testing.T) {
|
||||
clk := newClock()
|
||||
cue, done := 0, 0
|
||||
tt := view(400, 300, clk, false, func(c *ui.Context) {
|
||||
if feedback.ReadyGo(c, feedback.ReadyGoOptions{Cue: cue}).Done {
|
||||
done++
|
||||
}
|
||||
})
|
||||
cue++
|
||||
tt.Frame()
|
||||
wait(tt, clk, 5*time.Second)
|
||||
wait(tt, clk, time.Second)
|
||||
if done != 1 {
|
||||
t.Fatalf("a jump past GO! gave Done %d times", done)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShakeJoltsUnlessReduced(t *testing.T) {
|
||||
for _, reduce := range []bool{false, true} {
|
||||
clk := newClock()
|
||||
cue := 0
|
||||
tt := view(300, 100, clk, reduce, func(c *ui.Context) {
|
||||
feedback.Shake(c, feedback.ShakeOptions{Cue: cue}, func() {
|
||||
ui.Box(c).Size(20, 20).Label("box")
|
||||
})
|
||||
})
|
||||
x := find(t, tt, "box").X
|
||||
cue++
|
||||
tt.Frame()
|
||||
if jolted := find(t, tt, "box").X != x; jolted == reduce {
|
||||
t.Errorf("reduce %v: jolted %v", reduce, jolted)
|
||||
}
|
||||
wait(tt, clk, time.Second)
|
||||
if find(t, tt, "box").X != x {
|
||||
t.Error("the box did not settle")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user