├── .eslintignore
├── .eslintrc
├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── .vscode
└── launch.json
├── CHANGELOG.md
├── README.md
├── jest.config.js
├── package-lock.json
├── package.json
├── src
├── SuperTestGraphQL.ts
├── SuperTestWSGraphQL.ts
├── index.ts
├── supertest-graphql.test.ts
├── types.ts
└── utils.ts
└── tsconfig.json
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "parser": "@typescript-eslint/parser",
4 | "plugins": [
5 | "@typescript-eslint",
6 | "prettier"
7 | ],
8 | "extends": [
9 | "eslint:recommended",
10 | "plugin:@typescript-eslint/eslint-recommended",
11 | "plugin:@typescript-eslint/recommended",
12 | "prettier"
13 | ],
14 | "rules": {
15 | "prettier/prettier": "error"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: CI
5 |
6 | on:
7 | push:
8 | branches: [ master ]
9 | pull_request:
10 | branches: [ master ]
11 |
12 | jobs:
13 | test:
14 | runs-on: ubuntu-latest
15 |
16 | strategy:
17 | matrix:
18 | node-version: [12.x, 14.x, 16.x]
19 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
20 |
21 | steps:
22 | - uses: actions/checkout@v2
23 | - name: Use Node.js ${{ matrix.node-version }}
24 | uses: actions/setup-node@v2
25 | with:
26 | node-version: ${{ matrix.node-version }}
27 | cache: 'npm'
28 | - run: npm ci
29 | - run: npm run check-types
30 | - run: npm run lint
31 | - run: npm test
32 | shipit:
33 | runs-on: ubuntu-latest
34 | steps:
35 | - uses: actions/checkout@v2
36 | - uses: actions/setup-node@v2
37 | with:
38 | node-version: ${{ matrix.node-version }}
39 | cache: 'npm'
40 | - name: Prepare repository
41 | run: git fetch --unshallow --tags
42 | - run: npm ci
43 | - run: npm run release
44 | env:
45 | GH_TOKEN: ${{ secrets.GH_TOKEN }}
46 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
47 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 | .pnpm-debug.log*
9 |
10 | # Diagnostic reports (https://nodejs.org/api/report.html)
11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
12 |
13 | # Runtime data
14 | pids
15 | *.pid
16 | *.seed
17 | *.pid.lock
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 | *.lcov
25 |
26 | # nyc test coverage
27 | .nyc_output
28 |
29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
30 | .grunt
31 |
32 | # Bower dependency directory (https://bower.io/)
33 | bower_components
34 |
35 | # node-waf configuration
36 | .lock-wscript
37 |
38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
39 | build/Release
40 |
41 | # Dependency directories
42 | node_modules/
43 | jspm_packages/
44 |
45 | # Snowpack dependency directory (https://snowpack.dev/)
46 | web_modules/
47 |
48 | # TypeScript cache
49 | *.tsbuildinfo
50 |
51 | # Optional npm cache directory
52 | .npm
53 |
54 | # Optional eslint cache
55 | .eslintcache
56 |
57 | # Optional stylelint cache
58 | .stylelintcache
59 |
60 | # Microbundle cache
61 | .rpt2_cache/
62 | .rts2_cache_cjs/
63 | .rts2_cache_es/
64 | .rts2_cache_umd/
65 |
66 | # Optional REPL history
67 | .node_repl_history
68 |
69 | # Output of 'npm pack'
70 | *.tgz
71 |
72 | # Yarn Integrity file
73 | .yarn-integrity
74 |
75 | # dotenv environment variable files
76 | .env
77 | .env.development.local
78 | .env.test.local
79 | .env.production.local
80 | .env.local
81 |
82 | # parcel-bundler cache (https://parceljs.org/)
83 | .cache
84 | .parcel-cache
85 |
86 | # Next.js build output
87 | .next
88 | out
89 |
90 | # Nuxt.js build / generate output
91 | .nuxt
92 | dist
93 |
94 | # Gatsby files
95 | .cache/
96 | # Comment in the public line in if your project uses Gatsby and not Next.js
97 | # https://nextjs.org/blog/next-9-1#public-directory-support
98 | # public
99 |
100 | # vuepress build output
101 | .vuepress/dist
102 |
103 | # vuepress v2.x temp and cache directory
104 | .temp
105 | .cache
106 |
107 | # Serverless directories
108 | .serverless/
109 |
110 | # FuseBox cache
111 | .fusebox/
112 |
113 | # DynamoDB Local files
114 | .dynamodb/
115 |
116 | # TernJS port file
117 | .tern-port
118 |
119 | # Stores VSCode versions used for testing VSCode extensions
120 | .vscode-test
121 |
122 | # yarn v2
123 | .yarn/cache
124 | .yarn/unplugged
125 | .yarn/build-state.yml
126 | .yarn/install-state.gz
127 | .pnp.*
128 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Debug Tests",
9 | "type": "node",
10 | "request": "launch",
11 | "runtimeArgs": [
12 | "--inspect-brk",
13 | "${workspaceRoot}/node_modules/.bin/jest",
14 | "--runInBand"
15 | ],
16 | "console": "integratedTerminal",
17 | "internalConsoleOptions": "neverOpen",
18 | "port": 9229
19 | }
20 | ]
21 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # v1.1.4 (Thu Apr 21 2022)
2 |
3 | #### 🐛 Bug Fix
4 |
5 | - Bump minimist from 1.2.5 to 1.2.6 [#9](https://github.com/alexstrat/supertest-graphql/pull/9) ([@dependabot[bot]](https://github.com/dependabot[bot]))
6 |
7 | #### Authors: 1
8 |
9 | - [@dependabot[bot]](https://github.com/dependabot[bot])
10 |
11 | ---
12 |
13 | # v1.1.3 (Tue Mar 22 2022)
14 |
15 | #### 🐛 Bug Fix
16 |
17 | - Add supports for subscriptions [#6](https://github.com/alexstrat/supertest-graphql/pull/6) ([@alexstrat](https://github.com/alexstrat))
18 |
19 | #### Authors: 1
20 |
21 | - Alexandre Lacheze ([@alexstrat](https://github.com/alexstrat))
22 |
23 | ---
24 |
25 | # v1.1.2 (Thu Dec 23 2021)
26 |
27 | #### 🐛 Bug Fix
28 |
29 | - Remove self from deps [#4](https://github.com/alexstrat/supertest-graphql/pull/4) ([@alexstrat](https://github.com/alexstrat))
30 |
31 | #### Authors: 1
32 |
33 | - Alexandre Lacheze ([@alexstrat](https://github.com/alexstrat))
34 |
35 | ---
36 |
37 | # v1.1.1 (Sat Dec 18 2021)
38 |
39 | #### ⚠️ Pushed to `master`
40 |
41 | - Add auto badge ([@alexstrat](https://github.com/alexstrat))
42 |
43 | #### Authors: 1
44 |
45 | - Alexandre Lacheze ([@alexstrat](https://github.com/alexstrat))
46 |
47 | ---
48 |
49 | # v1.1.0 (Sat Dec 18 2021)
50 |
51 | #### 🚀 Enhancement
52 |
53 | - Remove usage of `any` in typing [#3](https://github.com/alexstrat/supertest-graphql/pull/3) ([@alexstrat](https://github.com/alexstrat))
54 |
55 | #### ⚠️ Pushed to `master`
56 |
57 | - add fetch tags ([@alexstrat](https://github.com/alexstrat))
58 |
59 | #### Authors: 1
60 |
61 | - Alexandre Lacheze ([@alexstrat](https://github.com/alexstrat))
62 |
63 | ---
64 |
65 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # supertest-graphql
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | > Extends [supertest](https://www.npmjs.com/package/supertest) to test a GraphQL endpoint
11 |
12 | ## Install
13 |
14 | ```sh
15 | npm install supertest-graphql
16 | ```
17 |
18 | ## Usage
19 |
20 | ```ts
21 | import request from 'supertest-graphql'
22 | import gql from 'graphql-tag'
23 |
24 | test('should get pets', async () => {
25 | const { data } = await request(app)
26 | .query(gql`
27 | query {
28 | pets {
29 | name
30 | petType
31 | }
32 | }
33 | `)
34 | .expectNoErrors()
35 |
36 | expect(data.pets).toHaveLength(2)
37 | })
38 | ```
39 |
40 | ### Set expectations
41 | `expectNoErrors` will verify that the API response has no `errors` in
42 | its result payload.
43 |
44 | ```ts
45 | await request(app)
46 | .query('blooop')
47 | .expectNoErrors()
48 | // expected no errors but got 1 error(s) in GraphQL response: Syntax Error: Unexpected Name "blooop".
49 | ```
50 | ### Variables
51 | ```ts
52 | const { data } = await request(app)
53 | .query(gql`
54 | query GetPets($first: Int){
55 | pets(first: $first) {
56 | name
57 | petType
58 | }
59 | }
60 | `)
61 | .variables({ first: 4 })
62 | ```
63 |
64 | ### Mutation
65 | ```ts
66 | const { data } = await request(app)
67 | .mutate(gql`
68 | mutation PetAnimal($petId: ID!) {
69 | petAnimal(petId: $petId) {
70 | name
71 | petType
72 | }
73 | }
74 | `)
75 | .variables({petId: 'my-cat' })
76 | ```
77 |
78 | ### Subscriptions with WebScoket
79 | ```ts
80 | import { supertestWs } from 'supertest-graphql'
81 | import gql from 'graphql-tag'
82 |
83 | // for websocket the server needs to be started and stopped manually
84 | beForeEach(() => server.listen(0, "localhost"))
85 | afterEach(() => server.close())
86 |
87 | test('should get pets', async () => {
88 | const sub = await supertestWs(app)
89 | .subscribe(gql`
90 | subscription {
91 | newPetAdded {
92 | name
93 | petType
94 | }
95 | }
96 | `)
97 |
98 | // will wait or pop the next value
99 | const { data } = await sub.next().expectNoErrors()
100 |
101 | expect(data.newPetAdded.name).toEqual('Fifi')
102 | })
103 | ```
104 |
105 | ### Authentication
106 | ```ts
107 | const { data } = await request(app)
108 | .auth('username', 'password')
109 | .query(...)
110 | ```
111 |
112 | or via headers:
113 | ```ts
114 | const { data } = await request(app)
115 | .set('authorization', 'my token')
116 | .query(...)
117 | ```
118 |
119 | For WebSocket with `connectionParams`:
120 | ```ts
121 | import { supertestWs } from 'supertest-graphql'
122 |
123 | const sub = await supertestWs(app)
124 | .connectionParams({
125 | token: 'my token'
126 | })
127 | .subscribe(...)
128 | ```
129 | ### Change GraphQL endpoint path
130 |
131 | By default, the execution are sent to `/graphql`.
132 |
133 | You can change this with `.path()`:
134 |
135 | ```ts
136 | const { data } = await request(app)
137 | .path('/new-graphql')
138 | .query(...)
139 | ```
140 |
141 | ### Use WebSocket legacy protocol
142 |
143 | ```ts
144 | import { supertestWs, LEGACY_WEBSOCKET_PROTOCOL } from 'supertest-graphql'
145 |
146 | const sub = await supertestWs(app)
147 | .protocol(LEGACY_WEBSOCKET_PROTOCOL)
148 | .subscribe(...)
149 | ```
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
2 | // eslint-disable-next-line no-undef
3 | module.exports = {
4 | preset: 'ts-jest',
5 | testEnvironment: 'node',
6 | };
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "supertest-graphql",
3 | "version": "1.1.4",
4 | "description": "Extends supertest to test a GraphQL endpoint",
5 | "main": "dist/index.js",
6 | "types": "dist/index.d.ts",
7 | "scripts": {
8 | "build": "rm -rf ./dist/* && tsc",
9 | "test": "jest",
10 | "check-types": "tsc --noEmit",
11 | "lint": "eslint . --ext .ts",
12 | "prepublishOnly": "npm run build",
13 | "release": "auto shipit"
14 | },
15 | "files": [
16 | "dist"
17 | ],
18 | "repository": {
19 | "type": "git",
20 | "url": "https://github.com/alexstrat/supertest-graphql"
21 | },
22 | "keywords": [
23 | "supertest",
24 | "graphql",
25 | "test",
26 | "jest",
27 | "apollo"
28 | ],
29 | "author": "Alexandre Lacheze",
30 | "license": "ISC",
31 | "peerDependencies": {
32 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0"
33 | },
34 | "devDependencies": {
35 | "@graphql-tools/schema": "^8.3.1",
36 | "@types/express": "^4.17.13",
37 | "@types/jest": "^27.0.3",
38 | "@types/ws": "^8.2.2",
39 | "@typescript-eslint/eslint-plugin": "^5.7.0",
40 | "@typescript-eslint/parser": "^5.7.0",
41 | "apollo-server-express": "^3.5.0",
42 | "auto": "^10.32.5",
43 | "eslint": "^8.4.1",
44 | "eslint-config-prettier": "^8.3.0",
45 | "eslint-plugin-prettier": "^4.0.0",
46 | "express": "^4.17.2",
47 | "graphql": "^16.1.0",
48 | "graphql-subscriptions": "^2.0.0",
49 | "jest": "^27.4.5",
50 | "prettier": "2.5.1",
51 | "ts-jest": "^27.1.2",
52 | "ts-node": "^10.4.0",
53 | "typescript": "^4.5.4"
54 | },
55 | "dependencies": {
56 | "delay": "^5.0.0",
57 | "@types/supertest": "^2.0.11",
58 | "graphql-ws": "^5.6.3",
59 | "subscriptions-transport-ws": "^0.11.0",
60 | "supertest": "^6.1.6",
61 | "ws": "^8.4.0",
62 | "zen-observable-ts": "^1.1.0"
63 | },
64 | "auto": {
65 | "plugins": [
66 | "npm"
67 | ]
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/SuperTestGraphQL.ts:
--------------------------------------------------------------------------------
1 | import { DocumentNode, ExecutionResult, GraphQLError, print } from "graphql";
2 | import { SuperAgentTest, Response } from "supertest";
3 | import { AssertFn, Variables } from "./types";
4 |
5 | import { asserNoError, getOperationName, wrapAssertFn } from "./utils";
6 |
7 | export type SuperTestExecutionResult = ExecutionResult & {
8 | response: Response;
9 | };
10 |
11 | export default class SuperTestGraphQL
12 | implements PromiseLike>
13 | {
14 | private _query?: string;
15 | private _operationName?: string;
16 | private _variables?: TVariables;
17 | private _path = "/graphql";
18 | private _asserts: AssertFn[] = [];
19 |
20 | constructor(private _supertest: SuperAgentTest) {}
21 |
22 | /**
23 | * Send a GraphQL Query Document to the GraphQL server for execution.
24 | * @param query - the query to execute as string or `DocumentNode`
25 | * @param variables - the variables for this query
26 | */
27 | query(query: DocumentNode | string, variables?: TVariables): this {
28 | return this.operation(query, variables);
29 | }
30 |
31 | /**
32 | * Send a GraphQL Query Document to the GraphQL server for execution.
33 | * @param mutation - the mutation to execute as string or `DocumentNode`
34 | * @param variables - the variables for this mutation
35 | */
36 | mutate(mutation: DocumentNode | string, variables?: TVariables): this {
37 | return this.operation(mutation, variables);
38 | }
39 |
40 | /**
41 | * Send a GraphQL Query Document to the GraphQL server for execution.
42 | * @param operation - the operation to execute as string or `DocumentNode`
43 | * @param variables - the variables for this operation
44 | */
45 | operation(operation: DocumentNode | string, variables?: TVariables): this {
46 | if (typeof operation !== "string") {
47 | this._operationName = getOperationName(operation);
48 | }
49 | this._query = typeof operation === "string" ? operation : print(operation);
50 | this._variables = variables;
51 | return this;
52 | }
53 |
54 | /**
55 | * Set variables.
56 | * @param - variables
57 | */
58 | variables(variables: TVariables): this {
59 | this._variables = variables;
60 | return this;
61 | }
62 |
63 | /**
64 | * Set the GraphQL endpoint path.
65 | *
66 | * @default "/graphql"
67 | */
68 | path(path: string): this {
69 | this._path = path;
70 | return this;
71 | }
72 |
73 | /**
74 | * Set authentication parameters for the request.
75 | *
76 | * @see [supragent.auth](https://visionmedia.github.io/superagent/#authentication)
77 | */
78 | auth(user: string, pass: string, options?: { type: "basic" | "auto" }): this;
79 | auth(token: string, options: { type: "bearer" }): this;
80 | auth(
81 | ...args:
82 | | [string, string]
83 | | [string, string, { type: "basic" | "auto" }?]
84 | | [string, { type: "bearer" }]
85 | ): this {
86 | this._supertest.auth(...(args as Parameters));
87 | return this;
88 | }
89 |
90 | /**
91 | * Set headers for the request.
92 | *
93 | * @see [supragent.set](https://visionmedia.github.io/superagent/#setting-header-fields)
94 | */
95 | // can't use Parameters<> because not supported with several overloads
96 | // https://github.com/microsoft/TypeScript/issues/26591
97 | set(field: object): this;
98 | set(field: string, val: string): this;
99 | set(field: "Cookie", val: string[]): this;
100 | set(...args: [object] | [string, string] | ["Cookie", string[]]): this {
101 | this._supertest.set(...(args as Parameters));
102 | return this;
103 | }
104 |
105 | /**
106 | * Assert that there is no errors (`.errors` field) in response returned from the GraphQL API.
107 | */
108 | expectNoErrors(): this {
109 | this._asserts.push(wrapAssertFn(asserNoError));
110 | return this;
111 | }
112 |
113 | /**
114 | * Access to underlying supertest instance.
115 | */
116 | supertest(): SuperAgentTest {
117 | return this._supertest;
118 | }
119 |
120 | private async assert(result: SuperTestExecutionResult): Promise {
121 | for (const assertFn of this._asserts) {
122 | const maybeError = await assertFn(result);
123 | if (maybeError instanceof Error) throw maybeError;
124 | }
125 | }
126 |
127 | async end(): Promise> {
128 | if (this._query === undefined)
129 | throw new Error("You should call `query` or `mutate`");
130 |
131 | const payload: RequestPayload = {
132 | query: this._query,
133 | };
134 | if (this._operationName) {
135 | payload.operationName = this._operationName;
136 | }
137 | if (this._variables) {
138 | payload.variables = this._variables;
139 | }
140 |
141 | const response = await this._supertest
142 | .post(this._path)
143 | .accept("application/json")
144 | .send(payload);
145 |
146 | if (typeof response.body !== "object") {
147 | throw new Error(`Received a non valid body ${response.body}`);
148 | }
149 | const result = { ...response.body, response };
150 |
151 | await this.assert(result);
152 |
153 | return { ...response.body, response };
154 | }
155 |
156 | async then, TResult2 = never>(
157 | onfulfilled?:
158 | | ((
159 | value: SuperTestExecutionResult
160 | ) => TResult1 | PromiseLike)
161 | | null,
162 | onrejected?: ((reason: unknown) => TResult2 | PromiseLike) | null
163 | ): Promise {
164 | try {
165 | if (this._query === undefined)
166 | throw new Error("You should call `query` or `mutate`");
167 | const res = await this.end();
168 | if (onfulfilled) return onfulfilled(res);
169 | // @ts-expect-error no idea why
170 | return res;
171 | } catch (e) {
172 | if (onrejected) return onrejected(e);
173 | throw new Error("No rejection");
174 | }
175 | }
176 | }
177 |
178 | type RequestPayload = {
179 | query: string;
180 | operationName?: string;
181 | variables?: TVariables;
182 | };
183 |
--------------------------------------------------------------------------------
/src/SuperTestWSGraphQL.ts:
--------------------------------------------------------------------------------
1 | import delay from "delay";
2 | import { DocumentNode, ExecutionResult, print } from "graphql";
3 | import { Client, createClient, Disposable, Sink } from "graphql-ws";
4 | import { ObjMap } from "graphql/jsutils/ObjMap";
5 | import { SubscriptionClient } from "subscriptions-transport-ws";
6 | import ws from "ws";
7 | import { Subscriber } from "zen-observable-ts";
8 |
9 | import { AssertFn, Variables } from "./types";
10 | import {
11 | asserNoError,
12 | BlockingQueue,
13 | getOperationName,
14 | wrapAssertFn,
15 | } from "./utils";
16 |
17 | // /!\ graphql-ws is the legacy one
18 | export type WebSocketProtocol = "graphql-transport-ws" | "graphql-ws";
19 |
20 | /**
21 | * The protocol implemented by the library `subscriptions-transport-ws` and
22 | * that is now considered legacy.
23 | */
24 | export const LEGACY_WEBSOCKET_PROTOCOL = "graphql-ws";
25 |
26 | export class SuperTestExecutionNextResult
27 | implements PromiseLike>
28 | {
29 | private _asserts: AssertFn[] = [];
30 |
31 | constructor(private pop: Promise>) {}
32 | async then<
33 | TResult1 = ExecutionResult>,
34 | TResult2 = never
35 | >(
36 | onfulfilled?:
37 | | ((
38 | value: ExecutionResult>
39 | ) => TResult1 | PromiseLike)
40 | | null,
41 | onrejected?: ((reason: any) => TResult2 | PromiseLike) | null
42 | ): Promise {
43 | const res = await this.pop;
44 | await this.assert(res);
45 | if (onfulfilled) return onfulfilled(res);
46 | // @ts-expect-error no idea why
47 | return res;
48 | }
49 |
50 | /**
51 | * Assert that there is no errors (`.errors` field) in response returned from the GraphQL API.
52 | */
53 | expectNoErrors(): this {
54 | this._asserts.push(wrapAssertFn(asserNoError));
55 | return this;
56 | }
57 |
58 | private async assert(result: ExecutionResult): Promise {
59 | for (const assertFn of this._asserts) {
60 | const maybeError = await assertFn(result);
61 | if (maybeError instanceof Error) throw maybeError;
62 | }
63 | }
64 | }
65 |
66 | export class SuperTestExecutionStreamingResult {
67 | private queue: BlockingQueue> = new BlockingQueue();
68 |
69 | constructor(
70 | private client: Disposable,
71 | subscriber: Subscriber>
72 | ) {
73 | subscriber({
74 | next: (res) => this.queue.push(res),
75 | complete: () => {
76 | // do something
77 | },
78 | error: () => {
79 | // do something
80 | },
81 | closed: false,
82 | });
83 | }
84 |
85 | /**
86 | * Get the next result that the operation is emitting.
87 | */
88 | next(): SuperTestExecutionNextResult {
89 | return new SuperTestExecutionNextResult(this.queue.pop());
90 | }
91 |
92 | /**
93 | * Flush the pending results from the queue.
94 | */
95 | flush(): ExecutionResult[] {
96 | return this.queue.flush();
97 | }
98 |
99 | /**
100 | * Assert that no more results are pending.
101 | */
102 | expectNoPending(): this {
103 | if (this.queue.length > 0) {
104 | throw new Error(`expect no pending, but got ${this.queue.length}`);
105 | }
106 | return this;
107 | }
108 |
109 | /**
110 | * Close the operation and the connection.
111 | */
112 | async close(): Promise {
113 | await this.client.dispose();
114 | }
115 | }
116 |
117 | export class SuperTestExecutionStreamingResultPool {
118 | private _subscriptions: SuperTestExecutionStreamingResult[] = [];
119 | async endAll(): Promise {
120 | await Promise.all(this._subscriptions.map((c) => c.close()));
121 | this._subscriptions = [];
122 | }
123 | add(sub: SuperTestExecutionStreamingResult): void {
124 | this._subscriptions.push(sub);
125 | }
126 | }
127 |
128 | type ConnectionParams = {
129 | [paramName: string]: any;
130 | };
131 | export default class SuperTestWSGraphQL
132 | implements PromiseLike>
133 | {
134 | private _query?: string;
135 | private _operationName?: string;
136 | private _variables?: TVariables;
137 | private _path = "/graphql";
138 | private _protocol: WebSocketProtocol = "graphql-transport-ws";
139 | private _connectionParams?: ConnectionParams;
140 |
141 | constructor(
142 | private _hostname: string,
143 | private _pool?: SuperTestExecutionStreamingResultPool
144 | ) {}
145 |
146 | /**
147 | * Send a GraphQL Query Document to the GraphQL server for execution.
148 | * @param operation - the query to execute as string or `DocumentNode`
149 | * @param variables - the variables for this query
150 | */
151 | subscribe(operation: DocumentNode | string, variables?: TVariables): this {
152 | this.operation(operation, variables);
153 | return this;
154 | }
155 |
156 | /**
157 | * Send a GraphQL Query Document to the GraphQL server for execution.
158 | * @param query - the query to execute as string or `DocumentNode`
159 | * @param variables - the variables for this query
160 | */
161 | query(query: DocumentNode | string, variables?: TVariables): this {
162 | return this.operation(query, variables);
163 | }
164 |
165 | /**
166 | * Send a GraphQL Query Document to the GraphQL server for execution.
167 | * @param mutation - the mutation to execute as string or `DocumentNode`
168 | * @param variables - the variables for this mutation
169 | */
170 | mutate(mutation: DocumentNode | string, variables?: TVariables): this {
171 | return this.operation(mutation, variables);
172 | }
173 |
174 | /**
175 | * Send a GraphQL Query Document to the GraphQL server for execution.
176 | * @param operation - the operation to execute as string or `DocumentNode`
177 | * @param variables - the variables for this operation
178 | */
179 | operation(operation: DocumentNode | string, variables?: TVariables): this {
180 | if (typeof operation !== "string") {
181 | this._operationName = getOperationName(operation);
182 | }
183 | this._query = typeof operation === "string" ? operation : print(operation);
184 | this._variables = variables;
185 | return this;
186 | }
187 |
188 | /**
189 | * Set variables.
190 | * @param - variables
191 | */
192 | variables(variables: TVariables): this {
193 | this._variables = variables;
194 | return this;
195 | }
196 |
197 | /**
198 | * Set the GraphQL endpoint path.
199 | *
200 | * @default "/graphql"
201 | */
202 | path(path: string): this {
203 | this._path = path;
204 | return this;
205 | }
206 |
207 | /**
208 | * Set the GraphQL WebSocket porotocol.
209 | * You can set the legacy protocol with the variable `LEGACY_WEBSOCKET_PROTOCOL`.
210 | */
211 | protocol(wsProtocol: WebSocketProtocol): this {
212 | this._protocol = wsProtocol;
213 | return this;
214 | }
215 |
216 | /**
217 | * Set connection params.
218 | */
219 | connectionParams(params: ConnectionParams): this {
220 | this._connectionParams = params;
221 | return this;
222 | }
223 |
224 | async then<
225 | TResult1 = SuperTestExecutionStreamingResult,
226 | TResult2 = never
227 | >(
228 | onfulfilled?:
229 | | ((
230 | value: SuperTestExecutionStreamingResult
231 | ) => TResult1 | PromiseLike)
232 | | null,
233 | onrejected?: ((reason: any) => TResult2 | PromiseLike) | null
234 | ): Promise {
235 | try {
236 | const url = new URL(this._path, this._hostname).toString();
237 | const connect =
238 | this._protocol === "graphql-ws"
239 | ? connectAndAdaptLegacyClient
240 | : connectClient;
241 |
242 | const client = await connect(url, this._connectionParams);
243 |
244 | if (!this._query) throw new Error("Missing a query");
245 | const query = this._query;
246 | const streamingResult = new SuperTestExecutionStreamingResult(
247 | client,
248 | (observer) =>
249 | client.subscribe(
250 | {
251 | query,
252 | variables: this._variables,
253 | operationName: this._operationName,
254 | },
255 | observer
256 | )
257 | );
258 |
259 | this._pool?.add(streamingResult);
260 |
261 | if (onfulfilled) return onfulfilled(streamingResult);
262 | // @ts-expect-error no idea why
263 | return streamingResult;
264 | } catch (e) {
265 | if (onrejected) return onrejected(e);
266 | throw new Error("No rejection");
267 | }
268 | }
269 | }
270 |
271 | type SubscriptionArgs = {
272 | query: string;
273 | variables?: TVariables;
274 | operationName?: string;
275 | };
276 |
277 | type AbstractClient = {
278 | dispose: () => void | Promise;
279 | subscribe: (
280 | args: SubscriptionArgs,
281 | observer: Sink>
282 | ) => () => void;
283 | };
284 |
285 | const connectClient = async (
286 | url: string,
287 | connectionParams?: ConnectionParams
288 | ): Promise => {
289 | return await new Promise((res, reject) => {
290 | const client = createClient({
291 | url,
292 | connectionParams,
293 | lazy: false,
294 | onNonLazyError: (error) => reject(error),
295 | webSocketImpl: ws,
296 | });
297 | client.on("connected", () => res(client));
298 | });
299 | };
300 |
301 | const connectAndAdaptLegacyClient = async (
302 | url: string,
303 | connectionParams?: ConnectionParams
304 | ): Promise => {
305 | const legacyClient = await new Promise((res, reject) => {
306 | const client = new SubscriptionClient(
307 | url,
308 | {
309 | connectionParams,
310 | connectionCallback: (error) => {
311 | if (error) {
312 | client.close();
313 | return reject(error);
314 | }
315 | res(client);
316 | },
317 | },
318 | ws
319 | );
320 | });
321 |
322 | return {
323 | dispose: () => {
324 | legacyClient.close();
325 | },
326 | subscribe: (args, observer) => {
327 | // @ts-expect-error not exact but fine
328 | const { unsubscribe } = legacyClient.request(args).subscribe(observer);
329 | return unsubscribe;
330 | },
331 | };
332 | };
333 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import * as http from "http";
2 | import * as https from "https";
3 | import { agent } from "supertest";
4 | import SuperTestGraphQL from "./SuperTestGraphQL";
5 | import SuperTestWSGraphQL, {
6 | SuperTestExecutionStreamingResultPool,
7 | } from "./SuperTestWSGraphQL";
8 | import { Variables } from "./types";
9 |
10 | /**
11 | * Test against the given `app` returnig a new `SuperTestGraphQL`.
12 | */
13 | const supertest = (
14 | app: unknown
15 | ): SuperTestGraphQL => {
16 | const supertest = agent(app);
17 | return new SuperTestGraphQL(supertest);
18 | };
19 |
20 | // todo: put me somewhere
21 | function getWSBase(server: https.Server | http.Server) {
22 | if (typeof server === "string") {
23 | return server;
24 | }
25 |
26 | const address = server.address();
27 | if (!address) {
28 | // see https://github.com/visionmedia/supertest/issues/566
29 | throw new Error(
30 | "Server must be listening:\n" +
31 | "beforeEach((done) => server.listen(0, 'localhost', done));\n" +
32 | "afterEach((done) => server.close(done));\n" +
33 | "\n" +
34 | "supertest's request(app) syntax is not supported (find out more: https://github.com/davidje13/superwstest#why-isnt-requestapp-supported)"
35 | );
36 | }
37 |
38 | const protocol = server instanceof https.Server ? "wss" : "ws";
39 | let hostname;
40 | if (typeof address === "object") {
41 | if (address.family.toLowerCase() === "ipv6") {
42 | hostname = `[${address.address}]`;
43 | } else {
44 | hostname = address.address;
45 | }
46 | } else {
47 | hostname = address;
48 | }
49 | // @ts-expect-error fix me
50 | return `${protocol}://${hostname}:${address.port}`;
51 | }
52 |
53 | const mainPool = new SuperTestExecutionStreamingResultPool();
54 |
55 | export const supertestWs = Object.assign(
56 | (
57 | // todo: accept string
58 | app: https.Server | http.Server | string
59 | ): SuperTestWSGraphQL => {
60 | // todo: prametrize
61 | const base = typeof app === "string" ? app : getWSBase(app);
62 | return new SuperTestWSGraphQL(base, mainPool);
63 | },
64 | {
65 | end: () => mainPool.endAll(),
66 | }
67 | );
68 |
69 | export * from "./SuperTestGraphQL";
70 | export * from "./SuperTestWSGraphQL";
71 | export * from "./types";
72 | export { SuperTestGraphQL };
73 | export default supertest;
74 |
--------------------------------------------------------------------------------
/src/supertest-graphql.test.ts:
--------------------------------------------------------------------------------
1 | import { gql, ApolloServer, ExpressContext } from "apollo-server-express";
2 | import express from "express";
3 | import { createServer, Server } from "http";
4 | import { SubscriptionServer } from "subscriptions-transport-ws";
5 | import { useServer } from "graphql-ws/lib/use/ws";
6 | import { execute, subscribe } from "graphql";
7 | import { makeExecutableSchema } from "@graphql-tools/schema";
8 | import { GraphQLFieldResolver } from "graphql";
9 | import { PubSub } from "graphql-subscriptions";
10 | import delay from "delay";
11 |
12 | import request, {
13 | LEGACY_WEBSOCKET_PROTOCOL,
14 | supertestWs,
15 | WebSocketProtocol,
16 | } from "./";
17 | import { WebSocketServer } from "ws";
18 |
19 | const typeDefs = gql`
20 | type Query {
21 | hi(name: String): String!
22 | }
23 | type Mutation {
24 | do: String!
25 | }
26 | type Subscription {
27 | onHi(name: String): String!
28 | }
29 | `;
30 |
31 | type QueryHiResolver = GraphQLFieldResolver<
32 | never,
33 | ExpressContext,
34 | { name?: string },
35 | string
36 | >;
37 | type MutationDoResolver = GraphQLFieldResolver<
38 | never,
39 | ExpressContext,
40 | never,
41 | string
42 | >;
43 |
44 | let server: Server;
45 | let queryHiResolver: jest.Mock>;
46 | let mutationDoResolver: jest.Mock>;
47 | const pubsub: PubSub = new PubSub();
48 |
49 | type InitServerOptions = {
50 | graphqlPath?: string;
51 | wsProtocol?: WebSocketProtocol;
52 | };
53 |
54 | const initTestServer = async ({
55 | graphqlPath = "/graphql",
56 | wsProtocol = "graphql-transport-ws",
57 | }: InitServerOptions = {}): Promise => {
58 | const app = express();
59 | const httpServer = createServer(app);
60 | queryHiResolver = jest.fn>(
61 | (_, { name = "" }) => `hi ${name}!`
62 | );
63 | mutationDoResolver = jest.fn>(
64 | () => "done!"
65 | );
66 | const schema = makeExecutableSchema({
67 | typeDefs,
68 | resolvers: {
69 | Query: {
70 | hi: queryHiResolver,
71 | },
72 | Mutation: {
73 | do: mutationDoResolver,
74 | },
75 | Subscription: {
76 | onHi: {
77 | subscribe: () => pubsub.asyncIterator(["ON_HI"]),
78 | resolve: (_, { name }: { name?: string | null }) => {
79 | return `Hi ${name ? name : "unknown"}`;
80 | },
81 | },
82 | },
83 | },
84 | });
85 |
86 | let closeWsMobile: () => Promise;
87 | if (wsProtocol === "graphql-ws") {
88 | const subscriptionServer = SubscriptionServer.create(
89 | {
90 | schema,
91 | execute,
92 | subscribe,
93 | },
94 | {
95 | server: httpServer,
96 | }
97 | );
98 | closeWsMobile = async () => subscriptionServer.close();
99 | } else {
100 | const server = new WebSocketServer({
101 | server: httpServer,
102 | path: graphqlPath,
103 | });
104 |
105 | const serverCleanup = useServer({ schema }, server);
106 | closeWsMobile = async () => await serverCleanup.dispose();
107 | }
108 |
109 | const server = new ApolloServer({
110 | schema,
111 | context: (c) => c,
112 | plugins: [
113 | {
114 | async serverWillStart() {
115 | return {
116 | async drainServer() {
117 | closeWsMobile();
118 | },
119 | };
120 | },
121 | },
122 | ],
123 | });
124 | await server.start();
125 | server.applyMiddleware({ app, path: graphqlPath });
126 | return httpServer;
127 | };
128 |
129 | beforeEach(async () => {
130 | server = await initTestServer();
131 | });
132 |
133 | describe(".query()", () => {
134 | test("it queries", async () => {
135 | const { data } = await request<{ hi: string }>(server).query(
136 | gql`
137 | query {
138 | hi
139 | }
140 | `
141 | );
142 | expect(data?.hi).toBe("hi !");
143 | });
144 | describe("with variables", () => {
145 | test("it queries", async () => {
146 | const { data } = await request<{ hi: string }>(server).query(
147 | gql`
148 | query Greetings($name: String!) {
149 | hi(name: $name)
150 | }
151 | `,
152 | { name: "Alex" }
153 | );
154 | expect(data?.hi).toBe("hi Alex!");
155 | });
156 | });
157 | describe("with errors in return", () => {
158 | it("should make them available", async () => {
159 | queryHiResolver.mockImplementation(() => {
160 | throw new Error("Bad");
161 | });
162 | const { errors } = await request<{ hi: string }>(server).query(
163 | gql`
164 | query {
165 | hi
166 | }
167 | `
168 | );
169 | expect(errors).toBeDefined();
170 | expect(errors).toHaveLength(1);
171 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
172 | expect(errors![0].message).toEqual("Bad");
173 | });
174 | });
175 | });
176 |
177 | describe(".mutate()", () => {
178 | test("it mutates", async () => {
179 | const { data } = await request<{ do: string }>(server).query(
180 | gql`
181 | mutation {
182 | do
183 | }
184 | `
185 | );
186 | expect(data?.do).toBe("done!");
187 | });
188 | });
189 |
190 | describe(".variables()", () => {
191 | test("it queries with variables", async () => {
192 | const { data } = await request<{ hi: string }>(server)
193 | .query(
194 | gql`
195 | query Greetings($name: String!) {
196 | hi(name: $name)
197 | }
198 | `
199 | )
200 | .variables({ name: "Alex" });
201 | expect(data?.hi).toBe("hi Alex!");
202 | });
203 | });
204 |
205 | describe(".path()", () => {
206 | it("changes the path to query graphql", async () => {
207 | server = await initTestServer({
208 | graphqlPath: "/specialUrl",
209 | });
210 | const { data } = await request<{ hi: string }>(server)
211 | .path("/specialUrl")
212 | .query(
213 | gql`
214 | query Greetings($name: String!) {
215 | hi(name: $name)
216 | }
217 | `
218 | )
219 | .variables({ name: "Alex" });
220 | expect(data?.hi).toBe("hi Alex!");
221 | });
222 | });
223 |
224 | describe(".set()", () => {
225 | test("it properly set headers", async () => {
226 | await request<{ hi: string }>(server)
227 | .set("authorization", "bar")
228 | .query(
229 | gql`
230 | query {
231 | hi
232 | }
233 | `
234 | );
235 | expect(queryHiResolver).toHaveBeenCalled();
236 | const { req } = queryHiResolver.mock.calls[0][2];
237 | expect(req.headers["authorization"]).toEqual("bar");
238 | });
239 | });
240 |
241 | describe(".auth()", () => {
242 | test("it properly set basic headers", async () => {
243 | await request<{ hi: string }>(server)
244 | .auth("username", "password")
245 | .query(
246 | gql`
247 | query {
248 | hi
249 | }
250 | `
251 | );
252 | expect(queryHiResolver).toHaveBeenCalled();
253 | const { req } = queryHiResolver.mock.calls[0][2];
254 | expect(req.headers["authorization"]).toEqual(
255 | "Basic dXNlcm5hbWU6cGFzc3dvcmQ="
256 | );
257 | });
258 | });
259 |
260 | describe(".expectNoErrors()", () => {
261 | it("when there is an error it should throw", async () => {
262 | queryHiResolver.mockImplementation(() => {
263 | throw new Error("Bad");
264 | });
265 | return expect(
266 | request<{ hi: string }>(server)
267 | .query(
268 | gql`
269 | query {
270 | hi
271 | }
272 | `
273 | )
274 | .expectNoErrors()
275 | ).rejects.toThrow(
276 | "expected no errors but got 1 error(s) in GraphQL response: Bad"
277 | );
278 | });
279 | it("when there is no error it should not throw", async () => {
280 | return expect(
281 | request<{ hi: string }>(server)
282 | .query(
283 | gql`
284 | query {
285 | hi
286 | }
287 | `
288 | )
289 | .expectNoErrors()
290 | ).resolves.not.toThrow();
291 | });
292 | });
293 |
294 | describe("test ws legacy", () => {
295 | beforeEach(async () => {
296 | server = await initTestServer({ wsProtocol: "graphql-ws" });
297 | await new Promise((res) =>
298 | server.listen(0, "localhost", () => res())
299 | );
300 | });
301 |
302 | afterEach(async () => {
303 | await supertestWs.end();
304 | server.close();
305 | });
306 |
307 | it("should work", async () => {
308 | const sub = await supertestWs(server).protocol(LEGACY_WEBSOCKET_PROTOCOL)
309 | .subscribe(gql`
310 | subscription {
311 | onHi
312 | }
313 | `);
314 |
315 | // there is no wayt to know if the subscription is active,
316 | // to avoid race conditions we need to wait a bit
317 | await delay(200);
318 |
319 | pubsub.publish("ON_HI", {});
320 | const res = await sub.next().expectNoErrors();
321 |
322 | expect(res.data).toEqual({ onHi: "Hi unknown" });
323 | });
324 | });
325 |
326 | describe("test ws", () => {
327 | beforeEach(async () => {
328 | server = await initTestServer();
329 | await new Promise((res) =>
330 | server.listen(0, "localhost", () => res())
331 | );
332 | });
333 |
334 | afterEach(async () => {
335 | await supertestWs.end();
336 | server.close();
337 | });
338 |
339 | it("should work", async () => {
340 | const sub = await supertestWs(server).subscribe(gql`
341 | subscription {
342 | onHi
343 | }
344 | `);
345 |
346 | // there is no wayt to know if the subscription is active,
347 | // to avoid race conditions we need to wait a bit
348 | await delay(200);
349 |
350 | pubsub.publish("ON_HI", {});
351 | const res = await sub.next().expectNoErrors();
352 |
353 | expect(res.data).toEqual({ onHi: "Hi unknown" });
354 | });
355 | });
356 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | import { ExecutionResult } from "graphql";
2 |
3 | export type Variables = { [key: string]: unknown };
4 |
5 | export type AssertFn = (
6 | result: ExecutionResult
7 | ) => Error | undefined | Promise;
8 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { DocumentNode, GraphQLError, OperationDefinitionNode } from "graphql";
2 | import { AssertFn } from "./types";
3 |
4 | export const getOperationName = (
5 | document: DocumentNode
6 | ): string | undefined => {
7 | let operationName = undefined;
8 |
9 | const operationDefinitions = document.definitions.filter(
10 | (definition) => definition.kind === "OperationDefinition"
11 | ) as OperationDefinitionNode[];
12 |
13 | if (operationDefinitions.length === 1) {
14 | operationName = operationDefinitions[0].name?.value;
15 | }
16 | return operationName;
17 | };
18 |
19 | type Pop = {
20 | resolve: (item: TItem | PromiseLike) => void;
21 | tm: NodeJS.Timeout | null;
22 | };
23 |
24 | // inspired from https://github.com/davidje13/superwstest/blob/main/src/BlockingQueue.mjs
25 | export class BlockingQueue {
26 | private pendingPush: TItem[] = [];
27 | private pendingPop: Pop[] = [];
28 |
29 | push(item: TItem): void {
30 | if (this.pendingPop.length > 0) {
31 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- it's verified with above condition
32 | const firstPendingPop = this.pendingPop.shift()!;
33 | if (typeof firstPendingPop.tm === "number") {
34 | clearTimeout(firstPendingPop.tm);
35 | }
36 | firstPendingPop.resolve(item);
37 | } else {
38 | this.pendingPush.push(item);
39 | }
40 | }
41 |
42 | async pop(timeout?: number): Promise {
43 | if (this.pendingPush.length > 0) {
44 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- it's verified with above condition
45 | return this.pendingPush.shift()!;
46 | }
47 | return new Promise((resolve, reject) => {
48 | const newPop: Pop = { resolve, tm: null };
49 | this.pendingPop.push(newPop);
50 | if (timeout !== undefined) {
51 | newPop.tm = setTimeout(() => {
52 | this.pendingPop = this.pendingPop.filter((pop) => pop !== newPop);
53 | reject(new Error(`Timeout after ${timeout}ms`));
54 | }, timeout);
55 | }
56 | });
57 | }
58 |
59 | flush(): TItem[] {
60 | const flushed = [...this.pendingPush];
61 | this.pendingPush = [];
62 | return flushed;
63 | }
64 |
65 | get length(): number {
66 | return this.pendingPush.length;
67 | }
68 | }
69 |
70 | /**
71 | * Wraps an assert function into another.
72 | * The wrapper function edit the stack trace of any assertion error, prepending a more useful stack to it.
73 | *
74 | * Borrowed from supertest
75 | */
76 | export function wrapAssertFn(
77 | assertFn: AssertFn
78 | ): AssertFn {
79 | const savedStack = new Error().stack?.split("\n").slice(3) || [];
80 |
81 | return async (res) => {
82 | let badStack;
83 | const err = await assertFn(res);
84 | if (err instanceof Error && err.stack) {
85 | badStack = err.stack.replace(err.message, "").split("\n").slice(1);
86 | err.stack = [err.toString()]
87 | .concat(savedStack)
88 | .concat("----")
89 | .concat(badStack)
90 | .join("\n");
91 | }
92 | return err;
93 | };
94 | }
95 |
96 | export const asserNoError: AssertFn = ({ errors }) => {
97 | if (errors && Array.isArray(errors) && errors.length > 0) {
98 | const errorSummary = (errors as GraphQLError[])
99 | .map((e) => e.message)
100 | .join(",");
101 | return new Error(
102 | `expected no errors but got ${errors.length} error(s) in GraphQL response: ${errorSummary}`
103 | );
104 | }
105 | };
106 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "ts-node": {
3 | "transpileOnly": true
4 | },
5 | "compilerOptions": {
6 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
7 |
8 | /* Projects */
9 | // "incremental": true, /* Enable incremental compilation */
10 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
11 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
12 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
13 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
14 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
15 |
16 | /* Language and Environment */
17 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
18 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
19 | // "jsx": "preserve", /* Specify what JSX code is generated. */
20 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
21 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
22 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
23 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
24 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
25 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
26 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
27 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
28 |
29 | /* Modules */
30 | "module": "commonjs", /* Specify what module code is generated. */
31 | "rootDir": "./src", /* Specify the root folder within your source files. */
32 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
33 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
34 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
35 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
36 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
37 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */
38 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
39 | // "resolveJsonModule": true, /* Enable importing .json files */
40 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */
41 |
42 | /* JavaScript Support */
43 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
44 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
45 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
46 |
47 | /* Emit */
48 | "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
49 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */
50 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
51 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
52 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
53 | "outDir": "./dist", /* Specify an output folder for all emitted files. */
54 | // "removeComments": true, /* Disable emitting comments. */
55 | // "noEmit": true, /* Disable emitting files from a compilation. */
56 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
57 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
58 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
59 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
60 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
61 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
62 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
63 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
64 | // "newLine": "crlf", /* Set the newline character for emitting files. */
65 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
66 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
67 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
68 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
69 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
70 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
71 |
72 | /* Interop Constraints */
73 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
74 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
75 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
76 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
77 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
78 |
79 | /* Type Checking */
80 | "strict": true, /* Enable all strict type-checking options. */
81 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
82 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
83 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
84 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
85 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
86 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
87 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
88 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
89 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
90 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
91 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
92 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
93 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
94 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
95 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
96 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
97 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
98 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
99 |
100 | /* Completeness */
101 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
102 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
103 | }
104 | }
105 |
--------------------------------------------------------------------------------