|
1 | 1 | """ |
2 | 2 | Tests for tkface.win.dpi module. |
3 | 3 |
|
4 | | -This module tests the DPI management functionality for Windows applications. |
| 4 | +This module tests the DPI management functionality for Windows applications, |
| 5 | +including automatic ttk widget configuration and tk widget patching. |
5 | 6 | """ |
6 | 7 |
|
7 | 8 | import contextlib |
|
12 | 13 | from unittest.mock import MagicMock, call, patch |
13 | 14 |
|
14 | 15 | import pytest |
| 16 | +from tkinter import ttk |
15 | 17 |
|
16 | 18 |
|
17 | 19 | # Common mock fixtures for DPI testing |
@@ -3105,3 +3107,160 @@ def test_scale_icon_between_1_0_and_1_25(self): |
3105 | 3107 | result = scale_icon('error', parent) |
3106 | 3108 | assert result == 'scaled_error_large' |
3107 | 3109 |
|
| 3110 | + |
| 3111 | +class TestTTKWidgetDPI: |
| 3112 | + """Test cases for automatic ttk widget DPI configuration.""" |
| 3113 | + |
| 3114 | + def test_configure_ttk_widgets_for_dpi_non_windows(self): |
| 3115 | + """Test configure_ttk_widgets_for_dpi on non-Windows platform.""" |
| 3116 | + with patch('tkface.win.dpi.is_windows', return_value=False): |
| 3117 | + from tkface.win.dpi import configure_ttk_widgets_for_dpi |
| 3118 | + # Should return early without error |
| 3119 | + configure_ttk_widgets_for_dpi(None) |
| 3120 | + |
| 3121 | + def test_configure_ttk_widgets_for_dpi_no_root(self): |
| 3122 | + """Test configure_ttk_widgets_for_dpi with None root.""" |
| 3123 | + with patch('tkface.win.dpi.is_windows', return_value=True): |
| 3124 | + from tkface.win.dpi import configure_ttk_widgets_for_dpi |
| 3125 | + # Should return early without error |
| 3126 | + configure_ttk_widgets_for_dpi(None) |
| 3127 | + |
| 3128 | + def test_configure_ttk_widgets_for_dpi_low_scaling(self): |
| 3129 | + """Test configure_ttk_widgets_for_dpi with scaling factor <= 1.0.""" |
| 3130 | + mock_root = MagicMock() |
| 3131 | + mock_root.DPI_scaling = 1.0 |
| 3132 | + |
| 3133 | + with patch('tkface.win.dpi.is_windows', return_value=True): |
| 3134 | + from tkface.win.dpi import configure_ttk_widgets_for_dpi |
| 3135 | + # Should return early without configuring styles |
| 3136 | + configure_ttk_widgets_for_dpi(mock_root) |
| 3137 | + |
| 3138 | + def test_configure_ttk_widgets_for_dpi_success(self): |
| 3139 | + """Test successful ttk widget configuration.""" |
| 3140 | + mock_root = MagicMock() |
| 3141 | + mock_root.DPI_scaling = 2.0 |
| 3142 | + |
| 3143 | + mock_style = MagicMock() |
| 3144 | + |
| 3145 | + with patch('tkface.win.dpi.is_windows', return_value=True), \ |
| 3146 | + patch('tkinter.ttk.Style', return_value=mock_style): |
| 3147 | + from tkface.win.dpi import configure_ttk_widgets_for_dpi |
| 3148 | + configure_ttk_widgets_for_dpi(mock_root) |
| 3149 | + |
| 3150 | + # Should configure both Checkbutton and Radiobutton styles |
| 3151 | + assert mock_style.configure.call_count == 2 |
| 3152 | + calls = mock_style.configure.call_args_list |
| 3153 | + assert calls[0][0][0] == "TCheckbutton" # First call for Checkbutton |
| 3154 | + assert calls[1][0][0] == "TRadiobutton" # Second call for Radiobutton |
| 3155 | + |
| 3156 | + def test_configure_ttk_widgets_for_dpi_style_error(self): |
| 3157 | + """Test configure_ttk_widgets_for_dpi with style configuration errors.""" |
| 3158 | + mock_root = MagicMock() |
| 3159 | + mock_root.DPI_scaling = 2.0 |
| 3160 | + |
| 3161 | + mock_style = MagicMock() |
| 3162 | + mock_style.configure.side_effect = Exception("Style error") |
| 3163 | + |
| 3164 | + with patch('tkface.win.dpi.is_windows', return_value=True), \ |
| 3165 | + patch('tkinter.ttk.Style', return_value=mock_style): |
| 3166 | + from tkface.win.dpi import configure_ttk_widgets_for_dpi |
| 3167 | + # Should not raise exception, should handle errors gracefully |
| 3168 | + configure_ttk_widgets_for_dpi(mock_root) |
| 3169 | + |
| 3170 | + |
| 3171 | +class TestAutoPatchTKWidgets: |
| 3172 | + """Test cases for automatic tk widget to ttk patching.""" |
| 3173 | + |
| 3174 | + def test_auto_patch_tk_widgets_non_windows(self): |
| 3175 | + """Test auto-patch on non-Windows platform.""" |
| 3176 | + with patch('tkface.win.dpi.is_windows', return_value=False): |
| 3177 | + from tkface.win.dpi import DPIManager |
| 3178 | + manager = DPIManager() |
| 3179 | + # Should return early without patching |
| 3180 | + manager._auto_patch_tk_widgets_to_ttk() |
| 3181 | + |
| 3182 | + def test_auto_patch_tk_widgets_already_patched(self): |
| 3183 | + """Test auto-patch when widgets are already patched.""" |
| 3184 | + # Mark widgets as already patched |
| 3185 | + tk.Checkbutton._tkface_patched_to_ttk = True |
| 3186 | + tk.Radiobutton._tkface_patched_to_ttk = True |
| 3187 | + |
| 3188 | + with patch('tkface.win.dpi.is_windows', return_value=True): |
| 3189 | + from tkface.win.dpi import DPIManager |
| 3190 | + manager = DPIManager() |
| 3191 | + # Should return early without double-patching |
| 3192 | + manager._auto_patch_tk_widgets_to_ttk() |
| 3193 | + |
| 3194 | + def test_auto_patch_tk_widgets_success(self): |
| 3195 | + """Test successful auto-patching of tk widgets.""" |
| 3196 | + # Remove patch markers if they exist |
| 3197 | + if hasattr(tk.Checkbutton, '_tkface_patched_to_ttk'): |
| 3198 | + delattr(tk.Checkbutton, '_tkface_patched_to_ttk') |
| 3199 | + if hasattr(tk.Radiobutton, '_tkface_patched_to_ttk'): |
| 3200 | + delattr(tk.Radiobutton, '_tkface_patched_to_ttk') |
| 3201 | + |
| 3202 | + original_checkbutton_init = tk.Checkbutton.__init__ |
| 3203 | + original_radiobutton_init = tk.Radiobutton.__init__ |
| 3204 | + |
| 3205 | + try: |
| 3206 | + with patch('tkface.win.dpi.is_windows', return_value=True): |
| 3207 | + from tkface.win.dpi import DPIManager |
| 3208 | + manager = DPIManager() |
| 3209 | + manager._auto_patch_tk_widgets_to_ttk() |
| 3210 | + |
| 3211 | + # Check that widgets are marked as patched |
| 3212 | + assert hasattr(tk.Checkbutton, '_tkface_patched_to_ttk') |
| 3213 | + assert hasattr(tk.Radiobutton, '_tkface_patched_to_ttk') |
| 3214 | + |
| 3215 | + # Check that constructors were replaced |
| 3216 | + assert tk.Checkbutton.__init__ != original_checkbutton_init |
| 3217 | + assert tk.Radiobutton.__init__ != original_radiobutton_init |
| 3218 | + |
| 3219 | + finally: |
| 3220 | + # Restore original constructors |
| 3221 | + tk.Checkbutton.__init__ = original_checkbutton_init |
| 3222 | + tk.Radiobutton.__init__ = original_radiobutton_init |
| 3223 | + if hasattr(tk.Checkbutton, '_tkface_patched_to_ttk'): |
| 3224 | + delattr(tk.Checkbutton, '_tkface_patched_to_ttk') |
| 3225 | + if hasattr(tk.Radiobutton, '_tkface_patched_to_ttk'): |
| 3226 | + delattr(tk.Radiobutton, '_tkface_patched_to_ttk') |
| 3227 | + |
| 3228 | + def test_auto_patch_tk_widgets_exception_handling(self): |
| 3229 | + """Test auto-patch exception handling.""" |
| 3230 | + with patch('tkface.win.dpi.is_windows', return_value=True), \ |
| 3231 | + patch.object(tk.Checkbutton, '__init__', side_effect=Exception("Patch error")): |
| 3232 | + from tkface.win.dpi import DPIManager |
| 3233 | + manager = DPIManager() |
| 3234 | + # Should not raise exception, should handle errors gracefully |
| 3235 | + manager._auto_patch_tk_widgets_to_ttk() |
| 3236 | + |
| 3237 | + |
| 3238 | +class TestDPIIntegration: |
| 3239 | + """Test cases for integrated DPI functionality.""" |
| 3240 | + |
| 3241 | + def test_dpi_function_includes_ttk_configuration(self): |
| 3242 | + """Test that dpi() function includes ttk widget configuration.""" |
| 3243 | + mock_root = MagicMock() |
| 3244 | + mock_root.winfo_id.return_value = 12345 |
| 3245 | + mock_root.DPI_scaling = 2.0 |
| 3246 | + |
| 3247 | + with patch('tkface.win.dpi.is_windows', return_value=True), \ |
| 3248 | + patch('tkface.win.dpi.DPIManager._get_hwnd_dpi', return_value=(192, 192, 2.0)), \ |
| 3249 | + patch('tkface.win.dpi.DPIManager._enable_dpi_awareness', return_value={"shcore": True}), \ |
| 3250 | + patch('tkface.win.dpi.DPIManager._apply_shcore_dpi_scaling'), \ |
| 3251 | + patch('tkface.win.dpi.DPIManager._fix_scaling'), \ |
| 3252 | + patch('tkface.win.dpi.DPIManager._apply_scaling_methods'), \ |
| 3253 | + patch('tkface.win.dpi.DPIManager._configure_ttk_widgets_for_dpi_internal') as mock_ttk_config, \ |
| 3254 | + patch('tkface.win.dpi.DPIManager._auto_patch_tk_widgets_to_ttk') as mock_auto_patch: |
| 3255 | + |
| 3256 | + from tkface.win.dpi import dpi |
| 3257 | + result = dpi(mock_root) |
| 3258 | + |
| 3259 | + # Should call both ttk configuration and auto-patch |
| 3260 | + mock_ttk_config.assert_called_once_with(mock_root) |
| 3261 | + mock_auto_patch.assert_called_once() |
| 3262 | + |
| 3263 | + # Should return successful result |
| 3264 | + assert result["enabled"] is True |
| 3265 | + assert result["dpi_awareness_set"] is True |
| 3266 | + |
0 commit comments