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
61 changes: 55 additions & 6 deletions src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,60 @@ const rolesDarkP3 = convertRolesToP3(rolesDark);
// VS Code Themes
// ============================================
const vscodeThemes = [
{ file: "themes/pierre-light.json", theme: makeTheme("Pierre Light", "light", rolesLight) },
{ file: "themes/pierre-light-soft.json", theme: makeTheme("Pierre Light Soft", "light", rolesLightSoft) },
{ file: "themes/pierre-dark.json", theme: makeTheme("Pierre Dark", "dark", rolesDark) },
{ file: "themes/pierre-dark-soft.json", theme: makeTheme("Pierre Dark Soft", "dark", rolesDarkSoft) },
{ file: "themes/pierre-light-vibrant.json", theme: makeTheme("Pierre Light Vibrant", "light", rolesLightP3) },
{ file: "themes/pierre-dark-vibrant.json", theme: makeTheme("Pierre Dark Vibrant", "dark", rolesDarkP3) }
{
file: "themes/pierre-light.json",
theme: makeTheme({
name: "pierre-light",
displayName: "Pierre Light",
type: "light",
roles: rolesLight
})
},
{
file: "themes/pierre-light-soft.json",
theme: makeTheme({
name: "pierre-light-soft",
displayName: "Pierre Light Soft",
type: "light",
roles: rolesLightSoft
})
},
{
file: "themes/pierre-dark.json",
theme: makeTheme({
name: "pierre-dark",
displayName: "Pierre Dark",
type: "dark",
roles: rolesDark
})
},
{
file: "themes/pierre-dark-soft.json",
theme: makeTheme({
name: "pierre-dark-soft",
displayName: "Pierre Dark Soft",
type: "dark",
roles: rolesDarkSoft
})
},
{
file: "themes/pierre-light-vibrant.json",
theme: makeTheme({
name: "pierre-light-vibrant",
displayName: "Pierre Light Vibrant",
type: "light",
roles: rolesLightP3
})
},
{
file: "themes/pierre-dark-vibrant.json",
theme: makeTheme({
name: "pierre-dark-vibrant",
displayName: "Pierre Dark Vibrant",
type: "dark",
roles: rolesDarkP3
})
}
];

for (const {file, theme} of vscodeThemes) {
Expand Down Expand Up @@ -53,6 +101,7 @@ const themeNames: string[] = [];
const themeDts = `/** VS Code / TextMate theme object (frozen at runtime). */
interface PierreTheme {
readonly name: string;
readonly displayName: string;
readonly type: "light" | "dark";
readonly colors: Readonly<Record<string, string>>;
readonly tokenColors: ReadonlyArray<{
Expand Down
71 changes: 50 additions & 21 deletions src/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,26 +42,46 @@ function collectColors(obj: any, path = ""): string[] {
return issues;
}

function testThemeGeneration(themeName: string, themeType: "light" | "dark", roles: any) {
console.log(`\n🧪 Testing ${themeName}...`);
function testThemeGeneration(
expectedName: string,
displayName: string,
themeType: "light" | "dark",
roles: any
) {
console.log(`\n🧪 Testing ${displayName}...`);
const errors: string[] = [];

try {
const theme = makeTheme(themeName, themeType, roles);
const theme = makeTheme({
name: expectedName,
displayName,
type: themeType,
roles
});
const themeMetadata = theme as { name?: string; displayName?: string };

// Test 1: Required properties exist
if (!theme.name) errors.push("Missing theme name");
if (!themeMetadata.name) errors.push("Missing theme name");
if (!themeMetadata.displayName) errors.push("Missing theme displayName");
if (!theme.type) errors.push("Missing theme type");
if (!theme.colors) errors.push("Missing colors object");
if (!theme.tokenColors) errors.push("Missing tokenColors array");
if (!theme.semanticTokenColors) errors.push("Missing semanticTokenColors object");

// Test 2: Type is correct
// Test 2: Theme metadata uses package-safe names plus display labels
if (themeMetadata.name !== expectedName) {
errors.push(`Expected name "${expectedName}" but got "${themeMetadata.name}"`);
}
if (themeMetadata.displayName !== displayName) {
errors.push(`Expected displayName "${displayName}" but got "${themeMetadata.displayName}"`);
}

// Test 3: Type is correct
if (theme.type !== themeType) {
errors.push(`Expected type "${themeType}" but got "${theme.type}"`);
}

// Test 3: Critical editor colors exist
// Test 4: Critical editor colors exist
const criticalColors = [
"editor.background",
"editor.foreground",
Expand All @@ -78,25 +98,25 @@ function testThemeGeneration(themeName: string, themeType: "light" | "dark", rol
}
}

// Test 4: Validate all color values
// Test 5: Validate all color values
const colorIssues = collectColors(theme.colors);
errors.push(...colorIssues);

// Test 5: Check for undefined/null values in colors
// Test 6: Check for undefined/null values in colors
for (const [key, value] of Object.entries(theme.colors)) {
if (value === undefined || value === null) {
errors.push(`Color "${key}" is ${value}`);
}
}

// Test 6: TokenColors should be an array with entries
// Test 7: TokenColors should be an array with entries
if (!Array.isArray(theme.tokenColors)) {
errors.push("tokenColors is not an array");
} else if (theme.tokenColors.length === 0) {
errors.push("tokenColors array is empty");
}

// Test 7: Validate tokenColors structure
// Test 8: Validate tokenColors structure
theme.tokenColors.forEach((token, idx) => {
if (!token.scope) {
errors.push(`tokenColors[${idx}] missing scope`);
Expand All @@ -111,7 +131,7 @@ function testThemeGeneration(themeName: string, themeType: "light" | "dark", rol
}
});

// Test 8: Semantic tokens validation
// Test 9: Semantic tokens validation
for (const [key, value] of Object.entries(theme.semanticTokenColors)) {
if (typeof value === "string") {
usedColors.add(value);
Expand All @@ -130,15 +150,15 @@ function testThemeGeneration(themeName: string, themeType: "light" | "dark", rol
}

if (errors.length === 0) {
console.log(`✅ ${themeName} passed all checks`);
console.log(`✅ ${displayName} passed all checks`);
return true;
} else {
console.error(`❌ ${themeName} failed with ${errors.length} error(s):`);
console.error(`❌ ${displayName} failed with ${errors.length} error(s):`);
errors.forEach(err => console.error(` - ${err}`));
return false;
}
} catch (error) {
console.error(`❌ ${themeName} threw an error:`, error);
console.error(`❌ ${displayName} threw an error:`, error);
return false;
}
}
Expand All @@ -148,13 +168,15 @@ function testGeneratedFiles() {
const errors: string[] = [];

const files = [
{ path: "themes/pierre-light.json", expectedType: "light" },
{ path: "themes/pierre-dark.json", expectedType: "dark" },
{ path: "themes/pierre-light-vibrant.json", expectedType: "light" },
{ path: "themes/pierre-dark-vibrant.json", expectedType: "dark" }
{ path: "themes/pierre-light.json", expectedType: "light", expectedName: "pierre-light", expectedDisplayName: "Pierre Light" },
{ path: "themes/pierre-light-soft.json", expectedType: "light", expectedName: "pierre-light-soft", expectedDisplayName: "Pierre Light Soft" },
{ path: "themes/pierre-dark.json", expectedType: "dark", expectedName: "pierre-dark", expectedDisplayName: "Pierre Dark" },
{ path: "themes/pierre-dark-soft.json", expectedType: "dark", expectedName: "pierre-dark-soft", expectedDisplayName: "Pierre Dark Soft" },
{ path: "themes/pierre-light-vibrant.json", expectedType: "light", expectedName: "pierre-light-vibrant", expectedDisplayName: "Pierre Light Vibrant" },
{ path: "themes/pierre-dark-vibrant.json", expectedType: "dark", expectedName: "pierre-dark-vibrant", expectedDisplayName: "Pierre Dark Vibrant" }
];

for (const { path, expectedType } of files) {
for (const { path, expectedType, expectedName, expectedDisplayName } of files) {
// Test 1: File exists
if (!existsSync(path)) {
errors.push(`File does not exist: ${path}`);
Expand All @@ -173,6 +195,13 @@ function testGeneratedFiles() {

// Test 3: Has required structure
if (!theme.name) errors.push(`${path}: Missing name`);
if (!theme.displayName) errors.push(`${path}: Missing displayName`);
if (theme.name !== expectedName) {
errors.push(`${path}: Expected name "${expectedName}" but got "${theme.name}"`);
}
if (theme.displayName !== expectedDisplayName) {
errors.push(`${path}: Expected displayName "${expectedDisplayName}" but got "${theme.displayName}"`);
}
if (!theme.type) errors.push(`${path}: Missing type`);
if (theme.type !== expectedType) {
errors.push(`${path}: Expected type "${expectedType}" but got "${theme.type}"`);
Expand Down Expand Up @@ -253,8 +282,8 @@ let allPassed = true;
allPassed = testPaletteRoles() && allPassed;

// Test theme generation
allPassed = testThemeGeneration("Pierre Light", "light", rolesLight) && allPassed;
allPassed = testThemeGeneration("Pierre Dark", "dark", rolesDark) && allPassed;
allPassed = testThemeGeneration("pierre-light", "Pierre Light", "light", rolesLight) && allPassed;
allPassed = testThemeGeneration("pierre-dark", "Pierre Dark", "dark", rolesDark) && allPassed;

// Test generated files (only if they exist - they should after build)
allPassed = testGeneratedFiles() && allPassed;
Expand Down
11 changes: 10 additions & 1 deletion src/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,24 @@ import type { Roles } from "./palette";

type VSCodeTheme = {
name: string;
displayName: string;
type: "light" | "dark";
colors: Record<string, string>;
tokenColors: any[];
semanticTokenColors: Record<string,string|{foreground:string;fontStyle?:string}>;
};

export function makeTheme(name: string, kind: "light"|"dark", c: Roles): VSCodeTheme {
type MakeThemeOptions = {
name: string;
displayName: string;
type: "light" | "dark";
roles: Roles;
};

export function makeTheme({ name, displayName, type: kind, roles: c }: MakeThemeOptions): VSCodeTheme {
return {
name,
displayName,
type: kind,
colors: {
// Core editor & text
Expand Down
3 changes: 2 additions & 1 deletion themes/pierre-dark-soft.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "Pierre Dark Soft",
"name": "pierre-dark-soft",
"displayName": "Pierre Dark Soft",
"type": "dark",
"colors": {
"editor.background": "#171717",
Expand Down
3 changes: 2 additions & 1 deletion themes/pierre-dark-vibrant.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "Pierre Dark Vibrant",
"name": "pierre-dark-vibrant",
"displayName": "Pierre Dark Vibrant",
"type": "dark",
"colors": {
"editor.background": "color(display-p3 0.039216 0.039216 0.039216)",
Expand Down
3 changes: 2 additions & 1 deletion themes/pierre-dark.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "Pierre Dark",
"name": "pierre-dark",
"displayName": "Pierre Dark",
"type": "dark",
"colors": {
"editor.background": "#0a0a0a",
Expand Down
3 changes: 2 additions & 1 deletion themes/pierre-light-soft.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "Pierre Light Soft",
"name": "pierre-light-soft",
"displayName": "Pierre Light Soft",
"type": "light",
"colors": {
"editor.background": "#ffffff",
Expand Down
3 changes: 2 additions & 1 deletion themes/pierre-light-vibrant.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "Pierre Light Vibrant",
"name": "pierre-light-vibrant",
"displayName": "Pierre Light Vibrant",
"type": "light",
"colors": {
"editor.background": "color(display-p3 1.000000 1.000000 1.000000)",
Expand Down
3 changes: 2 additions & 1 deletion themes/pierre-light.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "Pierre Light",
"name": "pierre-light",
"displayName": "Pierre Light",
"type": "light",
"colors": {
"editor.background": "#ffffff",
Expand Down
Loading