Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ exports[`categoriesDetailsSection > should render complete categories details 1`
"
`;

exports[`categoriesDetailsSection > should render filtered categories details > filtered 1`] = `
"## 🏷 Categories

### Bug Prevention

🟢 Score: **100** ✅

- 🟩 [No let](#no-let-eslint) (_Eslint_) - **0**
"
`;

exports[`categoriesOverviewSection > should render complete categories table 1`] = `
"| 🏷 Category | ⭐ Score | 🛡 Audits |
| :-------------------------------- | :-------: | :-------: |
Expand All @@ -54,6 +65,13 @@ exports[`categoriesOverviewSection > should render complete categories table 1`]
"
`;

exports[`categoriesOverviewSection > should render filtered categories table 1`] = `
"| 🏷 Category | ⭐ Score | 🛡 Audits |
| :-------------------------------- | :--------: | :-------: |
| [Bug Prevention](#bug-prevention) | 🟢 **100** | 1 |
"
`;

exports[`categoriesOverviewSection > should render targetScore icon "❌" if score fails 1`] = `
"| 🏷 Category | ⭐ Score | 🛡 Audits |
| :-------------------------------- | :---------: | :-------: |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@ import { slugify } from '../formatting.js';
import { HIERARCHY } from '../text-formats/index.js';
import { metaDescription } from './formatting.js';
import { getSortableAuditByRef, getSortableGroupByRef } from './sorting.js';
import type { ScoredGroup, ScoredReport } from './types.js';
import type { ScoreFilter, ScoredGroup, ScoredReport } from './types.js';
import {
countCategoryAudits,
formatReportScore,
getPluginNameFromSlug,
scoreFilter,
scoreMarker,
targetScoreIcon,
} from './utils.js';

export function categoriesOverviewSection(
report: Required<Pick<ScoredReport, 'plugins' | 'categories'>>,
options?: ScoreFilter,
): MarkdownDocument {
const { categories, plugins } = report;
return new MarkdownDocument().table(
Expand All @@ -23,26 +25,29 @@ export function categoriesOverviewSection(
{ heading: '⭐ Score', alignment: 'center' },
{ heading: '🛡 Audits', alignment: 'center' },
],
categories.map(({ title, refs, score, isBinary }) => [
// @TODO refactor `isBinary: boolean` to `targetScore: number` #713
// The heading "ID" is inferred from the heading text in Markdown.
md.link(`#${slugify(title)}`, title),
md`${scoreMarker(score)} ${md.bold(
formatReportScore(score),
)}${binaryIconSuffix(score, isBinary)}`,
countCategoryAudits(refs, plugins).toString(),
]),
categories
.filter(scoreFilter(options))
.map(({ title, refs, score, isBinary }) => [
// @TODO refactor `isBinary: boolean` to `targetScore: number` #713
// The heading "ID" is inferred from the heading text in Markdown.
md.link(`#${slugify(title)}`, title),
md`${scoreMarker(score)} ${md.bold(
formatReportScore(score),
)}${binaryIconSuffix(score, isBinary)}`,
countCategoryAudits(refs, plugins).toString(),
]),
);
}

export function categoriesDetailsSection(
report: Required<Pick<ScoredReport, 'plugins' | 'categories'>>,
options?: ScoreFilter,
): MarkdownDocument {
const { categories, plugins } = report;

const isScoreDisplayed = scoreFilter(options);
return new MarkdownDocument()
.heading(HIERARCHY.level_2, '🏷 Categories')
.$foreach(categories, (doc, category) =>
.$foreach(categories.filter(isScoreDisplayed), (doc, category) =>
doc
.heading(HIERARCHY.level_3, category.title)
.paragraph(metaDescription(category))
Expand All @@ -63,13 +68,17 @@ export function categoriesDetailsSection(
),
);
const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
return categoryGroupItem(group, groupAudits, pluginTitle);
return isScoreDisplayed(group)
? categoryGroupItem(group, groupAudits, pluginTitle)
: '';
}
// Add audit details
else {
const audit = getSortableAuditByRef(ref, plugins);
const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
return categoryRef(audit, pluginTitle);
return isScoreDisplayed(audit)
? categoryRef(audit, pluginTitle)
: '';
}
}),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
categoriesOverviewSection,
categoryGroupItem,
categoryRef,
} from './generate-md-report-categoy-section.js';
} from './generate-md-report-category-section.js';
import type { ScoredGroup, ScoredReport } from './types.js';

// === Categories Overview Section
Expand Down Expand Up @@ -49,6 +49,48 @@ describe('categoriesOverviewSection', () => {
).toMatchSnapshot();
});

it('should render filtered categories table', () => {
expect(
categoriesOverviewSection(
{
plugins: [
{
slug: 'eslint',
title: 'Eslint',
},
{
slug: 'lighthouse',
title: 'Lighthouse',
},
],
categories: [
{
slug: 'bug-prevention',
title: 'Bug Prevention',
score: 1,
refs: [{ slug: 'no-let', type: 'audit' }],
},
{
slug: 'performance',
title: 'Performance',
score: 0.74,
refs: [{ slug: 'largest-contentful-paint', type: 'audit' }],
},
{
slug: 'typescript',
title: 'Typescript',
score: 0.14,
refs: [{ slug: 'no-any', type: 'audit' }],
},
],
} as Required<Pick<ScoredReport, 'plugins' | 'categories'>>,
{
isScoreListed: score => score === 1,
},
).toString(),
).toMatchSnapshot();
});

it('should render targetScore icon "❌" if score fails', () => {
expect(
categoriesOverviewSection({
Expand Down Expand Up @@ -215,6 +257,68 @@ describe('categoriesDetailsSection', () => {
).toMatchSnapshot();
});

it('should render filtered categories details', () => {
expect(
categoriesDetailsSection(
{
plugins: [
{
slug: 'eslint',
title: 'Eslint',
audits: [
{ slug: 'no-let', title: 'No let', score: 1, value: 0 },
{ slug: 'no-any', title: 'No any', score: 0, value: 5 },
],
},
{
slug: 'lighthouse',
title: 'Lighthouse',
audits: [
{
slug: 'largest-contentful-paint',
title: 'Largest Contentful Paint',
score: 0.7,
value: 2905,
},
],
},
],
categories: [
{
slug: 'bug-prevention',
title: 'Bug Prevention',
score: 1,
isBinary: true,
refs: [{ slug: 'no-let', type: 'audit', plugin: 'eslint' }],
},
{
slug: 'performance',
title: 'Performance',
score: 0.74,
refs: [
{
slug: 'largest-contentful-paint',
type: 'audit',
plugin: 'lighthouse',
},
],
},
{
slug: 'typescript',
title: 'Typescript',
score: 0.14,
isBinary: true,
refs: [{ slug: 'no-any', type: 'audit', plugin: 'eslint' }],
},
],
} as Required<Pick<ScoredReport, 'plugins' | 'categories'>>,
{
isScoreListed: score => score === 1,
},
).toString(),
).toMatchSnapshot('filtered');
});

it('should render categories details and add "❌" when isBinary is failing', () => {
expect(
categoriesDetailsSection({
Expand Down
23 changes: 19 additions & 4 deletions packages/utils/src/lib/reports/generate-md-report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@ import {
import {
categoriesDetailsSection,
categoriesOverviewSection,
} from './generate-md-report-categoy-section.js';
} from './generate-md-report-category-section.js';
import type { MdReportOptions, ScoredReport } from './types.js';
import { formatReportScore, scoreMarker, severityMarker } from './utils.js';
import {
formatReportScore,
scoreFilter,
scoreMarker,
severityMarker,
} from './utils.js';

export function auditDetailsAuditValue({
score,
Expand All @@ -30,6 +35,10 @@ export function auditDetailsAuditValue({
)} (score: ${formatReportScore(score)})`;
}

/**
* Check if the report has categories.
* @param report
*/
function hasCategories(
report: ScoredReport,
): report is ScoredReport & Required<Pick<ScoredReport, 'categories'>> {
Expand All @@ -44,7 +53,10 @@ export function generateMdReport(
.heading(HIERARCHY.level_1, REPORT_HEADLINE_TEXT)
.$concat(
...(hasCategories(report)
? [categoriesOverviewSection(report), categoriesDetailsSection(report)]
? [
categoriesOverviewSection(report, options),
categoriesDetailsSection(report, options),
]
: []),
auditsSection(report, options),
aboutSection(report),
Expand Down Expand Up @@ -110,11 +122,14 @@ export function auditsSection(
{ plugins }: Pick<ScoredReport, 'plugins'>,
options?: MdReportOptions,
): MarkdownDocument {
const isScoreDisplayed = scoreFilter(options);
return new MarkdownDocument()
.heading(HIERARCHY.level_2, '🛡️ Audits')
.$foreach(
plugins.flatMap(plugin =>
plugin.audits.map(audit => ({ ...audit, plugin })),
plugin.audits
.filter(isScoreDisplayed)
.map(audit => ({ ...audit, plugin })),
),
(doc, { plugin, ...audit }) => {
const auditTitle = `${audit.title} (${plugin.title})`;
Expand Down
76 changes: 76 additions & 0 deletions packages/utils/src/lib/reports/generate-md-report.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,54 @@ const baseScoredReport = {
],
} as ScoredReport;

const baseScoredReport2 = {
date: '2025.01.01',
duration: 4200,
version: 'v1.0.0',
commit: {
message: 'ci: update action',
author: 'Michael <michael.hladky@push-based.io>',
date: new Date('2025.01.01'),
hash: '535b8e9e557336618a764f3fa45609d224a62837',
},
plugins: [
{
slug: 'lighthouse',
version: '1.0.1',
duration: 15_365,
title: 'Lighthouse',
audits: [
{
slug: 'largest-contentful-paint',
title: 'Largest Contentful Paint',
score: 0.6,
value: 2700,
},
{
slug: 'cumulative-layout-shift',
title: 'Cumulative Layout Shift',
score: 1,
value: 0,
},
],
},
],
categories: [
{
title: 'Speed',
slug: 'speed',
score: 0.93,
refs: [{ slug: 'largest-contentful-paint', plugin: 'lighthouse' }],
},
{
title: 'Visual Stability',
slug: 'visual-stability',
score: 1,
refs: [{ slug: 'cumulative-layout-shift', plugin: 'lighthouse' }],
},
],
} as ScoredReport;

// === Audit Details

describe('auditDetailsAuditValue', () => {
Expand Down Expand Up @@ -359,6 +407,22 @@ describe('auditsSection', () => {
).toMatch('🟩 **0** (score: 100)');
});

it('should render filtered result', () => {
const auditSection = auditsSection(
{
plugins: [
{ audits: [{ score: 1, value: 0 }] },
{ audits: [{ score: 0, value: 1 }] },
],
} as ScoredReport,
{
isScoreListed: (score: number) => score === 1,
},
).toString();
expect(auditSection).toMatch('(score: 100)');
expect(auditSection).not.toMatch('(score: 0)');
});

it('should render audit details', () => {
const md = auditsSection({
plugins: [
Expand Down Expand Up @@ -580,6 +644,18 @@ describe('generateMdReport', () => {
expect(md).toMatch('Made with ❤ by [Code PushUp]');
});

it('should render sections filtered by isScoreListed of the report', () => {
const md = generateMdReport(baseScoredReport2, {
isScoreListed: (score: number) => score === 1,
});

expect(md).toMatch('Visual Stability');
expect(md).toMatch('Cumulative Layout Shift');

expect(md).not.toMatch('Speed');
expect(md).not.toMatch('Largest Contentful Paint');
});

it('should skip categories section when categories are missing', () => {
const md = generateMdReport({ ...baseScoredReport, categories: undefined });
expect(md).not.toMatch('## 🏷 Categories');
Expand Down
Loading
Loading