diff --git a/.github/workflows/jest.yml b/.github/workflows/jest.yml new file mode 100644 index 0000000..dfcf5ed --- /dev/null +++ b/.github/workflows/jest.yml @@ -0,0 +1,61 @@ +name: Jest +on: push +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup Node.js + uses: actions/setup-node@v1 + with: + node-version: "14" + + # Speed up subsequent runs with caching + - name: Cache node modules + uses: actions/cache@v2 + env: + cache-name: cache-node-modules + with: + # npm cache files are stored in `~/.npm` on Linux/macOS + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + # Install required deps for action + - name: Install Dependencies + run: npm install + + # Finally, run our tests + - name: Run the tests + run: npm test + + - name: Tests ✅ + if: ${{ success() }} + run: | + curl --request POST \ + --url https://api.github.com/repos/${{ github.repository }}/statuses/${{ github.sha }} \ + --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \ + --header 'content-type: application/json' \ + --data '{ + "context": "tests", + "state": "success", + "description": "Tests passed", + "target_url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + }' + + - name: Tests 🚨 + if: ${{ failure() }} + run: | + curl --request POST \ + --url https://api.github.com/repos/${{ github.repository }}/statuses/${{ github.sha }} \ + --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \ + --header 'content-type: application/json' \ + --data '{ + "context": "tests", + "state": "failure", + "description": "Tests failed", + "target_url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + }' \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..87018c7 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,23 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug Jest Tests", + "type": "node", + "request": "launch", + "runtimeArgs": [ + "--inspect-brk", + "--experimental-vm-modules" + ], + "program": "${workspaceRoot}/node_modules/.bin/jest", + "args": [ + "--runInBand" + ], + "skipFiles": [ + "/**" + ], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + }, + ] +} \ No newline at end of file diff --git a/domain/article.js b/domain/article.js index 46432fe..ab93005 100644 --- a/domain/article.js +++ b/domain/article.js @@ -12,6 +12,10 @@ export class Article { this.contentFilePath = contentFilePath; } + /** + * Resets the cached properties. + * This is useful if the content of an article is changed through one of the setters. + */ clearData(){ this._html = null; this._markdown = null; @@ -21,6 +25,9 @@ export class Article { this._emphasizedTextData = null; } + /** + * Returns the path of the folder containg an article + */ get path(){ if(this._basePath) return this._basePath; this._basePath = this._contentFilePath ? path.dirname(this._contentFilePath) : null; @@ -57,6 +64,9 @@ export class Article { this._rawData = data; } + /** + * Returns the raw text of an article document. + */ get rawData(){ if(this._rawData) return this._rawData; diff --git a/index.js b/index.js index 8fe7e23..f7901f5 100644 --- a/index.js +++ b/index.js @@ -9,5 +9,6 @@ import { validateSyntaxSpecifiers } from './validations/code-blocks.js'; import { validateNestedLists } from './validations/lists.js'; import { validateBrokenLinks } from './validations/links.js'; import { ConfigManager } from './logic/config-manager.js'; +import { validateFolderName } from './validations/naming.js'; -export { Validator, ArticleManager, validateDuplicatedOpeningHeading, validateHeadingsNesting, validateMaxLength, validateNumberedHeadings, validateOpeningHeadingLevel, validateSpacing, validateTitleCase, validateMetaData, validateRules, validateImageDescriptions, validateImagePaths, validateReferencedAssets, validateSVGFiles, validateSyntaxSpecifiers, validateNestedLists, validateBrokenLinks, ConfigManager } \ No newline at end of file +export { Validator, ArticleManager, validateDuplicatedOpeningHeading, validateHeadingsNesting, validateMaxLength, validateNumberedHeadings, validateOpeningHeadingLevel, validateSpacing, validateTitleCase, validateMetaData, validateRules, validateImageDescriptions, validateImagePaths, validateReferencedAssets, validateSVGFiles, validateSyntaxSpecifiers, validateNestedLists, validateBrokenLinks, ConfigManager, validateFolderName } \ No newline at end of file diff --git a/package.json b/package.json index 5c55eec..d302d0e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "content-lint", - "version": "1.0.0", + "version": "1.1.0", "type": "module", "main": "index.js", "description": "A node.js infrastructure to validate markdown content.", diff --git a/test/metadata.test.js b/test/metadata.test.js index 7467e16..53f5ed8 100644 --- a/test/metadata.test.js +++ b/test/metadata.test.js @@ -36,7 +36,7 @@ articleA.rawData = contentA; const articleB = new Article(); articleB.rawData = contentB; -const schemaPath = "./rules/tutorial-metadata-schema.json"; +const schemaPath = "./test/resources/tutorial-metadata-schema.json"; test('Tests if missing/superfluous property is detected', () => { const errors = validateMetaData(articleA, schemaPath); diff --git a/test/naming.test.js b/test/naming.test.js new file mode 100644 index 0000000..65c4858 --- /dev/null +++ b/test/naming.test.js @@ -0,0 +1,20 @@ +import { Article } from '../domain/article.js' +import { validateFolderName } from '../validations/naming.js'; + +const articleA = new Article(); +articleA.path = "./demo/this_is_Not_compliant"; + +const articleB = new Article(); +articleB.path = "./demo/this-should-pass"; + +test('Tests if non-compliant article folder name is detected', () => { + const errors = validateFolderName(articleA); + console.log(errors); + expect(errors.length).toBe(2); +}); + +test('Tests if compliant article folder name passes validation', () => { + const errors = validateFolderName(articleB); + console.log(errors); + expect(errors.length).toBe(0); +}); \ No newline at end of file diff --git a/test/resources/tutorial-metadata-schema.json b/test/resources/tutorial-metadata-schema.json new file mode 100644 index 0000000..c885a6a --- /dev/null +++ b/test/resources/tutorial-metadata-schema.json @@ -0,0 +1,89 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "beta": { + "type": "boolean" + }, + "source" : { + "type": "string" + }, + "title": { + "type": "string", + "maxLength": 60 + }, + "coverImage": { + "type": "string" + }, + "author": { + "type": "string" + }, + "tags": { + "type": "array", + "items": [ + { + "type": "string" + } + ] + }, + "description": { + "type": "string" + }, + "difficulty": { + "type": "string" + }, + "hero_position": { + "type": "integer" + }, + "featuredImage": { + "type": "string" + }, + "software": { + "type": "array", + "items": [ + { + "type": "string" + } + ] + }, + "compatible-products":{ + "type": "array", + "items": [ + { + "type": "string" + } + ] + }, + "hardware": { + "type": "array", + "items": [ + { + "type": "string" + } + ] + }, + "libraries": { + "type": "array", + "items": [ + { + "type": "object", + "properties": { + "name" : { + "type" : "string" + }, + "url" : { + "type" : "string" + } + } + } + ] + } + }, + "required": [ + "title", + "tags", + "description", + "author" + ], + "additionalProperties" : false + } diff --git a/validations/naming.js b/validations/naming.js new file mode 100644 index 0000000..3183c7e --- /dev/null +++ b/validations/naming.js @@ -0,0 +1,30 @@ +import { ValidationIssue } from '../domain/validation-issue.js'; + +/** + * Checks if the foldername contains any discouraged underscores, spaces or upper-case letters. + * @param {Article} article + * @returns an array containg the occurred validation errors + */ +function validateFolderName(article) { + let errorsOccurred = []; + const issueType = ValidationIssue.Type.WARNING; + + if(article.path.indexOf("_") != -1){ + const errorMessage = "Folder path uses discouraged underscore."; + errorsOccurred.push(new ValidationIssue(errorMessage, article.path, issueType)); + } + + if(article.path.indexOf(" ") != -1){ + const errorMessage = "Folder path uses discouraged blanks."; + errorsOccurred.push(new ValidationIssue(errorMessage, article.path, issueType)); + } + + if(article.path != article.path.toLowerCase()){ + const errorMessage = "Folder path uses discouraged non-lower case characters."; + errorsOccurred.push(new ValidationIssue(errorMessage, article.path, issueType)); + } + + return errorsOccurred; +} + +export { validateFolderName } \ No newline at end of file