Repository: DenverCoder1/github-readme-streak-stats (php)

Issue:
Gradient background appears solid black on error cards
**Describe the bug**
Gradient background appears solid black on error cards instead of showing the gradient.

**To Reproduce**
Steps to reproduce the behavior:

1. Go to https://streak-stats.demolab.com/?user=&background=45%2CEB5454%2C96EB52
2. See error

**Expected behavior**
Background should appear with the requested colors

**Screenshots**

![image](https://user-images.githubusercontent.com/20955511/232573356-a7dc8205-836a-4161-a234-3259e90665f7.png)

**Desktop (please complete the following information):**

- OS: [e.g. iOS] Arch Linux
- Browser [e.g. chrome, safari] Brave
- Version [e.g. 22] Version 1.50.114 Chromium: 112.0.5615.49 (Official Build) (64-bit)


Pull request description:
fix: Apply background gradients on error cards
## Description

Fixes #494

### Type of change

<!-- Please delete options that are not relevant. -->

- [x] Bug fix (added a non-breaking change which fixes an issue)
- [ ] New feature (added a non-breaking change which adds functionality)
- [ ] Updated documentation (updated the readme, templates, or other repo files)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)

## How Has This Been Tested?

<!--
If you have changed a feature of the stats cards, please describe the tests you made to verify your changes.
Changes strictly related to documentation can skip this section.
-->

- [x] Tested locally with a valid username
- [x] Tested locally with an invalid username
- [x] Ran tests with `composer test`
- [x] Added or updated test cases to test new features

## Checklist:

- [x] I have checked to make sure no other [pull requests](https://github.com/DenverCoder1/github-readme-streak-stats/pulls?q=is%3Apr+sort%3Aupdated-desc+) are open for this issue
- [x] The code is properly formatted and is consistent with the existing code style
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings

## Screenshots

<!-- If you have updated the design or appearance, please include a screenshot of your changes. -->
![image](https://user-images.githubusercontent.com/20955511/232626246-64e79cc2-1c6f-47c3-8bfe-bdfaf78a392d.png)



Patch:
diff --git a/src/card.php b/src/card.php
index 8dc15b3..a9d5ca7 100644
--- a/src/card.php
+++ b/src/card.php
@@ -151,6 +151,22 @@ function getRequestedTheme(array $params): array
         $theme["border"] = "#0000"; // transparent
     }
 
+    // set background
+    $gradient = "";
+    $backgroundParts = explode(",", $theme["background"] ?? "");
+    if (count($backgroundParts) >= 3) {
+        $theme["background"] = "url(#gradient)";
+        $gradient = "<linearGradient id='gradient' gradientTransform='rotate({$backgroundParts[0]})' gradientUnits='userSpaceOnUse'>";
+        $backgroundColors = array_slice($backgroundParts, 1);
+        $colorCount = count($backgroundColors);
+        for ($index = 0; $index < $colorCount; $index++) {
+            $offset = ($index * 100) / ($colorCount - 1);
+            $gradient .= "<stop offset='{$offset}%' stop-color='#{$backgroundColors[$index]}' />";
+        }
+        $gradient .= "</linearGradient>";
+    }
+    $theme["backgroundGradient"] = $gradient;
+
     return $theme;
 }
 
@@ -353,24 +369,6 @@ function generateCard(array $stats, array $params = null): string
     $currentStreakOffset = $showCurrentStreak ? $columnOffsets[$nextColumnIndex++] : -999;
     $longestStreakOffset = $showLongestStreak ? $columnOffsets[$nextColumnIndex++] : -999;
 
-    // set background
-    $backgroundParts = explode(",", $theme["background"] ?? "");
-    $backgroundIsGradient = count($backgroundParts) >= 3;
-
-    $background = $theme["background"];
-    $gradient = "";
-    if ($backgroundIsGradient) {
-        $background = "url(#gradient)";
-        $gradient = "<defs><linearGradient id='gradient' gradientTransform='rotate({$backgroundParts[0]})' gradientUnits='userSpaceOnUse'>";
-        $backgroundColors = array_slice($backgroundParts, 1);
-        $colorCount = count($backgroundColors);
-        for ($index = 0; $index < $colorCount; $index++) {
-            $offset = ($index * 100) / ($colorCount - 1);
-            $gradient .= "<stop offset='{$offset}%' stop-color='#{$backgroundColors[$index]}' />";
-        }
-        $gradient .= "</linearGradient></defs>";
-    }
-
     // total contributions
     $totalContributions = $numFormatter->format($stats["totalContributions"]);
     $firstContribution = formatDate($stats["firstContribution"], $dateFormat, $localeCode);
@@ -439,7 +437,6 @@ function generateCard(array $stats, array $params = null): string
                 100% { opacity: 1; }
             }
         </style>
-        {$gradient}
         <defs>
             <clipPath id='outer_rectangle'>
                 <rect width='{$cardWidth}' height='195' rx='{$borderRadius}'/>
@@ -448,10 +445,11 @@ function generateCard(array $stats, array $params = null): string
                 <rect width='{$cardWidth}' height='195' fill='white'/>
                 <ellipse id='mask-ellipse' cx='{$currentStreakOffset}' cy='32' rx='13' ry='18' fill='black'/>
             </mask>
+            {$theme["backgroundGradient"]}
         </defs>
         <g clip-path='url(#outer_rectangle)'>
             <g style='isolation: isolate'>
-                <rect stroke='{$theme["border"]}' fill='{$background}' rx='{$borderRadius}' x='0.5' y='0.5' width='{$rectWidth}' height='194'/>
+                <rect stroke='{$theme["border"]}' fill='{$theme["background"]}' rx='{$borderRadius}' x='0.5' y='0.5' width='{$rectWidth}' height='194'/>
             </g>
             <g style='isolation: isolate'>
                 <line x1='{$barOffsets[0]}' y1='28' x2='{$barOffsets[0]}' y2='170' vector-effect='non-scaling-stroke' stroke-width='1' stroke='{$theme["stroke"]}' stroke-linejoin='miter' stroke-linecap='square' stroke-miterlimit='3'/>
@@ -572,6 +570,7 @@ function generateErrorCard(string $message, array $params = null): string
             <clipPath id='outer_rectangle'>
                 <rect width='{$cardWidth}' height='195' rx='{$borderRadius}'/>
             </clipPath>
+            {$theme["backgroundGradient"]}
         </defs>
         <g clip-path='url(#outer_rectangle)'>
             <g style='isolation: isolate'>


Test patch:
diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php
index 90c76df..f62a440 100644
--- a/tests/OptionsTest.php
+++ b/tests/OptionsTest.php
@@ -31,12 +31,9 @@ final class OptionsTest extends TestCase
         $themes = include "src/themes.php";
         foreach ($themes as $theme => $colors) {
             $actualColors = getRequestedTheme(["theme" => $theme]);
+            unset($actualColors["backgroundGradient"]);
             $this->assertEquals($colors, $actualColors);
         }
-        // test old theme names
-        $this->assertEquals($themes["holi-theme"], getRequestedTheme(["theme" => "holi_theme"]));
-        $this->assertEquals($themes["gruvbox-duo"], getRequestedTheme(["theme" => "gruvbox_duo"]));
-        $this->assertEquals($themes["deepblue"], getRequestedTheme(["theme" => "deepBlue"]));
     }
 
     /**
@@ -48,7 +45,9 @@ final class OptionsTest extends TestCase
         // request parameters
         $params = ["theme" => "not a theme name"];
         // test that invalid theme name gives default values
-        $this->assertEquals($this->defaultTheme, getRequestedTheme($params));
+        $actual = getRequestedTheme($params);
+        unset($actual["backgroundGradient"]);
+        $this->assertEquals($this->defaultTheme, $actual);
     }
 
     /**
@@ -101,7 +100,9 @@ final class OptionsTest extends TestCase
             // update parameter in expected result
             $expected = array_merge($expected, [$param => "#f00"]);
             // test color change
-            $this->assertEquals($expected, getRequestedTheme($params));
+            $actual = getRequestedTheme($params);
+            unset($actual["backgroundGradient"]);
+            $this->assertEquals($expected, $actual);
         }
     }
 
@@ -127,7 +128,9 @@ final class OptionsTest extends TestCase
             // update parameter in expected result
             $expected = array_merge($expected, ["background" => $output]);
             // test color change
-            $this->assertEquals($expected, getRequestedTheme($params));
+            $actual = getRequestedTheme($params);
+            unset($actual["backgroundGradient"]);
+            $this->assertEquals($expected, $actual);
         }
     }
 
@@ -146,7 +149,9 @@ final class OptionsTest extends TestCase
             // set request parameter
             $params = ["background" => $input];
             // test that theme is still default
-            $this->assertEquals($this->defaultTheme, getRequestedTheme($params));
+            $actual = getRequestedTheme($params);
+            unset($actual["backgroundGradient"]);
+            $this->assertEquals($this->defaultTheme, $actual);
         }
     }
 
diff --git a/tests/RenderTest.php b/tests/RenderTest.php
index 5d48832..b8b28f1 100644
--- a/tests/RenderTest.php
+++ b/tests/RenderTest.php
@@ -187,7 +187,7 @@ final class RenderTest extends TestCase
         $render = generateOutput($this->testStats, $this->testParams)["body"];
         $this->assertStringContainsString("fill='url(#gradient)'", $render);
         $this->assertStringContainsString(
-            "<defs><linearGradient id='gradient' gradientTransform='rotate(45)' gradientUnits='userSpaceOnUse'><stop offset='0%' stop-color='#f00' /><stop offset='100%' stop-color='#e11' /></linearGradient></defs>",
+            "<linearGradient id='gradient' gradientTransform='rotate(45)' gradientUnits='userSpaceOnUse'><stop offset='0%' stop-color='#f00' /><stop offset='100%' stop-color='#e11' /></linearGradient>",
             $render
         );
     }
@@ -201,7 +201,7 @@ final class RenderTest extends TestCase
         $render = generateOutput($this->testStats, $this->testParams)["body"];
         $this->assertStringContainsString("fill='url(#gradient)'", $render);
         $this->assertStringContainsString(
-            "<defs><linearGradient id='gradient' gradientTransform='rotate(-45)' gradientUnits='userSpaceOnUse'><stop offset='0%' stop-color='#f00' /><stop offset='33.333333333333%' stop-color='#4e5' /><stop offset='66.666666666667%' stop-color='#ddd' /><stop offset='100%' stop-color='#fff' /></linearGradient></defs>",
+            "<linearGradient id='gradient' gradientTransform='rotate(-45)' gradientUnits='userSpaceOnUse'><stop offset='0%' stop-color='#f00' /><stop offset='33.333333333333%' stop-color='#4e5' /><stop offset='66.666666666667%' stop-color='#ddd' /><stop offset='100%' stop-color='#fff' /></linearGradient>",
             $render
         );
     }
diff --git a/tests/expected/test_card.svg b/tests/expected/test_card.svg
index ea6ba66..ca488f0 100644
--- a/tests/expected/test_card.svg
+++ b/tests/expected/test_card.svg
@@ -11,7 +11,6 @@
                 100% { opacity: 1; }
             }
         </style>
-        
         <defs>
             <clipPath id='outer_rectangle'>
                 <rect width='495' height='195' rx='4.5'/>
@@ -20,6 +19,7 @@
                 <rect width='495' height='195' fill='white'/>
                 <ellipse id='mask-ellipse' cx='247.5' cy='32' rx='13' ry='18' fill='black'/>
             </mask>
+            
         </defs>
         <g clip-path='url(#outer_rectangle)'>
             <g style='isolation: isolate'>
diff --git a/tests/expected/test_error_card.svg b/tests/expected/test_error_card.svg
index 8dfe24f..23c28d2 100644
--- a/tests/expected/test_error_card.svg
+++ b/tests/expected/test_error_card.svg
@@ -8,6 +8,7 @@
             <clipPath id='outer_rectangle'>
                 <rect width='495' height='195' rx='4.5'/>
             </clipPath>
+            
         </defs>
         <g clip-path='url(#outer_rectangle)'>
             <g style='isolation: isolate'>