Skip to content

Commit ef7673e

Browse files
authored
Merge pull request #13 from JuliaPluto/dr14-refactor
fix: reconnect to notebook and new cell add
2 parents 3998829 + 1213e2a commit ef7673e

File tree

7 files changed

+823
-41
lines changed

7 files changed

+823
-41
lines changed

.github/workflows/ci.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,20 @@ jobs:
7474
node-version: '22'
7575
cache: 'npm'
7676

77+
- name: Setup Julia
78+
uses: julia-actions/setup-julia@v2
79+
with:
80+
version: '1.11'
81+
82+
- name: Install Pluto.jl
83+
run: julia -e 'using Pkg; Pkg.add("Pluto")'
84+
7785
- name: Install dependencies
7886
run: npm ci
7987

8088
- name: Run unit tests
8189
run: npm run test:unit
82-
# //TODO: figureout how to run these in CI and what actually to test there
90+
# //TODO: figureout how to run these in CI and what actually to test there
8391
# - name: Run vscode tests
8492
# run: npm test:unit
8593

docs/TESTING.md

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
# Testing Guide
2+
3+
## PlutoManager Tests
4+
5+
The PlutoManager tests use **comprehensive integration testing** with a real Pluto server.
6+
7+
### Test Approach
8+
9+
**Single Workflow Test** - One comprehensive test that:
10+
11+
- Opens a notebook
12+
- Performs all operations sequentially
13+
- Verifies each step works correctly
14+
- Tests the complete lifecycle end-to-end
15+
16+
This approach provides:
17+
18+
- ✅ Realistic usage patterns
19+
- ✅ Fast execution (no repeated setup/teardown)
20+
- ✅ Clear verification of functionality
21+
- ✅ Easy to understand workflow
22+
- ✅ Real Pluto server interaction
23+
24+
### Test Setup
25+
26+
- **beforeAll**:
27+
- Starts Julia Pluto server on port 1234
28+
- Creates test notebook file
29+
- Creates PlutoManager instance
30+
31+
- **afterAll**:
32+
- Disposes PlutoManager
33+
- Cleans up test files
34+
- Kills Pluto server
35+
36+
### Test Coverage
37+
38+
**One comprehensive integration test** that verifies all functionality:
39+
40+
1. **Initial State** (Steps 1)
41+
- Manager not connected
42+
- No open notebooks
43+
- Server URL correct
44+
45+
2. **Connection** (Steps 2-3)
46+
- Connect to server
47+
- Verify duplicate connection handling
48+
49+
3. **Event System Setup** (Step 4)
50+
- Register event listeners
51+
- Prepare to track events
52+
53+
4. **Open Notebook** (Steps 5-7)
54+
- Get worker for notebook
55+
- Verify worker caching
56+
- Check open notebooks list
57+
- Verify notebookOpened event
58+
59+
5. **Cell Operations** (Steps 8-12)
60+
- Execute existing cell
61+
- Add new cell
62+
- Execute new cell
63+
- Delete cell
64+
- Execute ephemeral code
65+
66+
6. **Event Testing** (Step 13)
67+
- Emit cellUpdated event
68+
- Verify event received
69+
70+
7. **Multiple Notebooks** (Step 14)
71+
- Open second notebook with custom content
72+
- Verify different worker
73+
- Close second notebook
74+
- Verify notebookClosed event
75+
76+
8. **Event Listener Removal** (Step 15)
77+
- Add and remove listener
78+
- Verify listener not called
79+
80+
9. **Close Operations** (Steps 16-18)
81+
- Close main notebook
82+
- Verify removed from list
83+
- Test graceful error handling
84+
85+
10. **Error Handling** (Step 19)
86+
- Test missing file error
87+
88+
11. **Final State** (Step 20)
89+
- Verify still connected
90+
- No notebooks open
91+
92+
**Plus 2 constructor tests**:
93+
94+
- Default port initialization
95+
- Custom server URL
96+
97+
### Running Tests
98+
99+
```bash
100+
# Run all tests (in parallel by default)
101+
npm test
102+
103+
# Run only PlutoManager tests
104+
npm run test:unit -- plutoManager.test.ts
105+
106+
# Run with coverage
107+
npm run test:coverage
108+
109+
# Run serially (for debugging)
110+
npm run test:unit -- --runInBand plutoManager.test.ts
111+
```
112+
113+
### Test Timeouts
114+
115+
- Main integration test: **2 minutes** (complete workflow)
116+
- `beforeAll`: **2 minutes** (for Pluto server startup)
117+
- `afterAll`: **30 seconds** (for cleanup)
118+
- Constructor tests: **5 seconds** (default)
119+
120+
### Mocking Strategy
121+
122+
**What we mock:**
123+
124+
- `vscode` module - Only what's needed by PlutoServerTaskManager:
125+
- `workspace.getConfiguration` (for Julia settings)
126+
- Task-related types (TaskExecution, ShellExecution, etc.)
127+
- Notebook types (for serializer tests)
128+
129+
**What we DON'T mock:**
130+
131+
- ❌ Pluto server - use real Julia Pluto server
132+
- ❌ File system - use real file operations
133+
-@plutojl/rainbow - use real library
134+
- ❌ PlutoServerTaskManager - use real implementation
135+
136+
This integration testing approach provides:
137+
138+
- ✅ More realistic test scenarios
139+
- ✅ Better confidence in functionality
140+
- ✅ Easier test maintenance
141+
- ✅ Real interaction testing with Pluto
142+
- ✅ Parallel test execution
143+
144+
### Prerequisites
145+
146+
**Local Development:**
147+
148+
- Julia 1.11+ must be installed and available in PATH
149+
- Pluto.jl package must be installed: `julia -e 'using Pkg; Pkg.add("Pluto")'`
150+
151+
**CI/CD:**
152+
153+
- GitHub Actions CI automatically installs Julia 1.11 and Pluto.jl
154+
- The test job uses `julia-actions/setup-julia@v2` to set up Julia
155+
- Pluto.jl is installed before running tests
156+
157+
### Test Structure
158+
159+
**Sequential Workflow Test**:
160+
161+
```typescript
162+
it("should perform complete notebook lifecycle workflow", async () => {
163+
// Step 1: Test initial state
164+
expect(manager.isConnected()).toBe(false);
165+
166+
// Step 2: Connect to server
167+
await manager.connect();
168+
expect(manager.isConnected()).toBe(true);
169+
170+
// Step 3-20: Sequential operations
171+
// - Open notebook
172+
// - Execute cells
173+
// - Add/delete cells
174+
// - Test events
175+
// - Close notebook
176+
// - Verify cleanup
177+
}, 120000);
178+
```
179+
180+
**Benefits of Sequential Testing**:
181+
182+
- ✅ Tests realistic usage patterns
183+
- ✅ Verifies operations work together
184+
- ✅ Faster than isolated tests (no repeated setup)
185+
- ✅ Easy to follow workflow
186+
- ✅ Clear failure points (which step failed)

src/__tests__/__mocks__/vscode.ts

Lines changed: 98 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// Mock vscode module for Jest tests
2+
// Only includes what's actually used by the tests
23

4+
// Notebook-related types (for serializer tests)
35
export enum NotebookCellKind {
46
Markup = 1,
57
Code = 2,
@@ -18,7 +20,7 @@ export class NotebookData {
1820
}
1921

2022
export class NotebookCellOutputItem {
21-
public static json(data: any, mime?: string): NotebookCellOutputItem {
23+
public static json(data: unknown, mime?: string): NotebookCellOutputItem {
2224
return new NotebookCellOutputItem(
2325
JSON.stringify(data),
2426
mime ?? "application/json"
@@ -35,22 +37,106 @@ export class NotebookCellOutput {
3537
constructor(public items: NotebookCellOutputItem[]) {}
3638
}
3739

38-
const promiseVoid = async (): Promise<void> => await Promise.resolve();
39-
// Add other vscode mocks as needed
40+
// Workspace (used by PlutoServerTaskManager)
4041
export const workspace = {
41-
fs: {
42-
readFile: async (): Promise<Uint8Array> =>
43-
await Promise.resolve(new Uint8Array()),
44-
writeFile: promiseVoid,
45-
},
42+
getConfiguration: (section?: string) => ({
43+
get: (key: string, defaultValue?: unknown) => {
44+
if (section === "julia" || !section) {
45+
if (key === "executablePath") return "julia";
46+
if (key === "environmentPath") return "";
47+
}
48+
return defaultValue;
49+
},
50+
}),
4651
};
4752

48-
export const window = {
49-
showInformationMessage: promiseVoid,
50-
showErrorMessage: promiseVoid,
51-
showWarningMessage: promiseVoid,
53+
// Task-related types (used by PlutoServerTaskManager)
54+
export enum TaskScope {
55+
Global = 1,
56+
Workspace = 2,
57+
}
58+
59+
export enum TaskRevealKind {
60+
Always = 1,
61+
Silent = 2,
62+
Never = 3,
63+
}
64+
65+
export enum TaskPanelKind {
66+
Shared = 1,
67+
Dedicated = 2,
68+
New = 3,
69+
}
70+
71+
export class TaskExecution {
72+
constructor(public task: Task) {}
73+
terminate() {}
74+
}
75+
76+
export class ShellExecution {
77+
constructor(
78+
public command: string,
79+
public args?: string[],
80+
public options?: Record<string, unknown>
81+
) {}
82+
}
83+
84+
export interface TaskDefinition {
85+
type: string;
86+
[key: string]: unknown;
87+
}
88+
89+
export interface TaskPresentationOptions {
90+
reveal?: TaskRevealKind;
91+
panel?: TaskPanelKind;
92+
showReuseMessage?: boolean;
93+
clear?: boolean;
94+
focus?: boolean;
95+
echo?: boolean;
96+
}
97+
98+
export class Task {
99+
public presentationOptions?: TaskPresentationOptions;
100+
public isBackground: boolean = false;
101+
102+
constructor(
103+
public definition: TaskDefinition,
104+
public scope: TaskScope | { uri: unknown; name?: string },
105+
public name: string,
106+
public source: string,
107+
public execution: ShellExecution,
108+
public problemMatchers?: string[]
109+
) {}
110+
}
111+
112+
// Tasks namespace (used by PlutoServerTaskManager)
113+
const taskEndListeners: Array<(e: { execution: TaskExecution }) => void> = [];
114+
115+
export const tasks = {
116+
executeTask: async (task: Task): Promise<TaskExecution> =>
117+
await Promise.resolve(new TaskExecution(task)),
118+
onDidEndTaskProcess: (
119+
listener: (e: { execution: TaskExecution }) => void
120+
) => {
121+
taskEndListeners.push(listener);
122+
return {
123+
dispose: () => {
124+
const index = taskEndListeners.indexOf(listener);
125+
if (index > -1) {
126+
taskEndListeners.splice(index, 1);
127+
}
128+
},
129+
};
130+
},
131+
taskExecutions: [] as TaskExecution[],
52132
};
53133

134+
// Disposable interface
135+
export interface Disposable {
136+
dispose(): void;
137+
}
138+
139+
// Uri (for general use)
54140
export const Uri = {
55141
file: (path: string) => ({ fsPath: path, toString: () => path }),
56142
parse: (uri: string) => ({ fsPath: uri, toString: () => uri }),

0 commit comments

Comments
 (0)