Files
SnapOtter/tests/unit/web/feedback-dialog.test.tsx
T
SnapOtterandGitHub a0d1c70172 feat: add usage onboarding survey overlay (#388)
* feat: add usage-survey feedback types and gating function

Claude-Session: https://claude.ai/code/session_01KAC9Lbx8AmebAnj9WQZXHp

* feat: add onboarding usage-survey i18n strings to all locales

Relabels three ambiguous feedback.usageTypes values (personal/team_internal/
business_workflow) and adds a new onboarding namespace (4 keys) across the
reference locale and all 20 translations, so the tree compiles at every
commit instead of only after both locale groups land.

Claude-Session: https://claude.ai/code/session_01KAC9Lbx8AmebAnj9WQZXHp

* feat: add UsageSurveyOverlay component

* feat: mount UsageSurveyOverlay inside AuthGuard

Claude-Session: https://claude.ai/code/session_01KAC9Lbx8AmebAnj9WQZXHp

* fix: use text-start instead of text-left for RTL support in UsageSurveyOverlay

* refactor: drop redundant usage-type field from the admin feedback dialog

Claude-Session: https://claude.ai/code/session_01KAC9Lbx8AmebAnj9WQZXHp

* feat: accept onboarding source and survey id in the feedback route

Claude-Session: https://claude.ai/code/session_01KAC9Lbx8AmebAnj9WQZXHp

* test: cover the onboarding source in the feedback route integration test

Claude-Session: https://claude.ai/code/session_01KAC9Lbx8AmebAnj9WQZXHp

* chore: remove orphaned usageTypeLabel i18n key

* refactor: derive feedback source/survey_id enums from a single source of truth

* perf: skip the settings fetch in UsageSurveyOverlay for non-admin users
2026-07-02 00:02:24 +08:00

82 lines
2.7 KiB
TypeScript

// @vitest-environment jsdom
import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";
import { FeedbackDialog } from "@/components/feedback/feedback-dialog";
const submitFeedback = vi.hoisted(() => vi.fn().mockResolvedValue({ ok: true, accepted: true }));
vi.mock("@/lib/feedback", async (importOriginal) => {
const actual: Record<string, unknown> = await importOriginal();
return {
...actual,
submitFeedback,
};
});
afterEach(() => {
cleanup();
submitFeedback.mockClear();
});
describe("FeedbackDialog", () => {
it("submits admin install feedback with install-specific fields", async () => {
render(<FeedbackDialog open={true} source="admin_installer" onClose={vi.fn()} />);
fireEvent.change(screen.getByLabelText("Install method"), {
target: { value: "docker_compose" },
});
fireEvent.change(screen.getByLabelText("Hardest setup area"), {
target: { value: "environment_variables" },
});
fireEvent.click(screen.getByLabelText("PDF/docs"));
fireEvent.click(screen.getByLabelText("Batch workflows"));
fireEvent.change(screen.getByLabelText("What should we improve first?"), {
target: { value: "The S3 example needs one complete compose snippet." },
});
fireEvent.click(screen.getByRole("button", { name: "Send feedback" }));
await waitFor(() => {
expect(submitFeedback).toHaveBeenCalledWith({
source: "admin_installer",
surveyId: "admin-install-v1",
promptVariant: "settings-card-v1",
feedbackType: "other",
message: "The S3 example needs one complete compose snippet.",
contactOk: false,
contactEmail: undefined,
contactName: undefined,
company: undefined,
installMethod: "docker_compose",
frictionArea: "environment_variables",
importantAreas: ["pdf_docs", "batch_workflows"],
});
});
});
it("does not submit contact fields without contact consent", async () => {
render(<FeedbackDialog open={true} source="global" onClose={vi.fn()} />);
fireEvent.change(screen.getByLabelText("Feedback"), {
target: { value: "I want a keyboard shortcut for download." },
});
expect(screen.queryByPlaceholderText("Email (optional)")).toBeNull();
fireEvent.click(screen.getByRole("button", { name: "Send feedback" }));
await waitFor(() => {
expect(submitFeedback).toHaveBeenCalledWith(
expect.objectContaining({
source: "global",
surveyId: "global-feedback-v1",
contactOk: false,
contactEmail: undefined,
contactName: undefined,
company: undefined,
}),
);
});
});
});