1+ import type { CategoryConfig } from '@code-pushup/models' ;
12import {
23 computeRelativePresetImport ,
34 generateConfigSource ,
@@ -16,6 +17,24 @@ const ESLINT_PLUGIN: PluginCodegenResult = {
1617 pluginInit : "await eslintPlugin({ patterns: '.' })" ,
1718} ;
1819
20+ const ESLINT_CATEGORIES : CategoryConfig [ ] = [
21+ {
22+ slug : 'bug-prevention' ,
23+ title : 'Bug prevention' ,
24+ refs : [ { type : 'group' , plugin : 'eslint' , slug : 'problems' , weight : 1 } ] ,
25+ } ,
26+ {
27+ slug : 'code-style' ,
28+ title : 'Code style' ,
29+ refs : [ { type : 'group' , plugin : 'eslint' , slug : 'suggestions' , weight : 1 } ] ,
30+ } ,
31+ ] ;
32+
33+ const ESLINT_PLUGIN_WITH_CATEGORIES : PluginCodegenResult = {
34+ ...ESLINT_PLUGIN ,
35+ categories : ESLINT_CATEGORIES ,
36+ } ;
37+
1938describe ( 'generateConfigSource' , ( ) => {
2039 describe ( 'TypeScript format' , ( ) => {
2140 it ( 'should generate config with TODO placeholder when no plugins provided' , ( ) => {
@@ -201,6 +220,155 @@ describe('generateConfigSource', () => {
201220 ) ;
202221 } ) ;
203222 } ) ;
223+
224+ describe ( 'categories' , ( ) => {
225+ it ( 'should include categories block when plugin provides categories' , ( ) => {
226+ expect ( generateConfigSource ( [ ESLINT_PLUGIN_WITH_CATEGORIES ] , 'ts' ) )
227+ . toMatchInlineSnapshot ( `
228+ "import eslintPlugin from '@code-pushup/eslint-plugin';
229+ import type { CoreConfig } from '@code-pushup/models';
230+
231+ export default {
232+ plugins: [
233+ await eslintPlugin({ patterns: '.' }),
234+ ],
235+ categories: [
236+ {
237+ slug: 'bug-prevention',
238+ title: 'Bug prevention',
239+ refs: [
240+ { type: 'group', plugin: 'eslint', slug: 'problems', weight: 1 },
241+ ],
242+ },
243+ {
244+ slug: 'code-style',
245+ title: 'Code style',
246+ refs: [
247+ { type: 'group', plugin: 'eslint', slug: 'suggestions', weight: 1 },
248+ ],
249+ },
250+ ],
251+ } satisfies CoreConfig;
252+ "
253+ ` ) ;
254+ } ) ;
255+
256+ it ( 'should omit categories block when no categories provided' , ( ) => {
257+ const source = generateConfigSource ( [ ESLINT_PLUGIN ] , 'ts' ) ;
258+ expect ( source ) . not . toContain ( 'categories' ) ;
259+ } ) ;
260+
261+ it ( 'should merge categories from multiple plugins' , ( ) => {
262+ const coveragePlugin : PluginCodegenResult = {
263+ imports : [
264+ {
265+ moduleSpecifier : '@code-pushup/coverage-plugin' ,
266+ defaultImport : 'coveragePlugin' ,
267+ } ,
268+ ] ,
269+ pluginInit : 'await coveragePlugin()' ,
270+ categories : [
271+ {
272+ slug : 'code-coverage' ,
273+ title : 'Code coverage' ,
274+ refs : [
275+ {
276+ type : 'group' ,
277+ plugin : 'coverage' ,
278+ slug : 'coverage' ,
279+ weight : 1 ,
280+ } ,
281+ ] ,
282+ } ,
283+ ] ,
284+ } ;
285+ const source = generateConfigSource (
286+ [ ESLINT_PLUGIN_WITH_CATEGORIES , coveragePlugin ] ,
287+ 'ts' ,
288+ ) ;
289+ expect ( source ) . toContain ( "slug: 'bug-prevention'" ) ;
290+ expect ( source ) . toContain ( "slug: 'code-style'" ) ;
291+ expect ( source ) . toContain ( "slug: 'code-coverage'" ) ;
292+ } ) ;
293+
294+ it ( 'should include categories in JS format config' , ( ) => {
295+ const source = generateConfigSource (
296+ [ ESLINT_PLUGIN_WITH_CATEGORIES ] ,
297+ 'js' ,
298+ ) ;
299+ expect ( source ) . toContain ( 'categories: [' ) ;
300+ expect ( source ) . toContain ( "slug: 'bug-prevention'" ) ;
301+ } ) ;
302+
303+ it . each ( [
304+ [ "Project's docs" , String . raw `title: 'Project\'s docs'` ] ,
305+ [ String . raw `C:\Users\test` , String . raw `title: 'C:\\Users\\test'` ] ,
306+ [ 'Line one\nLine two' , String . raw `title: 'Line one\nLine two'` ] ,
307+ ] ) ( 'should escape %j in category title' , ( title , expected ) => {
308+ const plugin : PluginCodegenResult = {
309+ ...ESLINT_PLUGIN ,
310+ categories : [
311+ {
312+ slug : 'test' ,
313+ title,
314+ refs : [ { type : 'audit' , plugin : 'p' , slug : 's' , weight : 1 } ] ,
315+ } ,
316+ ] ,
317+ } ;
318+ expect ( generateConfigSource ( [ plugin ] , 'ts' ) ) . toContain ( expected ) ;
319+ } ) ;
320+
321+ it ( 'should include description and docsUrl when provided' , ( ) => {
322+ const plugin : PluginCodegenResult = {
323+ ...ESLINT_PLUGIN ,
324+ categories : [
325+ {
326+ slug : 'perf' ,
327+ title : 'Performance' ,
328+ description : 'Measures runtime performance.' ,
329+ docsUrl : 'https://example.com/perf' ,
330+ refs : [ { type : 'audit' , plugin : 'perf' , slug : 'lcp' , weight : 1 } ] ,
331+ } ,
332+ ] ,
333+ } ;
334+ const source = generateConfigSource ( [ plugin ] , 'ts' ) ;
335+ expect ( source ) . toContain ( "description: 'Measures runtime performance.'" ) ;
336+ expect ( source ) . toContain ( "docsUrl: 'https://example.com/perf'" ) ;
337+ } ) ;
338+
339+ it ( 'should merge categories with same slug from different plugins' , ( ) => {
340+ const ref = ( plugin : string , slug : string ) => ( {
341+ type : 'group' as const ,
342+ plugin,
343+ slug,
344+ weight : 1 ,
345+ } ) ;
346+ const source = generateConfigSource (
347+ [
348+ {
349+ ...ESLINT_PLUGIN ,
350+ categories : [
351+ {
352+ slug : 'bugs' ,
353+ title : 'Bugs' ,
354+ refs : [ ref ( 'eslint' , 'problems' ) ] ,
355+ } ,
356+ ] ,
357+ } ,
358+ {
359+ ...ESLINT_PLUGIN ,
360+ categories : [
361+ { slug : 'bugs' , title : 'Bugs' , refs : [ ref ( 'ts' , 'errors' ) ] } ,
362+ ] ,
363+ } ,
364+ ] ,
365+ 'ts' ,
366+ ) ;
367+ expect ( source . match ( / s l u g : ' b u g s ' / g) ) . toHaveLength ( 1 ) ;
368+ expect ( source ) . toContain ( "plugin: 'eslint'" ) ;
369+ expect ( source ) . toContain ( "plugin: 'ts'" ) ;
370+ } ) ;
371+ } ) ;
204372} ) ;
205373
206374describe ( 'generatePresetSource' , ( ) => {
@@ -243,6 +411,43 @@ describe('generatePresetSource', () => {
243411 "
244412 ` ) ;
245413 } ) ;
414+
415+ it ( 'should include categories in TS preset source' , ( ) => {
416+ expect ( generatePresetSource ( [ ESLINT_PLUGIN_WITH_CATEGORIES ] , 'ts' ) )
417+ . toMatchInlineSnapshot ( `
418+ "import eslintPlugin from '@code-pushup/eslint-plugin';
419+ import type { CoreConfig } from '@code-pushup/models';
420+
421+ /**
422+ * Creates a Code PushUp config for a project.
423+ * @param project Project name
424+ */
425+ export async function createConfig(project: string): Promise<CoreConfig> {
426+ return {
427+ plugins: [
428+ await eslintPlugin({ patterns: '.' }),
429+ ],
430+ categories: [
431+ {
432+ slug: 'bug-prevention',
433+ title: 'Bug prevention',
434+ refs: [
435+ { type: 'group', plugin: 'eslint', slug: 'problems', weight: 1 },
436+ ],
437+ },
438+ {
439+ slug: 'code-style',
440+ title: 'Code style',
441+ refs: [
442+ { type: 'group', plugin: 'eslint', slug: 'suggestions', weight: 1 },
443+ ],
444+ },
445+ ],
446+ };
447+ }
448+ "
449+ ` ) ;
450+ } ) ;
246451} ) ;
247452
248453describe ( 'generateProjectSource' , ( ) => {
0 commit comments