diff --git a/common/test/keyboards/invalid/README.md b/common/test/keyboards/invalid/compiler/README.md
similarity index 100%
rename from common/test/keyboards/invalid/README.md
rename to common/test/keyboards/invalid/compiler/README.md
diff --git a/common/test/keyboards/invalid/source/CERR_400A_balochi_phonetic.kmn b/common/test/keyboards/invalid/compiler/source/CERR_400A_balochi_phonetic.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_400A_balochi_phonetic.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_400A_balochi_phonetic.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_400D_balochi_phonetic.kmn b/common/test/keyboards/invalid/compiler/source/CERR_400D_balochi_phonetic.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_400D_balochi_phonetic.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_400D_balochi_phonetic.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_400E_balochi_phonetic.kmn b/common/test/keyboards/invalid/compiler/source/CERR_400E_balochi_phonetic.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_400E_balochi_phonetic.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_400E_balochi_phonetic.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_4013_balochi_phonetic.kmn b/common/test/keyboards/invalid/compiler/source/CERR_4013_balochi_phonetic.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_4013_balochi_phonetic.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_4013_balochi_phonetic.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_4015_k_014___groups_and_virtual_keys.kmn b/common/test/keyboards/invalid/compiler/source/CERR_4015_k_014___groups_and_virtual_keys.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_4015_k_014___groups_and_virtual_keys.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_4015_k_014___groups_and_virtual_keys.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_4016_k_014___groups_and_virtual_keys.kmn b/common/test/keyboards/invalid/compiler/source/CERR_4016_k_014___groups_and_virtual_keys.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_4016_k_014___groups_and_virtual_keys.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_4016_k_014___groups_and_virtual_keys.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_4017_balochi_phonetic.kmn b/common/test/keyboards/invalid/compiler/source/CERR_4017_balochi_phonetic.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_4017_balochi_phonetic.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_4017_balochi_phonetic.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_4019_balochi_phonetic.kmn b/common/test/keyboards/invalid/compiler/source/CERR_4019_balochi_phonetic.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_4019_balochi_phonetic.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_4019_balochi_phonetic.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_401D_k_034___options_double_set_reset.kmn b/common/test/keyboards/invalid/compiler/source/CERR_401D_k_034___options_double_set_reset.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_401D_k_034___options_double_set_reset.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_401D_k_034___options_double_set_reset.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_4026_balochi_phonetic.kmn b/common/test/keyboards/invalid/compiler/source/CERR_4026_balochi_phonetic.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_4026_balochi_phonetic.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_4026_balochi_phonetic.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_4029_balochi_phonetic.kmn b/common/test/keyboards/invalid/compiler/source/CERR_4029_balochi_phonetic.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_4029_balochi_phonetic.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_4029_balochi_phonetic.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_402B_balochi_phonetic.kmn b/common/test/keyboards/invalid/compiler/source/CERR_402B_balochi_phonetic.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_402B_balochi_phonetic.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_402B_balochi_phonetic.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_402D_balochi_phonetic.kmn b/common/test/keyboards/invalid/compiler/source/CERR_402D_balochi_phonetic.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_402D_balochi_phonetic.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_402D_balochi_phonetic.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_4031_balochi_phonetic.kmn b/common/test/keyboards/invalid/compiler/source/CERR_4031_balochi_phonetic.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_4031_balochi_phonetic.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_4031_balochi_phonetic.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_4034_balochi_phonetic.kmn b/common/test/keyboards/invalid/compiler/source/CERR_4034_balochi_phonetic.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_4034_balochi_phonetic.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_4034_balochi_phonetic.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_4038_balochi_phonetic.kmn b/common/test/keyboards/invalid/compiler/source/CERR_4038_balochi_phonetic.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_4038_balochi_phonetic.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_4038_balochi_phonetic.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_4039_balochi_phonetic.kmn b/common/test/keyboards/invalid/compiler/source/CERR_4039_balochi_phonetic.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_4039_balochi_phonetic.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_4039_balochi_phonetic.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_403A_014_groups_and_virtual_keys.kmn b/common/test/keyboards/invalid/compiler/source/CERR_403A_014_groups_and_virtual_keys.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_403A_014_groups_and_virtual_keys.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_403A_014_groups_and_virtual_keys.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_403E_imsample.kmn b/common/test/keyboards/invalid/compiler/source/CERR_403E_imsample.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_403E_imsample.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_403E_imsample.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_4040_balochi_phonetic.kmn b/common/test/keyboards/invalid/compiler/source/CERR_4040_balochi_phonetic.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_4040_balochi_phonetic.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_4040_balochi_phonetic.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_4044_014_groups_and_virtual_keys.kmn b/common/test/keyboards/invalid/compiler/source/CERR_4044_014_groups_and_virtual_keys.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_4044_014_groups_and_virtual_keys.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_4044_014_groups_and_virtual_keys.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_4045_balochi_phonetic.kmn b/common/test/keyboards/invalid/compiler/source/CERR_4045_balochi_phonetic.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_4045_balochi_phonetic.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_4045_balochi_phonetic.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_4046_balochi_phonetic.kmn b/common/test/keyboards/invalid/compiler/source/CERR_4046_balochi_phonetic.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_4046_balochi_phonetic.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_4046_balochi_phonetic.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_4047_k_022___options_with_preset.kmn b/common/test/keyboards/invalid/compiler/source/CERR_4047_k_022___options_with_preset.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_4047_k_022___options_with_preset.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_4047_k_022___options_with_preset.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_4049_k_022___options_with_preset.kmn b/common/test/keyboards/invalid/compiler/source/CERR_4049_k_022___options_with_preset.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_4049_k_022___options_with_preset.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_4049_k_022___options_with_preset.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_404A_k_025___options_with_reset.kmn b/common/test/keyboards/invalid/compiler/source/CERR_404A_k_025___options_with_reset.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_404A_k_025___options_with_reset.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_404A_k_025___options_with_reset.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_404B_k_025___options_with_reset.kmn b/common/test/keyboards/invalid/compiler/source/CERR_404B_k_025___options_with_reset.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_404B_k_025___options_with_reset.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_404B_k_025___options_with_reset.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_404C_k_023___options_with_save.kmn b/common/test/keyboards/invalid/compiler/source/CERR_404C_k_023___options_with_save.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_404C_k_023___options_with_save.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_404C_k_023___options_with_save.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_404D_balochi_phonetic.kmn b/common/test/keyboards/invalid/compiler/source/CERR_404D_balochi_phonetic.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_404D_balochi_phonetic.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_404D_balochi_phonetic.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_404F_k_026___system_stores.kmn b/common/test/keyboards/invalid/compiler/source/CERR_404F_k_026___system_stores.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_404F_k_026___system_stores.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_404F_k_026___system_stores.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_405C_balochi_phonetic.kmn b/common/test/keyboards/invalid/compiler/source/CERR_405C_balochi_phonetic.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_405C_balochi_phonetic.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_405C_balochi_phonetic.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_405D_balochi_phonetic.kmn b/common/test/keyboards/invalid/compiler/source/CERR_405D_balochi_phonetic.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_405D_balochi_phonetic.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_405D_balochi_phonetic.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_405E_balochi_phonetic.kmn b/common/test/keyboards/invalid/compiler/source/CERR_405E_balochi_phonetic.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_405E_balochi_phonetic.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_405E_balochi_phonetic.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_4060_balochi_phonetic.kmn b/common/test/keyboards/invalid/compiler/source/CERR_4060_balochi_phonetic.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_4060_balochi_phonetic.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_4060_balochi_phonetic.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_4061_balochi_phonetic.kmn b/common/test/keyboards/invalid/compiler/source/CERR_4061_balochi_phonetic.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_4061_balochi_phonetic.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_4061_balochi_phonetic.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_4064_caps_lock_layer_3620.kmn b/common/test/keyboards/invalid/compiler/source/CERR_4064_caps_lock_layer_3620.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_4064_caps_lock_layer_3620.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_4064_caps_lock_layer_3620.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_4065_test_expansion_invalid.kmn b/common/test/keyboards/invalid/compiler/source/CERR_4065_test_expansion_invalid.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_4065_test_expansion_invalid.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_4065_test_expansion_invalid.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_4066_test_expansion_invalid.kmn b/common/test/keyboards/invalid/compiler/source/CERR_4066_test_expansion_invalid.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_4066_test_expansion_invalid.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_4066_test_expansion_invalid.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_4067_test_expansion_invalid.kmn b/common/test/keyboards/invalid/compiler/source/CERR_4067_test_expansion_invalid.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_4067_test_expansion_invalid.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_4067_test_expansion_invalid.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_4068_test_expansion_invalid.kmn b/common/test/keyboards/invalid/compiler/source/CERR_4068_test_expansion_invalid.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_4068_test_expansion_invalid.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_4068_test_expansion_invalid.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_4069_test_casedkeys_invalid_1.kmn b/common/test/keyboards/invalid/compiler/source/CERR_4069_test_casedkeys_invalid_1.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_4069_test_casedkeys_invalid_1.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_4069_test_casedkeys_invalid_1.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_406A_test_casedkeys_invalid_2.kmn b/common/test/keyboards/invalid/compiler/source/CERR_406A_test_casedkeys_invalid_2.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_406A_test_casedkeys_invalid_2.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_406A_test_casedkeys_invalid_2.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_406B_test_casedkeys_mnemonic_1.kmn b/common/test/keyboards/invalid/compiler/source/CERR_406B_test_casedkeys_mnemonic_1.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_406B_test_casedkeys_mnemonic_1.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_406B_test_casedkeys_mnemonic_1.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_406C_test_5963_readonlygroup_usenonreadonly.kmn b/common/test/keyboards/invalid/compiler/source/CERR_406C_test_5963_readonlygroup_usenonreadonly.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_406C_test_5963_readonlygroup_usenonreadonly.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_406C_test_5963_readonlygroup_usenonreadonly.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_406E_test_5963_readonlygroup_misplacedcontext.kmn b/common/test/keyboards/invalid/compiler/source/CERR_406E_test_5963_readonlygroup_misplacedcontext.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_406E_test_5963_readonlygroup_misplacedcontext.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_406E_test_5963_readonlygroup_misplacedcontext.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_406F_test_5963_newcontext_2.kmn b/common/test/keyboards/invalid/compiler/source/CERR_406F_test_5963_newcontext_2.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_406F_test_5963_newcontext_2.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_406F_test_5963_newcontext_2.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_4070_test_5963_postkeystroke_2.kmn b/common/test/keyboards/invalid/compiler/source/CERR_4070_test_5963_postkeystroke_2.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_4070_test_5963_postkeystroke_2.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_4070_test_5963_postkeystroke_2.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_4071_balochi_phonetic.kmn b/common/test/keyboards/invalid/compiler/source/CERR_4071_balochi_phonetic.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_4071_balochi_phonetic.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_4071_balochi_phonetic.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_4072_balochi_phonetic.kmn b/common/test/keyboards/invalid/compiler/source/CERR_4072_balochi_phonetic.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_4072_balochi_phonetic.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_4072_balochi_phonetic.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_4073_balochi_phonetic.kmn b/common/test/keyboards/invalid/compiler/source/CERR_4073_balochi_phonetic.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_4073_balochi_phonetic.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_4073_balochi_phonetic.kmn
diff --git a/common/test/keyboards/invalid/source/CERR_80C0_test_expansion_absurd.kmn b/common/test/keyboards/invalid/compiler/source/CERR_80C0_test_expansion_absurd.kmn
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR_80C0_test_expansion_absurd.kmn
rename to common/test/keyboards/invalid/compiler/source/CERR_80C0_test_expansion_absurd.kmn
diff --git a/common/test/keyboards/invalid/source/CERR__balochi_phonetic.ico b/common/test/keyboards/invalid/compiler/source/CERR__balochi_phonetic.ico
similarity index 100%
rename from common/test/keyboards/invalid/source/CERR__balochi_phonetic.ico
rename to common/test/keyboards/invalid/compiler/source/CERR__balochi_phonetic.ico
diff --git a/common/test/keyboards/invalid/engine/README.md b/common/test/keyboards/invalid/engine/README.md
new file mode 100644
index 0000000000..305a6d2091
--- /dev/null
+++ b/common/test/keyboards/invalid/engine/README.md
@@ -0,0 +1,12 @@
+# Invalid keyboards for testing Keyman Engine
+
+## javascript_error.kmp
+
+A keyboard package that has a Javascript keyboard with an error in `gs()` to test runtime keyboard handling
+
+## TODO: other invalid keyboards to add here
+
+* invalid_packages - missing files, metadata, etc
+* script_load_error - unparseable JS
+* invalid kmx
+* invalid fonts
diff --git a/common/test/keyboards/invalid/engine/javascript_error/HISTORY.md b/common/test/keyboards/invalid/engine/javascript_error/HISTORY.md
new file mode 100644
index 0000000000..fc4cd373a5
--- /dev/null
+++ b/common/test/keyboards/invalid/engine/javascript_error/HISTORY.md
@@ -0,0 +1,6 @@
+Banne Change History
+====================
+
+1.0 (2024-07-12)
+----------------
+* Created by Sophia Ku
diff --git a/common/test/keyboards/invalid/engine/javascript_error/LICENSE.md b/common/test/keyboards/invalid/engine/javascript_error/LICENSE.md
new file mode 100644
index 0000000000..9d9ff006e3
--- /dev/null
+++ b/common/test/keyboards/invalid/engine/javascript_error/LICENSE.md
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright © 2024 SIL
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/common/test/keyboards/invalid/engine/javascript_error/README.md b/common/test/keyboards/invalid/engine/javascript_error/README.md
new file mode 100644
index 0000000000..64ade5335d
--- /dev/null
+++ b/common/test/keyboards/invalid/engine/javascript_error/README.md
@@ -0,0 +1,14 @@
+# javascript_error keyboard
+
+## Description
+
+Keyboard with a script error in `gs()`, taken originally from release/b/banne
+
+The .js and .kmp files in modified_build/ have been manually tweaked to throw the error:
+
+```js
+ this.gs=function(t,e) {
+ throw new Error("javascript_error.js - runtime error");
+ // return this.g_main_0(t,e);
+ };
+```
\ No newline at end of file
diff --git a/common/test/keyboards/invalid/engine/javascript_error/javascript_error.kpj b/common/test/keyboards/invalid/engine/javascript_error/javascript_error.kpj
new file mode 100644
index 0000000000..d3a678044b
--- /dev/null
+++ b/common/test/keyboards/invalid/engine/javascript_error/javascript_error.kpj
@@ -0,0 +1,8 @@
+
+
+
+ 2.0
+ True
+ True
+
+
diff --git a/common/test/keyboards/invalid/engine/javascript_error/modified_build/javascript_error.js b/common/test/keyboards/invalid/engine/javascript_error/modified_build/javascript_error.js
new file mode 100644
index 0000000000..47c3661e20
--- /dev/null
+++ b/common/test/keyboards/invalid/engine/javascript_error/modified_build/javascript_error.js
@@ -0,0 +1,816 @@
+if(typeof keyman === 'undefined') {
+ console.log('Keyboard requires KeymanWeb 10.0 or later');
+ if(typeof tavultesoft !== 'undefined') tavultesoft.keymanweb.util.alert("This keyboard requires KeymanWeb 10.0 or later");
+} else {
+KeymanWeb.KR(new Keyboard_javascript_error());
+}
+function Keyboard_javascript_error()
+{
+ var modCodes = keyman.osk.modifierCodes;
+ var keyCodes = keyman.osk.keyCodes;
+
+ this._v=(typeof keyman!="undefined"&&typeof keyman.version=="string")?parseInt(keyman.version,10):9;
+ this.KI="Keyboard_javascript_error";
+ this.KN="javascript_error";
+ this.KMINVER="10.0";
+ this.KV={F:' 1em "Arial"',K102:0};
+ this.KV.KLS={
+ "default": ["`","1","2","3","4","5","6","7","8","9","0","-","=","","","","q","w","e","r","t","y","u","i","o","p","[","]","\\","","","","a","s","d","f","g","h","j","k","l",";","'","","","","","","\\","z","x","c","v","b","n","m",",",".","/","","","","","",""],
+ "shift": ["~","!","@","#","$","%","^","&","*","(",")","_","+","","","","Q","W","E","R","T","Y","U","I","O","P","{","}","|","","","","A","S","D","F","G","H","J","K","L",":","«","","","","","","|","Z","X","C","V","B","N","M","<",">","?","","","","","",""]
+ };
+ this.KV.BK=(function(x){
+ var
+ empty=Array.apply(null, Array(65)).map(String.prototype.valueOf,""),
+ result=[], v, i,
+ modifiers=['default','shift','ctrl','shift-ctrl','alt','shift-alt','ctrl-alt','shift-ctrl-alt'];
+ for(i=modifiers.length-1;i>=0;i--) {
+ v = x[modifiers[i]];
+ if(v || result.length > 0) {
+ result=(v ? v : empty).slice().concat(result);
+ }
+ }
+ return result;
+ })(this.KV.KLS);
+ this.KDU=0;
+ this.KH='';
+ this.KM=0;
+ this.KBVER="1.0";
+ this.KMBM=modCodes.SHIFT /* 0x0010 */;
+ this.KVKL={
+ "phone": {
+ "font": "Tahoma",
+ "layer": [
+ {
+ "id": "default",
+ "row": [
+ {
+ "id": "1",
+ "key": [
+ {
+ "id": "K_Q",
+ "text": "q"
+ },
+ {
+ "id": "K_W",
+ "text": "w"
+ },
+ {
+ "id": "K_E",
+ "text": "e"
+ },
+ {
+ "id": "K_R",
+ "text": "r"
+ },
+ {
+ "id": "K_T",
+ "text": "t"
+ },
+ {
+ "id": "K_Y",
+ "text": "y"
+ },
+ {
+ "id": "K_U",
+ "text": "u"
+ },
+ {
+ "id": "K_I",
+ "text": "i"
+ },
+ {
+ "id": "K_O",
+ "text": "o"
+ },
+ {
+ "id": "K_P",
+ "text": "p"
+ }
+ ]
+ },
+ {
+ "id": "2",
+ "key": [
+ {
+ "id": "K_A",
+ "text": "a",
+ "pad": "50"
+ },
+ {
+ "id": "K_S",
+ "text": "s",
+ "sk": [
+ {
+ "text": "š",
+ "id": "U_0161"
+ }
+ ]
+ },
+ {
+ "id": "K_D",
+ "text": "d"
+ },
+ {
+ "id": "K_F",
+ "text": "f"
+ },
+ {
+ "id": "K_G",
+ "text": "g"
+ },
+ {
+ "id": "K_H",
+ "text": "h"
+ },
+ {
+ "id": "K_J",
+ "text": "j"
+ },
+ {
+ "id": "K_K",
+ "text": "k"
+ },
+ {
+ "id": "K_L",
+ "text": "l"
+ },
+ {
+ "id": "T_new_88",
+ "width": "10",
+ "sp": "10"
+ }
+ ]
+ },
+ {
+ "id": "3",
+ "key": [
+ {
+ "id": "K_SHIFT",
+ "text": "*Shift*",
+ "sp": "1",
+ "nextlayer": "shift"
+ },
+ {
+ "id": "K_Z",
+ "text": "z"
+ },
+ {
+ "id": "K_X",
+ "text": "x"
+ },
+ {
+ "id": "K_C",
+ "text": "c"
+ },
+ {
+ "id": "K_V",
+ "text": "v"
+ },
+ {
+ "id": "K_B",
+ "text": "b"
+ },
+ {
+ "id": "K_N",
+ "text": "n"
+ },
+ {
+ "id": "K_M",
+ "text": "m"
+ },
+ {
+ "id": "K_PERIOD",
+ "text": ".",
+ "sk": [
+ {
+ "text": ",",
+ "id": "K_COMMA"
+ },
+ {
+ "text": "!",
+ "id": "K_1",
+ "layer": "shift"
+ },
+ {
+ "text": "?",
+ "id": "K_SLASH",
+ "layer": "shift"
+ },
+ {
+ "text": "‹",
+ "id": "U_2039"
+ },
+ {
+ "text": "›",
+ "id": "U_203A"
+ },
+ {
+ "text": "«",
+ "id": "U_00AB",
+ "layer": "shift"
+ },
+ {
+ "text": "»",
+ "id": "U_00BB"
+ },
+ {
+ "text": "\\",
+ "id": "K_BKSLASH"
+ },
+ {
+ "text": ":",
+ "id": "K_COLON",
+ "layer": "shift"
+ },
+ {
+ "text": ";",
+ "id": "K_COLON"
+ }
+ ]
+ },
+ {
+ "id": "K_BKSP",
+ "text": "*BkSp*",
+ "width": "100",
+ "sp": "1"
+ }
+ ]
+ },
+ {
+ "id": "4",
+ "key": [
+ {
+ "id": "K_NUMLOCK",
+ "text": "*123*",
+ "width": "150",
+ "sp": "1",
+ "nextlayer": "numeric"
+ },
+ {
+ "id": "K_LOPT",
+ "text": "*Menu*",
+ "width": "120",
+ "sp": "1"
+ },
+ {
+ "id": "K_SPACE",
+ "width": "610"
+ },
+ {
+ "id": "K_ENTER",
+ "text": "*Enter*",
+ "width": "150",
+ "sp": "1"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "shift",
+ "row": [
+ {
+ "id": "1",
+ "key": [
+ {
+ "id": "K_Q",
+ "text": "Q"
+ },
+ {
+ "id": "K_W",
+ "text": "W"
+ },
+ {
+ "id": "K_E",
+ "text": "E"
+ },
+ {
+ "id": "K_R",
+ "text": "R"
+ },
+ {
+ "id": "K_T",
+ "text": "T"
+ },
+ {
+ "id": "K_Y",
+ "text": "Y"
+ },
+ {
+ "id": "K_U",
+ "text": "U"
+ },
+ {
+ "id": "K_I",
+ "text": "I"
+ },
+ {
+ "id": "K_O",
+ "text": "O"
+ },
+ {
+ "id": "K_P",
+ "text": "P"
+ }
+ ]
+ },
+ {
+ "id": "2",
+ "key": [
+ {
+ "id": "K_A",
+ "text": "A",
+ "pad": "50"
+ },
+ {
+ "id": "K_S",
+ "text": "S",
+ "sk": [
+ {
+ "text": "Š",
+ "id": "U_0160"
+ }
+ ]
+ },
+ {
+ "id": "K_D",
+ "text": "D"
+ },
+ {
+ "id": "K_F",
+ "text": "F"
+ },
+ {
+ "id": "K_G",
+ "text": "G"
+ },
+ {
+ "id": "K_H",
+ "text": "H"
+ },
+ {
+ "id": "K_J",
+ "text": "J"
+ },
+ {
+ "id": "K_K",
+ "text": "K"
+ },
+ {
+ "id": "K_L",
+ "text": "L"
+ },
+ {
+ "id": "T_new_122",
+ "width": "10",
+ "sp": "10"
+ }
+ ]
+ },
+ {
+ "id": "3",
+ "key": [
+ {
+ "id": "K_SHIFT",
+ "text": "*Shift*",
+ "sp": "2",
+ "nextlayer": "default"
+ },
+ {
+ "id": "K_Z",
+ "text": "Z"
+ },
+ {
+ "id": "K_X",
+ "text": "X"
+ },
+ {
+ "id": "K_C",
+ "text": "C"
+ },
+ {
+ "id": "K_V",
+ "text": "V"
+ },
+ {
+ "id": "K_B",
+ "text": "B"
+ },
+ {
+ "id": "K_N",
+ "text": "N"
+ },
+ {
+ "id": "K_M",
+ "text": "M"
+ },
+ {
+ "id": "K_PERIOD",
+ "text": ".",
+ "layer": "default",
+ "sk": [
+ {
+ "text": ",",
+ "id": "K_COMMA",
+ "layer": "default"
+ },
+ {
+ "text": "!",
+ "id": "K_1",
+ "layer": "shift"
+ },
+ {
+ "text": "?",
+ "id": "K_SLASH",
+ "layer": "shift"
+ },
+ {
+ "text": "‹",
+ "id": "U_2039",
+ "layer": "default"
+ },
+ {
+ "text": "›",
+ "id": "U_203A"
+ },
+ {
+ "text": "«",
+ "id": "U_00AB",
+ "layer": "shift"
+ },
+ {
+ "text": "»",
+ "id": "U_00BB"
+ },
+ {
+ "text": "\\",
+ "id": "K_BKSLASH",
+ "layer": "default"
+ },
+ {
+ "text": ":",
+ "id": "K_COLON",
+ "layer": "shift"
+ },
+ {
+ "text": ";",
+ "id": "K_COLON",
+ "layer": "default"
+ }
+ ]
+ },
+ {
+ "id": "K_BKSP",
+ "text": "*BkSp*",
+ "sp": "1"
+ }
+ ]
+ },
+ {
+ "id": "4",
+ "key": [
+ {
+ "id": "K_NUMLOCK",
+ "text": "*123*",
+ "width": "150",
+ "sp": "1",
+ "nextlayer": "numeric"
+ },
+ {
+ "id": "K_LOPT",
+ "text": "*Menu*",
+ "width": "120",
+ "sp": "1"
+ },
+ {
+ "id": "K_SPACE",
+ "width": "610"
+ },
+ {
+ "id": "K_ENTER",
+ "text": "*Enter*",
+ "width": "150",
+ "sp": "1"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "numeric",
+ "row": [
+ {
+ "id": "1",
+ "key": [
+ {
+ "id": "K_1",
+ "text": "1"
+ },
+ {
+ "id": "K_2",
+ "text": "2"
+ },
+ {
+ "id": "K_3",
+ "text": "3"
+ },
+ {
+ "id": "K_4",
+ "text": "4"
+ },
+ {
+ "id": "K_5",
+ "text": "5"
+ },
+ {
+ "id": "K_6",
+ "text": "6"
+ },
+ {
+ "id": "K_7",
+ "text": "7"
+ },
+ {
+ "id": "K_8",
+ "text": "8"
+ },
+ {
+ "id": "K_9",
+ "text": "9"
+ },
+ {
+ "id": "K_0",
+ "text": "0",
+ "width": "100"
+ }
+ ]
+ },
+ {
+ "id": "2",
+ "key": [
+ {
+ "id": "K_4",
+ "text": "$",
+ "pad": "50",
+ "layer": "shift"
+ },
+ {
+ "id": "K_2",
+ "text": "@",
+ "layer": "shift"
+ },
+ {
+ "id": "K_3",
+ "text": "#",
+ "layer": "shift"
+ },
+ {
+ "id": "K_5",
+ "text": "%",
+ "layer": "shift"
+ },
+ {
+ "id": "K_7",
+ "text": "&",
+ "layer": "shift"
+ },
+ {
+ "id": "K_HYPHEN",
+ "text": "-"
+ },
+ {
+ "id": "K_HYPHEN",
+ "text": "_",
+ "layer": "shift"
+ },
+ {
+ "id": "K_EQUAL",
+ "text": "=",
+ "layer": "default"
+ },
+ {
+ "id": "K_BKSLASH",
+ "text": "|",
+ "layer": "shift"
+ },
+ {
+ "id": "K_BKSLASH",
+ "text": "\\",
+ "layer": "default"
+ }
+ ]
+ },
+ {
+ "id": "3",
+ "key": [
+ {
+ "id": "K_LBRKT",
+ "text": "[",
+ "pad": "110",
+ "sk": [
+ {
+ "text": "<",
+ "id": "K_COMMA",
+ "layer": "shift"
+ },
+ {
+ "text": "{",
+ "id": "K_LBRKT",
+ "layer": "shift"
+ }
+ ]
+ },
+ {
+ "id": "K_9",
+ "text": "(",
+ "layer": "shift",
+ "sk": [
+ {
+ "text": "‹",
+ "id": "U_2039"
+ },
+ {
+ "text": "«",
+ "id": "U_00AB"
+ }
+ ]
+ },
+ {
+ "id": "K_0",
+ "text": ")",
+ "layer": "shift",
+ "sk": [
+ {
+ "text": "›",
+ "id": "U_203A"
+ },
+ {
+ "text": "»",
+ "id": "U_00BB"
+ }
+ ]
+ },
+ {
+ "id": "K_RBRKT",
+ "text": "]",
+ "sk": [
+ {
+ "text": ">",
+ "id": "K_PERIOD",
+ "layer": "shift"
+ },
+ {
+ "text": "}",
+ "id": "K_RBRKT",
+ "layer": "shift"
+ }
+ ]
+ },
+ {
+ "id": "U_0027",
+ "text": "'"
+ },
+ {
+ "id": "U_0022",
+ "text": "\""
+ },
+ {
+ "id": "K_EQUAL",
+ "text": "+",
+ "layer": "shift"
+ },
+ {
+ "id": "K_8",
+ "text": "*",
+ "layer": "shift"
+ },
+ {
+ "id": "K_SLASH",
+ "text": "/"
+ },
+ {
+ "id": "K_BKSP",
+ "text": "*BkSp*",
+ "width": "100",
+ "sp": "1"
+ }
+ ]
+ },
+ {
+ "id": "4",
+ "key": [
+ {
+ "id": "K_LOWER",
+ "text": "*abc*",
+ "width": "150",
+ "sp": "1",
+ "nextlayer": "default"
+ },
+ {
+ "id": "K_LOPT",
+ "text": "*Menu*",
+ "width": "120",
+ "sp": "1"
+ },
+ {
+ "id": "K_SPACE",
+ "width": "610"
+ },
+ {
+ "id": "K_ENTER",
+ "text": "*Enter*",
+ "width": "150",
+ "sp": "1"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "displayUnderlying": false
+ }
+};
+ this.KVER="19.0.254.0";
+ this.KVS=[];
+ this.gs=function(t,e) {
+ throw new Error("javascript_error.js - runtime error");
+ // return this.g_main_0(t,e);
+ };
+ this.g_main_0=function(t,e) {
+ var k=KeymanWeb,r=0,m=0;
+ if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_QUOTE /* 0xDE */)) {
+ if(k.KFCM(1,t,['«'])){
+ r=m=1; // Line 26
+ k.KDC(1,t);
+ k.KO(-1,t,"»");
+ }
+ else if(k.KFCM(1,t,['»'])){
+ r=m=1; // Line 27
+ k.KDC(1,t);
+ k.KO(-1,t,"«");
+ }
+ else if(k.KFCM(1,t,[';'])){
+ r=m=1; // Line 30
+ k.KDC(1,t);
+ k.KO(-1,t,"\"");
+ }
+ else if(1){
+ r=m=1; // Line 25
+ k.KDC(0,t);
+ k.KO(-1,t,"«");
+ }
+ }
+ else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_QUOTE /* 0xDE */)) {
+ if(k.KFCM(1,t,['\''])){
+ r=m=1; // Line 20
+ k.KDC(1,t);
+ k.KO(-1,t,"‹");
+ }
+ else if(k.KFCM(1,t,['‹'])){
+ r=m=1; // Line 21
+ k.KDC(1,t);
+ k.KO(-1,t,"›");
+ }
+ else if(k.KFCM(1,t,['›'])){
+ r=m=1; // Line 22
+ k.KDC(1,t);
+ k.KO(-1,t,"‹");
+ }
+ else if(k.KFCM(1,t,['<'])){
+ r=m=1; // Line 33
+ k.KDC(1,t);
+ k.KO(-1,t,"‹");
+ }
+ else if(k.KFCM(1,t,['>'])){
+ r=m=1; // Line 34
+ k.KDC(1,t);
+ k.KO(-1,t,"›");
+ }
+ else if(k.KFCM(1,t,['s'])){
+ r=m=1; // Line 42
+ k.KDC(1,t);
+ k.KO(-1,t,"š");
+ }
+ else if(k.KFCM(1,t,['S'])){
+ r=m=1; // Line 43
+ k.KDC(1,t);
+ k.KO(-1,t,"Š");
+ }
+ }
+ else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_COMMA /* 0xBC */)) {
+ if(k.KFCM(1,t,['<'])){
+ r=m=1; // Line 35
+ k.KDC(1,t);
+ k.KO(-1,t,"«");
+ }
+ }
+ else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_PERIOD /* 0xBE */)) {
+ if(k.KFCM(1,t,['>'])){
+ r=m=1; // Line 36
+ k.KDC(1,t);
+ k.KO(-1,t,"»");
+ }
+ }
+ return r;
+ };
+}
diff --git a/common/test/keyboards/invalid/engine/javascript_error/modified_build/javascript_error.keyboard_info b/common/test/keyboards/invalid/engine/javascript_error/modified_build/javascript_error.keyboard_info
new file mode 100644
index 0000000000..234993db7d
--- /dev/null
+++ b/common/test/keyboards/invalid/engine/javascript_error/modified_build/javascript_error.keyboard_info
@@ -0,0 +1,39 @@
+{
+ "id": "javascript_error",
+ "name": "javascript_error",
+ "license": "mit",
+ "authorName": "Marc Durdin",
+ "description": "javascript_error test keyboard
",
+ "languages": {
+ "amf": {
+ "examples": [],
+ "languageName": "Hamer-Banne",
+ "displayName": "Hamer-Banne"
+ }
+ },
+ "lastModifiedDate": "2026-07-13T07:51:37.473Z",
+ "packageFilename": "javascript_error.kmp",
+ "packageFileSize": 6898,
+ "jsFilename": "javascript_error.js",
+ "jsFileSize": 20557,
+ "packageIncludes": [
+ "visualKeyboard",
+ "welcome",
+ "documentation"
+ ],
+ "version": "1.0",
+ "encodings": [
+ "unicode"
+ ],
+ "platformSupport": {
+ "windows": "full",
+ "macos": "full",
+ "linux": "full",
+ "desktopWeb": "full",
+ "ios": "full",
+ "android": "full",
+ "mobileWeb": "full"
+ },
+ "minKeymanVersion": "10.0",
+ "helpLink": "https://help.keyman.com/keyboard/javascript_error"
+}
\ No newline at end of file
diff --git a/common/test/keyboards/invalid/engine/javascript_error/modified_build/javascript_error.kmp b/common/test/keyboards/invalid/engine/javascript_error/modified_build/javascript_error.kmp
new file mode 100644
index 0000000000..a7e846f245
Binary files /dev/null and b/common/test/keyboards/invalid/engine/javascript_error/modified_build/javascript_error.kmp differ
diff --git a/common/test/keyboards/invalid/engine/javascript_error/modified_build/javascript_error.kmx b/common/test/keyboards/invalid/engine/javascript_error/modified_build/javascript_error.kmx
new file mode 100644
index 0000000000..d4fcf5dc0f
Binary files /dev/null and b/common/test/keyboards/invalid/engine/javascript_error/modified_build/javascript_error.kmx differ
diff --git a/common/test/keyboards/invalid/engine/javascript_error/modified_build/javascript_error.kvk b/common/test/keyboards/invalid/engine/javascript_error/modified_build/javascript_error.kvk
new file mode 100644
index 0000000000..be522b991f
Binary files /dev/null and b/common/test/keyboards/invalid/engine/javascript_error/modified_build/javascript_error.kvk differ
diff --git a/common/test/keyboards/invalid/engine/javascript_error/source/javascript_error.keyman-touch-layout b/common/test/keyboards/invalid/engine/javascript_error/source/javascript_error.keyman-touch-layout
new file mode 100644
index 0000000000..6bd2c4cb68
--- /dev/null
+++ b/common/test/keyboards/invalid/engine/javascript_error/source/javascript_error.keyman-touch-layout
@@ -0,0 +1,701 @@
+{
+ "phone": {
+ "font": "Tahoma",
+ "layer": [
+ {
+ "id": "default",
+ "row": [
+ {
+ "id": 1,
+ "key": [
+ {
+ "id": "K_Q",
+ "text": "q"
+ },
+ {
+ "id": "K_W",
+ "text": "w"
+ },
+ {
+ "id": "K_E",
+ "text": "e"
+ },
+ {
+ "id": "K_R",
+ "text": "r"
+ },
+ {
+ "id": "K_T",
+ "text": "t"
+ },
+ {
+ "id": "K_Y",
+ "text": "y"
+ },
+ {
+ "id": "K_U",
+ "text": "u"
+ },
+ {
+ "id": "K_I",
+ "text": "i"
+ },
+ {
+ "id": "K_O",
+ "text": "o"
+ },
+ {
+ "id": "K_P",
+ "text": "p"
+ }
+ ]
+ },
+ {
+ "id": 2,
+ "key": [
+ {
+ "id": "K_A",
+ "text": "a",
+ "pad": 50
+ },
+ {
+ "id": "K_S",
+ "text": "s",
+ "sk": [
+ {
+ "text": "š",
+ "id": "U_0161"
+ }
+ ]
+ },
+ {
+ "id": "K_D",
+ "text": "d"
+ },
+ {
+ "id": "K_F",
+ "text": "f"
+ },
+ {
+ "id": "K_G",
+ "text": "g"
+ },
+ {
+ "id": "K_H",
+ "text": "h"
+ },
+ {
+ "id": "K_J",
+ "text": "j"
+ },
+ {
+ "id": "K_K",
+ "text": "k"
+ },
+ {
+ "id": "K_L",
+ "text": "l"
+ },
+ {
+ "id": "T_new_88",
+ "text": "",
+ "width": 10,
+ "sp": 10
+ }
+ ]
+ },
+ {
+ "id": 3,
+ "key": [
+ {
+ "id": "K_SHIFT",
+ "text": "*Shift*",
+ "sp": 1,
+ "nextlayer": "shift"
+ },
+ {
+ "id": "K_Z",
+ "text": "z"
+ },
+ {
+ "id": "K_X",
+ "text": "x"
+ },
+ {
+ "id": "K_C",
+ "text": "c"
+ },
+ {
+ "id": "K_V",
+ "text": "v"
+ },
+ {
+ "id": "K_B",
+ "text": "b"
+ },
+ {
+ "id": "K_N",
+ "text": "n"
+ },
+ {
+ "id": "K_M",
+ "text": "m"
+ },
+ {
+ "id": "K_PERIOD",
+ "text": ".",
+ "sk": [
+ {
+ "text": ",",
+ "id": "K_COMMA"
+ },
+ {
+ "text": "!",
+ "id": "K_1",
+ "layer": "shift"
+ },
+ {
+ "text": "?",
+ "id": "K_SLASH",
+ "layer": "shift"
+ },
+ {
+ "text": "‹",
+ "id": "U_2039"
+ },
+ {
+ "text": "›",
+ "id": "U_203A"
+ },
+ {
+ "text": "«",
+ "id": "U_00AB",
+ "layer": "shift"
+ },
+ {
+ "text": "»",
+ "id": "U_00BB"
+ },
+ {
+ "text": "\\",
+ "id": "K_BKSLASH"
+ },
+ {
+ "text": ":",
+ "id": "K_COLON",
+ "layer": "shift"
+ },
+ {
+ "text": ";",
+ "id": "K_COLON"
+ }
+ ]
+ },
+ {
+ "id": "K_BKSP",
+ "text": "*BkSp*",
+ "width": 100,
+ "sp": 1
+ }
+ ]
+ },
+ {
+ "id": 4,
+ "key": [
+ {
+ "id": "K_NUMLOCK",
+ "text": "*123*",
+ "width": 150,
+ "sp": 1,
+ "nextlayer": "numeric"
+ },
+ {
+ "id": "K_LOPT",
+ "text": "*Menu*",
+ "width": 120,
+ "sp": 1
+ },
+ {
+ "id": "K_SPACE",
+ "text": "",
+ "width": 610,
+ "sp": 0
+ },
+ {
+ "id": "K_ENTER",
+ "text": "*Enter*",
+ "width": 150,
+ "sp": 1
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "shift",
+ "row": [
+ {
+ "id": 1,
+ "key": [
+ {
+ "id": "K_Q",
+ "text": "Q"
+ },
+ {
+ "id": "K_W",
+ "text": "W"
+ },
+ {
+ "id": "K_E",
+ "text": "E"
+ },
+ {
+ "id": "K_R",
+ "text": "R"
+ },
+ {
+ "id": "K_T",
+ "text": "T"
+ },
+ {
+ "id": "K_Y",
+ "text": "Y"
+ },
+ {
+ "id": "K_U",
+ "text": "U"
+ },
+ {
+ "id": "K_I",
+ "text": "I"
+ },
+ {
+ "id": "K_O",
+ "text": "O"
+ },
+ {
+ "id": "K_P",
+ "text": "P"
+ }
+ ]
+ },
+ {
+ "id": 2,
+ "key": [
+ {
+ "id": "K_A",
+ "text": "A",
+ "pad": 50
+ },
+ {
+ "id": "K_S",
+ "text": "S",
+ "sk": [
+ {
+ "text": "Š",
+ "id": "U_0160"
+ }
+ ]
+ },
+ {
+ "id": "K_D",
+ "text": "D"
+ },
+ {
+ "id": "K_F",
+ "text": "F"
+ },
+ {
+ "id": "K_G",
+ "text": "G"
+ },
+ {
+ "id": "K_H",
+ "text": "H"
+ },
+ {
+ "id": "K_J",
+ "text": "J"
+ },
+ {
+ "id": "K_K",
+ "text": "K"
+ },
+ {
+ "id": "K_L",
+ "text": "L"
+ },
+ {
+ "id": "T_new_122",
+ "text": "",
+ "width": 10,
+ "sp": 10
+ }
+ ]
+ },
+ {
+ "id": 3,
+ "key": [
+ {
+ "id": "K_SHIFT",
+ "text": "*Shift*",
+ "sp": 2,
+ "nextlayer": "default"
+ },
+ {
+ "id": "K_Z",
+ "text": "Z"
+ },
+ {
+ "id": "K_X",
+ "text": "X"
+ },
+ {
+ "id": "K_C",
+ "text": "C"
+ },
+ {
+ "id": "K_V",
+ "text": "V"
+ },
+ {
+ "id": "K_B",
+ "text": "B"
+ },
+ {
+ "id": "K_N",
+ "text": "N"
+ },
+ {
+ "id": "K_M",
+ "text": "M"
+ },
+ {
+ "id": "K_PERIOD",
+ "text": ".",
+ "layer": "default",
+ "sk": [
+ {
+ "text": ",",
+ "id": "K_COMMA",
+ "layer": "default"
+ },
+ {
+ "text": "!",
+ "id": "K_1",
+ "layer": "shift"
+ },
+ {
+ "text": "?",
+ "id": "K_SLASH",
+ "layer": "shift"
+ },
+ {
+ "text": "‹",
+ "id": "U_2039",
+ "layer": "default"
+ },
+ {
+ "text": "›",
+ "id": "U_203A"
+ },
+ {
+ "text": "«",
+ "id": "U_00AB",
+ "layer": "shift"
+ },
+ {
+ "text": "»",
+ "id": "U_00BB"
+ },
+ {
+ "text": "\\",
+ "id": "K_BKSLASH",
+ "layer": "default"
+ },
+ {
+ "text": ":",
+ "id": "K_COLON",
+ "layer": "shift"
+ },
+ {
+ "text": ";",
+ "id": "K_COLON",
+ "layer": "default"
+ }
+ ]
+ },
+ {
+ "id": "K_BKSP",
+ "text": "*BkSp*",
+ "sp": 1
+ }
+ ]
+ },
+ {
+ "id": 4,
+ "key": [
+ {
+ "id": "K_NUMLOCK",
+ "text": "*123*",
+ "width": 150,
+ "sp": 1,
+ "nextlayer": "numeric"
+ },
+ {
+ "id": "K_LOPT",
+ "text": "*Menu*",
+ "width": 120,
+ "sp": 1
+ },
+ {
+ "id": "K_SPACE",
+ "text": "",
+ "width": 610,
+ "sp": 0
+ },
+ {
+ "id": "K_ENTER",
+ "text": "*Enter*",
+ "width": 150,
+ "sp": 1
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "numeric",
+ "row": [
+ {
+ "id": 1,
+ "key": [
+ {
+ "id": "K_1",
+ "text": "1"
+ },
+ {
+ "id": "K_2",
+ "text": "2"
+ },
+ {
+ "id": "K_3",
+ "text": "3"
+ },
+ {
+ "id": "K_4",
+ "text": "4"
+ },
+ {
+ "id": "K_5",
+ "text": "5"
+ },
+ {
+ "id": "K_6",
+ "text": "6"
+ },
+ {
+ "id": "K_7",
+ "text": "7"
+ },
+ {
+ "id": "K_8",
+ "text": "8"
+ },
+ {
+ "id": "K_9",
+ "text": "9"
+ },
+ {
+ "id": "K_0",
+ "text": "0",
+ "width": 100
+ }
+ ]
+ },
+ {
+ "id": 2,
+ "key": [
+ {
+ "id": "K_4",
+ "text": "$",
+ "pad": 50,
+ "layer": "shift"
+ },
+ {
+ "id": "K_2",
+ "text": "@",
+ "layer": "shift"
+ },
+ {
+ "id": "K_3",
+ "text": "#",
+ "layer": "shift"
+ },
+ {
+ "id": "K_5",
+ "text": "%",
+ "layer": "shift"
+ },
+ {
+ "id": "K_7",
+ "text": "&",
+ "layer": "shift"
+ },
+ {
+ "id": "K_HYPHEN",
+ "text": "-"
+ },
+ {
+ "id": "K_HYPHEN",
+ "text": "_",
+ "layer": "shift"
+ },
+ {
+ "id": "K_EQUAL",
+ "text": "=",
+ "layer": "default"
+ },
+ {
+ "id": "K_BKSLASH",
+ "text": "|",
+ "layer": "shift"
+ },
+ {
+ "id": "K_BKSLASH",
+ "text": "\\",
+ "layer": "default"
+ }
+ ]
+ },
+ {
+ "id": 3,
+ "key": [
+ {
+ "id": "K_LBRKT",
+ "text": "[",
+ "pad": 110,
+ "sk": [
+ {
+ "text": "<",
+ "id": "K_COMMA",
+ "layer": "shift"
+ },
+ {
+ "text": "{",
+ "id": "K_LBRKT",
+ "layer": "shift"
+ }
+ ]
+ },
+ {
+ "id": "K_9",
+ "text": "(",
+ "layer": "shift",
+ "sk": [
+ {
+ "text": "‹",
+ "id": "U_2039"
+ },
+ {
+ "text": "«",
+ "id": "U_00AB"
+ }
+ ]
+ },
+ {
+ "id": "K_0",
+ "text": ")",
+ "layer": "shift",
+ "sk": [
+ {
+ "text": "›",
+ "id": "U_203A"
+ },
+ {
+ "text": "»",
+ "id": "U_00BB"
+ }
+ ]
+ },
+ {
+ "id": "K_RBRKT",
+ "text": "]",
+ "sk": [
+ {
+ "text": ">",
+ "id": "K_PERIOD",
+ "layer": "shift"
+ },
+ {
+ "text": "}",
+ "id": "K_RBRKT",
+ "layer": "shift"
+ }
+ ]
+ },
+ {
+ "id": "U_0027",
+ "text": "'"
+ },
+ {
+ "id": "U_0022",
+ "text": "\""
+ },
+ {
+ "id": "K_EQUAL",
+ "text": "+",
+ "layer": "shift"
+ },
+ {
+ "id": "K_8",
+ "text": "*",
+ "layer": "shift"
+ },
+ {
+ "id": "K_SLASH",
+ "text": "/"
+ },
+ {
+ "id": "K_BKSP",
+ "text": "*BkSp*",
+ "width": 100,
+ "sp": 1
+ }
+ ]
+ },
+ {
+ "id": 4,
+ "key": [
+ {
+ "id": "K_LOWER",
+ "text": "*abc*",
+ "width": 150,
+ "sp": 1,
+ "nextlayer": "default"
+ },
+ {
+ "id": "K_LOPT",
+ "text": "*Menu*",
+ "width": 120,
+ "sp": 1
+ },
+ {
+ "id": "K_SPACE",
+ "text": "",
+ "width": 610,
+ "sp": 0
+ },
+ {
+ "id": "K_ENTER",
+ "text": "*Enter*",
+ "width": 150,
+ "sp": 1
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/common/test/keyboards/invalid/engine/javascript_error/source/javascript_error.kmn b/common/test/keyboards/invalid/engine/javascript_error/source/javascript_error.kmn
new file mode 100644
index 0000000000..38925963e7
--- /dev/null
+++ b/common/test/keyboards/invalid/engine/javascript_error/source/javascript_error.kmn
@@ -0,0 +1,43 @@
+c banne generated from template at 2024-07-12 10:16:50
+c with name "Banne"
+store(&VERSION) '10.0'
+store(&NAME) 'javascript_error'
+store(©RIGHT) 'Copyright © SIL'
+store(&KEYBOARDVERSION) '1.0'
+store(&TARGETS) 'any'
+store(&VISUALKEYBOARD) 'javascript_error.kvks'
+store(&LAYOUTFILE) 'javascript_error.keyman-touch-layout'
+
+begin Unicode > use(main)
+
+group(main) using keys
+
+c ------------------------------------------------------------------------------
+c punctuation characters
+c ------------------------------------------------------------------------------
+
+c Toggle between ‹ and › by continuing to input '
+"'" + "'" > U+2039 c ‹
+U+2039 + "'" > U+203A c ›
+U+203A + "'" > U+2039 c ‹
+
+c Toggle between « and » by continuing to input "
++ '"' > U+00AB c «
+U+00AB + '"' > U+00BB c »
+U+00BB + '"' > U+00AB c «
+
+c Revert to default " character
+";" + '"' > '"'
+
+c Use angle brackets to type characters
+"<" + "'" > U+2039 c ‹
+">" + "'" > U+203A c ›
+"<" + "<" > U+00AB c «
+">" + ">" > U+00BB c »
+
+c ------------------------------------------------------------------------------
+c s with caron
+c ------------------------------------------------------------------------------
+
+"s" + "'" > U+0161 c š
+"S" + "'" > U+0160 c Š
diff --git a/common/test/keyboards/invalid/engine/javascript_error/source/javascript_error.kps b/common/test/keyboards/invalid/engine/javascript_error/source/javascript_error.kps
new file mode 100644
index 0000000000..56d8e9edb8
--- /dev/null
+++ b/common/test/keyboards/invalid/engine/javascript_error/source/javascript_error.kps
@@ -0,0 +1,75 @@
+
+
+
+ 17.0.326.0
+ 7.0
+
+
+
+ readme.htm
+ ..\LICENSE.md
+
+
+
+
+
+
+
+
+
+ javascript_error
+ Copyright © SIL
+ Marc Durdin
+ javascript_error test keyboard
+
+
+
+
+ ..\build\javascript_error.kmx
+
+ 0
+ .kmx
+
+
+ ..\build\javascript_error.js
+
+ 0
+ .js
+
+
+ ..\build\javascript_error.kvk
+
+ 0
+ .kvk
+
+
+ welcome.htm
+
+ 0
+ .htm
+
+
+ readme.htm
+
+ 0
+ .htm
+
+
+ ..\LICENSE.md
+
+ 0
+ .md
+
+
+
+
+ javascript_error
+ javascript_error
+ 1.0
+
+ Hamer-Banna
+
+
+
+
+
diff --git a/common/test/keyboards/invalid/engine/javascript_error/source/javascript_error.kvks b/common/test/keyboards/invalid/engine/javascript_error/source/javascript_error.kvks
new file mode 100644
index 0000000000..d4171d4e01
--- /dev/null
+++ b/common/test/keyboards/invalid/engine/javascript_error/source/javascript_error.kvks
@@ -0,0 +1,110 @@
+
+
+
+
+
+ `
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 0
+ -
+ =
+ q
+ w
+ e
+ r
+ t
+ y
+ u
+ i
+ o
+ p
+ [
+ ]
+ \
+ a
+ s
+ d
+ f
+ g
+ h
+ j
+ k
+ l
+ ;
+ '
+ \
+ z
+ x
+ c
+ v
+ b
+ n
+ m
+ ,
+ .
+ /
+
+
+ ~
+ !
+ @
+ #
+ $
+ %
+ ^
+ &
+ *
+ (
+ )
+ _
+ +
+ Q
+ W
+ E
+ R
+ T
+ Y
+ U
+ I
+ O
+ P
+ {
+ }
+ |
+ A
+ S
+ D
+ F
+ G
+ H
+ J
+ K
+ L
+ :
+ «
+ |
+ Z
+ X
+ C
+ V
+ B
+ N
+ M
+ <
+ >
+ ?
+
+
+
diff --git a/common/test/keyboards/invalid/engine/javascript_error/source/readme.htm b/common/test/keyboards/invalid/engine/javascript_error/source/readme.htm
new file mode 100644
index 0000000000..80f2c3f038
--- /dev/null
+++ b/common/test/keyboards/invalid/engine/javascript_error/source/readme.htm
@@ -0,0 +1,22 @@
+
+
+
+
+
+ Banne
+
+
+
+
+javascript_error
+
+
+Keyboard with a script error in `gs()`, taken originally from release/b/banne. The .js and .kmp files in modified_build/ have been manually tweaked to throw an error.
+
+
+
+
diff --git a/common/test/keyboards/invalid/engine/javascript_error/source/welcome.htm b/common/test/keyboards/invalid/engine/javascript_error/source/welcome.htm
new file mode 100644
index 0000000000..acc98782a0
--- /dev/null
+++ b/common/test/keyboards/invalid/engine/javascript_error/source/welcome.htm
@@ -0,0 +1,178 @@
+
+
+
+
+
+ Start Using Banne
+
+
+
+
+Start Using Banne
+
+
+Keyboard with a script error in `gs()`, taken originally from release/b/banne. The .js and .kmp files in modified_build/ have been manually tweaked to throw an error.
+
+
+Keyboard Layout
+Keystrokes
+
+
+
+ | Base |
+ + |
+ Modifier |
+ = |
+ Result |
+
+
+
+
+ | s |
+ + |
+ ' |
+ = |
+ š |
+
+
+ | S |
+ + |
+ ' |
+ = |
+ Š |
+
+
+
+Punctuation
+
+ The ' key has been modified to cycle between ‹ (U+2039) and › (U+203A) from the second keypress onwards.
+
+ The first keypress will produce the ' (U+0027) character as expected, and pressing it again will turn the ' into a ‹. Pressing the ' key one more time will produce a ›.
+
+
+ The " key has been modified to automatically type the character « (U+00AB). To cancel this, pressing the ; key before typing a " will produce a normal ".
+
+ Pressing the " key repeatedly will cycle between the » (U+00BB) and « characters.
+
+
+
+
+
+
+ | Base |
+ + |
+ Modifier |
+ = |
+ Result |
+
+
+
+
+ | ' |
+ + |
+ ' |
+ = |
+ ‹ |
+
+
+ | < |
+ + |
+ ' |
+ = |
+ ‹ |
+
+
+ | > |
+ + |
+ ' |
+ = |
+ › |
+
+
+ | " |
+ |
+ |
+ = |
+ « |
+
+
+ | < |
+ + |
+ < |
+ = |
+ « |
+
+
+ | > |
+ + |
+ > |
+ = |
+ » |
+
+
+
+
+
+
+
+ | Base |
+ + |
+ Modifier |
+ = |
+ Result |
+
+
+
+
+ | ‹ |
+ + |
+ ' |
+ = |
+ › |
+
+
+ | › |
+ + |
+ ' |
+ = |
+ ‹ |
+
+
+ | « |
+ + |
+ " |
+ = |
+ » |
+
+
+ | » |
+ + |
+ " |
+ = |
+ « |
+
+
+ | ; |
+ + |
+ " |
+ = |
+ " |
+
+
+
+
+
+
+
\ No newline at end of file