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
4 changes: 3 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export default tseslint.config(
String.raw`^.*/eslint(\.base)?\.config\.[cm]?js$`,
String.raw`^.*/code-pushup\.(config|preset)(\.m?[jt]s)?$`,
'^[./]+/tools/.*$',
String.raw`^[./]+/(testing/)?test-setup-config/src/index\.js$`,
],
depConstraints: [
{
Expand Down Expand Up @@ -115,6 +116,7 @@ export default tseslint.config(
files: ['**/*.ts', '**/*.js'],
rules: {
'n/file-extension-in-import': ['error', 'always'],
'unicorn/number-literal-case': 'off',
},
},
{
Expand All @@ -127,7 +129,7 @@ export default tseslint.config(
{
// tests need only be compatible with local Node version
// publishable packages should pick up version range from "engines" in their package.json
files: ['e2e/**/*.ts', 'testing/**/*.ts'],
files: ['e2e/**/*.ts', 'testing/**/*.ts', '**/*.test.ts'],
settings: {
node: {
version: fs.readFileSync('.node-version', 'utf8'),
Expand Down
2 changes: 1 addition & 1 deletion nx.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
"options": {
"command": "eslint",
"args": [
"{projectRoot}/**/*.ts",
"'{projectRoot}/**/*.ts'",
"{projectRoot}/package.json",
"--config={projectRoot}/eslint.config.js",
"--max-warnings=0",
Expand Down
15 changes: 1 addition & 14 deletions packages/ci/vitest.int.config.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,3 @@
import { createIntTestConfig } from '../../testing/test-setup-config/src/index.js';

let config = createIntTestConfig('ci');

config = {
...config,
test: {
...config.test,
setupFiles: [
...(config.test!.setupFiles || []),
'../../testing/test-setup/src/lib/logger.mock.ts',
],
},
};

export default config;
export default createIntTestConfig('ci');
15 changes: 1 addition & 14 deletions packages/ci/vitest.unit.config.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,3 @@
import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js';

let config = createUnitTestConfig('ci');

config = {
...config,
test: {
...config.test,
setupFiles: [
...(config.test!.setupFiles || []),
'../../testing/test-setup/src/lib/logger.mock.ts',
],
},
};

export default config;
export default createUnitTestConfig('ci');
15 changes: 1 addition & 14 deletions packages/core/src/lib/implementation/execute-plugin.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe('executePlugin', () => {
ReturnType<(typeof runnerModule)['executePluginRunner']>
>;

beforeAll(() => {
beforeEach(() => {
readRunnerResultsSpy = vi.spyOn(runnerModule, 'readRunnerResults');
executePluginRunnerSpy = vi.spyOn(runnerModule, 'executePluginRunner');
});
Expand All @@ -35,11 +35,6 @@ describe('executePlugin', () => {
});

it('should execute a valid plugin config and pass runner params', async () => {
const executePluginRunnerSpy = vi.spyOn(
runnerModule,
'executePluginRunner',
);

await expect(
executePlugin(MINIMAL_PLUGIN_CONFIG_MOCK, {
persist: {},
Expand Down Expand Up @@ -68,8 +63,6 @@ describe('executePlugin', () => {
});

it('should try to read cache if cache.read is true', async () => {
const readRunnerResultsSpy = vi.spyOn(runnerModule, 'readRunnerResults');

const validRunnerResult = {
duration: 0, // readRunnerResults now automatically sets this to 0 for cache hits
date: new Date().toISOString(), // readRunnerResults sets this to current time
Expand Down Expand Up @@ -106,12 +99,6 @@ describe('executePlugin', () => {
});

it('should try to execute runner if cache.read is true and file not present', async () => {
const readRunnerResultsSpy = vi.spyOn(runnerModule, 'readRunnerResults');
const executePluginRunnerSpy = vi.spyOn(
runnerModule,
'executePluginRunner',
);

readRunnerResultsSpy.mockResolvedValue(null);
const runnerResult = {
duration: 1000,
Expand Down
7 changes: 2 additions & 5 deletions packages/core/src/lib/implementation/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,15 @@ export async function executeRunnerConfig(
await removeDirectoryIfExists(path.dirname(outputFile));

// transform unknownAuditOutputs to auditOutputs
const audits = outputTransform ? await outputTransform(outputs) : outputs;

return audits;
return outputTransform ? await outputTransform(outputs) : outputs;
}

export async function executeRunnerFunction(
runner: RunnerFunction,
args: RunnerArgs,
): Promise<unknown> {
// execute plugin runner
const audits = await runner(args);
return audits;
return runner(args);
}

/**
Expand Down
1 change: 1 addition & 0 deletions packages/models/src/lib/implementation/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type SchemaValidationContext = {
*/
type ZodInputLooseAutocomplete<T extends ZodType> =
| z.input<T>
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
| {}
| null
| undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe('validate', () => {
afterEach(async () => {
// Allow any lingering async operations from transforms to complete
// This prevents unhandled rejections in subsequent tests
await new Promise(resolve => setImmediate(resolve));
await new Promise(setImmediate);
});

it('should return parsed data if valid', () => {
Expand Down
15 changes: 8 additions & 7 deletions packages/models/src/lib/plugin-config.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe, expect, it } from 'vitest';
import { ZodError } from 'zod';
import {
type PluginConfig,
pluginConfigSchema,
Expand Down Expand Up @@ -164,28 +165,28 @@ describe('pluginUrlsSchema', () => {
});

it('should throw for invalid URL', () => {
expect(() => pluginUrlsSchema.parse('invalid')).toThrow();
expect(() => pluginUrlsSchema.parse('invalid')).toThrow(ZodError);
});

it('should throw for array with invalid URL', () => {
expect(() =>
pluginUrlsSchema.parse(['https://example.com', 'invalid']),
).toThrow();
).toThrow(ZodError);
});

it('should throw for object with invalid URL', () => {
expect(() => pluginUrlsSchema.parse({ invalid: 1 })).toThrow();
expect(() => pluginUrlsSchema.parse({ invalid: 1 })).toThrow(ZodError);
});

it('should throw for invalid negative weight', () => {
expect(() =>
pluginUrlsSchema.parse({ 'https://example.com': -1 }),
).toThrow();
expect(() => pluginUrlsSchema.parse({ 'https://example.com': -1 })).toThrow(
ZodError,
);
});

it('should throw for invalid string weight', () => {
expect(() =>
pluginUrlsSchema.parse({ 'https://example.com': '1' }),
).toThrow();
).toThrow(ZodError);
});
});
6 changes: 4 additions & 2 deletions packages/nx-plugin/src/executors/cli/executor.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ describe('runAutorunExecutor', () => {
beforeAll(() => {
Object.entries(process.env)
.filter(([k]) => k.startsWith('CP_'))
.forEach(([k]) => delete process.env[k]);
.forEach(([k]) => Reflect.deleteProperty(process.env, k));
});

afterAll(() => {
Object.entries(processEnvCP).forEach(([k, v]) => (process.env[k] = v));
Object.entries(processEnvCP).forEach(([k, v]) =>
Reflect.set(process.env, k, v),
);
});

beforeEach(() => {
Expand Down
2 changes: 0 additions & 2 deletions packages/nx-plugin/src/executors/internal/cli.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { logger } from '@nx/devkit';

export function createCliCommandString(options?: {
args?: Record<string, unknown>;
command?: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import * as path from 'node:path';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { DEFAULT_TARGET_NAME, PACKAGE_NAME } from '../../internal/constants.js';
import { configurationGenerator } from './generator.js';

describe('configurationGenerator', () => {
Expand Down
6 changes: 4 additions & 2 deletions packages/nx-plugin/src/internal/execute-process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
export async function executeProcess(
cfg: import('@code-pushup/utils').ProcessConfig,
): Promise<import('@code-pushup/utils').ProcessResult> {
const { executeProcess } = await import('@code-pushup/utils');
return executeProcess(cfg);
const { executeProcess: executeProcessFromUtils } = await import(
'@code-pushup/utils'
);
return executeProcessFromUtils(cfg);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ export function createConfigurationTarget(options?: {
const args = objectToCliArgs({
...(projectName ? { project: projectName } : {}),
});
const argsString = args.length > 0 ? args.join(' ') : '';
const baseCommand = `nx g ${bin}:configuration`;
return {
command: `nx g ${bin}:configuration${args.length > 0 ? ` ${args.join(' ')}` : ''}`,
command: argsString ? `${baseCommand} ${argsString}` : baseCommand,
};
}
4 changes: 3 additions & 1 deletion packages/plugin-axe/src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ import {
} from '@code-pushup/models';
import { AXE_DEFAULT_PRESET, AXE_PRESETS } from './constants.js';

const DEFAULT_TIMEOUT_MS = 30_000;

export const axePluginOptionsSchema = z
.object({
preset: z.enum(AXE_PRESETS).default(AXE_DEFAULT_PRESET).meta({
description:
'Accessibility ruleset preset (default: wcag21aa for WCAG 2.1 Level AA compliance)',
}),
scoreTargets: pluginScoreTargetsSchema.optional(),
timeout: positiveIntSchema.default(30_000).meta({
timeout: positiveIntSchema.default(DEFAULT_TIMEOUT_MS).meta({
description:
'Page navigation timeout in milliseconds (default: 30000ms / 30s)',
}),
Expand Down
27 changes: 19 additions & 8 deletions packages/plugin-axe/src/lib/config.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe, expect, it } from 'vitest';
import { ZodError } from 'zod';
import { axePluginOptionsSchema } from './config.js';

describe('axePluginOptionsSchema', () => {
Expand Down Expand Up @@ -31,17 +32,21 @@ describe('axePluginOptionsSchema', () => {
});

it('should reject invalid preset values', () => {
expect(() => axePluginOptionsSchema.parse({ preset: 'wcag3aa' })).toThrow();
expect(() => axePluginOptionsSchema.parse({ preset: 'wcag3aa' })).toThrow(
ZodError,
);
});

it('should reject scoreTargets values greater than 1', () => {
expect(() => axePluginOptionsSchema.parse({ scoreTargets: 1.5 })).toThrow();
expect(() => axePluginOptionsSchema.parse({ scoreTargets: 1.5 })).toThrow(
ZodError,
);
});

it('should reject negative scoreTargets', () => {
expect(() =>
axePluginOptionsSchema.parse({ scoreTargets: -0.1 }),
).toThrow();
expect(() => axePluginOptionsSchema.parse({ scoreTargets: -0.1 })).toThrow(
ZodError,
);
});

it('should accept custom timeout value', () => {
Expand All @@ -51,11 +56,17 @@ describe('axePluginOptionsSchema', () => {
});

it('should reject non-positive timeout values', () => {
expect(() => axePluginOptionsSchema.parse({ timeout: 0 })).toThrow();
expect(() => axePluginOptionsSchema.parse({ timeout: -1000 })).toThrow();
expect(() => axePluginOptionsSchema.parse({ timeout: 0 })).toThrow(
ZodError,
);
expect(() => axePluginOptionsSchema.parse({ timeout: -1000 })).toThrow(
ZodError,
);
});

it('should reject non-integer timeout values', () => {
expect(() => axePluginOptionsSchema.parse({ timeout: 1000.5 })).toThrow();
expect(() => axePluginOptionsSchema.parse({ timeout: 1000.5 })).toThrow(
ZodError,
);
});
});
2 changes: 1 addition & 1 deletion packages/plugin-axe/src/lib/meta/groups.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ describe('transformRulesToGroups', () => {
'best-practice',
);

expect(groups).toSatisfyAll<Group>(({ slug }) => !slug.match(/^cat\./));
expect(groups).toSatisfyAll<Group>(({ slug }) => !/^cat\./.test(slug));
});

it('should include both WCAG 2.2 and category groups for "all" preset', () => {
Expand Down
3 changes: 2 additions & 1 deletion packages/plugin-axe/src/lib/meta/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export function transformRulesToGroups(
return createWcagGroups(rules, '2.1');
case 'wcag22aa':
return createWcagGroups(rules, '2.2');
// eslint-disable-next-line sonarjs/no-duplicate-string
case 'best-practice':
return createCategoryGroups(rules);
case 'all':
Expand Down Expand Up @@ -128,7 +129,7 @@ function createCategoryGroups(rules: axe.RuleMetadata[]): Group[] {
rules.flatMap(({ tags }) => tags.filter(tag => tag.startsWith('cat.'))),
);

return Array.from(categoryTags).map(tag => {
return [...categoryTags].map(tag => {
const slug = tag.replace('cat.', '');
const title = formatCategoryTitle(tag, slug);
const categoryRules = rules.filter(({ tags }) => tags.includes(tag));
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-axe/src/lib/processing.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
import { processAuditsAndGroups } from './processing';
import { processAuditsAndGroups } from './processing.js';

describe('processAuditsAndGroups', () => {
it('should return audits and groups without expansion when analyzing single URL', () => {
Expand Down
Loading