mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-07 17:36:00 +00:00
453 lines
No EOL
13 KiB
JavaScript
453 lines
No EOL
13 KiB
JavaScript
/*
|
|
* Unit tests for common utility functions/methods.
|
|
*/
|
|
|
|
import { assert } from 'chai';
|
|
import * as models from "@keymanapp/models-templates";
|
|
import * as wordBreakers from "@keymanapp/models-wordbreakers";
|
|
|
|
describe('Tokenization functions', function() {
|
|
describe('tokenize', function() {
|
|
it('tokenizes English using defaults, pre-whitespace caret', function() {
|
|
let context = {
|
|
left: "The quick brown fox",
|
|
right: " jumped over the lazy dog",
|
|
startOfBuffer: true,
|
|
endOfBuffer: true
|
|
};
|
|
|
|
let tokenization = models.tokenize(wordBreakers.default, context);
|
|
|
|
let expectedResult = {
|
|
left: ['The', 'quick', 'brown', 'fox'],
|
|
right: ['jumped', 'over', 'the', 'lazy', 'dog'],
|
|
caretSplitsToken: false
|
|
};
|
|
|
|
assert.deepEqual(tokenization, expectedResult);
|
|
});
|
|
|
|
it('tokenizes English using defaults, pre-whitespace caret, partial context', function() {
|
|
let context = {
|
|
left: "quick brown fox", // No "The"
|
|
right: " jumped over the lazy", // No "dog"
|
|
startOfBuffer: false,
|
|
endOfBuffer: false
|
|
};
|
|
|
|
let tokenization = models.tokenize(wordBreakers.default, context);
|
|
|
|
let expectedResult = {
|
|
left: ['quick', 'brown', 'fox'],
|
|
right: ['jumped', 'over', 'the', 'lazy'],
|
|
caretSplitsToken: false
|
|
};
|
|
|
|
assert.deepEqual(tokenization, expectedResult);
|
|
});
|
|
|
|
it('tokenizes English using defaults, post-whitespace caret', function() {
|
|
let context = {
|
|
left: "The quick brown fox ",
|
|
right: "jumped over the lazy dog",
|
|
startOfBuffer: true,
|
|
endOfBuffer: true
|
|
};
|
|
|
|
let tokenization = models.tokenize(wordBreakers.default, context);
|
|
|
|
// Technically, we're editing the start of the first token on the right
|
|
// when in this context.
|
|
let expectedResult = {
|
|
left: ['The', 'quick', 'brown', 'fox', ''],
|
|
right: ['jumped', 'over', 'the', 'lazy', 'dog'],
|
|
caretSplitsToken: true
|
|
};
|
|
|
|
assert.deepEqual(tokenization, expectedResult);
|
|
});
|
|
|
|
it('tokenizes English using defaults, post-whitespace caret, partial context', function() {
|
|
let context = {
|
|
left: "quick brown fox ",
|
|
right: "jumped over the lazy",
|
|
startOfBuffer: false,
|
|
endOfBuffer: false
|
|
};
|
|
|
|
let tokenization = models.tokenize(wordBreakers.default, context);
|
|
|
|
// Technically, we're editing the start of the first token on the right
|
|
// when in this context.
|
|
let expectedResult = {
|
|
left: ['quick', 'brown', 'fox', ''],
|
|
right: ['jumped', 'over', 'the', 'lazy'],
|
|
caretSplitsToken: true
|
|
};
|
|
|
|
assert.deepEqual(tokenization, expectedResult);
|
|
});
|
|
|
|
it('tokenizes English using defaults, splitting caret, complete context (start, end == true)', function() {
|
|
let context = {
|
|
left: "The quick brown fox jum",
|
|
right: "ped over the lazy dog",
|
|
startOfBuffer: true,
|
|
endOfBuffer: true
|
|
};
|
|
|
|
let tokenization = models.tokenize(wordBreakers.default, context);
|
|
|
|
let expectedResult = {
|
|
left: ['The', 'quick', 'brown', 'fox', 'jum'],
|
|
right: ['ped', 'over', 'the', 'lazy', 'dog'],
|
|
caretSplitsToken: true
|
|
};
|
|
|
|
assert.deepEqual(tokenization, expectedResult);
|
|
});
|
|
|
|
it('tokenizes English using defaults, splitting caret, incomplete context (start, end == false)', function() {
|
|
let context = {
|
|
left: "The quick brown fox jum",
|
|
right: "ped over the lazy dog",
|
|
startOfBuffer: false,
|
|
endOfBuffer: false
|
|
};
|
|
|
|
let tokenization = models.tokenize(wordBreakers.default, context);
|
|
|
|
let expectedResult = {
|
|
left: ['The', 'quick', 'brown', 'fox', 'jum'],
|
|
right: ['ped', 'over', 'the', 'lazy', 'dog'],
|
|
caretSplitsToken: true
|
|
};
|
|
|
|
assert.deepEqual(tokenization, expectedResult);
|
|
});
|
|
|
|
it('empty context case', function() {
|
|
// Wordbreaking on a empty space => no word.
|
|
let context = {
|
|
left: '', startOfBuffer: true,
|
|
right: '', endOfBuffer: true
|
|
};
|
|
|
|
let tokenization = models.tokenize(wordBreakers.default, context);
|
|
|
|
let expectedResult = {
|
|
left: [],
|
|
right: [],
|
|
caretSplitsToken: false
|
|
};
|
|
|
|
assert.deepEqual(tokenization, expectedResult);
|
|
});
|
|
|
|
it('nil context case', function() {
|
|
// Wordbreaking on a empty space => no word.
|
|
let tokenization = models.tokenize(wordBreakers.default, null);
|
|
|
|
let expectedResult = {
|
|
left: [],
|
|
right: [],
|
|
caretSplitsToken: false
|
|
};
|
|
|
|
assert.deepEqual(tokenization, expectedResult);
|
|
});
|
|
|
|
it('near-empty context: one space before caret', function() {
|
|
// Wordbreaking on a empty space => no word.
|
|
let context = {
|
|
left: ' ', startOfBuffer: true,
|
|
right: '', endOfBuffer: true
|
|
};
|
|
|
|
let tokenization = models.tokenize(wordBreakers.default, context);
|
|
|
|
let expectedResult = {
|
|
left: [''],
|
|
right: [],
|
|
caretSplitsToken: false
|
|
};
|
|
|
|
assert.deepEqual(tokenization, expectedResult);
|
|
});
|
|
|
|
// For the next few tests: a mocked wordbreaker for Khmer, a language
|
|
// without whitespace between words.
|
|
let mockedKhmerBreaker = function(text) {
|
|
// Step 1: Build constants for spans that a real wordbreaker would return.
|
|
let srok = { // Khmer romanization of 'ស្រុក'
|
|
text: 'ស្រុក',
|
|
start: 0,
|
|
end: 5, // ្រ = ្ + រ
|
|
length: 5
|
|
};
|
|
|
|
let sro = { // Khmer romanization of 'ស្រុ'
|
|
text: 'ស្រុ',
|
|
start: 0,
|
|
end: 4,
|
|
length: 4
|
|
};
|
|
|
|
let k = { // Not the proper Khmer romanization; 'k' used here for easier readability.
|
|
text: 'ក',
|
|
start: 0,
|
|
end: 1,
|
|
length: 1
|
|
};
|
|
|
|
let khmer = { // Khmer romanization of 'ខ្មែរ'
|
|
text: 'ខ្មែរ',
|
|
start: 0,
|
|
end: 5, // ្ម = ្ + ម
|
|
length: 5
|
|
}
|
|
|
|
// Step 2: Allow shifting a defined 'constant' span without mutating the definition.
|
|
let shiftSpan = function(span, delta) {
|
|
// Avoid mutating the parameter!
|
|
let shiftedSpan = {
|
|
text: span.text,
|
|
start: span.start + delta,
|
|
end: span.end + delta,
|
|
length: span.length
|
|
};
|
|
|
|
return shiftedSpan;
|
|
}
|
|
|
|
// Step 3: Define return values for the cases we expect to need mocking.
|
|
switch(text) {
|
|
case 'ស្រុ':
|
|
return [sro];
|
|
case 'ក':
|
|
return [k];
|
|
case 'ស្រុក':
|
|
return [srok];
|
|
case 'ខ្មែរ':
|
|
return [khmer];
|
|
case 'ស្រុកខ្មែរ':
|
|
return [srok, shiftSpan(khmer, srok.length)]; // array of the two.
|
|
case 'កខ្មែរ':
|
|
// I'd admittedly be at least somewhat surprised if a real wordbreaker got this
|
|
// and similar situations perfectly right... but at least it gives us what
|
|
// we need for a test.
|
|
return [k, shiftSpan(khmer, k.length)];
|
|
default:
|
|
throw "Dummying error - no return value specified for \"" + text + "\"!";
|
|
}
|
|
}
|
|
|
|
it('tokenizes Khmer using mocked wordbreaker, caret between words', function() {
|
|
// The two words:
|
|
// - ស្រុក - 'land'
|
|
// - ខ្មែរ - 'Khmer'
|
|
// Translation: Cambodia (informal), lit: "Khmer land" / "land of [the] Khmer"
|
|
|
|
let context = {
|
|
left: "ស្រុក",
|
|
right: "ខ្មែរ",
|
|
startOfBuffer: true,
|
|
endOfBuffer: true
|
|
};
|
|
|
|
let tokenization = models.tokenize(mockedKhmerBreaker, context);
|
|
|
|
let expectedResult = {
|
|
left: ['ស្រុក'],
|
|
right: ['ខ្មែរ'],
|
|
caretSplitsToken: false
|
|
};
|
|
|
|
assert.deepEqual(tokenization, expectedResult);
|
|
});
|
|
|
|
it('tokenizes Khmer using mocked wordbreaker, caret within word', function() {
|
|
// The two words:
|
|
// - ស្រុក - 'land'
|
|
// - ខ្មែរ - 'Khmer'
|
|
// Translation: Cambodia (informal), lit: "Khmer land" / "land of [the] Khmer"
|
|
|
|
let context = {
|
|
left: "ស្រុ",
|
|
right: "កខ្មែរ",
|
|
startOfBuffer: true,
|
|
endOfBuffer: true
|
|
};
|
|
|
|
let tokenization = models.tokenize(mockedKhmerBreaker, context);
|
|
|
|
let expectedResult = {
|
|
left: ['ស្រុ'],
|
|
right: ['ក', 'ខ្មែរ'],
|
|
caretSplitsToken: true
|
|
};
|
|
|
|
assert.deepEqual(tokenization, expectedResult);
|
|
});
|
|
|
|
let midLetterNonbreaker = (text) => {
|
|
let customization = {
|
|
rules: [{
|
|
match: (context) => {
|
|
if(context.propertyMatch(null, ["ALetter"], ["MidLetter"], ["eot"])) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
},
|
|
breakIfMatch: false
|
|
}],
|
|
propertyMapping: (char) => {
|
|
let hyphens = ['\u002d', '\u2010', '\u058a', '\u30a0'];
|
|
if(hyphens.includes(char)) {
|
|
return "MidLetter";
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
};
|
|
|
|
return wordBreakers.default(text, customization);
|
|
}
|
|
|
|
it('treats caret as `eot` for pre-caret text', function() {
|
|
let context = {
|
|
left: "don-", // We use a hyphen here b/c single-quote is hardcoded.
|
|
right: " worry",
|
|
endOfBuffer: true,
|
|
startOfBuffer: true
|
|
};
|
|
|
|
let tokenization = models.tokenize(wordBreakers.default, context);
|
|
|
|
assert.deepEqual(tokenization, {
|
|
left: ["don", "-"],
|
|
right: ["worry"],
|
|
caretSplitsToken: false
|
|
});
|
|
|
|
tokenization = models.tokenize(midLetterNonbreaker, context);
|
|
|
|
assert.deepEqual(tokenization, {
|
|
left: ["don-"],
|
|
right: ["worry"],
|
|
caretSplitsToken: false
|
|
});
|
|
});
|
|
|
|
it('handles mid-contraction tokenization', function() {
|
|
let context = {
|
|
left: "don:",
|
|
right: "t worry",
|
|
endOfBuffer: true,
|
|
startOfBuffer: true
|
|
};
|
|
|
|
let tokenization = models.tokenize(wordBreakers.default, context);
|
|
|
|
assert.deepEqual(tokenization, {
|
|
left: ["don", ":"], // This particular case feels like a possible issue.
|
|
right: ["t", "worry"], // It'd be a three-way split token, as "don:t" would
|
|
// be a single token were it not for the caret in the middle.
|
|
caretSplitsToken: false
|
|
})
|
|
|
|
tokenization = models.tokenize(midLetterNonbreaker, context);
|
|
|
|
assert.deepEqual(tokenization, {
|
|
left: ["don:"],
|
|
right: ["t", "worry"],
|
|
caretSplitsToken: true
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('getLastPreCaretToken', function() {
|
|
it('with pre-whitespace caret', function() {
|
|
let context = {
|
|
left: "The quick brown fox",
|
|
right: " jumped over the lazy dog",
|
|
startOfBuffer: true,
|
|
endOfBuffer: true
|
|
};
|
|
|
|
let tokenization = models.getLastPreCaretToken(wordBreakers.default, context);
|
|
|
|
assert.equal(tokenization, 'fox');
|
|
});
|
|
|
|
it('with post-whitespace caret', function() {
|
|
let context = {
|
|
left: "The quick brown fox ",
|
|
right: "jumped over the lazy dog",
|
|
startOfBuffer: true,
|
|
endOfBuffer: true
|
|
};
|
|
|
|
let tokenization = models.getLastPreCaretToken(wordBreakers.default, context);
|
|
|
|
assert.equal(tokenization, '');
|
|
});
|
|
|
|
it('within a token', function() {
|
|
let context = {
|
|
left: "The quick brown fox jum",
|
|
right: "ped over the lazy dog",
|
|
startOfBuffer: true,
|
|
endOfBuffer: true
|
|
};
|
|
|
|
let tokenization = models.getLastPreCaretToken(wordBreakers.default, context);
|
|
|
|
assert.equal(tokenization, 'jum');
|
|
});
|
|
});
|
|
|
|
describe('wordbreak', function() {
|
|
it('with pre-whitespace caret', function() {
|
|
let context = {
|
|
left: "The quick brown fox",
|
|
right: " jumped over the lazy dog",
|
|
startOfBuffer: true,
|
|
endOfBuffer: true
|
|
};
|
|
|
|
let tokenization = models.wordbreak(wordBreakers.default, context);
|
|
|
|
assert.equal(tokenization, 'fox');
|
|
});
|
|
|
|
it('with post-whitespace caret', function() {
|
|
let context = {
|
|
left: "The quick brown fox ",
|
|
right: "jumped over the lazy dog",
|
|
startOfBuffer: true,
|
|
endOfBuffer: true
|
|
};
|
|
|
|
let tokenization = models.wordbreak(wordBreakers.default, context);
|
|
|
|
assert.equal(tokenization, '');
|
|
});
|
|
|
|
// This version is subject to change. In the future, we may wish the wordbreak
|
|
// operation to include "the rest of the word" - the post-caret part.
|
|
it('within a token', function() {
|
|
let context = {
|
|
left: "The quick brown fox jum",
|
|
right: "ped over the lazy dog",
|
|
startOfBuffer: true,
|
|
endOfBuffer: true
|
|
};
|
|
|
|
let tokenization = models.wordbreak(wordBreakers.default, context);
|
|
|
|
assert.equal(tokenization, 'jum');
|
|
});
|
|
});
|
|
}); |