Backspace undid a keystroke: な became ん, かんじ became かんj, きゃ became ky. Like every Japanese IME it now deletes the last kana as shown — な goes, かんじ becomes かん, きゃ becomes き — while a romaji letter that never became kana still goes one at a time. Tab stepped through candidates in a search and committed in Japanese; it steps through them there too, with Space, and Shift+Tab steps back.
2187 lines
56 KiB
C
2187 lines
56 KiB
C
#include "strans.c"
|
||
#include "test.h"
|
||
|
||
static int typekeys(struct ct*, char*, Str*);
|
||
static int draindraw(Drawcmd*);
|
||
|
||
/* One key from the owner, as imhandlekey drives the engine. */
|
||
static int
|
||
keystroke(u32int ks, u32int mod, Str *com)
|
||
{
|
||
int eaten;
|
||
|
||
eaten = keymeaningful(ks) && transition(ks, mod, com);
|
||
redraw();
|
||
return eaten;
|
||
}
|
||
|
||
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(&im, 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));
|
||
checkstr(t, "Escape commits 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, 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));
|
||
impre(&im, &shown);
|
||
checkstr(t, "before backspace", "な", &shown);
|
||
im.kouho[0] = mkstr("stale-candidate");
|
||
im.nkouho = 1;
|
||
im.sel = 0;
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
CT_EQ_INT(t, -1, im.sel);
|
||
checkstr(t, "chosen candidate backspace", "na", &im.raw);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
checkstr(t, "backspace commit", "", &com);
|
||
checkstr(t, "backspace deletes the kana shown", "", &im.raw);
|
||
impre(&im, &shown);
|
||
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 = -1;
|
||
selected = im.kouho[9];
|
||
sclear(&com);
|
||
CT_CHECK(t, !keystroke('1', mods[i], &com));
|
||
CT_CHECK(t, scmp(&com, &selected) != 0);
|
||
}
|
||
}
|
||
|
||
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);
|
||
impre(&im, &shown);
|
||
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);
|
||
impre(&im, &shown);
|
||
checkstr(t, "stale release preserves owner", "にゃ", &shown);
|
||
CT_EQ_PTR(t, &b, activeowner);
|
||
|
||
res = ownerrequest(&b, Keyrelease, 0, 0);
|
||
checkstr(t, "release hands the reading back", "にゃ", &res.commit);
|
||
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);
|
||
checkstr(t, "reset hands the reading back", "か", &res.commit);
|
||
CT_EQ_INT(t, 0, res.preedit.n);
|
||
res = ownerrequest(&owner, Keypress, 'n', 0);
|
||
checkstr(t, "same owner after reset", "ん", &res.preedit);
|
||
}
|
||
|
||
void
|
||
engine_active_owner_caret(struct ct *t)
|
||
{
|
||
Caret a, moved;
|
||
Drawcmd dc;
|
||
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();
|
||
draindraw(nil);
|
||
im.l = getlang(LangEN);
|
||
ownerrequestat(&one, Keypress, 'x', 0, &a);
|
||
CT_CHECK(t, caret.valid);
|
||
CT_EQ_INT(t, 10, caret.x);
|
||
ownerrequestat(&one, Keycaret, 0, 0, &moved);
|
||
CT_EQ_INT(t, 80, caret.x);
|
||
CT_EQ_INT(t, 0, draindraw(nil));
|
||
im.kouho[0] = mkstr("candidate");
|
||
im.nkouho = 1;
|
||
im.sel = 0;
|
||
redraw();
|
||
if(CT_CHECK(t, draindraw(&dc) > 0))
|
||
CT_EQ_INT(t, 80, dc.caret.x);
|
||
moved.x = 90;
|
||
ownerrequestat(&two, Keycaret, 0, 0, &moved);
|
||
CT_EQ_INT(t, 80, caret.x);
|
||
CT_EQ_INT(t, 0, draindraw(nil));
|
||
ownerrequestat(&one, Keycaret, 0, 0, &moved);
|
||
CT_EQ_INT(t, 90, caret.x);
|
||
if(CT_CHECK(t, draindraw(&dc) > 0))
|
||
CT_EQ_INT(t, 90, dc.caret.x);
|
||
ownerrequestat(&one, Keycaret, 0, 0, &moved);
|
||
CT_EQ_INT(t, 0, draindraw(nil));
|
||
ownerrequestat(&one, Keypress, 'x', 0, &moved);
|
||
CT_EQ_INT(t, 0, draindraw(nil));
|
||
moved.x = 100;
|
||
ownerrequestat(&one, Keypress, 'x', 0, &moved);
|
||
CT_EQ_INT(t, 100, caret.x);
|
||
if(CT_CHECK(t, draindraw(&dc) > 0))
|
||
CT_EQ_INT(t, 100, dc.caret.x);
|
||
ownerrequest(&two, Keypress, 'n', 0);
|
||
CT_EQ_PTR(t, &two, activeowner);
|
||
CT_CHECK(t, !caret.valid);
|
||
}
|
||
|
||
void
|
||
engine_vietnamese_client_preedit(struct ct *t)
|
||
{
|
||
static const struct {
|
||
char *keys;
|
||
char *shown;
|
||
} cases[] = {
|
||
{ "as", "á" },
|
||
{ "aa", "â" },
|
||
{ "ddoong", "đông" },
|
||
{ "hoaf", "hoà" },
|
||
{ "quas", "quá" },
|
||
};
|
||
Drawcmd dc;
|
||
Keyres res;
|
||
Str com, shown;
|
||
char *k, owner;
|
||
int i;
|
||
|
||
for(i = 0; i < nelem(cases); i++){
|
||
init();
|
||
im.l = &testvi;
|
||
draindraw(nil);
|
||
sclear(&com);
|
||
for(k = cases[i].keys; *k != '\0'; k++){
|
||
res = ownerrequestcap(&owner, Cclientpreedit,
|
||
Keypress, *k, 0);
|
||
sappend(&com, &res.commit);
|
||
if(!res.eaten)
|
||
sputr(&com, *k);
|
||
}
|
||
/* What the client shows, what the popup shows, and what
|
||
* Enter commits are the same composed text. */
|
||
shown = com;
|
||
sappend(&shown, &res.preedit);
|
||
checkstr(t, "client preedit", cases[i].shown, &shown);
|
||
activecap = 0;
|
||
redraw();
|
||
if(CT_CHECK(t, draindraw(&dc) > 0))
|
||
CT_EQ_INT(t, 0, scmp(&dc.pre, &res.preedit));
|
||
CT_CHECK(t, !keystroke(Kret, 0, &com));
|
||
checkstr(t, "commit", cases[i].shown, &com);
|
||
}
|
||
}
|
||
|
||
void
|
||
engine_commit_contract(struct ct *t)
|
||
{
|
||
static const struct {
|
||
Rune key;
|
||
char *one;
|
||
char *all;
|
||
} cases[] = {
|
||
{ 'z', "ㅋ", "ㅋㅋㅋ" },
|
||
{ 'r', "ㄱ", "ㄱㄱㄱ" },
|
||
{ 'd', "ㅇ", "ㅇㅇㅇ" },
|
||
{ 'k', "ㅏ", "ㅏㅏㅏ" },
|
||
};
|
||
Keyres res;
|
||
Str all, com;
|
||
char owner;
|
||
int i, j;
|
||
|
||
for(j = 0; j < nelem(cases); j++){
|
||
init();
|
||
im.l = getlang(LangKO);
|
||
sclear(&all);
|
||
for(i = 0; i < 3; i++){
|
||
res = ownerrequestcap(&owner, Cclientpreedit,
|
||
Keypress, cases[j].key, 0);
|
||
CT_CHECK(t, res.eaten);
|
||
checkstr(t, "repeated jamo commit",
|
||
i == 0 ? "" : cases[j].one, &res.commit);
|
||
checkstr(t, "repeated jamo preedit", cases[j].one,
|
||
&res.preedit);
|
||
sappend(&all, &res.commit);
|
||
}
|
||
sappend(&all, &res.preedit);
|
||
checkstr(t, "repeated jamo total", cases[j].all, &all);
|
||
}
|
||
|
||
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 = 0;
|
||
CT_CHECK(t, keystroke(Kdown, 0, &com));
|
||
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('!', 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);
|
||
|
||
/* Without candidates the movement keys commit like any other. */
|
||
init();
|
||
im.l = getlang(LangKO);
|
||
sclear(&com);
|
||
if(!typekeys(t, "dy", &com))
|
||
return;
|
||
CT_CHECK(t, !keystroke(Kdown, 0, &com));
|
||
checkstr(t, "arrow commit", "요", &com);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
sclear(&com);
|
||
if(!typekeys(t, "ka", &com))
|
||
return;
|
||
/* A key outside the map commits the reading and passes on. */
|
||
CT_CHECK(t, !keystroke(0xf008, 0, &com));
|
||
checkstr(t, "non-ASCII key commit", "か", &com);
|
||
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', "ㅔ", "ㅖ" },
|
||
};
|
||
Drawcmd dc;
|
||
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);
|
||
draindraw(nil);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
checkstr(t, "Korean final backspace", "가", &im.pre);
|
||
if(CT_CHECK(t, draindraw(&dc) == 1))
|
||
checkstr(t, "Korean backspace redraw", "가", &dc.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_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", 't', Mctrl, LangEN, "", "", "á", 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 = -1;
|
||
}
|
||
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;
|
||
}
|
||
}
|
||
|
||
init();
|
||
im.l = getlang(LangKO);
|
||
sclear(&com);
|
||
if(typekeys(t, "rk", &com)){
|
||
CT_CHECK(t, !keystroke('p', Mctrl, &com));
|
||
checkstr(t, "Korean Ctrl passthrough", "가", &com);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
}
|
||
init();
|
||
im.l = &testvi;
|
||
sclear(&com);
|
||
if(typekeys(t, "as", &com)){
|
||
CT_CHECK(t, !keystroke('p', Mctrl, &com));
|
||
checkstr(t, "Vietnamese Ctrl passthrough", "á", &com);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
CT_EQ_INT(t, 0, im.raw.n);
|
||
}
|
||
vi->map = saved;
|
||
}
|
||
|
||
typedef struct Searchfix Searchfix;
|
||
struct Searchfix
|
||
{
|
||
Im im;
|
||
Search search;
|
||
Lang *dictlang;
|
||
Trie *dict;
|
||
int activecap;
|
||
Drawcmd lastdraw;
|
||
};
|
||
|
||
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)
|
||
{
|
||
char name[8];
|
||
int i;
|
||
|
||
clearkouho();
|
||
for(i = 0; i < n; i++){
|
||
snprint(name, sizeof name, "c%d", i+1);
|
||
im.kouho[i] = mkstr(name);
|
||
}
|
||
im.nkouho = n;
|
||
}
|
||
|
||
static void
|
||
candidatebegin(int n)
|
||
{
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
draindraw(nil);
|
||
setcandidates(n);
|
||
}
|
||
|
||
static int
|
||
checkcandidatepage(struct ct *t, char *where, int first, int n, int sel)
|
||
{
|
||
Drawcmd dc;
|
||
char want[8];
|
||
|
||
redraw();
|
||
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[] = {
|
||
{ 1, 0, 0, 1, 0 },
|
||
{ 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 },
|
||
{ 19, 0, 0, 9, 0 },
|
||
{ 32, 8, 0, 9, 8 },
|
||
{ 32, 17, 9, 9, 8 },
|
||
{ 32, 26, 18, 9, 8 },
|
||
{ 32, 31, 27, 5, 4 },
|
||
};
|
||
int i;
|
||
|
||
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)
|
||
{
|
||
Drawcmd dc;
|
||
|
||
/* Clearing a visible popup must publish an empty snapshot. */
|
||
candidatebegin(1);
|
||
redraw();
|
||
draindraw(nil);
|
||
setcandidates(0);
|
||
redraw();
|
||
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);
|
||
}
|
||
}
|
||
|
||
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;
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
im.pre = mkstr("reading");
|
||
setcandidates(1);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "unselected candidate commits reading", "reading", &com);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
im.pre = mkstr("reading");
|
||
setcandidates(2);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke(Ktab, 0, &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(Kret, 0, &com));
|
||
checkstr(t, "Tab steps through the candidates", "c2", &com);
|
||
|
||
candidatebegin(2);
|
||
im.pre = mkstr("reading");
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke(Kdown, 0, &com));
|
||
CT_CHECK(t, keystroke(Kdown, 0, &com));
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "explicit candidate", "c2", &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);
|
||
|
||
/* A digit with no such row on the page is an ordinary key: it
|
||
* confirms the candidate paged to and passes on. */
|
||
candidatebegin(32);
|
||
im.pre = mkstr("reading");
|
||
im.sel = 27;
|
||
sclear(&com);
|
||
CT_CHECK(t, !keystroke('9', 0, &com));
|
||
checkstr(t, "unavailable short-page digit", "c28", &com);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
}
|
||
|
||
static void
|
||
setdict(Trie *dict, char *key, char *val)
|
||
{
|
||
trieput(dict, key, strlen(key), 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->lastdraw = lastdraw;
|
||
draindraw(nil);
|
||
}
|
||
|
||
static void
|
||
emojibegin(Searchfix *f, int showpre)
|
||
{
|
||
searchsave(f, LangEMOJI);
|
||
f->dictlang->dict = trienew();
|
||
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)
|
||
{
|
||
Trie *dict;
|
||
|
||
dict = f->dictlang->dict;
|
||
f->dictlang->dict = f->dict;
|
||
trieclose(dict);
|
||
draindraw(nil);
|
||
im = f->im;
|
||
search = f->search;
|
||
activecap = f->activecap;
|
||
lastdraw = f->lastdraw;
|
||
}
|
||
|
||
void
|
||
engine_popup_preedit_capability(struct ct *t)
|
||
{
|
||
Drawcmd dc;
|
||
Keyres kres;
|
||
Lang *jp;
|
||
Trie *saved;
|
||
char client, inactive;
|
||
|
||
jp = getlang(LangJP);
|
||
saved = jp->dict;
|
||
jp->dict = trienew();
|
||
setdict(jp->dict, "か", "家");
|
||
init();
|
||
im.l = jp;
|
||
draindraw(nil);
|
||
ownerrequestcap(&client, Cclientpreedit, Keypress, 'k', 0);
|
||
ownerrequestcap(&client, Cclientpreedit, Keypress, 'a', 0);
|
||
CT_EQ_INT(t, 1, im.nkouho);
|
||
redraw();
|
||
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, -1, 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, -1, dc.sel);
|
||
checkstr(t, "popup candidate", "家", &dc.kouho[0]);
|
||
}
|
||
CT_EQ_INT(t, 1, im.nkouho);
|
||
CT_EQ_INT(t, -1, 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, -1, dc.sel);
|
||
checkstr(t, "restored client candidate", "家", &dc.kouho[0]);
|
||
}
|
||
CT_EQ_INT(t, 1, im.nkouho);
|
||
CT_EQ_INT(t, -1, 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);
|
||
trieclose(jp->dict);
|
||
jp->dict = saved;
|
||
}
|
||
|
||
static void
|
||
hanjabegin(Searchfix *f, int lang)
|
||
{
|
||
searchsave(f, LangHANJA);
|
||
f->dictlang->dict = trienew();
|
||
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", "んや" },
|
||
{ "nnnya", "んにゃ" },
|
||
{ "konnnichiha", "こんにちは" },
|
||
{ "wi", "うぃ" },
|
||
{ "vu", "ゔ" },
|
||
{ "xtu", "っ" },
|
||
{ "axtu", "あっ" },
|
||
{ "lla", "っぁ" },
|
||
{ "matcha", "まっちゃ" },
|
||
{ "zi", "じ" },
|
||
{ "[", "「" },
|
||
{ "kin'youbi", "きんようび" },
|
||
};
|
||
static const struct {
|
||
char *raw;
|
||
char *want;
|
||
} replay[] = {
|
||
{ "kitte", "きって" },
|
||
{ "matcha", "まっちゃ" },
|
||
{ "baggu", "ばっぐ" },
|
||
{ "kanj", "かんj" },
|
||
{ "n'ya", "んや" },
|
||
};
|
||
Str com, raw, 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);
|
||
impre(&im, &shown);
|
||
checkstr(t, cases[i].keys, cases[i].want, &shown);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "committed reading", cases[i].want, &com);
|
||
}
|
||
for(i = 0; i < nelem(replay); i++){
|
||
raw = mkstr(replay[i].raw);
|
||
transstr(getlang(LangJP), nil, &raw, &shown);
|
||
checkstr(t, replay[i].raw, replay[i].want, &shown);
|
||
}
|
||
}
|
||
|
||
void
|
||
engine_japanese_candidates(struct ct *t)
|
||
{
|
||
Trie *saved;
|
||
Lang *jp;
|
||
Str com, shown;
|
||
|
||
jp = getlang(LangJP);
|
||
saved = jp->dict;
|
||
jp->dict = trienew();
|
||
setdict(jp->dict, "かんじ", "漢字 幹事");
|
||
init();
|
||
im.l = jp;
|
||
sclear(&com);
|
||
if(!typekeys(t, "kanji", &com))
|
||
goto cleanup;
|
||
impre(&im, &shown);
|
||
checkstr(t, "complete Japanese reading", "かんじ", &shown);
|
||
if(!CT_EQ_INT(t, 2, im.nkouho))
|
||
goto cleanup;
|
||
CT_EQ_INT(t, -1, 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, 0, im.sel);
|
||
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);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
/* Space converts: it steps through the candidates and wraps;
|
||
* typing on takes the chosen one; Backspace goes back. */
|
||
if(!typekeys(t, "kanji", &com))
|
||
goto cleanup;
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke(' ', 0, &com));
|
||
CT_EQ_INT(t, 0, im.sel);
|
||
CT_CHECK(t, keystroke(' ', 0, &com));
|
||
CT_EQ_INT(t, 1, im.sel);
|
||
CT_CHECK(t, keystroke(' ', 0, &com));
|
||
CT_EQ_INT(t, 2, im.sel);
|
||
CT_CHECK(t, keystroke(' ', 0, &com));
|
||
CT_EQ_INT(t, 0, im.sel);
|
||
CT_CHECK(t, keystroke(' ', Mshift, &com));
|
||
CT_EQ_INT(t, 2, im.sel);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
CT_EQ_INT(t, -1, im.sel);
|
||
CT_EQ_INT(t, 3, im.nkouho);
|
||
CT_CHECK(t, keystroke(' ', 0, &com));
|
||
CT_CHECK(t, keystroke('w', 0, &com));
|
||
CT_CHECK(t, keystroke('o', 0, &com));
|
||
checkstr(t, "typing on takes the candidate", "漢字", &com);
|
||
impre(&im, &shown);
|
||
checkstr(t, "reading after the candidate", "を", &shown);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke(' ', 0, &com));
|
||
if(CT_EQ_INT(t, 1, im.nkouho))
|
||
checkstr(t, "Space offers Katakana without candidates", "ヲ",
|
||
&im.kouho[0]);
|
||
CT_EQ_INT(t, 0, im.sel);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "Katakana committed", "ヲ", &com);
|
||
CT_CHECK(t, !keystroke(' ', 0, &com));
|
||
sclear(&com);
|
||
if(!typekeys(t, "kanji", &com))
|
||
goto cleanup;
|
||
CT_EQ_INT(t, 2, im.nkouho);
|
||
CT_CHECK(t, keystroke(' ', 0, &com));
|
||
if(CT_EQ_INT(t, 3, im.nkouho))
|
||
checkstr(t, "Katakana after the candidates", "カンジ",
|
||
&im.kouho[2]);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
/* SKK's okuri-ari entries convert verbs: stem plus okurigana. */
|
||
setdict(jp->dict, "かk", "書 描");
|
||
setdict(jp->dict, "かく", "確");
|
||
if(!typekeys(t, "kaku", &com))
|
||
goto cleanup;
|
||
if(CT_EQ_INT(t, 3, im.nkouho)){
|
||
checkstr(t, "exact candidate first", "確", &im.kouho[0]);
|
||
checkstr(t, "okurigana put back", "書く", &im.kouho[1]);
|
||
checkstr(t, "second okuri candidate", "描く", &im.kouho[2]);
|
||
}
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
setdict(jp->dict, "おおk", "大");
|
||
if(!typekeys(t, "ookii", &com))
|
||
goto cleanup;
|
||
if(CT_EQ_INT(t, 1, im.nkouho))
|
||
checkstr(t, "two-kana okurigana", "大きい", &im.kouho[0]);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
setdict(jp->dict, "いt", "言 行");
|
||
setdict(jp->dict, "いっt", "言 行");
|
||
if(!typekeys(t, "itta", &com))
|
||
goto cleanup;
|
||
if(CT_EQ_INT(t, 2, im.nkouho)){
|
||
checkstr(t, "sokuon written in kana", "言った", &im.kouho[0]);
|
||
checkstr(t, "sokuon written in kana", "行った", &im.kouho[1]);
|
||
}
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
/* A romaji typo stays in the reading for Backspace to mend. */
|
||
if(!typekeys(t, "kanjr", &com))
|
||
goto cleanup;
|
||
impre(&im, &shown);
|
||
checkstr(t, "typo kept", "かんjr", &shown);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
impre(&im, &shown);
|
||
checkstr(t, "typo half mended", "かんj", &shown);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
if(!typekeys(t, "ji", &com))
|
||
goto cleanup;
|
||
impre(&im, &shown);
|
||
checkstr(t, "typo mended", "かんじ", &shown);
|
||
CT_EQ_INT(t, 2, im.nkouho);
|
||
CT_CHECK(t, keystroke('0', 0, &com));
|
||
checkstr(t, "0 commits the reading", "かんじ", &com);
|
||
sclear(&com);
|
||
if(!typekeys(t, "wo", &com))
|
||
goto cleanup;
|
||
CT_CHECK(t, keystroke('0', 0, &com));
|
||
checkstr(t, "0 commits a reading without candidates", "を", &com);
|
||
sclear(&com);
|
||
if(!typekeys(t, "kanji", &com))
|
||
goto cleanup;
|
||
CT_CHECK(t, keystroke(' ', 0, &com));
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
CT_EQ_INT(t, -1, im.sel);
|
||
impre(&im, &shown);
|
||
checkstr(t, "Escape goes back to the reading", "かんじ", &shown);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
CT_EQ_INT(t, 0, im.raw.n);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
sclear(&com);
|
||
if(!typekeys(t, "kanji", &com))
|
||
goto cleanup;
|
||
CT_CHECK(t, keystroke(Kdown, 0, &com));
|
||
CT_CHECK(t, keystroke('s', Mctrl, &com));
|
||
checkstr(t, "language switch takes the candidate", "漢字", &com);
|
||
CT_EQ_INT(t, LangKO, im.l->lang);
|
||
im.l = jp;
|
||
/* Backspace looks a complete shorter reading up again. */
|
||
setdict(jp->dict, "かん", "缶");
|
||
if(!typekeys(t, "kanji", &com))
|
||
goto cleanup;
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
if(CT_EQ_INT(t, 1, im.nkouho))
|
||
checkstr(t, "shorter reading candidate", "缶", &im.kouho[0]);
|
||
cleanup:
|
||
trieclose(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));
|
||
impre(&im, &shown);
|
||
checkstr(t, "backspace deletes じ", "かん", &shown);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
impre(&im, &shown);
|
||
checkstr(t, "backspace deletes ん", "か", &shown);
|
||
if(typekeys(t, "kyu", &com)){
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
impre(&im, &shown);
|
||
checkstr(t, "backspace deletes ゅ", "かき", &shown);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
CT_CHECK(t, !haspre(&im));
|
||
}
|
||
}
|
||
|
||
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(L'日', 0, &com));
|
||
checkstr(t, "real input boundary", "かんじ", &com);
|
||
}
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
sclear(&com);
|
||
if(typekeys(t, "KA", &com)){
|
||
impre(&im, &shown);
|
||
checkstr(t, "uppercase romaji", "か", &shown);
|
||
}
|
||
}
|
||
|
||
void
|
||
engine_katakana_sequences(struct ct *t)
|
||
{
|
||
static const struct {
|
||
char *keys;
|
||
char *want;
|
||
} cases[] = {
|
||
{ "kitte", "キッテ" },
|
||
{ "katta", "カッタ" },
|
||
{ "gakkou", "ガッコウ" },
|
||
{ "matcha", "マッチャ" },
|
||
{ "baggu", "バッグ" },
|
||
{ "xu", "ゥ" },
|
||
{ "vu", "ヴ" },
|
||
};
|
||
Str com, shown;
|
||
int i;
|
||
|
||
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);
|
||
impre(&im, &shown);
|
||
checkstr(t, cases[i].keys, cases[i].want, &shown);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "committed Katakana", cases[i].want, &com);
|
||
}
|
||
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(CT_CHECK(t, draindraw(&dc) > 0))
|
||
checkstr(t, "empty search shows itself", "☺", &dc.pre);
|
||
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);
|
||
redraw();
|
||
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, 4, im.nkouho);
|
||
checkstr(t, "raw candidate", "A", &im.kouho[0]);
|
||
checkstr(t, "deduplicated candidate", "B", &im.kouho[1]);
|
||
checkstr(t, "raw prefix candidate", "α", &im.kouho[2]);
|
||
checkstr(t, "local candidate", "C", &im.kouho[3]);
|
||
CT_CHECK(t, draindraw(&dc) > 0);
|
||
checkstr(t, "prefix popup query", "a", &dc.pre);
|
||
CT_EQ_INT(t, 4, 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);
|
||
im.l = getlang(LangEN);
|
||
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(Kesc, 0, &com));
|
||
}
|
||
searchend(&f);
|
||
}
|
||
|
||
void
|
||
engine_emoji_navigation(struct ct *t)
|
||
{
|
||
Drawcmd dc = {0};
|
||
Searchfix f;
|
||
Str com;
|
||
|
||
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);
|
||
CT_CHECK(t, keystroke(Kpgdown, 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, "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(Kesc, 0, &com));
|
||
}
|
||
searchend(&f);
|
||
}
|
||
|
||
void
|
||
engine_search_candidate_keys(struct ct *t)
|
||
{
|
||
Searchfix f;
|
||
Str com;
|
||
|
||
emojibegin(&f, 0);
|
||
im.l = getlang(LangEN);
|
||
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));
|
||
impre(&im, &shown);
|
||
checkstr(t, "modifier preserves Japanese", "か", &shown);
|
||
CT_CHECK(t, keystroke('E', Mctrl, &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, &com));
|
||
CT_CHECK(t, !keystroke('E', Mctrl|Msuper, &com));
|
||
CT_EQ_INT(t, 0, search.lang);
|
||
CT_CHECK(t, keystroke('E', Mctrl, &com));
|
||
CT_CHECK(t, !keystroke('V', Mctrl|Mshift, &com));
|
||
CT_EQ_INT(t, 0, search.lang);
|
||
CT_EQ_INT(t, LangJP, im.l->lang);
|
||
CT_CHECK(t, keystroke('E', Mctrl, &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)){
|
||
impre(&im, &shown);
|
||
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)){
|
||
impre(&im, &shown);
|
||
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);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "xyz", &com)){
|
||
/* Keys that match nothing show what they type in the language. */
|
||
CT_CHECK(t, !keystroke(Kspec|0x51, 0, &com));
|
||
checkstr(t, "special commits shown query", "툨", &com);
|
||
CT_CHECK(t, !search.lang);
|
||
CT_EQ_INT(t, LangKO, im.l->lang);
|
||
}
|
||
im.l = getlang(LangEN);
|
||
|
||
/* A language switch commits the query like any other pending text. */
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "xy", &com)){
|
||
CT_CHECK(t, keystroke('t', Mctrl, &com));
|
||
checkstr(t, "language switch commits query", "xy", &com);
|
||
CT_CHECK(t, !search.lang);
|
||
CT_EQ_INT(t, LangEN, im.l->lang);
|
||
}
|
||
|
||
/* So do the search keys, ending or changing the search. */
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "xy", &com)){
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
checkstr(t, "search key commits query", "xy", &com);
|
||
CT_CHECK(t, !search.lang);
|
||
}
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "xy", &com)){
|
||
CT_CHECK(t, keystroke('h', Mctrl, &com));
|
||
checkstr(t, "other search commits query", "xy", &com);
|
||
CT_EQ_INT(t, LangHANJA, search.lang);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
}
|
||
|
||
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_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)
|
||
{
|
||
static const struct {
|
||
char *keys;
|
||
char *text;
|
||
} empty[] = {
|
||
{ "rmf", "글" },
|
||
{ "r", "ㄱ" },
|
||
{ "gksrmf", "한글" },
|
||
};
|
||
Searchfix f;
|
||
Str com;
|
||
int i;
|
||
|
||
hanjabegin(&f, LangKO);
|
||
for(i = 0; i < nelem(empty); i++){
|
||
sclear(&com);
|
||
if(!CT_CHECK(t, keystroke('h', Mctrl, &com)) ||
|
||
!typekeys(t, empty[i].keys, &com))
|
||
goto cleanup;
|
||
checkstr(t, "Hanja reading without candidates",
|
||
empty[i].text, &search.text);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "Hanja commit without candidates",
|
||
empty[i].text, &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, "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);
|
||
|
||
/* Ctrl+H converts the pending syllable: it seeds the query. */
|
||
if(!typekeys(t, "gks", &com))
|
||
goto cleanup;
|
||
CT_CHECK(t, keystroke('h', Mctrl, &com));
|
||
checkstr(t, "nothing committed before Hanja", "", &com);
|
||
checkstr(t, "pending syllable is the query", "한", &search.text);
|
||
CT_EQ_INT(t, 2, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
checkstr(t, "seed backspaced", "", &search.text);
|
||
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);
|
||
/* Escape gives the seed back; keys typed compose from it. */
|
||
if(!typekeys(t, "gks", &com))
|
||
goto cleanup;
|
||
CT_CHECK(t, keystroke('h', Mctrl, &com));
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
CT_EQ_INT(t, 0, search.lang);
|
||
checkstr(t, "seed pending again", "한", &im.pre);
|
||
checkstr(t, "nothing committed", "", &com);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
CT_CHECK(t, keystroke('h', Mctrl, &com));
|
||
checkstr(t, "seed 하", "하", &search.text);
|
||
CT_CHECK(t, keystroke('s', 0, &com));
|
||
checkstr(t, "key composes with the seed", "한", &search.text);
|
||
CT_EQ_INT(t, 2, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
checkstr(t, "seed alone comes back", "하", &im.pre);
|
||
CT_CHECK(t, !keystroke(Kesc, 0, &com));
|
||
checkstr(t, "Korean Escape commits", "하", &com);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
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);
|
||
|
||
/* The keyboard's own keys: 한자 seeds like Ctrl+H, 한/영 toggles. */
|
||
init();
|
||
im.l = getlang(LangKO);
|
||
sclear(&com);
|
||
if(typekeys(t, "gks", &com)){
|
||
CT_CHECK(t, keystroke(Khanja, 0, &com));
|
||
checkstr(t, "Hanja key seed", "한", &search.text);
|
||
CT_EQ_INT(t, 2, im.nkouho);
|
||
CT_CHECK(t, keystroke(Khangul, 0, &com));
|
||
checkstr(t, "Hangul key commits the query", "한", &com);
|
||
CT_EQ_INT(t, 0, search.lang);
|
||
CT_EQ_INT(t, LangEN, im.l->lang);
|
||
CT_CHECK(t, keystroke(Khangul, 0, &com));
|
||
CT_EQ_INT(t, LangKO, im.l->lang);
|
||
CT_CHECK(t, keystroke(Kzenkaku, 0, &com));
|
||
CT_EQ_INT(t, LangJP, im.l->lang);
|
||
CT_CHECK(t, keystroke(Kkana, 0, &com));
|
||
CT_EQ_INT(t, LangJPK, im.l->lang);
|
||
CT_CHECK(t, keystroke(Kkana, 0, &com));
|
||
CT_EQ_INT(t, LangJP, im.l->lang);
|
||
CT_CHECK(t, keystroke(Kzenkaku, 0, &com));
|
||
CT_EQ_INT(t, LangEN, im.l->lang);
|
||
CT_CHECK(t, keystroke(Khenkan, 0, &com));
|
||
CT_EQ_INT(t, LangJP, im.l->lang);
|
||
sclear(&com);
|
||
if(typekeys(t, "ka", &com)){
|
||
CT_CHECK(t, keystroke(Khenkan, 0, &com));
|
||
CT_EQ_INT(t, 0, im.sel);
|
||
CT_CHECK(t, keystroke(Kmuhenkan, 0, &com));
|
||
checkstr(t, "無変換 commits", "カ", &com);
|
||
CT_EQ_INT(t, LangEN, 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, shown;
|
||
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(L'日', 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(L'日', 0, &com));
|
||
CT_EQ_INT(t, Maxrunes-1, com.n);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
|
||
/* Typing on at the limit commits the kana and keeps the syllable. */
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
for(i = 0; i < Maxrunes-1; i++)
|
||
im.pre.r[i] = L'あ';
|
||
im.pre.n = Maxrunes-1;
|
||
im.raw = mkstr("k");
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('a', 0, &com));
|
||
CT_EQ_INT(t, Maxrunes-1, com.n);
|
||
impre(&im, &shown);
|
||
checkstr(t, "syllable after the flush", "か", &shown);
|
||
}
|