@williamzujkowski/live-coding-music-mcp 1.0.0 → 3.0.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.
@@ -1,153 +0,0 @@
1
- import { PatternStore } from '../PatternStore';
2
- import { promises as fs } from 'fs';
3
- import path from 'path';
4
- import { tmpdir } from 'os';
5
- describe('PatternStore', () => {
6
- let store;
7
- let testDir;
8
- beforeEach(async () => {
9
- // Create a temporary directory for testing
10
- testDir = path.join(tmpdir(), 'strudel-test-' + Date.now());
11
- await fs.mkdir(testDir, { recursive: true });
12
- store = new PatternStore(testDir);
13
- });
14
- afterEach(async () => {
15
- // Clean up test directory
16
- try {
17
- await fs.rm(testDir, { recursive: true, force: true });
18
- }
19
- catch (error) {
20
- // Ignore cleanup errors
21
- }
22
- });
23
- describe('save', () => {
24
- test('should save a pattern successfully', async () => {
25
- const name = 'test-pattern';
26
- const content = 's("bd*4")';
27
- const tags = ['techno', 'test'];
28
- await store.save(name, content, tags);
29
- const files = await fs.readdir(testDir);
30
- expect(files).toContain('test-pattern.json');
31
- });
32
- test('should sanitize filename', async () => {
33
- const name = 'Test Pattern With Spaces!@#';
34
- const content = 's("bd*4")';
35
- await store.save(name, content);
36
- const files = await fs.readdir(testDir);
37
- expect(files).toContain('test_pattern_with_spaces___.json');
38
- });
39
- test('should save pattern with timestamp', async () => {
40
- const name = 'timestamped';
41
- const content = 's("bd*4")';
42
- await store.save(name, content);
43
- const pattern = await store.load(name);
44
- expect(pattern).not.toBeNull();
45
- expect(pattern.timestamp).toBeDefined();
46
- expect(new Date(pattern.timestamp).getTime()).toBeGreaterThan(0);
47
- });
48
- test('should overwrite existing pattern', async () => {
49
- const name = 'overwrite-test';
50
- await store.save(name, 's("bd*4")');
51
- await store.save(name, 's("cp*4")');
52
- const pattern = await store.load(name);
53
- expect(pattern.content).toBe('s("cp*4")');
54
- });
55
- });
56
- describe('load', () => {
57
- test('should load an existing pattern', async () => {
58
- const name = 'loadable';
59
- const content = 's("bd*4, cp*2")';
60
- const tags = ['house'];
61
- await store.save(name, content, tags);
62
- const loaded = await store.load(name);
63
- expect(loaded).not.toBeNull();
64
- expect(loaded.name).toBe(name);
65
- expect(loaded.content).toBe(content);
66
- expect(loaded.tags).toEqual(tags);
67
- });
68
- test('should return null for non-existent pattern', async () => {
69
- const loaded = await store.load('does-not-exist');
70
- expect(loaded).toBeNull();
71
- });
72
- test('should handle sanitized filenames when loading', async () => {
73
- const name = 'Pattern With Spaces';
74
- const content = 's("bd*4")';
75
- await store.save(name, content);
76
- const loaded = await store.load(name);
77
- expect(loaded).not.toBeNull();
78
- expect(loaded.name).toBe(name);
79
- });
80
- });
81
- describe('list', () => {
82
- test('should list all patterns', async () => {
83
- await store.save('pattern1', 's("bd*4")', ['techno']);
84
- await store.save('pattern2', 's("cp*4")', ['house']);
85
- await store.save('pattern3', 's("hh*8")', ['dnb']);
86
- const patterns = await store.list();
87
- expect(patterns).toHaveLength(3);
88
- expect(patterns.map(p => p.name).sort()).toEqual(['pattern1', 'pattern2', 'pattern3']);
89
- });
90
- test('should filter patterns by tag', async () => {
91
- await store.save('pattern1', 's("bd*4")', ['techno', 'dark']);
92
- await store.save('pattern2', 's("cp*4")', ['house']);
93
- await store.save('pattern3', 's("hh*8")', ['techno']);
94
- const technoPatterns = await store.list('techno');
95
- expect(technoPatterns).toHaveLength(2);
96
- expect(technoPatterns.map(p => p.name).sort()).toEqual(['pattern1', 'pattern3']);
97
- });
98
- test('should return patterns sorted by timestamp (newest first)', async () => {
99
- await store.save('old', 's("bd*4")');
100
- await new Promise(resolve => setTimeout(resolve, 10));
101
- await store.save('middle', 's("cp*4")');
102
- await new Promise(resolve => setTimeout(resolve, 10));
103
- await store.save('new', 's("hh*8")');
104
- const patterns = await store.list();
105
- expect(patterns[0].name).toBe('new');
106
- expect(patterns[1].name).toBe('middle');
107
- expect(patterns[2].name).toBe('old');
108
- });
109
- test('should return empty array for empty directory', async () => {
110
- const patterns = await store.list();
111
- expect(patterns).toEqual([]);
112
- });
113
- test('should ignore non-JSON files', async () => {
114
- await store.save('valid', 's("bd*4")');
115
- await fs.writeFile(path.join(testDir, 'invalid.txt'), 'not json');
116
- const patterns = await store.list();
117
- expect(patterns).toHaveLength(1);
118
- expect(patterns[0].name).toBe('valid');
119
- });
120
- });
121
- describe('edge cases', () => {
122
- test('should handle patterns with special characters in content', async () => {
123
- const name = 'special';
124
- const content = 's("bd*4").room(0.9)\n// Comment with "quotes"';
125
- await store.save(name, content);
126
- const loaded = await store.load(name);
127
- expect(loaded.content).toBe(content);
128
- });
129
- test('should handle very long pattern names', async () => {
130
- const name = 'a'.repeat(200);
131
- const content = 's("bd*4")';
132
- await store.save(name, content);
133
- const loaded = await store.load(name);
134
- expect(loaded).not.toBeNull();
135
- expect(loaded.name).toBe(name);
136
- });
137
- test('should handle empty tags array', async () => {
138
- const name = 'no-tags';
139
- const content = 's("bd*4")';
140
- await store.save(name, content, []);
141
- const loaded = await store.load(name);
142
- expect(loaded.tags).toEqual([]);
143
- });
144
- test('should handle unicode characters in names', async () => {
145
- const name = 'パターン-テスト';
146
- const content = 's("bd*4")';
147
- await store.save(name, content);
148
- const loaded = await store.load(name);
149
- expect(loaded).not.toBeNull();
150
- expect(loaded.name).toBe(name);
151
- });
152
- });
153
- });
@@ -1,130 +0,0 @@
1
- export class MockPage {
2
- content = '';
3
- isPlayingState = false;
4
- evaluateHandlers = new Map();
5
- url = '';
6
- async goto(url, options) {
7
- this.url = url;
8
- return {};
9
- }
10
- async waitForSelector(selector, options) {
11
- return {};
12
- }
13
- async waitForTimeout(timeout) {
14
- return new Promise(resolve => setTimeout(resolve, Math.min(timeout, 10)));
15
- }
16
- async click(selector, options) {
17
- if (selector.includes('play')) {
18
- this.isPlayingState = true;
19
- }
20
- else if (selector.includes('stop')) {
21
- this.isPlayingState = false;
22
- }
23
- }
24
- async type(text, options) {
25
- this.content += text;
26
- }
27
- async route(url, handler) {
28
- // Mock route interception
29
- }
30
- keyboard = {
31
- press: async (key) => {
32
- if (key.includes('Enter')) {
33
- this.isPlayingState = true;
34
- }
35
- else if (key.includes('Period')) {
36
- this.isPlayingState = false;
37
- }
38
- else if (key.includes('A')) {
39
- this.content = '';
40
- }
41
- },
42
- type: async (text) => {
43
- this.content += text;
44
- }
45
- };
46
- async evaluate(fn, ...args) {
47
- // Simulate common evaluate calls
48
- const fnString = fn.toString();
49
- if (fnString.includes('strudelAudioAnalyzer')) {
50
- if (fnString.includes('connect()')) {
51
- return undefined;
52
- }
53
- if (fnString.includes('analyze()')) {
54
- return {
55
- connected: true,
56
- timestamp: Date.now(),
57
- features: {
58
- average: 50,
59
- peak: 100,
60
- peakFrequency: 440,
61
- centroid: 250,
62
- bass: 60,
63
- lowMid: 50,
64
- mid: 40,
65
- highMid: 30,
66
- treble: 20,
67
- isPlaying: this.isPlayingState,
68
- isSilent: !this.isPlayingState,
69
- bassToTrebleRatio: '3.00',
70
- brightness: 'balanced'
71
- }
72
- };
73
- }
74
- }
75
- if (fnString.includes('.cm-content')) {
76
- if (fnString.includes('textContent')) {
77
- return this.content;
78
- }
79
- if (fnString.includes('dispatch')) {
80
- const newContent = args[0];
81
- if (typeof newContent === 'string') {
82
- this.content = newContent;
83
- }
84
- return undefined;
85
- }
86
- }
87
- // Allow custom handlers
88
- for (const [key, handler] of this.evaluateHandlers.entries()) {
89
- if (fnString.includes(key)) {
90
- return handler(...args);
91
- }
92
- }
93
- return undefined;
94
- }
95
- setEvaluateHandler(key, handler) {
96
- this.evaluateHandlers.set(key, handler);
97
- }
98
- getContent() {
99
- return this.content;
100
- }
101
- setContent(content) {
102
- this.content = content;
103
- }
104
- isPlaying() {
105
- return this.isPlayingState;
106
- }
107
- }
108
- export class MockBrowserContext {
109
- async newPage() {
110
- return new MockPage();
111
- }
112
- }
113
- export class MockBrowser {
114
- closed = false;
115
- async newContext(options) {
116
- return new MockBrowserContext();
117
- }
118
- async close() {
119
- this.closed = true;
120
- }
121
- isClosed() {
122
- return this.closed;
123
- }
124
- }
125
- export const createMockBrowser = () => {
126
- return new MockBrowser();
127
- };
128
- export const createMockPage = () => {
129
- return new MockPage();
130
- };