1974 lines
48 KiB
C
1974 lines
48 KiB
C
#include "strans.c"
|
||
#include "cutest/cutest.h"
|
||
|
||
extern Lang testvi;
|
||
|
||
Str mkstr(char*);
|
||
int checkstr(struct ct*, char*, char*, Str*);
|
||
Str shownpre(Im*);
|
||
|
||
static int typekeys(struct ct*, char*, Str*);
|
||
|
||
void
|
||
testengineinit(int lang)
|
||
{
|
||
init();
|
||
im.l = getlang(lang);
|
||
}
|
||
|
||
void
|
||
testenginehandle(Keyreq *req)
|
||
{
|
||
imhandlekey(req);
|
||
}
|
||
|
||
void*
|
||
testengineowner(void)
|
||
{
|
||
return activeowner;
|
||
}
|
||
|
||
void
|
||
testenginepreedit(Str *preedit)
|
||
{
|
||
impre(preedit);
|
||
}
|
||
|
||
void
|
||
testenginecaret(Caret *at)
|
||
{
|
||
*at = caret;
|
||
}
|
||
|
||
void
|
||
vietnamese_state_lifetime(struct ct *t)
|
||
{
|
||
Str com;
|
||
|
||
init();
|
||
im.l = &testvi;
|
||
im.pre = mkstr("as");
|
||
im.raw = mkstr("as");
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "committed Telex", "á", &com);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
CT_EQ_INT(t, 0, im.raw.n);
|
||
|
||
init();
|
||
im.l = &testvi;
|
||
im.pre = mkstr("as");
|
||
im.raw = mkstr("as");
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
CT_EQ_INT(t, 0, com.n);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
CT_EQ_INT(t, 0, im.raw.n);
|
||
|
||
init();
|
||
im.l = &testvi;
|
||
im.pre = mkstr("as");
|
||
im.raw = mkstr("as");
|
||
sclear(&com);
|
||
CT_CHECK(t, dotrans('q', &com));
|
||
checkstr(t, "completed Telex", "á", &com);
|
||
checkstr(t, "next preedit", "q", &im.pre);
|
||
checkstr(t, "next raw input", "q", &im.raw);
|
||
}
|
||
|
||
void
|
||
engine_clears_candidates(struct ct *t)
|
||
{
|
||
Str com;
|
||
|
||
init();
|
||
im.l = getlang(LangKO);
|
||
im.pre = mkstr("ㄱ");
|
||
im.nkouho = 2;
|
||
im.sel = 1;
|
||
sclear(&com);
|
||
dotrans('k', &com);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
CT_EQ_INT(t, -1, im.sel);
|
||
}
|
||
|
||
void
|
||
engine_backspace_clears_candidates(struct ct *t)
|
||
{
|
||
Lang *lang;
|
||
Str com, shown;
|
||
|
||
lang = getlang(LangJP);
|
||
init();
|
||
im.l = lang;
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('n', 0, &com));
|
||
CT_CHECK(t, keystroke('a', 0, &com));
|
||
shown = shownpre(&im);
|
||
checkstr(t, "before backspace", "な", &shown);
|
||
im.kouho[0] = mkstr("stale-candidate");
|
||
im.nkouho = 1;
|
||
im.sel = 0;
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
checkstr(t, "backspace commit", "", &com);
|
||
checkstr(t, "pending romaji after backspace", "n", &im.raw);
|
||
shown = shownpre(&im);
|
||
checkstr(t, "shown preedit after backspace", "ん", &shown);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
CT_EQ_INT(t, -1, im.sel);
|
||
}
|
||
|
||
void
|
||
engine_selects_visible_candidate(struct ct *t)
|
||
{
|
||
static const struct { int n, sel; Rune key; char *want; } cases[] = {
|
||
{ Maxdisp, 0, '9', "c9" },
|
||
{ Maxkouho, Maxdisp, '1', "c10" },
|
||
{ Maxkouho, 2*Maxdisp, '9', "c27" },
|
||
};
|
||
char name[8];
|
||
Str com;
|
||
int i, j;
|
||
|
||
for(i = 0; i < nelem(cases); i++){
|
||
init();
|
||
for(j = 0; j < cases[i].n; j++){
|
||
snprint(name, sizeof name, "c%d", j+1);
|
||
im.kouho[j] = mkstr(name);
|
||
}
|
||
im.nkouho = cases[i].n;
|
||
im.sel = cases[i].sel;
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke(cases[i].key, 0, &com));
|
||
checkstr(t, cases[i].want, cases[i].want, &com);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
}
|
||
}
|
||
|
||
void
|
||
engine_candidate_shortcut_modifiers(struct ct *t)
|
||
{
|
||
static u32int mods[] = { Mshift, Mctrl, Malt, Msuper };
|
||
Str com, selected;
|
||
int i;
|
||
|
||
for(i = 0; i < nelem(mods); i++){
|
||
init();
|
||
im.kouho[9] = mkstr("must-not-select");
|
||
im.nkouho = 10;
|
||
im.sel = 9;
|
||
selected = im.kouho[9];
|
||
sclear(&com);
|
||
CT_CHECK(t, !keystroke('1', mods[i], &com));
|
||
CT_CHECK(t, scmp(&com, &selected) != 0);
|
||
}
|
||
}
|
||
|
||
void
|
||
engine_dictionary_queue_latest_wins(struct ct *t)
|
||
{
|
||
Channel *saved;
|
||
Dictreq old, got;
|
||
Str key;
|
||
|
||
saved = dictreqc;
|
||
dictreqc = chancreate(sizeof(Dictreq), 1);
|
||
memset(&old, 0, sizeof old);
|
||
old.key = mkstr("old-key");
|
||
old.pre = mkstr("old-preedit");
|
||
old.lang = LangJP;
|
||
old.seq = 0;
|
||
CT_CHECK(t, channbsend(dictreqc, &old) > 0);
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
im.pre = mkstr("new-preedit");
|
||
key = mkstr("new-key");
|
||
dictsend(&im, &key);
|
||
if(CT_CHECK(t, channbrecv(dictreqc, &got) > 0)){
|
||
checkstr(t, "newest dictionary key", "new-key", &got.key);
|
||
checkstr(t, "newest dictionary preedit", "new-preedit", &got.pre);
|
||
CT_EQ_INT(t, LangJP, got.lang);
|
||
CT_EQ_UINT(t, dictseq, got.seq);
|
||
}
|
||
CT_CHECK(t, channbrecv(dictreqc, &got) <= 0);
|
||
chanfree(dictreqc);
|
||
dictreqc = saved;
|
||
}
|
||
|
||
static Keyres
|
||
ownerrequestatcap(void *owner, int cap, int op, Rune key, u32int mod,
|
||
Caret *caret)
|
||
{
|
||
Channel *reply;
|
||
Keyreq req;
|
||
Keyres res;
|
||
|
||
reply = chancreate(sizeof(Keyres), 1);
|
||
memset(&req, 0, sizeof req);
|
||
req.owner = owner;
|
||
req.cap = cap;
|
||
req.op = op;
|
||
req.ks = key;
|
||
req.mod = mod;
|
||
if(caret != nil)
|
||
req.caret = *caret;
|
||
req.reply = reply;
|
||
imhandlekey(&req);
|
||
chanrecv(reply, &res);
|
||
chanfree(reply);
|
||
return res;
|
||
}
|
||
|
||
static Keyres
|
||
ownerrequestat(void *owner, int op, Rune key, u32int mod, Caret *caret)
|
||
{
|
||
return ownerrequestatcap(owner, 0, op, key, mod, caret);
|
||
}
|
||
|
||
static Keyres
|
||
ownerrequest(void *owner, int op, Rune key, u32int mod)
|
||
{
|
||
return ownerrequestat(owner, op, key, mod, nil);
|
||
}
|
||
|
||
static Keyres
|
||
ownerrequestcap(void *owner, int cap, int op, Rune key, u32int mod)
|
||
{
|
||
return ownerrequestatcap(owner, cap, op, key, mod, nil);
|
||
}
|
||
|
||
void
|
||
engine_active_owner_lifecycle(struct ct *t)
|
||
{
|
||
Keyres res;
|
||
Str shown;
|
||
char a, b, c;
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
res = ownerrequest(&a, Keypress, 'k', 0);
|
||
CT_CHECK(t, res.eaten);
|
||
res = ownerrequest(&a, Keypress, 'a', 0);
|
||
checkstr(t, "first owner preedit", "か", &res.preedit);
|
||
CT_EQ_PTR(t, &a, activeowner);
|
||
|
||
res = ownerrequest(&b, Keypress, 'n', 0);
|
||
checkstr(t, "takeover resets old preedit", "ん", &res.preedit);
|
||
CT_EQ_PTR(t, &b, activeowner);
|
||
res = ownerrequest(&a, Keyreset, 0, 0);
|
||
CT_EQ_INT(t, 0, res.preedit.n);
|
||
shown = shownpre(&im);
|
||
checkstr(t, "stale reset preserves owner", "ん", &shown);
|
||
CT_EQ_PTR(t, &b, activeowner);
|
||
|
||
res = ownerrequest(&b, Keypress, 'y', 0);
|
||
res = ownerrequest(&b, Keypress, 'a', 0);
|
||
checkstr(t, "current owner continues", "にゃ", &res.preedit);
|
||
ownerrequest(&a, Keyrelease, 0, 0);
|
||
shown = shownpre(&im);
|
||
checkstr(t, "stale release preserves owner", "にゃ", &shown);
|
||
CT_EQ_PTR(t, &b, activeowner);
|
||
|
||
ownerrequest(&b, Keyrelease, 0, 0);
|
||
CT_EQ_PTR(t, nil, activeowner);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
CT_EQ_INT(t, 0, im.raw.n);
|
||
ownerrequest(&c, Keypress, Kmodfirst, 0);
|
||
CT_EQ_PTR(t, nil, activeowner);
|
||
res = ownerrequest(&c, Keypress, 'k', 0);
|
||
CT_EQ_PTR(t, &c, activeowner);
|
||
checkstr(t, "next owner first key", "k", &res.preedit);
|
||
}
|
||
|
||
void
|
||
engine_active_owner_reset(struct ct *t)
|
||
{
|
||
Caret at;
|
||
Keyres res;
|
||
char owner;
|
||
|
||
memset(&at, 0, sizeof at);
|
||
at.valid = 1;
|
||
at.x = 10;
|
||
at.y = 20;
|
||
at.h = 14;
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
ownerrequestat(&owner, Keypress, 'k', 0, &at);
|
||
res = ownerrequest(&owner, Keypress, 'a', 0);
|
||
checkstr(t, "preedit before reset", "か", &res.preedit);
|
||
res = ownerrequest(&owner, Keyreset, 0, 0);
|
||
CT_EQ_PTR(t, &owner, activeowner);
|
||
CT_EQ_INT(t, 0, res.preedit.n);
|
||
CT_CHECK(t, !caret.valid);
|
||
res = ownerrequest(&owner, Keypress, 'n', 0);
|
||
checkstr(t, "same owner after reset", "ん", &res.preedit);
|
||
}
|
||
|
||
void
|
||
engine_rejects_stale_dictionary_results(struct ct *t)
|
||
{
|
||
Dictreq a, b;
|
||
Dictres res;
|
||
Hmap *saved;
|
||
Lang *jp;
|
||
char one, two;
|
||
int n;
|
||
|
||
jp = getlang(LangJP);
|
||
saved = jp->dict;
|
||
jp->dict = hmapalloc(1);
|
||
init();
|
||
im.l = jp;
|
||
while(channbrecv(dictreqc, &a) > 0)
|
||
;
|
||
ownerrequest(&one, Keypress, 'k', 0);
|
||
ownerrequest(&one, Keypress, 'a', 0);
|
||
n = 0;
|
||
while(channbrecv(dictreqc, &a) > 0)
|
||
n++;
|
||
if(!CT_CHECK(t, n > 0))
|
||
goto cleanup;
|
||
ownerrequest(&one, Keyreset, 0, 0);
|
||
ownerrequest(&two, Keypress, 'k', 0);
|
||
ownerrequest(&two, Keypress, 'a', 0);
|
||
n = 0;
|
||
while(channbrecv(dictreqc, &b) > 0)
|
||
n++;
|
||
if(!CT_CHECK(t, n > 0))
|
||
goto cleanup;
|
||
CT_EQ_INT(t, 0, scmp(&a.key, &b.key));
|
||
CT_EQ_INT(t, 0, scmp(&a.pre, &b.pre));
|
||
CT_EQ_INT(t, a.lang, b.lang);
|
||
CT_CHECK(t, a.seq != b.seq);
|
||
|
||
memset(&res, 0, sizeof res);
|
||
res.key = a.pre;
|
||
res.lang = a.lang;
|
||
res.seq = a.seq;
|
||
res.kouho[0] = mkstr("stale");
|
||
res.nkouho = 1;
|
||
dictresult(&res);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
|
||
res.key = b.pre;
|
||
res.lang = b.lang;
|
||
res.seq = b.seq;
|
||
res.kouho[0] = mkstr("current");
|
||
dictresult(&res);
|
||
if(CT_EQ_INT(t, 1, im.nkouho))
|
||
checkstr(t, "current dictionary result", "current", &im.kouho[0]);
|
||
cleanup:
|
||
while(channbrecv(dictreqc, &a) > 0)
|
||
;
|
||
hmapfree(jp->dict);
|
||
jp->dict = saved;
|
||
}
|
||
|
||
void
|
||
engine_active_owner_caret(struct ct *t)
|
||
{
|
||
Caret a, moved;
|
||
char one, two;
|
||
|
||
memset(&a, 0, sizeof a);
|
||
a.valid = 1;
|
||
a.x = 10;
|
||
a.y = 20;
|
||
a.h = 14;
|
||
moved = a;
|
||
moved.x = 80;
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
ownerrequestat(&one, Keypress, 'k', 0, &a);
|
||
CT_CHECK(t, caret.valid);
|
||
CT_EQ_INT(t, 10, caret.x);
|
||
ownerrequestat(&two, Keycaret, 0, 0, &moved);
|
||
CT_EQ_INT(t, 10, caret.x);
|
||
ownerrequestat(&one, Keycaret, 0, 0, &moved);
|
||
CT_EQ_INT(t, 80, caret.x);
|
||
ownerrequest(&two, Keypress, 'n', 0);
|
||
CT_EQ_PTR(t, &two, activeowner);
|
||
CT_CHECK(t, !caret.valid);
|
||
}
|
||
|
||
void
|
||
engine_commit_contract(struct ct *t)
|
||
{
|
||
Str com;
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
sclear(&com);
|
||
if(!typekeys(t, "ka", &com))
|
||
return;
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "return commit", "か", &com);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
CT_EQ_INT(t, 0, im.raw.n);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
CT_EQ_INT(t, -1, im.sel);
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
sclear(&com);
|
||
if(!typekeys(t, "ka", &com))
|
||
return;
|
||
im.kouho[0] = mkstr("c1");
|
||
im.kouho[1] = mkstr("c2");
|
||
im.nkouho = 2;
|
||
im.sel = 1;
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "selected candidate", "c2", &com);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
CT_EQ_INT(t, -1, im.sel);
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
sclear(&com);
|
||
if(!typekeys(t, "ka", &com))
|
||
return;
|
||
im.nkouho = 1;
|
||
im.sel = -1;
|
||
CT_CHECK(t, !keystroke('q', 0, &com));
|
||
checkstr(t, "uneaten-key commit", "か", &com);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
CT_EQ_INT(t, 0, im.raw.n);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
CT_EQ_INT(t, -1, im.sel);
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
sclear(&com);
|
||
if(!typekeys(t, "ka", &com))
|
||
return;
|
||
CT_CHECK(t, keystroke(0xf008, 0, &com));
|
||
CT_EQ_INT(t, 2, com.n);
|
||
CT_EQ_UINT(t, 0x304b, com.r[0]);
|
||
CT_EQ_UINT(t, 0xf008, com.r[1]);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
}
|
||
|
||
void
|
||
engine_telex_history_bound(struct ct *t)
|
||
{
|
||
Str com;
|
||
Rune tone;
|
||
int i;
|
||
|
||
init();
|
||
im.l = &testvi;
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('a', 0, &com));
|
||
for(i = 0; i < Maxrunes; i++){
|
||
tone = i % 2 == 0 ? 's' : 'f';
|
||
if(!CT_CHECK(t, keystroke(tone, 0, &com)))
|
||
break;
|
||
}
|
||
checkstr(t, "bounded Telex commit", "à", &com);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
CT_EQ_INT(t, 0, im.raw.n);
|
||
}
|
||
|
||
void
|
||
engine_korean_modifiers_and_backspace(struct ct *t)
|
||
{
|
||
static const struct {
|
||
Rune key;
|
||
char *plain;
|
||
char *shifted;
|
||
} cases[] = {
|
||
{ 'r', "ㄱ", "ㄲ" }, { 'e', "ㄷ", "ㄸ" },
|
||
{ 'q', "ㅂ", "ㅃ" }, { 't', "ㅅ", "ㅆ" },
|
||
{ 'w', "ㅈ", "ㅉ" }, { 'o', "ㅐ", "ㅒ" },
|
||
{ 'p', "ㅔ", "ㅖ" },
|
||
};
|
||
Str com;
|
||
int i;
|
||
|
||
for(i = 0; i < nelem(cases); i++){
|
||
init();
|
||
im.l = getlang(LangKO);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke(cases[i].key, 0, &com));
|
||
checkstr(t, "plain Korean key", cases[i].plain, &im.pre);
|
||
|
||
init();
|
||
im.l = getlang(LangKO);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke(cases[i].key, Mshift, &com));
|
||
checkstr(t, "shifted Korean key", cases[i].shifted, &im.pre);
|
||
|
||
init();
|
||
im.l = getlang(LangKO);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke(cases[i].key - 'a' + 'A', 0, &com));
|
||
checkstr(t, "Caps Lock Korean key", cases[i].plain, &im.pre);
|
||
|
||
init();
|
||
im.l = getlang(LangKO);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke(cases[i].key, Mshift, &com));
|
||
checkstr(t, "Shift+Caps Korean key", cases[i].shifted, &im.pre);
|
||
}
|
||
|
||
init();
|
||
im.l = getlang(LangKO);
|
||
sclear(&com);
|
||
if(typekeys(t, "rkr", &com)){
|
||
checkstr(t, "Korean engine preedit", "각", &im.pre);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
checkstr(t, "Korean final backspace", "가", &im.pre);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
checkstr(t, "Korean vowel backspace", "ㄱ", &im.pre);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
CT_CHECK(t, !keystroke(Kback, 0, &com));
|
||
}
|
||
}
|
||
|
||
void
|
||
engine_direct_language_modes(struct ct *t)
|
||
{
|
||
Str com;
|
||
|
||
init();
|
||
sclear(&com);
|
||
CT_EQ_INT(t, LangEN, im.l->lang);
|
||
CT_EQ_PTR(t, nil, im.l->mapname);
|
||
CT_EQ_PTR(t, nil, im.l->map);
|
||
CT_EQ_PTR(t, nil, im.l->trans);
|
||
CT_CHECK(t, !keystroke('a', 0, &com));
|
||
CT_CHECK(t, !keystroke('Z', Mshift, &com));
|
||
checkstr(t, "English direct commit", "", &com);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
CT_EQ_INT(t, 0, im.raw.n);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
CT_CHECK(t, search.lang);
|
||
CT_EQ_INT(t, LangEN, im.l->lang);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
CT_CHECK(t, !search.lang);
|
||
CT_CHECK(t, keystroke('s', Mctrl, &com));
|
||
CT_EQ_INT(t, LangKO, im.l->lang);
|
||
CT_EQ_PTR(t, nil, im.l->mapname);
|
||
CT_EQ_PTR(t, nil, im.l->map);
|
||
CT_EQ_PTR(t, nil, im.l->dictname);
|
||
CT_EQ_PTR(t, nil, im.l->dictq);
|
||
CT_CHECK(t, strcmp(getlang(LangHANJA)->dictname, "hanja") == 0);
|
||
CT_CHECK(t, keystroke('r', 0, &com));
|
||
checkstr(t, "Korean without map", "ㄱ", &im.pre);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
}
|
||
|
||
void
|
||
engine_language_switch_state(struct ct *t)
|
||
{
|
||
static const struct {
|
||
char *name;
|
||
Rune key;
|
||
int mod;
|
||
int lang;
|
||
char *pre;
|
||
char *raw;
|
||
char *commit;
|
||
int stale;
|
||
} steps[] = {
|
||
{ "select Vietnamese", 'v', Mctrl, LangVI, "", "", "", 0 },
|
||
{ "Vietnamese a", 'a', 0, LangVI, "a", "a", "", 0 },
|
||
{ "Vietnamese tone", 's', 0, LangVI, "as", "as", "", 0 },
|
||
{ "switch to Korean", 's', Mctrl, LangKO, "", "", "", 1 },
|
||
{ "Korean initial", 'r', 0, LangKO, "ㄱ", "", "", 0 },
|
||
{ "Korean syllable", 'k', 0, LangKO, "가", "", "", 0 },
|
||
{ "switch to Vietnamese", 'v', Mctrl, LangVI, "", "", "", 0 },
|
||
{ "Vietnamese a again", 'a', 0, LangVI, "a", "a", "", 0 },
|
||
{ "Vietnamese tone again", 's', 0, LangVI, "as", "as", "", 0 },
|
||
{ "commit Vietnamese", Kret, 0, LangVI, "", "", "á", 0 },
|
||
};
|
||
char where[80];
|
||
Lang *vi;
|
||
Trie *saved;
|
||
Str com;
|
||
int eaten, i;
|
||
|
||
vi = getlang(LangVI);
|
||
if(!CT_CHECK(t, vi != nil))
|
||
return;
|
||
saved = vi->map;
|
||
vi->map = testvi.map;
|
||
init();
|
||
for(i = 0; i < nelem(steps); i++){
|
||
if(steps[i].stale){
|
||
im.kouho[0] = mkstr("stale");
|
||
im.nkouho = 1;
|
||
im.sel = 0;
|
||
}
|
||
sclear(&com);
|
||
eaten = keystroke(steps[i].key, steps[i].mod, &com);
|
||
if(eaten != 1){
|
||
CT_ERRORF(t, "%s: want eaten 1, got %d",
|
||
steps[i].name, eaten);
|
||
break;
|
||
}
|
||
if(im.l == nil || im.l->lang != steps[i].lang){
|
||
CT_ERRORF(t, "%s: want language %d, got %d",
|
||
steps[i].name, steps[i].lang,
|
||
im.l == nil ? -1 : im.l->lang);
|
||
break;
|
||
}
|
||
snprint(where, sizeof where, "%s preedit", steps[i].name);
|
||
if(!checkstr(t, where, steps[i].pre, &im.pre))
|
||
break;
|
||
snprint(where, sizeof where, "%s raw", steps[i].name);
|
||
if(!checkstr(t, where, steps[i].raw, &im.raw))
|
||
break;
|
||
snprint(where, sizeof where, "%s commit", steps[i].name);
|
||
if(!checkstr(t, where, steps[i].commit, &com))
|
||
break;
|
||
if(im.nkouho != 0 || im.sel != -1){
|
||
CT_ERRORF(t, "%s: want candidates 0 and selection -1, got %d and %d",
|
||
steps[i].name, im.nkouho, im.sel);
|
||
break;
|
||
}
|
||
}
|
||
vi->map = saved;
|
||
}
|
||
|
||
typedef struct Searchfix Searchfix;
|
||
struct Searchfix
|
||
{
|
||
Im im;
|
||
Search search;
|
||
Lang *dictlang;
|
||
Hmap *dict;
|
||
int activecap;
|
||
int visible;
|
||
};
|
||
|
||
static int
|
||
draindraw(Drawcmd *last)
|
||
{
|
||
Drawcmd dc = {0};
|
||
int n;
|
||
|
||
n = 0;
|
||
while(channbrecv(drawc, &dc) > 0){
|
||
n++;
|
||
if(last != nil)
|
||
*last = dc;
|
||
}
|
||
return n;
|
||
}
|
||
|
||
static void
|
||
setcandidates(int n)
|
||
{
|
||
Dictres res;
|
||
char name[8];
|
||
int i;
|
||
|
||
memset(&res, 0, sizeof res);
|
||
for(i = 0; i < n; i++){
|
||
snprint(name, sizeof name, "c%d", i+1);
|
||
res.kouho[i] = mkstr(name);
|
||
}
|
||
res.nkouho = n;
|
||
setkouho(&res);
|
||
}
|
||
|
||
static void
|
||
candidatebegin(int n)
|
||
{
|
||
init();
|
||
draindraw(nil);
|
||
setcandidates(n);
|
||
}
|
||
|
||
static int
|
||
checkcandidatepage(struct ct *t, char *where, int first, int n, int sel)
|
||
{
|
||
Drawcmd dc;
|
||
char want[8];
|
||
|
||
show();
|
||
if(!CT_CHECK(t, draindraw(&dc) > 0))
|
||
return 0;
|
||
if(!CT_EQ_INT(t, n, dc.nkouho) || !CT_EQ_INT(t, sel, dc.sel) ||
|
||
!CT_EQ_INT(t, first, dc.first) ||
|
||
!CT_EQ_INT(t, im.nkouho, dc.total))
|
||
return 0;
|
||
if(n == 0)
|
||
return 1;
|
||
snprint(want, sizeof want, "c%d", first+1);
|
||
if(!checkstr(t, where, want, &dc.kouho[0]))
|
||
return 0;
|
||
snprint(want, sizeof want, "c%d", first+n);
|
||
return checkstr(t, where, want, &dc.kouho[n-1]);
|
||
}
|
||
|
||
void
|
||
engine_candidate_page_metadata(struct ct *t)
|
||
{
|
||
static const struct {
|
||
int total;
|
||
int sel;
|
||
int first;
|
||
int shown;
|
||
int local;
|
||
} cases[] = {
|
||
{ 9, 8, 0, 9, 8 },
|
||
{ 10, 8, 0, 9, 8 },
|
||
{ 10, 9, 9, 1, 0 },
|
||
{ 18, 8, 0, 9, 8 },
|
||
{ 18, 17, 9, 9, 8 },
|
||
{ 32, 8, 0, 9, 8 },
|
||
{ 32, 17, 9, 9, 8 },
|
||
{ 32, 26, 18, 9, 8 },
|
||
{ 32, 31, 27, 5, 4 },
|
||
};
|
||
Drawcmd dc;
|
||
int i;
|
||
|
||
/* A clearing draw is the observable zero-candidate snapshot. */
|
||
candidatebegin(1);
|
||
show();
|
||
draindraw(nil);
|
||
setcandidates(0);
|
||
show();
|
||
if(CT_CHECK(t, draindraw(&dc) > 0)){
|
||
CT_EQ_INT(t, 0, dc.nkouho);
|
||
CT_EQ_INT(t, -1, dc.sel);
|
||
CT_EQ_INT(t, 0, dc.first);
|
||
CT_EQ_INT(t, 0, dc.total);
|
||
}
|
||
|
||
for(i = 0; i < nelem(cases); i++){
|
||
candidatebegin(cases[i].total);
|
||
im.sel = cases[i].sel;
|
||
checkcandidatepage(t, "candidate page metadata",
|
||
cases[i].first, cases[i].shown, cases[i].local);
|
||
}
|
||
}
|
||
|
||
void
|
||
engine_candidate_page_snapshots(struct ct *t)
|
||
{
|
||
static int totals[] = { 0, 1, 9, 10, 18, 19, 32 };
|
||
int i, n;
|
||
|
||
for(i = 0; i < nelem(totals); i++){
|
||
candidatebegin(totals[i]);
|
||
CT_EQ_INT(t, totals[i] == 0 ? -1 : 0, im.sel);
|
||
if(totals[i] == 0){
|
||
show();
|
||
CT_EQ_INT(t, 0, draindraw(nil));
|
||
continue;
|
||
}
|
||
n = totals[i] < Maxdisp ? totals[i] : Maxdisp;
|
||
checkcandidatepage(t, "default candidate page", 0, n, 0);
|
||
}
|
||
}
|
||
|
||
void
|
||
engine_candidate_page_movement(struct ct *t)
|
||
{
|
||
static const struct {
|
||
int n;
|
||
int sel;
|
||
Rune key;
|
||
int want;
|
||
} cases[] = {
|
||
{ 1, 0, Kup, 0 },
|
||
{ 9, 8, Kdown, 8 },
|
||
{ 10, 8, Kdown, 9 },
|
||
{ 10, 9, Kup, 8 },
|
||
{ 18, 8, Kpgdown, 17 },
|
||
{ 19, 17, Kdown, 18 },
|
||
{ 19, 18, Kup, 17 },
|
||
{ 32, 26, Kdown, 27 },
|
||
{ 32, 27, Kup, 26 },
|
||
{ 32, 0, Kpgup, 0 },
|
||
{ 32, 9, Kpgup, 0 },
|
||
{ 32, 22, Kpgdown, 31 },
|
||
{ 32, 31, Kpgup, 22 },
|
||
{ 32, 31, Kpgdown, 31 },
|
||
};
|
||
int first, i, n;
|
||
Str com;
|
||
|
||
for(i = 0; i < nelem(cases); i++){
|
||
candidatebegin(cases[i].n);
|
||
im.sel = cases[i].sel;
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke(cases[i].key, 0, &com));
|
||
CT_EQ_INT(t, cases[i].want, im.sel);
|
||
first = cases[i].want / Maxdisp * Maxdisp;
|
||
n = cases[i].n - first;
|
||
if(n > Maxdisp)
|
||
n = Maxdisp;
|
||
checkcandidatepage(t, "moved candidate page", first, n,
|
||
cases[i].want-first);
|
||
}
|
||
}
|
||
|
||
void
|
||
engine_candidate_completion(struct ct *t)
|
||
{
|
||
Str com;
|
||
|
||
candidatebegin(2);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "default candidate", "c1", &com);
|
||
|
||
candidatebegin(2);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke(Ktab, 0, &com));
|
||
checkstr(t, "ordinary Tab candidate", "c1", &com);
|
||
|
||
candidatebegin(2);
|
||
im.pre = mkstr("reading");
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('0', 0, &com));
|
||
checkstr(t, "ordinary zero reading", "reading", &com);
|
||
|
||
candidatebegin(0);
|
||
im.pre = mkstr("reading");
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "Enter without candidates", "reading", &com);
|
||
|
||
candidatebegin(32);
|
||
im.sel = 27;
|
||
sclear(&com);
|
||
CT_CHECK(t, !keystroke('9', 0, &com));
|
||
checkstr(t, "unavailable short-page digit", "", &com);
|
||
CT_EQ_INT(t, 32, im.nkouho);
|
||
CT_EQ_INT(t, 27, im.sel);
|
||
}
|
||
|
||
static void
|
||
setdict(Hmap **dict, char *key, char *val)
|
||
{
|
||
Str s;
|
||
|
||
s = mkstr(key);
|
||
hmapset(dict, &s, val, strlen(val));
|
||
}
|
||
|
||
static void
|
||
searchsave(Searchfix *f, int lang)
|
||
{
|
||
f->im = im;
|
||
f->search = search;
|
||
f->dictlang = getlang(lang);
|
||
f->dict = f->dictlang->dict;
|
||
f->activecap = activecap;
|
||
f->visible = visible;
|
||
draindraw(nil);
|
||
}
|
||
|
||
static void
|
||
emojibegin(Searchfix *f, int showpre)
|
||
{
|
||
searchsave(f, LangEMOJI);
|
||
f->dictlang->dict = hmapalloc(32);
|
||
setdict(&f->dictlang->dict, "a", "A B");
|
||
setdict(&f->dictlang->dict, "ㅁ", "B C");
|
||
setdict(&f->dictlang->dict, "alpha", "α");
|
||
setdict(&f->dictlang->dict, "smil", "😀 😄");
|
||
setdict(&f->dictlang->dict, "smile", "😀 😄");
|
||
setdict(&f->dictlang->dict, "웃음", "😀 😄 😂");
|
||
setdict(&f->dictlang->dict, "えがお", "😀 😄 😊");
|
||
setdict(&f->dictlang->dict, "エガオ", "😀 😄 😊");
|
||
setdict(&f->dictlang->dict, "heart", "❤️");
|
||
setdict(&f->dictlang->dict, "coffee", "☕️");
|
||
setdict(&f->dictlang->dict, "^", "¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ⁽");
|
||
setdict(&f->dictlang->dict, "^0", "⁰");
|
||
setdict(&f->dictlang->dict, "_", "₁ ₂ ₃ ₄ ₅ ₆ ₇ ₈ ₉ ₍");
|
||
setdict(&f->dictlang->dict, "<", "← ≤ ♥ ≠");
|
||
setdict(&f->dictlang->dict, "many",
|
||
"c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12");
|
||
init();
|
||
im.l = getlang(LangKO);
|
||
activecap = showpre ? 0 : Cclientpreedit;
|
||
}
|
||
|
||
static void
|
||
searchend(Searchfix *f)
|
||
{
|
||
Hmap *dict;
|
||
|
||
dict = f->dictlang->dict;
|
||
f->dictlang->dict = f->dict;
|
||
hmapfree(dict);
|
||
draindraw(nil);
|
||
im = f->im;
|
||
search = f->search;
|
||
activecap = f->activecap;
|
||
visible = f->visible;
|
||
}
|
||
|
||
void
|
||
engine_popup_preedit_capability(struct ct *t)
|
||
{
|
||
Dictres res;
|
||
Drawcmd dc;
|
||
Keyres kres;
|
||
char client, inactive;
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
draindraw(nil);
|
||
ownerrequestcap(&client, Cclientpreedit, Keypress, 'k', 0);
|
||
ownerrequestcap(&client, Cclientpreedit, Keypress, 'a', 0);
|
||
memset(&res, 0, sizeof res);
|
||
res.lang = LangJP;
|
||
res.key = mkstr("か");
|
||
res.seq = dictseq;
|
||
res.kouho[0] = mkstr("家");
|
||
res.nkouho = 1;
|
||
dictresult(&res);
|
||
im.sel = 0;
|
||
show();
|
||
if(CT_CHECK(t, draindraw(&dc) > 0)){
|
||
CT_EQ_INT(t, 0, dc.pre.n);
|
||
CT_EQ_INT(t, 1, dc.nkouho);
|
||
CT_EQ_INT(t, 0, dc.sel);
|
||
checkstr(t, "client-preedit candidate", "家", &dc.kouho[0]);
|
||
}
|
||
|
||
kres = ownerrequestcap(&client, 0, Keycap, 0, 0);
|
||
CT_CHECK(t, kres.eaten);
|
||
checkstr(t, "capability response preedit", "か", &kres.preedit);
|
||
if(CT_CHECK(t, draindraw(&dc) > 0)){
|
||
checkstr(t, "popup preedit", "か", &dc.pre);
|
||
CT_EQ_INT(t, 1, dc.nkouho);
|
||
CT_EQ_INT(t, 0, dc.sel);
|
||
checkstr(t, "popup candidate", "家", &dc.kouho[0]);
|
||
}
|
||
CT_EQ_INT(t, 1, im.nkouho);
|
||
CT_EQ_INT(t, 0, im.sel);
|
||
checkstr(t, "retained popup candidate", "家", &im.kouho[0]);
|
||
|
||
kres = ownerrequestcap(&client, 0, Keycap, 0, 0);
|
||
CT_CHECK(t, kres.eaten);
|
||
CT_EQ_INT(t, 0, draindraw(nil));
|
||
|
||
kres = ownerrequestcap(&inactive, Cclientpreedit, Keycap, 0, 0);
|
||
CT_CHECK(t, !kres.eaten);
|
||
CT_EQ_INT(t, 0, kres.preedit.n);
|
||
CT_EQ_PTR(t, &client, activeowner);
|
||
CT_EQ_INT(t, 0, activecap & Cclientpreedit);
|
||
CT_EQ_INT(t, 0, draindraw(nil));
|
||
|
||
kres = ownerrequestcap(&client, Cclientpreedit, Keycap, 0, 0);
|
||
CT_CHECK(t, kres.eaten);
|
||
checkstr(t, "restored client preedit", "か", &kres.preedit);
|
||
if(CT_CHECK(t, draindraw(&dc) > 0)){
|
||
CT_EQ_INT(t, 0, dc.pre.n);
|
||
CT_EQ_INT(t, 1, dc.nkouho);
|
||
CT_EQ_INT(t, 0, dc.sel);
|
||
checkstr(t, "restored client candidate", "家", &dc.kouho[0]);
|
||
}
|
||
CT_EQ_INT(t, 1, im.nkouho);
|
||
CT_EQ_INT(t, 0, im.sel);
|
||
checkstr(t, "retained client candidate", "家", &im.kouho[0]);
|
||
kres = ownerrequestcap(&client, Cclientpreedit, Keycap, 0, 0);
|
||
CT_CHECK(t, kres.eaten);
|
||
CT_EQ_INT(t, 0, draindraw(nil));
|
||
|
||
ownerrequest(&client, Keyrelease, 0, 0);
|
||
draindraw(nil);
|
||
}
|
||
|
||
static void
|
||
hanjabegin(Searchfix *f, int lang)
|
||
{
|
||
searchsave(f, LangHANJA);
|
||
f->dictlang->dict = hmapalloc(8);
|
||
setdict(&f->dictlang->dict, "한", "漢 韓");
|
||
setdict(&f->dictlang->dict, "가", "家");
|
||
setdict(&f->dictlang->dict, "ㄱ", "假");
|
||
setdict(&f->dictlang->dict, "한글", "文");
|
||
setdict(&f->dictlang->dict, "か", "仮");
|
||
init();
|
||
im.l = getlang(lang);
|
||
}
|
||
|
||
static int
|
||
typekeys(struct ct *t, char *keys, Str *com)
|
||
{
|
||
for(; *keys != '\0'; keys++)
|
||
if(!CT_CHECK(t, keystroke((uchar)*keys, 0, com)))
|
||
return 0;
|
||
return 1;
|
||
}
|
||
|
||
void
|
||
engine_japanese_readings(struct ct *t)
|
||
{
|
||
static const struct {
|
||
char *keys;
|
||
char *want;
|
||
} cases[] = {
|
||
{ "nya", "にゃ" },
|
||
{ "n'ya", "んや" },
|
||
{ "nnya", "んにゃ" },
|
||
{ "kin'youbi", "きんようび" },
|
||
};
|
||
Str com, shown;
|
||
int i;
|
||
|
||
for(i = 0; i < nelem(cases); i++){
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
sclear(&com);
|
||
if(!typekeys(t, cases[i].keys, &com))
|
||
continue;
|
||
checkstr(t, cases[i].keys, "", &com);
|
||
shown = shownpre(&im);
|
||
checkstr(t, cases[i].keys, cases[i].want, &shown);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "committed reading", cases[i].want, &com);
|
||
}
|
||
}
|
||
|
||
static int
|
||
newestrequest(Dictreq *req)
|
||
{
|
||
int n;
|
||
|
||
n = 0;
|
||
while(channbrecv(dictreqc, req) > 0)
|
||
n++;
|
||
return n;
|
||
}
|
||
|
||
void
|
||
engine_japanese_candidates(struct ct *t)
|
||
{
|
||
Dictreq req;
|
||
Dictres res;
|
||
Hmap *saved;
|
||
Lang *jp;
|
||
Str com, key, shown;
|
||
|
||
jp = getlang(LangJP);
|
||
saved = jp->dict;
|
||
jp->dict = hmapalloc(8);
|
||
key = mkstr("かんじ");
|
||
hmapset(&jp->dict, &key, "漢字 幹事", strlen("漢字 幹事"));
|
||
init();
|
||
im.l = jp;
|
||
sclear(&com);
|
||
if(!typekeys(t, "kanji", &com))
|
||
goto cleanup;
|
||
shown = shownpre(&im);
|
||
checkstr(t, "complete Japanese reading", "かんじ", &shown);
|
||
if(!CT_CHECK(t, newestrequest(&req) > 0))
|
||
goto cleanup;
|
||
checkstr(t, "dictionary lookup key", "かんじ", &req.key);
|
||
checkstr(t, "dictionary request identity", "かんじ", &req.pre);
|
||
dictlookup(&req, &res);
|
||
dictresult(&res);
|
||
if(!CT_EQ_INT(t, 2, im.nkouho))
|
||
goto cleanup;
|
||
CT_EQ_INT(t, 0, im.sel);
|
||
checkstr(t, "candidate one", "漢字", &im.kouho[0]);
|
||
checkstr(t, "candidate two", "幹事", &im.kouho[1]);
|
||
CT_CHECK(t, keystroke(Kdown, 0, &com));
|
||
CT_EQ_INT(t, 1, im.sel);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "selected Kanji", "幹事", &com);
|
||
cleanup:
|
||
newestrequest(&req);
|
||
hmapfree(jp->dict);
|
||
jp->dict = saved;
|
||
}
|
||
|
||
void
|
||
engine_japanese_backspace_and_boundaries(struct ct *t)
|
||
{
|
||
Str com, shown;
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
sclear(&com);
|
||
if(typekeys(t, "kanji", &com)){
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
shown = shownpre(&im);
|
||
checkstr(t, "backspace pending i", "かんj", &shown);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
shown = shownpre(&im);
|
||
checkstr(t, "backspace pending j", "かん", &shown);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
shown = shownpre(&im);
|
||
checkstr(t, "backspace accumulated kana", "か", &shown);
|
||
}
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
sclear(&com);
|
||
if(typekeys(t, "kanji", &com)){
|
||
CT_CHECK(t, keystroke('k', Mctrl, &com));
|
||
checkstr(t, "Hiragana mode boundary", "かんじ", &com);
|
||
CT_EQ_INT(t, LangJPK, im.l->lang);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
CT_EQ_INT(t, 0, im.raw.n);
|
||
sclear(&com);
|
||
if(typekeys(t, "ka", &com)){
|
||
CT_CHECK(t, keystroke('n', Mctrl, &com));
|
||
checkstr(t, "Katakana mode boundary", "カ", &com);
|
||
CT_EQ_INT(t, LangJP, im.l->lang);
|
||
}
|
||
}
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
sclear(&com);
|
||
if(typekeys(t, "kanji", &com)){
|
||
CT_CHECK(t, keystroke(' ', 0, &com));
|
||
checkstr(t, "real input boundary", "かんじ ", &com);
|
||
}
|
||
}
|
||
|
||
void
|
||
engine_katakana_sequences(struct ct *t)
|
||
{
|
||
static const struct {
|
||
char *keys;
|
||
char *want;
|
||
} cases[] = {
|
||
{ "kitte", "キッテ" },
|
||
{ "katta", "カッタ" },
|
||
{ "gakkou", "ガッコウ" },
|
||
{ "matcha", "マッチャ" },
|
||
{ "xu", "ゥ" },
|
||
{ "vu", "ヴ" },
|
||
};
|
||
Dictreq req;
|
||
Str com, shown;
|
||
int i;
|
||
|
||
newestrequest(&req);
|
||
for(i = 0; i < nelem(cases); i++){
|
||
init();
|
||
im.l = getlang(LangJPK);
|
||
sclear(&com);
|
||
if(!typekeys(t, cases[i].keys, &com))
|
||
continue;
|
||
checkstr(t, cases[i].keys, "", &com);
|
||
shown = shownpre(&im);
|
||
checkstr(t, cases[i].keys, cases[i].want, &shown);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "committed Katakana", cases[i].want, &com);
|
||
}
|
||
CT_EQ_INT(t, 0, newestrequest(&req));
|
||
CT_EQ_PTR(t, nil, getlang(LangJPK)->dictname);
|
||
}
|
||
|
||
void
|
||
engine_emoji_single_candidate(struct ct *t)
|
||
{
|
||
Drawcmd dc = {0};
|
||
Searchfix f;
|
||
Str com;
|
||
|
||
emojibegin(&f, 0);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
CT_CHECK(t, search.lang);
|
||
CT_EQ_INT(t, LangKO, im.l->lang);
|
||
if(typekeys(t, "alpha", &com)){
|
||
checkstr(t, "no automatic commit", "", &com);
|
||
checkstr(t, "raw English query", "alpha", &search.text);
|
||
CT_CHECK(t, search.lang);
|
||
CT_EQ_INT(t, 1, im.nkouho);
|
||
checkstr(t, "single candidate", "α", &im.kouho[0]);
|
||
CT_CHECK(t, draindraw(&dc) > 0);
|
||
CT_EQ_INT(t, 0, dc.pre.n);
|
||
CT_EQ_INT(t, 1, dc.nkouho);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "explicit candidate commit", "α", &com);
|
||
CT_CHECK(t, !search.lang);
|
||
CT_EQ_INT(t, LangKO, im.l->lang);
|
||
show();
|
||
CT_CHECK(t, draindraw(&dc) > 0);
|
||
CT_EQ_INT(t, 0, dc.pre.n);
|
||
CT_EQ_INT(t, 0, dc.nkouho);
|
||
}
|
||
searchend(&f);
|
||
}
|
||
|
||
void
|
||
engine_emoji_queries(struct ct *t)
|
||
{
|
||
Drawcmd dc = {0};
|
||
Searchfix f;
|
||
Str com;
|
||
|
||
emojibegin(&f, 1);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
CT_CHECK(t, keystroke('a', 0, &com));
|
||
checkstr(t, "raw query wins", "a", &search.text);
|
||
CT_EQ_INT(t, 3, im.nkouho);
|
||
checkstr(t, "raw candidate", "A", &im.kouho[0]);
|
||
checkstr(t, "deduplicated candidate", "B", &im.kouho[1]);
|
||
checkstr(t, "local candidate", "C", &im.kouho[2]);
|
||
CT_CHECK(t, draindraw(&dc) > 0);
|
||
checkstr(t, "prefix popup query", "a", &dc.pre);
|
||
CT_EQ_INT(t, 3, dc.nkouho);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "smile", &com)){
|
||
checkstr(t, "English alias", "smile", &search.text);
|
||
CT_EQ_INT(t, 2, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
checkstr(t, "backspaced query", "smil", &search.text);
|
||
CT_EQ_INT(t, 2, im.nkouho);
|
||
CT_CHECK(t, keystroke('e', 0, &com));
|
||
CT_CHECK(t, keystroke('2', 0, &com));
|
||
checkstr(t, "selected English alias", "😄", &com);
|
||
CT_EQ_INT(t, LangKO, im.l->lang);
|
||
}
|
||
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "SMILE", &com)){
|
||
checkstr(t, "folded English alias", "smile", &search.text);
|
||
CT_EQ_INT(t, 2, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
}
|
||
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "dntdma", &com)){
|
||
checkstr(t, "localized Korean query", "웃음", &search.text);
|
||
CT_EQ_INT(t, 3, im.nkouho);
|
||
CT_CHECK(t, draindraw(&dc) > 0);
|
||
checkstr(t, "popup query", "웃음", &dc.pre);
|
||
CT_EQ_INT(t, 3, dc.nkouho);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
checkstr(t, "cancel commit", "", &com);
|
||
CT_CHECK(t, !search.lang);
|
||
CT_EQ_INT(t, LangKO, im.l->lang);
|
||
}
|
||
searchend(&f);
|
||
}
|
||
|
||
void
|
||
engine_emoji_japanese_and_multirune(struct ct *t)
|
||
{
|
||
Searchfix f;
|
||
Str com;
|
||
|
||
emojibegin(&f, 0);
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "egao", &com)){
|
||
checkstr(t, "localized Hiragana query", "えがお", &search.text);
|
||
CT_EQ_INT(t, 3, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
}
|
||
|
||
init();
|
||
im.l = getlang(LangJPK);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "egao", &com)){
|
||
checkstr(t, "localized Katakana query", "エガオ", &search.text);
|
||
CT_EQ_INT(t, 3, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
}
|
||
|
||
init();
|
||
im.l = getlang(LangEN);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "heart", &com)){
|
||
CT_EQ_INT(t, 1, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "variation-selector emoji", "❤️", &com);
|
||
}
|
||
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "coffee", &com)){
|
||
CT_EQ_INT(t, 1, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "multi-codepoint emoji", "☕️", &com);
|
||
}
|
||
searchend(&f);
|
||
}
|
||
|
||
void
|
||
engine_emoji_digit_aliases(struct ct *t)
|
||
{
|
||
static const struct {
|
||
char *name;
|
||
Rune prefix;
|
||
Rune digit;
|
||
char *want;
|
||
} cases[] = {
|
||
{ "^1", '^', '1', "¹" },
|
||
{ "_2", '_', '2', "₂" },
|
||
{ "<3", '<', '3', "♥" },
|
||
};
|
||
Searchfix f;
|
||
Str com;
|
||
int i;
|
||
|
||
emojibegin(&f, 0);
|
||
for(i = 0; i < nelem(cases); i++){
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
CT_CHECK(t, keystroke(cases[i].prefix, Mshift, &com));
|
||
CT_CHECK(t, im.nkouho >= cases[i].digit - '0');
|
||
CT_CHECK(t, keystroke(cases[i].digit, 0, &com));
|
||
checkstr(t, cases[i].name, cases[i].want, &com);
|
||
CT_CHECK(t, !search.lang);
|
||
CT_EQ_INT(t, LangKO, im.l->lang);
|
||
}
|
||
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
CT_CHECK(t, keystroke('^', Mshift, &com));
|
||
CT_CHECK(t, keystroke('0', 0, &com));
|
||
checkstr(t, "zero query", "^0", &search.text);
|
||
checkstr(t, "zero does not commit", "", &com);
|
||
CT_EQ_INT(t, 1, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "zero alias", "⁰", &com);
|
||
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "smile", &com)){
|
||
CT_CHECK(t, keystroke('9', 0, &com));
|
||
checkstr(t, "missing number query", "smile9", &search.text);
|
||
checkstr(t, "missing number commit", "", &com);
|
||
CT_CHECK(t, search.lang);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "missing number text", "smile9", &com);
|
||
}
|
||
searchend(&f);
|
||
}
|
||
|
||
void
|
||
engine_emoji_navigation(struct ct *t)
|
||
{
|
||
Drawcmd dc = {0};
|
||
Searchfix f;
|
||
Str com;
|
||
int i;
|
||
|
||
emojibegin(&f, 0);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "many", &com)){
|
||
CT_EQ_INT(t, 12, im.nkouho);
|
||
CT_EQ_INT(t, 0, im.sel);
|
||
for(i = 0; i < Maxdisp; i++)
|
||
CT_CHECK(t, keystroke(Kdown, 0, &com));
|
||
CT_EQ_INT(t, Maxdisp, im.sel);
|
||
CT_CHECK(t, draindraw(&dc) > 0);
|
||
checkstr(t, "second-page first row", "c10", &dc.kouho[0]);
|
||
checkstr(t, "second-page last row", "c12", &dc.kouho[2]);
|
||
CT_EQ_INT(t, 3, dc.nkouho);
|
||
CT_EQ_INT(t, 0, dc.sel);
|
||
CT_CHECK(t, keystroke('1', 0, &com));
|
||
checkstr(t, "second-page row one", "c10", &com);
|
||
}
|
||
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "many", &com)){
|
||
for(i = 0; i < 11; i++)
|
||
CT_CHECK(t, keystroke(Kdown, 0, &com));
|
||
CT_EQ_INT(t, 11, im.sel);
|
||
CT_CHECK(t, keystroke('3', 0, &com));
|
||
checkstr(t, "short-page row three", "c12", &com);
|
||
}
|
||
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "smile", &com)){
|
||
CT_EQ_INT(t, 0, im.sel);
|
||
CT_CHECK(t, keystroke(Ktab, Mshift, &com));
|
||
CT_EQ_INT(t, 1, im.sel);
|
||
CT_CHECK(t, keystroke(Ktab, 0, &com));
|
||
CT_EQ_INT(t, 0, im.sel);
|
||
CT_CHECK(t, keystroke(Kdown, 0, &com));
|
||
CT_EQ_INT(t, 1, im.sel);
|
||
CT_CHECK(t, keystroke(Kup, 0, &com));
|
||
CT_EQ_INT(t, 0, im.sel);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "navigated candidate", "😀", &com);
|
||
}
|
||
searchend(&f);
|
||
}
|
||
|
||
void
|
||
engine_search_candidate_keys(struct ct *t)
|
||
{
|
||
Searchfix f;
|
||
Str com;
|
||
|
||
emojibegin(&f, 0);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "smile", &com)){
|
||
CT_EQ_INT(t, 0, im.sel);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "search default Enter", "😀", &com);
|
||
}
|
||
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "missing", &com)){
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
CT_EQ_INT(t, -1, im.sel);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "search query Enter", "missing", &com);
|
||
}
|
||
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
CT_CHECK(t, keystroke('^', Mshift, &com));
|
||
CT_CHECK(t, keystroke('0', 0, &com));
|
||
checkstr(t, "search zero query", "^0", &search.text);
|
||
checkstr(t, "search zero commit", "", &com);
|
||
CT_CHECK(t, search.lang);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "many", &com)){
|
||
im.sel = Maxdisp;
|
||
CT_CHECK(t, keystroke('1', Mshift, &com));
|
||
checkstr(t, "modified search digit commit", "", &com);
|
||
checkstr(t, "modified search digit query", "many1", &search.text);
|
||
CT_CHECK(t, search.lang);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
}
|
||
searchend(&f);
|
||
}
|
||
|
||
void
|
||
engine_emoji_preedit_languages(struct ct *t)
|
||
{
|
||
Searchfix f;
|
||
Str com, shown;
|
||
|
||
emojibegin(&f, 0);
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
sclear(&com);
|
||
if(typekeys(t, "ka", &com)){
|
||
CT_CHECK(t, !keystroke(Kmodfirst+2, 0, &com));
|
||
shown = shownpre(&im);
|
||
checkstr(t, "modifier preserves Japanese", "か", &shown);
|
||
CT_CHECK(t, keystroke('E', Mctrl|Mshift, &com));
|
||
checkstr(t, "committed Japanese preedit", "か", &com);
|
||
CT_CHECK(t, search.lang);
|
||
CT_EQ_INT(t, LangJP, im.l->lang);
|
||
CT_CHECK(t, !keystroke('E', Mctrl|Malt, &com));
|
||
CT_EQ_INT(t, 0, search.lang);
|
||
CT_CHECK(t, keystroke('E', Mctrl|Mshift, &com));
|
||
CT_CHECK(t, !keystroke('E', Mctrl|Msuper, &com));
|
||
CT_EQ_INT(t, 0, search.lang);
|
||
CT_CHECK(t, keystroke('E', Mctrl|Mshift, &com));
|
||
CT_EQ_INT(t, LangJP, im.l->lang);
|
||
CT_CHECK(t, keystroke('a', 0, &com));
|
||
CT_CHECK(t, !keystroke(Kmodfirst+2, 0, &com));
|
||
CT_CHECK(t, search.lang);
|
||
checkstr(t, "modifier preserves search", "a", &search.text);
|
||
CT_CHECK(t, !keystroke('P', Mctrl|Mshift, &com));
|
||
CT_CHECK(t, !search.lang);
|
||
sclear(&com);
|
||
if(typekeys(t, "na", &com)){
|
||
shown = shownpre(&im);
|
||
checkstr(t, "Japanese resumed", "な", &shown);
|
||
}
|
||
}
|
||
|
||
init();
|
||
im.l = getlang(LangKO);
|
||
sclear(&com);
|
||
if(typekeys(t, "rk", &com)){
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
checkstr(t, "committed Korean preedit", "가", &com);
|
||
if(typekeys(t, "smile", &com)){
|
||
CT_CHECK(t, keystroke('1', 0, &com));
|
||
checkstr(t, "Korean selection", "가😀", &com);
|
||
sclear(&com);
|
||
if(typekeys(t, "rk", &com))
|
||
checkstr(t, "Korean resumed", "가", &im.pre);
|
||
}
|
||
}
|
||
|
||
init();
|
||
im.l = &testvi;
|
||
sclear(&com);
|
||
if(typekeys(t, "as", &com)){
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
checkstr(t, "committed Telex preedit", "á", &com);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
CT_CHECK(t, !search.lang);
|
||
CT_EQ_PTR(t, &testvi, im.l);
|
||
sclear(&com);
|
||
if(typekeys(t, "as", &com)){
|
||
shown = shownpre(&im);
|
||
checkstr(t, "Telex resumed", "á", &shown);
|
||
checkstr(t, "Telex raw resumed", "as", &im.raw);
|
||
}
|
||
}
|
||
searchend(&f);
|
||
}
|
||
|
||
void
|
||
engine_emoji_start_and_unknown(struct ct *t)
|
||
{
|
||
Searchfix f;
|
||
Str com;
|
||
char want[Maxrunes+1];
|
||
int i;
|
||
|
||
emojibegin(&f, 0);
|
||
sclear(&com);
|
||
if(typekeys(t, "dkssud", &com)){
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
checkstr(t, "committed Korean preedit", "안녕", &com);
|
||
CT_CHECK(t, search.lang);
|
||
CT_EQ_INT(t, LangKO, im.l->lang);
|
||
if(typekeys(t, "xyz", &com)){
|
||
checkstr(t, "unknown displayed query", "xyz", &search.text);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "unknown query commit", "안녕xyz", &com);
|
||
CT_CHECK(t, !search.lang);
|
||
CT_EQ_INT(t, LangKO, im.l->lang);
|
||
}
|
||
}
|
||
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "xyz", &com)){
|
||
CT_CHECK(t, !keystroke(Kspec|0x51, 0, &com));
|
||
checkstr(t, "special commits shown query", "xyz", &com);
|
||
CT_CHECK(t, !search.lang);
|
||
CT_EQ_INT(t, LangKO, im.l->lang);
|
||
}
|
||
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
for(i = 0; i < Maxrunes; i++)
|
||
CT_CHECK(t, keystroke('q', 0, &com));
|
||
CT_EQ_INT(t, Maxrunes, search.raw.n);
|
||
CT_CHECK(t, search.lang);
|
||
CT_CHECK(t, !keystroke('z', 0, &com));
|
||
memset(want, 'q', Maxrunes);
|
||
want[Maxrunes] = '\0';
|
||
checkstr(t, "bounded query commit", want, &com);
|
||
CT_CHECK(t, !search.lang);
|
||
searchend(&f);
|
||
}
|
||
|
||
void
|
||
engine_emoji_dictionary_identity(struct ct *t)
|
||
{
|
||
Dictres res;
|
||
Searchfix f;
|
||
|
||
emojibegin(&f, 0);
|
||
im.pre = mkstr("same");
|
||
im.kouho[0] = mkstr("old");
|
||
im.nkouho = 1;
|
||
im.sel = 0;
|
||
memset(&res, 0, sizeof res);
|
||
res.key = im.pre;
|
||
res.seq = dictseq;
|
||
res.kouho[0] = mkstr("wrong");
|
||
res.nkouho = 1;
|
||
res.lang = LangJP;
|
||
dictresult(&res);
|
||
CT_EQ_INT(t, 1, im.nkouho);
|
||
checkstr(t, "wrong language ignored", "old", &im.kouho[0]);
|
||
|
||
res.lang = LangKO;
|
||
res.key = mkstr("other");
|
||
dictresult(&res);
|
||
CT_EQ_INT(t, 1, im.nkouho);
|
||
checkstr(t, "wrong preedit ignored", "old", &im.kouho[0]);
|
||
|
||
res.key = im.pre;
|
||
search.lang = LangEMOJI;
|
||
dictresult(&res);
|
||
CT_EQ_INT(t, 1, im.nkouho);
|
||
checkstr(t, "search response ignored", "old", &im.kouho[0]);
|
||
|
||
search.lang = 0;
|
||
res.kouho[0] = mkstr("right");
|
||
dictresult(&res);
|
||
CT_EQ_INT(t, 1, im.nkouho);
|
||
CT_EQ_INT(t, 0, im.sel);
|
||
checkstr(t, "matching response accepted", "right", &im.kouho[0]);
|
||
searchend(&f);
|
||
}
|
||
|
||
void
|
||
engine_hanja_search(struct ct *t)
|
||
{
|
||
Searchfix f;
|
||
Str com;
|
||
|
||
hanjabegin(&f, LangKO);
|
||
sclear(&com);
|
||
if(!CT_CHECK(t, keystroke('h', Mctrl, &com)))
|
||
goto cleanup;
|
||
CT_EQ_INT(t, LangHANJA, search.lang);
|
||
CT_EQ_INT(t, LangKO, im.l->lang);
|
||
if(!typekeys(t, "gks", &com))
|
||
goto cleanup;
|
||
checkstr(t, "Hanja reading", "한", &search.text);
|
||
CT_EQ_INT(t, 2, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kdown, 0, &com));
|
||
CT_CHECK(t, keystroke(Kdown, 0, &com));
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "navigated Hanja", "韓", &com);
|
||
CT_EQ_INT(t, 0, search.lang);
|
||
CT_EQ_INT(t, LangKO, im.l->lang);
|
||
cleanup:
|
||
searchend(&f);
|
||
}
|
||
|
||
void
|
||
engine_hanja_unknown_and_cancel(struct ct *t)
|
||
{
|
||
Searchfix f;
|
||
Str com;
|
||
|
||
hanjabegin(&f, LangKO);
|
||
sclear(&com);
|
||
if(!CT_CHECK(t, keystroke('h', Mctrl, &com)))
|
||
goto cleanup;
|
||
if(!typekeys(t, "rmf", &com))
|
||
goto cleanup;
|
||
checkstr(t, "unknown Hanja reading", "글", &search.text);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "unknown Hanja commit", "글", &com);
|
||
CT_EQ_INT(t, 0, search.lang);
|
||
CT_EQ_INT(t, LangKO, im.l->lang);
|
||
|
||
sclear(&com);
|
||
if(!CT_CHECK(t, keystroke('h', Mctrl, &com)))
|
||
goto cleanup;
|
||
if(!typekeys(t, "r", &com))
|
||
goto cleanup;
|
||
checkstr(t, "incomplete Hanja reading", "ㄱ", &search.text);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "incomplete Hanja commit", "ㄱ", &com);
|
||
|
||
sclear(&com);
|
||
if(!CT_CHECK(t, keystroke('h', Mctrl, &com)))
|
||
goto cleanup;
|
||
if(!typekeys(t, "gksrmf", &com))
|
||
goto cleanup;
|
||
checkstr(t, "multiple Hanja syllables", "한글", &search.text);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "multiple Hanja syllable commit", "한글", &com);
|
||
|
||
sclear(&com);
|
||
if(!CT_CHECK(t, keystroke('h', Mctrl, &com)))
|
||
goto cleanup;
|
||
if(!typekeys(t, "gks", &com))
|
||
goto cleanup;
|
||
CT_EQ_INT(t, 2, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
checkstr(t, "cancelled Hanja", "", &com);
|
||
CT_EQ_INT(t, 0, search.lang);
|
||
CT_EQ_INT(t, LangKO, im.l->lang);
|
||
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('h', Mctrl, &com));
|
||
CT_EQ_INT(t, LangHANJA, search.lang);
|
||
CT_CHECK(t, keystroke('h', Mctrl, &com));
|
||
CT_EQ_INT(t, 0, search.lang);
|
||
cleanup:
|
||
searchend(&f);
|
||
}
|
||
|
||
void
|
||
engine_hanja_korean_keys(struct ct *t)
|
||
{
|
||
Searchfix f;
|
||
Str com;
|
||
|
||
hanjabegin(&f, LangKO);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('h', Mctrl, &com));
|
||
CT_CHECK(t, keystroke('r', Mshift, &com));
|
||
CT_CHECK(t, keystroke('k', 0, &com));
|
||
checkstr(t, "shifted Hanja reading", "까", &search.text);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
|
||
CT_CHECK(t, keystroke('h', Mctrl, &com));
|
||
CT_CHECK(t, keystroke('R', 0, &com));
|
||
CT_CHECK(t, keystroke('k', 0, &com));
|
||
checkstr(t, "Caps Lock Hanja reading", "가", &search.text);
|
||
CT_EQ_INT(t, 1, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
searchend(&f);
|
||
}
|
||
|
||
void
|
||
engine_hanja_backspace(struct ct *t)
|
||
{
|
||
Searchfix f;
|
||
Str com;
|
||
|
||
hanjabegin(&f, LangKO);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('h', Mctrl, &com));
|
||
if(!typekeys(t, "gks", &com))
|
||
goto cleanup;
|
||
checkstr(t, "Hanja before backspace", "한", &search.text);
|
||
CT_EQ_INT(t, 2, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
checkstr(t, "Hanja final backspace", "하", &search.text);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
checkstr(t, "Hanja vowel backspace", "ㅎ", &search.text);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
checkstr(t, "empty Hanja search", "", &search.text);
|
||
CT_EQ_INT(t, LangHANJA, search.lang);
|
||
CT_CHECK(t, !keystroke(Kback, 0, &com));
|
||
CT_EQ_INT(t, 0, search.lang);
|
||
|
||
if(!typekeys(t, "gks", &com))
|
||
goto cleanup;
|
||
CT_CHECK(t, keystroke('h', Mctrl, &com));
|
||
checkstr(t, "preedit committed before Hanja", "한", &com);
|
||
CT_EQ_INT(t, LangHANJA, search.lang);
|
||
CT_CHECK(t, !keystroke(Kback, 0, &com));
|
||
CT_EQ_INT(t, 0, search.lang);
|
||
CT_EQ_INT(t, LangKO, im.l->lang);
|
||
cleanup:
|
||
searchend(&f);
|
||
}
|
||
|
||
void
|
||
engine_hanja_input_languages(struct ct *t)
|
||
{
|
||
Searchfix f;
|
||
Str com;
|
||
|
||
hanjabegin(&f, LangEN);
|
||
sclear(&com);
|
||
if(!CT_CHECK(t, keystroke('h', Mctrl, &com)))
|
||
goto cleanup;
|
||
if(!typekeys(t, "gks", &com))
|
||
goto cleanup;
|
||
checkstr(t, "English Hanja input", "gks", &search.text);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
CT_EQ_INT(t, LangEN, im.l->lang);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "English Hanja commit", "gks", &com);
|
||
CT_EQ_INT(t, LangEN, im.l->lang);
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
sclear(&com);
|
||
if(!CT_CHECK(t, keystroke('h', Mctrl, &com)))
|
||
goto cleanup;
|
||
if(!typekeys(t, "ka", &com))
|
||
goto cleanup;
|
||
checkstr(t, "Japanese Hanja input", "か", &search.text);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
CT_EQ_INT(t, LangJP, im.l->lang);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "Japanese Hanja commit", "か", &com);
|
||
CT_EQ_INT(t, LangJP, im.l->lang);
|
||
|
||
init();
|
||
im.l = getlang(LangEN);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('h', Mctrl, &com));
|
||
CT_CHECK(t, keystroke(L'한', 0, &com));
|
||
checkstr(t, "direct Hanja input", "한", &search.text);
|
||
CT_EQ_INT(t, 2, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "direct Hanja selection", "漢", &com);
|
||
CT_EQ_INT(t, LangEN, im.l->lang);
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('h', Mctrl, &com));
|
||
if(!typekeys(t, "ka", &com))
|
||
goto cleanup;
|
||
CT_CHECK(t, !keystroke('e', Mctrl|Malt, &com));
|
||
checkstr(t, "modified search boundary", "か", &com);
|
||
CT_EQ_INT(t, 0, search.lang);
|
||
CT_EQ_INT(t, LangJP, im.l->lang);
|
||
sclear(&com);
|
||
CT_CHECK(t, !keystroke('h', Mctrl|Msuper, &com));
|
||
CT_EQ_INT(t, 0, search.lang);
|
||
CT_EQ_INT(t, LangJP, im.l->lang);
|
||
cleanup:
|
||
searchend(&f);
|
||
}
|
||
|
||
static u32int
|
||
stressrand(u32int *state)
|
||
{
|
||
*state = *state * 1664525U + 1013904223U;
|
||
return *state;
|
||
}
|
||
|
||
static int
|
||
stressstr(Str *s)
|
||
{
|
||
return s->n >= 0 && s->n <= Maxrunes;
|
||
}
|
||
|
||
void
|
||
engine_randomized_stress(struct ct *t)
|
||
{
|
||
static Rune text[] = {
|
||
'a', 'e', 'g', 'i', 'j', 'k', 'm', 'n', 'o', 'r', 's', 't',
|
||
'u', 'w', 'y', '1', '2', '9', '\'', ' ', L'日',
|
||
};
|
||
static Rune modes[] = { 't', 'n', 'k', 's', 'v' };
|
||
Searchfix f;
|
||
Lang *vi;
|
||
Trie *vimap;
|
||
Keyres res;
|
||
Caret pos;
|
||
u32int rnd, mod;
|
||
char contexts[3];
|
||
void *owner, *owners[3];
|
||
int i, j, op;
|
||
|
||
vi = getlang(LangVI);
|
||
if(!CT_CHECK(t, vi != nil))
|
||
return;
|
||
vimap = vi->map;
|
||
vi->map = testvi.map;
|
||
emojibegin(&f, 0);
|
||
owners[0] = &contexts[0];
|
||
owners[1] = &contexts[1];
|
||
owners[2] = &contexts[2];
|
||
rnd = 0x5eed1234U;
|
||
for(i = 0; i < 16000; i++){
|
||
rnd = stressrand(&rnd);
|
||
owner = owners[(rnd >> 8) % nelem(owners)];
|
||
op = rnd % 28;
|
||
mod = (rnd >> 16) & Mmask;
|
||
memset(&pos, 0, sizeof pos);
|
||
pos.valid = (rnd >> 25) & 1;
|
||
pos.x = (rnd >> 3) & 0x3ff;
|
||
pos.y = (rnd >> 13) & 0x3ff;
|
||
pos.h = 1 + ((rnd >> 18) & 0x1f);
|
||
if(op < 13)
|
||
res = ownerrequestat(owner, Keypress,
|
||
text[(rnd >> 5) % nelem(text)], mod, &pos);
|
||
else if(op == 13)
|
||
res = ownerrequestat(owner, Keypress, Kback, 0, &pos);
|
||
else if(op == 14)
|
||
res = ownerrequestat(owner, Keypress, Kret, 0, &pos);
|
||
else if(op == 15)
|
||
res = ownerrequestat(owner, Keypress, Kesc, 0, &pos);
|
||
else if(op == 16)
|
||
res = ownerrequestat(owner, Keypress, Kdown, 0, &pos);
|
||
else if(op == 17)
|
||
res = ownerrequestat(owner, Keypress, Kup, 0, &pos);
|
||
else if(op == 18)
|
||
res = ownerrequestat(owner, Keypress, Ktab,
|
||
(rnd & 0x1000) ? Mshift : 0, &pos);
|
||
else if(op == 19)
|
||
res = ownerrequestat(owner, Keypress,
|
||
modes[(rnd >> 12) % nelem(modes)], Mctrl, &pos);
|
||
else if(op == 20)
|
||
res = ownerrequestat(owner, Keypress, 'e', Mctrl, &pos);
|
||
else if(op == 21)
|
||
res = ownerrequest(owner, Keyreset, 0, 0);
|
||
else if(op == 22)
|
||
res = ownerrequest(owner, Keyrelease, 0, 0);
|
||
else if(op == 23)
|
||
res = ownerrequestat(owner, Keycaret, 0, 0, &pos);
|
||
else if(op == 24)
|
||
res = ownerrequest(owner, Keypress,
|
||
Kmodfirst + ((rnd >> 20) & 7), 0);
|
||
else if(op == 25)
|
||
res = ownerrequestat(owner, Keypress, 'p', Mctrl, &pos);
|
||
else if(op == 26)
|
||
res = ownerrequestat(owner, Keypress, 0xf008, 0, &pos);
|
||
else
|
||
res = ownerrequestat(owner, Keypress,
|
||
'1' + ((rnd >> 11) % 9), 0, &pos);
|
||
|
||
if(!stressstr(&res.commit) || !stressstr(&res.preedit) ||
|
||
!stressstr(&im.pre) || !stressstr(&im.raw) ||
|
||
!stressstr(&search.raw) || !stressstr(&search.text) ||
|
||
im.l == nil || im.nkouho < 0 || im.nkouho > Maxkouho ||
|
||
im.sel < -1 || im.sel >= Maxkouho ||
|
||
(im.nkouho > 0 && im.sel >= im.nkouho)){
|
||
CT_ERRORF(t, "invalid engine state after stress operation %d", i);
|
||
break;
|
||
}
|
||
for(j = 0; j < im.nkouho; j++)
|
||
if(!stressstr(&im.kouho[j])){
|
||
CT_ERRORF(t, "invalid candidate after stress operation %d", i);
|
||
i = 16000;
|
||
break;
|
||
}
|
||
if(activeowner != nil && activeowner != owners[0] &&
|
||
activeowner != owners[1] && activeowner != owners[2]){
|
||
CT_ERRORF(t, "unknown active owner after stress operation %d", i);
|
||
break;
|
||
}
|
||
}
|
||
searchend(&f);
|
||
vi->map = vimap;
|
||
}
|
||
|
||
void
|
||
engine_full_boundary_passthrough(struct ct *t)
|
||
{
|
||
Str com;
|
||
int i;
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
for(i = 0; i < Maxrunes; i++)
|
||
im.pre.r[i] = L'あ';
|
||
im.pre.n = Maxrunes;
|
||
sclear(&com);
|
||
CT_CHECK(t, !keystroke(' ', 0, &com));
|
||
CT_EQ_INT(t, Maxrunes, com.n);
|
||
for(i = 0; i < Maxrunes; i++)
|
||
CT_EQ_UINT(t, L'あ', com.r[i]);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
CT_EQ_INT(t, 0, im.raw.n);
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
for(i = 0; i < Maxrunes-1; i++)
|
||
im.pre.r[i] = L'あ';
|
||
im.pre.n = Maxrunes-1;
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke(' ', 0, &com));
|
||
CT_EQ_INT(t, Maxrunes, com.n);
|
||
CT_EQ_UINT(t, ' ', com.r[Maxrunes-1]);
|
||
}
|