#define STRANS_TEST_ENGINE #include "../strans.c" #include "test.h" static int typekeys(struct ct*, char*, Str*); 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, -1, '9', "c9" }, { Maxdisp+3, Maxdisp+1, '1', "c3" }, }; 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_commit_contract(struct ct *t) { Str com; init(); im.l = getlang(LangJP); im.nkouho = 1; im.sel = -1; 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_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 Emojifix Emojifix; struct Emojifix { Im im; Search search; Hmap *dict; int popup; 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 setemoji(Hmap **dict, char *key, char *val) { Str s; s = mkstr(key); hmapset(dict, &s, val, strlen(val)); } static void emojibegin(Emojifix *f, int showpre) { Lang *l; l = getlang(LangEMOJI); f->im = im; f->search = search; f->dict = l->dict; f->popup = popup; f->visible = visible; draindraw(nil); l->dict = hmapalloc(32); setemoji(&l->dict, "a", "A B"); setemoji(&l->dict, "ㅁ", "B C"); setemoji(&l->dict, "alpha", "α"); setemoji(&l->dict, "smil", "😀 😄"); setemoji(&l->dict, "smile", "😀 😄"); setemoji(&l->dict, "웃음", "😀 😄 😂"); setemoji(&l->dict, "^", "¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ⁽"); setemoji(&l->dict, "^0", "⁰"); setemoji(&l->dict, "_", "₁ ₂ ₃ ₄ ₅ ₆ ₇ ₈ ₉ ₍"); setemoji(&l->dict, "<", "← ≤ ♥ ≠"); setemoji(&l->dict, "many", "c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12"); init(); im.l = getlang(LangKO); popup = showpre; } static void emojiend(Emojifix *f) { Lang *l; Hmap *dict; l = getlang(LangEMOJI); dict = l->dict; l->dict = f->dict; hmapfree(dict); draindraw(nil); im = f->im; search = f->search; popup = f->popup; visible = f->visible; } 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; 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); 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}; Emojifix f; Str com; emojibegin(&f, 1); sclear(&com); CT_CHECK(t, keystroke('p', Mctrl, &com)); CT_EQ_INT(t, 0, popup); CT_CHECK(t, keystroke('e', Mctrl, &com)); CT_CHECK(t, search.on); 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.on); 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.on); 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); } emojiend(&f); } void engine_emoji_queries(struct ct *t) { Drawcmd dc = {0}; Emojifix 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.on); CT_EQ_INT(t, LangKO, im.l->lang); } emojiend(&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', "♥" }, }; Emojifix 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.on); 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.on); CT_EQ_INT(t, 0, im.nkouho); CT_CHECK(t, keystroke(Kret, 0, &com)); checkstr(t, "missing number text", "smile9", &com); } emojiend(&f); } void engine_emoji_navigation(struct ct *t) { Drawcmd dc = {0}; Emojifix 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); for(i = 0; i < Maxdisp+1; i++) CT_CHECK(t, keystroke(Kdown, 0, &com)); CT_EQ_INT(t, Maxdisp, im.sel); CT_CHECK(t, draindraw(&dc) > 0); checkstr(t, "scrolled first row", "c2", &dc.kouho[0]); checkstr(t, "scrolled ninth row", "c10", &dc.kouho[8]); CT_EQ_INT(t, 8, dc.sel); CT_CHECK(t, keystroke('1', 0, &com)); checkstr(t, "scrolled row one", "c2", &com); } sclear(&com); CT_CHECK(t, keystroke('e', Mctrl, &com)); if(typekeys(t, "many", &com)){ for(i = 0; i < 12; i++) CT_CHECK(t, keystroke(Kdown, 0, &com)); CT_EQ_INT(t, 11, im.sel); CT_CHECK(t, keystroke('9', 0, &com)); checkstr(t, "scrolled row nine", "c12", &com); } sclear(&com); CT_CHECK(t, keystroke('e', Mctrl, &com)); if(typekeys(t, "smile", &com)){ 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(Ktab, Mshift, &com)); CT_EQ_INT(t, 1, im.sel); CT_CHECK(t, keystroke(Kret, 0, &com)); checkstr(t, "navigated candidate", "😄", &com); } emojiend(&f); } void engine_emoji_preedit_languages(struct ct *t) { Emojifix f; Str com, shown; int p; 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.on); CT_EQ_INT(t, LangJP, im.l->lang); CT_CHECK(t, keystroke('a', 0, &com)); p = popup; CT_CHECK(t, !keystroke(Kmodfirst+2, 0, &com)); CT_CHECK(t, search.on); checkstr(t, "modifier preserves search", "a", &search.text); CT_CHECK(t, keystroke('P', Mctrl|Mshift, &com)); CT_EQ_INT(t, !p, popup); CT_CHECK(t, search.on); CT_CHECK(t, !keystroke(Kmodfirst+2, 0, &com)); CT_CHECK(t, keystroke('E', Mctrl|Mshift, &com)); CT_CHECK(t, !search.on); 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.on); 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); } } emojiend(&f); } void engine_emoji_start_and_unknown(struct ct *t) { Emojifix 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.on); 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.on); 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.on); 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.on); 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.on); emojiend(&f); } void engine_emoji_dictionary_identity(struct ct *t) { Dictres res; Emojifix 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.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.on = 1; dictresult(&res); CT_EQ_INT(t, 1, im.nkouho); checkstr(t, "search response ignored", "old", &im.kouho[0]); search.on = 0; res.kouho[0] = mkstr("right"); dictresult(&res); CT_EQ_INT(t, 1, im.nkouho); CT_EQ_INT(t, -1, im.sel); checkstr(t, "matching response accepted", "right", &im.kouho[0]); emojiend(&f); }