adventure: show the item, not just its name and price
Renders what gogobee now sends: descriptions, the engine's own effect summary, slot and skill tags, and a Worn panel with the bond count. One row template for all three panels — worn, backpack, vault show the same facts and only the frame differs. The one distinction worth the wrapper struct: "inert" is a real problem state — the item is on you doing nothing because all three bonds are spoken for. The same item in a backpack isn't inert, it's just not worn yet. Rendering both the same would invent a problem the player doesn't have, on the exact panel they'd go to to fix it. An ItemView can't tell you which panel it's in, so itemRow carries it, and TestWhoInertOnlyWhenWorn pins it. Effect arrives resolved and Pete renders it as given. If it ever disagrees with what an item does in a fight, that's a gogobee bug — deriving it here from tier and slot would just be a second opinion that drifts. No migration: detail rides as a JSON blob.
This commit is contained in:
@@ -71,8 +71,23 @@ func seedWho(t *testing.T, owner string) *Server {
|
||||
if w := postDetail(t, s, "tok", detailPush{SnapshotAt: now, Players: []storage.PlayerDetail{{
|
||||
Localpart: owner,
|
||||
Token: "tok-josie",
|
||||
Inventory: []storage.ItemView{{Name: "Iron Ore", Type: "ore", Tier: 1, Value: 10}},
|
||||
Vault: []storage.ItemView{{Name: "Jeweled Crown", Type: "treasure", Tier: 4, Value: 5000}},
|
||||
Inventory: []storage.ItemView{
|
||||
{Name: "Iron Ore", Type: "ore", Tier: 1, Value: 10},
|
||||
{Name: "Miner's Pick", Type: "MasterworkGear", Tier: 3, Value: 300,
|
||||
Slot: "weapon", SkillSource: "mining", Desc: "A pick balanced for a long seam."},
|
||||
// Wants a bond, but it's in the backpack: not worn, so not inert.
|
||||
{Name: "Ring of Protection", Type: "ring", Tier: 4, Value: 900,
|
||||
Slot: "ring", Attunement: true, Effect: "-8% damage taken"},
|
||||
},
|
||||
Equipped: []storage.ItemView{
|
||||
{Name: "Flame Tongue", Type: "weapon", Value: 5000, Temper: 1, Slot: "weapon",
|
||||
Desc: "A sword that ignites on command.", Effect: "+15% damage",
|
||||
Attunement: true, Attuned: true},
|
||||
// Worn while the bond cap is full: on her, doing nothing.
|
||||
{Name: "Cloak of Elvenkind", Type: "wondrous", Value: 2000, Slot: "cloak",
|
||||
Effect: "faster to act, +4 HP", Attunement: true, Attuned: false},
|
||||
},
|
||||
Vault: []storage.ItemView{{Name: "Jeweled Crown", Type: "treasure", Tier: 4, Value: 5000}},
|
||||
House: storage.HouseView{Tier: 2, LoanBalance: 1500},
|
||||
Pets: []storage.PetView{{Type: "cat", Name: "Mittens", Level: 3}},
|
||||
}}}); w.Code != 200 {
|
||||
@@ -161,6 +176,48 @@ func TestWhoSelfUnlock(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestWhoItemDetail: the item panels carry the facts a player decides on —
|
||||
// descriptions, the engine's effect summary, slot and skill tags — and the worn
|
||||
// set renders with its bond count.
|
||||
func TestWhoItemDetail(t *testing.T) {
|
||||
s := seedWho(t, "josie")
|
||||
|
||||
body := getWho(t, s, "tok-josie", "josie").Body.String()
|
||||
for _, want := range []string{
|
||||
// "15% damage", not "+15%": html/template escapes a leading + to +.
|
||||
"Worn", "Flame Tongue", "A sword that ignites on command.", "15% damage",
|
||||
"1 of 3 bonds in use", // Flame Tongue is bonded; the Cloak is not
|
||||
"A pick balanced for a long seam.", "mining",
|
||||
} {
|
||||
if !strings.Contains(body, want) {
|
||||
t.Errorf("owner page missing item detail %q", want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestWhoInertOnlyWhenWorn pins the one distinction the panels can get wrong.
|
||||
// "Inert" is a real problem state — the item is on you and doing nothing because
|
||||
// every bond slot is full. An unworn attunement item is not inert, it's just not
|
||||
// worn; gogobee tracks bonds only on equipped rows, so its Attuned is undefined
|
||||
// rather than false. Rendering both the same would invent a problem the player
|
||||
// does not have, on the exact panel they'd use to fix it.
|
||||
func TestWhoInertOnlyWhenWorn(t *testing.T) {
|
||||
s := seedWho(t, "josie")
|
||||
|
||||
body := getWho(t, s, "tok-josie", "josie").Body.String()
|
||||
// The Cloak is worn with no bond free.
|
||||
if !strings.Contains(body, "inert") {
|
||||
t.Error("a worn item with no bond should be called out as inert")
|
||||
}
|
||||
if strings.Count(body, "inert") != 1 {
|
||||
t.Errorf("only the worn unbonded item is inert, got %d mentions", strings.Count(body, "inert"))
|
||||
}
|
||||
// The backpack Ring of Protection wants a bond but isn't worn.
|
||||
if !strings.Contains(body, "needs a bond") {
|
||||
t.Error("an unworn attunement item should say it needs a bond, not that it is inert")
|
||||
}
|
||||
}
|
||||
|
||||
// TestWhoSelfUnlockCaseInsensitive: Authentik may hand back a mixed-case
|
||||
// username; the localpart it maps to is lowercase. The owner must still unlock.
|
||||
func TestWhoSelfUnlockCaseInsensitive(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user