[PATCH 2/3] uiautomationcore/tests: Add navigation tests for MSAA providers.

Connor McAdams wine at gitlab.winehq.org
Sat Jun 11 16:57:53 CDT 2022


From: Connor McAdams <cmcadams at codeweavers.com>

Signed-off-by: Connor McAdams <cmcadams at codeweavers.com>
---
 dlls/uiautomationcore/tests/uiautomation.c | 630 ++++++++++++++++++++-
 1 file changed, 618 insertions(+), 12 deletions(-)

diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c
index 6c07edcffa8..b4958d2dc70 100644
--- a/dlls/uiautomationcore/tests/uiautomation.c
+++ b/dlls/uiautomationcore/tests/uiautomation.c
@@ -30,27 +30,36 @@
 static HRESULT (WINAPI *pUiaProviderFromIAccessible)(IAccessible *, long, DWORD, IRawElementProviderSimple **);
 
 #define DEFINE_EXPECT(func) \
-    static BOOL expect_ ## func = FALSE, called_ ## func = FALSE
+    static int expect_ ## func = 0, called_ ## func = 0
 
 #define SET_EXPECT(func) \
-    do { called_ ## func = FALSE; expect_ ## func = TRUE; } while(0)
+    do { called_ ## func = 0; expect_ ## func = 1; } while(0)
+
+#define SET_EXPECT_MULTI(func, num) \
+    do { called_ ## func = 0; expect_ ## func = num; } while(0)
 
 #define CHECK_EXPECT2(func) \
     do { \
         ok(expect_ ##func, "unexpected call " #func "\n"); \
-        called_ ## func = TRUE; \
+        called_ ## func++; \
     }while(0)
 
 #define CHECK_EXPECT(func) \
     do { \
         CHECK_EXPECT2(func); \
-        expect_ ## func = FALSE; \
+        expect_ ## func--; \
     }while(0)
 
 #define CHECK_CALLED(func) \
     do { \
         ok(called_ ## func, "expected " #func "\n"); \
-        expect_ ## func = called_ ## func = FALSE; \
+        expect_ ## func = called_ ## func = 0; \
+    }while(0)
+
+#define CHECK_CALLED_MULTI(func, num) \
+    do { \
+        ok(called_ ## func == num, "expected " #func " %d times (got %d)\n", num, called_ ## func); \
+        expect_ ## func = called_ ## func = 0; \
     }while(0)
 
 #define NAVDIR_INTERNAL_HWND 10
@@ -63,6 +72,7 @@ DEFINE_EXPECT(Accessible_get_accName);
 DEFINE_EXPECT(Accessible_get_accRole);
 DEFINE_EXPECT(Accessible_get_accState);
 DEFINE_EXPECT(Accessible_accLocation);
+DEFINE_EXPECT(Accessible_get_accChild);
 DEFINE_EXPECT(Accessible2_get_accParent);
 DEFINE_EXPECT(Accessible2_get_accChildCount);
 DEFINE_EXPECT(Accessible2_get_accName);
@@ -72,6 +82,18 @@ DEFINE_EXPECT(Accessible2_accLocation);
 DEFINE_EXPECT(Accessible2_QI_IAccIdentity);
 DEFINE_EXPECT(Accessible_child_accNavigate);
 DEFINE_EXPECT(Accessible_child_get_accParent);
+DEFINE_EXPECT(Accessible_child_get_accChildCount);
+DEFINE_EXPECT(Accessible_child_get_accName);
+DEFINE_EXPECT(Accessible_child_get_accRole);
+DEFINE_EXPECT(Accessible_child_get_accState);
+DEFINE_EXPECT(Accessible_child_accLocation);
+DEFINE_EXPECT(Accessible_child2_accNavigate);
+DEFINE_EXPECT(Accessible_child2_get_accParent);
+DEFINE_EXPECT(Accessible_child2_get_accChildCount);
+DEFINE_EXPECT(Accessible_child2_get_accName);
+DEFINE_EXPECT(Accessible_child2_get_accRole);
+DEFINE_EXPECT(Accessible_child2_get_accState);
+DEFINE_EXPECT(Accessible_child2_accLocation);
 
 static BOOL check_variant_i4(VARIANT *v, int val)
 {
@@ -117,7 +139,7 @@ static struct Accessible
     LONG child_count;
     LPCWSTR name;
     LONG left, top, width, height;
-} Accessible, Accessible2, Accessible_child;
+} Accessible, Accessible2, Accessible_child, Accessible_child2;
 
 static inline struct Accessible* impl_from_Accessible(IAccessible *iface)
 {
@@ -195,6 +217,8 @@ static HRESULT WINAPI Accessible_get_accParent(IAccessible *iface, IDispatch **o
 
     if (This == &Accessible_child)
         CHECK_EXPECT(Accessible_child_get_accParent);
+    else if (This == &Accessible_child2)
+        CHECK_EXPECT(Accessible_child2_get_accParent);
     else if (This == &Accessible2)
         CHECK_EXPECT(Accessible2_get_accParent);
     else
@@ -211,7 +235,11 @@ static HRESULT WINAPI Accessible_get_accChildCount(IAccessible *iface, LONG *out
 {
     struct Accessible *This = impl_from_Accessible(iface);
 
-    if (This == &Accessible2)
+    if (This == &Accessible_child)
+        CHECK_EXPECT(Accessible_child_get_accChildCount);
+    else if (This == &Accessible_child2)
+        CHECK_EXPECT(Accessible_child2_get_accChildCount);
+    else if (This == &Accessible2)
         CHECK_EXPECT(Accessible2_get_accChildCount);
     else
         CHECK_EXPECT(Accessible_get_accChildCount);
@@ -228,7 +256,38 @@ static HRESULT WINAPI Accessible_get_accChildCount(IAccessible *iface, LONG *out
 static HRESULT WINAPI Accessible_get_accChild(IAccessible *iface, VARIANT child_id,
         IDispatch **out_child)
 {
-    ok(0, "unexpected call\n");
+    struct Accessible *This = impl_from_Accessible(iface);
+
+    CHECK_EXPECT(Accessible_get_accChild);
+    ok(This == &Accessible, "unexpected call\n");
+
+    *out_child = NULL;
+    if (V_VT(&child_id) != VT_I4)
+        return E_INVALIDARG;
+
+    if (This == &Accessible)
+    {
+        switch (V_I4(&child_id))
+        {
+        case CHILDID_SELF:
+            return IAccessible_QueryInterface(&This->IAccessible_iface, &IID_IDispatch, (void **)out_child);
+
+        /* Simple element children. */
+        case 1:
+        case 3:
+            return S_FALSE;
+
+        case 2:
+            return IAccessible_QueryInterface(&Accessible_child.IAccessible_iface, &IID_IDispatch, (void **)out_child);
+
+        case 4:
+            return IAccessible_QueryInterface(&Accessible_child2.IAccessible_iface, &IID_IDispatch, (void **)out_child);
+
+        default:
+            break;
+
+        }
+    }
     return E_NOTIMPL;
 }
 
@@ -238,7 +297,11 @@ static HRESULT WINAPI Accessible_get_accName(IAccessible *iface, VARIANT child_i
     struct Accessible *This = impl_from_Accessible(iface);
 
     *out_name = NULL;
-    if (This == &Accessible2)
+    if (This == &Accessible_child)
+        CHECK_EXPECT(Accessible_child_get_accName);
+    else if (This == &Accessible_child2)
+        CHECK_EXPECT(Accessible_child2_get_accName);
+    else if (This == &Accessible2)
         CHECK_EXPECT(Accessible2_get_accName);
     else
         CHECK_EXPECT(Accessible_get_accName);
@@ -271,7 +334,11 @@ static HRESULT WINAPI Accessible_get_accRole(IAccessible *iface, VARIANT child_i
 {
     struct Accessible *This = impl_from_Accessible(iface);
 
-    if (This == &Accessible2)
+    if (This == &Accessible_child)
+        CHECK_EXPECT(Accessible_child_get_accRole);
+    else if (This == &Accessible_child2)
+        CHECK_EXPECT(Accessible_child2_get_accRole);
+    else if (This == &Accessible2)
         CHECK_EXPECT(Accessible2_get_accRole);
     else
         CHECK_EXPECT(Accessible_get_accRole);
@@ -291,11 +358,39 @@ static HRESULT WINAPI Accessible_get_accState(IAccessible *iface, VARIANT child_
 {
     struct Accessible *This = impl_from_Accessible(iface);
 
-    if (This == &Accessible2)
+    if (This == &Accessible_child)
+        CHECK_EXPECT(Accessible_child_get_accState);
+    else if (This == &Accessible_child2)
+        CHECK_EXPECT(Accessible_child2_get_accState);
+    else if (This == &Accessible2)
         CHECK_EXPECT(Accessible2_get_accState);
     else
         CHECK_EXPECT(Accessible_get_accState);
 
+    if (V_VT(&child_id) != VT_I4)
+        return E_INVALIDARG;
+
+    if (This == &Accessible && V_I4(&child_id) != CHILDID_SELF)
+    {
+        switch (V_I4(&child_id))
+        {
+        case 1:
+            V_VT(out_state) = VT_I4;
+            V_I4(out_state) = STATE_SYSTEM_INVISIBLE;
+            break;
+
+        case 3:
+            V_VT(out_state) = VT_I4;
+            V_I4(out_state) = STATE_SYSTEM_FOCUSABLE;
+            break;
+
+        default:
+            return E_INVALIDARG;
+        }
+
+        return S_OK;
+    }
+
     if (This->state)
     {
         V_VT(out_state) = VT_I4;
@@ -358,7 +453,11 @@ static HRESULT WINAPI Accessible_accLocation(IAccessible *iface, LONG *out_left,
 {
     struct Accessible *This = impl_from_Accessible(iface);
 
-    if (This == &Accessible2)
+    if (This == &Accessible_child)
+        CHECK_EXPECT(Accessible_child_accLocation);
+    else if (This == &Accessible_child2)
+        CHECK_EXPECT(Accessible_child2_accLocation);
+    else if (This == &Accessible2)
         CHECK_EXPECT(Accessible2_accLocation);
     else
         CHECK_EXPECT(Accessible_accLocation);
@@ -382,6 +481,8 @@ static HRESULT WINAPI Accessible_accNavigate(IAccessible *iface, LONG nav_direct
 
     if (This == &Accessible_child)
         CHECK_EXPECT(Accessible_child_accNavigate);
+    else if (This == &Accessible_child2)
+        CHECK_EXPECT(Accessible_child2_accNavigate);
     else
         CHECK_EXPECT(Accessible_accNavigate);
     VariantInit(out_var);
@@ -535,6 +636,17 @@ static struct Accessible Accessible_child =
     0, 0, 0, 0,
 };
 
+static struct Accessible Accessible_child2 =
+{
+    { &AccessibleVtbl },
+    { &OleWindowVtbl },
+    1,
+    &Accessible.IAccessible_iface,
+    0, 0,
+    0, 0, 0, NULL,
+    0, 0, 0, 0,
+};
+
 static IAccessible *acc_client;
 static LRESULT WINAPI test_wnd_proc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
 {
@@ -890,6 +1002,499 @@ static void set_accessible_props(struct Accessible *acc, INT role, INT state,
     acc->height = height;
 }
 
+#define check_fragment_acc( fragment, acc, cid) \
+        check_fragment_acc_( (fragment), (acc), (cid), __LINE__)
+static void check_fragment_acc_(IRawElementProviderFragment *elfrag, IAccessible *acc,
+        INT cid, int line)
+{
+    ILegacyIAccessibleProvider *accprov;
+    IAccessible *accessible;
+    INT child_id;
+    HRESULT hr;
+
+    hr = IRawElementProviderFragment_QueryInterface(elfrag, &IID_ILegacyIAccessibleProvider, (void **)&accprov);
+    ok_(__FILE__, line) (hr == S_OK, "Unexpected hr %#lx.\n", hr);
+    ok_(__FILE__, line) (!!accprov, "accprov == NULL\n");
+
+    hr = ILegacyIAccessibleProvider_GetIAccessible(accprov, &accessible);
+    ok_(__FILE__, line) (hr == S_OK, "Unexpected hr %#lx.\n", hr);
+    ok_(__FILE__, line) (accessible == acc, "accessible != acc\n");
+    IAccessible_Release(accessible);
+
+    hr = ILegacyIAccessibleProvider_get_ChildId(accprov, &child_id);
+    ok_(__FILE__, line) (hr == S_OK, "Unexpected hr %#lx.\n", hr);
+    ok_(__FILE__, line) (child_id == cid, "child_id != cid\n");
+
+    ILegacyIAccessibleProvider_Release(accprov);
+}
+
+static void test_uia_prov_from_acc_navigation(void)
+{
+    IRawElementProviderFragment *elfrag, *elfrag2, *elfrag3;
+    IRawElementProviderSimple *elprov, *elprov2;
+    HRESULT hr;
+
+    /*
+     * Full IAccessible parent, with 4 children:
+     * childid 1 is a simple element, with STATE_SYSTEM_INVISIBLE.
+     * childid 2 is Accessible_child.
+     * childid 3 is a simple element with STATE_SYSTEM_NORMAL.
+     * childid 4 is Accessible_child2.
+     */
+    hr = pUiaProviderFromIAccessible(&Accessible.IAccessible_iface, CHILDID_SELF, UIA_PFIA_DEFAULT, &elprov);
+    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+    ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
+
+    hr = IRawElementProviderSimple_QueryInterface(elprov, &IID_IRawElementProviderFragment, (void **)&elfrag);
+    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+    ok(!!elfrag, "elfrag == NULL\n");
+
+    /*
+     * First time doing NavigateDirection_Parent will result in the same root
+     * accessible check as get_HostRawElementProvider. If this IAccessible is
+     * the root for its associated HWND, NavigateDirection_Parent and
+     * NavigateDirection_Next/PreviousSibling will do nothing, as UI Automation
+     * provides non-client area providers for the root IAccessible's parent
+     * and siblings.
+     */
+    set_accessible_props(&Accessible, ROLE_SYSTEM_DOCUMENT, STATE_SYSTEM_FOCUSABLE, 4,
+            L"acc_name", 0, 0, 50, 50);
+    set_accessible_props(&Accessible2, ROLE_SYSTEM_DOCUMENT, STATE_SYSTEM_FOCUSABLE, 4,
+            L"acc_name", 0, 0, 50, 50);
+    acc_client = &Accessible2.IAccessible_iface;
+    SET_EXPECT(winproc_GETOBJECT_CLIENT);
+    SET_EXPECT(Accessible_get_accRole);
+    SET_EXPECT(Accessible_get_accState);
+    SET_EXPECT(Accessible_get_accChildCount);
+    SET_EXPECT(Accessible_accLocation);
+    SET_EXPECT(Accessible_get_accName);
+    SET_EXPECT(Accessible2_get_accRole);
+    SET_EXPECT(Accessible2_get_accState);
+    SET_EXPECT(Accessible2_get_accChildCount);
+    SET_EXPECT(Accessible2_accLocation);
+    SET_EXPECT(Accessible2_get_accName);
+    SET_EXPECT(Accessible2_QI_IAccIdentity);
+    SET_EXPECT(Accessible2_get_accParent);
+    elfrag2 = (void *)0xdeadbeef;
+    hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_Parent, &elfrag2);
+    ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
+    todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+    ok(!elfrag2, "elfrag2 != NULL\n");
+    todo_wine CHECK_CALLED(winproc_GETOBJECT_CLIENT);
+    todo_wine CHECK_CALLED(Accessible_get_accRole);
+    todo_wine CHECK_CALLED(Accessible_get_accState);
+    todo_wine CHECK_CALLED(Accessible_get_accChildCount);
+    todo_wine CHECK_CALLED(Accessible_accLocation);
+    todo_wine CHECK_CALLED(Accessible_get_accName);
+    todo_wine CHECK_CALLED(Accessible2_get_accRole);
+    todo_wine CHECK_CALLED(Accessible2_get_accState);
+    todo_wine CHECK_CALLED(Accessible2_get_accChildCount);
+    todo_wine CHECK_CALLED(Accessible2_accLocation);
+    todo_wine CHECK_CALLED(Accessible2_get_accName);
+    todo_wine CHECK_CALLED(Accessible2_QI_IAccIdentity);
+    todo_wine CHECK_CALLED(Accessible2_get_accParent);
+    acc_client = NULL;
+
+    if (SUCCEEDED(hr))
+    {
+        /* No check against root IAccessible, since it was done previously. */
+        elprov2 = (void *)0xdeadbeef;
+        hr = IRawElementProviderSimple_get_HostRawElementProvider(elprov, &elprov2);
+        ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+        ok(!!elprov2, "elprov == NULL, elprov %p\n", elprov2);
+        IRawElementProviderSimple_Release(elprov2);
+    }
+
+    /* Do nothing. */
+    elfrag2 = (void *)0xdeadbeef;
+    hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_Parent, &elfrag2);
+    ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
+    todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+    ok(!elfrag2, "elfrag2 != NULL\n");
+
+    elfrag2 = (void *)0xdeadbeef;
+    hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_NextSibling, &elfrag2);
+    ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
+    todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+    ok(!elfrag2, "elfrag2 != NULL\n");
+
+    elfrag2 = (void *)0xdeadbeef;
+    hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_PreviousSibling, &elfrag2);
+    ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
+    todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+    ok(!elfrag2, "elfrag2 != NULL\n");
+
+    /*
+     * Retrieve childid 2 (Accessible_child) as first child. childid 1 is skipped due to
+     * having a state of STATE_SYSTEM_INVISIBLE.
+     */
+    set_accessible_props(&Accessible_child, 0, STATE_SYSTEM_FOCUSABLE, 0, NULL, 0, 0, 0, 0);
+    set_accessible_props(&Accessible_child2, 0, STATE_SYSTEM_FOCUSABLE, 0, NULL, 0, 0, 0, 0);
+    SET_EXPECT_MULTI(Accessible_get_accChildCount, 3);
+    SET_EXPECT_MULTI(Accessible_get_accChild, 2);
+    SET_EXPECT(Accessible_get_accState);
+    SET_EXPECT(Accessible_child_get_accState);
+    SET_EXPECT(Accessible_child_accNavigate);
+    SET_EXPECT(Accessible_child_get_accParent);
+    hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_FirstChild, &elfrag2);
+    todo_wine ok(Accessible_child.ref == 2, "Unexpected refcnt %ld\n", Accessible_child.ref);
+    todo_wine ok(Accessible.ref == 3, "Unexpected refcnt %ld\n", Accessible.ref);
+    todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+    todo_wine ok(!!elfrag2, "elfrag2 == NULL\n");
+    todo_wine CHECK_CALLED_MULTI(Accessible_get_accChildCount, 3);
+    todo_wine CHECK_CALLED_MULTI(Accessible_get_accChild, 2);
+    todo_wine CHECK_CALLED(Accessible_child_get_accState);
+    todo_wine CHECK_CALLED(Accessible_child_accNavigate);
+    todo_wine CHECK_CALLED(Accessible_child_get_accParent);
+    if (elfrag2)
+    {
+        check_fragment_acc(elfrag2, &Accessible_child.IAccessible_iface, CHILDID_SELF);
+
+        SET_EXPECT(Accessible_get_accChildCount);
+        SET_EXPECT(Accessible_get_accChild);
+        SET_EXPECT(Accessible_get_accState);
+        hr = IRawElementProviderFragment_Navigate(elfrag2, NavigateDirection_NextSibling, &elfrag3);
+        ok(Accessible.ref == 5, "Unexpected refcnt %ld\n", Accessible.ref);
+        ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+        ok(!!elfrag3, "elfrag2 == NULL\n");
+        CHECK_CALLED(Accessible_get_accChildCount);
+        CHECK_CALLED(Accessible_get_accChild);
+        CHECK_CALLED(Accessible_get_accState);
+        check_fragment_acc(elfrag3, &Accessible.IAccessible_iface, 3);
+
+        IRawElementProviderFragment_Release(elfrag3);
+        ok(Accessible.ref == 3, "Unexpected refcnt %ld\n", Accessible.ref);
+        IRawElementProviderFragment_Release(elfrag2);
+        ok(Accessible_child.ref == 1, "Unexpected refcnt %ld\n", Accessible_child.ref);
+        ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
+    }
+
+    /* Retrieve childid 3 as first child now that Accessible_child is invisible. */
+    set_accessible_props(&Accessible_child, 0, STATE_SYSTEM_INVISIBLE, 0, NULL, 0, 0, 0, 0);
+    SET_EXPECT_MULTI(Accessible_get_accChildCount, 4);
+    SET_EXPECT_MULTI(Accessible_get_accChild, 3);
+    SET_EXPECT_MULTI(Accessible_get_accState, 2);
+    SET_EXPECT(Accessible_child_get_accState);
+    hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_FirstChild, &elfrag2);
+    todo_wine ok(Accessible.ref == 4, "Unexpected refcnt %ld\n", Accessible.ref);
+    todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+    todo_wine ok(!!elfrag2, "elfrag2 == NULL\n");
+    todo_wine CHECK_CALLED_MULTI(Accessible_get_accChildCount, 4);
+    todo_wine CHECK_CALLED_MULTI(Accessible_get_accChild, 3);
+    todo_wine CHECK_CALLED_MULTI(Accessible_get_accState, 2);
+    todo_wine CHECK_CALLED(Accessible_child_get_accState);
+    if (elfrag2)
+    {
+        check_fragment_acc(elfrag2, &Accessible.IAccessible_iface, 3);
+        IRawElementProviderFragment_Release(elfrag2);
+        ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
+    }
+
+    /* Retrieve childid 4 (Accessible_child2) as last child. */
+    set_accessible_props(&Accessible_child2, 0, STATE_SYSTEM_FOCUSABLE, 0, NULL, 0, 0, 0, 0);
+    SET_EXPECT_MULTI(Accessible_get_accChildCount, 2);
+    SET_EXPECT(Accessible_get_accChild);
+    SET_EXPECT(Accessible_child2_get_accState);
+    SET_EXPECT(Accessible_child2_accNavigate);
+    SET_EXPECT(Accessible_child2_get_accParent);
+    hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_LastChild, &elfrag2);
+    todo_wine ok(Accessible_child2.ref == 2, "Unexpected refcnt %ld\n", Accessible_child2.ref);
+    todo_wine ok(Accessible.ref == 3, "Unexpected refcnt %ld\n", Accessible.ref);
+    todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+    todo_wine ok(!!elfrag2, "elfrag2 == NULL\n");
+    todo_wine CHECK_CALLED_MULTI(Accessible_get_accChildCount, 2);
+    todo_wine CHECK_CALLED(Accessible_get_accChild);
+    todo_wine CHECK_CALLED(Accessible_child2_get_accState);
+    todo_wine CHECK_CALLED(Accessible_child2_accNavigate);
+    todo_wine CHECK_CALLED(Accessible_child2_get_accParent);
+    if (elfrag2)
+    {
+        check_fragment_acc(elfrag2, &Accessible_child2.IAccessible_iface, CHILDID_SELF);
+
+        SET_EXPECT(Accessible_get_accChildCount);
+        SET_EXPECT(Accessible_get_accChild);
+        SET_EXPECT(Accessible_get_accState);
+        hr = IRawElementProviderFragment_Navigate(elfrag2, NavigateDirection_PreviousSibling, &elfrag3);
+        ok(Accessible.ref == 5, "Unexpected refcnt %ld\n", Accessible.ref);
+        ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+        ok(!!elfrag3, "elfrag2 == NULL\n");
+        CHECK_CALLED(Accessible_get_accChildCount);
+        CHECK_CALLED(Accessible_get_accChild);
+        CHECK_CALLED(Accessible_get_accState);
+        check_fragment_acc(elfrag3, &Accessible.IAccessible_iface, 3);
+
+        IRawElementProviderFragment_Release(elfrag3);
+        ok(Accessible.ref == 3, "Unexpected refcnt %ld\n", Accessible.ref);
+        IRawElementProviderFragment_Release(elfrag2);
+        ok(Accessible_child2.ref == 1, "Unexpected refcnt %ld\n", Accessible_child2.ref);
+        ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
+    }
+
+    /* Retrieve childid 3 as last child, now that Accessible_child2 is STATE_SYSTEM_INVISIBLE. */
+    set_accessible_props(&Accessible_child2, 0, STATE_SYSTEM_INVISIBLE, 0, NULL, 0, 0, 0, 0);
+    SET_EXPECT_MULTI(Accessible_get_accChildCount, 3);
+    SET_EXPECT_MULTI(Accessible_get_accChild, 2);
+    SET_EXPECT(Accessible_get_accState);
+    SET_EXPECT(Accessible_child2_get_accState);
+    hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_LastChild, &elfrag2);
+    todo_wine ok(Accessible.ref == 4, "Unexpected refcnt %ld\n", Accessible.ref);
+    todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+    todo_wine ok(!!elfrag2, "elfrag2 == NULL\n");
+    todo_wine CHECK_CALLED_MULTI(Accessible_get_accChildCount, 3);
+    todo_wine CHECK_CALLED_MULTI(Accessible_get_accChild, 2);
+    todo_wine CHECK_CALLED(Accessible_get_accState);
+    todo_wine CHECK_CALLED(Accessible_child2_get_accState);
+    if (elfrag2)
+    {
+        check_fragment_acc(elfrag2, &Accessible.IAccessible_iface, 3);
+        IRawElementProviderFragment_Release(elfrag2);
+        ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
+    }
+
+    IRawElementProviderFragment_Release(elfrag);
+    IRawElementProviderSimple_Release(elprov);
+    ok(Accessible.ref == 1, "Unexpected refcnt %ld\n", Accessible.ref);
+
+    /*
+     * Full IAccessible child tests.
+     */
+    SET_EXPECT(Accessible_child_accNavigate);
+    SET_EXPECT(Accessible_child_get_accParent);
+    hr = pUiaProviderFromIAccessible(&Accessible_child.IAccessible_iface, 0, UIA_PFIA_DEFAULT, &elprov);
+    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+    CHECK_CALLED(Accessible_child_accNavigate);
+    CHECK_CALLED(Accessible_child_get_accParent);
+    ok(Accessible_child.ref == 2, "Unexpected refcnt %ld\n", Accessible_child.ref);
+
+    hr = IRawElementProviderSimple_QueryInterface(elprov, &IID_IRawElementProviderFragment, (void **)&elfrag);
+    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+    ok(!!elfrag, "elfrag == NULL\n");
+
+    /*
+     * After determining this isn't the root IAccessible, get_accParent will
+     * be used to get the parent.
+     */
+    set_accessible_props(&Accessible2, ROLE_SYSTEM_DOCUMENT, STATE_SYSTEM_FOCUSABLE, 0, NULL, 0, 0, 0, 0);
+    set_accessible_props(&Accessible_child, ROLE_SYSTEM_CLIENT, STATE_SYSTEM_FOCUSABLE, 0, NULL, 0, 0, 0, 0);
+    acc_client = &Accessible2.IAccessible_iface;
+    SET_EXPECT(winproc_GETOBJECT_CLIENT);
+    SET_EXPECT(Accessible_child_get_accRole);
+    SET_EXPECT(Accessible_child_get_accParent);
+    SET_EXPECT(Accessible2_get_accRole);
+    SET_EXPECT(Accessible2_QI_IAccIdentity);
+    SET_EXPECT(Accessible2_get_accParent);
+    hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_Parent, &elfrag2);
+    todo_wine ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
+    todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+    todo_wine ok(!!elfrag2, "elfrag2 == NULL\n");
+    todo_wine CHECK_CALLED(Accessible_child_get_accParent);
+    todo_wine CHECK_CALLED(Accessible_child_get_accRole);
+    todo_wine CHECK_CALLED(Accessible2_get_accRole);
+    todo_wine CHECK_CALLED(Accessible2_QI_IAccIdentity);
+    todo_wine CHECK_CALLED(Accessible2_get_accParent);
+    todo_wine CHECK_CALLED(winproc_GETOBJECT_CLIENT);
+    if (elfrag2)
+    {
+        check_fragment_acc(elfrag2, &Accessible.IAccessible_iface, CHILDID_SELF);
+        IRawElementProviderFragment_Release(elfrag2);
+        ok(Accessible.ref == 1, "Unexpected refcnt %ld\n", Accessible.ref);
+    }
+    acc_client = NULL;
+
+    /* Second call only does get_accParent, no root check. */
+    SET_EXPECT(Accessible_child_get_accParent);
+    hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_Parent, &elfrag2);
+    todo_wine ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
+    todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+    todo_wine ok(!!elfrag2, "elfrag2 == NULL\n");
+    todo_wine CHECK_CALLED(Accessible_child_get_accParent);
+    if (elfrag2)
+    {
+        check_fragment_acc(elfrag2, &Accessible.IAccessible_iface, CHILDID_SELF);
+        IRawElementProviderFragment_Release(elfrag2);
+        ok(Accessible.ref == 1, "Unexpected refcnt %ld\n", Accessible.ref);
+    }
+
+    /* ChildCount of 0, do nothing for First/Last child.*/
+    SET_EXPECT(Accessible_child_get_accChildCount);
+    hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_FirstChild, &elfrag2);
+    todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+    ok(!elfrag2, "elfrag2 != NULL\n");
+    todo_wine CHECK_CALLED(Accessible_child_get_accChildCount);
+
+    SET_EXPECT(Accessible_child_get_accChildCount);
+    hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_LastChild, &elfrag2);
+    todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+    ok(!elfrag2, "elfrag2 != NULL\n");
+    todo_wine CHECK_CALLED(Accessible_child_get_accChildCount);
+
+    /*
+     * In the case of sibling navigation on an IAccessible that wasn't
+     * received through previous navigation from a parent (i.e, from
+     * NavigateDirection_First/LastChild), we have to figure out which
+     * IAccessible child we represent by comparing against all children of our
+     * IAccessible parent. If we find more than one IAccessible that matches,
+     * or none at all that do, navigation will fail.
+     */
+    set_accessible_props(&Accessible_child, ROLE_SYSTEM_CLIENT, STATE_SYSTEM_FOCUSABLE, 1,
+            L"acc_child", 0, 0, 50, 50);
+    set_accessible_props(&Accessible_child2, ROLE_SYSTEM_CLIENT, STATE_SYSTEM_FOCUSABLE, 1,
+            L"acc_child", 0, 0, 50, 50);
+    SET_EXPECT_MULTI(Accessible_get_accChildCount, 5);
+    SET_EXPECT_MULTI(Accessible_get_accChild, 4);
+    SET_EXPECT(Accessible_child_get_accParent);
+    SET_EXPECT(Accessible_child_get_accRole);
+    SET_EXPECT(Accessible_child_get_accState);
+    SET_EXPECT(Accessible_child_get_accChildCount);
+    SET_EXPECT(Accessible_child_accLocation);
+    SET_EXPECT(Accessible_child_get_accName);
+    SET_EXPECT(Accessible_child2_get_accRole);
+    SET_EXPECT(Accessible_child2_get_accState);
+    SET_EXPECT(Accessible_child2_get_accChildCount);
+    SET_EXPECT(Accessible_child2_accLocation);
+    SET_EXPECT(Accessible_child2_get_accName);
+    hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_NextSibling, &elfrag2);
+    todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+    ok(!elfrag2, "elfrag2 != NULL\n");
+    todo_wine CHECK_CALLED_MULTI(Accessible_get_accChildCount, 5);
+    todo_wine CHECK_CALLED_MULTI(Accessible_get_accChild, 4);
+    todo_wine CHECK_CALLED(Accessible_child_get_accParent);
+    todo_wine CHECK_CALLED(Accessible_child_get_accRole);
+    todo_wine CHECK_CALLED(Accessible_child_get_accState);
+    todo_wine CHECK_CALLED(Accessible_child_get_accChildCount);
+    todo_wine CHECK_CALLED(Accessible_child_accLocation);
+    todo_wine CHECK_CALLED(Accessible_child_get_accName);
+    todo_wine CHECK_CALLED(Accessible_child2_get_accRole);
+    todo_wine CHECK_CALLED(Accessible_child2_get_accState);
+    todo_wine CHECK_CALLED(Accessible_child2_get_accChildCount);
+    todo_wine CHECK_CALLED(Accessible_child2_accLocation);
+    todo_wine CHECK_CALLED(Accessible_child2_get_accName);
+
+    /* Now they have a role mismatch, we can determine our position. */
+    set_accessible_props(&Accessible_child2, ROLE_SYSTEM_DOCUMENT, STATE_SYSTEM_FOCUSABLE, 1,
+            L"acc_child", 0, 0, 50, 50);
+    SET_EXPECT_MULTI(Accessible_get_accChildCount, 6);
+    SET_EXPECT_MULTI(Accessible_get_accChild, 5);
+    /* Check ChildID 1 for STATE_SYSTEM_INVISIBLE. */
+    SET_EXPECT(Accessible_get_accState);
+    SET_EXPECT(Accessible_child_get_accParent);
+    SET_EXPECT(Accessible_child_get_accRole);
+    SET_EXPECT(Accessible_child2_get_accRole);
+    hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_PreviousSibling, &elfrag2);
+    /*
+     * Even though we didn't get a new fragment, now that we know our
+     * position, a reference is added to the parent IAccessible.
+     */
+    todo_wine ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
+    todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+    ok(!elfrag2, "elfrag2 != NULL\n");
+    todo_wine CHECK_CALLED_MULTI(Accessible_get_accChildCount, 6);
+    todo_wine CHECK_CALLED_MULTI(Accessible_get_accChild, 5);
+    todo_wine CHECK_CALLED(Accessible_get_accState);
+    todo_wine CHECK_CALLED(Accessible_child_get_accParent);
+    todo_wine CHECK_CALLED(Accessible_child_get_accRole);
+    todo_wine CHECK_CALLED(Accessible_child2_get_accRole);
+
+    /* Now that we know our position, no extra nav work. */
+    SET_EXPECT(Accessible_get_accChildCount);
+    SET_EXPECT(Accessible_get_accChild);
+    SET_EXPECT(Accessible_get_accState);
+    hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_NextSibling, &elfrag2);
+    todo_wine ok(Accessible.ref == 4, "Unexpected refcnt %ld\n", Accessible.ref);
+    todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+    todo_wine ok(!!elfrag2, "elfrag2 == NULL\n");
+    todo_wine CHECK_CALLED(Accessible_get_accChildCount);
+    todo_wine CHECK_CALLED(Accessible_get_accChild);
+    todo_wine CHECK_CALLED(Accessible_get_accState);
+    if (elfrag2)
+    {
+        check_fragment_acc(elfrag2, &Accessible.IAccessible_iface, 3);
+        IRawElementProviderFragment_Release(elfrag2);
+        ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
+    }
+
+    IRawElementProviderFragment_Release(elfrag);
+    IRawElementProviderSimple_Release(elprov);
+    ok(Accessible_child.ref == 1, "Unexpected refcnt %ld\n", Accessible_child.ref);
+    ok(Accessible.ref == 1, "Unexpected refcnt %ld\n", Accessible.ref);
+
+    /*
+     * Simple element child tests.
+     */
+    hr = pUiaProviderFromIAccessible(&Accessible.IAccessible_iface, 1, UIA_PFIA_DEFAULT, &elprov);
+    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+    ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
+
+    hr = IRawElementProviderSimple_QueryInterface(elprov, &IID_IRawElementProviderFragment, (void **)&elfrag);
+    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+    ok(!!elfrag, "elfrag == NULL\n");
+
+    /*
+     * Simple child elements don't check the root IAccessible, because they
+     * can't be the root IAccessible.
+     */
+    hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_Parent, &elfrag2);
+    todo_wine ok(Accessible.ref == 3, "Unexpected refcnt %ld\n", Accessible.ref);
+    todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+    todo_wine ok(!!elfrag2, "elfrag2 == NULL\n");
+    if (elfrag2)
+    {
+        check_fragment_acc(elfrag2, &Accessible.IAccessible_iface, CHILDID_SELF);
+        IRawElementProviderFragment_Release(elfrag2);
+        ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
+    }
+
+    /*
+     * Test NavigateDirection_First/LastChild on simple child element. Does
+     * nothing, as simple children cannot have children.
+     */
+    hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_FirstChild, &elfrag2);
+    ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
+    todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+    ok(!elfrag2, "elfrag2 != NULL\n");
+
+    hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_LastChild, &elfrag2);
+    ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
+    todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+    ok(!elfrag2, "elfrag2 != NULL\n");
+
+    /*
+     * NavigateDirection_Next/PreviousSibling behaves normally, no IAccessible
+     * comparisons.
+     */
+    SET_EXPECT(Accessible_get_accChildCount);
+    SET_EXPECT(Accessible_get_accChild);
+    SET_EXPECT(Accessible_child_get_accState);
+    SET_EXPECT(Accessible_child_accNavigate);
+    SET_EXPECT(Accessible_child_get_accParent);
+    hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_NextSibling, &elfrag2);
+    todo_wine ok(Accessible_child.ref == 2, "Unexpected refcnt %ld\n", Accessible_child.ref);
+    todo_wine ok(Accessible.ref == 4, "Unexpected refcnt %ld\n", Accessible.ref);
+    todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+    todo_wine ok(!!elfrag2, "elfrag2 == NULL\n");
+    todo_wine CHECK_CALLED(Accessible_get_accChildCount);
+    todo_wine CHECK_CALLED(Accessible_get_accChild);
+    todo_wine CHECK_CALLED(Accessible_child_get_accState);
+    todo_wine CHECK_CALLED(Accessible_child_accNavigate);
+    todo_wine CHECK_CALLED(Accessible_child_get_accParent);
+    if (elfrag2)
+    {
+        check_fragment_acc(elfrag2, &Accessible_child.IAccessible_iface, CHILDID_SELF);
+        IRawElementProviderFragment_Release(elfrag2);
+        ok(Accessible_child.ref == 1, "Unexpected refcnt %ld\n", Accessible_child.ref);
+        ok(Accessible.ref == 3, "Unexpected refcnt %ld\n", Accessible.ref);
+    }
+
+    IRawElementProviderFragment_Release(elfrag);
+    IRawElementProviderSimple_Release(elprov);
+    ok(Accessible.ref == 1, "Unexpected refcnt %ld\n", Accessible.ref);
+
+    set_accessible_props(&Accessible, 0, 0, 0, NULL, 0, 0, 0, 0);
+    set_accessible_props(&Accessible2, 0, 0, 0, NULL, 0, 0, 0, 0);
+    set_accessible_props(&Accessible_child, 0, 0, 0, NULL, 0, 0, 0, 0);
+    set_accessible_props(&Accessible_child2, 0, 0, 0, NULL, 0, 0, 0, 0);
+}
+
 static void test_uia_prov_from_acc_properties(void)
 {
     IRawElementProviderSimple *elprov;
@@ -1510,6 +2115,7 @@ static void test_UiaProviderFromIAccessible(void)
     ok(Accessible.ref == 1, "Unexpected refcnt %ld\n", Accessible.ref);
 
     test_uia_prov_from_acc_properties();
+    test_uia_prov_from_acc_navigation();
 
     CoUninitialize();
     DestroyWindow(hwnd);
-- 
GitLab


https://gitlab.winehq.org/wine/wine/-/merge_requests/230



More information about the wine-devel mailing list