Home | History | Annotate | Download | only in format
      1 //  2016 and later: Unicode, Inc. and others.
      2 // License & terms of use: http://www.unicode.org/copyright.html#License
      3 /*
      4 *******************************************************************************
      5 *   Copyright (C) 2011-2012, International Business Machines
      6 *   Corporation and others.  All Rights Reserved.
      7 *******************************************************************************
      8 *   created on: 2011aug12
      9 *   created by: Markus W. Scherer
     10 */
     11 
     12 package com.ibm.icu.dev.test.format;
     13 
     14 import java.util.ArrayList;
     15 import java.util.Iterator;
     16 import java.util.List;
     17 import java.util.Locale;
     18 
     19 import org.junit.Test;
     20 import org.junit.runner.RunWith;
     21 import org.junit.runners.JUnit4;
     22 
     23 import com.ibm.icu.dev.test.TestFmwk;
     24 import com.ibm.icu.text.MessagePattern;
     25 import com.ibm.icu.text.MessagePatternUtil;
     26 import com.ibm.icu.text.MessagePatternUtil.ArgNode;
     27 import com.ibm.icu.text.MessagePatternUtil.ComplexArgStyleNode;
     28 import com.ibm.icu.text.MessagePatternUtil.MessageContentsNode;
     29 import com.ibm.icu.text.MessagePatternUtil.MessageNode;
     30 import com.ibm.icu.text.MessagePatternUtil.TextNode;
     31 import com.ibm.icu.text.MessagePatternUtil.VariantNode;
     32 
     33 /**
     34  * Test MessagePatternUtil (MessagePattern-as-tree-of-nodes API)
     35  * by building parallel trees of nodes and verifying that they match.
     36  */
     37 @RunWith(JUnit4.class)
     38 public final class MessagePatternUtilTest extends TestFmwk {
     39     // The following nested "Expect..." classes are used to build
     40     // a tree structure parallel to what the MessagePatternUtil class builds.
     41     // These nested test classes are not static so that they have access to TestFmwk methods.
     42 
     43     private class ExpectMessageNode {
     44         private ExpectMessageNode expectTextThatContains(String s) {
     45             contents.add(new ExpectTextNode(s));
     46             return this;
     47         }
     48         private ExpectMessageNode expectReplaceNumber() {
     49             contents.add(new ExpectMessageContentsNode());
     50             return this;
     51         }
     52         private ExpectMessageNode expectNoneArg(Object name) {
     53             contents.add(new ExpectArgNode(name));
     54             return this;
     55         }
     56         private ExpectMessageNode expectSimpleArg(Object name, String type) {
     57             contents.add(new ExpectArgNode(name, type));
     58             return this;
     59         }
     60         private ExpectMessageNode expectSimpleArg(Object name, String type, String style) {
     61             contents.add(new ExpectArgNode(name, type, style));
     62             return this;
     63         }
     64         private ExpectComplexArgNode expectChoiceArg(Object name) {
     65             return expectComplexArg(name, MessagePattern.ArgType.CHOICE);
     66         }
     67         private ExpectComplexArgNode expectPluralArg(Object name) {
     68             return expectComplexArg(name, MessagePattern.ArgType.PLURAL);
     69         }
     70         private ExpectComplexArgNode expectSelectArg(Object name) {
     71             return expectComplexArg(name, MessagePattern.ArgType.SELECT);
     72         }
     73         private ExpectComplexArgNode expectSelectOrdinalArg(Object name) {
     74             return expectComplexArg(name, MessagePattern.ArgType.SELECTORDINAL);
     75         }
     76         private ExpectComplexArgNode expectComplexArg(Object name, MessagePattern.ArgType argType) {
     77             ExpectComplexArgNode complexArg = new ExpectComplexArgNode(this, name, argType);
     78             contents.add(complexArg);
     79             return complexArg;
     80         }
     81         private ExpectComplexArgNode finishVariant() {
     82             return parent;
     83         }
     84         private void checkMatches(MessageNode msg) {
     85             // matches() prints all errors.
     86             matches(msg);
     87         }
     88         private boolean matches(MessageNode msg) {
     89             List<MessageContentsNode> msgContents = msg.getContents();
     90             boolean ok = assertEquals("different numbers of MessageContentsNode",
     91                                       contents.size(), msgContents.size());
     92             if (ok) {
     93                 Iterator<MessageContentsNode> msgIter = msgContents.iterator();
     94                 for (ExpectMessageContentsNode ec : contents) {
     95                     ok &= ec.matches(msgIter.next());
     96                 }
     97             }
     98             if (!ok) {
     99                 errln("error in message: " + msg.toString());
    100             }
    101             return ok;
    102         }
    103         private ExpectComplexArgNode parent;  // for finishVariant()
    104         private List<ExpectMessageContentsNode> contents =
    105             new ArrayList<ExpectMessageContentsNode>();
    106     }
    107 
    108     /**
    109      * Base class for message contents nodes.
    110      * Used directly for REPLACE_NUMBER nodes, subclassed for others.
    111      */
    112     private class ExpectMessageContentsNode {
    113         protected boolean matches(MessageContentsNode c) {
    114             return assertEquals("not a REPLACE_NUMBER node",
    115                                 MessageContentsNode.Type.REPLACE_NUMBER, c.getType());
    116         }
    117     }
    118 
    119     private class ExpectTextNode extends ExpectMessageContentsNode {
    120         private ExpectTextNode(String subString) {
    121             this.subString = subString;
    122         }
    123         @Override
    124         protected boolean matches(MessageContentsNode c) {
    125             return
    126                 assertEquals("not a TextNode",
    127                              MessageContentsNode.Type.TEXT, c.getType()) &&
    128                 assertTrue("TextNode does not contain \"" + subString + "\"",
    129                            ((TextNode)c).getText().contains(subString));
    130         }
    131         private String subString;
    132     }
    133 
    134     private class ExpectArgNode extends ExpectMessageContentsNode {
    135         private ExpectArgNode(Object name) {
    136             this(name, null, null);
    137         }
    138         private ExpectArgNode(Object name, String type) {
    139             this(name, type, null);
    140         }
    141         private ExpectArgNode(Object name, String type, String style) {
    142             if (name instanceof String) {
    143                 this.name = (String)name;
    144                 this.number = -1;
    145             } else {
    146                 this.number = (Integer)name;
    147                 this.name = Integer.toString(this.number);
    148             }
    149             if (type == null) {
    150                 argType = MessagePattern.ArgType.NONE;
    151             } else {
    152                 argType = MessagePattern.ArgType.SIMPLE;
    153             }
    154             this.type = type;
    155             this.style = style;
    156         }
    157         @Override
    158         protected boolean matches(MessageContentsNode c) {
    159             boolean ok =
    160                 assertEquals("not an ArgNode",
    161                              MessageContentsNode.Type.ARG, c.getType());
    162             if (!ok) {
    163                 return ok;
    164             }
    165             ArgNode arg = (ArgNode)c;
    166             ok &= assertEquals("unexpected ArgNode argType",
    167                                argType, arg.getArgType());
    168             ok &= assertEquals("unexpected ArgNode arg name",
    169                                name, arg.getName());
    170             ok &= assertEquals("unexpected ArgNode arg number",
    171                                number, arg.getNumber());
    172             ok &= assertEquals("unexpected ArgNode arg type name",
    173                                type, arg.getTypeName());
    174             ok &= assertEquals("unexpected ArgNode arg style",
    175                                style, arg.getSimpleStyle());
    176             if (argType == MessagePattern.ArgType.NONE || argType == MessagePattern.ArgType.SIMPLE) {
    177                 ok &= assertNull("unexpected non-null complex style", arg.getComplexStyle());
    178             }
    179             return ok;
    180         }
    181         private String name;
    182         private int number;
    183         protected MessagePattern.ArgType argType;
    184         private String type;
    185         private String style;
    186     }
    187 
    188     private class ExpectComplexArgNode extends ExpectArgNode {
    189         private ExpectComplexArgNode(ExpectMessageNode parent,
    190                                      Object name, MessagePattern.ArgType argType) {
    191             super(name, argType.toString().toLowerCase(Locale.ENGLISH));
    192             this.argType = argType;
    193             this.parent = parent;
    194         }
    195         private ExpectComplexArgNode expectOffset(double offset) {
    196             this.offset = offset;
    197             explicitOffset = true;
    198             return this;
    199         }
    200         private ExpectMessageNode expectVariant(String selector) {
    201             ExpectVariantNode variant = new ExpectVariantNode(this, selector);
    202             variants.add(variant);
    203             return variant.msg;
    204         }
    205         private ExpectMessageNode expectVariant(String selector, double value) {
    206             ExpectVariantNode variant = new ExpectVariantNode(this, selector, value);
    207             variants.add(variant);
    208             return variant.msg;
    209         }
    210         private ExpectMessageNode finishComplexArg() {
    211             return parent;
    212         }
    213         @Override
    214         protected boolean matches(MessageContentsNode c) {
    215             boolean ok = super.matches(c);
    216             if (!ok) {
    217                 return ok;
    218             }
    219             ArgNode arg = (ArgNode)c;
    220             ComplexArgStyleNode complexStyle = arg.getComplexStyle();
    221             ok &= assertNotNull("unexpected null complex style", complexStyle);
    222             if (!ok) {
    223                 return ok;
    224             }
    225             ok &= assertEquals("unexpected complex-style argType",
    226                                argType, complexStyle.getArgType());
    227             ok &= assertEquals("unexpected complex-style hasExplicitOffset()",
    228                                explicitOffset, complexStyle.hasExplicitOffset());
    229             ok &= assertEquals("unexpected complex-style offset",
    230                                offset, complexStyle.getOffset());
    231             List<VariantNode> complexVariants = complexStyle.getVariants();
    232             ok &= assertEquals("different number of variants",
    233                                variants.size(), complexVariants.size());
    234             if (!ok) {
    235                 return ok;
    236             }
    237             Iterator<VariantNode> complexIter = complexVariants.iterator();
    238             for (ExpectVariantNode variant : variants) {
    239                 ok &= variant.matches(complexIter.next());
    240             }
    241             return ok;
    242         }
    243         private ExpectMessageNode parent;  // for finishComplexArg()
    244         private boolean explicitOffset;
    245         private double offset;
    246         private List<ExpectVariantNode> variants = new ArrayList<ExpectVariantNode>();
    247     }
    248 
    249     private class ExpectVariantNode {
    250         private ExpectVariantNode(ExpectComplexArgNode parent, String selector) {
    251             this(parent, selector, MessagePattern.NO_NUMERIC_VALUE);
    252         }
    253         private ExpectVariantNode(ExpectComplexArgNode parent, String selector, double value) {
    254             this.selector = selector;
    255             numericValue = value;
    256             msg = new ExpectMessageNode();
    257             msg.parent = parent;
    258         }
    259         private boolean matches(VariantNode v) {
    260             boolean ok = assertEquals("different selector strings",
    261                                       selector, v.getSelector());
    262             ok &= assertEquals("different selector strings",
    263                                isSelectorNumeric(), v.isSelectorNumeric());
    264             ok &= assertEquals("different selector strings",
    265                                numericValue, v.getSelectorValue());
    266             return ok & msg.matches(v.getMessage());
    267         }
    268         private boolean isSelectorNumeric() {
    269             return numericValue != MessagePattern.NO_NUMERIC_VALUE;
    270         }
    271         private String selector;
    272         private double numericValue;
    273         private ExpectMessageNode msg;
    274     }
    275 
    276     // The actual tests start here. ---------------------------------------- ***
    277     // Sample message strings are mostly from the MessagePatternUtilDemo.
    278 
    279     @Test
    280     public void TestHello() {
    281         // No syntax.
    282         MessageNode msg = MessagePatternUtil.buildMessageNode("Hello!");
    283         ExpectMessageNode expect = new ExpectMessageNode().expectTextThatContains("Hello");
    284         expect.checkMatches(msg);
    285     }
    286 
    287     @Test
    288     public void TestHelloWithApos() {
    289         // Literal ASCII apostrophe.
    290         MessageNode msg = MessagePatternUtil.buildMessageNode("Hel'lo!");
    291         ExpectMessageNode expect = new ExpectMessageNode().expectTextThatContains("Hel'lo");
    292         expect.checkMatches(msg);
    293     }
    294 
    295     @Test
    296     public void TestHelloWithQuote() {
    297         // Apostrophe starts quoted literal text.
    298         MessageNode msg = MessagePatternUtil.buildMessageNode("Hel'{o!");
    299         ExpectMessageNode expect = new ExpectMessageNode().expectTextThatContains("Hel{o");
    300         expect.checkMatches(msg);
    301         // Terminating the quote should yield the same result.
    302         msg = MessagePatternUtil.buildMessageNode("Hel'{'o!");
    303         expect.checkMatches(msg);
    304         // Double apostrophe inside quoted literal text still encodes a single apostrophe.
    305         msg = MessagePatternUtil.buildMessageNode("a'{bc''de'f");
    306         expect = new ExpectMessageNode().expectTextThatContains("a{bc'def");
    307         expect.checkMatches(msg);
    308     }
    309 
    310     @Test
    311     public void TestNoneArg() {
    312         // Numbered argument.
    313         MessageNode msg = MessagePatternUtil.buildMessageNode("abc{0}def");
    314         ExpectMessageNode expect = new ExpectMessageNode().
    315             expectTextThatContains("abc").expectNoneArg(0).expectTextThatContains("def");
    316         expect.checkMatches(msg);
    317         // Named argument.
    318         msg = MessagePatternUtil.buildMessageNode("abc{ arg }def");
    319         expect = new ExpectMessageNode().
    320             expectTextThatContains("abc").expectNoneArg("arg").expectTextThatContains("def");
    321         expect.checkMatches(msg);
    322         // Numbered and named arguments.
    323         msg = MessagePatternUtil.buildMessageNode("abc{1}def{arg}ghi");
    324         expect = new ExpectMessageNode().
    325             expectTextThatContains("abc").expectNoneArg(1).expectTextThatContains("def").
    326             expectNoneArg("arg").expectTextThatContains("ghi");
    327         expect.checkMatches(msg);
    328     }
    329 
    330     @Test
    331     public void TestSimpleArg() {
    332         MessageNode msg = MessagePatternUtil.buildMessageNode("a'{bc''de'f{0,number,g'hi''jk'l#}");
    333         ExpectMessageNode expect = new ExpectMessageNode().
    334             expectTextThatContains("a{bc'def").expectSimpleArg(0, "number", "g'hi''jk'l#");
    335         expect.checkMatches(msg);
    336     }
    337 
    338     @Test
    339     public void TestSelectArg() {
    340         MessageNode msg = MessagePatternUtil.buildMessageNode(
    341                 "abc{2, number}ghi{3, select, xx {xxx} other {ooo}} xyz");
    342         ExpectMessageNode expect = new ExpectMessageNode().
    343             expectTextThatContains("abc").expectSimpleArg(2, "number").
    344             expectTextThatContains("ghi").
    345             expectSelectArg(3).
    346                 expectVariant("xx").expectTextThatContains("xxx").finishVariant().
    347                 expectVariant("other").expectTextThatContains("ooo").finishVariant().
    348                 finishComplexArg().
    349             expectTextThatContains(" xyz");
    350         expect.checkMatches(msg);
    351     }
    352 
    353     @Test
    354     public void TestPluralArg() {
    355         // Plural with only keywords.
    356         MessageNode msg = MessagePatternUtil.buildMessageNode(
    357                 "abc{num_people, plural, offset:17 few{fff} other {oooo}}xyz");
    358         ExpectMessageNode expect = new ExpectMessageNode().
    359             expectTextThatContains("abc").
    360             expectPluralArg("num_people").
    361                 expectOffset(17).
    362                 expectVariant("few").expectTextThatContains("fff").finishVariant().
    363                 expectVariant("other").expectTextThatContains("oooo").finishVariant().
    364                 finishComplexArg().
    365             expectTextThatContains("xyz");
    366         expect.checkMatches(msg);
    367         // Plural with explicit-value selectors.
    368         msg = MessagePatternUtil.buildMessageNode(
    369                 "abc{ num , plural , offset: 2 =1 {1} =-1 {-1} =3.14 {3.14} other {oo} }xyz");
    370         expect = new ExpectMessageNode().
    371             expectTextThatContains("abc").
    372             expectPluralArg("num").
    373                 expectOffset(2).
    374                 expectVariant("=1", 1).expectTextThatContains("1").finishVariant().
    375                 expectVariant("=-1", -1).expectTextThatContains("-1").finishVariant().
    376                 expectVariant("=3.14", 3.14).expectTextThatContains("3.14").finishVariant().
    377                 expectVariant("other").expectTextThatContains("oo").finishVariant().
    378                 finishComplexArg().
    379             expectTextThatContains("xyz");
    380         expect.checkMatches(msg);
    381         // Plural with number replacement.
    382         msg = MessagePatternUtil.buildMessageNode(
    383                 "a_{0,plural,other{num=#'#'=#'#'={1,number,##}!}}_z");
    384         expect = new ExpectMessageNode().
    385             expectTextThatContains("a_").
    386             expectPluralArg(0).
    387                 expectVariant("other").
    388                     expectTextThatContains("num=").expectReplaceNumber().
    389                     expectTextThatContains("#=").expectReplaceNumber().
    390                     expectTextThatContains("#=").expectSimpleArg(1, "number", "##").
    391                     expectTextThatContains("!").finishVariant().
    392                 finishComplexArg().
    393             expectTextThatContains("_z");
    394         expect.checkMatches(msg);
    395         // Plural with explicit offset:0.
    396         msg = MessagePatternUtil.buildMessageNode(
    397                 "a_{0,plural,offset:0 other{num=#!}}_z");
    398         expect = new ExpectMessageNode().
    399             expectTextThatContains("a_").
    400             expectPluralArg(0).
    401                 expectOffset(0).
    402                 expectVariant("other").
    403                     expectTextThatContains("num=").expectReplaceNumber().
    404                     expectTextThatContains("!").finishVariant().
    405                 finishComplexArg().
    406             expectTextThatContains("_z");
    407         expect.checkMatches(msg);
    408     }
    409 
    410 
    411     @Test
    412     public void TestSelectOrdinalArg() {
    413         MessageNode msg = MessagePatternUtil.buildMessageNode(
    414                 "abc{num, selectordinal, offset:17 =0{null} few{fff} other {oooo}}xyz");
    415         ExpectMessageNode expect = new ExpectMessageNode().
    416             expectTextThatContains("abc").
    417             expectSelectOrdinalArg("num").
    418                 expectOffset(17).
    419                 expectVariant("=0", 0).expectTextThatContains("null").finishVariant().
    420                 expectVariant("few").expectTextThatContains("fff").finishVariant().
    421                 expectVariant("other").expectTextThatContains("oooo").finishVariant().
    422                 finishComplexArg().
    423             expectTextThatContains("xyz");
    424         expect.checkMatches(msg);
    425     }
    426 
    427     @Test
    428     public void TestChoiceArg() {
    429         MessageNode msg = MessagePatternUtil.buildMessageNode(
    430                 "a_{0,choice,- #-inf|  5 five | 99 # ninety'|'nine  }_z");
    431         ExpectMessageNode expect = new ExpectMessageNode().
    432             expectTextThatContains("a_").
    433             expectChoiceArg(0).
    434                 expectVariant("#", Double.NEGATIVE_INFINITY).
    435                     expectTextThatContains("-inf").finishVariant().
    436                 expectVariant("", 5).expectTextThatContains(" five ").finishVariant().
    437                 expectVariant("#", 99).expectTextThatContains(" ninety|nine  ").finishVariant().
    438                 finishComplexArg().
    439             expectTextThatContains("_z");
    440         expect.checkMatches(msg);
    441     }
    442 
    443     @Test
    444     public void TestComplexArgs() {
    445         MessageNode msg = MessagePatternUtil.buildMessageNode(
    446                 "I don't {a,plural,other{w'{'on't #'#'}} and "+
    447                 "{b,select,other{shan't'}'}} '{'''know'''}' and "+
    448                 "{c,choice,0#can't'|'}"+
    449                 "{z,number,#'#'###.00'}'}.");
    450         ExpectMessageNode expect = new ExpectMessageNode().
    451             expectTextThatContains("I don't ").
    452             expectPluralArg("a").
    453                 expectVariant("other").
    454                     expectTextThatContains("w{on't ").expectReplaceNumber().
    455                     expectTextThatContains("#").finishVariant().
    456                 finishComplexArg().
    457             expectTextThatContains(" and ").
    458             expectSelectArg("b").
    459                 expectVariant("other").expectTextThatContains("shan't}").finishVariant().
    460                 finishComplexArg().
    461             expectTextThatContains(" {'know'} and ").
    462             expectChoiceArg("c").
    463                 expectVariant("#", 0).expectTextThatContains("can't|").finishVariant().
    464                 finishComplexArg().
    465             expectSimpleArg("z", "number", "#'#'###.00'}'").
    466             expectTextThatContains(".");
    467         expect.checkMatches(msg);
    468     }
    469 
    470     /**
    471      * @return the text string of the VariantNode's message;
    472      *         assumes that its message consists of only text
    473      */
    474     private String variantText(VariantNode v) {
    475         return ((TextNode)v.getMessage().getContents().get(0)).getText();
    476     }
    477 
    478     @Test
    479     public void TestPluralVariantsByType() {
    480         MessageNode msg = MessagePatternUtil.buildMessageNode(
    481                 "{p,plural,a{A}other{O}=4{iv}b{B}other{U}=2{ii}}");
    482         ExpectMessageNode expect = new ExpectMessageNode().
    483         expectPluralArg("p").
    484             expectVariant("a").expectTextThatContains("A").finishVariant().
    485             expectVariant("other").expectTextThatContains("O").finishVariant().
    486             expectVariant("=4", 4).expectTextThatContains("iv").finishVariant().
    487             expectVariant("b").expectTextThatContains("B").finishVariant().
    488             expectVariant("other").expectTextThatContains("U").finishVariant().
    489             expectVariant("=2", 2).expectTextThatContains("ii").finishVariant().
    490             finishComplexArg();
    491         if (!expect.matches(msg)) {
    492             return;
    493         }
    494         List<VariantNode> numericVariants = new ArrayList<VariantNode>();
    495         List<VariantNode> keywordVariants = new ArrayList<VariantNode>();
    496         VariantNode other =
    497             ((ArgNode)msg.getContents().get(0)).getComplexStyle().
    498             getVariantsByType(numericVariants, keywordVariants);
    499         assertEquals("'other' selector", "other", other.getSelector());
    500         assertEquals("message string of first 'other'", "O", variantText(other));
    501 
    502         assertEquals("numericVariants.size()", 2, numericVariants.size());
    503         VariantNode v = numericVariants.get(0);
    504         assertEquals("numericVariants[0] selector", "=4", v.getSelector());
    505         assertEquals("numericVariants[0] selector value", 4., v.getSelectorValue());
    506         assertEquals("numericVariants[0] text", "iv", variantText(v));
    507         v = numericVariants.get(1);
    508         assertEquals("numericVariants[1] selector", "=2", v.getSelector());
    509         assertEquals("numericVariants[1] selector value", 2., v.getSelectorValue());
    510         assertEquals("numericVariants[1] text", "ii", variantText(v));
    511 
    512         assertEquals("keywordVariants.size()", 2, keywordVariants.size());
    513         v = keywordVariants.get(0);
    514         assertEquals("keywordVariants[0] selector", "a", v.getSelector());
    515         assertFalse("keywordVariants[0].isSelectorNumeric()", v.isSelectorNumeric());
    516         assertEquals("keywordVariants[0] text", "A", variantText(v));
    517         v = keywordVariants.get(1);
    518         assertEquals("keywordVariants[1] selector", "b", v.getSelector());
    519         assertFalse("keywordVariants[1].isSelectorNumeric()", v.isSelectorNumeric());
    520         assertEquals("keywordVariants[1] text", "B", variantText(v));
    521     }
    522 
    523     @Test
    524     public void TestSelectVariantsByType() {
    525         MessageNode msg = MessagePatternUtil.buildMessageNode(
    526                 "{s,select,a{A}other{O}b{B}other{U}}");
    527         ExpectMessageNode expect = new ExpectMessageNode().
    528         expectSelectArg("s").
    529             expectVariant("a").expectTextThatContains("A").finishVariant().
    530             expectVariant("other").expectTextThatContains("O").finishVariant().
    531             expectVariant("b").expectTextThatContains("B").finishVariant().
    532             expectVariant("other").expectTextThatContains("U").finishVariant().
    533             finishComplexArg();
    534         if (!expect.matches(msg)) {
    535             return;
    536         }
    537         // Check that we can use numericVariants = null.
    538         List<VariantNode> keywordVariants = new ArrayList<VariantNode>();
    539         VariantNode other =
    540             ((ArgNode)msg.getContents().get(0)).getComplexStyle().
    541             getVariantsByType(null, keywordVariants);
    542         assertEquals("'other' selector", "other", other.getSelector());
    543         assertEquals("message string of first 'other'", "O", variantText(other));
    544 
    545         assertEquals("keywordVariants.size()", 2, keywordVariants.size());
    546         VariantNode v = keywordVariants.get(0);
    547         assertEquals("keywordVariants[0] selector", "a", v.getSelector());
    548         assertFalse("keywordVariants[0].isSelectorNumeric()", v.isSelectorNumeric());
    549         assertEquals("keywordVariants[0] text", "A", variantText(v));
    550         v = keywordVariants.get(1);
    551         assertEquals("keywordVariants[1] selector", "b", v.getSelector());
    552         assertFalse("keywordVariants[1].isSelectorNumeric()", v.isSelectorNumeric());
    553         assertEquals("keywordVariants[1] text", "B", variantText(v));
    554     }
    555 }
    556