Skip to content

Commit

Permalink
use TestAdapter from @yeoman/adapter
Browse files Browse the repository at this point in the history
  • Loading branch information
mshima committed Jun 8, 2023
1 parent 5d4b70a commit 41b2dde
Show file tree
Hide file tree
Showing 3 changed files with 14 additions and 143 deletions.
8 changes: 4 additions & 4 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"doc_path": "../yeoman-test-doc"
},
"dependencies": {
"@yeoman/adapter": "^1.0.4",
"@yeoman/adapter": "^1.1.0",
"inquirer": "^9.2.2",
"lodash-es": "^4.17.21",
"mem-fs": "^3.0.0",
Expand Down
147 changes: 9 additions & 138 deletions src/adapter.ts
Original file line number Diff line number Diff line change
@@ -1,144 +1,15 @@
import events from 'node:events';
import { PassThrough } from 'node:stream';
import { createLogger, type AdapterWithProgress } from '@yeoman/adapter';
// eslint-disable-next-line n/file-extension-in-import
import { TestAdapter as BaseTestAdapter, type TestAdapterOptions } from '@yeoman/adapter/testing';
import { spy as sinonSpy, stub as sinonStub } from 'sinon';
import type { PromptAnswers, PromptQuestion, Logger, QueuedAdapter, PromptQuestions, Task } from '@yeoman/types';
import { createPromptModule, type PromptModule } from 'inquirer';

export type DummyPromptCallback = (answer: any, { question, answers }: { question: PromptQuestion; answers: PromptAnswers }) => any;

export type DummyPromptOptions = {
mockedAnswers?: PromptAnswers;
callback?: DummyPromptCallback;
throwOnMissingAnswer?: boolean;
};

export type TestAdapterOptions = DummyPromptOptions & { log?: any };

export class DummyPrompt {
answers: PromptAnswers;
question: PromptQuestion;
callback!: DummyPromptCallback;
throwOnMissingAnswer = false;

constructor(question: PromptQuestion, _rl: any, answers: PromptAnswers, options: DummyPromptOptions = {}) {
const { mockedAnswers, callback, throwOnMissingAnswer } = options;
this.answers = { ...answers, ...mockedAnswers };
this.question = question;

this.callback = callback ?? (answers => answers);
this.throwOnMissingAnswer = throwOnMissingAnswer ?? false;
}

async run() {
let answer = this.answers[this.question.name!];
let isSet;

switch (this.question.type) {
case 'list': {
// List prompt accepts any answer value including null
isSet = answer !== undefined;
break;
}

case 'confirm': {
// Ensure that we don't replace `false` with default `true`
isSet = answer || answer === false;
break;
}

default: {
// Other prompts treat all falsy values to default
isSet = Boolean(answer);
}
}

if (!isSet) {
if (answer === undefined && this.question.default === undefined) {
const missingAnswerMessage = `yeoman-test: question ${this.question.name} was asked but answer was not provided`;
console.warn(missingAnswerMessage);
if (this.throwOnMissingAnswer) {
throw new Error(missingAnswerMessage);
}
}

answer = this.question.default;

if (answer === undefined && this.question.type === 'confirm') {
answer = true;
}
}

return this.callback(answer, { question: this.question, answers: this.answers });
}
}

export class TestAdapter implements AdapterWithProgress {
promptModule: PromptModule;
diff: any;
log: Logger;
registerDummyPrompt: (promptName: string, customPromptOptions?: DummyPromptOptions) => PromptModule;

export class TestAdapter extends BaseTestAdapter {
constructor(options: TestAdapterOptions = {}) {
const { log = createLogger(), ...promptOptions } = options;
this.promptModule = createPromptModule({
input: new PassThrough() as any,
output: new PassThrough() as any,
skipTTYChecks: true,
super({
spyFactory: ({ returns }) => (returns ? sinonStub().returns(returns) : sinonSpy()),
...options,
});

const actualRegisterPrompt = this.promptModule.registerPrompt.bind(this.promptModule);

this.registerDummyPrompt = (promptModuleName: string, customPromptOptions?: DummyPromptOptions) =>
actualRegisterPrompt(
promptModuleName,
class CustomDummyPrompt extends DummyPrompt {
constructor(question: PromptQuestion, rl: any, answers: PromptAnswers) {
super(question, rl, answers, customPromptOptions ?? promptOptions);
}
} as any,
);

this.promptModule.registerPrompt = (name: string) => this.registerDummyPrompt(name);

for (const promptName of Object.keys(this.promptModule.prompts)) {
this.promptModule.registerPrompt(promptName, undefined as any);
}

this.diff = sinonSpy();
this.log = sinonSpy() as any;
Object.assign(this.log, events.EventEmitter.prototype);

const descriptors = Object.getOwnPropertyDescriptors(log);
// Make sure all log methods are defined
const logMethods = Object.entries(descriptors)
.filter(([method, desc]) => typeof desc.value === 'function' && !Object.getOwnPropertyDescriptor(this.log, method))
.map(([method]) => method);
for (const methodName of logMethods) {
(this.log as any)[methodName] = sinonStub().returns(this.log);
}
}

async queue<TaskResultType>(fn: Task<TaskResultType>): Promise<void | TaskResultType> {
return fn(this);
}

async progress<ReturnType>(
fn: (progress: { step: (prefix: string, message: string, ...args: any[]) => void }) => ReturnType,
_options?: { disabled?: boolean | undefined; name?: string | undefined } | undefined,
): Promise<void | ReturnType> {
// eslint-disable-next-line @typescript-eslint/no-empty-function
return fn({ step() {} });
}

close(): void {
this.promptModule.restoreDefaultPrompts();
}

async prompt<A extends PromptAnswers = PromptAnswers>(
questions: PromptQuestions<A>,
initialAnswers?: Partial<A> | undefined,
): Promise<A> {
return this.promptModule(questions, initialAnswers);
}
}

// eslint-disable-next-line n/file-extension-in-import
export { DummyPrompt, type DummyPromptOptions, type DummyPromptCallback, type TestAdapterOptions } from '@yeoman/adapter/testing';

0 comments on commit 41b2dde

Please sign in to comment.