diff --git a/backend/app/data/utils.py b/backend/app/data/utils.py
index 1cdea1b..c2ade3e 100644
--- a/backend/app/data/utils.py
+++ b/backend/app/data/utils.py
@@ -184,18 +184,11 @@ def test_regex_pattern(
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.
-
- Args:
- pattern: The regex pattern to test
- tests: List of test dictionaries with 'input', 'expected', 'id', and 'passes' fields
-
- Returns:
- Tuple of (success, message, updated_tests)
+ Returns match information along with test results.
"""
logger.info(f"Starting regex pattern test - Pattern: {pattern}")
try:
- # Try to compile the regex with PCRE2 compatibility
try:
compiled_pattern = regex.compile(pattern,
regex.V1 | regex.IGNORECASE)
@@ -208,35 +201,45 @@ def test_regex_pattern(
current_time = datetime.now().isoformat()
logger.info(f"Processing {len(tests)} test cases")
- # Run each test
for test in tests:
test_id = test.get('id', 'unknown')
test_input = test.get('input', '')
expected = test.get('expected', False)
- logger.info(
- f"Running test {test_id} - Input: {test_input}, Expected: {expected}"
- )
-
try:
- # Test if pattern matches input
- matches = bool(compiled_pattern.search(test_input))
- # Update test result
+ match = compiled_pattern.search(test_input)
+ matches = bool(match)
+
+ # Update test result with basic fields
test['passes'] = matches == expected
test['lastRun'] = current_time
- if test['passes']:
- logger.info(
- f"Test {test_id} passed - Match result: {matches}")
+ # 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:
- logger.warning(
- f"Test {test_id} failed - Expected {expected}, got {matches}"
- )
+ 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.warning(f"Error running test {test_id}: {str(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))
@@ -245,9 +248,10 @@ def test_regex_pattern(
)
return True, "", tests
+
except Exception as e:
- logger.warning(f"Unexpected error in test_regex_pattern: {str(e)}",
- exc_info=True)
+ logger.error(f"Unexpected error in test_regex_pattern: {str(e)}",
+ exc_info=True)
return False, str(e), tests
diff --git a/frontend/src/components/regex/RegexTestingTab.jsx b/frontend/src/components/regex/RegexTestingTab.jsx
index 33879a4..cad5c62 100644
--- a/frontend/src/components/regex/RegexTestingTab.jsx
+++ b/frontend/src/components/regex/RegexTestingTab.jsx
@@ -1,28 +1,9 @@
-// RegexTestingTab.jsx
-import React, {useState, useCallback} from 'react';
+import React, {useState, useCallback, useEffect} from 'react';
import PropTypes from 'prop-types';
import {Plus, Loader, Play} from 'lucide-react';
import UnitTest from './UnitTest';
import AddUnitTestModal from './AddUnitTestModal';
-const formatTestDate = dateString => {
- if (!dateString) return null;
-
- try {
- return new Date(dateString).toLocaleString(undefined, {
- year: 'numeric',
- month: 'short',
- day: 'numeric',
- hour: '2-digit',
- minute: '2-digit',
- second: '2-digit'
- });
- } catch (error) {
- console.error('Error formatting date:', error);
- return dateString;
- }
-};
-
const RegexTestingTab = ({
pattern,
tests,
@@ -33,17 +14,45 @@ const RegexTestingTab = ({
const [isModalOpen, setIsModalOpen] = useState(false);
const [editingTest, setEditingTest] = useState(null);
+ useEffect(() => {
+ const needsAutoRun =
+ tests?.length > 0 &&
+ pattern &&
+ tests.some(test => test.passes !== undefined && !test.matchSpan);
+
+ if (needsAutoRun && !isRunningTests) {
+ onRunTests(pattern, tests);
+ }
+ }, []);
+
const handleAddOrUpdateTest = useCallback(
testData => {
let updatedTests;
if (editingTest) {
- // Update existing test
updatedTests = tests.map(test =>
- test.id === testData.id ? testData : test
+ test.id === testData.id
+ ? {
+ ...testData,
+ passes: false,
+ lastRun: null,
+ matchedContent: null,
+ matchSpan: null,
+ matchedGroups: []
+ }
+ : test
);
} else {
- // Add new test
- updatedTests = [...tests, testData];
+ updatedTests = [
+ ...tests,
+ {
+ ...testData,
+ passes: false,
+ lastRun: null,
+ matchedContent: null,
+ matchSpan: null,
+ matchedGroups: []
+ }
+ ];
}
onTestsChange(updatedTests);
onRunTests(pattern, updatedTests);
@@ -70,13 +79,12 @@ const RegexTestingTab = ({
setEditingTest(null);
}, []);
- // Calculate test statistics
const totalTests = tests?.length || 0;
const passedTests = tests?.filter(test => test.passes)?.length || 0;
return (
- {/* Header Section with Progress Bar */}
+ {/* Header with Progress Bar */}
@@ -132,10 +140,7 @@ const RegexTestingTab = ({
{tests.map(test => (
handleDeleteTest(test.id)}
onEdit={() => handleEditTest(test)}
@@ -170,7 +175,13 @@ RegexTestingTab.propTypes = {
input: PropTypes.string.isRequired,
expected: PropTypes.bool.isRequired,
passes: PropTypes.bool.isRequired,
- lastRun: PropTypes.string
+ lastRun: PropTypes.string,
+ matchedContent: PropTypes.string,
+ matchedGroups: PropTypes.arrayOf(PropTypes.string),
+ matchSpan: PropTypes.shape({
+ start: PropTypes.number,
+ end: PropTypes.number
+ })
})
),
onTestsChange: PropTypes.func.isRequired,
diff --git a/frontend/src/components/regex/UnitTest.jsx b/frontend/src/components/regex/UnitTest.jsx
index d0430a6..6181f73 100644
--- a/frontend/src/components/regex/UnitTest.jsx
+++ b/frontend/src/components/regex/UnitTest.jsx
@@ -3,154 +3,71 @@ import PropTypes from 'prop-types';
import {Trash2, Pencil} from 'lucide-react';
import DeleteConfirmationModal from '@ui/DeleteConfirmationModal';
-const MatchHighlight = ({input, pattern, test}) => {
- if (!pattern) return {input};
-
- try {
- const regex = new RegExp(pattern, 'g');
- const matches = [];
- let match;
-
- while ((match = regex.exec(input)) !== null) {
- // Avoid infinite loops with zero-length matches
- if (match.index === regex.lastIndex) {
- regex.lastIndex++;
- }
- matches.push(match);
- }
-
- if (!matches.length) {
- return {input};
- }
-
- let segments = [];
- let lastIndex = 0;
-
- matches.forEach(match => {
- let matchText = '';
- let matchStart = match.index;
-
- // Use capturing groups if they exist
- let capturingGroupIndex = 1;
- while (
- capturingGroupIndex < match.length &&
- !match[capturingGroupIndex]
- ) {
- capturingGroupIndex++;
- }
- if (capturingGroupIndex < match.length) {
- matchText = match[capturingGroupIndex];
-
- // Find the position of matchText in the input, starting from match.index
- matchStart = input.indexOf(matchText, match.index);
-
- if (matchStart === -1) {
- // If not found, skip this match
- return;
- }
- } else {
- // No capturing group match, use full match
- matchText = match[0];
- }
-
- // Add non-highlighted segment before the match
- if (matchStart > lastIndex) {
- segments.push({
- text: input.slice(lastIndex, matchStart),
- highlight: false
- });
- }
-
- // Add the highlighted match
- if (matchText.length > 0) {
- segments.push({
- text: matchText,
- highlight: true
- });
- lastIndex = matchStart + matchText.length;
- } else {
- // Handle zero-length matches
- lastIndex = matchStart;
- }
- });
-
- // Add any remaining non-highlighted text
- if (lastIndex < input.length) {
- segments.push({
- text: input.slice(lastIndex),
- highlight: false
- });
- }
-
- return (
-
-
- {segments.map((segment, idx) => (
-
- {segment.text}
-
- ))}
-
-
- );
- } catch (error) {
- console.error('Regex error:', error);
- return {input};
- }
-};
-
const UnitTest = ({test, pattern, onDelete, onEdit}) => {
const [showDeleteModal, setShowDeleteModal] = useState(false);
- const handleDeleteClick = () => {
- setShowDeleteModal(true);
- };
+ const renderHighlightedInput = () => {
+ if (!test.matchSpan) {
+ return (
+ {test.input}
+ );
+ }
- const handleConfirmDelete = () => {
- onDelete();
- setShowDeleteModal(false);
+ const preMatch = test.input.slice(0, test.matchSpan.start);
+ const match = test.input.slice(
+ test.matchSpan.start,
+ test.matchSpan.end
+ );
+ const postMatch = test.input.slice(test.matchSpan.end);
+
+ return (
+
+ {preMatch}
+
+ {match}
+
+ {postMatch}
+
+ );
};
return (
<>
+ relative rounded-lg border group
+ ${
+ test.passes
+ ? 'border-emerald-200 dark:border-emerald-800 bg-emerald-50 dark:bg-emerald-900/20'
+ : 'border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-900/20'
+ }
+ `}>
{/* Header */}
+ ${
+ test.passes
+ ? 'text-emerald-700 dark:text-emerald-300'
+ : 'text-red-700 dark:text-red-300'
+ }
+ `}>
{test.expected
? 'Should Match'
: 'Should Not Match'}
@@ -167,7 +84,7 @@ const UnitTest = ({test, pattern, onDelete, onEdit}) => {
@@ -179,11 +96,7 @@ const UnitTest = ({test, pattern, onDelete, onEdit}) => {
-
+ {renderHighlightedInput()}
@@ -192,7 +105,7 @@ const UnitTest = ({test, pattern, onDelete, onEdit}) => {
setShowDeleteModal(false)}
- onConfirm={handleConfirmDelete}
+ onConfirm={onDelete}
/>
>
);
@@ -204,19 +117,17 @@ UnitTest.propTypes = {
input: PropTypes.string.isRequired,
expected: PropTypes.bool.isRequired,
passes: PropTypes.bool.isRequired,
- lastRun: PropTypes.string
+ lastRun: PropTypes.string,
+ matchedContent: PropTypes.string,
+ matchedGroups: PropTypes.arrayOf(PropTypes.string),
+ matchSpan: PropTypes.shape({
+ start: PropTypes.number,
+ end: PropTypes.number
+ })
}).isRequired,
pattern: PropTypes.string.isRequired,
onDelete: PropTypes.func.isRequired,
onEdit: PropTypes.func.isRequired
};
-MatchHighlight.propTypes = {
- input: PropTypes.string.isRequired,
- pattern: PropTypes.string.isRequired,
- test: PropTypes.shape({
- passes: PropTypes.bool.isRequired
- }).isRequired
-};
-
export default UnitTest;