+///////////////////////////////////////////////////////////////////////////////
+// Name:        tests/formatconverter/formatconverter.cpp
+// Purpose:     Test wxFormatConverter
+// Author:      Mike Wetherell
+// RCS-ID:      $Id$
+// Copyright:   (c) 2004 Mike Wetherell
+// Licence:     wxWidgets licence
+///////////////////////////////////////////////////////////////////////////////
+
+//
+// Notes:
+//
+// The conversions wxFormatConverter currently does are as follows:
+//
+//    %s, %lS   ->  %ls
+//    %S, %hS   ->  %s
+//    %c, %lC   ->  %lc
+//    %C, %hC   ->  %c
+//
+// %hs and %hc stay the same.
+//
+// %S and %C aren't actually in the ISO C or C++ standards, but they can be
+// avoided when writing portable code.
+//
+// Nor are %hs or %hc in the standards, which means wxWidgets currently doesn't
+// have a specifier for 'char' types that is ok for all builds and platforms.
+//
+// The effect of using %hs/%hc is undefined, though RTLs are quite likely
+// to just ignore the 'h', so maybe it works as required even though it's
+// not legal.
+//
+// I've put in some checks, such as this which will flag up any platforms
+// where this is not the case:
+//
+//  CPPUNIT_ASSERT(wxString::Format(_T("%hs"), "test") == _T("test"));
+//
+
+#if defined(__GNUG__) && !defined(__APPLE__)
+    #pragma implementation
+    #pragma interface
+#endif
+
+// For compilers that support precompilation, includes "wx/wx.h".
+#include "wx/wxprec.h"
+
+#ifdef __BORLANDC__
+    #pragma hdrstop
+#endif
+
+// for all others, include the necessary headers
+#ifndef WX_PRECOMP
+    #include "wx/wx.h"
+#endif
+
+#include "wx/cppunit.h"
+
+// wxFormatConverter can only be tested in a Unicode non-Windows debug build
+//
+#if defined(wxNEED_PRINTF_CONVERSION) && defined(__WXDEBUG__)
+#define CAN_TEST
+extern wxString wxConvertFormat(const wxChar *format);
+#endif
+
+using namespace std;
+using namespace CppUnit;
+
+
+///////////////////////////////////////////////////////////////////////////////
+// The test case
+//
+// wxFormatConverter only changes %s, %c, %S and %C, all others are treated
+// equally, therefore it is enough to choose just one other for testing, %d
+// will do.
+
+class FormatConverterTestCase : public TestCase
+{
+    CPPUNIT_TEST_SUITE(FormatConverterTestCase);
+        CPPUNIT_TEST(format_d);
+        CPPUNIT_TEST(format_hd);
+        CPPUNIT_TEST(format_ld);
+        CPPUNIT_TEST(format_s);
+        CPPUNIT_TEST(format_hs);
+        CPPUNIT_TEST(format_ls);
+        CPPUNIT_TEST(format_c);
+        CPPUNIT_TEST(format_hc);
+        CPPUNIT_TEST(format_lc);
+#ifdef CAN_TEST
+        CPPUNIT_TEST(format_S);
+        CPPUNIT_TEST(format_hS);
+        CPPUNIT_TEST(format_lS);
+        CPPUNIT_TEST(format_C);
+        CPPUNIT_TEST(format_hC);
+        CPPUNIT_TEST(format_lC);
+        CPPUNIT_TEST(testLonger);
+#endif
+    CPPUNIT_TEST_SUITE_END();
+
+private:
+    void format_d();
+    void format_hd();
+    void format_ld();
+    void format_s();
+    void format_hs();
+    void format_ls();
+    void format_c();
+    void format_hc();
+    void format_lc();
+
+#ifdef CAN_TEST
+    void format_S();
+    void format_hS();
+    void format_lS();
+    void format_C();
+    void format_hC();
+    void format_lC();
+    void testLonger();
+
+    void doTest(const wxChar *input, const wxChar *expected);
+    void check(const wxString& input, const wxString& expected);
+#endif
+};
+
+void FormatConverterTestCase::format_d()
+{
+#ifdef CAN_TEST
+    doTest(_T("d"), _T("d"));
+#endif
+    CPPUNIT_ASSERT(wxString::Format(_T("%d"), 255) == _T("255"));
+}
+
+void FormatConverterTestCase::format_hd()
+{
+#ifdef CAN_TEST
+    doTest(_T("hd"), _T("hd"));
+#endif
+    short s = 32767;
+    CPPUNIT_ASSERT(wxString::Format(_T("%hd"), s) == _T("32767"));
+}
+
+void FormatConverterTestCase::format_ld()
+{
+#ifdef CAN_TEST
+    doTest(_T("ld"), _T("ld"));
+#endif
+    long l = 2147483647L;
+    CPPUNIT_ASSERT(wxString::Format(_T("%ld"), l) == _T("2147483647"));
+}
+
+void FormatConverterTestCase::format_s()
+{
+#ifdef CAN_TEST
+    doTest(_T("s"), _T("ls"));
+#endif
+    CPPUNIT_ASSERT(wxString::Format(_T("%s"), _T("test")) == _T("test"));
+}
+
+void FormatConverterTestCase::format_hs()
+{
+#ifdef CAN_TEST
+    doTest(_T("hs"), _T("hs"));
+#endif
+    CPPUNIT_ASSERT(wxString::Format(_T("%hs"), "test") == _T("test"));
+}
+
+void FormatConverterTestCase::format_ls()
+{
+#ifdef CAN_TEST
+    doTest(_T("ls"), _T("ls"));
+#endif
+    CPPUNIT_ASSERT(wxString::Format(_T("%ls"), L"test") == _T("test"));
+}
+
+void FormatConverterTestCase::format_c()
+{
+#ifdef CAN_TEST
+    doTest(_T("c"), _T("lc"));
+#endif
+    CPPUNIT_ASSERT(wxString::Format(_T("%c"), _T('x')) == _T("x"));
+}
+
+void FormatConverterTestCase::format_hc()
+{
+#ifdef CAN_TEST
+    doTest(_T("hc"), _T("hc"));
+#endif
+    CPPUNIT_ASSERT(wxString::Format(_T("%hc"), 'x') == _T("x"));
+}
+
+void FormatConverterTestCase::format_lc()
+{
+#ifdef CAN_TEST
+    doTest(_T("lc"), _T("lc"));
+#endif
+    CPPUNIT_ASSERT(wxString::Format(_T("%lc"), L'x') == _T("x"));
+}
+
+#ifdef CAN_TEST
+
+void FormatConverterTestCase::format_S()  { doTest(_T("S"),  _T("s"));  }
+void FormatConverterTestCase::format_hS() { doTest(_T("hS"), _T("s"));  }
+void FormatConverterTestCase::format_lS() { doTest(_T("lS"), _T("ls")); }
+
+void FormatConverterTestCase::format_C()  { doTest(_T("C"),  _T("c"));  }
+void FormatConverterTestCase::format_hC() { doTest(_T("hC"), _T("c"));  }
+void FormatConverterTestCase::format_lC() { doTest(_T("lC"), _T("lc")); }
+
+// It's possible that although a format converts correctly alone, it leaves
+// the converter in a bad state that will affect subsequent formats, so
+// check with a selection of longer patterns.
+//
+void FormatConverterTestCase::testLonger()
+{
+    struct {
+        const wxChar *input;
+        const wxChar *expected;
+    } formats[] = {
+        { _T("%d"),     _T("%d"),    },
+        { _T("%*hd"),   _T("%*hd")   },
+        { _T("%.4ld"),  _T("%.4ld")  },
+        { _T("%-.*s"),  _T("%-.*ls") },
+        { _T("%.*hs"),  _T("%.*hs"), },
+        { _T("%-.9ls"), _T("%-.9ls") },
+        { _T("%-*c"),   _T("%-*lc")  },
+        { _T("%3hc"),   _T("%3hc")   },
+        { _T("%-5lc"),  _T("%-5lc")  }
+    };
+    size_t i, j;
+
+    // exclude patterns that don't translate correctly alone from the test
+    for (i = 0; i < WXSIZEOF(formats); i++)
+        if (wxConvertFormat(formats[i].input) != formats[i].expected)
+            formats[i].input = NULL;
+
+    // test all possible pairs of the above patterns
+    for (i = 0; i < WXSIZEOF(formats); i++) {
+        if (formats[i].input) {
+            wxString input(formats[i].input);
+            wxString expected(formats[i].expected);
+
+            for (j = 0; j < WXSIZEOF(formats); j++)
+                if (formats[j].input)
+                    check(input + formats[j].input,
+                          expected + formats[j].expected);
+        }
+    }
+}
+
+void FormatConverterTestCase::doTest(const wxChar *input,
+                                     const wxChar *expected)
+{
+    static const wxChar *flag_width[] =
+        { _T(""), _T("*"), _T("10"), _T("-*"), _T("-10"), NULL };
+    static const wxChar *precision[] =
+        { _T(""), _T(".*"), _T(".10"), NULL };
+    static const wxChar *empty[] =
+        { _T(""), NULL };
+
+    // no precision for %c or %C
+    const wxChar **precs = wxTolower(input[wxStrlen(input)-1]) == _T('c') ?
+        empty : precision;
+
+    wxString fmt(_T("%"));
+
+    // try the test for a variety of combinations of flag, width and precision
+    for (const wxChar **prec = precs; *prec; prec++)
+        for (const wxChar **width = flag_width; *width; width++)
+            check(fmt + *width + *prec + input,
+                  fmt + *width + *prec + expected);
+}
+
+void FormatConverterTestCase::check(const wxString& input,
+                                    const wxString& expected)
+{
+    wxString result = wxConvertFormat(input);
+    wxString msg = _T("input: '") + input +
+                   _T("', result: '") + result +
+                   _T("', expected: '") + expected + _T("'");
+    CPPUNIT_ASSERT_MESSAGE(string(msg.mb_str()), result == expected);
+}
+
+#endif // CAN_TEST
+
+// register in the unnamed registry so that these tests are run by default
+CPPUNIT_TEST_SUITE_REGISTRATION(FormatConverterTestCase);
+
+// also include in it's own registry so that these tests can be run alone
+CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(FormatConverterTestCase,
+                                      "FormatConverterTestCase");
+