Skip to content

Commit 6da0ab1

Browse files
committed
refactor: change the way to manage metadata
Changed to using WeakMap to manage custom data according to VSCode documentation.
1 parent 964ab24 commit 6da0ab1

17 files changed

Lines changed: 203 additions & 557 deletions

src/api/run.ts

Lines changed: 21 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { getGrep, getRange, getCucumberSpec, getSpec } from './utils.js'
2-
import { isConfig, isSpec, isTestcase, isWdioTestItem } from '../test/index.js'
2+
import { RepositoryManager } from '../test/index.js'
33
import { log } from '../utils/logger.js'
44

55
import type * as vscode from 'vscode'
66
import type { RunTestOptions } from './types.js'
77
import type { WdioExtensionWorkerInterface } from './types.js'
8-
import type { SpecFileTestItem, WdioConfigTestItem, TestcaseTestItem } from '../test/index.js'
8+
import type { TestItemMetadata, TestItemMetadataWithRepository } from '../test/index.js'
99

1010
type Listeners = {
1111
stdout: (data: string) => void
@@ -31,11 +31,12 @@ export class TestRunner {
3131
* Run a test based on the provided TestItem
3232
*/
3333
public async run(test: vscode.TestItem) {
34+
const metadata = RepositoryManager.getMetadata(test)
3435
// Validate test item
35-
this.validateTestItem(test)
36+
this.validateTestItem(metadata)
3637

3738
// Create test execution options
38-
const testOptions = this.createTestOptions(test)
39+
const testOptions = this.createTestOptions(test, metadata)
3940

4041
try {
4142
// Execute test and process results
@@ -53,35 +54,29 @@ export class TestRunner {
5354
/**
5455
* Validates that the provided TestItem is a valid WebdriverIO test
5556
*/
56-
private validateTestItem(
57-
test: vscode.TestItem
58-
): asserts test is TestcaseTestItem | WdioConfigTestItem | SpecFileTestItem {
59-
if (!isWdioTestItem(test)) {
60-
throw new Error("The metadata for TestItem is not set. This is extension's bug.")
61-
}
62-
63-
if (!isConfig(test) && !isSpec(test) && !isTestcase(test)) {
57+
private validateTestItem(metadata: TestItemMetadata): asserts metadata is TestItemMetadataWithRepository {
58+
if ('repository' in metadata && typeof metadata.repository !== 'object') {
6459
throw new Error('Workspace TestItem is not valid.')
6560
}
6661
}
6762

6863
/**
6964
* Creates RunTestOptions based on the test type and framework
7065
*/
71-
private createTestOptions(test: TestcaseTestItem | WdioConfigTestItem | SpecFileTestItem): RunTestOptions {
72-
const isCucumberFramework = this.isCucumberFramework(test)
66+
private createTestOptions(test: vscode.TestItem, metadata: TestItemMetadataWithRepository): RunTestOptions {
67+
const isCucumberFramework = this.isCucumberFramework(metadata)
7368

7469
// Get appropriate specs based on the test framework and type
75-
const specs = this.determineSpecs(test, isCucumberFramework)
70+
const specs = this.determineSpecs(test, isCucumberFramework, metadata)
7671

7772
// Get grep pattern for mocha-like frameworks when testing individual test cases
78-
const grep = !isCucumberFramework && isTestcase(test) ? getGrep(test) : undefined
73+
const grep = !isCucumberFramework && metadata.isTestcase ? getGrep(test) : undefined
7974

8075
// Get line range information for test file
81-
const range = !isCucumberFramework && isTestcase(test) ? getRange(test) : undefined
76+
const range = !isCucumberFramework && metadata.isTestcase ? getRange(test) : undefined
8277

8378
return {
84-
configPath: test.metadata.repository.wdioConfigPath,
79+
configPath: metadata.repository.wdioConfigPath,
8580
specs,
8681
grep,
8782
range,
@@ -91,26 +86,23 @@ export class TestRunner {
9186
/**
9287
* Determines if the test is using Cucumber framework
9388
*/
94-
private isCucumberFramework(test: TestcaseTestItem | WdioConfigTestItem | SpecFileTestItem): boolean {
95-
return test.metadata.repository.framework === 'cucumber'
89+
private isCucumberFramework(metadata: TestItemMetadataWithRepository): boolean {
90+
return metadata.repository.framework === 'cucumber'
9691
}
9792

9893
/**
9994
* Determines the specs to run based on test type and framework
10095
*/
10196
private determineSpecs(
102-
test: TestcaseTestItem | WdioConfigTestItem | SpecFileTestItem,
103-
isCucumberFramework: boolean
97+
test: vscode.TestItem,
98+
isCucumberFramework: boolean,
99+
metadata: TestItemMetadataWithRepository
104100
): string[] | undefined {
105-
if (isCucumberFramework && isTestcase(test)) {
106-
return getCucumberSpec(test)
107-
}
108-
109-
if (isSpec(test) || isTestcase(test)) {
110-
return getSpec(test)
101+
if (isCucumberFramework) {
102+
return getCucumberSpec(test, metadata)
111103
}
112104

113-
return undefined
105+
return getSpec(test)
114106
}
115107

116108
/**

src/api/utils.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { WebSocketServer } from 'ws'
22

33
import { LOG_LEVEL, TEST_ID_SEPARATOR } from '../constants.js'
4+
import { RepositoryManager, type TestItemMetadataWithRepository } from '../test/index.js'
45
import { log } from '../utils/logger.js'
56

67
import type { Server } from 'node:http'
78
import type * as vscode from 'vscode'
8-
import type { TestcaseTestItem } from '../test/index.js'
99
import type { NumericLogLevel } from '../types.js'
1010

1111
export async function loggingFn(_logLevel: NumericLogLevel, message: string) {
@@ -52,15 +52,16 @@ export function getRange(test: vscode.TestItem) {
5252
return isEmptyRange ? undefined : test.range
5353
}
5454

55-
export function getCucumberSpec(testItem: TestcaseTestItem) {
55+
export function getCucumberSpec(testItem: vscode.TestItem, metadata: TestItemMetadataWithRepository) {
5656
const baseSpec = getSpec(testItem)
5757
if (!baseSpec) {
5858
return undefined
5959
}
60-
if (testItem.metadata.type === 'rule') {
60+
if (metadata.type === 'rule') {
6161
const specs = []
6262
for (const [_, childItem] of testItem.children) {
63-
if ((childItem as TestcaseTestItem).metadata.type === 'scenario') {
63+
const childeMetadata = RepositoryManager.getMetadata(childItem)
64+
if (childeMetadata.type === 'scenario') {
6465
const start = childItem.range?.start.line || 0
6566
const end = childItem.range?.end.line || 0
6667
if (start > 0 && end > 0) {
@@ -75,7 +76,7 @@ export function getCucumberSpec(testItem: TestcaseTestItem) {
7576
}
7677
}
7778

78-
if (testItem.metadata.type === 'scenario') {
79+
if (metadata.type === 'scenario') {
7980
const specs = []
8081
const start = testItem.range?.start.line || 0
8182
const end = testItem.range?.end.line || 0

src/test/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
export { RepositoryManager } from './manager.js'
2-
export { isWdioTestItem, isConfig, isSpec, isTestcase, isWorkspace } from './utils.js'
32
export { TestReporter } from './reporter.js'
43
export { TestfileWatcher } from './watcher.js'
54
export { convertUriToPath } from './converter.js'

src/test/manager.ts

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,33 @@ import { createRunProfile } from './utils.js'
88
import { TEST_ID_SEPARATOR } from '../constants.js'
99
import { log } from '../utils/logger.js'
1010

11-
import type { WdioConfigTestItem, WorkspaceTestItem } from './types.js'
11+
import type { TestItemMetadata } from './types.js'
1212
import type { ServerManager } from '../api/manager.js'
1313
import type { ExtensionConfigManager } from '../config/index.js'
1414

15+
class MetadataRepository {
16+
private static testMetadataRepository = new WeakMap<vscode.TestItem, TestItemMetadata>()
17+
public static getMetadata(testItem: vscode.TestItem) {
18+
const metadata = this.testMetadataRepository.get(testItem)
19+
if (!metadata) {
20+
throw new Error("The metadata for TestItem is not set. This is extension's bug.")
21+
}
22+
return metadata
23+
}
24+
25+
public static getRepository(testItem: vscode.TestItem) {
26+
const metadata = this.getMetadata(testItem)
27+
if (!metadata.repository) {
28+
throw new Error("The metadata for TestItem is not set. This is extension's bug.")
29+
}
30+
return metadata.repository
31+
}
32+
33+
public static setMetadata(testItem: vscode.TestItem, metadata: TestItemMetadata) {
34+
this.testMetadataRepository.set(testItem, metadata)
35+
}
36+
}
37+
1538
/**
1639
* workspace -- managed by this class
1740
* - configuration file (e.g. wdio.conf.js) -- managed by this class
@@ -22,18 +45,19 @@ import type { ExtensionConfigManager } from '../config/index.js'
2245

2346
const LOADING_TEST_ITEM_ID = '_resolving'
2447

25-
export class RepositoryManager implements vscode.Disposable {
48+
export class RepositoryManager extends MetadataRepository implements vscode.Disposable {
2649
private readonly _repos = new Set<TestRepository>()
2750
private _loadingTestItem: vscode.TestItem
28-
private _workspaceTestItems: WorkspaceTestItem[] = []
29-
private _wdioConfigTestItems: WdioConfigTestItem[] = []
51+
private _workspaceTestItems: vscode.TestItem[] = []
52+
private _wdioConfigTestItems: vscode.TestItem[] = []
3053
private isCreatedDefaultProfile = false
3154

3255
constructor(
3356
public readonly controller: vscode.TestController,
3457
public readonly configManager: ExtensionConfigManager,
3558
private readonly serverManager: ServerManager
3659
) {
60+
super()
3761
this._loadingTestItem = this.controller.createTestItem(LOADING_TEST_ITEM_ID, 'Resolving WebdriverIO Tests...')
3862
this._loadingTestItem.sortText = '.0' // show at first line
3963
this._loadingTestItem.busy = true
@@ -82,7 +106,8 @@ export class RepositoryManager implements vscode.Disposable {
82106
for (const workspaceTestItem of affectedWorkspaceItems) {
83107
const configTestItem = await this.createWdioConfigTestItem(workspaceTestItem, wdioConfigPath)
84108

85-
await configTestItem.metadata.repository.discoverAllTests()
109+
const repo = RepositoryManager.getRepository(configTestItem)
110+
await repo.discoverAllTests()
86111
if (!this.configManager.isMultiWorkspace) {
87112
this.controller.items.add(configTestItem)
88113
}
@@ -96,14 +121,12 @@ export class RepositoryManager implements vscode.Disposable {
96121
log.debug(`Remove the config file from ${affectedWorkspaceItems.length} workspace(s)`)
97122
const configUri = convertPathToUri(wdioConfigPath)
98123
for (const workspaceItem of affectedWorkspaceItems) {
99-
const config = workspaceItem.children.get(
100-
this.generateConfigTestItemId(workspaceItem, configUri)
101-
) as WdioConfigTestItem
124+
const config = workspaceItem.children.get(this.generateConfigTestItemId(workspaceItem, configUri))
102125
if (!config) {
103126
continue
104127
}
105128
log.debug(`Remove the TestItem: ${config.id}`)
106-
const targetRepo = config.metadata.repository
129+
const targetRepo = RepositoryManager.getRepository(config)
107130
targetRepo.dispose()
108131
this._repos.delete(targetRepo)
109132

@@ -140,22 +163,24 @@ export class RepositoryManager implements vscode.Disposable {
140163
`workspace:${workspaceFolder.uri.fsPath}`,
141164
workspaceFolder.name,
142165
workspaceFolder.uri
143-
) as WorkspaceTestItem
144-
workspaceItem['metadata'] = {
166+
)
167+
RepositoryManager.setMetadata(workspaceItem, {
168+
uri: workspaceFolder.uri,
145169
isWorkspace: true,
146170
isConfigFile: false,
147171
isSpecFile: false,
148-
}
172+
isTestcase: false,
173+
})
149174
return workspaceItem
150175
}
151176

152-
private async createWdioConfigTestItem(workspaceTestItem: WorkspaceTestItem, wdioConfigPath: string) {
153-
const configUri = convertPathToUri(wdioConfigPath)
177+
private async createWdioConfigTestItem(workspaceTestItem: vscode.TestItem, wdioConfigPath: string) {
178+
const uri = convertPathToUri(wdioConfigPath)
154179
const configItem = this.controller.createTestItem(
155-
this.generateConfigTestItemId(workspaceTestItem, configUri),
180+
this.generateConfigTestItemId(workspaceTestItem, uri),
156181
basename(wdioConfigPath),
157-
configUri
158-
) as WdioConfigTestItem
182+
uri
183+
)
159184

160185
workspaceTestItem.children.add(configItem)
161186
this._wdioConfigTestItems.push(configItem)
@@ -165,17 +190,20 @@ export class RepositoryManager implements vscode.Disposable {
165190
this._repos.add(repo)
166191

167192
configItem.description = relative(workspaceTestItem.uri!.fsPath, dirname(wdioConfigPath))
168-
configItem['metadata'] = {
193+
194+
RepositoryManager.setMetadata(configItem, {
195+
uri,
169196
isWorkspace: false,
170197
isConfigFile: true,
171198
isSpecFile: false,
199+
isTestcase: false,
172200
repository: repo,
173201
runProfiles: createRunProfile.call(this, configItem, !this.isCreatedDefaultProfile),
174-
}
202+
})
175203
return configItem
176204
}
177205

178-
private generateConfigTestItemId(workspaceTestItem: WorkspaceTestItem, configUri: vscode.Uri) {
206+
private generateConfigTestItemId(workspaceTestItem: vscode.TestItem, configUri: vscode.Uri) {
179207
return [workspaceTestItem.id, `config:${configUri.fsPath}`].join(TEST_ID_SEPARATOR)
180208
}
181209

src/test/reporter.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,13 @@ export class TestReporter {
7676
*/
7777
private processHierarchicalSuite(suite: TestSuite, parentItem: vscode.TestItem): void {
7878
// Find the suite TestItem in the repository
79-
const suiteItem = this._repository.searchSuite(suite.name, parentItem)
79+
let suiteItem: vscode.TestItem | undefined
80+
81+
parentItem.children.forEach((child) => {
82+
if (child.label === suite.name) {
83+
suiteItem = child
84+
}
85+
})
8086

8187
if (!suiteItem) {
8288
log.debug(`Suite TestItem not found for suite: ${suite.name}`)

0 commit comments

Comments
 (0)