mirror of
https://github.com/hanzoai/insights-js.git
synced 2026-10-30 18:34:19 +00:00
We own the ingestion server (hanzoai/insights: Django + rust/capture + nodejs plugin) AND this SDK, so there is no external wire contract to preserve. The server is already 100% posthog-free; this SDK was the last laggard. - $last_posthog_reset -> $last_insights_reset. This FIXES A LIVE MISMATCH: the server taxonomy (insights/taxonomy/taxonomy.py, insights/insightsql/ai.py) already expected the insights form while the SDK emitted the posthog form. - __PosthogExtensions__ -> __InsightsExtensions__; all PostHog/posthog/POSTHOG identifiers -> Insights/insights/INSIGHTS (src, tests, playground, playwright, scripts, rrweb subpackages, package.json keywords). - Hosts: DEFAULT_PLUGIN_HOST + DEFAULT_NUXT_HOST + the core host default pointed at PostHog's cloud (us.i.posthog.com) — a real bug. All -> insights.hanzo.ai. - Removed the us-assets/eu-assets CDN split in getRemoteConfig (PostHog-specific; Hanzo serves remote config from the same host) — it had degenerated to a no-op. - Fixed generate-docs writing to a stray references/@hanzo/ subdir. - Renamed the 3 posthog-named test files; dropped 58MB of stale reference snapshots named for upstream posthog versions we never shipped (they are generated output — references/ is now gitignored). Verified: typecheck errors 42 (HEAD) -> 18 (all pre-existing test-mock looseness); types package 0; every package.json valid with @hanzo/* names. Guard: grep -rioE 'posthog' packages ... | wc -l => 0
383 lines
11 KiB
TypeScript
383 lines
11 KiB
TypeScript
import { sendSurveyEvent } from '../src/surveys/components/Surveys'
|
|
import { Survey, SurveyQuestion } from '@hanzo/insights-core'
|
|
|
|
const createMockSurvey = (overrides: Partial<Survey> = {}): Survey => ({
|
|
id: 'test-survey-id',
|
|
name: 'Test Survey',
|
|
questions: [
|
|
{
|
|
id: 'question-1',
|
|
question: 'How satisfied are you?',
|
|
type: 'rating',
|
|
scale: 5,
|
|
originalQuestionIndex: 0,
|
|
} as SurveyQuestion,
|
|
{
|
|
id: 'question-2',
|
|
question: 'Any additional comments?',
|
|
type: 'open',
|
|
originalQuestionIndex: 1,
|
|
} as SurveyQuestion,
|
|
],
|
|
...overrides,
|
|
})
|
|
|
|
describe('sendSurveyEvent', () => {
|
|
let mockInsights: any
|
|
|
|
beforeEach(() => {
|
|
mockInsights = {
|
|
capture: jest.fn(),
|
|
}
|
|
})
|
|
|
|
describe('basic functionality', () => {
|
|
it('should capture survey sent event with correct base properties', () => {
|
|
const survey = createMockSurvey()
|
|
const responses = {
|
|
'$survey_response_question-1': 4,
|
|
'$survey_response_question-2': 'Great service!',
|
|
}
|
|
|
|
sendSurveyEvent(responses, survey, mockInsights)
|
|
|
|
expect(mockInsights.capture).toHaveBeenCalledWith('survey sent', {
|
|
$survey_name: 'Test Survey',
|
|
$survey_id: 'test-survey-id',
|
|
$survey_questions: [
|
|
{
|
|
id: 'question-1',
|
|
question: 'How satisfied are you?',
|
|
response: 4,
|
|
},
|
|
{
|
|
id: 'question-2',
|
|
question: 'Any additional comments?',
|
|
response: 'Great service!',
|
|
},
|
|
],
|
|
'$survey_response_question-1': 4,
|
|
'$survey_response_question-2': 'Great service!',
|
|
$survey_response: 4,
|
|
$survey_response_1: 'Great service!',
|
|
$set: {
|
|
'$survey_responded/test-survey-id': true,
|
|
},
|
|
})
|
|
})
|
|
|
|
it('should include survey language when provided', () => {
|
|
const survey = createMockSurvey()
|
|
|
|
sendSurveyShownEvent(survey, mockInsights, 'pt')
|
|
dismissedSurveyEvent(survey, {}, mockInsights, 'pt')
|
|
sendSurveyEvent({}, survey, mockInsights, 'pt')
|
|
|
|
expect(mockInsights.capture).toHaveBeenNthCalledWith(
|
|
1,
|
|
'survey shown',
|
|
expect.objectContaining({ $survey_language: 'pt' })
|
|
)
|
|
expect(mockInsights.capture).toHaveBeenNthCalledWith(
|
|
2,
|
|
'survey dismissed',
|
|
expect.objectContaining({ $survey_language: 'pt' })
|
|
)
|
|
expect(mockInsights.capture).toHaveBeenNthCalledWith(
|
|
3,
|
|
'survey sent',
|
|
expect.objectContaining({ $survey_language: 'pt' })
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('survey iterations', () => {
|
|
it('should include iteration properties when survey has current_iteration', () => {
|
|
const survey = createMockSurvey({
|
|
current_iteration: 2,
|
|
current_iteration_start_date: '2024-01-15',
|
|
})
|
|
const responses = {
|
|
'$survey_response_question-1': 4,
|
|
'$survey_response_question-2': 'Great service!',
|
|
}
|
|
|
|
sendSurveyEvent(responses, survey, mockInsights)
|
|
|
|
expect(mockInsights.capture).toHaveBeenCalledWith('survey sent', {
|
|
$survey_name: 'Test Survey',
|
|
$survey_id: 'test-survey-id',
|
|
$survey_iteration: 2,
|
|
$survey_iteration_start_date: '2024-01-15',
|
|
$survey_questions: [
|
|
{
|
|
id: 'question-1',
|
|
question: 'How satisfied are you?',
|
|
response: 4,
|
|
},
|
|
{
|
|
id: 'question-2',
|
|
question: 'Any additional comments?',
|
|
response: 'Great service!',
|
|
},
|
|
],
|
|
'$survey_response_question-1': 4,
|
|
'$survey_response_question-2': 'Great service!',
|
|
$survey_response: 4,
|
|
$survey_response_1: 'Great service!',
|
|
$set: {
|
|
'$survey_responded/test-survey-id/2': true,
|
|
},
|
|
})
|
|
})
|
|
|
|
it('should not include iteration properties when survey has no current_iteration', () => {
|
|
const survey = createMockSurvey({
|
|
current_iteration: undefined,
|
|
current_iteration_start_date: undefined,
|
|
})
|
|
const responses = { '$survey_response_question-1': 3 }
|
|
|
|
sendSurveyEvent(responses, survey, mockInsights)
|
|
|
|
const captureCall = mockInsights.capture.mock.calls[0][1]
|
|
expect(captureCall).not.toHaveProperty('$survey_iteration')
|
|
expect(captureCall).not.toHaveProperty('$survey_iteration_start_date')
|
|
expect(captureCall.$set).toEqual({
|
|
'$survey_responded/test-survey-id': true,
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('response handling', () => {
|
|
it('should handle string responses', () => {
|
|
const survey = createMockSurvey()
|
|
const responses = {
|
|
'$survey_response_question-2': 'This is a text response',
|
|
}
|
|
|
|
sendSurveyEvent(responses, survey, mockInsights)
|
|
|
|
const captureCall = mockInsights.capture.mock.calls[0][1]
|
|
expect(captureCall.$survey_questions[1].response).toBe('This is a text response')
|
|
})
|
|
|
|
it('should handle numeric responses', () => {
|
|
const survey = createMockSurvey()
|
|
const responses = {
|
|
'$survey_response_question-1': 5,
|
|
}
|
|
|
|
sendSurveyEvent(responses, survey, mockInsights)
|
|
|
|
const captureCall = mockInsights.capture.mock.calls[0][1]
|
|
expect(captureCall.$survey_questions[0].response).toBe(5)
|
|
})
|
|
|
|
it('should handle array responses by creating a copy', () => {
|
|
const survey = createMockSurvey({
|
|
questions: [
|
|
{
|
|
id: 'question-multi',
|
|
question: 'Select all that apply',
|
|
type: 'multiple_choice',
|
|
choices: ['Option 1', 'Option 2', 'Option 3'],
|
|
} as SurveyQuestion,
|
|
],
|
|
})
|
|
const originalArray = ['Option 1', 'Option 3']
|
|
const responses = {
|
|
'$survey_response_question-multi': originalArray,
|
|
}
|
|
|
|
sendSurveyEvent(responses, survey, mockInsights)
|
|
|
|
const captureCall = mockInsights.capture.mock.calls[0][1]
|
|
const responseArray = captureCall.$survey_questions[0].response
|
|
|
|
expect(responseArray).toEqual(['Option 1', 'Option 3'])
|
|
expect(responseArray).not.toBe(originalArray) // Should be a copy, not the same reference
|
|
})
|
|
|
|
it('should handle null responses', () => {
|
|
const survey = createMockSurvey()
|
|
const responses = {
|
|
'$survey_response_question-1': null,
|
|
}
|
|
|
|
sendSurveyEvent(responses, survey, mockInsights)
|
|
|
|
const captureCall = mockInsights.capture.mock.calls[0][1]
|
|
expect(captureCall.$survey_questions[0].response).toBeNull()
|
|
})
|
|
|
|
it('should return null for questions without responses', () => {
|
|
const survey = createMockSurvey()
|
|
const responses = {
|
|
'$survey_response_question-1': 4,
|
|
// question-2 has no response
|
|
}
|
|
|
|
sendSurveyEvent(responses, survey, mockInsights)
|
|
|
|
const captureCall = mockInsights.capture.mock.calls[0][1]
|
|
expect(captureCall.$survey_questions[0].response).toBe(4)
|
|
expect(captureCall.$survey_questions[1].response).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe('survey interaction property', () => {
|
|
it('should generate correct interaction property without iteration', () => {
|
|
const survey = createMockSurvey()
|
|
const responses = {}
|
|
|
|
sendSurveyEvent(responses, survey, mockInsights)
|
|
|
|
const captureCall = mockInsights.capture.mock.calls[0][1]
|
|
expect(captureCall.$set).toEqual({
|
|
'$survey_responded/test-survey-id': true,
|
|
})
|
|
})
|
|
|
|
it('should generate correct interaction property with iteration', () => {
|
|
const survey = createMockSurvey({
|
|
current_iteration: 3,
|
|
})
|
|
const responses = {}
|
|
|
|
sendSurveyEvent(responses, survey, mockInsights)
|
|
|
|
const captureCall = mockInsights.capture.mock.calls[0][1]
|
|
expect(captureCall.$set).toEqual({
|
|
'$survey_responded/test-survey-id/3': true,
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('complex scenarios', () => {
|
|
it('should handle survey with mixed question types and responses', () => {
|
|
const survey = createMockSurvey({
|
|
questions: [
|
|
{
|
|
id: 'rating-q',
|
|
question: 'Rate us',
|
|
type: 'rating',
|
|
scale: 10,
|
|
} as SurveyQuestion,
|
|
{
|
|
id: 'multi-q',
|
|
question: 'Select options',
|
|
type: 'multiple_choice',
|
|
choices: ['A', 'B', 'C'],
|
|
} as SurveyQuestion,
|
|
{
|
|
id: 'text-q',
|
|
question: 'Comments',
|
|
type: 'open',
|
|
} as SurveyQuestion,
|
|
],
|
|
})
|
|
const responses = {
|
|
'$survey_response_rating-q': 8,
|
|
'$survey_response_multi-q': ['A', 'C'],
|
|
'$survey_response_text-q': 'Good job!',
|
|
}
|
|
|
|
sendSurveyEvent(responses, survey, mockInsights)
|
|
|
|
const captureCall = mockInsights.capture.mock.calls[0][1]
|
|
expect(captureCall.$survey_questions).toEqual([
|
|
{
|
|
id: 'rating-q',
|
|
question: 'Rate us',
|
|
response: 8,
|
|
},
|
|
{
|
|
id: 'multi-q',
|
|
question: 'Select options',
|
|
response: ['A', 'C'],
|
|
},
|
|
{
|
|
id: 'text-q',
|
|
question: 'Comments',
|
|
response: 'Good job!',
|
|
},
|
|
])
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('dismissedSurveyEvent', () => {
|
|
let mockInsights: any
|
|
|
|
beforeEach(() => {
|
|
mockInsights = {
|
|
capture: jest.fn(),
|
|
}
|
|
})
|
|
|
|
it.each([
|
|
{
|
|
label: 'captures dismissal responses and marks partial completion when there are answers',
|
|
responses: {
|
|
'$survey_response_question-1': 4,
|
|
'$survey_response_question-2': 'Great service!',
|
|
},
|
|
expectedPayload: {
|
|
$survey_name: 'Test Survey',
|
|
$survey_id: 'test-survey-id',
|
|
$survey_partially_completed: true,
|
|
$survey_questions: [
|
|
{
|
|
id: 'question-1',
|
|
question: 'How satisfied are you?',
|
|
response: 4,
|
|
},
|
|
{
|
|
id: 'question-2',
|
|
question: 'Any additional comments?',
|
|
response: 'Great service!',
|
|
},
|
|
],
|
|
'$survey_response_question-1': 4,
|
|
'$survey_response_question-2': 'Great service!',
|
|
$survey_response: 4,
|
|
$survey_response_1: 'Great service!',
|
|
$set: {
|
|
'$survey_dismissed/test-survey-id': true,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
label: 'marks dismissal as not partially completed when no answers exist',
|
|
responses: {},
|
|
expectedPayload: {
|
|
$survey_name: 'Test Survey',
|
|
$survey_id: 'test-survey-id',
|
|
$survey_partially_completed: false,
|
|
$survey_questions: [
|
|
{
|
|
id: 'question-1',
|
|
question: 'How satisfied are you?',
|
|
response: undefined,
|
|
},
|
|
{
|
|
id: 'question-2',
|
|
question: 'Any additional comments?',
|
|
response: undefined,
|
|
},
|
|
],
|
|
$set: {
|
|
'$survey_dismissed/test-survey-id': true,
|
|
},
|
|
},
|
|
},
|
|
])('$label', ({ responses, expectedPayload }) => {
|
|
const survey = createMockSurvey()
|
|
|
|
dismissedSurveyEvent(survey, responses, mockInsights)
|
|
|
|
expect(mockInsights.capture).toHaveBeenCalledWith('survey dismissed', expectedPayload)
|
|
})
|
|
})
|