-
Notifications
You must be signed in to change notification settings - Fork 683
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
Validate Queries Plugin #1004
Changes from 3 commits
a156a48
4485573
1307c90
40cc0f3
0741250
3fa6405
f3db63e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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' | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2019 Adobe Inc. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this an official thing? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
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) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I LOVE this documentation. Thank you for making it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just noticed a table formatting error, pushing commit now. |
||
|
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); | ||
}); | ||
}); |
There was a problem hiding this comment.
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.