[PATCH v3 1/1] riched20/tests: Test for ITextDocument::Undo and ITextDocument::Redo.

Jinoh Kang wine at gitlab.winehq.org
Tue Jun 28 10:18:38 CDT 2022


From: Jinoh Kang <jinoh.kang.kr at gmail.com>

Signed-off-by: Jinoh Kang <jinoh.kang.kr at gmail.com>
---
 dlls/riched20/tests/richole.c | 477 ++++++++++++++++++++++++++++++++++
 1 file changed, 477 insertions(+)

diff --git a/dlls/riched20/tests/richole.c b/dlls/riched20/tests/richole.c
index 6a361bc3fa4..542de075bc6 100644
--- a/dlls/riched20/tests/richole.c
+++ b/dlls/riched20/tests/richole.c
@@ -4955,6 +4955,481 @@ static void test_clipboard(void)
   ITextRange_Release(range);
 }
 
+static void subtest_undo(const char *dummy_text)
+{
+  static const char *text_seq[] = {
+    "",
+    "1-alpha",
+    "2-beta",
+    "3-gamma",
+    "4-delta",
+    "5-epsilon",
+    "6-zeta",
+    "7-eta",
+  };
+  static LONG seq[] = { -1, -2, -3, -1, 1, 2, 3, 1, -5, 2, -1, 3, 1, 0 };
+  LONG i = 0, stack_pos = 0;
+  IRichEditOle *reole = NULL;
+  ITextDocument *doc = NULL;
+  ITextSelection *selection;
+  char buffer[1024] = "";
+  HRESULT hr;
+  HWND hwnd;
+  LONG count = 0;
+
+  winetest_push_context("(%Iu)", dummy_text ? strlen(dummy_text) : 0);
+
+  create_interfaces(&hwnd, &reole, &doc, &selection);
+
+  for (i = -2; i <= 2; i++)
+  {
+    if (i != tomFalse && i != tomTrue)
+    {
+      hr = ITextDocument_Undo(doc, i, NULL);
+      todo_wine
+      ok(hr == (i >= 1 ? S_OK : S_FALSE), "(%ld at 0) Undo: %#lx\n", i, hr);
+
+      count = 0xcccccccc;
+      hr = ITextDocument_Undo(doc, i, &count);
+      todo_wine
+      ok(hr == (i >= 1 ? S_OK : S_FALSE), "(%ld at 0) Undo: %#lx\n", i, hr);
+      todo_wine
+      ok(count == (i >= 1 ? i : 0), "(%ld at 0) Expected %ld, got %ld\n", i, i >= 0 ? i : 0, count);
+    }
+
+    hr = ITextDocument_Redo(doc, i, NULL);
+    todo_wine
+    ok(hr == (i == 0 ? S_OK : S_FALSE), "(%ld at 0) Redo: %#lx\n", i, hr);
+
+    count = 0xcccccccc;
+    hr = ITextDocument_Redo(doc, i, &count);
+    todo_wine
+    ok(hr == (i == 0 ? S_OK : S_FALSE), "(%ld at 0) Redo: %#lx\n", i, hr);
+    todo_wine
+    ok(count == 0, "(%ld at 0) got %ld\n", i, count);
+  }
+
+  while (stack_pos < ARRAY_SIZE(text_seq) - 1)
+  {
+    stack_pos++;
+    if (dummy_text)
+    {
+      hr = ITextDocument_Undo(doc, tomSuspend, NULL);
+      todo_wine
+      ok(hr == S_FALSE, "(@%ld) Undo: %#lx\n", stack_pos, hr);
+      if (SUCCEEDED(hr))
+      {
+        SendMessageA(hwnd, EM_SETSEL, 0, 0);
+        SendMessageA(hwnd, EM_REPLACESEL, TRUE, (LPARAM)dummy_text);
+        SendMessageA(hwnd, EM_SETSEL, 0, strlen(dummy_text));
+        SendMessageA(hwnd, EM_REPLACESEL, TRUE, (LPARAM)"");
+        hr = ITextDocument_Undo(doc, tomResume, NULL);
+        todo_wine
+        ok(hr == S_FALSE, "(@%ld) Undo: %#lx\n", stack_pos, hr);
+      }
+    }
+    SendMessageA(hwnd, EM_SETSEL, 0, -1);
+    SendMessageA(hwnd, EM_REPLACESEL, TRUE, (LPARAM)text_seq[stack_pos]);
+  }
+
+  for (i = 0; i < ARRAY_SIZE(seq); i++)
+  {
+    LONG expect_count;
+
+    memset(buffer, 0, sizeof(buffer));
+    SendMessageA(hwnd, WM_GETTEXT, ARRAY_SIZE(buffer), (LPARAM)buffer);
+    todo_wine_if(stack_pos != ARRAY_SIZE(text_seq) - 1)
+    ok(strcmp(buffer, text_seq[stack_pos]) == 0, "Expected %s, got %s\n",
+       wine_dbgstr_a(text_seq[stack_pos]), wine_dbgstr_a(buffer));
+
+    if (!seq[i]) break;
+
+    count = 0xcccccccc;
+    expect_count = labs(stack_pos - min(max(stack_pos + seq[i], 0), (LONG)ARRAY_SIZE(seq) - 1));
+    if (seq[i] < 0)
+    {
+      hr = ITextDocument_Undo(doc, -seq[i], &count);
+      todo_wine
+      ok(hr == S_OK, "(%ld@%ld) Undo: %#lx\n", i, stack_pos, hr);
+      todo_wine
+      ok(count == expect_count, "(%ld@%ld) Expected %ld, got %ld\n", i, stack_pos, expect_count, count);
+      stack_pos -= count;
+    }
+    else
+    {
+      hr = ITextDocument_Redo(doc, seq[i], &count);
+      todo_wine
+      ok(hr == (expect_count ? S_OK : S_FALSE), "(%ld@%ld) Redo: %#lx\n", i, stack_pos, hr);
+      todo_wine
+      ok(count == expect_count, "(%ld@%ld) Expected %ld, got %ld\n", i, stack_pos, expect_count, count);
+      stack_pos += count;
+    }
+
+    if (FAILED(hr) || count <= 0) break;
+  }
+
+  release_interfaces(&hwnd, &reole, &doc, &selection);
+  winetest_pop_context();
+}
+
+static void test_undo(void)
+{
+  subtest_undo(NULL);
+  subtest_undo("dummy 12345");
+}
+
+#define ok_msg_result(h,m,w,l,r) ok_msg_result_(__LINE__,#m,h,m,w,l,r)
+static void ok_msg_result_(int line, const char *desc, HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam, LRESULT expect)
+{
+  LRESULT lresult = SendMessageA(hwnd, message, wparam, lparam);
+  ok_(__FILE__,line)(lresult == expect, "%s: Expected %Id, got %Id\n", desc, expect, lresult);
+}
+
+enum editorUndoState {
+  firstUndoState = 0,
+  undoStateActive = 0,
+  undoStateSuspended = 1,
+  undoStateDisabled = 2,
+  numUndoStates = 3,
+};
+
+enum editorUndoStateAction {
+  firstUndoAction = 0,
+  undoActionNoOp = 0,
+  undoActionEnable = 1,
+  undoActionDisable = 2,
+  undoActionSuspend = 3,
+  undoActionResume = 4,
+  numUndoActions = 5
+};
+
+enum editorUndoStateTestFlags {
+  undoTestUseWindowMessages = 0x1,
+  undoTestResetUndoLimit = 0x2,
+  undoTestDoFirstUndo = 0x4,
+  undoTestDoFirstRedo = 0x8,
+  undoTestMaxFlag = 0x10,
+};
+
+static const unsigned char undo_control_state_transitions[numUndoStates][numUndoActions] = {
+  /* undoStateActive */
+  {
+    undoStateActive,    /* undoActionNoOp */
+    undoStateActive,    /* undoActionEnable */
+    undoStateDisabled,  /* undoActionDisable */
+    undoStateSuspended, /* undoActionSuspend */
+    undoStateActive     /* undoActionResume */
+  },
+  /* undoStateSuspended */
+  {
+    undoStateSuspended, /* undoActionNoOp */
+    undoStateSuspended, /* undoActionEnable */
+    undoStateDisabled,  /* undoActionDisable */
+    undoStateSuspended, /* undoActionSuspend */
+    undoStateActive     /* undoActionResume */
+  },
+  /* undoStateDisabled */
+  {
+    undoStateDisabled,  /* undoActionNoOp */
+    undoStateActive,    /* undoActionEnable */
+    undoStateDisabled,  /* undoActionDisable */
+    undoStateDisabled,  /* undoActionSuspend */
+    undoStateActive     /* undoActionResume */
+  }
+};
+
+struct undo_test
+{
+  HWND hwnd;
+  ITextDocument *doc;
+  int test_flags;
+  enum editorUndoState undo_ctl_state;
+  LONG_PTR undo_limit;
+  BOOL last_undo_status;
+  BOOL last_redo_status;
+};
+
+static HRESULT perform_editor_undo_state_action(struct undo_test *inst, enum editorUndoStateAction action, LONG *count)
+{
+  HRESULT hr = S_OK;
+
+  if (count) *count = 0xcccccccc;
+
+  switch (action)
+  {
+    case undoActionNoOp:
+      if (count) *count = 0;
+      break;
+    case undoActionEnable:
+      if (inst->test_flags & undoTestResetUndoLimit)
+      {
+        LONG_PTR cur_undo_limit = SendMessageA(inst->hwnd, EM_SETUNDOLIMIT, inst->undo_limit, 0);
+        ok(cur_undo_limit == inst->undo_limit, "Expected undo limit %Id, got %Id\n",
+           inst->undo_limit, cur_undo_limit);
+        if (count) *count = 0;
+      }
+      else
+      {
+        hr = ITextDocument_Undo(inst->doc, tomTrue, count);
+        todo_wine
+        ok(hr == S_FALSE, "Undo: %#lx\n", hr);
+      }
+      inst->last_undo_status = TRUE;
+      break;
+    case undoActionDisable:
+      hr = ITextDocument_Undo(inst->doc, tomFalse, count);
+      todo_wine
+      ok(hr == S_OK, "Undo: %#lx\n", hr);
+      inst->last_undo_status = FALSE;
+      inst->last_redo_status = FALSE;
+      break;
+    case undoActionSuspend:
+      hr = ITextDocument_Undo(inst->doc, tomSuspend, count);
+      todo_wine
+      ok(hr == S_FALSE, "Undo: %#lx\n", hr);
+      break;
+    case undoActionResume:
+      hr = ITextDocument_Undo(inst->doc, tomResume, count);
+      todo_wine
+      ok(hr == S_FALSE, "Undo: %#lx\n", hr);
+      break;
+    default:
+      ok(0, "unreachable\n");
+      break;
+  }
+
+  if (count)
+  {
+    todo_wine
+    ok(*count == 0, "Got %ld\n", *count);
+  }
+  if (SUCCEEDED(hr))
+  {
+    inst->undo_ctl_state = undo_control_state_transitions[inst->undo_ctl_state][action];
+  }
+  return hr;
+}
+
+static HRESULT set_editor_undo_state(struct undo_test *inst, enum editorUndoState state)
+{
+  HRESULT hr = S_OK;
+  switch (state)
+  {
+    case undoStateActive:
+      if (FAILED(hr = perform_editor_undo_state_action(inst, undoActionEnable, NULL))) break;
+      if (FAILED(hr = perform_editor_undo_state_action(inst, undoActionResume, NULL))) break;
+      break;
+    case undoStateSuspended:
+      if (FAILED(hr = perform_editor_undo_state_action(inst, undoActionEnable, NULL))) break;
+      if (FAILED(hr = perform_editor_undo_state_action(inst, undoActionSuspend, NULL))) break;
+      break;
+    case undoStateDisabled:
+      if (FAILED(hr = perform_editor_undo_state_action(inst, undoActionDisable, NULL))) break;
+      break;
+    default:
+      ok(0, "unreachable\n");
+      break;
+  }
+  ok(inst->undo_ctl_state == state, "expected state %d, got %d\n", state, inst->undo_ctl_state);
+  todo_wine
+  ok(SUCCEEDED(hr), "cannot set state to %d: %#lx\n", undoStateActive, hr);
+  return hr;
+}
+
+static BOOL perform_undo(struct undo_test *inst)
+{
+  LONG count;
+  HRESULT hr;
+  LRESULT lres;
+
+  if (inst->test_flags & undoTestUseWindowMessages)
+  {
+    lres = SendMessageA(inst->hwnd, EM_UNDO, 0, 0);
+    ok(lres == FALSE || lres == TRUE, "unexpected LRESULT %#Ix\n", lres);
+    return lres;
+  }
+
+  count = 0xcccccccc;
+  hr = ITextDocument_Undo(inst->doc, 1, &count);
+  todo_wine
+  ok(SUCCEEDED(hr), "got hr %#lx\n", hr);
+  todo_wine
+  ok(count == (hr == S_OK), "expected count %d, got %ld\n", hr == S_OK, count);
+  return hr == S_OK && count > 0;
+}
+
+static BOOL perform_redo(struct undo_test *inst)
+{
+  LONG count;
+  HRESULT hr;
+  LRESULT lres;
+
+  if (inst->test_flags & undoTestUseWindowMessages)
+  {
+    lres = SendMessageA(inst->hwnd, EM_REDO, 0, 0);
+    ok(lres == FALSE || lres == TRUE, "unexpected LRESULT %#Ix\n", lres);
+    return lres;
+  }
+
+  count = 0xcccccccc;
+  hr = ITextDocument_Redo(inst->doc, 1, &count);
+  todo_wine
+  ok(SUCCEEDED(hr), "got hr %#lx\n", hr);
+  todo_wine
+  ok(count == (hr == S_OK), "expected count %d, got %ld\n", hr == S_OK, count);
+  return hr == S_OK && count > 0;
+}
+
+static HRESULT subtest_undo_control(struct undo_test *inst, enum editorUndoStateAction action)
+{
+  LONG undo_count, redo_count, count;
+  static const char text_foo[] = "foo";
+  static const char text_bar[] = "bar";
+  static const char *last_text, *last_text2;
+  char buffer[1024] = "";
+  BOOL result;
+  HRESULT hr;
+
+  SendMessageA(inst->hwnd, EM_EMPTYUNDOBUFFER, 0, 0);
+  undo_count = redo_count = 0;
+  ok_msg_result(inst->hwnd, EM_CANUNDO, 0, 0, undo_count > 0);
+  ok_msg_result(inst->hwnd, EM_CANREDO, 0, 0, redo_count > 0);
+
+  SendMessageA(inst->hwnd, WM_SETTEXT, 0, (LPARAM)(last_text = ""));
+  SendMessageA(inst->hwnd, WM_GETTEXT, ARRAY_SIZE(buffer), (LPARAM)buffer);
+  last_text = "";
+  ok(strcmp(buffer, last_text) == 0,
+     "Expected %s, got %s\n", wine_dbgstr_a(""), wine_dbgstr_a(buffer));
+
+  SendMessageA(inst->hwnd, EM_SETSEL, 0, -1);
+  SendMessageA(inst->hwnd, EM_REPLACESEL, TRUE, (LPARAM)(last_text = text_foo));
+  if (inst->undo_ctl_state == undoStateActive) undo_count++, redo_count = 0;
+  ok_msg_result(inst->hwnd, EM_CANUNDO, 0, 0, undo_count > 0);
+  ok_msg_result(inst->hwnd, EM_CANREDO, 0, 0, redo_count > 0);
+  SendMessageA(inst->hwnd, WM_GETTEXT, ARRAY_SIZE(buffer), (LPARAM)buffer);
+  ok(strcmp(buffer, last_text) == 0, "Expected %s, got %s\n", wine_dbgstr_a(text_foo), wine_dbgstr_a(buffer));
+
+  hr = perform_editor_undo_state_action(inst, action, &count);
+  ok(SUCCEEDED(hr), "failed to transition\n");
+  if (FAILED(hr)) return hr;
+  if (inst->undo_ctl_state == undoStateDisabled) undo_count = redo_count = 0;
+  ok_msg_result(inst->hwnd, EM_CANUNDO, 0, 0, undo_count > 0);
+  ok_msg_result(inst->hwnd, EM_CANREDO, 0, 0, redo_count > 0);
+
+  if (inst->test_flags & undoTestDoFirstUndo)
+  {
+    BOOL expect = FALSE;
+    result = perform_undo(inst);
+    if (inst->undo_ctl_state == undoStateActive)
+    {
+      if (undo_count > 0) inst->last_undo_status = TRUE;
+      expect = inst->last_undo_status;
+    }
+    ok(result == expect, "state %d: expected %d, got %d\n", inst->undo_ctl_state, expect, result);
+    if (result && undo_count > 0) undo_count--, redo_count++, last_text = "";
+    ok_msg_result(inst->hwnd, EM_CANUNDO, 0, 0, undo_count > 0);
+    ok_msg_result(inst->hwnd, EM_CANREDO, 0, 0, redo_count > 0);
+    SendMessageA(inst->hwnd, WM_GETTEXT, ARRAY_SIZE(buffer), (LPARAM)buffer);
+    ok(strcmp(buffer, last_text) == 0, "Expected %s, got %s\n", wine_dbgstr_a(last_text), wine_dbgstr_a(buffer));
+  }
+
+  if (inst->test_flags & undoTestDoFirstRedo)
+  {
+    BOOL expect = FALSE;
+    result = perform_redo(inst);
+    if (inst->undo_ctl_state == undoStateActive)
+    {
+      if (redo_count > 0) inst->last_redo_status = TRUE;
+      expect = inst->last_redo_status;
+    }
+    ok(result == expect, "state %d: expected %d, got %d\n", inst->undo_ctl_state, expect, result);
+    if (result && redo_count > 0) undo_count++, redo_count--, last_text = text_foo;
+    ok_msg_result(inst->hwnd, EM_CANUNDO, 0, 0, undo_count > 0);
+    ok_msg_result(inst->hwnd, EM_CANREDO, 0, 0, redo_count > 0);
+    SendMessageA(inst->hwnd, WM_GETTEXT, ARRAY_SIZE(buffer), (LPARAM)buffer);
+    ok(strcmp(buffer, last_text) == 0, "Expected %s, got %s\n", wine_dbgstr_a(last_text), wine_dbgstr_a(buffer));
+  }
+
+  SendMessageA(inst->hwnd, EM_SETSEL, 0, -1);
+  SendMessageA(inst->hwnd, EM_REPLACESEL, TRUE, (LPARAM)(last_text2 = text_bar));
+  if (inst->undo_ctl_state == undoStateActive) undo_count++, redo_count = 0;
+  ok_msg_result(inst->hwnd, EM_CANUNDO, 0, 0, undo_count > 0);
+  ok_msg_result(inst->hwnd, EM_CANREDO, 0, 0, redo_count > 0);
+  SendMessageA(inst->hwnd, WM_GETTEXT, ARRAY_SIZE(buffer), (LPARAM)buffer);
+  ok(strcmp(buffer, last_text2) == 0, "Expected %s, got %s\n", wine_dbgstr_a(last_text2), wine_dbgstr_a(buffer));
+
+  result = perform_undo(inst);
+  if (undo_count > 0) inst->last_undo_status = TRUE;
+  ok((inst->undo_ctl_state == undoStateActive) == result, "state %d: got %d\n", inst->undo_ctl_state, result);
+  if (result && undo_count > 0) undo_count--, redo_count++, last_text2 = last_text;
+  ok_msg_result(inst->hwnd, EM_CANUNDO, 0, 0, undo_count > 0);
+  ok_msg_result(inst->hwnd, EM_CANREDO, 0, 0, redo_count > 0);
+  SendMessageA(inst->hwnd, WM_GETTEXT, ARRAY_SIZE(buffer), (LPARAM)buffer);
+  ok(strcmp(buffer, last_text2) == 0, "Expected %s, got %s\n", wine_dbgstr_a(last_text2), wine_dbgstr_a(buffer));
+
+  result = perform_redo(inst);
+  if (redo_count > 0) inst->last_redo_status = TRUE;
+  ok((inst->undo_ctl_state == undoStateActive) == result, "state %d: got %d\n", inst->undo_ctl_state, result);
+  if (result && redo_count > 0) undo_count++, redo_count--, last_text2 = text_bar;
+  ok_msg_result(inst->hwnd, EM_CANUNDO, 0, 0, undo_count > 0);
+  ok_msg_result(inst->hwnd, EM_CANREDO, 0, 0, redo_count > 0);
+  SendMessageA(inst->hwnd, WM_GETTEXT, ARRAY_SIZE(buffer), (LPARAM)buffer);
+  ok(strcmp(buffer, last_text2) == 0, "Expected %s, got %s\n", wine_dbgstr_a(last_text2), wine_dbgstr_a(buffer));
+
+  return S_OK;
+}
+
+static void test_undo_control(void)
+{
+  enum editorUndoState state0;
+  enum editorUndoStateAction action0, action1;
+  IRichEditOle *reole = NULL;
+  ITextSelection *selection;
+  struct undo_test inst = { NULL };
+  HRESULT hr;
+
+  create_interfaces(&inst.hwnd, &reole, &inst.doc, &selection);
+  inst.undo_ctl_state = undoStateActive;
+  inst.last_undo_status = TRUE;
+  inst.last_redo_status = TRUE;
+  inst.undo_limit = SendMessageA(inst.hwnd, EM_SETUNDOLIMIT, 100, 0);
+  ok(inst.undo_limit >= 1, "Message EM_SETUNDOLIMIT returned %#Ix\n", inst.undo_limit);
+
+  hr = set_editor_undo_state(&inst, undoStateActive);
+  if (SUCCEEDED(hr))
+  {
+    for (inst.test_flags = 0; inst.test_flags < undoTestMaxFlag; inst.test_flags++)
+    {
+      for (state0 = firstUndoState; state0 < numUndoStates; state0++)
+      {
+        for (action0 = firstUndoAction; action0 < numUndoActions; action0++)
+        {
+          for (action1 = firstUndoAction; action1 < numUndoActions; action1++)
+          {
+            winetest_push_context("%x:%d:%d >?:%d", inst.test_flags, state0, action0, action1);
+            hr = set_editor_undo_state(&inst, state0);
+            winetest_pop_context();
+
+            if (FAILED(hr)) continue;
+
+            winetest_push_context("%x:%d:%d+>?:%d", inst.test_flags, state0, action0, action1);
+            hr = subtest_undo_control(&inst, action0);
+            winetest_pop_context();
+
+            if (FAILED(hr)) continue;
+
+            winetest_push_context("%x:%d:%d>%d:%d+", inst.test_flags, state0, action0, inst.undo_ctl_state, action1);
+            subtest_undo_control(&inst, action1);
+            winetest_pop_context();
+          }
+        }
+      }
+    }
+  }
+
+  release_interfaces(&inst.hwnd, &reole, &inst.doc, &selection);
+}
+
 START_TEST(richole)
 {
   /* Must explicitly LoadLibrary(). The test has no references to functions in
@@ -4996,4 +5471,6 @@ START_TEST(richole)
   test_MoveEnd_story();
   test_character_movement();
   test_clipboard();
+  test_undo();
+  test_undo_control();
 }
-- 
GitLab

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



More information about the wine-devel mailing list