Skip to content

Commit

Permalink
fix: Fix isPartial not working with interfaces
Browse files Browse the repository at this point in the history
  • Loading branch information
NiGhTTraX committed May 22, 2024
1 parent 2a4f703 commit 169b336
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 31 deletions.
16 changes: 8 additions & 8 deletions src/matchers/is-partial.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type DeepPartial<T> = T extends ObjectType
const looksLikeObject = (value: unknown): value is ObjectType =>
isPlainObject(value);

const getExpectedObjectDiff = (actual: unknown, expected: ObjectType): object =>
const getExpectedObjectDiff = (actual: unknown, expected: unknown): object =>
Object.fromEntries(
getKeys(expected).map((key) => {
const expectedValue = getKey(expected, key);
Expand All @@ -33,10 +33,7 @@ const getExpectedObjectDiff = (actual: unknown, expected: ObjectType): object =>
})
);

const getActualObjectDiff = (
actual: unknown,
expected: ObjectType
): unknown => {
const getActualObjectDiff = (actual: unknown, expected: unknown): unknown => {
const actualKeys = getKeys(actual);
const expectedKeys = new Set(getKeys(expected));
const commonKeys = actualKeys.filter((key) => expectedKeys.has(key));
Expand Down Expand Up @@ -77,7 +74,7 @@ const getKey = (value: unknown, key: Property): unknown =>
// @ts-expect-error because we're fine with a runtime undefined value
value?.[key];

const isMatch = (actual: unknown, expected: ObjectType): boolean => {
const isMatch = (actual: unknown, expected: unknown): boolean => {
const actualKeys = getKeys(actual);
const expectedKeys = getKeys(expected);

Expand All @@ -101,7 +98,7 @@ const isMatch = (actual: unknown, expected: ObjectType): boolean => {
});
};

const deepPrintObject = (value: ObjectType) =>
const deepPrintObject = (value: unknown) =>
cloneDeepWith(value, (value) => {
if (isMatcher(value)) {
return value.toString();
Expand Down Expand Up @@ -129,7 +126,10 @@ const deepPrintObject = (value: ObjectType) =>
* @example
* It.isPartial({ foo: It.isString() })
*/
export const isPartial = <T extends ObjectType, K extends DeepPartial<T>>(
// T is not constrained to ObjectType because of
// https://github.com/microsoft/TypeScript/issues/57810,
// but K is to avoid inferring non-object partials
export const isPartial = <T, K extends DeepPartial<T> & ObjectType>(
partial: K
): TypeMatcher<T> =>
matches((actual) => isMatch(actual, partial), {
Expand Down
79 changes: 56 additions & 23 deletions tests/types.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,24 @@ it('type safety', () => {
when(() => fnany()).thenResolve(23);
}

function matcherSafety() {
function partialSafety() {
const number = (x: number) => x;
number(It.isAny());
// @ts-expect-error wrong matcher type
number(It.isString());
// @ts-expect-error wrong matcher type
number(It.isPlainObject());
// @ts-expect-error wrong matcher type
number(It.isArray());
number(
It.isPartial(
// @ts-expect-error non-object can't be partial-ed
{ toString: () => 'bar' }
)
);
number(
It.isPartial(
// @ts-expect-error non-object
42
)
);

const numberArray = (x: number[]) => x;
// @ts-expect-error array is not an object
numberArray(It.isPartial({ length: 2 }));

const nestedObject = (x: { foo: { bar: number; 42: string } }) => x;
nestedObject(It.isPlainObject());
Expand All @@ -68,24 +77,28 @@ it('type safety', () => {
// @ts-expect-error wrong nested property type
It.isPartial({ foo: { bar: 'boo' } })
);
// @ts-expect-error because TS can't infer the proper type
// See https://github.com/microsoft/TypeScript/issues/55164.
nestedObject(It.isPartial({ foo: It.isPartial({ bar: 1 }) }));

const numberArray = (x: number[]) => x;
numberArray(It.isArray());
numberArray(It.isArray([1, 2, 3]));
numberArray(It.isArray([It.isNumber()]));
// @ts-expect-error wrong type of array
numberArray(It.isArray(['a']));
// @ts-expect-error wrong nested matcher type
numberArray(It.isArray([It.isString()]));
nestedObject(
It.isPartial({
// @ts-expect-error because TS can't infer the proper type
// See https://github.com/microsoft/TypeScript/issues/55164.
foo: It.isPartial({ bar: 1 }),
})
);

interface InterfaceType {
foo: string;
}
const withInterface = (x: InterfaceType) => x;
withInterface(It.isPartial({ foo: 'bar' }));

const object = (x: { foo: number }) => x;
object(It.isPartial({ foo: It.isNumber() }));
object(
// @ts-expect-error wrong nested matcher type
It.isPartial({ foo: It.isString() })
It.isPartial({
// @ts-expect-error wrong nested matcher type
foo: It.isString(),
})
);

const objectWithArrays = (x: { foo: { bar: number[] } }) => x;
Expand Down Expand Up @@ -117,7 +130,7 @@ it('type safety', () => {
map: Map<unknown, unknown>;
set: Set<unknown>;
arr: Array<unknown>;
}) => {};
}) => data;
objectLikeValues({
// @ts-expect-error Maps are not objects
map: It.isPlainObject(),
Expand All @@ -134,9 +147,28 @@ it('type safety', () => {
// @ts-expect-error Arrays are not objects
arr: It.isPartial({}),
});
}

const string = (x: string) => string;
function matcherSafety() {
const number = (x: number) => x;
number(It.isAny());
// @ts-expect-error wrong matcher type
number(It.isString());
// @ts-expect-error wrong matcher type
number(It.isPlainObject());
// @ts-expect-error wrong matcher type
number(It.isArray());

const numberArray = (x: number[]) => x;
numberArray(It.isArray());
numberArray(It.isArray([1, 2, 3]));
numberArray(It.isArray([It.isNumber()]));
// @ts-expect-error wrong type of array
numberArray(It.isArray(['a']));
// @ts-expect-error wrong nested matcher type
numberArray(It.isArray([It.isString()]));

const string = (x: string) => x;
const startsWith = (expected: string) =>
It.matches<string>((actual) => actual.startsWith(expected));
string(startsWith('foo'));
Expand All @@ -157,6 +189,7 @@ it('type safety', () => {
// @ts-expect-error because the value can be undefined.
number(captureMatcher.value);
// @ts-expect-error number is not string
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
string(captureMatcher.value!);
}
});

0 comments on commit 169b336

Please sign in to comment.