From cd03f941a4f8e4bf5eef157c1f4aa1e84dd9306d Mon Sep 17 00:00:00 2001 From: Christopher Dierkens Date: Fri, 16 Aug 2024 14:02:44 -0400 Subject: [PATCH 1/6] fix: respect the default for a text prompt --- .changeset/cool-dogs-behave.md | 5 ++ packages/cli/src/internal/prompt/text.ts | 14 +++- packages/cli/test/Prompt.test.ts | 84 ++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 .changeset/cool-dogs-behave.md create mode 100644 packages/cli/test/Prompt.test.ts diff --git a/.changeset/cool-dogs-behave.md b/.changeset/cool-dogs-behave.md new file mode 100644 index 00000000000..15afb67af71 --- /dev/null +++ b/.changeset/cool-dogs-behave.md @@ -0,0 +1,5 @@ +--- +"@effect/cli": patch +--- + +Respect the `Prompt.TextOptions.default` for a prompt created with `Prompt.text` diff --git a/packages/cli/src/internal/prompt/text.ts b/packages/cli/src/internal/prompt/text.ts index f2a20beda82..7cac89668b3 100644 --- a/packages/cli/src/internal/prompt/text.ts +++ b/packages/cli/src/internal/prompt/text.ts @@ -5,6 +5,7 @@ import * as Optimize from "@effect/printer/Optimize" import * as Arr from "effect/Array" import * as Effect from "effect/Effect" import * as Option from "effect/Option" +import * as Predicate from "effect/Predicate" import * as Redacted from "effect/Redacted" import type * as Prompt from "../../Prompt.js" import * as InternalPrompt from "../prompt.js" @@ -264,7 +265,18 @@ function basePrompt( validate: Effect.succeed, ...options } - return InternalPrompt.custom(initialState, { + + const state: State = { + ...initialState, + ...(Predicate.isString(options.default) ? + { + value: options.default, + cursor: options.default.length + } : + undefined) + } + + return InternalPrompt.custom(state, { render: handleRender(opts), process: handleProcess(opts), clear: handleClear(opts) diff --git a/packages/cli/test/Prompt.test.ts b/packages/cli/test/Prompt.test.ts new file mode 100644 index 00000000000..8878601c889 --- /dev/null +++ b/packages/cli/test/Prompt.test.ts @@ -0,0 +1,84 @@ +import type * as CliApp from "@effect/cli/CliApp" +import * as Command from "@effect/cli/Command" +import * as Prompt from "@effect/cli/Prompt" +import * as MockConsole from "@effect/cli/test/services/MockConsole" +import * as MockTerminal from "@effect/cli/test/services/MockTerminal" +import {} from "@effect/platform" +import { Array, Effect } from "effect" +import * as Console from "effect/Console" +import * as Fiber from "effect/Fiber" +import * as Layer from "effect/Layer" +import { describe, expect, it } from "vitest" + +const MainLive = Effect.gen(function*(_) { + const console = yield* _(MockConsole.make) + return Layer.mergeAll( + Console.setConsole(console), + MockTerminal.layer + ) +}).pipe(Layer.unwrapEffect) + +const runEffect = ( + self: Effect.Effect +): Promise => Effect.provide(self, MainLive).pipe(Effect.runPromise) + +describe("Prompt", () => { + describe("text", () => { + it("should return an empty string when `default` on `Prompt.TextOptions` is not provided", () => + Effect.gen(function*(_) { + const prompt = Prompt.text({ + message: "This should not have a default" + }) + + const command = Command.prompt( + "prompt-command", + prompt, + (value) => + Console.log( + `Prompt value: "${value}"` + ) + ) + + const cli = Command.run(command, { + name: "Default Value App", + version: "0.0.1" + }) + + const fiber = yield* _(Effect.fork(cli([]))) + yield* _(MockTerminal.inputKey("Enter")) + yield* _(Fiber.join(fiber)) + const lines = yield* _(MockConsole.getLines({ stripAnsi: true })) + const result = Array.some(lines, (line) => line.includes("Prompt value: \"\"")) + expect(result).toBe(true) + }).pipe(runEffect)) + + it("should respect the `default` property on `Prompt.TextOptions`", () => + Effect.gen(function*(_) { + const prompt = Prompt.text({ + message: "This should have a default", + default: "default-value" + }) + + const command = Command.prompt( + "prompt-command", + prompt, + (value) => + Console.log( + `Prompt value: "${value}"` + ) + ) + + const cli = Command.run(command, { + name: "Default Value App", + version: "0.0.1" + }) + + const fiber = yield* _(Effect.fork(cli([]))) + yield* _(MockTerminal.inputKey("Enter")) + yield* _(Fiber.join(fiber)) + const lines = yield* _(MockConsole.getLines({ stripAnsi: true })) + const result = Array.some(lines, (line) => line.includes("Prompt value: \"default-value\"")) + expect(result).toBe(true) + }).pipe(runEffect)) + }) +}) From 31c97d79ea3d618e025c36218fbe11b98dca4fa1 Mon Sep 17 00:00:00 2001 From: Christopher Dierkens Date: Sat, 17 Aug 2024 12:17:23 -0400 Subject: [PATCH 2/6] fix: remove cli app from prompt tests --- packages/cli/test/Prompt.test.ts | 67 +++++++++----------------------- 1 file changed, 19 insertions(+), 48 deletions(-) diff --git a/packages/cli/test/Prompt.test.ts b/packages/cli/test/Prompt.test.ts index 8878601c889..4a6ef120c7d 100644 --- a/packages/cli/test/Prompt.test.ts +++ b/packages/cli/test/Prompt.test.ts @@ -1,83 +1,54 @@ -import type * as CliApp from "@effect/cli/CliApp" -import * as Command from "@effect/cli/Command" import * as Prompt from "@effect/cli/Prompt" import * as MockConsole from "@effect/cli/test/services/MockConsole" import * as MockTerminal from "@effect/cli/test/services/MockTerminal" -import {} from "@effect/platform" +import { } from "@effect/platform" import { Array, Effect } from "effect" import * as Console from "effect/Console" import * as Fiber from "effect/Fiber" import * as Layer from "effect/Layer" import { describe, expect, it } from "vitest" +import { Terminal } from "../../platform/src/Terminal.js" const MainLive = Effect.gen(function*(_) { const console = yield* _(MockConsole.make) return Layer.mergeAll( Console.setConsole(console), - MockTerminal.layer + MockTerminal.layer, ) }).pipe(Layer.unwrapEffect) const runEffect = ( - self: Effect.Effect + self: Effect.Effect ): Promise => Effect.provide(self, MainLive).pipe(Effect.runPromise) describe("Prompt", () => { describe("text", () => { - it("should return an empty string when `default` on `Prompt.TextOptions` is not provided", () => - Effect.gen(function*(_) { + it("should use the prompt value when no default is provided", () => + Effect.gen(function*() { const prompt = Prompt.text({ - message: "This should not have a default" + message: "This does not have a default", }) - const command = Command.prompt( - "prompt-command", - prompt, - (value) => - Console.log( - `Prompt value: "${value}"` - ) - ) - - const cli = Command.run(command, { - name: "Default Value App", - version: "0.0.1" - }) - - const fiber = yield* _(Effect.fork(cli([]))) - yield* _(MockTerminal.inputKey("Enter")) - yield* _(Fiber.join(fiber)) - const lines = yield* _(MockConsole.getLines({ stripAnsi: true })) - const result = Array.some(lines, (line) => line.includes("Prompt value: \"\"")) + const fiber = yield* Effect.fork(prompt) + yield* MockTerminal.inputKey("enter") + yield* Fiber.join(fiber) + const lines = yield* MockConsole.getLines({ stripAnsi: true }) + const result = Array.some(lines, (line) => line.includes("? This does not have a default › ")) expect(result).toBe(true) }).pipe(runEffect)) - it("should respect the `default` property on `Prompt.TextOptions`", () => - Effect.gen(function*(_) { + it("should use the default value when the default is provided", () => + Effect.gen(function*() { const prompt = Prompt.text({ message: "This should have a default", default: "default-value" }) - const command = Command.prompt( - "prompt-command", - prompt, - (value) => - Console.log( - `Prompt value: "${value}"` - ) - ) - - const cli = Command.run(command, { - name: "Default Value App", - version: "0.0.1" - }) - - const fiber = yield* _(Effect.fork(cli([]))) - yield* _(MockTerminal.inputKey("Enter")) - yield* _(Fiber.join(fiber)) - const lines = yield* _(MockConsole.getLines({ stripAnsi: true })) - const result = Array.some(lines, (line) => line.includes("Prompt value: \"default-value\"")) + const fiber = yield* Effect.fork(prompt) + yield* MockTerminal.inputKey("enter") + yield* Fiber.join(fiber) + const lines = yield* MockConsole.getLines({ stripAnsi: true }) + const result = Array.some(lines, (line) => line.includes("? This should have a default › default-value")) expect(result).toBe(true) }).pipe(runEffect)) }) From f28035adb38a297f28a0b9b5de8702cbdca5fa9c Mon Sep 17 00:00:00 2001 From: Christopher Dierkens Date: Sat, 17 Aug 2024 23:46:44 -0400 Subject: [PATCH 3/6] chore: simplify tests more --- packages/cli/test/Prompt.test.ts | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/packages/cli/test/Prompt.test.ts b/packages/cli/test/Prompt.test.ts index 4a6ef120c7d..103112a8249 100644 --- a/packages/cli/test/Prompt.test.ts +++ b/packages/cli/test/Prompt.test.ts @@ -1,24 +1,20 @@ import * as Prompt from "@effect/cli/Prompt" -import * as MockConsole from "@effect/cli/test/services/MockConsole" import * as MockTerminal from "@effect/cli/test/services/MockTerminal" -import { } from "@effect/platform" -import { Array, Effect } from "effect" -import * as Console from "effect/Console" +import type { Terminal } from "@effect/platform" +import { Effect } from "effect" import * as Fiber from "effect/Fiber" import * as Layer from "effect/Layer" import { describe, expect, it } from "vitest" -import { Terminal } from "../../platform/src/Terminal.js" + const MainLive = Effect.gen(function*(_) { - const console = yield* _(MockConsole.make) return Layer.mergeAll( - Console.setConsole(console), MockTerminal.layer, ) }).pipe(Layer.unwrapEffect) const runEffect = ( - self: Effect.Effect + self: Effect.Effect ): Promise => Effect.provide(self, MainLive).pipe(Effect.runPromise) describe("Prompt", () => { @@ -31,10 +27,9 @@ describe("Prompt", () => { const fiber = yield* Effect.fork(prompt) yield* MockTerminal.inputKey("enter") - yield* Fiber.join(fiber) - const lines = yield* MockConsole.getLines({ stripAnsi: true }) - const result = Array.some(lines, (line) => line.includes("? This does not have a default › ")) - expect(result).toBe(true) + const result = yield* Fiber.join(fiber) + + expect(result).toBe('') }).pipe(runEffect)) it("should use the default value when the default is provided", () => @@ -43,13 +38,12 @@ describe("Prompt", () => { message: "This should have a default", default: "default-value" }) - + const fiber = yield* Effect.fork(prompt) yield* MockTerminal.inputKey("enter") - yield* Fiber.join(fiber) - const lines = yield* MockConsole.getLines({ stripAnsi: true }) - const result = Array.some(lines, (line) => line.includes("? This should have a default › default-value")) - expect(result).toBe(true) + const result = yield* Fiber.join(fiber) + + expect(result).toBe('default-value') }).pipe(runEffect)) }) }) From 41fade279c79b9b1e628014a48bca33b81ccdc58 Mon Sep 17 00:00:00 2001 From: Christopher Dierkens Date: Sat, 17 Aug 2024 23:48:26 -0400 Subject: [PATCH 4/6] fix: use the text prompt default in processing --- packages/cli/src/internal/prompt/text.ts | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/packages/cli/src/internal/prompt/text.ts b/packages/cli/src/internal/prompt/text.ts index 7cac89668b3..bbc27086d4d 100644 --- a/packages/cli/src/internal/prompt/text.ts +++ b/packages/cli/src/internal/prompt/text.ts @@ -5,7 +5,6 @@ import * as Optimize from "@effect/printer/Optimize" import * as Arr from "effect/Array" import * as Effect from "effect/Effect" import * as Option from "effect/Option" -import * as Predicate from "effect/Predicate" import * as Redacted from "effect/Redacted" import type * as Prompt from "../../Prompt.js" import * as InternalPrompt from "../prompt.js" @@ -233,10 +232,11 @@ function handleProcess(options: Options) { } case "enter": case "return": { - return Effect.match(options.validate(state.value), { + const value = state.value.length > 0 ? state.value : options.default + return Effect.match(options.validate(value), { onFailure: (error) => Action.NextFrame({ - state: { ...state, error: Option.some(error) } + state: { ...state, value, error: Option.some(error) } }), onSuccess: (value) => Action.Submit({ value }) }) @@ -266,17 +266,7 @@ function basePrompt( ...options } - const state: State = { - ...initialState, - ...(Predicate.isString(options.default) ? - { - value: options.default, - cursor: options.default.length - } : - undefined) - } - - return InternalPrompt.custom(state, { + return InternalPrompt.custom(initialState, { render: handleRender(opts), process: handleProcess(opts), clear: handleClear(opts) From 9ec3d8c2840b0e4e265e218fe4411c1a2745f088 Mon Sep 17 00:00:00 2001 From: Christopher Dierkens Date: Sun, 18 Aug 2024 11:41:37 -0400 Subject: [PATCH 5/6] chore(cli): simplify promt test effect runner and normalize imports --- packages/cli/test/Prompt.test.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/cli/test/Prompt.test.ts b/packages/cli/test/Prompt.test.ts index 103112a8249..19d085cf02a 100644 --- a/packages/cli/test/Prompt.test.ts +++ b/packages/cli/test/Prompt.test.ts @@ -1,21 +1,15 @@ import * as Prompt from "@effect/cli/Prompt" import * as MockTerminal from "@effect/cli/test/services/MockTerminal" -import type { Terminal } from "@effect/platform" -import { Effect } from "effect" +import type { Terminal } from "@effect/platform/Terminal" +import * as Effect from "effect/Effect" import * as Fiber from "effect/Fiber" -import * as Layer from "effect/Layer" import { describe, expect, it } from "vitest" -const MainLive = Effect.gen(function*(_) { - return Layer.mergeAll( - MockTerminal.layer, - ) -}).pipe(Layer.unwrapEffect) const runEffect = ( - self: Effect.Effect -): Promise => Effect.provide(self, MainLive).pipe(Effect.runPromise) + self: Effect.Effect +): Promise => Effect.provide(self, MockTerminal.layer).pipe(Effect.runPromise) describe("Prompt", () => { describe("text", () => { From 338e718a41210bee62ce33b6bfd7d85c79969723 Mon Sep 17 00:00:00 2001 From: Christopher Dierkens Date: Sun, 18 Aug 2024 14:10:44 -0400 Subject: [PATCH 6/6] chore: lint fix to add extra line after imports --- packages/cli/test/Prompt.test.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/cli/test/Prompt.test.ts b/packages/cli/test/Prompt.test.ts index 19d085cf02a..9f6f771a12d 100644 --- a/packages/cli/test/Prompt.test.ts +++ b/packages/cli/test/Prompt.test.ts @@ -5,8 +5,6 @@ import * as Effect from "effect/Effect" import * as Fiber from "effect/Fiber" import { describe, expect, it } from "vitest" - - const runEffect = ( self: Effect.Effect ): Promise => Effect.provide(self, MockTerminal.layer).pipe(Effect.runPromise) @@ -16,14 +14,14 @@ describe("Prompt", () => { it("should use the prompt value when no default is provided", () => Effect.gen(function*() { const prompt = Prompt.text({ - message: "This does not have a default", + message: "This does not have a default" }) const fiber = yield* Effect.fork(prompt) yield* MockTerminal.inputKey("enter") const result = yield* Fiber.join(fiber) - expect(result).toBe('') + expect(result).toBe("") }).pipe(runEffect)) it("should use the default value when the default is provided", () => @@ -32,12 +30,12 @@ describe("Prompt", () => { message: "This should have a default", default: "default-value" }) - + const fiber = yield* Effect.fork(prompt) yield* MockTerminal.inputKey("enter") const result = yield* Fiber.join(fiber) - expect(result).toBe('default-value') + expect(result).toBe("default-value") }).pipe(runEffect)) }) })