diff --git a/main_test.go b/main_test.go index 86d36cd..cc2b041 100644 --- a/main_test.go +++ b/main_test.go @@ -291,6 +291,63 @@ func TestUIAPITokenDetailPanelHandlesMissingRemoveClickables(t *testing.T) { _ = u.apiTokenDetailPanel(gtx) } +func TestUIAPITokenDetailPanelResizesPolicyRemoveClickablesAcrossTokenSelection(t *testing.T) { + t.Parallel() + + u := newUIWithSession("desktop", &session.Manager{}) + u.masterPassword.SetText("correct horse battery staple") + if err := u.createVaultAction(); err != nil { + t.Fatalf("createVaultAction() error = %v", err) + } + + u.showAPITokensSection() + u.apiTokenName.SetText("CLI One") + u.apiTokenClientName.SetText("grpc-cli-1") + if err := u.issueAPITokenAction(); err != nil { + t.Fatalf("issueAPITokenAction() error = %v", err) + } + firstID := u.state.SelectedEntryID + u.apiPolicyOperation.SetText(string(apitokens.OperationListEntries)) + u.apiPolicyPath.SetText("Joe / codex") + u.apiPolicyAllow.Value = true + u.apiPolicyGroupScopeW.Value = true + if err := u.addAPIPolicyRuleAction(); err != nil { + t.Fatalf("addAPIPolicyRuleAction() error = %v", err) + } + + u.apiTokenName.SetText("CLI Two") + u.apiTokenClientName.SetText("grpc-cli-2") + if err := u.issueAPITokenAction(); err != nil { + t.Fatalf("issueAPITokenAction() error = %v", err) + } + secondID := u.state.SelectedEntryID + + ops := new(op.Ops) + gtx := layout.Context{ + Ops: ops, + Constraints: layout.Exact(image.Pt(800, 600)), + } + defer func() { + if r := recover(); r != nil { + t.Fatalf("apiTokenDetailPanel() panicked after token switch: %v", r) + } + }() + + u.state.SelectedEntryID = secondID + u.loadSelectedAPITokenIntoEditor() + if len(u.apiPolicyRemoves) != 0 { + t.Fatalf("len(apiPolicyRemoves) after selecting token without policies = %d, want 0", len(u.apiPolicyRemoves)) + } + _ = u.apiTokenDetailPanel(gtx) + + u.state.SelectedEntryID = firstID + u.loadSelectedAPITokenIntoEditor() + if len(u.apiPolicyRemoves) != 1 { + t.Fatalf("len(apiPolicyRemoves) after reselecting token with policy = %d, want 1", len(u.apiPolicyRemoves)) + } + _ = u.apiTokenDetailPanel(gtx) +} + func TestUIAPIAuditSectionShowsRecordedEvents(t *testing.T) { t.Parallel() diff --git a/ui_api.go b/ui_api.go index 921cb7b..5bde171 100644 --- a/ui_api.go +++ b/ui_api.go @@ -69,6 +69,20 @@ func (u *ui) selectedAPIToken() (apitokens.Token, bool) { return apitokens.Token{}, false } +func (u *ui) ensureAPIPolicyRemoveClickables(count int) []widget.Clickable { + if count <= 0 { + u.apiPolicyRemoves = nil + return nil + } + if len(u.apiPolicyRemoves) == count { + return u.apiPolicyRemoves + } + clicks := make([]widget.Clickable, count) + copy(clicks, u.apiPolicyRemoves) + u.apiPolicyRemoves = clicks + return clicks +} + func (u *ui) loadSelectedAPITokenIntoEditor() { token, ok := u.selectedAPIToken() if !ok { @@ -83,7 +97,7 @@ func (u *ui) loadSelectedAPITokenIntoEditor() { u.apiPolicyAllow.Value = true u.apiPolicyGroupScope = true u.apiPolicyGroupScopeW.Value = true - u.apiPolicyRemoves = nil + u.ensureAPIPolicyRemoveClickables(0) return } u.apiTokenName.SetText(token.Name) @@ -94,9 +108,7 @@ func (u *ui) loadSelectedAPITokenIntoEditor() { u.apiTokenExpiresAt.SetText("") } u.apiTokenDisabled.Value = token.Disabled - if len(u.apiPolicyRemoves) < len(token.Policies) { - u.apiPolicyRemoves = make([]widget.Clickable, len(token.Policies)) - } + u.ensureAPIPolicyRemoveClickables(len(token.Policies)) } func (u *ui) issueAPITokenAction() error { @@ -444,8 +456,9 @@ func (u *ui) apiAuditListPanel(gtx layout.Context) layout.Dimensions { func (u *ui) apiTokenDetailPanel(gtx layout.Context) layout.Dimensions { token, ok := u.selectedAPIToken() - if ok && len(u.apiPolicyRemoves) < len(token.Policies) { - u.apiPolicyRemoves = make([]widget.Clickable, len(token.Policies)) + removeClicks := u.ensureAPIPolicyRemoveClickables(0) + if ok { + removeClicks = u.ensureAPIPolicyRemoveClickables(len(token.Policies)) } rows := []layout.Widget{ func(gtx layout.Context) layout.Dimensions { @@ -548,13 +561,13 @@ func (u *ui) apiTokenDetailPanel(gtx layout.Context) layout.Dimensions { lbl := material.Label(u.theme, unit.Sp(13), ruleText) lbl.Color = mutedColor return lbl.Layout(gtx) - }), - layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return tonedButton(gtx, u.theme, &u.apiPolicyRemoves[index], "Remove") - }), - ) - }, + }), + layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return tonedButton(gtx, u.theme, &removeClicks[index], "Remove") + }), + ) + }, layout.Spacer{Height: unit.Dp(6)}.Layout, ) }