mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-05 08:25:32 +00:00
Merge branch 'epic/web-core' into chore/merge-master-into-web-core
This commit is contained in:
commit
a8093460fd
87 changed files with 1580 additions and 489 deletions
|
|
@ -1007,7 +1007,11 @@ Provides read-only information about a keyboard.
|
|||
typedef struct {
|
||||
km_core_cu const * version_string;
|
||||
km_core_cu const * id;
|
||||
|
||||
// TODO-web-core: Deprecate this field (#12497)
|
||||
// KMN_DEPRECATED
|
||||
km_core_path_name folder_path;
|
||||
|
||||
km_core_option_item const * default_options;
|
||||
} km_core_keyboard_attrs;
|
||||
|
||||
|
|
@ -1022,7 +1026,7 @@ typedef struct {
|
|||
: Keyman keyboard ID string.
|
||||
|
||||
`folder_path`
|
||||
: Path to the unpacked folder containing the keyboard and associated resources.
|
||||
: Path to the unpacked folder containing the keyboard and associated resources (deprecated).
|
||||
|
||||
`default_options`
|
||||
: Set of default values for any options included in the keyboard.
|
||||
|
|
@ -1096,12 +1100,16 @@ typedef struct {
|
|||
|
||||
## Description
|
||||
|
||||
DEPRECATED: use [km_core_keyboard_load_from_blob] instead.
|
||||
|
||||
Parse and load keyboard from the supplied path and a pointer to the loaded keyboard
|
||||
into the out paramter.
|
||||
into the out parameter.
|
||||
|
||||
## Specification
|
||||
|
||||
```c */
|
||||
// TODO-web-core: Deprecate this function (#12497)
|
||||
// KMN_DEPRECATED_API
|
||||
KMN_API
|
||||
km_core_status
|
||||
km_core_keyboard_load(km_core_path_name kb_path,
|
||||
|
|
@ -1140,6 +1148,60 @@ km_core_keyboard_load(km_core_path_name kb_path,
|
|||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
# km_core_keyboard_load_from_blob()
|
||||
|
||||
## Description
|
||||
|
||||
Parse and load keyboard from the supplied blob and a pointer to the loaded keyboard
|
||||
into the out paramter.
|
||||
|
||||
## Specification
|
||||
|
||||
```c */
|
||||
KMN_API
|
||||
km_core_status km_core_keyboard_load_from_blob(const km_core_path_name kb_name,
|
||||
const void* blob,
|
||||
const size_t blob_size,
|
||||
km_core_keyboard** keyboard);
|
||||
|
||||
/*
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
`kb_name`
|
||||
: a string with the name of the keyboard.
|
||||
|
||||
`blob`
|
||||
: a byte array containing the content of a KMX/KMX+ file.
|
||||
|
||||
`blob_size`
|
||||
: a size_t variable with the size of the blob in bytes.
|
||||
|
||||
`keyboard`
|
||||
: A pointer to result variable: A pointer to the opaque keyboard
|
||||
object returned by the Processor. This memory must be freed with a
|
||||
call to [km_core_keyboard_dispose].
|
||||
|
||||
## Returns
|
||||
|
||||
`KM_CORE_STATUS_OK`
|
||||
: On success.
|
||||
|
||||
`KM_CORE_STATUS_NO_MEM`
|
||||
: In the event an internal memory allocation fails.
|
||||
|
||||
`KM_CORE_STATUS_IO_ERROR`
|
||||
: In the event the keyboard file is unparseable for any reason
|
||||
|
||||
`KM_CORE_STATUS_INVALID_ARGUMENT`
|
||||
: In the event `keyboard` is null.
|
||||
|
||||
`KM_CORE_STATUS_OS_ERROR`
|
||||
: Bit 31 (high bit) set, bits 0-30 are an OS-specific error code.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
# km_core_keyboard_dispose()
|
||||
|
||||
## Description
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@
|
|||
#define _kmn_unused(x) UNUSED_ ## x __attribute__((__unused__))
|
||||
#else
|
||||
#define _kmn_unused(x) UNUSED_ ## x
|
||||
|
||||
#endif
|
||||
|
||||
#if defined _WIN32 || defined __CYGWIN__
|
||||
|
|
@ -36,7 +35,7 @@
|
|||
#undef _kmn_static_flag
|
||||
#else // How MSVC sepcifies function level attributes adn deprecation
|
||||
#define _kmn_and
|
||||
#define _kmn_tag_fn(a) __declspec(a)
|
||||
#define _kmn_tag_fn(a) __declspec(a)
|
||||
#define _kmn_deprecated_flag deprecated
|
||||
#endif
|
||||
#define _kmn_export_flag dllexport
|
||||
|
|
@ -48,6 +47,8 @@
|
|||
#define _KM_CORE_EXT_SEPARATOR ('.')
|
||||
#endif
|
||||
|
||||
#define KMN_DEPRECATED _kmn_tag_fn(_kmn_deprecated_flag)
|
||||
|
||||
#if defined KM_CORE_LIBRARY_STATIC
|
||||
#define KMN_API _kmn_tag_fn(_kmn_static_flag)
|
||||
#define KMN_DEPRECATED_API _kmn_tag_fn(_kmn_deprecated_flag _kmn_and _kmn_static_flag)
|
||||
|
|
|
|||
|
|
@ -17,18 +17,16 @@ void keyboard_attributes::render()
|
|||
// Make attributes point to the stored values above.
|
||||
id = _keyboard_id.c_str();
|
||||
version_string = _version_string.c_str();
|
||||
folder_path = _folder_path.c_str();
|
||||
default_options = _default_opts.data();
|
||||
}
|
||||
|
||||
|
||||
keyboard_attributes::keyboard_attributes(std::u16string const & kbid,
|
||||
std::u16string const & version,
|
||||
path_type const & path,
|
||||
options_store const &opts)
|
||||
: _keyboard_id(kbid),
|
||||
_version_string(version),
|
||||
_folder_path(path),
|
||||
_folder_path(""),
|
||||
_default_opts(opts)
|
||||
{
|
||||
// Ensure that the default_options array will be properly terminated.
|
||||
|
|
@ -40,7 +38,7 @@ keyboard_attributes::keyboard_attributes(std::u16string const & kbid,
|
|||
keyboard_attributes::keyboard_attributes(keyboard_attributes &&rhs)
|
||||
: _keyboard_id(std::move(rhs._keyboard_id)),
|
||||
_version_string(std::move(rhs._version_string)),
|
||||
_folder_path(std::move(rhs._folder_path)),
|
||||
_folder_path(""),
|
||||
_default_opts(std::move(rhs._default_opts))
|
||||
{
|
||||
rhs.id = rhs.version_string = nullptr;
|
||||
|
|
@ -58,7 +56,6 @@ json & km::core::operator << (json & j, km::core::keyboard_attributes const & kb
|
|||
{
|
||||
j << json::object
|
||||
<< "id" << kb.id
|
||||
<< "folder" << kb._folder_path
|
||||
<< "version" << kb.version_string
|
||||
<< "rules" << json::array << json::close;
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ namespace core
|
|||
{
|
||||
std::u16string _keyboard_id;
|
||||
std::u16string _version_string;
|
||||
// unused and deprecated
|
||||
core::path _folder_path;
|
||||
std::vector<option> _default_opts;
|
||||
|
||||
|
|
@ -33,7 +34,6 @@ namespace core
|
|||
|
||||
public:
|
||||
using options_store = decltype(_default_opts);
|
||||
using path_type = decltype(_folder_path);
|
||||
|
||||
keyboard_attributes()
|
||||
: km_core_keyboard_attrs {nullptr, nullptr, nullptr, nullptr} {}
|
||||
|
|
@ -42,7 +42,6 @@ namespace core
|
|||
|
||||
keyboard_attributes(std::u16string const & id,
|
||||
std::u16string const & version,
|
||||
path_type const & path,
|
||||
options_store const &opts);
|
||||
|
||||
keyboard_attributes & operator = (keyboard_attributes const &) = delete;
|
||||
|
|
@ -52,8 +51,6 @@ namespace core
|
|||
|
||||
options_store const & default_opts_store() const noexcept { return _default_opts; }
|
||||
options_store & default_opts_store() noexcept { return _default_opts; }
|
||||
|
||||
path_type const & path() const noexcept { return _folder_path; }
|
||||
};
|
||||
|
||||
json & operator << (json &, km::core::keyboard_attributes const &);
|
||||
|
|
|
|||
|
|
@ -14,47 +14,125 @@
|
|||
#include "keyman_core.h"
|
||||
|
||||
#include "keyboard.hpp"
|
||||
#include "processor.hpp"
|
||||
#include "kmx/kmx_processor.hpp"
|
||||
#include "ldml/ldml_processor.hpp"
|
||||
#include "mock/mock_processor.hpp"
|
||||
#include "processor.hpp"
|
||||
#include "utfcodec.hpp"
|
||||
|
||||
using namespace km::core;
|
||||
|
||||
namespace
|
||||
{
|
||||
abstract_processor * processor_factory(path const & kb_path) {
|
||||
// Some legacy packages may include upper-case file extensions
|
||||
// TODO-LDML: move file io out of core and into engine
|
||||
if (kb_path.suffix() == ".kmx" || kb_path.suffix() == ".KMX") {
|
||||
std::vector<uint8_t> buf;
|
||||
if(ldml_processor::is_kmxplus_file(kb_path, buf)) {
|
||||
abstract_processor * result = new ldml_processor(kb_path, buf);
|
||||
return result;
|
||||
}
|
||||
return new kmx_processor(kb_path);
|
||||
abstract_processor* processor_factory(path const & kb_name, const std::vector<uint8_t> & buf) {
|
||||
if (ldml_processor::is_handled(buf)) {
|
||||
return new ldml_processor(kb_name, buf);
|
||||
}
|
||||
else if (kb_path.suffix() == ".mock") {
|
||||
return new mock_processor(kb_path);
|
||||
if (kmx_processor::is_handled(buf)) {
|
||||
return new kmx_processor(kb_name, buf);
|
||||
}
|
||||
else {
|
||||
return new null_processor();
|
||||
if (mock_processor::is_handled(buf)) {
|
||||
return new mock_processor(kb_name);
|
||||
}
|
||||
return new null_processor();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
}
|
||||
km_core_status
|
||||
km_core_keyboard_load(km_core_path_name kb_path, km_core_keyboard **keyboard)
|
||||
keyboard_load_from_blob_internal(
|
||||
const km_core_path_name kb_name,
|
||||
const std::vector<uint8_t> & buf,
|
||||
km_core_keyboard** keyboard
|
||||
) {
|
||||
assert(keyboard);
|
||||
if (!keyboard) {
|
||||
return KM_CORE_STATUS_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
*keyboard = nullptr;
|
||||
try {
|
||||
abstract_processor* kp = processor_factory(kb_name, buf);
|
||||
km_core_status status = kp->validate();
|
||||
if (status != KM_CORE_STATUS_OK) {
|
||||
delete kp;
|
||||
return status;
|
||||
}
|
||||
*keyboard = static_cast<km_core_keyboard*>(kp);
|
||||
} catch (std::bad_alloc&) {
|
||||
return KM_CORE_STATUS_NO_MEM;
|
||||
}
|
||||
return KM_CORE_STATUS_OK;
|
||||
}
|
||||
|
||||
km_core_status
|
||||
km_core_keyboard_load_from_blob(
|
||||
const km_core_path_name kb_name,
|
||||
const void* blob,
|
||||
const size_t blob_size,
|
||||
km_core_keyboard** keyboard
|
||||
) {
|
||||
assert(keyboard);
|
||||
if (!keyboard || !blob) {
|
||||
return KM_CORE_STATUS_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> buf((uint8_t*)blob, (uint8_t*)blob + blob_size);
|
||||
return keyboard_load_from_blob_internal(kb_name, buf, keyboard);
|
||||
}
|
||||
|
||||
// TODO-web-core: Remove this code when we remove the deprecated km_core_keyboard_load method
|
||||
// BEGIN DEPRECATED
|
||||
#include <fstream>
|
||||
std::vector<uint8_t> load_kmx_file(path const& kb_path) {
|
||||
std::vector<uint8_t> data;
|
||||
std::ifstream file(static_cast<std::string>(kb_path), std::ios::binary | std::ios::ate);
|
||||
if (!file.good()) {
|
||||
return std::vector<uint8_t>();
|
||||
}
|
||||
const std::streamsize size = file.tellg();
|
||||
if (size >= KMX_MAX_ALLOWED_FILE_SIZE) {
|
||||
return std::vector<uint8_t>();
|
||||
}
|
||||
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
data.resize((size_t)size);
|
||||
if (!file.read((char*)data.data(), size)) {
|
||||
return std::vector<uint8_t>();
|
||||
}
|
||||
|
||||
file.close();
|
||||
return data;
|
||||
}
|
||||
|
||||
KMN_DEPRECATED_API
|
||||
km_core_status
|
||||
km_core_keyboard_load(km_core_path_name kb, km_core_keyboard **keyboard)
|
||||
{
|
||||
assert(keyboard);
|
||||
if (!keyboard)
|
||||
if (!keyboard || !kb) {
|
||||
return KM_CORE_STATUS_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
path const kb_path(kb);
|
||||
try
|
||||
{
|
||||
abstract_processor *kp = processor_factory(kb_path);
|
||||
km_core_status status = kp->validate();
|
||||
abstract_processor* kp = nullptr;
|
||||
km_core_status status = KM_CORE_STATUS_OK;
|
||||
// Some legacy packages may include upper-case file extensions
|
||||
if (kb_path.suffix() == ".kmx" || kb_path.suffix() == ".KMX") {
|
||||
std::vector<uint8_t> buf = load_kmx_file(kb_path);
|
||||
status = keyboard_load_from_blob_internal(kb_path.stem().c_str(), buf, (km_core_keyboard**)&kp);
|
||||
if (status != KM_CORE_STATUS_OK) {
|
||||
return status;
|
||||
}
|
||||
} else if (kb_path.suffix() == ".mock") {
|
||||
kp = new mock_processor(kb_path);
|
||||
} else {
|
||||
kp = new null_processor();
|
||||
}
|
||||
status = kp->validate();
|
||||
if (status != KM_CORE_STATUS_OK) {
|
||||
delete kp;
|
||||
return status;
|
||||
|
|
@ -67,6 +145,7 @@ km_core_keyboard_load(km_core_path_name kb_path, km_core_keyboard **keyboard)
|
|||
}
|
||||
return KM_CORE_STATUS_OK;
|
||||
}
|
||||
// END DEPRECATED
|
||||
|
||||
void
|
||||
km_core_keyboard_dispose(km_core_keyboard *keyboard)
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ using namespace kmx;
|
|||
#include <share.h>
|
||||
#endif
|
||||
|
||||
KMX_BOOL KMX_ProcessEvent::Load(km_core_path_name KeyboardName)
|
||||
{
|
||||
if(!LoadKeyboard(KeyboardName, &m_keyboard.Keyboard)) return FALSE; // I5136
|
||||
KMX_BOOL KMX_ProcessEvent::Load(PKMX_BYTE buf, size_t sz) {
|
||||
if(!LoadKeyboardFromBlob(buf, sz, &m_keyboard.Keyboard))
|
||||
return FALSE; // I5136
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
|
@ -51,50 +51,22 @@ const int km::core::kmx::CODE__SIZE[] = {
|
|||
// Ensure that all CODE_### sizes are defined
|
||||
static_assert(sizeof(CODE__SIZE) / sizeof(CODE__SIZE[0]) == (CODE_LASTCODE + 1), "Size of array CODE__SIZE not correct");
|
||||
|
||||
|
||||
|
||||
KMX_BOOL KMX_ProcessEvent::LoadKeyboard(km_core_path_name fileName, LPKEYBOARD *lpKeyboard)
|
||||
{
|
||||
PKMX_BYTE buf;
|
||||
FILE *fp;
|
||||
KMX_BOOL KMX_ProcessEvent::LoadKeyboardFromBlob(
|
||||
PKMX_BYTE original_buf,
|
||||
size_t sz,
|
||||
LPKEYBOARD* lpKeyboard
|
||||
) {
|
||||
LPKEYBOARD kbp;
|
||||
PKMX_BYTE buf;
|
||||
PKMX_BYTE filebase;
|
||||
|
||||
DebugLog("Loading file '%s'", fileName);
|
||||
if(!fileName || !lpKeyboard)
|
||||
{
|
||||
DebugLog("Bad Filename");
|
||||
|
||||
if (!lpKeyboard || !original_buf) {
|
||||
DebugLog("Invalid parameter");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
fp = _wfsopen(fileName, L"rb", _SH_DENYWR);
|
||||
#else
|
||||
fp = fopen(fileName, "rb");
|
||||
#endif
|
||||
if(fp == NULL)
|
||||
{
|
||||
DebugLog("Could not open file");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (fseek(fp, 0, SEEK_END) != 0) {
|
||||
fclose(fp);
|
||||
DebugLog("Could not fseek file");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
auto sz = ftell(fp);
|
||||
if (sz < 0) {
|
||||
fclose(fp);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (fseek(fp, 0, SEEK_SET) != 0) {
|
||||
fclose(fp);
|
||||
DebugLog("Could not fseek(set) file");
|
||||
return FALSE;
|
||||
}
|
||||
*lpKeyboard = NULL;
|
||||
|
||||
#ifdef KMX_64BIT
|
||||
// allocate enough memory for expanded data structure + original data.
|
||||
|
|
@ -108,57 +80,55 @@ KMX_BOOL KMX_ProcessEvent::LoadKeyboard(km_core_path_name fileName, LPKEYBOARD *
|
|||
buf = new KMX_BYTE[sz];
|
||||
#endif
|
||||
|
||||
if(!buf)
|
||||
{
|
||||
fclose(fp);
|
||||
if (!buf) {
|
||||
DebugLog("Not allocmem");
|
||||
return FALSE;
|
||||
}
|
||||
#ifdef KMX_64BIT
|
||||
filebase = buf + sz*2;
|
||||
filebase = buf + sz * 2;
|
||||
#else
|
||||
filebase = buf;
|
||||
#endif
|
||||
memcpy(filebase, original_buf, sz);
|
||||
|
||||
if (fread(filebase, 1, sz, fp) < (size_t) sz) {
|
||||
fclose(fp);
|
||||
DebugLog("Could not read file");
|
||||
if (*PKMX_DWORD(filebase) != KMX_DWORD(FILEID_COMPILED)) {
|
||||
DebugLog("Invalid keyboard - signature is invalid");
|
||||
delete[] buf;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
|
||||
if(*PKMX_DWORD(filebase) != KMX_DWORD(FILEID_COMPILED))
|
||||
{
|
||||
delete [] buf;
|
||||
DebugLog("Invalid file - signature is invalid");
|
||||
if (!VerifyKeyboard(filebase, sz)) {
|
||||
DebugLog("Verify keyboard failed");
|
||||
delete[] buf;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if(!VerifyKeyboard(filebase, sz)) return FALSE;
|
||||
|
||||
#ifdef KMX_64BIT
|
||||
kbp = CopyKeyboard(buf, filebase);
|
||||
#else
|
||||
kbp = FixupKeyboard(buf, filebase);
|
||||
#endif
|
||||
|
||||
if(!kbp) return FALSE;
|
||||
if (!kbp) {
|
||||
DebugLog("Can't copy/fixup keyboard");
|
||||
delete[] buf;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if(kbp->dwIdentifier != FILEID_COMPILED) {
|
||||
delete [] buf;
|
||||
if (kbp->dwIdentifier != FILEID_COMPILED) {
|
||||
DebugLog("errNotFileID");
|
||||
delete[] buf;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
*lpKeyboard = kbp;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
PKMX_WCHAR KMX_ProcessEvent::StringOffset(PKMX_BYTE base, KMX_DWORD offset)
|
||||
{
|
||||
if(offset == 0) return NULL;
|
||||
if(offset == 0)
|
||||
return NULL;
|
||||
return (PKMX_WCHAR)(base + offset);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ private:
|
|||
KMX_DWORD m_modifiers = 0;
|
||||
|
||||
/* File loading */
|
||||
KMX_BOOL LoadKeyboard(km_core_path_name fileName, LPKEYBOARD *lpKeyboard);
|
||||
KMX_BOOL LoadKeyboardFromBlob(PKMX_BYTE buf, size_t sz, LPKEYBOARD* lpKeyboard);
|
||||
KMX_BOOL VerifyKeyboard(PKMX_BYTE filebase, size_t sz);
|
||||
KMX_BOOL VerifyChecksum(PKMX_BYTE buf, size_t sz);
|
||||
#ifdef KMX_64BIT
|
||||
|
|
@ -96,7 +96,7 @@ public:
|
|||
KMX_ProcessEvent();
|
||||
~KMX_ProcessEvent();
|
||||
|
||||
KMX_BOOL Load(km_core_path_name keyboardName);
|
||||
KMX_BOOL Load(PKMX_BYTE buf, size_t sz);
|
||||
KMX_BOOL ProcessEvent(km_core_state *state, KMX_UINT vkey, KMX_DWORD modifiers, KMX_BOOL isKeyDown); // returns FALSE on error or key not matched
|
||||
|
||||
KMX_Actions *GetActions();
|
||||
|
|
|
|||
|
|
@ -38,9 +38,8 @@ km_core_status kmx_processor::validate() const {
|
|||
return _valid ? KM_CORE_STATUS_OK : KM_CORE_STATUS_INVALID_KEYBOARD;
|
||||
}
|
||||
|
||||
kmx_processor::kmx_processor(core::path p) {
|
||||
p.replace_extension(".kmx");
|
||||
_valid = bool(_kmx.Load(p.c_str()));
|
||||
kmx_processor::kmx_processor(std::u16string const& kb_name, const std::vector<uint8_t>& data) {
|
||||
_valid = bool(_kmx.Load((PKMX_BYTE)data.data(), data.size()));
|
||||
|
||||
if (!_valid)
|
||||
return;
|
||||
|
|
@ -57,8 +56,7 @@ kmx_processor::kmx_processor(core::path p) {
|
|||
auto v = _kmx.GetKeyboard()->Keyboard->version;
|
||||
auto vs = std::to_string(v >> 16) + "." + std::to_string(v & 0xffff);
|
||||
|
||||
_attributes = keyboard_attributes(static_cast<std::u16string>(p.stem()),
|
||||
std::u16string(vs.begin(), vs.end()), p.parent(), defaults);
|
||||
_attributes = keyboard_attributes(kb_name, std::u16string(vs.begin(), vs.end()), defaults);
|
||||
}
|
||||
|
||||
char16_t const *
|
||||
|
|
@ -414,3 +412,24 @@ km_core_keyboard_imx * kmx_processor::get_imx_list() const {
|
|||
return imx_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true the data is a KMX file, i.e. starts with 'KXTS'.
|
||||
*
|
||||
* @param data the keyboard blob
|
||||
* @return true if the processor can handle the keyboard, otherwise false.
|
||||
*/
|
||||
bool kmx_processor::is_handled(const std::vector<uint8_t>& data) {
|
||||
if (data.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data.size() < sizeof(COMP_KEYBOARD)) { // a KMX file is at least 64 bytes (KMX header)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data.size() >= KMX_MAX_ALLOWED_FILE_SIZE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ((kmx::PCOMP_KEYBOARD)data.data())->dwIdentifier == KMX_DWORD(FILEID_COMPILED); // 'KXTS'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ namespace core
|
|||
);
|
||||
|
||||
public:
|
||||
kmx_processor(path);
|
||||
kmx_processor(std::u16string const& kb_name, const std::vector<uint8_t>& data);
|
||||
|
||||
km_core_status
|
||||
process_event(
|
||||
|
|
@ -84,6 +84,8 @@ namespace core
|
|||
supports_normalization() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool is_handled(const std::vector<uint8_t>& buf);
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
|
|
|
|||
185
core/src/layout.hpp
Normal file
185
core/src/layout.hpp
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL International. MIT License.
|
||||
*
|
||||
* Keyman Keyboard Processor API - On-Screen Keyboard Layout Interfaces
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "keyman_core_api.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Possible directions of a flick
|
||||
*/
|
||||
enum keyboard_layout_flick_direction {
|
||||
/** flick up (north) */
|
||||
n = 0,
|
||||
/** flick down (south) */
|
||||
s = 1,
|
||||
/** flick right (east) */
|
||||
e = 2,
|
||||
/** flick left (west) */
|
||||
w = 3,
|
||||
/** flick up-right (north-east) */
|
||||
ne = 4,
|
||||
/** flick up-left (north-west) */
|
||||
nw = 5,
|
||||
/** flick down-right (south-east) */
|
||||
se = 6,
|
||||
/** flick down-left (south-west) */
|
||||
sw = 7
|
||||
};
|
||||
|
||||
/**
|
||||
* key type like regular key, framekeys, deadkeys, blank, etc.
|
||||
*/
|
||||
enum keyboard_layout_key_type {
|
||||
/** regular key */
|
||||
normal = 0,
|
||||
/** A 'frame' key, such as Shift or Enter */
|
||||
special = 1,
|
||||
/** A 'frame' key, such as Shift or Enter, which is is active, such as
|
||||
* the shift key on a shift layer */
|
||||
specialActive = 2,
|
||||
/** **KeymanWeb runtime private use:** a variant of `special` with the
|
||||
* keyboard font rather than 'KeymanwebOsk' font */
|
||||
customSpecial = 3,
|
||||
/** **KeymanWeb runtime private use:** a variant of `specialActive` with the
|
||||
* keyboard font rather than 'KeymanwebOsk' font. */
|
||||
customSpecialActive = 4,
|
||||
/** A deadkey */
|
||||
deadkey = 8,
|
||||
/** A key which is rendered as a blank keycap, should block any interaction */
|
||||
blank = 9,
|
||||
/** Renders the key only as a gap or spacer, should block any interaction */
|
||||
spacer = 10
|
||||
};
|
||||
|
||||
/**
|
||||
* A key on a touch layout/on-screen keyboard
|
||||
*/
|
||||
struct keyboard_layout_key {
|
||||
/** key id */
|
||||
std::u16string id; // TODO-WEB-CORE: perhaps necessary for special keys, Enter, etc? or can we get that from virtualKey?
|
||||
/** the virtual key code */
|
||||
int virtualKey; // TODO-WEB-CORE: do we need this? both id and virtualKey? Or just one of them?
|
||||
/** text to display on key cap */
|
||||
std::u16string display;
|
||||
/** hint e.g. for longpress */
|
||||
std::u16string hint;
|
||||
/** the type of key */
|
||||
keyboard_layout_key_type type;
|
||||
|
||||
/**
|
||||
* the modifier combination (not layer) that should be used in key events,
|
||||
* for this key, overriding the layer that the key is a part of.
|
||||
*/
|
||||
int modifiersOverride;
|
||||
/** the next layer to switch to after this key is pressed */
|
||||
std::u16string nextLayerId;
|
||||
|
||||
// touch layouts only
|
||||
|
||||
/** padding - space to the left of key (in what units?) */
|
||||
int gap;
|
||||
/** width of the key (in what units?) */
|
||||
int width;
|
||||
|
||||
/** longpress keys, also known as subkeys */
|
||||
std::vector<keyboard_layout_key> longpresses;
|
||||
/** multitaps */
|
||||
std::vector<keyboard_layout_key> multiTaps;
|
||||
/** flicks */
|
||||
std::map<keyboard_layout_flick_direction, keyboard_layout_key> flicks;
|
||||
};
|
||||
|
||||
/**
|
||||
* a row of keys on a touch layout/on-screen keyboard
|
||||
*/
|
||||
struct keyboard_layout_row {
|
||||
/** row id */
|
||||
int id; // TODO-WEB-CORE: do we need this? Web has it (`TouchLayoutRow`)
|
||||
/** keys in this row */
|
||||
std::vector<keyboard_layout_key> keys;
|
||||
};
|
||||
|
||||
/**
|
||||
* a layer with rows of keys on a touch layout/on-screen keyboard
|
||||
*/
|
||||
struct keyboard_layout_layer {
|
||||
/** layer id */
|
||||
std::u16string id;
|
||||
/** layer modifiers */
|
||||
// TODO-WEB-CORE: we added this during our discussion, but Web doesn't have it.
|
||||
// Should be an enum if it's needed.
|
||||
int modifiers; //? 0 = default, n = shift, etc. -1 = unspecified?
|
||||
/** rows in this layer */
|
||||
std::vector<keyboard_layout_row> rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* layout specification for a specific platform like desktop, phone or tablet
|
||||
*/
|
||||
struct keyboard_layout_platform {
|
||||
/** platform form factor, e.g. 'iso', 'touch', 'ansi', ... (see ldml spec) */
|
||||
std::u16string form;
|
||||
/** width of screen for touch layout */
|
||||
int minWidthMm; // we don't have mobile vs tablet, instead use this
|
||||
/** layers for this platform */
|
||||
std::vector<keyboard_layout_layer> layers;
|
||||
|
||||
// TODO-WEB-CORE: Do we need these:
|
||||
// Web additionally has:
|
||||
// - font (should be in CSS; we have it in `keyboard_layout`)
|
||||
// - fontsize (should be in CSS; we have it in `keyboard_layout`)
|
||||
// - displayUnderlying
|
||||
// - defaultHint ("none"|"dot"|"longpress"|"multitap"|"flick"|"flick-n"|"flick-ne"|
|
||||
// "flick-e"|"flick-se"|"flick-s"|"flick-sw"|"flick-w"|"flick-nw")
|
||||
};
|
||||
|
||||
/**
|
||||
* On screen keyboard description consisting of specific layouts for different
|
||||
* form factors.
|
||||
*/
|
||||
struct keyboard_layout {
|
||||
/** layouts for different form factors */
|
||||
std::vector<keyboard_layout_platform> platforms;
|
||||
/** font face name to use for key caps*/
|
||||
std::string fontFacename;
|
||||
/** font size to use for key caps */
|
||||
int fontSizeEm; // TODO-WEB-CORE: em? px? something else?
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the on-screen keyboard layout for the specified keyboard.
|
||||
*
|
||||
* @param keyboard [in] The keyboard to get the layout for.
|
||||
* @param layout [out] The on-screen keyboard layout.
|
||||
* @return km_core_status `KM_CORE_STATUS_OK`: On success.
|
||||
* `KM_CORE_STATUS_INVALID_ARGUMENT`: If `keyboard` is not a valid keyboard or `layout` is null.
|
||||
*/
|
||||
km_core_status
|
||||
keyboard_get_layout(
|
||||
km_core_keyboard const* keyboard,
|
||||
keyboard_layout** layout
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* Dispose the on-screen keyboard layout.
|
||||
*/
|
||||
void
|
||||
keyboard_layout_dispose(keyboard_layout* layout);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif
|
||||
|
|
@ -5,7 +5,6 @@
|
|||
Authors: Marc Durdin (MD)
|
||||
*/
|
||||
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include "ldml/ldml_processor.hpp"
|
||||
#include "ldml/ldml_transforms.hpp"
|
||||
|
|
@ -15,6 +14,7 @@
|
|||
#include "kmx/kmx_plus.h"
|
||||
#include "kmx/kmx_xstring.h"
|
||||
#include "kmx/kmx_processevent.h"
|
||||
#include "kmx/kmx_processor.hpp"
|
||||
#include "ldml/keyman_core_ldml.h"
|
||||
#include "kmx/kmx_file_validator.hpp"
|
||||
#include "debuglog.h"
|
||||
|
|
@ -36,19 +36,15 @@ namespace {
|
|||
namespace km {
|
||||
namespace core {
|
||||
|
||||
|
||||
ldml_processor::ldml_processor(path const & kb_path, const std::vector<uint8_t> &data)
|
||||
: abstract_processor(
|
||||
keyboard_attributes(kb_path.stem(), KM_CORE_LMDL_PROCESSOR_VERSION, kb_path.parent(), {})
|
||||
), _valid(false), transforms(), bksp_transforms(), keys(), normalization_disabled(false)
|
||||
{
|
||||
|
||||
ldml_processor::ldml_processor(std::u16string const& kb_name, const std::vector<uint8_t>& data)
|
||||
: abstract_processor(keyboard_attributes(kb_name, KM_CORE_LMDL_PROCESSOR_VERSION, {})),
|
||||
_valid(false), transforms(), bksp_transforms(), keys(), normalization_disabled(false) {
|
||||
if(data.size() <= sizeof(kmx::COMP_KEYBOARD_EX)) {
|
||||
DebugLog("data.size %zu too small", data.size());
|
||||
return;
|
||||
}
|
||||
|
||||
// // Locate the structs here, but still retain ptrs to the raw structs.
|
||||
// Locate the structs here, but still retain ptrs to the raw structs.
|
||||
kmx::KMX_FileValidator* comp_keyboard = (kmx::KMX_FileValidator*)data.data();
|
||||
|
||||
// Perform the standard validation
|
||||
|
|
@ -116,40 +112,24 @@ ldml_processor::ldml_processor(path const & kb_path, const std::vector<uint8_t>
|
|||
_valid = true;
|
||||
}
|
||||
|
||||
bool ldml_processor::is_kmxplus_file(path const & kb_path, std::vector<uint8_t>& data) {
|
||||
// TODO-LDML: we should refactor all the core components to delegate file loading
|
||||
// to the Engine, which requires an API change, but this makes delivery
|
||||
// of keyboard files more flexible under more WASM.
|
||||
|
||||
std::ifstream file(static_cast<std::string>(kb_path), std::ios::binary | std::ios::ate);
|
||||
if(!file.good()) {
|
||||
/**
|
||||
* Returns true if the data is a KMX+ file.
|
||||
*
|
||||
* @param data the keyboard blob
|
||||
* @return true if the processor can handle the keyboard, otherwise false.
|
||||
*/
|
||||
bool ldml_processor::is_handled(const std::vector<uint8_t>& data) {
|
||||
// Check if it's a blob from a KMX file
|
||||
if (!kmx_processor::is_handled(data)) {
|
||||
return false;
|
||||
}
|
||||
const std::streamsize size = file.tellg();
|
||||
if(size >= KMX_MAX_ALLOWED_FILE_SIZE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
data.resize((size_t)size);
|
||||
if(!file.read((char *) data.data(), size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
const kmx::PCOMP_KEYBOARD comp_keyboard = (kmx::PCOMP_KEYBOARD)data.data();
|
||||
|
||||
if(comp_keyboard->dwIdentifier != KMX_DWORD(FILEID_COMPILED)) {
|
||||
if (comp_keyboard->dwFileVersion < VERSION_160 || (comp_keyboard->dwFlags & KF_KMXPLUS) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(comp_keyboard->dwFileVersion < VERSION_160 || (comp_keyboard->dwFlags & KF_KMXPLUS) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// A KMXPlus file is in the buffer (although more validation is required and will
|
||||
// The buffer contains KMXPlus data (although more validation is required and will
|
||||
// be done in the constructor)
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,13 +30,12 @@ class ldml_event_state;
|
|||
class ldml_processor : public abstract_processor {
|
||||
public:
|
||||
ldml_processor(
|
||||
path const & kb_path,
|
||||
std::u16string const& kb_name,
|
||||
const std::vector<uint8_t> & data
|
||||
);
|
||||
|
||||
static bool is_kmxplus_file(
|
||||
path const & kb_path,
|
||||
std::vector<uint8_t>& data
|
||||
const std::vector<uint8_t> & data
|
||||
);
|
||||
|
||||
km_core_status
|
||||
|
|
@ -86,9 +85,11 @@ class ldml_processor : public abstract_processor {
|
|||
return !normalization_disabled;
|
||||
}
|
||||
|
||||
static bool is_handled(const std::vector<uint8_t> & buf);
|
||||
|
||||
private:
|
||||
/** process a key-up */
|
||||
void process_key_up(ldml_event_state &ldml_state) const;
|
||||
void process_key_up(ldml_event_state& ldml_state) const;
|
||||
|
||||
/** process a key-down (if it wasn't handled exceptionally) */
|
||||
void process_key_down(ldml_event_state &ldml_state) const;
|
||||
|
|
|
|||
|
|
@ -118,6 +118,7 @@ api_files = files(
|
|||
'km_core_state_api.cpp',
|
||||
'km_core_debug_api.cpp',
|
||||
'km_core_processevent_api.cpp',
|
||||
'wasm.cpp',
|
||||
)
|
||||
|
||||
core_files = files(
|
||||
|
|
@ -133,6 +134,16 @@ mock_files = files(
|
|||
'mock/mock_processor.cpp',
|
||||
)
|
||||
|
||||
if cpp_compiler.get_id() == 'emscripten'
|
||||
host_links = ['--whole-archive', '-sALLOW_MEMORY_GROWTH=1', '-sMODULARIZE=1',
|
||||
'-sEXPORT_ES6', '-sENVIRONMENT=web,webview',
|
||||
'--emit-tsd', 'km-core-interface.d.ts', '-sERROR_ON_UNDEFINED_SYMBOLS=0']
|
||||
|
||||
links += ['-sEXPORTED_RUNTIME_METHODS=[\'UTF8ToString\',\'stringToNewUTF8\',\'wasmExports\']',
|
||||
# Forcing inclusion of debug symbols
|
||||
'-g', '-Wlimited-postlink-optimizations', '--bind']
|
||||
endif
|
||||
|
||||
lib = library('keymancore',
|
||||
api_files,
|
||||
core_files,
|
||||
|
|
@ -160,3 +171,24 @@ pkg.generate(
|
|||
description: 'Keyman processor for KMN keyboards.',
|
||||
subdirs: headerdirs,
|
||||
libraries: lib)
|
||||
|
||||
if cpp_compiler.get_id() == 'emscripten'
|
||||
# Build an executable
|
||||
host = executable('km-core',
|
||||
cpp_args: defns,
|
||||
include_directories: inc,
|
||||
link_args: links + host_links,
|
||||
objects: lib.extract_all_objects(recursive: false))
|
||||
|
||||
if get_option('buildtype') == 'release'
|
||||
# TODO: #12888
|
||||
# Split debug symbols into separate wasm file for release builds only
|
||||
# as the release symbols will be uploaded to sentry
|
||||
# custom_target('core.wasm',
|
||||
# depends: host,
|
||||
# input: host,
|
||||
# output: 'core.wasm',
|
||||
# command: ['wasm-split', '@OUTDIR@/core.wasm', '-o', '@OUTPUT@', '--strip', '--debug-out=@OUTDIR@/core.debug.wasm'],
|
||||
# build_by_default: true)
|
||||
endif
|
||||
endif
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
Description: This is a test implementation of the keyboard processor API to
|
||||
enable testing API clients against a basic keyboard and give
|
||||
them something to link against and load.
|
||||
TODO: Add a mecahnism to trigger output of PERSIST_OPT &
|
||||
TODO: Add a mechanism to trigger output of PERSIST_OPT &
|
||||
RESET_OPT actions items, options support and context matching.
|
||||
Create Date: 17 Oct 2018
|
||||
Authors: Tim Eves (TSE)
|
||||
|
|
@ -74,7 +74,7 @@ namespace km {
|
|||
{
|
||||
mock_processor::mock_processor(core::path const & path)
|
||||
: abstract_processor(
|
||||
keyboard_attributes(path.stem(), u"3.145", path.parent(), {
|
||||
keyboard_attributes(path.stem(), u"3.145", {
|
||||
option{KM_CORE_OPT_KEYBOARD, u"__test_point", u"not tiggered"},
|
||||
})),
|
||||
_options({
|
||||
|
|
@ -228,5 +228,23 @@ namespace km {
|
|||
km_core_status mock_processor::validate() const { return KM_CORE_STATUS_OK; }
|
||||
|
||||
km_core_status null_processor::validate() const { return KM_CORE_STATUS_INVALID_ARGUMENT; }
|
||||
} // namespace core
|
||||
|
||||
/**
|
||||
* Returns true if the data starts with 'MOCK'.
|
||||
*
|
||||
* @param data the keyboard blob
|
||||
* @return true if the processor can handle the keyboard, otherwise false.
|
||||
*/
|
||||
bool mock_processor::is_handled(const std::vector<uint8_t>& data) {
|
||||
if (data.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data.size() < 4) { // a MOCK file is at least 4 bytes (MOCK)
|
||||
return false;
|
||||
}
|
||||
|
||||
return ((char*)data.data())[0] == 'M' && ((char*)data.data())[1] == 'O' && ((char*)data.data())[2] == 'C' && ((char*)data.data())[3] == 'K';
|
||||
}
|
||||
} // namespace core
|
||||
} // namespace km
|
||||
|
|
|
|||
|
|
@ -69,13 +69,21 @@ namespace core
|
|||
supports_normalization() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the data starts with 'MOCK'
|
||||
*
|
||||
* @param buf the keyboard blob
|
||||
* @return true if the processor can handle the keyboard, otherwise false.
|
||||
*/
|
||||
static bool is_handled(const std::vector<uint8_t>& data);
|
||||
};
|
||||
|
||||
class null_processor : public mock_processor {
|
||||
public:
|
||||
null_processor(): mock_processor(path())
|
||||
{
|
||||
_attributes = keyboard_attributes(u"null", u"0.0", path(), {});
|
||||
_attributes = keyboard_attributes(u"null", u"0.0", {});
|
||||
}
|
||||
|
||||
km_core_status validate() const override;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
#include "core_icu.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
|
|
|||
44
core/src/wasm.cpp
Normal file
44
core/src/wasm.cpp
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#ifdef __EMSCRIPTEN__
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten/emscripten.h>
|
||||
#include <emscripten/bind.h>
|
||||
|
||||
#else
|
||||
#define EMSCRIPTEN_KEEPALIVE
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
#define EXTERN extern "C" EMSCRIPTEN_KEEPALIVE
|
||||
#else
|
||||
#define EXTERN EMSCRIPTEN_KEEPALIVE
|
||||
#endif
|
||||
|
||||
#include <keyman_core.h>
|
||||
|
||||
constexpr km_core_attr const engine_attrs = {
|
||||
256,
|
||||
KM_CORE_LIB_CURRENT,
|
||||
KM_CORE_LIB_AGE,
|
||||
KM_CORE_LIB_REVISION,
|
||||
KM_CORE_TECH_KMX,
|
||||
"SIL International"
|
||||
};
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE km_core_attr const & tmp_wasm_attributes() {
|
||||
return engine_attrs;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_BINDINGS(core_interface) {
|
||||
|
||||
emscripten::value_object<km_core_attr>("km_core_attr")
|
||||
.field("max_context", &km_core_attr::max_context)
|
||||
.field("current", &km_core_attr::current)
|
||||
.field("revision", &km_core_attr::revision)
|
||||
.field("age", &km_core_attr::age)
|
||||
.field("technology", &km_core_attr::technology)
|
||||
//.field("vendor", &km_core_attr::vendor, emscripten::allow_raw_pointers())
|
||||
;
|
||||
|
||||
emscripten::function("tmp_wasm_attributes", &tmp_wasm_attributes);
|
||||
}
|
||||
#endif
|
||||
1
core/subprojects/.gitignore
vendored
1
core/subprojects/.gitignore
vendored
|
|
@ -2,3 +2,4 @@
|
|||
/*.zip
|
||||
/*.tgz
|
||||
/packagecache
|
||||
/googletest*/
|
||||
|
|
|
|||
16
core/subprojects/gtest.wrap
Normal file
16
core/subprojects/gtest.wrap
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
[wrap-file]
|
||||
directory = googletest-1.14.0
|
||||
source_url = https://github.com/google/googletest/archive/refs/tags/v1.14.0.tar.gz
|
||||
source_filename = gtest-1.14.0.tar.gz
|
||||
source_hash = 8ad598c73ad796e0d8280b082cebd82a630d73e73cd3c70057938a6501bba5d7
|
||||
patch_filename = gtest_1.14.0-2_patch.zip
|
||||
patch_url = https://github.com/mesonbuild/wrapdb/releases/download/gtest_1.14.0-2/gtest_1.14.0-2_patch.zip
|
||||
patch_hash = 4ec7f767364386a99f7b2d61678287a73ad6ba0f9998be43b51794c464a63732
|
||||
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/gtest_1.14.0-2/gtest-1.14.0.tar.gz
|
||||
wrapdb_version = 1.14.0-2
|
||||
|
||||
[provide]
|
||||
gtest = gtest_dep
|
||||
gtest_main = gtest_main_dep
|
||||
gmock = gmock_dep
|
||||
gmock_main = gmock_main_dep
|
||||
|
|
@ -10,10 +10,7 @@
|
|||
cmpfiles = ['-c', 'import sys; a = open(sys.argv[1], \'r\').read(); b = open(sys.argv[2], \'r\').read(); exit(not (a==b))']
|
||||
stnds = join_paths(meson.current_source_dir(), 'standards')
|
||||
|
||||
libsrc = include_directories(
|
||||
'../src',
|
||||
'../../common/include'
|
||||
)
|
||||
libsrc = include_directories('../src')
|
||||
|
||||
# kmx_test_source is required for linux builds, so always enable it even when we
|
||||
# disable all other tests
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ else
|
|||
endif
|
||||
|
||||
e = executable('jsontest', 'jsontest.cpp',
|
||||
include_directories: [libsrc],
|
||||
include_directories: [inc, libsrc],
|
||||
link_args: links + tests_flags,
|
||||
objects: lib.extract_objects('jsonpp.cpp'))
|
||||
test('jsontest', e, args: 'jsontest.json')
|
||||
|
|
|
|||
128
core/tests/unit/km_core_keyboard_api.tests.cpp
Normal file
128
core/tests/unit/km_core_keyboard_api.tests.cpp
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
// Copyright (c) 2024 SIL International
|
||||
// This software is licensed under the MIT license (http://opensource.org/licenses/MIT)
|
||||
|
||||
#include <fstream>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <keyman/keyman_core_api.h>
|
||||
#include "emscripten_filesystem.h"
|
||||
#include "load_kmx_file.hpp"
|
||||
|
||||
km::core::path test_dir;
|
||||
|
||||
// TODO-web-core: Remove this code when we remove the deprecated km_core_keyboard_load method
|
||||
// BEGIN DEPRECATED
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#define PRAGMA(X) _Pragma(#X)
|
||||
#define DISABLE_WARNING_PUSH PRAGMA(GCC diagnostic push)
|
||||
#define DISABLE_WARNING_POP PRAGMA(GCC diagnostic pop)
|
||||
#define DISABLE_WARNING(W) PRAGMA(GCC diagnostic ignored #W)
|
||||
#define DISABLE_WARNING_DEPRECATED_DECLARATIONS DISABLE_WARNING(-Wdeprecated-declarations)
|
||||
#else
|
||||
#define DISABLE_WARNING_PUSH
|
||||
#define DISABLE_WARNING_POP
|
||||
#define DISABLE_WARNING_DEPRECATED_DECLARATIONS
|
||||
#endif
|
||||
|
||||
class KmCoreKeyboardApiTests : public testing::Test {
|
||||
protected:
|
||||
km_core_keyboard* keyboard = nullptr;
|
||||
void TearDown() override {
|
||||
if (this->keyboard) {
|
||||
km_core_keyboard_dispose(this->keyboard);
|
||||
this->keyboard = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(KmCoreKeyboardApiTests, LoadFromFile) {
|
||||
// Setup
|
||||
km::core::path kmxfile = km::core::path(test_dir / "kmx/k_020___deadkeys_and_backspace.kmx");
|
||||
|
||||
// Execute
|
||||
DISABLE_WARNING_PUSH
|
||||
DISABLE_WARNING_DEPRECATED_DECLARATIONS
|
||||
auto status = km_core_keyboard_load(kmxfile.c_str(), &this->keyboard);
|
||||
DISABLE_WARNING_POP
|
||||
|
||||
// Verify
|
||||
EXPECT_EQ(status, KM_CORE_STATUS_OK);
|
||||
EXPECT_TRUE(this->keyboard != nullptr);
|
||||
}
|
||||
// END DEPRECATED
|
||||
|
||||
TEST_F(KmCoreKeyboardApiTests, LoadFromBlob) {
|
||||
// Setup
|
||||
km::core::path kmxfile = km::core::path(test_dir / "kmx/k_020___deadkeys_and_backspace.kmx");
|
||||
|
||||
std::vector<uint8_t> data = km::tests::load_kmx_file(kmxfile.native());
|
||||
ASSERT_GT(data.size(), (size_t)0);
|
||||
|
||||
// Execute
|
||||
auto status = km_core_keyboard_load_from_blob(kmxfile.stem().c_str(), data.data(), data.size(), &this->keyboard);
|
||||
|
||||
// Verify
|
||||
EXPECT_EQ(status, KM_CORE_STATUS_OK);
|
||||
EXPECT_TRUE(this->keyboard != nullptr);
|
||||
}
|
||||
|
||||
TEST_F(KmCoreKeyboardApiTests, LoadFromBlobMock) {
|
||||
// Setup
|
||||
km::core::path kmxfile = "mock_keyboard.mock";
|
||||
std::string blob_string = "MOCK";
|
||||
|
||||
std::vector<uint8_t> data = std::vector<uint8_t>(blob_string.begin(), blob_string.end());
|
||||
ASSERT_GT(data.size(), (size_t)0);
|
||||
|
||||
// Execute
|
||||
auto status = km_core_keyboard_load_from_blob(kmxfile.stem().c_str(), data.data(), data.size(), &this->keyboard);
|
||||
|
||||
// Verify
|
||||
EXPECT_EQ(status, KM_CORE_STATUS_OK);
|
||||
EXPECT_TRUE(this->keyboard != nullptr);
|
||||
}
|
||||
|
||||
TEST_F(KmCoreKeyboardApiTests, LoadFromBlobNull) {
|
||||
// Setup
|
||||
km::core::path kmxfile = "";
|
||||
|
||||
std::unique_ptr<uint8_t> data(new uint8_t[0]);
|
||||
|
||||
// Execute
|
||||
auto status = km_core_keyboard_load_from_blob(kmxfile.stem().c_str(), data.get(), 0, &this->keyboard);
|
||||
|
||||
// Verify
|
||||
EXPECT_EQ(status, KM_CORE_STATUS_INVALID_ARGUMENT);
|
||||
EXPECT_TRUE(this->keyboard == nullptr);
|
||||
}
|
||||
|
||||
TEST_F(KmCoreKeyboardApiTests, LoadFromBlobInvalidKeyboard) {
|
||||
// Setup
|
||||
km::core::path kmxfile = "invalid_keyboard.kmx";
|
||||
std::string blob_string = "KXTS";
|
||||
|
||||
std::vector<uint8_t> data = std::vector<uint8_t>(blob_string.begin(), blob_string.end());
|
||||
for (auto i = data.size(); i < 64; i++) {
|
||||
data.push_back(0);
|
||||
}
|
||||
ASSERT_GT(data.size(), (size_t)0);
|
||||
|
||||
// Execute
|
||||
auto status = km_core_keyboard_load_from_blob(kmxfile.stem().c_str(), data.data(), data.size(), &this->keyboard);
|
||||
|
||||
// Verify
|
||||
EXPECT_EQ(status, KM_CORE_STATUS_INVALID_KEYBOARD);
|
||||
EXPECT_TRUE(this->keyboard == nullptr);
|
||||
}
|
||||
|
||||
// provide our own `main` so that we can get the path of the exe so that
|
||||
// we have a well-defined location to find our test keyboards
|
||||
int main(int argc, char **argv) {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
test_dir = get_wasm_file_path(km::core::path(argv[0]).parent());
|
||||
#else
|
||||
test_dir = km::core::path(argv[0]).parent();
|
||||
#endif
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include <test_assert.h>
|
||||
#include "../emscripten_filesystem.h"
|
||||
#include "../load_kmx_file.hpp"
|
||||
|
||||
const km_core_action_item alert_action_item();
|
||||
const km_core_action_item bksp_action_item(uint8_t type, uintptr_t value);
|
||||
|
|
@ -57,7 +58,9 @@ void setup(const char *keyboard, const km_core_cu* context) {
|
|||
teardown();
|
||||
|
||||
km::core::path path = km::core::path::join(arg_path, keyboard);
|
||||
try_status(km_core_keyboard_load(path.native().c_str(), &test_kb));
|
||||
|
||||
auto blob = km::tests::load_kmx_file(path.native().c_str());
|
||||
try_status(km_core_keyboard_load_from_blob(path.stem().c_str(), blob.data(), blob.size(), &test_kb));
|
||||
try_status(km_core_state_create(test_kb, test_env_opts, &test_state));
|
||||
try_status(context_items_from_utf16(context, &citems));
|
||||
try_status(km_core_context_set(km_core_state_context(test_state), citems));
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
#include <test_assert.h>
|
||||
#include "../emscripten_filesystem.h"
|
||||
#include "../load_kmx_file.hpp"
|
||||
|
||||
using namespace km::core::kmx;
|
||||
|
||||
|
|
@ -54,8 +55,8 @@ void setup(const char *keyboard) {
|
|||
teardown();
|
||||
|
||||
km::core::path path = km::core::path::join(arg_path, keyboard);
|
||||
|
||||
try_status(km_core_keyboard_load(path.native().c_str(), &test_kb));
|
||||
auto blob = km::tests::load_kmx_file(path.native().c_str());
|
||||
try_status(km_core_keyboard_load_from_blob(path.stem().c_str(), blob.data(), blob.size(), &test_kb));
|
||||
try_status(km_core_state_create(test_kb, test_env_opts, &test_state));
|
||||
try_status(context_items_from_utf16(u"Hello 😁", &citems));
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@
|
|||
|
||||
#include "keyman_core.h"
|
||||
#include "path.hpp"
|
||||
|
||||
//#include "keyboard.hpp"
|
||||
#include "mock/mock_processor.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
|
@ -26,14 +25,11 @@ int main(int, char *[])
|
|||
km_core_keyboard_key * kb_key_list = nullptr;
|
||||
km_core_keyboard_imx * kb_imx_list = nullptr;
|
||||
|
||||
try_status(km_core_keyboard_load(test_kb_path.c_str(), &test_kb));
|
||||
test_kb = (km_core_keyboard *)new km::core::mock_processor(test_kb_path);
|
||||
try_status(km_core_keyboard_get_attrs(test_kb, &kb_attrs));
|
||||
try_status(km_core_keyboard_get_key_list(test_kb,&kb_key_list));
|
||||
try_status(km_core_keyboard_get_imx_list(test_kb,&kb_imx_list));
|
||||
|
||||
if (kb_attrs->folder_path != test_kb_path.parent())
|
||||
return __LINE__;
|
||||
|
||||
km_core_keyboard_dispose(test_kb);
|
||||
km_core_keyboard_key_list_dispose(kb_key_list);
|
||||
km_core_keyboard_imx_list_dispose(kb_imx_list);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include "path.hpp"
|
||||
#include "state.hpp"
|
||||
#include "action_items.hpp"
|
||||
#include "mock/mock_processor.hpp"
|
||||
|
||||
#include <test_assert.h>
|
||||
|
||||
|
|
@ -52,7 +53,6 @@ constexpr char const *doc1_expected = u8"\
|
|||
\"$schema\" : \"keyman/core/docs/introspection.schema\",\n\
|
||||
\"keyboard\" : {\n\
|
||||
\"id\" : \"dummy\",\n\
|
||||
\"folder\" : \"\",\n\
|
||||
\"version\" : \"3.145\",\n\
|
||||
\"rules\" : []\n\
|
||||
},\n\
|
||||
|
|
@ -78,7 +78,6 @@ constexpr char const *doc2_expected = u8"\
|
|||
\"$schema\" : \"keyman/core/docs/introspection.schema\",\n\
|
||||
\"keyboard\" : {\n\
|
||||
\"id\" : \"dummy\",\n\
|
||||
\"folder\" : \"\",\n\
|
||||
\"version\" : \"3.145\",\n\
|
||||
\"rules\" : []\n\
|
||||
},\n\
|
||||
|
|
@ -112,7 +111,7 @@ int main(int argc, char * argv[])
|
|||
km_core_keyboard * test_kb = nullptr;
|
||||
km_core_state * test_state = nullptr,
|
||||
* test_clone = nullptr;
|
||||
try_status(km_core_keyboard_load(km::core::path("dummy.mock").c_str(), &test_kb));
|
||||
test_kb = (km_core_keyboard *)new km::core::mock_processor(km::core::path("dummy.mock"));
|
||||
|
||||
// Simple sanity tests.
|
||||
try_status(km_core_state_create(test_kb, test_env_opts, &test_state));
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@
|
|||
|
||||
#include "context.hpp"
|
||||
#include "path.hpp"
|
||||
#include "mock/mock_processor.hpp"
|
||||
|
||||
#include "../emscripten_filesystem.h"
|
||||
#include "../load_kmx_file.hpp"
|
||||
#include <test_assert.h>
|
||||
|
||||
//-------------------------------------------------------------------------------------
|
||||
|
|
@ -41,7 +43,14 @@ setup(const char *keyboard, const km_core_cu *context, bool setup_app_context =
|
|||
teardown();
|
||||
|
||||
km::core::path path = km::core::path::join(arg_path, keyboard);
|
||||
try_status(km_core_keyboard_load(path.native().c_str(), &test_kb));
|
||||
auto blob = km::tests::load_kmx_file(path.native().c_str());
|
||||
const auto mock_extension = ".mock";
|
||||
if (strlen(keyboard) > strlen(mock_extension) && strcmp(keyboard + strlen(keyboard) - strlen(mock_extension), mock_extension) == 0) {
|
||||
km::core::abstract_processor* kp = new km::core::mock_processor(keyboard);
|
||||
test_kb = static_cast<km_core_keyboard*>(kp);
|
||||
} else {
|
||||
try_status(km_core_keyboard_load_from_blob(path.stem().c_str(), blob.data(), blob.size(), &test_kb));
|
||||
}
|
||||
try_status(km_core_state_create(test_kb, test_env_opts, &test_state));
|
||||
try_status(context_items_from_utf16(context, &citems));
|
||||
try_status(km_core_context_set(km_core_state_context(test_state), citems));
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
#include <test_assert.h>
|
||||
#include "../emscripten_filesystem.h"
|
||||
#include "../load_kmx_file.hpp"
|
||||
|
||||
km_core_option_item test_env_opts[] =
|
||||
{
|
||||
|
|
@ -48,7 +49,8 @@ void setup(const km_core_cu *app_context, const km_core_cu *cached_context, int
|
|||
teardown();
|
||||
|
||||
km::core::path path = km::core::path::join(arg_path, "..", "ldml", "keyboards", "k_001_tiny.kmx");
|
||||
try_status(km_core_keyboard_load(path.native().c_str(), &test_kb));
|
||||
auto blob = km::tests::load_kmx_file(path.native().c_str());
|
||||
try_status(km_core_keyboard_load_from_blob(path.stem().c_str(), blob.data(), blob.size(), &test_kb));
|
||||
try_status(km_core_state_create(test_kb, test_env_opts, &test_state));
|
||||
|
||||
try_status(set_context_from_string(km_core_state_context(test_state), cached_context));
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
#include <test_assert.h>
|
||||
#include "../emscripten_filesystem.h"
|
||||
#include "../load_kmx_file.hpp"
|
||||
|
||||
void compare_context(km_core_context *app_context, const km_core_cu* expected_final_app_context);
|
||||
|
||||
|
|
@ -46,7 +47,8 @@ void setup(const km_core_cu *app_context, const km_core_cu *cached_context_strin
|
|||
teardown();
|
||||
|
||||
km::core::path path = km::core::path::join(arg_path, "..", "ldml", "keyboards", "k_001_tiny.kmx");
|
||||
try_status(km_core_keyboard_load(path.native().c_str(), &test_kb));
|
||||
auto blob = km::tests::load_kmx_file(path.native().c_str());
|
||||
try_status(km_core_keyboard_load_from_blob(path.stem().c_str(), blob.data(), blob.size(), &test_kb));
|
||||
try_status(km_core_state_create(test_kb, test_env_opts, &test_state));
|
||||
|
||||
if(cached_context_string) {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
#include <test_assert.h>
|
||||
#include <test_color.h>
|
||||
#include "../emscripten_filesystem.h"
|
||||
#include "../load_kmx_file.hpp"
|
||||
|
||||
#include "kmx_test_source.hpp"
|
||||
|
||||
|
|
@ -191,7 +192,8 @@ run_test(const km::core::path &source, const km::core::path &compiled) {
|
|||
km_core_keyboard * test_kb = nullptr;
|
||||
km_core_state * test_state = nullptr;
|
||||
|
||||
try_status(km_core_keyboard_load(compiled.c_str(), &test_kb));
|
||||
auto blob = km::tests::load_kmx_file(compiled.native().c_str());
|
||||
try_status(km_core_keyboard_load_from_blob(compiled.stem().c_str(), blob.data(), blob.size(), &test_kb));
|
||||
|
||||
// Setup state, environment
|
||||
try_status(km_core_state_create(test_kb, test_env_opts, &test_state));
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include <test_assert.h>
|
||||
#include <test_color.h>
|
||||
#include "../emscripten_filesystem.h"
|
||||
#include "../load_kmx_file.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <iostream>
|
||||
|
|
@ -43,7 +44,8 @@ void test_external_event(const km::core::path &source_file){
|
|||
|
||||
km::core::path full_path = source_file;
|
||||
|
||||
try_status(km_core_keyboard_load(full_path.native().c_str(), &test_kb));
|
||||
auto blob = km::tests::load_kmx_file(full_path.native().c_str());
|
||||
try_status(km_core_keyboard_load_from_blob(full_path.stem().c_str(), blob.data(), blob.size(), &test_kb));
|
||||
|
||||
// Setup state, environment
|
||||
try_status(km_core_state_create(test_kb, test_env_opts, &test_state));
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include <test_assert.h>
|
||||
#include <test_color.h>
|
||||
#include "../emscripten_filesystem.h"
|
||||
#include "../load_kmx_file.hpp"
|
||||
#include "utfcodec.hpp"
|
||||
|
||||
#include <map>
|
||||
|
|
@ -164,7 +165,8 @@ void test_imx_list(const km::core::path &source_file){
|
|||
|
||||
km::core::path full_path = source_file;
|
||||
|
||||
try_status(km_core_keyboard_load(full_path.native().c_str(), &test_kb));
|
||||
auto blob = km::tests::load_kmx_file(full_path.native().c_str());
|
||||
try_status(km_core_keyboard_load_from_blob(full_path.stem().c_str(), blob.data(), blob.size(), &test_kb));
|
||||
|
||||
// Setup state, environment
|
||||
try_status(km_core_state_create(test_kb, test_env_opts, &test_state));
|
||||
|
|
@ -211,7 +213,8 @@ void test_queue_actions (const km::core::path &source_keyboard) {
|
|||
|
||||
km::core::path full_path = source_keyboard;
|
||||
|
||||
try_status(km_core_keyboard_load(full_path.native().c_str(), &test_kb));
|
||||
auto blob = km::tests::load_kmx_file(full_path.native().c_str());
|
||||
try_status(km_core_keyboard_load_from_blob(full_path.stem().c_str(), blob.data(), blob.size(), &test_kb));
|
||||
|
||||
// Setup state, environment
|
||||
try_status(km_core_state_create(test_kb, test_env_opts, &test_state));
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include <test_assert.h>
|
||||
#include <test_color.h>
|
||||
#include "../emscripten_filesystem.h"
|
||||
#include "../load_kmx_file.hpp"
|
||||
|
||||
using namespace km::core::kmx;
|
||||
|
||||
|
|
@ -56,7 +57,8 @@ void test_key_list(const km::core::path &source_file){
|
|||
|
||||
km::core::path full_path = source_file;
|
||||
|
||||
try_status(km_core_keyboard_load(full_path.native().c_str(), &test_kb));
|
||||
auto blob = km::tests::load_kmx_file(full_path.native().c_str());
|
||||
try_status(km_core_keyboard_load_from_blob(full_path.stem().c_str(), blob.data(), blob.size(), &test_kb));
|
||||
|
||||
// Setup state, environment
|
||||
try_status(km_core_state_create(test_kb, test_env_opts, &test_state));
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include <assert.h>
|
||||
|
||||
#include "keyman_core.h"
|
||||
#include "../load_kmx_file.hpp"
|
||||
|
||||
int main(int argc, const char *argv[]) {
|
||||
|
||||
|
|
@ -22,7 +23,8 @@ int main(int argc, const char *argv[]) {
|
|||
|
||||
km_core_status status;
|
||||
km_core_path_name nowhere = {0}; // this is a narrow or wide char string
|
||||
status = km_core_keyboard_load(nowhere, &test_kb);
|
||||
auto blob = km::tests::load_kmx_file(nowhere);
|
||||
status = km_core_keyboard_load_from_blob(nowhere, blob.data(), blob.size(), &test_kb);
|
||||
|
||||
std::cerr << "null km_core_keyboard_load = " << status << std::endl;
|
||||
assert(status == KM_CORE_STATUS_INVALID_ARGUMENT);
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
#include "ldml/ldml_markers.hpp"
|
||||
|
||||
#include "processor.hpp"
|
||||
#include "../load_kmx_file.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
|
|
@ -273,7 +274,8 @@ run_test(const km::core::path &source, const km::core::path &compiled, km::tests
|
|||
km_core_state * test_state = nullptr;
|
||||
|
||||
const km_core_status expect_load_status = test_source.get_expected_load_status();
|
||||
assert_equal(km_core_keyboard_load(compiled.c_str(), &test_kb), expect_load_status);
|
||||
auto blob = km::tests::load_kmx_file(compiled.native().c_str());
|
||||
assert_equal(km_core_keyboard_load_from_blob(compiled.stem().c_str(), blob.data(), blob.size(), &test_kb), expect_load_status);
|
||||
|
||||
if (expect_load_status != KM_CORE_STATUS_OK) {
|
||||
std::cout << "Keyboard was expected to be invalid, so exiting " << std::endl;
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
#include "unicode/uniset.h"
|
||||
#include "unicode/usetiter.h"
|
||||
|
||||
#include "../load_kmx_file.hpp"
|
||||
#include <test_color.h>
|
||||
|
||||
#define assert_or_return(expr) if(!(expr)) { \
|
||||
|
|
@ -56,8 +57,6 @@ namespace km {
|
|||
namespace tests {
|
||||
|
||||
|
||||
|
||||
|
||||
/** string munging */
|
||||
static void append_to_str(std::u16string &str, const char *buf) {
|
||||
const PKMX_WCHAR p = km::core::kmx::strtowstr((char *)buf); /** cast away const, unused*/
|
||||
|
|
@ -757,7 +756,8 @@ int LdmlJsonTestSourceFactory::load(const km::core::path &compiled, const km::co
|
|||
}
|
||||
|
||||
// check and load the KMX (yes, once again)
|
||||
if(!km::core::ldml_processor::is_kmxplus_file(compiled, rawdata)) {
|
||||
rawdata = km::tests::load_kmx_file(compiled);
|
||||
if (!km::core::ldml_processor::is_handled(rawdata)) {
|
||||
std::cerr << "Reading KMX for test purposes failed: " << compiled << std::endl;
|
||||
return __LINE__;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,10 @@ ldml = executable('ldml',
|
|||
)
|
||||
|
||||
core_ldml_min = executable('core_ldml_min',
|
||||
['core_ldml_min.cpp'],
|
||||
[
|
||||
'core_ldml_min.cpp',
|
||||
meson.current_source_dir() / '../load_kmx_file.cpp',
|
||||
],
|
||||
cpp_args: defns + warns,
|
||||
include_directories: [inc, libsrc],
|
||||
link_args: links,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
#include <test_assert.h>
|
||||
#include "../emscripten_filesystem.h"
|
||||
#include "../load_kmx_file.hpp"
|
||||
|
||||
//-------------------------------------------------------------------------------------
|
||||
// Context normalization tests
|
||||
|
|
@ -42,7 +43,8 @@ void setup(const char *keyboard) {
|
|||
teardown();
|
||||
|
||||
km::core::path path = km::core::path::join(arg_path, "keyboards", keyboard);
|
||||
try_status(km_core_keyboard_load(path.native().c_str(), &test_kb));
|
||||
auto blob = km::tests::load_kmx_file(path.native().c_str());
|
||||
try_status(km_core_keyboard_load_from_blob(path.stem().c_str(), blob.data(), blob.size(), &test_kb));
|
||||
try_status(km_core_state_create(test_kb, test_env_opts, &test_state));
|
||||
}
|
||||
|
||||
|
|
|
|||
34
core/tests/unit/load_kmx_file.cpp
Normal file
34
core/tests/unit/load_kmx_file.cpp
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <string>
|
||||
|
||||
#include "kmx_file.h"
|
||||
#include "path.hpp"
|
||||
#include "utfcodec.hpp"
|
||||
|
||||
namespace km::tests {
|
||||
|
||||
std::vector<uint8_t> load_kmx_file(km::core::path const& kb_path) {
|
||||
std::vector<uint8_t> data;
|
||||
std::ifstream file(static_cast<std::string>(kb_path), std::ios::binary | std::ios::ate);
|
||||
if (file.fail()) {
|
||||
return std::vector<uint8_t>();
|
||||
}
|
||||
const std::streamsize size = file.tellg();
|
||||
if (size >= KMX_MAX_ALLOWED_FILE_SIZE) {
|
||||
return std::vector<uint8_t>();
|
||||
}
|
||||
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
data.resize((size_t)size);
|
||||
if (!file.read((char*)data.data(), size)) {
|
||||
return std::vector<uint8_t>();
|
||||
}
|
||||
|
||||
file.close();
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
14
core/tests/unit/load_kmx_file.hpp
Normal file
14
core/tests/unit/load_kmx_file.hpp
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#ifndef __LOAD_KMX_FILE_HPP__
|
||||
#define __LOAD_KMX_FILE_HPP__
|
||||
|
||||
#include "path.hpp"
|
||||
|
||||
namespace km {
|
||||
namespace tests {
|
||||
|
||||
std::vector<uint8_t> load_kmx_file(km::core::path const& kb_path);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // __LOAD_KMX_FILE_HPP__
|
||||
|
|
@ -1,13 +1,41 @@
|
|||
node = find_program('node', required: true)
|
||||
|
||||
common_test_files = [
|
||||
gtest = subproject('gtest')
|
||||
gtest_dep = gtest.get_variable('gtest_dep')
|
||||
gmock_dep = gtest.get_variable('gmock_dep')
|
||||
|
||||
test_util_files = [
|
||||
meson.current_source_dir() / 'emscripten_filesystem.cpp',
|
||||
meson.current_source_dir() / 'load_kmx_file.cpp',
|
||||
]
|
||||
|
||||
common_test_files = [
|
||||
test_util_files,
|
||||
meson.global_source_root() / '../common/include/test_color.cpp'
|
||||
]
|
||||
|
||||
hextobin_root = meson.global_source_root() / '../common/tools/hextobin/build/hextobin.js'
|
||||
hextobin_cmd = [node, hextobin_root]
|
||||
|
||||
if cpp_compiler.get_id() == 'emscripten'
|
||||
extra_link_args = [ '-lnodefs.js' ]
|
||||
else
|
||||
extra_link_args = []
|
||||
endif
|
||||
|
||||
kmcorekeyboardapitests = executable('km_core_keyboard_api.tests',
|
||||
[
|
||||
'km_core_keyboard_api.tests.cpp',
|
||||
test_util_files,
|
||||
],
|
||||
include_directories: [inc, libsrc],
|
||||
link_args: [ links, extra_link_args ],
|
||||
dependencies: [icu_uc, icu_i18n, gtest_dep, gmock_dep],
|
||||
objects: lib.extract_all_objects(recursive: false),
|
||||
)
|
||||
|
||||
test('km-core-keyboard-api-tests', kmcorekeyboardapitests)
|
||||
|
||||
subdir('json')
|
||||
subdir('utftest')
|
||||
subdir('kmnkbd')
|
||||
|
|
|
|||
|
|
@ -6,5 +6,5 @@
|
|||
|
||||
e = executable('utftest', 'utftest.cpp',
|
||||
objects: lib.extract_objects('../../common/cpp/utfcodec.cpp'),
|
||||
include_directories: [libsrc])
|
||||
include_directories: [inc, libsrc])
|
||||
test('utftest', e)
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ https://help.keyman.com/developer/engine/android/latest-version/
|
|||
| KEYMAN_MIN_TARGET_VERSION_WINDOWS | 10 |
|
||||
| KEYMAN_MIN_VERSION_ANDROID_SDK | 21 |
|
||||
| KEYMAN_MIN_VERSION_CPP | 17 |
|
||||
| KEYMAN_MIN_VERSION_EMSCRIPTEN | 3.1.58 |
|
||||
| KEYMAN_MIN_VERSION_EMSCRIPTEN | 3.1.64 |
|
||||
| KEYMAN_MIN_VERSION_MESON | 1.0.0 |
|
||||
| KEYMAN_MIN_VERSION_NODE_MAJOR | 20 |
|
||||
| KEYMAN_MIN_VERSION_NPM | 10.5.1 |
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ libkeymancore.so.2 libkeymancore2 #MINVER#
|
|||
km_core_keyboard_imx_list_dispose@Base 17.0.195
|
||||
km_core_keyboard_key_list_dispose@Base 17.0.195
|
||||
km_core_keyboard_load@Base 17.0.195
|
||||
km_core_keyboard_load_from_blob@Base 18.0.123
|
||||
km_core_options_list_size@Base 17.0.195
|
||||
km_core_process_event@Base 17.0.195
|
||||
km_core_process_queued_actions@Base 17.0.195
|
||||
|
|
|
|||
2
package-lock.json
generated
2
package-lock.json
generated
|
|
@ -8761,6 +8761,7 @@
|
|||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.20.0.tgz",
|
||||
"integrity": "sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
|
|
@ -15647,6 +15648,7 @@
|
|||
"@sentry/cli": "^2.31.0",
|
||||
"@zip.js/zip.js": "^2.7.32",
|
||||
"c8": "^7.12.0",
|
||||
"express": "^4.19.2",
|
||||
"jsdom": "^23.0.1",
|
||||
"mocha": "^10.0.0",
|
||||
"tsx": "^4.19.0"
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ KEYMAN_MIN_TARGET_VERSION_WEB_SAFARI=13.0 # iOS 13.0, macOS 10.13.6+
|
|||
# Dependency versions
|
||||
KEYMAN_MIN_VERSION_NODE_MAJOR=20 # node version source of truth is /package.json:/engines/node; use KEYMAN_USE_NVM to automatically update
|
||||
KEYMAN_MIN_VERSION_NPM=10.5.1 # 10.5.0 has bug, discussed in #10350
|
||||
KEYMAN_MIN_VERSION_EMSCRIPTEN=3.1.58 # Use KEYMAN_USE_EMSDK to automatically update to this version
|
||||
KEYMAN_MIN_VERSION_EMSCRIPTEN=3.1.64 # Use KEYMAN_USE_EMSDK to automatically update to this version
|
||||
KEYMAN_MIN_VERSION_VISUAL_STUDIO=2019
|
||||
KEYMAN_MIN_VERSION_MESON=1.0.0
|
||||
|
||||
|
|
|
|||
|
|
@ -29,8 +29,9 @@ src/test/auto A Node-driven test suite for automated testing of Key
|
|||
|
||||
## Usage
|
||||
|
||||
Open **index.html** or **samples/index.html** in your browser. Be sure to
|
||||
compile Keyman Engine for Web before viewing the pages.
|
||||
Start the test server by running `./build.sh start`, then open
|
||||
your browser to http://localhost:3000. Be sure to compile Keyman Engine
|
||||
for Web before viewing the pages.
|
||||
|
||||
Refer to the samples for usage details.
|
||||
|
||||
|
|
|
|||
|
|
@ -20,12 +20,14 @@ builder_describe "Builds engine modules for Keyman Engine for Web (KMW)." \
|
|||
"clean" \
|
||||
"configure" \
|
||||
"build" \
|
||||
"start Starts the test server" \
|
||||
"test" \
|
||||
"coverage Create an HTML page with code coverage" \
|
||||
":app/browser The form of Keyman Engine for Web for use on websites" \
|
||||
":app/webview A puppetable version of KMW designed for use in a host app's WebView" \
|
||||
":app/ui Builds KMW's desktop form-factor keyboard-selection UI modules" \
|
||||
":engine/attachment Subset used for detecting valid page contexts for use in text editing " \
|
||||
":engine/core-processor Keyman Core WASM integration" \
|
||||
":engine/dom-utils A common subset of function used for DOM calculations, layout, etc" \
|
||||
":engine/events Specialized classes utilized to support KMW API events" \
|
||||
":engine/element-wrappers Subset used to integrate with website elements" \
|
||||
|
|
@ -60,6 +62,7 @@ builder_describe_outputs \
|
|||
build:app/webview "/web/build/app/webview/${config}/keymanweb-webview.js" \
|
||||
build:app/ui "/web/build/app/ui/${config}/kmwuitoggle.js" \
|
||||
build:engine/attachment "/web/build/engine/attachment/lib/index.mjs" \
|
||||
build:engine/core-processor "/web/build/engine/core-processor/lib/index.mjs" \
|
||||
build:engine/dom-utils "/web/build/engine/dom-utils/obj/index.js" \
|
||||
build:engine/events "/web/build/engine/events/lib/index.mjs" \
|
||||
build:engine/element-wrappers "/web/build/engine/element-wrappers/lib/index.mjs" \
|
||||
|
|
@ -166,6 +169,8 @@ builder_run_child_actions build:engine/attachment
|
|||
# Uses engine/interfaces (due to resource-path config interface)
|
||||
builder_run_child_actions build:engine/keyboard-storage
|
||||
|
||||
builder_run_child_actions build:engine/core-processor
|
||||
|
||||
# Uses engine/interfaces, engine/keyboard-storage, & engine/osk
|
||||
builder_run_child_actions build:engine/main
|
||||
|
||||
|
|
@ -201,3 +206,6 @@ builder_run_action test:help do_test_help
|
|||
|
||||
# Create coverage report
|
||||
builder_run_action coverage:_all coverage_action
|
||||
|
||||
# Start the test server
|
||||
builder_run_action start node src/tools/testing/test-server/index.cjs
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@
|
|||
"types": "./build/engine/attachment/obj/index.d.ts",
|
||||
"import": "./build/engine/attachment/obj/index.js"
|
||||
},
|
||||
"./engine/interfaces": {
|
||||
"es6-bundling": "./src/engine/interfaces/src/index.ts",
|
||||
"types": "./build/engine/interfaces/obj/index.d.ts",
|
||||
"import": "./build/engine/interfaces/obj/index.js"
|
||||
"./engine/core-processor": {
|
||||
"es6-bundling": "./src/engine/core-processor/src/index.ts",
|
||||
"types": "./build/engine/core-processor/obj/index.d.ts",
|
||||
"import": "./build/engine/core-processor/obj/index.js"
|
||||
},
|
||||
"./engine/dom-utils": {
|
||||
"es6-bundling": "./src/engine/dom-utils/src/index.ts",
|
||||
|
|
@ -32,6 +32,11 @@
|
|||
"types": "./build/engine/events/obj/index.d.ts",
|
||||
"import": "./build/engine/events/obj/index.js"
|
||||
},
|
||||
"./engine/interfaces": {
|
||||
"es6-bundling": "./src/engine/interfaces/src/index.ts",
|
||||
"types": "./build/engine/interfaces/obj/index.d.ts",
|
||||
"import": "./build/engine/interfaces/obj/index.js"
|
||||
},
|
||||
"./engine/js-processor": {
|
||||
"es6-bundling": "./src/engine/js-processor/src/index.ts",
|
||||
"types": "./build/engine/js-processor/obj/index.d.ts",
|
||||
|
|
@ -81,6 +86,10 @@
|
|||
"./engine/osk/internals": {
|
||||
"types": "./build/engine/osk/obj/test-index.d.ts",
|
||||
"import": "./build/engine/osk/obj/test-index.js"
|
||||
},
|
||||
"./tools/testing/test-utils": {
|
||||
"types": "./build/tools/testing/test-utils/obj/index.d.ts",
|
||||
"import": "./build/tools/testing/test-utils/obj/index.js"
|
||||
}
|
||||
},
|
||||
"imports": {
|
||||
|
|
@ -108,6 +117,7 @@
|
|||
"@sentry/cli": "^2.31.0",
|
||||
"@zip.js/zip.js": "^2.7.32",
|
||||
"c8": "^7.12.0",
|
||||
"express": "^4.19.2",
|
||||
"jsdom": "^23.0.1",
|
||||
"mocha": "^10.0.0",
|
||||
"tsx": "^4.19.0"
|
||||
|
|
|
|||
|
|
@ -72,6 +72,10 @@ compile_and_copy() {
|
|||
mkdir -p "$KEYMAN_ROOT/web/build/app/resources/osk"
|
||||
cp -R "$KEYMAN_ROOT/web/src/resources/osk/." "$KEYMAN_ROOT/web/build/app/resources/osk/"
|
||||
|
||||
# Copy Keyman Core build artifacts for local reference
|
||||
cp "${KEYMAN_ROOT}/web/build/engine/core-processor/obj/import/core/"km-core.{js,wasm} "${KEYMAN_ROOT}/web/build/app/browser/debug/"
|
||||
cp "${KEYMAN_ROOT}/web/build/engine/core-processor/obj/import/core/"km-core.{js,wasm} "${KEYMAN_ROOT}/web/build/app/browser/release/"
|
||||
|
||||
# Update the build/publish copy of our build artifacts
|
||||
prepare
|
||||
|
||||
|
|
|
|||
|
|
@ -60,8 +60,16 @@ compile_and_copy() {
|
|||
mkdir -p "$KEYMAN_ROOT/web/build/app/resources/osk"
|
||||
cp -R "$KEYMAN_ROOT/web/src/resources/osk/." "$KEYMAN_ROOT/web/build/app/resources/osk/"
|
||||
|
||||
# Copy Keyman Core build artifacts for local reference
|
||||
cp "${KEYMAN_ROOT}/web/build/engine/core-processor/obj/import/core/"km-core.{js,wasm} "${KEYMAN_ROOT}/web/build/app/webview/debug/"
|
||||
cp "${KEYMAN_ROOT}/web/build/engine/core-processor/obj/import/core/"km-core.{js,wasm} "${KEYMAN_ROOT}/web/build/app/webview/release/"
|
||||
|
||||
# Clean the sourcemaps of .. and . components
|
||||
for script in "$KEYMAN_ROOT/web/build/$SUBPROJECT_NAME/debug/"*.js; do
|
||||
if [[ "${script}" == *"/km-core.js" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
sourcemap="$script.map"
|
||||
node "$KEYMAN_ROOT/web/build/tools/building/sourcemap-root/index.js" \
|
||||
"$script" "$sourcemap" --clean --inline
|
||||
|
|
@ -70,6 +78,9 @@ compile_and_copy() {
|
|||
# Do NOT inline sourcemaps for release builds - we don't want them to affect
|
||||
# load time.
|
||||
for script in "$KEYMAN_ROOT/web/build/$SUBPROJECT_NAME/release/"*.js; do
|
||||
if [[ "${script}" == *"/km-core.js" ]]; then
|
||||
continue
|
||||
fi
|
||||
sourcemap="$script.map"
|
||||
node "$KEYMAN_ROOT/web/build/tools/building/sourcemap-root/index.js" \
|
||||
"$script" "$sourcemap" --clean
|
||||
|
|
|
|||
1
web/src/engine/core-processor/.gitignore
vendored
Normal file
1
web/src/engine/core-processor/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
src/import/
|
||||
64
web/src/engine/core-processor/build.sh
Executable file
64
web/src/engine/core-processor/build.sh
Executable file
|
|
@ -0,0 +1,64 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
## START STANDARD BUILD SCRIPT INCLUDE
|
||||
# adjust relative paths as necessary
|
||||
THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
||||
. "${THIS_SCRIPT%/*}/../../../../resources/build/builder.inc.sh"
|
||||
## END STANDARD BUILD SCRIPT INCLUDE
|
||||
|
||||
SUBPROJECT_NAME=engine/core-processor
|
||||
|
||||
. "${KEYMAN_ROOT}/web/common.inc.sh"
|
||||
. "${KEYMAN_ROOT}/resources/shellHelperFunctions.sh"
|
||||
|
||||
# ################################ Main script ################################
|
||||
|
||||
builder_describe "Keyman Core WASM integration" \
|
||||
"@/core:wasm" \
|
||||
"@/web/src/engine/common/web-utils" \
|
||||
"clean" \
|
||||
"configure" \
|
||||
"build" \
|
||||
"test" \
|
||||
"--ci+ Set to utilize CI-based test configurations & reporting."
|
||||
|
||||
builder_describe_outputs \
|
||||
configure "/web/src/engine/core-processor/src/import/core/km-core-interface.d.ts" \
|
||||
build "/web/build/${SUBPROJECT_NAME}/lib/index.mjs"
|
||||
|
||||
builder_parse "$@"
|
||||
|
||||
#### Build action definitions ####
|
||||
|
||||
do_clean() {
|
||||
rm -rf "${KEYMAN_ROOT}/web/build/${SUBPROJECT_NAME}"
|
||||
rm -rf "src/import/"
|
||||
}
|
||||
|
||||
do_configure() {
|
||||
verify_npm_setup
|
||||
|
||||
mkdir -p "src/import/core/"
|
||||
# we don't need this file for release builds, but it's nice to have
|
||||
# for reference and auto-completion
|
||||
cp "${KEYMAN_ROOT}/core/build/wasm/${BUILDER_CONFIGURATION}/src/km-core-interface.d.ts" "src/import/core/"
|
||||
}
|
||||
|
||||
copy_deps() {
|
||||
mkdir -p "${KEYMAN_ROOT}/web/build/${SUBPROJECT_NAME}/obj/import/core/"
|
||||
cp "${KEYMAN_ROOT}/core/build/wasm/${BUILDER_CONFIGURATION}/src/"km-core-interface.d.ts "${KEYMAN_ROOT}/web/build/${SUBPROJECT_NAME}/obj/import/core/"
|
||||
cp "${KEYMAN_ROOT}/core/build/wasm/${BUILDER_CONFIGURATION}/src/"km-core{.js,.wasm} "${KEYMAN_ROOT}/web/build/${SUBPROJECT_NAME}/obj/import/core/"
|
||||
}
|
||||
|
||||
do_build () {
|
||||
copy_deps
|
||||
compile "${SUBPROJECT_NAME}"
|
||||
|
||||
${BUNDLE_CMD} "${KEYMAN_ROOT}/web/build/${SUBPROJECT_NAME}/obj/index.js" \
|
||||
--out "${KEYMAN_ROOT}/web/build/${SUBPROJECT_NAME}/lib/index.mjs" \
|
||||
--format esm
|
||||
}
|
||||
|
||||
builder_run_action clean do_clean
|
||||
builder_run_action configure do_configure
|
||||
builder_run_action build do_build
|
||||
30
web/src/engine/core-processor/src/core-processor.ts
Normal file
30
web/src/engine/core-processor/src/core-processor.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
type km_core_attr = import('./import/core/km-core-interface.js').km_core_attr;
|
||||
|
||||
export class CoreProcessor {
|
||||
private instance: any;
|
||||
|
||||
/**
|
||||
* Initialize Core Processor
|
||||
* @param baseurl - The url where km-core.js is located
|
||||
*/
|
||||
public async init(baseurl: string): Promise<boolean> {
|
||||
|
||||
if (!this.instance) {
|
||||
try {
|
||||
const module = await import(baseurl + '/km-core.js');
|
||||
this.instance = await module.default({
|
||||
locateFile: function (path: string, scriptDirectory: string) {
|
||||
return baseurl + '/' + path;
|
||||
}
|
||||
});
|
||||
} catch (e: any) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return !!this.instance;
|
||||
};
|
||||
|
||||
public tmp_wasm_attributes(): km_core_attr {
|
||||
return this.instance.tmp_wasm_attributes();
|
||||
}
|
||||
}
|
||||
1
web/src/engine/core-processor/src/index.ts
Normal file
1
web/src/engine/core-processor/src/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from './core-processor.js';
|
||||
13
web/src/engine/core-processor/tsconfig.json
Normal file
13
web/src/engine/core-processor/tsconfig.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
// While the actual references themselves are headless, it compiles against the DOM-reliant OSK module.
|
||||
"extends": "../../tsconfig.dom.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"outDir": "../../../build/engine/core-processor/obj/",
|
||||
"tsBuildInfoFile": "../../../build/engine/core-processor/obj/tsconfig.tsbuildinfo",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
|
||||
"include": [ "**/*.ts", "src/import/core/km-core.js" ],
|
||||
}
|
||||
|
|
@ -106,6 +106,10 @@ export default class PathConfiguration implements OSKResourcePathConfiguration {
|
|||
return this._root;
|
||||
}
|
||||
|
||||
get basePath(): string {
|
||||
return this.sourcePath;
|
||||
}
|
||||
|
||||
get resources(): string {
|
||||
return this._resources;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,12 @@ do_build () {
|
|||
--format esm
|
||||
}
|
||||
|
||||
do_test() {
|
||||
test-headless "${SUBPROJECT_NAME}" ""
|
||||
test-headless-typescript "${SUBPROJECT_NAME}"
|
||||
}
|
||||
|
||||
builder_run_action configure verify_npm_setup
|
||||
builder_run_action clean rm -rf "${KEYMAN_ROOT}/web/build/${SUBPROJECT_NAME}"
|
||||
builder_run_action build do_build
|
||||
builder_run_action test test-headless "${SUBPROJECT_NAME}" ""
|
||||
builder_run_action test do_test
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ function do_configure() {
|
|||
|
||||
BUILD_DIR="${KEYMAN_ROOT}/web/build/${SUBPROJECT_NAME}"
|
||||
|
||||
function do_build() {
|
||||
do_build() {
|
||||
tsc --build "${THIS_SCRIPT_PATH}/tsconfig.all.json"
|
||||
|
||||
# Base product - the main keyboard processor
|
||||
|
|
@ -73,7 +73,12 @@ function do_build() {
|
|||
tsc --emitDeclarationOnly --outFile "${BUILD_DIR}/lib/node-keyboard-loader.d.ts" -p src/keyboards/loaders/tsconfig.node.json
|
||||
}
|
||||
|
||||
do_test() {
|
||||
test-headless "${SUBPROJECT_NAME}" ""
|
||||
test-headless-typescript "${SUBPROJECT_NAME}"
|
||||
}
|
||||
|
||||
builder_run_action configure do_configure
|
||||
builder_run_action clean rm -rf "${BUILD_DIR}"
|
||||
builder_run_action build do_build
|
||||
builder_run_action test test-headless "${SUBPROJECT_NAME}" ""
|
||||
builder_run_action test do_test
|
||||
|
|
|
|||
|
|
@ -3,12 +3,8 @@ export * from "./keyboards/defaultLayouts.js";
|
|||
export { default as Keyboard } from "./keyboards/keyboard.js";
|
||||
export * from "./keyboards/keyboard.js";
|
||||
export { KeyboardHarness, KeyboardKeymanGlobal, MinimalCodesInterface, MinimalKeymanGlobal } from "./keyboards/keyboardHarness.js";
|
||||
export {
|
||||
default as KeyboardLoaderBase,
|
||||
KeyboardLoadErrorBuilder,
|
||||
KeyboardMissingError,
|
||||
KeyboardScriptError
|
||||
} from "./keyboards/keyboardLoaderBase.js";
|
||||
export { KeyboardLoaderBase } from "./keyboards/keyboardLoaderBase.js";
|
||||
export { KeyboardLoadErrorBuilder, KeyboardMissingError, KeyboardScriptError, KeyboardDownloadError, InvalidKeyboardError } from './keyboards/keyboardLoadError.js'
|
||||
export {
|
||||
CloudKeyboardFont,
|
||||
internalizeFont,
|
||||
|
|
|
|||
104
web/src/engine/keyboard/src/keyboards/keyboardLoadError.ts
Normal file
104
web/src/engine/keyboard/src/keyboards/keyboardLoadError.ts
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
import { type KeyboardStub } from './keyboardLoaderBase.js';
|
||||
|
||||
export interface KeyboardLoadErrorBuilder {
|
||||
scriptError(err?: Error): void;
|
||||
missingError(err: Error): void;
|
||||
keyboardDownloadError(err: Error): void;
|
||||
|
||||
invalidKeyboard(err: Error): void;
|
||||
}
|
||||
|
||||
export class KeyboardScriptError extends Error {
|
||||
public readonly cause;
|
||||
|
||||
constructor(msg: string, cause?: Error) {
|
||||
super(msg);
|
||||
this.cause = cause;
|
||||
}
|
||||
}
|
||||
|
||||
export class KeyboardMissingError extends Error {
|
||||
public readonly cause;
|
||||
|
||||
constructor(msg: string, cause?: Error) {
|
||||
super(msg);
|
||||
this.cause = cause;
|
||||
}
|
||||
}
|
||||
|
||||
export class KeyboardDownloadError extends Error {
|
||||
public readonly cause;
|
||||
|
||||
constructor(message: string, cause?: Error) {
|
||||
super(message);
|
||||
this.cause = cause;
|
||||
}
|
||||
}
|
||||
|
||||
export class InvalidKeyboardError extends Error {
|
||||
public readonly cause;
|
||||
|
||||
constructor(message: string, cause?: Error) {
|
||||
super(message);
|
||||
this.cause = cause;
|
||||
}
|
||||
}
|
||||
|
||||
export class UriBasedErrorBuilder implements KeyboardLoadErrorBuilder {
|
||||
readonly uri: string;
|
||||
|
||||
constructor(uri: string) {
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
missingError(err: Error) {
|
||||
const msg = `Cannot find the keyboard at ${this.uri}.`;
|
||||
return new KeyboardMissingError(msg, err);
|
||||
}
|
||||
|
||||
scriptError(err: Error) {
|
||||
const msg = `Error registering the keyboard script at ${this.uri}; it may contain an error.`;
|
||||
return new KeyboardScriptError(msg, err);
|
||||
}
|
||||
|
||||
keyboardDownloadError(err: Error) {
|
||||
const msg = `Unable to download keyboard at ${this.uri}`;
|
||||
return new KeyboardDownloadError(msg, err);
|
||||
}
|
||||
|
||||
invalidKeyboard(err: Error) {
|
||||
const msg = `${this.uri} is not a valid keyboard file`;
|
||||
return new InvalidKeyboardError(msg, err);
|
||||
}
|
||||
}
|
||||
|
||||
export class StubBasedErrorBuilder implements KeyboardLoadErrorBuilder {
|
||||
readonly stub: KeyboardStub;
|
||||
|
||||
constructor(stub: KeyboardStub) {
|
||||
this.stub = stub;
|
||||
}
|
||||
|
||||
missingError(err: Error) {
|
||||
const stub = this.stub;
|
||||
const msg = `Cannot find the ${stub.name} keyboard for ${stub.langName} at ${stub.filename}.`;
|
||||
return new KeyboardMissingError(msg, err);
|
||||
}
|
||||
|
||||
scriptError(err: Error) {
|
||||
const stub = this.stub;
|
||||
const msg = `Error registering the ${stub.name} keyboard for ${stub.langName}; keyboard script at ${stub.filename} may contain an error.`;
|
||||
return new KeyboardScriptError(msg, err);
|
||||
}
|
||||
|
||||
keyboardDownloadError(err: Error) {
|
||||
const msg = `Unable to download ${this.stub.name} keyboard for ${this.stub.langName}`;
|
||||
return new KeyboardDownloadError(msg, err);
|
||||
}
|
||||
|
||||
invalidKeyboard(err: Error) {
|
||||
const msg = `${this.stub.name} is not a valid keyboard`;
|
||||
return new InvalidKeyboardError(msg, err);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,71 +1,11 @@
|
|||
import Keyboard from "./keyboard.js";
|
||||
import { KeyboardHarness } from "./keyboardHarness.js";
|
||||
import KeyboardProperties from "./keyboardProperties.js";
|
||||
import { KeyboardLoadErrorBuilder, StubBasedErrorBuilder, UriBasedErrorBuilder } from './keyboardLoadError.js';
|
||||
|
||||
type KeyboardStub = KeyboardProperties & { filename: string };
|
||||
export type KeyboardStub = KeyboardProperties & { filename: string };
|
||||
|
||||
export interface KeyboardLoadErrorBuilder {
|
||||
scriptError(err?: Error): void;
|
||||
missingError(err: Error): void;
|
||||
}
|
||||
|
||||
export class KeyboardScriptError extends Error {
|
||||
public readonly cause;
|
||||
|
||||
constructor(msg: string, cause?: Error) {
|
||||
super(msg);
|
||||
this.cause = cause;
|
||||
}
|
||||
}
|
||||
|
||||
export class KeyboardMissingError extends Error {
|
||||
public readonly cause;
|
||||
|
||||
constructor(msg: string, cause?: Error) {
|
||||
super(msg);
|
||||
this.cause = cause;
|
||||
}
|
||||
}
|
||||
|
||||
class UriBasedErrorBuilder implements KeyboardLoadErrorBuilder {
|
||||
readonly uri: string;
|
||||
|
||||
constructor(uri: string) {
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
missingError(err: Error) {
|
||||
const msg = `Cannot find the keyboard at ${this.uri}.`;
|
||||
return new KeyboardMissingError(msg, err);
|
||||
}
|
||||
|
||||
scriptError(err: Error) {
|
||||
const msg = `Error registering the keyboard script at ${this.uri}; it may contain an error.`;
|
||||
return new KeyboardScriptError(msg, err);
|
||||
}
|
||||
}
|
||||
|
||||
class StubBasedErrorBuilder implements KeyboardLoadErrorBuilder {
|
||||
readonly stub: KeyboardStub;
|
||||
|
||||
constructor(stub: KeyboardStub) {
|
||||
this.stub = stub;
|
||||
}
|
||||
|
||||
missingError(err: Error) {
|
||||
const stub = this.stub;
|
||||
const msg = `Cannot find the ${stub.name} keyboard for ${stub.langName} at ${stub.filename}.`;
|
||||
return new KeyboardMissingError(msg, err);
|
||||
}
|
||||
|
||||
scriptError(err: Error) {
|
||||
const stub = this.stub;
|
||||
const msg = `Error registering the ${stub.name} keyboard for ${stub.langName}; keyboard script at ${stub.filename} may contain an error.`;
|
||||
return new KeyboardScriptError(msg, err);
|
||||
}
|
||||
}
|
||||
|
||||
export default abstract class KeyboardLoaderBase {
|
||||
export abstract class KeyboardLoaderBase {
|
||||
private _harness: KeyboardHarness;
|
||||
|
||||
public get harness(): KeyboardHarness {
|
||||
|
|
@ -76,23 +16,49 @@ export default abstract class KeyboardLoaderBase {
|
|||
this._harness = harness;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a keyboard from a remote or local URI.
|
||||
*
|
||||
* @param uri The URI of the keyboard to load.
|
||||
* @returns A Promise that resolves to the loaded keyboard.
|
||||
*/
|
||||
public loadKeyboardFromPath(uri: string): Promise<Keyboard> {
|
||||
this.harness.install();
|
||||
const promise = this.loadKeyboardInternal(uri, new UriBasedErrorBuilder(uri));
|
||||
|
||||
return promise;
|
||||
return this.loadKeyboardInternal(uri, new UriBasedErrorBuilder(uri));
|
||||
}
|
||||
|
||||
public loadKeyboardFromStub(stub: KeyboardStub) {
|
||||
/**
|
||||
* Load a keyboard from keyboard stub.
|
||||
*
|
||||
* @param stub The stub of the keyboard to load.
|
||||
* @returns A Promise that resolves to the loaded keyboard.
|
||||
*/
|
||||
public async loadKeyboardFromStub(stub: KeyboardStub): Promise<Keyboard> {
|
||||
this.harness.install();
|
||||
let promise = this.loadKeyboardInternal(stub.filename, new StubBasedErrorBuilder(stub), stub.id);
|
||||
|
||||
return promise;
|
||||
return this.loadKeyboardInternal(stub.filename, new StubBasedErrorBuilder(stub));
|
||||
}
|
||||
|
||||
protected abstract loadKeyboardInternal(
|
||||
uri: string,
|
||||
errorBuilder: KeyboardLoadErrorBuilder,
|
||||
id?: string
|
||||
): Promise<Keyboard>;
|
||||
private async loadKeyboardInternal(uri: string, errorBuilder: KeyboardLoadErrorBuilder): Promise<Keyboard> {
|
||||
const byteArray = await this.loadKeyboardBlob(uri, errorBuilder);
|
||||
|
||||
if (byteArray.slice(0, 4) == Uint8Array.from([0x4b, 0x58, 0x54, 0x53])) { // 'KXTS'
|
||||
// KMX or LDML (KMX+) keyboard
|
||||
console.error("KMX keyboard loading is not yet implemented!");
|
||||
return null;
|
||||
}
|
||||
|
||||
let script: string;
|
||||
try {
|
||||
script = new TextDecoder('utf-8', { fatal: true }).decode(byteArray);
|
||||
} catch (e) {
|
||||
throw errorBuilder.invalidKeyboard(e);
|
||||
}
|
||||
|
||||
// .js keyboard
|
||||
return await this.loadKeyboardFromScript(script, errorBuilder);
|
||||
}
|
||||
|
||||
protected abstract loadKeyboardBlob(uri: string, errorBuilder: KeyboardLoadErrorBuilder): Promise<Uint8Array>;
|
||||
|
||||
protected abstract loadKeyboardFromScript(scriptSrc: string, errorBuilder: KeyboardLoadErrorBuilder): Promise<Keyboard>;
|
||||
}
|
||||
|
|
@ -2,9 +2,10 @@
|
|||
|
||||
///<reference lib="dom" />
|
||||
|
||||
import { Keyboard, KeyboardHarness, KeyboardLoaderBase, KeyboardLoadErrorBuilder, MinimalKeymanGlobal } from 'keyman/engine/keyboard';
|
||||
|
||||
import { ManagedPromise } from '@keymanapp/web-utils';
|
||||
import { default as Keyboard } from '../keyboard.js';
|
||||
import { KeyboardHarness, MinimalKeymanGlobal } from '../keyboardHarness.js';
|
||||
import { KeyboardLoaderBase } from '../keyboardLoaderBase.js';
|
||||
import { KeyboardLoadErrorBuilder } from '../keyboardLoadError.js';
|
||||
|
||||
export class DOMKeyboardLoader extends KeyboardLoaderBase {
|
||||
public readonly element: HTMLIFrameElement;
|
||||
|
|
@ -28,54 +29,44 @@ export class DOMKeyboardLoader extends KeyboardLoaderBase {
|
|||
this.performCacheBusting = cacheBust || false;
|
||||
}
|
||||
|
||||
protected loadKeyboardInternal(
|
||||
uri: string,
|
||||
errorBuilder: KeyboardLoadErrorBuilder,
|
||||
id?: string
|
||||
): Promise<Keyboard> {
|
||||
const promise = new ManagedPromise<Keyboard>();
|
||||
|
||||
if(this.performCacheBusting) {
|
||||
protected async loadKeyboardBlob(uri: string, errorBuilder: KeyboardLoadErrorBuilder): Promise<Uint8Array> {
|
||||
if (this.performCacheBusting) {
|
||||
uri = this.cacheBust(uri);
|
||||
}
|
||||
|
||||
let response: Response;
|
||||
try {
|
||||
const document = this.harness._jsGlobal.document;
|
||||
const script = document.createElement('script');
|
||||
if(id) {
|
||||
script.id = id;
|
||||
}
|
||||
document.head.appendChild(script);
|
||||
script.onerror = (err: any) => {
|
||||
promise.reject(errorBuilder.missingError(err));
|
||||
}
|
||||
script.onload = () => {
|
||||
if(this.harness.loadedKeyboard) {
|
||||
const keyboard = this.harness.loadedKeyboard;
|
||||
this.harness.loadedKeyboard = null;
|
||||
promise.resolve(keyboard);
|
||||
} else {
|
||||
promise.reject(errorBuilder.scriptError());
|
||||
}
|
||||
}
|
||||
|
||||
// On the oldest mobile devices we support, Promise.finally may not actually exist.
|
||||
// Fortunately... it's not that hard of an issue to work around.
|
||||
// Note: es6-shim doesn't polyfill Promise.finally!
|
||||
promise.then(() => {
|
||||
// It is safe to remove the script once it has been run (https://stackoverflow.com/a/37393041)
|
||||
script.remove();
|
||||
}).catch(() => {
|
||||
script.remove();
|
||||
});
|
||||
|
||||
// Now that EVERYTHING ELSE is ready, establish the link to the keyboard's script.
|
||||
script.src = uri;
|
||||
} catch (err) {
|
||||
return Promise.reject(err);
|
||||
response = await fetch(uri);
|
||||
} catch (e) {
|
||||
throw errorBuilder.keyboardDownloadError(e);
|
||||
}
|
||||
|
||||
return promise.corePromise;
|
||||
if (!response.ok) {
|
||||
throw errorBuilder.keyboardDownloadError(new Error(`HTTP ${response.status} ${response.statusText}`));
|
||||
}
|
||||
|
||||
let buffer: ArrayBuffer;
|
||||
try {
|
||||
buffer = await response.arrayBuffer();
|
||||
} catch (e) {
|
||||
throw errorBuilder.invalidKeyboard(e);
|
||||
}
|
||||
return new Uint8Array(buffer);
|
||||
}
|
||||
|
||||
protected async loadKeyboardFromScript(script: string, errorBuilder: KeyboardLoadErrorBuilder): Promise<Keyboard> {
|
||||
try {
|
||||
this.evalScriptInContext(script, this.harness._jsGlobal);
|
||||
} catch (e) {
|
||||
throw errorBuilder.scriptError(e);
|
||||
}
|
||||
const keyboard = this.harness.loadedKeyboard;
|
||||
if (!keyboard) {
|
||||
throw errorBuilder.scriptError();
|
||||
}
|
||||
|
||||
this.harness.loadedKeyboard = null;
|
||||
return keyboard;
|
||||
}
|
||||
|
||||
private cacheBust(uri: string) {
|
||||
|
|
@ -84,4 +75,14 @@ export class DOMKeyboardLoader extends KeyboardLoaderBase {
|
|||
// being ignored.
|
||||
return uri + "?v=" + (new Date()).getTime(); /*cache buster*/
|
||||
}
|
||||
|
||||
private evalScriptInContext(script: string, context: any) {
|
||||
const f = function (s: string) {
|
||||
// use indirect eval (eval?.() notation doesn't work because of esbuild bundling)
|
||||
const evalFunc = eval;
|
||||
return evalFunc(s);
|
||||
}
|
||||
f.call(context, script);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,9 +1,13 @@
|
|||
import { Keyboard, KeyboardHarness, KeyboardLoaderBase, KeyboardLoadErrorBuilder, MinimalKeymanGlobal } from 'keyman/engine/keyboard';
|
||||
import vm from 'node:vm';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
|
||||
import vm from 'vm';
|
||||
import fs from 'fs';
|
||||
import { globalObject } from '@keymanapp/web-utils';
|
||||
|
||||
import { default as Keyboard } from '../keyboard.js';
|
||||
import { KeyboardHarness, MinimalKeymanGlobal } from '../keyboardHarness.js';
|
||||
import { KeyboardLoaderBase } from '../keyboardLoaderBase.js';
|
||||
import { KeyboardLoadErrorBuilder } from '../keyboardLoadError.js';
|
||||
|
||||
export class NodeKeyboardLoader extends KeyboardLoaderBase {
|
||||
constructor()
|
||||
constructor(harness: KeyboardHarness);
|
||||
|
|
@ -22,22 +26,32 @@ export class NodeKeyboardLoader extends KeyboardLoaderBase {
|
|||
}
|
||||
}
|
||||
|
||||
protected loadKeyboardInternal(uri: string, errorBuilder: KeyboardLoadErrorBuilder): Promise<Keyboard> {
|
||||
protected async loadKeyboardBlob(uri: string, errorBuilder: KeyboardLoadErrorBuilder): Promise<Uint8Array> {
|
||||
// `fs` does not like 'file:///'; it IS "File System" oriented, after all, and wants a path, not a URI.
|
||||
if(uri.indexOf('file:///') == 0) {
|
||||
if (uri.indexOf('file:///') == 0) {
|
||||
uri = uri.substring('file:///'.length);
|
||||
}
|
||||
|
||||
let buffer: Buffer;
|
||||
try {
|
||||
buffer = await readFile(uri);
|
||||
} catch (err) {
|
||||
throw errorBuilder.keyboardDownloadError(err);
|
||||
}
|
||||
return Uint8Array.from(buffer);
|
||||
}
|
||||
|
||||
protected async loadKeyboardFromScript(scriptSrc: string, errorBuilder: KeyboardLoadErrorBuilder): Promise<Keyboard> {
|
||||
let script;
|
||||
try {
|
||||
script = new vm.Script(fs.readFileSync(uri).toString());
|
||||
script = new vm.Script(scriptSrc);
|
||||
} catch (err) {
|
||||
return Promise.reject(errorBuilder.missingError(err));
|
||||
throw errorBuilder.invalidKeyboard(err);
|
||||
}
|
||||
try {
|
||||
script.runInContext(this.harness._jsGlobal);
|
||||
} catch (err) {
|
||||
return Promise.reject(errorBuilder.scriptError(err));
|
||||
throw errorBuilder.scriptError(err);
|
||||
}
|
||||
|
||||
const keyboard = this.harness.loadedKeyboard;
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ SUBPROJECT_NAME=engine/main
|
|||
|
||||
builder_describe "Builds the Keyman Engine for Web's common top-level base classes." \
|
||||
"@/common/web/keyman-version" \
|
||||
"@/web/src/engine/core-processor" \
|
||||
"@/web/src/engine/keyboard" \
|
||||
"@/web/src/engine/interfaces build" \
|
||||
"@/web/src/engine/js-processor build" \
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
## engine/main
|
||||
# engine/main
|
||||
|
||||
This subproject holds modularized code converted from the old, namespaced version of KMW.
|
||||
This subproject holds modularized code converted from the old, namespaced version of KMW.
|
||||
Previously it was called `input-processor`.
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@
|
|||
|
||||
import ContextWindow from "./contextWindow.js";
|
||||
import { LanguageProcessor } from "./languageProcessor.js";
|
||||
import type { ModelSpec } from "keyman/engine/interfaces";
|
||||
import type { ModelSpec, PathConfiguration } from "keyman/engine/interfaces";
|
||||
import { globalObject, DeviceSpec } from "@keymanapp/web-utils";
|
||||
|
||||
import { CoreProcessor } from "keyman/engine/core-processor";
|
||||
import { Codes, type Keyboard, type KeyEvent } from "keyman/engine/keyboard";
|
||||
import {
|
||||
type Alternate,
|
||||
|
|
@ -34,6 +35,7 @@ export class InputProcessor {
|
|||
private contextDevice: DeviceSpec;
|
||||
private kbdProcessor: KeyboardProcessor;
|
||||
private lngProcessor: LanguageProcessor;
|
||||
private coreProcessor: CoreProcessor;
|
||||
|
||||
private readonly contextCache = new TranscriptionCache();
|
||||
|
||||
|
|
@ -49,6 +51,11 @@ export class InputProcessor {
|
|||
this.contextDevice = device;
|
||||
this.kbdProcessor = new KeyboardProcessor(device, options);
|
||||
this.lngProcessor = new LanguageProcessor(predictiveTextWorker, this.contextCache);
|
||||
this.coreProcessor = new CoreProcessor();
|
||||
}
|
||||
|
||||
public async init(paths: PathConfiguration) {
|
||||
this.coreProcessor.init(paths.basePath);
|
||||
}
|
||||
|
||||
public get languageProcessor(): LanguageProcessor {
|
||||
|
|
|
|||
|
|
@ -238,6 +238,8 @@ export default class KeymanEngine<
|
|||
// Initialize supplementary plane string extensions
|
||||
String.kmwEnableSupplementaryPlane(true);
|
||||
|
||||
await this.core.init(config.paths);
|
||||
|
||||
// Since we're not sandboxing keyboard loads yet, we just use `window` as the jsGlobal object.
|
||||
// All components initialized below require a properly-configured `config.paths` or similar.
|
||||
const keyboardLoader = new KeyboardLoader(this.interface, config.applyCacheBusting);
|
||||
|
|
|
|||
21
web/src/test/auto/dom/cases/core-processor/basic.spec.ts
Normal file
21
web/src/test/auto/dom/cases/core-processor/basic.spec.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { assert } from 'chai';
|
||||
import { CoreProcessor } from 'keyman/engine/core-processor';
|
||||
|
||||
const coreurl = '/web/build/engine/core-processor/obj/import/core';
|
||||
|
||||
// Test the CoreProcessor interface.
|
||||
describe('CoreProcessor', function () {
|
||||
it('can initialize without errors', async function () {
|
||||
const kp = new CoreProcessor();
|
||||
assert.isTrue(await kp.init(coreurl));
|
||||
});
|
||||
|
||||
it('can call temp function', async function () {
|
||||
const kp = new CoreProcessor();
|
||||
await kp.init(coreurl);
|
||||
const a = kp.tmp_wasm_attributes();
|
||||
assert.isNotNull(a);
|
||||
assert.isNumber(a.max_context);
|
||||
console.dir(a);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
import { assert } from 'chai';
|
||||
|
||||
import { DOMKeyboardLoader } from 'keyman/engine/keyboard/dom-keyboard-loader';
|
||||
import { extendString, KeyboardHarness, Keyboard, MinimalKeymanGlobal, DeviceSpec, KeyboardKeymanGlobal } from 'keyman/engine/keyboard';
|
||||
import { extendString, KeyboardHarness, Keyboard, MinimalKeymanGlobal, DeviceSpec, KeyboardKeymanGlobal, KeyboardDownloadError, KeyboardScriptError } from 'keyman/engine/keyboard';
|
||||
import { KeyboardInterface, Mock } from 'keyman/engine/js-processor';
|
||||
import { assertThrowsAsync } from 'keyman/tools/testing/test-utils';
|
||||
|
||||
declare let window: typeof globalThis;
|
||||
// KeymanEngine from the web/ folder... when available.
|
||||
|
|
@ -27,6 +28,24 @@ describe('Keyboard loading in DOM', function() {
|
|||
}
|
||||
})
|
||||
|
||||
it('throws error when keyboard does not exist', async () => {
|
||||
const harness = new KeyboardInterface(window, MinimalKeymanGlobal);
|
||||
const keyboardLoader = new DOMKeyboardLoader(harness);
|
||||
const nonExisting = '/does/not/exist.js';
|
||||
|
||||
await assertThrowsAsync(async () => await keyboardLoader.loadKeyboardFromPath(nonExisting),
|
||||
KeyboardDownloadError, `Unable to download keyboard at ${nonExisting}`);
|
||||
});
|
||||
|
||||
it('throws error when keyboard is invalid', async () => {
|
||||
const harness = new KeyboardInterface(window, MinimalKeymanGlobal);
|
||||
const keyboardLoader = new DOMKeyboardLoader(harness);
|
||||
const nonKeyboardPath = '/common/test/resources/index.mjs';
|
||||
|
||||
await assertThrowsAsync(async () => await keyboardLoader.loadKeyboardFromPath(nonKeyboardPath),
|
||||
KeyboardScriptError, `Error registering the keyboard script at ${nonKeyboardPath}; it may contain an error.`);
|
||||
});
|
||||
|
||||
it('`window`, disabled rule processing', async () => {
|
||||
const harness = new KeyboardHarness(window, MinimalKeymanGlobal);
|
||||
let keyboardLoader = new DOMKeyboardLoader(harness);
|
||||
|
|
@ -33,7 +33,7 @@ export default {
|
|||
nodeResolve: true,
|
||||
// Top-level, implicit 'default' group
|
||||
files: [
|
||||
'src/test/auto/dom/test_init_check.spec.ts',
|
||||
'web/src/test/auto/dom/test_init_check.spec.ts',
|
||||
// '**/*.spec.html'
|
||||
],
|
||||
groups: [
|
||||
|
|
@ -41,45 +41,50 @@ export default {
|
|||
name: 'engine/attachment',
|
||||
// Relative, from the containing package.json
|
||||
files: [
|
||||
'build/test/dom/cases/attachment/**/*.spec.html',
|
||||
'build/test/dom/cases/attachment/**/*.spec.mjs'
|
||||
'web/build/test/dom/cases/attachment/**/*.spec.html',
|
||||
'web/build/test/dom/cases/attachment/**/*.spec.mjs'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'app/browser',
|
||||
// Relative, from the containing package.json
|
||||
files: ['build/test/dom/cases/browser/**/*.spec.mjs']
|
||||
files: ['web/build/test/dom/cases/browser/**/*.spec.mjs']
|
||||
},
|
||||
{
|
||||
name: 'engine/core-processor',
|
||||
// Relative, from the containing package.json
|
||||
files: ['web/src/test/auto/dom/cases/core-processor/*.spec.ts']
|
||||
},
|
||||
{
|
||||
name: 'engine/dom-utils',
|
||||
// Relative, from the containing package.json
|
||||
files: ['build/test/dom/cases/dom-utils/**/*.spec.mjs']
|
||||
files: ['web/build/test/dom/cases/dom-utils/**/*.spec.mjs']
|
||||
},
|
||||
{
|
||||
name: 'engine/element-wrappers',
|
||||
// Relative, from the containing package.json
|
||||
files: ['build/test/dom/cases/element-wrappers/**/*.spec.mjs']
|
||||
files: ['web/build/test/dom/cases/element-wrappers/**/*.spec.mjs']
|
||||
},
|
||||
{
|
||||
name: 'engine/gesture-processor',
|
||||
// Relative, from the containing package.json
|
||||
// Note: here we use the .spec.html file in the src directory!
|
||||
files: ['src/test/auto/dom/cases/gesture-processor/**/*.spec.html']
|
||||
files: ['web/src/test/auto/dom/cases/gesture-processor/**/*.spec.html']
|
||||
},
|
||||
{
|
||||
name: 'engine/keyboard',
|
||||
// Relative, from the containing package.json
|
||||
files: ['build/test/dom/cases/keyboard/**/*.spec.mjs']
|
||||
files: ['web/build/test/dom/cases/keyboard/**/*.tests.mjs']
|
||||
},
|
||||
{
|
||||
name: 'engine/keyboard-storage',
|
||||
// Relative, from the containing package.json
|
||||
files: ['build/test/dom/cases/keyboard-storage/**/*.spec.mjs']
|
||||
files: ['web/build/test/dom/cases/keyboard-storage/**/*.spec.mjs']
|
||||
},
|
||||
{
|
||||
name: 'engine/osk',
|
||||
// Relative, from the containing package.json
|
||||
files: ['build/test/dom/cases/osk/**/*.spec.mjs']
|
||||
files: ['web/build/test/dom/cases/osk/**/*.spec.mjs']
|
||||
}
|
||||
],
|
||||
middleware: [
|
||||
|
|
@ -89,6 +94,12 @@ export default {
|
|||
context.url = '/web/src/test/auto' + context.url;
|
||||
}
|
||||
|
||||
return next();
|
||||
},
|
||||
function rewriteWasmContentType(context, next) {
|
||||
if (context.url.endsWith('.wasm')) {
|
||||
context.headers['content-type'] = 'application/wasm';
|
||||
}
|
||||
return next();
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { assert } from 'chai';
|
|||
import { createRequire } from 'module';
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
import { MinimalKeymanGlobal } from 'keyman/engine/keyboard';
|
||||
import { DeviceSpec, MinimalKeymanGlobal } from 'keyman/engine/keyboard';
|
||||
import { KeyboardInterface, Mock } from 'keyman/engine/js-processor';
|
||||
import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader';
|
||||
|
||||
|
|
@ -11,21 +11,22 @@ describe('Headless keyboard loading', function () {
|
|||
const laoPath = require.resolve('@keymanapp/common-test-resources/keyboards/lao_2008_basic.js');
|
||||
const khmerPath = require.resolve('@keymanapp/common-test-resources/keyboards/khmer_angkor.js');
|
||||
const nonKeyboardPath = require.resolve('@keymanapp/common-test-resources/index.mjs');
|
||||
const ipaPath = require.resolve('@keymanapp/common-test-resources/keyboards/sil_ipa.js');
|
||||
// const ipaPath = require.resolve('@keymanapp/common-test-resources/keyboards/sil_ipa.js');
|
||||
// Common test suite setup.
|
||||
|
||||
let device = {
|
||||
formFactor: 'desktop',
|
||||
OS: 'windows',
|
||||
browser: 'native'
|
||||
const device = {
|
||||
formFactor: DeviceSpec.FormFactor.Desktop,
|
||||
OS: DeviceSpec.OperatingSystem.Windows,
|
||||
browser: DeviceSpec.Browser.Native,
|
||||
touchable: false
|
||||
}
|
||||
|
||||
describe('Full harness loading', () => {
|
||||
it('successfully loads', async function () {
|
||||
// -- START: Standard Recorder-based unit test loading boilerplate --
|
||||
let harness = new KeyboardInterface({}, MinimalKeymanGlobal);
|
||||
let keyboardLoader = new NodeKeyboardLoader(harness);
|
||||
let keyboard = await keyboardLoader.loadKeyboardFromPath(laoPath);
|
||||
const harness = new KeyboardInterface({}, MinimalKeymanGlobal);
|
||||
const keyboardLoader = new NodeKeyboardLoader(harness);
|
||||
const keyboard = await keyboardLoader.loadKeyboardFromPath(laoPath);
|
||||
harness.activeKeyboard = keyboard;
|
||||
// -- END: Standard Recorder-based unit test loading boilerplate --
|
||||
|
||||
|
|
@ -35,9 +36,9 @@ describe('Headless keyboard loading', function () {
|
|||
|
||||
it('can evaluate rules', async function () {
|
||||
// -- START: Standard Recorder-based unit test loading boilerplate --
|
||||
let harness = new KeyboardInterface({}, MinimalKeymanGlobal);
|
||||
let keyboardLoader = new NodeKeyboardLoader(harness);
|
||||
let keyboard = await keyboardLoader.loadKeyboardFromPath(laoPath);
|
||||
const harness = new KeyboardInterface({}, MinimalKeymanGlobal);
|
||||
const keyboardLoader = new NodeKeyboardLoader(harness);
|
||||
const keyboard = await keyboardLoader.loadKeyboardFromPath(laoPath);
|
||||
harness.activeKeyboard = keyboard;
|
||||
// -- END: Standard Recorder-based unit test loading boilerplate --
|
||||
|
||||
|
|
@ -46,8 +47,8 @@ describe('Headless keyboard loading', function () {
|
|||
});
|
||||
|
||||
it('does not change the active kehboard', async function () {
|
||||
let harness = new KeyboardInterface({}, MinimalKeymanGlobal);
|
||||
let keyboardLoader = new NodeKeyboardLoader(harness);
|
||||
const harness = new KeyboardInterface({}, MinimalKeymanGlobal);
|
||||
const keyboardLoader = new NodeKeyboardLoader(harness);
|
||||
const lao_keyboard = await keyboardLoader.loadKeyboardFromPath(laoPath);
|
||||
assert.isNotOk(harness.activeKeyboard);
|
||||
assert.isOk(lao_keyboard);
|
||||
|
|
@ -66,8 +67,8 @@ describe('Headless keyboard loading', function () {
|
|||
it('throws distinct errors', async function () {
|
||||
const invalidPath = 'totally_invalid_path.js';
|
||||
|
||||
let harness = new KeyboardInterface({}, MinimalKeymanGlobal);
|
||||
let keyboardLoader = new NodeKeyboardLoader(harness);
|
||||
const harness = new KeyboardInterface({}, MinimalKeymanGlobal);
|
||||
const keyboardLoader = new NodeKeyboardLoader(harness);
|
||||
let missingError;
|
||||
try {
|
||||
await keyboardLoader.loadKeyboardFromPath(invalidPath);
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
import { assert } from 'chai';
|
||||
|
||||
import { createRequire } from 'module';
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
import { KeyboardHarness, MinimalKeymanGlobal } from 'keyman/engine/keyboard';
|
||||
import { KeyboardInterface, Mock } from 'keyman/engine/js-processor';
|
||||
import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader';
|
||||
|
||||
describe('Headless keyboard loading', function() {
|
||||
const laoPath = require.resolve('@keymanapp/common-test-resources/keyboards/lao_2008_basic.js');
|
||||
const khmerPath = require.resolve('@keymanapp/common-test-resources/keyboards/khmer_angkor.js');
|
||||
const nonKeyboardPath = require.resolve('@keymanapp/common-test-resources/index.mjs');
|
||||
const ipaPath = require.resolve('@keymanapp/common-test-resources/keyboards/sil_ipa.js');
|
||||
// Common test suite setup.
|
||||
|
||||
let device = {
|
||||
formFactor: 'desktop',
|
||||
OS: 'windows',
|
||||
browser: 'native'
|
||||
}
|
||||
|
||||
describe('Minimal harness loading', () => {
|
||||
it('successfully loads', async function() {
|
||||
// -- START: Standard Recorder-based unit test loading boilerplate --
|
||||
let harness = new KeyboardHarness({}, MinimalKeymanGlobal);
|
||||
let keyboardLoader = new NodeKeyboardLoader(harness);
|
||||
let keyboard = await keyboardLoader.loadKeyboardFromPath(laoPath);
|
||||
// -- END: Standard Recorder-based unit test loading boilerplate --
|
||||
|
||||
// Asserts that the harness's loading field is cleared once the load is complete.
|
||||
assert.isNotOk(harness.loadedKeyboard);
|
||||
|
||||
// Asserts that the `activeKeyboard` field was not set by the operation.
|
||||
assert.isNotOk(harness.activeKeyboard);
|
||||
|
||||
// This part provides assurance that the keyboard properly loaded.
|
||||
assert.equal(keyboard.id, "Keyboard_lao_2008_basic");
|
||||
});
|
||||
|
||||
it('successfully loads (has variable stores)', async () => {
|
||||
// -- START: Standard Recorder-based unit test loading boilerplate --
|
||||
let harness = new KeyboardHarness({}, MinimalKeymanGlobal);
|
||||
let keyboardLoader = new NodeKeyboardLoader(harness);
|
||||
let keyboard = await keyboardLoader.loadKeyboardFromPath(ipaPath);
|
||||
// -- END: Standard Recorder-based unit test loading boilerplate --
|
||||
|
||||
// This part provides extra assurance that the keyboard properly loaded.
|
||||
assert.equal(keyboard.id, "Keyboard_sil_ipa");
|
||||
});
|
||||
|
||||
it('cannot evaluate rules', async function() {
|
||||
// -- START: Standard Recorder-based unit test loading boilerplate --
|
||||
let harness = new KeyboardHarness({}, MinimalKeymanGlobal);
|
||||
let keyboardLoader = new NodeKeyboardLoader(harness);
|
||||
let keyboard = await keyboardLoader.loadKeyboardFromPath(laoPath);
|
||||
// -- END: Standard Recorder-based unit test loading boilerplate --
|
||||
|
||||
// Runs a blank KeyEvent through the keyboard's rule processing...
|
||||
// but via separate harness configured with a different captured global.
|
||||
let ruleHarness = new KeyboardInterface({}, MinimalKeymanGlobal);
|
||||
ruleHarness.activeKeyboard = keyboard;
|
||||
try {
|
||||
ruleHarness.processKeystroke(new Mock(), keyboard.constructNullKeyEvent(device));
|
||||
assert.fail();
|
||||
} catch (err) {
|
||||
// Drives home an important detail: the 'global' object is effectively
|
||||
// closure-captured. (Similar constraints may occur when experimenting with
|
||||
// 'sandboxed' keyboard loading in the DOM!)
|
||||
assert.equal(err.message, 'k.KKM is not a function');
|
||||
}
|
||||
});
|
||||
|
||||
it('accurately determines supported gesture types', async () => {
|
||||
// -- START: Standard Recorder-based unit test loading boilerplate --
|
||||
let harness = new KeyboardHarness({}, MinimalKeymanGlobal);
|
||||
let keyboardLoader = new NodeKeyboardLoader(harness);
|
||||
let km_keyboard = await keyboardLoader.loadKeyboardFromPath(khmerPath);
|
||||
// -- END: Standard Recorder-based unit test loading boilerplate --
|
||||
|
||||
// `khmer_angkor` - supports longpresses, but not flicks or multitaps.
|
||||
|
||||
const desktopLayout = km_keyboard.layout('desktop');
|
||||
assert.isFalse(desktopLayout.hasFlicks);
|
||||
assert.isFalse(desktopLayout.hasLongpresses);
|
||||
assert.isFalse(desktopLayout.hasMultitaps);
|
||||
|
||||
const mobileLayout = km_keyboard.layout('phone');
|
||||
assert.isFalse(mobileLayout.hasFlicks);
|
||||
assert.isTrue(mobileLayout.hasLongpresses);
|
||||
assert.isFalse(mobileLayout.hasMultitaps);
|
||||
});
|
||||
});
|
||||
});
|
||||
34
web/src/test/auto/headless/engine/keyboard/keyboard.tests.ts
Normal file
34
web/src/test/auto/headless/engine/keyboard/keyboard.tests.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { assert } from 'chai';
|
||||
|
||||
import { createRequire } from 'module';
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
import { KeyboardHarness, MinimalKeymanGlobal, DeviceSpec } from 'keyman/engine/keyboard';
|
||||
import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader';
|
||||
|
||||
|
||||
describe('Keyboard tests', function () {
|
||||
const khmerPath = require.resolve('@keymanapp/common-test-resources/keyboards/khmer_angkor.js');
|
||||
|
||||
it('accurately determines layout properties', async () => {
|
||||
// -- START: Standard Recorder-based unit test loading boilerplate --
|
||||
const harness = new KeyboardHarness({}, MinimalKeymanGlobal);
|
||||
const keyboardLoader = new NodeKeyboardLoader(harness);
|
||||
const km_keyboard = await keyboardLoader.loadKeyboardFromPath(khmerPath);
|
||||
// -- END: Standard Recorder-based unit test loading boilerplate --
|
||||
|
||||
// `khmer_angkor` - supports longpresses, but not flicks or multitaps.
|
||||
|
||||
// Phone supports longpress if the keyboard supports it.
|
||||
const mobileLayout = km_keyboard.layout(DeviceSpec.FormFactor.Phone);
|
||||
assert.isTrue(mobileLayout.hasLongpresses);
|
||||
assert.isFalse(mobileLayout.hasFlicks);
|
||||
assert.isFalse(mobileLayout.hasMultitaps);
|
||||
|
||||
// Desktop doesn't support longpress even if the keyboard supports it.
|
||||
const desktopLayout = km_keyboard.layout(DeviceSpec.FormFactor.Desktop);
|
||||
assert.isFalse(desktopLayout.hasLongpresses);
|
||||
assert.isFalse(desktopLayout.hasFlicks);
|
||||
assert.isFalse(desktopLayout.hasMultitaps);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
import { assert } from 'chai';
|
||||
|
||||
import { createRequire } from 'module';
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
import { KeyboardHarness, MinimalKeymanGlobal, KeyboardDownloadError, DeviceSpec, InvalidKeyboardError } from 'keyman/engine/keyboard';
|
||||
import { KeyboardInterface, Mock } from 'keyman/engine/js-processor';
|
||||
import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader';
|
||||
import { assertThrowsAsync, assertThrows } from 'keyman/tools/testing/test-utils';
|
||||
|
||||
describe('Headless keyboard loading', function() {
|
||||
const laoPath = require.resolve('@keymanapp/common-test-resources/keyboards/lao_2008_basic.js');
|
||||
const nonKeyboardPath = require.resolve('@keymanapp/common-test-resources/index.mjs');
|
||||
const ipaPath = require.resolve('@keymanapp/common-test-resources/keyboards/sil_ipa.js');
|
||||
const nonExisting = '/does/not/exist.js';
|
||||
// Common test suite setup.
|
||||
|
||||
const device = {
|
||||
formFactor: DeviceSpec.FormFactor.Desktop,
|
||||
OS: DeviceSpec.OperatingSystem.Windows,
|
||||
browser: DeviceSpec.Browser.Native,
|
||||
touchable: false
|
||||
}
|
||||
|
||||
describe('Minimal harness loading', () => {
|
||||
it('successfully loads a single keyboard from filesystem', async () => {
|
||||
// -- START: Standard Recorder-based unit test loading boilerplate --
|
||||
const harness = new KeyboardInterface({}, MinimalKeymanGlobal);
|
||||
const keyboardLoader = new NodeKeyboardLoader(harness);
|
||||
const keyboard = await keyboardLoader.loadKeyboardFromPath(laoPath);
|
||||
// -- END: Standard Recorder-based unit test loading boilerplate --
|
||||
|
||||
// Asserts that the harness's loading field is cleared once the load is complete.
|
||||
assert.isNotOk(harness.loadedKeyboard);
|
||||
|
||||
// Asserts that the `activeKeyboard` field was not set by the operation.
|
||||
assert.isNotOk(harness.activeKeyboard);
|
||||
|
||||
// This part provides assurance that the keyboard properly loaded.
|
||||
assert.equal(keyboard.id, "Keyboard_lao_2008_basic");
|
||||
});
|
||||
|
||||
it('throws error when keyboard does not exist', async () => {
|
||||
const harness = new KeyboardInterface({}, MinimalKeymanGlobal);
|
||||
const keyboardLoader = new NodeKeyboardLoader(harness);
|
||||
|
||||
await assertThrowsAsync(async () => await keyboardLoader.loadKeyboardFromPath(nonExisting),
|
||||
KeyboardDownloadError, `Unable to download keyboard at ${nonExisting}`);
|
||||
});
|
||||
|
||||
it('throws error when keyboard is invalid', async () => {
|
||||
const harness = new KeyboardInterface({}, MinimalKeymanGlobal);
|
||||
const keyboardLoader = new NodeKeyboardLoader(harness);
|
||||
|
||||
await assertThrowsAsync(async () => await keyboardLoader.loadKeyboardFromPath(nonKeyboardPath),
|
||||
InvalidKeyboardError, `${nonKeyboardPath} is not a valid keyboard file`);
|
||||
});
|
||||
|
||||
it('successfully loads (has variable stores)', async () => {
|
||||
// -- START: Standard Recorder-based unit test loading boilerplate --
|
||||
const harness = new KeyboardInterface({}, MinimalKeymanGlobal);
|
||||
const keyboardLoader = new NodeKeyboardLoader(harness);
|
||||
const keyboard = await keyboardLoader.loadKeyboardFromPath(ipaPath);
|
||||
// -- END: Standard Recorder-based unit test loading boilerplate --
|
||||
|
||||
// This part provides extra assurance that the keyboard properly loaded.
|
||||
assert.equal(keyboard.id, "Keyboard_sil_ipa");
|
||||
});
|
||||
|
||||
// TODO-WEB-CORE: figure out what the purpose of this test is
|
||||
// TODO-WEB-CORE: move to kbdInterface.tests.ts
|
||||
it('cannot evaluate rules', async function() {
|
||||
// -- START: Standard Recorder-based unit test loading boilerplate --
|
||||
const harness = new KeyboardHarness({}, MinimalKeymanGlobal);
|
||||
const keyboardLoader = new NodeKeyboardLoader(harness);
|
||||
const keyboard = await keyboardLoader.loadKeyboardFromPath(laoPath);
|
||||
// -- END: Standard Recorder-based unit test loading boilerplate --
|
||||
|
||||
// Runs a blank KeyEvent through the keyboard's rule processing...
|
||||
// but via separate harness configured with a different captured global.
|
||||
// This shows an important detail: the 'global' object is effectively
|
||||
// closure-captured. (Similar constraints may occur when experimenting with
|
||||
// 'sandboxed' keyboard loading in the DOM!)
|
||||
const ruleHarness = new KeyboardInterface({}, MinimalKeymanGlobal);
|
||||
ruleHarness.activeKeyboard = keyboard;
|
||||
assertThrows(() => ruleHarness.processKeystroke(new Mock(), keyboard.constructNullKeyEvent(device)), 'k.KKM is not a function');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -24,7 +24,7 @@ export default {
|
|||
concurrency: 10,
|
||||
nodeResolve: true,
|
||||
files: [
|
||||
'build/test/integrated//**/*.spec.mjs',
|
||||
'web/build/test/integrated//**/*.spec.mjs',
|
||||
// '**/*.spec.html'
|
||||
],
|
||||
middleware: [
|
||||
|
|
|
|||
10
web/src/test/manual/README.md
Normal file
10
web/src/test/manual/README.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# Manual tests
|
||||
|
||||
To run the the manual tests, start the test web server with:
|
||||
|
||||
```bash
|
||||
cd "$KEYMAN_ROOT"
|
||||
web/build.sh start
|
||||
```
|
||||
|
||||
Then open <http://localhost:3000> in your browser.
|
||||
|
|
@ -57,4 +57,4 @@ function do_copy() {
|
|||
}
|
||||
|
||||
builder_run_action clean rm -rf "$KEYMAN_ROOT/$DEST"
|
||||
builder_run_action build do_copy
|
||||
builder_run_action build do_copy
|
||||
|
|
|
|||
|
|
@ -1,17 +1,15 @@
|
|||
// JavaScript Document samplehdr.js: Keyboard management for KeymanWeb demonstration pages
|
||||
|
||||
/*
|
||||
/*
|
||||
This script is designed to test KeymanWeb error message handling.
|
||||
*/
|
||||
|
||||
function loadKeyboards()
|
||||
{
|
||||
function loadKeyboards()
|
||||
{
|
||||
var kmw=keyman;
|
||||
|
||||
|
||||
// We start by adding a keyboard correctly. It's best to include a 'control' in our experiment.
|
||||
kmw.addKeyboards({id:'us',name:'English',languages:{id:'en',name:'English'},
|
||||
filename:'../us-1.0.js'});
|
||||
|
||||
|
||||
// Insert a keyboard that cannot be found.
|
||||
kmw.addKeyboards({id:'lao_2008_basic',name:'wrong-filename',
|
||||
languages:{
|
||||
|
|
@ -19,9 +17,9 @@
|
|||
font:{family:'LaoWeb',source:['../font/saysettha_web.ttf','../font/saysettha_web.woff','../font/saysettha_web.eot']}
|
||||
},
|
||||
filename:'./missing_file.js' // Intentional error - the file doesn't exist, so the <script> tag will raise an error event.
|
||||
});
|
||||
|
||||
// Insert a keyboard that will generate a timing error.
|
||||
});
|
||||
|
||||
// Insert a keyboard that will generate a timing error.
|
||||
kmw.addKeyboards({id:'unparsable',name:'non-parsable',
|
||||
languages:{
|
||||
id:'lo',name:'debugging',region:'Asia',
|
||||
|
|
@ -29,14 +27,16 @@
|
|||
},
|
||||
filename:'./unparsable.js' // Intentional error - the file has no parsable keyboard, so while the <script> tag will load,
|
||||
// registration will fail.
|
||||
});
|
||||
});
|
||||
|
||||
// Insert a keyboard that will generate a timing error.
|
||||
// Insert a keyboard that will generate a timing error. `timeout.js` doesn't
|
||||
// exist, but the test server (web/src/tools/testing/test-server/index.cjs)
|
||||
// has special handling for that URL and times out after 10 seconds.
|
||||
kmw.addKeyboards({id:'timeout',name:'timeout',
|
||||
languages:{
|
||||
id:'lo',name:'debugging',region:'Asia',
|
||||
font:{family:'LaoWeb',source:['../font/saysettha_web.ttf','../font/saysettha_web.woff','../font/saysettha_web.eot']}
|
||||
},
|
||||
filename:'./timeout.js' // Intentional (simulated) error - the file never loads, simulating a server timeout.
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,9 +65,32 @@
|
|||
<h3>or in this input field:</h3>
|
||||
<input class='test' value='' placeholder='or here'/>
|
||||
|
||||
<h2>Expected error messages:</h2>
|
||||
<ul>
|
||||
<li><i>wrong-filename</i> keyboard: "Unable to download wrong-filename keyboard for debugging"</li>
|
||||
<li><i>non-parsable</i> keyboard: "Error registering the non-parsable keyboard for debugging; keyboard script at ./unparsable.js may contain an error."</li>
|
||||
<li><i>timeout</i> keyboard: "Sorry, the timeout keyboard for debugging is not currently available."</li>
|
||||
</ul>
|
||||
|
||||
<h3><a href="../index.html">Return to testing home page</a></h3>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
if (!window.location.href.startsWith('http://localhost:3000')) {
|
||||
const body = document.getElementsByTagName('body')[0];
|
||||
body.innerHTML = `<h1>KeymanWeb Sample Page - Error Testing page</h1>
|
||||
<h2>Unable to load this test page!</h2>
|
||||
<p>This page has to be loaded through the local test server.</p>
|
||||
<p>Do the following:</p>
|
||||
<ol>
|
||||
<li>Open a terminal and navigate to the Keyman source root directory</li>
|
||||
<li>Start the test server with <code>web/build.sh start</code></li>
|
||||
<li>Open <a href="http://localhost:3000/src/test/manual/web/keyboard-errors/index.html">http://localhost:3000/src/test/manual/web/keyboard-errors/index.html</a> in a browser</li>
|
||||
</ol>
|
||||
`;
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
<!--
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
(function() {
|
||||
var me = document.currentScript;
|
||||
console.log(me);
|
||||
|
||||
var onload = me.onload;
|
||||
if(onload) {
|
||||
me.onload = null;
|
||||
}
|
||||
|
||||
document.body.addEventListener('load', function(e) {
|
||||
// Prevent the element's onload event from firing
|
||||
if(e.srcElement == me || e.target == me) {
|
||||
e.cancelBubble = true;
|
||||
}
|
||||
}, {capture: true});
|
||||
|
||||
window.setTimeout(function () {
|
||||
// Restores the function after a slight delay.
|
||||
me.onload = onload;
|
||||
}, 1);
|
||||
})();
|
||||
|
|
@ -22,7 +22,8 @@ builder_describe "Builds the Keyman Engine for Web's development & unit-testing
|
|||
"--ci Does nothing for this script" \
|
||||
":bulk_rendering=testing/bulk_rendering Builds the bulk-rendering tool used to validate changes to OSK display code" \
|
||||
":recorder=testing/recorder Builds the KMW recorder tool used for development of unit-test resources" \
|
||||
":sourcemap-root=building/sourcemap-root Builds the sourcemap-cleaning tool used during minification of app/ builds"
|
||||
":sourcemap-root=building/sourcemap-root Builds the sourcemap-cleaning tool used during minification of app/ builds" \
|
||||
":test-utils=testing/test-utils Builds the test-utils module"
|
||||
|
||||
builder_parse "$@"
|
||||
|
||||
|
|
|
|||
25
web/src/tools/testing/test-server/index.cjs
Normal file
25
web/src/tools/testing/test-server/index.cjs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
const express = require('express')
|
||||
const path = require('path')
|
||||
const app = express()
|
||||
const port = 3000
|
||||
|
||||
app.use(express.static(path.join(__dirname, '../../../../')))
|
||||
|
||||
// for testing timeout error in web/src/test/manual/web/keyboard-errors
|
||||
const router = express.Router()
|
||||
router.get('/src/test/manual/web/keyboard-errors/timeout.js', async (req, res, next) => {
|
||||
console.log('timeout.js requested')
|
||||
return new Promise(() => {
|
||||
setTimeout(() => {
|
||||
res.set('Content-Type', 'application/json');
|
||||
res.status(200);
|
||||
|
||||
next();
|
||||
}, 10500); // > ContextManagerBase.TIMEOUT_THRESHOLD (10 seconds)
|
||||
});
|
||||
})
|
||||
app.use(router)
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Keyman test app listening on port ${port}`)
|
||||
})
|
||||
34
web/src/tools/testing/test-utils/build.sh
Executable file
34
web/src/tools/testing/test-utils/build.sh
Executable file
|
|
@ -0,0 +1,34 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Compile KeymanWeb's automated js/ts test utilities
|
||||
|
||||
## START STANDARD BUILD SCRIPT INCLUDE
|
||||
# adjust relative paths as necessary
|
||||
THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
||||
. "${THIS_SCRIPT%/*}/../../../../../resources/build/builder.inc.sh"
|
||||
## END STANDARD BUILD SCRIPT INCLUDE
|
||||
|
||||
SUBPROJECT_NAME=tools/testing/test-utils
|
||||
. "${KEYMAN_ROOT}/web/common.inc.sh"
|
||||
. "${KEYMAN_ROOT}/resources/shellHelperFunctions.sh"
|
||||
|
||||
################################ Main script ################################
|
||||
|
||||
builder_describe "Automated js/ts test utilities for KeymanWeb" \
|
||||
"clean" \
|
||||
"configure" \
|
||||
"build"
|
||||
|
||||
builder_describe_outputs \
|
||||
configure /node_modules \
|
||||
build "/web/build/${SUBPROJECT_NAME}/obj/index.js"
|
||||
|
||||
builder_parse "$@"
|
||||
|
||||
do_build ( ) {
|
||||
compile "${SUBPROJECT_NAME}"
|
||||
}
|
||||
|
||||
builder_run_action configure verify_npm_setup
|
||||
builder_run_action clean rm -rf "../../../../build/${SUBPROJECT_NAME}/"
|
||||
builder_run_action build do_build
|
||||
31
web/src/tools/testing/test-utils/index.ts
Normal file
31
web/src/tools/testing/test-utils/index.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { assert } from 'chai';
|
||||
|
||||
// export async function assertThrowsAsync(fn: () => Promise<any>, message?: string): Promise<void>;
|
||||
export async function assertThrowsAsync(fn: () => Promise<any>, type?: any, message?: string): Promise<void> {
|
||||
assert(!!type || !!message, 'at least one of type or message must be specified');
|
||||
if (typeof(type) === 'string') {
|
||||
message = type;
|
||||
type = undefined;
|
||||
}
|
||||
try {
|
||||
await fn();
|
||||
assert.fail('Expected function to throw an error, but it did not.');
|
||||
} catch (err) {
|
||||
if (type) {
|
||||
assert.isTrue(err instanceof type, `Expected error to be of type ${type.name}, but got ${err.constructor.name}`);
|
||||
}
|
||||
if (message) {
|
||||
assert.equal((err as Error).message, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function assertThrows(fn: () => any, message?: string): void;
|
||||
export function assertThrows(fn: () => any, type?: any, message?: string): void {
|
||||
assert(!!type || !!message, 'at least one of type or message must be specified');
|
||||
if (typeof(type) === 'string') {
|
||||
message = type;
|
||||
type = undefined;
|
||||
}
|
||||
assert.throws(fn, type, message);
|
||||
}
|
||||
11
web/src/tools/testing/test-utils/tsconfig.json
Normal file
11
web/src/tools/testing/test-utils/tsconfig.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.dom.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"outDir": "../../../../build/tools/testing/test-utils/obj/",
|
||||
"rootDir": ".",
|
||||
"tsBuildInfoFile": "../../../../build/tools/testing/test-utils/obj/tsconfig.tsbuildinfo"
|
||||
},
|
||||
|
||||
"include": [ "*.ts" ],
|
||||
}
|
||||
|
|
@ -41,6 +41,8 @@ fi
|
|||
|
||||
# End common configs.
|
||||
|
||||
builder_run_action test:dom web-test-runner --config "src/test/auto/dom/web-test-runner${WTR_CONFIG}.config.mjs" ${WTR_DEBUG}
|
||||
cd "${KEYMAN_ROOT}"
|
||||
|
||||
builder_run_action test:integrated web-test-runner --config "src/test/auto/integrated/web-test-runner${WTR_CONFIG}.config.mjs" ${WTR_DEBUG}
|
||||
builder_run_action test:dom web-test-runner --config "web/src/test/auto/dom/web-test-runner${WTR_CONFIG}.config.mjs" ${WTR_DEBUG}
|
||||
|
||||
builder_run_action test:integrated web-test-runner --config "web/src/test/auto/integrated/web-test-runner${WTR_CONFIG}.config.mjs" ${WTR_DEBUG}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue