Skip to content

Commit

Permalink
Add library exports and tests (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
benjie authored Sep 24, 2024
2 parents 2727502 + 82b54e6 commit 4604283
Show file tree
Hide file tree
Showing 15 changed files with 2,101 additions and 19 deletions.
3 changes: 3 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/node_modules
/dist
/__tests__/**/*.graphql
90 changes: 90 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
module.exports = {
parser: "@typescript-eslint/parser",
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:import/errors",
"plugin:import/typescript",
"prettier",
],
plugins: ["@typescript-eslint", "simple-import-sort", "import"],
parserOptions: {
ecmaVersion: 2018,
sourceType: "module",
},
env: {
node: true,
es6: true,
},
globals: {
NodeJS: false, // For TypeScript
},
rules: {
"no-unused-vars": 0,
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
args: "after-used",
ignoreRestSiblings: true,
},
],
curly: "error",
"no-else-return": 0,
"no-return-assign": [2, "except-parens"],
"no-underscore-dangle": 0,
camelcase: 0,
"prefer-arrow-callback": [
"error",
{
allowNamedFunctions: true,
},
],
"class-methods-use-this": 0,
"no-restricted-syntax": 0,
"no-param-reassign": [
"error",
{
props: false,
},
],

"arrow-body-style": 0,
"no-nested-ternary": 0,

/*
* simple-import-sort seems to be the most stable import sorting currently,
* disable others
*/
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error",
"sort-imports": "off",
"import/order": "off",

"import/no-deprecated": "warn",
"import/no-duplicates": "error",
// Doesn't support 'exports'?
"import/no-unresolved": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-namespace": "off",
},
overrides: [
{
files: ["__tests__/**/*", "test.js"],
rules: {
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/no-var-requires": 0,
"@typescript-eslint/ban-ts-comment": 0,
},
},
{
files: ["perfTest/**/*", "examples/**/*"],
rules: {
"@typescript-eslint/no-var-requires": 0,
},
},
],
};
41 changes: 41 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: CI

on: [push, pull_request]

env:
CI: true
NODE_OPTIONS: "--disable-proto=delete"

jobs:
test:
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [20.x]

steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: yarn --frozen-lockfile
- run: yarn prepack
- run: yarn test

lint:
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [20.x]

steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: yarn --frozen-lockfile
- run: yarn lint
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ generation and other such functionality.
Which command you use will depend on your setup; if you're using `graphql-toe`
then you'll want `semantic-to-strict` to really capitalize on the benefits of
semantic nullability. If you just want to use a semantic nullability SDL with
traditional tools that don't yet understand it, then `semantic-to-nullable`
will just strip out the semantic-non-null types for you.
traditional tools that don't yet understand it, then `semantic-to-nullable` will
just strip out the semantic-non-null types for you.

## Installation

Expand All @@ -37,8 +37,8 @@ pnpm install --save graphql-sock

If a value is "null only on error" then it can be null. This conversion strips
all semantic-non-null type wrappers from the SDL, making a schema that appears
as it traditionally would. This means that you won't reap any of the benefits
of semantic nullability, but you can support existing tools.
as it traditionally would. This means that you won't reap any of the benefits of
semantic nullability, but you can support existing tools.

```
semantic-to-nullable -i input.graphql -o output.graphql
Expand All @@ -52,8 +52,8 @@ will be thrown, then it will not be possible for you to read a `null` from a
"null only on error" position. As such, this position becomes equivalent to a
traditional non-null for you, so this conversion converts all semantic-non-null
type wrappers into traditional non-null wrappers. Your type generators can
therefore generate fewer nullables, and your frontend engineers have to do
fewer null checks and are therefore happier.
therefore generate fewer nullables, and your frontend engineers have to do fewer
null checks and are therefore happier.

```
semantic-to-strict -i input.graphql -o output.graphql
Expand Down
43 changes: 43 additions & 0 deletions __tests__/index.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// @ts-check

import { test } from "node:test";
import * as assert from "node:assert";
import { semanticToStrict, semanticToNullable } from "../dist/index.js";
import { buildSchema, printSchema } from "graphql";
import { readdir, readFile } from "node:fs/promises";

const TEST_DIR = import.meta.dirname;
const files = await readdir(TEST_DIR);

for (const file of files) {
if (file.endsWith(".test.graphql") && !file.startsWith(".")) {
test(file.replace(/\.test\.graphql$/, ""), async () => {
const sdl = await readFile(TEST_DIR + "/" + file, "utf8");
const schema = buildSchema(sdl);
await test("semantic-to-strict", async () => {
const expectedSdl = await readFile(
TEST_DIR + "/snapshots/" + file.replace(".test.", ".strict."),
"utf8",
);
const converted = semanticToStrict(schema);
assert.equal(
printSchema(converted).trim(),
expectedSdl.trim(),
"Expected semantic-to-strict to match",
);
});
await test("semantic-to-nullable", async () => {
const expectedSdl = await readFile(
TEST_DIR + "/snapshots/" + file.replace(".test.", ".nullable."),
"utf8",
);
const converted = semanticToNullable(schema);
assert.equal(
printSchema(converted).trim(),
expectedSdl.trim(),
"Expected semantic-to-nullable to match",
);
});
});
}
}
41 changes: 41 additions & 0 deletions __tests__/schema-with-directive.test.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
directive @semanticNonNull(levels: [Int!]) on FIELD_DEFINITION

type Query {
allThings(includingArchived: Boolean, first: Int!): ThingConnection
@semanticNonNull
}

type ThingConnection {
pageInfo: PageInfo!
nodes: [Thing] @semanticNonNull(levels: [0, 1])
}

type PageInfo {
startCursor: String @semanticNonNull(levels: [0])
endCursor: String @semanticNonNull
hasNextPage: Boolean @semanticNonNull
hasPreviousPage: Boolean @semanticNonNull
}

interface Thing {
id: ID!
name: String @semanticNonNull
description: String
}

type Book implements Thing {
id: ID!
# Test that this semantic-non-null doesn't cause issues
name: String* @semanticNonNull
description: String
# Test that this non-null gets stripped
pages: Int! @semanticNonNull
}

type Car implements Thing {
id: ID!
name: String @semanticNonNull
description: String
mileage: Float @semanticNonNull
}

File renamed without changes.
35 changes: 35 additions & 0 deletions __tests__/snapshots/schema-with-directive.nullable.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
type Query {
allThings(includingArchived: Boolean, first: Int!): ThingConnection
}

type ThingConnection {
pageInfo: PageInfo!
nodes: [Thing]
}

type PageInfo {
startCursor: String
endCursor: String
hasNextPage: Boolean
hasPreviousPage: Boolean
}

interface Thing {
id: ID!
name: String
description: String
}

type Book implements Thing {
id: ID!
name: String
description: String
pages: Int
}

type Car implements Thing {
id: ID!
name: String
description: String
mileage: Float
}
35 changes: 35 additions & 0 deletions __tests__/snapshots/schema-with-directive.strict.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
type Query {
allThings(includingArchived: Boolean, first: Int!): ThingConnection!
}

type ThingConnection {
pageInfo: PageInfo!
nodes: [Thing!]!
}

type PageInfo {
startCursor: String!
endCursor: String!
hasNextPage: Boolean!
hasPreviousPage: Boolean!
}

interface Thing {
id: ID!
name: String!
description: String
}

type Book implements Thing {
id: ID!
name: String!
description: String
pages: Int!
}

type Car implements Thing {
id: ID!
name: String!
description: String
mileage: Float!
}
35 changes: 35 additions & 0 deletions __tests__/snapshots/schema.nullable.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
type Query {
allThings(includingArchived: Boolean, first: Int!): ThingConnection
}

type ThingConnection {
pageInfo: PageInfo!
nodes: [Thing]
}

type PageInfo {
startCursor: String
endCursor: String
hasNextPage: Boolean
hasPreviousPage: Boolean
}

interface Thing {
id: ID!
name: String
description: String
}

type Book implements Thing {
id: ID!
name: String
description: String
pages: Int
}

type Car implements Thing {
id: ID!
name: String
description: String
mileage: Float
}
35 changes: 35 additions & 0 deletions __tests__/snapshots/schema.strict.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
type Query {
allThings(includingArchived: Boolean, first: Int!): ThingConnection!
}

type ThingConnection {
pageInfo: PageInfo!
nodes: [Thing!]!
}

type PageInfo {
startCursor: String!
endCursor: String!
hasNextPage: Boolean!
hasPreviousPage: Boolean!
}

interface Thing {
id: ID!
name: String!
description: String
}

type Book implements Thing {
id: ID!
name: String!
description: String
pages: Int!
}

type Car implements Thing {
id: ID!
name: String!
description: String
mileage: Float!
}
Loading

0 comments on commit 4604283

Please sign in to comment.