diff --git a/TODO b/TODO
index ac7c01c..86560a8 100644
--- a/TODO
+++ b/TODO
@@ -1,4 +1,5 @@
 * Tests for T_FSPath
+* Tests for T_ArrayHelpers
 * Generalize iterators
 * Fix output of appendDouble so it's consistent on both Linux
   and Windows
diff --git a/tests/strings.cc b/tests/strings.cc
index f6a446b..1bcbc4c 100644
--- a/tests/strings.cc
+++ b/tests/strings.cc
@@ -130,6 +130,42 @@ class StringsTest : public CppUnit::TestFixture
 		CPPUNIT_TEST( testMapped );
 		CPPUNIT_TEST( testToUpper );
 		CPPUNIT_TEST( testToLower );
+
+		CPPUNIT_TEST( testLeft );
+		CPPUNIT_TEST( testLeftTooLong );
+		CPPUNIT_TEST( testRight );
+		CPPUNIT_TEST( testRightTooLong );
+
+		CPPUNIT_TEST( testSubstrUnlimited );
+		CPPUNIT_TEST( testSubstrLen );
+		CPPUNIT_TEST( testSubstrZero );
+		CPPUNIT_TEST( testSubstrHighOffset );
+
+		CPPUNIT_TEST( testRange );
+		CPPUNIT_TEST( testRangeSame );
+		CPPUNIT_TEST( testRangeInverted );
+		CPPUNIT_TEST( testRangeHighStart );
+		CPPUNIT_TEST( testRangeHighEnd );
+
+		CPPUNIT_TEST( testTrimEmpty );
+		CPPUNIT_TEST( testTrimNone );
+		CPPUNIT_TEST( testTrimStart );
+		CPPUNIT_TEST( testTrimEnd );
+		CPPUNIT_TEST( testTrimBoth );
+
+		CPPUNIT_TEST( testRepChar );
+		CPPUNIT_TEST( testRepCharEmpty );
+		CPPUNIT_TEST( testRepCharMissing );
+		CPPUNIT_TEST( testRepCharLonger );
+		CPPUNIT_TEST( testRepCharShorter );
+
+		CPPUNIT_TEST( testRepStringSame );
+		CPPUNIT_TEST( testRepStringLonger );
+		CPPUNIT_TEST( testRepStringShorter );
+		CPPUNIT_TEST( testRepStringEmpty );
+		CPPUNIT_TEST( testRepStringMissing );
+		CPPUNIT_TEST( testRepStringEmptyInitial );
+		CPPUNIT_TEST( testRepStringLongInitial );
 	CPPUNIT_TEST_SUITE_END( );
 
 public:
@@ -257,6 +293,42 @@ public:
 	void testMapped( );
 	void testToUpper( );
 	void testToLower( );
+
+	void testLeft( );
+	void testLeftTooLong( );
+	void testRight( );
+	void testRightTooLong( );
+
+	void testSubstrUnlimited( );
+	void testSubstrLen( );
+	void testSubstrZero( );
+	void testSubstrHighOffset( );
+
+	void testRange( );
+	void testRangeSame( );
+	void testRangeInverted( );
+	void testRangeHighStart( );
+	void testRangeHighEnd( );
+
+	void testTrimEmpty( );
+	void testTrimNone( );
+	void testTrimStart( );
+	void testTrimEnd( );
+	void testTrimBoth( );
+
+	void testRepChar( );
+	void testRepCharEmpty( );
+	void testRepCharMissing( );
+	void testRepCharLonger( );
+	void testRepCharShorter( );
+
+	void testRepStringSame( );
+	void testRepStringShorter( );
+	void testRepStringLonger( );
+	void testRepStringEmpty( );
+	void testRepStringMissing( );
+	void testRepStringEmptyInitial( );
+	void testRepStringLongInitial( );
 };
 CPPUNIT_TEST_SUITE_REGISTRATION( StringsTest );
 
@@ -1221,3 +1293,241 @@ void StringsTest::testToLower( )
 	CPPUNIT_ASSERT_EQUAL( test.length( ) , result.length( ) );
 	CPPUNIT_ASSERT( result.equals( expected ) );
 }
+
+/*----------------------------------------------------------------------------*/
+
+void StringsTest::testLeft( )
+{
+	const T_String test{ "This IS a test, 123" };
+	const T_String expected{ "This" };
+	const T_String result{ test.left( 4 ) };
+	CPPUNIT_ASSERT( result.equals( expected ) );
+}
+
+void StringsTest::testLeftTooLong( )
+{
+	const T_String test{ "This IS a test, 123" };
+	const T_String result{ test.left( 400 ) };
+	CPPUNIT_ASSERT( result.equals( test ) );
+}
+
+void StringsTest::testRight( )
+{
+	const T_String test{ "This IS a test, 123" };
+	const T_String expected{ " 123" };
+	const T_String result{ test.right( 4 ) };
+	CPPUNIT_ASSERT( result.equals( expected ) );
+}
+
+void StringsTest::testRightTooLong( )
+{
+	const T_String test{ "This IS a test, 123" };
+	const T_String result{ test.right( 400 ) };
+	CPPUNIT_ASSERT( result.equals( test ) );
+}
+
+/*----------------------------------------------------------------------------*/
+
+void StringsTest::testSubstrUnlimited( )
+{
+	const T_String test{ "This IS a test, 123" };
+	const T_String expected{ "IS a test, 123" };
+	const T_String result{ test.substr( 5 ) };
+	CPPUNIT_ASSERT( result.equals( expected ) );
+}
+
+void StringsTest::testSubstrLen( )
+{
+	const T_String test{ "This IS a test, 123" };
+	const T_String expected{ "IS " };
+	const T_String result{ test.substr( 5 , 3 ) };
+	CPPUNIT_ASSERT( result.equals( expected ) );
+}
+
+void StringsTest::testSubstrZero( )
+{
+	const T_String test{ "This IS a test, 123" };
+	const T_String result{ test.substr( 5 , 0 ) };
+	CPPUNIT_ASSERT( !result );
+}
+
+void StringsTest::testSubstrHighOffset( )
+{
+	const T_String test{ "This IS a test, 123" };
+	const T_String result{ test.substr( 400 ) };
+	CPPUNIT_ASSERT( !result );
+}
+
+/*----------------------------------------------------------------------------*/
+
+void StringsTest::testRange( )
+{
+	const T_String test{ "This IS a test, 123" };
+	const T_String expected{ "IS" };
+	const T_String result{ test.range( 5 , 6 ) };
+	CPPUNIT_ASSERT( result.equals( expected ) );
+}
+
+void StringsTest::testRangeSame( )
+{
+	const T_String test{ "This IS a test, 123" };
+	const T_String expected{ "I" };
+	const T_String result{ test.range( 5 , 5 ) };
+	CPPUNIT_ASSERT( result.equals( expected ) );
+}
+
+void StringsTest::testRangeInverted( )
+{
+	const T_String test{ "This IS a test, 123" };
+	const T_String result{ test.range( 1 , 0 ) };
+	CPPUNIT_ASSERT( !result );
+}
+
+void StringsTest::testRangeHighStart( )
+{
+	const T_String test{ "This IS a test, 123" };
+	const T_String result{ test.range( 400 , 0 ) };
+	CPPUNIT_ASSERT( !result );
+}
+
+void StringsTest::testRangeHighEnd( )
+{
+	const T_String test{ "This IS a test, 123" };
+	const T_String result{ test.range( 16 , UINT32_MAX ) };
+	const T_String expected{ "123" };
+	CPPUNIT_ASSERT( result.equals( expected ) );
+}
+
+/*----------------------------------------------------------------------------*/
+
+void StringsTest::testTrimEmpty( )
+{
+	const T_String test;
+	const T_String result{ test.trim( ) };
+	CPPUNIT_ASSERT( result.equals( test ) );
+}
+
+void StringsTest::testTrimNone( )
+{
+	const T_String test{ "abc" };
+	const T_String result{ test.trim( ) };
+	CPPUNIT_ASSERT( result.equals( test ) );
+}
+
+void StringsTest::testTrimStart( )
+{
+	const T_String test{ "   \t\r\nabc" };
+	const T_String result{ test.trim( ) };
+	const T_String expected{ "abc" };
+	CPPUNIT_ASSERT( result.equals( expected ) );
+}
+
+void StringsTest::testTrimEnd( )
+{
+	const T_String test{ "abc   \t\r\n" };
+	const T_String result{ test.trim( ) };
+	const T_String expected{ "abc" };
+	CPPUNIT_ASSERT( result.equals( expected ) );
+}
+
+void StringsTest::testTrimBoth( )
+{
+	const T_String test{ "   \t\r\nabc   \t\r\n" };
+	const T_String result{ test.trim( ) };
+	const T_String expected{ "abc" };
+	CPPUNIT_ASSERT( result.equals( expected ) );
+}
+
+/*----------------------------------------------------------------------------*/
+
+void StringsTest::testRepChar( )
+{
+	const T_String test{ "aXXa" };
+	const T_String result{ test.replace( 'X' , 'b' ) };
+	const T_String expected{ "abba" };
+	CPPUNIT_ASSERT( result.equals( expected ) );
+}
+
+void StringsTest::testRepCharEmpty( )
+{
+	const T_String test{ };
+	const T_String result{ test.replace( 'X' , 'a' ) };
+	CPPUNIT_ASSERT( result.equals( test ) );
+}
+
+void StringsTest::testRepCharMissing( )
+{
+	const T_String test{ "abba" };
+	const T_String result{ test.replace( 'X' , 'a' ) };
+	CPPUNIT_ASSERT( result.equals( test ) );
+}
+
+void StringsTest::testRepCharLonger( )
+{
+	const T_String test{ "a\u20ac\u20aca" };
+	const T_String result{ test.replace( 0x20ac , 'b' ) };
+	const T_String expected{ "abba" };
+	CPPUNIT_ASSERT( result.equals( expected ) );
+}
+
+void StringsTest::testRepCharShorter( )
+{
+	const T_String test{ "abba" };
+	const T_String result{ test.replace( 'b' , 0xf1bb ) };
+	const T_String expected{ "a\uf1bb\uf1bba" };
+	CPPUNIT_ASSERT( result.equals( expected ) );
+}
+
+/*----------------------------------------------------------------------------*/
+
+void StringsTest::testRepStringSame( )
+{
+	const T_String test{ "aXXa" };
+	const T_String result{ test.replace( "X" , "b" ) };
+	const T_String expected{ "abba" };
+	CPPUNIT_ASSERT( result.equals( expected ) );
+}
+
+void StringsTest::testRepStringShorter( )
+{
+	const T_String test{ "aXXXXa" };
+	const T_String result{ test.replace( "XX" , "b" ) };
+	const T_String expected{ "abba" };
+	CPPUNIT_ASSERT( result.equals( expected ) );
+}
+
+void StringsTest::testRepStringLonger( )
+{
+	const T_String test{ "aXXa" };
+	const T_String result{ test.replace( "X" , "EH" ) };
+	const T_String expected{ "aEHEHa" };
+	CPPUNIT_ASSERT( result.equals( expected ) );
+}
+
+void StringsTest::testRepStringEmpty( )
+{
+	const T_String test;
+	const T_String result{ test.replace( "X" , "EH" ) };
+	CPPUNIT_ASSERT( result.equals( test ) );
+}
+
+void StringsTest::testRepStringMissing( )
+{
+	const T_String test{ "aXXa" };
+	const T_String result{ test.replace( "z" , "!" ) };
+	CPPUNIT_ASSERT( result.equals( test ) );
+}
+
+void StringsTest::testRepStringEmptyInitial( )
+{
+	const T_String test{ "aXXa" };
+	const T_String result{ test.replace( "" , "!" ) };
+	CPPUNIT_ASSERT( result.equals( test ) );
+}
+
+void StringsTest::testRepStringLongInitial( )
+{
+	const T_String test{ "aXXa" };
+	const T_String result{ test.replace( "wawawawa" , "!" ) };
+	CPPUNIT_ASSERT( result.equals( test ) );
+}