graphql-tools
Version:
A set of useful GraphQL tools
297 lines (256 loc) • 11.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.mockServer = exports.MockList = exports.addMockFunctionsToSchema = undefined;
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };
var _type = require('graphql/type');
var _graphql = require('graphql');
var _nodeUuid = require('node-uuid');
var _nodeUuid2 = _interopRequireDefault(_nodeUuid);
var _schemaGenerator = require('./schemaGenerator');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
// This function wraps addMockFunctionsToSchema for more convenience
function mockServer(schema) {
var mocks = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
var preserveResolvers = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2];
var mySchema = schema;
if (!(schema instanceof _type.GraphQLSchema)) {
// TODO: provide useful error messages here if this fails
mySchema = (0, _schemaGenerator.buildSchemaFromTypeDefinitions)(schema);
}
addMockFunctionsToSchema({ schema: mySchema, mocks: mocks, preserveResolvers: preserveResolvers });
return { query: function query(_query, vars) {
return (0, _graphql.graphql)(mySchema, _query, {}, {}, vars);
} };
}
// TODO allow providing a seed such that lengths of list could be deterministic
// this could be done by using casual to get a random list length if the casual
// object is global.
function addMockFunctionsToSchema() {
var _ref = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
var schema = _ref.schema;
var _ref$mocks = _ref.mocks;
var mocks = _ref$mocks === undefined ? {} : _ref$mocks;
var _ref$preserveResolver = _ref.preserveResolvers;
var preserveResolvers = _ref$preserveResolver === undefined ? false : _ref$preserveResolver;
function isObject(thing) {
return thing === Object(thing) && !Array.isArray(thing);
}
if (!schema) {
// XXX should we check that schema is an instance of GraphQLSchema?
throw new Error('Must provide schema to mock');
}
if (!isObject(mocks)) {
throw new Error('mocks must be of type Object');
}
// use Map internally, because that API is nicer.
var mockFunctionMap = new Map();
Object.keys(mocks).forEach(function (typeName) {
mockFunctionMap.set(typeName, mocks[typeName]);
});
mockFunctionMap.forEach(function (mockFunction, mockTypeName) {
if (typeof mockFunction !== 'function') {
throw new Error('mockFunctionMap[' + mockTypeName + '] must be a function');
}
});
var defaultMockMap = new Map();
defaultMockMap.set('Int', function () {
return Math.round(Math.random() * 200) - 100;
});
defaultMockMap.set('Float', function () {
return Math.random() * 200 - 100;
});
defaultMockMap.set('String', function () {
return 'Hello World';
});
defaultMockMap.set('Boolean', function () {
return Math.random() > 0.5;
});
defaultMockMap.set('ID', function () {
return _nodeUuid2.default.v4();
});
function mergeObjects(a, b) {
return Object.assign(a, b);
}
// returns a random element from that ary
function getRandomElement(ary) {
var sample = Math.floor(Math.random() * ary.length);
return ary[sample];
}
// takes either an object or a (possibly nested) array
// and completes the customMock object with any fields
// defined on genericMock
// only merges objects or arrays. Scalars are returned as is
function mergeMocks(genericMockFunction, customMock) {
if (Array.isArray(customMock)) {
return customMock.map(function (el) {
return mergeMocks(genericMockFunction, el);
});
}
if (isObject(customMock)) {
return mergeObjects(genericMockFunction(), customMock);
}
return customMock;
}
var mockType = function mockType(type, typeName, fieldName) {
// order of precendence for mocking:
// 1. if the object passed in already has fieldName, just use that
// --> if it's a function, that becomes your resolver
// --> if it's a value, the mock resolver will return that
// 2. if the nullableType is a list, recurse
// 2. if there's a mock defined for this typeName, that will be used
// 3. if there's no mock defined, use the default mocks for this type
return function () {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
var root = args[0];
var queryArgs = args[1];
var context = args[2];
var info = args[3];
// nullability doesn't matter for the purpose of mocking.
var fieldType = (0, _type.getNullableType)(type);
var namedFieldType = (0, _type.getNamedType)(fieldType);
if (root && typeof root[fieldName] !== 'undefined') {
var result = void 0;
// if we're here, the field is already defined
if (typeof root[fieldName] === 'function') {
result = root[fieldName].apply(root, args);
if (result instanceof MockList) {
var _result;
result = (_result = result).mock.apply(_result, args.concat([fieldType, mockType]));
}
} else {
result = root[fieldName];
}
// Now we merge the result with the default mock for this type.
// This allows overriding defaults while writing very little code.
if (mockFunctionMap.has(namedFieldType.name)) {
var _mockFunctionMap$get;
result = mergeMocks((_mockFunctionMap$get = mockFunctionMap.get(namedFieldType.name)).bind.apply(_mockFunctionMap$get, [null].concat(args)), result);
}
return result;
}
if (fieldType instanceof _type.GraphQLList) {
return [mockType(fieldType.ofType).apply(undefined, args), mockType(fieldType.ofType).apply(undefined, args)];
}
if (mockFunctionMap.has(fieldType.name)) {
// the object passed doesn't have this field, so we apply the default mock
return mockFunctionMap.get(fieldType.name).apply(undefined, args);
}
if (fieldType instanceof _type.GraphQLObjectType) {
// objects don't return actual data, we only need to mock scalars!
return {};
}
// TODO mocking Interface and Union types will require determining the
// resolve type before passing it on.
// XXX we recommend a generic way for resolve type here, which is defining
// typename on the object.
if (fieldType instanceof _type.GraphQLUnionType) {
// TODO: if a union type doesn't implement resolveType, we could overwrite
// it with a default that works with what's below.
return { typename: getRandomElement(fieldType.getTypes()) };
}
if (fieldType instanceof _type.GraphQLInterfaceType) {
var possibleTypes = schema.getPossibleTypes(fieldType);
return { typename: getRandomElement(possibleTypes) };
}
if (fieldType instanceof _type.GraphQLEnumType) {
return getRandomElement(fieldType.getValues()).value;
}
if (defaultMockMap.has(fieldType.name)) {
return defaultMockMap.get(fieldType.name).apply(undefined, args);
}
// if we get to here, we don't have a value, and we don't have a mock for this type,
// we could return undefined, but that would be hard to debug, so we throw instead.
throw new Error('No mock defined for type "' + fieldType.name + '"');
};
};
(0, _schemaGenerator.forEachField)(schema, function (field, typeName, fieldName) {
if (preserveResolvers && field.resolve) {
return;
}
// we have to handle the root mutation and root query types differently,
// because no resolver is called at the root.
var isOnQueryType = typeName === (schema.getQueryType() || {}).name;
var isOnMutationType = typeName === (schema.getMutationType() || {}).name;
if (isOnQueryType || isOnMutationType) {
if (mockFunctionMap.has(typeName)) {
var _ret = function () {
var rootMock = mockFunctionMap.get(typeName);
if (rootMock()[fieldName]) {
// TODO: assert that it's a function
// eslint-disable-next-line no-param-reassign
field.resolve = function (root) {
for (var _len2 = arguments.length, rest = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
rest[_key2 - 1] = arguments[_key2];
}
var updatedRoot = root || {}; // TODO: should we clone instead?
updatedRoot[fieldName] = rootMock()[fieldName];
// XXX this is a bit of a hack to still use mockType, which
// lets you mock lists etc. as well
// otherwise we could just set field.resolve to rootMock()[fieldName]
// it's like pretending there was a resolve function that ran before
// the root resolve function.
return mockType(field.type, typeName, fieldName).apply(undefined, [updatedRoot].concat(rest));
};
return {
v: void 0
};
}
}();
if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v;
}
}
// eslint-disable-next-line no-param-reassign
field.resolve = mockType(field.type, typeName, fieldName);
});
}
var MockList = function () {
// wrappedFunction can return another MockList or a value
function MockList(len, wrappedFunction) {
_classCallCheck(this, MockList);
this.len = len;
if (typeof wrappedFunction !== 'undefined') {
if (typeof wrappedFunction !== 'function') {
throw new Error('Second argument to MockList must be a function or undefined');
}
this.wrappedFunction = wrappedFunction;
}
}
_createClass(MockList, [{
key: 'mock',
value: function mock(root, args, context, info, fieldType, mockTypeFunc) {
function randint(low, high) {
return Math.floor(Math.random() * (high - low + 1) + low);
}
var arr = void 0;
if (Array.isArray(this.len)) {
arr = new Array(randint(this.len[0], this.len[1]));
} else {
arr = new Array(this.len);
}
for (var i = 0; i < arr.length; i++) {
if (typeof this.wrappedFunction === 'function') {
var res = this.wrappedFunction(root, args, context, info);
if (res instanceof MockList) {
var nullableType = (0, _type.getNullableType)(fieldType.ofType);
arr[i] = res.mock(root, args, context, info, nullableType, mockTypeFunc);
} else {
arr[i] = res;
}
} else {
arr[i] = mockTypeFunc(fieldType.ofType)(root, args, context, info);
}
}
return arr;
}
}]);
return MockList;
}();
exports.addMockFunctionsToSchema = addMockFunctionsToSchema;
exports.MockList = MockList;
exports.mockServer = mockServer;