Skip to content

Issue testing type guards with generics #8

@Goldziher

Description

@Goldziher

Hi there,

First of - thanks for this library. Its very helpful.

I am having issues testing type guards that use generics. For example, I have a guard that looks like this

const createGuard = <T>(
    validator: (value: unknown) => boolean,
    label: string,
) => {
    return (input: unknown, throwError = false): input is T => {
        if (!validator(input)) {
            if (throwError) {
                throw new TypeError(`expected input to be ${label}`);
            }
            return false;
        }
        return true;
    };
};

export function isArray<T>(
    input: unknown,
    valueGuard: (value: unknown) => boolean,
    throwError = false,
): input is T[] {
    return createGuard<T[]>(
        (value) => Array.isArray(value) && value.every(valueGuard),
        'Array',
    )(input, throwError);
}

I then test it like so:

describe('isArray', () => {
    it('returns true for positively tested array values', () => {
        expect(isArray<string>(stringArray, isString)).toBeTruthy();
        expect(isArray<number>(numberArray, isNumber)).toBeTruthy();
        expect(isArray<symbol>(symbolArray, isSymbol)).toBeTruthy();
        expect(isArray<object>(recordArray, isObject)).toBeTruthy();
        expect(
            isArray<string | number>(
                [...stringArray, ...numberArray],
                isUnion<string | number>(isString, isNumber),
            ),
        ).toBeTruthy();
    });
    it('returns false for negatively tested array values', () => {
        expect(isArray<string>(stringArray, isNumber)).toBeFalsy();
        expect(isArray<number>(numberArray, isString)).toBeFalsy();
        expect(isArray<symbol>(symbolArray, isObject)).toBeFalsy();
        expect(isArray<object>(recordArray, isSymbol)).toBeFalsy();
        expect(
            isArray<string | number>(
                [...symbolArray, ...recordArray],
                isUnion<string | number>(isString, isNumber),
            ),
        ).toBeFalsy();
    });
    it('returns false for non-array values', () => {
        expect(isArray<string>('', isString)).toBeFalsy();
        expect(isArray<string>(null, isString)).toBeFalsy();
        expect(isArray<string>(123, isString)).toBeFalsy();
        expect(isArray<string>(Symbol(), isString)).toBeFalsy();
        expect(isArray<string>({}, isString)).toBeFalsy();
    });
    it('throws error when throwError = true', () => {
        expect(() => isArray<string>('', isString, true)).toThrow();
        expect(() => isArray<string>(null, isString, true)).toThrow();
        expect(() => isArray<string>(123, isString, true)).toThrow();
    });
    it('guards type correctly', () => {
        const unknownArray: unknown = [...stringArray];
        if (isArray<string>(unknownArray, isString)) {
            expectTypeOf(unknownArray).toMatchTypeOf(stringArray);
        }
    });
});

Note the last it() where I use the expect-type library. This is the only way i figured that actually works for testing generics with this library. Is there another solution or maybe a planned fix?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions