diff --git a/.github/linters/.eslintrc.yml b/.github/linters/.eslintrc.yml index 51ecb52..73bb944 100644 --- a/.github/linters/.eslintrc.yml +++ b/.github/linters/.eslintrc.yml @@ -7,25 +7,25 @@ globals: SharedArrayBuffer: readonly ignorePatterns: - - "node_modules/" - - "dist/" - - "coverage/" - - "__tests__/" - - "*.json" - - "vitest.config.ts" - - "**/.*" + - 'node_modules/' + - 'dist/' + - 'coverage/' + - '__tests__/' + - '*.json' + - 'vitest.config.ts' + - '**/.*' -parser: "@typescript-eslint/parser" +parser: '@typescript-eslint/parser' parserOptions: ecmaVersion: 2023 sourceType: module project: - - "./tsconfig.json" + - './tsconfig.json' plugins: - vitest - - "@typescript-eslint" + - '@typescript-eslint' extends: - eslint:recommended @@ -33,48 +33,48 @@ extends: - plugin:@typescript-eslint/recommended rules: - camelcase: "off" - eslint-comments/no-use: "off" - eslint-comments/no-unused-disable: "off" - i18n-text/no-en: "off" - import/no-namespace: "off" - no-console: "off" - no-unused-vars: "off" - semi: "off" - "@typescript-eslint/array-type": "error" - "@typescript-eslint/await-thenable": "error" - "@typescript-eslint/ban-ts-comment": "error" - "@typescript-eslint/consistent-type-assertions": "error" - "@typescript-eslint/explicit-member-accessibility": - - "error" - - accessibility: "no-public" - "@typescript-eslint/explicit-function-return-type": - - "error" + camelcase: 'off' + eslint-comments/no-use: 'off' + eslint-comments/no-unused-disable: 'off' + i18n-text/no-en: 'off' + import/no-namespace: 'off' + no-console: 'off' + no-unused-vars: 'off' + semi: 'off' + '@typescript-eslint/array-type': 'error' + '@typescript-eslint/await-thenable': 'error' + '@typescript-eslint/ban-ts-comment': 'error' + '@typescript-eslint/consistent-type-assertions': 'error' + '@typescript-eslint/explicit-member-accessibility': + - 'error' + - accessibility: 'no-public' + '@typescript-eslint/explicit-function-return-type': + - 'error' - allowExpressions: true - "@typescript-eslint/no-array-constructor": "error" - "@typescript-eslint/no-empty-interface": "error" - "@typescript-eslint/no-explicit-any": "warn" - "@typescript-eslint/no-extraneous-class": "error" - "@typescript-eslint/no-for-in-array": "error" - "@typescript-eslint/no-inferrable-types": "error" - "@typescript-eslint/no-misused-new": "error" - "@typescript-eslint/no-namespace": "error" - "@typescript-eslint/no-non-null-assertion": "warn" - "@typescript-eslint/no-require-imports": "error" - "@typescript-eslint/no-unnecessary-qualifier": "error" - "@typescript-eslint/no-unnecessary-type-assertion": "error" - "@typescript-eslint/no-unused-vars": - - "error" - - argsIgnorePattern: "^_" - varsIgnorePattern: "^_" - "@typescript-eslint/no-useless-constructor": "error" - "@typescript-eslint/no-var-requires": "error" - "@typescript-eslint/prefer-for-of": "warn" - "@typescript-eslint/prefer-function-type": "warn" - "@typescript-eslint/prefer-includes": "error" - "@typescript-eslint/prefer-string-starts-ends-with": "error" - "@typescript-eslint/promise-function-async": "error" - "@typescript-eslint/require-array-sort-compare": "error" - "@typescript-eslint/restrict-plus-operands": "error" - "@typescript-eslint/space-before-function-paren": "off" - "@typescript-eslint/unbound-method": "error" + '@typescript-eslint/no-array-constructor': 'error' + '@typescript-eslint/no-empty-interface': 'error' + '@typescript-eslint/no-explicit-any': 'warn' + '@typescript-eslint/no-extraneous-class': 'error' + '@typescript-eslint/no-for-in-array': 'error' + '@typescript-eslint/no-inferrable-types': 'error' + '@typescript-eslint/no-misused-new': 'error' + '@typescript-eslint/no-namespace': 'error' + '@typescript-eslint/no-non-null-assertion': 'warn' + '@typescript-eslint/no-require-imports': 'error' + '@typescript-eslint/no-unnecessary-qualifier': 'error' + '@typescript-eslint/no-unnecessary-type-assertion': 'error' + '@typescript-eslint/no-unused-vars': + - 'error' + - argsIgnorePattern: '^_' + varsIgnorePattern: '^_' + '@typescript-eslint/no-useless-constructor': 'error' + '@typescript-eslint/no-var-requires': 'error' + '@typescript-eslint/prefer-for-of': 'warn' + '@typescript-eslint/prefer-function-type': 'warn' + '@typescript-eslint/prefer-includes': 'error' + '@typescript-eslint/prefer-string-starts-ends-with': 'error' + '@typescript-eslint/promise-function-async': 'error' + '@typescript-eslint/require-array-sort-compare': 'error' + '@typescript-eslint/restrict-plus-operands': 'error' + '@typescript-eslint/space-before-function-paren': 'off' + '@typescript-eslint/unbound-method': 'error' diff --git a/.github/workflows/check-dist.yml b/.github/workflows/check-dist.yml index 61efb24..ab528fc 100644 --- a/.github/workflows/check-dist.yml +++ b/.github/workflows/check-dist.yml @@ -60,4 +60,4 @@ jobs: uses: actions/upload-artifact@v4 with: name: dist - path: dist/ \ No newline at end of file + path: dist/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d666343..1021497 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,4 +70,4 @@ jobs: if [ "$(cat .github/sample.feature)" != "${{ steps.test-action.outputs.feature }}" ]; then echo "The output does not match the expected feature" exit 1 - fi \ No newline at end of file + fi diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index e80ebd7..5f8e060 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -45,4 +45,4 @@ jobs: - name: Perform CodeQL Analysis id: analyze - uses: github/codeql-action/analyze@v3 \ No newline at end of file + uses: github/codeql-action/analyze@v3 diff --git a/.prettierrc.json b/.prettierrc.json index 79333bb..a378146 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -13,4 +13,4 @@ "proseWrap": "always", "htmlWhitespaceSensitivity": "css", "endOfLine": "lf" -} \ No newline at end of file +} diff --git a/README.md b/README.md index 52543d2..0455311 100644 --- a/README.md +++ b/README.md @@ -7,14 +7,19 @@ -[issues]: https://docs.github.com/en/issues/tracking-your-work-with-issues/creating-an-issue -[pull requests]: https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/working-with-your-remote-repository-on-github-or-github-enterprise/creating-an-issue-or-pull-request +[issues]: + https://docs.github.com/en/issues/tracking-your-work-with-issues/creating-an-issue +[pull requests]: + https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/working-with-your-remote-repository-on-github-or-github-enterprise/creating-an-issue-or-pull-request # Gherkin feature extractor from GitHub issues -GitHub Action that processes Gherkin content from issues and turn them into a content that can be then saved into a file. +GitHub Action that processes Gherkin content from issues and turn them into a +content that can be then saved into a file. -It reads the issues from a repository and extracts the Gherkin content from them. The Gherkin content is then saved into a file. This expects the issue to contains a Gherkin content in the body of the issue, like the following: +It reads the issues from a repository and extracts the Gherkin content from +them. The Gherkin content is then saved into a file. This expects the issue to +contains a Gherkin content in the body of the issue, like the following: ```gherkin Feature: As a user, I want to be able to login to the system @@ -25,7 +30,8 @@ Feature: As a user, I want to be able to login to the system Then the user is logged in ``` -The action will extract the Gherkin content from the issue and provide it as a parameter. You can then save as a **feature** file. +The action will extract the Gherkin content from the issue and provide it as a +parameter. You can then save as a **feature** file. # Usage @@ -46,7 +52,8 @@ The action will extract the Gherkin content from the issue and provide it as a p # Example, Current Repository -Runs when an issue is created. Extracts the Gherkin content from the issue and saves it to a file. +Runs when an issue is created. Extracts the Gherkin content from the issue and +saves it to a file. ```yaml on: @@ -69,7 +76,8 @@ jobs: # Example, Current Repository but more verbose -Runs when an issue is created. Extracts the Gherkin content from the issue and saves it to a file. +Runs when an issue is created. Extracts the Gherkin content from the issue and +saves it to a file. ```yaml on: diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts index b592d28..c3b2b82 100644 --- a/__tests__/main.test.ts +++ b/__tests__/main.test.ts @@ -1,554 +1,554 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; -import * as core from "@actions/core"; -import { Octokit } from "@octokit/rest"; -import { run } from "../src/main"; - -vi.mock("@actions/core"); -vi.mock("@octokit/rest"); - -describe("run function", () => { - const mockGetInput = vi.fn(); - const mockGetBooleanInput = vi.fn(); - const mockSetOutput = vi.fn(); - const mockSetFailed = vi.fn(); - const mockNotice = vi.fn(); - const mockInfo = vi.fn(); - const mockWarning = vi.fn(); - const mockIssuesGet = vi.fn(); - const mockIssuesUpdate = vi.fn(); +import { describe, it, expect, vi, beforeEach } from 'vitest' +import * as core from '@actions/core' +import { Octokit } from '@octokit/rest' +import { run } from '../src/main' + +vi.mock('@actions/core') +vi.mock('@octokit/rest') + +describe('run function', () => { + const mockGetInput = vi.fn() + const mockGetBooleanInput = vi.fn() + const mockSetOutput = vi.fn() + const mockSetFailed = vi.fn() + const mockNotice = vi.fn() + const mockInfo = vi.fn() + const mockWarning = vi.fn() + const mockIssuesGet = vi.fn() + const mockIssuesUpdate = vi.fn() beforeEach(() => { - vi.resetAllMocks(); + vi.resetAllMocks() - core.getInput = mockGetInput; - core.getBooleanInput = mockGetBooleanInput; - core.setOutput = mockSetOutput; - core.setFailed = mockSetFailed; - core.notice = mockNotice; - core.info = mockInfo; - core.warning = mockWarning; + core.getInput = mockGetInput + core.getBooleanInput = mockGetBooleanInput + core.setOutput = mockSetOutput + core.setFailed = mockSetFailed + core.notice = mockNotice + core.info = mockInfo + core.warning = mockWarning Octokit.prototype.issues = { get: mockIssuesGet, - update: mockIssuesUpdate, - } as never; - }); + update: mockIssuesUpdate + } as never + }) - it("should extract Gherkin content from the issue body", async () => { + it('should extract Gherkin content from the issue body', async () => { // Mock inputs - mockGetInput.mockImplementation((key) => { - if (key === "token") return "fake-token"; - if (key === "owner") return "octocat"; - if (key === "repo") return "octocat/example-repo"; - if (key === "issue") return "1"; - if (key === "update_title") return "true"; - return undefined; - }); + mockGetInput.mockImplementation(key => { + if (key === 'token') return 'fake-token' + if (key === 'owner') return 'octocat' + if (key === 'repo') return 'octocat/example-repo' + if (key === 'issue') return '1' + if (key === 'update_title') return 'true' + return undefined + }) // Mock GitHub API response mockIssuesGet.mockResolvedValueOnce({ data: { - title: "Test Title", - body: "```gherkin\nFeature: Test Gherkin Content\n```", - user: { login: "octocat" }, - }, - }); + title: 'Test Title', + body: '```gherkin\nFeature: Test Gherkin Content\n```', + user: { login: 'octocat' } + } + }) - await run(); + await run() - expect(mockIssuesGet).toHaveBeenCalledOnce(); - expect(mockSetOutput).toHaveBeenCalledWith("title", "Test Title"); + expect(mockIssuesGet).toHaveBeenCalledOnce() + expect(mockSetOutput).toHaveBeenCalledWith('title', 'Test Title') expect(mockSetOutput).toHaveBeenCalledWith( - "body", - "```gherkin\nFeature: Test Gherkin Content\n```", - ); + 'body', + '```gherkin\nFeature: Test Gherkin Content\n```' + ) expect(mockSetOutput).toHaveBeenCalledWith( - "feature", - "Feature: Test Gherkin Content", - ); + 'feature', + 'Feature: Test Gherkin Content' + ) expect(mockNotice).toHaveBeenCalledWith( - "Gherkin content extracted successfully.", - ); - }); + 'Gherkin content extracted successfully.' + ) + }) - it("should set failure if no Gherkin content is found", async () => { + it('should set failure if no Gherkin content is found', async () => { // Mock inputs - mockGetInput.mockImplementation((key) => { - if (key === "token") return "fake-token"; - if (key === "owner") return "octocat"; - if (key === "repo") return "octocat/example-repo"; - if (key === "issue") return "1"; - return undefined; - }); + mockGetInput.mockImplementation(key => { + if (key === 'token') return 'fake-token' + if (key === 'owner') return 'octocat' + if (key === 'repo') return 'octocat/example-repo' + if (key === 'issue') return '1' + return undefined + }) // Mock GitHub API response with no Gherkin content mockIssuesGet.mockResolvedValueOnce({ data: { - title: "Test Title", - body: "No gherkin content here.", - user: { login: "octocat" }, - }, - }); + title: 'Test Title', + body: 'No gherkin content here.', + user: { login: 'octocat' } + } + }) - await run(); + await run() - expect(mockIssuesGet).toHaveBeenCalledOnce(); + expect(mockIssuesGet).toHaveBeenCalledOnce() expect(mockSetFailed).toHaveBeenCalledWith( - "Error: No Gherkin content found in the issue body.", - ); - }); + 'Error: No Gherkin content found in the issue body.' + ) + }) - it("should set failure if no content is found", async () => { + it('should set failure if no content is found', async () => { // Mock inputs - mockGetInput.mockImplementation((key) => { - if (key === "token") return "fake-token"; - if (key === "owner") return "octocat"; - if (key === "repo") return "octocat/example-repo"; - if (key === "issue") return "1"; - return undefined; - }); + mockGetInput.mockImplementation(key => { + if (key === 'token') return 'fake-token' + if (key === 'owner') return 'octocat' + if (key === 'repo') return 'octocat/example-repo' + if (key === 'issue') return '1' + return undefined + }) // Mock GitHub API response with no Gherkin content mockIssuesGet.mockResolvedValueOnce({ data: { - title: "Test Title", - user: { login: "octocat" }, - }, - }); + title: 'Test Title', + user: { login: 'octocat' } + } + }) - await run(); + await run() - expect(mockIssuesGet).toHaveBeenCalledOnce(); + expect(mockIssuesGet).toHaveBeenCalledOnce() expect(mockSetFailed).toHaveBeenCalledWith( - "Error: No Gherkin content found in the issue body.", - ); - }); + 'Error: No Gherkin content found in the issue body.' + ) + }) - it("should set failure if no param is passed", async () => { + it('should set failure if no param is passed', async () => { // Mock inputs - mockGetInput.mockImplementation((key) => { - if (key === "token") return ""; - if (key === "owner") return ""; - if (key === "repo") return ""; - if (key === "issue") return ""; - return undefined; - }); + mockGetInput.mockImplementation(key => { + if (key === 'token') return '' + if (key === 'owner') return '' + if (key === 'repo') return '' + if (key === 'issue') return '' + return undefined + }) // Mock GitHub API response with no Gherkin content mockIssuesGet.mockResolvedValueOnce({ data: { - title: "Test Title", - body: "No gherkin content here.", - user: { login: "octocat" }, - }, - }); + title: 'Test Title', + body: 'No gherkin content here.', + user: { login: 'octocat' } + } + }) - await run(); + await run() - expect(mockIssuesGet).toHaveBeenCalledOnce(); + expect(mockIssuesGet).toHaveBeenCalledOnce() expect(mockSetFailed).toHaveBeenCalledWith( - "Owner, repo, and issue number are required inputs.", - ); - }); + 'Owner, repo, and issue number are required inputs.' + ) + }) - it("should set failure if no token is passed", async () => { + it('should set failure if no token is passed', async () => { // Mock inputs - mockGetInput.mockImplementation((key) => { - if (key === "token") return ""; - if (key === "owner") return "octocat"; - if (key === "repo") return "octocat/example-repo"; - if (key === "issue") return "1"; - return undefined; - }); + mockGetInput.mockImplementation(key => { + if (key === 'token') return '' + if (key === 'owner') return 'octocat' + if (key === 'repo') return 'octocat/example-repo' + if (key === 'issue') return '1' + return undefined + }) // Mock GitHub API response with no Gherkin content mockIssuesGet.mockResolvedValueOnce({ data: { - title: "Test Title", - body: "No gherkin content here.", - user: { login: "octocat" }, - }, - }); + title: 'Test Title', + body: 'No gherkin content here.', + user: { login: 'octocat' } + } + }) - await run(); + await run() - expect(mockIssuesGet).toHaveBeenCalledOnce(); - expect(mockSetFailed).toHaveBeenCalledWith("GitHub token is required."); - }); + expect(mockIssuesGet).toHaveBeenCalledOnce() + expect(mockSetFailed).toHaveBeenCalledWith('GitHub token is required.') + }) - it("should set failure if no owner is passed", async () => { + it('should set failure if no owner is passed', async () => { // Mock inputs - mockGetInput.mockImplementation((key) => { - if (key === "token") return "fake-token"; - if (key === "owner") return ""; - if (key === "repo") return "octocat/example-repo"; - if (key === "issue") return "1"; - return undefined; - }); + mockGetInput.mockImplementation(key => { + if (key === 'token') return 'fake-token' + if (key === 'owner') return '' + if (key === 'repo') return 'octocat/example-repo' + if (key === 'issue') return '1' + return undefined + }) // Mock GitHub API response with no Gherkin content mockIssuesGet.mockResolvedValueOnce({ data: { - title: "Test Title", - body: "No gherkin content here.", - user: { login: "octocat" }, - }, - }); + title: 'Test Title', + body: 'No gherkin content here.', + user: { login: 'octocat' } + } + }) - await run(); + await run() - expect(mockIssuesGet).toHaveBeenCalledOnce(); + expect(mockIssuesGet).toHaveBeenCalledOnce() expect(mockSetFailed).toHaveBeenCalledWith( - "Owner, repo, and issue number are required inputs.", - ); - }); + 'Owner, repo, and issue number are required inputs.' + ) + }) - it("should set failure if no repo is passed", async () => { + it('should set failure if no repo is passed', async () => { // Mock inputs - mockGetInput.mockImplementation((key) => { - if (key === "token") return "fake-token"; - if (key === "owner") return "octocat"; - if (key === "repo") return ""; - if (key === "issue") return "1"; - return undefined; - }); + mockGetInput.mockImplementation(key => { + if (key === 'token') return 'fake-token' + if (key === 'owner') return 'octocat' + if (key === 'repo') return '' + if (key === 'issue') return '1' + return undefined + }) // Mock GitHub API response with no Gherkin content mockIssuesGet.mockResolvedValueOnce({ data: { - title: "Test Title", - body: "No gherkin content here.", - user: { login: "octocat" }, - }, - }); + title: 'Test Title', + body: 'No gherkin content here.', + user: { login: 'octocat' } + } + }) - await run(); + await run() - expect(mockIssuesGet).toHaveBeenCalledOnce(); + expect(mockIssuesGet).toHaveBeenCalledOnce() expect(mockSetFailed).toHaveBeenCalledWith( - "Owner, repo, and issue number are required inputs.", - ); - }); + 'Owner, repo, and issue number are required inputs.' + ) + }) - it("should set failure if no issue number is passed", async () => { + it('should set failure if no issue number is passed', async () => { // Mock inputs - mockGetInput.mockImplementation((key) => { - if (key === "token") return "fake-token"; - if (key === "owner") return "octocat"; - if (key === "repo") return "octocat/example-repo"; - if (key === "issue") return ""; - return undefined; - }); + mockGetInput.mockImplementation(key => { + if (key === 'token') return 'fake-token' + if (key === 'owner') return 'octocat' + if (key === 'repo') return 'octocat/example-repo' + if (key === 'issue') return '' + return undefined + }) // Mock GitHub API response with no Gherkin content mockIssuesGet.mockResolvedValueOnce({ data: { - title: "Test Title", - body: "No gherkin content here.", - user: { login: "octocat" }, - }, - }); + title: 'Test Title', + body: 'No gherkin content here.', + user: { login: 'octocat' } + } + }) - await run(); + await run() - expect(mockIssuesGet).toHaveBeenCalledOnce(); + expect(mockIssuesGet).toHaveBeenCalledOnce() expect(mockSetFailed).toHaveBeenCalledWith( - "Owner, repo, and issue number are required inputs.", - ); - }); + 'Owner, repo, and issue number are required inputs.' + ) + }) - it("should set failure if incorrect issue number is passed", async () => { + it('should set failure if incorrect issue number is passed', async () => { // Mock inputs - mockGetInput.mockImplementation((key) => { - if (key === "token") return "fake-token"; - if (key === "owner") return "octocat"; - if (key === "repo") return "octocat/example-repo"; - if (key === "issue") return "potato"; - return undefined; - }); + mockGetInput.mockImplementation(key => { + if (key === 'token') return 'fake-token' + if (key === 'owner') return 'octocat' + if (key === 'repo') return 'octocat/example-repo' + if (key === 'issue') return 'potato' + return undefined + }) // Mock GitHub API response with no Gherkin content mockIssuesGet.mockResolvedValueOnce({ data: { - title: "Test Title", - body: "No gherkin content here.", - user: { login: "octocat" }, - }, - }); + title: 'Test Title', + body: 'No gherkin content here.', + user: { login: 'octocat' } + } + }) - await run(); + await run() - expect(mockIssuesGet).toHaveBeenCalledOnce(); + expect(mockIssuesGet).toHaveBeenCalledOnce() expect(mockSetFailed).toHaveBeenCalledWith( - "Owner, repo, and issue number are required inputs.", - ); - }); + 'Owner, repo, and issue number are required inputs.' + ) + }) - it("should set failure if negative issue number is passed", async () => { + it('should set failure if negative issue number is passed', async () => { // Mock inputs - mockGetInput.mockImplementation((key) => { - if (key === "token") return "fake-token"; - if (key === "owner") return "octocat"; - if (key === "repo") return "octocat/example-repo"; - if (key === "issue") return "-5"; - return undefined; - }); + mockGetInput.mockImplementation(key => { + if (key === 'token') return 'fake-token' + if (key === 'owner') return 'octocat' + if (key === 'repo') return 'octocat/example-repo' + if (key === 'issue') return '-5' + return undefined + }) // Mock GitHub API response with no Gherkin content mockIssuesGet.mockResolvedValueOnce({ data: { - title: "Test Title", - body: "No gherkin content here.", - user: { login: "octocat" }, - }, - }); + title: 'Test Title', + body: 'No gherkin content here.', + user: { login: 'octocat' } + } + }) - await run(); + await run() - expect(mockIssuesGet).toHaveBeenCalledOnce(); + expect(mockIssuesGet).toHaveBeenCalledOnce() expect(mockSetFailed).toHaveBeenCalledWith( - "Owner, repo, and issue number are required inputs.", - ); - }); + 'Owner, repo, and issue number are required inputs.' + ) + }) - it("should set failure if issue number is passed as 0", async () => { + it('should set failure if issue number is passed as 0', async () => { // Mock inputs - mockGetInput.mockImplementation((key) => { - if (key === "token") return "fake-token"; - if (key === "owner") return "octocat"; - if (key === "repo") return "octocat/example-repo"; - if (key === "issue") return "0"; - return undefined; - }); + mockGetInput.mockImplementation(key => { + if (key === 'token') return 'fake-token' + if (key === 'owner') return 'octocat' + if (key === 'repo') return 'octocat/example-repo' + if (key === 'issue') return '0' + return undefined + }) // Mock GitHub API response with no Gherkin content mockIssuesGet.mockResolvedValueOnce({ data: { - title: "Test Title", - body: "No gherkin content here.", - user: { login: "octocat" }, - }, - }); + title: 'Test Title', + body: 'No gherkin content here.', + user: { login: 'octocat' } + } + }) - await run(); + await run() - expect(mockIssuesGet).toHaveBeenCalledOnce(); + expect(mockIssuesGet).toHaveBeenCalledOnce() expect(mockSetFailed).toHaveBeenCalledWith( - "Owner, repo, and issue number are required inputs.", - ); - }); + 'Owner, repo, and issue number are required inputs.' + ) + }) - it("should redo the issue title if default provided", async () => { + it('should redo the issue title if default provided', async () => { // Mock inputs - mockGetInput.mockImplementation((key) => { - if (key === "token") return "fake-token"; - if (key === "owner") return "octocat"; - if (key === "repo") return "octocat/example-repo"; - if (key === "issue") return "1"; - if (key === "default_title") return "Test Title"; - if (key === "update_title") return "true"; - return undefined; - }); - mockGetBooleanInput.mockImplementation((key) => { - if (key === "update_title") return true; - return undefined; - }); + mockGetInput.mockImplementation(key => { + if (key === 'token') return 'fake-token' + if (key === 'owner') return 'octocat' + if (key === 'repo') return 'octocat/example-repo' + if (key === 'issue') return '1' + if (key === 'default_title') return 'Test Title' + if (key === 'update_title') return 'true' + return undefined + }) + mockGetBooleanInput.mockImplementation(key => { + if (key === 'update_title') return true + return undefined + }) // Mock GitHub API response mockIssuesGet.mockResolvedValueOnce({ data: { - title: "Test Title", - body: "```gherkin\nFeature: Test Gherkin Content\n```", - user: { login: "octocat" }, - }, - }); + title: 'Test Title', + body: '```gherkin\nFeature: Test Gherkin Content\n```', + user: { login: 'octocat' } + } + }) mockIssuesUpdate.mockResolvedValueOnce({ data: { - title: "Automated test case #1 opened by octocat", - body: "```gherkin\nFeature: Test Gherkin Content\n```", - user: { login: "octocat" }, - }, - }); + title: 'Automated test case #1 opened by octocat', + body: '```gherkin\nFeature: Test Gherkin Content\n```', + user: { login: 'octocat' } + } + }) - await run(); + await run() - expect(mockIssuesGet).toHaveBeenCalledOnce(); - expect(mockIssuesUpdate).toHaveBeenCalledOnce(); + expect(mockIssuesGet).toHaveBeenCalledOnce() + expect(mockIssuesUpdate).toHaveBeenCalledOnce() expect(mockWarning).toHaveBeenCalledWith( - "No issue title provided, updating the issue title with the test case title", - ); + 'No issue title provided, updating the issue title with the test case title' + ) expect(mockSetOutput).toHaveBeenCalledWith( - "title", - "Automated test case #1 opened by octocat", - ); + 'title', + 'Automated test case #1 opened by octocat' + ) expect(mockSetOutput).toHaveBeenCalledWith( - "body", - "```gherkin\nFeature: Test Gherkin Content\n```", - ); + 'body', + '```gherkin\nFeature: Test Gherkin Content\n```' + ) expect(mockSetOutput).toHaveBeenCalledWith( - "feature", - "Feature: Test Gherkin Content", - ); + 'feature', + 'Feature: Test Gherkin Content' + ) expect(mockNotice).toHaveBeenCalledWith( - "Gherkin content extracted successfully.", - ); - }); + 'Gherkin content extracted successfully.' + ) + }) - it("should redo the issue title if default provided and no user", async () => { + it('should redo the issue title if default provided and no user', async () => { // Mock inputs - mockGetInput.mockImplementation((key) => { - if (key === "token") return "fake-token"; - if (key === "owner") return "octocat"; - if (key === "repo") return "octocat/example-repo"; - if (key === "issue") return "1"; - if (key === "default_title") return "Test Title"; - if (key === "update_title") return "true"; - return undefined; - }); - mockGetBooleanInput.mockImplementation((key) => { - if (key === "update_title") return true; - return undefined; - }); + mockGetInput.mockImplementation(key => { + if (key === 'token') return 'fake-token' + if (key === 'owner') return 'octocat' + if (key === 'repo') return 'octocat/example-repo' + if (key === 'issue') return '1' + if (key === 'default_title') return 'Test Title' + if (key === 'update_title') return 'true' + return undefined + }) + mockGetBooleanInput.mockImplementation(key => { + if (key === 'update_title') return true + return undefined + }) // Mock GitHub API response mockIssuesGet.mockResolvedValueOnce({ data: { - title: "Test Title", - body: "```gherkin\nFeature: Test Gherkin Content\n```", - }, - }); + title: 'Test Title', + body: '```gherkin\nFeature: Test Gherkin Content\n```' + } + }) mockIssuesUpdate.mockResolvedValueOnce({ data: { - title: "Automated test case #1 opened by unknown", - body: "```gherkin\nFeature: Test Gherkin Content\n```", - }, - }); + title: 'Automated test case #1 opened by unknown', + body: '```gherkin\nFeature: Test Gherkin Content\n```' + } + }) - await run(); + await run() - expect(mockIssuesGet).toHaveBeenCalledOnce(); - expect(mockIssuesUpdate).toHaveBeenCalledOnce(); + expect(mockIssuesGet).toHaveBeenCalledOnce() + expect(mockIssuesUpdate).toHaveBeenCalledOnce() expect(mockWarning).toHaveBeenCalledWith( - "No issue title provided, updating the issue title with the test case title", - ); + 'No issue title provided, updating the issue title with the test case title' + ) expect(mockSetOutput).toHaveBeenCalledWith( - "title", - "Automated test case #1 opened by unknown", - ); + 'title', + 'Automated test case #1 opened by unknown' + ) expect(mockSetOutput).toHaveBeenCalledWith( - "body", - "```gherkin\nFeature: Test Gherkin Content\n```", - ); + 'body', + '```gherkin\nFeature: Test Gherkin Content\n```' + ) expect(mockSetOutput).toHaveBeenCalledWith( - "feature", - "Feature: Test Gherkin Content", - ); + 'feature', + 'Feature: Test Gherkin Content' + ) expect(mockNotice).toHaveBeenCalledWith( - "Gherkin content extracted successfully.", - ); - }); + 'Gherkin content extracted successfully.' + ) + }) - it("should provide a new issue title if default provided but no update on original", async () => { + it('should provide a new issue title if default provided but no update on original', async () => { // Mock inputs - mockGetInput.mockImplementation((key) => { - if (key === "token") return "fake-token"; - if (key === "owner") return "octocat"; - if (key === "repo") return "octocat/example-repo"; - if (key === "issue") return "1"; - if (key === "default_title") return "Test Title"; - return undefined; - }); - mockGetBooleanInput.mockImplementation((key) => { - if (key === "update_title") return false; - return undefined; - }); + mockGetInput.mockImplementation(key => { + if (key === 'token') return 'fake-token' + if (key === 'owner') return 'octocat' + if (key === 'repo') return 'octocat/example-repo' + if (key === 'issue') return '1' + if (key === 'default_title') return 'Test Title' + return undefined + }) + mockGetBooleanInput.mockImplementation(key => { + if (key === 'update_title') return false + return undefined + }) // Mock GitHub API response mockIssuesGet.mockResolvedValueOnce({ data: { - title: "Test Title", - body: "```gherkin\nFeature: Test Gherkin Content\n```", - }, - }); + title: 'Test Title', + body: '```gherkin\nFeature: Test Gherkin Content\n```' + } + }) - await run(); + await run() - expect(mockIssuesGet).toHaveBeenCalledOnce(); - expect(mockIssuesUpdate).toBeCalledTimes(0); + expect(mockIssuesGet).toHaveBeenCalledOnce() + expect(mockIssuesUpdate).toBeCalledTimes(0) expect(mockWarning).toHaveBeenCalledWith( - "No issue title provided, updating the issue title with the test case title", - ); + 'No issue title provided, updating the issue title with the test case title' + ) expect(mockSetOutput).toHaveBeenCalledWith( - "title", - "Automated test case #1 opened by unknown", - ); + 'title', + 'Automated test case #1 opened by unknown' + ) expect(mockSetOutput).toHaveBeenCalledWith( - "body", - "```gherkin\nFeature: Test Gherkin Content\n```", - ); + 'body', + '```gherkin\nFeature: Test Gherkin Content\n```' + ) expect(mockSetOutput).toHaveBeenCalledWith( - "feature", - "Feature: Test Gherkin Content", - ); + 'feature', + 'Feature: Test Gherkin Content' + ) expect(mockNotice).toHaveBeenCalledWith( - "Gherkin content extracted successfully.", - ); - }); + 'Gherkin content extracted successfully.' + ) + }) - it("should return error if get fails and throws exception", async () => { + it('should return error if get fails and throws exception', async () => { // Mock inputs - mockGetInput.mockImplementation((key) => { - if (key === "token") return "fake-token"; - if (key === "owner") return "octocat"; - if (key === "repo") return "octocat/example-repo"; - if (key === "issue") return "1"; - return undefined; - }); + mockGetInput.mockImplementation(key => { + if (key === 'token') return 'fake-token' + if (key === 'owner') return 'octocat' + if (key === 'repo') return 'octocat/example-repo' + if (key === 'issue') return '1' + return undefined + }) // Mock GitHub API response with no Gherkin content mockIssuesGet.mockRejectedValueOnce( - new Error("Error: No Gherkin content found in the issue body."), - ); + new Error('Error: No Gherkin content found in the issue body.') + ) - await run(); + await run() - expect(mockIssuesGet).toHaveBeenCalledOnce(); + expect(mockIssuesGet).toHaveBeenCalledOnce() expect(mockSetFailed).toHaveBeenCalledWith( - "Error: No Gherkin content found in the issue body.", - ); - }); + 'Error: No Gherkin content found in the issue body.' + ) + }) - it("should return error if get fails and throws exception", async () => { + it('should return error if get fails and throws exception', async () => { // Mock inputs - mockGetInput.mockImplementation((key) => { - if (key === "token") return "fake-token"; - if (key === "owner") return "octocat"; - if (key === "repo") return "octocat/example-repo"; - if (key === "issue") return "1"; - return undefined; - }); + mockGetInput.mockImplementation(key => { + if (key === 'token') return 'fake-token' + if (key === 'owner') return 'octocat' + if (key === 'repo') return 'octocat/example-repo' + if (key === 'issue') return '1' + return undefined + }) // Mock GitHub API response with no Gherkin content mockIssuesGet.mockRejectedValueOnce( - "Error: No Gherkin content found in the issue body.", - ); + 'Error: No Gherkin content found in the issue body.' + ) - await run(); + await run() - expect(mockIssuesGet).toHaveBeenCalledOnce(); + expect(mockIssuesGet).toHaveBeenCalledOnce() expect(mockSetFailed).toHaveBeenCalledWith( - "An error occurred while extracting the Gherkin content.", - ); - }); -}); + 'An error occurred while extracting the Gherkin content.' + ) + }) +}) diff --git a/action.yml b/action.yml index 1fd30b3..cb84dbc 100644 --- a/action.yml +++ b/action.yml @@ -1,6 +1,8 @@ name: Gherking extractor for GitHub Issues -description: "GitHub Action that processes Gherkin content from issues and turn them into a content that can be then saved into a file" -author: "Government of British Columbia Natual Resources" +description: + 'GitHub Action that processes Gherkin content from issues and turn them into a + content that can be then saved into a file' +author: 'Government of British Columbia Natual Resources' branding: icon: check-square color: blue @@ -13,10 +15,13 @@ inputs: ### Typical / recommended default_title: - description: A default title for the issue, to check if the user has changed it + description: + A default title for the issue, to check if the user has changed it required: true update_title: - description: Defines if the issue title should be updated in case it is not different from the default one + description: + Defines if the issue title should be updated in case it is not different + from the default one required: true ### Usually a bad idea / not recommended @@ -33,7 +38,8 @@ inputs: # Define your outputs here. outputs: title: - description: The title of the issue. Usually the original title, unless it was updated + description: + The title of the issue. Usually the original title, unless it was updated body: description: The original body of the issue feature: diff --git a/renovate.json b/renovate.json index 5db72dd..22a9943 100644 --- a/renovate.json +++ b/renovate.json @@ -1,6 +1,4 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:recommended" - ] + "extends": ["config:recommended"] } diff --git a/src/index.ts b/src/index.ts index 050383e..b08f970 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ /** * The entrypoint for the action. */ -import { run } from "./main"; +import { run } from './main' // eslint-disable-next-line @typescript-eslint/no-floating-promises -run(); +run() diff --git a/src/main.ts b/src/main.ts index a0d3b84..ba93bb2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,33 +1,33 @@ -import * as core from "@actions/core"; -import { Octokit } from "@octokit/rest"; -import MarkdownIt from "markdown-it"; +import * as core from '@actions/core' +import { Octokit } from '@octokit/rest' +import MarkdownIt from 'markdown-it' /** * The main function for the action. * @returns {Promise} Resolves when the action is complete. */ export async function run(): Promise { - const token = core.getInput("token", { required: true }); + const token = core.getInput('token', { required: true }) if (!token) { - core.setFailed("GitHub token is required."); + core.setFailed('GitHub token is required.') } // Initialize the GitHub client with the token - const octokit = new Octokit({ auth: token }); + const octokit = new Octokit({ auth: token }) // GitHub repository information - const owner = core.getInput("owner", { required: true }); - const repo = core.getInput("repo", { required: true }); + const owner = core.getInput('owner', { required: true }) + const repo = core.getInput('repo', { required: true }) // Issue number to extract Gherkin content from - const issueNumber = parseInt(core.getInput("issue", { required: true })); + const issueNumber = parseInt(core.getInput('issue', { required: true })) // Default values - const defaultTitle = core.getInput("default_title") || "Test"; - const updateTitle = core.getBooleanInput("update_title") || false; + const defaultTitle = core.getInput('default_title') || 'Test' + const updateTitle = core.getBooleanInput('update_title') || false core.info( - `Extracting Gherkin content from issue #${issueNumber} in ${owner}/${repo}...`, - ); + `Extracting Gherkin content from issue #${issueNumber} in ${owner}/${repo}...` + ) if ( !owner || @@ -36,44 +36,44 @@ export async function run(): Promise { isNaN(issueNumber) || issueNumber <= 0 ) { - core.setFailed("Owner, repo, and issue number are required inputs."); + core.setFailed('Owner, repo, and issue number are required inputs.') } - const parsedRepo = repo.replace(owner + "/", ""); + const parsedRepo = repo.replace(owner + '/', '') try { // Fetch the issue data from GitHub const { data: issue } = await octokit.issues.get({ owner, repo: parsedRepo, - issue_number: issueNumber, - }); + issue_number: issueNumber + }) - const issueBody = issue.body || ""; - let updatedTitle = issue.title; + const issueBody = issue.body || '' + let updatedTitle = issue.title // Parse the markdown content - const md = new MarkdownIt(); - const tokens = md.parse(issueBody, {}); - let gherkinText = ""; + const md = new MarkdownIt() + const tokens = md.parse(issueBody, {}) + let gherkinText = '' // Look for the Gherkin code block tokens.forEach((token: any) => { - if (token.type === "fence" && token.info === "gherkin") { - gherkinText = token.content; + if (token.type === 'fence' && token.info === 'gherkin') { + gherkinText = token.content } - }); + }) // If the issue title is the default title, update it with the test case title // This is useful when the issue title is generic and we want to prevent duplicates if (issue.title === defaultTitle) { core.warning( - "No issue title provided, updating the issue title with the test case title", - ); + 'No issue title provided, updating the issue title with the test case title' + ) // The new title is generated with the issue number and the author's name - const authorName = `Automated test case #${issueNumber} opened by ${issue.user?.login || "unknown"}`; - updatedTitle = issue.title.replace(defaultTitle, authorName).trim(); + const authorName = `Automated test case #${issueNumber} opened by ${issue.user?.login || 'unknown'}` + updatedTitle = issue.title.replace(defaultTitle, authorName).trim() if (updateTitle) { // Update the issue title with the new title @@ -82,23 +82,23 @@ export async function run(): Promise { repo: parsedRepo, issue_number: issueNumber, title: updatedTitle, - body: issue.body, - }); + body: issue.body + }) } } if (!gherkinText) { - core.setFailed("Error: No Gherkin content found in the issue body."); + core.setFailed('Error: No Gherkin content found in the issue body.') } else { - core.setOutput("title", updatedTitle); - core.setOutput("body", issue.body); - core.setOutput("feature", gherkinText.trim()); + core.setOutput('title', updatedTitle) + core.setOutput('body', issue.body) + core.setOutput('feature', gherkinText.trim()) - core.notice("Gherkin content extracted successfully."); + core.notice('Gherkin content extracted successfully.') } } catch (error) { - if (error instanceof Error) core.setFailed(error.message); + if (error instanceof Error) core.setFailed(error.message) else - core.setFailed("An error occurred while extracting the Gherkin content."); + core.setFailed('An error occurred while extracting the Gherkin content.') } } diff --git a/vitest.config.ts b/vitest.config.ts index e5e297d..b5ac9e0 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,18 +1,18 @@ /// -import { defineConfig } from "vite"; +import { defineConfig } from 'vite' export default defineConfig({ test: { globals: true, mockReset: true, coverage: { - provider: "istanbul", + provider: 'istanbul', all: true, clean: true, - reportsDirectory: "./coverage", - reporter: ["text", "lcov", "json-summary"], - include: ["src/**/*.ts"], - exclude: ["node_modules", "dist", "coverage", "badges"], - }, - }, -}); + reportsDirectory: './coverage', + reporter: ['text', 'lcov', 'json-summary'], + include: ['src/**/*.ts'], + exclude: ['node_modules', 'dist', 'coverage', 'badges'] + } + } +})