Skip to content

Commit 9373849

Browse files
committed
test: replace fixed sleeps with polling and fix silent pass-through assertions
- Add poll() helper that retries with 250ms intervals instead of fixed 2-3 second setTimeout waits - Extract findPatchloomStatus() and findNotification() polling helpers - Fix Configure MCP and Quick Action tests: catch blocks no longer set gotNotification=true (the assertion was always passing regardless of actual behavior) - Remove unused SettingsEditor import - Add concrete assertion on the settings editor findSetting result Closes #24 Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
1 parent 9e4ddcd commit 9373849

1 file changed

Lines changed: 109 additions & 179 deletions

File tree

test/ui/extension.test.ts

Lines changed: 109 additions & 179 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,62 @@ import {
1414
StatusBar,
1515
InputBox,
1616
EditorView,
17-
SettingsEditor
1817
} from "vscode-extension-tester";
1918

19+
// ---------------------------------------------------------------------------
20+
// Polling helpers (replace fixed setTimeout waits)
21+
// ---------------------------------------------------------------------------
22+
23+
/** Poll fn until it returns a defined value, or throw after timeoutMs. */
24+
async function poll<T>(
25+
fn: () => Promise<T | undefined>,
26+
timeoutMs = 5000,
27+
): Promise<T> {
28+
const deadline = Date.now() + timeoutMs;
29+
while (Date.now() < deadline) {
30+
const result = await fn();
31+
if (result !== undefined) return result;
32+
await new Promise((r) => setTimeout(r, 250));
33+
}
34+
throw new Error(`poll timed out after ${timeoutMs}ms`);
35+
}
36+
37+
/** Find and return the text of a status bar item containing "Patchloom". */
38+
async function findPatchloomStatus(): Promise<string | undefined> {
39+
for (const item of await new StatusBar().getItems()) {
40+
try {
41+
const text = await item.getText();
42+
if (text.includes("Patchloom")) return text;
43+
} catch {
44+
// some items may not expose accessible text
45+
}
46+
}
47+
return undefined;
48+
}
49+
50+
/** Find, dismiss, and return a notification matching the given pattern. */
51+
async function findNotification(
52+
workbench: Workbench,
53+
pattern: RegExp,
54+
): Promise<string | undefined> {
55+
for (const n of await workbench.getNotifications()) {
56+
try {
57+
const msg = await n.getMessage();
58+
if (pattern.test(msg)) {
59+
await n.dismiss();
60+
return msg;
61+
}
62+
} catch {
63+
// notification may have auto-dismissed
64+
}
65+
}
66+
return undefined;
67+
}
68+
69+
// ---------------------------------------------------------------------------
70+
// Tests
71+
// ---------------------------------------------------------------------------
72+
2073
describe("Patchloom Extension UI", function () {
2174
this.timeout(60_000);
2275

@@ -26,236 +79,113 @@ describe("Patchloom Extension UI", function () {
2679
this.timeout(90_000);
2780
await VSBrowser.instance.waitForWorkbench();
2881
workbench = new Workbench();
29-
// Give the extension time to activate (onStartupFinished)
30-
await new Promise((resolve) => setTimeout(resolve, 3000));
82+
// Wait for the extension to activate and create its status bar item
83+
await poll(findPatchloomStatus, 15_000);
3184
});
3285

3386
describe("Status bar", function () {
3487
it("should display a Patchloom status bar item", async function () {
35-
const statusBar = new StatusBar();
36-
// The extension creates a status bar item with text containing "Patchloom"
37-
const items = await statusBar.getItems();
38-
const patchloomItem = items.find(
39-
(item) => item !== undefined
40-
);
41-
// At minimum, the status bar should have items (VS Code always has some)
42-
assert.ok(items.length > 0, "status bar should have items");
43-
44-
// Try to find our specific item by partial text match
45-
let found = false;
46-
for (const item of items) {
47-
try {
48-
const text = await item.getText();
49-
if (text.includes("Patchloom")) {
50-
found = true;
51-
break;
52-
}
53-
} catch {
54-
// Some items may not have accessible text
55-
}
56-
}
57-
assert.ok(found, "status bar should contain a Patchloom item");
88+
const text = await poll(findPatchloomStatus);
89+
assert.ok(text.includes("Patchloom"), "status bar should contain a Patchloom item");
5890
});
5991

60-
it("should show warning icon when patchloom binary is not found", async function () {
61-
const statusBar = new StatusBar();
62-
const items = await statusBar.getItems();
63-
let text = "";
64-
for (const item of items) {
65-
try {
66-
const t = await item.getText();
67-
if (t.includes("Patchloom")) {
68-
text = t;
69-
break;
70-
}
71-
} catch {
72-
// skip
73-
}
74-
}
75-
// In the test environment, patchloom is likely not on PATH,
76-
// so the status bar should show the warning variant
77-
// (unless patchloom.path is configured, which it shouldn't be in test)
92+
it("should have non-empty status text", async function () {
93+
const text = await poll(findPatchloomStatus);
7894
assert.ok(text.length > 0, "Patchloom status text should not be empty");
7995
});
8096
});
8197

8298
describe("Show Status command", function () {
83-
it("should display a status message when invoked", async function () {
99+
it("should display a notification when invoked", async function () {
84100
await workbench.executeCommand("Patchloom: Show Status");
85-
// The command shows an information or warning message
86-
// Wait for notification to appear
87-
await new Promise((resolve) => setTimeout(resolve, 2000));
88-
89-
const notifications = await workbench.getNotifications();
90-
let foundPatchloom = false;
91-
for (const notification of notifications) {
92-
try {
93-
const msg = await notification.getMessage();
94-
if (msg.includes("Patchloom") || msg.includes("patchloom")) {
95-
foundPatchloom = true;
96-
// Dismiss the notification
97-
await notification.dismiss();
98-
break;
99-
}
100-
} catch {
101-
// notification may have auto-dismissed
102-
}
103-
}
104-
assert.ok(foundPatchloom, "Show Status should display a notification mentioning Patchloom");
101+
const msg = await poll(
102+
() => findNotification(workbench, /[Pp]atchloom/),
103+
);
104+
assert.ok(msg, "Show Status should display a notification mentioning Patchloom");
105105
});
106106
});
107107

108108
describe("Open Settings command", function () {
109109
it("should open settings filtered to patchloom", async function () {
110110
await workbench.executeCommand("Patchloom: Open Settings");
111-
await new Promise((resolve) => setTimeout(resolve, 2000));
112-
113-
// Verify the settings editor opened
114-
const editorView = new EditorView();
115-
const titles = await editorView.getOpenEditorTitles();
116-
const hasSettings = titles.some(
117-
(title) => title.toLowerCase().includes("settings")
118-
);
119-
assert.ok(hasSettings, "Settings editor should be open after Open Settings command");
120-
121-
// Close the settings tab
122-
await editorView.closeAllEditors();
111+
await poll(async () => {
112+
const titles = await new EditorView().getOpenEditorTitles();
113+
return titles.some((t) => t.toLowerCase().includes("settings")) || undefined;
114+
});
115+
await new EditorView().closeAllEditors();
123116
});
124117
});
125118

126119
describe("Configure MCP command", function () {
127-
it("should show a quick pick or warning when invoked", async function () {
120+
it("should show a notification or quick pick when invoked", async function () {
128121
await workbench.executeCommand("Patchloom: Configure MCP");
129-
await new Promise((resolve) => setTimeout(resolve, 2000));
130122

131-
// If patchloom is not found, it shows a warning notification.
132-
// If patchloom is found, it shows a quick pick for target selection.
133-
// Either way, something should appear.
134-
const notifications = await workbench.getNotifications();
135-
let gotNotification = false;
136-
for (const notification of notifications) {
137-
try {
138-
const msg = await notification.getMessage();
139-
if (msg.includes("Patchloom") || msg.includes("patchloom") || msg.includes("MCP")) {
140-
gotNotification = true;
141-
await notification.dismiss();
142-
break;
143-
}
144-
} catch {
145-
// skip
146-
}
147-
}
148-
149-
if (!gotNotification) {
150-
// A quick pick may have appeared instead (patchloom is installed)
123+
// The command shows either a warning notification (binary not found)
124+
// or a quick pick for target selection (binary found).
125+
const result = await poll<{ kind: string }>(async () => {
126+
const msg = await findNotification(workbench, /[Pp]atchloom|MCP/);
127+
if (msg) return { kind: "notification" };
151128
try {
152129
const input = new InputBox();
153-
await input.cancel();
154-
gotNotification = true;
130+
await input.getText(); // throws if no input box visible
131+
return { kind: "input" };
155132
} catch {
156-
// No input box either; that's also acceptable if command completed silently
157-
gotNotification = true;
133+
return undefined; // neither appeared yet, keep polling
158134
}
159-
}
135+
});
160136

161-
assert.ok(gotNotification, "Configure MCP should show a notification or quick pick");
137+
assert.ok(result, "Configure MCP should produce a notification or quick pick");
138+
if (result.kind === "input") {
139+
try { await new InputBox().cancel(); } catch { /* already gone */ }
140+
}
162141
});
163142
});
164143

165144
describe("Quick Action command", function () {
166-
it("should show a quick pick or warning when invoked", async function () {
145+
it("should show a notification or quick pick when invoked", async function () {
167146
await workbench.executeCommand("Patchloom: Quick Action");
168-
await new Promise((resolve) => setTimeout(resolve, 2000));
169147

170-
const notifications = await workbench.getNotifications();
171-
let gotNotification = false;
172-
for (const notification of notifications) {
173-
try {
174-
const msg = await notification.getMessage();
175-
if (msg.includes("Patchloom") || msg.includes("patchloom")) {
176-
gotNotification = true;
177-
await notification.dismiss();
178-
break;
179-
}
180-
} catch {
181-
// skip
182-
}
183-
}
184-
185-
if (!gotNotification) {
148+
const result = await poll<{ kind: string; labels?: string[] }>(async () => {
149+
const msg = await findNotification(workbench, /[Pp]atchloom/);
150+
if (msg) return { kind: "notification" };
186151
try {
187152
const input = new InputBox();
188-
// If the quick pick appeared, verify it has our options
189153
const picks = await input.getQuickPicks();
190-
if (picks.length > 0) {
191-
const labels: string[] = [];
192-
for (const pick of picks) {
193-
labels.push(await pick.getLabel());
194-
}
195-
const hasReplace = labels.some((l) => l.includes("Replace"));
196-
const hasTidy = labels.some((l) => l.includes("Tidy"));
197-
assert.ok(hasReplace || hasTidy,
198-
`quick pick should contain Replace or Tidy, got: ${labels.join(", ")}`);
199-
}
200-
await input.cancel();
201-
gotNotification = true;
154+
const labels: string[] = [];
155+
for (const pick of picks) labels.push(await pick.getLabel());
156+
return { kind: "input", labels };
202157
} catch {
203-
gotNotification = true;
158+
return undefined; // keep polling
204159
}
160+
});
161+
162+
assert.ok(result, "Quick Action should produce a notification or quick pick");
163+
if (result.kind === "input") {
164+
assert.ok(
165+
result.labels!.some((l) => l.includes("Replace") || l.includes("Tidy")),
166+
`quick pick should contain Replace or Tidy, got: ${result.labels!.join(", ")}`,
167+
);
168+
try { await new InputBox().cancel(); } catch { /* already gone */ }
205169
}
206-
207-
assert.ok(gotNotification, "Quick Action should show a notification or quick pick");
208170
});
209171
});
210172

211173
describe("Configuration changes", function () {
212-
it("should react to showStatusBar being toggled off and on", async function () {
213-
const statusBar = new StatusBar();
214-
215-
// Verify Patchloom is in the status bar initially
216-
let items = await statusBar.getItems();
217-
let hasPatchloom = false;
218-
for (const item of items) {
219-
try {
220-
if ((await item.getText()).includes("Patchloom")) {
221-
hasPatchloom = true;
222-
break;
223-
}
224-
} catch {
225-
// skip
226-
}
227-
}
228-
assert.ok(hasPatchloom, "Patchloom should be in status bar initially");
229-
230-
// Open settings and toggle showStatusBar off
174+
it("should show Patchloom setting in the settings editor", async function () {
175+
await poll(findPatchloomStatus);
231176
const settingsEditor = await workbench.openSettings();
232-
await settingsEditor.findSetting("Show Status Bar", "Patchloom");
233-
// We verified it opens; toggling programmatically is more reliable
234-
// than clicking the checkbox via WebDriver
177+
const setting = await settingsEditor.findSetting("Show Status Bar", "Patchloom");
178+
assert.ok(setting, "should find the showStatusBar setting");
235179
await workbench.executeCommand("workbench.action.closeActiveEditor");
236180
});
237181
});
238182

239183
after(async function () {
240-
// Clean up: close all editors and dismiss notifications
241-
try {
242-
const editorView = new EditorView();
243-
await editorView.closeAllEditors();
244-
} catch {
245-
// may not have any editors open
246-
}
247-
184+
try { await new EditorView().closeAllEditors(); } catch { /* none open */ }
248185
try {
249-
const notifications = await workbench.getNotifications();
250-
for (const notification of notifications) {
251-
try {
252-
await notification.dismiss();
253-
} catch {
254-
// already dismissed
255-
}
186+
for (const n of await workbench.getNotifications()) {
187+
try { await n.dismiss(); } catch { /* already dismissed */ }
256188
}
257-
} catch {
258-
// no notifications
259-
}
189+
} catch { /* no notifications */ }
260190
});
261191
});

0 commit comments

Comments
 (0)