<!--
Copyright (c) 2019 The Khronos Group Inc.
Use of this source code is governed by an MIT-style license that can be
found in the LICENSE.txt file.
-->

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="../../resources/js-test-style.css"/>
<script src="../../js/js-test-pre.js"></script>
<script src="../../js/test-eval.js"></script>
</head>
<body>
<div id="description"></div>
<div id="console"></div>

<script>
"use strict";
description("Test DataView.");

var intArray1 = [0, 1, 2, 3, 100, 101, 102, 103, 128, 129, 130, 131, 252, 253, 254, 255];
var intArray2 = [31, 32, 33, 0, 1, 2, 3, 100, 101, 102, 103, 128, 129, 130, 131, 252, 253, 254, 255];
var emptyArray = [204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204];

var arrayBuffer = null;
var view = null;
var viewStart = 0;
var viewLength = 0;

function getElementSize(func)
{
    switch (func) {
    case "Int8":
    case "Uint8":
        return 1;
    case "Int16":
    case "Uint16":
        return 2;
    case "Int32":
    case "Uint32":
    case "Float32":
        return 4;
    case "Float64":
        return 8;
    default:
        debug("Should not reached");
    }
}

function checkGet(func, index, expected, littleEndian)
{
    var expr = "view.get" + func + "(" + index;
    if (littleEndian != undefined) {
        expr += ", ";
        expr += littleEndian ? "true" : "false";
    }
    expr += ")";
    if (index >= 0 && index + getElementSize(func) - 1 < view.byteLength)
        shouldBe(expr, expected);
    else
        shouldThrow(expr);
}

function checkSet(func, index, value, littleEndian)
{
    var expr = "view.set" + func + "(" + index + ", " + value;
    if (littleEndian != undefined) {
        expr += ", ";
        expr += littleEndian ? "true" : "false";
    }
    expr += ")";
    if (index >= 0 && index + getElementSize(func) - 1 < view.byteLength) {
        shouldBeUndefined(expr);
        checkGet(func, index, value, littleEndian);
    } else
        shouldThrow(expr);
}

function checkGetWithoutArgument(func, expected)
{
    var threw = false;
    var value;
    try {
        value = view["get" + func]();
    } catch (e) {
        threw = true;
    }

    if (threw) {
        // This used to be correct, but TC39 has changed the behavior of these methods.
        testPassed("view.get" + func + " with no arguments throws.");
    } else {
        if (value === expected) {
            testPassed("view.get" + func + " treats missing argument as 0.");
        } else {
            testFailed("view.get" + func + " accepts a missing argument but does not cast it to 0.");
        }
    }
}

function checkSetWithoutSecondArgument(func, index, isFloat)
{
    var expected = isFloat ? NaN : 0;
    var threw = false;
    var value;
    try {
        value = view["set" + func](index);
    } catch (e) {
        threw = true;
    }

    if (threw) {
        // This used to be correct, but TC39 has changed the behavior of these methods.
        testPassed("view.set" + func + " with missing second argument throws.");
    } else {
        var stored = view["get" + func](index);
        if (value === undefined && isFloat ? isNaN(stored) : stored === expected) {
            testPassed("view.set" + func + " treats missing second argument as " + expected + ".");
        } else {
            testFailed("view.set" + func + " accepts a missing second argument but does not cast it to " + expected + ".");
        }
    }
}

function checkSetWithoutArguments(func, isFloat)
{
    var expected = isFloat ? NaN : 0;
    var threw = false;
    var value;
    try {
        value = view["set" + func]();
    } catch (e) {
        threw = true;
    }

    if (threw) {
        // This used to be correct, but TC39 has changed the behavior of these methods.
        testPassed("view.set" + func + " with no arguments throws.");
    } else {
        var stored = view["get" + func](0);
        if (value === undefined && isFloat ? isNaN(stored) : stored === expected) {
            testPassed("view.set" + func + " treats missing first argument as 0.");
        } else {
            testFailed("view.set" + func + " accepts a missing first argument but does not cast it to 0.");
        }
    }
}

function testMissingArguments(func, constructor, isFloat)
{
    view = new DataView((new constructor(3)).buffer);
    view["set" + func](0, 1);
    view["set" + func](getElementSize(func), 2);
    checkGetWithoutArgument(func, 1);
    checkSetWithoutSecondArgument(func, getElementSize(func), isFloat);
    view = new DataView((new constructor(3)).buffer);
    view["set" + func](0, 1);
    checkSetWithoutArguments(func, isFloat);
}

function test(isTestingGet, func, index, value, littleEndian)
{
    if (isTestingGet)
        checkGet(func, index, value, littleEndian);
     else
        checkSet(func, index, value, littleEndian);
}

function createDataView(array, frontPaddingNum, littleEndian, start, length)
{
    if (!littleEndian)
        array.reverse();
    var paddingArray = new Array(frontPaddingNum);
    arrayBuffer = (new Uint8Array(paddingArray.concat(array))).buffer;
    viewStart = (start != undefined) ? start : 0;
    viewLength = (length != undefined) ? length : arrayBuffer.byteLength - viewStart;
    view = new DataView(arrayBuffer, viewStart, viewLength);
    if (!littleEndian)
        array.reverse(); // restore the array.
}

function runIntegerTestCases(isTestingGet, array, start, length)
{
    createDataView(array, 0, true, start, length);

    test(isTestingGet, "Int8", 0, "0");
    test(isTestingGet, "Int8", 8, "-128");
    test(isTestingGet, "Int8", 15, "-1");

    test(isTestingGet, "Uint8", 0, "0");
    test(isTestingGet, "Uint8", 8, "128");
    test(isTestingGet, "Uint8", 15, "255");

    // Little endian.
    test(isTestingGet, "Int16", 0, "256", true);
    test(isTestingGet, "Int16", 5, "26213", true);
    test(isTestingGet, "Int16", 9, "-32127", true);
    test(isTestingGet, "Int16", 14, "-2", true);

    // Big endian.
    test(isTestingGet, "Int16", 0, "1");
    test(isTestingGet, "Int16", 5, "25958");
    test(isTestingGet, "Int16", 9, "-32382");
    test(isTestingGet, "Int16", 14, "-257");

    // Little endian.
    test(isTestingGet, "Uint16", 0, "256", true);
    test(isTestingGet, "Uint16", 5, "26213", true);
    test(isTestingGet, "Uint16", 9, "33409", true);
    test(isTestingGet, "Uint16", 14, "65534", true);

    // Big endian.
    test(isTestingGet, "Uint16", 0, "1");
    test(isTestingGet, "Uint16", 5, "25958");
    test(isTestingGet, "Uint16", 9, "33154");
    test(isTestingGet, "Uint16", 14, "65279");

    // Little endian.
    test(isTestingGet, "Int32", 0, "50462976", true);
    test(isTestingGet, "Int32", 3, "1717920771", true);
    test(isTestingGet, "Int32", 6, "-2122291354", true);
    test(isTestingGet, "Int32", 9, "-58490239", true);
    test(isTestingGet, "Int32", 12, "-66052", true);

    // Big endian.
    test(isTestingGet, "Int32", 0, "66051");
    test(isTestingGet, "Int32", 3, "56911206");
    test(isTestingGet, "Int32", 6, "1718059137");
    test(isTestingGet, "Int32", 9, "-2122152964");
    test(isTestingGet, "Int32", 12, "-50462977");

    // Little endian.
    test(isTestingGet, "Uint32", 0, "50462976", true);
    test(isTestingGet, "Uint32", 3, "1717920771", true);
    test(isTestingGet, "Uint32", 6, "2172675942", true);
    test(isTestingGet, "Uint32", 9, "4236477057", true);
    test(isTestingGet, "Uint32", 12, "4294901244", true);

    // Big endian.
    test(isTestingGet, "Uint32", 0, "66051");
    test(isTestingGet, "Uint32", 3, "56911206");
    test(isTestingGet, "Uint32", 6, "1718059137");
    test(isTestingGet, "Uint32", 9, "2172814332");
    test(isTestingGet, "Uint32", 12, "4244504319");
}

function testFloat(isTestingGet, func, array, start, expected)
{
    // Little endian.
    createDataView(array, 0, true, start);
    test(isTestingGet, func, 0, expected, true);
    createDataView(array, 3, true, start);
    test(isTestingGet, func, 3, expected, true);
    createDataView(array, 7, true, start);
    test(isTestingGet, func, 7, expected, true);
    createDataView(array, 10, true, start);
    test(isTestingGet, func, 10, expected, true);

    // Big endian.
    createDataView(array, 0, false);
    test(isTestingGet, func, 0, expected, false);
    createDataView(array, 3, false);
    test(isTestingGet, func, 3, expected, false);
    createDataView(array, 7, false);
    test(isTestingGet, func, 7, expected, false);
    createDataView(array, 10, false);
    test(isTestingGet, func, 10, expected, false);
}

function runFloatTestCases(isTestingGet, start)
{
    testFloat(isTestingGet, "Float32", isTestingGet ? [0, 0, 32, 65] : emptyArray, start, "10");
    testFloat(isTestingGet, "Float32", isTestingGet ? [164, 112, 157, 63] : emptyArray, start, "1.2300000190734863");
    testFloat(isTestingGet, "Float32", isTestingGet ? [95, 53, 50, 199] : emptyArray, start, "-45621.37109375");
    testFloat(isTestingGet, "Float32", isTestingGet ? [255, 255, 255, 127] : emptyArray, start, "NaN");
    testFloat(isTestingGet, "Float32", isTestingGet ? [255, 255, 255, 255] : emptyArray, start, "-NaN");

    testFloat(isTestingGet, "Float64", isTestingGet ? [0, 0, 0, 0, 0, 0, 36, 64] : emptyArray, start, "10");
    testFloat(isTestingGet, "Float64", isTestingGet ? [174, 71, 225, 122, 20, 174, 243, 63] : emptyArray, start, "1.23");
    testFloat(isTestingGet, "Float64", isTestingGet ? [181, 55, 248, 30, 242, 179, 87, 193] : emptyArray, start, "-6213576.4839");
    testFloat(isTestingGet, "Float64", isTestingGet ? [255, 255, 255, 255, 255, 255, 255, 127] : emptyArray, start, "NaN");
    testFloat(isTestingGet, "Float64", isTestingGet ? [255, 255, 255, 255, 255, 255, 255, 255] : emptyArray, start, "-NaN");
}

function runNegativeIndexTests(isTestingGet)
{
    createDataView(intArray1, 0, true, 0, 16);

    test(isTestingGet, "Int8", -1, "0");
    test(isTestingGet, "Int8", -2, "0");

    test(isTestingGet, "Uint8", -1, "0");
    test(isTestingGet, "Uint8", -2, "0");

    test(isTestingGet, "Int16", -1, "0");
    test(isTestingGet, "Int16", -2, "0");
    test(isTestingGet, "Int16", -3, "0");

    test(isTestingGet, "Uint16", -1, "0");
    test(isTestingGet, "Uint16", -2, "0");
    test(isTestingGet, "Uint16", -3, "0");

    test(isTestingGet, "Int32", -1, "0");
    test(isTestingGet, "Int32", -3, "0");
    test(isTestingGet, "Int32", -5, "0");

    test(isTestingGet, "Uint32", -1, "0");
    test(isTestingGet, "Uint32", -3, "0");
    test(isTestingGet, "Uint32", -5, "0");

    createDataView([0, 0, 0, 0, 0, 0, 36, 64], 0, true, 0, 8);

    test(isTestingGet, "Float32", -1, "0");
    test(isTestingGet, "Float32", -3, "0");
    test(isTestingGet, "Float32", -5, "0");

    test(isTestingGet, "Float64", -1, "0");
    test(isTestingGet, "Float64", -5, "0");
    test(isTestingGet, "Float64", -9, "0");
}

function runConstructorTests()
{
    arrayBuffer = (new Uint8Array([1, 2])).buffer;

    debug("");
    debug("Test for constructor taking 1 argument");
    shouldBeDefined("view = new DataView(arrayBuffer)");
    shouldBe("view.byteOffset", "0");
    shouldBe("view.byteLength", "2");

    debug("");
    debug("Test for constructor taking 2 arguments");
    shouldBeDefined("view = new DataView(arrayBuffer, 1)");
    shouldBe("view.byteOffset", "1");
    shouldBe("view.byteLength", "1");

    debug("");
    debug("Test for constructor taking 3 arguments");
    shouldBeDefined("view = new DataView(arrayBuffer, 0, 1)");
    shouldBe("view.byteOffset", "0");
    shouldBe("view.byteLength", "1");

    debug("");
    debug("Test for constructor throwing exception");
    shouldThrow("view = new DataView(arrayBuffer, 0, 3)");
    shouldThrow("view = new DataView(arrayBuffer, 1, 2)");
    shouldThrow("view = new DataView(arrayBuffer, 2, 1)");
}

function runGetTests()
{
    debug("");
    debug("Test for get methods that work");
    runIntegerTestCases(true, intArray1, 0, 16);
    runFloatTestCases(true, 0);

    debug("");
    debug("Test for get methods that might read beyond range");
    runIntegerTestCases(true, intArray2, 3, 2);
    runFloatTestCases(true, 3);

    debug("");
    debug("Test for get methods that read from negative index");
    runNegativeIndexTests(true);
}

function runSetTests()
{
    debug("");
    debug("Test for set methods that work");
    runIntegerTestCases(false, emptyArray, 0, 16);
    runFloatTestCases(false);

    debug("");
    debug("Test for set methods that might write beyond the range");
    runIntegerTestCases(false, emptyArray, 3, 2);
    runFloatTestCases(false, 7);

    debug("");
    debug("Test for set methods that write to negative index");
    runNegativeIndexTests(false);
}

function runMissingArgumentTests()
{
    debug("");
    debug("Test for get and set methods missing arguments");
    testMissingArguments("Int8", Int8Array);
    testMissingArguments("Uint8", Uint8Array);
    testMissingArguments("Int16", Int16Array);
    testMissingArguments("Uint16", Uint16Array);
    testMissingArguments("Int32", Int32Array);
    testMissingArguments("Uint32", Uint32Array);
    testMissingArguments("Float32", Float32Array, true);
    testMissingArguments("Float64", Float64Array, true);
}

function runIndexingTests()
{
    debug("");
    debug("Test for indexing that should not work");
    view = new DataView((new Uint8Array([1, 2])).buffer);
    shouldBeUndefined("view[0]");
    shouldBeDefined("view[0] = 3");
    shouldBe("view.getUint8(0)", "1");
}

runConstructorTests();
runGetTests();
runSetTests();
runMissingArgumentTests();
runIndexingTests();
var successfullyParsed = true;
</script>

<script src="../../js/js-test-post.js"></script>
</body>
</html>
