@servicetitan/journey 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/README.md +7 -0
  2. package/dist/__mocks__/test-runtime.d.ts +16 -0
  3. package/dist/__mocks__/test-runtime.d.ts.map +1 -0
  4. package/dist/__tests__/ambient.test.d.ts +2 -0
  5. package/dist/__tests__/ambient.test.d.ts.map +1 -0
  6. package/dist/__tests__/axios.test.d.ts +2 -0
  7. package/dist/__tests__/axios.test.d.ts.map +1 -0
  8. package/dist/__tests__/config.test.d.ts +2 -0
  9. package/dist/__tests__/config.test.d.ts.map +1 -0
  10. package/dist/__tests__/context.test.d.ts +2 -0
  11. package/dist/__tests__/context.test.d.ts.map +1 -0
  12. package/dist/__tests__/datadog.test.d.ts +2 -0
  13. package/dist/__tests__/datadog.test.d.ts.map +1 -0
  14. package/dist/__tests__/define-journey.test.d.ts +2 -0
  15. package/dist/__tests__/define-journey.test.d.ts.map +1 -0
  16. package/dist/__tests__/expose-app-journey.test.d.ts +2 -0
  17. package/dist/__tests__/expose-app-journey.test.d.ts.map +1 -0
  18. package/dist/__tests__/fetch.test.d.ts +2 -0
  19. package/dist/__tests__/fetch.test.d.ts.map +1 -0
  20. package/dist/__tests__/jquery.test.d.ts +2 -0
  21. package/dist/__tests__/jquery.test.d.ts.map +1 -0
  22. package/dist/__tests__/registry.test.d.ts +2 -0
  23. package/dist/__tests__/registry.test.d.ts.map +1 -0
  24. package/dist/__tests__/request-key.test.d.ts +2 -0
  25. package/dist/__tests__/request-key.test.d.ts.map +1 -0
  26. package/dist/__tests__/requests.test.d.ts +2 -0
  27. package/dist/__tests__/requests.test.d.ts.map +1 -0
  28. package/dist/__tests__/runtime.test.d.ts +2 -0
  29. package/dist/__tests__/runtime.test.d.ts.map +1 -0
  30. package/dist/__tests__/scope.test.d.ts +2 -0
  31. package/dist/__tests__/scope.test.d.ts.map +1 -0
  32. package/dist/__tests__/sinks.test.d.ts +2 -0
  33. package/dist/__tests__/sinks.test.d.ts.map +1 -0
  34. package/dist/__tests__/step.test.d.ts +2 -0
  35. package/dist/__tests__/step.test.d.ts.map +1 -0
  36. package/dist/__tests__/timeouts.test.d.ts +2 -0
  37. package/dist/__tests__/timeouts.test.d.ts.map +1 -0
  38. package/dist/config.d.ts +42 -0
  39. package/dist/config.d.ts.map +1 -0
  40. package/dist/config.js +46 -0
  41. package/dist/config.js.map +1 -0
  42. package/dist/core/index.d.ts +6 -0
  43. package/dist/core/index.d.ts.map +1 -0
  44. package/dist/core/index.js +5 -0
  45. package/dist/core/index.js.map +1 -0
  46. package/dist/core/registry.d.ts +50 -0
  47. package/dist/core/registry.d.ts.map +1 -0
  48. package/dist/core/registry.js +102 -0
  49. package/dist/core/registry.js.map +1 -0
  50. package/dist/core/runtime.d.ts +51 -0
  51. package/dist/core/runtime.d.ts.map +1 -0
  52. package/dist/core/runtime.js +401 -0
  53. package/dist/core/runtime.js.map +1 -0
  54. package/dist/core/step-tag.d.ts +16 -0
  55. package/dist/core/step-tag.d.ts.map +1 -0
  56. package/dist/core/step-tag.js +35 -0
  57. package/dist/core/step-tag.js.map +1 -0
  58. package/dist/core/types.d.ts +143 -0
  59. package/dist/core/types.d.ts.map +1 -0
  60. package/dist/core/types.js +3 -0
  61. package/dist/core/types.js.map +1 -0
  62. package/dist/endpoint-policy.d.ts +29 -0
  63. package/dist/endpoint-policy.d.ts.map +1 -0
  64. package/dist/endpoint-policy.js +39 -0
  65. package/dist/endpoint-policy.js.map +1 -0
  66. package/dist/index.d.ts +15 -0
  67. package/dist/index.d.ts.map +1 -0
  68. package/dist/index.js +19 -0
  69. package/dist/index.js.map +1 -0
  70. package/dist/integrations/axios.d.ts +23 -0
  71. package/dist/integrations/axios.d.ts.map +1 -0
  72. package/dist/integrations/axios.js +51 -0
  73. package/dist/integrations/axios.js.map +1 -0
  74. package/dist/integrations/fetch.d.ts +21 -0
  75. package/dist/integrations/fetch.d.ts.map +1 -0
  76. package/dist/integrations/fetch.js +63 -0
  77. package/dist/integrations/fetch.js.map +1 -0
  78. package/dist/integrations/jquery/index.d.ts +22 -0
  79. package/dist/integrations/jquery/index.d.ts.map +1 -0
  80. package/dist/integrations/jquery/index.js +27 -0
  81. package/dist/integrations/jquery/index.js.map +1 -0
  82. package/dist/integrations/jquery/instrument.d.ts +18 -0
  83. package/dist/integrations/jquery/instrument.d.ts.map +1 -0
  84. package/dist/integrations/jquery/instrument.js +44 -0
  85. package/dist/integrations/jquery/instrument.js.map +1 -0
  86. package/dist/integrations/react/context.d.ts +6 -0
  87. package/dist/integrations/react/context.d.ts.map +1 -0
  88. package/dist/integrations/react/context.js +14 -0
  89. package/dist/integrations/react/context.js.map +1 -0
  90. package/dist/integrations/react/scope.d.ts +13 -0
  91. package/dist/integrations/react/scope.d.ts.map +1 -0
  92. package/dist/integrations/react/scope.js +27 -0
  93. package/dist/integrations/react/scope.js.map +1 -0
  94. package/dist/integrations/request-key.d.ts +17 -0
  95. package/dist/integrations/request-key.d.ts.map +1 -0
  96. package/dist/integrations/request-key.js +39 -0
  97. package/dist/integrations/request-key.js.map +1 -0
  98. package/dist/sinks/datadog.d.ts +6 -0
  99. package/dist/sinks/datadog.d.ts.map +1 -0
  100. package/dist/sinks/datadog.js +64 -0
  101. package/dist/sinks/datadog.js.map +1 -0
  102. package/package.json +44 -0
  103. package/src/__mocks__/test-runtime.ts +37 -0
  104. package/src/__tests__/ambient.test.ts +101 -0
  105. package/src/__tests__/axios.test.ts +365 -0
  106. package/src/__tests__/config.test.ts +261 -0
  107. package/src/__tests__/context.test.tsx +84 -0
  108. package/src/__tests__/datadog.test.ts +160 -0
  109. package/src/__tests__/define-journey.test.ts +176 -0
  110. package/src/__tests__/expose-app-journey.test.ts +149 -0
  111. package/src/__tests__/fetch.test.ts +264 -0
  112. package/src/__tests__/jquery.test.ts +327 -0
  113. package/src/__tests__/registry.test.ts +265 -0
  114. package/src/__tests__/request-key.test.ts +81 -0
  115. package/src/__tests__/requests.test.ts +337 -0
  116. package/src/__tests__/runtime.test.ts +273 -0
  117. package/src/__tests__/scope.test.tsx +128 -0
  118. package/src/__tests__/sinks.test.ts +99 -0
  119. package/src/__tests__/step.test.ts +305 -0
  120. package/src/__tests__/timeouts.test.ts +226 -0
  121. package/src/config.ts +87 -0
  122. package/src/core/index.ts +39 -0
  123. package/src/core/registry.ts +162 -0
  124. package/src/core/runtime.ts +489 -0
  125. package/src/core/step-tag.ts +24 -0
  126. package/src/core/types.ts +160 -0
  127. package/src/endpoint-policy.ts +66 -0
  128. package/src/index.ts +51 -0
  129. package/src/integrations/axios.ts +85 -0
  130. package/src/integrations/fetch.ts +97 -0
  131. package/src/integrations/jquery/index.ts +62 -0
  132. package/src/integrations/jquery/instrument.ts +84 -0
  133. package/src/integrations/react/context.ts +17 -0
  134. package/src/integrations/react/scope.tsx +32 -0
  135. package/src/integrations/request-key.ts +46 -0
  136. package/src/sinks/datadog.ts +71 -0
@@ -0,0 +1,273 @@
1
+ /** @jest-environment jsdom */
2
+ import { lastJourney, useJourneyTestRuntime } from '../__mocks__/test-runtime';
3
+ import { configureJourney, getRequestTimeouts } from '../config';
4
+ import {
5
+ activeJourneyStep,
6
+ completeJourney,
7
+ createJourneyRuntime,
8
+ defineJourney,
9
+ journeyStep,
10
+ resetJourney,
11
+ startJourney,
12
+ } from '../core';
13
+ import { sendToDatadog } from '../sinks/datadog';
14
+
15
+ jest.mock('../sinks/datadog', () => ({
16
+ sendToDatadog: jest.fn(),
17
+ }));
18
+
19
+ const def = {
20
+ name: 'runtime-journey',
21
+ team: 't',
22
+ group: 'g',
23
+ service: 's',
24
+ timeoutMs: 0,
25
+ };
26
+
27
+ describe('[journey] resetJourney', () => {
28
+ const emitMock = jest.fn();
29
+
30
+ useJourneyTestRuntime(emitMock);
31
+
32
+ describe('when a journey is open', () => {
33
+ beforeEach(() => {
34
+ startJourney(def);
35
+ });
36
+
37
+ const subject = () => resetJourney({ sinks: [emitMock] });
38
+
39
+ test('clears it without emitting', () => {
40
+ subject();
41
+
42
+ expect(emitMock).not.toHaveBeenCalled();
43
+ });
44
+
45
+ test('makes later complete a no-op', () => {
46
+ subject();
47
+ completeJourney(def.name);
48
+
49
+ expect(emitMock).not.toHaveBeenCalled();
50
+ });
51
+ });
52
+
53
+ describe('when request timeouts were customized', () => {
54
+ beforeEach(() => {
55
+ configureJourney({ requestTimeouts: { defaultMs: 100, endpoints: [] } });
56
+ });
57
+
58
+ const subject = () => resetJourney({ sinks: [emitMock] });
59
+
60
+ test('restores the 4s default', () => {
61
+ subject();
62
+
63
+ expect(getRequestTimeouts().defaultMs).toBe(4_000);
64
+ });
65
+ });
66
+
67
+ describe('when reset passes custom requestTimeouts', () => {
68
+ const subject = () =>
69
+ resetJourney({
70
+ sinks: [emitMock],
71
+ requestTimeouts: { defaultMs: 250, endpoints: [] },
72
+ });
73
+
74
+ test('applies those timeouts', () => {
75
+ subject();
76
+
77
+ expect(getRequestTimeouts().defaultMs).toBe(250);
78
+ });
79
+ });
80
+
81
+ describe('when reset is called with no options', () => {
82
+ beforeEach(() => {
83
+ resetJourney({ sinks: [emitMock] });
84
+ emitMock.mockClear();
85
+ startJourney(def);
86
+ completeJourney(def.name);
87
+ emitMock.mockClear();
88
+ });
89
+
90
+ const subject = () => resetJourney();
91
+
92
+ test('leaves sinks empty', () => {
93
+ subject();
94
+ startJourney(def);
95
+ completeJourney(def.name);
96
+
97
+ expect(emitMock).not.toHaveBeenCalled();
98
+ });
99
+ });
100
+
101
+ describe('when a step is in flight', () => {
102
+ let resolveWork!: () => void;
103
+ let stepPromise: Promise<unknown>;
104
+
105
+ beforeEach(() => {
106
+ startJourney(def);
107
+ const hang = new Promise<void>(resolve => {
108
+ resolveWork = resolve;
109
+ });
110
+ stepPromise = journeyStep(def.name, 'slow', async () => {
111
+ await hang;
112
+ });
113
+ });
114
+
115
+ afterEach(async () => {
116
+ resolveWork();
117
+ await stepPromise.catch(() => {});
118
+ });
119
+
120
+ const subject = () => resetJourney({ sinks: [emitMock] });
121
+
122
+ test('clears the ambient step', () => {
123
+ expect(activeJourneyStep()).not.toBeNull();
124
+
125
+ subject();
126
+
127
+ expect(activeJourneyStep()).toBeNull();
128
+ });
129
+ });
130
+
131
+ describe('when a journey countdown is armed', () => {
132
+ beforeEach(() => {
133
+ jest.useFakeTimers();
134
+ startJourney({ ...def, timeoutMs: 50 });
135
+ });
136
+
137
+ afterEach(() => {
138
+ jest.useRealTimers();
139
+ });
140
+
141
+ const subject = () => resetJourney({ sinks: [emitMock] });
142
+
143
+ test('prevents the timer from emitting', async () => {
144
+ subject();
145
+ emitMock.mockClear();
146
+
147
+ await jest.advanceTimersByTimeAsync(50);
148
+
149
+ expect(emitMock).not.toHaveBeenCalled();
150
+ });
151
+ });
152
+ });
153
+
154
+ describe('[journey] createJourneyRuntime', () => {
155
+ const defaultEmit = jest.fn();
156
+ const isolatedEmit = jest.fn();
157
+ let runtime: ReturnType<typeof createJourneyRuntime>;
158
+
159
+ beforeEach(() => {
160
+ resetJourney({
161
+ sinks: [defaultEmit],
162
+ requestTimeouts: { defaultMs: 4_000, endpoints: [] },
163
+ });
164
+ defaultEmit.mockClear();
165
+ isolatedEmit.mockClear();
166
+ runtime = createJourneyRuntime({ sinks: [isolatedEmit] });
167
+ });
168
+
169
+ afterEach(() => {
170
+ resetJourney({
171
+ sinks: [sendToDatadog],
172
+ requestTimeouts: { defaultMs: 4_000, endpoints: [] },
173
+ });
174
+ });
175
+
176
+ describe('when both runtimes open a journey', () => {
177
+ beforeEach(() => {
178
+ runtime.startJourney(def);
179
+ startJourney({ ...def, name: 'default-side' });
180
+ });
181
+
182
+ const subject = () => {
183
+ runtime.completeJourney(def.name);
184
+ completeJourney('default-side');
185
+ };
186
+
187
+ test('emits only to the matching sink', () => {
188
+ subject();
189
+
190
+ expect(isolatedEmit.mock.calls[0][0].journey.name).toBe(def.name);
191
+ expect(defaultEmit.mock.calls[0][0].journey.name).toBe('default-side');
192
+ });
193
+ });
194
+
195
+ describe('when the isolated runtime has its own requestTimeouts', () => {
196
+ beforeEach(() => {
197
+ runtime = createJourneyRuntime({
198
+ sinks: [isolatedEmit],
199
+ requestTimeouts: { defaultMs: 10, endpoints: [] },
200
+ });
201
+ configureJourney({ requestTimeouts: { defaultMs: 9_000, endpoints: [] } });
202
+ });
203
+
204
+ test('keeps timeouts separate from the default engine', () => {
205
+ expect(runtime.getRequestTimeouts().defaultMs).toBe(10);
206
+ expect(getRequestTimeouts().defaultMs).toBe(9_000);
207
+ });
208
+ });
209
+
210
+ describe('when the isolated runtime is reset', () => {
211
+ beforeEach(() => {
212
+ runtime.startJourney(def);
213
+ startJourney({ ...def, name: 'default-side' });
214
+ });
215
+
216
+ const subject = () => runtime.reset({ sinks: [isolatedEmit] });
217
+
218
+ test('does not clear the default engine', () => {
219
+ subject();
220
+ completeJourney('default-side');
221
+
222
+ expect(defaultEmit).toHaveBeenCalledTimes(1);
223
+ expect(lastJourney(defaultEmit).name).toBe('default-side');
224
+ });
225
+ });
226
+
227
+ describe('when defineJourney is used', () => {
228
+ const subject = () =>
229
+ defineJourney(def).step('load', step => {
230
+ step.complete();
231
+ });
232
+
233
+ test('binds to the default runtime', async () => {
234
+ await subject();
235
+
236
+ expect(defaultEmit).toHaveBeenCalledTimes(1);
237
+ expect(lastJourney(defaultEmit).name).toBe(def.name);
238
+ expect(isolatedEmit).not.toHaveBeenCalled();
239
+ });
240
+ });
241
+
242
+ describe('when a throwing sink is registered on the isolated runtime', () => {
243
+ let second: jest.Mock;
244
+
245
+ beforeEach(() => {
246
+ second = jest.fn();
247
+ runtime.setSinks([
248
+ () => {
249
+ throw new Error('boom');
250
+ },
251
+ second,
252
+ ]);
253
+ runtime.startJourney(def);
254
+ });
255
+
256
+ const subject = () => runtime.completeJourney(def.name);
257
+
258
+ test('still invokes later sinks', () => {
259
+ expect(() => subject()).not.toThrow();
260
+ expect(second).toHaveBeenCalledTimes(1);
261
+ });
262
+ });
263
+
264
+ describe('with create-time options', () => {
265
+ test('installs the initial sinks', () => {
266
+ const created = createJourneyRuntime({ sinks: [isolatedEmit] });
267
+ created.startJourney(def);
268
+ created.completeJourney(def.name);
269
+
270
+ expect(isolatedEmit).toHaveBeenCalledTimes(1);
271
+ });
272
+ });
273
+ });
@@ -0,0 +1,128 @@
1
+ /** @jest-environment jsdom */
2
+ import { render, screen } from '@testing-library/react';
3
+ import { useContext } from 'react';
4
+
5
+ import { useJourneyTestRuntime } from '../__mocks__/test-runtime';
6
+ import { completeJourney, failJourney } from '../core';
7
+ import { JourneyNameContext } from '../integrations/react/context';
8
+ import { JourneyScope } from '../integrations/react/scope';
9
+
10
+ const emitMock = jest.fn();
11
+
12
+ const def = {
13
+ name: 'scope-journey',
14
+ team: 'platform',
15
+ group: 'test',
16
+ service: 'journey',
17
+ timeoutMs: 0,
18
+ };
19
+
20
+ function NameProbe() {
21
+ const name = useContext(JourneyNameContext);
22
+ return <span data-testid="journey-name">{name}</span>;
23
+ }
24
+
25
+ describe('[journey] JourneyScope', () => {
26
+ useJourneyTestRuntime(emitMock);
27
+
28
+ test('opens a journey on mount and publishes the name via context', () => {
29
+ render(
30
+ <JourneyScope {...def}>
31
+ <NameProbe />
32
+ </JourneyScope>
33
+ );
34
+
35
+ expect(screen.getByTestId('journey-name').textContent).toBe(def.name);
36
+ completeJourney(def.name);
37
+ expect(emitMock).toHaveBeenCalledTimes(1);
38
+ expect(emitMock.mock.calls[0][0].journey.outcome).toBe('good');
39
+ });
40
+
41
+ test('applies tags from props', () => {
42
+ render(
43
+ <JourneyScope {...def} tags={{ plan: 'pro' }}>
44
+ <NameProbe />
45
+ </JourneyScope>
46
+ );
47
+
48
+ completeJourney(def.name);
49
+ expect(emitMock.mock.calls[0][0].journey.tags).toEqual({ plan: 'pro' });
50
+ });
51
+
52
+ test('excludes the journey on unmount', () => {
53
+ const { unmount } = render(
54
+ <JourneyScope {...def}>
55
+ <NameProbe />
56
+ </JourneyScope>
57
+ );
58
+
59
+ unmount();
60
+
61
+ expect(emitMock).toHaveBeenCalledTimes(1);
62
+ expect(emitMock.mock.calls[0][0].journey.outcome).toBe('excluded');
63
+ });
64
+
65
+ describe('when the journey already completed before unmount', () => {
66
+ test('unmount does not emit again', () => {
67
+ const { unmount } = render(
68
+ <JourneyScope {...def}>
69
+ <NameProbe />
70
+ </JourneyScope>
71
+ );
72
+
73
+ completeJourney(def.name);
74
+ expect(emitMock).toHaveBeenCalledTimes(1);
75
+ expect(emitMock.mock.calls[0][0].journey.outcome).toBe('good');
76
+
77
+ emitMock.mockClear();
78
+ unmount();
79
+
80
+ expect(emitMock).not.toHaveBeenCalled();
81
+ });
82
+ });
83
+
84
+ describe('when the name prop changes', () => {
85
+ const next = { ...def, name: 'scope-journey-next' };
86
+
87
+ test('excludes the prior journey and opens the new one', () => {
88
+ const { rerender } = render(
89
+ <JourneyScope {...def}>
90
+ <NameProbe />
91
+ </JourneyScope>
92
+ );
93
+
94
+ rerender(
95
+ <JourneyScope {...next}>
96
+ <NameProbe />
97
+ </JourneyScope>
98
+ );
99
+
100
+ expect(screen.getByTestId('journey-name').textContent).toBe(next.name);
101
+ expect(emitMock).toHaveBeenCalledTimes(1);
102
+ expect(emitMock.mock.calls[0][0].journey.name).toBe(def.name);
103
+ expect(emitMock.mock.calls[0][0].journey.outcome).toBe('excluded');
104
+
105
+ completeJourney(next.name);
106
+ expect(emitMock.mock.calls[1][0].journey.name).toBe(next.name);
107
+ expect(emitMock.mock.calls[1][0].journey.outcome).toBe('good');
108
+ });
109
+ });
110
+
111
+ describe('when a breach already occurred before unmount', () => {
112
+ test('unmount stays bad (breach-anchored)', () => {
113
+ const { unmount } = render(
114
+ <JourneyScope {...def}>
115
+ <NameProbe />
116
+ </JourneyScope>
117
+ );
118
+
119
+ failJourney(def.name, 'validation');
120
+ expect(emitMock.mock.calls[0][0].journey.outcome).toBe('bad');
121
+
122
+ emitMock.mockClear();
123
+ unmount();
124
+
125
+ expect(emitMock).not.toHaveBeenCalled();
126
+ });
127
+ });
128
+ });
@@ -0,0 +1,99 @@
1
+ /** @jest-environment jsdom */
2
+ jest.mock('../sinks/datadog', () => ({
3
+ sendToDatadog: jest.fn(),
4
+ }));
5
+
6
+ import { resetJourney } from '../core';
7
+ import { addJourneySink, emitJourneyEvent, setJourneySinks } from '../core/registry';
8
+ import type { JourneyEvent } from '../core/types';
9
+ import { sendToDatadog } from '../sinks/datadog';
10
+
11
+ describe('[journey] emitJourneyEvent', () => {
12
+ let payload: JourneyEvent;
13
+
14
+ beforeEach(() => {
15
+ jest.mocked(sendToDatadog).mockClear();
16
+ resetJourney({ sinks: [sendToDatadog] });
17
+ payload = {
18
+ journey: {
19
+ name: 'test',
20
+ team: 't',
21
+ group: 'g',
22
+ service: 's',
23
+ outcome: 'good',
24
+ durationMs: 1,
25
+ steps: [],
26
+ },
27
+ };
28
+ });
29
+
30
+ afterEach(() => {
31
+ resetJourney({ sinks: [sendToDatadog] });
32
+ });
33
+
34
+ const subject = () => emitJourneyEvent(payload);
35
+
36
+ test('forwards the payload to each configured sink', () => {
37
+ subject();
38
+
39
+ expect(sendToDatadog).toHaveBeenCalledWith(payload);
40
+ });
41
+
42
+ describe('with an additional sink', () => {
43
+ let extra: jest.Mock;
44
+
45
+ beforeEach(() => {
46
+ extra = jest.fn();
47
+ addJourneySink(extra);
48
+ });
49
+
50
+ test('invokes every configured sink', () => {
51
+ subject();
52
+
53
+ expect(sendToDatadog).toHaveBeenCalledWith(payload);
54
+ expect(extra).toHaveBeenCalledWith(payload);
55
+ });
56
+
57
+ test('continues to later sinks when one throws', () => {
58
+ setJourneySinks([
59
+ () => {
60
+ throw new Error('boom');
61
+ },
62
+ sendToDatadog,
63
+ extra,
64
+ ]);
65
+
66
+ expect(() => subject()).not.toThrow();
67
+ expect(sendToDatadog).toHaveBeenCalledWith(payload);
68
+ expect(extra).toHaveBeenCalledWith(payload);
69
+ });
70
+ });
71
+
72
+ describe('when sinks are replaced', () => {
73
+ let only: jest.Mock;
74
+
75
+ beforeEach(() => {
76
+ only = jest.fn();
77
+ setJourneySinks([only]);
78
+ });
79
+
80
+ test('does not call the previous sinks', () => {
81
+ subject();
82
+
83
+ expect(only).toHaveBeenCalledWith(payload);
84
+ expect(sendToDatadog).not.toHaveBeenCalled();
85
+ });
86
+ });
87
+
88
+ describe('with an empty sink list', () => {
89
+ beforeEach(() => {
90
+ setJourneySinks([]);
91
+ });
92
+
93
+ test('emits nowhere', () => {
94
+ subject();
95
+
96
+ expect(sendToDatadog).not.toHaveBeenCalled();
97
+ });
98
+ });
99
+ });