/** @jest-environment jsdom */ import { lastJourney, useJourneyTestRuntime } from '../../__test-utils__/test-runtime'; import { defineJourney, journeyStep } from '../../core'; import { moduleRuntime } from '../../core/runtime'; const emitMock = jest.fn(); const base = { name: 'handle-api', team: 'platform', group: 'test', service: 'journey', timeoutMs: 0, }; describe('[journey] defineJourney', () => { let journey = defineJourney(base); useJourneyTestRuntime(emitMock); beforeEach(() => { journey = defineJourney(base); }); describe('when no journey is open', () => { const subject = () => journey.step('do-work', step => { step.setAttribute('x', 1); step.completeJourney({ 'payment.id': 42 }); }); test('opens, records the step, and completes good', async () => { await subject(); expect(emitMock).toHaveBeenCalledTimes(1); const event = lastJourney(emitMock); expect(event.outcome).toBe('good'); expect(event.tags?.['payment.id']).toBe(42); expect(event.steps[0].attributes).toEqual({ x: 1 }); }); test('mountStep also opens the journey', async () => { await journey.mountStep('boot', step => { step.completeJourney(); }); expect(emitMock).toHaveBeenCalledTimes(1); expect(lastJourney(emitMock).outcome).toBe('good'); expect(lastJourney(emitMock).steps[0].name).toBe('boot'); }); }); describe('when a JourneyScope already opened the journey', () => { beforeEach(() => { moduleRuntime.startJourney(base); }); const subject = () => journey.step('do-work', step => { step.completeJourney(); }); test('does not restart the open journey', async () => { await subject(); expect(emitMock).toHaveBeenCalledTimes(1); expect(lastJourney(emitMock).outcome).toBe('good'); expect(lastJourney(emitMock).steps).toHaveLength(1); }); }); describe('when stepping via free journeyStep with a handle', () => { beforeEach(() => { journey.start(); }); const subject = () => journeyStep(journey, 'via-free', step => { step.completeJourney(); }); test('resolves the handle name', async () => { await subject(); expect(lastJourney(emitMock).steps[0].name).toBe('via-free'); }); }); describe('when using bound lifecycle helpers', () => { test('fail marks bad', () => { journey.start(); journey.fail('no-inventory'); expect(lastJourney(emitMock).outcome).toBe('bad'); expect(lastJourney(emitMock).reason).toBe('no-inventory'); }); test('exclude marks excluded', () => { journey.start(); journey.exclude(); expect(lastJourney(emitMock).outcome).toBe('excluded'); }); test('setTags merges onto the open journey', () => { journey.start(); journey.setTags({ plan: 'pro' }); journey.complete(); expect(lastJourney(emitMock).tags?.plan).toBe('pro'); }); test('start with tags applies them', () => { journey.start({ cohort: 'a' }); journey.complete(); expect(lastJourney(emitMock).tags?.cohort).toBe('a'); }); test('complete with tags applies them', () => { journey.start(); journey.complete({ done: true }); expect(lastJourney(emitMock).tags?.done).toBe(true); }); test('start restarts an already-open journey', () => { journey.start(); journey.start(); expect(emitMock).toHaveBeenCalledTimes(1); expect(lastJourney(emitMock).outcome).toBe('excluded'); journey.complete(); expect(lastJourney(emitMock).outcome).toBe('good'); }); test('complete after fail does not flip the outcome', () => { journey.start(); journey.fail('validation'); journey.complete({ ignored: true }); expect(emitMock).toHaveBeenCalledTimes(1); expect(lastJourney(emitMock).outcome).toBe('bad'); expect(lastJourney(emitMock).reason).toBe('validation'); }); }); describe('when mountStep runs under an already-open journey', () => { beforeEach(() => { moduleRuntime.startJourney(base); }); const subject = () => journey.mountStep('boot', step => { step.completeJourney(); }); test('does not restart the open journey', async () => { await subject(); expect(emitMock).toHaveBeenCalledTimes(1); expect(lastJourney(emitMock).outcome).toBe('good'); expect(lastJourney(emitMock).steps).toHaveLength(1); expect(lastJourney(emitMock).steps[0].name).toBe('boot'); }); }); describe('when keeping the original config on the handle', () => { test('exposes the same config object', () => { expect(journey.config).toBe(base); expect(journey.config.name).toBe(base.name); expect(journey.config.name).toBe(base.name); expect(journey.config.team).toBe(base.team); expect(journey.config.group).toBe(base.group); expect(journey.config.service).toBe(base.service); }); }); describe('when stepping via free journeyStep with a handle and nothing open', () => { const subject = () => journeyStep(journey, 'via-handle', step => { step.completeJourney(); }); test('auto-starts from the handle config', async () => { await subject(); expect(emitMock).toHaveBeenCalledTimes(1); expect(lastJourney(emitMock).outcome).toBe('good'); expect(lastJourney(emitMock).steps[0].name).toBe('via-handle'); }); }); });