Add platform-test diagnostic open provider
This commit is contained in:
parent
0cc4552506
commit
1d2190fc6c
|
|
@ -63,6 +63,17 @@
|
||||||
|
|
||||||
containerEl.innerHTML = '';
|
containerEl.innerHTML = '';
|
||||||
containerEl.className = 'pt-root';
|
containerEl.className = 'pt-root';
|
||||||
|
containerEl.__ptCleanup = [];
|
||||||
|
|
||||||
|
function trackCleanup(fn) {
|
||||||
|
if (typeof fn === 'function') {
|
||||||
|
if (!Array.isArray(containerEl.__ptCleanup)) {
|
||||||
|
try { fn(); } catch (e) { console.error('[platform-test] late cleanup error:', e); }
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
containerEl.__ptCleanup.push(fn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Header ─────────────────────────────────────────────────── */
|
/* ── Header ─────────────────────────────────────────────────── */
|
||||||
var header = div('pt-header', [
|
var header = div('pt-header', [
|
||||||
|
|
@ -81,11 +92,239 @@
|
||||||
]);
|
]);
|
||||||
var badgeRow = div('', [badge]);
|
var badgeRow = div('', [badge]);
|
||||||
|
|
||||||
|
/* ── Real Plugin API bridge checks ─────────────────────────── */
|
||||||
|
var savedValue = span('pt-list-value pt-saved-setting', 'Saved setting: loading...');
|
||||||
|
var capabilityValue = span('pt-list-value pt-capability-result', 'Capabilities: loading...');
|
||||||
|
var commandValue = span('pt-list-value pt-command-result', 'Command: registering...');
|
||||||
|
var eventValue = span('pt-list-value pt-event-result', 'Event: subscribing...');
|
||||||
|
var filesValue = span('pt-list-value pt-files-result', 'Files: running...');
|
||||||
|
var filesErrorValue = span('pt-list-value pt-files-error-result', 'Files error path: checking...');
|
||||||
|
var workbenchValue = span('pt-list-value pt-workbench-result', 'Workbench: ready');
|
||||||
|
function makeWorkbenchButton(className, label, request) {
|
||||||
|
return el('button', {
|
||||||
|
className: 'btn btn-primary ' + className,
|
||||||
|
onClick: function () {
|
||||||
|
workbenchValue.textContent = 'Workbench: opening...';
|
||||||
|
api.workbench.editResource(request)
|
||||||
|
.then(function (result) {
|
||||||
|
workbenchValue.textContent = 'Workbench: opened ' + result.request.path + ' with ' + (result.providerId || 'no-provider');
|
||||||
|
workbenchValue.setAttribute('data-workbench-status', result.status === 'opened' ? 'ok' : result.status);
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
workbenchValue.textContent = 'Workbench error: ' + (err && err.message ? err.message : String(err));
|
||||||
|
workbenchValue.setAttribute('data-workbench-status', 'error');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}, [label]);
|
||||||
|
}
|
||||||
|
var openTextWorkbenchButton = makeWorkbenchButton('pt-open-workbench-text', 'Open Text Diagnostic', {
|
||||||
|
kind: 'vault-file',
|
||||||
|
path: 'Docs/todo.txt',
|
||||||
|
extension: '.txt',
|
||||||
|
mime: 'text/plain',
|
||||||
|
context: { sourceView: 'files' },
|
||||||
|
});
|
||||||
|
var openMarkdownWorkbenchButton = makeWorkbenchButton('pt-open-workbench-markdown', 'Open Markdown Diagnostic', {
|
||||||
|
kind: 'vault-file',
|
||||||
|
path: 'Docs/readme.md',
|
||||||
|
extension: '.md',
|
||||||
|
context: { sourceView: 'files' },
|
||||||
|
});
|
||||||
|
var openNotesWorkbenchButton = makeWorkbenchButton('pt-open-workbench-notes', 'Open Notes Diagnostic', {
|
||||||
|
kind: 'vault-file',
|
||||||
|
path: 'Notes/Overview.md',
|
||||||
|
extension: '.md',
|
||||||
|
context: {
|
||||||
|
sourceView: 'notes',
|
||||||
|
isInsideNotesFolder: true,
|
||||||
|
notesMode: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
var settingInput = el('input', {
|
||||||
|
className: 'pt-setting-input',
|
||||||
|
type: 'text',
|
||||||
|
'aria-label': 'Saved setting',
|
||||||
|
value: 'changed value',
|
||||||
|
});
|
||||||
|
var saveStatus = span('pt-list-value', '');
|
||||||
|
var saveButton = el('button', {
|
||||||
|
className: 'btn btn-primary pt-save-setting',
|
||||||
|
onClick: function () {
|
||||||
|
saveStatus.textContent = 'Saving...';
|
||||||
|
api.settings.write('savedText', settingInput.value)
|
||||||
|
.then(function () {
|
||||||
|
savedValue.textContent = 'Saved setting: ' + settingInput.value;
|
||||||
|
saveStatus.textContent = 'Saved';
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
saveStatus.textContent = 'Error: ' + (err && err.message ? err.message : String(err));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}, ['Save Setting']);
|
||||||
|
|
||||||
|
api.settings.read('savedText')
|
||||||
|
.then(function (value) {
|
||||||
|
var text = value || '';
|
||||||
|
settingInput.value = text || 'changed value';
|
||||||
|
savedValue.textContent = 'Saved setting: ' + text;
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
savedValue.textContent = 'Settings error: ' + (err && err.message ? err.message : String(err));
|
||||||
|
});
|
||||||
|
|
||||||
|
api.capabilities.list()
|
||||||
|
.then(function (caps) {
|
||||||
|
capabilityValue.textContent = 'Capabilities: ' + caps.length + ' available';
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
capabilityValue.textContent = 'Capabilities error: ' + (err && err.message ? err.message : String(err));
|
||||||
|
});
|
||||||
|
|
||||||
|
api.capabilities.has('verstak/platform-test/v1')
|
||||||
|
.then(function (available) {
|
||||||
|
badge.setAttribute('data-capability-status', available ? 'available' : 'missing');
|
||||||
|
badge.lastChild.textContent = 'Frontend Bundle Loaded | capability ' + (available ? 'available' : 'missing');
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
badge.setAttribute('data-capability-status', 'error');
|
||||||
|
badge.lastChild.textContent = 'Capability error: ' + (err && err.message ? err.message : String(err));
|
||||||
|
});
|
||||||
|
|
||||||
|
api.commands.register('verstak.platform-test.show-version', function () {
|
||||||
|
return {
|
||||||
|
version: '0.1.0',
|
||||||
|
source: 'bundled-frontend',
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.then(function (unregister) {
|
||||||
|
trackCleanup(unregister);
|
||||||
|
return api.commands.execute('verstak.platform-test.show-version', {});
|
||||||
|
})
|
||||||
|
.then(function (result) {
|
||||||
|
badge.setAttribute('data-command-status', result.status || '');
|
||||||
|
commandValue.textContent = 'Command: ' + result.status + ' ' + result.result.version + ' from ' + result.result.source;
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
badge.setAttribute('data-command-status', 'error');
|
||||||
|
commandValue.textContent = 'Command error: ' + (err && err.message ? err.message : String(err));
|
||||||
|
console.error('[platform-test] command bridge error:', err);
|
||||||
|
});
|
||||||
|
|
||||||
|
api.events.subscribe('verstak.platform-test.echo', function (event) {
|
||||||
|
var message = event && event.payload ? event.payload.message : '';
|
||||||
|
eventValue.textContent = 'Event: received ' + message;
|
||||||
|
eventValue.setAttribute('data-event-status', 'received');
|
||||||
|
})
|
||||||
|
.then(function (unsubscribe) {
|
||||||
|
trackCleanup(unsubscribe);
|
||||||
|
return api.events.publish('verstak.platform-test.echo', { message: 'hello-event' });
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
eventValue.textContent = 'Event error: ' + (err && err.message ? err.message : String(err));
|
||||||
|
eventValue.setAttribute('data-event-status', 'error');
|
||||||
|
});
|
||||||
|
|
||||||
|
api.files.createFolder('PlatformTest')
|
||||||
|
.catch(function (err) {
|
||||||
|
if (String(err).indexOf('conflict') === -1) throw err;
|
||||||
|
})
|
||||||
|
.then(function () {
|
||||||
|
return api.files.writeText('PlatformTest/files-api.txt', 'hello files', { createIfMissing: true, overwrite: true });
|
||||||
|
})
|
||||||
|
.then(function () {
|
||||||
|
return api.files.readText('PlatformTest/files-api.txt');
|
||||||
|
})
|
||||||
|
.then(function (text) {
|
||||||
|
if (text !== 'hello files') throw new Error('read mismatch');
|
||||||
|
return api.files.list('PlatformTest');
|
||||||
|
})
|
||||||
|
.then(function (entries) {
|
||||||
|
var found = entries.some(function (entry) {
|
||||||
|
return entry.relativePath === 'PlatformTest/files-api.txt';
|
||||||
|
});
|
||||||
|
if (!found) throw new Error('list missing file');
|
||||||
|
return api.files.move('PlatformTest/files-api.txt', 'PlatformTest/files-api-moved.txt', { overwrite: true });
|
||||||
|
})
|
||||||
|
.then(function () {
|
||||||
|
return api.files.trash('PlatformTest/files-api-moved.txt');
|
||||||
|
})
|
||||||
|
.then(function () {
|
||||||
|
filesValue.textContent = 'Files: wrote/read/listed/moved/trashed';
|
||||||
|
filesValue.setAttribute('data-files-status', 'ok');
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
filesValue.textContent = 'Files error: ' + (err && err.message ? err.message : String(err));
|
||||||
|
filesValue.setAttribute('data-files-status', 'error');
|
||||||
|
});
|
||||||
|
|
||||||
|
api.files.readText('.verstak/vault.json')
|
||||||
|
.then(function () {
|
||||||
|
filesErrorValue.textContent = 'Files error path: unexpectedly allowed';
|
||||||
|
filesErrorValue.setAttribute('data-files-error-status', 'error');
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
var message = err && err.message ? err.message : String(err);
|
||||||
|
if (message.indexOf('reserved-path') === -1 && message.indexOf('.verstak') === -1) {
|
||||||
|
filesErrorValue.textContent = 'Files error path: wrong error ' + message;
|
||||||
|
filesErrorValue.setAttribute('data-files-error-status', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
filesErrorValue.textContent = 'Files error path: rejected reserved-path';
|
||||||
|
filesErrorValue.setAttribute('data-files-error-status', 'expected');
|
||||||
|
});
|
||||||
|
|
||||||
|
var bridgeCard = div('pt-card', [
|
||||||
|
el('h3', { className: 'pt-card-title' }, ['Real Plugin API Bridge']),
|
||||||
|
el('ul', { className: 'pt-list' }, [
|
||||||
|
el('li', { className: 'pt-list-item' }, [
|
||||||
|
span('pt-list-label', 'Persisted setting'),
|
||||||
|
savedValue,
|
||||||
|
]),
|
||||||
|
el('li', { className: 'pt-list-item' }, [
|
||||||
|
span('pt-list-label', 'New value'),
|
||||||
|
settingInput,
|
||||||
|
]),
|
||||||
|
el('li', { className: 'pt-list-item' }, [
|
||||||
|
saveButton,
|
||||||
|
saveStatus,
|
||||||
|
]),
|
||||||
|
el('li', { className: 'pt-list-item' }, [
|
||||||
|
span('pt-list-label', 'Capabilities'),
|
||||||
|
capabilityValue,
|
||||||
|
]),
|
||||||
|
el('li', { className: 'pt-list-item' }, [
|
||||||
|
span('pt-list-label', 'Command runtime'),
|
||||||
|
commandValue,
|
||||||
|
]),
|
||||||
|
el('li', { className: 'pt-list-item' }, [
|
||||||
|
span('pt-list-label', 'Event runtime'),
|
||||||
|
eventValue,
|
||||||
|
]),
|
||||||
|
el('li', { className: 'pt-list-item' }, [
|
||||||
|
span('pt-list-label', 'Files runtime'),
|
||||||
|
filesValue,
|
||||||
|
]),
|
||||||
|
el('li', { className: 'pt-list-item' }, [
|
||||||
|
span('pt-list-label', 'Files reserved path'),
|
||||||
|
filesErrorValue,
|
||||||
|
]),
|
||||||
|
el('li', { className: 'pt-list-item' }, [
|
||||||
|
span('pt-list-label', 'Workbench routing'),
|
||||||
|
openTextWorkbenchButton,
|
||||||
|
openMarkdownWorkbenchButton,
|
||||||
|
openNotesWorkbenchButton,
|
||||||
|
workbenchValue,
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
/* ── Test results summary ──────────────────────────────────── */
|
/* ── Test results summary ──────────────────────────────────── */
|
||||||
var testsData = [
|
var testsData = [
|
||||||
{ label: 'Plugin Registration', status: 'pass' },
|
{ label: 'Plugin Registration', status: 'pass' },
|
||||||
{ label: 'Capability: verstak/platform-test/v1', status: 'pass' },
|
{ label: 'Capability: verstak/platform-test/v1', status: 'pass' },
|
||||||
{ label: 'Capability: verstak/diagnostics/v1', status: 'pass' },
|
{ label: 'Capability: verstak/diagnostics/v1', status: 'pass' },
|
||||||
|
{ label: 'Capability: verstak/core/files/v1', status: 'pass' },
|
||||||
|
{ label: 'Capability: verstak/core/workbench/v1', status: 'pass' },
|
||||||
{ label: 'API Contract Compliance', status: 'pass' },
|
{ label: 'API Contract Compliance', status: 'pass' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -133,22 +372,29 @@
|
||||||
{ id: 'verstak/diagnostics/v1', label: 'Diagnostics API' },
|
{ id: 'verstak/diagnostics/v1', label: 'Diagnostics API' },
|
||||||
{ id: 'verstak/core/vault/v1', label: 'Vault API (optional)' },
|
{ id: 'verstak/core/vault/v1', label: 'Vault API (optional)' },
|
||||||
{ id: 'verstak/core/sync/v1', label: 'Sync API (optional)' },
|
{ id: 'verstak/core/sync/v1', label: 'Sync API (optional)' },
|
||||||
|
{ id: 'verstak/core/workbench/v1', label: 'Workbench API' },
|
||||||
];
|
];
|
||||||
|
|
||||||
var capList = el('ul', { className: 'pt-list' });
|
var capList = el('ul', { className: 'pt-list' });
|
||||||
knownCaps.forEach(function (cap) {
|
knownCaps.forEach(function (cap) {
|
||||||
var available = api.capabilities && api.capabilities.has
|
|
||||||
? api.capabilities.has(cap.id)
|
|
||||||
: false;
|
|
||||||
var dot = el('span', {
|
var dot = el('span', {
|
||||||
className: 'pt-cap-dot ' + (available ? 'pt-cap-dot-ok' : 'pt-cap-dot-missing'),
|
className: 'pt-cap-dot pt-cap-dot-missing',
|
||||||
});
|
});
|
||||||
var statusVal = available ? '✓ Available' : '— Unavailable';
|
var statusText = span('pt-list-value', 'Checking...');
|
||||||
var item = el('li', { className: 'pt-list-item' }, [
|
var item = el('li', { className: 'pt-list-item' }, [
|
||||||
el('span', { className: 'pt-list-label' }, [dot, ' ', cap.label]),
|
el('span', { className: 'pt-list-label' }, [dot, ' ', cap.label]),
|
||||||
span('pt-list-value', statusVal),
|
statusText,
|
||||||
]);
|
]);
|
||||||
capList.appendChild(item);
|
capList.appendChild(item);
|
||||||
|
api.capabilities.has(cap.id)
|
||||||
|
.then(function (available) {
|
||||||
|
dot.className = 'pt-cap-dot ' + (available ? 'pt-cap-dot-ok' : 'pt-cap-dot-missing');
|
||||||
|
statusText.textContent = available ? '✓ Available' : '— Unavailable';
|
||||||
|
})
|
||||||
|
.catch(function () {
|
||||||
|
dot.className = 'pt-cap-dot pt-cap-dot-missing';
|
||||||
|
statusText.textContent = 'Error';
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
var capsCard = div('pt-card', [
|
var capsCard = div('pt-card', [
|
||||||
|
|
@ -188,6 +434,12 @@
|
||||||
{ label: 'settings.write', ok: typeof api.settings.write === 'function' },
|
{ label: 'settings.write', ok: typeof api.settings.write === 'function' },
|
||||||
{ label: 'commands.execute', ok: typeof api.commands.execute === 'function' },
|
{ label: 'commands.execute', ok: typeof api.commands.execute === 'function' },
|
||||||
{ label: 'capabilities.has', ok: typeof api.capabilities.has === 'function' },
|
{ label: 'capabilities.has', ok: typeof api.capabilities.has === 'function' },
|
||||||
|
{ label: 'files.list', ok: typeof api.files.list === 'function' },
|
||||||
|
{ label: 'files.readText', ok: typeof api.files.readText === 'function' },
|
||||||
|
{ label: 'files.writeText', ok: typeof api.files.writeText === 'function' },
|
||||||
|
{ label: 'files.trash', ok: typeof api.files.trash === 'function' },
|
||||||
|
{ label: 'workbench.openResource', ok: typeof api.workbench.openResource === 'function' },
|
||||||
|
{ label: 'workbench.editResource', ok: typeof api.workbench.editResource === 'function' },
|
||||||
];
|
];
|
||||||
apiChecks.forEach(function (chk) {
|
apiChecks.forEach(function (chk) {
|
||||||
var dot = el('span', {
|
var dot = el('span', {
|
||||||
|
|
@ -209,12 +461,55 @@
|
||||||
/* ── Assemble ──────────────────────────────────────────────── */
|
/* ── Assemble ──────────────────────────────────────────────── */
|
||||||
containerEl.appendChild(header);
|
containerEl.appendChild(header);
|
||||||
containerEl.appendChild(badgeRow);
|
containerEl.appendChild(badgeRow);
|
||||||
|
containerEl.appendChild(bridgeCard);
|
||||||
containerEl.appendChild(testsCard);
|
containerEl.appendChild(testsCard);
|
||||||
containerEl.appendChild(capsCard);
|
containerEl.appendChild(capsCard);
|
||||||
containerEl.appendChild(infoCard);
|
containerEl.appendChild(infoCard);
|
||||||
containerEl.appendChild(apiCard);
|
containerEl.appendChild(apiCard);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
unmount: function (containerEl) {
|
||||||
|
if (Array.isArray(containerEl.__ptCleanup)) {
|
||||||
|
while (containerEl.__ptCleanup.length > 0) {
|
||||||
|
var cleanup = containerEl.__ptCleanup.pop();
|
||||||
|
try { cleanup(); } catch (e) { console.error('[platform-test] cleanup error:', e); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
containerEl.innerHTML = '';
|
||||||
|
containerEl.className = '';
|
||||||
|
delete containerEl.__ptCleanup;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* MarkdownDiagnosticProvider component */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
var MarkdownDiagnosticProvider = {
|
||||||
|
mount: function (containerEl, props) {
|
||||||
|
injectStyles();
|
||||||
|
|
||||||
|
var request = props && props.request ? props.request : {};
|
||||||
|
var context = request.context && (request.context.notesMode || request.context.isInsideNotesFolder)
|
||||||
|
? 'notes-markdown'
|
||||||
|
: ((request.extension === '.md' || request.extension === '.markdown') ? 'generic-markdown' : 'generic-text');
|
||||||
|
containerEl.innerHTML = '';
|
||||||
|
containerEl.className = 'pt-root';
|
||||||
|
|
||||||
|
var result = div('pt-card pt-workbench-result', [
|
||||||
|
el('h2', { className: 'pt-plugin-name' }, ['Workbench Diagnostic Provider']),
|
||||||
|
el('p', { className: 'pt-plugin-id' }, [
|
||||||
|
'Workbench: opened ' + (request.path || '') + ' with ' + ((props && props.providerId) || '') +
|
||||||
|
' mode=' + (request.mode || '') + ' context=' + context,
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
result.setAttribute('data-workbench-status', 'ok');
|
||||||
|
result.setAttribute('data-resource-path', request.path || '');
|
||||||
|
result.setAttribute('data-resource-mode', request.mode || '');
|
||||||
|
result.setAttribute('data-resource-context', context);
|
||||||
|
|
||||||
|
containerEl.appendChild(result);
|
||||||
|
},
|
||||||
|
|
||||||
unmount: function (containerEl) {
|
unmount: function (containerEl) {
|
||||||
containerEl.innerHTML = '';
|
containerEl.innerHTML = '';
|
||||||
containerEl.className = '';
|
containerEl.className = '';
|
||||||
|
|
@ -258,22 +553,22 @@
|
||||||
span('pt-counter-label', 'clicks (session only, no persistence)'),
|
span('pt-counter-label', 'clicks (session only, no persistence)'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
var incrementBtn = el('button', { className: 'pt-btn pt-btn-accent', onClick: function () {
|
var incrementBtn = el('button', { className: 'btn btn-primary', onClick: function () {
|
||||||
counterState.value += 1;
|
counterState.value += 1;
|
||||||
counterDisplay.firstChild.textContent = String(counterState.value);
|
counterDisplay.firstChild.textContent = String(counterState.value);
|
||||||
}}, ['+ Increment']);
|
}}, ['+ Increment']);
|
||||||
|
|
||||||
var decrementBtn = el('button', { className: 'pt-btn', onClick: function () {
|
var decrementBtn = el('button', { className: 'btn btn-secondary', onClick: function () {
|
||||||
counterState.value = Math.max(0, counterState.value - 1);
|
counterState.value = Math.max(0, counterState.value - 1);
|
||||||
counterDisplay.firstChild.textContent = String(counterState.value);
|
counterDisplay.firstChild.textContent = String(counterState.value);
|
||||||
}}, ['− Decrement']);
|
}}, ['− Decrement']);
|
||||||
|
|
||||||
var resetBtn = el('button', { className: 'pt-btn', onClick: function () {
|
var resetBtn = el('button', { className: 'btn btn-secondary', onClick: function () {
|
||||||
counterState.value = 0;
|
counterState.value = 0;
|
||||||
counterDisplay.firstChild.textContent = '0';
|
counterDisplay.firstChild.textContent = '0';
|
||||||
}}, ['↺ Reset']);
|
}}, ['↺ Reset']);
|
||||||
|
|
||||||
var btnGroup = div('', { style: { display: 'flex', gap: '0.5rem' } }, [
|
var btnGroup = el('div', { style: { display: 'flex', gap: '0.5rem' } }, [
|
||||||
incrementBtn, decrementBtn, resetBtn,
|
incrementBtn, decrementBtn, resetBtn,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
@ -331,6 +626,7 @@
|
||||||
components: {
|
components: {
|
||||||
DiagnosticsPanel: DiagnosticsPanel,
|
DiagnosticsPanel: DiagnosticsPanel,
|
||||||
PlatformTestSettings: PlatformTestSettings,
|
PlatformTestSettings: PlatformTestSettings,
|
||||||
|
MarkdownDiagnosticProvider: MarkdownDiagnosticProvider,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,17 @@
|
||||||
|
|
||||||
containerEl.innerHTML = '';
|
containerEl.innerHTML = '';
|
||||||
containerEl.className = 'pt-root';
|
containerEl.className = 'pt-root';
|
||||||
|
containerEl.__ptCleanup = [];
|
||||||
|
|
||||||
|
function trackCleanup(fn) {
|
||||||
|
if (typeof fn === 'function') {
|
||||||
|
if (!Array.isArray(containerEl.__ptCleanup)) {
|
||||||
|
try { fn(); } catch (e) { console.error('[platform-test] late cleanup error:', e); }
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
containerEl.__ptCleanup.push(fn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Header ─────────────────────────────────────────────────── */
|
/* ── Header ─────────────────────────────────────────────────── */
|
||||||
var header = div('pt-header', [
|
var header = div('pt-header', [
|
||||||
|
|
@ -81,11 +92,239 @@
|
||||||
]);
|
]);
|
||||||
var badgeRow = div('', [badge]);
|
var badgeRow = div('', [badge]);
|
||||||
|
|
||||||
|
/* ── Real Plugin API bridge checks ─────────────────────────── */
|
||||||
|
var savedValue = span('pt-list-value pt-saved-setting', 'Saved setting: loading...');
|
||||||
|
var capabilityValue = span('pt-list-value pt-capability-result', 'Capabilities: loading...');
|
||||||
|
var commandValue = span('pt-list-value pt-command-result', 'Command: registering...');
|
||||||
|
var eventValue = span('pt-list-value pt-event-result', 'Event: subscribing...');
|
||||||
|
var filesValue = span('pt-list-value pt-files-result', 'Files: running...');
|
||||||
|
var filesErrorValue = span('pt-list-value pt-files-error-result', 'Files error path: checking...');
|
||||||
|
var workbenchValue = span('pt-list-value pt-workbench-result', 'Workbench: ready');
|
||||||
|
function makeWorkbenchButton(className, label, request) {
|
||||||
|
return el('button', {
|
||||||
|
className: 'btn btn-primary ' + className,
|
||||||
|
onClick: function () {
|
||||||
|
workbenchValue.textContent = 'Workbench: opening...';
|
||||||
|
api.workbench.editResource(request)
|
||||||
|
.then(function (result) {
|
||||||
|
workbenchValue.textContent = 'Workbench: opened ' + result.request.path + ' with ' + (result.providerId || 'no-provider');
|
||||||
|
workbenchValue.setAttribute('data-workbench-status', result.status === 'opened' ? 'ok' : result.status);
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
workbenchValue.textContent = 'Workbench error: ' + (err && err.message ? err.message : String(err));
|
||||||
|
workbenchValue.setAttribute('data-workbench-status', 'error');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}, [label]);
|
||||||
|
}
|
||||||
|
var openTextWorkbenchButton = makeWorkbenchButton('pt-open-workbench-text', 'Open Text Diagnostic', {
|
||||||
|
kind: 'vault-file',
|
||||||
|
path: 'Docs/todo.txt',
|
||||||
|
extension: '.txt',
|
||||||
|
mime: 'text/plain',
|
||||||
|
context: { sourceView: 'files' },
|
||||||
|
});
|
||||||
|
var openMarkdownWorkbenchButton = makeWorkbenchButton('pt-open-workbench-markdown', 'Open Markdown Diagnostic', {
|
||||||
|
kind: 'vault-file',
|
||||||
|
path: 'Docs/readme.md',
|
||||||
|
extension: '.md',
|
||||||
|
context: { sourceView: 'files' },
|
||||||
|
});
|
||||||
|
var openNotesWorkbenchButton = makeWorkbenchButton('pt-open-workbench-notes', 'Open Notes Diagnostic', {
|
||||||
|
kind: 'vault-file',
|
||||||
|
path: 'Notes/Overview.md',
|
||||||
|
extension: '.md',
|
||||||
|
context: {
|
||||||
|
sourceView: 'notes',
|
||||||
|
isInsideNotesFolder: true,
|
||||||
|
notesMode: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
var settingInput = el('input', {
|
||||||
|
className: 'pt-setting-input',
|
||||||
|
type: 'text',
|
||||||
|
'aria-label': 'Saved setting',
|
||||||
|
value: 'changed value',
|
||||||
|
});
|
||||||
|
var saveStatus = span('pt-list-value', '');
|
||||||
|
var saveButton = el('button', {
|
||||||
|
className: 'btn btn-primary pt-save-setting',
|
||||||
|
onClick: function () {
|
||||||
|
saveStatus.textContent = 'Saving...';
|
||||||
|
api.settings.write('savedText', settingInput.value)
|
||||||
|
.then(function () {
|
||||||
|
savedValue.textContent = 'Saved setting: ' + settingInput.value;
|
||||||
|
saveStatus.textContent = 'Saved';
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
saveStatus.textContent = 'Error: ' + (err && err.message ? err.message : String(err));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}, ['Save Setting']);
|
||||||
|
|
||||||
|
api.settings.read('savedText')
|
||||||
|
.then(function (value) {
|
||||||
|
var text = value || '';
|
||||||
|
settingInput.value = text || 'changed value';
|
||||||
|
savedValue.textContent = 'Saved setting: ' + text;
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
savedValue.textContent = 'Settings error: ' + (err && err.message ? err.message : String(err));
|
||||||
|
});
|
||||||
|
|
||||||
|
api.capabilities.list()
|
||||||
|
.then(function (caps) {
|
||||||
|
capabilityValue.textContent = 'Capabilities: ' + caps.length + ' available';
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
capabilityValue.textContent = 'Capabilities error: ' + (err && err.message ? err.message : String(err));
|
||||||
|
});
|
||||||
|
|
||||||
|
api.capabilities.has('verstak/platform-test/v1')
|
||||||
|
.then(function (available) {
|
||||||
|
badge.setAttribute('data-capability-status', available ? 'available' : 'missing');
|
||||||
|
badge.lastChild.textContent = 'Frontend Bundle Loaded | capability ' + (available ? 'available' : 'missing');
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
badge.setAttribute('data-capability-status', 'error');
|
||||||
|
badge.lastChild.textContent = 'Capability error: ' + (err && err.message ? err.message : String(err));
|
||||||
|
});
|
||||||
|
|
||||||
|
api.commands.register('verstak.platform-test.show-version', function () {
|
||||||
|
return {
|
||||||
|
version: '0.1.0',
|
||||||
|
source: 'bundled-frontend',
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.then(function (unregister) {
|
||||||
|
trackCleanup(unregister);
|
||||||
|
return api.commands.execute('verstak.platform-test.show-version', {});
|
||||||
|
})
|
||||||
|
.then(function (result) {
|
||||||
|
badge.setAttribute('data-command-status', result.status || '');
|
||||||
|
commandValue.textContent = 'Command: ' + result.status + ' ' + result.result.version + ' from ' + result.result.source;
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
badge.setAttribute('data-command-status', 'error');
|
||||||
|
commandValue.textContent = 'Command error: ' + (err && err.message ? err.message : String(err));
|
||||||
|
console.error('[platform-test] command bridge error:', err);
|
||||||
|
});
|
||||||
|
|
||||||
|
api.events.subscribe('verstak.platform-test.echo', function (event) {
|
||||||
|
var message = event && event.payload ? event.payload.message : '';
|
||||||
|
eventValue.textContent = 'Event: received ' + message;
|
||||||
|
eventValue.setAttribute('data-event-status', 'received');
|
||||||
|
})
|
||||||
|
.then(function (unsubscribe) {
|
||||||
|
trackCleanup(unsubscribe);
|
||||||
|
return api.events.publish('verstak.platform-test.echo', { message: 'hello-event' });
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
eventValue.textContent = 'Event error: ' + (err && err.message ? err.message : String(err));
|
||||||
|
eventValue.setAttribute('data-event-status', 'error');
|
||||||
|
});
|
||||||
|
|
||||||
|
api.files.createFolder('PlatformTest')
|
||||||
|
.catch(function (err) {
|
||||||
|
if (String(err).indexOf('conflict') === -1) throw err;
|
||||||
|
})
|
||||||
|
.then(function () {
|
||||||
|
return api.files.writeText('PlatformTest/files-api.txt', 'hello files', { createIfMissing: true, overwrite: true });
|
||||||
|
})
|
||||||
|
.then(function () {
|
||||||
|
return api.files.readText('PlatformTest/files-api.txt');
|
||||||
|
})
|
||||||
|
.then(function (text) {
|
||||||
|
if (text !== 'hello files') throw new Error('read mismatch');
|
||||||
|
return api.files.list('PlatformTest');
|
||||||
|
})
|
||||||
|
.then(function (entries) {
|
||||||
|
var found = entries.some(function (entry) {
|
||||||
|
return entry.relativePath === 'PlatformTest/files-api.txt';
|
||||||
|
});
|
||||||
|
if (!found) throw new Error('list missing file');
|
||||||
|
return api.files.move('PlatformTest/files-api.txt', 'PlatformTest/files-api-moved.txt', { overwrite: true });
|
||||||
|
})
|
||||||
|
.then(function () {
|
||||||
|
return api.files.trash('PlatformTest/files-api-moved.txt');
|
||||||
|
})
|
||||||
|
.then(function () {
|
||||||
|
filesValue.textContent = 'Files: wrote/read/listed/moved/trashed';
|
||||||
|
filesValue.setAttribute('data-files-status', 'ok');
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
filesValue.textContent = 'Files error: ' + (err && err.message ? err.message : String(err));
|
||||||
|
filesValue.setAttribute('data-files-status', 'error');
|
||||||
|
});
|
||||||
|
|
||||||
|
api.files.readText('.verstak/vault.json')
|
||||||
|
.then(function () {
|
||||||
|
filesErrorValue.textContent = 'Files error path: unexpectedly allowed';
|
||||||
|
filesErrorValue.setAttribute('data-files-error-status', 'error');
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
var message = err && err.message ? err.message : String(err);
|
||||||
|
if (message.indexOf('reserved-path') === -1 && message.indexOf('.verstak') === -1) {
|
||||||
|
filesErrorValue.textContent = 'Files error path: wrong error ' + message;
|
||||||
|
filesErrorValue.setAttribute('data-files-error-status', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
filesErrorValue.textContent = 'Files error path: rejected reserved-path';
|
||||||
|
filesErrorValue.setAttribute('data-files-error-status', 'expected');
|
||||||
|
});
|
||||||
|
|
||||||
|
var bridgeCard = div('pt-card', [
|
||||||
|
el('h3', { className: 'pt-card-title' }, ['Real Plugin API Bridge']),
|
||||||
|
el('ul', { className: 'pt-list' }, [
|
||||||
|
el('li', { className: 'pt-list-item' }, [
|
||||||
|
span('pt-list-label', 'Persisted setting'),
|
||||||
|
savedValue,
|
||||||
|
]),
|
||||||
|
el('li', { className: 'pt-list-item' }, [
|
||||||
|
span('pt-list-label', 'New value'),
|
||||||
|
settingInput,
|
||||||
|
]),
|
||||||
|
el('li', { className: 'pt-list-item' }, [
|
||||||
|
saveButton,
|
||||||
|
saveStatus,
|
||||||
|
]),
|
||||||
|
el('li', { className: 'pt-list-item' }, [
|
||||||
|
span('pt-list-label', 'Capabilities'),
|
||||||
|
capabilityValue,
|
||||||
|
]),
|
||||||
|
el('li', { className: 'pt-list-item' }, [
|
||||||
|
span('pt-list-label', 'Command runtime'),
|
||||||
|
commandValue,
|
||||||
|
]),
|
||||||
|
el('li', { className: 'pt-list-item' }, [
|
||||||
|
span('pt-list-label', 'Event runtime'),
|
||||||
|
eventValue,
|
||||||
|
]),
|
||||||
|
el('li', { className: 'pt-list-item' }, [
|
||||||
|
span('pt-list-label', 'Files runtime'),
|
||||||
|
filesValue,
|
||||||
|
]),
|
||||||
|
el('li', { className: 'pt-list-item' }, [
|
||||||
|
span('pt-list-label', 'Files reserved path'),
|
||||||
|
filesErrorValue,
|
||||||
|
]),
|
||||||
|
el('li', { className: 'pt-list-item' }, [
|
||||||
|
span('pt-list-label', 'Workbench routing'),
|
||||||
|
openTextWorkbenchButton,
|
||||||
|
openMarkdownWorkbenchButton,
|
||||||
|
openNotesWorkbenchButton,
|
||||||
|
workbenchValue,
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
/* ── Test results summary ──────────────────────────────────── */
|
/* ── Test results summary ──────────────────────────────────── */
|
||||||
var testsData = [
|
var testsData = [
|
||||||
{ label: 'Plugin Registration', status: 'pass' },
|
{ label: 'Plugin Registration', status: 'pass' },
|
||||||
{ label: 'Capability: verstak/platform-test/v1', status: 'pass' },
|
{ label: 'Capability: verstak/platform-test/v1', status: 'pass' },
|
||||||
{ label: 'Capability: verstak/diagnostics/v1', status: 'pass' },
|
{ label: 'Capability: verstak/diagnostics/v1', status: 'pass' },
|
||||||
|
{ label: 'Capability: verstak/core/files/v1', status: 'pass' },
|
||||||
|
{ label: 'Capability: verstak/core/workbench/v1', status: 'pass' },
|
||||||
{ label: 'API Contract Compliance', status: 'pass' },
|
{ label: 'API Contract Compliance', status: 'pass' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -133,22 +372,29 @@
|
||||||
{ id: 'verstak/diagnostics/v1', label: 'Diagnostics API' },
|
{ id: 'verstak/diagnostics/v1', label: 'Diagnostics API' },
|
||||||
{ id: 'verstak/core/vault/v1', label: 'Vault API (optional)' },
|
{ id: 'verstak/core/vault/v1', label: 'Vault API (optional)' },
|
||||||
{ id: 'verstak/core/sync/v1', label: 'Sync API (optional)' },
|
{ id: 'verstak/core/sync/v1', label: 'Sync API (optional)' },
|
||||||
|
{ id: 'verstak/core/workbench/v1', label: 'Workbench API' },
|
||||||
];
|
];
|
||||||
|
|
||||||
var capList = el('ul', { className: 'pt-list' });
|
var capList = el('ul', { className: 'pt-list' });
|
||||||
knownCaps.forEach(function (cap) {
|
knownCaps.forEach(function (cap) {
|
||||||
var available = api.capabilities && api.capabilities.has
|
|
||||||
? api.capabilities.has(cap.id)
|
|
||||||
: false;
|
|
||||||
var dot = el('span', {
|
var dot = el('span', {
|
||||||
className: 'pt-cap-dot ' + (available ? 'pt-cap-dot-ok' : 'pt-cap-dot-missing'),
|
className: 'pt-cap-dot pt-cap-dot-missing',
|
||||||
});
|
});
|
||||||
var statusVal = available ? '✓ Available' : '— Unavailable';
|
var statusText = span('pt-list-value', 'Checking...');
|
||||||
var item = el('li', { className: 'pt-list-item' }, [
|
var item = el('li', { className: 'pt-list-item' }, [
|
||||||
el('span', { className: 'pt-list-label' }, [dot, ' ', cap.label]),
|
el('span', { className: 'pt-list-label' }, [dot, ' ', cap.label]),
|
||||||
span('pt-list-value', statusVal),
|
statusText,
|
||||||
]);
|
]);
|
||||||
capList.appendChild(item);
|
capList.appendChild(item);
|
||||||
|
api.capabilities.has(cap.id)
|
||||||
|
.then(function (available) {
|
||||||
|
dot.className = 'pt-cap-dot ' + (available ? 'pt-cap-dot-ok' : 'pt-cap-dot-missing');
|
||||||
|
statusText.textContent = available ? '✓ Available' : '— Unavailable';
|
||||||
|
})
|
||||||
|
.catch(function () {
|
||||||
|
dot.className = 'pt-cap-dot pt-cap-dot-missing';
|
||||||
|
statusText.textContent = 'Error';
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
var capsCard = div('pt-card', [
|
var capsCard = div('pt-card', [
|
||||||
|
|
@ -188,6 +434,12 @@
|
||||||
{ label: 'settings.write', ok: typeof api.settings.write === 'function' },
|
{ label: 'settings.write', ok: typeof api.settings.write === 'function' },
|
||||||
{ label: 'commands.execute', ok: typeof api.commands.execute === 'function' },
|
{ label: 'commands.execute', ok: typeof api.commands.execute === 'function' },
|
||||||
{ label: 'capabilities.has', ok: typeof api.capabilities.has === 'function' },
|
{ label: 'capabilities.has', ok: typeof api.capabilities.has === 'function' },
|
||||||
|
{ label: 'files.list', ok: typeof api.files.list === 'function' },
|
||||||
|
{ label: 'files.readText', ok: typeof api.files.readText === 'function' },
|
||||||
|
{ label: 'files.writeText', ok: typeof api.files.writeText === 'function' },
|
||||||
|
{ label: 'files.trash', ok: typeof api.files.trash === 'function' },
|
||||||
|
{ label: 'workbench.openResource', ok: typeof api.workbench.openResource === 'function' },
|
||||||
|
{ label: 'workbench.editResource', ok: typeof api.workbench.editResource === 'function' },
|
||||||
];
|
];
|
||||||
apiChecks.forEach(function (chk) {
|
apiChecks.forEach(function (chk) {
|
||||||
var dot = el('span', {
|
var dot = el('span', {
|
||||||
|
|
@ -209,12 +461,55 @@
|
||||||
/* ── Assemble ──────────────────────────────────────────────── */
|
/* ── Assemble ──────────────────────────────────────────────── */
|
||||||
containerEl.appendChild(header);
|
containerEl.appendChild(header);
|
||||||
containerEl.appendChild(badgeRow);
|
containerEl.appendChild(badgeRow);
|
||||||
|
containerEl.appendChild(bridgeCard);
|
||||||
containerEl.appendChild(testsCard);
|
containerEl.appendChild(testsCard);
|
||||||
containerEl.appendChild(capsCard);
|
containerEl.appendChild(capsCard);
|
||||||
containerEl.appendChild(infoCard);
|
containerEl.appendChild(infoCard);
|
||||||
containerEl.appendChild(apiCard);
|
containerEl.appendChild(apiCard);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
unmount: function (containerEl) {
|
||||||
|
if (Array.isArray(containerEl.__ptCleanup)) {
|
||||||
|
while (containerEl.__ptCleanup.length > 0) {
|
||||||
|
var cleanup = containerEl.__ptCleanup.pop();
|
||||||
|
try { cleanup(); } catch (e) { console.error('[platform-test] cleanup error:', e); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
containerEl.innerHTML = '';
|
||||||
|
containerEl.className = '';
|
||||||
|
delete containerEl.__ptCleanup;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* MarkdownDiagnosticProvider component */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
var MarkdownDiagnosticProvider = {
|
||||||
|
mount: function (containerEl, props) {
|
||||||
|
injectStyles();
|
||||||
|
|
||||||
|
var request = props && props.request ? props.request : {};
|
||||||
|
var context = request.context && (request.context.notesMode || request.context.isInsideNotesFolder)
|
||||||
|
? 'notes-markdown'
|
||||||
|
: ((request.extension === '.md' || request.extension === '.markdown') ? 'generic-markdown' : 'generic-text');
|
||||||
|
containerEl.innerHTML = '';
|
||||||
|
containerEl.className = 'pt-root';
|
||||||
|
|
||||||
|
var result = div('pt-card pt-workbench-result', [
|
||||||
|
el('h2', { className: 'pt-plugin-name' }, ['Workbench Diagnostic Provider']),
|
||||||
|
el('p', { className: 'pt-plugin-id' }, [
|
||||||
|
'Workbench: opened ' + (request.path || '') + ' with ' + ((props && props.providerId) || '') +
|
||||||
|
' mode=' + (request.mode || '') + ' context=' + context,
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
result.setAttribute('data-workbench-status', 'ok');
|
||||||
|
result.setAttribute('data-resource-path', request.path || '');
|
||||||
|
result.setAttribute('data-resource-mode', request.mode || '');
|
||||||
|
result.setAttribute('data-resource-context', context);
|
||||||
|
|
||||||
|
containerEl.appendChild(result);
|
||||||
|
},
|
||||||
|
|
||||||
unmount: function (containerEl) {
|
unmount: function (containerEl) {
|
||||||
containerEl.innerHTML = '';
|
containerEl.innerHTML = '';
|
||||||
containerEl.className = '';
|
containerEl.className = '';
|
||||||
|
|
@ -258,22 +553,22 @@
|
||||||
span('pt-counter-label', 'clicks (session only, no persistence)'),
|
span('pt-counter-label', 'clicks (session only, no persistence)'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
var incrementBtn = el('button', { className: 'pt-btn pt-btn-accent', onClick: function () {
|
var incrementBtn = el('button', { className: 'btn btn-primary', onClick: function () {
|
||||||
counterState.value += 1;
|
counterState.value += 1;
|
||||||
counterDisplay.firstChild.textContent = String(counterState.value);
|
counterDisplay.firstChild.textContent = String(counterState.value);
|
||||||
}}, ['+ Increment']);
|
}}, ['+ Increment']);
|
||||||
|
|
||||||
var decrementBtn = el('button', { className: 'pt-btn', onClick: function () {
|
var decrementBtn = el('button', { className: 'btn btn-secondary', onClick: function () {
|
||||||
counterState.value = Math.max(0, counterState.value - 1);
|
counterState.value = Math.max(0, counterState.value - 1);
|
||||||
counterDisplay.firstChild.textContent = String(counterState.value);
|
counterDisplay.firstChild.textContent = String(counterState.value);
|
||||||
}}, ['− Decrement']);
|
}}, ['− Decrement']);
|
||||||
|
|
||||||
var resetBtn = el('button', { className: 'pt-btn', onClick: function () {
|
var resetBtn = el('button', { className: 'btn btn-secondary', onClick: function () {
|
||||||
counterState.value = 0;
|
counterState.value = 0;
|
||||||
counterDisplay.firstChild.textContent = '0';
|
counterDisplay.firstChild.textContent = '0';
|
||||||
}}, ['↺ Reset']);
|
}}, ['↺ Reset']);
|
||||||
|
|
||||||
var btnGroup = div('', { style: { display: 'flex', gap: '0.5rem' } }, [
|
var btnGroup = el('div', { style: { display: 'flex', gap: '0.5rem' } }, [
|
||||||
incrementBtn, decrementBtn, resetBtn,
|
incrementBtn, decrementBtn, resetBtn,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
@ -331,6 +626,7 @@
|
||||||
components: {
|
components: {
|
||||||
DiagnosticsPanel: DiagnosticsPanel,
|
DiagnosticsPanel: DiagnosticsPanel,
|
||||||
PlatformTestSettings: PlatformTestSettings,
|
PlatformTestSettings: PlatformTestSettings,
|
||||||
|
MarkdownDiagnosticProvider: MarkdownDiagnosticProvider,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,14 @@
|
||||||
"verstak/diagnostics/v1"
|
"verstak/diagnostics/v1"
|
||||||
],
|
],
|
||||||
"requires": [
|
"requires": [
|
||||||
"verstak/core/plugin-manager/v1"
|
"verstak/core/plugin-manager/v1",
|
||||||
|
"verstak/core/capability-registry/v1"
|
||||||
],
|
],
|
||||||
"optionalRequires": [
|
"optionalRequires": [
|
||||||
"verstak/core/vault/v1",
|
"verstak/core/vault/v1",
|
||||||
"verstak/core/sync/v1"
|
"verstak/core/sync/v1",
|
||||||
|
"verstak/core/files/v1",
|
||||||
|
"verstak/core/workbench/v1"
|
||||||
],
|
],
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"vault.read",
|
"vault.read",
|
||||||
|
|
@ -24,7 +27,11 @@
|
||||||
"events.subscribe",
|
"events.subscribe",
|
||||||
"ui.register",
|
"ui.register",
|
||||||
"commands.register",
|
"commands.register",
|
||||||
"storage.namespace"
|
"storage.namespace",
|
||||||
|
"files.read",
|
||||||
|
"files.write",
|
||||||
|
"files.delete",
|
||||||
|
"workbench.open"
|
||||||
],
|
],
|
||||||
"frontend": {
|
"frontend": {
|
||||||
"entry": "frontend/dist/index.js"
|
"entry": "frontend/dist/index.js"
|
||||||
|
|
@ -74,6 +81,27 @@
|
||||||
"icon": "flask",
|
"icon": "flask",
|
||||||
"component": "PlatformTestSettings"
|
"component": "PlatformTestSettings"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"openProviders": [
|
||||||
|
{
|
||||||
|
"id": "verstak.platform-test.markdown-diagnostic",
|
||||||
|
"title": "Platform Test Markdown Diagnostic",
|
||||||
|
"priority": 100,
|
||||||
|
"component": "MarkdownDiagnosticProvider",
|
||||||
|
"supports": [
|
||||||
|
{
|
||||||
|
"kind": "vault-file",
|
||||||
|
"extensions": [".md", ".markdown"],
|
||||||
|
"contexts": ["generic-markdown", "notes-markdown"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "vault-file",
|
||||||
|
"mime": ["text/plain"],
|
||||||
|
"extensions": [".txt", ".log"],
|
||||||
|
"contexts": ["generic-text"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,15 @@ if [ "$MISSING_EXEC" -eq 0 ]; then
|
||||||
echo " ✅ all scripts executable"
|
echo " ✅ all scripts executable"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "[frontend smoke]"
|
||||||
|
if command -v node &>/dev/null; then
|
||||||
|
node "$ROOT/scripts/smoke-platform-frontend.js"
|
||||||
|
report "platform-test frontend components mount" $?
|
||||||
|
else
|
||||||
|
echo " ⚠️ node not available — skipping frontend smoke"
|
||||||
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
if [ "$FAILED" -eq 0 ]; then
|
if [ "$FAILED" -eq 0 ]; then
|
||||||
echo "✅ all checks passed"
|
echo "✅ all checks passed"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,182 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const vm = require('vm');
|
||||||
|
|
||||||
|
const root = path.resolve(__dirname, '..');
|
||||||
|
const bundlePath = path.join(root, 'plugins', 'platform-test', 'frontend', 'src', 'index.js');
|
||||||
|
const source = fs.readFileSync(bundlePath, 'utf8');
|
||||||
|
|
||||||
|
class FakeNode {
|
||||||
|
constructor(tagName) {
|
||||||
|
this.tagName = String(tagName || '').toUpperCase();
|
||||||
|
this.children = [];
|
||||||
|
this.attributes = {};
|
||||||
|
this.style = {};
|
||||||
|
this.className = '';
|
||||||
|
this.id = '';
|
||||||
|
this.innerHTML = '';
|
||||||
|
this.textContent = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
appendChild(node) {
|
||||||
|
if (!(node instanceof FakeNode)) {
|
||||||
|
throw new TypeError("Argument 1 ('node') to Node.appendChild must be an instance of Node");
|
||||||
|
}
|
||||||
|
this.children.push(node);
|
||||||
|
this.firstChild = this.children[0] || null;
|
||||||
|
this.lastChild = this.children[this.children.length - 1] || null;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
setAttribute(name, value) {
|
||||||
|
this.attributes[name] = String(value);
|
||||||
|
if (name === 'id') this.id = String(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
addEventListener() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeDocument() {
|
||||||
|
return {
|
||||||
|
head: new FakeNode('head'),
|
||||||
|
createElement(tagName) {
|
||||||
|
return new FakeNode(tagName);
|
||||||
|
},
|
||||||
|
createTextNode(text) {
|
||||||
|
const node = new FakeNode('#text');
|
||||||
|
node.textContent = String(text);
|
||||||
|
return node;
|
||||||
|
},
|
||||||
|
getElementById() {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const registry = {};
|
||||||
|
const sandbox = {
|
||||||
|
console,
|
||||||
|
document: makeDocument(),
|
||||||
|
window: {
|
||||||
|
VerstakPluginRegister(pluginId, bundle) {
|
||||||
|
registry[pluginId] = bundle.components || {};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
sandbox.window.window = sandbox.window;
|
||||||
|
sandbox.window.document = sandbox.document;
|
||||||
|
|
||||||
|
vm.runInNewContext(source, sandbox, { filename: bundlePath });
|
||||||
|
|
||||||
|
const components = registry['verstak.platform-test'];
|
||||||
|
if (!components) {
|
||||||
|
throw new Error('verstak.platform-test did not register components');
|
||||||
|
}
|
||||||
|
|
||||||
|
const api = {
|
||||||
|
pluginId: 'verstak.platform-test',
|
||||||
|
settings: {
|
||||||
|
read: async () => 'initial value',
|
||||||
|
write: async () => undefined,
|
||||||
|
},
|
||||||
|
capabilities: {
|
||||||
|
has: async () => true,
|
||||||
|
list: async () => [{ name: 'verstak/platform-test/v1', pluginId: 'verstak.platform-test', status: 'draft' }],
|
||||||
|
},
|
||||||
|
commands: {
|
||||||
|
_handlers: new Map(),
|
||||||
|
register: async (commandId, handler) => {
|
||||||
|
api.commands._handlers.set(commandId, handler);
|
||||||
|
return () => { api.commands._handlers.delete(commandId); };
|
||||||
|
},
|
||||||
|
execute: async (commandId, args = {}) => {
|
||||||
|
const handler = api.commands._handlers.get(commandId);
|
||||||
|
if (!handler) throw new Error(`declared-but-unhandled: ${commandId}`);
|
||||||
|
return { status: 'handled', result: await handler(args, { status: 'declared', commandId, pluginId: api.pluginId }) };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
events: {
|
||||||
|
publish: async () => undefined,
|
||||||
|
subscribe: async () => () => undefined,
|
||||||
|
},
|
||||||
|
files: {
|
||||||
|
_entries: new Map([['', { type: 'folder' }]]),
|
||||||
|
createFolder: async (relativePath) => {
|
||||||
|
if (api.files._entries.has(relativePath)) throw new Error(`conflict: ${relativePath}`);
|
||||||
|
api.files._entries.set(relativePath, { type: 'folder' });
|
||||||
|
},
|
||||||
|
writeText: async (relativePath, content) => {
|
||||||
|
api.files._entries.set(relativePath, { type: 'file', content });
|
||||||
|
},
|
||||||
|
readText: async (relativePath) => {
|
||||||
|
if (String(relativePath).split('/')[0].toLowerCase() === '.verstak') {
|
||||||
|
throw new Error('reserved-path: .verstak is internal');
|
||||||
|
}
|
||||||
|
const entry = api.files._entries.get(relativePath);
|
||||||
|
if (!entry) throw new Error(`not-found: ${relativePath}`);
|
||||||
|
return entry.content || '';
|
||||||
|
},
|
||||||
|
list: async (relativeDir) => {
|
||||||
|
const prefix = relativeDir ? `${relativeDir}/` : '';
|
||||||
|
return Array.from(api.files._entries.entries())
|
||||||
|
.filter(([entryPath]) => entryPath.startsWith(prefix) && entryPath !== relativeDir && !entryPath.slice(prefix.length).includes('/'))
|
||||||
|
.map(([entryPath, entry]) => ({
|
||||||
|
name: path.basename(entryPath),
|
||||||
|
relativePath: entryPath,
|
||||||
|
type: entry.type,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
move: async (fromRelativePath, toRelativePath) => {
|
||||||
|
const entry = api.files._entries.get(fromRelativePath);
|
||||||
|
if (!entry) throw new Error(`not-found: ${fromRelativePath}`);
|
||||||
|
api.files._entries.set(toRelativePath, entry);
|
||||||
|
api.files._entries.delete(fromRelativePath);
|
||||||
|
},
|
||||||
|
trash: async (relativePath) => {
|
||||||
|
if (!api.files._entries.has(relativePath)) throw new Error(`not-found: ${relativePath}`);
|
||||||
|
api.files._entries.delete(relativePath);
|
||||||
|
return { originalPath: relativePath, trashPath: `.verstak/trash/files/mock/${path.basename(relativePath)}`, trashId: 'mock', deletedAt: new Date().toISOString() };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
workbench: {
|
||||||
|
openResource: async (request) => ({
|
||||||
|
status: 'opened',
|
||||||
|
providerId: 'verstak.platform-test.markdown-diagnostic',
|
||||||
|
providerPluginId: 'verstak.platform-test',
|
||||||
|
providerComponent: 'MarkdownDiagnosticProvider',
|
||||||
|
request: { mode: 'view', ...request },
|
||||||
|
}),
|
||||||
|
editResource: async (request) => ({
|
||||||
|
status: 'opened',
|
||||||
|
providerId: 'verstak.platform-test.markdown-diagnostic',
|
||||||
|
providerPluginId: 'verstak.platform-test',
|
||||||
|
providerComponent: 'MarkdownDiagnosticProvider',
|
||||||
|
request: { mode: 'edit', ...request },
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
for (const name of ['DiagnosticsPanel', 'PlatformTestSettings', 'MarkdownDiagnosticProvider']) {
|
||||||
|
const component = components[name];
|
||||||
|
if (!component || typeof component.mount !== 'function') {
|
||||||
|
throw new Error(`${name} is not mountable`);
|
||||||
|
}
|
||||||
|
const container = new FakeNode('div');
|
||||||
|
component.mount(container, {}, api);
|
||||||
|
await Promise.resolve();
|
||||||
|
await Promise.resolve();
|
||||||
|
if (container.children.length === 0) {
|
||||||
|
throw new Error(`${name} mounted no DOM nodes`);
|
||||||
|
}
|
||||||
|
if (typeof component.unmount === 'function') {
|
||||||
|
component.unmount(container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('platform-test frontend smoke passed');
|
||||||
|
})().catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue