Line data Source code
1 : -- lua/yoda/picker_handler.lua
2 : -- Test picker UI handling - Wizard pattern with low complexity
3 : -- All functions refactored to have cyclomatic complexity ≤ 7
4 :
5 1 : local M = {}
6 :
7 : -- ============================================================================
8 : -- Constants
9 : -- ============================================================================
10 :
11 1 : local DEFAULT_MARKERS = {
12 : "bdd",
13 : "unit",
14 : "functional",
15 : "smoke",
16 : "critical",
17 : "performance",
18 : "regression",
19 : "integration",
20 : }
21 :
22 1 : local ALLURE_OPTIONS = {
23 : "Yes, open Allure report",
24 : "No, skip Allure report",
25 : }
26 :
27 2 : local CACHE_FILE_PATH = vim.fn.stdpath("cache") .. "/yoda_testpicker_marker.json"
28 :
29 : -- ============================================================================
30 : -- Cache Management (Extracted for DRY)
31 : -- ============================================================================
32 :
33 : --- Load cached test configuration
34 : --- Complexity: 1
35 : --- @return table Cached configuration
36 : local function load_cached_config()
37 23 : local config_loader = require("yoda.config_loader")
38 23 : return config_loader.load_marker(CACHE_FILE_PATH)
39 1 : end
40 :
41 : --- Save test configuration to cache
42 : --- Complexity: 1
43 : --- @param env string Environment
44 : --- @param region string Region
45 : --- @param markers string Markers
46 : --- @param open_allure boolean Allure preference
47 : local function save_cached_config(env, region, markers, open_allure)
48 5 : local config_loader = require("yoda.config_loader")
49 5 : config_loader.save_marker(CACHE_FILE_PATH, env, region, markers, open_allure)
50 6 : end
51 :
52 : -- ============================================================================
53 : -- Reordering Logic (Extracted for DRY - Used in 5 places!)
54 : -- ============================================================================
55 :
56 : --- Reorder items to show default first
57 : --- Complexity: 4 (1 if + 1 insert + 1 loop + 1 conditional insert)
58 : --- @param items table Array of items
59 : --- @param default any Default item to show first
60 : --- @return table Reordered items
61 : local function reorder_with_default_first(items, default)
62 23 : if not default then
63 1 : return items
64 : end
65 :
66 22 : local reordered = { default }
67 57 : for _, item in ipairs(items) do
68 35 : if item ~= default then
69 13 : table.insert(reordered, item)
70 : end
71 : end
72 22 : return reordered
73 1 : end
74 :
75 : -- ============================================================================
76 : -- Picker Wrapper (Extracted for DRY)
77 : -- ============================================================================
78 :
79 : --- Safe picker selection with cancellation handling
80 : --- Complexity: 2 (1 if + callback)
81 : --- @param items table Items to select from
82 : --- @param options table Picker options
83 : --- @param callback function Callback function
84 : local function safe_picker_select(items, options, callback)
85 23 : local picker = require("snacks.picker")
86 46 : picker.select(items, options, function(selection)
87 23 : if not selection then
88 3 : callback(nil)
89 3 : return
90 : end
91 20 : callback(selection)
92 43 : end)
93 24 : end
94 :
95 : -- ============================================================================
96 : -- Data Extraction Helpers
97 : -- ============================================================================
98 :
99 : --- Extract environment names from configuration
100 : --- Complexity: 3 (1 if + 1 loop + 1 sort)
101 : --- @param env_region table Environment configuration
102 : --- @return table Sorted list of environment names
103 : local function extract_env_names(env_region)
104 7 : if env_region.env_order then
105 7 : return env_region.env_order
106 : end
107 :
108 : local env_names = {}
109 : for env_name, _ in pairs(env_region.environments or env_region) do
110 : table.insert(env_names, env_name)
111 : end
112 : table.sort(env_names)
113 : return env_names
114 1 : end
115 :
116 : --- Parse environment and region from combined label
117 : --- @param label string Label in format "env (region)"
118 : --- @return string|nil, string|nil Environment and region
119 : local function parse_env_region_label(label)
120 4 : return label:match("^(.+)%s+%((.+)%)$")
121 1 : end
122 :
123 : --- Determine Allure preference from selection
124 : --- Complexity: 2 (1 if + 1 comparison)
125 : --- @param selection string|nil Selected option
126 : --- @param default boolean Default preference
127 : --- @return boolean Whether to open Allure
128 : local function determine_allure_preference(selection, default)
129 5 : if selection == nil then
130 : return default
131 : end
132 5 : return selection == ALLURE_OPTIONS[1]
133 1 : end
134 :
135 : --- Generate environment-region combination labels
136 : --- Complexity: 2 (2 nested loops)
137 : --- @param env_region table Environment and region configuration
138 : --- @return table List of formatted labels
139 : local function generate_env_region_labels(env_region)
140 5 : local items = {}
141 11 : for _, env in ipairs(env_region.environments) do
142 14 : for _, region in ipairs(env_region.regions) do
143 8 : table.insert(items, env .. " (" .. region .. ")")
144 : end
145 : end
146 5 : return items
147 1 : end
148 :
149 : -- ============================================================================
150 : -- Wizard Steps (Each step is now a focused function)
151 : -- ============================================================================
152 :
153 : --- Wizard Step 1: Select Environment
154 : --- Complexity: 4 (1 load + 1 reorder + 1 picker + 1 callback check)
155 : --- @param env_names table Available environments
156 : --- @param callback function Callback(selected_env)
157 : local function wizard_step_select_environment(env_names, callback)
158 7 : local cached = load_cached_config()
159 7 : local reordered = reorder_with_default_first(env_names, cached.environment)
160 :
161 14 : safe_picker_select(reordered, { prompt = "Select Environment:" }, function(selected_env)
162 7 : if not selected_env then
163 1 : callback(nil)
164 1 : return
165 : end
166 6 : callback(selected_env)
167 13 : end)
168 8 : end
169 :
170 : --- Wizard Step 2: Select Region
171 : --- Complexity: 5 (1 lookup + 1 check + 1 load + 1 reorder + 1 picker)
172 : --- @param env_region table Environment configuration
173 : --- @param selected_env string Selected environment
174 : --- @param callback function Callback(selected_region)
175 : local function wizard_step_select_region(env_region, selected_env, callback)
176 6 : local regions = (env_region.environments or env_region)[selected_env]
177 :
178 6 : if not regions or #regions == 0 then
179 : vim.notify("No regions found for environment: " .. selected_env, vim.log.levels.WARN)
180 : callback(nil)
181 : return
182 : end
183 :
184 6 : local cached = load_cached_config()
185 6 : local reordered = reorder_with_default_first(regions, cached.region)
186 :
187 12 : safe_picker_select(reordered, {
188 6 : prompt = "Select Region for " .. selected_env .. ":",
189 6 : }, callback)
190 7 : end
191 :
192 : --- Wizard Step 3: Select Markers (multiselect)
193 : --- Complexity: 4 (1 load + 1 check + 1 picker)
194 : --- @param callback function Callback(selected_markers)
195 : local function wizard_step_select_markers(callback)
196 5 : local picker = require("yoda-adapters.picker")
197 :
198 10 : picker.multiselect(DEFAULT_MARKERS, {
199 : prompt = "Select Test Markers (Tab to toggle selection, Enter to confirm):",
200 : }, function(selected)
201 5 : if not selected or #selected == 0 then
202 : callback(nil)
203 : return
204 : end
205 : -- Join markers with comma for storage
206 5 : local markers_str = table.concat(selected, ",")
207 5 : callback(markers_str)
208 10 : end)
209 6 : end
210 :
211 : --- Wizard Step 4: Select Allure Preference
212 : --- Complexity: 3 (1 load + 1 reorder + 1 picker)
213 : --- @param callback function Callback(open_allure)
214 : local function wizard_step_select_allure(callback)
215 5 : local cached = load_cached_config()
216 5 : local default_allure = cached.open_allure or false
217 5 : local default_text = default_allure and ALLURE_OPTIONS[1] or ALLURE_OPTIONS[2]
218 :
219 5 : local reordered = reorder_with_default_first(ALLURE_OPTIONS, default_text)
220 :
221 10 : safe_picker_select(reordered, {
222 : prompt = "Generate Allure Report?",
223 : }, function(selected)
224 5 : if not selected then
225 : callback(nil)
226 : return
227 : end
228 10 : callback(determine_allure_preference(selected, default_allure))
229 10 : end)
230 6 : end
231 :
232 : -- ============================================================================
233 : -- Configuration Display and Command Generation
234 : -- ============================================================================
235 :
236 : -- NOTE: Pytest command generation functions have been moved to pytest-atlas.nvim plugin
237 :
238 : -- ============================================================================
239 : -- Wizard Orchestration (Chain of wizard steps)
240 : -- ============================================================================
241 :
242 : --- Execute YAML-based multi-step wizard
243 : --- Complexity: 2 (1 extract + 1 step1 call)
244 : --- @param env_region table Environment to regions mapping
245 : --- @param callback function Callback function
246 1 : function M.handle_yaml_selection(env_region, callback)
247 7 : local env_names = extract_env_names(env_region)
248 :
249 : -- Chain wizard steps using extracted functions
250 14 : wizard_step_select_environment(env_names, function(selected_env)
251 7 : if not selected_env then
252 1 : callback(nil)
253 1 : return
254 : end
255 :
256 12 : wizard_step_select_region(env_region, selected_env, function(selected_region)
257 6 : if not selected_region then
258 1 : callback(nil)
259 1 : return
260 : end
261 :
262 10 : wizard_step_select_markers(function(markers_to_use)
263 5 : if not markers_to_use then
264 : callback(nil)
265 : return
266 : end
267 :
268 10 : wizard_step_select_allure(function(open_allure)
269 5 : if open_allure == nil then
270 : callback(nil)
271 : return
272 : end
273 :
274 : -- Save and return results
275 5 : save_cached_config(selected_env, selected_region, markers_to_use, open_allure)
276 :
277 : -- NOTE: Configuration preview moved to pytest-atlas.nvim plugin
278 :
279 10 : callback({
280 5 : environment = selected_env,
281 5 : region = selected_region,
282 5 : markers = markers_to_use,
283 5 : open_allure = open_allure,
284 : })
285 10 : end)
286 10 : end)
287 11 : end)
288 13 : end)
289 8 : end
290 :
291 : --- Handle JSON/fallback single-step selection
292 : --- Complexity: 6 (1 generate + 1 load + 1 if + 1 reorder + 1 picker + 1 parse check)
293 : --- @param env_region table Environment and region configuration
294 : --- @param callback function Callback function
295 1 : function M.handle_json_selection(env_region, callback)
296 5 : local items = generate_env_region_labels(env_region)
297 5 : local cached = load_cached_config()
298 :
299 5 : local default_label = nil
300 5 : if cached.environment and cached.region then
301 5 : default_label = cached.environment .. " (" .. cached.region .. ")"
302 : end
303 :
304 5 : local reordered = reorder_with_default_first(items, default_label)
305 :
306 10 : safe_picker_select(reordered, { prompt = "Select Test Environment:" }, function(choice)
307 5 : if not choice then
308 1 : callback(nil)
309 1 : return
310 : end
311 :
312 4 : local env, region = parse_env_region_label(choice)
313 4 : if env and region then
314 6 : callback({ environment = env, region = region })
315 : else
316 1 : vim.notify("Failed to parse environment and region from selection", vim.log.levels.ERROR)
317 1 : callback(nil)
318 : end
319 9 : end)
320 6 : end
321 :
322 : -- ============================================================================
323 : -- Public API
324 : -- ============================================================================
325 :
326 : -- NOTE: Test configuration display and command generation functions have been moved to pytest-atlas.nvim plugin
327 :
328 1 : return M
|