From e1bdf3d1e99a26124c9df618ee9700d4de2938ca Mon Sep 17 00:00:00 2001 From: David Pine <7679720+IEvangelist@users.noreply.github.com> Date: Wed, 9 Sep 2026 14:01:45 -0500 Subject: [PATCH 1/5] Enable every Aspire AppHost language Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/components/SimpleAppHostCode.astro | 61 ++++++------ src/frontend/src/data/apphost-languages.json | 8 +- .../tests/e2e/integrations-gallery.spec.ts | 19 ++-- src/frontend/tests/e2e/pivot-selector.spec.ts | 56 +++++++---- .../tests/unit/api-markdown.vitest.test.ts | 16 ++- .../unit/apphost-languages.vitest.test.ts | 51 ++++++++-- .../unit/custom-components.vitest.test.ts | 98 ++++++++++++++----- .../tests/unit/update-samples.vitest.test.ts | 21 ++-- 8 files changed, 222 insertions(+), 108 deletions(-) diff --git a/src/frontend/src/components/SimpleAppHostCode.astro b/src/frontend/src/components/SimpleAppHostCode.astro index 3f2bacbca..e1c50b8b8 100644 --- a/src/frontend/src/components/SimpleAppHostCode.astro +++ b/src/frontend/src/components/SimpleAppHostCode.astro @@ -1,6 +1,10 @@ --- import { Code } from '@astrojs/starlight/components'; import AppHostTabs from './AppHostTabs.astro'; +import { + appHostLanguageConfig, + type AppHostLanguageId, +} from '@utils/apphost-languages'; import { getSimpleAppHostCode, shiftCollapseValue, @@ -21,11 +25,22 @@ const { lang = 'csharp', mark, collapse }: Props = Astro.props; const { csharpCode, typescriptCode } = getSimpleAppHostCode(lang); const typescriptMark = shiftMarkerValue(mark, typescriptLineOffset); const typescriptCollapse = shiftCollapseValue(collapse, typescriptLineOffset); +const limitations = Object.fromEntries( + appHostLanguageConfig.languages + .filter( + (language) => + language.id !== 'csharp' && (language.id !== 'typescript' || !typescriptCode) + ) + .map((language) => [ + language.id, + `This example does not include a ${language.label} AppHost variant.`, + ]) +) as Partial>; --- -{ - typescriptCode ? ( - + + { + typescriptCode && ( - - - - - - ) : ( - <> - - - - ) -} + ) + } + + + {typescriptCode ? : } + + {typescriptCode && } diff --git a/src/frontend/src/data/apphost-languages.json b/src/frontend/src/data/apphost-languages.json index 79266a48a..57782ba2e 100644 --- a/src/frontend/src/data/apphost-languages.json +++ b/src/frontend/src/data/apphost-languages.json @@ -32,7 +32,7 @@ { "id": "python", "label": "Python", - "enabled": false, + "enabled": true, "experimental": true, "aliases": ["py"], "cliLanguage": "python", @@ -46,7 +46,7 @@ { "id": "go", "label": "Go", - "enabled": false, + "enabled": true, "experimental": true, "aliases": ["golang"], "cliLanguage": "go", @@ -60,7 +60,7 @@ { "id": "java", "label": "Java", - "enabled": false, + "enabled": true, "experimental": true, "aliases": [], "cliLanguage": "java", @@ -74,7 +74,7 @@ { "id": "rust", "label": "Rust", - "enabled": false, + "enabled": true, "experimental": true, "aliases": ["rs"], "cliLanguage": "rust", diff --git a/src/frontend/tests/e2e/integrations-gallery.spec.ts b/src/frontend/tests/e2e/integrations-gallery.spec.ts index 3fc8f8021..8b0701fd6 100644 --- a/src/frontend/tests/e2e/integrations-gallery.spec.ts +++ b/src/frontend/tests/e2e/integrations-gallery.spec.ts @@ -1,4 +1,5 @@ import { expect, test } from '@playwright/test'; +import appHostLanguageConfig from '../../src/data/apphost-languages.json' with { type: 'json' }; import { dismissCookieConsentIfVisible } from '@tests/e2e/helpers'; const SHOW_CLIENT = process.env.PUBLIC_SHOW_CLIENT_INTEGRATIONS === 'true'; @@ -84,12 +85,18 @@ test.describe('integrations gallery', () => { const postgresCard = page.locator('.card[data-title="aspire.hosting.postgresql"]'); const languageLinks = postgresCard.locator('.lang-button'); - const tsLink = languageLinks.first(); - const csharpLink = languageLinks.nth(1); - - await expect(languageLinks).toHaveCount(2); - await expect(tsLink).toHaveAttribute('href', /aspire-lang=typescript/); - await expect(csharpLink).toHaveAttribute('href', /aspire-lang=csharp/); + const enabledLanguages = appHostLanguageConfig.languages.filter( + (language) => language.enabled + ); + const tsLink = postgresCard.locator('.lang-button[href*="aspire-lang=typescript"]'); + const csharpLink = postgresCard.locator('.lang-button[href*="aspire-lang=csharp"]'); + + await expect(languageLinks).toHaveCount(enabledLanguages.length); + for (const language of enabledLanguages) { + await expect( + postgresCard.locator(`.lang-button[href*="aspire-lang=${language.id}"]`) + ).toHaveAttribute('href', new RegExp(`aspire-lang=${language.id}`)); + } // Both links should target the same base docs path — only the lang differs. const csharpHref = await csharpLink.getAttribute('href'); diff --git a/src/frontend/tests/e2e/pivot-selector.spec.ts b/src/frontend/tests/e2e/pivot-selector.spec.ts index 490155450..7e6093ffe 100644 --- a/src/frontend/tests/e2e/pivot-selector.spec.ts +++ b/src/frontend/tests/e2e/pivot-selector.spec.ts @@ -1,4 +1,5 @@ import { expect, test } from '@playwright/test'; +import appHostLanguageConfig from '../../src/data/apphost-languages.json' with { type: 'json' }; import { dismissCookieConsentIfVisible } from '@tests/e2e/helpers'; test('prerequisites apphost tabs default to TypeScript and persist selection', async ({ @@ -84,21 +85,34 @@ test('apphost tabs restore and sync the aspire-lang query string', async ({ page .toBe('csharp'); }); -test('disabled languages fall back while unknown values preserve saved preferences', async ({ +test('configured languages follow the registry while unknown values preserve saved preferences', async ({ page, }) => { + const python = appHostLanguageConfig.languages.find( + (language) => language.id === 'python' + )!; await page.goto('/get-started/prerequisites/?aspire-lang=python'); await dismissCookieConsentIfVisible(page); const appHostTabs = page.locator('starlight-tabs[data-sync-key="aspire-lang"]').first(); - await expect(page).toHaveURL(/\?aspire-lang=typescript$/); - await expect(appHostTabs.getByRole('tab', { name: 'TypeScript' })).toHaveAttribute( - 'aria-selected', - 'true' - ); - await expect - .poll(() => page.evaluate(() => localStorage.getItem('aspire-lang'))) - .toBe('typescript'); + if (python.enabled) { + await expect(page).toHaveURL(/\?aspire-lang=python$/); + await expect( + appHostTabs.getByRole('tab').filter({ hasText: /^Python$/ }) + ).toHaveAttribute('aria-selected', 'true'); + await expect + .poll(() => page.evaluate(() => localStorage.getItem('aspire-lang'))) + .toBe('python'); + } else { + await expect(page).toHaveURL(/\?aspire-lang=typescript$/); + await expect(appHostTabs.getByRole('tab', { name: 'TypeScript' })).toHaveAttribute( + 'aria-selected', + 'true' + ); + await expect + .poll(() => page.evaluate(() => localStorage.getItem('aspire-lang'))) + .toBe('typescript'); + } await page.evaluate(() => { localStorage.setItem('aspire-lang', 'csharp'); @@ -167,19 +181,23 @@ test('AppHost page restores the shared AppHost language query string', async ({ await expect(csharpContent).toBeHidden(); }); -test('disabled AppHost language pivots and media are absent from rendered pages', async ({ +test('AppHost language pivots and media follow the registry bits', async ({ page, }) => { await page.goto('/get-started/first-app/'); await dismissCookieConsentIfVisible(page); - await expect(page.locator('[data-pivot-block="typescript"]').first()).toBeAttached(); - await expect(page.locator('[data-pivot-block="csharp"]').first()).toBeAttached(); - await expect(page.locator('[data-pivot-block="python"]')).toHaveCount(0); - await expect(page.locator('[data-pivot-block="go"]')).toHaveCount(0); - await expect(page.locator('[data-pivot-block="java"]')).toHaveCount(0); - await expect(page.locator('[data-pivot-block="rust"]')).toHaveCount(0); - await expect( - page.locator('.asciinema-player-container[data-src="/casts/apphost-python.cast"]') - ).toHaveCount(0); + for (const language of appHostLanguageConfig.languages) { + const pivot = page.locator(`[data-pivot-block="${language.id}"]`); + const cast = page.locator( + `.asciinema-player-container[data-src="/casts/apphost-${language.id}.cast"]` + ); + if (language.enabled) { + await expect(pivot.first()).toBeAttached(); + await expect(cast).toHaveCount(1); + } else { + await expect(pivot).toHaveCount(0); + await expect(cast).toHaveCount(0); + } + } }); diff --git a/src/frontend/tests/unit/api-markdown.vitest.test.ts b/src/frontend/tests/unit/api-markdown.vitest.test.ts index b28bb08f3..840c8e95a 100644 --- a/src/frontend/tests/unit/api-markdown.vitest.test.ts +++ b/src/frontend/tests/unit/api-markdown.vitest.test.ts @@ -15,6 +15,7 @@ import { import { memberKindSlugs, resolveMemberAnchors } from '@utils/packages'; import { renderTypeScriptItemMarkdown, renderTypeScriptModuleMarkdown } from '@utils/typescript-api-markdown'; import type { TsApiDocument, TsHandleType } from '@utils/ts-modules'; +import { appHostLanguageConfig } from '@utils/apphost-languages'; vi.mock('astro:content', async (importOriginal) => { const actual = await importOriginal(); @@ -156,11 +157,16 @@ describe('API markdown routes', () => { const markdown = await readMarkdown(appHostItemRoute.GET?.({ props: route.props } as any)); expect(markdown).toContain(`# ${route.props.item.name}`); - expect(markdown).toContain('## TypeScript'); - expect(markdown).not.toContain('## Python'); - expect(markdown).not.toContain('## Go'); - expect(markdown).not.toContain('## Java'); - expect(markdown).not.toContain('## Rust'); + for (const language of appHostLanguageConfig.languages.filter( + (candidate) => candidate.generatedApi + )) { + const heading = `## ${language.label}`; + if (language.enabled) { + expect(markdown).toContain(heading); + } else { + expect(markdown).not.toContain(heading); + } + } }); it('returns markdown for a canonical AppHost member route', async () => { diff --git a/src/frontend/tests/unit/apphost-languages.vitest.test.ts b/src/frontend/tests/unit/apphost-languages.vitest.test.ts index 8362ac9bf..028c1e482 100644 --- a/src/frontend/tests/unit/apphost-languages.vitest.test.ts +++ b/src/frontend/tests/unit/apphost-languages.vitest.test.ts @@ -83,7 +83,7 @@ function escapeRegExp(value: string): string { } describe('AppHost language registry', () => { - test('keeps the canonical order and enables only the established languages initially', () => { + test('keeps the canonical order and returns the registry-enabled languages', () => { expect(appHostLanguageConfig.languages.map((language) => language.id)).toEqual([ 'typescript', 'csharp', @@ -92,10 +92,11 @@ describe('AppHost language registry', () => { 'java', 'rust', ]); - expect(getEnabledAppHostLanguages().map((language) => language.id)).toEqual([ - 'typescript', - 'csharp', - ]); + expect(getEnabledAppHostLanguages().map((language) => language.id)).toEqual( + appHostLanguageConfig.languages + .filter((language) => language.enabled) + .map((language) => language.id) + ); }); test('normalizes exact aliases without substring collisions', () => { @@ -104,13 +105,30 @@ describe('AppHost language registry', () => { expect(normalizeAppHostLanguage('C#')).toBe('csharp'); expect(normalizeAppHostLanguage('javascript')).toBeUndefined(); expect(normalizeAppHostLanguage('mongo')).toBeUndefined(); - expect(normalizeAppHostLanguage('python')).toBeUndefined(); + expect(normalizeAppHostLanguage('python')).toBe( + appHostLanguageConfig.languages.find((language) => language.id === 'python')?.enabled + ? 'python' + : undefined + ); expect(normalizeAppHostLanguage('python', false)).toBe('python'); }); test('publishes project links only for enabled AppHost languages', () => { expect(getAppHostLanguageProjectHref('typescript')).toBe('/app-host/typescript-apphost/'); - expect(getAppHostLanguageProjectHref('python')).toBeUndefined(); + const pythonEnabled = appHostLanguageConfig.languages.find( + (language) => language.id === 'python' + )?.enabled; + expect(getAppHostLanguageProjectHref('python')).toBe( + pythonEnabled ? '/app-host/python-apphost/' : undefined + ); + + const disabledPythonConfig = { + ...appHostLanguageConfig, + languages: appHostLanguageConfig.languages.map((language) => ({ + ...language, + enabled: language.id === 'python' ? false : language.enabled, + })), + }; const enabledPythonConfig = { ...appHostLanguageConfig, @@ -120,6 +138,7 @@ describe('AppHost language registry', () => { })), }; + expect(getAppHostLanguageProjectHref('python', disabledPythonConfig)).toBeUndefined(); expect(getAppHostLanguageProjectHref('python', enabledPythonConfig)).toBe( '/app-host/python-apphost/' ); @@ -400,7 +419,11 @@ builder.run() `; - const rendered = renderAppHostTabsInMarkdown(markdown, appHostLanguageConfig.languages); + const languages = appHostLanguageConfig.languages.map((language) => ({ + ...language, + enabled: ['typescript', 'csharp'].includes(language.id), + })); + const rendered = renderAppHostTabsInMarkdown(markdown, languages); expect(rendered).toContain('### TypeScript'); expect(rendered).toContain('### C#'); @@ -443,7 +466,11 @@ builder.run() `; - const rendered = renderAppHostTabsInMarkdown(markdown, appHostLanguageConfig.languages); + const languages = appHostLanguageConfig.languages.map((language) => ({ + ...language, + enabled: ['typescript', 'csharp'].includes(language.id), + })); + const rendered = renderAppHostTabsInMarkdown(markdown, languages); expect(rendered).toContain(' ### TypeScript'); expect(rendered).toContain(' ### C#'); @@ -458,7 +485,11 @@ Python preview content `; - const rendered = renderAppHostTabsInMarkdown(markdown, appHostLanguageConfig.languages); + const languages = appHostLanguageConfig.languages.map((language) => ({ + ...language, + enabled: language.id !== 'python', + })); + const rendered = renderAppHostTabsInMarkdown(markdown, languages); expect(rendered).toContain('TypeScript content'); expect(rendered).not.toContain('Python preview content'); diff --git a/src/frontend/tests/unit/custom-components.vitest.test.ts b/src/frontend/tests/unit/custom-components.vitest.test.ts index fe7b5b918..84590d336 100644 --- a/src/frontend/tests/unit/custom-components.vitest.test.ts +++ b/src/frontend/tests/unit/custom-components.vitest.test.ts @@ -925,27 +925,36 @@ describe('custom Astro component render coverage', () => { }); it('renders enabled AppHost languages from the shared registry', async () => { + const enabledLanguages = getEnabledAppHostLanguages(); const selectorHtml = normalizeHtml(await renderComponent(AppHostLanguageSelector)); const tabsHtml = normalizeHtml( await renderComponent(AppHostTabs, { - slots: { - typescript: '

TypeScript AppHost content

', - csharp: '

C# AppHost content

', - }, + slots: Object.fromEntries( + enabledLanguages.map((language) => [ + language.id, + `

${language.label} AppHost content

`, + ]) + ), }) ); - expect(selectorHtml).toContain('data-pivot-option="typescript"'); - expect(selectorHtml).toContain('data-pivot-option="csharp"'); - expect(selectorHtml).not.toContain('data-pivot-option="python"'); expect(tabsHtml).toContain('data-apphost-tabs'); - expect(tabsHtml).toContain('data-supports-typescript'); - expect(tabsHtml).toContain('data-supports-csharp'); - expect(tabsHtml).toContain('TypeScript AppHost content'); - expect(tabsHtml).toContain('C# AppHost content'); + for (const language of appHostLanguageConfig.languages) { + if (language.enabled) { + expect(selectorHtml).toContain(`data-pivot-option="${language.id}"`); + expect(tabsHtml).toContain(`data-supports-${language.id}`); + expect(tabsHtml).toContain(`${language.label} AppHost content`); + } else { + expect(selectorHtml).not.toContain(`data-pivot-option="${language.id}"`); + expect(tabsHtml).not.toContain(`data-supports-${language.id}`); + } + } }); - it('removes disabled AppHost language pivots from rendered output', async () => { + it('renders AppHost language pivots according to the registry bit', async () => { + const pythonEnabled = appHostLanguageConfig.languages.find( + (language) => language.id === 'python' + )?.enabled; const enabledHtml = normalizeHtml( await renderComponent(AppHostLanguagePivot, { props: { id: 'typescript' }, @@ -960,10 +969,17 @@ describe('custom Astro component render coverage', () => { ); expect(enabledHtml).toContain('Enabled language content'); - expect(disabledHtml).not.toContain('Disabled language content'); + if (pythonEnabled) { + expect(disabledHtml).toContain('Disabled language content'); + } else { + expect(disabledHtml).not.toContain('Disabled language content'); + } }); it('renders project links only for enabled AppHost languages', async () => { + const pythonEnabled = appHostLanguageConfig.languages.find( + (language) => language.id === 'python' + )?.enabled; const enabledHtml = normalizeHtml( await renderComponent(AppHostProjectLink, { props: { id: 'typescript' }, @@ -979,7 +995,11 @@ describe('custom Astro component render coverage', () => { expect(enabledHtml).toContain('href="/app-host/typescript-apphost/"'); expect(enabledHtml).toContain('TypeScript project structure'); - expect(disabledHtml).not.toContain('href='); + if (pythonEnabled) { + expect(disabledHtml).toContain('href="/app-host/python-apphost/"'); + } else { + expect(disabledHtml).not.toContain('href='); + } expect(disabledHtml).toContain('Python project structure'); }); @@ -996,13 +1016,17 @@ describe('custom Astro component render coverage', () => { }); it('renders an explicit limitation when an enabled language has no slot', async () => { + const limitations = Object.fromEntries( + getEnabledAppHostLanguages() + .filter((language) => language.id !== 'typescript') + .map((language) => [ + language.id, + 'This example is available only in the TypeScript AppHost SDK.', + ]) + ); const html = normalizeHtml( await renderComponent(AppHostTabs, { - props: { - limitations: { - csharp: 'This example is available only in generated AppHost SDKs.', - }, - }, + props: { limitations }, slots: { typescript: '

TypeScript AppHost content

', }, @@ -1011,7 +1035,7 @@ describe('custom Astro component render coverage', () => { expect(html).toContain('data-apphost-limitation="csharp"'); expect(html).toContain('C# AppHost limitation'); - expect(html).toContain('This example is available only in generated AppHost SDKs.'); + expect(html).toContain('This example is available only in the TypeScript AppHost SDK.'); }); it('renders the canonical TypeScript tab and apphost.mts before C#', async () => { @@ -1368,7 +1392,10 @@ describe('custom Astro component render coverage', () => { expect(ctaIconIndex).toBeLessThan(ctaLabelIndex); }); - it('keeps disabled AppHost languages out of authored sample UI', async () => { + it('renders authored sample AppHost metadata according to the registry bit', async () => { + const pythonEnabled = appHostLanguageConfig.languages.find( + (language) => language.id === 'python' + )?.enabled; const disabledLanguageSample = { ...sampleDetailFixture, appHost: 'python' as const, @@ -1394,11 +1421,19 @@ describe('custom Astro component render coverage', () => { }).then(normalizeHtml), ]); - expect(cardHtml).not.toContain('data-apphost="python"'); - expect(cardHtml).not.toContain('Python AppHost'); - expect(detailHtml).not.toContain('data-apphost="python"'); - expect(detailHtml).not.toContain('Python AppHost'); - expect(detailHtml).not.toContain('disabled AppHost metadata remains ingestible'); + if (pythonEnabled) { + expect(cardHtml).toContain('data-apphost="python"'); + expect(cardHtml).toContain('Python AppHost'); + expect(detailHtml).toContain('data-apphost="python"'); + expect(detailHtml).toContain('Python AppHost'); + expect(detailHtml).toContain('disabled AppHost metadata remains ingestible'); + } else { + expect(cardHtml).not.toContain('data-apphost="python"'); + expect(cardHtml).not.toContain('Python AppHost'); + expect(detailHtml).not.toContain('data-apphost="python"'); + expect(detailHtml).not.toContain('Python AppHost'); + expect(detailHtml).not.toContain('disabled AppHost metadata remains ingestible'); + } }); it('strips emphasized first paragraphs and long emphasized labels (paragraphPlainText doubling regression)', async () => { @@ -1510,7 +1545,16 @@ describe('custom Astro component render coverage', () => { { appHostLabel } ); - expect(disabledAppHostMarkdown).not.toContain('**AppHost:**'); + const pythonEnabled = appHostLanguageConfig.languages.find( + (language) => language.id === 'python' + )?.enabled; + if (pythonEnabled) { + expect(disabledAppHostMarkdown).toContain( + '**AppHost:** Python AppHost (Experimental)' + ); + } else { + expect(disabledAppHostMarkdown).not.toContain('**AppHost:**'); + } expect(disabledAppHostMarkdown).toContain('**Tags:** python'); }); diff --git a/src/frontend/tests/unit/update-samples.vitest.test.ts b/src/frontend/tests/unit/update-samples.vitest.test.ts index 5e8e5cf89..594cbdaa6 100644 --- a/src/frontend/tests/unit/update-samples.vitest.test.ts +++ b/src/frontend/tests/unit/update-samples.vitest.test.ts @@ -1,6 +1,7 @@ import { describe, expect, test } from 'vitest'; import samples from '@data/samples.json'; +import { appHostLanguageConfig } from '@utils/apphost-languages'; import { appHostBrandColor, appHostCodeLang, @@ -142,14 +143,18 @@ describe('sample AppHost presentation metadata', () => { }); test.each([ - ['typescript', true], - ['csproj', true], - ['file-based', true], - ['python', false], - ['go', false], - ['java', false], - ['rust', false], - ] as const)('reflects registry activation for %s', (kind, enabled) => { + 'typescript', + 'csproj', + 'file-based', + 'python', + 'go', + 'java', + 'rust', + ] as const)('reflects registry activation for %s', (kind) => { + const languageId = kind === 'csproj' || kind === 'file-based' ? 'csharp' : kind; + const enabled = appHostLanguageConfig.languages.find( + (language) => language.id === languageId + )?.enabled; expect(isAppHostEnabled(kind as AppHostKind)).toBe(enabled); }); }); From 84a0c4fbe300b34833f7dae46cd0628c5fb93d70 Mon Sep 17 00:00:00 2001 From: David Pine <7679720+IEvangelist@users.noreply.github.com> Date: Wed, 9 Sep 2026 15:32:07 -0500 Subject: [PATCH 2/5] Require AppHost registry entries in tests Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../unit/apphost-languages.vitest.test.ts | 9 +++----- .../unit/custom-components.vitest.test.ts | 22 ++++++++----------- .../tests/unit/update-samples.vitest.test.ts | 6 ++--- 3 files changed, 14 insertions(+), 23 deletions(-) diff --git a/src/frontend/tests/unit/apphost-languages.vitest.test.ts b/src/frontend/tests/unit/apphost-languages.vitest.test.ts index 028c1e482..2369eb69a 100644 --- a/src/frontend/tests/unit/apphost-languages.vitest.test.ts +++ b/src/frontend/tests/unit/apphost-languages.vitest.test.ts @@ -5,6 +5,7 @@ import { fileURLToPath } from 'node:url'; import { appHostLanguageConfig, + getAppHostLanguage, getAppHostLanguageProjectHref, getEnabledAppHostLanguages, normalizeAppHostLanguage, @@ -106,18 +107,14 @@ describe('AppHost language registry', () => { expect(normalizeAppHostLanguage('javascript')).toBeUndefined(); expect(normalizeAppHostLanguage('mongo')).toBeUndefined(); expect(normalizeAppHostLanguage('python')).toBe( - appHostLanguageConfig.languages.find((language) => language.id === 'python')?.enabled - ? 'python' - : undefined + getAppHostLanguage('python').enabled ? 'python' : undefined ); expect(normalizeAppHostLanguage('python', false)).toBe('python'); }); test('publishes project links only for enabled AppHost languages', () => { expect(getAppHostLanguageProjectHref('typescript')).toBe('/app-host/typescript-apphost/'); - const pythonEnabled = appHostLanguageConfig.languages.find( - (language) => language.id === 'python' - )?.enabled; + const pythonEnabled = getAppHostLanguage('python').enabled; expect(getAppHostLanguageProjectHref('python')).toBe( pythonEnabled ? '/app-host/python-apphost/' : undefined ); diff --git a/src/frontend/tests/unit/custom-components.vitest.test.ts b/src/frontend/tests/unit/custom-components.vitest.test.ts index 84590d336..aa5377db4 100644 --- a/src/frontend/tests/unit/custom-components.vitest.test.ts +++ b/src/frontend/tests/unit/custom-components.vitest.test.ts @@ -61,7 +61,11 @@ import YouTubeCard from '@components/YouTubeCard.astro'; import YouTubeEmbed from '@components/YouTubeEmbed.astro'; import YouTubeGrid from '@components/YouTubeGrid.astro'; import samplesData from '@data/samples.json'; -import { appHostLanguageConfig, getEnabledAppHostLanguages } from '@utils/apphost-languages'; +import { + appHostLanguageConfig, + getAppHostLanguage, + getEnabledAppHostLanguages, +} from '@utils/apphost-languages'; import daTranslations from '../../src/content/i18n/da.json'; import deTranslations from '../../src/content/i18n/de.json'; import enTranslations from '../../src/content/i18n/en.json'; @@ -952,9 +956,7 @@ describe('custom Astro component render coverage', () => { }); it('renders AppHost language pivots according to the registry bit', async () => { - const pythonEnabled = appHostLanguageConfig.languages.find( - (language) => language.id === 'python' - )?.enabled; + const pythonEnabled = getAppHostLanguage('python').enabled; const enabledHtml = normalizeHtml( await renderComponent(AppHostLanguagePivot, { props: { id: 'typescript' }, @@ -977,9 +979,7 @@ describe('custom Astro component render coverage', () => { }); it('renders project links only for enabled AppHost languages', async () => { - const pythonEnabled = appHostLanguageConfig.languages.find( - (language) => language.id === 'python' - )?.enabled; + const pythonEnabled = getAppHostLanguage('python').enabled; const enabledHtml = normalizeHtml( await renderComponent(AppHostProjectLink, { props: { id: 'typescript' }, @@ -1393,9 +1393,7 @@ describe('custom Astro component render coverage', () => { }); it('renders authored sample AppHost metadata according to the registry bit', async () => { - const pythonEnabled = appHostLanguageConfig.languages.find( - (language) => language.id === 'python' - )?.enabled; + const pythonEnabled = getAppHostLanguage('python').enabled; const disabledLanguageSample = { ...sampleDetailFixture, appHost: 'python' as const, @@ -1545,9 +1543,7 @@ describe('custom Astro component render coverage', () => { { appHostLabel } ); - const pythonEnabled = appHostLanguageConfig.languages.find( - (language) => language.id === 'python' - )?.enabled; + const pythonEnabled = getAppHostLanguage('python').enabled; if (pythonEnabled) { expect(disabledAppHostMarkdown).toContain( '**AppHost:** Python AppHost (Experimental)' diff --git a/src/frontend/tests/unit/update-samples.vitest.test.ts b/src/frontend/tests/unit/update-samples.vitest.test.ts index 594cbdaa6..13b949946 100644 --- a/src/frontend/tests/unit/update-samples.vitest.test.ts +++ b/src/frontend/tests/unit/update-samples.vitest.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from 'vitest'; import samples from '@data/samples.json'; -import { appHostLanguageConfig } from '@utils/apphost-languages'; +import { getAppHostLanguage } from '@utils/apphost-languages'; import { appHostBrandColor, appHostCodeLang, @@ -152,9 +152,7 @@ describe('sample AppHost presentation metadata', () => { 'rust', ] as const)('reflects registry activation for %s', (kind) => { const languageId = kind === 'csproj' || kind === 'file-based' ? 'csharp' : kind; - const enabled = appHostLanguageConfig.languages.find( - (language) => language.id === languageId - )?.enabled; + const enabled = getAppHostLanguage(languageId).enabled; expect(isAppHostEnabled(kind as AppHostKind)).toBe(enabled); }); }); From f5be5bf275e72eaae1d32519c3749f34eb47c7e3 Mon Sep 17 00:00:00 2001 From: David Pine <7679720+IEvangelist@users.noreply.github.com> Date: Wed, 9 Sep 2026 19:24:39 -0500 Subject: [PATCH 3/5] Fix activated language E2E regressions Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/frontend/src/components/AppHostBuilder.astro | 2 +- src/frontend/src/components/PivotSelector.astro | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/frontend/src/components/AppHostBuilder.astro b/src/frontend/src/components/AppHostBuilder.astro index ff4281a66..88e0ec4ea 100644 --- a/src/frontend/src/components/AppHostBuilder.astro +++ b/src/frontend/src/components/AppHostBuilder.astro @@ -631,7 +631,7 @@ for (const language of appHostLanguageConfig.languages) { padding: 0.18rem 0.35rem; border: 1px solid color-mix(in srgb, var(--sl-color-orange) 55%, transparent); border-radius: 999px; - color: var(--sl-color-orange-high); + color: inherit; background: color-mix(in srgb, var(--sl-color-orange-low) 70%, transparent); font-size: 0.62rem; font-weight: 700; diff --git a/src/frontend/src/components/PivotSelector.astro b/src/frontend/src/components/PivotSelector.astro index 007c785f6..beff449bf 100644 --- a/src/frontend/src/components/PivotSelector.astro +++ b/src/frontend/src/components/PivotSelector.astro @@ -82,6 +82,8 @@ const renderedOptions =