| // © 2017 and later: Unicode, Inc. and others. |
| // License & terms of use: http://www.unicode.org/copyright.html |
| |
| #include "unicode/utypes.h" |
| |
| #if !UCONFIG_NO_FORMATTING |
| |
| #include "unicode/utf16.h" |
| #include "putilimp.h" |
| #include "intltest.h" |
| #include "formatted_string_builder.h" |
| #include "formattedval_impl.h" |
| #include "unicode/unum.h" |
| |
| |
| class FormattedStringBuilderTest : public IntlTest { |
| public: |
| void testInsertAppendUnicodeString(); |
| void testSplice(); |
| void testInsertAppendCodePoint(); |
| void testCopy(); |
| void testFields(); |
| void testUnlimitedCapacity(); |
| void testCodePoints(); |
| |
| void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0); |
| |
| private: |
| void assertEqualsImpl(const UnicodeString &a, const FormattedStringBuilder &b); |
| }; |
| |
| static const char16_t *EXAMPLE_STRINGS[] = { |
| u"", |
| u"xyz", |
| u"The quick brown fox jumps over the lazy dog", |
| u"😁", |
| u"mixed 😇 and ASCII", |
| u"with combining characters like 🇦🇧🇨🇩", |
| u"A very very very very very very very very very very long string to force heap"}; |
| |
| void FormattedStringBuilderTest::runIndexedTest(int32_t index, UBool exec, const char *&name, char *) { |
| if (exec) { |
| logln("TestSuite FormattedStringBuilderTest: "); |
| } |
| TESTCASE_AUTO_BEGIN; |
| TESTCASE_AUTO(testInsertAppendUnicodeString); |
| TESTCASE_AUTO(testSplice); |
| TESTCASE_AUTO(testInsertAppendCodePoint); |
| TESTCASE_AUTO(testCopy); |
| TESTCASE_AUTO(testFields); |
| TESTCASE_AUTO(testUnlimitedCapacity); |
| TESTCASE_AUTO(testCodePoints); |
| TESTCASE_AUTO_END; |
| } |
| |
| void FormattedStringBuilderTest::testInsertAppendUnicodeString() { |
| UErrorCode status = U_ZERO_ERROR; |
| UnicodeString sb1; |
| FormattedStringBuilder sb2; |
| for (const char16_t* strPtr : EXAMPLE_STRINGS) { |
| UnicodeString str(strPtr); |
| |
| FormattedStringBuilder sb3; |
| sb1.append(str); |
| sb2.append(str, kUndefinedField, status); |
| assertSuccess("Appending to sb2", status); |
| sb3.append(str, kUndefinedField, status); |
| assertSuccess("Appending to sb3", status); |
| assertEqualsImpl(sb1, sb2); |
| assertEqualsImpl(str, sb3); |
| |
| UnicodeString sb4; |
| FormattedStringBuilder sb5; |
| sb4.append(u"😇"); |
| sb4.append(str); |
| sb4.append(u"xx"); |
| sb5.append(u"😇xx", kUndefinedField, status); |
| assertSuccess("Appending to sb5", status); |
| sb5.insert(2, str, kUndefinedField, status); |
| assertSuccess("Inserting into sb5", status); |
| assertEqualsImpl(sb4, sb5); |
| |
| int start = uprv_min(1, str.length()); |
| int end = uprv_min(10, str.length()); |
| sb4.insert(3, str, start, end - start); // UnicodeString uses length instead of end index |
| sb5.insert(3, str, start, end, kUndefinedField, status); |
| assertSuccess("Inserting into sb5 again", status); |
| assertEqualsImpl(sb4, sb5); |
| |
| UnicodeString sb4cp(sb4); |
| FormattedStringBuilder sb5cp(sb5); |
| sb4.append(sb4cp); |
| sb5.append(sb5cp, status); |
| assertSuccess("Appending again to sb5", status); |
| assertEqualsImpl(sb4, sb5); |
| } |
| } |
| |
| void FormattedStringBuilderTest::testSplice() { |
| static const struct TestCase { |
| const char16_t* input; |
| const int32_t startThis; |
| const int32_t endThis; |
| } cases[] = { |
| { u"", 0, 0 }, |
| { u"abc", 0, 0 }, |
| { u"abc", 1, 1 }, |
| { u"abc", 1, 2 }, |
| { u"abc", 0, 2 }, |
| { u"abc", 0, 3 }, |
| { u"lorem ipsum dolor sit amet", 8, 8 }, |
| { u"lorem ipsum dolor sit amet", 8, 11 }, // 3 chars, equal to replacement "xyz" |
| { u"lorem ipsum dolor sit amet", 8, 18 } }; // 10 chars, larger than several replacements |
| |
| UErrorCode status = U_ZERO_ERROR; |
| UnicodeString sb1; |
| FormattedStringBuilder sb2; |
| for (auto cas : cases) { |
| for (const char16_t* replacementPtr : EXAMPLE_STRINGS) { |
| UnicodeString replacement(replacementPtr); |
| |
| // Test replacement with full string |
| sb1.remove(); |
| sb1.append(cas.input); |
| sb1.replace(cas.startThis, cas.endThis - cas.startThis, replacement); |
| sb2.clear(); |
| sb2.append(cas.input, kUndefinedField, status); |
| sb2.splice(cas.startThis, cas.endThis, replacement, 0, replacement.length(), kUndefinedField, status); |
| assertSuccess("Splicing into sb2 first time", status); |
| assertEqualsImpl(sb1, sb2); |
| |
| // Test replacement with partial string |
| if (replacement.length() <= 2) { |
| continue; |
| } |
| sb1.remove(); |
| sb1.append(cas.input); |
| sb1.replace(cas.startThis, cas.endThis - cas.startThis, UnicodeString(replacement, 1, 2)); |
| sb2.clear(); |
| sb2.append(cas.input, kUndefinedField, status); |
| sb2.splice(cas.startThis, cas.endThis, replacement, 1, 3, kUndefinedField, status); |
| assertSuccess("Splicing into sb2 second time", status); |
| assertEqualsImpl(sb1, sb2); |
| } |
| } |
| } |
| |
| void FormattedStringBuilderTest::testInsertAppendCodePoint() { |
| static const UChar32 cases[] = { |
| 0, 1, 60, 127, 128, 0x7fff, 0x8000, 0xffff, 0x10000, 0x1f000, 0x10ffff}; |
| UErrorCode status = U_ZERO_ERROR; |
| UnicodeString sb1; |
| FormattedStringBuilder sb2; |
| for (UChar32 cas : cases) { |
| FormattedStringBuilder sb3; |
| sb1.append(cas); |
| sb2.appendCodePoint(cas, kUndefinedField, status); |
| assertSuccess("Appending to sb2", status); |
| sb3.appendCodePoint(cas, kUndefinedField, status); |
| assertSuccess("Appending to sb3", status); |
| assertEqualsImpl(sb1, sb2); |
| assertEquals("Length of sb3", U16_LENGTH(cas), sb3.length()); |
| assertEquals("Code point count of sb3", 1, sb3.codePointCount()); |
| assertEquals( |
| "First code unit in sb3", |
| !U_IS_SUPPLEMENTARY(cas) ? (char16_t) cas : U16_LEAD(cas), |
| sb3.charAt(0)); |
| |
| UnicodeString sb4; |
| FormattedStringBuilder sb5; |
| sb4.append(u"😇xx"); |
| sb4.insert(2, cas); |
| sb5.append(u"😇xx", kUndefinedField, status); |
| assertSuccess("Appending to sb5", status); |
| sb5.insertCodePoint(2, cas, kUndefinedField, status); |
| assertSuccess("Inserting into sb5", status); |
| assertEqualsImpl(sb4, sb5); |
| |
| UnicodeString sb6; |
| FormattedStringBuilder sb7; |
| sb6.append(cas); |
| if (U_IS_SUPPLEMENTARY(cas)) { |
| sb7.appendChar16(U16_TRAIL(cas), kUndefinedField, status); |
| sb7.insertChar16(0, U16_LEAD(cas), kUndefinedField, status); |
| } else { |
| sb7.insertChar16(0, cas, kUndefinedField, status); |
| } |
| assertSuccess("Insert/append into sb7", status); |
| assertEqualsImpl(sb6, sb7); |
| } |
| } |
| |
| void FormattedStringBuilderTest::testCopy() { |
| UErrorCode status = U_ZERO_ERROR; |
| for (UnicodeString str : EXAMPLE_STRINGS) { |
| FormattedStringBuilder sb1; |
| sb1.append(str, kUndefinedField, status); |
| assertSuccess("Appending to sb1 first time", status); |
| FormattedStringBuilder sb2(sb1); |
| assertTrue("Content should equal itself", sb1.contentEquals(sb2)); |
| |
| sb1.append("12345", kUndefinedField, status); |
| assertSuccess("Appending to sb1 second time", status); |
| assertFalse("Content should no longer equal itself", sb1.contentEquals(sb2)); |
| } |
| } |
| |
| void FormattedStringBuilderTest::testFields() { |
| typedef FormattedStringBuilder::Field Field; |
| UErrorCode status = U_ZERO_ERROR; |
| // Note: This is a C++11 for loop that calls the UnicodeString constructor on each iteration. |
| for (UnicodeString str : EXAMPLE_STRINGS) { |
| FormattedValueStringBuilderImpl sbi(kUndefinedField); |
| FormattedStringBuilder& sb = sbi.getStringRef(); |
| sb.append(str, kUndefinedField, status); |
| assertSuccess("Appending to sb", status); |
| sb.append(str, {UFIELD_CATEGORY_NUMBER, UNUM_CURRENCY_FIELD}, status); |
| assertSuccess("Appending to sb", status); |
| assertEquals("Reference string copied twice", str.length() * 2, sb.length()); |
| for (int32_t i = 0; i < str.length(); i++) { |
| assertEquals("Null field first", |
| kUndefinedField.bits, sb.fieldAt(i).bits); |
| assertEquals("Currency field second", |
| Field(UFIELD_CATEGORY_NUMBER, UNUM_CURRENCY_FIELD).bits, |
| sb.fieldAt(i + str.length()).bits); |
| } |
| |
| // Very basic FieldPosition test. More robust tests happen in NumberFormatTest. |
| // Let NumberFormatTest also take care of FieldPositionIterator material. |
| FieldPosition fp(UNUM_CURRENCY_FIELD); |
| sbi.nextFieldPosition(fp, status); |
| assertSuccess("Populating the FieldPosition", status); |
| assertEquals("Currency start position", str.length(), fp.getBeginIndex()); |
| assertEquals("Currency end position", str.length() * 2, fp.getEndIndex()); |
| |
| if (str.length() > 0) { |
| sb.insertCodePoint(2, 100, {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD}, status); |
| assertSuccess("Inserting code point into sb", status); |
| assertEquals("New length", str.length() * 2 + 1, sb.length()); |
| assertEquals("Integer field", |
| Field(UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD).bits, |
| sb.fieldAt(2).bits); |
| } |
| |
| FormattedStringBuilder old(sb); |
| sb.append(old, status); |
| assertSuccess("Appending to myself", status); |
| int32_t numNull = 0; |
| int32_t numCurr = 0; |
| int32_t numInt = 0; |
| for (int32_t i = 0; i < sb.length(); i++) { |
| auto field = sb.fieldAt(i); |
| assertEquals("Field should equal location in old", |
| old.fieldAt(i % old.length()).bits, field.bits); |
| if (field == kUndefinedField) { |
| numNull++; |
| } else if (field == Field(UFIELD_CATEGORY_NUMBER, UNUM_CURRENCY_FIELD)) { |
| numCurr++; |
| } else if (field == Field(UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD)) { |
| numInt++; |
| } else { |
| errln("Encountered unknown field"); |
| } |
| } |
| assertEquals("Number of null fields", str.length() * 2, numNull); |
| assertEquals("Number of currency fields", numNull, numCurr); |
| assertEquals("Number of integer fields", str.length() > 0 ? 2 : 0, numInt); |
| } |
| } |
| |
| void FormattedStringBuilderTest::testUnlimitedCapacity() { |
| UErrorCode status = U_ZERO_ERROR; |
| FormattedStringBuilder builder; |
| // The builder should never fail upon repeated appends. |
| for (int i = 0; i < 1000; i++) { |
| UnicodeString message("Iteration #"); |
| message += Int64ToUnicodeString(i); |
| assertEquals(message, builder.length(), i); |
| builder.appendCodePoint(u'x', kUndefinedField, status); |
| assertSuccess(message, status); |
| assertEquals(message, builder.length(), i + 1); |
| } |
| } |
| |
| void FormattedStringBuilderTest::testCodePoints() { |
| UErrorCode status = U_ZERO_ERROR; |
| FormattedStringBuilder nsb; |
| assertEquals("First is -1 on empty string", -1, nsb.getFirstCodePoint()); |
| assertEquals("Last is -1 on empty string", -1, nsb.getLastCodePoint()); |
| assertEquals("Length is 0 on empty string", 0, nsb.codePointCount()); |
| |
| nsb.append(u"q", kUndefinedField, status); |
| assertSuccess("Spot 1", status); |
| assertEquals("First is q", u'q', nsb.getFirstCodePoint()); |
| assertEquals("Last is q", u'q', nsb.getLastCodePoint()); |
| assertEquals("0th is q", u'q', nsb.codePointAt(0)); |
| assertEquals("Before 1st is q", u'q', nsb.codePointBefore(1)); |
| assertEquals("Code point count is 1", 1, nsb.codePointCount()); |
| |
| // 🚀 is two char16s |
| nsb.append(u"🚀", kUndefinedField, status); |
| assertSuccess("Spot 2" ,status); |
| assertEquals("First is still q", u'q', nsb.getFirstCodePoint()); |
| assertEquals("Last is space ship", 128640, nsb.getLastCodePoint()); |
| assertEquals("1st is space ship", 128640, nsb.codePointAt(1)); |
| assertEquals("Before 1st is q", u'q', nsb.codePointBefore(1)); |
| assertEquals("Before 3rd is space ship", 128640, nsb.codePointBefore(3)); |
| assertEquals("Code point count is 2", 2, nsb.codePointCount()); |
| } |
| |
| void FormattedStringBuilderTest::assertEqualsImpl(const UnicodeString &a, const FormattedStringBuilder &b) { |
| // TODO: Why won't this compile without the IntlTest:: qualifier? |
| IntlTest::assertEquals("Lengths should be the same", a.length(), b.length()); |
| IntlTest::assertEquals("Code point counts should be the same", a.countChar32(), b.codePointCount()); |
| |
| if (a.length() != b.length()) { |
| return; |
| } |
| |
| for (int32_t i = 0; i < a.length(); i++) { |
| IntlTest::assertEquals( |
| UnicodeString(u"Char at position ") + Int64ToUnicodeString(i) + |
| UnicodeString(u" in \"") + a + UnicodeString("\" versus \"") + |
| b.toUnicodeString() + UnicodeString("\""), a.charAt(i), b.charAt(i)); |
| } |
| } |
| |
| |
| extern IntlTest *createFormattedStringBuilderTest() { |
| return new FormattedStringBuilderTest(); |
| } |
| |
| #endif /* #if !UCONFIG_NO_FORMATTING */ |