Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions internal/runner/jest.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,23 @@ func (j Jest) commandNameAndArgs(cmd string, testCases []string) (string, []stri
if err != nil {
return "", []string{}, err
}

// Escape parentheses and brackets in test file paths since Jest
// interprets CLI arguments as regex patterns. These characters are
// commonly used in Next.js App Router paths (e.g., (main) for route
// groups, [id] for dynamic routes) and cause regex matching issues.
// We don't escape all regex metacharacters (like dots) because they
// work fine in practice and escaping them breaks existing behavior.
escapedTestCases := make([]string, len(testCases))
for i, testCase := range testCases {
escapedTestCases[i] = escapeJestPathPattern(testCase)
}

idx := slices.Index(words, "{{testExamples}}")
if idx < 0 {
words = append(words, testCases...)
words = append(words, escapedTestCases...)
} else {
words = slices.Replace(words, idx, idx+1, testCases...)
words = slices.Replace(words, idx, idx+1, escapedTestCases...)
}

outputIdx := slices.Index(words, "{{resultPath}}")
Expand Down Expand Up @@ -241,3 +253,16 @@ func (j Jest) retryCommandNameAndArgs(cmd string, testCases []string, testPaths
func (j Jest) GetExamples(files []string) ([]plan.TestCase, error) {
return nil, fmt.Errorf("not supported in Jest")
}

// escapeJestPathPattern escapes characters in file paths that cause issues
// when Jest interprets them as regex patterns. This specifically targets
// parentheses and brackets used in Next.js App Router conventions.
func escapeJestPathPattern(path string) string {
replacer := strings.NewReplacer(
"(", "\\(",
")", "\\)",
"[", "\\[",
"]", "\\]",
)
return replacer.Replace(path)
}
38 changes: 38 additions & 0 deletions internal/runner/jest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,7 @@ func TestJestCommandNameAndArgs_WithInterpolationPlaceholder(t *testing.T) {
}

wantName := "jest"
// Paths with parentheses and brackets are escaped for regex since Jest interprets them as patterns
wantArgs := []string{"spec/user.spec.js", "spec/billing.spec.js", "--outputFile", "jest.json"}

if diff := cmp.Diff(gotName, wantName); diff != "" {
Expand All @@ -379,6 +380,7 @@ func TestJestCommandNameAndArgs_WithoutInterpolationPlaceholder(t *testing.T) {
}

wantName := "jest"
// Paths with parentheses and brackets are escaped for regex since Jest interprets them as patterns
wantArgs := []string{"--json", "--outputFile", "jest.json", "spec/user.spec.js", "spec/billing.spec.js"}

if diff := cmp.Diff(gotName, wantName); diff != "" {
Expand Down Expand Up @@ -413,6 +415,42 @@ func TestJestCommandNameAndArgs_InvalidTestCommand(t *testing.T) {
}
}

func TestJestCommandNameAndArgs_WithSpecialCharactersInPath(t *testing.T) {
// Test paths with special regex characters like parentheses (Next.js route groups)
// and square brackets (Next.js dynamic routes)
testCases := []string{
"src/app/(main)/page.test.tsx",
"src/app/(main)/[catalogId]/product.test.tsx",
}
testCommand := "jest {{testExamples}} --outputFile {{resultPath}}"

jest := NewJest(RunnerConfig{
TestCommand: testCommand,
ResultPath: "jest.json",
})

gotName, gotArgs, err := jest.commandNameAndArgs(testCommand, testCases)
if err != nil {
t.Errorf("commandNameAndArgs(%q, %q) error = %v", testCases, testCommand, err)
}

wantName := "jest"
// Parentheses and brackets should be escaped for regex (Next.js App Router conventions)
wantArgs := []string{
`src/app/\(main\)/page.test.tsx`,
`src/app/\(main\)/\[catalogId\]/product.test.tsx`,
"--outputFile",
"jest.json",
}

if diff := cmp.Diff(gotName, wantName); diff != "" {
t.Errorf("commandNameAndArgs(%q, %q) diff (-got +want):\n%s", testCases, testCommand, diff)
}
if diff := cmp.Diff(gotArgs, wantArgs); diff != "" {
t.Errorf("commandNameAndArgs(%q, %q) diff (-got +want):\n%s", testCases, testCommand, diff)
}
}

func TestJestRetryCommandNameAndArgs_HappyPath(t *testing.T) {
testCases := []string{"this will fail", "this other one will fail"}
testPaths := []string{"spec/user.spec.js", "spec/billing.spec.js"}
Expand Down