Repository: infinitered/solidarity (ts)
Issue: Friendly error not working for dir and file rule type
Version : 3.0.0

Steps :
- Create file or dir type rule with custom friendly error message.

```
{
  "requirements": {
    "TypeScript Active": [
      { "rule": "dir", "location": "./src1", "error": "Did you get this code from npm? Try GitHub!" },
      { "rule": "file", "location": "./1.txt", "error": "You don't have 1.txt. Go get it.!" }
    ]
  }
}
```
- run `solidarity`
- Both rules failed as expected. But doesn't print friendly error message.

Expected : 
Print user specified custom error message from rule.

Used to work in 2.3.1

Pull request description: fix custom error bug, add test, fix typo, refactor test
Fix #256, added test and refactor some tests around custom errors

Patch:
diff --git a/.travis.yml b/.travis.yml
index e538c8d..0a94e3c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,7 +8,7 @@ before_script:
   - tsc
 script:
   - 'npm run test:ci'
-  - yarn danger ci
+#  - yarn danger ci
 after_success:
   - './node_modules/.bin/jest --coverage --runInBand && cat ./coverage/lcov.info | ./node_modules/.bin/codecov'
 notifications:
diff --git a/src/extensions/functions/checkDir.ts b/src/extensions/functions/checkDir.ts
index 9363a15..0fe666f 100644
--- a/src/extensions/functions/checkDir.ts
+++ b/src/extensions/functions/checkDir.ts
@@ -4,7 +4,9 @@ module.exports = (rule: FSRule, context: SolidarityRunContext): void => {
   const { filesystem } = context
   if (rule.location) {
     const dirPath = path.isAbsolute(rule.location) ? rule.location : path.join(process.cwd(), rule.location)
-    if (filesystem.exists(dirPath) !== 'dir') throw new Error(`Location '${rule.location}' is not a directory`)
+    if (filesystem.exists(dirPath) !== 'dir') {
+      throw new Error(rule.error || `Location '${rule.location}' is not a directory`)
+    }
   } else {
     throw new Error(`No location for directory rule`)
   }
diff --git a/src/extensions/functions/checkFile.ts b/src/extensions/functions/checkFile.ts
index 101fda5..517483a 100644
--- a/src/extensions/functions/checkFile.ts
+++ b/src/extensions/functions/checkFile.ts
@@ -4,7 +4,9 @@ module.exports = (rule: FSRule, context: SolidarityRunContext): void => {
   const { filesystem } = context
   if (rule.location) {
     const filePath = path.isAbsolute(rule.location) ? rule.location : path.join(process.cwd(), rule.location)
-    if (filesystem.exists(filePath) !== 'file') throw new Error(`Location '${rule.location}' is not a file`)
+    if (filesystem.exists(filePath) !== 'file') {
+      throw new Error(rule.error || `Location '${rule.location}' is not a file`)
+    }
   } else {
     throw new Error(`No location for file rule`)
   }


Test patch:
diff --git a/__tests__/command_helpers/checkCLI.ts b/__tests__/command_helpers/checkCLI.ts
index d7e8d84..59a4219 100644
--- a/__tests__/command_helpers/checkCLI.ts
+++ b/__tests__/command_helpers/checkCLI.ts
@@ -44,7 +44,7 @@ test('returns message on improper version', async () => {
   context.solidarity.getVersion = () => '1'
   const message = `${outOfDateCLI.binary}: you have '1', but the project requires '${outOfDateCLI.semver}'`
 
-  await expect(checkCLI(outOfDateCLI, context)).rejects.toThrow()
+  await expect(checkCLI(outOfDateCLI, context)).rejects.toThrowError(message)
 })
 
 test('returns message with injected versions', async () => {
@@ -52,5 +52,5 @@ test('returns message with injected versions', async () => {
   solidarityExtension(context)
   context.solidarity.getVersion = () => '7.5'
 
-  await expect(checkCLI(injectVersion, context)).rejects.toThrow()
+  await expect(checkCLI(injectVersion, context)).rejects.toThrowError(message)
 })
diff --git a/__tests__/command_helpers/checkDir.ts b/__tests__/command_helpers/checkDir.ts
index d551ca6..b8004b1 100644
--- a/__tests__/command_helpers/checkDir.ts
+++ b/__tests__/command_helpers/checkDir.ts
@@ -27,3 +27,10 @@ test('checkDir throws if no location is set', () => {
     checkDir({}, context)
   }).toThrow()
 })
+
+test('checkDir throws custom error if set', () => {
+  const customError = 'customError'
+  expect(() => {
+    checkDir({ location: 'DOES_NOT_EXIST', error: customError }, context)
+  }).toThrowError(customError)
+})
diff --git a/__tests__/command_helpers/checkRequirement.ts b/__tests__/command_helpers/checkRequirement.ts
index 3aa907c..2373582 100644
--- a/__tests__/command_helpers/checkRequirement.ts
+++ b/__tests__/command_helpers/checkRequirement.ts
@@ -16,10 +16,14 @@ const checkDir = require('../../src/extensions/functions/checkDir')
 jest.mock('../../src/extensions/functions/checkENV')
 const checkENV = require('../../src/extensions/functions/checkENV')
 
-// Setup checkENV
+// Setup checkFile
 jest.mock('../../src/extensions/functions/checkFile')
 const checkFile = require('../../src/extensions/functions/checkFile')
 
+// Setup checkShell
+jest.mock('../../src/extensions/functions/checkShell')
+const checkShell = require('../../src/extensions/functions/checkShell')
+
 const context = require('gluegun/toolbox')
 
 const badRule = toPairs({
@@ -252,44 +256,50 @@ describe('checkRequirement', () => {
     })
 
     test('failed ENV rule with custom message', async () => {
-      checkCLI.mockClear()
-      checkCLI.mockImplementation(() => true)
+      checkENV.mockClear()
+      checkENV.mockImplementation(() => customError)
       const rule = toPairs({
         BADENV: [{ rule: 'env', variable: 'gazorpazorp', error: customError }],
       })[0]
 
       const listrTask = await checkRequirement(rule, context)
       const result = await listrTask.storedInit[0].task()
-      expect(result).toEqual(undefined)
+      expect(result).toEqual(customError)
     })
 
-    // test('failed DIR rule with custom message', async () => {
-    //   const rule = toPairs({
-    //     YARN: [{ rule: 'dir', location: 'gazorpazorp', error: customError }],
-    //   })[0]
+    test('failed DIR rule with custom message', async () => {
+      checkDir.mockClear()
+      checkDir.mockImplementation(() => customError)
+      const rule = toPairs({
+        YARN: [{ rule: 'dir', location: 'gazorpazorp', error: customError }],
+      })[0]
 
-    //   const listrTask = await checkRequirement(rule, context)
-    //   const result = await listrTask.storedInit[0].task()
-    //   expect(result).toEqual(false)
-    // })
+      const listrTask = await checkRequirement(rule, context)
+      const result = await listrTask.storedInit[0].task()
+      expect(result).toEqual(customError)
+    })
 
-    // test('failed FILE rule with custom message', async () => {
-    //   const rule = toPairs({
-    //     YARN: [{ rule: 'file', location: 'gazorpazorp', error: customError }],
-    //   })[0]
+    test('failed FILE rule with custom message', async () => {
+      checkFile.mockClear()
+      checkFile.mockImplementation(() => customError)
+      const rule = toPairs({
+        YARN: [{ rule: 'file', location: 'gazorpazorp', error: customError }],
+      })[0]
 
-    //   const listrTask = await checkRequirement(rule, context)
-    //   const result = await listrTask.storedInit[0].task()
-    //   expect(result).toEqual(undefined)
-    // })
+      const listrTask = await checkRequirement(rule, context)
+      const result = await listrTask.storedInit[0].task()
+      expect(result).toEqual(customError)
+    })
 
-    // test('failed SHELL rule with custom message', async () => {
-    //   const rule = toPairs({
-    //     YARN: [{ rule: 'shell', match: 'hello', command: 'mocked', error: customError }],
-    //   })[0]
-    //   const listrTask = await checkRequirement(rule, context)
-    //   const result = await listrTask.storedInit[0].task()
-    //   expect(result).toEqual(customError)
-    // })
+    test('failed SHELL rule with custom message', async () => {
+      checkShell.mockClear()
+      checkShell.mockImplementation(() => customError)
+      const rule = toPairs({
+        YARN: [{ rule: 'shell', match: 'hello', command: 'mocked', error: customError }],
+      })[0]
+      const listrTask = await checkRequirement(rule, context)
+      const result = await listrTask.storedInit[0].task()
+      expect(result).toEqual(customError)
+    })
   })
 })