Repository: nvaccess/nvda

Issue:
nvda is not announcing when a cell in excel contains formula 

### Steps to reproduce:
in nvda the option to use uia for excel is disabled.
open a spreadsheet in excel and enter a formula
now place the arrows focus on the cell with the formula. 
### Actual behavior:
nvda does not report that the cell has a formula 
### Expected behavior:
nvda should announce if the cell has a formula.
I remember this working a while ago, I don't know who caused the problem, if an nvda or office update.
If you check the option to use uia for excel in nvda's advanced options everything works fine.
I still don't allow the use of uia in excel because it still doesn't allow the use of the navigation mode and the list of elements. 
### System configuration
#### NVDA installed/portable/running from source:
instaled
#### NVDA version:
nvda.exe, NVDA alpha-24917,12319e7b

#### Windows version:
11 22000.556
#### Name and version of other software in use when reproducing the issue:
EXCEL.EXE, Microsoft Office 16.0.14931.20132

#### Other information about your system:

### Other questions
#### Does the issue still occur after restarting your computer?
yes
#### Have you tried any other versions of NVDA? If so, please report their behaviors.
no
#### If NVDA add-ons are disabled, is your problem still occurring?
yes
#### Does the issue still occur after you run the COM Registration Fixing Tool in NVDA's tools menu?
yes


Discussion:
Yes, it is confirmed also on my side with Excel 2016 version '16.0.14931.20120':
* NVDA run from source (last master = 6e39f7f7b7b7858e10d06b22c1c82dff7a3cb425): 'has formula' is not reported.
* NVDA 2021.3.3 installed: 'has formula' is reported.

Thus it seems to be a regression during 2022.1 alpha stage.

Cc @feerrenrut, @michaelDCurran 


Caused by merge of pr #13414 
In-proc code for Excel contains some state constants which should mirror NvDA's controlTypes state constants. 
However, with the merge of that pr, the state constants no longer match.

Resolving patch:
diff --git a/source/NVDAObjects/window/excel.py b/source/NVDAObjects/window/excel.py
--- a/source/NVDAObjects/window/excel.py
+++ b/source/NVDAObjects/window/excel.py
@@ -5,6 +5,11 @@
 
 import abc
 import ctypes
+import enum
+from typing import (
+	Optional, Dict,
+)
+
 from comtypes import COMError, BSTR
 import comtypes.automation
 import wx
@@ -1059,13 +1064,42 @@ def _get_locationText(self):
 NVCELLINFOFLAG_FORMULA=0x80
 NVCELLINFOFLAG_ALL=0xffff
 
+
+class NvCellState(enum.IntEnum):
+	# These values must match NvCellState in `nvdaHelper/remote/excel/constants.h`
+	EXPANDED = 1 << 1,
+	COLLAPSED = 1 << 2,
+	LINKED = 1 << 3,
+	HASPOPUP = 1 << 4,
+	PROTECTED = 1 << 5,
+	HASFORMULA = 1 << 6,
+	HASCOMMENT = 1 << 7,
+	CROPPED = 1 << 8,
+	OVERFLOWING = 1 << 9,
+	UNLOCKED = 1 << 10,
+
+
+_nvCellStatesToStates: Dict[NvCellState, controlTypes.State] = {
+	NvCellState.EXPANDED: controlTypes.State.EXPANDED,
+	NvCellState.COLLAPSED: controlTypes.State.COLLAPSED,
+	NvCellState.LINKED: controlTypes.State.LINKED,
+	NvCellState.HASPOPUP: controlTypes.State.HASPOPUP,
+	NvCellState.PROTECTED: controlTypes.State.PROTECTED,
+	NvCellState.HASFORMULA: controlTypes.State.HASFORMULA,
+	NvCellState.HASCOMMENT: controlTypes.State.HASCOMMENT,
+	NvCellState.CROPPED: controlTypes.State.CROPPED,
+	NvCellState.OVERFLOWING: controlTypes.State.OVERFLOWING,
+	NvCellState.UNLOCKED: controlTypes.State.UNLOCKED,
+}
+
+
 class ExcelCellInfo(ctypes.Structure):
 		_fields_=[
 			('text',comtypes.BSTR),
 			('address',comtypes.BSTR),
 			('inputTitle',comtypes.BSTR),
 			('inputMessage',comtypes.BSTR),
-			('states',ctypes.c_longlong),
+			('nvCellStates', ctypes.c_longlong),  # bitwise OR of the NvCellState enum values.
 			('rowNumber',ctypes.c_long),
 			('rowSpan',ctypes.c_long),
 			('columnNumber',ctypes.c_long),
@@ -1075,6 +1109,7 @@ class ExcelCellInfo(ctypes.Structure):
 			('formula',comtypes.BSTR),
 		]
 
+
 class ExcelCellInfoQuickNavItem(browseMode.QuickNavItem):
 
 	def __init__( self , parentIterator, cellInfo):
@@ -1181,7 +1216,10 @@ def collectionFromWorksheet(self,worksheetObject):
 
 class ExcelCell(ExcelBase):
 
-	def _get_excelCellInfo(self):
+	excelCellInfo: Optional[ExcelCellInfo]
+	"""Type info for auto property: _get_excelCellInfo"""
+
+	def _get_excelCellInfo(self) -> Optional[ExcelCellInfo]:
 		if not self.appModule.helperLocalBindingHandle:
 			return None
 		ci=ExcelCellInfo()
@@ -1382,10 +1420,14 @@ def _get_states(self):
 		cellInfo=self.excelCellInfo
 		if not cellInfo:
 			return states
-		stateBits=cellInfo.states
-		for state in controlTypes.State:
-			if stateBits & state.value:
-				states.add(state)
+		nvCellStates = cellInfo.nvCellStates
+
+		for possibleCellState in NvCellState:
+			if nvCellStates & possibleCellState.value:
+				states.add(
+					# intentionally use indexing operator so an error is raised for a missing key
+					_nvCellStatesToStates[possibleCellState]
+				)
 		return states
 
 	def event_typedCharacter(self,ch):


Test patch:
diff --git a/tests/unit/test_excel.py b/tests/unit/test_excel.py
new file mode 100644
--- /dev/null
+++ b/tests/unit/test_excel.py
@@ -0,0 +1,18 @@
+# A part of NonVisual Desktop Access (NVDA)
+# This file is covered by the GNU General Public License.
+# See the file COPYING for more details.
+# Copyright (C) 2022 NV Access Limited
+
+"""Unit tests for the excel module.
+"""
+
+import unittest
+import NVDAObjects.window.excel as excel
+
+
+class TestCellStates(unittest.TestCase):
+	def test_cellStateMapsToState(self):
+		"""All Excel Cell info states should map to a controlTypes.State
+		"""
+		for cellState in excel.NvCellState:
+			excel._nvCellStatesToStates[cellState]  # throws if cellState is missing