Skip to content

Commit

Permalink
feat(tests): support phpunit
Browse files Browse the repository at this point in the history
  • Loading branch information
innocenzi committed Nov 8, 2024
1 parent 3900c4d commit f9056b0
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 35 deletions.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,17 +99,17 @@
},
"hybridly.test.directory": {
"type": "string",
"description": "Relative path to the test directory, if custom."
"description": "Relative path to the test directory, if custom. Supports Pest only."
},
"hybridly.test.retry": {
"type": "boolean",
"default": true,
"description": "Prioritize previously-failed tests when running them."
"description": "Prioritize previously-failed tests when running them. Supports Pest only."
},
"hybridly.test.bail": {
"type": "boolean",
"default": true,
"description": "Stop execution upon first non-passing test."
"description": "Stop execution upon first non-passing test. Supports Pest only."
},
"hybridly.test.arguments": {
"type": "string",
Expand Down
4 changes: 2 additions & 2 deletions src/commands/run-current-test-file.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { ExtensionContext } from '../context'
import { registerPhpFileCommand } from '../utils/commands'
import { hasPest } from '../utils/composer'
import { hasTestRunner } from '../utils/composer'
import { runTestsTask } from '../utils/tests'

export async function registerRunCurrentTestFileCommand(context: ExtensionContext) {
registerPhpFileCommand(context, 'run-current-test-file', async ({ editor }) => {
if (!hasPest(context.cwd)) {
if (!hasTestRunner(context.cwd)) {
return
}

Expand Down
46 changes: 37 additions & 9 deletions src/commands/run-current-test.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,55 @@
import type { ExtensionContext } from '../context'
import { registerPhpFileCommand } from '../utils/commands'
import { hasPest } from '../utils/composer'
import { getTestRunner, hasTestRunner } from '../utils/composer'
import { runTestsTask } from '../utils/tests'

export async function registerRunCurrentTestCommand(context: ExtensionContext) {
registerPhpFileCommand(context, 'run-current-test', async ({ editor }) => {
if (!hasPest(context.cwd)) {
if (!hasTestRunner(context.cwd)) {
return
}

let line = editor.selection.active.line
let method: string | undefined
const testRunner = getTestRunner(context.cwd)

while (line > 0) {
const lineText = editor.document.lineAt(line).text
const match = lineText.match(/^\s*(?:it|test)\((.+['"])[,)]/m)
if (testRunner === 'pest') {
while (line > 0) {
const lineText = editor.document.lineAt(line).text
const match = lineText.match(/^\s*(?:it|test)\((.+['"])[,)]/m)

if (match) {
method = match[1]
break
if (match) {
method = match[1]
break
}

line = line - 1
}
}

if (testRunner === 'phpunit') {
while (line > 0) {
const lineText = editor.document.lineAt(line).text
const methodRE = /^\s*(?:public|private|protected)\s+function\s+(test_.+)\s*\(/m

const testPrefixMatch = lineText.match(methodRE)
if (testPrefixMatch) {
method = testPrefixMatch[1]
break
}

line = line - 1
if (lineText.match(/^\s*#\[Test(?:\(\))?\]/m)) {
const methodLine = editor.document.lineAt(line + 1).text
const methodMatch = methodLine.match(methodRE)

if (methodMatch) {
method = methodMatch[1]
break
}
}

line = line - 1
}
}

const filter = method ? ` --filter ${method}` : ''
Expand Down
6 changes: 3 additions & 3 deletions src/commands/run-directory-tests.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { dirname } from 'node:path'
import type { ExtensionContext } from '../context'
import { registerPhpFileCommand } from '../utils/commands'
import { hasPest } from '../utils/composer'
import { hasTestRunner } from '../utils/composer'
import { runTestsTask } from '../utils/tests'

export async function registerRunDirectoryTestsCommand(context: ExtensionContext) {
registerPhpFileCommand(context, 'run-directory-tests', async (ctx) => {
if (!hasPest(context.cwd)) {
if (!hasTestRunner(context.cwd)) {
return
}

await runTestsTask(context.cwd, dirname(ctx.file.relativePath))
await runTestsTask(context.cwd, dirname(ctx.file.fullPath))
})
}
4 changes: 2 additions & 2 deletions src/commands/run-tests.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { ExtensionContext } from '../context'
import { registerEditorCommand } from '../utils/commands'
import { hasPest } from '../utils/composer'
import { hasTestRunner } from '../utils/composer'
import { runTestsTask } from '../utils/tests'

export async function registerRunTestsCommand(context: ExtensionContext) {
registerEditorCommand(context, 'php.run-tests', async () => {
if (!hasPest(context.cwd)) {
if (!hasTestRunner(context.cwd)) {
return
}

Expand Down
31 changes: 24 additions & 7 deletions src/utils/composer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import path from 'node:path'
import { window } from 'vscode'
import { log } from './log'

let hasLoggedPest = false
let testRunner: TestRunner
let hasLoggedTestRunner = false
let hasLoggedComposer = false
const caches = new Map<string, any>()

Expand Down Expand Up @@ -34,12 +35,28 @@ export function hasComposerPackage(cwd: string, pkg: string): boolean {
return !!getComposer(cwd)?.require?.[pkg] || !!getComposer(cwd)?.['require-dev']?.[pkg]
}

export function hasPest(cwd: string) {
if (!hasComposerPackage(cwd, 'pestphp/pest')) {
if (!hasLoggedPest) {
log.appendLine('Pest was not found in `composer.json`.')
window.showErrorMessage('Pest is not installed in this project.')
hasLoggedPest = true
export type TestRunner = 'pest' | 'phpunit'

export function getTestRunner(cwd: string): TestRunner | undefined {
if (testRunner) {
return testRunner
}

if (hasComposerPackage(cwd, 'pestphp/pest')) {
return testRunner = 'pest'
}

if (hasComposerPackage(cwd, 'phpunit/phpunit')) {
return testRunner = 'phpunit'
}
}

export function hasTestRunner(cwd: string) {
if (!getTestRunner(cwd)) {
if (!hasLoggedTestRunner) {
log.appendLine('Compatible test runner not found in `composer.json`.')
window.showErrorMessage('A compatible test runner is not installed in this project.')
hasLoggedTestRunner = true
}

return false
Expand Down
2 changes: 2 additions & 0 deletions src/utils/psr4.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface ControllerAction {
}

export interface PhpFile {
fullPath: string
relativePath: string
fileName: string
className: string
Expand Down Expand Up @@ -83,6 +84,7 @@ export async function resolvePhpFile(workspace: Uri, file: Uri): Promise<PhpFile
}, null, 2))

return {
fullPath: file.fsPath,
relativePath,
fileName,
className,
Expand Down
22 changes: 13 additions & 9 deletions src/utils/tests.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import path from 'node:path'
import { ShellExecution, Task, TaskScope, commands, tasks, window } from 'vscode'
import { getSetting } from '../settings'
import { getTestRunner } from './composer'

interface LatestTestContext {
cwd: string
Expand All @@ -23,24 +24,27 @@ export async function runTestsTask(cwd: string, args: string = '') {
const retries = getSetting('test.retry')
const bail = getSetting('test.bail')
const settingArgs = getSetting('test.arguments')
const testRunner = getTestRunner(cwd)

if (testDirectory && !args.includes('--test-directory')) {
args += ` --test-directory=${testDirectory} ${args}`
}
if (testRunner === 'pest') {
if (testDirectory && !args.includes('--test-directory')) {
args += ` --test-directory=${testDirectory} ${args}`
}

if (retries && !args.includes('--retry')) {
args += ' --retry'
}
if (retries && !args.includes('--retry')) {
args += ' --retry'
}

if (bail && !args.includes('--bail')) {
args += ' --bail'
if (bail && !args.includes('--bail')) {
args += ' --bail'
}
}

if (settingArgs) {
args += ` ${settingArgs}`
}

const binaryName = process.platform === 'win32' ? 'pest.bat' : 'pest'
const binaryName = `${testRunner}${process.platform === 'win32' ? '.bat' : ''}`
const binaryPath = path.join(cwd, 'vendor', 'bin', binaryName)
const command = `${binaryPath} ${args}`

Expand Down

0 comments on commit f9056b0

Please sign in to comment.