mirror of
https://github.com/wassname/talk.git
synced 2026-08-11 11:27:10 +08:00
[CORL-331] Better tests with types (#2270)
* feat: suspending, banning, now propogation * feat: new mutation api with hooks support * feat: better types in tests and refactor * fix: lint
This commit is contained in:
@@ -114,8 +114,8 @@ function valueToState(
|
||||
// Start from the first unit,
|
||||
// keep first unit if value is set to 0,
|
||||
// otherwise use better matching unit if the value is fully dividable by the unit.
|
||||
unit = units.reduce(
|
||||
(x, cur) => (parsed % cur === 0 && parsed !== 0 ? cur : x)
|
||||
unit = units.reduce((x, cur) =>
|
||||
parsed % cur === 0 && parsed !== 0 ? cur : x
|
||||
);
|
||||
}
|
||||
// Compute new value relative to the selected unit.
|
||||
|
||||
@@ -6,9 +6,9 @@ import { PasswordField as PasswordFieldUI } from "talk-ui/components";
|
||||
|
||||
export interface Props
|
||||
extends Omit<
|
||||
PropTypesOf<typeof PasswordFieldUI>,
|
||||
"showPasswordTitle" | "hidePasswordTitle"
|
||||
> {}
|
||||
PropTypesOf<typeof PasswordFieldUI>,
|
||||
"showPasswordTitle" | "hidePasswordTitle"
|
||||
> {}
|
||||
|
||||
const PasswordField: StatelessComponent<Props> = props => (
|
||||
<Localized
|
||||
|
||||
@@ -26,10 +26,10 @@ import { PostMessageService } from "../postMessage";
|
||||
import SendPymReady from "./SendPymReady";
|
||||
import { TalkContext, TalkContextProvider } from "./TalkContext";
|
||||
|
||||
export type InitLocalState = ((
|
||||
export type InitLocalState = (
|
||||
environment: Environment,
|
||||
context: TalkContext
|
||||
) => void | Promise<void>);
|
||||
) => void | Promise<void>;
|
||||
|
||||
interface CreateContextArguments {
|
||||
/** Locales that the user accepts, usually `navigator.languages`. */
|
||||
@@ -104,7 +104,7 @@ function createRelayEnvironment() {
|
||||
return { environment, tokenGetter };
|
||||
}
|
||||
|
||||
function createRestAPI(tokenGetter: (() => string)) {
|
||||
function createRestAPI(tokenGetter: () => string) {
|
||||
return new RestClient("/api", tokenGetter);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,7 @@ import React from "react";
|
||||
interface Props {
|
||||
form: FormApi;
|
||||
rootKey?: string;
|
||||
children: (
|
||||
params: {
|
||||
onInitValues: (data: any) => void;
|
||||
}
|
||||
) => React.ReactNode;
|
||||
children: (params: { onInitValues: (data: any) => void }) => React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,11 +7,9 @@ import { AddSubmitHook, SubmitHook, SubmitHookContextProvider } from "./";
|
||||
|
||||
interface Props {
|
||||
onExecute: (data: any, form: FormApi) => Promise<void>;
|
||||
children: (
|
||||
params: {
|
||||
onSubmit: (settings: any, form: FormApi) => void;
|
||||
}
|
||||
) => React.ReactNode;
|
||||
children: (params: {
|
||||
onSubmit: (settings: any, form: FormApi) => void;
|
||||
}) => React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,7 @@ export interface BundledLocales {
|
||||
}
|
||||
|
||||
export interface LoadableLocales {
|
||||
[locale: string]: (() => Promise<string>);
|
||||
[locale: string]: () => Promise<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,6 +15,8 @@ import { TalkContext, withContext } from "../bootstrap";
|
||||
* and the signature (input: I) => Promise<R>. Calling
|
||||
* this will call the specified `commit` callback with
|
||||
* the Relay `environment` provided by the context.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
function createMutationContainer<T extends string, I, R>(
|
||||
propName: T,
|
||||
|
||||
@@ -9,8 +9,8 @@ type RecordSourceProxy<T> = T extends object
|
||||
readonly [P in keyof T]?: T[P] extends Array<infer U>
|
||||
? ReadonlyArray<RecordSourceProxy<U>>
|
||||
: T[P] extends ReadonlyArray<infer V>
|
||||
? ReadonlyArray<RecordSourceProxy<V>>
|
||||
: RecordSourceProxy<T[P]>
|
||||
? ReadonlyArray<RecordSourceProxy<V>>
|
||||
: RecordSourceProxy<T[P]>
|
||||
}
|
||||
: T;
|
||||
|
||||
|
||||
@@ -35,14 +35,18 @@ export type MutationProp<
|
||||
> = T extends Mutation<any, infer I, infer R>
|
||||
? Parameters<T["commit"]>[1] extends undefined
|
||||
? () => R
|
||||
: keyof Parameters<T["commit"]>[1] extends never ? () => R : (input: I) => R
|
||||
: keyof Parameters<T["commit"]>[1] extends never
|
||||
? () => R
|
||||
: (input: I) => R
|
||||
: never;
|
||||
|
||||
type RemoveClientMutationID<T> = T extends Promise<infer U>
|
||||
? Promise<
|
||||
U extends { clientMutationId: any } ? Omit<U, "clientMutationId"> : U
|
||||
>
|
||||
: T extends { clientMutationId: any } ? Omit<T, "clientMutationId"> : T;
|
||||
: T extends { clientMutationId: any }
|
||||
? Omit<T, "clientMutationId">
|
||||
: T;
|
||||
|
||||
export function createMutation<N extends string, I, R>(
|
||||
name: N,
|
||||
|
||||
@@ -10,21 +10,18 @@ export default function useLoadMore(
|
||||
count: number
|
||||
): [() => void, boolean] {
|
||||
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
||||
const loadMore = useCallback(
|
||||
() => {
|
||||
if (!relay.hasMore() || relay.isLoading()) {
|
||||
return;
|
||||
const loadMore = useCallback(() => {
|
||||
if (!relay.hasMore() || relay.isLoading()) {
|
||||
return;
|
||||
}
|
||||
setIsLoadingMore(true);
|
||||
relay.loadMore(count, error => {
|
||||
setIsLoadingMore(false);
|
||||
if (error) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.error(error);
|
||||
}
|
||||
setIsLoadingMore(true);
|
||||
relay.loadMore(count, error => {
|
||||
setIsLoadingMore(false);
|
||||
if (error) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
},
|
||||
[relay]
|
||||
);
|
||||
});
|
||||
}, [relay]);
|
||||
return [loadMore, isLoadingMore];
|
||||
}
|
||||
|
||||
@@ -15,34 +15,31 @@ export default function useRefetch<V = Variables>(
|
||||
): [() => void, boolean] {
|
||||
const [manualRefetchCount, setManualRefetchCount] = useState(0);
|
||||
const [refetching, setRefetching] = useState(false);
|
||||
useEffectAfterMount(
|
||||
() => {
|
||||
setRefetching(true);
|
||||
const disposable = relay.refetchConnection(
|
||||
10,
|
||||
error => {
|
||||
setRefetching(false);
|
||||
if (error) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
variables
|
||||
);
|
||||
return () => {
|
||||
if (disposable) {
|
||||
disposable.dispose();
|
||||
useEffectAfterMount(() => {
|
||||
setRefetching(true);
|
||||
const disposable = relay.refetchConnection(
|
||||
10,
|
||||
error => {
|
||||
setRefetching(false);
|
||||
if (error) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
},
|
||||
[
|
||||
relay,
|
||||
manualRefetchCount,
|
||||
...Object.keys(variables).reduce<any[]>((a, k) => {
|
||||
a.push((variables as any)[k]);
|
||||
return a;
|
||||
}, []),
|
||||
]
|
||||
);
|
||||
},
|
||||
variables
|
||||
);
|
||||
return () => {
|
||||
if (disposable) {
|
||||
disposable.dispose();
|
||||
}
|
||||
};
|
||||
}, [
|
||||
relay,
|
||||
manualRefetchCount,
|
||||
...Object.keys(variables).reduce<any[]>((a, k) => {
|
||||
a.push((variables as any)[k]);
|
||||
return a;
|
||||
}, []),
|
||||
]);
|
||||
return [() => setManualRefetchCount(manualRefetchCount + 1), refetching];
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import createPymStorage from "./PymStorage";
|
||||
|
||||
class PymStub {
|
||||
public listeners: Record<string, ((msg: string) => void)> = {};
|
||||
public listeners: Record<string, (msg: string) => void> = {};
|
||||
public messages: Array<{ key: string; value: string }> = [];
|
||||
public type: string;
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ class PymStorage implements PromisifiedStorage {
|
||||
/** A Map of requestID => {resolve, reject} */
|
||||
private requests: Record<
|
||||
string,
|
||||
{ resolve: ((v: any) => void); reject: ((v: any) => void) }
|
||||
{ resolve: (v: any) => void; reject: (v: any) => void }
|
||||
> = {};
|
||||
|
||||
/** Requests method with parameters over pym. */
|
||||
|
||||
@@ -1,24 +1,46 @@
|
||||
import { merge } from "lodash";
|
||||
import { pureMerge } from "talk-common/utils";
|
||||
import { DeepPartial } from "talk-framework/types";
|
||||
|
||||
/**
|
||||
* Fixture prepares schema type to be used in fixtures.
|
||||
* It adds an optional `__typename` to the schema type and
|
||||
* marks fields as optional.
|
||||
* Callbackify turns Fields e.g. `{a: string}`
|
||||
* to also allow a callback `{a: string | () => string}`
|
||||
*/
|
||||
export type Fixture<T> = T extends object
|
||||
export type Callbackify<T> = T extends object
|
||||
?
|
||||
| {
|
||||
[P in keyof T]: T[P] extends Array<infer U>
|
||||
? Array<Callbackify<U>>
|
||||
: T[P] extends ReadonlyArray<infer V>
|
||||
? ReadonlyArray<Callbackify<V>>
|
||||
: Callbackify<T[P]>
|
||||
}
|
||||
| (() => {
|
||||
[P in keyof T]: T[P] extends Array<infer U>
|
||||
? Array<Callbackify<U>>
|
||||
: T[P] extends ReadonlyArray<infer V>
|
||||
? ReadonlyArray<Callbackify<V>>
|
||||
: Callbackify<T[P]>
|
||||
})
|
||||
: T | (() => T);
|
||||
|
||||
/**
|
||||
* WithTypename adds `__typename` to allowed props deeply.
|
||||
*/
|
||||
export type WithTypename<T> = T extends object
|
||||
? {
|
||||
// (cvle): We don't use & { __typename?: string } because for some reason
|
||||
// typescript would allow field names that are not defined!
|
||||
[P in keyof T | "__typename"]?: P extends keyof T
|
||||
? T[P] extends Array<infer U>
|
||||
? Array<Fixture<U>>
|
||||
: T[P] extends ReadonlyArray<infer V>
|
||||
? ReadonlyArray<Fixture<V>>
|
||||
: Fixture<T[P]>
|
||||
: string
|
||||
}
|
||||
[P in keyof T]: T[P] extends Array<infer U>
|
||||
? Array<WithTypename<U>>
|
||||
: T[P] extends ReadonlyArray<infer V>
|
||||
? ReadonlyArray<WithTypename<V>>
|
||||
: WithTypename<T[P]>
|
||||
} & { __typename?: string }
|
||||
: T;
|
||||
|
||||
/**
|
||||
* Fixture adds typenames and is deeply partial.
|
||||
*/
|
||||
export type Fixture<T> = DeepPartial<WithTypename<T>>;
|
||||
|
||||
/**
|
||||
* createFixture lets you input the data of a schema object as deep partial
|
||||
* including it's `__typename`, merged it with `base` and return it as the
|
||||
@@ -26,9 +48,12 @@ export type Fixture<T> = T extends object
|
||||
* to only include fields that exists in `data` and `base` though to
|
||||
* type this it seems we need partial generic inferation support.
|
||||
*/
|
||||
export default function createFixture<T>(data: Fixture<T>, base?: T): T {
|
||||
export default function createFixture<T>(
|
||||
data: Fixture<T>,
|
||||
base?: T
|
||||
): WithTypename<T> {
|
||||
if (base) {
|
||||
return merge({}, base, data);
|
||||
return pureMerge(base, data) as any;
|
||||
}
|
||||
return data as T;
|
||||
return data as any;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import createFixture, { Fixture } from "./createFixture";
|
||||
import createFixture, { Fixture, WithTypename } from "./createFixture";
|
||||
|
||||
/**
|
||||
* createFixtures lets you input an array of data of a schema object as deep partial
|
||||
@@ -10,6 +10,6 @@ import createFixture, { Fixture } from "./createFixture";
|
||||
export default function createFixtures<T>(
|
||||
data: Array<Fixture<T>>,
|
||||
base?: T
|
||||
): T[] {
|
||||
return data.map(d => createFixture(d, base)) as T[];
|
||||
): Array<WithTypename<T>> {
|
||||
return data.map(d => createFixture(d, base)) as any;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,36 @@
|
||||
import { identity, omit } from "lodash";
|
||||
import sinon from "sinon";
|
||||
import { Mutation } from "talk-framework/lib/relay/mutation";
|
||||
import { Omit } from "talk-framework/types";
|
||||
|
||||
import { Fixture } from "./createFixture";
|
||||
import { Resolver } from "./createTestRenderer";
|
||||
|
||||
export type NoClientMutationID<T> = T extends { clientMutationId: any }
|
||||
? Omit<T, "clientMutationId">
|
||||
: T;
|
||||
|
||||
export type MutationResult<T> = NoClientMutationID<Fixture<T>>;
|
||||
export type MutationResultVariations<T> = T extends Resolver<any, infer R>
|
||||
? MutationResult<R>
|
||||
: never;
|
||||
|
||||
export type MutationResolverCallback<T extends Resolver<any, any>> = (data: {
|
||||
variables: T extends Resolver<infer V, any>
|
||||
? V extends { input: infer W }
|
||||
? NoClientMutationID<W>
|
||||
: never
|
||||
: never;
|
||||
callCount: number;
|
||||
typecheck: (data: MutationResultVariations<T>) => MutationResultVariations<T>;
|
||||
}) => MutationResultVariations<T>;
|
||||
|
||||
/**
|
||||
* createMutationResolverStub makes it easier to write a SinonStub.
|
||||
* Given a `ResolverType` from the Schema it'll provide types as well!.
|
||||
*/
|
||||
export default function createMutationResolverStub<
|
||||
T extends Mutation<any, any, any>
|
||||
>(
|
||||
callback: (
|
||||
variables: T extends Mutation<any, infer I, any> ? I : never,
|
||||
callCount: number
|
||||
) => T extends Mutation<any, any, infer R>
|
||||
? R extends Promise<infer U> ? U | R : R | Promise<R>
|
||||
: never
|
||||
) {
|
||||
T extends Resolver<any, any>
|
||||
>(callback: MutationResolverCallback<T>) {
|
||||
let callCount = 0;
|
||||
const lastClientMutationIds: any[] = [];
|
||||
const resolver = async (_: any, data: any) => {
|
||||
@@ -18,7 +38,11 @@ export default function createMutationResolverStub<
|
||||
expectAndFail(clientMutationId).toBeTruthy();
|
||||
expectAndFail(lastClientMutationIds).not.toContain(clientMutationId);
|
||||
lastClientMutationIds.push(clientMutationId);
|
||||
const result = await callback(data.input, callCount++);
|
||||
const result = await callback({
|
||||
variables: omit(data.input, "clientMutationId"),
|
||||
callCount: callCount++,
|
||||
typecheck: identity,
|
||||
});
|
||||
expectAndFail(result.clientMutationId).toBeUndefined();
|
||||
result.clientMutationId = clientMutationId;
|
||||
return result;
|
||||
|
||||
@@ -1,17 +1,33 @@
|
||||
import { identity } from "lodash";
|
||||
import sinon from "sinon";
|
||||
|
||||
type Resolver<V, R> = (parent: any, args: V, context: any, info: any) => R;
|
||||
import { Fixture } from "./createFixture";
|
||||
import { Resolver } from "./createTestRenderer";
|
||||
|
||||
export type QueryResult<T> = Fixture<T>;
|
||||
export type QueryResultVariations<
|
||||
T extends Resolver<any, any>
|
||||
> = T extends Resolver<any, infer R> ? QueryResult<R> : never;
|
||||
|
||||
export type QueryResolverCallback<T extends Resolver<any, any>> = (data: {
|
||||
variables: T extends Resolver<infer V, any> ? V : never;
|
||||
callCount: number;
|
||||
typecheck: (data: QueryResultVariations<T>) => QueryResultVariations<T>;
|
||||
}) => QueryResultVariations<T>;
|
||||
|
||||
/**
|
||||
* createQueryResolverStub makes it easier to write a SinonStub.
|
||||
* Given a `ResolverType` from the Schema it'll provide types as well!.
|
||||
*/
|
||||
export default function createQueryResolverStub<T extends Resolver<any, any>>(
|
||||
callback: (
|
||||
variables: T extends Resolver<infer V, any> ? V : never,
|
||||
callCount: number
|
||||
) => T extends Resolver<any, infer R>
|
||||
? R extends Promise<infer U> ? U | R : R | Promise<R>
|
||||
: never
|
||||
callback: QueryResolverCallback<T>
|
||||
) {
|
||||
let callCount = 0;
|
||||
return sinon
|
||||
.stub()
|
||||
.callsFake((_: any, data: any) => callback(data, callCount++));
|
||||
return sinon.stub().callsFake((fallback: any, variables: any) => {
|
||||
return callback({
|
||||
variables: variables || fallback,
|
||||
callCount: callCount++,
|
||||
typecheck: identity,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export interface CreateRelayEnvironmentNetworkParams {
|
||||
/** project name of graphql-config */
|
||||
projectName: string;
|
||||
/** graphql resolvers */
|
||||
resolvers: IResolvers<any, any>;
|
||||
resolvers?: IResolvers<any, any>;
|
||||
/** If enabled, graphql responses will be logged to the console */
|
||||
logNetwork?: boolean;
|
||||
/** If enabled, graphql errors will be muted */
|
||||
@@ -99,7 +99,7 @@ export default function createRelayEnvironment(
|
||||
if (params.network) {
|
||||
const schema = loadSchema(
|
||||
params.network.projectName,
|
||||
params.network.resolvers,
|
||||
params.network.resolvers || {},
|
||||
{ requireResolversForResolveType: false }
|
||||
);
|
||||
network = Network.create(
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import { mapValues } from "lodash";
|
||||
import { SinonStub } from "sinon";
|
||||
|
||||
import createMutationResolverStub, {
|
||||
MutationResolverCallback,
|
||||
} from "./createMutationResolverStub";
|
||||
import createQueryResolverStub, {
|
||||
QueryResolverCallback,
|
||||
} from "./createQueryResolverStub";
|
||||
import { Resolvers } from "./createTestRenderer";
|
||||
|
||||
export interface ResolversTemplate<T extends Resolvers = any> {
|
||||
Query?: {
|
||||
[P in keyof Required<T>["Query"]]: QueryResolverCallback<
|
||||
Required<T>["Query"][P]
|
||||
>
|
||||
};
|
||||
Mutation?: {
|
||||
[P in keyof Required<T>["Mutation"]]: MutationResolverCallback<
|
||||
Required<T>["Mutation"][P]
|
||||
>
|
||||
};
|
||||
}
|
||||
|
||||
export interface ResolversStub<T extends Resolvers = any> {
|
||||
Query?: { [P in keyof Required<T>["Query"]]: SinonStub };
|
||||
Mutation?: { [P in keyof Required<T>["Mutation"]]: SinonStub };
|
||||
}
|
||||
|
||||
function isSinonStub(v: any) {
|
||||
return v.called !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* createResolversStub is a helper for creating resolvers.
|
||||
*
|
||||
* Instead of writing:
|
||||
* ```ts
|
||||
* {
|
||||
* Query: {
|
||||
* settings: createQueryResolverStub<QueryToSettingsResolver>(({ variables }) => settings)
|
||||
* users: createQueryResolverStub<QueryToUsersResolver>(() => users)
|
||||
* viewer: createQueryResolverStub<QueryToSettingsResolver>(() => viewer)
|
||||
* },
|
||||
* Mutation: {
|
||||
* // Same goes for Mutations
|
||||
* }
|
||||
* },
|
||||
* ```
|
||||
*
|
||||
* You can do
|
||||
* ```ts
|
||||
* createResolversStub<GQLResolver>({
|
||||
* Query: {
|
||||
* settings: ({ variables }) => settings
|
||||
* users: () => users
|
||||
* viewer: () => viewer
|
||||
* },
|
||||
* Mutation: {
|
||||
* // Same goes for Mutations
|
||||
* }
|
||||
* }),
|
||||
* ```
|
||||
*/
|
||||
export default function createResolversStub<T extends Resolvers = any>(
|
||||
resolvers: ResolversTemplate<T>
|
||||
): ResolversStub<T> {
|
||||
const result: any = {};
|
||||
if (resolvers.Query) {
|
||||
result.Query = mapValues(resolvers.Query, v =>
|
||||
typeof v === "function" && !isSinonStub(v)
|
||||
? createQueryResolverStub(v)
|
||||
: v
|
||||
);
|
||||
}
|
||||
if (resolvers.Mutation) {
|
||||
result.Mutation = mapValues(resolvers.Mutation, v =>
|
||||
typeof v === "function" && !isSinonStub(v)
|
||||
? createMutationResolverStub(v)
|
||||
: v
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import { EventEmitter2 } from "eventemitter2";
|
||||
import { IResolvers } from "graphql-tools";
|
||||
import { noop } from "lodash";
|
||||
import path from "path";
|
||||
import React from "react";
|
||||
import TestRenderer, { ReactTestRenderer } from "react-test-renderer";
|
||||
import { Environment, RecordProxy, RecordSourceProxy } from "relay-runtime";
|
||||
|
||||
import { TalkContext, TalkContextProvider } from "talk-framework/lib/bootstrap";
|
||||
import { PostMessageService } from "talk-framework/lib/postMessage";
|
||||
import { RestClient } from "talk-framework/lib/rest";
|
||||
import { createPromisifiedStorage } from "talk-framework/lib/storage";
|
||||
import { createUUIDGenerator } from "talk-framework/testHelpers";
|
||||
|
||||
import createFluentBundle from "./createFluentBundle";
|
||||
import createRelayEnvironment from "./createRelayEnvironment";
|
||||
|
||||
export type Resolver<V, R> = (
|
||||
parent: any,
|
||||
args: V,
|
||||
context: any,
|
||||
info: any
|
||||
) => R;
|
||||
|
||||
export interface Resolvers<Q extends Resolver<any, any> = any, M = any> {
|
||||
Query?: Q;
|
||||
Mutation?: M;
|
||||
}
|
||||
|
||||
export interface TestResolvers<T extends Resolvers = any> {
|
||||
Query?: { [P in keyof Required<T>["Query"]]: (() => any) };
|
||||
Mutation?: { [P in keyof Required<T>["Mutation"]]: (() => any) };
|
||||
}
|
||||
|
||||
function createNodeMock(element: React.ReactElement<any>) {
|
||||
if (element.type === "div") {
|
||||
return {
|
||||
innerHtml: "",
|
||||
className: "",
|
||||
focus: noop,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export interface CreateTestRendererParams<T extends Resolvers = any> {
|
||||
logNetwork?: boolean;
|
||||
muteNetworkErrors?: boolean;
|
||||
resolvers?: TestResolvers<T>;
|
||||
browserInfo?: TalkContext["browserInfo"];
|
||||
initLocalState?: (
|
||||
local: RecordProxy,
|
||||
source: RecordSourceProxy,
|
||||
environment: Environment
|
||||
) => void;
|
||||
}
|
||||
|
||||
export default function createTestRenderer<
|
||||
T extends { Query?: any; Mutation?: any } = any
|
||||
>(
|
||||
target: string,
|
||||
element: React.ReactNode,
|
||||
params: CreateTestRendererParams<T>
|
||||
) {
|
||||
const environment = createRelayEnvironment({
|
||||
network: {
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: params.logNetwork,
|
||||
resolvers: params.resolvers as IResolvers<any, any>,
|
||||
muteNetworkErrors: params.muteNetworkErrors,
|
||||
projectName: "tenant",
|
||||
},
|
||||
initLocalState: (localRecord, source, env) => {
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, env);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const context: TalkContext = {
|
||||
relayEnvironment: environment,
|
||||
locales: ["en-US"],
|
||||
localeBundles: [
|
||||
createFluentBundle(
|
||||
target,
|
||||
path.resolve(__dirname, "../../../../locales/en-US")
|
||||
),
|
||||
],
|
||||
localStorage: createPromisifiedStorage(),
|
||||
sessionStorage: createPromisifiedStorage(),
|
||||
rest: new RestClient("http://localhost/api"),
|
||||
postMessage: new PostMessageService(),
|
||||
browserInfo: params.browserInfo || { ios: false },
|
||||
uuidGenerator: createUUIDGenerator(),
|
||||
eventEmitter: new EventEmitter2({ wildcard: true, maxListeners: 20 }),
|
||||
clearSession: () => Promise.resolve(),
|
||||
};
|
||||
|
||||
let testRenderer: ReactTestRenderer;
|
||||
TestRenderer.act(() => {
|
||||
testRenderer = TestRenderer.create(
|
||||
<TalkContextProvider value={context}>{element}</TalkContextProvider>,
|
||||
{ createNodeMock }
|
||||
);
|
||||
});
|
||||
return { context, testRenderer: testRenderer! };
|
||||
}
|
||||
@@ -11,7 +11,6 @@ export {
|
||||
export { default as createUUIDGenerator } from "./createUUIDGenerator";
|
||||
export * from "./denormalize";
|
||||
export { default as limitSnapshotTo } from "./limitSnapshotTo";
|
||||
export { default as inputPredicate } from "./inputPredicate";
|
||||
export { default as within } from "./within";
|
||||
export { default as wait } from "./wait";
|
||||
export { default as waitForElement } from "./waitForElement";
|
||||
@@ -22,9 +21,18 @@ export { default as replaceHistoryLocation } from "./replaceHistoryLocation";
|
||||
export { default as createAccessToken } from "./createAccessToken";
|
||||
export { default as findParentsWithType } from "./findParentsWithType";
|
||||
export { default as findParentWithType } from "./findParentWithType";
|
||||
export { default as createFixture } from "./createFixture";
|
||||
export {
|
||||
default as createFixture,
|
||||
Fixture,
|
||||
WithTypename,
|
||||
} from "./createFixture";
|
||||
export { default as createFixtures } from "./createFixtures";
|
||||
export {
|
||||
default as createMutationResolverStub,
|
||||
} from "./createMutationResolverStub";
|
||||
export { default as createQueryResolverStub } from "./createQueryResolverStub";
|
||||
export {
|
||||
default as createTestRenderer,
|
||||
CreateTestRendererParams,
|
||||
} from "./createTestRenderer";
|
||||
export { default as createResolversStub } from "./createResolversStub";
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import { ReactTestInstance } from "react-test-renderer";
|
||||
|
||||
const inputPredicate = (nameOrID: string) => (n: ReactTestInstance) => {
|
||||
return (
|
||||
[n.props.name, n.props.id].indexOf(nameOrID) > -1 &&
|
||||
["input", "button"].indexOf(n.type as string) > -1
|
||||
);
|
||||
};
|
||||
|
||||
export default inputPredicate;
|
||||
@@ -12,8 +12,8 @@ export type NoFragmentRefs<T> = T extends object
|
||||
? T extends ((...args: any[]) => any)
|
||||
? T
|
||||
: T extends ReadonlyArray<infer U>
|
||||
? ReadonlyArray<NoFragmentRefs2<U>> // TODO: (cvle) this should normally reference itself but it complains about a circular reference.
|
||||
: { [P in keyof OmitFragments<T>]: NoFragmentRefs<T[P]> }
|
||||
? ReadonlyArray<NoFragmentRefs2<U>> // TODO: (cvle) this should normally reference itself but it complains about a circular reference.
|
||||
: { [P in keyof OmitFragments<T>]: NoFragmentRefs<T[P]> }
|
||||
: T;
|
||||
|
||||
// TODO: (cvle) these NoFragmentRefX are a workaround for above issue
|
||||
@@ -21,16 +21,16 @@ export type NoFragmentRefs2<T> = T extends object
|
||||
? T extends ((...args: any[]) => any)
|
||||
? T
|
||||
: T extends ReadonlyArray<infer U>
|
||||
? ReadonlyArray<NoFragmentRefs3<U>>
|
||||
: { [P in keyof OmitFragments<T>]: NoFragmentRefs<T[P]> }
|
||||
? ReadonlyArray<NoFragmentRefs3<U>>
|
||||
: { [P in keyof OmitFragments<T>]: NoFragmentRefs<T[P]> }
|
||||
: T;
|
||||
|
||||
export type NoFragmentRefs3<T> = T extends object
|
||||
? T extends ((...args: any[]) => any)
|
||||
? T
|
||||
: T extends ReadonlyArray<infer U>
|
||||
? ReadonlyArray<NoFragmentRefs4<U>>
|
||||
: { [P in keyof OmitFragments<T>]: NoFragmentRefs<T[P]> }
|
||||
? ReadonlyArray<NoFragmentRefs4<U>>
|
||||
: { [P in keyof OmitFragments<T>]: NoFragmentRefs<T[P]> }
|
||||
: T;
|
||||
|
||||
export type NoFragmentRefs4<T> = T extends object
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { ReactTestInstance } from "react-test-renderer";
|
||||
|
||||
import {
|
||||
getByID,
|
||||
queryByID,
|
||||
} from "./byID";
|
||||
import { getByID, queryByID } from "./byID";
|
||||
import {
|
||||
getAllByLabelText,
|
||||
getByLabelText,
|
||||
@@ -17,12 +14,7 @@ import {
|
||||
queryByTestID,
|
||||
} from "./byTestID";
|
||||
import { getAllByText, getByText, queryAllByText, queryByText } from "./byText";
|
||||
import {
|
||||
getAllByType,
|
||||
getByType,
|
||||
queryAllByType,
|
||||
queryByType,
|
||||
} from "./byType";
|
||||
import { getAllByType, getByType, queryAllByType, queryByType } from "./byType";
|
||||
import toJSON from "./toJSON";
|
||||
|
||||
type Func0<R> = () => R;
|
||||
@@ -30,13 +22,15 @@ type Func1<A, R> = (a?: A) => R;
|
||||
type Func2<A, B, R> = (a: A, b?: B) => R;
|
||||
type Func3<A, B, C, R> = (a: A, b: B, c?: C) => R;
|
||||
|
||||
type RemoveFirstArgument<T, R> =
|
||||
T extends [any, any, any, any?] ? Func3<T[1], T[2], T[3], R> :
|
||||
T extends [any, any, any?] ? Func2<T[1], T[2], R> :
|
||||
T extends [any, any?] ? Func1<T[1], R> :
|
||||
T extends [any] ? Func0<R> :
|
||||
unknown
|
||||
;
|
||||
type RemoveFirstArgument<T, R> = T extends [any, any, any, any?]
|
||||
? Func3<T[1], T[2], T[3], R>
|
||||
: T extends [any, any, any?]
|
||||
? Func2<T[1], T[2], R>
|
||||
: T extends [any, any?]
|
||||
? Func1<T[1], R>
|
||||
: T extends [any]
|
||||
? Func0<R>
|
||||
: unknown;
|
||||
|
||||
// tslint:disable
|
||||
// @TODO: currently tslint fails to parse this: `...any[]`.
|
||||
@@ -66,5 +60,5 @@ export default function within(container: ReactTestInstance) {
|
||||
queryByType: applyContainer(container, queryByType),
|
||||
queryAllByType: applyContainer(container, queryAllByType),
|
||||
toJSON: () => toJSON(container),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user