@@ -610,6 +610,152 @@ def create_mock_root_without_dpi_scaling():
610610
611611
612612# Comprehensive test context helper
613+ def _setup_wintypes_mock (wintypes_mock ):
614+ """Setup wintypes mock context if specified."""
615+ if not wintypes_mock :
616+ return None
617+ return WintypesPointerMockContext (
618+ wintypes_mock .get ('hwnd' , 12345 ),
619+ wintypes_mock .get ('monitor_handle' , 2 ),
620+ wintypes_mock .get ('x_dpi' , 96 ),
621+ wintypes_mock .get ('y_dpi' , 96 )
622+ )
623+
624+
625+ def _setup_windll_config (patch_ctx , windll_config ):
626+ """Setup windll configuration if specified."""
627+ if not windll_config :
628+ return
629+ patch_ctx .get_windll ().shcore .GetDpiForMonitor .return_value = windll_config .get ('get_dpi_return' )
630+ patch_ctx .get_windll ().shcore .GetScaleFactorForDevice .return_value = windll_config .get ('get_scale_return' , 200 )
631+
632+
633+ def _setup_tkinter_root ():
634+ """Setup tkinter root if needed."""
635+ try :
636+ # Check if Tkinter is already running
637+ try :
638+ existing_root = tk ._default_root
639+ if existing_root is not None :
640+ # If there's already a root, use it
641+ root = existing_root
642+ else :
643+ # Create a new root
644+ root = tk .Tk ()
645+ root .withdraw ()
646+ except Exception as exc :
647+ # If there's any issue, try to create a new root
648+ logging .debug ("Failed to use existing root, creating new one: %s" , exc )
649+ root = tk .Tk ()
650+ root .withdraw ()
651+
652+ # Test if the root is actually working
653+ root .update_idletasks ()
654+ return root
655+ except (tk .TclError , RuntimeError , Exception ) as e :
656+ # If Tkinter is not available, skip the test
657+ pytest .skip (f"Tkinter not available: { e } " )
658+
659+
660+ def _apply_patches (manager , patches ):
661+ """Apply patches to manager methods."""
662+ patch_contexts = []
663+ if not patches :
664+ return patch_contexts
665+
666+ for patch_config in patches :
667+ target = patch_config ['target' ]
668+ method_name = target .split ('.' )[- 1 ]
669+ patch_ctx_obj = patch .object (
670+ manager , method_name ,
671+ return_value = patch_config .get ('return_value' ),
672+ side_effect = patch_config .get ('side_effect' )
673+ )
674+ patch_contexts .append (patch_ctx_obj )
675+ return patch_contexts
676+
677+
678+ def _apply_tkinter_patches (manager , tkinter_patches , original_methods ):
679+ """Apply tkinter patches and store original methods."""
680+ if not tkinter_patches :
681+ return
682+
683+ for patch_config in tkinter_patches :
684+ tk_class = patch_config ['class' ]
685+ method_name = patch_config ['method' ]
686+ scaling_factor = patch_config .get ('scaling_factor' , 2.0 )
687+
688+ original_method = getattr (tk_class , method_name )
689+ original_methods [(tk_class , method_name )] = original_method
690+
691+ # Apply the patch
692+ if method_name == '__init__' :
693+ # For constructors, use the unified method
694+ if tk_class .__name__ == 'Button' :
695+ patch_method = getattr (manager , '_patch_button_constructor' )
696+ patch_method (scaling_factor )
697+ else :
698+ # For other constructors, use the unified method
699+ patch_method = getattr (manager , '_apply_standard_widget_patch' )
700+ patch_method (tk_class , scaling_factor )
701+ else :
702+ # For layout methods, add '_method' suffix
703+ patch_method = getattr (manager , f'_patch_{ method_name } _method' )
704+ patch_method (scaling_factor )
705+
706+
707+ def _apply_treeview_patches (treeview_patches , original_methods ):
708+ """Apply treeview patches and store original methods."""
709+ if not treeview_patches :
710+ return
711+
712+ from tkinter import ttk
713+ if 'column' in treeview_patches :
714+ original_methods [(ttk .Treeview , 'column' )] = ttk .Treeview .column
715+ if 'style' in treeview_patches :
716+ original_methods [(ttk .Style , 'configure' )] = ttk .Style .configure
717+
718+
719+ def _prepare_test_args (self , manager , patch_ctx , wintypes_ctx , root , args , test_func , is_windows ):
720+ """Prepare test arguments based on test function signature."""
721+ test_args = [self , manager , patch_ctx ]
722+ if wintypes_ctx :
723+ test_args .append (wintypes_ctx )
724+ if root :
725+ test_args .insert (1 , root ) # Insert root after self
726+
727+ # Add commonly needed mock objects if test function expects them
728+ import inspect
729+ sig = inspect .signature (test_func )
730+ param_names = list (sig .parameters .keys ())
731+
732+ # Add mock objects if they are expected by the test function
733+ if 'mock_root' in param_names :
734+ test_args .append (create_basic_mock_root ())
735+ if 'mock_windll' in param_names :
736+ test_args .append (patch_ctx .get_windll () if is_windows else None )
737+ if 'mock_dpi_values' in param_names :
738+ test_args .append ((192 , 192 , 2.0 ) if is_windows else (96 , 96 , 1.0 ))
739+
740+ test_args .extend (args )
741+ return test_args
742+
743+
744+ def _cleanup_test_context (original_methods , root ):
745+ """Cleanup test context by restoring original methods and destroying root."""
746+ # Restore original methods
747+ for (tk_class , method_name ), original_method in original_methods .items ():
748+ setattr (tk_class , method_name , original_method )
749+
750+ # Cleanup root
751+ if root :
752+ try :
753+ root .destroy ()
754+ except Exception as e :
755+ # Log cleanup errors but don't fail the test
756+ print (f"Warning: Failed to destroy root in cleanup: { e } " )
757+
758+
613759def with_test_context (
614760 is_windows = True ,
615761 wintypes_mock = None ,
@@ -652,97 +798,28 @@ def wrapper(self, *args, **kwargs):
652798 manager = create_dpi_manager_for_test ()
653799
654800 # Setup wintypes mock if specified
655- wintypes_ctx = None
656- if wintypes_mock :
657- wintypes_ctx = WintypesPointerMockContext (
658- wintypes_mock .get ('hwnd' , 12345 ),
659- wintypes_mock .get ('monitor_handle' , 2 ),
660- wintypes_mock .get ('x_dpi' , 96 ),
661- wintypes_mock .get ('y_dpi' , 96 )
662- )
801+ wintypes_ctx = _setup_wintypes_mock (wintypes_mock )
663802
664803 # Setup windll config if specified
665- windll_ctx = None
666- if windll_config :
667- # Configure windll directly in patch_ctx
668- patch_ctx .get_windll ().shcore .GetDpiForMonitor .return_value = windll_config .get ('get_dpi_return' )
669- patch_ctx .get_windll ().shcore .GetScaleFactorForDevice .return_value = windll_config .get ('get_scale_return' , 200 )
804+ _setup_windll_config (patch_ctx , windll_config )
670805
671806 # Setup tkinter root if specified
672807 root = None
673808 if real_tkinter_root :
674- try :
675- # Check if Tkinter is already running
676- try :
677- existing_root = tk ._default_root
678- if existing_root is not None :
679- # If there's already a root, use it
680- root = existing_root
681- else :
682- # Create a new root
683- root = tk .Tk ()
684- root .withdraw ()
685- except Exception as exc :
686- # If there's any issue, try to create a new root
687- logging .debug ("Failed to use existing root, creating new one: %s" , exc )
688- root = tk .Tk ()
689- root .withdraw ()
690-
691- # Test if the root is actually working
692- root .update_idletasks ()
693- except (tk .TclError , RuntimeError , Exception ) as e :
694- # If Tkinter is not available, skip the test
695- pytest .skip (f"Tkinter not available: { e } " )
809+ root = _setup_tkinter_root ()
696810
697811 # Store original methods for restoration
698812 original_methods = {}
699813
700814 try :
701815 # Apply patches
702- patch_contexts = []
703- if patches :
704- for patch_config in patches :
705- target = patch_config ['target' ]
706- method_name = target .split ('.' )[- 1 ]
707- patch_ctx_obj = patch .object (
708- manager , method_name ,
709- return_value = patch_config .get ('return_value' ),
710- side_effect = patch_config .get ('side_effect' )
711- )
712- patch_contexts .append (patch_ctx_obj )
816+ patch_contexts = _apply_patches (manager , patches )
713817
714818 # Apply tkinter patches
715- if tkinter_patches :
716- for patch_config in tkinter_patches :
717- tk_class = patch_config ['class' ]
718- method_name = patch_config ['method' ]
719- scaling_factor = patch_config .get ('scaling_factor' , 2.0 )
720-
721- original_method = getattr (tk_class , method_name )
722- original_methods [(tk_class , method_name )] = original_method
723-
724- # Apply the patch
725- if method_name == '__init__' :
726- # For constructors, use the unified method
727- if tk_class .__name__ == 'Button' :
728- patch_method = getattr (manager , '_patch_button_constructor' )
729- patch_method (scaling_factor )
730- else :
731- # For other constructors, use the unified method
732- patch_method = getattr (manager , '_apply_standard_widget_patch' )
733- patch_method (tk_class , scaling_factor )
734- else :
735- # For layout methods, add '_method' suffix
736- patch_method = getattr (manager , f'_patch_{ method_name } _method' )
737- patch_method (scaling_factor )
819+ _apply_tkinter_patches (manager , tkinter_patches , original_methods )
738820
739821 # Apply treeview patches
740- if treeview_patches :
741- from tkinter import ttk
742- if 'column' in treeview_patches :
743- original_methods [(ttk .Treeview , 'column' )] = ttk .Treeview .column
744- if 'style' in treeview_patches :
745- original_methods [(ttk .Style , 'configure' )] = ttk .Style .configure
822+ _apply_treeview_patches (treeview_patches , original_methods )
746823
747824 # Execute test with all contexts
748825 contexts = [patch_ctx ]
@@ -755,41 +832,14 @@ def wrapper(self, *args, **kwargs):
755832 stack .enter_context (ctx )
756833
757834 # Prepare test arguments
758- test_args = [self , manager , patch_ctx ]
759- if wintypes_ctx :
760- test_args .append (wintypes_ctx )
761- if root :
762- test_args .insert (1 , root ) # Insert root after self
763-
764- # Add commonly needed mock objects if test function expects them
765- import inspect
766- sig = inspect .signature (test_func )
767- param_names = list (sig .parameters .keys ())
768-
769- # Add mock objects if they are expected by the test function
770- if 'mock_root' in param_names :
771- test_args .append (create_basic_mock_root ())
772- if 'mock_windll' in param_names :
773- test_args .append (patch_ctx .get_windll () if is_windows else None )
774- if 'mock_dpi_values' in param_names :
775- test_args .append ((192 , 192 , 2.0 ) if is_windows else (96 , 96 , 1.0 ))
776-
777- test_args .extend (args )
835+ test_args = _prepare_test_args (
836+ self , manager , patch_ctx , wintypes_ctx , root , args , test_func , is_windows
837+ )
778838
779839 return test_func (* test_args , ** kwargs )
780840
781841 finally :
782- # Restore original methods
783- for (tk_class , method_name ), original_method in original_methods .items ():
784- setattr (tk_class , method_name , original_method )
785-
786- # Cleanup root
787- if root :
788- try :
789- root .destroy ()
790- except Exception as e :
791- # Log cleanup errors but don't fail the test
792- print (f"Warning: Failed to destroy root in cleanup: { e } " )
842+ _cleanup_test_context (original_methods , root )
793843
794844 return wrapper
795845 return decorator
0 commit comments