Merge branch 'change/web/context-tracker-backspaces-and-alignment' into fix/web/context-match-path-ordering

This commit is contained in:
Joshua Horton 2025-01-20 08:58:44 +07:00
commit cbf86b355f
8 changed files with 560 additions and 15 deletions

View file

@ -1,5 +1,21 @@
# Keyman Version History
## 18.0.172 alpha 2025-01-19
* chore(common): Add 17.0.333 to version history (#12926)
## 18.0.171 alpha 2025-01-18
* fix(common/web): add StrsItem.isEqual() method (#12868)
* fix(common/web): handle invalid order and tertiary arguments to ElementString.fromString() (#12882)
## 18.0.170 alpha 2025-01-17
* chore(linux): update copyright year (#12918)
* fix(web): patch unit test with execution dependent on autocorrect state (#12940)
* test(common/web): unit tests for element-string (#12811)
* chore(linux): Update debian changelog (#12917)
## 18.0.169 alpha 2025-01-17
* chore(windows): remove `postinstall` state from mermaid diagram (#12923)
@ -953,7 +969,7 @@
## 18.0.41 alpha 2024-05-22
* fix(developer): handle `KM_CORE_IT_INVALIDATE_CONTEXT` in debugger (#11488)
* chore(linux): Trigger GHA packaging for stable builds :cherries: (#11495)
* chore(linux): Trigger GHA packaging for stable builds (#11495)
* chore(android,mac,windows): Update crowdin strings for DE (#11497)
* feat(web): custom infrastructure for @web/test-runner use (#11403)
* chore(web): conversion of lm-worker browser-test for @web/test-runner use (#11404)
@ -1149,6 +1165,17 @@
* chore(common): move to 18.0 alpha (#10713)
* chore: move to 18.0 alpha
## 17.0.333 stable 2025-01-16
* fix(core): permanently disable logging (#12674)
* fix(linux): pushing of updated changelog branch (#12819)
* fix(linux): work around Lintian errors (#12817)
* fix(core): implement ldml_processor::get_key_list() (#12816)
* chore(linux): Update debian changelog (#12022)
* fix(android): use main looper to dispatch key events when OSK is hidden (#12875)
* chore: use GitHub PR titles when writing HISTORY.md (#12908)
* fix(developer): filter incorrect fonts out of .keyboard_info (#12913)
## 17.0.332 stable 2024-11-06
* fix(developer): create Server config directory before options save (#12609)

View file

@ -1 +1 @@
18.0.170
18.0.173

View file

@ -19,8 +19,8 @@ export class ElemElement {
order: number; // -128 to +127; used only by reorder element values
tertiary: number; // -128 to +127; used only by reorder element values
flags: ElemElementFlags;
isEqual(a: ElemElement) {
return a.value === this.value &&
isEqual(a: ElemElement): boolean {
return a.value.isEqual(this.value) &&
a.order === this.order &&
a.tertiary === this.tertiary &&
a.flags === this.flags;
@ -104,8 +104,8 @@ export class ElementString extends Array<ElemElement> {
typeFlag |= constants.elem_flags_type_str;
}
}
elem.order = orders.length ? parseInt(orders[i], 10) : 0;
elem.tertiary = tertiaries.length ? parseInt(tertiaries[i], 10) : 0;
elem.order = orders.length ? this.parseIntOrZero(orders[i]) : 0;
elem.tertiary = tertiaries.length ? this.parseIntOrZero(tertiaries[i]) : 0;
elem.flags = ElemElementFlags.none |
(ElemElementFlags.type & typeFlag) |
(tertiary_bases?.[i] == '1' /* TODO-LDML: or 'true'? */ ? ElemElementFlags.tertiary_base : 0) |
@ -125,5 +125,9 @@ export class ElementString extends Array<ElemElement> {
}
return true;
}
private static parseIntOrZero(str: string): number {
const num = parseInt(str, 10);
return !Number.isNaN(num) ? num : 0;
}
}
;

View file

@ -131,6 +131,10 @@ export class StrsItem {
get isOneChar() {
return this.char !== undefined;
}
isEqual(a: StrsItem): boolean {
return a.value === this.value && a.char === this.char;
}
};
/**

View file

@ -0,0 +1,498 @@
/*
* Keyman is copyright (C) SIL Global. MIT License.
*
* Created by Dr Mark C. Sinclair on 2024-12-09
*
* Test code for element-string.ts
*/
import 'mocha';
import { assert } from 'chai';
import { constants } from '@keymanapp/ldml-keyboard-constants';
import { ElemElementFlags, ElemElement, ElementString } from '../../../src/kmx/kmx-plus/element-string.js';
import { StrsItem, UsetItem, Strs, StrsOptions, DependencySections, CharStrsItem, Uset } from '../../../src/kmx/kmx-plus/kmx-plus.js';
import { UnicodeSet, UnicodeSetParser } from '../../../src/ldml-keyboard/unicodeset-parser-api.js';
import { ElementParser, ElementSegment, ElementType } from '../../../src/ldml-keyboard/pattern-parser.js';
const GOTHIC_A = new StrsItem("𐌰", 0x10330);
const GOTHIC_B = new StrsItem("𐌱", 0x10331);
const GOTHIC_C = new StrsItem("𐌲", 0x10332);
const GOTHIC_D = new StrsItem("𐌳", 0x10333);
const HI_GOTHIC_A = new StrsItem('\ud800', 0xd800);
const LO_GOTHIC_A = new StrsItem('\udf30', 0xdf30);
const GOTHIC_PATTERN = "[𐌰-𐍊]";
const GOTHIC_STRSITEM = new StrsItem(GOTHIC_PATTERN);
const GOTHIC_SET = new UnicodeSet(GOTHIC_PATTERN, [[0x10330,0x1034A]]);
const GOTHIC_USETITEM = new UsetItem(GOTHIC_SET, GOTHIC_STRSITEM);
const UGARITIC_PATTERN = "[𐎀-𐎟]";
const UGARITIC_STRSITEM = new StrsItem(UGARITIC_PATTERN);
const UGARITIC_SET = new UnicodeSet(UGARITIC_PATTERN, [[0x10380,0x1039F]]);
const UGARITIC_USETITEM = new UsetItem(UGARITIC_SET, UGARITIC_STRSITEM);
class TestUnicodeSetParser implements UnicodeSetParser {
parseUnicodeSet = (pattern: string, rangeCount: number) : UnicodeSet | null => GOTHIC_SET;
sizeUnicodeSet = (pattern: string) : number => 1;
};
const origElementParserSegment = ElementParser.segment;
let sections: DependencySections = null;
describe('Test of ElementString file', () => {
describe('Test of ElemElement', () => {
describe('Test of isEqual()', () => {
it('returns true when elems identical', () => {
const one = initElemElement();
const two = initElemElement();
assert.isTrue(one.isEqual(two));
});
it('returns true when elems are clones', () => {
const one = initElemElement(new StrsItem("𐌰", 0x10330));
const two = initElemElement(new StrsItem("𐌰", 0x10330));
assert.isTrue(one.isEqual(two));
});
it('returns false when value differs', () => {
const one = initElemElement(GOTHIC_A);
const two = initElemElement(GOTHIC_B);
assert.isFalse(one.isEqual(two));
});
it('returns false when order differs', () => {
const one = initElemElement(GOTHIC_A, GOTHIC_USETITEM, 0);
const two = initElemElement(GOTHIC_A, GOTHIC_USETITEM, 1);
assert.isFalse(one.isEqual(two));
});
it('returns false when tertiary differs', () => {
const one = initElemElement(GOTHIC_A, GOTHIC_USETITEM, 0, 0);
const two = initElemElement(GOTHIC_A, GOTHIC_USETITEM, 0, 1);
assert.isFalse(one.isEqual(two));
});
it('returns false when flags differs', () => {
const one = initElemElement(GOTHIC_A, GOTHIC_USETITEM, 0, 0, ElemElementFlags.none);
const two = initElemElement(GOTHIC_A, GOTHIC_USETITEM, 0, 0, ElemElementFlags.type);
assert.isFalse(one.isEqual(two));
});
it('returns true even though uset differs', () => {
const one = initElemElement(GOTHIC_A, GOTHIC_USETITEM);
const two = initElemElement(GOTHIC_A, UGARITIC_USETITEM);
assert.isTrue(one.isEqual(two));
});
});
});
describe('Test of ElementString class', () => {
beforeEach(() => {
sections = {
strs: new Strs(),
uset: new Uset(),
usetparser: new TestUnicodeSetParser(),
};
ElementParser.segment = stubElementParserSegment_CodePoint;
});
afterEach(() => {
ElementParser.segment = origElementParserSegment;
});
describe('Test of fromStrings()', () => {
it('returns an empty ElementString if source is null', () => {
const es = ElementString.fromStrings({}, null);
assert.deepEqual(es, new ElementString());
});
it('can create an ElementString from a string array', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(sections, ["𐌰", "𐌱", "𐌲"]);
const expected = [
initElemElement(GOTHIC_A),
initElemElement(GOTHIC_B),
initElemElement(GOTHIC_C),
];
assert.deepEqual(actual, expected);
});
it('can create an ElementString from a string', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(sections, "𐌰𐌱𐌲");
const expected = [
initElemElement(GOTHIC_A),
initElemElement(GOTHIC_B),
initElemElement(GOTHIC_C),
];
assert.deepEqual(actual, expected);
});
it('can apply order string', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(
sections,
"𐌰𐌱𐌲",
"1 2 3",
);
const expected = [
initElemElement(GOTHIC_A, undefined, 1),
initElemElement(GOTHIC_B, undefined, 2),
initElemElement(GOTHIC_C, undefined, 3),
];
assert.deepEqual(actual, expected);
});
it('can apply single order to all', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(
sections,
"𐌰𐌱𐌲",
"1",
);
const expected = [
initElemElement(GOTHIC_A, undefined, 1),
initElemElement(GOTHIC_B, undefined, 1),
initElemElement(GOTHIC_C, undefined, 1),
];
assert.deepEqual(actual, expected);
});
it('can handle order string that is too short', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(
sections,
"𐌰𐌱𐌲",
"1 2",
);
const expected = [
initElemElement(GOTHIC_A, undefined, 1),
initElemElement(GOTHIC_B, undefined, 2),
initElemElement(GOTHIC_C, undefined, 0),
];
assert.deepEqual(actual, expected);
});
it('can handle non-number in order string', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(
sections,
"𐌰𐌱𐌲",
"1 A 3",
);
const expected = [
initElemElement(GOTHIC_A, undefined, 1),
initElemElement(GOTHIC_B, undefined, 0),
initElemElement(GOTHIC_C, undefined, 3),
];
assert.deepEqual(actual, expected);
});
it('can apply tertiary string', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(
sections,
"𐌰𐌱𐌲",
null,
"1 2 3",
);
const expected = [
initElemElement(GOTHIC_A, undefined, 0, 1),
initElemElement(GOTHIC_B, undefined, 0, 2),
initElemElement(GOTHIC_C, undefined, 0, 3),
];
assert.deepEqual(actual, expected);
});
it('can apply single tertiary to all', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(
sections,
"𐌰𐌱𐌲",
null,
"1",
);
const expected = [
initElemElement(GOTHIC_A, undefined, 0, 1),
initElemElement(GOTHIC_B, undefined, 0, 1),
initElemElement(GOTHIC_C, undefined, 0, 1),
];
assert.deepEqual(actual, expected);
});
it('can handle tertiary string that is too short', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(
sections,
"𐌰𐌱𐌲",
null,
"1 2",
);
const expected = [
initElemElement(GOTHIC_A, undefined, 0, 1),
initElemElement(GOTHIC_B, undefined, 0, 2),
initElemElement(GOTHIC_C, undefined, 0, 0),
];
assert.deepEqual(actual, expected);
});
it('can handle non-number in tertiary string', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(
sections,
"𐌰𐌱𐌲",
null,
"1 A 3",
);
const expected = [
initElemElement(GOTHIC_A, undefined, 0, 1),
initElemElement(GOTHIC_B, undefined, 0, 0),
initElemElement(GOTHIC_C, undefined, 0, 3),
];
assert.deepEqual(actual, expected);
});
it('can apply tertiary_base string', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(
sections,
"𐌰𐌱𐌲",
null,
null,
"1 0 1",
);
const expected = [
initElemElement(GOTHIC_A, undefined, 0, 0, ElemElementFlags.tertiary_base),
initElemElement(GOTHIC_B, undefined, 0, 0, ElemElementFlags.none),
initElemElement(GOTHIC_C, undefined, 0, 0, ElemElementFlags.tertiary_base),
];
assert.deepEqual(actual, expected);
});
it('can apply single tertiary_base to all', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(
sections,
"𐌰𐌱𐌲",
null,
null,
"1",
);
const expected = [
initElemElement(GOTHIC_A, undefined, 0, 0, ElemElementFlags.tertiary_base),
initElemElement(GOTHIC_B, undefined, 0, 0, ElemElementFlags.tertiary_base),
initElemElement(GOTHIC_C, undefined, 0, 0, ElemElementFlags.tertiary_base),
];
assert.deepEqual(actual, expected);
});
it('can handle tertiary_base string that is too short', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(
sections,
"𐌰𐌱𐌲",
null,
null,
"1 0",
);
const expected = [
initElemElement(GOTHIC_A, undefined, 0, 0, ElemElementFlags.tertiary_base),
initElemElement(GOTHIC_B, undefined, 0, 0, ElemElementFlags.none),
initElemElement(GOTHIC_C, undefined, 0, 0, ElemElementFlags.none),
];
assert.deepEqual(actual, expected);
});
it('can apply prebase string', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(
sections,
"𐌰𐌱𐌲",
null,
null,
null,
"1 0 1",
);
const expected = [
initElemElement(GOTHIC_A, undefined, 0, 0, ElemElementFlags.prebase),
initElemElement(GOTHIC_B, undefined, 0, 0, ElemElementFlags.none),
initElemElement(GOTHIC_C, undefined, 0, 0, ElemElementFlags.prebase),
];
assert.deepEqual(actual, expected);
});
it('can apply single prebase to all', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(
sections,
"𐌰𐌱𐌲",
null,
null,
null,
"1",
);
const expected = [
initElemElement(GOTHIC_A, undefined, 0, 0, ElemElementFlags.prebase),
initElemElement(GOTHIC_B, undefined, 0, 0, ElemElementFlags.prebase),
initElemElement(GOTHIC_C, undefined, 0, 0, ElemElementFlags.prebase),
];
assert.deepEqual(actual, expected);
});
it('can handle prebase string that is too short', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(
sections,
"𐌰𐌱𐌲",
null,
null,
null,
"1 0",
);
const expected = [
initElemElement(GOTHIC_A, undefined, 0, 0, ElemElementFlags.prebase),
initElemElement(GOTHIC_B, undefined, 0, 0, ElemElementFlags.none),
initElemElement(GOTHIC_C, undefined, 0, 0, ElemElementFlags.none),
];
assert.deepEqual(actual, expected);
});
it('can create an ElementString from a uset string', () => {
ElementParser.segment = stubElementParserSegment_Uset;
sections.strs.allocString = stubStrsAllocString_Str;
sections.uset.allocUset = stubUsetAllocUset;
const actual = ElementString.fromStrings(sections, "[𐌰-𐍊]");
const expected = [
initElemElement(
new StrsItem(''),
GOTHIC_USETITEM,
0,
0,
constants.elem_flags_type_uset,
),
];
assert.deepEqual(actual, expected);
});
it('returns null for an invalid unicode set size', () => {
ElementParser.segment = stubElementParserSegment_Uset;
sections.usetparser.sizeUnicodeSet = (pattern: string) : number => -1;
assert.isNull(ElementString.fromStrings(sections, "[𐌰-𐍊]"));
});
it('returns null if it cannot parse the unicode set', () => {
ElementParser.segment = stubElementParserSegment_Uset;
sections.usetparser.parseUnicodeSet = (pattern: string, rangeCount: number) : UnicodeSet | null => null;
assert.isNull(ElementString.fromStrings(sections, "[𐌰-𐍊]"));
});
it('can handle quad strings', () => {
sections.strs.allocString = stubStrsAllocString_Char;
ElementParser.segment = stubElementParserSegment_Escaped;
const actual = ElementString.fromStrings(sections, "\\ud800\\udf30");
const expected = [
initElemElement(HI_GOTHIC_A),
initElemElement(LO_GOTHIC_A),
];
assert.deepEqual(actual, expected);
});
it('can handle ElemElement of string type', () => {
sections.strs.allocString = stubStrsAllocString_Str;
const actual = ElementString.fromStrings(sections, ["𐌰𐌱𐌲",]);
const expected = [
initElemElement(
new StrsItem("𐌰𐌱𐌲"),
undefined,
0,
0,
constants.elem_flags_type_str,
),
];
assert.deepEqual(actual, expected);
});
});
describe('Test of isEqual()', () => {
it('returns true when ElementStrings are identical', () => {
const es = initElementString([
initElemElement(GOTHIC_A),
initElemElement(GOTHIC_B),
initElemElement(GOTHIC_C),
]);
assert.isTrue(es.isEqual(es));
});
it('returns true when ElementStrings are clones', () => {
const one = initElementString([
initElemElement(GOTHIC_A),
initElemElement(GOTHIC_B),
initElemElement(GOTHIC_C),
]);
const two = initElementString([
initElemElement(GOTHIC_A),
initElemElement(GOTHIC_B),
initElemElement(GOTHIC_C),
]);
assert.isTrue(one.isEqual(two));
});
it('returns false when ElementStrings are different lengths', () => {
const one = initElementString([
initElemElement(GOTHIC_A),
initElemElement(GOTHIC_B),
initElemElement(GOTHIC_C),
]);
const two = initElementString([
initElemElement(GOTHIC_A),
initElemElement(GOTHIC_B),
]);
assert.isFalse(one.isEqual(two));
});
it('returns false when ElementStrings have different ElemElements', () => {
const one = initElementString([
initElemElement(GOTHIC_A),
initElemElement(GOTHIC_B),
initElemElement(GOTHIC_C),
]);
const two = initElementString([
initElemElement(GOTHIC_A),
initElemElement(GOTHIC_B),
initElemElement(GOTHIC_D),
]);
assert.isFalse(one.isEqual(two));
});
});
describe('Test of parseIntOrZero()', () => {
it('returns a number for a valid string', () => {
const num = ElementString['parseIntOrZero']('1');
assert.equal(num, 1);
});
it('returns zero for an invalid string', () => {
const num = ElementString['parseIntOrZero']('A');
assert.equal(num, 0);
});
it('returns zero for undefined', () => {
const num = ElementString['parseIntOrZero'](undefined);
assert.equal(num, 0);
});
it('returns zero for a null string', () => {
const num = ElementString['parseIntOrZero'](null);
assert.equal(num, 0);
});
});
});
});
function initElemElement(
value: StrsItem = GOTHIC_A,
uset: UsetItem = undefined,
order: number = 0,
tertiary: number = 0,
flags: ElemElementFlags = ElemElementFlags.none,
): ElemElement {
const ee = new ElemElement();
ee.value = value;
ee.uset = uset;
ee.order = order;
ee.tertiary = tertiary;
ee.flags = flags;
return ee;
};
function initElementString(elemElements: ElemElement[]): ElementString {
const es: ElementString = new ElementString();
elemElements.forEach((ee) => {es.push(ee)});
return es;
};
function stubStrsAllocString_Char(s?: string, opts?: StrsOptions, sections?: DependencySections): StrsItem {
return new CharStrsItem(s);
};
function stubStrsAllocString_Str(s?: string, opts?: StrsOptions, sections?: DependencySections): StrsItem {
return new StrsItem(s);
};
function stubElementParserSegment_CodePoint(str: string): ElementSegment[] {
return [...str].map(s => new ElementSegment(s, ElementType.codepoint));
};
function stubElementParserSegment_Uset(str: string): ElementSegment[] {
return [new ElementSegment(str, ElementType.uset)];
};
function stubElementParserSegment_Escaped(str: string): ElementSegment[] {
const strs = str.match(/\\u[0-9a-fA-F]{4}/g);
return strs.map((s) => new ElementSegment(s, ElementType.escaped));
};
function stubUsetAllocUset(set: UnicodeSet, sections: DependencySections) : UsetItem {
return new UsetItem(set, new StrsItem(set.pattern));
};

View file

@ -1,3 +1,9 @@
keyman (17.0.333-1) unstable; urgency=medium
* New upstream release.
-- Eberhard Beilharz <eb1@sil.org> Thu, 16 Jan 2025 16:29:44 +0100
keyman (17.0.332-1) unstable; urgency=medium
* set environment variable for rendering of downloads dialog (#12617)

View file

@ -4,24 +4,24 @@ Upstream-Contact: Keyman team <support@keyman.com>
Source: https://github.com/keymanapp/keyman
Files: *
Copyright: 2018-2024 SIL Global
Copyright: 2018-2025 SIL Global
License: MIT
Files: linux/ibus-keyman/*
Copyright: 2004-2024 SIL Global
Copyright: 2004-2025 SIL Global
License: GPL-2+
Files: linux/ibus-keyman/src/keymanutil.c
linux/ibus-keyman/src/keymanutil.h
linux/ibus-keyman/src/kmpdetails.c
linux/ibus-keyman/src/kmpdetails.h
Copyright: 2009-2024 SIL Global
Copyright: 2009-2025 SIL Global
License: GPL-2+ or MIT
Files: linux/ibus-keyman/src/keyman-service.c
linux/ibus-keyman/src/keyman-service.h
linux/keyman-config/buildtools/help2md
Copyright: 2018-2024 SIL Global
Copyright: 2018-2025 SIL Global
License: GPL-3+
Files: linux/ibus-keyman/tests/ibusimcontext.c
@ -31,7 +31,7 @@ Copyright: 2008-2010, Peng Huang <shawn.p.huang@gmail.com>
2008-2013, Peng Huang <shawn.p.huang@gmail.com>
2008-2021, Red Hat, Inc.
2015-2021, Takao Fujiwara <takao.fujiwara1@gmail.com>
2021-2024, SIL Global
2021-2025, SIL Global
License: LGPL-2.1+
Files: linux/keyman-config/buildtools/help2man
@ -41,7 +41,7 @@ License: GPL-3+
Files: debian/com.keyman.config.appdata.xml
debian/com.keyman.ibus_keyman.metainfo.xml
Copyright: 2019 Daniel Glassey <wdg@debian.org>
2022-2024 SIL Global
2022-2025 SIL Global
License: MIT
License: MIT

View file

@ -161,15 +161,21 @@ describe("PredictionContext", () => {
assert.equal(updateFake.callCount, 3);
suggestions = updateFake.thirdCall.args[0];
// Note: this unit test was originally written with auto-correct on!
// #11941 was written 2024-07-25 (added unit test for auto-correction method)
// #12169 was written 2024-08-14, which is what added THIS unit test.
// This does re-use the apply-revert oriented mocking.
// Should skip the (second) "apple", "apply", "apps" round, as it became outdated
// by its following request before its response could be received.
assert.deepEqual(suggestions.map((obj) => obj.displayAs), ['“apple”', 'applied']);
assert.equal(suggestions.find((obj) => obj.tag == 'keep').displayAs, '“apple”');
assert.deepEqual(suggestions.map((obj) => obj.displayAs), ['applied']); // '“apple”' included with auto-correct enabled.
// Is not displayed; we only display it if auto-correct is on, as 'applied' would be automatic then.
assert.equal(predictiveContext.keepSuggestion.displayAs, '“apple”');
// assert.equal(suggestions.find((obj) => obj.tag == 'keep').displayAs, '“apple”'); // with auto-correct enabled.
assert.equal(suggestions.find((obj) => obj.transform.deleteLeft != 0).displayAs, 'applied');
// Our reused mocking doesn't directly provide the 'keep' suggestion; we
// need to remove it before testing for set equality.
assert.deepEqual(suggestions.splice(1), expected);
assert.deepEqual(suggestions /*.splice(1)*/, expected);
});
it('sendUpdateState retrieves the most recent suggestion set', async function() {