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>
74 lines
2.8 KiB
TypeScript
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");
|
|
});
|
|
});
|