|
1 | 1 | import * as assert from 'assert'; |
2 | 2 | import * as vscode from 'vscode'; |
| 3 | +import { CONFIG, ACCESS, ACTIONS, CONNECTION } from '../../../config'; |
3 | 4 |
|
4 | 5 | suite('Web Extension Tests', () => { |
5 | 6 | test('Sanity Check', () => { |
6 | 7 | assert.strictEqual(9 + 10, 19, '9 + 10 shouldn\'t be 21, it should be 19!'); |
7 | 8 | }); |
8 | 9 |
|
9 | | - test('Extension activates', async () => { |
10 | | - // `publisher.name` from package.json: VSC-NeuroPilot.neuropilot-base |
| 10 | + test('Extension exists', async () => { |
11 | 11 | const extension = vscode.extensions.getExtension('VSC-NeuroPilot.neuropilot-base'); |
12 | | - assert.ok(extension, 'Extension should be installed'); |
13 | | - await extension!.activate(); |
14 | | - assert.ok(extension!.isActive, 'Extension should be active'); |
| 12 | + assert.ok(extension, 'Extension vsc-neuropilot.neuropilot-base should be installed!'); |
| 13 | + await extension.activate(); |
| 14 | + assert.ok(extension!.isActive, 'Extension should be active!'); |
15 | 15 | }); |
16 | 16 |
|
17 | 17 | test('Workspace folder is correct', () => { |
18 | 18 | const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; |
19 | 19 | assert.ok(workspaceFolder, 'Workspace folder should be defined!'); |
20 | 20 | const acceptable = new Set(['test-playground', 'mount']); |
21 | | - assert.ok(acceptable.has(workspaceFolder.name), `Workspace name should be one of ${Array.from(acceptable).join(', ')}`); |
| 21 | + assert.ok(acceptable.has(workspaceFolder.name), `Workspace name should be one of ${Array.from(acceptable).join(', ')}!`); |
| 22 | + }); |
| 23 | + |
| 24 | + test('All package.json settings have corresponding config class entries', () => { |
| 25 | + const extension = vscode.extensions.getExtension('VSC-NeuroPilot.neuropilot-base'); |
| 26 | + assert.ok(extension, 'Extension should exist'); |
| 27 | + |
| 28 | + const packageJSON = extension!.packageJSON; |
| 29 | + const configuration = packageJSON.contributes?.configuration?.[0]; |
| 30 | + assert.ok(configuration, 'Configuration should exist in package.json'); |
| 31 | + |
| 32 | + const properties = configuration.properties; |
| 33 | + assert.ok(properties, 'Properties should exist in configuration'); |
| 34 | + |
| 35 | + // Get all setting keys from package.json (excluding permission settings) |
| 36 | + const settingKeys = Object.keys(properties).filter(key => |
| 37 | + key.startsWith('neuropilot.') && |
| 38 | + !key.startsWith('neuropilot.permission.'), |
| 39 | + ); |
| 40 | + |
| 41 | + // Automatically discover properties from config classes |
| 42 | + const configClasses = { |
| 43 | + CONFIG, |
| 44 | + ACCESS, |
| 45 | + CONNECTION, |
| 46 | + ACTIONS, |
| 47 | + }; |
| 48 | + |
| 49 | + const mappedSettings = new Set<string>(); |
| 50 | + const classErrors: string[] = []; |
| 51 | + |
| 52 | + // Test each config class and collect accessible properties |
| 53 | + for (const [className, classInstance] of Object.entries(configClasses)) { |
| 54 | + try { |
| 55 | + const properties = Object.getOwnPropertyNames(Object.getPrototypeOf(classInstance)) |
| 56 | + // eslint-disable-next-line @typescript-eslint/no-explicit-any |
| 57 | + .filter(prop => prop !== 'constructor' && typeof (classInstance as any)[prop] !== 'function'); |
| 58 | + |
| 59 | + // Try to access each property to see what settings it corresponds to |
| 60 | + for (const prop of properties) { |
| 61 | + try { |
| 62 | + // Access the property to trigger the getter |
| 63 | + //const value = (classInstance as any)[prop]; |
| 64 | + |
| 65 | + // Map property names to potential setting names |
| 66 | + const potentialSettings = [ |
| 67 | + `neuropilot.${prop}`, |
| 68 | + `neuropilot.${className.toLowerCase()}.${prop}`, |
| 69 | + // Handle camelCase to kebab-case conversion |
| 70 | + `neuropilot.${prop.replace(/([A-Z])/g, '-$1').toLowerCase()}`, |
| 71 | + `neuropilot.${className.toLowerCase()}.${prop.replace(/([A-Z])/g, '-$1').toLowerCase()}`, |
| 72 | + ]; |
| 73 | + |
| 74 | + // Check if any of these potential settings exist in package.json |
| 75 | + for (const setting of potentialSettings) { |
| 76 | + if ((properties as unknown as Record<string, unknown>)[setting]) { |
| 77 | + mappedSettings.add(setting); |
| 78 | + } |
| 79 | + } |
| 80 | + |
| 81 | + } catch { |
| 82 | + // Some getters might fail, that's ok |
| 83 | + } |
| 84 | + } |
| 85 | + } catch (erm) { |
| 86 | + classErrors.push(`${className}: ${erm}`); |
| 87 | + } |
| 88 | + } |
| 89 | + |
| 90 | + // Filter out deprecated settings |
| 91 | + const deprecatedSettings = [ |
| 92 | + 'neuropilot.websocketUrl', |
| 93 | + 'neuropilot.gameName', |
| 94 | + 'neuropilot.initialContext', |
| 95 | + 'neuropilot.includePattern', |
| 96 | + 'neuropilot.excludePattern', |
| 97 | + 'neuropilot.allowUnsafePaths', |
| 98 | + 'neuropilot.disabledActions', |
| 99 | + 'neuropilot.hideCopilotRequests', |
| 100 | + 'neuropilot.allowRunningAllTasks', |
| 101 | + 'neuropilot.enableCancelEvents', |
| 102 | + ]; |
| 103 | + |
| 104 | + const activeSettings = settingKeys.filter(key => !deprecatedSettings.includes(key)); |
| 105 | + |
| 106 | + // Find unmapped settings |
| 107 | + const unmappedSettings = activeSettings.filter(setting => !mappedSettings.has(setting)); |
| 108 | + |
| 109 | + // Report any class access errors |
| 110 | + if (classErrors.length > 0) { |
| 111 | + console.warn('Config class access errors:', classErrors); |
| 112 | + } |
| 113 | + |
| 114 | + // Main assertion |
| 115 | + assert.strictEqual( |
| 116 | + unmappedSettings.length, |
| 117 | + 0, |
| 118 | + `The following settings from package.json are not accessible through config classes: ${unmappedSettings.join(', ')}\n` + |
| 119 | + `Mapped settings: ${Array.from(mappedSettings).sort().join(', ')}`, |
| 120 | + ); |
| 121 | + |
| 122 | + // Verify config classes are functional |
| 123 | + try { |
| 124 | + // Test that we can access at least some properties from each class |
| 125 | + assert.ok(Object.prototype.hasOwnProperty.call(CONFIG, 'beforeContext') || typeof CONFIG.beforeContext !== 'undefined', 'CONFIG should have accessible properties'); |
| 126 | + assert.ok(Object.prototype.hasOwnProperty.call(ACCESS, 'includePattern') || typeof ACCESS.includePattern !== 'undefined', 'ACCESS should have accessible properties'); |
| 127 | + assert.ok(Object.prototype.hasOwnProperty.call(CONNECTION, 'websocketUrl') || typeof CONNECTION.websocketUrl !== 'undefined', 'CONNECTION should have accessible properties'); |
| 128 | + assert.ok(Object.prototype.hasOwnProperty.call(ACTIONS, 'disabledActions') || typeof ACTIONS.disabledActions !== 'undefined', 'ACTIONS should have accessible properties'); |
| 129 | + } catch (erm) { |
| 130 | + assert.fail(`Config class verification failed: ${erm}`); |
| 131 | + } |
22 | 132 | }); |
23 | 133 |
|
24 | 134 | // We also need a test to ensure that polyfilled modules (i.e. assert) are successfully bundled. |
| 135 | + test('assert module is polyfilled and works', () => { |
| 136 | + // eslint-disable-next-line @typescript-eslint/no-require-imports |
| 137 | + const assert = require('assert'); |
| 138 | + try { |
| 139 | + assert.strictEqual(1, 2, 'Should throw'); |
| 140 | + assert.fail('assert did not throw as expected'); |
| 141 | + } catch (erm: unknown) { |
| 142 | + assert.ok(erm instanceof assert.AssertionError, 'Error should be an AssertionError'); |
| 143 | + } |
| 144 | + }); |
25 | 145 | }); |
0 commit comments