feat: moves project to using vitest

This commit is contained in:
Marais Rossouw
2025-07-24 18:08:26 +10:00
parent 64cbe74d35
commit 4ba8e6bc1e
15 changed files with 643 additions and 7110 deletions
+1 -1
View File
@@ -43,7 +43,7 @@ jobs:
- name: Test - name: Test
id: npm-ci-test id: npm-ci-test
run: npm run ci-test run: npm run test
env: env:
GITHUB_TOKEN: ${{ github.token }} GITHUB_TOKEN: ${{ github.token }}
+9 -9
View File
@@ -1,11 +1,11 @@
import type * as core from '@actions/core' import type * as core from '@actions/core'
import { jest } from '@jest/globals' import { vi } from 'vitest'
export const debug = jest.fn<typeof core.debug>() export const debug = vi.fn<typeof core.debug>()
export const error = jest.fn<typeof core.error>() export const error = vi.fn<typeof core.error>()
export const info = jest.fn<typeof core.info>() export const info = vi.fn<typeof core.info>()
export const getInput = jest.fn<typeof core.getInput>() export const getInput = vi.fn<typeof core.getInput>()
export const getBooleanInput = jest.fn<typeof core.getBooleanInput>() export const getBooleanInput = vi.fn<typeof core.getBooleanInput>()
export const setOutput = jest.fn<typeof core.setOutput>() export const setOutput = vi.fn<typeof core.setOutput>()
export const setFailed = jest.fn<typeof core.setFailed>() export const setFailed = vi.fn<typeof core.setFailed>()
export const warning = jest.fn<typeof core.warning>() export const warning = vi.fn<typeof core.warning>()
+2 -2
View File
@@ -1,3 +1,3 @@
import { jest } from '@jest/globals' import { vi } from 'vitest'
export const wait = jest.fn<typeof import('../src/wait.js').wait>() export const wait = vi.fn<typeof import('../src/wait.js').wait>()
+1 -1
View File
@@ -1,4 +1,4 @@
import { describe, it, expect } from '@jest/globals' import { describe, it, expect } from 'vitest'
import { import {
buildMessages, buildMessages,
buildResponseFormat, buildResponseFormat,
+6 -11
View File
@@ -1,26 +1,21 @@
/** import { vi, it, expect, beforeEach, describe } from 'vitest'
* Unit tests for the helpers module, src/helpers.ts
*/
import { jest } from '@jest/globals'
import * as core from '../__fixtures__/core.js' import * as core from '../__fixtures__/core.js'
// Mock fs module const mockExistsSync = vi.fn()
const mockExistsSync = jest.fn() const mockReadFileSync = vi.fn()
const mockReadFileSync = jest.fn()
jest.unstable_mockModule('fs', () => ({ vi.mock('fs', () => ({
existsSync: mockExistsSync, existsSync: mockExistsSync,
readFileSync: mockReadFileSync readFileSync: mockReadFileSync
})) }))
jest.unstable_mockModule('@actions/core', () => core) vi.mock('@actions/core', () => core)
// Import the module being tested
const { loadContentFromFileOrInput } = await import('../src/helpers.js') const { loadContentFromFileOrInput } = await import('../src/helpers.js')
describe('helpers.ts', () => { describe('helpers.ts', () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks() vi.clearAllMocks()
}) })
describe('loadContentFromFileOrInput', () => { describe('loadContentFromFileOrInput', () => {
+19 -17
View File
@@ -1,32 +1,34 @@
/** import {
* Unit tests for the inference module, src/inference.ts vi,
*/ type MockedFunction,
import { jest } from '@jest/globals' beforeEach,
expect,
describe,
it
} from 'vitest'
import * as core from '../__fixtures__/core.js' import * as core from '../__fixtures__/core.js'
// Mock Azure AI Inference
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const mockPost = jest.fn() as jest.MockedFunction<any> const mockPost = vi.fn() as MockedFunction<any>
const mockPath = jest.fn(() => ({ post: mockPost })) const mockPath = vi.fn(() => ({ post: mockPost }))
const mockClient = jest.fn(() => ({ path: mockPath })) const mockClient = vi.fn(() => ({ path: mockPath }))
jest.unstable_mockModule('@azure-rest/ai-inference', () => ({ vi.mock('@azure-rest/ai-inference', () => ({
default: mockClient, default: mockClient,
isUnexpected: jest.fn(() => false) isUnexpected: vi.fn(() => false)
})) }))
jest.unstable_mockModule('@azure/core-auth', () => ({ vi.mock('@azure/core-auth', () => ({
AzureKeyCredential: jest.fn() AzureKeyCredential: vi.fn()
})) }))
// Mock MCP functions
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const mockExecuteToolCalls = jest.fn() as jest.MockedFunction<any> const mockExecuteToolCalls = vi.fn() as MockedFunction<any>
jest.unstable_mockModule('../src/mcp.js', () => ({ vi.mock('../src/mcp.js', () => ({
executeToolCalls: mockExecuteToolCalls executeToolCalls: mockExecuteToolCalls
})) }))
jest.unstable_mockModule('@actions/core', () => core) vi.mock('@actions/core', () => core)
// Import the module being tested // Import the module being tested
const { simpleInference, mcpInference } = await import('../src/inference.js') const { simpleInference, mcpInference } = await import('../src/inference.js')
@@ -44,7 +46,7 @@ describe('inference.ts', () => {
} }
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks() vi.clearAllMocks()
}) })
describe('simpleInference', () => { describe('simpleInference', () => {
+21 -13
View File
@@ -1,38 +1,46 @@
import { describe, it, expect, beforeEach, jest } from '@jest/globals' import {
describe,
it,
expect,
beforeEach,
vi,
type MockedFunction,
type Mock
} from 'vitest'
import * as core from '../__fixtures__/core.js' import * as core from '../__fixtures__/core.js'
// Create fs mocks // Create fs mocks
const mockExistsSync = jest.fn() const mockExistsSync = vi.fn()
const mockReadFileSync = jest.fn() const mockReadFileSync = vi.fn()
const mockWriteFileSync = jest.fn() const mockWriteFileSync = vi.fn()
// Create inference mocks // Create inference mocks
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const mockSimpleInference = jest.fn() as jest.MockedFunction<any> const mockSimpleInference = vi.fn() as MockedFunction<any>
const mockMcpInference = jest.fn() const mockMcpInference = vi.fn()
// Create MCP mocks // Create MCP mocks
const mockConnectToGitHubMCP = jest.fn() const mockConnectToGitHubMCP = vi.fn()
// Mock fs module // Mock fs module
jest.unstable_mockModule('fs', () => ({ vi.mock('fs', () => ({
existsSync: mockExistsSync, existsSync: mockExistsSync,
readFileSync: mockReadFileSync, readFileSync: mockReadFileSync,
writeFileSync: mockWriteFileSync writeFileSync: mockWriteFileSync
})) }))
// Mock the inference functions // Mock the inference functions
jest.unstable_mockModule('../src/inference.js', () => ({ vi.mock('../src/inference.js', () => ({
simpleInference: mockSimpleInference, simpleInference: mockSimpleInference,
mcpInference: mockMcpInference mcpInference: mockMcpInference
})) }))
// Mock the MCP connection // Mock the MCP connection
jest.unstable_mockModule('../src/mcp.js', () => ({ vi.mock('../src/mcp.js', () => ({
connectToGitHubMCP: mockConnectToGitHubMCP connectToGitHubMCP: mockConnectToGitHubMCP
})) }))
jest.unstable_mockModule('@actions/core', () => core) vi.mock('@actions/core', () => core)
// The module being tested should be imported dynamically. This ensures that the // The module being tested should be imported dynamically. This ensures that the
// mocks are used in place of any actual dependencies. // mocks are used in place of any actual dependencies.
@@ -40,7 +48,7 @@ const { run } = await import('../src/main.js')
describe('main.ts - prompt.yml integration', () => { describe('main.ts - prompt.yml integration', () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks() vi.clearAllMocks()
// Mock environment variables // Mock environment variables
process.env['GITHUB_TOKEN'] = 'test-token' process.env['GITHUB_TOKEN'] = 'test-token'
@@ -62,7 +70,7 @@ describe('main.ts - prompt.yml integration', () => {
}) })
// Mock core.getBooleanInput // Mock core.getBooleanInput
const mockGetBooleanInput = core.getBooleanInput as jest.Mock const mockGetBooleanInput = core.getBooleanInput as Mock
mockGetBooleanInput.mockReturnValue(false) mockGetBooleanInput.mockReturnValue(false)
// Mock fs.readFileSync for prompt file // Mock fs.readFileSync for prompt file
+19 -15
View File
@@ -1,21 +1,25 @@
/** import {
* Unit tests for the action's main functionality, src/main.ts vi,
*/ describe,
import { jest } from '@jest/globals' expect,
it,
beforeEach,
type MockedFunction
} from 'vitest'
import * as core from '../__fixtures__/core.js' import * as core from '../__fixtures__/core.js'
// Default to throwing errors to catch unexpected calls // Default to throwing errors to catch unexpected calls
const mockExistsSync = jest.fn().mockImplementation(() => { const mockExistsSync = vi.fn().mockImplementation(() => {
throw new Error( throw new Error(
'Unexpected call to existsSync - test should override this implementation' 'Unexpected call to existsSync - test should override this implementation'
) )
}) })
const mockReadFileSync = jest.fn().mockImplementation(() => { const mockReadFileSync = vi.fn().mockImplementation(() => {
throw new Error( throw new Error(
'Unexpected call to readFileSync - test should override this implementation' 'Unexpected call to readFileSync - test should override this implementation'
) )
}) })
const mockWriteFileSync = jest.fn() const mockWriteFileSync = vi.fn()
/** /**
* Helper function to mock file system operations for one or more files * Helper function to mock file system operations for one or more files
@@ -83,7 +87,7 @@ function verifyStandardResponse(): void {
) )
} }
jest.unstable_mockModule('fs', () => ({ vi.mock('fs', () => ({
existsSync: mockExistsSync, existsSync: mockExistsSync,
readFileSync: mockReadFileSync, readFileSync: mockReadFileSync,
writeFileSync: mockWriteFileSync writeFileSync: mockWriteFileSync
@@ -91,22 +95,22 @@ jest.unstable_mockModule('fs', () => ({
// Mock MCP and inference modules // Mock MCP and inference modules
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const mockConnectToGitHubMCP = jest.fn() as jest.MockedFunction<any> const mockConnectToGitHubMCP = vi.fn() as MockedFunction<any>
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const mockSimpleInference = jest.fn() as jest.MockedFunction<any> const mockSimpleInference = vi.fn() as MockedFunction<any>
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const mockMcpInference = jest.fn() as jest.MockedFunction<any> const mockMcpInference = vi.fn() as MockedFunction<any>
jest.unstable_mockModule('../src/mcp.js', () => ({ vi.mock('../src/mcp.js', () => ({
connectToGitHubMCP: mockConnectToGitHubMCP connectToGitHubMCP: mockConnectToGitHubMCP
})) }))
jest.unstable_mockModule('../src/inference.js', () => ({ vi.mock('../src/inference.js', () => ({
simpleInference: mockSimpleInference, simpleInference: mockSimpleInference,
mcpInference: mockMcpInference mcpInference: mockMcpInference
})) }))
jest.unstable_mockModule('@actions/core', () => core) vi.mock('@actions/core', () => core)
// The module being tested should be imported dynamically. This ensures that the // The module being tested should be imported dynamically. This ensures that the
// mocks are used in place of any actual dependencies. // mocks are used in place of any actual dependencies.
@@ -115,7 +119,7 @@ const { run } = await import('../src/main.js')
describe('main.ts', () => { describe('main.ts', () => {
// Reset all mocks before each test // Reset all mocks before each test
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks() vi.clearAllMocks()
// Remove any existing GITHUB_TOKEN // Remove any existing GITHUB_TOKEN
delete process.env.GITHUB_TOKEN delete process.env.GITHUB_TOKEN
+18 -17
View File
@@ -1,16 +1,20 @@
/** import {
* Unit tests for the MCP module, src/mcp.ts vi,
*/ type MockedFunction,
import { jest } from '@jest/globals' describe,
it,
expect,
beforeEach
} from 'vitest'
import * as core from '../__fixtures__/core.js' import * as core from '../__fixtures__/core.js'
// Mock MCP SDK // Mock MCP SDK
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const mockConnect = jest.fn() as jest.MockedFunction<any> const mockConnect = vi.fn() as MockedFunction<any>
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const mockListTools = jest.fn() as jest.MockedFunction<any> const mockListTools = vi.fn() as MockedFunction<any>
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const mockCallTool = jest.fn() as jest.MockedFunction<any> const mockCallTool = vi.fn() as MockedFunction<any>
const mockClient = { const mockClient = {
connect: mockConnect, connect: mockConnect,
@@ -19,18 +23,15 @@ const mockClient = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any } as any
jest.unstable_mockModule('@modelcontextprotocol/sdk/client/index.js', () => ({ vi.mock('@modelcontextprotocol/sdk/client/index.js', () => ({
Client: jest.fn(() => mockClient) Client: vi.fn(() => mockClient)
})) }))
jest.unstable_mockModule( vi.mock('@modelcontextprotocol/sdk/client/streamableHttp.js', () => ({
'@modelcontextprotocol/sdk/client/streamableHttp.js', StreamableHTTPClientTransport: vi.fn()
() => ({ }))
StreamableHTTPClientTransport: jest.fn()
})
)
jest.unstable_mockModule('@actions/core', () => core) vi.mock('@actions/core', () => core)
// Import the module being tested // Import the module being tested
const { connectToGitHubMCP, executeToolCall, executeToolCalls } = await import( const { connectToGitHubMCP, executeToolCall, executeToolCalls } = await import(
@@ -39,7 +40,7 @@ const { connectToGitHubMCP, executeToolCall, executeToolCalls } = await import(
describe('mcp.ts', () => { describe('mcp.ts', () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks() vi.clearAllMocks()
}) })
describe('connectToGitHubMCP', () => { describe('connectToGitHubMCP', () => {
+1 -1
View File
@@ -1,4 +1,4 @@
import { describe, it, expect } from '@jest/globals' import { describe, it, expect } from 'vitest'
import * as path from 'path' import * as path from 'path'
import { fileURLToPath } from 'url' import { fileURLToPath } from 'url'
import { import {
-7
View File
@@ -1,12 +1,9 @@
// See: https://eslint.org/docs/latest/use/configure/configuration-files // See: https://eslint.org/docs/latest/use/configure/configuration-files
import { fixupPluginRules } from '@eslint/compat'
import { FlatCompat } from '@eslint/eslintrc' import { FlatCompat } from '@eslint/eslintrc'
import js from '@eslint/js' import js from '@eslint/js'
import typescriptEslint from '@typescript-eslint/eslint-plugin' import typescriptEslint from '@typescript-eslint/eslint-plugin'
import tsParser from '@typescript-eslint/parser' import tsParser from '@typescript-eslint/parser'
import _import from 'eslint-plugin-import'
import jest from 'eslint-plugin-jest'
import prettier from 'eslint-plugin-prettier' import prettier from 'eslint-plugin-prettier'
import globals from 'globals' import globals from 'globals'
import path from 'node:path' import path from 'node:path'
@@ -28,13 +25,10 @@ export default [
'eslint:recommended', 'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended', 'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended',
'plugin:jest/recommended',
'plugin:prettier/recommended' 'plugin:prettier/recommended'
), ),
{ {
plugins: { plugins: {
import: fixupPluginRules(_import),
jest,
prettier, prettier,
'@typescript-eslint': typescriptEslint '@typescript-eslint': typescriptEslint
}, },
@@ -42,7 +36,6 @@ export default [
languageOptions: { languageOptions: {
globals: { globals: {
...globals.node, ...globals.node,
...globals.jest,
Atomics: 'readonly', Atomics: 'readonly',
SharedArrayBuffer: 'readonly' SharedArrayBuffer: 'readonly'
}, },
-40
View File
@@ -1,40 +0,0 @@
// See: https://jestjs.io/docs/configuration
/** @type {import('ts-jest').JestConfigWithTsJest} **/
export default {
clearMocks: true,
collectCoverage: true,
collectCoverageFrom: ['./src/**'],
coverageDirectory: './coverage',
coveragePathIgnorePatterns: ['/node_modules/', '/dist/'],
coverageReporters: ['json-summary', 'text', 'lcov'],
// Uncomment the below lines if you would like to enforce a coverage threshold
// for your action. This will fail the build if the coverage is below the
// specified thresholds.
// coverageThreshold: {
// global: {
// branches: 100,
// functions: 100,
// lines: 100,
// statements: 100
// }
// },
extensionsToTreatAsEsm: ['.ts'],
moduleFileExtensions: ['ts', 'js'],
preset: 'ts-jest',
reporters: ['default'],
resolver: 'ts-jest-resolver',
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
testPathIgnorePatterns: ['/dist/', '/node_modules/'],
transform: {
'^.+\\.ts$': [
'ts-jest',
{
tsconfig: 'tsconfig.eslint.json',
useESM: true
}
]
},
verbose: true
}
+542 -6966
View File
File diff suppressed because it is too large Load Diff
+4 -9
View File
@@ -10,7 +10,6 @@
}, },
"scripts": { "scripts": {
"bundle": "npm run format:write && npm run package", "bundle": "npm run format:write && npm run package",
"ci-test": "NODE_OPTIONS=--experimental-vm-modules NODE_NO_WARNINGS=1 npx jest",
"coverage": "npx make-coverage-badge --output-path ./badges/coverage.svg", "coverage": "npx make-coverage-badge --output-path ./badges/coverage.svg",
"format:write": "npx prettier --write .", "format:write": "npx prettier --write .",
"format:check": "npx prettier --check .", "format:check": "npx prettier --check .",
@@ -18,7 +17,8 @@
"local-action": "npx @github/local-action . src/main.ts .env", "local-action": "npx @github/local-action . src/main.ts .env",
"package": "npx rollup --config rollup.config.ts --configPlugin @rollup/plugin-typescript", "package": "npx rollup --config rollup.config.ts --configPlugin @rollup/plugin-typescript",
"package:watch": "npm run package -- --watch", "package:watch": "npm run package -- --watch",
"test": "npx cross-env NODE_OPTIONS=--experimental-vm-modules NODE_NO_WARNINGS=1 npx jest", "test": "vitest --run",
"test:watch": "vitest --watch",
"all": "npm run format:write && npm run lint && npm run test && npm run coverage && npm run package" "all": "npm run format:write && npm run lint && npm run test && npm run coverage && npm run package"
}, },
"license": "MIT", "license": "MIT",
@@ -36,11 +36,9 @@
"@azure/core-sse": "latest", "@azure/core-sse": "latest",
"@eslint/compat": "^1.3.0", "@eslint/compat": "^1.3.0",
"@github/local-action": "^5.1.0", "@github/local-action": "^5.1.0",
"@jest/globals": "^30.0.2",
"@rollup/plugin-commonjs": "^28.0.5", "@rollup/plugin-commonjs": "^28.0.5",
"@rollup/plugin-node-resolve": "^16.0.1", "@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-typescript": "^12.1.2", "@rollup/plugin-typescript": "^12.1.2",
"@types/jest": "^30.0.0",
"@types/node": "^22.15.31", "@types/node": "^22.15.31",
"@typescript-eslint/eslint-plugin": "^8.34.0", "@typescript-eslint/eslint-plugin": "^8.34.0",
"@typescript-eslint/parser": "^8.32.1", "@typescript-eslint/parser": "^8.32.1",
@@ -48,16 +46,13 @@
"eslint-config-prettier": "^10.1.5", "eslint-config-prettier": "^10.1.5",
"eslint-import-resolver-typescript": "^4.4.3", "eslint-import-resolver-typescript": "^4.4.3",
"eslint-plugin-import": "^2.31.0", "eslint-plugin-import": "^2.31.0",
"eslint-plugin-jest": "^28.14.0",
"eslint-plugin-prettier": "^5.4.1", "eslint-plugin-prettier": "^5.4.1",
"jest": "^30.0.4",
"make-coverage-badge": "^1.2.0", "make-coverage-badge": "^1.2.0",
"prettier": "^3.5.3", "prettier": "^3.5.3",
"prettier-eslint": "^16.4.2", "prettier-eslint": "^16.4.2",
"rollup": "^4.43.0", "rollup": "^4.43.0",
"ts-jest": "^29.4.0", "typescript": "^5.8.3",
"ts-jest-resolver": "^2.0.1", "vitest": "^3"
"typescript": "^5.8.3"
}, },
"optionalDependencies": { "optionalDependencies": {
"@rollup/rollup-linux-x64-gnu": "*" "@rollup/rollup-linux-x64-gnu": "*"
-1
View File
@@ -11,7 +11,6 @@
"__tests__", "__tests__",
"src", "src",
"eslint.config.mjs", "eslint.config.mjs",
"jest.config.js",
"rollup.config.ts" "rollup.config.ts"
] ]
} }