import pytest

from .js_helper import TestCase, _do_test_raw

from validator.testcases.javascript.jstypes import JSWrapper
from validator.testcases.javascript.actions import _get_as_num


def test_array_destructuring():
    """
    Make sure that multi-level and prototype array destructuring don't cause
    tracebacks.
    """
    assert not _do_test_raw("""
    [a, b, c, d] = [1, 2, 3, 4];
    [] = bar();
    """).failed()

    assert not _do_test_raw("""
    function foo(x, y, [a, b, c], z) {
        bar();
    }
    """).failed()


@pytest.mark.parametrize("input,output", [
    (1, 1),
    (1.0, 1.0),
    ('1', 1),
    ('1.0', 1.0),
    (None, 0),
    ('0xF', 15),
    (True, 1),
    (False, 0),
    (JSWrapper(3), 3),
    (JSWrapper(None), 0),
])
def test_get_as_num(input, output):
    assert _get_as_num(input) == output


def test_spidermonkey_warning():
    """
    Test that stderr warnings in Spidermonkey do not trip runtime errors.
    """
    # The following is attempting to store the octal "999999999999" in x, but
    # this is an invalid octal obviously. We need to "use strict" here because
    # the latest versions of spidermonkey simply accept that as a base 10
    # number, despite the "0" prefix.
    # We need spidermonkey to choke on this code, and this test makes sure that
    # when spidermonkey does, it doesn't break the validator.
    assert _do_test_raw("""
    "use strict";
    var x = 0999999999999;
    """).failed()


@pytest.mark.parametrize("test_input", [
    ('function foo() { %s; }'),
    ('function foo() { %s; yield 1; }'),
    ('function foo() { yield %s; }'),
    ('var foo = function () { %s; }'),
    ('var foo = function () { %s; yield 1; }'),
    ('function* foo() { %s; }'),
    ('var foo = function* () { %s; }'),
    ('var foo = function () %s'),
    ('var foo = () => %s'),
    ('var foo = () => { %s }'),
    ('if (true) { %s }'),
    ('if (true) ; else { %s }'),
    ('while (true) { %s }'),
    ('do { %s } while (true)'),

    ('for (;;) { %s }'),

    ('for (x=%s;;) {}'),
    ('for (let x=%s;;) {}'),
    ('for (var x=%s;;) {}'),
    ('for (const x=%s;;) {}'),

    ('for (let x=foo;;) { %s }'),
    ('for (var x=foo;;) { %s }'),

    ('for (; %s;) {}'),
    ('for (;; %s) {}'),

    ('for (let x in y) { %s }'),
    ('for (let x of y) { %s }'),
    ('for (let x in (%s)) {}'),
    ('for (let x of (%s)) {}'),

    ('try { %s } catch (e) {}'),
    ('try {} catch (e) { %s }'),
    ('try {} finally { %s }'),
    ('try {} catch (e if %s) {}'),
    ('let x = %s'),
    ('var x = %s'),
    ('const x = %s'),
])
def test_blocks_evaluated(test_input):
    """
    Tests that blocks of code are actually evaluated under normal
    circumstances.
    """
    err = _do_test_raw(test_input % 'eval(evilStuff)')
    assert err.message_count == 1, \
        'Missing expected failure for block: %s' % test_input
    assert err.warnings[0]['id'] == ('javascript', 'dangerous_global', 'eval')


@pytest.mark.parametrize("test_input", [
    'function foo() { %s; yield 1; }',
    'function foo() { yield %s; }',
    'var foo = function () { %s; yield 1; }',
    'function* foo() { %s; yield 1; }',
    'function* foo() { yield %s; }',
    'var foo = function* () { %s; yield 1; }',
])
def test_generators(test_input):
    """
    Tests that generators inside simple `function`s (opposed to `function*`)
    are not raising syntax errors.
    We use `version(180)` in `testcases.javascript.jsshell.JSShell` so we don't
    use the latest "version" (185 at the time of this writing) when running
    spidermonkey. See
    https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/JSAPI_reference/JSVersion)  # noqa
    which deprecates generators with 'function' (see
    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/Legacy_generator_function)  # noqa
    If we get to the point where we want to drop support for generators as
    `function`s, then we'll need to remove `version(180)` and modify this test.
    """

    ID = ('javascript', 'dangerous_global', 'eval')

    EVIL = 'eval(evilStuff)'
    # The following should raise a "dangerous global", and not a syntax
    # error.
    err = _do_test_raw(test_input % EVIL)
    assert err.message_count == 1, \
        'Missing expected failure for block: %s' % test_input
    assert err.warnings[0]['id'] == ID


class TestTemplateString(TestCase):
    WARNING = {'id': ('testcases_chromemanifest', 'test_resourcemodules',
                      'resource_modules')}

    def test_template_string(self):
        """Tests that plain template strings trigger warnings like normal
        strings."""

        self.run_script('`JavaScript-global-property`')
        self.assert_failed(with_warnings=[self.WARNING])

    def test_template_complex_string(self):
        """Tests that complex template strings trigger warnings like normal
        strings."""

        self.run_script("`JavaS${'cript-'}glob${'al-pro'}perty`")
        self.assert_failed(with_warnings=[self.WARNING])

    def test_tagged_template_string(self):
        """Tests that tagged template strings are treated as calls."""

        warning = {'id': ('testcases_javascript_instanceactions',
                          '_call_expression', 'called_createelement')}

        assert not _do_test_raw("""
            d.createElement(); "script";
            d.createElement; "script";
        """).failed()

        self.run_script("""
            d.createElement`script`
        """)
        self.assert_failed(with_warnings=[warning])
