or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

generator-helpers.mdindex.mdrun-context.mdrun-result.mdtest-adapter.mdtest-environment.md

test-adapter.mddocs/

0

# Test Adapter

1

2

The TestAdapter provides a mock implementation of the Yeoman adapter interface, enabling precise control over user prompts and interactions during generator testing. It extends the base TestAdapter from `@yeoman/adapter` with additional yeoman-test specific functionality.

3

4

## Capabilities

5

6

### TestAdapter Creation

7

8

Create and configure TestAdapter instances for prompt mocking and interaction simulation.

9

10

```typescript { .api }

11

/**

12

* TestAdapter constructor with optional configuration

13

* @param options - Configuration options for the adapter

14

*/

15

class TestAdapter {

16

constructor(options?: TestAdapterOptions);

17

18

// Inherits from @yeoman/adapter TestAdapter

19

prompt<T extends PromptAnswers = PromptAnswers>(

20

questions: PromptQuestions<T>

21

): Promise<T>;

22

23

log: {

24

write: (message?: string) => boolean;

25

writeln: (message?: string) => boolean;

26

ok: (message?: string) => boolean;

27

error: (message?: string) => boolean;

28

skip: (message?: string) => boolean;

29

force: (message?: string) => boolean;

30

create: (message?: string) => boolean;

31

invoke: (message?: string) => boolean;

32

conflict: (message?: string) => boolean;

33

identical: (message?: string) => boolean;

34

info: (message?: string) => boolean;

35

table: (options: { head: string[]; rows: string[][] }) => void;

36

};

37

}

38

39

interface TestAdapterOptions {

40

/** Pre-configured answers for prompts */

41

mockedAnswers?: PromptAnswers;

42

43

/** Throw error if no answer provided for a prompt */

44

throwOnMissingAnswer?: boolean;

45

46

/** Callback function called for each prompt answer */

47

callback?: DummyPromptCallback;

48

49

/** Custom spy factory for creating mocks */

50

spyFactory?: (options: { returns?: any }) => any;

51

}

52

53

type DummyPromptCallback = (

54

this: any,

55

answer: any,

56

options: { question: any }

57

) => any;

58

59

type PromptAnswers = Record<string, any>;

60

type PromptQuestions<T> = Array<{

61

type?: string;

62

name: keyof T;

63

message: string;

64

default?: any;

65

choices?: any[];

66

validate?: Function;

67

filter?: Function;

68

when?: Function | boolean;

69

}>;

70

```

71

72

**Usage Examples:**

73

74

```typescript

75

import { TestAdapter } from "yeoman-test";

76

77

// Basic adapter

78

const adapter = new TestAdapter();

79

80

// Pre-configured adapter

81

const adapter = new TestAdapter({

82

mockedAnswers: {

83

projectName: "my-app",

84

features: ["typescript", "testing"],

85

confirmOverwrite: true

86

}

87

});

88

89

// Use with environment

90

const env = await helpers.createEnv({ adapter });

91

```

92

93

### Prompt Mocking

94

95

Configure automatic responses to generator prompts with validation and transformation.

96

97

```typescript { .api }

98

interface TestAdapterOptions {

99

/** Object mapping prompt names to their answers */

100

mockedAnswers?: PromptAnswers;

101

102

/** Throw error when a prompt has no mocked answer (default: false) */

103

throwOnMissingAnswer?: boolean;

104

105

/** Custom callback to process each prompt answer */

106

callback?: DummyPromptCallback;

107

}

108

109

type DummyPromptCallback = (

110

this: TestAdapter,

111

answer: any,

112

options: {

113

question: {

114

name: string;

115

message: string;

116

type?: string;

117

default?: any;

118

choices?: any[];

119

}

120

}

121

) => any;

122

```

123

124

**Usage Examples:**

125

126

```typescript

127

// Simple answer mocking

128

const adapter = new TestAdapter({

129

mockedAnswers: {

130

projectName: "test-project",

131

author: "Test Author",

132

features: ["feature1", "feature2"],

133

confirmInstall: false

134

},

135

throwOnMissingAnswer: true // Fail if unexpected prompts occur

136

});

137

138

// Advanced prompt processing

139

const adapter = new TestAdapter({

140

mockedAnswers: {

141

projectName: "My Project",

142

email: "test@example.com"

143

},

144

145

callback(answer, { question }) {

146

console.log(`Prompt: ${question.message}`);

147

console.log(`Raw answer: ${answer}`);

148

149

// Transform answers based on question type

150

switch (question.name) {

151

case "projectName":

152

// Convert to slug format

153

return answer.toLowerCase().replace(/\s+/g, "-");

154

155

case "email":

156

// Validate email format

157

if (!/\S+@\S+\.\S+/.test(answer)) {

158

throw new Error("Invalid email format");

159

}

160

return answer.toLowerCase();

161

162

default:

163

return answer;

164

}

165

}

166

});

167

```

168

169

### Logging Interface

170

171

The TestAdapter provides a comprehensive logging interface that can be mocked or monitored during tests.

172

173

```typescript { .api }

174

interface TestAdapterLogger {

175

/** Write message without newline */

176

write(message?: string): boolean;

177

178

/** Write message with newline */

179

writeln(message?: string): boolean;

180

181

/** Log success message */

182

ok(message?: string): boolean;

183

184

/** Log error message */

185

error(message?: string): boolean;

186

187

/** Log skip message */

188

skip(message?: string): boolean;

189

190

/** Log force message */

191

force(message?: string): boolean;

192

193

/** Log create message */

194

create(message?: string): boolean;

195

196

/** Log invoke message */

197

invoke(message?: string): boolean;

198

199

/** Log conflict message */

200

conflict(message?: string): boolean;

201

202

/** Log identical message */

203

identical(message?: string): boolean;

204

205

/** Log info message */

206

info(message?: string): boolean;

207

208

/** Display table */

209

table(options: { head: string[]; rows: string[][] }): void;

210

}

211

```

212

213

**Usage Examples:**

214

215

```typescript

216

import { TestAdapter } from "yeoman-test";

217

import { mock } from "node:test";

218

219

// Mock the logger for testing

220

const logSpy = mock.fn();

221

const adapter = new TestAdapter();

222

223

// Override log methods

224

adapter.log.ok = logSpy;

225

adapter.log.error = logSpy;

226

227

// Use in generator test

228

await helpers.run("my-generator", {}, { adapter });

229

230

// Verify logging behavior

231

expect(logSpy.mock.callCount()).toBeGreaterThan(0);

232

expect(logSpy.mock.calls[0].arguments[0]).toContain("Success");

233

```

234

235

### Prompt Interaction Testing

236

237

Test complex prompt flows and conditional prompts.

238

239

```typescript

240

// Test conditional prompts

241

const adapter = new TestAdapter({

242

mockedAnswers: {

243

useTypeScript: true,

244

tsConfigPath: "./tsconfig.json", // Only asked if useTypeScript is true

245

features: ["linting", "testing"],

246

customLinter: "eslint" // Only asked if "linting" is selected

247

},

248

249

callback(answer, { question }) {

250

// Log all prompt interactions for debugging

251

console.log(`Q: ${question.message}`);

252

console.log(`A: ${JSON.stringify(answer)}`);

253

return answer;

254

}

255

});

256

257

// Test validation failures

258

const adapter = new TestAdapter({

259

mockedAnswers: {

260

port: "3000",

261

email: "invalid-email" // Will trigger validation error

262

},

263

264

callback(answer, { question }) {

265

if (question.name === "email" && question.validate) {

266

const validationResult = question.validate(answer);

267

if (validationResult !== true) {

268

throw new Error(`Validation failed: ${validationResult}`);

269

}

270

}

271

return answer;

272

}

273

});

274

```

275

276

### Integration with RunContext

277

278

TestAdapter integrates seamlessly with RunContext through adapter options.

279

280

```typescript

281

import helpers from "yeoman-test";

282

283

// Create custom adapter

284

const adapter = new TestAdapter({

285

mockedAnswers: {

286

projectName: "test-app",

287

features: ["typescript"]

288

}

289

});

290

291

// Use with RunContext

292

await helpers.run("my-generator")

293

.withAdapterOptions({

294

mockedAnswers: {

295

additional: "answers" // Merged with adapter's answers

296

}

297

})

298

.withEnvironment((env) => {

299

// Override environment adapter

300

env.adapter = adapter;

301

});

302

303

// Alternative: Pass adapter through environment options

304

await helpers.run("my-generator", {}, {

305

adapter: adapter

306

});

307

```

308

309

## Advanced Usage Patterns

310

311

### Custom Spy Factory

312

313

```typescript

314

import { mock } from "node:test";

315

316

const adapter = new TestAdapter({

317

spyFactory: ({ returns }) => {

318

const spy = mock.fn();

319

if (returns !== undefined) {

320

spy.mockReturnValue(returns);

321

}

322

return spy;

323

}

324

});

325

326

// Custom spies will be used internally for prompt mocking

327

```

328

329

### Dynamic Answer Generation

330

331

```typescript

332

const adapter = new TestAdapter({

333

callback(answer, { question }) {

334

// Generate dynamic answers based on question

335

if (question.name === "timestamp") {

336

return new Date().toISOString();

337

}

338

339

if (question.name === "randomId") {

340

return Math.random().toString(36).substr(2, 9);

341

}

342

343

if (question.type === "list" && !answer) {

344

// Default to first choice for list questions

345

return question.choices[0];

346

}

347

348

return answer;

349

}

350

});

351

```

352

353

### Prompt Flow Testing

354

355

```typescript

356

describe("generator prompt flow", () => {

357

it("handles conditional prompts correctly", async () => {

358

const promptAnswers = [];

359

360

const adapter = new TestAdapter({

361

mockedAnswers: {

362

projectType: "library",

363

includeDocs: true,

364

docsFormat: "markdown" // Only asked if includeDocs is true

365

},

366

367

callback(answer, { question }) {

368

promptAnswers.push({ name: question.name, answer });

369

return answer;

370

}

371

});

372

373

await helpers.run("my-generator", {}, { adapter });

374

375

// Verify prompt sequence

376

expect(promptAnswers).toHaveLength(3);

377

expect(promptAnswers[0].name).toBe("projectType");

378

expect(promptAnswers[1].name).toBe("includeDocs");

379

expect(promptAnswers[2].name).toBe("docsFormat");

380

});

381

});

382

```

383

384

## Error Handling

385

386

The TestAdapter provides several error handling mechanisms:

387

388

```typescript

389

// Strict mode - fail on unexpected prompts

390

const strictAdapter = new TestAdapter({

391

throwOnMissingAnswer: true,

392

mockedAnswers: {

393

expected: "value"

394

}

395

});

396

397

// This will throw an error if any prompts other than "expected" are encountered

398

399

// Graceful fallbacks

400

const flexibleAdapter = new TestAdapter({

401

mockedAnswers: {

402

primary: "answer"

403

},

404

405

callback(answer, { question }) {

406

if (answer === undefined) {

407

// Provide fallback for unmocked prompts

408

switch (question.type) {

409

case "confirm":

410

return false;

411

case "input":

412

return question.default || "";

413

case "list":

414

return question.choices?.[0];

415

default:

416

return null;

417

}

418

}

419

return answer;

420

}

421

});

422

```

423

424

The TestAdapter provides comprehensive control over generator prompt interactions, enabling precise testing of user input scenarios and prompt flow validation.