fix(arch): address final architecture review findings
- commit
- 2bfaca97f5e024146aeba3486d3bd60c60872f0d
- author
- brice · · 2026-06-25 15:47 UTC
- parent
- ec8c3ee
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
16 files · +142 -118
M cmd/dumber/main.go +2 -2
| @@ -801,8 +801,8 @@ func createUseCases(repos *repositories, cfg *config.Config) *useCases { | |||
| 801 | 801 | historyUC.SetHistoryMutationCoordinator(historyRecorderUC) | |
| 802 | 802 | ||
| 803 | 803 | return &useCases{ | |
| 804 | - | tabs: usecase.NewManageTabsUseCase(idGenerator, usecase.WithManageTabsLocalPathResolver(localPaths)), | |
| 805 | - | panes: usecase.NewManagePanesUseCase(idGenerator, usecase.WithManagePanesLocalPathResolver(localPaths)), | |
| 804 | + | tabs: usecase.NewManageTabsUseCase(idGenerator, localPaths), | |
| 805 | + | panes: usecase.NewManagePanesUseCase(idGenerator, localPaths), | |
| 806 | 806 | history: historyUC, | |
| 807 | 807 | favorites: usecase.NewManageFavoritesUseCase(repos.favorite, repos.folder, repos.tag), | |
| 808 | 808 | zoom: usecase.NewManageZoomUseCase(repos.zoom, defaultZoom, zoomCache), | |
| @@ -801,8 +801,8 @@ func createUseCases(repos *repositories, cfg *config.Config) *useCases { | |||||
| 801 | historyUC.SetHistoryMutationCoordinator(historyRecorderUC) | 801 | historyUC.SetHistoryMutationCoordinator(historyRecorderUC) | ||
| 802 | 802 | ||||
| 803 | return &useCases{ | 803 | return &useCases{ | ||
| 804 | - | tabs: usecase.NewManageTabsUseCase(idGenerator, usecase.WithManageTabsLocalPathResolver(localPaths)), | 804 | + | tabs: usecase.NewManageTabsUseCase(idGenerator, localPaths), |
| 805 | - | panes: usecase.NewManagePanesUseCase(idGenerator, usecase.WithManagePanesLocalPathResolver(localPaths)), | 805 | + | panes: usecase.NewManagePanesUseCase(idGenerator, localPaths), |
| 806 | history: historyUC, | 806 | history: historyUC, | ||
| 807 | favorites: usecase.NewManageFavoritesUseCase(repos.favorite, repos.folder, repos.tag), | 807 | favorites: usecase.NewManageFavoritesUseCase(repos.favorite, repos.folder, repos.tag), | ||
| 808 | zoom: usecase.NewManageZoomUseCase(repos.zoom, defaultZoom, zoomCache), | 808 | zoom: usecase.NewManageZoomUseCase(repos.zoom, defaultZoom, zoomCache), | ||
M internal/application/usecase/manage_panes.go +18 -25
| @@ -75,26 +75,12 @@ type ManagePanesUseCase struct { | |||
| 75 | 75 | normalizer *NavigationURLNormalizer | |
| 76 | 76 | } | |
| 77 | 77 | ||
| 78 | - | // ManagePanesOption configures ManagePanesUseCase. | |
| 79 | - | type ManagePanesOption func(*ManagePanesUseCase) | |
| 80 | - | ||
| 81 | - | // WithManagePanesLocalPathResolver configures local path resolution for initial navigation URLs. | |
| 82 | - | func WithManagePanesLocalPathResolver(resolver port.LocalPathResolver) ManagePanesOption { | |
| 83 | - | return func(uc *ManagePanesUseCase) { | |
| 84 | - | uc.normalizer = NewNavigationURLNormalizer(resolver) | |
| 85 | - | } | |
| 86 | - | } | |
| 87 | - | ||
| 88 | 78 | // NewManagePanesUseCase creates a new pane management use case. | |
| 89 | - | func NewManagePanesUseCase(idGenerator IDGenerator, opts ...ManagePanesOption) *ManagePanesUseCase { | |
| 90 | - | uc := &ManagePanesUseCase{ | |
| 79 | + | func NewManagePanesUseCase(idGenerator IDGenerator, localPaths port.LocalPathResolver) *ManagePanesUseCase { | |
| 80 | + | return &ManagePanesUseCase{ | |
| 91 | 81 | idGenerator: idGenerator, | |
| 92 | - | normalizer: NewNavigationURLNormalizer(nil), | |
| 93 | - | } | |
| 94 | - | for _, opt := range opts { | |
| 95 | - | opt(uc) | |
| 82 | + | normalizer: NewNavigationURLNormalizer(localPaths), | |
| 96 | 83 | } | |
| 97 | - | return uc | |
| 98 | 84 | } | |
| 99 | 85 | ||
| 100 | 86 | // NormalizeNavigationURL normalizes user navigation input for pane creation. | |
| @@ -105,6 +91,16 @@ func (uc *ManagePanesUseCase) NormalizeNavigationURL(ctx context.Context, input | |||
| 105 | 91 | return uc.normalizer.normalize(ctx, input) | |
| 106 | 92 | } | |
| 107 | 93 | ||
| 94 | + | func (uc *ManagePanesUseCase) newPane(ctx context.Context, paneID entity.PaneID, initialURL string) *entity.Pane { | |
| 95 | + | pane := entity.NewPane(paneID) | |
| 96 | + | if initialURL != "" { | |
| 97 | + | pane.URI = uc.normalizer.normalize(ctx, initialURL) | |
| 98 | + | } else { | |
| 99 | + | pane.URI = "about:blank" | |
| 100 | + | } | |
| 101 | + | return pane | |
| 102 | + | } | |
| 103 | + | ||
| 108 | 104 | // SplitPaneInput contains parameters for splitting a pane. | |
| 109 | 105 | type SplitPaneInput struct { | |
| 110 | 106 | Workspace *entity.Workspace | |
| @@ -160,12 +156,7 @@ func (uc *ManagePanesUseCase) Split(ctx context.Context, input SplitPaneInput) ( | |||
| 160 | 156 | newPane = input.NewPane | |
| 161 | 157 | } else { | |
| 162 | 158 | paneID := entity.PaneID(uc.idGenerator()) | |
| 163 | - | newPane = entity.NewPane(paneID) | |
| 164 | - | if input.InitialURL != "" { | |
| 165 | - | newPane.URI = uc.normalizer.normalize(ctx, input.InitialURL) | |
| 166 | - | } else { | |
| 167 | - | newPane.URI = "about:blank" | |
| 168 | - | } | |
| 159 | + | newPane = uc.newPane(ctx, paneID, input.InitialURL) | |
| 169 | 160 | } | |
| 170 | 161 | ||
| 171 | 162 | // Create new pane node | |
| @@ -1006,6 +997,7 @@ func (uc *ManagePanesUseCase) CreateStack( | |||
| 1006 | 997 | ctx context.Context, | |
| 1007 | 998 | ws *entity.Workspace, | |
| 1008 | 999 | paneNode *entity.PaneNode, | |
| 1000 | + | initialURL string, | |
| 1009 | 1001 | ) (*CreateStackOutput, error) { | |
| 1010 | 1002 | log := logging.FromContext(ctx) | |
| 1011 | 1003 | log.Debug().Str("pane_id", paneNode.ID).Msg("creating stack from pane") | |
| @@ -1036,7 +1028,7 @@ func (uc *ManagePanesUseCase) CreateStack( | |||
| 1036 | 1028 | ||
| 1037 | 1029 | // Create new pane | |
| 1038 | 1030 | newPaneID := entity.PaneID(uc.idGenerator()) | |
| 1039 | - | newPane := entity.NewPane(newPaneID) | |
| 1031 | + | newPane := uc.newPane(ctx, newPaneID, initialURL) | |
| 1040 | 1032 | ||
| 1041 | 1033 | // Create child node for the new pane | |
| 1042 | 1034 | newChildNode := &entity.PaneNode{ | |
| @@ -1081,6 +1073,7 @@ func (uc *ManagePanesUseCase) AddToStack( | |||
| 1081 | 1073 | ws *entity.Workspace, | |
| 1082 | 1074 | stackNode *entity.PaneNode, | |
| 1083 | 1075 | pane *entity.Pane, | |
| 1076 | + | initialURL string, | |
| 1084 | 1077 | ) (*AddToStackOutput, error) { | |
| 1085 | 1078 | log := logging.FromContext(ctx) | |
| 1086 | 1079 | ||
| @@ -1097,7 +1090,7 @@ func (uc *ManagePanesUseCase) AddToStack( | |||
| 1097 | 1090 | // Create pane if not provided | |
| 1098 | 1091 | if pane == nil { | |
| 1099 | 1092 | paneID := entity.PaneID(uc.idGenerator()) | |
| 1100 | - | pane = entity.NewPane(paneID) | |
| 1093 | + | pane = uc.newPane(ctx, paneID, initialURL) | |
| 1101 | 1094 | } | |
| 1102 | 1095 | ||
| 1103 | 1096 | log.Debug(). | |
| @@ -75,26 +75,12 @@ type ManagePanesUseCase struct { | |||||
| 75 | normalizer *NavigationURLNormalizer | 75 | normalizer *NavigationURLNormalizer | ||
| 76 | } | 76 | } | ||
| 77 | 77 | ||||
| 78 | - | // ManagePanesOption configures ManagePanesUseCase. | |||
| 79 | - | type ManagePanesOption func(*ManagePanesUseCase) | |||
| 80 | - | ||||
| 81 | - | // WithManagePanesLocalPathResolver configures local path resolution for initial navigation URLs. | |||
| 82 | - | func WithManagePanesLocalPathResolver(resolver port.LocalPathResolver) ManagePanesOption { | |||
| 83 | - | return func(uc *ManagePanesUseCase) { | |||
| 84 | - | uc.normalizer = NewNavigationURLNormalizer(resolver) | |||
| 85 | - | } | |||
| 86 | - | } | |||
| 87 | - | ||||
| 88 | // NewManagePanesUseCase creates a new pane management use case. | 78 | // NewManagePanesUseCase creates a new pane management use case. | ||
| 89 | - | func NewManagePanesUseCase(idGenerator IDGenerator, opts ...ManagePanesOption) *ManagePanesUseCase { | 79 | + | func NewManagePanesUseCase(idGenerator IDGenerator, localPaths port.LocalPathResolver) *ManagePanesUseCase { |
| 90 | - | uc := &ManagePanesUseCase{ | 80 | + | return &ManagePanesUseCase{ |
| 91 | idGenerator: idGenerator, | 81 | idGenerator: idGenerator, | ||
| 92 | - | normalizer: NewNavigationURLNormalizer(nil), | 82 | + | normalizer: NewNavigationURLNormalizer(localPaths), |
| 93 | - | } | |||
| 94 | - | for _, opt := range opts { | |||
| 95 | - | opt(uc) | |||
| 96 | } | 83 | } | ||
| 97 | - | return uc | |||
| 98 | } | 84 | } | ||
| 99 | 85 | ||||
| 100 | // NormalizeNavigationURL normalizes user navigation input for pane creation. | 86 | // NormalizeNavigationURL normalizes user navigation input for pane creation. | ||
| @@ -105,6 +91,16 @@ func (uc *ManagePanesUseCase) NormalizeNavigationURL(ctx context.Context, input | |||||
| 105 | return uc.normalizer.normalize(ctx, input) | 91 | return uc.normalizer.normalize(ctx, input) | ||
| 106 | } | 92 | } | ||
| 107 | 93 | ||||
| 94 | + | func (uc *ManagePanesUseCase) newPane(ctx context.Context, paneID entity.PaneID, initialURL string) *entity.Pane { | |||
| 95 | + | pane := entity.NewPane(paneID) | |||
| 96 | + | if initialURL != "" { | |||
| 97 | + | pane.URI = uc.normalizer.normalize(ctx, initialURL) | |||
| 98 | + | } else { | |||
| 99 | + | pane.URI = "about:blank" | |||
| 100 | + | } | |||
| 101 | + | return pane | |||
| 102 | + | } | |||
| 103 | + | ||||
| 108 | // SplitPaneInput contains parameters for splitting a pane. | 104 | // SplitPaneInput contains parameters for splitting a pane. | ||
| 109 | type SplitPaneInput struct { | 105 | type SplitPaneInput struct { | ||
| 110 | Workspace *entity.Workspace | 106 | Workspace *entity.Workspace | ||
| @@ -160,12 +156,7 @@ func (uc *ManagePanesUseCase) Split(ctx context.Context, input SplitPaneInput) ( | |||||
| 160 | newPane = input.NewPane | 156 | newPane = input.NewPane | ||
| 161 | } else { | 157 | } else { | ||
| 162 | paneID := entity.PaneID(uc.idGenerator()) | 158 | paneID := entity.PaneID(uc.idGenerator()) | ||
| 163 | - | newPane = entity.NewPane(paneID) | 159 | + | newPane = uc.newPane(ctx, paneID, input.InitialURL) |
| 164 | - | if input.InitialURL != "" { | |||
| 165 | - | newPane.URI = uc.normalizer.normalize(ctx, input.InitialURL) | |||
| 166 | - | } else { | |||
| 167 | - | newPane.URI = "about:blank" | |||
| 168 | - | } | |||
| 169 | } | 160 | } | ||
| 170 | 161 | ||||
| 171 | // Create new pane node | 162 | // Create new pane node | ||
| @@ -1006,6 +997,7 @@ func (uc *ManagePanesUseCase) CreateStack( | |||||
| 1006 | ctx context.Context, | 997 | ctx context.Context, | ||
| 1007 | ws *entity.Workspace, | 998 | ws *entity.Workspace, | ||
| 1008 | paneNode *entity.PaneNode, | 999 | paneNode *entity.PaneNode, | ||
| 1000 | + | initialURL string, | |||
| 1009 | ) (*CreateStackOutput, error) { | 1001 | ) (*CreateStackOutput, error) { | ||
| 1010 | log := logging.FromContext(ctx) | 1002 | log := logging.FromContext(ctx) | ||
| 1011 | log.Debug().Str("pane_id", paneNode.ID).Msg("creating stack from pane") | 1003 | log.Debug().Str("pane_id", paneNode.ID).Msg("creating stack from pane") | ||
| @@ -1036,7 +1028,7 @@ func (uc *ManagePanesUseCase) CreateStack( | |||||
| 1036 | 1028 | ||||
| 1037 | // Create new pane | 1029 | // Create new pane | ||
| 1038 | newPaneID := entity.PaneID(uc.idGenerator()) | 1030 | newPaneID := entity.PaneID(uc.idGenerator()) | ||
| 1039 | - | newPane := entity.NewPane(newPaneID) | 1031 | + | newPane := uc.newPane(ctx, newPaneID, initialURL) |
| 1040 | 1032 | ||||
| 1041 | // Create child node for the new pane | 1033 | // Create child node for the new pane | ||
| 1042 | newChildNode := &entity.PaneNode{ | 1034 | newChildNode := &entity.PaneNode{ | ||
| @@ -1081,6 +1073,7 @@ func (uc *ManagePanesUseCase) AddToStack( | |||||
| 1081 | ws *entity.Workspace, | 1073 | ws *entity.Workspace, | ||
| 1082 | stackNode *entity.PaneNode, | 1074 | stackNode *entity.PaneNode, | ||
| 1083 | pane *entity.Pane, | 1075 | pane *entity.Pane, | ||
| 1076 | + | initialURL string, | |||
| 1084 | ) (*AddToStackOutput, error) { | 1077 | ) (*AddToStackOutput, error) { | ||
| 1085 | log := logging.FromContext(ctx) | 1078 | log := logging.FromContext(ctx) | ||
| 1086 | 1079 | ||||
| @@ -1097,7 +1090,7 @@ func (uc *ManagePanesUseCase) AddToStack( | |||||
| 1097 | // Create pane if not provided | 1090 | // Create pane if not provided | ||
| 1098 | if pane == nil { | 1091 | if pane == nil { | ||
| 1099 | paneID := entity.PaneID(uc.idGenerator()) | 1092 | paneID := entity.PaneID(uc.idGenerator()) | ||
| 1100 | - | pane = entity.NewPane(paneID) | 1093 | + | pane = uc.newPane(ctx, paneID, initialURL) |
| 1101 | } | 1094 | } | ||
| 1102 | 1095 | ||||
| 1103 | log.Debug(). | 1096 | log.Debug(). | ||
M internal/application/usecase/manage_panes_close_stack_test.go +7 -7
| @@ -8,7 +8,7 @@ import ( | |||
| 8 | 8 | ) | |
| 9 | 9 | ||
| 10 | 10 | func TestManagePanesUseCase_CloseStackedPane_RemovesFromStackWithMoreThanTwo(t *testing.T) { | |
| 11 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | |
| 11 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) | |
| 12 | 12 | ctx := context.Background() | |
| 13 | 13 | ||
| 14 | 14 | // Create a stack with 3 panes | |
| @@ -44,7 +44,7 @@ func TestManagePanesUseCase_CloseStackedPane_RemovesFromStackWithMoreThanTwo(t * | |||
| 44 | 44 | } | |
| 45 | 45 | ||
| 46 | 46 | func TestManagePanesUseCase_CloseStackedPane_DissolvesStackWithTwo(t *testing.T) { | |
| 47 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | |
| 47 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) | |
| 48 | 48 | ctx := context.Background() | |
| 49 | 49 | ||
| 50 | 50 | // Create a stack with 2 panes | |
| @@ -84,7 +84,7 @@ func TestManagePanesUseCase_CloseStackedPane_DissolvesStackWithTwo(t *testing.T) | |||
| 84 | 84 | } | |
| 85 | 85 | ||
| 86 | 86 | func TestManagePanesUseCase_CloseStackedPane_UpdatesActivePaneWhenClosingActive(t *testing.T) { | |
| 87 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | |
| 87 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) | |
| 88 | 88 | ctx := context.Background() | |
| 89 | 89 | ||
| 90 | 90 | // Create a stack with 3 panes, middle one is active | |
| @@ -113,7 +113,7 @@ func TestManagePanesUseCase_CloseStackedPane_UpdatesActivePaneWhenClosingActive( | |||
| 113 | 113 | } | |
| 114 | 114 | ||
| 115 | 115 | func TestManagePanesUseCase_CloseStackedPane_PreservesActivePaneWhenClosingNonActive(t *testing.T) { | |
| 116 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | |
| 116 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) | |
| 117 | 117 | ctx := context.Background() | |
| 118 | 118 | ||
| 119 | 119 | // Create a stack with 3 panes, first one is active | |
| @@ -138,7 +138,7 @@ func TestManagePanesUseCase_CloseStackedPane_PreservesActivePaneWhenClosingNonAc | |||
| 138 | 138 | } | |
| 139 | 139 | ||
| 140 | 140 | func TestManagePanesUseCase_CloseStackedPane_DissolvesNestedStack(t *testing.T) { | |
| 141 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | |
| 141 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) | |
| 142 | 142 | ctx := context.Background() | |
| 143 | 143 | ||
| 144 | 144 | // Create a split with a stack on the left | |
| @@ -185,7 +185,7 @@ func TestManagePanesUseCase_CloseStackedPane_DissolvesNestedStack(t *testing.T) | |||
| 185 | 185 | } | |
| 186 | 186 | ||
| 187 | 187 | func TestManagePanesUseCase_CloseStackedPane_ClosingFirstPaneUpdatesIndex(t *testing.T) { | |
| 188 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | |
| 188 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) | |
| 189 | 189 | ctx := context.Background() | |
| 190 | 190 | ||
| 191 | 191 | // Create a stack with 3 panes, first one is active | |
| @@ -218,7 +218,7 @@ func TestManagePanesUseCase_CloseStackedPane_ClosingFirstPaneUpdatesIndex(t *tes | |||
| 218 | 218 | } | |
| 219 | 219 | ||
| 220 | 220 | func TestManagePanesUseCase_CloseStackedPane_ClosingLastPaneUpdatesIndex(t *testing.T) { | |
| 221 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | |
| 221 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) | |
| 222 | 222 | ctx := context.Background() | |
| 223 | 223 | ||
| 224 | 224 | // Create a stack with 3 panes, last one is active | |
| @@ -8,7 +8,7 @@ import ( | |||||
| 8 | ) | 8 | ) | ||
| 9 | 9 | ||||
| 10 | func TestManagePanesUseCase_CloseStackedPane_RemovesFromStackWithMoreThanTwo(t *testing.T) { | 10 | func TestManagePanesUseCase_CloseStackedPane_RemovesFromStackWithMoreThanTwo(t *testing.T) { | ||
| 11 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | 11 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) |
| 12 | ctx := context.Background() | 12 | ctx := context.Background() | ||
| 13 | 13 | ||||
| 14 | // Create a stack with 3 panes | 14 | // Create a stack with 3 panes | ||
| @@ -44,7 +44,7 @@ func TestManagePanesUseCase_CloseStackedPane_RemovesFromStackWithMoreThanTwo(t * | |||||
| 44 | } | 44 | } | ||
| 45 | 45 | ||||
| 46 | func TestManagePanesUseCase_CloseStackedPane_DissolvesStackWithTwo(t *testing.T) { | 46 | func TestManagePanesUseCase_CloseStackedPane_DissolvesStackWithTwo(t *testing.T) { | ||
| 47 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | 47 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) |
| 48 | ctx := context.Background() | 48 | ctx := context.Background() | ||
| 49 | 49 | ||||
| 50 | // Create a stack with 2 panes | 50 | // Create a stack with 2 panes | ||
| @@ -84,7 +84,7 @@ func TestManagePanesUseCase_CloseStackedPane_DissolvesStackWithTwo(t *testing.T) | |||||
| 84 | } | 84 | } | ||
| 85 | 85 | ||||
| 86 | func TestManagePanesUseCase_CloseStackedPane_UpdatesActivePaneWhenClosingActive(t *testing.T) { | 86 | func TestManagePanesUseCase_CloseStackedPane_UpdatesActivePaneWhenClosingActive(t *testing.T) { | ||
| 87 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | 87 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) |
| 88 | ctx := context.Background() | 88 | ctx := context.Background() | ||
| 89 | 89 | ||||
| 90 | // Create a stack with 3 panes, middle one is active | 90 | // Create a stack with 3 panes, middle one is active | ||
| @@ -113,7 +113,7 @@ func TestManagePanesUseCase_CloseStackedPane_UpdatesActivePaneWhenClosingActive( | |||||
| 113 | } | 113 | } | ||
| 114 | 114 | ||||
| 115 | func TestManagePanesUseCase_CloseStackedPane_PreservesActivePaneWhenClosingNonActive(t *testing.T) { | 115 | func TestManagePanesUseCase_CloseStackedPane_PreservesActivePaneWhenClosingNonActive(t *testing.T) { | ||
| 116 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | 116 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) |
| 117 | ctx := context.Background() | 117 | ctx := context.Background() | ||
| 118 | 118 | ||||
| 119 | // Create a stack with 3 panes, first one is active | 119 | // Create a stack with 3 panes, first one is active | ||
| @@ -138,7 +138,7 @@ func TestManagePanesUseCase_CloseStackedPane_PreservesActivePaneWhenClosingNonAc | |||||
| 138 | } | 138 | } | ||
| 139 | 139 | ||||
| 140 | func TestManagePanesUseCase_CloseStackedPane_DissolvesNestedStack(t *testing.T) { | 140 | func TestManagePanesUseCase_CloseStackedPane_DissolvesNestedStack(t *testing.T) { | ||
| 141 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | 141 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) |
| 142 | ctx := context.Background() | 142 | ctx := context.Background() | ||
| 143 | 143 | ||||
| 144 | // Create a split with a stack on the left | 144 | // Create a split with a stack on the left | ||
| @@ -185,7 +185,7 @@ func TestManagePanesUseCase_CloseStackedPane_DissolvesNestedStack(t *testing.T) | |||||
| 185 | } | 185 | } | ||
| 186 | 186 | ||||
| 187 | func TestManagePanesUseCase_CloseStackedPane_ClosingFirstPaneUpdatesIndex(t *testing.T) { | 187 | func TestManagePanesUseCase_CloseStackedPane_ClosingFirstPaneUpdatesIndex(t *testing.T) { | ||
| 188 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | 188 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) |
| 189 | ctx := context.Background() | 189 | ctx := context.Background() | ||
| 190 | 190 | ||||
| 191 | // Create a stack with 3 panes, first one is active | 191 | // Create a stack with 3 panes, first one is active | ||
| @@ -218,7 +218,7 @@ func TestManagePanesUseCase_CloseStackedPane_ClosingFirstPaneUpdatesIndex(t *tes | |||||
| 218 | } | 218 | } | ||
| 219 | 219 | ||||
| 220 | func TestManagePanesUseCase_CloseStackedPane_ClosingLastPaneUpdatesIndex(t *testing.T) { | 220 | func TestManagePanesUseCase_CloseStackedPane_ClosingLastPaneUpdatesIndex(t *testing.T) { | ||
| 221 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | 221 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) |
| 222 | ctx := context.Background() | 222 | ctx := context.Background() | ||
| 223 | 223 | ||||
| 224 | // Create a stack with 3 panes, last one is active | 224 | // Create a stack with 3 panes, last one is active | ||
M internal/application/usecase/manage_panes_consume_expel_test.go +11 -11
| @@ -8,7 +8,7 @@ import ( | |||
| 8 | 8 | ) | |
| 9 | 9 | ||
| 10 | 10 | func TestManagePanesUseCase_ConsumeOrExpel_ConsumeLeft_CyclesHorizontalToVerticalThenStack(t *testing.T) { | |
| 11 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | |
| 11 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) | |
| 12 | 12 | ctx := context.Background() | |
| 13 | 13 | ||
| 14 | 14 | left := leaf("left") | |
| @@ -61,7 +61,7 @@ func TestManagePanesUseCase_ConsumeOrExpel_ConsumeLeft_CyclesHorizontalToVertica | |||
| 61 | 61 | } | |
| 62 | 62 | ||
| 63 | 63 | func TestManagePanesUseCase_ConsumeOrExpel_ConsumeRight_CyclesHorizontalToVerticalThenStack(t *testing.T) { | |
| 64 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | |
| 64 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) | |
| 65 | 65 | ctx := context.Background() | |
| 66 | 66 | ||
| 67 | 67 | left := leaf("left") | |
| @@ -114,7 +114,7 @@ func TestManagePanesUseCase_ConsumeOrExpel_ConsumeRight_CyclesHorizontalToVertic | |||
| 114 | 114 | } | |
| 115 | 115 | ||
| 116 | 116 | func TestManagePanesUseCase_ConsumeOrExpel_ConsumeIntoExistingStack_AppendsToEnd(t *testing.T) { | |
| 117 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | |
| 117 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) | |
| 118 | 118 | ctx := context.Background() | |
| 119 | 119 | ||
| 120 | 120 | stackNode := stack(leaf("a"), leaf("b")) | |
| @@ -141,7 +141,7 @@ func TestManagePanesUseCase_ConsumeOrExpel_ConsumeIntoExistingStack_AppendsToEnd | |||
| 141 | 141 | } | |
| 142 | 142 | ||
| 143 | 143 | func TestManagePanesUseCase_ConsumeOrExpel_ConsumeLeft_InVerticalSplitWithStackedSibling_Noops(t *testing.T) { | |
| 144 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | |
| 144 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) | |
| 145 | 145 | ctx := context.Background() | |
| 146 | 146 | ||
| 147 | 147 | top := leaf("top") | |
| @@ -165,7 +165,7 @@ func TestManagePanesUseCase_ConsumeOrExpel_ConsumeLeft_InVerticalSplitWithStacke | |||
| 165 | 165 | } | |
| 166 | 166 | ||
| 167 | 167 | func TestManagePanesUseCase_ConsumeOrExpel_Expel_DissolvesTwoPaneStack(t *testing.T) { | |
| 168 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | |
| 168 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) | |
| 169 | 169 | ctx := context.Background() | |
| 170 | 170 | ||
| 171 | 171 | stackNode := stack(leaf("a"), leaf("b")) | |
| @@ -200,7 +200,7 @@ func TestManagePanesUseCase_ConsumeOrExpel_Expel_DissolvesTwoPaneStack(t *testin | |||
| 200 | 200 | } | |
| 201 | 201 | ||
| 202 | 202 | func TestManagePanesUseCase_ConsumeOrExpel_ExpelThenConsumeRight_CyclesVerticalToHorizontal(t *testing.T) { | |
| 203 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | |
| 203 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) | |
| 204 | 204 | ctx := context.Background() | |
| 205 | 205 | ||
| 206 | 206 | stackNode := stack(leaf("a"), leaf("b")) | |
| @@ -228,7 +228,7 @@ func TestManagePanesUseCase_ConsumeOrExpel_ExpelThenConsumeRight_CyclesVerticalT | |||
| 228 | 228 | } | |
| 229 | 229 | ||
| 230 | 230 | func TestManagePanesUseCase_ConsumeOrExpel_ExpelThenConsumeLeft_ReturnsToStack(t *testing.T) { | |
| 231 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | |
| 231 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) | |
| 232 | 232 | ctx := context.Background() | |
| 233 | 233 | ||
| 234 | 234 | stackNode := stack(leaf("a"), leaf("b")) | |
| @@ -259,7 +259,7 @@ func TestManagePanesUseCase_ConsumeOrExpel_ExpelThenConsumeLeft_ReturnsToStack(t | |||
| 259 | 259 | } | |
| 260 | 260 | ||
| 261 | 261 | func TestManagePanesUseCase_ConsumeOrExpel_Expel_StackRemainsWhenMoreThanTwo(t *testing.T) { | |
| 262 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | |
| 262 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) | |
| 263 | 263 | ctx := context.Background() | |
| 264 | 264 | ||
| 265 | 265 | stackNode := stack(leaf("a"), leaf("b"), leaf("c")) | |
| @@ -293,7 +293,7 @@ func TestManagePanesUseCase_ConsumeOrExpel_Expel_StackRemainsWhenMoreThanTwo(t * | |||
| 293 | 293 | } | |
| 294 | 294 | ||
| 295 | 295 | func TestManagePanesUseCase_ConsumeOrExpel_NoSibling_ReturnsWarningAndNoop(t *testing.T) { | |
| 296 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | |
| 296 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) | |
| 297 | 297 | ctx := context.Background() | |
| 298 | 298 | ||
| 299 | 299 | left := leaf("left") | |
| @@ -317,7 +317,7 @@ func TestManagePanesUseCase_ConsumeOrExpel_NoSibling_ReturnsWarningAndNoop(t *te | |||
| 317 | 317 | } | |
| 318 | 318 | ||
| 319 | 319 | func TestManagePanesUseCase_ConsumeOrExpel_NestedSplits_ConsumesImmediateSiblingOnly(t *testing.T) { | |
| 320 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | |
| 320 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) | |
| 321 | 321 | ctx := context.Background() | |
| 322 | 322 | ||
| 323 | 323 | a := leaf("a") | |
| @@ -346,7 +346,7 @@ func TestManagePanesUseCase_ConsumeOrExpel_NestedSplits_ConsumesImmediateSibling | |||
| 346 | 346 | } | |
| 347 | 347 | ||
| 348 | 348 | func TestManagePanesUseCase_ConsumeOrExpel_OnlyOnePane(t *testing.T) { | |
| 349 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | |
| 349 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) | |
| 350 | 350 | ctx := context.Background() | |
| 351 | 351 | ||
| 352 | 352 | only := leaf("only") | |
| @@ -8,7 +8,7 @@ import ( | |||||
| 8 | ) | 8 | ) | ||
| 9 | 9 | ||||
| 10 | func TestManagePanesUseCase_ConsumeOrExpel_ConsumeLeft_CyclesHorizontalToVerticalThenStack(t *testing.T) { | 10 | func TestManagePanesUseCase_ConsumeOrExpel_ConsumeLeft_CyclesHorizontalToVerticalThenStack(t *testing.T) { | ||
| 11 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | 11 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) |
| 12 | ctx := context.Background() | 12 | ctx := context.Background() | ||
| 13 | 13 | ||||
| 14 | left := leaf("left") | 14 | left := leaf("left") | ||
| @@ -61,7 +61,7 @@ func TestManagePanesUseCase_ConsumeOrExpel_ConsumeLeft_CyclesHorizontalToVertica | |||||
| 61 | } | 61 | } | ||
| 62 | 62 | ||||
| 63 | func TestManagePanesUseCase_ConsumeOrExpel_ConsumeRight_CyclesHorizontalToVerticalThenStack(t *testing.T) { | 63 | func TestManagePanesUseCase_ConsumeOrExpel_ConsumeRight_CyclesHorizontalToVerticalThenStack(t *testing.T) { | ||
| 64 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | 64 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) |
| 65 | ctx := context.Background() | 65 | ctx := context.Background() | ||
| 66 | 66 | ||||
| 67 | left := leaf("left") | 67 | left := leaf("left") | ||
| @@ -114,7 +114,7 @@ func TestManagePanesUseCase_ConsumeOrExpel_ConsumeRight_CyclesHorizontalToVertic | |||||
| 114 | } | 114 | } | ||
| 115 | 115 | ||||
| 116 | func TestManagePanesUseCase_ConsumeOrExpel_ConsumeIntoExistingStack_AppendsToEnd(t *testing.T) { | 116 | func TestManagePanesUseCase_ConsumeOrExpel_ConsumeIntoExistingStack_AppendsToEnd(t *testing.T) { | ||
| 117 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | 117 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) |
| 118 | ctx := context.Background() | 118 | ctx := context.Background() | ||
| 119 | 119 | ||||
| 120 | stackNode := stack(leaf("a"), leaf("b")) | 120 | stackNode := stack(leaf("a"), leaf("b")) | ||
| @@ -141,7 +141,7 @@ func TestManagePanesUseCase_ConsumeOrExpel_ConsumeIntoExistingStack_AppendsToEnd | |||||
| 141 | } | 141 | } | ||
| 142 | 142 | ||||
| 143 | func TestManagePanesUseCase_ConsumeOrExpel_ConsumeLeft_InVerticalSplitWithStackedSibling_Noops(t *testing.T) { | 143 | func TestManagePanesUseCase_ConsumeOrExpel_ConsumeLeft_InVerticalSplitWithStackedSibling_Noops(t *testing.T) { | ||
| 144 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | 144 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) |
| 145 | ctx := context.Background() | 145 | ctx := context.Background() | ||
| 146 | 146 | ||||
| 147 | top := leaf("top") | 147 | top := leaf("top") | ||
| @@ -165,7 +165,7 @@ func TestManagePanesUseCase_ConsumeOrExpel_ConsumeLeft_InVerticalSplitWithStacke | |||||
| 165 | } | 165 | } | ||
| 166 | 166 | ||||
| 167 | func TestManagePanesUseCase_ConsumeOrExpel_Expel_DissolvesTwoPaneStack(t *testing.T) { | 167 | func TestManagePanesUseCase_ConsumeOrExpel_Expel_DissolvesTwoPaneStack(t *testing.T) { | ||
| 168 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | 168 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) |
| 169 | ctx := context.Background() | 169 | ctx := context.Background() | ||
| 170 | 170 | ||||
| 171 | stackNode := stack(leaf("a"), leaf("b")) | 171 | stackNode := stack(leaf("a"), leaf("b")) | ||
| @@ -200,7 +200,7 @@ func TestManagePanesUseCase_ConsumeOrExpel_Expel_DissolvesTwoPaneStack(t *testin | |||||
| 200 | } | 200 | } | ||
| 201 | 201 | ||||
| 202 | func TestManagePanesUseCase_ConsumeOrExpel_ExpelThenConsumeRight_CyclesVerticalToHorizontal(t *testing.T) { | 202 | func TestManagePanesUseCase_ConsumeOrExpel_ExpelThenConsumeRight_CyclesVerticalToHorizontal(t *testing.T) { | ||
| 203 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | 203 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) |
| 204 | ctx := context.Background() | 204 | ctx := context.Background() | ||
| 205 | 205 | ||||
| 206 | stackNode := stack(leaf("a"), leaf("b")) | 206 | stackNode := stack(leaf("a"), leaf("b")) | ||
| @@ -228,7 +228,7 @@ func TestManagePanesUseCase_ConsumeOrExpel_ExpelThenConsumeRight_CyclesVerticalT | |||||
| 228 | } | 228 | } | ||
| 229 | 229 | ||||
| 230 | func TestManagePanesUseCase_ConsumeOrExpel_ExpelThenConsumeLeft_ReturnsToStack(t *testing.T) { | 230 | func TestManagePanesUseCase_ConsumeOrExpel_ExpelThenConsumeLeft_ReturnsToStack(t *testing.T) { | ||
| 231 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | 231 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) |
| 232 | ctx := context.Background() | 232 | ctx := context.Background() | ||
| 233 | 233 | ||||
| 234 | stackNode := stack(leaf("a"), leaf("b")) | 234 | stackNode := stack(leaf("a"), leaf("b")) | ||
| @@ -259,7 +259,7 @@ func TestManagePanesUseCase_ConsumeOrExpel_ExpelThenConsumeLeft_ReturnsToStack(t | |||||
| 259 | } | 259 | } | ||
| 260 | 260 | ||||
| 261 | func TestManagePanesUseCase_ConsumeOrExpel_Expel_StackRemainsWhenMoreThanTwo(t *testing.T) { | 261 | func TestManagePanesUseCase_ConsumeOrExpel_Expel_StackRemainsWhenMoreThanTwo(t *testing.T) { | ||
| 262 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | 262 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) |
| 263 | ctx := context.Background() | 263 | ctx := context.Background() | ||
| 264 | 264 | ||||
| 265 | stackNode := stack(leaf("a"), leaf("b"), leaf("c")) | 265 | stackNode := stack(leaf("a"), leaf("b"), leaf("c")) | ||
| @@ -293,7 +293,7 @@ func TestManagePanesUseCase_ConsumeOrExpel_Expel_StackRemainsWhenMoreThanTwo(t * | |||||
| 293 | } | 293 | } | ||
| 294 | 294 | ||||
| 295 | func TestManagePanesUseCase_ConsumeOrExpel_NoSibling_ReturnsWarningAndNoop(t *testing.T) { | 295 | func TestManagePanesUseCase_ConsumeOrExpel_NoSibling_ReturnsWarningAndNoop(t *testing.T) { | ||
| 296 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | 296 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) |
| 297 | ctx := context.Background() | 297 | ctx := context.Background() | ||
| 298 | 298 | ||||
| 299 | left := leaf("left") | 299 | left := leaf("left") | ||
| @@ -317,7 +317,7 @@ func TestManagePanesUseCase_ConsumeOrExpel_NoSibling_ReturnsWarningAndNoop(t *te | |||||
| 317 | } | 317 | } | ||
| 318 | 318 | ||||
| 319 | func TestManagePanesUseCase_ConsumeOrExpel_NestedSplits_ConsumesImmediateSiblingOnly(t *testing.T) { | 319 | func TestManagePanesUseCase_ConsumeOrExpel_NestedSplits_ConsumesImmediateSiblingOnly(t *testing.T) { | ||
| 320 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | 320 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) |
| 321 | ctx := context.Background() | 321 | ctx := context.Background() | ||
| 322 | 322 | ||||
| 323 | a := leaf("a") | 323 | a := leaf("a") | ||
| @@ -346,7 +346,7 @@ func TestManagePanesUseCase_ConsumeOrExpel_NestedSplits_ConsumesImmediateSibling | |||||
| 346 | } | 346 | } | ||
| 347 | 347 | ||||
| 348 | func TestManagePanesUseCase_ConsumeOrExpel_OnlyOnePane(t *testing.T) { | 348 | func TestManagePanesUseCase_ConsumeOrExpel_OnlyOnePane(t *testing.T) { | ||
| 349 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | 349 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) |
| 350 | ctx := context.Background() | 350 | ctx := context.Background() | ||
| 351 | 351 | ||||
| 352 | only := leaf("only") | 352 | only := leaf("only") | ||
M internal/application/usecase/manage_panes_resize_test.go +5 -5
| @@ -9,7 +9,7 @@ import ( | |||
| 9 | 9 | ) | |
| 10 | 10 | ||
| 11 | 11 | func TestManagePanesUseCase_Resize_Errors(t *testing.T) { | |
| 12 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | |
| 12 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) | |
| 13 | 13 | ||
| 14 | 14 | ctx := context.Background() | |
| 15 | 15 | ||
| @@ -33,7 +33,7 @@ func TestManagePanesUseCase_Resize_Errors(t *testing.T) { | |||
| 33 | 33 | } | |
| 34 | 34 | ||
| 35 | 35 | func TestManagePanesUseCase_Resize_VerticalDividerMove(t *testing.T) { | |
| 36 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | |
| 36 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) | |
| 37 | 37 | ctx := context.Background() | |
| 38 | 38 | ||
| 39 | 39 | top := &entity.PaneNode{ID: "top", Pane: &entity.Pane{ID: "top"}} | |
| @@ -67,7 +67,7 @@ func TestManagePanesUseCase_Resize_VerticalDividerMove(t *testing.T) { | |||
| 67 | 67 | } | |
| 68 | 68 | ||
| 69 | 69 | func TestManagePanesUseCase_Resize_SmartResizeGrowsAndShrinksActivePane(t *testing.T) { | |
| 70 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | |
| 70 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) | |
| 71 | 71 | ctx := context.Background() | |
| 72 | 72 | ||
| 73 | 73 | top := &entity.PaneNode{ID: "top", Pane: &entity.Pane{ID: "top"}} | |
| @@ -101,7 +101,7 @@ func TestManagePanesUseCase_Resize_SmartResizeGrowsAndShrinksActivePane(t *testi | |||
| 101 | 101 | } | |
| 102 | 102 | ||
| 103 | 103 | func TestManagePanesUseCase_Resize_ClampsToMinPanePercent(t *testing.T) { | |
| 104 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | |
| 104 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) | |
| 105 | 105 | ctx := context.Background() | |
| 106 | 106 | ||
| 107 | 107 | left := &entity.PaneNode{ID: "left", Pane: &entity.Pane{ID: "left"}} | |
| @@ -127,7 +127,7 @@ func TestManagePanesUseCase_Resize_ClampsToMinPanePercent(t *testing.T) { | |||
| 127 | 127 | } | |
| 128 | 128 | ||
| 129 | 129 | func TestManagePanesUseCase_Resize_TargetsStackContainer(t *testing.T) { | |
| 130 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | |
| 130 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) | |
| 131 | 131 | ctx := context.Background() | |
| 132 | 132 | ||
| 133 | 133 | stackLeaf1 := &entity.PaneNode{ID: "s1", Pane: &entity.Pane{ID: "s1"}} | |
| @@ -9,7 +9,7 @@ import ( | |||||
| 9 | ) | 9 | ) | ||
| 10 | 10 | ||||
| 11 | func TestManagePanesUseCase_Resize_Errors(t *testing.T) { | 11 | func TestManagePanesUseCase_Resize_Errors(t *testing.T) { | ||
| 12 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | 12 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) |
| 13 | 13 | ||||
| 14 | ctx := context.Background() | 14 | ctx := context.Background() | ||
| 15 | 15 | ||||
| @@ -33,7 +33,7 @@ func TestManagePanesUseCase_Resize_Errors(t *testing.T) { | |||||
| 33 | } | 33 | } | ||
| 34 | 34 | ||||
| 35 | func TestManagePanesUseCase_Resize_VerticalDividerMove(t *testing.T) { | 35 | func TestManagePanesUseCase_Resize_VerticalDividerMove(t *testing.T) { | ||
| 36 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | 36 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) |
| 37 | ctx := context.Background() | 37 | ctx := context.Background() | ||
| 38 | 38 | ||||
| 39 | top := &entity.PaneNode{ID: "top", Pane: &entity.Pane{ID: "top"}} | 39 | top := &entity.PaneNode{ID: "top", Pane: &entity.Pane{ID: "top"}} | ||
| @@ -67,7 +67,7 @@ func TestManagePanesUseCase_Resize_VerticalDividerMove(t *testing.T) { | |||||
| 67 | } | 67 | } | ||
| 68 | 68 | ||||
| 69 | func TestManagePanesUseCase_Resize_SmartResizeGrowsAndShrinksActivePane(t *testing.T) { | 69 | func TestManagePanesUseCase_Resize_SmartResizeGrowsAndShrinksActivePane(t *testing.T) { | ||
| 70 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | 70 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) |
| 71 | ctx := context.Background() | 71 | ctx := context.Background() | ||
| 72 | 72 | ||||
| 73 | top := &entity.PaneNode{ID: "top", Pane: &entity.Pane{ID: "top"}} | 73 | top := &entity.PaneNode{ID: "top", Pane: &entity.Pane{ID: "top"}} | ||
| @@ -101,7 +101,7 @@ func TestManagePanesUseCase_Resize_SmartResizeGrowsAndShrinksActivePane(t *testi | |||||
| 101 | } | 101 | } | ||
| 102 | 102 | ||||
| 103 | func TestManagePanesUseCase_Resize_ClampsToMinPanePercent(t *testing.T) { | 103 | func TestManagePanesUseCase_Resize_ClampsToMinPanePercent(t *testing.T) { | ||
| 104 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | 104 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) |
| 105 | ctx := context.Background() | 105 | ctx := context.Background() | ||
| 106 | 106 | ||||
| 107 | left := &entity.PaneNode{ID: "left", Pane: &entity.Pane{ID: "left"}} | 107 | left := &entity.PaneNode{ID: "left", Pane: &entity.Pane{ID: "left"}} | ||
| @@ -127,7 +127,7 @@ func TestManagePanesUseCase_Resize_ClampsToMinPanePercent(t *testing.T) { | |||||
| 127 | } | 127 | } | ||
| 128 | 128 | ||||
| 129 | func TestManagePanesUseCase_Resize_TargetsStackContainer(t *testing.T) { | 129 | func TestManagePanesUseCase_Resize_TargetsStackContainer(t *testing.T) { | ||
| 130 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | 130 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) |
| 131 | ctx := context.Background() | 131 | ctx := context.Background() | ||
| 132 | 132 | ||||
| 133 | stackLeaf1 := &entity.PaneNode{ID: "s1", Pane: &entity.Pane{ID: "s1"}} | 133 | stackLeaf1 := &entity.PaneNode{ID: "s1", Pane: &entity.Pane{ID: "s1"}} | ||
M internal/application/usecase/manage_panes_split_test.go +2 -2
| @@ -9,7 +9,7 @@ import ( | |||
| 9 | 9 | ) | |
| 10 | 10 | ||
| 11 | 11 | func TestManagePanesUseCase_Split_ValidDirections(t *testing.T) { | |
| 12 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | |
| 12 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) | |
| 13 | 13 | ctx := context.Background() | |
| 14 | 14 | ||
| 15 | 15 | dirs := []SplitDirection{SplitLeft, SplitRight, SplitUp, SplitDown} | |
| @@ -51,7 +51,7 @@ func TestManagePanesUseCase_Split_ValidDirections(t *testing.T) { | |||
| 51 | 51 | } | |
| 52 | 52 | ||
| 53 | 53 | func TestManagePanesUseCase_Split_InvalidDirection_ReturnsError(t *testing.T) { | |
| 54 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | |
| 54 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) | |
| 55 | 55 | ctx := context.Background() | |
| 56 | 56 | ||
| 57 | 57 | target := leaf("target") | |
| @@ -9,7 +9,7 @@ import ( | |||||
| 9 | ) | 9 | ) | ||
| 10 | 10 | ||||
| 11 | func TestManagePanesUseCase_Split_ValidDirections(t *testing.T) { | 11 | func TestManagePanesUseCase_Split_ValidDirections(t *testing.T) { | ||
| 12 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | 12 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) |
| 13 | ctx := context.Background() | 13 | ctx := context.Background() | ||
| 14 | 14 | ||||
| 15 | dirs := []SplitDirection{SplitLeft, SplitRight, SplitUp, SplitDown} | 15 | dirs := []SplitDirection{SplitLeft, SplitRight, SplitUp, SplitDown} | ||
| @@ -51,7 +51,7 @@ func TestManagePanesUseCase_Split_ValidDirections(t *testing.T) { | |||||
| 51 | } | 51 | } | ||
| 52 | 52 | ||||
| 53 | func TestManagePanesUseCase_Split_InvalidDirection_ReturnsError(t *testing.T) { | 53 | func TestManagePanesUseCase_Split_InvalidDirection_ReturnsError(t *testing.T) { | ||
| 54 | - | uc := NewManagePanesUseCase(func() string { return "id" }) | 54 | + | uc := NewManagePanesUseCase(func() string { return "id" }, nil) |
| 55 | ctx := context.Background() | 55 | ctx := context.Background() | ||
| 56 | 56 | ||||
| 57 | target := leaf("target") | 57 | target := leaf("target") | ||
M internal/application/usecase/manage_tabs.go +3 -17
| @@ -18,26 +18,12 @@ type ManageTabsUseCase struct { | |||
| 18 | 18 | normalizer *NavigationURLNormalizer | |
| 19 | 19 | } | |
| 20 | 20 | ||
| 21 | - | // ManageTabsOption configures ManageTabsUseCase. | |
| 22 | - | type ManageTabsOption func(*ManageTabsUseCase) | |
| 23 | - | ||
| 24 | - | // WithManageTabsLocalPathResolver configures local path resolution for initial navigation URLs. | |
| 25 | - | func WithManageTabsLocalPathResolver(resolver port.LocalPathResolver) ManageTabsOption { | |
| 26 | - | return func(uc *ManageTabsUseCase) { | |
| 27 | - | uc.normalizer = NewNavigationURLNormalizer(resolver) | |
| 28 | - | } | |
| 29 | - | } | |
| 30 | - | ||
| 31 | 21 | // NewManageTabsUseCase creates a new tab management use case. | |
| 32 | - | func NewManageTabsUseCase(idGenerator IDGenerator, opts ...ManageTabsOption) *ManageTabsUseCase { | |
| 33 | - | uc := &ManageTabsUseCase{ | |
| 22 | + | func NewManageTabsUseCase(idGenerator IDGenerator, localPaths port.LocalPathResolver) *ManageTabsUseCase { | |
| 23 | + | return &ManageTabsUseCase{ | |
| 34 | 24 | idGenerator: idGenerator, | |
| 35 | - | normalizer: NewNavigationURLNormalizer(nil), | |
| 36 | - | } | |
| 37 | - | for _, opt := range opts { | |
| 38 | - | opt(uc) | |
| 25 | + | normalizer: NewNavigationURLNormalizer(localPaths), | |
| 39 | 26 | } | |
| 40 | - | return uc | |
| 41 | 27 | } | |
| 42 | 28 | ||
| 43 | 29 | // CreateTabInput contains parameters for creating a new tab. | |
| @@ -18,26 +18,12 @@ type ManageTabsUseCase struct { | |||||
| 18 | normalizer *NavigationURLNormalizer | 18 | normalizer *NavigationURLNormalizer | ||
| 19 | } | 19 | } | ||
| 20 | 20 | ||||
| 21 | - | // ManageTabsOption configures ManageTabsUseCase. | |||
| 22 | - | type ManageTabsOption func(*ManageTabsUseCase) | |||
| 23 | - | ||||
| 24 | - | // WithManageTabsLocalPathResolver configures local path resolution for initial navigation URLs. | |||
| 25 | - | func WithManageTabsLocalPathResolver(resolver port.LocalPathResolver) ManageTabsOption { | |||
| 26 | - | return func(uc *ManageTabsUseCase) { | |||
| 27 | - | uc.normalizer = NewNavigationURLNormalizer(resolver) | |||
| 28 | - | } | |||
| 29 | - | } | |||
| 30 | - | ||||
| 31 | // NewManageTabsUseCase creates a new tab management use case. | 21 | // NewManageTabsUseCase creates a new tab management use case. | ||
| 32 | - | func NewManageTabsUseCase(idGenerator IDGenerator, opts ...ManageTabsOption) *ManageTabsUseCase { | 22 | + | func NewManageTabsUseCase(idGenerator IDGenerator, localPaths port.LocalPathResolver) *ManageTabsUseCase { |
| 33 | - | uc := &ManageTabsUseCase{ | 23 | + | return &ManageTabsUseCase{ |
| 34 | idGenerator: idGenerator, | 24 | idGenerator: idGenerator, | ||
| 35 | - | normalizer: NewNavigationURLNormalizer(nil), | 25 | + | normalizer: NewNavigationURLNormalizer(localPaths), |
| 36 | - | } | |||
| 37 | - | for _, opt := range opts { | |||
| 38 | - | opt(uc) | |||
| 39 | } | 26 | } | ||
| 40 | - | return uc | |||
| 41 | } | 27 | } | ||
| 42 | 28 | ||||
| 43 | // CreateTabInput contains parameters for creating a new tab. | 29 | // CreateTabInput contains parameters for creating a new tab. | ||
M internal/application/usecase/navigation_url_usecase_integration_test.go +3 -3
| @@ -14,7 +14,7 @@ func TestManageTabsCreateResolvesExistingLocalInitialURL(t *testing.T) { | |||
| 14 | 14 | tabsFile := filepath.Join(string(filepath.Separator), "tmp", "tabs.html") | |
| 15 | 15 | uc := NewManageTabsUseCase( | |
| 16 | 16 | sequentialIDGenerator("tab", "workspace", "pane"), | |
| 17 | - | WithManageTabsLocalPathResolver(fakeLocalPathResolver{paths: map[string]string{"tabs.html": tabsFile}}), | |
| 17 | + | fakeLocalPathResolver{paths: map[string]string{"tabs.html": tabsFile}}, | |
| 18 | 18 | ) | |
| 19 | 19 | ||
| 20 | 20 | out, err := uc.Create(ctx, CreateTabInput{TabList: tabs, InitialURL: "tabs.html"}) | |
| @@ -32,7 +32,7 @@ func TestManageTabsCreateWithPaneResolvesExistingLocalInitialURL(t *testing.T) { | |||
| 32 | 32 | tabsFile := filepath.Join(string(filepath.Separator), "tmp", "provided.html") | |
| 33 | 33 | uc := NewManageTabsUseCase( | |
| 34 | 34 | sequentialIDGenerator("tab", "workspace"), | |
| 35 | - | WithManageTabsLocalPathResolver(fakeLocalPathResolver{paths: map[string]string{"provided.html": tabsFile}}), | |
| 35 | + | fakeLocalPathResolver{paths: map[string]string{"provided.html": tabsFile}}, | |
| 36 | 36 | ) | |
| 37 | 37 | pane := entity.NewPane("provided-pane") | |
| 38 | 38 | ||
| @@ -50,7 +50,7 @@ func TestManagePanesSplitResolvesExistingLocalInitialURL(t *testing.T) { | |||
| 50 | 50 | paneFile := filepath.Join(string(filepath.Separator), "tmp", "split.html") | |
| 51 | 51 | uc := NewManagePanesUseCase( | |
| 52 | 52 | sequentialIDGenerator("new-pane", "parent"), | |
| 53 | - | WithManagePanesLocalPathResolver(fakeLocalPathResolver{paths: map[string]string{"split.html": paneFile}}), | |
| 53 | + | fakeLocalPathResolver{paths: map[string]string{"split.html": paneFile}}, | |
| 54 | 54 | ) | |
| 55 | 55 | active := entity.NewPane("active-pane") | |
| 56 | 56 | activeNode := &entity.PaneNode{ID: "active-node", Pane: active} | |
| @@ -14,7 +14,7 @@ func TestManageTabsCreateResolvesExistingLocalInitialURL(t *testing.T) { | |||||
| 14 | tabsFile := filepath.Join(string(filepath.Separator), "tmp", "tabs.html") | 14 | tabsFile := filepath.Join(string(filepath.Separator), "tmp", "tabs.html") | ||
| 15 | uc := NewManageTabsUseCase( | 15 | uc := NewManageTabsUseCase( | ||
| 16 | sequentialIDGenerator("tab", "workspace", "pane"), | 16 | sequentialIDGenerator("tab", "workspace", "pane"), | ||
| 17 | - | WithManageTabsLocalPathResolver(fakeLocalPathResolver{paths: map[string]string{"tabs.html": tabsFile}}), | 17 | + | fakeLocalPathResolver{paths: map[string]string{"tabs.html": tabsFile}}, |
| 18 | ) | 18 | ) | ||
| 19 | 19 | ||||
| 20 | out, err := uc.Create(ctx, CreateTabInput{TabList: tabs, InitialURL: "tabs.html"}) | 20 | out, err := uc.Create(ctx, CreateTabInput{TabList: tabs, InitialURL: "tabs.html"}) | ||
| @@ -32,7 +32,7 @@ func TestManageTabsCreateWithPaneResolvesExistingLocalInitialURL(t *testing.T) { | |||||
| 32 | tabsFile := filepath.Join(string(filepath.Separator), "tmp", "provided.html") | 32 | tabsFile := filepath.Join(string(filepath.Separator), "tmp", "provided.html") | ||
| 33 | uc := NewManageTabsUseCase( | 33 | uc := NewManageTabsUseCase( | ||
| 34 | sequentialIDGenerator("tab", "workspace"), | 34 | sequentialIDGenerator("tab", "workspace"), | ||
| 35 | - | WithManageTabsLocalPathResolver(fakeLocalPathResolver{paths: map[string]string{"provided.html": tabsFile}}), | 35 | + | fakeLocalPathResolver{paths: map[string]string{"provided.html": tabsFile}}, |
| 36 | ) | 36 | ) | ||
| 37 | pane := entity.NewPane("provided-pane") | 37 | pane := entity.NewPane("provided-pane") | ||
| 38 | 38 | ||||
| @@ -50,7 +50,7 @@ func TestManagePanesSplitResolvesExistingLocalInitialURL(t *testing.T) { | |||||
| 50 | paneFile := filepath.Join(string(filepath.Separator), "tmp", "split.html") | 50 | paneFile := filepath.Join(string(filepath.Separator), "tmp", "split.html") | ||
| 51 | uc := NewManagePanesUseCase( | 51 | uc := NewManagePanesUseCase( | ||
| 52 | sequentialIDGenerator("new-pane", "parent"), | 52 | sequentialIDGenerator("new-pane", "parent"), | ||
| 53 | - | WithManagePanesLocalPathResolver(fakeLocalPathResolver{paths: map[string]string{"split.html": paneFile}}), | 53 | + | fakeLocalPathResolver{paths: map[string]string{"split.html": paneFile}}, |
| 54 | ) | 54 | ) | ||
| 55 | active := entity.NewPane("active-pane") | 55 | active := entity.NewPane("active-pane") | ||
| 56 | activeNode := &entity.PaneNode{ID: "active-node", Pane: active} | 56 | activeNode := &entity.PaneNode{ID: "active-node", Pane: active} | ||
M internal/infrastructure/config/validation.go +25 -4
| @@ -446,7 +446,13 @@ func validateWorkspaceNewPaneURL(config *Config) []string { | |||
| 446 | 446 | ||
| 447 | 447 | func validateWorkspaceURLValue(fieldPath, value string) []string { | |
| 448 | 448 | trimmed := strings.TrimSpace(value) | |
| 449 | - | if isLocalPathLikeWorkspaceURL(trimmed) || isExistingRelativeWorkspacePath(trimmed) { | |
| 449 | + | if isLocalPathLikeWorkspaceURL(trimmed) { | |
| 450 | + | if isExistingWorkspacePath(trimmed) { | |
| 451 | + | return nil | |
| 452 | + | } | |
| 453 | + | return []string{fmt.Sprintf("%s local path must exist (got: %s)", fieldPath, value)} | |
| 454 | + | } | |
| 455 | + | if isExistingRelativeWorkspacePath(trimmed) { | |
| 450 | 456 | return nil | |
| 451 | 457 | } | |
| 452 | 458 | candidate := trimmed | |
| @@ -478,11 +484,19 @@ func isLocalPathLikeWorkspaceURL(value string) bool { | |||
| 478 | 484 | strings.HasPrefix(value, "~/") | |
| 479 | 485 | } | |
| 480 | 486 | ||
| 481 | - | func isExistingRelativeWorkspacePath(value string) bool { | |
| 482 | - | if value == "" || strings.Contains(value, "://") || filepath.IsAbs(value) { | |
| 487 | + | func isExistingWorkspacePath(value string) bool { | |
| 488 | + | if value == "" || strings.Contains(value, "://") { | |
| 483 | 489 | return false | |
| 484 | 490 | } | |
| 485 | - | absPath, err := filepath.Abs(value) | |
| 491 | + | candidate := value | |
| 492 | + | if strings.HasPrefix(candidate, "~/") { | |
| 493 | + | homeDir, err := os.UserHomeDir() | |
| 494 | + | if err != nil || homeDir == "" { | |
| 495 | + | return false | |
| 496 | + | } | |
| 497 | + | candidate = filepath.Join(homeDir, strings.TrimPrefix(candidate, "~/")) | |
| 498 | + | } | |
| 499 | + | absPath, err := filepath.Abs(candidate) | |
| 486 | 500 | if err != nil { | |
| 487 | 501 | return false | |
| 488 | 502 | } | |
| @@ -490,6 +504,13 @@ func isExistingRelativeWorkspacePath(value string) bool { | |||
| 490 | 504 | return err == nil | |
| 491 | 505 | } | |
| 492 | 506 | ||
| 507 | + | func isExistingRelativeWorkspacePath(value string) bool { | |
| 508 | + | if value == "" || strings.Contains(value, "://") || filepath.IsAbs(value) { | |
| 509 | + | return false | |
| 510 | + | } | |
| 511 | + | return isExistingWorkspacePath(value) | |
| 512 | + | } | |
| 513 | + | ||
| 493 | 514 | func validateOmnibox(config *Config) []string { | |
| 494 | 515 | var validationErrors []string | |
| 495 | 516 | if config.Omnibox.MostVisitedDays < 0 { | |
| @@ -446,7 +446,13 @@ func validateWorkspaceNewPaneURL(config *Config) []string { | |||||
| 446 | 446 | ||||
| 447 | func validateWorkspaceURLValue(fieldPath, value string) []string { | 447 | func validateWorkspaceURLValue(fieldPath, value string) []string { | ||
| 448 | trimmed := strings.TrimSpace(value) | 448 | trimmed := strings.TrimSpace(value) | ||
| 449 | - | if isLocalPathLikeWorkspaceURL(trimmed) || isExistingRelativeWorkspacePath(trimmed) { | 449 | + | if isLocalPathLikeWorkspaceURL(trimmed) { |
| 450 | + | if isExistingWorkspacePath(trimmed) { | |||
| 451 | + | return nil | |||
| 452 | + | } | |||
| 453 | + | return []string{fmt.Sprintf("%s local path must exist (got: %s)", fieldPath, value)} | |||
| 454 | + | } | |||
| 455 | + | if isExistingRelativeWorkspacePath(trimmed) { | |||
| 450 | return nil | 456 | return nil | ||
| 451 | } | 457 | } | ||
| 452 | candidate := trimmed | 458 | candidate := trimmed | ||
| @@ -478,11 +484,19 @@ func isLocalPathLikeWorkspaceURL(value string) bool { | |||||
| 478 | strings.HasPrefix(value, "~/") | 484 | strings.HasPrefix(value, "~/") | ||
| 479 | } | 485 | } | ||
| 480 | 486 | ||||
| 481 | - | func isExistingRelativeWorkspacePath(value string) bool { | 487 | + | func isExistingWorkspacePath(value string) bool { |
| 482 | - | if value == "" || strings.Contains(value, "://") || filepath.IsAbs(value) { | 488 | + | if value == "" || strings.Contains(value, "://") { |
| 483 | return false | 489 | return false | ||
| 484 | } | 490 | } | ||
| 485 | - | absPath, err := filepath.Abs(value) | 491 | + | candidate := value |
| 492 | + | if strings.HasPrefix(candidate, "~/") { | |||
| 493 | + | homeDir, err := os.UserHomeDir() | |||
| 494 | + | if err != nil || homeDir == "" { | |||
| 495 | + | return false | |||
| 496 | + | } | |||
| 497 | + | candidate = filepath.Join(homeDir, strings.TrimPrefix(candidate, "~/")) | |||
| 498 | + | } | |||
| 499 | + | absPath, err := filepath.Abs(candidate) | |||
| 486 | if err != nil { | 500 | if err != nil { | ||
| 487 | return false | 501 | return false | ||
| 488 | } | 502 | } | ||
| @@ -490,6 +504,13 @@ func isExistingRelativeWorkspacePath(value string) bool { | |||||
| 490 | return err == nil | 504 | return err == nil | ||
| 491 | } | 505 | } | ||
| 492 | 506 | ||||
| 507 | + | func isExistingRelativeWorkspacePath(value string) bool { | |||
| 508 | + | if value == "" || strings.Contains(value, "://") || filepath.IsAbs(value) { | |||
| 509 | + | return false | |||
| 510 | + | } | |||
| 511 | + | return isExistingWorkspacePath(value) | |||
| 512 | + | } | |||
| 513 | + | ||||
| 493 | func validateOmnibox(config *Config) []string { | 514 | func validateOmnibox(config *Config) []string { | ||
| 494 | var validationErrors []string | 515 | var validationErrors []string | ||
| 495 | if config.Omnibox.MostVisitedDays < 0 { | 516 | if config.Omnibox.MostVisitedDays < 0 { | ||
M internal/infrastructure/config/validation_test.go +26 -2
| @@ -3,6 +3,7 @@ package config | |||
| 3 | 3 | import ( | |
| 4 | 4 | "math" | |
| 5 | 5 | "os" | |
| 6 | + | "path/filepath" | |
| 6 | 7 | "testing" | |
| 7 | 8 | ||
| 8 | 9 | "github.com/stretchr/testify/assert" | |
| @@ -293,8 +294,18 @@ func TestValidateConfig_CEFConfig(t *testing.T) { | |||
| 293 | 294 | } | |
| 294 | 295 | } | |
| 295 | 296 | ||
| 296 | - | func TestValidateConfig_WorkspaceNewPaneURLAllowsLocalPathLikeValues(t *testing.T) { | |
| 297 | - | for _, value := range []string{"/tmp/page.html", "./page.html", "../page.html", "~/page.html"} { | |
| 297 | + | func TestValidateConfig_WorkspaceNewPaneURLAllowsExistingLocalPathLikeValues(t *testing.T) { | |
| 298 | + | tmpDir := t.TempDir() | |
| 299 | + | oldWD, err := os.Getwd() | |
| 300 | + | require.NoError(t, err) | |
| 301 | + | childDir := filepath.Join(tmpDir, "child") | |
| 302 | + | require.NoError(t, os.Mkdir(childDir, 0755)) | |
| 303 | + | require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "page.html"), []byte("ok"), 0644)) | |
| 304 | + | require.NoError(t, os.WriteFile(filepath.Join(childDir, "page.html"), []byte("ok"), 0644)) | |
| 305 | + | require.NoError(t, os.Chdir(childDir)) | |
| 306 | + | t.Cleanup(func() { _ = os.Chdir(oldWD) }) | |
| 307 | + | ||
| 308 | + | for _, value := range []string{filepath.Join(tmpDir, "page.html"), "./page.html", "../page.html"} { | |
| 298 | 309 | t.Run(value, func(t *testing.T) { | |
| 299 | 310 | cfg := DefaultConfig() | |
| 300 | 311 | cfg.Workspace.NewPaneURL = value | |
| @@ -305,6 +316,19 @@ func TestValidateConfig_WorkspaceNewPaneURLAllowsLocalPathLikeValues(t *testing. | |||
| 305 | 316 | } | |
| 306 | 317 | } | |
| 307 | 318 | ||
| 319 | + | func TestValidateConfig_WorkspaceNewPaneURLRejectsMissingLocalPathLikeValues(t *testing.T) { | |
| 320 | + | for _, value := range []string{"/missing/dumber/page.html", "./missing.html", "../missing.html", "~/missing-dumber-page.html"} { | |
| 321 | + | t.Run(value, func(t *testing.T) { | |
| 322 | + | cfg := DefaultConfig() | |
| 323 | + | cfg.Workspace.NewPaneURL = value | |
| 324 | + | ||
| 325 | + | err := validateConfig(cfg) | |
| 326 | + | require.Error(t, err) | |
| 327 | + | assert.Contains(t, err.Error(), "workspace.new_pane_url local path must exist") | |
| 328 | + | }) | |
| 329 | + | } | |
| 330 | + | } | |
| 331 | + | ||
| 308 | 332 | func TestValidateConfig_WorkspaceNewPaneURLAllowsExistingBareRelativeFile(t *testing.T) { | |
| 309 | 333 | tmpDir := t.TempDir() | |
| 310 | 334 | oldWD, err := os.Getwd() | |
| @@ -3,6 +3,7 @@ package config | |||||
| 3 | import ( | 3 | import ( | ||
| 4 | "math" | 4 | "math" | ||
| 5 | "os" | 5 | "os" | ||
| 6 | + | "path/filepath" | |||
| 6 | "testing" | 7 | "testing" | ||
| 7 | 8 | ||||
| 8 | "github.com/stretchr/testify/assert" | 9 | "github.com/stretchr/testify/assert" | ||
| @@ -293,8 +294,18 @@ func TestValidateConfig_CEFConfig(t *testing.T) { | |||||
| 293 | } | 294 | } | ||
| 294 | } | 295 | } | ||
| 295 | 296 | ||||
| 296 | - | func TestValidateConfig_WorkspaceNewPaneURLAllowsLocalPathLikeValues(t *testing.T) { | 297 | + | func TestValidateConfig_WorkspaceNewPaneURLAllowsExistingLocalPathLikeValues(t *testing.T) { |
| 297 | - | for _, value := range []string{"/tmp/page.html", "./page.html", "../page.html", "~/page.html"} { | 298 | + | tmpDir := t.TempDir() |
| 299 | + | oldWD, err := os.Getwd() | |||
| 300 | + | require.NoError(t, err) | |||
| 301 | + | childDir := filepath.Join(tmpDir, "child") | |||
| 302 | + | require.NoError(t, os.Mkdir(childDir, 0755)) | |||
| 303 | + | require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "page.html"), []byte("ok"), 0644)) | |||
| 304 | + | require.NoError(t, os.WriteFile(filepath.Join(childDir, "page.html"), []byte("ok"), 0644)) | |||
| 305 | + | require.NoError(t, os.Chdir(childDir)) | |||
| 306 | + | t.Cleanup(func() { _ = os.Chdir(oldWD) }) | |||
| 307 | + | ||||
| 308 | + | for _, value := range []string{filepath.Join(tmpDir, "page.html"), "./page.html", "../page.html"} { | |||
| 298 | t.Run(value, func(t *testing.T) { | 309 | t.Run(value, func(t *testing.T) { | ||
| 299 | cfg := DefaultConfig() | 310 | cfg := DefaultConfig() | ||
| 300 | cfg.Workspace.NewPaneURL = value | 311 | cfg.Workspace.NewPaneURL = value | ||
| @@ -305,6 +316,19 @@ func TestValidateConfig_WorkspaceNewPaneURLAllowsLocalPathLikeValues(t *testing. | |||||
| 305 | } | 316 | } | ||
| 306 | } | 317 | } | ||
| 307 | 318 | ||||
| 319 | + | func TestValidateConfig_WorkspaceNewPaneURLRejectsMissingLocalPathLikeValues(t *testing.T) { | |||
| 320 | + | for _, value := range []string{"/missing/dumber/page.html", "./missing.html", "../missing.html", "~/missing-dumber-page.html"} { | |||
| 321 | + | t.Run(value, func(t *testing.T) { | |||
| 322 | + | cfg := DefaultConfig() | |||
| 323 | + | cfg.Workspace.NewPaneURL = value | |||
| 324 | + | ||||
| 325 | + | err := validateConfig(cfg) | |||
| 326 | + | require.Error(t, err) | |||
| 327 | + | assert.Contains(t, err.Error(), "workspace.new_pane_url local path must exist") | |||
| 328 | + | }) | |||
| 329 | + | } | |||
| 330 | + | } | |||
| 331 | + | ||||
| 308 | func TestValidateConfig_WorkspaceNewPaneURLAllowsExistingBareRelativeFile(t *testing.T) { | 332 | func TestValidateConfig_WorkspaceNewPaneURLAllowsExistingBareRelativeFile(t *testing.T) { | ||
| 309 | tmpDir := t.TempDir() | 333 | tmpDir := t.TempDir() | ||
| 310 | oldWD, err := os.Getwd() | 334 | oldWD, err := os.Getwd() | ||
M internal/ui/app_browser_launch_test.go +12 -12
| @@ -1104,7 +1104,7 @@ func TestApp_OpenFreshWindowRecordsTabOwnership(t *testing.T) { | |||
| 1104 | 1104 | created := &browserWindow{id: "window-1", tabs: entity.NewTabList()} | |
| 1105 | 1105 | app := &App{ | |
| 1106 | 1106 | tabs: entity.NewTabList(), | |
| 1107 | - | tabsUC: usecase.NewManageTabsUseCase(func() string { return "id-1" }), | |
| 1107 | + | tabsUC: usecase.NewManageTabsUseCase(func() string { return "id-1" }, nil), | |
| 1108 | 1108 | browserWindows: map[string]*browserWindow{existingWindow.id: existingWindow}, | |
| 1109 | 1109 | windowForTab: map[entity.TabID]*browserWindow{existingTab.ID: existingWindow}, | |
| 1110 | 1110 | workspaceViews: map[entity.TabID]*component.WorkspaceView{entity.TabID("id-1"): &component.WorkspaceView{}}, | |
| @@ -1167,7 +1167,7 @@ func TestApp_CreateInitialTabNoFallbackAfterEmptyWindowRestore(t *testing.T) { | |||
| 1167 | 1167 | ctx := context.Background() | |
| 1168 | 1168 | sessionID := entity.SessionID("session-empty-windows-fallback") | |
| 1169 | 1169 | state := entity.SnapshotFromWindowTabLists(sessionID, []entity.WindowTabListState{}, 0, time.Unix(123, 0)) | |
| 1170 | - | tabsUC := usecase.NewManageTabsUseCase(func() string { return "fallback-tab" }) | |
| 1170 | + | tabsUC := usecase.NewManageTabsUseCase(func() string { return "fallback-tab" }, nil) | |
| 1171 | 1171 | windowTabs := entity.NewTabList() | |
| 1172 | 1172 | bw := &browserWindow{id: "window-1", tabs: windowTabs} | |
| 1173 | 1173 | app := &App{ | |
| @@ -1361,7 +1361,7 @@ func TestApp_OpenFreshWindowRollsBackOnTabCreationFailure(t *testing.T) { | |||
| 1361 | 1361 | ||
| 1362 | 1362 | app := &App{ | |
| 1363 | 1363 | tabs: entity.NewTabList(), | |
| 1364 | - | tabsUC: usecase.NewManageTabsUseCase(func() string { return "id-1" }), | |
| 1364 | + | tabsUC: usecase.NewManageTabsUseCase(func() string { return "id-1" }, nil), | |
| 1365 | 1365 | browserWindows: map[string]*browserWindow{existingWindow.id: existingWindow}, | |
| 1366 | 1366 | windowForTab: map[entity.TabID]*browserWindow{existingTab.ID: existingWindow}, | |
| 1367 | 1367 | browserWindowFactory: func(context.Context, string) (*browserWindow, error) { | |
| @@ -1403,7 +1403,7 @@ func TestApp_OpenFreshWindowTargetsNewWindowTabBar(t *testing.T) { | |||
| 1403 | 1403 | setWindowTabBar(t, oldWindow, newTestTabBarShell(t)) | |
| 1404 | 1404 | setWindowTabBar(t, newWindow, newTestTabBarShell(t, createdTabID)) | |
| 1405 | 1405 | tabs := entity.NewTabList() | |
| 1406 | - | tabsUC := usecase.NewManageTabsUseCase(func() string { return string(createdTabID) }) | |
| 1406 | + | tabsUC := usecase.NewManageTabsUseCase(func() string { return string(createdTabID) }, nil) | |
| 1407 | 1407 | ||
| 1408 | 1408 | existingTab := entity.NewTab(existingTabID, entity.WorkspaceID("existing-workspace"), entity.NewPane(entity.PaneID("existing-pane"))) | |
| 1409 | 1409 | existingTabs := entity.NewTabList() | |
| @@ -2280,7 +2280,7 @@ func TestApp_CreatePopupTabUsesParentPaneOwnerWhenFocusIsStale(t *testing.T) { | |||
| 2280 | 2280 | deps: &Dependencies{}, | |
| 2281 | 2281 | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), | |
| 2282 | 2282 | tabs: globalTabs, | |
| 2283 | - | tabsUC: usecase.NewManageTabsUseCase(func() string { return "popup-tab" }), | |
| 2283 | + | tabsUC: usecase.NewManageTabsUseCase(func() string { return "popup-tab" }, nil), | |
| 2284 | 2284 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | |
| 2285 | 2285 | windowForTab: map[entity.TabID]*browserWindow{firstTab.ID: first, secondTab.ID: second}, | |
| 2286 | 2286 | workspaceViews: make(map[entity.TabID]*component.WorkspaceView), | |
| @@ -2341,7 +2341,7 @@ func TestApp_TabCoordinatorCallbacksUseTargetWindowWhenFocusIsStale(t *testing.T | |||
| 2341 | 2341 | deps: &Dependencies{}, | |
| 2342 | 2342 | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), | |
| 2343 | 2343 | tabs: entity.NewTabList(), | |
| 2344 | - | tabsUC: usecase.NewManageTabsUseCase(func() string { return "created-tab" }), | |
| 2344 | + | tabsUC: usecase.NewManageTabsUseCase(func() string { return "created-tab" }, nil), | |
| 2345 | 2345 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | |
| 2346 | 2346 | lastFocusedWindowID: first.id, | |
| 2347 | 2347 | mainWindow: first.mainWindow, | |
| @@ -2973,7 +2973,7 @@ func TestApp_DispatchBrowserWindowActionSwitchTabIndexUsesSourceWindow(t *testin | |||
| 2973 | 2973 | app := &App{ | |
| 2974 | 2974 | deps: &Dependencies{}, | |
| 2975 | 2975 | runtimeConfig: runtimeConfigStateForTest(cfg), | |
| 2976 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 2976 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen(), nil), | |
| 2977 | 2977 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | |
| 2978 | 2978 | windowForTab: map[entity.TabID]*browserWindow{firstTab.ID: first, secondTab1.ID: second, secondTab2.ID: second}, | |
| 2979 | 2979 | lastFocusedWindowID: first.id, // stale — should NOT be used | |
| @@ -3016,7 +3016,7 @@ func TestApp_DispatchBrowserWindowActionSwitchTabIndexCreatesOnlyOneMissingTab(t | |||
| 3016 | 3016 | app := &App{ | |
| 3017 | 3017 | deps: &Dependencies{}, | |
| 3018 | 3018 | runtimeConfig: runtimeConfigStateForTest(cfg), | |
| 3019 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 3019 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen(), nil), | |
| 3020 | 3020 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | |
| 3021 | 3021 | windowForTab: map[entity.TabID]*browserWindow{firstTab.ID: first, secondTab.ID: second}, | |
| 3022 | 3022 | lastFocusedWindowID: first.id, // stale — should NOT be used | |
| @@ -3062,7 +3062,7 @@ func TestApp_SwitchBrowserWindowTabIndexNegativeIndexIsIgnored(t *testing.T) { | |||
| 3062 | 3062 | app := &App{ | |
| 3063 | 3063 | deps: &Dependencies{}, | |
| 3064 | 3064 | runtimeConfig: runtimeConfigStateForTest(cfg), | |
| 3065 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 3065 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen(), nil), | |
| 3066 | 3066 | browserWindows: map[string]*browserWindow{second.id: second}, | |
| 3067 | 3067 | windowForTab: map[entity.TabID]*browserWindow{secondTab.ID: second}, | |
| 3068 | 3068 | } | |
| @@ -3089,7 +3089,7 @@ func TestApp_DispatchBrowserWindowActionSwitchTabIndexNegativeDoesNotMutateWindo | |||
| 3089 | 3089 | app := &App{ | |
| 3090 | 3090 | deps: &Dependencies{}, | |
| 3091 | 3091 | runtimeConfig: runtimeConfigStateForTest(cfg), | |
| 3092 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 3092 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen(), nil), | |
| 3093 | 3093 | browserWindows: map[string]*browserWindow{second.id: second}, | |
| 3094 | 3094 | lastFocusedWindowID: "window-1", | |
| 3095 | 3095 | } | |
| @@ -3119,7 +3119,7 @@ func TestApp_DispatchBrowserWindowActionSwitchTabIndexMissingURLDoesNotCreateTab | |||
| 3119 | 3119 | app := &App{ | |
| 3120 | 3120 | deps: &Dependencies{}, | |
| 3121 | 3121 | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), | |
| 3122 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 3122 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen(), nil), | |
| 3123 | 3123 | browserWindows: map[string]*browserWindow{second.id: second}, | |
| 3124 | 3124 | windowForTab: map[entity.TabID]*browserWindow{secondTab.ID: second}, | |
| 3125 | 3125 | } | |
| @@ -3145,7 +3145,7 @@ func TestApp_SwitchBrowserWindowTabIndexMissingURLDoesNotMutateWindowState(t *te | |||
| 3145 | 3145 | app := &App{ | |
| 3146 | 3146 | deps: &Dependencies{}, | |
| 3147 | 3147 | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), | |
| 3148 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 3148 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen(), nil), | |
| 3149 | 3149 | browserWindows: map[string]*browserWindow{second.id: second}, | |
| 3150 | 3150 | lastFocusedWindowID: "window-1", | |
| 3151 | 3151 | } | |
| @@ -1104,7 +1104,7 @@ func TestApp_OpenFreshWindowRecordsTabOwnership(t *testing.T) { | |||||
| 1104 | created := &browserWindow{id: "window-1", tabs: entity.NewTabList()} | 1104 | created := &browserWindow{id: "window-1", tabs: entity.NewTabList()} | ||
| 1105 | app := &App{ | 1105 | app := &App{ | ||
| 1106 | tabs: entity.NewTabList(), | 1106 | tabs: entity.NewTabList(), | ||
| 1107 | - | tabsUC: usecase.NewManageTabsUseCase(func() string { return "id-1" }), | 1107 | + | tabsUC: usecase.NewManageTabsUseCase(func() string { return "id-1" }, nil), |
| 1108 | browserWindows: map[string]*browserWindow{existingWindow.id: existingWindow}, | 1108 | browserWindows: map[string]*browserWindow{existingWindow.id: existingWindow}, | ||
| 1109 | windowForTab: map[entity.TabID]*browserWindow{existingTab.ID: existingWindow}, | 1109 | windowForTab: map[entity.TabID]*browserWindow{existingTab.ID: existingWindow}, | ||
| 1110 | workspaceViews: map[entity.TabID]*component.WorkspaceView{entity.TabID("id-1"): &component.WorkspaceView{}}, | 1110 | workspaceViews: map[entity.TabID]*component.WorkspaceView{entity.TabID("id-1"): &component.WorkspaceView{}}, | ||
| @@ -1167,7 +1167,7 @@ func TestApp_CreateInitialTabNoFallbackAfterEmptyWindowRestore(t *testing.T) { | |||||
| 1167 | ctx := context.Background() | 1167 | ctx := context.Background() | ||
| 1168 | sessionID := entity.SessionID("session-empty-windows-fallback") | 1168 | sessionID := entity.SessionID("session-empty-windows-fallback") | ||
| 1169 | state := entity.SnapshotFromWindowTabLists(sessionID, []entity.WindowTabListState{}, 0, time.Unix(123, 0)) | 1169 | state := entity.SnapshotFromWindowTabLists(sessionID, []entity.WindowTabListState{}, 0, time.Unix(123, 0)) | ||
| 1170 | - | tabsUC := usecase.NewManageTabsUseCase(func() string { return "fallback-tab" }) | 1170 | + | tabsUC := usecase.NewManageTabsUseCase(func() string { return "fallback-tab" }, nil) |
| 1171 | windowTabs := entity.NewTabList() | 1171 | windowTabs := entity.NewTabList() | ||
| 1172 | bw := &browserWindow{id: "window-1", tabs: windowTabs} | 1172 | bw := &browserWindow{id: "window-1", tabs: windowTabs} | ||
| 1173 | app := &App{ | 1173 | app := &App{ | ||
| @@ -1361,7 +1361,7 @@ func TestApp_OpenFreshWindowRollsBackOnTabCreationFailure(t *testing.T) { | |||||
| 1361 | 1361 | ||||
| 1362 | app := &App{ | 1362 | app := &App{ | ||
| 1363 | tabs: entity.NewTabList(), | 1363 | tabs: entity.NewTabList(), | ||
| 1364 | - | tabsUC: usecase.NewManageTabsUseCase(func() string { return "id-1" }), | 1364 | + | tabsUC: usecase.NewManageTabsUseCase(func() string { return "id-1" }, nil), |
| 1365 | browserWindows: map[string]*browserWindow{existingWindow.id: existingWindow}, | 1365 | browserWindows: map[string]*browserWindow{existingWindow.id: existingWindow}, | ||
| 1366 | windowForTab: map[entity.TabID]*browserWindow{existingTab.ID: existingWindow}, | 1366 | windowForTab: map[entity.TabID]*browserWindow{existingTab.ID: existingWindow}, | ||
| 1367 | browserWindowFactory: func(context.Context, string) (*browserWindow, error) { | 1367 | browserWindowFactory: func(context.Context, string) (*browserWindow, error) { | ||
| @@ -1403,7 +1403,7 @@ func TestApp_OpenFreshWindowTargetsNewWindowTabBar(t *testing.T) { | |||||
| 1403 | setWindowTabBar(t, oldWindow, newTestTabBarShell(t)) | 1403 | setWindowTabBar(t, oldWindow, newTestTabBarShell(t)) | ||
| 1404 | setWindowTabBar(t, newWindow, newTestTabBarShell(t, createdTabID)) | 1404 | setWindowTabBar(t, newWindow, newTestTabBarShell(t, createdTabID)) | ||
| 1405 | tabs := entity.NewTabList() | 1405 | tabs := entity.NewTabList() | ||
| 1406 | - | tabsUC := usecase.NewManageTabsUseCase(func() string { return string(createdTabID) }) | 1406 | + | tabsUC := usecase.NewManageTabsUseCase(func() string { return string(createdTabID) }, nil) |
| 1407 | 1407 | ||||
| 1408 | existingTab := entity.NewTab(existingTabID, entity.WorkspaceID("existing-workspace"), entity.NewPane(entity.PaneID("existing-pane"))) | 1408 | existingTab := entity.NewTab(existingTabID, entity.WorkspaceID("existing-workspace"), entity.NewPane(entity.PaneID("existing-pane"))) | ||
| 1409 | existingTabs := entity.NewTabList() | 1409 | existingTabs := entity.NewTabList() | ||
| @@ -2280,7 +2280,7 @@ func TestApp_CreatePopupTabUsesParentPaneOwnerWhenFocusIsStale(t *testing.T) { | |||||
| 2280 | deps: &Dependencies{}, | 2280 | deps: &Dependencies{}, | ||
| 2281 | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), | 2281 | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), | ||
| 2282 | tabs: globalTabs, | 2282 | tabs: globalTabs, | ||
| 2283 | - | tabsUC: usecase.NewManageTabsUseCase(func() string { return "popup-tab" }), | 2283 | + | tabsUC: usecase.NewManageTabsUseCase(func() string { return "popup-tab" }, nil), |
| 2284 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | 2284 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | ||
| 2285 | windowForTab: map[entity.TabID]*browserWindow{firstTab.ID: first, secondTab.ID: second}, | 2285 | windowForTab: map[entity.TabID]*browserWindow{firstTab.ID: first, secondTab.ID: second}, | ||
| 2286 | workspaceViews: make(map[entity.TabID]*component.WorkspaceView), | 2286 | workspaceViews: make(map[entity.TabID]*component.WorkspaceView), | ||
| @@ -2341,7 +2341,7 @@ func TestApp_TabCoordinatorCallbacksUseTargetWindowWhenFocusIsStale(t *testing.T | |||||
| 2341 | deps: &Dependencies{}, | 2341 | deps: &Dependencies{}, | ||
| 2342 | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), | 2342 | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), | ||
| 2343 | tabs: entity.NewTabList(), | 2343 | tabs: entity.NewTabList(), | ||
| 2344 | - | tabsUC: usecase.NewManageTabsUseCase(func() string { return "created-tab" }), | 2344 | + | tabsUC: usecase.NewManageTabsUseCase(func() string { return "created-tab" }, nil), |
| 2345 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | 2345 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | ||
| 2346 | lastFocusedWindowID: first.id, | 2346 | lastFocusedWindowID: first.id, | ||
| 2347 | mainWindow: first.mainWindow, | 2347 | mainWindow: first.mainWindow, | ||
| @@ -2973,7 +2973,7 @@ func TestApp_DispatchBrowserWindowActionSwitchTabIndexUsesSourceWindow(t *testin | |||||
| 2973 | app := &App{ | 2973 | app := &App{ | ||
| 2974 | deps: &Dependencies{}, | 2974 | deps: &Dependencies{}, | ||
| 2975 | runtimeConfig: runtimeConfigStateForTest(cfg), | 2975 | runtimeConfig: runtimeConfigStateForTest(cfg), | ||
| 2976 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | 2976 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen(), nil), |
| 2977 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | 2977 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | ||
| 2978 | 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}, | ||
| 2979 | lastFocusedWindowID: first.id, // stale — should NOT be used | 2979 | lastFocusedWindowID: first.id, // stale — should NOT be used | ||
| @@ -3016,7 +3016,7 @@ func TestApp_DispatchBrowserWindowActionSwitchTabIndexCreatesOnlyOneMissingTab(t | |||||
| 3016 | app := &App{ | 3016 | app := &App{ | ||
| 3017 | deps: &Dependencies{}, | 3017 | deps: &Dependencies{}, | ||
| 3018 | runtimeConfig: runtimeConfigStateForTest(cfg), | 3018 | runtimeConfig: runtimeConfigStateForTest(cfg), | ||
| 3019 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | 3019 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen(), nil), |
| 3020 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | 3020 | browserWindows: map[string]*browserWindow{first.id: first, second.id: second}, | ||
| 3021 | windowForTab: map[entity.TabID]*browserWindow{firstTab.ID: first, secondTab.ID: second}, | 3021 | windowForTab: map[entity.TabID]*browserWindow{firstTab.ID: first, secondTab.ID: second}, | ||
| 3022 | lastFocusedWindowID: first.id, // stale — should NOT be used | 3022 | lastFocusedWindowID: first.id, // stale — should NOT be used | ||
| @@ -3062,7 +3062,7 @@ func TestApp_SwitchBrowserWindowTabIndexNegativeIndexIsIgnored(t *testing.T) { | |||||
| 3062 | app := &App{ | 3062 | app := &App{ | ||
| 3063 | deps: &Dependencies{}, | 3063 | deps: &Dependencies{}, | ||
| 3064 | runtimeConfig: runtimeConfigStateForTest(cfg), | 3064 | runtimeConfig: runtimeConfigStateForTest(cfg), | ||
| 3065 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | 3065 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen(), nil), |
| 3066 | browserWindows: map[string]*browserWindow{second.id: second}, | 3066 | browserWindows: map[string]*browserWindow{second.id: second}, | ||
| 3067 | windowForTab: map[entity.TabID]*browserWindow{secondTab.ID: second}, | 3067 | windowForTab: map[entity.TabID]*browserWindow{secondTab.ID: second}, | ||
| 3068 | } | 3068 | } | ||
| @@ -3089,7 +3089,7 @@ func TestApp_DispatchBrowserWindowActionSwitchTabIndexNegativeDoesNotMutateWindo | |||||
| 3089 | app := &App{ | 3089 | app := &App{ | ||
| 3090 | deps: &Dependencies{}, | 3090 | deps: &Dependencies{}, | ||
| 3091 | runtimeConfig: runtimeConfigStateForTest(cfg), | 3091 | runtimeConfig: runtimeConfigStateForTest(cfg), | ||
| 3092 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | 3092 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen(), nil), |
| 3093 | browserWindows: map[string]*browserWindow{second.id: second}, | 3093 | browserWindows: map[string]*browserWindow{second.id: second}, | ||
| 3094 | lastFocusedWindowID: "window-1", | 3094 | lastFocusedWindowID: "window-1", | ||
| 3095 | } | 3095 | } | ||
| @@ -3119,7 +3119,7 @@ func TestApp_DispatchBrowserWindowActionSwitchTabIndexMissingURLDoesNotCreateTab | |||||
| 3119 | app := &App{ | 3119 | app := &App{ | ||
| 3120 | deps: &Dependencies{}, | 3120 | deps: &Dependencies{}, | ||
| 3121 | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), | 3121 | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), | ||
| 3122 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | 3122 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen(), nil), |
| 3123 | browserWindows: map[string]*browserWindow{second.id: second}, | 3123 | browserWindows: map[string]*browserWindow{second.id: second}, | ||
| 3124 | windowForTab: map[entity.TabID]*browserWindow{secondTab.ID: second}, | 3124 | windowForTab: map[entity.TabID]*browserWindow{secondTab.ID: second}, | ||
| 3125 | } | 3125 | } | ||
| @@ -3145,7 +3145,7 @@ func TestApp_SwitchBrowserWindowTabIndexMissingURLDoesNotMutateWindowState(t *te | |||||
| 3145 | app := &App{ | 3145 | app := &App{ | ||
| 3146 | deps: &Dependencies{}, | 3146 | deps: &Dependencies{}, | ||
| 3147 | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), | 3147 | runtimeConfig: runtimeConfigStateForTest(&config.Config{}), | ||
| 3148 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | 3148 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen(), nil), |
| 3149 | browserWindows: map[string]*browserWindow{second.id: second}, | 3149 | browserWindows: map[string]*browserWindow{second.id: second}, | ||
| 3150 | lastFocusedWindowID: "window-1", | 3150 | lastFocusedWindowID: "window-1", | ||
| 3151 | } | 3151 | } | ||
M internal/ui/browser_window_history_sidebar_test.go +1 -1
| @@ -709,7 +709,7 @@ func TestApp_HistorySidebarConfig_OpenInNewPaneCallback(t *testing.T) { | |||
| 709 | 709 | ||
| 710 | 710 | bw := &browserWindow{id: "window-1", tabs: bwTabs} | |
| 711 | 711 | ||
| 712 | - | panesUC := usecase.NewManagePanesUseCase(func() string { return "pane-2" }) | |
| 712 | + | panesUC := usecase.NewManagePanesUseCase(func() string { return "pane-2" }, nil) | |
| 713 | 713 | wsCoord := coordinator.NewWorkspaceCoordinator(ctx, coordinator.WorkspaceCoordinatorConfig{ | |
| 714 | 714 | PanesUC: panesUC, | |
| 715 | 715 | GetActiveWS: func() (*entity.Workspace, *component.WorkspaceView) { | |
| @@ -709,7 +709,7 @@ func TestApp_HistorySidebarConfig_OpenInNewPaneCallback(t *testing.T) { | |||||
| 709 | 709 | ||||
| 710 | bw := &browserWindow{id: "window-1", tabs: bwTabs} | 710 | bw := &browserWindow{id: "window-1", tabs: bwTabs} | ||
| 711 | 711 | ||||
| 712 | - | panesUC := usecase.NewManagePanesUseCase(func() string { return "pane-2" }) | 712 | + | panesUC := usecase.NewManagePanesUseCase(func() string { return "pane-2" }, nil) |
| 713 | wsCoord := coordinator.NewWorkspaceCoordinator(ctx, coordinator.WorkspaceCoordinatorConfig{ | 713 | wsCoord := coordinator.NewWorkspaceCoordinator(ctx, coordinator.WorkspaceCoordinatorConfig{ | ||
| 714 | PanesUC: panesUC, | 714 | PanesUC: panesUC, | ||
| 715 | GetActiveWS: func() (*entity.Workspace, *component.WorkspaceView) { | 715 | GetActiveWS: func() (*entity.Workspace, *component.WorkspaceView) { | ||
M internal/ui/browser_window_test.go +4 -4
| @@ -139,7 +139,7 @@ func TestTabCoordinator_CloseReleasesClosedTabWorkspaceWebViews(t *testing.T) { | |||
| 139 | 139 | app := &App{ | |
| 140 | 140 | deps: &Dependencies{}, | |
| 141 | 141 | runtimeConfig: runtimeConfigStateForTest(config.DefaultConfig()), | |
| 142 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 142 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen(), nil), | |
| 143 | 143 | contentCoord: contentCoord, | |
| 144 | 144 | browserWindows: map[string]*browserWindow{bw.id: bw}, | |
| 145 | 145 | tabs: entity.NewTabList(), | |
| @@ -183,7 +183,7 @@ func TestTabCoordinator_SwitchDoesNotReleaseWorkspaceWebViews(t *testing.T) { | |||
| 183 | 183 | app := &App{ | |
| 184 | 184 | deps: &Dependencies{}, | |
| 185 | 185 | runtimeConfig: runtimeConfigStateForTest(config.DefaultConfig()), | |
| 186 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 186 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen(), nil), | |
| 187 | 187 | contentCoord: contentCoord, | |
| 188 | 188 | browserWindows: map[string]*browserWindow{bw.id: bw}, | |
| 189 | 189 | tabs: entity.NewTabList(), | |
| @@ -218,7 +218,7 @@ func TestTabCoordinator_CloseLastTabReleasesWorkspaceBeforeWindowRemoval(t *test | |||
| 218 | 218 | app := &App{ | |
| 219 | 219 | deps: &Dependencies{}, | |
| 220 | 220 | runtimeConfig: runtimeConfigStateForTest(config.DefaultConfig()), | |
| 221 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | |
| 221 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen(), nil), | |
| 222 | 222 | contentCoord: contentCoord, | |
| 223 | 223 | browserWindows: map[string]*browserWindow{bw.id: bw}, | |
| 224 | 224 | tabs: entity.NewTabList(), | |
| @@ -462,7 +462,7 @@ func TestOpenFreshWindow_DispatchesAndTracksBrowserWindow(t *testing.T) { | |||
| 462 | 462 | func TestOpenFreshWindow_FirstWindowRegistersShellWithoutCreatingTab(t *testing.T) { | |
| 463 | 463 | app := &App{ | |
| 464 | 464 | tabs: entity.NewTabList(), | |
| 465 | - | tabsUC: usecase.NewManageTabsUseCase(func() string { return "unexpected-tab" }), | |
| 465 | + | tabsUC: usecase.NewManageTabsUseCase(func() string { return "unexpected-tab" }, nil), | |
| 466 | 466 | browserWindows: make(map[string]*browserWindow), | |
| 467 | 467 | } | |
| 468 | 468 | app.dispatchOnMainThread = func(label string, fn func()) syncdispatch.SyncDispatchResult { | |
| @@ -139,7 +139,7 @@ func TestTabCoordinator_CloseReleasesClosedTabWorkspaceWebViews(t *testing.T) { | |||||
| 139 | app := &App{ | 139 | app := &App{ | ||
| 140 | deps: &Dependencies{}, | 140 | deps: &Dependencies{}, | ||
| 141 | runtimeConfig: runtimeConfigStateForTest(config.DefaultConfig()), | 141 | runtimeConfig: runtimeConfigStateForTest(config.DefaultConfig()), | ||
| 142 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | 142 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen(), nil), |
| 143 | contentCoord: contentCoord, | 143 | contentCoord: contentCoord, | ||
| 144 | browserWindows: map[string]*browserWindow{bw.id: bw}, | 144 | browserWindows: map[string]*browserWindow{bw.id: bw}, | ||
| 145 | tabs: entity.NewTabList(), | 145 | tabs: entity.NewTabList(), | ||
| @@ -183,7 +183,7 @@ func TestTabCoordinator_SwitchDoesNotReleaseWorkspaceWebViews(t *testing.T) { | |||||
| 183 | app := &App{ | 183 | app := &App{ | ||
| 184 | deps: &Dependencies{}, | 184 | deps: &Dependencies{}, | ||
| 185 | runtimeConfig: runtimeConfigStateForTest(config.DefaultConfig()), | 185 | runtimeConfig: runtimeConfigStateForTest(config.DefaultConfig()), | ||
| 186 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | 186 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen(), nil), |
| 187 | contentCoord: contentCoord, | 187 | contentCoord: contentCoord, | ||
| 188 | browserWindows: map[string]*browserWindow{bw.id: bw}, | 188 | browserWindows: map[string]*browserWindow{bw.id: bw}, | ||
| 189 | tabs: entity.NewTabList(), | 189 | tabs: entity.NewTabList(), | ||
| @@ -218,7 +218,7 @@ func TestTabCoordinator_CloseLastTabReleasesWorkspaceBeforeWindowRemoval(t *test | |||||
| 218 | app := &App{ | 218 | app := &App{ | ||
| 219 | deps: &Dependencies{}, | 219 | deps: &Dependencies{}, | ||
| 220 | runtimeConfig: runtimeConfigStateForTest(config.DefaultConfig()), | 220 | runtimeConfig: runtimeConfigStateForTest(config.DefaultConfig()), | ||
| 221 | - | tabsUC: usecase.NewManageTabsUseCase(counterIDGen()), | 221 | + | tabsUC: usecase.NewManageTabsUseCase(counterIDGen(), nil), |
| 222 | contentCoord: contentCoord, | 222 | contentCoord: contentCoord, | ||
| 223 | browserWindows: map[string]*browserWindow{bw.id: bw}, | 223 | browserWindows: map[string]*browserWindow{bw.id: bw}, | ||
| 224 | tabs: entity.NewTabList(), | 224 | tabs: entity.NewTabList(), | ||
| @@ -462,7 +462,7 @@ func TestOpenFreshWindow_DispatchesAndTracksBrowserWindow(t *testing.T) { | |||||
| 462 | func TestOpenFreshWindow_FirstWindowRegistersShellWithoutCreatingTab(t *testing.T) { | 462 | func TestOpenFreshWindow_FirstWindowRegistersShellWithoutCreatingTab(t *testing.T) { | ||
| 463 | app := &App{ | 463 | app := &App{ | ||
| 464 | tabs: entity.NewTabList(), | 464 | tabs: entity.NewTabList(), | ||
| 465 | - | tabsUC: usecase.NewManageTabsUseCase(func() string { return "unexpected-tab" }), | 465 | + | tabsUC: usecase.NewManageTabsUseCase(func() string { return "unexpected-tab" }, nil), |
| 466 | browserWindows: make(map[string]*browserWindow), | 466 | browserWindows: make(map[string]*browserWindow), | ||
| 467 | } | 467 | } | ||
| 468 | app.dispatchOnMainThread = func(label string, fn func()) syncdispatch.SyncDispatchResult { | 468 | app.dispatchOnMainThread = func(label string, fn func()) syncdispatch.SyncDispatchResult { | ||
M internal/ui/coordinator/tab_test.go +5 -5
| @@ -26,7 +26,7 @@ func TestTabCoordinator_CloseTargetEmptyDoesNotQuitGlobally(t *testing.T) { | |||
| 26 | 26 | secondTabs.Add(entity.NewTab(entity.TabID("second-tab"), entity.WorkspaceID("ws-second"), entity.NewPane(entity.PaneID("pane-second")))) | |
| 27 | 27 | ||
| 28 | 28 | gen := counterIDGen() | |
| 29 | - | tabsUC := usecase.NewManageTabsUseCase(gen) | |
| 29 | + | tabsUC := usecase.NewManageTabsUseCase(gen, nil) | |
| 30 | 30 | ||
| 31 | 31 | coord := NewTabCoordinator(ctx, TabCoordinatorConfig{ | |
| 32 | 32 | TabsUC: tabsUC, | |
| @@ -124,7 +124,7 @@ func TestTabCoordinator_SwitchByIndexUsesTargetTabListOnly(t *testing.T) { | |||
| 124 | 124 | secondTabs.Add(entity.NewTab(entity.TabID("second-tab-2"), entity.WorkspaceID("ws-second-2"), entity.NewPane(entity.PaneID("pane-second-2")))) | |
| 125 | 125 | ||
| 126 | 126 | gen := counterIDGen() | |
| 127 | - | tabsUC := usecase.NewManageTabsUseCase(gen) | |
| 127 | + | tabsUC := usecase.NewManageTabsUseCase(gen, nil) | |
| 128 | 128 | ||
| 129 | 129 | // Create TabCoordinator without global tabs (target-driven). | |
| 130 | 130 | coord := NewTabCoordinator(ctx, TabCoordinatorConfig{ | |
| @@ -175,7 +175,7 @@ func TestTabCoordinator_CreateAndSwitchCallbacksCarryTarget(t *testing.T) { | |||
| 175 | 175 | secondTabs.Add(entity.NewTab(entity.TabID("second-tab-1"), entity.WorkspaceID("ws-second-1"), entity.NewPane(entity.PaneID("pane-second-1")))) | |
| 176 | 176 | secondTabs.Add(entity.NewTab(entity.TabID("second-tab-2"), entity.WorkspaceID("ws-second-2"), entity.NewPane(entity.PaneID("pane-second-2")))) | |
| 177 | 177 | ||
| 178 | - | coord := NewTabCoordinator(ctx, TabCoordinatorConfig{TabsUC: usecase.NewManageTabsUseCase(counterIDGen())}) | |
| 178 | + | coord := NewTabCoordinator(ctx, TabCoordinatorConfig{TabsUC: usecase.NewManageTabsUseCase(counterIDGen(), nil)}) | |
| 179 | 179 | coord.SetCurrentTarget(TabTarget{Tabs: firstTabs}) | |
| 180 | 180 | ||
| 181 | 181 | secondTarget := TabTarget{Tabs: secondTabs} | |
| @@ -222,7 +222,7 @@ func TestTabCoordinator_SwitchByIndexCreatingCreatesMissingTabs(t *testing.T) { | |||
| 222 | 222 | secondTabs := entity.NewTabList() | |
| 223 | 223 | secondTabs.Add(entity.NewTab(entity.TabID("second-tab-1"), entity.WorkspaceID("ws-second-1"), entity.NewPane(entity.PaneID("pane-second-1")))) | |
| 224 | 224 | ||
| 225 | - | coord := NewTabCoordinator(ctx, TabCoordinatorConfig{TabsUC: usecase.NewManageTabsUseCase(counterIDGen())}) | |
| 225 | + | coord := NewTabCoordinator(ctx, TabCoordinatorConfig{TabsUC: usecase.NewManageTabsUseCase(counterIDGen(), nil)}) | |
| 226 | 226 | ||
| 227 | 227 | secondTarget := TabTarget{Tabs: secondTabs} | |
| 228 | 228 | ||
| @@ -260,7 +260,7 @@ func TestTabCoordinator_SwitchByIndexCreatingMissingURLError(t *testing.T) { | |||
| 260 | 260 | targetTabs := entity.NewTabList() | |
| 261 | 261 | targetTabs.Add(entity.NewTab(entity.TabID("tab-1"), entity.WorkspaceID("ws-1"), entity.NewPane(entity.PaneID("pane-1")))) | |
| 262 | 262 | ||
| 263 | - | coord := NewTabCoordinator(ctx, TabCoordinatorConfig{TabsUC: usecase.NewManageTabsUseCase(counterIDGen())}) | |
| 263 | + | coord := NewTabCoordinator(ctx, TabCoordinatorConfig{TabsUC: usecase.NewManageTabsUseCase(counterIDGen(), nil)}) | |
| 264 | 264 | ||
| 265 | 265 | target := TabTarget{Tabs: targetTabs} | |
| 266 | 266 | ||
| @@ -26,7 +26,7 @@ func TestTabCoordinator_CloseTargetEmptyDoesNotQuitGlobally(t *testing.T) { | |||||
| 26 | secondTabs.Add(entity.NewTab(entity.TabID("second-tab"), entity.WorkspaceID("ws-second"), entity.NewPane(entity.PaneID("pane-second")))) | 26 | secondTabs.Add(entity.NewTab(entity.TabID("second-tab"), entity.WorkspaceID("ws-second"), entity.NewPane(entity.PaneID("pane-second")))) | ||
| 27 | 27 | ||||
| 28 | gen := counterIDGen() | 28 | gen := counterIDGen() | ||
| 29 | - | tabsUC := usecase.NewManageTabsUseCase(gen) | 29 | + | tabsUC := usecase.NewManageTabsUseCase(gen, nil) |
| 30 | 30 | ||||
| 31 | coord := NewTabCoordinator(ctx, TabCoordinatorConfig{ | 31 | coord := NewTabCoordinator(ctx, TabCoordinatorConfig{ | ||
| 32 | TabsUC: tabsUC, | 32 | TabsUC: tabsUC, | ||
| @@ -124,7 +124,7 @@ func TestTabCoordinator_SwitchByIndexUsesTargetTabListOnly(t *testing.T) { | |||||
| 124 | secondTabs.Add(entity.NewTab(entity.TabID("second-tab-2"), entity.WorkspaceID("ws-second-2"), entity.NewPane(entity.PaneID("pane-second-2")))) | 124 | secondTabs.Add(entity.NewTab(entity.TabID("second-tab-2"), entity.WorkspaceID("ws-second-2"), entity.NewPane(entity.PaneID("pane-second-2")))) | ||
| 125 | 125 | ||||
| 126 | gen := counterIDGen() | 126 | gen := counterIDGen() | ||
| 127 | - | tabsUC := usecase.NewManageTabsUseCase(gen) | 127 | + | tabsUC := usecase.NewManageTabsUseCase(gen, nil) |
| 128 | 128 | ||||
| 129 | // Create TabCoordinator without global tabs (target-driven). | 129 | // Create TabCoordinator without global tabs (target-driven). | ||
| 130 | coord := NewTabCoordinator(ctx, TabCoordinatorConfig{ | 130 | coord := NewTabCoordinator(ctx, TabCoordinatorConfig{ | ||
| @@ -175,7 +175,7 @@ func TestTabCoordinator_CreateAndSwitchCallbacksCarryTarget(t *testing.T) { | |||||
| 175 | secondTabs.Add(entity.NewTab(entity.TabID("second-tab-1"), entity.WorkspaceID("ws-second-1"), entity.NewPane(entity.PaneID("pane-second-1")))) | 175 | secondTabs.Add(entity.NewTab(entity.TabID("second-tab-1"), entity.WorkspaceID("ws-second-1"), entity.NewPane(entity.PaneID("pane-second-1")))) | ||
| 176 | secondTabs.Add(entity.NewTab(entity.TabID("second-tab-2"), entity.WorkspaceID("ws-second-2"), entity.NewPane(entity.PaneID("pane-second-2")))) | 176 | secondTabs.Add(entity.NewTab(entity.TabID("second-tab-2"), entity.WorkspaceID("ws-second-2"), entity.NewPane(entity.PaneID("pane-second-2")))) | ||
| 177 | 177 | ||||
| 178 | - | coord := NewTabCoordinator(ctx, TabCoordinatorConfig{TabsUC: usecase.NewManageTabsUseCase(counterIDGen())}) | 178 | + | coord := NewTabCoordinator(ctx, TabCoordinatorConfig{TabsUC: usecase.NewManageTabsUseCase(counterIDGen(), nil)}) |
| 179 | coord.SetCurrentTarget(TabTarget{Tabs: firstTabs}) | 179 | coord.SetCurrentTarget(TabTarget{Tabs: firstTabs}) | ||
| 180 | 180 | ||||
| 181 | secondTarget := TabTarget{Tabs: secondTabs} | 181 | secondTarget := TabTarget{Tabs: secondTabs} | ||
| @@ -222,7 +222,7 @@ func TestTabCoordinator_SwitchByIndexCreatingCreatesMissingTabs(t *testing.T) { | |||||
| 222 | secondTabs := entity.NewTabList() | 222 | secondTabs := entity.NewTabList() | ||
| 223 | secondTabs.Add(entity.NewTab(entity.TabID("second-tab-1"), entity.WorkspaceID("ws-second-1"), entity.NewPane(entity.PaneID("pane-second-1")))) | 223 | secondTabs.Add(entity.NewTab(entity.TabID("second-tab-1"), entity.WorkspaceID("ws-second-1"), entity.NewPane(entity.PaneID("pane-second-1")))) | ||
| 224 | 224 | ||||
| 225 | - | coord := NewTabCoordinator(ctx, TabCoordinatorConfig{TabsUC: usecase.NewManageTabsUseCase(counterIDGen())}) | 225 | + | coord := NewTabCoordinator(ctx, TabCoordinatorConfig{TabsUC: usecase.NewManageTabsUseCase(counterIDGen(), nil)}) |
| 226 | 226 | ||||
| 227 | secondTarget := TabTarget{Tabs: secondTabs} | 227 | secondTarget := TabTarget{Tabs: secondTabs} | ||
| 228 | 228 | ||||
| @@ -260,7 +260,7 @@ func TestTabCoordinator_SwitchByIndexCreatingMissingURLError(t *testing.T) { | |||||
| 260 | targetTabs := entity.NewTabList() | 260 | targetTabs := entity.NewTabList() | ||
| 261 | targetTabs.Add(entity.NewTab(entity.TabID("tab-1"), entity.WorkspaceID("ws-1"), entity.NewPane(entity.PaneID("pane-1")))) | 261 | targetTabs.Add(entity.NewTab(entity.TabID("tab-1"), entity.WorkspaceID("ws-1"), entity.NewPane(entity.PaneID("pane-1")))) | ||
| 262 | 262 | ||||
| 263 | - | coord := NewTabCoordinator(ctx, TabCoordinatorConfig{TabsUC: usecase.NewManageTabsUseCase(counterIDGen())}) | 263 | + | coord := NewTabCoordinator(ctx, TabCoordinatorConfig{TabsUC: usecase.NewManageTabsUseCase(counterIDGen(), nil)}) |
| 264 | 264 | ||||
| 265 | target := TabTarget{Tabs: targetTabs} | 265 | target := TabTarget{Tabs: targetTabs} | ||
| 266 | 266 | ||||
M internal/ui/coordinator/workspace.go +15 -15
| @@ -1460,24 +1460,23 @@ func (c *WorkspaceCoordinator) StackPane(ctx context.Context) error { | |||
| 1460 | 1460 | return nil | |
| 1461 | 1461 | } | |
| 1462 | 1462 | ||
| 1463 | - | // Create new pane entity | |
| 1464 | - | newPaneID := entity.PaneID(c.generateID()) | |
| 1465 | - | newPane := entity.NewPane(newPaneID) | |
| 1466 | - | newPane.URI = c.panesUC.NormalizeNavigationURL(ctx, c.newPaneURL) | |
| 1467 | - | newPane.Title = defaultPaneTitle | |
| 1468 | - | ||
| 1469 | - | // Determine if we need to create a new stack or add to existing | |
| 1463 | + | // Determine if we need to create a new stack or add to existing. | |
| 1470 | 1464 | var stackNode *entity.PaneNode | |
| 1465 | + | var newPane *entity.Pane | |
| 1466 | + | var newPaneID entity.PaneID | |
| 1471 | 1467 | var needsFirstPaneTitleUpdate bool | |
| 1472 | 1468 | ||
| 1473 | 1469 | if stackCtx.activeNode.Parent != nil && stackCtx.activeNode.Parent.IsStacked { | |
| 1474 | 1470 | // Already in a stack - use AddToStack use case | |
| 1475 | 1471 | stackNode = stackCtx.activeNode.Parent | |
| 1476 | - | output, err := c.panesUC.AddToStack(ctx, stackCtx.ws, stackNode, newPane) | |
| 1472 | + | output, err := c.panesUC.AddToStack(ctx, stackCtx.ws, stackNode, nil, c.newPaneURL) | |
| 1477 | 1473 | if err != nil { | |
| 1478 | 1474 | log.Error().Err(err).Msg("failed to add pane to stack via use case") | |
| 1479 | 1475 | return err | |
| 1480 | 1476 | } | |
| 1477 | + | newPane = output.NewPaneNode.Pane | |
| 1478 | + | newPaneID = newPane.ID | |
| 1479 | + | newPane.Title = defaultPaneTitle | |
| 1481 | 1480 | log.Debug(). | |
| 1482 | 1481 | Int("stack_size", len(stackNode.Children)). | |
| 1483 | 1482 | Int("insert_index", output.StackIndex). | |
| @@ -1485,28 +1484,29 @@ func (c *WorkspaceCoordinator) StackPane(ctx context.Context) error { | |||
| 1485 | 1484 | } else if stackCtx.activeNode.IsStacked { | |
| 1486 | 1485 | // Active node is already a stack container - add to it | |
| 1487 | 1486 | stackNode = stackCtx.activeNode | |
| 1488 | - | output, err := c.panesUC.AddToStack(ctx, stackCtx.ws, stackNode, newPane) | |
| 1487 | + | output, err := c.panesUC.AddToStack(ctx, stackCtx.ws, stackNode, nil, c.newPaneURL) | |
| 1489 | 1488 | if err != nil { | |
| 1490 | 1489 | log.Error().Err(err).Msg("failed to add pane to stack via use case") | |
| 1491 | 1490 | return err | |
| 1492 | 1491 | } | |
| 1492 | + | newPane = output.NewPaneNode.Pane | |
| 1493 | + | newPaneID = newPane.ID | |
| 1494 | + | newPane.Title = defaultPaneTitle | |
| 1493 | 1495 | log.Debug(). | |
| 1494 | 1496 | Int("stack_size", len(stackNode.Children)). | |
| 1495 | 1497 | Int("insert_index", output.StackIndex). | |
| 1496 | 1498 | Msg("added to stack container via use case") | |
| 1497 | 1499 | } else { | |
| 1498 | - | // Need to create a new stack - use CreateStack use case | |
| 1499 | - | // But CreateStack creates its own pane, so we need a different approach | |
| 1500 | - | // Convert to stack first, then the new pane is already created | |
| 1501 | - | output, err := c.panesUC.CreateStack(ctx, stackCtx.ws, stackCtx.activeNode) | |
| 1500 | + | // Need to create a new stack - use CreateStack use case. | |
| 1501 | + | output, err := c.panesUC.CreateStack(ctx, stackCtx.ws, stackCtx.activeNode, c.newPaneURL) | |
| 1502 | 1502 | if err != nil { | |
| 1503 | 1503 | log.Error().Err(err).Msg("failed to create stack via use case") | |
| 1504 | 1504 | return err | |
| 1505 | 1505 | } | |
| 1506 | 1506 | stackNode = output.StackNode | |
| 1507 | - | // CreateStack creates its own new pane, use that instead | |
| 1508 | 1507 | newPane = output.NewPane | |
| 1509 | 1508 | newPaneID = newPane.ID | |
| 1509 | + | newPane.Title = defaultPaneTitle | |
| 1510 | 1510 | needsFirstPaneTitleUpdate = true | |
| 1511 | 1511 | ||
| 1512 | 1512 | // Update the original pane's title in the domain | |
| @@ -2035,7 +2035,7 @@ func (c *WorkspaceCoordinator) insertPopupStacked(ctx context.Context, input con | |||
| 2035 | 2035 | stackNode, conversionInfo := c.resolveOrCreateStackNode(ctx, parentNode, input.ParentPaneID) | |
| 2036 | 2036 | ||
| 2037 | 2037 | // Add popup pane to stack using use case | |
| 2038 | - | if _, err := c.panesUC.AddToStack(ctx, ws, stackNode, input.PopupPane); err != nil { | |
| 2038 | + | if _, err := c.panesUC.AddToStack(ctx, ws, stackNode, input.PopupPane, ""); err != nil { | |
| 2039 | 2039 | // Rollback stack conversion if we created a new stack | |
| 2040 | 2040 | conversionInfo.revert() | |
| 2041 | 2041 | return fmt.Errorf("failed to add popup to stack: %w", err) | |
| @@ -1460,24 +1460,23 @@ func (c *WorkspaceCoordinator) StackPane(ctx context.Context) error { | |||||
| 1460 | return nil | 1460 | return nil | ||
| 1461 | } | 1461 | } | ||
| 1462 | 1462 | ||||
| 1463 | - | // Create new pane entity | 1463 | + | // Determine if we need to create a new stack or add to existing. |
| 1464 | - | newPaneID := entity.PaneID(c.generateID()) | |||
| 1465 | - | newPane := entity.NewPane(newPaneID) | |||
| 1466 | - | newPane.URI = c.panesUC.NormalizeNavigationURL(ctx, c.newPaneURL) | |||
| 1467 | - | newPane.Title = defaultPaneTitle | |||
| 1468 | - | ||||
| 1469 | - | // Determine if we need to create a new stack or add to existing | |||
| 1470 | var stackNode *entity.PaneNode | 1464 | var stackNode *entity.PaneNode | ||
| 1465 | + | var newPane *entity.Pane | |||
| 1466 | + | var newPaneID entity.PaneID | |||
| 1471 | var needsFirstPaneTitleUpdate bool | 1467 | var needsFirstPaneTitleUpdate bool | ||
| 1472 | 1468 | ||||
| 1473 | if stackCtx.activeNode.Parent != nil && stackCtx.activeNode.Parent.IsStacked { | 1469 | if stackCtx.activeNode.Parent != nil && stackCtx.activeNode.Parent.IsStacked { | ||
| 1474 | // Already in a stack - use AddToStack use case | 1470 | // Already in a stack - use AddToStack use case | ||
| 1475 | stackNode = stackCtx.activeNode.Parent | 1471 | stackNode = stackCtx.activeNode.Parent | ||
| 1476 | - | output, err := c.panesUC.AddToStack(ctx, stackCtx.ws, stackNode, newPane) | 1472 | + | output, err := c.panesUC.AddToStack(ctx, stackCtx.ws, stackNode, nil, c.newPaneURL) |
| 1477 | if err != nil { | 1473 | if err != nil { | ||
| 1478 | log.Error().Err(err).Msg("failed to add pane to stack via use case") | 1474 | log.Error().Err(err).Msg("failed to add pane to stack via use case") | ||
| 1479 | return err | 1475 | return err | ||
| 1480 | } | 1476 | } | ||
| 1477 | + | newPane = output.NewPaneNode.Pane | |||
| 1478 | + | newPaneID = newPane.ID | |||
| 1479 | + | newPane.Title = defaultPaneTitle | |||
| 1481 | log.Debug(). | 1480 | log.Debug(). | ||
| 1482 | Int("stack_size", len(stackNode.Children)). | 1481 | Int("stack_size", len(stackNode.Children)). | ||
| 1483 | Int("insert_index", output.StackIndex). | 1482 | Int("insert_index", output.StackIndex). | ||
| @@ -1485,28 +1484,29 @@ func (c *WorkspaceCoordinator) StackPane(ctx context.Context) error { | |||||
| 1485 | } else if stackCtx.activeNode.IsStacked { | 1484 | } else if stackCtx.activeNode.IsStacked { | ||
| 1486 | // Active node is already a stack container - add to it | 1485 | // Active node is already a stack container - add to it | ||
| 1487 | stackNode = stackCtx.activeNode | 1486 | stackNode = stackCtx.activeNode | ||
| 1488 | - | output, err := c.panesUC.AddToStack(ctx, stackCtx.ws, stackNode, newPane) | 1487 | + | output, err := c.panesUC.AddToStack(ctx, stackCtx.ws, stackNode, nil, c.newPaneURL) |
| 1489 | if err != nil { | 1488 | if err != nil { | ||
| 1490 | log.Error().Err(err).Msg("failed to add pane to stack via use case") | 1489 | log.Error().Err(err).Msg("failed to add pane to stack via use case") | ||
| 1491 | return err | 1490 | return err | ||
| 1492 | } | 1491 | } | ||
| 1492 | + | newPane = output.NewPaneNode.Pane | |||
| 1493 | + | newPaneID = newPane.ID | |||
| 1494 | + | newPane.Title = defaultPaneTitle | |||
| 1493 | log.Debug(). | 1495 | log.Debug(). | ||
| 1494 | Int("stack_size", len(stackNode.Children)). | 1496 | Int("stack_size", len(stackNode.Children)). | ||
| 1495 | Int("insert_index", output.StackIndex). | 1497 | Int("insert_index", output.StackIndex). | ||
| 1496 | Msg("added to stack container via use case") | 1498 | Msg("added to stack container via use case") | ||
| 1497 | } else { | 1499 | } else { | ||
| 1498 | - | // Need to create a new stack - use CreateStack use case | 1500 | + | // Need to create a new stack - use CreateStack use case. |
| 1499 | - | // But CreateStack creates its own pane, so we need a different approach | 1501 | + | output, err := c.panesUC.CreateStack(ctx, stackCtx.ws, stackCtx.activeNode, c.newPaneURL) |
| 1500 | - | // Convert to stack first, then the new pane is already created | |||
| 1501 | - | output, err := c.panesUC.CreateStack(ctx, stackCtx.ws, stackCtx.activeNode) | |||
| 1502 | if err != nil { | 1502 | if err != nil { | ||
| 1503 | log.Error().Err(err).Msg("failed to create stack via use case") | 1503 | log.Error().Err(err).Msg("failed to create stack via use case") | ||
| 1504 | return err | 1504 | return err | ||
| 1505 | } | 1505 | } | ||
| 1506 | stackNode = output.StackNode | 1506 | stackNode = output.StackNode | ||
| 1507 | - | // CreateStack creates its own new pane, use that instead | |||
| 1508 | newPane = output.NewPane | 1507 | newPane = output.NewPane | ||
| 1509 | newPaneID = newPane.ID | 1508 | newPaneID = newPane.ID | ||
| 1509 | + | newPane.Title = defaultPaneTitle | |||
| 1510 | needsFirstPaneTitleUpdate = true | 1510 | needsFirstPaneTitleUpdate = true | ||
| 1511 | 1511 | ||||
| 1512 | // Update the original pane's title in the domain | 1512 | // Update the original pane's title in the domain | ||
| @@ -2035,7 +2035,7 @@ func (c *WorkspaceCoordinator) insertPopupStacked(ctx context.Context, input con | |||||
| 2035 | stackNode, conversionInfo := c.resolveOrCreateStackNode(ctx, parentNode, input.ParentPaneID) | 2035 | stackNode, conversionInfo := c.resolveOrCreateStackNode(ctx, parentNode, input.ParentPaneID) | ||
| 2036 | 2036 | ||||
| 2037 | // Add popup pane to stack using use case | 2037 | // Add popup pane to stack using use case | ||
| 2038 | - | if _, err := c.panesUC.AddToStack(ctx, ws, stackNode, input.PopupPane); err != nil { | 2038 | + | if _, err := c.panesUC.AddToStack(ctx, ws, stackNode, input.PopupPane, ""); err != nil { |
| 2039 | // Rollback stack conversion if we created a new stack | 2039 | // Rollback stack conversion if we created a new stack | ||
| 2040 | conversionInfo.revert() | 2040 | conversionInfo.revert() | ||
| 2041 | return fmt.Errorf("failed to add popup to stack: %w", err) | 2041 | return fmt.Errorf("failed to add popup to stack: %w", err) | ||
M internal/ui/coordinator/workspace_systemview_test.go +3 -3
| @@ -19,7 +19,7 @@ func TestWorkspaceCoordinator_ToggleSystemViewRightSplitsWithTargetURL(t *testin | |||
| 19 | 19 | id := ids[idx] | |
| 20 | 20 | idx++ | |
| 21 | 21 | return id | |
| 22 | - | }) | |
| 22 | + | }, nil) | |
| 23 | 23 | ||
| 24 | 24 | initialPane := entity.NewPane("pane-1") | |
| 25 | 25 | initialPane.URI = "https://example.com" | |
| @@ -56,7 +56,7 @@ func TestWorkspaceCoordinator_ToggleSystemViewRightFocusesExistingPane(t *testin | |||
| 56 | 56 | PanesUC: usecase.NewManagePanesUseCase(func() string { | |
| 57 | 57 | t.Fatal("PanesUC should not be invoked when focusing an existing systemview pane") | |
| 58 | 58 | return "unused" | |
| 59 | - | }), | |
| 59 | + | }, nil), | |
| 60 | 60 | GetActiveWS: func() (*entity.Workspace, *component.WorkspaceView) { | |
| 61 | 61 | return ws, nil | |
| 62 | 62 | }, | |
| @@ -71,7 +71,7 @@ func TestWorkspaceCoordinator_ToggleSystemViewRightFocusesExistingPane(t *testin | |||
| 71 | 71 | ||
| 72 | 72 | func TestWorkspaceCoordinator_ToggleSystemViewRightClosesActivePane(t *testing.T) { | |
| 73 | 73 | ctx := context.Background() | |
| 74 | - | uc := usecase.NewManagePanesUseCase(func() string { return "unused" }) | |
| 74 | + | uc := usecase.NewManagePanesUseCase(func() string { return "unused" }, nil) | |
| 75 | 75 | left := testLeafNode("pane-1") | |
| 76 | 76 | left.Pane.URI = "https://example.com" | |
| 77 | 77 | right := testLeafNode("pane-2") | |
| @@ -19,7 +19,7 @@ func TestWorkspaceCoordinator_ToggleSystemViewRightSplitsWithTargetURL(t *testin | |||||
| 19 | id := ids[idx] | 19 | id := ids[idx] | ||
| 20 | idx++ | 20 | idx++ | ||
| 21 | return id | 21 | return id | ||
| 22 | - | }) | 22 | + | }, nil) |
| 23 | 23 | ||||
| 24 | initialPane := entity.NewPane("pane-1") | 24 | initialPane := entity.NewPane("pane-1") | ||
| 25 | initialPane.URI = "https://example.com" | 25 | initialPane.URI = "https://example.com" | ||
| @@ -56,7 +56,7 @@ func TestWorkspaceCoordinator_ToggleSystemViewRightFocusesExistingPane(t *testin | |||||
| 56 | PanesUC: usecase.NewManagePanesUseCase(func() string { | 56 | PanesUC: usecase.NewManagePanesUseCase(func() string { | ||
| 57 | t.Fatal("PanesUC should not be invoked when focusing an existing systemview pane") | 57 | t.Fatal("PanesUC should not be invoked when focusing an existing systemview pane") | ||
| 58 | return "unused" | 58 | return "unused" | ||
| 59 | - | }), | 59 | + | }, nil), |
| 60 | GetActiveWS: func() (*entity.Workspace, *component.WorkspaceView) { | 60 | GetActiveWS: func() (*entity.Workspace, *component.WorkspaceView) { | ||
| 61 | return ws, nil | 61 | return ws, nil | ||
| 62 | }, | 62 | }, | ||
| @@ -71,7 +71,7 @@ func TestWorkspaceCoordinator_ToggleSystemViewRightFocusesExistingPane(t *testin | |||||
| 71 | 71 | ||||
| 72 | func TestWorkspaceCoordinator_ToggleSystemViewRightClosesActivePane(t *testing.T) { | 72 | func TestWorkspaceCoordinator_ToggleSystemViewRightClosesActivePane(t *testing.T) { | ||
| 73 | ctx := context.Background() | 73 | ctx := context.Background() | ||
| 74 | - | uc := usecase.NewManagePanesUseCase(func() string { return "unused" }) | 74 | + | uc := usecase.NewManagePanesUseCase(func() string { return "unused" }, nil) |
| 75 | left := testLeafNode("pane-1") | 75 | left := testLeafNode("pane-1") | ||
| 76 | left.Pane.URI = "https://example.com" | 76 | left.Pane.URI = "https://example.com" | ||
| 77 | right := testLeafNode("pane-2") | 77 | right := testLeafNode("pane-2") | ||