diff --git a/astro.config.mjs b/astro.config.mjs index 5309221..5c37edf 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,10 +1,78 @@ import { defineConfig } from 'astro/config'; import mdx from '@astrojs/mdx'; +const workbenchTheme = { + name: 'workbench', + type: 'light', + colors: { + 'editor.background': '#EEEAE2', + 'editor.foreground': '#3D342E', + }, + tokenColors: [ + { + scope: ['comment', 'comment.line', 'comment.block'], + settings: { foreground: '#948880', fontStyle: 'italic' }, + }, + { + scope: ['keyword', 'storage', 'keyword.control'], + settings: { foreground: '#A0522D' }, + }, + { + scope: ['string', 'string.template'], + settings: { foreground: '#7D6C2F' }, + }, + { + scope: ['constant', 'constant.numeric', 'constant.language'], + settings: { foreground: '#7D6C2F' }, + }, + { + scope: ['entity.name.function', 'support.function'], + settings: { foreground: '#2B2421', fontStyle: 'bold' }, + }, + { + scope: ['entity.name.type', 'entity.name.class', 'support.type'], + settings: { foreground: '#B07051' }, + }, + { + scope: ['variable', 'variable.other'], + settings: { foreground: '#3D342E' }, + }, + { + scope: ['punctuation', 'punctuation.definition', 'punctuation.separator'], + settings: { foreground: '#6B5F55' }, + }, + { + scope: ['entity.name.tag'], + settings: { foreground: '#A0522D' }, + }, + { + scope: ['entity.other.attribute-name'], + settings: { foreground: '#7D6C2F' }, + }, + { + scope: ['support.type.property-name', 'meta.object-literal.key'], + settings: { foreground: '#2B2421' }, + }, + { + scope: ['keyword.operator', 'keyword.operator.assignment'], + settings: { foreground: '#6B5F55' }, + }, + { + scope: ['keyword.control.import', 'keyword.control.export'], + settings: { foreground: '#A0522D' }, + }, + ], +}; + export default defineConfig({ site: 'https://kenotron.github.io', base: '/crashtestdev', integrations: [ mdx(), ], -}); \ No newline at end of file + markdown: { + shikiConfig: { + theme: workbenchTheme, + }, + }, +}); diff --git a/tests/shiki-theme.test.mjs b/tests/shiki-theme.test.mjs new file mode 100644 index 0000000..34b2c37 --- /dev/null +++ b/tests/shiki-theme.test.mjs @@ -0,0 +1,153 @@ +import { describe, it } from 'node:test'; +import { strict as assert } from 'node:assert'; + +// Import the config - defineConfig is a passthrough so this works +const configModule = await import('../astro.config.mjs'); +const config = configModule.default; + +describe('workbenchTheme in astro.config.mjs', () => { + it('should have markdown.shikiConfig.theme defined', () => { + assert.ok(config.markdown, 'config.markdown should exist'); + assert.ok(config.markdown.shikiConfig, 'config.markdown.shikiConfig should exist'); + assert.ok(config.markdown.shikiConfig.theme, 'config.markdown.shikiConfig.theme should exist'); + }); + + it('should have theme name "workbench" and type "light"', () => { + const theme = config.markdown.shikiConfig.theme; + assert.equal(theme.name, 'workbench'); + assert.equal(theme.type, 'light'); + }); + + it('should have correct editor colors', () => { + const theme = config.markdown.shikiConfig.theme; + assert.ok(theme.colors, 'theme.colors should exist'); + assert.equal(theme.colors['editor.background'], '#EEEAE2'); + assert.equal(theme.colors['editor.foreground'], '#3D342E'); + }); + + it('should have exactly 13 tokenColor scopes', () => { + const theme = config.markdown.shikiConfig.theme; + assert.ok(Array.isArray(theme.tokenColors), 'tokenColors should be an array'); + assert.equal(theme.tokenColors.length, 13); + }); + + // Helper to find a tokenColor entry by matching a scope string + function findTokenColor(theme, scopeSubstring) { + return theme.tokenColors.find((tc) => { + const scopes = Array.isArray(tc.scope) ? tc.scope : [tc.scope]; + return scopes.some((s) => s.includes(scopeSubstring)); + }); + } + + it('should style comments as #948880 italic', () => { + const theme = config.markdown.shikiConfig.theme; + const tc = findTokenColor(theme, 'comment'); + assert.ok(tc, 'comments tokenColor should exist'); + assert.equal(tc.settings.foreground, '#948880'); + assert.equal(tc.settings.fontStyle, 'italic'); + }); + + it('should style keywords/storage/control as #A0522D (terracotta)', () => { + const theme = config.markdown.shikiConfig.theme; + const tc = findTokenColor(theme, 'keyword'); + assert.ok(tc, 'keywords tokenColor should exist'); + assert.equal(tc.settings.foreground, '#A0522D'); + // Verify scope includes storage and control + const scopes = Array.isArray(tc.scope) ? tc.scope : [tc.scope]; + const hasStorage = scopes.some((s) => s.includes('storage')); + const hasControl = scopes.some((s) => s.includes('control')); + assert.ok(hasStorage, 'keywords scope should include storage'); + assert.ok(hasControl, 'keywords scope should include control'); + }); + + it('should style strings/templates as #7D6C2F (tarnished brass)', () => { + const theme = config.markdown.shikiConfig.theme; + const tc = findTokenColor(theme, 'string'); + assert.ok(tc, 'strings tokenColor should exist'); + assert.equal(tc.settings.foreground, '#7D6C2F'); + }); + + it('should style constants as #7D6C2F', () => { + const theme = config.markdown.shikiConfig.theme; + const tc = findTokenColor(theme, 'constant'); + assert.ok(tc, 'constants tokenColor should exist'); + assert.equal(tc.settings.foreground, '#7D6C2F'); + }); + + it('should style functions as #2B2421 bold', () => { + const theme = config.markdown.shikiConfig.theme; + const tc = findTokenColor(theme, 'entity.name.function'); + assert.ok(tc, 'functions tokenColor should exist'); + assert.equal(tc.settings.foreground, '#2B2421'); + assert.equal(tc.settings.fontStyle, 'bold'); + }); + + it('should style types/classes as #B07051', () => { + const theme = config.markdown.shikiConfig.theme; + const tc = findTokenColor(theme, 'entity.name.type'); + assert.ok(tc, 'types/classes tokenColor should exist'); + assert.equal(tc.settings.foreground, '#B07051'); + }); + + it('should style variables as #3D342E', () => { + const theme = config.markdown.shikiConfig.theme; + const tc = findTokenColor(theme, 'variable'); + assert.ok(tc, 'variables tokenColor should exist'); + assert.equal(tc.settings.foreground, '#3D342E'); + }); + + it('should style punctuation as #6B5F55', () => { + const theme = config.markdown.shikiConfig.theme; + const tc = findTokenColor(theme, 'punctuation'); + assert.ok(tc, 'punctuation tokenColor should exist'); + assert.equal(tc.settings.foreground, '#6B5F55'); + }); + + it('should style HTML tags as #A0522D', () => { + const theme = config.markdown.shikiConfig.theme; + const tc = findTokenColor(theme, 'entity.name.tag'); + assert.ok(tc, 'HTML tags tokenColor should exist'); + assert.equal(tc.settings.foreground, '#A0522D'); + }); + + it('should style HTML attributes as #7D6C2F', () => { + const theme = config.markdown.shikiConfig.theme; + const tc = findTokenColor(theme, 'entity.other.attribute-name'); + assert.ok(tc, 'HTML attributes tokenColor should exist'); + assert.equal(tc.settings.foreground, '#7D6C2F'); + }); + + it('should style property names as #2B2421', () => { + const theme = config.markdown.shikiConfig.theme; + const tc = findTokenColor(theme, 'support.type.property-name'); + assert.ok(tc, 'property names tokenColor should exist'); + assert.equal(tc.settings.foreground, '#2B2421'); + }); + + it('should style operators as #6B5F55', () => { + const theme = config.markdown.shikiConfig.theme; + const tc = findTokenColor(theme, 'keyword.operator'); + assert.ok(tc, 'operators tokenColor should exist'); + assert.equal(tc.settings.foreground, '#6B5F55'); + }); + + it('should style imports/exports as #A0522D', () => { + const theme = config.markdown.shikiConfig.theme; + const tc = findTokenColor(theme, 'keyword.control.import'); + assert.ok(tc, 'imports/exports tokenColor should exist'); + assert.equal(tc.settings.foreground, '#A0522D'); + }); + + it('should preserve site and base config', () => { + assert.equal(config.site, 'https://kenotron.github.io'); + assert.equal(config.base, '/crashtestdev'); + }); + + it('should include mdx integration', () => { + assert.ok(config.integrations, 'integrations should exist'); + const hasMdx = config.integrations.some( + (i) => i.name === '@astrojs/mdx' + ); + assert.ok(hasMdx, 'mdx integration should be present'); + }); +}); \ No newline at end of file