@pheem49/mint 1.2.4 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -23
- package/index.html +16 -0
- package/main.js +36 -83
- package/mint-cli-logic.js +19 -0
- package/mint-cli.js +316 -39
- package/package.json +20 -3
- package/src/AI_Brain/Gemini_API.js +439 -20
- package/src/AI_Brain/agent_orchestrator.js +73 -0
- package/src/AI_Brain/autonomous_brain.js +2 -0
- package/src/AI_Brain/knowledge_base.js +199 -125
- package/src/AI_Brain/memory_store.js +318 -0
- package/src/Automation_Layer/file_operations.js +41 -19
- package/src/CLI/chat_router.js +181 -0
- package/src/CLI/chat_ui.js +321 -110
- package/src/CLI/code_agent.js +556 -0
- package/src/CLI/code_session_memory.js +62 -0
- package/src/CLI/list_features.js +1 -0
- package/src/CLI/onboarding.js +53 -6
- package/src/CLI/workspace_manager.js +81 -0
- package/src/Plugins/mcp_manager.js +95 -0
- package/src/Plugins/plugin_manager.js +2 -2
- package/src/Plugins/spotify.js +168 -40
- package/src/Plugins/system_monitor.js +72 -0
- package/src/System/config_manager.js +61 -8
- package/src/System/granular_automation.js +88 -0
- package/src/System/notifications.js +23 -0
- package/src/UI/settings.html +167 -65
- package/src/UI/settings.js +253 -42
- package/tests/agent_orchestrator.test.js +41 -0
- package/tests/config_manager.test.js +141 -0
- package/tests/memory_store.test.js +185 -0
- package/tests/spotify.test.js +201 -0
- package/tests/system_monitor.test.js +37 -0
- package/tests/workspace_manager.test.js +41 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests: memory_store.js
|
|
3
|
+
* Tests profile CRUD, usage pattern recording, and getUserContext output.
|
|
4
|
+
* Uses an isolated in-memory DB path via MINT_TEST_DB env var.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
|
|
11
|
+
// ── Isolate DB per test run ─────────────────────────────────────────────────
|
|
12
|
+
let tempDir;
|
|
13
|
+
let memStore;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
jest.resetModules();
|
|
17
|
+
|
|
18
|
+
// Create isolated temp directory
|
|
19
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mint-mem-test-'));
|
|
20
|
+
|
|
21
|
+
// Patch os.homedir so memory_store uses our temp .mint dir
|
|
22
|
+
jest.spyOn(os, 'homedir').mockReturnValue(tempDir);
|
|
23
|
+
|
|
24
|
+
// Require fresh instance (no cache)
|
|
25
|
+
memStore = require('../src/AI_Brain/memory_store');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
jest.restoreAllMocks();
|
|
30
|
+
jest.resetModules();
|
|
31
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// ── Profile tests ──────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
describe('memory_store — setProfile / getProfile', () => {
|
|
37
|
+
test('stores and retrieves a string value', () => {
|
|
38
|
+
memStore.setProfile('test_key', 'hello');
|
|
39
|
+
expect(memStore.getProfile('test_key')).toBe('hello');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('returns defaultValue when key does not exist', () => {
|
|
43
|
+
expect(memStore.getProfile('nonexistent', 'default_val')).toBe('default_val');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('overwrites existing key', () => {
|
|
47
|
+
memStore.setProfile('key', 'first');
|
|
48
|
+
memStore.setProfile('key', 'second');
|
|
49
|
+
expect(memStore.getProfile('key')).toBe('second');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('stores numeric value as string', () => {
|
|
53
|
+
memStore.setProfile('count', 42);
|
|
54
|
+
expect(memStore.getProfile('count')).toBe('42');
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// ── recordInteraction tests ────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
describe('memory_store — recordInteraction', () => {
|
|
61
|
+
test('records preferred language as thai for thai text', () => {
|
|
62
|
+
memStore.recordInteraction('ช่วยเขียนโค้ดให้หน่อยนะคะ', 'โค้ดค่ะ');
|
|
63
|
+
expect(memStore.getProfile('preferred_language')).toBe('thai');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('records preferred language as english for english text', () => {
|
|
67
|
+
memStore.recordInteraction('please help me write a function', 'sure!');
|
|
68
|
+
expect(memStore.getProfile('preferred_language')).toBe('english');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('increments total_interactions counter', () => {
|
|
72
|
+
memStore.recordInteraction('msg 1', 'reply 1');
|
|
73
|
+
memStore.recordInteraction('msg 2', 'reply 2');
|
|
74
|
+
expect(memStore.getProfile('total_interactions')).toBe('2');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('records coding keywords as patterns', () => {
|
|
78
|
+
memStore.recordInteraction('fix the Python script bug', 'sure!');
|
|
79
|
+
const patterns = memStore.getTopPatterns(10).map(p => p.pattern);
|
|
80
|
+
// 'python' and 'script' are keywords extracted (stop words removed)
|
|
81
|
+
expect(patterns.some(p => ['python', 'script', 'fix'].includes(p))).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('does not throw on empty message', () => {
|
|
85
|
+
expect(() => memStore.recordInteraction('', '')).not.toThrow();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// ── getTopPatterns tests ───────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
describe('memory_store — getTopPatterns', () => {
|
|
92
|
+
test('returns patterns sorted by count descending', () => {
|
|
93
|
+
memStore.recordInteraction('python python python', 'ok');
|
|
94
|
+
memStore.recordInteraction('python javascript', 'ok');
|
|
95
|
+
const patterns = memStore.getTopPatterns(5);
|
|
96
|
+
const patternNames = patterns.map(p => p.pattern);
|
|
97
|
+
expect(patternNames[0]).toBe('python'); // python used most
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('returns empty array when no interactions recorded', () => {
|
|
101
|
+
expect(memStore.getTopPatterns(5)).toEqual([]);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// ── getUserContext tests ───────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
describe('memory_store — getUserContext', () => {
|
|
108
|
+
test('returns empty string when no data', () => {
|
|
109
|
+
expect(memStore.getUserContext()).toBe('');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('includes preferred_language in context output', () => {
|
|
113
|
+
memStore.recordInteraction('สวัสดีครับ', 'สวัสดีค่ะ');
|
|
114
|
+
const ctx = memStore.getUserContext();
|
|
115
|
+
expect(ctx).toContain('thai');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('includes total_interactions in context', () => {
|
|
119
|
+
memStore.recordInteraction('hello', 'hi');
|
|
120
|
+
const ctx = memStore.getUserContext();
|
|
121
|
+
expect(ctx).toContain('1');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('context starts with the user context header', () => {
|
|
125
|
+
memStore.setProfile('preferred_language', 'english');
|
|
126
|
+
const ctx = memStore.getUserContext();
|
|
127
|
+
expect(ctx).toContain('[LONG-TERM USER CONTEXT');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('context ends with END marker', () => {
|
|
131
|
+
memStore.setProfile('preferred_language', 'thai');
|
|
132
|
+
const ctx = memStore.getUserContext();
|
|
133
|
+
expect(ctx).toContain('[END USER CONTEXT]');
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// ── Response Cache tests ──────────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
describe('memory_store — response cache', () => {
|
|
140
|
+
test('stores and retrieves a response', () => {
|
|
141
|
+
const query = 'What is 2+2?';
|
|
142
|
+
const response = { response: '4', action: { type: 'none' } };
|
|
143
|
+
memStore.cacheResponse(query, response);
|
|
144
|
+
|
|
145
|
+
const cached = memStore.getCachedResponse(query);
|
|
146
|
+
expect(cached.response).toBe('4');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('returns null for missing query', () => {
|
|
150
|
+
expect(memStore.getCachedResponse('where is my cat?')).toBe(null);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('is case-insensitive and ignores whitespace', () => {
|
|
154
|
+
const query = ' Hello World ';
|
|
155
|
+
const response = { response: 'Hi!', action: { type: 'none' } };
|
|
156
|
+
memStore.cacheResponse(query, response);
|
|
157
|
+
|
|
158
|
+
const cached = memStore.getCachedResponse('hello world');
|
|
159
|
+
expect(cached.response).toBe('Hi!');
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// ── saveSessionSummary / getRecentMemories tests ───────────────────────────
|
|
164
|
+
|
|
165
|
+
describe('memory_store — session summaries', () => {
|
|
166
|
+
test('saves and retrieves a summary', () => {
|
|
167
|
+
memStore.saveSessionSummary('User asked about Python scripting', ['python', 'coding']);
|
|
168
|
+
const memories = memStore.getRecentMemories(5);
|
|
169
|
+
expect(memories.length).toBe(1);
|
|
170
|
+
expect(memories[0].summary).toBe('User asked about Python scripting');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test('ignores too-short summaries', () => {
|
|
174
|
+
memStore.saveSessionSummary('ok', []);
|
|
175
|
+
const memories = memStore.getRecentMemories(5);
|
|
176
|
+
expect(memories.length).toBe(0);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test('returns most recent summaries first', () => {
|
|
180
|
+
memStore.saveSessionSummary('First session about web development', []);
|
|
181
|
+
memStore.saveSessionSummary('Second session about machine learning', []);
|
|
182
|
+
const memories = memStore.getRecentMemories(5);
|
|
183
|
+
expect(memories[0].summary).toContain('Second');
|
|
184
|
+
});
|
|
185
|
+
});
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests: spotify.js plugin
|
|
3
|
+
* Tests the plugin interface and all action handlers.
|
|
4
|
+
* Mocks exec/playerctl so no real Spotify needed.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// ── Mock child_process before requiring the module ─────────────────────────
|
|
8
|
+
jest.mock('child_process', () => ({
|
|
9
|
+
exec: jest.fn(),
|
|
10
|
+
execSync: jest.fn(),
|
|
11
|
+
promisify: undefined, // will use our mock exec
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
// Mock promisify to return our mock exec as async
|
|
15
|
+
const { exec: mockExec } = require('child_process');
|
|
16
|
+
jest.mock('util', () => ({
|
|
17
|
+
...jest.requireActual('util'),
|
|
18
|
+
promisify: (fn) => {
|
|
19
|
+
// Return an async wrapper around mockExec
|
|
20
|
+
return (...args) => new Promise((resolve, reject) => {
|
|
21
|
+
mockExec(...args, (err, stdout, stderr) => {
|
|
22
|
+
if (err) reject(err);
|
|
23
|
+
else resolve({ stdout: stdout || '', stderr: stderr || '' });
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
let spotify;
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
jest.resetModules();
|
|
33
|
+
jest.clearAllMocks();
|
|
34
|
+
spotify = require('../src/Plugins/spotify');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// ── Plugin interface tests ─────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
describe('Spotify Plugin — interface', () => {
|
|
40
|
+
test('has required plugin fields', () => {
|
|
41
|
+
expect(spotify.name).toBe('spotify');
|
|
42
|
+
expect(typeof spotify.description).toBe('string');
|
|
43
|
+
expect(typeof spotify.execute).toBe('function');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('description mentions valid commands', () => {
|
|
47
|
+
expect(spotify.description).toMatch(/play/);
|
|
48
|
+
expect(spotify.description).toMatch(/pause/);
|
|
49
|
+
expect(spotify.description).toMatch(/next/);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// ── Playback control tests ─────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
describe('Spotify Plugin — playback commands', () => {
|
|
56
|
+
const playbackCmds = ['play', 'pause', 'stop', 'next', 'previous'];
|
|
57
|
+
|
|
58
|
+
playbackCmds.forEach(cmd => {
|
|
59
|
+
test(`executes "${cmd}" and returns success message`, async () => {
|
|
60
|
+
mockExec.mockImplementation((command, callback) => {
|
|
61
|
+
callback(null, '', '');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const result = await spotify.execute(cmd);
|
|
65
|
+
expect(typeof result).toBe('string');
|
|
66
|
+
expect(result.length).toBeGreaterThan(0);
|
|
67
|
+
// Should not contain error text
|
|
68
|
+
expect(result).not.toMatch(/เกิดข้อผิดพลาด/);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('returns "spotify not running" error when playerctl reports no players', async () => {
|
|
73
|
+
const err = new Error('No players found');
|
|
74
|
+
err.stderr = 'No players found';
|
|
75
|
+
mockExec.mockImplementation((command, callback) => {
|
|
76
|
+
callback(err, '', 'No players found');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const result = await spotify.execute('play');
|
|
80
|
+
expect(result).toContain('Spotify ยังไม่ได้เปิดอยู่');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('returns playerctl missing error when exit code is 127', async () => {
|
|
84
|
+
const err = new Error('command not found: playerctl');
|
|
85
|
+
err.code = 127;
|
|
86
|
+
mockExec.mockImplementation((command, callback) => {
|
|
87
|
+
callback(err, '', '');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const result = await spotify.execute('play');
|
|
91
|
+
expect(result).toContain('playerctl');
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// ── Now Playing tests ──────────────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
describe('Spotify Plugin — now_playing', () => {
|
|
98
|
+
test('returns formatted now playing string', async () => {
|
|
99
|
+
let callCount = 0;
|
|
100
|
+
mockExec.mockImplementation((command, callback) => {
|
|
101
|
+
callCount++;
|
|
102
|
+
// Mock: title, artist, album, status calls
|
|
103
|
+
if (command.includes('title')) callback(null, 'Dynamite\n', '');
|
|
104
|
+
else if (command.includes('artist')) callback(null, 'BTS\n', '');
|
|
105
|
+
else if (command.includes('album')) callback(null, 'BE\n', '');
|
|
106
|
+
else if (command.includes('status')) callback(null, 'Playing\n', '');
|
|
107
|
+
else callback(null, '', '');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const result = await spotify.execute('now_playing');
|
|
111
|
+
expect(result).toContain('Dynamite');
|
|
112
|
+
expect(result).toContain('BTS');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('"status" alias also triggers now_playing', async () => {
|
|
116
|
+
mockExec.mockImplementation((command, callback) => {
|
|
117
|
+
if (command.includes('title')) callback(null, 'Test Song\n', '');
|
|
118
|
+
else if (command.includes('artist')) callback(null, 'Test Artist\n', '');
|
|
119
|
+
else if (command.includes('album')) callback(null, 'Test Album\n', '');
|
|
120
|
+
else if (command.includes('status')) callback(null, 'Paused\n', '');
|
|
121
|
+
else callback(null, '', '');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const result = await spotify.execute('status');
|
|
125
|
+
expect(result).toContain('Test Song');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// ── Volume tests ───────────────────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
describe('Spotify Plugin — volume', () => {
|
|
132
|
+
test('sets volume to valid level', async () => {
|
|
133
|
+
mockExec.mockImplementation((command, callback) => {
|
|
134
|
+
callback(null, '', '');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const result = await spotify.execute('volume 70');
|
|
138
|
+
expect(result).toContain('70%');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('rejects invalid volume level > 100', async () => {
|
|
142
|
+
const result = await spotify.execute('volume 150');
|
|
143
|
+
expect(result).toContain('0-100');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('rejects non-numeric volume', async () => {
|
|
147
|
+
const result = await spotify.execute('volume abc');
|
|
148
|
+
expect(result).toContain('0-100');
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// ── Shuffle tests ──────────────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
describe('Spotify Plugin — shuffle', () => {
|
|
155
|
+
test('enables shuffle with "shuffle on"', async () => {
|
|
156
|
+
mockExec.mockImplementation((command, callback) => {
|
|
157
|
+
callback(null, '', '');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const result = await spotify.execute('shuffle on');
|
|
161
|
+
expect(result).toContain('Shuffle');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('disables shuffle with "shuffle off"', async () => {
|
|
165
|
+
mockExec.mockImplementation((command, callback) => {
|
|
166
|
+
callback(null, '', '');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const result = await spotify.execute('shuffle off');
|
|
170
|
+
expect(result).toContain('Shuffle');
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// ── Search tests ───────────────────────────────────────────────────────────
|
|
175
|
+
|
|
176
|
+
describe('Spotify Plugin — search', () => {
|
|
177
|
+
test('returns message with search query', async () => {
|
|
178
|
+
// xdg-open will fail in test env, but searchSpotify catches and returns URL
|
|
179
|
+
const result = await spotify.execute('search BTS Dynamite');
|
|
180
|
+
expect(result).toContain('BTS Dynamite');
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test('empty search query returns error message', async () => {
|
|
184
|
+
const result = await spotify.execute('search ');
|
|
185
|
+
expect(result).toContain('กรุณาระบุ');
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// ── Unknown command tests ──────────────────────────────────────────────────
|
|
190
|
+
|
|
191
|
+
describe('Spotify Plugin — unknown command', () => {
|
|
192
|
+
test('returns helpful error for unknown target', async () => {
|
|
193
|
+
const result = await spotify.execute('dance');
|
|
194
|
+
expect(result).toContain('ไม่รู้จักคำสั่ง');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test('returns helpful error for empty target', async () => {
|
|
198
|
+
const result = await spotify.execute('');
|
|
199
|
+
expect(result).toContain('ไม่รู้จักคำสั่ง');
|
|
200
|
+
});
|
|
201
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests: system_monitor.js plugin
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const systemMonitor = require('../src/Plugins/system_monitor');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
|
|
8
|
+
describe('System Monitor Plugin', () => {
|
|
9
|
+
test('has required plugin fields', () => {
|
|
10
|
+
expect(systemMonitor.name).toBe('system_monitor');
|
|
11
|
+
expect(typeof systemMonitor.description).toBe('string');
|
|
12
|
+
expect(typeof systemMonitor.execute).toBe('function');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('returns stats for "stats" target', async () => {
|
|
16
|
+
const result = await systemMonitor.execute('stats');
|
|
17
|
+
expect(result).toContain('System Health Report');
|
|
18
|
+
expect(result).toContain('CPU Load');
|
|
19
|
+
expect(result).toContain('Memory');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('returns cpu info for "cpu" target', async () => {
|
|
23
|
+
const result = await systemMonitor.execute('cpu');
|
|
24
|
+
expect(result).toContain('CPU Load');
|
|
25
|
+
expect(result).toContain('Cores');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('returns memory info for "memory" target', async () => {
|
|
29
|
+
const result = await systemMonitor.execute('memory');
|
|
30
|
+
expect(result).toContain('Memory Status');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('returns disk info for "disk" target', async () => {
|
|
34
|
+
const result = await systemMonitor.execute('disk');
|
|
35
|
+
expect(result).toContain('Disk Status');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests: workspace_manager.js
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const wsManager = require('../src/CLI/workspace_manager');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
|
|
10
|
+
describe('Workspace Manager', () => {
|
|
11
|
+
let tempDir;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
// Create a temp workspace directory
|
|
15
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mint-ws-test-'));
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('adds and lists workspaces', () => {
|
|
23
|
+
wsManager.addWorkspace('test-ws', tempDir, 'Be careful with files');
|
|
24
|
+
const list = wsManager.listWorkspaces();
|
|
25
|
+
expect(list['test-ws']).toBeDefined();
|
|
26
|
+
expect(list['test-ws'].path).toBe(path.resolve(tempDir));
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('detects workspace by path', () => {
|
|
30
|
+
wsManager.addWorkspace('test-ws', tempDir);
|
|
31
|
+
const ws = wsManager.getWorkspaceByPath(tempDir);
|
|
32
|
+
expect(ws.name).toBe('test-ws');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('removes workspaces', () => {
|
|
36
|
+
wsManager.addWorkspace('test-ws', tempDir);
|
|
37
|
+
wsManager.removeWorkspace('test-ws');
|
|
38
|
+
const list = wsManager.listWorkspaces();
|
|
39
|
+
expect(list['test-ws']).toBeUndefined();
|
|
40
|
+
});
|
|
41
|
+
});
|