From d37234de6ee2dd01027c04a130e7acba42156318 Mon Sep 17 00:00:00 2001 From: serrebidev Date: Tue, 8 Sep 2026 12:44:37 -0700 Subject: [PATCH 1/2] Use the current NVDA TextRegion API --- addon/globalPlugins/columnsReview/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addon/globalPlugins/columnsReview/__init__.py b/addon/globalPlugins/columnsReview/__init__.py index d0e79ea..edfba44 100644 --- a/addon/globalPlugins/columnsReview/__init__.py +++ b/addon/globalPlugins/columnsReview/__init__.py @@ -39,6 +39,7 @@ import addonHandler import api import braille +from braille.regions.base import TextRegion import config import core import ctypes @@ -690,7 +691,7 @@ def reportEmpty(self): return brlText = " {0}".format(text) if regions[-1].rawText != brlText: - newRegion = braille.TextRegion(brlText) + newRegion = TextRegion(brlText) newRegion.focusToHardLeft = True newRegion.update() regions.append(newRegion) From e49175f965a6047e3f2dd8b625c4bd68268d1dde Mon Sep 17 00:00:00 2001 From: serrebidev Date: Tue, 8 Sep 2026 19:33:19 -0700 Subject: [PATCH 2/2] Preserve older NVDA TextRegion compatibility --- addon/globalPlugins/columnsReview/__init__.py | 5 +- addon/globalPlugins/columnsReview/compat.py | 6 ++ tests/unit/test_braille_compat.py | 67 +++++++++++++++++++ 3 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 tests/unit/test_braille_compat.py diff --git a/addon/globalPlugins/columnsReview/__init__.py b/addon/globalPlugins/columnsReview/__init__.py index edfba44..76418c1 100644 --- a/addon/globalPlugins/columnsReview/__init__.py +++ b/addon/globalPlugins/columnsReview/__init__.py @@ -39,7 +39,6 @@ import addonHandler import api import braille -from braille.regions.base import TextRegion import config import core import ctypes @@ -60,7 +59,7 @@ from .actions import ACTIONS, actionFromName, configuredActions from .commonFunc import NVDALocale, findAllDescendantWindows, getScriptGestures -from .compat import CTWRAPPER, zeroItemsTemplate, rangeFunc +from .compat import CTWRAPPER, TextRegion, zeroItemsTemplate, rangeFunc from . import configManager from . import configSpec from . import dialogs @@ -928,7 +927,7 @@ def getSelectedItems(self): parentHandle, sysListView32.LVM_GETNEXTITEM, selItemIndex, - ctypes.c_void_p(sysListView32.LVNI_SELECTED) + ctypes.c_void_p(sysListView32.LVNI_SELECTED), ) return items diff --git a/addon/globalPlugins/columnsReview/compat.py b/addon/globalPlugins/columnsReview/compat.py index bcfd11e..1b0b39a 100644 --- a/addon/globalPlugins/columnsReview/compat.py +++ b/addon/globalPlugins/columnsReview/compat.py @@ -3,6 +3,12 @@ import controlTypes +try: + from braille.regions.base import TextRegion as TextRegion +except ImportError: + # NVDA before 2027.1 exposes TextRegion directly in braille. + from braille import TextRegion as TextRegion + try: from buildVersion import version_year, version_major, version_minor except ImportError: diff --git a/tests/unit/test_braille_compat.py b/tests/unit/test_braille_compat.py new file mode 100644 index 0000000..fde40e2 --- /dev/null +++ b/tests/unit/test_braille_compat.py @@ -0,0 +1,67 @@ +"""Exercise the compatibility module without requiring a running NVDA instance.""" + +from pathlib import Path +import runpy +import sys +from types import ModuleType +import unittest +from unittest.mock import patch + + +COMPAT_PATH = Path(__file__).resolve().parents[2] / "addon" / "globalPlugins" / "columnsReview" / "compat.py" + + +class TestTextRegionCompatibility(unittest.TestCase): + def loadCompat(self, brailleModules): + version = ModuleType("buildVersion") + version.version_year = 2027 + version.version_major = 1 + version.version_minor = 0 + modules = { + "controlTypes": ModuleType("controlTypes"), + "buildVersion": version, + "braille.regions": None, + "braille.regions.base": None, + } + modules.update(brailleModules) + with patch.dict(sys.modules, modules): + return runpy.run_path( + str(COMPAT_PATH), + init_globals={"ngettext": lambda singular, plural, count: plural}, + ) + + def test_current_api_does_not_access_deprecated_alias(self): + braille = ModuleType("braille") + braille.__path__ = [] + + def rejectLegacyAccess(name): + if name == "TextRegion": + raise AssertionError("The deprecated braille.TextRegion alias was accessed") + raise AttributeError(name) + + braille.__getattr__ = rejectLegacyAccess + regions = ModuleType("braille.regions") + regions.__path__ = [] + base = ModuleType("braille.regions.base") + base.TextRegion = type("CurrentTextRegion", (), {}) + compat = self.loadCompat( + { + "braille": braille, + "braille.regions": regions, + "braille.regions.base": base, + }, + ) + self.assertIs(compat["TextRegion"], base.TextRegion) + + def test_legacy_braille_module(self): + braille = ModuleType("braille") + braille.TextRegion = type("LegacyTextRegion", (), {}) + compat = self.loadCompat({"braille": braille}) + self.assertIs(compat["TextRegion"], braille.TextRegion) + + def test_braille_package_without_regions(self): + braille = ModuleType("braille") + braille.__path__ = [] + braille.TextRegion = type("LegacyTextRegion", (), {}) + compat = self.loadCompat({"braille": braille}) + self.assertIs(compat["TextRegion"], braille.TextRegion)