Skip to content

Conversation

@joehan
Copy link
Contributor

@joehan joehan commented Aug 29, 2025

Description

From Jules:

Adding unit tests for prompt.ts

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary of Changes

Hello @joehan, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request focuses on bolstering the robustness of the prompt module by adding extensive unit tests. The primary goal is to ensure that the prompting functions behave predictably and correctly, especially when used in non-interactive environments where user input is not available. This enhancement improves the reliability of the module and prevents unexpected errors in automated or script-driven contexts.

Highlights

  • Enhanced Test Coverage for prompt.ts: This pull request introduces a comprehensive suite of unit tests for the prompt.ts module, significantly improving test coverage for various prompt types.
  • Non-Interactive Mode Testing: New tests specifically validate the behavior of prompt.confirm, prompt.input, prompt.checkbox, prompt.select, prompt.number, prompt.password, and prompt.search functions when operating in nonInteractive mode.
  • Default Value Handling: Tests ensure that prompts correctly utilize provided default values in nonInteractive mode and throw FirebaseError when a default is not supplied in this mode, as expected.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds a comprehensive set of unit tests for the non-interactive behavior of various prompt types in prompt.ts. The tests correctly cover cases where a default value is provided and where it is not. My feedback focuses on improving the maintainability and readability of the new test code by reducing duplication and improving code style.

Comment on lines +54 to 214
it("handles non-interactive with default", async () => {
const result = await prompt.confirm({
message: "Continue?",
nonInteractive: true,
default: false,
});
expect(result).to.be.false;
});

it("throws in non-interactive without default", async () => {
await expect(
prompt.confirm({
message: "Continue?",
nonInteractive: true,
}),
).to.be.rejectedWith(
FirebaseError,
'Question "Continue?" does not have a default and cannot be answered in non-interactive mode',
);
});
});

describe("input", () => {
it("handles non-interactive with default", async () => {
const result = await prompt.input({
message: "Name?",
nonInteractive: true,
default: "Inigo Montoya",
});
expect(result).to.equal("Inigo Montoya");
});

it("throws in non-interactive without default", async () => {
await expect(
prompt.input({
message: "Name?",
nonInteractive: true,
}),
).to.be.rejectedWith(
FirebaseError,
'Question "Name?" does not have a default and cannot be answered in non-interactive mode',
);
});
});

describe("checkbox", () => {
it("handles non-interactive with default", async () => {
const result = await prompt.checkbox({
message: "Tools?",
nonInteractive: true,
choices: ["hammer", "wrench", "saw"],
default: ["hammer", "wrench"],
});
expect(result).to.deep.equal(["hammer", "wrench"]);
});

it("throws in non-interactive without default", async () => {
await expect(
prompt.checkbox({
message: "Tools?",
nonInteractive: true,
choices: ["hammer", "wrench", "saw"],
}),
).to.be.rejectedWith(
FirebaseError,
'Question "Tools?" does not have a default and cannot be answered in non-interactive mode',
);
});
});

describe("select", () => {
it("handles non-interactive with default", async () => {
const result = await prompt.select({
message: "Tool?",
nonInteractive: true,
choices: ["hammer", "wrench", "saw"],
default: "wrench",
});
expect(result).to.equal("wrench");
});

it("throws in non-interactive without default", async () => {
await expect(
prompt.select({
message: "Tool?",
nonInteractive: true,
choices: ["hammer", "wrench", "saw"],
}),
).to.be.rejectedWith(
FirebaseError,
'Question "Tool?" does not have a default and cannot be answered in non-interactive mode',
);
});
});

describe("number", () => {
it("handles non-interactive with default", async () => {
const result = await prompt.number({
message: "Count?",
nonInteractive: true,
default: 42,
});
expect(result).to.equal(42);
});

it("throws in non-interactive without default", async () => {
await expect(
prompt.number({
message: "Count?",
nonInteractive: true,
}),
).to.be.rejectedWith(
FirebaseError,
'Question "Count?" does not have a default and cannot be answered in non-interactive mode',
);
});
});

describe("password", () => {
it("throws in non-interactive", async () => {
await expect(
prompt.password({
message: "Password?",
nonInteractive: true,
}),
).to.be.rejectedWith(
FirebaseError,
'Question "Password?" does not have a default and cannot be answered in non-interactive mode',
);
});
});

describe("search", () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const source = (term: string | undefined) => {
return ["a", "b", "c"];
};

it("handles non-interactive with default", async () => {
const result = await prompt.search({
message: "Letter?",
nonInteractive: true,
source,
default: "b",
});
expect(result).to.equal("b");
});

it("throws in non-interactive without default", async () => {
await expect(
prompt.search({
message: "Letter?",
nonInteractive: true,
source,
}),
).to.be.rejectedWith(
FirebaseError,
'Question "Letter?" does not have a default and cannot be answered in non-interactive mode',
);
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The tests for different prompt types in non-interactive mode are very similar and contain a lot of duplicated code. This reduces readability and maintainability.

To improve this, consider refactoring to a data-driven approach. You can define an array of test cases and iterate over it to generate the tests dynamically. This will make the test suite more concise and easier to extend.

Here is an example of how you could structure this:

const testCases = [
  {
    name: "confirm",
    promptFn: prompt.confirm,
    message: "Continue?",
    default: false,
    extraOpts: {},
    assertion: (result: boolean) => expect(result).to.be.false,
  },
  {
    name: "input",
    promptFn: prompt.input,
    message: "Name?",
    default: "Inigo Montoya",
    extraOpts: {},
    assertion: (result: string) => expect(result).to.equal("Inigo Montoya"),
  },
  {
    name: "checkbox",
    promptFn: prompt.checkbox,
    message: "Tools?",
    default: ["hammer", "wrench"],
    extraOpts: { choices: ["hammer", "wrench", "saw"] },
    assertion: (result: string[]) => expect(result).to.deep.equal(["hammer", "wrench"]),
  },
  // ... other test cases for select, number, search
];

testCases.forEach(({ name, promptFn, message, default: defaultValue, extraOpts, assertion }) => {
  describe(name, () => {
    it("handles non-interactive with default", async () => {
      const result = await (promptFn as any)({
        message,
        nonInteractive: true,
        default: defaultValue,
        ...extraOpts,
      });
      assertion(result);
    });

    it("throws in non-interactive without default", async () => {
      await expect(
        (promptFn as any)({
          message,
          nonInteractive: true,
          ...extraOpts,
        }),
      ).to.be.rejectedWith(
        FirebaseError,
        `Question "${message}" does not have a default and cannot be answered in non-interactive mode`,
      );
    });
  });
});

// The 'password' prompt is a special case and can be tested separately.
describe("password", () => {
  it("throws in non-interactive", async () => {
    await expect(
      prompt.password({
        message: "Password?",
        nonInteractive: true,
      }),
    ).to.be.rejectedWith(
      FirebaseError,
      'Question "Password?" does not have a default and cannot be answered in non-interactive mode',
    );
  });
});

Comment on lines +187 to +188
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const source = (term: string | undefined) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To avoid using eslint-disable-next-line, you can prefix the unused term parameter with an underscore. This is a common convention to signal that a parameter is intentionally unused and satisfies the linter.

      const source = (_term: string | undefined) => {

Copy link
Contributor

@aalej aalej left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@joehan joehan enabled auto-merge (squash) September 2, 2025 18:31
@joehan joehan disabled auto-merge September 2, 2025 19:54
@joehan joehan merged commit a175544 into master Sep 2, 2025
47 of 48 checks passed
@joehan joehan deleted the test-prompt branch September 2, 2025 19:54
@github-project-automation github-project-automation bot moved this from Approved [PR] to Done in [Cloud] Extensions + Functions Sep 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants