Chris@14: Chris@14: * Chris@14: * For the full copyright and license information, please view the LICENSE Chris@14: * file that was distributed with this source code. Chris@14: */ Chris@14: Chris@14: namespace SebastianBergmann\CodeCoverage\Report\Html; Chris@14: Chris@14: use SebastianBergmann\CodeCoverage\Node\AbstractNode; Chris@14: use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; Chris@14: Chris@14: /** Chris@14: * Renders the dashboard for a directory node. Chris@14: */ Chris@14: class Dashboard extends Renderer Chris@14: { Chris@14: /** Chris@14: * @param DirectoryNode $node Chris@14: * @param string $file Chris@14: * Chris@14: * @throws \InvalidArgumentException Chris@14: */ Chris@14: public function render(DirectoryNode $node, $file) Chris@14: { Chris@14: $classes = $node->getClassesAndTraits(); Chris@14: $template = new \Text_Template( Chris@14: $this->templatePath . 'dashboard.html', Chris@14: '{{', Chris@14: '}}' Chris@14: ); Chris@14: Chris@14: $this->setCommonTemplateVariables($template, $node); Chris@14: Chris@14: $baseLink = $node->getId() . '/'; Chris@14: $complexity = $this->complexity($classes, $baseLink); Chris@14: $coverageDistribution = $this->coverageDistribution($classes); Chris@14: $insufficientCoverage = $this->insufficientCoverage($classes, $baseLink); Chris@14: $projectRisks = $this->projectRisks($classes, $baseLink); Chris@14: Chris@14: $template->setVar( Chris@14: [ Chris@14: 'insufficient_coverage_classes' => $insufficientCoverage['class'], Chris@14: 'insufficient_coverage_methods' => $insufficientCoverage['method'], Chris@14: 'project_risks_classes' => $projectRisks['class'], Chris@14: 'project_risks_methods' => $projectRisks['method'], Chris@14: 'complexity_class' => $complexity['class'], Chris@14: 'complexity_method' => $complexity['method'], Chris@14: 'class_coverage_distribution' => $coverageDistribution['class'], Chris@14: 'method_coverage_distribution' => $coverageDistribution['method'] Chris@14: ] Chris@14: ); Chris@14: Chris@14: $template->renderTo($file); Chris@14: } Chris@14: Chris@14: /** Chris@14: * Returns the data for the Class/Method Complexity charts. Chris@14: * Chris@14: * @param array $classes Chris@14: * @param string $baseLink Chris@14: * Chris@14: * @return array Chris@14: */ Chris@14: protected function complexity(array $classes, $baseLink) Chris@14: { Chris@14: $result = ['class' => [], 'method' => []]; Chris@14: Chris@14: foreach ($classes as $className => $class) { Chris@14: foreach ($class['methods'] as $methodName => $method) { Chris@14: if ($className !== '*') { Chris@14: $methodName = $className . '::' . $methodName; Chris@14: } Chris@14: Chris@14: $result['method'][] = [ Chris@14: $method['coverage'], Chris@14: $method['ccn'], Chris@14: \sprintf( Chris@14: '%s', Chris@14: \str_replace($baseLink, '', $method['link']), Chris@14: $methodName Chris@14: ) Chris@14: ]; Chris@14: } Chris@14: Chris@14: $result['class'][] = [ Chris@14: $class['coverage'], Chris@14: $class['ccn'], Chris@14: \sprintf( Chris@14: '%s', Chris@14: \str_replace($baseLink, '', $class['link']), Chris@14: $className Chris@14: ) Chris@14: ]; Chris@14: } Chris@14: Chris@14: return [ Chris@14: 'class' => \json_encode($result['class']), Chris@14: 'method' => \json_encode($result['method']) Chris@14: ]; Chris@14: } Chris@14: Chris@14: /** Chris@14: * Returns the data for the Class / Method Coverage Distribution chart. Chris@14: * Chris@14: * @param array $classes Chris@14: * Chris@14: * @return array Chris@14: */ Chris@14: protected function coverageDistribution(array $classes) Chris@14: { Chris@14: $result = [ Chris@14: 'class' => [ Chris@14: '0%' => 0, Chris@14: '0-10%' => 0, Chris@14: '10-20%' => 0, Chris@14: '20-30%' => 0, Chris@14: '30-40%' => 0, Chris@14: '40-50%' => 0, Chris@14: '50-60%' => 0, Chris@14: '60-70%' => 0, Chris@14: '70-80%' => 0, Chris@14: '80-90%' => 0, Chris@14: '90-100%' => 0, Chris@14: '100%' => 0 Chris@14: ], Chris@14: 'method' => [ Chris@14: '0%' => 0, Chris@14: '0-10%' => 0, Chris@14: '10-20%' => 0, Chris@14: '20-30%' => 0, Chris@14: '30-40%' => 0, Chris@14: '40-50%' => 0, Chris@14: '50-60%' => 0, Chris@14: '60-70%' => 0, Chris@14: '70-80%' => 0, Chris@14: '80-90%' => 0, Chris@14: '90-100%' => 0, Chris@14: '100%' => 0 Chris@14: ] Chris@14: ]; Chris@14: Chris@14: foreach ($classes as $class) { Chris@14: foreach ($class['methods'] as $methodName => $method) { Chris@14: if ($method['coverage'] === 0) { Chris@14: $result['method']['0%']++; Chris@14: } elseif ($method['coverage'] === 100) { Chris@14: $result['method']['100%']++; Chris@14: } else { Chris@14: $key = \floor($method['coverage'] / 10) * 10; Chris@14: $key = $key . '-' . ($key + 10) . '%'; Chris@14: $result['method'][$key]++; Chris@14: } Chris@14: } Chris@14: Chris@14: if ($class['coverage'] === 0) { Chris@14: $result['class']['0%']++; Chris@14: } elseif ($class['coverage'] === 100) { Chris@14: $result['class']['100%']++; Chris@14: } else { Chris@14: $key = \floor($class['coverage'] / 10) * 10; Chris@14: $key = $key . '-' . ($key + 10) . '%'; Chris@14: $result['class'][$key]++; Chris@14: } Chris@14: } Chris@14: Chris@14: return [ Chris@14: 'class' => \json_encode(\array_values($result['class'])), Chris@14: 'method' => \json_encode(\array_values($result['method'])) Chris@14: ]; Chris@14: } Chris@14: Chris@14: /** Chris@14: * Returns the classes / methods with insufficient coverage. Chris@14: * Chris@14: * @param array $classes Chris@14: * @param string $baseLink Chris@14: * Chris@14: * @return array Chris@14: */ Chris@14: protected function insufficientCoverage(array $classes, $baseLink) Chris@14: { Chris@14: $leastTestedClasses = []; Chris@14: $leastTestedMethods = []; Chris@14: $result = ['class' => '', 'method' => '']; Chris@14: Chris@14: foreach ($classes as $className => $class) { Chris@14: foreach ($class['methods'] as $methodName => $method) { Chris@14: if ($method['coverage'] < $this->highLowerBound) { Chris@14: $key = $methodName; Chris@14: Chris@14: if ($className !== '*') { Chris@14: $key = $className . '::' . $methodName; Chris@14: } Chris@14: Chris@14: $leastTestedMethods[$key] = $method['coverage']; Chris@14: } Chris@14: } Chris@14: Chris@14: if ($class['coverage'] < $this->highLowerBound) { Chris@14: $leastTestedClasses[$className] = $class['coverage']; Chris@14: } Chris@14: } Chris@14: Chris@14: \asort($leastTestedClasses); Chris@14: \asort($leastTestedMethods); Chris@14: Chris@14: foreach ($leastTestedClasses as $className => $coverage) { Chris@14: $result['class'] .= \sprintf( Chris@14: '