mirror of
https://github.com/python/cpython.git
synced 2025-10-02 05:12:23 +00:00
bpo-30853: IDLE: Factor a VarTrace class from configdialog.ConfigDialog. (#2872)
The new class manages pairs of tk Variables and trace callbacks. It is completely covered by new tests.
This commit is contained in:
parent
5cff637979
commit
45bf723c6c
2 changed files with 147 additions and 2 deletions
|
@ -1846,6 +1846,61 @@ class ConfigDialog(Toplevel):
|
||||||
self.ext_userCfg.Save()
|
self.ext_userCfg.Save()
|
||||||
|
|
||||||
|
|
||||||
|
class VarTrace:
|
||||||
|
"""Maintain Tk variables trace state."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Store Tk variables and callbacks.
|
||||||
|
|
||||||
|
untraced: List of tuples (var, callback)
|
||||||
|
that do not have the callback attached
|
||||||
|
to the Tk var.
|
||||||
|
traced: List of tuples (var, callback) where
|
||||||
|
that callback has been attached to the var.
|
||||||
|
"""
|
||||||
|
self.untraced = []
|
||||||
|
self.traced = []
|
||||||
|
|
||||||
|
def add(self, var, callback):
|
||||||
|
"""Add (var, callback) tuple to untraced list.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
var: Tk variable instance.
|
||||||
|
callback: Function to be used as a callback or
|
||||||
|
a tuple with IdleConf values for default
|
||||||
|
callback.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
Tk variable instance.
|
||||||
|
"""
|
||||||
|
if isinstance(callback, tuple):
|
||||||
|
callback = self.make_callback(var, callback)
|
||||||
|
self.untraced.append((var, callback))
|
||||||
|
return var
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def make_callback(var, config):
|
||||||
|
"Return default callback function to add values to changes instance."
|
||||||
|
def default_callback(*params):
|
||||||
|
"Add config values to changes instance."
|
||||||
|
changes.add_option(*config, var.get())
|
||||||
|
return default_callback
|
||||||
|
|
||||||
|
def attach(self):
|
||||||
|
"Attach callback to all vars that are not traced."
|
||||||
|
while self.untraced:
|
||||||
|
var, callback = self.untraced.pop()
|
||||||
|
var.trace_add('write', callback)
|
||||||
|
self.traced.append((var, callback))
|
||||||
|
|
||||||
|
def detach(self):
|
||||||
|
"Remove callback from traced vars."
|
||||||
|
while self.traced:
|
||||||
|
var, callback = self.traced.pop()
|
||||||
|
var.trace_remove('write', var.trace_info()[0][1])
|
||||||
|
self.untraced.append((var, callback))
|
||||||
|
|
||||||
|
|
||||||
help_common = '''\
|
help_common = '''\
|
||||||
When you click either the Apply or Ok buttons, settings in this
|
When you click either the Apply or Ok buttons, settings in this
|
||||||
dialog that are different from IDLE's default are saved in
|
dialog that are different from IDLE's default are saved in
|
||||||
|
|
|
@ -3,11 +3,12 @@
|
||||||
Half the class creates dialog, half works with user customizations.
|
Half the class creates dialog, half works with user customizations.
|
||||||
Coverage: 46% just by creating dialog, 60% with current tests.
|
Coverage: 46% just by creating dialog, 60% with current tests.
|
||||||
"""
|
"""
|
||||||
from idlelib.configdialog import ConfigDialog, idleConf, changes
|
from idlelib.configdialog import ConfigDialog, idleConf, changes, VarTrace
|
||||||
from test.support import requires
|
from test.support import requires
|
||||||
requires('gui')
|
requires('gui')
|
||||||
from tkinter import Tk
|
from tkinter import Tk, IntVar, BooleanVar
|
||||||
import unittest
|
import unittest
|
||||||
|
from unittest import mock
|
||||||
import idlelib.config as config
|
import idlelib.config as config
|
||||||
from idlelib.idle_test.mock_idle import Func
|
from idlelib.idle_test.mock_idle import Func
|
||||||
|
|
||||||
|
@ -248,5 +249,94 @@ class GeneralTest(unittest.TestCase):
|
||||||
#def test_help_sources(self): pass # TODO
|
#def test_help_sources(self): pass # TODO
|
||||||
|
|
||||||
|
|
||||||
|
class TestVarTrace(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
changes.clear()
|
||||||
|
self.v1 = IntVar(root)
|
||||||
|
self.v2 = BooleanVar(root)
|
||||||
|
self.called = 0
|
||||||
|
self.tracers = VarTrace()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
del self.v1, self.v2
|
||||||
|
|
||||||
|
def var_changed_increment(self, *params):
|
||||||
|
self.called += 13
|
||||||
|
|
||||||
|
def var_changed_boolean(self, *params):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_init(self):
|
||||||
|
self.assertEqual(self.tracers.untraced, [])
|
||||||
|
self.assertEqual(self.tracers.traced, [])
|
||||||
|
|
||||||
|
def test_add(self):
|
||||||
|
tr = self.tracers
|
||||||
|
func = Func()
|
||||||
|
cb = tr.make_callback = mock.Mock(return_value=func)
|
||||||
|
|
||||||
|
v1 = tr.add(self.v1, self.var_changed_increment)
|
||||||
|
self.assertIsInstance(v1, IntVar)
|
||||||
|
v2 = tr.add(self.v2, self.var_changed_boolean)
|
||||||
|
self.assertIsInstance(v2, BooleanVar)
|
||||||
|
|
||||||
|
v3 = IntVar(root)
|
||||||
|
v3 = tr.add(v3, ('main', 'section', 'option'))
|
||||||
|
cb.assert_called_once()
|
||||||
|
cb.assert_called_with(v3, ('main', 'section', 'option'))
|
||||||
|
|
||||||
|
expected = [(v1, self.var_changed_increment),
|
||||||
|
(v2, self.var_changed_boolean),
|
||||||
|
(v3, func)]
|
||||||
|
self.assertEqual(tr.traced, [])
|
||||||
|
self.assertEqual(tr.untraced, expected)
|
||||||
|
|
||||||
|
del tr.make_callback
|
||||||
|
|
||||||
|
def test_make_callback(self):
|
||||||
|
tr = self.tracers
|
||||||
|
cb = tr.make_callback(self.v1, ('main', 'section', 'option'))
|
||||||
|
self.assertTrue(callable(cb))
|
||||||
|
self.v1.set(42)
|
||||||
|
# Not attached, so set didn't invoke the callback.
|
||||||
|
self.assertNotIn('section', changes['main'])
|
||||||
|
# Invoke callback manually.
|
||||||
|
cb()
|
||||||
|
self.assertIn('section', changes['main'])
|
||||||
|
self.assertEqual(changes['main']['section']['option'], '42')
|
||||||
|
|
||||||
|
def test_attach_detach(self):
|
||||||
|
tr = self.tracers
|
||||||
|
v1 = tr.add(self.v1, self.var_changed_increment)
|
||||||
|
v2 = tr.add(self.v2, self.var_changed_boolean)
|
||||||
|
expected = [(v1, self.var_changed_increment),
|
||||||
|
(v2, self.var_changed_boolean)]
|
||||||
|
|
||||||
|
# Attach callbacks and test call increment.
|
||||||
|
tr.attach()
|
||||||
|
self.assertEqual(tr.untraced, [])
|
||||||
|
self.assertCountEqual(tr.traced, expected)
|
||||||
|
v1.set(1)
|
||||||
|
self.assertEqual(v1.get(), 1)
|
||||||
|
self.assertEqual(self.called, 13)
|
||||||
|
|
||||||
|
# Check that only one callback is attached to a variable.
|
||||||
|
# If more than one callback were attached, then var_changed_increment
|
||||||
|
# would be called twice and the counter would be 2.
|
||||||
|
self.called = 0
|
||||||
|
tr.attach()
|
||||||
|
v1.set(1)
|
||||||
|
self.assertEqual(self.called, 13)
|
||||||
|
|
||||||
|
# Detach callbacks.
|
||||||
|
self.called = 0
|
||||||
|
tr.detach()
|
||||||
|
self.assertEqual(tr.traced, [])
|
||||||
|
self.assertCountEqual(tr.untraced, expected)
|
||||||
|
v1.set(1)
|
||||||
|
self.assertEqual(self.called, 0)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main(verbosity=2)
|
unittest.main(verbosity=2)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue