Skip to content

Commit 3cb8fa4

Browse files
authored
feat(plugin-lighthouse): add only filters logic for categories (#515)
1 parent b38620e commit 3cb8fa4

12 files changed

+441
-209
lines changed

examples/plugins/src/lighthouse/src/lighthouse.plugin.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ import {
88
} from '@code-pushup/models';
99
import {
1010
ensureDirectoryExists,
11-
filterAuditsBySlug,
12-
filterGroupsByAuditSlug,
11+
filterItemRefsBy,
1312
toArray,
1413
} from '@code-pushup/utils';
1514
import {
@@ -74,8 +73,10 @@ export async function create(options: PluginOptions) {
7473
onlyCategories: ['performance'],
7574
headless,
7675
}),
77-
audits: filterAuditsBySlug(audits, onlyAudits),
78-
groups: filterGroupsByAuditSlug([categoryCorePerfGroup], onlyAudits),
76+
audits: audits.filter(({ slug }) => onlyAudits.includes(slug)),
77+
groups: filterItemRefsBy([categoryCorePerfGroup], ({ slug }) =>
78+
onlyAudits.includes(slug),
79+
),
7980
} satisfies PluginConfig;
8081
}
8182

Original file line numberDiff line numberDiff line change
@@ -1,36 +1,12 @@
11
import { expect } from 'vitest';
2-
import {
3-
auditSchema,
4-
groupSchema,
5-
pluginConfigSchema,
6-
} from '@code-pushup/models';
7-
import { AUDITS, GROUPS } from './constants';
2+
import { pluginConfigSchema } from '@code-pushup/models';
83
import { lighthousePlugin } from './lighthouse-plugin';
94

105
describe('lighthousePlugin', () => {
116
it('should create valid plugin config', () => {
12-
const pluginConfig = lighthousePlugin({
13-
url: 'https://code-pushup-portal.com',
14-
});
7+
const pluginConfig = lighthousePlugin('https://code-pushup-portal.com');
158
expect(() => pluginConfigSchema.parse(pluginConfig)).not.toThrow();
169
expect(pluginConfig.audits).toHaveLength(168);
1710
expect(pluginConfig.groups).toHaveLength(5);
1811
});
1912
});
20-
21-
describe('generated-constants', () => {
22-
it.each(AUDITS.map(a => [a.slug, a]))(
23-
'should parse audit "%s" correctly',
24-
(_, audit) => {
25-
expect(() => auditSchema.parse(audit)).not.toThrow();
26-
expect(audit.description).toEqual(expect.any(String));
27-
},
28-
);
29-
30-
it.each(GROUPS.map(a => [a.slug, a]))(
31-
'should parse group "%s" correctly',
32-
(_, group) => {
33-
expect(() => groupSchema.parse(group)).not.toThrow();
34-
},
35-
);
36-
});
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,28 @@
1-
import { Audit, AuditOutputs, Group, PluginConfig } from '@code-pushup/models';
21
import {
3-
filterAuditsBySlug,
4-
filterGroupsByAuditSlug,
5-
} from '@code-pushup/utils';
2+
type Config as LighthouseConfig,
3+
type CliFlags as LighthouseFlags,
4+
} from 'lighthouse';
5+
import { PluginConfig } from '@code-pushup/models';
66
import { AUDITS, GROUPS, LIGHTHOUSE_PLUGIN_SLUG } from './constants';
7-
import { validateOnlyAudits } from './utils';
8-
9-
export type LighthousePluginOptions = {
10-
url: string;
11-
outputPath?: string;
12-
onlyAudits?: string | string[];
13-
verbose?: boolean;
14-
headless?: boolean;
15-
userDataDir?: string;
16-
};
7+
import { filterAuditsAndGroupsByOnlyOptions } from './utils';
178

189
export function lighthousePlugin(
19-
options: LighthousePluginOptions,
10+
url: string,
11+
flags?: Partial<LighthouseFlags>,
12+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
13+
config?: Partial<LighthouseConfig>,
2014
): PluginConfig {
21-
const { onlyAudits = [] } = options;
22-
23-
validateOnlyAudits(AUDITS, onlyAudits);
24-
const audits: Audit[] = filterAuditsBySlug(AUDITS, onlyAudits);
25-
const groups: Group[] = filterGroupsByAuditSlug(GROUPS, onlyAudits);
26-
15+
const { audits, groups } = filterAuditsAndGroupsByOnlyOptions(
16+
AUDITS,
17+
GROUPS,
18+
flags,
19+
);
2720
return {
2821
slug: LIGHTHOUSE_PLUGIN_SLUG,
2922
title: 'Lighthouse',
3023
icon: 'lighthouse',
3124
audits,
3225
groups,
33-
runner: (): AuditOutputs =>
34-
audits.map(audit => ({
35-
...audit,
36-
score: 0,
37-
value: 0,
38-
})),
26+
runner: () => audits.map(({ slug }) => ({ slug, value: 0, score: 0 })),
3927
};
4028
}

packages/plugin-lighthouse/src/lib/lighthouse-plugin.unit.test.ts

+5-9
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,15 @@ import { lighthousePlugin } from './lighthouse-plugin';
99

1010
describe('lighthousePlugin-config-object', () => {
1111
it('should create valid plugin config', () => {
12-
const pluginConfig = lighthousePlugin({
13-
url: 'https://code-pushup-portal.com',
14-
});
12+
const pluginConfig = lighthousePlugin('https://code-pushup-portal.com');
1513
expect(() => pluginConfigSchema.parse(pluginConfig)).not.toThrow();
1614
expect(pluginConfig.audits.length).toBeGreaterThan(0);
1715
expect(pluginConfig.groups?.length).toBeGreaterThan(0);
1816
});
1917

2018
it('should filter audits by onlyAudits string "first-contentful-paint"', () => {
21-
const pluginConfig = lighthousePlugin({
22-
url: 'https://code-pushup-portal.com',
23-
onlyAudits: 'first-contentful-paint',
19+
const pluginConfig = lighthousePlugin('https://code-pushup-portal.com', {
20+
onlyAudits: ['first-contentful-paint'],
2421
});
2522

2623
expect(() => pluginConfigSchema.parse(pluginConfig)).not.toThrow();
@@ -33,9 +30,8 @@ describe('lighthousePlugin-config-object', () => {
3330
});
3431

3532
it('should filter groups by onlyAudits string "first-contentful-paint"', () => {
36-
const pluginConfig = lighthousePlugin({
37-
url: 'https://code-pushup-portal.com',
38-
onlyAudits: 'first-contentful-paint',
33+
const pluginConfig = lighthousePlugin('https://code-pushup-portal.com', {
34+
onlyAudits: ['first-contentful-paint'],
3935
});
4036

4137
expect(() => pluginConfigSchema.parse(pluginConfig)).not.toThrow();

packages/plugin-lighthouse/src/lib/utils.ts

+68-7
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import type { CliFlags } from 'lighthouse';
2-
import { Audit } from '@code-pushup/models';
3-
import { objectToCliArgs, toArray } from '@code-pushup/utils';
1+
import type { CliFlags as LighthouseFlags } from 'lighthouse';
2+
import { Audit, Group } from '@code-pushup/models';
3+
import { filterItemRefsBy, objectToCliArgs, toArray } from '@code-pushup/utils';
44
import { LIGHTHOUSE_REPORT_NAME } from './constants';
55

66
type RefinedLighthouseOption = {
7-
url: CliFlags['_'];
8-
chromeFlags?: Record<CliFlags['chromeFlags'][number], string>;
7+
url: LighthouseFlags['_'];
8+
chromeFlags?: Record<LighthouseFlags['chromeFlags'][number], string>;
99
};
1010
export type LighthouseCliOptions = RefinedLighthouseOption &
11-
Partial<Omit<CliFlags, keyof RefinedLighthouseOption>>;
11+
Partial<Omit<LighthouseFlags, keyof RefinedLighthouseOption>>;
1212

1313
export function getLighthouseCliArguments(
1414
options: LighthouseCliOptions,
@@ -59,7 +59,7 @@ export class AuditsNotImplementedError extends Error {
5959
export function validateOnlyAudits(
6060
audits: Audit[],
6161
onlyAudits: string | string[],
62-
): audits is Audit[] {
62+
): boolean {
6363
const missingAudtis = toArray(onlyAudits).filter(
6464
slug => !audits.some(audit => audit.slug === slug),
6565
);
@@ -68,3 +68,64 @@ export function validateOnlyAudits(
6868
}
6969
return true;
7070
}
71+
72+
export class CategoriesNotImplementedError extends Error {
73+
constructor(categorySlugs: string[]) {
74+
super(`categories: "${categorySlugs.join(', ')}" not implemented`);
75+
}
76+
}
77+
78+
export function validateOnlyCategories(
79+
groups: Group[],
80+
onlyCategories: string | string[],
81+
): boolean {
82+
const missingCategories = toArray(onlyCategories).filter(slug =>
83+
groups.every(group => group.slug !== slug),
84+
);
85+
if (missingCategories.length > 0) {
86+
throw new CategoriesNotImplementedError(missingCategories);
87+
}
88+
return true;
89+
}
90+
91+
export function filterAuditsAndGroupsByOnlyOptions(
92+
audits: Audit[],
93+
groups: Group[],
94+
options?: Pick<LighthouseFlags, 'onlyAudits' | 'onlyCategories'>,
95+
): {
96+
audits: Audit[];
97+
groups: Group[];
98+
} {
99+
const { onlyAudits, onlyCategories } = options ?? {};
100+
101+
// category wins over audits
102+
if (onlyCategories && onlyCategories.length > 0) {
103+
validateOnlyCategories(groups, onlyCategories);
104+
105+
const categorieSlugs = new Set(onlyCategories);
106+
const filteredGroups: Group[] = groups.filter(({ slug }) =>
107+
categorieSlugs.has(slug),
108+
);
109+
const auditSlugsFromRemainingGroups = new Set(
110+
filteredGroups.flatMap(({ refs }) => refs.map(({ slug }) => slug)),
111+
);
112+
return {
113+
audits: audits.filter(({ slug }) =>
114+
auditSlugsFromRemainingGroups.has(slug),
115+
),
116+
groups: filteredGroups,
117+
};
118+
} else if (onlyAudits && onlyAudits.length > 0) {
119+
validateOnlyAudits(audits, onlyAudits);
120+
const auditSlugs = new Set(onlyAudits);
121+
return {
122+
audits: audits.filter(({ slug }) => auditSlugs.has(slug)),
123+
groups: filterItemRefsBy(groups, ({ slug }) => auditSlugs.has(slug)),
124+
};
125+
}
126+
// return unchanged
127+
return {
128+
audits,
129+
groups,
130+
};
131+
}

0 commit comments

Comments
 (0)