Some checks failed
CI/CD / detect-changes (pull_request) Successful in 4s
CI/CD / build-and-deploy (pull_request) Has been cancelled
CI/CD / test-sqlite (pull_request) Has been cancelled
CI/CD / test-go-pg (pull_request) Has been cancelled
CI/CD / test-frontend (pull_request) Has been cancelled
Prefix enforcement prevents extension CSS from leaking into the kernel
or sibling extensions. All 12 in-tree packages migrated to .ext-{slug}-*
naming convention.
- Add data-ext attribute to extension mount container
- Add CSS linter (scripts/lint-package-css.sh) enforcing .ext-{slug} prefix
- Add kernel CSS contract doc (docs/EXTENSION-CSS.md)
- Migrate 12 packages: chat, dashboard, editor, git-board, hello-dashboard,
icd-test-runner, notes, schedules, sdk-test-runner, tasks,
team-activity-log, workflow-demo (CSS + JS in lockstep)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
263 lines
9.2 KiB
JavaScript
263 lines
9.2 KiB
JavaScript
/**
|
|
* SDK Test Runner — UI
|
|
*
|
|
* Renders: summary bar, domain filter, live results table, export.
|
|
* Follows ICD runner visual conventions.
|
|
*/
|
|
(function () {
|
|
'use strict';
|
|
var T = window.SDKR;
|
|
if (!T) return;
|
|
|
|
// ─── Verdict Colors ────────────────────────────────────────
|
|
|
|
var COLORS = {
|
|
PASS: 'var(--success, #22c55e)',
|
|
SDK_BUG: 'var(--warning, #f59e0b)',
|
|
ICD_BUG: 'var(--danger, #ef4444)',
|
|
SHAPE_BUG: '#a855f7',
|
|
SKIP: 'var(--text3, #888)',
|
|
ERROR: 'var(--danger, #ef4444)'
|
|
};
|
|
|
|
var LABELS = {
|
|
PASS: 'PASS',
|
|
SDK_BUG: 'SDK BUG',
|
|
ICD_BUG: 'ICD BUG',
|
|
SHAPE_BUG: 'SHAPE',
|
|
SKIP: 'SKIP',
|
|
ERROR: 'ERROR'
|
|
};
|
|
|
|
// ─── DOM Helpers ──────────────────────────────────────────
|
|
|
|
function el(tag, attrs, children) {
|
|
var e = document.createElement(tag);
|
|
if (attrs) Object.keys(attrs).forEach(function (k) {
|
|
if (k === 'className') e.className = attrs[k];
|
|
else if (k === 'style' && typeof attrs[k] === 'object') Object.assign(e.style, attrs[k]);
|
|
else if (k.indexOf('on') === 0) e.addEventListener(k.slice(2).toLowerCase(), attrs[k]);
|
|
else e.setAttribute(k, attrs[k]);
|
|
});
|
|
if (children) {
|
|
if (!Array.isArray(children)) children = [children];
|
|
children.forEach(function (c) {
|
|
if (typeof c === 'string') e.appendChild(document.createTextNode(c));
|
|
else if (c) e.appendChild(c);
|
|
});
|
|
}
|
|
return e;
|
|
}
|
|
|
|
function esc(s) { return String(s || ''); }
|
|
|
|
// ─── Shell ────────────────────────────────────────────────
|
|
|
|
T.renderShell = function () {
|
|
var mount = T.mount;
|
|
mount.innerHTML = '';
|
|
|
|
// Header
|
|
var header = el('div', { className: 'ext-sdk-test-runner-header' }, [
|
|
el('h1', {}, 'SDK Test Runner'),
|
|
el('span', { className: 'ext-sdk-test-runner-version' }, 'v' + (T.manifest.version || '?'))
|
|
]);
|
|
mount.appendChild(header);
|
|
|
|
// Description
|
|
mount.appendChild(el('p', { className: 'ext-sdk-test-runner-desc' },
|
|
'Dual-path validation: SDK call → raw ICD fetch. ' +
|
|
'SDK fail + ICD pass = SDK bug. Both fail = ICD bug.'));
|
|
|
|
// ── Fixtures Panel ──
|
|
var fixturePanel = el('div', { className: 'ext-sdk-test-runner-fixtures' });
|
|
|
|
var fixRow = el('div', { className: 'ext-sdk-test-runner-fixture-row' });
|
|
fixRow.appendChild(el('span', { className: 'ext-sdk-test-runner-fixture-label' }, 'FIXTURES:'));
|
|
|
|
// Provider dropdown
|
|
var provSelect = el('select', { className: 'ext-sdk-test-runner-select' });
|
|
provSelect.appendChild(el('option', { value: '' }, '— provider —'));
|
|
['openrouter', 'openai', 'anthropic', 'venice'].forEach(function (p) {
|
|
provSelect.appendChild(el('option', { value: p }, p));
|
|
});
|
|
fixRow.appendChild(provSelect);
|
|
|
|
// API key input
|
|
var keyInput = el('input', {
|
|
type: 'password', className: 'ext-sdk-test-runner-input',
|
|
placeholder: 'API key (optional — enables model tests)'
|
|
});
|
|
fixRow.appendChild(keyInput);
|
|
|
|
// Fixture status
|
|
var fixStatus = el('span', { className: 'ext-sdk-test-runner-fixture-status' }, '');
|
|
|
|
fixRow.appendChild(fixStatus);
|
|
fixturePanel.appendChild(fixRow);
|
|
|
|
var fixNote = el('div', { className: 'ext-sdk-test-runner-fixture-note' },
|
|
'Provide a provider + API key to unlock embedding/model tests. ' +
|
|
'Without a key, those tests are skipped. Key is never stored.');
|
|
fixturePanel.appendChild(fixNote);
|
|
|
|
mount.appendChild(fixturePanel);
|
|
|
|
// ── Fixture wiring: store user input for provisionFixtures to use ──
|
|
T._fixtureInput = { provSelect: provSelect, keyInput: keyInput, statusEl: fixStatus };
|
|
|
|
// Controls row
|
|
var controlRow = el('div', { className: 'ext-sdk-test-runner-controls' });
|
|
|
|
// Domain filter checkboxes
|
|
var filterBox = el('div', { className: 'ext-sdk-test-runner-filters' });
|
|
filterBox.appendChild(el('span', { className: 'ext-sdk-test-runner-filter-label' }, 'Domains: '));
|
|
var domainNames = Object.keys(T.domains).sort();
|
|
var domainChecks = {};
|
|
|
|
domainNames.forEach(function (name) {
|
|
var label = el('label', { className: 'ext-sdk-test-runner-check' });
|
|
var cb = el('input', { type: 'checkbox', checked: 'checked' });
|
|
domainChecks[name] = cb;
|
|
label.appendChild(cb);
|
|
label.appendChild(document.createTextNode(' ' + name));
|
|
filterBox.appendChild(label);
|
|
});
|
|
controlRow.appendChild(filterBox);
|
|
|
|
// Buttons
|
|
var btnRow = el('div', { className: 'ext-sdk-test-runner-btn-row' });
|
|
var runBtn = el('button', { className: 'ext-sdk-test-runner-btn ext-sdk-test-runner-btn-primary', onClick: onRun }, 'Run All');
|
|
var abortBtn = el('button', { className: 'ext-sdk-test-runner-btn ext-sdk-test-runner-btn-danger', onClick: function () { T.abort(); } }, 'Abort');
|
|
abortBtn.style.display = 'none';
|
|
var exportBtn = el('button', { className: 'ext-sdk-test-runner-btn', onClick: onExport }, 'Export JSON');
|
|
btnRow.appendChild(runBtn);
|
|
btnRow.appendChild(abortBtn);
|
|
btnRow.appendChild(exportBtn);
|
|
controlRow.appendChild(btnRow);
|
|
mount.appendChild(controlRow);
|
|
|
|
// Summary bar
|
|
var summaryBar = el('div', { className: 'ext-sdk-test-runner-summary' });
|
|
mount.appendChild(summaryBar);
|
|
|
|
// Results table
|
|
var tableWrap = el('div', { className: 'ext-sdk-test-runner-table-wrap' });
|
|
var table = el('table', { className: 'ext-sdk-test-runner-table' });
|
|
var thead = el('thead', {}, [
|
|
el('tr', {}, [
|
|
el('th', {}, '#'),
|
|
el('th', {}, 'Domain'),
|
|
el('th', {}, 'Group'),
|
|
el('th', {}, 'Test'),
|
|
el('th', {}, 'Verdict'),
|
|
el('th', {}, 'ms'),
|
|
el('th', {}, 'Detail')
|
|
])
|
|
]);
|
|
table.appendChild(thead);
|
|
var tbody = el('tbody', {});
|
|
table.appendChild(tbody);
|
|
tableWrap.appendChild(table);
|
|
mount.appendChild(tableWrap);
|
|
|
|
// ── Callbacks ──
|
|
|
|
function updateSummary() {
|
|
var s = T.stats;
|
|
var total = T.results.length;
|
|
summaryBar.innerHTML = '';
|
|
var items = [
|
|
['PASS', s.pass],
|
|
['SDK_BUG', s.sdk_bug],
|
|
['ICD_BUG', s.icd_bug],
|
|
['SHAPE_BUG', s.shape_bug],
|
|
['SKIP', s.skip]
|
|
];
|
|
items.forEach(function (item) {
|
|
var badge = el('span', {
|
|
className: 'ext-sdk-test-runner-badge',
|
|
style: {
|
|
background: item[1] > 0 ? COLORS[item[0]] : 'var(--bg2, #333)',
|
|
color: item[1] > 0 ? '#fff' : 'var(--text3, #888)'
|
|
}
|
|
}, LABELS[item[0]] + ': ' + item[1]);
|
|
summaryBar.appendChild(badge);
|
|
});
|
|
summaryBar.appendChild(el('span', { className: 'ext-sdk-test-runner-badge',
|
|
style: { background: 'var(--bg2, #333)' }
|
|
}, 'Total: ' + total));
|
|
}
|
|
|
|
T.onResult = function (entry) {
|
|
var idx = T.results.length;
|
|
var v = entry.verdict;
|
|
var detail = '';
|
|
if (entry.sdkErr) detail += 'SDK: ' + esc(entry.sdkErr);
|
|
if (entry.rawErr) detail += (detail ? ' | ' : '') + 'ICD: ' + esc(entry.rawErr);
|
|
if (entry.note) detail += (detail ? ' | ' : '') + esc(entry.note);
|
|
|
|
var row = el('tr', { className: 'ext-sdk-test-runner-row ext-sdk-test-runner-row-' + v.toLowerCase() }, [
|
|
el('td', {}, String(idx)),
|
|
el('td', {}, esc(entry.domain)),
|
|
el('td', {}, esc(entry.group)),
|
|
el('td', { className: 'ext-sdk-test-runner-test-name' }, esc(entry.name)),
|
|
el('td', {}, [
|
|
el('span', {
|
|
className: 'ext-sdk-test-runner-verdict',
|
|
style: { color: COLORS[v] || '#fff' }
|
|
}, LABELS[v] || v)
|
|
]),
|
|
el('td', { className: 'ext-sdk-test-runner-ms' }, String(entry.durationMs)),
|
|
el('td', { className: 'ext-sdk-test-runner-detail' }, detail)
|
|
]);
|
|
tbody.appendChild(row);
|
|
updateSummary();
|
|
|
|
// Auto-scroll
|
|
row.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
};
|
|
|
|
T.onStart = function () {
|
|
tbody.innerHTML = '';
|
|
runBtn.disabled = true;
|
|
runBtn.textContent = 'Running...';
|
|
abortBtn.style.display = '';
|
|
updateSummary();
|
|
};
|
|
|
|
T.onComplete = function () {
|
|
runBtn.disabled = false;
|
|
runBtn.textContent = 'Run All';
|
|
abortBtn.style.display = 'none';
|
|
updateSummary();
|
|
};
|
|
|
|
function onRun() {
|
|
var selected = [];
|
|
domainNames.forEach(function (n) {
|
|
if (domainChecks[n].checked) selected.push(n);
|
|
});
|
|
T.runAll(selected.length === domainNames.length ? null : selected);
|
|
}
|
|
|
|
function onExport() {
|
|
var data = {
|
|
timestamp: new Date().toISOString(),
|
|
version: T.manifest.version,
|
|
stats: T.stats,
|
|
results: T.results
|
|
};
|
|
var blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
|
var a = document.createElement('a');
|
|
a.href = URL.createObjectURL(blob);
|
|
a.download = 'sdk-test-results-' + Date.now() + '.json';
|
|
a.click();
|
|
URL.revokeObjectURL(a.href);
|
|
}
|
|
|
|
updateSummary();
|
|
};
|
|
|
|
})();
|