feat: add proxy labels and default profiles
This commit is contained in:
+181
-84
@@ -33,11 +33,12 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
instanceNameColumnWeight = 20
|
||||
instanceBrowserColumnWeight = 10
|
||||
instanceDirectoryColumnWeight = 29
|
||||
instancePortColumnWeight = 10
|
||||
instanceStatusColumnWeight = 13
|
||||
instanceNameColumnWeight = 17
|
||||
instanceBrowserColumnWeight = 9
|
||||
instanceDirectoryColumnWeight = 28
|
||||
instanceProxyColumnWeight = 9
|
||||
instancePortColumnWeight = 9
|
||||
instanceStatusColumnWeight = 10
|
||||
instanceActionColumnWeight = 18
|
||||
)
|
||||
|
||||
@@ -314,6 +315,7 @@ type Shell struct {
|
||||
formFeedback string
|
||||
editFeedback string
|
||||
proxyServer widget.Editor
|
||||
proxyName widget.Editor
|
||||
proxySave widget.Clickable
|
||||
proxyDelete widget.Clickable
|
||||
proxyFeedback string
|
||||
@@ -332,69 +334,71 @@ type Shell struct {
|
||||
pendingProxyDelete string
|
||||
proxyDeleteFocus bool
|
||||
|
||||
list widget.List
|
||||
settingsList widget.List
|
||||
tabsList widget.List
|
||||
rows []InstanceRow
|
||||
selectedInstanceID string
|
||||
rowClicks map[string]*widget.Clickable
|
||||
startClicks map[string]*widget.Clickable
|
||||
editClicks map[string]*widget.Clickable
|
||||
deleteClicks map[string]*widget.Clickable
|
||||
deleteConfirm widget.Clickable
|
||||
deleteCancel widget.Clickable
|
||||
deleteBlocker widget.Clickable
|
||||
startStates map[string]*instanceStartState
|
||||
startResults chan instanceStartResult
|
||||
stopStates map[string]*instanceStopState
|
||||
stopResults chan instanceStopResult
|
||||
refreshClickState instanceRefreshState
|
||||
refreshResults chan instanceRefreshResult
|
||||
tabsResults chan instanceTabsResult
|
||||
managedExitMu sync.Mutex
|
||||
managedExitResults []ManagedInstanceExit
|
||||
pendingManagedExit map[managedExitKey]ManagedInstanceExit
|
||||
unexpectedExits []unexpectedExitNotice
|
||||
unexpectedExitOpen bool
|
||||
unexpectedExitAck widget.Clickable
|
||||
unexpectedExitBlocker widget.Clickable
|
||||
unexpectedExitFocus bool
|
||||
unexpectedExitFocusID string
|
||||
nextInstance uint64
|
||||
instanceFeedback string
|
||||
pendingDeleteID string
|
||||
editingID string
|
||||
editOriginal InstanceRow
|
||||
pendingEditDiscard bool
|
||||
editFocusPending bool
|
||||
focusRestoreID string
|
||||
focusStartID string
|
||||
tabsOpen bool
|
||||
tabsLoading bool
|
||||
tabsRequest uint64
|
||||
tabsInstanceID string
|
||||
tabsFingerprint string
|
||||
tabsTargets []CDPTarget
|
||||
tabsFeedback string
|
||||
tabsCancel context.CancelFunc
|
||||
tabsClose widget.Clickable
|
||||
tabsBlocker widget.Clickable
|
||||
tabsFocusPending bool
|
||||
tabsReturnFocus bool
|
||||
onSave func(SettingsState)
|
||||
onInstancesChanged func([]InstanceRow)
|
||||
onProxiesChanged func([]ProxyOption)
|
||||
instanceStarter InstanceStarter
|
||||
instanceStopper InstanceStopper
|
||||
instanceRefresher InstanceRefresher
|
||||
instanceTabInspector InstanceTabInspector
|
||||
pathSearcher PathSearcher
|
||||
invalidate func()
|
||||
searches map[PathField]*pathSearchState
|
||||
searchResults chan pathSearchResult
|
||||
directoryChooser DirectoryChooser
|
||||
directoryPick directoryPickState
|
||||
directoryResults chan directoryPickResult
|
||||
list widget.List
|
||||
settingsList widget.List
|
||||
tabsList widget.List
|
||||
rows []InstanceRow
|
||||
selectedInstanceID string
|
||||
rowClicks map[string]*widget.Clickable
|
||||
startClicks map[string]*widget.Clickable
|
||||
editClicks map[string]*widget.Clickable
|
||||
deleteClicks map[string]*widget.Clickable
|
||||
deleteConfirm widget.Clickable
|
||||
deleteCancel widget.Clickable
|
||||
deleteBlocker widget.Clickable
|
||||
startStates map[string]*instanceStartState
|
||||
startResults chan instanceStartResult
|
||||
stopStates map[string]*instanceStopState
|
||||
stopResults chan instanceStopResult
|
||||
refreshClickState instanceRefreshState
|
||||
refreshResults chan instanceRefreshResult
|
||||
tabsResults chan instanceTabsResult
|
||||
managedExitMu sync.Mutex
|
||||
managedExitResults []ManagedInstanceExit
|
||||
pendingManagedExit map[managedExitKey]ManagedInstanceExit
|
||||
unexpectedExits []unexpectedExitNotice
|
||||
unexpectedExitOpen bool
|
||||
unexpectedExitAck widget.Clickable
|
||||
unexpectedExitBlocker widget.Clickable
|
||||
unexpectedExitFocus bool
|
||||
unexpectedExitFocusID string
|
||||
nextInstance uint64
|
||||
pendingCreateID string
|
||||
defaultInstanceDirRoot string
|
||||
instanceFeedback string
|
||||
pendingDeleteID string
|
||||
editingID string
|
||||
editOriginal InstanceRow
|
||||
pendingEditDiscard bool
|
||||
editFocusPending bool
|
||||
focusRestoreID string
|
||||
focusStartID string
|
||||
tabsOpen bool
|
||||
tabsLoading bool
|
||||
tabsRequest uint64
|
||||
tabsInstanceID string
|
||||
tabsFingerprint string
|
||||
tabsTargets []CDPTarget
|
||||
tabsFeedback string
|
||||
tabsCancel context.CancelFunc
|
||||
tabsClose widget.Clickable
|
||||
tabsBlocker widget.Clickable
|
||||
tabsFocusPending bool
|
||||
tabsReturnFocus bool
|
||||
onSave func(SettingsState)
|
||||
onInstancesChanged func([]InstanceRow)
|
||||
onProxiesChanged func([]ProxyOption)
|
||||
instanceStarter InstanceStarter
|
||||
instanceStopper InstanceStopper
|
||||
instanceRefresher InstanceRefresher
|
||||
instanceTabInspector InstanceTabInspector
|
||||
pathSearcher PathSearcher
|
||||
invalidate func()
|
||||
searches map[PathField]*pathSearchState
|
||||
searchResults chan pathSearchResult
|
||||
directoryChooser DirectoryChooser
|
||||
directoryPick directoryPickState
|
||||
directoryResults chan directoryPickResult
|
||||
}
|
||||
|
||||
func NewShell(theme *material.Theme) *Shell {
|
||||
@@ -459,10 +463,23 @@ func (s *Shell) SetProxies(options []ProxyOption) {
|
||||
}
|
||||
if !s.proxyExists(s.settingsProxyID) {
|
||||
s.settingsProxyID = ""
|
||||
s.proxyName.SetText("")
|
||||
s.proxyServer.SetText("")
|
||||
}
|
||||
}
|
||||
|
||||
// SetDefaultInstanceUserDataRoot supplies the already resolved executable-side
|
||||
// profile root. It is deliberately a string-only UI boundary: no filesystem
|
||||
// lookup, creation, or permission check happens in the Gio shell.
|
||||
func (s *Shell) SetDefaultInstanceUserDataRoot(root string) {
|
||||
root = strings.TrimSpace(root)
|
||||
if root == "" || !filepath.IsAbs(root) {
|
||||
s.defaultInstanceDirRoot = ""
|
||||
return
|
||||
}
|
||||
s.defaultInstanceDirRoot = filepath.Clean(root)
|
||||
}
|
||||
|
||||
func (s *Shell) SetSettings(value SettingsState) {
|
||||
s.chromePath.SetText(value.ChromePath)
|
||||
s.edgePath.SetText(value.EdgePath)
|
||||
@@ -662,6 +679,17 @@ func (s *Shell) consumeControls(gtx layout.Context) {
|
||||
func (s *Shell) beginCreate() {
|
||||
s.page = pageCreate
|
||||
s.formFeedback = ""
|
||||
s.pendingCreateID = s.allocateInstanceID()
|
||||
s.instanceName.SetText("")
|
||||
s.instanceURL.SetText("")
|
||||
s.createProxyID = ""
|
||||
s.browserKind.Value = "chrome"
|
||||
if defaultDir := s.defaultCreateInstanceDir(s.pendingCreateID); defaultDir != "" {
|
||||
s.instanceDir.SetText(defaultDir)
|
||||
} else {
|
||||
s.instanceDir.SetText("")
|
||||
s.formFeedback = "无法确定程序目录中的默认 User Data Dir;请选择或输入一个绝对路径。"
|
||||
}
|
||||
s.instancePort.SetText(strconv.Itoa(s.recommendRemoteDebugPort("")))
|
||||
}
|
||||
|
||||
@@ -1016,6 +1044,7 @@ func (s *Shell) instanceListRow(row InstanceRow) layout.Widget {
|
||||
layout.Flexed(instanceNameColumnWeight, material.Body1(s.theme, row.Name).Layout),
|
||||
layout.Flexed(instanceBrowserColumnWeight, material.Body2(s.theme, row.Browser).Layout),
|
||||
layout.Flexed(instanceDirectoryColumnWeight, pathCell(s.theme, row.UserDataDir)),
|
||||
layout.Flexed(instanceProxyColumnWeight, proxyNameCell(s.theme, s.proxyDisplayName(row.ProxyID))),
|
||||
layout.Flexed(instancePortColumnWeight, remoteDebugPortCell(s.theme, row)),
|
||||
layout.Flexed(instanceStatusColumnWeight, statusLabel(s.theme, row.Status)),
|
||||
)
|
||||
@@ -1049,6 +1078,7 @@ func (s *Shell) instanceHeader(gtx layout.Context) layout.Dimensions {
|
||||
layout.Flexed(instanceNameColumnWeight, material.Caption(s.theme, "实例名称").Layout),
|
||||
layout.Flexed(instanceBrowserColumnWeight, material.Caption(s.theme, "浏览器类型").Layout),
|
||||
layout.Flexed(instanceDirectoryColumnWeight, material.Caption(s.theme, "用户数据目录").Layout),
|
||||
layout.Flexed(instanceProxyColumnWeight, material.Caption(s.theme, "代理").Layout),
|
||||
layout.Flexed(instancePortColumnWeight, material.Caption(s.theme, "调试端口").Layout),
|
||||
layout.Flexed(instanceStatusColumnWeight, material.Caption(s.theme, "状态").Layout),
|
||||
layout.Flexed(instanceActionColumnWeight, func(gtx layout.Context) layout.Dimensions {
|
||||
@@ -1072,6 +1102,12 @@ func remoteDebugPortCell(theme *material.Theme, row InstanceRow) layout.Widget {
|
||||
return style.Layout
|
||||
}
|
||||
|
||||
func proxyNameCell(theme *material.Theme, name string) layout.Widget {
|
||||
style := material.Body2(theme, name)
|
||||
style.MaxLines = 1
|
||||
return style.Layout
|
||||
}
|
||||
|
||||
func actualRemoteDebugPortText(port int) string {
|
||||
if port > 0 {
|
||||
return strconv.Itoa(port)
|
||||
@@ -1965,6 +2001,8 @@ func (s *Shell) proxySettings(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(s.settingsField(func(gtx layout.Context) layout.Dimensions { return s.proxyPickerField(gtx, proxyPickerSettings) })),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||
layout.Rigid(s.settingsField(s.formField("代理名称", "用于实例列表和选择器识别;名称必须唯一", &s.proxyName))),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||
layout.Rigid(s.settingsField(s.formField("代理地址", "支持 http、https、socks4、socks5;不允许用户名、密码或路径", &s.proxyServer))),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||
layout.Rigid(s.settingsActionBar(func(gtx layout.Context) layout.Dimensions {
|
||||
@@ -2124,7 +2162,7 @@ func (s *Shell) proxyPickerField(gtx layout.Context, target proxyPickerTarget) l
|
||||
click = &s.settingsProxyPick
|
||||
selected = s.settingsProxyID
|
||||
label = "已保存代理"
|
||||
help = "选择项会回填代理地址;选择新建会清空输入框"
|
||||
help = "选择项会回填代理名称和地址;选择新建会清空输入框"
|
||||
}
|
||||
for click.Clicked(gtx) {
|
||||
s.proxyPicker = proxyPickerState{open: true, target: target}
|
||||
@@ -2138,17 +2176,26 @@ func (s *Shell) proxyPickerField(gtx layout.Context, target proxyPickerTarget) l
|
||||
}
|
||||
|
||||
func (s *Shell) proxyLabel(id string) string {
|
||||
if option, exists := s.proxyOption(id); exists {
|
||||
return proxyPickerLabel(option)
|
||||
}
|
||||
return s.proxyDisplayName(id)
|
||||
}
|
||||
|
||||
func (s *Shell) proxyDisplayName(id string) string {
|
||||
if id == "" {
|
||||
return "无代理"
|
||||
}
|
||||
for _, option := range s.proxies {
|
||||
if option.ID == id {
|
||||
return option.Server
|
||||
}
|
||||
if option, exists := s.proxyOption(id); exists {
|
||||
return option.Name
|
||||
}
|
||||
return "代理不可用"
|
||||
}
|
||||
|
||||
func proxyPickerLabel(option ProxyOption) string {
|
||||
return option.Name + " · " + option.Server
|
||||
}
|
||||
|
||||
func (s *Shell) proxyExists(id string) bool {
|
||||
if _, exists := s.proxyOption(id); exists {
|
||||
return true
|
||||
@@ -2186,10 +2233,12 @@ func (s *Shell) selectPickerProxy(id string) {
|
||||
} else if s.proxyPicker.target == proxyPickerSettings {
|
||||
s.settingsProxyID = id
|
||||
if id == "" {
|
||||
s.proxyName.SetText("")
|
||||
s.proxyServer.SetText("")
|
||||
} else {
|
||||
for _, option := range s.proxies {
|
||||
if option.ID == id {
|
||||
s.proxyName.SetText(option.Name)
|
||||
s.proxyServer.SetText(option.Server)
|
||||
break
|
||||
}
|
||||
@@ -2252,7 +2301,14 @@ func (s *Shell) proxyPickerCard(gtx layout.Context) layout.Dimensions {
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return s.proxyPickerChoiceFor(option.ID).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Inset{Top: unit.Dp(7), Bottom: unit.Dp(7), Left: unit.Dp(10), Right: unit.Dp(10)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return material.Body1(s.theme, option.Server).Layout(gtx)
|
||||
name := material.Body1(s.theme, option.Name)
|
||||
name.MaxLines = 1
|
||||
server := material.Caption(s.theme, option.Server)
|
||||
server.MaxLines = 1
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(name.Layout),
|
||||
layout.Rigid(server.Layout),
|
||||
)
|
||||
})
|
||||
})
|
||||
}),
|
||||
@@ -2300,8 +2356,8 @@ func (s *Shell) createInstanceFromForm() {
|
||||
return
|
||||
}
|
||||
userDataDir := strings.TrimSpace(s.instanceDir.Text())
|
||||
if userDataDir == "" {
|
||||
s.formFeedback = "请选择或输入 User Data Dir 后再创建。"
|
||||
if userDataDir == "" || !filepath.IsAbs(userDataDir) {
|
||||
s.formFeedback = "请选择或输入绝对路径的 User Data Dir 后再创建。"
|
||||
return
|
||||
}
|
||||
preferredPort, err := normalizePreferredRemoteDebugPort(s.instancePort.Text())
|
||||
@@ -2313,12 +2369,15 @@ func (s *Shell) createInstanceFromForm() {
|
||||
s.formFeedback = fmt.Sprintf("端口 %d 已被其他 Chub 实例预留或使用。", preferredPort)
|
||||
return
|
||||
}
|
||||
s.nextInstance++
|
||||
instanceID := s.pendingCreateID
|
||||
if instanceID == "" || s.instanceIDExists(instanceID) {
|
||||
instanceID = s.allocateInstanceID()
|
||||
}
|
||||
s.rows = append(s.rows, InstanceRow{
|
||||
ID: fmt.Sprintf("instance-%d", s.nextInstance),
|
||||
ID: instanceID,
|
||||
Name: name,
|
||||
Browser: browserDisplay(s.browserKind.Value),
|
||||
UserDataDir: userDataDir,
|
||||
UserDataDir: filepath.Clean(userDataDir),
|
||||
TargetURL: strings.TrimSpace(s.instanceURL.Text()),
|
||||
ProxyID: s.createProxyID,
|
||||
PreferredRemoteDebugPort: preferredPort,
|
||||
@@ -2326,9 +2385,36 @@ func (s *Shell) createInstanceFromForm() {
|
||||
})
|
||||
s.page = pageInstances
|
||||
s.formFeedback = ""
|
||||
s.pendingCreateID = ""
|
||||
s.notifyInstancesChanged()
|
||||
}
|
||||
|
||||
func (s *Shell) defaultCreateInstanceDir(instanceID string) string {
|
||||
if s.defaultInstanceDirRoot == "" || instanceID == "" {
|
||||
return ""
|
||||
}
|
||||
return filepath.Join(s.defaultInstanceDirRoot, instanceID)
|
||||
}
|
||||
|
||||
func (s *Shell) allocateInstanceID() string {
|
||||
for {
|
||||
s.nextInstance++
|
||||
candidate := fmt.Sprintf("instance-%d-%d", time.Now().UnixNano(), s.nextInstance)
|
||||
if !s.instanceIDExists(candidate) && candidate != s.pendingCreateID {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Shell) instanceIDExists(id string) bool {
|
||||
for _, row := range s.rows {
|
||||
if row.ID == id {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Shell) togglePathSearch(field PathField) {
|
||||
state := s.searches[field]
|
||||
if state == nil {
|
||||
@@ -2469,12 +2555,21 @@ func (s *Shell) deleteClickFor(id string) *widget.Clickable {
|
||||
}
|
||||
|
||||
func (s *Shell) saveProxy() {
|
||||
name, err := domain.NormalizeProxyName(s.proxyName.Text())
|
||||
if err != nil {
|
||||
s.proxyFeedback = "请输入代理名称。"
|
||||
return
|
||||
}
|
||||
server, err := domain.NormalizeProxyServer(s.proxyServer.Text())
|
||||
if err != nil || server == "" {
|
||||
s.proxyFeedback = "代理地址必须是无认证的 scheme://host:port。"
|
||||
return
|
||||
}
|
||||
for _, option := range s.proxies {
|
||||
if option.ID != s.settingsProxyID && strings.EqualFold(option.Name, name) {
|
||||
s.proxyFeedback = "该代理名称已存在,请使用其他名称。"
|
||||
return
|
||||
}
|
||||
if option.ID != s.settingsProxyID && option.Server == server {
|
||||
s.proxyFeedback = "该代理地址已存在,请从下拉框选择它。"
|
||||
return
|
||||
@@ -2483,15 +2578,15 @@ func (s *Shell) saveProxy() {
|
||||
if s.settingsProxyID == "" {
|
||||
s.nextProxy++
|
||||
s.settingsProxyID = fmt.Sprintf("proxy-%d", s.nextProxy)
|
||||
s.proxies = append(s.proxies, ProxyOption{ID: s.settingsProxyID, Name: server, Server: server})
|
||||
s.proxyFeedback = fmt.Sprintf("已添加代理“%s”;后续启动将使用该地址。", server)
|
||||
s.proxies = append(s.proxies, ProxyOption{ID: s.settingsProxyID, Name: name, Server: server})
|
||||
s.proxyFeedback = fmt.Sprintf("已添加代理“%s”;后续启动将使用该地址。", name)
|
||||
} else {
|
||||
updated := false
|
||||
for index := range s.proxies {
|
||||
if s.proxies[index].ID != s.settingsProxyID {
|
||||
continue
|
||||
}
|
||||
s.proxies[index].Name = server
|
||||
s.proxies[index].Name = name
|
||||
s.proxies[index].Server = server
|
||||
updated = true
|
||||
break
|
||||
@@ -2500,8 +2595,9 @@ func (s *Shell) saveProxy() {
|
||||
s.proxyFeedback = "所选代理已不可用,请重新选择或新建。"
|
||||
return
|
||||
}
|
||||
s.proxyFeedback = fmt.Sprintf("已保存代理“%s”;后续启动将使用最新端点。", server)
|
||||
s.proxyFeedback = fmt.Sprintf("已保存代理“%s”;后续启动将使用最新端点。", name)
|
||||
}
|
||||
s.proxyName.SetText(name)
|
||||
s.proxyServer.SetText(server)
|
||||
s.notifyProxiesChanged()
|
||||
}
|
||||
@@ -2552,9 +2648,10 @@ func (s *Shell) confirmProxyDelete() {
|
||||
}
|
||||
if s.settingsProxyID == id {
|
||||
s.settingsProxyID = ""
|
||||
s.proxyName.SetText("")
|
||||
s.proxyServer.SetText("")
|
||||
}
|
||||
s.proxyFeedback = fmt.Sprintf("已删除代理“%s”。", option.Server)
|
||||
s.proxyFeedback = fmt.Sprintf("已删除代理“%s”。", option.Name)
|
||||
s.notifyProxiesChanged()
|
||||
return
|
||||
}
|
||||
|
||||
+67
-13
@@ -68,13 +68,16 @@ func TestShellInstanceRowsContainRequiredColumnsAndIndependentActionControls(t *
|
||||
}
|
||||
|
||||
func TestInstanceListColumnWeightsPrioritizeDirectoryAndCompactAction(t *testing.T) {
|
||||
got := instanceNameColumnWeight + instanceBrowserColumnWeight + instanceDirectoryColumnWeight + instancePortColumnWeight + instanceStatusColumnWeight + instanceActionColumnWeight
|
||||
got := instanceNameColumnWeight + instanceBrowserColumnWeight + instanceDirectoryColumnWeight + instanceProxyColumnWeight + instancePortColumnWeight + instanceStatusColumnWeight + instanceActionColumnWeight
|
||||
if got != 100 {
|
||||
t.Fatalf("instance column weights = %d, want 100", got)
|
||||
}
|
||||
if instanceDirectoryColumnWeight <= instanceNameColumnWeight {
|
||||
t.Fatal("user data directory must have the widest instance-list column")
|
||||
}
|
||||
if instanceProxyColumnWeight >= instanceDirectoryColumnWeight {
|
||||
t.Fatal("proxy name must remain a compact display-only column")
|
||||
}
|
||||
if instanceActionColumnWeight <= instanceStatusColumnWeight {
|
||||
t.Fatal("actions column must fit both compact icon commands")
|
||||
}
|
||||
@@ -716,8 +719,11 @@ func TestShellRequestsDiscardConfirmationForDirtyEdit(t *testing.T) {
|
||||
func TestShellCreatesAndEditsInstanceProxySelection(t *testing.T) {
|
||||
shell := NewShell(material.NewTheme())
|
||||
shell.SetProxies([]ProxyOption{{ID: "proxy-sg", Name: "新加坡", Server: "http://127.0.0.1:8080"}})
|
||||
if label := shell.proxyLabel("proxy-sg"); label != "http://127.0.0.1:8080" {
|
||||
t.Fatalf("proxy label = %q, want complete proxy address", label)
|
||||
if label := shell.proxyLabel("proxy-sg"); label != "新加坡 · http://127.0.0.1:8080" {
|
||||
t.Fatalf("proxy label = %q, want name and complete proxy address", label)
|
||||
}
|
||||
if display := shell.proxyDisplayName("proxy-sg"); display != "新加坡" {
|
||||
t.Fatalf("proxy display name = %q", display)
|
||||
}
|
||||
shell.instanceName.SetText("新实例")
|
||||
shell.instanceDir.SetText(t.TempDir())
|
||||
@@ -741,6 +747,7 @@ func TestShellPreventsDeletingReferencedProxy(t *testing.T) {
|
||||
shell.SetProxies([]ProxyOption{{ID: "proxy-sg", Name: "新加坡", Server: "http://127.0.0.1:8080"}})
|
||||
shell.rows[0].ProxyID = "proxy-sg"
|
||||
shell.settingsProxyID = "proxy-sg"
|
||||
shell.proxyName.SetText("新加坡")
|
||||
shell.proxyServer.SetText("http://127.0.0.1:8080")
|
||||
changes := 0
|
||||
shell.OnProxiesChanged(func([]ProxyOption) { changes++ })
|
||||
@@ -754,13 +761,13 @@ func TestShellPreventsDeletingReferencedProxy(t *testing.T) {
|
||||
t.Fatalf("pending proxy deletion = %q", shell.pendingProxyDelete)
|
||||
}
|
||||
shell.cancelProxyDelete()
|
||||
if shell.pendingProxyDelete != "" || len(shell.proxies) != 1 || changes != 0 || shell.settingsProxyID != "proxy-sg" || shell.proxyServer.Text() != "http://127.0.0.1:8080" {
|
||||
t.Fatalf("cancelled delete state = proxies %#v, changes %d, selection %q, server %q", shell.proxies, changes, shell.settingsProxyID, shell.proxyServer.Text())
|
||||
if shell.pendingProxyDelete != "" || len(shell.proxies) != 1 || changes != 0 || shell.settingsProxyID != "proxy-sg" || shell.proxyName.Text() != "新加坡" || shell.proxyServer.Text() != "http://127.0.0.1:8080" {
|
||||
t.Fatalf("cancelled delete state = proxies %#v, changes %d, selection %q, name %q, server %q", shell.proxies, changes, shell.settingsProxyID, shell.proxyName.Text(), shell.proxyServer.Text())
|
||||
}
|
||||
shell.requestProxyDelete()
|
||||
shell.confirmProxyDelete()
|
||||
if len(shell.proxies) != 0 || changes != 1 || shell.settingsProxyID != "" || shell.proxyServer.Text() != "" {
|
||||
t.Fatalf("unreferenced delete state = proxies %#v, changes %d, selection %q, server %q", shell.proxies, changes, shell.settingsProxyID, shell.proxyServer.Text())
|
||||
if len(shell.proxies) != 0 || changes != 1 || shell.settingsProxyID != "" || shell.proxyName.Text() != "" || shell.proxyServer.Text() != "" {
|
||||
t.Fatalf("unreferenced delete state = proxies %#v, changes %d, selection %q, name %q, server %q", shell.proxies, changes, shell.settingsProxyID, shell.proxyName.Text(), shell.proxyServer.Text())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -772,29 +779,76 @@ func TestShellSavesProxyFromAddressPicker(t *testing.T) {
|
||||
|
||||
shell.proxyPicker.target = proxyPickerSettings
|
||||
shell.selectPickerProxy("proxy-sg")
|
||||
if shell.settingsProxyID != "proxy-sg" || shell.proxyServer.Text() != "http://127.0.0.1:8080" {
|
||||
t.Fatalf("selected proxy = %q, server %q", shell.settingsProxyID, shell.proxyServer.Text())
|
||||
if shell.settingsProxyID != "proxy-sg" || shell.proxyName.Text() != "旧名称" || shell.proxyServer.Text() != "http://127.0.0.1:8080" {
|
||||
t.Fatalf("selected proxy = %q, name %q, server %q", shell.settingsProxyID, shell.proxyName.Text(), shell.proxyServer.Text())
|
||||
}
|
||||
|
||||
shell.proxyName.SetText("新加坡出口")
|
||||
shell.proxyServer.SetText("http://127.0.0.1:8081")
|
||||
shell.saveProxy()
|
||||
if len(shell.proxies) != 1 || shell.proxies[0].ID != "proxy-sg" || shell.proxies[0].Name != "http://127.0.0.1:8081" || shell.proxies[0].Server != "http://127.0.0.1:8081" || changes != 1 {
|
||||
if len(shell.proxies) != 1 || shell.proxies[0].ID != "proxy-sg" || shell.proxies[0].Name != "新加坡出口" || shell.proxies[0].Server != "http://127.0.0.1:8081" || changes != 1 {
|
||||
t.Fatalf("updated proxy = %#v, changes %d", shell.proxies, changes)
|
||||
}
|
||||
if display := shell.proxyDisplayName("proxy-sg"); display != "新加坡出口" {
|
||||
t.Fatalf("updated proxy display name = %q", display)
|
||||
}
|
||||
|
||||
shell.proxyPicker.target = proxyPickerSettings
|
||||
shell.selectPickerProxy("")
|
||||
if shell.settingsProxyID != "" || shell.proxyServer.Text() != "" {
|
||||
t.Fatalf("new proxy state = id %q, server %q", shell.settingsProxyID, shell.proxyServer.Text())
|
||||
if shell.settingsProxyID != "" || shell.proxyName.Text() != "" || shell.proxyServer.Text() != "" {
|
||||
t.Fatalf("new proxy state = id %q, name %q, server %q", shell.settingsProxyID, shell.proxyName.Text(), shell.proxyServer.Text())
|
||||
}
|
||||
|
||||
shell.proxyName.SetText("东京出口")
|
||||
shell.proxyServer.SetText("http://127.0.0.1:8082")
|
||||
shell.saveProxy()
|
||||
if len(shell.proxies) != 2 || shell.settingsProxyID == "" || shell.proxies[1].Name != "http://127.0.0.1:8082" || shell.proxies[1].Server != "http://127.0.0.1:8082" || changes != 2 {
|
||||
if len(shell.proxies) != 2 || shell.settingsProxyID == "" || shell.proxies[1].Name != "东京出口" || shell.proxies[1].Server != "http://127.0.0.1:8082" || changes != 2 {
|
||||
t.Fatalf("created proxy = %#v, selected %q, changes %d", shell.proxies, shell.settingsProxyID, changes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShellRejectsDuplicateProxyNameWithoutReplacingSelectedProfile(t *testing.T) {
|
||||
shell := NewShell(material.NewTheme())
|
||||
shell.SetProxies([]ProxyOption{
|
||||
{ID: "proxy-sg", Name: "新加坡出口", Server: "http://127.0.0.1:8080"},
|
||||
{ID: "proxy-jp", Name: "东京出口", Server: "http://127.0.0.1:8081"},
|
||||
})
|
||||
shell.proxyPicker.target = proxyPickerSettings
|
||||
shell.selectPickerProxy("proxy-jp")
|
||||
shell.proxyName.SetText(" 新加坡出口 ")
|
||||
shell.proxyServer.SetText("http://127.0.0.1:8082")
|
||||
shell.saveProxy()
|
||||
if !strings.Contains(shell.proxyFeedback, "名称已存在") || shell.proxies[1].Name != "东京出口" || shell.proxies[1].Server != "http://127.0.0.1:8081" {
|
||||
t.Fatalf("duplicate name state = proxies %#v, feedback %q", shell.proxies, shell.proxyFeedback)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShellUsesDistinctDefaultDirectoriesForNewInstances(t *testing.T) {
|
||||
shell := NewShell(material.NewTheme())
|
||||
root := filepath.Join(t.TempDir(), "user_data_dirs")
|
||||
shell.SetDefaultInstanceUserDataRoot(root)
|
||||
|
||||
shell.beginCreate()
|
||||
firstID := shell.pendingCreateID
|
||||
firstDir := shell.instanceDir.Text()
|
||||
if firstID == "" || firstDir != filepath.Join(root, firstID) {
|
||||
t.Fatalf("first default directory = %q for id %q", firstDir, firstID)
|
||||
}
|
||||
shell.instanceName.SetText("第一个实例")
|
||||
shell.createInstanceFromForm()
|
||||
created := shell.rows[len(shell.rows)-1]
|
||||
if created.ID != firstID || created.UserDataDir != firstDir {
|
||||
t.Fatalf("created default instance = %#v", created)
|
||||
}
|
||||
|
||||
shell.beginCreate()
|
||||
secondID := shell.pendingCreateID
|
||||
secondDir := shell.instanceDir.Text()
|
||||
if secondID == "" || secondID == firstID || secondDir != filepath.Join(root, secondID) || secondDir == firstDir {
|
||||
t.Fatalf("second default directory = %q for id %q, first %q", secondDir, secondID, firstDir)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShellMarksUnexpectedManagedExitAndRestoresStartFocus(t *testing.T) {
|
||||
shell := NewShell(material.NewTheme())
|
||||
target := shell.rows[0]
|
||||
|
||||
Reference in New Issue
Block a user