diff --git a/core/tests/unit/api/actions_get_api.tests.cpp b/core/tests/unit/api/actions_get_api.tests.cpp index 10a1a979a8..8d975a94f1 100644 --- a/core/tests/unit/api/actions_get_api.tests.cpp +++ b/core/tests/unit/api/actions_get_api.tests.cpp @@ -15,73 +15,26 @@ #include "context.hpp" #include "../helpers/core_test_helpers.h" +#include "./actions_test_data.h" -struct TestData { - const char* test_name; - - /** - * the app context stored in the state, _before_ transform is applied -- NFU - */ - const km_core_cu *initial_app_context; - - /** - * cached context _after_ actions have been applied -- guaranteed NFD - * (essentially, this is initial_cached_context - - * actions_code_points_to_delete + actions_output) - no markers supported - */ - const km_core_cu *final_cached_context; - - /** - * number of NFD code points that the keyboard processor has asked to remove - * in its actions - */ - int actions_code_points_to_delete; - - /** - * NFD string that the keyboard processor has asked to insert in its actions - */ - const std::u32string actions_output; - - /** - * expected: NFU code points to ask app to remove - */ - const unsigned int expected_delete; - - /** - * expected: adjusted NFC output to insert into the app - */ - const std::u32string expected_output; - - /** - * expected: NFU adjusted final app context, which will be NFC from the - * boundary of the transform, but will not have been modified prior to that. - * Should match char-for-char what the app ends up with in its text buffer. - */ - const km_core_cu *expected_final_app_context; - - /** - * expected: the characters deleted from the context - */ - const std::u32string expected_deleted_context; -}; - -std::string GenerateTestName(const testing::TestParamInfo& info) { - return info.param.test_name; -} - -class GetActionApiTest : public testing::TestWithParam { +class GetActionApiTest : public testing::TestWithParam { protected: km_core_keyboard * test_kb = nullptr; km_core_state * test_state = nullptr; km_core_actions * test_actions = nullptr; - void Initialize(TestData const& data) { + void Initialize(ActionsTestData const& data) { + if(data.final_cached_context_string == nullptr) { + // We skip the shared tests that don't have a input context string + return; + } + km::core::path path = km::core::path::join(test_dir, "..", "ldml", "fixtures", "keyboards", "17.0", "k_001_tiny.kmx"); auto blob = km::tests::load_kmx_file(path.native().c_str()); ASSERT_STATUS_OK(km_core_keyboard_load_from_blob(path.stem().c_str(), blob.data(), blob.size(), &test_kb)); ASSERT_STATUS_OK(km_core_state_create(test_kb, test_empty_env_opts, &test_state)); - ASSERT_STATUS_OK(set_context_from_string(km_core_state_context(test_state), data.final_cached_context)); + ASSERT_STATUS_OK(set_context_from_string(km_core_state_context(test_state), data.final_cached_context_string)); ASSERT_STATUS_OK(set_context_from_string(km_core_state_app_context(test_state), data.initial_app_context)); test_actions = new km_core_actions; @@ -132,6 +85,11 @@ TEST_P(GetActionApiTest, TestActionsApi) { auto data = GetParam(); ASSERT_NO_FATAL_FAILURE(Initialize(data)); + if(data.final_cached_context_string == nullptr) { + // We skip the shared tests that don't have a input context string + return; + } + // setup(initial_app_context, final_cached_context, actions_code_points_to_delete, actions_output); auto actual_actions = km_core_state_get_actions(test_state); @@ -162,184 +120,6 @@ TEST_P(GetActionApiTest, TestActionsApi) { delete [] actual_final_app_context; } -const TestData values[] = { - // Null boundary tests +// Note that .final_cached_context_items is not used in these tests - { - "Noop", - /* app context pre transform: */ u"", - /* cached context post transform: */ u"", - /* action del, output: */ 0, U"", - // ---- results ---- - /* action del, output: */ 0, U"", - /* app_context: */ u"", - /* expected del */ U"" - }, - - { - "NoOutput", - /* app context pre transform: */ u"abc", - /* cached context post transform: */ u"abc", - /* action del, output: */ 0, U"", - // ---- results ---- - /* action del, output: */ 0, U"", - /* app_context: */ u"abc", - /* expected del */ U"" - }, - - { - "NoContext", - /* app context pre transform: */ u"", - /* cached context post transform: */ u"def", - /* action del, output: */ 0, U"def", - // ---- results ---- - /* action del, output: */ 0, U"def", - /* app_context: */ u"def", - /* expected del */ U"" - }, - - // Simple tests -- no deletions involved - - { - "NoNormalization", - /* app context pre transform: */ u"abc", - /* cached context post transform: */ u"abcdef", - /* action del, output: */ 0, U"def", - // ---- results ---- - /* action del, output: */ 0, U"def", - /* app_context: */ u"abcdef", - /* expected del */ U"" - }, - - { - "OutputToNfcBasic", - /* app context pre transform: */ u"abc", - /* cached context post transform: */ u"abcde\u0300f", - /* action del, output: */ 0, U"de\u0300f", - // ---- results ---- - /* action del, output: */ 0, U"dèf", - /* app_context: */ u"abcdèf", - /* expected del */ U"" - }, - - { - "OutputToNfcHefty", - /* app context pre transform: */ u"abc", - /* cached context post transform: */ u"abcA\u0300" u"e\u0316\u0301" u"\u0073\u0323\u0307" u"\u0041\u030a" u"\U000114B9\U000114B0", - /* action del, output: */ 0, U"A\u0300" U"e\u0316\u0301" U"\u0073\u0323\u0307" U"\u0041\u030a" U"\U000114B9\U000114B0", - // ---- results ---- - /* action del, output: */ 0, U"À" U"é̖" U"\u1e69" U"\u00c5" U"\U000114BC", - /* app_context: */ u"abcÀé̖\u1e69\u00c5\U000114BC", - /* expected del */ U"" - }, - - // Interaction with input context when not on normalization boundary - - { - "BacktrackOneCharacterToCombineAsNfc", - /* app context pre transform: */ u"XYZA", - /* cached context post transform: */ u"XYZA\u0300abc", - /* action del, output: */ 0, U"\u0300abc", - // ---- results ---- - /* action del, output: */ 1, U"Àabc", - /* app_context: */ u"XYZÀabc", - /* expected del */ U"A" - }, - - { - "BacktrackECombCirc2CharsToCombineAsNfc", - /* app context pre transform: */ u"abce\u0302", - /* cached context post transform: */ u"abce\u0323\u0302", - /* action del, output: */ 1, U"\u0323\u0302", - // ---- results ---- - /* action del, output: */ 2, U"ệ", - /* app_context: */ u"abcệ", - /* expected del */ U"e\u0302" - }, - - { - "OneBackspaceForNfdConvertsIntoOneCharInNfcAndRecombine", - /* app context pre transform: */ u"abcê", - /* cached context post transform: */ u"abce\u0323\u0302", - /* action del, output: */ 1, U"\u0323\u0302", // NFD input; delete 1: \u0302 - // ---- results ---- - /* action del, output: */ 1, U"ệ", // NFC output; delete 1: ê - /* app_context: */ u"abcệ", - /* expected del */ U"ê" - }, - - // a\u0300 should not be normalized because it is not otherwise impacted by - // the action. - { - "AvoidEditingTooFarBackInContextWhenFindingNormalizationBoundary", - /* app context pre transform: */ u"a\u0300bcê", - /* cached context post transform: */ u"a\u0300bce\u0323\u0302", - /* action del, output: */ 1, U"\u0323\u0302", // NFD input; delete 1: \u0302 - // ---- results ---- - /* action del, output: */ 1, U"ệ", // NFC output; delete 1: ê - /* app_context: */ u"a\u0300bcệ", - /* expected del */ U"ê" - }, - - // If we don't reach a normalization boundary, we still should continue to work - { - "NormalizableLettersAtStartOfContext", - /* app context pre transform: */ u"\u0300", - /* cached context post transform: */ u"\u0323\u0300\u0302", - /* action del, output: */ 1, U"\u0323\u0300\u0302", // NFD input; - // ---- results ---- - /* action del, output: */ 1, U"\u0323\u0300\u0302", // NFC output is still decomposed because there is no base - /* app_context: */ u"\u0323\u0300\u0302", - /* expected del */ U"\u0300" - }, - - // Modifies the base as well as diacritic - - { - "TwoBackspacesForNfdConvertsIntoOneCharInNfcAndRecombine", - /* app context pre transform: */ u"abcê", - /* cached context post transform: */ u"abca\u0323\u0302", - /* action del, output: */ 2, U"a\u0323\u0302", // NFD input; delete 2: e\u0302 - // ---- results ---- - /* action del, output: */ 1, U"ậ", // NFC output; delete 1: ê - /* app_context: */ u"abcậ", - /* expected del */ U"ê" - }, - - // surrogate pair tests - - { - "SurrogatePairInContext", - /* app context pre transform: */ u"abc\U0001F607ê", - /* cached context post transform: */ u"abc\U0001F607a\u0323\u0302", - /* action del, output: */ 2, U"a\u0323\u0302", - // ---- results ---- - /* action del, output: */ 1, U"ậ", - /* app_context: */ u"abc\U0001F607ậ", - /* expected del */ U"ê" - }, - - { - "SurrogatePairInOutput", - /* app context pre transform: */ u"abc", - /* cached context post transform: */ u"abc\U0001F607", - /* action del, output: */ 0, U"\U0001F607", - // ---- results ---- - /* action del, output: */ 0, U"\U0001F607", - /* app_context: */ u"abc\U0001F607", - /* expected del */ U"" - }, - - { - "SurrogatePairsInBothContextAndOutput", - /* app context pre transform: */ u"a\U0001F607bcê", - /* cached context post transform: */ u"a\U0001F607bca\U0001F60E", - /* action del, output: */ 2, U"a\U0001F60E", - // ---- results ---- - /* action del, output: */ 1, U"a\U0001F60E", - /* app_context: */ u"a\U0001F607bca\U0001F60E", - /* expected del */ U"ê" - } -}; - -INSTANTIATE_TEST_SUITE_P(KeymanCore, GetActionApiTest, testing::ValuesIn(values), GenerateTestName); +INSTANTIATE_TEST_SUITE_P(KeymanCore, GetActionApiTest, testing::ValuesIn(actionsTestData), GenerateTestName); diff --git a/core/tests/unit/api/actions_normalize.tests.cpp b/core/tests/unit/api/actions_normalize.tests.cpp index 22d6644e61..13aba519d7 100644 --- a/core/tests/unit/api/actions_normalize.tests.cpp +++ b/core/tests/unit/api/actions_normalize.tests.cpp @@ -15,68 +15,15 @@ #include "context.hpp" #include "../helpers/core_test_helpers.h" +#include "./actions_test_data.h" -struct TestData { - const char* test_name; - - /** - * the app context stored in the state, _before_ transform is applied -- NFU - */ - const km_core_cu *initial_app_context; - - /** - * cached context _after_ actions have been applied -- guaranteed NFD - * (essentially, this is initial_cached_context - - * actions_code_points_to_delete + actions_output) - no markers supported - */ - const km_core_cu *final_cached_context_string; - - /** - * cached context _after_ actions have been applied -- guaranteed NFD - * (essentially, this is initial_cached_context - - * actions_code_points_to_delete + actions_output) - markers supported - */ - const km_core_context_item *final_cached_context_items; - - /** - * number of NFD code points that the keyboard processor has asked to remove in its actions - */ - int actions_code_points_to_delete; - - /** - * NFD string that the keyboard processor has asked to insert in its actions - */ - const std::u32string actions_output; - - /** - * expected: NFU code points to ask app to remove - */ - const unsigned int expected_delete; - - /** - * expected: adjusted NFC output to insert into the app - */ - const std::u32string expected_output; - - /** - * expected: NFU adjusted final app context, which will be NFC from the - * boundary of the transform, but will not have been modified prior to that. - * Should match char-for-char what the app ends up with in its text buffer. - */ - const km_core_cu *expected_final_app_context; -}; - -std::string GenerateTestName(const testing::TestParamInfo& info) { - return info.param.test_name; -} - -class ActionsNormalizeApiTest : public testing::TestWithParam { +class ActionsNormalizeApiTest : public testing::TestWithParam { protected: km_core_keyboard * test_kb = nullptr; km_core_state * test_state = nullptr; km_core_actions test_actions = {0}; - void Initialize(TestData const& data) { + void Initialize(ActionsTestData const& data) { km::core::path path = km::core::path::join(test_dir, "..", "ldml", "fixtures", "keyboards", "17.0", "k_001_tiny.kmx"); auto blob = km::tests::load_kmx_file(path.native().c_str()); ASSERT_STATUS_OK(km_core_keyboard_load_from_blob(path.stem().c_str(), blob.data(), blob.size(), &test_kb)); @@ -143,282 +90,4 @@ TEST_P(ActionsNormalizeApiTest, TestActionsNormalize) { ASSERT_NO_FATAL_FAILURE(km::tests::compare_context(km_core_state_app_context(test_state), data.expected_final_app_context)); } -const km_core_context_item items_1[] = { //u"a\U0001F607b\uFFFF\u0008\u0001ca\U0001F60E", - { KM_CORE_CT_CHAR, {0,}, { 0x0061 } }, - { KM_CORE_CT_CHAR, {0,}, { 0x1F607 } }, - { KM_CORE_CT_CHAR, {0,}, { 0x0062 } }, - { KM_CORE_CT_MARKER, {0,}, { 0x1 } }, - { KM_CORE_CT_CHAR, {0,}, { 0x0063 } }, - { KM_CORE_CT_CHAR, {0,}, { 0x0061 } }, - { KM_CORE_CT_CHAR, {0,}, { 0x1F60E } }, - KM_CORE_CONTEXT_ITEM_END -}; - -const km_core_context_item items_2[] = { //u"a\U0001F607bca\U0001F60E\uFFFF\u0008\u0001", - { KM_CORE_CT_CHAR, {0,}, { 0x0061 } }, - { KM_CORE_CT_CHAR, {0,}, { 0x1F607 } }, - { KM_CORE_CT_CHAR, {0,}, { 0x0062 } }, - { KM_CORE_CT_CHAR, {0,}, { 0x0063 } }, - { KM_CORE_CT_CHAR, {0,}, { 0x0061 } }, - { KM_CORE_CT_CHAR, {0,}, { 0x1F60E } }, - { KM_CORE_CT_MARKER, {0,}, { 0x1 } }, - KM_CORE_CONTEXT_ITEM_END -}; - -const km_core_context_item items_11067[] = { - { KM_CORE_CT_CHAR, {0,}, { U'𐒻' } }, - { KM_CORE_CT_CHAR, {0,}, { U'𐒷' } }, - KM_CORE_CONTEXT_ITEM_END -}; - -const TestData values[] = { - // Null boundary tests - { - "Noop", - /* app context pre transform: */ u"", - /* cached context post transform: */ u"", - /* cached context post transform: */ nullptr, - /* action del, output: */ 0, U"", - // ---- results ---- - /* action del, output: */ 0, U"", - /* app_context: */ u"" - }, - - { - "NoOutput", - /* app context pre transform: */ u"abc", - /* cached context post transform: */ u"abc", - /* cached context post transform: */ nullptr, - /* action del, output: */ 0, U"", - // ---- results ---- - /* action del, output: */ 0, U"", - /* app_context: */ u"abc" - }, - - { - "NoContext", - /* app context pre transform: */ u"", - /* cached context post transform: */ u"def", - /* cached context post transform: */ nullptr, - /* action del, output: */ 0, U"def", - // ---- results ---- - /* action del, output: */ 0, U"def", - /* app_context: */ u"def" - }, - - // Simple tests -- no deletions involved - - { - "NoNormalization", - /* app context pre transform: */ u"abc", - /* cached context post transform: */ u"abcdef", - /* cached context post transform: */ nullptr, - /* action del, output: */ 0, U"def", - // ---- results ---- - /* action del, output: */ 0, U"def", - /* app_context: */ u"abcdef" - }, - - { - "OutputToNfcBasic", - /* app context pre transform: */ u"abc", - /* cached context post transform: */ u"abcde\u0300f", - /* cached context post transform: */ nullptr, - /* action del, output: */ 0, U"de\u0300f", - // ---- results ---- - /* action del, output: */ 0, U"dèf", - /* app_context: */ u"abcdèf" - }, - - { - "OutputToNfcHefty", - /* app context pre transform: */ u"abc", - /* cached context post transform: */ u"abcA\u0300" u"e\u0316\u0301" u"\u0073\u0323\u0307" u"\u0041\u030a" u"\U000114B9\U000114B0", - /* cached context post transform: */ nullptr, - /* action del, output: */ 0, U"A\u0300" U"e\u0316\u0301" U"\u0073\u0323\u0307" U"\u0041\u030a" U"\U000114B9\U000114B0", - // ---- results ---- - /* action del, output: */ 0, U"À" U"é̖" U"\u1e69" U"\u00c5" U"\U000114BC", - /* app_context: */ u"abcÀé̖\u1e69\u00c5\U000114BC" - }, - - // Interaction with input context when not on normalization boundary - - { - "BacktrackOneCharacterToCombineAsNfc", - /* app context pre transform: */ u"XYZA", - /* cached context post transform: */ u"XYZA\u0300abc", - /* cached context post transform: */ nullptr, - /* action del, output: */ 0, U"\u0300abc", - // ---- results ---- - /* action del, output: */ 1, U"Àabc", - /* app_context: */ u"XYZÀabc" - }, - - { - "BacktrackEcombCirc2CharsToCombineAsNfc", - /* app context pre transform: */ u"abce\u0302", - /* cached context post transform: */ u"abce\u0323\u0302", - /* cached context post transform: */ nullptr, - /* action del, output: */ 1, U"\u0323\u0302", - // ---- results ---- - /* action del, output: */ 2, U"ệ", - /* app_context: */ u"abcệ" - }, - - { - "OneBackspaceToDeleteLastNfdCharacter15487", - /* app context pre transform: */ u"abcê", // NFC - /* cached context post transform: */ u"abce", - /* cached context post transform: */ nullptr, - /* action del, output: */ 1, U"", // NFD input; delete 1: \u0302 - // ---- results ---- - /* action del, output: */ 1, U"e", // NFC output; delete 1: e - /* app_context: */ u"abce" - }, - - { - "OneBackspaceToDeleteLastNfdCharacterWithNfcAppContext15487", - /* app context pre transform: */ u"abce\u0302", // NFD - /* cached context post transform: */ u"abce", - /* cached context post transform: */ nullptr, - /* action del, output: */ 1, U"", // NFD input; delete 1: \u0302 - // ---- results ---- - /* action del, output: */ 1, U"", // NFC output; delete 1: e - /* app_context: */ u"abce" - }, - - { - "OneBackspaceForNfdConvertsIntoOneCharInNfcAndRecombine", - /* app context pre transform: */ u"abcê", - /* cached context post transform: */ u"abce\u0323\u0302", - /* cached context post transform: */ nullptr, - /* action del, output: */ 1, U"\u0323\u0302", // NFD input; delete 1: \u0302 - // ---- results ---- - /* action del, output: */ 1, U"ệ", // NFC output; delete 1: ê - /* app_context: */ u"abcệ" - }, - - // a\u0300 should not be normalized because it is not otherwise impacted by - // the action. - { - "AvoidEditingTooFarBackInContextWhenFindingNormalizationBoundary", - /* app context pre transform: */ u"a\u0300bcê", - /* cached context post transform: */ u"a\u0300bce\u0323\u0302", - /* cached context post transform: */ nullptr, - /* action del, output: */ 1, U"\u0323\u0302", // NFD input; delete 1: \u0302 - // ---- results ---- - /* action del, output: */ 1, U"ệ", // NFC output; delete 1: ê - /* app_context: */ u"a\u0300bcệ" - }, - - // If we don't reach a normalization boundary, we still should continue to work - { - "NormalizableLettersAtStartOfContext", - /* app context pre transform: */ u"\u0300", - /* cached context post transform: */ u"\u0323\u0300\u0302", - /* cached context post transform: */ nullptr, - /* action del, output: */ 1, U"\u0323\u0300\u0302", // NFD input; - // ---- results ---- - /* action del, output: */ 1, U"\u0323\u0300\u0302", // NFC output is still decomposed because there is no base - /* app_context: */ u"\u0323\u0300\u0302" - }, - - // #15505 - normalization of Bengali characters - { - "BengaliNormalizationOfU09C7U09D7U09CC", - /* app context pre transform: */ u"\u0995\u09C7", - /* cached context post transform: */ u"\u0995\u09C7\u09D7", - /* cached context post transform: */ nullptr, - /* action del, output: */ 0, U"\u09D7", - // ---- results ---- - /* action del, output: */ 1, U"\u09CC", - /* app_context: */ u"\u0995\u09CC" - }, - - // Modifies the base as well as diacritic - - { - "TwoBackspacesForNfdConvertsIntoOneCharInNfcAndRecombine", - /* app context pre transform: */ u"abcê", - /* cached context post transform: */ u"abca\u0323\u0302", - /* cached context post transform: */ nullptr, - /* action del, output: */ 2, U"a\u0323\u0302", // NFD input; delete 2: e\u0302 - // ---- results ---- - /* action del, output: */ 1, U"ậ", // NFC output; delete 1: ê - /* app_context: */ u"abcậ" - }, - - // surrogate pair tests - - { - "SurrogatePairInContext", - /* app context pre transform: */ u"abc\U0001F607ê", - /* cached context post transform: */ u"abc\U0001F607a\u0323\u0302", - /* cached context post transform: */ nullptr, - /* action del, output: */ 2, U"a\u0323\u0302", - // ---- results ---- - /* action del, output: */ 1, U"ậ", - /* app_context: */ u"abc\U0001F607ậ" - }, - - { - "SurrogatePairInOutput", - /* app context pre transform: */ u"abc", - /* cached context post transform: */ u"abc\U0001F607", - /* cached context post transform: */ nullptr, - /* action del, output: */ 0, U"\U0001F607", - // ---- results ---- - /* action del, output: */ 0, U"\U0001F607", - /* app_context: */ u"abc\U0001F607" - }, - - { - "SurrogatePairsInBothContextAndOutput", - /* app context pre transform: */ u"a\U0001F607bcê", - /* cached context post transform: */ u"a\U0001F607bca\U0001F60E", - /* cached context post transform: */ nullptr, - /* action del, output: */ 2, U"a\U0001F60E", - // ---- results ---- - /* action del, output: */ 1, U"a\U0001F60E", - /* app_context: */ u"a\U0001F607bca\U0001F60E" - }, - - // Marker tests - - { - "AMarkerInTheCachedContextShouldNotShowUpInAppContext", - /* app context pre transform: */ u"a\U0001F607bcê", - /* cached context post transform: */ nullptr, - /* cached context post transform: */ &items_1[0], - - /* action del, output: */ 2, U"a\U0001F60E", - // ---- results ---- - /* action del, output: */ 1, U"a\U0001F60E", - /* app_context: */ u"a\U0001F607bca\U0001F60E" - }, - - { - "AMarkerInTheModifiedSectionOfCachedContextShouldNotShowUpInAppContext", - /* app context pre transform: */ u"a\U0001F607bcê", - /* cached context post transform: */ nullptr, - /* cached context post transform: */ &items_2[0], - /* action del, output: */ 2, U"a\U0001F60E", - // ---- results ---- - /* action del, output: */ 1, U"a\U0001F60E", - /* app_context: */ u"a\U0001F607bca\U0001F60E" - }, - - // regression #11067 - { - "ANonBmpCharInContext11067", - /* app context pre transform: */ u"𐒻", - /* cached context post transform: */ u"𐒻𐒷", - /* cached context post transform: */ &items_11067[0], - /* action del, output: */ 0, U"𐒻𐒷", - // ---- results ---- - /* action del, output: */ 1, U"𐒻𐒷", - /* app_context: */ u"𐒻𐒷" - } -}; - -INSTANTIATE_TEST_SUITE_P(KeymanCore, ActionsNormalizeApiTest, testing::ValuesIn(values), GenerateTestName); +INSTANTIATE_TEST_SUITE_P(KeymanCore, ActionsNormalizeApiTest, testing::ValuesIn(actionsTestData), GenerateTestName); diff --git a/core/tests/unit/api/actions_test_data.cpp b/core/tests/unit/api/actions_test_data.cpp new file mode 100644 index 0000000000..2566fe02ce --- /dev/null +++ b/core/tests/unit/api/actions_test_data.cpp @@ -0,0 +1,306 @@ + +#include "./actions_test_data.h" + +const km_core_context_item items_1[] = { //u"a\U0001F607b\uFFFF\u0008\u0001ca\U0001F60E", + { KM_CORE_CT_CHAR, {0,}, { 0x0061 } }, + { KM_CORE_CT_CHAR, {0,}, { 0x1F607 } }, + { KM_CORE_CT_CHAR, {0,}, { 0x0062 } }, + { KM_CORE_CT_MARKER, {0,}, { 0x1 } }, + { KM_CORE_CT_CHAR, {0,}, { 0x0063 } }, + { KM_CORE_CT_CHAR, {0,}, { 0x0061 } }, + { KM_CORE_CT_CHAR, {0,}, { 0x1F60E } }, + KM_CORE_CONTEXT_ITEM_END +}; + +const km_core_context_item items_2[] = { //u"a\U0001F607bca\U0001F60E\uFFFF\u0008\u0001", + { KM_CORE_CT_CHAR, {0,}, { 0x0061 } }, + { KM_CORE_CT_CHAR, {0,}, { 0x1F607 } }, + { KM_CORE_CT_CHAR, {0,}, { 0x0062 } }, + { KM_CORE_CT_CHAR, {0,}, { 0x0063 } }, + { KM_CORE_CT_CHAR, {0,}, { 0x0061 } }, + { KM_CORE_CT_CHAR, {0,}, { 0x1F60E } }, + { KM_CORE_CT_MARKER, {0,}, { 0x1 } }, + KM_CORE_CONTEXT_ITEM_END +}; + +const km_core_context_item items_11067[] = { + { KM_CORE_CT_CHAR, {0,}, { U'𐒻' } }, + { KM_CORE_CT_CHAR, {0,}, { U'𐒷' } }, + KM_CORE_CONTEXT_ITEM_END +}; + +const std::vector actionsTestData = { + // Null boundary tests + + { + "Noop", + /* app context pre transform: */ u"", + /* cached context post transform: */ u"", + /* cached context post transform: */ nullptr, + /* action del, output: */ 0, U"", + // ---- results ---- + /* expected action del, output: */ 0, U"", + /* expected app_context: */ u"", + /* expected del */ U"" + }, + + { + "NoOutput", + /* app context pre transform: */ u"abc", + /* cached context post transform: */ u"abc", + /* cached context post transform: */ nullptr, + /* action del, output: */ 0, U"", + // ---- results ---- + /* expected action del, output: */ 0, U"", + /* expected app_context: */ u"abc", + /* expected del */ U"" + }, + + { + "NoContext", + /* app context pre transform: */ u"", + /* cached context post transform: */ u"def", + /* cached context post transform: */ nullptr, + /* action del, output: */ 0, U"def", + // ---- results ---- + /* expected action del, output: */ 0, U"def", + /* expected app_context: */ u"def", + /* expected del */ U"" + }, + + // Simple tests -- no deletions involved + + { + "NoNormalization", + /* app context pre transform: */ u"abc", + /* cached context post transform: */ u"abcdef", + /* cached context post transform: */ nullptr, + /* action del, output: */ 0, U"def", + // ---- results ---- + /* expected action del, output: */ 0, U"def", + /* expected app_context: */ u"abcdef", + /* expected del */ U"" + }, + + { + "OutputToNfcBasic", + /* app context pre transform: */ u"abc", + /* cached context post transform: */ u"abcde\u0300f", + /* cached context post transform: */ nullptr, + /* action del, output: */ 0, U"de\u0300f", + // ---- results ---- + /* expected action del, output: */ 0, U"dèf", + /* expected app_context: */ u"abcdèf", + /* expected del */ U"" + }, + + { + "OutputToNfcHefty", + /* app context pre transform: */ u"abc", + /* cached context post transform: */ u"abcA\u0300" u"e\u0316\u0301" u"\u0073\u0323\u0307" u"\u0041\u030a" u"\U000114B9\U000114B0", + /* cached context post transform: */ nullptr, + /* action del, output: */ 0, U"A\u0300" U"e\u0316\u0301" U"\u0073\u0323\u0307" U"\u0041\u030a" U"\U000114B9\U000114B0", + // ---- results ---- + /* expected action del, output: */ 0, U"À" U"é̖" U"\u1e69" U"\u00c5" U"\U000114BC", + /* expected app_context: */ u"abcÀé̖\u1e69\u00c5\U000114BC", + /* expected del */ U"" + }, + + // Interaction with input context when not on normalization boundary + + { + "BacktrackOneCharacterToCombineAsNfc", + /* app context pre transform: */ u"XYZA", + /* cached context post transform: */ u"XYZA\u0300abc", + /* cached context post transform: */ nullptr, + /* action del, output: */ 0, U"\u0300abc", + // ---- results ---- + /* expected action del, output: */ 1, U"Àabc", + /* expected app_context: */ u"XYZÀabc", + /* expected del */ U"A" + }, + + { + "BacktrackECombCirc2CharsToCombineAsNfc", + /* app context pre transform: */ u"abce\u0302", + /* cached context post transform: */ u"abce\u0323\u0302", + /* cached context post transform: */ nullptr, + /* action del, output: */ 1, U"\u0323\u0302", + // ---- results ---- + /* expected action del, output: */ 2, U"ệ", + /* expected app_context: */ u"abcệ", + /* expected del */ U"e\u0302" + }, + + { + "OneBackspaceToDeleteLastNfdCharacter15487", + /* app context pre transform: */ u"abcê", // NFC + /* cached context post transform: */ u"abce", + /* cached context post transform: */ nullptr, + /* action del, output: */ 1, U"", // NFD input; delete 1: \u0302 + // ---- results ---- + /* expected action del, output: */ 1, U"e", // NFC output; delete 1: e + /* expected app_context: */ u"abce", + /* expected del */ U"ê" + }, + + { + "OneBackspaceToDeleteLastNfdCharacterWithNfdAppContext15487", + /* app context pre transform: */ u"abce\u0302", // NFD + /* cached context post transform: */ u"abce", + /* cached context post transform: */ nullptr, + /* action del, output: */ 1, U"", // NFD input; delete 1: \u0302 + // ---- results ---- + /* expected action del, output: */ 1, U"", // NFC output; delete 1: e + /* expected app_context: */ u"abce", + /* expected del */ U"\u0302" + }, + + { + "OneBackspaceForNfdConvertsIntoOneCharInNfcAndRecombine", + /* app context pre transform: */ u"abcê", + /* cached context post transform: */ u"abce\u0323\u0302", + /* cached context post transform: */ nullptr, + /* action del, output: */ 1, U"\u0323\u0302", // NFD input; delete 1: \u0302 + // ---- results ---- + /* expected action del, output: */ 1, U"ệ", // NFC output; delete 1: ê + /* expected app_context: */ u"abcệ", + /* expected del */ U"ê" + }, + + // a\u0300 should not be normalized because it is not otherwise impacted by + // the action. + { + "AvoidEditingTooFarBackInContextWhenFindingNormalizationBoundary", + /* app context pre transform: */ u"a\u0300bcê", + /* cached context post transform: */ u"a\u0300bce\u0323\u0302", + /* cached context post transform: */ nullptr, + /* action del, output: */ 1, U"\u0323\u0302", // NFD input; delete 1: \u0302 + // ---- results ---- + /* expected action del, output: */ 1, U"ệ", // NFC output; delete 1: ê + /* expected app_context: */ u"a\u0300bcệ", + /* expected del */ U"ê" + }, + + // If we don't reach a normalization boundary, we still should continue to work + { + "NormalizableLettersAtStartOfContext", + /* app context pre transform: */ u"\u0300", + /* cached context post transform: */ u"\u0323\u0300\u0302", + /* cached context post transform: */ nullptr, + /* action del, output: */ 1, U"\u0323\u0300\u0302", // NFD input; + // ---- results ---- + /* expected action del, output: */ 1, U"\u0323\u0300\u0302", // NFC output is still decomposed because there is no base + /* expected app_context: */ u"\u0323\u0300\u0302", + /* expected del */ U"\u0300" + }, + + // #15505 - normalization of Bengali characters + { + "BengaliNormalizationOfU09C7U09D7U09CC", + /* app context pre transform: */ u"\u0995\u09C7", + /* cached context post transform: */ u"\u0995\u09C7\u09D7", + /* cached context post transform: */ nullptr, + /* action del, output: */ 0, U"\u09D7", + // ---- results ---- + /* expected action del, output: */ 1, U"\u09CC", + /* expected app_context: */ u"\u0995\u09CC", + /* expected del */ U"\u09C7" + }, + + // Modifies the base as well as diacritic + + { + "TwoBackspacesForNfdConvertsIntoOneCharInNfcAndRecombine", + /* app context pre transform: */ u"abcê", + /* cached context post transform: */ u"abca\u0323\u0302", + /* cached context post transform: */ nullptr, + /* action del, output: */ 2, U"a\u0323\u0302", // NFD input; delete 2: e\u0302 + // ---- results ---- + /* expected action del, output: */ 1, U"ậ", // NFC output; delete 1: ê + /* expected app_context: */ u"abcậ", + /* expected del */ U"ê" + }, + + // surrogate pair tests + + { + "SurrogatePairInContext", + /* app context pre transform: */ u"abc\U0001F607ê", + /* cached context post transform: */ u"abc\U0001F607a\u0323\u0302", + /* cached context post transform: */ nullptr, + /* action del, output: */ 2, U"a\u0323\u0302", + // ---- results ---- + /* expected action del, output: */ 1, U"ậ", + /* expected app_context: */ u"abc\U0001F607ậ", + /* expected del */ U"ê" + }, + + { + "SurrogatePairInOutput", + /* app context pre transform: */ u"abc", + /* cached context post transform: */ u"abc\U0001F607", + /* cached context post transform: */ nullptr, + /* action del, output: */ 0, U"\U0001F607", + // ---- results ---- + /* expected action del, output: */ 0, U"\U0001F607", + /* expected app_context: */ u"abc\U0001F607", + /* expected del */ U"" + }, + + { + "SurrogatePairsInBothContextAndOutput", + /* app context pre transform: */ u"a\U0001F607bcê", + /* cached context post transform: */ u"a\U0001F607bca\U0001F60E", + /* cached context post transform: */ nullptr, + /* action del, output: */ 2, U"a\U0001F60E", + // ---- results ---- + /* expected action del, output: */ 1, U"a\U0001F60E", + /* expected app_context: */ u"a\U0001F607bca\U0001F60E", + /* expected del */ U"ê" + }, + + // Marker tests + + { + "AMarkerInTheCachedContextShouldNotShowUpInAppContext", + /* app context pre transform: */ u"a\U0001F607bcê", + /* cached context post transform: */ nullptr, + /* cached context post transform: */ &items_1[0], + + /* action del, output: */ 2, U"a\U0001F60E", + // ---- results ---- + /* expected action del, output: */ 1, U"a\U0001F60E", + /* expected app_context: */ u"a\U0001F607bca\U0001F60E", + /* expected del */ U"ê" + }, + + { + "AMarkerInTheModifiedSectionOfCachedContextShouldNotShowUpInAppContext", + /* app context pre transform: */ u"a\U0001F607bcê", + /* cached context post transform: */ nullptr, + /* cached context post transform: */ &items_2[0], + /* action del, output: */ 2, U"a\U0001F60E", + // ---- results ---- + /* expected action del, output: */ 1, U"a\U0001F60E", + /* expected app_context: */ u"a\U0001F607bca\U0001F60E", + /* expected del */ U"ê" + }, + + // regression #11067 + { + "ANonBmpCharInContext11067", + /* app context pre transform: */ u"𐒻", + /* cached context post transform: */ u"𐒻𐒷", + /* cached context post transform: */ &items_11067[0], + /* action del, output: */ 0, U"𐒻𐒷", + // ---- results ---- + /* expected action del, output: */ 1, U"𐒻𐒷", + /* expected app_context: */ u"𐒻𐒷", + /* expected del: */ U"\x104BB" + } +}; + +std::string GenerateTestName(const testing::TestParamInfo& info) { + return info.param.test_name; +} diff --git a/core/tests/unit/api/actions_test_data.h b/core/tests/unit/api/actions_test_data.h new file mode 100644 index 0000000000..92b8db5108 --- /dev/null +++ b/core/tests/unit/api/actions_test_data.h @@ -0,0 +1,64 @@ +#pragma once +#include +#include +#include +#include "keyman_core.h" + +struct ActionsTestData { + const char* test_name; + + /** + * the app context stored in the state, _before_ transform is applied -- NFU + */ + const km_core_cu *initial_app_context; + + /** + * cached context _after_ actions have been applied -- guaranteed NFD + * (essentially, this is initial_cached_context - + * actions_code_points_to_delete + actions_output) - no markers supported + */ + const km_core_cu *final_cached_context_string; + + /** + * cached context _after_ actions have been applied -- guaranteed NFD + * (essentially, this is initial_cached_context - + * actions_code_points_to_delete + actions_output) - markers supported + */ + const km_core_context_item *final_cached_context_items; + + /** + * number of NFD code points that the keyboard processor has asked to remove in its actions + */ + int actions_code_points_to_delete; + + /** + * NFD string that the keyboard processor has asked to insert in its actions + */ + const std::u32string actions_output; + + /** + * expected: NFU code points to ask app to remove + */ + const unsigned int expected_delete; + + /** + * expected: adjusted NFC output to insert into the app + */ + const std::u32string expected_output; + + /** + * expected: NFU adjusted final app context, which will be NFC from the + * boundary of the transform, but will not have been modified prior to that. + * Should match char-for-char what the app ends up with in its text buffer. + */ + const km_core_cu *expected_final_app_context; + + /** + * expected: the characters deleted from the context + */ + const std::u32string expected_deleted_context; +}; + +extern const std::vector actionsTestData; + +std::string GenerateTestName(const testing::TestParamInfo& info); diff --git a/core/tests/unit/api/actions_update_app_context_nfu.tests.cpp b/core/tests/unit/api/actions_update_app_context_nfu.tests.cpp index 36330daea7..bf98601c34 100644 --- a/core/tests/unit/api/actions_update_app_context_nfu.tests.cpp +++ b/core/tests/unit/api/actions_update_app_context_nfu.tests.cpp @@ -15,72 +15,15 @@ #include "context.hpp" #include "../helpers/core_test_helpers.h" +#include "./actions_test_data.h" -// TODO-WEB-CORE: merge with actions_normalize.tests.cpp? These are identical; -// note that actions_get_api and actions_set_api data are subtly different, -// opportunity to merge those too? - -struct TestData { - const char* test_name; - - /** - * the app context stored in the state, _before_ transform is applied -- NFU - */ - const km_core_cu *initial_app_context; - - /** - * cached context _after_ actions have been applied -- guaranteed NFD - * (essentially, this is initial_cached_context - - * actions_code_points_to_delete + actions_output) - no markers supported - */ - const km_core_cu *final_cached_context_string; - - /** - * cached context _after_ actions have been applied -- guaranteed NFD - * (essentially, this is initial_cached_context - - * actions_code_points_to_delete + actions_output) - markers supported - */ - const km_core_context_item *final_cached_context_items; - - /** - * number of NFD code points that the keyboard processor has asked to remove in its actions - */ - int actions_code_points_to_delete; - - /** - * NFD string that the keyboard processor has asked to insert in its actions - */ - const std::u32string actions_output; - - /** - * expected: NFU code points to ask app to remove - */ - const unsigned int expected_delete; - - /** - * expected: adjusted NFC output to insert into the app - */ - const std::u32string expected_output; - - /** - * expected: NFU adjusted final app context, which will be NFC from the - * boundary of the transform, but will not have been modified prior to that. - * Should match char-for-char what the app ends up with in its text buffer. - */ - const km_core_cu *expected_final_app_context; -}; - -std::string GenerateTestName(const testing::TestParamInfo& info) { - return info.param.test_name; -} - -class ActionsUpdateAppContextNfuApiTest : public testing::TestWithParam { +class ActionsUpdateAppContextNfuApiTest : public testing::TestWithParam { protected: km_core_keyboard * test_kb = nullptr; km_core_state * test_state = nullptr; km_core_actions test_actions = {0}; - void Initialize(TestData const& data) { + void Initialize(ActionsTestData const& data) { km::core::path path = km::core::path::join(test_dir, "..", "ldml", "fixtures", "keyboards", "17.0", "k_001_tiny.kmx"); auto blob = km::tests::load_kmx_file(path.native().c_str()); ASSERT_STATUS_OK(km_core_keyboard_load_from_blob(path.stem().c_str(), blob.data(), blob.size(), &test_kb)); @@ -172,7 +115,23 @@ const km_core_context_item items_2[] = { //u"a\U0001F607bca\U0001F60E\uFFFF\u000 KM_CORE_CONTEXT_ITEM_END }; -const TestData values[] = { +const km_core_context_item items_11067[] = { + { KM_CORE_CT_CHAR, {0,}, { U'𐒻' } }, + { KM_CORE_CT_CHAR, {0,}, { U'𐒷' } }, + KM_CORE_CONTEXT_ITEM_END +}; + +/** + * Test cases for non-normalized app context update. These look superficially + * similar to the data in `actionsTestData`, but diverge when taking into + * account normalization of output. Several normalization-related tests in + * `actionsTestData` are therefore also currently excluded here. + * + * @todo: (low priority) `actionsTestData` and `actionsTestDataNFU` could be + * combined with additional entries for each NFU result. + */ + +const std::vector actionsTestDataNFU = { // Null boundary tests { @@ -182,8 +141,9 @@ const TestData values[] = { /* cached context post transform: */ nullptr, /* action del, output: */ 0, U"", // ---- results ---- - /* action del, output: */ 0, U"", - /* app_context: */ u"" + /* expected action del, output: */ 0, U"", + /* expected app_context: */ u"", + /* expected del */ U"" }, { @@ -193,8 +153,9 @@ const TestData values[] = { /* cached context post transform: */ nullptr, /* action del, output: */ 0, U"", // ---- results ---- - /* action del, output: */ 0, U"", - /* app_context: */ u"abc" + /* expected action del, output: */ 0, U"", + /* expected app_context: */ u"abc", + /* expected del */ U"" }, { @@ -204,10 +165,26 @@ const TestData values[] = { /* cached context post transform: */ nullptr, /* action del, output: */ 0, U"def", // ---- results ---- - /* action del, output: */ 0, U"def", - /* app_context: */ u"def" + /* expected action del, output: */ 0, U"def", + /* expected app_context: */ u"def", + /* expected del */ U"" }, + // Simple tests -- no deletions involved + + { + "NoNormalization", + /* app context pre transform: */ u"abc", + /* cached context post transform: */ u"abcdef", + /* cached context post transform: */ nullptr, + /* action del, output: */ 0, U"def", + // ---- results ---- + /* expected action del, output: */ 0, U"def", + /* expected app_context: */ u"abcdef", + /* expected del */ U"" + }, + + // surrogate pair tests { @@ -217,8 +194,9 @@ const TestData values[] = { /* cached context post transform: */ nullptr, /* action del, output: */ 1, U"a\u0323\u0302", // ---- results ---- - /* action del, output: */ 1, U"a\u0323\u0302", - /* app_context: */ u"abc\U0001F607a\u0323\u0302" + /* expected action del, output: */ 1, U"a\u0323\u0302", + /* expected app_context: */ u"abc\U0001F607a\u0323\u0302", + /* expected del */ U"ê" }, { @@ -228,8 +206,9 @@ const TestData values[] = { /* cached context post transform: */ nullptr, /* action del, output: */ 0, U"\U0001F607", // ---- results ---- - /* action del, output: */ 0, U"\U0001F607", - /* app_context: */ u"abc\U0001F607" + /* expected action del, output: */ 0, U"\U0001F607", + /* expected app_context: */ u"abc\U0001F607", + /* expected del */ U"" }, { @@ -239,8 +218,9 @@ const TestData values[] = { /* cached context post transform: */ nullptr, /* action del, output: */ 1, U"a\U0001F60E", // ---- results ---- - /* action del, output: */ 1, U"a\U0001F60E", - /* app_context: */ u"a\U0001F607bca\U0001F60E" + /* expected action del, output: */ 1, U"a\U0001F60E", + /* expected app_context: */ u"a\U0001F607bca\U0001F60E", + /* expected del */ U"ê" }, // Marker tests @@ -252,8 +232,9 @@ const TestData values[] = { /* cached context post transform: */ &items_1[0], /* action del, output: */ 1, U"a\U0001F60E", // ---- results ---- - /* action del, output: */ 1, U"a\U0001F60E", - /* app_context: */ u"a\U0001F607bca\U0001F60E" + /* expected action del, output: */ 1, U"a\U0001F60E", + /* expected app_context: */ u"a\U0001F607bca\U0001F60E", + /* expected del */ U"ê" }, { @@ -263,9 +244,23 @@ const TestData values[] = { /* cached context post transform: */ &items_2[0], /* action del, output: */ 1, U"a\U0001F60E", // ---- results ---- - /* action del, output: */ 1, U"a\U0001F60E", - /* app_context: */ u"a\U0001F607bca\U0001F60E" + /* expected action del, output: */ 1, U"a\U0001F60E", + /* expected app_context: */ u"a\U0001F607bca\U0001F60E", + /* expected del */ U"ê" + }, + + // regression #11067 + { + "ANonBmpCharInContext11067", + /* app context pre transform: */ u"𐒻", + /* cached context post transform: */ u"𐒻𐒷", + /* cached context post transform: */ &items_11067[0], + /* action del, output: */ 1, U"𐒻𐒷", + // ---- results ---- + /* expected action del, output: */ 1, U"𐒻𐒷", + /* expected app_context: */ u"𐒻𐒷", + /* expected del: */ U"\x104BB" } }; -INSTANTIATE_TEST_SUITE_P(KeymanCore, ActionsUpdateAppContextNfuApiTest, testing::ValuesIn(values), GenerateTestName); +INSTANTIATE_TEST_SUITE_P(KeymanCore, ActionsUpdateAppContextNfuApiTest, testing::ValuesIn(actionsTestDataNFU), GenerateTestName); diff --git a/core/tests/unit/api/meson.build b/core/tests/unit/api/meson.build index 97e04b5002..04d2802d6e 100644 --- a/core/tests/unit/api/meson.build +++ b/core/tests/unit/api/meson.build @@ -40,15 +40,15 @@ tests = [ ['debug-api-tests', 'debug_api.tests.cpp'], ['kmx_xstring-tests', 'kmx_xstring.tests.cpp'], ['kmx_context-tests', 'kmx_context.tests.cpp'], - ['actions_normalize-tests', 'actions_normalize.tests.cpp'], + ['actions_normalize-tests', ['actions_normalize.tests.cpp', 'actions_test_data.cpp']], # renamed to avoid heuristic that causes "update" in a filename to require # elevation on Windows! this could also be avoided by adding a manifest # resource to each executable (e.g. with mt.exe) # https://learn.microsoft.com/en-us/windows/security/application-security/application-control/user-account-control/architecture#installer-detection-technology - ['actions_updatx_app_context_nfu-tests', 'actions_update_app_context_nfu.tests.cpp'], + ['actions_updatx_app_context_nfu-tests', ['actions_update_app_context_nfu.tests.cpp', 'actions_test_data.cpp']], - ['actions_get_api-tests', 'actions_get_api.tests.cpp'], + ['actions_get_api-tests', ['actions_get_api.tests.cpp', 'actions_test_data.cpp']], ['km_core_keyboard_api-tests', 'km_core_keyboard_api.tests.cpp'], ['km_core_process_event-tests', 'km_core_process_event.tests.cpp'], ['key_list-tests', 'kmx_key_list.tests.cpp'],