Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

language support for @oneOf #3769

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/graphiql-react/src/schema.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,7 @@ function useIntrospectionQuery({
let query = getIntrospectionQuery({
inputValueDeprecation,
schemaDescription,
oneOf: true,
});
if (introspectionQueryName) {
query = query.replace('query IntrospectionQuery', `query ${queryName}`);
Expand Down
2 changes: 1 addition & 1 deletion packages/graphiql/cypress/e2e/docs.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ describe('GraphiQL DocExplorer - search', () => {
beforeEach(() => {
cy.get('.graphiql-sidebar button').eq(0).click();
cy.dataCy('doc-explorer-input').type('test');
cy.dataCy('doc-explorer-option').should('have.length', 7);
cy.dataCy('doc-explorer-option').should('have.length', 8);
});

it('Searches docs for values', () => {
Expand Down
80 changes: 48 additions & 32 deletions packages/graphiql/test/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,40 +50,54 @@ const TestEnum = new GraphQLEnumType({
const TestInputObject = new GraphQLInputObjectType({
name: 'TestInput',
description: 'Test all sorts of inputs in this input object type.',
fields: () => ({
string: {
type: GraphQLString,
description: 'Repeats back this string',
},
int: { type: GraphQLInt },
float: { type: GraphQLFloat },
boolean: { type: GraphQLBoolean },
id: { type: GraphQLID },
enum: { type: TestEnum },
object: { type: TestInputObject },
defaultValueString: {
type: GraphQLString,
defaultValue: 'test default value',
},
defaultValueBoolean: {
type: GraphQLBoolean,
defaultValue: false,
},
defaultValueInt: {
type: GraphQLInt,
defaultValue: 5,
},
// List
listString: { type: new GraphQLList(GraphQLString) },
listInt: { type: new GraphQLList(GraphQLInt) },
listFloat: { type: new GraphQLList(GraphQLFloat) },
listBoolean: { type: new GraphQLList(GraphQLBoolean) },
listID: { type: new GraphQLList(GraphQLID) },
listEnum: { type: new GraphQLList(TestEnum) },
listObject: { type: new GraphQLList(TestInputObject) },
}),
fields: () => inputFields,
});

const TestOneOfInputObject = new GraphQLInputObjectType({
name: 'TestOneOfInput',
description: 'Test @oneOf input types with this input object type.',
isOneOf: true,
fields: () =>
// remove defaultValue which is not compatible with @oneOf
Object.entries(inputFields).reduce((a, [k, { defaultValue, ...value }]) => {
a[k] = value;
return a;
}, {}),
});

const inputFields = {
string: {
type: GraphQLString,
description: 'Repeats back this string',
},
int: { type: GraphQLInt },
float: { type: GraphQLFloat },
boolean: { type: GraphQLBoolean },
id: { type: GraphQLID },
enum: { type: TestEnum },
object: { type: TestInputObject },
defaultValueString: {
type: GraphQLString,
defaultValue: 'test default value',
},
defaultValueBoolean: {
type: GraphQLBoolean,
defaultValue: false,
},
defaultValueInt: {
type: GraphQLInt,
defaultValue: 5,
},
// List
listString: { type: new GraphQLList(GraphQLString) },
listInt: { type: new GraphQLList(GraphQLInt) },
listFloat: { type: new GraphQLList(GraphQLFloat) },
listBoolean: { type: new GraphQLList(GraphQLBoolean) },
listID: { type: new GraphQLList(GraphQLID) },
listEnum: { type: new GraphQLList(TestEnum) },
listObject: { type: new GraphQLList(TestInputObject) },
};

const TestInterface = new GraphQLInterfaceType({
name: 'TestInterface',
description: 'Test interface.',
Expand Down Expand Up @@ -250,6 +264,7 @@ const TestType = new GraphQLObjectType({
description: '`test` field from `Test` type.',
resolve: () => ({}),
},

deferrable: {
type: DeferrableObject,
resolve: () => ({}),
Expand Down Expand Up @@ -333,6 +348,7 @@ const TestType = new GraphQLObjectType({
id: { type: GraphQLID },
enum: { type: TestEnum },
object: { type: TestInputObject },
oneOfObject: { type: TestOneOfInputObject },
defaultValue: {
type: GraphQLString,
defaultValue: 'test default value',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ describe('MessageProcessor with config', () => {
character: 0,
},
end: {
line: 102 + offset,
line: 103 + offset,
character: 1,
},
});
Expand All @@ -450,11 +450,11 @@ describe('MessageProcessor with config', () => {
// this might break, please adjust if you see a failure here
expect(serializeRange(schemaDefs[0].range)).toEqual({
start: {
line: 104 + offset,
line: 105 + offset,
character: 0,
},
end: {
line: 112 + offset,
line: 113 + offset,
character: 1,
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ input InputType {
obj: InputType
}

input OneOfInputType @oneOf {
key: String
value: Int
obj: InputType
}

interface TestInterface {
"""
example
Expand All @@ -69,6 +75,7 @@ type Query {
inputTypeTest(args: InputType = { key: "key" }): TestType
deprecatedField: TestType @deprecated(reason: "Use test instead.")
union: TestUnion
oneOfInputTypeTest(oneOf: OneOfInputType): String
}

union TestUnion = Droid | TestType
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ const expectedResults = {
label: 'inputTypeTest',
detail: 'TestType',
},
oneOfInputTypeTest: {
detail: 'String',
label: 'oneOfInputTypeTest',
},
appearsIn: {
label: 'appearsIn',
detail: '[Episode]',
Expand Down Expand Up @@ -163,7 +167,7 @@ describe('getAutocompleteSuggestions', () => {
},

{
sortText: '7__schema',
sortText: '8__schema',
label: '__schema',
detail: '__Schema!',
},
Expand Down Expand Up @@ -213,6 +217,7 @@ describe('getAutocompleteSuggestions', () => {
expectedResults.hero,
expectedResults.human,
expectedResults.inputTypeTest,
expectedResults.oneOfInputTypeTest,
expectedResults.union,
]);

Expand All @@ -236,6 +241,7 @@ describe('getAutocompleteSuggestions', () => {
expectedResults.hero,
expectedResults.human,
expectedResults.inputTypeTest,
expectedResults.oneOfInputTypeTest,
expectedResults.union,
]);
});
Expand All @@ -250,6 +256,7 @@ describe('getAutocompleteSuggestions', () => {
expectedResults.hero,
expectedResults.human,
expectedResults.inputTypeTest,
expectedResults.oneOfInputTypeTest,
expectedResults.union,
]);
});
Expand Down Expand Up @@ -322,6 +329,13 @@ describe('getAutocompleteSuggestions', () => {
insertText: 'inputTypeTest {\n $1\n}',
labelDetails: { detail: ' TestType' },
},
{
...expectedResults.oneOfInputTypeTest,
command: suggestionCommand,
insertTextFormat: 2,
insertText: 'oneOfInputTypeTest\n',
labelDetails: { detail: ' String' },
},
{
label: 'union',
insertTextFormat: 2,
Expand Down Expand Up @@ -419,7 +433,9 @@ describe('getAutocompleteSuggestions', () => {
{ label: 'Boolean', documentation: GraphQLBoolean.description },
{ label: 'Episode' },
{ label: 'InputType' },

{ label: 'Int', documentation: GraphQLInt.description },
{ label: 'OneOfInputType' },
{ label: 'String', documentation: GraphQLString.description },
]);
});
Expand All @@ -433,7 +449,9 @@ describe('getAutocompleteSuggestions', () => {
...metaArgs,

{ label: 'InputType' },

{ label: 'Int', documentation: GraphQLInt.description },
{ label: 'OneOfInputType' },
{ label: 'String', documentation: GraphQLString.description },
]);
});
Expand Down Expand Up @@ -654,7 +672,7 @@ describe('getAutocompleteSuggestions', () => {
];
it('provides correct testInput type field suggestions', () => {
expect(
testSuggestions('{ inputTypeTest(args: {', new Position(0, 23)),
testSuggestions('{ inputTypeTest(args: { ', new Position(0, 24)),
).toEqual(inputArgs);
});

Expand All @@ -664,6 +682,37 @@ describe('getAutocompleteSuggestions', () => {
).toEqual(inputArgs);
});

it('provides correct oneOf input type field suggestions', () => {
const args = [
{ ...inputArgs[0], detail: 'String' },
inputArgs[1],
inputArgs[2],
];
expect(
testSuggestions('{ oneOfInputTypeTest(oneOf: {', new Position(0, 29)),
).toEqual(args);
});

it('provides no more field suggestions once a oneOf field is chosen', () => {
expect(
testSuggestions(
'{ oneOfInputTypeTest(oneOf: { value: 2 ',
new Position(0, 40),
),
).toEqual([]);
});

// TODO: decide if we want this. Discussing with @benjie, we might want to actually give the user flexibility here,
// instead of being strict
it('provides no more field suggestions once a oneOf field is chose and a user begins typing another field', () => {
expect(
testSuggestions(
'{ oneOfInputTypeTest(oneOf: { value: 2 d',
new Position(0, 40),
),
).toEqual([]);
});

it('provides correct field name suggestion inside inline fragment', () => {
expect(
testSuggestions(
Expand Down Expand Up @@ -813,6 +862,7 @@ describe('getAutocompleteSuggestions', () => {
it('provides input objects to be extended', () => {
expect(testSuggestions('extend input ', new Position(0, 13))).toEqual([
{ label: 'InputType' },
{ label: 'OneOfInputType' },
]);
});

Expand Down Expand Up @@ -847,8 +897,10 @@ describe('getAutocompleteSuggestions', () => {
).toEqual([
{ label: 'Boolean' },
{ label: 'Episode' },

{ label: 'InputType' },
{ label: 'Int' },
{ label: 'OneOfInputType' },
{ label: 'String' },
]));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,11 +312,23 @@ export function getAutocompleteSuggestions(
(kind === RuleKinds.OBJECT_FIELD && step === 0)) &&
typeInfo.objectFieldDefs
) {
const objectFields = objectValues(typeInfo.objectFieldDefs);
const { inputType, objectFieldDefs } = typeInfo;
const { string: tokenString } = token;
if (
inputType &&
'isOneOf' in inputType &&
inputType?.isOneOf === true &&
(prevState?.prevState?.kind !== 'Argument' || tokenString !== '{')
) {
// return empty array early if a oneOf field has already been provided
return [];
}
const objectFields = objectValues(objectFieldDefs);
const completionKind =
kind === RuleKinds.OBJECT_VALUE
? CompletionItemKind.Value
: CompletionItemKind.Field;

return hintList(
token,
objectFields.map(field => ({
Expand Down
10 changes: 8 additions & 2 deletions packages/graphql-language-service/src/parser/getTypeInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,11 @@ export function getTypeInfo(
}
}
}
inputType = argDef?.type;
if (argDef?.type) {
inputType = argDef.type;
}
break;

case RuleKinds.VARIABLE_DEFINITION:
case RuleKinds.VARIABLE:
type = inputType;
Expand All @@ -241,12 +244,15 @@ export function getTypeInfo(
objectType instanceof GraphQLInputObjectType
? objectType.getFields()
: null;
inputType = objectType;
break;
// TODO: needs tests
case RuleKinds.OBJECT_FIELD:
const objectField =
state.name && objectFieldDefs ? objectFieldDefs[state.name] : null;
inputType = objectField?.type;
if (objectField?.type) {
inputType = objectField?.type;
}
// @ts-expect-error
fieldDef = objectField as GraphQLField<null, null>;
type = fieldDef ? fieldDef.type : null;
Expand Down
7 changes: 6 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10538,7 +10538,12 @@ [email protected], graphql-ws@^5.5.5:
resolved "https://registry.yarnpkg.com/graphql-ws/-/graphql-ws-5.14.0.tgz#766f249f3974fc2c48fae0d1fb20c2c4c79cd591"
integrity sha512-itrUTQZP/TgswR4GSSYuwWUzrE/w5GhbwM2GX3ic2U7aw33jgEsayfIlvaj7/GcIvZgNMzsPTrE5hqPuFUiE5g==

"graphql@^16.8.1 || ^17.0.0-alpha.2", graphql@^16.9.0:
"graphql@^16.8.1 || ^17.0.0-alpha.2":
version "17.0.0-alpha.7"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-17.0.0-alpha.7.tgz#707e7457d7ed5316a8d7940f78809a2eb5854383"
integrity sha512-kdteHez9s0lfNAGntSwnDBpxSl09sBWEFxFRPS/Z8K1nCD4FZ2wVGwXuj5dvrTKcqOA+O8ujAJ3CiY/jXhs14g==

graphql@^16.9.0:
version "16.9.0"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.9.0.tgz#1c310e63f16a49ce1fbb230bd0a000e99f6f115f"
integrity sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==
Expand Down
Loading