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

Validate Queries Plugin #1004

Merged
merged 7 commits into from
Mar 15, 2019
Merged
Show file tree
Hide file tree
Changes from 3 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
10 changes: 9 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,15 @@ const jestConfig = {
configureProject('scripts', 'CI Scripts', () => ({
testEnvironment: 'node',
testMatch: [`<rootDir>/scripts/${testGlob}`]
}))
})),
// Test the graphql-cli plugin
configureProject(
'graphql-cli-validate-magento-pwa-queries',
'GraphQL CLI Plugin',
() => ({
testEnvironment: 'node'
})
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for sticking with this convention we're doing.

],
// Include files with zero tests in overall coverage analysis by specifying
// coverage paths manually.
Expand Down
9 changes: 9 additions & 0 deletions magento-compatibility.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* This file describes PWA Studio to Magento version compatabilities.
*/

// PWA Studio version -> Magento version.
module.exports = {
'>2.0.0': '2.3.1',
'2.0.0': '2.3.0'
};
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "2.1.0-dev",
"private": true,
"workspaces": [
"packages/graphql-cli-validate-magento-pwa-queries",
"packages/peregrine",
"packages/pwa-buildpack",
"packages/venia-concept",
Expand Down Expand Up @@ -59,8 +60,6 @@
"eslint-plugin-react": "^7.12.4",
"execa": "~1.0.0",
"figures": "~2.0.0",
"graphql": "^14.1.1",
"graphql-tag": "^2.10.1",
"husky": "~1.3.1",
"jest": "^24.3.1",
"jest-fetch-mock": "^2.1.1",
Expand Down
21 changes: 21 additions & 0 deletions packages/graphql-cli-validate-magento-pwa-queries/LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2019 Adobe Inc.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this an official thing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Published to NPM so I was just covering all the bases 🤷‍♂️


Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
74 changes: 74 additions & 0 deletions packages/graphql-cli-validate-magento-pwa-queries/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
[![tested with jest](https://img.shields.io/badge/tested_with-jest-99424f.svg)](https://github.com/facebook/jest) [![jest](https://jestjs.io/img/jest-badge.svg)](https://github.com/facebook/jest)


# graphql-cli-validate-magento-pwa-queries

Validate your project's GraphQL queries against a schema.

## Installation

```
yarn add graphql-cli graphql-cli-validate-magento-pwa-queries
```

## Summary

Given the following `.graphqlconfig`:

```
{
"projects": {
"myApp": {
"schemaPath": "mySchema.json",
"extensions": {
"endpoints": {
"default": "https://myEndpoint.com/graphql"
},
"validate-magento-pwa-queries": {
"clients": ["apollo", "literal"],
"filesGlob": "src/**/*.{js,graphql,gql}"
}
}
}
}
}
```

The command
```
graphql-cli get-schema --project myApp
```
will [download the GraphQL schema](https://oss.prisma.io/content/graphql-cli/06-schema-handling)
from `https://myEndpoint.com/graphql` and store it in `mySchema.json`.

Then the command
```
graphql-cli validate-magento-pwa-queries --project myApp
```

will validate all `apollo` and `literal` GraphQL queries it finds in `.js`, `.graphql`, or `.gql` files in the `src/` directory
against that schema.

## Options

This plugin supports the following command line options:

| Option | Description | Type | Default |
| --- | --- | --- | --- |
| `--project`, `-p` | The project name as specified in `.graphqlconfig`. | `string` | `""` |

You can also specifiy the following options in your `.graphqlconfig`:

| Option | Description | Type |
| --- | --- | --- | --- |
| `--clients`, `-c` | GraphQL clients in use in this project. | `array` |
| `--filesGlob`, `-f` | A glob used to target files for validation. | `string` |
jimbo marked this conversation as resolved.
Show resolved Hide resolved

## Further Reading

* [graphql-config](https://github.com/prisma/graphql-config)
* [graphql-cli](https://github.com/graphql-cli/graphql-cli)
* [eslint-plugin-graphql](https://github.com/apollographql/eslint-plugin-graphql)
* [graphql/no-deprecated-fields rule](https://github.com/apollographql/eslint-plugin-graphql#no-deprecated-fields-validation-rule)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I LOVE this documentation. Thank you for making it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just noticed a table formatting error, pushing commit now.


3 changes: 3 additions & 0 deletions packages/graphql-cli-validate-magento-pwa-queries/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const plugin = require('./lib/index.js');

module.exports = plugin;
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
const plugin = require('../index');

const fs = require('fs');
const eslint = require('eslint');

jest.mock('fs');
jest.mock('eslint');

test('it exports the correct command name', () => {
expect(plugin.command).toBe('validate-magento-pwa-queries');
});

test('it exports a description', () => {
expect(plugin.desc).toBeTruthy();
});

describe('supportedArguments', () => {
test('it supports a project command line argument', () => {
const keys = Object.keys(plugin.supportedArguments);

expect(keys).toHaveLength(1);
expect(keys).toContain('project');
});
});

describe('builder', () => {
const mockArgs = {
options: jest.fn()
};
afterEach(() => {
mockArgs.options.mockClear();
});

test('it is a function', () => {
expect(plugin.builder).toBeInstanceOf(Function);
});

test('it calls args.options with the correct supported arguments', () => {
plugin.builder(mockArgs);

expect(mockArgs.options).toHaveBeenCalled();
expect(mockArgs.options).toHaveBeenCalledWith(
plugin.supportedArguments
);
});
});

describe('handler', () => {
const mockArgs = {
project: 'myApp'
};
const mockContext = {
getProjectConfig: jest.fn(() => {
return Promise.resolve({
config: {
extensions: {
'validate-magento-pwa-queries': {
clients: ['apollo', 'literal'],
filesGlob: '*.graphql'
}
},
schemaPath: 'unit test'
}
});
}),
spinner: {
fail: jest.fn(),
start: jest.fn(),
succeed: jest.fn()
}
};

let eslintCLIEngineSpy;
let existsSyncSpy;
let mockConsoleLog;
let mockConsoleWarn;
let mockProcessExit;

beforeAll(() => {
const noop = () => {};

// For happy paths, mock a report that indicates no errors.
eslintCLIEngineSpy = jest.spyOn(eslint, 'CLIEngine');
eslintCLIEngineSpy.mockImplementation(() => ({
executeOnFiles: jest.fn().mockImplementation(() => ({
errorCount: 0,
results: {
length: Number.POSITIVE_INFINITY
}
})),
resolveFileGlobPatterns: jest.fn()
}));

// For happy paths, mock the file existing.
existsSyncSpy = jest.spyOn(fs, 'existsSync');
existsSyncSpy.mockImplementation(() => true);

mockConsoleLog = jest.spyOn(console, 'log');
mockConsoleLog.mockImplementation(noop);

mockConsoleWarn = jest.spyOn(console, 'warn');
mockConsoleWarn.mockImplementation(noop);

mockProcessExit = jest.spyOn(process, 'exit');
mockProcessExit.mockImplementation(noop);
});
afterEach(() => {
eslintCLIEngineSpy.mockClear();
existsSyncSpy.mockClear();
mockConsoleLog.mockClear();
mockConsoleWarn.mockClear();
mockProcessExit.mockClear();
});
afterAll(() => {
eslintCLIEngineSpy.mockRestore();
existsSyncSpy.mockRestore();
mockConsoleLog.mockRestore();
mockConsoleWarn.mockRestore();
mockProcessExit.mockRestore();
});

test('it is a function', () => {
expect(plugin.handler).toBeInstanceOf(Function);
});

test('it returns undefined', async () => {
const actual = await plugin.handler(mockContext, mockArgs);

expect(actual).toBeUndefined();
});

test("it throws if the schema doesn't exist locally", async () => {
// Mock the file not existing.
existsSyncSpy.mockImplementationOnce(() => false);

await plugin.handler(mockContext, mockArgs);

expect(existsSyncSpy).toHaveBeenCalled();
expect(mockContext.spinner.fail).toHaveBeenCalled();
expect(mockProcessExit).toHaveBeenCalledWith(1);
});

test('it creates a validator with the correct configuration', async () => {
const expectedRule = [
'error',
// These objects are derived from mockArgs.
{
env: 'apollo',
projectName: 'myApp'
},
{
env: 'literal',
projectName: 'myApp'
}
];

await plugin.handler(mockContext, mockArgs);

const lintConfiguration = eslintCLIEngineSpy.mock.calls[0][0];

const keys = Object.keys(lintConfiguration);
expect(keys).toHaveLength(4);
expect(keys).toContain('parser');
expect(keys).toContain('plugins');
expect(keys).toContain('rules');
expect(keys).toContain('useEslintrc');

expect(lintConfiguration.parser).toBe('babel-eslint');

expect(lintConfiguration.plugins).toHaveLength(1);
expect(lintConfiguration.plugins).toContain('graphql');

const rulesKeys = Object.keys(lintConfiguration.rules);
expect(rulesKeys).toHaveLength(2);
expect(rulesKeys).toContain('graphql/template-strings');
expect(rulesKeys).toContain('graphql/no-deprecated-fields');

const templateStringsRule =
lintConfiguration.rules['graphql/template-strings'];
expect(templateStringsRule).toEqual(expectedRule);

const deprecatedFieldsRule =
lintConfiguration.rules['graphql/no-deprecated-fields'];
expect(deprecatedFieldsRule).toEqual(expectedRule);

expect(lintConfiguration.useEslintrc).toBe(false);
});

test('it logs an appropriate message when there are no errors', async () => {
await plugin.handler(mockContext, mockArgs);

expect(mockConsoleLog).toHaveBeenCalled();
expect(mockProcessExit).toHaveBeenCalledWith(0);
});

test('it warns when there are errors', async () => {
eslintCLIEngineSpy.mockImplementationOnce(() => ({
executeOnFiles: jest.fn().mockImplementation(() => ({
errorCount: Number.POSITIVE_INFINITY,
results: {
length: Number.POSITIVE_INFINITY
}
})),
getFormatter: jest.fn().mockImplementation(() => {
return function() {};
}),
resolveFileGlobPatterns: jest.fn()
}));

await plugin.handler(mockContext, mockArgs);

expect(mockConsoleWarn).toHaveBeenCalled();
expect(mockProcessExit).toHaveBeenCalledWith(1);
});
});
Loading