import { SelectorFactory } from "../selector_factory";

interface IFoo {
    bar: string;
    baz: number;
}

class Foo implements IFoo {
    public bar: string;
    public baz: number;
    constructor(str: string, num: number) {
        this.bar = str;
        this.baz = num;
    }
}

const expected_DefaultPredicate: Foo = new Foo("abc", 1);
const expected_OverwritePredicate1: Foo = new Foo("def", 2);
const expected_OverwritePredicate2: Foo = new Foo("ghi", 3);

const getCollection = (): Foo[] => [
    new Foo("abc", 1),
    new Foo("def", 2),
    new Foo("ghi", 3)
];

const collection: Foo[] = getCollection();
const prop: keyof Foo = "bar";
const expectedValue = "abc";
const overwriteDefaultPredicate: interfaces.IPredicate<Foo> = (foo: Foo) => foo.bar !== expectedValue;

let SelectorFactoryInstance: interfaces.ISelectorFactory<Foo> = SelectorFactory;
let SelectorInstance: interfaces.ISelector<Foo> = SelectorFactoryInstance(collection);
let SelectedRecordsInstance: interfaces.ISelectedRecords<Foo> = SelectorInstance(expectedValue, prop);
let actual: Foo[] = SelectedRecordsInstance();

describe("SelectorFactory", () => {

    test("can be called with a collection", () => {
        expect(SelectorFactoryInstance(collection)).not.toThrow();
    });

    test("returns a Selector function", () => {
        expect(SelectorInstance).toBeDefined();
        expect(SelectorInstance).toBeInstanceOf(Function);
    });
});

describe("Selector", () => {

    test("Selector can be called with a value and a key, returns a function", () => {
        const sut = SelectorInstance(expectedValue, prop);
        expect(SelectorInstance(expectedValue, prop)).not.toThrow();
        expect(sut).toBeInstanceOf(Function);
    });

    test("Selector call can overwrite default Predicate", () => {
        const sut = SelectorInstance(expectedValue, prop, overwriteDefaultPredicate);

        expect(SelectorInstance(expectedValue, prop)).not.toThrow();
        expect(sut).toBeInstanceOf(Function);
        expect(sut()).toBeDefined();
        expect(sut().length).toEqual(2);
        expect(sut()[0]).toMatchObject(expected_OverwritePredicate1);
        expect(sut()[1]).toMatchObject(expected_OverwritePredicate2);
    });

    test("Selector does not mutate wrapping collection", () => {
        const propA: keyof Foo = "bar";
        const propB: keyof Foo = "baz";
        const A = SelectorFactoryInstance(collection);
        const B = SelectorFactoryInstance(collection);

        const selectorA = A("abc", propA);
        const selectorB = B(123, propB);

        const selectedB = selectorB();
        expect(selectedB.length).toEqual(0);

        const selectedA = selectorA();
        expect(selectedA.length).toEqual(1);
        expect(selectedA[0]).toMatchObject(new Foo("abc", 1));
    });

});

describe("SelectedRecords", () => {

    test("SelectedRecords contains the collection filtered by the property", () => {
        expect(actual.length).toEqual(1);
        expect(actual[0]).toMatchObject(expected_DefaultPredicate);
    });
});