538 lines
18 KiB
Go
538 lines
18 KiB
Go
package main
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/charmbracelet/lipgloss"
|
|
"github.com/charmbracelet/x/ansi"
|
|
"github.com/muesli/termenv"
|
|
)
|
|
|
|
func TestDiffletExpressionsAndFramesHaveStableCellDimensions(t *testing.T) {
|
|
expressions := []DiffletExpression{
|
|
DiffletIdle, DiffletFocused, DiffletHappy, DiffletApproved,
|
|
DiffletSurprised, DiffletConcerned, DiffletConfused, DiffletSad,
|
|
DiffletAnnoyed, DiffletError, DiffletSleeping, DiffletCurious,
|
|
}
|
|
for _, expression := range expressions {
|
|
face := diffletFaces[expression]
|
|
if width := lipgloss.Width(face); width != 3 {
|
|
t.Errorf("%s face width = %d, want 3: %q", expression, width, face)
|
|
}
|
|
lines := (DiffletFrame{Expression: expression}).lines()
|
|
if len(lines) != diffletHeight {
|
|
t.Fatalf("%s has %d lines, want %d", expression, len(lines), diffletHeight)
|
|
}
|
|
for row, line := range lines {
|
|
if width := lipgloss.Width(line); width != diffletWidth {
|
|
t.Errorf("%s row %d width = %d, want %d: %q",
|
|
expression, row, width, diffletWidth, line)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDiffletCanonicalFramePreservesGlyphsAndSpacing(t *testing.T) {
|
|
got := (DiffletFrame{Expression: DiffletIdle}).lines()
|
|
want := []string{
|
|
"▄███████▄",
|
|
"█+ •_• -█",
|
|
"▀███████▀",
|
|
" ▝ ▘ ",
|
|
}
|
|
if strings.Join(got, "\n") != strings.Join(want, "\n") {
|
|
t.Fatalf("canonical frame:\n%q\nwant:\n%q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestDiffletStylesOnlyDiffSigns(t *testing.T) {
|
|
previousProfile := lipgloss.ColorProfile()
|
|
lipgloss.SetColorProfile(termenv.TrueColor)
|
|
t.Cleanup(func() { lipgloss.SetColorProfile(previousProfile) })
|
|
previousOK, previousBad := okStyle, badStyle
|
|
okStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#00ff00"))
|
|
badStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#ff0000"))
|
|
t.Cleanup(func() { okStyle, badStyle = previousOK, previousBad })
|
|
|
|
rendered := (DiffletFrame{Expression: DiffletIdle}).render()
|
|
wantMiddle := "█" + okStyle.Render("+") + " •_• " + badStyle.Render("-") + "█"
|
|
if rendered[1] != wantMiddle {
|
|
t.Fatalf("middle row = %q, want %q", rendered[1], wantMiddle)
|
|
}
|
|
if ansi.Strip(strings.Join(rendered, "\n")) !=
|
|
strings.Join((DiffletFrame{Expression: DiffletIdle}).lines(), "\n") {
|
|
t.Fatal("styling changed Difflet glyphs or spacing")
|
|
}
|
|
for _, row := range []int{0, 2, 3} {
|
|
if strings.Contains(rendered[row], "\x1b[") {
|
|
t.Fatalf("row %d unexpectedly styled: %q", row, rendered[row])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDiffletEnabledSitsRightOfWrappingHeader(t *testing.T) {
|
|
app := NewAppWithSettings(
|
|
&recordingService{}, "owner", "repository", false, 10, 10,
|
|
AppSettings{Mascot: true},
|
|
)
|
|
updated, _ := app.Update(tea.WindowSizeMsg{Width: 24, Height: 12})
|
|
app = updated.(App)
|
|
rendered := strings.Split(app.View(), "\n")
|
|
if len(rendered) < diffletHeight+2 {
|
|
t.Fatalf("rendered rows = %d, want at least %d", len(rendered), diffletHeight+2)
|
|
}
|
|
|
|
headerWidth := diffletHeaderWidth(app.width)
|
|
content := app
|
|
content.headerWidth = headerWidth
|
|
_, _, ok := splitHeader(content.viewContent())
|
|
if !ok {
|
|
t.Fatal("test view did not expose a header")
|
|
}
|
|
wantMascotLeft := headerWidth + diffletGap
|
|
for row, mascotLine := range (DiffletFrame{Expression: DiffletIdle}).lines() {
|
|
plain := ansi.Strip(rendered[row])
|
|
if !strings.Contains(plain, strings.TrimRight(mascotLine, " ")) {
|
|
t.Fatalf("mascot row %d is not visible: %q", row, plain)
|
|
}
|
|
left := strings.Index(plain, strings.TrimRight(mascotLine, " "))
|
|
if left != wantMascotLeft {
|
|
t.Fatalf("mascot row %d starts at %d, want adjacent position %d",
|
|
row, left, wantMascotLeft)
|
|
}
|
|
}
|
|
blankRow := -1
|
|
var headerText strings.Builder
|
|
for row := 0; row < len(rendered); row++ {
|
|
line := rendered[row]
|
|
plain := ansi.Strip(line)
|
|
if strings.TrimSpace(plain) == "" {
|
|
blankRow = row
|
|
break
|
|
}
|
|
headerText.WriteString(strings.TrimSpace(
|
|
ansi.Cut(plain, 0, wantMascotLeft-diffletGap),
|
|
))
|
|
}
|
|
normalizedHeader := strings.ReplaceAll(headerText.String(), " ", "")
|
|
for _, information := range []string{"diple", "owner/repository", "assignedtoyou"} {
|
|
if !strings.Contains(normalizedHeader, information) {
|
|
t.Fatalf("wrapped header does not contain %q: %s", information, normalizedHeader)
|
|
}
|
|
}
|
|
if blankRow < diffletHeight {
|
|
t.Fatalf("blank row = %d, want at or below mascot row %d",
|
|
blankRow, diffletHeight)
|
|
}
|
|
}
|
|
|
|
func TestDiffletIsCenteredAtNormalWidths(t *testing.T) {
|
|
mascot := (DiffletFrame{Expression: DiffletIdle}).lines()
|
|
const width = 40
|
|
headerWidth := diffletHeaderWidth(width)
|
|
rendered := strings.Split(
|
|
renderHeaderWithDifflet([]string{"diple"}, mascot, width, headerWidth, diffletGap),
|
|
"\n",
|
|
)
|
|
for row, line := range rendered[:diffletHeight] {
|
|
mascotText := strings.TrimRight(mascot[row], " ")
|
|
wantLeft := (width - diffletWidth) / 2
|
|
if left := strings.Index(ansi.Strip(line), mascotText); left != wantLeft {
|
|
t.Fatalf("row %d mascot starts at %d, want %d: %q",
|
|
row, left, wantLeft, line)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDiffletLongHeaderKeepsMascotCentered(t *testing.T) {
|
|
const width = 80
|
|
headerWidth := diffletHeaderWidth(width)
|
|
if headerWidth != 33 {
|
|
t.Fatalf("header width = %d, want 33", headerWidth)
|
|
}
|
|
mascot := (DiffletFrame{Expression: DiffletIdle}).lines()
|
|
rendered := strings.Split(
|
|
renderHeaderWithDifflet(
|
|
[]string{strings.Repeat("x", headerWidth)},
|
|
mascot,
|
|
width,
|
|
headerWidth,
|
|
diffletGap,
|
|
),
|
|
"\n",
|
|
)
|
|
mascotLeft := strings.Index(ansi.Strip(rendered[0]), strings.TrimRight(mascot[0], " "))
|
|
if mascotLeft != headerWidth+diffletGap {
|
|
t.Fatalf("mascot starts at %d, want %d", mascotLeft, headerWidth+diffletGap)
|
|
}
|
|
if mascotLeft != (width-diffletWidth)/2 {
|
|
t.Fatalf("mascot starts at %d, want centered position %d",
|
|
mascotLeft, (width-diffletWidth)/2)
|
|
}
|
|
}
|
|
|
|
func TestDiffletFallsBackToSmallRightPadding(t *testing.T) {
|
|
const width = 24
|
|
headerWidth := diffletHeaderWidth(width)
|
|
mascotLeft := headerWidth + diffletGap
|
|
if rightPadding := width - mascotLeft - diffletWidth; rightPadding != 1 {
|
|
t.Fatalf("fallback right padding = %d, want 1", rightPadding)
|
|
}
|
|
}
|
|
|
|
func TestDiffletDisabledPreservesViewExactly(t *testing.T) {
|
|
app := NewAppWithSettings(
|
|
&recordingService{}, "", "", false, 10, 10,
|
|
AppSettings{MascotExpressive: true, MascotAnimated: true},
|
|
)
|
|
updated, _ := app.Update(tea.WindowSizeMsg{Width: 20, Height: 12})
|
|
app = updated.(App)
|
|
got := app.View()
|
|
want := app.viewContent()
|
|
if got != want {
|
|
t.Fatalf("disabled mascot changed rendered view:\ngot:\n%q\nwant:\n%q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestDiffletDashboardIsCenteredBesideMetadata(t *testing.T) {
|
|
app := NewAppWithSettings(
|
|
&recordingService{}, "owner", "repository", false, 10, 10,
|
|
AppSettings{Mascot: true},
|
|
)
|
|
app.screen = dashboardScreen
|
|
app.loading = false
|
|
app.details = PRDetails{
|
|
PullRequest: PullRequest{
|
|
RepoWithOwner: "owner/repository", Number: 42,
|
|
Title: "A useful title", Author: "alice",
|
|
},
|
|
HeadRef: "feature", BaseRef: "main",
|
|
}
|
|
updated, _ := app.Update(tea.WindowSizeMsg{Width: 80, Height: 24})
|
|
app = updated.(App)
|
|
|
|
rendered := strings.Split(app.View(), "\n")
|
|
headerHeight := len(app.dashboardHeaderLines())
|
|
if strings.TrimSpace(ansi.Strip(rendered[headerHeight])) == "" {
|
|
t.Fatal("dashboard left a blank row between its title and metadata")
|
|
}
|
|
for row, mascotLine := range (DiffletFrame{Expression: DiffletIdle}).lines() {
|
|
plain := ansi.Strip(rendered[headerHeight+row])
|
|
mascotText := strings.TrimRight(mascotLine, " ")
|
|
mascotIndex := strings.Index(plain, mascotText)
|
|
left := -1
|
|
if mascotIndex >= 0 {
|
|
left = lipgloss.Width(plain[:mascotIndex])
|
|
}
|
|
if left != (app.width-diffletWidth)/2 {
|
|
t.Fatalf("row %d mascot starts at %d, want centered position %d: %q",
|
|
row, left, (app.width-diffletWidth)/2, plain)
|
|
}
|
|
}
|
|
for row, label := range []string{"author", "branches", "review"} {
|
|
if !strings.Contains(ansi.Strip(rendered[headerHeight+row]), label) {
|
|
t.Fatalf("dashboard row %d does not place %q beside mascot: %q",
|
|
row, label, ansi.Strip(rendered[headerHeight+row]))
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDiffletIsHiddenInEditorAndPopups(t *testing.T) {
|
|
app := NewAppWithSettings(
|
|
&recordingService{}, "owner", "repository", false, 10, 10,
|
|
AppSettings{Mascot: true},
|
|
)
|
|
app.screen = dashboardScreen
|
|
app.loading = false
|
|
app.details = PRDetails{PullRequest: PullRequest{
|
|
RepoWithOwner: "owner/repository", Number: 42, Title: "Title",
|
|
}}
|
|
updated, _ := app.Update(tea.WindowSizeMsg{Width: 80, Height: 24})
|
|
app = updated.(App)
|
|
|
|
for _, mode := range []writeMode{writePREdit, writeReplyConfirm} {
|
|
app.writeMode = mode
|
|
if got, want := app.View(), app.viewContent(); got != want {
|
|
t.Fatalf("write mode %d changed by enabled mascot:\ngot:\n%q\nwant:\n%q",
|
|
mode, got, want)
|
|
}
|
|
if strings.Contains(ansi.Strip(app.View()), "▄███████▄") {
|
|
t.Fatalf("write mode %d displayed the mascot", mode)
|
|
}
|
|
}
|
|
app.writeMode = writeNone
|
|
app.helpVisible = true
|
|
if got, want := app.View(), app.viewContent(); got != want {
|
|
t.Fatalf("help popup changed by enabled mascot:\ngot:\n%q\nwant:\n%q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestDiffletEnabledEditorCanRevealLastDescriptionRow(t *testing.T) {
|
|
app := NewAppWithSettings(
|
|
&recordingPRService{}, "owner", "repository", false, 10, 10,
|
|
AppSettings{Mascot: true},
|
|
)
|
|
app.screen = dashboardScreen
|
|
app.loading = false
|
|
app.width, app.height = 50, 12
|
|
app.details = PRDetails{
|
|
PullRequest: PullRequest{
|
|
ID: "pr", RepoWithOwner: "owner/repository", Number: 42,
|
|
Title: "Title",
|
|
},
|
|
BaseRef: "main",
|
|
Body: strings.Repeat("description row\n", 20) + "LAST DESCRIPTION ROW",
|
|
Permissions: ViewerPermissions{
|
|
CanUpdatePR: true,
|
|
},
|
|
}
|
|
app.startPREdit()
|
|
app.prEditEditors[prEditBodyField].Cursor =
|
|
len([]rune(app.prEditEditors[prEditBodyField].Text))
|
|
app.ensurePREditCursorVisible()
|
|
|
|
rendered := ansi.Strip(app.View())
|
|
if !strings.Contains(rendered, "LAST DESCRIPTION ROW") {
|
|
t.Fatalf("last description row is outside the editor viewport:\n%s", rendered)
|
|
}
|
|
if strings.Contains(rendered, "▄███████▄") {
|
|
t.Fatal("editor displayed the mascot instead of using its full height")
|
|
}
|
|
}
|
|
|
|
func TestDashboardDiffletRemainsCenteredAtNarrowWidths(t *testing.T) {
|
|
mascot := (DiffletFrame{Expression: DiffletIdle}).lines()
|
|
metadata := []string{"author", "branches", "review", "checks"}
|
|
for width := diffletWidth; width < 20; width++ {
|
|
rendered := renderDashboardMetadataWithDifflet(metadata, mascot, width)
|
|
for row, mascotLine := range mascot {
|
|
mascotText := strings.TrimRight(mascotLine, " ")
|
|
var mascotRow string
|
|
for _, line := range rendered {
|
|
if strings.Contains(line, mascotText) {
|
|
mascotRow = line
|
|
break
|
|
}
|
|
}
|
|
index := strings.Index(mascotRow, mascotText)
|
|
left := -1
|
|
if index >= 0 {
|
|
left = lipgloss.Width(mascotRow[:index])
|
|
}
|
|
if want := max(0, (width-diffletWidth)/2); left != want {
|
|
t.Fatalf("width %d row %d mascot starts at %d, want %d: %q",
|
|
width, row, left, want, mascotRow)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDashboardLoadingDiffletIsCentered(t *testing.T) {
|
|
app := NewAppWithSettings(
|
|
&recordingService{}, "owner", "repository", false, 10, 10,
|
|
AppSettings{Mascot: true},
|
|
)
|
|
app.screen = dashboardScreen
|
|
app.loading = true
|
|
app.details = PRDetails{PullRequest: PullRequest{
|
|
RepoWithOwner: "owner/repository", Number: 42, Title: "Title",
|
|
}}
|
|
updated, _ := app.Update(tea.WindowSizeMsg{Width: 80, Height: 24})
|
|
app = updated.(App)
|
|
|
|
rendered := strings.Split(app.View(), "\n")
|
|
mascotLine := strings.TrimRight((DiffletFrame{Expression: DiffletIdle}).lines()[0], " ")
|
|
for row, line := range rendered {
|
|
plain := ansi.Strip(line)
|
|
index := strings.Index(plain, mascotLine)
|
|
if index < 0 {
|
|
continue
|
|
}
|
|
if left := lipgloss.Width(plain[:index]); left != (app.width-diffletWidth)/2 {
|
|
t.Fatalf("loading mascot starts at %d, want %d: %q",
|
|
left, (app.width-diffletWidth)/2, plain)
|
|
}
|
|
if row != len(app.dashboardHeaderLines()) {
|
|
t.Fatalf("loading mascot begins on row %d, want %d",
|
|
row, len(app.dashboardHeaderLines()))
|
|
}
|
|
return
|
|
}
|
|
t.Fatal("loading dashboard did not display the mascot")
|
|
}
|
|
|
|
func TestDiffletHeaderMeasurementMatchesRenderedHeader(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
screen screen
|
|
}{
|
|
{name: "picker", screen: prScreen},
|
|
{name: "dashboard", screen: dashboardScreen},
|
|
{name: "threads", screen: threadScreen},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
app := NewAppWithSettings(
|
|
&recordingService{}, "owner", "repository", false, 10, 10,
|
|
AppSettings{Mascot: true},
|
|
)
|
|
app.screen = test.screen
|
|
app.details = PRDetails{PullRequest: PullRequest{
|
|
Owner: "owner", Repository: "repository",
|
|
RepoWithOwner: "owner/repository", Number: 42,
|
|
Title: "A pull request with a useful title",
|
|
}}
|
|
updated, _ := app.Update(tea.WindowSizeMsg{Width: 80, Height: 24})
|
|
app = updated.(App)
|
|
app.headerWidth = diffletHeaderWidth(app.width)
|
|
|
|
measured, ok := app.diffletHeaderLineCount()
|
|
if !ok {
|
|
t.Fatal("normal screen did not expose measurable header lines")
|
|
}
|
|
renderedHeader, _, ok := splitHeader(app.viewContent())
|
|
if !ok {
|
|
t.Fatal("rendered normal screen did not expose a header")
|
|
}
|
|
if measured != len(renderedHeader) {
|
|
t.Fatalf("measured header lines = %d, rendered = %d",
|
|
measured, len(renderedHeader))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDiffletKeepsCompleteFrameAtNarrowPhysicalMinimum(t *testing.T) {
|
|
app := NewAppWithSettings(
|
|
&recordingService{}, "", "", false, 10, 10,
|
|
AppSettings{Mascot: true},
|
|
)
|
|
updated, _ := app.Update(tea.WindowSizeMsg{Width: diffletWidth, Height: 12})
|
|
app = updated.(App)
|
|
rendered := strings.Split(app.View(), "\n")
|
|
for row, mascotLine := range (DiffletFrame{Expression: DiffletIdle}).lines() {
|
|
if got := ansi.Strip(rendered[row]); got != mascotLine {
|
|
t.Fatalf("mascot row %d = %q, want %q", row, got, mascotLine)
|
|
}
|
|
}
|
|
headerBelowMascot := ansi.Strip(strings.Join(rendered[diffletHeight:], "\n"))
|
|
normalizedHeader := strings.NewReplacer("\n", "", " ", "").Replace(headerBelowMascot)
|
|
if !strings.Contains(normalizedHeader, "diple") {
|
|
t.Fatalf("header did not wrap below complete mascot: %q", headerBelowMascot)
|
|
}
|
|
}
|
|
|
|
func TestDiffletStopsTicksBelowPhysicalMinimum(t *testing.T) {
|
|
app := NewAppWithSettings(
|
|
&recordingService{}, "", "", false, 10, 10,
|
|
AppSettings{Mascot: true, MascotAnimated: true},
|
|
)
|
|
if command := app.difflet.start(); command != nil {
|
|
t.Fatal("hidden startup mascot scheduled a tick")
|
|
}
|
|
updated, command := app.Update(tea.WindowSizeMsg{Width: 12, Height: 12})
|
|
app = updated.(App)
|
|
if command == nil {
|
|
t.Fatal("visible animated mascot did not schedule a tick")
|
|
}
|
|
generation := app.difflet.generation
|
|
updated, command = app.Update(tea.WindowSizeMsg{Width: 8, Height: 12})
|
|
app = updated.(App)
|
|
if command != nil {
|
|
t.Fatal("mascot below its nine-cell minimum scheduled a tick")
|
|
}
|
|
if app.difflet.visible {
|
|
t.Fatal("mascot below its nine-cell minimum remained visible")
|
|
}
|
|
updated, command = app.Update(diffletTickMsg{generation: generation})
|
|
app = updated.(App)
|
|
if command != nil || app.difflet.visible {
|
|
t.Fatal("stale tick restarted hidden mascot")
|
|
}
|
|
}
|
|
|
|
func TestDiffletStaticOptionsSelectFinalSafeExpression(t *testing.T) {
|
|
model := newDifflet(true, false, false)
|
|
if command := model.setState(diffletSuccess); command != nil {
|
|
t.Fatal("animation-disabled Difflet scheduled a tick")
|
|
}
|
|
if model.expression != DiffletHappy {
|
|
t.Fatalf("static success = %s, want %s", model.expression, DiffletHappy)
|
|
}
|
|
if command := model.setState(diffletRecoverableError); command != nil {
|
|
t.Fatal("non-expressive Difflet scheduled an error animation")
|
|
}
|
|
if model.expression != DiffletConcerned {
|
|
t.Fatalf("non-expressive error = %s, want %s", model.expression, DiffletConcerned)
|
|
}
|
|
model.setState(diffletFatalError)
|
|
if model.expression != DiffletError {
|
|
t.Fatalf("non-expressive fatal error = %s, want %s", model.expression, DiffletError)
|
|
}
|
|
}
|
|
|
|
func TestDiffletFocusedStateDoesNotLoop(t *testing.T) {
|
|
model := newDifflet(true, true, true)
|
|
if command := model.setState(diffletFocused); command != nil {
|
|
t.Fatal("focused state scheduled continuous animation")
|
|
}
|
|
if model.expression != DiffletFocused || len(model.animation.frames) != 0 {
|
|
t.Fatalf("focused state = expression %s, animation %#v",
|
|
model.expression, model.animation)
|
|
}
|
|
}
|
|
|
|
func TestDiffletLoadingFootShuffleSequence(t *testing.T) {
|
|
model := newDifflet(true, false, true)
|
|
if command := model.setState(diffletLoading); command == nil {
|
|
t.Fatal("loading animation did not schedule a tick")
|
|
}
|
|
want := []string{" ▝ ▘ ", " ▝ ▘ ", " ▝ ▘ ", " ▝ ▘ "}
|
|
for index, feet := range want {
|
|
if got := model.frameLines()[3]; got != feet {
|
|
t.Fatalf("loading frame %d feet = %q, want %q", index, got, feet)
|
|
}
|
|
if index < len(want)-1 {
|
|
if command := model.update(diffletTickMsg{generation: model.generation}); command == nil {
|
|
t.Fatalf("loading frame %d stopped looping", index)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDiffletOneShotStopsAndSettles(t *testing.T) {
|
|
model := newDifflet(true, true, true)
|
|
model.setState(diffletSuccess)
|
|
for index := 0; index < 3; index++ {
|
|
if command := model.update(diffletTickMsg{generation: model.generation}); command == nil {
|
|
t.Fatalf("success animation stopped at frame %d", index)
|
|
}
|
|
}
|
|
if command := model.update(diffletTickMsg{generation: model.generation}); command != nil {
|
|
t.Fatal("completed success animation scheduled another tick")
|
|
}
|
|
if len(model.animation.frames) != 0 || model.expression != DiffletHappy {
|
|
t.Fatalf("success settled with animation=%#v expression=%s",
|
|
model.animation, model.expression)
|
|
}
|
|
}
|
|
|
|
func TestDiffletLoopStopsOnStateChangeAndRejectsStaleTick(t *testing.T) {
|
|
model := newDifflet(true, true, true)
|
|
model.setState(diffletLoading)
|
|
oldGeneration := model.generation
|
|
if command := model.update(diffletTickMsg{generation: oldGeneration}); command == nil {
|
|
t.Fatal("loading animation did not continue")
|
|
}
|
|
model.setState(diffletConcerned)
|
|
if command := model.update(diffletTickMsg{generation: oldGeneration}); command != nil {
|
|
t.Fatal("stale tick scheduled another tick")
|
|
}
|
|
if model.expression != DiffletConcerned || model.frame != 0 {
|
|
t.Fatalf("stale tick changed newer state: expression=%s frame=%d",
|
|
model.expression, model.frame)
|
|
}
|
|
}
|