feat: configure Shiki syntax highlighting with warm workbench theme
This commit is contained in:
+69
-1
@@ -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(),
|
||||
],
|
||||
});
|
||||
markdown: {
|
||||
shikiConfig: {
|
||||
theme: workbenchTheme,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user