Skip to content

Enable coverage all:true, fix Vitest 4 threshold format, migrate fix-article-navigation to TypeScript#697

Merged
pethers merged 13 commits intomainfrom
copilot/enable-coverage-tracking
Mar 1, 2026
Merged

Enable coverage all:true, fix Vitest 4 threshold format, migrate fix-article-navigation to TypeScript#697
pethers merged 13 commits intomainfrom
copilot/enable-coverage-tracking

Conversation

Copy link
Contributor

Copilot AI commented Mar 1, 2026

  • Enable all: true in vitest.config.js coverage configuration
  • Fix coverage threshold format (moved to proper thresholds sub-object for Vitest 4)
  • Adjust threshold values to match real baseline (25/20/25/25) with target documented
  • Migrate scripts/fix-article-navigation.py to scripts/fix-article-navigation.ts
  • Fix shebang: #!/usr/bin/env node#!/usr/bin/env -S npx tsx
  • Fix summary label: "Language switchers added/updated" → "Language switchers added"
  • Update all 9 workflow .md files to use npx tsx scripts/fix-article-navigation.ts
  • Remove locally-duplicated language constants; import shared modules from scripts/article-template/
  • Fix processArticle() to also repair article-top-nav blocks missing back-to-news
  • Extract pure transformContent() and add tests/fix-article-navigation.test.ts (16 tests covering switcher insertion, 14-language generation, active-language marking, skip-link ordering, Pattern A/B top-nav insertion, missing back-to-news repair, and idempotency)
  • Fix baseSlug JSDoc: "2026-01-01-article" (without news/ prefix)
  • Use SLUG = '2026-01-01-test' (no news/ prefix) in tests to match production discoverArticles() format
  • Replace fragile exact-attribute assertions with order-independent regex checks for active/aria-current
  • Preserve accessibility: insert language switcher after skip-link (when present) so skip-link remains the first focusable element after <body>
  • All 56 test files, 2277 tests pass
Original prompt

This section details on the original issue you should resolve

<issue_title>Enable comprehensive coverage tracking with all: true and identify untested code</issue_title>
<issue_description>## 📋 Issue Type
Quality / Testing

🎯 Objective

Enable Vitest coverage all: true mode to track coverage across all source files, not just those imported by tests, and achieve measurable improvement in untested code paths.

📊 Current State

In vitest.config.js:

// Disabled: tests don't import dashboard modules (by design)
all: false,

With all: false, coverage reports only include files that are actually imported by test files. This means files that have zero test coverage don't appear in reports at all, hiding potential quality gaps.

Current thresholds: 70% lines, 70% functions, 60% branches, 70% statements.

Many browser dashboard files are excluded from coverage:

  • js/anomaly-detection-dashboard.js
  • js/election-cycle-dashboard.js
  • js/ministry-dashboard.js
  • js/party-dashboard.js
  • js/politician-dashboard.js
  • js/pre-election-dashboard.js
  • js/seasonal-patterns-dashboard.js
  • Several scripts/ and dashboard/ files

🚀 Desired State

  • all: true enabled in Vitest coverage configuration
  • Coverage reports include all source files, showing true project coverage
  • Critical untested modules identified and prioritized for testing
  • Coverage thresholds adjusted if needed to avoid blocking during transition

🔧 Implementation Approach

  1. Set all: true in vitest.config.js
  2. Run coverage to assess the real baseline
  3. Review coverage report to identify critical untested paths
  4. Prioritize testing for modules with security or data integrity impact
  5. Add structural tests for dashboard modules (testing DOM output, not Chart.js rendering)
  6. Incrementally raise thresholds as coverage improves

🤖 Recommended Agent

test-specialist — Test coverage improvement

✅ Acceptance Criteria

  • all: true enabled in Vitest config
  • Coverage report includes all source files
  • Real baseline coverage documented
  • No existing tests broken
  • Plan for improving untested critical paths

📚 References

  • Config: vitest.config.js (coverage section)
  • Tests: tests/ directory (53 test files)
  • Docs: TESTING.md

🏷️ Labels

type:testing, priority:medium, component:testing</issue_description>

<agent_instructions>Migrate Python scripts to TypeScript. Start with fix-article-navigation.py since it's used in workflows and article-quality-enhancer.ts already does similar work. Ensure idempotent operation and --dry-run flag support.</agent_instructions>

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

…ation to TypeScript

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Copilot AI changed the title [WIP] Enable comprehensive coverage tracking with all: true Enable coverage all:true, fix Vitest 4 threshold format, migrate fix-article-navigation to TypeScript Mar 1, 2026
@pethers pethers requested a review from Copilot March 1, 2026 02:59
@github-actions github-actions bot added documentation Documentation updates javascript JavaScript code changes workflow GitHub Actions workflows ci-cd CI/CD pipeline changes testing Test coverage refactor Code refactoring news News articles and content generation agentic-workflow Agentic workflow changes size-l Large change (250-1000 lines) labels Mar 1, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves test quality signals by enabling comprehensive Vitest coverage collection and fixing Vitest 4 threshold configuration, and it ports the article navigation “fix” utility from Python to TypeScript while updating workflow documentation to reference the new TS fallback command.

Changes:

  • Enable coverage.all: true and move coverage thresholds to coverage.thresholds in vitest.config.js (Vitest 4 format).
  • Add scripts/fix-article-navigation.ts (TypeScript port of the existing Python fixer).
  • Update workflow documentation examples to use npx tsx scripts/fix-article-navigation.ts instead of the Python script.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
vitest.config.js Enables full-file coverage reporting and fixes threshold config format for Vitest 4.
scripts/fix-article-navigation.ts Adds a TS implementation of the navigation fixer (language switcher + article-top-nav).
.github/workflows/news-weekly-review.md Updates fallback command reference to TS script.
.github/workflows/news-week-ahead.md Updates fallback command reference to TS script.
.github/workflows/news-propositions.md Updates fallback command reference to TS script.
.github/workflows/news-motions.md Updates fallback command reference to TS script.
.github/workflows/news-monthly-review.md Updates fallback command reference to TS script.
.github/workflows/news-month-ahead.md Updates fallback command reference to TS script.
.github/workflows/news-evening-analysis.md Updates script table entry to TS script.
.github/workflows/news-committee-reports.md Updates fallback command reference to TS script.
.github/workflows/news-article-generator.md Updates script table entry to TS script (including --dry-run).


console.log('=== Summary ===');
console.log(`Total files processed: ${total}`);
console.log(`Language switchers added/updated: ${switchersAdded}`);
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The summary prints “Language switchers added/updated”, but the counter is incremented only when a switcher is newly added (not when it’s updated/replaced). Either adjust the counter to include updates or change the summary label so the reported numbers are accurate.

Suggested change
console.log(`Language switchers added/updated: ${switchersAdded}`);
console.log(`Language switchers added: ${switchersAdded}`);

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 41bce96 — label changed to Language switchers added: to match what the counter actually tracks.

Comment on lines +32 to +40
// Coverage thresholds — intentionally low during transition while all:true
// exposes previously-hidden zero-coverage files. Raise incrementally as
// tests are added for the dashboard and browser modules.
// Target thresholds (raise as coverage improves): lines:70, functions:70, branches:60, statements:70
thresholds: {
lines: 25,
functions: 20,
branches: 25,
statements: 25,
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment block updates the coverage thresholds, but the rationale references “all:true” transition; please keep the comment and the numeric thresholds in sync if the baseline changes, otherwise it’s easy for thresholds to drift from reality.

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,241 @@
#!/usr/bin/env node
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file is intended to be executed via npx tsx ..., but it has a #!/usr/bin/env node shebang. If someone runs the script directly (e.g., makes it executable and calls it), Node will try to execute TypeScript and fail. Align the shebang with the documented execution method (e.g., use a tsx-compatible shebang) or remove the shebang to avoid accidental misuse.

Suggested change
#!/usr/bin/env node
#!/usr/bin/env -S npx tsx

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 41bce96 — shebang updated to #!/usr/bin/env -S npx tsx.

Signed-off-by: James Pether Sörling <pethers@users.noreply.github.com>
@pethers
Copy link
Member

pethers commented Mar 1, 2026

@copilot apply changes based on the comments in this thread

@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

…tion.ts

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.

* with a back-to-news link are present. Idempotent — safe to call multiple times.
*
* @param content Original HTML string
* @param baseSlug Article base slug (e.g. "news/2026-01-01-article")
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The baseSlug parameter doc says it’s like "news/2026-01-01-article", but generateArticleLanguageSwitcher() expects a base slug without the news/ prefix (e.g. "2026-02-13-evening-analysis") and discoverArticles() currently produces that form. Updating this comment to reflect the real/required baseSlug format will help avoid introducing broken relative links (e.g. news/... resolving to news/news/...).

Suggested change
* @param baseSlug Article base slug (e.g. "news/2026-01-01-article")
* @param baseSlug Article base slug without "news/" prefix (e.g. "2026-01-01-article")

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 629e127 — JSDoc updated to "2026-01-01-article" (no news/ prefix).

Comment on lines 119 to 150
describe('transformContent — language switcher', () => {
it('inserts language-switcher immediately after <body>', () => {
const { content } = transformContent(BARE_ARTICLE, SLUG, 'en');
// The switcher nav must appear right after <body> (possibly with a newline)
expect(content).toMatch(/<body>\s*<nav class="language-switcher"/);
});

it('adds language-switcher when missing', () => {
const { content, addedSwitcher } = transformContent(BARE_ARTICLE, SLUG, 'en');
expect(addedSwitcher).toBe(true);
expect(content).toContain('language-switcher');
expect(content).toContain(`href="${SLUG}-en.html"`);
expect(content).toContain(`href="${SLUG}-sv.html"`);
});

it('does not set addedSwitcher when switcher is already present', () => {
const { addedSwitcher } = transformContent(buildCompleteArticle(SLUG), SLUG, 'en');
expect(addedSwitcher).toBe(false);
});

it('generates all 14 language links', () => {
const { content } = transformContent(BARE_ARTICLE, SLUG, 'en');
for (const lang of ['en', 'sv', 'da', 'no', 'fi', 'de', 'fr', 'es', 'nl', 'ar', 'he', 'ja', 'ko', 'zh']) {
expect(content).toContain(`hreflang="${lang}"`);
}
});

it('marks current language as active with aria-current', () => {
const { content } = transformContent(BARE_ARTICLE, SLUG, 'sv');
expect(content).toContain('aria-current="page"');
expect(content).toContain(`href="${SLUG}-sv.html" class="lang-link active"`);
expect(content).not.toContain(`href="${SLUG}-en.html" class="lang-link active"`);
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests use SLUG = 'news/...' and assert href="${SLUG}-xx.html", but the fixer discovers base slugs from filenames (e.g. 2026-02-13-evening-analysis) and the real article switchers use relative filenames without a news/ prefix. To keep tests representative of production usage and avoid masking path-related bugs, use a base slug without news/ and update the expectations accordingly.

Suggested change
describe('transformContent — language switcher', () => {
it('inserts language-switcher immediately after <body>', () => {
const { content } = transformContent(BARE_ARTICLE, SLUG, 'en');
// The switcher nav must appear right after <body> (possibly with a newline)
expect(content).toMatch(/<body>\s*<nav class="language-switcher"/);
});
it('adds language-switcher when missing', () => {
const { content, addedSwitcher } = transformContent(BARE_ARTICLE, SLUG, 'en');
expect(addedSwitcher).toBe(true);
expect(content).toContain('language-switcher');
expect(content).toContain(`href="${SLUG}-en.html"`);
expect(content).toContain(`href="${SLUG}-sv.html"`);
});
it('does not set addedSwitcher when switcher is already present', () => {
const { addedSwitcher } = transformContent(buildCompleteArticle(SLUG), SLUG, 'en');
expect(addedSwitcher).toBe(false);
});
it('generates all 14 language links', () => {
const { content } = transformContent(BARE_ARTICLE, SLUG, 'en');
for (const lang of ['en', 'sv', 'da', 'no', 'fi', 'de', 'fr', 'es', 'nl', 'ar', 'he', 'ja', 'ko', 'zh']) {
expect(content).toContain(`hreflang="${lang}"`);
}
});
it('marks current language as active with aria-current', () => {
const { content } = transformContent(BARE_ARTICLE, SLUG, 'sv');
expect(content).toContain('aria-current="page"');
expect(content).toContain(`href="${SLUG}-sv.html" class="lang-link active"`);
expect(content).not.toContain(`href="${SLUG}-en.html" class="lang-link active"`);
const BASE_SLUG = SLUG.replace(/^news\//, '');
describe('transformContent — language switcher', () => {
it('inserts language-switcher immediately after <body>', () => {
const { content } = transformContent(BARE_ARTICLE, BASE_SLUG, 'en');
// The switcher nav must appear right after <body> (possibly with a newline)
expect(content).toMatch(/<body>\s*<nav class="language-switcher"/);
});
it('adds language-switcher when missing', () => {
const { content, addedSwitcher } = transformContent(BARE_ARTICLE, BASE_SLUG, 'en');
expect(addedSwitcher).toBe(true);
expect(content).toContain('language-switcher');
expect(content).toContain(`href="${BASE_SLUG}-en.html`);
expect(content).toContain(`href="${BASE_SLUG}-sv.html`);
});
it('does not set addedSwitcher when switcher is already present', () => {
const { addedSwitcher } = transformContent(buildCompleteArticle(BASE_SLUG), BASE_SLUG, 'en');
expect(addedSwitcher).toBe(false);
});
it('generates all 14 language links', () => {
const { content } = transformContent(BARE_ARTICLE, BASE_SLUG, 'en');
for (const lang of ['en', 'sv', 'da', 'no', 'fi', 'de', 'fr', 'es', 'nl', 'ar', 'he', 'ja', 'ko', 'zh']) {
expect(content).toContain(`hreflang="${lang}"`);
}
});
it('marks current language as active with aria-current', () => {
const { content } = transformContent(BARE_ARTICLE, BASE_SLUG, 'sv');
expect(content).toContain('aria-current="page"');
expect(content).toContain(`href="${BASE_SLUG}-sv.html" class="lang-link active"`);
expect(content).not.toContain(`href="${BASE_SLUG}-en.html" class="lang-link active"`);

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 629e127SLUG changed to '2026-01-01-test' (bare slug, matching the form produced by discoverArticles()). All fixtures and buildCompleteArticle updated accordingly.

Comment on lines 148 to 150
expect(content).toContain('aria-current="page"');
expect(content).toContain(`href="${SLUG}-sv.html" class="lang-link active"`);
expect(content).not.toContain(`href="${SLUG}-en.html" class="lang-link active"`);
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few assertions are sensitive to exact attribute ordering/spacing in the generated <a> tags (e.g. checking for the full substring href="..." class="lang-link active"). If generateArticleLanguageSwitcher() changes attribute order without changing semantics, these tests will fail. Consider using a regex/assertion that checks href, active class, and aria-current independently to make the tests resilient to harmless formatting changes.

Suggested change
expect(content).toContain('aria-current="page"');
expect(content).toContain(`href="${SLUG}-sv.html" class="lang-link active"`);
expect(content).not.toContain(`href="${SLUG}-en.html" class="lang-link active"`);
// At least one link must indicate the current page
expect(content).toContain('aria-current="page"');
// Find the <a> element for the current language (sv)
const svLinkMatch = content.match(new RegExp(`<a[^>]*href="${SLUG}-sv.html"[^>]*>`));
expect(svLinkMatch).not.toBeNull();
const svLinkTag = svLinkMatch![0];
// It must have both lang-link and active classes (in any order) and aria-current="page"
expect(svLinkTag).toMatch(/class="[^"]*\blang-link\b[^"]*\bactive\b[^"]*"/);
expect(svLinkTag).toMatch(/aria-current="page"/);
// Find the <a> element for a non-current language (en)
const enLinkMatch = content.match(new RegExp(`<a[^>]*href="${SLUG}-en.html"[^>]*>`));
expect(enLinkMatch).not.toBeNull();
const enLinkTag = enLinkMatch![0];
// It must not be marked as active and must not have aria-current
expect(enLinkTag).not.toMatch(/\bactive\b/);
expect(enLinkTag).not.toMatch(/aria-current/);

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 629e127 — the marks current language as active test now extracts the <a> tag via regex and asserts lang-link, active, and aria-current independently, so harmless attribute-order changes won't break it.

@pethers
Copy link
Member

pethers commented Mar 1, 2026

@copilot apply changes based on the comments in this thread

…ink assertions

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.

const hasSwitcher = result.includes('language-switcher');
if (!hasSwitcher) {
const switcherHtml = generateArticleLanguageSwitcher(baseSlug, lang);
result = result.replace(/(<body>)/, `$1\n${switcherHtml}`);
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transformContent() inserts the language switcher immediately after <body>. In real news articles, the skip-link (<a ... class="skip-link">) is expected to be the first focusable element after <body> (see scripts/article-template/template.ts). Inserting the switcher before the skip-link will change keyboard navigation behavior and reduces accessibility. Consider inserting the switcher after the skip-link when it exists (and only falling back to after <body> when it doesn’t).

Suggested change
result = result.replace(/(<body>)/, `$1\n${switcherHtml}`);
// Prefer inserting AFTER the skip-link (<a class="skip-link">) when present,
// so the skip-link remains the first focusable element after <body>.
const bodyAndSkipLinkPattern =
/(<body[^>]*>\s*(?:<!--[\s\S]*?-->[\s]*)?)(<a[^>]*class="skip-link"[^>]*>[\s\S]*?<\/a>)/;
if (bodyAndSkipLinkPattern.test(result)) {
result = result.replace(
bodyAndSkipLinkPattern,
`$1$2\n${switcherHtml}`,
);
} else {
// Fallback: insert immediately after <body> when no skip-link exists.
result = result.replace(/(<body[^>]*>)/, `$1\n${switcherHtml}`);
}

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6afac64. When a skip-link is present, the switcher is now inserted after it (preserving it as the first focusable element). Falls back to inserting right after <body> when no skip-link exists. Added a test covering both cases.

@pethers
Copy link
Member

pethers commented Mar 1, 2026

@copilot apply changes based on the comments in this thread

@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

…v order

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.

@pethers pethers marked this pull request as ready for review March 1, 2026 13:28
@pethers pethers merged commit 68dfbdc into main Mar 1, 2026
19 checks passed
@pethers pethers deleted the copilot/enable-coverage-tracking branch March 1, 2026 13:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agentic-workflow Agentic workflow changes ci-cd CI/CD pipeline changes documentation Documentation updates javascript JavaScript code changes news News articles and content generation refactor Code refactoring size-l Large change (250-1000 lines) testing Test coverage workflow GitHub Actions workflows

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enable comprehensive coverage tracking with all: true and identify untested code

3 participants