Files
sase.tr/apps/web/src/lib/__tests__/surveys.test.ts
Semih Yesilyurt 72c3fe0dc2 feat(surveys): replace Formbricks with self-rendered PostHog API surveys
Formbricks CE turned out to gate person-based targeting (setUserId/attributes
→ 403 enterprise) — the exact flexibility surveys need. PostHog already holds
the person properties, so surveys move back there in API mode: posthog-js
evaluates eligibility (event triggers, targeting flags like
subscription_status=active, wait periods, per-distinct_id dedup) via
getActiveMatchingSurveys, and we render the popover ourselves — zero PostHog
branding, sase.tr dark-theme styling.

- lib/surveys.ts: display manager + capture payload builders that mirror
  posthog-js's own "survey shown/dismissed/sent" shapes exactly
  ($survey_response_<qid>, $set $survey_dismissed/<id>[/iter], seenSurvey_*,
  lastSeenSurveyDate) so the PostHog Surveys results UI works unchanged
- components/survey-popover.tsx: single_choice (+Diğer), open text, rating/NPS
- posthog.ts: capture/pageview/identify now schedule survey checks; register
  deploy_env (dev.sase.tr ships the key now → staging traffic is filterable)
- remove @formbricks/js, its CSP entries and build args

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 00:40:18 +03:00

74 lines
2.8 KiB
TypeScript

import type { Survey } from "posthog-js";
import { describe, expect, it } from "vitest";
import {
buildDismissedProperties,
buildSentProperties,
seenSurveyStorageKey,
surveyInteractionKey,
} from "../surveys";
// The capture payloads must match what posthog-js's own widget sends — the
// Surveys results UI, the internal targeting flags ($survey_dismissed/<id>)
// and cross-device dedup all key off these exact shapes.
function mkSurvey(overrides: Record<string, unknown> = {}): Survey {
return {
id: "sv1",
name: "Test Survey",
type: "api",
questions: [
{ id: "q1", type: "single_choice", question: "Neden?", choices: ["A", "B"] },
{ id: "q2", type: "open", question: "Detay?", optional: true },
],
current_iteration: null,
current_iteration_start_date: null,
...overrides,
} as unknown as Survey;
}
describe("surveyInteractionKey", () => {
it("builds the plain person-property key for one-off surveys", () => {
expect(surveyInteractionKey(mkSurvey(), "dismissed")).toBe("$survey_dismissed/sv1");
});
it("appends the iteration for recurring surveys (matches the internal targeting flag)", () => {
const s = mkSurvey({ current_iteration: 1 });
expect(surveyInteractionKey(s, "responded")).toBe("$survey_responded/sv1/1");
});
});
describe("seenSurveyStorageKey", () => {
it("matches posthog-js seenSurvey_ localStorage convention", () => {
expect(seenSurveyStorageKey(mkSurvey())).toBe("seenSurvey_sv1");
expect(seenSurveyStorageKey(mkSurvey({ current_iteration: 2 }))).toBe("seenSurvey_sv1_2");
});
});
describe("buildSentProperties", () => {
it("keys responses by question id and marks the responded person property", () => {
const props = buildSentProperties(mkSurvey(), { q1: "A", q2: null }, "sub-1");
expect(props.$survey_id).toBe("sv1");
expect(props.$survey_response_q1).toBe("A");
expect(props.$survey_response_q2).toBeNull();
expect(props.$survey_completed).toBe(true);
expect(props.$survey_submission_id).toBe("sub-1");
expect(props.$set).toEqual({ "$survey_responded/sv1": true });
expect(props.$survey_questions).toEqual([
{ id: "q1", question: "Neden?", response: "A" },
{ id: "q2", question: "Detay?", response: null },
]);
});
});
describe("buildDismissedProperties", () => {
it("flags partial completion only when something was answered", () => {
const empty = buildDismissedProperties(mkSurvey(), { q1: null });
expect(empty.$survey_partially_completed).toBe(false);
expect(empty.$set).toEqual({ "$survey_dismissed/sv1": true });
const partial = buildDismissedProperties(mkSurvey(), { q1: "B", q2: null });
expect(partial.$survey_partially_completed).toBe(true);
expect(partial.$survey_response_q1).toBe("B");
});
});