Skip to content

Commit

Permalink
fix(expect): improve generic expected types, restrict pre modifier in…
Browse files Browse the repository at this point in the history
…terface
  • Loading branch information
TomokiMiyauci committed Dec 15, 2021
1 parent d4009d3 commit c8ebebe
Show file tree
Hide file tree
Showing 12 changed files with 91 additions and 127 deletions.
2 changes: 1 addition & 1 deletion _e2e/fn_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ test("should define rejected value as default", async () => {

test("should define rejected value as only once", async () => {
const mockObject = fn().onceRejectedValue(Error("test"));
await expect(mockObject()).rejects.toEqual(Error("test"));
await expect(mockObject() as Promise<never>).rejects.toEqual(Error("test"));
expect(mockObject()).not.toBeDefined();
});

Expand Down
11 changes: 11 additions & 0 deletions _types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,23 @@ type IsTuple<T extends readonly unknown[]> = number extends T["length"] ? false

type Primitive = string | number | bigint | symbol | boolean | null | undefined;

type PickOf<T, U> = {
[k in keyof T as (T[k] extends U ? k : never)]: T[k];
};

type OverwriteOf<T, U> = {
[k in keyof T]: U;
};

export type {
AnyFn,
FirstParameter,
IsArityX,
IsPromise,
IsTuple,
OverwriteOf,
PickByFirstParameter,
PickOf,
Primitive,
Resolve,
ReturnTypePromisifyMap,
Expand Down
88 changes: 40 additions & 48 deletions expect/expect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,63 +15,24 @@ import { stringifyEquality } from "../helper/equal.ts";

import type {
AnyFn,
IsPromise,
PickByFirstParameter,
FirstParameter,
OverwriteOf,
PickOf,
Resolve,
ReturnTypePromisifyMap,
ShiftFnArg,
} from "../_types.ts";
import type { Matcher, MatchResult } from "../matcher/types.ts";
import type {
PickModifierByType,
ExtractOf,
PostModifierFn,
PreModifier,
PreModifierFn,
} from "../modifier/types.ts";
import type { ModifierMap } from "../modifier/types.ts";
import type { MatcherMap } from "../matcher/types.ts";
import type { StringifyResultArgs } from "../helper/format.ts";

type ReservedWord = "resolves" | "rejects";

type PickOf<T, U> = {
[k in keyof T as (T[k] extends U ? k : never)]: T[k];
};

type Expected<
T extends MatcherMap,
U,
Pre extends PickModifierByType<ModifierMap, "pre">,
Post extends PickModifierByType<ModifierMap, "post">,
IsPromise extends boolean,
> =
& {
[k in keyof Pre]: IsPromise extends true ? k extends ReservedWord ?
& ReturnTypePromisifyMap<
Omit<ShiftFnArgMap<PickByFirstParameter<T, Resolve<U>>>, k>
>
& {
[k in keyof Post]: IsPromise extends true
? ReturnTypePromisifyMap<ShiftFnArgMap<T>>
: ShiftFnArgMap<T>;
}
: ReturnTypePromisifyMap<
Omit<ShiftFnArgMap<PickByFirstParameter<T, U>>, k>
>
:
& Omit<ShiftFnArgMap<T>, k>
& {
[k in keyof Post]: IsPromise extends true
? ReturnTypePromisifyMap<ShiftFnArgMap<T>>
: ShiftFnArgMap<T>;
};
}
& {
[k in keyof Post]: IsPromise extends true
? ReturnTypePromisifyMap<Omit<ShiftFnArgMap<T>, k>>
: Omit<ShiftFnArgMap<T>, k>;
}
& MatcherFilter<U, T>;

type ShiftFnArgMap<T extends Record<PropertyKey, AnyFn>> = {
[k in keyof T]: ShiftFnArg<T[k]>;
};
Expand All @@ -80,6 +41,38 @@ type MatcherFilter<T, U extends Record<string, Matcher>> = ShiftFnArgMap<
PickOf<U, (actual: T, ...args: any[]) => unknown>
>;

type FilterPreModifier<
A,
T extends ExtractOf<ModifierMap, { type: "pre" }>,
> = {
[
k
in keyof T as (T[k] extends PreModifier
? A extends FirstParameter<T[k]["fn"]>["actual"] ? k : never
: never)
]: T[k];
};

type Expected<
Actual,
Matcher extends MatcherMap,
Pre extends ExtractOf<ModifierMap, { type: "pre" }>,
Post extends ExtractOf<ModifierMap, { type: "post" }>,
> =
& OverwriteOf<
FilterPreModifier<Actual, Pre>,
& ReturnTypePromisifyMap<MatcherFilter<Resolve<Actual>, Matcher>>
& OverwriteOf<
Post,
ReturnTypePromisifyMap<MatcherFilter<Actual, Matcher>>
>
>
& OverwriteOf<
Post,
MatcherFilter<Actual, Matcher>
>
& MatcherFilter<Actual, Matcher>;

/** define custom expect */
function defineExpect<
MatcherObject extends MatcherMap,
Expand All @@ -93,11 +86,10 @@ function defineExpect<
return <T = unknown>(
actual: T,
): Expected<
MatcherObject,
T,
PickModifierByType<Modifier, "pre">,
PickModifierByType<Modifier, "post">,
IsPromise<T>
MatcherObject,
ExtractOf<Modifier, { type: "pre" }>,
ExtractOf<Modifier, { type: "post" }>
> => {
let pre: [string | symbol, PreModifierFn] | undefined = undefined;
let post: [string | symbol, PostModifierFn] | undefined = undefined;
Expand Down
4 changes: 2 additions & 2 deletions mock/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ import { expect, fn, test } from "https://deno.land/x/unitest@$VERSION/mod.ts";

test("should define rejected value as default", () => {
const mockObject = fn().defaultRejectedValue(Error("error"));
expect(mockObject()).rejects.toEqual(Error("error"));
expect(mockObject() as Promise<never>).rejects.toEqual(Error("error"));
});
```

Expand All @@ -224,7 +224,7 @@ import { expect, fn, test } from "https://deno.land/x/unitest@$VERSION/mod.ts";

test("should define rejected value as only once", async () => {
const mockObject = fn().onceRejectedValue(Error("test"));
await expect(mockObject()).rejects.toEqual(Error("test"));
await expect(mockObject() as Promise<never>).rejects.toEqual(Error("test"));
expect(mockObject()).not.toBeDefined();
});
```
Expand Down
4 changes: 2 additions & 2 deletions mock/fn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ interface MockObject<A extends readonly unknown[] = any[], R = unknown> {
*
* test("should define rejected value as default", () => {
* const mockObject = fn().defaultRejectedValue(Error("error"));
* expect(mockObject()).rejects.toEqual(Error("error"));
* expect(mockObject() as Promise<never>).rejects.toEqual(Error("error"));
* });
* ```
*/
Expand Down Expand Up @@ -118,7 +118,7 @@ interface MockObject<A extends readonly unknown[] = any[], R = unknown> {
*
* test("should define rejected value as only once", async () => {
* const mockObject = fn().onceRejectedValue(Error("test"));
* await expect(mockObject()).rejects.toEqual(Error("test"));
* await expect(mockObject() as Promise<never>).rejects.toEqual(Error("test"));
* expect(mockObject()).not.toBeDefined();
* });
* ```
Expand Down
15 changes: 9 additions & 6 deletions mock/fn_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,17 +201,20 @@ Deno.test("defaultRejectedValue", async () => {
Deno.test("onceRejectedValue", async () => {
assertExists(fn().onceRejectedValue);

await expect(fn().onceRejectedValue(Error("test"))()).rejects.toBeInstanceOf(
Error,
);
await expect(fn().onceRejectedValue(Error("test"))() as Promise<never>)
.rejects.toBeInstanceOf(
Error,
);

const mockObject = fn().onceRejectedValue(Error("test")).onceRejectedValue(
Error("test2"),
);

await expect(mockObject()).rejects.toEqual(Error("test"));
await expect(mockObject()).rejects.toEqual(Error("test2"));
await expect(mockObject()).rejects.not.toBeDefined();
await expect(mockObject() as Promise<never>).rejects.toEqual(Error("test"));
await expect(mockObject() as Promise<never>).rejects.toEqual(
Error("test2"),
);
await expect(mockObject() as Promise<never>).rejects.not.toBeDefined();
});

Deno.test("mockClear", () => {
Expand Down
4 changes: 2 additions & 2 deletions modifier/not.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ function predict(
* });
* ```
*/
const not: PostModifier = {
const not = {
type: "post",
fn: predict,
};
} as PostModifier;

export { not, predict };
18 changes: 5 additions & 13 deletions modifier/rejects.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,14 @@
// Copyright 2021-Present the Unitest authors. All rights reserved. MIT license.
// This module is browser compatible.

import type {
PreModifier,
PreModifierContext,
PreModifierResult,
} from "./types.ts";
import { AssertionError, isPromise } from "../deps.ts";
import type { PreModifierContext, PreModifierResult } from "./types.ts";
import { AssertionError } from "../deps.ts";
import { stringify } from "../helper/format.ts";

/** predict for `rejects` */
async function predict(
{ actual }: PreModifierContext,
{ actual }: PreModifierContext<Promise<unknown>>,
): Promise<PreModifierResult> | never {
if (!isPromise(actual)) {
throw new AssertionError("expected value must be a Promise");
}

const resolvedActual = await actual.then((v) => [false, v] as const).catch((
v,
) => [true, v as unknown] as const);
Expand All @@ -41,8 +33,8 @@ async function predict(
* });
* ```
*/
const rejects: PreModifier = {
type: "pre",
const rejects = {
type: "pre" as const,
fn: predict,
};

Expand Down
11 changes: 0 additions & 11 deletions modifier/rejects_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,6 @@ Deno.test({
AssertionError,
`Promise did not reject. resolved to 1`,
);

assertRejects(
() =>
predict({
actual: "test",
matcherArgs: [],
matcher: {} as any,
}),
AssertionError,
`expected value must be a Promise`,
);
},
});

Expand Down
17 changes: 4 additions & 13 deletions modifier/resolves.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
// Copyright 2021-Present the Unitest authors. All rights reserved. MIT license.
// This module is browser compatible.
import type {
PreModifier,
PreModifierContext,
PreModifierResult,
} from "./types.ts";
import { AssertionError, isPromise } from "../deps.ts";
import type { PreModifierContext, PreModifierResult } from "./types.ts";

/** predict for `resolves` */
async function predict(
{ actual }: PreModifierContext,
{ actual }: PreModifierContext<Promise<unknown>>,
): Promise<PreModifierResult> {
if (!isPromise(actual)) {
throw new AssertionError("expected value must be a Promise");
}

const resolvedActual = await actual;

return {
Expand All @@ -31,8 +22,8 @@ async function predict(
* });
* ```
*/
const resolves: PreModifier = {
type: "pre",
const resolves = {
type: "pre" as const,
fn: predict,
};

Expand Down
15 changes: 1 addition & 14 deletions modifier/resolves_test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright 2021-Present the Unitest authors. All rights reserved. MIT license.
import { predict, resolves } from "./resolves.ts";
import { assertEquals, assertRejects } from "../dev_deps.ts";
import { AssertionError } from "../deps.ts";
import { assertEquals } from "../dev_deps.ts";

Deno.test({
name: "predict",
Expand All @@ -14,18 +13,6 @@ Deno.test({
}),
{ actual: "test" },
);

assertRejects(
() => {
return predict({
actual: "test",
matcherArgs: [],
matcher: {} as any,
});
},
AssertionError,
"expected value must be a Promise",
);
},
});

Expand Down
Loading

0 comments on commit c8ebebe

Please sign in to comment.