mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-10 19:05:31 +00:00
change(web): element-wrapper modularization + unit test conversion
This commit is contained in:
parent
62cdf6a054
commit
fc84af4842
38 changed files with 2656 additions and 2494 deletions
|
|
@ -72,8 +72,8 @@ module.exports = {
|
|||
|
||||
proxies: {
|
||||
"/resources/": "/base/common/test/resources/",
|
||||
"/node_modules/": "/base/node_modules/",
|
||||
"/@keymanapp/lm-worker/": "/base/node_modules/@keymanapp/lm-worker/"
|
||||
// "/node_modules/": "/base/node_modules/",
|
||||
// "/@keymanapp/lm-worker/": "/base/node_modules/@keymanapp/lm-worker/"
|
||||
},
|
||||
|
||||
// web server port
|
||||
|
|
|
|||
|
|
@ -59,5 +59,6 @@
|
|||
"@keymanapp/web-utils": "*",
|
||||
"@types/node": "^11.9.4",
|
||||
"eventemitter3": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
|
|
|
|||
24
web/src/engine/device-detect/build-bundler.js
Normal file
24
web/src/engine/device-detect/build-bundler.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Note: while this file is not meant to exist long-term, it provides a nice
|
||||
* low-level proof-of-concept for esbuild bundling of the various Web submodules.
|
||||
*
|
||||
* Add some extra code at the end of src/index.ts and run it to verify successful bundling!
|
||||
*/
|
||||
|
||||
import esbuild from 'esbuild';
|
||||
import { spawn } from 'child_process';
|
||||
|
||||
await esbuild.build({
|
||||
bundle: true,
|
||||
sourcemap: true,
|
||||
format: "esm",
|
||||
nodePaths: ['../../../../node_modules'],
|
||||
entryPoints: {
|
||||
'index': '../../../build/engine/device-detect/obj/kmwdevice.js',
|
||||
},
|
||||
external: ['fs', 'vm'],
|
||||
outdir: '../../../build/engine/device-detect/lib/',
|
||||
outExtension: { '.js': '.mjs' },
|
||||
tsconfig: './tsconfig.json',
|
||||
target: "es5"
|
||||
});
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
import StyleConstants from 'utils/styleConstants.js';
|
||||
import StyleConstants from './utils/styleConstants.js';
|
||||
import { DeviceSpec, Version } from "@keymanapp/web-utils/build/obj/index.js";
|
||||
|
||||
// The Device object definition -------------------------------------------------
|
||||
|
||||
export class Device {
|
||||
export default class Device {
|
||||
touchable: boolean;
|
||||
OS: string;
|
||||
formFactor: string;
|
||||
|
|
|
|||
24
web/src/engine/element-wrappers/build-bundler.js
Normal file
24
web/src/engine/element-wrappers/build-bundler.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Note: while this file is not meant to exist long-term, it provides a nice
|
||||
* low-level proof-of-concept for esbuild bundling of the various Web submodules.
|
||||
*
|
||||
* Add some extra code at the end of src/index.ts and run it to verify successful bundling!
|
||||
*/
|
||||
|
||||
import esbuild from 'esbuild';
|
||||
import { spawn } from 'child_process';
|
||||
|
||||
await esbuild.build({
|
||||
bundle: true,
|
||||
sourcemap: true,
|
||||
format: "esm",
|
||||
nodePaths: ['../../../../node_modules'],
|
||||
entryPoints: {
|
||||
'index': '../../../build/engine/element-wrappers/obj/index.js',
|
||||
},
|
||||
external: ['fs', 'vm'],
|
||||
outdir: '../../../build/engine/element-wrappers/lib/',
|
||||
outExtension: { '.js': '.mjs' },
|
||||
tsconfig: './tsconfig.json',
|
||||
target: "es5"
|
||||
});
|
||||
|
|
@ -1,8 +1,4 @@
|
|||
## engine/element-wrappers
|
||||
|
||||
This submodule provides a subset of the main engine's Web-oriented code that's used to 'wrap' webpage
|
||||
elements as part of KMW attachment and interface the element with the `keyboard-processor` submodule.
|
||||
|
||||
At this time, said code is included as part of KMW's main source. It is provided here as a
|
||||
separate module for use in unit tests, though it is 100% engine code. It is not currently practical
|
||||
to 100% modularize it in KMW's current state due to cross-references.
|
||||
elements as part of KMW attachment and interface the element with the `keyboard-processor` submodule.
|
||||
267
web/src/engine/element-wrappers/src/contentEditable.ts
Normal file
267
web/src/engine/element-wrappers/src/contentEditable.ts
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
import OutputTarget from './outputTarget.js';
|
||||
|
||||
class SelectionCaret {
|
||||
node: Node;
|
||||
offset: number;
|
||||
|
||||
constructor(node, offset) {
|
||||
this.node = node;
|
||||
this.offset = offset;
|
||||
}
|
||||
}
|
||||
|
||||
class SelectionRange {
|
||||
start: SelectionCaret;
|
||||
end: SelectionCaret;
|
||||
|
||||
constructor(start, end) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
}
|
||||
|
||||
export default class ContentEditable extends OutputTarget {
|
||||
root: HTMLElement;
|
||||
|
||||
constructor(ele: HTMLElement) {
|
||||
if(ele.isContentEditable) {
|
||||
super();
|
||||
this.root = ele;
|
||||
} else {
|
||||
throw "Specified element is not already content-editable!";
|
||||
}
|
||||
}
|
||||
|
||||
get isSynthetic(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
getElement(): HTMLElement {
|
||||
return this.root;
|
||||
}
|
||||
|
||||
isSelectionEmpty(): boolean {
|
||||
if(!this.hasSelection()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.root.ownerDocument.getSelection().isCollapsed;
|
||||
}
|
||||
|
||||
hasSelection(): boolean {
|
||||
let Lsel = this.root.ownerDocument.getSelection();
|
||||
|
||||
if(this.root != Lsel.anchorNode && !this.root.contains(Lsel.anchorNode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(this.root != Lsel.focusNode && !this.root.contains(Lsel.focusNode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
clearSelection(): void {
|
||||
if(this.hasSelection()) {
|
||||
let Lsel = this.root.ownerDocument.getSelection();
|
||||
|
||||
if(!Lsel.isCollapsed) {
|
||||
Lsel.deleteFromDocument(); // I2134, I2192
|
||||
}
|
||||
} else {
|
||||
console.warn("Attempted to clear an unowned Selection!");
|
||||
}
|
||||
}
|
||||
|
||||
invalidateSelection(): void { /* No cache maintenance needed here, partly because
|
||||
* it's impossible to cache a Selection; it mutates.
|
||||
*/ }
|
||||
|
||||
getCarets(): SelectionRange {
|
||||
let Lsel = this.root.ownerDocument.getSelection();
|
||||
let code = Lsel.anchorNode.compareDocumentPosition(Lsel.focusNode);
|
||||
|
||||
if(Lsel.isCollapsed) {
|
||||
let caret = new SelectionCaret(Lsel.anchorNode, Lsel.anchorOffset);
|
||||
return new SelectionRange(caret, caret);
|
||||
} else {
|
||||
let anchor = new SelectionCaret(Lsel.anchorNode, Lsel.anchorOffset);
|
||||
let focus = new SelectionCaret(Lsel.focusNode, Lsel.focusOffset);
|
||||
|
||||
if(anchor.node == focus.node) {
|
||||
code = (focus.offset - anchor.offset > 0) ? 2 : 4;
|
||||
}
|
||||
|
||||
if(code & 2) {
|
||||
return new SelectionRange(anchor, focus);
|
||||
} else { // Default
|
||||
// can test against code & 4 to ensure Focus is before anchor, though.
|
||||
return new SelectionRange(focus, anchor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getDeadkeyCaret(): number {
|
||||
return this.getTextBeforeCaret().kmwLength();
|
||||
}
|
||||
|
||||
getTextBeforeCaret(): string {
|
||||
if(!this.hasSelection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let caret = this.getCarets().start;
|
||||
|
||||
if(caret.node.nodeType != 3) {
|
||||
return ''; // Must be a text node to provide a context.
|
||||
}
|
||||
|
||||
return caret.node.textContent.substr(0, caret.offset);
|
||||
}
|
||||
|
||||
getTextAfterCaret(): string {
|
||||
if(!this.hasSelection()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let caret = this.getCarets().end;
|
||||
|
||||
if(caret.node.nodeType != 3) {
|
||||
return ''; // Must be a text node to provide a context.
|
||||
}
|
||||
|
||||
return caret.node.textContent.substr(caret.offset);
|
||||
}
|
||||
|
||||
getText(): string {
|
||||
return this.root.innerText;
|
||||
}
|
||||
|
||||
deleteCharsBeforeCaret(dn: number) {
|
||||
if(!this.hasSelection() || dn <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let start = this.getCarets().start;
|
||||
|
||||
// Bounds-check on the number of chars to delete.
|
||||
if(dn > start.offset) {
|
||||
dn = start.offset;
|
||||
}
|
||||
|
||||
if(start.node.nodeType != 3) {
|
||||
console.warn("Deletion of characters requested without available context!");
|
||||
return; // No context to delete characters from.
|
||||
}
|
||||
|
||||
let range = this.root.ownerDocument.createRange();
|
||||
let dnOffset = start.offset - start.node.nodeValue.substr(0, start.offset)._kmwSubstr(-dn).length;
|
||||
|
||||
range.setStart(start.node, dnOffset);
|
||||
range.setEnd(start.node, start.offset);
|
||||
|
||||
this.adjustDeadkeys(-dn);
|
||||
range.deleteContents();
|
||||
// No need to reposition the caret - the DOM will auto-move the selection accordingly, since
|
||||
// we didn't use the selection to delete anything.
|
||||
}
|
||||
|
||||
insertTextBeforeCaret(s: string) {
|
||||
if(!this.hasSelection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let start = this.getCarets().start;
|
||||
let delta = s._kmwLength();
|
||||
let Lsel = this.root.ownerDocument.getSelection();
|
||||
|
||||
if(delta == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.adjustDeadkeys(delta);
|
||||
|
||||
// While Selection.extend() was really nice for this, IE didn't support it whatsoever.
|
||||
// However, IE (11, at least) DID support setting selections via ranges, so we were still
|
||||
// able to manage the caret properly.
|
||||
//
|
||||
// TODO: double-check that it was only IE-motivated, re-implement with Selection.extend().
|
||||
let finalCaret = this.root.ownerDocument.createRange();
|
||||
|
||||
if(start.node.nodeType == 3) {
|
||||
let textStart = <Text> start.node;
|
||||
textStart.insertData(start.offset, s);
|
||||
finalCaret.setStart(textStart, start.offset + s.length);
|
||||
} else {
|
||||
// Create a new text node - empty control
|
||||
var n = start.node.ownerDocument.createTextNode(s);
|
||||
|
||||
let range = this.root.ownerDocument.createRange();
|
||||
range.setStart(start.node, start.offset);
|
||||
range.collapse(true);
|
||||
range.insertNode(n);
|
||||
finalCaret.setStart(n, s.length);
|
||||
}
|
||||
|
||||
finalCaret.collapse(true);
|
||||
Lsel.removeAllRanges();
|
||||
try {
|
||||
Lsel.addRange(finalCaret);
|
||||
} catch(e) {
|
||||
// Chrome (through 4.0 at least) throws an exception because it has not synchronised its content with the selection.
|
||||
// scrollIntoView synchronises the content for selection
|
||||
start.node.parentElement.scrollIntoView();
|
||||
Lsel.addRange(finalCaret);
|
||||
}
|
||||
Lsel.collapseToEnd();
|
||||
}
|
||||
|
||||
handleNewlineAtCaret(): void {
|
||||
// TODO: Implement.
|
||||
//
|
||||
// As it turns out, we never had an implementation for handling newline inputs from the OSK for this element type.
|
||||
// At least this way, it's more explicit.
|
||||
//
|
||||
// Note: consult "// Create a new text node - empty control" case in insertTextBeforeCaret -
|
||||
// this helps to handle the browser-default implementation of newline handling. In particular,
|
||||
// entry of the first character after a newline.
|
||||
//
|
||||
// If raw newlines are entered into the HTML, but as with usual HTML, they're interpreted as excess whitespace and
|
||||
// have no effect. We need to add DOM elements for a functional newline.
|
||||
}
|
||||
|
||||
protected setTextAfterCaret(s: string) {
|
||||
if(!this.hasSelection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let caret = this.getCarets().end;
|
||||
let delta = s._kmwLength();
|
||||
let Lsel = this.root.ownerDocument.getSelection();
|
||||
|
||||
if(delta == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This is designed explicitly for use in direct-setting operations; deadkeys
|
||||
// will be handled after this method.
|
||||
|
||||
if(caret.node.nodeType == 3) {
|
||||
let textStart = <Text> caret.node;
|
||||
textStart.replaceData(caret.offset, textStart.length, s);
|
||||
} else {
|
||||
// Create a new text node - empty control
|
||||
var n = caret.node.ownerDocument.createTextNode(s);
|
||||
|
||||
let range = this.root.ownerDocument.createRange();
|
||||
range.setStart(caret.node, caret.offset);
|
||||
range.collapse(true);
|
||||
range.insertNode(n);
|
||||
}
|
||||
}
|
||||
|
||||
doInputEvent() {
|
||||
this.dispatchInputEventOn(this.root);
|
||||
}
|
||||
}
|
||||
352
web/src/engine/element-wrappers/src/designIFrame.ts
Normal file
352
web/src/engine/element-wrappers/src/designIFrame.ts
Normal file
|
|
@ -0,0 +1,352 @@
|
|||
import OutputTarget from './outputTarget.js';
|
||||
|
||||
class SelectionCaret {
|
||||
node: Node;
|
||||
offset: number;
|
||||
|
||||
constructor(node, offset) {
|
||||
this.node = node;
|
||||
this.offset = offset;
|
||||
}
|
||||
}
|
||||
|
||||
class SelectionRange {
|
||||
start: SelectionCaret;
|
||||
end: SelectionCaret;
|
||||
|
||||
constructor(start, end) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
}
|
||||
|
||||
class StyleCommand {
|
||||
cmd: string;
|
||||
stateType: number;
|
||||
cache: string|boolean;
|
||||
|
||||
constructor(c: string, s:number) {
|
||||
this.cmd = c;
|
||||
this.stateType = s;
|
||||
}
|
||||
}
|
||||
|
||||
export default class DesignIFrame extends OutputTarget {
|
||||
root: HTMLIFrameElement;
|
||||
doc: Document;
|
||||
docRoot: HTMLElement;
|
||||
|
||||
commandCache: StyleCommand[];
|
||||
|
||||
constructor(ele: HTMLIFrameElement) {
|
||||
super();
|
||||
this.root = ele;
|
||||
|
||||
if(ele.contentWindow && ele.contentWindow.document && ele.contentWindow.document.designMode == 'on') {
|
||||
this.doc = ele.contentWindow.document;
|
||||
this.docRoot = ele.contentWindow.document.documentElement;
|
||||
} else {
|
||||
throw "Specified IFrame is not in design-mode!";
|
||||
}
|
||||
}
|
||||
|
||||
get isSynthetic(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
getElement(): HTMLIFrameElement {
|
||||
return this.root;
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
this.doc.defaultView.focus(); // I3363 (Build 301)
|
||||
}
|
||||
|
||||
isSelectionEmpty(): boolean {
|
||||
if(!this.hasSelection()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.doc.getSelection().isCollapsed;
|
||||
}
|
||||
|
||||
hasSelection(): boolean {
|
||||
let Lsel = this.doc.getSelection();
|
||||
let outerSel = document.getSelection();
|
||||
|
||||
// If the outer doc's selection matches, we're active.
|
||||
if(outerSel.anchorNode == Lsel.anchorNode && outerSel.focusNode == Lsel.focusNode) {
|
||||
return true;
|
||||
} else {
|
||||
// Problem: for testing, we can't enforce the ideal (ie: first) condition.
|
||||
// Technically, the IFrame _will_ always have its own internal selection, though... so... it kinda works?
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
clearSelection(): void {
|
||||
if(this.hasSelection()) {
|
||||
let Lsel = this.doc.getSelection();
|
||||
|
||||
if(!Lsel.isCollapsed) {
|
||||
Lsel.deleteFromDocument(); // I2134, I2192
|
||||
}
|
||||
} else {
|
||||
console.warn("Attempted to clear an unowned Selection!");
|
||||
}
|
||||
}
|
||||
|
||||
invalidateSelection(): void { /* No cache maintenance needed here, partly because
|
||||
* it's impossible to cache a Selection; it mutates.
|
||||
*/ }
|
||||
|
||||
getCarets(): SelectionRange {
|
||||
let Lsel = this.doc.getSelection();
|
||||
let code = Lsel.anchorNode.compareDocumentPosition(Lsel.focusNode);
|
||||
|
||||
if(Lsel.isCollapsed) {
|
||||
let caret = new SelectionCaret(Lsel.anchorNode, Lsel.anchorOffset);
|
||||
return new SelectionRange(caret, caret);
|
||||
} else {
|
||||
let anchor = new SelectionCaret(Lsel.anchorNode, Lsel.anchorOffset);
|
||||
let focus = new SelectionCaret(Lsel.focusNode, Lsel.focusOffset);
|
||||
|
||||
if(anchor.node == focus.node) {
|
||||
code = (focus.offset - anchor.offset > 0) ? 2 : 4;
|
||||
}
|
||||
|
||||
if(code & 2) {
|
||||
return new SelectionRange(anchor, focus);
|
||||
} else { // Default
|
||||
// can test against code & 4 to ensure Focus is before anchor, though.
|
||||
return new SelectionRange(focus, anchor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getDeadkeyCaret(): number {
|
||||
return this.getTextBeforeCaret().kmwLength();
|
||||
}
|
||||
|
||||
getTextBeforeCaret(): string {
|
||||
if(!this.hasSelection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let caret = this.getCarets().start;
|
||||
|
||||
if(caret.node.nodeType != 3) {
|
||||
return ''; // Must be a text node to provide a context.
|
||||
}
|
||||
|
||||
return caret.node.textContent.substr(0, caret.offset);
|
||||
}
|
||||
|
||||
getTextAfterCaret(): string {
|
||||
if(!this.hasSelection()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let caret = this.getCarets().end;
|
||||
|
||||
if(caret.node.nodeType != 3) {
|
||||
return ''; // Must be a text node to provide a context.
|
||||
}
|
||||
|
||||
return caret.node.textContent.substr(caret.offset);
|
||||
}
|
||||
|
||||
getText(): string {
|
||||
return this.docRoot.innerText;
|
||||
}
|
||||
|
||||
deleteCharsBeforeCaret(dn: number) {
|
||||
if(!this.hasSelection() || dn <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let start = this.getCarets().start;
|
||||
|
||||
// Bounds-check on the number of chars to delete.
|
||||
if(dn > start.offset) {
|
||||
dn = start.offset;
|
||||
}
|
||||
|
||||
if(start.node.nodeType != 3) {
|
||||
console.warn("Deletion of characters requested without available context!");
|
||||
return; // No context to delete characters from.
|
||||
}
|
||||
|
||||
let range = this.doc.createRange();
|
||||
let dnOffset = start.offset - start.node.nodeValue.substr(0, start.offset)._kmwSubstr(-dn).length;
|
||||
|
||||
range.setStart(start.node, dnOffset);
|
||||
range.setEnd(start.node, start.offset);
|
||||
|
||||
this.adjustDeadkeys(-dn);
|
||||
range.deleteContents();
|
||||
// No need to reposition the caret - the DOM will auto-move the selection accordingly, since
|
||||
// we didn't use the selection to delete anything.
|
||||
}
|
||||
|
||||
insertTextBeforeCaret(s: string) {
|
||||
if(!this.hasSelection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let start = this.getCarets().start;
|
||||
let delta = s._kmwLength();
|
||||
let Lsel = this.doc.getSelection();
|
||||
|
||||
if(delta == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.adjustDeadkeys(delta);
|
||||
|
||||
// While Selection.extend() was really nice for this, IE didn't support it whatsoever.
|
||||
// However, IE (11, at least) DID support setting selections via ranges, so we were still
|
||||
// able to manage the caret properly.
|
||||
//
|
||||
// TODO: double-check that it was only IE-motivated, re-implement with Selection.extend().
|
||||
let finalCaret = this.root.ownerDocument.createRange();
|
||||
|
||||
if(start.node.nodeType == 3) {
|
||||
let textStart = <Text> start.node;
|
||||
textStart.insertData(start.offset, s);
|
||||
finalCaret.setStart(textStart, start.offset + s.length);
|
||||
} else {
|
||||
// Create a new text node - empty control
|
||||
var n = this.doc.createTextNode(s);
|
||||
|
||||
let range = this.doc.createRange();
|
||||
range.setStart(start.node, start.offset);
|
||||
range.collapse(true);
|
||||
range.insertNode(n);
|
||||
finalCaret.setStart(n, s.length);
|
||||
}
|
||||
|
||||
finalCaret.collapse(true);
|
||||
Lsel.removeAllRanges();
|
||||
try {
|
||||
Lsel.addRange(finalCaret);
|
||||
} catch(e) {
|
||||
// Chrome (through 4.0 at least) throws an exception because it has not synchronised its content with the selection.
|
||||
// scrollIntoView synchronises the content for selection
|
||||
start.node.parentElement.scrollIntoView();
|
||||
Lsel.addRange(finalCaret);
|
||||
}
|
||||
Lsel.collapseToEnd();
|
||||
}
|
||||
|
||||
handleNewlineAtCaret(): void {
|
||||
// TODO: Implement.
|
||||
//
|
||||
// As it turns out, we never had an implementation for handling newline inputs from the OSK for this element type.
|
||||
// At least this way, it's more explicit.
|
||||
//
|
||||
// Note: consult "// Create a new text node - empty control" case in insertTextBeforeCaret -
|
||||
// this helps to handle the browser-default implementation of newline handling. In particular,
|
||||
// entry of the first character after a newline.
|
||||
//
|
||||
// If raw newlines are entered into the HTML, but as with usual HTML, they're interpreted as excess whitespace and
|
||||
// have no effect. We need to add DOM elements for a functional newline.
|
||||
}
|
||||
|
||||
protected setTextAfterCaret(s: string) {
|
||||
if(!this.hasSelection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let caret = this.getCarets().end;
|
||||
let delta = s._kmwLength();
|
||||
let Lsel = this.doc.getSelection();
|
||||
|
||||
if(delta == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This is designed explicitly for use in direct-setting operations; deadkeys
|
||||
// will be handled after this method.
|
||||
|
||||
if(caret.node.nodeType == 3) {
|
||||
let textStart = <Text> caret.node;
|
||||
textStart.replaceData(caret.offset, textStart.length, s);
|
||||
} else {
|
||||
// Create a new text node - empty control
|
||||
var n = caret.node.ownerDocument.createTextNode(s);
|
||||
|
||||
let range = this.root.ownerDocument.createRange();
|
||||
range.setStart(caret.node, caret.offset);
|
||||
range.collapse(true);
|
||||
range.insertNode(n);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function saveProperties
|
||||
* Scope Private
|
||||
* Description Build and create list of styles that can be applied in iframes
|
||||
*/
|
||||
saveProperties() {
|
||||
// Formerly _CacheCommands.
|
||||
var _CacheableCommands=[
|
||||
new StyleCommand('backcolor',1), new StyleCommand('fontname',1), new StyleCommand('fontsize',1),
|
||||
new StyleCommand('forecolor',1), new StyleCommand('bold',0), new StyleCommand('italic',0),
|
||||
new StyleCommand('strikethrough',0), new StyleCommand('subscript',0),
|
||||
new StyleCommand('superscript',0), new StyleCommand('underline',0)
|
||||
];
|
||||
|
||||
if(this.doc.defaultView) {
|
||||
_CacheableCommands.push(new StyleCommand('hilitecolor',1));
|
||||
}
|
||||
|
||||
for(var n=0; n < _CacheableCommands.length; n++) { // I1511 - array prototype extended
|
||||
let cmd = _CacheableCommands[n];
|
||||
//KeymanWeb._Debug('Command:'+_CacheableCommands[n][0]);
|
||||
if(cmd.stateType == 1) {
|
||||
cmd.cache = this.doc.queryCommandValue(cmd.cmd);
|
||||
} else {
|
||||
cmd.cache = this.doc.queryCommandState(cmd.cmd);
|
||||
}
|
||||
}
|
||||
this.commandCache = _CacheableCommands;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function restoreProperties
|
||||
* Scope Private
|
||||
* Description Restore styles in IFRAMEs (??)
|
||||
*/
|
||||
restoreProperties(_func?: () => void): void {
|
||||
// Formerly _CacheCommandsReset.
|
||||
if(!this.commandCache) {
|
||||
console.error("No command cache exists to restore!");
|
||||
}
|
||||
|
||||
for(var n=0; n < this.commandCache.length; n++) { // I1511 - array prototype extended
|
||||
let cmd = this.commandCache[n];
|
||||
|
||||
//KeymanWeb._Debug('ResetCacheCommand:'+_CacheableCommands[n][0]+'='+_CacheableCommands[n][2]);
|
||||
if(cmd.stateType == 1) {
|
||||
if(this.doc.queryCommandValue(cmd.cmd) != cmd.cache) {
|
||||
if(_func) {
|
||||
_func();
|
||||
}
|
||||
this.doc.execCommand(cmd.cmd, false, <string> cmd.cache);
|
||||
}
|
||||
} else if(this.doc.queryCommandState(cmd.cmd) != cmd.cache) {
|
||||
if(_func) {
|
||||
_func();
|
||||
}
|
||||
//KeymanWeb._Debug('executing command '+_CacheableCommand[n][0]);
|
||||
this.doc.execCommand(cmd.cmd, false, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
doInputEvent() {
|
||||
// Root = the iframe, the outermost component and the one we were originally told to attach to.
|
||||
this.dispatchInputEventOn(this.root);
|
||||
}
|
||||
}
|
||||
7
web/src/engine/element-wrappers/src/index.ts
Normal file
7
web/src/engine/element-wrappers/src/index.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export { default as wrapElement } from './wrapElement.js';
|
||||
export { default as ContentEditable } from './contentEditable.js';
|
||||
export { default as DesignIFrame } from './designIFrame.js';
|
||||
export { default as Input } from './input.js';
|
||||
export { default as OutputTarget } from './outputTarget.js';
|
||||
export { default as TextArea } from './textarea.js';
|
||||
export { nestedInstanceOf } from './utils.js';
|
||||
233
web/src/engine/element-wrappers/src/input.ts
Normal file
233
web/src/engine/element-wrappers/src/input.ts
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
import OutputTarget, { BaseEventMap } from './outputTarget.js';
|
||||
|
||||
interface EventMap extends BaseEventMap {
|
||||
/**
|
||||
* Used to facilitate a pre-modularization utility method we wish to maintain:
|
||||
```
|
||||
export function forceScroll(element: HTMLInputElement | HTMLTextAreaElement) {
|
||||
// Only executes when com.keyman.DOMEventHandlers is defined.
|
||||
//
|
||||
// We bypass this whenever operating in the embedded format.
|
||||
if(com && com.keyman && com.keyman['DOMEventHandlers'] && !com.keyman['singleton']['isEmbedded']) {
|
||||
let DOMEventHandlers = com.keyman['DOMEventHandlers'];
|
||||
|
||||
let selectionStart = element.selectionStart;
|
||||
let selectionEnd = element.selectionEnd;
|
||||
|
||||
DOMEventHandlers.states._IgnoreBlurFocus = true;
|
||||
//Forces scrolling; the re-focus triggers the scroll, at least.
|
||||
element.blur();
|
||||
element.focus();
|
||||
DOMEventHandlers.states._IgnoreBlurFocus = false;
|
||||
|
||||
// On Edge, it appears that the blur/focus combination will reset the caret position
|
||||
// under certain scenarios during unit tests. So, we re-set it afterward.
|
||||
element.selectionStart = selectionStart;
|
||||
element.selectionEnd = selectionEnd;
|
||||
}
|
||||
}
|
||||
```
|
||||
* References to the event-handlers & related states objects are not available within this submodule.
|
||||
*
|
||||
* It is the parts between and including the _IgnoreBlurFocus references that must be
|
||||
* implemented externally.
|
||||
*/
|
||||
'scrollfocusrequest': (element: HTMLInputElement) => void,
|
||||
|
||||
/**
|
||||
* This event will be raised when a newline is received by wrapped elements not of
|
||||
* the 'search' or 'submit' types.
|
||||
*
|
||||
* Original code this is replacing:
|
||||
```
|
||||
// Allows compiling this separately from the main body of KMW.
|
||||
// TODO: rework class to accept a class-static 'callback' from the DOM module that this can call.
|
||||
// Would eliminate the need for this 'static' reference.
|
||||
// Only strongly matters once we better modularize KMW, with web-dom vs web-dom-targets vs web-core, etc.
|
||||
if(com.keyman["singleton"]) {
|
||||
com.keyman["singleton"].domManager.moveToNext(false);
|
||||
}
|
||||
```
|
||||
* This does not belong in a modularized version of this class; it must be supplied
|
||||
* by the consuming top-level products instead.
|
||||
*/
|
||||
'unhandlednewline': (element: HTMLInputElement) => void
|
||||
}
|
||||
|
||||
export default class Input extends OutputTarget<EventMap> {
|
||||
root: HTMLInputElement;
|
||||
|
||||
/**
|
||||
* Tracks the most recently-cached selection start index.
|
||||
*/
|
||||
private _cachedSelectionStart: number
|
||||
|
||||
/**
|
||||
* Tracks the most recently processed, extended-string-based selection start index.
|
||||
* When the element's selectionStart value changes, this should be invalidated.
|
||||
*/
|
||||
private processedSelectionStart: number;
|
||||
|
||||
/**
|
||||
* Tracks the most recently processed, extended-string-based selection end index.
|
||||
* When the element's selectionEnd value changes, this should be invalidated.
|
||||
*/
|
||||
private processedSelectionEnd: number;
|
||||
|
||||
constructor(ele: HTMLInputElement) {
|
||||
super();
|
||||
|
||||
this.root = ele;
|
||||
this._cachedSelectionStart = -1;
|
||||
|
||||
// Intended to facilitate reimplmentation of the old `forceScroll` as an event handler
|
||||
// defined externally, but automatically set on class construction.
|
||||
Input.constructorExtensions(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* This may be set to define additional construction behaviors to perform, such as
|
||||
* automatically setting handlers for defined events.
|
||||
*/
|
||||
public static constructorExtensions: (constructingInstance: Input) => void = () => {};
|
||||
|
||||
get isSynthetic(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
getElement(): HTMLInputElement {
|
||||
return this.root;
|
||||
}
|
||||
|
||||
clearSelection(): void {
|
||||
// Processes our codepoint-based variants of selectionStart and selectionEnd.
|
||||
this.getCaret(); // updates processedSelectionStart if required
|
||||
this.root.value = this.root.value._kmwSubstring(0, this.processedSelectionStart) + this.root.value._kmwSubstring(this.processedSelectionEnd); //I3319
|
||||
|
||||
this.setCaret(this.processedSelectionStart);
|
||||
}
|
||||
|
||||
isSelectionEmpty(): boolean {
|
||||
return this.root.selectionStart == this.root.selectionEnd;
|
||||
}
|
||||
|
||||
hasSelection(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
invalidateSelection() {
|
||||
// Since .selectionStart will never return this value, we use it to indicate
|
||||
// the need to refresh our processed indices.
|
||||
this._cachedSelectionStart = -1;
|
||||
}
|
||||
|
||||
getCaret(): number {
|
||||
if(this.root.selectionStart != this._cachedSelectionStart) {
|
||||
this._cachedSelectionStart = this.root.selectionStart; // KMW-1
|
||||
this.processedSelectionStart = this.root.value._kmwCodeUnitToCodePoint(this.root.selectionStart); // I3319
|
||||
this.processedSelectionEnd = this.root.value._kmwCodeUnitToCodePoint(this.root.selectionEnd); // I3319
|
||||
}
|
||||
return this.root.selectionDirection == 'forward' ? this.processedSelectionEnd : this.processedSelectionStart;
|
||||
}
|
||||
|
||||
getDeadkeyCaret(): number {
|
||||
return this.getCaret();
|
||||
}
|
||||
|
||||
setCaret(caret: number) {
|
||||
this.setSelection(caret, caret, "none");
|
||||
}
|
||||
|
||||
setSelection(start: number, end: number, direction: "forward" | "backward" | "none") {
|
||||
let domStart = this.root.value._kmwCodePointToCodeUnit(start);
|
||||
let domEnd = this.root.value._kmwCodePointToCodeUnit(end);
|
||||
this.root.setSelectionRange(domStart, domEnd, direction);
|
||||
|
||||
this.processedSelectionStart = start;
|
||||
this.processedSelectionEnd = end;
|
||||
|
||||
this.events.emit('scrollfocusrequest', this.root);
|
||||
|
||||
this.root.setSelectionRange(domStart, domEnd, direction);
|
||||
}
|
||||
|
||||
getSelectionDirection(): "forward" | "backward" | "none" {
|
||||
return this.root.selectionDirection;
|
||||
}
|
||||
|
||||
getTextBeforeCaret(): string {
|
||||
this.getCaret();
|
||||
return this.getText()._kmwSubstring(0, this.processedSelectionStart);
|
||||
}
|
||||
|
||||
setTextBeforeCaret(text: string) {
|
||||
this.getCaret();
|
||||
let selectionLength = this.processedSelectionEnd - this.processedSelectionStart;
|
||||
let direction = this.getSelectionDirection();
|
||||
let newCaret = text._kmwLength();
|
||||
this.root.value = text + this.getText()._kmwSubstring(this.processedSelectionStart);
|
||||
|
||||
this.setSelection(newCaret, newCaret + selectionLength, direction);
|
||||
}
|
||||
|
||||
protected setTextAfterCaret(s: string) {
|
||||
let c = this.getCaret();
|
||||
let direction = this.getSelectionDirection();
|
||||
|
||||
this.root.value = this.getTextBeforeCaret() + s;
|
||||
this.setSelection(this.processedSelectionStart, this.processedSelectionEnd, direction);
|
||||
}
|
||||
|
||||
getTextAfterCaret(): string {
|
||||
this.getCaret();
|
||||
return this.getText()._kmwSubstring(this.processedSelectionEnd);
|
||||
}
|
||||
|
||||
getText(): string {
|
||||
return this.root.value;
|
||||
}
|
||||
|
||||
deleteCharsBeforeCaret(dn: number) {
|
||||
if(dn > 0) {
|
||||
let curText = this.getTextBeforeCaret();
|
||||
let caret = this.processedSelectionStart;
|
||||
|
||||
if(dn > caret) {
|
||||
dn = caret;
|
||||
}
|
||||
|
||||
this.adjustDeadkeys(-dn);
|
||||
this.setTextBeforeCaret(curText.kmwSubstring(0, caret - dn));
|
||||
this.setCaret(caret - dn);
|
||||
}
|
||||
}
|
||||
|
||||
insertTextBeforeCaret(s: string) {
|
||||
if(!s) {
|
||||
return;
|
||||
}
|
||||
|
||||
let caret = this.getCaret();
|
||||
let front = this.getTextBeforeCaret();
|
||||
let back = this.getText()._kmwSubstring(this.processedSelectionStart);
|
||||
|
||||
this.adjustDeadkeys(s._kmwLength());
|
||||
this.root.value = front + s + back;
|
||||
this.setCaret(caret + s._kmwLength());
|
||||
}
|
||||
|
||||
handleNewlineAtCaret(): void {
|
||||
const inputEle = this.root;
|
||||
// Can't occur for Mocks - just Input types.
|
||||
if (inputEle && (inputEle.type == 'search' || inputEle.type == 'submit')) {
|
||||
inputEle.disabled=false;
|
||||
inputEle.form.submit();
|
||||
} else {
|
||||
this.events.emit('unhandlednewline', inputEle);
|
||||
}
|
||||
}
|
||||
|
||||
doInputEvent() {
|
||||
this.dispatchInputEventOn(this.root);
|
||||
}
|
||||
}
|
||||
62
web/src/engine/element-wrappers/src/outputTarget.ts
Normal file
62
web/src/engine/element-wrappers/src/outputTarget.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import OutputTargetBase from "@keymanapp/keyboard-processor/build/obj/text/outputTarget.js";
|
||||
import EventEmitter from 'eventemitter3';
|
||||
|
||||
export interface BaseEventMap {
|
||||
/**
|
||||
* Meant to facilitate the following code that existed pre-modularization:
|
||||
```
|
||||
// This class has non-integrated unit tests in which the `singleton` object doesn't exist.
|
||||
// Thus, we need to test for this case.
|
||||
let keyman = com.keyman['singleton'];
|
||||
|
||||
// Signal the necessary text changes to the embedding app, if it exists.
|
||||
if(keyman && keyman['oninserttext'] && keyman.isEmbedded) {
|
||||
keyman['oninserttext'](transform.deleteLeft, transform.insert, transform.deleteRight);
|
||||
}
|
||||
```
|
||||
*/
|
||||
'oninserttext': (deleteLeft: number, insert: string, deleteRight: number) => void;
|
||||
}
|
||||
|
||||
export default abstract class OutputTarget<EventMap extends BaseEventMap = BaseEventMap> extends OutputTargetBase {
|
||||
// JS/TS can't do multiple inheritance, so we maintain class events on a readonly field.
|
||||
public readonly events: EventEmitter<EventMap, this> = new EventEmitter<EventMap, this>();
|
||||
|
||||
/**
|
||||
* Returns the underlying element / document modeled by the wrapper.
|
||||
*/
|
||||
abstract getElement(): HTMLElement;
|
||||
|
||||
public focus(): void {
|
||||
const ele = this.getElement();
|
||||
if(ele.focus) {
|
||||
ele.focus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper method for doInputEvent; creates a simple common event and default dispatching.
|
||||
* @param elem
|
||||
*/
|
||||
protected dispatchInputEventOn(elem: HTMLElement) {
|
||||
let event: InputEvent;
|
||||
|
||||
// `undefined` in pre-Chrome Edge and Chrome for Android before version 60.
|
||||
if(window['InputEvent']) { // can't condition on the type directly; TS optimizes that out.
|
||||
event = new InputEvent('input', {"bubbles": true, "cancelable": false});
|
||||
}
|
||||
|
||||
if(elem && event) {
|
||||
elem.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
apply(transform: Transform) {
|
||||
super.apply(transform);
|
||||
|
||||
// The TS compiler can't quite handle this typing scenario properly; the following cast
|
||||
// allows us to work around its type inference limitations.
|
||||
const baseEvents = (this.events as unknown as EventEmitter<BaseEventMap, this>);
|
||||
baseEvents.emit('oninserttext', transform.deleteLeft, transform.insert, transform.deleteRight);
|
||||
}
|
||||
}
|
||||
216
web/src/engine/element-wrappers/src/textarea.ts
Normal file
216
web/src/engine/element-wrappers/src/textarea.ts
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
import OutputTarget, { BaseEventMap } from './outputTarget.js';
|
||||
|
||||
interface EventMap extends BaseEventMap {
|
||||
/**
|
||||
* Used to facilitate a pre-modularization utility method we wish to maintain:
|
||||
```
|
||||
export function forceScroll(element: HTMLInputElement | HTMLTextAreaElement) {
|
||||
// Only executes when com.keyman.DOMEventHandlers is defined.
|
||||
//
|
||||
// We bypass this whenever operating in the embedded format.
|
||||
if(com && com.keyman && com.keyman['DOMEventHandlers'] && !com.keyman['singleton']['isEmbedded']) {
|
||||
let DOMEventHandlers = com.keyman['DOMEventHandlers'];
|
||||
|
||||
let selectionStart = element.selectionStart;
|
||||
let selectionEnd = element.selectionEnd;
|
||||
|
||||
DOMEventHandlers.states._IgnoreBlurFocus = true;
|
||||
//Forces scrolling; the re-focus triggers the scroll, at least.
|
||||
element.blur();
|
||||
element.focus();
|
||||
DOMEventHandlers.states._IgnoreBlurFocus = false;
|
||||
|
||||
// On Edge, it appears that the blur/focus combination will reset the caret position
|
||||
// under certain scenarios during unit tests. So, we re-set it afterward.
|
||||
element.selectionStart = selectionStart;
|
||||
element.selectionEnd = selectionEnd;
|
||||
}
|
||||
}
|
||||
```
|
||||
* References to the event-handlers & related states objects are not available within this submodule.
|
||||
*
|
||||
* It is the parts between and including the _IgnoreBlurFocus references that must be
|
||||
* implemented externally.
|
||||
*/
|
||||
'scrollfocusrequest': (element: HTMLTextAreaElement) => void,
|
||||
}
|
||||
|
||||
export default class TextArea extends OutputTarget<EventMap> {
|
||||
root: HTMLTextAreaElement;
|
||||
|
||||
/**
|
||||
* Tracks the most recently-cached selection start index.
|
||||
*/
|
||||
private _cachedSelectionStart: number
|
||||
|
||||
/**
|
||||
* Tracks the most recently processed, extended-string-based selection start index.
|
||||
* When the element's selectionStart value changes, this should be invalidated.
|
||||
*/
|
||||
private processedSelectionStart: number;
|
||||
|
||||
/**
|
||||
* Tracks the most recently processed, extended-string-based selection end index.
|
||||
* When the element's selectionEnd value changes, this should be invalidated.
|
||||
*/
|
||||
private processedSelectionEnd: number;
|
||||
|
||||
/**
|
||||
* Used to temporarily store the y-axis scroll coordinate.
|
||||
*/
|
||||
private scrollTop?: number;
|
||||
|
||||
/**
|
||||
* Used to temporarily store the x-axis scroll coordinate.
|
||||
*/
|
||||
private scrollLeft?: number;
|
||||
|
||||
constructor(ele: HTMLTextAreaElement) {
|
||||
super();
|
||||
|
||||
this.root = ele;
|
||||
this._cachedSelectionStart = -1;
|
||||
// Intended to facilitate reimplmentation of the old `forceScroll` as an event handler
|
||||
// defined externally, but automatically set on class construction.
|
||||
TextArea.constructorExtensions(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* This may be set to define additional construction behaviors to perform, such as
|
||||
* automatically setting handlers for defined events.
|
||||
*/
|
||||
public static constructorExtensions: (constructingInstance: TextArea) => void = () => {};
|
||||
|
||||
get isSynthetic(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
getElement(): HTMLTextAreaElement {
|
||||
return this.root;
|
||||
}
|
||||
|
||||
clearSelection(): void {
|
||||
// Processes our codepoint-based variants of selectionStart and selectionEnd.
|
||||
this.getCaret(); // updates processedSelectionStart if required
|
||||
this.root.value = this.root.value._kmwSubstring(0, this.processedSelectionStart) + this.root.value._kmwSubstring(this.processedSelectionEnd); //I3319
|
||||
|
||||
this.setCaret(this.processedSelectionStart);
|
||||
}
|
||||
|
||||
isSelectionEmpty(): boolean {
|
||||
return this.root.selectionStart == this.root.selectionEnd;
|
||||
}
|
||||
|
||||
hasSelection(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
invalidateSelection() {
|
||||
// Since .selectionStart will never return this value, we use it to indicate
|
||||
// the need to refresh our processed indices.
|
||||
this._cachedSelectionStart = -1;
|
||||
}
|
||||
|
||||
getCaret(): number {
|
||||
if(this.root.selectionStart != this._cachedSelectionStart) {
|
||||
this._cachedSelectionStart = this.root.selectionStart; // KMW-1
|
||||
this.processedSelectionStart = this.root.value._kmwCodeUnitToCodePoint(this.root.selectionStart); // I3319
|
||||
this.processedSelectionEnd = this.root.value._kmwCodeUnitToCodePoint(this.root.selectionEnd); // I3319
|
||||
}
|
||||
return this.root.selectionDirection == 'forward' ? this.processedSelectionEnd : this.processedSelectionStart;
|
||||
}
|
||||
|
||||
getDeadkeyCaret(): number {
|
||||
return this.getCaret();
|
||||
}
|
||||
|
||||
setCaret(caret: number) {
|
||||
this.setSelection(caret, caret, "none");
|
||||
}
|
||||
|
||||
setSelection(start: number, end: number, direction: "forward" | "backward" | "none") {
|
||||
let domStart = this.root.value._kmwCodePointToCodeUnit(start);
|
||||
let domEnd = this.root.value._kmwCodePointToCodeUnit(end);
|
||||
this.root.setSelectionRange(domStart, domEnd, direction);
|
||||
|
||||
this.processedSelectionStart = start;
|
||||
this.processedSelectionEnd = end;
|
||||
|
||||
this.events.emit('scrollfocusrequest', this.root);
|
||||
|
||||
this.root.setSelectionRange(domStart, domEnd, direction);
|
||||
}
|
||||
|
||||
getSelectionDirection(): "forward" | "backward" | "none" {
|
||||
return this.root.selectionDirection;
|
||||
}
|
||||
|
||||
getTextBeforeCaret(): string {
|
||||
this.getCaret();
|
||||
return this.getText()._kmwSubstring(0, this.processedSelectionStart);
|
||||
}
|
||||
|
||||
setTextBeforeCaret(text: string) {
|
||||
this.getCaret();
|
||||
let selectionLength = this.processedSelectionEnd - this.processedSelectionStart;
|
||||
let direction = this.getSelectionDirection();
|
||||
let newCaret = text._kmwLength();
|
||||
this.root.value = text + this.getText()._kmwSubstring(this.processedSelectionStart);
|
||||
|
||||
this.setSelection(newCaret, newCaret + selectionLength, direction);
|
||||
}
|
||||
|
||||
protected setTextAfterCaret(s: string) {
|
||||
let c = this.getCaret();
|
||||
let direction = this.getSelectionDirection();
|
||||
|
||||
this.root.value = this.getTextBeforeCaret() + s;
|
||||
this.setSelection(this.processedSelectionStart, this.processedSelectionEnd, direction);
|
||||
}
|
||||
|
||||
getTextAfterCaret(): string {
|
||||
this.getCaret();
|
||||
return this.getText()._kmwSubstring(this.processedSelectionEnd);
|
||||
}
|
||||
|
||||
getText(): string {
|
||||
return this.root.value;
|
||||
}
|
||||
|
||||
deleteCharsBeforeCaret(dn: number) {
|
||||
if(dn > 0) {
|
||||
let curText = this.getTextBeforeCaret();
|
||||
let caret = this.processedSelectionStart;
|
||||
|
||||
if(dn > caret) {
|
||||
dn = caret;
|
||||
}
|
||||
|
||||
this.adjustDeadkeys(-dn);
|
||||
this.setTextBeforeCaret(curText.kmwSubstring(0, caret - dn));
|
||||
this.setCaret(caret - dn);
|
||||
}
|
||||
}
|
||||
|
||||
insertTextBeforeCaret(s: string) {
|
||||
if(!s) {
|
||||
return;
|
||||
}
|
||||
|
||||
let caret = this.getCaret();
|
||||
let front = this.getTextBeforeCaret();
|
||||
let back = this.getText()._kmwSubstring(this.processedSelectionStart);
|
||||
|
||||
this.adjustDeadkeys(s._kmwLength());
|
||||
this.root.value = front + s + back;
|
||||
this.setCaret(caret + s._kmwLength());
|
||||
}
|
||||
|
||||
handleNewlineAtCaret(): void {
|
||||
this.insertTextBeforeCaret('\n');
|
||||
}
|
||||
|
||||
doInputEvent() {
|
||||
this.dispatchInputEventOn(this.root);
|
||||
}
|
||||
}
|
||||
43
web/src/engine/element-wrappers/src/utils.ts
Normal file
43
web/src/engine/element-wrappers/src/utils.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* Checks the type of an input DOM-related object while ensuring that it is checked against the correct prototype,
|
||||
* as class prototypes are (by specification) scoped upon the owning Window.
|
||||
*
|
||||
* See https://stackoverflow.com/questions/43587286/why-does-instanceof-return-false-on-chrome-safari-and-edge-and-true-on-firefox
|
||||
* for more details.
|
||||
*
|
||||
* @param {Element|Event} Pelem An element of the web page or one of its IFrame-based subdocuments.
|
||||
* @param {string} className The plain-text name of the expected Element type.
|
||||
* @return {boolean}
|
||||
*/
|
||||
export function nestedInstanceOf(Pelem: Event|EventTarget, className: string): boolean {
|
||||
var scopedClass;
|
||||
|
||||
if(!Pelem) {
|
||||
// If we're bothering to check something's type, null references don't match
|
||||
// what we're looking for.
|
||||
return false;
|
||||
}
|
||||
if (Pelem['Window']) { // Window objects contain the class definitions for types held within them. So, we can check for those.
|
||||
return className == 'Window';
|
||||
} else if (Pelem['defaultView']) { // Covers Document.
|
||||
scopedClass = Pelem['defaultView'][className];
|
||||
} else if(Pelem['ownerDocument']) {
|
||||
scopedClass = (Pelem as Node).ownerDocument.defaultView[className];
|
||||
} else if(Pelem['target']) {
|
||||
var event = Pelem as Event;
|
||||
|
||||
if(this.instanceof(event.target, 'Window')) {
|
||||
scopedClass = event.target[className];
|
||||
} else if(this.instanceof(event.target, 'Document')) {
|
||||
scopedClass = (event.target as Document).defaultView[className];
|
||||
} else if(this.instanceof(event.target, 'HTMLElement')) {
|
||||
scopedClass = (event.target as HTMLElement).ownerDocument.defaultView[className];
|
||||
}
|
||||
}
|
||||
|
||||
if(scopedClass) {
|
||||
return Pelem instanceof scopedClass;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
31
web/src/engine/element-wrappers/src/wrapElement.ts
Normal file
31
web/src/engine/element-wrappers/src/wrapElement.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import type OutputTarget from './outputTarget.js';
|
||||
import Input from './input.js';
|
||||
import TextArea from './textarea.js';
|
||||
import DesignIFrame from './designIFrame.js';
|
||||
import ContentEditable from './contentEditable.js';
|
||||
import { nestedInstanceOf } from './utils.js';
|
||||
|
||||
export default function wrapElement(e: HTMLElement): OutputTarget<any> {
|
||||
// Complex type scoping is implemented here so that kmwutils.ts is not a dependency for test compilations.
|
||||
|
||||
if(nestedInstanceOf(e, "HTMLInputElement")) {
|
||||
return new Input(<HTMLInputElement> e);
|
||||
} else if(nestedInstanceOf(e, "HTMLTextAreaElement")) {
|
||||
return new TextArea(<HTMLTextAreaElement> e);
|
||||
} else if(nestedInstanceOf(e, "HTMLIFrameElement")) {
|
||||
let iframe = <HTMLIFrameElement> e;
|
||||
|
||||
if(iframe.contentWindow && iframe.contentWindow.document && iframe.contentWindow.document.designMode == "on") {
|
||||
return new DesignIFrame(iframe);
|
||||
} else if (e.isContentEditable) {
|
||||
// Do content-editable <iframe>s make sense?
|
||||
return new ContentEditable(e);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else if(e.isContentEditable) {
|
||||
return new ContentEditable(e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"outFile": "../../../build/engine/element-wrappers/obj/index.bundled.js"
|
||||
},
|
||||
|
||||
"references": [
|
||||
{ "path": "../../../../common/web/keyman-version", "prepend": true },
|
||||
{ "path": "../../../../common/web/utils", "prepend": true },
|
||||
{ "path": "../../../../common/web/keyboard-processor/src", "prepend": true }
|
||||
]
|
||||
}
|
||||
|
|
@ -4,28 +4,21 @@
|
|||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"inlineSources": true,
|
||||
"module": "none",
|
||||
"outFile": "../../../build/engine/element-wrappers/obj/index.js",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"module": "es6",
|
||||
"moduleResolution": "Node",
|
||||
"sourceMap": true,
|
||||
"target": "es5"
|
||||
"target": "es5",
|
||||
"outDir": "../../../build/engine/element-wrappers/obj/",
|
||||
"tsBuildInfoFile": "../../../build/engine/element-wrappers/obj/tsconfig.tsbuildinfo",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
|
||||
"files": [
|
||||
"../main/dom/targets/wrapElement.ts",
|
||||
"../main/dom/targets/input.ts",
|
||||
"../main/dom/targets/textarea.ts",
|
||||
"../main/dom/targets/contentEditable.ts",
|
||||
"../main/dom/targets/designIFrame.ts",
|
||||
"../main/dom/targets/outputTarget.ts",
|
||||
"../main/kmwexthtml.ts",
|
||||
"../main/dom/utils.ts",
|
||||
"../main/kmwtypedefs.ts",
|
||||
"../../../../node_modules/eventemitter3/index.js"
|
||||
],
|
||||
"include": [ "src/**/*.ts" ],
|
||||
|
||||
"references": [
|
||||
{ "path": "../../../../common/web/keyman-version" },
|
||||
{ "path": "../../../../common/web/utils" },
|
||||
{ "path": "../../../../common/web/keyboard-processor/src" }
|
||||
// { "path": "../../../../common/web/keyman-version" },
|
||||
// { "path": "../../../../common/web/utils" },
|
||||
{ "path": "../../../../common/web/keyboard-processor" }
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,267 +0,0 @@
|
|||
namespace com.keyman.dom.targets {
|
||||
class SelectionCaret {
|
||||
node: Node;
|
||||
offset: number;
|
||||
|
||||
constructor(node, offset) {
|
||||
this.node = node;
|
||||
this.offset = offset;
|
||||
}
|
||||
}
|
||||
|
||||
class SelectionRange {
|
||||
start: SelectionCaret;
|
||||
end: SelectionCaret;
|
||||
|
||||
constructor(start, end) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
}
|
||||
|
||||
export class ContentEditable extends OutputTarget {
|
||||
root: HTMLElement;
|
||||
|
||||
constructor(ele: HTMLElement) {
|
||||
if(ele.isContentEditable) {
|
||||
super();
|
||||
this.root = ele;
|
||||
} else {
|
||||
throw "Specified element is not already content-editable!";
|
||||
}
|
||||
}
|
||||
|
||||
get isSynthetic(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
getElement(): HTMLElement {
|
||||
return this.root;
|
||||
}
|
||||
|
||||
isSelectionEmpty(): boolean {
|
||||
if(!this.hasSelection()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.root.ownerDocument.getSelection().isCollapsed;
|
||||
}
|
||||
|
||||
hasSelection(): boolean {
|
||||
let Lsel = this.root.ownerDocument.getSelection();
|
||||
|
||||
if(this.root != Lsel.anchorNode && !this.root.contains(Lsel.anchorNode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(this.root != Lsel.focusNode && !this.root.contains(Lsel.focusNode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
clearSelection(): void {
|
||||
if(this.hasSelection()) {
|
||||
let Lsel = this.root.ownerDocument.getSelection();
|
||||
|
||||
if(!Lsel.isCollapsed) {
|
||||
Lsel.deleteFromDocument(); // I2134, I2192
|
||||
}
|
||||
} else {
|
||||
console.warn("Attempted to clear an unowned Selection!");
|
||||
}
|
||||
}
|
||||
|
||||
invalidateSelection(): void { /* No cache maintenance needed here, partly because
|
||||
* it's impossible to cache a Selection; it mutates.
|
||||
*/ }
|
||||
|
||||
getCarets(): SelectionRange {
|
||||
let Lsel = this.root.ownerDocument.getSelection();
|
||||
let code = Lsel.anchorNode.compareDocumentPosition(Lsel.focusNode);
|
||||
|
||||
if(Lsel.isCollapsed) {
|
||||
let caret = new SelectionCaret(Lsel.anchorNode, Lsel.anchorOffset);
|
||||
return new SelectionRange(caret, caret);
|
||||
} else {
|
||||
let anchor = new SelectionCaret(Lsel.anchorNode, Lsel.anchorOffset);
|
||||
let focus = new SelectionCaret(Lsel.focusNode, Lsel.focusOffset);
|
||||
|
||||
if(anchor.node == focus.node) {
|
||||
code = (focus.offset - anchor.offset > 0) ? 2 : 4;
|
||||
}
|
||||
|
||||
if(code & 2) {
|
||||
return new SelectionRange(anchor, focus);
|
||||
} else { // Default
|
||||
// can test against code & 4 to ensure Focus is before anchor, though.
|
||||
return new SelectionRange(focus, anchor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getDeadkeyCaret(): number {
|
||||
return this.getTextBeforeCaret().kmwLength();
|
||||
}
|
||||
|
||||
getTextBeforeCaret(): string {
|
||||
if(!this.hasSelection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let caret = this.getCarets().start;
|
||||
|
||||
if(caret.node.nodeType != 3) {
|
||||
return ''; // Must be a text node to provide a context.
|
||||
}
|
||||
|
||||
return caret.node.textContent.substr(0, caret.offset);
|
||||
}
|
||||
|
||||
getTextAfterCaret(): string {
|
||||
if(!this.hasSelection()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let caret = this.getCarets().end;
|
||||
|
||||
if(caret.node.nodeType != 3) {
|
||||
return ''; // Must be a text node to provide a context.
|
||||
}
|
||||
|
||||
return caret.node.textContent.substr(caret.offset);
|
||||
}
|
||||
|
||||
getText(): string {
|
||||
return this.root.innerText;
|
||||
}
|
||||
|
||||
deleteCharsBeforeCaret(dn: number) {
|
||||
if(!this.hasSelection() || dn <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let start = this.getCarets().start;
|
||||
|
||||
// Bounds-check on the number of chars to delete.
|
||||
if(dn > start.offset) {
|
||||
dn = start.offset;
|
||||
}
|
||||
|
||||
if(start.node.nodeType != 3) {
|
||||
console.warn("Deletion of characters requested without available context!");
|
||||
return; // No context to delete characters from.
|
||||
}
|
||||
|
||||
let range = this.root.ownerDocument.createRange();
|
||||
let dnOffset = start.offset - start.node.nodeValue.substr(0, start.offset)._kmwSubstr(-dn).length;
|
||||
|
||||
range.setStart(start.node, dnOffset);
|
||||
range.setEnd(start.node, start.offset);
|
||||
|
||||
this.adjustDeadkeys(-dn);
|
||||
range.deleteContents();
|
||||
// No need to reposition the caret - the DOM will auto-move the selection accordingly, since
|
||||
// we didn't use the selection to delete anything.
|
||||
}
|
||||
|
||||
insertTextBeforeCaret(s: string) {
|
||||
if(!this.hasSelection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let start = this.getCarets().start;
|
||||
let delta = s._kmwLength();
|
||||
let Lsel = this.root.ownerDocument.getSelection();
|
||||
|
||||
if(delta == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.adjustDeadkeys(delta);
|
||||
|
||||
// While Selection.extend() was really nice for this, IE didn't support it whatsoever.
|
||||
// However, IE (11, at least) DID support setting selections via ranges, so we were still
|
||||
// able to manage the caret properly.
|
||||
//
|
||||
// TODO: double-check that it was only IE-motivated, re-implement with Selection.extend().
|
||||
let finalCaret = this.root.ownerDocument.createRange();
|
||||
|
||||
if(start.node.nodeType == 3) {
|
||||
let textStart = <Text> start.node;
|
||||
textStart.insertData(start.offset, s);
|
||||
finalCaret.setStart(textStart, start.offset + s.length);
|
||||
} else {
|
||||
// Create a new text node - empty control
|
||||
var n = start.node.ownerDocument.createTextNode(s);
|
||||
|
||||
let range = this.root.ownerDocument.createRange();
|
||||
range.setStart(start.node, start.offset);
|
||||
range.collapse(true);
|
||||
range.insertNode(n);
|
||||
finalCaret.setStart(n, s.length);
|
||||
}
|
||||
|
||||
finalCaret.collapse(true);
|
||||
Lsel.removeAllRanges();
|
||||
try {
|
||||
Lsel.addRange(finalCaret);
|
||||
} catch(e) {
|
||||
// Chrome (through 4.0 at least) throws an exception because it has not synchronised its content with the selection.
|
||||
// scrollIntoView synchronises the content for selection
|
||||
start.node.parentElement.scrollIntoView();
|
||||
Lsel.addRange(finalCaret);
|
||||
}
|
||||
Lsel.collapseToEnd();
|
||||
}
|
||||
|
||||
handleNewlineAtCaret(): void {
|
||||
// TODO: Implement.
|
||||
//
|
||||
// As it turns out, we never had an implementation for handling newline inputs from the OSK for this element type.
|
||||
// At least this way, it's more explicit.
|
||||
//
|
||||
// Note: consult "// Create a new text node - empty control" case in insertTextBeforeCaret -
|
||||
// this helps to handle the browser-default implementation of newline handling. In particular,
|
||||
// entry of the first character after a newline.
|
||||
//
|
||||
// If raw newlines are entered into the HTML, but as with usual HTML, they're interpreted as excess whitespace and
|
||||
// have no effect. We need to add DOM elements for a functional newline.
|
||||
}
|
||||
|
||||
protected setTextAfterCaret(s: string) {
|
||||
if(!this.hasSelection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let caret = this.getCarets().end;
|
||||
let delta = s._kmwLength();
|
||||
let Lsel = this.root.ownerDocument.getSelection();
|
||||
|
||||
if(delta == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This is designed explicitly for use in direct-setting operations; deadkeys
|
||||
// will be handled after this method.
|
||||
|
||||
if(caret.node.nodeType == 3) {
|
||||
let textStart = <Text> caret.node;
|
||||
textStart.replaceData(caret.offset, textStart.length, s);
|
||||
} else {
|
||||
// Create a new text node - empty control
|
||||
var n = caret.node.ownerDocument.createTextNode(s);
|
||||
|
||||
let range = this.root.ownerDocument.createRange();
|
||||
range.setStart(caret.node, caret.offset);
|
||||
range.collapse(true);
|
||||
range.insertNode(n);
|
||||
}
|
||||
}
|
||||
|
||||
doInputEvent() {
|
||||
this.dispatchInputEventOn(this.root);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,352 +0,0 @@
|
|||
namespace com.keyman.dom.targets {
|
||||
class SelectionCaret {
|
||||
node: Node;
|
||||
offset: number;
|
||||
|
||||
constructor(node, offset) {
|
||||
this.node = node;
|
||||
this.offset = offset;
|
||||
}
|
||||
}
|
||||
|
||||
class SelectionRange {
|
||||
start: SelectionCaret;
|
||||
end: SelectionCaret;
|
||||
|
||||
constructor(start, end) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
}
|
||||
|
||||
class StyleCommand {
|
||||
cmd: string;
|
||||
stateType: number;
|
||||
cache: string|boolean;
|
||||
|
||||
constructor(c: string, s:number) {
|
||||
this.cmd = c;
|
||||
this.stateType = s;
|
||||
}
|
||||
}
|
||||
|
||||
export class DesignIFrame extends OutputTarget {
|
||||
root: HTMLIFrameElement;
|
||||
doc: Document;
|
||||
docRoot: HTMLElement;
|
||||
|
||||
commandCache: StyleCommand[];
|
||||
|
||||
constructor(ele: HTMLIFrameElement) {
|
||||
super();
|
||||
this.root = ele;
|
||||
|
||||
if(ele.contentWindow && ele.contentWindow.document && ele.contentWindow.document.designMode == 'on') {
|
||||
this.doc = ele.contentWindow.document;
|
||||
this.docRoot = ele.contentWindow.document.documentElement;
|
||||
} else {
|
||||
throw "Specified IFrame is not in design-mode!";
|
||||
}
|
||||
}
|
||||
|
||||
get isSynthetic(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
getElement(): HTMLIFrameElement {
|
||||
return this.root;
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
this.doc.defaultView.focus(); // I3363 (Build 301)
|
||||
}
|
||||
|
||||
isSelectionEmpty(): boolean {
|
||||
if(!this.hasSelection()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.doc.getSelection().isCollapsed;
|
||||
}
|
||||
|
||||
hasSelection(): boolean {
|
||||
let Lsel = this.doc.getSelection();
|
||||
let outerSel = document.getSelection();
|
||||
|
||||
// If the outer doc's selection matches, we're active.
|
||||
if(outerSel.anchorNode == Lsel.anchorNode && outerSel.focusNode == Lsel.focusNode) {
|
||||
return true;
|
||||
} else {
|
||||
// Problem: for testing, we can't enforce the ideal (ie: first) condition.
|
||||
// Technically, the IFrame _will_ always have its own internal selection, though... so... it kinda works?
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
clearSelection(): void {
|
||||
if(this.hasSelection()) {
|
||||
let Lsel = this.doc.getSelection();
|
||||
|
||||
if(!Lsel.isCollapsed) {
|
||||
Lsel.deleteFromDocument(); // I2134, I2192
|
||||
}
|
||||
} else {
|
||||
console.warn("Attempted to clear an unowned Selection!");
|
||||
}
|
||||
}
|
||||
|
||||
invalidateSelection(): void { /* No cache maintenance needed here, partly because
|
||||
* it's impossible to cache a Selection; it mutates.
|
||||
*/ }
|
||||
|
||||
getCarets(): SelectionRange {
|
||||
let Lsel = this.doc.getSelection();
|
||||
let code = Lsel.anchorNode.compareDocumentPosition(Lsel.focusNode);
|
||||
|
||||
if(Lsel.isCollapsed) {
|
||||
let caret = new SelectionCaret(Lsel.anchorNode, Lsel.anchorOffset);
|
||||
return new SelectionRange(caret, caret);
|
||||
} else {
|
||||
let anchor = new SelectionCaret(Lsel.anchorNode, Lsel.anchorOffset);
|
||||
let focus = new SelectionCaret(Lsel.focusNode, Lsel.focusOffset);
|
||||
|
||||
if(anchor.node == focus.node) {
|
||||
code = (focus.offset - anchor.offset > 0) ? 2 : 4;
|
||||
}
|
||||
|
||||
if(code & 2) {
|
||||
return new SelectionRange(anchor, focus);
|
||||
} else { // Default
|
||||
// can test against code & 4 to ensure Focus is before anchor, though.
|
||||
return new SelectionRange(focus, anchor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getDeadkeyCaret(): number {
|
||||
return this.getTextBeforeCaret().kmwLength();
|
||||
}
|
||||
|
||||
getTextBeforeCaret(): string {
|
||||
if(!this.hasSelection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let caret = this.getCarets().start;
|
||||
|
||||
if(caret.node.nodeType != 3) {
|
||||
return ''; // Must be a text node to provide a context.
|
||||
}
|
||||
|
||||
return caret.node.textContent.substr(0, caret.offset);
|
||||
}
|
||||
|
||||
getTextAfterCaret(): string {
|
||||
if(!this.hasSelection()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let caret = this.getCarets().end;
|
||||
|
||||
if(caret.node.nodeType != 3) {
|
||||
return ''; // Must be a text node to provide a context.
|
||||
}
|
||||
|
||||
return caret.node.textContent.substr(caret.offset);
|
||||
}
|
||||
|
||||
getText(): string {
|
||||
return this.docRoot.innerText;
|
||||
}
|
||||
|
||||
deleteCharsBeforeCaret(dn: number) {
|
||||
if(!this.hasSelection() || dn <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let start = this.getCarets().start;
|
||||
|
||||
// Bounds-check on the number of chars to delete.
|
||||
if(dn > start.offset) {
|
||||
dn = start.offset;
|
||||
}
|
||||
|
||||
if(start.node.nodeType != 3) {
|
||||
console.warn("Deletion of characters requested without available context!");
|
||||
return; // No context to delete characters from.
|
||||
}
|
||||
|
||||
let range = this.doc.createRange();
|
||||
let dnOffset = start.offset - start.node.nodeValue.substr(0, start.offset)._kmwSubstr(-dn).length;
|
||||
|
||||
range.setStart(start.node, dnOffset);
|
||||
range.setEnd(start.node, start.offset);
|
||||
|
||||
this.adjustDeadkeys(-dn);
|
||||
range.deleteContents();
|
||||
// No need to reposition the caret - the DOM will auto-move the selection accordingly, since
|
||||
// we didn't use the selection to delete anything.
|
||||
}
|
||||
|
||||
insertTextBeforeCaret(s: string) {
|
||||
if(!this.hasSelection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let start = this.getCarets().start;
|
||||
let delta = s._kmwLength();
|
||||
let Lsel = this.doc.getSelection();
|
||||
|
||||
if(delta == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.adjustDeadkeys(delta);
|
||||
|
||||
// While Selection.extend() was really nice for this, IE didn't support it whatsoever.
|
||||
// However, IE (11, at least) DID support setting selections via ranges, so we were still
|
||||
// able to manage the caret properly.
|
||||
//
|
||||
// TODO: double-check that it was only IE-motivated, re-implement with Selection.extend().
|
||||
let finalCaret = this.root.ownerDocument.createRange();
|
||||
|
||||
if(start.node.nodeType == 3) {
|
||||
let textStart = <Text> start.node;
|
||||
textStart.insertData(start.offset, s);
|
||||
finalCaret.setStart(textStart, start.offset + s.length);
|
||||
} else {
|
||||
// Create a new text node - empty control
|
||||
var n = this.doc.createTextNode(s);
|
||||
|
||||
let range = this.doc.createRange();
|
||||
range.setStart(start.node, start.offset);
|
||||
range.collapse(true);
|
||||
range.insertNode(n);
|
||||
finalCaret.setStart(n, s.length);
|
||||
}
|
||||
|
||||
finalCaret.collapse(true);
|
||||
Lsel.removeAllRanges();
|
||||
try {
|
||||
Lsel.addRange(finalCaret);
|
||||
} catch(e) {
|
||||
// Chrome (through 4.0 at least) throws an exception because it has not synchronised its content with the selection.
|
||||
// scrollIntoView synchronises the content for selection
|
||||
start.node.parentElement.scrollIntoView();
|
||||
Lsel.addRange(finalCaret);
|
||||
}
|
||||
Lsel.collapseToEnd();
|
||||
}
|
||||
|
||||
handleNewlineAtCaret(): void {
|
||||
// TODO: Implement.
|
||||
//
|
||||
// As it turns out, we never had an implementation for handling newline inputs from the OSK for this element type.
|
||||
// At least this way, it's more explicit.
|
||||
//
|
||||
// Note: consult "// Create a new text node - empty control" case in insertTextBeforeCaret -
|
||||
// this helps to handle the browser-default implementation of newline handling. In particular,
|
||||
// entry of the first character after a newline.
|
||||
//
|
||||
// If raw newlines are entered into the HTML, but as with usual HTML, they're interpreted as excess whitespace and
|
||||
// have no effect. We need to add DOM elements for a functional newline.
|
||||
}
|
||||
|
||||
protected setTextAfterCaret(s: string) {
|
||||
if(!this.hasSelection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let caret = this.getCarets().end;
|
||||
let delta = s._kmwLength();
|
||||
let Lsel = this.doc.getSelection();
|
||||
|
||||
if(delta == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This is designed explicitly for use in direct-setting operations; deadkeys
|
||||
// will be handled after this method.
|
||||
|
||||
if(caret.node.nodeType == 3) {
|
||||
let textStart = <Text> caret.node;
|
||||
textStart.replaceData(caret.offset, textStart.length, s);
|
||||
} else {
|
||||
// Create a new text node - empty control
|
||||
var n = caret.node.ownerDocument.createTextNode(s);
|
||||
|
||||
let range = this.root.ownerDocument.createRange();
|
||||
range.setStart(caret.node, caret.offset);
|
||||
range.collapse(true);
|
||||
range.insertNode(n);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function saveProperties
|
||||
* Scope Private
|
||||
* Description Build and create list of styles that can be applied in iframes
|
||||
*/
|
||||
saveProperties() {
|
||||
// Formerly _CacheCommands.
|
||||
var _CacheableCommands=[
|
||||
new StyleCommand('backcolor',1), new StyleCommand('fontname',1), new StyleCommand('fontsize',1),
|
||||
new StyleCommand('forecolor',1), new StyleCommand('bold',0), new StyleCommand('italic',0),
|
||||
new StyleCommand('strikethrough',0), new StyleCommand('subscript',0),
|
||||
new StyleCommand('superscript',0), new StyleCommand('underline',0)
|
||||
];
|
||||
|
||||
if(this.doc.defaultView) {
|
||||
_CacheableCommands.push(new StyleCommand('hilitecolor',1));
|
||||
}
|
||||
|
||||
for(var n=0; n < _CacheableCommands.length; n++) { // I1511 - array prototype extended
|
||||
let cmd = _CacheableCommands[n];
|
||||
//KeymanWeb._Debug('Command:'+_CacheableCommands[n][0]);
|
||||
if(cmd.stateType == 1) {
|
||||
cmd.cache = this.doc.queryCommandValue(cmd.cmd);
|
||||
} else {
|
||||
cmd.cache = this.doc.queryCommandState(cmd.cmd);
|
||||
}
|
||||
}
|
||||
this.commandCache = _CacheableCommands;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function restoreProperties
|
||||
* Scope Private
|
||||
* Description Restore styles in IFRAMEs (??)
|
||||
*/
|
||||
restoreProperties(_func?: () => void): void {
|
||||
// Formerly _CacheCommandsReset.
|
||||
if(!this.commandCache) {
|
||||
console.error("No command cache exists to restore!");
|
||||
}
|
||||
|
||||
for(var n=0; n < this.commandCache.length; n++) { // I1511 - array prototype extended
|
||||
let cmd = this.commandCache[n];
|
||||
|
||||
//KeymanWeb._Debug('ResetCacheCommand:'+_CacheableCommands[n][0]+'='+_CacheableCommands[n][2]);
|
||||
if(cmd.stateType == 1) {
|
||||
if(this.doc.queryCommandValue(cmd.cmd) != cmd.cache) {
|
||||
if(_func) {
|
||||
_func();
|
||||
}
|
||||
this.doc.execCommand(cmd.cmd, false, <string> cmd.cache);
|
||||
}
|
||||
} else if(this.doc.queryCommandState(cmd.cmd) != cmd.cache) {
|
||||
if(_func) {
|
||||
_func();
|
||||
}
|
||||
//KeymanWeb._Debug('executing command '+_CacheableCommand[n][0]);
|
||||
this.doc.execCommand(cmd.cmd, false, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
doInputEvent() {
|
||||
// Root = the iframe, the outermost component and the one we were originally told to attach to.
|
||||
this.dispatchInputEventOn(this.root);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,177 +0,0 @@
|
|||
namespace com.keyman.dom.targets {
|
||||
|
||||
export class Input extends OutputTarget {
|
||||
root: HTMLInputElement;
|
||||
|
||||
/**
|
||||
* Tracks the most recently-cached selection start index.
|
||||
*/
|
||||
private _cachedSelectionStart: number
|
||||
|
||||
/**
|
||||
* Tracks the most recently processed, extended-string-based selection start index.
|
||||
* When the element's selectionStart value changes, this should be invalidated.
|
||||
*/
|
||||
private processedSelectionStart: number;
|
||||
|
||||
/**
|
||||
* Tracks the most recently processed, extended-string-based selection end index.
|
||||
* When the element's selectionEnd value changes, this should be invalidated.
|
||||
*/
|
||||
private processedSelectionEnd: number;
|
||||
|
||||
constructor(ele: HTMLInputElement) {
|
||||
super();
|
||||
|
||||
this.root = ele;
|
||||
this._cachedSelectionStart = -1;
|
||||
}
|
||||
|
||||
get isSynthetic(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
getElement(): HTMLInputElement {
|
||||
return this.root;
|
||||
}
|
||||
|
||||
clearSelection(): void {
|
||||
// Processes our codepoint-based variants of selectionStart and selectionEnd.
|
||||
this.getCaret(); // updates processedSelectionStart if required
|
||||
this.root.value = this.root.value._kmwSubstring(0, this.processedSelectionStart) + this.root.value._kmwSubstring(this.processedSelectionEnd); //I3319
|
||||
|
||||
this.setCaret(this.processedSelectionStart);
|
||||
}
|
||||
|
||||
isSelectionEmpty(): boolean {
|
||||
return this.root.selectionStart == this.root.selectionEnd;
|
||||
}
|
||||
|
||||
hasSelection(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
invalidateSelection() {
|
||||
// Since .selectionStart will never return this value, we use it to indicate
|
||||
// the need to refresh our processed indices.
|
||||
this._cachedSelectionStart = -1;
|
||||
}
|
||||
|
||||
getCaret(): number {
|
||||
if(this.root.selectionStart != this._cachedSelectionStart) {
|
||||
this._cachedSelectionStart = this.root.selectionStart; // KMW-1
|
||||
this.processedSelectionStart = this.root.value._kmwCodeUnitToCodePoint(this.root.selectionStart); // I3319
|
||||
this.processedSelectionEnd = this.root.value._kmwCodeUnitToCodePoint(this.root.selectionEnd); // I3319
|
||||
}
|
||||
return this.root.selectionDirection == 'forward' ? this.processedSelectionEnd : this.processedSelectionStart;
|
||||
}
|
||||
|
||||
getDeadkeyCaret(): number {
|
||||
return this.getCaret();
|
||||
}
|
||||
|
||||
setCaret(caret: number) {
|
||||
this.setSelection(caret, caret, "none");
|
||||
}
|
||||
|
||||
setSelection(start: number, end: number, direction: "forward" | "backward" | "none") {
|
||||
let domStart = this.root.value._kmwCodePointToCodeUnit(start);
|
||||
let domEnd = this.root.value._kmwCodePointToCodeUnit(end);
|
||||
this.root.setSelectionRange(domStart, domEnd, direction);
|
||||
|
||||
this.processedSelectionStart = start;
|
||||
this.processedSelectionEnd = end;
|
||||
|
||||
Utils.forceScroll(this.root);
|
||||
}
|
||||
|
||||
getSelectionDirection(): "forward" | "backward" | "none" {
|
||||
return this.root.selectionDirection;
|
||||
}
|
||||
|
||||
getTextBeforeCaret(): string {
|
||||
this.getCaret();
|
||||
return this.getText()._kmwSubstring(0, this.processedSelectionStart);
|
||||
}
|
||||
|
||||
setTextBeforeCaret(text: string) {
|
||||
this.getCaret();
|
||||
let selectionLength = this.processedSelectionEnd - this.processedSelectionStart;
|
||||
let direction = this.getSelectionDirection();
|
||||
let newCaret = text._kmwLength();
|
||||
this.root.value = text + this.getText()._kmwSubstring(this.processedSelectionStart);
|
||||
|
||||
this.setSelection(newCaret, newCaret + selectionLength, direction);
|
||||
}
|
||||
|
||||
protected setTextAfterCaret(s: string) {
|
||||
let c = this.getCaret();
|
||||
let direction = this.getSelectionDirection();
|
||||
|
||||
this.root.value = this.getTextBeforeCaret() + s;
|
||||
this.setSelection(this.processedSelectionStart, this.processedSelectionEnd, direction);
|
||||
}
|
||||
|
||||
getTextAfterCaret(): string {
|
||||
this.getCaret();
|
||||
return this.getText()._kmwSubstring(this.processedSelectionEnd);
|
||||
}
|
||||
|
||||
getText(): string {
|
||||
return this.root.value;
|
||||
}
|
||||
|
||||
deleteCharsBeforeCaret(dn: number) {
|
||||
if(dn > 0) {
|
||||
let curText = this.getTextBeforeCaret();
|
||||
let caret = this.processedSelectionStart;
|
||||
|
||||
if(dn > caret) {
|
||||
dn = caret;
|
||||
}
|
||||
|
||||
this.adjustDeadkeys(-dn);
|
||||
this.setTextBeforeCaret(curText.kmwSubstring(0, caret - dn));
|
||||
this.setCaret(caret - dn);
|
||||
}
|
||||
}
|
||||
|
||||
insertTextBeforeCaret(s: string) {
|
||||
if(!s) {
|
||||
return;
|
||||
}
|
||||
|
||||
let caret = this.getCaret();
|
||||
let front = this.getTextBeforeCaret();
|
||||
let back = this.getText()._kmwSubstring(this.processedSelectionStart);
|
||||
|
||||
this.adjustDeadkeys(s._kmwLength());
|
||||
this.root.value = front + s + back;
|
||||
this.setCaret(caret + s._kmwLength());
|
||||
}
|
||||
|
||||
handleNewlineAtCaret(): void {
|
||||
Input.newlineHandler(this.root);
|
||||
}
|
||||
|
||||
static newlineHandler(inputEle: HTMLInputElement) {
|
||||
// Can't occur for Mocks - just Input types.
|
||||
if (inputEle && (inputEle.type == 'search' || inputEle.type == 'submit')) {
|
||||
inputEle.disabled=false;
|
||||
inputEle.form.submit();
|
||||
} else {
|
||||
// Allows compiling this separately from the main body of KMW.
|
||||
// TODO: rework class to accept a class-static 'callback' from the DOM module that this can call.
|
||||
// Would eliminate the need for this 'static' reference.
|
||||
// Only strongly matters once we better modularize KMW, with web-dom vs web-dom-targets vs web-core, etc.
|
||||
if(com.keyman["singleton"]) {
|
||||
com.keyman["singleton"].domManager.moveToNext(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
doInputEvent() {
|
||||
this.dispatchInputEventOn(this.root);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
namespace com.keyman.dom.targets {
|
||||
export abstract class OutputTarget extends text.OutputTarget {
|
||||
/**
|
||||
* Returns the underlying element / document modeled by the wrapper.
|
||||
*/
|
||||
abstract getElement(): HTMLElement;
|
||||
|
||||
public focus(): void {
|
||||
const ele = this.getElement();
|
||||
if(ele.focus) {
|
||||
ele.focus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper method for doInputEvent; creates a simple common event and default dispatching.
|
||||
* @param elem
|
||||
*/
|
||||
protected dispatchInputEventOn(elem: HTMLElement) {
|
||||
let event: InputEvent;
|
||||
|
||||
// `undefined` in pre-Chrome Edge and Chrome for Android before version 60.
|
||||
if(window['InputEvent']) { // can't condition on the type directly; TS optimizes that out.
|
||||
event = new InputEvent('input', {"bubbles": true, "cancelable": false});
|
||||
}
|
||||
|
||||
if(elem && event) {
|
||||
elem.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
apply(transform: Transform) {
|
||||
super.apply(transform);
|
||||
|
||||
// This class has non-integrated unit tests in which the `singleton` object doesn't exist.
|
||||
// Thus, we need to test for this case.
|
||||
let keyman = com.keyman['singleton'];
|
||||
|
||||
// Signal the necessary text changes to the embedding app, if it exists.
|
||||
if(keyman && keyman['oninserttext'] && keyman.isEmbedded) {
|
||||
keyman['oninserttext'](transform.deleteLeft, transform.insert, transform.deleteRight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,170 +0,0 @@
|
|||
namespace com.keyman.dom.targets {
|
||||
export class TextArea extends OutputTarget {
|
||||
root: HTMLTextAreaElement;
|
||||
|
||||
/**
|
||||
* Tracks the most recently-cached selection start index.
|
||||
*/
|
||||
private _cachedSelectionStart: number
|
||||
|
||||
/**
|
||||
* Tracks the most recently processed, extended-string-based selection start index.
|
||||
* When the element's selectionStart value changes, this should be invalidated.
|
||||
*/
|
||||
private processedSelectionStart: number;
|
||||
|
||||
/**
|
||||
* Tracks the most recently processed, extended-string-based selection end index.
|
||||
* When the element's selectionEnd value changes, this should be invalidated.
|
||||
*/
|
||||
private processedSelectionEnd: number;
|
||||
|
||||
/**
|
||||
* Used to temporarily store the y-axis scroll coordinate.
|
||||
*/
|
||||
private scrollTop?: number;
|
||||
|
||||
/**
|
||||
* Used to temporarily store the x-axis scroll coordinate.
|
||||
*/
|
||||
private scrollLeft?: number;
|
||||
|
||||
constructor(ele: HTMLTextAreaElement) {
|
||||
super();
|
||||
|
||||
this.root = ele;
|
||||
this._cachedSelectionStart = -1;
|
||||
}
|
||||
|
||||
get isSynthetic(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
getElement(): HTMLTextAreaElement {
|
||||
return this.root;
|
||||
}
|
||||
|
||||
clearSelection(): void {
|
||||
// Processes our codepoint-based variants of selectionStart and selectionEnd.
|
||||
this.getCaret(); // updates processedSelectionStart if required
|
||||
this.root.value = this.root.value._kmwSubstring(0, this.processedSelectionStart) + this.root.value._kmwSubstring(this.processedSelectionEnd); //I3319
|
||||
|
||||
this.setCaret(this.processedSelectionStart);
|
||||
}
|
||||
|
||||
isSelectionEmpty(): boolean {
|
||||
return this.root.selectionStart == this.root.selectionEnd;
|
||||
}
|
||||
|
||||
hasSelection(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
invalidateSelection() {
|
||||
// Since .selectionStart will never return this value, we use it to indicate
|
||||
// the need to refresh our processed indices.
|
||||
this._cachedSelectionStart = -1;
|
||||
}
|
||||
|
||||
getCaret(): number {
|
||||
if(this.root.selectionStart != this._cachedSelectionStart) {
|
||||
this._cachedSelectionStart = this.root.selectionStart; // KMW-1
|
||||
this.processedSelectionStart = this.root.value._kmwCodeUnitToCodePoint(this.root.selectionStart); // I3319
|
||||
this.processedSelectionEnd = this.root.value._kmwCodeUnitToCodePoint(this.root.selectionEnd); // I3319
|
||||
}
|
||||
return this.root.selectionDirection == 'forward' ? this.processedSelectionEnd : this.processedSelectionStart;
|
||||
}
|
||||
|
||||
getDeadkeyCaret(): number {
|
||||
return this.getCaret();
|
||||
}
|
||||
|
||||
setCaret(caret: number) {
|
||||
this.setSelection(caret, caret, "none");
|
||||
}
|
||||
|
||||
setSelection(start: number, end: number, direction: "forward" | "backward" | "none") {
|
||||
let domStart = this.root.value._kmwCodePointToCodeUnit(start);
|
||||
let domEnd = this.root.value._kmwCodePointToCodeUnit(end);
|
||||
this.root.setSelectionRange(domStart, domEnd, direction);
|
||||
|
||||
this.processedSelectionStart = start;
|
||||
this.processedSelectionEnd = end;
|
||||
|
||||
Utils.forceScroll(this.root);
|
||||
}
|
||||
|
||||
getSelectionDirection(): "forward" | "backward" | "none" {
|
||||
return this.root.selectionDirection;
|
||||
}
|
||||
|
||||
getTextBeforeCaret(): string {
|
||||
this.getCaret();
|
||||
return this.getText()._kmwSubstring(0, this.processedSelectionStart);
|
||||
}
|
||||
|
||||
setTextBeforeCaret(text: string) {
|
||||
this.getCaret();
|
||||
let selectionLength = this.processedSelectionEnd - this.processedSelectionStart;
|
||||
let direction = this.getSelectionDirection();
|
||||
let newCaret = text._kmwLength();
|
||||
this.root.value = text + this.getText()._kmwSubstring(this.processedSelectionStart);
|
||||
|
||||
this.setSelection(newCaret, newCaret + selectionLength, direction);
|
||||
}
|
||||
|
||||
protected setTextAfterCaret(s: string) {
|
||||
let c = this.getCaret();
|
||||
let direction = this.getSelectionDirection();
|
||||
|
||||
this.root.value = this.getTextBeforeCaret() + s;
|
||||
this.setSelection(this.processedSelectionStart, this.processedSelectionEnd, direction);
|
||||
}
|
||||
|
||||
getTextAfterCaret(): string {
|
||||
this.getCaret();
|
||||
return this.getText()._kmwSubstring(this.processedSelectionEnd);
|
||||
}
|
||||
|
||||
getText(): string {
|
||||
return this.root.value;
|
||||
}
|
||||
|
||||
deleteCharsBeforeCaret(dn: number) {
|
||||
if(dn > 0) {
|
||||
let curText = this.getTextBeforeCaret();
|
||||
let caret = this.processedSelectionStart;
|
||||
|
||||
if(dn > caret) {
|
||||
dn = caret;
|
||||
}
|
||||
|
||||
this.adjustDeadkeys(-dn);
|
||||
this.setTextBeforeCaret(curText.kmwSubstring(0, caret - dn));
|
||||
this.setCaret(caret - dn);
|
||||
}
|
||||
}
|
||||
|
||||
insertTextBeforeCaret(s: string) {
|
||||
if(!s) {
|
||||
return;
|
||||
}
|
||||
|
||||
let caret = this.getCaret();
|
||||
let front = this.getTextBeforeCaret();
|
||||
let back = this.getText()._kmwSubstring(this.processedSelectionStart);
|
||||
|
||||
this.adjustDeadkeys(s._kmwLength());
|
||||
this.root.value = front + s + back;
|
||||
this.setCaret(caret + s._kmwLength());
|
||||
}
|
||||
|
||||
handleNewlineAtCaret(): void {
|
||||
this.insertTextBeforeCaret('\n');
|
||||
}
|
||||
|
||||
doInputEvent() {
|
||||
this.dispatchInputEventOn(this.root);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
/// <reference path="outputTarget.ts" />
|
||||
// Defines a basic HTMLInputElement wrapper.
|
||||
///<reference path="input.ts" />
|
||||
// Defines a basic HTMLTextAreaElement wrapper.
|
||||
///<reference path="textarea.ts" />
|
||||
// Defines a basic content-editable wrapper.
|
||||
///<reference path="contentEditable.ts" />
|
||||
// Defines a basic design-mode IFrame wrapper.
|
||||
///<reference path="designIFrame.ts" />
|
||||
|
||||
namespace com.keyman.dom.targets {
|
||||
export function wrapElement(e: HTMLElement): OutputTarget {
|
||||
// Complex type scoping is implemented here so that kmwutils.ts is not a dependency for test compilations.
|
||||
|
||||
if(Utils.instanceof(e, "HTMLInputElement")) {
|
||||
return new Input(<HTMLInputElement> e);
|
||||
} else if(Utils.instanceof(e, "HTMLTextAreaElement")) {
|
||||
return new TextArea(<HTMLTextAreaElement> e);
|
||||
} else if(Utils.instanceof(e, "HTMLIFrameElement")) {
|
||||
let iframe = <HTMLIFrameElement> e;
|
||||
|
||||
if(iframe.contentWindow && iframe.contentWindow.document && iframe.contentWindow.document.designMode == "on") {
|
||||
return new DesignIFrame(iframe);
|
||||
} else if (e.isContentEditable) {
|
||||
// Do content-editable <iframe>s make sense?
|
||||
return new ContentEditable(e);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else if(e.isContentEditable) {
|
||||
return new ContentEditable(e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,7 @@
|
|||
namespace com.keyman.dom {
|
||||
// NOTE:
|
||||
// - instanceOf -> element-wrappers, now called nestedInstanceOf
|
||||
// - forceScroll -> element-wrappers, but I believe it's only ever called from there.
|
||||
|
||||
// Defines DOM-related utility functions that are not reliant on KMW's internal state.
|
||||
export class Utils {
|
||||
|
|
@ -109,73 +112,5 @@ namespace com.keyman.dom {
|
|||
}
|
||||
return Lcurtop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the type of an input DOM-related object while ensuring that it is checked against the correct prototype,
|
||||
* as class prototypes are (by specification) scoped upon the owning Window.
|
||||
*
|
||||
* See https://stackoverflow.com/questions/43587286/why-does-instanceof-return-false-on-chrome-safari-and-edge-and-true-on-firefox
|
||||
* for more details.
|
||||
*
|
||||
* @param {Element|Event} Pelem An element of the web page or one of its IFrame-based subdocuments.
|
||||
* @param {string} className The plain-text name of the expected Element type.
|
||||
* @return {boolean}
|
||||
*/
|
||||
static instanceof(Pelem: Event|EventTarget, className: string): boolean {
|
||||
var scopedClass;
|
||||
|
||||
if(!Pelem) {
|
||||
// If we're bothering to check something's type, null references don't match
|
||||
// what we're looking for.
|
||||
return false;
|
||||
}
|
||||
if (Pelem['Window']) { // Window objects contain the class definitions for types held within them. So, we can check for those.
|
||||
return className == 'Window';
|
||||
} else if (Pelem['defaultView']) { // Covers Document.
|
||||
scopedClass = Pelem['defaultView'][className];
|
||||
} else if(Pelem['ownerDocument']) {
|
||||
scopedClass = (Pelem as Node).ownerDocument.defaultView[className];
|
||||
} else if(Pelem['target']) {
|
||||
var event = Pelem as Event;
|
||||
|
||||
if(this.instanceof(event.target, 'Window')) {
|
||||
scopedClass = event.target[className];
|
||||
} else if(this.instanceof(event.target, 'Document')) {
|
||||
scopedClass = (event.target as Document).defaultView[className];
|
||||
} else if(this.instanceof(event.target, 'HTMLElement')) {
|
||||
scopedClass = (event.target as HTMLElement).ownerDocument.defaultView[className];
|
||||
}
|
||||
}
|
||||
|
||||
if(scopedClass) {
|
||||
return Pelem instanceof scopedClass;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static forceScroll(element: HTMLInputElement | HTMLTextAreaElement) {
|
||||
// Needed to allow ./build_dev_resources.sh to complete;
|
||||
// only executes when com.keyman.DOMEventHandlers is defined.
|
||||
//
|
||||
// We also bypass this whenever operating in the embedded format.
|
||||
if(com && com.keyman && com.keyman['DOMEventHandlers'] && !com.keyman['singleton']['isEmbedded']) {
|
||||
let DOMEventHandlers = com.keyman['DOMEventHandlers'];
|
||||
|
||||
let selectionStart = element.selectionStart;
|
||||
let selectionEnd = element.selectionEnd;
|
||||
|
||||
DOMEventHandlers.states._IgnoreBlurFocus = true;
|
||||
//Forces scrolling; the re-focus triggers the scroll, at least.
|
||||
element.blur();
|
||||
element.focus();
|
||||
DOMEventHandlers.states._IgnoreBlurFocus = false;
|
||||
|
||||
// On Edge, it appears that the blur/focus combination will reset the caret position
|
||||
// under certain scenarios during unit tests. So, we re-set it afterward.
|
||||
element.selectionStart = selectionStart;
|
||||
element.selectionEnd = selectionEnd;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,10 @@
|
|||
"extends": "../../tsconfig.base.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"outFile": "../../../build/engine/main/obj/keymanweb.js"
|
||||
"outFile": "../../../build/engine/main/obj/keymanweb.js",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"module": "es6",
|
||||
"moduleResolution": "Node",
|
||||
},
|
||||
|
||||
"include": [
|
||||
|
|
@ -20,9 +23,9 @@
|
|||
"references": [
|
||||
{ "path": "../../../../common/web/keyman-version"},
|
||||
{ "path": "../../../../common/web/utils"},
|
||||
{ "path": "../../../../common/predictive-text/browser.tsconfig.json"},
|
||||
{ "path": "../../../../common/web/input-processor/src"},
|
||||
{ "path": "../../../../common/web/keyboard-processor/src"},
|
||||
{ "path": "../../../../common/predictive-text"},
|
||||
{ "path": "../../../../common/web/input-processor"},
|
||||
{ "path": "../../../../common/web/keyboard-processor"},
|
||||
{ "path": "../../../../common/web/lm-message-types" },
|
||||
{ "path": "../device-detect" }
|
||||
]
|
||||
|
|
|
|||
|
|
@ -38,15 +38,28 @@ module.exports = {
|
|||
// list of files / patterns to load in the browser
|
||||
files: [
|
||||
'web/src/test/auto/modernizr.js', // A dependency-managed utility script that helps with browser feature detection.
|
||||
'web/build/engine/element-wrappers/obj/index.bundled.js', // Defines com.keyman.dom objects separate from KMW for unit testing.
|
||||
'web/build/engine/device-detect/obj/index.bundled.js', // Defines com.keyman.utils.Device, separated from KMW for use in unit test setup.
|
||||
'web/build/tools/testing/recorder/obj/index.js', // The object definitions used to generate/replicate key events for engine tests.
|
||||
// 'web/build/engine/element-wrappers/obj/index.bundled.js', // Defines com.keyman.dom objects separate from KMW for unit testing.
|
||||
// 'web/build/engine/device-detect/obj/index.bundled.js', // Defines com.keyman.utils.Device, separated from KMW for use in unit test setup.
|
||||
// 'web/build/tools/testing/recorder/obj/index.js', // The object definitions used to generate/replicate key events for engine tests.
|
||||
// Includes KMW's Device class, which is used by test_utils below.
|
||||
'web/src/test/auto/test_init_check.js', // Ensures that tests will initialize properly
|
||||
{pattern: 'web/src/test/auto/test_init_check.js', type: 'module'}, // Ensures that tests will initialize properly
|
||||
'common/test/resources/timeout-adapter.js', // Handles configuration timeout setup at runtime.
|
||||
'web/src/test/auto/test_utils.js', // A basic utility script useful for constructing tests
|
||||
'web/src/test/auto/cases/**/*.js', // Where the tests actually reside.
|
||||
{pattern: 'web/src/test/auto/test_utils.js', type: 'module'}, // A basic utility script useful for constructing tests
|
||||
|
||||
{pattern: 'web/src/test/auto/cases/**/*.js', type: 'module'}, // Where the tests actually reside.
|
||||
'common/test/resources/json/**/*.json', // Where pre-loaded JSON resides.
|
||||
|
||||
{pattern: 'web/build/**/*.js', watched: true, served: true, included: false}, // Includes all top-level KMW products
|
||||
{pattern: 'web/build/**/*.js.map', watched: true, served: true, included: false}, // and their sourcemaps.
|
||||
{pattern: 'web/build/**/*.mjs', watched: true, served: true, included: false}, // Includes all top-level KMW products
|
||||
{pattern: 'web/build/**/*.mjs.map', watched: true, served: true, included: false}, // and their sourcemaps.
|
||||
{ pattern: 'common/predictive-text/build/obj/**/*.*', watched: true, served: true, included: false },
|
||||
{ pattern: 'common/predictive-text/build/obj/**/*.js.map', watched: true, served: true, included: false },
|
||||
// { pattern: 'common/web/lm-worker/build/lib/*.js', watched: true, served: true, included: false},
|
||||
// { pattern: 'common/web/lm-worker/build/lib/*.js.map', watched: true, served: true, included: false},
|
||||
{pattern: 'common/web/**/*.js', watched: true, served: true, included: false},
|
||||
{pattern: 'common/web/**/*.js.map', watched: true, served: true, included: false},
|
||||
|
||||
{pattern: 'common/test/resources/fixtures/**/*.html', watched: true}, // HTML structures useful for testing.
|
||||
{pattern: 'common/test/resources/**/*.*', watched: true, served: true, included: false}, // General testing resources.
|
||||
{pattern: 'web/build/app/web/debug/**/*.css', watched: false, served: true, included: false}, // OSK resources
|
||||
|
|
@ -66,7 +79,11 @@ module.exports = {
|
|||
"/source/": "/base/web/build/app/web/debug/",
|
||||
"/ui-source/": "/base/web/build/app/ui/debug/",
|
||||
"/resources/": "/base/common/test/resources/",
|
||||
"/source/recorder_InputEvents.js.map": "/base/common/tests/recorder_InputEvents.js.map"
|
||||
"/source/recorder_InputEvents.js.map": "/base/common/tests/recorder_InputEvents.js.map",
|
||||
"/node_modules/": "/base/node_modules/",
|
||||
"/@keymanapp/web-utils/": "/base/common/web/utils/",
|
||||
"/@keymanapp/keyman/": "/base/web/",
|
||||
"/@keymanapp/keyboard-processor/": "/base/common/web/keyboard-processor/"
|
||||
},
|
||||
|
||||
|
||||
|
|
@ -1,257 +1,257 @@
|
|||
var assert = chai.assert;
|
||||
// var assert = chai.assert;
|
||||
|
||||
describe('Attachment API', function() {
|
||||
this.timeout(testconfig.timeouts.standard);
|
||||
// describe('Attachment API', function() {
|
||||
// this.timeout(testconfig.timeouts.standard);
|
||||
|
||||
before(function() {
|
||||
assert.isFalse(com.keyman.karma.DEVICE_DETECT_FAILURE, "Cannot run due to device detection failure.");
|
||||
fixture.setBase('fixtures');
|
||||
// before(function() {
|
||||
// assert.isFalse(com.keyman.karma.DEVICE_DETECT_FAILURE, "Cannot run due to device detection failure.");
|
||||
// fixture.setBase('fixtures');
|
||||
|
||||
this.timeout(testconfig.timeouts.scriptLoad * 3);
|
||||
return setupKMW({ attachType:'manual' }, testconfig.timeouts.scriptLoad).then(() => {
|
||||
const kbd1 = loadKeyboardFromJSON("/keyboards/lao_2008_basic.json", testconfig.timeouts.scriptLoad, { passive: true });
|
||||
const kbd2 = loadKeyboardFromJSON("/keyboards/khmer_angkor.json", testconfig.timeouts.scriptLoad, { passive: true });
|
||||
return Promise.all([kbd1, kbd2]).then(() => {
|
||||
return keyman.setActiveKeyboard("lao_2008_basic", "lo");
|
||||
});
|
||||
});
|
||||
});
|
||||
// this.timeout(testconfig.timeouts.scriptLoad * 3);
|
||||
// return setupKMW({ attachType:'manual' }, testconfig.timeouts.scriptLoad).then(() => {
|
||||
// const kbd1 = loadKeyboardFromJSON("/keyboards/lao_2008_basic.json", testconfig.timeouts.scriptLoad, { passive: true });
|
||||
// const kbd2 = loadKeyboardFromJSON("/keyboards/khmer_angkor.json", testconfig.timeouts.scriptLoad, { passive: true });
|
||||
// return Promise.all([kbd1, kbd2]).then(() => {
|
||||
// return keyman.setActiveKeyboard("lao_2008_basic", "lo");
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
||||
after(function() {
|
||||
keyman.removeKeyboards('lao_2008_basic');
|
||||
keyman.removeKeyboards('khmer_angkor');
|
||||
teardownKMW();
|
||||
});
|
||||
// after(function() {
|
||||
// keyman.removeKeyboards('lao_2008_basic');
|
||||
// keyman.removeKeyboards('khmer_angkor');
|
||||
// teardownKMW();
|
||||
// });
|
||||
|
||||
beforeEach(function() {
|
||||
fixture.load("robustAttachment.html");
|
||||
});
|
||||
// beforeEach(function() {
|
||||
// fixture.load("robustAttachment.html");
|
||||
// });
|
||||
|
||||
afterEach(function(done) {
|
||||
fixture.cleanup();
|
||||
window.setTimeout(function(){
|
||||
done();
|
||||
}, testconfig.timeouts.eventDelay);
|
||||
});
|
||||
// afterEach(function(done) {
|
||||
// fixture.cleanup();
|
||||
// window.setTimeout(function(){
|
||||
// done();
|
||||
// }, testconfig.timeouts.eventDelay);
|
||||
// });
|
||||
|
||||
it("Attachment/Detachment", function(done) {
|
||||
// Since we're in 'manual', we start detached.
|
||||
var ele = document.getElementById(DynamicElements.addInput());
|
||||
// it("Attachment/Detachment", function(done) {
|
||||
// // Since we're in 'manual', we start detached.
|
||||
// var ele = document.getElementById(DynamicElements.addInput());
|
||||
|
||||
window.setTimeout(function() {
|
||||
// Ensure we didn't auto-attach.
|
||||
DynamicElements.assertDetached(ele);
|
||||
let eventDriver = new KMWRecorder.BrowserDriver(ele);
|
||||
eventDriver.simulateEvent(DynamicElements.keyCommand);
|
||||
// window.setTimeout(function() {
|
||||
// // Ensure we didn't auto-attach.
|
||||
// DynamicElements.assertDetached(ele);
|
||||
// let eventDriver = new KMWRecorder.BrowserDriver(ele);
|
||||
// eventDriver.simulateEvent(DynamicElements.keyCommand);
|
||||
|
||||
var val = ele.value;
|
||||
ele.value = "";
|
||||
assert.equal(val, DynamicElements.disabledOutput, "'Detached' element performed keystroke processing!");
|
||||
// var val = ele.value;
|
||||
// ele.value = "";
|
||||
// assert.equal(val, DynamicElements.disabledOutput, "'Detached' element performed keystroke processing!");
|
||||
|
||||
keyman.attachToControl(ele);
|
||||
DynamicElements.assertAttached(ele); // Happens in-line, since we directly request the attachment.
|
||||
// keyman.attachToControl(ele);
|
||||
// DynamicElements.assertAttached(ele); // Happens in-line, since we directly request the attachment.
|
||||
|
||||
eventDriver = new KMWRecorder.BrowserDriver(ele);
|
||||
eventDriver.simulateEvent(DynamicElements.keyCommand);
|
||||
// eventDriver = new KMWRecorder.BrowserDriver(ele);
|
||||
// eventDriver.simulateEvent(DynamicElements.keyCommand);
|
||||
|
||||
val = retrieveAndReset(ele);
|
||||
// val = retrieveAndReset(ele);
|
||||
|
||||
assert.equal(val, DynamicElements.enabledLaoOutput, "'Attached' element did not perform keystroke processing!");
|
||||
// assert.equal(val, DynamicElements.enabledLaoOutput, "'Attached' element did not perform keystroke processing!");
|
||||
|
||||
done();
|
||||
}, testconfig.timeouts.eventDelay);
|
||||
});
|
||||
// done();
|
||||
// }, testconfig.timeouts.eventDelay);
|
||||
// });
|
||||
|
||||
it("Enablement/Disablement", function(done) {
|
||||
// Since we're in 'manual', we start detached.
|
||||
var ele = document.getElementById(DynamicElements.addInput());
|
||||
window.setTimeout(function() {
|
||||
keyman.attachToControl(ele);
|
||||
keyman.disableControl(ele);
|
||||
// it("Enablement/Disablement", function(done) {
|
||||
// // Since we're in 'manual', we start detached.
|
||||
// var ele = document.getElementById(DynamicElements.addInput());
|
||||
// window.setTimeout(function() {
|
||||
// keyman.attachToControl(ele);
|
||||
// keyman.disableControl(ele);
|
||||
|
||||
// It appears that mobile devices do not instantly trigger the MutationObserver, so we need a small timeout
|
||||
// for the change to take effect.
|
||||
window.setTimeout(function() {
|
||||
DynamicElements.assertAttached(ele);
|
||||
let eventDriver = new KMWRecorder.BrowserDriver(ele);
|
||||
eventDriver.simulateEvent(DynamicElements.keyCommand);
|
||||
val = retrieveAndReset(ele);
|
||||
assert.equal(val, DynamicElements.disabledOutput, "'Disabled' element performed keystroke processing!");
|
||||
// // It appears that mobile devices do not instantly trigger the MutationObserver, so we need a small timeout
|
||||
// // for the change to take effect.
|
||||
// window.setTimeout(function() {
|
||||
// DynamicElements.assertAttached(ele);
|
||||
// let eventDriver = new KMWRecorder.BrowserDriver(ele);
|
||||
// eventDriver.simulateEvent(DynamicElements.keyCommand);
|
||||
// val = retrieveAndReset(ele);
|
||||
// assert.equal(val, DynamicElements.disabledOutput, "'Disabled' element performed keystroke processing!");
|
||||
|
||||
keyman.enableControl(ele);
|
||||
window.setTimeout(function() {
|
||||
DynamicElements.assertAttached(ele); // Happens in-line, since we directly request the attachment.
|
||||
let eventDriver = new KMWRecorder.BrowserDriver(ele);
|
||||
eventDriver.simulateEvent(DynamicElements.keyCommand);
|
||||
val = retrieveAndReset(ele);
|
||||
assert.equal(val, DynamicElements.enabledLaoOutput, "'Enabled' element did not perform keystroke processing!");
|
||||
done();
|
||||
}, testconfig.timeouts.eventDelay);
|
||||
}, testconfig.timeouts.eventDelay);
|
||||
}, testconfig.timeouts.eventDelay);
|
||||
});
|
||||
// keyman.enableControl(ele);
|
||||
// window.setTimeout(function() {
|
||||
// DynamicElements.assertAttached(ele); // Happens in-line, since we directly request the attachment.
|
||||
// let eventDriver = new KMWRecorder.BrowserDriver(ele);
|
||||
// eventDriver.simulateEvent(DynamicElements.keyCommand);
|
||||
// val = retrieveAndReset(ele);
|
||||
// assert.equal(val, DynamicElements.enabledLaoOutput, "'Enabled' element did not perform keystroke processing!");
|
||||
// done();
|
||||
// }, testconfig.timeouts.eventDelay);
|
||||
// }, testconfig.timeouts.eventDelay);
|
||||
// }, testconfig.timeouts.eventDelay);
|
||||
// });
|
||||
|
||||
it("Keyboard Management (active control)", function() {
|
||||
// It appears that event generation + inline event dispatching is a bit time-intensive on some browsers.
|
||||
this.timeout(testconfig.timeouts.standard * 2);
|
||||
// it("Keyboard Management (active control)", function() {
|
||||
// // It appears that event generation + inline event dispatching is a bit time-intensive on some browsers.
|
||||
// this.timeout(testconfig.timeouts.standard * 2);
|
||||
|
||||
var input = document.getElementById(DynamicElements.addInput());
|
||||
var textarea = document.getElementById(DynamicElements.addText());
|
||||
// var input = document.getElementById(DynamicElements.addInput());
|
||||
// var textarea = document.getElementById(DynamicElements.addText());
|
||||
|
||||
keyman.attachToControl(input);
|
||||
keyman.attachToControl(textarea);
|
||||
// keyman.attachToControl(input);
|
||||
// keyman.attachToControl(textarea);
|
||||
|
||||
keyman.setActiveElement(input);
|
||||
// We assume from the other tests that running on the Lao keyboard will give proper output.
|
||||
// It'd be a redundant check.
|
||||
// keyman.setActiveElement(input);
|
||||
// // We assume from the other tests that running on the Lao keyboard will give proper output.
|
||||
// // It'd be a redundant check.
|
||||
|
||||
// Set control with independent keyboard.
|
||||
keyman.setKeyboardForControl(input, "khmer_angkor", "km");
|
||||
var eventDriver = new KMWRecorder.BrowserDriver(input);
|
||||
eventDriver.simulateEvent(DynamicElements.keyCommand);
|
||||
val = retrieveAndReset(input);
|
||||
assert.equal(val, DynamicElements.enabledKhmerOutput, "KMW did not use control's keyboard settings!");
|
||||
// // Set control with independent keyboard.
|
||||
// keyman.setKeyboardForControl(input, "khmer_angkor", "km");
|
||||
// var eventDriver = new KMWRecorder.BrowserDriver(input);
|
||||
// eventDriver.simulateEvent(DynamicElements.keyCommand);
|
||||
// val = retrieveAndReset(input);
|
||||
// assert.equal(val, DynamicElements.enabledKhmerOutput, "KMW did not use control's keyboard settings!");
|
||||
|
||||
// Swap to a global-linked control...
|
||||
keyman.setActiveElement(textarea);
|
||||
eventDriver = new KMWRecorder.BrowserDriver(textarea);
|
||||
eventDriver.simulateEvent(DynamicElements.keyCommand);
|
||||
val = retrieveAndReset(textarea);
|
||||
assert.equal(val, DynamicElements.enabledLaoOutput, "KMW did not use manage keyboard settings correctly for global-linked control!");
|
||||
// // Swap to a global-linked control...
|
||||
// keyman.setActiveElement(textarea);
|
||||
// eventDriver = new KMWRecorder.BrowserDriver(textarea);
|
||||
// eventDriver.simulateEvent(DynamicElements.keyCommand);
|
||||
// val = retrieveAndReset(textarea);
|
||||
// assert.equal(val, DynamicElements.enabledLaoOutput, "KMW did not use manage keyboard settings correctly for global-linked control!");
|
||||
|
||||
// Swap back and check that the settings persist.
|
||||
keyman.setActiveElement(input);
|
||||
eventDriver = new KMWRecorder.BrowserDriver(input);
|
||||
eventDriver.simulateEvent(DynamicElements.keyCommand);
|
||||
val = retrieveAndReset(input);
|
||||
assert.equal(val, DynamicElements.enabledKhmerOutput, "KMW forgot control's independent keyboard settings!");
|
||||
// // Swap back and check that the settings persist.
|
||||
// keyman.setActiveElement(input);
|
||||
// eventDriver = new KMWRecorder.BrowserDriver(input);
|
||||
// eventDriver.simulateEvent(DynamicElements.keyCommand);
|
||||
// val = retrieveAndReset(input);
|
||||
// assert.equal(val, DynamicElements.enabledKhmerOutput, "KMW forgot control's independent keyboard settings!");
|
||||
|
||||
// Finally, clear the independent setting.
|
||||
keyman.setKeyboardForControl(input, null, null);
|
||||
eventDriver.simulateEvent(DynamicElements.keyCommand);
|
||||
val = retrieveAndReset(input);
|
||||
assert.equal(val, DynamicElements.enabledLaoOutput, "KMW did not properly clear control's independent keyboard settings!");
|
||||
});
|
||||
// // Finally, clear the independent setting.
|
||||
// keyman.setKeyboardForControl(input, null, null);
|
||||
// eventDriver.simulateEvent(DynamicElements.keyCommand);
|
||||
// val = retrieveAndReset(input);
|
||||
// assert.equal(val, DynamicElements.enabledLaoOutput, "KMW did not properly clear control's independent keyboard settings!");
|
||||
// });
|
||||
|
||||
it("Keyboard Management (inactive control)", function() {
|
||||
// It appears that event generation + inline event dispatching is a bit time-intensive on some browsers.
|
||||
this.timeout(testconfig.timeouts.standard * 2);
|
||||
// it("Keyboard Management (inactive control)", function() {
|
||||
// // It appears that event generation + inline event dispatching is a bit time-intensive on some browsers.
|
||||
// this.timeout(testconfig.timeouts.standard * 2);
|
||||
|
||||
var input = document.getElementById(DynamicElements.addInput());
|
||||
var textarea = document.getElementById(DynamicElements.addText());
|
||||
// var input = document.getElementById(DynamicElements.addInput());
|
||||
// var textarea = document.getElementById(DynamicElements.addText());
|
||||
|
||||
keyman.attachToControl(input);
|
||||
keyman.attachToControl(textarea);
|
||||
// keyman.attachToControl(input);
|
||||
// keyman.attachToControl(textarea);
|
||||
|
||||
// We assume from the other tests that running on the Lao keyboard will give proper output.
|
||||
// It'd be a redundant check.
|
||||
// // We assume from the other tests that running on the Lao keyboard will give proper output.
|
||||
// // It'd be a redundant check.
|
||||
|
||||
// We are testing that setting a specific keyboard for an inactive control does not affect
|
||||
// the currently active control. The textarea control will be manually set to Khmer,
|
||||
// and the input control will get the document default of Lao.
|
||||
// // We are testing that setting a specific keyboard for an inactive control does not affect
|
||||
// // the currently active control. The textarea control will be manually set to Khmer,
|
||||
// // and the input control will get the document default of Lao.
|
||||
|
||||
keyman.setActiveElement(input);
|
||||
// Set textarea control with independent keyboard khmer_angkor.
|
||||
keyman.setKeyboardForControl(textarea, "khmer_angkor", "km");
|
||||
var eventDriver = new KMWRecorder.BrowserDriver(input);
|
||||
eventDriver.simulateEvent(DynamicElements.keyCommand);
|
||||
val = retrieveAndReset(input);
|
||||
assert.equal(val, DynamicElements.enabledLaoOutput, "KMW set independent keyboard for the incorrect control!");
|
||||
// keyman.setActiveElement(input);
|
||||
// // Set textarea control with independent keyboard khmer_angkor.
|
||||
// keyman.setKeyboardForControl(textarea, "khmer_angkor", "km");
|
||||
// var eventDriver = new KMWRecorder.BrowserDriver(input);
|
||||
// eventDriver.simulateEvent(DynamicElements.keyCommand);
|
||||
// val = retrieveAndReset(input);
|
||||
// assert.equal(val, DynamicElements.enabledLaoOutput, "KMW set independent keyboard for the incorrect control!");
|
||||
|
||||
// Swap to the textarea control with its overridden khmer_angkor keyboard...
|
||||
keyman.setActiveElement(textarea);
|
||||
eventDriver = new KMWRecorder.BrowserDriver(textarea);
|
||||
eventDriver.simulateEvent(DynamicElements.keyCommand);
|
||||
val = retrieveAndReset(textarea);
|
||||
assert.equal(val, DynamicElements.enabledKhmerOutput, "KMW did not properly store keyboard for the previously-inactive control!");
|
||||
// // Swap to the textarea control with its overridden khmer_angkor keyboard...
|
||||
// keyman.setActiveElement(textarea);
|
||||
// eventDriver = new KMWRecorder.BrowserDriver(textarea);
|
||||
// eventDriver.simulateEvent(DynamicElements.keyCommand);
|
||||
// val = retrieveAndReset(textarea);
|
||||
// assert.equal(val, DynamicElements.enabledKhmerOutput, "KMW did not properly store keyboard for the previously-inactive control!");
|
||||
|
||||
// Swap back to the input control and check that the settings persist.
|
||||
keyman.setActiveElement(input);
|
||||
keyman.setKeyboardForControl(textarea, null, null);
|
||||
// // Swap back to the input control and check that the settings persist.
|
||||
// keyman.setActiveElement(input);
|
||||
// keyman.setKeyboardForControl(textarea, null, null);
|
||||
|
||||
eventDriver = new KMWRecorder.BrowserDriver(input);
|
||||
eventDriver.simulateEvent(DynamicElements.keyCommand);
|
||||
val = retrieveAndReset(input);
|
||||
assert.equal(val, DynamicElements.enabledLaoOutput, "KMW made a strange error when clearing an inactive control's keyboard setting!");
|
||||
// eventDriver = new KMWRecorder.BrowserDriver(input);
|
||||
// eventDriver.simulateEvent(DynamicElements.keyCommand);
|
||||
// val = retrieveAndReset(input);
|
||||
// assert.equal(val, DynamicElements.enabledLaoOutput, "KMW made a strange error when clearing an inactive control's keyboard setting!");
|
||||
|
||||
keyman.setActiveElement(textarea);
|
||||
// Finally, after clearing the independent setting, check that we are back to Lao output as expected for the textarea
|
||||
eventDriver = new KMWRecorder.BrowserDriver(textarea);
|
||||
eventDriver.simulateEvent(DynamicElements.keyCommand);
|
||||
val = retrieveAndReset(textarea);
|
||||
assert.equal(val, DynamicElements.enabledLaoOutput, "KMW did not properly clear control's independent keyboard settings!");
|
||||
});
|
||||
});
|
||||
// keyman.setActiveElement(textarea);
|
||||
// // Finally, after clearing the independent setting, check that we are back to Lao output as expected for the textarea
|
||||
// eventDriver = new KMWRecorder.BrowserDriver(textarea);
|
||||
// eventDriver.simulateEvent(DynamicElements.keyCommand);
|
||||
// val = retrieveAndReset(textarea);
|
||||
// assert.equal(val, DynamicElements.enabledLaoOutput, "KMW did not properly clear control's independent keyboard settings!");
|
||||
// });
|
||||
// });
|
||||
|
||||
|
||||
describe('Attachment Checks (Desktop, \'auto\')', function() {
|
||||
// describe('Attachment Checks (Desktop, \'auto\')', function() {
|
||||
|
||||
this.timeout(testconfig.timeouts.standard);
|
||||
// this.timeout(testconfig.timeouts.standard);
|
||||
|
||||
before(function() {
|
||||
this.timeout(testconfig.timeouts.scriptLoad);
|
||||
// before(function() {
|
||||
// this.timeout(testconfig.timeouts.scriptLoad);
|
||||
|
||||
fixture.setBase('fixtures');
|
||||
return setupKMW({ attachType:'auto' }, testconfig.timeouts.scriptLoad);
|
||||
});
|
||||
// fixture.setBase('fixtures');
|
||||
// return setupKMW({ attachType:'auto' }, testconfig.timeouts.scriptLoad);
|
||||
// });
|
||||
|
||||
beforeEach(function() {
|
||||
fixture.load("robustAttachment.html");
|
||||
});
|
||||
// beforeEach(function() {
|
||||
// fixture.load("robustAttachment.html");
|
||||
// });
|
||||
|
||||
after(function() {
|
||||
teardownKMW();
|
||||
});
|
||||
// after(function() {
|
||||
// teardownKMW();
|
||||
// });
|
||||
|
||||
afterEach(function(done) {
|
||||
fixture.cleanup();
|
||||
window.setTimeout(function(){
|
||||
done();
|
||||
}, testconfig.timeouts.eventDelay);
|
||||
})
|
||||
// afterEach(function(done) {
|
||||
// fixture.cleanup();
|
||||
// window.setTimeout(function(){
|
||||
// done();
|
||||
// }, testconfig.timeouts.eventDelay);
|
||||
// })
|
||||
|
||||
describe('Element Type', function() {
|
||||
it('<input>', function(done) {
|
||||
var ID = DynamicElements.addInput();
|
||||
var ele = document.getElementById(ID);
|
||||
// describe('Element Type', function() {
|
||||
// it('<input>', function(done) {
|
||||
// var ID = DynamicElements.addInput();
|
||||
// var ele = document.getElementById(ID);
|
||||
|
||||
DynamicElements.assertAttached(ele, done);
|
||||
});
|
||||
// DynamicElements.assertAttached(ele, done);
|
||||
// });
|
||||
|
||||
it('<textarea>', function(done) {
|
||||
var ID = DynamicElements.addText();
|
||||
var ele = document.getElementById(ID);
|
||||
// it('<textarea>', function(done) {
|
||||
// var ID = DynamicElements.addText();
|
||||
// var ele = document.getElementById(ID);
|
||||
|
||||
DynamicElements.assertAttached(ele, done);
|
||||
});
|
||||
// DynamicElements.assertAttached(ele, done);
|
||||
// });
|
||||
|
||||
it.skip('<iframe>', function(done) {
|
||||
this.timeout(testconfig.timeouts.scriptLoad * 2); // Just in case, for iframe loading time.
|
||||
// it.skip('<iframe>', function(done) {
|
||||
// this.timeout(testconfig.timeouts.scriptLoad * 2); // Just in case, for iframe loading time.
|
||||
|
||||
var ID = DynamicElements.addIFrame(function() {
|
||||
var ele = document.getElementById(ID);
|
||||
var innerEle = ele.contentDocument.getElementById('iframe_input');
|
||||
// var ID = DynamicElements.addIFrame(function() {
|
||||
// var ele = document.getElementById(ID);
|
||||
// var innerEle = ele.contentDocument.getElementById('iframe_input');
|
||||
|
||||
// No need to track data on the iframe itself.
|
||||
assert.isFalse(keyman.isAttached(ele));
|
||||
assert.isNotNull(innerEle);
|
||||
assert.isTrue(keyman.isAttached(innerEle));
|
||||
keyman.detachFromControl(ele);
|
||||
// // No need to track data on the iframe itself.
|
||||
// assert.isFalse(keyman.isAttached(ele));
|
||||
// assert.isNotNull(innerEle);
|
||||
// assert.isTrue(keyman.isAttached(innerEle));
|
||||
// keyman.detachFromControl(ele);
|
||||
|
||||
window.setTimeout(function() {
|
||||
done();
|
||||
}, testconfig.timeouts.eventDelay);
|
||||
});
|
||||
});
|
||||
// window.setTimeout(function() {
|
||||
// done();
|
||||
// }, testconfig.timeouts.eventDelay);
|
||||
// });
|
||||
// });
|
||||
|
||||
// If we were to set up a design-mode iframe test, that one would need to be conditioned on
|
||||
// whether or not we were on a touch-device, at least as of this time. (#7343)
|
||||
// // If we were to set up a design-mode iframe test, that one would need to be conditioned on
|
||||
// // whether or not we were on a touch-device, at least as of this time. (#7343)
|
||||
|
||||
it('contentEditable=true', function(done) {
|
||||
var ID = DynamicElements.addEditable();
|
||||
var ele = document.getElementById(ID);
|
||||
// it('contentEditable=true', function(done) {
|
||||
// var ID = DynamicElements.addEditable();
|
||||
// var ele = document.getElementById(ID);
|
||||
|
||||
DynamicElements.assertAttached(ele, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
// DynamicElements.assertAttached(ele, done);
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
|
@ -1,154 +1,154 @@
|
|||
var assert = chai.assert;
|
||||
// var assert = chai.assert;
|
||||
|
||||
describe('Basic KeymanWeb', function() {
|
||||
this.timeout(testconfig.timeouts.standard);
|
||||
// describe('Basic KeymanWeb', function() {
|
||||
// this.timeout(testconfig.timeouts.standard);
|
||||
|
||||
before(function() {
|
||||
// These tests require use of KMW's device-detection functionality.
|
||||
assert.isFalse(com.keyman.karma.DEVICE_DETECT_FAILURE, "Cannot run due to device detection failure.");
|
||||
})
|
||||
// before(function() {
|
||||
// // These tests require use of KMW's device-detection functionality.
|
||||
// assert.isFalse(com.keyman.karma.DEVICE_DETECT_FAILURE, "Cannot run due to device detection failure.");
|
||||
// })
|
||||
|
||||
beforeEach(function() {
|
||||
this.timeout(testconfig.timeouts.scriptLoad);
|
||||
// beforeEach(function() {
|
||||
// this.timeout(testconfig.timeouts.scriptLoad);
|
||||
|
||||
fixture.setBase('fixtures');
|
||||
fixture.load("singleInput.html");
|
||||
return setupKMW(null, testconfig.timeouts.scriptLoad);
|
||||
});
|
||||
// fixture.setBase('fixtures');
|
||||
// fixture.load("singleInput.html");
|
||||
// return setupKMW(null, testconfig.timeouts.scriptLoad);
|
||||
// });
|
||||
|
||||
afterEach(function() {
|
||||
fixture.cleanup();
|
||||
teardownKMW();
|
||||
});
|
||||
// afterEach(function() {
|
||||
// fixture.cleanup();
|
||||
// teardownKMW();
|
||||
// });
|
||||
|
||||
describe('Initialization', function() {
|
||||
it('KMW should attach to the input element.', function() {
|
||||
var singleton = document.getElementById('singleton');
|
||||
assert.isTrue(keyman.isAttached(singleton), "KeymanWeb did not automatically attach to the element!");
|
||||
});
|
||||
it('KMW\'s initialization variable should indicate completion.', function() {
|
||||
assert(keyman.initialized == 2, 'Keyman indicates incomplete initialization!');
|
||||
});
|
||||
});
|
||||
});
|
||||
// describe('Initialization', function() {
|
||||
// it('KMW should attach to the input element.', function() {
|
||||
// var singleton = document.getElementById('singleton');
|
||||
// assert.isTrue(keyman.isAttached(singleton), "KeymanWeb did not automatically attach to the element!");
|
||||
// });
|
||||
// it('KMW\'s initialization variable should indicate completion.', function() {
|
||||
// assert(keyman.initialized == 2, 'Keyman indicates incomplete initialization!');
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
||||
Modernizr.on('touchevents', function(result) {
|
||||
if(!result) {
|
||||
describe('Basic Toggle UI', function() {
|
||||
this.timeout(testconfig.timeouts.scriptLoad);
|
||||
// Modernizr.on('touchevents', function(result) {
|
||||
// if(!result) {
|
||||
// describe('Basic Toggle UI', function() {
|
||||
// this.timeout(testconfig.timeouts.scriptLoad);
|
||||
|
||||
beforeEach(function() {
|
||||
this.timeout(testconfig.timeouts.uiLoad);
|
||||
fixture.setBase('fixtures');
|
||||
fixture.load('singleInput.html');
|
||||
// beforeEach(function() {
|
||||
// this.timeout(testconfig.timeouts.uiLoad);
|
||||
// fixture.setBase('fixtures');
|
||||
// fixture.load('singleInput.html');
|
||||
|
||||
// Loads two scripts in parallel, but just in case, 2x timeout.
|
||||
return setupKMW('toggle', testconfig.timeouts.uiLoad);
|
||||
});
|
||||
// // Loads two scripts in parallel, but just in case, 2x timeout.
|
||||
// return setupKMW('toggle', testconfig.timeouts.uiLoad);
|
||||
// });
|
||||
|
||||
afterEach(function() {
|
||||
fixture.cleanup();
|
||||
teardownKMW();
|
||||
});
|
||||
// afterEach(function() {
|
||||
// fixture.cleanup();
|
||||
// teardownKMW();
|
||||
// });
|
||||
|
||||
it('The Toggle UI initializes correctly.', function() {
|
||||
assert(keyman.ui.initialized, 'Initialization flag is set to false!');
|
||||
// it('The Toggle UI initializes correctly.', function() {
|
||||
// assert(keyman.ui.initialized, 'Initialization flag is set to false!');
|
||||
|
||||
assert.isNotNull(keyman.ui.controller, 'Failed to create the controller element!');
|
||||
// assert.isNotNull(keyman.ui.controller, 'Failed to create the controller element!');
|
||||
|
||||
var divs = document.getElementsByTagName("div");
|
||||
var match = false;
|
||||
// var divs = document.getElementsByTagName("div");
|
||||
// var match = false;
|
||||
|
||||
for(var i=0; i < divs.length; i++) {
|
||||
if(divs[i] == keyman.ui.controller) {
|
||||
match = true;
|
||||
}
|
||||
}
|
||||
// for(var i=0; i < divs.length; i++) {
|
||||
// if(divs[i] == keyman.ui.controller) {
|
||||
// match = true;
|
||||
// }
|
||||
// }
|
||||
|
||||
assert(match, 'Controller element has not been added to the page!');
|
||||
})
|
||||
});
|
||||
// assert(match, 'Controller element has not been added to the page!');
|
||||
// })
|
||||
// });
|
||||
|
||||
describe('Basic Button UI', function() {
|
||||
// describe('Basic Button UI', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
this.timeout(testconfig.timeouts.uiLoad);
|
||||
fixture.setBase('fixtures');
|
||||
fixture.load('singleInput.html');
|
||||
// beforeEach(function() {
|
||||
// this.timeout(testconfig.timeouts.uiLoad);
|
||||
// fixture.setBase('fixtures');
|
||||
// fixture.load('singleInput.html');
|
||||
|
||||
// Loads two scripts in parallel, but just in case, 2x timeout.
|
||||
return setupKMW('button', testconfig.timeouts.uiLoad);
|
||||
});
|
||||
// // Loads two scripts in parallel, but just in case, 2x timeout.
|
||||
// return setupKMW('button', testconfig.timeouts.uiLoad);
|
||||
// });
|
||||
|
||||
afterEach(function() {
|
||||
fixture.cleanup();
|
||||
teardownKMW();
|
||||
});
|
||||
// afterEach(function() {
|
||||
// fixture.cleanup();
|
||||
// teardownKMW();
|
||||
// });
|
||||
|
||||
it('The Button UI initializes correctly.', function() {
|
||||
assert(keyman.ui.init, 'Initialization flag is set to false!');
|
||||
})
|
||||
});
|
||||
// it('The Button UI initializes correctly.', function() {
|
||||
// assert(keyman.ui.init, 'Initialization flag is set to false!');
|
||||
// })
|
||||
// });
|
||||
|
||||
describe('Basic Float UI', function() {
|
||||
// describe('Basic Float UI', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
this.timeout(testconfig.timeouts.uiLoad);
|
||||
fixture.setBase('fixtures');
|
||||
fixture.load('singleInput.html');
|
||||
// beforeEach(function() {
|
||||
// this.timeout(testconfig.timeouts.uiLoad);
|
||||
// fixture.setBase('fixtures');
|
||||
// fixture.load('singleInput.html');
|
||||
|
||||
// Loads two scripts in parallel, but just in case, 2x timeout.
|
||||
return setupKMW('float', testconfig.timeouts.uiLoad);
|
||||
});
|
||||
// // Loads two scripts in parallel, but just in case, 2x timeout.
|
||||
// return setupKMW('float', testconfig.timeouts.uiLoad);
|
||||
// });
|
||||
|
||||
afterEach(function() {
|
||||
fixture.cleanup();
|
||||
teardownKMW();
|
||||
});
|
||||
// afterEach(function() {
|
||||
// fixture.cleanup();
|
||||
// teardownKMW();
|
||||
// });
|
||||
|
||||
it('The Float UI initializes correctly.', function() {
|
||||
assert(keyman.ui.initialized, 'Initialization flag is set to false!');
|
||||
// it('The Float UI initializes correctly.', function() {
|
||||
// assert(keyman.ui.initialized, 'Initialization flag is set to false!');
|
||||
|
||||
assert.isNotNull(keyman.ui.outerDiv, 'Failed to create the floating controller element!');
|
||||
// assert.isNotNull(keyman.ui.outerDiv, 'Failed to create the floating controller element!');
|
||||
|
||||
var divs = document.getElementsByTagName("div");
|
||||
var match = false;
|
||||
// var divs = document.getElementsByTagName("div");
|
||||
// var match = false;
|
||||
|
||||
for(var i=0; i < divs.length; i++) {
|
||||
if(divs[i] == keyman.ui.outerDiv) {
|
||||
match = true;
|
||||
}
|
||||
}
|
||||
// for(var i=0; i < divs.length; i++) {
|
||||
// if(divs[i] == keyman.ui.outerDiv) {
|
||||
// match = true;
|
||||
// }
|
||||
// }
|
||||
|
||||
assert(match, 'Floating controller element has not been added to the page!');
|
||||
})
|
||||
});
|
||||
// assert(match, 'Floating controller element has not been added to the page!');
|
||||
// })
|
||||
// });
|
||||
|
||||
describe('Basic Toolbar UI', function() {
|
||||
// describe('Basic Toolbar UI', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
this.timeout(testconfig.timeouts.uiLoad);
|
||||
fixture.setBase('fixtures');
|
||||
fixture.load('singleInput.html');
|
||||
// beforeEach(function() {
|
||||
// this.timeout(testconfig.timeouts.uiLoad);
|
||||
// fixture.setBase('fixtures');
|
||||
// fixture.load('singleInput.html');
|
||||
|
||||
// Loads two scripts in parallel, but just in case, 2x timeout.
|
||||
return setupKMW('toolbar', testconfig.timeouts.uiLoad);
|
||||
});
|
||||
// // Loads two scripts in parallel, but just in case, 2x timeout.
|
||||
// return setupKMW('toolbar', testconfig.timeouts.uiLoad);
|
||||
// });
|
||||
|
||||
afterEach(function() {
|
||||
fixture.cleanup();
|
||||
teardownKMW();
|
||||
});
|
||||
// afterEach(function() {
|
||||
// fixture.cleanup();
|
||||
// teardownKMW();
|
||||
// });
|
||||
|
||||
it('The Toolbar UI initializes correctly.', function() {
|
||||
assert(keyman.ui.init, 'Initialization flag is set to false!');
|
||||
// it('The Toolbar UI initializes correctly.', function() {
|
||||
// assert(keyman.ui.init, 'Initialization flag is set to false!');
|
||||
|
||||
var kwc = document.getElementById('KeymanWebControl');
|
||||
assert.isNotNull(kwc, 'Toolbar DIV was not added to the page!');
|
||||
// var kwc = document.getElementById('KeymanWebControl');
|
||||
// assert.isNotNull(kwc, 'Toolbar DIV was not added to the page!');
|
||||
|
||||
var toolbar = document.getElementById('kmw_controls');
|
||||
assert.isNotNull(toolbar, 'The main toolbar element was not added to the page!');
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
// var toolbar = document.getElementById('kmw_controls');
|
||||
// assert.isNotNull(toolbar, 'The main toolbar element was not added to the page!');
|
||||
// })
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
|
|
@ -1,5 +1,14 @@
|
|||
var assert = chai.assert;
|
||||
|
||||
import * as wrappers from '/@keymanapp/keyman/build/engine/element-wrappers/lib/index.mjs';
|
||||
import { Mock } from '/@keymanapp/keyboard-processor/build/obj/text/outputTarget.js';
|
||||
import extendString from '../../../../../common/web/utils/build/obj/kmwstring.js';
|
||||
import Device from '/@keymanapp/keyman/build/engine/device-detect/lib/index.mjs';
|
||||
|
||||
import { toSupplementaryPairString, DynamicElements, DEVICE_DETECT_FAILURE } from '../test_utils.js';
|
||||
|
||||
extendString();
|
||||
|
||||
var InterfaceTests;
|
||||
|
||||
// Define common interface testing functions that can be run upon the OutputTarget interface.
|
||||
|
|
@ -24,7 +33,7 @@ if(typeof InterfaceTests == 'undefined') {
|
|||
InterfaceTests.Input.setupElement = function() {
|
||||
var id = DynamicElements.addInput();
|
||||
var elem = document.getElementById(id);
|
||||
var wrapper = new com.keyman.dom.targets.Input(elem);
|
||||
var wrapper = new wrappers.Input(elem);
|
||||
|
||||
return {elem: elem, wrapper: wrapper};
|
||||
}
|
||||
|
|
@ -70,7 +79,7 @@ if(typeof InterfaceTests == 'undefined') {
|
|||
InterfaceTests.TextArea.setupElement = function() {
|
||||
var id = DynamicElements.addText();
|
||||
var elem = document.getElementById(id);
|
||||
var wrapper = new com.keyman.dom.targets.TextArea(elem);
|
||||
var wrapper = new wrappers.TextArea(elem);
|
||||
|
||||
return {elem: elem, wrapper: wrapper};
|
||||
}
|
||||
|
|
@ -120,7 +129,7 @@ if(typeof InterfaceTests == 'undefined') {
|
|||
InterfaceTests.ContentEditable.setupElement = function() {
|
||||
var id = DynamicElements.addEditable();
|
||||
var elem = document.getElementById(id);
|
||||
var wrapper = new com.keyman.dom.targets.ContentEditable(elem);
|
||||
var wrapper = new wrappers.ContentEditable(elem);
|
||||
|
||||
return {elem: elem, wrapper: wrapper, node: null};
|
||||
}
|
||||
|
|
@ -128,7 +137,7 @@ if(typeof InterfaceTests == 'undefined') {
|
|||
InterfaceTests.ContentEditable.setupDummyElement = function() {
|
||||
var id = DynamicElements.addEditable();
|
||||
var elem = document.getElementById(id);
|
||||
var wrapper = new com.keyman.dom.targets.ContentEditable(elem);
|
||||
var wrapper = new wrappers.ContentEditable(elem);
|
||||
|
||||
return {elem: elem, wrapper: wrapper, node: null};
|
||||
}
|
||||
|
|
@ -155,7 +164,7 @@ if(typeof InterfaceTests == 'undefined') {
|
|||
}
|
||||
|
||||
InterfaceTests.ContentEditable.setSelectionRange = function(pair, start, end) {
|
||||
var device = new com.keyman.Device();
|
||||
var device = new Device();
|
||||
device.detect();
|
||||
|
||||
var node = pair.elem.childNodes[0];
|
||||
|
|
@ -221,11 +230,11 @@ if(typeof InterfaceTests == 'undefined') {
|
|||
|
||||
var id1 = DynamicElements.addDesignIFrame(function() {
|
||||
var id2 = DynamicElements.addDesignIFrame(function() {
|
||||
elem1 = document.getElementById(id1);
|
||||
elem2 = document.getElementById(id2);
|
||||
let elem1 = document.getElementById(id1);
|
||||
let elem2 = document.getElementById(id2);
|
||||
|
||||
obj.mainPair = {elem: elem1, wrapper: new com.keyman.dom.targets.DesignIFrame(elem1), document: elem1.contentWindow.document};
|
||||
obj.dummyPair = {elem: elem2, wrapper: new com.keyman.dom.targets.DesignIFrame(elem2), document: elem1.contentWindow.document};
|
||||
obj.mainPair = {elem: elem1, wrapper: new wrappers.DesignIFrame(elem1), document: elem1.contentWindow.document};
|
||||
obj.dummyPair = {elem: elem2, wrapper: new wrappers.DesignIFrame(elem2), document: elem1.contentWindow.document};
|
||||
|
||||
done();
|
||||
});
|
||||
|
|
@ -262,7 +271,7 @@ if(typeof InterfaceTests == 'undefined') {
|
|||
}
|
||||
|
||||
InterfaceTests.DesignIFrame.setSelectionRange = function(pair, start, end) {
|
||||
var device = new com.keyman.Device();
|
||||
var device = new Device();
|
||||
device.detect();
|
||||
|
||||
var node = pair.document.documentElement.childNodes[0];
|
||||
|
|
@ -319,7 +328,7 @@ if(typeof InterfaceTests == 'undefined') {
|
|||
InterfaceTests.Mock = {};
|
||||
|
||||
InterfaceTests.Mock.setupElement = function() {
|
||||
return {wrapper: new com.keyman.text.Mock()};
|
||||
return {wrapper: new Mock()};
|
||||
}
|
||||
|
||||
InterfaceTests.Mock.resetWithText = function(pair, string) {
|
||||
|
|
@ -1136,7 +1145,7 @@ describe('Element Input/Output Interfacing', function() {
|
|||
describe('Wrapper: Content-Editable Elements (using DIVs)', function() {
|
||||
before(function() {
|
||||
// These tests require use of KMW's device-detection functionality.
|
||||
assert.isFalse(com.keyman.karma.DEVICE_DETECT_FAILURE, "Cannot run due to device detection failure.");
|
||||
assert.isFalse(DEVICE_DETECT_FAILURE, "Cannot run due to device detection failure.");
|
||||
})
|
||||
|
||||
describe('Caret Handling', function() {
|
||||
|
|
@ -1228,7 +1237,7 @@ describe('Element Input/Output Interfacing', function() {
|
|||
|
||||
before(function() {
|
||||
// These tests require use of KMW's device-detection functionality.
|
||||
assert.isFalse(com.keyman.karma.DEVICE_DETECT_FAILURE, "Cannot run due to device detection failure.");
|
||||
assert.isFalse(DEVICE_DETECT_FAILURE, "Cannot run due to device detection failure.");
|
||||
})
|
||||
|
||||
beforeEach(function(done) {
|
||||
|
|
|
|||
|
|
@ -1,292 +1,292 @@
|
|||
var assert = chai.assert;
|
||||
// var assert = chai.assert;
|
||||
|
||||
describe('Engine - Browser Interactions', function() {
|
||||
this.timeout(testconfig.timeouts.scriptLoad);
|
||||
// describe('Engine - Browser Interactions', function() {
|
||||
// this.timeout(testconfig.timeouts.scriptLoad);
|
||||
|
||||
before(function() {
|
||||
fixture.setBase('fixtures');
|
||||
return setupKMW(null, testconfig.timeouts.scriptLoad);
|
||||
});
|
||||
// before(function() {
|
||||
// fixture.setBase('fixtures');
|
||||
// return setupKMW(null, testconfig.timeouts.scriptLoad);
|
||||
// });
|
||||
|
||||
beforeEach(function(done) {
|
||||
fixture.load("singleInput.html");
|
||||
// beforeEach(function(done) {
|
||||
// fixture.load("singleInput.html");
|
||||
|
||||
window.setTimeout(function() {
|
||||
done()
|
||||
}, testconfig.timeouts.eventDelay);
|
||||
});
|
||||
// window.setTimeout(function() {
|
||||
// done()
|
||||
// }, testconfig.timeouts.eventDelay);
|
||||
// });
|
||||
|
||||
after(function() {
|
||||
teardownKMW();
|
||||
});
|
||||
// after(function() {
|
||||
// teardownKMW();
|
||||
// });
|
||||
|
||||
afterEach(function() {
|
||||
fixture.cleanup();
|
||||
});
|
||||
// afterEach(function() {
|
||||
// fixture.cleanup();
|
||||
// });
|
||||
|
||||
describe('RegisterStub', function() {
|
||||
it('RegisterStub on same keyboard twice', function() {
|
||||
this.timeout(testconfig.timeouts.scriptLoad);
|
||||
// describe('RegisterStub', function() {
|
||||
// it('RegisterStub on same keyboard twice', function() {
|
||||
// this.timeout(testconfig.timeouts.scriptLoad);
|
||||
|
||||
var test_callback = function() {
|
||||
assert.isNotNull(keyman.getKeyboard("lao_2008_basic", "lo"), "Keyboard stub was not registered!");
|
||||
assert.equal(keyman.getActiveKeyboard(), "Keyboard_lao_2008_basic", "Keyboard not set automatically!");
|
||||
keyman.removeKeyboards('lao_2008_basic');
|
||||
assert.equal(keyman.getActiveKeyboard(), '', "Keyboard not removed correctly!");
|
||||
}
|
||||
// var test_callback = function() {
|
||||
// assert.isNotNull(keyman.getKeyboard("lao_2008_basic", "lo"), "Keyboard stub was not registered!");
|
||||
// assert.equal(keyman.getActiveKeyboard(), "Keyboard_lao_2008_basic", "Keyboard not set automatically!");
|
||||
// keyman.removeKeyboards('lao_2008_basic');
|
||||
// assert.equal(keyman.getActiveKeyboard(), '', "Keyboard not removed correctly!");
|
||||
// }
|
||||
|
||||
let finalPromise = loadKeyboardFromJSON("/keyboards/lao_2008_basic.json", testconfig.timeouts.scriptLoad)
|
||||
.then(test_callback);
|
||||
// let finalPromise = loadKeyboardFromJSON("/keyboards/lao_2008_basic.json", testconfig.timeouts.scriptLoad)
|
||||
// .then(test_callback);
|
||||
|
||||
var stub = {
|
||||
'KI': 'Keyboard_lao_2008_basic',
|
||||
'KN': 'Lao 2008 Basic',
|
||||
'KLC': 'lo',
|
||||
'KL': 'Lao',
|
||||
'KF': 'resources/keyboards/lao_2008_basic.js'
|
||||
};
|
||||
assert.equal(com.keyman.text.KeyboardInterface.prototype.registerStub(stub), 1, "Registering existing keyboard should return 1!");
|
||||
return finalPromise;
|
||||
});
|
||||
// var stub = {
|
||||
// 'KI': 'Keyboard_lao_2008_basic',
|
||||
// 'KN': 'Lao 2008 Basic',
|
||||
// 'KLC': 'lo',
|
||||
// 'KL': 'Lao',
|
||||
// 'KF': 'resources/keyboards/lao_2008_basic.js'
|
||||
// };
|
||||
// assert.equal(com.keyman.text.KeyboardInterface.prototype.registerStub(stub), 1, "Registering existing keyboard should return 1!");
|
||||
// return finalPromise;
|
||||
// });
|
||||
|
||||
});
|
||||
// });
|
||||
|
||||
describe('Variable Stores', function() {
|
||||
this.timeout(testconfig.timeouts.scriptLoad + testconfig.timeouts.standard);
|
||||
// describe('Variable Stores', function() {
|
||||
// this.timeout(testconfig.timeouts.scriptLoad + testconfig.timeouts.standard);
|
||||
|
||||
beforeEach(function() {
|
||||
return loadKeyboardFromJSON("/keyboards/options_with_save.json", testconfig.timeouts.scriptLoad);
|
||||
});
|
||||
// beforeEach(function() {
|
||||
// return loadKeyboardFromJSON("/keyboards/options_with_save.json", testconfig.timeouts.scriptLoad);
|
||||
// });
|
||||
|
||||
after(function() {
|
||||
keyman.removeKeyboards('options_with_save');
|
||||
fixture.cleanup();
|
||||
});
|
||||
// after(function() {
|
||||
// keyman.removeKeyboards('options_with_save');
|
||||
// fixture.cleanup();
|
||||
// });
|
||||
|
||||
it('Backing up and restoring (loadStore/saveStore)', function() {
|
||||
// Keyboard's default value is 0, corresponding to "no foo."
|
||||
var keyboardID = "options_with_save";
|
||||
var prefixedKeyboardID = "Keyboard_" + keyboardID;
|
||||
var storeName = "foo";
|
||||
// it('Backing up and restoring (loadStore/saveStore)', function() {
|
||||
// // Keyboard's default value is 0, corresponding to "no foo."
|
||||
// var keyboardID = "options_with_save";
|
||||
// var prefixedKeyboardID = "Keyboard_" + keyboardID;
|
||||
// var storeName = "foo";
|
||||
|
||||
return keyman.setActiveKeyboard(keyboardID, 'en').then(function() {
|
||||
// Alas, saveStore itself requires the keyboard to be active!
|
||||
KeymanWeb.saveStore(storeName, 1);
|
||||
// return keyman.setActiveKeyboard(keyboardID, 'en').then(function() {
|
||||
// // Alas, saveStore itself requires the keyboard to be active!
|
||||
// KeymanWeb.saveStore(storeName, 1);
|
||||
|
||||
// First, ensure that we get the same thing if we load the value immediately.
|
||||
var value = KeymanWeb.loadStore(prefixedKeyboardID, storeName, 0);
|
||||
assert.equal(value, 1, "loadStore did not see the value saved to initialize the test before resetting keyboard");
|
||||
// // First, ensure that we get the same thing if we load the value immediately.
|
||||
// var value = KeymanWeb.loadStore(prefixedKeyboardID, storeName, 0);
|
||||
// assert.equal(value, 1, "loadStore did not see the value saved to initialize the test before resetting keyboard");
|
||||
|
||||
// Reload the keyboard so that we can test its loaded value.
|
||||
keyman.removeKeyboards(keyboardID, true);
|
||||
}).then(() => {
|
||||
return loadKeyboardFromJSON("/keyboards/options_with_save.json", testconfig.timeouts.scriptLoad);
|
||||
}).then(() => {
|
||||
return keyman.setActiveKeyboard(keyboardID, 'en');
|
||||
}).then(() => {
|
||||
// This requires proper storage to a cookie, as we'll be on a new instance of the same keyboard.
|
||||
var value = KeymanWeb.loadStore(prefixedKeyboardID, storeName, 0);
|
||||
assert.equal(value, 1, "Did not properly save and reload variable store setting");
|
||||
}).finally(() => {
|
||||
KeymanWeb.saveStore(storeName, 0);
|
||||
});
|
||||
});
|
||||
// // Reload the keyboard so that we can test its loaded value.
|
||||
// keyman.removeKeyboards(keyboardID, true);
|
||||
// }).then(() => {
|
||||
// return loadKeyboardFromJSON("/keyboards/options_with_save.json", testconfig.timeouts.scriptLoad);
|
||||
// }).then(() => {
|
||||
// return keyman.setActiveKeyboard(keyboardID, 'en');
|
||||
// }).then(() => {
|
||||
// // This requires proper storage to a cookie, as we'll be on a new instance of the same keyboard.
|
||||
// var value = KeymanWeb.loadStore(prefixedKeyboardID, storeName, 0);
|
||||
// assert.equal(value, 1, "Did not properly save and reload variable store setting");
|
||||
// }).finally(() => {
|
||||
// KeymanWeb.saveStore(storeName, 0);
|
||||
// });
|
||||
// });
|
||||
|
||||
it("Multiple-sequence check", function() {
|
||||
this.timeout(testconfig.timeouts.standard + testconfig.timeouts.scriptLoad * 3);
|
||||
var keyboardID = "options_with_save";
|
||||
var storeName = "foo";
|
||||
// it("Multiple-sequence check", function() {
|
||||
// this.timeout(testconfig.timeouts.standard + testconfig.timeouts.scriptLoad * 3);
|
||||
// var keyboardID = "options_with_save";
|
||||
// var storeName = "foo";
|
||||
|
||||
return keyman.setActiveKeyboard(keyboardID, 'en').then(function() {
|
||||
KeymanWeb.saveStore(storeName, 1);
|
||||
keyman.removeKeyboards(keyboardID, true);
|
||||
}).then(() => {
|
||||
// First test: expects option to be "on" from cookie-init setting, emitting "foo.", then turning option "off".
|
||||
return runKeyboardTestFromJSON('/engine_tests/options_with_save_1.json',
|
||||
{usingOSK: false},
|
||||
assert.equal,
|
||||
testconfig.timeouts.scriptLoad)
|
||||
}).then(() => {
|
||||
// Reset the keyboard... again.
|
||||
keyman.removeKeyboards(keyboardID, true);
|
||||
// return keyman.setActiveKeyboard(keyboardID, 'en').then(function() {
|
||||
// KeymanWeb.saveStore(storeName, 1);
|
||||
// keyman.removeKeyboards(keyboardID, true);
|
||||
// }).then(() => {
|
||||
// // First test: expects option to be "on" from cookie-init setting, emitting "foo.", then turning option "off".
|
||||
// return runKeyboardTestFromJSON('/engine_tests/options_with_save_1.json',
|
||||
// {usingOSK: false},
|
||||
// assert.equal,
|
||||
// testconfig.timeouts.scriptLoad)
|
||||
// }).then(() => {
|
||||
// // Reset the keyboard... again.
|
||||
// keyman.removeKeyboards(keyboardID, true);
|
||||
|
||||
// Second test: expects option to still be "off" b/c cookies.
|
||||
return runKeyboardTestFromJSON('/engine_tests/options_with_save_2.json',
|
||||
{usingOSK: false},
|
||||
assert.equal,
|
||||
testconfig.timeouts.scriptLoad);
|
||||
});
|
||||
});
|
||||
});
|
||||
// // Second test: expects option to still be "off" b/c cookies.
|
||||
// return runKeyboardTestFromJSON('/engine_tests/options_with_save_2.json',
|
||||
// {usingOSK: false},
|
||||
// assert.equal,
|
||||
// testconfig.timeouts.scriptLoad);
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
||||
// Performs basic processing system checks/tests to ensure the sequence testing
|
||||
// is based on correct assumptions about the code.
|
||||
describe('Integrated Simulation Checks', function() {
|
||||
this.timeout(testconfig.timeouts.standard);
|
||||
// // Performs basic processing system checks/tests to ensure the sequence testing
|
||||
// // is based on correct assumptions about the code.
|
||||
// describe('Integrated Simulation Checks', function() {
|
||||
// this.timeout(testconfig.timeouts.standard);
|
||||
|
||||
before(function() {
|
||||
this.timeout = testconfig.timeouts.scriptLoad;
|
||||
return loadKeyboardFromJSON("/keyboards/lao_2008_basic.json", testconfig.timeouts.scriptLoad);
|
||||
});
|
||||
// before(function() {
|
||||
// this.timeout = testconfig.timeouts.scriptLoad;
|
||||
// return loadKeyboardFromJSON("/keyboards/lao_2008_basic.json", testconfig.timeouts.scriptLoad);
|
||||
// });
|
||||
|
||||
beforeEach(function() {
|
||||
var inputElem = document.getElementById('singleton');
|
||||
keyman.setActiveElement(inputElem);
|
||||
inputElem.value = "";
|
||||
});
|
||||
// beforeEach(function() {
|
||||
// var inputElem = document.getElementById('singleton');
|
||||
// keyman.setActiveElement(inputElem);
|
||||
// inputElem.value = "";
|
||||
// });
|
||||
|
||||
after(function() {
|
||||
keyman.removeKeyboards('lao_2008_basic');
|
||||
fixture.cleanup();
|
||||
});
|
||||
// after(function() {
|
||||
// keyman.removeKeyboards('lao_2008_basic');
|
||||
// fixture.cleanup();
|
||||
// });
|
||||
|
||||
it('Simple Keypress', function() {
|
||||
var inputElem = document.getElementById('singleton');
|
||||
// it('Simple Keypress', function() {
|
||||
// var inputElem = document.getElementById('singleton');
|
||||
|
||||
var lao_s_key_json = {"type": "key", "key":"s", "code":"KeyS","keyCode":83,"modifierSet":0,"location":0};
|
||||
var lao_s_event = new KMWRecorder.PhysicalInputEventSpec(lao_s_key_json);
|
||||
// var lao_s_key_json = {"type": "key", "key":"s", "code":"KeyS","keyCode":83,"modifierSet":0,"location":0};
|
||||
// var lao_s_event = new KMWRecorder.PhysicalInputEventSpec(lao_s_key_json);
|
||||
|
||||
let eventDriver = new KMWRecorder.BrowserDriver(inputElem);
|
||||
eventDriver.simulateEvent(lao_s_event);
|
||||
// let eventDriver = new KMWRecorder.BrowserDriver(inputElem);
|
||||
// eventDriver.simulateEvent(lao_s_event);
|
||||
|
||||
if(inputElem['base']) {
|
||||
inputElem = inputElem['base'];
|
||||
}
|
||||
assert.equal(inputElem.value, "ຫ");
|
||||
});
|
||||
// if(inputElem['base']) {
|
||||
// inputElem = inputElem['base'];
|
||||
// }
|
||||
// assert.equal(inputElem.value, "ຫ");
|
||||
// });
|
||||
|
||||
it('Simple OSK click', function() {
|
||||
var inputElem = document.getElementById('singleton');
|
||||
// it('Simple OSK click', function() {
|
||||
// var inputElem = document.getElementById('singleton');
|
||||
|
||||
var lao_s_osk_json = {"type": "osk", "keyID": 'shift-K_S'};
|
||||
var lao_s_event = new KMWRecorder.OSKInputEventSpec(lao_s_osk_json);
|
||||
// var lao_s_osk_json = {"type": "osk", "keyID": 'shift-K_S'};
|
||||
// var lao_s_event = new KMWRecorder.OSKInputEventSpec(lao_s_osk_json);
|
||||
|
||||
let eventDriver = new KMWRecorder.BrowserDriver(inputElem);
|
||||
eventDriver.simulateEvent(lao_s_event);
|
||||
// let eventDriver = new KMWRecorder.BrowserDriver(inputElem);
|
||||
// eventDriver.simulateEvent(lao_s_event);
|
||||
|
||||
if(inputElem['base']) {
|
||||
inputElem = inputElem['base'];
|
||||
}
|
||||
assert.equal(inputElem.value, ";");
|
||||
});
|
||||
})
|
||||
// if(inputElem['base']) {
|
||||
// inputElem = inputElem['base'];
|
||||
// }
|
||||
// assert.equal(inputElem.value, ";");
|
||||
// });
|
||||
// })
|
||||
|
||||
describe('Sequence Simulation Checks', function() {
|
||||
this.timeout(testconfig.timeouts.scriptLoad);
|
||||
// describe('Sequence Simulation Checks', function() {
|
||||
// this.timeout(testconfig.timeouts.scriptLoad);
|
||||
|
||||
it('Keyboard simulation', function() {
|
||||
return runKeyboardTestFromJSON('/engine_tests/basic_lao_simulation.json', {usingOSK: false}, assert.equal, testconfig.timeouts.scriptLoad);
|
||||
});
|
||||
// it('Keyboard simulation', function() {
|
||||
// return runKeyboardTestFromJSON('/engine_tests/basic_lao_simulation.json', {usingOSK: false}, assert.equal, testconfig.timeouts.scriptLoad);
|
||||
// });
|
||||
|
||||
it('OSK simulation', function() {
|
||||
return runKeyboardTestFromJSON('/engine_tests/basic_lao_simulation.json', {usingOSK: true}, assert.equal, testconfig.timeouts.scriptLoad);
|
||||
})
|
||||
});
|
||||
});
|
||||
// it('OSK simulation', function() {
|
||||
// return runKeyboardTestFromJSON('/engine_tests/basic_lao_simulation.json', {usingOSK: true}, assert.equal, testconfig.timeouts.scriptLoad);
|
||||
// })
|
||||
// });
|
||||
// });
|
||||
|
||||
describe('Unmatched Final Groups', function() {
|
||||
this.timeout(testconfig.timeouts.scriptLoad);
|
||||
// describe('Unmatched Final Groups', function() {
|
||||
// this.timeout(testconfig.timeouts.scriptLoad);
|
||||
|
||||
before(function() {
|
||||
fixture.setBase('fixtures');
|
||||
return setupKMW(null, testconfig.timeouts.scriptLoad + testconfig.timeouts.eventDelay);
|
||||
});
|
||||
// before(function() {
|
||||
// fixture.setBase('fixtures');
|
||||
// return setupKMW(null, testconfig.timeouts.scriptLoad + testconfig.timeouts.eventDelay);
|
||||
// });
|
||||
|
||||
beforeEach(function(done) {
|
||||
fixture.load("singleTextArea.html");
|
||||
// beforeEach(function(done) {
|
||||
// fixture.load("singleTextArea.html");
|
||||
|
||||
window.setTimeout(function() {
|
||||
done()
|
||||
}, testconfig.timeouts.eventDelay);
|
||||
});
|
||||
// window.setTimeout(function() {
|
||||
// done()
|
||||
// }, testconfig.timeouts.eventDelay);
|
||||
// });
|
||||
|
||||
after(function() {
|
||||
teardownKMW();
|
||||
});
|
||||
// after(function() {
|
||||
// teardownKMW();
|
||||
// });
|
||||
|
||||
afterEach(function() {
|
||||
fixture.cleanup();
|
||||
});
|
||||
// afterEach(function() {
|
||||
// fixture.cleanup();
|
||||
// });
|
||||
|
||||
it('matches rule from early group AND performs default behavior', function() {
|
||||
// While a TAB-oriented version would be nice, it's much harder to write the test
|
||||
// to detect change in last input element.
|
||||
return runKeyboardTestFromJSON('/engine_tests/ghp_enter.json', {usingOSK: true}, assert.equal, testconfig.timeouts.scriptLoad);
|
||||
});
|
||||
});
|
||||
// it('matches rule from early group AND performs default behavior', function() {
|
||||
// // While a TAB-oriented version would be nice, it's much harder to write the test
|
||||
// // to detect change in last input element.
|
||||
// return runKeyboardTestFromJSON('/engine_tests/ghp_enter.json', {usingOSK: true}, assert.equal, testconfig.timeouts.scriptLoad);
|
||||
// });
|
||||
// });
|
||||
|
||||
// Kept separate to maintain an extra-clean setup for this test.
|
||||
describe('Engine - Browser Interactions', function() {
|
||||
this.timeout(testconfig.timeouts.scriptLoad);
|
||||
// // Kept separate to maintain an extra-clean setup for this test.
|
||||
// describe('Engine - Browser Interactions', function() {
|
||||
// this.timeout(testconfig.timeouts.scriptLoad);
|
||||
|
||||
before(function() {
|
||||
fixture.setBase('fixtures');
|
||||
});
|
||||
// before(function() {
|
||||
// fixture.setBase('fixtures');
|
||||
// });
|
||||
|
||||
beforeEach(function() {
|
||||
fixture.load("singleInput.html");
|
||||
return setupKMW(null, testconfig.timeouts.scriptLoad);
|
||||
});
|
||||
// beforeEach(function() {
|
||||
// fixture.load("singleInput.html");
|
||||
// return setupKMW(null, testconfig.timeouts.scriptLoad);
|
||||
// });
|
||||
|
||||
afterEach(function() {
|
||||
fixture.cleanup();
|
||||
teardownKMW();
|
||||
});
|
||||
// afterEach(function() {
|
||||
// fixture.cleanup();
|
||||
// teardownKMW();
|
||||
// });
|
||||
|
||||
describe('Keyboard Loading', function() {
|
||||
it('Local', function() {
|
||||
this.timeout(testconfig.timeouts.scriptLoad);
|
||||
// describe('Keyboard Loading', function() {
|
||||
// it('Local', function() {
|
||||
// this.timeout(testconfig.timeouts.scriptLoad);
|
||||
|
||||
return loadKeyboardFromJSON("/keyboards/lao_2008_basic.json",
|
||||
testconfig.timeouts.scriptLoad).then(function() {
|
||||
assert.isNotNull(keyman.getKeyboard("lao_2008_basic", "lo"), "Keyboard stub was not registered!");
|
||||
assert.equal(keyman.getActiveKeyboard(), "Keyboard_lao_2008_basic", "Keyboard not set automatically!");
|
||||
keyman.removeKeyboards('lao_2008_basic');
|
||||
assert.equal(keyman.getActiveKeyboard(), '', "Keyboard not removed correctly!");
|
||||
});
|
||||
});
|
||||
// return loadKeyboardFromJSON("/keyboards/lao_2008_basic.json",
|
||||
// testconfig.timeouts.scriptLoad).then(function() {
|
||||
// assert.isNotNull(keyman.getKeyboard("lao_2008_basic", "lo"), "Keyboard stub was not registered!");
|
||||
// assert.equal(keyman.getActiveKeyboard(), "Keyboard_lao_2008_basic", "Keyboard not set automatically!");
|
||||
// keyman.removeKeyboards('lao_2008_basic');
|
||||
// assert.equal(keyman.getActiveKeyboard(), '', "Keyboard not removed correctly!");
|
||||
// });
|
||||
// });
|
||||
|
||||
it('Automatically sets first available keyboard', function() {
|
||||
this.timeout(2 * testconfig.timeouts.scriptLoad);
|
||||
// it('Automatically sets first available keyboard', function() {
|
||||
// this.timeout(2 * testconfig.timeouts.scriptLoad);
|
||||
|
||||
return loadKeyboardFromJSON("/keyboards/lao_2008_basic.json",
|
||||
testconfig.timeouts.scriptLoad,
|
||||
{passive: true}).then(() => {
|
||||
// Because we're loading the keyboard 'passively', KMW's setActiveKeyboard function is auto-called
|
||||
// on the stub-add. That specific call (for first keyboard auto-activation) is outside of KMW's
|
||||
// current Promise chain, so we can't _directly_ rely on a KMW Promise to test it.
|
||||
return new Promise((resolve) => {
|
||||
let hasResolved = false;
|
||||
// So, we give KMW the time needed for auto-activation to happen, polling a bit actively so that we don't
|
||||
// wait unnecessarily long after it occurs.
|
||||
let absoluteTimer = window.setTimeout(() => {
|
||||
if(!hasResolved) {
|
||||
resolve();
|
||||
hasResolved = true;
|
||||
}
|
||||
// return loadKeyboardFromJSON("/keyboards/lao_2008_basic.json",
|
||||
// testconfig.timeouts.scriptLoad,
|
||||
// {passive: true}).then(() => {
|
||||
// // Because we're loading the keyboard 'passively', KMW's setActiveKeyboard function is auto-called
|
||||
// // on the stub-add. That specific call (for first keyboard auto-activation) is outside of KMW's
|
||||
// // current Promise chain, so we can't _directly_ rely on a KMW Promise to test it.
|
||||
// return new Promise((resolve) => {
|
||||
// let hasResolved = false;
|
||||
// // So, we give KMW the time needed for auto-activation to happen, polling a bit actively so that we don't
|
||||
// // wait unnecessarily long after it occurs.
|
||||
// let absoluteTimer = window.setTimeout(() => {
|
||||
// if(!hasResolved) {
|
||||
// resolve();
|
||||
// hasResolved = true;
|
||||
// }
|
||||
|
||||
window.clearTimeout(intervalTimer);
|
||||
}, testconfig.timeouts.scriptLoad);
|
||||
// window.clearTimeout(intervalTimer);
|
||||
// }, testconfig.timeouts.scriptLoad);
|
||||
|
||||
let intervalTimer = window.setInterval(() => {
|
||||
if(keyman.getActiveKeyboard() != '') {
|
||||
window.clearTimeout(intervalTimer);
|
||||
window.clearTimeout(absoluteTimer);
|
||||
// let intervalTimer = window.setInterval(() => {
|
||||
// if(keyman.getActiveKeyboard() != '') {
|
||||
// window.clearTimeout(intervalTimer);
|
||||
// window.clearTimeout(absoluteTimer);
|
||||
|
||||
if(!hasResolved) {
|
||||
resolve();
|
||||
hasResolved = true;
|
||||
}
|
||||
}
|
||||
}, 50);
|
||||
});
|
||||
// Once this delay-Promise resolves successfully (either way)...
|
||||
}).then(function() {
|
||||
assert.isNotNull(keyman.getKeyboard("lao_2008_basic", "lo"), "Keyboard stub was not registered!");
|
||||
assert.equal(keyman.getActiveKeyboard(), "Keyboard_lao_2008_basic", "Keyboard not set automatically!");
|
||||
keyman.removeKeyboards('lao_2008_basic');
|
||||
assert.equal(keyman.getActiveKeyboard(), '', "Keyboard not removed correctly!");
|
||||
}); // THEN we run our checks.
|
||||
});
|
||||
});
|
||||
});
|
||||
// if(!hasResolved) {
|
||||
// resolve();
|
||||
// hasResolved = true;
|
||||
// }
|
||||
// }
|
||||
// }, 50);
|
||||
// });
|
||||
// // Once this delay-Promise resolves successfully (either way)...
|
||||
// }).then(function() {
|
||||
// assert.isNotNull(keyman.getKeyboard("lao_2008_basic", "lo"), "Keyboard stub was not registered!");
|
||||
// assert.equal(keyman.getActiveKeyboard(), "Keyboard_lao_2008_basic", "Keyboard not set automatically!");
|
||||
// keyman.removeKeyboards('lao_2008_basic');
|
||||
// assert.equal(keyman.getActiveKeyboard(), '', "Keyboard not removed correctly!");
|
||||
// }); // THEN we run our checks.
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
|
@ -1,47 +1,47 @@
|
|||
var assert = chai.assert;
|
||||
// var assert = chai.assert;
|
||||
|
||||
describe('Engine - Chirality', function() {
|
||||
this.timeout(testconfig.timeouts.scriptLoad);
|
||||
// describe('Engine - Chirality', function() {
|
||||
// this.timeout(testconfig.timeouts.scriptLoad);
|
||||
|
||||
before(function() {
|
||||
fixture.setBase('fixtures');
|
||||
return setupKMW(null, testconfig.timeouts.scriptLoad);
|
||||
});
|
||||
// before(function() {
|
||||
// fixture.setBase('fixtures');
|
||||
// return setupKMW(null, testconfig.timeouts.scriptLoad);
|
||||
// });
|
||||
|
||||
beforeEach(function(done) {
|
||||
fixture.load("singleInput.html");
|
||||
// beforeEach(function(done) {
|
||||
// fixture.load("singleInput.html");
|
||||
|
||||
window.setTimeout(function() {
|
||||
done()
|
||||
}, testconfig.timeouts.eventDelay);
|
||||
});
|
||||
// window.setTimeout(function() {
|
||||
// done()
|
||||
// }, testconfig.timeouts.eventDelay);
|
||||
// });
|
||||
|
||||
after(function() {
|
||||
teardownKMW();
|
||||
});
|
||||
// after(function() {
|
||||
// teardownKMW();
|
||||
// });
|
||||
|
||||
afterEach(function() {
|
||||
fixture.cleanup();
|
||||
});
|
||||
// afterEach(function() {
|
||||
// fixture.cleanup();
|
||||
// });
|
||||
|
||||
it('Keyboard + OSK simulation', function() {
|
||||
this.timeout(testconfig.timeouts.scriptLoad * (testconfig.mobile ? 1 : 2));
|
||||
/* Interestingly, this still works on iOS, probably because we're able to force-set
|
||||
* the 'location' property in the simulated event on mobile devices, even when iOS neglects to
|
||||
* set it for real events.
|
||||
*/
|
||||
return runKeyboardTestFromJSON('/engine_tests/chirality.json',
|
||||
{usingOSK: false},
|
||||
assert.equal,
|
||||
testconfig.timeouts.scriptLoad).then(() => {
|
||||
/* We only really care to test the 'desktop' OSK because of how it directly models the modifier keys.
|
||||
*
|
||||
* The 'phone' and 'layout' versions take shortcuts that bypass any tricky chiral logic;
|
||||
* a better test for those would be to ensure the touch OSK is constructed properly.
|
||||
*/
|
||||
if(!testconfig.mobile) {
|
||||
return runKeyboardTestFromJSON('/engine_tests/chirality.json', {usingOSK: true}, assert.equal, testconfig.timeouts.scriptLoad);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
// it('Keyboard + OSK simulation', function() {
|
||||
// this.timeout(testconfig.timeouts.scriptLoad * (testconfig.mobile ? 1 : 2));
|
||||
// /* Interestingly, this still works on iOS, probably because we're able to force-set
|
||||
// * the 'location' property in the simulated event on mobile devices, even when iOS neglects to
|
||||
// * set it for real events.
|
||||
// */
|
||||
// return runKeyboardTestFromJSON('/engine_tests/chirality.json',
|
||||
// {usingOSK: false},
|
||||
// assert.equal,
|
||||
// testconfig.timeouts.scriptLoad).then(() => {
|
||||
// /* We only really care to test the 'desktop' OSK because of how it directly models the modifier keys.
|
||||
// *
|
||||
// * The 'phone' and 'layout' versions take shortcuts that bypass any tricky chiral logic;
|
||||
// * a better test for those would be to ensure the touch OSK is constructed properly.
|
||||
// */
|
||||
// if(!testconfig.mobile) {
|
||||
// return runKeyboardTestFromJSON('/engine_tests/chirality.json', {usingOSK: true}, assert.equal, testconfig.timeouts.scriptLoad);
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
|
@ -1,113 +1,113 @@
|
|||
var assert = chai.assert;
|
||||
// var assert = chai.assert;
|
||||
|
||||
describe('Event Management', function() {
|
||||
this.timeout(testconfig.timeouts.standard);
|
||||
// describe('Event Management', function() {
|
||||
// this.timeout(testconfig.timeouts.standard);
|
||||
|
||||
before(function() {
|
||||
this.timeout(testconfig.timeouts.scriptLoad * 2);
|
||||
fixture.setBase('fixtures');
|
||||
fixture.load("eventTestConfig.html");
|
||||
// before(function() {
|
||||
// this.timeout(testconfig.timeouts.scriptLoad * 2);
|
||||
// fixture.setBase('fixtures');
|
||||
// fixture.load("eventTestConfig.html");
|
||||
|
||||
return setupKMW(null, testconfig.timeouts.scriptLoad).then(() => {
|
||||
// We use this keyboard since we only need minimal input functionality for these tests.
|
||||
// Smaller is better when dealing with net latency.
|
||||
return loadKeyboardFromJSON("/keyboards/test_simple_deadkeys.json", testconfig.timeouts.scriptLoad);
|
||||
});
|
||||
});
|
||||
// return setupKMW(null, testconfig.timeouts.scriptLoad).then(() => {
|
||||
// // We use this keyboard since we only need minimal input functionality for these tests.
|
||||
// // Smaller is better when dealing with net latency.
|
||||
// return loadKeyboardFromJSON("/keyboards/test_simple_deadkeys.json", testconfig.timeouts.scriptLoad);
|
||||
// });
|
||||
// });
|
||||
|
||||
after(function() {
|
||||
teardownKMW();
|
||||
});
|
||||
// after(function() {
|
||||
// teardownKMW();
|
||||
// });
|
||||
|
||||
it('Keystroke-based onChange event generation', function() {
|
||||
var simple_A = {"type":"key","key":"a","code":"KeyA","keyCode":65,"modifierSet":0,"location":0};
|
||||
var event = new KMWRecorder.PhysicalInputEventSpec(simple_A);
|
||||
// it('Keystroke-based onChange event generation', function() {
|
||||
// var simple_A = {"type":"key","key":"a","code":"KeyA","keyCode":65,"modifierSet":0,"location":0};
|
||||
// var event = new KMWRecorder.PhysicalInputEventSpec(simple_A);
|
||||
|
||||
var ele = document.getElementById("input");
|
||||
// var ele = document.getElementById("input");
|
||||
|
||||
ele.onchange = function() {
|
||||
ele.onchange = null;
|
||||
}
|
||||
// ele.onchange = function() {
|
||||
// ele.onchange = null;
|
||||
// }
|
||||
|
||||
// A bit of a force-hack to ensure the element is seen as active for the tests.
|
||||
com.keyman.dom['DOMEventHandlers'].states._lastActiveElement = ele;
|
||||
com.keyman.dom['DOMEventHandlers'].states._activeElement = ele;
|
||||
// // A bit of a force-hack to ensure the element is seen as active for the tests.
|
||||
// com.keyman.dom['DOMEventHandlers'].states._lastActiveElement = ele;
|
||||
// com.keyman.dom['DOMEventHandlers'].states._activeElement = ele;
|
||||
|
||||
let eventDriver = new KMWRecorder.BrowserDriver(ele);
|
||||
eventDriver.simulateEvent(event);
|
||||
// let eventDriver = new KMWRecorder.BrowserDriver(ele);
|
||||
// eventDriver.simulateEvent(event);
|
||||
|
||||
let focusEvent = new FocusEvent('blur', {relatedTarget: ele});
|
||||
ele.dispatchEvent(focusEvent);
|
||||
// let focusEvent = new FocusEvent('blur', {relatedTarget: ele});
|
||||
// ele.dispatchEvent(focusEvent);
|
||||
|
||||
// Asserts that the handler is called. As the handler clears itself, it will only
|
||||
// remain set if it hasn't been called.
|
||||
assert.isNull(ele.onchange, '`onchange` handler was not called');
|
||||
});
|
||||
// // Asserts that the handler is called. As the handler clears itself, it will only
|
||||
// // remain set if it hasn't been called.
|
||||
// assert.isNull(ele.onchange, '`onchange` handler was not called');
|
||||
// });
|
||||
|
||||
it('OSK-based onChange event generation', function() {
|
||||
var simple_A = {"type":"osk","keyID":"default-K_A"};
|
||||
var event = new KMWRecorder.OSKInputEventSpec(simple_A);
|
||||
// it('OSK-based onChange event generation', function() {
|
||||
// var simple_A = {"type":"osk","keyID":"default-K_A"};
|
||||
// var event = new KMWRecorder.OSKInputEventSpec(simple_A);
|
||||
|
||||
var ele = document.getElementById("input");
|
||||
// var ele = document.getElementById("input");
|
||||
|
||||
ele.onchange = function() {
|
||||
ele.onchange = null;
|
||||
}
|
||||
// ele.onchange = function() {
|
||||
// ele.onchange = null;
|
||||
// }
|
||||
|
||||
// A bit of a force-hack to ensure the element is seen as active for the tests.
|
||||
com.keyman.dom['DOMEventHandlers'].states._lastActiveElement = ele;
|
||||
com.keyman.dom['DOMEventHandlers'].states._activeElement = ele;
|
||||
// // A bit of a force-hack to ensure the element is seen as active for the tests.
|
||||
// com.keyman.dom['DOMEventHandlers'].states._lastActiveElement = ele;
|
||||
// com.keyman.dom['DOMEventHandlers'].states._activeElement = ele;
|
||||
|
||||
let eventDriver = new KMWRecorder.BrowserDriver(ele);
|
||||
eventDriver.simulateEvent(event);
|
||||
// let eventDriver = new KMWRecorder.BrowserDriver(ele);
|
||||
// eventDriver.simulateEvent(event);
|
||||
|
||||
let focusEvent = new FocusEvent('blur', {relatedTarget: ele});
|
||||
ele.dispatchEvent(focusEvent);
|
||||
// let focusEvent = new FocusEvent('blur', {relatedTarget: ele});
|
||||
// ele.dispatchEvent(focusEvent);
|
||||
|
||||
// Asserts that the handler is called. As the handler clears itself, it will only
|
||||
// remain set if it hasn't been called.
|
||||
assert.isNull(ele.onchange, '`onchange` handler was not called');
|
||||
});
|
||||
// // Asserts that the handler is called. As the handler clears itself, it will only
|
||||
// // remain set if it hasn't been called.
|
||||
// assert.isNull(ele.onchange, '`onchange` handler was not called');
|
||||
// });
|
||||
|
||||
it('Keystroke-based onInput event generation', function() {
|
||||
var simple_A = {"type":"key","key":"a","code":"KeyA","keyCode":65,"modifierSet":0,"location":0};
|
||||
var event = new KMWRecorder.PhysicalInputEventSpec(simple_A);
|
||||
// it('Keystroke-based onInput event generation', function() {
|
||||
// var simple_A = {"type":"key","key":"a","code":"KeyA","keyCode":65,"modifierSet":0,"location":0};
|
||||
// var event = new KMWRecorder.PhysicalInputEventSpec(simple_A);
|
||||
|
||||
var ele = document.getElementById("input");
|
||||
// var ele = document.getElementById("input");
|
||||
|
||||
var counterObj = {i:0};
|
||||
var fin = 3;
|
||||
// var counterObj = {i:0};
|
||||
// var fin = 3;
|
||||
|
||||
ele.addEventListener("input", function() {
|
||||
counterObj.i++;
|
||||
});
|
||||
// ele.addEventListener("input", function() {
|
||||
// counterObj.i++;
|
||||
// });
|
||||
|
||||
let eventDriver = new KMWRecorder.BrowserDriver(ele);
|
||||
eventDriver.simulateEvent(event);
|
||||
eventDriver.simulateEvent(event);
|
||||
eventDriver.simulateEvent(event);
|
||||
// let eventDriver = new KMWRecorder.BrowserDriver(ele);
|
||||
// eventDriver.simulateEvent(event);
|
||||
// eventDriver.simulateEvent(event);
|
||||
// eventDriver.simulateEvent(event);
|
||||
|
||||
assert.equal(counterObj.i, fin, "Event handler not called the expected number of times");
|
||||
});
|
||||
// assert.equal(counterObj.i, fin, "Event handler not called the expected number of times");
|
||||
// });
|
||||
|
||||
it('OSK-based onInput event generation', function() {
|
||||
var simple_A = {"type":"osk","keyID":"default-K_A"};
|
||||
var event = new KMWRecorder.OSKInputEventSpec(simple_A);
|
||||
// it('OSK-based onInput event generation', function() {
|
||||
// var simple_A = {"type":"osk","keyID":"default-K_A"};
|
||||
// var event = new KMWRecorder.OSKInputEventSpec(simple_A);
|
||||
|
||||
var ele = document.getElementById("input");
|
||||
// var ele = document.getElementById("input");
|
||||
|
||||
var counterObj = {i:0};
|
||||
var fin = 3;
|
||||
// var counterObj = {i:0};
|
||||
// var fin = 3;
|
||||
|
||||
ele.addEventListener("input", function() {
|
||||
counterObj.i++;
|
||||
});
|
||||
// ele.addEventListener("input", function() {
|
||||
// counterObj.i++;
|
||||
// });
|
||||
|
||||
let eventDriver = new KMWRecorder.BrowserDriver(ele);
|
||||
eventDriver.simulateEvent(event);
|
||||
eventDriver.simulateEvent(event);
|
||||
eventDriver.simulateEvent(event);
|
||||
// let eventDriver = new KMWRecorder.BrowserDriver(ele);
|
||||
// eventDriver.simulateEvent(event);
|
||||
// eventDriver.simulateEvent(event);
|
||||
// eventDriver.simulateEvent(event);
|
||||
|
||||
assert.equal(counterObj.i, fin, "Event handler not called the expected number of times");
|
||||
});
|
||||
});
|
||||
// assert.equal(counterObj.i, fin, "Event handler not called the expected number of times");
|
||||
// });
|
||||
// });
|
||||
|
|
@ -1,4 +1,12 @@
|
|||
var assert = chai.assert;
|
||||
let assert = chai.assert;
|
||||
|
||||
import { Mock } from '/@keymanapp/keyboard-processor/build/obj/text/outputTarget.js';
|
||||
import { Input } from '/@keymanapp/keyman/build/engine/element-wrappers/lib/index.mjs';
|
||||
import extendString from '../../../../../common/web/utils/build/obj/kmwstring.js';
|
||||
|
||||
import { toSupplementaryPairString, DynamicElements } from '../test_utils.js';
|
||||
|
||||
extendString();
|
||||
|
||||
var MockTests;
|
||||
|
||||
|
|
@ -26,7 +34,7 @@ if(typeof MockTests == 'undefined') {
|
|||
MockTests.initBase = function() {
|
||||
var id = DynamicElements.addInput();
|
||||
var elem = document.getElementById(id);
|
||||
var wrapper = new com.keyman.dom.targets.Input(elem);
|
||||
var wrapper = new Input(elem);
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
|
@ -86,7 +94,7 @@ describe('OutputTarget Mocking', function() {
|
|||
describe('The "Mock" output target', function() {
|
||||
describe('Initialization', function() {
|
||||
it('properly initializes from a raw string', function() {
|
||||
var mock = new com.keyman.text.Mock(MockTests.Apple.mixed);
|
||||
var mock = new Mock(MockTests.Apple.mixed);
|
||||
|
||||
assert.equal(mock.getText(), MockTests.Apple.mixed);
|
||||
assert.equal(mock.getDeadkeyCaret(), 5);
|
||||
|
|
@ -95,7 +103,7 @@ describe('OutputTarget Mocking', function() {
|
|||
it('copies an existing OutputTarget without a text selection', function() {
|
||||
var base = MockTests.setupBase(4);
|
||||
|
||||
var mock = com.keyman.text.Mock.from(base);
|
||||
var mock = Mock.from(base);
|
||||
assert.equal(mock.getText(), MockTests.Apple.mixed);
|
||||
assert.deepEqual(mock.deadkeys(), base.deadkeys());
|
||||
});
|
||||
|
|
@ -103,7 +111,7 @@ describe('OutputTarget Mocking', function() {
|
|||
it('copies an existing OutputTarget with a text selection', function() {
|
||||
var base = MockTests.setupBase(4, 5);
|
||||
|
||||
var mock = com.keyman.text.Mock.from(base);
|
||||
var mock = Mock.from(base);
|
||||
// The selection should appear to be automatically deleted, as any text mutation
|
||||
// by KMW would automatically erase the text anyway.
|
||||
assert.equal(mock.getText(), MockTests.Apple.mixed.substr(0, 5));
|
||||
|
|
@ -117,7 +125,7 @@ describe('OutputTarget Mocking', function() {
|
|||
it('is not affected by mutation of the source element', function() {
|
||||
// Already-verified code
|
||||
var base = MockTests.setupBase(4);
|
||||
var mock = com.keyman.text.Mock.from(base);
|
||||
var mock = Mock.from(base);
|
||||
var baseInitDks = base.deadkeys().clone();
|
||||
|
||||
// Now for the actual test.
|
||||
|
|
@ -136,7 +144,7 @@ describe('OutputTarget Mocking', function() {
|
|||
it('does not affect the source element when mutated', function() {
|
||||
// Already-verified code
|
||||
var base = MockTests.setupBase(4);
|
||||
var mock = com.keyman.text.Mock.from(base);
|
||||
var mock = Mock.from(base);
|
||||
var baseInitDks = base.deadkeys().clone();
|
||||
|
||||
// Now for the actual test.
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,5 @@
|
|||
module.exports = function(config) {
|
||||
var base = require("./base.conf.js");
|
||||
var base = require("./base.conf.cjs");
|
||||
|
||||
var specifics = {
|
||||
// test results reporter to use
|
||||
|
|
@ -2,6 +2,8 @@
|
|||
// We'll let things break after this reports, since this will likely signal a LOT of other failures.
|
||||
var assert = chai.assert;
|
||||
|
||||
import Device from '/@keymanapp/keyman/build/engine/device-detect/lib/index.mjs';
|
||||
|
||||
/* Note - we still have to prevent errors setting up test resources;
|
||||
* Karma will fail to report errors for affected browsers otherwise.
|
||||
*
|
||||
|
|
@ -16,7 +18,7 @@ describe('Test Initialization', function() {
|
|||
it("Detects device without JS errors", function() {
|
||||
var device;
|
||||
try {
|
||||
device = new com.keyman.Device();
|
||||
device = new Device();
|
||||
device.detect();
|
||||
|
||||
console.log("Detected platform: " + device.browser + " on " + device.OS + " with form factor " + device.formFactor);
|
||||
|
|
|
|||
|
|
@ -1,200 +1,199 @@
|
|||
// KeymanWeb test suite - processing of the Karma configuration's client.args parameter.
|
||||
// // KeymanWeb test suite - processing of the Karma configuration's client.args parameter.
|
||||
|
||||
com = com || {};
|
||||
com.keyman = com.keyman || {};
|
||||
com.keyman.karma = com.keyman.karma || {};
|
||||
com.keyman.karma.DEVICE_DETECT_FAILURE = false;
|
||||
import Device from '/@keymanapp/keyman/build/engine/device-detect/lib/index.mjs';
|
||||
|
||||
export let DEVICE_DETECT_FAILURE = false;
|
||||
|
||||
// If we've set things up to support Device dection without loading KMW...
|
||||
try {
|
||||
let device = new com.keyman.Device();
|
||||
let device = new Device();
|
||||
device.detect();
|
||||
} catch (err) {
|
||||
// Sets a warning flag that unit-test files can use to disable themselves.
|
||||
com.keyman.karma.DEVICE_DETECT_FAILURE = true;
|
||||
DEVICE_DETECT_FAILURE = true;
|
||||
}
|
||||
|
||||
// Keyman test suite utility methods
|
||||
// // Keyman test suite utility methods
|
||||
|
||||
var setupKMW = function(kmwOptions, timeout) {
|
||||
var ui;
|
||||
// var setupKMW = function(kmwOptions, timeout) {
|
||||
// var ui;
|
||||
|
||||
if(typeof(kmwOptions) == 'string' || typeof(kmwOptions) == 'undefined' || kmwOptions == null) {
|
||||
ui = kmwOptions;
|
||||
// if(typeof(kmwOptions) == 'string' || typeof(kmwOptions) == 'undefined' || kmwOptions == null) {
|
||||
// ui = kmwOptions;
|
||||
|
||||
var kmwOptions = {
|
||||
attachType:'auto',
|
||||
root:'source',
|
||||
resources:'../../../../source'
|
||||
};
|
||||
// var kmwOptions = {
|
||||
// attachType:'auto',
|
||||
// root:'source',
|
||||
// resources:'../../../../source'
|
||||
// };
|
||||
|
||||
if(ui) {
|
||||
kmwOptions.ui = ui;
|
||||
}
|
||||
}
|
||||
// if(ui) {
|
||||
// kmwOptions.ui = ui;
|
||||
// }
|
||||
// }
|
||||
|
||||
const kmwPromise = setupScript('source/keymanweb.js', timeout, (scriptEle) => {
|
||||
fixture.el.appendChild(scriptEle);
|
||||
});
|
||||
// const kmwPromise = setupScript('source/keymanweb.js', timeout, (scriptEle) => {
|
||||
// fixture.el.appendChild(scriptEle);
|
||||
// });
|
||||
|
||||
ui = kmwOptions.ui;
|
||||
// ui = kmwOptions.ui;
|
||||
|
||||
kmwOptions.attachType = kmwOptions.attachType ? kmwOptions.attachType : 'auto';
|
||||
// kmwOptions.attachType = kmwOptions.attachType ? kmwOptions.attachType : 'auto';
|
||||
|
||||
if(!kmwOptions.root) {
|
||||
kmwOptions.root = 'source';
|
||||
}
|
||||
// if(!kmwOptions.root) {
|
||||
// kmwOptions.root = 'source';
|
||||
// }
|
||||
|
||||
if(!kmwOptions.resources) {
|
||||
kmwOptions.resources = '../../../../source';
|
||||
}
|
||||
// if(!kmwOptions.resources) {
|
||||
// kmwOptions.resources = '../../../../source';
|
||||
// }
|
||||
|
||||
let uiPromise;
|
||||
if(ui) {
|
||||
uiPromise = setupScript('ui-source/kmwui' + ui + '.js', timeout, (scriptEle) => {
|
||||
fixture.el.appendChild(scriptEle);
|
||||
});
|
||||
// let uiPromise;
|
||||
// if(ui) {
|
||||
// uiPromise = setupScript('ui-source/kmwui' + ui + '.js', timeout, (scriptEle) => {
|
||||
// fixture.el.appendChild(scriptEle);
|
||||
// });
|
||||
|
||||
kmwOptions.ui=ui;
|
||||
}
|
||||
// kmwOptions.ui=ui;
|
||||
// }
|
||||
|
||||
let compositePromise = kmwPromise;
|
||||
if(uiPromise) {
|
||||
compositePromise = Promise.all([kmwPromise, uiPromise]);
|
||||
}
|
||||
// let compositePromise = kmwPromise;
|
||||
// if(uiPromise) {
|
||||
// compositePromise = Promise.all([kmwPromise, uiPromise]);
|
||||
// }
|
||||
|
||||
return finalPromise = compositePromise.then(() => {
|
||||
if(window['keyman']) {
|
||||
return window['keyman'].init(kmwOptions);
|
||||
} else {
|
||||
return Promise.reject();
|
||||
}
|
||||
});
|
||||
}
|
||||
// return finalPromise = compositePromise.then(() => {
|
||||
// if(window['keyman']) {
|
||||
// return window['keyman'].init(kmwOptions);
|
||||
// } else {
|
||||
// return Promise.reject();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
/**
|
||||
* Produces a script element tied to a Promise for its eventual load (or failure thereof).
|
||||
*
|
||||
* The script element is only available via callback due to implementation constraints.
|
||||
*
|
||||
* @param {*} src The source script's (relative) path on the test server.
|
||||
* @param {*} timeout
|
||||
* @param {*} functor A callback to handle the script element.
|
||||
* @returns
|
||||
*/
|
||||
var setupScript = function(src, timeout, functor) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const Lscript = document.createElement('script');
|
||||
let hasResolved = false;
|
||||
Lscript.charset="UTF-8"; // KMEW-89
|
||||
Lscript.type = 'text/javascript';
|
||||
Lscript.async = false;
|
||||
// /**
|
||||
// * Produces a script element tied to a Promise for its eventual load (or failure thereof).
|
||||
// *
|
||||
// * The script element is only available via callback due to implementation constraints.
|
||||
// *
|
||||
// * @param {*} src The source script's (relative) path on the test server.
|
||||
// * @param {*} timeout
|
||||
// * @param {*} functor A callback to handle the script element.
|
||||
// * @returns
|
||||
// */
|
||||
// var setupScript = function(src, timeout, functor) {
|
||||
// return new Promise((resolve, reject) => {
|
||||
// const Lscript = document.createElement('script');
|
||||
// let hasResolved = false;
|
||||
// Lscript.charset="UTF-8"; // KMEW-89
|
||||
// Lscript.type = 'text/javascript';
|
||||
// Lscript.async = false;
|
||||
|
||||
const timer = window.setTimeout(() => {
|
||||
reject("Script load attempt timed out.");
|
||||
}, timeout);
|
||||
// const timer = window.setTimeout(() => {
|
||||
// reject("Script load attempt timed out.");
|
||||
// }, timeout);
|
||||
|
||||
Lscript.onload = Lscript.onreadystatechange = () => {
|
||||
window.clearTimeout(timer);
|
||||
if(!hasResolved && (Lscript.readyState === undefined || Lscript.readyState == "complete")) {
|
||||
hasResolved = true;
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
// Lscript.onload = Lscript.onreadystatechange = () => {
|
||||
// window.clearTimeout(timer);
|
||||
// if(!hasResolved && (Lscript.readyState === undefined || Lscript.readyState == "complete")) {
|
||||
// hasResolved = true;
|
||||
// resolve();
|
||||
// }
|
||||
// }
|
||||
|
||||
Lscript.onerror = (err) => {
|
||||
window.clearTimeout(timer);
|
||||
reject(err);
|
||||
}
|
||||
// Lscript.onerror = (err) => {
|
||||
// window.clearTimeout(timer);
|
||||
// reject(err);
|
||||
// }
|
||||
|
||||
Lscript.src = src;
|
||||
// Lscript.src = src;
|
||||
|
||||
functor(Lscript);
|
||||
});
|
||||
}
|
||||
// functor(Lscript);
|
||||
// });
|
||||
// }
|
||||
|
||||
var teardownKMW = function() {
|
||||
var error = null;
|
||||
if(keyman) { // If our setupKMW fails somehow, this guard prevents a second error report on teardown.
|
||||
// var teardownKMW = function() {
|
||||
// var error = null;
|
||||
// if(keyman) { // If our setupKMW fails somehow, this guard prevents a second error report on teardown.
|
||||
|
||||
// We want to be SURE teardown works correctly, or we'll get lots of strange errors on other tests.
|
||||
// Thus, error-handling on shutdown itself. It HAS mattered.
|
||||
try {
|
||||
keyman['shutdown']();
|
||||
} catch(err) {
|
||||
error = err;
|
||||
}
|
||||
// // We want to be SURE teardown works correctly, or we'll get lots of strange errors on other tests.
|
||||
// // Thus, error-handling on shutdown itself. It HAS mattered.
|
||||
// try {
|
||||
// keyman['shutdown']();
|
||||
// } catch(err) {
|
||||
// error = err;
|
||||
// }
|
||||
|
||||
try {
|
||||
var success = delete window["keyman"];
|
||||
if(!success) {
|
||||
window["keyman"] = undefined;
|
||||
}
|
||||
} finally {
|
||||
if(error) {
|
||||
console.log("Error during KMW shutdown!");
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// try {
|
||||
// var success = delete window["keyman"];
|
||||
// if(!success) {
|
||||
// window["keyman"] = undefined;
|
||||
// }
|
||||
// } finally {
|
||||
// if(error) {
|
||||
// console.log("Error during KMW shutdown!");
|
||||
// throw error;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
var loadKeyboardStub = function(stub, timeout, params) {
|
||||
var kbdName = "Keyboard_" + stub.id;
|
||||
// var loadKeyboardStub = function(stub, timeout, params) {
|
||||
// var kbdName = "Keyboard_" + stub.id;
|
||||
|
||||
keyman.addKeyboards(stub);
|
||||
if(!params || !params.passive) {
|
||||
return keyman.setActiveKeyboard(kbdName, stub.languages.id);
|
||||
} else if(keyman.getActiveKeyboard() != kbdName) {
|
||||
return setupScript(stub.filename, timeout, (ele) => {
|
||||
fixture.el.appendChild(ele);
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
// keyman.addKeyboards(stub);
|
||||
// if(!params || !params.passive) {
|
||||
// return keyman.setActiveKeyboard(kbdName, stub.languages.id);
|
||||
// } else if(keyman.getActiveKeyboard() != kbdName) {
|
||||
// return setupScript(stub.filename, timeout, (ele) => {
|
||||
// fixture.el.appendChild(ele);
|
||||
// });
|
||||
// } else {
|
||||
// return Promise.resolve();
|
||||
// }
|
||||
// }
|
||||
|
||||
var loadKeyboardFromJSON = function(jsonPath, timeout, params) {
|
||||
var stub = fixture.load(jsonPath, true);
|
||||
// var loadKeyboardFromJSON = function(jsonPath, timeout, params) {
|
||||
// var stub = fixture.load(jsonPath, true);
|
||||
|
||||
return loadKeyboardStub(stub, timeout, params);
|
||||
}
|
||||
// return loadKeyboardStub(stub, timeout, params);
|
||||
// }
|
||||
|
||||
function runLoadedKeyboardTest(testDef, device, usingOSK, assertCallback) {
|
||||
var inputElem = document.getElementById('singleton');
|
||||
// function runLoadedKeyboardTest(testDef, device, usingOSK, assertCallback) {
|
||||
// var inputElem = document.getElementById('singleton');
|
||||
|
||||
let proctor = new KMWRecorder.BrowserProctor(inputElem, device, usingOSK, assertCallback);
|
||||
testDef.test(proctor);
|
||||
}
|
||||
// let proctor = new KMWRecorder.BrowserProctor(inputElem, device, usingOSK, assertCallback);
|
||||
// testDef.test(proctor);
|
||||
// }
|
||||
|
||||
function runKeyboardTestFromJSON(jsonPath, params, assertCallback, timeout) {
|
||||
var testSpec = new KMWRecorder.KeyboardTest(fixture.load(jsonPath, true));
|
||||
let device = new com.keyman.Device();
|
||||
device.detect();
|
||||
// function runKeyboardTestFromJSON(jsonPath, params, assertCallback, timeout) {
|
||||
// var testSpec = new KMWRecorder.KeyboardTest(fixture.load(jsonPath, true));
|
||||
// let device = new com.keyman.Device();
|
||||
// device.detect();
|
||||
|
||||
return loadKeyboardStub(testSpec.keyboard, timeout).then(() => {
|
||||
runLoadedKeyboardTest(testSpec, device.coreSpec, params.usingOSK, assertCallback);
|
||||
}).finally(() => {
|
||||
keyman.removeKeyboards(testSpec.keyboard.id);
|
||||
});
|
||||
}
|
||||
// return loadKeyboardStub(testSpec.keyboard, timeout).then(() => {
|
||||
// runLoadedKeyboardTest(testSpec, device.coreSpec, params.usingOSK, assertCallback);
|
||||
// }).finally(() => {
|
||||
// keyman.removeKeyboards(testSpec.keyboard.id);
|
||||
// });
|
||||
// }
|
||||
|
||||
function retrieveAndReset(Pelem) {
|
||||
let val = Pelem.value;
|
||||
Pelem.value = "";
|
||||
// function retrieveAndReset(Pelem) {
|
||||
// let val = Pelem.value;
|
||||
// Pelem.value = "";
|
||||
|
||||
return val;
|
||||
}
|
||||
// return val;
|
||||
// }
|
||||
|
||||
// Useful for tests related to strings with supplementary pairs.
|
||||
var toSupplementaryPairString = function(code){
|
||||
export function toSupplementaryPairString(code) {
|
||||
var H = Math.floor((code - 0x10000) / 0x400) + 0xD800;
|
||||
var L = (code - 0x10000) % 0x400 + 0xDC00;
|
||||
|
||||
return String.fromCharCode(H, L);
|
||||
}
|
||||
|
||||
var toEscapedSupplementaryPairString = function(code){
|
||||
export function toEscapedSupplementaryPairString(code) {
|
||||
var H = (Math.floor((code - 0x10000) / 0x400) + 0xD800).toString(16);
|
||||
var L = ((code - 0x10000) % 0x400 + 0xDC00).toString(16);
|
||||
|
||||
|
|
@ -204,7 +203,7 @@ var toEscapedSupplementaryPairString = function(code){
|
|||
// Defines an object for dynamically adding elements for testing purposes.
|
||||
// Designed for use with the robustAttachment.html fixture.
|
||||
|
||||
var DynamicElements;
|
||||
export let DynamicElements;
|
||||
var inputCounter = 0;
|
||||
|
||||
if(typeof(DynamicElements) == 'undefined') {
|
||||
|
|
@ -316,17 +315,17 @@ if(typeof(DynamicElements) == 'undefined') {
|
|||
}
|
||||
}
|
||||
|
||||
// Is utilized only by the attachmentAPI test case, but it was originally defined as part of the same
|
||||
// object as the rest of DynamicElements, which is useful for numerous test cases.
|
||||
DynamicElements.init = function() {
|
||||
var s_key_json = {"type": "key", "key":"s", "code":"KeyS","keyCode":83,"modifierSet":0,"location":0};
|
||||
DynamicElements.keyCommand = new KMWRecorder.PhysicalInputEventSpec(s_key_json);
|
||||
// // Is utilized only by the attachmentAPI test case, but it was originally defined as part of the same
|
||||
// // object as the rest of DynamicElements, which is useful for numerous test cases.
|
||||
// DynamicElements.init = function() {
|
||||
// var s_key_json = {"type": "key", "key":"s", "code":"KeyS","keyCode":83,"modifierSet":0,"location":0};
|
||||
// DynamicElements.keyCommand = new KMWRecorder.PhysicalInputEventSpec(s_key_json);
|
||||
|
||||
DynamicElements.enabledLaoOutput = "ຫ";
|
||||
DynamicElements.enabledKhmerOutput = "ស";
|
||||
// Simulated JavaScript events do not produce text output.
|
||||
DynamicElements.disabledOutput = "";
|
||||
}
|
||||
// DynamicElements.enabledLaoOutput = "ຫ";
|
||||
// DynamicElements.enabledKhmerOutput = "ស";
|
||||
// // Simulated JavaScript events do not produce text output.
|
||||
// DynamicElements.disabledOutput = "";
|
||||
// }
|
||||
|
||||
DynamicElements.init();
|
||||
}
|
||||
// DynamicElements.init();
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue