Added javadoc and significant test cases
diff --git a/src/main/java/org/apache/commons/text/CasedString.java b/src/main/java/org/apache/commons/text/CasedString.java
index 5d1153a..34418cf 100644
--- a/src/main/java/org/apache/commons/text/CasedString.java
+++ b/src/main/java/org/apache/commons/text/CasedString.java
@@ -20,6 +20,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Locale;
 import java.util.function.Function;
 import java.util.function.Predicate;
 
@@ -37,46 +38,56 @@
      * A method to join camel string fragments together.
      */
     private static final Function<String[], String> CAMEL_JOINER = a -> {
-        StringBuilder sb = new StringBuilder(a[0]);
+        StringBuilder sb = new StringBuilder(a[0].toLowerCase(Locale.ROOT));
 
         for (int i = 1; i < a.length; i++) {
-            sb.append(WordUtils.capitalize(a[i]));
+            sb.append(WordUtils.capitalize(a[i].toLowerCase(Locale.ROOT)));
         }
         return sb.toString();
     };
 
     /**
-     * An enumeration of supported string cases.
+     * An enumeration of supported string cases.  These cases tag strings as having a specific format.
      */
     public enum StringCase {
         /**
-         * Camel case identifies strings like 'CamelCase'.
+         * Camel case tags strings like 'CamelCase' or 'camelCase'. This conversion forces the first character to
+         * lower case. If specific capitalization rules are required use {@code WordUtils.capitalize()} to set the first
+         * character of the string.
          */
-        Camel(Character::isUpperCase, true, CAMEL_JOINER),
+        CAMEL(Character::isUpperCase, true, CAMEL_JOINER),
         /**
-         * Snake case identifies strings like 'Snake_Case'.
+         * Snake case tags strings like 'Snake_Case'.  This conversion does not change the capitalization of any characters
+         * in the string.  If specific capitalization is required use {@code String.upperCase}, {@code String.upperCase},
+         * {@code WordUtils.capitalize()}, or {@code WordUtils.uncapitalize()} as required.
          */
-        Snake(c -> c == '_', false, a -> String.join("_", a)),
+        SNAKE(c -> c == '_', false, a -> String.join("_", a)),
         /**
-         * Kebab case identifies strings like 'kebab-case'.
+         * Kebab case tags strings like 'kebab-case'.  This conversion does not change the capitalization of any characters
+         * in the string.  If specific capitalization is required use {@code String.upperCase}, {@code String.upperCase},
+         * {@code WordUtils.capitalize()}, or {@code WordUtils.uncapitalize()} as required.
          */
-        Kebab(c -> c == '-', false, a -> String.join("-", a)),
+        KEBAB(c -> c == '-', false, a -> String.join("-", a)),
 
         /**
-         * Phrase case identifies phrases of words like 'phrase case'.
+         * Phrase case tags phrases of words like 'phrase case'. This conversion does not change the capitalization of any characters
+         * in the string.  If specific capitalization is required use {@code String.upperCase}, {@code String.upperCase},
+         * {@code WordUtils.capitalize()}, or {@code WordUtils.uncapitalize()} as required.
          */
-        Phrase(c -> c == ' ', false, a -> String.join(" ", a)),
+        PHRASE(Character::isWhitespace, false, a -> String.join(" ", a)),
 
         /**
-         * Dot case identifies strings of words like 'dot.case'.
+         * Dot case tags phrases of words like 'phrase.case'. This conversion does not change the capitalization of any characters
+         * in the string.  If specific capitalization is required use {@code String.upperCase}, {@code String.upperCase},
+         * {@code WordUtils.capitalize()}, or {@code WordUtils.uncapitalize()} as required.
          */
-        Dot(c -> c == '.', false, a -> String.join(".", a));
+        DOT(c -> c == '.', false, a -> String.join(".", a));
 
         /** test for split position character. */
         private final Predicate<Character> splitter;
         /** if {@code true} split position character will be preserved in following segment. */
         private final boolean preserveSplit;
-        /** a function to joing the segments into this case type. */
+        /** a function to joining the segments into this case type. */
         private final Function<String[], String> joiner;
 
         /**
@@ -90,6 +101,48 @@
             this.preserveSplit = preserveSplit;
             this.joiner = joiner;
         }
+
+        /**
+         * Creates a cased string from a collection of segments.
+         * @param segments the segments to create the CasedString from.
+         * @return a CasedString
+         */
+        public String assemble(String[] segments) {
+            return segments.length == 0 ? null : this.joiner.apply(segments);
+        }
+
+        /**
+         * Returns an array of each of the segments in this CasedString.  Segments are defined as the strings between
+         * the separators in the CasedString.  for the CAMEL case the segments are determined by the presence of a capital letter.
+         * @return the array of Strings that are segments of the cased string.
+         */
+        public String[] getSegments(String string) {
+            if (string == null) {
+                return new String[0];
+            }
+            if (string.isEmpty()) {
+                return new String[]{""};
+            }
+            List<String> lst = new ArrayList<>();
+            StringBuilder sb = new StringBuilder();
+            for (char c : string.toCharArray()) {
+                if (splitter.test(c)) {
+                    if (sb.length() > 0) {
+                        lst.add(sb.toString());
+                        sb.setLength(0);
+                    }
+                    if (preserveSplit) {
+                        sb.append(c);
+                    }
+                } else {
+                    sb.append(c);
+                }
+            }
+            if (sb.length() > 0) {
+                lst.add(sb.toString());
+            }
+            return lst.toArray(new String[0]);
+        }
     }
 
     /**
@@ -98,30 +151,17 @@
      * @param string The string.
      */
     public CasedString(StringCase stringCase, String string) {
-        this.string = string;
+        this.string = string == null ? null : stringCase.assemble(stringCase.getSegments(string.trim()));
         this.stringCase = stringCase;
     }
 
-    private String[] split() {
-        List<String> lst = new ArrayList<>();
-        StringBuilder sb = new StringBuilder();
-        for (char c : string.toCharArray()) {
-            if (stringCase.splitter.test(c)) {
-                if (sb.length() > 0) {
-                    lst.add(sb.toString());
-                    sb.setLength(0);
-                }
-                if (stringCase.preserveSplit) {
-                    sb.append(c);
-                }
-            } else {
-                sb.append(c);
-            }
-        }
-        if (sb.length() > 0) {
-            lst.add(sb.toString());
-        }
-        return lst.toArray(new String[0]);
+    /**
+     * Returns an array of each of the segments in this CasedString.  Segments are defined as the strings between
+     * the separators in the CasedString.  for the CAMEL case the segments are determined by the presence of a capital letter.
+     * @return the array of Strings that are segments of the cased string.
+     */
+    public String[] getSegments() {
+        return stringCase.getSegments(string);
     }
 
     /**
@@ -134,7 +174,7 @@
         if (stringCase == this.stringCase) {
             return string;
         }
-        return stringCase.joiner.apply(split());
+        return string == null ? null : stringCase.joiner.apply(getSegments());
     }
 
     /**
diff --git a/src/test/java/org/apache/commons/text/CasedStringTest.java b/src/test/java/org/apache/commons/text/CasedStringTest.java
index f0c0007..106a93a 100644
--- a/src/test/java/org/apache/commons/text/CasedStringTest.java
+++ b/src/test/java/org/apache/commons/text/CasedStringTest.java
@@ -18,48 +18,141 @@
  */
 package org.apache.commons.text;
 
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.stream.Stream;
 
+import org.apache.commons.text.CasedString.StringCase;
+import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
 
 public class CasedStringTest {
 
-    private static String expectedValue(CasedString.StringCase stringCase) {
+    private static String helloWorldValue(StringCase stringCase) {
         switch (stringCase) {
-            case Camel:
-                return "HelloWorld";
-            case Kebab:
-                return "Hello-World";
-            case Phrase:
-                return "Hello World";
-            case Snake:
-                return "Hello_World";
-            case Dot:
-                return "Hello.World";
+            case CAMEL:
+                return "helloWorld";
+            case KEBAB:
+                return "hello-World";
+            case PHRASE:
+                return "hello World";
+            case SNAKE:
+                return "hello_World";
+            case DOT:
+                return "hello.World";
             default:
-                throw new RuntimeException("Unsupported StringCase: " + stringCase);
+                fail("Unsupported StringCase: " + stringCase);
         }
+        return null; // keeps compiler happy
     }
 
+    private static final CasedString CAMEL = new CasedString(StringCase.CAMEL, "aCamelString");
+    private static final CasedString PHRASE = new CasedString(StringCase.PHRASE, "A test PhrAse");
+    private static final CasedString KEBAB = new CasedString(StringCase.KEBAB, "A-kebAb-string");
+    private static final CasedString SNAKE = new CasedString(StringCase.SNAKE, "A_snaKE_string");
+    private static final CasedString DOT = new CasedString(StringCase.DOT, "A.dOt.string");
+    private static final CasedString ABCDEF = new CasedString(StringCase.PHRASE, "a  b  c  @def");
+    /**
+     * tests the conversion from each Cased string type to every other type.
+     * @param underTest the CasedString being tested.
+     */
     @ParameterizedTest
     @MethodSource("conversionProvider")
-    public void testConversions(CasedString underTest) {
-        for (CasedString.StringCase stringCase : CasedString.StringCase.values()) {
-            assertEquals(expectedValue(stringCase), underTest.toCase(stringCase), () -> "failed converting to " + stringCase);
+    public void testCrossProductConversions(CasedString underTest) {
+        for (StringCase stringCase : StringCase.values()) {
+            assertEquals(helloWorldValue(stringCase), underTest.toCase(stringCase), () -> "failed converting to " + stringCase);
+        }
+    }
+    /* generates the hello world Cased String for every StringCase */
+    private static Stream<Arguments> conversionProvider() {
+        List<Arguments> lst = new ArrayList<>();
+        for (StringCase stringCase : StringCase.values()) {
+            lst.add(Arguments.of(new CasedString(stringCase, helloWorldValue(stringCase))));
+        }
+        return lst.stream();
+    }
+
+    @Test
+    public void testNullConstructor() {
+        for (StringCase stringCase : StringCase.values()) {
+            CasedString underTest = new CasedString(stringCase, null);
+            assertThat(underTest.toString()).isNull();
+            assertThat(underTest.getSegments()).isEmpty();
+            // test a null underTest can convert to all others types.
+            for (CasedString.StringCase otherCase : StringCase.values()) {
+                assertThat(underTest.toCase(otherCase)).isNull();
+            }
         }
     }
 
-    private static Stream<Arguments> conversionProvider() {
-        List<Arguments> lst = new ArrayList<>();
-        for (CasedString.StringCase stringCase : CasedString.StringCase.values()) {
-            lst.add(Arguments.of(new CasedString(stringCase, expectedValue(stringCase))));
-        }
-        return lst.stream();
+    @Test
+    public void testToCamelCase() {
+        assertThat(new CasedString(StringCase.CAMEL, "").toString()).isEqualTo("");
+        assertThat(new CasedString(StringCase.CAMEL, "  ").toString()).isEqualTo("");
+        assertThat(new CasedString(StringCase.CAMEL, "Tocamelcase").toString()).isEqualTo("tocamelcase");
+        assertThat(new CasedString(StringCase.PHRASE, "\uD800\uDF00 \uD800\uDF02").toCase(StringCase.CAMEL)).isEqualTo("\uD800\uDF00\uD800\uDF02");
+        assertThat(ABCDEF.toCase(StringCase.CAMEL)).isEqualTo("aBC@def");
+        assertThat(CAMEL.toCase(StringCase.CAMEL)).isEqualTo("aCamelString");
+        assertThat(PHRASE.toCase(StringCase.CAMEL)).isEqualTo("aTestPhrase");
+        assertThat(KEBAB.toCase(StringCase.CAMEL)).isEqualTo("aKebabString");
+        assertThat(SNAKE.toCase(StringCase.CAMEL)).isEqualTo("aSnakeString");
+        assertThat(DOT.toCase(StringCase.CAMEL)).isEqualTo("aDotString");
+        assertThat(new CasedString(StringCase.PHRASE, "TO  CAMEL CASE").toCase(StringCase.CAMEL)).isEqualTo("toCamelCase");
+        assertThat(new CasedString(StringCase.KEBAB, "   to-CAMEL-cASE").toCase(StringCase.CAMEL)).isEqualTo("toCamelCase");
+        assertThat(new CasedString(StringCase.DOT, "To.Camel.Case").toCase(StringCase.CAMEL)).isEqualTo("toCamelCase");
+    }
+
+    @Test
+    public void testToPhraseTest() {
+        assertThat(new CasedString(StringCase.PHRASE, "").toString()).isEqualTo("");
+        assertThat(new CasedString(StringCase.PHRASE, "  ").toString()).isEqualTo("");
+        assertThat(ABCDEF.toCase(StringCase.PHRASE)).isEqualTo("a b c @def");
+        assertThat(CAMEL.toCase(StringCase.PHRASE)).isEqualTo("a Camel String");
+        assertThat(PHRASE.toCase(StringCase.PHRASE)).isEqualTo("A test PhrAse");
+        assertThat(KEBAB.toCase(StringCase.PHRASE)).isEqualTo("A kebAb string");
+        assertThat(SNAKE.toCase(StringCase.PHRASE)).isEqualTo("A snaKE string");
+        assertThat(DOT.toCase(StringCase.PHRASE)).isEqualTo("A dOt string");
+    }
+
+    @Test
+    public void testToKebabTest() {
+        assertThat(new CasedString(StringCase.KEBAB, "").toString()).isEqualTo("");
+        assertThat(new CasedString(StringCase.KEBAB, "  ").toString()).isEqualTo("");
+        assertThat(ABCDEF.toCase(StringCase.KEBAB)).isEqualTo("a-b-c-@def");
+        assertThat(CAMEL.toCase(StringCase.KEBAB)).isEqualTo("a-Camel-String");
+        assertThat(PHRASE.toCase(StringCase.KEBAB)).isEqualTo("A-test-PhrAse");
+        assertThat(KEBAB.toCase(StringCase.KEBAB)).isEqualTo("A-kebAb-string");
+        assertThat(SNAKE.toCase(StringCase.KEBAB)).isEqualTo("A-snaKE-string");
+        assertThat(DOT.toCase(StringCase.KEBAB)).isEqualTo("A-dOt-string");
+    }
+
+    @Test
+    public void testToSnakeTest() {
+        assertThat(new CasedString(StringCase.SNAKE, "").toString()).isEqualTo("");
+        assertThat(new CasedString(StringCase.SNAKE, "  ").toString()).isEqualTo("");
+        assertThat(ABCDEF.toCase(StringCase.SNAKE)).isEqualTo("a_b_c_@def");
+        assertThat(CAMEL.toCase(StringCase.SNAKE)).isEqualTo("a_Camel_String");
+        assertThat(PHRASE.toCase(StringCase.SNAKE)).isEqualTo("A_test_PhrAse");
+        assertThat(KEBAB.toCase(StringCase.SNAKE)).isEqualTo("A_kebAb_string");
+        assertThat(SNAKE.toCase(StringCase.SNAKE)).isEqualTo("A_snaKE_string");
+        assertThat(DOT.toCase(StringCase.SNAKE)).isEqualTo("A_dOt_string");
+    }
+
+    @Test
+    public void testToDotTest() {
+        assertThat(new CasedString(StringCase.DOT, "").toString()).isEqualTo("");
+        assertThat(new CasedString(StringCase.DOT, "  ").toString()).isEqualTo("");
+        assertThat(ABCDEF.toCase(StringCase.DOT)).isEqualTo("a.b.c.@def");
+        assertThat(CAMEL.toCase(StringCase.DOT)).isEqualTo("a.Camel.String");
+        assertThat(PHRASE.toCase(StringCase.DOT)).isEqualTo("A.test.PhrAse");
+        assertThat(KEBAB.toCase(StringCase.DOT)).isEqualTo("A.kebAb.string");
+        assertThat(SNAKE.toCase(StringCase.DOT)).isEqualTo("A.snaKE.string");
+        assertThat(DOT.toCase(StringCase.DOT)).isEqualTo("A.dOt.string");
     }
 }