@graphql-tools/mock 8.4.2 → 8.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -3,3 +3,4 @@ export * from './addMocksToSchema';
3
3
  export * from './mockServer';
4
4
  export * from './types';
5
5
  export * from './MockList';
6
+ export * from './pagination';
package/index.js CHANGED
@@ -59,6 +59,10 @@ function copyOwnProps(target, ...sources) {
59
59
  }
60
60
  return target;
61
61
  }
62
+ const isRootType = (type, schema) => {
63
+ const rootTypeNames = utils.getRootTypeNames(schema);
64
+ return rootTypeNames.has(type.name);
65
+ };
62
66
 
63
67
  /**
64
68
  * @internal
@@ -678,12 +682,12 @@ function addMocksToSchema({ schema: schema$1, store: maybeStore, mocks, typePoli
678
682
  if (mocks && !isObject(mocks)) {
679
683
  throw new Error('mocks must be of type Object');
680
684
  }
681
- const store = maybeStore ||
682
- createMockStore({
683
- schema: schema$1,
684
- mocks,
685
- typePolicies,
686
- });
685
+ const mockStore = createMockStore({
686
+ schema: schema$1,
687
+ mocks,
688
+ typePolicies,
689
+ });
690
+ const store = maybeStore || mockStore;
687
691
  const resolvers = typeof resolversOrFnResolvers === 'function' ? resolversOrFnResolvers(store) : resolversOrFnResolvers;
688
692
  const mockResolver = (source, args, contex, info) => {
689
693
  const defaultResolvedValue = graphql.defaultFieldResolver(source, args, contex, info);
@@ -708,6 +712,10 @@ function addMocksToSchema({ schema: schema$1, store: maybeStore, mocks, typePoli
708
712
  fieldArgs: args,
709
713
  });
710
714
  }
715
+ if (defaultResolvedValue === undefined) {
716
+ // any is used here because generateValueFromType is a private method at time of writing
717
+ return mockStore.generateValueFromType(info.returnType);
718
+ }
711
719
  return undefined;
712
720
  };
713
721
  const typeResolver = data => {
@@ -797,10 +805,6 @@ function addMocksToSchema({ schema: schema$1, store: maybeStore, mocks, typePoli
797
805
  });
798
806
  return resolvers ? schema.addResolversToSchema(schemaWithMocks, resolvers) : schemaWithMocks;
799
807
  }
800
- const isRootType = (type, schema) => {
801
- const rootTypeNames = utils.getRootTypeNames(schema);
802
- return rootTypeNames.has(type.name);
803
- };
804
808
 
805
809
  /**
806
810
  * A convenience wrapper on top of addMocksToSchema. It adds your mock resolvers
@@ -835,6 +839,76 @@ function mockServer(schema$1, mocks, preserveResolvers = false) {
835
839
  };
836
840
  }
837
841
 
842
+ /**
843
+ * Produces a resolver that'll mock a [Relay-style cursor pagination](https://relay.dev/graphql/connections.htm).
844
+ *
845
+ * ```ts
846
+ * const schemaWithMocks = addMocksToSchema({
847
+ * schema,
848
+ * resolvers: (store) => ({
849
+ * User: {
850
+ * friends: relayStylePaginationMock(store),
851
+ * }
852
+ * }),
853
+ * })
854
+ * ```
855
+ * @param store the MockStore
856
+ */
857
+ const relayStylePaginationMock = (store, { cursorFn = node => `${node.$ref.key}`, applyOnNodes, allNodesFn, } = {}) => {
858
+ return (parent, args, context, info) => {
859
+ const source = isRootType(info.parentType, info.schema) ? makeRef(info.parentType.name, 'ROOT') : parent;
860
+ const allNodesFn_ = allNodesFn !== null && allNodesFn !== void 0 ? allNodesFn : defaultAllNodesFn(store);
861
+ let allNodes = allNodesFn_(source, args, context, info);
862
+ if (applyOnNodes) {
863
+ allNodes = applyOnNodes(allNodes, args);
864
+ }
865
+ const allEdges = allNodes.map(node => ({
866
+ node,
867
+ cursor: cursorFn(node),
868
+ }));
869
+ let start, end;
870
+ const { first, after, last, before } = args;
871
+ if (typeof first === 'number') {
872
+ // forward pagination
873
+ if (last || before) {
874
+ throw new Error("if `first` is provided, `last` or `before` can't be provided");
875
+ }
876
+ const afterIndex = after ? allEdges.findIndex(e => e.cursor === after) : -1;
877
+ start = afterIndex + 1;
878
+ end = afterIndex + 1 + first;
879
+ }
880
+ else if (typeof last === 'number') {
881
+ // backward pagination
882
+ if (first || after) {
883
+ throw new Error("if `last` is provided, `first` or `after` can't be provided");
884
+ }
885
+ const foundBeforeIndex = before ? allEdges.findIndex(e => e.cursor === before) : -1;
886
+ const beforeIndex = foundBeforeIndex !== -1 ? foundBeforeIndex : allNodes.length;
887
+ start = allEdges.length - (allEdges.length - beforeIndex) - last;
888
+ // negative index on Array.slice indicate offset from end of sequence => we don't want
889
+ if (start < 0)
890
+ start = 0;
891
+ end = beforeIndex;
892
+ }
893
+ else {
894
+ throw new Error('A `first` or a `last` arguments should be provided');
895
+ }
896
+ const edges = allEdges.slice(start, end);
897
+ const pageInfo = {
898
+ startCursor: edges.length > 0 ? edges[0].cursor : '',
899
+ endCursor: edges.length > 0 ? edges[edges.length - 1].cursor : '',
900
+ hasNextPage: end < allEdges.length - 1,
901
+ hasPreviousPage: start > 0,
902
+ };
903
+ return {
904
+ edges,
905
+ pageInfo,
906
+ totalCount: allEdges.length,
907
+ };
908
+ };
909
+ };
910
+ const defaultAllNodesFn = (store) => (parent, _, __, info) => store.get(parent, [info.fieldName, 'edges']).map(e => store.get(e, 'node'));
911
+
838
912
  exports.MockList = MockList;
839
913
  exports.MockStore = MockStore;
840
914
  exports.addMocksToSchema = addMocksToSchema;
@@ -846,3 +920,4 @@ exports.isMockList = isMockList;
846
920
  exports.isRecord = isRecord;
847
921
  exports.isRef = isRef;
848
922
  exports.mockServer = mockServer;
923
+ exports.relayStylePaginationMock = relayStylePaginationMock;
package/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { isNullableType, getNullableType, isCompositeType, isAbstractType, isListType, isScalarType, isEnumType, isObjectType, GraphQLString, isInterfaceType, isSchema, isUnionType, GraphQLUnionType, GraphQLInterfaceType, defaultFieldResolver, graphql } from 'graphql';
2
2
  import stringify from 'fast-json-stable-stringify';
3
- import { mapSchema, MapperKind, getRootTypeNames } from '@graphql-tools/utils';
3
+ import { getRootTypeNames, mapSchema, MapperKind } from '@graphql-tools/utils';
4
4
  import { addResolversToSchema, makeExecutableSchema } from '@graphql-tools/schema';
5
5
 
6
6
  function isRef(maybeRef) {
@@ -53,6 +53,10 @@ function copyOwnProps(target, ...sources) {
53
53
  }
54
54
  return target;
55
55
  }
56
+ const isRootType = (type, schema) => {
57
+ const rootTypeNames = getRootTypeNames(schema);
58
+ return rootTypeNames.has(type.name);
59
+ };
56
60
 
57
61
  /**
58
62
  * @internal
@@ -672,12 +676,12 @@ function addMocksToSchema({ schema, store: maybeStore, mocks, typePolicies, reso
672
676
  if (mocks && !isObject(mocks)) {
673
677
  throw new Error('mocks must be of type Object');
674
678
  }
675
- const store = maybeStore ||
676
- createMockStore({
677
- schema,
678
- mocks,
679
- typePolicies,
680
- });
679
+ const mockStore = createMockStore({
680
+ schema,
681
+ mocks,
682
+ typePolicies,
683
+ });
684
+ const store = maybeStore || mockStore;
681
685
  const resolvers = typeof resolversOrFnResolvers === 'function' ? resolversOrFnResolvers(store) : resolversOrFnResolvers;
682
686
  const mockResolver = (source, args, contex, info) => {
683
687
  const defaultResolvedValue = defaultFieldResolver(source, args, contex, info);
@@ -702,6 +706,10 @@ function addMocksToSchema({ schema, store: maybeStore, mocks, typePolicies, reso
702
706
  fieldArgs: args,
703
707
  });
704
708
  }
709
+ if (defaultResolvedValue === undefined) {
710
+ // any is used here because generateValueFromType is a private method at time of writing
711
+ return mockStore.generateValueFromType(info.returnType);
712
+ }
705
713
  return undefined;
706
714
  };
707
715
  const typeResolver = data => {
@@ -791,10 +799,6 @@ function addMocksToSchema({ schema, store: maybeStore, mocks, typePolicies, reso
791
799
  });
792
800
  return resolvers ? addResolversToSchema(schemaWithMocks, resolvers) : schemaWithMocks;
793
801
  }
794
- const isRootType = (type, schema) => {
795
- const rootTypeNames = getRootTypeNames(schema);
796
- return rootTypeNames.has(type.name);
797
- };
798
802
 
799
803
  /**
800
804
  * A convenience wrapper on top of addMocksToSchema. It adds your mock resolvers
@@ -829,4 +833,74 @@ function mockServer(schema, mocks, preserveResolvers = false) {
829
833
  };
830
834
  }
831
835
 
832
- export { MockList, MockStore, addMocksToSchema, assertIsRef, createMockStore, deepResolveMockList, defaultMocks, isMockList, isRecord, isRef, mockServer };
836
+ /**
837
+ * Produces a resolver that'll mock a [Relay-style cursor pagination](https://relay.dev/graphql/connections.htm).
838
+ *
839
+ * ```ts
840
+ * const schemaWithMocks = addMocksToSchema({
841
+ * schema,
842
+ * resolvers: (store) => ({
843
+ * User: {
844
+ * friends: relayStylePaginationMock(store),
845
+ * }
846
+ * }),
847
+ * })
848
+ * ```
849
+ * @param store the MockStore
850
+ */
851
+ const relayStylePaginationMock = (store, { cursorFn = node => `${node.$ref.key}`, applyOnNodes, allNodesFn, } = {}) => {
852
+ return (parent, args, context, info) => {
853
+ const source = isRootType(info.parentType, info.schema) ? makeRef(info.parentType.name, 'ROOT') : parent;
854
+ const allNodesFn_ = allNodesFn !== null && allNodesFn !== void 0 ? allNodesFn : defaultAllNodesFn(store);
855
+ let allNodes = allNodesFn_(source, args, context, info);
856
+ if (applyOnNodes) {
857
+ allNodes = applyOnNodes(allNodes, args);
858
+ }
859
+ const allEdges = allNodes.map(node => ({
860
+ node,
861
+ cursor: cursorFn(node),
862
+ }));
863
+ let start, end;
864
+ const { first, after, last, before } = args;
865
+ if (typeof first === 'number') {
866
+ // forward pagination
867
+ if (last || before) {
868
+ throw new Error("if `first` is provided, `last` or `before` can't be provided");
869
+ }
870
+ const afterIndex = after ? allEdges.findIndex(e => e.cursor === after) : -1;
871
+ start = afterIndex + 1;
872
+ end = afterIndex + 1 + first;
873
+ }
874
+ else if (typeof last === 'number') {
875
+ // backward pagination
876
+ if (first || after) {
877
+ throw new Error("if `last` is provided, `first` or `after` can't be provided");
878
+ }
879
+ const foundBeforeIndex = before ? allEdges.findIndex(e => e.cursor === before) : -1;
880
+ const beforeIndex = foundBeforeIndex !== -1 ? foundBeforeIndex : allNodes.length;
881
+ start = allEdges.length - (allEdges.length - beforeIndex) - last;
882
+ // negative index on Array.slice indicate offset from end of sequence => we don't want
883
+ if (start < 0)
884
+ start = 0;
885
+ end = beforeIndex;
886
+ }
887
+ else {
888
+ throw new Error('A `first` or a `last` arguments should be provided');
889
+ }
890
+ const edges = allEdges.slice(start, end);
891
+ const pageInfo = {
892
+ startCursor: edges.length > 0 ? edges[0].cursor : '',
893
+ endCursor: edges.length > 0 ? edges[edges.length - 1].cursor : '',
894
+ hasNextPage: end < allEdges.length - 1,
895
+ hasPreviousPage: start > 0,
896
+ };
897
+ return {
898
+ edges,
899
+ pageInfo,
900
+ totalCount: allEdges.length,
901
+ };
902
+ };
903
+ };
904
+ const defaultAllNodesFn = (store) => (parent, _, __, info) => store.get(parent, [info.fieldName, 'edges']).map(e => store.get(e, 'node'));
905
+
906
+ export { MockList, MockStore, addMocksToSchema, assertIsRef, createMockStore, deepResolveMockList, defaultMocks, isMockList, isRecord, isRef, mockServer, relayStylePaginationMock };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graphql-tools/mock",
3
- "version": "8.4.2",
3
+ "version": "8.5.0",
4
4
  "description": "A set of utils for faster development of GraphQL tools",
5
5
  "sideEffects": false,
6
6
  "peerDependencies": {
@@ -32,6 +32,7 @@
32
32
  "./*": {
33
33
  "require": "./*.js",
34
34
  "import": "./*.mjs"
35
- }
35
+ },
36
+ "./package.json": "./package.json"
36
37
  }
37
38
  }
@@ -0,0 +1,86 @@
1
+ import { IFieldResolver } from '@graphql-tools/utils';
2
+ import { GraphQLResolveInfo } from 'graphql';
3
+ import { IMockStore, Ref } from './types';
4
+ export declare type AllNodesFn<TContext, TArgs extends RelayPaginationParams> = (parent: Ref, args: TArgs, context: TContext, info: GraphQLResolveInfo) => Ref[];
5
+ export declare type RelayStylePaginationMockOptions<TContext, TArgs extends RelayPaginationParams> = {
6
+ /**
7
+ * Use this option to apply filtering or sorting on the nodes given the
8
+ * arguments the paginated field receives.
9
+ *
10
+ * ```ts
11
+ * {
12
+ * User: {
13
+ * friends: mockedRelayStylePagination<
14
+ * unknown,
15
+ * RelayPaginationParams & { sortByBirthdateDesc?: boolean}
16
+ * >(
17
+ * store, {
18
+ * applyOnEdges: (edges, { sortByBirthdateDesc }) => {
19
+ * if (!sortByBirthdateDesc) return edges
20
+ * return _.sortBy(edges, (e) => store.get(e, ['node', 'birthdate']))
21
+ * }
22
+ * }),
23
+ * }
24
+ * }
25
+ * ```
26
+ */
27
+ applyOnNodes?: (nodeRefs: Ref[], args: TArgs) => Ref[];
28
+ /**
29
+ * A function that'll be used to get all the nodes used for pagination.
30
+ *
31
+ * By default, it will use the nodes of the field this pagination is attached to.
32
+ *
33
+ * This option is handy when several paginable fields should share
34
+ * the same base nodes:
35
+ * ```ts
36
+ * {
37
+ * User: {
38
+ * friends: mockedRelayStylePagination(store),
39
+ * maleFriends: mockedRelayStylePagination(store, {
40
+ * allNodesFn: (userRef) =>
41
+ * store
42
+ * .get(userRef, ['friends', 'edges'])
43
+ * .map((e) => store.get(e, 'node'))
44
+ * .filter((userRef) => store.get(userRef, 'sex') === 'male')
45
+ * })
46
+ * }
47
+ * }
48
+ * ```
49
+ */
50
+ allNodesFn?: AllNodesFn<TContext, TArgs>;
51
+ /**
52
+ * The function that'll be used to compute the cursor of a node.
53
+ *
54
+ * By default, it'll use `MockStore` internal reference `Ref`'s `key`
55
+ * as cursor.
56
+ */
57
+ cursorFn?: (nodeRef: Ref) => string;
58
+ };
59
+ export declare type RelayPaginationParams = {
60
+ first?: number;
61
+ after?: string;
62
+ last?: number;
63
+ before?: string;
64
+ };
65
+ export declare type RelayPageInfo = {
66
+ hasPreviousPage: boolean;
67
+ hasNextPage: boolean;
68
+ startCursor: string;
69
+ endCursor: string;
70
+ };
71
+ /**
72
+ * Produces a resolver that'll mock a [Relay-style cursor pagination](https://relay.dev/graphql/connections.htm).
73
+ *
74
+ * ```ts
75
+ * const schemaWithMocks = addMocksToSchema({
76
+ * schema,
77
+ * resolvers: (store) => ({
78
+ * User: {
79
+ * friends: relayStylePaginationMock(store),
80
+ * }
81
+ * }),
82
+ * })
83
+ * ```
84
+ * @param store the MockStore
85
+ */
86
+ export declare const relayStylePaginationMock: <TContext, TArgs extends RelayPaginationParams = RelayPaginationParams>(store: IMockStore, { cursorFn, applyOnNodes, allNodesFn, }?: RelayStylePaginationMockOptions<TContext, TArgs>) => IFieldResolver<Ref<string>, TContext, TArgs, any>;
package/utils.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { GraphQLObjectType, GraphQLSchema } from 'graphql';
1
2
  import { Ref, KeyTypeConstraints } from './types';
2
3
  export declare function uuidv4(): string;
3
4
  export declare const randomListLength: () => number;
@@ -6,3 +7,4 @@ export declare function makeRef<KeyT extends KeyTypeConstraints = string>(typeNa
6
7
  export declare function isObject(thing: any): boolean;
7
8
  export declare function copyOwnPropsIfNotPresent(target: Record<string, any>, source: Record<string, any>): void;
8
9
  export declare function copyOwnProps(target: Record<string, any>, ...sources: Array<Record<string, any>>): Record<string, any>;
10
+ export declare const isRootType: (type: GraphQLObjectType, schema: GraphQLSchema) => boolean;