mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-24 16:02:55 +08:00
fix(jetbrains): align session question views
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Improve question-based session views so UI text uses editor-sized interface fonts, actions align consistently, and permission prompts show a header icon.
|
||||
+38
-5
@@ -5,14 +5,17 @@ import ai.kilocode.client.session.ui.style.SessionEditorStyleTarget
|
||||
import ai.kilocode.client.session.ui.style.SessionUiStyle
|
||||
import ai.kilocode.client.ui.RoundedContentPanel
|
||||
import ai.kilocode.client.ui.UiStyle
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.ui.components.JBTextArea
|
||||
import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||
import com.intellij.util.ui.JBUI
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.Color
|
||||
import java.awt.Component
|
||||
import java.awt.Dimension
|
||||
import javax.swing.Box
|
||||
import javax.swing.BoxLayout
|
||||
import javax.swing.Icon
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JPanel
|
||||
|
||||
@@ -27,9 +30,9 @@ import javax.swing.JPanel
|
||||
* outer card shell so they share the same background, padding, and text
|
||||
* styling without duplicating the setup.
|
||||
*
|
||||
* The column always contains (in order): optional top, [headerText],
|
||||
* The column always contains (in order): optional top, header row with [headerText],
|
||||
* [descriptionText], optional body, optional footer. Call [setTopPanel],
|
||||
* [setBody], or [setFooter] to replace those slots at any time.
|
||||
* [setHeaderIcon], [setBody], or [setFooter] to replace those slots at any time.
|
||||
*/
|
||||
class BaseSessionQuestionPanel : RoundedContentPanel(
|
||||
UiStyle.Gap.lg(),
|
||||
@@ -38,7 +41,7 @@ class BaseSessionQuestionPanel : RoundedContentPanel(
|
||||
|
||||
private var style = SessionEditorStyle.current()
|
||||
|
||||
// All JBTextArea instances that need editor-font updates, paired with bold flag
|
||||
// All JBTextArea instances that need style updates, paired with bold flag
|
||||
private val tracked = mutableListOf<Pair<JBTextArea, Boolean>>()
|
||||
|
||||
// ---- header text ----
|
||||
@@ -47,6 +50,23 @@ class BaseSessionQuestionPanel : RoundedContentPanel(
|
||||
// ---- description text ----
|
||||
val descriptionText: JBTextArea = makeText("", UiStyle.Colors.weak(), bold = false)
|
||||
|
||||
private val icon = JBLabel().apply {
|
||||
border = JBUI.Borders.emptyRight(UiStyle.Gap.sm())
|
||||
isVisible = false
|
||||
}
|
||||
|
||||
private val header = object : JPanel(BorderLayout(UiStyle.Gap.sm(), 0)) {
|
||||
override fun getMaximumSize(): Dimension {
|
||||
val size = preferredSize
|
||||
return Dimension(Int.MAX_VALUE, size.height)
|
||||
}
|
||||
}.apply {
|
||||
isOpaque = false
|
||||
alignmentX = Component.LEFT_ALIGNMENT
|
||||
add(icon, BorderLayout.WEST)
|
||||
add(headerText, BorderLayout.CENTER)
|
||||
}
|
||||
|
||||
// ---- slot fields ----
|
||||
private var top: JComponent? = null
|
||||
private var body: JComponent? = null
|
||||
@@ -77,6 +97,19 @@ class BaseSessionQuestionPanel : RoundedContentPanel(
|
||||
rebuildCol()
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional icon rendered at the left edge of the header row.
|
||||
* Pass `null` to remove the icon while keeping header text alignment stable.
|
||||
*/
|
||||
@RequiresEdt
|
||||
fun setHeaderIcon(icon: Icon?, tooltip: String? = null) {
|
||||
this.icon.icon = icon
|
||||
this.icon.toolTipText = tooltip
|
||||
this.icon.isVisible = icon != null
|
||||
this.icon.revalidate()
|
||||
this.icon.repaint()
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the body slot that comes after the header/description.
|
||||
* Pass `null` to remove the current body.
|
||||
@@ -116,7 +149,7 @@ class BaseSessionQuestionPanel : RoundedContentPanel(
|
||||
private fun rebuildCol() {
|
||||
col.removeAll()
|
||||
top?.let { col.add(it) }
|
||||
col.add(headerText)
|
||||
col.add(header)
|
||||
col.add(descriptionText)
|
||||
body?.let {
|
||||
col.add(gap())
|
||||
@@ -182,7 +215,7 @@ class BaseSessionQuestionPanel : RoundedContentPanel(
|
||||
}
|
||||
|
||||
private fun applyFont(area: JBTextArea, bold: Boolean) {
|
||||
val font = if (bold) style.boldEditorFont else style.transcriptFont
|
||||
val font = if (bold) style.boldUiFont else style.uiFont
|
||||
if (area.font != font) area.font = font
|
||||
}
|
||||
}
|
||||
|
||||
+11
-2
@@ -7,10 +7,13 @@ import ai.kilocode.client.session.ui.shared.applyButton
|
||||
import ai.kilocode.client.session.ui.shared.dismissButton
|
||||
import ai.kilocode.client.session.ui.style.SessionEditorStyle
|
||||
import ai.kilocode.client.session.ui.style.SessionEditorStyleTarget
|
||||
import ai.kilocode.client.ui.UiStyle
|
||||
import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||
import com.intellij.util.ui.components.BorderLayoutPanel
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.Component
|
||||
import javax.swing.Box
|
||||
import javax.swing.BoxLayout
|
||||
import javax.swing.JPanel
|
||||
|
||||
/**
|
||||
@@ -43,9 +46,15 @@ class LoginRequiredView(
|
||||
val footer = JPanel(BorderLayout()).apply {
|
||||
isOpaque = false
|
||||
alignmentX = Component.LEFT_ALIGNMENT
|
||||
add(dismissButton, BorderLayout.WEST)
|
||||
add(openProfileButton, BorderLayout.EAST)
|
||||
}
|
||||
val actions = JPanel().apply {
|
||||
isOpaque = false
|
||||
layout = BoxLayout(this, BoxLayout.X_AXIS)
|
||||
add(dismissButton)
|
||||
add(Box.createHorizontalStrut(UiStyle.Gap.sm()))
|
||||
add(openProfileButton)
|
||||
}
|
||||
footer.add(actions, BorderLayout.EAST)
|
||||
|
||||
card.setFooter(footer)
|
||||
|
||||
|
||||
+4
@@ -14,6 +14,7 @@ import ai.kilocode.client.session.ui.style.SessionUiStyle
|
||||
import ai.kilocode.client.ui.UiStyle
|
||||
import ai.kilocode.client.ui.md.MdView
|
||||
import ai.kilocode.rpc.dto.PermissionReplyDto
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.ui.components.JBScrollPane
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.components.BorderLayoutPanel
|
||||
@@ -75,6 +76,7 @@ class PermissionView(
|
||||
actions.add(run)
|
||||
footer.add(actions, BorderLayout.EAST)
|
||||
|
||||
card.setHeaderIcon(AllIcons.General.Warning, KiloBundle.message("session.permission.title"))
|
||||
card.setBody(body)
|
||||
card.setFooter(footer)
|
||||
addToCenter(card)
|
||||
@@ -239,6 +241,8 @@ class PermissionView(
|
||||
// Test helpers
|
||||
internal fun runButtonForTest() = run
|
||||
internal fun denyButtonForTest() = deny
|
||||
internal fun firstCmdViewForTest() = cmdViews.firstOrNull()
|
||||
internal fun headerFontForTest() = card.headerText.font
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+7
-2
@@ -108,7 +108,9 @@ class QuestionResultView(tool: Tool) : PartView() {
|
||||
|
||||
override fun applyStyle(style: SessionEditorStyle) {
|
||||
this.style = style
|
||||
val label = setFont(title, style.boldEditorFont) || setFont(sub, style.smallEditorFont)
|
||||
val t = setFont(title, style.boldUiFont)
|
||||
val s = setFont(sub, style.smallUiFont)
|
||||
val label = t || s
|
||||
val body = texts.fold(false) { acc, item -> setFont(item.first, item.second) || acc }
|
||||
if (!label && !body) return
|
||||
refresh()
|
||||
@@ -137,6 +139,9 @@ class QuestionResultView(tool: Tool) : PartView() {
|
||||
|
||||
fun bodyFonts(): List<Font> = texts.map { it.first.font }
|
||||
|
||||
fun titleFont(): Font = title.font
|
||||
fun subFont(): Font = sub.font
|
||||
|
||||
override fun dumpLabel(): String = "QuestionResultView#$contentId(${labelText()})"
|
||||
|
||||
companion object {
|
||||
@@ -268,7 +273,7 @@ class QuestionResultView(tool: Tool) : PartView() {
|
||||
}
|
||||
|
||||
private fun setFont(area: JBTextArea, bold: Boolean): Boolean {
|
||||
val font = if (bold) style.boldEditorFont else style.transcriptFont
|
||||
val font = if (bold) style.boldUiFont else style.uiFont
|
||||
if (area.font == font) return false
|
||||
area.font = font
|
||||
return true
|
||||
|
||||
+7
-3
@@ -98,7 +98,6 @@ class QuestionView(
|
||||
nav.add(fwd)
|
||||
topPanel.add(summary, BorderLayout.WEST)
|
||||
topPanel.add(nav, BorderLayout.EAST)
|
||||
footer.add(dismiss, BorderLayout.WEST)
|
||||
footer.add(right, BorderLayout.EAST)
|
||||
|
||||
card.setTopPanel(topPanel)
|
||||
@@ -176,9 +175,11 @@ class QuestionView(
|
||||
|
||||
private fun syncFooter(q: Question) {
|
||||
right.removeAll()
|
||||
right.add(dismiss)
|
||||
if (review(q)) {
|
||||
val back = dismissButton(KiloBundle.message("session.question.back")) { goBack() }
|
||||
val submit = applyButton(KiloBundle.message("session.question.submit")) { doReply() }
|
||||
right.add(Box.createHorizontalStrut(UiStyle.Gap.sm()))
|
||||
right.add(back)
|
||||
right.add(Box.createHorizontalStrut(UiStyle.Gap.sm()))
|
||||
right.add(submit)
|
||||
@@ -200,6 +201,7 @@ class QuestionView(
|
||||
}
|
||||
}
|
||||
}
|
||||
right.add(Box.createHorizontalStrut(UiStyle.Gap.sm()))
|
||||
right.add(button)
|
||||
}
|
||||
|
||||
@@ -207,8 +209,10 @@ class QuestionView(
|
||||
val ready = selections.getOrNull(idx)?.isNotEmpty() == true
|
||||
back.isEnabled = idx > 0
|
||||
fwd.isEnabled = idx < q.items.size && ready
|
||||
val backLabel = KiloBundle.message("session.question.back")
|
||||
val dismissLabel = KiloBundle.message("session.question.dismiss")
|
||||
for (node in right.components) {
|
||||
if (node is SessionQuestionButton && node.text != KiloBundle.message("session.question.back")) {
|
||||
if (node is SessionQuestionButton && node.text != backLabel && node.text != dismissLabel) {
|
||||
node.isEnabled = review(q) || ready
|
||||
}
|
||||
}
|
||||
@@ -436,7 +440,7 @@ class QuestionView(
|
||||
}
|
||||
|
||||
private fun setFont(area: JBTextArea, bold: Boolean): Boolean {
|
||||
val font = if (bold) style.boldEditorFont else style.transcriptFont
|
||||
val font = if (bold) style.boldUiFont else style.uiFont
|
||||
if (area.font == font) return false
|
||||
area.font = font
|
||||
return true
|
||||
|
||||
+18
@@ -50,4 +50,22 @@ class SessionEditorStyleTest : BasePlatformTestCase() {
|
||||
assertTrue(style.smallEditorFont.size < style.editorSize)
|
||||
assertEquals(style.editorSize, style.uiFont.size)
|
||||
}
|
||||
|
||||
fun `test ui fonts use platform label family not editor family`() {
|
||||
val style = SessionEditorStyle.create(family = "Courier New", size = 22)
|
||||
|
||||
// uiFont / boldUiFont / smallUiFont must NOT use the editor font family
|
||||
assertFalse("uiFont should not use editor font family", style.uiFont.name == "Courier New")
|
||||
assertFalse("boldUiFont should not use editor font family", style.boldUiFont.name == "Courier New")
|
||||
assertFalse("smallUiFont should not use editor font family", style.smallUiFont.name == "Courier New")
|
||||
}
|
||||
|
||||
fun `test ui fonts inherit editor size`() {
|
||||
val style = SessionEditorStyle.create(family = "Courier New", size = 22)
|
||||
|
||||
assertEquals("uiFont size should match editor size", 22, style.uiFont.size)
|
||||
assertEquals("boldUiFont size should match editor size", 22, style.boldUiFont.size)
|
||||
assertTrue("boldUiFont should be bold", style.boldUiFont.isBold)
|
||||
assertTrue("smallUiFont should be smaller than editor size", style.smallUiFont.size < style.editorSize)
|
||||
}
|
||||
}
|
||||
|
||||
+79
-3
@@ -1,8 +1,12 @@
|
||||
package ai.kilocode.client.session.ui.shared
|
||||
|
||||
import ai.kilocode.client.session.ui.style.SessionEditorStyle
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.testFramework.fixtures.BasePlatformTestCase
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.ui.components.JBTextArea
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.Container
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JLabel
|
||||
@@ -40,7 +44,7 @@ class BaseSessionQuestionPanelTest : BasePlatformTestCase() {
|
||||
val col = findCol(panel)!!
|
||||
val comps = col.components.toList()
|
||||
val topIdx = comps.indexOf(top)
|
||||
val headerIdx = comps.indexOf(panel.headerText)
|
||||
val headerIdx = comps.indexOf(panel.headerText.parent)
|
||||
assertTrue("top should appear before headerText", topIdx < headerIdx)
|
||||
}
|
||||
}
|
||||
@@ -169,7 +173,7 @@ class BaseSessionQuestionPanelTest : BasePlatformTestCase() {
|
||||
val col = findCol(panel)!!
|
||||
val comps = col.components.toList()
|
||||
val topIdx = comps.indexOf(top)
|
||||
val headerIdx = comps.indexOf(panel.headerText)
|
||||
val headerIdx = comps.indexOf(panel.headerText.parent)
|
||||
val descIdx = comps.indexOf(panel.descriptionText)
|
||||
val bodyIdx = comps.indexOf(body)
|
||||
val footerIdx = comps.indexOf(footer)
|
||||
@@ -195,7 +199,38 @@ class BaseSessionQuestionPanelTest : BasePlatformTestCase() {
|
||||
edt {
|
||||
val panel = BaseSessionQuestionPanel()
|
||||
val col = findCol(panel)!!
|
||||
assertEquals("headerText + descriptionText only", 2, col.componentCount)
|
||||
assertEquals("header row + descriptionText only", 2, col.componentCount)
|
||||
}
|
||||
}
|
||||
|
||||
// ------ header left icon ------
|
||||
|
||||
fun `test setHeaderIcon adds icon to the left side of header row`() {
|
||||
edt {
|
||||
val panel = BaseSessionQuestionPanel()
|
||||
panel.setHeaderIcon(AllIcons.General.Warning, "warning")
|
||||
|
||||
val header = panel.headerText.parent as JPanel
|
||||
val layout = header.layout as BorderLayout
|
||||
val labels = findAll<JBLabel>(header).filter { it.icon != null }
|
||||
assertEquals("Expected one header icon", 1, labels.size)
|
||||
assertSame(AllIcons.General.Warning, labels[0].icon)
|
||||
assertEquals("warning", labels[0].toolTipText)
|
||||
assertEquals(BorderLayout.WEST, layout.getConstraints(labels[0]))
|
||||
assertEquals(BorderLayout.CENTER, layout.getConstraints(panel.headerText))
|
||||
}
|
||||
}
|
||||
|
||||
fun `test setHeaderIcon null hides header icon without removing header row`() {
|
||||
edt {
|
||||
val panel = BaseSessionQuestionPanel()
|
||||
panel.setHeaderIcon(AllIcons.General.Warning)
|
||||
panel.setHeaderIcon(null)
|
||||
|
||||
val header = panel.headerText.parent as Container
|
||||
val labels = findAll<JBLabel>(header).filter { it.icon != null && it.isVisible }
|
||||
assertTrue("Header icon should be hidden after setHeaderIcon(null)", labels.isEmpty())
|
||||
assertSame(header, panel.headerText.parent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,6 +278,36 @@ class BaseSessionQuestionPanelTest : BasePlatformTestCase() {
|
||||
}
|
||||
}
|
||||
|
||||
// ------ applyStyle: UI fonts ------
|
||||
|
||||
fun `test applyStyle applies boldUiFont to header and uiFont to description`() {
|
||||
edt {
|
||||
val panel = BaseSessionQuestionPanel()
|
||||
val style = SessionEditorStyle.create(family = "Courier New", size = 20)
|
||||
panel.applyStyle(style)
|
||||
|
||||
assertEquals("headerText should use boldUiFont", style.boldUiFont, panel.headerText.font)
|
||||
assertEquals("descriptionText should use uiFont", style.uiFont, panel.descriptionText.font)
|
||||
}
|
||||
}
|
||||
|
||||
fun `test applyStyle does not apply editor font family to header or description`() {
|
||||
edt {
|
||||
val panel = BaseSessionQuestionPanel()
|
||||
val style = SessionEditorStyle.create(family = "Courier New", size = 20)
|
||||
panel.applyStyle(style)
|
||||
|
||||
assertFalse(
|
||||
"headerText should not use editor font family",
|
||||
panel.headerText.font.name == "Courier New",
|
||||
)
|
||||
assertFalse(
|
||||
"descriptionText should not use editor font family",
|
||||
panel.descriptionText.font.name == "Courier New",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ------ helpers ------
|
||||
|
||||
private fun <T> edt(block: () -> T): T {
|
||||
@@ -270,4 +335,15 @@ class BaseSessionQuestionPanelTest : BasePlatformTestCase() {
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private inline fun <reified T> findAll(root: Container): List<T> = findAllCls(root, T::class.java)
|
||||
|
||||
private fun <T> findAllCls(root: Container, cls: Class<T>): List<T> {
|
||||
val result = mutableListOf<T>()
|
||||
if (cls.isInstance(root)) result.add(cls.cast(root))
|
||||
for (child in root.components) {
|
||||
if (child is Container) result.addAll(findAllCls(child, cls))
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
+49
@@ -1,6 +1,7 @@
|
||||
package ai.kilocode.client.session.views
|
||||
|
||||
import ai.kilocode.client.session.ui.shared.SessionQuestionButton
|
||||
import ai.kilocode.client.session.ui.style.SessionEditorStyle
|
||||
import ai.kilocode.client.session.ui.style.SessionUiStyle
|
||||
import com.intellij.ide.ui.laf.darcula.ui.DarculaButtonUI
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
@@ -105,6 +106,18 @@ class LoginRequiredViewTest : BasePlatformTestCase() {
|
||||
}
|
||||
}
|
||||
|
||||
fun `test login action buttons share right-aligned footer group`() {
|
||||
edt {
|
||||
val view = LoginRequiredView(openProfile = {}, dismiss = {})
|
||||
view.show("Sign in required.")
|
||||
|
||||
val dismiss = view.dismissButton
|
||||
val open = view.openProfileButton
|
||||
assertSame("Dismiss and open profile should be in the same right-aligned group", dismiss.parent, open.parent)
|
||||
assertTrue("Dismiss should appear before open profile", dismiss.parent.components.indexOf(dismiss) < open.parent.components.indexOf(open))
|
||||
}
|
||||
}
|
||||
|
||||
// ------ callbacks ------
|
||||
|
||||
fun `test open profile button click invokes openProfile callback`() {
|
||||
@@ -161,6 +174,42 @@ class LoginRequiredViewTest : BasePlatformTestCase() {
|
||||
}
|
||||
}
|
||||
|
||||
// ------ fonts: UI family, editor size ------
|
||||
|
||||
fun `test header uses boldUiFont not editor font family`() {
|
||||
edt {
|
||||
val view = LoginRequiredView(openProfile = {}, dismiss = {})
|
||||
view.show("Sign in required.")
|
||||
val style = SessionEditorStyle.create(family = "Courier New", size = 20)
|
||||
view.applyStyle(style)
|
||||
|
||||
val title = findAll<JBTextArea>(view).firstOrNull { it.font.isBold }
|
||||
assertNotNull("Bold title text area should be present", title)
|
||||
assertFalse(
|
||||
"Title font should not use editor font family",
|
||||
title!!.font.name == "Courier New",
|
||||
)
|
||||
assertEquals("Title font size should match editor size", 20, title.font.size)
|
||||
}
|
||||
}
|
||||
|
||||
fun `test description uses uiFont not editor font family`() {
|
||||
edt {
|
||||
val view = LoginRequiredView(openProfile = {}, dismiss = {})
|
||||
view.show("Sign in required.")
|
||||
val style = SessionEditorStyle.create(family = "Courier New", size = 20)
|
||||
view.applyStyle(style)
|
||||
|
||||
val desc = findAll<JBTextArea>(view).firstOrNull { it.text == "Sign in required." }
|
||||
assertNotNull("Description text area should be present", desc)
|
||||
assertFalse(
|
||||
"Description font should not use editor font family",
|
||||
desc!!.font.name == "Courier New",
|
||||
)
|
||||
assertEquals("Description font size should match editor size", 20, desc.font.size)
|
||||
}
|
||||
}
|
||||
|
||||
// ------ helpers ------
|
||||
|
||||
private fun <T> edt(block: () -> T): T {
|
||||
|
||||
+54
@@ -5,11 +5,14 @@ import ai.kilocode.client.session.model.PermissionFileDiff
|
||||
import ai.kilocode.client.session.model.PermissionMeta
|
||||
import ai.kilocode.client.session.model.PermissionRequestState
|
||||
import ai.kilocode.client.session.ui.shared.BaseSessionQuestionPanel
|
||||
import ai.kilocode.client.session.ui.style.SessionEditorStyle
|
||||
import ai.kilocode.client.session.ui.style.SessionUiStyle
|
||||
import ai.kilocode.rpc.dto.PermissionReplyDto
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.ide.ui.laf.darcula.ui.DarculaButtonUI
|
||||
import com.intellij.testFramework.fixtures.BasePlatformTestCase
|
||||
import com.intellij.ui.components.JBHtmlPane
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.ui.components.JBScrollPane
|
||||
import java.awt.Container
|
||||
import javax.swing.AbstractButton
|
||||
@@ -262,6 +265,16 @@ class PermissionViewTest : BasePlatformTestCase() {
|
||||
assertTrue("Expected a BaseSessionQuestionPanel after show", panels.isNotEmpty())
|
||||
}
|
||||
|
||||
fun `test permission icon is rendered in header`() {
|
||||
view.show(permission())
|
||||
|
||||
val labels = findAll<JBLabel>(view)
|
||||
assertTrue(
|
||||
"Expected permission warning icon in header",
|
||||
labels.any { it.icon == AllIcons.General.Warning },
|
||||
)
|
||||
}
|
||||
|
||||
// ------ new: shared button types ------
|
||||
|
||||
fun `test run button is SessionQuestionButton with primary true`() {
|
||||
@@ -355,6 +368,47 @@ class PermissionViewTest : BasePlatformTestCase() {
|
||||
assertEquals(SessionUiStyle.View.headerHover(), scroll.viewport.background)
|
||||
}
|
||||
|
||||
// ------ fonts: header UI family, command code block editor family ------
|
||||
|
||||
fun `test permission header uses boldUiFont not editor font family`() {
|
||||
view.show(
|
||||
Permission(
|
||||
id = "perm_font",
|
||||
sessionId = "ses",
|
||||
name = "bash",
|
||||
patterns = emptyList(),
|
||||
always = emptyList(),
|
||||
meta = PermissionMeta(command = "ls"),
|
||||
)
|
||||
)
|
||||
val style = SessionEditorStyle.create(family = "Courier New", size = 18)
|
||||
view.applyStyle(style)
|
||||
|
||||
val header = view.headerFontForTest()
|
||||
assertFalse("Permission header should not use editor font family", header.name == "Courier New")
|
||||
assertTrue("Permission header should be bold", header.isBold)
|
||||
assertEquals("Permission header size should match editor size", 18, header.size)
|
||||
}
|
||||
|
||||
fun `test command code block retains editor font family`() {
|
||||
view.show(
|
||||
Permission(
|
||||
id = "perm_codefont",
|
||||
sessionId = "ses",
|
||||
name = "bash",
|
||||
patterns = emptyList(),
|
||||
always = emptyList(),
|
||||
meta = PermissionMeta(command = "git log"),
|
||||
)
|
||||
)
|
||||
val style = SessionEditorStyle.create(family = "Courier New", size = 18)
|
||||
view.applyStyle(style)
|
||||
|
||||
val md = view.firstCmdViewForTest()
|
||||
assertNotNull("Should have at least one command MdView", md)
|
||||
assertEquals("Code block codeFont should use editor family", "Courier New", md!!.codeFont)
|
||||
}
|
||||
|
||||
private fun permission() = Permission(
|
||||
id = "perm1",
|
||||
sessionId = "ses_test",
|
||||
|
||||
+20
-3
@@ -166,7 +166,7 @@ class QuestionResultViewTest : BasePlatformTestCase() {
|
||||
|
||||
// ------ applyStyle ------
|
||||
|
||||
fun `test applyStyle updates fonts`() {
|
||||
fun `test applyStyle updates body fonts to UI font family`() {
|
||||
val tool = completedTool(
|
||||
input = mapOf("questions" to """[{"question":"Q1"}]"""),
|
||||
metadata = mapOf("answers" to """[["A1"]]"""),
|
||||
@@ -177,8 +177,25 @@ class QuestionResultViewTest : BasePlatformTestCase() {
|
||||
view.applyStyle(style)
|
||||
view.toggle()
|
||||
|
||||
assertTrue(view.bodyFonts().contains(style.transcriptFont))
|
||||
assertTrue(view.bodyFonts().contains(style.boldEditorFont))
|
||||
assertTrue(view.bodyFonts().contains(style.uiFont))
|
||||
assertTrue(view.bodyFonts().contains(style.boldUiFont))
|
||||
assertFalse("Body should not use editor transcript font", view.bodyFonts().any { it.name == "Courier New" })
|
||||
}
|
||||
|
||||
fun `test applyStyle updates header label fonts to UI font family`() {
|
||||
val tool = completedTool(
|
||||
input = mapOf("questions" to """[{"question":"Q1"}]"""),
|
||||
metadata = mapOf("answers" to """[["A1"]]"""),
|
||||
)
|
||||
val view = QuestionResultView(tool)
|
||||
val style = SessionEditorStyle.create(family = "Courier New", size = 22)
|
||||
|
||||
view.applyStyle(style)
|
||||
|
||||
assertEquals("Title should use boldUiFont", style.boldUiFont, view.titleFont())
|
||||
assertEquals("Subtitle should use smallUiFont", style.smallUiFont, view.subFont())
|
||||
assertFalse("Title should not use editor font family", view.titleFont().name == "Courier New")
|
||||
assertFalse("Subtitle should not use editor font family", view.subFont().name == "Courier New")
|
||||
}
|
||||
|
||||
// ------ update ------
|
||||
|
||||
+30
-5
@@ -87,6 +87,31 @@ class QuestionViewTest : BasePlatformTestCase() {
|
||||
assertTrue(replies.isEmpty())
|
||||
}
|
||||
|
||||
fun `test question action buttons share right-aligned footer group`() {
|
||||
view.show(singleSelectQuestion("req_actions"))
|
||||
|
||||
val dismiss = button(view, "Dismiss")
|
||||
val submit = button(view, "Submit")
|
||||
assertSame("Dismiss and Submit should be in the same right-aligned group", dismiss.parent, submit.parent)
|
||||
assertTrue("Dismiss should appear before Submit", dismiss.parent.components.indexOf(dismiss) < submit.parent.components.indexOf(submit))
|
||||
}
|
||||
|
||||
fun `test review action buttons share right-aligned footer group`() {
|
||||
view.show(twoItemQuestion("req_review_actions"))
|
||||
option<JBRadioButton>(view, "Minimal").doClick()
|
||||
button(view, "Next").doClick()
|
||||
option<JBRadioButton>(view, "Unit").doClick()
|
||||
button(view, "Review").doClick()
|
||||
|
||||
val dismiss = button(view, "Dismiss")
|
||||
val back = button(view, "Back")
|
||||
val submit = button(view, "Submit")
|
||||
assertSame("Dismiss and Back should be in the same right-aligned group", dismiss.parent, back.parent)
|
||||
assertSame("Back and Submit should be in the same right-aligned group", back.parent, submit.parent)
|
||||
assertTrue("Dismiss should appear before Back", dismiss.parent.components.indexOf(dismiss) < back.parent.components.indexOf(back))
|
||||
assertTrue("Back should appear before Submit", back.parent.components.indexOf(back) < submit.parent.components.indexOf(submit))
|
||||
}
|
||||
|
||||
// ------ radio options ------
|
||||
|
||||
fun `test single question renders radio options`() {
|
||||
@@ -159,19 +184,19 @@ class QuestionViewTest : BasePlatformTestCase() {
|
||||
assertEquals("description should align in the text renderer", label.parent, desc.parent)
|
||||
|
||||
val style = SessionEditorStyle.current()
|
||||
assertEquals("option label should use bold editor font", style.boldEditorFont, label.font)
|
||||
assertEquals("description should use transcript font", style.transcriptFont, desc.font)
|
||||
assertEquals("option label should use boldUiFont", style.boldUiFont, label.font)
|
||||
assertEquals("description should use uiFont", style.uiFont, desc.font)
|
||||
}
|
||||
|
||||
fun `test question title and hint use editor fonts`() {
|
||||
fun `test question title and hint use UI-family editor-sized fonts`() {
|
||||
view.show(singleSelectQuestion("q_fonts"))
|
||||
|
||||
val style = SessionEditorStyle.current()
|
||||
val title = text(view, "Choose approach")
|
||||
val hint = text(view, "Select one answer")
|
||||
|
||||
assertEquals(style.boldEditorFont, title.font)
|
||||
assertEquals(style.transcriptFont, hint.font)
|
||||
assertEquals(style.boldUiFont, title.font)
|
||||
assertEquals(style.uiFont, hint.font)
|
||||
}
|
||||
|
||||
// ------ multi-question navigation ------
|
||||
|
||||
Reference in New Issue
Block a user