diff --git a/packages/plugins/typescript/operations/tests/ts-documents.spec.ts b/packages/plugins/typescript/operations/tests/ts-documents.spec.ts index 6ece8a0ce3a..377fe0855e8 100644 --- a/packages/plugins/typescript/operations/tests/ts-documents.spec.ts +++ b/packages/plugins/typescript/operations/tests/ts-documents.spec.ts @@ -10,6 +10,10 @@ describe('TypeScript Operations Plugin', () => { const schema = buildSchema(/* GraphQL */ ` scalar DateTime + input InputType { + t: String + } + type User { id: ID! username: String! @@ -90,10 +94,10 @@ describe('TypeScript Operations Plugin', () => { config: any = {}, pluginSchema = schema, usage = '', - openPlayground = false + suspenseErrors = [] ) => { const m = mergeOutputs([await tsPlugin(pluginSchema, [], config, { outputFile: '' }), content, usage]); - await validateTs(m, null, null, null, openPlayground); + validateTs(m, undefined, undefined, undefined, suspenseErrors); return m; }; @@ -173,7 +177,7 @@ describe('TypeScript Operations Plugin', () => { )> } ); `); - await validate(content, config); + await validate(content, config, schema, '', [`Cannot find namespace 'Types'.`]); }); it('Should handle "namespacedImportName" and "preResolveTypes" together', async () => { @@ -215,7 +219,7 @@ describe('TypeScript Operations Plugin', () => { `export type TestQuery = { __typename?: 'Query', f?: Types.Maybe, user: { __typename?: 'User', id: string, f?: Types.Maybe, j?: Types.Maybe } };` ); - await validate(content, config); + await validate(content, config, schema, '', [`Cannot find namespace 'Types'.`]); }); it('Should generate the correct output when using immutableTypes config', async () => { @@ -1850,7 +1854,7 @@ describe('TypeScript Operations Plugin', () => { ) } ); `); - await validate(content, config); + await validate(content, config, schema); }); it('Should generate the correct intersection for fragments when using with interfaces with same type', async () => { @@ -2456,7 +2460,7 @@ describe('TypeScript Operations Plugin', () => { innerRequired: Array | Scalars['String']; }>;` ); - await validate(content, config); + await validate(content, config, schema); }); it('Should handle operation variables correctly when they use custom scalars', async () => { @@ -3919,6 +3923,70 @@ describe('TypeScript Operations Plugin', () => { }); describe('Issues', () => { + it('#4212 - Should merge TS arrays in a more elegant way', async () => { + const testSchema = buildSchema(/* GraphQL */ ` + type Item { + id: ID! + name: String! + } + + type Object { + items: [Item!]! + } + + type Query { + obj: Object + } + `); + + const query = parse(/* GraphQL */ ` + fragment Object1 on Object { + items { + id + } + } + + fragment Object2 on Object { + items { + name + } + } + + fragment CombinedObject on Object { + ...Object1 + ...Object2 + } + + query test { + obj { + ...CombinedObject + } + } + `); + + const { content } = await plugin( + testSchema, + [{ location: '', document: query }], + {}, + { + outputFile: 'graphql.ts', + } + ); + + await validate( + content, + {}, + testSchema, + ` + function test (t: TestQuery) { + for (const item of t.obj!.items) { + console.log(item.id, item.name, item.__typename); + } + } + ` + ); + }); + it('#5422 - Error when interface doesnt have implemeting types', async () => { const testSchema = buildSchema(/* GraphQL */ ` interface A { diff --git a/packages/plugins/typescript/react-apollo/tests/react-apollo.spec.ts b/packages/plugins/typescript/react-apollo/tests/react-apollo.spec.ts index df79a09e73d..57114e59b4d 100644 --- a/packages/plugins/typescript/react-apollo/tests/react-apollo.spec.ts +++ b/packages/plugins/typescript/react-apollo/tests/react-apollo.spec.ts @@ -56,13 +56,12 @@ describe('React Apollo', () => { output: Types.PluginOutput, testSchema: GraphQLSchema, documents: Types.DocumentFile[], - config: any, - playground = false + config: any ) => { const tsOutput = await tsPlugin(testSchema, documents, config, { outputFile: '' }); const tsDocumentsOutput = await tsDocumentsPlugin(testSchema, documents, config, { outputFile: '' }); const merged = mergeOutputs([tsOutput, tsDocumentsOutput, output]); - validateTs(merged, undefined, true, false, playground); + validateTs(merged, undefined, true, false); return merged; }; diff --git a/packages/plugins/typescript/react-query/tests/react-query.spec.ts b/packages/plugins/typescript/react-query/tests/react-query.spec.ts index 350938b1737..162d87dd573 100644 --- a/packages/plugins/typescript/react-query/tests/react-query.spec.ts +++ b/packages/plugins/typescript/react-query/tests/react-query.spec.ts @@ -9,13 +9,12 @@ const validateTypeScript = async ( output: Types.PluginOutput, testSchema: GraphQLSchema, documents: Types.DocumentFile[], - config: any, - playground = false + config: any ) => { const tsOutput = await tsPlugin(testSchema, documents, config, { outputFile: '' }); const tsDocumentsOutput = await tsDocumentsPlugin(testSchema, documents, config, { outputFile: '' }); const merged = mergeOutputs([tsOutput, tsDocumentsOutput, output]); - validateTs(merged, undefined, true, false, playground); + validateTs(merged, undefined, true, false); return merged; }; @@ -118,7 +117,7 @@ describe('React-Query', () => { );`); expect(out.content).toMatchSnapshot(); - await validateTypeScript(mergeOutputs(out), schema, docs, config, false); + await validateTypeScript(mergeOutputs(out), schema, docs, config); }); it('Should generate query correctly with internal mapper', async () => { @@ -155,7 +154,7 @@ describe('React-Query', () => { );`); expect(out.content).toMatchSnapshot(); - await validateTypeScript(mergeOutputs(out), schema, docs, config, false); + await validateTypeScript(mergeOutputs(out), schema, docs, config); }); it('Should generate mutation correctly with lazy variables', async () => { @@ -195,7 +194,7 @@ describe('React-Query', () => { );`); expect(out.content).toMatchSnapshot(); - await validateTypeScript(mergeOutputs(out), schema, docs, config, false); + await validateTypeScript(mergeOutputs(out), schema, docs, config); }); }); @@ -242,7 +241,7 @@ describe('React-Query', () => { );`); expect(out.content).toMatchSnapshot(); - await validateTypeScript(mergeOutputs(out), schema, docs, config, false); + await validateTypeScript(mergeOutputs(out), schema, docs, config); }); it('Should support useTypeImports', async () => { const config = { @@ -312,7 +311,7 @@ describe('React-Query', () => { );`); expect(out.content).toMatchSnapshot(); - await validateTypeScript(mergeOutputs(out), schema, docs, config, false); + await validateTypeScript(mergeOutputs(out), schema, docs, config); }); it('Should generate query correctly with fetch config', async () => { @@ -352,7 +351,7 @@ describe('React-Query', () => { }`); expect(out.content).toMatchSnapshot(); - await validateTypeScript(mergeOutputs(out), schema, docs, config, false); + await validateTypeScript(mergeOutputs(out), schema, docs, config); }); it('Should generate query correctly with hardcoded endpoint from env var', async () => { @@ -386,7 +385,7 @@ describe('React-Query', () => { }`); expect(out.content).toMatchSnapshot(); - await validateTypeScript(mergeOutputs(out), schema, docs, config, false); + await validateTypeScript(mergeOutputs(out), schema, docs, config); }); it('Should generate query correctly with hardcoded endpoint from just identifier', async () => { @@ -420,7 +419,7 @@ describe('React-Query', () => { }`); expect(out.content).toMatchSnapshot(); - await validateTypeScript(mergeOutputs(out), schema, docs, config, false); + await validateTypeScript(mergeOutputs(out), schema, docs, config); }); }); @@ -464,7 +463,7 @@ describe('React-Query', () => { );`); expect(out.content).toMatchSnapshot(); - await validateTypeScript(mergeOutputs(out), schema, docs, config, false); + await validateTypeScript(mergeOutputs(out), schema, docs, config); }); }); diff --git a/packages/plugins/typescript/vue-apollo/tests/vue-apollo.spec.ts b/packages/plugins/typescript/vue-apollo/tests/vue-apollo.spec.ts index edc65d8cfe3..75f9e8e4262 100644 --- a/packages/plugins/typescript/vue-apollo/tests/vue-apollo.spec.ts +++ b/packages/plugins/typescript/vue-apollo/tests/vue-apollo.spec.ts @@ -56,13 +56,12 @@ describe('Vue Apollo', () => { output: Types.PluginOutput, testSchema: GraphQLSchema, documents: Types.DocumentFile[], - config: any, - playground = false + config: any ) => { const tsOutput = await tsPlugin(testSchema, documents, config, { outputFile: '' }); const tsDocumentsOutput = await tsDocumentsPlugin(testSchema, documents, config, { outputFile: '' }); const merged = mergeOutputs([tsOutput, tsDocumentsOutput, output]); - validateTs(merged, undefined, true, false, playground); + validateTs(merged, undefined, true, false); return merged; }; diff --git a/packages/utils/graphql-codegen-testing/package.json b/packages/utils/graphql-codegen-testing/package.json index b3eb426a585..874ed919a7f 100644 --- a/packages/utils/graphql-codegen-testing/package.json +++ b/packages/utils/graphql-codegen-testing/package.json @@ -32,7 +32,6 @@ "lz-string": "^1.4.4", "graphql-helix": "1.2.3", "nock": "13.0.10", - "open": "^7.3.0", "tslib": "~2.1.0" }, "publishConfig": { diff --git a/packages/utils/graphql-codegen-testing/src/typescript.ts b/packages/utils/graphql-codegen-testing/src/typescript.ts index f65a659ae58..23a1bb516a1 100644 --- a/packages/utils/graphql-codegen-testing/src/typescript.ts +++ b/packages/utils/graphql-codegen-testing/src/typescript.ts @@ -6,14 +6,11 @@ import { JsxEmit, ModuleKind, createSourceFile, - ScriptKind, flattenDiagnosticMessageText, createCompilerHost, createProgram, - Diagnostic, } from 'typescript'; import { resolve, join, dirname } from 'path'; -import open from 'open'; const { compressToEncodedURIComponent } = require('lz-string'); @@ -27,21 +24,22 @@ export function validateTs( emitDecoratorMetadata: true, target: ScriptTarget.ES5, typeRoots: [resolve(require.resolve('typescript'), '../../../@types/')], - jsx: JsxEmit.Preserve, + jsx: JsxEmit.React, allowJs: true, + skipLibCheck: true, lib: [ join(dirname(require.resolve('typescript')), 'lib.es5.d.ts'), join(dirname(require.resolve('typescript')), 'lib.es6.d.ts'), join(dirname(require.resolve('typescript')), 'lib.dom.d.ts'), join(dirname(require.resolve('typescript')), 'lib.scripthost.d.ts'), join(dirname(require.resolve('typescript')), 'lib.es2015.d.ts'), - join(dirname(require.resolve('typescript')), 'lib.esnext.asynciterable.d.ts'), + join(dirname(require.resolve('typescript')), 'lib.esnext.d.ts'), ], module: ModuleKind.ESNext, }, isTsx = false, isStrict = false, - openPlayground = false + suspenseErrors: string[] = [] ): void { if (process.env.SKIP_VALIDATION) { return; @@ -60,45 +58,68 @@ export function validateTs( ? pluginOutput : [...(pluginOutput.prepend || []), pluginOutput.content, ...(pluginOutput.append || [])].join('\n'); - try { - const testFile = `test-file.${isTsx ? 'tsx' : 'ts'}`; - const result = createSourceFile( - testFile, - contents, - ScriptTarget.ES2016, - false, - isTsx ? ScriptKind.TSX : undefined - ) as { parseDiagnostics?: Diagnostic[] }; - - const allDiagnostics = result.parseDiagnostics; - - if (allDiagnostics && allDiagnostics.length > 0) { - const errors: string[] = []; - - allDiagnostics.forEach(diagnostic => { - if (diagnostic.file) { - const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!); - const message = flattenDiagnosticMessageText(diagnostic.messageText, '\n'); - errors.push(`${line + 1},${character + 1}: ${message} -> - ${contents.split('\n')[line]}`); - } else { - errors.push(`${flattenDiagnosticMessageText(diagnostic.messageText, '\n')}`); - } + const testFile = `test-file.${isTsx ? 'tsx' : 'ts'}`; + + const host = createCompilerHost(options); + const program = createProgram([testFile], options, { + ...host, + getSourceFile: ( + fileName: string, + languageVersion: ScriptTarget, + onError?: (message: string) => void, + shouldCreateNewSourceFile?: boolean + ) => { + if (fileName === testFile) { + return createSourceFile(fileName, contents, options.target); + } - const relevantErrors = errors.filter(e => !e.includes('Cannot find module')); + return host.getSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile); + }, + writeFile: function () {}, + useCaseSensitiveFileNames: function () { + return false; + }, + getCanonicalFileName: function (filename) { + return filename; + }, + getCurrentDirectory: function () { + return ''; + }, + getNewLine: function () { + return '\n'; + }, + }); + const emitResult = program.emit(); + const allDiagnostics = emitResult.diagnostics; + const errors: string[] = []; + + allDiagnostics.forEach(diagnostic => { + if (diagnostic.file) { + const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start); + const message = flattenDiagnosticMessageText(diagnostic.messageText, '\n'); + errors.push(`${line + 1},${character + 1}: ${message} -> + ${contents.split('\n')[line]}`); + } else { + errors.push(`${flattenDiagnosticMessageText(diagnostic.messageText, '\n')}`); + } + }); - if (relevantErrors && relevantErrors.length > 0) { - throw new Error(relevantErrors.join('\n')); - } - }); + const relevantErrors = errors.filter(e => { + if (e.includes('Cannot find module')) { + return false; } - } catch (e) { - if (openPlayground && !process.env) { - const compressedCode = compressToEncodedURIComponent(contents); - open('http://www.typescriptlang.org/play/#code/' + compressedCode); + + for (const suspenseError of suspenseErrors) { + if (e.includes(suspenseError)) { + return false; + } } - throw e; + return true; + }); + + if (relevantErrors && relevantErrors.length > 0) { + throw new Error(relevantErrors.join('\n')); } } diff --git a/packages/utils/graphql-codegen-testing/src/typings.d.ts b/packages/utils/graphql-codegen-testing/src/typings.d.ts index c7b12fbdc00..45082e2959c 100644 --- a/packages/utils/graphql-codegen-testing/src/typings.d.ts +++ b/packages/utils/graphql-codegen-testing/src/typings.d.ts @@ -1,2 +1 @@ -declare module 'open'; declare module 'lz-string';