Coverage for tests/ui/test_pipeline_review_page.py: 100%

360 statements  

« prev     ^ index     » next       coverage.py v7.15.0, created at 2026-07-03 15:51 +0000

1import json 

2import os 

3import pytest 

4from unittest.mock import Mock, patch, MagicMock 

5from PyQt6.QtWidgets import QApplication, QMessageBox 

6from unittest.mock import patch 

7 

8from main.ui.pipeline_review_page import PipelineReviewPage 

9 

10 

11@pytest.fixture 

12def pipeline_config_basic(temp_workspace): 

13 """Creates a base config with one patient.""" 

14 config = { 

15 "sub-01": { 

16 "mri": os.path.join(temp_workspace, "sub-01", "anat", "T1w.nii"), 

17 "pet": os.path.join(temp_workspace, "sub-01", "pet", "pet.nii"), 

18 "need_revision": False 

19 } 

20 } 

21 

22 pipeline_dir = os.path.join(temp_workspace, "pipeline") 

23 os.makedirs(pipeline_dir, exist_ok=True) 

24 config_path = os.path.join(pipeline_dir, "01_config.json") 

25 

26 with open(config_path, "w", encoding="utf-8") as f: 

27 json.dump(config, f, indent=4) 

28 

29 return config_path, config 

30 

31 

32@pytest.fixture 

33def pipeline_config_multiple(temp_workspace): 

34 """Creates config with multiple patients and versions.""" 

35 configs = [ 

36 { 

37 "sub-01": { 

38 "mri": os.path.join(temp_workspace, "sub-01", "anat", "T1w.nii"), 

39 "need_revision": False 

40 } 

41 }, 

42 { 

43 "sub-01": { 

44 "mri": os.path.join(temp_workspace, "sub-01", "anat", "T1w.nii"), 

45 "need_revision": False 

46 }, 

47 "sub-02": { 

48 "mri": "path/to/mri2.nii", 

49 "need_revision": True 

50 } 

51 } 

52 ] 

53 

54 pipeline_dir = os.path.join(temp_workspace, "pipeline") 

55 os.makedirs(pipeline_dir, exist_ok=True) 

56 

57 paths = [] 

58 for idx, config in enumerate(configs, start=1): 

59 config_path = os.path.join(pipeline_dir, f"{idx:02d}_config.json") 

60 with open(config_path, "w", encoding="utf-8") as f: 

61 json.dump(config, f, indent=4) 

62 paths.append(config_path) 

63 

64 return paths, configs 

65 

66 

67@pytest.fixture 

68def pipeline_config_need_revision(temp_workspace): 

69 """Creates config with patients needing revision.""" 

70 config = { 

71 "sub-01": { 

72 "mri": os.path.join(temp_workspace, "sub-01", "anat", "T1w.nii"), 

73 "need_revision": True 

74 }, 

75 "sub-02": { 

76 "mri": "path/to/mri2.nii", 

77 "need_revision": False 

78 } 

79 } 

80 

81 pipeline_dir = os.path.join(temp_workspace, "pipeline") 

82 os.makedirs(pipeline_dir, exist_ok=True) 

83 config_path = os.path.join(pipeline_dir, "01_config.json") 

84 

85 with open(config_path, "w", encoding="utf-8") as f: 

86 json.dump(config, f, indent=4) 

87 

88 return config_path, config 

89 

90 

91class TestPipelineReviewPageInitialization: 

92 """Tests for page initialization.""" 

93 

94 def test_initialization_basic(self, qtbot, mock_context, pipeline_config_basic): 

95 """Test basic initialization.""" 

96 config_path, config = pipeline_config_basic 

97 page = PipelineReviewPage(mock_context) 

98 qtbot.addWidget(page) 

99 

100 assert page.context == mock_context 

101 assert page.workspace_path == mock_context["workspace_path"] 

102 assert page.config_path == config_path 

103 assert page.pipeline_config == config 

104 assert page.previous_page is None 

105 assert page.next_page is None 

106 

107 def test_initialization_with_previous_page(self, qtbot, mock_context, pipeline_config_basic): 

108 """Test initialization with a previous page.""" 

109 previous = Mock() 

110 page = PipelineReviewPage(mock_context, previous_page=previous) 

111 qtbot.addWidget(page) 

112 

113 assert page.previous_page == previous 

114 

115 def test_ui_elements_created(self, qtbot, mock_context, pipeline_config_basic): 

116 """Test that all UI elements are created.""" 

117 page = PipelineReviewPage(mock_context) 

118 qtbot.addWidget(page) 

119 

120 assert page.header is not None 

121 assert page.config_info is not None 

122 assert page.info_label is not None 

123 assert isinstance(page.patient_widgets, dict) 

124 

125 def test_patient_widgets_created(self, qtbot, mock_context, pipeline_config_basic): 

126 """Test creation of widgets for patients.""" 

127 page = PipelineReviewPage(mock_context) 

128 qtbot.addWidget(page) 

129 

130 assert "sub-01" in page.patient_widgets 

131 assert page.patient_widgets["sub-01"] is not None 

132 

133 

134class TestConfigFileDetection: 

135 """Tests for config file detection.""" 

136 

137 def test_find_latest_config_single(self, qtbot, mock_context, pipeline_config_basic): 

138 """Test detection with a single config file.""" 

139 config_path, _ = pipeline_config_basic 

140 page = PipelineReviewPage(mock_context) 

141 qtbot.addWidget(page) 

142 

143 assert page.config_path == config_path 

144 assert os.path.basename(page.config_path) == "01_config.json" 

145 

146 def test_find_latest_config_multiple(self, qtbot, mock_context, pipeline_config_multiple): 

147 """Test detection with multiple configs (should pick the latest).""" 

148 paths, configs = pipeline_config_multiple 

149 page = PipelineReviewPage(mock_context) 

150 qtbot.addWidget(page) 

151 

152 # Should pick the config with the highest ID (02_config.json) 

153 assert page.config_path == paths[-1] 

154 assert os.path.basename(page.config_path) == "02_config.json" 

155 assert page.pipeline_config == configs[-1] 

156 

157 def test_find_latest_config_no_pipeline_dir(self, qtbot, mock_context, temp_workspace): 

158 """Test when the pipeline directory does not exist.""" 

159 # Remove the pipeline directory if it exists 

160 pipeline_dir = os.path.join(temp_workspace, "pipeline") 

161 if os.path.exists(pipeline_dir): 

162 import shutil 

163 shutil.rmtree(pipeline_dir) 

164 

165 page = PipelineReviewPage(mock_context) 

166 qtbot.addWidget(page) 

167 

168 expected_path = os.path.join(temp_workspace, "pipeline", "pipeline_config.json") 

169 assert page.config_path == expected_path 

170 assert page.pipeline_config == {} 

171 

172 def test_find_latest_config_no_files(self, qtbot, mock_context, temp_workspace): 

173 """Test when the pipeline directory is empty.""" 

174 pipeline_dir = os.path.join(temp_workspace, "pipeline") 

175 os.makedirs(pipeline_dir, exist_ok=True) 

176 

177 page = PipelineReviewPage(mock_context) 

178 qtbot.addWidget(page) 

179 

180 expected_path = os.path.join(pipeline_dir, "pipeline_config.json") 

181 assert page.config_path == expected_path 

182 assert page.pipeline_config == {} 

183 

184 def test_find_latest_config_invalid_names(self, qtbot, mock_context, temp_workspace): 

185 """Test with config files having invalid names.""" 

186 pipeline_dir = os.path.join(temp_workspace, "pipeline") 

187 os.makedirs(pipeline_dir, exist_ok=True) 

188 

189 # Create files with non-conforming names 

190 invalid_files = ["config.json", "abc_config.json", "test.json"] 

191 for fname in invalid_files: 

192 with open(os.path.join(pipeline_dir, fname), "w") as f: 

193 json.dump({}, f) 

194 

195 page = PipelineReviewPage(mock_context) 

196 qtbot.addWidget(page) 

197 

198 # Should use default because no file is valid 

199 expected_path = os.path.join(pipeline_dir, "pipeline_config.json") 

200 assert page.config_path == expected_path 

201 

202 

203class TestConfigLoading: 

204 """Tests for configuration loading.""" 

205 

206 def test_load_config_valid(self, qtbot, mock_context, pipeline_config_basic): 

207 """Test loading a valid config.""" 

208 config_path, expected_config = pipeline_config_basic 

209 page = PipelineReviewPage(mock_context) 

210 qtbot.addWidget(page) 

211 

212 assert page.pipeline_config == expected_config 

213 

214 def test_load_config_invalid_json(self, qtbot, mock_context, temp_workspace, mock_logger): 

215 """Test loading a config with invalid JSON.""" 

216 pipeline_dir = os.path.join(temp_workspace, "pipeline") 

217 os.makedirs(pipeline_dir, exist_ok=True) 

218 config_path = os.path.join(pipeline_dir, "01_config.json") 

219 

220 # Write invalid JSON 

221 with open(config_path, "w") as f: 

222 f.write("{invalid json") 

223 

224 with patch("main.ui.pipeline_review_page.log", mock_logger): 

225 page = PipelineReviewPage(mock_context) 

226 qtbot.addWidget(page) 

227 

228 assert page.pipeline_config == {} 

229 mock_logger.error.assert_called_once() 

230 

231 def test_load_config_file_not_found(self, qtbot, mock_context, temp_workspace, mock_logger): 

232 """Test when the config file does not exist.""" 

233 with patch("main.ui.pipeline_review_page.log", mock_logger): 

234 page = PipelineReviewPage(mock_context) 

235 qtbot.addWidget(page) 

236 

237 assert page.pipeline_config == {} 

238 mock_logger.warning.assert_called_once() 

239 

240 

241class TestSaveConfiguration: 

242 """Tests for saving the configuration.""" 

243 

244 def test_save_single_patient(self, qtbot, mock_context, pipeline_config_basic): 

245 """Test saving a single patient's configuration.""" 

246 config_path, _ = pipeline_config_basic 

247 page = PipelineReviewPage(mock_context) 

248 qtbot.addWidget(page) 

249 

250 # Modify and save 

251 new_files = { 

252 "mri": "new_path.nii", 

253 "pet": "new_pet.nii", 

254 "need_revision": False 

255 } 

256 page._save_single_patient("sub-01", new_files) 

257 

258 # Verify save to file 

259 with open(config_path, "r", encoding="utf-8") as f: 

260 saved_config = json.load(f) 

261 

262 assert saved_config["sub-01"] == new_files 

263 assert page.pipeline_config["sub-01"] == new_files 

264 

265 def test_save_multiple_patients(self, qtbot, mock_context, pipeline_config_basic): 

266 """Test saving multiple patients.""" 

267 config_path, _ = pipeline_config_basic 

268 page = PipelineReviewPage(mock_context) 

269 qtbot.addWidget(page) 

270 

271 # Add a new patient 

272 new_patient = { 

273 "mri": "path2.nii", 

274 "need_revision": False 

275 } 

276 page._save_single_patient("sub-02", new_patient) 

277 

278 # Verify 

279 with open(config_path, "r", encoding="utf-8") as f: 

280 saved_config = json.load(f) 

281 

282 assert "sub-01" in saved_config 

283 assert "sub-02" in saved_config 

284 assert saved_config["sub-02"] == new_patient 

285 

286 

287class TestOnEnter: 

288 """Tests for the on_enter method.""" 

289 

290 def test_on_enter_no_changes(self, qtbot, mock_context, pipeline_config_basic, mock_logger): 

291 """Test on_enter when there are no changes.""" 

292 page = PipelineReviewPage(mock_context) 

293 qtbot.addWidget(page) 

294 

295 initial_config_path = page.config_path 

296 initial_config = page.pipeline_config.copy() 

297 

298 with patch("main.ui.pipeline_review_page.log", mock_logger): 

299 page.on_enter() 

300 

301 assert page.config_path == initial_config_path 

302 assert page.pipeline_config == initial_config 

303 mock_logger.debug.assert_called_once() 

304 

305 def test_on_enter_new_config_file(self, qtbot, mock_context, pipeline_config_basic, mock_logger): 

306 """Test on_enter when a new config file is created.""" 

307 page = PipelineReviewPage(mock_context) 

308 qtbot.addWidget(page) 

309 

310 # Create new config 

311 pipeline_dir = os.path.join(mock_context["workspace_path"], "pipeline") 

312 new_config_path = os.path.join(pipeline_dir, "02_config.json") 

313 new_config = { 

314 "sub-03": { 

315 "mri": "new_path.nii", 

316 "need_revision": False 

317 } 

318 } 

319 with open(new_config_path, "w", encoding="utf-8") as f: 

320 json.dump(new_config, f) 

321 

322 with patch("main.ui.pipeline_review_page.log", mock_logger): 

323 page.on_enter() 

324 

325 assert page.config_path == new_config_path 

326 assert page.pipeline_config == new_config 

327 mock_logger.info.assert_called_once() 

328 

329 def test_on_enter_config_content_changed(self, qtbot, mock_context, pipeline_config_basic, mock_logger): 

330 """Test on_enter when the config content changes.""" 

331 config_path, _ = pipeline_config_basic 

332 page = PipelineReviewPage(mock_context) 

333 qtbot.addWidget(page) 

334 

335 # Modify the config file directly 

336 modified_config = { 

337 "sub-01": { 

338 "mri": "modified_path.nii", 

339 "need_revision": True 

340 } 

341 } 

342 with open(config_path, "w", encoding="utf-8") as f: 

343 json.dump(modified_config, f) 

344 

345 with patch("main.ui.pipeline_review_page.log", mock_logger): 

346 page.on_enter() 

347 

348 assert page.pipeline_config == modified_config 

349 mock_logger.info.assert_called_once() 

350 

351 

352class TestNextNavigation: 

353 """Tests for navigating to the next page.""" 

354 

355 @patch('ui.pipeline_execution_page.get_bin_path') 

356 def test_next_all_saved(self, mock_get_bin, qtbot, mock_context, pipeline_config_basic): 

357 """Test next when all patients are saved.""" 

358 mock_get_bin.return_value = "/fake/path/to/pipeline_runner" 

359 

360 page = PipelineReviewPage(mock_context) 

361 qtbot.addWidget(page) 

362 

363 result = page.next(mock_context) 

364 

365 assert result != page 

366 assert page.next_page is not None 

367 assert "PipelineExecutionPage" in str(type(page.next_page)) 

368 

369 def test_next_need_revision(self, qtbot, mock_context, pipeline_config_need_revision): 

370 """Test next when patients need revision.""" 

371 config_path, _ = pipeline_config_need_revision 

372 page = PipelineReviewPage(mock_context) 

373 qtbot.addWidget(page) 

374 

375 with patch.object(QMessageBox, 'exec', return_value=QMessageBox.StandardButton.Ok): 

376 result = page.next(mock_context) 

377 

378 assert result == page # Must stay on the current page 

379 assert page.next_page is None 

380 

381 @patch('ui.pipeline_execution_page.get_bin_path') 

382 def test_next_existing_next_page(self, mock_get_bin, qtbot, mock_context, pipeline_config_basic): 

383 """Test next when next_page already exists.""" 

384 mock_get_bin.return_value = "/fake/path/to/pipeline_runner" 

385 

386 page = PipelineReviewPage(mock_context) 

387 qtbot.addWidget(page) 

388 

389 # First call 

390 result1 = page.next(mock_context) 

391 first_next_page = page.next_page 

392 

393 # Simulate returning to the page 

394 page.on_enter() 

395 

396 # Second call 

397 result2 = page.next(mock_context) 

398 

399 # Must reuse the same next_page 

400 assert page.next_page == first_next_page 

401 

402 @patch('ui.pipeline_execution_page.get_bin_path') 

403 def test_next_updates_history(self, mock_get_bin, qtbot, mock_context, pipeline_config_basic): 

404 """Test that next adds to history.""" 

405 mock_get_bin.return_value = "/fake/path/to/pipeline_runner" 

406 

407 page = PipelineReviewPage(mock_context) 

408 qtbot.addWidget(page) 

409 

410 initial_history_len = len(mock_context["history"]) 

411 page.next(mock_context) 

412 

413 assert len(mock_context["history"]) == initial_history_len + 1 

414 assert page.next_page in mock_context["history"] 

415 

416 

417class TestBackNavigation: 

418 """Tests for navigating to the previous page.""" 

419 

420 def test_back_with_output_folder(self, qtbot, mock_context, pipeline_config_basic, mock_logger): 

421 """Test back when output folder exists (must keep config).""" 

422 config_path, _ = pipeline_config_basic 

423 page = PipelineReviewPage(mock_context) 

424 qtbot.addWidget(page) 

425 

426 # Create the output folder 

427 pipeline_dir = os.path.dirname(config_path) 

428 output_folder = os.path.join(pipeline_dir, "01_output") 

429 os.makedirs(output_folder) 

430 

431 previous = Mock() 

432 page.previous_page = previous 

433 

434 with patch("main.ui.pipeline_review_page.log", mock_logger): 

435 result = page.back() 

436 

437 assert result == previous 

438 assert os.path.exists(config_path) # Config must not be deleted 

439 mock_logger.info.assert_called() 

440 

441 def test_back_without_output_folder(self, qtbot, mock_context, pipeline_config_basic, mock_logger): 

442 """Test back when output folder does not exist (must delete config).""" 

443 config_path, _ = pipeline_config_basic 

444 page = PipelineReviewPage(mock_context) 

445 qtbot.addWidget(page) 

446 

447 previous = Mock() 

448 page.previous_page = previous 

449 

450 with patch("main.ui.pipeline_review_page.log", mock_logger): 

451 result = page.back() 

452 

453 assert result == previous 

454 assert not os.path.exists(config_path) # Config must be deleted 

455 mock_logger.info.assert_called() 

456 

457 def test_back_no_previous_page(self, qtbot, mock_context, pipeline_config_basic): 

458 """Test back without previous_page.""" 

459 page = PipelineReviewPage(mock_context) 

460 qtbot.addWidget(page) 

461 

462 result = page.back() 

463 

464 assert result is None 

465 

466 def test_back_calls_on_enter(self, qtbot, mock_context, pipeline_config_basic): 

467 """Test that back calls on_enter on the previous page.""" 

468 page = PipelineReviewPage(mock_context) 

469 qtbot.addWidget(page) 

470 

471 previous = Mock() 

472 page.previous_page = previous 

473 

474 page.back() 

475 

476 previous.on_enter.assert_called_once() 

477 

478 def test_back_error_handling(self, qtbot, mock_context, pipeline_config_basic, mock_logger): 

479 """Test error handling during back.""" 

480 page = PipelineReviewPage(mock_context) 

481 qtbot.addWidget(page) 

482 

483 # Make the config_path invalid 

484 page.config_path = "/invalid/path/config.json" 

485 

486 previous = Mock() 

487 page.previous_page = previous 

488 

489 with patch("main.ui.pipeline_review_page.log", mock_logger): 

490 result = page.back() 

491 

492 assert result == previous 

493 mock_logger.error.assert_called() 

494 

495 

496class TestTranslation: 

497 """Tests for translations.""" 

498 

499 def test_translate_ui_called_on_init(self, qtbot, mock_context, pipeline_config_basic): 

500 """Test that _translate_ui is called during init.""" 

501 with patch.object(PipelineReviewPage, '_translate_ui') as mock_translate: 

502 page = PipelineReviewPage(mock_context) 

503 qtbot.addWidget(page) 

504 

505 mock_translate.assert_called() 

506 

507 def test_translate_ui_updates_labels(self, qtbot, mock_context, pipeline_config_basic): 

508 """Test that _translate_ui updates labels.""" 

509 page = PipelineReviewPage(mock_context) 

510 qtbot.addWidget(page) 

511 

512 # Save original texts 

513 original_header = page.header.text() 

514 

515 # Call _translate_ui 

516 page._translate_ui() 

517 

518 # Verify texts were updated (even if identical) 

519 assert page.header.text() is not None 

520 assert page.config_info.text() is not None 

521 assert page.info_label.text() is not None 

522 

523 def test_language_changed_signal(self, mock_context): 

524 from main.ui.pipeline_review_page import PipelineReviewPage 

525 page = PipelineReviewPage(mock_context) 

526 

527 # Replace the function but reconnect the signal 

528 page._translate_ui = Mock() 

529 mock_context["language_changed"].connect(page._translate_ui) 

530 

531 # Now emitting the signal must trigger the mock 

532 mock_context["language_changed"].emit("it") 

533 

534 page._translate_ui.assert_called_once() 

535 

536 

537class TestUISetup: 

538 """Tests for UI configuration.""" 

539 

540 def test_setup_ui_clears_existing_layout(self, qtbot, mock_context, pipeline_config_basic): 

541 """Test that _setup_ui clears the existing layout.""" 

542 page = PipelineReviewPage(mock_context) 

543 qtbot.addWidget(page) 

544 

545 initial_count = page.main_layout.count() 

546 

547 # Call _setup_ui again 

548 page._setup_ui() 

549 

550 # The count might be the same or different, but it shouldn't crash 

551 assert page.main_layout.count() >= 0 

552 

553 def test_setup_ui_creates_scroll_area(self, qtbot, mock_context, pipeline_config_basic): 

554 """Test that a scroll area is created.""" 

555 page = PipelineReviewPage(mock_context) 

556 qtbot.addWidget(page) 

557 

558 # Search for scroll area in the layout 

559 scroll_found = False 

560 for i in range(page.main_layout.count()): 

561 widget = page.main_layout.itemAt(i).widget() 

562 if widget and "QScrollArea" in str(type(widget)): 

563 scroll_found = True 

564 break 

565 

566 assert scroll_found 

567 

568 def test_patient_widgets_created_for_all_patients(self, qtbot, mock_context, pipeline_config_multiple): 

569 """Test that widgets are created for all patients.""" 

570 paths, configs = pipeline_config_multiple 

571 page = PipelineReviewPage(mock_context) 

572 qtbot.addWidget(page) 

573 

574 # Must have widgets for all patients in the last config 

575 last_config = configs[-1] 

576 for patient_id in last_config.keys(): 

577 assert patient_id in page.patient_widgets 

578 

579 

580class TestEdgeCases: 

581 """Tests for edge cases.""" 

582 

583 def test_empty_config(self, qtbot, mock_context, temp_workspace): 

584 """Test with an empty config.""" 

585 pipeline_dir = os.path.join(temp_workspace, "pipeline") 

586 os.makedirs(pipeline_dir, exist_ok=True) 

587 config_path = os.path.join(pipeline_dir, "01_config.json") 

588 

589 with open(config_path, "w", encoding="utf-8") as f: 

590 json.dump({}, f) 

591 

592 page = PipelineReviewPage(mock_context) 

593 qtbot.addWidget(page) 

594 

595 assert page.pipeline_config == {} 

596 assert len(page.patient_widgets) == 0 

597 

598 def test_very_large_config(self, qtbot, mock_context, temp_workspace): 

599 """Test with many patients.""" 

600 pipeline_dir = os.path.join(temp_workspace, "pipeline") 

601 os.makedirs(pipeline_dir, exist_ok=True) 

602 config_path = os.path.join(pipeline_dir, "01_config.json") 

603 

604 # Create config with 100 patients 

605 large_config = { 

606 f"sub-{i:03d}": { 

607 "mri": f"path{i}.nii", 

608 "need_revision": False 

609 } 

610 for i in range(100) 

611 } 

612 

613 with open(config_path, "w", encoding="utf-8") as f: 

614 json.dump(large_config, f) 

615 

616 page = PipelineReviewPage(mock_context) 

617 qtbot.addWidget(page) 

618 

619 assert len(page.pipeline_config) == 100 

620 assert len(page.patient_widgets) == 100 

621 

622 def test_special_characters_in_patient_id(self, qtbot, mock_context, temp_workspace): 

623 """Test with special characters in patient IDs.""" 

624 pipeline_dir = os.path.join(temp_workspace, "pipeline") 

625 os.makedirs(pipeline_dir, exist_ok=True) 

626 config_path = os.path.join(pipeline_dir, "01_config.json") 

627 

628 config = { 

629 "sub-01_special-chars": { 

630 "mri": "path.nii", 

631 "need_revision": False 

632 } 

633 } 

634 

635 with open(config_path, "w", encoding="utf-8") as f: 

636 json.dump(config, f) 

637 

638 page = PipelineReviewPage(mock_context) 

639 qtbot.addWidget(page) 

640 

641 assert "sub-01_special-chars" in page.patient_widgets 

642 

643 def test_unicode_in_paths(self, qtbot, mock_context, temp_workspace): 

644 """Test with unicode characters in paths.""" 

645 pipeline_dir = os.path.join(temp_workspace, "pipeline") 

646 os.makedirs(pipeline_dir, exist_ok=True) 

647 config_path = os.path.join(pipeline_dir, "01_config.json") 

648 

649 config = { 

650 "sub-01": { 

651 "mri": "path/àèéìòù/file.nii", 

652 "need_revision": False 

653 } 

654 } 

655 

656 with open(config_path, "w", encoding="utf-8") as f: 

657 json.dump(config, f) 

658 

659 page = PipelineReviewPage(mock_context) 

660 qtbot.addWidget(page) 

661 

662 assert page.pipeline_config["sub-01"]["mri"] == "path/àèéìòù/file.nii"