package gio import ( "fmt" "image" "testing" "gioui.org/layout" "gioui.org/op" "gioui.org/unit" "softbox.local/core/application" "softbox.local/core/domain" ) func TestAppShellFillsWindow(t *testing.T) { var operations op.Ops size := image.Pt(1080, 720) context := layout.Context{ Ops: &operations, Metric: unit.Metric{PxPerDp: 1, PxPerSp: 1}, Constraints: layout.Exact(size), } dimensions := NewAppShell("Modern").Layout(context, NewTheme()) if dimensions.Size != size { t.Fatalf("Layout() size = %v, want %v", dimensions.Size, size) } } func TestAppShellVirtualizesLargeCatalog(t *testing.T) { items := make([]application.CatalogListItem, 500) for index := range items { items[index] = application.CatalogListItem{ ID: fmt.Sprintf("app-%03d", index), Name: fmt.Sprintf("软件 %03d", index), Version: "1.0.0", Category: "工具", Tags: []string{"工具"}, Status: domain.StatusNotInstalled, Installable: true, } } shell := NewAppShell("Modern", items...) context := testContext(image.Pt(1080, 420)) shell.Layout(context, NewTheme()) if shell.lastRendered <= 0 || shell.lastRendered >= len(items) { t.Fatalf( "lastRendered = %d, want visible subset of %d", shell.lastRendered, len(items), ) } } func TestAppShellKeepsRowControlsByAppID(t *testing.T) { items := []application.CatalogListItem{ {ID: "app-one", Name: "One", Version: "1.0.0", Category: "工具"}, {ID: "app-two", Name: "Two", Version: "1.0.0", Category: "图像"}, } shell := NewAppShell("Modern", items...) original := shell.rows["app-two"] shell.ApplyIcon("app-one", image.NewNRGBA(image.Rect(0, 0, 16, 16))) shell.ApplyIcon("app-two", image.NewNRGBA(image.Rect(0, 0, 24, 24))) shell.model.SetCategory("图像") shell.Layout(testContext(image.Pt(1080, 720)), NewTheme()) if shell.rows["app-two"] != original { t.Fatal("row controls were recreated after filtering") } shell.SetItems([]application.CatalogListItem{ {ID: "app-two", Name: "Two", Version: "1.1.0", Category: "图像"}, }) if shell.rows["app-two"] != original { t.Fatal("row controls were recreated after snapshot update") } if _, exists := shell.rows["app-one"]; exists { t.Fatal("removed app retained row controls") } if _, exists := shell.icons["app-one"]; exists { t.Fatal("removed app retained prepared icon") } icon, exists := shell.icons["app-two"] if !exists { t.Fatal("retained app lost its prepared icon") } if icon.Size() != image.Pt(24, 24) { t.Fatalf("retained app icon size = %v", icon.Size()) } } func TestAppShellRendersSelectedDetailAndAppliedIcon(t *testing.T) { item := application.CatalogListItem{ ID: "json-parser", Name: "JSON解析工具", Description: "格式化并检查 JSON", Version: "1.2.0", Category: "开发工具", Tags: []string{"JSON", "格式化"}, Homepage: "https://example.invalid/json-parser", Tutorial: "https://example.invalid/json-parser/tutorial", Status: domain.StatusInstalled, Installed: true, } shell := NewAppShell("Modern", item) icon := image.NewNRGBA(image.Rect(0, 0, 32, 32)) shell.ApplyIcon(item.ID, icon) shell.model.Select(item.ID) shell.Layout(testContext(image.Pt(1280, 800)), NewTheme()) if !shell.detailRendered { t.Fatal("selected app detail was not rendered") } if _, exists := shell.icons[item.ID]; !exists { t.Fatal("ApplyIcon did not retain the prepared image operation") } shell.ApplyIcon(item.ID, nil) if _, exists := shell.icons[item.ID]; exists { t.Fatal("ApplyIcon(nil) did not remove the image") } } func testContext(size image.Point) layout.Context { var operations op.Ops return layout.Context{ Ops: &operations, Metric: unit.Metric{PxPerDp: 1, PxPerSp: 1}, Constraints: layout.Exact(size), } }