diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md index 5bd233d46..64de0d494 100644 --- a/docs/USER_GUIDE.md +++ b/docs/USER_GUIDE.md @@ -77,7 +77,7 @@ A new frame gets the roll's film type, and the crop, print stock and Lab setting ### Viewing -* **Before / After** (`\`, or the **Print** menu): splits the canvas between the default conversion and your edit. Hold `\` to see the default alone. +* **Before / After** (`\`, or the **Print** menu): splits the canvas between the default conversion and your edit. Hold `\` to see the default alone. To compare with an earlier version, right-click it in **History**. * **Peeks**: hold `N` for the scan as loaded (the negative), `M` for the flat master, and `P` for the camera's own JPEG. The **Print** menu has them too. * **Reference View** (`Shift+R`): pins this frame beside the canvas, to match other frames to it. * **Probe at Cursor** (`Shift+I`, **Overlays** menu): shows the density and zone under the pointer. @@ -441,13 +441,13 @@ A **work print** is a version of this frame that you keep under a name, like the * **Save Work Print…** (`Ctrl+Shift+S`) keeps the current edit. * **Click** a work print to use it. `Ctrl+Z` goes back. -* **⋮** or right-click: **Export This Version…**, **Rename…** or **Delete…**. +* **⋮** or right-click: **Compare With This Version**, **Export This Version…**, **Rename…** or **Delete…**. **Compare With This Version** opens **Before / After** with the work print on the left. * **Load Metadata** (off at first): also restores the metadata saved with the work print. ### Edit history -Every change you made, newest on top (the last 100). **Now** marks the current state; after an undo, later steps show as **Undone** until your next edit. Click a step to go back to it. **⋮** or right-click → **Export This Version…** exports a past state. +Every change you made, newest on top (the last 100). **Now** marks the current state; after an undo, later steps show as **Undone** until your next edit. Click a step to go back to it. **⋮** or right-click → **Export This Version…** exports a past state. **Compare With This Step** opens **Before / After** with that step on the left, on your current crop. Press `\` to close it. --- diff --git a/negpy/desktop/controller.py b/negpy/desktop/controller.py index 0eb265950..2567c4269 100644 --- a/negpy/desktop/controller.py +++ b/negpy/desktop/controller.py @@ -412,6 +412,24 @@ def baseline_compare_config(config: WorkspaceConfig) -> WorkspaceConfig: ) +def reference_compare_config(reference: WorkspaceConfig, config: WorkspaceConfig) -> WorkspaceConfig: + """ + The 'before' config for a split against a history step or work print: its look on the + current framing, composite, flat field and output settings, so both halves line up. + The preview source is not decoded again, so a step's other source settings take no effect. + """ + return replace( + reference, + geometry=config.geometry, + stitch=config.stitch, + hdr=config.hdr, + flatfield=config.flatfield, + rgbscan=config.rgbscan, + metadata=config.metadata, + export=config.export, + ) + + # Solved knee fields under their slider names (sidebar/tone.py). _KNEE_LABELS = { "shadow_grade": tr("Shadows Grade"), @@ -7369,6 +7387,9 @@ def request_render( self.render_requested.emit(task) def _baseline_compare_config(self) -> WorkspaceConfig: + reference = self.state.compare_reference + if reference is not None: + return reference_compare_config(reference, self.state.config) return baseline_compare_config(self.state.config) def _compare_before_key(self) -> str: @@ -7404,10 +7425,42 @@ def exit_compare(self) -> None: self.state.compare_before = None self.state.compare_before_rect = None self.state.compare_before_key = "" + self.state.compare_reference = None + self.state.compare_reference_label = "" if self.state.compare_mode: self.state.compare_mode = False self.compare_changed.emit(False) + def compare_with_history_step(self, index: int) -> None: + """Open the split against a history step instead of the default conversion.""" + file_hash = self.state.current_file_hash + if not file_hash: + return + # The live top step may not be persisted yet: it lives in state.config. + config = self.state.config if index == self.state.undo_index else self.session.repo.load_history_step(file_hash, index) + self._compare_with(config, tr("STEP {number}", number=index)) + + def compare_with_work_print(self, name: str) -> None: + """Open the split against a work print instead of the default conversion.""" + file_hash = self.state.current_file_hash + if not file_hash: + return + self._compare_with(self.session.repo.load_work_print(file_hash, name), name.upper()) + + def _compare_with(self, config: Optional[WorkspaceConfig], label: str) -> None: + if config is None or self.state.preview_raw is None: + return + self.state.compare_reference = config + self.state.compare_reference_label = label + if not self.state.compare_mode: + self.toggle_compare() + return + # Drop the old reference's pixels, or they show under the new badge until the render lands. + self.state.compare_before = None + self.state.compare_before_key = "" + self.compare_frame_ready.emit() + self._request_compare_baseline() + def begin_compare_hold(self) -> None: """Show the baseline over the whole picture until end_compare_hold. Only over the print: a peek, the test strip or an uncropped tool view has no baseline that lines up.""" @@ -7434,7 +7487,8 @@ def end_compare_hold(self) -> None: self.compare_hold_changed.emit(False) def toggle_compare(self) -> None: - """Toggle the before/after split between the edit and the auto baseline.""" + """Toggle the before/after split between the edit and the auto baseline, or the + history step or work print set by _compare_with.""" if self.state.preview_raw is None: return if self.state.compare_mode: diff --git a/negpy/desktop/session.py b/negpy/desktop/session.py index 4767533f2..45559aebe 100644 --- a/negpy/desktop/session.py +++ b/negpy/desktop/session.py @@ -328,6 +328,10 @@ class AppState: compare_split: float = 0.5 # True while the compare key is held: the whole picture shows the baseline. compare_hold: bool = False + # A history step or work print the split compares against instead of the default + # conversion, and its badge text. Held as a config: a later edit can delete the step. + compare_reference: Optional[Any] = None + compare_reference_label: str = "" # Export presets (globally managed, not per-file) export_presets: List[ExportPreset] = field(default_factory=list) diff --git a/negpy/desktop/view/canvas/overlay.py b/negpy/desktop/view/canvas/overlay.py index 739771c8d..140a4a028 100644 --- a/negpy/desktop/view/canvas/overlay.py +++ b/negpy/desktop/view/canvas/overlay.py @@ -1396,8 +1396,9 @@ def _draw_compare_split(self, painter: QPainter) -> None: knob = QRectF(centre.x() - _SPLIT_HANDLE_PX, centre.y() - _SPLIT_HANDLE_PX, 2 * _SPLIT_HANDLE_PX, 2 * _SPLIT_HANDLE_PX) painter.drawText(knob, Qt.AlignmentFlag.AlignCenter, "◂▸") - if x - target.left() > 92: - draw_view_badge(painter, tr("BEFORE"), target.left() + THEME.space_xl, target.top() + THEME.space_xl) + before_text = self.state.compare_reference_label or tr("BEFORE") + if x - target.left() > max(92.0, view_badge_width(painter, before_text) + 2 * THEME.space_xl): + draw_view_badge(painter, before_text, target.left() + THEME.space_xl, target.top() + THEME.space_xl) if target.right() - x > 92: after = tr("AFTER") draw_view_badge( diff --git a/negpy/desktop/view/sidebar/history.py b/negpy/desktop/view/sidebar/history.py index 8669c4e32..1d78c64a0 100644 --- a/negpy/desktop/view/sidebar/history.py +++ b/negpy/desktop/view/sidebar/history.py @@ -218,9 +218,14 @@ def _on_context_menu(self, pos) -> None: if item is None: return menu = QMenu(self) + # The current step is the After half, so comparing against it shows nothing. + compare_action = None if item.data(_TAG_ROLE) == "now" else menu.addAction(tr("Compare With This Step")) export_action = menu.addAction(tr("Export This Version…")) - if menu.exec(self.list.mapToGlobal(pos)) is export_action: + chosen = menu.exec(self.list.mapToGlobal(pos)) + if chosen is export_action: self.controller.export_history_step(item.data(_INDEX_ROLE)) + elif chosen is not None and chosen is compare_action: + self.controller.compare_with_history_step(item.data(_INDEX_ROLE)) def _on_work_print_menu(self, pos) -> None: item = self.work_prints.itemAt(pos) @@ -228,11 +233,14 @@ def _on_work_print_menu(self, pos) -> None: return name = item.text() menu = QMenu(self) + compare_action = menu.addAction(tr("Compare With This Version")) export_action = menu.addAction(tr("Export This Version…")) rename_action = menu.addAction(tr("Rename…")) delete_action = menu.addAction(tr("Delete…")) chosen = menu.exec(self.work_prints.mapToGlobal(pos)) - if chosen is export_action: + if chosen is compare_action: + self.controller.compare_with_work_print(name) + elif chosen is export_action: self.controller.export_work_print(name) elif chosen is rename_action: new_name, ok = QInputDialog.getText(self, tr("Rename Work Print"), tr("Name:"), text=name) diff --git a/negpy/translations/USER_GUIDE.en.md b/negpy/translations/USER_GUIDE.en.md index 5bd233d46..64de0d494 100644 --- a/negpy/translations/USER_GUIDE.en.md +++ b/negpy/translations/USER_GUIDE.en.md @@ -77,7 +77,7 @@ A new frame gets the roll's film type, and the crop, print stock and Lab setting ### Viewing -* **Before / After** (`\`, or the **Print** menu): splits the canvas between the default conversion and your edit. Hold `\` to see the default alone. +* **Before / After** (`\`, or the **Print** menu): splits the canvas between the default conversion and your edit. Hold `\` to see the default alone. To compare with an earlier version, right-click it in **History**. * **Peeks**: hold `N` for the scan as loaded (the negative), `M` for the flat master, and `P` for the camera's own JPEG. The **Print** menu has them too. * **Reference View** (`Shift+R`): pins this frame beside the canvas, to match other frames to it. * **Probe at Cursor** (`Shift+I`, **Overlays** menu): shows the density and zone under the pointer. @@ -441,13 +441,13 @@ A **work print** is a version of this frame that you keep under a name, like the * **Save Work Print…** (`Ctrl+Shift+S`) keeps the current edit. * **Click** a work print to use it. `Ctrl+Z` goes back. -* **⋮** or right-click: **Export This Version…**, **Rename…** or **Delete…**. +* **⋮** or right-click: **Compare With This Version**, **Export This Version…**, **Rename…** or **Delete…**. **Compare With This Version** opens **Before / After** with the work print on the left. * **Load Metadata** (off at first): also restores the metadata saved with the work print. ### Edit history -Every change you made, newest on top (the last 100). **Now** marks the current state; after an undo, later steps show as **Undone** until your next edit. Click a step to go back to it. **⋮** or right-click → **Export This Version…** exports a past state. +Every change you made, newest on top (the last 100). **Now** marks the current state; after an undo, later steps show as **Undone** until your next edit. Click a step to go back to it. **⋮** or right-click → **Export This Version…** exports a past state. **Compare With This Step** opens **Before / After** with that step on the left, on your current crop. Press `\` to close it. --- diff --git a/negpy/translations/USER_GUIDE.ja.md b/negpy/translations/USER_GUIDE.ja.md index fbaf7aeae..f6dc9ce0f 100644 --- a/negpy/translations/USER_GUIDE.ja.md +++ b/negpy/translations/USER_GUIDE.ja.md @@ -285,20 +285,20 @@ NegPy は、フィルムスキャンをポジ画像に仕上げる仮想暗室 - **保存…**:現在の設定のうちどれを新しいプリセットにするかを選びます。プリセットにはトリミングと光学補正の設定をフラットフィールドのプロファイルと共に保存できますが、フレームごとのトリミング、回転、反転は保存しません。 - **ペン**と**ゴミ箱**:選択したプリセットを編集または削除します。 - + ## ワークプリント **ワークプリント**は、名前を付けて残すこのフレームのバージョンです。仕上げまでに作るテストプリントと同じです。 - **ワークプリントを保存…**(`Ctrl+Shift+S`)で現在の編集を残します。 - ワークプリントを**クリック**すると使います。`Ctrl+Z` で戻ります。 -- **⋮** または右クリック:**このバージョンをエクスポート…**、**名前を変更…**、**削除…**。 +- **⋮** または右クリック:**このバージョンと比較**、**このバージョンをエクスポート…**、**名前を変更…**、**削除…**。**このバージョンと比較**は、ワークプリントを左側にして**ビフォー / アフター**を開きます。 - **メタデータのロード**(最初はオフ):ワークプリントと一緒に保存したメタデータも戻します。 - + ## 編集履歴 -行ったすべての変更を新しい順に表示します(最新 100 件)。**現在**が今の状態です。元に戻した後は、次に編集するまで後の手順が**取り消し済み**になります。手順をクリックするとその状態に戻ります。**⋮** または右クリック →**このバージョンをエクスポート…**で過去の状態を書き出します。 +行ったすべての変更を新しい順に表示します(最新 100 件)。**現在**が今の状態です。元に戻した後は、次に編集するまで後の手順が**取り消し済み**になります。手順をクリックするとその状態に戻ります。**⋮** または右クリック →**このバージョンをエクスポート…**で過去の状態を書き出します。**このステップと比較**は、そのステップを左側にして、現在のトリミングで**ビフォー / アフター**を開きます。`\` で閉じます。 ## セットアップ diff --git a/negpy/translations/USER_GUIDE.ko.md b/negpy/translations/USER_GUIDE.ko.md index 00d84f303..0443e3f1f 100644 --- a/negpy/translations/USER_GUIDE.ko.md +++ b/negpy/translations/USER_GUIDE.ko.md @@ -285,20 +285,20 @@ NegPy는 필름 스캔을 포지티브 이미지로 만드는 가상 암실입 - **저장…**: 현재 설정 중 무엇을 새 사전 설정으로 저장할지 고릅니다. 사전 설정에는 자르기와 광학 보정 설정을 플랫 필드 프로필과 함께 저장할 수 있지만, 프레임별 자르기, 회전, 뒤집기는 저장하지 않습니다. - **펜**과 **휴지통**: 선택한 사전 설정을 편집하거나 삭제합니다. - + ## 작업 인화물 **작업 인화**는 이름을 붙여 보관하는 이 프레임의 버전으로, 최종 인화까지 만드는 테스트 인화와 같습니다. - **작업 인화 저장…**(`Ctrl+Shift+S`)으로 현재 편집을 보관합니다. - 작업 인화를 **클릭**하면 사용합니다. `Ctrl+Z`로 되돌립니다. -- **⋮** 또는 오른쪽 클릭: **이 버전 내보내기…**, **이름 바꾸기…**, **삭제…**. +- **⋮** 또는 오른쪽 클릭: **이 버전과 비교**, **이 버전 내보내기…**, **이름 바꾸기…**, **삭제…**. **이 버전과 비교**는 작업 인화를 왼쪽에 두고 **전/후**를 엽니다. - **메타데이터 로드**(처음에는 꺼짐): 작업 인화와 함께 저장한 메타데이터도 되돌립니다. - + ## 편집 기록 -내가 한 모든 변경을 최신순으로 보여줍니다(최근 100개). **현재**는 지금 상태입니다. 실행 취소 뒤에는 다음 편집 전까지 이후 단계가 **취소됨**으로 표시됩니다. 단계를 클릭하면 그 상태로 돌아갑니다. **⋮** 또는 오른쪽 클릭 → **이 버전 내보내기…**로 지난 상태를 내보냅니다. +내가 한 모든 변경을 최신순으로 보여줍니다(최근 100개). **현재**는 지금 상태입니다. 실행 취소 뒤에는 다음 편집 전까지 이후 단계가 **취소됨**으로 표시됩니다. 단계를 클릭하면 그 상태로 돌아갑니다. **⋮** 또는 오른쪽 클릭 → **이 버전 내보내기…**로 지난 상태를 내보냅니다. **이 단계와 비교**는 그 단계를 왼쪽에 두고 현재 자르기로 **전/후**를 엽니다. `\`를 누르면 닫힙니다. ## 기본 설정 diff --git a/negpy/translations/USER_GUIDE.zh_CN.md b/negpy/translations/USER_GUIDE.zh_CN.md index bf2f1485a..e34d096bd 100644 --- a/negpy/translations/USER_GUIDE.zh_CN.md +++ b/negpy/translations/USER_GUIDE.zh_CN.md @@ -285,20 +285,20 @@ NegPy 通过虚拟暗房将胶片扫描制作为正像。源图像保持原样 - **保存…**:选择当前设置中哪些保存为新预设。预设可保存裁切和光学设置及平场配置,但从不保存单帧的裁切、旋转或翻转。 - **笔**和**垃圾桶**:编辑或删除所选预设。 - + ## 工作样片 **工作样片**是你以名称保留的此帧版本,就像得到最终照片前的试印。 - **保存工作样片…**(`Ctrl+Shift+S`)保留当前编辑。 - **点击**工作样片即可使用。`Ctrl+Z` 返回。 -- **⋮** 或右键:**导出此版本…**、**重命名…**或**删除…**。 +- **⋮** 或右键:**与此版本对比**、**导出此版本…**、**重命名…**或**删除…**。**与此版本对比**会打开**前后对比**,工作样片在左侧。 - **加载元数据**(初始关闭):同时恢复随工作样片保存的元数据。 - + ## 编辑历史 -你做过的每一次更改,最新的在上(保留最近 100 次)。**当前**标出现在的状态;撤销后,之后的步骤会显示为**已撤销**,直到你下一次编辑。点击某一步即可回到那时的状态。**⋮** 或右键 → **导出此版本…**可导出过去的状态。 +你做过的每一次更改,最新的在上(保留最近 100 次)。**当前**标出现在的状态;撤销后,之后的步骤会显示为**已撤销**,直到你下一次编辑。点击某一步即可回到那时的状态。**⋮** 或右键 → **导出此版本…**可导出过去的状态。**与此步骤对比**会以当前裁剪打开**前后对比**,该步骤在左侧。按 `\` 关闭。 ## 基础设置 diff --git a/negpy/translations/ja.json b/negpy/translations/ja.json index 843ab5265..ec7f27855 100644 --- a/negpy/translations/ja.json +++ b/negpy/translations/ja.json @@ -521,6 +521,8 @@ "Combines the short and long exposure fusion with multi-pass stacking for the most range and least noise, the slowest option.": "短い露出と長い露出の合成をマルチパス スタッキングと組み合わせ、レンジを最大に、ノイズを最小にします。最も遅いオプションです。", "Commit the solved print (Enter)": "求めたプリントを確定します (Enter)", "Compare": "比較", + "Compare With This Step": "このステップと比較", + "Compare With This Version": "このバージョンと比較", "Composition guide shown in the crop tool": "トリミングツールに表示する構図ガイド", "Compression": "圧縮", "Compute and Save": "計算して保存する", @@ -2090,6 +2092,7 @@ "SELECTED MASK": "選択中のマスク", "SIZE": "サイズ", "SPLIT TONING": "スプリットトーン", + "STEP {number}": "ステップ {number}", "Sabattier": "サバティエ", "Sabattier Strength": "サバティエの強さ", "Sabattier Strength ↑/↓": "サバティエの強さ ↑/↓", diff --git a/negpy/translations/ko.json b/negpy/translations/ko.json index e6574ae93..c89db83c6 100644 --- a/negpy/translations/ko.json +++ b/negpy/translations/ko.json @@ -521,6 +521,8 @@ "Combines the short and long exposure fusion with multi-pass stacking for the most range and least noise, the slowest option.": "짧은·긴 노출 퓨전과 멀티 패스 스태킹을 결합해 가장 넓은 계조와 가장 적은 노이즈를 얻는, 가장 느린 옵션입니다.", "Commit the solved print (Enter)": "계산된 인화 적용(Enter)", "Compare": "비교", + "Compare With This Step": "이 단계와 비교", + "Compare With This Version": "이 버전과 비교", "Composition guide shown in the crop tool": "자르기 도구에 표시되는 구도 가이드", "Compression": "압축", "Compute and Save": "계산 및 저장", @@ -2090,6 +2092,7 @@ "SELECTED MASK": "선택한 마스크", "SIZE": "크기", "SPLIT TONING": "스플릿 토닝", + "STEP {number}": "단계 {number}", "Sabattier": "사바티에", "Sabattier Strength": "사바티에 강도", "Sabattier Strength ↑/↓": "사바티에 강도 ↑/↓", diff --git a/negpy/translations/zh_CN.json b/negpy/translations/zh_CN.json index e9521cebb..b1ec425bb 100644 --- a/negpy/translations/zh_CN.json +++ b/negpy/translations/zh_CN.json @@ -521,6 +521,8 @@ "Combines the short and long exposure fusion with multi-pass stacking for the most range and least noise, the slowest option.": "结合短长曝光融合与多次叠加,获得最大动态范围和最少噪点,速度最慢。", "Commit the solved print (Enter)": "应用求得的印相结果(Enter)", "Compare": "对比", + "Compare With This Step": "与此步骤对比", + "Compare With This Version": "与此版本对比", "Composition guide shown in the crop tool": "裁切工具显示的构图参考线", "Compression": "压缩", "Compute and Save": "计算并保存", @@ -2090,6 +2092,7 @@ "SELECTED MASK": "所选蒙版", "SIZE": "尺寸", "SPLIT TONING": "分离色调", + "STEP {number}": "步骤 {number}", "Sabattier": "萨巴蒂尔", "Sabattier Strength": "萨巴蒂尔强度", "Sabattier Strength ↑/↓": "萨巴蒂尔强度 ↑/↓", diff --git a/tests/test_compare_baseline.py b/tests/test_compare_baseline.py index 6028c82a2..5305557cf 100644 --- a/tests/test_compare_baseline.py +++ b/tests/test_compare_baseline.py @@ -5,7 +5,7 @@ from dataclasses import replace -from negpy.desktop.controller import baseline_compare_config +from negpy.desktop.controller import baseline_compare_config, reference_compare_config from negpy.domain.models import WorkspaceConfig from negpy.features.altprocess.models import AltProcess, AltProcessConfig from negpy.features.finish.models import FinishConfig @@ -73,3 +73,18 @@ def test_baseline_exposure_is_the_shipped_default_on_transparency() -> None: untouched slide must carry it, not the bare dataclass default.""" cfg = replace(WorkspaceConfig(), process=replace(WorkspaceConfig().process, process_mode=ProcessMode.E6)) assert baseline_compare_config(cfg).exposure.grade == DEFAULT_WORKSPACE_CONFIG.exposure.grade + + +def test_reference_keeps_its_look_on_the_current_framing() -> None: + edited = _edited_config() + reference = replace( + WorkspaceConfig(), + exposure=replace(edited.exposure, density=0.2), + geometry=replace(edited.geometry, fine_rotation=-4.0), + ) + before = reference_compare_config(reference, edited) + assert before.exposure == reference.exposure + assert before.lab == reference.lab + assert before.process == reference.process + assert before.geometry == edited.geometry + assert before.export == edited.export diff --git a/tests/test_compare_split.py b/tests/test_compare_split.py index e52211c32..ce43b56a6 100644 --- a/tests/test_compare_split.py +++ b/tests/test_compare_split.py @@ -244,6 +244,54 @@ def test_leaving_the_frame_drops_the_split(self): self.assertFalse(self.controller.state.compare_mode) self.assertIsNone(self.controller.state.compare_before) + def _step_config(self) -> WorkspaceConfig: + from dataclasses import replace + + config = self.controller.state.config + return replace(config, exposure=replace(config.exposure, density=config.exposure.density + 0.3)) + + def test_a_history_step_is_the_before_half(self): + step = self._step_config() + self.controller.state.current_file_hash = "abc" + self.controller.state.undo_index = 3 + self.controller.session.repo.load_history_step.return_value = step + + self.controller.compare_with_history_step(1) + + self.controller.session.repo.load_history_step.assert_called_once_with("abc", 1) + self.assertTrue(self.controller.state.compare_mode) + self.assertTrue(self.tasks[-1].compare) + self.assertEqual(self.tasks[-1].config.exposure, step.exposure) + self.assertEqual(self.controller.state.compare_reference_label, "STEP 1") + + def test_a_new_reference_re_captures_the_open_split(self): + self.controller.state.current_file_hash = "abc" + self.controller.toggle_compare() + self._finish(self.tasks[-1]) + self._finish(self.tasks[-1]) # the repaint of the edit + + step = self._step_config() + self.controller.session.repo.load_work_print.return_value = step + self.controller.compare_with_work_print("Warm") + + self.assertTrue(self.controller.state.compare_mode) + self.assertIsNone(self.controller.state.compare_before) + self.assertTrue(self.tasks[-1].compare) + self.assertEqual(self.tasks[-1].config.exposure, step.exposure) + self.assertEqual(self.controller.state.compare_reference_label, "WARM") + + def test_leaving_the_split_forgets_the_reference(self): + self.controller.state.current_file_hash = "abc" + self.controller.session.repo.load_work_print.return_value = self._step_config() + self.controller.compare_with_work_print("Warm") + + self.controller.toggle_compare() + self.assertIsNone(self.controller.state.compare_reference) + self.assertEqual(self.controller.state.compare_reference_label, "") + from negpy.desktop.controller import baseline_compare_config + + self.assertEqual(self.controller._baseline_compare_config(), baseline_compare_config(self.controller.state.config)) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_destructive_guards.py b/tests/test_destructive_guards.py index b54120960..3154623cd 100644 --- a/tests/test_destructive_guards.py +++ b/tests/test_destructive_guards.py @@ -87,7 +87,7 @@ def test_deleting_a_work_print_asks_first(qapp): menu = MagicMock() delete_action = object() - menu.addAction.side_effect = [object(), object(), delete_action] + menu.addAction.side_effect = [object(), object(), object(), delete_action] menu.exec.return_value = delete_action with patch("negpy.desktop.view.sidebar.history.QMenu", return_value=menu):