mirror of
git://source.winehq.org/git/wine.git
synced 2024-09-02 10:53:38 +00:00
jscript: Implement Number.prototype.toLocaleString.
Signed-off-by: Gabriel Ivăncescu <gabrielopcode@gmail.com> Signed-off-by: Jacek Caban <jacek@codeweavers.com> Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
parent
2921714f3c
commit
8aefdf48f9
|
@ -449,6 +449,7 @@ HRESULT regexp_string_match(script_ctx_t*,jsdisp_t*,jsstr_t*,jsval_t*) DECLSPEC_
|
|||
|
||||
BOOL bool_obj_value(jsdisp_t*) DECLSPEC_HIDDEN;
|
||||
unsigned array_get_length(jsdisp_t*) DECLSPEC_HIDDEN;
|
||||
HRESULT localize_number(script_ctx_t*,DOUBLE,BOOL,jsstr_t**) DECLSPEC_HIDDEN;
|
||||
|
||||
HRESULT JSGlobal_eval(script_ctx_t*,jsval_t,WORD,unsigned,jsval_t*,jsval_t*) DECLSPEC_HIDDEN;
|
||||
HRESULT Object_get_proto_(script_ctx_t*,jsval_t,WORD,unsigned,jsval_t*,jsval_t*) DECLSPEC_HIDDEN;
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include <locale.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "jscript.h"
|
||||
|
@ -341,11 +342,90 @@ static HRESULT Number_toString(script_ctx_t *ctx, jsval_t vthis, WORD flags, uns
|
|||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT localize_number(script_ctx_t *ctx, DOUBLE val, BOOL new_format, jsstr_t **ret)
|
||||
{
|
||||
WCHAR buf[316], decimal[8], thousands[8], *numstr;
|
||||
NUMBERFMTW *format = NULL, format_buf;
|
||||
LCID lcid = ctx->lcid;
|
||||
_locale_t locale;
|
||||
unsigned convlen;
|
||||
jsstr_t *str;
|
||||
int len;
|
||||
|
||||
/* FIXME: Localize this */
|
||||
if(!isfinite(val))
|
||||
return to_string(ctx, jsval_number(val), ret);
|
||||
|
||||
/* Native never uses an exponent, even if the number is very large, it will in fact
|
||||
return all the digits (with thousands separators). jscript.dll uses two digits for
|
||||
fraction even if they are zero (likely default numDigits) and always returns them,
|
||||
while mshtml's jscript uses 3 digits and trims trailing zeros (on same locale).
|
||||
This is even for very small numbers, such as 0.0000999, which will simply be 0. */
|
||||
if(!(locale = _create_locale(LC_ALL, "C")))
|
||||
return E_OUTOFMEMORY;
|
||||
len = _swprintf_l(buf, ARRAY_SIZE(buf), L"%.3f", locale, val);
|
||||
_free_locale(locale);
|
||||
|
||||
if(new_format) {
|
||||
WCHAR grouping[10];
|
||||
|
||||
format = &format_buf;
|
||||
format->NumDigits = 3;
|
||||
while(buf[--len] == '0')
|
||||
format->NumDigits--;
|
||||
|
||||
/* same logic as VarFormatNumber */
|
||||
grouping[2] = '\0';
|
||||
if(!GetLocaleInfoW(lcid, LOCALE_SGROUPING, grouping, ARRAY_SIZE(grouping)))
|
||||
format->Grouping = 3;
|
||||
else
|
||||
format->Grouping = (grouping[2] == '2' ? 32 : grouping[0] - '0');
|
||||
|
||||
if(!GetLocaleInfoW(lcid, LOCALE_ILZERO | LOCALE_RETURN_NUMBER, (WCHAR*)&format->LeadingZero, 2))
|
||||
format->LeadingZero = 0;
|
||||
if(!GetLocaleInfoW(lcid, LOCALE_INEGNUMBER | LOCALE_RETURN_NUMBER, (WCHAR*)&format->NegativeOrder, 2))
|
||||
format->NegativeOrder = 1;
|
||||
format->lpDecimalSep = decimal;
|
||||
if(!GetLocaleInfoW(lcid, LOCALE_SDECIMAL, decimal, ARRAY_SIZE(decimal)))
|
||||
wcscpy(decimal, L".");
|
||||
format->lpThousandSep = thousands;
|
||||
if(!GetLocaleInfoW(lcid, LOCALE_STHOUSAND, thousands, ARRAY_SIZE(thousands)))
|
||||
wcscpy(thousands, L",");
|
||||
}
|
||||
|
||||
if(!(convlen = GetNumberFormatW(lcid, 0, buf, format, NULL, 0)) ||
|
||||
!(str = jsstr_alloc_buf(convlen - 1, &numstr)))
|
||||
return E_OUTOFMEMORY;
|
||||
|
||||
if(!GetNumberFormatW(lcid, 0, buf, format, numstr, convlen)) {
|
||||
jsstr_release(str);
|
||||
return E_OUTOFMEMORY;
|
||||
}
|
||||
|
||||
*ret = str;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT Number_toLocaleString(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv,
|
||||
jsval_t *r)
|
||||
{
|
||||
FIXME("\n");
|
||||
return E_NOTIMPL;
|
||||
jsstr_t *str;
|
||||
HRESULT hres;
|
||||
DOUBLE val;
|
||||
|
||||
TRACE("\n");
|
||||
|
||||
hres = numberval_this(vthis, &val);
|
||||
if(FAILED(hres))
|
||||
return hres;
|
||||
|
||||
if(r) {
|
||||
hres = localize_number(ctx, val, ctx->version >= SCRIPTLANGUAGEVERSION_ES5, &str);
|
||||
if(FAILED(hres))
|
||||
return hres;
|
||||
*r = jsval_string(str);
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT Number_toFixed(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv,
|
||||
|
|
|
@ -1365,6 +1365,11 @@ ok(tmp === "0", "num().toString = " + tmp);
|
|||
tmp = (new Number(5.5)).toString(2);
|
||||
ok(tmp === "101.1", "num(5.5).toString(2) = " + tmp);
|
||||
|
||||
tmp = (new Number(12)).toLocaleString();
|
||||
ok(tmp.indexOf(String.fromCharCode(0)) == -1, "invalid null byte");
|
||||
tmp = Number.prototype.toLocaleString.call(NaN);
|
||||
ok(tmp.indexOf(String.fromCharCode(0)) == -1, "invalid null byte");
|
||||
|
||||
tmp = (new Number(3)).toFixed(3);
|
||||
ok(tmp === "3.000", "num(3).toFixed(3) = " + tmp);
|
||||
tmp = (new Number(3)).toFixed();
|
||||
|
@ -2594,6 +2599,8 @@ testException(function() {arr.test();}, "E_NO_PROPERTY");
|
|||
testException(function() {[1,2,3].sort(nullDisp);}, "E_JSCRIPT_EXPECTED");
|
||||
testException(function() {Number.prototype.toString.call(arr);}, "E_NOT_NUM");
|
||||
testException(function() {Number.prototype.toFixed.call(arr);}, "E_NOT_NUM");
|
||||
testException(function() {Number.prototype.toLocaleString.call(arr);}, "E_NOT_NUM");
|
||||
testException(function() {Number.prototype.toLocaleString.call(null);}, "E_NOT_NUM");
|
||||
testException(function() {(new Number(3)).toString(1);}, "E_INVALID_CALL_ARG");
|
||||
testException(function() {(new Number(3)).toFixed(21);}, "E_FRACTION_DIGITS_OUT_OF_RANGE");
|
||||
testException(function() {(new Number(1)).toPrecision(0);}, "E_PRECISION_OUT_OF_RANGE");
|
||||
|
|
|
@ -186,6 +186,7 @@ static BOOL strict_dispid_check, testing_expr;
|
|||
static const char *test_name = "(null)";
|
||||
static IDispatch *script_disp;
|
||||
static int invoke_version;
|
||||
static BOOL use_english;
|
||||
static IActiveScriptError *script_error;
|
||||
static IActiveScript *script_engine;
|
||||
static const CLSID *engine_clsid = &CLSID_JScript;
|
||||
|
@ -1834,7 +1835,7 @@ static ULONG WINAPI ActiveScriptSite_Release(IActiveScriptSite *iface)
|
|||
|
||||
static HRESULT WINAPI ActiveScriptSite_GetLCID(IActiveScriptSite *iface, LCID *plcid)
|
||||
{
|
||||
*plcid = GetUserDefaultLCID();
|
||||
*plcid = use_english ? MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT) : GetUserDefaultLCID();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
|
@ -2996,6 +2997,39 @@ static void test_default_value(void)
|
|||
close_script(script);
|
||||
}
|
||||
|
||||
static void test_number_localization(void)
|
||||
{
|
||||
static struct {
|
||||
const WCHAR *num;
|
||||
const WCHAR *expect;
|
||||
} tests[] = {
|
||||
{ L"0", L"0.00" },
|
||||
{ L"+1234.5", L"1,234.50" },
|
||||
{ L"-1337.7331", L"-1,337.73" },
|
||||
{ L"-0.0123", L"-0.01" },
|
||||
{ L"-0.0198", L"-0.02" },
|
||||
{ L"0.004", L"0.00" },
|
||||
{ L"65536.5", L"65,536.50" },
|
||||
{ L"NaN", L"NaN" }
|
||||
};
|
||||
static const WCHAR fmt[] = L"Number.prototype.toLocaleString.call(%s)";
|
||||
WCHAR script_buf[ARRAY_SIZE(fmt) + 32];
|
||||
HRESULT hres;
|
||||
unsigned i;
|
||||
VARIANT v;
|
||||
|
||||
use_english = TRUE;
|
||||
for(i = 0; i < ARRAY_SIZE(tests); i++) {
|
||||
swprintf(script_buf, ARRAY_SIZE(script_buf), fmt, tests[i].num);
|
||||
hres = parse_script_expr(script_buf, &v, NULL);
|
||||
ok(hres == S_OK, "[%u] parse_script_expr failed: %08lx\n", i, hres);
|
||||
ok(V_VT(&v) == VT_BSTR, "[%u] V_VT(v) = %d\n", i, V_VT(&v));
|
||||
ok(!lstrcmpW(V_BSTR(&v), tests[i].expect), "[%u] got %s\n", i, wine_dbgstr_w(V_BSTR(&v)));
|
||||
VariantClear(&v);
|
||||
}
|
||||
use_english = FALSE;
|
||||
}
|
||||
|
||||
static void test_script_exprs(void)
|
||||
{
|
||||
VARIANT v;
|
||||
|
@ -3060,6 +3094,7 @@ static void test_script_exprs(void)
|
|||
ok(!lstrcmpW(V_BSTR(&v), L"wine"), "V_BSTR(v) = %s\n", wine_dbgstr_w(V_BSTR(&v)));
|
||||
VariantClear(&v);
|
||||
|
||||
test_number_localization();
|
||||
test_default_value();
|
||||
test_propputref();
|
||||
test_retval();
|
||||
|
|
|
@ -28,6 +28,7 @@ var JS_E_REGEXP_EXPECTED = 0x800a1398;
|
|||
var JS_E_INVALID_WRITABLE_PROP_DESC = 0x800a13ac;
|
||||
var JS_E_NONCONFIGURABLE_REDEFINED = 0x800a13d6;
|
||||
var JS_E_NONWRITABLE_MODIFIED = 0x800a13d7;
|
||||
var JS_E_WRONG_THIS = 0x800a13fc;
|
||||
|
||||
var tests = [];
|
||||
|
||||
|
@ -68,6 +69,55 @@ sync_test("toISOString", function() {
|
|||
expect_exception(function() { new Date(31494784780800001).toISOString(); });
|
||||
});
|
||||
|
||||
sync_test("Number toLocaleString", function() {
|
||||
var r = Number.prototype.toLocaleString.length;
|
||||
ok(r === 0, "length = " + r);
|
||||
var tests = [
|
||||
[ 0.0, "0" ],
|
||||
[ 1234.5, "1,234.5" ],
|
||||
[ -1337.7331, "-1,337.733" ],
|
||||
[ -0.0123, "-0.012" ],
|
||||
[-0.0198, "-0.02" ],
|
||||
[ 0.004, "0.004" ],
|
||||
[ 99.004, "99.004" ],
|
||||
[ 99.0004, "99" ],
|
||||
[ 65536.5, "65,536.5" ],
|
||||
[ NaN, "NaN" ]
|
||||
];
|
||||
|
||||
if(external.isEnglish) {
|
||||
for(var i = 0; i < tests.length; i++) {
|
||||
r = Number.prototype.toLocaleString.call(tests[i][0]);
|
||||
ok(r === tests[i][1], "[" + i + "] got " + r);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Number.prototype.toLocaleString.call("50");
|
||||
ok(false, "expected exception calling it on string");
|
||||
}catch(ex) {
|
||||
var n = ex.number >>> 0;
|
||||
todo_wine.
|
||||
ok(n === JS_E_WRONG_THIS, "called on string threw " + n);
|
||||
}
|
||||
try {
|
||||
Number.prototype.toLocaleString.call(undefined);
|
||||
ok(false, "expected exception calling it on undefined");
|
||||
}catch(ex) {
|
||||
var n = ex.number >>> 0;
|
||||
todo_wine.
|
||||
ok(n === JS_E_WRONG_THIS, "called on undefined threw " + n);
|
||||
}
|
||||
try {
|
||||
Number.prototype.toLocaleString.call(external.nullDisp);
|
||||
ok(false, "expected exception calling it on nullDisp");
|
||||
}catch(ex) {
|
||||
var n = ex.number >>> 0;
|
||||
todo_wine.
|
||||
ok(n === JS_E_WRONG_THIS, "called on nullDisp threw " + n);
|
||||
}
|
||||
});
|
||||
|
||||
sync_test("indexOf", function() {
|
||||
function expect(array, args, exr) {
|
||||
var r = Array.prototype.indexOf.apply(array, args);
|
||||
|
|
|
@ -153,13 +153,14 @@ DEFINE_EXPECT(GetTypeInfo);
|
|||
#define DISPID_EXTERNAL_WRITESTREAM 0x300006
|
||||
#define DISPID_EXTERNAL_GETVT 0x300007
|
||||
#define DISPID_EXTERNAL_NULL_DISP 0x300008
|
||||
#define DISPID_EXTERNAL_IS_ENGLISH 0x300009
|
||||
|
||||
static const GUID CLSID_TestScript =
|
||||
{0x178fc163,0xf585,0x4e24,{0x9c,0x13,0x4b,0xb7,0xfa,0xf8,0x07,0x46}};
|
||||
static const GUID CLSID_TestActiveX =
|
||||
{0x178fc163,0xf585,0x4e24,{0x9c,0x13,0x4b,0xb7,0xfa,0xf8,0x06,0x46}};
|
||||
|
||||
static BOOL is_ie9plus;
|
||||
static BOOL is_ie9plus, is_english;
|
||||
static IHTMLDocument2 *notif_doc;
|
||||
static IOleDocumentView *view;
|
||||
static IDispatchEx *window_dispex;
|
||||
|
@ -599,6 +600,10 @@ static HRESULT WINAPI externalDisp_GetDispID(IDispatchEx *iface, BSTR bstrName,
|
|||
*pid = DISPID_EXTERNAL_NULL_DISP;
|
||||
return S_OK;
|
||||
}
|
||||
if(!lstrcmpW(bstrName, L"isEnglish")) {
|
||||
*pid = DISPID_EXTERNAL_IS_ENGLISH;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
ok(0, "unexpected name %s\n", wine_dbgstr_w(bstrName));
|
||||
return DISP_E_UNKNOWNNAME;
|
||||
|
@ -784,6 +789,21 @@ static HRESULT WINAPI externalDisp_InvokeEx(IDispatchEx *iface, DISPID id, LCID
|
|||
V_DISPATCH(pvarRes) = NULL;
|
||||
return S_OK;
|
||||
|
||||
case DISPID_EXTERNAL_IS_ENGLISH:
|
||||
ok(wFlags == INVOKE_PROPERTYGET, "wFlags = %x\n", wFlags);
|
||||
ok(pdp != NULL, "pdp == NULL\n");
|
||||
ok(!pdp->rgvarg, "rgvarg != NULL\n");
|
||||
ok(!pdp->rgdispidNamedArgs, "rgdispidNamedArgs != NULL\n");
|
||||
ok(!pdp->cArgs, "cArgs = %d\n", pdp->cArgs);
|
||||
ok(!pdp->cNamedArgs, "cNamedArgs = %d\n", pdp->cNamedArgs);
|
||||
ok(pvarRes != NULL, "pvarRes == NULL\n");
|
||||
ok(V_VT(pvarRes) == VT_EMPTY, "V_VT(pvarRes) = %d\n", V_VT(pvarRes));
|
||||
ok(pei != NULL, "pei == NULL\n");
|
||||
|
||||
V_VT(pvarRes) = VT_BOOL;
|
||||
V_BOOL(pvarRes) = is_english ? VARIANT_TRUE : VARIANT_FALSE;
|
||||
return S_OK;
|
||||
|
||||
default:
|
||||
ok(0, "unexpected call\n");
|
||||
return E_NOTIMPL;
|
||||
|
@ -3743,6 +3763,19 @@ static HWND create_container_window(void)
|
|||
300, 300, NULL, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
static void detect_locale(void)
|
||||
{
|
||||
HMODULE kernel32 = GetModuleHandleA("kernel32.dll");
|
||||
LANGID (WINAPI *pGetThreadUILanguage)(void) = (void*)GetProcAddress(kernel32, "GetThreadUILanguage");
|
||||
|
||||
is_english = ((!pGetThreadUILanguage || PRIMARYLANGID(pGetThreadUILanguage()) == LANG_ENGLISH) &&
|
||||
PRIMARYLANGID(GetUserDefaultUILanguage()) == LANG_ENGLISH &&
|
||||
PRIMARYLANGID(GetUserDefaultLangID()) == LANG_ENGLISH);
|
||||
|
||||
if(!is_english)
|
||||
skip("Skipping some tests in non-English locale\n");
|
||||
}
|
||||
|
||||
static BOOL check_ie(void)
|
||||
{
|
||||
IHTMLDocument2 *doc;
|
||||
|
@ -3779,6 +3812,7 @@ START_TEST(script)
|
|||
CoInitialize(NULL);
|
||||
container_hwnd = create_container_window();
|
||||
|
||||
detect_locale();
|
||||
if(argc > 2) {
|
||||
init_protocol_handler();
|
||||
run_script_as_http_with_mode(argv[2], NULL, "11");
|
||||
|
|
Loading…
Reference in a new issue