Skip to content

Commit 3089d02

Browse files
authored
tests: remove Window.vscode() and related logic (#3182)
## Problem Writing wrappers for the sole purpose of making an implementation easier to test is not a sustainable approach. ## Solution * Remove `Window.vscode()` and `FakeWindow` * Extend `TestWindow` to cover the same scenarios that `FakeWindow` was used for ### Important Notes This PR uses Proxy heavily in order to make "testable" versions of native vscode objects. Some things like `showInformationMessage` and `withProgress` do not have an API that can be manipulated programmatically. For these methods, we have to create in-mem representations that can be tested. These currently live in `src/test/shared/vscode/message.ts`.
1 parent 2682e01 commit 3089d02

File tree

136 files changed

+2558
-3487
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

136 files changed

+2558
-3487
lines changed

docs/TESTPLAN.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,38 @@ modifications/workarounds in `src/test/testRunner.ts`.
7373
- Testing AWS SDK client functionality is cumbersome, verbose, and low-yield.
7474
- Test code uses multiple “mocking” frameworks, which is confusing, error-prone, hard to onboard, and hard to use.
7575
- Coverage not counted for integ tests (because of unresolved tooling issue).
76+
77+
## Window
78+
79+
Certain VS Code API calls are not easily controlled programtically. `vscode.window` is a major source of these functions as it is closely related to the UI. To facilitate some semblance of UI testing, all unit tests have access to a proxied `vscode.window` object via `getTestWindow()`.
80+
81+
### Inspecting State
82+
83+
The test window will capture relevant UI state that can be inspected at test time. For example, you can check to see if any messages were shown by looking at `getTestWindow().shownMessages` which is an array of message objects.
84+
85+
Some VS Code API operations do not expose "native" UI elements that can be inspected. In these cases, in-memory test versions have been created. In every other case the native VS Code API is used directly and extended to make them more testable.
86+
87+
### Event-Driven Interactions
88+
89+
Checking the state works well if user interactions are not required by the code being tested. But many times the code will be waiting for the user's response.
90+
91+
To handle this, test code can register event handler that listen for when a certain type of UI element is shown. For example, if we wanted to always accept the first item of a quick pick we can do this:
92+
93+
```ts
94+
getTestWindow().onDidShowQuickPick(async picker => {
95+
// Some pickers load items asychronously
96+
// Wait until the picker is not busy before accepting an item
97+
await picker.untilReady()
98+
picker.acceptItem(picker.items[0])
99+
})
100+
```
101+
102+
Utility functions related to events can be used to iterate over captured UI elements:
103+
104+
```ts
105+
const pickers = captureEvent(getTestWindow().onDidShowQuickPick)
106+
const firstPicker = await pickers.next()
107+
const secondPicker = await pickers.next()
108+
```
109+
110+
Exceptions thrown within one of these handlers will cause the current test to fail. This allows you to make assertions within the callback without worrying about causing the test to hang.

src/apigateway/commands/copyUrl.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { Window } from '../../shared/vscode/window'
76
import { copyToClipboard } from '../../shared/utilities/messages'
87
import * as nls from 'vscode-nls'
98
const localize = nls.loadMessageBundle()
@@ -23,18 +22,14 @@ interface StageInvokeUrlQuickPick extends vscode.QuickPickItem {
2322
detail: string
2423
}
2524

26-
export async function copyUrlCommand(
27-
node: RestApiNode,
28-
regionProvider: RegionProvider,
29-
window = Window.vscode()
30-
): Promise<void> {
25+
export async function copyUrlCommand(node: RestApiNode, regionProvider: RegionProvider): Promise<void> {
3126
const region = node.regionCode
3227
const dnsSuffix = regionProvider.getDnsSuffixForRegion(region) || defaultDnsSuffix
3328
const client = new DefaultApiGatewayClient(region)
3429

3530
let stages: Stage[]
3631
try {
37-
stages = await window.withProgress(
32+
stages = await vscode.window.withProgress(
3833
{
3934
cancellable: false,
4035
location: ProgressLocation.Window,
@@ -58,7 +53,7 @@ export async function copyUrlCommand(
5853
}))
5954

6055
if (quickPickItems.length === 0) {
61-
window.showInformationMessage(
56+
vscode.window.showInformationMessage(
6257
localize('AWS.apig.copyUrlNoStages', "Failed to copy URL because '{0}' has no stages", node.name)
6358
)
6459
telemetry.apigateway_copyUrl.emit({ result: 'Failed' })

src/apprunner/commands/pauseService.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import * as nls from 'vscode-nls'
77
const localize = nls.loadMessageBundle()
88

9-
import * as vscode from 'vscode'
109
import * as localizedText from '../../shared/localizedText'
1110
import { showConfirmationMessage } from '../../shared/utilities/messages'
1211
import { AppRunnerServiceNode } from '../explorer/apprunnerServiceNode'
@@ -27,7 +26,7 @@ export async function pauseService(node: AppRunnerServiceNode): Promise<void> {
2726
)
2827
const confirmationOptions = { prompt: notifyPrompt, confirm: localizedText.ok, cancel: localizedText.cancel }
2928

30-
if (shouldNotify && !(await showConfirmationMessage(confirmationOptions, vscode.window))) {
29+
if (shouldNotify && !(await showConfirmationMessage(confirmationOptions))) {
3130
telemetryResult = 'Cancelled'
3231
return
3332
}

src/awsexplorer/commands/copyArn.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6+
import * as vscode from 'vscode'
67
import * as nls from 'vscode-nls'
78
import { showLogOutputChannel } from '../../shared/logger'
89
const localize = nls.loadMessageBundle()
910

1011
import { AWSResourceNode } from '../../shared/treeview/nodes/awsResourceNode'
1112
import { Env } from '../../shared/vscode/env'
1213
import { copyToClipboard } from '../../shared/utilities/messages'
13-
import { Window } from '../../shared/vscode/window'
1414
import { Commands } from '../../shared/vscode/commands'
1515
import { getIdeProperties } from '../../shared/extensionUtilities'
1616
import { TreeShim } from '../../shared/treeview/utils'
@@ -20,17 +20,16 @@ import { TreeShim } from '../../shared/treeview/utils'
2020
*/
2121
export async function copyArnCommand(
2222
node: AWSResourceNode | TreeShim<AWSResourceNode>,
23-
window = Window.vscode(),
2423
env = Env.vscode(),
2524
commands = Commands.vscode()
2625
): Promise<void> {
2726
node = node instanceof TreeShim ? node.node.resource : node
2827

2928
try {
30-
copyToClipboard(node.arn, 'ARN', window, env)
29+
copyToClipboard(node.arn, 'ARN', env)
3130
} catch (e) {
3231
const logsItem = localize('AWS.generic.message.viewLogs', 'View Logs...')
33-
window
32+
vscode.window
3433
.showErrorMessage(
3534
localize(
3635
'AWS.explorerNode.noArnFound',

src/awsexplorer/commands/copyName.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
import { Env } from '../../shared/vscode/env'
77
import { copyToClipboard } from '../../shared/utilities/messages'
8-
import { Window } from '../../shared/vscode/window'
98
import { AWSResourceNode } from '../../shared/treeview/nodes/awsResourceNode'
109
import { TreeShim } from '../../shared/treeview/utils'
1110

@@ -14,10 +13,9 @@ import { TreeShim } from '../../shared/treeview/utils'
1413
*/
1514
export async function copyNameCommand(
1615
node: AWSResourceNode | TreeShim<AWSResourceNode>,
17-
window = Window.vscode(),
1816
env = Env.vscode()
1917
): Promise<void> {
2018
node = node instanceof TreeShim ? node.node.resource : node
2119

22-
await copyToClipboard(node.name, 'name', window, env)
20+
await copyToClipboard(node.name, 'name', env)
2321
}

src/cloudWatchLogs/commands/saveCurrentLogStreamContent.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,13 @@ import * as fs from 'fs-extra'
1111
import * as path from 'path'
1212
import { SystemUtilities } from '../../shared/systemUtilities'
1313
import { Result } from '../../shared/telemetry/telemetry'
14-
import { Window } from '../../shared/vscode/window'
1514
import { parseCloudWatchLogsUri } from '../cloudWatchLogsUtils'
1615
import { LogStreamRegistry } from '../registry/logStreamRegistry'
1716
import { telemetry } from '../../shared/telemetry/telemetry'
1817

1918
export async function saveCurrentLogStreamContent(
2019
uri: vscode.Uri | undefined,
21-
registry: LogStreamRegistry,
22-
window = Window.vscode()
20+
registry: LogStreamRegistry
2321
): Promise<void> {
2422
let result: Result = 'Succeeded'
2523

@@ -39,7 +37,7 @@ export async function saveCurrentLogStreamContent(
3937
const uriComponents = parseCloudWatchLogsUri(uri)
4038

4139
const localizedLogFile = localize('AWS.command.saveCurrentLogStreamContent.logfile', 'Log File')
42-
const selectedUri = await window.showSaveDialog({
40+
const selectedUri = await vscode.window.showSaveDialog({
4341
defaultUri: vscode.Uri.parse(path.join(workspaceDir.toString(), uriComponents.streamName)),
4442
filters: {
4543
[localizedLogFile]: ['log'],

src/codecatalyst/tools.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,9 @@ interface MissingTool {
3838

3939
export const hostNamePrefix = 'aws-devenv-'
4040

41-
export async function ensureDependencies(
42-
window = vscode.window
43-
): Promise<Result<DependencyPaths, CancellationError | Error>> {
41+
export async function ensureDependencies(): Promise<Result<DependencyPaths, CancellationError | Error>> {
4442
if (!isExtensionInstalled('ms-vscode-remote.remote-ssh')) {
45-
showInstallExtensionMsg('ms-vscode-remote.remote-ssh', 'Remote SSH', 'Connecting to Dev Environment', window)
43+
showInstallExtensionMsg('ms-vscode-remote.remote-ssh', 'Remote SSH', 'Connecting to Dev Environment')
4644

4745
return Result.err(
4846
new ToolkitError('Remote SSH extension not installed', {

src/credentials/credentialsUtilities.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ export function showLoginFailedMessage(credentialsId: string, errMsg: string): v
4040

4141
showViewLogsMessage(
4242
localize('AWS.message.credentials.invalid', 'Credentials "{0}" failed to connect: {1}', credentialsId, errMsg),
43-
vscode.window,
4443
'error',
4544
buttons
4645
).then((selection: string | undefined) => {

src/dynamicResources/commands/copyIdentifier.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,11 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { Window } from '../../shared/vscode/window'
76
import { Env } from '../../shared/vscode/env'
87
import { copyToClipboard } from '../../shared/utilities/messages'
98
import { telemetry } from '../../shared/telemetry/telemetry'
109

11-
export async function copyIdentifier(
12-
typeName: string,
13-
identifier: string,
14-
window = Window.vscode(),
15-
env = Env.vscode()
16-
) {
17-
copyToClipboard(identifier, 'identifier', window, env)
10+
export async function copyIdentifier(typeName: string, identifier: string, env = Env.vscode()) {
11+
copyToClipboard(identifier, 'identifier', env)
1812
telemetry.dynamicresource_copyIdentifier.emit({ resourceType: typeName })
1913
}

src/dynamicResources/commands/deleteResource.ts

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
import * as vscode from 'vscode'
77
import * as nls from 'vscode-nls'
8-
import { Window } from '../../shared/vscode/window'
98
import { getLogger } from '../../shared/logger/logger'
109
import { showConfirmationMessage, showViewLogsMessage } from '../../shared/utilities/messages'
1110
import { CloudControlClient } from '../../shared/clients/cloudControlClient'
@@ -17,24 +16,20 @@ const localize = nls.loadMessageBundle()
1716
export async function deleteResource(
1817
cloudControl: CloudControlClient,
1918
typeName: string,
20-
identifier: string,
21-
window = Window.vscode()
19+
identifier: string
2220
): Promise<boolean> {
2321
getLogger().info(`deleteResource called for type ${typeName} identifier ${identifier}`)
24-
const ok = await showConfirmationMessage(
25-
{
26-
prompt: localize('aws.resources.deleteResource.prompt', 'Delete resource {0} ({1})?', identifier, typeName),
27-
confirm: localize('AWS.generic.delete', 'Delete'),
28-
cancel: localize('AWS.generic.cancel', 'Cancel'),
29-
},
30-
window
31-
)
22+
const ok = await showConfirmationMessage({
23+
prompt: localize('aws.resources.deleteResource.prompt', 'Delete resource {0} ({1})?', identifier, typeName),
24+
confirm: localize('AWS.generic.delete', 'Delete'),
25+
cancel: localize('AWS.generic.cancel', 'Cancel'),
26+
})
3227
if (!ok) {
3328
getLogger().info(`Cancelled delete resource type ${typeName} identifier ${identifier}`)
3429
return false
3530
}
3631

37-
return await window.withProgress(
32+
return await vscode.window.withProgress(
3833
{
3934
location: vscode.ProgressLocation.Notification,
4035
cancellable: false,
@@ -55,7 +50,7 @@ export async function deleteResource(
5550

5651
getLogger().info(`Deleted resource type ${typeName} identifier ${identifier}`)
5752

58-
window.showInformationMessage(
53+
vscode.window.showInformationMessage(
5954
localize('aws.resources.deleteResource.success', 'Deleted resource {0} ({1})', identifier, typeName)
6055
)
6156
return true
@@ -66,7 +61,7 @@ export async function deleteResource(
6661
getLogger().warn(
6762
`Resource type ${typeName} does not support DELETE action in ${cloudControl.regionCode}`
6863
)
69-
window.showWarningMessage(
64+
vscode.window.showWarningMessage(
7065
localize(
7166
'aws.resources.deleteResource.unsupported',
7267
'Resource type {0} does not currently support delete in {1}',
@@ -84,8 +79,7 @@ export async function deleteResource(
8479
'Failed to delete resource {0} ({1})',
8580
identifier,
8681
typeName
87-
),
88-
window
82+
)
8983
)
9084
return false
9185
} finally {

0 commit comments

Comments
 (0)