mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-21 14:07:20 +08:00
fix(jetbrains): improve worktree PR tooltips
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Show pull request titles and clearer multi-line badge tooltips in the Agent Manager worktree list.
|
||||
+14
-12
@@ -96,7 +96,7 @@ class KiloWorktreeRpcApiImpl : KiloWorktreeRpcApi {
|
||||
var status = GhAvailability.OK
|
||||
val data = parallel(items) { item ->
|
||||
if (status != GhAvailability.OK) return@parallel null
|
||||
val out = runGh(Path.of(item.path).normalize(), "pr", "view", item.branch, "--json", "number,state,isDraft,url")
|
||||
val out = runGh(Path.of(item.path).normalize(), "pr", "view", item.branch, "--json", "number,state,isDraft,url,title")
|
||||
if (!out.ok) {
|
||||
when (prError(out.stderr)) {
|
||||
GhAvailability.UNAUTH -> status = GhAvailability.UNAUTH
|
||||
@@ -318,18 +318,20 @@ class KiloWorktreeRpcApiImpl : KiloWorktreeRpcApi {
|
||||
return GhAvailability.OK
|
||||
}
|
||||
|
||||
private fun parsePr(path: String, raw: String): WorktreePrDto? {
|
||||
val obj = runCatching { json.parseToJsonElement(raw) as? JsonObject }.getOrNull() ?: return null
|
||||
val number = obj["number"]?.jsonPrimitive?.intOrNull ?: return null
|
||||
val url = obj["url"]?.jsonPrimitive?.content?.takeIf { it.isNotBlank() } ?: return null
|
||||
val draft = obj["isDraft"]?.jsonPrimitive?.booleanOrNull == true
|
||||
val state = if (draft) GhState.DRAFT else when (obj["state"]?.jsonPrimitive?.content?.uppercase()) {
|
||||
"MERGED" -> GhState.MERGED
|
||||
"CLOSED" -> GhState.CLOSED
|
||||
else -> GhState.OPEN
|
||||
}
|
||||
return WorktreePrDto(path, number, state, url)
|
||||
}
|
||||
|
||||
internal fun parsePr(path: String, raw: String): WorktreePrDto? {
|
||||
val obj = runCatching { json.parseToJsonElement(raw) as? JsonObject }.getOrNull() ?: return null
|
||||
val number = obj["number"]?.jsonPrimitive?.intOrNull ?: return null
|
||||
val url = obj["url"]?.jsonPrimitive?.content?.takeIf { it.isNotBlank() } ?: return null
|
||||
val title = obj["title"]?.jsonPrimitive?.content?.trim().orEmpty()
|
||||
val draft = obj["isDraft"]?.jsonPrimitive?.booleanOrNull == true
|
||||
val state = if (draft) GhState.DRAFT else when (obj["state"]?.jsonPrimitive?.content?.uppercase()) {
|
||||
"MERGED" -> GhState.MERGED
|
||||
"CLOSED" -> GhState.CLOSED
|
||||
else -> GhState.OPEN
|
||||
}
|
||||
return WorktreePrDto(path, number, state, url, title)
|
||||
}
|
||||
|
||||
private val json = Json { prettyPrint = true; ignoreUnknownKeys = true }
|
||||
|
||||
+14
@@ -1,6 +1,7 @@
|
||||
package ai.kilocode.backend.rpc
|
||||
|
||||
import ai.kilocode.rpc.dto.CreateWorktreeRequestDto
|
||||
import ai.kilocode.rpc.dto.GhState
|
||||
import ai.kilocode.rpc.dto.WorktreeDto
|
||||
import com.intellij.execution.configurations.GeneralCommandLine
|
||||
import com.intellij.execution.process.CapturingProcessHandler
|
||||
@@ -337,6 +338,19 @@ class KiloWorktreeRpcApiImplTest {
|
||||
assertEquals(0, item.behind)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parsePr reads title from gh output`() {
|
||||
val pull = assertNotNull(parsePr("/repo/.kilo/worktrees/feature-x", """
|
||||
{"number":12,"state":"OPEN","isDraft":false,"url":"https://example.test/pr/12","title":" Fix login bug "}
|
||||
""".trimIndent()))
|
||||
|
||||
assertEquals("/repo/.kilo/worktrees/feature-x", pull.path)
|
||||
assertEquals(12, pull.number)
|
||||
assertEquals(GhState.OPEN, pull.state)
|
||||
assertEquals("https://example.test/pr/12", pull.url)
|
||||
assertEquals("Fix login bug", pull.title)
|
||||
}
|
||||
|
||||
private fun initRepo() {
|
||||
git(repo, "init")
|
||||
git(repo, "config", "user.email", "test@kilo.ai")
|
||||
|
||||
+6
-2
@@ -11,6 +11,7 @@ import ai.kilocode.client.agentManager.worktree.WorktreeEditorMatchers
|
||||
import ai.kilocode.client.agentManager.worktree.WorktreeSessionEditorMatcher
|
||||
import ai.kilocode.client.agentManager.worktree.WorktreeSessionEditorKind
|
||||
import ai.kilocode.client.agentManager.worktree.ensureWorktreeSessionEditorKind
|
||||
import ai.kilocode.client.agentManager.worktree.prTooltip
|
||||
import ai.kilocode.client.agentManager.worktree.normalizeWorktreePath
|
||||
import ai.kilocode.client.agentManager.worktree.style
|
||||
import ai.kilocode.client.agentManager.worktree.worktreeActivityBadge
|
||||
@@ -481,11 +482,13 @@ class AgentManagerPanel(
|
||||
val pr: WorktreePrDto?,
|
||||
) : ActiveListItem {
|
||||
override val key: String get() = dto.id
|
||||
override val title: String get() = dto.name
|
||||
override val description: String get() = dto.path.trimEnd('/').substringAfterLast('/')
|
||||
override val title: String get() = pr?.title?.trim()?.takeIf { it.isNotBlank() } ?: dto.name
|
||||
override val description: String get() = defaultName
|
||||
override val tooltip: String? get() = null
|
||||
override val icon = WorktreeIcons.forRow(dto.locked, pending)
|
||||
override val search: String get() = listOfNotNull(dto.name, dto.branch, dto.path, dto.lockReason).joinToString(" ")
|
||||
private val defaultName: String get() = dto.path.trimEnd('/').substringAfterLast('/')
|
||||
private val customName: String? get() = dto.name.takeIf { it != defaultName }
|
||||
override val badges: List<ActiveListBadge>
|
||||
get() {
|
||||
if (pending || deleting) return emptyList()
|
||||
@@ -503,6 +506,7 @@ class AgentManagerPanel(
|
||||
ahead = s?.ahead ?: 0,
|
||||
behind = s?.behind ?: 0,
|
||||
pr = p?.let { ActiveListBadge("#${it.number}", style(it.state)) },
|
||||
prTooltip = p?.let { prTooltip(it, customName) },
|
||||
onChanges = s?.let { { openBranchDiff(dto.path) } },
|
||||
onPr = p?.url?.let { url -> { BrowserUtil.browse(url) } },
|
||||
)
|
||||
|
||||
+1
-8
@@ -1,7 +1,6 @@
|
||||
package ai.kilocode.client.agentManager.worktree
|
||||
|
||||
import ai.kilocode.client.session.SessionActivityKind
|
||||
import ai.kilocode.client.ui.UiStyle
|
||||
import ai.kilocode.client.ui.list.ActiveListBadge
|
||||
import ai.kilocode.rpc.dto.SessionActivityDto
|
||||
import ai.kilocode.rpc.dto.SessionActivityKindDto
|
||||
@@ -12,14 +11,8 @@ internal fun aggregateWorktreeActivity(
|
||||
.groupBy { normalize(it.directory) }
|
||||
.mapValues { (_, items) -> items.map { kind(it.kind) }.minBy(::rank) }
|
||||
|
||||
/**
|
||||
* The worktree lists mute the informational [SessionActivityKind.RUNNING] state into the same
|
||||
* subtle pill Settings uses for its "built-in" badge, so only the actionable states (question /
|
||||
* permission) keep the prominent primary styling.
|
||||
*/
|
||||
internal fun worktreeActivityBadge(kind: SessionActivityKind): ActiveListBadge {
|
||||
val style = if (kind == SessionActivityKind.RUNNING) UiStyle.Badge.Secondary else kind.style()
|
||||
return ActiveListBadge(kind.label(), style)
|
||||
return ActiveListBadge(kind.label(), kind.style())
|
||||
}
|
||||
|
||||
internal fun normalizeWorktreePath(path: String): String = normalize(path)
|
||||
|
||||
+35
-8
@@ -17,6 +17,7 @@ import com.intellij.openapi.util.IconLoader
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.util.ui.JBFont
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.xml.util.XmlStringUtil
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.Cursor
|
||||
import java.awt.Dimension
|
||||
@@ -83,14 +84,14 @@ internal class WorktreeStatsView(
|
||||
if (this.stats == stats && this.pull == pull) return
|
||||
this.stats = stats
|
||||
this.pull = pull
|
||||
sync(stats, pull?.let { ActiveListBadge("#${it.number}", style(it.state)) }, pull?.url, pull?.let { KiloBundle.message("worktree.pr.tooltip", it.number, it.state.name.lowercase()) })
|
||||
sync(stats, pull?.let { ActiveListBadge("#${it.number}", style(it.state)) }, pull?.url, pull?.let(::prTooltip))
|
||||
}
|
||||
|
||||
fun update(stats: WorktreeStatsDto?, badge: ActiveListBadge?) {
|
||||
if (this.stats == stats && pull == null && (pr.icon as? FilledBadgeIcon)?.text == badge?.text) return
|
||||
fun update(stats: WorktreeStatsDto?, badge: ActiveListBadge?, prTip: String? = badge?.text) {
|
||||
if (this.stats == stats && pull == null && (pr.icon as? FilledBadgeIcon)?.text == badge?.text && prHit.tip == prTip) return
|
||||
this.stats = stats
|
||||
this.pull = null
|
||||
sync(stats, badge, null, badge?.text)
|
||||
sync(stats, badge, null, prTip)
|
||||
}
|
||||
|
||||
private fun sync(stats: WorktreeStatsDto?, badge: ActiveListBadge?, link: String?, tip: String?) {
|
||||
@@ -172,8 +173,34 @@ internal class WorktreeStatsView(
|
||||
}
|
||||
|
||||
internal fun style(state: GhState): UiStyle.Badge.Style = when (state) {
|
||||
GhState.OPEN -> UiStyle.Badge.Primary
|
||||
GhState.DRAFT -> UiStyle.Badge.Secondary
|
||||
GhState.MERGED -> UiStyle.Badge.Highlight
|
||||
GhState.CLOSED -> UiStyle.Badge.Alert
|
||||
GhState.OPEN -> UiStyle.Badge.PullRequestOpen
|
||||
GhState.DRAFT -> UiStyle.Badge.PullRequestDraft
|
||||
GhState.MERGED -> UiStyle.Badge.PullRequestMerged
|
||||
GhState.CLOSED -> UiStyle.Badge.PullRequestClosed
|
||||
}
|
||||
|
||||
internal fun stateLabel(state: GhState): String = when (state) {
|
||||
GhState.OPEN -> KiloBundle.message("worktree.pr.state.open")
|
||||
GhState.DRAFT -> KiloBundle.message("worktree.pr.state.draft")
|
||||
GhState.MERGED -> KiloBundle.message("worktree.pr.state.merged")
|
||||
GhState.CLOSED -> KiloBundle.message("worktree.pr.state.closed")
|
||||
}
|
||||
|
||||
internal fun prTooltip(pull: WorktreePrDto, name: String? = null): String {
|
||||
val title = pull.title.trim()
|
||||
val head = buildString {
|
||||
append(stateLabel(pull.state))
|
||||
append(" #")
|
||||
append(pull.number)
|
||||
if (title.isNotBlank()) {
|
||||
append(' ')
|
||||
append(title)
|
||||
}
|
||||
}
|
||||
val lines = listOfNotNull(
|
||||
head,
|
||||
name?.takeIf { title.isNotBlank() }?.let { "($it)" },
|
||||
KiloBundle.message("worktree.pr.tooltip.open"),
|
||||
).map(XmlStringUtil::escapeString)
|
||||
return XmlStringUtil.wrapInHtml(lines.joinToString("<br>"))
|
||||
}
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ enum class SessionActivityKind {
|
||||
}
|
||||
|
||||
fun style(): UiStyle.Badge.Style = when (this) {
|
||||
RUNNING -> UiStyle.Badge.Alert
|
||||
RUNNING -> UiStyle.Badge.SessionRunning
|
||||
LOGIN_REQUIRED, PERMISSION, PLAN, QUESTION -> UiStyle.Badge.Primary
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +105,66 @@ object UiStyle {
|
||||
JBColor(Color.BLACK, Color.WHITE),
|
||||
)
|
||||
}
|
||||
|
||||
object SessionRunning : Style {
|
||||
override fun bg(): Color = JBColor.namedColor(
|
||||
"Kilo.SessionStatus.runningBadgeBackground",
|
||||
JBColor(Color(0xF9, 0x73, 0x16), Color(0xC2, 0x41, 0x0C)),
|
||||
)
|
||||
|
||||
override fun fg(): Color = JBColor.namedColor(
|
||||
"Kilo.SessionStatus.runningBadgeForeground",
|
||||
Color.WHITE,
|
||||
)
|
||||
}
|
||||
|
||||
object PullRequestOpen : Style {
|
||||
override fun bg(): Color = JBColor.namedColor(
|
||||
"Kilo.PullRequest.openBadgeBackground",
|
||||
JBColor(Color(0x1F, 0x88, 0x3D), Color(0x23, 0x86, 0x36)),
|
||||
)
|
||||
|
||||
override fun fg(): Color = JBColor.namedColor(
|
||||
"Kilo.PullRequest.openBadgeForeground",
|
||||
Color.WHITE,
|
||||
)
|
||||
}
|
||||
|
||||
object PullRequestDraft : Style {
|
||||
override fun bg(): Color = JBColor.namedColor(
|
||||
"Kilo.PullRequest.draftBadgeBackground",
|
||||
JBColor(Color(0x6E, 0x77, 0x81), Color(0x6E, 0x76, 0x81)),
|
||||
)
|
||||
|
||||
override fun fg(): Color = JBColor.namedColor(
|
||||
"Kilo.PullRequest.draftBadgeForeground",
|
||||
Color.WHITE,
|
||||
)
|
||||
}
|
||||
|
||||
object PullRequestMerged : Style {
|
||||
override fun bg(): Color = JBColor.namedColor(
|
||||
"Kilo.PullRequest.mergedBadgeBackground",
|
||||
JBColor(Color(0x82, 0x50, 0xDF), Color(0x89, 0x57, 0xE5)),
|
||||
)
|
||||
|
||||
override fun fg(): Color = JBColor.namedColor(
|
||||
"Kilo.PullRequest.mergedBadgeForeground",
|
||||
Color.WHITE,
|
||||
)
|
||||
}
|
||||
|
||||
object PullRequestClosed : Style {
|
||||
override fun bg(): Color = JBColor.namedColor(
|
||||
"Kilo.PullRequest.closedBadgeBackground",
|
||||
JBColor(Color(0xCF, 0x22, 0x2E), Color(0xDA, 0x36, 0x33)),
|
||||
)
|
||||
|
||||
override fun fg(): Color = JBColor.namedColor(
|
||||
"Kilo.PullRequest.closedBadgeForeground",
|
||||
Color.WHITE,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Theme-aware colors and color math used by multiple UI surfaces. */
|
||||
|
||||
+1
@@ -23,6 +23,7 @@ internal data class ActiveListMetrics(
|
||||
val ahead: Int = 0,
|
||||
val behind: Int = 0,
|
||||
val pr: ActiveListBadge? = null,
|
||||
val prTooltip: String? = null,
|
||||
/** Click handler for the changes badge, e.g. open the branch diff. Null leaves it inert. */
|
||||
val onChanges: (() -> Unit)? = null,
|
||||
/** Click handler for the PR badge, e.g. open the pull request. Null leaves it inert. */
|
||||
|
||||
+2
-2
@@ -85,7 +85,7 @@ internal class ActiveListRenderer(
|
||||
// instead of drifting to the far right of the row.
|
||||
private val header = titleGroup.align(HAlign.LEFT, VAlign.CENTER)
|
||||
private val desc = JBLabel()
|
||||
private val metrics = WorktreeStatsView(fill = false)
|
||||
private val metrics = WorktreeStatsView()
|
||||
private val metricsPane = metrics.align(HAlign.RIGHT, VAlign.CENTER)
|
||||
// The description (branch) line carries the changes/PR metrics on its trailing edge so they sit
|
||||
// on the branch row instead of spanning the full row height.
|
||||
@@ -216,7 +216,7 @@ internal class ActiveListRenderer(
|
||||
}
|
||||
desc.foreground = weak
|
||||
val data = if (value.deleting) null else value.metrics
|
||||
metrics.update(data?.let { WorktreeStatsDto("", it.additions, it.deletions, it.ahead, it.behind) }, data?.pr)
|
||||
metrics.update(data?.let { WorktreeStatsDto("", it.additions, it.deletions, it.ahead, it.behind) }, data?.pr, data?.prTooltip ?: data?.pr?.text)
|
||||
metrics.setActions(data?.onChanges, data?.onPr)
|
||||
val end = if (value.deleting) KiloBundle.message("common.deleting") else value.trailing.orEmpty()
|
||||
trail.text = end
|
||||
|
||||
@@ -369,8 +369,12 @@ worktree.configure.branch.required=Branch name is required
|
||||
worktree.stats.diff.tooltip={0} additions, {1} deletions
|
||||
worktree.stats.ahead.tooltip=Commits ahead of base branch
|
||||
worktree.stats.behind.tooltip=Commits behind base branch
|
||||
worktree.stats.tooltip={0} commits ahead, {1} behind, +{2} -{3}. Click to open the diff against the base branch.
|
||||
worktree.pr.tooltip=Pull request #{0} ({1}). Click to open it in your browser.
|
||||
worktree.stats.tooltip=<html>{0} commits ahead, {1} behind, +{2} -{3}.<br>Click to open the diff against the base branch.</html>
|
||||
worktree.pr.state.open=Open
|
||||
worktree.pr.state.draft=Draft
|
||||
worktree.pr.state.merged=Merged
|
||||
worktree.pr.state.closed=Closed
|
||||
worktree.pr.tooltip.open=Click to open the pull request in your browser.
|
||||
worktree.gh.missing.title=GitHub CLI not found
|
||||
worktree.gh.missing.content=Install gh to show pull request badges for worktrees.
|
||||
worktree.gh.install=Install
|
||||
|
||||
+53
@@ -18,6 +18,8 @@ import ai.kilocode.client.session.SessionActivityKind
|
||||
import ai.kilocode.client.ui.list.ActiveListBadge
|
||||
import ai.kilocode.client.ui.list.ActiveListItem
|
||||
import ai.kilocode.client.ui.list.ActiveListMetrics
|
||||
import ai.kilocode.client.ui.list.ACTIVE_LIST_PR_CELL
|
||||
import ai.kilocode.client.ui.list.activeListCellBounds
|
||||
import ai.kilocode.client.ui.list.activeListToolWindowBackground
|
||||
import ai.kilocode.client.vfs.KiloPath
|
||||
import ai.kilocode.client.vfs.KiloVfsManager
|
||||
@@ -43,6 +45,7 @@ import com.intellij.ui.components.JBList
|
||||
import com.intellij.ui.components.JBScrollPane
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import java.awt.event.MouseEvent
|
||||
import java.awt.Point
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.SwingUtilities
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
@@ -454,6 +457,54 @@ class AgentManagerPanelTest : BasePlatformTestCase() {
|
||||
assertFalse(edt { panel.canOpenPr(null) })
|
||||
}
|
||||
|
||||
fun `test pr title replaces row name and tooltip reveals custom name`() {
|
||||
val path = "${project.basePath!!}/.kilo/worktrees/feature-x"
|
||||
val item = WorktreeDto(path, "Feature Label", "feature/x", path)
|
||||
rpc.listed += item
|
||||
rpc.prResult = WorktreePrListDto(GhAvailability.OK, listOf(WorktreePrDto(path, 7, GhState.DRAFT, "https://example.test/pr/7", "Fix <login> bug")))
|
||||
val timers = TestUiTimers()
|
||||
ApplicationManager.getApplication().replaceService(KiloWorktreeService::class.java, service, testRootDisposable)
|
||||
project.replaceService(WorktreeStatusService::class.java, WorktreeStatusService(project, coroutines.scope, timers), testRootDisposable)
|
||||
val controller = WorktreeController(service, project.basePath!!, coroutines.scope)
|
||||
val panel = edt { AgentManagerPanel(testRootDisposable, controller, project) }
|
||||
edt { controller.reload() }
|
||||
timers.advanceBy(300)
|
||||
flush()
|
||||
|
||||
val row = row(panel, 0)
|
||||
assertEquals("Fix <login> bug", row.title)
|
||||
val tip = row.metrics?.prTooltip ?: error("expected PR tooltip")
|
||||
assertEquals("<html>Draft #7 Fix <login> bug<br>(Feature Label)<br>Click to open the pull request in your browser.</html>", tip)
|
||||
val list = edt { UIUtil.findComponentOfType(panel, JBList::class.java)!! }
|
||||
edt {
|
||||
list.size = java.awt.Dimension(360, 80)
|
||||
list.doLayout()
|
||||
UIUtil.dispatchAllInvocationEvents()
|
||||
}
|
||||
val area = edt { activeListCellBounds(list, 0, selected = false).getValue(ACTIVE_LIST_PR_CELL) }
|
||||
|
||||
assertEquals(tip, edt { list.getToolTipText(MouseEvent(list, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, center(area).x, center(area).y, 0, false)) })
|
||||
}
|
||||
|
||||
fun `test blank pr title keeps row name and omits custom name line`() {
|
||||
val path = "${project.basePath!!}/.kilo/worktrees/feature-x"
|
||||
val item = WorktreeDto(path, "Feature Label", "feature/x", path)
|
||||
rpc.listed += item
|
||||
rpc.prResult = WorktreePrListDto(GhAvailability.OK, listOf(WorktreePrDto(path, 8, GhState.OPEN, "https://example.test/pr/8", " ")))
|
||||
val timers = TestUiTimers()
|
||||
ApplicationManager.getApplication().replaceService(KiloWorktreeService::class.java, service, testRootDisposable)
|
||||
project.replaceService(WorktreeStatusService::class.java, WorktreeStatusService(project, coroutines.scope, timers), testRootDisposable)
|
||||
val controller = WorktreeController(service, project.basePath!!, coroutines.scope)
|
||||
val panel = edt { AgentManagerPanel(testRootDisposable, controller, project) }
|
||||
edt { controller.reload() }
|
||||
timers.advanceBy(300)
|
||||
flush()
|
||||
|
||||
val row = row(panel, 0)
|
||||
assertEquals("Feature Label", row.title)
|
||||
assertEquals("<html>Open #8<br>Click to open the pull request in your browser.</html>", row.metrics?.prTooltip)
|
||||
}
|
||||
|
||||
fun `test worktree row hides badge while pending or deleting`() {
|
||||
val path = "feature/y"
|
||||
val activity = MutableStateFlow(mapOf(
|
||||
@@ -482,6 +533,8 @@ class AgentManagerPanelTest : BasePlatformTestCase() {
|
||||
return edt { list.model.getElementAt(idx) as ActiveListItem }
|
||||
}
|
||||
|
||||
private fun center(rect: java.awt.Rectangle) = Point(rect.x + rect.width / 2, rect.y + rect.height / 2)
|
||||
|
||||
private fun pump() {
|
||||
ApplicationManager.getApplication().invokeAndWait { UIUtil.dispatchAllInvocationEvents() }
|
||||
}
|
||||
|
||||
+2
-3
@@ -1,7 +1,6 @@
|
||||
package ai.kilocode.client.agentManager.worktree
|
||||
|
||||
import ai.kilocode.client.session.SessionActivityKind
|
||||
import ai.kilocode.client.ui.UiStyle
|
||||
import ai.kilocode.rpc.dto.SessionActivityDto
|
||||
import ai.kilocode.rpc.dto.SessionActivityKindDto
|
||||
import kotlin.test.Test
|
||||
@@ -43,8 +42,8 @@ class WorktreeActivityTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `running badge uses the subtle secondary pill while actionable kinds stay prominent`() {
|
||||
assertEquals(UiStyle.Badge.Secondary, worktreeActivityBadge(SessionActivityKind.RUNNING).style)
|
||||
fun `worktree activity badges use shared session activity styles`() {
|
||||
assertEquals(SessionActivityKind.RUNNING.style(), worktreeActivityBadge(SessionActivityKind.RUNNING).style)
|
||||
assertEquals(SessionActivityKind.QUESTION.style(), worktreeActivityBadge(SessionActivityKind.QUESTION).style)
|
||||
assertEquals(SessionActivityKind.PERMISSION.style(), worktreeActivityBadge(SessionActivityKind.PERMISSION).style)
|
||||
assertEquals(SessionActivityKind.RUNNING.label(), worktreeActivityBadge(SessionActivityKind.RUNNING).text)
|
||||
|
||||
+1
-2
@@ -12,7 +12,6 @@ import ai.kilocode.client.session.history.LocalHistoryItem
|
||||
import ai.kilocode.client.testing.FakeSessionRpcApi
|
||||
import ai.kilocode.client.testing.TestCoroutines
|
||||
import ai.kilocode.client.testing.fire
|
||||
import ai.kilocode.client.ui.UiStyle
|
||||
import ai.kilocode.client.ui.list.ActiveListBadge
|
||||
import ai.kilocode.client.ui.list.ActiveListItem
|
||||
import ai.kilocode.client.ui.list.activeListSectionTitle
|
||||
@@ -146,7 +145,7 @@ class WorktreeSessionEditorPanelTest : BasePlatformTestCase() {
|
||||
assertEquals("Session ses_1", row.title)
|
||||
assertNull(row.icon)
|
||||
assertNull(row.description)
|
||||
assertEquals(listOf(ActiveListBadge(SessionActivityKind.RUNNING.label(), UiStyle.Badge.Secondary)), row.badges)
|
||||
assertEquals(listOf(ActiveListBadge(SessionActivityKind.RUNNING.label(), SessionActivityKind.RUNNING.style())), row.badges)
|
||||
assertNull(row.trailing)
|
||||
assertEquals(HistoryTime.title(HistoryTime.section(LocalHistoryItem(session))), row.section)
|
||||
}
|
||||
|
||||
+15
@@ -940,6 +940,7 @@ class SettingsListViewTest : BasePlatformTestCase() {
|
||||
UIUtil.dispatchAllInvocationEvents()
|
||||
|
||||
val area = activeListCellBounds(view.list, 0, selected = true).getValue(ACTIVE_LIST_PR_CELL)
|
||||
assertEquals("#12", view.list.getToolTipText(event(view.list, center(area))))
|
||||
hover(view, center(area))
|
||||
assertEquals(Cursor.HAND_CURSOR, view.list.cursor.type)
|
||||
|
||||
@@ -948,6 +949,20 @@ class SettingsListViewTest : BasePlatformTestCase() {
|
||||
}
|
||||
}
|
||||
|
||||
fun `test pr badge uses custom tooltip when supplied`() {
|
||||
edt {
|
||||
val view = ActiveListView("Empty") { _, _ -> }
|
||||
view.update(listOf(metricsItem("wt", "Alpha", ActiveListMetrics(pr = ActiveListBadge("#12"), prTooltip = "PR details"))))
|
||||
view.list.size = Dimension(360, 80)
|
||||
view.list.doLayout()
|
||||
UIUtil.dispatchAllInvocationEvents()
|
||||
|
||||
val area = activeListCellBounds(view.list, 0, selected = true).getValue(ACTIVE_LIST_PR_CELL)
|
||||
|
||||
assertEquals("PR details", view.list.getToolTipText(event(view.list, center(area))))
|
||||
}
|
||||
}
|
||||
|
||||
fun `test inert changes badge is not hit tested`() {
|
||||
edt {
|
||||
val view = ActiveListView("Empty") { _, _ -> }
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package ai.kilocode.client.ui
|
||||
|
||||
import ai.kilocode.client.agentManager.worktree.style
|
||||
import ai.kilocode.client.session.SessionActivityKind
|
||||
import ai.kilocode.client.session.ui.style.SessionUiStyle
|
||||
import ai.kilocode.rpc.dto.GhState
|
||||
import com.intellij.testFramework.fixtures.BasePlatformTestCase
|
||||
import com.intellij.util.ui.JBUI
|
||||
import java.awt.Color
|
||||
@@ -45,4 +48,19 @@ class UiStyleTest : BasePlatformTestCase() {
|
||||
assertTrue(SessionUiStyle.View.Tool.BODY_LINES > 0)
|
||||
assertEquals(5, SessionUiStyle.View.Reasoning.BODY_LINES)
|
||||
}
|
||||
|
||||
fun `test session status badges use shared styles`() {
|
||||
assertSame(UiStyle.Badge.SessionRunning, SessionActivityKind.RUNNING.style())
|
||||
assertSame(UiStyle.Badge.Primary, SessionActivityKind.QUESTION.style())
|
||||
assertSame(UiStyle.Badge.Primary, SessionActivityKind.PLAN.style())
|
||||
assertSame(UiStyle.Badge.Primary, SessionActivityKind.PERMISSION.style())
|
||||
assertSame(UiStyle.Badge.Primary, SessionActivityKind.LOGIN_REQUIRED.style())
|
||||
}
|
||||
|
||||
fun `test pull request states use github badge styles`() {
|
||||
assertSame(UiStyle.Badge.PullRequestOpen, style(GhState.OPEN))
|
||||
assertSame(UiStyle.Badge.PullRequestDraft, style(GhState.DRAFT))
|
||||
assertSame(UiStyle.Badge.PullRequestMerged, style(GhState.MERGED))
|
||||
assertSame(UiStyle.Badge.PullRequestClosed, style(GhState.CLOSED))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ data class WorktreePrDto(
|
||||
val number: Int,
|
||||
val state: GhState,
|
||||
val url: String,
|
||||
val title: String = "",
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
||||
Reference in New Issue
Block a user