chore(core): merge actions test data for unit tests

Merge actions test data struct and some of the data for the actions API
unit tests. actions_update_app_context_nfu.tests.cpp data has not been
merged because the expected results are different, as it does not do
normalization, unlike actions_get_api and actions_normalize.

Merging of that data could be done, but would require adding extra
fields to each test result, which would make the data considerably
harder to read! Thus I have left it alone for now, just merged the
struct, which is still a win.

Fixes: #15913
This commit is contained in:
Marc Durdin 2026-05-07 12:31:19 +02:00
parent a3de441eda
commit 82f4c954a5
6 changed files with 466 additions and 652 deletions

View file

@ -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<TestData>& info) {
return info.param.test_name;
}
class GetActionApiTest : public testing::TestWithParam<TestData> {
class GetActionApiTest : public testing::TestWithParam<ActionsTestData> {
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);

View file

@ -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<TestData>& info) {
return info.param.test_name;
}
class ActionsNormalizeApiTest : public testing::TestWithParam<TestData> {
class ActionsNormalizeApiTest : public testing::TestWithParam<ActionsTestData> {
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);

View file

@ -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> 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<ActionsTestData>& info) {
return info.param.test_name;
}

View file

@ -0,0 +1,64 @@
#pragma once
#include <string>
#include <vector>
#include <gtest/gtest.h>
#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> actionsTestData;
std::string GenerateTestName(const testing::TestParamInfo<ActionsTestData>& info);

View file

@ -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<TestData>& info) {
return info.param.test_name;
}
class ActionsUpdateAppContextNfuApiTest : public testing::TestWithParam<TestData> {
class ActionsUpdateAppContextNfuApiTest : public testing::TestWithParam<ActionsTestData> {
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<ActionsTestData> 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);

View file

@ -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'],