mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-11 19:35:32 +00:00
fix(web): add unit tests for DeadkeyTracker and fix a few bugs
- initialize `Deadkey.matched` in c'tor - fix `Deadkey.equal` method - verify deadkey exists before removing it in `DeadkeyTracker.remove` - add unit tests for DeadkeyTracker Test-bot: skip
This commit is contained in:
parent
9710f5b06a
commit
98fa9e3003
4 changed files with 305 additions and 9 deletions
|
|
@ -627,6 +627,8 @@ export class JSKeyboardInterface extends KeyboardHarness {
|
|||
* @param {number} d deadkey
|
||||
* @return {boolean} True if deadkey found selected context matches val
|
||||
* Description Match deadkey at current cursor position
|
||||
*
|
||||
* Deprecated: use `fullContextMatch` instead.
|
||||
*/
|
||||
deadkeyMatch(n: number, textStore: TextStore, d: number): boolean {
|
||||
return textStore.hasDeadkeyMatch(n, d);
|
||||
|
|
|
|||
|
|
@ -11,10 +11,11 @@ export class Deadkey {
|
|||
this.p = pos;
|
||||
this.d = id;
|
||||
this.o = Deadkey.ordinalSeed++;
|
||||
this.matched = 0;
|
||||
}
|
||||
|
||||
match(p: number, d: number): boolean {
|
||||
const result:boolean = (this.p == p && this.d == d);
|
||||
match(pos: number, deadkey: number): boolean {
|
||||
const result:boolean = (this.p == pos && this.d == deadkey);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
@ -39,7 +40,7 @@ export class Deadkey {
|
|||
}
|
||||
|
||||
equal(other: Deadkey) {
|
||||
return this.d == other.d && this.p == other.d && this.o == other.o;
|
||||
return this.d == other.d && this.p == other.p && this.o == other.o;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -82,11 +83,11 @@ export class DeadkeyTracker {
|
|||
* Scope Public
|
||||
* @param {number} caretPos current cursor position
|
||||
* @param {number} n expected offset of deadkey from cursor
|
||||
* @param {number} d deadkey
|
||||
* @param {number} deadkey deadkey
|
||||
* @return {boolean} True if deadkey found selected context matches val
|
||||
* Description Match deadkey at current cursor position
|
||||
*/
|
||||
isMatch(caretPos: number, n: number, d: number): boolean {
|
||||
isMatch(caretPos: number, n: number, deadkey: number): boolean {
|
||||
if(this.dks.length == 0) {
|
||||
return false; // I3318
|
||||
}
|
||||
|
|
@ -96,7 +97,7 @@ export class DeadkeyTracker {
|
|||
for(let i = 0; i < this.dks.length; i++) {
|
||||
// Don't re-match an already-matched deadkey. It's possible to have two identical
|
||||
// entries, and they should be kept separately.
|
||||
if(this.dks[i].match(n, d) && !this.dks[i].matched) {
|
||||
if(this.dks[i].match(n, deadkey) && !this.dks[i].matched) {
|
||||
this.dks[i].set();
|
||||
// Assumption: since we match the first possible entry in the array, we
|
||||
// match the entry with the lower ordinal - the 'first' deadkey in the position.
|
||||
|
|
@ -115,7 +116,9 @@ export class DeadkeyTracker {
|
|||
|
||||
remove(dk: Deadkey) {
|
||||
const index = this.dks.indexOf(dk);
|
||||
this.dks.splice(index, 1);
|
||||
if (index > -1) {
|
||||
this.dks.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
clear() {
|
||||
|
|
@ -161,7 +164,6 @@ export class DeadkeyTracker {
|
|||
}
|
||||
|
||||
const otherDks = other.dks;
|
||||
const matchedDks: Deadkey[] = [];
|
||||
|
||||
for(const dk of this.dks) {
|
||||
const match = otherDks.find((otherDk) => dk.equal(otherDk));
|
||||
|
|
@ -170,7 +172,7 @@ export class DeadkeyTracker {
|
|||
}
|
||||
}
|
||||
|
||||
return matchedDks.length == otherDks.length;
|
||||
return true;
|
||||
}
|
||||
|
||||
count(): number {
|
||||
|
|
|
|||
|
|
@ -38,6 +38,15 @@ export { TextStoreLanguageProcessorInterface } from "./textStoreLanguageProcesso
|
|||
export { findCommonSubstringEndIndex } from "./stringDivergence.js";
|
||||
export { Deadkey } from "./deadkeys.js";
|
||||
|
||||
import { DeadkeyTracker } from './deadkeys.js';
|
||||
|
||||
/**
|
||||
* these are exported only for unit tests, do not use
|
||||
*/
|
||||
export const unitTestEndpoints = {
|
||||
DeadkeyTracker,
|
||||
};
|
||||
|
||||
// TODO-web-core: why do we export these here?
|
||||
export * from "keyman/common/web-utils";
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,283 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { Deadkey, unitTestEndpoints } from 'keyman/engine/keyboard';
|
||||
|
||||
const DeadkeyTracker = unitTestEndpoints.DeadkeyTracker;
|
||||
|
||||
describe('DeadkeyTracker', function() {
|
||||
it('can add deadkeys and return count', function() {
|
||||
const tracker = new DeadkeyTracker();
|
||||
tracker.add(new Deadkey(3, 1));
|
||||
tracker.add(new Deadkey(5, 2));
|
||||
tracker.add(new Deadkey(10, 3));
|
||||
|
||||
assert.equal(tracker.dks.length, 3);
|
||||
assert.equal(tracker.count(), 3);
|
||||
});
|
||||
|
||||
it('sorts deadkeys by position, latest first', function() {
|
||||
const tracker = new DeadkeyTracker();
|
||||
tracker.add(new Deadkey(5, 2));
|
||||
tracker.add(new Deadkey(3, 1));
|
||||
tracker.add(new Deadkey(10, 3));
|
||||
|
||||
const sortedDeadkeys = tracker.toSortedArray();
|
||||
|
||||
assert.equal(sortedDeadkeys[0].p, 10);
|
||||
assert.equal(sortedDeadkeys[1].p, 5);
|
||||
assert.equal(sortedDeadkeys[2].p, 3);
|
||||
});
|
||||
|
||||
it('can adjust positions', function () {
|
||||
const tracker = new DeadkeyTracker();
|
||||
tracker.add(new Deadkey(3, 1));
|
||||
tracker.add(new Deadkey(5, 2));
|
||||
tracker.add(new Deadkey(10, 3));
|
||||
|
||||
tracker.adjustPositions(4, 2); // adjust deadkeys after position 4 by +2
|
||||
|
||||
const sortedDeadkeys = tracker.toSortedArray();
|
||||
assert.equal(sortedDeadkeys[0].p, 12); // 10 + 2
|
||||
assert.equal(sortedDeadkeys[1].p, 7); // 5 + 2
|
||||
assert.equal(sortedDeadkeys[2].p, 3); // unchanged
|
||||
});
|
||||
|
||||
describe('equal', function () {
|
||||
it('treats cloned tracker as equal', function () {
|
||||
const tracker1 = new DeadkeyTracker();
|
||||
tracker1.add(new Deadkey(3, 1));
|
||||
tracker1.add(new Deadkey(5, 2));
|
||||
tracker1.add(new Deadkey(10, 3));
|
||||
|
||||
const tracker2 = tracker1.clone();
|
||||
|
||||
assert.isTrue(tracker1.equal(tracker2));
|
||||
});
|
||||
|
||||
it('treats two trackers with same deadkeys create separately not as equal', function () {
|
||||
const tracker1 = new DeadkeyTracker();
|
||||
tracker1.add(new Deadkey(3, 1));
|
||||
tracker1.add(new Deadkey(5, 2));
|
||||
tracker1.add(new Deadkey(10, 3));
|
||||
|
||||
const tracker2 = new DeadkeyTracker();
|
||||
tracker2.add(new Deadkey(3, 1));
|
||||
tracker2.add(new Deadkey(5, 2));
|
||||
tracker2.add(new Deadkey(10, 3));
|
||||
|
||||
assert.isFalse(tracker1.equal(tracker2));
|
||||
});
|
||||
|
||||
it('treats two trackers with same deadkeys in different order not as equal', function () {
|
||||
const tracker1 = new DeadkeyTracker();
|
||||
tracker1.add(new Deadkey(5, 2));
|
||||
tracker1.add(new Deadkey(3, 1));
|
||||
tracker1.add(new Deadkey(10, 3));
|
||||
|
||||
const tracker2 = new DeadkeyTracker();
|
||||
tracker2.add(new Deadkey(10, 3));
|
||||
tracker2.add(new Deadkey(5, 2));
|
||||
tracker2.add(new Deadkey(3, 1));
|
||||
|
||||
assert.isFalse(tracker1.equal(tracker2));
|
||||
});
|
||||
|
||||
it('treats two trackers with same deadkeys in different places not as equal', function () {
|
||||
const tracker1 = new DeadkeyTracker();
|
||||
tracker1.add(new Deadkey(7, 2));
|
||||
tracker1.add(new Deadkey(3, 1));
|
||||
tracker1.add(new Deadkey(12, 3));
|
||||
|
||||
const tracker2 = new DeadkeyTracker();
|
||||
tracker2.add(new Deadkey(10, 3));
|
||||
tracker2.add(new Deadkey(5, 2));
|
||||
tracker2.add(new Deadkey(3, 1));
|
||||
|
||||
assert.isFalse(tracker1.equal(tracker2));
|
||||
});
|
||||
|
||||
it('treats a tracker with additional deadkeys not as equal', function () {
|
||||
const tracker1 = new DeadkeyTracker();
|
||||
tracker1.add(new Deadkey(3, 1));
|
||||
tracker1.add(new Deadkey(5, 2));
|
||||
tracker1.add(new Deadkey(10, 3));
|
||||
|
||||
const tracker2 = tracker1.clone();
|
||||
tracker2.add(new Deadkey(15, 4));
|
||||
|
||||
assert.isFalse(tracker1.equal(tracker2));
|
||||
});
|
||||
});
|
||||
|
||||
it('can clear deadkeys', function() {
|
||||
const tracker = new DeadkeyTracker();
|
||||
tracker.add(new Deadkey(3, 1));
|
||||
tracker.add(new Deadkey(5, 2));
|
||||
tracker.add(new Deadkey(10, 3));
|
||||
|
||||
tracker.clear();
|
||||
|
||||
assert.equal(tracker.count(), 0);
|
||||
});
|
||||
|
||||
describe('remove', function () {
|
||||
it('can remove existing deadkey', function () {
|
||||
const tracker = new DeadkeyTracker();
|
||||
tracker.add(new Deadkey(3, 1));
|
||||
const dk = new Deadkey(5, 2);
|
||||
tracker.add(dk);
|
||||
tracker.add(new Deadkey(10, 3));
|
||||
|
||||
tracker.remove(dk);
|
||||
|
||||
assert.equal(tracker.count(), 2);
|
||||
assert.equal(tracker.dks[0].p, 3);
|
||||
assert.equal(tracker.dks[1].p, 10);
|
||||
});
|
||||
|
||||
it('does not remove similar deadkey', function () {
|
||||
const tracker = new DeadkeyTracker();
|
||||
tracker.add(new Deadkey(3, 1));
|
||||
tracker.add(new Deadkey(5, 2));
|
||||
tracker.add(new Deadkey(10, 3));
|
||||
|
||||
tracker.remove(new Deadkey(5, 2));
|
||||
|
||||
assert.equal(tracker.count(), 3);
|
||||
assert.equal(tracker.dks[0].p, 3);
|
||||
assert.equal(tracker.dks[1].p, 5);
|
||||
assert.equal(tracker.dks[2].p, 10);
|
||||
});
|
||||
});
|
||||
|
||||
it('sorts deadkeys in cloned tracker', function () {
|
||||
const tracker1 = new DeadkeyTracker();
|
||||
tracker1.add(new Deadkey(5, 1));
|
||||
tracker1.add(new Deadkey(3, 2));
|
||||
tracker1.add(new Deadkey(10, 3));
|
||||
|
||||
const tracker2 = tracker1.clone();
|
||||
|
||||
assert.equal(tracker2.count(), 3);
|
||||
assert.equal(tracker2.dks[0].p, 10);
|
||||
assert.equal(tracker2.dks[1].p, 5);
|
||||
assert.equal(tracker2.dks[2].p, 3);
|
||||
});
|
||||
|
||||
it('can delete matched deadkeys', function () {
|
||||
const dk1 = new Deadkey(5, 1);
|
||||
const dk2 = new Deadkey(3, 2);
|
||||
const dk3 = new Deadkey(10, 3);
|
||||
const tracker = new DeadkeyTracker();
|
||||
tracker.add(dk1);
|
||||
tracker.add(dk2);
|
||||
tracker.add(dk3);
|
||||
dk2.set();
|
||||
dk3.set();
|
||||
|
||||
tracker.deleteMatched();
|
||||
|
||||
assert.equal(tracker.count(), 1);
|
||||
assert.equal(tracker.dks[0].p, 5);
|
||||
});
|
||||
|
||||
it('can reset matched deadkeys', function () {
|
||||
const dk1 = new Deadkey(5, 1);
|
||||
const dk2 = new Deadkey(3, 2);
|
||||
const dk3 = new Deadkey(10, 3);
|
||||
const tracker = new DeadkeyTracker();
|
||||
tracker.add(dk1);
|
||||
tracker.add(dk2);
|
||||
tracker.add(dk3);
|
||||
dk2.set();
|
||||
dk3.set();
|
||||
|
||||
tracker.resetMatched();
|
||||
|
||||
assert.equal(tracker.count(), 3);
|
||||
assert.equal(tracker.dks[0].matched, 0);
|
||||
assert.equal(tracker.dks[1].matched, 0);
|
||||
assert.equal(tracker.dks[2].matched, 0);
|
||||
});
|
||||
|
||||
describe('isMatch', function () {
|
||||
it('returns false without having any deadkeys', function () {
|
||||
const tracker = new DeadkeyTracker();
|
||||
|
||||
assert.isFalse(tracker.isMatch(0, 1, 2));
|
||||
});
|
||||
|
||||
it('returns false for non-matching deadkey', function () {
|
||||
const tracker = new DeadkeyTracker();
|
||||
tracker.add(new Deadkey(3, 1));
|
||||
tracker.add(new Deadkey(5, 2));
|
||||
tracker.add(new Deadkey(10, 3));
|
||||
|
||||
assert.isFalse(tracker.isMatch(0, 88, 99));
|
||||
|
||||
assert.equal(tracker.dks[0].matched, 0);
|
||||
assert.equal(tracker.dks[1].matched, 0);
|
||||
assert.equal(tracker.dks[2].matched, 0);
|
||||
});
|
||||
|
||||
it('returns false if offset is wrong', function () {
|
||||
const tracker = new DeadkeyTracker();
|
||||
tracker.add(new Deadkey(3, 1));
|
||||
tracker.add(new Deadkey(5, 2));
|
||||
tracker.add(new Deadkey(10, 3));
|
||||
|
||||
// REVIEW: this behavior seems odd
|
||||
assert.isFalse(tracker.isMatch(0, 3, 1));
|
||||
|
||||
assert.equal(tracker.dks[0].matched, 0);
|
||||
assert.equal(tracker.dks[1].matched, 0);
|
||||
assert.equal(tracker.dks[2].matched, 0);
|
||||
});
|
||||
|
||||
it('returns true for matching deadkey and sets matched flag on deadkey', function () {
|
||||
const tracker = new DeadkeyTracker();
|
||||
tracker.add(new Deadkey(3, 1));
|
||||
tracker.add(new Deadkey(5, 2));
|
||||
tracker.add(new Deadkey(10, 3));
|
||||
|
||||
// REVIEW: this behavior seems odd
|
||||
assert.isTrue(tracker.isMatch(0, -3, 1));
|
||||
|
||||
assert.equal(tracker.dks[0].matched, 1);
|
||||
assert.equal(tracker.dks[1].matched, 0);
|
||||
assert.equal(tracker.dks[2].matched, 0);
|
||||
});
|
||||
|
||||
it('returns true for matching deadkey if caret on dk', function () {
|
||||
const tracker = new DeadkeyTracker();
|
||||
tracker.add(new Deadkey(3, 1));
|
||||
tracker.add(new Deadkey(5, 2));
|
||||
tracker.add(new Deadkey(10, 3));
|
||||
|
||||
assert.isTrue(tracker.isMatch(3, 0, 1));
|
||||
|
||||
assert.equal(tracker.dks[0].matched, 1);
|
||||
assert.equal(tracker.dks[1].matched, 0);
|
||||
assert.equal(tracker.dks[2].matched, 0);
|
||||
});
|
||||
|
||||
it('returns false for matching deadkey if already matched and resets matched flag', function () {
|
||||
const tracker = new DeadkeyTracker();
|
||||
tracker.add(new Deadkey(3, 1));
|
||||
tracker.add(new Deadkey(5, 2));
|
||||
tracker.add(new Deadkey(10, 3));
|
||||
tracker.dks[0].matched = 1;
|
||||
tracker.dks[1].matched = 1;
|
||||
tracker.dks[2].matched = 1;
|
||||
|
||||
assert.isFalse(tracker.isMatch(0, -3, 1));
|
||||
|
||||
assert.equal(tracker.dks[0].matched, 0);
|
||||
assert.equal(tracker.dks[1].matched, 0);
|
||||
assert.equal(tracker.dks[2].matched, 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue