mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-22 10:51:02 +01:00
feat(regex): implement .NET regex testing via PowerShell and enhance UI components
This commit is contained in:
@@ -259,25 +259,29 @@ def run_tests(category):
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
logger.warning("Rejected test request - no JSON data provided")
|
||||
logger.warning("Test request rejected: no JSON data")
|
||||
return jsonify({"error": "No JSON data provided"}), 400
|
||||
|
||||
tests = data.get('tests', [])
|
||||
if not tests:
|
||||
logger.warning("Rejected test request - no test cases provided")
|
||||
logger.warning("Test request rejected: no tests provided")
|
||||
return jsonify({"error":
|
||||
"At least one test case is required"}), 400
|
||||
|
||||
if category == 'regex_pattern':
|
||||
pattern = data.get('pattern')
|
||||
logger.info(f"Processing regex test request - Pattern: {pattern}")
|
||||
|
||||
if not pattern:
|
||||
logger.warning("Rejected test request - missing pattern")
|
||||
logger.warning("Test request rejected: missing pattern")
|
||||
return jsonify({"error": "Pattern is required"}), 400
|
||||
|
||||
success, message, updated_tests = test_regex_pattern(
|
||||
pattern, tests)
|
||||
|
||||
if success and updated_tests:
|
||||
passed = sum(1 for t in updated_tests if t.get('passes'))
|
||||
total = len(updated_tests)
|
||||
logger.info(f"Tests completed: {passed}/{total} passed")
|
||||
|
||||
elif category == 'custom_format':
|
||||
conditions = data.get('conditions', [])
|
||||
@@ -300,10 +304,8 @@ def run_tests(category):
|
||||
return jsonify(
|
||||
{"error": "Testing not supported for this category"}), 400
|
||||
|
||||
logger.info(f"Test execution completed - Success: {success}")
|
||||
|
||||
if not success:
|
||||
logger.warning(f"Test execution failed - {message}")
|
||||
logger.error(f"Test execution failed: {message}")
|
||||
return jsonify({"success": False, "message": message}), 400
|
||||
|
||||
return jsonify({"success": True, "tests": updated_tests}), 200
|
||||
|
||||
@@ -542,76 +542,67 @@ def test_regex_pattern(
|
||||
pattern: str,
|
||||
tests: List[Dict[str, Any]]) -> Tuple[bool, str, List[Dict[str, Any]]]:
|
||||
"""
|
||||
Test a regex pattern against a list of test cases using PCRE2 compatible engine.
|
||||
Test a regex pattern against a list of test cases using .NET regex engine via PowerShell.
|
||||
Returns match information along with test results.
|
||||
"""
|
||||
logger.info(f"Starting regex pattern test - Pattern: {pattern}")
|
||||
|
||||
try:
|
||||
try:
|
||||
compiled_pattern = regex.compile(pattern,
|
||||
regex.V1 | regex.IGNORECASE)
|
||||
logger.info(
|
||||
"Pattern compiled successfully with PCRE2 compatibility")
|
||||
except regex.error as e:
|
||||
logger.warning(f"Invalid regex pattern: {str(e)}")
|
||||
return False, f"Invalid regex pattern: {str(e)}", tests
|
||||
|
||||
current_time = datetime.now().isoformat()
|
||||
logger.info(f"Processing {len(tests)} test cases")
|
||||
|
||||
for test in tests:
|
||||
test_id = test.get('id', 'unknown')
|
||||
test_input = test.get('input', '')
|
||||
expected = test.get('expected', False)
|
||||
|
||||
try:
|
||||
match = compiled_pattern.search(test_input)
|
||||
matches = bool(match)
|
||||
|
||||
# Update test result with basic fields
|
||||
test['passes'] = matches == expected
|
||||
test['lastRun'] = current_time
|
||||
|
||||
# Add match information
|
||||
if match:
|
||||
test['matchedContent'] = match.group(0)
|
||||
test['matchSpan'] = {
|
||||
'start': match.start(),
|
||||
'end': match.end()
|
||||
}
|
||||
# Get all capture groups if they exist
|
||||
test['matchedGroups'] = [g for g in match.groups()
|
||||
] if match.groups() else []
|
||||
else:
|
||||
test['matchedContent'] = None
|
||||
test['matchSpan'] = None
|
||||
test['matchedGroups'] = []
|
||||
|
||||
logger.info(
|
||||
f"Test {test_id} {'passed' if test['passes'] else 'failed'} - Match: {matches}, Expected: {expected}"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error running test {test_id}: {str(e)}")
|
||||
test['passes'] = False
|
||||
test['lastRun'] = current_time
|
||||
test['matchedContent'] = None
|
||||
test['matchSpan'] = None
|
||||
test['matchedGroups'] = []
|
||||
|
||||
# Log overall results
|
||||
passed_tests = sum(1 for test in tests if test.get('passes', False))
|
||||
logger.info(
|
||||
f"Test execution complete - {passed_tests}/{len(tests)} tests passed"
|
||||
# Get the path to the test.ps1 script
|
||||
script_path = os.path.join('/app', 'scripts', 'test.ps1')
|
||||
if not os.path.exists(script_path):
|
||||
# Fallback for local development
|
||||
script_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'scripts', 'test.ps1')
|
||||
|
||||
# Prepare the input data
|
||||
input_data = {
|
||||
'pattern': pattern,
|
||||
'tests': tests
|
||||
}
|
||||
|
||||
# Run PowerShell script
|
||||
result = subprocess.run(
|
||||
['pwsh', '-File', script_path],
|
||||
input=json.dumps(input_data),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
)
|
||||
|
||||
return True, "", tests
|
||||
|
||||
|
||||
if result.returncode != 0 and not result.stdout:
|
||||
logger.error(f"PowerShell script failed: {result.stderr}")
|
||||
return False, "Failed to run tests", tests
|
||||
|
||||
# Parse JSON output
|
||||
try:
|
||||
output = json.loads(result.stdout.strip())
|
||||
except json.JSONDecodeError:
|
||||
# Try to find JSON in the output
|
||||
lines = result.stdout.strip().split('\n')
|
||||
for line in reversed(lines):
|
||||
if line.strip():
|
||||
try:
|
||||
output = json.loads(line)
|
||||
break
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
else:
|
||||
logger.error(f"No valid JSON found in output: {result.stdout}")
|
||||
return False, "Failed to parse test results", tests
|
||||
|
||||
if output.get('success'):
|
||||
return True, "Tests completed successfully", output.get('tests', tests)
|
||||
else:
|
||||
return False, output.get('message', 'Tests failed'), tests
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.error("Test execution timed out")
|
||||
return False, "Test execution timed out", tests
|
||||
except FileNotFoundError:
|
||||
logger.error("PowerShell (pwsh) not found")
|
||||
return False, "PowerShell is not available", tests
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error in test_regex_pattern: {str(e)}",
|
||||
exc_info=True)
|
||||
return False, str(e), tests
|
||||
logger.error(f"Error running tests: {e}")
|
||||
return False, f"Test error: {str(e)}", tests
|
||||
|
||||
|
||||
def test_format_conditions(conditions: List[Dict],
|
||||
|
||||
107
backend/scripts/test.ps1
Executable file
107
backend/scripts/test.ps1
Executable file
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env pwsh
|
||||
# Run regex tests against a pattern
|
||||
|
||||
# Set output encoding to UTF-8
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# Read from stdin
|
||||
$inputText = $input
|
||||
if (-not $inputText) {
|
||||
$inputText = [System.Console]::In.ReadToEnd()
|
||||
}
|
||||
|
||||
if (-not $inputText) {
|
||||
Write-Output (ConvertTo-Json @{
|
||||
success = $false
|
||||
message = "No input provided"
|
||||
} -Compress)
|
||||
exit 0
|
||||
}
|
||||
|
||||
try {
|
||||
$data = $inputText | ConvertFrom-Json
|
||||
$Pattern = $data.pattern
|
||||
$tests = $data.tests
|
||||
}
|
||||
catch {
|
||||
Write-Output (ConvertTo-Json @{
|
||||
success = $false
|
||||
message = "Failed to parse input JSON: $_"
|
||||
} -Compress)
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Ensure we have required inputs
|
||||
if ([string]::IsNullOrWhiteSpace($Pattern)) {
|
||||
Write-Output (ConvertTo-Json @{
|
||||
success = $false
|
||||
message = "No pattern provided"
|
||||
} -Compress)
|
||||
exit 0
|
||||
}
|
||||
|
||||
if (-not $tests -or $tests.Count -eq 0) {
|
||||
Write-Output (ConvertTo-Json @{
|
||||
success = $false
|
||||
message = "No tests provided"
|
||||
} -Compress)
|
||||
exit 0
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
# Create the regex object with case-insensitive option
|
||||
$regex = [System.Text.RegularExpressions.Regex]::new($Pattern, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)
|
||||
|
||||
# Process each test
|
||||
$results = @()
|
||||
|
||||
foreach ($test in $tests) {
|
||||
$match = $regex.Match($test.input)
|
||||
$passes = ($match.Success -eq $test.expected)
|
||||
|
||||
$result = @{
|
||||
id = $test.id
|
||||
input = $test.input
|
||||
expected = $test.expected
|
||||
passes = $passes
|
||||
}
|
||||
|
||||
if ($match.Success) {
|
||||
# Include match details for highlighting (using original format)
|
||||
$result.matchedContent = $match.Value
|
||||
$result.matchSpan = @{
|
||||
start = $match.Index
|
||||
end = $match.Index + $match.Length
|
||||
}
|
||||
|
||||
# Include capture groups if any
|
||||
$groups = @()
|
||||
for ($i = 1; $i -lt $match.Groups.Count; $i++) {
|
||||
if ($match.Groups[$i].Success) {
|
||||
$groups += $match.Groups[$i].Value
|
||||
}
|
||||
}
|
||||
$result.matchedGroups = $groups
|
||||
}
|
||||
else {
|
||||
$result.matchedContent = $null
|
||||
$result.matchSpan = $null
|
||||
$result.matchedGroups = @()
|
||||
}
|
||||
|
||||
$results += $result
|
||||
}
|
||||
|
||||
Write-Output (ConvertTo-Json @{
|
||||
success = $true
|
||||
tests = $results
|
||||
} -Compress -Depth 10)
|
||||
}
|
||||
catch {
|
||||
Write-Output (ConvertTo-Json @{
|
||||
success = $false
|
||||
message = $_.Exception.Message
|
||||
} -Compress)
|
||||
}
|
||||
@@ -23,15 +23,13 @@ const AddUnitTestModal = ({isOpen, onClose, onAdd, tests, editTest = null}) => {
|
||||
const handleSubmit = () => {
|
||||
const getNextTestId = testArray => {
|
||||
if (!testArray || testArray.length === 0) return 1;
|
||||
return Math.max(...testArray.map(test => test.id)) + 1;
|
||||
return Math.max(...testArray.map(test => test.id || 0)) + 1;
|
||||
};
|
||||
|
||||
const testData = {
|
||||
id: editTest ? editTest.id : getNextTestId(tests),
|
||||
input,
|
||||
expected: shouldMatch,
|
||||
passes: false,
|
||||
lastRun: null
|
||||
expected: shouldMatch
|
||||
};
|
||||
|
||||
onAdd(testData);
|
||||
|
||||
@@ -6,7 +6,7 @@ import RegexTestingTab from './RegexTestingTab';
|
||||
import {useRegexModal} from '@hooks/useRegexModal';
|
||||
import {RegexPatterns} from '@api/data';
|
||||
import Alert from '@ui/Alert';
|
||||
import {Loader, Play} from 'lucide-react';
|
||||
import {Loader, Play, Save, Trash2, Check} from 'lucide-react';
|
||||
|
||||
const RegexModal = ({
|
||||
pattern: initialPattern,
|
||||
@@ -84,12 +84,13 @@ const RegexModal = ({
|
||||
{initialPattern && !isCloning && (
|
||||
<button
|
||||
onClick={handleDelete}
|
||||
className={`px-4 py-2 text-white rounded transition-colors ${
|
||||
isDeleting
|
||||
? 'bg-red-600 hover:bg-red-700'
|
||||
: 'bg-red-500 hover:bg-red-600'
|
||||
}`}>
|
||||
{isDeleting ? 'Confirm Delete' : 'Delete'}
|
||||
className='inline-flex items-center gap-2 px-4 py-2 rounded bg-gray-800 border border-gray-700 text-gray-200 hover:bg-gray-700 transition-colors'>
|
||||
{isDeleting ? (
|
||||
<Check className="w-4 h-4 text-green-500" />
|
||||
) : (
|
||||
<Trash2 className="w-4 h-4 text-red-500" />
|
||||
)}
|
||||
<span>Delete</span>
|
||||
</button>
|
||||
)}
|
||||
<div className='flex gap-2'>
|
||||
@@ -97,20 +98,20 @@ const RegexModal = ({
|
||||
<button
|
||||
onClick={() => handleRunTests(patternValue, tests)}
|
||||
disabled={isRunningTests}
|
||||
className='inline-flex items-center px-4 py-2 bg-green-600 hover:bg-green-700
|
||||
disabled:bg-green-600/50 text-white rounded transition-colors'>
|
||||
className='inline-flex items-center gap-2 px-4 py-2 rounded bg-gray-800 border border-gray-700 text-gray-200 hover:bg-gray-700 disabled:opacity-50 transition-colors'>
|
||||
{isRunningTests ? (
|
||||
<Loader className='w-4 h-4 mr-2 animate-spin' />
|
||||
<Loader className="w-4 h-4 text-yellow-500 animate-spin" />
|
||||
) : (
|
||||
<Play className='w-4 h-4 mr-2' />
|
||||
<Play className="w-4 h-4 text-green-500" />
|
||||
)}
|
||||
Run Tests
|
||||
<span>Run Tests</span>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={handleSave}
|
||||
className='bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded transition-colors'>
|
||||
Save
|
||||
className='inline-flex items-center gap-2 px-4 py-2 rounded bg-gray-800 border border-gray-700 text-gray-200 hover:bg-gray-700 transition-colors'>
|
||||
<Save className="w-4 h-4 text-blue-500" />
|
||||
<span>Save</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,52 +13,47 @@ const RegexTestingTab = ({
|
||||
}) => {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [editingTest, setEditingTest] = useState(null);
|
||||
const [testResults, setTestResults] = useState({});
|
||||
|
||||
// Wrapped run tests function that stores results
|
||||
const handleRunTests = useCallback(async (testPattern, testData) => {
|
||||
const results = await onRunTests(testPattern, testData);
|
||||
if (results && Array.isArray(results)) {
|
||||
// Store results by test ID
|
||||
const resultsMap = {};
|
||||
results.forEach(result => {
|
||||
resultsMap[result.id] = result;
|
||||
});
|
||||
setTestResults(resultsMap);
|
||||
}
|
||||
return results;
|
||||
}, [onRunTests]);
|
||||
|
||||
useEffect(() => {
|
||||
const needsAutoRun =
|
||||
tests?.length > 0 &&
|
||||
pattern &&
|
||||
tests.some(test => test.passes !== undefined && !test.matchSpan);
|
||||
|
||||
if (needsAutoRun && !isRunningTests) {
|
||||
onRunTests(pattern, tests);
|
||||
// Run tests when pattern or tests change
|
||||
if (tests?.length > 0 && pattern && !isRunningTests) {
|
||||
handleRunTests(pattern, tests);
|
||||
}
|
||||
}, []);
|
||||
}, [pattern]); // Only re-run when pattern changes
|
||||
|
||||
const handleAddOrUpdateTest = useCallback(
|
||||
testData => {
|
||||
let updatedTests;
|
||||
if (editingTest) {
|
||||
updatedTests = tests.map(test =>
|
||||
test.id === testData.id
|
||||
? {
|
||||
...testData,
|
||||
passes: false,
|
||||
lastRun: null,
|
||||
matchedContent: null,
|
||||
matchSpan: null,
|
||||
matchedGroups: []
|
||||
}
|
||||
: test
|
||||
test.id === testData.id ? testData : test
|
||||
);
|
||||
} else {
|
||||
updatedTests = [
|
||||
...tests,
|
||||
{
|
||||
...testData,
|
||||
passes: false,
|
||||
lastRun: null,
|
||||
matchedContent: null,
|
||||
matchSpan: null,
|
||||
matchedGroups: []
|
||||
}
|
||||
];
|
||||
updatedTests = [...tests, testData];
|
||||
}
|
||||
onTestsChange(updatedTests);
|
||||
onRunTests(pattern, updatedTests);
|
||||
// Run tests automatically after adding/updating
|
||||
if (pattern) {
|
||||
handleRunTests(pattern, updatedTests);
|
||||
}
|
||||
setEditingTest(null);
|
||||
},
|
||||
[tests, onTestsChange, onRunTests, pattern, editingTest]
|
||||
[tests, onTestsChange, handleRunTests, pattern, editingTest]
|
||||
);
|
||||
|
||||
const handleEditTest = useCallback(test => {
|
||||
@@ -80,72 +75,81 @@ const RegexTestingTab = ({
|
||||
}, []);
|
||||
|
||||
const totalTests = tests?.length || 0;
|
||||
const passedTests = tests?.filter(test => test.passes)?.length || 0;
|
||||
const passedTests = tests?.filter(test => {
|
||||
const result = testResults[test.id];
|
||||
return result?.passes;
|
||||
})?.length || 0;
|
||||
|
||||
return (
|
||||
<div className='flex flex-col h-full'>
|
||||
{/* Header with Progress Bar */}
|
||||
<div className='flex items-center justify-between pb-4 pr-2'>
|
||||
{/* Header */}
|
||||
<div className='flex items-center justify-between pb-4'>
|
||||
<div>
|
||||
<h2 className='text-xl font-semibold text-gray-900 dark:text-white mb-3'>
|
||||
<h2 className='text-xl font-semibold text-gray-900 dark:text-white mb-1'>
|
||||
Unit Tests
|
||||
</h2>
|
||||
<div className='flex items-center gap-3'>
|
||||
<div className='h-1.5 w-32 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden'>
|
||||
<div
|
||||
className='h-full bg-emerald-500 rounded-full transition-all duration-300'
|
||||
style={{
|
||||
width: `${
|
||||
totalTests
|
||||
? (passedTests / totalTests) * 100
|
||||
: 0
|
||||
}%`
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<span className='text-sm text-gray-600 dark:text-gray-300'>
|
||||
{totalTests > 0
|
||||
? `${passedTests}/${totalTests} tests passing`
|
||||
: 'No tests added yet'}
|
||||
</span>
|
||||
</div>
|
||||
{totalTests > 0 && (
|
||||
<p className='text-sm text-gray-600 dark:text-gray-400'>
|
||||
{passedTests} of {totalTests} tests passing
|
||||
{totalTests > 0 && ` (${Math.round((passedTests / totalTests) * 100)}%)`}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
{tests?.length > 0 && (
|
||||
<button
|
||||
onClick={() => onRunTests(pattern, tests)}
|
||||
onClick={() => handleRunTests(pattern, tests)}
|
||||
disabled={isRunningTests}
|
||||
className='inline-flex items-center px-3 py-2 text-sm font-medium rounded-md bg-green-600 hover:bg-green-700 disabled:bg-green-600/50 text-white'>
|
||||
className='inline-flex items-center gap-2 px-3 py-1.5 text-sm rounded bg-gray-800 border border-gray-700 text-gray-200 hover:bg-gray-700 disabled:opacity-50 transition-colors'>
|
||||
{isRunningTests ? (
|
||||
<Loader className='w-4 h-4 mr-2 animate-spin' />
|
||||
<Loader className='w-3.5 h-3.5 text-yellow-500 animate-spin' />
|
||||
) : (
|
||||
<Play className='w-4 h-4 mr-2' />
|
||||
<Play className='w-3.5 h-3.5 text-green-500' />
|
||||
)}
|
||||
Run Tests
|
||||
<span>Run Tests</span>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => setIsModalOpen(true)}
|
||||
className='inline-flex items-center px-3 py-2 text-sm font-medium rounded-md bg-blue-600 hover:bg-blue-700 text-white'>
|
||||
<Plus className='w-4 h-4 mr-2' />
|
||||
Add Test
|
||||
className='inline-flex items-center gap-2 px-3 py-1.5 text-sm rounded bg-gray-800 border border-gray-700 text-gray-200 hover:bg-gray-700 transition-colors'>
|
||||
<Plus className='w-3.5 h-3.5 text-blue-500' />
|
||||
<span>Add Test</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Progress Bar */}
|
||||
{totalTests > 0 && (
|
||||
<div className='mb-4'>
|
||||
<div className='h-2 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden'>
|
||||
<div
|
||||
className='h-full bg-emerald-500 transition-all duration-500 ease-out'
|
||||
style={{width: `${(passedTests / totalTests) * 100}%`}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Test List */}
|
||||
<div className='flex-1 overflow-y-auto pr-2'>
|
||||
{tests?.length > 0 ? (
|
||||
<div className='space-y-3'>
|
||||
{tests.map(test => (
|
||||
<UnitTest
|
||||
key={test.id}
|
||||
test={test}
|
||||
pattern={pattern}
|
||||
onDelete={() => handleDeleteTest(test.id)}
|
||||
onEdit={() => handleEditTest(test)}
|
||||
/>
|
||||
))}
|
||||
{tests.map(test => {
|
||||
// Merge saved test with runtime results
|
||||
const testWithResults = {
|
||||
...test,
|
||||
...testResults[test.id]
|
||||
};
|
||||
return (
|
||||
<UnitTest
|
||||
key={test.id}
|
||||
test={testWithResults}
|
||||
pattern={pattern}
|
||||
onDelete={() => handleDeleteTest(test.id)}
|
||||
onEdit={() => handleEditTest(test)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className='text-center py-12 rounded-lg'>
|
||||
@@ -173,15 +177,7 @@ RegexTestingTab.propTypes = {
|
||||
PropTypes.shape({
|
||||
id: PropTypes.number.isRequired,
|
||||
input: PropTypes.string.isRequired,
|
||||
expected: PropTypes.bool.isRequired,
|
||||
passes: PropTypes.bool.isRequired,
|
||||
lastRun: PropTypes.string,
|
||||
matchedContent: PropTypes.string,
|
||||
matchedGroups: PropTypes.arrayOf(PropTypes.string),
|
||||
matchSpan: PropTypes.shape({
|
||||
start: PropTypes.number,
|
||||
end: PropTypes.number
|
||||
})
|
||||
expected: PropTypes.bool.isRequired
|
||||
})
|
||||
),
|
||||
onTestsChange: PropTypes.func.isRequired,
|
||||
|
||||
@@ -68,11 +68,7 @@ const UnitTest = ({test, pattern, onDelete, onEdit}) => {
|
||||
: 'Should Not Match'}
|
||||
</span>
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
<span className='text-xs text-gray-500 dark:text-gray-400'>
|
||||
Last run: {test.lastRun}
|
||||
</span>
|
||||
<div className='flex gap-2'>
|
||||
<div className='flex gap-2'>
|
||||
<button
|
||||
onClick={onEdit}
|
||||
className='p-1 rounded shrink-0 transition-transform transform hover:scale-110'>
|
||||
@@ -83,7 +79,6 @@ const UnitTest = ({test, pattern, onDelete, onEdit}) => {
|
||||
className='p-1 rounded shrink-0 transition-transform transform hover:scale-110'>
|
||||
<Trash2 className='w-4 h-4 text-gray-500 dark:text-gray-400' />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -112,7 +107,6 @@ UnitTest.propTypes = {
|
||||
input: PropTypes.string.isRequired,
|
||||
expected: PropTypes.bool.isRequired,
|
||||
passes: PropTypes.bool.isRequired,
|
||||
lastRun: PropTypes.string,
|
||||
matchedContent: PropTypes.string,
|
||||
matchedGroups: PropTypes.arrayOf(PropTypes.string),
|
||||
matchSpan: PropTypes.shape({
|
||||
|
||||
@@ -79,12 +79,19 @@ export const useRegexModal = (initialPattern, onSave) => {
|
||||
}
|
||||
|
||||
try {
|
||||
// Clean tests to only include saved data
|
||||
const cleanTests = tests.map((test, index) => ({
|
||||
id: test.id || index + 1,
|
||||
input: test.input,
|
||||
expected: test.expected
|
||||
}));
|
||||
|
||||
const data = {
|
||||
name,
|
||||
pattern: patternValue,
|
||||
description,
|
||||
tags,
|
||||
tests
|
||||
tests: cleanTests
|
||||
};
|
||||
|
||||
if (initialPattern && !isCloning) {
|
||||
@@ -111,15 +118,16 @@ export const useRegexModal = (initialPattern, onSave) => {
|
||||
const handleRunTests = useCallback(
|
||||
async (pattern, tests) => {
|
||||
try {
|
||||
const updatedTests = await runTests(pattern, tests);
|
||||
if (updatedTests) {
|
||||
setTests(updatedTests);
|
||||
}
|
||||
const testResults = await runTests(pattern, tests);
|
||||
// We don't update the tests state with results
|
||||
// Results are only used for display, not saved
|
||||
return testResults;
|
||||
} catch (error) {
|
||||
console.error('Error running tests:', error);
|
||||
Alert.error(
|
||||
error.message || 'Failed to run tests. Please try again.'
|
||||
);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
[runTests]
|
||||
|
||||
@@ -34,14 +34,12 @@ export const useRegexTesting = onUpdateTests => {
|
||||
}
|
||||
);
|
||||
|
||||
// Update tests through the callback
|
||||
if (onUpdateTests) {
|
||||
onUpdateTests(result.tests);
|
||||
}
|
||||
// Return the test results (with match information)
|
||||
// Don't save these results, just return them for display
|
||||
return result.tests;
|
||||
} else {
|
||||
Alert.error(result.message || 'Failed to run tests');
|
||||
return tests;
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error running tests:', error);
|
||||
|
||||
Reference in New Issue
Block a user