Skip to content

Commit

Permalink
test: add tests for debugging (#255)
Browse files Browse the repository at this point in the history
* test: add tests for debugging quick start command

* test: add tests for debugging session

* test: add test for device selection when debugging multiple devices

* test: replace `stubFetch` with actual response

`node-fetch` gets bundled in production builds, so we cant stub that in e2e tests.

* chore: add single test run for production builds

* docs: add missing `disposedSpy` dockblock
  • Loading branch information
byCedric authored Mar 10, 2024
1 parent 1b88b19 commit 3cc442a
Show file tree
Hide file tree
Showing 11 changed files with 364 additions and 34 deletions.
23 changes: 23 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,29 @@
],
"preLaunchTask": "launch-test-production"
},
{
"name": "Test: current file (production)",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"${workspaceFolder}/test/fixture",
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionTestsPath=${workspaceFolder}/out/test/mocha/vscode-runner",
"--disable-extensions"
],
"env": {
"CI": "true", // Force snapshots into "no update" mode
"UPDATE_SNAPSHOT": null, // Force snapshots into "no update" mode
"VSCODE_EXPO_DEBUG": "vscode-expo*",
"VSCODE_EXPO_TELEMETRY_KEY": null,
"VSCODE_EXPO_TEST_PATTERN": "${fileBasenameNoExtension}"
},
"outFiles": [
"${workspaceFolder}/out/**/*.js"
],
"preLaunchTask": "launch-test-production"
},
{
"name": "Test (no-build)",
"type": "extensionHost",
Expand Down
61 changes: 58 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"@sucrase/webpack-loader": "^2.0.0",
"@tsconfig/node18": "^18.2.1",
"@types/chai": "^4.3.5",
"@types/chai-as-promised": "^7.1.8",
"@types/chai-subset": "^1.3.3",
"@types/debug": "^4.1.7",
"@types/mocha": "^10.0.1",
Expand All @@ -66,10 +67,12 @@
"@types/sinon": "^10.0.16",
"@types/sinon-chai": "^3.2.12",
"@types/vscode": "^1.86.0",
"@types/ws": "^8.5.10",
"@vscode/test-electron": "^2.3.9",
"@vscode/vsce": "^2.21.0",
"arg": "^5.0.2",
"chai": "^4.3.8",
"chai-as-promised": "^7.1.1",
"chai-subset": "^1.6.0",
"conventional-changelog-conventionalcommits": "^6.1.0",
"eslint": "^8.48.0",
Expand All @@ -93,7 +96,8 @@
"sucrase": "^3.20.3",
"typescript": "^5.2.2",
"webpack": "^5.76.0",
"webpack-cli": "^5.1.4"
"webpack-cli": "^5.1.4",
"ws": "^8.16.0"
},
"publisher": "expo",
"icon": "images/logo-marketplace.png",
Expand Down
150 changes: 150 additions & 0 deletions src/__tests__/expoDebuggers.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { expect } from 'chai';
import { match } from 'sinon';
import vscode from 'vscode';

import { mockDevice, stubInspectorProxy } from './utils/debugging';
import { disposedSpy, disposedStub } from './utils/sinon';
import { getWorkspaceUri } from './utils/vscode';

describe('ExpoDebuggersProvider', () => {
describe('command', () => {
it('prompts for project root if no workspace is found', async () => {
using input = disposedStub(vscode.window, 'showInputBox');
await vscode.commands.executeCommand('expo.debug.start');
expect(input).to.be.calledWith(match({ prompt: 'Enter the path to the Expo project' }));
});

it('fails when project root doesnt exist', async () => {
using input = disposedStub(vscode.window, 'showInputBox');
using error = disposedStub(vscode.window, 'showErrorMessage');

input.returns(Promise.resolve('./'));
await vscode.commands.executeCommand('expo.debug.start');

expect(error).to.be.calledWith(match('Could not find any Expo projects in'));
});

it('aborts when no path was entered', async () => {
using input = disposedStub(vscode.window, 'showInputBox');
using debug = disposedStub(vscode.debug, 'startDebugging');

input.returns(Promise.resolve(''));
await vscode.commands.executeCommand('expo.debug.start');

expect(debug).not.to.be.called;
});

it('starts debugging session when project is found', async () => {
using input = disposedStub(vscode.window, 'showInputBox');
using debug = disposedStub(vscode.debug, 'startDebugging');

input.returns(Promise.resolve('./debugging'));
await vscode.commands.executeCommand('expo.debug.start');

expect(debug).to.be.calledWith(
undefined,
match({
type: 'expo',
request: 'attach',
name: 'Inspect Expo app',
projectRoot: getWorkspaceUri('debugging').fsPath,
})
);
});
});

describe('debugger', () => {
it('fails when using "type: launch"', async () => {
const action = () =>
vscode.debug.startDebugging(undefined, {
type: 'expo',
request: 'launch',
name: 'Inspect Expo app',
projectRoot: getWorkspaceUri('debugging').fsPath,
});

await expect(action()).to.eventually.rejected;
});

it('starts debug session with device url', async () => {
await using proxy = await stubInspectorProxy();
using upgrade = disposedSpy(proxy.sockets, 'handleUpgrade');
const device = mockDevice({ deviceName: 'Fake target' }, proxy);

// Return the devices when requested, without stubbing fetch.
// Note, stubbing fetch doesn't work when testing production build as `node-fetch` gets bundled.
proxy.app.callsFake((req, res) => {
if (req.url === '/json/list') return res.end(JSON.stringify([device]));
throw new Error('Invalid request: ' + req.url);
});

await vscode.debug.startDebugging(undefined, {
type: 'expo',
request: 'attach',
name: 'Inspect Expo app',
bundlerHost: proxy.serverUrl.hostname,
bundlerPort: proxy.serverUrl.port,
projectRoot: getWorkspaceUri('debugging').fsPath,
});

await vscode.commands.executeCommand('workbench.action.debug.stop');

expect(proxy.app).to.be.called;
expect(upgrade).to.be.called;

// Ensure the debug URL is correct, it should look like:
// /inspector/debug?device=DEVICE_ID&page=PAGE_ID&userAgent=USER_AGENT
const request = upgrade.getCall(1).args[0];
expect(request.url).to.include('/inspector/debug');
expect(request.url).to.include(`?device=${device.id}`);
expect(request.url).to.include(`&page=`);
expect(request.url).to.include(`&userAgent=vscode`);
});

it('starts debug session with user-picked device url', async () => {
await using proxy = await stubInspectorProxy();
using upgrade = disposedSpy(proxy.sockets, 'handleUpgrade');
using quickPick = disposedStub(vscode.window, 'showQuickPick');
const devices = [
mockDevice({ deviceName: 'Another target' }, proxy),
mockDevice({ deviceName: 'Fake target', id: 'the-one' }, proxy),
mockDevice({ deviceName: 'Yet another target' }, proxy),
];

// Return the devices when requested, without stubbing fetch.
// Note, stubbing fetch doesn't work when testing production build as `node-fetch` gets bundled.
proxy.app.callsFake((req, res) => {
if (req.url === '/json/list') return res.end(JSON.stringify(devices));
throw new Error('Invalid request: ' + req.url);
});

// @ts-expect-error - We are using string return values, not quickpick items
quickPick.returns(Promise.resolve('Fake target'));

await vscode.debug.startDebugging(undefined, {
type: 'expo',
request: 'attach',
name: 'Inspect Expo app',
bundlerHost: proxy.serverUrl.hostname,
bundlerPort: proxy.serverUrl.port,
projectRoot: getWorkspaceUri('debugging').fsPath,
});

await vscode.commands.executeCommand('workbench.action.debug.stop');

expect(proxy.app).to.be.called;
expect(upgrade).to.be.called;
expect(quickPick).to.be.calledWith(
match.array.deepEquals(['Another target', 'Fake target', 'Yet another target']),
match({
placeHolder: 'Select a device to debug',
})
);

// Ensure the debug URL is correct, it should use the "Fake target" device ID
const request = upgrade.getCall(1).args[0];
expect(request.url).to.include('/inspector/debug');
expect(request.url).to.include(`?device=the-one`);
});
});
});
Loading

0 comments on commit 3cc442a

Please sign in to comment.