refactor(ui): move runtime config state out of app
- commit
- 42e6c82f8ebacebbfd415df053ad469ac02b6580
- author
- brice · · 2026-06-23 16:32 UTC
- committer
- brice · · 2026-06-23 20:01 UTC
- parent
- a0ed55e
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
10 files · +334 -230
M internal/ui/app.go +57 -66
| @@ -78,9 +78,7 @@ type App struct { | |||
| 78 | 78 | gtkApp *gtk.Application | |
| 79 | 79 | mainWindow *window.MainWindow | |
| 80 | 80 | ||
| 81 | - | runtimeConfig port.RuntimeConfigSnapshot | |
| 82 | - | runtimeConfigLoaded bool | |
| 83 | - | popupBrowsingContexts entity.BrowsingContextConfig | |
| 81 | + | runtimeConfig *runtimeConfigState | |
| 84 | 82 | ||
| 85 | 83 | browserWindows map[string]*browserWindow | |
| 86 | 84 | browserWindowOrder []string // registration order of window IDs | |
| @@ -185,20 +183,17 @@ func New(deps *Dependencies) (*App, error) { | |||
| 185 | 183 | return nil, err | |
| 186 | 184 | } | |
| 187 | 185 | ctx, cancel := context.WithCancelCause(deps.Ctx) | |
| 188 | - | initialRuntimeConfig := deps.RuntimeConfig.Current() | |
| 189 | 186 | ||
| 190 | 187 | app := &App{ | |
| 191 | - | deps: deps, | |
| 192 | - | runtimeConfig: initialRuntimeConfig, | |
| 193 | - | runtimeConfigLoaded: true, | |
| 194 | - | popupBrowsingContexts: initialRuntimeConfig.UI.Workspace.BrowsingContexts, | |
| 195 | - | tabs: entity.NewTabList(), | |
| 196 | - | tabsUC: deps.TabsUC, | |
| 197 | - | panesUC: deps.PanesUC, | |
| 198 | - | workspaceViews: make(map[entity.TabID]*component.WorkspaceView), | |
| 199 | - | windowForTab: make(map[entity.TabID]*browserWindow), | |
| 200 | - | floatingSessions: make(map[floatingSessionKey]*floatingWorkspaceSession), | |
| 201 | - | browserWindows: make(map[string]*browserWindow), | |
| 188 | + | deps: deps, | |
| 189 | + | runtimeConfig: newRuntimeConfigState(deps.RuntimeConfig), | |
| 190 | + | tabs: entity.NewTabList(), | |
| 191 | + | tabsUC: deps.TabsUC, | |
| 192 | + | panesUC: deps.PanesUC, | |
| 193 | + | workspaceViews: make(map[entity.TabID]*component.WorkspaceView), | |
| 194 | + | windowForTab: make(map[entity.TabID]*browserWindow), | |
| 195 | + | floatingSessions: make(map[floatingSessionKey]*floatingWorkspaceSession), | |
| 196 | + | browserWindows: make(map[string]*browserWindow), | |
| 202 | 197 | dispatchOnMainThread: func(label string, fn func()) syncdispatch.SyncDispatchResult { | |
| 203 | 198 | if fn != nil { | |
| 204 | 199 | fn() | |
| @@ -264,8 +259,8 @@ func (a *App) runtimeConfigSnapshot() port.RuntimeConfigSnapshot { | |||
| 264 | 259 | if a == nil { | |
| 265 | 260 | return port.RuntimeConfigSnapshot{} | |
| 266 | 261 | } | |
| 267 | - | if a.runtimeConfigLoaded { | |
| 268 | - | return a.runtimeConfig | |
| 262 | + | if a.runtimeConfig != nil { | |
| 263 | + | return a.runtimeConfig.Current() | |
| 269 | 264 | } | |
| 270 | 265 | if a.deps != nil && a.deps.RuntimeConfig != nil { | |
| 271 | 266 | return a.deps.RuntimeConfig.Current() | |
| @@ -273,30 +268,6 @@ func (a *App) runtimeConfigSnapshot() port.RuntimeConfigSnapshot { | |||
| 273 | 268 | return port.RuntimeConfigSnapshot{} | |
| 274 | 269 | } | |
| 275 | 270 | ||
| 276 | - | func (a *App) updateRuntimeConfig(snapshot port.RuntimeConfigSnapshot) { | |
| 277 | - | if a != nil { | |
| 278 | - | a.runtimeConfig = snapshot | |
| 279 | - | a.runtimeConfigLoaded = true | |
| 280 | - | a.updatePopupRuntimeConfig(snapshot.UI.Workspace.BrowsingContexts) | |
| 281 | - | } | |
| 282 | - | } | |
| 283 | - | ||
| 284 | - | func (a *App) updatePopupRuntimeConfig(cfg entity.BrowsingContextConfig) { | |
| 285 | - | if a != nil { | |
| 286 | - | a.popupBrowsingContexts = cfg | |
| 287 | - | } | |
| 288 | - | } | |
| 289 | - | ||
| 290 | - | func (a *App) popupBrowsingContextConfig() *entity.BrowsingContextConfig { | |
| 291 | - | if a == nil { | |
| 292 | - | return nil | |
| 293 | - | } | |
| 294 | - | if !a.runtimeConfigLoaded { | |
| 295 | - | a.updatePopupRuntimeConfig(a.runtimeConfigSnapshot().UI.Workspace.BrowsingContexts) | |
| 296 | - | } | |
| 297 | - | return &a.popupBrowsingContexts | |
| 298 | - | } | |
| 299 | - | ||
| 300 | 271 | // Run starts the GTK application and blocks until it exits. | |
| 301 | 272 | // Returns the exit code. | |
| 302 | 273 | func (a *App) Run(ctx context.Context, args []string) int { | |
| @@ -3394,7 +3365,7 @@ func (a *App) initCoordinators(ctx context.Context) { | |||
| 3394 | 3365 | } | |
| 3395 | 3366 | a.contentCoord.SetPopupConfig( | |
| 3396 | 3367 | a.engine.Factory(), | |
| 3397 | - | a.popupBrowsingContextConfig(), | |
| 3368 | + | &runtimeCfg.Workspace.BrowsingContexts, | |
| 3398 | 3369 | a.generateID, | |
| 3399 | 3370 | ) | |
| 3400 | 3371 | a.contentCoord.SetPopupWindowIDResolver(func(paneID entity.PaneID) (string, bool) { | |
| @@ -5115,35 +5086,55 @@ func (a *App) initConfigWatcher(ctx context.Context) { | |||
| 5115 | 5086 | ||
| 5116 | 5087 | // Hot-reload appearance and keybindings on config change. | |
| 5117 | 5088 | a.deps.RuntimeConfig.OnChange(func(snapshot port.RuntimeConfigSnapshot) { | |
| 5118 | - | cb := glib.SourceFunc(func(_ uintptr) bool { | |
| 5119 | - | a.updateRuntimeConfig(snapshot) | |
| 5120 | - | runtimeCfg := snapshot.UI | |
| 5121 | - | workspaceCfg := runtimeCfg.Workspace | |
| 5122 | - | sessionCfg := runtimeCfg.Session | |
| 5123 | - | a.syncExternalThemeWatcher(ctx) | |
| 5124 | - | a.applyAppearanceConfig(ctx) | |
| 5125 | - | for _, bw := range a.browserWindows { | |
| 5126 | - | if bw == nil { | |
| 5127 | - | continue | |
| 5128 | - | } | |
| 5129 | - | // Reapply sidebar width from live config (reloads after sidebar_width | |
| 5130 | - | // changes in the config file). | |
| 5131 | - | bw.applySidebarWidthConfig(a) | |
| 5132 | - | if bw.keyboardHandler != nil { | |
| 5133 | - | bw.keyboardHandler.ReloadShortcuts(ctx, &workspaceCfg, &sessionCfg) | |
| 5134 | - | } | |
| 5135 | - | if bw.globalShortcutHandler != nil { | |
| 5136 | - | bw.globalShortcutHandler.ReloadShortcuts(ctx, &workspaceCfg, &sessionCfg) | |
| 5137 | - | } | |
| 5138 | - | } | |
| 5139 | - | return false | |
| 5140 | - | }) | |
| 5141 | - | glib.IdleAdd(&cb, 0) | |
| 5089 | + | a.dispatchRuntimeConfigChange(ctx, snapshot) | |
| 5142 | 5090 | }) | |
| 5143 | 5091 | ||
| 5144 | 5092 | log.Debug().Msg("config watcher initialized") | |
| 5145 | 5093 | } | |
| 5146 | 5094 | ||
| 5095 | + | func (a *App) dispatchRuntimeConfigChange(ctx context.Context, snapshot port.RuntimeConfigSnapshot) { | |
| 5096 | + | run := func() { | |
| 5097 | + | a.applyRuntimeConfigChange(ctx, snapshot) | |
| 5098 | + | } | |
| 5099 | + | if a != nil && a.dispatchOnMainThread != nil { | |
| 5100 | + | a.dispatchOnMainThread("ui.runtime_config_reload", run) | |
| 5101 | + | return | |
| 5102 | + | } | |
| 5103 | + | run() | |
| 5104 | + | } | |
| 5105 | + | ||
| 5106 | + | func (a *App) applyRuntimeConfigChange(ctx context.Context, snapshot port.RuntimeConfigSnapshot) { | |
| 5107 | + | if a == nil { | |
| 5108 | + | return | |
| 5109 | + | } | |
| 5110 | + | if a.runtimeConfig == nil { | |
| 5111 | + | a.runtimeConfig = newRuntimeConfigState(nil) | |
| 5112 | + | } | |
| 5113 | + | a.runtimeConfig.Update(snapshot) | |
| 5114 | + | if a.contentCoord != nil { | |
| 5115 | + | a.contentCoord.UpdatePopupConfig(snapshot.UI.Workspace.BrowsingContexts) | |
| 5116 | + | } | |
| 5117 | + | runtimeCfg := snapshot.UI | |
| 5118 | + | workspaceCfg := runtimeCfg.Workspace | |
| 5119 | + | sessionCfg := runtimeCfg.Session | |
| 5120 | + | a.syncExternalThemeWatcher(ctx) | |
| 5121 | + | a.applyAppearanceConfig(ctx) | |
| 5122 | + | for _, bw := range a.browserWindows { | |
| 5123 | + | if bw == nil { | |
| 5124 | + | continue | |
| 5125 | + | } | |
| 5126 | + | // Reapply sidebar width from live config (reloads after sidebar_width | |
| 5127 | + | // changes in the config file). | |
| 5128 | + | bw.applySidebarWidthConfig(a) | |
| 5129 | + | if bw.keyboardHandler != nil { | |
| 5130 | + | bw.keyboardHandler.ReloadShortcuts(ctx, &workspaceCfg, &sessionCfg) | |
| 5131 | + | } | |
| 5132 | + | if bw.globalShortcutHandler != nil { | |
| 5133 | + | bw.globalShortcutHandler.ReloadShortcuts(ctx, &workspaceCfg, &sessionCfg) | |
| 5134 | + | } | |
| 5135 | + | } | |
| 5136 | + | } | |
| 5137 | + | ||
| 5147 | 5138 | func (a *App) syncExternalThemeWatcher(ctx context.Context) { | |
| 5148 | 5139 | if a == nil || a.deps == nil || a.deps.ExternalThemeWatcher == nil { | |
| 5149 | 5140 | return | |
| @@ -78,9 +78,7 @@ type App struct { | |||||
| 78 | gtkApp *gtk.Application | 78 | gtkApp *gtk.Application | ||
| 79 | mainWindow *window.MainWindow | 79 | mainWindow *window.MainWindow | ||
| 80 | 80 | ||||
| 81 | - | runtimeConfig port.RuntimeConfigSnapshot | 81 | + | runtimeConfig *runtimeConfigState |
| 82 | - | runtimeConfigLoaded bool | |||
| 83 | - | popupBrowsingContexts entity.BrowsingContextConfig | |||
| 84 | 82 | ||||
| 85 | browserWindows map[string]*browserWindow | 83 | browserWindows map[string]*browserWindow | ||
| 86 | browserWindowOrder []string // registration order of window IDs | 84 | browserWindowOrder []string // registration order of window IDs | ||
| @@ -185,20 +183,17 @@ func New(deps *Dependencies) (*App, error) { | |||||
| 185 | return nil, err | 183 | return nil, err | ||
| 186 | } | 184 | } | ||
| 187 | ctx, cancel := context.WithCancelCause(deps.Ctx) | 185 | ctx, cancel := context.WithCancelCause(deps.Ctx) | ||
| 188 | - | initialRuntimeConfig := deps.RuntimeConfig.Current() | |||
| 189 | 186 | ||||
| 190 | app := &App{ | 187 | app := &App{ | ||
| 191 | - | deps: deps, | 188 | + | deps: deps, |
| 192 | - | runtimeConfig: initialRuntimeConfig, | 189 | + | runtimeConfig: newRuntimeConfigState(deps.RuntimeConfig), |
| 193 | - | runtimeConfigLoaded: true, | 190 | + | tabs: entity.NewTabList(), |
| 194 | - | popupBrowsingContexts: initialRuntimeConfig.UI.Workspace.BrowsingContexts, | 191 | + | tabsUC: deps.TabsUC, |
| 195 | - | tabs: entity.NewTabList(), | 192 | + | panesUC: deps.PanesUC, |
| 196 | - | tabsUC: deps.TabsUC, | 193 | + | workspaceViews: make(map[entity.TabID]*component.WorkspaceView), |
| 197 | - | panesUC: deps.PanesUC, | 194 | + | windowForTab: make(map[entity.TabID]*browserWindow), |
| 198 | - | workspaceViews: make(map[entity.TabID]*component.WorkspaceView), | 195 | + | floatingSessions: make(map[floatingSessionKey]*floatingWorkspaceSession), |
| 199 | - | windowForTab: make(map[entity.TabID]*browserWindow), | 196 | + | browserWindows: make(map[string]*browserWindow), |
| 200 | - | floatingSessions: make(map[floatingSessionKey]*floatingWorkspaceSession), | |||
| 201 | - | browserWindows: make(map[string]*browserWindow), | |||
| 202 | dispatchOnMainThread: func(label string, fn func()) syncdispatch.SyncDispatchResult { | 197 | dispatchOnMainThread: func(label string, fn func()) syncdispatch.SyncDispatchResult { | ||
| 203 | if fn != nil { | 198 | if fn != nil { | ||
| 204 | fn() | 199 | fn() | ||
| @@ -264,8 +259,8 @@ func (a *App) runtimeConfigSnapshot() port.RuntimeConfigSnapshot { | |||||
| 264 | if a == nil { | 259 | if a == nil { | ||
| 265 | return port.RuntimeConfigSnapshot{} | 260 | return port.RuntimeConfigSnapshot{} | ||
| 266 | } | 261 | } | ||
| 267 | - | if a.runtimeConfigLoaded { | 262 | + | if a.runtimeConfig != nil { |
| 268 | - | return a.runtimeConfig | 263 | + | return a.runtimeConfig.Current() |
| 269 | } | 264 | } | ||
| 270 | if a.deps != nil && a.deps.RuntimeConfig != nil { | 265 | if a.deps != nil && a.deps.RuntimeConfig != nil { | ||
| 271 | return a.deps.RuntimeConfig.Current() | 266 | return a.deps.RuntimeConfig.Current() | ||
| @@ -273,30 +268,6 @@ func (a *App) runtimeConfigSnapshot() port.RuntimeConfigSnapshot { | |||||
| 273 | return port.RuntimeConfigSnapshot{} | 268 | return port.RuntimeConfigSnapshot{} | ||
| 274 | } | 269 | } | ||
| 275 | 270 | ||||
| 276 | - | func (a *App) updateRuntimeConfig(snapshot port.RuntimeConfigSnapshot) { | |||
| 277 | - | if a != nil { | |||
| 278 | - | a.runtimeConfig = snapshot | |||
| 279 | - | a.runtimeConfigLoaded = true | |||
| 280 | - | a.updatePopupRuntimeConfig(snapshot.UI.Workspace.BrowsingContexts) | |||
| 281 | - | } | |||
| 282 | - | } | |||
| 283 | - | ||||
| 284 | - | func (a *App) updatePopupRuntimeConfig(cfg entity.BrowsingContextConfig) { | |||
| 285 | - | if a != nil { | |||
| 286 | - | a.popupBrowsingContexts = cfg | |||
| 287 | - | } | |||
| 288 | - | } | |||
| 289 | - | ||||
| 290 | - | func (a *App) popupBrowsingContextConfig() *entity.BrowsingContextConfig { | |||
| 291 | - | if a == nil { | |||
| 292 | - | return nil | |||
| 293 | - | } | |||
| 294 | - | if !a.runtimeConfigLoaded { | |||
| 295 | - | a.updatePopupRuntimeConfig(a.runtimeConfigSnapshot().UI.Workspace.BrowsingContexts) | |||
| 296 | - | } | |||
| 297 | - | return &a.popupBrowsingContexts | |||
| 298 | - | } | |||
| 299 | - | ||||
| 300 | // Run starts the GTK application and blocks until it exits. | 271 | // Run starts the GTK application and blocks until it exits. | ||
| 301 | // Returns the exit code. | 272 | // Returns the exit code. | ||
| 302 | func (a *App) Run(ctx context.Context, args []string) int { | 273 | func (a *App) Run(ctx context.Context, args []string) int { | ||
| @@ -3394,7 +3365,7 @@ func (a *App) initCoordinators(ctx context.Context) { | |||||
| 3394 | } | 3365 | } | ||
| 3395 | a.contentCoord.SetPopupConfig( | 3366 | a.contentCoord.SetPopupConfig( | ||
| 3396 | a.engine.Factory(), | 3367 | a.engine.Factory(), | ||
| 3397 | - | a.popupBrowsingContextConfig(), | 3368 | + | &runtimeCfg.Workspace.BrowsingContexts, |
| 3398 | a.generateID, | 3369 | a.generateID, | ||
| 3399 | ) | 3370 | ) | ||
| 3400 | a.contentCoord.SetPopupWindowIDResolver(func(paneID entity.PaneID) (string, bool) { | 3371 | a.contentCoord.SetPopupWindowIDResolver(func(paneID entity.PaneID) (string, bool) { | ||
| @@ -5115,35 +5086,55 @@ func (a *App) initConfigWatcher(ctx context.Context) { | |||||
| 5115 | 5086 | ||||
| 5116 | // Hot-reload appearance and keybindings on config change. | 5087 | // Hot-reload appearance and keybindings on config change. | ||
| 5117 | a.deps.RuntimeConfig.OnChange(func(snapshot port.RuntimeConfigSnapshot) { | 5088 | a.deps.RuntimeConfig.OnChange(func(snapshot port.RuntimeConfigSnapshot) { | ||
| 5118 | - | cb := glib.SourceFunc(func(_ uintptr) bool { | 5089 | + | a.dispatchRuntimeConfigChange(ctx, snapshot) |
| 5119 | - | a.updateRuntimeConfig(snapshot) | |||
| 5120 | - | runtimeCfg := snapshot.UI | |||
| 5121 | - | workspaceCfg := runtimeCfg.Workspace | |||
| 5122 | - | sessionCfg := runtimeCfg.Session | |||
| 5123 | - | a.syncExternalThemeWatcher(ctx) | |||
| 5124 | - | a.applyAppearanceConfig(ctx) | |||
| 5125 | - | for _, bw := range a.browserWindows { | |||
| 5126 | - | if bw == nil { | |||
| 5127 | - | continue | |||
| 5128 | - | } | |||
| 5129 | - | // Reapply sidebar width from live config (reloads after sidebar_width | |||
| 5130 | - | // changes in the config file). | |||
| 5131 | - | bw.applySidebarWidthConfig(a) | |||
| 5132 | - | if bw.keyboardHandler != nil { | |||
| 5133 | - | bw.keyboardHandler.ReloadShortcuts(ctx, &workspaceCfg, &sessionCfg) | |||
| 5134 | - | } | |||
| 5135 | - | if bw.globalShortcutHandler != nil { | |||
| 5136 | - | bw.globalShortcutHandler.ReloadShortcuts(ctx, &workspaceCfg, &sessionCfg) | |||
| 5137 | - | } | |||
| 5138 | - | } | |||
| 5139 | - | return false | |||
| 5140 | - | }) | |||
| 5141 | - | glib.IdleAdd(&cb, 0) | |||
| 5142 | }) | 5090 | }) | ||
| 5143 | 5091 | ||||
| 5144 | log.Debug().Msg("config watcher initialized") | 5092 | log.Debug().Msg("config watcher initialized") | ||
| 5145 | } | 5093 | } | ||
| 5146 | 5094 | ||||
| 5095 | + | func (a *App) dispatchRuntimeConfigChange(ctx context.Context, snapshot port.RuntimeConfigSnapshot) { | |||
| 5096 | + | run := func() { | |||
| 5097 | + | a.applyRuntimeConfigChange(ctx, snapshot) | |||
| 5098 | + | } | |||
| 5099 | + | if a != nil && a.dispatchOnMainThread != nil { | |||
| 5100 | + | a.dispatchOnMainThread("ui.runtime_config_reload", run) | |||
| 5101 | + | return | |||
| 5102 | + | } | |||
| 5103 | + | run() | |||
| 5104 | + | } | |||
| 5105 | + | ||||
| 5106 | + | func (a *App) applyRuntimeConfigChange(ctx context.Context, snapshot port.RuntimeConfigSnapshot) { | |||
| 5107 | + | if a == nil { | |||
| 5108 | + | return | |||
| 5109 | + | } | |||
| 5110 | + | if a.runtimeConfig == nil { | |||
| 5111 | + | a.runtimeConfig = newRuntimeConfigState(nil) | |||
| 5112 | + | } | |||
| 5113 | + | a.runtimeConfig.Update(snapshot) | |||
| 5114 | + | if a.contentCoord != nil { | |||
| 5115 | + | a.contentCoord.UpdatePopupConfig(snapshot.UI.Workspace.BrowsingContexts) | |||
| 5116 | + | } | |||
| 5117 | + | runtimeCfg := snapshot.UI | |||
| 5118 | + | workspaceCfg := runtimeCfg.Workspace | |||
| 5119 | + | sessionCfg := runtimeCfg.Session | |||
| 5120 | + | a.syncExternalThemeWatcher(ctx) | |||
| 5121 | + | a.applyAppearanceConfig(ctx) | |||
| 5122 | + | for _, bw := range a.browserWindows { | |||
| 5123 | + | if bw == nil { | |||
| 5124 | + | continue | |||
| 5125 | + | } | |||
| 5126 | + | // Reapply sidebar width from live config (reloads after sidebar_width | |||
| 5127 | + | // changes in the config file). | |||
| 5128 | + | bw.applySidebarWidthConfig(a) | |||
| 5129 | + | if bw.keyboardHandler != nil { | |||
| 5130 | + | bw.keyboardHandler.ReloadShortcuts(ctx, &workspaceCfg, &sessionCfg) | |||
| 5131 | + | } | |||
| 5132 | + | if bw.globalShortcutHandler != nil { | |||
| 5133 | + | bw.globalShortcutHandler.ReloadShortcuts(ctx, &workspaceCfg, &sessionCfg) | |||
| 5134 | + | } | |||
| 5135 | + | } | |||
| 5136 | + | } | |||
| 5137 | + | ||||
| 5147 | func (a *App) syncExternalThemeWatcher(ctx context.Context) { | 5138 | func (a *App) syncExternalThemeWatcher(ctx context.Context) { | ||
| 5148 | if a == nil || a.deps == nil || a.deps.ExternalThemeWatcher == nil { | 5139 | if a == nil || a.deps == nil || a.deps.ExternalThemeWatcher == nil { | ||
| 5149 | return | 5140 | return | ||
M internal/ui/app_browser_launch_test.go +65 -90
| @@ -1023,8 +1023,7 @@ func TestApp_CheckConfigMigrationUsesLastFocusedBrowserWindowToaster(t *testing. | |||
| 1023 | 1023 | deps: &Dependencies{ | |
| 1024 | 1024 | MigrationChecker: migrator, | |
| 1025 | 1025 | }, | |
| 1026 | - | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | |
| 1027 | - | runtimeConfigLoaded: true, | |
| 1026 | + | runtimeConfig: runtimeConfigStateForTest(cfg), | |
| 1028 | 1027 | browserWindows: map[string]*browserWindow{bw.id: bw}, | |
| 1029 | 1028 | lastFocusedWindowID: bw.id, | |
| 1030 | 1029 | } | |
| @@ -1136,8 +1135,7 @@ func TestApp_RestoreSessionHandlesEmptyWindowSnapshotAsSuccess(t *testing.T) { | |||
| 1136 | 1135 | deps: &Dependencies{ | |
| 1137 | 1136 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | |
| 1138 | 1137 | }, | |
| 1139 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 1140 | - | runtimeConfigLoaded: true, | |
| 1138 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), | |
| 1141 | 1139 | tabs: entity.NewTabList(), | |
| 1142 | 1140 | browserWindows: map[string]*browserWindow{staleWindow.id: staleWindow}, | |
| 1143 | 1141 | lastFocusedWindowID: staleWindow.id, | |
| @@ -1178,8 +1176,7 @@ func TestApp_CreateInitialTabNoFallbackAfterEmptyWindowRestore(t *testing.T) { | |||
| 1178 | 1176 | InitialURL: "https://fallback.example", | |
| 1179 | 1177 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | |
| 1180 | 1178 | }, | |
| 1181 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 1182 | - | runtimeConfigLoaded: true, | |
| 1179 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), | |
| 1183 | 1180 | tabs: entity.NewTabList(), | |
| 1184 | 1181 | tabsUC: tabsUC, | |
| 1185 | 1182 | tabCoord: coordinator.NewTabCoordinator(ctx, coordinator.TabCoordinatorConfig{TabsUC: tabsUC, Tabs: entity.NewTabList()}), | |
| @@ -1219,14 +1216,13 @@ func TestApp_RestoreSessionClearsStaleUIStateBeforeApplyingRestoredTabs(t *testi | |||
| 1219 | 1216 | deps: &Dependencies{ | |
| 1220 | 1217 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, restoredSessionID, entity.SnapshotFromTabList(restoredSessionID, restoredTabs)), | |
| 1221 | 1218 | }, | |
| 1222 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 1223 | - | runtimeConfigLoaded: true, | |
| 1224 | - | mainWindow: mainWindow, | |
| 1225 | - | widgetFactory: layout.NewGtkWidgetFactory(), | |
| 1226 | - | tabs: entity.NewTabList(), | |
| 1227 | - | browserWindows: map[string]*browserWindow{staleWindow.id: staleWindow}, | |
| 1228 | - | workspaceViews: map[entity.TabID]*component.WorkspaceView{staleTab.ID: &component.WorkspaceView{}}, | |
| 1229 | - | windowForTab: map[entity.TabID]*browserWindow{staleTab.ID: staleWindow}, | |
| 1219 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), | |
| 1220 | + | mainWindow: mainWindow, | |
| 1221 | + | widgetFactory: layout.NewGtkWidgetFactory(), | |
| 1222 | + | tabs: entity.NewTabList(), | |
| 1223 | + | browserWindows: map[string]*browserWindow{staleWindow.id: staleWindow}, | |
| 1224 | + | workspaceViews: map[entity.TabID]*component.WorkspaceView{staleTab.ID: &component.WorkspaceView{}}, | |
| 1225 | + | windowForTab: map[entity.TabID]*browserWindow{staleTab.ID: staleWindow}, | |
| 1230 | 1226 | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{ | |
| 1231 | 1227 | staleSessionKey: {}, | |
| 1232 | 1228 | }, | |
| @@ -1287,16 +1283,15 @@ func TestApp_RestoreSessionWiresStackedPaneTitleBarCallbacks(t *testing.T) { | |||
| 1287 | 1283 | deps: &Dependencies{ | |
| 1288 | 1284 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, stackedSessionID, entity.SnapshotFromTabList(stackedSessionID, stackedTabs)), | |
| 1289 | 1285 | }, | |
| 1290 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 1291 | - | runtimeConfigLoaded: true, | |
| 1292 | - | mainWindow: mainWindow, | |
| 1293 | - | widgetFactory: layout.NewGtkWidgetFactory(), | |
| 1294 | - | contentCoord: &contentcoord.Coordinator{}, | |
| 1295 | - | wsCoord: coordinator.NewWorkspaceCoordinator(context.Background(), coordinator.WorkspaceCoordinatorConfig{ContentCoord: &contentcoord.Coordinator{}}), | |
| 1296 | - | tabs: entity.NewTabList(), | |
| 1297 | - | browserWindows: map[string]*browserWindow{}, | |
| 1298 | - | workspaceViews: map[entity.TabID]*component.WorkspaceView{}, | |
| 1299 | - | windowForTab: map[entity.TabID]*browserWindow{}, | |
| 1286 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), | |
| 1287 | + | mainWindow: mainWindow, | |
| 1288 | + | widgetFactory: layout.NewGtkWidgetFactory(), | |
| 1289 | + | contentCoord: &contentcoord.Coordinator{}, | |
| 1290 | + | wsCoord: coordinator.NewWorkspaceCoordinator(context.Background(), coordinator.WorkspaceCoordinatorConfig{ContentCoord: &contentcoord.Coordinator{}}), | |
| 1291 | + | tabs: entity.NewTabList(), | |
| 1292 | + | browserWindows: map[string]*browserWindow{}, | |
| 1293 | + | workspaceViews: map[entity.TabID]*component.WorkspaceView{}, | |
| 1294 | + | windowForTab: map[entity.TabID]*browserWindow{}, | |
| 1300 | 1295 | } | |
| 1301 | 1296 | ||
| 1302 | 1297 | if err := app.restoreSession(context.Background(), stackedSessionID); err != nil { | |
| @@ -1774,8 +1769,7 @@ func TestApp_MoveActivePaneToTabFromBrowserWindowAnchorsOwningWindow(t *testing. | |||
| 1774 | 1769 | tabs: tabs, | |
| 1775 | 1770 | movePaneToTabUC: usecase.NewMovePaneToTabUseCase(func() string { return "new-tab" }), | |
| 1776 | 1771 | deps: &Dependencies{}, | |
| 1777 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 1778 | - | runtimeConfigLoaded: true, | |
| 1772 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), | |
| 1779 | 1773 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | |
| 1780 | 1774 | lastFocusedWindowID: second.id, | |
| 1781 | 1775 | } | |
| @@ -1813,8 +1807,7 @@ func TestApp_MoveActivePaneToTabFromBrowserWindowActivatesTargetOwnerForCrossWin | |||
| 1813 | 1807 | tabs: tabs, | |
| 1814 | 1808 | movePaneToTabUC: usecase.NewMovePaneToTabUseCase(func() string { return "generated" }), | |
| 1815 | 1809 | deps: &Dependencies{}, | |
| 1816 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 1817 | - | runtimeConfigLoaded: true, | |
| 1810 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), | |
| 1818 | 1811 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | |
| 1819 | 1812 | windowForTab: map[entity.TabID]*browserWindow{tab1.ID: first, tab2.ID: second, tab3.ID: second}, | |
| 1820 | 1813 | lastFocusedWindowID: first.id, | |
| @@ -1870,8 +1863,7 @@ func TestApp_MoveActivePaneToTabFromBrowserWindowCrossWindowDerivedMirror(t *tes | |||
| 1870 | 1863 | tabs: tabs, | |
| 1871 | 1864 | movePaneToTabUC: usecase.NewMovePaneToTabUseCase(func() string { return "generated" }), | |
| 1872 | 1865 | deps: &Dependencies{}, | |
| 1873 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 1874 | - | runtimeConfigLoaded: true, | |
| 1866 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), | |
| 1875 | 1867 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | |
| 1876 | 1868 | windowForTab: map[entity.TabID]*browserWindow{tab1.ID: first, tab3.ID: second}, | |
| 1877 | 1869 | lastFocusedWindowID: first.id, | |
| @@ -1921,12 +1913,11 @@ func TestApp_InitKeyboardHandlerDoesNotReattachExistingWindowInput(t *testing.T) | |||
| 1921 | 1913 | globalShortcutHandler: sentinelShortcutHandler, | |
| 1922 | 1914 | } | |
| 1923 | 1915 | app := &App{ | |
| 1924 | - | deps: &Dependencies{}, | |
| 1925 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 1926 | - | runtimeConfigLoaded: true, | |
| 1927 | - | mainWindow: mainWindow, | |
| 1928 | - | browserWindows: map[string]*browserWindow{bw.id: bw}, | |
| 1929 | - | kbDispatcher: dispatcher.NewKeyboardDispatcher(context.Background(), nil, nil, nil, nil, dispatcher.KeyboardActions{}, func(context.Context) entity.PaneID { return "" }), | |
| 1916 | + | deps: &Dependencies{}, | |
| 1917 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), | |
| 1918 | + | mainWindow: mainWindow, | |
| 1919 | + | browserWindows: map[string]*browserWindow{bw.id: bw}, | |
| 1920 | + | kbDispatcher: dispatcher.NewKeyboardDispatcher(context.Background(), nil, nil, nil, nil, dispatcher.KeyboardActions{}, func(context.Context) entity.PaneID { return "" }), | |
| 1930 | 1921 | } | |
| 1931 | 1922 | ||
| 1932 | 1923 | app.initKeyboardHandler(context.Background()) | |
| @@ -1978,9 +1969,8 @@ func TestApp_UpdateBrowserWindowTabBarVisibilityAutoHidesSingleTabButPreservesAl | |||
| 1978 | 1969 | cfg := &config.Config{} | |
| 1979 | 1970 | cfg.Workspace.HideTabBarWhenSingleTab = true | |
| 1980 | 1971 | app := &App{ | |
| 1981 | - | deps: &Dependencies{}, | |
| 1982 | - | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | |
| 1983 | - | runtimeConfigLoaded: true, | |
| 1972 | + | deps: &Dependencies{}, | |
| 1973 | + | runtimeConfig: runtimeConfigStateForTest(cfg), | |
| 1984 | 1974 | } | |
| 1985 | 1975 | ||
| 1986 | 1976 | tab1 := entity.NewTab(entity.TabID("tab-1"), entity.WorkspaceID("workspace-1"), entity.NewPane(entity.PaneID("pane-1"))) | |
| @@ -2051,9 +2041,8 @@ func TestApp_UpdateBrowserWindowTabBarVisibilityHonorsHideWhenSingleTabDisabled( | |||
| 2051 | 2041 | cfg := &config.Config{} | |
| 2052 | 2042 | cfg.Workspace.HideTabBarWhenSingleTab = false | |
| 2053 | 2043 | app := &App{ | |
| 2054 | - | deps: &Dependencies{}, | |
| 2055 | - | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | |
| 2056 | - | runtimeConfigLoaded: true, | |
| 2044 | + | deps: &Dependencies{}, | |
| 2045 | + | runtimeConfig: runtimeConfigStateForTest(cfg), | |
| 2057 | 2046 | } | |
| 2058 | 2047 | ||
| 2059 | 2048 | app.updateBrowserWindowTabBarVisibility(bw) | |
| @@ -2111,9 +2100,8 @@ func TestApp_UpdateBrowserWindowTabBarVisibility_BottomInset(t *testing.T) { | |||
| 2111 | 2100 | cfg := &config.Config{} | |
| 2112 | 2101 | cfg.Workspace.HideTabBarWhenSingleTab = true | |
| 2113 | 2102 | app := &App{ | |
| 2114 | - | deps: &Dependencies{}, | |
| 2115 | - | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | |
| 2116 | - | runtimeConfigLoaded: true, | |
| 2103 | + | deps: &Dependencies{}, | |
| 2104 | + | runtimeConfig: runtimeConfigStateForTest(cfg), | |
| 2117 | 2105 | } | |
| 2118 | 2106 | ||
| 2119 | 2107 | tab1 := entity.NewTab(entity.TabID("tab-1"), entity.WorkspaceID("workspace-1"), entity.NewPane(entity.PaneID("pane-1"))) | |
| @@ -2171,8 +2159,7 @@ func TestApp_HandlePaneFullscreenChanged_ClearsBottomInset(t *testing.T) { | |||
| 2171 | 2159 | app := &App{ | |
| 2172 | 2160 | tabs: tabs, | |
| 2173 | 2161 | deps: &Dependencies{}, | |
| 2174 | - | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | |
| 2175 | - | runtimeConfigLoaded: true, | |
| 2162 | + | runtimeConfig: runtimeConfigStateForTest(cfg), | |
| 2176 | 2163 | browserWindows: map[string]*browserWindow{bw.id: bw}, | |
| 2177 | 2164 | lastFocusedWindowID: bw.id, | |
| 2178 | 2165 | } | |
| @@ -2291,8 +2278,7 @@ func TestApp_CreatePopupTabUsesParentPaneOwnerWhenFocusIsStale(t *testing.T) { | |||
| 2291 | 2278 | second := &browserWindow{id: "window-2", tabs: secondTabs, mainWindow: &window.MainWindow{}} | |
| 2292 | 2279 | app := &App{ | |
| 2293 | 2280 | deps: &Dependencies{}, | |
| 2294 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 2295 | - | runtimeConfigLoaded: true, | |
| 2281 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), | |
| 2296 | 2282 | tabs: globalTabs, | |
| 2297 | 2283 | tabsUC: usecase.NewManageTabsUseCase(func() string { return "popup-tab" }), | |
| 2298 | 2284 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | |
| @@ -2353,8 +2339,7 @@ func TestApp_TabCoordinatorCallbacksUseTargetWindowWhenFocusIsStale(t *testing.T | |||
| 2353 | 2339 | second := &browserWindow{id: "window-2", tabs: secondTabs, mainWindow: &window.MainWindow{}} | |
| 2354 | 2340 | app := &App{ | |
| 2355 | 2341 | deps: &Dependencies{}, | |
| 2356 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 2357 | - | runtimeConfigLoaded: true, | |
| 2342 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), | |
| 2358 | 2343 | tabs: entity.NewTabList(), | |
| 2359 | 2344 | tabsUC: usecase.NewManageTabsUseCase(func() string { return "created-tab" }), | |
| 2360 | 2345 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | |
| @@ -2987,8 +2972,7 @@ func TestApp_DispatchBrowserWindowActionSwitchTabIndexUsesSourceWindow(t *testin | |||
| 2987 | 2972 | } | |
| 2988 | 2973 | app := &App{ | |
| 2989 | 2974 | deps: &Dependencies{}, | |
| 2990 | - | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | |
| 2991 | - | runtimeConfigLoaded: true, | |
| 2975 | + | runtimeConfig: runtimeConfigStateForTest(cfg), | |
| 2992 | 2976 | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 2993 | 2977 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | |
| 2994 | 2978 | windowForTab: map[entity.TabID]*browserWindow{firstTab.ID: first, secondTab1.ID: second, secondTab2.ID: second}, | |
| @@ -3031,8 +3015,7 @@ func TestApp_DispatchBrowserWindowActionSwitchTabIndexCreatesOnlyOneMissingTab(t | |||
| 3031 | 3015 | } | |
| 3032 | 3016 | app := &App{ | |
| 3033 | 3017 | deps: &Dependencies{}, | |
| 3034 | - | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | |
| 3035 | - | runtimeConfigLoaded: true, | |
| 3018 | + | runtimeConfig: runtimeConfigStateForTest(cfg), | |
| 3036 | 3019 | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 3037 | 3020 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | |
| 3038 | 3021 | windowForTab: map[entity.TabID]*browserWindow{firstTab.ID: first, secondTab.ID: second}, | |
| @@ -3077,12 +3060,11 @@ func TestApp_SwitchBrowserWindowTabIndexNegativeIndexIsIgnored(t *testing.T) { | |||
| 3077 | 3060 | ||
| 3078 | 3061 | cfg := &config.Config{Workspace: config.WorkspaceConfig{NewPaneURL: "about:blank"}} | |
| 3079 | 3062 | app := &App{ | |
| 3080 | - | deps: &Dependencies{}, | |
| 3081 | - | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | |
| 3082 | - | runtimeConfigLoaded: true, | |
| 3083 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 3084 | - | browserWindows: map[string]*browserWindow{second.id: second}, | |
| 3085 | - | windowForTab: map[entity.TabID]*browserWindow{secondTab.ID: second}, | |
| 3063 | + | deps: &Dependencies{}, | |
| 3064 | + | runtimeConfig: runtimeConfigStateForTest(cfg), | |
| 3065 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 3066 | + | browserWindows: map[string]*browserWindow{second.id: second}, | |
| 3067 | + | windowForTab: map[entity.TabID]*browserWindow{secondTab.ID: second}, | |
| 3086 | 3068 | } | |
| 3087 | 3069 | app.initTabCoordinator(ctx) | |
| 3088 | 3070 | ||
| @@ -3106,8 +3088,7 @@ func TestApp_DispatchBrowserWindowActionSwitchTabIndexNegativeDoesNotMutateWindo | |||
| 3106 | 3088 | cfg := &config.Config{Workspace: config.WorkspaceConfig{NewPaneURL: "about:blank"}} | |
| 3107 | 3089 | app := &App{ | |
| 3108 | 3090 | deps: &Dependencies{}, | |
| 3109 | - | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | |
| 3110 | - | runtimeConfigLoaded: true, | |
| 3091 | + | runtimeConfig: runtimeConfigStateForTest(cfg), | |
| 3111 | 3092 | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 3112 | 3093 | browserWindows: map[string]*browserWindow{second.id: second}, | |
| 3113 | 3094 | lastFocusedWindowID: "window-1", | |
| @@ -3136,12 +3117,11 @@ func TestApp_DispatchBrowserWindowActionSwitchTabIndexMissingURLDoesNotCreateTab | |||
| 3136 | 3117 | second := &browserWindow{id: "window-2", tabs: secondTabs} | |
| 3137 | 3118 | ||
| 3138 | 3119 | app := &App{ | |
| 3139 | - | deps: &Dependencies{}, | |
| 3140 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 3141 | - | runtimeConfigLoaded: true, | |
| 3142 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 3143 | - | browserWindows: map[string]*browserWindow{second.id: second}, | |
| 3144 | - | windowForTab: map[entity.TabID]*browserWindow{secondTab.ID: second}, | |
| 3120 | + | deps: &Dependencies{}, | |
| 3121 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), | |
| 3122 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 3123 | + | browserWindows: map[string]*browserWindow{second.id: second}, | |
| 3124 | + | windowForTab: map[entity.TabID]*browserWindow{secondTab.ID: second}, | |
| 3145 | 3125 | } | |
| 3146 | 3126 | app.initTabCoordinator(ctx) | |
| 3147 | 3127 | ||
| @@ -3164,8 +3144,7 @@ func TestApp_SwitchBrowserWindowTabIndexMissingURLDoesNotMutateWindowState(t *te | |||
| 3164 | 3144 | ||
| 3165 | 3145 | app := &App{ | |
| 3166 | 3146 | deps: &Dependencies{}, | |
| 3167 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 3168 | - | runtimeConfigLoaded: true, | |
| 3147 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), | |
| 3169 | 3148 | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 3170 | 3149 | browserWindows: map[string]*browserWindow{second.id: second}, | |
| 3171 | 3150 | lastFocusedWindowID: "window-1", | |
| @@ -3208,8 +3187,7 @@ func TestApp_WorkspaceOmniboxNavigateUsesOwnerWindow(t *testing.T) { | |||
| 3208 | 3187 | ||
| 3209 | 3188 | app := &App{ | |
| 3210 | 3189 | deps: &Dependencies{}, | |
| 3211 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 3212 | - | runtimeConfigLoaded: true, | |
| 3190 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), | |
| 3213 | 3191 | widgetFactory: layout.NewGtkWidgetFactory(), | |
| 3214 | 3192 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | |
| 3215 | 3193 | windowForTab: map[entity.TabID]*browserWindow{tab1.ID: first, tab2.ID: second}, | |
| @@ -3306,9 +3284,8 @@ func TestApp_RestoreSessionDoesNotLeakStaleWindowsIntoTabMerge(t *testing.T) { | |||
| 3306 | 3284 | deps: &Dependencies{ | |
| 3307 | 3285 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | |
| 3308 | 3286 | }, | |
| 3309 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 3310 | - | runtimeConfigLoaded: true, | |
| 3311 | - | mainWindow: mainWindow, | |
| 3287 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), | |
| 3288 | + | mainWindow: mainWindow, | |
| 3312 | 3289 | browserWindows: map[string]*browserWindow{ | |
| 3313 | 3290 | staleBW1.id: staleBW1, | |
| 3314 | 3291 | staleBW2.id: staleBW2, | |
| @@ -3387,14 +3364,13 @@ func TestApp_RestoreSessionHonorsActiveWindowIndex(t *testing.T) { | |||
| 3387 | 3364 | deps: &Dependencies{ | |
| 3388 | 3365 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | |
| 3389 | 3366 | }, | |
| 3390 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 3391 | - | runtimeConfigLoaded: true, | |
| 3392 | - | mainWindow: mainWindow, | |
| 3393 | - | widgetFactory: layout.NewGtkWidgetFactory(), | |
| 3394 | - | browserWindows: map[string]*browserWindow{firstBW.id: firstBW}, | |
| 3395 | - | tabs: entity.NewTabList(), | |
| 3396 | - | windowForTab: map[entity.TabID]*browserWindow{}, | |
| 3397 | - | workspaceViews: map[entity.TabID]*component.WorkspaceView{}, | |
| 3367 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), | |
| 3368 | + | mainWindow: mainWindow, | |
| 3369 | + | widgetFactory: layout.NewGtkWidgetFactory(), | |
| 3370 | + | browserWindows: map[string]*browserWindow{firstBW.id: firstBW}, | |
| 3371 | + | tabs: entity.NewTabList(), | |
| 3372 | + | windowForTab: map[entity.TabID]*browserWindow{}, | |
| 3373 | + | workspaceViews: map[entity.TabID]*component.WorkspaceView{}, | |
| 3398 | 3374 | browserWindowFactory: func(ctx context.Context, url string) (*browserWindow, error) { | |
| 3399 | 3375 | runtimeW1 = &browserWindow{id: "runtime-w1", mainWindow: mainWindow} | |
| 3400 | 3376 | return runtimeW1, nil | |
| @@ -3455,12 +3431,11 @@ func TestApp_RestoreSessionFailsOnAdditionalWindowCreationError(t *testing.T) { | |||
| 3455 | 3431 | deps: &Dependencies{ | |
| 3456 | 3432 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | |
| 3457 | 3433 | }, | |
| 3458 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | |
| 3459 | - | runtimeConfigLoaded: true, | |
| 3460 | - | mainWindow: mainWindow, | |
| 3461 | - | browserWindows: map[string]*browserWindow{firstBW.id: firstBW}, | |
| 3462 | - | tabs: entity.NewTabList(), | |
| 3463 | - | windowForTab: map[entity.TabID]*browserWindow{}, | |
| 3434 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), | |
| 3435 | + | mainWindow: mainWindow, | |
| 3436 | + | browserWindows: map[string]*browserWindow{firstBW.id: firstBW}, | |
| 3437 | + | tabs: entity.NewTabList(), | |
| 3438 | + | windowForTab: map[entity.TabID]*browserWindow{}, | |
| 3464 | 3439 | browserWindowFactory: func(ctx context.Context, url string) (*browserWindow, error) { | |
| 3465 | 3440 | return nil, factoryErr | |
| 3466 | 3441 | }, | |
| @@ -1023,8 +1023,7 @@ func TestApp_CheckConfigMigrationUsesLastFocusedBrowserWindowToaster(t *testing. | |||||
| 1023 | deps: &Dependencies{ | 1023 | deps: &Dependencies{ | ||
| 1024 | MigrationChecker: migrator, | 1024 | MigrationChecker: migrator, | ||
| 1025 | }, | 1025 | }, | ||
| 1026 | - | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | 1026 | + | runtimeConfig: runtimeConfigStateForTest(cfg), |
| 1027 | - | runtimeConfigLoaded: true, | |||
| 1028 | browserWindows: map[string]*browserWindow{bw.id: bw}, | 1027 | browserWindows: map[string]*browserWindow{bw.id: bw}, | ||
| 1029 | lastFocusedWindowID: bw.id, | 1028 | lastFocusedWindowID: bw.id, | ||
| 1030 | } | 1029 | } | ||
| @@ -1136,8 +1135,7 @@ func TestApp_RestoreSessionHandlesEmptyWindowSnapshotAsSuccess(t *testing.T) { | |||||
| 1136 | deps: &Dependencies{ | 1135 | deps: &Dependencies{ | ||
| 1137 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | 1136 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | ||
| 1138 | }, | 1137 | }, | ||
| 1139 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | 1138 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), |
| 1140 | - | runtimeConfigLoaded: true, | |||
| 1141 | tabs: entity.NewTabList(), | 1139 | tabs: entity.NewTabList(), | ||
| 1142 | browserWindows: map[string]*browserWindow{staleWindow.id: staleWindow}, | 1140 | browserWindows: map[string]*browserWindow{staleWindow.id: staleWindow}, | ||
| 1143 | lastFocusedWindowID: staleWindow.id, | 1141 | lastFocusedWindowID: staleWindow.id, | ||
| @@ -1178,8 +1176,7 @@ func TestApp_CreateInitialTabNoFallbackAfterEmptyWindowRestore(t *testing.T) { | |||||
| 1178 | InitialURL: "https://fallback.example", | 1176 | InitialURL: "https://fallback.example", | ||
| 1179 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | 1177 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | ||
| 1180 | }, | 1178 | }, | ||
| 1181 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | 1179 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), |
| 1182 | - | runtimeConfigLoaded: true, | |||
| 1183 | tabs: entity.NewTabList(), | 1180 | tabs: entity.NewTabList(), | ||
| 1184 | tabsUC: tabsUC, | 1181 | tabsUC: tabsUC, | ||
| 1185 | tabCoord: coordinator.NewTabCoordinator(ctx, coordinator.TabCoordinatorConfig{TabsUC: tabsUC, Tabs: entity.NewTabList()}), | 1182 | tabCoord: coordinator.NewTabCoordinator(ctx, coordinator.TabCoordinatorConfig{TabsUC: tabsUC, Tabs: entity.NewTabList()}), | ||
| @@ -1219,14 +1216,13 @@ func TestApp_RestoreSessionClearsStaleUIStateBeforeApplyingRestoredTabs(t *testi | |||||
| 1219 | deps: &Dependencies{ | 1216 | deps: &Dependencies{ | ||
| 1220 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, restoredSessionID, entity.SnapshotFromTabList(restoredSessionID, restoredTabs)), | 1217 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, restoredSessionID, entity.SnapshotFromTabList(restoredSessionID, restoredTabs)), | ||
| 1221 | }, | 1218 | }, | ||
| 1222 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | 1219 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), |
| 1223 | - | runtimeConfigLoaded: true, | 1220 | + | mainWindow: mainWindow, |
| 1224 | - | mainWindow: mainWindow, | 1221 | + | widgetFactory: layout.NewGtkWidgetFactory(), |
| 1225 | - | widgetFactory: layout.NewGtkWidgetFactory(), | 1222 | + | tabs: entity.NewTabList(), |
| 1226 | - | tabs: entity.NewTabList(), | 1223 | + | browserWindows: map[string]*browserWindow{staleWindow.id: staleWindow}, |
| 1227 | - | browserWindows: map[string]*browserWindow{staleWindow.id: staleWindow}, | 1224 | + | workspaceViews: map[entity.TabID]*component.WorkspaceView{staleTab.ID: &component.WorkspaceView{}}, |
| 1228 | - | workspaceViews: map[entity.TabID]*component.WorkspaceView{staleTab.ID: &component.WorkspaceView{}}, | 1225 | + | windowForTab: map[entity.TabID]*browserWindow{staleTab.ID: staleWindow}, |
| 1229 | - | windowForTab: map[entity.TabID]*browserWindow{staleTab.ID: staleWindow}, | |||
| 1230 | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{ | 1226 | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{ | ||
| 1231 | staleSessionKey: {}, | 1227 | staleSessionKey: {}, | ||
| 1232 | }, | 1228 | }, | ||
| @@ -1287,16 +1283,15 @@ func TestApp_RestoreSessionWiresStackedPaneTitleBarCallbacks(t *testing.T) { | |||||
| 1287 | deps: &Dependencies{ | 1283 | deps: &Dependencies{ | ||
| 1288 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, stackedSessionID, entity.SnapshotFromTabList(stackedSessionID, stackedTabs)), | 1284 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, stackedSessionID, entity.SnapshotFromTabList(stackedSessionID, stackedTabs)), | ||
| 1289 | }, | 1285 | }, | ||
| 1290 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | 1286 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), |
| 1291 | - | runtimeConfigLoaded: true, | 1287 | + | mainWindow: mainWindow, |
| 1292 | - | mainWindow: mainWindow, | 1288 | + | widgetFactory: layout.NewGtkWidgetFactory(), |
| 1293 | - | widgetFactory: layout.NewGtkWidgetFactory(), | 1289 | + | contentCoord: &contentcoord.Coordinator{}, |
| 1294 | - | contentCoord: &contentcoord.Coordinator{}, | 1290 | + | wsCoord: coordinator.NewWorkspaceCoordinator(context.Background(), coordinator.WorkspaceCoordinatorConfig{ContentCoord: &contentcoord.Coordinator{}}), |
| 1295 | - | wsCoord: coordinator.NewWorkspaceCoordinator(context.Background(), coordinator.WorkspaceCoordinatorConfig{ContentCoord: &contentcoord.Coordinator{}}), | 1291 | + | tabs: entity.NewTabList(), |
| 1296 | - | tabs: entity.NewTabList(), | 1292 | + | browserWindows: map[string]*browserWindow{}, |
| 1297 | - | browserWindows: map[string]*browserWindow{}, | 1293 | + | workspaceViews: map[entity.TabID]*component.WorkspaceView{}, |
| 1298 | - | workspaceViews: map[entity.TabID]*component.WorkspaceView{}, | 1294 | + | windowForTab: map[entity.TabID]*browserWindow{}, |
| 1299 | - | windowForTab: map[entity.TabID]*browserWindow{}, | |||
| 1300 | } | 1295 | } | ||
| 1301 | 1296 | ||||
| 1302 | if err := app.restoreSession(context.Background(), stackedSessionID); err != nil { | 1297 | if err := app.restoreSession(context.Background(), stackedSessionID); err != nil { | ||
| @@ -1774,8 +1769,7 @@ func TestApp_MoveActivePaneToTabFromBrowserWindowAnchorsOwningWindow(t *testing. | |||||
| 1774 | tabs: tabs, | 1769 | tabs: tabs, | ||
| 1775 | movePaneToTabUC: usecase.NewMovePaneToTabUseCase(func() string { return "new-tab" }), | 1770 | movePaneToTabUC: usecase.NewMovePaneToTabUseCase(func() string { return "new-tab" }), | ||
| 1776 | deps: &Dependencies{}, | 1771 | deps: &Dependencies{}, | ||
| 1777 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | 1772 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), |
| 1778 | - | runtimeConfigLoaded: true, | |||
| 1779 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | 1773 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | ||
| 1780 | lastFocusedWindowID: second.id, | 1774 | lastFocusedWindowID: second.id, | ||
| 1781 | } | 1775 | } | ||
| @@ -1813,8 +1807,7 @@ func TestApp_MoveActivePaneToTabFromBrowserWindowActivatesTargetOwnerForCrossWin | |||||
| 1813 | tabs: tabs, | 1807 | tabs: tabs, | ||
| 1814 | movePaneToTabUC: usecase.NewMovePaneToTabUseCase(func() string { return "generated" }), | 1808 | movePaneToTabUC: usecase.NewMovePaneToTabUseCase(func() string { return "generated" }), | ||
| 1815 | deps: &Dependencies{}, | 1809 | deps: &Dependencies{}, | ||
| 1816 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | 1810 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), |
| 1817 | - | runtimeConfigLoaded: true, | |||
| 1818 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | 1811 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | ||
| 1819 | windowForTab: map[entity.TabID]*browserWindow{tab1.ID: first, tab2.ID: second, tab3.ID: second}, | 1812 | windowForTab: map[entity.TabID]*browserWindow{tab1.ID: first, tab2.ID: second, tab3.ID: second}, | ||
| 1820 | lastFocusedWindowID: first.id, | 1813 | lastFocusedWindowID: first.id, | ||
| @@ -1870,8 +1863,7 @@ func TestApp_MoveActivePaneToTabFromBrowserWindowCrossWindowDerivedMirror(t *tes | |||||
| 1870 | tabs: tabs, | 1863 | tabs: tabs, | ||
| 1871 | movePaneToTabUC: usecase.NewMovePaneToTabUseCase(func() string { return "generated" }), | 1864 | movePaneToTabUC: usecase.NewMovePaneToTabUseCase(func() string { return "generated" }), | ||
| 1872 | deps: &Dependencies{}, | 1865 | deps: &Dependencies{}, | ||
| 1873 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | 1866 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), |
| 1874 | - | runtimeConfigLoaded: true, | |||
| 1875 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | 1867 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | ||
| 1876 | windowForTab: map[entity.TabID]*browserWindow{tab1.ID: first, tab3.ID: second}, | 1868 | windowForTab: map[entity.TabID]*browserWindow{tab1.ID: first, tab3.ID: second}, | ||
| 1877 | lastFocusedWindowID: first.id, | 1869 | lastFocusedWindowID: first.id, | ||
| @@ -1921,12 +1913,11 @@ func TestApp_InitKeyboardHandlerDoesNotReattachExistingWindowInput(t *testing.T) | |||||
| 1921 | globalShortcutHandler: sentinelShortcutHandler, | 1913 | globalShortcutHandler: sentinelShortcutHandler, | ||
| 1922 | } | 1914 | } | ||
| 1923 | app := &App{ | 1915 | app := &App{ | ||
| 1924 | - | deps: &Dependencies{}, | 1916 | + | deps: &Dependencies{}, |
| 1925 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | 1917 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), |
| 1926 | - | runtimeConfigLoaded: true, | 1918 | + | mainWindow: mainWindow, |
| 1927 | - | mainWindow: mainWindow, | 1919 | + | browserWindows: map[string]*browserWindow{bw.id: bw}, |
| 1928 | - | browserWindows: map[string]*browserWindow{bw.id: bw}, | 1920 | + | kbDispatcher: dispatcher.NewKeyboardDispatcher(context.Background(), nil, nil, nil, nil, dispatcher.KeyboardActions{}, func(context.Context) entity.PaneID { return "" }), |
| 1929 | - | kbDispatcher: dispatcher.NewKeyboardDispatcher(context.Background(), nil, nil, nil, nil, dispatcher.KeyboardActions{}, func(context.Context) entity.PaneID { return "" }), | |||
| 1930 | } | 1921 | } | ||
| 1931 | 1922 | ||||
| 1932 | app.initKeyboardHandler(context.Background()) | 1923 | app.initKeyboardHandler(context.Background()) | ||
| @@ -1978,9 +1969,8 @@ func TestApp_UpdateBrowserWindowTabBarVisibilityAutoHidesSingleTabButPreservesAl | |||||
| 1978 | cfg := &config.Config{} | 1969 | cfg := &config.Config{} | ||
| 1979 | cfg.Workspace.HideTabBarWhenSingleTab = true | 1970 | cfg.Workspace.HideTabBarWhenSingleTab = true | ||
| 1980 | app := &App{ | 1971 | app := &App{ | ||
| 1981 | - | deps: &Dependencies{}, | 1972 | + | deps: &Dependencies{}, |
| 1982 | - | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | 1973 | + | runtimeConfig: runtimeConfigStateForTest(cfg), |
| 1983 | - | runtimeConfigLoaded: true, | |||
| 1984 | } | 1974 | } | ||
| 1985 | 1975 | ||||
| 1986 | tab1 := entity.NewTab(entity.TabID("tab-1"), entity.WorkspaceID("workspace-1"), entity.NewPane(entity.PaneID("pane-1"))) | 1976 | tab1 := entity.NewTab(entity.TabID("tab-1"), entity.WorkspaceID("workspace-1"), entity.NewPane(entity.PaneID("pane-1"))) | ||
| @@ -2051,9 +2041,8 @@ func TestApp_UpdateBrowserWindowTabBarVisibilityHonorsHideWhenSingleTabDisabled( | |||||
| 2051 | cfg := &config.Config{} | 2041 | cfg := &config.Config{} | ||
| 2052 | cfg.Workspace.HideTabBarWhenSingleTab = false | 2042 | cfg.Workspace.HideTabBarWhenSingleTab = false | ||
| 2053 | app := &App{ | 2043 | app := &App{ | ||
| 2054 | - | deps: &Dependencies{}, | 2044 | + | deps: &Dependencies{}, |
| 2055 | - | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | 2045 | + | runtimeConfig: runtimeConfigStateForTest(cfg), |
| 2056 | - | runtimeConfigLoaded: true, | |||
| 2057 | } | 2046 | } | ||
| 2058 | 2047 | ||||
| 2059 | app.updateBrowserWindowTabBarVisibility(bw) | 2048 | app.updateBrowserWindowTabBarVisibility(bw) | ||
| @@ -2111,9 +2100,8 @@ func TestApp_UpdateBrowserWindowTabBarVisibility_BottomInset(t *testing.T) { | |||||
| 2111 | cfg := &config.Config{} | 2100 | cfg := &config.Config{} | ||
| 2112 | cfg.Workspace.HideTabBarWhenSingleTab = true | 2101 | cfg.Workspace.HideTabBarWhenSingleTab = true | ||
| 2113 | app := &App{ | 2102 | app := &App{ | ||
| 2114 | - | deps: &Dependencies{}, | 2103 | + | deps: &Dependencies{}, |
| 2115 | - | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | 2104 | + | runtimeConfig: runtimeConfigStateForTest(cfg), |
| 2116 | - | runtimeConfigLoaded: true, | |||
| 2117 | } | 2105 | } | ||
| 2118 | 2106 | ||||
| 2119 | tab1 := entity.NewTab(entity.TabID("tab-1"), entity.WorkspaceID("workspace-1"), entity.NewPane(entity.PaneID("pane-1"))) | 2107 | tab1 := entity.NewTab(entity.TabID("tab-1"), entity.WorkspaceID("workspace-1"), entity.NewPane(entity.PaneID("pane-1"))) | ||
| @@ -2171,8 +2159,7 @@ func TestApp_HandlePaneFullscreenChanged_ClearsBottomInset(t *testing.T) { | |||||
| 2171 | app := &App{ | 2159 | app := &App{ | ||
| 2172 | tabs: tabs, | 2160 | tabs: tabs, | ||
| 2173 | deps: &Dependencies{}, | 2161 | deps: &Dependencies{}, | ||
| 2174 | - | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | 2162 | + | runtimeConfig: runtimeConfigStateForTest(cfg), |
| 2175 | - | runtimeConfigLoaded: true, | |||
| 2176 | browserWindows: map[string]*browserWindow{bw.id: bw}, | 2163 | browserWindows: map[string]*browserWindow{bw.id: bw}, | ||
| 2177 | lastFocusedWindowID: bw.id, | 2164 | lastFocusedWindowID: bw.id, | ||
| 2178 | } | 2165 | } | ||
| @@ -2291,8 +2278,7 @@ func TestApp_CreatePopupTabUsesParentPaneOwnerWhenFocusIsStale(t *testing.T) { | |||||
| 2291 | second := &browserWindow{id: "window-2", tabs: secondTabs, mainWindow: &window.MainWindow{}} | 2278 | second := &browserWindow{id: "window-2", tabs: secondTabs, mainWindow: &window.MainWindow{}} | ||
| 2292 | app := &App{ | 2279 | app := &App{ | ||
| 2293 | deps: &Dependencies{}, | 2280 | deps: &Dependencies{}, | ||
| 2294 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | 2281 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), |
| 2295 | - | runtimeConfigLoaded: true, | |||
| 2296 | tabs: globalTabs, | 2282 | tabs: globalTabs, | ||
| 2297 | tabsUC: usecase.NewManageTabsUseCase(func() string { return "popup-tab" }), | 2283 | tabsUC: usecase.NewManageTabsUseCase(func() string { return "popup-tab" }), | ||
| 2298 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | 2284 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | ||
| @@ -2353,8 +2339,7 @@ func TestApp_TabCoordinatorCallbacksUseTargetWindowWhenFocusIsStale(t *testing.T | |||||
| 2353 | second := &browserWindow{id: "window-2", tabs: secondTabs, mainWindow: &window.MainWindow{}} | 2339 | second := &browserWindow{id: "window-2", tabs: secondTabs, mainWindow: &window.MainWindow{}} | ||
| 2354 | app := &App{ | 2340 | app := &App{ | ||
| 2355 | deps: &Dependencies{}, | 2341 | deps: &Dependencies{}, | ||
| 2356 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | 2342 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), |
| 2357 | - | runtimeConfigLoaded: true, | |||
| 2358 | tabs: entity.NewTabList(), | 2343 | tabs: entity.NewTabList(), | ||
| 2359 | tabsUC: usecase.NewManageTabsUseCase(func() string { return "created-tab" }), | 2344 | tabsUC: usecase.NewManageTabsUseCase(func() string { return "created-tab" }), | ||
| 2360 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | 2345 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | ||
| @@ -2987,8 +2972,7 @@ func TestApp_DispatchBrowserWindowActionSwitchTabIndexUsesSourceWindow(t *testin | |||||
| 2987 | } | 2972 | } | ||
| 2988 | app := &App{ | 2973 | app := &App{ | ||
| 2989 | deps: &Dependencies{}, | 2974 | deps: &Dependencies{}, | ||
| 2990 | - | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | 2975 | + | runtimeConfig: runtimeConfigStateForTest(cfg), |
| 2991 | - | runtimeConfigLoaded: true, | |||
| 2992 | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | 2976 | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | ||
| 2993 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | 2977 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | ||
| 2994 | windowForTab: map[entity.TabID]*browserWindow{firstTab.ID: first, secondTab1.ID: second, secondTab2.ID: second}, | 2978 | windowForTab: map[entity.TabID]*browserWindow{firstTab.ID: first, secondTab1.ID: second, secondTab2.ID: second}, | ||
| @@ -3031,8 +3015,7 @@ func TestApp_DispatchBrowserWindowActionSwitchTabIndexCreatesOnlyOneMissingTab(t | |||||
| 3031 | } | 3015 | } | ||
| 3032 | app := &App{ | 3016 | app := &App{ | ||
| 3033 | deps: &Dependencies{}, | 3017 | deps: &Dependencies{}, | ||
| 3034 | - | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | 3018 | + | runtimeConfig: runtimeConfigStateForTest(cfg), |
| 3035 | - | runtimeConfigLoaded: true, | |||
| 3036 | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | 3019 | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | ||
| 3037 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | 3020 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | ||
| 3038 | windowForTab: map[entity.TabID]*browserWindow{firstTab.ID: first, secondTab.ID: second}, | 3021 | windowForTab: map[entity.TabID]*browserWindow{firstTab.ID: first, secondTab.ID: second}, | ||
| @@ -3077,12 +3060,11 @@ func TestApp_SwitchBrowserWindowTabIndexNegativeIndexIsIgnored(t *testing.T) { | |||||
| 3077 | 3060 | ||||
| 3078 | cfg := &config.Config{Workspace: config.WorkspaceConfig{NewPaneURL: "about:blank"}} | 3061 | cfg := &config.Config{Workspace: config.WorkspaceConfig{NewPaneURL: "about:blank"}} | ||
| 3079 | app := &App{ | 3062 | app := &App{ | ||
| 3080 | - | deps: &Dependencies{}, | 3063 | + | deps: &Dependencies{}, |
| 3081 | - | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | 3064 | + | runtimeConfig: runtimeConfigStateForTest(cfg), |
| 3082 | - | runtimeConfigLoaded: true, | 3065 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), |
| 3083 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | 3066 | + | browserWindows: map[string]*browserWindow{second.id: second}, |
| 3084 | - | browserWindows: map[string]*browserWindow{second.id: second}, | 3067 | + | windowForTab: map[entity.TabID]*browserWindow{secondTab.ID: second}, |
| 3085 | - | windowForTab: map[entity.TabID]*browserWindow{secondTab.ID: second}, | |||
| 3086 | } | 3068 | } | ||
| 3087 | app.initTabCoordinator(ctx) | 3069 | app.initTabCoordinator(ctx) | ||
| 3088 | 3070 | ||||
| @@ -3106,8 +3088,7 @@ func TestApp_DispatchBrowserWindowActionSwitchTabIndexNegativeDoesNotMutateWindo | |||||
| 3106 | cfg := &config.Config{Workspace: config.WorkspaceConfig{NewPaneURL: "about:blank"}} | 3088 | cfg := &config.Config{Workspace: config.WorkspaceConfig{NewPaneURL: "about:blank"}} | ||
| 3107 | app := &App{ | 3089 | app := &App{ | ||
| 3108 | deps: &Dependencies{}, | 3090 | deps: &Dependencies{}, | ||
| 3109 | - | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | 3091 | + | runtimeConfig: runtimeConfigStateForTest(cfg), |
| 3110 | - | runtimeConfigLoaded: true, | |||
| 3111 | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | 3092 | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | ||
| 3112 | browserWindows: map[string]*browserWindow{second.id: second}, | 3093 | browserWindows: map[string]*browserWindow{second.id: second}, | ||
| 3113 | lastFocusedWindowID: "window-1", | 3094 | lastFocusedWindowID: "window-1", | ||
| @@ -3136,12 +3117,11 @@ func TestApp_DispatchBrowserWindowActionSwitchTabIndexMissingURLDoesNotCreateTab | |||||
| 3136 | second := &browserWindow{id: "window-2", tabs: secondTabs} | 3117 | second := &browserWindow{id: "window-2", tabs: secondTabs} | ||
| 3137 | 3118 | ||||
| 3138 | app := &App{ | 3119 | app := &App{ | ||
| 3139 | - | deps: &Dependencies{}, | 3120 | + | deps: &Dependencies{}, |
| 3140 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | 3121 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), |
| 3141 | - | runtimeConfigLoaded: true, | 3122 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), |
| 3142 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | 3123 | + | browserWindows: map[string]*browserWindow{second.id: second}, |
| 3143 | - | browserWindows: map[string]*browserWindow{second.id: second}, | 3124 | + | windowForTab: map[entity.TabID]*browserWindow{secondTab.ID: second}, |
| 3144 | - | windowForTab: map[entity.TabID]*browserWindow{secondTab.ID: second}, | |||
| 3145 | } | 3125 | } | ||
| 3146 | app.initTabCoordinator(ctx) | 3126 | app.initTabCoordinator(ctx) | ||
| 3147 | 3127 | ||||
| @@ -3164,8 +3144,7 @@ func TestApp_SwitchBrowserWindowTabIndexMissingURLDoesNotMutateWindowState(t *te | |||||
| 3164 | 3144 | ||||
| 3165 | app := &App{ | 3145 | app := &App{ | ||
| 3166 | deps: &Dependencies{}, | 3146 | deps: &Dependencies{}, | ||
| 3167 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | 3147 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), |
| 3168 | - | runtimeConfigLoaded: true, | |||
| 3169 | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | 3148 | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | ||
| 3170 | browserWindows: map[string]*browserWindow{second.id: second}, | 3149 | browserWindows: map[string]*browserWindow{second.id: second}, | ||
| 3171 | lastFocusedWindowID: "window-1", | 3150 | lastFocusedWindowID: "window-1", | ||
| @@ -3208,8 +3187,7 @@ func TestApp_WorkspaceOmniboxNavigateUsesOwnerWindow(t *testing.T) { | |||||
| 3208 | 3187 | ||||
| 3209 | app := &App{ | 3188 | app := &App{ | ||
| 3210 | deps: &Dependencies{}, | 3189 | deps: &Dependencies{}, | ||
| 3211 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | 3190 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), |
| 3212 | - | runtimeConfigLoaded: true, | |||
| 3213 | widgetFactory: layout.NewGtkWidgetFactory(), | 3191 | widgetFactory: layout.NewGtkWidgetFactory(), | ||
| 3214 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | 3192 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | ||
| 3215 | windowForTab: map[entity.TabID]*browserWindow{tab1.ID: first, tab2.ID: second}, | 3193 | windowForTab: map[entity.TabID]*browserWindow{tab1.ID: first, tab2.ID: second}, | ||
| @@ -3306,9 +3284,8 @@ func TestApp_RestoreSessionDoesNotLeakStaleWindowsIntoTabMerge(t *testing.T) { | |||||
| 3306 | deps: &Dependencies{ | 3284 | deps: &Dependencies{ | ||
| 3307 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | 3285 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | ||
| 3308 | }, | 3286 | }, | ||
| 3309 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | 3287 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), |
| 3310 | - | runtimeConfigLoaded: true, | 3288 | + | mainWindow: mainWindow, |
| 3311 | - | mainWindow: mainWindow, | |||
| 3312 | browserWindows: map[string]*browserWindow{ | 3289 | browserWindows: map[string]*browserWindow{ | ||
| 3313 | staleBW1.id: staleBW1, | 3290 | staleBW1.id: staleBW1, | ||
| 3314 | staleBW2.id: staleBW2, | 3291 | staleBW2.id: staleBW2, | ||
| @@ -3387,14 +3364,13 @@ func TestApp_RestoreSessionHonorsActiveWindowIndex(t *testing.T) { | |||||
| 3387 | deps: &Dependencies{ | 3364 | deps: &Dependencies{ | ||
| 3388 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | 3365 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | ||
| 3389 | }, | 3366 | }, | ||
| 3390 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | 3367 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), |
| 3391 | - | runtimeConfigLoaded: true, | 3368 | + | mainWindow: mainWindow, |
| 3392 | - | mainWindow: mainWindow, | 3369 | + | widgetFactory: layout.NewGtkWidgetFactory(), |
| 3393 | - | widgetFactory: layout.NewGtkWidgetFactory(), | 3370 | + | browserWindows: map[string]*browserWindow{firstBW.id: firstBW}, |
| 3394 | - | browserWindows: map[string]*browserWindow{firstBW.id: firstBW}, | 3371 | + | tabs: entity.NewTabList(), |
| 3395 | - | tabs: entity.NewTabList(), | 3372 | + | windowForTab: map[entity.TabID]*browserWindow{}, |
| 3396 | - | windowForTab: map[entity.TabID]*browserWindow{}, | 3373 | + | workspaceViews: map[entity.TabID]*component.WorkspaceView{}, |
| 3397 | - | workspaceViews: map[entity.TabID]*component.WorkspaceView{}, | |||
| 3398 | browserWindowFactory: func(ctx context.Context, url string) (*browserWindow, error) { | 3374 | browserWindowFactory: func(ctx context.Context, url string) (*browserWindow, error) { | ||
| 3399 | runtimeW1 = &browserWindow{id: "runtime-w1", mainWindow: mainWindow} | 3375 | runtimeW1 = &browserWindow{id: "runtime-w1", mainWindow: mainWindow} | ||
| 3400 | return runtimeW1, nil | 3376 | return runtimeW1, nil | ||
| @@ -3455,12 +3431,11 @@ func TestApp_RestoreSessionFailsOnAdditionalWindowCreationError(t *testing.T) { | |||||
| 3455 | deps: &Dependencies{ | 3431 | deps: &Dependencies{ | ||
| 3456 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | 3432 | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | ||
| 3457 | }, | 3433 | }, | ||
| 3458 | - | runtimeConfig: runtimeConfigSnapshotForTest(&config.Config{}), | 3434 | + | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), |
| 3459 | - | runtimeConfigLoaded: true, | 3435 | + | mainWindow: mainWindow, |
| 3460 | - | mainWindow: mainWindow, | 3436 | + | browserWindows: map[string]*browserWindow{firstBW.id: firstBW}, |
| 3461 | - | browserWindows: map[string]*browserWindow{firstBW.id: firstBW}, | 3437 | + | tabs: entity.NewTabList(), |
| 3462 | - | tabs: entity.NewTabList(), | 3438 | + | windowForTab: map[entity.TabID]*browserWindow{}, |
| 3463 | - | windowForTab: map[entity.TabID]*browserWindow{}, | |||
| 3464 | browserWindowFactory: func(ctx context.Context, url string) (*browserWindow, error) { | 3439 | browserWindowFactory: func(ctx context.Context, url string) (*browserWindow, error) { | ||
| 3465 | return nil, factoryErr | 3440 | return nil, factoryErr | ||
| 3466 | }, | 3441 | }, | ||
M internal/ui/browser_window_test.go +27 -30
| @@ -137,16 +137,15 @@ func TestTabCoordinator_CloseReleasesClosedTabWorkspaceWebViews(t *testing.T) { | |||
| 137 | 137 | ||
| 138 | 138 | bw := &browserWindow{id: "window-1", tabs: tabs} | |
| 139 | 139 | app := &App{ | |
| 140 | - | deps: &Dependencies{}, | |
| 141 | - | runtimeConfig: runtimeConfigSnapshotForTest(config.DefaultConfig()), | |
| 142 | - | runtimeConfigLoaded: true, | |
| 143 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 144 | - | contentCoord: contentCoord, | |
| 145 | - | browserWindows: map[string]*browserWindow{bw.id: bw}, | |
| 146 | - | tabs: entity.NewTabList(), | |
| 147 | - | workspaceViews: map[entity.TabID]*component.WorkspaceView{closedTab.ID: {}, survivingTab.ID: {}}, | |
| 148 | - | windowForTab: map[entity.TabID]*browserWindow{closedTab.ID: bw, survivingTab.ID: bw}, | |
| 149 | - | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{}, | |
| 140 | + | deps: &Dependencies{}, | |
| 141 | + | runtimeConfig: runtimeConfigStateForTest(config.DefaultConfig()), | |
| 142 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 143 | + | contentCoord: contentCoord, | |
| 144 | + | browserWindows: map[string]*browserWindow{bw.id: bw}, | |
| 145 | + | tabs: entity.NewTabList(), | |
| 146 | + | workspaceViews: map[entity.TabID]*component.WorkspaceView{closedTab.ID: {}, survivingTab.ID: {}}, | |
| 147 | + | windowForTab: map[entity.TabID]*browserWindow{closedTab.ID: bw, survivingTab.ID: bw}, | |
| 148 | + | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{}, | |
| 150 | 149 | } | |
| 151 | 150 | app.tabs.Add(closedTab) | |
| 152 | 151 | app.tabs.Add(survivingTab) | |
| @@ -182,16 +181,15 @@ func TestTabCoordinator_SwitchDoesNotReleaseWorkspaceWebViews(t *testing.T) { | |||
| 182 | 181 | ||
| 183 | 182 | bw := &browserWindow{id: "window-1", tabs: tabs} | |
| 184 | 183 | app := &App{ | |
| 185 | - | deps: &Dependencies{}, | |
| 186 | - | runtimeConfig: runtimeConfigSnapshotForTest(config.DefaultConfig()), | |
| 187 | - | runtimeConfigLoaded: true, | |
| 188 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 189 | - | contentCoord: contentCoord, | |
| 190 | - | browserWindows: map[string]*browserWindow{bw.id: bw}, | |
| 191 | - | tabs: entity.NewTabList(), | |
| 192 | - | workspaceViews: map[entity.TabID]*component.WorkspaceView{firstTab.ID: {}, secondTab.ID: {}}, | |
| 193 | - | windowForTab: map[entity.TabID]*browserWindow{firstTab.ID: bw, secondTab.ID: bw}, | |
| 194 | - | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{}, | |
| 184 | + | deps: &Dependencies{}, | |
| 185 | + | runtimeConfig: runtimeConfigStateForTest(config.DefaultConfig()), | |
| 186 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 187 | + | contentCoord: contentCoord, | |
| 188 | + | browserWindows: map[string]*browserWindow{bw.id: bw}, | |
| 189 | + | tabs: entity.NewTabList(), | |
| 190 | + | workspaceViews: map[entity.TabID]*component.WorkspaceView{firstTab.ID: {}, secondTab.ID: {}}, | |
| 191 | + | windowForTab: map[entity.TabID]*browserWindow{firstTab.ID: bw, secondTab.ID: bw}, | |
| 192 | + | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{}, | |
| 195 | 193 | } | |
| 196 | 194 | app.tabs.Add(firstTab) | |
| 197 | 195 | app.tabs.Add(secondTab) | |
| @@ -218,16 +216,15 @@ func TestTabCoordinator_CloseLastTabReleasesWorkspaceBeforeWindowRemoval(t *test | |||
| 218 | 216 | ||
| 219 | 217 | bw := &browserWindow{id: "window-1", tabs: tabs} | |
| 220 | 218 | app := &App{ | |
| 221 | - | deps: &Dependencies{}, | |
| 222 | - | runtimeConfig: runtimeConfigSnapshotForTest(config.DefaultConfig()), | |
| 223 | - | runtimeConfigLoaded: true, | |
| 224 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 225 | - | contentCoord: contentCoord, | |
| 226 | - | browserWindows: map[string]*browserWindow{bw.id: bw}, | |
| 227 | - | tabs: entity.NewTabList(), | |
| 228 | - | workspaceViews: map[entity.TabID]*component.WorkspaceView{closedTab.ID: {}}, | |
| 229 | - | windowForTab: map[entity.TabID]*browserWindow{closedTab.ID: bw}, | |
| 230 | - | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{}, | |
| 219 | + | deps: &Dependencies{}, | |
| 220 | + | runtimeConfig: runtimeConfigStateForTest(config.DefaultConfig()), | |
| 221 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 222 | + | contentCoord: contentCoord, | |
| 223 | + | browserWindows: map[string]*browserWindow{bw.id: bw}, | |
| 224 | + | tabs: entity.NewTabList(), | |
| 225 | + | workspaceViews: map[entity.TabID]*component.WorkspaceView{closedTab.ID: {}}, | |
| 226 | + | windowForTab: map[entity.TabID]*browserWindow{closedTab.ID: bw}, | |
| 227 | + | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{}, | |
| 231 | 228 | } | |
| 232 | 229 | app.tabs.Add(closedTab) | |
| 233 | 230 | app.initTabCoordinator(ctx) | |
| @@ -137,16 +137,15 @@ func TestTabCoordinator_CloseReleasesClosedTabWorkspaceWebViews(t *testing.T) { | |||||
| 137 | 137 | ||||
| 138 | bw := &browserWindow{id: "window-1", tabs: tabs} | 138 | bw := &browserWindow{id: "window-1", tabs: tabs} | ||
| 139 | app := &App{ | 139 | app := &App{ | ||
| 140 | - | deps: &Dependencies{}, | 140 | + | deps: &Dependencies{}, |
| 141 | - | runtimeConfig: runtimeConfigSnapshotForTest(config.DefaultConfig()), | 141 | + | runtimeConfig: runtimeConfigStateForTest(config.DefaultConfig()), |
| 142 | - | runtimeConfigLoaded: true, | 142 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), |
| 143 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | 143 | + | contentCoord: contentCoord, |
| 144 | - | contentCoord: contentCoord, | 144 | + | browserWindows: map[string]*browserWindow{bw.id: bw}, |
| 145 | - | browserWindows: map[string]*browserWindow{bw.id: bw}, | 145 | + | tabs: entity.NewTabList(), |
| 146 | - | tabs: entity.NewTabList(), | 146 | + | workspaceViews: map[entity.TabID]*component.WorkspaceView{closedTab.ID: {}, survivingTab.ID: {}}, |
| 147 | - | workspaceViews: map[entity.TabID]*component.WorkspaceView{closedTab.ID: {}, survivingTab.ID: {}}, | 147 | + | windowForTab: map[entity.TabID]*browserWindow{closedTab.ID: bw, survivingTab.ID: bw}, |
| 148 | - | windowForTab: map[entity.TabID]*browserWindow{closedTab.ID: bw, survivingTab.ID: bw}, | 148 | + | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{}, |
| 149 | - | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{}, | |||
| 150 | } | 149 | } | ||
| 151 | app.tabs.Add(closedTab) | 150 | app.tabs.Add(closedTab) | ||
| 152 | app.tabs.Add(survivingTab) | 151 | app.tabs.Add(survivingTab) | ||
| @@ -182,16 +181,15 @@ func TestTabCoordinator_SwitchDoesNotReleaseWorkspaceWebViews(t *testing.T) { | |||||
| 182 | 181 | ||||
| 183 | bw := &browserWindow{id: "window-1", tabs: tabs} | 182 | bw := &browserWindow{id: "window-1", tabs: tabs} | ||
| 184 | app := &App{ | 183 | app := &App{ | ||
| 185 | - | deps: &Dependencies{}, | 184 | + | deps: &Dependencies{}, |
| 186 | - | runtimeConfig: runtimeConfigSnapshotForTest(config.DefaultConfig()), | 185 | + | runtimeConfig: runtimeConfigStateForTest(config.DefaultConfig()), |
| 187 | - | runtimeConfigLoaded: true, | 186 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), |
| 188 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | 187 | + | contentCoord: contentCoord, |
| 189 | - | contentCoord: contentCoord, | 188 | + | browserWindows: map[string]*browserWindow{bw.id: bw}, |
| 190 | - | browserWindows: map[string]*browserWindow{bw.id: bw}, | 189 | + | tabs: entity.NewTabList(), |
| 191 | - | tabs: entity.NewTabList(), | 190 | + | workspaceViews: map[entity.TabID]*component.WorkspaceView{firstTab.ID: {}, secondTab.ID: {}}, |
| 192 | - | workspaceViews: map[entity.TabID]*component.WorkspaceView{firstTab.ID: {}, secondTab.ID: {}}, | 191 | + | windowForTab: map[entity.TabID]*browserWindow{firstTab.ID: bw, secondTab.ID: bw}, |
| 193 | - | windowForTab: map[entity.TabID]*browserWindow{firstTab.ID: bw, secondTab.ID: bw}, | 192 | + | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{}, |
| 194 | - | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{}, | |||
| 195 | } | 193 | } | ||
| 196 | app.tabs.Add(firstTab) | 194 | app.tabs.Add(firstTab) | ||
| 197 | app.tabs.Add(secondTab) | 195 | app.tabs.Add(secondTab) | ||
| @@ -218,16 +216,15 @@ func TestTabCoordinator_CloseLastTabReleasesWorkspaceBeforeWindowRemoval(t *test | |||||
| 218 | 216 | ||||
| 219 | bw := &browserWindow{id: "window-1", tabs: tabs} | 217 | bw := &browserWindow{id: "window-1", tabs: tabs} | ||
| 220 | app := &App{ | 218 | app := &App{ | ||
| 221 | - | deps: &Dependencies{}, | 219 | + | deps: &Dependencies{}, |
| 222 | - | runtimeConfig: runtimeConfigSnapshotForTest(config.DefaultConfig()), | 220 | + | runtimeConfig: runtimeConfigStateForTest(config.DefaultConfig()), |
| 223 | - | runtimeConfigLoaded: true, | 221 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), |
| 224 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | 222 | + | contentCoord: contentCoord, |
| 225 | - | contentCoord: contentCoord, | 223 | + | browserWindows: map[string]*browserWindow{bw.id: bw}, |
| 226 | - | browserWindows: map[string]*browserWindow{bw.id: bw}, | 224 | + | tabs: entity.NewTabList(), |
| 227 | - | tabs: entity.NewTabList(), | 225 | + | workspaceViews: map[entity.TabID]*component.WorkspaceView{closedTab.ID: {}}, |
| 228 | - | workspaceViews: map[entity.TabID]*component.WorkspaceView{closedTab.ID: {}}, | 226 | + | windowForTab: map[entity.TabID]*browserWindow{closedTab.ID: bw}, |
| 229 | - | windowForTab: map[entity.TabID]*browserWindow{closedTab.ID: bw}, | 227 | + | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{}, |
| 230 | - | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{}, | |||
| 231 | } | 228 | } | ||
| 232 | app.tabs.Add(closedTab) | 229 | app.tabs.Add(closedTab) | ||
| 233 | app.initTabCoordinator(ctx) | 230 | app.initTabCoordinator(ctx) | ||
M internal/ui/coordinator/content/popup.go +4 -0
| @@ -151,6 +151,10 @@ func (c *Coordinator) SetPopupConfig( | |||
| 151 | 151 | c.ensurePopupManager().setConfig(factory, popupConfig, generateID) | |
| 152 | 152 | } | |
| 153 | 153 | ||
| 154 | + | func (c *Coordinator) UpdatePopupConfig(popupConfig entity.BrowsingContextConfig) { | |
| 155 | + | c.ensurePopupManager().updatePopupConfig(popupConfig) | |
| 156 | + | } | |
| 157 | + | ||
| 154 | 158 | func (c *Coordinator) SetPopupWindowIDResolver(fn func(entity.PaneID) (string, bool)) { | |
| 155 | 159 | c.ensurePopupManager().setWindowIDResolver(fn) | |
| 156 | 160 | } | |
| @@ -151,6 +151,10 @@ func (c *Coordinator) SetPopupConfig( | |||||
| 151 | c.ensurePopupManager().setConfig(factory, popupConfig, generateID) | 151 | c.ensurePopupManager().setConfig(factory, popupConfig, generateID) | ||
| 152 | } | 152 | } | ||
| 153 | 153 | ||||
| 154 | + | func (c *Coordinator) UpdatePopupConfig(popupConfig entity.BrowsingContextConfig) { | |||
| 155 | + | c.ensurePopupManager().updatePopupConfig(popupConfig) | |||
| 156 | + | } | |||
| 157 | + | ||||
| 154 | func (c *Coordinator) SetPopupWindowIDResolver(fn func(entity.PaneID) (string, bool)) { | 158 | func (c *Coordinator) SetPopupWindowIDResolver(fn func(entity.PaneID) (string, bool)) { | ||
| 155 | c.ensurePopupManager().setWindowIDResolver(fn) | 159 | c.ensurePopupManager().setWindowIDResolver(fn) | ||
| 156 | } | 160 | } | ||
M internal/ui/coordinator/content/popup_manager.go +46 -12
| @@ -18,7 +18,8 @@ import ( | |||
| 18 | 18 | // leak into the application/usecase or domain layers. | |
| 19 | 19 | type popupManager struct { | |
| 20 | 20 | factory port.WebViewFactory | |
| 21 | - | popupConfig *entity.BrowsingContextConfig | |
| 21 | + | popupConfig entity.BrowsingContextConfig | |
| 22 | + | hasPopupConfig bool | |
| 22 | 23 | onInsertPopup func(ctx context.Context, input InsertPopupInput) error | |
| 23 | 24 | onOpenNativePopup func(ctx context.Context, input NativePopupInput) error | |
| 24 | 25 | onClosePane func(ctx context.Context, paneID entity.PaneID) error | |
| @@ -103,10 +104,38 @@ func (pm *popupManager) setConfig( | |||
| 103 | 104 | } | |
| 104 | 105 | pm.ensureInitialized() | |
| 105 | 106 | pm.factory = factory | |
| 106 | - | pm.popupConfig = popupConfig | |
| 107 | + | pm.setPopupConfig(popupConfig) | |
| 107 | 108 | pm.generatePaneID = generateID | |
| 108 | 109 | } | |
| 109 | 110 | ||
| 111 | + | func (pm *popupManager) setPopupConfig(popupConfig *entity.BrowsingContextConfig) { | |
| 112 | + | if pm == nil { | |
| 113 | + | return | |
| 114 | + | } | |
| 115 | + | if popupConfig == nil { | |
| 116 | + | pm.popupConfig = entity.BrowsingContextConfig{} | |
| 117 | + | pm.hasPopupConfig = false | |
| 118 | + | return | |
| 119 | + | } | |
| 120 | + | pm.popupConfig = *popupConfig | |
| 121 | + | pm.hasPopupConfig = true | |
| 122 | + | } | |
| 123 | + | ||
| 124 | + | func (pm *popupManager) updatePopupConfig(popupConfig entity.BrowsingContextConfig) { | |
| 125 | + | if pm == nil { | |
| 126 | + | return | |
| 127 | + | } | |
| 128 | + | pm.popupConfig = popupConfig | |
| 129 | + | pm.hasPopupConfig = true | |
| 130 | + | } | |
| 131 | + | ||
| 132 | + | func (pm *popupManager) currentPopupConfig() *entity.BrowsingContextConfig { | |
| 133 | + | if pm == nil || !pm.hasPopupConfig { | |
| 134 | + | return nil | |
| 135 | + | } | |
| 136 | + | return &pm.popupConfig | |
| 137 | + | } | |
| 138 | + | ||
| 110 | 139 | func (pm *popupManager) setWindowIDResolver(fn func(entity.PaneID) (string, bool)) { | |
| 111 | 140 | if pm == nil { | |
| 112 | 141 | return | |
| @@ -409,7 +438,8 @@ func (pm *popupManager) popupParentURIAtOpen( | |||
| 409 | 438 | hooks popupCoordinatorHooks, | |
| 410 | 439 | targetURI string, | |
| 411 | 440 | ) string { | |
| 412 | - | if pm.popupConfig == nil || !pm.popupConfig.OAuthAutoClose || !IsOAuthURL(targetURI) { | |
| 441 | + | cfg := pm.currentPopupConfig() | |
| 442 | + | if cfg == nil || !cfg.OAuthAutoClose || !IsOAuthURL(targetURI) { | |
| 413 | 443 | return "" | |
| 414 | 444 | } | |
| 415 | 445 | parentURIAtOpen := parentWV.URI() | |
| @@ -439,6 +469,7 @@ func (pm *popupManager) openNativePopup( | |||
| 439 | 469 | log.Error().Err(err).Msg("failed to create webview for native popup") | |
| 440 | 470 | return nil | |
| 441 | 471 | } | |
| 472 | + | cfg := pm.currentPopupConfig() | |
| 442 | 473 | pm.setBrowsingContextDecision(popupWV, decision) | |
| 443 | 474 | if err := pm.onOpenNativePopup(ctx, NativePopupInput{ | |
| 444 | 475 | ParentPaneID: parentPaneID, | |
| @@ -447,7 +478,7 @@ func (pm *popupManager) openNativePopup( | |||
| 447 | 478 | PopupWebView: popupWV, | |
| 448 | 479 | TargetURI: req.TargetURI, | |
| 449 | 480 | Request: req, | |
| 450 | - | ObserveOAuthAutoClose: pm.popupConfig != nil && pm.popupConfig.OAuthAutoClose && IsOAuthURL(req.TargetURI), | |
| 481 | + | ObserveOAuthAutoClose: cfg != nil && cfg.OAuthAutoClose && IsOAuthURL(req.TargetURI), | |
| 451 | 482 | }); err != nil { | |
| 452 | 483 | log.Error().Err(err).Msg("failed to open native popup host") | |
| 453 | 484 | popupWV.Destroy() | |
| @@ -476,7 +507,8 @@ func (pm *popupManager) handlePopupCreate( | |||
| 476 | 507 | Bool("no_javascript_access", req.NoJavaScriptAccess). | |
| 477 | 508 | Msg("popup create request") | |
| 478 | 509 | ||
| 479 | - | if pm.popupConfig != nil && !pm.popupConfig.OpenInNewPane { | |
| 510 | + | cfg := pm.currentPopupConfig() | |
| 511 | + | if cfg != nil && !cfg.OpenInNewPane { | |
| 480 | 512 | log.Debug().Msg("popups disabled by config, blocking") | |
| 481 | 513 | return nil | |
| 482 | 514 | } | |
| @@ -534,10 +566,10 @@ func (pm *popupManager) handlePopupCreate( | |||
| 534 | 566 | ||
| 535 | 567 | popupType := DetectPopupType(req.FrameName) | |
| 536 | 568 | popupID := popupWV.ID() | |
| 537 | - | behavior := GetBehavior(popupType, pm.popupConfig) | |
| 569 | + | behavior := GetBehavior(popupType, cfg) | |
| 538 | 570 | placement := "right" | |
| 539 | - | if pm.popupConfig != nil { | |
| 540 | - | placement = pm.popupConfig.Placement | |
| 571 | + | if cfg != nil { | |
| 572 | + | placement = cfg.Placement | |
| 541 | 573 | } | |
| 542 | 574 | paneID, popupPane := pm.createPopupPane(popupID, parentPaneID, req.TargetURI) | |
| 543 | 575 | ||
| @@ -562,8 +594,9 @@ func (pm *popupManager) finishPopupCreate( | |||
| 562 | 594 | create popupCreateContext, | |
| 563 | 595 | ) port.WebView { | |
| 564 | 596 | log := logging.FromContext(ctx) | |
| 565 | - | hasConfig := pm.popupConfig != nil | |
| 566 | - | oauthEnabled := hasConfig && pm.popupConfig.OAuthAutoClose | |
| 597 | + | cfg := pm.currentPopupConfig() | |
| 598 | + | hasConfig := cfg != nil | |
| 599 | + | oauthEnabled := hasConfig && cfg.OAuthAutoClose | |
| 567 | 600 | isOAuth := IsOAuthURL(create.Request.TargetURI) | |
| 568 | 601 | ||
| 569 | 602 | log.Debug(). | |
| @@ -792,7 +825,8 @@ func (pm *popupManager) handleLinkMiddleClick( | |||
| 792 | 825 | Str("uri", logging.TruncateURL(uri, logURLMaxLen)). | |
| 793 | 826 | Msg("middle-click/ctrl+click on link") | |
| 794 | 827 | ||
| 795 | - | if pm.popupConfig != nil && !pm.popupConfig.OpenInNewPane { | |
| 828 | + | cfg := pm.currentPopupConfig() | |
| 829 | + | if cfg != nil && !cfg.OpenInNewPane { | |
| 796 | 830 | log.Debug().Msg("popups disabled by config, ignoring middle-click") | |
| 797 | 831 | return false | |
| 798 | 832 | } | |
| @@ -845,7 +879,7 @@ func (pm *popupManager) handleLinkMiddleClick( | |||
| 845 | 879 | hooks.setupWebViewCallbacks(ctx, paneID, newWV) | |
| 846 | 880 | } | |
| 847 | 881 | ||
| 848 | - | behavior, placement := popupTabInsertionConfig(pm.popupConfig) | |
| 882 | + | behavior, placement := popupTabInsertionConfig(pm.currentPopupConfig()) | |
| 849 | 883 | ||
| 850 | 884 | if pm.onInsertPopup != nil { | |
| 851 | 885 | popupInput := InsertPopupInput{ | |
| @@ -18,7 +18,8 @@ import ( | |||||
| 18 | // leak into the application/usecase or domain layers. | 18 | // leak into the application/usecase or domain layers. | ||
| 19 | type popupManager struct { | 19 | type popupManager struct { | ||
| 20 | factory port.WebViewFactory | 20 | factory port.WebViewFactory | ||
| 21 | - | popupConfig *entity.BrowsingContextConfig | 21 | + | popupConfig entity.BrowsingContextConfig |
| 22 | + | hasPopupConfig bool | |||
| 22 | onInsertPopup func(ctx context.Context, input InsertPopupInput) error | 23 | onInsertPopup func(ctx context.Context, input InsertPopupInput) error | ||
| 23 | onOpenNativePopup func(ctx context.Context, input NativePopupInput) error | 24 | onOpenNativePopup func(ctx context.Context, input NativePopupInput) error | ||
| 24 | onClosePane func(ctx context.Context, paneID entity.PaneID) error | 25 | onClosePane func(ctx context.Context, paneID entity.PaneID) error | ||
| @@ -103,10 +104,38 @@ func (pm *popupManager) setConfig( | |||||
| 103 | } | 104 | } | ||
| 104 | pm.ensureInitialized() | 105 | pm.ensureInitialized() | ||
| 105 | pm.factory = factory | 106 | pm.factory = factory | ||
| 106 | - | pm.popupConfig = popupConfig | 107 | + | pm.setPopupConfig(popupConfig) |
| 107 | pm.generatePaneID = generateID | 108 | pm.generatePaneID = generateID | ||
| 108 | } | 109 | } | ||
| 109 | 110 | ||||
| 111 | + | func (pm *popupManager) setPopupConfig(popupConfig *entity.BrowsingContextConfig) { | |||
| 112 | + | if pm == nil { | |||
| 113 | + | return | |||
| 114 | + | } | |||
| 115 | + | if popupConfig == nil { | |||
| 116 | + | pm.popupConfig = entity.BrowsingContextConfig{} | |||
| 117 | + | pm.hasPopupConfig = false | |||
| 118 | + | return | |||
| 119 | + | } | |||
| 120 | + | pm.popupConfig = *popupConfig | |||
| 121 | + | pm.hasPopupConfig = true | |||
| 122 | + | } | |||
| 123 | + | ||||
| 124 | + | func (pm *popupManager) updatePopupConfig(popupConfig entity.BrowsingContextConfig) { | |||
| 125 | + | if pm == nil { | |||
| 126 | + | return | |||
| 127 | + | } | |||
| 128 | + | pm.popupConfig = popupConfig | |||
| 129 | + | pm.hasPopupConfig = true | |||
| 130 | + | } | |||
| 131 | + | ||||
| 132 | + | func (pm *popupManager) currentPopupConfig() *entity.BrowsingContextConfig { | |||
| 133 | + | if pm == nil || !pm.hasPopupConfig { | |||
| 134 | + | return nil | |||
| 135 | + | } | |||
| 136 | + | return &pm.popupConfig | |||
| 137 | + | } | |||
| 138 | + | ||||
| 110 | func (pm *popupManager) setWindowIDResolver(fn func(entity.PaneID) (string, bool)) { | 139 | func (pm *popupManager) setWindowIDResolver(fn func(entity.PaneID) (string, bool)) { | ||
| 111 | if pm == nil { | 140 | if pm == nil { | ||
| 112 | return | 141 | return | ||
| @@ -409,7 +438,8 @@ func (pm *popupManager) popupParentURIAtOpen( | |||||
| 409 | hooks popupCoordinatorHooks, | 438 | hooks popupCoordinatorHooks, | ||
| 410 | targetURI string, | 439 | targetURI string, | ||
| 411 | ) string { | 440 | ) string { | ||
| 412 | - | if pm.popupConfig == nil || !pm.popupConfig.OAuthAutoClose || !IsOAuthURL(targetURI) { | 441 | + | cfg := pm.currentPopupConfig() |
| 442 | + | if cfg == nil || !cfg.OAuthAutoClose || !IsOAuthURL(targetURI) { | |||
| 413 | return "" | 443 | return "" | ||
| 414 | } | 444 | } | ||
| 415 | parentURIAtOpen := parentWV.URI() | 445 | parentURIAtOpen := parentWV.URI() | ||
| @@ -439,6 +469,7 @@ func (pm *popupManager) openNativePopup( | |||||
| 439 | log.Error().Err(err).Msg("failed to create webview for native popup") | 469 | log.Error().Err(err).Msg("failed to create webview for native popup") | ||
| 440 | return nil | 470 | return nil | ||
| 441 | } | 471 | } | ||
| 472 | + | cfg := pm.currentPopupConfig() | |||
| 442 | pm.setBrowsingContextDecision(popupWV, decision) | 473 | pm.setBrowsingContextDecision(popupWV, decision) | ||
| 443 | if err := pm.onOpenNativePopup(ctx, NativePopupInput{ | 474 | if err := pm.onOpenNativePopup(ctx, NativePopupInput{ | ||
| 444 | ParentPaneID: parentPaneID, | 475 | ParentPaneID: parentPaneID, | ||
| @@ -447,7 +478,7 @@ func (pm *popupManager) openNativePopup( | |||||
| 447 | PopupWebView: popupWV, | 478 | PopupWebView: popupWV, | ||
| 448 | TargetURI: req.TargetURI, | 479 | TargetURI: req.TargetURI, | ||
| 449 | Request: req, | 480 | Request: req, | ||
| 450 | - | ObserveOAuthAutoClose: pm.popupConfig != nil && pm.popupConfig.OAuthAutoClose && IsOAuthURL(req.TargetURI), | 481 | + | ObserveOAuthAutoClose: cfg != nil && cfg.OAuthAutoClose && IsOAuthURL(req.TargetURI), |
| 451 | }); err != nil { | 482 | }); err != nil { | ||
| 452 | log.Error().Err(err).Msg("failed to open native popup host") | 483 | log.Error().Err(err).Msg("failed to open native popup host") | ||
| 453 | popupWV.Destroy() | 484 | popupWV.Destroy() | ||
| @@ -476,7 +507,8 @@ func (pm *popupManager) handlePopupCreate( | |||||
| 476 | Bool("no_javascript_access", req.NoJavaScriptAccess). | 507 | Bool("no_javascript_access", req.NoJavaScriptAccess). | ||
| 477 | Msg("popup create request") | 508 | Msg("popup create request") | ||
| 478 | 509 | ||||
| 479 | - | if pm.popupConfig != nil && !pm.popupConfig.OpenInNewPane { | 510 | + | cfg := pm.currentPopupConfig() |
| 511 | + | if cfg != nil && !cfg.OpenInNewPane { | |||
| 480 | log.Debug().Msg("popups disabled by config, blocking") | 512 | log.Debug().Msg("popups disabled by config, blocking") | ||
| 481 | return nil | 513 | return nil | ||
| 482 | } | 514 | } | ||
| @@ -534,10 +566,10 @@ func (pm *popupManager) handlePopupCreate( | |||||
| 534 | 566 | ||||
| 535 | popupType := DetectPopupType(req.FrameName) | 567 | popupType := DetectPopupType(req.FrameName) | ||
| 536 | popupID := popupWV.ID() | 568 | popupID := popupWV.ID() | ||
| 537 | - | behavior := GetBehavior(popupType, pm.popupConfig) | 569 | + | behavior := GetBehavior(popupType, cfg) |
| 538 | placement := "right" | 570 | placement := "right" | ||
| 539 | - | if pm.popupConfig != nil { | 571 | + | if cfg != nil { |
| 540 | - | placement = pm.popupConfig.Placement | 572 | + | placement = cfg.Placement |
| 541 | } | 573 | } | ||
| 542 | paneID, popupPane := pm.createPopupPane(popupID, parentPaneID, req.TargetURI) | 574 | paneID, popupPane := pm.createPopupPane(popupID, parentPaneID, req.TargetURI) | ||
| 543 | 575 | ||||
| @@ -562,8 +594,9 @@ func (pm *popupManager) finishPopupCreate( | |||||
| 562 | create popupCreateContext, | 594 | create popupCreateContext, | ||
| 563 | ) port.WebView { | 595 | ) port.WebView { | ||
| 564 | log := logging.FromContext(ctx) | 596 | log := logging.FromContext(ctx) | ||
| 565 | - | hasConfig := pm.popupConfig != nil | 597 | + | cfg := pm.currentPopupConfig() |
| 566 | - | oauthEnabled := hasConfig && pm.popupConfig.OAuthAutoClose | 598 | + | hasConfig := cfg != nil |
| 599 | + | oauthEnabled := hasConfig && cfg.OAuthAutoClose | |||
| 567 | isOAuth := IsOAuthURL(create.Request.TargetURI) | 600 | isOAuth := IsOAuthURL(create.Request.TargetURI) | ||
| 568 | 601 | ||||
| 569 | log.Debug(). | 602 | log.Debug(). | ||
| @@ -792,7 +825,8 @@ func (pm *popupManager) handleLinkMiddleClick( | |||||
| 792 | Str("uri", logging.TruncateURL(uri, logURLMaxLen)). | 825 | Str("uri", logging.TruncateURL(uri, logURLMaxLen)). | ||
| 793 | Msg("middle-click/ctrl+click on link") | 826 | Msg("middle-click/ctrl+click on link") | ||
| 794 | 827 | ||||
| 795 | - | if pm.popupConfig != nil && !pm.popupConfig.OpenInNewPane { | 828 | + | cfg := pm.currentPopupConfig() |
| 829 | + | if cfg != nil && !cfg.OpenInNewPane { | |||
| 796 | log.Debug().Msg("popups disabled by config, ignoring middle-click") | 830 | log.Debug().Msg("popups disabled by config, ignoring middle-click") | ||
| 797 | return false | 831 | return false | ||
| 798 | } | 832 | } | ||
| @@ -845,7 +879,7 @@ func (pm *popupManager) handleLinkMiddleClick( | |||||
| 845 | hooks.setupWebViewCallbacks(ctx, paneID, newWV) | 879 | hooks.setupWebViewCallbacks(ctx, paneID, newWV) | ||
| 846 | } | 880 | } | ||
| 847 | 881 | ||||
| 848 | - | behavior, placement := popupTabInsertionConfig(pm.popupConfig) | 882 | + | behavior, placement := popupTabInsertionConfig(pm.currentPopupConfig()) |
| 849 | 883 | ||||
| 850 | if pm.onInsertPopup != nil { | 884 | if pm.onInsertPopup != nil { | ||
| 851 | popupInput := InsertPopupInput{ | 885 | popupInput := InsertPopupInput{ | ||
M internal/ui/coordinator/content/popup_test.go +22 -2
| @@ -719,7 +719,10 @@ func TestSetPopupConfig_SetsFields(t *testing.T) { | |||
| 719 | 719 | c.SetPopupConfig(factory, cfg, genID) | |
| 720 | 720 | ||
| 721 | 721 | assert.Equal(t, factory, c.popups.factory) | |
| 722 | - | assert.Equal(t, cfg, c.popups.popupConfig) | |
| 722 | + | assert.True(t, c.popups.hasPopupConfig) | |
| 723 | + | assert.Equal(t, *cfg, c.popups.popupConfig) | |
| 724 | + | cfg.Behavior = entity.PopupBehaviorWindowed | |
| 725 | + | assert.Equal(t, entity.PopupBehaviorSplit, c.popups.popupConfig.Behavior) | |
| 723 | 726 | assert.NotNil(t, c.popups.generatePaneID) | |
| 724 | 727 | assert.Equal(t, "test-id", c.popups.generatePaneID()) | |
| 725 | 728 | } | |
| @@ -731,10 +734,27 @@ func TestSetPopupConfig_NilConfigAllowed(t *testing.T) { | |||
| 731 | 734 | c.SetPopupConfig(nil, nil, nil) | |
| 732 | 735 | ||
| 733 | 736 | assert.Nil(t, c.popups.factory) | |
| 734 | - | assert.Nil(t, c.popups.popupConfig) | |
| 737 | + | assert.False(t, c.popups.hasPopupConfig) | |
| 738 | + | assert.Equal(t, entity.BrowsingContextConfig{}, c.popups.popupConfig) | |
| 735 | 739 | assert.Nil(t, c.popups.generatePaneID) | |
| 736 | 740 | } | |
| 737 | 741 | ||
| 742 | + | func TestUpdatePopupConfig_CopiesLatestValue(t *testing.T) { | |
| 743 | + | t.Parallel() | |
| 744 | + | ||
| 745 | + | c := &Coordinator{} | |
| 746 | + | initial := &entity.BrowsingContextConfig{OpenInNewPane: true, OAuthAutoClose: false} | |
| 747 | + | c.SetPopupConfig(nil, initial, nil) | |
| 748 | + | ||
| 749 | + | c.UpdatePopupConfig(entity.BrowsingContextConfig{OpenInNewPane: false, OAuthAutoClose: true}) | |
| 750 | + | ||
| 751 | + | assert.True(t, c.popups.hasPopupConfig) | |
| 752 | + | assert.False(t, c.popups.popupConfig.OpenInNewPane) | |
| 753 | + | assert.True(t, c.popups.popupConfig.OAuthAutoClose) | |
| 754 | + | assert.True(t, initial.OpenInNewPane) | |
| 755 | + | assert.False(t, initial.OAuthAutoClose) | |
| 756 | + | } | |
| 757 | + | ||
| 738 | 758 | type popupNavigationWebViewStub struct { | |
| 739 | 759 | *mocks.MockWebView | |
| 740 | 760 | primed []string | |
| @@ -719,7 +719,10 @@ func TestSetPopupConfig_SetsFields(t *testing.T) { | |||||
| 719 | c.SetPopupConfig(factory, cfg, genID) | 719 | c.SetPopupConfig(factory, cfg, genID) | ||
| 720 | 720 | ||||
| 721 | assert.Equal(t, factory, c.popups.factory) | 721 | assert.Equal(t, factory, c.popups.factory) | ||
| 722 | - | assert.Equal(t, cfg, c.popups.popupConfig) | 722 | + | assert.True(t, c.popups.hasPopupConfig) |
| 723 | + | assert.Equal(t, *cfg, c.popups.popupConfig) | |||
| 724 | + | cfg.Behavior = entity.PopupBehaviorWindowed | |||
| 725 | + | assert.Equal(t, entity.PopupBehaviorSplit, c.popups.popupConfig.Behavior) | |||
| 723 | assert.NotNil(t, c.popups.generatePaneID) | 726 | assert.NotNil(t, c.popups.generatePaneID) | ||
| 724 | assert.Equal(t, "test-id", c.popups.generatePaneID()) | 727 | assert.Equal(t, "test-id", c.popups.generatePaneID()) | ||
| 725 | } | 728 | } | ||
| @@ -731,10 +734,27 @@ func TestSetPopupConfig_NilConfigAllowed(t *testing.T) { | |||||
| 731 | c.SetPopupConfig(nil, nil, nil) | 734 | c.SetPopupConfig(nil, nil, nil) | ||
| 732 | 735 | ||||
| 733 | assert.Nil(t, c.popups.factory) | 736 | assert.Nil(t, c.popups.factory) | ||
| 734 | - | assert.Nil(t, c.popups.popupConfig) | 737 | + | assert.False(t, c.popups.hasPopupConfig) |
| 738 | + | assert.Equal(t, entity.BrowsingContextConfig{}, c.popups.popupConfig) | |||
| 735 | assert.Nil(t, c.popups.generatePaneID) | 739 | assert.Nil(t, c.popups.generatePaneID) | ||
| 736 | } | 740 | } | ||
| 737 | 741 | ||||
| 742 | + | func TestUpdatePopupConfig_CopiesLatestValue(t *testing.T) { | |||
| 743 | + | t.Parallel() | |||
| 744 | + | ||||
| 745 | + | c := &Coordinator{} | |||
| 746 | + | initial := &entity.BrowsingContextConfig{OpenInNewPane: true, OAuthAutoClose: false} | |||
| 747 | + | c.SetPopupConfig(nil, initial, nil) | |||
| 748 | + | ||||
| 749 | + | c.UpdatePopupConfig(entity.BrowsingContextConfig{OpenInNewPane: false, OAuthAutoClose: true}) | |||
| 750 | + | ||||
| 751 | + | assert.True(t, c.popups.hasPopupConfig) | |||
| 752 | + | assert.False(t, c.popups.popupConfig.OpenInNewPane) | |||
| 753 | + | assert.True(t, c.popups.popupConfig.OAuthAutoClose) | |||
| 754 | + | assert.True(t, initial.OpenInNewPane) | |||
| 755 | + | assert.False(t, initial.OAuthAutoClose) | |||
| 756 | + | } | |||
| 757 | + | ||||
| 738 | type popupNavigationWebViewStub struct { | 758 | type popupNavigationWebViewStub struct { | ||
| 739 | *mocks.MockWebView | 759 | *mocks.MockWebView | ||
| 740 | primed []string | 760 | primed []string | ||
M internal/ui/external_theme_reload_test.go +8 -13
| @@ -106,10 +106,9 @@ func TestApplyAppearanceConfigSendsCompleteEngineSettingsPayload(t *testing.T) { | |||
| 106 | 106 | Return(nil). | |
| 107 | 107 | Once() | |
| 108 | 108 | app := &App{ | |
| 109 | - | deps: &Dependencies{}, | |
| 110 | - | runtimeConfig: bootstrap.RuntimeConfigSnapshotFromConfig(cfg), | |
| 111 | - | runtimeConfigLoaded: true, | |
| 112 | - | engine: engine, | |
| 109 | + | deps: &Dependencies{}, | |
| 110 | + | runtimeConfig: runtimeConfigStateFromSnapshotForTest(bootstrap.RuntimeConfigSnapshotFromConfig(cfg)), | |
| 111 | + | engine: engine, | |
| 113 | 112 | } | |
| 114 | 113 | ||
| 115 | 114 | app.applyAppearanceConfig(ctx) | |
| @@ -151,8 +150,7 @@ func TestExternalThemeWatcherCallbackAppliesThemeThroughSharedPath(t *testing.T) | |||
| 151 | 150 | ExternalThemeSource: source, | |
| 152 | 151 | ExternalThemeWatcher: watcher, | |
| 153 | 152 | }, | |
| 154 | - | runtimeConfig: bootstrap.RuntimeConfigSnapshotFromConfig(cfg), | |
| 155 | - | runtimeConfigLoaded: true, | |
| 153 | + | runtimeConfig: runtimeConfigStateFromSnapshotForTest(bootstrap.RuntimeConfigSnapshotFromConfig(cfg)), | |
| 156 | 154 | dispatchOnMainThread: immediateDispatchForExternalThemeTest, | |
| 157 | 155 | engine: engine, | |
| 158 | 156 | } | |
| @@ -186,8 +184,7 @@ func TestExternalThemeReloadKeepsLastGoodAndDisablingClearsIt(t *testing.T) { | |||
| 186 | 184 | ResolveThemeUC: usecase.NewResolveThemeUseCase(source), | |
| 187 | 185 | ExternalThemeSource: source, | |
| 188 | 186 | }, | |
| 189 | - | runtimeConfig: bootstrap.RuntimeConfigSnapshotFromConfig(cfg), | |
| 190 | - | runtimeConfigLoaded: true, | |
| 187 | + | runtimeConfig: runtimeConfigStateFromSnapshotForTest(bootstrap.RuntimeConfigSnapshotFromConfig(cfg)), | |
| 191 | 188 | dispatchOnMainThread: immediateDispatchForExternalThemeTest, | |
| 192 | 189 | } | |
| 193 | 190 | ||
| @@ -204,7 +201,7 @@ func TestExternalThemeReloadKeepsLastGoodAndDisablingClearsIt(t *testing.T) { | |||
| 204 | 201 | require.Equal(t, "#000000", app.deps.Theme.GetCurrentPalette().Background) | |
| 205 | 202 | ||
| 206 | 203 | cfg.Appearance.ExternalTheme.Enabled = false | |
| 207 | - | app.updateRuntimeConfig(bootstrap.RuntimeConfigSnapshotFromConfig(cfg)) | |
| 204 | + | setRuntimeConfigSnapshotForTest(app, bootstrap.RuntimeConfigSnapshotFromConfig(cfg)) | |
| 208 | 205 | app.applyAppearanceConfig(ctx) | |
| 209 | 206 | require.Equal(t, cfg.Appearance.DarkPalette.Background, app.deps.Theme.GetCurrentPalette().Background) | |
| 210 | 207 | } | |
| @@ -227,8 +224,7 @@ func TestExternalThemeReloadKeepsLastGoodOnReadError(t *testing.T) { | |||
| 227 | 224 | ResolveThemeUC: usecase.NewResolveThemeUseCase(source), | |
| 228 | 225 | ExternalThemeSource: source, | |
| 229 | 226 | }, | |
| 230 | - | runtimeConfig: bootstrap.RuntimeConfigSnapshotFromConfig(cfg), | |
| 231 | - | runtimeConfigLoaded: true, | |
| 227 | + | runtimeConfig: runtimeConfigStateFromSnapshotForTest(bootstrap.RuntimeConfigSnapshotFromConfig(cfg)), | |
| 232 | 228 | dispatchOnMainThread: immediateDispatchForExternalThemeTest, | |
| 233 | 229 | } | |
| 234 | 230 | ||
| @@ -259,8 +255,7 @@ func TestExternalThemeReloadRecoversAfterMalformedUpdate(t *testing.T) { | |||
| 259 | 255 | ResolveThemeUC: usecase.NewResolveThemeUseCase(source), | |
| 260 | 256 | ExternalThemeSource: source, | |
| 261 | 257 | }, | |
| 262 | - | runtimeConfig: bootstrap.RuntimeConfigSnapshotFromConfig(cfg), | |
| 263 | - | runtimeConfigLoaded: true, | |
| 258 | + | runtimeConfig: runtimeConfigStateFromSnapshotForTest(bootstrap.RuntimeConfigSnapshotFromConfig(cfg)), | |
| 264 | 259 | dispatchOnMainThread: immediateDispatchForExternalThemeTest, | |
| 265 | 260 | } | |
| 266 | 261 | ||
| @@ -106,10 +106,9 @@ func TestApplyAppearanceConfigSendsCompleteEngineSettingsPayload(t *testing.T) { | |||||
| 106 | Return(nil). | 106 | Return(nil). | ||
| 107 | Once() | 107 | Once() | ||
| 108 | app := &App{ | 108 | app := &App{ | ||
| 109 | - | deps: &Dependencies{}, | 109 | + | deps: &Dependencies{}, |
| 110 | - | runtimeConfig: bootstrap.RuntimeConfigSnapshotFromConfig(cfg), | 110 | + | runtimeConfig: runtimeConfigStateFromSnapshotForTest(bootstrap.RuntimeConfigSnapshotFromConfig(cfg)), |
| 111 | - | runtimeConfigLoaded: true, | 111 | + | engine: engine, |
| 112 | - | engine: engine, | |||
| 113 | } | 112 | } | ||
| 114 | 113 | ||||
| 115 | app.applyAppearanceConfig(ctx) | 114 | app.applyAppearanceConfig(ctx) | ||
| @@ -151,8 +150,7 @@ func TestExternalThemeWatcherCallbackAppliesThemeThroughSharedPath(t *testing.T) | |||||
| 151 | ExternalThemeSource: source, | 150 | ExternalThemeSource: source, | ||
| 152 | ExternalThemeWatcher: watcher, | 151 | ExternalThemeWatcher: watcher, | ||
| 153 | }, | 152 | }, | ||
| 154 | - | runtimeConfig: bootstrap.RuntimeConfigSnapshotFromConfig(cfg), | 153 | + | runtimeConfig: runtimeConfigStateFromSnapshotForTest(bootstrap.RuntimeConfigSnapshotFromConfig(cfg)), |
| 155 | - | runtimeConfigLoaded: true, | |||
| 156 | dispatchOnMainThread: immediateDispatchForExternalThemeTest, | 154 | dispatchOnMainThread: immediateDispatchForExternalThemeTest, | ||
| 157 | engine: engine, | 155 | engine: engine, | ||
| 158 | } | 156 | } | ||
| @@ -186,8 +184,7 @@ func TestExternalThemeReloadKeepsLastGoodAndDisablingClearsIt(t *testing.T) { | |||||
| 186 | ResolveThemeUC: usecase.NewResolveThemeUseCase(source), | 184 | ResolveThemeUC: usecase.NewResolveThemeUseCase(source), | ||
| 187 | ExternalThemeSource: source, | 185 | ExternalThemeSource: source, | ||
| 188 | }, | 186 | }, | ||
| 189 | - | runtimeConfig: bootstrap.RuntimeConfigSnapshotFromConfig(cfg), | 187 | + | runtimeConfig: runtimeConfigStateFromSnapshotForTest(bootstrap.RuntimeConfigSnapshotFromConfig(cfg)), |
| 190 | - | runtimeConfigLoaded: true, | |||
| 191 | dispatchOnMainThread: immediateDispatchForExternalThemeTest, | 188 | dispatchOnMainThread: immediateDispatchForExternalThemeTest, | ||
| 192 | } | 189 | } | ||
| 193 | 190 | ||||
| @@ -204,7 +201,7 @@ func TestExternalThemeReloadKeepsLastGoodAndDisablingClearsIt(t *testing.T) { | |||||
| 204 | require.Equal(t, "#000000", app.deps.Theme.GetCurrentPalette().Background) | 201 | require.Equal(t, "#000000", app.deps.Theme.GetCurrentPalette().Background) | ||
| 205 | 202 | ||||
| 206 | cfg.Appearance.ExternalTheme.Enabled = false | 203 | cfg.Appearance.ExternalTheme.Enabled = false | ||
| 207 | - | app.updateRuntimeConfig(bootstrap.RuntimeConfigSnapshotFromConfig(cfg)) | 204 | + | setRuntimeConfigSnapshotForTest(app, bootstrap.RuntimeConfigSnapshotFromConfig(cfg)) |
| 208 | app.applyAppearanceConfig(ctx) | 205 | app.applyAppearanceConfig(ctx) | ||
| 209 | require.Equal(t, cfg.Appearance.DarkPalette.Background, app.deps.Theme.GetCurrentPalette().Background) | 206 | require.Equal(t, cfg.Appearance.DarkPalette.Background, app.deps.Theme.GetCurrentPalette().Background) | ||
| 210 | } | 207 | } | ||
| @@ -227,8 +224,7 @@ func TestExternalThemeReloadKeepsLastGoodOnReadError(t *testing.T) { | |||||
| 227 | ResolveThemeUC: usecase.NewResolveThemeUseCase(source), | 224 | ResolveThemeUC: usecase.NewResolveThemeUseCase(source), | ||
| 228 | ExternalThemeSource: source, | 225 | ExternalThemeSource: source, | ||
| 229 | }, | 226 | }, | ||
| 230 | - | runtimeConfig: bootstrap.RuntimeConfigSnapshotFromConfig(cfg), | 227 | + | runtimeConfig: runtimeConfigStateFromSnapshotForTest(bootstrap.RuntimeConfigSnapshotFromConfig(cfg)), |
| 231 | - | runtimeConfigLoaded: true, | |||
| 232 | dispatchOnMainThread: immediateDispatchForExternalThemeTest, | 228 | dispatchOnMainThread: immediateDispatchForExternalThemeTest, | ||
| 233 | } | 229 | } | ||
| 234 | 230 | ||||
| @@ -259,8 +255,7 @@ func TestExternalThemeReloadRecoversAfterMalformedUpdate(t *testing.T) { | |||||
| 259 | ResolveThemeUC: usecase.NewResolveThemeUseCase(source), | 255 | ResolveThemeUC: usecase.NewResolveThemeUseCase(source), | ||
| 260 | ExternalThemeSource: source, | 256 | ExternalThemeSource: source, | ||
| 261 | }, | 257 | }, | ||
| 262 | - | runtimeConfig: bootstrap.RuntimeConfigSnapshotFromConfig(cfg), | 258 | + | runtimeConfig: runtimeConfigStateFromSnapshotForTest(bootstrap.RuntimeConfigSnapshotFromConfig(cfg)), |
| 263 | - | runtimeConfigLoaded: true, | |||
| 264 | dispatchOnMainThread: immediateDispatchForExternalThemeTest, | 259 | dispatchOnMainThread: immediateDispatchForExternalThemeTest, | ||
| 265 | } | 260 | } | ||
| 266 | 261 | ||||
A internal/ui/runtime_config_state.go +38 -0
| @@ -0,0 +1,38 @@ | |||
| 1 | + | package ui | |
| 2 | + | ||
| 3 | + | import "github.com/bnema/dumber/internal/application/port" | |
| 4 | + | ||
| 5 | + | type runtimeConfigState struct { | |
| 6 | + | provider port.RuntimeConfigProvider | |
| 7 | + | snapshot port.RuntimeConfigSnapshot | |
| 8 | + | loaded bool | |
| 9 | + | } | |
| 10 | + | ||
| 11 | + | func newRuntimeConfigState(provider port.RuntimeConfigProvider) *runtimeConfigState { | |
| 12 | + | state := &runtimeConfigState{provider: provider} | |
| 13 | + | if provider != nil { | |
| 14 | + | state.Update(provider.Current()) | |
| 15 | + | } | |
| 16 | + | return state | |
| 17 | + | } | |
| 18 | + | ||
| 19 | + | func (s *runtimeConfigState) Current() port.RuntimeConfigSnapshot { | |
| 20 | + | if s == nil { | |
| 21 | + | return port.RuntimeConfigSnapshot{} | |
| 22 | + | } | |
| 23 | + | if s.loaded { | |
| 24 | + | return s.snapshot | |
| 25 | + | } | |
| 26 | + | if s.provider != nil { | |
| 27 | + | return s.provider.Current() | |
| 28 | + | } | |
| 29 | + | return port.RuntimeConfigSnapshot{} | |
| 30 | + | } | |
| 31 | + | ||
| 32 | + | func (s *runtimeConfigState) Update(snapshot port.RuntimeConfigSnapshot) { | |
| 33 | + | if s == nil { | |
| 34 | + | return | |
| 35 | + | } | |
| 36 | + | s.snapshot = snapshot | |
| 37 | + | s.loaded = true | |
| 38 | + | } | |
| @@ -0,0 +1,38 @@ | |||||
| 1 | + | package ui | |||
| 2 | + | ||||
| 3 | + | import "github.com/bnema/dumber/internal/application/port" | |||
| 4 | + | ||||
| 5 | + | type runtimeConfigState struct { | |||
| 6 | + | provider port.RuntimeConfigProvider | |||
| 7 | + | snapshot port.RuntimeConfigSnapshot | |||
| 8 | + | loaded bool | |||
| 9 | + | } | |||
| 10 | + | ||||
| 11 | + | func newRuntimeConfigState(provider port.RuntimeConfigProvider) *runtimeConfigState { | |||
| 12 | + | state := &runtimeConfigState{provider: provider} | |||
| 13 | + | if provider != nil { | |||
| 14 | + | state.Update(provider.Current()) | |||
| 15 | + | } | |||
| 16 | + | return state | |||
| 17 | + | } | |||
| 18 | + | ||||
| 19 | + | func (s *runtimeConfigState) Current() port.RuntimeConfigSnapshot { | |||
| 20 | + | if s == nil { | |||
| 21 | + | return port.RuntimeConfigSnapshot{} | |||
| 22 | + | } | |||
| 23 | + | if s.loaded { | |||
| 24 | + | return s.snapshot | |||
| 25 | + | } | |||
| 26 | + | if s.provider != nil { | |||
| 27 | + | return s.provider.Current() | |||
| 28 | + | } | |||
| 29 | + | return port.RuntimeConfigSnapshot{} | |||
| 30 | + | } | |||
| 31 | + | ||||
| 32 | + | func (s *runtimeConfigState) Update(snapshot port.RuntimeConfigSnapshot) { | |||
| 33 | + | if s == nil { | |||
| 34 | + | return | |||
| 35 | + | } | |||
| 36 | + | s.snapshot = snapshot | |||
| 37 | + | s.loaded = true | |||
| 38 | + | } | |||
M internal/ui/runtime_config_test.go +48 -14
| @@ -4,10 +4,14 @@ import ( | |||
| 4 | 4 | "context" | |
| 5 | 5 | "testing" | |
| 6 | 6 | ||
| 7 | + | "github.com/stretchr/testify/mock" | |
| 8 | + | ||
| 7 | 9 | "github.com/bnema/dumber/internal/application/dto" | |
| 8 | 10 | "github.com/bnema/dumber/internal/application/port" | |
| 9 | 11 | portmocks "github.com/bnema/dumber/internal/application/port/mocks" | |
| 10 | 12 | "github.com/bnema/dumber/internal/domain/entity" | |
| 13 | + | "github.com/bnema/dumber/internal/shared/syncdispatch" | |
| 14 | + | "github.com/bnema/dumber/internal/ui/coordinator/content" | |
| 11 | 15 | ) | |
| 12 | 16 | ||
| 13 | 17 | func TestDependenciesValidateAcceptsRuntimeConfigOnly(t *testing.T) { | |
| @@ -49,9 +53,9 @@ func TestAppRuntimeConfigReturnsProviderSnapshot(t *testing.T) { | |||
| 49 | 53 | } | |
| 50 | 54 | } | |
| 51 | 55 | ||
| 52 | - | func TestAppUpdateRuntimeConfigStoresLatestSnapshot(t *testing.T) { | |
| 56 | + | func TestAppRuntimeConfigStateStoresLatestSnapshot(t *testing.T) { | |
| 53 | 57 | app := &App{} | |
| 54 | - | app.updateRuntimeConfig(port.RuntimeConfigSnapshot{ | |
| 58 | + | setRuntimeConfigSnapshotForTest(app, port.RuntimeConfigSnapshot{ | |
| 55 | 59 | UI: port.RuntimeUIConfig{SidebarWidth: 340}, | |
| 56 | 60 | }) | |
| 57 | 61 | ||
| @@ -61,32 +65,62 @@ func TestAppUpdateRuntimeConfigStoresLatestSnapshot(t *testing.T) { | |||
| 61 | 65 | } | |
| 62 | 66 | } | |
| 63 | 67 | ||
| 64 | - | func TestAppUpdateRuntimeConfigRefreshesStablePopupConfigPointer(t *testing.T) { | |
| 65 | - | app := &App{} | |
| 66 | - | app.updateRuntimeConfig(port.RuntimeConfigSnapshot{ | |
| 68 | + | func TestConfigWatcherOnChangeUpdatesRuntimeConfigState(t *testing.T) { | |
| 69 | + | ctx := context.Background() | |
| 70 | + | provider := portmocks.NewMockRuntimeConfigProvider(t) | |
| 71 | + | provider.EXPECT().Watch().Return(nil).Once() | |
| 72 | + | var onChange func(port.RuntimeConfigSnapshot) | |
| 73 | + | provider.EXPECT(). | |
| 74 | + | OnChange(mock.Anything). | |
| 75 | + | Run(func(fn func(port.RuntimeConfigSnapshot)) { | |
| 76 | + | onChange = fn | |
| 77 | + | }). | |
| 78 | + | Once() | |
| 79 | + | ||
| 80 | + | initial := port.RuntimeConfigSnapshot{ | |
| 67 | 81 | UI: port.RuntimeUIConfig{ | |
| 68 | 82 | Workspace: entity.WorkspaceConfig{ | |
| 69 | 83 | BrowsingContexts: entity.BrowsingContextConfig{OpenInNewPane: true}, | |
| 70 | 84 | }, | |
| 71 | 85 | }, | |
| 72 | - | }) | |
| 73 | - | ptr := app.popupBrowsingContextConfig() | |
| 86 | + | } | |
| 87 | + | app := &App{ | |
| 88 | + | deps: &Dependencies{RuntimeConfig: provider}, | |
| 89 | + | runtimeConfig: runtimeConfigStateFromSnapshotForTest(initial), | |
| 90 | + | contentCoord: &content.Coordinator{}, | |
| 91 | + | browserWindows: map[string]*browserWindow{}, | |
| 92 | + | dispatchOnMainThread: func(label string, fn func()) syncdispatch.SyncDispatchResult { | |
| 93 | + | if label != "ui.runtime_config_reload" { | |
| 94 | + | t.Fatalf("dispatch label=%q, want ui.runtime_config_reload", label) | |
| 95 | + | } | |
| 96 | + | fn() | |
| 97 | + | return syncdispatch.SyncDispatchResult{Label: label, Status: syncdispatch.SyncDispatchCompleted} | |
| 98 | + | }, | |
| 99 | + | } | |
| 100 | + | app.contentCoord.SetPopupConfig(nil, &initial.UI.Workspace.BrowsingContexts, nil) | |
| 74 | 101 | ||
| 75 | - | app.updateRuntimeConfig(port.RuntimeConfigSnapshot{ | |
| 102 | + | app.initConfigWatcher(ctx) | |
| 103 | + | if onChange == nil { | |
| 104 | + | t.Fatal("runtime config OnChange callback was not registered") | |
| 105 | + | } | |
| 106 | + | ||
| 107 | + | onChange(port.RuntimeConfigSnapshot{ | |
| 76 | 108 | UI: port.RuntimeUIConfig{ | |
| 109 | + | SidebarWidth: 360, | |
| 77 | 110 | Workspace: entity.WorkspaceConfig{ | |
| 78 | 111 | BrowsingContexts: entity.BrowsingContextConfig{OpenInNewPane: false, OAuthAutoClose: true}, | |
| 79 | 112 | }, | |
| 80 | 113 | }, | |
| 81 | 114 | }) | |
| 82 | 115 | ||
| 83 | - | if ptr != app.popupBrowsingContextConfig() { | |
| 84 | - | t.Fatal("popup config pointer must remain stable for content coordinator") | |
| 116 | + | got := app.runtimeConfigSnapshot() | |
| 117 | + | if got.UI.SidebarWidth != 360 { | |
| 118 | + | t.Fatalf("SidebarWidth=%d, want 360", got.UI.SidebarWidth) | |
| 85 | 119 | } | |
| 86 | - | if ptr.OpenInNewPane { | |
| 87 | - | t.Fatal("popup config pointer did not observe updated OpenInNewPane") | |
| 120 | + | if got.UI.Workspace.BrowsingContexts.OpenInNewPane { | |
| 121 | + | t.Fatal("runtime config state did not observe updated popup OpenInNewPane") | |
| 88 | 122 | } | |
| 89 | - | if !ptr.OAuthAutoClose { | |
| 90 | - | t.Fatal("popup config pointer did not observe updated OAuthAutoClose") | |
| 123 | + | if !got.UI.Workspace.BrowsingContexts.OAuthAutoClose { | |
| 124 | + | t.Fatal("runtime config state did not observe updated popup OAuthAutoClose") | |
| 91 | 125 | } | |
| 92 | 126 | } | |
| @@ -4,10 +4,14 @@ import ( | |||||
| 4 | "context" | 4 | "context" | ||
| 5 | "testing" | 5 | "testing" | ||
| 6 | 6 | ||||
| 7 | + | "github.com/stretchr/testify/mock" | |||
| 8 | + | ||||
| 7 | "github.com/bnema/dumber/internal/application/dto" | 9 | "github.com/bnema/dumber/internal/application/dto" | ||
| 8 | "github.com/bnema/dumber/internal/application/port" | 10 | "github.com/bnema/dumber/internal/application/port" | ||
| 9 | portmocks "github.com/bnema/dumber/internal/application/port/mocks" | 11 | portmocks "github.com/bnema/dumber/internal/application/port/mocks" | ||
| 10 | "github.com/bnema/dumber/internal/domain/entity" | 12 | "github.com/bnema/dumber/internal/domain/entity" | ||
| 13 | + | "github.com/bnema/dumber/internal/shared/syncdispatch" | |||
| 14 | + | "github.com/bnema/dumber/internal/ui/coordinator/content" | |||
| 11 | ) | 15 | ) | ||
| 12 | 16 | ||||
| 13 | func TestDependenciesValidateAcceptsRuntimeConfigOnly(t *testing.T) { | 17 | func TestDependenciesValidateAcceptsRuntimeConfigOnly(t *testing.T) { | ||
| @@ -49,9 +53,9 @@ func TestAppRuntimeConfigReturnsProviderSnapshot(t *testing.T) { | |||||
| 49 | } | 53 | } | ||
| 50 | } | 54 | } | ||
| 51 | 55 | ||||
| 52 | - | func TestAppUpdateRuntimeConfigStoresLatestSnapshot(t *testing.T) { | 56 | + | func TestAppRuntimeConfigStateStoresLatestSnapshot(t *testing.T) { |
| 53 | app := &App{} | 57 | app := &App{} | ||
| 54 | - | app.updateRuntimeConfig(port.RuntimeConfigSnapshot{ | 58 | + | setRuntimeConfigSnapshotForTest(app, port.RuntimeConfigSnapshot{ |
| 55 | UI: port.RuntimeUIConfig{SidebarWidth: 340}, | 59 | UI: port.RuntimeUIConfig{SidebarWidth: 340}, | ||
| 56 | }) | 60 | }) | ||
| 57 | 61 | ||||
| @@ -61,32 +65,62 @@ func TestAppUpdateRuntimeConfigStoresLatestSnapshot(t *testing.T) { | |||||
| 61 | } | 65 | } | ||
| 62 | } | 66 | } | ||
| 63 | 67 | ||||
| 64 | - | func TestAppUpdateRuntimeConfigRefreshesStablePopupConfigPointer(t *testing.T) { | 68 | + | func TestConfigWatcherOnChangeUpdatesRuntimeConfigState(t *testing.T) { |
| 65 | - | app := &App{} | 69 | + | ctx := context.Background() |
| 66 | - | app.updateRuntimeConfig(port.RuntimeConfigSnapshot{ | 70 | + | provider := portmocks.NewMockRuntimeConfigProvider(t) |
| 71 | + | provider.EXPECT().Watch().Return(nil).Once() | |||
| 72 | + | var onChange func(port.RuntimeConfigSnapshot) | |||
| 73 | + | provider.EXPECT(). | |||
| 74 | + | OnChange(mock.Anything). | |||
| 75 | + | Run(func(fn func(port.RuntimeConfigSnapshot)) { | |||
| 76 | + | onChange = fn | |||
| 77 | + | }). | |||
| 78 | + | Once() | |||
| 79 | + | ||||
| 80 | + | initial := port.RuntimeConfigSnapshot{ | |||
| 67 | UI: port.RuntimeUIConfig{ | 81 | UI: port.RuntimeUIConfig{ | ||
| 68 | Workspace: entity.WorkspaceConfig{ | 82 | Workspace: entity.WorkspaceConfig{ | ||
| 69 | BrowsingContexts: entity.BrowsingContextConfig{OpenInNewPane: true}, | 83 | BrowsingContexts: entity.BrowsingContextConfig{OpenInNewPane: true}, | ||
| 70 | }, | 84 | }, | ||
| 71 | }, | 85 | }, | ||
| 72 | - | }) | 86 | + | } |
| 73 | - | ptr := app.popupBrowsingContextConfig() | 87 | + | app := &App{ |
| 88 | + | deps: &Dependencies{RuntimeConfig: provider}, | |||
| 89 | + | runtimeConfig: runtimeConfigStateFromSnapshotForTest(initial), | |||
| 90 | + | contentCoord: &content.Coordinator{}, | |||
| 91 | + | browserWindows: map[string]*browserWindow{}, | |||
| 92 | + | dispatchOnMainThread: func(label string, fn func()) syncdispatch.SyncDispatchResult { | |||
| 93 | + | if label != "ui.runtime_config_reload" { | |||
| 94 | + | t.Fatalf("dispatch label=%q, want ui.runtime_config_reload", label) | |||
| 95 | + | } | |||
| 96 | + | fn() | |||
| 97 | + | return syncdispatch.SyncDispatchResult{Label: label, Status: syncdispatch.SyncDispatchCompleted} | |||
| 98 | + | }, | |||
| 99 | + | } | |||
| 100 | + | app.contentCoord.SetPopupConfig(nil, &initial.UI.Workspace.BrowsingContexts, nil) | |||
| 74 | 101 | ||||
| 75 | - | app.updateRuntimeConfig(port.RuntimeConfigSnapshot{ | 102 | + | app.initConfigWatcher(ctx) |
| 103 | + | if onChange == nil { | |||
| 104 | + | t.Fatal("runtime config OnChange callback was not registered") | |||
| 105 | + | } | |||
| 106 | + | ||||
| 107 | + | onChange(port.RuntimeConfigSnapshot{ | |||
| 76 | UI: port.RuntimeUIConfig{ | 108 | UI: port.RuntimeUIConfig{ | ||
| 109 | + | SidebarWidth: 360, | |||
| 77 | Workspace: entity.WorkspaceConfig{ | 110 | Workspace: entity.WorkspaceConfig{ | ||
| 78 | BrowsingContexts: entity.BrowsingContextConfig{OpenInNewPane: false, OAuthAutoClose: true}, | 111 | BrowsingContexts: entity.BrowsingContextConfig{OpenInNewPane: false, OAuthAutoClose: true}, | ||
| 79 | }, | 112 | }, | ||
| 80 | }, | 113 | }, | ||
| 81 | }) | 114 | }) | ||
| 82 | 115 | ||||
| 83 | - | if ptr != app.popupBrowsingContextConfig() { | 116 | + | got := app.runtimeConfigSnapshot() |
| 84 | - | t.Fatal("popup config pointer must remain stable for content coordinator") | 117 | + | if got.UI.SidebarWidth != 360 { |
| 118 | + | t.Fatalf("SidebarWidth=%d, want 360", got.UI.SidebarWidth) | |||
| 85 | } | 119 | } | ||
| 86 | - | if ptr.OpenInNewPane { | 120 | + | if got.UI.Workspace.BrowsingContexts.OpenInNewPane { |
| 87 | - | t.Fatal("popup config pointer did not observe updated OpenInNewPane") | 121 | + | t.Fatal("runtime config state did not observe updated popup OpenInNewPane") |
| 88 | } | 122 | } | ||
| 89 | - | if !ptr.OAuthAutoClose { | 123 | + | if !got.UI.Workspace.BrowsingContexts.OAuthAutoClose { |
| 90 | - | t.Fatal("popup config pointer did not observe updated OAuthAutoClose") | 124 | + | t.Fatal("runtime config state did not observe updated popup OAuthAutoClose") |
| 91 | } | 125 | } | ||
| 92 | } | 126 | } | ||
M internal/ui/runtime_config_test_helpers_test.go +19 -3
| @@ -10,10 +10,26 @@ func runtimeConfigSnapshotForTest(cfg *config.Config) port.RuntimeConfigSnapshot | |||
| 10 | 10 | return bootstrap.RuntimeConfigSnapshotFromConfig(cfg) | |
| 11 | 11 | } | |
| 12 | 12 | ||
| 13 | + | func runtimeConfigStateFromSnapshotForTest(snapshot port.RuntimeConfigSnapshot) *runtimeConfigState { | |
| 14 | + | state := newRuntimeConfigState(nil) | |
| 15 | + | state.Update(snapshot) | |
| 16 | + | return state | |
| 17 | + | } | |
| 18 | + | ||
| 19 | + | func runtimeConfigStateForTest(cfg *config.Config) *runtimeConfigState { | |
| 20 | + | return runtimeConfigStateFromSnapshotForTest(runtimeConfigSnapshotForTest(cfg)) | |
| 21 | + | } | |
| 22 | + | ||
| 23 | + | func setRuntimeConfigSnapshotForTest(app *App, snapshot port.RuntimeConfigSnapshot) { | |
| 24 | + | if app.runtimeConfig == nil { | |
| 25 | + | app.runtimeConfig = newRuntimeConfigState(nil) | |
| 26 | + | } | |
| 27 | + | app.runtimeConfig.Update(snapshot) | |
| 28 | + | } | |
| 29 | + | ||
| 13 | 30 | func appWithRuntimeConfigForTest(cfg *config.Config) *App { | |
| 14 | 31 | return &App{ | |
| 15 | - | deps: &Dependencies{}, | |
| 16 | - | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | |
| 17 | - | runtimeConfigLoaded: true, | |
| 32 | + | deps: &Dependencies{}, | |
| 33 | + | runtimeConfig: runtimeConfigStateForTest(cfg), | |
| 18 | 34 | } | |
| 19 | 35 | } | |
| @@ -10,10 +10,26 @@ func runtimeConfigSnapshotForTest(cfg *config.Config) port.RuntimeConfigSnapshot | |||||
| 10 | return bootstrap.RuntimeConfigSnapshotFromConfig(cfg) | 10 | return bootstrap.RuntimeConfigSnapshotFromConfig(cfg) | ||
| 11 | } | 11 | } | ||
| 12 | 12 | ||||
| 13 | + | func runtimeConfigStateFromSnapshotForTest(snapshot port.RuntimeConfigSnapshot) *runtimeConfigState { | |||
| 14 | + | state := newRuntimeConfigState(nil) | |||
| 15 | + | state.Update(snapshot) | |||
| 16 | + | return state | |||
| 17 | + | } | |||
| 18 | + | ||||
| 19 | + | func runtimeConfigStateForTest(cfg *config.Config) *runtimeConfigState { | |||
| 20 | + | return runtimeConfigStateFromSnapshotForTest(runtimeConfigSnapshotForTest(cfg)) | |||
| 21 | + | } | |||
| 22 | + | ||||
| 23 | + | func setRuntimeConfigSnapshotForTest(app *App, snapshot port.RuntimeConfigSnapshot) { | |||
| 24 | + | if app.runtimeConfig == nil { | |||
| 25 | + | app.runtimeConfig = newRuntimeConfigState(nil) | |||
| 26 | + | } | |||
| 27 | + | app.runtimeConfig.Update(snapshot) | |||
| 28 | + | } | |||
| 29 | + | ||||
| 13 | func appWithRuntimeConfigForTest(cfg *config.Config) *App { | 30 | func appWithRuntimeConfigForTest(cfg *config.Config) *App { | ||
| 14 | return &App{ | 31 | return &App{ | ||
| 15 | - | deps: &Dependencies{}, | 32 | + | deps: &Dependencies{}, |
| 16 | - | runtimeConfig: runtimeConfigSnapshotForTest(cfg), | 33 | + | runtimeConfig: runtimeConfigStateForTest(cfg), |
| 17 | - | runtimeConfigLoaded: true, | |||
| 18 | } | 34 | } | ||
| 19 | } | 35 | } | ||