| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148 |
- import { render, waitFor } from '@testing-library/react'
- import { beforeEach, describe, expect, it, vi } from 'vitest'
- import { Telemetry } from './telemetry'
- const mocks = vi.hoisted(() => ({
- identify: vi.fn(),
- useUser: vi.fn(),
- useOrganizationsQuery: vi.fn(),
- }))
- vi.mock('common', async (importOriginal) => {
- const actual = await importOriginal<typeof import('common')>()
- return {
- ...actual,
- posthogClient: {
- identify: mocks.identify,
- },
- useUser: () => mocks.useUser(),
- PageTelemetry: () => null,
- }
- })
- vi.mock('ui-patterns/consent', () => ({
- useConsentToast: () => ({ hasAcceptedConsent: true }),
- }))
- vi.mock('@/data/organizations/organizations-query', () => ({
- useOrganizationsQuery: () => mocks.useOrganizationsQuery(),
- }))
- vi.mock('@/hooks/misc/useSelectedOrganization', () => ({
- useSelectedOrganizationQuery: () => ({ data: undefined }),
- }))
- vi.mock('@sentry/nextjs', () => ({
- setUser: vi.fn(),
- }))
- const USER_ID = 'user-abc-123'
- const CREATED_AT = '2026-05-14T22:30:00.000Z'
- const orgs = (count: number) =>
- Array.from({ length: count }, (_, i) => ({ id: i, slug: `org-${i}` }))
- describe('Telemetry — posthog identify mirroring', () => {
- beforeEach(() => {
- mocks.identify.mockReset()
- mocks.useUser.mockReset()
- mocks.useOrganizationsQuery.mockReset()
- })
- it('fires identify with both org_count and signup_timestamp when user and orgs are loaded', async () => {
- mocks.useUser.mockReturnValue({ id: USER_ID, created_at: CREATED_AT })
- mocks.useOrganizationsQuery.mockReturnValue({ data: orgs(1) })
- render(<Telemetry />)
- await waitFor(() => {
- expect(mocks.identify).toHaveBeenCalledWith(USER_ID, {
- org_count: 1,
- signup_timestamp: CREATED_AT,
- })
- })
- })
- it('omits signup_timestamp when created_at is missing', async () => {
- mocks.useUser.mockReturnValue({ id: USER_ID, created_at: undefined })
- mocks.useOrganizationsQuery.mockReturnValue({ data: orgs(1) })
- render(<Telemetry />)
- await waitFor(() => {
- expect(mocks.identify).toHaveBeenCalledWith(USER_ID, { org_count: 1 })
- })
- })
- it('dedupes when nothing has changed', async () => {
- mocks.useUser.mockReturnValue({ id: USER_ID, created_at: CREATED_AT })
- mocks.useOrganizationsQuery.mockReturnValue({ data: orgs(1) })
- const { rerender } = render(<Telemetry />)
- await waitFor(() => expect(mocks.identify).toHaveBeenCalledTimes(1))
- rerender(<Telemetry />)
- rerender(<Telemetry />)
- // Still only one identify — same user, same orgCount, same signupTimestamp.
- expect(mocks.identify).toHaveBeenCalledTimes(1)
- })
- it('re-fires identify when created_at arrives after the first effect run (CodeRabbit regression)', async () => {
- // Initial render: user.id is set, but created_at is still undefined (e.g. partial session parse).
- mocks.useUser.mockReturnValue({ id: USER_ID, created_at: undefined })
- mocks.useOrganizationsQuery.mockReturnValue({ data: orgs(1) })
- const { rerender } = render(<Telemetry />)
- await waitFor(() => expect(mocks.identify).toHaveBeenCalledTimes(1))
- expect(mocks.identify).toHaveBeenLastCalledWith(USER_ID, { org_count: 1 })
- // created_at lands later. The dedup ref must track signupTimestamp,
- // and the effect deps must include user.created_at, or the second
- // identify gets silently skipped — which is exactly the race we shipped
- // the original fix to close.
- mocks.useUser.mockReturnValue({ id: USER_ID, created_at: CREATED_AT })
- rerender(<Telemetry />)
- await waitFor(() => expect(mocks.identify).toHaveBeenCalledTimes(2))
- expect(mocks.identify).toHaveBeenLastCalledWith(USER_ID, {
- org_count: 1,
- signup_timestamp: CREATED_AT,
- })
- })
- it('re-fires identify when org_count changes', async () => {
- mocks.useUser.mockReturnValue({ id: USER_ID, created_at: CREATED_AT })
- mocks.useOrganizationsQuery.mockReturnValue({ data: orgs(1) })
- const { rerender } = render(<Telemetry />)
- await waitFor(() => expect(mocks.identify).toHaveBeenCalledTimes(1))
- mocks.useOrganizationsQuery.mockReturnValue({ data: orgs(2) })
- rerender(<Telemetry />)
- await waitFor(() => expect(mocks.identify).toHaveBeenCalledTimes(2))
- expect(mocks.identify).toHaveBeenLastCalledWith(USER_ID, {
- org_count: 2,
- signup_timestamp: CREATED_AT,
- })
- })
- it('does not fire identify when user or orgs are missing', async () => {
- // user missing
- mocks.useUser.mockReturnValue(null)
- mocks.useOrganizationsQuery.mockReturnValue({ data: orgs(1) })
- const first = render(<Telemetry />)
- await new Promise((r) => setTimeout(r, 10))
- expect(mocks.identify).not.toHaveBeenCalled()
- first.unmount()
- // user present but orgs not loaded
- mocks.useUser.mockReturnValue({ id: USER_ID, created_at: CREATED_AT })
- mocks.useOrganizationsQuery.mockReturnValue({ data: undefined })
- render(<Telemetry />)
- await new Promise((r) => setTimeout(r, 10))
- expect(mocks.identify).not.toHaveBeenCalled()
- })
- })
|