feat: webapp container tab state management
This commit is contained in:
@@ -0,0 +1,54 @@
|
|||||||
|
package top.yeij.hearth.webapp
|
||||||
|
|
||||||
|
// 单个 WebView 标签:id 唯一标识,active 表示是否为当前激活标签
|
||||||
|
// Single WebView tab: id is the unique key, active marks the currently-focused tab
|
||||||
|
data class Tab(val id: String, val url: String, val active: Boolean = false)
|
||||||
|
|
||||||
|
// 多标签状态管理(纯 Kotlin 数据层,不依赖 WebView,可 JVM 单测)
|
||||||
|
// 真实 WebView 导航(goBack/goForward/reload)留待 Task 13 集成时接入
|
||||||
|
// Multi-tab state management (pure Kotlin data layer, WebView-free, JVM-testable)
|
||||||
|
// Real WebView navigation (goBack/goForward/reload) is wired up in Task 13
|
||||||
|
class WebAppContainer {
|
||||||
|
private val tabs = mutableListOf<Tab>()
|
||||||
|
|
||||||
|
// 打开标签:已存在则激活它(不重复添加),否则新增并激活
|
||||||
|
// Open a tab: if it already exists just activate it, otherwise add and activate
|
||||||
|
fun open(id: String, url: String) {
|
||||||
|
val existing = tabs.find { it.id == id }
|
||||||
|
if (existing != null) {
|
||||||
|
setActive(id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (i in tabs.indices) tabs[i] = tabs[i].copy(active = false)
|
||||||
|
tabs.add(Tab(id, url, active = true))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭标签:若关闭后没有激活标签则激活第一个
|
||||||
|
// Close a tab: if none remain active, activate the first one
|
||||||
|
fun close(id: String) {
|
||||||
|
tabs.removeAll { it.id == id }
|
||||||
|
if (tabs.isNotEmpty() && tabs.none { it.active }) {
|
||||||
|
tabs[0] = tabs[0].copy(active = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换激活标签
|
||||||
|
// Switch the active tab
|
||||||
|
fun switchTo(id: String) {
|
||||||
|
setActive(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回标签列表副本(外部无法直接改动内部状态)
|
||||||
|
// Return a defensive copy of the tab list
|
||||||
|
fun tabs(): List<Tab> = tabs.toList()
|
||||||
|
|
||||||
|
private fun setActive(id: String) {
|
||||||
|
for (i in tabs.indices) tabs[i] = tabs[i].copy(active = tabs[i].id == id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 真实回退由 WebView 层实现,数据层仅占位返回 false
|
||||||
|
// Real back navigation lives in the WebView layer; data layer is a stub
|
||||||
|
fun goBack(): Boolean = false
|
||||||
|
fun goForward(): Boolean = false
|
||||||
|
fun reload() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package top.yeij.hearth.webapp
|
||||||
|
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class WebAppContainerTest {
|
||||||
|
@Test
|
||||||
|
fun open_addsTab_andBecomesActive() {
|
||||||
|
val c = WebAppContainer()
|
||||||
|
c.open("a", "http://x/a")
|
||||||
|
c.open("b", "http://x/b")
|
||||||
|
assertEquals(2, c.tabs().size)
|
||||||
|
assertEquals("b", c.tabs().first { it.active }.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun close_removesTab() {
|
||||||
|
val c = WebAppContainer()
|
||||||
|
c.open("a", "http://x/a"); c.open("b", "http://x/b")
|
||||||
|
c.close("a")
|
||||||
|
assertEquals(1, c.tabs().size)
|
||||||
|
assertEquals("b", c.tabs()[0].id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun goBack_noHistory_returnsFalse() {
|
||||||
|
val c = WebAppContainer()
|
||||||
|
c.open("a", "http://x/a")
|
||||||
|
assertTrue(!c.goBack()) // 无历史
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun open_existingTab_activatesWithoutDuplicating() {
|
||||||
|
val c = WebAppContainer()
|
||||||
|
c.open("a", "http://x/a")
|
||||||
|
c.open("b", "http://x/b")
|
||||||
|
c.open("a", "http://x/a")
|
||||||
|
assertEquals(2, c.tabs().size)
|
||||||
|
assertEquals("a", c.tabs().first { it.active }.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun switchTo_activatesSpecifiedTab() {
|
||||||
|
val c = WebAppContainer()
|
||||||
|
c.open("a", "http://x/a")
|
||||||
|
c.open("b", "http://x/b")
|
||||||
|
c.switchTo("a")
|
||||||
|
assertEquals("a", c.tabs().first { it.active }.id)
|
||||||
|
assertEquals(1, c.tabs().count { it.active })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun close_activeTab_activatesFirstRemaining() {
|
||||||
|
val c = WebAppContainer()
|
||||||
|
c.open("a", "http://x/a")
|
||||||
|
c.open("b", "http://x/b")
|
||||||
|
c.close("b")
|
||||||
|
assertEquals("a", c.tabs().first { it.active }.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user