-
Notifications
You must be signed in to change notification settings - Fork 7
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
chore(cli): refactor checks into decorators #121
Conversation
Pull Request Report PR Title ✅ Title follows the conventional commit spec. |
@@ -19,6 +19,7 @@ | |||
"cli-ux": "^5.5.1", | |||
"coveo.analytics": "^2.18.4", | |||
"create-react-app": "^4.0.3", | |||
"dedent": "^0.7.0", |
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.
Neat library to avoid oopsies in indentation with template string
https://www.npmjs.com/package/dedent
"@types/fs-extra": "^9.0.6", | ||
"@types/jest": "^26.0.22", |
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.
To have describe in here.
"@types/node": "^10", | ||
"chai": "^4.2.0", | ||
"jest": "^26.6.3", | ||
"patch-package": "^6.2.2", | ||
"rimraf": "^3.0.2", | ||
"ts-jest": "^26.5.1", | ||
"ts-node": "^8", | ||
"typescript": "^3.3" | ||
"typescript": "4.2.3" |
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.
Could be reverted:
I wanted to migrate the tsconfig to a composite project to split the test tsconfig from the source tsconfig (to avoid having describe everywhere), but was unable because of oclif/oclif#299.
Oclif went hay-wire when I tried to split it.
@@ -75,12 +75,12 @@ describe('auth:login', () => { | |||
test | |||
.command(['auth:login', '-e', 'foo']) | |||
.catch(/Expected --environment=foo/) | |||
.it('reject invalid environment'); | |||
.it('reject invalid environment', async () => {}); |
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.
I noted timing issue and promise timing out here.
I'm honestly not too sure of what's going in in this tests, the syntax is really painful to read.
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.
the syntax is really painful to read.
What it does:
* start a test
* execute the command auth:login -e foo -> try to login in environment "foo" which is not a valid environment
* catch the error, and regex match on `/Expected --environment=foo/` on the output (stderr)
* .it(the description of the test)
https://oclif.io/docs/testing and https://github.com/oclif/fancy-test for more documentation
That's how all the UT for the commands themselves will look like, so better get used to it ;)
|
||
static description = `Create a Coveo Headless-powered search page with the React web framework. See ${linkToReadme}`; | ||
static description = |
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.
Because the page about the command was really shallow after reworking it, I removed the link.
declare global { | ||
namespace NodeJS { | ||
interface Global { | ||
config: IConfig; | ||
} | ||
} | ||
} |
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.
I put the typing shenanigan in a declaration file to keep things tidy.
expect(fakeCommand.warn).toHaveBeenCalledWith(dedent` | ||
Required version invalid: "foo". | ||
Please report this error to Coveo: https://github.com/coveo/cli/issues/new | ||
`); |
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.
See below for more explanation ;)
}; | ||
|
||
await expect( | ||
IsNodeVersionAbove('0.0.1')((fakeCommand as unknown) as Command) |
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.
I prolly could do a bit cleaner than the double cast with some Jest-pirouette, but I didn't think it was worth the hassle.
target.warn(dedent` | ||
Required version invalid: "${requiredVersion}". | ||
Please report this error to Coveo: https://github.com/coveo/cli/issues/new | ||
`); |
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.
If this warning is triggered, there's a big chance that it's not the user machine that ain't properly set up, but that we messed up and didn't call properly the function.
Hopefully, this code will never run, but if it does, at least the user will know what to do (contact us so we can fix it).
export function warnHowToInstallNode(target: Command) { | ||
target.warn(dedent` | ||
Please visit ${linkToReadme} for more detailed installation information. | ||
`); | ||
} | ||
|
||
export function isMissingExecutable(output: SpawnProcessOutput) { | ||
return ( | ||
output.exitCode !== null && | ||
Math.abs(output.exitCode) === constants.errno.ENOENT | ||
); | ||
} |
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.
Will be refactorized on a subsequent PR as utils, for now, that was not worth it. (when introducing decorator for Angular).
) => Boolean | Promise<Boolean>; | ||
|
||
export function Preconditions(...preconditions: PreconditionFunction[]) { | ||
return function ( |
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.
Fun anecdote: don't put async here, typings get messy (rightfully so)
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.
Nice Job
@@ -75,12 +75,12 @@ describe('auth:login', () => { | |||
test | |||
.command(['auth:login', '-e', 'foo']) | |||
.catch(/Expected --environment=foo/) | |||
.it('reject invalid environment'); | |||
.it('reject invalid environment', async () => {}); |
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.
the syntax is really painful to read.
What it does:
* start a test
* execute the command auth:login -e foo -> try to login in environment "foo" which is not a valid environment
* catch the error, and regex match on `/Expected --environment=foo/` on the output (stderr)
* .it(the description of the test)
https://oclif.io/docs/testing and https://github.com/oclif/fancy-test for more documentation
That's how all the UT for the commands themselves will look like, so better get used to it ;)
describe('if all preconditions succeed', () => { | ||
it('should executes preconditions the original command', async () => { | ||
let counter = 0; | ||
const orderChecker = jest.fn(); |
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.
I see you put quite a lot of emphasis in the tests on "the order is respected".
Is that really an important/meaningful thing to test ?
I guess it can be, if we have preconditions that depends on one another, but right now it does not seem to be the case that we have such a need.
The most important thing to verify is that the original function is only called after all preconditions had a chance to execute.
Maybe we could simplify the test to only focus on this type of verification ?
}); | ||
|
||
describe('if one precondition failed', () => { | ||
it('should executes all the preconditions prior to the failing one and nothing else', async () => { |
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.
Similar comment as above, is that really something that we are interested in ?
If all preconditions are independant from one another, the order is not actually important, just that the "command" does not get executed if at least one precondition fails..
}; | ||
|
||
await expect( | ||
IsNpxInstalled()((fakeCommand as unknown) as Command) |
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.
For readability's sake, I would add a function getFakeCommand(): Command {...}
util function that does the cast + boilerplate.
That you could reuse for (I think?) all the tests in this file.
IsNpxInstalled()((fakeCommand as unknown) as Command) | ||
).resolves.toBe(false); | ||
expect(fakeCommand.warn).toHaveBeenCalledTimes(2); | ||
expect(fakeCommand.warn).toHaveBeenCalledWith(dedent` |
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.
I'm not sure I would have a test that verifies the "exact same message contained in the command, just copy pasted".
Whenever we want to change a comma for the warning message, the tests will start to fail for no "good" reason.
I would focus on regex matching a couple important part: the readme link, a "high level check" on some important part of the message (eg: /some random error oh no/
or /a valid npx installation/
, stuff like this).
The exact wording itself does not seem very important to validate with a UT.
Unit Tested 💯
CDX-203