diff --git a/frontend/security-utils.js b/frontend/security-utils.js index f3019175..6f9e6a8d 100644 --- a/frontend/security-utils.js +++ b/frontend/security-utils.js @@ -233,6 +233,35 @@ ); } + /** Shared empty-state renderer — consistent messaging across all result lists. */ + function buildEmptyStateHtml(message, { tag = 'ok' } = {}) { + return `${escHtml(message)}`; + } + + /** Renders a list of issue cards, or a consistent empty state if none exist. */ + function buildIssuesListHtml(issues, { emptyMessage = '✓ No issues found. Code looks clean!' } = {}) { + if (!Array.isArray(issues) || issues.length === 0) { + return buildEmptyStateHtml(emptyMessage); + } + return issues.map(buildIssueCardHtml).join(''); + } + + /** Renders a list of suggestion cards, or a consistent empty state if none exist. */ + function buildSuggestionsListHtml(suggestions, { emptyMessage = 'No suggestions available.' } = {}) { + if (!Array.isArray(suggestions) || suggestions.length === 0) { + return buildEmptyStateHtml(emptyMessage); + } + return suggestions.map(buildSuggestCardHtml).join(''); + } + + /** Renders a stored history/favorites list, or a consistent empty state if none exist. */ + function buildStoredListHtml(entries, loaderOpts, { emptyMessage = 'No entries yet.' } = {}) { + if (!Array.isArray(entries) || entries.length === 0) { + return `
${escHtml(emptyMessage)}
`; + } + return entries.map((e) => buildStoredListItemHtml(e, loaderOpts)).join(''); + } + global.QyverixSecurity = { ALLOWED_PRIORITIES, ALLOWED_SEVERITIES, @@ -256,5 +285,9 @@ bindKeyboardShortcuts, buildIssueCardHtml, buildSuggestCardHtml, + buildEmptyStateHtml, + buildIssuesListHtml, + buildSuggestionsListHtml, + buildStoredListHtml, }; })(typeof window !== 'undefined' ? window : globalThis); diff --git a/frontend/tests/security-utils.test.mjs b/frontend/tests/security-utils.test.mjs index a0863976..25ee4c5f 100644 --- a/frontend/tests/security-utils.test.mjs +++ b/frontend/tests/security-utils.test.mjs @@ -301,3 +301,60 @@ describe('normal code preserved', () => { }); } }); + +describe('empty-state rendering', () => { + it('buildEmptyStateHtml escapes the message and applies a safe tag', () => { + const html = SEC.buildEmptyStateHtml(''); + assertPlainTextHtml(html, 'empty state'); + assert.ok(html.includes('tag-ok')); + }); + + it('buildEmptyStateHtml falls back to "ok" tag for unsafe tag input', () => { + const html = SEC.buildEmptyStateHtml('done', { tag: '