All files / generators swot-content.ts

100% Statements 18/18
100% Branches 14/14
100% Functions 4/4
100% Lines 16/16

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147                                                            134x 134x     134x                                     88x 134x   88x                                                             25x     23x         23x   22x 22x   25x           25x           25x           25x             25x                                          
// SPDX-FileCopyrightText: 2024-2026 Hack23 AB
// SPDX-License-Identifier: Apache-2.0
 
/**
 * @module Generators/SwotContent
 * @description Pure functions for building SWOT analysis HTML sections.
 * Generates accessible, CSS-only SWOT matrix visualizations that can be
 * embedded in any article type. Designed for agentic workflows to extend
 * articles with strategic analysis using data from MCP servers or other sources.
 *
 * The SWOT framework covers:
 * - **Strengths**: Internal positive factors
 * - **Weaknesses**: Internal negative factors
 * - **Opportunities**: External positive factors
 * - **Threats**: External negative factors
 */
 
import { escapeHTML } from '../utils/file-utils.js';
import { getLocalizedString, SWOT_STRINGS } from '../constants/languages.js';
import type { SwotAnalysis, SwotItem, SwotStrings } from '../types/index.js';
 
// ─── Sub-section builders ────────────────────────────────────────────────────
 
/**
 * Build a single SWOT item HTML.
 *
 * @param item - SWOT item with text and optional severity
 * @returns HTML list item string
 */
function buildSwotItem(item: SwotItem): string {
  const severityClass = item.severity ? ` swot-severity-${escapeHTML(item.severity)}` : '';
  const severityBadge = item.severity
    ? ` <span class="swot-severity-badge" role="img" aria-label="${escapeHTML(item.severity)}"></span>`
    : '';
  return `<li class="swot-item${severityClass}">${escapeHTML(item.text)}${severityBadge}</li>`;
}
 
/**
 * Build a single SWOT quadrant HTML.
 *
 * @param quadrantKey - Quadrant identifier (strengths/weaknesses/opportunities/threats)
 * @param items - Items in this quadrant
 * @param label - Localized quadrant heading
 * @param description - Accessible description
 * @returns HTML string for one quadrant
 */
function buildSwotQuadrant(
  quadrantKey: string,
  items: readonly SwotItem[],
  label: string,
  description: string
): string {
  const itemsHtml =
    items.length > 0
      ? items.map((item) => buildSwotItem(item)).join('\n                ')
      : '<li class="swot-item swot-empty">—</li>';
  return `<div class="swot-quadrant swot-${escapeHTML(quadrantKey)}" role="region" aria-label="${escapeHTML(label)}">
              <h4 class="swot-quadrant-heading">${escapeHTML(label)}</h4>
              <p class="swot-quadrant-desc">${escapeHTML(description)}</p>
              <ul class="swot-list">
                ${itemsHtml}
              </ul>
            </div>`;
}
 
// ─── Main builder ────────────────────────────────────────────────────────────
 
/**
 * Build a complete SWOT analysis section HTML.
 *
 * Generates an accessible CSS Grid-based SWOT matrix that can be embedded
 * in any article type. The visualization is pure HTML/CSS with no JavaScript
 * required, ensuring compatibility with strict CSP policies.
 *
 * Returns an empty string if the analysis is null/undefined or contains
 * no items in any quadrant.
 *
 * @param analysis - SWOT analysis data (null/undefined returns empty string)
 * @param lang - BCP 47 language code for localized headings (defaults to "en")
 * @param heading - Optional custom section heading override
 * @returns HTML section string or empty string
 */
export function buildSwotSection(
  analysis: SwotAnalysis | null | undefined,
  lang: string = 'en',
  heading?: string
): string {
  if (!analysis) return '';
 
  const totalItems =
    analysis.strengths.length +
    analysis.weaknesses.length +
    analysis.opportunities.length +
    analysis.threats.length;
 
  if (totalItems === 0) return '';
 
  const strings: SwotStrings = getLocalizedString(SWOT_STRINGS, lang);
  const sectionHeading = heading ?? analysis.title ?? strings.sectionHeading;
 
  const strengthsHtml = buildSwotQuadrant(
    'strengths',
    analysis.strengths,
    strings.strengthsLabel,
    strings.strengthsDesc
  );
  const weaknessesHtml = buildSwotQuadrant(
    'weaknesses',
    analysis.weaknesses,
    strings.weaknessesLabel,
    strings.weaknessesDesc
  );
  const opportunitiesHtml = buildSwotQuadrant(
    'opportunities',
    analysis.opportunities,
    strings.opportunitiesLabel,
    strings.opportunitiesDesc
  );
  const threatsHtml = buildSwotQuadrant(
    'threats',
    analysis.threats,
    strings.threatsLabel,
    strings.threatsDesc
  );
 
  return `
          <section class="swot-analysis" role="region" aria-label="${escapeHTML(sectionHeading)}">
            <h2>${escapeHTML(sectionHeading)}</h2>
            <div class="swot-matrix">
              <div class="swot-axis-labels">
                <span class="swot-axis-internal">${escapeHTML(strings.internalLabel)}</span>
                <span class="swot-axis-external">${escapeHTML(strings.externalLabel)}</span>
              </div>
              <div class="swot-grid">
                <div class="swot-row swot-row-positive">
                  ${strengthsHtml}
                  ${opportunitiesHtml}
                </div>
                <div class="swot-row swot-row-negative">
                  ${weaknessesHtml}
                  ${threatsHtml}
                </div>
              </div>
            </div>
          </section>`;
}