/** * ICD Test Runner — CRUD: Editor Package (v0.31.0) * Tests the editor .pkg lifecycle: install → surface accessible → * settings → export/import round-trip → core removal verification. * Uses T.crud.buildTestZip() from _helpers.js. */ (function () { 'use strict'; var T = window.ICD; if (!T) return; if (!T.crud) T.crud = {}; var buildTestZip = T.crud.buildTestZip; T.crud.editorPackage = async function (testTag) { if (T.user.role !== 'admin') return; // ── edpkg: Editor package install + lifecycle ── var editorPkgId = null; await T.test('crud', 'editorPackage', 'edpkg: POST /admin/packages/install (editor .pkg)', async function () { var manifest = JSON.stringify({ id: 'editor', title: 'Editor', type: 'full', version: '0.31.0', tier: 'browser', author: 'Switchboard Core', description: 'Code editor with workspace management', route: '/s/editor', layout: 'editor', permissions: [], settings: [ { key: 'font_size', label: 'Font Size', type: 'number', 'default': 13 }, { key: 'tab_size', label: 'Tab Size', type: 'number', 'default': 4 }, { key: 'word_wrap', label: 'Word Wrap', type: 'boolean', 'default': false } ] }); var zipBlob = buildTestZip({ 'manifest.json': manifest, 'js/main.js': '// editor package entry point\nconsole.log("[EditorPkg] loaded");', 'css/main.css': '.surface-editor { display: flex; }' }); var d = await T.apiUpload('/admin/packages/install', zipBlob, 'editor.pkg'); T.assert(d.id === 'editor', 'package id should be "editor", got: ' + d.id); editorPkgId = d.id; T.registerCleanup(async function () { if (editorPkgId) { var token = await T.getAuthToken(); return T.authFetch(token, 'DELETE', '/admin/packages/' + editorPkgId); } }); }); if (!editorPkgId) return; await T.test('crud', 'editorPackage', 'edpkg: GET /admin/packages/:id (verify type full)', async function () { var d = await T.apiGet('/admin/packages/' + editorPkgId); T.assertShape(d, T.S.extension, 'editor package'); T.assert(d.type === 'full', 'type should be "full", got: ' + d.type); T.assert(d.tier === 'browser', 'tier should be "browser", got: ' + d.tier); T.assert(d.source === 'extension', 'source should be "extension", got: ' + d.source); T.assert(d.enabled === true, 'newly installed package should be enabled'); T.assert(d.version === '0.31.0', 'version mismatch'); }); await T.test('crud', 'editorPackage', 'edpkg: GET /admin/packages (editor in list)', async function () { var d = await T.apiGet('/admin/packages'); T.assertHasKey(d, 'data', '/admin/packages'); var found = d.data.some(function (s) { return s.id === 'editor'; }); T.assert(found, 'editor package should appear in surfaces list'); }); await T.test('crud', 'editorPackage', 'edpkg: core /editor removed (404)', async function () { // The old /editor route should no longer exist — it was removed in CS1. // We test by checking the surfaces list for a core editor entry. var d = await T.apiGet('/admin/packages'); var surfaces = d.data || []; var coreEditor = surfaces.find(function (s) { return s.id === 'editor' && s.source === 'core'; }); T.assert(!coreEditor, 'no core editor surface should exist — editor is now a package'); }); // ── Settings ── await T.test('crud', 'editorPackage', 'edpkg: GET /admin/packages/:id/settings', async function () { var d = await T.apiGet('/admin/packages/' + editorPkgId + '/settings'); // Settings endpoint returns the current settings (or defaults) T.assert(typeof d === 'object', 'settings should be an object'); }); await T.test('crud', 'editorPackage', 'edpkg: PUT /admin/packages/:id/settings', async function () { var settings = { font_size: 16, tab_size: 2, word_wrap: true }; var d = await T.apiPut('/admin/packages/' + editorPkgId + '/settings', settings); T.assert(d._status === 200 || d._status === 204 || !d._status, 'settings PUT should succeed'); // Verify persistence var check = await T.apiGet('/admin/packages/' + editorPkgId + '/settings'); var vals = check.values || {}; T.assert(vals.font_size === 16, 'font_size should persist as 16'); }); // ── Export/Import Round-Trip ── var exportBlob = null; await T.test('crud', 'editorPackage', 'edpkg: GET /admin/packages/:id/export', async function () { var token = await T.getAuthToken(); var resp = await fetch(T.base + '/api/v1/admin/packages/' + editorPkgId + '/export', { headers: { 'Authorization': 'Bearer ' + token } }); T.assert(resp.ok, 'export should return 200, got: ' + resp.status); exportBlob = await resp.blob(); T.assert(exportBlob.size > 0, 'export blob should not be empty'); }); if (exportBlob) { await T.test('crud', 'editorPackage', 'edpkg: DELETE + re-import round-trip', async function () { // Delete the editor package var token = await T.getAuthToken(); var delResp = await T.authFetch(token, 'DELETE', '/admin/packages/' + editorPkgId); T.assert(!delResp._status || delResp._status < 300, 'delete should succeed'); // Re-import from exported blob var d = await T.apiUpload('/admin/packages/install', exportBlob, 'editor.pkg'); T.assert(d.id === 'editor', 'reimported package id should be "editor"'); // Verify it's back var check = await T.apiGet('/admin/packages/' + editorPkgId); T.assert(check.id === 'editor', 'reimported editor should be readable'); T.assert(check.type === 'full', 'reimported type should be "full"'); }); } // ── Extension Nav ── await T.test('crud', 'editorPackage', 'edpkg: editor in extension nav items', async function () { // When installed, editor should appear in the surfaces list as an extension surface var d = await T.apiGet('/admin/packages'); var surfaces = d.data || []; var editorSurface = surfaces.find(function (s) { return s.id === 'editor'; }); T.assert(editorSurface, 'editor should be in surfaces list'); T.assert(editorSurface.source === 'extension', 'editor source should be "extension"'); T.assert(editorSurface.enabled === true, 'editor should be enabled'); }); }; })();