// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████       █      █      █      █      █ █▄  ▀███ █       ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█  ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄  ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄   █ ▄▄▄▄▄ ┃
// ┃ █      ██████ █  ▀█▄       █ ██████      █      ███▌▐███ ███████▄ █       ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ 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 { STD_DATE, int_float_string_data, pivoted_output } from "./_shared";

((perspective) => {
    test.describe("to_arrow()", function () {
        test("serializes boolean arrays correctly", async function () {
            // prevent regression in boolean parsing
            let table = await perspective.table({
                bool: [true, false, true, false, true, false, false],
            });
            let view = await table.view();
            let arrow = await view.to_arrow();
            let json = await view.to_json();

            expect(json).toEqual([
                { bool: true },
                { bool: false },
                { bool: true },
                { bool: false },
                { bool: true },
                { bool: false },
                { bool: false },
            ]);

            let table2 = await perspective.table(arrow);
            let view2 = await table2.view();
            let json2 = await view2.to_json();
            expect(json2).toEqual(json);

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

        test("does not break when booleans are undefined", async function () {
            let table = await perspective.table([
                { int: 1, bool: true },
                { int: 2, bool: false },
                { int: 3, bool: true },
                { int: 4, bool: undefined },
            ]);
            let view = await table.view();
            let arrow = await view.to_arrow();
            let json = await view.to_json();

            expect(json).toEqual([
                { int: 1, bool: true },
                { int: 2, bool: false },
                { int: 3, bool: true },
                { int: 4, bool: null },
            ]);

            let table2 = await perspective.table(arrow);
            let view2 = await table2.view();
            let json2 = await view2.to_json();
            expect(json2).toEqual(json);

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

        test("arrow output respects start/end rows", async function () {
            let table = await perspective.table(int_float_string_data);
            let view = await table.view();
            let arrow = await view.to_arrow({
                start_row: 1,
                end_row: 2,
            });
            let json2 = await view.to_json();
            //expect(arrow.byteLength).toEqual(1010);

            let table2 = await perspective.table(arrow);
            let view2 = await table2.view();
            let json = await view2.to_json();
            expect(json).toEqual(json2.slice(1, 2));

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

        test("Transitive arrow output 0-sided", async function () {
            let table = await perspective.table(int_float_string_data);
            let view = await table.view();
            let arrow = await view.to_arrow();
            let json2 = await view.to_json();
            //expect(arrow.byteLength).toEqual(1010);

            let table2 = await perspective.table(arrow);
            let view2 = await table2.view();
            let json = await view2.to_json();
            expect(json).toEqual(json2);

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

        test("Transitive arrow output 0-sided hidden sort", async function () {
            let table = await perspective.table(int_float_string_data);
            let view = await table.view({
                columns: ["float"],
                sort: [["string", "desc"]],
            });
            let arrow = await view.to_arrow();
            let json2 = await view.to_json();
            //expect(arrow.byteLength).toEqual(1010);

            let table2 = await perspective.table(arrow);
            let view2 = await table2.view();
            let json = await view2.to_json();
            expect(json).toEqual(json2);

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

        test("Transitive arrow output 0-sided, with row range", async function () {
            let table = await perspective.table(int_float_string_data);
            let view = await table.view();
            let arrow = await view.to_arrow({ start_row: 1, end_row: 3 });
            let json2 = await view.to_json({ start_row: 1, end_row: 3 });
            // expect(arrow.byteLength).toEqual(908);

            let table2 = await perspective.table(arrow);
            let view2 = await table2.view();
            let json = await view2.to_json();
            expect(json).toEqual(json2);
            expect(json.length).toEqual(2);

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

        test("Transitive arrow output 0-sided, with col range", async function () {
            let table = await perspective.table(int_float_string_data);
            let view = await table.view();
            let arrow = await view.to_arrow({ start_col: 1, end_col: 3 });
            let json2 = await view.to_json({ start_col: 1, end_col: 3 });
            // expect(arrow.byteLength).toEqual(908);

            let table2 = await perspective.table(arrow);
            let view2 = await table2.view();
            let json = await view2.to_json();
            expect(json).toEqual(json2);
            expect(json.length).toEqual(4);

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

        test("Transitive arrow output 1-sided", async function () {
            let table = await perspective.table(int_float_string_data);
            let view = await table.view({ group_by: ["string"] });
            let json = await view.to_json();
            let arrow = await view.to_arrow();
            let table2 = await perspective.table(arrow);
            let view2 = await table2.view();
            let json2 = await view2.to_json();

            expect(json).toEqual(
                json2.map((x) => {
                    x.__ROW_PATH__ = [x["string (Group by 1)"]].filter(
                        (x) => x,
                    );
                    delete x["string (Group by 1)"];
                    return x;
                }),
            );

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

        test("Arrow output 1-sided mean", async function () {
            let table = await perspective.table({
                float: [1.25, 2.25, 3.25, 4.25],
                string: ["a", "a", "b", "b"],
            });
            let view = await table.view({
                group_by: ["string"],
                aggregates: { float: "mean" },
            });
            let arrow = await view.to_arrow();
            let table2 = await perspective.table(arrow);
            let view2 = await table2.view();
            let result = await view2.to_columns();

            expect(result).toEqual({
                "string (Group by 1)": [null, "a", "b"],
                float: [2.75, 1.75, 3.75],
                string: [4, 2, 2],
            });

            await view2.delete();
            await table2.delete();
            await view.delete();
            await table.delete();
        });

        test("Transitive arrow output 1-sided mean", async function () {
            let table = await perspective.table(int_float_string_data);
            let view = await table.view({
                group_by: ["string"],
                aggregates: { float: "mean" },
            });
            let json = await view.to_json();
            let arrow = await view.to_arrow();
            let table2 = await perspective.table(arrow);
            let view2 = await table2.view();
            let json2 = await view2.to_json();

            expect(json).toEqual(
                json2.map((x) => {
                    x.__ROW_PATH__ = [x["string (Group by 1)"]].filter(
                        (x) => x,
                    );
                    delete x["string (Group by 1)"];
                    return x;
                }),
            );

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

        test("Transitive arrow output 1-sided sorted mean", async function () {
            let table = await perspective.table(int_float_string_data);
            let view = await table.view({
                group_by: ["string"],
                aggregates: { float: "mean" },
                sort: [["string", "desc"]],
            });
            let json = await view.to_json();
            let arrow = await view.to_arrow();
            let table2 = await perspective.table(arrow);
            let view2 = await table2.view();
            let json2 = await view2.to_json();

            expect(json).toEqual(
                json2.map((x) => {
                    x.__ROW_PATH__ = [x["string (Group by 1)"]].filter(
                        (x) => x,
                    );
                    delete x["string (Group by 1)"];
                    return x;
                }),
            );

            view2.delete();
            table2.delete();
            view.delete();
            table.delete();
        });
        test("Transitive arrow output 1-sided hidden sort mean", async function () {
            let table = await perspective.table(int_float_string_data);
            let view = await table.view({
                group_by: ["string"],
                aggregates: { float: "mean" },
                columns: ["float", "int"],
                sort: [["string", "desc"]],
            });
            let json = await view.to_json();
            let arrow = await view.to_arrow();
            let table2 = await perspective.table(arrow);
            let view2 = await table2.view();
            let json2 = await view2.to_json();

            expect(json).toEqual(
                json2.map((x) => {
                    x.__ROW_PATH__ = [x["string (Group by 1)"]].filter(
                        (x) => x,
                    );
                    delete x["string (Group by 1)"];
                    return x;
                }),
            );

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

        test("Transitive arrow output 1-sided with row range", async function () {
            let table = await perspective.table(int_float_string_data);
            let view = await table.view({ group_by: ["string"] });
            let json = await view.to_json({ start_row: 1, end_row: 3 });
            let arrow = await view.to_arrow({ start_row: 1, end_row: 3 });
            let table2 = await perspective.table(arrow);
            let view2 = await table2.view();
            let json2 = await view2.to_json();

            expect(json).toEqual(
                json2.map((x) => {
                    x.__ROW_PATH__ = [x["string (Group by 1)"]].filter(
                        (x) => x,
                    );
                    delete x["string (Group by 1)"];
                    return x;
                }),
            );

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

        test("Transitive arrow output 1-sided with col range", async function () {
            let table = await perspective.table(int_float_string_data);
            let view = await table.view({ group_by: ["string"] });
            let json = await view.to_json({ start_col: 1, end_col: 3 });
            let arrow = await view.to_arrow({ start_col: 1, end_col: 3 });
            let table2 = await perspective.table(arrow);
            let view2 = await table2.view();
            let json2 = await view2.to_json();

            expect(json).toEqual(
                json2.map((x) => {
                    x.__ROW_PATH__ = [x["string (Group by 1)"]].filter(
                        (x) => x,
                    );
                    delete x["string (Group by 1)"];
                    return x;
                }),
            );

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

        test("Transitive arrow output 2-sided", async function () {
            let table = await perspective.table(int_float_string_data);
            let view = await table.view({
                group_by: ["string"],
                split_by: ["int"],
            });
            let json = await view.to_json();
            let arrow = await view.to_arrow();
            let table2 = await perspective.table(arrow);
            let view2 = await table2.view();
            let json2 = await view2.to_json();

            expect(json).toEqual(
                json2.map((x) => {
                    x.__ROW_PATH__ = [x["string (Group by 1)"]].filter(
                        (x) => x,
                    );
                    delete x["string (Group by 1)"];
                    return x;
                }),
            );

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

        test("Transitive arrow output 2-sided sorted", async function () {
            let table = await perspective.table(int_float_string_data);
            let view = await table.view({
                group_by: ["string"],
                split_by: ["int"],
                sort: [["int", "desc"]],
            });
            let json = await view.to_json();
            let arrow = await view.to_arrow();
            let table2 = await perspective.table(arrow);
            let view2 = await table2.view();
            let json2 = await view2.to_json();

            expect(json).toEqual(
                json2.map((x) => {
                    x.__ROW_PATH__ = [x["string (Group by 1)"]].filter(
                        (x) => x,
                    );
                    delete x["string (Group by 1)"];
                    return x;
                }),
            );

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

        test("Transitive arrow output 2-sided with row range", async function () {
            let table = await perspective.table(int_float_string_data);
            let view = await table.view({
                group_by: ["string"],
                split_by: ["int"],
            });
            let json = await view.to_json({ start_row: 1, end_row: 3 });
            let arrow = await view.to_arrow({ start_row: 1, end_row: 3 });
            let table2 = await perspective.table(arrow);
            let view2 = await table2.view();
            let json2 = await view2.to_json();

            expect(json).toEqual(
                json2.map((x) => {
                    x.__ROW_PATH__ = [x["string (Group by 1)"]].filter(
                        (x) => x,
                    );
                    delete x["string (Group by 1)"];
                    return x;
                }),
            );

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

        test("Transitive arrow output 2-sided with col range", async function () {
            let table = await perspective.table(int_float_string_data);
            let view = await table.view({
                group_by: ["string"],
                split_by: ["int"],
            });
            let json = await view.to_json({ start_col: 1, end_col: 3 });
            let arrow = await view.to_arrow({ start_col: 1, end_col: 3 });
            let table2 = await perspective.table(arrow);
            let view2 = await table2.view();
            let json2 = await view2.to_json();

            expect(json).toEqual(
                json2.map((x) => {
                    x.__ROW_PATH__ = [x["string (Group by 1)"]].filter(
                        (x) => x,
                    );
                    delete x["string (Group by 1)"];
                    return x;
                }),
            );

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

        test("Transitive arrow output 2-sided column only", async function () {
            let table = await perspective.table(int_float_string_data);
            let view = await table.view({ split_by: ["string"] });
            let json = await view.to_json();
            let arrow = await view.to_arrow();
            let table2 = await perspective.table(arrow);
            let view2 = await table2.view();
            let json2 = await view2.to_json();

            expect(json).toEqual(json2);

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

        test("Transitive arrow output 2-sided column only hidden sort", async function () {
            let table = await perspective.table(int_float_string_data);
            let view = await table.view({
                split_by: ["string"],
                columns: ["float"],
                sort: [["int", "desc"]],
            });
            let json = await view.to_json();
            let arrow = await view.to_arrow();
            let table2 = await perspective.table(arrow);
            let view2 = await table2.view();
            let json2 = await view2.to_json();

            expect(json).toEqual(json2);

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

        test("Transitive arrow output 2-sided column only sorted", async function () {
            let table = await perspective.table(int_float_string_data);
            let view = await table.view({
                split_by: ["string"],
                sort: [["int", "desc"]],
            });
            let json = await view.to_json();
            let arrow = await view.to_arrow();
            let table2 = await perspective.table(arrow);
            let view2 = await table2.view();
            let json2 = await view2.to_json();

            expect(json).toEqual(json2);

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

        test("Transitive arrow output 2-sided column only row range", async function () {
            let table = await perspective.table(int_float_string_data);
            let view = await table.view({ split_by: ["string"] });
            let json = await view.to_json({ start_row: 1, end_row: 3 });
            let arrow = await view.to_arrow({ start_row: 1, end_row: 3 });
            let table2 = await perspective.table(arrow);
            let view2 = await table2.view();
            let json2 = await view2.to_json();

            expect(json).toEqual(json2);

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

        test("Transitive arrow output 2-sided column only col range", async function () {
            let table = await perspective.table(int_float_string_data);
            let view = await table.view({ split_by: ["string"] });
            let json = await view.to_json({ start_col: 1, end_col: 3 });
            let arrow = await view.to_arrow({ start_col: 1, end_col: 3 });
            let table2 = await perspective.table(arrow);
            let view2 = await table2.view();
            let json2 = await view2.to_json();

            expect(json).toEqual(json2);

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

        test.describe("to_format with index", function () {
            test.describe("0-sided", function () {
                test("should return correct pkey for unindexed table", async function () {
                    let table = await perspective.table(int_float_string_data);
                    let view = await table.view();
                    let json = await view.to_json({
                        start_row: 0,
                        end_row: 1,
                        start_col: 1,
                        end_col: 2,
                        index: true,
                    });
                    expect(json).toEqual([{ float: 2.25, __INDEX__: [0] }]);
                    view.delete();
                    table.delete();
                });

                test("should return correct pkey for float indexed table", async function () {
                    let table = await perspective.table(int_float_string_data, {
                        index: "float",
                    });
                    let view = await table.view();
                    let json = await view.to_json({
                        start_row: 0,
                        end_row: 1,
                        start_col: 1,
                        end_col: 2,
                        index: true,
                    });
                    expect(json).toEqual([{ float: 2.25, __INDEX__: [2.25] }]);
                    view.delete();
                    table.delete();
                });

                test("should return correct pkey for string indexed table", async function () {
                    let table = await perspective.table(int_float_string_data, {
                        index: "string",
                    });
                    let view = await table.view();
                    let json = await view.to_json({
                        start_row: 0,
                        end_row: 1,
                        start_col: 1,
                        end_col: 2,
                        index: true,
                    });
                    expect(json).toEqual([{ float: 2.25, __INDEX__: ["a"] }]);
                    view.delete();
                    table.delete();
                });

                test("should return correct pkey for date indexed table", async function () {
                    // default data generates the same datetime for each row, thus pkeys get collapsed
                    const data = [
                        { int: 1, datetime: new Date() },
                        { int: 2, datetime: new Date() },
                    ];
                    data[1].datetime.setDate(data[1].datetime.getDate() + 1);
                    let table = await perspective.table(data, {
                        index: "datetime",
                    });
                    let view = await table.view();
                    let json = await view.to_json({
                        start_row: 1,
                        end_row: 2,
                        index: true,
                    });
                    expect(json).toEqual([
                        {
                            int: 2,
                            datetime: data[1].datetime.getTime(),
                            __INDEX__: [data[1].datetime.getTime()],
                        },
                    ]);
                    view.delete();
                    table.delete();
                });

                test("should return correct pkey for all rows + columns on an unindexed table", async function () {
                    let table = await perspective.table(int_float_string_data);
                    let view = await table.view();
                    let json = await view.to_json({
                        index: true,
                    });

                    for (let i = 0; i < json.length; i++) {
                        expect(json[i].__INDEX__).toEqual([i]);
                    }
                    view.delete();
                    table.delete();
                });

                test("should return correct pkey for all rows + columns on an indexed table", async function () {
                    let table = await perspective.table(int_float_string_data, {
                        index: "string",
                    });
                    let view = await table.view();
                    let json = await view.to_json({
                        index: true,
                    });

                    let pkeys = ["a", "b", "c", "d"];
                    for (let i = 0; i < json.length; i++) {
                        expect(json[i].__INDEX__).toEqual([pkeys[i]]);
                    }
                    view.delete();
                    table.delete();
                });
            });
        });

        test.describe("0-sided column subset", function () {
            test("should return correct pkey for unindexed table", async function () {
                let table = await perspective.table(int_float_string_data);
                let view = await table.view({
                    columns: ["int", "datetime"],
                });
                let json = await view.to_json({
                    start_row: 0,
                    end_row: 1,
                    start_col: 1,
                    end_col: 2,
                    index: true,
                });
                expect(json).toEqual([
                    {
                        datetime:
                            int_float_string_data[0]["datetime"].getTime(),
                        __INDEX__: [0],
                    },
                ]);
                view.delete();
                table.delete();
            });

            test("should return correct pkey for float indexed table", async function () {
                let table = await perspective.table(int_float_string_data, {
                    index: "float",
                });
                let view = await table.view({
                    columns: ["float", "int"],
                });
                let json = await view.to_json({
                    start_row: 0,
                    end_row: 1,
                    start_col: 1,
                    end_col: 2,
                    index: true,
                });
                expect(json).toEqual([{ int: 1, __INDEX__: [2.25] }]);
                view.delete();
                table.delete();
            });

            test("should return correct pkey for string indexed table", async function () {
                let table = await perspective.table(int_float_string_data, {
                    index: "string",
                });
                let view = await table.view({
                    columns: ["string", "datetime"],
                });
                let json = await view.to_json({
                    start_row: 0,
                    end_row: 1,
                    start_col: 1,
                    end_col: 2,
                    index: true,
                });
                expect(json).toEqual([
                    {
                        datetime:
                            int_float_string_data[0]["datetime"].getTime(),
                        __INDEX__: ["a"],
                    },
                ]);
                view.delete();
                table.delete();
            });

            test("should return correct pkey for date indexed table", async function () {
                // default data generates the same datetime for each row, thus pkeys get collapsed
                const data = [
                    { int: 1, datetime: new Date() },
                    { int: 2, datetime: new Date() },
                ];
                data[1].datetime.setDate(data[1].datetime.getDate() + 1);
                let table = await perspective.table(data, {
                    index: "datetime",
                });
                let view = await table.view({
                    columns: ["int"],
                });
                let json = await view.to_json({
                    start_row: 1,
                    end_row: 2,
                    index: true,
                });
                expect(json).toEqual([
                    { int: 2, __INDEX__: [data[1].datetime.getTime()] },
                ]);
                view.delete();
                table.delete();
            });

            test("should return correct pkey for all rows + columns on an unindexed table", async function () {
                let table = await perspective.table(int_float_string_data);
                let view = await table.view({
                    columns: ["int"],
                });
                let json = await view.to_json({
                    index: true,
                });

                for (let i = 0; i < json.length; i++) {
                    expect(json[i].__INDEX__).toEqual([i]);
                }
                view.delete();
                table.delete();
            });

            test("should return correct pkey for all rows + columns on an indexed table", async function () {
                let table = await perspective.table(int_float_string_data, {
                    index: "string",
                });
                let view = await table.view();
                let json = await view.to_json({
                    index: true,
                });

                let pkeys = ["a", "b", "c", "d"];
                for (let i = 0; i < json.length; i++) {
                    expect(json[i].__INDEX__).toEqual([pkeys[i]]);
                }
                view.delete();
                table.delete();
            });
        });

        test.describe("0-sided column subset invalid bounds", function () {
            test("should return correct pkey for unindexed table, invalid column", async function () {
                let table = await perspective.table(int_float_string_data);
                let view = await table.view({
                    columns: ["int"],
                });
                let json = await view.to_json({
                    start_row: 0,
                    end_row: 1,
                    start_col: 1,
                    end_col: 2,
                    index: true,
                });
                expect(json).toEqual([{ __INDEX__: [0] }]);
                view.delete();
                table.delete();
            });

            test("should not return pkey for unindexed table, invalid row", async function () {
                let table = await perspective.table(int_float_string_data);
                let view = await table.view({
                    columns: ["int"],
                });
                let json = await view.to_json({
                    start_row: 10,
                    end_row: 15,
                    index: true,
                });
                expect(json).toEqual([]);
                view.delete();
                table.delete();
            });

            test("should return correct pkey for float indexed table, invalid column", async function () {
                let table = await perspective.table(int_float_string_data, {
                    index: "float",
                });
                let view = await table.view({
                    columns: ["float"],
                });
                let json = await view.to_json({
                    start_row: 0,
                    end_row: 1,
                    start_col: 1,
                    end_col: 2,
                    index: true,
                });
                expect(json).toEqual([{ __INDEX__: [2.25] }]);
                view.delete();
                table.delete();
            });

            test("should not return pkey for float indexed table, invalid row", async function () {
                let table = await perspective.table(int_float_string_data, {
                    index: "float",
                });
                let view = await table.view({
                    columns: ["float"],
                });
                let json = await view.to_json({
                    start_row: 10,
                    end_row: 15,
                    start_col: 1,
                    end_col: 2,
                    index: true,
                });
                expect(json).toEqual([]);
                view.delete();
                table.delete();
            });

            test("should return correct pkey for string indexed table, invalid column", async function () {
                let table = await perspective.table(int_float_string_data, {
                    index: "string",
                });
                let view = await table.view({
                    columns: ["string"],
                });
                let json = await view.to_json({
                    start_row: 0,
                    end_row: 1,
                    start_col: 1,
                    end_col: 2,
                    index: true,
                });
                expect(json).toEqual([{ __INDEX__: ["a"] }]);
                view.delete();
                table.delete();
            });

            test("should not return pkey for string indexed table, invalid row", async function () {
                let table = await perspective.table(int_float_string_data, {
                    index: "string",
                });
                let view = await table.view({
                    columns: ["string"],
                });
                let json = await view.to_json({
                    start_row: 10,
                    end_row: 11,
                    start_col: 1,
                    end_col: 2,
                    index: true,
                });
                expect(json).toEqual([]);
                view.delete();
                table.delete();
            });

            test("should return correct pkey for date indexed table, invalid column", async function () {
                // default data generates the same datetime for each row, thus pkeys get collapsed
                const data = [
                    { int: 1, datetime: new Date() },
                    { int: 2, datetime: new Date() },
                ];
                data[1].datetime.setDate(data[1].datetime.getDate() + 1);
                let table = await perspective.table(data, {
                    index: "datetime",
                });
                let view = await table.view({
                    columns: ["int"],
                });
                let json = await view.to_json({
                    start_col: 2,
                    index: true,
                });
                expect(json).toEqual([
                    {
                        __INDEX__: [data[0].datetime.getTime()],
                    },
                    {
                        __INDEX__: [data[1].datetime.getTime()],
                    },
                ]);
                view.delete();
                table.delete();
            });

            test("should not return pkey for date indexed table, invalid row", async function () {
                // default data generates the same datetime for each row, thus pkeys get collapsed
                const data = [
                    { int: 1, datetime: new Date() },
                    { int: 2, datetime: new Date() },
                ];
                data[1].datetime.setDate(data[1].datetime.getDate() + 1);
                let table = await perspective.table(data, {
                    index: "datetime",
                });
                let view = await table.view({
                    columns: ["int"],
                });
                let json = await view.to_json({
                    start_row: 11,
                    start_row: 12,
                    index: true,
                });
                expect(json).toEqual([]);
                view.delete();
                table.delete();
            });
        });

        test.describe("0-sided sorted", function () {
            test("should return correct pkey for unindexed table", async function () {
                let table = await perspective.table(int_float_string_data);
                let view = await table.view({
                    sort: [["float", "desc"]],
                });
                let json = await view.to_json({
                    start_row: 0,
                    end_row: 1,
                    start_col: 1,
                    end_col: 2,
                    index: true,
                });
                expect(json).toEqual([{ float: 5.25, __INDEX__: [3] }]);
                view.delete();
                table.delete();
            });

            test("should return correct pkey for float indexed table", async function () {
                let table = await perspective.table(int_float_string_data, {
                    index: "float",
                });
                let view = await table.view({
                    sort: [["float", "desc"]],
                });
                let json = await view.to_json({
                    start_row: 0,
                    end_row: 1,
                    start_col: 1,
                    end_col: 2,
                    index: true,
                });
                expect(json).toEqual([{ float: 5.25, __INDEX__: [5.25] }]);
                view.delete();
                table.delete();
            });

            test("should return correct pkey for string indexed table", async function () {
                let table = await perspective.table(int_float_string_data, {
                    index: "string",
                });
                let view = await table.view({
                    sort: [["float", "desc"]],
                });
                let json = await view.to_json({
                    start_row: 0,
                    end_row: 1,
                    start_col: 1,
                    end_col: 2,
                    index: true,
                });
                expect(json).toEqual([{ float: 5.25, __INDEX__: ["d"] }]);
                view.delete();
                table.delete();
            });

            test("should return correct pkey for date indexed table", async function () {
                // default data generates the same datetime for each row,
                // thus pkeys get collapsed
                const data = [
                    { int: 200, datetime: new Date() },
                    { int: 100, datetime: new Date() },
                ];
                data[1].datetime.setDate(data[1].datetime.getDate() + 1);
                let table = await perspective.table(data, {
                    index: "datetime",
                });
                let view = await table.view({
                    sort: [["int", "desc"]],
                });
                let json = await view.to_json({
                    index: true,
                });
                expect(json).toEqual([
                    {
                        int: 200,
                        datetime: data[0].datetime.getTime(),
                        __INDEX__: [data[0].datetime.getTime()],
                    },
                    {
                        int: 100,
                        datetime: data[1].datetime.getTime(),
                        __INDEX__: [data[1].datetime.getTime()],
                    },
                ]);
                view.delete();
                table.delete();
            });

            test("should return correct pkey for all rows + columns on an unindexed table", async function () {
                let table = await perspective.table(int_float_string_data);
                let view = await table.view({
                    sort: [["float", "asc"]],
                });
                let json = await view.to_json({
                    index: true,
                });

                for (let i = 0; i < json.length; i++) {
                    expect(json[i].__INDEX__).toEqual([i]);
                }
                view.delete();
                table.delete();
            });

            test("should return correct pkey for all rows + columns on an indexed table", async function () {
                let table = await perspective.table(int_float_string_data, {
                    index: "string",
                });
                let view = await table.view({
                    sort: [["float", "desc"]],
                });
                let json = await view.to_json({
                    index: true,
                });

                let pkeys = ["d", "c", "b", "a"];
                for (let i = 0; i < json.length; i++) {
                    expect(json[i].__INDEX__).toEqual([pkeys[i]]);
                }
                view.delete();
                table.delete();
            });
        });

        test.describe("1-sided", function () {
            test("should generate pkeys of aggregated rows for 1-sided", async function () {
                let table = await perspective.table(int_float_string_data);
                let view = await table.view({
                    group_by: ["int"],
                });
                let json = await view.to_json({
                    index: true,
                });

                // total row contains all pkeys for underlying rows; each aggregated row should have pkeys for the rows that belong to it
                expect(json).toEqual(pivoted_output);
                view.delete();
                table.delete();
            });
        });

        test.describe("2-sided", function () {
            test.skip("should generate pkey for 2-sided", async function () {
                // 2-sided implicit pkeys do not work
                let table = await perspective.table(int_float_string_data);
                let view = await table.view({
                    group_by: ["int"],
                    split_by: ["float"],
                });
                let json = await view.to_json({
                    index: true,
                });

                expect(json[0]["__INDEX__"]).toEqual([0, 1]); // total row should have indices for every row inside it

                let idx = 0;
                for (let item of json.slice(1)) {
                    expect(item["__INDEX__"]).toEqual([idx]);
                    idx++;
                }
            });
        });
    });
})(perspective);
