Skip to content

Commit

Permalink
chore: simplify types
Browse files Browse the repository at this point in the history
  • Loading branch information
alessbell committed Aug 5, 2024
1 parent d88d254 commit 256d939
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 145 deletions.
104 changes: 52 additions & 52 deletions src/__tests__/handlers.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,58 +179,58 @@ describe("integration tests", () => {
});
});

// describe.skip("integration tests with github schema", () => {
// it("renders a component fetching from the GitHub api", async () => {
// const client = makeClient();

// const APP_QUERY = gql`
// query AppQuery {
// repository(owner: "octocat", name: "Hello-World") {
// issues(last: 20, states: CLOSED) {
// edges {
// node {
// title
// url
// labels(first: 5) {
// edges {
// node {
// name
// }
// }
// }
// }
// }
// }
// }
// }
// `;

// const Shell = () => {
// console.log("render");
// return (
// <ApolloProvider client={client}>
// <Suspense fallback={<h1>Loading...</h1>}>
// <App />
// </Suspense>
// </ApolloProvider>
// );
// };

// const App = () => {
// const { data } = useSuspenseQuery(APP_QUERY);
// if (data) {
// console.log(data.repository.issues.edges);
// }
// return <>hi</>;
// };

// render(<Shell />);

// await waitFor(() =>
// expect(screen.getByText(/beanie/i)).toBeInTheDocument(),
// );
// });
// });
describe.skip("integration tests with github schema", () => {
it("renders a component fetching from the GitHub api", async () => {
const client = makeClient();

const APP_QUERY = gql`
query AppQuery {
repository(owner: "octocat", name: "Hello-World") {
issues(last: 20, states: CLOSED) {
edges {
node {
title
url
labels(first: 5) {
edges {
node {
name
}
}
}
}
}
}
}
}
`;

const Shell = () => {
console.log("render");
return (
<ApolloProvider client={client}>
<Suspense fallback={<h1>Loading...</h1>}>
<App />
</Suspense>
</ApolloProvider>
);
};

const App = () => {
const { data } = useSuspenseQuery(APP_QUERY);
if (data) {
console.log(data.repository.issues.edges);

Check failure on line 222 in src/__tests__/handlers.test.tsx

View workflow job for this annotation

GitHub Actions / Check types

Property 'repository' does not exist on type '{}'.
}
return <>hi</>;
};

render(<Shell />);

await waitFor(() =>
expect(screen.getByText(/beanie/i)).toBeInTheDocument(),
);
});
});

describe("unit tests", () => {
it("can roll back delay via disposable", () => {
Expand Down
3 changes: 3 additions & 0 deletions src/__tests__/mocks/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ const { handler, replaceSchema, withResolvers, replaceDelay } =
})),
},
},
mocks: {
String: () => "foo bar",
},
});

const handlers = [handler];
Expand Down
32 changes: 22 additions & 10 deletions src/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@ import {
type IMocks,
type IMockStore,
} from "@graphql-tools/mock";
import { mergeResolvers } from "@graphql-tools/merge";
import {
createDefaultResolvers,
createPossibleTypesMap,
mockCustomScalars,
mockEnums,
hasDirectives,
mergeResolvers,
generateEnumMocksFromSchema,
} from "./utilities.ts";
import type { IResolvers, Maybe } from "@graphql-tools/utils";

const encoder = new TextEncoder();

Expand Down Expand Up @@ -63,6 +64,7 @@ function createHandler<TResolvers>(
const schemaWithMocks = createSchemaWithDefaultMocks<TResolvers>(
executableSchema,
resolvers,
mocks,
);

return createHandlerFromSchema<TResolvers>({
Expand All @@ -73,20 +75,27 @@ function createHandler<TResolvers>(

function createSchemaWithDefaultMocks<TResolvers>(
executableSchema: GraphQLSchema,
resolvers: Resolvers<TResolvers>,
resolvers: Partial<TResolvers> | ((store: IMockStore) => Partial<TResolvers>),
mocks?: IMocks<TResolvers>,
) {
const enumMocks = mockEnums(executableSchema);
const enumMocks = generateEnumMocksFromSchema(executableSchema);
const customScalarMocks = mockCustomScalars(executableSchema);
const typesMap = createPossibleTypesMap(executableSchema);
const defaultResolvers = createDefaultResolvers(typesMap);

return addMocksToSchema<TResolvers>({
schema: executableSchema,
// @ts-expect-error reconcile mock resolver types
mocks: mergeResolvers(enumMocks, customScalarMocks, mocks ?? {}),
// @ts-expect-error reconcile mock resolver types
resolvers: mergeResolvers(defaultResolvers, resolvers),
mocks: {
...enumMocks,
...customScalarMocks,
...mocks,
} as IMocks<TResolvers>,
resolvers: mergeResolvers([
defaultResolvers,
resolvers as Maybe<
IResolvers<{ __typename?: string | undefined }, unknown>
>,
]) as Partial<TResolvers>,
preserveResolvers: true,
});
}
Expand Down Expand Up @@ -126,8 +135,11 @@ function createHandlerFromSchema<TResolvers>(
function withResolvers(resolvers: Resolvers<TResolvers>) {
const oldSchema = testSchema;

// @ts-expect-error reconcile mock resolver types
testSchema = addResolversToSchema({ schema: oldSchema, resolvers });
testSchema = addResolversToSchema({
schema: oldSchema,
// @ts-expect-error reconcile mock resolver types
resolvers,
});

function restore() {
testSchema = oldSchema;
Expand Down
15 changes: 5 additions & 10 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,18 @@ import {
createSchemaWithDefaultMocks,
} from "./handlers.js";
import {
mockEnums,
mergeResolvers,
mockCustomScalars,
createPossibleTypesMap,
createDefaultResolvers,
type ResolverMap,
createPossibleTypesMap,
generateEnumMocksFromSchema,
mockCustomScalars,
} from "./utilities.ts";

export type { ResolverMap };

export {
createHandler,
createHandlerFromSchema,
mockEnums,
mergeResolvers,
generateEnumMocksFromSchema,
mockCustomScalars,
createPossibleTypesMap,
createDefaultResolvers,
createPossibleTypesMap,
createSchemaWithDefaultMocks,
};
75 changes: 2 additions & 73 deletions src/utilities.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {
type ASTNode,
type GraphQLEnumValue,
type GraphQLResolveInfo,
type GraphQLSchema,
GraphQLEnumType,
isUnionType,
Expand Down Expand Up @@ -93,7 +92,7 @@ const sortEnumValues = () => {
// TODO: memoize
// Creates a map of enum types and mock resolver functions that return
// the first possible value.
function mockEnums(schema: GraphQLSchema) {
function generateEnumMocksFromSchema(schema: GraphQLSchema) {
return Object.fromEntries(
Object.entries(schema.getTypeMap())
.filter(
Expand Down Expand Up @@ -142,80 +141,10 @@ function mockCustomScalars(schema: GraphQLSchema) {
return mockScalarsMap;
}

export type ResolverMap<ResolversTypes> = {
[key in keyof ResolversTypes]?: () => ResolversTypes[key] extends () => unknown
?
| ReturnType<ResolversTypes[key]>
| {
[key2 in keyof ReturnType<ResolversTypes[key]>]:
| ReturnType<ResolversTypes[key]>[key2]
| ((
args: Record<string, unknown> | undefined,
context: unknown,
field: GraphQLResolveInfo,
) => ReturnType<ResolversTypes[key]>[key2]);
}
| null
: null;
};

// adapted from https://github.com/apollographql/graphql-tools/pull/1084/files
// as per https://www.freecodecamp.org/news/a-new-approach-to-mocking-graphql-data-1ef49de3d491/

/**
* Given a map of mock GraphQL resolver functions, merge in a map of
* desired mocks. Generally, `target` will be the default mocked values,
* and `input` will be the values desired for a portal example or Jest tests.
*/
function mergeResolver<RTypes>(
target: ResolverMap<RTypes>,
input: ResolverMap<RTypes>,
) {
const inputTypenames = Object.keys(input) as (keyof ResolverMap<RTypes>)[];
const merged = inputTypenames.reduce(
(accum, key) => {
const inputResolver = input[key];
if (!inputResolver) throw new Error("missing input resolver");
if (Object.prototype.hasOwnProperty.call(target, key)) {
const resolvedInput: unknown = inputResolver();
const resolvedTarget: unknown = target[key]?.();
if (
!!resolvedTarget &&
!!resolvedInput &&
typeof resolvedTarget === "object" &&
typeof resolvedInput === "object" &&
!Array.isArray(resolvedTarget) &&
!Array.isArray(resolvedInput)
) {
return {
...accum,
[key]: () => ({ ...resolvedTarget, ...resolvedInput }),
};
}
}
return { ...accum, [key]: inputResolver };
},
{ ...target },
);
return merged;
}

function mergeResolvers<RTypes>(
target: ResolverMap<RTypes>,
...inputs: ResolverMap<RTypes>[]
) {
let resolver = target;
inputs.forEach((input) => {
resolver = mergeResolver<RTypes>(resolver, input);
});
return resolver;
}

export {
hasDirectives,
createPossibleTypesMap,
createDefaultResolvers,
mockEnums,
generateEnumMocksFromSchema,
mockCustomScalars,
mergeResolvers,
};

1 comment on commit 256d939

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lines Statements Branches Functions
Coverage: 91%
90.9% (120/132) 84.61% (33/39) 75% (24/32)
Tests Skipped Failures Errors Time
6 1 💤 0 ❌ 0 🔥 2.7s ⏱️
Coverage Report (91%)
File% Stmts% Branch% Funcs% LinesUncovered Line #s
All files90.984.617591.47 
   handlers.ts93.6710081.2593.5120–130
   utilities.ts86.797668.7588.4678, 125–136

Please sign in to comment.