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
188 changes: 112 additions & 76 deletions __tests__/catalog-validation.test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { describe, test, expect } from '@jest/globals';
import { softwareCatalog } from '../src/data/software-catalog.js';
import { configurations } from '../src/data/configurations.js';
import { describe, test, expect, beforeAll } from '@jest/globals';
import { loadTestSoftwareCatalog } from './test-helpers/load-software-catalog.js';
import { loadTestConfigurations } from './test-helpers/load-configurations.js';
import { categories, configCategories } from '../src/data/categories.js';

// Catalogs loaded asynchronously before tests run
let softwareCatalog;
let configurations;

// Extract valid category IDs from categories.js
const VALID_SOFTWARE_CATEGORIES = categories.map(cat => cat.id);
const VALID_CONFIG_CATEGORIES = configCategories.map(cat => cat.id);
Expand All @@ -13,6 +17,12 @@ const isKebabCase = (str) => /^[a-z0-9]+(-[a-z0-9]+)*$/.test(str);
const isHexColor = (str) => /^#[0-9A-Fa-f]{6}$/.test(str);
const hasWingetFormat = (str) => str && str.includes('.');

// Load catalogs before all tests
beforeAll(async () => {
softwareCatalog = await loadTestSoftwareCatalog();
configurations = await loadTestConfigurations();
});

describe('Software Catalog Validation', () => {
test('should have software items', () => {
expect(softwareCatalog).toBeDefined();
Expand All @@ -26,59 +36,71 @@ describe('Software Catalog Validation', () => {
expect(ids.length).toBe(uniqueIds.size);
});

test.each(softwareCatalog)('software "$name" should have valid schema', (item) => {
// Required fields
expect(item.id).toBeDefined();
expect(typeof item.id).toBe('string');
expect(item.id.length).toBeGreaterThan(0);
test('all software should have valid schema', () => {
softwareCatalog.forEach((item) => {
// Required fields
expect(item.id).toBeDefined();
expect(typeof item.id).toBe('string');
expect(item.id.length).toBeGreaterThan(0);

expect(item.name).toBeDefined();
expect(typeof item.name).toBe('string');
expect(item.name.length).toBeGreaterThan(0);
expect(item.name).toBeDefined();
expect(typeof item.name).toBe('string');
expect(item.name.length).toBeGreaterThan(0);

expect(item.description).toBeDefined();
expect(typeof item.description).toBe('string');
expect(item.description.length).toBeGreaterThan(0);
expect(item.description).toBeDefined();
expect(typeof item.description).toBe('string');
expect(item.description.length).toBeGreaterThan(0);

expect(item.category).toBeDefined();
expect(typeof item.category).toBe('string');
expect(item.category).toBeDefined();
expect(typeof item.category).toBe('string');

expect(item.wingetId).toBeDefined();
expect(typeof item.wingetId).toBe('string');
expect(item.wingetId.length).toBeGreaterThan(0);
expect(item.wingetId).toBeDefined();
expect(typeof item.wingetId).toBe('string');
expect(item.wingetId.length).toBeGreaterThan(0);

expect(item.icon).toBeDefined();
expect(typeof item.icon).toBe('string');
expect(item.icon.length).toBeGreaterThan(0);
expect(item.icon).toBeDefined();
expect(typeof item.icon).toBe('string');
expect(item.icon.length).toBeGreaterThan(0);

expect(item.popular).toBeDefined();
expect(typeof item.popular).toBe('boolean');
expect(item.popular).toBeDefined();
expect(typeof item.popular).toBe('boolean');

expect(item.requiresAdmin).toBeDefined();
expect(typeof item.requiresAdmin).toBe('boolean');
expect(item.requiresAdmin).toBeDefined();
expect(typeof item.requiresAdmin).toBe('boolean');

expect(item.license).toBeDefined();
expect(typeof item.license).toBe('string');
expect(item.license).toBeDefined();
expect(typeof item.license).toBe('string');
});
});

test.each(softwareCatalog)('software "$name" ID should be kebab-case', (item) => {
expect(isKebabCase(item.id)).toBe(true);
test('all software IDs should be kebab-case', () => {
softwareCatalog.forEach((item) => {
expect(isKebabCase(item.id)).toBe(true);
});
});

test.each(softwareCatalog)('software "$name" should have valid category (ENUM)', (item) => {
expect(VALID_SOFTWARE_CATEGORIES).toContain(item.category);
test('all software should have valid category (ENUM)', () => {
softwareCatalog.forEach((item) => {
expect(VALID_SOFTWARE_CATEGORIES).toContain(item.category);
});
});

test.each(softwareCatalog)('software "$name" should have valid license (ENUM)', (item) => {
expect(VALID_LICENSES).toContain(item.license);
test('all software should have valid license (ENUM)', () => {
softwareCatalog.forEach((item) => {
expect(VALID_LICENSES).toContain(item.license);
});
});

test.each(softwareCatalog)('software "$name" wingetId should have valid format', (item) => {
expect(hasWingetFormat(item.wingetId)).toBe(true);
test('all software wingetId should have valid format', () => {
softwareCatalog.forEach((item) => {
expect(hasWingetFormat(item.wingetId)).toBe(true);
});
});

test.each(softwareCatalog.filter(item => item.iconColor))('software "$name" iconColor should be valid hex', (item) => {
expect(isHexColor(item.iconColor)).toBe(true);
test('all software with iconColor should have valid hex', () => {
softwareCatalog.filter(item => item.iconColor).forEach((item) => {
expect(isHexColor(item.iconColor)).toBe(true);
});
});
});

Expand All @@ -95,63 +117,77 @@ describe('Configurations Validation', () => {
expect(ids.length).toBe(uniqueIds.size);
});

test.each(configurations)('configuration "$name" should have valid schema', (item) => {
// Required fields
expect(item.id).toBeDefined();
expect(typeof item.id).toBe('string');
expect(item.id.length).toBeGreaterThan(0);
test('all configurations should have valid schema', () => {
configurations.forEach((item) => {
// Required fields
expect(item.id).toBeDefined();
expect(typeof item.id).toBe('string');
expect(item.id.length).toBeGreaterThan(0);

expect(item.name).toBeDefined();
expect(typeof item.name).toBe('string');
expect(item.name.length).toBeGreaterThan(0);
expect(item.name).toBeDefined();
expect(typeof item.name).toBe('string');
expect(item.name.length).toBeGreaterThan(0);

expect(item.description).toBeDefined();
expect(typeof item.description).toBe('string');
expect(item.description.length).toBeGreaterThan(0);
expect(item.description).toBeDefined();
expect(typeof item.description).toBe('string');
expect(item.description.length).toBeGreaterThan(0);

expect(item.category).toBeDefined();
expect(typeof item.category).toBe('string');
expect(item.category).toBeDefined();
expect(typeof item.category).toBe('string');

expect(item.recommended).toBeDefined();
expect(typeof item.recommended).toBe('boolean');
expect(item.recommended).toBeDefined();
expect(typeof item.recommended).toBe('boolean');

expect(item.requiresRestart).toBeDefined();
expect(typeof item.requiresRestart).toBe('boolean');
expect(item.requiresRestart).toBeDefined();
expect(typeof item.requiresRestart).toBe('boolean');

expect(item.requiresAdmin).toBeDefined();
expect(typeof item.requiresAdmin).toBe('boolean');
expect(item.requiresAdmin).toBeDefined();
expect(typeof item.requiresAdmin).toBe('boolean');
});
});

test.each(configurations)('configuration "$name" ID should be kebab-case', (item) => {
expect(isKebabCase(item.id)).toBe(true);
test('all configuration IDs should be kebab-case', () => {
configurations.forEach((item) => {
expect(isKebabCase(item.id)).toBe(true);
});
});

test.each(configurations)('configuration "$name" should have valid category (ENUM)', (item) => {
expect(VALID_CONFIG_CATEGORIES).toContain(item.category);
test('all configurations should have valid category (ENUM)', () => {
configurations.forEach((item) => {
expect(VALID_CONFIG_CATEGORIES).toContain(item.category);
});
});

test.each(configurations)('configuration "$name" should have at least one bat array', (item) => {
const hasRegistryBat = Array.isArray(item.registryBat) && item.registryBat.length > 0;
const hasCommandBat = Array.isArray(item.commandBat) && item.commandBat.length > 0;
expect(hasRegistryBat || hasCommandBat).toBe(true);
test('all configurations should have at least one bat array', () => {
configurations.forEach((item) => {
const hasRegistryBat = Array.isArray(item.registryBat) && item.registryBat.length > 0;
const hasCommandBat = Array.isArray(item.commandBat) && item.commandBat.length > 0;
expect(hasRegistryBat || hasCommandBat).toBe(true);
});
});

test.each(configurations.filter(item => item.registryBat))('configuration "$name" registryBat should be array of strings', (item) => {
expect(Array.isArray(item.registryBat)).toBe(true);
item.registryBat.forEach(cmd => {
expect(typeof cmd).toBe('string');
test('all configurations with registryBat should have array of strings', () => {
configurations.filter(item => item.registryBat).forEach((item) => {
expect(Array.isArray(item.registryBat)).toBe(true);
item.registryBat.forEach(cmd => {
expect(typeof cmd).toBe('string');
});
});
});

test.each(configurations.filter(item => item.commandBat))('configuration "$name" commandBat should be array of strings', (item) => {
expect(Array.isArray(item.commandBat)).toBe(true);
item.commandBat.forEach(cmd => {
expect(typeof cmd).toBe('string');
test('all configurations with commandBat should have array of strings', () => {
configurations.filter(item => item.commandBat).forEach((item) => {
expect(Array.isArray(item.commandBat)).toBe(true);
item.commandBat.forEach(cmd => {
expect(typeof cmd).toBe('string');
});
});
});

test.each(configurations.filter(item => item.warning))('configuration "$name" warning should be a string', (item) => {
expect(typeof item.warning).toBe('string');
expect(item.warning.length).toBeGreaterThan(0);
test('all configurations with warning should have string warning', () => {
configurations.filter(item => item.warning).forEach((item) => {
expect(typeof item.warning).toBe('string');
expect(item.warning.length).toBeGreaterThan(0);
});
});
});
30 changes: 30 additions & 0 deletions __tests__/test-helpers/load-configurations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Test helper for loading configurations in Jest environment
* Uses Node.js glob instead of Vite's import.meta.glob
*/

import { glob } from 'glob';
import path from 'path';
import { fileURLToPath } from 'url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const configurationsPath = path.join(__dirname, '../../src/data/configurations');

/**
* Load all configuration entries from the configurations directory
* @returns {Promise<Array>} Array of all configuration objects
*/
export const loadTestConfigurations = async () => {
const files = await glob('**/*.js', { cwd: configurationsPath });
const items = [];

for (const file of files) {
const fullPath = path.join(configurationsPath, file);
const module = await import(fullPath);
if (module.default) {
items.push(module.default);
}
}

return items.sort((a, b) => a.name.localeCompare(b.name));
};
30 changes: 30 additions & 0 deletions __tests__/test-helpers/load-software-catalog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Test helper for loading software catalog in Jest environment
* Uses Node.js glob instead of Vite's import.meta.glob
*/

import { glob } from 'glob';
import path from 'path';
import { fileURLToPath } from 'url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const softwarePath = path.join(__dirname, '../../src/data/software');

/**
* Load all software entries from the software directory
* @returns {Promise<Array>} Array of all software objects
*/
export const loadTestSoftwareCatalog = async () => {
const files = await glob('**/*.js', { cwd: softwarePath });
const items = [];

for (const file of files) {
const fullPath = path.join(softwarePath, file);
const module = await import(fullPath);
if (module.default) {
items.push(module.default);
}
}

return items.sort((a, b) => a.name.localeCompare(b.name));
};
Loading