/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.javascript.tests;

import org.junit.Assert;
import org.junit.Test;
import org.mozilla.javascript.RhinoException;
import org.mozilla.javascript.ScriptableObject;


/**
 * Unit tests for <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=549604">bug 549604</a>.
 * This tests verify the properties of a JS exception and ensures that they don't change with different optimization levels.
 *
 * @author Marc Guillemot
 */
public class ErrorPropertiesTest {
    final static String LS = System.getProperty("line.separator");

    private void testScriptStackTrace(final String script, final String expectedStackTrace) {
        testScriptStackTrace(script, expectedStackTrace, -1);
        testScriptStackTrace(script, expectedStackTrace, 0);
        testScriptStackTrace(script, expectedStackTrace, 1);
    }

    private void testScriptStackTrace(final String script, final String expectedStackTrace,
                                      final int optimizationLevel) {
        try {
            Utils.executeScript(script, optimizationLevel);
        }
        catch (final RhinoException e) {
            Assert.assertEquals(expectedStackTrace, e.getScriptStackTrace());
        }
    }

    @Test
    public void fileName() {
        testIt("try { null.method() } catch (e) { e.fileName }", "myScript.js");
        testIt("try { null.property } catch (e) { e.fileName }", "myScript.js");
    }

    @Test
    public void lineNumber() {
        testIt("try { null.method() } catch (e) { e.lineNumber }", 1);
        testIt("try {\n null.method() \n} catch (e) { e.lineNumber }", 2);
        testIt("\ntry \n{\n null.method() \n} catch (e) { e.lineNumber }", 4);

        testIt("function f() {\n null.method(); \n}\n try { f() } catch (e) { e.lineNumber }", 2);
    }

    @Test
    public void defaultStack() {
        RhinoException.useMozillaStackStyle(false);
        testScriptStackTrace("null.method()", "\tat myScript.js:1" + LS);
        final String script = "function f() \n{\n  null.method();\n}\nf();\n";
        testScriptStackTrace(script, "\tat myScript.js:3 (f)" + LS + "\tat myScript.js:5" + LS);
        testIt("try { null.method() } catch (e) { e.stack }", "\tat myScript.js:1" + LS);
        final String expectedStack = "\tat myScript.js:2 (f)" + LS + "\tat myScript.js:4" + LS;
        testIt("function f() {\n null.method(); \n}\n try { f() } catch (e) { e.stack }", expectedStack);
    }

    @Test
    public void mozillaStack() {
        RhinoException.useMozillaStackStyle(true);
        testScriptStackTrace("null.method()", "@myScript.js:1" + LS);
        final String script = "function f() \n{\n  null.method();\n}\nf();\n";
        testScriptStackTrace(script, "f()@myScript.js:3" + LS + "@myScript.js:5" + LS);
        testIt("try { null.method() } catch (e) { e.stack }", "@myScript.js:1" + LS);
        final String expectedStack = "f()@myScript.js:2" + LS + "@myScript.js:4" + LS;
        testIt("function f() {\n null.method(); \n}\n try { f() } catch (e) { e.stack }", expectedStack);
    }

    private void testIt(final String script, final Object expected) {
        Utils.runWithAllOptimizationLevels(cx -> {
            try {
                final ScriptableObject scope = cx.initStandardObjects();
                final Object o = cx.evaluateString(scope, script,
                        "myScript.js", 1, null);
                Assert.assertEquals(expected, o);
                return o;
            }
            catch (final RuntimeException e) {
                throw e;
            }
            catch (final Exception e) {
                throw new RuntimeException(e);
            }
        });
    }
}
