// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████       █      █      █      █      █ █▄  ▀███ █       ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█  ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄  ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄   █ ▄▄▄▄▄ ┃
// ┃ █      ██████ █  ▀█▄       █ ██████      █      ███▌▐███ ███████▄ █       ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors.                              ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

import { test, expect } from "@perspective-dev/test";
import perspective from "./perspective_client";

import moment from "moment";
import * as arrows from "./test_arrows.js";

const data = [
    { x: 1, y: "a", z: true },
    { x: 2, y: "b", z: false },
    { x: 3, y: "c", z: true },
    { x: 4, y: "d", z: false },
];

const col_data = {
    x: [1, 2, 3, 4],
    y: ["a", "b", "c", "d"],
    z: [true, false, true, false],
};

const meta = {
    x: "integer",
    y: "string",
    z: "boolean",
};

const data_3 = [
    { w: 1.5, x: 1, y: "a", z: true },
    { w: 2.5, x: 2, y: "b", z: false },
    { w: 3.5, x: 3, y: "c", z: true },
    { w: 4.5, x: 4, y: "d", z: false },
];

const data_7 = {
    w: [1.5, 2.5, 3.5, 4.5],
    x: [1, 2, 3, 4],
    y: ["a", "b", "c", "d"],
    z: [true, false, true, false],
};

const int_in_string = [{ a: "1" }, { a: "2" }, { a: "12345" }];

const float_in_string = [{ a: "1.5" }, { a: "2.5" }, { a: "12345.56789" }];

const meta_3 = {
    w: "float",
    x: "integer",
    y: "string",
    z: "boolean",
};

const arrow_result = [
    {
        f32: 1.5,
        f64: 1.5,
        i64: 1,
        i32: 1,
        i16: 1,
        i8: 1,
        bool: true,
        char: "a",
        dict: "a",
        "datetime(ms)": +new Date("2018-01-25"),
        "datetime(us)": +new Date("2018-01-25"),
        "datetime(ns)": +new Date("2018-01-25"),
    },
    {
        f32: 2.5,
        f64: 2.5,
        i64: 2,
        i32: 2,
        i16: 2,
        i8: 2,
        bool: false,
        char: "b",
        dict: "b",
        "datetime(ms)": +new Date("2018-01-26"),
        "datetime(us)": +new Date("2018-01-26"),
        "datetime(ns)": +new Date("2018-01-26"),
    },
    {
        f32: 3.5,
        f64: 3.5,
        i64: 3,
        i32: 3,
        i16: 3,
        i8: 3,
        bool: true,
        char: "c",
        dict: "c",
        "datetime(ms)": +new Date("2018-01-27"),
        "datetime(us)": +new Date("2018-01-27"),
        "datetime(ns)": +new Date("2018-01-27"),
    },
    {
        f32: 4.5,
        f64: 4.5,
        i64: 4,
        i32: 4,
        i16: 4,
        i8: 4,
        bool: false,
        char: "",
        dict: "",
        "datetime(ms)": +new Date("2018-01-28"),
        "datetime(us)": +new Date("2018-01-28"),
        "datetime(ns)": +new Date("2018-01-28"),
    },
    {
        f32: null,
        f64: null,
        i64: null,
        i32: null,
        i16: null,
        i8: null,
        bool: null,
        char: null,
        dict: null,
        "datetime(ms)": null,
        "datetime(us)": null,
        "datetime(ns)": null,
    },
];

let arrow_lists_data = {
    uint32_arr: ["[1,2,3,4]", "[5]", "[43215]", "[]"],
    string_arr: [
        `["#1","second","third","fourth"]`,
        `["fifth"]`,
        `[]`,
        `["Last!"]`,
    ],
    bool_arr: [
        "[true,false,false,true,true,false,false,true,true]",
        "[]",
        "[true]",
        "[false]",
    ],
    float_arr: [
        "[]",
        "[3.14,3.141592653589793,6.283185307179586,1.4142135623730951]",
        "[12.00001]",
        "[100000000.0,100000000.0,100000000.0,100000000.0]",
    ],
};

let arrow_date_data = {
    "jan-2019": [
        "2019-01-01",
        "2019-01-02",
        "2019-01-03",
        "2019-01-04",
        "2019-01-05",
        "2019-01-06",
        "2019-01-07",
        "2019-01-08",
        "2019-01-09",
        "2019-01-10",
        "2019-01-11",
        "2019-01-12",
        "2019-01-13",
        "2019-01-14",
        "2019-01-15",
        "2019-01-16",
        "2019-01-17",
        "2019-01-18",
        "2019-01-19",
        "2019-01-20",
        "2019-01-21",
        "2019-01-22",
        "2019-01-23",
        "2019-01-24",
        "2019-01-25",
        "2019-01-26",
        "2019-01-27",
        "2019-01-28",
        "2019-01-29",
        "2019-01-30",
        "2019-01-31",
    ],
    "feb-2020": [
        "2020-02-01",
        "2020-02-02",
        "2020-02-03",
        "2020-02-04",
        "2020-02-05",
        "2020-02-06",
        "2020-02-07",
        "2020-02-08",
        "2020-02-09",
        "2020-02-10",
        "2020-02-11",
        "2020-02-12",
        "2020-02-13",
        "2020-02-14",
        "2020-02-15",
        "2020-02-16",
        "2020-02-17",
        "2020-02-18",
        "2020-02-19",
        "2020-02-20",
        "2020-02-21",
        "2020-02-22",
        "2020-02-23",
        "2020-02-24",
        "2020-02-25",
        "2020-02-26",
        "2020-02-27",
        "2020-02-28",
        "2020-02-29",
        null,
        null,
    ],
    "mar-2019": [
        "2019-03-01",
        "2019-03-02",
        "2019-03-03",
        "2019-03-04",
        "2019-03-05",
        "2019-03-06",
        "2019-03-07",
        "2019-03-08",
        "2019-03-09",
        "2019-03-10",
        "2019-03-11",
        "2019-03-12",
        "2019-03-13",
        "2019-03-14",
        "2019-03-15",
        "2019-03-16",
        "2019-03-17",
        "2019-03-18",
        "2019-03-19",
        "2019-03-20",
        "2019-03-21",
        "2019-03-22",
        "2019-03-23",
        "2019-03-24",
        "2019-03-25",
        "2019-03-26",
        "2019-03-27",
        "2019-03-28",
        "2019-03-29",
        "2019-03-30",
        "2019-03-31",
    ],
    "apr-2020": [
        "2020-04-01",
        "2020-04-02",
        "2020-04-03",
        "2020-04-04",
        "2020-04-05",
        "2020-04-06",
        "2020-04-07",
        "2020-04-08",
        "2020-04-09",
        "2020-04-10",
        "2020-04-11",
        "2020-04-12",
        "2020-04-13",
        "2020-04-14",
        "2020-04-15",
        "2020-04-16",
        "2020-04-17",
        "2020-04-18",
        "2020-04-19",
        "2020-04-20",
        "2020-04-21",
        "2020-04-22",
        "2020-04-23",
        "2020-04-24",
        "2020-04-25",
        "2020-04-26",
        "2020-04-27",
        "2020-04-28",
        "2020-04-29",
        "2020-04-30",
        null,
    ],
};

// transform arrow strings into timestamps
for (const k in arrow_date_data) {
    arrow_date_data[k] = arrow_date_data[k].map((d) =>
        d ? new Date(d).getTime() : null,
    );
}

const dt = () => {
    let dt = new Date();
    dt.setHours(4);
    dt.setMinutes(12);
    return dt;
};

const data_4 = [{ v: dt() }];

const data_5 = [{ v: "11-09-2017" }];

const meta_4 = { v: "datetime" };

const csv = "x,y,z\n1,a,true\n2,b,false\n3,c,true\n4,d,false";

const data_6 = [{ x: "š" }];

const int_float_data = [
    { int: 1, float: 2.25 },
    { int: 2, float: 3.5 },
    { int: 3, float: 4.75 },
    { int: 4, float: 5.25 },
];
const int_float_string_data = [
    { int: 1, float: 2.25, string: "a" },
    { int: 2, float: 3.5, string: "b" },
    { int: 3, float: 4.75, string: "c" },
    { int: 4, float: 5.25, string: "d" },
];

// out of order to make sure we can read out of perspective in insertion
// order, not pkey order.
const all_types_data = [
    {
        int: 4,
        float: 5.25,
        string: "d",
        date: new Date(2020, 3, 15),
        datetime: new Date(2020, 0, 15, 23, 30),
        boolean: true,
    },
    {
        int: 3,
        float: 4.75,
        string: "c",
        date: new Date(2020, 2, 15),
        datetime: new Date(2020, 0, 15, 18, 30),
        boolean: false,
    },
    {
        int: 2,
        float: 3.5,
        string: "b",
        date: new Date(2020, 1, 15),
        datetime: new Date(2020, 0, 15, 12, 30),
        boolean: true,
    },
    {
        int: 1,
        float: 2.25,
        string: "a",
        date: new Date(2020, 0, 15),
        datetime: new Date(2020, 0, 15, 6, 30),
        boolean: false,
    },
    // values above should be replaced with these values below, due to
    // indexing
    {
        int: 4,
        float: 5.25,
        string: "d",
        date: new Date(2020, 3, 15),
        datetime: new Date(2020, 0, 15, 23, 30),
        boolean: true,
    },
    {
        int: 3,
        float: 4.75,
        string: "c",
        date: new Date(2020, 2, 15),
        datetime: new Date(2020, 0, 15, 18, 30),
        boolean: false,
    },
    {
        int: 2,
        float: 3.5,
        string: "b",
        date: new Date(2020, 1, 15),
        datetime: new Date(2020, 0, 15, 12, 30),
        boolean: true,
    },
    {
        int: 1,
        float: 2.25,
        string: "a",
        date: new Date(2020, 0, 15),
        datetime: new Date(2020, 0, 15, 6, 30),
        boolean: false,
    },
];

const datetime_data = [
    { datetime: new Date(), int: 1 },
    { datetime: new Date(), int: 1 },
    { datetime: new Date(), int: 2 },
    { datetime: new Date(), int: 2 },
];

// utility for checking typed arrays
function validate_typed_array(typed_array, column_data) {
    let is_valid = true;
    for (let i = 0; i < typed_array.length; i++) {
        if (column_data[i] !== null) {
            if (typed_array[i] !== column_data[i]) {
                is_valid = false;
                break;
            }
        }
    }
    return is_valid;
}

((perspective) => {
    // test.describe("Execute", function () {
    //     test.skip("serialized functions in a worker", async function () {
    //         var table = await perspective.table({
    //             x: "integer",
    //             y: "string",
    //             z: "boolean",
    //         });
    //         table.execute((t) => {
    //             t.update([
    //                 { x: 1, y: "a", z: true },
    //                 { x: 2, y: "b", z: false },
    //                 { x: 3, y: "c", z: true },
    //                 { x: 4, y: "d", z: false },
    //             ]);
    //         });
    //         let view = await table.view({});
    //         let js = await view.to_json();
    //         expect(js).toEqual([
    //             { x: 1, y: "a", z: true },
    //             { x: 2, y: "b", z: false },
    //             { x: 3, y: "c", z: true },
    //             { x: 4, y: "d", z: false },
    //         ]);
    //         view.delete();
    //         table.delete();
    //     });
    // });

    test.describe("Destructors", function () {
        test("calls delete() on table with no views", async function () {
            let table = await perspective.table(data);
            await table.delete();
            expect(true).toEqual(true);
        });

        test("calls delete on a view, then a table", async function () {
            var table = await perspective.table(data);
            var view = await table.view();
            await view.delete();
            await table.delete();
            expect(true).toEqual(true);
        });

        test("calls delete on multiple views, then a table", async function () {
            var table = await perspective.table(data);
            var view1 = await table.view();
            var view2 = await table.view();
            await view1.delete();
            await view2.delete();
            await table.delete();
            expect(true).toEqual(true);
        });
    });

    test.describe("Schema", function () {
        test("0-sided view", async function () {
            const table = await perspective.table(int_float_string_data);
            const view = await table.view();
            const schema = await view.schema();
            expect(schema).toEqual({
                int: "integer",
                float: "float",
                string: "string",
            });
            view.delete();
            table.delete();
        });

        test("0-sided view with columns selection", async function () {
            const table = await perspective.table(int_float_string_data);
            const view = await table.view({ columns: ["float", "string"] });
            const schema = await view.schema();
            expect(schema).toEqual({ float: "float", string: "string" });
            view.delete();
            table.delete();
        });
    });

    test.describe.skip("Typed Arrays", function () {
        test("Respects start/end rows", async function () {
            var table = await perspective.table(int_float_data);
            var view = await table.view();
            const result = await view.col_to_js_typed_array("int", {
                start_row: 1,
                end_row: 2,
            });
            expect(result[0].byteLength).toEqual(4);
            view.delete();
            table.delete();
        });

        test("Int, 0-sided view", async function () {
            var table = await perspective.table(int_float_data);
            var view = await table.view();
            const result = await view.col_to_js_typed_array("int");
            expect(result[0].byteLength).toEqual(16);
            view.delete();
            table.delete();
        });

        test("Float, 0-sided view", async function () {
            var table = await perspective.table(int_float_data);
            var view = await table.view();
            const result = await view.col_to_js_typed_array("float");
            expect(result[0].byteLength).toEqual(32);
            view.delete();
            table.delete();
        });

        test("Datetime, 0-sided view", async function () {
            var table = await perspective.table(datetime_data);
            var view = await table.view();
            var schema = await view.schema();
            expect(schema).toEqual({ datetime: "datetime", int: "integer" });
            const result = await view.col_to_js_typed_array("datetime");
            expect(result[0].byteLength).toEqual(32);
            view.delete();
            table.delete();
        });

        test("Int, 1-sided view", async function () {
            var table = await perspective.table(int_float_data);
            var view = await table.view({
                group_by: ["int"],
                columns: ["int", "float"],
            });
            const result = await view.col_to_js_typed_array("int");
            // should include aggregate row
            expect(result[0].byteLength).toEqual(20);
            view.delete();
            table.delete();
        });

        test("Float, 1-sided view", async function () {
            var table = await perspective.table(int_float_data);
            var view = await table.view({
                group_by: ["int"],
                columns: ["int", "float"],
            });
            const result = await view.col_to_js_typed_array("float");
            expect(result[0].byteLength).toEqual(40);
            view.delete();
            table.delete();
        });

        test("Datetime, 1-sided view", async function () {
            var table = await perspective.table(datetime_data);
            var view = await table.view({
                group_by: ["int"],
                columns: ["datetime"],
                aggregates: { datetime: "high" },
            });
            const result = await view.col_to_js_typed_array("datetime");
            expect(result[0].byteLength).toEqual(24);
            view.delete();
            table.delete();
        });

        test("Int, 2-sided view with group by", async function () {
            var table = await perspective.table(int_float_data);
            var view = await table.view({
                split_by: ["float"],
                group_by: ["int"],
                columns: ["int", "float"],
            });
            const result = await view.col_to_js_typed_array("3.5|int");
            expect(result[0].byteLength).toEqual(20);
            view.delete();
            table.delete();
        });

        test("Float, 2-sided view with group by", async function () {
            var table = await perspective.table(int_float_data);
            var view = await table.view({
                split_by: ["float"],
                group_by: ["int"],
                columns: ["int", "float"],
            });
            const result = await view.col_to_js_typed_array("3.5|float");
            expect(result[0].byteLength).toEqual(40);
            view.delete();
            table.delete();
        });

        test("Int, 2-sided view, no group by", async function () {
            var table = await perspective.table(int_float_data);
            var view = await table.view({ split_by: ["float"] });
            const result = await view.col_to_js_typed_array("3.5|int");
            // bytelength should not include the aggregate row
            expect(result[0].byteLength).toEqual(16);
            view.delete();
            table.delete();
        });

        test("Float, 2-sided view, no group by", async function () {
            var table = await perspective.table(int_float_data);
            var view = await table.view({ split_by: ["float"] });
            const result = await view.col_to_js_typed_array("3.5|float");
            expect(result[0].byteLength).toEqual(32);
            view.delete();
            table.delete();
        });

        test("Symmetric output with to_columns, 0-sided", async function () {
            let table = await perspective.table(int_float_data);
            let view = await table.view();
            let cols = await view.to_columns();

            for (let col in cols) {
                let ta = await view.col_to_js_typed_array(col);
                let column = cols[col];
                if (ta !== undefined && column !== undefined) {
                    expect(ta[0].length).toEqual(cols[col].length);
                    expect(validate_typed_array(ta[0], cols[col])).toEqual(
                        true,
                    );
                }
            }
            view.delete();
            table.delete();
        });

        test("Symmetric output with to_columns, 1-sided", async function () {
            let table = await perspective.table(int_float_string_data);
            let view = await table.view({
                group_by: ["int"],
                columns: ["int", "float"],
            });
            let cols = await view.to_columns();

            for (let col in cols) {
                let ta = await view.col_to_js_typed_array(col);
                let column = cols[col];
                if (ta !== undefined && column !== undefined) {
                    expect(ta[0].length).toEqual(cols[col].length);
                    expect(validate_typed_array(ta[0], cols[col])).toEqual(
                        true,
                    );
                }
            }
            view.delete();
            table.delete();
        });
    });

    test.describe("Other `View`s", function () {
        test("Construct a table from another view", async function () {
            const table = await perspective.table(int_float_string_data);
            const view = await table.view();
            const table2 = await perspective.table(view);
            const view2 = await table2.view();
            const data = await view2.to_json();
            expect(data).toEqual(int_float_string_data);
            view2.delete();
            table2.delete();
            view.delete();
            table.delete();
        });
    });

    test.describe("Errors", function () {
        test("Table constructor should throw an exception and reject promise", async function () {
            expect.assertions(1);
            perspective.table([1, 2, 3]).catch((error) => {
                expect(error.message).toContain(
                    "Abort(): Cannot determine data types without column names!\n",
                );
            });
        });

        test("View constructor should throw an exception and reject promise", async function () {
            expect.assertions(1);
            const table = await perspective.table(int_float_string_data);
            table
                .view({
                    group_by: ["abcd"],
                })
                .catch((error) => {
                    expect(error.message).toContain(
                        "Abort(): Invalid column 'abcd' found in View group_by.\n",
                    );
                    table.delete();
                });
        });

        test("Table constructor should throw an exception on await", async function () {
            expect.assertions(1);

            try {
                await perspective.table([1, 2, 3]);
            } catch (error) {
                expect(error.message).toContain(
                    "Abort(): Cannot determine data types without column names!\n",
                );
            }
        });

        test("View constructor should throw an exception on await", async function () {
            expect.assertions(1);
            const table = await perspective.table(int_float_string_data);

            try {
                await table.view({
                    group_by: ["abcd"],
                });
            } catch (error) {
                expect(error.message).toContain(
                    "Abort(): Invalid column 'abcd' found in View group_by.\n",
                );
                table.delete();
            }
        });

        test("Table constructor pads short trailing columns with null", async function () {
            const table = await perspective.table({
                overflow: [1, 2, 3, 4, 5, 6, 7, 8, 9],
                short: [1],
            });
            const view = await table.view();
            const result = await view.to_columns();
            expect(result).toEqual({
                overflow: [1, 2, 3, 4, 5, 6, 7, 8, 9],
                short: [1, null, null, null, null, null, null, null, null],
            });
            await view.delete();
            await table.delete();
        });

        test("Table constructor pads short leading columns with null", async function () {
            const table = await perspective.table({
                short: [1],
                overflow: [1, 2, 3, 4, 5, 6, 7, 8, 9],
            });
            const view = await table.view();
            const result = await view.to_columns();
            expect(result).toEqual({
                short: [1, null, null, null, null, null, null, null, null],
                overflow: [1, 2, 3, 4, 5, 6, 7, 8, 9],
            });
            await view.delete();
            await table.delete();
        });

        test("Table constructor handles columns of equal length", async function () {
            const table = await perspective.table({
                a: [1, 2, 3],
                b: ["x", "y", "z"],
            });
            const view = await table.view();
            const result = await view.to_columns();
            expect(result).toEqual({
                a: [1, 2, 3],
                b: ["x", "y", "z"],
            });
            await view.delete();
            await table.delete();
        });

        test("Table update pads short columns with null", async function () {
            const table = await perspective.table({
                a: "integer",
                b: "integer",
            });
            await table.update({
                a: [1, 2, 3, 4, 5],
                b: [1],
            });
            const view = await table.view();
            const result = await view.to_columns();
            expect(result).toEqual({
                a: [1, 2, 3, 4, 5],
                b: [1, null, null, null, null],
            });

            // The table must remain usable for subsequent updates.
            await table.update({ a: [10], b: [20] });
            const result2 = await view.to_columns();
            expect(result2).toEqual({
                a: [1, 2, 3, 4, 5, 10],
                b: [1, null, null, null, null, 20],
            });
            await view.delete();
            await table.delete();
        });
    });

    test.describe("Formatters", function () {
        test("Serializes a simple view to CSV", async function () {
            var table = await perspective.table(data);
            var view = await table.view({});
            var answer = `"x","y","z"\n1,"a",true\n2,"b",false\n3,"c",true\n4,"d",false\n`;
            let result = await view.to_csv();
            expect(result).toEqual(answer);
            view.delete();
            table.delete();
        });

        test("Serializes 1 sided view to CSV", async function () {
            var table = await perspective.table(data);
            var view = await table.view({
                group_by: ["z"],
                columns: ["x"],
            });
            var answer = `"z (Group by 1)","x"\n,10\nfalse,6\ntrue,4\n`;
            let result = await view.to_csv();
            expect(result).toEqual(answer);
            view.delete();
            table.delete();
        });

        test("Serializes a 2 sided view to CSV", async function () {
            var table = await perspective.table(data);
            var view = await table.view({
                group_by: ["z"],
                split_by: ["y"],
                columns: ["x"],
            });
            var answer = `"z (Group by 1)","a|x","b|x","c|x","d|x"\n,1,2,3,4\nfalse,,2,,4\ntrue,1,,3,\n`;
            let result = await view.to_csv();
            expect(result).toEqual(answer);
            view.delete();
            table.delete();
        });

        test("Serializes a simple view to column-oriented JSON", async function () {
            var table = await perspective.table(data_3);
            var view = await table.view({});
            let result = await view.to_columns();
            expect(result).toEqual(data_7);
            view.delete();
            table.delete();
        });
    });

    test.describe("CSV parsing", function () {
        test("Does not lose leading 0's when a CSV column is declared as a string", async function () {
            let table = await perspective.table({ x: "string", y: "integer" });
            await table.update("x,y\n000123,000123");
            let view = await table.view();
            let result = await view.to_json();
            expect(result).toEqual([{ x: "000123", y: 123 }]);
            view.delete();
            table.delete();
        });

        // Added
        test("Does not lose leading 0's when a CSV column is declared as a string (async)", async function () {
            let table = await perspective.table({ x: "string", y: "integer" });
            await table.update("x,y\n000123,000123");
            let view = await table.view();
            let result = await view.to_json();
            expect(result).toEqual([{ x: "000123", y: 123 }]);
            view.delete();
            table.delete();
        });

        test("Handles duplicate column names with different types", async function () {
            const csv = "A,A\ntest,1";
            let table = await perspective.table(csv);
            let view = await table.view();
            let csv2 = await view.to_json();
            expect(csv2).toEqual([{ A: "test", "A*": 1 }]);
            view.delete();
            table.delete();
        });

        test("Handles duplicate column names with rename collisions", async function () {
            const csv = "A,A,A*\ntest,1,2";
            let table = await perspective.table(csv);
            let view = await table.view();
            let csv2 = await view.to_json();
            expect(csv2).toEqual([{ A: "test", "A*": 1, "A**": 2 }]);
            view.delete();
            table.delete();
        });

        test("Handles strings with quotation characters and commas", async function () {
            let table = await perspective.table({ x: "string", y: "integer" });
            await table.update([
                { x: "Test, hello!", y: 1 },
                { x: 'Test2"', y: 2 },
                { x: 'Test3, Hello!"', y: 3 },
            ]);
            let view = await table.view();
            let result = await view.to_csv();
            expect(result).toEqual(
                `"x","y"\n"Test, hello!",1\n"Test2""",2\n"Test3, Hello!""",3\n`,
            );
            view.delete();
            table.delete();
        });

        // Added
        test("Handles strings with quotation characters and commas (async)", async function () {
            let table = await perspective.table({ x: "string", y: "integer" });
            await table.update([
                { x: "Test, hello!", y: 1 },
                { x: 'Test2"', y: 2 },
                { x: 'Test3, Hello!"', y: 3 },
            ]);
            let view = await table.view();
            let result = await view.to_csv();
            expect(result).toEqual(
                `"x","y"\n"Test, hello!",1\n"Test2""",2\n"Test3, Hello!""",3\n`,
            );
            view.delete();
            table.delete();
        });

        test("Transitively loads a CSV created from `to_csv()` on a table with a datetime column", async function () {
            // Assert that the CSV parser can handle POSIX timestamps.
            let table = await perspective.table(arrow_date_data);
            let view = await table.view();
            let schema = await table.schema();
            let csv = await view.to_csv();
            let table2 = await perspective.table(csv);
            let schema2 = await table2.schema();
            let view2 = await table2.view();
            let csv2 = await view2.to_csv();
            expect(schema2).toEqual(schema);
            expect(csv2).toEqual(csv);
            view2.delete();
            table2.delete();
            view.delete();
            table.delete();
        });
    });

    test.describe("ndjson", function () {
        test("basic constructor", async function () {
            var table = await perspective.table(`{"x":1}\n{"x":2}`, {
                format: "ndjson",
            });
            var view = await table.view();
            let result = await view.to_json();
            expect(result).toEqual([{ x: 1 }, { x: 2 }]);
            view.delete();
            table.delete();
        });

        test("date types", async function () {
            const ndjson = [];
            for (const row of data_4) {
                ndjson.push(row);
            }
            var table = await perspective.table(
                ndjson.map(JSON.stringify).join("\n"),
                {
                    format: "ndjson",
                },
            );
            var view = await table.view();
            let result = await view.to_json();
            expect(result).toEqual([{ v: +data_4[0]["v"] }]);
            view.delete();
            table.delete();
        });

        test("Handles dates when provided a schema", async function () {
            var table = await perspective.table(meta_4);
            await table.update(data_4.map(JSON.stringify).join("\n"), {
                format: "ndjson",
            });

            let view = await table.view();
            let result = await view.to_json();
            expect(result).toEqual([{ v: +data_4[0]["v"] }]);
            view.delete();
            table.delete();
        });

        test("Handles many objects without newline separators", async function () {
            const expected = [];
            for (let i = 0; i < 64; i++) {
                expected.push({ a: `row-${i}` });
            }
            const data = expected.map(JSON.stringify).join("");
            const table = await perspective.table(data, { format: "ndjson" });
            const view = await table.view();
            expect(await table.size()).toEqual(64);
            expect(await view.to_json()).toEqual(expected);
            await view.delete();
            await table.delete();
        });
    });

    test.describe("Constructors", function () {
        test("JSON constructor", async function () {
            var table = await perspective.table(data);
            var view = await table.view();
            let result = await view.to_json();
            expect(result).toEqual(data);
            view.delete();
            table.delete();
        });

        test("JSON column oriented constructor", async function () {
            var table = await perspective.table(col_data);
            var view = await table.view();
            let result = await view.to_json();
            expect(result).toEqual(data);
            view.delete();
            table.delete();
        });

        test("Arrow constructor", async function () {
            var table = await perspective.table(arrows.test_null_arrow.slice());
            var view = await table.view();
            let result = await view.to_json();
            expect(result).toEqual(arrow_result);
            view.delete();
            table.delete();
        });

        test.skip("Arrow (chunked format) constructor", async function () {
            var table = await perspective.table(arrows.chunked_arrow.slice());
            var view = await table.view();
            let result = await view.to_json();
            expect(result.length).toEqual(10);
            view.delete();
            table.delete();
        });

        test("Arrow date32 constructor", async function () {
            const table = await perspective.table(arrows.date32_arrow.slice());
            const view = await table.view();
            const result = await view.to_columns();
            expect(result).toEqual(arrow_date_data);
            view.delete();
            table.delete();
        });

        test("Arrow float32 constructor", async function () {
            const table = await perspective.table(arrows.float32_arrow.slice());
            const view = await table.view();
            const result = await view.to_columns();
            expect(result).toEqual({
                id: [1, 2, 3],
                name: ["Alice", "Bob", "Charlie"],
                score: [92.5, 87.30000305175781, 95.80000305175781],
            });
            view.delete();
            table.delete();
        });

        test("Arrow date64 constructor", async function () {
            const table = await perspective.table(arrows.date64_arrow.slice());
            const view = await table.view();
            const result = await view.to_columns();
            expect(result).toEqual(arrow_date_data);
            view.delete();
            table.delete();
        });

        test("Arrow Lists constructor", async function () {
            const table = await perspective.table(arrows.lists_arrow.slice());
            const view = await table.view();
            const result = await view.to_columns();
            expect(result).toEqual(arrow_lists_data);
            view.delete();
            table.delete();
        });

        test("Arrow dictionary constructor", async function () {
            const table = await perspective.table(arrows.dict_arrow.slice());
            const view = await table.view();
            const result = await view.to_columns();
            expect(result).toEqual({
                a: ["abc", "def", "def", null, "abc"],
                b: ["klm", "hij", null, "hij", "klm"],
            });
            view.delete();
            table.delete();
        });

        test("CSV constructor with null columns", async function () {
            const table = await perspective.table("x,y\n1,");
            const view = await table.view();
            expect(await table.schema()).toEqual({
                x: "integer",
                y: "string",
            });
            const result = await view.to_columns();
            expect(result).toEqual({
                x: [1],
                y: [null],
            });
            await view.delete();
            await table.delete();
        });

        test("CSV constructor indexed, with null columns, and update", async function () {
            const table = await perspective.table("x,y\n1,", { index: "x" });
            const view = await table.view();
            expect(await table.schema()).toEqual({
                x: "integer",
                y: "string",
            });
            const result = await view.to_columns();
            expect(result).toEqual({
                x: [1],
                y: [null],
            });
            await table.update("x,y\n1,abc\n2,123");
            const result2 = await view.to_columns();
            expect(result2).toEqual({
                x: [1, 2],
                y: ["abc", "123"],
            });
            await view.delete();
            await table.delete();
        });

        test("Meta constructor", async function () {
            var table = await perspective.table(meta);
            var view = await table.view();
            let result = await view.to_json();
            expect(result).toEqual([]);
            view.delete();
            table.delete();
        });

        test("Handles floats", async function () {
            var table = await perspective.table(data_3);
            var view = await table.view();
            let result = await view.to_json();
            expect(result).toEqual(data_3);
            view.delete();
            table.delete();
        });

        test("Infers ints wrapped in strings", async function () {
            var table = await perspective.table(int_in_string);
            var view = await table.view();
            let result = await view.schema();
            expect(result).toEqual({ a: "integer" });
            view.delete();
            table.delete();
        });

        test("Infers floats wrapped in strings", async function () {
            var table = await perspective.table(float_in_string);
            var view = await table.view();
            let result = await view.schema();
            expect(result).toEqual({ a: "float" });
            view.delete();
            table.delete();
        });

        test("Infers correct type for empty string columns", async function () {
            var table = await perspective.table([
                { x: "", y: 1 },
                { x: "", y: 2 },
                { x: "", y: 3 },
                { x: "", y: 4 },
            ]);
            var view = await table.view();
            let result = await view.schema();
            expect(result).toEqual({ x: "string", y: "integer" });
            view.delete();
            table.delete();
        });

        test("Returns the correct number of rows for column-only views", async function () {
            var table = await perspective.table(data);
            var view = await table.view();
            var num_rows = await view.num_rows();
            var view2 = await table.view({
                split_by: ["x"],
            });
            var num_rows_col_only = await view2.num_rows();
            expect(num_rows_col_only).toEqual(num_rows);
            view.delete();
            view2.delete();
            table.delete();
        });

        test.skip("Handles inconsistent rows with same width", async function () {
            const int_to_float = [
                { x: 1, y: 2 },
                { y: 2, z: 3 },
            ];
            var table = await perspective.table(int_to_float);
            var view = await table.view();
            var json = await view.to_json();
            expect(json).toEqual([
                { x: 1, y: 2, z: null },
                { x: null, y: 2, z: 3 },
            ]);
            view.delete();
            table.delete();
        });

        test.skip("Handles inconsistent rows", async function () {
            const int_to_float = [{ x: 1 }, { y: 2, z: 3 }];
            var table = await perspective.table(int_to_float);
            var schema = await table.schema();
            expect(schema).toEqual({
                x: "integer",
                y: "integer",
                z: "integer",
            });
            var view = await table.view();
            var json = await view.to_json();
            expect(json).toEqual([
                { x: 1, y: null, z: null },
                { x: null, y: 2, z: 3 },
            ]);
            view.delete();
            table.delete();
        });

        test("Upgrades integer columns to strings", async function () {
            const int_to_float = {
                a: [1, 2, 3, "x", "y"],
            };

            var table = await perspective.table(int_to_float);
            var schema_1 = await table.schema();
            expect(schema_1["a"]).toEqual("string");
            var view = await table.view();
            var json = await view.to_json();
            expect(json).toEqual([
                { a: "1" },
                { a: "2" },
                { a: "3" },
                { a: "x" },
                { a: "y" },
            ]);
            view.delete();
            table.delete();
        });

        test("Upgrades integer columns with values beyond max/min_int to float", async function () {
            const int_to_float = {
                a: [1, 2, 3, 2147483667, 5],
            };

            var table = await perspective.table(int_to_float);
            var view = await table.view();
            var json = await view.to_columns();
            expect(json).toEqual(int_to_float);
            var schema_2 = await table.schema();
            expect(schema_2["a"]).toEqual("float");
            view.delete();
            table.delete();
        });

        // This currently won't work, and I'm unclear we want it to - upgrading
        // ths column in place is easy, but once the gnode and potentially
        // contexts have been created, this becomes much more difficult.
        test.skip("Upgrades integer columns with values beyond max/min_int to float old", async function () {
            const schema = {
                a: "integer",
            };

            const int_to_float = {
                a: [1, 2, 3, 2147483667, 5],
            };

            var table = await perspective.table(schema);
            var schema_1 = await table.schema();
            expect(schema_1["a"]).toEqual("integer");

            await table.update(int_to_float);

            var schema_2 = await table.schema();
            expect(schema_2["a"]).toEqual("float");
            table.delete();
        });

        test("Does not infer float column as integers", async function () {
            const int_to_float = [];
            for (let x = 0; x < 200; x++) {
                int_to_float.push({ a: 1 });
            }
            int_to_float.push({ a: 2147483667 });

            var table = await perspective.table(int_to_float);
            var schema_1 = await table.schema();
            expect(schema_1["a"]).toEqual("float");
            let view = await table.view();
            var data = await view.to_json();
            expect(data).toEqual(int_to_float);

            view.delete();
            table.delete();
        });

        test("has correct size", async function () {
            var table = await perspective.table(data);
            let result = await table.size();
            expect(result).toEqual(4);
            table.delete();
        });

        test("has a schema", async function () {
            var table = await perspective.table(data);
            let result = await table.schema();
            expect(result).toEqual(meta);
            table.delete();
        });

        test("has columns", async function () {
            var table = await perspective.table(data);
            let result = await table.columns();
            expect(result).toEqual(["x", "y", "z"]);
            table.delete();
        });

        test("Handles floats schemas", async function () {
            var table = await perspective.table(data_3);
            let result = await table.schema();
            expect(result).toEqual(meta_3);
            table.delete();
        });

        test("Generates correct date schemas", async function () {
            var table = await perspective.table(data_4);
            let result = await table.schema();
            expect(result).toEqual(meta_4);
            table.delete();
        });

        test("Handles date updates when constructed from a schema", async function () {
            var table = await perspective.table(meta_4);
            await table.update(data_4);
            let view = await table.view();
            let result = await view.to_json();
            expect(result).toEqual([{ v: +data_4[0]["v"] }]);
            view.delete();
            table.delete();
        });

        test("Handles datetime values", async function () {
            var table = await perspective.table(data_4);
            let view = await table.view();
            let result = await view.to_json();
            expect(result).toEqual([{ v: +data_4[0]["v"] }]);
            view.delete();
            table.delete();
        });

        test("Handles datetime strings", async function () {
            var table = await perspective.table(data_5);
            let view = await table.view();
            let result = await view.to_json();
            expect(result).toEqual([
                { v: +moment(data_5[0]["v"], "MM-DD-YYYY") },
            ]);
            view.delete();
            table.delete();
        });

        test("Handles datetime strings in US locale string old", async function () {
            // FIXME: 1/1/2020, 12:30:45 PM = 1/1/2020, 7:30:45 AM UTC, but
            // because C++ strptime in Emscripten parses the time as 12:30:45AM,
            // the output is 12/31/2019 19:30:45 PM UTC. This is clearly wrong.
            const data = {
                x: [
                    "1/1/2020, 12:30:45 PM",
                    "1/1/2020, 12:30:45 AM",
                    "03/15/2020, 11:30:45 AM",
                    "06/30/2020, 01:30:45 AM",
                    "06/30/2020, 01:30:45 PM",
                    "12/31/2020, 11:59:59 PM",
                ],
            };
            const table = await perspective.table(data);
            const schema = await table.schema();

            expect(schema).toEqual({
                x: "datetime",
            });

            const view = await table.view();
            const result = await view.to_columns();

            const expected = {
                x: [
                    "1/1/2020, 12:30:45 PM",
                    "1/1/2020, 12:30:45 AM",
                    "03/15/2020, 11:30:45 AM",
                    "06/30/2020, 01:30:45 AM",
                    "06/30/2020, 01:30:45 PM",
                    "12/31/2020, 11:59:59 PM",
                ].map((v) => new Date(v).getTime()),
            };

            expect(result).toEqual(expected);
            view.delete();
            table.delete();
        });

        test("Handles datetime strings in US locale string", async function () {
            const data = {
                x: [
                    "1/1/2020, 05:30:45 AM",
                    "03/15/2020, 11:30:45 AM",
                    "06/30/2020, 01:30:45 AM",
                    "12/31/2020, 11:59:59 PM",
                ],
            };
            const table = await perspective.table(data);
            const schema = await table.schema();

            expect(schema).toEqual({
                x: "datetime",
            });

            const view = await table.view();
            const result = await view.to_columns();

            const expected = {
                x: [
                    "1/1/2020, 05:30:45 AM",
                    "03/15/2020, 11:30:45 AM",
                    "06/30/2020, 01:30:45 AM",
                    "12/31/2020, 11:59:59 PM",
                ].map((v) => new Date(v).getTime()),
            };

            expect(result).toEqual(expected);
            view.delete();
            table.delete();
        });

        test("Handles datetime values with mixed formats", async function () {
            var table = await perspective.table({ datetime: "datetime" });
            await table.update([
                { datetime: new Date(1549257586108) },
                { datetime: "2019-01-30" },
                { datetime: 11 },
            ]);
            let view = await table.view();
            let result = await view.to_json();
            expect(result).toEqual([
                { datetime: 1549257586108 },
                { datetime: 1548806400000 },
                { datetime: 11 },
            ]);
            view.delete();
            table.delete();
        });

        test("Handles date values", async function () {
            var table = await perspective.table({ v: "date" });
            await table.update(data_4);
            let view = await table.view();
            let result = await view.to_json();
            let d = new Date(data_4[0]["v"]);
            d.setHours(0);
            d.setMinutes(0);
            d.setSeconds(0);
            d.setMilliseconds(0);
            expect(result).toEqual([{ v: +d }]);
            view.delete();
            table.delete();
        });

        test("CSV Parses an ISO-8601 formatted string with milliseconds and timezone", async function () {
            let table = await perspective.table(
                "d\n2008-09-15T10:00:00.123+05:45",
            );
            let view = await table.view();
            let result = await view.to_csv();
            expect(result).toEqual('"d"\n2008-09-15 04:15:00.123\n');
            await view.delete();
            await table.delete();
        });

        test("JSON Parses an ISO-8601 formatted string with milliseconds and timezone", async function () {
            let table = await perspective.table({
                d: ["2008-09-15T10:00:00.123+05:45"],
            });
            let view = await table.view();
            let result = await view.to_csv();
            expect(result).toEqual('"d"\n2008-09-15 04:15:00.123\n');
            await view.delete();
            await table.delete();
        });

        test("CSV Updates an ISO-8601 formatted string with milliseconds and timezone", async function () {
            let table = await perspective.table({ d: "datetime" });
            await table.update("d\n2008-09-15T10:00:00.123+05:45");
            let view = await table.view();
            let result = await view.to_csv();
            expect(result).toEqual('"d"\n2008-09-15 04:15:00.123\n');
            await view.delete();
            await table.delete();
        });

        test("JSON Updates an ISO-8601 formatted string with milliseconds and timezone", async function () {
            let table = await perspective.table({ d: "datetime" });
            await table.update({
                d: ["2008-09-15T10:00:00.123+05:45"],
            });
            let view = await table.view();
            let result = await view.to_csv();
            expect(result).toEqual('"d"\n2008-09-15 04:15:00.123\n');
            await view.delete();
            await table.delete();
        });

        test("CSV Parses an ISO-8601 formatted string with microseconds and timezone", async function () {
            let table = await perspective.table(
                "d\n2008-09-15T15:30:00.123456+05:30",
            );
            let view = await table.view();
            let result = await view.to_csv();
            expect(result).toEqual('"d"\n2008-09-15 10:00:00.123\n');
            await view.delete();
            await table.delete();
        });

        test("JSON Parses an ISO-8601 formatted string with microseconds and timezone", async function () {
            let table = await perspective.table({
                d: ["2008-09-15T15:30:00.123456+05:30"],
            });
            let view = await table.view();
            let result = await view.to_csv();
            expect(result).toEqual('"d"\n2008-09-15 10:00:00.123\n');
            await view.delete();
            await table.delete();
        });

        test("CSV Updates an ISO-8601 formatted string with microseconds and timezone", async function () {
            let table = await perspective.table({ d: "datetime" });
            await table.update("d\n2008-09-15T15:30:00.123456+05:30");
            let view = await table.view();
            let result = await view.to_csv();
            expect(result).toEqual('"d"\n2008-09-15 10:00:00.123\n');
            await view.delete();
            await table.delete();
        });

        test("JSON Updates an ISO-8601 formatted string with microseconds and timezone", async function () {
            let table = await perspective.table({ d: "datetime" });
            await table.update({
                d: ["2008-09-15T15:30:00.123456+05:30"],
            });
            let view = await table.view();
            let result = await view.to_csv();
            expect(result).toEqual('"d"\n2008-09-15 10:00:00.123\n');
            await view.delete();
            await table.delete();
        });

        test("CSV Parses an ISO-8601 formatted string with nanoseconds and timezone", async function () {
            let table = await perspective.table(
                "d\n2008-09-15T15:15:00.123456789-05:15",
            );
            let view = await table.view();
            let result = await view.to_csv();
            expect(result).toEqual('"d"\n2008-09-15 20:30:00.123\n');
            await view.delete();
            await table.delete();
        });

        test("JSON Parses an ISO-8601 formatted string with nanoseconds and timezone", async function () {
            let table = await perspective.table({
                d: ["2008-09-15T15:15:00.123456789-05:15"],
            });
            let view = await table.view();
            let result = await view.to_csv();
            expect(result).toEqual('"d"\n2008-09-15 20:30:00.123\n');
            await view.delete();
            await table.delete();
        });

        test("CSV Updates an ISO-8601 formatted string with nanoseconds and timezone", async function () {
            let table = await perspective.table({ d: "datetime" });
            await table.update("d\n2008-09-15T15:15:00.123456789-05:15");
            let view = await table.view();
            let result = await view.to_csv();
            expect(result).toEqual('"d"\n2008-09-15 20:30:00.123\n');
            await view.delete();
            await table.delete();
        });

        test("JSON Updates an ISO-8601 formatted string with nanoseconds and timezone", async function () {
            let table = await perspective.table({ d: "datetime" });
            await table.update({
                d: ["2008-09-15T15:15:00.123456789-05:15"],
            });
            let view = await table.view();
            let result = await view.to_csv();
            expect(result).toEqual('"d"\n2008-09-15 20:30:00.123\n');
            await view.delete();
            await table.delete();
        });

        test("CSV Reader parses milliseconds", async function () {
            const a = '"start"\n2024-08-14T14:06:07.826Z';
            const table = await perspective.table(a);
            const view = await table.view();
            const csv1 = await view.to_csv();
            expect(csv1).toEqual('"start"\n2024-08-14 14:06:07.826\n');
            view.delete();
            table.delete();
        });

        test("CSV Reader update parses milliseconds", async function () {
            const a = '"d"\n2024-08-14T14:06:07.826Z';
            const table = await perspective.table({
                d: "datetime",
            });
            await table.update(a);
            const view = await table.view();
            const csv1 = await view.to_csv();
            expect(csv1).toEqual('"d"\n2024-08-14 14:06:07.826\n');
            view.delete();
            table.delete();
        });

        test("CSV Reader update updates microseconds", async function () {
            const a = '"d"\n2024-08-14T14:06:07.826789Z';
            const table = await perspective.table({
                d: "datetime",
            });
            await table.update(a);
            const view = await table.view();
            const csv1 = await view.to_csv();
            expect(csv1).toEqual('"d"\n2024-08-14 14:06:07.826\n');
            view.delete();
            table.delete();
        });

        test("CSV Reader update updates nanoseconds", async function () {
            const a = '"d"\n2024-08-14T14:06:07.826789789Z';
            const table = await perspective.table({
                d: "datetime",
            });
            await table.update(a);
            const view = await table.view();
            const csv1 = await view.to_csv();
            expect(csv1).toEqual('"d"\n2024-08-14 14:06:07.826\n');
            view.delete();
            table.delete();
        });

        test("CSV Reader update parses microseconds", async function () {
            const a = '"d"\n2024-08-14T14:06:07.826789Z';
            const table = await perspective.table(a);
            const view = await table.view();
            const csv1 = await view.to_csv();
            expect(csv1).toEqual('"d"\n2024-08-14 14:06:07.826\n');
            view.delete();
            table.delete();
        });

        test("CSV Reader update parses nanoseconds", async function () {
            const a = '"d"\n2024-08-14T14:06:07.826789789Z';
            const table = await perspective.table(a);
            const view = await table.view();
            const csv1 = await view.to_csv();
            expect(csv1).toEqual('"d"\n2024-08-14 14:06:07.826\n');
            view.delete();
            table.delete();
        });

        test("JSON Reader parses milliseconds", async function () {
            const a = "2024-08-14T14:06:07.826Z";
            const table = await perspective.table({
                d: "datetime",
            });
            await table.update({ d: [a] });
            const view = await table.view();
            const csv1 = await view.to_csv();
            expect(csv1).toEqual('"d"\n2024-08-14 14:06:07.826\n');
            view.delete();
            table.delete();
        });

        test("Handles utf16 column names", async function () {
            var table = await perspective.table({ š: [1, 2, 3] });
            let view = await table.view({});
            let result = await view.schema();
            expect(result).toEqual({ š: "integer" });
            view.delete();
            table.delete();
        });

        test("Handles utf16", async function () {
            var table = await perspective.table(data_6);
            let view = await table.view({});
            let result = await view.to_json();
            expect(result).toEqual(data_6);
            view.delete();
            table.delete();
        });

        test.describe("Datetime constructors", function () {
            test("Correctly parses an ISO-8601 formatted string", async function () {
                let table = await perspective.table({
                    d: ["2011-10-05T14:48:00"],
                });
                let view = await table.view({});
                let result = await view.schema();
                expect(result["d"]).toEqual("datetime");
                await view.delete();
                await table.delete();
            });

            test("Correctly parses an ISO-8601 formatted string 2", async function () {
                let table = await perspective.table({
                    d: ["2011-10-05T14:48:00.000"],
                });
                let view = await table.view({});
                let result = await view.schema();
                expect(result["d"]).toEqual("datetime");
                await view.delete();
                await table.delete();
            });

            test("Correctly parses an ISO-8601 formatted string 3", async function () {
                let table = await perspective.table({
                    d: ["2011-10-05T14:48:00Z"],
                });
                let view = await table.view({});
                let result = await view.schema();
                expect(result["d"]).toEqual("datetime");
                await view.delete();
                await table.delete();
            });

            test("Correctly parses an ISO-8601 formatted string 4", async function () {
                let table = await perspective.table({
                    d: ["2011-10-05T14:48:00.000Z"],
                });
                let view = await table.view({});
                let result = await view.schema();
                expect(result["d"]).toEqual("datetime");
                await view.delete();
                await table.delete();
            });

            test("Correctly parses an ISO-8601 formatted string with timezone", async function () {
                let table = await perspective.table({
                    d: ["2008-09-15T15:53:00+05:00"],
                });
                let view = await table.view({});
                let result = await view.schema();
                expect(result["d"]).toEqual("datetime");
                await view.delete();
                await table.delete();
            });

            test.skip("Correctly parses an RFC 2822 formatted string", async function () {
                let table = await perspective.table({
                    d: ["Wed, 05 Oct 2011 22:26:12 -0400"],
                });
                let view = await table.view({});
                let result = await view.schema();
                expect(result["d"]).toEqual("datetime");
                await view.delete();
                await table.delete();
            });

            // Not all formats covered by JS parser, test intended for C++ parser
            test.skip("Correctly parses all m-d-y formatted strings", async function () {
                let datestrings = [
                    "08-15-2009",
                    "08/15/2009",
                    "08-15-2009",
                    "02 28 2009",
                    "08/15/10",
                    "31 08 2009",
                ];
                for (let str of datestrings) {
                    let table = await perspective.table({ d: [str] });
                    let view = await table.view({});
                    let result = await view.schema();
                    expect(result["d"]).toEqual("datetime");
                    await view.delete();
                    await table.delete();
                }
            });

            // Only implemented in the C++ date parser - skip
            test.skip("Correctly parses a 'dd mm yyyy' formatted string", async function () {
                let table = await perspective.table({ d: ["15 08 08"] });
                let view = await table.view({});
                let result = await view.schema();
                expect(result["d"]).toEqual("datetime");
                await view.delete();
                await table.delete();
            });

            test("Does parse a date string in non-US formatting", async function () {
                let table = await perspective.table({ d: ["2018/07/30"] });
                let view = await table.view({});
                let result = await view.schema();
                expect(result["d"]).toEqual("date");
                await view.delete();
                await table.delete();
            });

            test("Does not mistakenly parse a date-like string", async function () {
                let table = await perspective.table({ d: ["Jan 14, 14"] });
                let view = await table.view({});
                let result = await view.schema();
                expect(result["d"]).toEqual("string");
                await view.delete();
                await table.delete();
            });
        });

        test("allocates a large tables", async function () {
            function makeid() {
                var text = "";
                var possible = Array.from(Array(26).keys()).map((x) =>
                    String.fromCharCode(x + 65),
                );
                for (var i = 0; i < 15; i++)
                    text +=
                        possible[Math.floor(Math.random() * possible.length)];
                return text;
            }
            let data = [];
            for (let i = 0; i < 35000; i++) {
                data.push({
                    a: makeid(),
                    b: makeid(),
                    c: makeid(),
                    d: makeid(),
                    w: i + 0.5,
                    x: i,
                    y: makeid(),
                });
            }
            let table = await perspective.table(data);
            let view = await table.view();
            let result = await view.to_json();
            expect(result.length).toEqual(35000);
            view.delete();
            table.delete();
        }, 3000);

        test.describe("get_index/get_limit", function () {
            test("get_index() on unindexed table", async function () {
                const table = await perspective.table(data);
                expect(await table.get_index()).toBeUndefined();
                await table.delete();
            });

            test("get_index() on an indexed table", async function () {
                const table = await perspective.table(data, { index: "x" });
                expect(await table.get_index()).toEqual("x");
                await table.delete();
            });

            test("get_limit() on table without limit", async function () {
                const table = await perspective.table(data);
                expect(await table.get_limit()).toBeUndefined();
                await table.delete();
            });

            test("get_limit() on table with limit", async function () {
                const table = await perspective.table(data, { limit: 2 });
                expect(await table.get_limit()).toEqual(2);
                await table.delete();
            });
        });

        test.describe("Limit table constructors", function () {
            // This is a better version of the OG but still questionable.
            // IMO we should not allow this without an explicit `__INDEX__`.
            test("Should not apply partial updates on limit tables when index wraps", async function () {
                const table = await perspective.table(
                    { a: "float", b: "float" },
                    {
                        limit: 3,
                    },
                );

                await table.update([
                    { a: 10, b: 1 },
                    { a: 20, b: 2 },
                ]);

                await table.update([{ a: 30 }, { a: 40 }]);
                const view = await table.view();
                const records = await view.to_json();
                await view.delete();
                await table.delete();
                expect(records).toEqual([
                    { a: 20, b: 2 },
                    { a: 30, b: null },
                    { a: 40, b: null },
                ]);
            });

            // We don' tsupport mixed-partial updates anymore!
            test.skip("OG - Should not apply partial updates on limit tables when index wraps", async function () {
                const table = await perspective.table(
                    { a: "float", b: "float" },
                    {
                        limit: 3,
                    },
                );

                await table.update([
                    { a: 10 },
                    { b: 1 },
                    { a: 20 },
                    { a: null, b: 2 },
                ]);
                const view = await table.view();
                const records = await view.to_json();
                await view.delete();
                await table.delete();

                const table2 = await perspective.table(
                    { a: "float", b: "float" },
                    {
                        limit: 3,
                    },
                );

                table2.update([{ a: 10 }, { b: 1 }, { a: 20 }, { b: 2 }]);
                const view2 = await table2.view();
                const records2 = await view2.to_json();
                await view2.delete();
                await table2.delete();

                expect(records).toEqual(records2);
            });
        });

        test.describe("Indexed table constructors", function () {
            test("Should index on an integer column", async function () {
                const table = await perspective.table(all_types_data, {
                    index: "int",
                });
                const view = await table.view();
                expect(await table.size()).toEqual(4);
                expect(await view.to_json()).toEqual([
                    {
                        int: 1,
                        float: 2.25,
                        string: "a",
                        date: 1579046400000,
                        datetime: 1579069800000,
                        boolean: false,
                    },
                    {
                        int: 2,
                        float: 3.5,
                        string: "b",
                        date: 1581724800000,
                        datetime: 1579091400000,
                        boolean: true,
                    },
                    {
                        int: 3,
                        float: 4.75,
                        string: "c",
                        date: 1584230400000,
                        datetime: 1579113000000,
                        boolean: false,
                    },
                    {
                        int: 4,
                        float: 5.25,
                        string: "d",
                        date: 1586908800000,
                        datetime: 1579131000000,
                        boolean: true,
                    },
                ]);
                await view.delete();
                await table.delete();
            });

            test("Should index on a float column", async function () {
                const table = await perspective.table(all_types_data, {
                    index: "float",
                });
                const view = await table.view();
                expect(await table.size()).toEqual(4);
                expect(await view.to_json()).toEqual([
                    {
                        int: 1,
                        float: 2.25,
                        string: "a",
                        date: 1579046400000,
                        datetime: 1579069800000,
                        boolean: false,
                    },
                    {
                        int: 2,
                        float: 3.5,
                        string: "b",
                        date: 1581724800000,
                        datetime: 1579091400000,
                        boolean: true,
                    },
                    {
                        int: 3,
                        float: 4.75,
                        string: "c",
                        date: 1584230400000,
                        datetime: 1579113000000,
                        boolean: false,
                    },
                    {
                        int: 4,
                        float: 5.25,
                        string: "d",
                        date: 1586908800000,
                        datetime: 1579131000000,
                        boolean: true,
                    },
                ]);
                await view.delete();
                await table.delete();
            });

            test("Should index on a string column", async function () {
                const table = await perspective.table(all_types_data, {
                    index: "string",
                });
                const view = await table.view();
                expect(await table.size()).toEqual(4);
                expect(await view.to_json()).toEqual([
                    {
                        int: 1,
                        float: 2.25,
                        string: "a",
                        date: 1579046400000,
                        datetime: 1579069800000,
                        boolean: false,
                    },
                    {
                        int: 2,
                        float: 3.5,
                        string: "b",
                        date: 1581724800000,
                        datetime: 1579091400000,
                        boolean: true,
                    },
                    {
                        int: 3,
                        float: 4.75,
                        string: "c",
                        date: 1584230400000,
                        datetime: 1579113000000,
                        boolean: false,
                    },
                    {
                        int: 4,
                        float: 5.25,
                        string: "d",
                        date: 1586908800000,
                        datetime: 1579131000000,
                        boolean: true,
                    },
                ]);
                await view.delete();
                await table.delete();
            });

            test("Should index on a date column", async function () {
                const table = await perspective.table(all_types_data, {
                    index: "date",
                });
                const view = await table.view();
                expect(await table.size()).toEqual(4);
                expect(await view.to_json()).toEqual([
                    {
                        int: 1,
                        float: 2.25,
                        string: "a",
                        date: 1579046400000,
                        datetime: 1579069800000,
                        boolean: false,
                    },
                    {
                        int: 2,
                        float: 3.5,
                        string: "b",
                        date: 1581724800000,
                        datetime: 1579091400000,
                        boolean: true,
                    },
                    {
                        int: 3,
                        float: 4.75,
                        string: "c",
                        date: 1584230400000,
                        datetime: 1579113000000,
                        boolean: false,
                    },
                    {
                        int: 4,
                        float: 5.25,
                        string: "d",
                        date: 1586908800000,
                        datetime: 1579131000000,
                        boolean: true,
                    },
                ]);
                await view.delete();
                await table.delete();
            });

            test("Should index on a datetime column", async function () {
                const table = await perspective.table(all_types_data, {
                    index: "datetime",
                });
                const view = await table.view();
                expect(await table.size()).toEqual(4);
                expect(await view.to_json()).toEqual([
                    {
                        int: 1,
                        float: 2.25,
                        string: "a",
                        date: 1579046400000,
                        datetime: 1579069800000,
                        boolean: false,
                    },
                    {
                        int: 2,
                        float: 3.5,
                        string: "b",
                        date: 1581724800000,
                        datetime: 1579091400000,
                        boolean: true,
                    },
                    {
                        int: 3,
                        float: 4.75,
                        string: "c",
                        date: 1584230400000,
                        datetime: 1579113000000,
                        boolean: false,
                    },
                    {
                        int: 4,
                        float: 5.25,
                        string: "d",
                        date: 1586908800000,
                        datetime: 1579131000000,
                        boolean: true,
                    },
                ]);
                await view.delete();
                await table.delete();
            });
        });
    });
})(perspective);
